refactor wallets.dart

This commit is contained in:
julian 2023-10-31 11:13:26 -06:00
parent 6db89bb18f
commit 568a0cab1a
2 changed files with 105 additions and 276 deletions

View file

@ -8,165 +8,36 @@
* *
*/ */
import 'package:flutter/foundation.dart'; import 'package:isar/isar.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/db/hive/db.dart'; import 'package:stackwallet/db/hive/db.dart';
import 'package:stackwallet/models/node_model.dart'; import 'package:stackwallet/db/isar/main_db.dart';
import 'package:stackwallet/services/coins/coin_service.dart';
import 'package:stackwallet/services/coins/manager.dart';
import 'package:stackwallet/services/node_service.dart'; import 'package:stackwallet/services/node_service.dart';
import 'package:stackwallet/services/transaction_notification_tracker.dart'; import 'package:stackwallet/services/transaction_notification_tracker.dart';
import 'package:stackwallet/services/wallets_service.dart';
import 'package:stackwallet/utilities/default_nodes.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/enums/sync_type_enum.dart'; import 'package:stackwallet/utilities/enums/sync_type_enum.dart';
import 'package:stackwallet/utilities/listenable_list.dart';
import 'package:stackwallet/utilities/listenable_map.dart';
import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/prefs.dart'; import 'package:stackwallet/utilities/prefs.dart';
import 'package:tuple/tuple.dart'; import 'package:stackwallet/wallets/isar_models/wallet_info.dart';
import 'package:stackwallet/wallets/wallet/wallet.dart';
final ListenableList<ChangeNotifierProvider<Manager>> _nonFavorites = class Wallets {
ListenableList();
ListenableList<ChangeNotifierProvider<Manager>> get nonFavorites =>
_nonFavorites;
final ListenableList<ChangeNotifierProvider<Manager>> _favorites =
ListenableList();
ListenableList<ChangeNotifierProvider<Manager>> get favorites => _favorites;
class Wallets extends ChangeNotifier {
Wallets._private(); Wallets._private();
@override
dispose() {
//todo: check if print needed
// debugPrint("Wallets dispose was called!!");
super.dispose();
}
static final Wallets _sharedInstance = Wallets._private(); static final Wallets _sharedInstance = Wallets._private();
static Wallets get sharedInstance => _sharedInstance; static Wallets get sharedInstance => _sharedInstance;
late WalletsService walletsService;
late NodeService nodeService; late NodeService nodeService;
late MainDB mainDB;
// mirrored maps for access to reading managers without using riverpod ref bool get hasWallets => _wallets.isNotEmpty;
static final ListenableMap<String, ChangeNotifierProvider<Manager>>
_managerProviderMap = ListenableMap();
static final ListenableMap<String, Manager> _managerMap = ListenableMap();
bool get hasWallets => _managerProviderMap.isNotEmpty;
List<ChangeNotifierProvider<Manager>> get managerProviders =>
_managerProviderMap.values.toList(growable: false);
List<Manager> get managers => _managerMap.values.toList(growable: false);
List<String> getWalletIdsFor({required Coin coin}) {
final List<String> result = [];
for (final manager in _managerMap.values) {
if (manager.coin == coin) {
result.add(manager.walletId);
}
}
return result;
}
List<Tuple2<Coin, List<ChangeNotifierProvider<Manager>>>>
getManagerProvidersByCoin() {
Map<Coin, List<ChangeNotifierProvider<Manager>>> map = {};
for (final manager in _managerMap.values) {
if (map[manager.coin] == null) {
map[manager.coin] = [];
}
map[manager.coin]!.add(_managerProviderMap[manager.walletId]
as ChangeNotifierProvider<Manager>);
}
final List<Tuple2<Coin, List<ChangeNotifierProvider<Manager>>>> result = [];
for (final coin in Coin.values) {
if (map[coin] != null) {
result.add(Tuple2(coin, map[coin]!));
}
}
return result;
}
List<ChangeNotifierProvider<Manager>> getManagerProvidersForCoin(Coin coin) {
List<ChangeNotifierProvider<Manager>> result = [];
for (final manager in _managerMap.values) {
if (manager.coin == coin) {
result.add(_managerProviderMap[manager.walletId]
as ChangeNotifierProvider<Manager>);
}
}
return result;
}
ChangeNotifierProvider<Manager> getManagerProvider(String walletId) {
return _managerProviderMap[walletId] as ChangeNotifierProvider<Manager>;
}
Manager getManager(String walletId) {
return _managerMap[walletId] as Manager;
}
void addWallet({required String walletId, required Manager manager}) {
_managerMap.add(walletId, manager, true);
_managerProviderMap.add(
walletId, ChangeNotifierProvider<Manager>((_) => manager), true);
if (manager.isFavorite) {
_favorites.add(
_managerProviderMap[walletId] as ChangeNotifierProvider<Manager>,
false);
} else {
_nonFavorites.add(
_managerProviderMap[walletId] as ChangeNotifierProvider<Manager>,
false);
}
notifyListeners();
}
void removeWallet({required String walletId}) {
if (_managerProviderMap[walletId] == null) {
Logging.instance.log(
"Wallets.removeWallet($walletId) failed. ManagerProvider with $walletId not found!",
level: LogLevel.Warning);
return;
}
final provider = _managerProviderMap[walletId]!;
// in both non and favorites for removal
_favorites.remove(provider, true);
_nonFavorites.remove(provider, true);
_managerProviderMap.remove(walletId, true);
_managerMap.remove(walletId, true)!.exitCurrentWallet();
notifyListeners();
}
static bool hasLoaded = false; static bool hasLoaded = false;
Future<void> _initLinearly( final Map<String, Wallet> _wallets = {};
List<Tuple2<Manager, bool>> tuples,
) async {
for (final tuple in tuples) {
await tuple.item1.initializeExisting();
if (tuple.item2 && !tuple.item1.shouldAutoSync) {
tuple.item1.shouldAutoSync = true;
}
}
}
static int _count = 0; Wallet getWallet(String walletId) => _wallets[walletId]!;
Future<void> load(Prefs prefs) async {
//todo: check if print needed Future<void> load(Prefs prefs, MainDB mainDB) async {
// debugPrint("++++++++++++++ Wallets().load() called: ${++_count} times");
if (hasLoaded) { if (hasLoaded) {
return; return;
} }
@ -175,18 +46,22 @@ class Wallets extends ChangeNotifier {
// clear out any wallet hive boxes where the wallet was deleted in previous app run // clear out any wallet hive boxes where the wallet was deleted in previous app run
for (final walletId in DB.instance for (final walletId in DB.instance
.values<String>(boxName: DB.boxNameWalletsToDeleteOnStart)) { .values<String>(boxName: DB.boxNameWalletsToDeleteOnStart)) {
await DB.instance.deleteBoxFromDisk(boxName: walletId); await mainDB.isar.writeTxn(() async => await mainDB.isar.walletInfo
.where()
.walletIdEqualTo(walletId)
.deleteAll());
} }
// clear list // clear list
await DB.instance await DB.instance
.deleteAll<String>(boxName: DB.boxNameWalletsToDeleteOnStart); .deleteAll<String>(boxName: DB.boxNameWalletsToDeleteOnStart);
final map = await walletsService.walletNames; final walletInfoList = await mainDB.isar.walletInfo.where().findAll();
if (walletInfoList.isEmpty) {
return;
}
List<Future<dynamic>> walletInitFutures = []; List<Future<void>> walletInitFutures = [];
List<Tuple2<Manager, bool>> walletsToInitLinearly = []; List<({Wallet wallet, bool shouldAutoSync})> walletsToInitLinearly = [];
final favIdList = await walletsService.getFavoriteWalletIds();
List<String> walletIdsToEnableAutoSync = []; List<String> walletIdsToEnableAutoSync = [];
bool shouldAutoSyncAll = false; bool shouldAutoSyncAll = false;
@ -202,92 +77,47 @@ class Wallets extends ChangeNotifier {
break; break;
} }
for (final entry in map.entries) { for (final walletInfo in walletInfoList) {
try { try {
final walletId = entry.value.walletId;
late final bool isVerified;
try {
isVerified =
await walletsService.isMnemonicVerified(walletId: walletId);
} catch (e, s) {
Logging.instance.log("$e $s", level: LogLevel.Warning);
isVerified = false;
}
Logging.instance.log( Logging.instance.log(
"LOADING WALLET: ${entry.value.toString()} IS VERIFIED: $isVerified", "LOADING WALLET: ${walletInfo.name}:${walletInfo.walletId} "
level: LogLevel.Info); "IS VERIFIED: ${walletInfo.isMnemonicVerified}",
if (isVerified) { level: LogLevel.Info,
if (_managerMap[walletId] == null && );
_managerProviderMap[walletId] == null) {
final coin = entry.value.coin;
NodeModel node = nodeService.getPrimaryNodeFor(coin: coin) ??
DefaultNodes.getNodeFor(coin);
// ElectrumXNode? node = await nodeService.getCurrentNode(coin: coin);
// folowing shouldn't be needed as the defaults get saved on init if (walletInfo.isMnemonicVerified) {
// if (node == null) { // TODO: integrate this into the new wallets somehow?
// node = DefaultNodes.getNodeFor(coin); // requires some thinking
// final txTracker =
// // save default node TransactionNotificationTracker(walletId: walletInfo.walletId);
// nodeService.add(node, false);
// }
final txTracker = final wallet = await Wallet.load(
TransactionNotificationTracker(walletId: walletId); walletId: walletInfo.walletId,
mainDB: mainDB,
secureStorageInterface: nodeService.secureStorageInterface,
nodeService: nodeService,
prefs: prefs,
);
final failovers = nodeService.failoverNodesFor(coin: coin); final shouldSetAutoSync = shouldAutoSyncAll ||
walletIdsToEnableAutoSync.contains(walletInfo.walletId);
// load wallet if (walletInfo.coin == Coin.monero ||
final wallet = CoinServiceAPI.from( walletInfo.coin == Coin.wownero) {
coin, // walletsToInitLinearly.add(Tuple2(manager, shouldSetAutoSync));
walletId, } else {
entry.value.name, walletInitFutures.add(wallet.init().then((_) {
nodeService.secureStorageInterface, if (shouldSetAutoSync) {
node, wallet.shouldAutoSync = true;
txTracker,
prefs,
failovers,
);
final manager = Manager(wallet);
final shouldSetAutoSync = shouldAutoSyncAll ||
walletIdsToEnableAutoSync.contains(manager.walletId);
if (manager.coin == Coin.monero || manager.coin == Coin.wownero) {
// walletsToInitLinearly.add(Tuple2(manager, shouldSetAutoSync));
} else {
walletInitFutures.add(manager.initializeExisting().then((value) {
if (shouldSetAutoSync) {
manager.shouldAutoSync = true;
}
}));
}
_managerMap.add(walletId, manager, false);
final managerProvider =
ChangeNotifierProvider<Manager>((_) => manager);
_managerProviderMap.add(walletId, managerProvider, false);
final favIndex = favIdList.indexOf(walletId);
if (favIndex == -1) {
_nonFavorites.add(managerProvider, true);
} else {
// it is a favorite
if (favIndex >= _favorites.length) {
_favorites.add(managerProvider, true);
} else {
_favorites.insert(favIndex, managerProvider, true);
} }
} }));
} }
_wallets[wallet.walletId] = wallet;
} else { } else {
// wallet creation was not completed by user so we remove it completely // wallet creation was not completed by user so we remove it completely
await walletsService.deleteWallet(entry.value.name, false); await _deleteWallet(walletInfo.walletId);
// await walletsService.deleteWallet(walletInfo.name, false);
} }
} catch (e, s) { } catch (e, s) {
Logging.instance.log("$e $s", level: LogLevel.Fatal); Logging.instance.log("$e $s", level: LogLevel.Fatal);
@ -300,22 +130,19 @@ class Wallets extends ChangeNotifier {
_initLinearly(walletsToInitLinearly), _initLinearly(walletsToInitLinearly),
...walletInitFutures, ...walletInitFutures,
]); ]);
notifyListeners();
} else if (walletInitFutures.isNotEmpty) { } else if (walletInitFutures.isNotEmpty) {
await Future.wait(walletInitFutures); await Future.wait(walletInitFutures);
notifyListeners();
} else if (walletsToInitLinearly.isNotEmpty) { } else if (walletsToInitLinearly.isNotEmpty) {
await _initLinearly(walletsToInitLinearly); await _initLinearly(walletsToInitLinearly);
notifyListeners();
} }
} }
Future<void> loadAfterStackRestore( Future<void> loadAfterStackRestore(
Prefs prefs, List<Manager> managers) async { Prefs prefs,
List<Future<dynamic>> walletInitFutures = []; List<Wallet> wallets,
List<Tuple2<Manager, bool>> walletsToInitLinearly = []; ) async {
List<Future<void>> walletInitFutures = [];
final favIdList = await walletsService.getFavoriteWalletIds(); List<({Wallet wallet, bool shouldAutoSync})> walletsToInitLinearly = [];
List<String> walletIdsToEnableAutoSync = []; List<String> walletIdsToEnableAutoSync = [];
bool shouldAutoSyncAll = false; bool shouldAutoSyncAll = false;
@ -331,53 +158,32 @@ class Wallets extends ChangeNotifier {
break; break;
} }
for (final manager in managers) { for (final wallet in wallets) {
final walletId = manager.walletId; Logging.instance.log(
"LOADING WALLET: ${wallet.walletInfo.name}:${wallet.walletId} IS VERIFIED: ${wallet.walletInfo.isMnemonicVerified}",
level: LogLevel.Info,
);
final isVerified = if (wallet.walletInfo.isMnemonicVerified) {
await walletsService.isMnemonicVerified(walletId: walletId); final shouldSetAutoSync = shouldAutoSyncAll ||
//todo: check if print needed walletIdsToEnableAutoSync.contains(wallet.walletId);
// debugPrint(
// "LOADING RESTORED WALLET: ${manager.walletName} ${manager.walletId} IS VERIFIED: $isVerified");
if (isVerified) { if (wallet.walletInfo.coin == Coin.monero ||
if (_managerMap[walletId] == null && wallet.walletInfo.coin == Coin.wownero) {
_managerProviderMap[walletId] == null) { // walletsToInitLinearly.add(Tuple2(manager, shouldSetAutoSync));
final shouldSetAutoSync = shouldAutoSyncAll || } else {
walletIdsToEnableAutoSync.contains(manager.walletId); walletInitFutures.add(wallet.init().then((value) {
if (shouldSetAutoSync) {
if (manager.coin == Coin.monero || manager.coin == Coin.wownero) { wallet.shouldAutoSync = true;
// walletsToInitLinearly.add(Tuple2(manager, shouldSetAutoSync));
} else {
walletInitFutures.add(manager.initializeExisting().then((value) {
if (shouldSetAutoSync) {
manager.shouldAutoSync = true;
}
}));
}
_managerMap.add(walletId, manager, false);
final managerProvider =
ChangeNotifierProvider<Manager>((_) => manager);
_managerProviderMap.add(walletId, managerProvider, false);
final favIndex = favIdList.indexOf(walletId);
if (favIndex == -1) {
_nonFavorites.add(managerProvider, true);
} else {
// it is a favorite
if (favIndex >= _favorites.length) {
_favorites.add(managerProvider, true);
} else {
_favorites.insert(favIndex, managerProvider, true);
} }
} }));
} }
_wallets[wallet.walletId] = wallet;
} else { } else {
// wallet creation was not completed by user so we remove it completely // wallet creation was not completed by user so we remove it completely
await walletsService.deleteWallet(manager.walletName, false); await _deleteWallet(wallet.walletId);
// await walletsService.deleteWallet(walletInfo.name, false);
} }
} }
@ -386,13 +192,29 @@ class Wallets extends ChangeNotifier {
_initLinearly(walletsToInitLinearly), _initLinearly(walletsToInitLinearly),
...walletInitFutures, ...walletInitFutures,
]); ]);
notifyListeners();
} else if (walletInitFutures.isNotEmpty) { } else if (walletInitFutures.isNotEmpty) {
await Future.wait(walletInitFutures); await Future.wait(walletInitFutures);
notifyListeners();
} else if (walletsToInitLinearly.isNotEmpty) { } else if (walletsToInitLinearly.isNotEmpty) {
await _initLinearly(walletsToInitLinearly); await _initLinearly(walletsToInitLinearly);
notifyListeners();
} }
} }
Future<void> _initLinearly(
List<({Wallet wallet, bool shouldAutoSync})> dataList,
) async {
for (final data in dataList) {
await data.wallet.init();
if (data.shouldAutoSync && !data.wallet.shouldAutoSync) {
data.wallet.shouldAutoSync = true;
}
}
}
Future<void> _deleteWallet(String walletId) async {
// TODO proper clean up of other wallet data in addition to the following
await mainDB.isar.writeTxn(() async => await mainDB.isar.walletInfo
.where()
.walletIdEqualTo(walletId)
.deleteAll());
}
} }

View file

@ -328,6 +328,8 @@ abstract class Wallet<T extends CryptoCurrency> {
Future<bool> pingCheck(); Future<bool> pingCheck();
Future<void> updateNode();
Future<void> updateTransactions(); Future<void> updateTransactions();
Future<void> updateUTXOs(); Future<void> updateUTXOs();
Future<void> updateBalance(); Future<void> updateBalance();
@ -436,7 +438,12 @@ abstract class Wallet<T extends CryptoCurrency> {
} }
} }
Future<void> exit() async {} Future<void> exit() async {
// TODO:
}
Future<void> updateNode(); Future<void> init() async {
// TODO: make sure subclasses override this if they require some set up
// especially xmr/wow/epiccash
}
} }