From 93b040bc5f6e6da01e1ebd141ccc4ee6043cba07 Mon Sep 17 00:00:00 2001 From: julian-CStack Date: Mon, 17 Apr 2023 07:53:39 -0600 Subject: [PATCH 01/28] WIP e-cash --- .../add_edit_node_view.dart | 1 + lib/services/coins/coin_service.dart | 12 + lib/services/coins/ecash/ecash_wallet.dart | 2655 +++++++++++++++++ lib/utilities/address_utils.dart | 3 + lib/utilities/assets.dart | 4 + lib/utilities/block_explorers.dart | 2 + lib/utilities/constants.dart | 6 +- lib/utilities/default_nodes.dart | 17 +- lib/utilities/enums/coin_enum.dart | 20 + .../enums/derive_path_type_enum.dart | 1 + lib/utilities/theme/color_theme.dart | 3 + lib/utilities/theme/stack_colors.dart | 2 + 12 files changed, 2723 insertions(+), 3 deletions(-) create mode 100644 lib/services/coins/ecash/ecash_wallet.dart diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart index 6afe7ff7f..1dd7c1f67 100644 --- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart +++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart @@ -717,6 +717,7 @@ class _NodeFormState extends ConsumerState { case Coin.firoTestNet: case Coin.dogecoinTestNet: case Coin.epicCash: + case Coin.eCash: return false; case Coin.ethereum: diff --git a/lib/services/coins/coin_service.dart b/lib/services/coins/coin_service.dart index ff75822fd..48fa59630 100644 --- a/lib/services/coins/coin_service.dart +++ b/lib/services/coins/coin_service.dart @@ -7,6 +7,7 @@ import 'package:stackwallet/models/paymint/fee_object_model.dart'; import 'package:stackwallet/services/coins/bitcoin/bitcoin_wallet.dart'; import 'package:stackwallet/services/coins/bitcoincash/bitcoincash_wallet.dart'; import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart'; +import 'package:stackwallet/services/coins/ecash/ecash_wallet.dart'; import 'package:stackwallet/services/coins/epiccash/epiccash_wallet.dart'; import 'package:stackwallet/services/coins/ethereum/ethereum_wallet.dart'; import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; @@ -233,6 +234,17 @@ abstract class CoinServiceAPI { cachedClient: cachedClient, tracker: tracker, ); + + case Coin.eCash: + return ECashWallet( + walletId: walletId, + walletName: walletName, + coin: coin, + secureStore: secureStorageInterface, + client: client, + cachedClient: cachedClient, + tracker: tracker, + ); } } diff --git a/lib/services/coins/ecash/ecash_wallet.dart b/lib/services/coins/ecash/ecash_wallet.dart new file mode 100644 index 000000000..d7c1b08db --- /dev/null +++ b/lib/services/coins/ecash/ecash_wallet.dart @@ -0,0 +1,2655 @@ +import 'dart:async'; +import 'dart:io'; +import 'dart:math'; + +import 'package:bech32/bech32.dart'; +import 'package:bip32/bip32.dart' as bip32; +import 'package:bip39/bip39.dart' as bip39; +import 'package:bitcoindart/bitcoindart.dart'; +import 'package:bs58check/bs58check.dart' as bs58check; +import 'package:decimal/decimal.dart'; +import 'package:flutter/foundation.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; +import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart'; +import 'package:stackwallet/electrumx_rpc/electrumx.dart'; +import 'package:stackwallet/exceptions/electrumx/no_such_transaction.dart'; +import 'package:stackwallet/models/balance.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models; +import 'package:stackwallet/models/paymint/fee_object_model.dart'; +import 'package:stackwallet/models/signing_data.dart'; +import 'package:stackwallet/services/coins/coin_service.dart'; +import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart'; +import 'package:stackwallet/services/event_bus/events/global/refresh_percent_changed_event.dart'; +import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart'; +import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; +import 'package:stackwallet/services/event_bus/global_event_bus.dart'; +import 'package:stackwallet/services/mixins/coin_control_interface.dart'; +import 'package:stackwallet/services/mixins/electrum_x_parsing.dart'; +import 'package:stackwallet/services/mixins/wallet_cache.dart'; +import 'package:stackwallet/services/mixins/wallet_db.dart'; +import 'package:stackwallet/services/mixins/xpubable.dart'; +import 'package:stackwallet/services/node_service.dart'; +import 'package:stackwallet/services/notifications_api.dart'; +import 'package:stackwallet/services/transaction_notification_tracker.dart'; +import 'package:stackwallet/utilities/address_utils.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/bip32_utils.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/default_nodes.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart'; +import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart'; +import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/prefs.dart'; +import 'package:tuple/tuple.dart'; +import 'package:uuid/uuid.dart'; + +const int MINIMUM_CONFIRMATIONS = 1; + +const String GENESIS_HASH_MAINNET = + "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"; +const String GENESIS_HASH_TESTNET = + "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943"; + +final Amount DUST_LIMIT = Amount( + rawValue: BigInt.from(546), + fractionDigits: Coin.particl.decimals, +); + +final eCashNetwork = NetworkType( + messagePrefix: '\x18Bitcoin Signed Message:\n', + bech32: 'bc', + bip32: Bip32Type(public: 0x0488b21e, private: 0x0488ade4), + pubKeyHash: 0x00, + scriptHash: 0x05, + wif: 0x80, +); + +final eCashNetworkTestnet = NetworkType( + messagePrefix: '\x18Bitcoin Signed Message:\n', + bech32: 'tb', + bip32: Bip32Type(public: 0x043587cf, private: 0x04358394), + pubKeyHash: 0x6f, + scriptHash: 0xc4, + wif: 0xef, +); + +String constructDerivePath({ + required DerivePathType derivePathType, + required int networkWIF, + int account = 0, + required int chain, + required int index, +}) { + String coinType; + switch (networkWIF) { + case 0x80: // btc mainnet wif + coinType = "0"; // btc mainnet + break; + case 0xef: // btc testnet wif + coinType = "1"; // btc testnet + break; + default: + throw Exception("Invalid ECash network wif used!"); + } + + int purpose; + switch (derivePathType) { + case DerivePathType.bip44: + purpose = 44; + break; + // case DerivePathType.bip49: + // purpose = 49; + // break; + // case DerivePathType.bip84: + // purpose = 84; + // break; + default: + throw Exception("DerivePathType $derivePathType not supported"); + } + + return "m/$purpose'/$coinType'/$account'/$chain/$index"; +} + +class ECashWallet extends CoinServiceAPI + with WalletCache, WalletDB, ElectrumXParsing, CoinControlInterface + implements XPubAble { + ECashWallet({ + required String walletId, + required String walletName, + required Coin coin, + required ElectrumX client, + required CachedElectrumX cachedClient, + required TransactionNotificationTracker tracker, + required SecureStorageInterface secureStore, + MainDB? mockableOverride, + }) { + txTracker = tracker; + _walletId = walletId; + _walletName = walletName; + _coin = coin; + _electrumXClient = client; + _cachedElectrumXClient = cachedClient; + _secureStore = secureStore; + initCache(walletId, coin); + initWalletDB(mockableOverride: mockableOverride); + initCoinControlInterface( + walletId: walletId, + walletName: walletName, + coin: coin, + db: db, + getChainHeight: () => chainHeight, + refreshedBalanceCallback: (balance) async { + _balance = balance; + await updateCachedBalance(_balance!); + }, + ); + } + + static const integrationTestFlag = + bool.fromEnvironment("IS_INTEGRATION_TEST"); + + final _prefs = Prefs.instance; + + Timer? timer; + late final Coin _coin; + + late final TransactionNotificationTracker txTracker; + + NetworkType get _network { + switch (coin) { + case Coin.eCash: + return eCashNetwork; + // case Coin.bitcoinTestNet: + // return testnet; + default: + throw Exception("Invalid network type!"); + } + } + + @override + set isFavorite(bool markFavorite) { + _isFavorite = markFavorite; + updateCachedIsFavorite(markFavorite); + } + + @override + bool get isFavorite => _isFavorite ??= getCachedIsFavorite(); + + bool? _isFavorite; + + @override + Coin get coin => _coin; + + @override + Future> get utxos => db.getUTXOs(walletId).findAll(); + + @override + Future> get transactions => db + .getTransactions(walletId) + .filter() + .not() + .group((q) => q + .subTypeEqualTo(isar_models.TransactionSubType.bip47Notification) + .and() + .typeEqualTo(isar_models.TransactionType.incoming)) + .sortByTimestampDesc() + .findAll(); + + @override + Future get currentReceivingAddress async => + (await _currentReceivingAddress).value; + + Future get _currentReceivingAddress async => + (await db + .getAddresses(walletId) + .filter() + .typeEqualTo(isar_models.AddressType.p2wpkh) + .subTypeEqualTo(isar_models.AddressSubType.receiving) + .sortByDerivationIndexDesc() + .findFirst()) ?? + await _generateAddressForChain(0, 0, DerivePathTypeExt.primaryFor(coin)); + + Future get currentChangeAddress async => + (await _currentChangeAddress).value; + + Future get _currentChangeAddress async => + (await db + .getAddresses(walletId) + .filter() + .typeEqualTo(isar_models.AddressType.p2wpkh) + .subTypeEqualTo(isar_models.AddressSubType.change) + .sortByDerivationIndexDesc() + .findFirst()) ?? + await _generateAddressForChain(1, 0, DerivePathTypeExt.primaryFor(coin)); + + Future get currentChangeAddressP2PKH async => + (await _currentChangeAddressP2PKH).value; + + Future get _currentChangeAddressP2PKH async => + (await db + .getAddresses(walletId) + .filter() + .typeEqualTo(isar_models.AddressType.p2pkh) + .subTypeEqualTo(isar_models.AddressSubType.change) + .sortByDerivationIndexDesc() + .findFirst()) ?? + await _generateAddressForChain(1, 0, DerivePathType.bip44); + + @override + Future exit() async { + _hasCalledExit = true; + timer?.cancel(); + timer = null; + stopNetworkAlivePinging(); + } + + bool _hasCalledExit = false; + + @override + bool get hasCalledExit => _hasCalledExit; + + @override + Future get fees => _feeObject ??= _getFees(); + Future? _feeObject; + + @override + Future get maxFee async { + final fee = (await fees).fast as String; + final satsFee = + Decimal.parse(fee) * Decimal.fromInt(Constants.satsPerCoin(coin)); + return satsFee.floor().toBigInt().toInt(); + } + + @override + Future> get mnemonic => _getMnemonicList(); + + @override + Future get mnemonicString => + _secureStore.read(key: '${_walletId}_mnemonic'); + + @override + Future get mnemonicPassphrase => _secureStore.read( + key: '${_walletId}_mnemonicPassphrase', + ); + + Future get chainHeight async { + try { + final result = await _electrumXClient.getBlockHeadTip(); + final height = result["height"] as int; + await updateCachedChainHeight(height); + if (height > storedChainHeight) { + GlobalEventBus.instance.fire( + UpdatedInBackgroundEvent( + "Updated current chain height in $walletId $walletName!", + walletId, + ), + ); + } + return height; + } catch (e, s) { + Logging.instance.log("Exception caught in chainHeight: $e\n$s", + level: LogLevel.Error); + return storedChainHeight; + } + } + + @override + int get storedChainHeight => getCachedChainHeight(); + + DerivePathType addressType({required String address}) { + Uint8List? decodeBase58; + Segwit? decodeBech32; + try { + decodeBase58 = bs58check.decode(address); + } catch (err) { + // Base58check decode fail + } + if (decodeBase58 != null) { + if (decodeBase58[0] == _network.pubKeyHash) { + // P2PKH + return DerivePathType.bip44; + } + if (decodeBase58[0] == _network.scriptHash) { + // P2SH + return DerivePathType.bip49; + } + throw ArgumentError('Invalid version or Network mismatch'); + } else { + try { + decodeBech32 = segwit.decode(address); + } catch (err) { + // Bech32 decode fail + } + if (_network.bech32 != decodeBech32!.hrp) { + throw ArgumentError('Invalid prefix or Network mismatch'); + } + if (decodeBech32.version != 0) { + throw ArgumentError('Invalid address version'); + } + // P2WPKH + return DerivePathType.bip84; + } + } + + bool longMutex = false; + + @override + bool validateAddress(String address) { + return Address.validateAddress(address, _network); + } + + @override + String get walletId => _walletId; + late final String _walletId; + + @override + String get walletName => _walletName; + late String _walletName; + + // setter for updating on rename + @override + set walletName(String newName) => _walletName = newName; + + late ElectrumX _electrumXClient; + + ElectrumX get electrumXClient => _electrumXClient; + + late CachedElectrumX _cachedElectrumXClient; + + CachedElectrumX get cachedElectrumXClient => _cachedElectrumXClient; + + late SecureStorageInterface _secureStore; + + @override + Future updateNode(bool shouldRefresh) async { + final failovers = NodeService(secureStorageInterface: _secureStore) + .failoverNodesFor(coin: coin) + .map((e) => ElectrumXNode( + address: e.host, + port: e.port, + name: e.name, + id: e.id, + useSSL: e.useSSL, + )) + .toList(); + final newNode = await getCurrentNode(); + _cachedElectrumXClient = CachedElectrumX.from( + node: newNode, + prefs: _prefs, + failovers: failovers, + ); + _electrumXClient = ElectrumX.from( + node: newNode, + prefs: _prefs, + failovers: failovers, + ); + + if (shouldRefresh) { + unawaited(refresh()); + } + } + + Future> _getMnemonicList() async { + final _mnemonicString = await mnemonicString; + if (_mnemonicString == null) { + return []; + } + final List data = _mnemonicString.split(' '); + return data; + } + + Future getCurrentNode() async { + final node = NodeService(secureStorageInterface: _secureStore) + .getPrimaryNodeFor(coin: coin) ?? + DefaultNodes.getNodeFor(coin); + + return ElectrumXNode( + address: node.host, + port: node.port, + name: node.name, + useSSL: node.useSSL, + id: node.id, + ); + } + + Future> _fetchAllOwnAddresses() async { + final allAddresses = await db + .getAddresses(walletId) + .filter() + .not() + .typeEqualTo(isar_models.AddressType.nonWallet) + .and() + .not() + .subTypeEqualTo(isar_models.AddressSubType.nonWallet) + .findAll(); + return allAddresses; + } + + Future _getFees() async { + try { + //TODO adjust numbers for different speeds? + const int f = 1, m = 5, s = 20; + + final fast = await electrumXClient.estimateFee(blocks: f); + final medium = await electrumXClient.estimateFee(blocks: m); + final slow = await electrumXClient.estimateFee(blocks: s); + + final feeObject = FeeObject( + numberOfBlocksFast: f, + numberOfBlocksAverage: m, + numberOfBlocksSlow: s, + fast: Amount.fromDecimal( + fast, + fractionDigits: coin.decimals, + ).raw.toInt(), + medium: Amount.fromDecimal( + medium, + fractionDigits: coin.decimals, + ).raw.toInt(), + slow: Amount.fromDecimal( + slow, + fractionDigits: coin.decimals, + ).raw.toInt(), + ); + + Logging.instance.log("fetched fees: $feeObject", level: LogLevel.Info); + return feeObject; + } catch (e) { + Logging.instance + .log("Exception rethrown from _getFees(): $e", level: LogLevel.Error); + rethrow; + } + } + + Future _generateNewWallet() async { + Logging.instance + .log("IS_INTEGRATION_TEST: $integrationTestFlag", level: LogLevel.Info); + if (!integrationTestFlag) { + try { + final features = await electrumXClient + .getServerFeatures() + .timeout(const Duration(seconds: 3)); + Logging.instance.log("features: $features", level: LogLevel.Info); + switch (coin) { + case Coin.eCash: + if (features['genesis_hash'] != GENESIS_HASH_MAINNET) { + throw Exception("genesis hash does not match main net!"); + } + break; + // case Coin.e: + // if (features['genesis_hash'] != GENESIS_HASH_TESTNET) { + // throw Exception("genesis hash does not match test net!"); + // } + // break; + default: + throw Exception( + "Attempted to generate a ECashWallet using a non eCash coin type: ${coin.name}"); + } + } catch (e, s) { + Logging.instance.log("$e/n$s", level: LogLevel.Info); + } + } + + // this should never fail + if ((await mnemonicString) != null || (await mnemonicPassphrase) != null) { + throw Exception( + "Attempted to overwrite mnemonic on generate new wallet!"); + } + await _secureStore.write( + key: '${_walletId}_mnemonic', + value: bip39.generateMnemonic(strength: 256)); + await _secureStore.write(key: '${_walletId}_mnemonicPassphrase', value: ""); + + // Generate and add addresses to relevant arrays + final initialAddresses = await Future.wait([ + // // P2WPKH + // _generateAddressForChain(0, 0, DerivePathType.bip84), + // _generateAddressForChain(1, 0, DerivePathType.bip84), + // + // // P2PKH + _generateAddressForChain(0, 0, DerivePathType.bip44), + _generateAddressForChain(1, 0, DerivePathType.bip44), + + // // P2SH + // _generateAddressForChain(0, 0, DerivePathType.bip49), + // _generateAddressForChain(1, 0, DerivePathType.bip49), + ]); + + await db.putAddresses(initialAddresses); + + Logging.instance.log("_generateNewWalletFinished", level: LogLevel.Info); + } + + /// Generates a new internal or external chain address for the wallet using a BIP84, BIP44, or BIP49 derivation path. + /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! + /// [index] - This can be any integer >= 0 + Future _generateAddressForChain( + int chain, + int index, + DerivePathType derivePathType, + ) async { + final _mnemonic = await mnemonicString; + final _mnemonicPassphrase = await mnemonicPassphrase; + if (_mnemonicPassphrase == null) { + Logging.instance.log( + "Exception in _generateAddressForChain: mnemonic passphrase null, possible migration issue; if using internal builds, delete wallet and restore from seed, if using a release build, please file bug report", + level: LogLevel.Error); + } + + final derivePath = constructDerivePath( + derivePathType: derivePathType, + networkWIF: _network.wif, + chain: chain, + index: index, + ); + final node = await Bip32Utils.getBip32Node( + _mnemonic!, + _mnemonicPassphrase!, + _network, + derivePath, + ); + + final data = PaymentData(pubkey: node.publicKey); + String address; + isar_models.AddressType addrType; + + switch (derivePathType) { + case DerivePathType.bip44: + address = P2PKH(data: data, network: _network).data.address!; + addrType = isar_models.AddressType.p2pkh; + break; + case DerivePathType.bip49: + address = P2SH( + data: PaymentData( + redeem: P2WPKH(data: data, network: _network).data), + network: _network) + .data + .address!; + addrType = isar_models.AddressType.p2sh; + break; + case DerivePathType.bip84: + address = P2WPKH(network: _network, data: data).data.address!; + addrType = isar_models.AddressType.p2wpkh; + break; + default: + throw Exception("DerivePathType $derivePathType not supported"); + } + + // add generated address & info to derivations + // await addDerivation( + // chain: chain, + // address: address, + // pubKey: Format.uint8listToString(node.publicKey), + // wif: node.toWIF(), + // derivePathType: derivePathType, + // ); + + return isar_models.Address( + walletId: walletId, + value: address, + publicKey: node.publicKey, + type: addrType, + derivationIndex: index, + derivationPath: isar_models.DerivationPath()..value = derivePath, + subType: chain == 0 + ? isar_models.AddressSubType.receiving + : isar_models.AddressSubType.change, + ); + } + + /// Returns the latest receiving/change (external/internal) address for the wallet depending on [chain] + /// and + /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! + Future _getCurrentAddressForChain( + int chain, + DerivePathType derivePathType, + ) async { + final subType = chain == 0 // Here, we assume that chain == 1 if it isn't 0 + ? isar_models.AddressSubType.receiving + : isar_models.AddressSubType.change; + + isar_models.AddressType type; + isar_models.Address? address; + switch (derivePathType) { + case DerivePathType.bip44: + type = isar_models.AddressType.p2pkh; + break; + case DerivePathType.bip49: + type = isar_models.AddressType.p2sh; + break; + case DerivePathType.bip84: + type = isar_models.AddressType.p2wpkh; + break; + default: + throw Exception("DerivePathType unsupported"); + } + address = await db + .getAddresses(walletId) + .filter() + .typeEqualTo(type) + .subTypeEqualTo(subType) + .sortByDerivationIndexDesc() + .findFirst(); + return address!.value; + } + + Future>> fastFetch(List allTxHashes) async { + List> allTransactions = []; + + const futureLimit = 30; + List>> transactionFutures = []; + int currentFutureCount = 0; + for (final txHash in allTxHashes) { + Future> transactionFuture = + cachedElectrumXClient.getTransaction( + txHash: txHash, + verbose: true, + coin: coin, + ); + transactionFutures.add(transactionFuture); + currentFutureCount++; + if (currentFutureCount > futureLimit) { + currentFutureCount = 0; + await Future.wait(transactionFutures); + for (final fTx in transactionFutures) { + final tx = await fTx; + + allTransactions.add(tx); + } + } + } + if (currentFutureCount != 0) { + currentFutureCount = 0; + await Future.wait(transactionFutures); + for (final fTx in transactionFutures) { + final tx = await fTx; + + allTransactions.add(tx); + } + } + return allTransactions; + } + + Future _updateUTXOs() async { + final allAddresses = await _fetchAllOwnAddresses(); + + try { + final fetchedUtxoList = >>[]; + + final Map>> batches = {}; + const batchSizeMax = 100; + int batchNumber = 0; + for (int i = 0; i < allAddresses.length; i++) { + if (batches[batchNumber] == null) { + batches[batchNumber] = {}; + } + final scripthash = + AddressUtils.convertToScriptHash(allAddresses[i].value, _network); + batches[batchNumber]!.addAll({ + scripthash: [scripthash] + }); + if (i % batchSizeMax == batchSizeMax - 1) { + batchNumber++; + } + } + + for (int i = 0; i < batches.length; i++) { + final response = + await _electrumXClient.getBatchUTXOs(args: batches[i]!); + for (final entry in response.entries) { + if (entry.value.isNotEmpty) { + fetchedUtxoList.add(entry.value); + } + } + } + + final List outputArray = []; + + for (int i = 0; i < fetchedUtxoList.length; i++) { + for (int j = 0; j < fetchedUtxoList[i].length; j++) { + final jsonUTXO = fetchedUtxoList[i][j]; + + final txn = await cachedElectrumXClient.getTransaction( + txHash: jsonUTXO["tx_hash"] as String, + verbose: true, + coin: coin, + ); + + bool shouldBlock = false; + String? blockReason; + String? label; + + final vout = jsonUTXO["tx_pos"] as int; + + final outputs = txn["vout"] as List; + + String? utxoOwnerAddress; + // get UTXO owner address + for (final output in outputs) { + if (output["n"] == vout) { + utxoOwnerAddress = + output["scriptPubKey"]?["addresses"]?[0] as String? ?? + output["scriptPubKey"]?["address"] as String?; + } + } + + final utxo = isar_models.UTXO( + walletId: walletId, + txid: txn["txid"] as String, + vout: vout, + value: jsonUTXO["value"] as int, + name: label ?? "", + isBlocked: shouldBlock, + blockedReason: blockReason, + isCoinbase: txn["is_coinbase"] as bool? ?? false, + blockHash: txn["blockhash"] as String?, + blockHeight: jsonUTXO["height"] as int?, + blockTime: txn["blocktime"] as int?, + address: utxoOwnerAddress, + ); + + outputArray.add(utxo); + } + } + + Logging.instance + .log('Outputs fetched: $outputArray', level: LogLevel.Info); + + await db.updateUTXOs(walletId, outputArray); + + // finally update balance + await _updateBalance(); + } catch (e, s) { + Logging.instance + .log("Output fetch unsuccessful: $e\n$s", level: LogLevel.Error); + } + } + + Future _updateBalance() async { + await refreshBalance(); + } + + @override + Balance get balance => _balance ??= getCachedBalance(); + Balance? _balance; + + Future getTxCount({required String address}) async { + String? scripthash; + try { + scripthash = AddressUtils.convertToScriptHash(address, _network); + final transactions = + await electrumXClient.getHistory(scripthash: scripthash); + return transactions.length; + } catch (e) { + Logging.instance.log( + "Exception rethrown in _getTxCount(address: $address, scripthash: $scripthash): $e", + level: LogLevel.Error); + rethrow; + } + } + + Future> _getBatchTxCount({ + required Map addresses, + }) async { + try { + final Map> args = {}; + for (final entry in addresses.entries) { + args[entry.key] = [ + AddressUtils.convertToScriptHash(entry.value, _network) + ]; + } + final response = await electrumXClient.getBatchHistory(args: args); + + final Map result = {}; + for (final entry in response.entries) { + result[entry.key] = entry.value.length; + } + return result; + } catch (e, s) { + Logging.instance.log( + "Exception rethrown in _getBatchTxCount(address: $addresses: $e\n$s", + level: LogLevel.Error); + rethrow; + } + } + + Future _checkReceivingAddressForTransactions() async { + try { + final currentReceiving = await _currentReceivingAddress; + + final int txCount = await getTxCount(address: currentReceiving.value); + Logging.instance.log( + 'Number of txs for current receiving address $currentReceiving: $txCount', + level: LogLevel.Info); + + if (txCount >= 1 || currentReceiving.derivationIndex < 0) { + // First increment the receiving index + final newReceivingIndex = currentReceiving.derivationIndex + 1; + + // Use new index to derive a new receiving address + final newReceivingAddress = await _generateAddressForChain( + 0, newReceivingIndex, DerivePathTypeExt.primaryFor(coin)); + + final existing = await db + .getAddresses(walletId) + .filter() + .valueEqualTo(newReceivingAddress.value) + .findFirst(); + if (existing == null) { + // Add that new change address + await db.putAddress(newReceivingAddress); + } else { + // we need to update the address + await db.updateAddress(existing, newReceivingAddress); + } + // keep checking until address with no tx history is set as current + await _checkReceivingAddressForTransactions(); + } + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $e\n$s", + level: LogLevel.Error); + rethrow; + } + } + + Future _checkChangeAddressForTransactions() async { + try { + final currentChange = await _currentChangeAddress; + final int txCount = await getTxCount(address: currentChange.value); + Logging.instance.log( + 'Number of txs for current change address $currentChange: $txCount', + level: LogLevel.Info); + + if (txCount >= 1 || currentChange.derivationIndex < 0) { + // First increment the change index + final newChangeIndex = currentChange.derivationIndex + 1; + + // Use new index to derive a new change address + final newChangeAddress = await _generateAddressForChain( + 1, newChangeIndex, DerivePathTypeExt.primaryFor(coin)); + + final existing = await db + .getAddresses(walletId) + .filter() + .valueEqualTo(newChangeAddress.value) + .findFirst(); + if (existing == null) { + // Add that new change address + await db.putAddress(newChangeAddress); + } else { + // we need to update the address + await db.updateAddress(existing, newChangeAddress); + } + // keep checking until address with no tx history is set as current + await _checkChangeAddressForTransactions(); + } + } on SocketException catch (se, s) { + Logging.instance.log( + "SocketException caught in _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $se\n$s", + level: LogLevel.Error); + return; + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $e\n$s", + level: LogLevel.Error); + rethrow; + } + } + + Future>> _fetchHistory( + List allAddresses) async { + try { + List> allTxHashes = []; + + final Map>> batches = {}; + final Map requestIdToAddressMap = {}; + const batchSizeMax = 100; + int batchNumber = 0; + for (int i = 0; i < allAddresses.length; i++) { + if (batches[batchNumber] == null) { + batches[batchNumber] = {}; + } + final scripthash = + AddressUtils.convertToScriptHash(allAddresses[i], _network); + final id = Logger.isTestEnv ? "$i" : const Uuid().v1(); + requestIdToAddressMap[id] = allAddresses[i]; + batches[batchNumber]!.addAll({ + id: [scripthash] + }); + if (i % batchSizeMax == batchSizeMax - 1) { + batchNumber++; + } + } + + for (int i = 0; i < batches.length; i++) { + final response = + await _electrumXClient.getBatchHistory(args: batches[i]!); + for (final entry in response.entries) { + for (int j = 0; j < entry.value.length; j++) { + entry.value[j]["address"] = requestIdToAddressMap[entry.key]; + if (!allTxHashes.contains(entry.value[j])) { + allTxHashes.add(entry.value[j]); + } + } + } + } + + return allTxHashes; + } catch (e, s) { + Logging.instance.log("_fetchHistory: $e\n$s", level: LogLevel.Error); + rethrow; + } + } + + bool _duplicateTxCheck( + List> allTransactions, String txid) { + for (int i = 0; i < allTransactions.length; i++) { + if (allTransactions[i]["txid"] == txid) { + return true; + } + } + return false; + } + + Future _refreshTransactions() async { + final List allAddresses = + await _fetchAllOwnAddresses(); + + final Set> allTxHashes = + (await _fetchHistory(allAddresses.map((e) => e.value).toList())) + .toSet(); + + // // prefetch/cache + // Set hashes = {}; + // for (var element in allReceivingTxHashes) { + // hashes.add(element['tx_hash'] as String); + // } + // await fastFetch(hashes.toList()); + + List> allTransactions = []; + + final currentHeight = await chainHeight; + + for (final txHash in allTxHashes) { + final storedTx = await db + .getTransactions(walletId) + .filter() + .txidEqualTo(txHash["tx_hash"] as String) + .findFirst(); + + if (storedTx == null || + !storedTx.isConfirmed(currentHeight, MINIMUM_CONFIRMATIONS)) { + final tx = await cachedElectrumXClient.getTransaction( + txHash: txHash["tx_hash"] as String, + verbose: true, + coin: coin, + ); + + if (!_duplicateTxCheck(allTransactions, tx["txid"] as String)) { + tx["address"] = await db + .getAddresses(walletId) + .filter() + .valueEqualTo(txHash["address"] as String) + .findFirst(); + tx["height"] = txHash["height"]; + allTransactions.add(tx); + } + } + } + + // // prefetch/cache + // Set vHashes = {}; + // for (final txObject in allTransactions) { + // for (int i = 0; i < (txObject["vin"] as List).length; i++) { + // final input = txObject["vin"]![i] as Map; + // final prevTxid = input["txid"] as String; + // vHashes.add(prevTxid); + // } + // } + // await fastFetch(vHashes.toList()); + + final List> txnsData = + []; + + for (final txObject in allTransactions) { + final data = await parseTransaction( + txObject, + cachedElectrumXClient, + allAddresses, + coin, + MINIMUM_CONFIRMATIONS, + walletId, + ); + + txnsData.add(data); + } + await db.addNewTransactionData(txnsData, walletId); + + // quick hack to notify manager to call notifyListeners if + // transactions changed + if (txnsData.isNotEmpty) { + GlobalEventBus.instance.fire( + UpdatedInBackgroundEvent( + "Transactions updated/added for: $walletId $walletName ", + walletId, + ), + ); + } + } + + int estimateTxFee({required int vSize, required int feeRatePerKB}) { + return vSize * (feeRatePerKB / 1000).ceil(); + } + + /// The coinselection algorithm decides whether or not the user is eligible to make the transaction + /// with [satoshiAmountToSend] and [selectedTxFeeRate]. If so, it will call buildTrasaction() and return + /// a map containing the tx hex along with other important information. If not, then it will return + /// an integer (1 or 2) + dynamic coinSelection({ + required int satoshiAmountToSend, + required int selectedTxFeeRate, + required String recipientAddress, + required bool coinControl, + required bool isSendAll, + int additionalOutputs = 0, + List? utxos, + }) async { + Logging.instance + .log("Starting coinSelection ----------", level: LogLevel.Info); + final List availableOutputs = utxos ?? await this.utxos; + final currentChainHeight = await chainHeight; + final List spendableOutputs = []; + int spendableSatoshiValue = 0; + + // Build list of spendable outputs and totaling their satoshi amount + for (final utxo in availableOutputs) { + if (utxo.isBlocked == false && + utxo.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS) && + utxo.used != true) { + spendableOutputs.add(utxo); + spendableSatoshiValue += utxo.value; + } + } + + if (coinControl) { + if (spendableOutputs.length < availableOutputs.length) { + throw ArgumentError("Attempted to use an unavailable utxo"); + } + } + + // don't care about sorting if using all utxos + if (!coinControl) { + // sort spendable by age (oldest first) + spendableOutputs.sort((a, b) => b.blockTime!.compareTo(a.blockTime!)); + } + + Logging.instance.log("spendableOutputs.length: ${spendableOutputs.length}", + level: LogLevel.Info); + Logging.instance.log("availableOutputs.length: ${availableOutputs.length}", + level: LogLevel.Info); + Logging.instance + .log("spendableOutputs: $spendableOutputs", level: LogLevel.Info); + Logging.instance.log("spendableSatoshiValue: $spendableSatoshiValue", + level: LogLevel.Info); + Logging.instance + .log("satoshiAmountToSend: $satoshiAmountToSend", level: LogLevel.Info); + // If the amount the user is trying to send is smaller than the amount that they have spendable, + // then return 1, which indicates that they have an insufficient balance. + if (spendableSatoshiValue < satoshiAmountToSend) { + return 1; + // If the amount the user wants to send is exactly equal to the amount they can spend, then return + // 2, which indicates that they are not leaving enough over to pay the transaction fee + } else if (spendableSatoshiValue == satoshiAmountToSend && !isSendAll) { + return 2; + } + // If neither of these statements pass, we assume that the user has a spendable balance greater + // than the amount they're attempting to send. Note that this value still does not account for + // the added transaction fee, which may require an extra input and will need to be checked for + // later on. + + // Possible situation right here + int satoshisBeingUsed = 0; + int inputsBeingConsumed = 0; + List utxoObjectsToUse = []; + + if (!coinControl) { + for (var i = 0; + satoshisBeingUsed < satoshiAmountToSend && + i < spendableOutputs.length; + i++) { + utxoObjectsToUse.add(spendableOutputs[i]); + satoshisBeingUsed += spendableOutputs[i].value; + inputsBeingConsumed += 1; + } + for (int i = 0; + i < additionalOutputs && + inputsBeingConsumed < spendableOutputs.length; + i++) { + utxoObjectsToUse.add(spendableOutputs[inputsBeingConsumed]); + satoshisBeingUsed += spendableOutputs[inputsBeingConsumed].value; + inputsBeingConsumed += 1; + } + } else { + satoshisBeingUsed = spendableSatoshiValue; + utxoObjectsToUse = spendableOutputs; + inputsBeingConsumed = spendableOutputs.length; + } + + Logging.instance + .log("satoshisBeingUsed: $satoshisBeingUsed", level: LogLevel.Info); + Logging.instance + .log("inputsBeingConsumed: $inputsBeingConsumed", level: LogLevel.Info); + Logging.instance + .log('utxoObjectsToUse: $utxoObjectsToUse', level: LogLevel.Info); + + // numberOfOutputs' length must always be equal to that of recipientsArray and recipientsAmtArray + List recipientsArray = [recipientAddress]; + List recipientsAmtArray = [satoshiAmountToSend]; + + // gather required signing data + final utxoSigningData = await fetchBuildTxData(utxoObjectsToUse); + + if (isSendAll) { + Logging.instance + .log("Attempting to send all $coin", level: LogLevel.Info); + + final int vSizeForOneOutput = (await buildTransaction( + utxoSigningData: utxoSigningData, + recipients: [recipientAddress], + satoshiAmounts: [satoshisBeingUsed - 1], + ))["vSize"] as int; + int feeForOneOutput = estimateTxFee( + vSize: vSizeForOneOutput, + feeRatePerKB: selectedTxFeeRate, + ); + + final int roughEstimate = roughFeeEstimate( + spendableOutputs.length, + 1, + selectedTxFeeRate, + ).raw.toInt(); + if (feeForOneOutput < roughEstimate) { + feeForOneOutput = roughEstimate; + } + + final int amount = satoshiAmountToSend - feeForOneOutput; + dynamic txn = await buildTransaction( + utxoSigningData: utxoSigningData, + recipients: recipientsArray, + satoshiAmounts: [amount], + ); + Map transactionObject = { + "hex": txn["hex"], + "recipient": recipientsArray[0], + "recipientAmt": Amount( + rawValue: BigInt.from(amount), + fractionDigits: coin.decimals, + ), + "fee": feeForOneOutput, + "vSize": txn["vSize"], + "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), + }; + return transactionObject; + } + + final int vSizeForOneOutput; + try { + vSizeForOneOutput = (await buildTransaction( + utxoSigningData: utxoSigningData, + recipients: [recipientAddress], + satoshiAmounts: [satoshisBeingUsed - 1], + ))["vSize"] as int; + } catch (e) { + Logging.instance.log("vSizeForOneOutput: $e", level: LogLevel.Error); + rethrow; + } + + final int vSizeForTwoOutPuts; + try { + vSizeForTwoOutPuts = (await buildTransaction( + utxoSigningData: utxoSigningData, + recipients: [ + recipientAddress, + await _getCurrentAddressForChain( + 1, DerivePathTypeExt.primaryFor(coin)), + ], + satoshiAmounts: [ + satoshiAmountToSend, + max(0, satoshisBeingUsed - satoshiAmountToSend - 1), + ], + ))["vSize"] as int; + } catch (e) { + Logging.instance.log("vSizeForTwoOutPuts: $e", level: LogLevel.Error); + rethrow; + } + + // Assume 1 output, only for recipient and no change + final feeForOneOutput = estimateTxFee( + vSize: vSizeForOneOutput, + feeRatePerKB: selectedTxFeeRate, + ); + // Assume 2 outputs, one for recipient and one for change + final feeForTwoOutputs = estimateTxFee( + vSize: vSizeForTwoOutPuts, + feeRatePerKB: selectedTxFeeRate, + ); + + Logging.instance + .log("feeForTwoOutputs: $feeForTwoOutputs", level: LogLevel.Info); + Logging.instance + .log("feeForOneOutput: $feeForOneOutput", level: LogLevel.Info); + + if (satoshisBeingUsed - satoshiAmountToSend > feeForOneOutput) { + if (satoshisBeingUsed - satoshiAmountToSend > + feeForOneOutput + DUST_LIMIT.raw.toInt()) { + // Here, we know that theoretically, we may be able to include another output(change) but we first need to + // factor in the value of this output in satoshis. + int changeOutputSize = + satoshisBeingUsed - satoshiAmountToSend - feeForTwoOutputs; + // We check to see if the user can pay for the new transaction with 2 outputs instead of one. If they can and + // the second output's size > DUST_LIMIT satoshis, we perform the mechanics required to properly generate and use a new + // change address. + if (changeOutputSize > DUST_LIMIT.raw.toInt() && + satoshisBeingUsed - satoshiAmountToSend - changeOutputSize == + feeForTwoOutputs) { + // generate new change address if current change address has been used + await _checkChangeAddressForTransactions(); + final String newChangeAddress = await _getCurrentAddressForChain( + 1, DerivePathTypeExt.primaryFor(coin)); + + int feeBeingPaid = + satoshisBeingUsed - satoshiAmountToSend - changeOutputSize; + + recipientsArray.add(newChangeAddress); + recipientsAmtArray.add(changeOutputSize); + // At this point, we have the outputs we're going to use, the amounts to send along with which addresses + // we intend to send these amounts to. We have enough to send instructions to build the transaction. + Logging.instance.log('2 outputs in tx', level: LogLevel.Info); + Logging.instance + .log('Input size: $satoshisBeingUsed', level: LogLevel.Info); + Logging.instance.log('Recipient output size: $satoshiAmountToSend', + level: LogLevel.Info); + Logging.instance.log('Change Output Size: $changeOutputSize', + level: LogLevel.Info); + Logging.instance.log( + 'Difference (fee being paid): $feeBeingPaid sats', + level: LogLevel.Info); + Logging.instance + .log('Estimated fee: $feeForTwoOutputs', level: LogLevel.Info); + dynamic txn = await buildTransaction( + utxoSigningData: utxoSigningData, + recipients: recipientsArray, + satoshiAmounts: recipientsAmtArray, + ); + + // make sure minimum fee is accurate if that is being used + if (txn["vSize"] - feeBeingPaid == 1) { + int changeOutputSize = + satoshisBeingUsed - satoshiAmountToSend - (txn["vSize"] as int); + feeBeingPaid = + satoshisBeingUsed - satoshiAmountToSend - changeOutputSize; + recipientsAmtArray.removeLast(); + recipientsAmtArray.add(changeOutputSize); + Logging.instance.log('Adjusted Input size: $satoshisBeingUsed', + level: LogLevel.Info); + Logging.instance.log( + 'Adjusted Recipient output size: $satoshiAmountToSend', + level: LogLevel.Info); + Logging.instance.log( + 'Adjusted Change Output Size: $changeOutputSize', + level: LogLevel.Info); + Logging.instance.log( + 'Adjusted Difference (fee being paid): $feeBeingPaid sats', + level: LogLevel.Info); + Logging.instance.log('Adjusted Estimated fee: $feeForTwoOutputs', + level: LogLevel.Info); + txn = await buildTransaction( + utxoSigningData: utxoSigningData, + recipients: recipientsArray, + satoshiAmounts: recipientsAmtArray, + ); + } + + Map transactionObject = { + "hex": txn["hex"], + "recipient": recipientsArray[0], + "recipientAmt": Amount( + rawValue: BigInt.from(recipientsAmtArray[0]), + fractionDigits: coin.decimals, + ), + "fee": feeBeingPaid, + "vSize": txn["vSize"], + "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), + }; + return transactionObject; + } else { + // Something went wrong here. It either overshot or undershot the estimated fee amount or the changeOutputSize + // is smaller than or equal to DUST_LIMIT. Revert to single output transaction. + Logging.instance.log('1 output in tx', level: LogLevel.Info); + Logging.instance + .log('Input size: $satoshisBeingUsed', level: LogLevel.Info); + Logging.instance.log('Recipient output size: $satoshiAmountToSend', + level: LogLevel.Info); + Logging.instance.log( + 'Difference (fee being paid): ${satoshisBeingUsed - satoshiAmountToSend} sats', + level: LogLevel.Info); + Logging.instance + .log('Estimated fee: $feeForOneOutput', level: LogLevel.Info); + dynamic txn = await buildTransaction( + utxoSigningData: utxoSigningData, + recipients: recipientsArray, + satoshiAmounts: recipientsAmtArray, + ); + Map transactionObject = { + "hex": txn["hex"], + "recipient": recipientsArray[0], + "recipientAmt": Amount( + rawValue: BigInt.from(recipientsAmtArray[0]), + fractionDigits: coin.decimals, + ), + "fee": satoshisBeingUsed - satoshiAmountToSend, + "vSize": txn["vSize"], + "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), + }; + return transactionObject; + } + } else { + // No additional outputs needed since adding one would mean that it'd be smaller than DUST_LIMIT sats + // which makes it uneconomical to add to the transaction. Here, we pass data directly to instruct + // the wallet to begin crafting the transaction that the user requested. + Logging.instance.log('1 output in tx', level: LogLevel.Info); + Logging.instance + .log('Input size: $satoshisBeingUsed', level: LogLevel.Info); + Logging.instance.log('Recipient output size: $satoshiAmountToSend', + level: LogLevel.Info); + Logging.instance.log( + 'Difference (fee being paid): ${satoshisBeingUsed - satoshiAmountToSend} sats', + level: LogLevel.Info); + Logging.instance + .log('Estimated fee: $feeForOneOutput', level: LogLevel.Info); + dynamic txn = await buildTransaction( + utxoSigningData: utxoSigningData, + recipients: recipientsArray, + satoshiAmounts: recipientsAmtArray, + ); + Map transactionObject = { + "hex": txn["hex"], + "recipient": recipientsArray[0], + "recipientAmt": Amount( + rawValue: BigInt.from(recipientsAmtArray[0]), + fractionDigits: coin.decimals, + ), + "fee": satoshisBeingUsed - satoshiAmountToSend, + "vSize": txn["vSize"], + "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), + }; + return transactionObject; + } + } else if (satoshisBeingUsed - satoshiAmountToSend == feeForOneOutput) { + // In this scenario, no additional change output is needed since inputs - outputs equal exactly + // what we need to pay for fees. Here, we pass data directly to instruct the wallet to begin + // crafting the transaction that the user requested. + Logging.instance.log('1 output in tx', level: LogLevel.Info); + Logging.instance + .log('Input size: $satoshisBeingUsed', level: LogLevel.Info); + Logging.instance.log('Recipient output size: $satoshiAmountToSend', + level: LogLevel.Info); + Logging.instance.log( + 'Fee being paid: ${satoshisBeingUsed - satoshiAmountToSend} sats', + level: LogLevel.Info); + Logging.instance + .log('Estimated fee: $feeForOneOutput', level: LogLevel.Info); + dynamic txn = await buildTransaction( + utxoSigningData: utxoSigningData, + recipients: recipientsArray, + satoshiAmounts: recipientsAmtArray, + ); + Map transactionObject = { + "hex": txn["hex"], + "recipient": recipientsArray[0], + "recipientAmt": Amount( + rawValue: BigInt.from(recipientsAmtArray[0]), + fractionDigits: coin.decimals, + ), + "fee": feeForOneOutput, + "vSize": txn["vSize"], + "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), + }; + return transactionObject; + } else { + // Remember that returning 2 indicates that the user does not have a sufficient balance to + // pay for the transaction fee. Ideally, at this stage, we should check if the user has any + // additional outputs they're able to spend and then recalculate fees. + Logging.instance.log( + 'Cannot pay tx fee - checking for more outputs and trying again', + level: LogLevel.Warning); + // try adding more outputs + if (spendableOutputs.length > inputsBeingConsumed) { + return coinSelection( + satoshiAmountToSend: satoshiAmountToSend, + selectedTxFeeRate: selectedTxFeeRate, + recipientAddress: recipientAddress, + isSendAll: isSendAll, + additionalOutputs: additionalOutputs + 1, + utxos: utxos, + coinControl: coinControl, + ); + } + return 2; + } + } + + Future> fetchBuildTxData( + List utxosToUse, + ) async { + // return data + List signingData = []; + + try { + // Populating the addresses to check + for (var i = 0; i < utxosToUse.length; i++) { + if (utxosToUse[i].address == null) { + final txid = utxosToUse[i].txid; + final tx = await _cachedElectrumXClient.getTransaction( + txHash: txid, + coin: coin, + ); + for (final output in tx["vout"] as List) { + final n = output["n"]; + if (n != null && n == utxosToUse[i].vout) { + utxosToUse[i] = utxosToUse[i].copyWith( + address: output["scriptPubKey"]?["addresses"]?[0] as String? ?? + output["scriptPubKey"]["address"] as String, + ); + } + } + } + + final derivePathType = addressType(address: utxosToUse[i].address!); + + signingData.add( + SigningData( + derivePathType: derivePathType, + utxo: utxosToUse[i], + ), + ); + } + + final root = await Bip32Utils.getBip32Root( + (await mnemonicString)!, + (await mnemonicPassphrase)!, + _network, + ); + + for (final sd in signingData) { + final address = await db.getAddress(walletId, sd.utxo.address!); + final node = await Bip32Utils.getBip32NodeFromRoot( + root, + address!.derivationPath!.value, + ); + + final paymentData = PaymentData(pubkey: node.publicKey); + + final PaymentData data; + final Uint8List? redeemScript; + + switch (sd.derivePathType) { + case DerivePathType.bip44: + data = P2PKH( + data: paymentData, + network: _network, + ).data; + redeemScript = null; + break; + + case DerivePathType.bip49: + final p2wpkh = P2WPKH( + data: paymentData, + network: _network, + ).data; + redeemScript = p2wpkh.output; + data = P2SH( + data: PaymentData(redeem: p2wpkh), + network: _network, + ).data; + break; + + case DerivePathType.bip84: + data = P2WPKH( + data: paymentData, + network: _network, + ).data; + redeemScript = null; + break; + + default: + throw Exception("DerivePathType unsupported"); + } + + final keyPair = ECPair.fromWIF( + node.toWIF(), + network: _network, + ); + + sd.redeemScript = redeemScript; + sd.output = data.output; + sd.keyPair = keyPair; + } + + return signingData; + } catch (e, s) { + Logging.instance + .log("fetchBuildTxData() threw: $e,\n$s", level: LogLevel.Error); + rethrow; + } + } + + /// Builds and signs a transaction + Future> buildTransaction({ + required List utxoSigningData, + required List recipients, + required List satoshiAmounts, + }) async { + Logging.instance + .log("Starting buildTransaction ----------", level: LogLevel.Info); + + final txb = TransactionBuilder(network: _network); + txb.setVersion(1); + + // Add transaction inputs + for (var i = 0; i < utxoSigningData.length; i++) { + final txid = utxoSigningData[i].utxo.txid; + txb.addInput( + txid, + utxoSigningData[i].utxo.vout, + null, + utxoSigningData[i].output!, + ); + } + + // Add transaction output + for (var i = 0; i < recipients.length; i++) { + txb.addOutput(recipients[i], satoshiAmounts[i]); + } + + try { + // Sign the transaction accordingly + for (var i = 0; i < utxoSigningData.length; i++) { + txb.sign( + vin: i, + keyPair: utxoSigningData[i].keyPair!, + witnessValue: utxoSigningData[i].utxo.value, + redeemScript: utxoSigningData[i].redeemScript, + ); + } + } catch (e, s) { + Logging.instance.log("Caught exception while signing transaction: $e\n$s", + level: LogLevel.Error); + rethrow; + } + + final builtTx = txb.build(); + final vSize = builtTx.virtualSize(); + + return {"hex": builtTx.toHex(), "vSize": vSize}; + } + + @override + Future fullRescan( + int maxUnusedAddressGap, + int maxNumberOfIndexesToCheck, + ) async { + Logging.instance.log("Starting full rescan!", level: LogLevel.Info); + longMutex = true; + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.syncing, + walletId, + coin, + ), + ); + + // clear cache + await _cachedElectrumXClient.clearSharedTransactionCache(coin: coin); + + // back up data + // await _rescanBackup(); + + await db.deleteWalletBlockchainData(walletId); + + try { + final _mnemonic = await mnemonicString; + final _mnemonicPassphrase = await mnemonicPassphrase; + if (_mnemonicPassphrase == null) { + Logging.instance.log( + "Exception in fullRescan: mnemonic passphrase null, possible migration issue; if using internal builds, delete wallet and restore from seed, if using a release build, please file bug report", + level: LogLevel.Error); + } + + await _recoverWalletFromBIP32SeedPhrase( + mnemonic: _mnemonic!, + mnemonicPassphrase: _mnemonicPassphrase!, + maxUnusedAddressGap: maxUnusedAddressGap, + maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, + isRescan: true, + ); + + longMutex = false; + await refresh(); + Logging.instance.log("Full rescan complete!", level: LogLevel.Info); + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.synced, + walletId, + coin, + ), + ); + } catch (e, s) { + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.unableToSync, + walletId, + coin, + ), + ); + + // restore from backup + // await _rescanRestore(); + + longMutex = false; + Logging.instance.log("Exception rethrown from fullRescan(): $e\n$s", + level: LogLevel.Error); + rethrow; + } + } + + Future _recoverWalletFromBIP32SeedPhrase({ + required String mnemonic, + required String mnemonicPassphrase, + int maxUnusedAddressGap = 20, + int maxNumberOfIndexesToCheck = 1000, + bool isRescan = false, + }) async { + longMutex = true; + + final root = await Bip32Utils.getBip32Root( + mnemonic, + mnemonicPassphrase, + _network, + ); + + List bip44P2pkhReceiveAddressArray = []; + // List p2shReceiveAddressArray = []; + int bip44P2pkhReceiveIndex = -1; + // int p2shReceiveIndex = -1; + + List bip44P2pkhChangeAddressArray = []; + // List p2shChangeAddressArray = []; + int bip44P2pkhChangeIndex = -1; + // int p2shChangeIndex = -1; + + const txCountBatchSize = 12; + + try { + // receiving addresses + Logging.instance + .log("checking receiving addresses...", level: LogLevel.Info); + final resultReceiveBip44 = _checkGaps(maxNumberOfIndexesToCheck, + maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip44, 0); + + // final resultReceive49 = _checkGaps(maxNumberOfIndexesToCheck, + // maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip49, 0); + + Logging.instance + .log("checking change addresses...", level: LogLevel.Info); + // change addresses + final bip44ResultChange = _checkGaps(maxNumberOfIndexesToCheck, + maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip44, 1); + + // final resultChange49 = _checkGaps(maxNumberOfIndexesToCheck, + // maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip49, 1); + + await Future.wait([ + resultReceiveBip44, + // resultReceive49, + bip44ResultChange, + // resultChange49, + ]); + + bip44P2pkhReceiveAddressArray = (await resultReceiveBip44)['addressArray'] + as List; + bip44P2pkhReceiveIndex = (await resultReceiveBip44)['index'] as int; + + // p2shReceiveAddressArray = + // (await resultReceive49)['addressArray'] as List; + // p2shReceiveIndex = (await resultReceive49)['index'] as int; + + bip44P2pkhChangeAddressArray = (await bip44ResultChange)['addressArray'] + as List; + bip44P2pkhChangeIndex = (await bip44ResultChange)['index'] as int; + + // p2shChangeAddressArray = + // (await resultChange49)['addressArray'] as List; + // p2shChangeIndex = (await resultChange49)['index'] as int; + + // If restoring a wallet that never received any funds, then set receivingArray manually + // If we didn't do this, it'd store an empty array + if (bip44P2pkhReceiveIndex == -1) { + final address = + await _generateAddressForChain(0, 0, DerivePathType.bip44); + bip44P2pkhReceiveAddressArray.add(address); + } + // if (p2shReceiveIndex == -1) { + // final address = + // await _generateAddressForChain(0, 0, DerivePathType.bip49); + // p2shReceiveAddressArray.add(address); + // } + + // If restoring a wallet that never sent any funds with change, then set changeArray + // manually. If we didn't do this, it'd store an empty array. + if (bip44P2pkhChangeIndex == -1) { + final address = + await _generateAddressForChain(1, 0, DerivePathType.bip44); + bip44P2pkhChangeAddressArray.add(address); + } + // if (p2shChangeIndex == -1) { + // final address = + // await _generateAddressForChain(1, 0, DerivePathType.bip49); + // p2shChangeAddressArray.add(address); + // } + + final addressesToStore = [ + ...bip44P2pkhReceiveAddressArray, + ...bip44P2pkhChangeAddressArray, + // ...p2shReceiveAddressArray, + // ...p2shChangeAddressArray, + ]; + + if (isRescan) { + await db.updateOrPutAddresses(addressesToStore); + } else { + await db.putAddresses(addressesToStore); + } + + await _updateUTXOs(); + + await Future.wait([ + updateCachedId(walletId), + updateCachedIsFavorite(false), + ]); + + longMutex = false; + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from _recoverWalletFromBIP32SeedPhrase(): $e\n$s", + level: LogLevel.Info); + + longMutex = false; + rethrow; + } + } + + Future> _checkGaps( + int maxNumberOfIndexesToCheck, + int maxUnusedAddressGap, + int txCountBatchSize, + bip32.BIP32 root, + DerivePathType type, + int chain, + ) async { + List addressArray = []; + int returningIndex = -1; + int gapCounter = 0; + for (int index = 0; + index < maxNumberOfIndexesToCheck && gapCounter < maxUnusedAddressGap; + index += txCountBatchSize) { + List iterationsAddressArray = []; + Logging.instance.log( + "index: $index, \t GapCounter $chain ${type.name}: $gapCounter", + level: LogLevel.Info); + + final _id = "k_$index"; + Map txCountCallArgs = {}; + final Map receivingNodes = {}; + + for (int j = 0; j < txCountBatchSize; j++) { + final derivePath = constructDerivePath( + derivePathType: type, + networkWIF: root.network.wif, + chain: chain, + index: index + j, + ); + final node = await Bip32Utils.getBip32NodeFromRoot(root, derivePath); + + String addressString; + final data = PaymentData(pubkey: node.publicKey); + isar_models.AddressType addrType; + switch (type) { + case DerivePathType.bip44: + addressString = P2PKH(data: data, network: _network).data.address!; + addrType = isar_models.AddressType.p2pkh; + break; + case DerivePathType.bip49: + addressString = P2SH( + data: PaymentData( + redeem: P2WPKH(data: data, network: _network).data), + network: _network) + .data + .address!; + addrType = isar_models.AddressType.p2sh; + break; + case DerivePathType.bip84: + addressString = P2WPKH(network: _network, data: data).data.address!; + addrType = isar_models.AddressType.p2wpkh; + break; + default: + throw Exception("DerivePathType $type not supported"); + } + + final address = isar_models.Address( + walletId: walletId, + value: addressString, + publicKey: node.publicKey, + type: addrType, + derivationIndex: index + j, + derivationPath: isar_models.DerivationPath()..value = derivePath, + subType: chain == 0 + ? isar_models.AddressSubType.receiving + : isar_models.AddressSubType.change, + ); + + receivingNodes.addAll({ + "${_id}_$j": { + "node": node, + "address": address, + } + }); + txCountCallArgs.addAll({ + "${_id}_$j": addressString, + }); + } + + // get address tx counts + final counts = await _getBatchTxCount(addresses: txCountCallArgs); + + // check and add appropriate addresses + for (int k = 0; k < txCountBatchSize; k++) { + int count = counts["${_id}_$k"]!; + if (count > 0) { + final node = receivingNodes["${_id}_$k"]; + final address = node["address"] as isar_models.Address; + // add address to array + addressArray.add(address); + iterationsAddressArray.add(address.value); + // set current index + returningIndex = index + k; + // reset counter + gapCounter = 0; + // add info to derivations + } + + // increase counter when no tx history found + if (count == 0) { + gapCounter++; + } + } + // cache all the transactions while waiting for the current function to finish. + unawaited(getTransactionCacheEarly(iterationsAddressArray)); + } + return { + "addressArray": addressArray, + "index": returningIndex, + }; + } + + Future getTransactionCacheEarly(List allAddresses) async { + try { + final List> allTxHashes = + await _fetchHistory(allAddresses); + for (final txHash in allTxHashes) { + try { + unawaited(cachedElectrumXClient.getTransaction( + txHash: txHash["tx_hash"] as String, + verbose: true, + coin: coin, + )); + } catch (e) { + continue; + } + } + } catch (e) { + // + } + } + + bool _shouldAutoSync = false; + + @override + bool get shouldAutoSync => _shouldAutoSync; + + @override + set shouldAutoSync(bool shouldAutoSync) { + if (_shouldAutoSync != shouldAutoSync) { + _shouldAutoSync = shouldAutoSync; + if (!shouldAutoSync) { + timer?.cancel(); + timer = null; + stopNetworkAlivePinging(); + } else { + startNetworkAlivePinging(); + refresh(); + } + } + } + + bool isActive = false; + + @override + void Function(bool)? get onIsActiveWalletChanged => + (isActive) => this.isActive = isActive; + + @override + Future estimateFeeFor(Amount amount, int feeRate) async { + final available = balance.spendable; + + if (available == amount) { + return amount - (await sweepAllEstimate(feeRate)); + } else if (amount <= Amount.zero || amount > available) { + return roughFeeEstimate(1, 2, feeRate); + } + + Amount runningBalance = Amount( + rawValue: BigInt.zero, + fractionDigits: coin.decimals, + ); + int inputCount = 0; + for (final output in (await utxos)) { + if (!output.isBlocked) { + runningBalance += Amount( + rawValue: BigInt.from(output.value), + fractionDigits: coin.decimals, + ); + inputCount++; + if (runningBalance > amount) { + break; + } + } + } + + final oneOutPutFee = roughFeeEstimate(inputCount, 1, feeRate); + final twoOutPutFee = roughFeeEstimate(inputCount, 2, feeRate); + + if (runningBalance - amount > oneOutPutFee) { + if (runningBalance - amount > oneOutPutFee + DUST_LIMIT) { + final change = runningBalance - amount - twoOutPutFee; + if (change > DUST_LIMIT && + runningBalance - amount - change == twoOutPutFee) { + return runningBalance - amount - change; + } else { + return runningBalance - amount; + } + } else { + return runningBalance - amount; + } + } else if (runningBalance - amount == oneOutPutFee) { + return oneOutPutFee; + } else { + return twoOutPutFee; + } + } + + // TODO: correct formula for ecash? + Amount roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB) { + return Amount( + rawValue: BigInt.from(((181 * inputCount) + (34 * outputCount) + 10) * + (feeRatePerKB / 1000).ceil()), + fractionDigits: coin.decimals, + ); + } + + Future sweepAllEstimate(int feeRate) async { + int available = 0; + int inputCount = 0; + for (final output in (await utxos)) { + if (!output.isBlocked && + output.isConfirmed(storedChainHeight, MINIMUM_CONFIRMATIONS)) { + available += output.value; + inputCount++; + } + } + + // transaction will only have 1 output minus the fee + final estimatedFee = roughFeeEstimate(inputCount, 1, feeRate); + + return Amount( + rawValue: BigInt.from(available), + fractionDigits: coin.decimals, + ) - + estimatedFee; + } + + @override + Future generateNewAddress() async { + try { + final currentReceiving = await _currentReceivingAddress; + + final newReceivingIndex = currentReceiving.derivationIndex + 1; + + // Use new index to derive a new receiving address + final newReceivingAddress = await _generateAddressForChain( + 0, newReceivingIndex, DerivePathTypeExt.primaryFor(coin)); + + // Add that new receiving address + await db.putAddress(newReceivingAddress); + + return true; + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from generateNewAddress(): $e\n$s", + level: LogLevel.Error); + return false; + } + } + + @override + Future get xpub async { + final node = await Bip32Utils.getBip32Root( + (await mnemonic).join(" "), + await mnemonicPassphrase ?? "", + _network, + ); + + return node.neutered().toBase58(); + } + + @override + Future> prepareSend({ + required String address, + required Amount amount, + Map? args, + }) async { + try { + final feeRateType = args?["feeRate"]; + final feeRateAmount = args?["feeRateAmount"]; + final utxos = args?["UTXOs"] as Set?; + if (feeRateType is FeeRateType || feeRateAmount is int) { + late final int rate; + if (feeRateType is FeeRateType) { + int fee = 0; + final feeObject = await fees; + switch (feeRateType) { + case FeeRateType.fast: + fee = feeObject.fast; + break; + case FeeRateType.average: + fee = feeObject.medium; + break; + case FeeRateType.slow: + fee = feeObject.slow; + break; + } + rate = fee; + } else { + rate = feeRateAmount as int; + } + + // check for send all + bool isSendAll = false; + if (amount == balance.spendable) { + isSendAll = true; + } + + final bool coinControl = utxos != null; + + final txData = await coinSelection( + satoshiAmountToSend: amount.raw.toInt(), + selectedTxFeeRate: rate, + recipientAddress: address, + isSendAll: isSendAll, + utxos: utxos?.toList(), + coinControl: coinControl, + ); + + Logging.instance.log("prepare send: $txData", level: LogLevel.Info); + try { + if (txData is int) { + switch (txData) { + case 1: + throw Exception("Insufficient balance!"); + case 2: + throw Exception( + "Insufficient funds to pay for transaction fee!"); + default: + throw Exception("Transaction failed with error code $txData"); + } + } else { + final hex = txData["hex"]; + + if (hex is String) { + final fee = txData["fee"] as int; + final vSize = txData["vSize"] as int; + + Logging.instance + .log("prepared txHex: $hex", level: LogLevel.Info); + Logging.instance.log("prepared fee: $fee", level: LogLevel.Info); + Logging.instance + .log("prepared vSize: $vSize", level: LogLevel.Info); + + // fee should never be less than vSize sanity check + if (fee < vSize) { + throw Exception( + "Error in fee calculation: Transaction fee cannot be less than vSize"); + } + + return txData as Map; + } else { + throw Exception("prepared hex is not a String!!!"); + } + } + } catch (e, s) { + Logging.instance.log("Exception rethrown from prepareSend(): $e\n$s", + level: LogLevel.Error); + rethrow; + } + } else { + throw ArgumentError("Invalid fee rate argument provided!"); + } + } catch (e, s) { + Logging.instance.log("Exception rethrown from prepareSend(): $e\n$s", + level: LogLevel.Error); + rethrow; + } + } + + @override + Future confirmSend({required Map txData}) async { + try { + Logging.instance.log("confirmSend txData: $txData", level: LogLevel.Info); + + final hex = txData["hex"] as String; + + final txHash = await _electrumXClient.broadcastTransaction(rawTx: hex); + Logging.instance.log("Sent txHash: $txHash", level: LogLevel.Info); + + final utxos = txData["usedUTXOs"] as List; + + // mark utxos as used + await db.putUTXOs(utxos.map((e) => e.copyWith(used: true)).toList()); + + return txHash; + } catch (e, s) { + Logging.instance.log("Exception rethrown from confirmSend(): $e\n$s", + level: LogLevel.Error); + rethrow; + } + } + + @override + Future testNetworkConnection() async { + try { + final result = await _electrumXClient.ping(); + return result; + } catch (_) { + return false; + } + } + + Timer? _networkAliveTimer; + + void startNetworkAlivePinging() { + // call once on start right away + _periodicPingCheck(); + + // then periodically check + _networkAliveTimer = Timer.periodic( + Constants.networkAliveTimerDuration, + (_) async { + _periodicPingCheck(); + }, + ); + } + + void _periodicPingCheck() async { + bool hasNetwork = await testNetworkConnection(); + _isConnected = hasNetwork; + if (_isConnected != hasNetwork) { + NodeConnectionStatus status = hasNetwork + ? NodeConnectionStatus.connected + : NodeConnectionStatus.disconnected; + GlobalEventBus.instance + .fire(NodeConnectionStatusChangedEvent(status, walletId, coin)); + } + } + + void stopNetworkAlivePinging() { + _networkAliveTimer?.cancel(); + _networkAliveTimer = null; + } + + bool _isConnected = false; + + @override + bool get isConnected => _isConnected; + + @override + Future initializeNew() async { + Logging.instance + .log("Generating new ${coin.prettyName} wallet.", level: LogLevel.Info); + + if (getCachedId() != null) { + throw Exception( + "Attempted to initialize a new wallet using an existing wallet ID!"); + } + + await _prefs.init(); + try { + await _generateNewWallet(); + } catch (e, s) { + Logging.instance.log("Exception rethrown from initializeNew(): $e\n$s", + level: LogLevel.Fatal); + rethrow; + } + await Future.wait([ + updateCachedId(walletId), + updateCachedIsFavorite(false), + ]); + } + + @override + Future initializeExisting() async { + Logging.instance.log("initializeExisting() ${coin.prettyName} wallet.", + level: LogLevel.Info); + + if (getCachedId() == null) { + throw Exception( + "Attempted to initialize an existing wallet using an unknown wallet ID!"); + } + + await _prefs.init(); + // await _checkCurrentChangeAddressesForTransactions(); + // await _checkCurrentReceivingAddressesForTransactions(); + } + + // hack to add tx to txData before refresh completes + // required based on current app architecture where we don't properly store + // transactions locally in a good way + @override + Future updateSentCachedTxData(Map txData) async { + final transaction = isar_models.Transaction( + walletId: walletId, + txid: txData["txid"] as String, + timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, + type: isar_models.TransactionType.outgoing, + subType: isar_models.TransactionSubType.none, + // precision may be lost here hence the following amountString + amount: (txData["recipientAmt"] as Amount).raw.toInt(), + amountString: (txData["recipientAmt"] as Amount).toJsonString(), + fee: txData["fee"] as int, + height: null, + isCancelled: false, + isLelantus: false, + otherData: null, + slateId: null, + nonce: null, + inputs: [], + outputs: [], + ); + + final address = txData["address"] is String + ? await db.getAddress(walletId, txData["address"] as String) + : null; + + await db.addNewTransactionData( + [ + Tuple2(transaction, address), + ], + walletId, + ); + } + + @override + bool get isRefreshing => refreshMutex; + + bool refreshMutex = false; + + //TODO Show percentages properly/more consistently + /// Refreshes display data for the wallet + @override + Future refresh() async { + if (refreshMutex) { + Logging.instance.log("$walletId $walletName refreshMutex denied", + level: LogLevel.Info); + return; + } else { + refreshMutex = true; + } + + try { + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.syncing, + walletId, + coin, + ), + ); + + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.0, walletId)); + + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.1, walletId)); + + final currentHeight = await chainHeight; + const storedHeight = 1; //await storedChainHeight; + + Logging.instance + .log("chain height: $currentHeight", level: LogLevel.Info); + // Logging.instance + // .log("cached height: $storedHeight", level: LogLevel.Info); + + if (currentHeight != storedHeight) { + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.2, walletId)); + await _checkChangeAddressForTransactions(); + + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.3, walletId)); + await _checkReceivingAddressForTransactions(); + + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.4, walletId)); + + final fetchFuture = _refreshTransactions(); + GlobalEventBus.instance + .fire(RefreshPercentChangedEvent(0.50, walletId)); + + final feeObj = _getFees(); + GlobalEventBus.instance + .fire(RefreshPercentChangedEvent(0.60, walletId)); + + GlobalEventBus.instance + .fire(RefreshPercentChangedEvent(0.70, walletId)); + _feeObject = Future(() => feeObj); + + await fetchFuture; + GlobalEventBus.instance + .fire(RefreshPercentChangedEvent(0.80, walletId)); + + await _updateUTXOs(); + await getAllTxsToWatch(); + GlobalEventBus.instance + .fire(RefreshPercentChangedEvent(0.90, walletId)); + } + + refreshMutex = false; + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(1.0, walletId)); + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.synced, + walletId, + coin, + ), + ); + + if (shouldAutoSync) { + timer ??= Timer.periodic(const Duration(seconds: 30), (timer) async { + Logging.instance.log( + "Periodic refresh check for $walletId $walletName in object instance: $hashCode", + level: LogLevel.Info); + // chain height check currently broken + // if ((await chainHeight) != (await storedChainHeight)) { + if (await refreshIfThereIsNewData()) { + await refresh(); + GlobalEventBus.instance.fire(UpdatedInBackgroundEvent( + "New data found in $walletId $walletName in background!", + walletId)); + } + // } + }); + } + } catch (error, strace) { + refreshMutex = false; + GlobalEventBus.instance.fire( + NodeConnectionStatusChangedEvent( + NodeConnectionStatus.disconnected, + walletId, + coin, + ), + ); + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.unableToSync, + walletId, + coin, + ), + ); + Logging.instance.log( + "Caught exception in refreshWalletData(): $error\n$strace", + level: LogLevel.Error); + } + } + + Future refreshIfThereIsNewData() async { + if (longMutex) return false; + if (_hasCalledExit) return false; + Logging.instance.log("refreshIfThereIsNewData", level: LogLevel.Info); + + try { + bool needsRefresh = false; + Set txnsToCheck = {}; + + for (final String txid in txTracker.pendings) { + if (!txTracker.wasNotifiedConfirmed(txid)) { + txnsToCheck.add(txid); + } + } + + for (String txid in txnsToCheck) { + final txn = await electrumXClient.getTransaction(txHash: txid); + int confirmations = txn["confirmations"] as int? ?? 0; + bool isUnconfirmed = confirmations < MINIMUM_CONFIRMATIONS; + if (!isUnconfirmed) { + // unconfirmedTxs = {}; + needsRefresh = true; + break; + } + } + if (!needsRefresh) { + final allOwnAddresses = await _fetchAllOwnAddresses(); + List> allTxs = await _fetchHistory( + allOwnAddresses.map((e) => e.value).toList(growable: false)); + for (Map transaction in allTxs) { + final txid = transaction['tx_hash'] as String; + if ((await db + .getTransactions(walletId) + .filter() + .txidMatches(txid) + .findFirst()) == + null) { + Logging.instance.log( + " txid not found in address history already ${transaction['tx_hash']}", + level: LogLevel.Info); + needsRefresh = true; + break; + } + } + } + return needsRefresh; + } on NoSuchTransactionException catch (e) { + // TODO: move direct transactions elsewhere + await db.isar.writeTxn(() async { + await db.isar.transactions.deleteByTxidWalletId(e.txid, walletId); + }); + await txTracker.deleteTransaction(e.txid); + return true; + } catch (e, s) { + Logging.instance.log( + "Exception caught in refreshIfThereIsNewData: $e\n$s", + level: LogLevel.Error); + rethrow; + } + } + + Future getAllTxsToWatch() async { + if (_hasCalledExit) return; + List unconfirmedTxnsToNotifyPending = []; + List unconfirmedTxnsToNotifyConfirmed = []; + + final currentChainHeight = await chainHeight; + + final txCount = await db.getTransactions(walletId).count(); + + const paginateLimit = 50; + + for (int i = 0; i < txCount; i += paginateLimit) { + final transactions = await db + .getTransactions(walletId) + .offset(i) + .limit(paginateLimit) + .findAll(); + for (final tx in transactions) { + if (tx.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS)) { + // get all transactions that were notified as pending but not as confirmed + if (txTracker.wasNotifiedPending(tx.txid) && + !txTracker.wasNotifiedConfirmed(tx.txid)) { + unconfirmedTxnsToNotifyConfirmed.add(tx); + } + } else { + // get all transactions that were not notified as pending yet + if (!txTracker.wasNotifiedPending(tx.txid)) { + unconfirmedTxnsToNotifyPending.add(tx); + } + } + } + } + + // notify on unconfirmed transactions + for (final tx in unconfirmedTxnsToNotifyPending) { + final confirmations = tx.getConfirmations(currentChainHeight); + + if (tx.type == isar_models.TransactionType.incoming) { + unawaited(NotificationApi.showNotification( + title: "Incoming transaction", + body: walletName, + walletId: walletId, + iconAssetName: Assets.svg.iconFor(coin: coin), + date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), + shouldWatchForUpdates: confirmations < MINIMUM_CONFIRMATIONS, + coinName: coin.name, + txid: tx.txid, + confirmations: confirmations, + requiredConfirmations: MINIMUM_CONFIRMATIONS, + )); + await txTracker.addNotifiedPending(tx.txid); + } else if (tx.type == isar_models.TransactionType.outgoing) { + unawaited(NotificationApi.showNotification( + title: "Sending transaction", + body: walletName, + walletId: walletId, + iconAssetName: Assets.svg.iconFor(coin: coin), + date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), + shouldWatchForUpdates: confirmations < MINIMUM_CONFIRMATIONS, + coinName: coin.name, + txid: tx.txid, + confirmations: confirmations, + requiredConfirmations: MINIMUM_CONFIRMATIONS, + )); + await txTracker.addNotifiedPending(tx.txid); + } + } + + // notify on confirmed + for (final tx in unconfirmedTxnsToNotifyConfirmed) { + if (tx.type == isar_models.TransactionType.incoming) { + unawaited(NotificationApi.showNotification( + title: "Incoming transaction confirmed", + body: walletName, + walletId: walletId, + iconAssetName: Assets.svg.iconFor(coin: coin), + date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), + shouldWatchForUpdates: false, + coinName: coin.name, + )); + await txTracker.addNotifiedConfirmed(tx.txid); + } else if (tx.type == isar_models.TransactionType.outgoing) { + unawaited(NotificationApi.showNotification( + title: "Outgoing transaction confirmed", + body: walletName, + walletId: walletId, + iconAssetName: Assets.svg.iconFor(coin: coin), + date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), + shouldWatchForUpdates: false, + coinName: coin.name, + )); + await txTracker.addNotifiedConfirmed(tx.txid); + } + } + } + + @override + Future recoverFromMnemonic({ + required String mnemonic, + String? mnemonicPassphrase, + required int maxUnusedAddressGap, + required int maxNumberOfIndexesToCheck, + required int height, + }) async { + longMutex = true; + final start = DateTime.now(); + try { + Logging.instance.log("IS_INTEGRATION_TEST: $integrationTestFlag", + level: LogLevel.Info); + if (!integrationTestFlag) { + final features = await electrumXClient.getServerFeatures(); + Logging.instance.log("features: $features", level: LogLevel.Info); + switch (coin) { + case Coin.eCash: + if (features['genesis_hash'] != GENESIS_HASH_MAINNET) { + throw Exception("genesis hash does not match main net!"); + } + break; + // case Coin.bitcoinTestNet: + // if (features['genesis_hash'] != GENESIS_HASH_TESTNET) { + // throw Exception("genesis hash does not match test net!"); + // } + // break; + default: + throw Exception( + "Attempted to generate a ECashWallet using a non eCash coin type: ${coin.name}"); + } + } + // check to make sure we aren't overwriting a mnemonic + // this should never fail + if ((await mnemonicString) != null || + (await this.mnemonicPassphrase) != null) { + longMutex = false; + throw Exception("Attempted to overwrite mnemonic on restore!"); + } + await _secureStore.write( + key: '${_walletId}_mnemonic', value: mnemonic.trim()); + await _secureStore.write( + key: '${_walletId}_mnemonicPassphrase', + value: mnemonicPassphrase ?? "", + ); + + await _recoverWalletFromBIP32SeedPhrase( + mnemonic: mnemonic.trim(), + mnemonicPassphrase: mnemonicPassphrase ?? "", + maxUnusedAddressGap: maxUnusedAddressGap, + maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, + ); + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from recoverFromMnemonic(): $e\n$s", + level: LogLevel.Error); + longMutex = false; + rethrow; + } + longMutex = false; + + final end = DateTime.now(); + Logging.instance.log( + "$walletName recovery time: ${end.difference(start).inMilliseconds} millis", + level: LogLevel.Info); + } +} diff --git a/lib/utilities/address_utils.dart b/lib/utilities/address_utils.dart index 0006110c9..44850bc65 100644 --- a/lib/utilities/address_utils.dart +++ b/lib/utilities/address_utils.dart @@ -5,6 +5,7 @@ import 'package:crypto/crypto.dart'; import 'package:flutter_libepiccash/epic_cash.dart'; import 'package:stackwallet/services/coins/bitcoincash/bitcoincash_wallet.dart'; import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart'; +import 'package:stackwallet/services/coins/ecash/ecash_wallet.dart'; import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; import 'package:stackwallet/services/coins/litecoin/litecoin_wallet.dart'; import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart'; @@ -61,6 +62,8 @@ class AddressUtils { return true; //TODO - validate ETH address case Coin.firo: return Address.validateAddress(address, firoNetwork); + case Coin.eCash: + return Address.validateAddress(address, eCashNetwork); case Coin.monero: return RegExp("[a-zA-Z0-9]{95}").hasMatch(address) || RegExp("[a-zA-Z0-9]{106}").hasMatch(address); diff --git a/lib/utilities/assets.dart b/lib/utilities/assets.dart index c1401b8cd..44a3520fc 100644 --- a/lib/utilities/assets.dart +++ b/lib/utilities/assets.dart @@ -319,6 +319,8 @@ class _SVG { return bitcoincash; case Coin.dogecoin: return dogecoin; + case Coin.eCash: + return dogecoin; case Coin.epicCash: return epicCash; case Coin.ethereum: @@ -373,6 +375,8 @@ class _SVG { return bitcoincashImage(context); case Coin.dogecoin: return dogecoinImage(context); + case Coin.eCash: + return dogecoinImage(context); case Coin.epicCash: return epicCashImage(context); case Coin.firo: diff --git a/lib/utilities/block_explorers.dart b/lib/utilities/block_explorers.dart index deffb5311..00b570ad5 100644 --- a/lib/utilities/block_explorers.dart +++ b/lib/utilities/block_explorers.dart @@ -15,6 +15,8 @@ Uri getBlockExplorerTransactionUrlFor({ return Uri.parse("https://chain.so/tx/BTCTEST/$txid"); case Coin.dogecoin: return Uri.parse("https://chain.so/tx/DOGE/$txid"); + case Coin.eCash: + return Uri.parse("https://explorer.bitcoinabc.org/tx/$txid"); case Coin.dogecoinTestNet: return Uri.parse("https://chain.so/tx/DOGETEST/$txid"); case Coin.epicCash: diff --git a/lib/utilities/constants.dart b/lib/utilities/constants.dart index 92e99849d..132a5c156 100644 --- a/lib/utilities/constants.dart +++ b/lib/utilities/constants.dart @@ -57,6 +57,7 @@ abstract class Constants { case Coin.firo: case Coin.bitcoinTestNet: case Coin.dogecoinTestNet: + case Coin.eCash: case Coin.firoTestNet: case Coin.epicCash: case Coin.namecoin: @@ -86,6 +87,7 @@ abstract class Constants { case Coin.bitcoinTestNet: case Coin.dogecoinTestNet: case Coin.firoTestNet: + case Coin.eCash: case Coin.epicCash: case Coin.namecoin: case Coin.particl: @@ -115,6 +117,7 @@ abstract class Constants { case Coin.bitcoinTestNet: case Coin.dogecoinTestNet: case Coin.firoTestNet: + case Coin.eCash: case Coin.epicCash: case Coin.ethereum: case Coin.namecoin: @@ -137,10 +140,9 @@ abstract class Constants { switch (coin) { case Coin.bitcoin: case Coin.bitcoinTestNet: - return 600; - case Coin.bitcoincash: case Coin.bitcoincashTestnet: + case Coin.eCash: return 600; case Coin.dogecoin: diff --git a/lib/utilities/default_nodes.dart b/lib/utilities/default_nodes.dart index 785c5561b..0f8425c88 100644 --- a/lib/utilities/default_nodes.dart +++ b/lib/utilities/default_nodes.dart @@ -1,6 +1,5 @@ import 'package:stackwallet/models/node_model.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; -// import 'package:web3dart/browser.dart'; abstract class DefaultNodes { static const String defaultNodeIdPrefix = "default_"; @@ -13,6 +12,7 @@ abstract class DefaultNodes { dogecoin, firo, monero, + eCash, epicCash, ethereum, bitcoincash, @@ -219,6 +219,18 @@ abstract class DefaultNodes { isDown: false, ); + static NodeModel get eCash => NodeModel( + host: "electrum.bitcoinabc.org", + port: 50002, + name: defaultName, + id: _nodeId(Coin.eCash), + useSSL: true, + enabled: true, + coinName: Coin.eCash.name, + isFailover: true, + isDown: false, + ); + static NodeModel getNodeFor(Coin coin) { switch (coin) { case Coin.bitcoin: @@ -233,6 +245,9 @@ abstract class DefaultNodes { case Coin.dogecoin: return dogecoin; + case Coin.eCash: + return eCash; + case Coin.epicCash: return epicCash; diff --git a/lib/utilities/enums/coin_enum.dart b/lib/utilities/enums/coin_enum.dart index 0490174f0..caf79a61f 100644 --- a/lib/utilities/enums/coin_enum.dart +++ b/lib/utilities/enums/coin_enum.dart @@ -3,6 +3,7 @@ import 'package:stackwallet/services/coins/bitcoincash/bitcoincash_wallet.dart' as bch; import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart' as doge; +import 'package:stackwallet/services/coins/ecash/ecash_wallet.dart' as ecash; import 'package:stackwallet/services/coins/epiccash/epiccash_wallet.dart' as epic; import 'package:stackwallet/services/coins/ethereum/ethereum_wallet.dart' @@ -22,6 +23,7 @@ enum Coin { bitcoin, bitcoincash, dogecoin, + eCash, epicCash, ethereum, firo, @@ -59,6 +61,8 @@ extension CoinExt on Coin { return "Dogecoin"; case Coin.epicCash: return "Epic Cash"; + case Coin.eCash: + return "E-Cash"; case Coin.ethereum: return "Ethereum"; case Coin.firo: @@ -98,6 +102,8 @@ extension CoinExt on Coin { return "EPIC"; case Coin.ethereum: return "ETH"; + case Coin.eCash: + return "XEC"; case Coin.firo: return "FIRO"; case Coin.monero: @@ -136,6 +142,8 @@ extension CoinExt on Coin { return "epic"; case Coin.ethereum: return "ethereum"; + case Coin.eCash: + return "ecash"; case Coin.firo: return "firo"; case Coin.monero: @@ -173,6 +181,7 @@ extension CoinExt on Coin { case Coin.bitcoincashTestnet: case Coin.firoTestNet: case Coin.dogecoinTestNet: + case Coin.eCash: return true; case Coin.epicCash: @@ -195,6 +204,7 @@ extension CoinExt on Coin { case Coin.firo: case Coin.namecoin: case Coin.particl: + case Coin.eCash: case Coin.epicCash: case Coin.monero: case Coin.wownero: @@ -220,6 +230,7 @@ extension CoinExt on Coin { case Coin.ethereum: case Coin.monero: case Coin.wownero: + case Coin.eCash: return false; case Coin.dogecoinTestNet: @@ -244,6 +255,7 @@ extension CoinExt on Coin { case Coin.ethereum: case Coin.monero: case Coin.wownero: + case Coin.eCash: return this; case Coin.dogecoinTestNet: @@ -288,6 +300,9 @@ extension CoinExt on Coin { case Coin.epicCash: return epic.MINIMUM_CONFIRMATIONS; + case Coin.eCash: + return ecash.MINIMUM_CONFIRMATIONS; + case Coin.ethereum: return eth.MINIMUM_CONFIRMATIONS; @@ -339,6 +354,11 @@ Coin coinFromPrettyName(String name) { case "firo": return Coin.firo; + case "E-Cash": + case "ecash": + case "eCash": + return Coin.eCash; + case "Monero": case "monero": return Coin.monero; diff --git a/lib/utilities/enums/derive_path_type_enum.dart b/lib/utilities/enums/derive_path_type_enum.dart index 8aa519727..7d8bef8ae 100644 --- a/lib/utilities/enums/derive_path_type_enum.dart +++ b/lib/utilities/enums/derive_path_type_enum.dart @@ -17,6 +17,7 @@ extension DerivePathTypeExt on DerivePathType { case Coin.dogecoinTestNet: case Coin.firo: case Coin.firoTestNet: + case Coin.eCash: return DerivePathType.bip44; case Coin.bitcoin: diff --git a/lib/utilities/theme/color_theme.dart b/lib/utilities/theme/color_theme.dart index 879c7be71..5635df793 100644 --- a/lib/utilities/theme/color_theme.dart +++ b/lib/utilities/theme/color_theme.dart @@ -309,6 +309,7 @@ class CoinThemeColor { Color get firo => const Color(0xFFFF897A); Color get dogecoin => const Color(0xFFFFE079); Color get epicCash => const Color(0xFFC5C7CB); + Color get eCash => Colors.pink; Color get ethereum => const Color(0xFFA7ADE9); Color get monero => const Color(0xFFFF9E6B); Color get namecoin => const Color(0xFF91B1E1); @@ -331,6 +332,8 @@ class CoinThemeColor { return dogecoin; case Coin.epicCash: return epicCash; + case Coin.eCash: + return eCash; case Coin.ethereum: return ethereum; case Coin.firo: diff --git a/lib/utilities/theme/stack_colors.dart b/lib/utilities/theme/stack_colors.dart index f9a1f39c1..b45f87ba2 100644 --- a/lib/utilities/theme/stack_colors.dart +++ b/lib/utilities/theme/stack_colors.dart @@ -1670,6 +1670,8 @@ class StackColors extends ThemeExtension { return _coin.dogecoin; case Coin.epicCash: return _coin.epicCash; + case Coin.eCash: + return _coin.eCash; case Coin.ethereum: return _coin.ethereum; case Coin.firo: From ba77b40674b47f61769aaa650d55615dc619bb39 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 17 Apr 2023 08:27:44 -0600 Subject: [PATCH 02/28] non batched utxo fetch if server doesn't support batching --- lib/services/coins/ecash/ecash_wallet.dart | 59 ++++++++++++++-------- 1 file changed, 38 insertions(+), 21 deletions(-) diff --git a/lib/services/coins/ecash/ecash_wallet.dart b/lib/services/coins/ecash/ecash_wallet.dart index d7c1b08db..9ee72eccf 100644 --- a/lib/services/coins/ecash/ecash_wallet.dart +++ b/lib/services/coins/ecash/ecash_wallet.dart @@ -680,29 +680,46 @@ class ECashWallet extends CoinServiceAPI try { final fetchedUtxoList = >>[]; - final Map>> batches = {}; - const batchSizeMax = 100; - int batchNumber = 0; - for (int i = 0; i < allAddresses.length; i++) { - if (batches[batchNumber] == null) { - batches[batchNumber] = {}; + if (serverCanBatch) { + final Map>> batches = {}; + const batchSizeMax = 100; + int batchNumber = 0; + for (int i = 0; i < allAddresses.length; i++) { + if (batches[batchNumber] == null) { + batches[batchNumber] = {}; + } + final scriptHash = AddressUtils.convertToScriptHash( + allAddresses[i].value, + _network, + ); + batches[batchNumber]!.addAll({ + scriptHash: [scriptHash] + }); + if (i % batchSizeMax == batchSizeMax - 1) { + batchNumber++; + } } - final scripthash = - AddressUtils.convertToScriptHash(allAddresses[i].value, _network); - batches[batchNumber]!.addAll({ - scripthash: [scripthash] - }); - if (i % batchSizeMax == batchSizeMax - 1) { - batchNumber++; - } - } - for (int i = 0; i < batches.length; i++) { - final response = - await _electrumXClient.getBatchUTXOs(args: batches[i]!); - for (final entry in response.entries) { - if (entry.value.isNotEmpty) { - fetchedUtxoList.add(entry.value); + for (int i = 0; i < batches.length; i++) { + final response = await _electrumXClient.getBatchUTXOs( + args: batches[i]!, + ); + for (final entry in response.entries) { + if (entry.value.isNotEmpty) { + fetchedUtxoList.add(entry.value); + } + } + } + } else { + for (int i = 0; i < allAddresses.length; i++) { + final scriptHash = AddressUtils.convertToScriptHash( + allAddresses[i].value, + _network, + ); + + final utxos = await electrumXClient.getUTXOs(scripthash: scriptHash); + if (utxos.isNotEmpty) { + fetchedUtxoList.add(utxos); } } } From 9c5c0cb1791d7f10a1182fa076798da17aaeec8c Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 17 Apr 2023 09:18:50 -0600 Subject: [PATCH 03/28] non batched tx count if server doesn't support batching --- lib/services/coins/ecash/ecash_wallet.dart | 164 +++++++++++++++++++-- 1 file changed, 149 insertions(+), 15 deletions(-) diff --git a/lib/services/coins/ecash/ecash_wallet.dart b/lib/services/coins/ecash/ecash_wallet.dart index 9ee72eccf..47146731e 100644 --- a/lib/services/coins/ecash/ecash_wallet.dart +++ b/lib/services/coins/ecash/ecash_wallet.dart @@ -809,6 +809,23 @@ class ECashWallet extends CoinServiceAPI } } + Future _getTxCount({required isar_models.Address address}) async { + try { + final response = await electrumXClient.getHistory( + scripthash: AddressUtils.convertToScriptHash( + address.value, + _network, + ), + ); + return response.length; + } catch (e, s) { + Logging.instance.log( + "Exception rethrown in _getTxCount(address: $address: $e\n$s", + level: LogLevel.Error); + rethrow; + } + } + Future> _getBatchTxCount({ required Map addresses, }) async { @@ -1711,13 +1728,33 @@ class ECashWallet extends CoinServiceAPI // int p2shChangeIndex = -1; const txCountBatchSize = 12; + const receiveChain = 0; + const changeChain = 1; try { // receiving addresses Logging.instance .log("checking receiving addresses...", level: LogLevel.Info); - final resultReceiveBip44 = _checkGaps(maxNumberOfIndexesToCheck, - maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip44, 0); + final Future, int>> resultReceiveBip44; + + if (serverCanBatch) { + resultReceiveBip44 = _checkGapsBatched( + maxNumberOfIndexesToCheck, + maxUnusedAddressGap, + txCountBatchSize, + root, + DerivePathType.bip44, + receiveChain, + ); + } else { + resultReceiveBip44 = _checkGaps( + maxNumberOfIndexesToCheck, + maxUnusedAddressGap, + root, + DerivePathType.bip44, + receiveChain, + ); + } // final resultReceive49 = _checkGaps(maxNumberOfIndexesToCheck, // maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip49, 0); @@ -1725,8 +1762,26 @@ class ECashWallet extends CoinServiceAPI Logging.instance .log("checking change addresses...", level: LogLevel.Info); // change addresses - final bip44ResultChange = _checkGaps(maxNumberOfIndexesToCheck, - maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip44, 1); + final Future, int>> bip44ResultChange; + + if (serverCanBatch) { + bip44ResultChange = _checkGapsBatched( + maxNumberOfIndexesToCheck, + maxUnusedAddressGap, + txCountBatchSize, + root, + DerivePathType.bip44, + changeChain, + ); + } else { + bip44ResultChange = _checkGaps( + maxNumberOfIndexesToCheck, + maxUnusedAddressGap, + root, + DerivePathType.bip44, + changeChain, + ); + } // final resultChange49 = _checkGaps(maxNumberOfIndexesToCheck, // maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip49, 1); @@ -1738,17 +1793,15 @@ class ECashWallet extends CoinServiceAPI // resultChange49, ]); - bip44P2pkhReceiveAddressArray = (await resultReceiveBip44)['addressArray'] - as List; - bip44P2pkhReceiveIndex = (await resultReceiveBip44)['index'] as int; + bip44P2pkhReceiveAddressArray = (await resultReceiveBip44).item1; + bip44P2pkhReceiveIndex = (await resultReceiveBip44).item2; // p2shReceiveAddressArray = // (await resultReceive49)['addressArray'] as List; // p2shReceiveIndex = (await resultReceive49)['index'] as int; - bip44P2pkhChangeAddressArray = (await bip44ResultChange)['addressArray'] - as List; - bip44P2pkhChangeIndex = (await bip44ResultChange)['index'] as int; + bip44P2pkhChangeAddressArray = (await bip44ResultChange).item1; + bip44P2pkhChangeIndex = (await bip44ResultChange).item2; // p2shChangeAddressArray = // (await resultChange49)['addressArray'] as List; @@ -1811,7 +1864,90 @@ class ECashWallet extends CoinServiceAPI } } - Future> _checkGaps( + Future, int>> _checkGaps( + int maxNumberOfIndexesToCheck, + int maxUnusedAddressGap, + bip32.BIP32 root, + DerivePathType type, + int chain, + ) async { + List addressArray = []; + int returningIndex = -1; + int gapCounter = 0; + for (int index = 0; + index < maxNumberOfIndexesToCheck && gapCounter < maxUnusedAddressGap; + index++) { + Logging.instance.log( + "index: $index, \t GapCounter chain=$chain ${type.name}: $gapCounter", + level: LogLevel.Info); + + final derivePath = constructDerivePath( + derivePathType: type, + networkWIF: root.network.wif, + chain: chain, + index: index, + ); + final node = await Bip32Utils.getBip32NodeFromRoot(root, derivePath); + + String addressString; + final data = PaymentData(pubkey: node.publicKey); + isar_models.AddressType addressType; + switch (type) { + case DerivePathType.bip44: + addressString = P2PKH(data: data, network: _network).data.address!; + addressType = isar_models.AddressType.p2pkh; + break; + case DerivePathType.bip49: + addressString = P2SH( + data: PaymentData( + redeem: P2WPKH(data: data, network: _network).data), + network: _network) + .data + .address!; + addressType = isar_models.AddressType.p2sh; + break; + case DerivePathType.bip84: + addressString = P2WPKH(network: _network, data: data).data.address!; + addressType = isar_models.AddressType.p2wpkh; + break; + default: + throw Exception("DerivePathType $type not supported"); + } + + final address = isar_models.Address( + walletId: walletId, + value: addressString, + publicKey: node.publicKey, + type: addressType, + derivationIndex: index, + derivationPath: isar_models.DerivationPath()..value = derivePath, + subType: chain == 0 + ? isar_models.AddressSubType.receiving + : isar_models.AddressSubType.change, + ); + + // get address tx count + final count = await _getTxCount(address: address); + + // check and add appropriate addresses + if (count > 0) { + // add address to array + addressArray.add(address); + // set current index + returningIndex = index; + // reset counter + gapCounter = 0; + // add info to derivations + } else { + // increase counter when no tx history found + gapCounter++; + } + } + + return Tuple2(addressArray, returningIndex); + } + + Future, int>> _checkGapsBatched( int maxNumberOfIndexesToCheck, int maxUnusedAddressGap, int txCountBatchSize, @@ -1918,10 +2054,8 @@ class ECashWallet extends CoinServiceAPI // cache all the transactions while waiting for the current function to finish. unawaited(getTransactionCacheEarly(iterationsAddressArray)); } - return { - "addressArray": addressArray, - "index": returningIndex, - }; + + return Tuple2(addressArray, returningIndex); } Future getTransactionCacheEarly(List allAddresses) async { From 9df5bc4f3657d83f2b5edb59f61b13b26e8c6b85 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 17 Apr 2023 09:28:02 -0600 Subject: [PATCH 04/28] non batched tx history fetching if server doesn't support batching --- lib/services/coins/ecash/ecash_wallet.dart | 77 ++++++++++++++-------- 1 file changed, 50 insertions(+), 27 deletions(-) diff --git a/lib/services/coins/ecash/ecash_wallet.dart b/lib/services/coins/ecash/ecash_wallet.dart index 47146731e..e14c7f260 100644 --- a/lib/services/coins/ecash/ecash_wallet.dart +++ b/lib/services/coins/ecash/ecash_wallet.dart @@ -936,38 +936,61 @@ class ECashWallet extends CoinServiceAPI } Future>> _fetchHistory( - List allAddresses) async { + List allAddresses, + ) async { try { List> allTxHashes = []; - final Map>> batches = {}; - final Map requestIdToAddressMap = {}; - const batchSizeMax = 100; - int batchNumber = 0; - for (int i = 0; i < allAddresses.length; i++) { - if (batches[batchNumber] == null) { - batches[batchNumber] = {}; + if (serverCanBatch) { + final Map>> batches = {}; + final Map requestIdToAddressMap = {}; + const batchSizeMax = 100; + int batchNumber = 0; + for (int i = 0; i < allAddresses.length; i++) { + if (batches[batchNumber] == null) { + batches[batchNumber] = {}; + } + final scriptHash = AddressUtils.convertToScriptHash( + allAddresses[i], + _network, + ); + final id = Logger.isTestEnv ? "$i" : const Uuid().v1(); + requestIdToAddressMap[id] = allAddresses[i]; + batches[batchNumber]!.addAll({ + id: [scriptHash] + }); + if (i % batchSizeMax == batchSizeMax - 1) { + batchNumber++; + } } - final scripthash = - AddressUtils.convertToScriptHash(allAddresses[i], _network); - final id = Logger.isTestEnv ? "$i" : const Uuid().v1(); - requestIdToAddressMap[id] = allAddresses[i]; - batches[batchNumber]!.addAll({ - id: [scripthash] - }); - if (i % batchSizeMax == batchSizeMax - 1) { - batchNumber++; - } - } - for (int i = 0; i < batches.length; i++) { - final response = - await _electrumXClient.getBatchHistory(args: batches[i]!); - for (final entry in response.entries) { - for (int j = 0; j < entry.value.length; j++) { - entry.value[j]["address"] = requestIdToAddressMap[entry.key]; - if (!allTxHashes.contains(entry.value[j])) { - allTxHashes.add(entry.value[j]); + for (int i = 0; i < batches.length; i++) { + final response = + await _electrumXClient.getBatchHistory(args: batches[i]!); + for (final entry in response.entries) { + for (int j = 0; j < entry.value.length; j++) { + entry.value[j]["address"] = requestIdToAddressMap[entry.key]; + if (!allTxHashes.contains(entry.value[j])) { + allTxHashes.add(entry.value[j]); + } + } + } + } + } else { + for (int i = 0; i < allAddresses.length; i++) { + final scriptHash = AddressUtils.convertToScriptHash( + allAddresses[i], + _network, + ); + + final response = await electrumXClient.getHistory( + scripthash: scriptHash, + ); + + for (int j = 0; j < response.length; j++) { + response[j]["address"] = allAddresses[i]; + if (!allTxHashes.contains(response[j])) { + allTxHashes.add(response[j]); } } } From e01b422573f4071d0a9bf7d5abcda17413bd7d5e Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 17 Apr 2023 09:42:55 -0600 Subject: [PATCH 05/28] check server version --- lib/services/coins/ecash/ecash_wallet.dart | 38 ++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/lib/services/coins/ecash/ecash_wallet.dart b/lib/services/coins/ecash/ecash_wallet.dart index e14c7f260..bf83496fc 100644 --- a/lib/services/coins/ecash/ecash_wallet.dart +++ b/lib/services/coins/ecash/ecash_wallet.dart @@ -474,6 +474,10 @@ class ECashWallet extends CoinServiceAPI .getServerFeatures() .timeout(const Duration(seconds: 3)); Logging.instance.log("features: $features", level: LogLevel.Info); + + _serverVersion = + _parseServerVersion(features["server_version"] as String); + switch (coin) { case Coin.eCash: if (features['genesis_hash'] != GENESIS_HASH_MAINNET) { @@ -674,6 +678,29 @@ class ECashWallet extends CoinServiceAPI return allTransactions; } + double? _serverVersion; + bool get serverCanBatch => _serverVersion != null && _serverVersion! >= 1.6; + + // stupid + fragile + double? _parseServerVersion(String version) { + double? result; + try { + final list = version.split(" "); + if (list.isNotEmpty) { + final numberStrings = list.last.split("."); + final major = numberStrings.removeAt(0); + + result = double.tryParse("$major.${numberStrings.join("")}"); + } + } catch (_) {} + + Logging.instance.log( + "$walletName _parseServerVersion($version) => $result", + level: LogLevel.Info, + ); + return result; + } + Future _updateUTXOs() async { final allAddresses = await _fetchAllOwnAddresses(); @@ -2439,6 +2466,15 @@ class ECashWallet extends CoinServiceAPI Logging.instance.log("initializeExisting() ${coin.prettyName} wallet.", level: LogLevel.Info); + try { + final features = await electrumXClient.getServerFeatures(); + _serverVersion = + _parseServerVersion(features["server_version"] as String); + } catch (_) { + // catch nothing as failure here means we just do not batch certain rpc + // calls + } + if (getCachedId() == null) { throw Exception( "Attempted to initialize an existing wallet using an unknown wallet ID!"); @@ -2776,6 +2812,8 @@ class ECashWallet extends CoinServiceAPI if (!integrationTestFlag) { final features = await electrumXClient.getServerFeatures(); Logging.instance.log("features: $features", level: LogLevel.Info); + _serverVersion = + _parseServerVersion(features["server_version"] as String); switch (coin) { case Coin.eCash: if (features['genesis_hash'] != GENESIS_HASH_MAINNET) { From f87c5e8aa788a598188f81315aa848ca9ab18acb Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 17 Apr 2023 09:47:51 -0600 Subject: [PATCH 06/28] duplicate code clean up --- lib/services/coins/ecash/ecash_wallet.dart | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/lib/services/coins/ecash/ecash_wallet.dart b/lib/services/coins/ecash/ecash_wallet.dart index bf83496fc..7a51ff2c8 100644 --- a/lib/services/coins/ecash/ecash_wallet.dart +++ b/lib/services/coins/ecash/ecash_wallet.dart @@ -838,13 +838,7 @@ class ECashWallet extends CoinServiceAPI Future _getTxCount({required isar_models.Address address}) async { try { - final response = await electrumXClient.getHistory( - scripthash: AddressUtils.convertToScriptHash( - address.value, - _network, - ), - ); - return response.length; + return await getTxCount(address: address.value); } catch (e, s) { Logging.instance.log( "Exception rethrown in _getTxCount(address: $address: $e\n$s", From 148cd6f5b0d11818e587cb307de5d70198850598 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 17 Apr 2023 12:56:35 -0600 Subject: [PATCH 07/28] update decimals for eCash --- lib/utilities/constants.dart | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/utilities/constants.dart b/lib/utilities/constants.dart index 132a5c156..50000f22d 100644 --- a/lib/utilities/constants.dart +++ b/lib/utilities/constants.dart @@ -26,6 +26,7 @@ abstract class Constants { // // true; // true for development, static const int _satsPerCoinEthereum = 1000000000000000000; + static const int _satsPerCoinECash = 100; static const int _satsPerCoinMonero = 1000000000000; static const int _satsPerCoinWownero = 100000000000; static const int _satsPerCoin = 100000000; @@ -33,6 +34,7 @@ abstract class Constants { static const int _decimalPlacesWownero = 11; static const int _decimalPlacesMonero = 12; static const int _decimalPlacesEthereum = 18; + static const int _decimalPlacesECash = 2; static const int notificationsMax = 0xFFFFFFFF; static const Duration networkAliveTimerDuration = Duration(seconds: 10); @@ -57,7 +59,6 @@ abstract class Constants { case Coin.firo: case Coin.bitcoinTestNet: case Coin.dogecoinTestNet: - case Coin.eCash: case Coin.firoTestNet: case Coin.epicCash: case Coin.namecoin: @@ -72,6 +73,9 @@ abstract class Constants { case Coin.ethereum: return _satsPerCoinEthereum; + + case Coin.eCash: + return _satsPerCoinECash; } } @@ -87,7 +91,6 @@ abstract class Constants { case Coin.bitcoinTestNet: case Coin.dogecoinTestNet: case Coin.firoTestNet: - case Coin.eCash: case Coin.epicCash: case Coin.namecoin: case Coin.particl: @@ -101,6 +104,9 @@ abstract class Constants { case Coin.ethereum: return _decimalPlacesEthereum; + + case Coin.eCash: + return _decimalPlacesECash; } } From 8b76ef4f30c6a7045a10ca63ec8da0496735e5a7 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 17 Apr 2023 12:58:34 -0600 Subject: [PATCH 08/28] ecash derivation path --- lib/services/coins/ecash/ecash_wallet.dart | 376 ++++++++++-------- .../enums/derive_path_type_enum.dart | 5 +- 2 files changed, 225 insertions(+), 156 deletions(-) diff --git a/lib/services/coins/ecash/ecash_wallet.dart b/lib/services/coins/ecash/ecash_wallet.dart index 7a51ff2c8..6765ad33a 100644 --- a/lib/services/coins/ecash/ecash_wallet.dart +++ b/lib/services/coins/ecash/ecash_wallet.dart @@ -86,12 +86,26 @@ String constructDerivePath({ }) { String coinType; switch (networkWIF) { - case 0x80: // btc mainnet wif - coinType = "0"; // btc mainnet - break; - case 0xef: // btc testnet wif - coinType = "1"; // btc testnet + case 0x80: // mainnet wif + switch (derivePathType) { + case DerivePathType.bip44: + case DerivePathType.bip49: + coinType = "0"; + break; + case DerivePathType.bch44: + coinType = "144"; + break; + case DerivePathType.eCash44: + coinType = "899"; + break; + default: + throw Exception( + "DerivePathType $derivePathType not supported for coinType"); + } break; + case 0xef: // testnet wif + throw Exception( + "DerivePathType $derivePathType not supported for coinType"); default: throw Exception("Invalid ECash network wif used!"); } @@ -99,14 +113,13 @@ String constructDerivePath({ int purpose; switch (derivePathType) { case DerivePathType.bip44: + case DerivePathType.bch44: + case DerivePathType.eCash44: purpose = 44; break; - // case DerivePathType.bip49: - // purpose = 49; - // break; - // case DerivePathType.bip84: - // purpose = 84; - // break; + case DerivePathType.bip49: + purpose = 49; + break; default: throw Exception("DerivePathType $derivePathType not supported"); } @@ -207,8 +220,9 @@ class ECashWallet extends CoinServiceAPI (await db .getAddresses(walletId) .filter() - .typeEqualTo(isar_models.AddressType.p2wpkh) + .typeEqualTo(isar_models.AddressType.p2pkh) .subTypeEqualTo(isar_models.AddressSubType.receiving) + .derivationPath((q) => q.valueStartsWith("m/44'/899")) .sortByDerivationIndexDesc() .findFirst()) ?? await _generateAddressForChain(0, 0, DerivePathTypeExt.primaryFor(coin)); @@ -220,25 +234,13 @@ class ECashWallet extends CoinServiceAPI (await db .getAddresses(walletId) .filter() - .typeEqualTo(isar_models.AddressType.p2wpkh) + .typeEqualTo(isar_models.AddressType.p2pkh) .subTypeEqualTo(isar_models.AddressSubType.change) + .derivationPath((q) => q.valueStartsWith("m/44'/899")) .sortByDerivationIndexDesc() .findFirst()) ?? await _generateAddressForChain(1, 0, DerivePathTypeExt.primaryFor(coin)); - Future get currentChangeAddressP2PKH async => - (await _currentChangeAddressP2PKH).value; - - Future get _currentChangeAddressP2PKH async => - (await db - .getAddresses(walletId) - .filter() - .typeEqualTo(isar_models.AddressType.p2pkh) - .subTypeEqualTo(isar_models.AddressSubType.change) - .sortByDerivationIndexDesc() - .findFirst()) ?? - await _generateAddressForChain(1, 0, DerivePathType.bip44); - @override Future exit() async { _hasCalledExit = true; @@ -508,19 +510,55 @@ class ECashWallet extends CoinServiceAPI value: bip39.generateMnemonic(strength: 256)); await _secureStore.write(key: '${_walletId}_mnemonicPassphrase', value: ""); + const int startingIndex = 0; + const int receiveChain = 0; + const int changeChain = 1; + // Generate and add addresses to relevant arrays final initialAddresses = await Future.wait([ - // // P2WPKH - // _generateAddressForChain(0, 0, DerivePathType.bip84), - // _generateAddressForChain(1, 0, DerivePathType.bip84), - // - // // P2PKH - _generateAddressForChain(0, 0, DerivePathType.bip44), - _generateAddressForChain(1, 0, DerivePathType.bip44), + // P2PKH + _generateAddressForChain( + receiveChain, + startingIndex, + DerivePathType.eCash44, + ), + _generateAddressForChain( + changeChain, + startingIndex, + DerivePathType.eCash44, + ), + _generateAddressForChain( + receiveChain, + startingIndex, + DerivePathType.bip44, + ), + _generateAddressForChain( + changeChain, + startingIndex, + DerivePathType.bip44, + ), + _generateAddressForChain( + receiveChain, + startingIndex, + DerivePathType.bch44, + ), + _generateAddressForChain( + changeChain, + startingIndex, + DerivePathType.bch44, + ), // // P2SH - // _generateAddressForChain(0, 0, DerivePathType.bip49), - // _generateAddressForChain(1, 0, DerivePathType.bip49), + _generateAddressForChain( + receiveChain, + startingIndex, + DerivePathType.bip49, + ), + _generateAddressForChain( + changeChain, + startingIndex, + DerivePathType.bip49, + ), ]); await db.putAddresses(initialAddresses); @@ -563,6 +601,8 @@ class ECashWallet extends CoinServiceAPI switch (derivePathType) { case DerivePathType.bip44: + case DerivePathType.bch44: + case DerivePathType.eCash44: address = P2PKH(data: data, network: _network).data.address!; addrType = isar_models.AddressType.p2pkh; break; @@ -575,10 +615,6 @@ class ECashWallet extends CoinServiceAPI .address!; addrType = isar_models.AddressType.p2sh; break; - case DerivePathType.bip84: - address = P2WPKH(network: _network, data: data).data.address!; - addrType = isar_models.AddressType.p2wpkh; - break; default: throw Exception("DerivePathType $derivePathType not supported"); } @@ -617,25 +653,38 @@ class ECashWallet extends CoinServiceAPI : isar_models.AddressSubType.change; isar_models.AddressType type; - isar_models.Address? address; + String coinType; + String purpose; switch (derivePathType) { case DerivePathType.bip44: type = isar_models.AddressType.p2pkh; + coinType = "0"; + purpose = "44"; + break; + case DerivePathType.bch44: + type = isar_models.AddressType.p2pkh; + coinType = "145"; + purpose = "44"; + break; + case DerivePathType.eCash44: + type = isar_models.AddressType.p2pkh; + coinType = "899"; + purpose = "44"; break; case DerivePathType.bip49: type = isar_models.AddressType.p2sh; - break; - case DerivePathType.bip84: - type = isar_models.AddressType.p2wpkh; + coinType = "0"; + purpose = "49"; break; default: throw Exception("DerivePathType unsupported"); } - address = await db + final address = await db .getAddresses(walletId) .filter() .typeEqualTo(type) .subTypeEqualTo(subType) + .derivationPath((q) => q.valueStartsWith("m/$purpose'/$coinType")) .sortByDerivationIndexDesc() .findFirst(); return address!.value; @@ -956,6 +1005,30 @@ class ECashWallet extends CoinServiceAPI } } + Future _checkCurrentReceivingAddressesForTransactions() async { + try { + await _checkReceivingAddressForTransactions(); + // await _checkReceivingAddressForTransactionsP2PKH(); + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from _checkCurrentReceivingAddressesForTransactions(): $e\n$s", + level: LogLevel.Error); + rethrow; + } + } + + Future _checkCurrentChangeAddressesForTransactions() async { + try { + await _checkChangeAddressForTransactions(); + // await _checkP2PKHChangeAddressForTransactions(); + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from _checkCurrentChangeAddressesForTransactions(): $e\n$s", + level: LogLevel.Error); + rethrow; + } + } + Future>> _fetchHistory( List allAddresses, ) async { @@ -1761,128 +1834,130 @@ class ECashWallet extends CoinServiceAPI _network, ); - List bip44P2pkhReceiveAddressArray = []; - // List p2shReceiveAddressArray = []; - int bip44P2pkhReceiveIndex = -1; - // int p2shReceiveIndex = -1; + final deriveTypes = [ + DerivePathType.eCash44, + DerivePathType.bch44, + DerivePathType.bip44, + DerivePathType.bip49, + ]; - List bip44P2pkhChangeAddressArray = []; - // List p2shChangeAddressArray = []; - int bip44P2pkhChangeIndex = -1; - // int p2shChangeIndex = -1; + final List, DerivePathType>>> + receiveFutures = []; + final List, DerivePathType>>> + changeFutures = []; const txCountBatchSize = 12; const receiveChain = 0; const changeChain = 1; + const indexZero = 0; try { // receiving addresses - Logging.instance - .log("checking receiving addresses...", level: LogLevel.Info); - final Future, int>> resultReceiveBip44; + Logging.instance.log( + "checking receiving addresses...", + level: LogLevel.Info, + ); if (serverCanBatch) { - resultReceiveBip44 = _checkGapsBatched( - maxNumberOfIndexesToCheck, - maxUnusedAddressGap, - txCountBatchSize, - root, - DerivePathType.bip44, - receiveChain, - ); + for (final type in deriveTypes) { + receiveFutures.add( + _checkGapsBatched( + maxNumberOfIndexesToCheck, + maxUnusedAddressGap, + txCountBatchSize, + root, + type, + receiveChain, + ), + ); + } } else { - resultReceiveBip44 = _checkGaps( - maxNumberOfIndexesToCheck, - maxUnusedAddressGap, - root, - DerivePathType.bip44, - receiveChain, - ); + for (final type in deriveTypes) { + receiveFutures.add( + _checkGaps( + maxNumberOfIndexesToCheck, + maxUnusedAddressGap, + root, + type, + receiveChain, + ), + ); + } } - // final resultReceive49 = _checkGaps(maxNumberOfIndexesToCheck, - // maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip49, 0); - - Logging.instance - .log("checking change addresses...", level: LogLevel.Info); + Logging.instance.log( + "checking change addresses...", + level: LogLevel.Info, + ); // change addresses - final Future, int>> bip44ResultChange; if (serverCanBatch) { - bip44ResultChange = _checkGapsBatched( - maxNumberOfIndexesToCheck, - maxUnusedAddressGap, - txCountBatchSize, - root, - DerivePathType.bip44, - changeChain, - ); + for (final type in deriveTypes) { + changeFutures.add( + _checkGapsBatched( + maxNumberOfIndexesToCheck, + maxUnusedAddressGap, + txCountBatchSize, + root, + type, + changeChain, + ), + ); + } } else { - bip44ResultChange = _checkGaps( - maxNumberOfIndexesToCheck, - maxUnusedAddressGap, - root, - DerivePathType.bip44, - changeChain, - ); + for (final type in deriveTypes) { + changeFutures.add( + _checkGaps( + maxNumberOfIndexesToCheck, + maxUnusedAddressGap, + root, + type, + changeChain, + ), + ); + } } - // final resultChange49 = _checkGaps(maxNumberOfIndexesToCheck, - // maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip49, 1); - - await Future.wait([ - resultReceiveBip44, - // resultReceive49, - bip44ResultChange, - // resultChange49, + // io limitations may require running these linearly instead + final futuresResult = await Future.wait([ + Future.wait(receiveFutures), + Future.wait(changeFutures), ]); - bip44P2pkhReceiveAddressArray = (await resultReceiveBip44).item1; - bip44P2pkhReceiveIndex = (await resultReceiveBip44).item2; + final receiveResults = futuresResult[0]; + final changeResults = futuresResult[1]; - // p2shReceiveAddressArray = - // (await resultReceive49)['addressArray'] as List; - // p2shReceiveIndex = (await resultReceive49)['index'] as int; - - bip44P2pkhChangeAddressArray = (await bip44ResultChange).item1; - bip44P2pkhChangeIndex = (await bip44ResultChange).item2; - - // p2shChangeAddressArray = - // (await resultChange49)['addressArray'] as List; - // p2shChangeIndex = (await resultChange49)['index'] as int; + final List addressesToStore = []; // If restoring a wallet that never received any funds, then set receivingArray manually // If we didn't do this, it'd store an empty array - if (bip44P2pkhReceiveIndex == -1) { - final address = - await _generateAddressForChain(0, 0, DerivePathType.bip44); - bip44P2pkhReceiveAddressArray.add(address); + for (final tuple in receiveResults) { + if (tuple.item1.isEmpty) { + final address = await _generateAddressForChain( + receiveChain, + indexZero, + tuple.item2, + ); + addressesToStore.add(address); + } else { + addressesToStore.addAll(tuple.item1); + } } - // if (p2shReceiveIndex == -1) { - // final address = - // await _generateAddressForChain(0, 0, DerivePathType.bip49); - // p2shReceiveAddressArray.add(address); - // } // If restoring a wallet that never sent any funds with change, then set changeArray // manually. If we didn't do this, it'd store an empty array. - if (bip44P2pkhChangeIndex == -1) { - final address = - await _generateAddressForChain(1, 0, DerivePathType.bip44); - bip44P2pkhChangeAddressArray.add(address); + for (final tuple in changeResults) { + if (tuple.item1.isEmpty) { + final address = await _generateAddressForChain( + changeChain, + indexZero, + tuple.item2, + ); + addressesToStore.add(address); + } else { + addressesToStore.addAll(tuple.item1); + } } - // if (p2shChangeIndex == -1) { - // final address = - // await _generateAddressForChain(1, 0, DerivePathType.bip49); - // p2shChangeAddressArray.add(address); - // } - - final addressesToStore = [ - ...bip44P2pkhReceiveAddressArray, - ...bip44P2pkhChangeAddressArray, - // ...p2shReceiveAddressArray, - // ...p2shChangeAddressArray, - ]; if (isRescan) { await db.updateOrPutAddresses(addressesToStore); @@ -1908,7 +1983,7 @@ class ECashWallet extends CoinServiceAPI } } - Future, int>> _checkGaps( + Future, DerivePathType>> _checkGaps( int maxNumberOfIndexesToCheck, int maxUnusedAddressGap, bip32.BIP32 root, @@ -1916,7 +1991,6 @@ class ECashWallet extends CoinServiceAPI int chain, ) async { List addressArray = []; - int returningIndex = -1; int gapCounter = 0; for (int index = 0; index < maxNumberOfIndexesToCheck && gapCounter < maxUnusedAddressGap; @@ -1938,6 +2012,8 @@ class ECashWallet extends CoinServiceAPI isar_models.AddressType addressType; switch (type) { case DerivePathType.bip44: + case DerivePathType.bch44: + case DerivePathType.eCash44: addressString = P2PKH(data: data, network: _network).data.address!; addressType = isar_models.AddressType.p2pkh; break; @@ -1950,10 +2026,6 @@ class ECashWallet extends CoinServiceAPI .address!; addressType = isar_models.AddressType.p2sh; break; - case DerivePathType.bip84: - addressString = P2WPKH(network: _network, data: data).data.address!; - addressType = isar_models.AddressType.p2wpkh; - break; default: throw Exception("DerivePathType $type not supported"); } @@ -1977,8 +2049,6 @@ class ECashWallet extends CoinServiceAPI if (count > 0) { // add address to array addressArray.add(address); - // set current index - returningIndex = index; // reset counter gapCounter = 0; // add info to derivations @@ -1988,10 +2058,10 @@ class ECashWallet extends CoinServiceAPI } } - return Tuple2(addressArray, returningIndex); + return Tuple2(addressArray, type); } - Future, int>> _checkGapsBatched( + Future, DerivePathType>> _checkGapsBatched( int maxNumberOfIndexesToCheck, int maxUnusedAddressGap, int txCountBatchSize, @@ -2000,7 +2070,6 @@ class ECashWallet extends CoinServiceAPI int chain, ) async { List addressArray = []; - int returningIndex = -1; int gapCounter = 0; for (int index = 0; index < maxNumberOfIndexesToCheck && gapCounter < maxUnusedAddressGap; @@ -2028,6 +2097,8 @@ class ECashWallet extends CoinServiceAPI isar_models.AddressType addrType; switch (type) { case DerivePathType.bip44: + case DerivePathType.bch44: + case DerivePathType.eCash44: addressString = P2PKH(data: data, network: _network).data.address!; addrType = isar_models.AddressType.p2pkh; break; @@ -2040,10 +2111,6 @@ class ECashWallet extends CoinServiceAPI .address!; addrType = isar_models.AddressType.p2sh; break; - case DerivePathType.bip84: - addressString = P2WPKH(network: _network, data: data).data.address!; - addrType = isar_models.AddressType.p2wpkh; - break; default: throw Exception("DerivePathType $type not supported"); } @@ -2084,7 +2151,6 @@ class ECashWallet extends CoinServiceAPI addressArray.add(address); iterationsAddressArray.add(address.value); // set current index - returningIndex = index + k; // reset counter gapCounter = 0; // add info to derivations @@ -2099,7 +2165,7 @@ class ECashWallet extends CoinServiceAPI unawaited(getTransactionCacheEarly(iterationsAddressArray)); } - return Tuple2(addressArray, returningIndex); + return Tuple2(addressArray, type); } Future getTransactionCacheEarly(List allAddresses) async { @@ -2556,10 +2622,10 @@ class ECashWallet extends CoinServiceAPI if (currentHeight != storedHeight) { GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.2, walletId)); - await _checkChangeAddressForTransactions(); + await _checkCurrentChangeAddressesForTransactions(); GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.3, walletId)); - await _checkReceivingAddressForTransactions(); + await _checkCurrentReceivingAddressesForTransactions(); GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.4, walletId)); diff --git a/lib/utilities/enums/derive_path_type_enum.dart b/lib/utilities/enums/derive_path_type_enum.dart index 7d8bef8ae..72899f5bd 100644 --- a/lib/utilities/enums/derive_path_type_enum.dart +++ b/lib/utilities/enums/derive_path_type_enum.dart @@ -6,6 +6,7 @@ enum DerivePathType { bip49, bip84, eth, + eCash44, } extension DerivePathTypeExt on DerivePathType { @@ -17,7 +18,6 @@ extension DerivePathTypeExt on DerivePathType { case Coin.dogecoinTestNet: case Coin.firo: case Coin.firoTestNet: - case Coin.eCash: return DerivePathType.bip44; case Coin.bitcoin: @@ -28,6 +28,9 @@ extension DerivePathTypeExt on DerivePathType { case Coin.particl: return DerivePathType.bip84; + case Coin.eCash: + return DerivePathType.eCash44; + case Coin.ethereum: // TODO: do we need something here? return DerivePathType.eth; From 23a6236b4c3e332a209f913754b08a60ee6ae47f Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 17 May 2023 12:16:38 -0600 Subject: [PATCH 09/28] update branch --- lib/services/coins/ecash/ecash_wallet.dart | 95 ++++++++++++---------- lib/themes/coin_icon_provider.dart | 2 + lib/themes/coin_image_provider.dart | 4 + lib/themes/color_theme.dart | 3 + 4 files changed, 60 insertions(+), 44 deletions(-) diff --git a/lib/services/coins/ecash/ecash_wallet.dart b/lib/services/coins/ecash/ecash_wallet.dart index 6765ad33a..9c6e2dccf 100644 --- a/lib/services/coins/ecash/ecash_wallet.dart +++ b/lib/services/coins/ecash/ecash_wallet.dart @@ -30,11 +30,9 @@ import 'package:stackwallet/services/mixins/wallet_cache.dart'; import 'package:stackwallet/services/mixins/wallet_db.dart'; import 'package:stackwallet/services/mixins/xpubable.dart'; import 'package:stackwallet/services/node_service.dart'; -import 'package:stackwallet/services/notifications_api.dart'; import 'package:stackwallet/services/transaction_notification_tracker.dart'; import 'package:stackwallet/utilities/address_utils.dart'; import 'package:stackwallet/utilities/amount/amount.dart'; -import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/bip32_utils.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/default_nodes.dart'; @@ -44,6 +42,7 @@ import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/prefs.dart'; +import 'package:stackwallet/widgets/crypto_notifications.dart'; import 'package:tuple/tuple.dart'; import 'package:uuid/uuid.dart'; @@ -2798,32 +2797,34 @@ class ECashWallet extends CoinServiceAPI final confirmations = tx.getConfirmations(currentChainHeight); if (tx.type == isar_models.TransactionType.incoming) { - unawaited(NotificationApi.showNotification( - title: "Incoming transaction", - body: walletName, - walletId: walletId, - iconAssetName: Assets.svg.iconFor(coin: coin), - date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), - shouldWatchForUpdates: confirmations < MINIMUM_CONFIRMATIONS, - coinName: coin.name, - txid: tx.txid, - confirmations: confirmations, - requiredConfirmations: MINIMUM_CONFIRMATIONS, - )); + CryptoNotificationsEventBus.instance.fire( + CryptoNotificationEvent( + title: "Incoming transaction", + walletId: walletId, + walletName: walletName, + date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), + shouldWatchForUpdates: confirmations < MINIMUM_CONFIRMATIONS, + coin: coin, + txid: tx.txid, + confirmations: confirmations, + requiredConfirmations: MINIMUM_CONFIRMATIONS, + ), + ); await txTracker.addNotifiedPending(tx.txid); } else if (tx.type == isar_models.TransactionType.outgoing) { - unawaited(NotificationApi.showNotification( - title: "Sending transaction", - body: walletName, - walletId: walletId, - iconAssetName: Assets.svg.iconFor(coin: coin), - date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), - shouldWatchForUpdates: confirmations < MINIMUM_CONFIRMATIONS, - coinName: coin.name, - txid: tx.txid, - confirmations: confirmations, - requiredConfirmations: MINIMUM_CONFIRMATIONS, - )); + CryptoNotificationsEventBus.instance.fire( + CryptoNotificationEvent( + title: "Sending transaction", + walletId: walletId, + date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), + shouldWatchForUpdates: confirmations < MINIMUM_CONFIRMATIONS, + txid: tx.txid, + confirmations: confirmations, + requiredConfirmations: MINIMUM_CONFIRMATIONS, + walletName: walletName, + coin: coin, + ), + ); await txTracker.addNotifiedPending(tx.txid); } } @@ -2831,26 +2832,32 @@ class ECashWallet extends CoinServiceAPI // notify on confirmed for (final tx in unconfirmedTxnsToNotifyConfirmed) { if (tx.type == isar_models.TransactionType.incoming) { - unawaited(NotificationApi.showNotification( - title: "Incoming transaction confirmed", - body: walletName, - walletId: walletId, - iconAssetName: Assets.svg.iconFor(coin: coin), - date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), - shouldWatchForUpdates: false, - coinName: coin.name, - )); + CryptoNotificationsEventBus.instance.fire( + CryptoNotificationEvent( + title: "Incoming transaction confirmed", + walletId: walletId, + date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), + shouldWatchForUpdates: false, + txid: tx.txid, + requiredConfirmations: MINIMUM_CONFIRMATIONS, + walletName: walletName, + coin: coin, + ), + ); await txTracker.addNotifiedConfirmed(tx.txid); } else if (tx.type == isar_models.TransactionType.outgoing) { - unawaited(NotificationApi.showNotification( - title: "Outgoing transaction confirmed", - body: walletName, - walletId: walletId, - iconAssetName: Assets.svg.iconFor(coin: coin), - date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), - shouldWatchForUpdates: false, - coinName: coin.name, - )); + CryptoNotificationsEventBus.instance.fire( + CryptoNotificationEvent( + title: "Outgoing transaction confirmed", + walletId: walletId, + date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), + shouldWatchForUpdates: false, + txid: tx.txid, + requiredConfirmations: MINIMUM_CONFIRMATIONS, + walletName: walletName, + coin: coin, + ), + ); await txTracker.addNotifiedConfirmed(tx.txid); } } diff --git a/lib/themes/coin_icon_provider.dart b/lib/themes/coin_icon_provider.dart index 75b8a3e11..41e344e6b 100644 --- a/lib/themes/coin_icon_provider.dart +++ b/lib/themes/coin_icon_provider.dart @@ -17,6 +17,8 @@ final coinIconProvider = Provider.family((ref, coin) { case Coin.dogecoin: case Coin.dogecoinTestNet: return assets.dogecoin; + case Coin.eCash: + return assets.epicCash; case Coin.epicCash: return assets.epicCash; case Coin.firo: diff --git a/lib/themes/coin_image_provider.dart b/lib/themes/coin_image_provider.dart index 60ef466a7..c8331356b 100644 --- a/lib/themes/coin_image_provider.dart +++ b/lib/themes/coin_image_provider.dart @@ -14,6 +14,8 @@ final coinImageProvider = Provider.family((ref, coin) { return assets.bitcoincashImage; case Coin.dogecoin: return assets.dogecoinImage; + case Coin.eCash: + return assets.epicCashImage; case Coin.epicCash: return assets.epicCashImage; case Coin.firo: @@ -51,6 +53,8 @@ final coinImageSecondaryProvider = Provider.family((ref, coin) { return assets.bitcoincashImageSecondary; case Coin.dogecoin: return assets.dogecoinImageSecondary; + case Coin.eCash: + return assets.epicCashImageSecondary; case Coin.epicCash: return assets.epicCashImageSecondary; case Coin.firo: diff --git a/lib/themes/color_theme.dart b/lib/themes/color_theme.dart index fe261456c..5f631ad75 100644 --- a/lib/themes/color_theme.dart +++ b/lib/themes/color_theme.dart @@ -12,6 +12,7 @@ class CoinThemeColorDefault { Color get firo => const Color(0xFFFF897A); Color get dogecoin => const Color(0xFFFFE079); Color get epicCash => const Color(0xFFC5C7CB); + Color get eCash => const Color(0xFFC5C7CB); Color get ethereum => const Color(0xFFA7ADE9); Color get monero => const Color(0xFFFF9E6B); Color get namecoin => const Color(0xFF91B1E1); @@ -32,6 +33,8 @@ class CoinThemeColorDefault { case Coin.dogecoin: case Coin.dogecoinTestNet: return dogecoin; + case Coin.eCash: + return epicCash; case Coin.epicCash: return epicCash; case Coin.ethereum: From dfc283c7577fddf900aba6913e9909b54fd515a0 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 17 May 2023 13:14:16 -0600 Subject: [PATCH 10/28] ecashaddrs working? --- lib/services/coins/ecash/ecash_wallet.dart | 575 ++++++++++++++------- pubspec.lock | 6 +- pubspec.yaml | 4 +- 3 files changed, 395 insertions(+), 190 deletions(-) diff --git a/lib/services/coins/ecash/ecash_wallet.dart b/lib/services/coins/ecash/ecash_wallet.dart index 9c6e2dccf..9cfd1c1ed 100644 --- a/lib/services/coins/ecash/ecash_wallet.dart +++ b/lib/services/coins/ecash/ecash_wallet.dart @@ -5,6 +5,7 @@ import 'dart:math'; import 'package:bech32/bech32.dart'; import 'package:bip32/bip32.dart' as bip32; import 'package:bip39/bip39.dart' as bip39; +import 'package:bitbox/bitbox.dart' as bitbox; import 'package:bitcoindart/bitcoindart.dart'; import 'package:bs58check/bs58check.dart' as bs58check; import 'package:decimal/decimal.dart'; @@ -55,7 +56,7 @@ const String GENESIS_HASH_TESTNET = final Amount DUST_LIMIT = Amount( rawValue: BigInt.from(546), - fractionDigits: Coin.particl.decimals, + fractionDigits: Coin.eCash.decimals, ); final eCashNetwork = NetworkType( @@ -88,12 +89,8 @@ String constructDerivePath({ case 0x80: // mainnet wif switch (derivePathType) { case DerivePathType.bip44: - case DerivePathType.bip49: coinType = "0"; break; - case DerivePathType.bch44: - coinType = "144"; - break; case DerivePathType.eCash44: coinType = "899"; break; @@ -112,13 +109,9 @@ String constructDerivePath({ int purpose; switch (derivePathType) { case DerivePathType.bip44: - case DerivePathType.bch44: case DerivePathType.eCash44: purpose = 44; break; - case DerivePathType.bip49: - purpose = 49; - break; default: throw Exception("DerivePathType $derivePathType not supported"); } @@ -304,6 +297,18 @@ class ECashWallet extends CoinServiceAPI DerivePathType addressType({required String address}) { Uint8List? decodeBase58; Segwit? decodeBech32; + try { + if (bitbox.Address.detectFormat(address) == + bitbox.Address.formatCashAddr) { + if (validateCashAddr(address)) { + address = bitbox.Address.toLegacyAddress(address); + } else { + throw ArgumentError('$address is not currently supported'); + } + } + } catch (_) { + // invalid cash addr format + } try { decodeBase58 = bs58check.decode(address); } catch (err) { @@ -338,9 +343,36 @@ class ECashWallet extends CoinServiceAPI bool longMutex = false; + bool validateCashAddr(String cashAddr) { + String addr = cashAddr; + if (cashAddr.contains(":")) { + addr = cashAddr.split(":").last; + } + + return addr.startsWith("q"); + } + @override bool validateAddress(String address) { - return Address.validateAddress(address, _network); + try { + // 0 for bitcoincash: address scheme, 1 for legacy address + final format = bitbox.Address.detectFormat(address); + if (kDebugMode) { + print("format $format"); + } + + if (_coin == Coin.bitcoincashTestnet) { + return true; + } + + if (format == bitbox.Address.formatCashAddr) { + return validateCashAddr(address); + } else { + return address.startsWith("1"); + } + } catch (e) { + return false; + } } @override @@ -536,28 +568,6 @@ class ECashWallet extends CoinServiceAPI startingIndex, DerivePathType.bip44, ), - _generateAddressForChain( - receiveChain, - startingIndex, - DerivePathType.bch44, - ), - _generateAddressForChain( - changeChain, - startingIndex, - DerivePathType.bch44, - ), - - // // P2SH - _generateAddressForChain( - receiveChain, - startingIndex, - DerivePathType.bip49, - ), - _generateAddressForChain( - changeChain, - startingIndex, - DerivePathType.bip49, - ), ]); await db.putAddresses(initialAddresses); @@ -600,19 +610,10 @@ class ECashWallet extends CoinServiceAPI switch (derivePathType) { case DerivePathType.bip44: - case DerivePathType.bch44: case DerivePathType.eCash44: address = P2PKH(data: data, network: _network).data.address!; addrType = isar_models.AddressType.p2pkh; - break; - case DerivePathType.bip49: - address = P2SH( - data: PaymentData( - redeem: P2WPKH(data: data, network: _network).data), - network: _network) - .data - .address!; - addrType = isar_models.AddressType.p2sh; + address = bitbox.Address.toECashAddress(address); break; default: throw Exception("DerivePathType $derivePathType not supported"); @@ -660,21 +661,11 @@ class ECashWallet extends CoinServiceAPI coinType = "0"; purpose = "44"; break; - case DerivePathType.bch44: - type = isar_models.AddressType.p2pkh; - coinType = "145"; - purpose = "44"; - break; case DerivePathType.eCash44: type = isar_models.AddressType.p2pkh; coinType = "899"; purpose = "44"; break; - case DerivePathType.bip49: - type = isar_models.AddressType.p2sh; - coinType = "0"; - purpose = "49"; - break; default: throw Exception("DerivePathType unsupported"); } @@ -749,6 +740,23 @@ class ECashWallet extends CoinServiceAPI return result; } + /// attempts to convert a string to a valid scripthash + /// + /// Returns the scripthash or throws an exception on invalid bch address + String _convertToScriptHash(String address, NetworkType network) { + try { + if (bitbox.Address.detectFormat(address) == + bitbox.Address.formatCashAddr && + validateCashAddr(address)) { + final addressLegacy = bitbox.Address.toLegacyAddress(address); + return AddressUtils.convertToScriptHash(addressLegacy, network); + } + return AddressUtils.convertToScriptHash(address, network); + } catch (e) { + rethrow; + } + } + Future _updateUTXOs() async { final allAddresses = await _fetchAllOwnAddresses(); @@ -763,7 +771,7 @@ class ECashWallet extends CoinServiceAPI if (batches[batchNumber] == null) { batches[batchNumber] = {}; } - final scriptHash = AddressUtils.convertToScriptHash( + final scriptHash = _convertToScriptHash( allAddresses[i].value, _network, ); @@ -787,7 +795,7 @@ class ECashWallet extends CoinServiceAPI } } else { for (int i = 0; i < allAddresses.length; i++) { - final scriptHash = AddressUtils.convertToScriptHash( + final scriptHash = _convertToScriptHash( allAddresses[i].value, _network, ); @@ -872,7 +880,7 @@ class ECashWallet extends CoinServiceAPI Future getTxCount({required String address}) async { String? scripthash; try { - scripthash = AddressUtils.convertToScriptHash(address, _network); + scripthash = _convertToScriptHash(address, _network); final transactions = await electrumXClient.getHistory(scripthash: scripthash); return transactions.length; @@ -901,9 +909,7 @@ class ECashWallet extends CoinServiceAPI try { final Map> args = {}; for (final entry in addresses.entries) { - args[entry.key] = [ - AddressUtils.convertToScriptHash(entry.value, _network) - ]; + args[entry.key] = [_convertToScriptHash(entry.value, _network)]; } final response = await electrumXClient.getBatchHistory(args: args); @@ -1043,7 +1049,7 @@ class ECashWallet extends CoinServiceAPI if (batches[batchNumber] == null) { batches[batchNumber] = {}; } - final scriptHash = AddressUtils.convertToScriptHash( + final scriptHash = _convertToScriptHash( allAddresses[i], _network, ); @@ -1071,7 +1077,7 @@ class ECashWallet extends CoinServiceAPI } } else { for (int i = 0; i < allAddresses.length; i++) { - final scriptHash = AddressUtils.convertToScriptHash( + final scriptHash = _convertToScriptHash( allAddresses[i], _network, ); @@ -1107,24 +1113,37 @@ class ECashWallet extends CoinServiceAPI } Future _refreshTransactions() async { - final List allAddresses = - await _fetchAllOwnAddresses(); + List allAddressesOld = await _fetchAllOwnAddresses(); - final Set> allTxHashes = - (await _fetchHistory(allAddresses.map((e) => e.value).toList())) - .toSet(); + Set receivingAddresses = allAddressesOld + .where((e) => e.subType == isar_models.AddressSubType.receiving) + .map((e) { + if (bitbox.Address.detectFormat(e.value) == bitbox.Address.formatLegacy && + (addressType(address: e.value) == DerivePathType.bip44 || + addressType(address: e.value) == DerivePathType.eCash44)) { + return bitbox.Address.toECashAddress(e.value); + } else { + return e.value; + } + }).toSet(); - // // prefetch/cache - // Set hashes = {}; - // for (var element in allReceivingTxHashes) { - // hashes.add(element['tx_hash'] as String); - // } - // await fastFetch(hashes.toList()); + Set changeAddresses = allAddressesOld + .where((e) => e.subType == isar_models.AddressSubType.change) + .map((e) { + if (bitbox.Address.detectFormat(e.value) == bitbox.Address.formatLegacy && + (addressType(address: e.value) == DerivePathType.bip44 || + addressType(address: e.value) == DerivePathType.eCash44)) { + return bitbox.Address.toECashAddress(e.value); + } else { + return e.value; + } + }).toSet(); + + final List> allTxHashes = + await _fetchHistory([...receivingAddresses, ...changeAddresses]); List> allTransactions = []; - final currentHeight = await chainHeight; - for (final txHash in allTxHashes) { final storedTx = await db .getTransactions(walletId) @@ -1133,7 +1152,9 @@ class ECashWallet extends CoinServiceAPI .findFirst(); if (storedTx == null || - !storedTx.isConfirmed(currentHeight, MINIMUM_CONFIRMATIONS)) { + storedTx.address.value == null || + storedTx.height == null || + (storedTx.height != null && storedTx.height! <= 0)) { final tx = await cachedElectrumXClient.getTransaction( txHash: txHash["tx_hash"] as String, verbose: true, @@ -1152,37 +1173,210 @@ class ECashWallet extends CoinServiceAPI } } - // // prefetch/cache - // Set vHashes = {}; - // for (final txObject in allTransactions) { - // for (int i = 0; i < (txObject["vin"] as List).length; i++) { - // final input = txObject["vin"]![i] as Map; - // final prevTxid = input["txid"] as String; - // vHashes.add(prevTxid); - // } - // } - // await fastFetch(vHashes.toList()); + final List> txns = []; - final List> txnsData = - []; + for (final txData in allTransactions) { + Set inputAddresses = {}; + Set outputAddresses = {}; - for (final txObject in allTransactions) { - final data = await parseTransaction( - txObject, - cachedElectrumXClient, - allAddresses, - coin, - MINIMUM_CONFIRMATIONS, - walletId, + Logging.instance.log(txData, level: LogLevel.Fatal); + + Amount totalInputValue = Amount( + rawValue: BigInt.from(0), + fractionDigits: coin.decimals, + ); + Amount totalOutputValue = Amount( + rawValue: BigInt.from(0), + fractionDigits: coin.decimals, ); - txnsData.add(data); + Amount amountSentFromWallet = Amount( + rawValue: BigInt.from(0), + fractionDigits: coin.decimals, + ); + Amount amountReceivedInWallet = Amount( + rawValue: BigInt.from(0), + fractionDigits: coin.decimals, + ); + Amount changeAmount = Amount( + rawValue: BigInt.from(0), + fractionDigits: coin.decimals, + ); + + // parse inputs + for (final input in txData["vin"] as List) { + final prevTxid = input["txid"] as String; + final prevOut = input["vout"] as int; + + // fetch input tx to get address + final inputTx = await cachedElectrumXClient.getTransaction( + txHash: prevTxid, + coin: coin, + ); + + for (final output in inputTx["vout"] as List) { + // check matching output + if (prevOut == output["n"]) { + // get value + final value = Amount.fromDecimal( + Decimal.parse(output["value"].toString()), + fractionDigits: coin.decimals, + ); + + // add value to total + totalInputValue = totalInputValue + value; + + // get input(prevOut) address + final address = + output["scriptPubKey"]?["addresses"]?[0] as String? ?? + output["scriptPubKey"]?["address"] as String?; + + if (address != null) { + inputAddresses.add(address); + + // if input was from my wallet, add value to amount sent + if (receivingAddresses.contains(address) || + changeAddresses.contains(address)) { + amountSentFromWallet = amountSentFromWallet + value; + } + } + } + } + } + + // parse outputs + for (final output in txData["vout"] as List) { + // get value + final value = Amount.fromDecimal( + Decimal.parse(output["value"].toString()), + fractionDigits: coin.decimals, + ); + + // add value to total + totalOutputValue += value; + + // get output address + final address = output["scriptPubKey"]?["addresses"]?[0] as String? ?? + output["scriptPubKey"]?["address"] as String?; + if (address != null) { + outputAddresses.add(address); + + // if output was to my wallet, add value to amount received + if (receivingAddresses.contains(address)) { + amountReceivedInWallet += value; + } else if (changeAddresses.contains(address)) { + changeAmount += value; + } + } + } + + final mySentFromAddresses = [ + ...receivingAddresses.intersection(inputAddresses), + ...changeAddresses.intersection(inputAddresses) + ]; + final myReceivedOnAddresses = + receivingAddresses.intersection(outputAddresses); + final myChangeReceivedOnAddresses = + changeAddresses.intersection(outputAddresses); + + final fee = totalInputValue - totalOutputValue; + + // this is the address initially used to fetch the txid + isar_models.Address transactionAddress = + txData["address"] as isar_models.Address; + + isar_models.TransactionType type; + Amount amount; + if (mySentFromAddresses.isNotEmpty && myReceivedOnAddresses.isNotEmpty) { + // tx is sent to self + type = isar_models.TransactionType.sentToSelf; + amount = + amountSentFromWallet - amountReceivedInWallet - fee - changeAmount; + } else if (mySentFromAddresses.isNotEmpty) { + // outgoing tx + type = isar_models.TransactionType.outgoing; + amount = amountSentFromWallet - changeAmount - fee; + final possible = + outputAddresses.difference(myChangeReceivedOnAddresses).first; + + if (transactionAddress.value != possible) { + transactionAddress = isar_models.Address( + walletId: walletId, + value: possible, + publicKey: [], + type: isar_models.AddressType.nonWallet, + derivationIndex: -1, + derivationPath: null, + subType: isar_models.AddressSubType.nonWallet, + ); + } + } else { + // incoming tx + type = isar_models.TransactionType.incoming; + amount = amountReceivedInWallet; + } + + List inputs = []; + List outputs = []; + + for (final json in txData["vin"] as List) { + bool isCoinBase = json['coinbase'] != null; + final input = isar_models.Input( + txid: json['txid'] as String, + vout: json['vout'] as int? ?? -1, + scriptSig: json['scriptSig']?['hex'] as String?, + scriptSigAsm: json['scriptSig']?['asm'] as String?, + isCoinbase: isCoinBase ? isCoinBase : json['is_coinbase'] as bool?, + sequence: json['sequence'] as int?, + innerRedeemScriptAsm: json['innerRedeemscriptAsm'] as String?, + ); + inputs.add(input); + } + + for (final json in txData["vout"] as List) { + final output = isar_models.Output( + scriptPubKey: json['scriptPubKey']?['hex'] as String?, + scriptPubKeyAsm: json['scriptPubKey']?['asm'] as String?, + scriptPubKeyType: json['scriptPubKey']?['type'] as String?, + scriptPubKeyAddress: + json["scriptPubKey"]?["addresses"]?[0] as String? ?? + json['scriptPubKey']['type'] as String, + value: Amount.fromDecimal( + Decimal.parse(json["value"].toString()), + fractionDigits: coin.decimals, + ).raw.toInt(), + ); + outputs.add(output); + } + + final tx = isar_models.Transaction( + walletId: walletId, + txid: txData["txid"] as String, + timestamp: txData["blocktime"] as int? ?? + (DateTime.now().millisecondsSinceEpoch ~/ 1000), + type: type, + subType: isar_models.TransactionSubType.none, + amount: amount.raw.toInt(), + amountString: amount.toJsonString(), + fee: fee.raw.toInt(), + height: txData["height"] as int?, + isCancelled: false, + isLelantus: false, + slateId: null, + otherData: null, + nonce: null, + inputs: inputs, + outputs: outputs, + ); + + txns.add(Tuple2(tx, transactionAddress)); } - await db.addNewTransactionData(txnsData, walletId); + + await db.addNewTransactionData(txns, walletId); // quick hack to notify manager to call notifyListeners if // transactions changed - if (txnsData.isNotEmpty) { + if (txns.isNotEmpty) { GlobalEventBus.instance.fire( UpdatedInBackgroundEvent( "Transactions updated/added for: $walletId $walletName ", @@ -1309,6 +1503,7 @@ class ECashWallet extends CoinServiceAPI .log("Attempting to send all $coin", level: LogLevel.Info); final int vSizeForOneOutput = (await buildTransaction( + utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: [recipientAddress], satoshiAmounts: [satoshisBeingUsed - 1], @@ -1329,6 +1524,7 @@ class ECashWallet extends CoinServiceAPI final int amount = satoshiAmountToSend - feeForOneOutput; dynamic txn = await buildTransaction( + utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: recipientsArray, satoshiAmounts: [amount], @@ -1350,6 +1546,7 @@ class ECashWallet extends CoinServiceAPI final int vSizeForOneOutput; try { vSizeForOneOutput = (await buildTransaction( + utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: [recipientAddress], satoshiAmounts: [satoshisBeingUsed - 1], @@ -1362,6 +1559,7 @@ class ECashWallet extends CoinServiceAPI final int vSizeForTwoOutPuts; try { vSizeForTwoOutPuts = (await buildTransaction( + utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: [ recipientAddress, @@ -1432,6 +1630,7 @@ class ECashWallet extends CoinServiceAPI Logging.instance .log('Estimated fee: $feeForTwoOutputs', level: LogLevel.Info); dynamic txn = await buildTransaction( + utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: recipientsArray, satoshiAmounts: recipientsAmtArray, @@ -1459,6 +1658,7 @@ class ECashWallet extends CoinServiceAPI Logging.instance.log('Adjusted Estimated fee: $feeForTwoOutputs', level: LogLevel.Info); txn = await buildTransaction( + utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: recipientsArray, satoshiAmounts: recipientsAmtArray, @@ -1491,6 +1691,7 @@ class ECashWallet extends CoinServiceAPI Logging.instance .log('Estimated fee: $feeForOneOutput', level: LogLevel.Info); dynamic txn = await buildTransaction( + utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: recipientsArray, satoshiAmounts: recipientsAmtArray, @@ -1523,6 +1724,7 @@ class ECashWallet extends CoinServiceAPI Logging.instance .log('Estimated fee: $feeForOneOutput', level: LogLevel.Info); dynamic txn = await buildTransaction( + utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: recipientsArray, satoshiAmounts: recipientsAmtArray, @@ -1555,6 +1757,7 @@ class ECashWallet extends CoinServiceAPI Logging.instance .log('Estimated fee: $feeForOneOutput', level: LogLevel.Info); dynamic txn = await buildTransaction( + utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: recipientsArray, satoshiAmounts: recipientsAmtArray, @@ -1603,20 +1806,28 @@ class ECashWallet extends CoinServiceAPI try { // Populating the addresses to check for (var i = 0; i < utxosToUse.length; i++) { - if (utxosToUse[i].address == null) { - final txid = utxosToUse[i].txid; - final tx = await _cachedElectrumXClient.getTransaction( - txHash: txid, - coin: coin, - ); - for (final output in tx["vout"] as List) { - final n = output["n"]; - if (n != null && n == utxosToUse[i].vout) { - utxosToUse[i] = utxosToUse[i].copyWith( - address: output["scriptPubKey"]?["addresses"]?[0] as String? ?? - output["scriptPubKey"]["address"] as String, - ); + final txid = utxosToUse[i].txid; + final tx = await _cachedElectrumXClient.getTransaction( + txHash: txid, + coin: coin, + ); + + for (final output in tx["vout"] as List) { + final n = output["n"]; + if (n != null && n == utxosToUse[i].vout) { + String address = + output["scriptPubKey"]?["addresses"]?[0] as String? ?? + output["scriptPubKey"]["address"] as String; + if (bitbox.Address.detectFormat(address) != + bitbox.Address.formatCashAddr) { + try { + address = bitbox.Address.toECashAddress(address); + } catch (_) { + rethrow; + } } + + utxosToUse[i] = utxosToUse[i].copyWith(address: address); } } @@ -1650,6 +1861,7 @@ class ECashWallet extends CoinServiceAPI switch (sd.derivePathType) { case DerivePathType.bip44: + case DerivePathType.eCash44: data = P2PKH( data: paymentData, network: _network, @@ -1657,26 +1869,6 @@ class ECashWallet extends CoinServiceAPI redeemScript = null; break; - case DerivePathType.bip49: - final p2wpkh = P2WPKH( - data: paymentData, - network: _network, - ).data; - redeemScript = p2wpkh.output; - data = P2SH( - data: PaymentData(redeem: p2wpkh), - network: _network, - ).data; - break; - - case DerivePathType.bip84: - data = P2WPKH( - data: paymentData, - network: _network, - ).data; - redeemScript = null; - break; - default: throw Exception("DerivePathType unsupported"); } @@ -1701,52 +1893,85 @@ class ECashWallet extends CoinServiceAPI /// Builds and signs a transaction Future> buildTransaction({ + required List utxosToUse, required List utxoSigningData, required List recipients, required List satoshiAmounts, }) async { - Logging.instance - .log("Starting buildTransaction ----------", level: LogLevel.Info); + final builder = bitbox.Bitbox.transactionBuilder( + testnet: coin == Coin.bitcoincashTestnet, + ); - final txb = TransactionBuilder(network: _network); - txb.setVersion(1); + // retrieve address' utxos from the rest api + List _utxos = + []; // await Bitbox.Address.utxo(address) as List; + for (var element in utxosToUse) { + _utxos.add(bitbox.Utxo( + element.txid, + element.vout, + bitbox.BitcoinCash.fromSatoshi(element.value), + element.value, + 0, + MINIMUM_CONFIRMATIONS + 1)); + } + Logger.print("bch utxos: $_utxos"); - // Add transaction inputs - for (var i = 0; i < utxoSigningData.length; i++) { - final txid = utxoSigningData[i].utxo.txid; - txb.addInput( - txid, - utxoSigningData[i].utxo.vout, - null, - utxoSigningData[i].output!, - ); + // placeholder for input signatures + final List> signatures = []; + + // placeholder for total input balance + // int totalBalance = 0; + + // iterate through the list of address _utxos and use them as inputs for the + // withdrawal transaction + for (var utxo in _utxos) { + // add the utxo as an input for the transaction + builder.addInput(utxo.txid, utxo.vout); + final ec = + utxoSigningData.firstWhere((e) => e.utxo.txid == utxo.txid).keyPair!; + + final bitboxEC = bitbox.ECPair.fromWIF(ec.toWIF()); + + // add a signature to the list to be used later + signatures.add({ + "vin": signatures.length, + "key_pair": bitboxEC, + "original_amount": utxo.satoshis + }); + + // totalBalance += utxo.satoshis; } - // Add transaction output - for (var i = 0; i < recipients.length; i++) { - txb.addOutput(recipients[i], satoshiAmounts[i]); + // calculate the fee based on number of inputs and one expected output + // final fee = + // bitbox.BitcoinCash.getByteCount(signatures.length, recipients.length); + + // calculate how much balance will be left over to spend after the fee + // final sendAmount = totalBalance - fee; + + // add the output based on the address provided in the testing data + for (int i = 0; i < recipients.length; i++) { + String recipient = recipients[i]; + int satoshiAmount = satoshiAmounts[i]; + builder.addOutput(recipient, satoshiAmount); } - try { - // Sign the transaction accordingly - for (var i = 0; i < utxoSigningData.length; i++) { - txb.sign( - vin: i, - keyPair: utxoSigningData[i].keyPair!, - witnessValue: utxoSigningData[i].utxo.value, - redeemScript: utxoSigningData[i].redeemScript, - ); - } - } catch (e, s) { - Logging.instance.log("Caught exception while signing transaction: $e\n$s", - level: LogLevel.Error); - rethrow; + // sign all inputs + for (var signature in signatures) { + builder.sign( + signature["vin"] as int, + signature["key_pair"] as bitbox.ECPair, + signature["original_amount"] as int); } - final builtTx = txb.build(); - final vSize = builtTx.virtualSize(); + // build the transaction + final tx = builder.build(); + final txHex = tx.toHex(); + final vSize = tx.virtualSize(); + //todo: check if print needed + Logger.print("ecash raw hex: $txHex"); - return {"hex": builtTx.toHex(), "vSize": vSize}; + return {"hex": txHex, "vSize": vSize}; } @override @@ -1835,9 +2060,7 @@ class ECashWallet extends CoinServiceAPI final deriveTypes = [ DerivePathType.eCash44, - DerivePathType.bch44, DerivePathType.bip44, - DerivePathType.bip49, ]; final List, DerivePathType>>> @@ -2011,19 +2234,10 @@ class ECashWallet extends CoinServiceAPI isar_models.AddressType addressType; switch (type) { case DerivePathType.bip44: - case DerivePathType.bch44: case DerivePathType.eCash44: addressString = P2PKH(data: data, network: _network).data.address!; addressType = isar_models.AddressType.p2pkh; - break; - case DerivePathType.bip49: - addressString = P2SH( - data: PaymentData( - redeem: P2WPKH(data: data, network: _network).data), - network: _network) - .data - .address!; - addressType = isar_models.AddressType.p2sh; + addressString = bitbox.Address.toECashAddress(addressString); break; default: throw Exception("DerivePathType $type not supported"); @@ -2096,19 +2310,10 @@ class ECashWallet extends CoinServiceAPI isar_models.AddressType addrType; switch (type) { case DerivePathType.bip44: - case DerivePathType.bch44: case DerivePathType.eCash44: addressString = P2PKH(data: data, network: _network).data.address!; addrType = isar_models.AddressType.p2pkh; - break; - case DerivePathType.bip49: - addressString = P2SH( - data: PaymentData( - redeem: P2WPKH(data: data, network: _network).data), - network: _network) - .data - .address!; - addrType = isar_models.AddressType.p2sh; + addressString = bitbox.Address.toECashAddress(addressString); break; default: throw Exception("DerivePathType $type not supported"); diff --git a/pubspec.lock b/pubspec.lock index b021401bd..600a92d67 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -120,9 +120,9 @@ packages: dependency: "direct main" description: path: "." - ref: ea65073efbaf395a5557e8cd7bd72f195cd7eb11 - resolved-ref: ea65073efbaf395a5557e8cd7bd72f195cd7eb11 - url: "https://github.com/Quppy/bitbox-flutter.git" + ref: "50bf29957514a5712466ba37590a851212a244bf" + resolved-ref: "50bf29957514a5712466ba37590a851212a244bf" + url: "https://github.com/PiRK/bitbox-flutter.git" source: git version: "1.0.1" bitcoindart: diff --git a/pubspec.yaml b/pubspec.yaml index 9a4ad438b..9cd249b2f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -85,8 +85,8 @@ dependencies: ref: 3bef5acc21340f3cc78df0ad1dce5868a3ed68a5 bitbox: git: - url: https://github.com/Quppy/bitbox-flutter.git - ref: ea65073efbaf395a5557e8cd7bd72f195cd7eb11 + url: https://github.com/PiRK/bitbox-flutter.git + ref: 50bf29957514a5712466ba37590a851212a244bf bip32: ^2.0.0 bech32: git: From a3422696bad13d99b5209e21ede5cab05faff1a7 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 17 May 2023 15:55:17 -0600 Subject: [PATCH 11/28] fix --- lib/services/coins/ecash/ecash_wallet.dart | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/services/coins/ecash/ecash_wallet.dart b/lib/services/coins/ecash/ecash_wallet.dart index 9cfd1c1ed..5bed07bc4 100644 --- a/lib/services/coins/ecash/ecash_wallet.dart +++ b/lib/services/coins/ecash/ecash_wallet.dart @@ -89,7 +89,7 @@ String constructDerivePath({ case 0x80: // mainnet wif switch (derivePathType) { case DerivePathType.bip44: - coinType = "0"; + coinType = "145"; break; case DerivePathType.eCash44: coinType = "899"; @@ -558,16 +558,16 @@ class ECashWallet extends CoinServiceAPI startingIndex, DerivePathType.eCash44, ), - _generateAddressForChain( - receiveChain, - startingIndex, - DerivePathType.bip44, - ), - _generateAddressForChain( - changeChain, - startingIndex, - DerivePathType.bip44, - ), + // _generateAddressForChain( + // receiveChain, + // startingIndex, + // DerivePathType.bip44, + // ), + // _generateAddressForChain( + // changeChain, + // startingIndex, + // DerivePathType.bip44, + // ), ]); await db.putAddresses(initialAddresses); From 4f3a2830c5b335157ae2f593b926c79ca078cc09 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 17 May 2023 15:55:36 -0600 Subject: [PATCH 12/28] debug address details info --- .../transaction_details_view.dart | 52 +++++++++++++++---- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/lib/pages/wallet_view/transaction_views/transaction_details_view.dart b/lib/pages/wallet_view/transaction_views/transaction_details_view.dart index 5b8e04e0e..440356166 100644 --- a/lib/pages/wallet_view/transaction_views/transaction_details_view.dart +++ b/lib/pages/wallet_view/transaction_views/transaction_details_view.dart @@ -1,11 +1,13 @@ import 'dart:async'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; +import 'package:stackwallet/pages/receive_view/addresses/address_details_view.dart'; import 'package:stackwallet/pages/wallet_view/sub_widgets/tx_icon.dart'; import 'package:stackwallet/pages/wallet_view/transaction_views/dialogs/cancelling_transaction_progress_dialog.dart'; import 'package:stackwallet/pages/wallet_view/transaction_views/edit_note_view.dart'; @@ -605,17 +607,45 @@ class _TransactionDetailsViewState crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - _transaction.type == - TransactionType.outgoing - ? "Sent to" - : "Receiving address", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context) - : STextStyles.itemSubtitle( - context), + ConditionalParent( + condition: kDebugMode, + builder: (child) { + return Row( + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, + children: [ + child, + CustomTextButton( + text: "Info", + onTap: () { + Navigator.of(context) + .pushNamed( + AddressDetailsView + .routeName, + arguments: Tuple2( + _transaction.address + .value!.id, + widget.walletId, + ), + ); + }, + ) + ], + ); + }, + child: Text( + _transaction.type == + TransactionType.outgoing + ? "Sent to" + : "Receiving address", + style: isDesktop + ? STextStyles + .desktopTextExtraExtraSmall( + context) + : STextStyles.itemSubtitle( + context), + ), ), const SizedBox( height: 8, From ee21a2dbb54b0a6a8205001c9d2675e36b068fb8 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 17 May 2023 16:57:38 -0600 Subject: [PATCH 13/28] WIP prepare for ecash assets --- lib/models/isar/stack_theme.dart | 9 +++++++++ lib/themes/coin_icon_provider.dart | 2 +- lib/themes/coin_image_provider.dart | 4 ++-- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/models/isar/stack_theme.dart b/lib/models/isar/stack_theme.dart index 1e46c841b..ff5164098 100644 --- a/lib/models/isar/stack_theme.dart +++ b/lib/models/isar/stack_theme.dart @@ -2014,6 +2014,7 @@ class ThemeAssets { late final String bitcoincash; late final String dogecoin; late final String epicCash; + late final String? eCash; late final String ethereum; late final String firo; late final String monero; @@ -2024,6 +2025,7 @@ class ThemeAssets { late final String bitcoincashImage; late final String dogecoinImage; late final String epicCashImage; + late final String? eCashImage; late final String ethereumImage; late final String firoImage; late final String litecoinImage; @@ -2035,6 +2037,7 @@ class ThemeAssets { late final String bitcoincashImageSecondary; late final String dogecoinImageSecondary; late final String epicCashImageSecondary; + late final String? eCashImageSecondary; late final String ethereumImageSecondary; late final String firoImageSecondary; late final String litecoinImageSecondary; @@ -2101,6 +2104,8 @@ class ThemeAssets { "$applicationThemesDirectoryPath/$themeId/assets/${json["dogecoin"] as String}" ..epicCash = "$applicationThemesDirectoryPath/$themeId/assets/${json["epicCash"] as String}" + ..eCash = + "$applicationThemesDirectoryPath/$themeId/assets/${json["eCash"] as String}" ..ethereum = "$applicationThemesDirectoryPath/$themeId/assets/${json["ethereum"] as String}" ..firo = @@ -2121,6 +2126,8 @@ class ThemeAssets { "$applicationThemesDirectoryPath/$themeId/assets/${json["dogecoin_image"] as String}" ..epicCashImage = "$applicationThemesDirectoryPath/$themeId/assets/${json["epicCash_image"] as String}" + ..eCashImage = + "$applicationThemesDirectoryPath/$themeId/assets/${json["eCash_image"] as String}" ..ethereumImage = "$applicationThemesDirectoryPath/$themeId/assets/${json["ethereum_image"] as String}" ..firoImage = @@ -2143,6 +2150,8 @@ class ThemeAssets { "$applicationThemesDirectoryPath/$themeId/assets/${json["dogecoin_image_secondary"] as String}" ..epicCashImageSecondary = "$applicationThemesDirectoryPath/$themeId/assets/${json["epicCash_image_secondary"] as String}" + ..eCashImageSecondary = + "$applicationThemesDirectoryPath/$themeId/assets/${json["eCash_image_secondary"] as String?}" ..ethereumImageSecondary = "$applicationThemesDirectoryPath/$themeId/assets/${json["ethereum_image_secondary"] as String}" ..firoImageSecondary = diff --git a/lib/themes/coin_icon_provider.dart b/lib/themes/coin_icon_provider.dart index 41e344e6b..e895bdb7b 100644 --- a/lib/themes/coin_icon_provider.dart +++ b/lib/themes/coin_icon_provider.dart @@ -18,7 +18,7 @@ final coinIconProvider = Provider.family((ref, coin) { case Coin.dogecoinTestNet: return assets.dogecoin; case Coin.eCash: - return assets.epicCash; + return assets.ecash; case Coin.epicCash: return assets.epicCash; case Coin.firo: diff --git a/lib/themes/coin_image_provider.dart b/lib/themes/coin_image_provider.dart index c8331356b..4e9958e8f 100644 --- a/lib/themes/coin_image_provider.dart +++ b/lib/themes/coin_image_provider.dart @@ -15,7 +15,7 @@ final coinImageProvider = Provider.family((ref, coin) { case Coin.dogecoin: return assets.dogecoinImage; case Coin.eCash: - return assets.epicCashImage; + return assets.eCashImage; case Coin.epicCash: return assets.epicCashImage; case Coin.firo: @@ -54,7 +54,7 @@ final coinImageSecondaryProvider = Provider.family((ref, coin) { case Coin.dogecoin: return assets.dogecoinImageSecondary; case Coin.eCash: - return assets.epicCashImageSecondary; + return assets.eCashImageSecondary; case Coin.epicCash: return assets.epicCashImageSecondary; case Coin.firo: From 37259643ab4297501064c11e2c06aef6c9cebc8b Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 18 May 2023 07:33:44 -0600 Subject: [PATCH 14/28] unwrap optionals --- lib/themes/coin_icon_provider.dart | 2 +- lib/themes/coin_image_provider.dart | 4 ++-- lib/themes/color_theme.dart | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/themes/coin_icon_provider.dart b/lib/themes/coin_icon_provider.dart index e895bdb7b..f4ab93a5b 100644 --- a/lib/themes/coin_icon_provider.dart +++ b/lib/themes/coin_icon_provider.dart @@ -18,7 +18,7 @@ final coinIconProvider = Provider.family((ref, coin) { case Coin.dogecoinTestNet: return assets.dogecoin; case Coin.eCash: - return assets.ecash; + return assets.eCash!; case Coin.epicCash: return assets.epicCash; case Coin.firo: diff --git a/lib/themes/coin_image_provider.dart b/lib/themes/coin_image_provider.dart index 4e9958e8f..9ecb77df0 100644 --- a/lib/themes/coin_image_provider.dart +++ b/lib/themes/coin_image_provider.dart @@ -15,7 +15,7 @@ final coinImageProvider = Provider.family((ref, coin) { case Coin.dogecoin: return assets.dogecoinImage; case Coin.eCash: - return assets.eCashImage; + return assets.eCashImage!; case Coin.epicCash: return assets.epicCashImage; case Coin.firo: @@ -54,7 +54,7 @@ final coinImageSecondaryProvider = Provider.family((ref, coin) { case Coin.dogecoin: return assets.dogecoinImageSecondary; case Coin.eCash: - return assets.eCashImageSecondary; + return assets.eCashImageSecondary!; case Coin.epicCash: return assets.epicCashImageSecondary; case Coin.firo: diff --git a/lib/themes/color_theme.dart b/lib/themes/color_theme.dart index 5f631ad75..b4f00adcf 100644 --- a/lib/themes/color_theme.dart +++ b/lib/themes/color_theme.dart @@ -34,7 +34,7 @@ class CoinThemeColorDefault { case Coin.dogecoinTestNet: return dogecoin; case Coin.eCash: - return epicCash; + return eCash; case Coin.epicCash: return epicCash; case Coin.ethereum: From 98ce9701a558436dc8f346399906305c0ca5833b Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 18 May 2023 07:41:26 -0600 Subject: [PATCH 15/28] ecash from ticker fix --- lib/utilities/enums/coin_enum.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/utilities/enums/coin_enum.dart b/lib/utilities/enums/coin_enum.dart index caf79a61f..f1109a626 100644 --- a/lib/utilities/enums/coin_enum.dart +++ b/lib/utilities/enums/coin_enum.dart @@ -424,6 +424,8 @@ Coin coinFromTickerCaseInsensitive(String ticker) { return Coin.dogecoin; case "epic": return Coin.epicCash; + case "xec": + return Coin.eCash; case "eth": return Coin.ethereum; case "firo": From bc6185801df7d091cac969a6654bc5b25fbb9979 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Thu, 18 May 2023 09:02:37 -0600 Subject: [PATCH 16/28] corrected ecash json path --- lib/models/isar/stack_theme.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/models/isar/stack_theme.dart b/lib/models/isar/stack_theme.dart index ff5164098..18ccd0a2f 100644 --- a/lib/models/isar/stack_theme.dart +++ b/lib/models/isar/stack_theme.dart @@ -2105,7 +2105,7 @@ class ThemeAssets { ..epicCash = "$applicationThemesDirectoryPath/$themeId/assets/${json["epicCash"] as String}" ..eCash = - "$applicationThemesDirectoryPath/$themeId/assets/${json["eCash"] as String}" + "$applicationThemesDirectoryPath/$themeId/assets/${json["ecash"] as String}" ..ethereum = "$applicationThemesDirectoryPath/$themeId/assets/${json["ethereum"] as String}" ..firo = @@ -2127,7 +2127,8 @@ class ThemeAssets { ..epicCashImage = "$applicationThemesDirectoryPath/$themeId/assets/${json["epicCash_image"] as String}" ..eCashImage = - "$applicationThemesDirectoryPath/$themeId/assets/${json["eCash_image"] as String}" + "$applicationThemesDirectoryPath/$themeId/assets/${json["ecash_image"] as String}" + "$applicationThemesDirectoryPath/$themeId/assets/${json["ecash_image"] as String}" ..ethereumImage = "$applicationThemesDirectoryPath/$themeId/assets/${json["ethereum_image"] as String}" ..firoImage = @@ -2151,7 +2152,7 @@ class ThemeAssets { ..epicCashImageSecondary = "$applicationThemesDirectoryPath/$themeId/assets/${json["epicCash_image_secondary"] as String}" ..eCashImageSecondary = - "$applicationThemesDirectoryPath/$themeId/assets/${json["eCash_image_secondary"] as String?}" + "$applicationThemesDirectoryPath/$themeId/assets/${json["ecash_image_secondary"] as String?}" ..ethereumImageSecondary = "$applicationThemesDirectoryPath/$themeId/assets/${json["ethereum_image_secondary"] as String}" ..firoImageSecondary = From c82564c96c8a64696b0833a4a05a7e838c30eb19 Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 18 May 2023 09:35:12 -0600 Subject: [PATCH 17/28] disable ecash assets in prep for dynamic theme coin assets migrate --- lib/models/isar/stack_theme.dart | 10 ---------- lib/themes/coin_icon_provider.dart | 2 +- lib/themes/coin_image_provider.dart | 4 ++-- 3 files changed, 3 insertions(+), 13 deletions(-) diff --git a/lib/models/isar/stack_theme.dart b/lib/models/isar/stack_theme.dart index 18ccd0a2f..1e46c841b 100644 --- a/lib/models/isar/stack_theme.dart +++ b/lib/models/isar/stack_theme.dart @@ -2014,7 +2014,6 @@ class ThemeAssets { late final String bitcoincash; late final String dogecoin; late final String epicCash; - late final String? eCash; late final String ethereum; late final String firo; late final String monero; @@ -2025,7 +2024,6 @@ class ThemeAssets { late final String bitcoincashImage; late final String dogecoinImage; late final String epicCashImage; - late final String? eCashImage; late final String ethereumImage; late final String firoImage; late final String litecoinImage; @@ -2037,7 +2035,6 @@ class ThemeAssets { late final String bitcoincashImageSecondary; late final String dogecoinImageSecondary; late final String epicCashImageSecondary; - late final String? eCashImageSecondary; late final String ethereumImageSecondary; late final String firoImageSecondary; late final String litecoinImageSecondary; @@ -2104,8 +2101,6 @@ class ThemeAssets { "$applicationThemesDirectoryPath/$themeId/assets/${json["dogecoin"] as String}" ..epicCash = "$applicationThemesDirectoryPath/$themeId/assets/${json["epicCash"] as String}" - ..eCash = - "$applicationThemesDirectoryPath/$themeId/assets/${json["ecash"] as String}" ..ethereum = "$applicationThemesDirectoryPath/$themeId/assets/${json["ethereum"] as String}" ..firo = @@ -2126,9 +2121,6 @@ class ThemeAssets { "$applicationThemesDirectoryPath/$themeId/assets/${json["dogecoin_image"] as String}" ..epicCashImage = "$applicationThemesDirectoryPath/$themeId/assets/${json["epicCash_image"] as String}" - ..eCashImage = - "$applicationThemesDirectoryPath/$themeId/assets/${json["ecash_image"] as String}" - "$applicationThemesDirectoryPath/$themeId/assets/${json["ecash_image"] as String}" ..ethereumImage = "$applicationThemesDirectoryPath/$themeId/assets/${json["ethereum_image"] as String}" ..firoImage = @@ -2151,8 +2143,6 @@ class ThemeAssets { "$applicationThemesDirectoryPath/$themeId/assets/${json["dogecoin_image_secondary"] as String}" ..epicCashImageSecondary = "$applicationThemesDirectoryPath/$themeId/assets/${json["epicCash_image_secondary"] as String}" - ..eCashImageSecondary = - "$applicationThemesDirectoryPath/$themeId/assets/${json["ecash_image_secondary"] as String?}" ..ethereumImageSecondary = "$applicationThemesDirectoryPath/$themeId/assets/${json["ethereum_image_secondary"] as String}" ..firoImageSecondary = diff --git a/lib/themes/coin_icon_provider.dart b/lib/themes/coin_icon_provider.dart index f4ab93a5b..9e7ac9f2c 100644 --- a/lib/themes/coin_icon_provider.dart +++ b/lib/themes/coin_icon_provider.dart @@ -18,7 +18,7 @@ final coinIconProvider = Provider.family((ref, coin) { case Coin.dogecoinTestNet: return assets.dogecoin; case Coin.eCash: - return assets.eCash!; + return assets.bitcoin; case Coin.epicCash: return assets.epicCash; case Coin.firo: diff --git a/lib/themes/coin_image_provider.dart b/lib/themes/coin_image_provider.dart index 9ecb77df0..c5eb8f0f6 100644 --- a/lib/themes/coin_image_provider.dart +++ b/lib/themes/coin_image_provider.dart @@ -15,7 +15,7 @@ final coinImageProvider = Provider.family((ref, coin) { case Coin.dogecoin: return assets.dogecoinImage; case Coin.eCash: - return assets.eCashImage!; + return assets.bitcoinImage; case Coin.epicCash: return assets.epicCashImage; case Coin.firo: @@ -54,7 +54,7 @@ final coinImageSecondaryProvider = Provider.family((ref, coin) { case Coin.dogecoin: return assets.dogecoinImageSecondary; case Coin.eCash: - return assets.eCashImageSecondary!; + return assets.bitcoinImageSecondary; case Coin.epicCash: return assets.epicCashImageSecondary; case Coin.firo: From b946bba01b6a90ab1843de97347a542bbf17192f Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 18 May 2023 12:52:48 -0600 Subject: [PATCH 18/28] v2 theme assets --- lib/models/isar/stack_theme.dart | 1302 ++--- lib/models/isar/stack_theme.g.dart | 4801 +++++++++++++++-- lib/notifications/notification_card.dart | 6 +- .../exchange_view/trade_details_view.dart | 14 +- .../wallet_view/sub_widgets/tx_icon.dart | 4 +- .../desktop_all_trades_view.dart | 4 +- lib/themes/coin_icon_provider.dart | 68 +- lib/themes/coin_image_provider.dart | 151 +- lib/themes/theme_providers.dart | 8 + lib/widgets/trade_card.dart | 8 +- test/sample_data/theme_json_v2.dart | 252 + 11 files changed, 5382 insertions(+), 1236 deletions(-) create mode 100644 test/sample_data/theme_json_v2.dart diff --git a/lib/models/isar/stack_theme.dart b/lib/models/isar/stack_theme.dart index 1e46c841b..5809e4443 100644 --- a/lib/models/isar/stack_theme.dart +++ b/lib/models/isar/stack_theme.dart @@ -16,13 +16,13 @@ class StackTheme { /// id of theme on themes server @Index(unique: true, replace: true) - final String themeId; + late final String themeId; /// the theme name that will be displayed in app - final String name; + late final String name; // system brightness - final String brightnessString; + late final String brightnessString; /// convenience enum conversion for stored [brightnessString] @ignore @@ -44,7 +44,7 @@ class StackTheme { Color get background => _background ??= Color(backgroundInt); @ignore Color? _background; - final int backgroundInt; + late final int backgroundInt; // ==== backgroundAppBar ===================================================== @@ -53,7 +53,7 @@ class StackTheme { _backgroundAppBar ??= Color(backgroundAppBarInt); @ignore Color? _backgroundAppBar; - final int backgroundAppBarInt; + late final int backgroundAppBarInt; // ==== gradientBackground ===================================================== @@ -73,7 +73,7 @@ class StackTheme { @ignore Gradient? _gradientBackground; - final String? gradientBackgroundString; + late final String? gradientBackgroundString; // ==== boxShadows ===================================================== @@ -86,7 +86,7 @@ class StackTheme { ); @ignore BoxShadow? _standardBoxShadow; - final String standardBoxShadowString; + late final String standardBoxShadowString; @ignore BoxShadow? get homeViewButtonBarBoxShadow { @@ -105,7 +105,7 @@ class StackTheme { @ignore BoxShadow? _homeViewButtonBarBoxShadow; - final String? homeViewButtonBarBoxShadowString; + late final String? homeViewButtonBarBoxShadowString; // ==== overlay ===================================================== @@ -113,7 +113,7 @@ class StackTheme { Color get overlay => _overlay ??= Color(overlayInt); @ignore Color? _overlay; - final int overlayInt; + late final int overlayInt; // ==== accentColorBlue ===================================================== @@ -123,7 +123,7 @@ class StackTheme { ); @ignore Color? _accentColorBlue; - final int accentColorBlueInt; + late final int accentColorBlueInt; // ==== accentColorGreen ===================================================== @@ -133,7 +133,7 @@ class StackTheme { ); @ignore Color? _accentColorGreen; - final int accentColorGreenInt; + late final int accentColorGreenInt; // ==== accentColorYellow ===================================================== @@ -143,7 +143,7 @@ class StackTheme { ); @ignore Color? _accentColorYellow; - final int accentColorYellowInt; + late final int accentColorYellowInt; // ==== accentColorRed ===================================================== @@ -153,7 +153,7 @@ class StackTheme { ); @ignore Color? _accentColorRed; - final int accentColorRedInt; + late final int accentColorRedInt; // ==== accentColorOrange ===================================================== @@ -163,7 +163,7 @@ class StackTheme { ); @ignore Color? _accentColorOrange; - final int accentColorOrangeInt; + late final int accentColorOrangeInt; // ==== accentColorDark ===================================================== @@ -173,7 +173,7 @@ class StackTheme { ); @ignore Color? _accentColorDark; - final int accentColorDarkInt; + late final int accentColorDarkInt; // ==== shadow ===================================================== @@ -183,7 +183,7 @@ class StackTheme { ); @ignore Color? _shadow; - final int shadowInt; + late final int shadowInt; // ==== textDark ===================================================== @@ -193,7 +193,7 @@ class StackTheme { ); @ignore Color? _textDark; - final int textDarkInt; + late final int textDarkInt; // ==== textDark2 ===================================================== @@ -203,7 +203,7 @@ class StackTheme { ); @ignore Color? _textDark2; - final int textDark2Int; + late final int textDark2Int; // ==== textDark3 ===================================================== @@ -213,7 +213,7 @@ class StackTheme { ); @ignore Color? _textDark3; - final int textDark3Int; + late final int textDark3Int; // ==== textSubtitle1 ===================================================== @@ -223,7 +223,7 @@ class StackTheme { ); @ignore Color? _textSubtitle1; - final int textSubtitle1Int; + late final int textSubtitle1Int; // ==== textSubtitle2 ===================================================== @@ -233,7 +233,7 @@ class StackTheme { ); @ignore Color? _textSubtitle2; - final int textSubtitle2Int; + late final int textSubtitle2Int; // ==== textSubtitle3 ===================================================== @@ -243,7 +243,7 @@ class StackTheme { ); @ignore Color? _textSubtitle3; - final int textSubtitle3Int; + late final int textSubtitle3Int; // ==== textSubtitle4 ===================================================== @@ -253,7 +253,7 @@ class StackTheme { ); @ignore Color? _textSubtitle4; - final int textSubtitle4Int; + late final int textSubtitle4Int; // ==== textSubtitle5 ===================================================== @@ -263,7 +263,7 @@ class StackTheme { ); @ignore Color? _textSubtitle5; - final int textSubtitle5Int; + late final int textSubtitle5Int; // ==== textSubtitle6 ===================================================== @@ -273,7 +273,7 @@ class StackTheme { ); @ignore Color? _textSubtitle6; - final int textSubtitle6Int; + late final int textSubtitle6Int; // ==== textWhite ===================================================== @@ -283,7 +283,7 @@ class StackTheme { ); @ignore Color? _textWhite; - final int textWhiteInt; + late final int textWhiteInt; // ==== textFavoriteCard ===================================================== @@ -293,7 +293,7 @@ class StackTheme { ); @ignore Color? _textFavoriteCard; - final int textFavoriteCardInt; + late final int textFavoriteCardInt; // ==== textError ===================================================== @@ -303,7 +303,7 @@ class StackTheme { ); @ignore Color? _textError; - final int textErrorInt; + late final int textErrorInt; // ==== textRestore ===================================================== @@ -313,7 +313,7 @@ class StackTheme { ); @ignore Color? _textRestore; - final int textRestoreInt; + late final int textRestoreInt; // ==== buttonBackPrimary ===================================================== @@ -323,7 +323,7 @@ class StackTheme { ); @ignore Color? _buttonBackPrimary; - final int buttonBackPrimaryInt; + late final int buttonBackPrimaryInt; // ==== buttonBackSecondary ===================================================== @@ -333,7 +333,7 @@ class StackTheme { ); @ignore Color? _buttonBackSecondary; - final int buttonBackSecondaryInt; + late final int buttonBackSecondaryInt; // ==== buttonBackPrimaryDisabled ===================================================== @@ -343,7 +343,7 @@ class StackTheme { ); @ignore Color? _buttonBackPrimaryDisabled; - final int buttonBackPrimaryDisabledInt; + late final int buttonBackPrimaryDisabledInt; // ==== buttonBackSecondaryDisabled ===================================================== @@ -354,7 +354,7 @@ class StackTheme { ); @ignore Color? _buttonBackSecondaryDisabled; - final int buttonBackSecondaryDisabledInt; + late final int buttonBackSecondaryDisabledInt; // ==== buttonBackBorder ===================================================== @@ -364,7 +364,7 @@ class StackTheme { ); @ignore Color? _buttonBackBorder; - final int buttonBackBorderInt; + late final int buttonBackBorderInt; // ==== buttonBackBorderDisabled ===================================================== @@ -374,7 +374,7 @@ class StackTheme { ); @ignore Color? _buttonBackBorderDisabled; - final int buttonBackBorderDisabledInt; + late final int buttonBackBorderDisabledInt; // ==== buttonBackBorderSecondary ===================================================== @@ -384,7 +384,7 @@ class StackTheme { ); @ignore Color? _buttonBackBorderSecondary; - final int buttonBackBorderSecondaryInt; + late final int buttonBackBorderSecondaryInt; // ==== buttonBackBorderSecondaryDisabled ===================================================== @@ -395,7 +395,7 @@ class StackTheme { ); @ignore Color? _buttonBackBorderSecondaryDisabled; - final int buttonBackBorderSecondaryDisabledInt; + late final int buttonBackBorderSecondaryDisabledInt; // ==== numberBackDefault ===================================================== @@ -405,7 +405,7 @@ class StackTheme { ); @ignore Color? _numberBackDefault; - final int numberBackDefaultInt; + late final int numberBackDefaultInt; // ==== numpadBackDefault ===================================================== @@ -415,7 +415,7 @@ class StackTheme { ); @ignore Color? _numpadBackDefault; - final int numpadBackDefaultInt; + late final int numpadBackDefaultInt; // ==== bottomNavBack ===================================================== @@ -425,7 +425,7 @@ class StackTheme { ); @ignore Color? _bottomNavBack; - final int bottomNavBackInt; + late final int bottomNavBackInt; // ==== buttonTextPrimary ===================================================== @@ -435,7 +435,7 @@ class StackTheme { ); @ignore Color? _buttonTextPrimary; - final int buttonTextPrimaryInt; + late final int buttonTextPrimaryInt; // ==== buttonTextSecondary ===================================================== @@ -445,7 +445,7 @@ class StackTheme { ); @ignore Color? _buttonTextSecondary; - final int buttonTextSecondaryInt; + late final int buttonTextSecondaryInt; // ==== buttonTextPrimaryDisabled ===================================================== @@ -455,7 +455,7 @@ class StackTheme { ); @ignore Color? _buttonTextPrimaryDisabled; - final int buttonTextPrimaryDisabledInt; + late final int buttonTextPrimaryDisabledInt; // ==== buttonTextSecondaryDisabled ===================================================== @@ -464,7 +464,7 @@ class StackTheme { _buttonTextSecondaryDisabled ??= Color(buttonTextSecondaryDisabledInt); @ignore Color? _buttonTextSecondaryDisabled; - final int buttonTextSecondaryDisabledInt; + late final int buttonTextSecondaryDisabledInt; // ==== buttonTextBorder ===================================================== @@ -473,7 +473,7 @@ class StackTheme { _buttonTextBorder ??= Color(buttonTextBorderInt); @ignore Color? _buttonTextBorder; - final int buttonTextBorderInt; + late final int buttonTextBorderInt; // ==== buttonTextDisabled ===================================================== @@ -482,7 +482,7 @@ class StackTheme { _buttonTextDisabled ??= Color(buttonTextDisabledInt); @ignore Color? _buttonTextDisabled; - final int buttonTextDisabledInt; + late final int buttonTextDisabledInt; // ==== buttonTextBorderless ===================================================== @@ -491,7 +491,7 @@ class StackTheme { _buttonTextBorderless ??= Color(buttonTextBorderlessInt); @ignore Color? _buttonTextBorderless; - final int buttonTextBorderlessInt; + late final int buttonTextBorderlessInt; // ==== buttonTextBorderlessDisabled ===================================================== @@ -500,7 +500,7 @@ class StackTheme { _buttonTextBorderlessDisabled ??= Color(buttonTextBorderlessDisabledInt); @ignore Color? _buttonTextBorderlessDisabled; - final int buttonTextBorderlessDisabledInt; + late final int buttonTextBorderlessDisabledInt; // ==== numberTextDefault ===================================================== @@ -509,7 +509,7 @@ class StackTheme { _numberTextDefault ??= Color(numberTextDefaultInt); @ignore Color? _numberTextDefault; - final int numberTextDefaultInt; + late final int numberTextDefaultInt; // ==== numpadTextDefault ===================================================== @@ -518,7 +518,7 @@ class StackTheme { _numpadTextDefault ??= Color(numpadTextDefaultInt); @ignore Color? _numpadTextDefault; - final int numpadTextDefaultInt; + late final int numpadTextDefaultInt; // ==== bottomNavText ===================================================== @@ -526,7 +526,7 @@ class StackTheme { Color get bottomNavText => _bottomNavText ??= Color(bottomNavTextInt); @ignore Color? _bottomNavText; - final int bottomNavTextInt; + late final int bottomNavTextInt; // ==== customTextButtonEnabledText ===================================================== @@ -535,7 +535,7 @@ class StackTheme { _customTextButtonEnabledText ??= Color(customTextButtonEnabledTextInt); @ignore Color? _customTextButtonEnabledText; - final int customTextButtonEnabledTextInt; + late final int customTextButtonEnabledTextInt; // ==== customTextButtonDisabledText ===================================================== @@ -544,7 +544,7 @@ class StackTheme { _customTextButtonDisabledText ??= Color(customTextButtonDisabledTextInt); @ignore Color? _customTextButtonDisabledText; - final int customTextButtonDisabledTextInt; + late final int customTextButtonDisabledTextInt; // ==== switchBGOn ===================================================== @@ -552,7 +552,7 @@ class StackTheme { Color get switchBGOn => _switchBGOn ??= Color(switchBGOnInt); @ignore Color? _switchBGOn; - final int switchBGOnInt; + late final int switchBGOnInt; // ==== switchBGOff ===================================================== @@ -560,7 +560,7 @@ class StackTheme { Color get switchBGOff => _switchBGOff ??= Color(switchBGOffInt); @ignore Color? _switchBGOff; - final int switchBGOffInt; + late final int switchBGOffInt; // ==== switchBGDisabled ===================================================== @@ -569,7 +569,7 @@ class StackTheme { _switchBGDisabled ??= Color(switchBGDisabledInt); @ignore Color? _switchBGDisabled; - final int switchBGDisabledInt; + late final int switchBGDisabledInt; // ==== switchCircleOn ===================================================== @@ -577,7 +577,7 @@ class StackTheme { Color get switchCircleOn => _switchCircleOn ??= Color(switchCircleOnInt); @ignore Color? _switchCircleOn; - final int switchCircleOnInt; + late final int switchCircleOnInt; // ==== switchCircleOff ===================================================== @@ -585,7 +585,7 @@ class StackTheme { Color get switchCircleOff => _switchCircleOff ??= Color(switchCircleOffInt); @ignore Color? _switchCircleOff; - final int switchCircleOffInt; + late final int switchCircleOffInt; // ==== switchCircleDisabled ===================================================== @@ -594,7 +594,7 @@ class StackTheme { _switchCircleDisabled ??= Color(switchCircleDisabledInt); @ignore Color? _switchCircleDisabled; - final int switchCircleDisabledInt; + late final int switchCircleDisabledInt; // ==== stepIndicatorBGCheck ===================================================== @@ -603,7 +603,7 @@ class StackTheme { _stepIndicatorBGCheck ??= Color(stepIndicatorBGCheckInt); @ignore Color? _stepIndicatorBGCheck; - final int stepIndicatorBGCheckInt; + late final int stepIndicatorBGCheckInt; // ==== stepIndicatorBGNumber ===================================================== @@ -612,7 +612,7 @@ class StackTheme { _stepIndicatorBGNumber ??= Color(stepIndicatorBGNumberInt); @ignore Color? _stepIndicatorBGNumber; - final int stepIndicatorBGNumberInt; + late final int stepIndicatorBGNumberInt; // ==== stepIndicatorBGInactive ===================================================== @@ -621,7 +621,7 @@ class StackTheme { _stepIndicatorBGInactive ??= Color(stepIndicatorBGInactiveInt); @ignore Color? _stepIndicatorBGInactive; - final int stepIndicatorBGInactiveInt; + late final int stepIndicatorBGInactiveInt; // ==== stepIndicatorBGLines ===================================================== @@ -630,7 +630,7 @@ class StackTheme { _stepIndicatorBGLines ??= Color(stepIndicatorBGLinesInt); @ignore Color? _stepIndicatorBGLines; - final int stepIndicatorBGLinesInt; + late final int stepIndicatorBGLinesInt; // ==== stepIndicatorBGLinesInactive ===================================================== @@ -639,7 +639,7 @@ class StackTheme { _stepIndicatorBGLinesInactive ??= Color(stepIndicatorBGLinesInactiveInt); @ignore Color? _stepIndicatorBGLinesInactive; - final int stepIndicatorBGLinesInactiveInt; + late final int stepIndicatorBGLinesInactiveInt; // ==== stepIndicatorIconText ===================================================== @@ -648,7 +648,7 @@ class StackTheme { _stepIndicatorIconText ??= Color(stepIndicatorIconTextInt); @ignore Color? _stepIndicatorIconText; - final int stepIndicatorIconTextInt; + late final int stepIndicatorIconTextInt; // ==== stepIndicatorIconNumber ===================================================== @@ -657,7 +657,7 @@ class StackTheme { _stepIndicatorIconNumber ??= Color(stepIndicatorIconNumberInt); @ignore Color? _stepIndicatorIconNumber; - final int stepIndicatorIconNumberInt; + late final int stepIndicatorIconNumberInt; // ==== stepIndicatorIconInactive ===================================================== @@ -666,7 +666,7 @@ class StackTheme { _stepIndicatorIconInactive ??= Color(stepIndicatorIconInactiveInt); @ignore Color? _stepIndicatorIconInactive; - final int stepIndicatorIconInactiveInt; + late final int stepIndicatorIconInactiveInt; // ==== checkboxBGChecked ===================================================== @@ -675,7 +675,7 @@ class StackTheme { _checkboxBGChecked ??= Color(checkboxBGCheckedInt); @ignore Color? _checkboxBGChecked; - final int checkboxBGCheckedInt; + late final int checkboxBGCheckedInt; // ==== checkboxBorderEmpty ===================================================== @@ -684,7 +684,7 @@ class StackTheme { _checkboxBorderEmpty ??= Color(checkboxBorderEmptyInt); @ignore Color? _checkboxBorderEmpty; - final int checkboxBorderEmptyInt; + late final int checkboxBorderEmptyInt; // ==== checkboxBGDisabled ===================================================== @@ -693,7 +693,7 @@ class StackTheme { _checkboxBGDisabled ??= Color(checkboxBGDisabledInt); @ignore Color? _checkboxBGDisabled; - final int checkboxBGDisabledInt; + late final int checkboxBGDisabledInt; // ==== checkboxIconChecked ===================================================== @@ -702,7 +702,7 @@ class StackTheme { _checkboxIconChecked ??= Color(checkboxIconCheckedInt); @ignore Color? _checkboxIconChecked; - final int checkboxIconCheckedInt; + late final int checkboxIconCheckedInt; // ==== checkboxIconDisabled ===================================================== @@ -711,7 +711,7 @@ class StackTheme { _checkboxIconDisabled ??= Color(checkboxIconDisabledInt); @ignore Color? _checkboxIconDisabled; - final int checkboxIconDisabledInt; + late final int checkboxIconDisabledInt; // ==== checkboxTextLabel ===================================================== @@ -720,7 +720,7 @@ class StackTheme { _checkboxTextLabel ??= Color(checkboxTextLabelInt); @ignore Color? _checkboxTextLabel; - final int checkboxTextLabelInt; + late final int checkboxTextLabelInt; // ==== snackBarBackSuccess ===================================================== @@ -729,7 +729,7 @@ class StackTheme { _snackBarBackSuccess ??= Color(snackBarBackSuccessInt); @ignore Color? _snackBarBackSuccess; - final int snackBarBackSuccessInt; + late final int snackBarBackSuccessInt; // ==== snackBarBackError ===================================================== @@ -738,7 +738,7 @@ class StackTheme { _snackBarBackError ??= Color(snackBarBackErrorInt); @ignore Color? _snackBarBackError; - final int snackBarBackErrorInt; + late final int snackBarBackErrorInt; // ==== snackBarBackInfo ===================================================== @@ -747,7 +747,7 @@ class StackTheme { _snackBarBackInfo ??= Color(snackBarBackInfoInt); @ignore Color? _snackBarBackInfo; - final int snackBarBackInfoInt; + late final int snackBarBackInfoInt; // ==== snackBarTextSuccess ===================================================== @@ -756,7 +756,7 @@ class StackTheme { _snackBarTextSuccess ??= Color(snackBarTextSuccessInt); @ignore Color? _snackBarTextSuccess; - final int snackBarTextSuccessInt; + late final int snackBarTextSuccessInt; // ==== snackBarTextError ===================================================== @@ -765,7 +765,7 @@ class StackTheme { _snackBarTextError ??= Color(snackBarTextErrorInt); @ignore Color? _snackBarTextError; - final int snackBarTextErrorInt; + late final int snackBarTextErrorInt; // ==== snackBarTextInfo ===================================================== @@ -774,7 +774,7 @@ class StackTheme { _snackBarTextInfo ??= Color(snackBarTextInfoInt); @ignore Color? _snackBarTextInfo; - final int snackBarTextInfoInt; + late final int snackBarTextInfoInt; // ==== bottomNavIconBack ===================================================== @@ -783,7 +783,7 @@ class StackTheme { _bottomNavIconBack ??= Color(bottomNavIconBackInt); @ignore Color? _bottomNavIconBack; - final int bottomNavIconBackInt; + late final int bottomNavIconBackInt; // ==== bottomNavIconIcon ===================================================== @@ -792,7 +792,7 @@ class StackTheme { _bottomNavIconIcon ??= Color(bottomNavIconIconInt); @ignore Color? _bottomNavIconIcon; - final int bottomNavIconIconInt; + late final int bottomNavIconIconInt; // ==== bottomNavIconIcon highlighted ===================================================== @@ -801,7 +801,7 @@ class StackTheme { _bottomNavIconIconHighlighted ??= Color(bottomNavIconIconHighlightedInt); @ignore Color? _bottomNavIconIconHighlighted; - final int bottomNavIconIconHighlightedInt; + late final int bottomNavIconIconHighlightedInt; // ==== topNavIconPrimary ===================================================== @@ -810,7 +810,7 @@ class StackTheme { _topNavIconPrimary ??= Color(topNavIconPrimaryInt); @ignore Color? _topNavIconPrimary; - final int topNavIconPrimaryInt; + late final int topNavIconPrimaryInt; // ==== topNavIconGreen ===================================================== @@ -818,7 +818,7 @@ class StackTheme { Color get topNavIconGreen => _topNavIconGreen ??= Color(topNavIconGreenInt); @ignore Color? _topNavIconGreen; - final int topNavIconGreenInt; + late final int topNavIconGreenInt; // ==== topNavIconYellow ===================================================== @@ -827,7 +827,7 @@ class StackTheme { _topNavIconYellow ??= Color(topNavIconYellowInt); @ignore Color? _topNavIconYellow; - final int topNavIconYellowInt; + late final int topNavIconYellowInt; // ==== topNavIconRed ===================================================== @@ -835,7 +835,7 @@ class StackTheme { Color get topNavIconRed => _topNavIconRed ??= Color(topNavIconRedInt); @ignore Color? _topNavIconRed; - final int topNavIconRedInt; + late final int topNavIconRedInt; // ==== settingsIconBack ===================================================== @@ -844,7 +844,7 @@ class StackTheme { _settingsIconBack ??= Color(settingsIconBackInt); @ignore Color? _settingsIconBack; - final int settingsIconBackInt; + late final int settingsIconBackInt; // ==== settingsIconIcon ===================================================== @@ -853,7 +853,7 @@ class StackTheme { _settingsIconIcon ??= Color(settingsIconIconInt); @ignore Color? _settingsIconIcon; - final int settingsIconIconInt; + late final int settingsIconIconInt; // ==== settingsIconBack2 ===================================================== @@ -862,7 +862,7 @@ class StackTheme { _settingsIconBack2 ??= Color(settingsIconBack2Int); @ignore Color? _settingsIconBack2; - final int settingsIconBack2Int; + late final int settingsIconBack2Int; // ==== settingsIconElement ===================================================== @@ -871,7 +871,7 @@ class StackTheme { _settingsIconElement ??= Color(settingsIconElementInt); @ignore Color? _settingsIconElement; - final int settingsIconElementInt; + late final int settingsIconElementInt; // ==== textFieldActiveBG ===================================================== @@ -880,7 +880,7 @@ class StackTheme { _textFieldActiveBG ??= Color(textFieldActiveBGInt); @ignore Color? _textFieldActiveBG; - final int textFieldActiveBGInt; + late final int textFieldActiveBGInt; // ==== textFieldDefaultBG ===================================================== @@ -889,7 +889,7 @@ class StackTheme { _textFieldDefaultBG ??= Color(textFieldDefaultBGInt); @ignore Color? _textFieldDefaultBG; - final int textFieldDefaultBGInt; + late final int textFieldDefaultBGInt; // ==== textFieldErrorBG ===================================================== @@ -898,7 +898,7 @@ class StackTheme { _textFieldErrorBG ??= Color(textFieldErrorBGInt); @ignore Color? _textFieldErrorBG; - final int textFieldErrorBGInt; + late final int textFieldErrorBGInt; // ==== textFieldSuccessBG ===================================================== @@ -907,7 +907,7 @@ class StackTheme { _textFieldSuccessBG ??= Color(textFieldSuccessBGInt); @ignore Color? _textFieldSuccessBG; - final int textFieldSuccessBGInt; + late final int textFieldSuccessBGInt; // ==== textFieldErrorBorder ===================================================== @@ -916,7 +916,7 @@ class StackTheme { _textFieldErrorBorder ??= Color(textFieldErrorBorderInt); @ignore Color? _textFieldErrorBorder; - final int textFieldErrorBorderInt; + late final int textFieldErrorBorderInt; // ==== textFieldSuccessBorder ===================================================== @@ -925,7 +925,7 @@ class StackTheme { _textFieldSuccessBorder ??= Color(textFieldSuccessBorderInt); @ignore Color? _textFieldSuccessBorder; - final int textFieldSuccessBorderInt; + late final int textFieldSuccessBorderInt; // ==== textFieldActiveSearchIconLeft ===================================================== @@ -934,7 +934,7 @@ class StackTheme { Color(textFieldActiveSearchIconLeftInt); @ignore Color? _textFieldActiveSearchIconLeft; - final int textFieldActiveSearchIconLeftInt; + late final int textFieldActiveSearchIconLeftInt; // ==== textFieldDefaultSearchIconLeft ===================================================== @@ -944,7 +944,7 @@ class StackTheme { Color(textFieldDefaultSearchIconLeftInt); @ignore Color? _textFieldDefaultSearchIconLeft; - final int textFieldDefaultSearchIconLeftInt; + late final int textFieldDefaultSearchIconLeftInt; // ==== textFieldErrorSearchIconLeft ===================================================== @@ -953,7 +953,7 @@ class StackTheme { _textFieldErrorSearchIconLeft ??= Color(textFieldErrorSearchIconLeftInt); @ignore Color? _textFieldErrorSearchIconLeft; - final int textFieldErrorSearchIconLeftInt; + late final int textFieldErrorSearchIconLeftInt; // ==== textFieldSuccessSearchIconLeft ===================================================== @@ -963,7 +963,7 @@ class StackTheme { Color(textFieldSuccessSearchIconLeftInt); @ignore Color? _textFieldSuccessSearchIconLeft; - final int textFieldSuccessSearchIconLeftInt; + late final int textFieldSuccessSearchIconLeftInt; // ==== textFieldActiveText ===================================================== @@ -972,7 +972,7 @@ class StackTheme { _textFieldActiveText ??= Color(textFieldActiveTextInt); @ignore Color? _textFieldActiveText; - final int textFieldActiveTextInt; + late final int textFieldActiveTextInt; // ==== textFieldDefaultText ===================================================== @@ -981,7 +981,7 @@ class StackTheme { _textFieldDefaultText ??= Color(textFieldDefaultTextInt); @ignore Color? _textFieldDefaultText; - final int textFieldDefaultTextInt; + late final int textFieldDefaultTextInt; // ==== textFieldErrorText ===================================================== @@ -990,7 +990,7 @@ class StackTheme { _textFieldErrorText ??= Color(textFieldErrorTextInt); @ignore Color? _textFieldErrorText; - final int textFieldErrorTextInt; + late final int textFieldErrorTextInt; // ==== textFieldSuccessText ===================================================== @@ -999,7 +999,7 @@ class StackTheme { _textFieldSuccessText ??= Color(textFieldSuccessTextInt); @ignore Color? _textFieldSuccessText; - final int textFieldSuccessTextInt; + late final int textFieldSuccessTextInt; // ==== textFieldActiveLabel ===================================================== @@ -1008,7 +1008,7 @@ class StackTheme { _textFieldActiveLabel ??= Color(textFieldActiveLabelInt); @ignore Color? _textFieldActiveLabel; - final int textFieldActiveLabelInt; + late final int textFieldActiveLabelInt; // ==== textFieldErrorLabel ===================================================== @@ -1017,7 +1017,7 @@ class StackTheme { _textFieldErrorLabel ??= Color(textFieldErrorLabelInt); @ignore Color? _textFieldErrorLabel; - final int textFieldErrorLabelInt; + late final int textFieldErrorLabelInt; // ==== textFieldSuccessLabel ===================================================== @@ -1026,7 +1026,7 @@ class StackTheme { _textFieldSuccessLabel ??= Color(textFieldSuccessLabelInt); @ignore Color? _textFieldSuccessLabel; - final int textFieldSuccessLabelInt; + late final int textFieldSuccessLabelInt; // ==== textFieldActiveSearchIconRight ===================================================== @@ -1036,7 +1036,7 @@ class StackTheme { Color(textFieldActiveSearchIconRightInt); @ignore Color? _textFieldActiveSearchIconRight; - final int textFieldActiveSearchIconRightInt; + late final int textFieldActiveSearchIconRightInt; // ==== textFieldDefaultSearchIconRight ===================================================== @@ -1046,7 +1046,7 @@ class StackTheme { Color(textFieldDefaultSearchIconRightInt); @ignore Color? _textFieldDefaultSearchIconRight; - final int textFieldDefaultSearchIconRightInt; + late final int textFieldDefaultSearchIconRightInt; // ==== textFieldErrorSearchIconRight ===================================================== @@ -1055,7 +1055,7 @@ class StackTheme { Color(textFieldErrorSearchIconRightInt); @ignore Color? _textFieldErrorSearchIconRight; - final int textFieldErrorSearchIconRightInt; + late final int textFieldErrorSearchIconRightInt; // ==== textFieldSuccessSearchIconRight ===================================================== @@ -1065,7 +1065,7 @@ class StackTheme { Color(textFieldSuccessSearchIconRightInt); @ignore Color? _textFieldSuccessSearchIconRight; - final int textFieldSuccessSearchIconRightInt; + late final int textFieldSuccessSearchIconRightInt; // ==== settingsItem2ActiveBG ===================================================== @@ -1074,7 +1074,7 @@ class StackTheme { _settingsItem2ActiveBG ??= Color(settingsItem2ActiveBGInt); @ignore Color? _settingsItem2ActiveBG; - final int settingsItem2ActiveBGInt; + late final int settingsItem2ActiveBGInt; // ==== settingsItem2ActiveText ===================================================== @@ -1083,7 +1083,7 @@ class StackTheme { _settingsItem2ActiveText ??= Color(settingsItem2ActiveTextInt); @ignore Color? _settingsItem2ActiveText; - final int settingsItem2ActiveTextInt; + late final int settingsItem2ActiveTextInt; // ==== settingsItem2ActiveSub ===================================================== @@ -1092,7 +1092,7 @@ class StackTheme { _settingsItem2ActiveSub ??= Color(settingsItem2ActiveSubInt); @ignore Color? _settingsItem2ActiveSub; - final int settingsItem2ActiveSubInt; + late final int settingsItem2ActiveSubInt; // ==== radioButtonIconBorder ===================================================== @@ -1101,7 +1101,7 @@ class StackTheme { _radioButtonIconBorder ??= Color(radioButtonIconBorderInt); @ignore Color? _radioButtonIconBorder; - final int radioButtonIconBorderInt; + late final int radioButtonIconBorderInt; // ==== radioButtonIconBorderDisabled ===================================================== @@ -1110,7 +1110,7 @@ class StackTheme { Color(radioButtonIconBorderDisabledInt); @ignore Color? _radioButtonIconBorderDisabled; - final int radioButtonIconBorderDisabledInt; + late final int radioButtonIconBorderDisabledInt; // ==== radioButtonBorderEnabled ===================================================== @@ -1119,7 +1119,7 @@ class StackTheme { _radioButtonBorderEnabled ??= Color(radioButtonBorderEnabledInt); @ignore Color? _radioButtonBorderEnabled; - final int radioButtonBorderEnabledInt; + late final int radioButtonBorderEnabledInt; // ==== radioButtonBorderDisabled ===================================================== @@ -1128,7 +1128,7 @@ class StackTheme { _radioButtonBorderDisabled ??= Color(radioButtonBorderDisabledInt); @ignore Color? _radioButtonBorderDisabled; - final int radioButtonBorderDisabledInt; + late final int radioButtonBorderDisabledInt; // ==== radioButtonIconCircle ===================================================== @@ -1137,7 +1137,7 @@ class StackTheme { _radioButtonIconCircle ??= Color(radioButtonIconCircleInt); @ignore Color? _radioButtonIconCircle; - final int radioButtonIconCircleInt; + late final int radioButtonIconCircleInt; // ==== radioButtonIconEnabled ===================================================== @@ -1146,7 +1146,7 @@ class StackTheme { _radioButtonIconEnabled ??= Color(radioButtonIconEnabledInt); @ignore Color? _radioButtonIconEnabled; - final int radioButtonIconEnabledInt; + late final int radioButtonIconEnabledInt; // ==== radioButtonTextEnabled ===================================================== @@ -1155,7 +1155,7 @@ class StackTheme { _radioButtonTextEnabled ??= Color(radioButtonTextEnabledInt); @ignore Color? _radioButtonTextEnabled; - final int radioButtonTextEnabledInt; + late final int radioButtonTextEnabledInt; // ==== radioButtonTextDisabled ===================================================== @@ -1164,7 +1164,7 @@ class StackTheme { _radioButtonTextDisabled ??= Color(radioButtonTextDisabledInt); @ignore Color? _radioButtonTextDisabled; - final int radioButtonTextDisabledInt; + late final int radioButtonTextDisabledInt; // ==== radioButtonLabelEnabled ===================================================== @@ -1173,7 +1173,7 @@ class StackTheme { _radioButtonLabelEnabled ??= Color(radioButtonLabelEnabledInt); @ignore Color? _radioButtonLabelEnabled; - final int radioButtonLabelEnabledInt; + late final int radioButtonLabelEnabledInt; // ==== radioButtonLabelDisabled ===================================================== @@ -1182,7 +1182,7 @@ class StackTheme { _radioButtonLabelDisabled ??= Color(radioButtonLabelDisabledInt); @ignore Color? _radioButtonLabelDisabled; - final int radioButtonLabelDisabledInt; + late final int radioButtonLabelDisabledInt; // ==== infoItemBG ===================================================== @@ -1190,7 +1190,7 @@ class StackTheme { Color get infoItemBG => _infoItemBG ??= Color(infoItemBGInt); @ignore Color? _infoItemBG; - final int infoItemBGInt; + late final int infoItemBGInt; // ==== infoItemLabel ===================================================== @@ -1198,7 +1198,7 @@ class StackTheme { Color get infoItemLabel => _infoItemLabel ??= Color(infoItemLabelInt); @ignore Color? _infoItemLabel; - final int infoItemLabelInt; + late final int infoItemLabelInt; // ==== infoItemText ===================================================== @@ -1206,7 +1206,7 @@ class StackTheme { Color get infoItemText => _infoItemText ??= Color(infoItemTextInt); @ignore Color? _infoItemText; - final int infoItemTextInt; + late final int infoItemTextInt; // ==== infoItemIcons ===================================================== @@ -1214,7 +1214,7 @@ class StackTheme { Color get infoItemIcons => _infoItemIcons ??= Color(infoItemIconsInt); @ignore Color? _infoItemIcons; - final int infoItemIconsInt; + late final int infoItemIconsInt; // ==== popupBG ===================================================== @@ -1222,7 +1222,7 @@ class StackTheme { Color get popupBG => _popupBG ??= Color(popupBGInt); @ignore Color? _popupBG; - final int popupBGInt; + late final int popupBGInt; // ==== currencyListItemBG ===================================================== @@ -1231,7 +1231,7 @@ class StackTheme { _currencyListItemBG ??= Color(currencyListItemBGInt); @ignore Color? _currencyListItemBG; - final int currencyListItemBGInt; + late final int currencyListItemBGInt; // ==== stackWalletBG ===================================================== @@ -1239,7 +1239,7 @@ class StackTheme { Color get stackWalletBG => _stackWalletBG ??= Color(stackWalletBGInt); @ignore Color? _stackWalletBG; - final int stackWalletBGInt; + late final int stackWalletBGInt; // ==== stackWalletMid ===================================================== @@ -1247,7 +1247,7 @@ class StackTheme { Color get stackWalletMid => _stackWalletMid ??= Color(stackWalletMidInt); @ignore Color? _stackWalletMid; - final int stackWalletMidInt; + late final int stackWalletMidInt; // ==== stackWalletBottom ===================================================== @@ -1256,7 +1256,7 @@ class StackTheme { _stackWalletBottom ??= Color(stackWalletBottomInt); @ignore Color? _stackWalletBottom; - final int stackWalletBottomInt; + late final int stackWalletBottomInt; // ==== bottomNavShadow ===================================================== @@ -1264,7 +1264,7 @@ class StackTheme { Color get bottomNavShadow => _bottomNavShadow ??= Color(bottomNavShadowInt); @ignore Color? _bottomNavShadow; - final int bottomNavShadowInt; + late final int bottomNavShadowInt; // ==== favoriteStarActive ===================================================== @@ -1273,7 +1273,7 @@ class StackTheme { _favoriteStarActive ??= Color(favoriteStarActiveInt); @ignore Color? _favoriteStarActive; - final int favoriteStarActiveInt; + late final int favoriteStarActiveInt; // ==== favoriteStarInactive ===================================================== @@ -1282,7 +1282,7 @@ class StackTheme { _favoriteStarInactive ??= Color(favoriteStarInactiveInt); @ignore Color? _favoriteStarInactive; - final int favoriteStarInactiveInt; + late final int favoriteStarInactiveInt; // ==== splash ===================================================== @@ -1290,7 +1290,7 @@ class StackTheme { Color get splash => _splash ??= Color(splashInt); @ignore Color? _splash; - final int splashInt; + late final int splashInt; // ==== highlight ===================================================== @@ -1298,7 +1298,7 @@ class StackTheme { Color get highlight => _highlight ??= Color(highlightInt); @ignore Color? _highlight; - final int highlightInt; + late final int highlightInt; // ==== warningForeground ===================================================== @@ -1307,7 +1307,7 @@ class StackTheme { _warningForeground ??= Color(warningForegroundInt); @ignore Color? _warningForeground; - final int warningForegroundInt; + late final int warningForegroundInt; // ==== warningBackground ===================================================== @@ -1316,7 +1316,7 @@ class StackTheme { _warningBackground ??= Color(warningBackgroundInt); @ignore Color? _warningBackground; - final int warningBackgroundInt; + late final int warningBackgroundInt; // ==== loadingOverlayTextColor ===================================================== @@ -1325,7 +1325,7 @@ class StackTheme { _loadingOverlayTextColor ??= Color(loadingOverlayTextColorInt); @ignore Color? _loadingOverlayTextColor; - final int loadingOverlayTextColorInt; + late final int loadingOverlayTextColorInt; // ==== myStackContactIconBG ===================================================== @@ -1334,7 +1334,7 @@ class StackTheme { _myStackContactIconBG ??= Color(myStackContactIconBGInt); @ignore Color? _myStackContactIconBG; - final int myStackContactIconBGInt; + late final int myStackContactIconBGInt; // ==== textConfirmTotalAmount ===================================================== @@ -1343,7 +1343,7 @@ class StackTheme { _textConfirmTotalAmount ??= Color(textConfirmTotalAmountInt); @ignore Color? _textConfirmTotalAmount; - final int textConfirmTotalAmountInt; + late final int textConfirmTotalAmountInt; // ==== textSelectedWordTableItem ===================================================== @@ -1352,7 +1352,7 @@ class StackTheme { _textSelectedWordTableItem ??= Color(textSelectedWordTableItemInt); @ignore Color? _textSelectedWordTableItem; - final int textSelectedWordTableItemInt; + late final int textSelectedWordTableItemInt; // ==== rateTypeToggleColorOn ===================================================== @@ -1361,7 +1361,7 @@ class StackTheme { _rateTypeToggleColorOn ??= Color(rateTypeToggleColorOnInt); @ignore Color? _rateTypeToggleColorOn; - final int rateTypeToggleColorOnInt; + late final int rateTypeToggleColorOnInt; // ==== rateTypeToggleColorOff ===================================================== @@ -1370,7 +1370,7 @@ class StackTheme { _rateTypeToggleColorOff ??= Color(rateTypeToggleColorOffInt); @ignore Color? _rateTypeToggleColorOff; - final int rateTypeToggleColorOffInt; + late final int rateTypeToggleColorOffInt; // ==== rateTypeToggleDesktopColorOn ===================================================== @@ -1379,7 +1379,7 @@ class StackTheme { _rateTypeToggleDesktopColorOn ??= Color(rateTypeToggleDesktopColorOnInt); @ignore Color? _rateTypeToggleDesktopColorOn; - final int rateTypeToggleDesktopColorOnInt; + late final int rateTypeToggleDesktopColorOnInt; // ==== rateTypeToggleDesktopColorOff ===================================================== @@ -1388,7 +1388,7 @@ class StackTheme { Color(rateTypeToggleDesktopColorOffInt); @ignore Color? _rateTypeToggleDesktopColorOff; - final int rateTypeToggleDesktopColorOffInt; + late final int rateTypeToggleDesktopColorOffInt; // ==== ethTagText ===================================================== @@ -1396,7 +1396,7 @@ class StackTheme { Color get ethTagText => _ethTagText ??= Color(ethTagTextInt); @ignore Color? _ethTagText; - final int ethTagTextInt; + late final int ethTagTextInt; // ==== ethTagBG ===================================================== @@ -1404,7 +1404,7 @@ class StackTheme { Color get ethTagBG => _ethTagBG ??= Color(ethTagBGInt); @ignore Color? _ethTagBG; - final int ethTagBGInt; + late final int ethTagBGInt; // ==== ethWalletTagText ===================================================== @@ -1413,7 +1413,7 @@ class StackTheme { _ethWalletTagText ??= Color(ethWalletTagTextInt); @ignore Color? _ethWalletTagText; - final int ethWalletTagTextInt; + late final int ethWalletTagTextInt; // ==== ethWalletTagBG ===================================================== @@ -1421,7 +1421,7 @@ class StackTheme { Color get ethWalletTagBG => _ethWalletTagBG ??= Color(ethWalletTagBGInt); @ignore Color? _ethWalletTagBG; - final int ethWalletTagBGInt; + late final int ethWalletTagBGInt; // ==== tokenSummaryTextPrimary ===================================================== @@ -1430,7 +1430,7 @@ class StackTheme { _tokenSummaryTextPrimary ??= Color(tokenSummaryTextPrimaryInt); @ignore Color? _tokenSummaryTextPrimary; - final int tokenSummaryTextPrimaryInt; + late final int tokenSummaryTextPrimaryInt; // ==== tokenSummaryTextSecondary ===================================================== @@ -1439,7 +1439,7 @@ class StackTheme { _tokenSummaryTextSecondary ??= Color(tokenSummaryTextSecondaryInt); @ignore Color? _tokenSummaryTextSecondary; - final int tokenSummaryTextSecondaryInt; + late final int tokenSummaryTextSecondaryInt; // ==== tokenSummaryBG ===================================================== @@ -1447,7 +1447,7 @@ class StackTheme { Color get tokenSummaryBG => _tokenSummaryBG ??= Color(tokenSummaryBGInt); @ignore Color? _tokenSummaryBG; - final int tokenSummaryBGInt; + late final int tokenSummaryBGInt; // ==== tokenSummaryButtonBG ===================================================== @@ -1456,7 +1456,7 @@ class StackTheme { _tokenSummaryButtonBG ??= Color(tokenSummaryButtonBGInt); @ignore Color? _tokenSummaryButtonBG; - final int tokenSummaryButtonBGInt; + late final int tokenSummaryButtonBGInt; // ==== tokenSummaryIcon ===================================================== @@ -1465,7 +1465,7 @@ class StackTheme { _tokenSummaryIcon ??= Color(tokenSummaryIconInt); @ignore Color? _tokenSummaryIcon; - final int tokenSummaryIconInt; + late final int tokenSummaryIconInt; // ==== coinColors ===================================================== @@ -1474,478 +1474,341 @@ class StackTheme { _coinColors ??= parseCoinColors(coinColorsJsonString); @ignore Map? _coinColors; - final String coinColorsJsonString; + late final String coinColorsJsonString; // ==== assets ===================================================== - final ThemeAssets assets; + @Name("assets") // legacy "column" name + late final ThemeAssets? assetsV1; + + late final ThemeAssetsV2? assetsV2; + + @ignore + IThemeAssets get assets => assetsV2 ?? assetsV1!; // =========================================================================== - StackTheme({ - required this.themeId, - required this.name, - required this.assets, - required this.brightnessString, - required this.backgroundInt, - required this.backgroundAppBarInt, - required this.gradientBackgroundString, - required this.standardBoxShadowString, - required this.homeViewButtonBarBoxShadowString, - required this.overlayInt, - required this.accentColorBlueInt, - required this.accentColorGreenInt, - required this.accentColorYellowInt, - required this.accentColorRedInt, - required this.accentColorOrangeInt, - required this.accentColorDarkInt, - required this.shadowInt, - required this.textDarkInt, - required this.textDark2Int, - required this.textDark3Int, - required this.textSubtitle1Int, - required this.textSubtitle2Int, - required this.textSubtitle3Int, - required this.textSubtitle4Int, - required this.textSubtitle5Int, - required this.textSubtitle6Int, - required this.textWhiteInt, - required this.textFavoriteCardInt, - required this.textErrorInt, - required this.textRestoreInt, - required this.buttonBackPrimaryInt, - required this.buttonBackSecondaryInt, - required this.buttonBackPrimaryDisabledInt, - required this.buttonBackSecondaryDisabledInt, - required this.buttonBackBorderInt, - required this.buttonBackBorderDisabledInt, - required this.buttonBackBorderSecondaryInt, - required this.buttonBackBorderSecondaryDisabledInt, - required this.numberBackDefaultInt, - required this.numpadBackDefaultInt, - required this.bottomNavBackInt, - required this.buttonTextPrimaryInt, - required this.buttonTextSecondaryInt, - required this.buttonTextPrimaryDisabledInt, - required this.buttonTextSecondaryDisabledInt, - required this.buttonTextBorderInt, - required this.buttonTextDisabledInt, - required this.buttonTextBorderlessInt, - required this.buttonTextBorderlessDisabledInt, - required this.numberTextDefaultInt, - required this.numpadTextDefaultInt, - required this.bottomNavTextInt, - required this.customTextButtonEnabledTextInt, - required this.customTextButtonDisabledTextInt, - required this.switchBGOnInt, - required this.switchBGOffInt, - required this.switchBGDisabledInt, - required this.switchCircleOnInt, - required this.switchCircleOffInt, - required this.switchCircleDisabledInt, - required this.stepIndicatorBGCheckInt, - required this.stepIndicatorBGNumberInt, - required this.stepIndicatorBGInactiveInt, - required this.stepIndicatorBGLinesInt, - required this.stepIndicatorBGLinesInactiveInt, - required this.stepIndicatorIconTextInt, - required this.stepIndicatorIconNumberInt, - required this.stepIndicatorIconInactiveInt, - required this.checkboxBGCheckedInt, - required this.checkboxBorderEmptyInt, - required this.checkboxBGDisabledInt, - required this.checkboxIconCheckedInt, - required this.checkboxIconDisabledInt, - required this.checkboxTextLabelInt, - required this.snackBarBackSuccessInt, - required this.snackBarBackErrorInt, - required this.snackBarBackInfoInt, - required this.snackBarTextSuccessInt, - required this.snackBarTextErrorInt, - required this.snackBarTextInfoInt, - required this.bottomNavIconBackInt, - required this.bottomNavIconIconInt, - required this.bottomNavIconIconHighlightedInt, - required this.topNavIconPrimaryInt, - required this.topNavIconGreenInt, - required this.topNavIconYellowInt, - required this.topNavIconRedInt, - required this.settingsIconBackInt, - required this.settingsIconIconInt, - required this.settingsIconBack2Int, - required this.settingsIconElementInt, - required this.textFieldActiveBGInt, - required this.textFieldDefaultBGInt, - required this.textFieldErrorBGInt, - required this.textFieldSuccessBGInt, - required this.textFieldErrorBorderInt, - required this.textFieldSuccessBorderInt, - required this.textFieldActiveSearchIconLeftInt, - required this.textFieldDefaultSearchIconLeftInt, - required this.textFieldErrorSearchIconLeftInt, - required this.textFieldSuccessSearchIconLeftInt, - required this.textFieldActiveTextInt, - required this.textFieldDefaultTextInt, - required this.textFieldErrorTextInt, - required this.textFieldSuccessTextInt, - required this.textFieldActiveLabelInt, - required this.textFieldErrorLabelInt, - required this.textFieldSuccessLabelInt, - required this.textFieldActiveSearchIconRightInt, - required this.textFieldDefaultSearchIconRightInt, - required this.textFieldErrorSearchIconRightInt, - required this.textFieldSuccessSearchIconRightInt, - required this.settingsItem2ActiveBGInt, - required this.settingsItem2ActiveTextInt, - required this.settingsItem2ActiveSubInt, - required this.radioButtonIconBorderInt, - required this.radioButtonIconBorderDisabledInt, - required this.radioButtonBorderEnabledInt, - required this.radioButtonBorderDisabledInt, - required this.radioButtonIconCircleInt, - required this.radioButtonIconEnabledInt, - required this.radioButtonTextEnabledInt, - required this.radioButtonTextDisabledInt, - required this.radioButtonLabelEnabledInt, - required this.radioButtonLabelDisabledInt, - required this.infoItemBGInt, - required this.infoItemLabelInt, - required this.infoItemTextInt, - required this.infoItemIconsInt, - required this.popupBGInt, - required this.currencyListItemBGInt, - required this.stackWalletBGInt, - required this.stackWalletMidInt, - required this.stackWalletBottomInt, - required this.bottomNavShadowInt, - required this.favoriteStarActiveInt, - required this.favoriteStarInactiveInt, - required this.splashInt, - required this.highlightInt, - required this.warningForegroundInt, - required this.warningBackgroundInt, - required this.loadingOverlayTextColorInt, - required this.myStackContactIconBGInt, - required this.textConfirmTotalAmountInt, - required this.textSelectedWordTableItemInt, - required this.rateTypeToggleColorOnInt, - required this.rateTypeToggleColorOffInt, - required this.rateTypeToggleDesktopColorOnInt, - required this.rateTypeToggleDesktopColorOffInt, - required this.ethTagTextInt, - required this.ethTagBGInt, - required this.ethWalletTagTextInt, - required this.ethWalletTagBGInt, - required this.tokenSummaryTextPrimaryInt, - required this.tokenSummaryTextSecondaryInt, - required this.tokenSummaryBGInt, - required this.tokenSummaryButtonBGInt, - required this.tokenSummaryIconInt, - required this.coinColorsJsonString, - }); + late final int? version; + + StackTheme(); factory StackTheme.fromJson({ required Map json, required String applicationThemesDirectoryPath, }) { - return StackTheme( - themeId: json["id"] as String, - name: json["name"] as String, - brightnessString: json["brightness"] as String, - backgroundInt: parseColor(json["colors"]["background"] as String), - backgroundAppBarInt: - parseColor(json["colors"]["background_app_bar"] as String), - gradientBackgroundString: json["colors"]["gradients"] != null + final version = json["version"] as int? ?? 1; + + return StackTheme() + ..version = version + ..assetsV1 = version == 1 + ? ThemeAssets.fromJson( + json: Map.from(json["assets"] as Map), + applicationThemesDirectoryPath: applicationThemesDirectoryPath, + themeId: json["id"] as String, + ) + : null + ..assetsV2 = version == 2 + ? ThemeAssetsV2.fromJson( + json: Map.from(json["assets"] as Map), + applicationThemesDirectoryPath: applicationThemesDirectoryPath, + themeId: json["id"] as String, + ) + : null + ..themeId = json["id"] as String + ..name = json["name"] as String + ..brightnessString = json["brightness"] as String + ..backgroundInt = parseColor(json["colors"]["background"] as String) + ..backgroundAppBarInt = + parseColor(json["colors"]["background_app_bar"] as String) + ..gradientBackgroundString = json["colors"]["gradients"] != null ? jsonEncode(json["colors"]["gradients"]) - : null, - standardBoxShadowString: - jsonEncode(json["colors"]["box_shadows"]["standard"] as Map), - homeViewButtonBarBoxShadowString: + : null + ..standardBoxShadowString = + jsonEncode(json["colors"]["box_shadows"]["standard"] as Map) + ..homeViewButtonBarBoxShadowString = json["colors"]["box_shadows"]["home_view_button_bar"] == null ? null : jsonEncode( - json["colors"]["box_shadows"]["home_view_button_bar"] as Map), - coinColorsJsonString: jsonEncode(json["colors"]['coin'] as Map), - assets: ThemeAssets.fromJson( - json: Map.from(json["assets"] as Map), - applicationThemesDirectoryPath: applicationThemesDirectoryPath, - themeId: json["id"] as String, - ), - overlayInt: parseColor(json["colors"]["overlay"] as String), - accentColorBlueInt: - parseColor(json["colors"]["accent_color_blue"] as String), - accentColorGreenInt: - parseColor(json["colors"]["accent_color_green"] as String), - accentColorYellowInt: - parseColor(json["colors"]["accent_color_yellow"] as String), - accentColorRedInt: - parseColor(json["colors"]["accent_color_red"] as String), - accentColorOrangeInt: - parseColor(json["colors"]["accent_color_orange"] as String), - accentColorDarkInt: - parseColor(json["colors"]["accent_color_dark"] as String), - shadowInt: parseColor(json["colors"]["shadow"] as String), - textDarkInt: parseColor(json["colors"]["text_dark_one"] as String), - textDark2Int: parseColor(json["colors"]["text_dark_two"] as String), - textDark3Int: parseColor(json["colors"]["text_dark_three"] as String), - textWhiteInt: parseColor(json["colors"]["text_white"] as String), - textFavoriteCardInt: - parseColor(json["colors"]["text_favorite"] as String), - textErrorInt: parseColor(json["colors"]["text_error"] as String), - textRestoreInt: parseColor(json["colors"]["text_restore"] as String), - buttonBackPrimaryInt: - parseColor(json["colors"]["button_back_primary"] as String), - buttonBackSecondaryInt: - parseColor(json["colors"]["button_back_secondary"] as String), - buttonBackPrimaryDisabledInt: - parseColor(json["colors"]["button_back_primary_disabled"] as String), - buttonBackSecondaryDisabledInt: parseColor( - json["colors"]["button_back_secondary_disabled"] as String), - buttonBackBorderInt: - parseColor(json["colors"]["button_back_border"] as String), - buttonBackBorderDisabledInt: - parseColor(json["colors"]["button_back_border_disabled"] as String), - buttonBackBorderSecondaryInt: - parseColor(json["colors"]["button_back_border_secondary"] as String), - buttonBackBorderSecondaryDisabledInt: parseColor( - json["colors"]["button_back_border_secondary_disabled"] as String), - numberBackDefaultInt: - parseColor(json["colors"]["number_back_default"] as String), - numpadBackDefaultInt: - parseColor(json["colors"]["numpad_back_default"] as String), - bottomNavBackInt: parseColor(json["colors"]["bottom_nav_back"] as String), - textSubtitle1Int: - parseColor(json["colors"]["text_subtitle_one"] as String), - textSubtitle2Int: - parseColor(json["colors"]["text_subtitle_two"] as String), - textSubtitle3Int: - parseColor(json["colors"]["text_subtitle_three"] as String), - textSubtitle4Int: - parseColor(json["colors"]["text_subtitle_four"] as String), - textSubtitle5Int: - parseColor(json["colors"]["text_subtitle_five"] as String), - textSubtitle6Int: - parseColor(json["colors"]["text_subtitle_six"] as String), - buttonTextPrimaryInt: - parseColor(json["colors"]["button_text_primary"] as String), - buttonTextSecondaryInt: - parseColor(json["colors"]["button_text_secondary"] as String), - buttonTextPrimaryDisabledInt: - parseColor(json["colors"]["button_text_primary_disabled"] as String), - buttonTextSecondaryDisabledInt: parseColor( - json["colors"]["button_text_secondary_disabled"] as String), - buttonTextBorderInt: - parseColor(json["colors"]["button_text_border"] as String), - buttonTextDisabledInt: - parseColor(json["colors"]["button_text_disabled"] as String), - buttonTextBorderlessInt: - parseColor(json["colors"]["button_text_borderless"] as String), - buttonTextBorderlessDisabledInt: parseColor( - json["colors"]["button_text_borderless_disabled"] as String), - numberTextDefaultInt: - parseColor(json["colors"]["number_text_default"] as String), - numpadTextDefaultInt: - parseColor(json["colors"]["numpad_text_default"] as String), - bottomNavTextInt: parseColor(json["colors"]["bottom_nav_text"] as String), - customTextButtonEnabledTextInt: parseColor( - json["colors"]["custom_text_button_enabled_text"] as String), - customTextButtonDisabledTextInt: parseColor( - json["colors"]["custom_text_button_disabled_text"] as String), - switchBGOnInt: parseColor(json["colors"]["switch_bg_on"] as String), - switchBGOffInt: parseColor(json["colors"]["switch_bg_off"] as String), - switchBGDisabledInt: - parseColor(json["colors"]["switch_bg_disabled"] as String), - switchCircleOnInt: - parseColor(json["colors"]["switch_circle_on"] as String), - switchCircleOffInt: - parseColor(json["colors"]["switch_circle_off"] as String), - switchCircleDisabledInt: - parseColor(json["colors"]["switch_circle_disabled"] as String), - stepIndicatorBGCheckInt: - parseColor(json["colors"]["step_indicator_bg_check"] as String), - stepIndicatorBGNumberInt: - parseColor(json["colors"]["step_indicator_bg_number"] as String), - stepIndicatorBGInactiveInt: - parseColor(json["colors"]["step_indicator_bg_inactive"] as String), - stepIndicatorBGLinesInt: - parseColor(json["colors"]["step_indicator_bg_lines"] as String), - stepIndicatorBGLinesInactiveInt: parseColor( - json["colors"]["step_indicator_bg_lines_inactive"] as String), - stepIndicatorIconTextInt: - parseColor(json["colors"]["step_indicator_icon_text"] as String), - stepIndicatorIconNumberInt: - parseColor(json["colors"]["step_indicator_icon_number"] as String), - stepIndicatorIconInactiveInt: - parseColor(json["colors"]["step_indicator_icon_inactive"] as String), - checkboxBGCheckedInt: - parseColor(json["colors"]["checkbox_bg_checked"] as String), - checkboxBorderEmptyInt: - parseColor(json["colors"]["checkbox_border_empty"] as String), - checkboxBGDisabledInt: - parseColor(json["colors"]["checkbox_bg_disabled"] as String), - checkboxIconCheckedInt: - parseColor(json["colors"]["checkbox_icon_checked"] as String), - checkboxIconDisabledInt: - parseColor(json["colors"]["checkbox_icon_disabled"] as String), - checkboxTextLabelInt: - parseColor(json["colors"]["checkbox_text_label"] as String), - snackBarBackSuccessInt: - parseColor(json["colors"]["snack_bar_back_success"] as String), - snackBarBackErrorInt: - parseColor(json["colors"]["snack_bar_back_error"] as String), - snackBarBackInfoInt: - parseColor(json["colors"]["snack_bar_back_info"] as String), - snackBarTextSuccessInt: - parseColor(json["colors"]["snack_bar_text_success"] as String), - snackBarTextErrorInt: - parseColor(json["colors"]["snack_bar_text_error"] as String), - snackBarTextInfoInt: - parseColor(json["colors"]["snack_bar_text_info"] as String), - bottomNavIconBackInt: - parseColor(json["colors"]["bottom_nav_icon_back"] as String), - bottomNavIconIconInt: - parseColor(json["colors"]["bottom_nav_icon_icon"] as String), - bottomNavIconIconHighlightedInt: parseColor( - json["colors"]["bottom_nav_icon_icon_highlighted"] as String), - topNavIconPrimaryInt: - parseColor(json["colors"]["top_nav_icon_primary"] as String), - topNavIconGreenInt: - parseColor(json["colors"]["top_nav_icon_green"] as String), - topNavIconYellowInt: - parseColor(json["colors"]["top_nav_icon_yellow"] as String), - topNavIconRedInt: - parseColor(json["colors"]["top_nav_icon_red"] as String), - settingsIconBackInt: - parseColor(json["colors"]["settings_icon_back"] as String), - settingsIconIconInt: - parseColor(json["colors"]["settings_icon_icon"] as String), - settingsIconBack2Int: - parseColor(json["colors"]["settings_icon_back_two"] as String), - settingsIconElementInt: - parseColor(json["colors"]["settings_icon_element"] as String), - textFieldActiveBGInt: - parseColor(json["colors"]["text_field_active_bg"] as String), - textFieldDefaultBGInt: - parseColor(json["colors"]["text_field_default_bg"] as String), - textFieldErrorBGInt: - parseColor(json["colors"]["text_field_error_bg"] as String), - textFieldSuccessBGInt: - parseColor(json["colors"]["text_field_success_bg"] as String), - textFieldErrorBorderInt: - parseColor(json["colors"]["text_field_error_border"] as String), - textFieldSuccessBorderInt: - parseColor(json["colors"]["text_field_success_border"] as String), - textFieldActiveSearchIconLeftInt: parseColor( - json["colors"]["text_field_active_search_icon_left"] as String), - textFieldDefaultSearchIconLeftInt: parseColor( - json["colors"]["text_field_default_search_icon_left"] as String), - textFieldErrorSearchIconLeftInt: parseColor( - json["colors"]["text_field_error_search_icon_left"] as String), - textFieldSuccessSearchIconLeftInt: parseColor( - json["colors"]["text_field_success_search_icon_left"] as String), - textFieldActiveTextInt: - parseColor(json["colors"]["text_field_active_text"] as String), - textFieldDefaultTextInt: - parseColor(json["colors"]["text_field_default_text"] as String), - textFieldErrorTextInt: - parseColor(json["colors"]["text_field_error_text"] as String), - textFieldSuccessTextInt: - parseColor(json["colors"]["text_field_success_text"] as String), - textFieldActiveLabelInt: - parseColor(json["colors"]["text_field_active_label"] as String), - textFieldErrorLabelInt: - parseColor(json["colors"]["text_field_error_label"] as String), - textFieldSuccessLabelInt: - parseColor(json["colors"]["text_field_success_label"] as String), - textFieldActiveSearchIconRightInt: parseColor( - json["colors"]["text_field_active_search_icon_right"] as String), - textFieldDefaultSearchIconRightInt: parseColor( - json["colors"]["text_field_default_search_icon_right"] as String), - textFieldErrorSearchIconRightInt: parseColor( - json["colors"]["text_field_error_search_icon_right"] as String), - textFieldSuccessSearchIconRightInt: parseColor( - json["colors"]["text_field_success_search_icon_right"] as String), - settingsItem2ActiveBGInt: parseColor( - json["colors"]["settings_item_level_two_active_bg"] as String), - settingsItem2ActiveTextInt: parseColor( - json["colors"]["settings_item_level_two_active_text"] as String), - settingsItem2ActiveSubInt: parseColor( - json["colors"]["settings_item_level_two_active_sub"] as String), - radioButtonIconBorderInt: - parseColor(json["colors"]["radio_button_icon_border"] as String), - radioButtonIconBorderDisabledInt: parseColor( - json["colors"]["radio_button_icon_border_disabled"] as String), - radioButtonBorderEnabledInt: - parseColor(json["colors"]["radio_button_border_enabled"] as String), - radioButtonBorderDisabledInt: - parseColor(json["colors"]["radio_button_border_disabled"] as String), - radioButtonIconCircleInt: - parseColor(json["colors"]["radio_button_icon_circle"] as String), - radioButtonIconEnabledInt: - parseColor(json["colors"]["radio_button_icon_enabled"] as String), - radioButtonTextEnabledInt: - parseColor(json["colors"]["radio_button_text_enabled"] as String), - radioButtonTextDisabledInt: - parseColor(json["colors"]["radio_button_text_disabled"] as String), - radioButtonLabelEnabledInt: - parseColor(json["colors"]["radio_button_label_enabled"] as String), - radioButtonLabelDisabledInt: - parseColor(json["colors"]["radio_button_label_disabled"] as String), - infoItemBGInt: parseColor(json["colors"]["info_item_bg"] as String), - infoItemLabelInt: parseColor(json["colors"]["info_item_label"] as String), - infoItemTextInt: parseColor(json["colors"]["info_item_text"] as String), - infoItemIconsInt: parseColor(json["colors"]["info_item_icons"] as String), - popupBGInt: parseColor(json["colors"]["popup_bg"] as String), - currencyListItemBGInt: - parseColor(json["colors"]["currency_list_item_bg"] as String), - stackWalletBGInt: parseColor(json["colors"]["sw_bg"] as String), - stackWalletMidInt: parseColor(json["colors"]["sw_mid"] as String), - stackWalletBottomInt: parseColor(json["colors"]["sw_bottom"] as String), - bottomNavShadowInt: - parseColor(json["colors"]["bottom_nav_shadow"] as String), - splashInt: parseColor(json["colors"]["splash"] as String), - highlightInt: parseColor(json["colors"]["highlight"] as String), - warningForegroundInt: - parseColor(json["colors"]["warning_foreground"] as String), - warningBackgroundInt: - parseColor(json["colors"]["warning_background"] as String), - loadingOverlayTextColorInt: - parseColor(json["colors"]["loading_overlay_text_color"] as String), - myStackContactIconBGInt: - parseColor(json["colors"]["my_stack_contact_icon_bg"] as String), - textConfirmTotalAmountInt: - parseColor(json["colors"]["text_confirm_total_amount"] as String), - textSelectedWordTableItemInt: parseColor( - json["colors"]["text_selected_word_table_iterm"] as String), - favoriteStarActiveInt: - parseColor(json["colors"]["favorite_star_active"] as String), - favoriteStarInactiveInt: - parseColor(json["colors"]["favorite_star_inactive"] as String), - rateTypeToggleColorOnInt: - parseColor(json["colors"]["rate_type_toggle_color_on"] as String), - rateTypeToggleColorOffInt: - parseColor(json["colors"]["rate_type_toggle_color_off"] as String), - rateTypeToggleDesktopColorOnInt: parseColor( - json["colors"]["rate_type_toggle_desktop_color_on"] as String), - rateTypeToggleDesktopColorOffInt: parseColor( - json["colors"]["rate_type_toggle_desktop_color_off"] as String), - ethTagTextInt: parseColor(json["colors"]["eth_tag_text"] as String), - ethTagBGInt: parseColor(json["colors"]["eth_tag_bg"] as String), - ethWalletTagTextInt: - parseColor(json["colors"]["eth_wallet_tag_text"] as String), - ethWalletTagBGInt: - parseColor(json["colors"]["eth_wallet_tag_bg"] as String), - tokenSummaryTextPrimaryInt: - parseColor(json["colors"]["token_summary_text_primary"] as String), - tokenSummaryTextSecondaryInt: - parseColor(json["colors"]["token_summary_text_secondary"] as String), - tokenSummaryBGInt: - parseColor(json["colors"]["token_summary_bg"] as String), - tokenSummaryButtonBGInt: - parseColor(json["colors"]["token_summary_button_bg"] as String), - tokenSummaryIconInt: - parseColor(json["colors"]["token_summary_icon"] as String), - ); + json["colors"]["box_shadows"]["home_view_button_bar"] as Map) + ..coinColorsJsonString = jsonEncode(json["colors"]['coin'] as Map) + ..overlayInt = parseColor(json["colors"]["overlay"] as String) + ..accentColorBlueInt = + parseColor(json["colors"]["accent_color_blue"] as String) + ..accentColorGreenInt = + parseColor(json["colors"]["accent_color_green"] as String) + ..accentColorYellowInt = + parseColor(json["colors"]["accent_color_yellow"] as String) + ..accentColorRedInt = + parseColor(json["colors"]["accent_color_red"] as String) + ..accentColorOrangeInt = + parseColor(json["colors"]["accent_color_orange"] as String) + ..accentColorDarkInt = + parseColor(json["colors"]["accent_color_dark"] as String) + ..shadowInt = parseColor(json["colors"]["shadow"] as String) + ..textDarkInt = parseColor(json["colors"]["text_dark_one"] as String) + ..textDark2Int = parseColor(json["colors"]["text_dark_two"] as String) + ..textDark3Int = parseColor(json["colors"]["text_dark_three"] as String) + ..textWhiteInt = parseColor(json["colors"]["text_white"] as String) + ..textFavoriteCardInt = + parseColor(json["colors"]["text_favorite"] as String) + ..textErrorInt = parseColor(json["colors"]["text_error"] as String) + ..textRestoreInt = parseColor(json["colors"]["text_restore"] as String) + ..buttonBackPrimaryInt = + parseColor(json["colors"]["button_back_primary"] as String) + ..buttonBackSecondaryInt = + parseColor(json["colors"]["button_back_secondary"] as String) + ..buttonBackPrimaryDisabledInt = + parseColor(json["colors"]["button_back_primary_disabled"] as String) + ..buttonBackSecondaryDisabledInt = + parseColor(json["colors"]["button_back_secondary_disabled"] as String) + ..buttonBackBorderInt = + parseColor(json["colors"]["button_back_border"] as String) + ..buttonBackBorderDisabledInt = + parseColor(json["colors"]["button_back_border_disabled"] as String) + ..buttonBackBorderSecondaryInt = + parseColor(json["colors"]["button_back_border_secondary"] as String) + ..buttonBackBorderSecondaryDisabledInt = parseColor( + json["colors"]["button_back_border_secondary_disabled"] as String) + ..numberBackDefaultInt = + parseColor(json["colors"]["number_back_default"] as String) + ..numpadBackDefaultInt = + parseColor(json["colors"]["numpad_back_default"] as String) + ..bottomNavBackInt = + parseColor(json["colors"]["bottom_nav_back"] as String) + ..textSubtitle1Int = + parseColor(json["colors"]["text_subtitle_one"] as String) + ..textSubtitle2Int = + parseColor(json["colors"]["text_subtitle_two"] as String) + ..textSubtitle3Int = + parseColor(json["colors"]["text_subtitle_three"] as String) + ..textSubtitle4Int = + parseColor(json["colors"]["text_subtitle_four"] as String) + ..textSubtitle5Int = + parseColor(json["colors"]["text_subtitle_five"] as String) + ..textSubtitle6Int = + parseColor(json["colors"]["text_subtitle_six"] as String) + ..buttonTextPrimaryInt = + parseColor(json["colors"]["button_text_primary"] as String) + ..buttonTextSecondaryInt = + parseColor(json["colors"]["button_text_secondary"] as String) + ..buttonTextPrimaryDisabledInt = + parseColor(json["colors"]["button_text_primary_disabled"] as String) + ..buttonTextSecondaryDisabledInt = + parseColor(json["colors"]["button_text_secondary_disabled"] as String) + ..buttonTextBorderInt = + parseColor(json["colors"]["button_text_border"] as String) + ..buttonTextDisabledInt = + parseColor(json["colors"]["button_text_disabled"] as String) + ..buttonTextBorderlessInt = + parseColor(json["colors"]["button_text_borderless"] as String) + ..buttonTextBorderlessDisabledInt = parseColor( + json["colors"]["button_text_borderless_disabled"] as String) + ..numberTextDefaultInt = + parseColor(json["colors"]["number_text_default"] as String) + ..numpadTextDefaultInt = + parseColor(json["colors"]["numpad_text_default"] as String) + ..bottomNavTextInt = + parseColor(json["colors"]["bottom_nav_text"] as String) + ..customTextButtonEnabledTextInt = parseColor( + json["colors"]["custom_text_button_enabled_text"] as String) + ..customTextButtonDisabledTextInt = parseColor( + json["colors"]["custom_text_button_disabled_text"] as String) + ..switchBGOnInt = parseColor(json["colors"]["switch_bg_on"] as String) + ..switchBGOffInt = parseColor(json["colors"]["switch_bg_off"] as String) + ..switchBGDisabledInt = + parseColor(json["colors"]["switch_bg_disabled"] as String) + ..switchCircleOnInt = + parseColor(json["colors"]["switch_circle_on"] as String) + ..switchCircleOffInt = + parseColor(json["colors"]["switch_circle_off"] as String) + ..switchCircleDisabledInt = + parseColor(json["colors"]["switch_circle_disabled"] as String) + ..stepIndicatorBGCheckInt = + parseColor(json["colors"]["step_indicator_bg_check"] as String) + ..stepIndicatorBGNumberInt = + parseColor(json["colors"]["step_indicator_bg_number"] as String) + ..stepIndicatorBGInactiveInt = + parseColor(json["colors"]["step_indicator_bg_inactive"] as String) + ..stepIndicatorBGLinesInt = + parseColor(json["colors"]["step_indicator_bg_lines"] as String) + ..stepIndicatorBGLinesInactiveInt = parseColor( + json["colors"]["step_indicator_bg_lines_inactive"] as String) + ..stepIndicatorIconTextInt = + parseColor(json["colors"]["step_indicator_icon_text"] as String) + ..stepIndicatorIconNumberInt = + parseColor(json["colors"]["step_indicator_icon_number"] as String) + ..stepIndicatorIconInactiveInt = + parseColor(json["colors"]["step_indicator_icon_inactive"] as String) + ..checkboxBGCheckedInt = + parseColor(json["colors"]["checkbox_bg_checked"] as String) + ..checkboxBorderEmptyInt = + parseColor(json["colors"]["checkbox_border_empty"] as String) + ..checkboxBGDisabledInt = + parseColor(json["colors"]["checkbox_bg_disabled"] as String) + ..checkboxIconCheckedInt = + parseColor(json["colors"]["checkbox_icon_checked"] as String) + ..checkboxIconDisabledInt = + parseColor(json["colors"]["checkbox_icon_disabled"] as String) + ..checkboxTextLabelInt = + parseColor(json["colors"]["checkbox_text_label"] as String) + ..snackBarBackSuccessInt = + parseColor(json["colors"]["snack_bar_back_success"] as String) + ..snackBarBackErrorInt = + parseColor(json["colors"]["snack_bar_back_error"] as String) + ..snackBarBackInfoInt = + parseColor(json["colors"]["snack_bar_back_info"] as String) + ..snackBarTextSuccessInt = + parseColor(json["colors"]["snack_bar_text_success"] as String) + ..snackBarTextErrorInt = + parseColor(json["colors"]["snack_bar_text_error"] as String) + ..snackBarTextInfoInt = + parseColor(json["colors"]["snack_bar_text_info"] as String) + ..bottomNavIconBackInt = + parseColor(json["colors"]["bottom_nav_icon_back"] as String) + ..bottomNavIconIconInt = + parseColor(json["colors"]["bottom_nav_icon_icon"] as String) + ..bottomNavIconIconHighlightedInt = parseColor( + json["colors"]["bottom_nav_icon_icon_highlighted"] as String) + ..topNavIconPrimaryInt = + parseColor(json["colors"]["top_nav_icon_primary"] as String) + ..topNavIconGreenInt = + parseColor(json["colors"]["top_nav_icon_green"] as String) + ..topNavIconYellowInt = + parseColor(json["colors"]["top_nav_icon_yellow"] as String) + ..topNavIconRedInt = + parseColor(json["colors"]["top_nav_icon_red"] as String) + ..settingsIconBackInt = + parseColor(json["colors"]["settings_icon_back"] as String) + ..settingsIconIconInt = + parseColor(json["colors"]["settings_icon_icon"] as String) + ..settingsIconBack2Int = + parseColor(json["colors"]["settings_icon_back_two"] as String) + ..settingsIconElementInt = + parseColor(json["colors"]["settings_icon_element"] as String) + ..textFieldActiveBGInt = + parseColor(json["colors"]["text_field_active_bg"] as String) + ..textFieldDefaultBGInt = + parseColor(json["colors"]["text_field_default_bg"] as String) + ..textFieldErrorBGInt = + parseColor(json["colors"]["text_field_error_bg"] as String) + ..textFieldSuccessBGInt = + parseColor(json["colors"]["text_field_success_bg"] as String) + ..textFieldErrorBorderInt = + parseColor(json["colors"]["text_field_error_border"] as String) + ..textFieldSuccessBorderInt = + parseColor(json["colors"]["text_field_success_border"] as String) + ..textFieldActiveSearchIconLeftInt = parseColor( + json["colors"]["text_field_active_search_icon_left"] as String) + ..textFieldDefaultSearchIconLeftInt = parseColor( + json["colors"]["text_field_default_search_icon_left"] as String) + ..textFieldErrorSearchIconLeftInt = parseColor( + json["colors"]["text_field_error_search_icon_left"] as String) + ..textFieldSuccessSearchIconLeftInt = parseColor( + json["colors"]["text_field_success_search_icon_left"] as String) + ..textFieldActiveTextInt = + parseColor(json["colors"]["text_field_active_text"] as String) + ..textFieldDefaultTextInt = + parseColor(json["colors"]["text_field_default_text"] as String) + ..textFieldErrorTextInt = + parseColor(json["colors"]["text_field_error_text"] as String) + ..textFieldSuccessTextInt = + parseColor(json["colors"]["text_field_success_text"] as String) + ..textFieldActiveLabelInt = + parseColor(json["colors"]["text_field_active_label"] as String) + ..textFieldErrorLabelInt = + parseColor(json["colors"]["text_field_error_label"] as String) + ..textFieldSuccessLabelInt = + parseColor(json["colors"]["text_field_success_label"] as String) + ..textFieldActiveSearchIconRightInt = parseColor( + json["colors"]["text_field_active_search_icon_right"] as String) + ..textFieldDefaultSearchIconRightInt = parseColor( + json["colors"]["text_field_default_search_icon_right"] as String) + ..textFieldErrorSearchIconRightInt = parseColor( + json["colors"]["text_field_error_search_icon_right"] as String) + ..textFieldSuccessSearchIconRightInt = parseColor( + json["colors"]["text_field_success_search_icon_right"] as String) + ..settingsItem2ActiveBGInt = parseColor( + json["colors"]["settings_item_level_two_active_bg"] as String) + ..settingsItem2ActiveTextInt = parseColor( + json["colors"]["settings_item_level_two_active_text"] as String) + ..settingsItem2ActiveSubInt = parseColor( + json["colors"]["settings_item_level_two_active_sub"] as String) + ..radioButtonIconBorderInt = + parseColor(json["colors"]["radio_button_icon_border"] as String) + ..radioButtonIconBorderDisabledInt = parseColor( + json["colors"]["radio_button_icon_border_disabled"] as String) + ..radioButtonBorderEnabledInt = + parseColor(json["colors"]["radio_button_border_enabled"] as String) + ..radioButtonBorderDisabledInt = + parseColor(json["colors"]["radio_button_border_disabled"] as String) + ..radioButtonIconCircleInt = + parseColor(json["colors"]["radio_button_icon_circle"] as String) + ..radioButtonIconEnabledInt = + parseColor(json["colors"]["radio_button_icon_enabled"] as String) + ..radioButtonTextEnabledInt = + parseColor(json["colors"]["radio_button_text_enabled"] as String) + ..radioButtonTextDisabledInt = + parseColor(json["colors"]["radio_button_text_disabled"] as String) + ..radioButtonLabelEnabledInt = + parseColor(json["colors"]["radio_button_label_enabled"] as String) + ..radioButtonLabelDisabledInt = + parseColor(json["colors"]["radio_button_label_disabled"] as String) + ..infoItemBGInt = parseColor(json["colors"]["info_item_bg"] as String) + ..infoItemLabelInt = + parseColor(json["colors"]["info_item_label"] as String) + ..infoItemTextInt = parseColor(json["colors"]["info_item_text"] as String) + ..infoItemIconsInt = + parseColor(json["colors"]["info_item_icons"] as String) + ..popupBGInt = parseColor(json["colors"]["popup_bg"] as String) + ..currencyListItemBGInt = + parseColor(json["colors"]["currency_list_item_bg"] as String) + ..stackWalletBGInt = parseColor(json["colors"]["sw_bg"] as String) + ..stackWalletMidInt = parseColor(json["colors"]["sw_mid"] as String) + ..stackWalletBottomInt = parseColor(json["colors"]["sw_bottom"] as String) + ..bottomNavShadowInt = + parseColor(json["colors"]["bottom_nav_shadow"] as String) + ..splashInt = parseColor(json["colors"]["splash"] as String) + ..highlightInt = parseColor(json["colors"]["highlight"] as String) + ..warningForegroundInt = + parseColor(json["colors"]["warning_foreground"] as String) + ..warningBackgroundInt = + parseColor(json["colors"]["warning_background"] as String) + ..loadingOverlayTextColorInt = + parseColor(json["colors"]["loading_overlay_text_color"] as String) + ..myStackContactIconBGInt = + parseColor(json["colors"]["my_stack_contact_icon_bg"] as String) + ..textConfirmTotalAmountInt = + parseColor(json["colors"]["text_confirm_total_amount"] as String) + ..textSelectedWordTableItemInt = + parseColor(json["colors"]["text_selected_word_table_iterm"] as String) + ..favoriteStarActiveInt = + parseColor(json["colors"]["favorite_star_active"] as String) + ..favoriteStarInactiveInt = + parseColor(json["colors"]["favorite_star_inactive"] as String) + ..rateTypeToggleColorOnInt = + parseColor(json["colors"]["rate_type_toggle_color_on"] as String) + ..rateTypeToggleColorOffInt = + parseColor(json["colors"]["rate_type_toggle_color_off"] as String) + ..rateTypeToggleDesktopColorOnInt = parseColor( + json["colors"]["rate_type_toggle_desktop_color_on"] as String) + ..rateTypeToggleDesktopColorOffInt = parseColor( + json["colors"]["rate_type_toggle_desktop_color_off"] as String) + ..ethTagTextInt = parseColor(json["colors"]["eth_tag_text"] as String) + ..ethTagBGInt = parseColor(json["colors"]["eth_tag_bg"] as String) + ..ethWalletTagTextInt = + parseColor(json["colors"]["eth_wallet_tag_text"] as String) + ..ethWalletTagBGInt = + parseColor(json["colors"]["eth_wallet_tag_bg"] as String) + ..tokenSummaryTextPrimaryInt = + parseColor(json["colors"]["token_summary_text_primary"] as String) + ..tokenSummaryTextSecondaryInt = + parseColor(json["colors"]["token_summary_text_secondary"] as String) + ..tokenSummaryBGInt = + parseColor(json["colors"]["token_summary_bg"] as String) + ..tokenSummaryButtonBGInt = + parseColor(json["colors"]["token_summary_button_bg"] as String) + ..tokenSummaryIconInt = + parseColor(json["colors"]["token_summary_icon"] as String); } /// Grab the int value of the hex color string. @@ -1990,25 +1853,44 @@ class StackTheme { } @Embedded(inheritance: false) -class ThemeAssets { +class ThemeAssets implements IThemeAssets { + @override late final String bellNew; + @override late final String buy; + @override late final String exchange; + @override late final String personaIncognito; + @override late final String personaEasy; + @override late final String stack; + @override late final String stackIcon; + @override late final String receive; + @override late final String receivePending; + @override late final String receiveCancelled; + @override late final String send; + @override late final String sendPending; + @override late final String sendCancelled; + @override late final String themeSelector; + @override late final String themePreview; + @override late final String txExchange; + @override late final String txExchangePending; + @override late final String txExchangeFailed; + late final String bitcoin; late final String litecoin; late final String bitcoincash; @@ -2042,7 +1924,9 @@ class ThemeAssets { late final String wowneroImageSecondary; late final String namecoinImageSecondary; late final String particlImageSecondary; + @override late final String? loadingGif; + @override late final String? background; // todo: add all assets expected in json @@ -2165,3 +2049,191 @@ class ThemeAssets { : null; } } + +@Embedded(inheritance: false) +class ThemeAssetsV2 implements IThemeAssets { + @override + late final String bellNew; + @override + late final String buy; + @override + late final String exchange; + @override + late final String personaIncognito; + @override + late final String personaEasy; + @override + late final String stack; + @override + late final String stackIcon; + @override + late final String receive; + @override + late final String receivePending; + @override + late final String receiveCancelled; + @override + late final String send; + @override + late final String sendPending; + @override + late final String sendCancelled; + @override + late final String themeSelector; + @override + late final String themePreview; + @override + late final String txExchange; + @override + late final String txExchangePending; + @override + late final String txExchangeFailed; + @override + late final String? loadingGif; + @override + late final String? background; + + late final String coinPlaceholder; + + @ignore + Map get coinIcons => _coinIcons ??= parseCoinAssetsString( + coinIconsString, + placeHolder: coinPlaceholder, + ); + @ignore + Map? _coinIcons; + late final String coinIconsString; + + @ignore + Map get coinImages => _coinImages ??= parseCoinAssetsString( + coinImagesString, + placeHolder: coinPlaceholder, + ); + @ignore + Map? _coinImages; + late final String coinImagesString; + + @ignore + Map get coinSecondaryImages => + _coinSecondaryImages ??= parseCoinAssetsString( + coinSecondaryImagesString, + placeHolder: coinPlaceholder, + ); + @ignore + Map? _coinSecondaryImages; + late final String coinSecondaryImagesString; + + ThemeAssetsV2(); + + factory ThemeAssetsV2.fromJson({ + required Map json, + required String applicationThemesDirectoryPath, + required String themeId, + }) { + return ThemeAssetsV2() + ..bellNew = + "$applicationThemesDirectoryPath/$themeId/assets/${json["bell_new"] as String}" + ..buy = + "$applicationThemesDirectoryPath/$themeId/assets/${json["buy"] as String}" + ..exchange = + "$applicationThemesDirectoryPath/$themeId/assets/${json["exchange"] as String}" + ..personaIncognito = + "$applicationThemesDirectoryPath/$themeId/assets/${json["persona_incognito"] as String}" + ..personaEasy = + "$applicationThemesDirectoryPath/$themeId/assets/${json["persona_easy"] as String}" + ..stack = + "$applicationThemesDirectoryPath/$themeId/assets/${json["stack"] as String}" + ..stackIcon = + "$applicationThemesDirectoryPath/$themeId/assets/${json["stack_icon"] as String}" + ..receive = + "$applicationThemesDirectoryPath/$themeId/assets/${json["receive"] as String}" + ..receivePending = + "$applicationThemesDirectoryPath/$themeId/assets/${json["receive_pending"] as String}" + ..receiveCancelled = + "$applicationThemesDirectoryPath/$themeId/assets/${json["receive_cancelled"] as String}" + ..send = + "$applicationThemesDirectoryPath/$themeId/assets/${json["send"] as String}" + ..sendPending = + "$applicationThemesDirectoryPath/$themeId/assets/${json["send_pending"] as String}" + ..sendCancelled = + "$applicationThemesDirectoryPath/$themeId/assets/${json["send_cancelled"] as String}" + ..themeSelector = + "$applicationThemesDirectoryPath/$themeId/assets/${json["theme_selector"] as String}" + ..themePreview = + "$applicationThemesDirectoryPath/$themeId/assets/${json["theme_preview"] as String}" + ..txExchange = + "$applicationThemesDirectoryPath/$themeId/assets/${json["tx_exchange"] as String}" + ..txExchangePending = + "$applicationThemesDirectoryPath/$themeId/assets/${json["tx_exchange_pending"] as String}" + ..txExchangeFailed = + "$applicationThemesDirectoryPath/$themeId/assets/${json["tx_exchange_failed"] as String}" + ..coinPlaceholder = + "$applicationThemesDirectoryPath/$themeId/assets/${json["coin_placeholder"] as String}" + ..coinIconsString = createCoinAssetsString( + "$applicationThemesDirectoryPath/$themeId/assets", + Map.from(json["coins"]["icons"] as Map), + ) + ..coinImagesString = createCoinAssetsString( + "$applicationThemesDirectoryPath/$themeId/assets", + Map.from(json["coins"]["images"] as Map), + ) + ..coinSecondaryImagesString = createCoinAssetsString( + "$applicationThemesDirectoryPath/$themeId/assets", + Map.from(json["coins"]["secondaries"] as Map), + ) + ..loadingGif = json["loading_gif"] is String + ? "$applicationThemesDirectoryPath/$themeId/assets/${json["loading_gif"] as String}" + : null + ..background = json["background"] is String + ? "$applicationThemesDirectoryPath/$themeId/assets/${json["background"] as String}" + : null; + } + + static String createCoinAssetsString(String path, Map json) { + final Map map = {}; + for (final entry in json.entries) { + map[entry.key] = "$path/${entry.value as String}"; + } + return jsonEncode(map); + } + + static Map parseCoinAssetsString( + String jsonString, { + required String placeHolder, + }) { + final json = jsonDecode(jsonString) as Map; + final map = Map.from(json); + + final Map result = {}; + + for (final coin in Coin.values) { + result[coin] = map[coin.name] as String? ?? placeHolder; + } + + return result; + } +} + +abstract class IThemeAssets { + String get bellNew; + String get buy; + String get exchange; + String get personaIncognito; + String get personaEasy; + String get stack; + String get stackIcon; + String get receive; + String get receivePending; + String get receiveCancelled; + String get send; + String get sendPending; + String get sendCancelled; + String get themeSelector; + String get themePreview; + String get txExchange; + String get txExchangePending; + String get txExchangeFailed; + + String? get loadingGif; + String? get background; +} diff --git a/lib/models/isar/stack_theme.g.dart b/lib/models/isar/stack_theme.g.dart index 64d8c3f6c..ad6f905bd 100644 --- a/lib/models/isar/stack_theme.g.dart +++ b/lib/models/isar/stack_theme.g.dart @@ -53,763 +53,774 @@ const StackThemeSchema = CollectionSchema( type: IsarType.object, target: r'ThemeAssets', ), - r'backgroundAppBarInt': PropertySchema( + r'assetsV2': PropertySchema( id: 7, + name: r'assetsV2', + type: IsarType.object, + target: r'ThemeAssetsV2', + ), + r'backgroundAppBarInt': PropertySchema( + id: 8, name: r'backgroundAppBarInt', type: IsarType.long, ), r'backgroundInt': PropertySchema( - id: 8, + id: 9, name: r'backgroundInt', type: IsarType.long, ), r'bottomNavBackInt': PropertySchema( - id: 9, + id: 10, name: r'bottomNavBackInt', type: IsarType.long, ), r'bottomNavIconBackInt': PropertySchema( - id: 10, + id: 11, name: r'bottomNavIconBackInt', type: IsarType.long, ), r'bottomNavIconIconHighlightedInt': PropertySchema( - id: 11, + id: 12, name: r'bottomNavIconIconHighlightedInt', type: IsarType.long, ), r'bottomNavIconIconInt': PropertySchema( - id: 12, + id: 13, name: r'bottomNavIconIconInt', type: IsarType.long, ), r'bottomNavShadowInt': PropertySchema( - id: 13, + id: 14, name: r'bottomNavShadowInt', type: IsarType.long, ), r'bottomNavTextInt': PropertySchema( - id: 14, + id: 15, name: r'bottomNavTextInt', type: IsarType.long, ), r'brightnessString': PropertySchema( - id: 15, + id: 16, name: r'brightnessString', type: IsarType.string, ), r'buttonBackBorderDisabledInt': PropertySchema( - id: 16, + id: 17, name: r'buttonBackBorderDisabledInt', type: IsarType.long, ), r'buttonBackBorderInt': PropertySchema( - id: 17, + id: 18, name: r'buttonBackBorderInt', type: IsarType.long, ), r'buttonBackBorderSecondaryDisabledInt': PropertySchema( - id: 18, + id: 19, name: r'buttonBackBorderSecondaryDisabledInt', type: IsarType.long, ), r'buttonBackBorderSecondaryInt': PropertySchema( - id: 19, + id: 20, name: r'buttonBackBorderSecondaryInt', type: IsarType.long, ), r'buttonBackPrimaryDisabledInt': PropertySchema( - id: 20, + id: 21, name: r'buttonBackPrimaryDisabledInt', type: IsarType.long, ), r'buttonBackPrimaryInt': PropertySchema( - id: 21, + id: 22, name: r'buttonBackPrimaryInt', type: IsarType.long, ), r'buttonBackSecondaryDisabledInt': PropertySchema( - id: 22, + id: 23, name: r'buttonBackSecondaryDisabledInt', type: IsarType.long, ), r'buttonBackSecondaryInt': PropertySchema( - id: 23, + id: 24, name: r'buttonBackSecondaryInt', type: IsarType.long, ), r'buttonTextBorderInt': PropertySchema( - id: 24, + id: 25, name: r'buttonTextBorderInt', type: IsarType.long, ), r'buttonTextBorderlessDisabledInt': PropertySchema( - id: 25, + id: 26, name: r'buttonTextBorderlessDisabledInt', type: IsarType.long, ), r'buttonTextBorderlessInt': PropertySchema( - id: 26, + id: 27, name: r'buttonTextBorderlessInt', type: IsarType.long, ), r'buttonTextDisabledInt': PropertySchema( - id: 27, + id: 28, name: r'buttonTextDisabledInt', type: IsarType.long, ), r'buttonTextPrimaryDisabledInt': PropertySchema( - id: 28, + id: 29, name: r'buttonTextPrimaryDisabledInt', type: IsarType.long, ), r'buttonTextPrimaryInt': PropertySchema( - id: 29, + id: 30, name: r'buttonTextPrimaryInt', type: IsarType.long, ), r'buttonTextSecondaryDisabledInt': PropertySchema( - id: 30, + id: 31, name: r'buttonTextSecondaryDisabledInt', type: IsarType.long, ), r'buttonTextSecondaryInt': PropertySchema( - id: 31, + id: 32, name: r'buttonTextSecondaryInt', type: IsarType.long, ), r'checkboxBGCheckedInt': PropertySchema( - id: 32, + id: 33, name: r'checkboxBGCheckedInt', type: IsarType.long, ), r'checkboxBGDisabledInt': PropertySchema( - id: 33, + id: 34, name: r'checkboxBGDisabledInt', type: IsarType.long, ), r'checkboxBorderEmptyInt': PropertySchema( - id: 34, + id: 35, name: r'checkboxBorderEmptyInt', type: IsarType.long, ), r'checkboxIconCheckedInt': PropertySchema( - id: 35, + id: 36, name: r'checkboxIconCheckedInt', type: IsarType.long, ), r'checkboxIconDisabledInt': PropertySchema( - id: 36, + id: 37, name: r'checkboxIconDisabledInt', type: IsarType.long, ), r'checkboxTextLabelInt': PropertySchema( - id: 37, + id: 38, name: r'checkboxTextLabelInt', type: IsarType.long, ), r'coinColorsJsonString': PropertySchema( - id: 38, + id: 39, name: r'coinColorsJsonString', type: IsarType.string, ), r'currencyListItemBGInt': PropertySchema( - id: 39, + id: 40, name: r'currencyListItemBGInt', type: IsarType.long, ), r'customTextButtonDisabledTextInt': PropertySchema( - id: 40, + id: 41, name: r'customTextButtonDisabledTextInt', type: IsarType.long, ), r'customTextButtonEnabledTextInt': PropertySchema( - id: 41, + id: 42, name: r'customTextButtonEnabledTextInt', type: IsarType.long, ), r'ethTagBGInt': PropertySchema( - id: 42, + id: 43, name: r'ethTagBGInt', type: IsarType.long, ), r'ethTagTextInt': PropertySchema( - id: 43, + id: 44, name: r'ethTagTextInt', type: IsarType.long, ), r'ethWalletTagBGInt': PropertySchema( - id: 44, + id: 45, name: r'ethWalletTagBGInt', type: IsarType.long, ), r'ethWalletTagTextInt': PropertySchema( - id: 45, + id: 46, name: r'ethWalletTagTextInt', type: IsarType.long, ), r'favoriteStarActiveInt': PropertySchema( - id: 46, + id: 47, name: r'favoriteStarActiveInt', type: IsarType.long, ), r'favoriteStarInactiveInt': PropertySchema( - id: 47, + id: 48, name: r'favoriteStarInactiveInt', type: IsarType.long, ), r'gradientBackgroundString': PropertySchema( - id: 48, + id: 49, name: r'gradientBackgroundString', type: IsarType.string, ), r'highlightInt': PropertySchema( - id: 49, + id: 50, name: r'highlightInt', type: IsarType.long, ), r'homeViewButtonBarBoxShadowString': PropertySchema( - id: 50, + id: 51, name: r'homeViewButtonBarBoxShadowString', type: IsarType.string, ), r'infoItemBGInt': PropertySchema( - id: 51, + id: 52, name: r'infoItemBGInt', type: IsarType.long, ), r'infoItemIconsInt': PropertySchema( - id: 52, + id: 53, name: r'infoItemIconsInt', type: IsarType.long, ), r'infoItemLabelInt': PropertySchema( - id: 53, + id: 54, name: r'infoItemLabelInt', type: IsarType.long, ), r'infoItemTextInt': PropertySchema( - id: 54, + id: 55, name: r'infoItemTextInt', type: IsarType.long, ), r'loadingOverlayTextColorInt': PropertySchema( - id: 55, + id: 56, name: r'loadingOverlayTextColorInt', type: IsarType.long, ), r'myStackContactIconBGInt': PropertySchema( - id: 56, + id: 57, name: r'myStackContactIconBGInt', type: IsarType.long, ), r'name': PropertySchema( - id: 57, + id: 58, name: r'name', type: IsarType.string, ), r'numberBackDefaultInt': PropertySchema( - id: 58, + id: 59, name: r'numberBackDefaultInt', type: IsarType.long, ), r'numberTextDefaultInt': PropertySchema( - id: 59, + id: 60, name: r'numberTextDefaultInt', type: IsarType.long, ), r'numpadBackDefaultInt': PropertySchema( - id: 60, + id: 61, name: r'numpadBackDefaultInt', type: IsarType.long, ), r'numpadTextDefaultInt': PropertySchema( - id: 61, + id: 62, name: r'numpadTextDefaultInt', type: IsarType.long, ), r'overlayInt': PropertySchema( - id: 62, + id: 63, name: r'overlayInt', type: IsarType.long, ), r'popupBGInt': PropertySchema( - id: 63, + id: 64, name: r'popupBGInt', type: IsarType.long, ), r'radioButtonBorderDisabledInt': PropertySchema( - id: 64, + id: 65, name: r'radioButtonBorderDisabledInt', type: IsarType.long, ), r'radioButtonBorderEnabledInt': PropertySchema( - id: 65, + id: 66, name: r'radioButtonBorderEnabledInt', type: IsarType.long, ), r'radioButtonIconBorderDisabledInt': PropertySchema( - id: 66, + id: 67, name: r'radioButtonIconBorderDisabledInt', type: IsarType.long, ), r'radioButtonIconBorderInt': PropertySchema( - id: 67, + id: 68, name: r'radioButtonIconBorderInt', type: IsarType.long, ), r'radioButtonIconCircleInt': PropertySchema( - id: 68, + id: 69, name: r'radioButtonIconCircleInt', type: IsarType.long, ), r'radioButtonIconEnabledInt': PropertySchema( - id: 69, + id: 70, name: r'radioButtonIconEnabledInt', type: IsarType.long, ), r'radioButtonLabelDisabledInt': PropertySchema( - id: 70, + id: 71, name: r'radioButtonLabelDisabledInt', type: IsarType.long, ), r'radioButtonLabelEnabledInt': PropertySchema( - id: 71, + id: 72, name: r'radioButtonLabelEnabledInt', type: IsarType.long, ), r'radioButtonTextDisabledInt': PropertySchema( - id: 72, + id: 73, name: r'radioButtonTextDisabledInt', type: IsarType.long, ), r'radioButtonTextEnabledInt': PropertySchema( - id: 73, + id: 74, name: r'radioButtonTextEnabledInt', type: IsarType.long, ), r'rateTypeToggleColorOffInt': PropertySchema( - id: 74, + id: 75, name: r'rateTypeToggleColorOffInt', type: IsarType.long, ), r'rateTypeToggleColorOnInt': PropertySchema( - id: 75, + id: 76, name: r'rateTypeToggleColorOnInt', type: IsarType.long, ), r'rateTypeToggleDesktopColorOffInt': PropertySchema( - id: 76, + id: 77, name: r'rateTypeToggleDesktopColorOffInt', type: IsarType.long, ), r'rateTypeToggleDesktopColorOnInt': PropertySchema( - id: 77, + id: 78, name: r'rateTypeToggleDesktopColorOnInt', type: IsarType.long, ), r'settingsIconBack2Int': PropertySchema( - id: 78, + id: 79, name: r'settingsIconBack2Int', type: IsarType.long, ), r'settingsIconBackInt': PropertySchema( - id: 79, + id: 80, name: r'settingsIconBackInt', type: IsarType.long, ), r'settingsIconElementInt': PropertySchema( - id: 80, + id: 81, name: r'settingsIconElementInt', type: IsarType.long, ), r'settingsIconIconInt': PropertySchema( - id: 81, + id: 82, name: r'settingsIconIconInt', type: IsarType.long, ), r'settingsItem2ActiveBGInt': PropertySchema( - id: 82, + id: 83, name: r'settingsItem2ActiveBGInt', type: IsarType.long, ), r'settingsItem2ActiveSubInt': PropertySchema( - id: 83, + id: 84, name: r'settingsItem2ActiveSubInt', type: IsarType.long, ), r'settingsItem2ActiveTextInt': PropertySchema( - id: 84, + id: 85, name: r'settingsItem2ActiveTextInt', type: IsarType.long, ), r'shadowInt': PropertySchema( - id: 85, + id: 86, name: r'shadowInt', type: IsarType.long, ), r'snackBarBackErrorInt': PropertySchema( - id: 86, + id: 87, name: r'snackBarBackErrorInt', type: IsarType.long, ), r'snackBarBackInfoInt': PropertySchema( - id: 87, + id: 88, name: r'snackBarBackInfoInt', type: IsarType.long, ), r'snackBarBackSuccessInt': PropertySchema( - id: 88, + id: 89, name: r'snackBarBackSuccessInt', type: IsarType.long, ), r'snackBarTextErrorInt': PropertySchema( - id: 89, + id: 90, name: r'snackBarTextErrorInt', type: IsarType.long, ), r'snackBarTextInfoInt': PropertySchema( - id: 90, + id: 91, name: r'snackBarTextInfoInt', type: IsarType.long, ), r'snackBarTextSuccessInt': PropertySchema( - id: 91, + id: 92, name: r'snackBarTextSuccessInt', type: IsarType.long, ), r'splashInt': PropertySchema( - id: 92, + id: 93, name: r'splashInt', type: IsarType.long, ), r'stackWalletBGInt': PropertySchema( - id: 93, + id: 94, name: r'stackWalletBGInt', type: IsarType.long, ), r'stackWalletBottomInt': PropertySchema( - id: 94, + id: 95, name: r'stackWalletBottomInt', type: IsarType.long, ), r'stackWalletMidInt': PropertySchema( - id: 95, + id: 96, name: r'stackWalletMidInt', type: IsarType.long, ), r'standardBoxShadowString': PropertySchema( - id: 96, + id: 97, name: r'standardBoxShadowString', type: IsarType.string, ), r'stepIndicatorBGCheckInt': PropertySchema( - id: 97, + id: 98, name: r'stepIndicatorBGCheckInt', type: IsarType.long, ), r'stepIndicatorBGInactiveInt': PropertySchema( - id: 98, + id: 99, name: r'stepIndicatorBGInactiveInt', type: IsarType.long, ), r'stepIndicatorBGLinesInactiveInt': PropertySchema( - id: 99, + id: 100, name: r'stepIndicatorBGLinesInactiveInt', type: IsarType.long, ), r'stepIndicatorBGLinesInt': PropertySchema( - id: 100, + id: 101, name: r'stepIndicatorBGLinesInt', type: IsarType.long, ), r'stepIndicatorBGNumberInt': PropertySchema( - id: 101, + id: 102, name: r'stepIndicatorBGNumberInt', type: IsarType.long, ), r'stepIndicatorIconInactiveInt': PropertySchema( - id: 102, + id: 103, name: r'stepIndicatorIconInactiveInt', type: IsarType.long, ), r'stepIndicatorIconNumberInt': PropertySchema( - id: 103, + id: 104, name: r'stepIndicatorIconNumberInt', type: IsarType.long, ), r'stepIndicatorIconTextInt': PropertySchema( - id: 104, + id: 105, name: r'stepIndicatorIconTextInt', type: IsarType.long, ), r'switchBGDisabledInt': PropertySchema( - id: 105, + id: 106, name: r'switchBGDisabledInt', type: IsarType.long, ), r'switchBGOffInt': PropertySchema( - id: 106, + id: 107, name: r'switchBGOffInt', type: IsarType.long, ), r'switchBGOnInt': PropertySchema( - id: 107, + id: 108, name: r'switchBGOnInt', type: IsarType.long, ), r'switchCircleDisabledInt': PropertySchema( - id: 108, + id: 109, name: r'switchCircleDisabledInt', type: IsarType.long, ), r'switchCircleOffInt': PropertySchema( - id: 109, + id: 110, name: r'switchCircleOffInt', type: IsarType.long, ), r'switchCircleOnInt': PropertySchema( - id: 110, + id: 111, name: r'switchCircleOnInt', type: IsarType.long, ), r'textConfirmTotalAmountInt': PropertySchema( - id: 111, + id: 112, name: r'textConfirmTotalAmountInt', type: IsarType.long, ), r'textDark2Int': PropertySchema( - id: 112, + id: 113, name: r'textDark2Int', type: IsarType.long, ), r'textDark3Int': PropertySchema( - id: 113, + id: 114, name: r'textDark3Int', type: IsarType.long, ), r'textDarkInt': PropertySchema( - id: 114, + id: 115, name: r'textDarkInt', type: IsarType.long, ), r'textErrorInt': PropertySchema( - id: 115, + id: 116, name: r'textErrorInt', type: IsarType.long, ), r'textFavoriteCardInt': PropertySchema( - id: 116, + id: 117, name: r'textFavoriteCardInt', type: IsarType.long, ), r'textFieldActiveBGInt': PropertySchema( - id: 117, + id: 118, name: r'textFieldActiveBGInt', type: IsarType.long, ), r'textFieldActiveLabelInt': PropertySchema( - id: 118, + id: 119, name: r'textFieldActiveLabelInt', type: IsarType.long, ), r'textFieldActiveSearchIconLeftInt': PropertySchema( - id: 119, + id: 120, name: r'textFieldActiveSearchIconLeftInt', type: IsarType.long, ), r'textFieldActiveSearchIconRightInt': PropertySchema( - id: 120, + id: 121, name: r'textFieldActiveSearchIconRightInt', type: IsarType.long, ), r'textFieldActiveTextInt': PropertySchema( - id: 121, + id: 122, name: r'textFieldActiveTextInt', type: IsarType.long, ), r'textFieldDefaultBGInt': PropertySchema( - id: 122, + id: 123, name: r'textFieldDefaultBGInt', type: IsarType.long, ), r'textFieldDefaultSearchIconLeftInt': PropertySchema( - id: 123, + id: 124, name: r'textFieldDefaultSearchIconLeftInt', type: IsarType.long, ), r'textFieldDefaultSearchIconRightInt': PropertySchema( - id: 124, + id: 125, name: r'textFieldDefaultSearchIconRightInt', type: IsarType.long, ), r'textFieldDefaultTextInt': PropertySchema( - id: 125, + id: 126, name: r'textFieldDefaultTextInt', type: IsarType.long, ), r'textFieldErrorBGInt': PropertySchema( - id: 126, + id: 127, name: r'textFieldErrorBGInt', type: IsarType.long, ), r'textFieldErrorBorderInt': PropertySchema( - id: 127, + id: 128, name: r'textFieldErrorBorderInt', type: IsarType.long, ), r'textFieldErrorLabelInt': PropertySchema( - id: 128, + id: 129, name: r'textFieldErrorLabelInt', type: IsarType.long, ), r'textFieldErrorSearchIconLeftInt': PropertySchema( - id: 129, + id: 130, name: r'textFieldErrorSearchIconLeftInt', type: IsarType.long, ), r'textFieldErrorSearchIconRightInt': PropertySchema( - id: 130, + id: 131, name: r'textFieldErrorSearchIconRightInt', type: IsarType.long, ), r'textFieldErrorTextInt': PropertySchema( - id: 131, + id: 132, name: r'textFieldErrorTextInt', type: IsarType.long, ), r'textFieldSuccessBGInt': PropertySchema( - id: 132, + id: 133, name: r'textFieldSuccessBGInt', type: IsarType.long, ), r'textFieldSuccessBorderInt': PropertySchema( - id: 133, + id: 134, name: r'textFieldSuccessBorderInt', type: IsarType.long, ), r'textFieldSuccessLabelInt': PropertySchema( - id: 134, + id: 135, name: r'textFieldSuccessLabelInt', type: IsarType.long, ), r'textFieldSuccessSearchIconLeftInt': PropertySchema( - id: 135, + id: 136, name: r'textFieldSuccessSearchIconLeftInt', type: IsarType.long, ), r'textFieldSuccessSearchIconRightInt': PropertySchema( - id: 136, + id: 137, name: r'textFieldSuccessSearchIconRightInt', type: IsarType.long, ), r'textFieldSuccessTextInt': PropertySchema( - id: 137, + id: 138, name: r'textFieldSuccessTextInt', type: IsarType.long, ), r'textRestoreInt': PropertySchema( - id: 138, + id: 139, name: r'textRestoreInt', type: IsarType.long, ), r'textSelectedWordTableItemInt': PropertySchema( - id: 139, + id: 140, name: r'textSelectedWordTableItemInt', type: IsarType.long, ), r'textSubtitle1Int': PropertySchema( - id: 140, + id: 141, name: r'textSubtitle1Int', type: IsarType.long, ), r'textSubtitle2Int': PropertySchema( - id: 141, + id: 142, name: r'textSubtitle2Int', type: IsarType.long, ), r'textSubtitle3Int': PropertySchema( - id: 142, + id: 143, name: r'textSubtitle3Int', type: IsarType.long, ), r'textSubtitle4Int': PropertySchema( - id: 143, + id: 144, name: r'textSubtitle4Int', type: IsarType.long, ), r'textSubtitle5Int': PropertySchema( - id: 144, + id: 145, name: r'textSubtitle5Int', type: IsarType.long, ), r'textSubtitle6Int': PropertySchema( - id: 145, + id: 146, name: r'textSubtitle6Int', type: IsarType.long, ), r'textWhiteInt': PropertySchema( - id: 146, + id: 147, name: r'textWhiteInt', type: IsarType.long, ), r'themeId': PropertySchema( - id: 147, + id: 148, name: r'themeId', type: IsarType.string, ), r'tokenSummaryBGInt': PropertySchema( - id: 148, + id: 149, name: r'tokenSummaryBGInt', type: IsarType.long, ), r'tokenSummaryButtonBGInt': PropertySchema( - id: 149, + id: 150, name: r'tokenSummaryButtonBGInt', type: IsarType.long, ), r'tokenSummaryIconInt': PropertySchema( - id: 150, + id: 151, name: r'tokenSummaryIconInt', type: IsarType.long, ), r'tokenSummaryTextPrimaryInt': PropertySchema( - id: 151, + id: 152, name: r'tokenSummaryTextPrimaryInt', type: IsarType.long, ), r'tokenSummaryTextSecondaryInt': PropertySchema( - id: 152, + id: 153, name: r'tokenSummaryTextSecondaryInt', type: IsarType.long, ), r'topNavIconGreenInt': PropertySchema( - id: 153, + id: 154, name: r'topNavIconGreenInt', type: IsarType.long, ), r'topNavIconPrimaryInt': PropertySchema( - id: 154, + id: 155, name: r'topNavIconPrimaryInt', type: IsarType.long, ), r'topNavIconRedInt': PropertySchema( - id: 155, + id: 156, name: r'topNavIconRedInt', type: IsarType.long, ), r'topNavIconYellowInt': PropertySchema( - id: 156, + id: 157, name: r'topNavIconYellowInt', type: IsarType.long, ), + r'version': PropertySchema( + id: 158, + name: r'version', + type: IsarType.long, + ), r'warningBackgroundInt': PropertySchema( - id: 157, + id: 159, name: r'warningBackgroundInt', type: IsarType.long, ), r'warningForegroundInt': PropertySchema( - id: 158, + id: 160, name: r'warningForegroundInt', type: IsarType.long, ) @@ -835,7 +846,10 @@ const StackThemeSchema = CollectionSchema( ) }, links: {}, - embeddedSchemas: {r'ThemeAssets': ThemeAssetsSchema}, + embeddedSchemas: { + r'ThemeAssets': ThemeAssetsSchema, + r'ThemeAssetsV2': ThemeAssetsV2Schema + }, getId: _stackThemeGetId, getLinks: _stackThemeGetLinks, attach: _stackThemeAttach, @@ -848,9 +862,22 @@ int _stackThemeEstimateSize( Map> allOffsets, ) { var bytesCount = offsets.last; - bytesCount += 3 + - ThemeAssetsSchema.estimateSize( - object.assets, allOffsets[ThemeAssets]!, allOffsets); + { + final value = object.assetsV1; + if (value != null) { + bytesCount += 3 + + ThemeAssetsSchema.estimateSize( + value, allOffsets[ThemeAssets]!, allOffsets); + } + } + { + final value = object.assetsV2; + if (value != null) { + bytesCount += 3 + + ThemeAssetsV2Schema.estimateSize( + value, allOffsets[ThemeAssetsV2]!, allOffsets); + } + } bytesCount += 3 + object.brightnessString.length * 3; bytesCount += 3 + object.coinColorsJsonString.length * 3; { @@ -887,160 +914,167 @@ void _stackThemeSerialize( offsets[6], allOffsets, ThemeAssetsSchema.serialize, - object.assets, + object.assetsV1, ); - writer.writeLong(offsets[7], object.backgroundAppBarInt); - writer.writeLong(offsets[8], object.backgroundInt); - writer.writeLong(offsets[9], object.bottomNavBackInt); - writer.writeLong(offsets[10], object.bottomNavIconBackInt); - writer.writeLong(offsets[11], object.bottomNavIconIconHighlightedInt); - writer.writeLong(offsets[12], object.bottomNavIconIconInt); - writer.writeLong(offsets[13], object.bottomNavShadowInt); - writer.writeLong(offsets[14], object.bottomNavTextInt); - writer.writeString(offsets[15], object.brightnessString); - writer.writeLong(offsets[16], object.buttonBackBorderDisabledInt); - writer.writeLong(offsets[17], object.buttonBackBorderInt); - writer.writeLong(offsets[18], object.buttonBackBorderSecondaryDisabledInt); - writer.writeLong(offsets[19], object.buttonBackBorderSecondaryInt); - writer.writeLong(offsets[20], object.buttonBackPrimaryDisabledInt); - writer.writeLong(offsets[21], object.buttonBackPrimaryInt); - writer.writeLong(offsets[22], object.buttonBackSecondaryDisabledInt); - writer.writeLong(offsets[23], object.buttonBackSecondaryInt); - writer.writeLong(offsets[24], object.buttonTextBorderInt); - writer.writeLong(offsets[25], object.buttonTextBorderlessDisabledInt); - writer.writeLong(offsets[26], object.buttonTextBorderlessInt); - writer.writeLong(offsets[27], object.buttonTextDisabledInt); - writer.writeLong(offsets[28], object.buttonTextPrimaryDisabledInt); - writer.writeLong(offsets[29], object.buttonTextPrimaryInt); - writer.writeLong(offsets[30], object.buttonTextSecondaryDisabledInt); - writer.writeLong(offsets[31], object.buttonTextSecondaryInt); - writer.writeLong(offsets[32], object.checkboxBGCheckedInt); - writer.writeLong(offsets[33], object.checkboxBGDisabledInt); - writer.writeLong(offsets[34], object.checkboxBorderEmptyInt); - writer.writeLong(offsets[35], object.checkboxIconCheckedInt); - writer.writeLong(offsets[36], object.checkboxIconDisabledInt); - writer.writeLong(offsets[37], object.checkboxTextLabelInt); - writer.writeString(offsets[38], object.coinColorsJsonString); - writer.writeLong(offsets[39], object.currencyListItemBGInt); - writer.writeLong(offsets[40], object.customTextButtonDisabledTextInt); - writer.writeLong(offsets[41], object.customTextButtonEnabledTextInt); - writer.writeLong(offsets[42], object.ethTagBGInt); - writer.writeLong(offsets[43], object.ethTagTextInt); - writer.writeLong(offsets[44], object.ethWalletTagBGInt); - writer.writeLong(offsets[45], object.ethWalletTagTextInt); - writer.writeLong(offsets[46], object.favoriteStarActiveInt); - writer.writeLong(offsets[47], object.favoriteStarInactiveInt); - writer.writeString(offsets[48], object.gradientBackgroundString); - writer.writeLong(offsets[49], object.highlightInt); - writer.writeString(offsets[50], object.homeViewButtonBarBoxShadowString); - writer.writeLong(offsets[51], object.infoItemBGInt); - writer.writeLong(offsets[52], object.infoItemIconsInt); - writer.writeLong(offsets[53], object.infoItemLabelInt); - writer.writeLong(offsets[54], object.infoItemTextInt); - writer.writeLong(offsets[55], object.loadingOverlayTextColorInt); - writer.writeLong(offsets[56], object.myStackContactIconBGInt); - writer.writeString(offsets[57], object.name); - writer.writeLong(offsets[58], object.numberBackDefaultInt); - writer.writeLong(offsets[59], object.numberTextDefaultInt); - writer.writeLong(offsets[60], object.numpadBackDefaultInt); - writer.writeLong(offsets[61], object.numpadTextDefaultInt); - writer.writeLong(offsets[62], object.overlayInt); - writer.writeLong(offsets[63], object.popupBGInt); - writer.writeLong(offsets[64], object.radioButtonBorderDisabledInt); - writer.writeLong(offsets[65], object.radioButtonBorderEnabledInt); - writer.writeLong(offsets[66], object.radioButtonIconBorderDisabledInt); - writer.writeLong(offsets[67], object.radioButtonIconBorderInt); - writer.writeLong(offsets[68], object.radioButtonIconCircleInt); - writer.writeLong(offsets[69], object.radioButtonIconEnabledInt); - writer.writeLong(offsets[70], object.radioButtonLabelDisabledInt); - writer.writeLong(offsets[71], object.radioButtonLabelEnabledInt); - writer.writeLong(offsets[72], object.radioButtonTextDisabledInt); - writer.writeLong(offsets[73], object.radioButtonTextEnabledInt); - writer.writeLong(offsets[74], object.rateTypeToggleColorOffInt); - writer.writeLong(offsets[75], object.rateTypeToggleColorOnInt); - writer.writeLong(offsets[76], object.rateTypeToggleDesktopColorOffInt); - writer.writeLong(offsets[77], object.rateTypeToggleDesktopColorOnInt); - writer.writeLong(offsets[78], object.settingsIconBack2Int); - writer.writeLong(offsets[79], object.settingsIconBackInt); - writer.writeLong(offsets[80], object.settingsIconElementInt); - writer.writeLong(offsets[81], object.settingsIconIconInt); - writer.writeLong(offsets[82], object.settingsItem2ActiveBGInt); - writer.writeLong(offsets[83], object.settingsItem2ActiveSubInt); - writer.writeLong(offsets[84], object.settingsItem2ActiveTextInt); - writer.writeLong(offsets[85], object.shadowInt); - writer.writeLong(offsets[86], object.snackBarBackErrorInt); - writer.writeLong(offsets[87], object.snackBarBackInfoInt); - writer.writeLong(offsets[88], object.snackBarBackSuccessInt); - writer.writeLong(offsets[89], object.snackBarTextErrorInt); - writer.writeLong(offsets[90], object.snackBarTextInfoInt); - writer.writeLong(offsets[91], object.snackBarTextSuccessInt); - writer.writeLong(offsets[92], object.splashInt); - writer.writeLong(offsets[93], object.stackWalletBGInt); - writer.writeLong(offsets[94], object.stackWalletBottomInt); - writer.writeLong(offsets[95], object.stackWalletMidInt); - writer.writeString(offsets[96], object.standardBoxShadowString); - writer.writeLong(offsets[97], object.stepIndicatorBGCheckInt); - writer.writeLong(offsets[98], object.stepIndicatorBGInactiveInt); - writer.writeLong(offsets[99], object.stepIndicatorBGLinesInactiveInt); - writer.writeLong(offsets[100], object.stepIndicatorBGLinesInt); - writer.writeLong(offsets[101], object.stepIndicatorBGNumberInt); - writer.writeLong(offsets[102], object.stepIndicatorIconInactiveInt); - writer.writeLong(offsets[103], object.stepIndicatorIconNumberInt); - writer.writeLong(offsets[104], object.stepIndicatorIconTextInt); - writer.writeLong(offsets[105], object.switchBGDisabledInt); - writer.writeLong(offsets[106], object.switchBGOffInt); - writer.writeLong(offsets[107], object.switchBGOnInt); - writer.writeLong(offsets[108], object.switchCircleDisabledInt); - writer.writeLong(offsets[109], object.switchCircleOffInt); - writer.writeLong(offsets[110], object.switchCircleOnInt); - writer.writeLong(offsets[111], object.textConfirmTotalAmountInt); - writer.writeLong(offsets[112], object.textDark2Int); - writer.writeLong(offsets[113], object.textDark3Int); - writer.writeLong(offsets[114], object.textDarkInt); - writer.writeLong(offsets[115], object.textErrorInt); - writer.writeLong(offsets[116], object.textFavoriteCardInt); - writer.writeLong(offsets[117], object.textFieldActiveBGInt); - writer.writeLong(offsets[118], object.textFieldActiveLabelInt); - writer.writeLong(offsets[119], object.textFieldActiveSearchIconLeftInt); - writer.writeLong(offsets[120], object.textFieldActiveSearchIconRightInt); - writer.writeLong(offsets[121], object.textFieldActiveTextInt); - writer.writeLong(offsets[122], object.textFieldDefaultBGInt); - writer.writeLong(offsets[123], object.textFieldDefaultSearchIconLeftInt); - writer.writeLong(offsets[124], object.textFieldDefaultSearchIconRightInt); - writer.writeLong(offsets[125], object.textFieldDefaultTextInt); - writer.writeLong(offsets[126], object.textFieldErrorBGInt); - writer.writeLong(offsets[127], object.textFieldErrorBorderInt); - writer.writeLong(offsets[128], object.textFieldErrorLabelInt); - writer.writeLong(offsets[129], object.textFieldErrorSearchIconLeftInt); - writer.writeLong(offsets[130], object.textFieldErrorSearchIconRightInt); - writer.writeLong(offsets[131], object.textFieldErrorTextInt); - writer.writeLong(offsets[132], object.textFieldSuccessBGInt); - writer.writeLong(offsets[133], object.textFieldSuccessBorderInt); - writer.writeLong(offsets[134], object.textFieldSuccessLabelInt); - writer.writeLong(offsets[135], object.textFieldSuccessSearchIconLeftInt); - writer.writeLong(offsets[136], object.textFieldSuccessSearchIconRightInt); - writer.writeLong(offsets[137], object.textFieldSuccessTextInt); - writer.writeLong(offsets[138], object.textRestoreInt); - writer.writeLong(offsets[139], object.textSelectedWordTableItemInt); - writer.writeLong(offsets[140], object.textSubtitle1Int); - writer.writeLong(offsets[141], object.textSubtitle2Int); - writer.writeLong(offsets[142], object.textSubtitle3Int); - writer.writeLong(offsets[143], object.textSubtitle4Int); - writer.writeLong(offsets[144], object.textSubtitle5Int); - writer.writeLong(offsets[145], object.textSubtitle6Int); - writer.writeLong(offsets[146], object.textWhiteInt); - writer.writeString(offsets[147], object.themeId); - writer.writeLong(offsets[148], object.tokenSummaryBGInt); - writer.writeLong(offsets[149], object.tokenSummaryButtonBGInt); - writer.writeLong(offsets[150], object.tokenSummaryIconInt); - writer.writeLong(offsets[151], object.tokenSummaryTextPrimaryInt); - writer.writeLong(offsets[152], object.tokenSummaryTextSecondaryInt); - writer.writeLong(offsets[153], object.topNavIconGreenInt); - writer.writeLong(offsets[154], object.topNavIconPrimaryInt); - writer.writeLong(offsets[155], object.topNavIconRedInt); - writer.writeLong(offsets[156], object.topNavIconYellowInt); - writer.writeLong(offsets[157], object.warningBackgroundInt); - writer.writeLong(offsets[158], object.warningForegroundInt); + writer.writeObject( + offsets[7], + allOffsets, + ThemeAssetsV2Schema.serialize, + object.assetsV2, + ); + writer.writeLong(offsets[8], object.backgroundAppBarInt); + writer.writeLong(offsets[9], object.backgroundInt); + writer.writeLong(offsets[10], object.bottomNavBackInt); + writer.writeLong(offsets[11], object.bottomNavIconBackInt); + writer.writeLong(offsets[12], object.bottomNavIconIconHighlightedInt); + writer.writeLong(offsets[13], object.bottomNavIconIconInt); + writer.writeLong(offsets[14], object.bottomNavShadowInt); + writer.writeLong(offsets[15], object.bottomNavTextInt); + writer.writeString(offsets[16], object.brightnessString); + writer.writeLong(offsets[17], object.buttonBackBorderDisabledInt); + writer.writeLong(offsets[18], object.buttonBackBorderInt); + writer.writeLong(offsets[19], object.buttonBackBorderSecondaryDisabledInt); + writer.writeLong(offsets[20], object.buttonBackBorderSecondaryInt); + writer.writeLong(offsets[21], object.buttonBackPrimaryDisabledInt); + writer.writeLong(offsets[22], object.buttonBackPrimaryInt); + writer.writeLong(offsets[23], object.buttonBackSecondaryDisabledInt); + writer.writeLong(offsets[24], object.buttonBackSecondaryInt); + writer.writeLong(offsets[25], object.buttonTextBorderInt); + writer.writeLong(offsets[26], object.buttonTextBorderlessDisabledInt); + writer.writeLong(offsets[27], object.buttonTextBorderlessInt); + writer.writeLong(offsets[28], object.buttonTextDisabledInt); + writer.writeLong(offsets[29], object.buttonTextPrimaryDisabledInt); + writer.writeLong(offsets[30], object.buttonTextPrimaryInt); + writer.writeLong(offsets[31], object.buttonTextSecondaryDisabledInt); + writer.writeLong(offsets[32], object.buttonTextSecondaryInt); + writer.writeLong(offsets[33], object.checkboxBGCheckedInt); + writer.writeLong(offsets[34], object.checkboxBGDisabledInt); + writer.writeLong(offsets[35], object.checkboxBorderEmptyInt); + writer.writeLong(offsets[36], object.checkboxIconCheckedInt); + writer.writeLong(offsets[37], object.checkboxIconDisabledInt); + writer.writeLong(offsets[38], object.checkboxTextLabelInt); + writer.writeString(offsets[39], object.coinColorsJsonString); + writer.writeLong(offsets[40], object.currencyListItemBGInt); + writer.writeLong(offsets[41], object.customTextButtonDisabledTextInt); + writer.writeLong(offsets[42], object.customTextButtonEnabledTextInt); + writer.writeLong(offsets[43], object.ethTagBGInt); + writer.writeLong(offsets[44], object.ethTagTextInt); + writer.writeLong(offsets[45], object.ethWalletTagBGInt); + writer.writeLong(offsets[46], object.ethWalletTagTextInt); + writer.writeLong(offsets[47], object.favoriteStarActiveInt); + writer.writeLong(offsets[48], object.favoriteStarInactiveInt); + writer.writeString(offsets[49], object.gradientBackgroundString); + writer.writeLong(offsets[50], object.highlightInt); + writer.writeString(offsets[51], object.homeViewButtonBarBoxShadowString); + writer.writeLong(offsets[52], object.infoItemBGInt); + writer.writeLong(offsets[53], object.infoItemIconsInt); + writer.writeLong(offsets[54], object.infoItemLabelInt); + writer.writeLong(offsets[55], object.infoItemTextInt); + writer.writeLong(offsets[56], object.loadingOverlayTextColorInt); + writer.writeLong(offsets[57], object.myStackContactIconBGInt); + writer.writeString(offsets[58], object.name); + writer.writeLong(offsets[59], object.numberBackDefaultInt); + writer.writeLong(offsets[60], object.numberTextDefaultInt); + writer.writeLong(offsets[61], object.numpadBackDefaultInt); + writer.writeLong(offsets[62], object.numpadTextDefaultInt); + writer.writeLong(offsets[63], object.overlayInt); + writer.writeLong(offsets[64], object.popupBGInt); + writer.writeLong(offsets[65], object.radioButtonBorderDisabledInt); + writer.writeLong(offsets[66], object.radioButtonBorderEnabledInt); + writer.writeLong(offsets[67], object.radioButtonIconBorderDisabledInt); + writer.writeLong(offsets[68], object.radioButtonIconBorderInt); + writer.writeLong(offsets[69], object.radioButtonIconCircleInt); + writer.writeLong(offsets[70], object.radioButtonIconEnabledInt); + writer.writeLong(offsets[71], object.radioButtonLabelDisabledInt); + writer.writeLong(offsets[72], object.radioButtonLabelEnabledInt); + writer.writeLong(offsets[73], object.radioButtonTextDisabledInt); + writer.writeLong(offsets[74], object.radioButtonTextEnabledInt); + writer.writeLong(offsets[75], object.rateTypeToggleColorOffInt); + writer.writeLong(offsets[76], object.rateTypeToggleColorOnInt); + writer.writeLong(offsets[77], object.rateTypeToggleDesktopColorOffInt); + writer.writeLong(offsets[78], object.rateTypeToggleDesktopColorOnInt); + writer.writeLong(offsets[79], object.settingsIconBack2Int); + writer.writeLong(offsets[80], object.settingsIconBackInt); + writer.writeLong(offsets[81], object.settingsIconElementInt); + writer.writeLong(offsets[82], object.settingsIconIconInt); + writer.writeLong(offsets[83], object.settingsItem2ActiveBGInt); + writer.writeLong(offsets[84], object.settingsItem2ActiveSubInt); + writer.writeLong(offsets[85], object.settingsItem2ActiveTextInt); + writer.writeLong(offsets[86], object.shadowInt); + writer.writeLong(offsets[87], object.snackBarBackErrorInt); + writer.writeLong(offsets[88], object.snackBarBackInfoInt); + writer.writeLong(offsets[89], object.snackBarBackSuccessInt); + writer.writeLong(offsets[90], object.snackBarTextErrorInt); + writer.writeLong(offsets[91], object.snackBarTextInfoInt); + writer.writeLong(offsets[92], object.snackBarTextSuccessInt); + writer.writeLong(offsets[93], object.splashInt); + writer.writeLong(offsets[94], object.stackWalletBGInt); + writer.writeLong(offsets[95], object.stackWalletBottomInt); + writer.writeLong(offsets[96], object.stackWalletMidInt); + writer.writeString(offsets[97], object.standardBoxShadowString); + writer.writeLong(offsets[98], object.stepIndicatorBGCheckInt); + writer.writeLong(offsets[99], object.stepIndicatorBGInactiveInt); + writer.writeLong(offsets[100], object.stepIndicatorBGLinesInactiveInt); + writer.writeLong(offsets[101], object.stepIndicatorBGLinesInt); + writer.writeLong(offsets[102], object.stepIndicatorBGNumberInt); + writer.writeLong(offsets[103], object.stepIndicatorIconInactiveInt); + writer.writeLong(offsets[104], object.stepIndicatorIconNumberInt); + writer.writeLong(offsets[105], object.stepIndicatorIconTextInt); + writer.writeLong(offsets[106], object.switchBGDisabledInt); + writer.writeLong(offsets[107], object.switchBGOffInt); + writer.writeLong(offsets[108], object.switchBGOnInt); + writer.writeLong(offsets[109], object.switchCircleDisabledInt); + writer.writeLong(offsets[110], object.switchCircleOffInt); + writer.writeLong(offsets[111], object.switchCircleOnInt); + writer.writeLong(offsets[112], object.textConfirmTotalAmountInt); + writer.writeLong(offsets[113], object.textDark2Int); + writer.writeLong(offsets[114], object.textDark3Int); + writer.writeLong(offsets[115], object.textDarkInt); + writer.writeLong(offsets[116], object.textErrorInt); + writer.writeLong(offsets[117], object.textFavoriteCardInt); + writer.writeLong(offsets[118], object.textFieldActiveBGInt); + writer.writeLong(offsets[119], object.textFieldActiveLabelInt); + writer.writeLong(offsets[120], object.textFieldActiveSearchIconLeftInt); + writer.writeLong(offsets[121], object.textFieldActiveSearchIconRightInt); + writer.writeLong(offsets[122], object.textFieldActiveTextInt); + writer.writeLong(offsets[123], object.textFieldDefaultBGInt); + writer.writeLong(offsets[124], object.textFieldDefaultSearchIconLeftInt); + writer.writeLong(offsets[125], object.textFieldDefaultSearchIconRightInt); + writer.writeLong(offsets[126], object.textFieldDefaultTextInt); + writer.writeLong(offsets[127], object.textFieldErrorBGInt); + writer.writeLong(offsets[128], object.textFieldErrorBorderInt); + writer.writeLong(offsets[129], object.textFieldErrorLabelInt); + writer.writeLong(offsets[130], object.textFieldErrorSearchIconLeftInt); + writer.writeLong(offsets[131], object.textFieldErrorSearchIconRightInt); + writer.writeLong(offsets[132], object.textFieldErrorTextInt); + writer.writeLong(offsets[133], object.textFieldSuccessBGInt); + writer.writeLong(offsets[134], object.textFieldSuccessBorderInt); + writer.writeLong(offsets[135], object.textFieldSuccessLabelInt); + writer.writeLong(offsets[136], object.textFieldSuccessSearchIconLeftInt); + writer.writeLong(offsets[137], object.textFieldSuccessSearchIconRightInt); + writer.writeLong(offsets[138], object.textFieldSuccessTextInt); + writer.writeLong(offsets[139], object.textRestoreInt); + writer.writeLong(offsets[140], object.textSelectedWordTableItemInt); + writer.writeLong(offsets[141], object.textSubtitle1Int); + writer.writeLong(offsets[142], object.textSubtitle2Int); + writer.writeLong(offsets[143], object.textSubtitle3Int); + writer.writeLong(offsets[144], object.textSubtitle4Int); + writer.writeLong(offsets[145], object.textSubtitle5Int); + writer.writeLong(offsets[146], object.textSubtitle6Int); + writer.writeLong(offsets[147], object.textWhiteInt); + writer.writeString(offsets[148], object.themeId); + writer.writeLong(offsets[149], object.tokenSummaryBGInt); + writer.writeLong(offsets[150], object.tokenSummaryButtonBGInt); + writer.writeLong(offsets[151], object.tokenSummaryIconInt); + writer.writeLong(offsets[152], object.tokenSummaryTextPrimaryInt); + writer.writeLong(offsets[153], object.tokenSummaryTextSecondaryInt); + writer.writeLong(offsets[154], object.topNavIconGreenInt); + writer.writeLong(offsets[155], object.topNavIconPrimaryInt); + writer.writeLong(offsets[156], object.topNavIconRedInt); + writer.writeLong(offsets[157], object.topNavIconYellowInt); + writer.writeLong(offsets[158], object.version); + writer.writeLong(offsets[159], object.warningBackgroundInt); + writer.writeLong(offsets[160], object.warningForegroundInt); } StackTheme _stackThemeDeserialize( @@ -1049,173 +1083,178 @@ StackTheme _stackThemeDeserialize( List offsets, Map> allOffsets, ) { - final object = StackTheme( - accentColorBlueInt: reader.readLong(offsets[0]), - accentColorDarkInt: reader.readLong(offsets[1]), - accentColorGreenInt: reader.readLong(offsets[2]), - accentColorOrangeInt: reader.readLong(offsets[3]), - accentColorRedInt: reader.readLong(offsets[4]), - accentColorYellowInt: reader.readLong(offsets[5]), - assets: reader.readObjectOrNull( - offsets[6], - ThemeAssetsSchema.deserialize, - allOffsets, - ) ?? - ThemeAssets(), - backgroundAppBarInt: reader.readLong(offsets[7]), - backgroundInt: reader.readLong(offsets[8]), - bottomNavBackInt: reader.readLong(offsets[9]), - bottomNavIconBackInt: reader.readLong(offsets[10]), - bottomNavIconIconHighlightedInt: reader.readLong(offsets[11]), - bottomNavIconIconInt: reader.readLong(offsets[12]), - bottomNavShadowInt: reader.readLong(offsets[13]), - bottomNavTextInt: reader.readLong(offsets[14]), - brightnessString: reader.readString(offsets[15]), - buttonBackBorderDisabledInt: reader.readLong(offsets[16]), - buttonBackBorderInt: reader.readLong(offsets[17]), - buttonBackBorderSecondaryDisabledInt: reader.readLong(offsets[18]), - buttonBackBorderSecondaryInt: reader.readLong(offsets[19]), - buttonBackPrimaryDisabledInt: reader.readLong(offsets[20]), - buttonBackPrimaryInt: reader.readLong(offsets[21]), - buttonBackSecondaryDisabledInt: reader.readLong(offsets[22]), - buttonBackSecondaryInt: reader.readLong(offsets[23]), - buttonTextBorderInt: reader.readLong(offsets[24]), - buttonTextBorderlessDisabledInt: reader.readLong(offsets[25]), - buttonTextBorderlessInt: reader.readLong(offsets[26]), - buttonTextDisabledInt: reader.readLong(offsets[27]), - buttonTextPrimaryDisabledInt: reader.readLong(offsets[28]), - buttonTextPrimaryInt: reader.readLong(offsets[29]), - buttonTextSecondaryDisabledInt: reader.readLong(offsets[30]), - buttonTextSecondaryInt: reader.readLong(offsets[31]), - checkboxBGCheckedInt: reader.readLong(offsets[32]), - checkboxBGDisabledInt: reader.readLong(offsets[33]), - checkboxBorderEmptyInt: reader.readLong(offsets[34]), - checkboxIconCheckedInt: reader.readLong(offsets[35]), - checkboxIconDisabledInt: reader.readLong(offsets[36]), - checkboxTextLabelInt: reader.readLong(offsets[37]), - coinColorsJsonString: reader.readString(offsets[38]), - currencyListItemBGInt: reader.readLong(offsets[39]), - customTextButtonDisabledTextInt: reader.readLong(offsets[40]), - customTextButtonEnabledTextInt: reader.readLong(offsets[41]), - ethTagBGInt: reader.readLong(offsets[42]), - ethTagTextInt: reader.readLong(offsets[43]), - ethWalletTagBGInt: reader.readLong(offsets[44]), - ethWalletTagTextInt: reader.readLong(offsets[45]), - favoriteStarActiveInt: reader.readLong(offsets[46]), - favoriteStarInactiveInt: reader.readLong(offsets[47]), - gradientBackgroundString: reader.readStringOrNull(offsets[48]), - highlightInt: reader.readLong(offsets[49]), - homeViewButtonBarBoxShadowString: reader.readStringOrNull(offsets[50]), - infoItemBGInt: reader.readLong(offsets[51]), - infoItemIconsInt: reader.readLong(offsets[52]), - infoItemLabelInt: reader.readLong(offsets[53]), - infoItemTextInt: reader.readLong(offsets[54]), - loadingOverlayTextColorInt: reader.readLong(offsets[55]), - myStackContactIconBGInt: reader.readLong(offsets[56]), - name: reader.readString(offsets[57]), - numberBackDefaultInt: reader.readLong(offsets[58]), - numberTextDefaultInt: reader.readLong(offsets[59]), - numpadBackDefaultInt: reader.readLong(offsets[60]), - numpadTextDefaultInt: reader.readLong(offsets[61]), - overlayInt: reader.readLong(offsets[62]), - popupBGInt: reader.readLong(offsets[63]), - radioButtonBorderDisabledInt: reader.readLong(offsets[64]), - radioButtonBorderEnabledInt: reader.readLong(offsets[65]), - radioButtonIconBorderDisabledInt: reader.readLong(offsets[66]), - radioButtonIconBorderInt: reader.readLong(offsets[67]), - radioButtonIconCircleInt: reader.readLong(offsets[68]), - radioButtonIconEnabledInt: reader.readLong(offsets[69]), - radioButtonLabelDisabledInt: reader.readLong(offsets[70]), - radioButtonLabelEnabledInt: reader.readLong(offsets[71]), - radioButtonTextDisabledInt: reader.readLong(offsets[72]), - radioButtonTextEnabledInt: reader.readLong(offsets[73]), - rateTypeToggleColorOffInt: reader.readLong(offsets[74]), - rateTypeToggleColorOnInt: reader.readLong(offsets[75]), - rateTypeToggleDesktopColorOffInt: reader.readLong(offsets[76]), - rateTypeToggleDesktopColorOnInt: reader.readLong(offsets[77]), - settingsIconBack2Int: reader.readLong(offsets[78]), - settingsIconBackInt: reader.readLong(offsets[79]), - settingsIconElementInt: reader.readLong(offsets[80]), - settingsIconIconInt: reader.readLong(offsets[81]), - settingsItem2ActiveBGInt: reader.readLong(offsets[82]), - settingsItem2ActiveSubInt: reader.readLong(offsets[83]), - settingsItem2ActiveTextInt: reader.readLong(offsets[84]), - shadowInt: reader.readLong(offsets[85]), - snackBarBackErrorInt: reader.readLong(offsets[86]), - snackBarBackInfoInt: reader.readLong(offsets[87]), - snackBarBackSuccessInt: reader.readLong(offsets[88]), - snackBarTextErrorInt: reader.readLong(offsets[89]), - snackBarTextInfoInt: reader.readLong(offsets[90]), - snackBarTextSuccessInt: reader.readLong(offsets[91]), - splashInt: reader.readLong(offsets[92]), - stackWalletBGInt: reader.readLong(offsets[93]), - stackWalletBottomInt: reader.readLong(offsets[94]), - stackWalletMidInt: reader.readLong(offsets[95]), - standardBoxShadowString: reader.readString(offsets[96]), - stepIndicatorBGCheckInt: reader.readLong(offsets[97]), - stepIndicatorBGInactiveInt: reader.readLong(offsets[98]), - stepIndicatorBGLinesInactiveInt: reader.readLong(offsets[99]), - stepIndicatorBGLinesInt: reader.readLong(offsets[100]), - stepIndicatorBGNumberInt: reader.readLong(offsets[101]), - stepIndicatorIconInactiveInt: reader.readLong(offsets[102]), - stepIndicatorIconNumberInt: reader.readLong(offsets[103]), - stepIndicatorIconTextInt: reader.readLong(offsets[104]), - switchBGDisabledInt: reader.readLong(offsets[105]), - switchBGOffInt: reader.readLong(offsets[106]), - switchBGOnInt: reader.readLong(offsets[107]), - switchCircleDisabledInt: reader.readLong(offsets[108]), - switchCircleOffInt: reader.readLong(offsets[109]), - switchCircleOnInt: reader.readLong(offsets[110]), - textConfirmTotalAmountInt: reader.readLong(offsets[111]), - textDark2Int: reader.readLong(offsets[112]), - textDark3Int: reader.readLong(offsets[113]), - textDarkInt: reader.readLong(offsets[114]), - textErrorInt: reader.readLong(offsets[115]), - textFavoriteCardInt: reader.readLong(offsets[116]), - textFieldActiveBGInt: reader.readLong(offsets[117]), - textFieldActiveLabelInt: reader.readLong(offsets[118]), - textFieldActiveSearchIconLeftInt: reader.readLong(offsets[119]), - textFieldActiveSearchIconRightInt: reader.readLong(offsets[120]), - textFieldActiveTextInt: reader.readLong(offsets[121]), - textFieldDefaultBGInt: reader.readLong(offsets[122]), - textFieldDefaultSearchIconLeftInt: reader.readLong(offsets[123]), - textFieldDefaultSearchIconRightInt: reader.readLong(offsets[124]), - textFieldDefaultTextInt: reader.readLong(offsets[125]), - textFieldErrorBGInt: reader.readLong(offsets[126]), - textFieldErrorBorderInt: reader.readLong(offsets[127]), - textFieldErrorLabelInt: reader.readLong(offsets[128]), - textFieldErrorSearchIconLeftInt: reader.readLong(offsets[129]), - textFieldErrorSearchIconRightInt: reader.readLong(offsets[130]), - textFieldErrorTextInt: reader.readLong(offsets[131]), - textFieldSuccessBGInt: reader.readLong(offsets[132]), - textFieldSuccessBorderInt: reader.readLong(offsets[133]), - textFieldSuccessLabelInt: reader.readLong(offsets[134]), - textFieldSuccessSearchIconLeftInt: reader.readLong(offsets[135]), - textFieldSuccessSearchIconRightInt: reader.readLong(offsets[136]), - textFieldSuccessTextInt: reader.readLong(offsets[137]), - textRestoreInt: reader.readLong(offsets[138]), - textSelectedWordTableItemInt: reader.readLong(offsets[139]), - textSubtitle1Int: reader.readLong(offsets[140]), - textSubtitle2Int: reader.readLong(offsets[141]), - textSubtitle3Int: reader.readLong(offsets[142]), - textSubtitle4Int: reader.readLong(offsets[143]), - textSubtitle5Int: reader.readLong(offsets[144]), - textSubtitle6Int: reader.readLong(offsets[145]), - textWhiteInt: reader.readLong(offsets[146]), - themeId: reader.readString(offsets[147]), - tokenSummaryBGInt: reader.readLong(offsets[148]), - tokenSummaryButtonBGInt: reader.readLong(offsets[149]), - tokenSummaryIconInt: reader.readLong(offsets[150]), - tokenSummaryTextPrimaryInt: reader.readLong(offsets[151]), - tokenSummaryTextSecondaryInt: reader.readLong(offsets[152]), - topNavIconGreenInt: reader.readLong(offsets[153]), - topNavIconPrimaryInt: reader.readLong(offsets[154]), - topNavIconRedInt: reader.readLong(offsets[155]), - topNavIconYellowInt: reader.readLong(offsets[156]), - warningBackgroundInt: reader.readLong(offsets[157]), - warningForegroundInt: reader.readLong(offsets[158]), + final object = StackTheme(); + object.accentColorBlueInt = reader.readLong(offsets[0]); + object.accentColorDarkInt = reader.readLong(offsets[1]); + object.accentColorGreenInt = reader.readLong(offsets[2]); + object.accentColorOrangeInt = reader.readLong(offsets[3]); + object.accentColorRedInt = reader.readLong(offsets[4]); + object.accentColorYellowInt = reader.readLong(offsets[5]); + object.assetsV1 = reader.readObjectOrNull( + offsets[6], + ThemeAssetsSchema.deserialize, + allOffsets, ); + object.assetsV2 = reader.readObjectOrNull( + offsets[7], + ThemeAssetsV2Schema.deserialize, + allOffsets, + ); + object.backgroundAppBarInt = reader.readLong(offsets[8]); + object.backgroundInt = reader.readLong(offsets[9]); + object.bottomNavBackInt = reader.readLong(offsets[10]); + object.bottomNavIconBackInt = reader.readLong(offsets[11]); + object.bottomNavIconIconHighlightedInt = reader.readLong(offsets[12]); + object.bottomNavIconIconInt = reader.readLong(offsets[13]); + object.bottomNavShadowInt = reader.readLong(offsets[14]); + object.bottomNavTextInt = reader.readLong(offsets[15]); + object.brightnessString = reader.readString(offsets[16]); + object.buttonBackBorderDisabledInt = reader.readLong(offsets[17]); + object.buttonBackBorderInt = reader.readLong(offsets[18]); + object.buttonBackBorderSecondaryDisabledInt = reader.readLong(offsets[19]); + object.buttonBackBorderSecondaryInt = reader.readLong(offsets[20]); + object.buttonBackPrimaryDisabledInt = reader.readLong(offsets[21]); + object.buttonBackPrimaryInt = reader.readLong(offsets[22]); + object.buttonBackSecondaryDisabledInt = reader.readLong(offsets[23]); + object.buttonBackSecondaryInt = reader.readLong(offsets[24]); + object.buttonTextBorderInt = reader.readLong(offsets[25]); + object.buttonTextBorderlessDisabledInt = reader.readLong(offsets[26]); + object.buttonTextBorderlessInt = reader.readLong(offsets[27]); + object.buttonTextDisabledInt = reader.readLong(offsets[28]); + object.buttonTextPrimaryDisabledInt = reader.readLong(offsets[29]); + object.buttonTextPrimaryInt = reader.readLong(offsets[30]); + object.buttonTextSecondaryDisabledInt = reader.readLong(offsets[31]); + object.buttonTextSecondaryInt = reader.readLong(offsets[32]); + object.checkboxBGCheckedInt = reader.readLong(offsets[33]); + object.checkboxBGDisabledInt = reader.readLong(offsets[34]); + object.checkboxBorderEmptyInt = reader.readLong(offsets[35]); + object.checkboxIconCheckedInt = reader.readLong(offsets[36]); + object.checkboxIconDisabledInt = reader.readLong(offsets[37]); + object.checkboxTextLabelInt = reader.readLong(offsets[38]); + object.coinColorsJsonString = reader.readString(offsets[39]); + object.currencyListItemBGInt = reader.readLong(offsets[40]); + object.customTextButtonDisabledTextInt = reader.readLong(offsets[41]); + object.customTextButtonEnabledTextInt = reader.readLong(offsets[42]); + object.ethTagBGInt = reader.readLong(offsets[43]); + object.ethTagTextInt = reader.readLong(offsets[44]); + object.ethWalletTagBGInt = reader.readLong(offsets[45]); + object.ethWalletTagTextInt = reader.readLong(offsets[46]); + object.favoriteStarActiveInt = reader.readLong(offsets[47]); + object.favoriteStarInactiveInt = reader.readLong(offsets[48]); + object.gradientBackgroundString = reader.readStringOrNull(offsets[49]); + object.highlightInt = reader.readLong(offsets[50]); + object.homeViewButtonBarBoxShadowString = + reader.readStringOrNull(offsets[51]); object.id = id; + object.infoItemBGInt = reader.readLong(offsets[52]); + object.infoItemIconsInt = reader.readLong(offsets[53]); + object.infoItemLabelInt = reader.readLong(offsets[54]); + object.infoItemTextInt = reader.readLong(offsets[55]); + object.loadingOverlayTextColorInt = reader.readLong(offsets[56]); + object.myStackContactIconBGInt = reader.readLong(offsets[57]); + object.name = reader.readString(offsets[58]); + object.numberBackDefaultInt = reader.readLong(offsets[59]); + object.numberTextDefaultInt = reader.readLong(offsets[60]); + object.numpadBackDefaultInt = reader.readLong(offsets[61]); + object.numpadTextDefaultInt = reader.readLong(offsets[62]); + object.overlayInt = reader.readLong(offsets[63]); + object.popupBGInt = reader.readLong(offsets[64]); + object.radioButtonBorderDisabledInt = reader.readLong(offsets[65]); + object.radioButtonBorderEnabledInt = reader.readLong(offsets[66]); + object.radioButtonIconBorderDisabledInt = reader.readLong(offsets[67]); + object.radioButtonIconBorderInt = reader.readLong(offsets[68]); + object.radioButtonIconCircleInt = reader.readLong(offsets[69]); + object.radioButtonIconEnabledInt = reader.readLong(offsets[70]); + object.radioButtonLabelDisabledInt = reader.readLong(offsets[71]); + object.radioButtonLabelEnabledInt = reader.readLong(offsets[72]); + object.radioButtonTextDisabledInt = reader.readLong(offsets[73]); + object.radioButtonTextEnabledInt = reader.readLong(offsets[74]); + object.rateTypeToggleColorOffInt = reader.readLong(offsets[75]); + object.rateTypeToggleColorOnInt = reader.readLong(offsets[76]); + object.rateTypeToggleDesktopColorOffInt = reader.readLong(offsets[77]); + object.rateTypeToggleDesktopColorOnInt = reader.readLong(offsets[78]); + object.settingsIconBack2Int = reader.readLong(offsets[79]); + object.settingsIconBackInt = reader.readLong(offsets[80]); + object.settingsIconElementInt = reader.readLong(offsets[81]); + object.settingsIconIconInt = reader.readLong(offsets[82]); + object.settingsItem2ActiveBGInt = reader.readLong(offsets[83]); + object.settingsItem2ActiveSubInt = reader.readLong(offsets[84]); + object.settingsItem2ActiveTextInt = reader.readLong(offsets[85]); + object.shadowInt = reader.readLong(offsets[86]); + object.snackBarBackErrorInt = reader.readLong(offsets[87]); + object.snackBarBackInfoInt = reader.readLong(offsets[88]); + object.snackBarBackSuccessInt = reader.readLong(offsets[89]); + object.snackBarTextErrorInt = reader.readLong(offsets[90]); + object.snackBarTextInfoInt = reader.readLong(offsets[91]); + object.snackBarTextSuccessInt = reader.readLong(offsets[92]); + object.splashInt = reader.readLong(offsets[93]); + object.stackWalletBGInt = reader.readLong(offsets[94]); + object.stackWalletBottomInt = reader.readLong(offsets[95]); + object.stackWalletMidInt = reader.readLong(offsets[96]); + object.standardBoxShadowString = reader.readString(offsets[97]); + object.stepIndicatorBGCheckInt = reader.readLong(offsets[98]); + object.stepIndicatorBGInactiveInt = reader.readLong(offsets[99]); + object.stepIndicatorBGLinesInactiveInt = reader.readLong(offsets[100]); + object.stepIndicatorBGLinesInt = reader.readLong(offsets[101]); + object.stepIndicatorBGNumberInt = reader.readLong(offsets[102]); + object.stepIndicatorIconInactiveInt = reader.readLong(offsets[103]); + object.stepIndicatorIconNumberInt = reader.readLong(offsets[104]); + object.stepIndicatorIconTextInt = reader.readLong(offsets[105]); + object.switchBGDisabledInt = reader.readLong(offsets[106]); + object.switchBGOffInt = reader.readLong(offsets[107]); + object.switchBGOnInt = reader.readLong(offsets[108]); + object.switchCircleDisabledInt = reader.readLong(offsets[109]); + object.switchCircleOffInt = reader.readLong(offsets[110]); + object.switchCircleOnInt = reader.readLong(offsets[111]); + object.textConfirmTotalAmountInt = reader.readLong(offsets[112]); + object.textDark2Int = reader.readLong(offsets[113]); + object.textDark3Int = reader.readLong(offsets[114]); + object.textDarkInt = reader.readLong(offsets[115]); + object.textErrorInt = reader.readLong(offsets[116]); + object.textFavoriteCardInt = reader.readLong(offsets[117]); + object.textFieldActiveBGInt = reader.readLong(offsets[118]); + object.textFieldActiveLabelInt = reader.readLong(offsets[119]); + object.textFieldActiveSearchIconLeftInt = reader.readLong(offsets[120]); + object.textFieldActiveSearchIconRightInt = reader.readLong(offsets[121]); + object.textFieldActiveTextInt = reader.readLong(offsets[122]); + object.textFieldDefaultBGInt = reader.readLong(offsets[123]); + object.textFieldDefaultSearchIconLeftInt = reader.readLong(offsets[124]); + object.textFieldDefaultSearchIconRightInt = reader.readLong(offsets[125]); + object.textFieldDefaultTextInt = reader.readLong(offsets[126]); + object.textFieldErrorBGInt = reader.readLong(offsets[127]); + object.textFieldErrorBorderInt = reader.readLong(offsets[128]); + object.textFieldErrorLabelInt = reader.readLong(offsets[129]); + object.textFieldErrorSearchIconLeftInt = reader.readLong(offsets[130]); + object.textFieldErrorSearchIconRightInt = reader.readLong(offsets[131]); + object.textFieldErrorTextInt = reader.readLong(offsets[132]); + object.textFieldSuccessBGInt = reader.readLong(offsets[133]); + object.textFieldSuccessBorderInt = reader.readLong(offsets[134]); + object.textFieldSuccessLabelInt = reader.readLong(offsets[135]); + object.textFieldSuccessSearchIconLeftInt = reader.readLong(offsets[136]); + object.textFieldSuccessSearchIconRightInt = reader.readLong(offsets[137]); + object.textFieldSuccessTextInt = reader.readLong(offsets[138]); + object.textRestoreInt = reader.readLong(offsets[139]); + object.textSelectedWordTableItemInt = reader.readLong(offsets[140]); + object.textSubtitle1Int = reader.readLong(offsets[141]); + object.textSubtitle2Int = reader.readLong(offsets[142]); + object.textSubtitle3Int = reader.readLong(offsets[143]); + object.textSubtitle4Int = reader.readLong(offsets[144]); + object.textSubtitle5Int = reader.readLong(offsets[145]); + object.textSubtitle6Int = reader.readLong(offsets[146]); + object.textWhiteInt = reader.readLong(offsets[147]); + object.themeId = reader.readString(offsets[148]); + object.tokenSummaryBGInt = reader.readLong(offsets[149]); + object.tokenSummaryButtonBGInt = reader.readLong(offsets[150]); + object.tokenSummaryIconInt = reader.readLong(offsets[151]); + object.tokenSummaryTextPrimaryInt = reader.readLong(offsets[152]); + object.tokenSummaryTextSecondaryInt = reader.readLong(offsets[153]); + object.topNavIconGreenInt = reader.readLong(offsets[154]); + object.topNavIconPrimaryInt = reader.readLong(offsets[155]); + object.topNavIconRedInt = reader.readLong(offsets[156]); + object.topNavIconYellowInt = reader.readLong(offsets[157]); + object.version = reader.readLongOrNull(offsets[158]); + object.warningBackgroundInt = reader.readLong(offsets[159]); + object.warningForegroundInt = reader.readLong(offsets[160]); return object; } @@ -1240,13 +1279,16 @@ P _stackThemeDeserializeProp

( return (reader.readLong(offset)) as P; case 6: return (reader.readObjectOrNull( - offset, - ThemeAssetsSchema.deserialize, - allOffsets, - ) ?? - ThemeAssets()) as P; + offset, + ThemeAssetsSchema.deserialize, + allOffsets, + )) as P; case 7: - return (reader.readLong(offset)) as P; + return (reader.readObjectOrNull( + offset, + ThemeAssetsV2Schema.deserialize, + allOffsets, + )) as P; case 8: return (reader.readLong(offset)) as P; case 9: @@ -1262,9 +1304,9 @@ P _stackThemeDeserializeProp

( case 14: return (reader.readLong(offset)) as P; case 15: - return (reader.readString(offset)) as P; - case 16: return (reader.readLong(offset)) as P; + case 16: + return (reader.readString(offset)) as P; case 17: return (reader.readLong(offset)) as P; case 18: @@ -1308,9 +1350,9 @@ P _stackThemeDeserializeProp

( case 37: return (reader.readLong(offset)) as P; case 38: - return (reader.readString(offset)) as P; - case 39: return (reader.readLong(offset)) as P; + case 39: + return (reader.readString(offset)) as P; case 40: return (reader.readLong(offset)) as P; case 41: @@ -1328,13 +1370,13 @@ P _stackThemeDeserializeProp

( case 47: return (reader.readLong(offset)) as P; case 48: - return (reader.readStringOrNull(offset)) as P; + return (reader.readLong(offset)) as P; case 49: - return (reader.readLong(offset)) as P; - case 50: return (reader.readStringOrNull(offset)) as P; - case 51: + case 50: return (reader.readLong(offset)) as P; + case 51: + return (reader.readStringOrNull(offset)) as P; case 52: return (reader.readLong(offset)) as P; case 53: @@ -1346,9 +1388,9 @@ P _stackThemeDeserializeProp

( case 56: return (reader.readLong(offset)) as P; case 57: - return (reader.readString(offset)) as P; - case 58: return (reader.readLong(offset)) as P; + case 58: + return (reader.readString(offset)) as P; case 59: return (reader.readLong(offset)) as P; case 60: @@ -1424,9 +1466,9 @@ P _stackThemeDeserializeProp

( case 95: return (reader.readLong(offset)) as P; case 96: - return (reader.readString(offset)) as P; - case 97: return (reader.readLong(offset)) as P; + case 97: + return (reader.readString(offset)) as P; case 98: return (reader.readLong(offset)) as P; case 99: @@ -1526,9 +1568,9 @@ P _stackThemeDeserializeProp

( case 146: return (reader.readLong(offset)) as P; case 147: - return (reader.readString(offset)) as P; - case 148: return (reader.readLong(offset)) as P; + case 148: + return (reader.readString(offset)) as P; case 149: return (reader.readLong(offset)) as P; case 150: @@ -1548,6 +1590,10 @@ P _stackThemeDeserializeProp

( case 157: return (reader.readLong(offset)) as P; case 158: + return (reader.readLongOrNull(offset)) as P; + case 159: + return (reader.readLong(offset)) as P; + case 160: return (reader.readLong(offset)) as P; default: throw IsarError('Unknown property with id $propertyId'); @@ -2081,6 +2127,40 @@ extension StackThemeQueryFilter }); } + QueryBuilder assetsV1IsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'assets', + )); + }); + } + + QueryBuilder + assetsV1IsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'assets', + )); + }); + } + + QueryBuilder assetsV2IsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'assetsV2', + )); + }); + } + + QueryBuilder + assetsV2IsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'assetsV2', + )); + }); + } + QueryBuilder backgroundAppBarIntEqualTo(int value) { return QueryBuilder.apply(this, (query) { @@ -11120,6 +11200,77 @@ extension StackThemeQueryFilter }); } + QueryBuilder versionIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'version', + )); + }); + } + + QueryBuilder + versionIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'version', + )); + }); + } + + QueryBuilder versionEqualTo( + int? value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'version', + value: value, + )); + }); + } + + QueryBuilder + versionGreaterThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'version', + value: value, + )); + }); + } + + QueryBuilder versionLessThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'version', + value: value, + )); + }); + } + + QueryBuilder versionBetween( + int? lower, + int? upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'version', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + QueryBuilder warningBackgroundIntEqualTo(int value) { return QueryBuilder.apply(this, (query) { @@ -11235,12 +11386,19 @@ extension StackThemeQueryFilter extension StackThemeQueryObject on QueryBuilder { - QueryBuilder assets( + QueryBuilder assetsV1( FilterQuery q) { return QueryBuilder.apply(this, (query) { return query.object(q, r'assets'); }); } + + QueryBuilder assetsV2( + FilterQuery q) { + return QueryBuilder.apply(this, (query) { + return query.object(q, r'assetsV2'); + }); + } } extension StackThemeQueryLinks @@ -13378,6 +13536,18 @@ extension StackThemeQuerySortBy }); } + QueryBuilder sortByVersion() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'version', Sort.asc); + }); + } + + QueryBuilder sortByVersionDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'version', Sort.desc); + }); + } + QueryBuilder sortByWarningBackgroundInt() { return QueryBuilder.apply(this, (query) { @@ -15551,6 +15721,18 @@ extension StackThemeQuerySortThenBy }); } + QueryBuilder thenByVersion() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'version', Sort.asc); + }); + } + + QueryBuilder thenByVersionDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'version', Sort.desc); + }); + } + QueryBuilder thenByWarningBackgroundInt() { return QueryBuilder.apply(this, (query) { @@ -16649,6 +16831,12 @@ extension StackThemeQueryWhereDistinct }); } + QueryBuilder distinctByVersion() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'version'); + }); + } + QueryBuilder distinctByWarningBackgroundInt() { return QueryBuilder.apply(this, (query) { @@ -16711,12 +16899,19 @@ extension StackThemeQueryProperty }); } - QueryBuilder assetsProperty() { + QueryBuilder assetsV1Property() { return QueryBuilder.apply(this, (query) { return query.addPropertyName(r'assets'); }); } + QueryBuilder + assetsV2Property() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'assetsV2'); + }); + } + QueryBuilder backgroundAppBarIntProperty() { return QueryBuilder.apply(this, (query) { @@ -17728,6 +17923,12 @@ extension StackThemeQueryProperty }); } + QueryBuilder versionProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'version'); + }); + } + QueryBuilder warningBackgroundIntProperty() { return QueryBuilder.apply(this, (query) { @@ -25567,3 +25768,3611 @@ extension ThemeAssetsQueryFilter extension ThemeAssetsQueryObject on QueryBuilder {} + +// coverage:ignore-file +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters + +const ThemeAssetsV2Schema = Schema( + name: r'ThemeAssetsV2', + id: -373522695224267013, + properties: { + r'background': PropertySchema( + id: 0, + name: r'background', + type: IsarType.string, + ), + r'bellNew': PropertySchema( + id: 1, + name: r'bellNew', + type: IsarType.string, + ), + r'buy': PropertySchema( + id: 2, + name: r'buy', + type: IsarType.string, + ), + r'coinIconsString': PropertySchema( + id: 3, + name: r'coinIconsString', + type: IsarType.string, + ), + r'coinImagesString': PropertySchema( + id: 4, + name: r'coinImagesString', + type: IsarType.string, + ), + r'coinPlaceholder': PropertySchema( + id: 5, + name: r'coinPlaceholder', + type: IsarType.string, + ), + r'coinSecondaryImagesString': PropertySchema( + id: 6, + name: r'coinSecondaryImagesString', + type: IsarType.string, + ), + r'exchange': PropertySchema( + id: 7, + name: r'exchange', + type: IsarType.string, + ), + r'loadingGif': PropertySchema( + id: 8, + name: r'loadingGif', + type: IsarType.string, + ), + r'personaEasy': PropertySchema( + id: 9, + name: r'personaEasy', + type: IsarType.string, + ), + r'personaIncognito': PropertySchema( + id: 10, + name: r'personaIncognito', + type: IsarType.string, + ), + r'receive': PropertySchema( + id: 11, + name: r'receive', + type: IsarType.string, + ), + r'receiveCancelled': PropertySchema( + id: 12, + name: r'receiveCancelled', + type: IsarType.string, + ), + r'receivePending': PropertySchema( + id: 13, + name: r'receivePending', + type: IsarType.string, + ), + r'send': PropertySchema( + id: 14, + name: r'send', + type: IsarType.string, + ), + r'sendCancelled': PropertySchema( + id: 15, + name: r'sendCancelled', + type: IsarType.string, + ), + r'sendPending': PropertySchema( + id: 16, + name: r'sendPending', + type: IsarType.string, + ), + r'stack': PropertySchema( + id: 17, + name: r'stack', + type: IsarType.string, + ), + r'stackIcon': PropertySchema( + id: 18, + name: r'stackIcon', + type: IsarType.string, + ), + r'themePreview': PropertySchema( + id: 19, + name: r'themePreview', + type: IsarType.string, + ), + r'themeSelector': PropertySchema( + id: 20, + name: r'themeSelector', + type: IsarType.string, + ), + r'txExchange': PropertySchema( + id: 21, + name: r'txExchange', + type: IsarType.string, + ), + r'txExchangeFailed': PropertySchema( + id: 22, + name: r'txExchangeFailed', + type: IsarType.string, + ), + r'txExchangePending': PropertySchema( + id: 23, + name: r'txExchangePending', + type: IsarType.string, + ) + }, + estimateSize: _themeAssetsV2EstimateSize, + serialize: _themeAssetsV2Serialize, + deserialize: _themeAssetsV2Deserialize, + deserializeProp: _themeAssetsV2DeserializeProp, +); + +int _themeAssetsV2EstimateSize( + ThemeAssetsV2 object, + List offsets, + Map> allOffsets, +) { + var bytesCount = offsets.last; + { + final value = object.background; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + bytesCount += 3 + object.bellNew.length * 3; + bytesCount += 3 + object.buy.length * 3; + bytesCount += 3 + object.coinIconsString.length * 3; + bytesCount += 3 + object.coinImagesString.length * 3; + bytesCount += 3 + object.coinPlaceholder.length * 3; + bytesCount += 3 + object.coinSecondaryImagesString.length * 3; + bytesCount += 3 + object.exchange.length * 3; + { + final value = object.loadingGif; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + bytesCount += 3 + object.personaEasy.length * 3; + bytesCount += 3 + object.personaIncognito.length * 3; + bytesCount += 3 + object.receive.length * 3; + bytesCount += 3 + object.receiveCancelled.length * 3; + bytesCount += 3 + object.receivePending.length * 3; + bytesCount += 3 + object.send.length * 3; + bytesCount += 3 + object.sendCancelled.length * 3; + bytesCount += 3 + object.sendPending.length * 3; + bytesCount += 3 + object.stack.length * 3; + bytesCount += 3 + object.stackIcon.length * 3; + bytesCount += 3 + object.themePreview.length * 3; + bytesCount += 3 + object.themeSelector.length * 3; + bytesCount += 3 + object.txExchange.length * 3; + bytesCount += 3 + object.txExchangeFailed.length * 3; + bytesCount += 3 + object.txExchangePending.length * 3; + return bytesCount; +} + +void _themeAssetsV2Serialize( + ThemeAssetsV2 object, + IsarWriter writer, + List offsets, + Map> allOffsets, +) { + writer.writeString(offsets[0], object.background); + writer.writeString(offsets[1], object.bellNew); + writer.writeString(offsets[2], object.buy); + writer.writeString(offsets[3], object.coinIconsString); + writer.writeString(offsets[4], object.coinImagesString); + writer.writeString(offsets[5], object.coinPlaceholder); + writer.writeString(offsets[6], object.coinSecondaryImagesString); + writer.writeString(offsets[7], object.exchange); + writer.writeString(offsets[8], object.loadingGif); + writer.writeString(offsets[9], object.personaEasy); + writer.writeString(offsets[10], object.personaIncognito); + writer.writeString(offsets[11], object.receive); + writer.writeString(offsets[12], object.receiveCancelled); + writer.writeString(offsets[13], object.receivePending); + writer.writeString(offsets[14], object.send); + writer.writeString(offsets[15], object.sendCancelled); + writer.writeString(offsets[16], object.sendPending); + writer.writeString(offsets[17], object.stack); + writer.writeString(offsets[18], object.stackIcon); + writer.writeString(offsets[19], object.themePreview); + writer.writeString(offsets[20], object.themeSelector); + writer.writeString(offsets[21], object.txExchange); + writer.writeString(offsets[22], object.txExchangeFailed); + writer.writeString(offsets[23], object.txExchangePending); +} + +ThemeAssetsV2 _themeAssetsV2Deserialize( + Id id, + IsarReader reader, + List offsets, + Map> allOffsets, +) { + final object = ThemeAssetsV2(); + object.background = reader.readStringOrNull(offsets[0]); + object.bellNew = reader.readString(offsets[1]); + object.buy = reader.readString(offsets[2]); + object.coinIconsString = reader.readString(offsets[3]); + object.coinImagesString = reader.readString(offsets[4]); + object.coinPlaceholder = reader.readString(offsets[5]); + object.coinSecondaryImagesString = reader.readString(offsets[6]); + object.exchange = reader.readString(offsets[7]); + object.loadingGif = reader.readStringOrNull(offsets[8]); + object.personaEasy = reader.readString(offsets[9]); + object.personaIncognito = reader.readString(offsets[10]); + object.receive = reader.readString(offsets[11]); + object.receiveCancelled = reader.readString(offsets[12]); + object.receivePending = reader.readString(offsets[13]); + object.send = reader.readString(offsets[14]); + object.sendCancelled = reader.readString(offsets[15]); + object.sendPending = reader.readString(offsets[16]); + object.stack = reader.readString(offsets[17]); + object.stackIcon = reader.readString(offsets[18]); + object.themePreview = reader.readString(offsets[19]); + object.themeSelector = reader.readString(offsets[20]); + object.txExchange = reader.readString(offsets[21]); + object.txExchangeFailed = reader.readString(offsets[22]); + object.txExchangePending = reader.readString(offsets[23]); + return object; +} + +P _themeAssetsV2DeserializeProp

( + IsarReader reader, + int propertyId, + int offset, + Map> allOffsets, +) { + switch (propertyId) { + case 0: + return (reader.readStringOrNull(offset)) as P; + case 1: + return (reader.readString(offset)) as P; + case 2: + return (reader.readString(offset)) as P; + case 3: + return (reader.readString(offset)) as P; + case 4: + return (reader.readString(offset)) as P; + case 5: + return (reader.readString(offset)) as P; + case 6: + return (reader.readString(offset)) as P; + case 7: + return (reader.readString(offset)) as P; + case 8: + return (reader.readStringOrNull(offset)) as P; + case 9: + return (reader.readString(offset)) as P; + case 10: + return (reader.readString(offset)) as P; + case 11: + return (reader.readString(offset)) as P; + case 12: + return (reader.readString(offset)) as P; + case 13: + return (reader.readString(offset)) as P; + case 14: + return (reader.readString(offset)) as P; + case 15: + return (reader.readString(offset)) as P; + case 16: + return (reader.readString(offset)) as P; + case 17: + return (reader.readString(offset)) as P; + case 18: + return (reader.readString(offset)) as P; + case 19: + return (reader.readString(offset)) as P; + case 20: + return (reader.readString(offset)) as P; + case 21: + return (reader.readString(offset)) as P; + case 22: + return (reader.readString(offset)) as P; + case 23: + return (reader.readString(offset)) as P; + default: + throw IsarError('Unknown property with id $propertyId'); + } +} + +extension ThemeAssetsV2QueryFilter + on QueryBuilder { + QueryBuilder + backgroundIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'background', + )); + }); + } + + QueryBuilder + backgroundIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'background', + )); + }); + } + + QueryBuilder + backgroundEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'background', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + backgroundGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'background', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + backgroundLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'background', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + backgroundBetween( + String? lower, + String? upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'background', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + backgroundStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'background', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + backgroundEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'background', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + backgroundContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'background', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + backgroundMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'background', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + backgroundIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'background', + value: '', + )); + }); + } + + QueryBuilder + backgroundIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'background', + value: '', + )); + }); + } + + QueryBuilder + bellNewEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'bellNew', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bellNewGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'bellNew', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bellNewLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'bellNew', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bellNewBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'bellNew', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bellNewStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'bellNew', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bellNewEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'bellNew', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bellNewContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'bellNew', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bellNewMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'bellNew', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bellNewIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'bellNew', + value: '', + )); + }); + } + + QueryBuilder + bellNewIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'bellNew', + value: '', + )); + }); + } + + QueryBuilder buyEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'buy', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + buyGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'buy', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder buyLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'buy', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder buyBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'buy', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + buyStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'buy', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder buyEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'buy', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder buyContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'buy', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder buyMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'buy', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + buyIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'buy', + value: '', + )); + }); + } + + QueryBuilder + buyIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'buy', + value: '', + )); + }); + } + + QueryBuilder + coinIconsStringEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'coinIconsString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinIconsStringGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'coinIconsString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinIconsStringLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'coinIconsString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinIconsStringBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'coinIconsString', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinIconsStringStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'coinIconsString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinIconsStringEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'coinIconsString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinIconsStringContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'coinIconsString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinIconsStringMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'coinIconsString', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinIconsStringIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'coinIconsString', + value: '', + )); + }); + } + + QueryBuilder + coinIconsStringIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'coinIconsString', + value: '', + )); + }); + } + + QueryBuilder + coinImagesStringEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'coinImagesString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinImagesStringGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'coinImagesString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinImagesStringLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'coinImagesString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinImagesStringBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'coinImagesString', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinImagesStringStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'coinImagesString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinImagesStringEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'coinImagesString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinImagesStringContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'coinImagesString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinImagesStringMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'coinImagesString', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinImagesStringIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'coinImagesString', + value: '', + )); + }); + } + + QueryBuilder + coinImagesStringIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'coinImagesString', + value: '', + )); + }); + } + + QueryBuilder + coinPlaceholderEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'coinPlaceholder', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinPlaceholderGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'coinPlaceholder', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinPlaceholderLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'coinPlaceholder', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinPlaceholderBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'coinPlaceholder', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinPlaceholderStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'coinPlaceholder', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinPlaceholderEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'coinPlaceholder', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinPlaceholderContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'coinPlaceholder', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinPlaceholderMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'coinPlaceholder', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinPlaceholderIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'coinPlaceholder', + value: '', + )); + }); + } + + QueryBuilder + coinPlaceholderIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'coinPlaceholder', + value: '', + )); + }); + } + + QueryBuilder + coinSecondaryImagesStringEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'coinSecondaryImagesString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinSecondaryImagesStringGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'coinSecondaryImagesString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinSecondaryImagesStringLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'coinSecondaryImagesString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinSecondaryImagesStringBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'coinSecondaryImagesString', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinSecondaryImagesStringStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'coinSecondaryImagesString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinSecondaryImagesStringEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'coinSecondaryImagesString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinSecondaryImagesStringContains(String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'coinSecondaryImagesString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinSecondaryImagesStringMatches(String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'coinSecondaryImagesString', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinSecondaryImagesStringIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'coinSecondaryImagesString', + value: '', + )); + }); + } + + QueryBuilder + coinSecondaryImagesStringIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'coinSecondaryImagesString', + value: '', + )); + }); + } + + QueryBuilder + exchangeEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'exchange', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + exchangeGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'exchange', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + exchangeLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'exchange', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + exchangeBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'exchange', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + exchangeStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'exchange', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + exchangeEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'exchange', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + exchangeContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'exchange', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + exchangeMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'exchange', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + exchangeIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'exchange', + value: '', + )); + }); + } + + QueryBuilder + exchangeIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'exchange', + value: '', + )); + }); + } + + QueryBuilder + loadingGifIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'loadingGif', + )); + }); + } + + QueryBuilder + loadingGifIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'loadingGif', + )); + }); + } + + QueryBuilder + loadingGifEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'loadingGif', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + loadingGifGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'loadingGif', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + loadingGifLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'loadingGif', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + loadingGifBetween( + String? lower, + String? upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'loadingGif', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + loadingGifStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'loadingGif', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + loadingGifEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'loadingGif', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + loadingGifContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'loadingGif', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + loadingGifMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'loadingGif', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + loadingGifIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'loadingGif', + value: '', + )); + }); + } + + QueryBuilder + loadingGifIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'loadingGif', + value: '', + )); + }); + } + + QueryBuilder + personaEasyEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'personaEasy', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaEasyGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'personaEasy', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaEasyLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'personaEasy', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaEasyBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'personaEasy', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaEasyStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'personaEasy', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaEasyEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'personaEasy', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaEasyContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'personaEasy', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaEasyMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'personaEasy', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaEasyIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'personaEasy', + value: '', + )); + }); + } + + QueryBuilder + personaEasyIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'personaEasy', + value: '', + )); + }); + } + + QueryBuilder + personaIncognitoEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'personaIncognito', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaIncognitoGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'personaIncognito', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaIncognitoLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'personaIncognito', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaIncognitoBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'personaIncognito', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaIncognitoStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'personaIncognito', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaIncognitoEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'personaIncognito', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaIncognitoContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'personaIncognito', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaIncognitoMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'personaIncognito', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaIncognitoIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'personaIncognito', + value: '', + )); + }); + } + + QueryBuilder + personaIncognitoIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'personaIncognito', + value: '', + )); + }); + } + + QueryBuilder + receiveEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'receive', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receiveGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'receive', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receiveLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'receive', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receiveBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'receive', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receiveStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'receive', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receiveEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'receive', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receiveContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'receive', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receiveMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'receive', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receiveIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'receive', + value: '', + )); + }); + } + + QueryBuilder + receiveIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'receive', + value: '', + )); + }); + } + + QueryBuilder + receiveCancelledEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'receiveCancelled', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receiveCancelledGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'receiveCancelled', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receiveCancelledLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'receiveCancelled', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receiveCancelledBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'receiveCancelled', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receiveCancelledStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'receiveCancelled', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receiveCancelledEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'receiveCancelled', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receiveCancelledContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'receiveCancelled', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receiveCancelledMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'receiveCancelled', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receiveCancelledIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'receiveCancelled', + value: '', + )); + }); + } + + QueryBuilder + receiveCancelledIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'receiveCancelled', + value: '', + )); + }); + } + + QueryBuilder + receivePendingEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'receivePending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receivePendingGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'receivePending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receivePendingLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'receivePending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receivePendingBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'receivePending', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receivePendingStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'receivePending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receivePendingEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'receivePending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receivePendingContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'receivePending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receivePendingMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'receivePending', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receivePendingIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'receivePending', + value: '', + )); + }); + } + + QueryBuilder + receivePendingIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'receivePending', + value: '', + )); + }); + } + + QueryBuilder sendEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'send', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'send', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'send', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder sendBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'send', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'send', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'send', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'send', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder sendMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'send', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'send', + value: '', + )); + }); + } + + QueryBuilder + sendIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'send', + value: '', + )); + }); + } + + QueryBuilder + sendCancelledEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'sendCancelled', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendCancelledGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'sendCancelled', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendCancelledLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'sendCancelled', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendCancelledBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'sendCancelled', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendCancelledStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'sendCancelled', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendCancelledEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'sendCancelled', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendCancelledContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'sendCancelled', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendCancelledMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'sendCancelled', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendCancelledIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'sendCancelled', + value: '', + )); + }); + } + + QueryBuilder + sendCancelledIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'sendCancelled', + value: '', + )); + }); + } + + QueryBuilder + sendPendingEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'sendPending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendPendingGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'sendPending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendPendingLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'sendPending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendPendingBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'sendPending', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendPendingStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'sendPending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendPendingEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'sendPending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendPendingContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'sendPending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendPendingMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'sendPending', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendPendingIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'sendPending', + value: '', + )); + }); + } + + QueryBuilder + sendPendingIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'sendPending', + value: '', + )); + }); + } + + QueryBuilder + stackEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'stack', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + stackGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'stack', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + stackLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'stack', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + stackBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'stack', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + stackStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'stack', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + stackEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'stack', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + stackContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'stack', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + stackMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'stack', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + stackIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'stack', + value: '', + )); + }); + } + + QueryBuilder + stackIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'stack', + value: '', + )); + }); + } + + QueryBuilder + stackIconEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'stackIcon', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + stackIconGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'stackIcon', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + stackIconLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'stackIcon', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + stackIconBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'stackIcon', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + stackIconStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'stackIcon', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + stackIconEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'stackIcon', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + stackIconContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'stackIcon', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + stackIconMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'stackIcon', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + stackIconIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'stackIcon', + value: '', + )); + }); + } + + QueryBuilder + stackIconIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'stackIcon', + value: '', + )); + }); + } + + QueryBuilder + themePreviewEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'themePreview', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themePreviewGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'themePreview', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themePreviewLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'themePreview', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themePreviewBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'themePreview', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themePreviewStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'themePreview', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themePreviewEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'themePreview', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themePreviewContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'themePreview', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themePreviewMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'themePreview', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themePreviewIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'themePreview', + value: '', + )); + }); + } + + QueryBuilder + themePreviewIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'themePreview', + value: '', + )); + }); + } + + QueryBuilder + themeSelectorEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'themeSelector', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themeSelectorGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'themeSelector', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themeSelectorLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'themeSelector', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themeSelectorBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'themeSelector', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themeSelectorStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'themeSelector', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themeSelectorEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'themeSelector', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themeSelectorContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'themeSelector', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themeSelectorMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'themeSelector', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themeSelectorIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'themeSelector', + value: '', + )); + }); + } + + QueryBuilder + themeSelectorIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'themeSelector', + value: '', + )); + }); + } + + QueryBuilder + txExchangeEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'txExchange', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'txExchange', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'txExchange', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'txExchange', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'txExchange', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'txExchange', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'txExchange', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'txExchange', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'txExchange', + value: '', + )); + }); + } + + QueryBuilder + txExchangeIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'txExchange', + value: '', + )); + }); + } + + QueryBuilder + txExchangeFailedEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'txExchangeFailed', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeFailedGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'txExchangeFailed', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeFailedLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'txExchangeFailed', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeFailedBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'txExchangeFailed', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeFailedStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'txExchangeFailed', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeFailedEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'txExchangeFailed', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeFailedContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'txExchangeFailed', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeFailedMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'txExchangeFailed', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeFailedIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'txExchangeFailed', + value: '', + )); + }); + } + + QueryBuilder + txExchangeFailedIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'txExchangeFailed', + value: '', + )); + }); + } + + QueryBuilder + txExchangePendingEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'txExchangePending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangePendingGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'txExchangePending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangePendingLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'txExchangePending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangePendingBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'txExchangePending', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangePendingStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'txExchangePending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangePendingEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'txExchangePending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangePendingContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'txExchangePending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangePendingMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'txExchangePending', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangePendingIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'txExchangePending', + value: '', + )); + }); + } + + QueryBuilder + txExchangePendingIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'txExchangePending', + value: '', + )); + }); + } +} + +extension ThemeAssetsV2QueryObject + on QueryBuilder {} diff --git a/lib/notifications/notification_card.dart b/lib/notifications/notification_card.dart index c8f09ec7e..08fbd2a3c 100644 --- a/lib/notifications/notification_card.dart +++ b/lib/notifications/notification_card.dart @@ -32,7 +32,7 @@ class NotificationCard extends ConsumerWidget { static const double mobileIconSize = 24; static const double desktopIconSize = 30; - String coinIconPath(ThemeAssets assets, WidgetRef ref) { + String coinIconPath(IThemeAssets assets, WidgetRef ref) { try { final coin = coinFromPrettyName(notification.coinName); return ref.read(coinIconProvider(coin)); @@ -61,7 +61,7 @@ class NotificationCard extends ConsumerWidget { File( coinIconPath( ref.watch( - themeProvider.select((value) => value.assets), + themeAssetsProvider, ), ref), ), @@ -79,7 +79,7 @@ class NotificationCard extends ConsumerWidget { File( coinIconPath( ref.watch( - themeProvider.select((value) => value.assets), + themeAssetsProvider, ), ref), ), diff --git a/lib/pages/exchange_view/trade_details_view.dart b/lib/pages/exchange_view/trade_details_view.dart index 3618a14a5..4b9f95c42 100644 --- a/lib/pages/exchange_view/trade_details_view.dart +++ b/lib/pages/exchange_view/trade_details_view.dart @@ -113,7 +113,7 @@ class _TradeDetailsViewState extends ConsumerState { super.initState(); } - String _fetchIconAssetForStatus(String statusString, ThemeAssets assets) { + String _fetchIconAssetForStatus(String statusString, IThemeAssets assets) { ChangeNowTransactionStatus? status; try { if (statusString.toLowerCase().startsWith("waiting")) { @@ -322,11 +322,7 @@ class _TradeDetailsViewState extends ConsumerState { File( _fetchIconAssetForStatus( trade.status, - ref.watch( - themeProvider.select( - (value) => value.assets, - ), - ), + ref.watch(themeAssetsProvider), ), ), width: 32, @@ -393,11 +389,7 @@ class _TradeDetailsViewState extends ConsumerState { File( _fetchIconAssetForStatus( trade.status, - ref.watch( - themeProvider.select( - (value) => value.assets, - ), - ), + ref.watch(themeAssetsProvider), ), ), width: 32, diff --git a/lib/pages/wallet_view/sub_widgets/tx_icon.dart b/lib/pages/wallet_view/sub_widgets/tx_icon.dart index 5b85861c0..ed7d94e64 100644 --- a/lib/pages/wallet_view/sub_widgets/tx_icon.dart +++ b/lib/pages/wallet_view/sub_widgets/tx_icon.dart @@ -24,7 +24,7 @@ class TxIcon extends ConsumerWidget { static const Size size = Size(32, 32); String _getAssetName( - bool isCancelled, bool isReceived, bool isPending, ThemeAssets assets) { + bool isCancelled, bool isReceived, bool isPending, IThemeAssets assets) { if (!isReceived && transaction.subType == TransactionSubType.mint) { if (isCancelled) { return Assets.svg.anonymizeFailed; @@ -65,7 +65,7 @@ class TxIcon extends ConsumerWidget { currentHeight, coin.requiredConfirmations, ), - ref.watch(themeProvider).assets, + ref.watch(themeAssetsProvider), ); return SizedBox( diff --git a/lib/pages_desktop_specific/desktop_exchange/desktop_all_trades_view.dart b/lib/pages_desktop_specific/desktop_exchange/desktop_all_trades_view.dart index 4fcbb5a82..e92d743ba 100644 --- a/lib/pages_desktop_specific/desktop_exchange/desktop_all_trades_view.dart +++ b/lib/pages_desktop_specific/desktop_exchange/desktop_all_trades_view.dart @@ -288,7 +288,7 @@ class DesktopTradeRowCard extends ConsumerStatefulWidget { class _DesktopTradeRowCardState extends ConsumerState { late final String tradeId; - String _fetchIconAssetForStatus(String statusString, ThemeAssets assets) { + String _fetchIconAssetForStatus(String statusString, IThemeAssets assets) { ChangeNowTransactionStatus? status; try { if (statusString.toLowerCase().startsWith("waiting")) { @@ -528,7 +528,7 @@ class _DesktopTradeRowCardState extends ConsumerState { _fetchIconAssetForStatus( trade.status, ref.watch( - themeProvider.select((value) => value.assets), + themeAssetsProvider, ), ), ), diff --git a/lib/themes/coin_icon_provider.dart b/lib/themes/coin_icon_provider.dart index 9e7ac9f2c..bfa21668c 100644 --- a/lib/themes/coin_icon_provider.dart +++ b/lib/themes/coin_icon_provider.dart @@ -1,38 +1,44 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; import 'package:stackwallet/themes/theme_providers.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; final coinIconProvider = Provider.family((ref, coin) { - final assets = ref.watch(themeProvider).assets; - switch (coin) { - case Coin.bitcoin: - case Coin.bitcoinTestNet: - return assets.bitcoin; - case Coin.litecoin: - case Coin.litecoinTestNet: - return assets.litecoin; - case Coin.bitcoincash: - case Coin.bitcoincashTestnet: - return assets.bitcoincash; - case Coin.dogecoin: - case Coin.dogecoinTestNet: - return assets.dogecoin; - case Coin.eCash: - return assets.bitcoin; - case Coin.epicCash: - return assets.epicCash; - case Coin.firo: - case Coin.firoTestNet: - return assets.firo; - case Coin.monero: - return assets.monero; - case Coin.wownero: - return assets.wownero; - case Coin.namecoin: - return assets.namecoin; - case Coin.particl: - return assets.particl; - case Coin.ethereum: - return assets.ethereum; + final assets = ref.watch(themeAssetsProvider); + + if (assets is ThemeAssets) { + switch (coin) { + case Coin.bitcoin: + case Coin.bitcoinTestNet: + return assets.bitcoin; + case Coin.litecoin: + case Coin.litecoinTestNet: + return assets.litecoin; + case Coin.bitcoincash: + case Coin.bitcoincashTestnet: + return assets.bitcoincash; + case Coin.dogecoin: + case Coin.dogecoinTestNet: + return assets.dogecoin; + case Coin.eCash: + return assets.bitcoin; + case Coin.epicCash: + return assets.epicCash; + case Coin.firo: + case Coin.firoTestNet: + return assets.firo; + case Coin.monero: + return assets.monero; + case Coin.wownero: + return assets.wownero; + case Coin.namecoin: + return assets.namecoin; + case Coin.particl: + return assets.particl; + case Coin.ethereum: + return assets.ethereum; + } + } else { + return (assets as ThemeAssetsV2).coinIcons[coin]!; } }); diff --git a/lib/themes/coin_image_provider.dart b/lib/themes/coin_image_provider.dart index c5eb8f0f6..eb79bd2e2 100644 --- a/lib/themes/coin_image_provider.dart +++ b/lib/themes/coin_image_provider.dart @@ -1,81 +1,92 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; import 'package:stackwallet/themes/theme_providers.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; final coinImageProvider = Provider.family((ref, coin) { - final assets = ref.watch(themeProvider).assets; - switch (coin) { - case Coin.bitcoin: - return assets.bitcoinImage; - case Coin.litecoin: - case Coin.litecoinTestNet: - return assets.litecoinImage; - case Coin.bitcoincash: - return assets.bitcoincashImage; - case Coin.dogecoin: - return assets.dogecoinImage; - case Coin.eCash: - return assets.bitcoinImage; - case Coin.epicCash: - return assets.epicCashImage; - case Coin.firo: - return assets.firoImage; - case Coin.monero: - return assets.moneroImage; - case Coin.wownero: - return assets.wowneroImage; - case Coin.namecoin: - return assets.namecoinImage; - case Coin.particl: - return assets.particlImage; - case Coin.bitcoinTestNet: - return assets.bitcoinImage; - case Coin.bitcoincashTestnet: - return assets.bitcoincashImage; - case Coin.firoTestNet: - return assets.firoImage; - case Coin.dogecoinTestNet: - return assets.dogecoinImage; - case Coin.ethereum: - return assets.ethereumImage; + final assets = ref.watch(themeAssetsProvider); + + if (assets is ThemeAssets) { + switch (coin) { + case Coin.bitcoin: + return assets.bitcoinImage; + case Coin.litecoin: + case Coin.litecoinTestNet: + return assets.litecoinImage; + case Coin.bitcoincash: + return assets.bitcoincashImage; + case Coin.dogecoin: + return assets.dogecoinImage; + case Coin.eCash: + return assets.bitcoinImage; + case Coin.epicCash: + return assets.epicCashImage; + case Coin.firo: + return assets.firoImage; + case Coin.monero: + return assets.moneroImage; + case Coin.wownero: + return assets.wowneroImage; + case Coin.namecoin: + return assets.namecoinImage; + case Coin.particl: + return assets.particlImage; + case Coin.bitcoinTestNet: + return assets.bitcoinImage; + case Coin.bitcoincashTestnet: + return assets.bitcoincashImage; + case Coin.firoTestNet: + return assets.firoImage; + case Coin.dogecoinTestNet: + return assets.dogecoinImage; + case Coin.ethereum: + return assets.ethereumImage; + } + } else { + return (assets as ThemeAssetsV2).coinImages[coin]!; } }); final coinImageSecondaryProvider = Provider.family((ref, coin) { - final assets = ref.watch(themeProvider).assets; - switch (coin) { - case Coin.bitcoin: - return assets.bitcoinImageSecondary; - case Coin.litecoin: - case Coin.litecoinTestNet: - return assets.litecoinImageSecondary; - case Coin.bitcoincash: - return assets.bitcoincashImageSecondary; - case Coin.dogecoin: - return assets.dogecoinImageSecondary; - case Coin.eCash: - return assets.bitcoinImageSecondary; - case Coin.epicCash: - return assets.epicCashImageSecondary; - case Coin.firo: - return assets.firoImageSecondary; - case Coin.monero: - return assets.moneroImageSecondary; - case Coin.wownero: - return assets.wowneroImageSecondary; - case Coin.namecoin: - return assets.namecoinImageSecondary; - case Coin.particl: - return assets.particlImageSecondary; - case Coin.bitcoinTestNet: - return assets.bitcoinImageSecondary; - case Coin.bitcoincashTestnet: - return assets.bitcoincashImageSecondary; - case Coin.firoTestNet: - return assets.firoImageSecondary; - case Coin.dogecoinTestNet: - return assets.dogecoinImageSecondary; - case Coin.ethereum: - return assets.ethereumImageSecondary; + final assets = ref.watch(themeAssetsProvider); + + if (assets is ThemeAssets) { + switch (coin) { + case Coin.bitcoin: + return assets.bitcoinImageSecondary; + case Coin.litecoin: + case Coin.litecoinTestNet: + return assets.litecoinImageSecondary; + case Coin.bitcoincash: + return assets.bitcoincashImageSecondary; + case Coin.dogecoin: + return assets.dogecoinImageSecondary; + case Coin.eCash: + return assets.bitcoinImageSecondary; + case Coin.epicCash: + return assets.epicCashImageSecondary; + case Coin.firo: + return assets.firoImageSecondary; + case Coin.monero: + return assets.moneroImageSecondary; + case Coin.wownero: + return assets.wowneroImageSecondary; + case Coin.namecoin: + return assets.namecoinImageSecondary; + case Coin.particl: + return assets.particlImageSecondary; + case Coin.bitcoinTestNet: + return assets.bitcoinImageSecondary; + case Coin.bitcoincashTestnet: + return assets.bitcoincashImageSecondary; + case Coin.firoTestNet: + return assets.firoImageSecondary; + case Coin.dogecoinTestNet: + return assets.dogecoinImageSecondary; + case Coin.ethereum: + return assets.ethereumImageSecondary; + } + } else { + return (assets as ThemeAssetsV2).coinSecondaryImages[coin]!; } }); diff --git a/lib/themes/theme_providers.dart b/lib/themes/theme_providers.dart index 36fc8cf5f..08a5be65c 100644 --- a/lib/themes/theme_providers.dart +++ b/lib/themes/theme_providers.dart @@ -20,3 +20,11 @@ final themeProvider = StateProvider( ), ), ); + +final themeAssetsProvider = StateProvider( + (ref) => ref.watch( + themeProvider.select( + (value) => value.assets, + ), + ), +); diff --git a/lib/widgets/trade_card.dart b/lib/widgets/trade_card.dart index 4625c6714..3e442e07a 100644 --- a/lib/widgets/trade_card.dart +++ b/lib/widgets/trade_card.dart @@ -24,7 +24,7 @@ class TradeCard extends ConsumerWidget { final Trade trade; final VoidCallback onTap; - String _fetchIconAssetForStatus(String statusString, ThemeAssets assets) { + String _fetchIconAssetForStatus(String statusString, IThemeAssets assets) { ChangeNowTransactionStatus? status; try { if (statusString.toLowerCase().startsWith("waiting")) { @@ -89,11 +89,7 @@ class TradeCard extends ConsumerWidget { File( _fetchIconAssetForStatus( trade.status, - ref.watch( - themeProvider.select( - (value) => value.assets, - ), - ), + ref.watch(themeAssetsProvider), ), ), width: 32, diff --git a/test/sample_data/theme_json_v2.dart b/test/sample_data/theme_json_v2.dart new file mode 100644 index 000000000..3f9fbfb7f --- /dev/null +++ b/test/sample_data/theme_json_v2.dart @@ -0,0 +1,252 @@ +const Map lightThemeJsonMap = { + "name": "Light", + "version": 2, + "id": "light", + "brightness": "light", + "colors": { + // keys for coin colors must match the Coin enum name value exactly + "coin": { + "bitcoin": "0xFFFCC17B", + "litecoin": "0xFF7FA6E1", + "bitcoincash": "0xFF7BCFB8", + "firo": "0xFFFF897A", + "dogecoin": "0xFFFFE079", + "eCash": "0xFFC5C7CB", + "epicCash": "0xFFC5C7CB", + "ethereum": "0xFFA7ADE9", + "monero": "0xFFFF9E6B", + "namecoin": "0xFF91B1E1", + "wownero": "0xFFED80C1", + "particl": "0xFF8175BD" + }, + "background": "0xFFF7F7F7", + "background_app_bar": "0xFFF7F7F7", + "overlay": "0xFF111215", + "accent_color_blue": "0xFF0052DF", + "accent_color_green": "0xFF4CC0A0", + "accent_color_yellow": "0xFFF7D65D", + "accent_color_red": "0xFFD34E50", + "accent_color_orange": "0xFFFEA68D", + "accent_color_dark": "0xFF232323", + "shadow": "0x0F2D3132", + "text_dark_one": "0xFF232323", + "text_dark_two": "0xFF414141", + "text_dark_three": "0xFF747778", + "text_white": "0xFFFFFFFF", + "text_favorite": "0xFF232323", + "text_error": "0xFF930006", + "text_restore": "0xFF111215", + "text_subtitle_one": "0xFF8E9192", + "text_subtitle_two": "0xFFA9ACAC", + "text_subtitle_three": "0xFFC4C7C7", + "text_subtitle_four": "0xFFE0E3E3", + "text_subtitle_five": "0xFFEEEFF1", + "text_subtitle_six": "0xFFF5F5F5", + "button_back_primary": "0xFF232323", + "button_back_secondary": "0xFFE0E3E3", + "button_back_primary_disabled": "0xFFD7D7D7", + "button_back_secondary_disabled": "0xFFF0F1F1", + "button_back_border": "0xFF232323", + "button_back_border_disabled": "0xFFB6B6B6", + "button_back_border_secondary": "0xFFE0E3E3", + "button_back_border_secondary_disabled": "0xFFF0F1F1", + "number_back_default": "0xFFFFFFFF", + "numpad_back_default": "0xFF232323", + "bottom_nav_back": "0xFFFFFFFF", + "button_text_primary": "0xFFFFFFFF", + "button_text_secondary": "0xFF232323", + "button_text_primary_disabled": "0xFFF8F8F8", + "button_text_secondary_disabled": "0xFFB7B7B7", + "button_text_border": "0xFF232323", + "button_text_disabled": "0xFFB6B6B6", + "button_text_borderless": "0xFF0052DF", + "button_text_borderless_disabled": "0xFFB6B6B6", + "number_text_default": "0xFF232323", + "numpad_text_default": "0xFFFFFFFF", + "bottom_nav_text": "0xFF232323", + "custom_text_button_enabled_text": "0xFF0052DF", + "custom_text_button_disabled_text": "0xFF8E9192", + "switch_bg_on": "0xFF0052DF", + "switch_bg_off": "0xFFD8E4FB", + "switch_bg_disabled": "0xFFC5C6C9", + "switch_circle_on": "0xFFDAE2FF", + "switch_circle_off": "0xFFFBFCFF", + "switch_circle_disabled": "0xFFFBFCFF", + "step_indicator_bg_check": "0xFFD9E2FF", + "step_indicator_bg_number": "0xFFD9E2FF", + "step_indicator_bg_inactive": "0xFFCDCDCD", + "step_indicator_bg_lines": "0xFF0056D2", + "step_indicator_bg_lines_inactive": "0xFFCDCDCD", + "step_indicator_icon_text": "0xFF0056D2", + "step_indicator_icon_number": "0xFF0056D2", + "step_indicator_icon_inactive": "0xFFF7F7F7", + "checkbox_bg_checked": "0xFF0056D2", + "checkbox_border_empty": "0xFF8E9192", + "checkbox_bg_disabled": "0xFFADC7EC", + "checkbox_icon_checked": "0xFFFFFFFF", + "checkbox_icon_disabled": "0xFFFFFFFF", + "checkbox_text_label": "0xFF232323", + "snack_bar_back_success": "0xFFB9E9D4", + "snack_bar_back_error": "0xFFFFDAD4", + "snack_bar_back_info": "0xFFDAE2FF", + "snack_bar_text_success": "0xFF006C4D", + "snack_bar_text_error": "0xFF930006", + "snack_bar_text_info": "0xFF002A78", + "bottom_nav_icon_back": "0xFFA2A2A2", + "bottom_nav_icon_icon": "0xFF232323", + "bottom_nav_icon_icon_highlighted": "0xFF232323", + "top_nav_icon_primary": "0xFF232323", + "top_nav_icon_green": "0xFF00A578", + "top_nav_icon_yellow": "0xFFF4C517", + "top_nav_icon_red": "0xFFC00205", + "settings_icon_back": "0xFFE0E3E3", + "settings_icon_icon": "0xFF232323", + "settings_icon_back_two": "0xFF94D6C4", + "settings_icon_element": "0xFF00A578", + "text_field_active_bg": "0xFFEEEFF1", + "text_field_default_bg": "0xFFEEEFF1", + "text_field_error_bg": "0xFFFFDAD4", + "text_field_success_bg": "0xFFB9E9D4", + "text_field_error_border": "0xFFFFDAD4", + "text_field_success_border": "0xFFB9E9D4", + "text_field_active_search_icon_left": "0xFFA9ACAC", + "text_field_default_search_icon_left": "0xFFA9ACAC", + "text_field_error_search_icon_left": "0xFF930006", + "text_field_success_search_icon_left": "0xFF006C4D", + "text_field_active_text": "0xFF232323", + "text_field_default_text": "0xFFA9ACAC", + "text_field_error_text": "0xFF000000", + "text_field_success_text": "0xFF000000", + "text_field_active_label": "0xFFA9ACAC", + "text_field_error_label": "0xFF930006", + "text_field_success_label": "0xFF006C4D", + "text_field_active_search_icon_right": "0xFF747778", + "text_field_default_search_icon_right": "0xFF747778", + "text_field_error_search_icon_right": "0xFF930006", + "text_field_success_search_icon_right": "0xFF006C4D", + "settings_item_level_two_active_bg": "0xFFFFFFFF", + "settings_item_level_two_active_text": "0xFF232323", + "settings_item_level_two_active_sub": "0xFF8E9192", + "radio_button_icon_border": "0xFF0056D2", + "radio_button_icon_border_disabled": "0xFF8F909A", + "radio_button_border_enabled": "0xFF0056D2", + "radio_button_border_disabled": "0xFF8F909A", + "radio_button_icon_circle": "0xFF0056D2", + "radio_button_icon_enabled": "0xFF0056D2", + "radio_button_text_enabled": "0xFF44464E", + "radio_button_text_disabled": "0xFF44464E", + "radio_button_label_enabled": "0xFF8E9192", + "radio_button_label_disabled": "0xFF8E9192", + "info_item_bg": "0xFFFFFFFF", + "info_item_label": "0xFF8E9192", + "info_item_text": "0xFF232323", + "info_item_icons": "0xFF0056D2", + "popup_bg": "0xFFFFFFFF", + "currency_list_item_bg": "0xFFF9F9FC", + "sw_bg": "0xFFFFFFFF", + "sw_mid": "0xFFFFFFFF", + "sw_bottom": "0xFF232323", + "bottom_nav_shadow": "0xFF282E33", + "favorite_star_active": "0xFF0056D2", + "favorite_star_inactive": "0xFFC4C7C7", + "splash": "0x358E9192", + "highlight": "0x44A9ACAC", + "warning_foreground": "0xFF232323", + "warning_background": "0xFFFFDAD3", + "loading_overlay_text_color": "0xFFF7F7F7", + "my_stack_contact_icon_bg": "0xFFEEEFF1", + "text_confirm_total_amount": "0xFF232323", + "text_selected_word_table_iterm": "0xFF232323", + "rate_type_toggle_color_on": "0xFFEEEFF1", + "rate_type_toggle_color_off": "0xFFFFFFFF", + "rate_type_toggle_desktop_color_on": "0xFFEEEFF1", + "rate_type_toggle_desktop_color_off": "0xFFE0E3E3", + "eth_tag_text": "0xFFFFFFFF", + "eth_tag_bg": "0xFF4D5798", + "eth_wallet_tag_text": "0xFF4D5798", + "eth_wallet_tag_bg": "0xFFF0F3FD", + "token_summary_text_primary": "0xFF232323", + "token_summary_text_secondary": "0xFF8488AB", + "token_summary_bg": "0xFFE9EAFF", + "token_summary_button_bg": "0xFFFFFFFF", + "token_summary_icon": "0xFF424A97", + "box_shadows": { + "standard": { + "color": "0x0F2D3132", + "spread_radius": 3.0, + "blur_radius": 4.0 + }, + "home_view_button_bar": { + "color": "0x0F2D3132", + "spread_radius": 3.0, + "blur_radius": 4.0 + } + } + }, + "assets": { + "coin_placeholder": "dummy.svg", + // keys for coin assets must match the Coin enum name value exactly + "coins": { + "icons": { + "bitcoin": "dummy.svg", + "litecoin": "dummy.svg", + "bitcoincash": "dummy.svg", + "dogecoin": "dummy.svg", + "eCash": "dummy.svg", + "epicCash": "dummy.svg", + "ethereum": "dummy.svg", + "firo": "dummy.svg", + "monero": "dummy.svg", + "wownero": "dummy.svg", + "namecoin": "dummy.svg", + "particl": "dummy.svg" + }, + "images": { + "bitcoin": "dummy.svg", + "litecoin": "dummy.svg", + "bitcoincash": "dummy.svg", + "dogecoin": "dummy.svg", + "eCash": "dummy.svg", + "epicCash": "dummy.svg", + "ethereum": "dummy.svg", + "firo": "dummy.svg", + "monero": "dummy.svg", + "wownero": "dummy.svg", + "namecoin": "dummy.svg", + "particl": "dummy.svg" + }, + "secondaries": { + "bitcoin": "dummy.svg", + "litecoin": "dummy.svg", + "bitcoincash": "dummy.svg", + "dogecoin": "dummy.svg", + "eCash": "dummy.svg", + "epicCash": "dummy.svg", + "ethereum": "dummy.svg", + "firo": "dummy.svg", + "monero": "dummy.svg", + "wownero": "dummy.svg", + "namecoin": "dummy.svg", + "particl": "dummy.svg" + } + }, + "bell_new": "dummy.svg", + "persona_incognito": "dummy.svg.svg", + "persona_easy": "dummy.svg", + "stack": "dummy.svg", + "stack_icon": "dummy.svg", + "receive": "dummy.svg", + "receive_pending": "dummy.svg", + "receive_cancelled": "dummy.svg", + "send": "dummy.svg", + "tx_exchange": "dummy.svg", + "tx_exchange_pending": "dummy.svg", + "tx_exchange_failed": "dummy.svg", + "buy": "dummy.svg", + "exchange": "dummy.svg", + "send_pending": "dummy.svg", + "send_cancelled": "dummy.svg", + "theme_selector": "dummy.svg", + "theme_preview": "dummy.png" + } +}; From f0e16fc7da2912656a4ce28f631296e5eb095333 Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 18 May 2023 16:13:52 -0600 Subject: [PATCH 19/28] fix: coin icons for testnet wallets --- lib/themes/coin_icon_provider.dart | 2 +- lib/themes/coin_image_provider.dart | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/themes/coin_icon_provider.dart b/lib/themes/coin_icon_provider.dart index bfa21668c..6c17969e6 100644 --- a/lib/themes/coin_icon_provider.dart +++ b/lib/themes/coin_icon_provider.dart @@ -39,6 +39,6 @@ final coinIconProvider = Provider.family((ref, coin) { return assets.ethereum; } } else { - return (assets as ThemeAssetsV2).coinIcons[coin]!; + return (assets as ThemeAssetsV2).coinIcons[coin.mainNetVersion]!; } }); diff --git a/lib/themes/coin_image_provider.dart b/lib/themes/coin_image_provider.dart index eb79bd2e2..239e1d1cb 100644 --- a/lib/themes/coin_image_provider.dart +++ b/lib/themes/coin_image_provider.dart @@ -43,7 +43,7 @@ final coinImageProvider = Provider.family((ref, coin) { return assets.ethereumImage; } } else { - return (assets as ThemeAssetsV2).coinImages[coin]!; + return (assets as ThemeAssetsV2).coinImages[coin.mainNetVersion]!; } }); @@ -87,6 +87,6 @@ final coinImageSecondaryProvider = Provider.family((ref, coin) { return assets.ethereumImageSecondary; } } else { - return (assets as ThemeAssetsV2).coinSecondaryImages[coin]!; + return (assets as ThemeAssetsV2).coinSecondaryImages[coin.mainNetVersion]!; } }); From fb31bb1db88477db8826d90b7d9091c24f023197 Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 18 May 2023 16:16:22 -0600 Subject: [PATCH 20/28] update bundled themes --- assets/default_themes/dark.zip | Bin 528880 -> 567322 bytes assets/default_themes/light.zip | Bin 476773 -> 514498 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/assets/default_themes/dark.zip b/assets/default_themes/dark.zip index 1d8899567f3e324577a47085dafa6a1bececd095..e2291ba2b8ca8dfc1c3414a827c3607f46da25e1 100644 GIT binary patch delta 42793 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