mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-26 06:08:40 +01:00
Cw 1551 quick bitcoin wallet sync (#3446)
* add Wallet Accounts page and integrate UI * account model refactor * add walletInfoAccount table and API * add WalletAccountList API * add multi-account support for Electrum wallets * add Bitcoin lightning card and card style handling * refine account filtering and card selection * update current account balance on wallet changes * update configure.dar * Use named parameters for card design * Add Bitcoin account handling and refactor lookup * refactor dashboard * refactor cards UI and account balance handling * Improve Bitcoin account selection and balances * restrict unspent coin usage to current account * filter transactions by current Bitcoin account * refactor electrum wallet * refactor address generation * add accountIndex to address fetch logic * Update configure.dart * reload transactions on Bitcoin account change * restore multiple BTC accounts and improve fetch * show account balance with in btc * track account index in Bitcoin transactions * discover btc BIP39 accounts during restore * fix account switching and card ordering bugs * fix card customizer * limit account name length in modal * update BTC account list balance display * auto-reformat * formating fix * skip HD map validation * fix 0 balance issue * minor fix * quick bitcoin wallet sync * restore shuffle change output * restore address lookup fix * Update electrum.dart * remove account customizer * Merge branch 'CW-1142-Add-accounts-feature-to-BTC' into CW-1551-Quick-Bitcoin-Wallet-Sync * probe only segwit in account discovery * add account discovery limit and tracking * Improve Electrum sync account handling [skip ci] * remove legacy monero account and address list UI * rework BTC account balance refres * avoid unnecessary BTC account reloads * refactor account header in addresses page * fix blank popup when editing an account * fix: dedupe UTXO balance * minor fix * add Bitcoin current account API * Apply batched suggestions from code review [skip ci] Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * address PR review comments [skip ci] * remove account customizer and fix accounts init * restrict extra BTC accounts to SegWit * add BTC multi-account toggle * Fix BTC sync gating and account balance update * minor fix * Update electrum_wallet.dart * store selected account on WalletInfo * Refine wallet account reset visibility * refactor BTC address prep and move accounts page * refactor account stack and customizer routing * migrate legacy Lightning card styles * unify dashboard account change state * fix account routing and Electrum balances * fix account setup and wallet cards UI * disable Bitcoin Accounts for all hardware * merge conflicts fix * minor fixes * update cards_view [skip ci] * reverted cards view update [skip ci] * fix asset name * moved the transaction filtering into the electrum wallet * localization * disable accounts for watch-only wallets * fix: stop card render from switching the wallet account * fix: restore Lightning transactions in tx history * minor fix * fix: skip duplicate legacy address generation [skip ci] * fix: correct account balance/design mismatch bugs * addressing review comments --------- Co-authored-by: Robert Malikowski <malikowskirobert@gmail.com> Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
co-authored by
Omar Hatem
Robert Malikowski
parent
0503d54290
commit
d7ebf42814
@@ -256,7 +256,10 @@ class CWBitcoin extends Bitcoin {
|
||||
List<BitcoinUnspent> getUnspents(Object wallet,
|
||||
{UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any}) {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
return bitcoinWallet.unspentCoins.where((element) {
|
||||
|
||||
final coins = bitcoinWallet.unspentCoinsForCurrentAccount;
|
||||
|
||||
return coins.where((element) {
|
||||
switch (coinTypeToSpendFrom) {
|
||||
case UnspentCoinType.mweb:
|
||||
return element.bitcoinAddressRecord.type == SegwitAddresType.mweb;
|
||||
@@ -919,4 +922,36 @@ class CWBitcoin extends Bitcoin {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
ElectrumBalance balanceForAccount(Object wallet, int accountIndex) {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
return bitcoinWallet.balanceForAccount(accountIndex);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<int, Object> accountBalancesSnapshot(Object wallet) {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
return Map<int, Object>.of(bitcoinWallet.accountBalances);
|
||||
}
|
||||
|
||||
@override
|
||||
List<TransactionInfo> getCurrentAccountBitcoinTransactions(Object wallet) =>
|
||||
(wallet as ElectrumWallet).currentAccountBitcoinTransactions;
|
||||
|
||||
@override
|
||||
Future<void> setCurrentAccount(Object wallet, int accountIndex) async {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
await bitcoinWallet.setCurrentAccount(accountIndex);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Account> getCurrentAccount(Object wallet) async {
|
||||
final electrumWallet = wallet as ElectrumWallet;
|
||||
final acc = await electrumWallet.loadCurrentAccount();
|
||||
return Account(
|
||||
id: acc.accountIndex,
|
||||
label: acc.label,
|
||||
balance: electrumWallet.balanceForAccount(acc.accountIndex).confirmed.toString());
|
||||
}
|
||||
}
|
||||
|
||||
+78
-19
@@ -55,7 +55,6 @@ import 'package:cake_wallet/nano/nano.dart';
|
||||
import 'package:cake_wallet/new-ui/model/charts/price_store.dart';
|
||||
import 'package:cake_wallet/new-ui/new_dashboard.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/about_page.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/account_customizer.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/bridge/bridge_amount_page.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/bridge/bridge_network_page.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/bridge/bridge_receiving_wallet_page.dart';
|
||||
@@ -131,6 +130,7 @@ import 'package:cake_wallet/src/screens/settings/privacy_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/security_backup_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/silent_payments_logs_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/silent_payments_settings.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/wallet_accounts_page.dart';
|
||||
import 'package:cake_wallet/src/screens/setup_2fa/modify_2fa_page.dart';
|
||||
import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa.dart';
|
||||
import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_enter_code_page.dart';
|
||||
@@ -214,11 +214,14 @@ import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/hardware_wallet/trezor_connect_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/integrations/deuro_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/link_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart';
|
||||
import 'package:cake_wallet/view_model/monero_account_list/monero_account_edit_or_create_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/monero_account_list/monero_account_list_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/nano_account_list/nano_account_edit_or_create_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/nano_account_list/nano_account_list_view_model.dart';
|
||||
import "package:cake_wallet/view_model/wallet_account_list/account_edit_or_create_view_model.dart";
|
||||
import 'package:cake_wallet/view_model/wallet_account_list/account_list_item.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_account_list/bitcoin_account_list/bitcoin_account_edit_or_create_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_account_list/bitcoin_account_list/bitcoin_account_list_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_account_list/monero_account_list/monero_account_edit_or_create_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_account_list/monero_account_list/monero_account_list_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_account_list/nano_account_list/nano_account_edit_or_create_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_account_list/nano_account_list/nano_account_list_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/node_list/node_list_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/order_details_view_model.dart';
|
||||
@@ -248,6 +251,7 @@ import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_details_view_
|
||||
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart';
|
||||
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/bridge/bridge_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_account_list/wallet_account_list_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
|
||||
@@ -290,6 +294,9 @@ import 'buy/meld/meld_buy_provider.dart';
|
||||
import 'dogecoin/dogecoin.dart';
|
||||
import 'new-ui/viewmodels/card_customizer/card_customizer_bloc.dart';
|
||||
import 'new-ui/widgets/addresses_page/address_info.dart';
|
||||
import 'src/screens/buy/buy_sell_page.dart';
|
||||
import "src/screens/settings/widgets/account_creation_modal.dart";
|
||||
|
||||
|
||||
final getIt = GetIt.instance;
|
||||
|
||||
@@ -535,6 +542,13 @@ Future<void> setup({
|
||||
getIt.registerFactory(() => DashboardViewModel(
|
||||
tradeMonitor: getIt.get<TradeMonitor>(),
|
||||
balanceViewModel: getIt.get<BalanceViewModel>(),
|
||||
accountListViewModelFactory: () {
|
||||
final wallet = getIt.get<AppStore>().wallet;
|
||||
if (wallet == null) return null;
|
||||
final hasAccounts = wallet.hasAccountsSupport;
|
||||
if (!hasAccounts) return null;
|
||||
return getIt.get<WalletAccountListViewModel>();
|
||||
},
|
||||
appStore: getIt.get<AppStore>(),
|
||||
tradesStore: getIt.get<TradesStore>(),
|
||||
ordersStore: getIt.get<OrdersStore>(),
|
||||
@@ -560,21 +574,11 @@ Future<void> setup({
|
||||
getIt.registerLazySingleton<PriceStore>(() => PriceStore());
|
||||
|
||||
getIt.registerFactory<ChartsBloc>(
|
||||
() => ChartsBloc(appStore: getIt.get<AppStore>(), priceStore: getIt.get<PriceStore>()));
|
||||
() => ChartsBloc(appStore: getIt.get<AppStore>(), priceStore: getIt.get<PriceStore>()));
|
||||
|
||||
getIt.registerFactory<ChartsPage>(() => ChartsPage(
|
||||
chartsBloc: getIt.get<ChartsBloc>(),
|
||||
));
|
||||
|
||||
getIt.registerFactory<AccountCreationModal>(() => AccountCreationModal(
|
||||
accountEditOrCreateViewModel: getIt.get<MoneroAccountEditOrCreateViewModel>()));
|
||||
|
||||
getIt.registerFactoryParam<AccountCustomizer, DashboardViewModel, void>(
|
||||
(dashboardViewModel, _) => AccountCustomizer(
|
||||
accountListViewModel: getIt.get<MoneroAccountListViewModel>(),
|
||||
dashboardViewModel: dashboardViewModel,
|
||||
),
|
||||
);
|
||||
chartsBloc: getIt.get<ChartsBloc>(),
|
||||
));
|
||||
|
||||
getIt.registerFactory<LightningUsernameBloc>(
|
||||
() => LightningUsernameBloc(getIt.get<AppStore>().wallet!));
|
||||
@@ -898,6 +902,30 @@ Future<void> setup({
|
||||
'Unexpected wallet type: ${wallet.type} for generate Monero AccountListViewModel');
|
||||
});
|
||||
|
||||
getIt.registerFactory<BitcoinAccountListViewModel>(() {
|
||||
final wallet = getIt.get<AppStore>().wallet!;
|
||||
if (wallet.type == WalletType.bitcoin) {
|
||||
return BitcoinAccountListViewModel(wallet, getIt.get<SettingsStore>());
|
||||
}
|
||||
throw Exception(
|
||||
'Unexpected wallet type: ${wallet.type} for generate Bitcoin AccountListViewModel');
|
||||
});
|
||||
|
||||
getIt.registerFactory<WalletAccountListViewModel>(() {
|
||||
final wallet = getIt.get<AppStore>().wallet!;
|
||||
|
||||
switch (wallet.type) {
|
||||
case WalletType.bitcoin:
|
||||
return getIt.get<BitcoinAccountListViewModel>();
|
||||
case WalletType.monero:
|
||||
case WalletType.wownero:
|
||||
case WalletType.haven:
|
||||
return getIt.get<MoneroAccountListViewModel>();
|
||||
default:
|
||||
throw Exception('Unsupported wallet type for WalletAccountListViewModel: ${wallet.type}');
|
||||
}
|
||||
});
|
||||
|
||||
getIt.registerFactoryParam<MoneroAccountEditOrCreateViewModel, AccountListItem?, void>(
|
||||
(AccountListItem? account, _) => MoneroAccountEditOrCreateViewModel(
|
||||
monero!.getAccountList(getIt.get<AppStore>().wallet!),
|
||||
@@ -905,6 +933,12 @@ Future<void> setup({
|
||||
wallet: getIt.get<AppStore>().wallet!,
|
||||
accountListItem: account));
|
||||
|
||||
getIt.registerFactory<BitcoinAccountEditOrCreateViewModel>(
|
||||
() => BitcoinAccountEditOrCreateViewModel(
|
||||
wallet: getIt.get<AppStore>().wallet!,
|
||||
),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<NanoAccountEditOrCreateViewModel, NanoAccount?, void>(
|
||||
(NanoAccount? account, _) =>
|
||||
NanoAccountEditOrCreateViewModel(nano!.getAccountList(getIt.get<AppStore>().wallet!),
|
||||
@@ -1002,6 +1036,31 @@ Future<void> setup({
|
||||
getIt.registerFactory(() => SecurityBackupPage(getIt.get<SecuritySettingsViewModel>(),
|
||||
getIt.get<AuthService>(), getIt.get<AppStore>().wallet!.isHardwareWallet));
|
||||
|
||||
|
||||
getIt.registerFactory<WalletAccountEditOrCreateViewModel>(() {
|
||||
final wallet = getIt.get<AppStore>().wallet!;
|
||||
switch (wallet.type) {
|
||||
case WalletType.bitcoin:
|
||||
return getIt.get<BitcoinAccountEditOrCreateViewModel>();
|
||||
case WalletType.monero:
|
||||
case WalletType.wownero:
|
||||
case WalletType.haven:
|
||||
return getIt.get<MoneroAccountEditOrCreateViewModel>();
|
||||
default:
|
||||
throw Exception(
|
||||
'Unsupported wallet type for WalletAccountEditOrCreateViewModel: ${wallet.type}');
|
||||
}
|
||||
});
|
||||
|
||||
getIt.registerFactoryParam<WalletAccountsPage, DashboardViewModel, void>(
|
||||
(dashboardViewModel, _) => WalletAccountsPage(
|
||||
dashboardViewModel: dashboardViewModel,
|
||||
accountListViewModel:
|
||||
dashboardViewModel.accountListViewModel ?? getIt.get<WalletAccountListViewModel>(),
|
||||
accountEditOrCreateViewModel: getIt.get<WalletAccountEditOrCreateViewModel>(),
|
||||
),
|
||||
);
|
||||
|
||||
getIt.registerFactory(() => PrivacyPage(getIt.get<PrivacySettingsViewModel>()));
|
||||
|
||||
|
||||
|
||||
@@ -117,7 +117,6 @@ class PreferencesKey {
|
||||
static const showDecredInfoCard = 'show_decred_info_card';
|
||||
static const forceDecentralizedExchanges = 'force_decentralized_exchanges';
|
||||
static const decentralizedExchangesPromptDismissed = 'decentralized_exchanges_prompt_dismissed';
|
||||
|
||||
static String moneroWalletUpdateV1Key(String name) =>
|
||||
'${PreferencesKey.moneroWalletPasswordUpdateV1Base}_${name}';
|
||||
|
||||
|
||||
@@ -1,505 +0,0 @@
|
||||
import 'dart:ui';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/card_customizer.dart';
|
||||
import 'package:cake_wallet/new-ui/viewmodels/card_customizer/card_customizer_bloc.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/cards/balance_card.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/modal_grab_handle.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/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_two_actions.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/dashboard/dashboard_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart';
|
||||
import 'package:cake_wallet/view_model/monero_account_list/monero_account_edit_or_create_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/monero_account_list/monero_account_list_view_model.dart';
|
||||
import 'package:cw_core/balance_card_style_settings.dart';
|
||||
import 'package:cw_core/card_design.dart';
|
||||
import 'package:cw_core/generate_name.dart';
|
||||
import 'package:cw_core/sync_status.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
|
||||
|
||||
class AccountCustomizerListItem {
|
||||
final BalanceCard card;
|
||||
final int order;
|
||||
final AccountListItem accountListItem;
|
||||
|
||||
AccountCustomizerListItem(
|
||||
{required this.card, required this.order, required this.accountListItem});
|
||||
}
|
||||
|
||||
class AccountCustomizer extends StatefulWidget {
|
||||
const AccountCustomizer(
|
||||
{super.key, required this.accountListViewModel, required this.dashboardViewModel});
|
||||
|
||||
final MoneroAccountListViewModel accountListViewModel;
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
|
||||
@override
|
||||
State<AccountCustomizer> createState() => _AccountCustomizerState();
|
||||
}
|
||||
|
||||
class _AccountCustomizerState extends State<AccountCustomizer> {
|
||||
static const double _kStackVisibleFactor = 0.2;
|
||||
late final double cardWidth = MediaQuery.of(context).size.width * 0.9;
|
||||
|
||||
final List<AccountCustomizerListItem> _items = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
loadCards();
|
||||
final activeId = widget.accountListViewModel.selected.id;
|
||||
for (int i = 0; i < _items.length - 1; i++) {
|
||||
if (_items[i].accountListItem.id == activeId) {
|
||||
final lastIndex = _items.length - 1;
|
||||
final temp = _items[i];
|
||||
_items[i] = _items[lastIndex];
|
||||
_items[lastIndex] = temp;
|
||||
saveCardOrder();
|
||||
widget.dashboardViewModel.loadCardDesigns();
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
saveCardOrder().then((value) => widget.dashboardViewModel.loadCardDesigns());
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void loadCards() {
|
||||
_items.clear();
|
||||
|
||||
final accounts = widget.accountListViewModel.accounts;
|
||||
for (int i = 0; i < accounts.length; i++) {
|
||||
final index = widget.dashboardViewModel.cardOrder[i];
|
||||
|
||||
if (index == null || index >= accounts.length) {
|
||||
// db order broken.
|
||||
reset();
|
||||
break;
|
||||
}
|
||||
|
||||
_items.add(AccountCustomizerListItem(
|
||||
card: BalanceCard(
|
||||
accountName: accounts[index].label,
|
||||
accountIndex: accounts[index].id,
|
||||
balance: accounts[index].balance ?? "0.00",
|
||||
accountBalance: accounts[index].balance ?? "0.00",
|
||||
designSwitchDuration: Duration.zero,
|
||||
assetName: widget.accountListViewModel.currency.title,
|
||||
onCustomizeTapped: (i == accounts.length - 1) ? _openCardCustomizer : null,
|
||||
selected: i == accounts.length - 1,
|
||||
width: cardWidth,
|
||||
design: widget.dashboardViewModel.cardDesigns[index],
|
||||
),
|
||||
order: index,
|
||||
accountListItem: accounts[index]));
|
||||
}
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_items.isEmpty) return SizedBox.shrink();
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(24))),
|
||||
child: Column(
|
||||
children: [
|
||||
ModalTopBar(
|
||||
title: S.of(context).wallet_accounts,
|
||||
leadingIcon: Icon(Icons.close),
|
||||
leadingSemanticLabel: S.of(context).close,
|
||||
onLeadingPressed: Navigator.of(context).maybePop,
|
||||
trailingIcon: Icon(Icons.refresh),
|
||||
trailingSemanticLabel: S.of(context).reset,
|
||||
onTrailingPressed: showResetDialog,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 24.0),
|
||||
child: Text(
|
||||
S.of(context).account_customizer_desc,
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Stack(
|
||||
children: [
|
||||
ReorderableListView.builder(
|
||||
padding: EdgeInsets.only(bottom: 196),
|
||||
scrollController: ModalScrollController.of(context),
|
||||
onReorder: reorder,
|
||||
proxyDecorator: (child, index, animation) {
|
||||
return AnimatedBuilder(
|
||||
animation: animation,
|
||||
builder: (context, _) {
|
||||
final animValue = Curves.easeOutCubic.transform(animation.value);
|
||||
final scale = lerpDouble(1, 1.05, animValue)!;
|
||||
|
||||
return Opacity(
|
||||
opacity: 1 - animValue.clamp(0.0, 0.1),
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: cardWidth,
|
||||
child: Transform.scale(
|
||||
scale: scale,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: _items[index].card,
|
||||
);
|
||||
},
|
||||
itemCount: _items.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final card = _items[index].card;
|
||||
// The stack is ordered bottom to top, so the last item
|
||||
// is the account currently in front — the selected one.
|
||||
final selectedItemIndex = _items.length - 1;
|
||||
|
||||
return Container(
|
||||
key: ValueKey(index),
|
||||
// One labeled, selectable node per account; the card's own
|
||||
// texts stay reachable underneath it.
|
||||
child: Semantics(
|
||||
button: true,
|
||||
selected: selectedItemIndex == index,
|
||||
label: _items[index].accountListItem.label,
|
||||
onTap: () => reorder(index, _items.length),
|
||||
child: GestureDetector(
|
||||
excludeFromSemantics: true,
|
||||
onTap: () {
|
||||
reorder(index, _items.length);
|
||||
},
|
||||
child: Align(
|
||||
alignment: Alignment.topCenter,
|
||||
heightFactor: _kStackVisibleFactor,
|
||||
child: card,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
SafeArea(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(bottom: 50),
|
||||
child: Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: MergeSemantics(
|
||||
child: Semantics(
|
||||
button: true,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(999999),
|
||||
onTap: _showAddAccountModal,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
borderRadius: BorderRadius.circular(999999)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 18.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
spacing: 8,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.add,
|
||||
size: 28,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
Text(
|
||||
S.of(context).add_account,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
fontWeight: FontWeight.w500),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)))),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
bool _checkReadyToManage() {
|
||||
if (widget.dashboardViewModel.status is! SyncedSyncStatus) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertWithOneAction(
|
||||
alertTitle: S.of(context).wallet_is_syncing,
|
||||
alertContent: S.of(context).cannot_manage_accounts_during_sync,
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: Navigator.of(context).pop));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<void> _showAddAccountModal() async {
|
||||
if (!_checkReadyToManage()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final res = await showCupertinoModalBottomSheet(
|
||||
context: context,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (context) {
|
||||
final modal = getIt.get<AccountCreationModal>();
|
||||
return Material(child: modal);
|
||||
});
|
||||
if (res != null && res is bool && res == true) {
|
||||
await widget.dashboardViewModel.loadCardDesigns();
|
||||
loadCards();
|
||||
await saveCardOrder();
|
||||
}
|
||||
}
|
||||
|
||||
void _openCardCustomizer() {
|
||||
if (!_checkReadyToManage()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final bloc = getIt.get<CardCustomizerBloc>(param1: false);
|
||||
|
||||
Navigator.of(context).push(CupertinoPageRoute(
|
||||
builder: (context) {
|
||||
return BlocProvider(
|
||||
create: (context) => bloc,
|
||||
child: Material(
|
||||
child: CardCustomizer(
|
||||
cryptoTitle: widget.dashboardViewModel.wallet.currency.fullName ??
|
||||
widget.dashboardViewModel.wallet.currency.name,
|
||||
cryptoName: widget.dashboardViewModel.wallet.currency.name,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
)).then((_) async {
|
||||
bloc.add(DesignSaved());
|
||||
await bloc.stream.firstWhere((item) => item is CardCustomizerSaved);
|
||||
await widget.dashboardViewModel.loadCardDesigns();
|
||||
loadCards();
|
||||
});
|
||||
}
|
||||
|
||||
void reorder(int oldIndex, int newIndex) {
|
||||
setState(() {
|
||||
if (oldIndex < newIndex) {
|
||||
newIndex -= 1;
|
||||
}
|
||||
final AccountCustomizerListItem item = _items.removeAt(oldIndex);
|
||||
_items.insert(newIndex, item);
|
||||
});
|
||||
|
||||
// necessary to copy all this to keep constant constructor for BalanceCard
|
||||
for (int i = 0; i < _items.length; i++) {
|
||||
_items[i] = AccountCustomizerListItem(
|
||||
card: BalanceCard(
|
||||
accountName: _items[i].card.accountName,
|
||||
balance: _items[i].card.balance,
|
||||
accountIndex: _items[i].card.accountIndex,
|
||||
accountBalance: _items[i].card.accountBalance,
|
||||
assetName: _items[i].card.assetName,
|
||||
designSwitchDuration: _items[i].card.designSwitchDuration,
|
||||
onCustomizeTapped: (i == _items.length - 1) ? _openCardCustomizer : null,
|
||||
selected: i == _items.length - 1,
|
||||
width: _items[i].card.width,
|
||||
design: _items[i].card.design,
|
||||
),
|
||||
order: i,
|
||||
accountListItem: _items[i].accountListItem);
|
||||
}
|
||||
|
||||
if (newIndex == _items.length - 1 || oldIndex == _items.length - 1) {
|
||||
widget.accountListViewModel.select(_items[_items.length - 1].accountListItem);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> saveCardOrder() async {
|
||||
for (int i = 0; i < _items.length; i++) {
|
||||
final idx = _items.indexWhere((element) => element.accountListItem.id == i);
|
||||
printV("$i: $idx");
|
||||
|
||||
await BalanceCardStyleSettings.fromCardDesign(
|
||||
walletInfoId: widget.dashboardViewModel.wallet.walletInfo.internalId,
|
||||
accountIndex: i,
|
||||
cardOrder: idx,
|
||||
design: _items[idx].card.design)
|
||||
.insert();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> showResetDialog() async {
|
||||
final res = await showPopUp(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertWithTwoActions(
|
||||
alertTitle: S.of(context).reset,
|
||||
alertContent: S.of(context).card_order_reset_desc,
|
||||
leftButtonText: S.of(context).yes,
|
||||
rightButtonText: S.of(context).no,
|
||||
actionLeftButton: () {
|
||||
Navigator.of(context).pop(true);
|
||||
},
|
||||
actionRightButton: Navigator.of(context).pop);
|
||||
});
|
||||
if (res != null && res is bool && res) {
|
||||
reset();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> reset() async {
|
||||
_items.clear();
|
||||
|
||||
final accounts = widget.accountListViewModel.accounts;
|
||||
for (int i = 0; i < widget.accountListViewModel.accounts.length; i++) {
|
||||
_items.add(AccountCustomizerListItem(
|
||||
card: BalanceCard(
|
||||
accountName: accounts[i].label,
|
||||
accountIndex: accounts[i].id,
|
||||
balance: accounts[i].balance ?? "0.00",
|
||||
accountBalance: accounts[i].balance ?? "0.00",
|
||||
assetName: widget.accountListViewModel.currency.title,
|
||||
selected: true,
|
||||
designSwitchDuration: Duration(milliseconds: 200),
|
||||
width: cardWidth,
|
||||
design: i >= widget.dashboardViewModel.cardDesigns.length
|
||||
? CardDesign.genericDefault
|
||||
: widget.dashboardViewModel.cardDesigns[i],
|
||||
),
|
||||
order: i,
|
||||
accountListItem: accounts[i]));
|
||||
}
|
||||
|
||||
saveCardOrder();
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
class AccountCreationModal extends StatefulWidget {
|
||||
const AccountCreationModal({super.key, required this.accountEditOrCreateViewModel});
|
||||
|
||||
final MoneroAccountEditOrCreateViewModel accountEditOrCreateViewModel;
|
||||
|
||||
@override
|
||||
State<AccountCreationModal> createState() => _AccountCreationModalState();
|
||||
}
|
||||
|
||||
class _AccountCreationModalState extends State<AccountCreationModal> {
|
||||
final TextEditingController _controller = TextEditingController();
|
||||
bool _loading = false;
|
||||
|
||||
Future<void> _generateAccountName() async => _controller.text = await generateName();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(30))),
|
||||
child: SafeArea(
|
||||
top: false,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ModalGrabHandle(),
|
||||
ModalTopBar(title: S.of(context).create_account),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18.0),
|
||||
child: Column(
|
||||
spacing: 50,
|
||||
children: [
|
||||
SizedBox(),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
borderRadius: BorderRadius.circular(16)),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _controller,
|
||||
decoration: InputDecoration(hintText: S.of(context).account_name),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Semantics(
|
||||
button: true,
|
||||
label: S.of(context).generate_name,
|
||||
onTap: _generateAccountName,
|
||||
child: ExcludeSemantics(
|
||||
child: GestureDetector(
|
||||
onTap: _generateAccountName,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
borderRadius: BorderRadius.circular(5)),
|
||||
child: CakeImageWidget(
|
||||
imageUrl: "assets/new-ui/randomize.svg",
|
||||
colorFilter: ColorFilter.mode(
|
||||
Theme.of(context).colorScheme.primary, BlendMode.srcIn),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
NewPrimaryButton(
|
||||
onPressed: () async {
|
||||
if (_loading) return;
|
||||
setState(() {
|
||||
_loading = true;
|
||||
});
|
||||
widget.accountEditOrCreateViewModel.label = _controller.text;
|
||||
await widget.accountEditOrCreateViewModel.save();
|
||||
Navigator.of(context).pop(true);
|
||||
},
|
||||
text: S.of(context).continue_text,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
isLoading: _loading,
|
||||
),
|
||||
SizedBox(),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -87,7 +87,12 @@ class _NewAddressesPageState extends State<NewAddressesPage> {
|
||||
});
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
reaction((context) => widget.dashboardViewModel.cardDesigns.first, (value) {
|
||||
reaction((_) {
|
||||
final index = widget.dashboardViewModel.accountListViewModel?.selectedAccount?.id ?? 0;
|
||||
final designs = widget.dashboardViewModel.cardDesigns;
|
||||
return (index >= 0 && index < designs.length) ? designs[index] : null;
|
||||
}, (value) {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
design = value;
|
||||
});
|
||||
@@ -130,8 +135,7 @@ class _NewAddressesPageState extends State<NewAddressesPage> {
|
||||
child: Column(
|
||||
spacing: 16,
|
||||
children: [
|
||||
if (widget.dashboardViewModel.type == WalletType.monero ||
|
||||
widget.dashboardViewModel.type == WalletType.wownero)
|
||||
if (widget.dashboardViewModel.isMultiAccountsEnabled)
|
||||
Observer(
|
||||
builder: (_) => AccountPreviewHeader(
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
@@ -271,8 +275,7 @@ class AccountPreviewHeader extends StatelessWidget {
|
||||
final CardDesign? design;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
Widget build(BuildContext context) => Container(
|
||||
height: 64,
|
||||
width: MediaQuery.of(context).size.width * 0.9,
|
||||
decoration: BoxDecoration(
|
||||
@@ -295,9 +298,7 @@ class AccountPreviewHeader extends StatelessWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
monero?.getCurrentAccount(dashboardViewModel.wallet).label ??
|
||||
wownero?.getCurrentAccount(dashboardViewModel.wallet).label ??
|
||||
"",
|
||||
dashboardViewModel.accountListViewModel?.selectedAccount?.label ?? '',
|
||||
style: TextStyle(fontSize: 12, color: Theme.of(context).colorScheme.primary),
|
||||
),
|
||||
Text(
|
||||
@@ -325,7 +326,6 @@ class AccountPreviewHeader extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AddressRow extends StatelessWidget {
|
||||
|
||||
@@ -1,21 +1,77 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
import "dart:async";
|
||||
import "dart:math";
|
||||
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/new-ui/viewmodels/card_customizer/card_customizer_bloc.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/cards/balance_card.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/card_design.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import "package:cake_wallet/di.dart";
|
||||
import "package:cake_wallet/generated/i18n.dart";
|
||||
import "package:cake_wallet/new-ui/modal_navigator.dart";
|
||||
import "package:cake_wallet/new-ui/viewmodels/card_customizer/card_customizer_bloc.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/coins_page/cards/balance_card.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:cake_wallet/view_model/dashboard/dashboard_view_model.dart";
|
||||
import "package:cw_core/card_design.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_bloc/flutter_bloc.dart";
|
||||
import "package:modal_bottom_sheet/modal_bottom_sheet.dart";
|
||||
|
||||
class CardCustomizer extends StatefulWidget {
|
||||
const CardCustomizer({super.key, required this.cryptoTitle, required this.cryptoName});
|
||||
const CardCustomizer({required this.cryptoTitle, required this.cryptoName, super.key});
|
||||
|
||||
final String cryptoTitle;
|
||||
final String cryptoName;
|
||||
|
||||
static Future<void> show({
|
||||
required BuildContext context,
|
||||
required DashboardViewModel dashboardViewModel,
|
||||
required bool lightningMode,
|
||||
bool asModalSheet = true,
|
||||
VoidCallback? onSaved,
|
||||
}) async {
|
||||
final bloc = getIt.get<CardCustomizerBloc>(
|
||||
param1: lightningMode,
|
||||
param2: dashboardViewModel.settingsStore.displayAmountsInSatoshi,
|
||||
);
|
||||
|
||||
final customizer = BlocProvider(
|
||||
create: (_) => bloc,
|
||||
child: Material(
|
||||
child: CardCustomizer(
|
||||
cryptoTitle: dashboardViewModel.wallet.currency.fullName ??
|
||||
dashboardViewModel.wallet.currency.name,
|
||||
cryptoName: dashboardViewModel.wallet.currency.name,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (asModalSheet) {
|
||||
await CupertinoScaffold.showCupertinoModalBottomSheet(
|
||||
barrierColor: Colors.black.withAlpha(60),
|
||||
context: context,
|
||||
builder: (context) => ModalNavigator(
|
||||
parentContext: context,
|
||||
heightMode: ModalHeightModes.fullScreen,
|
||||
rootPage: customizer,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
await Navigator.of(context).push(
|
||||
MaterialPageRoute<void>(builder: (_) => customizer),
|
||||
);
|
||||
}
|
||||
|
||||
bloc.add(DesignSaved());
|
||||
|
||||
await bloc.stream.firstWhere(
|
||||
(state) => state is CardCustomizerSaved,
|
||||
orElse: () => bloc.state,
|
||||
);
|
||||
|
||||
await dashboardViewModel.loadCardDesigns();
|
||||
await dashboardViewModel.accountListViewModel?.reload();
|
||||
|
||||
onSaved?.call();
|
||||
}
|
||||
|
||||
@override
|
||||
State<CardCustomizer> createState() => _CardCustomizerState();
|
||||
}
|
||||
@@ -63,298 +119,303 @@ class _CardCustomizerState extends State<CardCustomizer> {
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocListener<CardCustomizerBloc, CardCustomizerState>(
|
||||
listenWhen: (previous, current) => previous.accountName != current.accountName,
|
||||
listener: (context, state) {
|
||||
if (accountNameController.text != state.accountName) {
|
||||
accountNameController.text = state.accountName;
|
||||
}
|
||||
},
|
||||
child: BlocBuilder<CardCustomizerBloc, CardCustomizerState>(
|
||||
builder: (context, state) {
|
||||
if (state is CardCustomizerNotLoaded) return SizedBox.shrink();
|
||||
return PopScope(
|
||||
child: SingleChildScrollView(
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
spacing: 25.0,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ModalTopBar(
|
||||
title: editEnabled ? S.of(context).edit_account : S.of(context).edit_card,
|
||||
leadingIcon: Icon(Icons.close),
|
||||
leadingSemanticLabel: S.of(context).close,
|
||||
// trailingIcon: editEnabled ? Icon(Icons.delete_forever) : null,
|
||||
onLeadingPressed: () => Navigator.of(context).maybePop(),
|
||||
// onTrailingPressed: () {},
|
||||
),
|
||||
if (editEnabled)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18.0),
|
||||
child: Column(
|
||||
spacing: 8.0,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(S.of(context).account_name),
|
||||
TextField(
|
||||
maxLength: 32,
|
||||
decoration: InputDecoration(counterText: ""),
|
||||
onChanged: (value) {
|
||||
context.read<CardCustomizerBloc>().add(AccountNameChanged(value));
|
||||
},
|
||||
controller: accountNameController,
|
||||
)
|
||||
],
|
||||
),
|
||||
Widget build(BuildContext context) => BlocListener<CardCustomizerBloc, CardCustomizerState>(
|
||||
listenWhen: (previous, current) => previous.accountName != current.accountName,
|
||||
listener: (context, state) {
|
||||
if (accountNameController.text != state.accountName) {
|
||||
accountNameController.text = state.accountName;
|
||||
}
|
||||
},
|
||||
child: BlocBuilder<CardCustomizerBloc, CardCustomizerState>(
|
||||
builder: (context, state) {
|
||||
if (state is CardCustomizerNotLoaded) return SizedBox.shrink();
|
||||
return PopScope(
|
||||
child: SingleChildScrollView(
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
spacing: 25.0,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ModalTopBar(
|
||||
title: editEnabled ? S.of(context).edit_account : S.of(context).edit_card,
|
||||
leadingIcon: Icon(Icons.close),
|
||||
leadingSemanticLabel: S.of(context).close,
|
||||
// trailingIcon: editEnabled ? Icon(Icons.delete_forever) : null,
|
||||
onLeadingPressed: () => Navigator.of(context).maybePop(),
|
||||
// onTrailingPressed: () {},
|
||||
),
|
||||
BalanceCard(
|
||||
width: min(MediaQuery.of(context).size.width * 0.87, 768),
|
||||
selected: true,
|
||||
designSwitchDuration: Duration(milliseconds: 300),
|
||||
accountName: editEnabled ? state.accountName : "",
|
||||
balance: "0.00",
|
||||
assetName: state.displaySats ? "sats" : widget.cryptoName,
|
||||
capitalizeAssetName: !state.displaySats,
|
||||
design: state.selectedDesign,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18.0),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
if (editEnabled)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18.0),
|
||||
child: Column(
|
||||
spacing: 8.0,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Column(
|
||||
spacing: 8.0,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(S.of(context).card_style),
|
||||
Container(
|
||||
height: 63,
|
||||
child: ListView.separated(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: state.availableDesigns.length,
|
||||
separatorBuilder: (context, index) {
|
||||
return SizedBox(width: 8.0);
|
||||
},
|
||||
itemBuilder: (context, index) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
context
|
||||
.read<CardCustomizerBloc>()
|
||||
.add(CardDesignSelected(index));
|
||||
},
|
||||
child: AnimatedContainer(
|
||||
duration: Duration(milliseconds: 300),
|
||||
decoration: ShapeDecoration(
|
||||
shape: RoundedSuperellipseBorder(
|
||||
side: BorderSide(
|
||||
color:
|
||||
((index == state.selectedDesignIndex)
|
||||
Text(S.of(context).account_name),
|
||||
TextField(
|
||||
maxLength: 32,
|
||||
decoration: InputDecoration(counterText: ""),
|
||||
onChanged: (value) {
|
||||
context.read<CardCustomizerBloc>().add(AccountNameChanged(value));
|
||||
},
|
||||
controller: accountNameController,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
BalanceCard(
|
||||
width: min(MediaQuery.of(context).size.width * 0.87, 768),
|
||||
selected: true,
|
||||
designSwitchDuration: Duration(milliseconds: 300),
|
||||
accountName: editEnabled ? state.accountName : "",
|
||||
balance: "0.00",
|
||||
assetName: state.displaySats ? "sats" : widget.cryptoName,
|
||||
capitalizeAssetName: !state.displaySats,
|
||||
design: state.selectedDesign,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18.0),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Column(
|
||||
spacing: 8.0,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(S.of(context).card_style),
|
||||
SizedBox(
|
||||
height: 63,
|
||||
child: ListView.separated(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: state.availableDesigns.length,
|
||||
separatorBuilder: (context, index) =>
|
||||
const SizedBox(width: 8),
|
||||
itemBuilder: (context, index) => GestureDetector(
|
||||
onTap: () {
|
||||
context
|
||||
.read<CardCustomizerBloc>()
|
||||
.add(CardDesignSelected(index));
|
||||
},
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
decoration: ShapeDecoration(
|
||||
shape: RoundedSuperellipseBorder(
|
||||
side: BorderSide(
|
||||
color: ((index ==
|
||||
state.selectedDesignIndex)
|
||||
? Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurface
|
||||
: Colors.transparent),
|
||||
width: 1),
|
||||
borderRadius:
|
||||
BorderRadiusGeometry.circular(12),
|
||||
width: 1),
|
||||
borderRadius:
|
||||
BorderRadiusGeometry.circular(12),
|
||||
),
|
||||
),
|
||||
child: AnimatedScale(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
scale: index == state.selectedDesignIndex
|
||||
? 0.94
|
||||
: 1,
|
||||
child: BalanceCard(
|
||||
width: 96,
|
||||
borderRadius: 10,
|
||||
selected: false,
|
||||
designSwitchDuration:
|
||||
const Duration(milliseconds: 300),
|
||||
design:
|
||||
_cardStylePreviewDesign(state, index),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: AnimatedScale(
|
||||
duration: Duration(milliseconds: 200),
|
||||
scale: index == state.selectedDesignIndex
|
||||
? 0.94
|
||||
: 1,
|
||||
child: BalanceCard(
|
||||
width: 96,
|
||||
borderRadius: 10,
|
||||
selected: false,
|
||||
designSwitchDuration:
|
||||
Duration(milliseconds: 300),
|
||||
design: _cardStylePreviewDesign(state, index),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
],
|
||||
)),
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 350),
|
||||
switchInCurve: Curves.easeOut,
|
||||
switchOutCurve: Curves.easeIn,
|
||||
transitionBuilder: (Widget child, Animation<double> animation) {
|
||||
return FadeTransition(
|
||||
)),
|
||||
),
|
||||
],
|
||||
)),
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 350),
|
||||
switchInCurve: Curves.easeOut,
|
||||
switchOutCurve: Curves.easeIn,
|
||||
transitionBuilder: (child, Animation<double> animation) =>
|
||||
FadeTransition(
|
||||
opacity: animation,
|
||||
child: SizeTransition(
|
||||
sizeFactor: animation,
|
||||
axisAlignment: -1,
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: _showIconStylePanel(state)
|
||||
? Column(
|
||||
key: const ValueKey('icon_style_panel'),
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 8),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: Column(
|
||||
spacing: 8.0,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(S.of(context).icon_style),
|
||||
SizedBox(
|
||||
height: 48,
|
||||
child: ListView.separated(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: state.availableIconPaths.length,
|
||||
separatorBuilder: (context, _) =>
|
||||
const SizedBox(width: 8.0),
|
||||
itemBuilder: (context, index) {
|
||||
final icon = state.availableIconPaths[index];
|
||||
final isSelected =
|
||||
index == state.selectedIconIndex;
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
context
|
||||
.read<CardCustomizerBloc>()
|
||||
.add(IconStyleSelected(index));
|
||||
},
|
||||
child: AnimatedContainer(
|
||||
duration:
|
||||
const Duration(milliseconds: 200),
|
||||
width: 48,
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
color: Theme.of(context).brightness ==
|
||||
Brightness.light
|
||||
? Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurfaceVariant
|
||||
.withAlpha(64)
|
||||
: Theme.of(context)
|
||||
.colorScheme
|
||||
.surfaceContainerHigh,
|
||||
border: Border.all(
|
||||
color: isSelected
|
||||
),
|
||||
child: _showIconStylePanel(state)
|
||||
? Column(
|
||||
key: const ValueKey("icon_style_panel"),
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 8),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: Column(
|
||||
spacing: 8.0,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(S.of(context).icon_style),
|
||||
SizedBox(
|
||||
height: 48,
|
||||
child: ListView.separated(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: state.availableIconPaths.length,
|
||||
separatorBuilder: (context, _) =>
|
||||
const SizedBox(width: 8.0),
|
||||
itemBuilder: (context, index) {
|
||||
final icon =
|
||||
state.availableIconPaths[index];
|
||||
final isSelected =
|
||||
index == state.selectedIconIndex;
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
context
|
||||
.read<CardCustomizerBloc>()
|
||||
.add(IconStyleSelected(index));
|
||||
},
|
||||
child: AnimatedContainer(
|
||||
duration:
|
||||
const Duration(milliseconds: 200),
|
||||
width: 48,
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
BorderRadius.circular(18),
|
||||
color: Theme.of(context).brightness ==
|
||||
Brightness.light
|
||||
? Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurface
|
||||
: Colors.transparent,
|
||||
width: 2,
|
||||
.onSurfaceVariant
|
||||
.withAlpha(64)
|
||||
: Theme.of(context)
|
||||
.colorScheme
|
||||
.surfaceContainerHigh,
|
||||
border: Border.all(
|
||||
color: isSelected
|
||||
? Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurface
|
||||
: Colors.transparent,
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: CakeImageWidget(
|
||||
imageUrl: icon.path),
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: CakeImageWidget(
|
||||
imageUrl: icon.path),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: const SizedBox.shrink(key: ValueKey("icon_style_hidden")),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
borderRadius: BorderRadius.circular(16)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Column(
|
||||
spacing: 8.0,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(S.of(context).color),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
child: Wrap(
|
||||
direction: Axis.horizontal,
|
||||
spacing: 4, // space between items in a row
|
||||
runSpacing: 8,
|
||||
children: List.generate(
|
||||
state.availableColors.length,
|
||||
(index) => Material(
|
||||
borderRadius:
|
||||
BorderRadius.circular(999999999),
|
||||
child: InkWell(
|
||||
borderRadius:
|
||||
BorderRadius.circular(999999999),
|
||||
onTap: () {
|
||||
context
|
||||
.read<CardCustomizerBloc>()
|
||||
.add(ColorSelected(index));
|
||||
},
|
||||
child: Stack(
|
||||
children: [
|
||||
AnimatedOpacity(
|
||||
duration: const Duration(
|
||||
milliseconds: 200),
|
||||
opacity: index ==
|
||||
state.selectedColorIndex
|
||||
? 1
|
||||
: 0,
|
||||
child: Container(
|
||||
width: 32,
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
BorderRadius.circular(
|
||||
99999999),
|
||||
border: Border.all(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurface))),
|
||||
),
|
||||
AnimatedScale(
|
||||
duration: const Duration(
|
||||
milliseconds: 200),
|
||||
scale: index ==
|
||||
state.selectedColorIndex
|
||||
? 0.8
|
||||
: 1,
|
||||
child: Container(
|
||||
width: 32,
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
BorderRadius.circular(
|
||||
99999999,
|
||||
),
|
||||
gradient: state
|
||||
.availableColors[index]),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: const SizedBox.shrink(key: ValueKey('icon_style_hidden')),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
borderRadius: BorderRadius.circular(16)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Column(
|
||||
spacing: 8.0,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(S.of(context).color),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
child: Wrap(
|
||||
direction: Axis.horizontal,
|
||||
spacing: 4, // space between items in a row
|
||||
runSpacing: 8,
|
||||
children: List.generate(state.availableColors.length,
|
||||
(index) {
|
||||
return Material(
|
||||
borderRadius: BorderRadius.circular(999999999),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(999999999),
|
||||
onTap: () {
|
||||
context
|
||||
.read<CardCustomizerBloc>()
|
||||
.add(ColorSelected(index));
|
||||
},
|
||||
child: Stack(
|
||||
children: [
|
||||
AnimatedOpacity(
|
||||
duration: Duration(milliseconds: 200),
|
||||
opacity: index == state.selectedColorIndex
|
||||
? 1
|
||||
: 0,
|
||||
child: Container(
|
||||
width: 32,
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
BorderRadius.circular(99999999),
|
||||
border: Border.all(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurface))),
|
||||
),
|
||||
AnimatedScale(
|
||||
duration: Duration(milliseconds: 200),
|
||||
scale: index == state.selectedColorIndex
|
||||
? 0.8
|
||||
: 1,
|
||||
child: Container(
|
||||
width: 32,
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
BorderRadius.circular(99999999),
|
||||
gradient:
|
||||
state.availableColors[index]),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
)),
|
||||
],
|
||||
)),
|
||||
)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
+205
-235
@@ -1,35 +1,40 @@
|
||||
import "dart:async";
|
||||
|
||||
import 'package:cake_wallet/core/auth_service.dart';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import "package:cake_wallet/bitcoin/bitcoin.dart";
|
||||
import "package:cake_wallet/core/auth_service.dart";
|
||||
import "package:cake_wallet/di.dart";
|
||||
import "package:cake_wallet/generated/i18n.dart";
|
||||
import "package:cake_wallet/main.dart";
|
||||
import 'package:cake_wallet/new-ui/modal_navigator.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/account_customizer.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/card_customizer.dart';
|
||||
import "package:cake_wallet/new-ui/modal_navigator.dart";
|
||||
import "package:cake_wallet/new-ui/pages/card_customizer.dart";
|
||||
import "package:cake_wallet/new-ui/pages/seed/seed_backup_reminder_page.dart";
|
||||
import 'package:cake_wallet/new-ui/pages/settings_page.dart';
|
||||
import 'package:cake_wallet/new-ui/viewmodels/card_customizer/card_customizer_bloc.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/action_row/coin_action_row.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/assets_history/assets_history_section.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/cards/cards_view.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/mweb_ad.dart';
|
||||
import "package:cake_wallet/new-ui/pages/send_page.dart";
|
||||
import "package:cake_wallet/new-ui/pages/settings_page.dart";
|
||||
import "package:cake_wallet/new-ui/pages/wallet_accounts_page.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/buy_sell/buy_sell_selector_modal.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/coins_page/action_row/coin_action_row.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/coins_page/assets_history/assets_history_section.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/coins_page/cards/balance_card.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/coins_page/cards/cards_view.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/coins_page/mweb_ad.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/coins_page/seed_backup_reminder_card.dart";
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/top_bar_widget/top_bar.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/unconfirmed_balance_widget.dart';
|
||||
import "package:cake_wallet/new-ui/widgets/coins_page/top_bar_widget/top_bar.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/coins_page/unconfirmed_balance_widget.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/coins_page/zcash_migration_modal.dart";
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/monero_account_list/monero_account_list_view_model.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
|
||||
import "package:cake_wallet/routes.dart";
|
||||
import "package:cake_wallet/utils/payment_request.dart";
|
||||
import "package:cake_wallet/view_model/dashboard/dashboard_view_model.dart";
|
||||
import "package:cake_wallet/view_model/dashboard/nft_view_model.dart";
|
||||
import "package:cw_core/unspent_coin_type.dart";
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
import "package:flutter/cupertino.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_mobx/flutter_mobx.dart";
|
||||
import "package:mobx/mobx.dart";
|
||||
import "package:modal_bottom_sheet/modal_bottom_sheet.dart";
|
||||
|
||||
class NewHomePage extends StatefulWidget {
|
||||
NewHomePage({super.key, required this.dashboardViewModel, required this.nftViewModel});
|
||||
const NewHomePage({required this.dashboardViewModel, required this.nftViewModel, super.key});
|
||||
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
final NFTViewModel nftViewModel;
|
||||
@@ -39,20 +44,15 @@ class NewHomePage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _NewHomePageState extends State<NewHomePage> with RouteAware {
|
||||
MoneroAccountListViewModel? accountListViewModel;
|
||||
bool _lightningMode = false;
|
||||
late final ReactionDisposer _walletReaction;
|
||||
late final ReactionDisposer _migrationReaction;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_setAccountViewModel();
|
||||
|
||||
_walletReaction = reaction((_) => widget.dashboardViewModel.wallet, (_) {
|
||||
_setAccountViewModel();
|
||||
setState(() {
|
||||
_lightningMode = false;
|
||||
});
|
||||
setState(() {});
|
||||
});
|
||||
|
||||
_migrationReaction = reaction((_) => widget.dashboardViewModel.isMigratingToIronwood, (val) {
|
||||
@@ -91,178 +91,183 @@ class _NewHomePageState extends State<NewHomePage> with RouteAware {
|
||||
@override
|
||||
void didPopNext() => widget.dashboardViewModel.loadSeedBackupReminder();
|
||||
|
||||
void _setAccountViewModel() {
|
||||
accountListViewModel = widget.dashboardViewModel.balanceViewModel.hasAccounts
|
||||
? getIt.get<MoneroAccountListViewModel>()
|
||||
: null;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: MediaQuery.of(context).size.height,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Theme.of(context).colorScheme.surface,
|
||||
Theme.of(context).colorScheme.surfaceDim,
|
||||
],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
Widget build(BuildContext context) => Container(
|
||||
height: MediaQuery.of(context).size.height,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Theme.of(context).colorScheme.surface,
|
||||
Theme.of(context).colorScheme.surfaceDim,
|
||||
],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
CustomScrollView(
|
||||
physics: BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
|
||||
sliver: CupertinoSliverRefreshControl(
|
||||
refreshTriggerPullDistance: 160,
|
||||
refreshIndicatorExtent: 90,
|
||||
onRefresh: () {
|
||||
unawaited(widget.nftViewModel.getNFTAssetByWallet());
|
||||
child: Stack(
|
||||
children: [
|
||||
CustomScrollView(
|
||||
physics: const BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
|
||||
sliver: CupertinoSliverRefreshControl(
|
||||
refreshTriggerPullDistance: 160,
|
||||
refreshIndicatorExtent: 90,
|
||||
onRefresh: () {
|
||||
unawaited(widget.nftViewModel.getNFTAssetByWallet());
|
||||
|
||||
return widget.dashboardViewModel.refreshDashboard();
|
||||
},
|
||||
return widget.dashboardViewModel.refreshDashboard();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
spacing: 24.0,
|
||||
children: [
|
||||
TopBar(
|
||||
key: ValueKey(widget.dashboardViewModel.wallet.id),
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
lightningMode: _lightningMode,
|
||||
onLightningSwitchPress: () {
|
||||
setState(() {
|
||||
_lightningMode = !_lightningMode;
|
||||
});
|
||||
},
|
||||
onSettingsButtonPress: () async {
|
||||
await CupertinoScaffold.showCupertinoModalBottomSheet(
|
||||
context: context,
|
||||
barrierColor: Colors.black.withAlpha(85),
|
||||
builder: (context) => FractionallySizedBox(
|
||||
child: Material(
|
||||
child: NewSettingsPage(
|
||||
SliverToBoxAdapter(
|
||||
child: Observer(
|
||||
builder: (_) {
|
||||
final _lightningMode = widget.dashboardViewModel.lightningMode;
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
spacing: 24.0,
|
||||
children: [
|
||||
TopBar(
|
||||
key: ValueKey(widget.dashboardViewModel.wallet.id),
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
authService: getIt.get<AuthService>(),
|
||||
))),
|
||||
);
|
||||
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
_setAccountViewModel();
|
||||
await widget.dashboardViewModel.loadCardDesigns();
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
),
|
||||
Column(
|
||||
spacing: 20,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Observer(
|
||||
builder: (_) => CardsView(
|
||||
key: ValueKey(widget.dashboardViewModel.wallet.name),
|
||||
onCustomizeTapped: openCardCustomizer,
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
accountListViewModel: accountListViewModel,
|
||||
onCompactModeBackgroundCardsTapped: openAccountCustomizer,
|
||||
lightningMode: _lightningMode,
|
||||
onLightningSwitchPress: () {
|
||||
widget.dashboardViewModel.toggleLightningMode();
|
||||
},
|
||||
onSettingsButtonPress: () {
|
||||
CupertinoScaffold.showCupertinoModalBottomSheet(
|
||||
context: context,
|
||||
barrierColor: Colors.black.withAlpha(85),
|
||||
builder: (context) => FractionallySizedBox(
|
||||
child: Material(
|
||||
child: NewSettingsPage(
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
authService: getIt.get<AuthService>(),
|
||||
),
|
||||
),
|
||||
),
|
||||
).then((_) async {
|
||||
widget.dashboardViewModel.accountListViewModel?.reload();
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
await widget.dashboardViewModel.loadCardDesigns();
|
||||
if (mounted) setState(() {});
|
||||
});
|
||||
},
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
switchInCurve: Curves.easeOut,
|
||||
switchOutCurve: Curves.easeOut,
|
||||
layoutBuilder: (currentChild, previousChildren) => Stack(
|
||||
// the front (selected) card sits at the bottom of the stack box
|
||||
alignment: Alignment.bottomCenter,
|
||||
children: <Widget>[
|
||||
...previousChildren,
|
||||
if (currentChild != null) currentChild,
|
||||
],
|
||||
),
|
||||
child: KeyedSubtree(
|
||||
key: ValueKey(_lightningMode),
|
||||
child: CardsView(
|
||||
key: ValueKey(
|
||||
"${widget.dashboardViewModel.wallet.name}_${_lightningMode}_${widget.dashboardViewModel.accountListViewModel?.accounts.length ?? 0}_${widget.dashboardViewModel.cardDesigns.length}"),
|
||||
onCustomizeTapped: openCardCustomizer,
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
accountListViewModel:
|
||||
widget.dashboardViewModel.accountListViewModel,
|
||||
onCompactModeBackgroundCardsTapped: openAccountCustomizer,
|
||||
lightningMode: _lightningMode),
|
||||
),
|
||||
),
|
||||
),
|
||||
Observer(builder: (_) {
|
||||
return AnimatedSize(
|
||||
duration: Duration(milliseconds: 150),
|
||||
curve: Curves.easeInOutCubic,
|
||||
child: (widget.dashboardViewModel.shouldShowBalanceHiddenMessage)
|
||||
? Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 12,
|
||||
width: double.infinity,
|
||||
),
|
||||
Text(
|
||||
S.of(context).long_press_show_balance,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurfaceVariant),
|
||||
)
|
||||
],
|
||||
)
|
||||
: SizedBox(width: double.infinity),
|
||||
);
|
||||
}),
|
||||
UnconfirmedBalanceWidget(
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
),
|
||||
],
|
||||
),
|
||||
Observer(
|
||||
builder: (_) {
|
||||
return Column(
|
||||
children: [
|
||||
CoinActionRow(
|
||||
lightningMode: _lightningMode,
|
||||
showSwap: widget.dashboardViewModel.isEnabledSwapAction,
|
||||
walletType: widget.dashboardViewModel.wallet.type,
|
||||
),
|
||||
MwebAd(
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
),
|
||||
SeedBackupReminderCard(
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
onTap: openSeedBackupReminder,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
Observer(
|
||||
builder: (_) => AnimatedSize(
|
||||
duration: const Duration(milliseconds: 150),
|
||||
curve: Curves.easeInOutCubic,
|
||||
child: (widget.dashboardViewModel
|
||||
.shouldShowBalanceHiddenMessage)
|
||||
? Column(
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
width: double.infinity,
|
||||
),
|
||||
Text(
|
||||
S.of(context).long_press_show_balance,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurfaceVariant),
|
||||
)
|
||||
],
|
||||
)
|
||||
: const SizedBox(width: double.infinity),
|
||||
)),
|
||||
UnconfirmedBalanceWidget(
|
||||
dashboardViewModel: widget.dashboardViewModel),
|
||||
SeedBackupReminderCard(
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
onTap: openSeedBackupReminder,
|
||||
),
|
||||
],
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
CoinActionRow(
|
||||
lightningMode: _lightningMode,
|
||||
showSwap: widget.dashboardViewModel.isEnabledSwapAction,
|
||||
walletType: widget.dashboardViewModel.wallet.type,
|
||||
),
|
||||
MwebAd(
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
Observer(
|
||||
builder: (_) => AssetsHistorySection(
|
||||
nftViewModel: widget.nftViewModel,
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
Observer(
|
||||
builder: (_) => AssetsHistorySection(
|
||||
nftViewModel: widget.nftViewModel,
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
),
|
||||
),
|
||||
const SliverToBoxAdapter(
|
||||
child: SizedBox(height: 80.0),
|
||||
)
|
||||
]),
|
||||
Container(
|
||||
height: (MediaQuery.of(context).padding.top),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.bottomCenter,
|
||||
end: Alignment.topCenter,
|
||||
colors: <Color>[
|
||||
Theme.of(context).colorScheme.surface.withAlpha(5),
|
||||
Theme.of(context).colorScheme.surface.withAlpha(25),
|
||||
Theme.of(context).colorScheme.surface.withAlpha(50),
|
||||
Theme.of(context).colorScheme.surface.withAlpha(100),
|
||||
Theme.of(context).colorScheme.surface.withAlpha(150),
|
||||
Theme.of(context).colorScheme.surface.withAlpha(175),
|
||||
Theme.of(context).colorScheme.surface.withAlpha(200),
|
||||
],
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: SizedBox(height: 80.0),
|
||||
)
|
||||
]),
|
||||
Container(
|
||||
height: (MediaQuery.of(context).padding.top),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.bottomCenter,
|
||||
end: Alignment.topCenter,
|
||||
colors: <Color>[
|
||||
Theme.of(context).colorScheme.surface.withAlpha(5),
|
||||
Theme.of(context).colorScheme.surface.withAlpha(25),
|
||||
Theme.of(context).colorScheme.surface.withAlpha(50),
|
||||
Theme.of(context).colorScheme.surface.withAlpha(100),
|
||||
Theme.of(context).colorScheme.surface.withAlpha(150),
|
||||
Theme.of(context).colorScheme.surface.withAlpha(175),
|
||||
Theme.of(context).colorScheme.surface.withAlpha(200),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
Future<void> openCardCustomizer() async {
|
||||
await CardCustomizer.show(
|
||||
context: context,
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
lightningMode: widget.dashboardViewModel.lightningMode,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -278,54 +283,19 @@ class _NewHomePageState extends State<NewHomePage> with RouteAware {
|
||||
}
|
||||
|
||||
Future<void> openAccountCustomizer() async {
|
||||
final accountList = accountListViewModel;
|
||||
if (accountList == null) {
|
||||
return;
|
||||
}
|
||||
if (widget.dashboardViewModel.accountListViewModel == null) return;
|
||||
|
||||
await CupertinoScaffold.showCupertinoModalBottomSheet(
|
||||
barrierColor: Colors.black.withAlpha(60),
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return ModalNavigator(
|
||||
parentContext: context,
|
||||
heightMode: ModalHeightModes.fullScreen,
|
||||
rootPage: Material(
|
||||
child: AccountCustomizer(
|
||||
accountListViewModel: accountList,
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
builder: (context) => ModalNavigator(
|
||||
parentContext: context,
|
||||
heightMode: ModalHeightModes.fullScreen,
|
||||
rootPage: Material(
|
||||
child: getIt.get<WalletAccountsPage>(param1: widget.dashboardViewModel),
|
||||
),
|
||||
),
|
||||
);
|
||||
await widget.dashboardViewModel.loadCardDesigns();
|
||||
}
|
||||
|
||||
void openCardCustomizer() async {
|
||||
final bloc = getIt.get<CardCustomizerBloc>(
|
||||
param1: _lightningMode,
|
||||
param2: widget.dashboardViewModel.settingsStore.displayAmountsInSatoshi);
|
||||
await CupertinoScaffold.showCupertinoModalBottomSheet(
|
||||
barrierColor: Colors.black.withAlpha(60),
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return ModalNavigator(
|
||||
parentContext: context,
|
||||
heightMode: ModalHeightModes.fullScreen,
|
||||
rootPage: BlocProvider(
|
||||
create: (context) => bloc,
|
||||
child: Material(
|
||||
child: CardCustomizer(
|
||||
cryptoTitle: widget.dashboardViewModel.wallet.currency.fullName ??
|
||||
widget.dashboardViewModel.wallet.currency.name,
|
||||
cryptoName: widget.dashboardViewModel.wallet.currency.name,
|
||||
)),
|
||||
));
|
||||
},
|
||||
);
|
||||
bloc.add(DesignSaved());
|
||||
await bloc.stream.firstWhere((s) => s is CardCustomizerSaved);
|
||||
widget.dashboardViewModel.loadCardDesigns();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +25,6 @@ bool _hasMweb(DashboardViewModel vm) => vm.hasMweb;
|
||||
|
||||
bool _hasWalletConnect(DashboardViewModel vm) => vm.hasWalletConnect;
|
||||
|
||||
bool _hasAccounts(DashboardViewModel vm) => vm.balanceViewModel.hasAccounts;
|
||||
|
||||
bool _requiresKeyImageSync(DashboardViewModel vm) =>
|
||||
vm.wallet.type == WalletType.monero &&
|
||||
[HardwareWalletType.cupcake, HardwareWalletType.trezor].contains(vm.wallet.hardwareWalletType);
|
||||
@@ -65,8 +63,8 @@ class SettingsSectionData {
|
||||
SettingsListItem(
|
||||
"assets/new-ui/settings_row_icons/accounts.svg",
|
||||
S.current.accounts,
|
||||
Routes.accountCustomizer,
|
||||
condition: _hasAccounts,
|
||||
Routes.walletAccountsPage,
|
||||
condition: (vm) => vm.wallet.hasAccountsSupport,
|
||||
routeArgsBuilder: (vm) => vm,
|
||||
),
|
||||
SettingsListItem(
|
||||
|
||||
@@ -0,0 +1,567 @@
|
||||
import "dart:async";
|
||||
import "dart:ui";
|
||||
|
||||
import "package:cake_wallet/di.dart";
|
||||
import "package:cake_wallet/entities/bitcoin_amount_display_mode.dart";
|
||||
import "package:cake_wallet/entities/new_ui_entities/list_item/list_item_toggle.dart";
|
||||
import "package:cake_wallet/generated/i18n.dart";
|
||||
import "package:cake_wallet/new-ui/pages/card_customizer.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/coins_page/cards/balance_card.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart";
|
||||
import "package:cake_wallet/src/screens/settings/widgets/account_creation_modal.dart";
|
||||
import "package:cake_wallet/src/widgets/alert_with_one_action.dart";
|
||||
import "package:cake_wallet/src/widgets/alert_with_two_actions.dart";
|
||||
import "package:cake_wallet/src/widgets/new_list_row/new_list_section.dart";
|
||||
import "package:cake_wallet/utils/show_pop_up.dart";
|
||||
import "package:cake_wallet/view_model/dashboard/dashboard_view_model.dart";
|
||||
import "package:cake_wallet/view_model/wallet_account_list/account_edit_or_create_view_model.dart";
|
||||
import "package:cake_wallet/view_model/wallet_account_list/account_list_item.dart";
|
||||
import "package:cake_wallet/view_model/wallet_account_list/wallet_account_list_view_model.dart";
|
||||
import "package:cw_core/balance_card_style_settings.dart";
|
||||
import "package:cw_core/card_design.dart";
|
||||
import "package:cw_core/sync_status.dart";
|
||||
import "package:cw_core/utils/print_verbose.dart";
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:mobx/mobx.dart";
|
||||
import "package:modal_bottom_sheet/modal_bottom_sheet.dart";
|
||||
|
||||
class AccountCustomizerListItem {
|
||||
AccountCustomizerListItem({
|
||||
required this.card,
|
||||
required this.order,
|
||||
required this.accountListItem,
|
||||
});
|
||||
|
||||
final BalanceCard card;
|
||||
final int order;
|
||||
final AccountListItem accountListItem;
|
||||
}
|
||||
|
||||
class WalletAccountsPage extends StatefulWidget {
|
||||
const WalletAccountsPage({
|
||||
required this.accountListViewModel,
|
||||
required this.accountEditOrCreateViewModel,
|
||||
required this.dashboardViewModel,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final WalletAccountListViewModel accountListViewModel;
|
||||
final WalletAccountEditOrCreateViewModel accountEditOrCreateViewModel;
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
|
||||
@override
|
||||
State<WalletAccountsPage> createState() => _WalletAccountsPageState();
|
||||
}
|
||||
|
||||
class _WalletAccountsPageState extends State<WalletAccountsPage> {
|
||||
static const double _kStackVisibleFactor = 0.2;
|
||||
late final double cardWidth = MediaQuery.of(context).size.width * 0.9;
|
||||
|
||||
final List<AccountCustomizerListItem> _items = [];
|
||||
|
||||
ReactionDisposer? _accountsReaction;
|
||||
|
||||
WalletAccountListViewModel get accountListViewModel =>
|
||||
widget.dashboardViewModel.accountListViewModel ?? widget.accountListViewModel;
|
||||
|
||||
bool get _isMultiAccountsEnabled =>
|
||||
widget.dashboardViewModel.isMultiAccountsEnabled;
|
||||
|
||||
String get _assetName => widget.dashboardViewModel.appStore.amountParsingProxy
|
||||
.getCryptoSymbol(accountListViewModel.currency);
|
||||
|
||||
|
||||
bool get _capitalizeAssetName =>
|
||||
widget.dashboardViewModel.wallet.type != WalletType.bitcoin ||
|
||||
widget.dashboardViewModel.settingsStore.displayAmountsInSatoshi !=
|
||||
BitcoinAmountDisplayMode.satoshi;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_accountsReaction = reaction(
|
||||
(_) => accountListViewModel.accounts
|
||||
.map((account) => "${account.id}:${account.label}:${account.balance}")
|
||||
.join(","),
|
||||
(_) {
|
||||
if (!mounted) return;
|
||||
loadCards();
|
||||
},
|
||||
);
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
await widget.dashboardViewModel.loadCardDesigns();
|
||||
if (!mounted) return;
|
||||
loadCards();
|
||||
|
||||
if (!_isMultiAccountsEnabled) return;
|
||||
|
||||
final activeId = accountListViewModel.selectedAccount?.id;
|
||||
|
||||
for (int i = 0; i < _items.length - 1; i++) {
|
||||
if (activeId != null && _items[i].accountListItem.id == activeId) {
|
||||
final lastIndex = _items.length - 1;
|
||||
final temp = _items[i];
|
||||
_items[i] = _items[lastIndex];
|
||||
_items[lastIndex] = temp;
|
||||
|
||||
await saveCardOrder();
|
||||
await widget.dashboardViewModel.loadCardDesigns();
|
||||
loadCards();
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_accountsReaction?.call();
|
||||
accountListViewModel.reload();
|
||||
saveCardOrder().then((value) => widget.dashboardViewModel.loadCardDesigns());
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void loadCards() {
|
||||
final allAccounts = accountListViewModel.accounts;
|
||||
final visible = widget.dashboardViewModel.visibleAccounts;
|
||||
|
||||
if (visible.isEmpty && _items.isNotEmpty) return;
|
||||
|
||||
_items.clear();
|
||||
|
||||
if (!_isMultiAccountsEnabled) {
|
||||
if (visible.isNotEmpty) {
|
||||
final account = visible.first;
|
||||
final realIndex = allAccounts.indexWhere((a) => a.id == account.id);
|
||||
final design = (realIndex >= 0 &&
|
||||
realIndex < widget.dashboardViewModel.cardDesigns.length)
|
||||
? widget.dashboardViewModel.cardDesigns[realIndex]
|
||||
: CardDesign.genericDefault;
|
||||
|
||||
_items.add(AccountCustomizerListItem(
|
||||
card: BalanceCard(
|
||||
accountName: account.label,
|
||||
accountIndex: account.id,
|
||||
balance: account.balance ?? "0.00",
|
||||
accountBalance: account.balance ?? "0.00",
|
||||
designSwitchDuration: Duration.zero,
|
||||
assetName: _assetName,
|
||||
capitalizeAssetName: _capitalizeAssetName,
|
||||
selected: true,
|
||||
width: cardWidth,
|
||||
design: design,
|
||||
),
|
||||
order: realIndex >= 0 ? realIndex : 0,
|
||||
accountListItem: account,
|
||||
));
|
||||
}
|
||||
if (mounted) setState(() {});
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < allAccounts.length; i++) {
|
||||
final realIndex = widget.dashboardViewModel.cardOrder[i];
|
||||
|
||||
if (realIndex == null || realIndex < 0 || realIndex >= allAccounts.length) {
|
||||
// db order broken.
|
||||
reset();
|
||||
break;
|
||||
}
|
||||
|
||||
_items.add(AccountCustomizerListItem(
|
||||
card: BalanceCard(
|
||||
accountName: allAccounts[realIndex].label,
|
||||
accountIndex: allAccounts[realIndex].id,
|
||||
balance: allAccounts[realIndex].balance ?? "0.00",
|
||||
accountBalance: allAccounts[realIndex].balance ?? "0.00",
|
||||
designSwitchDuration: Duration.zero,
|
||||
assetName: _assetName,
|
||||
capitalizeAssetName: _capitalizeAssetName,
|
||||
selected: i == allAccounts.length - 1,
|
||||
width: cardWidth,
|
||||
design: widget.dashboardViewModel.cardDesigns[realIndex],
|
||||
),
|
||||
order: realIndex,
|
||||
accountListItem: allAccounts[realIndex]));
|
||||
}
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final showResetButton = _isMultiAccountsEnabled && _items.length > 1;
|
||||
final showAccountsToggle = widget.dashboardViewModel.canToggleMultiAccounts;
|
||||
final isToggleEnabled = widget.dashboardViewModel.isMultiAccountsEnabled;
|
||||
|
||||
if (_items.isEmpty && !showAccountsToggle) return const SizedBox.shrink();
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(24))),
|
||||
child: Column(
|
||||
children: [
|
||||
ModalTopBar(
|
||||
title: S.of(context).wallet_accounts,
|
||||
leadingIcon: const Icon(Icons.close),
|
||||
leadingSemanticLabel: S.of(context).close,
|
||||
onLeadingPressed: Navigator.of(context).maybePop,
|
||||
trailingIcon: showResetButton ? const Icon(Icons.refresh) : null,
|
||||
trailingSemanticLabel: S.of(context).reset,
|
||||
onTrailingPressed: showResetDialog,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
if (showAccountsToggle)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: NewListSections(
|
||||
sections: {
|
||||
"": [
|
||||
ListItemToggle(
|
||||
keyValue: S.of(context).multiple_accounts,
|
||||
label: S.of(context).multiple_accounts,
|
||||
value: isToggleEnabled,
|
||||
onChanged: (val) async {
|
||||
await widget.dashboardViewModel.setMultiAccountsEnabled(val);
|
||||
if (!mounted) return;
|
||||
loadCards();
|
||||
}),
|
||||
],
|
||||
},
|
||||
),
|
||||
),
|
||||
if (_isMultiAccountsEnabled && _items.isNotEmpty)
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 24.0),
|
||||
child: Text(
|
||||
S.of(context).account_customizer_desc,
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Stack(
|
||||
children: [
|
||||
ReorderableListView.builder(
|
||||
padding: const EdgeInsets.only(bottom: 196),
|
||||
scrollController: ModalScrollController.of(context),
|
||||
onReorder: reorder,
|
||||
proxyDecorator: (child, index, animation) => AnimatedBuilder(
|
||||
animation: animation,
|
||||
builder: (context, _) {
|
||||
final animValue = Curves.easeOutCubic.transform(animation.value);
|
||||
final scale = lerpDouble(1, 1.05, animValue)!;
|
||||
|
||||
return Opacity(
|
||||
opacity: 1 - animValue.clamp(0.0, 0.1),
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: cardWidth,
|
||||
child: Transform.scale(
|
||||
scale: scale,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: _items[index].card,
|
||||
),
|
||||
itemCount: _items.length,
|
||||
itemBuilder: (context, int index) {
|
||||
final card = _items[index].card;
|
||||
final selectedItemIndex = _items.length - 1;
|
||||
|
||||
return Container(
|
||||
key: ValueKey(index),
|
||||
child: Semantics(
|
||||
button: true,
|
||||
selected: selectedItemIndex == index,
|
||||
label: _items[index].accountListItem.label,
|
||||
onTap: () => reorder(index, _items.length),
|
||||
child: GestureDetector(
|
||||
excludeFromSemantics: true,
|
||||
onTap: () => reorder(index, _items.length),
|
||||
child: Align(
|
||||
alignment: Alignment.topCenter,
|
||||
heightFactor: _kStackVisibleFactor,
|
||||
child: card,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 50),
|
||||
child: Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: MergeSemantics(
|
||||
child: Semantics(
|
||||
button: true,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(999999),
|
||||
onTap: _showAddAccountModal,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
borderRadius: BorderRadius.circular(999999),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 18),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
spacing: 8,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.add,
|
||||
size: 28,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
Text(
|
||||
S.of(context).add_account,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
bool _checkReadyToManage() {
|
||||
if (widget.dashboardViewModel.wallet.type == WalletType.bitcoin) {
|
||||
return true;
|
||||
}
|
||||
if (widget.dashboardViewModel.status is! SyncedSyncStatus) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertWithOneAction(
|
||||
alertTitle: S.of(context).wallet_is_syncing,
|
||||
alertContent: S.of(context).cannot_manage_accounts_during_sync,
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: Navigator.of(context).pop,
|
||||
),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<void> _showAddAccountModal() async {
|
||||
if (!_checkReadyToManage()) return;
|
||||
|
||||
final createViewModel = getIt.get<WalletAccountEditOrCreateViewModel>();
|
||||
|
||||
final res = await showCupertinoModalBottomSheet(
|
||||
context: context,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (context) => Material(
|
||||
child: AccountCreationModal(
|
||||
viewModel: createViewModel,
|
||||
),
|
||||
));
|
||||
|
||||
if (res != null && res is bool && res == true) {
|
||||
await accountListViewModel.reload();
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
|
||||
final accounts = accountListViewModel.accounts;
|
||||
if (accounts.isNotEmpty) {
|
||||
final newestAccount = accounts.reduce((a, b) => a.id > b.id ? a : b);
|
||||
await accountListViewModel.select(newestAccount);
|
||||
}
|
||||
|
||||
await widget.dashboardViewModel.loadCardDesigns();
|
||||
loadCards();
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _openCardCustomizer() async {
|
||||
if (!_checkReadyToManage()) {
|
||||
return;
|
||||
}
|
||||
|
||||
await CardCustomizer.show(
|
||||
context: context,
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
lightningMode: widget.dashboardViewModel.lightningMode,
|
||||
asModalSheet: false,
|
||||
onSaved: _reloadAccountsAndCards,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _reloadAccountsAndCards() async {
|
||||
await accountListViewModel.reload();
|
||||
await widget.dashboardViewModel.loadCardDesigns();
|
||||
if (!mounted) return;
|
||||
loadCards();
|
||||
}
|
||||
|
||||
void _onCardsReordered(int oldRealIndex, int newRealIndex) {
|
||||
final accounts = accountListViewModel.accounts;
|
||||
if (oldRealIndex < 0 ||
|
||||
newRealIndex < 0 ||
|
||||
oldRealIndex >= accounts.length ||
|
||||
newRealIndex >= accounts.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
final oldAccountId = accounts[oldRealIndex].id;
|
||||
final newAccountId = accounts[newRealIndex].id;
|
||||
|
||||
final from = _items.indexWhere((item) => item.accountListItem.id == oldAccountId);
|
||||
final to = _items.indexWhere((item) => item.accountListItem.id == newAccountId);
|
||||
if (from < 0 || to < 0 || from == to) return;
|
||||
|
||||
final insertIndex = from < to ? to + 1 : to;
|
||||
reorder(from, insertIndex);
|
||||
|
||||
saveCardOrder().then((_) {
|
||||
if (!mounted) return;
|
||||
widget.dashboardViewModel.loadCardDesigns();
|
||||
});
|
||||
}
|
||||
|
||||
void reorder(int oldIndex, int newIndex) {
|
||||
if (!_isMultiAccountsEnabled) return;
|
||||
setState(() {
|
||||
if (oldIndex < newIndex) {
|
||||
newIndex -= 1;
|
||||
}
|
||||
final AccountCustomizerListItem item = _items.removeAt(oldIndex);
|
||||
_items.insert(newIndex, item);
|
||||
});
|
||||
|
||||
// necessary to copy all this to keep constant constructor for BalanceCard
|
||||
for (int i = 0; i < _items.length; i++) {
|
||||
_items[i] = AccountCustomizerListItem(
|
||||
card: BalanceCard(
|
||||
accountName: _items[i].card.accountName,
|
||||
balance: _items[i].card.balance,
|
||||
accountIndex: _items[i].card.accountIndex,
|
||||
accountBalance: _items[i].card.accountBalance,
|
||||
assetName: _items[i].card.assetName,
|
||||
capitalizeAssetName: _items[i].card.capitalizeAssetName,
|
||||
designSwitchDuration: _items[i].card.designSwitchDuration,
|
||||
onCustomizeTapped: (i == _items.length - 1) ? _openCardCustomizer : null,
|
||||
selected: i == _items.length - 1,
|
||||
width: _items[i].card.width,
|
||||
design: _items[i].card.design,
|
||||
),
|
||||
order: i,
|
||||
accountListItem: _items[i].accountListItem);
|
||||
}
|
||||
|
||||
if (newIndex == _items.length - 1 || oldIndex == _items.length - 1) {
|
||||
accountListViewModel.select(_items[_items.length - 1].accountListItem);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> saveCardOrder() async {
|
||||
|
||||
if (!_isMultiAccountsEnabled) return;
|
||||
|
||||
for (int orderIndex = 0; orderIndex < _items.length; orderIndex++) {
|
||||
final item = _items[orderIndex];
|
||||
printV("${item.accountListItem.id}: $orderIndex");
|
||||
|
||||
final existing = await BalanceCardStyleSettings.get(
|
||||
widget.dashboardViewModel.wallet.walletInfo.internalId,
|
||||
item.accountListItem.id,
|
||||
);
|
||||
|
||||
await BalanceCardStyleSettings.fromCardDesign(
|
||||
walletInfoId: widget.dashboardViewModel.wallet.walletInfo.internalId,
|
||||
accountIndex: item.accountListItem.id,
|
||||
cardOrder: orderIndex,
|
||||
design: item.card.design,
|
||||
iconStyleIndex: existing?.iconStyleIndex ?? 0,
|
||||
gradientIndexOverride: existing?.gradientIndex)
|
||||
.insert();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> showResetDialog() async {
|
||||
final res = await showPopUp(
|
||||
context: context,
|
||||
builder: (context) => AlertWithTwoActions(
|
||||
alertTitle: S.of(context).reset,
|
||||
alertContent: S.of(context).card_order_reset_desc,
|
||||
leftButtonText: S.of(context).yes,
|
||||
rightButtonText: S.of(context).no,
|
||||
actionLeftButton: () {
|
||||
Navigator.of(context).pop(true);
|
||||
},
|
||||
actionRightButton: Navigator.of(context).pop));
|
||||
if (res != null && res is bool && res) {
|
||||
reset();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> reset() async {
|
||||
_items.clear();
|
||||
|
||||
final accounts = accountListViewModel.accounts;
|
||||
for (int i = 0; i < accounts.length; i++) {
|
||||
_items.add(AccountCustomizerListItem(
|
||||
card: BalanceCard(
|
||||
accountName: accounts[i].label,
|
||||
accountIndex: accounts[i].id,
|
||||
balance: accounts[i].balance ?? "0.00",
|
||||
accountBalance: accounts[i].balance ?? "0.00",
|
||||
assetName: _assetName,
|
||||
capitalizeAssetName: _capitalizeAssetName,
|
||||
selected: i == accounts.length - 1,
|
||||
designSwitchDuration: const Duration(milliseconds: 200),
|
||||
width: cardWidth,
|
||||
design: i >= widget.dashboardViewModel.cardDesigns.length
|
||||
? CardDesign.genericDefault
|
||||
: widget.dashboardViewModel.cardDesigns[i],
|
||||
),
|
||||
order: i,
|
||||
accountListItem: accounts[i]));
|
||||
}
|
||||
|
||||
await saveCardOrder();
|
||||
|
||||
if (accounts.isNotEmpty) {
|
||||
await accountListViewModel.select(accounts.last);
|
||||
}
|
||||
|
||||
await widget.dashboardViewModel.loadCardDesigns();
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import "package:cake_wallet/bitcoin/bitcoin.dart";
|
||||
import 'package:cake_wallet/monero/monero.dart';
|
||||
import 'package:cake_wallet/wownero/wownero.dart';
|
||||
import "package:cw_core/balance_card_style_settings.dart";
|
||||
@@ -30,6 +31,9 @@ class CardCustomizerBloc extends Bloc<CardCustomizerEvent, CardCustomizerState>
|
||||
add(_Init());
|
||||
}
|
||||
|
||||
bool get _accountNameEnabled =>
|
||||
!lightningMode && _wallet.walletInfo.isMultiAccountsEnabled == true;
|
||||
|
||||
List<Gradient> _updateAvailableColors(CardDesign currentDesign) {
|
||||
final list = List<Gradient>.from(CardDesign.allGradients, growable: true);
|
||||
if (CardDesign.specialDesignsForCurrencies[_wallet.currency] != null) {
|
||||
@@ -83,23 +87,9 @@ class CardCustomizerBloc extends Bloc<CardCustomizerEvent, CardCustomizerState>
|
||||
}
|
||||
|
||||
void _init(_Init event, Emitter<CardCustomizerState> emit) async {
|
||||
late final account;
|
||||
if (_wallet.type == WalletType.monero) {
|
||||
account = monero!.getCurrentAccount(_wallet);
|
||||
} else if (_wallet.type == WalletType.wownero) {
|
||||
account = wownero!.getCurrentAccount(_wallet);
|
||||
} else {
|
||||
account = null;
|
||||
}
|
||||
final accountName = (account?.label ?? "") as String;
|
||||
late final int accountIndex;
|
||||
if (account != null) {
|
||||
accountIndex = account.id as int;
|
||||
} else if (lightningMode) {
|
||||
accountIndex = 0;
|
||||
} else {
|
||||
accountIndex = -1;
|
||||
}
|
||||
final accountInfo = await _getCurrentAccountInfo();
|
||||
final accountName = accountInfo.accountName;
|
||||
final accountIndex = accountInfo.accountIndex;
|
||||
final curr = lightningMode ? CryptoCurrency.btcln : _wallet.currency;
|
||||
final currentDesignSettings = await _loadCurrentDesignSettings(accountIndex);
|
||||
final currentDesign = CardDesign.fromStyleSettings(currentDesignSettings, curr);
|
||||
@@ -123,6 +113,39 @@ class CardCustomizerBloc extends Bloc<CardCustomizerEvent, CardCustomizerState>
|
||||
selectedIconIndex: selectedIconIndex));
|
||||
}
|
||||
|
||||
Future<({String accountName, int accountIndex})> _getCurrentAccountInfo() async {
|
||||
if (lightningMode) {
|
||||
return (accountName: "", accountIndex: -2);
|
||||
}
|
||||
|
||||
final account = await _resolveCurrentAccount();
|
||||
|
||||
if (!_accountNameEnabled) {
|
||||
return (accountName: "", accountIndex: account.accountIndex);
|
||||
}
|
||||
|
||||
return account;
|
||||
}
|
||||
|
||||
Future<({String accountName, int accountIndex})> _resolveCurrentAccount() async {
|
||||
if (_wallet.type == WalletType.monero) {
|
||||
final account = monero!.getCurrentAccount(_wallet);
|
||||
return (accountName: account.label, accountIndex: account.id);
|
||||
}
|
||||
|
||||
if (_wallet.type == WalletType.wownero) {
|
||||
final account = wownero!.getCurrentAccount(_wallet);
|
||||
return (accountName: account.label, accountIndex: account.id);
|
||||
}
|
||||
|
||||
if (_wallet.type == WalletType.bitcoin) {
|
||||
final account = await bitcoin!.getCurrentAccount(_wallet);
|
||||
return (accountName: account.label, accountIndex: account.id);
|
||||
}
|
||||
|
||||
return (accountName: "", accountIndex: -1);
|
||||
}
|
||||
|
||||
void _onDesignSelected(CardDesignSelected event, Emitter<CardCustomizerState> emit) {
|
||||
final newColors = _updateAvailableColors(state.availableDesigns[event.newDesignIndex]);
|
||||
late final int newColorIndex;
|
||||
@@ -152,32 +175,34 @@ class CardCustomizerBloc extends Bloc<CardCustomizerEvent, CardCustomizerState>
|
||||
emit(state.copyWith(accountName: event.newAccountName));
|
||||
}
|
||||
|
||||
void _onDesignSaved(DesignSaved event, Emitter<CardCustomizerState> emit) {
|
||||
BalanceCardStyleSettings.fromCardDesign(
|
||||
Future<void> _onDesignSaved(DesignSaved event, Emitter<CardCustomizerState> emit) async {
|
||||
await BalanceCardStyleSettings.fromCardDesign(
|
||||
walletInfoId: _wallet.walletInfo.internalId,
|
||||
accountIndex: state.accountIndex,
|
||||
cardOrder: state.cardOrder,
|
||||
design: state.selectedDesign,
|
||||
iconStyleIndex: state.selectedIconIndex,
|
||||
gradientIndexOverride: state.selectedColorIndex)
|
||||
.insert()
|
||||
.then((value) {
|
||||
emit(CardCustomizerSaved(
|
||||
state.selectedDesignIndex,
|
||||
state.selectedColorIndex,
|
||||
state.availableDesigns,
|
||||
state.availableColors,
|
||||
state.accountName,
|
||||
state.accountIndex,
|
||||
state.displaySats,
|
||||
state.cardOrder,
|
||||
availableIconPaths: state.availableIconPaths,
|
||||
selectedIconIndex: state.selectedIconIndex));
|
||||
});
|
||||
saveAccountName();
|
||||
.insert();
|
||||
|
||||
await saveAccountName();
|
||||
|
||||
emit(CardCustomizerSaved(
|
||||
state.selectedDesignIndex,
|
||||
state.selectedColorIndex,
|
||||
state.availableDesigns,
|
||||
state.availableColors,
|
||||
state.accountName,
|
||||
state.accountIndex,
|
||||
state.displaySats,
|
||||
state.cardOrder,
|
||||
availableIconPaths: state.availableIconPaths,
|
||||
selectedIconIndex: state.selectedIconIndex));
|
||||
}
|
||||
|
||||
Future<void> saveAccountName() async {
|
||||
if (!_accountNameEnabled) return;
|
||||
|
||||
if (_wallet.type == WalletType.monero) {
|
||||
await saveMoneroAccountName();
|
||||
}
|
||||
@@ -185,6 +210,9 @@ class CardCustomizerBloc extends Bloc<CardCustomizerEvent, CardCustomizerState>
|
||||
if (_wallet.type == WalletType.wownero) {
|
||||
await saveWowneroAccountName();
|
||||
}
|
||||
if (_wallet.type == WalletType.bitcoin && !lightningMode) {
|
||||
await saveBitcoinAccountName();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> saveMoneroAccountName() async {
|
||||
@@ -202,4 +230,11 @@ class CardCustomizerBloc extends Bloc<CardCustomizerEvent, CardCustomizerState>
|
||||
|
||||
await _wallet.save();
|
||||
}
|
||||
|
||||
Future<void> saveBitcoinAccountName() async {
|
||||
await _wallet.walletInfo.renameAccount(
|
||||
accountIndex: state.accountIndex,
|
||||
label: state.accountName,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,10 +215,12 @@ class BalanceCard extends StatelessWidget {
|
||||
child: AnimatedSwitcher(
|
||||
duration: designSwitchDuration,
|
||||
layoutBuilder: (currentChild, previousChildren) {
|
||||
final currentKey = currentChild?.key;
|
||||
return Stack(
|
||||
alignment: Alignment.centerLeft,
|
||||
children: <Widget>[
|
||||
...previousChildren,
|
||||
for (final child in previousChildren)
|
||||
if (child.key != currentKey) child,
|
||||
if (currentChild != null) currentChild,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -13,7 +13,7 @@ import 'package:cake_wallet/utils/feature_flag.dart';
|
||||
import 'package:cake_wallet/utils/payment_request.dart';
|
||||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/monero_account_list/monero_account_list_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_account_list/wallet_account_list_view_model.dart';
|
||||
import 'package:cw_core/card_design.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/unspent_coin_type.dart';
|
||||
@@ -30,14 +30,14 @@ import 'balance_card.dart';
|
||||
class CardsView extends StatefulWidget {
|
||||
const CardsView(
|
||||
{super.key,
|
||||
required this.dashboardViewModel,
|
||||
required this.accountListViewModel,
|
||||
required this.lightningMode,
|
||||
required this.onCompactModeBackgroundCardsTapped,
|
||||
required this.onCustomizeTapped});
|
||||
required this.dashboardViewModel,
|
||||
required this.accountListViewModel,
|
||||
required this.lightningMode,
|
||||
required this.onCompactModeBackgroundCardsTapped,
|
||||
required this.onCustomizeTapped});
|
||||
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
final MoneroAccountListViewModel? accountListViewModel;
|
||||
final WalletAccountListViewModel? accountListViewModel;
|
||||
final VoidCallback onCompactModeBackgroundCardsTapped;
|
||||
final VoidCallback onCustomizeTapped;
|
||||
final bool lightningMode;
|
||||
@@ -49,22 +49,39 @@ class CardsView extends StatefulWidget {
|
||||
class _CardsViewState extends State<CardsView> {
|
||||
late int _selectedIndex;
|
||||
bool isFirstBuild = true;
|
||||
ReactionDisposer? _cardOrderReaction;
|
||||
|
||||
int get _fallbackSelectedIndex =>
|
||||
max(0, widget.dashboardViewModel.cardOrder.length - 1);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_selectedIndex = widget.dashboardViewModel.cardOrder.length - 1;
|
||||
reaction(
|
||||
(_) => widget.dashboardViewModel.cardOrder.values.toList(),
|
||||
(_) => setState(() {
|
||||
_selectedIndex = widget.dashboardViewModel.cardOrder.length - 1;
|
||||
}));
|
||||
_selectedIndex = _fallbackSelectedIndex;
|
||||
_cardOrderReaction = reaction(
|
||||
(_) => widget.dashboardViewModel.cardOrder.values.toList(),
|
||||
(_) => setState(() {
|
||||
_selectedIndex = _fallbackSelectedIndex;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_cardOrderReaction?.call();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
static const Duration animDuration = Duration(milliseconds: 200);
|
||||
static const int compactModeTreshold = 4;
|
||||
static const int maxCards = 5;
|
||||
|
||||
/// No account name on the card: Lightning card, or Bitcoin with multi-accounts off.
|
||||
bool get _hideAccountInfo =>
|
||||
widget.lightningMode ||
|
||||
(widget.dashboardViewModel.wallet.type == WalletType.bitcoin &&
|
||||
!widget.dashboardViewModel.isMultiAccountsEnabled);
|
||||
|
||||
Widget _buildCard(int visualIndex, int realIndex, int numCards, double parentWidth,
|
||||
Map<int, int> order, bool compactMode, double overlapAmount) {
|
||||
final baseTop = overlapAmount * (numCards - 1);
|
||||
@@ -79,7 +96,7 @@ class _CardsViewState extends State<CardsView> {
|
||||
|
||||
final isSelected = _selectedIndex == visualIndex;
|
||||
final accounts = widget.accountListViewModel?.accounts;
|
||||
final cardLabel = (accounts != null && realIndex < accounts.length)
|
||||
final cardLabel = (!_hideAccountInfo && accounts != null && realIndex < accounts.length)
|
||||
? accounts[realIndex].label
|
||||
: S.of(context).balance;
|
||||
|
||||
@@ -89,7 +106,7 @@ class _CardsViewState extends State<CardsView> {
|
||||
widget.onCompactModeBackgroundCardsTapped();
|
||||
} else if (!compactMode) {
|
||||
setState(() {
|
||||
if (widget.accountListViewModel != null)
|
||||
if (widget.accountListViewModel != null && !widget.lightningMode)
|
||||
widget.accountListViewModel!.select(widget.accountListViewModel!.accounts[realIndex]);
|
||||
_selectedIndex = visualIndex;
|
||||
});
|
||||
@@ -121,9 +138,9 @@ class _CardsViewState extends State<CardsView> {
|
||||
label: cardLabel,
|
||||
hint: isSelected
|
||||
? (widget.dashboardViewModel.balanceViewModel.displayMode ==
|
||||
BalanceDisplayMode.hiddenBalance
|
||||
? S.of(context).long_press_show_balance
|
||||
: S.of(context).long_press_hide_balance)
|
||||
BalanceDisplayMode.hiddenBalance
|
||||
? S.of(context).long_press_show_balance
|
||||
: S.of(context).long_press_hide_balance)
|
||||
: null,
|
||||
onTap: onCardTap,
|
||||
onLongPress: isSelected ? onCardLongPress : null,
|
||||
@@ -168,13 +185,14 @@ class _CardsViewState extends State<CardsView> {
|
||||
realIndex >= widget.dashboardViewModel.cardDesigns.length)
|
||||
cardDesign = CardDesign.genericDefault;
|
||||
else if (widget.lightningMode)
|
||||
cardDesign = widget.dashboardViewModel.cardDesigns[realIndex + 1];
|
||||
// the lightning design is always the last one (after all bitcoin accounts)
|
||||
cardDesign = widget.dashboardViewModel.cardDesigns.last;
|
||||
else
|
||||
cardDesign = widget.dashboardViewModel.cardDesigns[realIndex];
|
||||
|
||||
final String accountName;
|
||||
final String accountBalance;
|
||||
if (account == null) {
|
||||
if (account == null || _hideAccountInfo) {
|
||||
accountName = "";
|
||||
accountBalance = "";
|
||||
} else {
|
||||
@@ -188,30 +206,30 @@ class _CardsViewState extends State<CardsView> {
|
||||
|
||||
final List<BalanceCardAction> actions = widget.lightningMode
|
||||
? [
|
||||
BalanceCardAction(
|
||||
label: S.current.bitcoin_lightning_deposit,
|
||||
icon: Icons.arrow_downward,
|
||||
onTap: depositToL2,
|
||||
),
|
||||
BalanceCardAction(
|
||||
label: S.current.bitcoin_lightning_withdraw,
|
||||
icon: Icons.arrow_upward,
|
||||
onTap: withdrawFromL2,
|
||||
)
|
||||
]
|
||||
BalanceCardAction(
|
||||
label: S.current.bitcoin_lightning_deposit,
|
||||
icon: Icons.arrow_downward,
|
||||
onTap: depositToL2,
|
||||
),
|
||||
BalanceCardAction(
|
||||
label: S.current.bitcoin_lightning_withdraw,
|
||||
icon: Icons.arrow_upward,
|
||||
onTap: withdrawFromL2,
|
||||
)
|
||||
]
|
||||
: widget.dashboardViewModel.isEnabledTradeAction
|
||||
? [
|
||||
BalanceCardAction(
|
||||
label: S.current.buy,
|
||||
icon: Icons.arrow_forward_ios_rounded,
|
||||
iconSize: 12,
|
||||
onTap: () {
|
||||
showModalBottomSheet(
|
||||
context: context, builder: (context) => BuySellSelectorModal());
|
||||
},
|
||||
)
|
||||
]
|
||||
: [];
|
||||
? [
|
||||
BalanceCardAction(
|
||||
label: S.current.buy,
|
||||
icon: Icons.arrow_forward_ios_rounded,
|
||||
iconSize: 12,
|
||||
onTap: () {
|
||||
showModalBottomSheet(
|
||||
context: context, builder: (context) => BuySellSelectorModal());
|
||||
},
|
||||
)
|
||||
]
|
||||
: [];
|
||||
|
||||
return BalanceCard(
|
||||
width: effectiveCardWidth,
|
||||
@@ -263,31 +281,55 @@ class _CardsViewState extends State<CardsView> {
|
||||
|
||||
double _getBoxHeight(int numCards, double overlapAmount) {
|
||||
return
|
||||
/* height of initial card */
|
||||
(2 / 3.2) * (effectiveCardWidth) +
|
||||
/* height of bg card * amount of bg cards */
|
||||
overlapAmount * ((numCards) - 1);
|
||||
/* height of initial card */
|
||||
(2 / 3.2) * (effectiveCardWidth) +
|
||||
/* height of bg card * amount of bg cards */
|
||||
overlapAmount * ((numCards) - 1);
|
||||
}
|
||||
|
||||
|
||||
int _visibleCardsCount() {
|
||||
if (widget.dashboardViewModel.wallet.type != WalletType.bitcoin) {
|
||||
return widget.dashboardViewModel.cardDesigns.length;
|
||||
}
|
||||
|
||||
if (widget.lightningMode || !widget.dashboardViewModel.isMultiAccountsEnabled) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return widget.accountListViewModel?.accounts.length ?? 1;
|
||||
}
|
||||
|
||||
int? _visualIndexForSelectedAccount(Map<int, int> order) {
|
||||
final vm = widget.accountListViewModel;
|
||||
if (vm == null || _hideAccountInfo) return null;
|
||||
|
||||
final selectedId = vm.selectedAccount?.id;
|
||||
final realIndex = vm.accounts.indexWhere((a) => a.id == selectedId);
|
||||
if (realIndex < 0) return null;
|
||||
|
||||
for (final entry in order.entries) {
|
||||
if (entry.value == realIndex) return entry.key;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Observer(builder: (context) {
|
||||
Widget build(BuildContext context) => Observer(builder: (context) {
|
||||
final parentWidth = MediaQuery.of(context).size.width;
|
||||
final children = <Widget>[];
|
||||
|
||||
int numCards = widget.dashboardViewModel.wallet.type == WalletType.bitcoin
|
||||
? 1
|
||||
: widget.dashboardViewModel.cardDesigns.length;
|
||||
int numCards = _visibleCardsCount();
|
||||
if (numCards == 0) numCards = 1;
|
||||
|
||||
if (_selectedIndex >= (numCards)) {
|
||||
if (_selectedIndex >= numCards) {
|
||||
_selectedIndex = 0;
|
||||
}
|
||||
|
||||
Map<int, int> order = widget.dashboardViewModel.cardOrder.length != numCards
|
||||
? Map<int, int>.fromEntries(
|
||||
List.generate(numCards, (i) => MapEntry(i, i)),
|
||||
)
|
||||
List.generate(numCards, (i) => MapEntry(i, i)),
|
||||
)
|
||||
: widget.dashboardViewModel.cardOrder;
|
||||
|
||||
for (int i = min(numCards - 1, maxCards); i >= 0; i--) {
|
||||
@@ -299,6 +341,9 @@ class _CardsViewState extends State<CardsView> {
|
||||
}
|
||||
}
|
||||
|
||||
final followIndex = _visualIndexForSelectedAccount(order);
|
||||
if (followIndex != null) _selectedIndex = followIndex;
|
||||
|
||||
final bool compactMode = numCards >= compactModeTreshold;
|
||||
final double overlapAmount = compactMode ? 5.0 : 46.0;
|
||||
for (int i = min(numCards - 1, maxCards); i >= 0; i--) {
|
||||
@@ -306,14 +351,6 @@ class _CardsViewState extends State<CardsView> {
|
||||
|
||||
int realIndex = order[visualIndex]!;
|
||||
|
||||
if (visualIndex == _selectedIndex &&
|
||||
widget.accountListViewModel != null &&
|
||||
realIndex < widget.accountListViewModel!.accounts.length &&
|
||||
widget.accountListViewModel?.selected.label !=
|
||||
widget.accountListViewModel?.accounts[realIndex].label) {
|
||||
widget.accountListViewModel!.select(widget.accountListViewModel!.accounts[realIndex]);
|
||||
}
|
||||
|
||||
children.add(_buildCard(
|
||||
visualIndex, realIndex, numCards, parentWidth, order, compactMode, overlapAmount));
|
||||
}
|
||||
@@ -331,7 +368,6 @@ class _CardsViewState extends State<CardsView> {
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> depositToL2() async {
|
||||
PaymentRequest? paymentRequest = null;
|
||||
@@ -343,7 +379,7 @@ class _CardsViewState extends State<CardsView> {
|
||||
}
|
||||
} else if (widget.dashboardViewModel.type == WalletType.bitcoin) {
|
||||
final depositAddress =
|
||||
await bitcoin!.getUnusedSpakDepositAddress(widget.dashboardViewModel.wallet);
|
||||
await bitcoin!.getUnusedSpakDepositAddress(widget.dashboardViewModel.wallet);
|
||||
if ((depositAddress?.isNotEmpty ?? false)) {
|
||||
paymentRequest = PaymentRequest.fromUri(Uri.parse("bitcoin:$depositAddress"));
|
||||
}
|
||||
@@ -352,10 +388,10 @@ class _CardsViewState extends State<CardsView> {
|
||||
if (widget.dashboardViewModel.type == WalletType.bitcoin) {
|
||||
final page = getIt.get<NewSendPage>(
|
||||
param1: SendPageParams(
|
||||
initialPaymentRequest: paymentRequest,
|
||||
unspentCoinType: UnspentCoinType.nonMweb,
|
||||
mode: SendPageModes.lightningDeposit,
|
||||
));
|
||||
initialPaymentRequest: paymentRequest,
|
||||
unspentCoinType: UnspentCoinType.nonMweb,
|
||||
mode: SendPageModes.lightningDeposit,
|
||||
));
|
||||
showCupertinoModalBottomSheet(
|
||||
context: context,
|
||||
barrierColor: Colors.black.withAlpha(128),
|
||||
@@ -399,10 +435,10 @@ class _CardsViewState extends State<CardsView> {
|
||||
if (widget.dashboardViewModel.type == WalletType.bitcoin) {
|
||||
final page = getIt.get<NewSendPage>(
|
||||
param1: SendPageParams(
|
||||
initialPaymentRequest: paymentRequest,
|
||||
unspentCoinType: unspentCoinType,
|
||||
mode: SendPageModes.lightningWithdrawal,
|
||||
));
|
||||
initialPaymentRequest: paymentRequest,
|
||||
unspentCoinType: unspentCoinType,
|
||||
mode: SendPageModes.lightningWithdrawal,
|
||||
));
|
||||
showCupertinoModalBottomSheet(
|
||||
context: context,
|
||||
barrierColor: Colors.black.withAlpha(128),
|
||||
@@ -425,4 +461,4 @@ class _CardsViewState extends State<CardsView> {
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,14 +16,12 @@ import 'package:mobx/mobx.dart';
|
||||
|
||||
class TopBar extends StatefulWidget {
|
||||
const TopBar({
|
||||
required this.lightningMode,
|
||||
required this.onLightningSwitchPress,
|
||||
required this.dashboardViewModel,
|
||||
required this.onSettingsButtonPress,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final bool lightningMode;
|
||||
final VoidCallback onLightningSwitchPress;
|
||||
final VoidCallback onSettingsButtonPress;
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
@@ -73,8 +71,8 @@ class _TopBarState extends State<TopBar> {
|
||||
|
||||
void _bindStatusReaction() {
|
||||
_statusReactionDisposer = reaction(
|
||||
(_) => widget.dashboardViewModel.status.runtimeType,
|
||||
(status) {
|
||||
(_) => widget.dashboardViewModel.status.runtimeType,
|
||||
(status) {
|
||||
syncedMessageTimer?.cancel();
|
||||
syncedMessageTimer = null;
|
||||
|
||||
@@ -107,78 +105,78 @@ class _TopBarState extends State<TopBar> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Padding(
|
||||
padding: EdgeInsets.only(left: 18, right: 18, top: 10 + _additionalTopPadding(context)),
|
||||
child: Observer(
|
||||
builder: (_) => Row(
|
||||
spacing: 12,
|
||||
children: [
|
||||
widget.dashboardViewModel.hasLightning
|
||||
? LightningSwitcher(
|
||||
lightningMode: widget.lightningMode,
|
||||
onLightningSwitchPress: widget.onLightningSwitchPress,
|
||||
)
|
||||
: ChainIcon(
|
||||
iconPath: widget.dashboardViewModel.wallet.currency.flatIconPath ?? "",
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
isSyncHeavy: widget.dashboardViewModel.isSyncHeavy,
|
||||
showSyncedMessage: showSyncedMessage,
|
||||
padding: EdgeInsets.only(
|
||||
bottom: 10, left: 18, right: 18, top: 10 + _additionalTopPadding(context)),
|
||||
child: Observer(
|
||||
builder: (_) => Row(
|
||||
spacing: 12,
|
||||
children: [
|
||||
widget.dashboardViewModel.hasLightning
|
||||
? LightningSwitcher(
|
||||
lightningMode: widget.dashboardViewModel.lightningMode,
|
||||
onLightningSwitchPress: widget.onLightningSwitchPress,
|
||||
)
|
||||
: ChainIcon(
|
||||
iconPath: widget.dashboardViewModel.wallet.currency.flatIconPath ?? "",
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
isSyncHeavy: widget.dashboardViewModel.isSyncHeavy,
|
||||
showSyncedMessage: showSyncedMessage,
|
||||
),
|
||||
Expanded(
|
||||
child: SizedBox(
|
||||
height: 36,
|
||||
child: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 150),
|
||||
reverseDuration: Duration.zero,
|
||||
layoutBuilder: (currentChild, previousChildren) => Stack(
|
||||
alignment: Alignment.centerLeft,
|
||||
children: [
|
||||
...previousChildren,
|
||||
if (currentChild != null) currentChild,
|
||||
],
|
||||
),
|
||||
child: replacesWalletName
|
||||
? SyncBar(
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
isSyncHeavy: widget.dashboardViewModel.isSyncHeavy,
|
||||
showSyncedMessage: showSyncedMessage,
|
||||
)
|
||||
: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: WalletInfoBar(
|
||||
name: widget.dashboardViewModel.wallet.name,
|
||||
hardwareWalletType: widget.dashboardViewModel.wallet.hardwareWalletType,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: SizedBox(
|
||||
height: 36,
|
||||
child: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 150),
|
||||
reverseDuration: Duration.zero,
|
||||
layoutBuilder: (currentChild, previousChildren) => Stack(
|
||||
alignment: Alignment.centerLeft,
|
||||
children: [
|
||||
...previousChildren,
|
||||
if (currentChild != null) currentChild,
|
||||
],
|
||||
),
|
||||
child: replacesWalletName
|
||||
? SyncBar(
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
isSyncHeavy: widget.dashboardViewModel.isSyncHeavy,
|
||||
showSyncedMessage: showSyncedMessage,
|
||||
)
|
||||
: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: WalletInfoBar(
|
||||
name: widget.dashboardViewModel.wallet.name,
|
||||
hardwareWalletType:
|
||||
widget.dashboardViewModel.wallet.hardwareWalletType,
|
||||
),
|
||||
),
|
||||
if (widget.dashboardViewModel.isTorEnabled) ...[
|
||||
const SizedBox(width: 6),
|
||||
SyncBar(
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
isSyncHeavy: widget.dashboardViewModel.isSyncHeavy,
|
||||
showSyncedMessage: showSyncedMessage,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
if (widget.dashboardViewModel.isTorEnabled) ...[
|
||||
const SizedBox(width: 6),
|
||||
SyncBar(
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
isSyncHeavy: widget.dashboardViewModel.isSyncHeavy,
|
||||
showSyncedMessage: showSyncedMessage,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
ModernButton.svg(
|
||||
key: const ValueKey("home_page_settings_button_key"),
|
||||
iconColor: Theme.of(context).colorScheme.primary,
|
||||
size: 36,
|
||||
onPressed: () {
|
||||
HapticFeedback.mediumImpact();
|
||||
widget.onSettingsButtonPress();
|
||||
},
|
||||
svgPath: "assets/new-ui/top-settings.svg",
|
||||
semanticLabel: S.of(context).settings_title,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
ModernButton.svg(
|
||||
key: const ValueKey("home_page_settings_button_key"),
|
||||
iconColor: Theme.of(context).colorScheme.primary,
|
||||
size: 36,
|
||||
onPressed: () {
|
||||
HapticFeedback.mediumImpact();
|
||||
widget.onSettingsButtonPress();
|
||||
},
|
||||
svgPath: "assets/new-ui/top-settings.svg",
|
||||
semanticLabel: S.of(context).settings_title,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
//FIXME remove after this gets fixed flutter-side
|
||||
double _additionalTopPadding(BuildContext context) {
|
||||
@@ -188,4 +186,4 @@ class _TopBarState extends State<TopBar> {
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,7 @@ import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class WalletInfoBar extends StatelessWidget {
|
||||
const WalletInfoBar({
|
||||
required this.name,
|
||||
required this.hardwareWalletType,
|
||||
super.key,
|
||||
});
|
||||
const WalletInfoBar({required this.name, required this.hardwareWalletType, super.key});
|
||||
|
||||
final String name;
|
||||
final HardwareWalletType? hardwareWalletType;
|
||||
@@ -22,6 +18,8 @@ class WalletInfoBar extends StatelessWidget {
|
||||
label: semanticsLabel,
|
||||
child: ExcludeSemantics(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 150),
|
||||
@@ -33,7 +31,7 @@ class WalletInfoBar extends StatelessWidget {
|
||||
child: hardwareWalletIcon == null
|
||||
? const SizedBox.shrink(key: ValueKey("empty"))
|
||||
: Padding(
|
||||
padding: const EdgeInsets.only(right: 4),
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: CakeImageWidget(
|
||||
imageUrl: hardwareWalletIcon!,
|
||||
key: const ValueKey("hardware_wallet_icon"),
|
||||
|
||||
+8
-9
@@ -4,7 +4,6 @@ import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
|
||||
import "package:cake_wallet/core/auth_service.dart";
|
||||
import 'package:cake_wallet/core/new_wallet_arguments.dart';
|
||||
import 'package:cake_wallet/new-ui/new_dashboard.dart';
|
||||
import "package:cake_wallet/new-ui/pages/account_customizer.dart";
|
||||
import 'package:cake_wallet/new-ui/pages/about_page.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/bridge/bridge_history_page.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/bridge/bridge_network_page.dart';
|
||||
@@ -51,6 +50,7 @@ import 'package:cake_wallet/src/screens/dev/monero_background_sync.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/moneroc_cache_debug.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/moneroc_call_profiler.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/network_requests.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/wallet_accounts_page.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:cake_wallet/utils/feature_flag.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/qr_tools_page.dart';
|
||||
@@ -129,8 +129,8 @@ import "package:cake_wallet/view_model/dashboard/nft_send_view_model.dart";
|
||||
import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/sign_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_account_list/account_list_item.dart';
|
||||
import 'package:cake_wallet/view_model/hardware_wallet/trezor_connect_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart';
|
||||
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_groups_display_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/seed_settings_view_model.dart';
|
||||
@@ -617,13 +617,6 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||
param1: args?['editingNode'] as Node?, param2: args?['isSelected'] as bool?);
|
||||
return CupertinoPageRoute<void>(builder: (_) => page);
|
||||
|
||||
case Routes.accountCustomizer:
|
||||
return handleRouteWithPlatformAwareness(
|
||||
(_) => getIt.get<AccountCustomizer>(
|
||||
param1: settings.arguments! as DashboardViewModel,
|
||||
),
|
||||
);
|
||||
|
||||
case Routes.addressBook:
|
||||
return handleRouteWithPlatformAwareness(
|
||||
(context) => getIt.get<ContactListPage>(),
|
||||
@@ -853,6 +846,12 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||
case Routes.manageNodes:
|
||||
return MaterialPageRoute<void>(builder: (_) => getIt.get<ManageNodesPage>(param1: false));
|
||||
|
||||
case Routes.walletAccountsPage:
|
||||
final dashboardViewModel = settings.arguments as DashboardViewModel;
|
||||
return handleRouteWithPlatformAwareness(
|
||||
(_) => getIt.get<WalletAccountsPage>(param1: dashboardViewModel),
|
||||
);
|
||||
|
||||
case Routes.managePowNodes:
|
||||
return MaterialPageRoute<void>(builder: (_) => getIt.get<ManageNodesPage>(param1: true));
|
||||
|
||||
|
||||
+1
-2
@@ -31,8 +31,6 @@ class Routes {
|
||||
static const newPowNode = '/new_pow_node_list';
|
||||
static const login = '/login';
|
||||
static const splash = '/splash';
|
||||
static const accountCreation = '/account_new';
|
||||
static const accountCustomizer = "/account_customizer";
|
||||
static const nanoAccountCreation = '/nano_account_new';
|
||||
static const addressBook = '/address_book';
|
||||
static const pickerAddressBook = '/picker_address_book';
|
||||
@@ -112,6 +110,7 @@ class Routes {
|
||||
static const setup2faInfoPage = '/setup_2fa_info_page';
|
||||
static const homeSettings = '/home_settings';
|
||||
static const editToken = '/edit_token';
|
||||
static const walletAccountsPage = '/wallet_accounts_page';
|
||||
static const manageNodes = '/manage_nodes';
|
||||
static const managePowNodes = '/manage_pow_nodes';
|
||||
static const walletConnectConnectionsListing = '/wallet-connect-connections-listing';
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
import "package:cake_wallet/core/execution_state.dart";
|
||||
import "package:cake_wallet/generated/i18n.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/modal_grab_handle.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/widgets/cake_image_widget.dart";
|
||||
import "package:cake_wallet/view_model/wallet_account_list/account_edit_or_create_view_model.dart";
|
||||
import "package:cw_core/generate_name.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter/services.dart";
|
||||
import "package:flutter_mobx/flutter_mobx.dart";
|
||||
|
||||
class AccountCreationModal extends StatefulWidget {
|
||||
const AccountCreationModal({
|
||||
required this.viewModel,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final WalletAccountEditOrCreateViewModel viewModel;
|
||||
|
||||
@override
|
||||
State<AccountCreationModal> createState() => _AccountCreationModalState();
|
||||
}
|
||||
|
||||
class _AccountCreationModalState extends State<AccountCreationModal> {
|
||||
static const int maxAccountNameLength = 25;
|
||||
|
||||
final TextEditingController _controller = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller.text = widget.viewModel.label;
|
||||
_controller.addListener(() {
|
||||
widget.viewModel.label = _controller.text;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _onSubmit() async {
|
||||
if (widget.viewModel.state is IsExecutingState) return;
|
||||
if (_controller.text.trim().isEmpty || _controller.text.length > maxAccountNameLength) return;
|
||||
|
||||
await widget.viewModel.save();
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
if (widget.viewModel.state is ExecutedSuccessfullyState) {
|
||||
Navigator.of(context).pop(true);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(30))),
|
||||
child: SafeArea(
|
||||
top: false,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const ModalGrabHandle(),
|
||||
ModalTopBar(
|
||||
title: widget.viewModel.isEdit
|
||||
? S.of(context).edit_account
|
||||
: S.of(context).create_account,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18),
|
||||
child: Column(
|
||||
spacing: 50,
|
||||
children: [
|
||||
const SizedBox(),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _controller,
|
||||
maxLength: maxAccountNameLength,
|
||||
inputFormatters: [
|
||||
LengthLimitingTextInputFormatter(maxAccountNameLength),
|
||||
],
|
||||
decoration: InputDecoration(
|
||||
hintText: S.of(context).account_name,
|
||||
counterText: "",
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: GestureDetector(
|
||||
onTap: () async {
|
||||
final generated = await generateName();
|
||||
_controller.text = generated.length > maxAccountNameLength
|
||||
? generated.substring(0, maxAccountNameLength)
|
||||
: generated;
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
borderRadius: BorderRadius.circular(5)),
|
||||
child: CakeImageWidget(
|
||||
imageUrl: "assets/new-ui/randomize.svg",
|
||||
colorFilter: ColorFilter.mode(
|
||||
Theme.of(context).colorScheme.primary, BlendMode.srcIn),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
ValueListenableBuilder<TextEditingValue>(
|
||||
valueListenable: _controller,
|
||||
builder: (context, value, _) => Observer(
|
||||
builder: (_) => NewPrimaryButton(
|
||||
onPressed: _onSubmit,
|
||||
text: S.of(context).continue_text,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
disabled: value.text.trim().isEmpty ||
|
||||
value.text.length > maxAccountNameLength,
|
||||
isLoading: widget.viewModel.state is IsExecutingState,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -159,7 +159,7 @@ abstract class BalanceViewModelBase with Store {
|
||||
bool get isEVMCompatible => isEVMCompatibleChain(wallet.type);
|
||||
|
||||
@computed
|
||||
bool get hasAccounts => [WalletType.monero, WalletType.wownero].contains(wallet.type);
|
||||
bool get hasAccounts => wallet.hasAccountsSupport;
|
||||
|
||||
@computed
|
||||
SortBalanceBy get sortBalanceBy => settingsStore.sortBalanceBy;
|
||||
|
||||
@@ -5,6 +5,9 @@ import 'dart:io' show Platform;
|
||||
import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/core/key_service.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_account_list/wallet_account_list_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_account_list/account_list_item.dart';
|
||||
import 'package:cw_core/account.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/date_section_item.dart';
|
||||
import "package:cw_core/balance_card_style_settings.dart";
|
||||
import 'package:cake_wallet/core/trade_monitor.dart';
|
||||
@@ -77,6 +80,7 @@ class DashboardViewModel = DashboardViewModelBase with _$DashboardViewModel;
|
||||
abstract class DashboardViewModelBase with Store {
|
||||
DashboardViewModelBase(
|
||||
{required this.balanceViewModel,
|
||||
required this.accountListViewModelFactory,
|
||||
required this.tradeMonitor,
|
||||
required this.appStore,
|
||||
required this.tradesStore,
|
||||
@@ -106,7 +110,7 @@ abstract class DashboardViewModelBase with Store {
|
||||
showDecredInfoCard = wallet.type == WalletType.decred &&
|
||||
(sharedPreferences.getBool(PreferencesKey.showDecredInfoCard) ?? true);
|
||||
showSeedBackupReminder = wallet.walletInfo.showSeedBackupReminder;
|
||||
|
||||
isMultiAccountsEnabled = wallet.walletInfo.isMultiAccountsEnabled == true;
|
||||
name = wallet.name;
|
||||
type = wallet.type;
|
||||
isShowFirstYatIntroduction = false;
|
||||
@@ -115,6 +119,7 @@ abstract class DashboardViewModelBase with Store {
|
||||
unawaited(isBackgroundSyncEnabled());
|
||||
unawaited(isBatteryOptimizationEnabled());
|
||||
unawaited(_loadConstraints());
|
||||
accountListViewModel = accountListViewModelFactory();
|
||||
final _wallet = wallet;
|
||||
|
||||
loadFilterItems();
|
||||
@@ -122,8 +127,7 @@ abstract class DashboardViewModelBase with Store {
|
||||
if (_wallet.type == WalletType.monero) {
|
||||
subname = monero!.getCurrentAccount(_wallet).label;
|
||||
|
||||
_onMoneroAccountChangeReaction = reaction(
|
||||
(_) => monero!.getMoneroWalletDetails(wallet).account,
|
||||
_onAccountChangeReaction = reaction((_) => monero!.getMoneroWalletDetails(wallet).account,
|
||||
(Account account) => _onMoneroAccountChange(_wallet));
|
||||
|
||||
_onMoneroBalanceChangeReaction = reaction(
|
||||
@@ -151,9 +155,9 @@ abstract class DashboardViewModelBase with Store {
|
||||
} else if (_wallet.type == WalletType.wownero) {
|
||||
subname = wow.wownero!.getCurrentAccount(_wallet).label;
|
||||
|
||||
_onMoneroAccountChangeReaction = reaction(
|
||||
_onAccountChangeReaction = reaction(
|
||||
(_) => wow.wownero!.getWowneroWalletDetails(wallet).account,
|
||||
(wow.Account account) => _onMoneroAccountChange(_wallet));
|
||||
(Account account) => _onMoneroAccountChange(_wallet));
|
||||
|
||||
_onMoneroBalanceChangeReaction = reaction(
|
||||
(_) => wow.wownero!.getWowneroWalletDetails(wallet).balance,
|
||||
@@ -179,21 +183,12 @@ abstract class DashboardViewModelBase with Store {
|
||||
),
|
||||
);
|
||||
} else {
|
||||
final sortedTransactions = [...wallet.transactionHistory.transactions.values];
|
||||
sortedTransactions.sort((a, b) => a.date.compareTo(b.date));
|
||||
|
||||
transactions = ObservableList.of(
|
||||
sortedTransactions.map(
|
||||
(transaction) => TransactionListItem(
|
||||
transaction: transaction,
|
||||
balanceViewModel: balanceViewModel,
|
||||
appStore: appStore,
|
||||
key: ValueKey('${_wallet.type.name}_transaction_history_item_${transaction.id}_key'),
|
||||
),
|
||||
),
|
||||
);
|
||||
subname = "";
|
||||
_reloadTransactions();
|
||||
}
|
||||
|
||||
_setupBitcoinAccountChangeReaction(_wallet);
|
||||
|
||||
// TODO: nano sub-account generation is disabled:
|
||||
// if (_wallet.type == WalletType.nano || _wallet.type == WalletType.banano) {
|
||||
// subname = nano!.getCurrentAccount(_wallet).label;
|
||||
@@ -201,7 +196,9 @@ abstract class DashboardViewModelBase with Store {
|
||||
|
||||
_walletChangeDisposer?.reaction.dispose();
|
||||
_walletChangeDisposer = reaction((_) => appStore.wallet, (wallet) {
|
||||
accountListViewModel = accountListViewModelFactory();
|
||||
_onWalletChange(wallet);
|
||||
resetLightningMode();
|
||||
_checkMweb();
|
||||
loadCardDesigns();
|
||||
showDecredInfoCard = wallet?.type == WalletType.decred &&
|
||||
@@ -362,8 +359,14 @@ abstract class DashboardViewModelBase with Store {
|
||||
|
||||
transactions.clear();
|
||||
|
||||
final filteredTransactions = wallet.type == WalletType.bitcoin
|
||||
? bitcoin!.getCurrentAccountBitcoinTransactions(wallet)
|
||||
: wallet.transactionHistory.transactions.values.toList();
|
||||
filteredTransactions.sort((a, b) => a.date.compareTo(b.date));
|
||||
|
||||
|
||||
transactions.addAll(
|
||||
wallet.transactionHistory.transactions.values.map(
|
||||
filteredTransactions.map(
|
||||
(transaction) => TransactionListItem(
|
||||
transaction: transaction,
|
||||
balanceViewModel: balanceViewModel,
|
||||
@@ -425,60 +428,67 @@ abstract class DashboardViewModelBase with Store {
|
||||
|
||||
@action
|
||||
Future<void> loadCardDesigns() async {
|
||||
final accountStyleSettings =
|
||||
final walletCardStyleSettings =
|
||||
await BalanceCardStyleSettings.getAll(wallet.walletInfo.internalId);
|
||||
final btcAccounts =
|
||||
wallet.type == WalletType.bitcoin ? await wallet.walletInfo.getAccounts() : null;
|
||||
final lightningCardIndex = btcAccounts?.length;
|
||||
|
||||
late final int numAccounts;
|
||||
int numAccounts = 1;
|
||||
if (wallet.type == WalletType.monero) {
|
||||
numAccounts = monero!.getAccountList(wallet).accounts.length;
|
||||
} else if (wallet.type == WalletType.wownero) {
|
||||
numAccounts = wow.wownero!.getAccountList(wallet).accounts.length;
|
||||
} else if (wallet.type == WalletType.bitcoin) {
|
||||
// bitcoin and lightning
|
||||
numAccounts = 2;
|
||||
} else {
|
||||
numAccounts = 1;
|
||||
numAccounts = btcAccounts!.length + 1; // adding 1 for the lightning account
|
||||
}
|
||||
|
||||
final usesAccountIndices = wallet.type == WalletType.bitcoin || balanceViewModel.hasAccounts;
|
||||
|
||||
cardDesigns.clear();
|
||||
Map<int, int> newOrder = {};
|
||||
final newOrder = <int, int>{};
|
||||
|
||||
final orderableCount = (wallet.type == WalletType.bitcoin) ? numAccounts - 1 : numAccounts;
|
||||
|
||||
for (int i = 0; i < numAccounts; i++) {
|
||||
late final int index;
|
||||
if (balanceViewModel.hasAccounts) {
|
||||
final isLightning = wallet.type == WalletType.bitcoin && i == lightningCardIndex;
|
||||
if (isLightning) {
|
||||
index = -2;
|
||||
} else if (usesAccountIndices) {
|
||||
index = i;
|
||||
} else if (wallet.type == WalletType.bitcoin && i == 1) {
|
||||
index = 0;
|
||||
} else {
|
||||
index = -1;
|
||||
}
|
||||
|
||||
final setting = accountStyleSettings.where((e) => e.accountIndex == index).firstOrNull;
|
||||
final setting = walletCardStyleSettings.where((e) => e.accountIndex == index).firstOrNull ??
|
||||
(index == 0 && wallet.type != WalletType.bitcoin
|
||||
? walletCardStyleSettings.where((e) => e.accountIndex == -1).firstOrNull
|
||||
: null);
|
||||
|
||||
late final CryptoCurrency curr;
|
||||
if (wallet.type == WalletType.bitcoin && i == 1) {
|
||||
curr = CryptoCurrency.btcln;
|
||||
} else {
|
||||
curr = wallet.currency;
|
||||
}
|
||||
final curr = isLightning ? CryptoCurrency.btcln : wallet.currency;
|
||||
|
||||
cardDesigns.add(CardDesign.fromStyleSettings(setting, curr));
|
||||
if (setting?.cardOrder != null) {
|
||||
newOrder[setting!.cardOrder] = i;
|
||||
|
||||
if (isLightning) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (setting?.cardOrder != null &&
|
||||
setting!.cardOrder >= 0 &&
|
||||
setting.cardOrder < orderableCount) {
|
||||
newOrder[setting.cardOrder] = i;
|
||||
}
|
||||
}
|
||||
|
||||
// making sure ALL accounts have numbers, even the ones that existed before this feature was a thing
|
||||
for (int i = 0; i < numAccounts; i++) {
|
||||
if (!newOrder.containsKey(i) && !(wallet.type != WalletType.bitcoin && i == 1)) {
|
||||
for (int i = 0; i < orderableCount; i++) {
|
||||
if (!newOrder.containsValue(i)) {
|
||||
int free = 0;
|
||||
while (newOrder.containsValue(free)) {
|
||||
while (newOrder.containsKey(free)) {
|
||||
free++;
|
||||
}
|
||||
if (wallet.type == WalletType.bitcoin) {
|
||||
newOrder[free] = 0;
|
||||
} else {
|
||||
newOrder[free] = i;
|
||||
}
|
||||
newOrder[free] = i;
|
||||
}
|
||||
}
|
||||
cardOrder = newOrder.asObservable();
|
||||
@@ -496,19 +506,22 @@ abstract class DashboardViewModelBase with Store {
|
||||
: wallet.type == WalletType.wownero
|
||||
? wow.wownero!.getCurrentAccount(wallet).id
|
||||
: null;
|
||||
final List<TransactionInfo> relevantTxs = [];
|
||||
|
||||
for (final tx in appStore.wallet!.transactionHistory.transactions.values) {
|
||||
bool isRelevant = true;
|
||||
if (wallet.type == WalletType.monero) {
|
||||
isRelevant = monero!.getTransactionInfoAccountId(tx) == currentAccountId;
|
||||
} else if (wallet.type == WalletType.wownero) {
|
||||
isRelevant = wow.wownero!.getTransactionInfoAccountId(tx) == currentAccountId;
|
||||
}
|
||||
final allTxs = appStore.wallet!.transactionHistory.transactions.values;
|
||||
final List<TransactionInfo> relevantTxs;
|
||||
|
||||
if (isRelevant) {
|
||||
relevantTxs.add(tx);
|
||||
}
|
||||
if (wallet.type == WalletType.monero) {
|
||||
relevantTxs = allTxs
|
||||
.where((tx) => monero!.getTransactionInfoAccountId(tx) == currentAccountId)
|
||||
.toList();
|
||||
} else if (wallet.type == WalletType.wownero) {
|
||||
relevantTxs = allTxs
|
||||
.where((tx) => wow.wownero!.getTransactionInfoAccountId(tx) == currentAccountId)
|
||||
.toList();
|
||||
} else if (wallet.type == WalletType.bitcoin) {
|
||||
relevantTxs = bitcoin!.getCurrentAccountBitcoinTransactions(wallet);
|
||||
} else {
|
||||
relevantTxs = allTxs.toList();
|
||||
}
|
||||
// printV("Transaction disposer callback (relevantTxs: ${relevantTxs.length} current: ${transactions.length})");
|
||||
|
||||
@@ -600,6 +613,12 @@ abstract class DashboardViewModelBase with Store {
|
||||
@observable
|
||||
ObservableMap<int, int> cardOrder;
|
||||
|
||||
@observable
|
||||
bool lightningMode = false;
|
||||
|
||||
@observable
|
||||
WalletAccountListViewModel? accountListViewModel;
|
||||
|
||||
@computed
|
||||
bool get isDarkTheme => appStore.themeStore.currentTheme.isDark;
|
||||
|
||||
@@ -896,6 +915,42 @@ abstract class DashboardViewModelBase with Store {
|
||||
@observable
|
||||
late bool showSeedBackupReminder;
|
||||
|
||||
@computed
|
||||
bool get canToggleMultiAccounts => wallet.canToggleMultiAccounts;
|
||||
|
||||
@observable
|
||||
late bool isMultiAccountsEnabled;
|
||||
|
||||
@computed
|
||||
List<AccountListItem> get visibleAccounts {
|
||||
final all = accountListViewModel?.accounts;
|
||||
if (all == null || all.isEmpty) return const <AccountListItem>[];
|
||||
if (isMultiAccountsEnabled) return all.toList(growable: false);
|
||||
final primary = all.where((a) => a.id == 0).firstOrNull ?? all.first;
|
||||
return <AccountListItem>[primary];
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> setMultiAccountsEnabled(bool value) async {
|
||||
if (!canToggleMultiAccounts) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
await _selectPrimaryAccount();
|
||||
}
|
||||
|
||||
wallet.walletInfo.isMultiAccountsEnabled = value;
|
||||
await wallet.walletInfo.save();
|
||||
isMultiAccountsEnabled = value;
|
||||
await accountListViewModel?.reload();
|
||||
await loadCardDesigns();
|
||||
|
||||
if (value) {
|
||||
unawaited(wallet.startSync());
|
||||
}
|
||||
}
|
||||
|
||||
@computed
|
||||
bool get hasBalance => wallet.balance.values.any(
|
||||
(balance) =>
|
||||
@@ -1161,6 +1216,8 @@ abstract class DashboardViewModelBase with Store {
|
||||
bitcoin!.updatePayjoinState(wallet, true);
|
||||
}
|
||||
|
||||
final WalletAccountListViewModel? Function() accountListViewModelFactory;
|
||||
|
||||
BalanceViewModel balanceViewModel;
|
||||
|
||||
TradeMonitor tradeMonitor;
|
||||
@@ -1215,7 +1272,7 @@ abstract class DashboardViewModelBase with Store {
|
||||
@computed
|
||||
bool get isEnabledBulletinAction => !settingsStore.disableBulletin;
|
||||
|
||||
ReactionDisposer? _onMoneroAccountChangeReaction;
|
||||
ReactionDisposer? _onAccountChangeReaction;
|
||||
|
||||
ReactionDisposer? _onMoneroBalanceChangeReaction;
|
||||
|
||||
@@ -1289,6 +1346,31 @@ abstract class DashboardViewModelBase with Store {
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
void toggleLightningMode() {
|
||||
lightningMode = !lightningMode;
|
||||
|
||||
// Lightning belongs to the primary account only
|
||||
if (lightningMode) {
|
||||
unawaited(_selectPrimaryAccount());
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _selectPrimaryAccount() async {
|
||||
if (wallet.type != WalletType.bitcoin) return;
|
||||
|
||||
final vm = accountListViewModel;
|
||||
if (vm == null || vm.selectedAccount?.id == 0) return;
|
||||
|
||||
final primary = vm.accounts.where((a) => a.id == 0).firstOrNull;
|
||||
if (primary != null) {
|
||||
await vm.select(primary);
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
void resetLightningMode() => lightningMode = false;
|
||||
|
||||
@action
|
||||
void _onWalletChange(
|
||||
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>? wallet) {
|
||||
@@ -1299,16 +1381,19 @@ abstract class DashboardViewModelBase with Store {
|
||||
this.wallet = wallet;
|
||||
type = wallet.type;
|
||||
name = wallet.name;
|
||||
isMultiAccountsEnabled = wallet.walletInfo.isMultiAccountsEnabled == true;
|
||||
|
||||
_onAccountChangeReaction?.reaction.dispose();
|
||||
_onAccountChangeReaction = null;
|
||||
loadFilterItems();
|
||||
|
||||
if (wallet.type == WalletType.monero) {
|
||||
subname = monero!.getCurrentAccount(wallet).label;
|
||||
|
||||
_onMoneroAccountChangeReaction?.reaction.dispose();
|
||||
_onAccountChangeReaction?.reaction.dispose();
|
||||
_onMoneroBalanceChangeReaction?.reaction.dispose();
|
||||
|
||||
_onMoneroAccountChangeReaction = reaction(
|
||||
(_) => monero!.getMoneroWalletDetails(wallet).account,
|
||||
_onAccountChangeReaction = reaction((_) => monero!.getMoneroWalletDetails(wallet).account,
|
||||
(Account account) => _onMoneroAccountChange(wallet));
|
||||
|
||||
_onMoneroBalanceChangeReaction = reaction(
|
||||
@@ -1319,12 +1404,12 @@ abstract class DashboardViewModelBase with Store {
|
||||
} else if (wallet.type == WalletType.wownero) {
|
||||
subname = wow.wownero!.getCurrentAccount(wallet).label;
|
||||
|
||||
_onMoneroAccountChangeReaction?.reaction.dispose();
|
||||
_onAccountChangeReaction?.reaction.dispose();
|
||||
_onMoneroBalanceChangeReaction?.reaction.dispose();
|
||||
|
||||
_onMoneroAccountChangeReaction = reaction(
|
||||
_onAccountChangeReaction = reaction(
|
||||
(_) => wow.wownero!.getWowneroWalletDetails(wallet).account,
|
||||
(wow.Account account) => _onMoneroAccountChange(wallet));
|
||||
(Account account) => _onMoneroAccountChange(wallet));
|
||||
|
||||
_onMoneroBalanceChangeReaction = reaction(
|
||||
(_) => wow.wownero!.getWowneroWalletDetails(wallet).balance,
|
||||
@@ -1335,7 +1420,7 @@ abstract class DashboardViewModelBase with Store {
|
||||
// FIX-ME: Check for side effects
|
||||
// subname = null;
|
||||
subname = '';
|
||||
|
||||
_setupBitcoinAccountChangeReaction(wallet);
|
||||
_reloadTransactions();
|
||||
}
|
||||
|
||||
@@ -1378,6 +1463,21 @@ abstract class DashboardViewModelBase with Store {
|
||||
}, _transactionDisposerCallback, delay: 300);
|
||||
}
|
||||
|
||||
void _setupBitcoinAccountChangeReaction(WalletBase wallet) {
|
||||
if (wallet.type != WalletType.bitcoin) {
|
||||
return;
|
||||
}
|
||||
|
||||
_onAccountChangeReaction?.reaction.dispose();
|
||||
_onAccountChangeReaction = reaction(
|
||||
(_) => accountListViewModel?.selectedAccount?.id,
|
||||
(_) {
|
||||
_reloadTransactions();
|
||||
},
|
||||
fireImmediately: true,
|
||||
);
|
||||
}
|
||||
|
||||
@action
|
||||
void _onMoneroAccountChange(WalletBase wallet) {
|
||||
if (wallet.type == WalletType.monero) {
|
||||
|
||||
@@ -55,10 +55,6 @@ class TransactionListItem extends ActionListItem with Keyable {
|
||||
}
|
||||
|
||||
String get formattedTitle {
|
||||
if (balanceViewModel.wallet.type == WalletType.bitcoin &&
|
||||
transaction.additionalInfo['hasMissingInputTx'] == true) {
|
||||
return 'Transaction has missing data';
|
||||
}
|
||||
|
||||
if (transaction.additionalInfo['isIronwoodMigration'] == true) {
|
||||
return 'Migration';
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import 'package:cake_wallet/core/execution_state.dart';
|
||||
|
||||
abstract class WalletAccountEditOrCreateViewModel {
|
||||
bool get isEdit;
|
||||
ExecutionState get state;
|
||||
String get label;
|
||||
set label(String value);
|
||||
Future<void> save();
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/core/execution_state.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_account_list/account_edit_or_create_view_model.dart';
|
||||
import 'package:cw_core/balance_card_style_settings.dart';
|
||||
import 'package:cw_core/card_design.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'bitcoin_account_edit_or_create_view_model.g.dart';
|
||||
|
||||
class BitcoinAccountEditOrCreateViewModel = BitcoinAccountEditOrCreateViewModelBase
|
||||
with _$BitcoinAccountEditOrCreateViewModel;
|
||||
|
||||
abstract class BitcoinAccountEditOrCreateViewModelBase
|
||||
with Store
|
||||
implements WalletAccountEditOrCreateViewModel {
|
||||
BitcoinAccountEditOrCreateViewModelBase({required WalletBase wallet})
|
||||
: state = InitialExecutionState(),
|
||||
label = "",
|
||||
_wallet = wallet;
|
||||
|
||||
@override
|
||||
bool get isEdit => false;
|
||||
|
||||
@override
|
||||
@observable
|
||||
ExecutionState state;
|
||||
|
||||
@override
|
||||
@observable
|
||||
String label;
|
||||
|
||||
final WalletBase _wallet;
|
||||
|
||||
Future<List<Gradient>> _getUsableCardGradients() async {
|
||||
final List<Gradient> ret = List<Gradient>.from(CardDesign.allGradients);
|
||||
final designs = (await BalanceCardStyleSettings.getAll(_wallet.walletInfo.internalId))
|
||||
.map((item) => CardDesign.fromStyleSettings(item, _wallet.currency));
|
||||
for (final design in designs) {
|
||||
ret.remove(design.gradient);
|
||||
}
|
||||
return ret.isNotEmpty ? ret : CardDesign.allGradients;
|
||||
}
|
||||
|
||||
Future<void> _saveRandomCardDesign(int accountIndex) async {
|
||||
final gradients = await _getUsableCardGradients();
|
||||
final accounts = await _wallet.walletInfo.getAccounts();
|
||||
|
||||
await BalanceCardStyleSettings.fromCardDesign(
|
||||
walletInfoId: _wallet.walletInfo.internalId,
|
||||
accountIndex: accountIndex,
|
||||
cardOrder: accounts.length - 1,
|
||||
design: CardDesign.specialDesignsForCurrencies[_wallet.currency]!
|
||||
.withGradient(gradients[Random().nextInt(gradients.length)]))
|
||||
.insert();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> save() async {
|
||||
try {
|
||||
state = IsExecutingState();
|
||||
|
||||
final accountIndex = await _createAccount(label);
|
||||
await _saveRandomCardDesign(accountIndex);
|
||||
|
||||
await _wallet.save();
|
||||
state = ExecutedSuccessfullyState();
|
||||
} catch (e) {
|
||||
state = FailureState(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
Future<int> _createAccount(String label) async {
|
||||
final accounts = await _wallet.walletInfo.getAccounts();
|
||||
|
||||
final nextIndex = accounts.isEmpty
|
||||
? 0
|
||||
: accounts.map((e) => e.accountIndex).reduce((a, b) => a > b ? a : b) + 1;
|
||||
|
||||
await _wallet.walletInfo.addAccount(
|
||||
accountIndex: nextIndex,
|
||||
label: label.isEmpty ? "Account $nextIndex" : label,
|
||||
);
|
||||
|
||||
await bitcoin!.setCurrentAccount(_wallet, nextIndex);
|
||||
|
||||
return nextIndex;
|
||||
}
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/core/amount_parsing_proxy.dart';
|
||||
import 'package:cake_wallet/entities/balance_display_mode.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_account_list/account_list_item.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_account_list/wallet_account_list_view_model.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'bitcoin_account_list_view_model.g.dart';
|
||||
|
||||
class BitcoinAccountListViewModel = BitcoinAccountListViewModelBase
|
||||
with _$BitcoinAccountListViewModel;
|
||||
|
||||
abstract class BitcoinAccountListViewModelBase with Store implements WalletAccountListViewModel {
|
||||
BitcoinAccountListViewModelBase(this._wallet, this.settingsStore) {
|
||||
unawaited(_loadAccounts());
|
||||
|
||||
reaction(
|
||||
(_) => bitcoin!.accountBalancesSnapshot(_wallet),
|
||||
(snapshot) => unawaited(_refreshBalances(snapshot)),
|
||||
equals: const MapEquality<int, Object>().equals,
|
||||
);
|
||||
|
||||
reaction(
|
||||
(_) => settingsStore.balanceDisplayMode,
|
||||
(_) => unawaited(_recomputeBalanceStrings()),
|
||||
);
|
||||
}
|
||||
|
||||
final SettingsStore settingsStore;
|
||||
final WalletBase _wallet;
|
||||
|
||||
List<WalletInfoAccount>? _cachedStoredAccounts;
|
||||
Future<void>? _loadAccountsFuture;
|
||||
|
||||
CryptoCurrency get currency => _wallet.currency;
|
||||
|
||||
@override
|
||||
@observable
|
||||
ObservableList<AccountListItem> accounts = ObservableList<AccountListItem>();
|
||||
|
||||
@override
|
||||
@observable
|
||||
AccountListItem? selectedAccount;
|
||||
|
||||
@override
|
||||
@action
|
||||
Future<void> select(AccountListItem account) async {
|
||||
await bitcoin!.setCurrentAccount(_wallet, account.id);
|
||||
runInAction(() => selectedAccount = account);
|
||||
await reload();
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> _loadAccounts() => _runLoad(() => _wallet.walletInfo.getAccounts());
|
||||
|
||||
@action
|
||||
Future<void> _refreshBalances(Map<int, Object> balancesSnapshot) {
|
||||
final cached = _cachedStoredAccounts;
|
||||
final hasUnknownAccount = cached == null ||
|
||||
balancesSnapshot.keys
|
||||
.any((accountIndex) => !cached.any((a) => a.accountIndex == accountIndex));
|
||||
|
||||
if (hasUnknownAccount) return _loadAccounts();
|
||||
return _runLoad(() async => cached);
|
||||
}
|
||||
|
||||
|
||||
@action
|
||||
Future<void> _recomputeBalanceStrings() {
|
||||
final cached = _cachedStoredAccounts;
|
||||
if (cached == null) return _loadAccounts();
|
||||
return _runLoad(() async => cached);
|
||||
}
|
||||
|
||||
Future<void> _runLoad(Future<List<WalletInfoAccount>> Function() fetch) {
|
||||
final inProgress = _loadAccountsFuture;
|
||||
if (inProgress != null) {
|
||||
return inProgress;
|
||||
}
|
||||
|
||||
final future = _applyAccounts(fetch);
|
||||
_loadAccountsFuture = future;
|
||||
return future.whenComplete(() => _loadAccountsFuture = null);
|
||||
}
|
||||
|
||||
Future<void> _applyAccounts(Future<List<WalletInfoAccount>> Function() fetch) async {
|
||||
final storedAccounts = await fetch();
|
||||
_cachedStoredAccounts = storedAccounts;
|
||||
|
||||
final currentAccountIndex = _wallet.walletInfo.currentAccountIndex;
|
||||
final items = storedAccounts
|
||||
.map((account) => AccountListItem(
|
||||
id: account.accountIndex,
|
||||
label: account.label,
|
||||
balance: _balanceForAccount(account.accountIndex),
|
||||
isSelected: account.accountIndex == currentAccountIndex,
|
||||
))
|
||||
.toList();
|
||||
|
||||
runInAction(() {
|
||||
accounts = ObservableList<AccountListItem>.of(items);
|
||||
selectedAccount = items.isEmpty
|
||||
? null
|
||||
: items.firstWhere(
|
||||
(account) => account.isSelected,
|
||||
orElse: () => items.first,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@action
|
||||
Future<void> reload() => _loadAccounts();
|
||||
|
||||
String _balanceForAccount(int accountIndex) {
|
||||
if (settingsStore.balanceDisplayMode == BalanceDisplayMode.hiddenBalance) {
|
||||
return '●●●●●●';
|
||||
}
|
||||
final balance = bitcoin!.balanceForAccount(_wallet, accountIndex);
|
||||
|
||||
return AmountParsingProxy(settingsStore.displayAmountsInSatoshi).asDisplayString(balance.available);
|
||||
}
|
||||
}
|
||||
+41
-36
@@ -1,5 +1,6 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:cake_wallet/view_model/wallet_account_list/account_edit_or_create_view_model.dart';
|
||||
import 'package:cake_wallet/wownero/wownero.dart';
|
||||
import 'package:cw_core/balance_card_style_settings.dart';
|
||||
import 'package:cw_core/card_design.dart';
|
||||
@@ -9,14 +10,16 @@ import 'package:flutter/material.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/core/execution_state.dart';
|
||||
import 'package:cake_wallet/monero/monero.dart';
|
||||
import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_account_list/account_list_item.dart';
|
||||
|
||||
part 'monero_account_edit_or_create_view_model.g.dart';
|
||||
|
||||
class MoneroAccountEditOrCreateViewModel = MoneroAccountEditOrCreateViewModelBase
|
||||
with _$MoneroAccountEditOrCreateViewModel;
|
||||
|
||||
abstract class MoneroAccountEditOrCreateViewModelBase with Store {
|
||||
abstract class MoneroAccountEditOrCreateViewModelBase
|
||||
with Store
|
||||
implements WalletAccountEditOrCreateViewModel {
|
||||
MoneroAccountEditOrCreateViewModelBase(this._moneroAccountList, this._wowneroAccountList,
|
||||
{required WalletBase wallet, AccountListItem? accountListItem})
|
||||
: state = InitialExecutionState(),
|
||||
@@ -61,49 +64,51 @@ abstract class MoneroAccountEditOrCreateViewModelBase with Store {
|
||||
}
|
||||
|
||||
Future<void> save() async {
|
||||
await _saveRandomCardDesign();
|
||||
if (_wallet.type == WalletType.monero) {
|
||||
await saveMonero();
|
||||
}
|
||||
try {
|
||||
state = IsExecutingState();
|
||||
|
||||
if (_wallet.type == WalletType.wownero) {
|
||||
await saveWownero();
|
||||
if (!isEdit) await _saveRandomCardDesign();
|
||||
|
||||
if (_wallet.type == WalletType.monero) {
|
||||
await saveMonero();
|
||||
} else if (_wallet.type == WalletType.wownero) {
|
||||
await saveWownero();
|
||||
}
|
||||
|
||||
await _wallet.save();
|
||||
state = ExecutedSuccessfullyState();
|
||||
} catch (e) {
|
||||
state = FailureState(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> saveMonero() async {
|
||||
try {
|
||||
state = IsExecutingState();
|
||||
|
||||
if (_accountListItem != null) {
|
||||
await _moneroAccountList.setLabelAccount(_wallet,
|
||||
accountIndex: _accountListItem.id, label: label);
|
||||
} else {
|
||||
await _moneroAccountList.addAccount(_wallet, label: label);
|
||||
}
|
||||
|
||||
await _wallet.save();
|
||||
state = ExecutedSuccessfullyState();
|
||||
} catch (e) {
|
||||
state = FailureState(e.toString());
|
||||
if (_accountListItem != null) {
|
||||
await _moneroAccountList.setLabelAccount(
|
||||
_wallet,
|
||||
accountIndex: _accountListItem.id,
|
||||
label: label,
|
||||
);
|
||||
} else {
|
||||
await _moneroAccountList.addAccount(
|
||||
_wallet,
|
||||
label: label,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> saveWownero() async {
|
||||
try {
|
||||
state = IsExecutingState();
|
||||
|
||||
if (_accountListItem != null) {
|
||||
await _wowneroAccountList?.setLabelAccount(_wallet,
|
||||
accountIndex: _accountListItem.id, label: label);
|
||||
} else {
|
||||
await _wowneroAccountList?.addAccount(_wallet, label: label);
|
||||
}
|
||||
|
||||
await _wallet.save();
|
||||
state = ExecutedSuccessfullyState();
|
||||
} catch (e) {
|
||||
state = FailureState(e.toString());
|
||||
if (_accountListItem != null) {
|
||||
await _wowneroAccountList?.setLabelAccount(
|
||||
_wallet,
|
||||
accountIndex: _accountListItem.id,
|
||||
label: label,
|
||||
);
|
||||
} else {
|
||||
await _wowneroAccountList?.addAccount(
|
||||
_wallet,
|
||||
label: label,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
-11
@@ -1,29 +1,25 @@
|
||||
import 'package:cake_wallet/entities/balance_display_mode.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_account_list/wallet_account_list_view_model.dart';
|
||||
import 'package:cake_wallet/wownero/wownero.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_account_list/account_list_item.dart';
|
||||
import 'package:cake_wallet/monero/monero.dart';
|
||||
|
||||
part 'monero_account_list_view_model.g.dart';
|
||||
|
||||
class MoneroAccountListViewModel = MoneroAccountListViewModelBase with _$MoneroAccountListViewModel;
|
||||
|
||||
abstract class MoneroAccountListViewModelBase with Store {
|
||||
MoneroAccountListViewModelBase(this._wallet, this.settingsStore) : scrollOffsetFromTop = 0;
|
||||
abstract class MoneroAccountListViewModelBase with Store implements WalletAccountListViewModel {
|
||||
MoneroAccountListViewModelBase(this._wallet, this.settingsStore);
|
||||
|
||||
final SettingsStore settingsStore;
|
||||
|
||||
@observable
|
||||
double scrollOffsetFromTop;
|
||||
|
||||
@action
|
||||
void setScrollOffsetFromTop(double scrollOffsetFromTop) {
|
||||
this.scrollOffsetFromTop = scrollOffsetFromTop;
|
||||
}
|
||||
@override
|
||||
AccountListItem? get selectedAccount => selected;
|
||||
|
||||
CryptoCurrency get currency => _wallet.currency;
|
||||
|
||||
@@ -65,7 +61,11 @@ abstract class MoneroAccountListViewModelBase with Store {
|
||||
|
||||
final WalletBase _wallet;
|
||||
|
||||
void select(AccountListItem item) {
|
||||
@override
|
||||
Future<void> reload() async {}
|
||||
|
||||
@override
|
||||
Future<void> select(AccountListItem item) async {
|
||||
if (_wallet.type == WalletType.monero) {
|
||||
monero!.setCurrentAccount(
|
||||
_wallet,
|
||||
@@ -0,0 +1,14 @@
|
||||
import 'package:cake_wallet/view_model/wallet_account_list/account_list_item.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
|
||||
abstract class WalletAccountListViewModel {
|
||||
List<AccountListItem> get accounts;
|
||||
|
||||
AccountListItem? get selectedAccount;
|
||||
|
||||
CryptoCurrency get currency;
|
||||
|
||||
Future<void> select(AccountListItem account);
|
||||
|
||||
Future<void> reload();
|
||||
}
|
||||
Reference in New Issue
Block a user