feat: redesign for CurrencyPicker and FiatPicker, introduce recents and currency groupings (#3248)

* Redesign for CurrencyPicker and FiatPicker, introduce recents and currency groupings

* chore: cleanup

* refactor: switch recents to previously completed trades, specify stables to be displayed, fix scrollable and adjust badge

* refactor: switch recents to previously completed trades, specify stables to be displayed, fix scrollable, adjust badge, add fiat and multinetwork currency pickers to buy and sell flows

* fresh set of changes and fixes for new currency picker flow

* add chain label to items on search and smooth pop

* add chain pill and label to xstocks

* properly filter search results

* shorten chain pill name for BSC tokens

* Review fixes

* cleanups

* safe pop

---------

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
David Adegoke
2026-06-03 16:38:16 +02:00
committed by GitHub
co-authored by Omar Hatem
parent 4365462279
commit 8af85351e9
62 changed files with 3004 additions and 182 deletions
+37 -22
View File
@@ -4,7 +4,6 @@ import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/core/open_crypto_pay/open_cryptopay_service.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/contact_record.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/exchange/trade.dart';
@@ -13,6 +12,9 @@ import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/new-ui/modal_navigator.dart';
import 'package:cake_wallet/new-ui/pages/coin_control_page.dart';
import 'package:cake_wallet/new-ui/widgets/animated_dropdown.dart';
import 'package:cake_wallet/new-ui/widgets/currency_picker/currency_picker_args.dart';
import 'package:cake_wallet/new-ui/widgets/currency_picker/currency_picker_sheet.dart';
import 'package:cake_wallet/new-ui/widgets/currency_picker/fiat_currency_picker_sheet.dart';
import 'package:cake_wallet/new-ui/widgets/keyboard_hide_overlay.dart';
import 'package:cake_wallet/new-ui/widgets/picker.dart';
import 'package:cake_wallet/new-ui/widgets/send_page/fiat_amount_bar.dart';
@@ -36,7 +38,6 @@ import 'package:cake_wallet/new-ui/widgets/send_page/send_amount_input.dart';
import 'package:cake_wallet/new-ui/widgets/send_page/send_syncing_indicator.dart';
import 'package:cake_wallet/routes.dart' show Routes;
import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart';
import 'package:cake_wallet/src/widgets/bottom_sheet/payment_confirmation_bottom_sheet.dart';
@@ -51,7 +52,6 @@ import 'package:cake_wallet/view_model/send/send_view_model.dart';
import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
import 'package:cake_wallet/view_model/wallet_switcher_view_model.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/currency.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/utils/print_verbose.dart';
@@ -809,26 +809,41 @@ class _NewSendPageState extends State<NewSendPage> {
final output = widget.sendViewModel.outputs[_selectedOutput];
showPopUp<void>(
context: context,
builder: (_) => CurrencyPicker(
key: ValueKey('send_page_currency_picker_dialog_button_key'),
selectedAtIndex: _fiatInputMode
? widget.sendViewModel.fiatCurrencies.indexOf(widget.sendViewModel.fiatCurrency)
: widget.sendViewModel.currencies.indexOf(widget.sendViewModel.selectedCryptoCurrency),
items:
_fiatInputMode ? widget.sendViewModel.fiatCurrencies : widget.sendViewModel.currencies,
hintText: S.of(context).search_currency,
onItemSelected: (Currency cur) async {
late final selectedCurrency;
if (_fiatInputMode) {
selectedCurrency = widget.sendViewModel.setFiatCurrency(cur as FiatCurrency);
} else {
selectedCurrency =
widget.sendViewModel.selectedCryptoCurrency = (cur as CryptoCurrency);
}
if (_fiatInputMode) {
FiatCurrencyPickerSheet.show(
context: context,
selected: widget.sendViewModel.fiatCurrency,
onSelected: (cur) async {
widget.sendViewModel.setFiatCurrency(cur);
await output.calculateEstimatedFee();
return selectedCurrency;
},
);
return;
}
final isFiatDisabled = widget.sendViewModel.isFiatDisabled;
final balanceByAsset = <CryptoCurrency, CurrencyPickerBalance>{
for (final r in widget.sendViewModel.balanceViewModel.formattedBalances)
r.asset: CurrencyPickerBalance(
amount: '${r.availableBalance} ${r.asset.title}',
fiat: isFiatDisabled
? null
: '${r.fiatAvailableBalanceRaw} ${r.fiatCurrencyTicker}',
fiatValue: isFiatDisabled ? null : double.tryParse(r.fiatAvailableBalanceRaw),
),
};
CurrencyPickerSheet.show(
context: context,
args: CurrencyPickerArgs(
items: widget.sendViewModel.currencies,
selected: widget.sendViewModel.selectedCryptoCurrency,
filterByNetwork: widget.sendViewModel.walletType,
balanceByAsset: balanceByAsset,
symbolResolver: widget.sendViewModel.amountParsingProxy.getCryptoSymbol,
onSelected: (currency) {
widget.sendViewModel.selectedCryptoCurrency = currency;
output.calculateEstimatedFee();
},
),
);
+15 -14
View File
@@ -20,8 +20,9 @@ import 'package:cake_wallet/new-ui/widgets/swap_page/refund_address_modal.dart';
import 'package:cake_wallet/new-ui/widgets/swap_page/swap_address_selection_modal.dart';
import 'package:cake_wallet/new-ui/widgets/swap_page/swap_confirm_sheet.dart';
import 'package:cake_wallet/new-ui/widgets/swap_page/swap_limit_popup.dart';
import 'package:cake_wallet/new-ui/widgets/currency_picker/currency_picker_args.dart';
import 'package:cake_wallet/new-ui/widgets/currency_picker/currency_picker_sheet.dart';
import 'package:cake_wallet/new-ui/widgets/swap_page/swap_options_page.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/present_provider_picker.dart';
import 'package:cake_wallet/src/screens/send/widgets/extract_address_from_parsed.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
@@ -1213,9 +1214,11 @@ class SwapAmountBoxState extends State<SwapAmountBox> {
}
void _presentCurrencyPicker() {
final currencies = widget.isReceiverCard
final rawCurrencies = widget.isReceiverCard
? widget.exchangeViewModel.receiveCurrencies
: widget.exchangeViewModel.depositCurrencies;
final currencies = rawCurrencies.whereType<CryptoCurrency>().toList();
appendEvmDefaultTokens(currencies);
if (widget.exchangeViewModel.wallet.type == WalletType.bitcoin) {
currencies.sort((a, b) {
if (a == CryptoCurrency.btcln) return -1;
@@ -1224,20 +1227,18 @@ class SwapAmountBoxState extends State<SwapAmountBox> {
});
}
showPopUp<void>(
final selected = widget.isReceiverCard
? widget.exchangeViewModel.receiveCurrency
: widget.exchangeViewModel.depositCurrency;
CurrencyPickerSheet.show(
context: context,
builder: (_) => CurrencyPicker(
key: ValueKey('send_page_currency_picker_dialog_button_key'),
selectedAtIndex: currencies.indexOf(widget.isReceiverCard
? widget.exchangeViewModel.receiveCurrency
: widget.exchangeViewModel.depositCurrency),
args: CurrencyPickerArgs(
items: currencies,
hintText: S.of(context).search_currency,
onItemSelected: (Currency cur) async {
if (cur is CryptoCurrency) {
widget.onCurrencySelected(cur);
}
},
selected: selected,
recentsSource: RecentsSource.trades,
onSelected: widget.onCurrencySelected,
symbolResolver: widget.exchangeViewModel.amountParsingProxy.getCryptoSymbol,
),
);
}
@@ -0,0 +1,83 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
class ChainChipStrip extends StatelessWidget {
const ChainChipStrip({
super.key,
required this.walletTypes,
required this.selected,
required this.onSelected,
});
final List<WalletType> walletTypes;
final WalletType? selected;
final ValueChanged<WalletType?> onSelected;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.only(bottom: 4),
height: 48,
child: ListView(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 16),
children: [
_ChainChip(
label: S.of(context).picker_chip_all,
isSelected: selected == null,
onTap: () => onSelected(null),
),
for (final type in walletTypes)
_ChainChip(
label: walletTypeToString(type),
isSelected: selected == type,
onTap: () => onSelected(type),
),
],
),
);
}
}
class _ChainChip extends StatelessWidget {
const _ChainChip({
required this.label,
required this.isSelected,
required this.onTap,
});
final String label;
final bool isSelected;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
final colors = Theme.of(context).colorScheme;
return Padding(
padding: const EdgeInsetsDirectional.only(end: 8),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(80),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10),
decoration: BoxDecoration(
color: isSelected ? colors.primary : colors.surfaceContainer,
borderRadius: BorderRadius.circular(20),
),
child: Center(
child: Text(
label,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w500,
fontSize: isSelected ? 14 : 12,
letterSpacing: -0.07,
color: isSelected ? colors.onPrimary : colors.primary,
),
),
),
),
),
);
}
}
@@ -0,0 +1,77 @@
import 'package:cake_wallet/evm/evm.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
import 'package:cw_core/currency_groups.dart';
import 'package:cw_core/wallet_type.dart';
String chainNameForCurrency(CryptoCurrency c) {
if (c == CryptoCurrency.btcln) return 'Lightning';
final wt = cryptoCurrencyOrTokenToWalletType(c);
return wt != null ? walletTypeToString(wt) : (c.tag ?? '');
}
final Set<String> _stablecoinSymbols = {
for (final c in CryptoCurrency.all)
if (c.groups.contains(CurrencyGroups.stablecoin)) c.title.toUpperCase(),
};
bool isTrustedStablecoin(CryptoCurrency c) =>
(c.groups.contains(CurrencyGroups.stablecoin) ||
_stablecoinSymbols.contains(c.title.toUpperCase())) && !c.isPotentialScam;
const _kEvmDefaultTokenNatives = <CryptoCurrency>[
CryptoCurrency.baseEth,
CryptoCurrency.arbEth,
CryptoCurrency.maticpoly,
];
void appendEvmDefaultTokens(List<CryptoCurrency> into) {
if (evm == null) return;
String keyFor(CryptoCurrency c) {
final wt = cryptoCurrencyOrTokenToWalletType(c);
final chain = wt?.toString() ?? (c.tag ?? '').toUpperCase();
return '${c.title.toUpperCase()}|$chain';
}
final seen = <String>{for (final c in into) keyFor(c)};
for (final native in _kEvmDefaultTokenNatives) {
final chainId = getChainIdByCryptoCurrency(native);
if (chainId == null) continue;
for (final t in evm!.getDefaultTokensByChainId(chainId)) {
if (seen.add(keyFor(t))) into.add(t);
}
}
}
class CurrencyPickerBalance {
const CurrencyPickerBalance(
{required this.amount, this.fiat, this.fiatValue});
final String amount;
final String? fiat;
final double? fiatValue;
}
enum RecentsSource { none, trades, orders }
class CurrencyPickerArgs {
const CurrencyPickerArgs({
this.selected,
required this.items,
this.balanceByAsset,
this.filterByNetwork,
required this.onSelected,
required this.symbolResolver,
this.recentsSource = RecentsSource.none,
});
final CryptoCurrency? selected;
final List<CryptoCurrency> items;
final WalletType? filterByNetwork;
final void Function(CryptoCurrency) onSelected;
final String Function(CryptoCurrency) symbolResolver;
final Map<CryptoCurrency, CurrencyPickerBalance>? balanceByAsset;
final RecentsSource recentsSource;
bool get isPreFiltered => filterByNetwork != null;
}
@@ -0,0 +1,32 @@
import 'package:flutter/material.dart';
class CurrencyPickerListContainer extends StatelessWidget {
const CurrencyPickerListContainer({super.key, required this.rows});
final List<Widget> rows;
@override
Widget build(BuildContext context) {
final colors = Theme.of(context).colorScheme;
final children = <Widget>[];
for (var i = 0; i < rows.length; i++) {
children.add(rows[i]);
if (i != rows.length - 1) {
children.add(Divider(
height: 1,
thickness: 1,
color: colors.surfaceContainerHigh,
indent: 56,
endIndent: 24,
));
}
}
return Container(
decoration: BoxDecoration(
color: colors.surfaceContainer,
borderRadius: BorderRadius.circular(18),
),
child: Column(mainAxisSize: MainAxisSize.min, children: children),
);
}
}
@@ -0,0 +1,140 @@
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:flutter/material.dart';
class CurrencyPickerRow extends StatelessWidget {
const CurrencyPickerRow({
super.key,
required this.currency,
required this.isSelected,
required this.trailing,
required this.onTap,
this.chainPillLabel,
this.chainBadgePath,
});
final CryptoCurrency currency;
final bool isSelected;
final Widget trailing;
final VoidCallback onTap;
final String? chainPillLabel;
final String? chainBadgePath;
@override
Widget build(BuildContext context) {
final colors = Theme.of(context).colorScheme;
return InkWell(
onTap: onTap,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
child: Row(
children: [
_IconWithBadge(currency: currency, badgePath: chainBadgePath),
const SizedBox(width: 12),
Expanded(
child: Row(
children: [
Flexible(
child: Text(
currency.fullName ?? currency.title,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w500,
),
),
),
if (chainPillLabel != null) ...[
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: colors.surfaceContainerHigh,
borderRadius: BorderRadius.circular(10),
),
child: Text(
chainPillLabel!,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
fontWeight: FontWeight.w500,
color: colors.onSecondaryContainer,
),
),
),
],
],
),
),
const SizedBox(width: 8),
trailing,
const SizedBox(width: 8),
Icon(
Icons.chevron_right,
size: 20,
color: isSelected ? colors.primary : colors.onSurfaceVariant,
),
],
),
),
);
}
}
class _IconWithBadge extends StatelessWidget {
const _IconWithBadge({required this.currency, required this.badgePath});
final CryptoCurrency currency;
final String? badgePath;
@override
Widget build(BuildContext context) {
final icon = CakeImageWidget(
imageUrl: currency.iconPath,
width: 32,
height: 32,
fit: BoxFit.cover,
errorWidget: Container(
width: 32,
height: 32,
child: Center(
child: Text(
currency.title.length >= 2 ? currency.title.substring(0, 2) : currency.title,
),
),
),
);
if (badgePath == null) return icon;
return SizedBox(
width: 36,
height: 36,
child: Stack(
clipBehavior: Clip.none,
children: [
icon,
Positioned(
right: -2,
bottom: -2,
child: Container(
width: 18,
height: 18,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6),
border: Border.all(
color: Theme.of(context).colorScheme.surfaceContainerHigh,
width: 2,
),
color: Theme.of(context).colorScheme.onSurface,
),
padding: const EdgeInsets.all(2),
child: CakeImageWidget(
imageUrl: badgePath,
color: Theme.of(context).colorScheme.surface,
width: 13,
height: 13,
fit: BoxFit.cover,
),
),
),
],
),
);
}
}
@@ -0,0 +1,68 @@
import 'package:cw_core/crypto_currency.dart';
import 'package:flutter/material.dart';
class CurrencyPickerSearchField extends StatelessWidget {
const CurrencyPickerSearchField({
super.key,
required this.controller,
required this.hintText,
});
final TextEditingController controller;
final String hintText;
@override
Widget build(BuildContext context) {
final colors = Theme.of(context).colorScheme;
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Container(
height: 48,
padding: const EdgeInsets.symmetric(horizontal: 14),
decoration: BoxDecoration(
color: colors.surfaceContainer,
border: Border.all(color: colors.surfaceContainer, width: 1),
borderRadius: BorderRadius.circular(28),
),
child: Row(
children: [
Icon(Icons.search, size: 20, color: colors.primary),
const SizedBox(width: 10),
Expanded(
child: TextFormField(
controller: controller,
style: Theme.of(context).textTheme.bodyMedium,
decoration: InputDecoration(
isCollapsed: true,
border: InputBorder.none,
focusedBorder: InputBorder.none,
hintText: hintText,
hintStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: colors.onSurfaceVariant,
),
),
),
),
if (controller.text.isNotEmpty)
InkWell(
onTap: controller.clear,
customBorder: const CircleBorder(),
child: Padding(
padding: const EdgeInsets.all(4),
child: Icon(Icons.close, size: 16, color: colors.onSurfaceVariant),
),
),
],
),
),
);
}
}
bool currencyMatchesQuery(CryptoCurrency c, String query) {
if (query.isEmpty) return true;
final q = query.toLowerCase();
return c.title.toLowerCase().contains(q) ||
c.name.toLowerCase().contains(q) ||
(c.fullName?.toLowerCase().contains(q) ?? false);
}
@@ -0,0 +1,56 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/new-ui/widgets/currency_picker/currency_picker_args.dart';
import 'package:cake_wallet/new-ui/widgets/currency_picker/multi_network_currency_picker.dart';
import 'package:cake_wallet/new-ui/widgets/currency_picker/single_network_currency_picker.dart';
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
import 'package:flutter/material.dart';
class CurrencyPickerSheet extends StatelessWidget {
const CurrencyPickerSheet({super.key, required this.args});
final CurrencyPickerArgs args;
static Future<void> show({
required BuildContext context,
required CurrencyPickerArgs args,
}) {
return showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
useSafeArea: true,
enableDrag: false,
isDismissible: false,
backgroundColor: Colors.transparent,
builder: (_) => CurrencyPickerSheet(args: args),
);
}
@override
Widget build(BuildContext context) {
final colors = Theme.of(context).colorScheme;
return Padding(
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.vertical(top: Radius.circular(24)),
color: colors.surface,
),
child: Column(
mainAxisSize: MainAxisSize.max,
children: [
ModalTopBar(
title: S.of(context).select_asset,
leadingIcon: const Icon(Icons.close),
onLeadingPressed: () => Navigator.of(context).maybePop(),
),
Expanded(
child: args.isPreFiltered
? SingleNetworkCurrencyPicker(args: args)
: MultiNetworkCurrencyPicker(args: args),
),
],
),
),
);
}
}
@@ -0,0 +1,314 @@
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/new-ui/widgets/currency_picker/currency_picker_list_container.dart';
import 'package:cake_wallet/new-ui/widgets/currency_picker/currency_picker_search_field.dart';
import 'package:cake_wallet/new-ui/widgets/currency_picker/fiat_currency_row.dart';
import 'package:cake_wallet/new-ui/widgets/currency_picker/fiat_currency_search_result.dart';
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:flutter/material.dart';
class FiatCurrencyPickerSheet extends StatefulWidget {
const FiatCurrencyPickerSheet({
super.key,
required this.selected,
required this.onSelected,
this.cryptoOption,
this.onCryptoSelected,
});
final Object? selected;
final ValueChanged<FiatCurrency> onSelected;
final CryptoCurrency? cryptoOption;
final ValueChanged<CryptoCurrency>? onCryptoSelected;
static const _popularOrder = <FiatCurrency>[
FiatCurrency.usd,
FiatCurrency.eur,
FiatCurrency.gbp,
FiatCurrency.jpy,
FiatCurrency.cny,
];
static Future<void> show({
required BuildContext context,
required Object? selected,
required ValueChanged<FiatCurrency> onSelected,
CryptoCurrency? cryptoOption,
ValueChanged<CryptoCurrency>? onCryptoSelected,
}) {
return showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
useSafeArea: true,
enableDrag: false,
backgroundColor: Colors.transparent,
builder: (_) => FiatCurrencyPickerSheet(
selected: selected,
onSelected: onSelected,
cryptoOption: cryptoOption,
onCryptoSelected: onCryptoSelected,
),
);
}
@override
State<FiatCurrencyPickerSheet> createState() => _FiatCurrencyPickerSheetState();
}
class _FiatCurrencyPickerSheetState extends State<FiatCurrencyPickerSheet> {
final TextEditingController _searchController = TextEditingController();
late final List<FiatCurrency> _allSorted = [...FiatCurrency.all]..sort(
(a, b) => a.fullName.toLowerCase().compareTo(b.fullName.toLowerCase()),
);
bool get _isSearching => _searchController.text.trim().isNotEmpty;
@override
void initState() {
super.initState();
_searchController.addListener(() => setState(() {}));
}
@override
void dispose() {
_searchController.dispose();
super.dispose();
}
bool _matches(FiatCurrency c, String query) {
if (query.isEmpty) return true;
final q = query.toLowerCase();
return c.fullName.toLowerCase().contains(q) || c.title.toLowerCase().contains(q);
}
void _selectFiatCurrency(FiatCurrency c) {
widget.onSelected(c);
Navigator.of(context).maybePop();
}
void _selectCryptoCurrency(CryptoCurrency c) {
widget.onCryptoSelected?.call(c);
Navigator.of(context).maybePop();
}
@override
Widget build(BuildContext context) {
final colors = Theme.of(context).colorScheme;
final selected = widget.selected;
final selectedFiat = selected is FiatCurrency ? selected : null;
final popular = FiatCurrencyPickerSheet._popularOrder
.where((c) => c != selectedFiat)
.toList(growable: false);
final query = _searchController.text.trim();
final filteredAll = _allSorted.where((c) => _matches(c, query)).toList(growable: false);
return Padding(
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.vertical(top: Radius.circular(24)),
color: colors.surface,
),
child: Column(
mainAxisSize: MainAxisSize.max,
children: [
ModalTopBar(
title: S.of(context).select_fiat_currency_title,
leadingIcon: const Icon(Icons.close),
onLeadingPressed: () => Navigator.of(context).maybePop(),
),
Expanded(
child: _isSearching
? FiatCurrencySearchResults(
items: filteredAll,
onSelected: _selectFiatCurrency,
selected: selectedFiat,
)
: FiatCurrencyPickerBody(
selected: selected,
cryptoOption: widget.cryptoOption,
popular: popular,
all: filteredAll,
onSelectedFiat: _selectFiatCurrency,
onSelectedCrypto: _selectCryptoCurrency,
),
),
CurrencyPickerSearchField(
controller: _searchController,
hintText: S.of(context).search,
),
const SizedBox(height: 24),
],
),
),
);
}
}
class FiatCurrencyPickerBody extends StatelessWidget {
const FiatCurrencyPickerBody({
super.key,
required this.selected,
required this.cryptoOption,
required this.popular,
required this.all,
required this.onSelectedFiat,
required this.onSelectedCrypto,
});
final Object? selected;
final CryptoCurrency? cryptoOption;
final List<FiatCurrency> popular;
final List<FiatCurrency> all;
final void Function(FiatCurrency) onSelectedFiat;
final void Function(CryptoCurrency) onSelectedCrypto;
@override
Widget build(BuildContext context) {
final selectedFiat = selected is FiatCurrency ? selected as FiatCurrency : null;
final selectedCrypto = selected is CryptoCurrency ? selected as CryptoCurrency : null;
final showCryptoSection = cryptoOption != null && selectedCrypto == null;
return ListView(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
children: [
if (selectedFiat != null || selectedCrypto != null) ...[
_SectionHeader(label: S.of(context).picker_section_selected),
CurrencyPickerListContainer(
rows: [
if (selectedFiat != null)
FiatCurrencyRow(
currency: selectedFiat,
isSelected: true,
onTap: () => onSelectedFiat(selectedFiat),
),
if (selectedCrypto != null)
_CryptoOptionRow(
currency: selectedCrypto,
isSelected: true,
onTap: () => onSelectedCrypto(selectedCrypto),
),
],
),
const SizedBox(height: 16),
],
if (showCryptoSection) ...[
_SectionHeader(label: S.of(context).picker_section_crypto),
CurrencyPickerListContainer(
rows: [
_CryptoOptionRow(
currency: cryptoOption!,
isSelected: false,
onTap: () => onSelectedCrypto(cryptoOption!),
),
],
),
const SizedBox(height: 16),
],
if (popular.isNotEmpty) ...[
_SectionHeader(label: S.of(context).picker_section_popular),
CurrencyPickerListContainer(
rows: [
for (final c in popular)
FiatCurrencyRow(
currency: c,
isSelected: false,
onTap: () => onSelectedFiat(c),
),
],
),
const SizedBox(height: 16),
],
_SectionHeader(label: S.of(context).picker_section_all_currencies),
CurrencyPickerListContainer(
rows: [
for (final c in all)
FiatCurrencyRow(
currency: c,
isSelected: c == selectedFiat,
onTap: () => onSelectedFiat(c),
),
],
),
],
);
}
}
class _SectionHeader extends StatelessWidget {
const _SectionHeader({required this.label});
final String label;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8),
child: Text(
label,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
);
}
}
class _CryptoOptionRow extends StatelessWidget {
const _CryptoOptionRow({
required this.currency,
required this.isSelected,
required this.onTap,
});
final CryptoCurrency currency;
final bool isSelected;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
final colors = Theme.of(context).colorScheme;
return InkWell(
onTap: onTap,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
child: Row(
children: [
CakeImageWidget(
imageUrl: currency.iconPath,
width: 28,
height: 28,
fit: BoxFit.cover,
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
currency.fullName ?? currency.title,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 2),
Text(
currency.title,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: colors.onSurfaceVariant,
),
),
],
),
),
if (isSelected) Icon(Icons.check, size: 20, color: colors.primary),
],
),
),
);
}
}
@@ -0,0 +1,67 @@
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:flutter/material.dart';
class FiatCurrencyRow extends StatelessWidget {
const FiatCurrencyRow({
required this.currency,
required this.isSelected,
required this.onTap,
});
final FiatCurrency currency;
final bool isSelected;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
final colors = Theme.of(context).colorScheme;
return InkWell(
onTap: onTap,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
child: Row(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: CakeImageWidget(
imageUrl: 'assets/images/flags/${currency.countryCode}.png',
width: 28,
height: 20,
fit: BoxFit.cover,
errorWidget: Container(
width: 28,
height: 20,
color: colors.surfaceContainer,
),
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
currency.fullName,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 2),
Text(
currency.title,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: colors.onSurfaceVariant,
),
),
],
),
),
if (isSelected) Icon(Icons.check, size: 20, color: colors.primary),
],
),
),
);
}
}
@@ -0,0 +1,52 @@
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/new-ui/widgets/currency_picker/currency_picker_list_container.dart';
import 'package:cake_wallet/new-ui/widgets/currency_picker/fiat_currency_row.dart';
import 'package:flutter/material.dart';
class FiatCurrencySearchResults extends StatelessWidget {
const FiatCurrencySearchResults({
super.key,
required this.items,
required this.onSelected,
required this.selected,
});
final List<FiatCurrency> items;
final void Function(FiatCurrency) onSelected;
final FiatCurrency? selected;
@override
Widget build(BuildContext context) {
if (items.isEmpty) {
return Center(
child: Padding(
padding: const EdgeInsets.all(24),
child: Text(
S.of(context).picker_no_matches,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
),
);
}
return ListView(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
children: [
CurrencyPickerListContainer(
rows: [
for (final c in items)
FiatCurrencyRow(
currency: c,
isSelected: c == selected,
onTap: () => onSelected(c),
),
],
),
],
);
}
}
@@ -0,0 +1,682 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/new-ui/widgets/currency_picker/chain_chip_strip.dart';
import 'package:cake_wallet/new-ui/widgets/currency_picker/currency_picker_args.dart';
import 'package:cake_wallet/new-ui/widgets/currency_picker/picker_recents_loader.dart';
import 'package:cake_wallet/new-ui/widgets/currency_picker/currency_picker_list_container.dart';
import 'package:cake_wallet/new-ui/widgets/currency_picker/currency_picker_row.dart';
import 'package:cake_wallet/new-ui/widgets/currency_picker/currency_picker_search_field.dart';
import 'package:cake_wallet/new-ui/widgets/currency_picker/picker_section_header.dart';
import 'package:cake_wallet/new-ui/widgets/currency_picker/pill_grid.dart';
import 'package:cake_wallet/new-ui/widgets/currency_picker/select_network_page.dart';
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cake_wallet/wallet_types.g.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
import 'package:cw_core/currency_groups.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
class MultiNetworkCurrencyPicker extends StatefulWidget {
const MultiNetworkCurrencyPicker({super.key, required this.args});
final CurrencyPickerArgs args;
@override
State<MultiNetworkCurrencyPicker> createState() => _MultiNetworkCurrencyPickerState();
}
class _MultiNetworkCurrencyPickerState extends State<MultiNetworkCurrencyPicker> {
bool _recentsLoaded = false;
WalletType? _selectedNetwork;
List<CryptoCurrency> _recents = const [];
final TextEditingController _searchController = TextEditingController();
bool get _isSearching => _searchController.text.trim().isNotEmpty;
late final List<WalletType> _networks = _computeNetworks();
List<WalletType> _computeNetworks() {
final counts = <WalletType, int>{};
for (final currency in widget.args.items) {
final walletType = cryptoCurrencyOrTokenToWalletType(currency);
if (walletType == null) continue;
counts[walletType] = (counts[walletType] ?? 0) + 1;
}
return counts.entries.where((e) => e.value >= 2).map((e) => e.key).toList(growable: false);
}
Set<CryptoCurrency> get _natives =>
{for (final walletType in availableWalletTypes) walletTypeToCryptoCurrency(walletType)};
@override
void initState() {
super.initState();
_searchController.addListener(() => setState(() {}));
_loadRecents();
}
@override
void dispose() {
_searchController.dispose();
super.dispose();
}
Future<void> _loadRecents() async {
try {
final recents = await PickerRecentsLoader.load(
source: widget.args.recentsSource,
visibleItems: widget.args.items,
);
if (!mounted) return;
setState(() {
_recents = recents;
_recentsLoaded = true;
});
} catch (e, s) {
printV('load picker recents failed: $e\n$s');
if (!mounted) return;
setState(() => _recentsLoaded = true);
}
}
bool _matchesNetwork(CryptoCurrency c) {
if (_selectedNetwork == null) return true;
return cryptoCurrencyOrTokenToWalletType(c) == _selectedNetwork;
}
List<CryptoCurrency> get _visibleItems {
final query = _searchController.text.trim();
return widget.args.items
.where(_hasSupportedChain)
.where((c) => currencyMatchesQuery(c, query) && _matchesNetwork(c))
.toList(growable: false);
}
bool _hasSupportedChain(CryptoCurrency c) {
if (c.tag == null) return true;
return cryptoCurrencyOrTokenToWalletType(c) != null;
}
void _selectCurrency(CryptoCurrency currency) {
widget.args.onSelected(currency);
Navigator.of(context).maybePop();
}
void _onStablecoinPillTapped(CryptoCurrency tapped) {
var variants = widget.args.items
.where((c) =>
isTrustedStablecoin(c) &&
c.title.toUpperCase() == tapped.title.toUpperCase() &&
cryptoCurrencyOrTokenToWalletType(c) != null)
.toList();
if (_selectedNetwork != null) {
final filtered = variants
.where((c) => cryptoCurrencyOrTokenToWalletType(c) == _selectedNetwork)
.toList(growable: false);
if (filtered.isNotEmpty) variants = filtered;
}
if (variants.length <= 1) {
_selectCurrency(variants.isNotEmpty ? variants.first : tapped);
return;
}
Navigator.of(context).push(
PageRouteBuilder<void>(
opaque: true,
transitionDuration: const Duration(milliseconds: 250),
reverseTransitionDuration: const Duration(milliseconds: 250),
pageBuilder: (_, __, ___) => SelectNetworkPage(
assetTitle: tapped.title,
assetFullName: tapped.fullName,
assetIconPath: tapped.iconPath,
variants: variants,
onSelected: widget.args.onSelected,
),
transitionsBuilder: (_, animation, __, child) => SlideTransition(
position: animation.drive(
Tween<Offset>(begin: const Offset(0, 1), end: Offset.zero)
.chain(CurveTween(curve: Curves.easeInOut)),
),
child: child,
),
),
);
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.max,
children: [
if (_networks.length > 1)
ChainChipStrip(
walletTypes: _networks,
selected: _selectedNetwork,
onSelected: (network) => setState(() => _selectedNetwork = network),
),
Expanded(
child: _MultiNetworkPickerBody(
items: _visibleItems,
isSearching: _isSearching,
recents: _recents,
recentsLoaded: _recentsLoaded,
natives: _natives,
selected: widget.args.selected,
symbolResolver: widget.args.symbolResolver,
onSelect: _selectCurrency,
onStablecoinTap: _onStablecoinPillTapped,
),
),
CurrencyPickerSearchField(
controller: _searchController,
hintText: S.of(context).search,
),
const SizedBox(height: 24),
],
);
}
}
class _MultiNetworkPickerBody extends StatefulWidget {
const _MultiNetworkPickerBody({
required this.items,
required this.isSearching,
required this.recents,
required this.recentsLoaded,
required this.natives,
required this.selected,
required this.symbolResolver,
required this.onSelect,
required this.onStablecoinTap,
});
final bool isSearching;
final bool recentsLoaded;
final CryptoCurrency? selected;
final List<CryptoCurrency> items;
final List<CryptoCurrency> recents;
final Set<CryptoCurrency> natives;
final void Function(CryptoCurrency) onSelect;
final String Function(CryptoCurrency) symbolResolver;
final void Function(CryptoCurrency) onStablecoinTap;
@override
State<_MultiNetworkPickerBody> createState() => _MultiNetworkPickerBodyState();
}
class _MultiNetworkPickerBodyState extends State<_MultiNetworkPickerBody> {
static const int _previewCount = 3;
final ScrollController _scrollController = ScrollController();
bool _moreCryptosSectionExpanded = false;
bool _xstocksSectionExpanded = false;
@override
void initState() {
super.initState();
final selected = widget.selected;
if (selected == null) return;
final moreCryptos = _computeMoreCryptosSection(widget.items);
final xstocks = _computeXstocksSection(widget.items);
if (moreCryptos.indexOf(selected) >= _previewCount) {
_moreCryptosSectionExpanded = true;
}
if (xstocks.indexOf(selected) >= _previewCount) {
_xstocksSectionExpanded = true;
}
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
List<CryptoCurrency> _computeMoreCryptosSection(List<CryptoCurrency> from) {
final list = from
.where((c) =>
!widget.natives.contains(c) &&
cryptoCurrencyOrTokenToWalletType(c) == null &&
c.tag == null &&
!c.groups.contains(CurrencyGroups.stablecoin) &&
!c.groups.contains(CurrencyGroups.tokenizedStock))
.toList();
list.sort((a, b) => (a.fullName ?? a.title).toLowerCase().compareTo(
(b.fullName ?? b.title).toLowerCase(),
));
return list;
}
List<CryptoCurrency> _computeXstocksSection(List<CryptoCurrency> from) {
return from
.where((c) => c.groups.contains(CurrencyGroups.tokenizedStock))
.toList(growable: false);
}
@override
Widget build(BuildContext context) {
final items = widget.items;
final isSearching = widget.isSearching;
final recents = widget.recents;
final recentsLoaded = widget.recentsLoaded;
final natives = widget.natives;
final selected = widget.selected;
final symbolResolver = widget.symbolResolver;
final onSelect = widget.onSelect;
final onStablecoinTap = widget.onStablecoinTap;
if (items.isEmpty) {
return Center(
child: Padding(
padding: const EdgeInsets.all(24),
child: Text(
S.of(context).picker_no_matches,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
),
);
}
if (isSearching) {
return NotificationListener<ScrollNotification>(
onNotification: (_) => true,
child: ListView(
controller: _scrollController,
primary: false,
physics: const ClampingScrollPhysics(),
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
children: [
CurrencyPickerListContainer(
rows: [
for (final item in items)
CurrencyPickerRow(
currency: item,
isSelected: selected != null && selected == item,
chainPillLabel: _chainPillLabelFor(item),
chainBadgePath: _chainBadgePathFor(item),
trailing: _SymbolTrailing(
currency: item,
symbolResolver: symbolResolver,
),
onTap: () => onSelect(item),
),
],
),
],
),
);
}
final visibleRecents = recents.where(items.contains).toList(growable: false);
final seenStablecoinTitles = <String>{};
final stablecoins = items
.where((c) => isTrustedStablecoin(c) && seenStablecoinTitles.add(c.title.toUpperCase()))
.toList(growable: false);
final cryptocurrencies = items.where(natives.contains).toList(growable: false);
final moreCryptos = _computeMoreCryptosSection(items);
final xstocks = _computeXstocksSection(items);
final allAssets = [...items]..sort((a, b) => (a.fullName ?? a.title).toLowerCase().compareTo(
(b.fullName ?? b.title).toLowerCase(),
));
final section = _selectedSection(
recents: visibleRecents,
stablecoins: stablecoins,
cryptocurrencies: cryptocurrencies,
moreCryptos: moreCryptos,
xstocks: xstocks,
);
final moreCryptosVisible = _moreCryptosSectionExpanded
? moreCryptos
: moreCryptos.take(_previewCount).toList(growable: false);
final xstocksVisible =
_xstocksSectionExpanded ? xstocks : xstocks.take(_previewCount).toList(growable: false);
return NotificationListener<ScrollNotification>(
onNotification: (_) => true,
child: ListView(
controller: _scrollController,
primary: false,
physics: const ClampingScrollPhysics(),
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
children: [
if (recentsLoaded && visibleRecents.isNotEmpty)
_PickerSection(
title: S.of(context).picker_section_recents,
child: _RecentsRow(
items: visibleRecents,
selected: section == _SelSection.recents ? selected : null,
symbolResolver: symbolResolver,
onTap: onSelect,
),
),
if (stablecoins.isNotEmpty)
_PickerSection(
title: S.of(context).picker_section_stablecoins,
child: PillGrid(
items: stablecoins,
selected: section == _SelSection.stablecoins ? selected : null,
onTap: onStablecoinTap,
symbolResolver: symbolResolver,
),
),
if (cryptocurrencies.isNotEmpty)
_PickerSection(
title: S.of(context).picker_section_cryptocurrencies,
child: CurrencyPickerListContainer(
rows: [
for (final item in cryptocurrencies)
CurrencyPickerRow(
currency: item,
isSelected: section == _SelSection.cryptocurrencies && selected == item,
chainPillLabel: _chainPillLabelFor(item),
chainBadgePath: _chainBadgePathFor(item),
trailing: _SymbolTrailing(
currency: item,
symbolResolver: symbolResolver,
),
onTap: () => onSelect(item),
),
],
),
),
if (moreCryptos.isNotEmpty)
_PickerSection(
title: S.of(context).picker_section_more_cryptocurrencies,
child: CurrencyPickerListContainer(
rows: [
for (final item in moreCryptosVisible)
CurrencyPickerRow(
currency: item,
isSelected: section == _SelSection.moreCryptocurrencies && selected == item,
trailing: _SymbolTrailing(
currency: item,
symbolResolver: symbolResolver,
),
onTap: () => onSelect(item),
),
if (moreCryptos.length > _previewCount)
_SeeAllRow(
expanded: _moreCryptosSectionExpanded,
onTap: () => setState(
() => _moreCryptosSectionExpanded = !_moreCryptosSectionExpanded),
),
],
),
),
if (xstocks.isNotEmpty)
_PickerSection(
title: S.of(context).picker_section_tokenized_stocks,
child: CurrencyPickerListContainer(
rows: [
for (final item in xstocksVisible)
CurrencyPickerRow(
currency: item,
isSelected: section == _SelSection.xstocks && selected == item,
trailing: _SymbolTrailing(
currency: item,
symbolResolver: symbolResolver,
),
onTap: () => onSelect(item),
chainPillLabel: _chainPillLabelFor(item),
chainBadgePath: _chainBadgePathFor(item),
),
if (xstocks.length > _previewCount)
_SeeAllRow(
expanded: _xstocksSectionExpanded,
onTap: () =>
setState(() => _xstocksSectionExpanded = !_xstocksSectionExpanded),
),
],
),
),
if (allAssets.isNotEmpty)
_PickerSection(
title: S.of(context).picker_section_all_assets,
child: CurrencyPickerListContainer(
rows: [
for (final item in allAssets)
CurrencyPickerRow(
currency: item,
isSelected: section == _SelSection.allAssets && selected == item,
chainPillLabel: _chainPillLabelFor(item),
chainBadgePath: _chainBadgePathFor(item),
trailing: _SymbolTrailing(
currency: item,
symbolResolver: symbolResolver,
),
onTap: () => onSelect(item),
),
],
),
),
],
),
);
}
String? _chainPillLabelFor(CryptoCurrency c) {
if (c == CryptoCurrency.btcln) return _shortChainLabel(c);
if (_isL2NativeEth(c)) return _shortChainLabel(c);
if (widget.natives.contains(c)) return null;
if (cryptoCurrencyOrTokenToWalletType(c) == null) return null;
return _shortChainLabel(c);
}
String _shortChainLabel(CryptoCurrency c) {
if (cryptoCurrencyOrTokenToWalletType(c) == WalletType.bsc) return 'BSC';
return chainNameForCurrency(c);
}
String? _chainBadgePathFor(CryptoCurrency c) {
if (_isL2NativeEth(c)) return c.chainIconPath;
if (widget.natives.contains(c)) return null;
final wt = cryptoCurrencyOrTokenToWalletType(c);
if (wt == null) return null;
return c.chainIconPath ?? walletTypeToCryptoCurrency(wt).chainIconPath;
}
bool _isL2NativeEth(CryptoCurrency c) =>
c == CryptoCurrency.arbEth || c == CryptoCurrency.baseEth;
_SelSection? _selectedSection({
required List<CryptoCurrency> recents,
required List<CryptoCurrency> stablecoins,
required List<CryptoCurrency> cryptocurrencies,
required List<CryptoCurrency> moreCryptos,
required List<CryptoCurrency> xstocks,
}) {
final s = widget.selected;
if (s == null) return null;
if (recents.contains(s)) return _SelSection.recents;
if (stablecoins.any((c) => c.title.toUpperCase() == s.title.toUpperCase())) {
return _SelSection.stablecoins;
}
if (cryptocurrencies.contains(s)) return _SelSection.cryptocurrencies;
if (moreCryptos.contains(s)) return _SelSection.moreCryptocurrencies;
if (xstocks.contains(s)) return _SelSection.xstocks;
return _SelSection.allAssets;
}
}
class _PickerSection extends StatelessWidget {
const _PickerSection({required this.title, required this.child});
final String title;
final Widget child;
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
PickerSectionHeader(title: title),
child,
const SizedBox(height: 16),
],
);
}
}
class _SymbolTrailing extends StatelessWidget {
const _SymbolTrailing({
required this.currency,
required this.symbolResolver,
});
final CryptoCurrency currency;
final String Function(CryptoCurrency) symbolResolver;
@override
Widget build(BuildContext context) {
return Text(
symbolResolver(currency),
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
);
}
}
class _SeeAllRow extends StatelessWidget {
const _SeeAllRow({
required this.expanded,
required this.onTap,
});
final bool expanded;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
final colors = Theme.of(context).colorScheme;
final label = expanded ? S.of(context).picker_show_less : S.of(context).picker_see_all;
return InkWell(
onTap: onTap,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 14),
child: Row(
children: [
Expanded(
child: Text(
label,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w500,
color: colors.primary,
),
),
),
],
),
),
);
}
}
class _RecentsRow extends StatelessWidget {
const _RecentsRow({
required this.items,
required this.selected,
required this.symbolResolver,
required this.onTap,
});
final List<CryptoCurrency> items;
final CryptoCurrency? selected;
final String Function(CryptoCurrency) symbolResolver;
final void Function(CryptoCurrency) onTap;
@override
Widget build(BuildContext context) {
return SizedBox(
height: 44,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: items.length,
separatorBuilder: (_, __) => const SizedBox(width: 8),
itemBuilder: (_, i) {
final item = items[i];
return _RecentPill(
currency: item,
label: symbolResolver(item),
isSelected: selected != null && selected == item,
onTap: () => onTap(item),
);
},
),
);
}
}
class _RecentPill extends StatelessWidget {
const _RecentPill({
required this.currency,
required this.label,
required this.isSelected,
required this.onTap,
});
final CryptoCurrency currency;
final String label;
final bool isSelected;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
final colors = Theme.of(context).colorScheme;
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(80),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
decoration: BoxDecoration(
color: colors.surfaceContainer,
borderRadius: BorderRadius.circular(80),
border: isSelected ? Border.all(color: colors.primary, width: 1.5) : null,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
CakeImageWidget(
imageUrl: currency.iconPath,
width: 24,
height: 24,
fit: BoxFit.cover,
),
const SizedBox(width: 8),
Text(
label,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w600,
),
),
],
),
),
);
}
}
enum _SelSection {
recents,
stablecoins,
cryptocurrencies,
moreCryptocurrencies,
xstocks,
allAssets,
}
@@ -0,0 +1,97 @@
import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/exchange/trade_state.dart';
import 'package:cake_wallet/new-ui/widgets/currency_picker/currency_picker_args.dart';
import 'package:cake_wallet/order/order.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:hive/hive.dart';
class PickerRecentsLoader {
const PickerRecentsLoader._();
static const int defaultLimit = 6;
static final Set<TradeState> _executedTradeStates = {
TradeState.complete,
TradeState.completed,
TradeState.finished,
TradeState.success,
TradeState.settled,
TradeState.traded,
};
static Future<List<CryptoCurrency>> load({
required RecentsSource source,
required List<CryptoCurrency> visibleItems,
int limit = defaultLimit,
}) async {
switch (source) {
case RecentsSource.none:
return const [];
case RecentsSource.trades:
return _fromTrades(visibleItems, limit);
case RecentsSource.orders:
return _fromOrders(visibleItems, limit);
}
}
static Future<List<CryptoCurrency>> _fromTrades(
List<CryptoCurrency> visibleItems,
int limit,
) async {
final trades = await Trade.getAll();
final ordered = trades
.where((t) => _executedTradeStates.contains(t.state))
.toList()
..sort((a, b) => (b.createdAt ?? DateTime.fromMillisecondsSinceEpoch(0))
.compareTo(a.createdAt ?? DateTime.fromMillisecondsSinceEpoch(0)));
return _collect(
ordered.expand((t) => [t.from, t.to]),
visibleItems,
limit,
);
}
static Future<List<CryptoCurrency>> _fromOrders(
List<CryptoCurrency> visibleItems,
int limit,
) async {
final orders = Hive.box<Order>(Order.boxName).values.toList()
..sort((a, b) => b.createdAt.compareTo(a.createdAt));
return _collect(
orders.expand((o) => [_tryResolve(o.from), _tryResolve(o.to)]),
visibleItems,
limit,
);
}
static List<CryptoCurrency> _collect(
Iterable<CryptoCurrency?> candidates,
List<CryptoCurrency> visibleItems,
int limit,
) {
final visibleSet = visibleItems.toSet();
final seen = <CryptoCurrency>{};
final collected = <CryptoCurrency>[];
for (final candidate in candidates) {
if (candidate == null) continue;
if (!visibleSet.contains(candidate)) continue;
if (!seen.add(candidate)) continue;
collected.add(candidate);
if (collected.length >= limit) break;
}
return collected;
}
static CryptoCurrency? _tryResolve(String? raw) {
if (raw == null || raw.isEmpty) return null;
try {
return CryptoCurrency.fromString(raw);
} catch (_) {
return null;
}
}
}
@@ -0,0 +1,21 @@
import 'package:flutter/material.dart';
class PickerSectionHeader extends StatelessWidget {
const PickerSectionHeader({super.key, required this.title});
final String title;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.fromLTRB(4, 12, 4, 8),
child: Text(
title,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
);
}
}
@@ -0,0 +1,100 @@
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:flutter/material.dart';
class PillGrid extends StatelessWidget {
const PillGrid({
super.key,
required this.items,
required this.selected,
required this.onTap,
required this.symbolResolver,
});
final List<CryptoCurrency> items;
final CryptoCurrency? selected;
final ValueChanged<CryptoCurrency> onTap;
final String Function(CryptoCurrency c) symbolResolver;
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
const gap = 8.0;
final cardWidth = (constraints.maxWidth - gap) / 2;
return Wrap(
spacing: gap,
runSpacing: gap,
children: [
for (final item in items)
SizedBox(
width: cardWidth,
child: _PillCard(
currency: item,
isSelected: selected != null && selected == item,
label: symbolResolver(item),
onTap: () => onTap(item),
),
),
],
);
},
);
}
}
class _PillCard extends StatelessWidget {
const _PillCard({
required this.currency,
required this.isSelected,
required this.label,
required this.onTap,
});
final CryptoCurrency currency;
final bool isSelected;
final String label;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
final colors = Theme.of(context).colorScheme;
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(80),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
decoration: BoxDecoration(
color: colors.surfaceContainer,
borderRadius: BorderRadius.circular(80),
border: isSelected ? Border.all(color: colors.primary, width: 1.5) : null,
),
child: Row(
children: [
CakeImageWidget(
imageUrl: currency.iconPath,
width: 24,
height: 24,
fit: BoxFit.cover,
),
const SizedBox(width: 8),
Expanded(
child: Text(
label,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w600,
),
),
),
Icon(
Icons.chevron_right,
size: 18,
color: colors.onSurfaceVariant,
),
],
),
),
);
}
}
@@ -0,0 +1,140 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/new-ui/widgets/currency_picker/currency_picker_args.dart';
import 'package:cake_wallet/new-ui/widgets/currency_picker/currency_picker_list_container.dart';
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
class SelectNetworkPage extends StatelessWidget {
const SelectNetworkPage({
super.key,
required this.assetTitle,
required this.assetFullName,
required this.assetIconPath,
required this.variants,
required this.onSelected,
});
final String assetTitle;
final String? assetFullName;
final String? assetIconPath;
final List<CryptoCurrency> variants;
final ValueChanged<CryptoCurrency> onSelected;
@override
Widget build(BuildContext context) {
final colors = Theme.of(context).colorScheme;
return Scaffold(
backgroundColor: colors.surface,
body: SafeArea(
child: Column(
children: [
ModalTopBar(
title: S.of(context).select_network_title,
leadingIcon: const Icon(Icons.arrow_back),
onLeadingPressed: () => Navigator.of(context).maybePop(),
),
const SizedBox(height: 24),
CakeImageWidget(
imageUrl: assetIconPath,
width: 75,
height: 75,
fit: BoxFit.cover,
),
const SizedBox(height: 12),
Text(
assetTitle,
style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontSize: 20,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 12),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 32),
child: Text(
S.of(context).select_network_subtitle,
textAlign: TextAlign.center,
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(color: colors.onSurfaceVariant),
),
),
const SizedBox(height: 24),
Expanded(
child: ListView(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
children: [
CurrencyPickerListContainer(
rows: [
for (final variant in variants)
_NetworkRow(
variant: variant,
onTap: () {
final nav = Navigator.of(context);
onSelected(variant);
if (nav.canPop()) nav.pop();
if (nav.canPop()) nav.pop();
},
),
],
),
],
),
),
],
),
),
);
}
}
class _NetworkRow extends StatelessWidget {
const _NetworkRow({required this.variant, required this.onTap});
final CryptoCurrency variant;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
final colors = Theme.of(context).colorScheme;
final wt = cryptoCurrencyOrTokenToWalletType(variant);
final networkIconPath =
wt != null ? getCryptoCurrencyIconForWalletListItem(wt) : variant.iconPath;
final networkName = chainNameForCurrency(variant);
return InkWell(
onTap: onTap,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 14),
child: Row(
children: [
CakeImageWidget(
imageUrl: networkIconPath,
width: 28,
height: 28,
fit: BoxFit.cover,
),
const SizedBox(width: 12),
Expanded(
child: Text(
networkName,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w500,
),
),
),
Icon(
Icons.chevron_right,
size: 20,
color: colors.onSurfaceVariant,
),
],
),
),
);
}
}
@@ -0,0 +1,163 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/new-ui/widgets/currency_picker/currency_picker_args.dart';
import 'package:cake_wallet/new-ui/widgets/currency_picker/currency_picker_list_container.dart';
import 'package:cake_wallet/new-ui/widgets/currency_picker/currency_picker_row.dart';
import 'package:cake_wallet/new-ui/widgets/currency_picker/currency_picker_search_field.dart';
import 'package:cake_wallet/new-ui/widgets/currency_picker/picker_section_header.dart';
import 'package:cake_wallet/reactions/wallet_utils.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
class SingleNetworkCurrencyPicker extends StatefulWidget {
const SingleNetworkCurrencyPicker({super.key, required this.args});
final CurrencyPickerArgs args;
@override
State<SingleNetworkCurrencyPicker> createState() => _SingleNetworkCurrencyPickerState();
}
class _SingleNetworkCurrencyPickerState extends State<SingleNetworkCurrencyPicker> {
CurrencyPickerArgs get _args => widget.args;
WalletType get _network => _args.filterByNetwork!;
final TextEditingController _searchController = TextEditingController();
@override
void initState() {
super.initState();
_searchController.addListener(() => setState(() {}));
}
@override
void dispose() {
_searchController.dispose();
super.dispose();
}
void _selectCurrency(CryptoCurrency currency) {
_args.onSelected(currency);
Navigator.of(context).maybePop();
}
double _fiatValueFor(CryptoCurrency c) => _args.balanceByAsset?[c]?.fiatValue ?? 0;
@override
Widget build(BuildContext context) {
final native = walletTypeToCryptoCurrency(_network);
final query = _searchController.text.trim();
final tokens = _args.items
.where((c) => c != native)
.where((c) => currencyMatchesQuery(c, query))
.toList();
final insertOrder = {for (var i = 0; i < tokens.length; i++) tokens[i]: i};
tokens.sort((a, b) {
final av = _fiatValueFor(a);
final bv = _fiatValueFor(b);
if (bv != av) return bv.compareTo(av);
return (insertOrder[a] ?? 0).compareTo(insertOrder[b] ?? 0);
});
final nativeMatches = currencyMatchesQuery(native, query);
return Column(
mainAxisSize: MainAxisSize.max,
children: [
Expanded(
child: (nativeMatches || tokens.isNotEmpty)
? ListView(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
children: [
if (nativeMatches) ...[
PickerSectionHeader(title: S.of(context).picker_section_gas_token),
CurrencyPickerListContainer(
rows: [
CurrencyPickerRow(
currency: native,
isSelected: _args.selected == native,
trailing: _BalanceTrailing(balance: _args.balanceByAsset?[native]),
onTap: () => _selectCurrency(native),
),
],
),
const SizedBox(height: 16),
],
if (tokens.isNotEmpty) ...[
PickerSectionHeader(
title: S
.of(context)
.picker_section_tokens_standard(tokenStandardFor(_network)),
),
CurrencyPickerListContainer(
rows: [
for (final t in tokens)
CurrencyPickerRow(
currency: t,
isSelected: _args.selected == t,
trailing: _BalanceTrailing(balance: _args.balanceByAsset?[t]),
onTap: () => _selectCurrency(t),
),
],
),
],
],
)
: Center(
child: Padding(
padding: const EdgeInsets.all(24),
child: Text(
S.of(context).picker_no_matches,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
),
),
),
CurrencyPickerSearchField(
controller: _searchController,
hintText: S.of(context).search,
),
const SizedBox(height: 24),
],
);
}
}
class _BalanceTrailing extends StatelessWidget {
const _BalanceTrailing({required this.balance});
final CurrencyPickerBalance? balance;
@override
Widget build(BuildContext context) {
final colors = Theme.of(context).colorScheme;
final amount = balance?.amount ?? '—';
final fiat = balance?.fiat;
return Column(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: [
Text(
amount,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w500,
),
),
if (fiat != null && fiat.isNotEmpty)
Padding(
padding: const EdgeInsets.only(top: 2),
child: Text(
fiat,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
fontWeight: FontWeight.w400,
color: colors.onSurfaceVariant,
),
),
),
],
);
}
}
@@ -1,16 +1,16 @@
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/new-ui/widgets/currency_picker/currency_picker_args.dart';
import 'package:cake_wallet/new-ui/widgets/currency_picker/currency_picker_sheet.dart';
import 'package:cake_wallet/new-ui/widgets/currency_picker/fiat_currency_picker_sheet.dart';
import 'package:cake_wallet/new-ui/widgets/coins_page/token_image_widget.dart';
import 'package:cake_wallet/new-ui/widgets/new_primary_button.dart';
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:flutter_svg/svg.dart';
class ReceiveAmountModal extends StatefulWidget {
const ReceiveAmountModal(
@@ -217,35 +217,31 @@ class _ReceiveAmountModalState extends State<ReceiveAmountModal> {
);
}
void _presentTokenCurrencyPicker(BuildContext context) async {
await showPopUp(
builder: (_) => CurrencyPicker(
selectedAtIndex: widget.walletAddressListViewModel.tokenCurrencyIndex,
items: widget.walletAddressListViewModel.tokenCurrencies,
hintText: S.of(context).search_currency,
onItemSelected: widget.walletAddressListViewModel.setTokenCurrency,
),
void _presentTokenCurrencyPicker(BuildContext context) {
final items = widget.walletAddressListViewModel.tokenCurrencies
.whereType<CryptoCurrency>()
.toList();
CurrencyPickerSheet.show(
context: context,
args: CurrencyPickerArgs(
items: items,
selected: widget.walletAddressListViewModel.tokenCurrency,
onSelected: widget.walletAddressListViewModel.setTokenCurrency,
symbolResolver: widget.walletAddressListViewModel.amountParsingProxy.getCryptoSymbol,
),
);
}
void _presentFiatCurrencyPicker(BuildContext context) async {
final currs = widget.walletAddressListViewModel.currencies.toList();
// the first options should be the crypto and the default fiat currency
currs.sort((a, b) {
if(a == widget.walletAddressListViewModel.fiatCurrency || a is CryptoCurrency) return -1;
if(b == widget.walletAddressListViewModel.fiatCurrency || a is CryptoCurrency) return 1;
return 0;
});
await showPopUp(
builder: (_) => CurrencyPicker(
selectedAtIndex: currs.indexOf(widget.walletAddressListViewModel.selectedCurrency),
items: currs,
hintText: S.of(context).search_currency,
onItemSelected: widget.walletAddressListViewModel.selectCurrency,
),
void _presentFiatCurrencyPicker(BuildContext context) {
final selected = widget.walletAddressListViewModel.selectedCurrency;
final cryptoOption =
widget.walletAddressListViewModel.currencies.whereType<CryptoCurrency>().firstOrNull;
FiatCurrencyPickerSheet.show(
context: context,
selected: selected,
cryptoOption: cryptoOption,
onSelected: widget.walletAddressListViewModel.selectCurrency,
onCryptoSelected: widget.walletAddressListViewModel.selectCurrency,
);
}
}
+19 -1
View File
@@ -52,4 +52,22 @@ bool hasTokens(WalletType walletType) {
default:
return false;
}
}
}
String tokenStandardFor(WalletType walletType) {
switch (walletType) {
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.arbitrum:
case WalletType.base:
return 'ERC-20';
case WalletType.bsc:
return 'BEP-20';
case WalletType.solana:
return 'SPL';
case WalletType.tron:
return 'TRC-20';
default:
return walletTypeToString(walletType);
}
}
+28
View File
@@ -3,6 +3,9 @@ import 'package:cake_wallet/core/address_validator.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/new-ui/widgets/currency_picker/currency_picker_args.dart';
import 'package:cake_wallet/new-ui/widgets/currency_picker/currency_picker_sheet.dart';
import 'package:cake_wallet/new-ui/widgets/currency_picker/fiat_currency_picker_sheet.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/desktop_exchange_cards_section.dart';
@@ -398,6 +401,7 @@ class BuySellPage extends BasePage {
showLimitsField: false,
currencies: buySellViewModel.fiatCurrencies,
onCurrencySelected: (currency) => buySellViewModel.changeFiatCurrency(currency: currency),
onTapCurrencyPicker: _presentFiatPicker,
imageArrow: Image.asset(
'assets/images/arrow_bottom_purple_icon.png',
color: Theme.of(context).colorScheme.primary,
@@ -435,6 +439,7 @@ class BuySellPage extends BasePage {
isMoneroWallet: buySellViewModel.wallet == WalletType.monero,
currencies: buySellViewModel.cryptoCurrencies,
onCurrencySelected: (currency) => buySellViewModel.changeCryptoCurrency(currency: currency),
onTapCurrencyPicker: _presentCryptoPicker,
imageArrow: Image.asset(
'assets/images/arrow_bottom_cake_green.png',
color: Theme.of(context).colorScheme.primary,
@@ -504,6 +509,29 @@ class BuySellPage extends BasePage {
);
}
void _presentFiatPicker(BuildContext context) {
FiatCurrencyPickerSheet.show(
context: context,
selected: buySellViewModel.fiatCurrency,
onSelected: (currency) => buySellViewModel.changeFiatCurrency(currency: currency),
);
}
void _presentCryptoPicker(BuildContext context) {
final items = [...buySellViewModel.cryptoCurrencies];
appendEvmDefaultTokens(items);
CurrencyPickerSheet.show(
context: context,
args: CurrencyPickerArgs(
items: items,
selected: buySellViewModel.cryptoCurrency,
recentsSource: RecentsSource.orders,
onSelected: (currency) => buySellViewModel.changeCryptoCurrency(currency: currency),
symbolResolver: buySellViewModel.amountParsingProxy.getCryptoSymbol,
),
);
}
Future<String> fetchParsedAddress(
BuildContext context,
String domain,
@@ -48,6 +48,7 @@ class ExchangeCard<T extends Currency> extends StatefulWidget {
this.onPushAddressBookButton,
this.onDispose,
this.useSatoshis = false,
this.onTapCurrencyPicker,
required this.cardInstanceName,
}) : super(key: key);
@@ -84,6 +85,7 @@ class ExchangeCard<T extends Currency> extends StatefulWidget {
final String cardInstanceName;
final Color fillColor;
final bool useSatoshis;
final void Function(BuildContext context)? onTapCurrencyPicker;
@override
ExchangeCardState<T> createState() => ExchangeCardState<T>();
@@ -447,6 +449,10 @@ class ExchangeCardState<T extends Currency> extends State<ExchangeCard<T>> {
}
void _presentPicker(BuildContext context) {
if (widget.onTapCurrencyPicker != null) {
widget.onTapCurrencyPicker!(context);
return;
}
showPopUp<void>(
context: context,
builder: (_) => CurrencyPicker(
@@ -1,11 +1,11 @@
import 'package:cake_wallet/entities/bitcoin_amount_display_mode.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/entities/language_service.dart';
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_regular_row.dart';
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_selector.dart';
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_toggle.dart';
import 'package:cake_wallet/entities/sync_status_display_mode.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/new-ui/widgets/currency_picker/fiat_currency_picker_sheet.dart';
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_choices_cell.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_theme_choice.dart';
@@ -179,35 +179,11 @@ class DisplaySettingsPage extends StatelessWidget {
keyValue: "display_settings_fiat_currency",
label: S.of(context).settings_currency,
options: [_displaySettingsViewModel.fiatCurrency.title],
onTap: () async {
final items = FiatCurrency.all;
final selectedAtIndex =
items.indexOf(_displaySettingsViewModel.fiatCurrency);
await showPopUp<void>(
context: context,
builder: (_) => Picker(
items: items,
selectedAtIndex: selectedAtIndex,
mainAxisAlignment: MainAxisAlignment.start,
onItemSelected: (FiatCurrency currency) {
_displaySettingsViewModel.setFiatCurrency(currency);
},
images: FiatCurrency.all
.map((e) => Image.asset("assets/images/flags/${e.countryCode}.png"))
.toList(),
hintText: S.of(context).search_currency,
isGridView: true,
matchingCriteria: (FiatCurrency currency, String searchText) {
return currency.title.toLowerCase().contains(searchText) ||
currency.fullName.toLowerCase().contains(searchText);
},
isSeparated: false,
),
);
}),
onTap: () => FiatCurrencyPickerSheet.show(
context: context,
selected: _displaySettingsViewModel.fiatCurrency,
onSelected: _displaySettingsViewModel.setFiatCurrency,
)),
ListItemSelector(
keyValue: "display_settings_language",
label: S.of(context).settings_change_language,
@@ -5,6 +5,7 @@ import 'package:cake_wallet/buy/buy_quote.dart';
import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart';
import 'package:cake_wallet/buy/payment_method.dart';
import 'package:cake_wallet/buy/sell_buy_states.dart';
import 'package:cake_wallet/core/amount_parsing_proxy.dart';
import 'package:cake_wallet/core/selectable_option.dart';
import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
@@ -87,6 +88,8 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
final AppStore _appStore;
AmountParsingProxy get amountParsingProxy => _appStore.amountParsingProxy;
Quote? bestRateQuote;
Quote? selectedQuote;
@@ -38,11 +38,9 @@ import 'package:cake_wallet/exchange/provider/xoswap_exchange_provider.dart';
import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/exchange/trade_request.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
import 'package:cake_wallet/new-ui/widgets/currency_picker/fiat_currency_picker_sheet.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/utils/exchange_provider_logger.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cw_core/currency.dart';
import "package:cw_core/wallet_info.dart";
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
import 'package:cake_wallet/store/dashboard/trades_store.dart';
@@ -270,17 +268,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
}
void showFiatCurrencyPicker(BuildContext context) {
showPopUp<void>(
FiatCurrencyPickerSheet.show(
context: context,
builder: (_) => CurrencyPicker(
key: ValueKey('send_page_currency_picker_dialog_button_key'),
selectedAtIndex: FiatCurrency.all.indexOf(fiat),
items: FiatCurrency.all,
hintText: S.of(context).search_currency,
onItemSelected: (Currency cur) async {
_settingsStore.fiatCurrency = (cur as FiatCurrency);
},
),
selected: fiat,
onSelected: (cur) => _settingsStore.fiatCurrency = cur,
);
}
@@ -2,6 +2,7 @@ import 'dart:core';
import 'dart:developer' as dev;
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/core/amount_parsing_proxy.dart';
import 'package:cake_wallet/core/fiat_conversion_service.dart';
import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
import 'package:cake_wallet/decred/decred.dart';
@@ -167,6 +168,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
bool get hasPayjoin =>
wallet.type == WalletType.bitcoin && !isLightning && !isSilentPayments && uri.toString().contains("payjo.in");
AmountParsingProxy get amountParsingProxy => _appStore.amountParsingProxy;
@computed
FiatCurrency get fiatCurrency => _appStore.settingsStore.fiatCurrency;