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
@@ -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