diff --git a/assets/default_themes/dark.zip b/assets/default_themes/dark.zip index 1d8899567..e2291ba2b 100644 Binary files a/assets/default_themes/dark.zip and b/assets/default_themes/dark.zip differ diff --git a/assets/default_themes/light.zip b/assets/default_themes/light.zip index 093a1b2eb..c9579152d 100644 Binary files a/assets/default_themes/light.zip and b/assets/default_themes/light.zip differ diff --git a/assets/svg/anonymize.svg b/assets/svg/anonymize.svg new file mode 100644 index 000000000..676501302 --- /dev/null +++ b/assets/svg/anonymize.svg @@ -0,0 +1,83 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/crypto_plugins/flutter_libmonero b/crypto_plugins/flutter_libmonero index 81659ce57..73d257ed2 160000 --- a/crypto_plugins/flutter_libmonero +++ b/crypto_plugins/flutter_libmonero @@ -1 +1 @@ -Subproject commit 81659ce57952c5ab54ffe6bacfbf43da159fff3e +Subproject commit 73d257ed2fe5b204cf3589822e226301b187b86d diff --git a/lib/electrumx_rpc/cached_electrumx.dart b/lib/electrumx_rpc/cached_electrumx.dart index 935e8605e..66e32238c 100644 --- a/lib/electrumx_rpc/cached_electrumx.dart +++ b/lib/electrumx_rpc/cached_electrumx.dart @@ -4,43 +4,23 @@ import 'package:stackwallet/db/hive/db.dart'; import 'package:stackwallet/electrumx_rpc/electrumx.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/logger.dart'; -import 'package:stackwallet/utilities/prefs.dart'; import 'package:string_validator/string_validator.dart'; class CachedElectrumX { - final ElectrumX? electrumXClient; - - final String server; - final int port; - final bool useSSL; - - final Prefs prefs; - final List failovers; + final ElectrumX electrumXClient; static const minCacheConfirms = 30; const CachedElectrumX({ - required this.server, - required this.port, - required this.useSSL, - required this.prefs, - required this.failovers, - this.electrumXClient, + required this.electrumXClient, }); factory CachedElectrumX.from({ - required ElectrumXNode node, - required Prefs prefs, - required List failovers, - ElectrumX? electrumXClient, + required ElectrumX electrumXClient, }) => CachedElectrumX( - server: node.address, - port: node.port, - useSSL: node.useSSL, - prefs: prefs, - failovers: failovers, - electrumXClient: electrumXClient); + electrumXClient: electrumXClient, + ); Future> getAnonymitySet({ required String groupId, @@ -66,16 +46,7 @@ class CachedElectrumX { set = Map.from(cachedSet); } - final client = electrumXClient ?? - ElectrumX( - host: server, - port: port, - useSSL: useSSL, - prefs: prefs, - failovers: failovers, - ); - - final newSet = await client.getAnonymitySet( + final newSet = await electrumXClient.getAnonymitySet( groupId: groupId, blockhash: set["blockHash"] as String, ); @@ -115,8 +86,9 @@ class CachedElectrumX { key: groupId, value: set); Logging.instance.log( - "Updated currently anonymity set for ${coin.name} with group ID $groupId", - level: LogLevel.Info); + "Updated current anonymity set for ${coin.name} with group ID $groupId", + level: LogLevel.Info, + ); } return set; @@ -151,16 +123,8 @@ class CachedElectrumX { final cachedTx = DB.instance.get( boxName: DB.instance.boxNameTxCache(coin: coin), key: txHash) as Map?; if (cachedTx == null) { - final client = electrumXClient ?? - ElectrumX( - host: server, - port: port, - useSSL: useSSL, - prefs: prefs, - failovers: failovers, - ); - final Map result = - await client.getTransaction(txHash: txHash, verbose: verbose); + final Map result = await electrumXClient + .getTransaction(txHash: txHash, verbose: verbose); result.remove("hex"); result.remove("lelantusData"); @@ -187,31 +151,25 @@ class CachedElectrumX { } } - Future> getUsedCoinSerials({ + Future> getUsedCoinSerials({ required Coin coin, int startNumber = 0, }) async { try { - List? cachedSerials = DB.instance.get( + final _list = DB.instance.get( boxName: DB.instance.boxNameUsedSerialsCache(coin: coin), key: "serials") as List?; - cachedSerials ??= []; + List cachedSerials = + _list == null ? [] : List.from(_list); final startNumber = cachedSerials.length; - final client = electrumXClient ?? - ElectrumX( - host: server, - port: port, - useSSL: useSSL, - prefs: prefs, - failovers: failovers, - ); + final serials = + await electrumXClient.getUsedCoinSerials(startNumber: startNumber); + List newSerials = []; - final serials = await client.getUsedCoinSerials(startNumber: startNumber); - List newSerials = []; - for (var element in (serials["serials"] as List)) { + for (final element in (serials["serials"] as List)) { if (!isHexadecimal(element as String)) { newSerials.add(base64ToHex(element)); } else { @@ -221,9 +179,10 @@ class CachedElectrumX { cachedSerials.addAll(newSerials); await DB.instance.put( - boxName: DB.instance.boxNameUsedSerialsCache(coin: coin), - key: "serials", - value: cachedSerials); + boxName: DB.instance.boxNameUsedSerialsCache(coin: coin), + key: "serials", + value: cachedSerials, + ); return cachedSerials; } catch (e, s) { diff --git a/lib/electrumx_rpc/electrumx.dart b/lib/electrumx_rpc/electrumx.dart index 6667c3de5..20f046320 100644 --- a/lib/electrumx_rpc/electrumx.dart +++ b/lib/electrumx_rpc/electrumx.dart @@ -132,6 +132,11 @@ class ElectrumX { final response = await _rpcClient!.request(jsonRequestString); + print("================================================="); + print("TYPE: ${response.runtimeType}"); + print("RESPONSE: $response"); + print("================================================="); + if (response["error"] != null) { if (response["error"] .toString() @@ -310,6 +315,13 @@ class ElectrumX { requestID: requestID, command: 'blockchain.headers.subscribe', ); + if (response["result"] == null) { + Logging.instance.log( + "getBlockHeadTip returned null response", + level: LogLevel.Error, + ); + throw 'getBlockHeadTip returned null response'; + } return Map.from(response["result"] as Map); } catch (e) { rethrow; diff --git a/lib/electrumx_rpc/rpc.dart b/lib/electrumx_rpc/rpc.dart index 3510e4113..c7f654cc2 100644 --- a/lib/electrumx_rpc/rpc.dart +++ b/lib/electrumx_rpc/rpc.dart @@ -1,7 +1,9 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'dart:typed_data'; +import 'package:mutex/mutex.dart'; import 'package:stackwallet/utilities/logger.dart'; // hacky fix to receive large jsonrpc responses @@ -12,65 +14,219 @@ class JsonRPC { this.useSSL = false, this.connectionTimeout = const Duration(seconds: 60), }); - bool useSSL; - String host; - int port; - Duration connectionTimeout; + final bool useSSL; + final String host; + final int port; + final Duration connectionTimeout; - Future request(String jsonRpcRequest) async { - Socket? socket; - final completer = Completer(); - final List responseData = []; + final _requestMutex = Mutex(); + final _JsonRPCRequestQueue _requestQueue = _JsonRPCRequestQueue(); + Socket? _socket; + StreamSubscription? _subscription; - void dataHandler(List data) { - responseData.addAll(data); + void _dataHandler(List data) { + if (_requestQueue.isEmpty) { + // probably just return although this case should never actually hit + return; + } - // 0x0A is newline - // https://electrumx-spesmilo.readthedocs.io/en/latest/protocol-basics.html - if (data.last == 0x0A) { - try { - final response = json.decode(String.fromCharCodes(responseData)); - completer.complete(response); - } catch (e, s) { - Logging.instance - .log("JsonRPC json.decode: $e\n$s", level: LogLevel.Error); - completer.completeError(e, s); - } finally { - socket?.destroy(); + final req = _requestQueue.next; + req.appendDataAndCheckIfComplete(data); + + if (req.isComplete) { + _onReqCompleted(req); + } + } + + void _errorHandler(Object error, StackTrace trace) { + Logging.instance.log( + "JsonRPC errorHandler: $error\n$trace", + level: LogLevel.Error, + ); + + final req = _requestQueue.next; + req.completer.completeError(error, trace); + _onReqCompleted(req); + } + + void _doneHandler() { + Logging.instance.log( + "JsonRPC doneHandler: " + "connection closed to $host:$port, destroying socket", + level: LogLevel.Info, + ); + + if (_requestQueue.isNotEmpty) { + Logging.instance.log( + "JsonRPC doneHandler: queue not empty but connection closed, " + "completing pending requests with errors", + level: LogLevel.Error, + ); + + for (final req in _requestQueue.queue) { + if (!req.isComplete) { + try { + throw Exception( + "JsonRPC doneHandler: socket closed " + "before request could complete", + ); + } catch (e, s) { + req.completer.completeError(e, s); + } } } + _requestQueue.clear(); } - void errorHandler(Object error, StackTrace trace) { - Logging.instance - .log("JsonRPC errorHandler: $error\n$trace", level: LogLevel.Error); - completer.completeError(error, trace); - socket?.destroy(); + disconnect(); + } + + void _onReqCompleted(_JsonRPCRequest req) { + _requestQueue.remove(req); + if (_requestQueue.isNotEmpty) { + _sendNextAvailableRequest(); + } + } + + void _sendNextAvailableRequest() { + if (_requestQueue.isEmpty) { + // TODO handle properly + throw Exception("JSON RPC queue empty"); } - void doneHandler() { - socket?.destroy(); - } + final req = _requestQueue.next; - if (useSSL) { - await SecureSocket.connect(host, port, - timeout: connectionTimeout, - onBadCertificate: (_) => true).then((Socket sock) { - socket = sock; - socket?.listen(dataHandler, - onError: errorHandler, onDone: doneHandler, cancelOnError: true); - }); + _socket!.write('${req.jsonRequest}\r\n'); + + req.initiateTimeout(const Duration(seconds: 10)); + // Logging.instance.log( + // "JsonRPC request: wrote request ${req.jsonRequest} " + // "to socket $host:$port", + // level: LogLevel.Info, + // ); + } + + Future request(String jsonRpcRequest) async { + // todo: handle this better? + // Do we need to check the subscription, too? + await _requestMutex.protect(() async { + if (_socket == null) { + Logging.instance.log( + "JsonRPC request: opening socket $host:$port", + level: LogLevel.Info, + ); + await connect(); + } + }); + + final req = _JsonRPCRequest( + jsonRequest: jsonRpcRequest, + completer: Completer(), + ); + + _requestQueue.add(req); + + // if this is the only/first request then send it right away + if (_requestQueue.length == 1) { + _sendNextAvailableRequest(); } else { - await Socket.connect(host, port, timeout: connectionTimeout) - .then((Socket sock) { - socket = sock; - socket?.listen(dataHandler, - onError: errorHandler, onDone: doneHandler, cancelOnError: true); - }); + // Logging.instance.log( + // "JsonRPC request: queued request $jsonRpcRequest " + // "to socket $host:$port", + // level: LogLevel.Info, + // ); } - socket?.write('$jsonRpcRequest\r\n'); + return req.completer.future.onError( + (error, stackTrace) => + Exception("return req.completer.future.onError: $error"), + ); + } - return completer.future; + void disconnect() { + // TODO: maybe clear req queue here and wrap in mutex? + _subscription?.cancel().then((_) => _subscription = null); + _socket?.destroy(); + _socket = null; + } + + Future connect() async { + if (useSSL) { + _socket ??= await SecureSocket.connect( + host, + port, + timeout: connectionTimeout, + onBadCertificate: (_) => true, + ); // TODO do not automatically trust bad certificates + } else { + _socket ??= await Socket.connect( + host, + port, + timeout: connectionTimeout, + ); + } + await _subscription?.cancel(); + _subscription = _socket!.listen( + _dataHandler, + onError: _errorHandler, + onDone: _doneHandler, + cancelOnError: true, + ); } } + +class _JsonRPCRequestQueue { + final List<_JsonRPCRequest> _rq = []; + + void add(_JsonRPCRequest req) => _rq.add(req); + + bool remove(_JsonRPCRequest req) => _rq.remove(req); + + void clear() => _rq.clear(); + + bool get isEmpty => _rq.isEmpty; + bool get isNotEmpty => _rq.isNotEmpty; + int get length => _rq.length; + _JsonRPCRequest get next => _rq.first; + List<_JsonRPCRequest> get queue => _rq.toList(growable: false); +} + +class _JsonRPCRequest { + final String jsonRequest; + final Completer completer; + final List _responseData = []; + + _JsonRPCRequest({required this.jsonRequest, required this.completer}); + + void appendDataAndCheckIfComplete(List data) { + _responseData.addAll(data); + // 0x0A is newline + // https://electrumx-spesmilo.readthedocs.io/en/latest/protocol-basics.html + if (data.last == 0x0A) { + try { + final response = json.decode(String.fromCharCodes(_responseData)); + completer.complete(response); + } catch (e, s) { + Logging.instance.log( + "JsonRPC json.decode: $e\n$s", + level: LogLevel.Error, + ); + completer.completeError(e, s); + } + } + } + + void initiateTimeout(Duration timeout) { + Future.delayed(timeout).then((_) { + if (!isComplete) { + try { + throw Exception("_JsonRPCRequest timed out: $jsonRequest"); + } catch (e, s) { + completer.completeError(e, s); + } + } + }); + } + + bool get isComplete => completer.isCompleted; +} diff --git a/lib/electrumx_rpc/rpc2.dart b/lib/electrumx_rpc/rpc2.dart new file mode 100644 index 000000000..15d2076cc --- /dev/null +++ b/lib/electrumx_rpc/rpc2.dart @@ -0,0 +1,261 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:mutex/mutex.dart'; +import 'package:stackwallet/utilities/logger.dart'; + +// hacky fix to receive large jsonrpc responses +class JsonRPC { + JsonRPC({ + required this.host, + required this.port, + this.useSSL = false, + this.connectionTimeout = const Duration(seconds: 60), + }); + final bool useSSL; + final String host; + final int port; + final Duration connectionTimeout; + + final _requestMutex = Mutex(); + final _JsonRPCRequestQueue _requestQueue = _JsonRPCRequestQueue(); + Socket? _socket; + StreamSubscription? _subscription; + + void _dataHandler(List data) { + _requestQueue.nextIncompleteReq.then((req) { + if (req != null) { + req.appendDataAndCheckIfComplete(data); + + if (req.isComplete) { + _onReqCompleted(req); + } + } else { + Logging.instance.log( + "_dataHandler found a null req!", + level: LogLevel.Warning, + ); + } + }); + } + + void _errorHandler(Object error, StackTrace trace) { + _requestQueue.nextIncompleteReq.then((req) { + if (req != null) { + req.completer.completeError(error, trace); + _onReqCompleted(req); + } else { + Logging.instance.log( + "_errorHandler found a null req!", + level: LogLevel.Warning, + ); + } + }); + } + + void _doneHandler() { + Logging.instance.log( + "JsonRPC doneHandler: " + "connection closed to $host:$port, destroying socket", + level: LogLevel.Info, + ); + + disconnect(reason: "JsonRPC _doneHandler() called"); + } + + void _onReqCompleted(_JsonRPCRequest req) { + _requestQueue.remove(req).then((value) { + if (kDebugMode) { + print("Request removed from queue: $value"); + } + // attempt to send next request + _sendNextAvailableRequest(); + }); + } + + void _sendNextAvailableRequest() { + _requestQueue.nextIncompleteReq.then((req) { + if (req != null) { + // \r\n required by electrumx server + _socket!.write('${req.jsonRequest}\r\n'); + + // TODO different timeout length? + req.initiateTimeout(const Duration(seconds: 10)); + } else { + Logging.instance.log( + "_sendNextAvailableRequest found a null req!", + level: LogLevel.Warning, + ); + } + }); + } + + // TODO: non dynamic type + Future request(String jsonRpcRequest) async { + await _requestMutex.protect(() async { + if (_socket == null) { + Logging.instance.log( + "JsonRPC request: opening socket $host:$port", + level: LogLevel.Info, + ); + await connect(); + } + }); + + final req = _JsonRPCRequest( + jsonRequest: jsonRpcRequest, + completer: Completer(), + ); + + // if this is the only/first request then send it right away + await _requestQueue.add( + req, + onInitialRequestAdded: _sendNextAvailableRequest, + ); + + return req.completer.future.onError( + (error, stackTrace) => Exception( + "return req.completer.future.onError: $error", + ), + ); + } + + Future disconnect({String reason = "none"}) async { + await _requestMutex.protect(() async { + await _subscription?.cancel(); + _subscription = null; + _socket?.destroy(); + _socket = null; + + // clean up remaining queue + await _requestQueue.completeRemainingWithError( + "JsonRPC disconnect() called with reason: \"$reason\"", + ); + }); + } + + Future connect() async { + if (_socket != null) { + throw Exception( + "JsonRPC attempted to connect to an already existing socket!", + ); + } + + if (useSSL) { + _socket = await SecureSocket.connect( + host, + port, + timeout: connectionTimeout, + onBadCertificate: (_) => true, + ); // TODO do not automatically trust bad certificates + } else { + _socket = await Socket.connect( + host, + port, + timeout: connectionTimeout, + ); + } + + _subscription = _socket!.listen( + _dataHandler, + onError: _errorHandler, + onDone: _doneHandler, + cancelOnError: true, + ); + } +} + +class _JsonRPCRequestQueue { + final m = Mutex(); + + final List<_JsonRPCRequest> _rq = []; + + Future add( + _JsonRPCRequest req, { + VoidCallback? onInitialRequestAdded, + }) async { + return await m.protect(() async { + _rq.add(req); + if (_rq.length == 1) { + onInitialRequestAdded?.call(); + } + }); + } + + Future remove(_JsonRPCRequest req) async { + return await m.protect(() async => _rq.remove(req)); + } + + Future<_JsonRPCRequest?> get nextIncompleteReq async { + return await m.protect(() async { + try { + return _rq.firstWhere((e) => !e.isComplete); + } catch (_) { + // no incomplete requests found + return null; + } + }); + } + + Future completeRemainingWithError( + String error, { + StackTrace? stackTrace, + }) async { + await m.protect(() async { + for (final req in _rq) { + if (!req.isComplete) { + req.completer.completeError(Exception(error), stackTrace); + } + } + _rq.clear(); + }); + } + + Future get isEmpty async { + return await m.protect(() async { + return _rq.isEmpty; + }); + } +} + +class _JsonRPCRequest { + final String jsonRequest; + final Completer completer; + final List _responseData = []; + + _JsonRPCRequest({required this.jsonRequest, required this.completer}); + + void appendDataAndCheckIfComplete(List data) { + _responseData.addAll(data); + // 0x0A is newline + // https://electrumx-spesmilo.readthedocs.io/en/latest/protocol-basics.html + if (data.last == 0x0A) { + try { + final response = json.decode(String.fromCharCodes(_responseData)); + completer.complete(response); + } catch (e, s) { + Logging.instance.log( + "JsonRPC json.decode: $e\n$s", + level: LogLevel.Error, + ); + completer.completeError(e, s); + } + } + } + + void initiateTimeout(Duration timeout) { + Future.delayed(timeout).then((_) { + if (!isComplete) { + try { + throw Exception("_JsonRPCRequest timed out: $jsonRequest"); + } catch (e, s) { + completer.completeError(e, s); + } + } + }); + } + + bool get isComplete => completer.isCompleted; +} diff --git a/lib/main.dart b/lib/main.dart index 5083dfdb2..d8b91f734 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -71,7 +71,7 @@ final openedFromSWBFileStringStateProvider = // runs the MyApp widget and checks for new users, caching the value in the // miscellaneous box for later use void main() async { - WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); + WidgetsFlutterBinding.ensureInitialized(); GoogleFonts.config.allowRuntimeFetching = false; if (Platform.isIOS) { Util.libraryPath = await getLibraryDirectory(); @@ -180,7 +180,9 @@ void main() async { } monero.onStartup(); - wownero.onStartup(); + if (!Platform.isLinux && !Platform.isWindows) { + wownero.onStartup(); + } dynamic tor = Tor(); tor.start(); @@ -192,33 +194,8 @@ void main() async { await MainDB.instance.initMainDB(); ThemeService.instance.init(MainDB.instance); - // install default themes - if (!(await ThemeService.instance.verifyInstalled(themeId: "light"))) { - Logging.instance.log( - "Installing default light theme...", - level: LogLevel.Info, - ); - final lightZip = await rootBundle.load("assets/default_themes/light.zip"); - await ThemeService.instance - .install(themeArchiveData: lightZip.buffer.asUint8List()); - Logging.instance.log( - "Installing default light theme... finished", - level: LogLevel.Info, - ); - } - if (!(await ThemeService.instance.verifyInstalled(themeId: "dark"))) { - Logging.instance.log( - "Installing default dark theme... ", - level: LogLevel.Info, - ); - final darkZip = await rootBundle.load("assets/default_themes/dark.zip"); - await ThemeService.instance - .install(themeArchiveData: darkZip.buffer.asUint8List()); - Logging.instance.log( - "Installing default dark theme... finished", - level: LogLevel.Info, - ); - } + // check and update or install default themes + await ThemeService.instance.checkDefaultThemesOnStartup(); runApp(const ProviderScope(child: MyApp())); } 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/add_wallet_views/add_wallet_view/add_wallet_view.dart b/lib/pages/add_wallet_views/add_wallet_view/add_wallet_view.dart index c8cd467e8..d2dd8e30f 100644 --- a/lib/pages/add_wallet_views/add_wallet_view/add_wallet_view.dart +++ b/lib/pages/add_wallet_views/add_wallet_view/add_wallet_view.dart @@ -120,6 +120,8 @@ class _AddWalletViewState extends ConsumerState { if (Platform.isWindows) { _coins.remove(Coin.monero); _coins.remove(Coin.wownero); + } else if (Platform.isLinux) { + _coins.remove(Coin.wownero); } coinEntities.addAll(_coins.map((e) => CoinEntity(e))); diff --git a/lib/pages/address_book_views/subviews/contact_details_view.dart b/lib/pages/address_book_views/subviews/contact_details_view.dart index b25120584..b7d54ea71 100644 --- a/lib/pages/address_book_views/subviews/contact_details_view.dart +++ b/lib/pages/address_book_views/subviews/contact_details_view.dart @@ -270,7 +270,7 @@ class _ContactDetailsViewState extends ConsumerState { onPressed: () { Navigator.of(context).pushNamed( EditContactNameEmojiView.routeName, - arguments: _contact.id, + arguments: _contact.customId, ); }, style: Theme.of(context) @@ -318,7 +318,7 @@ class _ContactDetailsViewState extends ConsumerState { onTap: () { Navigator.of(context).pushNamed( AddNewContactAddressView.routeName, - arguments: _contact.id, + arguments: _contact.customId, ); }, ), @@ -385,7 +385,7 @@ class _ContactDetailsViewState extends ConsumerState { Navigator.of(context).pushNamed( EditContactAddressView.routeName, - arguments: Tuple2(_contact.id, e), + arguments: Tuple2(_contact.customId, e), ); }, child: RoundedContainer( diff --git a/lib/pages/address_book_views/subviews/contact_popup.dart b/lib/pages/address_book_views/subviews/contact_popup.dart index 88cad7f33..fafebfcb1 100644 --- a/lib/pages/address_book_views/subviews/contact_popup.dart +++ b/lib/pages/address_book_views/subviews/contact_popup.dart @@ -110,7 +110,7 @@ class ContactPopUp extends ConsumerWidget { .textFieldDefaultBG, borderRadius: BorderRadius.circular(32), ), - child: contact.id == "default" + child: contact.customId == "default" ? Center( child: SvgPicture.file( File( @@ -146,13 +146,13 @@ class ContactPopUp extends ConsumerWidget { STextStyles.itemSubtitle12(context), ), ), - if (contact.id != "default") + if (contact.customId != "default") TextButton( onPressed: () { Navigator.pop(context); Navigator.of(context).pushNamed( ContactDetailsView.routeName, - arguments: contact.id, + arguments: contact.customId, ); }, style: Theme.of(context) @@ -176,7 +176,7 @@ class ContactPopUp extends ConsumerWidget { ), ), SizedBox( - height: contact.id == "default" ? 16 : 8, + height: contact.customId == "default" ? 16 : 8, ), Container( height: 1, @@ -233,14 +233,14 @@ class ContactPopUp extends ConsumerWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (contact.id == "default") + if (contact.customId == "default") Text( e.other!, style: STextStyles.itemSubtitle12( context), ), - if (contact.id != "default") + if (contact.customId != "default") Text( "${e.label} (${e.coin.ticker})", style: @@ -336,13 +336,13 @@ class ContactPopUp extends ConsumerWidget { ), ], ), - if (contact.id != "default" && + if (contact.customId != "default" && hasActiveWallet && !isExchangeFlow) const SizedBox( width: 4, ), - if (contact.id != "default" && + if (contact.customId != "default" && hasActiveWallet && !isExchangeFlow) Column( diff --git a/lib/pages/exchange_view/trade_details_view.dart b/lib/pages/exchange_view/trade_details_view.dart index 3618a14a5..89501a74c 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, @@ -1232,7 +1224,7 @@ class _TradeDetailsViewState extends ConsumerState { if (trade.exchangeName .startsWith(TrocadorExchange.exchangeName)) { url = - "https://trocador.app/en/checkout${trade.tradeId}"; + "https://trocador.app/en/checkout/${trade.tradeId}"; } } return ConditionalParent( diff --git a/lib/pages/paynym/paynym_claim_view.dart b/lib/pages/paynym/paynym_claim_view.dart index c703c581d..efeb33071 100644 --- a/lib/pages/paynym/paynym_claim_view.dart +++ b/lib/pages/paynym/paynym_claim_view.dart @@ -138,10 +138,8 @@ class _PaynymClaimViewState extends ConsumerState { const Spacer( flex: 1, ), - Image( - image: AssetImage( - Assets.svg.unclaimedPaynym, - ), + SvgPicture.asset( + Assets.svg.unclaimedPaynym, width: MediaQuery.of(context).size.width / 2, ), const SizedBox( diff --git a/lib/pages/pinpad_views/create_pin_view.dart b/lib/pages/pinpad_views/create_pin_view.dart index 7ad3344ec..18e974ebf 100644 --- a/lib/pages/pinpad_views/create_pin_view.dart +++ b/lib/pages/pinpad_views/create_pin_view.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; @@ -9,7 +10,6 @@ import 'package:stackwallet/providers/global/secure_store_provider.dart'; import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/biometrics.dart'; -import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/widgets/background.dart'; @@ -35,10 +35,11 @@ class CreatePinView extends ConsumerStatefulWidget { class _CreatePinViewState extends ConsumerState { BoxDecoration get _pinPutDecoration { return BoxDecoration( - color: Theme.of(context).extension()!.textSubtitle3, + color: Theme.of(context).extension()!.infoItemIcons, border: Border.all( - width: 1, - color: Theme.of(context).extension()!.textSubtitle3), + width: 1, + color: Theme.of(context).extension()!.infoItemIcons, + ), borderRadius: BorderRadius.circular(6), ); } @@ -57,10 +58,13 @@ class _CreatePinViewState extends ConsumerState { late SecureStorageInterface _secureStore; late Biometrics biometrics; + int pinCount = 1; + @override initState() { _secureStore = ref.read(secureStoreProvider); biometrics = widget.biometrics; + super.initState(); } @@ -71,11 +75,13 @@ class _CreatePinViewState extends ConsumerState { _pinPutController2.dispose(); _pinPutFocusNode1.dispose(); _pinPutFocusNode2.dispose(); + super.dispose(); } @override Widget build(BuildContext context) { + // int pinCount = 1; return Background( child: Scaffold( backgroundColor: Theme.of(context).extension()!.background, @@ -116,7 +122,7 @@ class _CreatePinViewState extends ConsumerState { height: 36, ), CustomPinPut( - fieldsCount: Constants.pinLength, + fieldsCount: pinCount, eachFieldHeight: 12, eachFieldWidth: 12, textStyle: STextStyles.label(context).copyWith( @@ -140,21 +146,23 @@ class _CreatePinViewState extends ConsumerState { ), isRandom: ref.read(prefsChangeNotifierProvider).randomizePIN, - submittedFieldDecoration: _pinPutDecoration.copyWith( - color: Theme.of(context) - .extension()! - .infoItemIcons, - border: Border.all( - width: 1, - color: Theme.of(context) - .extension()! - .infoItemIcons, - ), - ), + submittedFieldDecoration: _pinPutDecoration, selectedFieldDecoration: _pinPutDecoration, followingFieldDecoration: _pinPutDecoration, + onPinLengthChanged: (newLength) { + setState(() { + pinCount = newLength; + }); + }, onSubmit: (String pin) { - if (pin.length == Constants.pinLength) { + if (pin.length < 4) { + showFloatingFlushBar( + type: FlushBarType.warning, + message: "PIN not long enough!", + iconAsset: Assets.svg.alertCircle, + context: context, + ); + } else { _pageController.nextPage( duration: const Duration(milliseconds: 300), curve: Curves.linear, @@ -184,7 +192,7 @@ class _CreatePinViewState extends ConsumerState { height: 36, ), CustomPinPut( - fieldsCount: Constants.pinLength, + fieldsCount: pinCount, eachFieldHeight: 12, eachFieldWidth: 12, textStyle: STextStyles.infoSmall(context).copyWith( diff --git a/lib/pages/pinpad_views/lock_screen_view.dart b/lib/pages/pinpad_views/lock_screen_view.dart index 802488fca..5df9acbf2 100644 --- a/lib/pages/pinpad_views/lock_screen_view.dart +++ b/lib/pages/pinpad_views/lock_screen_view.dart @@ -13,7 +13,6 @@ import 'package:stackwallet/themes/stack_colors.dart'; // import 'package:stackwallet/providers/global/should_show_lockscreen_on_resume_state_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/biometrics.dart'; -import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/show_loading.dart'; @@ -189,10 +188,11 @@ class _LockscreenViewState extends ConsumerState { BoxDecoration get _pinPutDecoration { return BoxDecoration( - color: Theme.of(context).extension()!.textSubtitle2, + color: Theme.of(context).extension()!.infoItemIcons, border: Border.all( - width: 1, - color: Theme.of(context).extension()!.textSubtitle2), + width: 1, + color: Theme.of(context).extension()!.infoItemIcons, + ), borderRadius: BorderRadius.circular(6), ); } @@ -202,6 +202,7 @@ class _LockscreenViewState extends ConsumerState { late SecureStorageInterface _secureStore; late Biometrics biometrics; + int pinCount = 1; Widget get _body => Background( child: SafeArea( @@ -274,13 +275,7 @@ class _LockscreenViewState extends ConsumerState { height: 52, ), CustomPinPut( - // customKey: CustomKey( - // onPressed: _checkUseBiometrics, - // iconAssetName: Platform.isIOS - // ? Assets.svg.faceId - // : Assets.svg.fingerprint, - // ), - fieldsCount: Constants.pinLength, + fieldsCount: pinCount, eachFieldHeight: 12, eachFieldWidth: 12, textStyle: STextStyles.label(context).copyWith( @@ -302,19 +297,7 @@ class _LockscreenViewState extends ConsumerState { .background, counterText: "", ), - submittedFieldDecoration: _pinPutDecoration.copyWith( - color: Theme.of(context) - .extension()! - .infoItemIcons, - border: Border.all( - width: 1, - color: Theme.of(context) - .extension()! - .infoItemIcons, - ), - ), - selectedFieldDecoration: _pinPutDecoration, - followingFieldDecoration: _pinPutDecoration, + submittedFieldDecoration: _pinPutDecoration, isRandom: ref .read(prefsChangeNotifierProvider) .randomizePIN, diff --git a/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart b/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart index bc1281156..46867c7e1 100644 --- a/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart +++ b/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart @@ -150,6 +150,7 @@ class _GenerateUriQrCodeViewState extends State { String receivingAddress = widget.receivingAddress; if ((widget.coin == Coin.bitcoincash || + widget.coin == Coin.eCash || widget.coin == Coin.bitcoincashTestnet) && receivingAddress.contains(":")) { // remove cash addr prefix @@ -246,6 +247,7 @@ class _GenerateUriQrCodeViewState extends State { String receivingAddress = widget.receivingAddress; if ((widget.coin == Coin.bitcoincash || + widget.coin == Coin.eCash || widget.coin == Coin.bitcoincashTestnet) && receivingAddress.contains(":")) { // remove cash addr prefix diff --git a/lib/pages/settings_views/global_settings_view/appearance_settings/manage_themes.dart b/lib/pages/settings_views/global_settings_view/appearance_settings/manage_themes.dart index 1b7fc0415..b363108c7 100644 --- a/lib/pages/settings_views/global_settings_view/appearance_settings/manage_themes.dart +++ b/lib/pages/settings_views/global_settings_view/appearance_settings/manage_themes.dart @@ -225,14 +225,14 @@ class _IncognitoInstalledThemesState extends ConsumerState { late final StreamSubscription _subscription; - List> installedThemeIdNames = []; + List> installedThemeIdNames = []; void _updateInstalledList() { installedThemeIdNames = ref .read(pThemeService) .installedThemes .where((e) => e.themeId != "light" && e.themeId != "dark") - .map((e) => Tuple2(e.themeId, e.name)) + .map((e) => Tuple3(e.themeId, e.name, e.version)) .toList(); } @@ -274,6 +274,7 @@ class _IncognitoInstalledThemesState data: StackThemeMetaData( name: e.item2, id: e.item1, + version: e.item3 ?? 1, sha256: "", size: "", previewImageUrl: "", diff --git a/lib/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/stack_theme_card.dart b/lib/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/stack_theme_card.dart index 7b36c873a..2f6f03091 100644 --- a/lib/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/stack_theme_card.dart +++ b/lib/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/stack_theme_card.dart @@ -39,6 +39,7 @@ class _StackThemeCardState extends ConsumerState { late final StreamSubscription _subscription; late bool _hasTheme; + bool _needsUpdate = false; String? _cachedSize; Future _downloadAndInstall() async { @@ -84,6 +85,7 @@ class _StackThemeCardState extends ConsumerState { title: message, onOkPressed: (_) { setState(() { + _needsUpdate = !result; _hasTheme = result; }); }, @@ -141,16 +143,21 @@ class _StackThemeCardState extends ConsumerState { } } + StackTheme? getInstalled() => ref + .read(mainDBProvider) + .isar + .stackThemes + .where() + .themeIdEqualTo(widget.data.id) + .findFirstSync(); + @override void initState() { - _hasTheme = ref - .read(mainDBProvider) - .isar - .stackThemes - .where() - .themeIdEqualTo(widget.data.id) - .countSync() > - 0; + final installedTheme = getInstalled(); + _hasTheme = installedTheme != null; + if (_hasTheme) { + _needsUpdate = widget.data.version > (installedTheme?.version ?? 0); + } _subscription = ref .read(mainDBProvider) @@ -158,18 +165,16 @@ class _StackThemeCardState extends ConsumerState { .stackThemes .watchLazy() .listen((event) async { - final hasTheme = (await ref - .read(mainDBProvider) - .isar - .stackThemes - .where() - .themeIdEqualTo(widget.data.id) - .count()) > - 0; + final installedTheme = getInstalled(); + final hasTheme = installedTheme != null; if (_hasTheme != hasTheme && mounted) { WidgetsBinding.instance.addPostFrameCallback((timeStamp) { setState(() { _hasTheme = hasTheme; + if (hasTheme) { + _needsUpdate = + widget.data.version > (installedTheme.version ?? 0); + } }); }); } @@ -272,6 +277,16 @@ class _StackThemeCardState extends ConsumerState { } }, ), + if (_hasTheme && _needsUpdate) + const SizedBox( + height: 12, + ), + if (_hasTheme && _needsUpdate) + PrimaryButton( + label: "Update", + buttonHeight: isDesktop ? ButtonHeight.s : ButtonHeight.l, + onPressed: _downloadPressed, + ), const SizedBox( height: 12, ), 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 690b54be6..aa5461ab2 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 @@ -150,6 +150,7 @@ class _AddEditNodeViewState extends ConsumerState { case Coin.bitcoincash: case Coin.litecoin: case Coin.dogecoin: + case Coin.eCash: case Coin.firo: case Coin.namecoin: case Coin.particl: @@ -717,6 +718,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/pages/settings_views/global_settings_view/security_views/change_pin_view/change_pin_view.dart b/lib/pages/settings_views/global_settings_view/security_views/change_pin_view/change_pin_view.dart index 0e848ec12..c88233a6d 100644 --- a/lib/pages/settings_views/global_settings_view/security_views/change_pin_view/change_pin_view.dart +++ b/lib/pages/settings_views/global_settings_view/security_views/change_pin_view/change_pin_view.dart @@ -6,7 +6,6 @@ import 'package:stackwallet/providers/global/prefs_provider.dart'; import 'package:stackwallet/providers/global/secure_store_provider.dart'; import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; -import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/widgets/background.dart'; @@ -27,10 +26,11 @@ class ChangePinView extends ConsumerStatefulWidget { class _ChangePinViewState extends ConsumerState { BoxDecoration get _pinPutDecoration { return BoxDecoration( - color: Theme.of(context).extension()!.textSubtitle2, + color: Theme.of(context).extension()!.infoItemIcons, border: Border.all( - width: 1, - color: Theme.of(context).extension()!.textSubtitle2), + width: 1, + color: Theme.of(context).extension()!.infoItemIcons, + ), borderRadius: BorderRadius.circular(6), ); } @@ -48,6 +48,8 @@ class _ChangePinViewState extends ConsumerState { late final SecureStorageInterface _secureStore; + int pinCount = 1; + @override void initState() { _secureStore = ref.read(secureStoreProvider); @@ -101,7 +103,7 @@ class _ChangePinViewState extends ConsumerState { height: 52, ), CustomPinPut( - fieldsCount: Constants.pinLength, + fieldsCount: pinCount, eachFieldHeight: 12, eachFieldWidth: 12, textStyle: STextStyles.label(context).copyWith( @@ -125,21 +127,18 @@ class _ChangePinViewState extends ConsumerState { ), isRandom: ref.read(prefsChangeNotifierProvider).randomizePIN, - submittedFieldDecoration: _pinPutDecoration.copyWith( - color: Theme.of(context) - .extension()! - .infoItemIcons, - border: Border.all( - width: 1, - color: Theme.of(context) - .extension()! - .infoItemIcons, - ), - ), + submittedFieldDecoration: _pinPutDecoration, selectedFieldDecoration: _pinPutDecoration, followingFieldDecoration: _pinPutDecoration, onSubmit: (String pin) { - if (pin.length == Constants.pinLength) { + if (pin.length < 4) { + showFloatingFlushBar( + type: FlushBarType.warning, + message: "PIN not long enough!", + iconAsset: Assets.svg.alertCircle, + context: context, + ); + } else { _pageController.nextPage( duration: const Duration(milliseconds: 300), curve: Curves.linear, @@ -165,7 +164,7 @@ class _ChangePinViewState extends ConsumerState { height: 52, ), CustomPinPut( - fieldsCount: Constants.pinLength, + fieldsCount: pinCount, eachFieldHeight: 12, eachFieldWidth: 12, textStyle: STextStyles.infoSmall(context).copyWith( @@ -192,17 +191,7 @@ class _ChangePinViewState extends ConsumerState { ), isRandom: ref.read(prefsChangeNotifierProvider).randomizePIN, - submittedFieldDecoration: _pinPutDecoration.copyWith( - color: Theme.of(context) - .extension()! - .infoItemIcons, - border: Border.all( - width: 1, - color: Theme.of(context) - .extension()! - .infoItemIcons, - ), - ), + submittedFieldDecoration: _pinPutDecoration, selectedFieldDecoration: _pinPutDecoration, followingFieldDecoration: _pinPutDecoration, onSubmit: (String pin) async { diff --git a/lib/pages/settings_views/global_settings_view/support_view.dart b/lib/pages/settings_views/global_settings_view/support_view.dart index 19dae67ec..0f38912c0 100644 --- a/lib/pages/settings_views/global_settings_view/support_view.dart +++ b/lib/pages/settings_views/global_settings_view/support_view.dart @@ -190,7 +190,7 @@ class AboutItem extends StatelessWidget { height: iconSize, color: Theme.of(context) .extension()! - .bottomNavIconIcon, + .topNavIconPrimary, ), ), const SizedBox( 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/wallet_view/transaction_views/transaction_details_view.dart b/lib/pages/wallet_view/transaction_views/transaction_details_view.dart index 5b8e04e0e..d3f8467cd 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,66 @@ 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: () { + if (isDesktop) { + showDialog( + context: context, + builder: (_) => + DesktopDialog( + maxHeight: + double.infinity, + child: + AddressDetailsView( + addressId: + _transaction + .address + .value! + .id, + walletId: widget + .walletId, + ), + ), + ); + } else { + 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, @@ -982,6 +1033,7 @@ class _TransactionDetailsViewState final String height; if (widget.coin == Coin.bitcoincash || + widget.coin == Coin.eCash || widget.coin == Coin.bitcoincashTestnet) { height = "${_transaction.height != null && _transaction.height! > 0 ? _transaction.height! : "Pending"}"; @@ -1099,6 +1151,46 @@ class _TransactionDetailsViewState ], ), ), + if (kDebugMode) + isDesktop + ? const _Divider() + : const SizedBox( + height: 12, + ), + if (kDebugMode) + RoundedWhiteContainer( + padding: isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + "Tx sub type", + style: isDesktop + ? STextStyles + .desktopTextExtraExtraSmall( + context) + : STextStyles.itemSubtitle(context), + ), + SelectableText( + _transaction.subType.toString(), + style: isDesktop + ? STextStyles + .desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ) + : STextStyles.itemSubtitle12(context), + ), + ], + ), + ), isDesktop ? const _Divider() : const SizedBox( diff --git a/lib/pages/wallets_view/sub_widgets/favorite_card.dart b/lib/pages/wallets_view/sub_widgets/favorite_card.dart index 87ffbccd1..8daf05a5d 100644 --- a/lib/pages/wallets_view/sub_widgets/favorite_card.dart +++ b/lib/pages/wallets_view/sub_widgets/favorite_card.dart @@ -6,7 +6,7 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:stackwallet/pages/wallet_view/wallet_view.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart'; import 'package:stackwallet/providers/providers.dart'; -import 'package:stackwallet/services/coins/manager.dart'; +import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; import 'package:stackwallet/themes/coin_icon_provider.dart'; import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/amount/amount.dart'; @@ -24,13 +24,11 @@ class FavoriteCard extends ConsumerStatefulWidget { required this.walletId, required this.width, required this.height, - required this.managerProvider, }) : super(key: key); final String walletId; final double width; final double height; - final ChangeNotifierProvider managerProvider; @override ConsumerState createState() => _FavoriteCardState(); @@ -38,15 +36,10 @@ class FavoriteCard extends ConsumerStatefulWidget { class _FavoriteCardState extends ConsumerState { late final String walletId; - late final ChangeNotifierProvider managerProvider; - - Amount _cachedBalance = Amount.zero; - Amount _cachedFiatValue = Amount.zero; @override void initState() { walletId = widget.walletId; - managerProvider = widget.managerProvider; super.initState(); } @@ -55,9 +48,13 @@ class _FavoriteCardState extends ConsumerState { @override Widget build(BuildContext context) { - final coin = ref.watch(managerProvider.select((value) => value.coin)); + final coin = ref.watch( + walletsChangeNotifierProvider + .select((value) => value.getManager(walletId).coin), + ); final externalCalls = ref.watch( - prefsChangeNotifierProvider.select((value) => value.externalCalls)); + prefsChangeNotifierProvider.select((value) => value.externalCalls), + ); return ConditionalParent( condition: Util.isDesktop, @@ -109,7 +106,10 @@ class _FavoriteCardState extends ConsumerState { child: GestureDetector( onTap: () async { if (coin == Coin.monero || coin == Coin.wownero) { - await ref.read(managerProvider).initializeExisting(); + await ref + .read(walletsChangeNotifierProvider) + .getManager(walletId) + .initializeExisting(); } if (mounted) { if (Util.isDesktop) { @@ -122,7 +122,9 @@ class _FavoriteCardState extends ConsumerState { WalletView.routeName, arguments: Tuple2( walletId, - managerProvider, + ref + .read(walletsChangeNotifierProvider) + .getManagerProvider(walletId), ), ); } @@ -205,8 +207,12 @@ class _FavoriteCardState extends ConsumerState { children: [ Expanded( child: Text( - ref.watch(managerProvider - .select((value) => value.walletName)), + ref.watch( + walletsChangeNotifierProvider.select( + (value) => + value.getManager(walletId).walletName, + ), + ), style: STextStyles.itemSubtitle12(context).copyWith( color: Theme.of(context) .extension()! @@ -225,41 +231,54 @@ class _FavoriteCardState extends ConsumerState { ], ), ), - FutureBuilder( - future: Future(() => ref.watch(managerProvider - .select((value) => value.balance.total))), - builder: (builderContext, AsyncSnapshot snapshot) { - if (snapshot.connectionState == ConnectionState.done && - snapshot.hasData) { - if (snapshot.data != null) { - _cachedBalance = snapshot.data!; - if (externalCalls && _cachedBalance > Amount.zero) { - _cachedFiatValue = (_cachedBalance.decimal * - ref - .watch( - priceAnd24hChangeNotifierProvider - .select( - (value) => value.getPrice(coin), - ), - ) - .item1) - .toAmount(fractionDigits: 2); - } - } + Builder( + builder: (context) { + final balance = ref.watch( + walletsChangeNotifierProvider.select( + (value) => value.getManager(walletId).balance, + ), + ); + + Amount total = balance.total; + if (coin == Coin.firo || coin == Coin.firoTestNet) { + final balancePrivate = ref.watch( + walletsChangeNotifierProvider.select( + (value) => (value.getManager(walletId).wallet + as FiroWallet) + .balancePrivate, + ), + ); + + total += balancePrivate.total; } + + Amount fiatTotal = Amount.zero; + + if (externalCalls && total > Amount.zero) { + fiatTotal = (total.decimal * + ref + .watch( + priceAnd24hChangeNotifierProvider.select( + (value) => value.getPrice(coin), + ), + ) + .item1) + .toAmount(fractionDigits: 2); + } + return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ FittedBox( fit: BoxFit.scaleDown, child: Text( - "${_cachedBalance.localizedStringAsFixed( + "${total.localizedStringAsFixed( locale: ref.watch( - localeServiceChangeNotifierProvider - .select((value) => value.locale), + localeServiceChangeNotifierProvider.select( + (value) => value.locale, + ), ), - decimalPlaces: ref.watch(managerProvider - .select((value) => value.coin.decimals)), + decimalPlaces: coin.decimals, )} ${coin.ticker}", style: STextStyles.titleBold12(context).copyWith( fontSize: 16, @@ -275,15 +294,17 @@ class _FavoriteCardState extends ConsumerState { ), if (externalCalls) Text( - "${_cachedFiatValue.localizedStringAsFixed( + "${fiatTotal.localizedStringAsFixed( locale: ref.watch( - localeServiceChangeNotifierProvider - .select((value) => value.locale), + localeServiceChangeNotifierProvider.select( + (value) => value.locale, + ), ), decimalPlaces: 2, )} ${ref.watch( - prefsChangeNotifierProvider - .select((value) => value.currency), + prefsChangeNotifierProvider.select( + (value) => value.currency, + ), )}", style: STextStyles.itemSubtitle12(context).copyWith( diff --git a/lib/pages/wallets_view/sub_widgets/favorite_wallets.dart b/lib/pages/wallets_view/sub_widgets/favorite_wallets.dart index afbea4562..665c147e1 100644 --- a/lib/pages/wallets_view/sub_widgets/favorite_wallets.dart +++ b/lib/pages/wallets_view/sub_widgets/favorite_wallets.dart @@ -211,7 +211,6 @@ class _FavoriteWalletsState extends ConsumerState { child: FavoriteCard( key: Key("favCard_$walletId"), walletId: walletId!, - managerProvider: managerProvider!, width: cardWidth, height: cardHeight, ), @@ -219,7 +218,6 @@ class _FavoriteWalletsState extends ConsumerState { : FavoriteCard( key: Key("favCard_$walletId"), walletId: walletId!, - managerProvider: managerProvider!, width: cardWidth, height: cardHeight, ) diff --git a/lib/pages_desktop_specific/address_book_view/subwidgets/desktop_contact_details.dart b/lib/pages_desktop_specific/address_book_view/subwidgets/desktop_contact_details.dart index 453fe1cb2..9e1701fd2 100644 --- a/lib/pages_desktop_specific/address_book_view/subwidgets/desktop_contact_details.dart +++ b/lib/pages_desktop_specific/address_book_view/subwidgets/desktop_contact_details.dart @@ -111,7 +111,7 @@ class _DesktopContactDetailsState extends ConsumerState { width: 32, height: 32, decoration: BoxDecoration( - color: contact.id == "default" + color: contact.customId == "default" ? Colors.transparent : Theme.of(context) .extension()! 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/pages_desktop_specific/desktop_menu_item.dart b/lib/pages_desktop_specific/desktop_menu_item.dart index 10c2ad25d..a194c87b2 100644 --- a/lib/pages_desktop_specific/desktop_menu_item.dart +++ b/lib/pages_desktop_specific/desktop_menu_item.dart @@ -63,8 +63,8 @@ class DesktopBuyIcon extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return SvgPicture.asset( - ref.watch(themeProvider.select((value) => value.assets.buy)), + return SvgPicture.file( + File(ref.watch(themeAssetsProvider).buy), width: 20, height: 20, color: DesktopMenuItemId.buy == diff --git a/lib/pages_desktop_specific/my_stack_view/desktop_favorite_wallets.dart b/lib/pages_desktop_specific/my_stack_view/desktop_favorite_wallets.dart index b9ce38eb4..03d0df2ca 100644 --- a/lib/pages_desktop_specific/my_stack_view/desktop_favorite_wallets.dart +++ b/lib/pages_desktop_specific/my_stack_view/desktop_favorite_wallets.dart @@ -74,7 +74,6 @@ class DesktopFavoriteWallets extends ConsumerWidget { key: Key(walletName), width: cardWidth, height: cardHeight, - managerProvider: managerProvider, ); }) ], diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/more_features/more_features_dialog.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/more_features/more_features_dialog.dart index e2dbfa015..1549bf1c9 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/more_features/more_features_dialog.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/more_features/more_features_dialog.dart @@ -69,7 +69,7 @@ class _MoreFeaturesDialogState extends ConsumerState { _MoreFeaturesItem( label: "Anonymize funds", detail: "Anonymize funds", - iconAsset: Assets.svg.anonymize, + iconAsset: Assets.svg.recycle, onPressed: () => widget.onAnonymizeAllPressed?.call(), ), if (manager.hasWhirlpoolSupport) diff --git a/lib/pages_desktop_specific/password/desktop_login_view.dart b/lib/pages_desktop_specific/password/desktop_login_view.dart index 3a145e542..6ec7e0115 100644 --- a/lib/pages_desktop_specific/password/desktop_login_view.dart +++ b/lib/pages_desktop_specific/password/desktop_login_view.dart @@ -252,7 +252,7 @@ class _DesktopLoginViewState extends ConsumerState { ), suffixIcon: UnconstrainedBox( child: SizedBox( - height: 70, + height: 40, child: Row( children: [ const SizedBox( diff --git a/lib/pages_desktop_specific/settings/settings_menu/appearance_settings/appearance_settings.dart b/lib/pages_desktop_specific/settings/settings_menu/appearance_settings/appearance_settings.dart index 4c500eaf7..a005028b1 100644 --- a/lib/pages_desktop_specific/settings/settings_menu/appearance_settings/appearance_settings.dart +++ b/lib/pages_desktop_specific/settings/settings_menu/appearance_settings/appearance_settings.dart @@ -118,44 +118,6 @@ class _AppearanceOptionSettings ], ), ), - // const Padding( - // padding: EdgeInsets.all(10.0), - // child: Divider( - // thickness: 0.5, - // ), - // ), - // Padding( - // padding: const EdgeInsets.all(10.0), - // child: Row( - // mainAxisAlignment: MainAxisAlignment.spaceBetween, - // children: [ - // Text( - // "System brightness", - // style: STextStyles.desktopTextExtraSmall(context) - // .copyWith( - // color: Theme.of(context) - // .extension()! - // .textDark), - // textAlign: TextAlign.left, - // ), - // SizedBox( - // height: 20, - // width: 40, - // child: DraggableSwitchButton( - // isOn: ref.watch( - // prefsChangeNotifierProvider.select( - // (value) => value.enableSystemBrightness), - // ), - // onValueChanged: (newValue) { - // ref - // .read(prefsChangeNotifierProvider) - // .enableSystemBrightness = newValue; - // }, - // ), - // ) - // ], - // ), - // ), const Padding( padding: EdgeInsets.all(10.0), child: Divider( diff --git a/lib/pages_desktop_specific/settings/settings_menu/nodes_settings.dart b/lib/pages_desktop_specific/settings/settings_menu/nodes_settings.dart index 7c39b6c91..1bab6ab58 100644 --- a/lib/pages_desktop_specific/settings/settings_menu/nodes_settings.dart +++ b/lib/pages_desktop_specific/settings/settings_menu/nodes_settings.dart @@ -58,6 +58,8 @@ class _NodesSettings extends ConsumerState { if (Platform.isWindows) { _coins.remove(Coin.monero); _coins.remove(Coin.wownero); + } else if (Platform.isLinux) { + _coins.remove(Coin.wownero); } searchNodeController = TextEditingController(); diff --git a/lib/services/coins/bitcoin/bitcoin_wallet.dart b/lib/services/coins/bitcoin/bitcoin_wallet.dart index cfa3b73cf..0c7c86059 100644 --- a/lib/services/coins/bitcoin/bitcoin_wallet.dart +++ b/lib/services/coins/bitcoin/bitcoin_wallet.dart @@ -1342,16 +1342,14 @@ class BitcoinWallet extends CoinServiceAPI )) .toList(); final newNode = await getCurrentNode(); - _cachedElectrumXClient = CachedElectrumX.from( - node: newNode, - prefs: _prefs, - failovers: failovers, - ); _electrumXClient = ElectrumX.from( node: newNode, prefs: _prefs, failovers: failovers, ); + _cachedElectrumXClient = CachedElectrumX.from( + electrumXClient: _electrumXClient, + ); if (shouldRefresh) { unawaited(refresh()); @@ -1468,7 +1466,7 @@ class BitcoinWallet extends CoinServiceAPI } await _secureStore.write( key: '${_walletId}_mnemonic', - value: bip39.generateMnemonic(strength: 256)); + value: bip39.generateMnemonic(strength: 128)); await _secureStore.write(key: '${_walletId}_mnemonicPassphrase', value: ""); // Generate and add addresses to relevant arrays diff --git a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart index e5117d08b..47458a7d8 100644 --- a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart +++ b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart @@ -1234,16 +1234,14 @@ class BitcoinCashWallet extends CoinServiceAPI )) .toList(); final newNode = await getCurrentNode(); - _cachedElectrumXClient = CachedElectrumX.from( - node: newNode, - prefs: _prefs, - failovers: failovers, - ); _electrumXClient = ElectrumX.from( node: newNode, prefs: _prefs, failovers: failovers, ); + _cachedElectrumXClient = CachedElectrumX.from( + electrumXClient: _electrumXClient, + ); if (shouldRefresh) { unawaited(refresh()); @@ -1371,7 +1369,7 @@ class BitcoinCashWallet extends CoinServiceAPI } await _secureStore.write( key: '${_walletId}_mnemonic', - value: bip39.generateMnemonic(strength: 256)); + value: bip39.generateMnemonic(strength: 128)); await _secureStore.write(key: '${_walletId}_mnemonicPassphrase', value: ""); // Generate and add addresses to relevant arrays diff --git a/lib/services/coins/coin_service.dart b/lib/services/coins/coin_service.dart index ff75822fd..28e5cc8ff 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'; @@ -55,17 +56,7 @@ abstract class CoinServiceAPI { prefs: prefs, ); final cachedClient = CachedElectrumX.from( - node: electrumxNode, - failovers: failovers - .map((e) => ElectrumXNode( - address: e.host, - port: e.port, - name: e.name, - id: e.id, - useSSL: e.useSSL, - )) - .toList(), - prefs: prefs, + electrumXClient: client, ); switch (coin) { case Coin.firo: @@ -233,6 +224,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/dogecoin/dogecoin_wallet.dart b/lib/services/coins/dogecoin/dogecoin_wallet.dart index 05170a883..cc081539c 100644 --- a/lib/services/coins/dogecoin/dogecoin_wallet.dart +++ b/lib/services/coins/dogecoin/dogecoin_wallet.dart @@ -1194,16 +1194,14 @@ class DogecoinWallet extends CoinServiceAPI )) .toList(); final newNode = await getCurrentNode(); - _cachedElectrumXClient = CachedElectrumX.from( - node: newNode, - prefs: _prefs, - failovers: failovers, - ); _electrumXClient = ElectrumX.from( node: newNode, prefs: _prefs, failovers: failovers, ); + _cachedElectrumXClient = CachedElectrumX.from( + electrumXClient: _electrumXClient, + ); if (shouldRefresh) { unawaited(refresh()); @@ -1318,7 +1316,7 @@ class DogecoinWallet extends CoinServiceAPI } await _secureStore.write( key: '${_walletId}_mnemonic', - value: bip39.generateMnemonic(strength: 256)); + value: bip39.generateMnemonic(strength: 128)); await _secureStore.write( key: '${_walletId}_mnemonicPassphrase', value: "", diff --git a/lib/services/coins/ecash/ecash_wallet.dart b/lib/services/coins/ecash/ecash_wallet.dart new file mode 100644 index 000000000..4459c3fbc --- /dev/null +++ b/lib/services/coins/ecash/ecash_wallet.dart @@ -0,0 +1,3137 @@ +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:bitbox/bitbox.dart' as bitbox; +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/transaction_notification_tracker.dart'; +import 'package:stackwallet/utilities/address_utils.dart'; +import 'package:stackwallet/utilities/amount/amount.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:stackwallet/widgets/crypto_notifications.dart'; +import 'package:tuple/tuple.dart'; +import 'package:uuid/uuid.dart'; + +const int MINIMUM_CONFIRMATIONS = 0; + +const String GENESIS_HASH_MAINNET = + "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"; +const String GENESIS_HASH_TESTNET = + "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943"; + +final Amount DUST_LIMIT = Amount( + rawValue: BigInt.from(546), + fractionDigits: Coin.eCash.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: // mainnet wif + switch (derivePathType) { + case DerivePathType.bip44: + coinType = "145"; + 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!"); + } + + int purpose; + switch (derivePathType) { + case DerivePathType.bip44: + case DerivePathType.eCash44: + purpose = 44; + 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.p2pkh) + .subTypeEqualTo(isar_models.AddressSubType.receiving) + .derivationPath((q) => q.valueStartsWith("m/44'/899")) + .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.p2pkh) + .subTypeEqualTo(isar_models.AddressSubType.change) + .derivationPath((q) => q.valueStartsWith("m/44'/899")) + .sortByDerivationIndexDesc() + .findFirst()) ?? + await _generateAddressForChain(1, 0, DerivePathTypeExt.primaryFor(coin)); + + @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 { + 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) { + // 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; + + bool validateCashAddr(String cashAddr) { + String addr = cashAddr; + if (cashAddr.contains(":")) { + addr = cashAddr.split(":").last; + } + + return addr.startsWith("q"); + } + + @override + bool validateAddress(String address) { + 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 + 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(); + _electrumXClient = ElectrumX.from( + node: newNode, + prefs: _prefs, + failovers: failovers, + ); + _cachedElectrumXClient = CachedElectrumX.from( + electrumXClient: _electrumXClient, + ); + + 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); + + _serverVersion = + _parseServerVersion(features["server_version"] as String); + + 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: 128)); + 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([ + // P2PKH + _generateAddressForChain( + receiveChain, + startingIndex, + DerivePathType.eCash44, + ), + _generateAddressForChain( + changeChain, + startingIndex, + DerivePathType.eCash44, + ), + // _generateAddressForChain( + // receiveChain, + // startingIndex, + // DerivePathType.bip44, + // ), + // _generateAddressForChain( + // changeChain, + // startingIndex, + // DerivePathType.bip44, + // ), + ]); + + 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: + case DerivePathType.eCash44: + address = P2PKH(data: data, network: _network).data.address!; + addrType = isar_models.AddressType.p2pkh; + address = bitbox.Address.toECashAddress(address); + 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; + String coinType; + String purpose; + switch (derivePathType) { + case DerivePathType.bip44: + type = isar_models.AddressType.p2pkh; + coinType = "0"; + purpose = "44"; + break; + case DerivePathType.eCash44: + type = isar_models.AddressType.p2pkh; + coinType = "899"; + purpose = "44"; + break; + default: + throw Exception("DerivePathType unsupported"); + } + final address = await db + .getAddresses(walletId) + .filter() + .typeEqualTo(type) + .subTypeEqualTo(subType) + .derivationPath((q) => q.valueStartsWith("m/$purpose'/$coinType")) + .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; + } + + 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; + } + + /// 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(); + + try { + final fetchedUtxoList = >>[]; + + 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 = _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); + } + } + } + } else { + for (int i = 0; i < allAddresses.length; i++) { + final scriptHash = _convertToScriptHash( + allAddresses[i].value, + _network, + ); + + final utxos = await electrumXClient.getUTXOs(scripthash: scriptHash); + if (utxos.isNotEmpty) { + fetchedUtxoList.add(utxos); + } + } + } + + 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 = _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 _getTxCount({required isar_models.Address address}) async { + try { + return await getTxCount(address: address.value); + } 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 { + try { + final Map> args = {}; + for (final entry in addresses.entries) { + args[entry.key] = [_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 _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 { + try { + List> allTxHashes = []; + + 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 = _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]); + } + } + } + } + } else { + for (int i = 0; i < allAddresses.length; i++) { + final scriptHash = _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]); + } + } + } + } + + 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 { + List allAddressesOld = await _fetchAllOwnAddresses(); + + 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(); + + 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 = []; + + for (final txHash in allTxHashes) { + final storedTx = await db + .getTransactions(walletId) + .filter() + .txidEqualTo(txHash["tx_hash"] as String) + .findFirst(); + + if (storedTx == null || + 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, + 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); + } + } + } + + final List> txns = []; + + for (final txData in allTransactions) { + Set inputAddresses = {}; + Set outputAddresses = {}; + + 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, + ); + + 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(txns, walletId); + + // quick hack to notify manager to call notifyListeners if + // transactions changed + if (txns.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( + utxosToUse: utxoObjectsToUse, + 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( + utxosToUse: utxoObjectsToUse, + 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( + utxosToUse: utxoObjectsToUse, + 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( + utxosToUse: utxoObjectsToUse, + 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( + utxosToUse: utxoObjectsToUse, + 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( + utxosToUse: utxoObjectsToUse, + 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( + utxosToUse: utxoObjectsToUse, + 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( + utxosToUse: utxoObjectsToUse, + 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( + utxosToUse: utxoObjectsToUse, + 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++) { + 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); + } + } + + 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: + case DerivePathType.eCash44: + data = P2PKH( + 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 utxosToUse, + required List utxoSigningData, + required List recipients, + required List satoshiAmounts, + }) async { + final builder = bitbox.Bitbox.transactionBuilder( + testnet: false, //coin == Coin.bitcoincashTestnet, + ); + + // 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"); + + // 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; + } + + // 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); + } + + // 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); + } + + // 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": txHex, "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, + ); + + final deriveTypes = [ + DerivePathType.eCash44, + DerivePathType.bip44, + ]; + + 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, + ); + + if (serverCanBatch) { + for (final type in deriveTypes) { + receiveFutures.add( + _checkGapsBatched( + maxNumberOfIndexesToCheck, + maxUnusedAddressGap, + txCountBatchSize, + root, + type, + receiveChain, + ), + ); + } + } else { + for (final type in deriveTypes) { + receiveFutures.add( + _checkGaps( + maxNumberOfIndexesToCheck, + maxUnusedAddressGap, + root, + type, + receiveChain, + ), + ); + } + } + + Logging.instance.log( + "checking change addresses...", + level: LogLevel.Info, + ); + // change addresses + + if (serverCanBatch) { + for (final type in deriveTypes) { + changeFutures.add( + _checkGapsBatched( + maxNumberOfIndexesToCheck, + maxUnusedAddressGap, + txCountBatchSize, + root, + type, + changeChain, + ), + ); + } + } else { + for (final type in deriveTypes) { + changeFutures.add( + _checkGaps( + maxNumberOfIndexesToCheck, + maxUnusedAddressGap, + root, + type, + changeChain, + ), + ); + } + } + + // io limitations may require running these linearly instead + final futuresResult = await Future.wait([ + Future.wait(receiveFutures), + Future.wait(changeFutures), + ]); + + final receiveResults = futuresResult[0]; + final changeResults = futuresResult[1]; + + 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 + 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 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. + 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 (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, DerivePathType>> _checkGaps( + int maxNumberOfIndexesToCheck, + int maxUnusedAddressGap, + bip32.BIP32 root, + DerivePathType type, + int chain, + ) async { + List addressArray = []; + 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: + case DerivePathType.eCash44: + addressString = P2PKH(data: data, network: _network).data.address!; + addressType = isar_models.AddressType.p2pkh; + addressString = bitbox.Address.toECashAddress(addressString); + 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); + // reset counter + gapCounter = 0; + // add info to derivations + } else { + // increase counter when no tx history found + gapCounter++; + } + } + + return Tuple2(addressArray, type); + } + + Future, DerivePathType>> _checkGapsBatched( + int maxNumberOfIndexesToCheck, + int maxUnusedAddressGap, + int txCountBatchSize, + bip32.BIP32 root, + DerivePathType type, + int chain, + ) async { + List addressArray = []; + 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: + case DerivePathType.eCash44: + addressString = P2PKH(data: data, network: _network).data.address!; + addrType = isar_models.AddressType.p2pkh; + addressString = bitbox.Address.toECashAddress(addressString); + 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 + // 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 Tuple2(addressArray, type); + } + + 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); + + 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!"); + } + + 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 _checkCurrentChangeAddressesForTransactions(); + + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.3, walletId)); + await _checkCurrentReceivingAddressesForTransactions(); + + 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) { + 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) { + 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); + } + } + + // notify on confirmed + for (final tx in unconfirmedTxnsToNotifyConfirmed) { + if (tx.type == isar_models.TransactionType.incoming) { + 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) { + 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); + } + } + } + + @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); + _serverVersion = + _parseServerVersion(features["server_version"] as String); + 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/services/coins/ethereum/ethereum_wallet.dart b/lib/services/coins/ethereum/ethereum_wallet.dart index 0e933a1c0..2052ead4b 100644 --- a/lib/services/coins/ethereum/ethereum_wallet.dart +++ b/lib/services/coins/ethereum/ethereum_wallet.dart @@ -350,7 +350,7 @@ class EthereumWallet extends CoinServiceAPI with WalletCache, WalletDB { "Attempted to overwrite mnemonic on generate new wallet!"); } - final String mnemonic = bip39.generateMnemonic(strength: 256); + final String mnemonic = bip39.generateMnemonic(strength: 128); await _secureStore.write(key: '${_walletId}_mnemonic', value: mnemonic); await _secureStore.write( key: '${_walletId}_mnemonicPassphrase', diff --git a/lib/services/coins/firo/firo_wallet.dart b/lib/services/coins/firo/firo_wallet.dart index 57e916e2e..fa9cfca5c 100644 --- a/lib/services/coins/firo/firo_wallet.dart +++ b/lib/services/coins/firo/firo_wallet.dart @@ -4,6 +4,7 @@ import 'dart:io'; import 'dart:isolate'; import 'dart:math'; +import 'package:bip32/bip32.dart' as bip32; import 'package:bip39/bip39.dart' as bip39; import 'package:bitcoindart/bitcoindart.dart'; import 'package:decimal/decimal.dart'; @@ -145,37 +146,25 @@ Future executeNative(Map arguments) async { } else if (function == "restore") { final latestSetId = arguments['latestSetId'] as int; final setDataMap = arguments['setDataMap'] as Map; - final usedSerialNumbers = arguments['usedSerialNumbers'] as List?; + final usedSerialNumbers = arguments['usedSerialNumbers'] as List; final mnemonic = arguments['mnemonic'] as String; final mnemonicPassphrase = arguments['mnemonicPassphrase'] as String; final coin = arguments['coin'] as Coin; - final network = arguments['network'] as NetworkType?; - if (!(usedSerialNumbers == null || network == null)) { - var restoreData = await isolateRestore( - mnemonic, - mnemonicPassphrase, - coin, - latestSetId, - setDataMap, - usedSerialNumbers, - network, - ); - sendPort.send(restoreData); - return; - } - } else if (function == "isolateDerive") { - final mnemonic = arguments['mnemonic'] as String; - final mnemonicPassphrase = arguments['mnemonicPassphrase'] as String; - final from = arguments['from'] as int; - final to = arguments['to'] as int; - final network = arguments['network'] as NetworkType?; - if (!(network == null)) { - var derived = await isolateDerive( - mnemonic, mnemonicPassphrase, from, to, network); - sendPort.send(derived); - return; - } + final network = arguments['network'] as NetworkType; + + final restoreData = await isolateRestore( + mnemonic, + mnemonicPassphrase, + coin, + latestSetId, + setDataMap, + usedSerialNumbers, + network, + ); + sendPort.send(restoreData); + return; } + Logging.instance.log( "Error Arguments for $function not formatted correctly", level: LogLevel.Fatal); @@ -199,76 +188,13 @@ void stop(ReceivePort port) { } } -Future> isolateDerive( - String mnemonic, - String mnemonicPassphrase, - int from, - int to, - NetworkType _network, -) async { - Map result = {}; - Map allReceive = {}; - Map allChange = {}; - final root = await Bip32Utils.getBip32Root( - mnemonic, - mnemonicPassphrase, - _network, - ); - - for (int i = from; i < to; i++) { - final derivePathReceiving = constructDerivePath( - networkWIF: _network.wif, - chain: 0, - index: i, - ); - var currentNode = await Bip32Utils.getBip32NodeFromRoot( - root, - derivePathReceiving, - ); - var address = P2PKH( - network: _network, data: PaymentData(pubkey: currentNode.publicKey)) - .data - .address!; - allReceive["$i"] = { - "publicKey": Format.uint8listToString(currentNode.publicKey), - "wif": currentNode.toWIF(), - "address": address, - }; - - final derivePathChange = constructDerivePath( - networkWIF: _network.wif, - chain: 1, - index: i, - ); - currentNode = await Bip32Utils.getBip32NodeFromRoot( - root, - derivePathChange, - ); - address = P2PKH( - network: _network, data: PaymentData(pubkey: currentNode.publicKey)) - .data - .address!; - allChange["$i"] = { - "publicKey": Format.uint8listToString(currentNode.publicKey), - "wif": currentNode.toWIF(), - "address": address, - }; - if (i % 50 == 0) { - Logging.instance.log("thread at $i", level: LogLevel.Info); - } - } - result['receive'] = allReceive; - result['change'] = allChange; - return result; -} - Future> isolateRestore( String mnemonic, String mnemonicPassphrase, Coin coin, int _latestSetId, Map _setDataMap, - List _usedSerialNumbers, + List _usedSerialNumbers, NetworkType network, ) async { List jindexes = []; @@ -279,11 +205,7 @@ Future> isolateRestore( var currentIndex = 0; try { - final usedSerialNumbers = _usedSerialNumbers; - Set usedSerialNumbersSet = {}; - for (int ind = 0; ind < usedSerialNumbers.length; ind++) { - usedSerialNumbersSet.add(usedSerialNumbers[ind]); - } + Set usedSerialNumbersSet = _usedSerialNumbers.toSet(); final root = await Bip32Utils.getBip32Root( mnemonic, @@ -296,43 +218,60 @@ Future> isolateRestore( chain: MINT_INDEX, index: currentIndex, ); - final mintKeyPair = - await Bip32Utils.getBip32NodeFromRoot(root, _derivePath); - final mintTag = CreateTag( - Format.uint8listToString(mintKeyPair.privateKey!), - currentIndex, - Format.uint8listToString(mintKeyPair.identifier), - isTestnet: coin == Coin.firoTestNet); + final bip32.BIP32 mintKeyPair = await Bip32Utils.getBip32NodeFromRoot( + root, + _derivePath, + ); + final String mintTag = CreateTag( + Format.uint8listToString(mintKeyPair.privateKey!), + currentIndex, + Format.uint8listToString(mintKeyPair.identifier), + isTestnet: coin == Coin.firoTestNet, + ); for (var setId = 1; setId <= _latestSetId; setId++) { - final setData = _setDataMap[setId]; - final foundCoin = setData["coins"].firstWhere( - (dynamic e) => e[1] == mintTag, - orElse: () => []); + final setData = _setDataMap[setId] as Map; + final foundCoin = (setData["coins"] as List).firstWhere( + (e) => e[1] == mintTag, + orElse: () => [], + ); if (foundCoin.length == 4) { lastFoundIndex = currentIndex; - if (foundCoin[2] is int) { - final amount = foundCoin[2] as int; - final serialNumber = GetSerialNumber(amount, - Format.uint8listToString(mintKeyPair.privateKey!), currentIndex, - isTestnet: coin == Coin.firoTestNet); - String publicCoin = foundCoin[0] as String; - String txId = foundCoin[3] as String; - bool isUsed = usedSerialNumbersSet.contains(serialNumber); - final duplicateCoin = lelantusCoins.firstWhere((element) { - final coin = element.values.first; - return coin.txId == txId && - coin.index == currentIndex && - coin.anonymitySetId != setId; - }, orElse: () => {}); + + final String publicCoin = foundCoin[0] as String; + final String txId = foundCoin[3] as String; + + // this value will either be an int or a String + final dynamic thirdValue = foundCoin[2]; + + if (thirdValue is int) { + final int amount = thirdValue; + final String serialNumber = GetSerialNumber( + amount, + Format.uint8listToString(mintKeyPair.privateKey!), + currentIndex, + isTestnet: coin == Coin.firoTestNet, + ); + final bool isUsed = usedSerialNumbersSet.contains(serialNumber); + final duplicateCoin = lelantusCoins.firstWhere( + (element) { + final coin = element.values.first; + return coin.txId == txId && + coin.index == currentIndex && + coin.anonymitySetId != setId; + }, + orElse: () => {}, + ); if (duplicateCoin.isNotEmpty) { - //todo: check if print needed - // debugPrint("removing duplicate: $duplicateCoin"); + Logging.instance.log( + "Firo isolateRestore removing duplicate coin: $duplicateCoin", + level: LogLevel.Info, + ); lelantusCoins.remove(duplicateCoin); } lelantusCoins.add({ - publicCoin: LelantusCoin( + txId: LelantusCoin( currentIndex, amount, publicCoin, @@ -341,47 +280,56 @@ Future> isolateRestore( isUsed, ) }); - Logging.instance - .log("amount $amount used $isUsed", level: LogLevel.Info); - } else { - final keyPath = GetAesKeyPath(foundCoin[0] as String); - final derivePath = constructDerivePath( + Logging.instance.log( + "amount $amount used $isUsed", + level: LogLevel.Info, + ); + } else if (thirdValue is String) { + final int keyPath = GetAesKeyPath(publicCoin); + final String derivePath = constructDerivePath( networkWIF: network.wif, chain: JMINT_INDEX, index: keyPath, ); - final aesKeyPair = - await Bip32Utils.getBip32NodeFromRoot(root, derivePath); + final aesKeyPair = await Bip32Utils.getBip32NodeFromRoot( + root, + derivePath, + ); if (aesKeyPair.privateKey != null) { - final aesPrivateKey = - Format.uint8listToString(aesKeyPair.privateKey!); - final amount = decryptMintAmount( + final String aesPrivateKey = Format.uint8listToString( + aesKeyPair.privateKey!, + ); + final int amount = decryptMintAmount( aesPrivateKey, - foundCoin[2] as String, + thirdValue, ); - final serialNumber = GetSerialNumber( - amount, - Format.uint8listToString(mintKeyPair.privateKey!), - currentIndex, - isTestnet: coin == Coin.firoTestNet); - String publicCoin = foundCoin[0] as String; - String txId = foundCoin[3] as String; + final String serialNumber = GetSerialNumber( + amount, + Format.uint8listToString(mintKeyPair.privateKey!), + currentIndex, + isTestnet: coin == Coin.firoTestNet, + ); bool isUsed = usedSerialNumbersSet.contains(serialNumber); - final duplicateCoin = lelantusCoins.firstWhere((element) { - final coin = element.values.first; - return coin.txId == txId && - coin.index == currentIndex && - coin.anonymitySetId != setId; - }, orElse: () => {}); + final duplicateCoin = lelantusCoins.firstWhere( + (element) { + final coin = element.values.first; + return coin.txId == txId && + coin.index == currentIndex && + coin.anonymitySetId != setId; + }, + orElse: () => {}, + ); if (duplicateCoin.isNotEmpty) { - //todo: check if print needed - // debugPrint("removing duplicate: $duplicateCoin"); + Logging.instance.log( + "Firo isolateRestore removing duplicate coin: $duplicateCoin", + level: LogLevel.Info, + ); lelantusCoins.remove(duplicateCoin); } lelantusCoins.add({ - '${foundCoin[3]!}': LelantusCoin( + txId: LelantusCoin( currentIndex, amount, publicCoin, @@ -392,9 +340,24 @@ Future> isolateRestore( }); jindexes.add(currentIndex); - spendTxIds.add(foundCoin[3] as String); + spendTxIds.add(txId); + } else { + Logging.instance.log( + "AES keypair derivation issue for derive path: $derivePath", + level: LogLevel.Warning, + ); } + } else { + Logging.instance.log( + "Unexpected coin found: $foundCoin", + level: LogLevel.Warning, + ); } + } else { + Logging.instance.log( + "Coin not found in data with the mint tag: $mintTag", + level: LogLevel.Warning, + ); } } @@ -442,7 +405,7 @@ Future> staticProcessRestore( tx = null; } - if (tx == null) { + if (tx == null || tx.subType == isar_models.TransactionSubType.join) { // This is a jmint. return; } @@ -900,6 +863,7 @@ class FiroWallet extends CoinServiceAPI .or() .isLelantusEqualTo(false) .findAll(); + // _transactionData ??= _refreshTransactions(); // models.TransactionData? cachedTxData; @@ -943,10 +907,12 @@ class FiroWallet extends CoinServiceAPI /// Holds the max fee that can be sent Future? _maxFee; + @override Future get maxFee => _maxFee ??= _fetchMaxFee(); Future? _feeObject; + @override Future get fees => _feeObject ??= _getFees(); @@ -978,6 +944,7 @@ class FiroWallet extends CoinServiceAPI await _generateAddressForChain(1, 0); late String _walletName; + @override String get walletName => _walletName; @@ -987,6 +954,7 @@ class FiroWallet extends CoinServiceAPI /// unique wallet id late final String _walletId; + @override String get walletId => _walletId; @@ -1287,9 +1255,11 @@ class FiroWallet extends CoinServiceAPI } late ElectrumX _electrumXClient; + ElectrumX get electrumXClient => _electrumXClient; late CachedElectrumX _cachedElectrumXClient; + CachedElectrumX get cachedElectrumXClient => _cachedElectrumXClient; late SecureStorageInterface _secureStore; @@ -1703,66 +1673,65 @@ class FiroWallet extends CoinServiceAPI String? pubKey; String? wif; - // fetch receiving derivations if null - receiveDerivations[sd.derivePathType] ??= Map.from( - jsonDecode((await _secureStore.read( - key: "${walletId}_receiveDerivations", - )) ?? - "{}") as Map, - ); + final address = await db.getAddress(walletId, sd.utxo.address!); + if (address?.derivationPath != null) { + final node = await Bip32Utils.getBip32Node( + (await mnemonicString)!, + (await mnemonicPassphrase)!, + _network, + address!.derivationPath!.value, + ); - dynamic receiveDerivation; - for (int j = 0; - j < receiveDerivations[sd.derivePathType]!.length && - receiveDerivation == null; - j++) { - if (receiveDerivations[sd.derivePathType]!["$j"]["address"] == - sd.utxo.address!) { - receiveDerivation = receiveDerivations[sd.derivePathType]!["$j"]; - } + wif = node.toWIF(); + pubKey = Format.uint8listToString(node.publicKey); } - - if (receiveDerivation != null) { - pubKey = receiveDerivation["publicKey"] as String; - wif = receiveDerivation["wif"] as String; - } else { - // fetch change derivations if null - changeDerivations[sd.derivePathType] ??= Map.from( + if (wif == null || pubKey == null) { + // fetch receiving derivations if null + receiveDerivations[sd.derivePathType] ??= Map.from( jsonDecode((await _secureStore.read( - key: "${walletId}_changeDerivations", + key: "${walletId}_receiveDerivations", )) ?? "{}") as Map, ); - dynamic changeDerivation; + dynamic receiveDerivation; for (int j = 0; - j < changeDerivations[sd.derivePathType]!.length && - changeDerivation == null; + j < receiveDerivations[sd.derivePathType]!.length && + receiveDerivation == null; j++) { - if (changeDerivations[sd.derivePathType]!["$j"]["address"] == + if (receiveDerivations[sd.derivePathType]!["$j"]["address"] == sd.utxo.address!) { - changeDerivation = changeDerivations[sd.derivePathType]!["$j"]; + receiveDerivation = receiveDerivations[sd.derivePathType]!["$j"]; } } - if (changeDerivation != null) { - pubKey = changeDerivation["publicKey"] as String; - wif = changeDerivation["wif"] as String; - } - } - - if (wif == null || pubKey == null) { - final address = await db.getAddress(walletId, sd.utxo.address!); - if (address?.derivationPath != null) { - final node = await Bip32Utils.getBip32Node( - (await mnemonicString)!, - (await mnemonicPassphrase)!, - _network, - address!.derivationPath!.value, + if (receiveDerivation != null) { + pubKey = receiveDerivation["publicKey"] as String; + wif = receiveDerivation["wif"] as String; + } else { + // fetch change derivations if null + changeDerivations[sd.derivePathType] ??= Map.from( + jsonDecode((await _secureStore.read( + key: "${walletId}_changeDerivations", + )) ?? + "{}") as Map, ); - wif = node.toWIF(); - pubKey = Format.uint8listToString(node.publicKey); + dynamic changeDerivation; + for (int j = 0; + j < changeDerivations[sd.derivePathType]!.length && + changeDerivation == null; + j++) { + if (changeDerivations[sd.derivePathType]!["$j"]["address"] == + sd.utxo.address!) { + changeDerivation = changeDerivations[sd.derivePathType]!["$j"]; + } + } + + if (changeDerivation != null) { + pubKey = changeDerivation["publicKey"] as String; + wif = changeDerivation["wif"] as String; + } } } @@ -1793,6 +1762,8 @@ class FiroWallet extends CoinServiceAPI sd.redeemScript = redeemScript; sd.output = data.output; sd.keyPair = keyPair; + } else { + throw Exception("key or wif not found for ${sd.utxo}"); } } @@ -1869,16 +1840,14 @@ class FiroWallet extends CoinServiceAPI ) .toList(); final newNode = await _getCurrentNode(); - _cachedElectrumXClient = CachedElectrumX.from( - node: newNode, - prefs: _prefs, - failovers: failovers, - ); _electrumXClient = ElectrumX.from( node: newNode, prefs: _prefs, failovers: failovers, ); + _cachedElectrumXClient = CachedElectrumX.from( + electrumXClient: _electrumXClient, + ); if (shouldRefresh) { unawaited(refresh()); @@ -1959,12 +1928,13 @@ class FiroWallet extends CoinServiceAPI if ((await db .getTransactions(walletId) .filter() - .txidMatches(txid) - .findFirst()) == - null) { + .txidEqualTo(txid) + .count()) == + 0) { Logging.instance.log( - " txid not found in address history already ${transaction['tx_hash']}", - level: LogLevel.Info); + " txid not found in address history already ${transaction['tx_hash']}", + level: LogLevel.Info, + ); needsRefresh = true; break; } @@ -1987,79 +1957,27 @@ class FiroWallet extends CoinServiceAPI final currentChainHeight = await chainHeight; - final txTxns = await db - .getTransactions(walletId) - .filter() - .isLelantusIsNull() - .or() - .isLelantusEqualTo(false) - .findAll(); - final ltxTxns = await db - .getTransactions(walletId) - .filter() - .isLelantusEqualTo(true) - .findAll(); + final txCount = await db.getTransactions(walletId).count(); - for (isar_models.Transaction tx in txTxns) { - isar_models.Transaction? lTx; - try { - lTx = ltxTxns.firstWhere((e) => e.txid == tx.txid); - } catch (_) { - lTx = null; - } + const paginateLimit = 50; - if (tx.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS)) { - if (txTracker.wasNotifiedPending(tx.txid) && - !txTracker.wasNotifiedConfirmed(tx.txid)) { + 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 - unconfirmedTxnsToNotifyConfirmed.add(tx); - } - if (lTx != null && - (lTx.inputs.isEmpty || lTx.inputs.first.txid.isEmpty) && - lTx.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS) == - false && - tx.type == isar_models.TransactionType.incoming) { - // If this is a received that is past 1 or more confirmations and has not been minted, - if (!txTracker.wasNotifiedPending(tx.txid)) { - unconfirmedTxnsToNotifyPending.add(tx); - } - } - } else { - if (!txTracker.wasNotifiedPending(tx.txid)) { - // get all transactions that were not notified as pending yet - unconfirmedTxnsToNotifyPending.add(tx); - } - } - } - - for (isar_models.Transaction tx in txTxns) { - if (!tx.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS) && - tx.inputs.first.txid.isNotEmpty) { - // Get all normal txs that are at 0 confirmations - unconfirmedTxnsToNotifyPending - .removeWhere((e) => e.txid == tx.inputs.first.txid); - Logging.instance.log("removed tx: ${tx.txid}", level: LogLevel.Info); - } - } - - for (isar_models.Transaction lTX in ltxTxns) { - isar_models.Transaction? tx; - try { - tx = ltxTxns.firstWhere((e) => e.txid == lTX.txid); - } catch (_) { - tx = null; - } - - if (tx == null) { - // if this is a ltx transaction that is unconfirmed and not represented in the normal transaction set. - if (!lTX.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS)) { - if (!txTracker.wasNotifiedPending(lTX.txid)) { - unconfirmedTxnsToNotifyPending.add(lTX); + if (txTracker.wasNotifiedPending(tx.txid) && + !txTracker.wasNotifiedConfirmed(tx.txid)) { + unconfirmedTxnsToNotifyConfirmed.add(tx); } } else { - if (txTracker.wasNotifiedPending(lTX.txid) && - !txTracker.wasNotifiedConfirmed(lTX.txid)) { - unconfirmedTxnsToNotifyConfirmed.add(lTX); + // get all transactions that were not notified as pending yet + if (!txTracker.wasNotifiedPending(tx.txid)) { + unconfirmedTxnsToNotifyPending.add(tx); } } } @@ -2192,7 +2110,7 @@ class FiroWallet extends CoinServiceAPI } await _secureStore.write( key: '${_walletId}_mnemonic', - value: bip39.generateMnemonic(strength: 256)); + value: bip39.generateMnemonic(strength: 128)); await _secureStore.write( key: '${_walletId}_mnemonicPassphrase', value: "", @@ -2210,6 +2128,7 @@ class FiroWallet extends CoinServiceAPI } bool refreshMutex = false; + @override bool get isRefreshing => refreshMutex; @@ -2236,28 +2155,6 @@ class FiroWallet extends CoinServiceAPI GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.0, walletId)); - final receiveDerivationsString = - await _secureStore.read(key: "${walletId}_receiveDerivations"); - if (receiveDerivationsString == null || - receiveDerivationsString == "{}") { - GlobalEventBus.instance - .fire(RefreshPercentChangedEvent(0.05, walletId)); - final mnemonic = await mnemonicString; - final mnemonicPassphrase = - await _secureStore.read(key: '${_walletId}_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); - } - - await fillAddresses( - mnemonic!, - mnemonicPassphrase!, - numberOfThreads: Platform.numberOfProcessors - isolates.length - 1, - ); - } - await checkReceivingAddressForTransactions(); GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.1, walletId)); @@ -2404,12 +2301,6 @@ class FiroWallet extends CoinServiceAPI element.values.any((elementCoin) => elementCoin.value == 0)); } final jindexes = firoGetJIndex(); - final transactions = await _txnData; - final lelantusTransactionsd = await db - .getTransactions(walletId) - .filter() - .isLelantusEqualTo(true) - .findAll(); List coins = []; @@ -2423,42 +2314,26 @@ class FiroWallet extends CoinServiceAPI for (int i = 0; i < lelantusCoinsList.length; i++) { // Logging.instance.log("lelantusCoinsList[$i]: ${lelantusCoinsList[i]}"); + final txid = lelantusCoinsList[i].txId; final txn = await cachedElectrumXClient.getTransaction( - txHash: lelantusCoinsList[i].txId, + txHash: txid, verbose: true, coin: coin, ); final confirmations = txn["confirmations"]; bool isUnconfirmed = confirmations is int && confirmations < 1; + + final tx = await db.getTransaction(walletId, txid); + if (!jindexes!.contains(lelantusCoinsList[i].index) && - transactions - .where((e) => e.txid == lelantusCoinsList[i].txId) - .isEmpty && - !(lelantusTransactionsd - .where((e) => e.txid == lelantusCoinsList[i].txId) - .isNotEmpty && - lelantusTransactionsd - .where((e) => e.txid == lelantusCoinsList[i].txId) - .first - .isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS))) { + tx != null && + tx.isLelantus == true && + !(tx.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS))) { isUnconfirmed = true; } - // TODO: optimize the following - if ((transactions - .where((e) => e.txid == lelantusCoinsList[i].txId) - .isNotEmpty && - !transactions - .where((e) => e.txid == lelantusCoinsList[i].txId) - .first - .isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS)) || - (lelantusTransactionsd - .where((e) => e.txid == lelantusCoinsList[i].txId) - .isNotEmpty && - !lelantusTransactionsd - .where((e) => e.txid == lelantusCoinsList[i].txId) - .first - .isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS))) { + if (tx != null && + !tx.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS)) { continue; } if (!lelantusCoinsList[i].isUsed && @@ -2482,61 +2357,54 @@ class FiroWallet extends CoinServiceAPI lelantusCoins.removeWhere((element) => element.values.any((elementCoin) => elementCoin.value == 0)); } - final data = await _txnData; - final lData = await db - .getTransactions(walletId) - .filter() - .isLelantusEqualTo(true) - .findAll(); + final currentChainHeight = await chainHeight; final jindexes = firoGetJIndex(); int intLelantusBalance = 0; int unconfirmedLelantusBalance = 0; - for (var element in lelantusCoins) { - element.forEach((key, value) { - isar_models.Transaction? tx; - try { - tx == data.firstWhere((e) => e.txid == value.txId); - } catch (_) { - tx = null; - } + for (final element in lelantusCoins) { + element.forEach((key, lelantusCoin) { + isar_models.Transaction? txn = db.isar.transactions + .where() + .txidWalletIdEqualTo( + lelantusCoin.txId, + walletId, + ) + .findFirstSync(); - isar_models.Transaction? ltx; - try { - ltx = lData.firstWhere((e) => e.txid == value.txId); - } catch (_) { - ltx = null; - } - - // Logging.instance.log("$value $tx $ltx"); - if (!jindexes!.contains(value.index) && tx == null) { - if (!value.isUsed && - ltx != null && - ltx.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS)) { - // mint tx, add value to balance - intLelantusBalance += value.value; - } /* else { + if (txn == null) { + // TODO: ?????????????????????????????????????? + } else { + bool isLelantus = txn.isLelantus == true; + if (!jindexes!.contains(lelantusCoin.index) && isLelantus) { + if (!lelantusCoin.isUsed && + txn.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS)) { + // mint tx, add value to balance + intLelantusBalance += lelantusCoin.value; + } /* else { // This coin is not confirmed and may be replaced }*/ - } else if (jindexes.contains(value.index) && - tx == null && - !value.isUsed && - ltx != null && - !ltx.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS)) { - unconfirmedLelantusBalance += value.value; - } else if (jindexes.contains(value.index) && !value.isUsed) { - intLelantusBalance += value.value; - } else if (!value.isUsed && - (tx == null - ? true - : tx.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS) != - false)) { - intLelantusBalance += value.value; - } else if (tx != null && - tx.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS) == - false) { - unconfirmedLelantusBalance += value.value; + } else if (jindexes.contains(lelantusCoin.index) && + isLelantus && + !lelantusCoin.isUsed && + !txn.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS)) { + unconfirmedLelantusBalance += lelantusCoin.value; + } else if (jindexes.contains(lelantusCoin.index) && + !lelantusCoin.isUsed) { + intLelantusBalance += lelantusCoin.value; + } else if (!lelantusCoin.isUsed && + (txn.isLelantus == true + ? true + : txn.isConfirmed( + currentChainHeight, MINIMUM_CONFIRMATIONS) != + false)) { + intLelantusBalance += lelantusCoin.value; + } else if (!isLelantus && + txn.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS) == + false) { + unconfirmedLelantusBalance += lelantusCoin.value; + } } }); } @@ -2685,7 +2553,7 @@ class FiroWallet extends CoinServiceAPI var tmpTotal = total; var index = 1; var mints = >[]; - final nextFreeMintIndex = firoGetMintIndex()!; + final nextFreeMintIndex = firoGetMintIndex(); while (tmpTotal > 0) { final mintValue = min(tmpTotal, MINT_LIMIT); final mint = await _getMintHex( @@ -2721,12 +2589,13 @@ class FiroWallet extends CoinServiceAPI /// Builds and signs a transaction Future> buildMintTransaction( - List utxosToUse, - int satoshisPerRecipient, - List> mintsMap) async { + List utxosToUse, + int satoshisPerRecipient, + List> mintsMap, + ) async { //todo: check if print needed // debugPrint(utxosToUse.toString()); - List addressesToDerive = []; + List addressStringsToGet = []; // Populating the addresses to derive for (var i = 0; i < utxosToUse.length; i++) { @@ -2745,64 +2614,95 @@ class FiroWallet extends CoinServiceAPI final address = vouts[outputIndex]["scriptPubKey"]["addresses"][0] as String?; if (address != null) { - addressesToDerive.add(address); + addressStringsToGet.add(address); } } } - List elipticCurvePairArray = []; + final List addresses = []; + for (final addressString in addressStringsToGet) { + final address = await db.getAddress(walletId, addressString); + if (address == null) { + Logging.instance.log( + "Failed to fetch the corresponding address object for $addressString", + level: LogLevel.Fatal, + ); + } else { + addresses.add(address); + } + } + + List ellipticCurvePairArray = []; List outputDataArray = []; - final receiveDerivationsString = - await _secureStore.read(key: "${walletId}_receiveDerivations"); - final changeDerivationsString = - await _secureStore.read(key: "${walletId}_changeDerivations"); + Map? receiveDerivations; + Map? changeDerivations; - final receiveDerivations = Map.from( - jsonDecode(receiveDerivationsString ?? "{}") as Map); - final changeDerivations = Map.from( - jsonDecode(changeDerivationsString ?? "{}") as Map); + for (final addressString in addressStringsToGet) { + String? pubKey; + String? wif; - for (var i = 0; i < addressesToDerive.length; i++) { - final addressToCheckFor = addressesToDerive[i]; + final address = await db.getAddress(walletId, addressString); - for (var i = 0; i < receiveDerivations.length; i++) { - final receive = receiveDerivations["$i"]; - final change = changeDerivations["$i"]; + if (address?.derivationPath != null) { + final node = await Bip32Utils.getBip32Node( + (await mnemonicString)!, + (await mnemonicPassphrase)!, + _network, + address!.derivationPath!.value, + ); + wif = node.toWIF(); + pubKey = Format.uint8listToString(node.publicKey); + } - if (receive['address'] == addressToCheckFor) { - Logging.instance - .log('Receiving found on loop $i', level: LogLevel.Info); - // Logging.instance.log( - // 'decoded receive[\'wif\'] version: ${wif.decode(receive['wif'] as String)}, _network: $_network'); - elipticCurvePairArray - .add(ECPair.fromWIF(receive['wif'] as String, network: _network)); - outputDataArray.add(P2PKH( - network: _network, - data: PaymentData( - pubkey: Format.stringToUint8List( - receive['publicKey'] as String))) - .data - .output!); - break; + if (wif == null || pubKey == null) { + receiveDerivations ??= Map.from( + jsonDecode((await _secureStore.read( + key: "${walletId}_receiveDerivations")) ?? + "{}") as Map, + ); + for (var i = 0; i < receiveDerivations.length; i++) { + final receive = receiveDerivations["$i"]; + if (receive['address'] == addressString) { + wif = receive['wif'] as String; + pubKey = receive['publicKey'] as String; + break; + } } - if (change['address'] == addressToCheckFor) { - Logging.instance.log('Change found on loop $i', level: LogLevel.Info); - // Logging.instance.log( - // 'decoded change[\'wif\'] version: ${wif.decode(change['wif'] as String)}, _network: $_network'); - elipticCurvePairArray - .add(ECPair.fromWIF(change['wif'] as String, network: _network)); - outputDataArray.add(P2PKH( - network: _network, - data: PaymentData( - pubkey: Format.stringToUint8List( - change['publicKey'] as String))) - .data - .output!); - break; + if (wif == null || pubKey == null) { + changeDerivations ??= Map.from( + jsonDecode((await _secureStore.read( + key: "${walletId}_changeDerivations")) ?? + "{}") as Map, + ); + + for (var i = 0; i < changeDerivations.length; i++) { + final change = changeDerivations["$i"]; + if (change['address'] == addressString) { + wif = change['wif'] as String; + pubKey = change['publicKey'] as String; + + break; + } + } } } + + ellipticCurvePairArray.add( + ECPair.fromWIF( + wif!, + network: _network, + ), + ); + outputDataArray.add(P2PKH( + network: _network, + data: PaymentData( + pubkey: Format.stringToUint8List( + pubKey!, + ), + ), + ).data.output!); } final txb = TransactionBuilder(network: _network); @@ -2818,7 +2718,7 @@ class FiroWallet extends CoinServiceAPI amount += utxosToUse[i].value; } - final index = firoGetMintIndex()!; + final index = firoGetMintIndex(); Logging.instance.log("index of mint $index", level: LogLevel.Info); for (var mintsElement in mintsMap) { @@ -2831,7 +2731,7 @@ class FiroWallet extends CoinServiceAPI for (var i = 0; i < utxosToUse.length; i++) { txb.sign( vin: i, - keyPair: elipticCurvePairArray[i], + keyPair: ellipticCurvePairArray[i], witnessValue: utxosToUse[i].value, ); } @@ -3085,7 +2985,7 @@ class FiroWallet extends CoinServiceAPI // if a jmint was made add it to the unspent coin index LelantusCoin jmint = LelantusCoin( - index!, + index, transactionInfo['jmintValue'] as int? ?? 0, transactionInfo['publicCoin'] as String, transactionInfo['txid'] as String, @@ -3262,17 +3162,18 @@ class FiroWallet extends CoinServiceAPI ); } - //TODO call get transaction and check each tx to see if it is a "received" tx? - Future _getReceivedTxCount({required String address}) async { + Future _getTxCount({required String address}) async { try { - final scripthash = AddressUtils.convertToScriptHash(address, _network); - final transactions = - await electrumXClient.getHistory(scripthash: scripthash); + final scriptHash = AddressUtils.convertToScriptHash(address, _network); + final transactions = await electrumXClient.getHistory( + scripthash: scriptHash, + ); return transactions.length; } catch (e) { Logging.instance.log( - "Exception rethrown in _getReceivedTxCount(address: $address): $e", - level: LogLevel.Error); + "Exception rethrown in _getReceivedTxCount(address: $address): $e", + level: LogLevel.Error, + ); rethrow; } } @@ -3281,8 +3182,7 @@ class FiroWallet extends CoinServiceAPI try { final currentReceiving = await _currentReceivingAddress; - final int txCount = - await _getReceivedTxCount(address: currentReceiving.value); + final int txCount = await _getTxCount(address: currentReceiving.value); Logging.instance.log( 'Number of txs for current receiving address $currentReceiving: $txCount', level: LogLevel.Info); @@ -3328,8 +3228,7 @@ class FiroWallet extends CoinServiceAPI Future checkChangeAddressForTransactions() async { try { final currentChange = await _currentChangeAddress; - final int txCount = - await _getReceivedTxCount(address: currentChange.value); + final int txCount = await _getTxCount(address: currentChange.value); Logging.instance.log( 'Number of txs for current change address: $currentChange: $txCount', level: LogLevel.Info); @@ -3377,27 +3276,13 @@ class FiroWallet extends CoinServiceAPI .getAddresses(walletId) .filter() .not() - .typeEqualTo(isar_models.AddressType.nonWallet) - .and() - .group((q) => q - .subTypeEqualTo(isar_models.AddressSubType.receiving) - .or() - .subTypeEqualTo(isar_models.AddressSubType.change)) + .group( + (q) => q + .typeEqualTo(isar_models.AddressType.nonWallet) + .or() + .subTypeEqualTo(isar_models.AddressSubType.nonWallet), + ) .findAll(); - // final List allAddresses = []; - // final receivingAddresses = - // DB.instance.get(boxName: walletId, key: 'receivingAddresses') - // as List; - // final changeAddresses = - // DB.instance.get(boxName: walletId, key: 'changeAddresses') - // as List; - // - // for (var i = 0; i < receivingAddresses.length; i++) { - // allAddresses.add(receivingAddresses[i] as String); - // } - // for (var i = 0; i < changeAddresses.length; i++) { - // allAddresses.add(changeAddresses[i] as String); - // } return allAddresses; } @@ -3460,252 +3345,451 @@ class FiroWallet extends CoinServiceAPI final List allAddresses = await _fetchAllOwnAddresses(); - final List> allTxHashes = - await _fetchHistory(allAddresses.map((e) => e.value).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); - } - } - } - - final List> txnsData = - []; - + Set receivingAddresses = allAddresses + .where((e) => e.subType == isar_models.AddressSubType.receiving) + .map((e) => e.value) + .toSet(); Set changeAddresses = allAddresses .where((e) => e.subType == isar_models.AddressSubType.change) .map((e) => e.value) .toSet(); - for (final txObject in allTransactions) { - // Logging.instance.log(txObject); - List sendersArray = []; - List recipientsArray = []; + final List> allTxHashes = + await _fetchHistory(allAddresses.map((e) => e.value).toList()); - // Usually only has value when txType = 'Send' - int inputAmtSentFromWallet = 0; - // Usually has value regardless of txType due to change addresses - int outputAmtAddressedToWallet = 0; + List> allTransactions = []; - for (final input in txObject["vin"] as List) { - final address = input["address"] as String?; - if (address != null) { - sendersArray.add(address); - } - } + // final currentHeight = await chainHeight; - // Logging.instance.log("sendersArray: $sendersArray"); + for (final txHash in allTxHashes) { + // final storedTx = await db + // .getTransactions(walletId) + // .filter() + // .txidEqualTo(txHash["tx_hash"] as String) + // .findFirst(); - for (final output in txObject["vout"] as List) { - final address = output["scriptPubKey"]?["addresses"]?[0] as String? ?? - output["scriptPubKey"]?["address"] as String?; - if (address != null) { - recipientsArray.add(address); - } - } - // Logging.instance.log("recipientsArray: $recipientsArray"); - - final foundInSenders = - allAddresses.any((element) => sendersArray.contains(element.value)); - // Logging.instance.log("foundInSenders: $foundInSenders"); - - String outAddress = ""; - - int fees = 0; - - // If txType = Sent, then calculate inputAmtSentFromWallet, calculate who received how much in aliens array (check outputs) - if (foundInSenders) { - int outAmount = 0; - int inAmount = 0; - bool nFeesUsed = false; - - for (final input in txObject["vin"] as List) { - final nFees = input["nFees"]; - if (nFees != null) { - nFeesUsed = true; - fees = (Decimal.parse(nFees.toString()) * - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toBigInt() - .toInt(); - } - final address = input["address"] as String?; - final value = input["valueSat"] as int?; - if (address != null && value != null) { - if (allAddresses.where((e) => e.value == address).isNotEmpty) { - inputAmtSentFromWallet += value; - } - } - - if (value != null) { - inAmount += value; - } - } - - for (final output in txObject["vout"] as List) { - final address = output["scriptPubKey"]?["addresses"]?[0] as String? ?? - output["scriptPubKey"]?["address"] as String?; - final value = output["value"]; - - if (value != null) { - outAmount += (Decimal.parse(value.toString()) * - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toBigInt() - .toInt(); - - if (address != null) { - if (changeAddresses.contains(address)) { - inputAmtSentFromWallet -= (Decimal.parse(value.toString()) * - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toBigInt() - .toInt(); - } else { - outAddress = address; - } - } - } - } - - fees = nFeesUsed ? fees : inAmount - outAmount; - inputAmtSentFromWallet -= inAmount - outAmount; - } else { - for (final input in txObject["vin"] as List) { - final nFees = input["nFees"]; - if (nFees != null) { - fees += (Decimal.parse(nFees.toString()) * - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toBigInt() - .toInt(); - } - } - - for (final output in txObject["vout"] as List) { - final addresses = output["scriptPubKey"]["addresses"] as List?; - if (addresses != null && addresses.isNotEmpty) { - final address = addresses[0] as String; - final value = output["value"] ?? 0; - // Logging.instance.log(address + value.toString()); - - if (allAddresses.where((e) => e.value == address).isNotEmpty) { - outputAmtAddressedToWallet += (Decimal.parse(value.toString()) * - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toBigInt() - .toInt(); - outAddress = address; - } - } - } - } - - isar_models.TransactionType type; - isar_models.TransactionSubType subType = - isar_models.TransactionSubType.none; - int amount; - if (foundInSenders) { - type = isar_models.TransactionType.outgoing; - amount = inputAmtSentFromWallet; - - if (txObject["vout"][0]["scriptPubKey"]["type"] == "lelantusmint") { - subType = isar_models.TransactionSubType.mint; - } - } else { - type = isar_models.TransactionType.incoming; - amount = outputAmtAddressedToWallet; - } - - final transactionAddress = - allAddresses.firstWhere((e) => e.value == outAddress, - orElse: () => isar_models.Address( - walletId: walletId, - value: outAddress, - derivationIndex: -1, - derivationPath: null, - type: isar_models.AddressType.nonWallet, - subType: isar_models.AddressSubType.nonWallet, - publicKey: [], - )); - - List outs = []; - List ins = []; - - for (final json in txObject["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?, - ); - ins.add(input); - } - - for (final json in txObject["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(), - ); - outs.add(output); - } - - final tx = isar_models.Transaction( - walletId: walletId, - txid: txObject["txid"] as String, - timestamp: txObject["blocktime"] as int? ?? - (DateTime.now().millisecondsSinceEpoch ~/ 1000), - type: type, - subType: subType, - amount: amount, - amountString: Amount( - rawValue: BigInt.from(amount), - fractionDigits: Coin.firo.decimals, - ).toJsonString(), - fee: fees, - height: txObject["height"] as int? ?? 0, - isCancelled: false, - isLelantus: false, - slateId: null, - otherData: null, - nonce: null, - inputs: ins, - outputs: outs, + // if (storedTx == null || + // !storedTx.isConfirmed(currentHeight, MINIMUM_CONFIRMATIONS)) { + final tx = await cachedElectrumXClient.getTransaction( + txHash: txHash["tx_hash"] as String, + verbose: true, + coin: coin, ); - txnsData.add(Tuple2(tx, transactionAddress)); + 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); + } + // } + } + + final List> txnsData = + []; + + for (final txObject in allTransactions) { + final inputList = txObject["vin"] as List; + final outputList = txObject["vout"] as List; + + bool isMint = false; + bool isJMint = false; + + // check if tx is Mint or jMint + for (final output in outputList) { + if (output["scriptPubKey"]?["type"] == "lelantusmint") { + final asm = output["scriptPubKey"]?["asm"] as String?; + if (asm != null) { + if (asm.startsWith("OP_LELANTUSJMINT")) { + isJMint = true; + break; + } else if (asm.startsWith("OP_LELANTUSMINT")) { + isMint = true; + break; + } else { + Logging.instance.log( + "Unknown mint op code found for lelantusmint tx: ${txObject["txid"]}", + level: LogLevel.Error, + ); + } + } else { + Logging.instance.log( + "ASM for lelantusmint tx: ${txObject["txid"]} is null!", + level: LogLevel.Error, + ); + } + } + } + + Set inputAddresses = {}; + Set outputAddresses = {}; + + Amount totalInputValue = Amount( + rawValue: BigInt.zero, + fractionDigits: coin.decimals, + ); + Amount totalOutputValue = Amount( + rawValue: BigInt.zero, + fractionDigits: coin.decimals, + ); + + Amount amountSentFromWallet = Amount( + rawValue: BigInt.zero, + fractionDigits: coin.decimals, + ); + Amount amountReceivedInWallet = Amount( + rawValue: BigInt.zero, + fractionDigits: coin.decimals, + ); + Amount changeAmount = Amount( + rawValue: BigInt.zero, + fractionDigits: coin.decimals, + ); + + // Parse mint transaction ================================================ + // We should be able to assume this belongs to this wallet + if (isMint) { + List ins = []; + + // Parse inputs + for (final input in inputList) { + // Both value and address should not be null for a mint + final address = input["address"] as String?; + final value = input["valueSat"] as int?; + + // We should not need to check whether the mint belongs to this + // wallet as any tx we look up will be looked up by one of this + // wallet's addresses + if (address != null && value != null) { + totalInputValue += value.toAmountAsRaw( + fractionDigits: coin.decimals, + ); + } + + ins.add( + isar_models.Input( + txid: input['txid'] as String? ?? "", + vout: input['vout'] as int? ?? -1, + scriptSig: input['scriptSig']?['hex'] as String?, + scriptSigAsm: input['scriptSig']?['asm'] as String?, + isCoinbase: input['is_coinbase'] as bool?, + sequence: input['sequence'] as int?, + innerRedeemScriptAsm: input['innerRedeemscriptAsm'] as String?, + ), + ); + } + + // Parse outputs + for (final output in outputList) { + // get value + final value = Amount.fromDecimal( + Decimal.parse(output["value"].toString()), + fractionDigits: coin.decimals, + ); + + // add value to total + totalOutputValue += value; + } + + final fee = totalInputValue - totalOutputValue; + final tx = isar_models.Transaction( + walletId: walletId, + txid: txObject["txid"] as String, + timestamp: txObject["blocktime"] as int? ?? + (DateTime.now().millisecondsSinceEpoch ~/ 1000), + type: isar_models.TransactionType.sentToSelf, + subType: isar_models.TransactionSubType.mint, + amount: totalOutputValue.raw.toInt(), + amountString: totalOutputValue.toJsonString(), + fee: fee.raw.toInt(), + height: txObject["height"] as int?, + isCancelled: false, + isLelantus: true, + slateId: null, + otherData: null, + nonce: null, + inputs: ins, + outputs: [], + ); + + txnsData.add(Tuple2(tx, null)); + + // Otherwise parse JMint transaction =================================== + } else if (isJMint) { + Amount jMintFees = Amount( + rawValue: BigInt.zero, + fractionDigits: coin.decimals, + ); + + // Parse inputs + List ins = []; + for (final input in inputList) { + // JMint fee + final nFee = Decimal.tryParse(input["nFees"].toString()); + if (nFee != null) { + final fees = Amount.fromDecimal( + nFee, + fractionDigits: coin.decimals, + ); + + jMintFees += fees; + } + + ins.add( + isar_models.Input( + txid: input['txid'] as String? ?? "", + vout: input['vout'] as int? ?? -1, + scriptSig: input['scriptSig']?['hex'] as String?, + scriptSigAsm: input['scriptSig']?['asm'] as String?, + isCoinbase: input['is_coinbase'] as bool?, + sequence: input['sequence'] as int?, + innerRedeemScriptAsm: input['innerRedeemscriptAsm'] as String?, + ), + ); + } + + bool nonWalletAddressFoundInOutputs = false; + + // Parse outputs + List outs = []; + for (final output in outputList) { + // get value + final value = Amount.fromDecimal( + Decimal.parse(output["value"].toString()), + fractionDigits: coin.decimals, + ); + + // add value to total + totalOutputValue += value; + + final address = output["scriptPubKey"]?["addresses"]?[0] as String? ?? + output['scriptPubKey']?['address'] as String?; + + if (address != null) { + outputAddresses.add(address); + if (receivingAddresses.contains(address) || + changeAddresses.contains(address)) { + amountReceivedInWallet += value; + } else { + nonWalletAddressFoundInOutputs = true; + } + } + + outs.add( + isar_models.Output( + scriptPubKey: output['scriptPubKey']?['hex'] as String?, + scriptPubKeyAsm: output['scriptPubKey']?['asm'] as String?, + scriptPubKeyType: output['scriptPubKey']?['type'] as String?, + scriptPubKeyAddress: address ?? "jmint", + value: value.raw.toInt(), + ), + ); + } + + const subType = isar_models.TransactionSubType.join; + final type = nonWalletAddressFoundInOutputs + ? isar_models.TransactionType.outgoing + : isar_models.TransactionType.incoming; + + final amount = nonWalletAddressFoundInOutputs + ? totalOutputValue + : amountReceivedInWallet; + + final possibleNonWalletAddresses = + receivingAddresses.difference(outputAddresses); + final possibleReceivingAddresses = + receivingAddresses.intersection(outputAddresses); + + final transactionAddress = nonWalletAddressFoundInOutputs + ? isar_models.Address( + walletId: walletId, + value: possibleNonWalletAddresses.first, + derivationIndex: -1, + derivationPath: null, + type: isar_models.AddressType.nonWallet, + subType: isar_models.AddressSubType.nonWallet, + publicKey: [], + ) + : allAddresses.firstWhere( + (e) => e.value == possibleReceivingAddresses.first, + ); + + final tx = isar_models.Transaction( + walletId: walletId, + txid: txObject["txid"] as String, + timestamp: txObject["blocktime"] as int? ?? + (DateTime.now().millisecondsSinceEpoch ~/ 1000), + type: type, + subType: subType, + amount: amount.raw.toInt(), + amountString: amount.toJsonString(), + fee: jMintFees.raw.toInt(), + height: txObject["height"] as int?, + isCancelled: false, + isLelantus: true, + slateId: null, + otherData: null, + nonce: null, + inputs: ins, + outputs: outs, + ); + + txnsData.add(Tuple2(tx, transactionAddress)); + + // Assume non lelantus transaction ===================================== + } else { + // parse inputs + List ins = []; + for (final input in inputList) { + final valueSat = input["valueSat"] as int?; + final address = input["address"] as String? ?? + input["scriptPubKey"]?["address"] as String? ?? + input["scriptPubKey"]?["addresses"]?[0] as String?; + + if (address != null && valueSat != null) { + final value = valueSat.toAmountAsRaw( + fractionDigits: coin.decimals, + ); + + // add value to total + totalInputValue += value; + inputAddresses.add(address); + + // if input was from my wallet, add value to amount sent + if (receivingAddresses.contains(address) || + changeAddresses.contains(address)) { + amountSentFromWallet += value; + } + } + + ins.add( + isar_models.Input( + txid: input['txid'] as String, + vout: input['vout'] as int? ?? -1, + scriptSig: input['scriptSig']?['hex'] as String?, + scriptSigAsm: input['scriptSig']?['asm'] as String?, + isCoinbase: input['is_coinbase'] as bool?, + sequence: input['sequence'] as int?, + innerRedeemScriptAsm: input['innerRedeemscriptAsm'] as String?, + ), + ); + } + + // parse outputs + List outs = []; + for (final output in outputList) { + // 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; + } + } + + outs.add( + isar_models.Output( + scriptPubKey: output['scriptPubKey']?['hex'] as String?, + scriptPubKeyAsm: output['scriptPubKey']?['asm'] as String?, + scriptPubKeyType: output['scriptPubKey']?['type'] as String?, + scriptPubKeyAddress: address ?? "", + value: value.raw.toInt(), + ), + ); + } + + 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 = + txObject["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; + + // should be 0 + 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, + derivationIndex: -1, + derivationPath: null, + subType: isar_models.AddressSubType.nonWallet, + type: isar_models.AddressType.nonWallet, + publicKey: [], + ); + } + } else { + // incoming tx + type = isar_models.TransactionType.incoming; + amount = amountReceivedInWallet; + } + + final tx = isar_models.Transaction( + walletId: walletId, + txid: txObject["txid"] as String, + timestamp: txObject["blocktime"] as int? ?? + (DateTime.now().millisecondsSinceEpoch ~/ 1000), + type: type, + subType: isar_models.TransactionSubType.none, + // amount may overflow. Deprecated. Use amountString + amount: amount.raw.toInt(), + amountString: amount.toJsonString(), + fee: fee.raw.toInt(), + height: txObject["height"] as int?, + isCancelled: false, + isLelantus: false, + slateId: null, + otherData: null, + nonce: null, + inputs: ins, + outputs: outs, + ); + + txnsData.add(Tuple2(tx, transactionAddress)); + } } await db.addNewTransactionData(txnsData, walletId); @@ -3859,72 +3943,6 @@ class FiroWallet extends CoinServiceAPI return address!.value; } - Future fillAddresses( - String suppliedMnemonic, - String mnemonicPassphrase, { - int perBatch = 50, - int numberOfThreads = 4, - }) async { - if (numberOfThreads <= 0) { - numberOfThreads = 1; - } - if (Platform.environment["FLUTTER_TEST"] == "true" || integrationTestFlag) { - perBatch = 10; - numberOfThreads = 4; - } - - final receiveDerivationsString = - await _secureStore.read(key: "${walletId}_receiveDerivations"); - final changeDerivationsString = - await _secureStore.read(key: "${walletId}_changeDerivations"); - - var receiveDerivations = Map.from( - jsonDecode(receiveDerivationsString ?? "{}") as Map); - var changeDerivations = Map.from( - jsonDecode(changeDerivationsString ?? "{}") as Map); - - final int start = receiveDerivations.length; - - List ports = List.empty(growable: true); - for (int i = 0; i < numberOfThreads; i++) { - ReceivePort receivePort = await getIsolate({ - "function": "isolateDerive", - "mnemonic": suppliedMnemonic, - "mnemonicPassphrase": mnemonicPassphrase, - "from": start + i * perBatch, - "to": start + (i + 1) * perBatch, - "network": _network, - }); - ports.add(receivePort); - } - for (int i = 0; i < numberOfThreads; i++) { - ReceivePort receivePort = ports.elementAt(i); - var message = await receivePort.first; - if (message is String) { - Logging.instance.log("this is a string", level: LogLevel.Error); - stop(receivePort); - throw Exception("isolateDerive isolate failed"); - } - stop(receivePort); - Logging.instance.log('Closing isolateDerive!', level: LogLevel.Info); - receiveDerivations.addAll(message['receive'] as Map); - changeDerivations.addAll(message['change'] as Map); - } - Logging.instance.log("isolate derives", level: LogLevel.Info); - // Logging.instance.log(receiveDerivations); - // Logging.instance.log(changeDerivations); - - final newReceiveDerivationsString = jsonEncode(receiveDerivations); - final newChangeDerivationsString = jsonEncode(changeDerivations); - - await _secureStore.write( - key: "${walletId}_receiveDerivations", - value: newReceiveDerivationsString); - await _secureStore.write( - key: "${walletId}_changeDerivations", - value: newChangeDerivationsString); - } - /// Generates a new internal or external chain address for the wallet using a BIP84 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 @@ -3934,75 +3952,44 @@ class FiroWallet extends CoinServiceAPI 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", + "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); } - Map? derivations; - if (chain == 0) { - final receiveDerivationsString = - await _secureStore.read(key: "${walletId}_receiveDerivations"); - derivations = Map.from( - jsonDecode(receiveDerivationsString ?? "{}") as Map); - } else if (chain == 1) { - final changeDerivationsString = - await _secureStore.read(key: "${walletId}_changeDerivations"); - derivations = Map.from( - jsonDecode(changeDerivationsString ?? "{}") as Map); - } final derivePath = constructDerivePath( networkWIF: _network.wif, chain: chain, index: index, ); - if (derivations!.isNotEmpty) { - if (derivations["$index"] == null) { - await fillAddresses( - _mnemonic!, - _mnemonicPassphrase!, - numberOfThreads: Platform.numberOfProcessors - isolates.length - 1, - ); - Logging.instance.log("calling _generateAddressForChain recursively", - level: LogLevel.Info); - return _generateAddressForChain(chain, index); - } - return isar_models.Address( - walletId: walletId, - value: derivations["$index"]['address'] as String, - publicKey: Format.stringToUint8List( - derivations["$index"]['publicKey'] as String), - type: isar_models.AddressType.p2pkh, - derivationIndex: index, - derivationPath: isar_models.DerivationPath()..value = derivePath, - subType: chain == 0 - ? isar_models.AddressSubType.receiving - : isar_models.AddressSubType.change, - ); - } else { - final node = await Bip32Utils.getBip32Node( - _mnemonic!, - _mnemonicPassphrase!, - _network, - derivePath, - ); - final address = - P2PKH(network: _network, data: PaymentData(pubkey: node.publicKey)) - .data - .address!; + final node = await Bip32Utils.getBip32Node( + _mnemonic!, + _mnemonicPassphrase!, + _network, + derivePath, + ); - return isar_models.Address( - walletId: walletId, - value: address, - publicKey: node.publicKey, - type: isar_models.AddressType.p2pkh, - derivationIndex: index, - derivationPath: isar_models.DerivationPath()..value = derivePath, - subType: chain == 0 - ? isar_models.AddressSubType.receiving - : isar_models.AddressSubType.change, - ); - } + final address = P2PKH( + network: _network, + data: PaymentData( + pubkey: node.publicKey, + ), + ).data.address!; + + return isar_models.Address( + walletId: walletId, + value: address, + publicKey: node.publicKey, + type: isar_models.AddressType.p2pkh, + derivationIndex: index, + derivationPath: isar_models.DerivationPath()..value = derivePath, + subType: chain == 0 + ? isar_models.AddressSubType.receiving + : isar_models.AddressSubType.change, + ); } // /// Takes in a list of isar_models.UTXOs and adds a name (dependent on object index within list) @@ -4087,6 +4074,8 @@ class FiroWallet extends CoinServiceAPI _mnemonic!, _mnemonicPassphrase!, maxUnusedAddressGap, + maxNumberOfIndexesToCheck, + true, ); longMutex = false; @@ -4124,151 +4113,6 @@ class FiroWallet extends CoinServiceAPI await _secureStore.delete(key: "${walletId}_changeDerivations"); } - // Future _rescanBackup() async { - // Logging.instance.log("starting rescan backup", level: LogLevel.Info); - // - // // backup current and clear data - // final tempReceivingAddresses = - // DB.instance.get(boxName: walletId, key: 'receivingAddresses'); - // await DB.instance.delete( - // key: 'receivingAddresses', - // boxName: walletId, - // ); - // await DB.instance.put( - // boxName: walletId, - // key: 'receivingAddresses_BACKUP', - // value: tempReceivingAddresses); - // - // final tempChangeAddresses = - // DB.instance.get(boxName: walletId, key: 'changeAddresses'); - // await DB.instance.delete( - // key: 'changeAddresses', - // boxName: walletId, - // ); - // await DB.instance.put( - // boxName: walletId, - // key: 'changeAddresses_BACKUP', - // value: tempChangeAddresses); - // - // final tempReceivingIndex = - // DB.instance.get(boxName: walletId, key: 'receivingIndex'); - // await DB.instance.delete( - // key: 'receivingIndex', - // boxName: walletId, - // ); - // await DB.instance.put( - // boxName: walletId, - // key: 'receivingIndex_BACKUP', - // value: tempReceivingIndex); - // - // final tempChangeIndex = - // DB.instance.get(boxName: walletId, key: 'changeIndex'); - // await DB.instance.delete( - // key: 'changeIndex', - // boxName: walletId, - // ); - // await DB.instance.put( - // boxName: walletId, key: 'changeIndex_BACKUP', value: tempChangeIndex); - // - // final receiveDerivationsString = - // await _secureStore.read(key: "${walletId}_receiveDerivations"); - // final changeDerivationsString = - // await _secureStore.read(key: "${walletId}_changeDerivations"); - // - // await _secureStore.write( - // key: "${walletId}_receiveDerivations_BACKUP", - // value: receiveDerivationsString); - // await _secureStore.write( - // key: "${walletId}_changeDerivations_BACKUP", - // value: changeDerivationsString); - // - // await _secureStore.write( - // key: "${walletId}_receiveDerivations", value: null); - // await _secureStore.write(key: "${walletId}_changeDerivations", value: null); - // - // // back up but no need to delete - // final tempMintIndex = - // DB.instance.get(boxName: walletId, key: 'mintIndex'); - // await DB.instance.put( - // boxName: walletId, key: 'mintIndex_BACKUP', value: tempMintIndex); - // - // final tempLelantusCoins = - // DB.instance.get(boxName: walletId, key: '_lelantus_coins'); - // await DB.instance.put( - // boxName: walletId, - // key: '_lelantus_coins_BACKUP', - // value: tempLelantusCoins); - // - // final tempJIndex = - // DB.instance.get(boxName: walletId, key: 'jindex'); - // await DB.instance.put( - // boxName: walletId, key: 'jindex_BACKUP', value: tempJIndex); - // - // final tempLelantusTxModel = DB.instance - // .get(boxName: walletId, key: 'latest_lelantus_tx_model'); - // await DB.instance.put( - // boxName: walletId, - // key: 'latest_lelantus_tx_model_BACKUP', - // value: tempLelantusTxModel); - // - // Logging.instance.log("rescan backup complete", level: LogLevel.Info); - // } - // - // Future _rescanRestore() async { - // Logging.instance.log("starting rescan restore", level: LogLevel.Info); - // - // // restore from backup - // final tempReceivingAddresses = DB.instance - // .get(boxName: walletId, key: 'receivingAddresses_BACKUP'); - // final tempChangeAddresses = DB.instance - // .get(boxName: walletId, key: 'changeAddresses_BACKUP'); - // final tempReceivingIndex = DB.instance - // .get(boxName: walletId, key: 'receivingIndex_BACKUP'); - // final tempChangeIndex = - // DB.instance.get(boxName: walletId, key: 'changeIndex_BACKUP'); - // final tempMintIndex = - // DB.instance.get(boxName: walletId, key: 'mintIndex_BACKUP'); - // final tempLelantusCoins = DB.instance - // .get(boxName: walletId, key: '_lelantus_coins_BACKUP'); - // final tempJIndex = - // DB.instance.get(boxName: walletId, key: 'jindex_BACKUP'); - // final tempLelantusTxModel = DB.instance.get( - // boxName: walletId, key: 'latest_lelantus_tx_model_BACKUP'); - // - // final receiveDerivationsString = - // await _secureStore.read(key: "${walletId}_receiveDerivations_BACKUP"); - // final changeDerivationsString = - // await _secureStore.read(key: "${walletId}_changeDerivations_BACKUP"); - // - // await _secureStore.write( - // key: "${walletId}_receiveDerivations", value: receiveDerivationsString); - // await _secureStore.write( - // key: "${walletId}_changeDerivations", value: changeDerivationsString); - // - // await DB.instance.put( - // boxName: walletId, - // key: 'receivingAddresses', - // value: tempReceivingAddresses); - // await DB.instance.put( - // boxName: walletId, key: 'changeAddresses', value: tempChangeAddresses); - // await DB.instance.put( - // boxName: walletId, key: 'receivingIndex', value: tempReceivingIndex); - // await DB.instance.put( - // boxName: walletId, key: 'changeIndex', value: tempChangeIndex); - // await DB.instance.put( - // boxName: walletId, key: 'mintIndex', value: tempMintIndex); - // await DB.instance.put( - // boxName: walletId, key: '_lelantus_coins', value: tempLelantusCoins); - // await DB.instance - // .put(boxName: walletId, key: 'jindex', value: tempJIndex); - // await DB.instance.put( - // boxName: walletId, - // key: 'latest_lelantus_tx_model', - // value: tempLelantusTxModel); - // - // Logging.instance.log("rescan restore complete", level: LogLevel.Info); - // } - /// wrapper for _recoverWalletFromBIP32SeedPhrase() @override Future recoverFromMnemonic({ @@ -4329,6 +4173,8 @@ class FiroWallet extends CoinServiceAPI mnemonic.trim(), mnemonicPassphrase ?? "", maxUnusedAddressGap, + maxNumberOfIndexesToCheck, + false, ); await compute( @@ -4363,142 +4209,270 @@ class FiroWallet extends CoinServiceAPI return setDataMap; } - Future _makeDerivations( + 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, int>> _checkGaps( + int maxNumberOfIndexesToCheck, + int maxUnusedAddressGap, + int txCountBatchSize, + bip32.BIP32 root, + int chain, + ) async { + List addressArray = []; + int gapCounter = 0; + int highestIndexWithHistory = 0; + + for (int index = 0; + index < maxNumberOfIndexesToCheck && gapCounter < maxUnusedAddressGap; + index += txCountBatchSize) { + List iterationsAddressArray = []; + Logging.instance.log( + "index: $index, \t GapCounter $chain: $gapCounter", + level: LogLevel.Info, + ); + + final _id = "k_$index"; + Map txCountCallArgs = {}; + + for (int j = 0; j < txCountBatchSize; j++) { + final derivePath = constructDerivePath( + networkWIF: root.network.wif, + chain: chain, + index: index + j, + ); + final node = await Bip32Utils.getBip32NodeFromRoot(root, derivePath); + + final data = PaymentData(pubkey: node.publicKey); + final String addressString = P2PKH( + data: data, + network: _network, + ).data.address!; + const isar_models.AddressType addrType = isar_models.AddressType.p2pkh; + + 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, + ); + + addressArray.add(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) { + iterationsAddressArray.add(txCountCallArgs["${_id}_$k"]!); + + // update highest + highestIndexWithHistory = index + k; + + // reset counter + gapCounter = 0; + } + + // 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 Tuple2(addressArray, highestIndexWithHistory); + } + + 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) { + // + } + } + + Future _recoverHistory( String suppliedMnemonic, String mnemonicPassphrase, int maxUnusedAddressGap, + int maxNumberOfIndexesToCheck, + bool isRescan, ) async { - List receivingAddressArray = []; - List changeAddressArray = []; + final root = await Bip32Utils.getBip32Root( + suppliedMnemonic, + mnemonicPassphrase, + _network, + ); - int receivingIndex = -1; - int changeIndex = -1; + final List, int>>> receiveFutures = + []; + final List, int>>> changeFutures = + []; - // The gap limit will be capped at 20 - int receivingGapCounter = 0; - int changeGapCounter = 0; + const receiveChain = 0; + const changeChain = 1; + const indexZero = 0; - await fillAddresses(suppliedMnemonic, mnemonicPassphrase, - numberOfThreads: Platform.numberOfProcessors - isolates.length - 1); + // actual size is 36 due to p2pkh, p2sh, and p2wpkh so 12x3 + const txCountBatchSize = 12; - final receiveDerivationsString = - await _secureStore.read(key: "${walletId}_receiveDerivations"); - final changeDerivationsString = - await _secureStore.read(key: "${walletId}_changeDerivations"); + try { + // receiving addresses + Logging.instance.log( + "checking receiving addresses...", + level: LogLevel.Info, + ); - final receiveDerivations = Map.from( - jsonDecode(receiveDerivationsString ?? "{}") as Map); - final changeDerivations = Map.from( - jsonDecode(changeDerivationsString ?? "{}") as Map); + receiveFutures.add( + _checkGaps( + maxNumberOfIndexesToCheck, + maxUnusedAddressGap, + txCountBatchSize, + root, + receiveChain, + ), + ); - // log("rcv: $receiveDerivations"); - // log("chg: $changeDerivations"); + // change addresses + Logging.instance.log( + "checking change addresses...", + level: LogLevel.Info, + ); + changeFutures.add( + _checkGaps( + maxNumberOfIndexesToCheck, + maxUnusedAddressGap, + txCountBatchSize, + root, + changeChain, + ), + ); - // Deriving and checking for receiving addresses - for (var i = 0; i < receiveDerivations.length; i++) { - // Break out of loop when receivingGapCounter hits maxUnusedAddressGap - // Same gap limit for change as for receiving, breaks when it hits maxUnusedAddressGap - if (receivingGapCounter >= maxUnusedAddressGap && - changeGapCounter >= maxUnusedAddressGap) { - break; - } + // io limitations may require running these linearly instead + final futuresResult = await Future.wait([ + Future.wait(receiveFutures), + Future.wait(changeFutures), + ]); - final receiveDerivation = receiveDerivations["$i"]; - final address = receiveDerivation['address'] as String; + final receiveResults = futuresResult[0]; + final changeResults = futuresResult[1]; - final changeDerivation = changeDerivations["$i"]; - final _address = changeDerivation['address'] as String; - Future? futureNumTxs; - Future? _futureNumTxs; - if (receivingGapCounter < maxUnusedAddressGap) { - futureNumTxs = _getReceivedTxCount(address: address); - } - if (changeGapCounter < maxUnusedAddressGap) { - _futureNumTxs = _getReceivedTxCount(address: _address); - } - try { - if (futureNumTxs != null) { - int numTxs = await futureNumTxs; - if (numTxs >= 1) { - receivingIndex = i; - final derivePath = constructDerivePath( - networkWIF: _network.wif, - chain: 0, - index: receivingIndex, - ); - final addr = isar_models.Address( - walletId: walletId, - value: address, - publicKey: Format.stringToUint8List( - receiveDerivation['publicKey'] as String), - type: isar_models.AddressType.p2pkh, - derivationIndex: i, - derivationPath: isar_models.DerivationPath()..value = derivePath, - subType: isar_models.AddressSubType.receiving, - ); - receivingAddressArray.add(addr); - } else if (numTxs == 0) { - receivingGapCounter += 1; - } + final List addressesToStore = []; + + int highestReceivingIndexWithHistory = 0; + // 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 + for (final tuple in receiveResults) { + if (tuple.item1.isEmpty) { + final address = await _generateAddressForChain( + receiveChain, + indexZero, + ); + addressesToStore.add(address); + } else { + highestReceivingIndexWithHistory = + max(tuple.item2, highestReceivingIndexWithHistory); + addressesToStore.addAll(tuple.item1); } - } catch (e, s) { - Logging.instance.log( - "Exception rethrown from recoverWalletFromBIP32SeedPhrase(): $e\n$s", - level: LogLevel.Error); - rethrow; } - try { - if (_futureNumTxs != null) { - int numTxs = await _futureNumTxs; - if (numTxs >= 1) { - changeIndex = i; - final derivePath = constructDerivePath( - networkWIF: _network.wif, - chain: 1, - index: changeIndex, - ); - final addr = isar_models.Address( - walletId: walletId, - value: _address, - publicKey: Format.stringToUint8List( - changeDerivation['publicKey'] as String), - type: isar_models.AddressType.p2pkh, - derivationIndex: i, - derivationPath: isar_models.DerivationPath()..value = derivePath, - subType: isar_models.AddressSubType.change, - ); - changeAddressArray.add(addr); - } else if (numTxs == 0) { - changeGapCounter += 1; - } + int highestChangeIndexWithHistory = 0; + // 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. + for (final tuple in changeResults) { + if (tuple.item1.isEmpty) { + final address = await _generateAddressForChain( + changeChain, + indexZero, + ); + addressesToStore.add(address); + } else { + highestChangeIndexWithHistory = + max(tuple.item2, highestChangeIndexWithHistory); + addressesToStore.addAll(tuple.item1); } - } catch (e, s) { - Logging.instance.log( - "Exception rethrown from recoverWalletFromBIP32SeedPhrase(): $e\n$s", - level: LogLevel.Error); - rethrow; } - } - // 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 (receivingIndex == -1) { - final receivingAddress = await _generateAddressForChain(0, 0); - receivingAddressArray.add(receivingAddress); - } + // remove extra addresses to help minimize risk of creating a large gap + addressesToStore.removeWhere((e) => + e.subType == isar_models.AddressSubType.change && + e.derivationIndex > highestChangeIndexWithHistory); + addressesToStore.removeWhere((e) => + e.subType == isar_models.AddressSubType.receiving && + e.derivationIndex > highestReceivingIndexWithHistory); - // 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 (changeIndex == -1) { - final changeAddress = await _generateAddressForChain(1, 0); - changeAddressArray.add(changeAddress); - } + if (isRescan) { + await db.updateOrPutAddresses(addressesToStore); + } else { + await db.putAddresses(addressesToStore); + } - await db.updateOrPutAddresses([ - ...receivingAddressArray, - ...changeAddressArray, - ]); + await Future.wait([ + _refreshTransactions(), + _refreshUTXOs(), + ]); + + await Future.wait([ + updateCachedId(walletId), + updateCachedIsFavorite(false), + ]); + + longMutex = false; + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from _recoverWalletFromBIP32SeedPhrase(): $e\n$s", + level: LogLevel.Error); + + longMutex = false; + rethrow; + } } /// Recovers wallet from [suppliedMnemonic]. Expects a valid mnemonic. @@ -4506,6 +4480,8 @@ class FiroWallet extends CoinServiceAPI String suppliedMnemonic, String mnemonicPassphrase, int maxUnusedAddressGap, + int maxNumberOfIndexesToCheck, + bool isRescan, ) async { longMutex = true; Logging.instance @@ -4513,16 +4489,26 @@ class FiroWallet extends CoinServiceAPI try { final latestSetId = await getLatestSetId(); final setDataMap = getSetDataMap(latestSetId); + final usedSerialNumbers = getUsedCoinSerials(); - final makeDerivations = _makeDerivations( - suppliedMnemonic, mnemonicPassphrase, maxUnusedAddressGap); + final generateAndCheckAddresses = _recoverHistory( + suppliedMnemonic, + mnemonicPassphrase, + maxUnusedAddressGap, + maxNumberOfIndexesToCheck, + isRescan, + ); await Future.wait([ updateCachedId(walletId), updateCachedIsFavorite(false), ]); - await Future.wait([usedSerialNumbers, setDataMap, makeDerivations]); + await Future.wait([ + usedSerialNumbers, + setDataMap, + generateAndCheckAddresses, + ]); await _restore(latestSetId, await setDataMap, await usedSerialNumbers); longMutex = false; @@ -4535,8 +4521,11 @@ class FiroWallet extends CoinServiceAPI } } - Future _restore(int latestSetId, Map setDataMap, - dynamic usedSerialNumbers) async { + Future _restore( + int latestSetId, + Map setDataMap, + List usedSerialNumbers, + ) async { final _mnemonic = await mnemonicString; final _mnemonicPassphrase = await mnemonicPassphrase; @@ -4707,7 +4696,7 @@ class FiroWallet extends CoinServiceAPI } } - Future> getUsedCoinSerials() async { + Future> getUsedCoinSerials() async { try { final response = await cachedElectrumXClient.getUsedCoinSerials( coin: coin, diff --git a/lib/services/coins/litecoin/litecoin_wallet.dart b/lib/services/coins/litecoin/litecoin_wallet.dart index 5e1e8064c..765f3d81a 100644 --- a/lib/services/coins/litecoin/litecoin_wallet.dart +++ b/lib/services/coins/litecoin/litecoin_wallet.dart @@ -1325,16 +1325,14 @@ class LitecoinWallet extends CoinServiceAPI )) .toList(); final newNode = await getCurrentNode(); - _cachedElectrumXClient = CachedElectrumX.from( - node: newNode, - prefs: _prefs, - failovers: failovers, - ); _electrumXClient = ElectrumX.from( node: newNode, prefs: _prefs, failovers: failovers, ); + _cachedElectrumXClient = CachedElectrumX.from( + electrumXClient: _electrumXClient, + ); if (shouldRefresh) { unawaited(refresh()); @@ -1498,7 +1496,7 @@ class LitecoinWallet extends CoinServiceAPI } await _secureStore.write( key: '${_walletId}_mnemonic', - value: bip39.generateMnemonic(strength: 256)); + value: bip39.generateMnemonic(strength: 128)); await _secureStore.write( key: '${_walletId}_mnemonicPassphrase', value: "", @@ -1793,6 +1791,18 @@ class LitecoinWallet extends CoinServiceAPI coin: coin, ); + bool shouldBlock = false; + String? blockReason; + String? label; + + final utxoAmount = jsonUTXO["value"] as int; + + if (utxoAmount <= 10000) { + shouldBlock = true; + blockReason = "May contain ordinal"; + label = "Possible ordinal"; + } + final vout = jsonUTXO["tx_pos"] as int; final outputs = txn["vout"] as List; @@ -1811,10 +1821,10 @@ class LitecoinWallet extends CoinServiceAPI walletId: walletId, txid: txn["txid"] as String, vout: vout, - value: jsonUTXO["value"] as int, - name: "", - isBlocked: false, - blockedReason: null, + value: utxoAmount, + name: label ?? "", + isBlocked: shouldBlock, + blockedReason: blockReason, isCoinbase: txn["is_coinbase"] as bool? ?? false, blockHash: txn["blockhash"] as String?, blockHeight: jsonUTXO["height"] as int?, @@ -1826,16 +1836,20 @@ class LitecoinWallet extends CoinServiceAPI } } - Logging.instance - .log('Outputs fetched: $outputArray', level: LogLevel.Info); + 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); + Logging.instance.log( + "Output fetch unsuccessful: $e\n$s", + level: LogLevel.Error, + ); } } diff --git a/lib/services/coins/namecoin/namecoin_wallet.dart b/lib/services/coins/namecoin/namecoin_wallet.dart index 19c5a823b..640b2cba0 100644 --- a/lib/services/coins/namecoin/namecoin_wallet.dart +++ b/lib/services/coins/namecoin/namecoin_wallet.dart @@ -1314,16 +1314,14 @@ class NamecoinWallet extends CoinServiceAPI )) .toList(); final newNode = await getCurrentNode(); - _cachedElectrumXClient = CachedElectrumX.from( - node: newNode, - prefs: _prefs, - failovers: failovers, - ); _electrumXClient = ElectrumX.from( node: newNode, prefs: _prefs, failovers: failovers, ); + _cachedElectrumXClient = CachedElectrumX.from( + electrumXClient: _electrumXClient, + ); if (shouldRefresh) { unawaited(refresh()); @@ -1479,7 +1477,7 @@ class NamecoinWallet extends CoinServiceAPI } await _secureStore.write( key: '${_walletId}_mnemonic', - value: bip39.generateMnemonic(strength: 256)); + value: bip39.generateMnemonic(strength: 128)); await _secureStore.write( key: '${_walletId}_mnemonicPassphrase', value: "", @@ -2803,19 +2801,18 @@ class NamecoinWallet extends CoinServiceAPI // Add transaction output for (var i = 0; i < recipients.length; i++) { - txb.addOutput(recipients[i], satoshiAmounts[i], namecoin.bech32!); + txb.addOutput(recipients[i], satoshiAmounts[i], _network.bech32!); } try { // Sign the transaction accordingly 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!, - _network.bech32!, + txb.sign( + vin: i, + keyPair: utxoSigningData[i].keyPair!, + witnessValue: utxoSigningData[i].utxo.value, + redeemScript: utxoSigningData[i].redeemScript, + overridePrefix: _network.bech32!, ); } } catch (e, s) { @@ -2824,7 +2821,7 @@ class NamecoinWallet extends CoinServiceAPI rethrow; } - final builtTx = txb.build(namecoin.bech32!); + final builtTx = txb.build(_network.bech32!); final vSize = builtTx.virtualSize(); return {"hex": builtTx.toHex(), "vSize": vSize}; diff --git a/lib/services/coins/particl/particl_wallet.dart b/lib/services/coins/particl/particl_wallet.dart index f16d8a5f2..34e5ede45 100644 --- a/lib/services/coins/particl/particl_wallet.dart +++ b/lib/services/coins/particl/particl_wallet.dart @@ -1242,16 +1242,14 @@ class ParticlWallet extends CoinServiceAPI )) .toList(); final newNode = await getCurrentNode(); - _cachedElectrumXClient = CachedElectrumX.from( - node: newNode, - prefs: _prefs, - failovers: failovers, - ); _electrumXClient = ElectrumX.from( node: newNode, prefs: _prefs, failovers: failovers, ); + _cachedElectrumXClient = CachedElectrumX.from( + electrumXClient: _electrumXClient, + ); if (shouldRefresh) { unawaited(refresh()); @@ -1394,7 +1392,7 @@ class ParticlWallet extends CoinServiceAPI } await _secureStore.write( key: '${_walletId}_mnemonic', - value: bip39.generateMnemonic(strength: 256)); + value: bip39.generateMnemonic(strength: 128)); await _secureStore.write( key: '${_walletId}_mnemonicPassphrase', value: "", diff --git a/lib/services/mixins/electrum_x_parsing.dart b/lib/services/mixins/electrum_x_parsing.dart index c313a91eb..92cf86305 100644 --- a/lib/services/mixins/electrum_x_parsing.dart +++ b/lib/services/mixins/electrum_x_parsing.dart @@ -148,19 +148,31 @@ mixin ElectrumXParsing { type = TransactionType.outgoing; amount = amountSentFromWallet - changeAmount - fee; - final possible = - outputAddresses.difference(myChangeReceivedOnAddresses).first; + // non wallet addresses found in tx outputs + final nonWalletOutAddresses = outputAddresses.difference( + myChangeReceivedOnAddresses, + ); - if (transactionAddress.value != possible) { - transactionAddress = Address( - walletId: walletId, - value: possible, - derivationIndex: -1, - derivationPath: null, - subType: AddressSubType.nonWallet, - type: AddressType.nonWallet, - publicKey: [], - ); + if (nonWalletOutAddresses.isNotEmpty) { + final possible = nonWalletOutAddresses.first; + + if (transactionAddress.value != possible) { + transactionAddress = Address( + walletId: walletId, + value: possible, + derivationIndex: -1, + derivationPath: null, + subType: AddressSubType.nonWallet, + type: AddressType.nonWallet, + publicKey: [], + ); + } + } else { + // some other type of tx where the receiving address is + // one of my change addresses + + type = TransactionType.sentToSelf; + amount = changeAmount; } } else { // incoming tx diff --git a/lib/services/mixins/firo_hive.dart b/lib/services/mixins/firo_hive.dart index 321724ad1..180c9f278 100644 --- a/lib/services/mixins/firo_hive.dart +++ b/lib/services/mixins/firo_hive.dart @@ -35,9 +35,10 @@ mixin FiroHive { } // mintIndex - int? firoGetMintIndex() { + int firoGetMintIndex() { return DB.instance.get(boxName: _walletId, key: "mintIndex") - as int?; + as int? ?? + 0; } Future firoUpdateMintIndex(int mintIndex) async { diff --git a/lib/services/price.dart b/lib/services/price.dart index 2a50f0cf1..16bd93c6a 100644 --- a/lib/services/price.dart +++ b/lib/services/price.dart @@ -89,7 +89,7 @@ class PriceAPI { final uri = Uri.parse("https://api.coingecko.com/api/v3/coins/markets?vs_currency" "=${baseCurrency.toLowerCase()}" - "&ids=monero,bitcoin,litecoin,epic-cash,zcoin,dogecoin," + "&ids=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin," "bitcoin-cash,namecoin,wownero,ethereum,particl" "&order=market_cap_desc&per_page=50&page=1&sparkline=false"); diff --git a/lib/themes/coin_icon_provider.dart b/lib/themes/coin_icon_provider.dart index 75b8a3e11..6c17969e6 100644 --- a/lib/themes/coin_icon_provider.dart +++ b/lib/themes/coin_icon_provider.dart @@ -1,36 +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.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.mainNetVersion]!; } }); diff --git a/lib/themes/coin_image_provider.dart b/lib/themes/coin_image_provider.dart index 60ef466a7..239e1d1cb 100644 --- a/lib/themes/coin_image_provider.dart +++ b/lib/themes/coin_image_provider.dart @@ -1,77 +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.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.mainNetVersion]!; } }); 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.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.mainNetVersion]!; } }); diff --git a/lib/themes/color_theme.dart b/lib/themes/color_theme.dart index fe261456c..b4f00adcf 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 eCash; case Coin.epicCash: return epicCash; case Coin.ethereum: diff --git a/lib/themes/stack_colors.dart b/lib/themes/stack_colors.dart index 8a326cb97..6717615b1 100644 --- a/lib/themes/stack_colors.dart +++ b/lib/themes/stack_colors.dart @@ -1682,6 +1682,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: 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/themes/theme_service.dart b/lib/themes/theme_service.dart index 2f3d5e6fb..e130daba8 100644 --- a/lib/themes/theme_service.dart +++ b/lib/themes/theme_service.dart @@ -1,9 +1,9 @@ import 'dart:convert'; import 'dart:io'; -import 'dart:typed_data'; import 'package:archive/archive_io.dart'; import 'package:crypto/crypto.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:http/http.dart'; import 'package:isar/isar.dart'; @@ -17,6 +17,7 @@ final pThemeService = Provider((ref) { }); class ThemeService { + static const _currentDefaultThemeVersion = 2; ThemeService._(); static ThemeService? _instance; static ThemeService get instance => _instance ??= ThemeService._(); @@ -92,6 +93,70 @@ class ThemeService { } } + Future checkDefaultThemesOnStartup() async { + // install default themes + if (!(await ThemeService.instance.verifyInstalled(themeId: "light"))) { + Logging.instance.log( + "Installing default light theme...", + level: LogLevel.Info, + ); + final lightZip = await rootBundle.load("assets/default_themes/light.zip"); + await ThemeService.instance + .install(themeArchiveData: lightZip.buffer.asUint8List()); + Logging.instance.log( + "Installing default light theme... finished", + level: LogLevel.Info, + ); + } else { + // check installed version + final theme = ThemeService.instance.getTheme(themeId: "light"); + if ((theme?.version ?? 1) < _currentDefaultThemeVersion) { + Logging.instance.log( + "Updating default light theme...", + level: LogLevel.Info, + ); + final lightZip = + await rootBundle.load("assets/default_themes/light.zip"); + await ThemeService.instance + .install(themeArchiveData: lightZip.buffer.asUint8List()); + Logging.instance.log( + "Updating default light theme... finished", + level: LogLevel.Info, + ); + } + } + + if (!(await ThemeService.instance.verifyInstalled(themeId: "dark"))) { + Logging.instance.log( + "Installing default dark theme... ", + level: LogLevel.Info, + ); + final darkZip = await rootBundle.load("assets/default_themes/dark.zip"); + await ThemeService.instance + .install(themeArchiveData: darkZip.buffer.asUint8List()); + Logging.instance.log( + "Installing default dark theme... finished", + level: LogLevel.Info, + ); + } else { + // check installed version + final theme = ThemeService.instance.getTheme(themeId: "dark"); + if ((theme?.version ?? 1) < _currentDefaultThemeVersion) { + Logging.instance.log( + "Updating default dark theme...", + level: LogLevel.Info, + ); + final darkZip = await rootBundle.load("assets/default_themes/dark.zip"); + await ThemeService.instance + .install(themeArchiveData: darkZip.buffer.asUint8List()); + Logging.instance.log( + "Updating default dark theme... finished", + level: LogLevel.Info, + ); + } + } + } + // TODO more thorough check/verification of theme Future verifyInstalled({required String themeId}) async { final dbHasTheme = @@ -175,6 +240,7 @@ class ThemeService { class StackThemeMetaData { final String name; final String id; + final int version; final String sha256; final String size; final String previewImageUrl; @@ -182,6 +248,7 @@ class StackThemeMetaData { StackThemeMetaData({ required this.name, required this.id, + required this.version, required this.sha256, required this.size, required this.previewImageUrl, @@ -192,6 +259,7 @@ class StackThemeMetaData { return StackThemeMetaData( name: map["name"] as String, id: map["id"] as String, + version: map["version"] as int? ?? 1, sha256: map["sha256"] as String, size: map["size"] as String, previewImageUrl: map["previewImageUrl"] as String, @@ -210,6 +278,7 @@ class StackThemeMetaData { return "$runtimeType(" "name: $name, " "id: $id, " + "version: $version, " "sha256: $sha256, " "size: $size, " "previewImageUrl: $previewImageUrl" 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 2a0909966..e9ea7ad58 100644 --- a/lib/utilities/assets.dart +++ b/lib/utilities/assets.dart @@ -165,6 +165,7 @@ class _SVG { String get questionMessage => "assets/svg/message-question.svg"; String get envelope => "assets/svg/envelope.svg"; String get share => "assets/svg/share-2.svg"; + String get recycle => "assets/svg/anonymize.svg"; String get anonymize => "assets/svg/tx-icon-anonymize.svg"; String get anonymizePending => "assets/svg/tx-icon-anonymize-pending.svg"; String get anonymizeFailed => "assets/svg/tx-icon-anonymize-failed.svg"; @@ -193,7 +194,7 @@ class _SVG { String get exchange3 => "assets/svg/exchange-3.svg"; String get messageQuestion => "assets/svg/message-question-1.svg"; String get list => "assets/svg/list-ul.svg"; - String get unclaimedPaynym => "assets/svg/unclaimed.png"; + String get unclaimedPaynym => "assets/svg/unclaimed.svg"; String get trocadorRatingA => "assets/svg/trocador_rating_a.svg"; String get trocadorRatingB => "assets/svg/trocador_rating_b.svg"; diff --git a/lib/utilities/block_explorers.dart b/lib/utilities/block_explorers.dart index a5619587e..cf0628fd2 100644 --- a/lib/utilities/block_explorers.dart +++ b/lib/utilities/block_explorers.dart @@ -17,6 +17,8 @@ Uri getDefaultBlockExplorerUrlFor({ return Uri.parse("https://mempool.space/testnet/tx/$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 9a82262d0..05a785b41 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,12 +34,11 @@ 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); - static const int pinLength = 4; - // Enable Logger.print statements static const bool disableLogger = false; @@ -71,6 +71,9 @@ abstract class Constants { case Coin.ethereum: return _satsPerCoinEthereum; + + case Coin.eCash: + return _satsPerCoinECash; } } @@ -99,6 +102,9 @@ abstract class Constants { case Coin.ethereum: return _decimalPlacesEthereum; + + case Coin.eCash: + return _decimalPlacesECash; } } @@ -115,6 +121,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 +144,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: @@ -175,7 +181,7 @@ abstract class Constants { } } - static const int seedPhraseWordCountBip39 = 24; + static const int seedPhraseWordCountBip39 = 12; static const int seedPhraseWordCountMonero = 25; static const Map monthMapShort = { 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..2f06c7ba9 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 "eCash"; 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; @@ -404,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": diff --git a/lib/utilities/enums/derive_path_type_enum.dart b/lib/utilities/enums/derive_path_type_enum.dart index 8aa519727..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 { @@ -27,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; diff --git a/lib/utilities/util.dart b/lib/utilities/util.dart index 6a31fae04..ecd9a7cca 100644 --- a/lib/utilities/util.dart +++ b/lib/utilities/util.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; +import 'dart:developer'; import 'dart:io'; import 'package:device_info_plus/device_info_plus.dart'; @@ -50,4 +52,15 @@ abstract class Util { } return MaterialColor(color.value, swatch); } + + static void printJson(dynamic json) { + if (json is Map || json is List) { + final spaces = ' ' * 4; + final encoder = JsonEncoder.withIndent(spaces); + final pretty = encoder.convert(json); + log(pretty); + } else { + log(dynamic.toString()); + } + } } diff --git a/lib/widgets/custom_pin_put/custom_pin_put.dart b/lib/widgets/custom_pin_put/custom_pin_put.dart index 4a4e4a4e4..a3dc6b2e6 100644 --- a/lib/widgets/custom_pin_put/custom_pin_put.dart +++ b/lib/widgets/custom_pin_put/custom_pin_put.dart @@ -53,8 +53,10 @@ class CustomPinPut extends StatefulWidget { this.mainAxisSize = MainAxisSize.max, this.autofillHints, this.customKey, - }) : assert(fieldsCount > 0), - super(key: key); + this.onPinLengthChanged, + }) : super(key: key); + + final void Function(int)? onPinLengthChanged; final double? width; final double? height; diff --git a/lib/widgets/custom_pin_put/custom_pin_put_state.dart b/lib/widgets/custom_pin_put/custom_pin_put_state.dart index 355656638..833abf969 100644 --- a/lib/widgets/custom_pin_put/custom_pin_put_state.dart +++ b/lib/widgets/custom_pin_put/custom_pin_put_state.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:flutter/material.dart'; import 'package:stackwallet/widgets/custom_pin_put/custom_pin_put.dart'; import 'package:stackwallet/widgets/custom_pin_put/pin_keyboard.dart'; @@ -10,6 +12,13 @@ class CustomPinPutState extends State int get selectedIndex => _controller.value.text.length; + int _pinCount = 0; + int get pinCount => _pinCount; + set pinCount(int newCount) { + _pinCount = newCount; + widget.onPinLengthChanged?.call(newCount); + } + @override void initState() { _controller = widget.controller ?? TextEditingController(); @@ -50,22 +59,19 @@ class CustomPinPutState extends State @override Widget build(BuildContext context) { - // final bool randomize = ref - // .read(prefsChangeNotifierProvider) - // .randomizePIN; return SizedBox( width: widget.width, height: widget.height, child: Column( children: [ SizedBox( - width: (30 * widget.fieldsCount) - 18, + width: max((30 * pinCount) - 18, 1), child: Stack( children: [ _hiddenTextField, Align( alignment: Alignment.bottomCenter, - child: _fields, + child: _fields(pinCount), ), ], ), @@ -75,15 +81,22 @@ class CustomPinPutState extends State isRandom: widget.isRandom, customKey: widget.customKey, onNumberKeyPressed: (number) { - if (_controller.text.length < widget.fieldsCount) { - _controller.text += number; - } + _controller.text += number; + + // add a set state and have the counter increment + setState(() { + pinCount = _controller.text.length; + }); }, onBackPressed: () { final text = _controller.text; if (text.isNotEmpty) { _controller.text = text.substring(0, text.length - 1); + setState(() { + pinCount = _controller.text.length; + }); } + // decrement counter here }, onSubmitPressed: () { final pin = _controller.value.text; @@ -117,7 +130,7 @@ class CustomPinPutState extends State textCapitalization: widget.textCapitalization, inputFormatters: widget.inputFormatters, enableInteractiveSelection: false, - maxLength: widget.fieldsCount, + maxLength: 10, showCursor: false, scrollPadding: EdgeInsets.zero, decoration: widget.inputDecoration, @@ -127,21 +140,22 @@ class CustomPinPutState extends State ); } - Widget get _fields { + // have it include an int as a param + Widget _fields(int count) { return ValueListenableBuilder( valueListenable: _textControllerValue, builder: (BuildContext context, value, Widget? child) { return Row( mainAxisSize: widget.mainAxisSize, mainAxisAlignment: widget.fieldsAlignment, - children: _buildFieldsWithSeparator(), + children: _buildFieldsWithSeparator(count), ); }, ); } - List _buildFieldsWithSeparator() { - final fields = Iterable.generate(widget.fieldsCount).map((index) { + List _buildFieldsWithSeparator(int count) { + final fields = Iterable.generate(count).map((index) { return _getField(index); }).toList(); diff --git a/lib/widgets/managed_favorite.dart b/lib/widgets/managed_favorite.dart index 6383c8501..cd03e7f59 100644 --- a/lib/widgets/managed_favorite.dart +++ b/lib/widgets/managed_favorite.dart @@ -4,8 +4,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; import 'package:stackwallet/themes/coin_icon_provider.dart'; import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; @@ -34,6 +36,28 @@ class _ManagedFavoriteCardState extends ConsumerState { final isDesktop = Util.isDesktop; + final balance = ref.watch( + walletsChangeNotifierProvider.select( + (value) => value.getManager(widget.walletId).balance, + ), + ); + + Amount total = balance.total; + if (manager.coin == Coin.firo || manager.coin == Coin.firoTestNet) { + final balancePrivate = ref.watch( + walletsChangeNotifierProvider.select( + (value) => (value + .getManager( + widget.walletId, + ) + .wallet as FiroWallet) + .balancePrivate, + ), + ); + + total += balancePrivate.total; + } + return RoundedWhiteContainer( padding: EdgeInsets.all(isDesktop ? 0 : 4.0), child: RawMaterialButton( @@ -107,7 +131,7 @@ class _ManagedFavoriteCardState extends ConsumerState { ), Expanded( child: Text( - "${manager.balance.total.localizedStringAsFixed( + "${total.localizedStringAsFixed( locale: ref.watch( localeServiceChangeNotifierProvider.select( (value) => value.locale, @@ -150,7 +174,7 @@ class _ManagedFavoriteCardState extends ConsumerState { height: 2, ), Text( - "${manager.balance.total.localizedStringAsFixed( + "${total.localizedStringAsFixed( locale: ref.watch( localeServiceChangeNotifierProvider.select( (value) => value.locale, 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/pubspec.lock b/pubspec.lock index 484245463..a0bd23cb2 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 b189b6fa0..e18e1ad2a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,7 +11,7 @@ description: Stack Wallet # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.7.8+171 +version: 1.7.11+175 environment: sdk: ">=2.17.0 <3.0.0" @@ -88,8 +88,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: @@ -291,6 +291,7 @@ flutter: - assets/svg/message-question.svg - assets/svg/envelope.svg - assets/svg/share-2.svg + - assets/svg/anonymize.svg - assets/svg/tx-icon-anonymize.svg - assets/svg/tx-icon-anonymize-pending.svg - assets/svg/tx-icon-anonymize-failed.svg diff --git a/test/cached_electrumx_test.dart b/test/cached_electrumx_test.dart index 329de9daf..f1c3a0c41 100644 --- a/test/cached_electrumx_test.dart +++ b/test/cached_electrumx_test.dart @@ -126,12 +126,8 @@ void main() { ).thenThrow(Exception()); final cachedClient = CachedElectrumX( - electrumXClient: client, - port: 0, - failovers: [], - server: '', - useSSL: true, - prefs: Prefs.instance); + electrumXClient: client, + ); expect( () async => await cachedClient.getTransaction( @@ -143,12 +139,8 @@ void main() { test("clearSharedTransactionCache", () async { final cachedClient = CachedElectrumX( - server: '', - electrumXClient: MockElectrumX(), - port: 0, - useSSL: true, - prefs: MockPrefs(), - failovers: []); + electrumXClient: MockElectrumX(), + ); bool didThrow = false; try { @@ -174,11 +166,7 @@ void main() { useSSL: true, ); - final client = CachedElectrumX.from( - node: node, - prefs: MockPrefs(), - failovers: [], - electrumXClient: MockElectrumX()); + final client = CachedElectrumX.from(electrumXClient: MockElectrumX()); expect(client, isA()); }); diff --git a/test/price_test.dart b/test/price_test.dart index b93255976..cc7427489 100644 --- a/test/price_test.dart +++ b/test/price_test.dart @@ -27,13 +27,69 @@ void main() { when(client.get( Uri.parse( "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids" - "=monero,bitcoin,litecoin,epic-cash,zcoin,dogecoin,bitcoin-cash" + "=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin,bitcoin-cash" ",namecoin,wownero,ethereum,particl&order=market_cap_desc&per_page=50" "&page=1&sparkline=false"), headers: { 'Content-Type': 'application/json' })).thenAnswer((_) async => Response( - '[{"id":"bitcoin","symbol":"btc","name":"Bitcoin","image":"https://assets.coingecko.com/coins/images/1/large/bitcoin.png?1547033579","current_price":1.0,"market_cap":19128800,"market_cap_rank":1,"fully_diluted_valuation":21000000,"total_volume":1272132,"high_24h":1.0,"low_24h":1.0,"price_change_24h":0.0,"price_change_percentage_24h":0.0,"market_cap_change_24h":950.0,"market_cap_change_percentage_24h":0.00497,"circulating_supply":19128800.0,"total_supply":21000000.0,"max_supply":21000000.0,"ath":1.003301,"ath_change_percentage":-0.32896,"ath_date":"2019-10-15T16:00:56.136Z","atl":0.99895134,"atl_change_percentage":0.10498,"atl_date":"2019-10-21T00:00:00.000Z","roi":null,"last_updated":"2022-08-22T16:37:59.237Z"},{"id":"dogecoin","symbol":"doge","name":"Dogecoin","image":"https://assets.coingecko.com/coins/images/5/large/dogecoin.png?1547792256","current_price":3.15e-06,"market_cap":417916,"market_cap_rank":10,"fully_diluted_valuation":null,"total_volume":27498,"high_24h":3.26e-06,"low_24h":3.13e-06,"price_change_24h":-8.6889947714e-08,"price_change_percentage_24h":-2.68533,"market_cap_change_24h":-11370.894861206936,"market_cap_change_percentage_24h":-2.64879,"circulating_supply":132670764299.894,"total_supply":null,"max_supply":null,"ath":1.264e-05,"ath_change_percentage":-75.05046,"ath_date":"2021-05-07T23:04:53.026Z","atl":1.50936e-07,"atl_change_percentage":1989.69346,"atl_date":"2020-12-17T09:18:05.654Z","roi":null,"last_updated":"2022-08-22T16:38:15.113Z"},{"id":"monero","symbol":"xmr","name":"Monero","image":"https://assets.coingecko.com/coins/images/69/large/monero_logo.png?1547033729","current_price":0.00717236,"market_cap":130002,"market_cap_rank":29,"fully_diluted_valuation":null,"total_volume":4901,"high_24h":0.00731999,"low_24h":0.00707511,"price_change_24h":-5.6133543212467e-05,"price_change_percentage_24h":-0.77656,"market_cap_change_24h":-1007.8447677436197,"market_cap_change_percentage_24h":-0.76929,"circulating_supply":18147820.3764146,"total_supply":null,"max_supply":null,"ath":0.03475393,"ath_change_percentage":-79.32037,"ath_date":"2018-01-09T00:00:00.000Z","atl":0.00101492,"atl_change_percentage":608.13327,"atl_date":"2014-12-18T00:00:00.000Z","roi":null,"last_updated":"2022-08-22T16:38:26.347Z"},{"id":"zcoin","symbol":"firo","name":"Firo","image":"https://assets.coingecko.com/coins/images/479/large/firocoingecko.png?1636537544","current_price":0.0001096,"market_cap":1252,"market_cap_rank":604,"fully_diluted_valuation":2349,"total_volume":90.573,"high_24h":0.00011148,"low_24h":0.00010834,"price_change_24h":-9.87561775002e-07,"price_change_percentage_24h":-0.89304,"market_cap_change_24h":-10.046635178462793,"market_cap_change_percentage_24h":-0.79578,"circulating_supply":11411043.8354697,"total_supply":21400000.0,"max_supply":21400000.0,"ath":0.01616272,"ath_change_percentage":-99.3208,"ath_date":"2018-04-04T16:04:48.408Z","atl":4.268e-05,"atl_change_percentage":157.22799,"atl_date":"2022-05-12T07:28:47.088Z","roi":null,"last_updated":"2022-08-22T16:38:47.229Z"},{"id":"epic-cash","symbol":"epic","name":"Epic Cash","image":"https://assets.coingecko.com/coins/images/9520/large/Epic_Coin_NO_drop_shadow.png?1620122642","current_price":2.803e-05,"market_cap":415.109,"market_cap_rank":953,"fully_diluted_valuation":null,"total_volume":0.2371557,"high_24h":3.053e-05,"low_24h":2.581e-05,"price_change_24h":1.9e-06,"price_change_percentage_24h":7.27524,"market_cap_change_24h":28.26753,"market_cap_change_percentage_24h":7.30726,"circulating_supply":14808052.0,"total_supply":21000000.0,"max_supply":null,"ath":0.00013848,"ath_change_percentage":-79.75864,"ath_date":"2021-12-11T08:39:41.129Z","atl":5.74028e-07,"atl_change_percentage":4783.08078,"atl_date":"2020-03-13T16:55:01.177Z","roi":null,"last_updated":"2022-08-22T16:38:32.826Z"}]', + '[{"id":"bitcoin","symbol":"btc","name":"Bitcoin","image":"https://asse' + 'ts.coingecko.com/coins/images/1/large/bitcoin.png?1547033579","curr' + 'ent_price":1.0,"market_cap":19128800,"market_cap_rank":1,"fully_dil' + 'uted_valuation":21000000,"total_volume":1272132,"high_24h":1.0,"low' + '_24h":1.0,"price_change_24h":0.0,"price_change_percentage_24h":0.0,' + '"market_cap_change_24h":950.0,"market_cap_change_percentage_24h":0.0' + '0497,"circulating_supply":19128800.0,"total_supply":21000000.0,"max' + '_supply":21000000.0,"ath":1.003301,"ath_change_percentage":-0.32896' + ',"ath_date":"2019-10-15T16:00:56.136Z","atl":0.99895134,"atl_change_' + 'percentage":0.10498,"atl_date":"2019-10-21T00:00:00.000Z","roi":nul' + 'l,"last_updated":"2022-08-22T16:37:59.237Z"},{"id":"dogecoin","symb' + 'ol":"doge","name":"Dogecoin","image":"https://assets.coingecko.com/' + 'coins/images/5/large/dogecoin.png?1547792256","current_price":3.15e' + '-06,"market_cap":417916,"market_cap_rank":10,"fully_diluted_valuati' + 'on":null,"total_volume":27498,"high_24h":3.26e-06,"low_24h":3.13e-0' + '6,"price_change_24h":-8.6889947714e-08,"price_change_percentage_24h' + '":-2.68533,"market_cap_change_24h":-11370.894861206936,"market_cap_c' + 'hange_percentage_24h":-2.64879,"circulating_supply":132670764299.89' + '4,"total_supply":null,"max_supply":null,"ath":1.264e-05,"ath_change' + '_percentage":-75.05046,"ath_date":"2021-05-07T23:04:53.026Z","atl":' + '1.50936e-07,"atl_change_percentage":1989.69346,"atl_date":"2020-12-' + '17T09:18:05.654Z","roi":null,"last_updated":"2022-08-22T16:38:15.11' + '3Z"},{"id":"monero","symbol":"xmr","name":"Monero","image":"https:/' + '/assets.coingecko.com/coins/images/69/large/monero_logo.png?1547033' + '729","current_price":0.00717236,"market_cap":130002,"market_cap_ran' + 'k":29,"fully_diluted_valuation":null,"total_volume":4901,"high_24h":' + '0.00731999,"low_24h":0.00707511,"price_change_24h":-5.6133543212467' + 'e-05,"price_change_percentage_24h":-0.77656,"market_cap_change_24h"' + ':-1007.8447677436197,"market_cap_change_percentage_24h":-0.76929,"c' + 'irculating_supply":18147820.3764146,"total_supply":null,"max_supply' + '":null,"ath":0.03475393,"ath_change_percentage":-79.32037,"ath_date' + '":"2018-01-09T00:00:00.000Z","atl":0.00101492,"atl_change_percentag' + 'e":608.13327,"atl_date":"2014-12-18T00:00:00.000Z","roi":null,"las' + 't_updated":"2022-08-22T16:38:26.347Z"},{"id":"zcoin","symbol":"firo' + '","name":"Firo","image":"https://assets.coingecko.com/coins/images/' + '479/large/firocoingecko.png?1636537544","current_price":0.0001096,"' + 'market_cap":1252,"market_cap_rank":604,"fully_diluted_valuation":234' + '9,"total_volume":90.573,"high_24h":0.00011148,"low_24h":0.00010834,' + '"price_change_24h":-9.87561775002e-07,"price_change_percentage_24h' + '":-0.89304,"market_cap_change_24h":-10.046635178462793,"market_cap_' + 'change_percentage_24h":-0.79578,"circulating_supply":11411043.83546' + '97,"total_supply":21400000.0,"max_supply":21400000.0,"ath":0.016162' + '72,"ath_change_percentage":-99.3208,"ath_date":"2018-04-04T16:04:48.' + '408Z","atl":4.268e-05,"atl_change_percentage":157.22799,"atl_date":"' + '2022-05-12T07:28:47.088Z","roi":null,"last_updated":"2022-08-22T16' + ':38:47.229Z"},{"id":"epic-cash","symbol":"epic","name":"Epic Cash",' + '"image":"https://assets.coingecko.com/coins/images/9520/large/Epic_C' + 'oin_NO_drop_shadow.png?1620122642","current_price":2.803e-05,"marke' + 't_cap":415.109,"market_cap_rank":953,"fully_diluted_valuation":null' + ',"total_volume":0.2371557,"high_24h":3.053e-05,"low_24h":2.581e-05' + ',"price_change_24h":1.9e-06,"price_change_percentage_24h":7.27524,"' + 'market_cap_change_24h":28.26753,"market_cap_change_percentage_24h":' + '7.30726,"circulating_supply":14808052.0,"total_supply":21000000.0,"' + 'max_supply":null,"ath":0.00013848,"ath_change_percentage":-79.75864' + ',"ath_date":"2021-12-11T08:39:41.129Z","atl":5.74028e-07,"atl_chang' + 'e_percentage":4783.08078,"atl_date":"2020-03-13T16:55:01.177Z","roi' + '":null,"last_updated":"2022-08-22T16:38:32.826Z"}]', 200)); final priceAPI = PriceAPI(client); @@ -45,7 +101,7 @@ void main() { price.toString(), '{Coin.bitcoin: [1, 0.0], Coin.bitcoincash: [0, 0.0], ' 'Coin.dogecoin: [0.00000315, -2.68533], ' - 'Coin.epicCash: [0.00002803, 7.27524], ' + 'Coin.eCash: [0, 0.0], Coin.epicCash: [0.00002803, 7.27524], ' 'Coin.ethereum: [0, 0.0], ' 'Coin.firo: [0.0001096, -0.89304], Coin.litecoin: [0, 0.0], ' 'Coin.monero: [0.00717236, -0.77656], Coin.namecoin: [0, 0.0], ' @@ -57,7 +113,7 @@ void main() { verify(client.get( Uri.parse( "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc" - "&ids=monero,bitcoin,litecoin,epic-cash,zcoin,dogecoin," + "&ids=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin," "bitcoin-cash,namecoin,wownero,ethereum,particl" "&order=market_cap_desc&per_page=50&page=1&sparkline=false", ), @@ -72,13 +128,69 @@ void main() { when(client.get( Uri.parse( "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&" - "ids=monero,bitcoin,litecoin,epic-cash,zcoin,dogecoin," + "ids=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin," "bitcoin-cash,namecoin,wownero,ethereum,particl" "&order=market_cap_desc&per_page=50&page=1&sparkline=false"), headers: { 'Content-Type': 'application/json' })).thenAnswer((_) async => Response( - '[{"id":"bitcoin","symbol":"btc","name":"Bitcoin","image":"https://assets.coingecko.com/coins/images/1/large/bitcoin.png?1547033579","current_price":1.0,"market_cap":19128800,"market_cap_rank":1,"fully_diluted_valuation":21000000,"total_volume":1272132,"high_24h":1.0,"low_24h":1.0,"price_change_24h":0.0,"price_change_percentage_24h":0.0,"market_cap_change_24h":950.0,"market_cap_change_percentage_24h":0.00497,"circulating_supply":19128800.0,"total_supply":21000000.0,"max_supply":21000000.0,"ath":1.003301,"ath_change_percentage":-0.32896,"ath_date":"2019-10-15T16:00:56.136Z","atl":0.99895134,"atl_change_percentage":0.10498,"atl_date":"2019-10-21T00:00:00.000Z","roi":null,"last_updated":"2022-08-22T16:37:59.237Z"},{"id":"dogecoin","symbol":"doge","name":"Dogecoin","image":"https://assets.coingecko.com/coins/images/5/large/dogecoin.png?1547792256","current_price":3.15e-06,"market_cap":417916,"market_cap_rank":10,"fully_diluted_valuation":null,"total_volume":27498,"high_24h":3.26e-06,"low_24h":3.13e-06,"price_change_24h":-8.6889947714e-08,"price_change_percentage_24h":-2.68533,"market_cap_change_24h":-11370.894861206936,"market_cap_change_percentage_24h":-2.64879,"circulating_supply":132670764299.894,"total_supply":null,"max_supply":null,"ath":1.264e-05,"ath_change_percentage":-75.05046,"ath_date":"2021-05-07T23:04:53.026Z","atl":1.50936e-07,"atl_change_percentage":1989.69346,"atl_date":"2020-12-17T09:18:05.654Z","roi":null,"last_updated":"2022-08-22T16:38:15.113Z"},{"id":"monero","symbol":"xmr","name":"Monero","image":"https://assets.coingecko.com/coins/images/69/large/monero_logo.png?1547033729","current_price":0.00717236,"market_cap":130002,"market_cap_rank":29,"fully_diluted_valuation":null,"total_volume":4901,"high_24h":0.00731999,"low_24h":0.00707511,"price_change_24h":-5.6133543212467e-05,"price_change_percentage_24h":-0.77656,"market_cap_change_24h":-1007.8447677436197,"market_cap_change_percentage_24h":-0.76929,"circulating_supply":18147820.3764146,"total_supply":null,"max_supply":null,"ath":0.03475393,"ath_change_percentage":-79.32037,"ath_date":"2018-01-09T00:00:00.000Z","atl":0.00101492,"atl_change_percentage":608.13327,"atl_date":"2014-12-18T00:00:00.000Z","roi":null,"last_updated":"2022-08-22T16:38:26.347Z"},{"id":"zcoin","symbol":"firo","name":"Firo","image":"https://assets.coingecko.com/coins/images/479/large/firocoingecko.png?1636537544","current_price":0.0001096,"market_cap":1252,"market_cap_rank":604,"fully_diluted_valuation":2349,"total_volume":90.573,"high_24h":0.00011148,"low_24h":0.00010834,"price_change_24h":-9.87561775002e-07,"price_change_percentage_24h":-0.89304,"market_cap_change_24h":-10.046635178462793,"market_cap_change_percentage_24h":-0.79578,"circulating_supply":11411043.8354697,"total_supply":21400000.0,"max_supply":21400000.0,"ath":0.01616272,"ath_change_percentage":-99.3208,"ath_date":"2018-04-04T16:04:48.408Z","atl":4.268e-05,"atl_change_percentage":157.22799,"atl_date":"2022-05-12T07:28:47.088Z","roi":null,"last_updated":"2022-08-22T16:38:47.229Z"},{"id":"epic-cash","symbol":"epic","name":"Epic Cash","image":"https://assets.coingecko.com/coins/images/9520/large/Epic_Coin_NO_drop_shadow.png?1620122642","current_price":2.803e-05,"market_cap":415.109,"market_cap_rank":953,"fully_diluted_valuation":null,"total_volume":0.2371557,"high_24h":3.053e-05,"low_24h":2.581e-05,"price_change_24h":1.9e-06,"price_change_percentage_24h":7.27524,"market_cap_change_24h":28.26753,"market_cap_change_percentage_24h":7.30726,"circulating_supply":14808052.0,"total_supply":21000000.0,"max_supply":null,"ath":0.00013848,"ath_change_percentage":-79.75864,"ath_date":"2021-12-11T08:39:41.129Z","atl":5.74028e-07,"atl_change_percentage":4783.08078,"atl_date":"2020-03-13T16:55:01.177Z","roi":null,"last_updated":"2022-08-22T16:38:32.826Z"}]', + '[{"id":"bitcoin","symbol":"btc","name":"Bitcoin","image":"https://a' + 'ssets.coingecko.com/coins/images/1/large/bitcoin.png?1547033579","c' + 'urrent_price":1.0,"market_cap":19128800,"market_cap_rank":1,"fully_' + 'diluted_valuation":21000000,"total_volume":1272132,"high_24h":1.0,"' + 'low_24h":1.0,"price_change_24h":0.0,"price_change_percentage_24h":0' + '.0,"market_cap_change_24h":950.0,"market_cap_change_percentage_24h"' + ':0.00497,"circulating_supply":19128800.0,"total_supply":21000000.0,"' + 'max_supply":21000000.0,"ath":1.003301,"ath_change_percentage":-0.32' + '896,"ath_date":"2019-10-15T16:00:56.136Z","atl":0.99895134,"atl_cha' + 'nge_percentage":0.10498,"atl_date":"2019-10-21T00:00:00.000Z","roi"' + ':null,"last_updated":"2022-08-22T16:37:59.237Z"},{"id":"dogecoin","' + 'symbol":"doge","name":"Dogecoin","image":"https://assets.coingecko.' + 'com/coins/images/5/large/dogecoin.png?1547792256","current_price":3' + '.15e-06,"market_cap":417916,"market_cap_rank":10,"fully_diluted_val' + 'uation":null,"total_volume":27498,"high_24h":3.26e-06,"low_24h":3.1' + '3e-06,"price_change_24h":-8.6889947714e-08,"price_change_percentage' + '_24h":-2.68533,"market_cap_change_24h":-11370.894861206936,"market_' + 'cap_change_percentage_24h":-2.64879,"circulating_supply":1326707642' + '99.894,"total_supply":null,"max_supply":null,"ath":1.264e-05,"ath_c' + 'hange_percentage":-75.05046,"ath_date":"2021-05-07T23:04:53.026Z","' + 'atl":1.50936e-07,"atl_change_percentage":1989.69346,"atl_date":"202' + '0-12-17T09:18:05.654Z","roi":null,"last_updated":"2022-08-22T16:38:' + '15.113Z"},{"id":"monero","symbol":"xmr","name":"Monero","image":"ht' + 'tps://assets.coingecko.com/coins/images/69/large/monero_logo.png?15' + '47033729","current_price":0.00717236,"market_cap":130002,"market_cap' + '_rank":29,"fully_diluted_valuation":null,"total_volume":4901,"high' + '_24h":0.00731999,"low_24h":0.00707511,"price_change_24h":-5.613354' + '3212467e-05,"price_change_percentage_24h":-0.77656,"market_cap_chan' + 'ge_24h":-1007.8447677436197,"market_cap_change_percentage_24h":-0.7' + '6929,"circulating_supply":18147820.3764146,"total_supply":null,"ma' + 'x_supply":null,"ath":0.03475393,"ath_change_percentage":-79.32037,"' + 'ath_date":"2018-01-09T00:00:00.000Z","atl":0.00101492,"atl_change_' + 'percentage":608.13327,"atl_date":"2014-12-18T00:00:00.000Z","roi":n' + 'ull,"last_updated":"2022-08-22T16:38:26.347Z"},{"id":"zcoin","symbo' + 'l":"firo","name":"Firo","image":"https://assets.coingecko.com/coins' + '/images/479/large/firocoingecko.png?1636537544","current_price":0.0' + '001096,"market_cap":1252,"market_cap_rank":604,"fully_diluted_valu' + 'ation":2349,"total_volume":90.573,"high_24h":0.00011148,"low_24h":0' + '.00010834,"price_change_24h":-9.87561775002e-07,"price_change_perce' + 'ntage_24h":-0.89304,"market_cap_change_24h":-10.046635178462793,"ma' + 'rket_cap_change_percentage_24h":-0.79578,"circulating_supply":11411' + '043.8354697,"total_supply":21400000.0,"max_supply":21400000.0,"ath"' + ':0.01616272,"ath_change_percentage":-99.3208,"ath_date":"2018-04-04' + 'T16:04:48.408Z","atl":4.268e-05,"atl_change_percentage":157.22799,' + '"atl_date":"2022-05-12T07:28:47.088Z","roi":null,"last_updated":"2' + '022-08-22T16:38:47.229Z"},{"id":"epic-cash","symbol":"epic","name":"' + 'Epic Cash","image":"https://assets.coingecko.com/coins/images/9520/' + 'large/Epic_Coin_NO_drop_shadow.png?1620122642","current_price":2.80' + '3e-05,"market_cap":415.109,"market_cap_rank":953,"fully_diluted_val' + 'uation":null,"total_volume":0.2371557,"high_24h":3.053e-05,"low_24h' + '":2.581e-05,"price_change_24h":1.9e-06,"price_change_percentage_24' + 'h":7.27524,"market_cap_change_24h":28.26753,"market_cap_change_perc' + 'entage_24h":7.30726,"circulating_supply":14808052.0,"total_supply":' + '21000000.0,"max_supply":null,"ath":0.00013848,"ath_change_percentag' + 'e":-79.75864,"ath_date":"2021-12-11T08:39:41.129Z","atl":5.74028e-0' + '7,"atl_change_percentage":4783.08078,"atl_date":"2020-03-13T16:55:01' + '.177Z","roi":null,"last_updated":"2022-08-22T16:38:32.826Z"}]', 200)); final priceAPI = PriceAPI(client); @@ -94,7 +206,7 @@ void main() { expect( cachedPrice.toString(), '{Coin.bitcoin: [1, 0.0], Coin.bitcoincash: [0, 0.0],' - ' Coin.dogecoin: [0.00000315, -2.68533], Coin.epicCash: [0.00002803, 7.27524],' + ' Coin.dogecoin: [0.00000315, -2.68533], Coin.eCash: [0, 0.0], Coin.epicCash: [0.00002803, 7.27524],' ' Coin.ethereum: [0, 0.0], Coin.firo: [0.0001096, -0.89304], ' 'Coin.litecoin: [0, 0.0], Coin.monero: [0.00717236, -0.77656], ' 'Coin.namecoin: [0, 0.0], Coin.particl: [0, 0.0], Coin.wownero: [0, 0.0], ' @@ -105,7 +217,10 @@ void main() { // verify only called once during filling of cache verify(client.get( Uri.parse( - "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids=monero,bitcoin,litecoin,epic-cash,zcoin,dogecoin,bitcoin-cash,namecoin,wownero,ethereum,particl&order=market_cap_desc&per_page=50&page=1&sparkline=false"), + "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids" + "=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin," + "bitcoin-cash,namecoin,wownero,ethereum,particl" + "&order=market_cap_desc&per_page=50&page=1&sparkline=false"), headers: {'Content-Type': 'application/json'})).called(1); verifyNoMoreInteractions(client); @@ -117,13 +232,70 @@ void main() { when(client.get( Uri.parse( "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc" - "&ids=monero,bitcoin,litecoin,epic-cash,zcoin,dogecoin," + "&ids=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin," "bitcoin-cash,namecoin,wownero,ethereum,particl" "&order=market_cap_desc&per_page=50&page=1&sparkline=false"), headers: { 'Content-Type': 'application/json' })).thenAnswer((_) async => Response( - '[{"id":"bitcoin","symbol":"btc","name":com/coins/images/1/large/bitcoin.png?1547033579","current_price":1.0,"market_cap":19128800,"market_cap_rank":1,"fully_diluted_valuation":21000000,"total_volume":1272132,"high_24h":1.0,"low_24h":1.0,"price_change_24h":0.0,"price_change_percentage_24h":0.0,"market_cap_change_24h":950.0,"market_cap_change_percentage_24h":0.00497,"circulating_supply":19128800.0,"total_supply":21000000.0,"max_supply":21000000.0,"ath":1.003301,"ath_change_percentage":-0.32896,"ath_date":"2019-10-15T16:00:56.136Z","atl":0.99895134,"atl_change_percentage":0.10498,"atl_date":"2019-10-21T00:00:00.000Z","roi":null,"last_updated":"2022-08-22T16:37:59.237Z"},{"id":"dogecoin","symbol":"doge","name":"Dogecoin","image":"https://assets.coingecko.com/coins/images/5/large/dogecoin.png?1547792256","current_price":3.15e-06,"market_cap":417916,"market_cap_rank":10,"fully_diluted_valuation":null,"total_volume":27498,"high_24h":3.26e-06,"low_24h":3.13e-06,"price_change_24h":-8.6889947714e-08,"price_change_percentage_24h":-2.68533,"market_cap_change_24h":-11370.894861206936,"market_cap_change_percentage_24h":-2.64879,"circulating_supply":132670764299.894,"total_supply":null,"max_supply":null,"ath":1.264e-05,"ath_change_percentage":-75.05046,"ath_date":"2021-05-07T23:04:53.026Z","atl":1.50936e-07,"atl_change_percentage":1989.69346,"atl_date":"2020-12-17T09:18:05.654Z","roi":null,"last_updated":"2022-08-22T16:38:15.113Z"},{"id":"monero","symbol":"xmr","name":"Monero","image":"https://assets.coingecko.com/coins/images/69/large/monero_logo.png?1547033729","current_price":0.00717236,"market_cap":130002,"market_cap_rank":29,"fully_diluted_valuation":null,"total_volume":4901,"high_24h":0.00731999,"low_24h":0.00707511,"price_change_24h":-5.6133543212467e-05,"price_change_percentage_24h":-0.77656,"market_cap_change_24h":-1007.8447677436197,"market_cap_change_percentage_24h":-0.76929,"circulating_supply":18147820.3764146,"total_supply":null,"max_supply":null,"ath":0.03475393,"ath_change_percentage":-79.32037,"ath_date":"2018-01-09T00:00:00.000Z","atl":0.00101492,"atl_change_percentage":608.13327,"atl_date":"2014-12-18T00:00:00.000Z","roi":null,"last_updated":"2022-08-22T16:38:26.347Z"},{"id":"zcoin","symbol":"firo","name":"Firo","image":"https://assets.coingecko.com/coins/images/479/large/firocoingecko.png?1636537544","current_price":0.0001096,"market_cap":1252,"market_cap_rank":604,"fully_diluted_valuation":2349,"total_volume":90.573,"high_24h":0.00011148,"low_24h":0.00010834,"price_change_24h":-9.87561775002e-07,"price_change_percentage_24h":-0.89304,"market_cap_change_24h":-10.046635178462793,"market_cap_change_percentage_24h":-0.79578,"circulating_supply":11411043.8354697,"total_supply":21400000.0,"max_supply":21400000.0,"ath":0.01616272,"ath_change_percentage":-99.3208,"ath_date":"2018-04-04T16:04:48.408Z","atl":4.268e-05,"atl_change_percentage":157.22799,"atl_date":"2022-05-12T07:28:47.088Z","roi":null,"last_updated":"2022-08-22T16:38:47.229Z"},{"id":"epic-cash","symbol":"epic","name":"Epic Cash","image":"https://assets.coingecko.com/coins/images/9520/large/Epic_Coin_NO_drop_shadow.png?1620122642","current_price":2.803e-05,"market_cap":415.109,"market_cap_rank":953,"fully_diluted_valuation":null,"total_volume":0.2371557,"high_24h":3.053e-05,"low_24h":2.581e-05,"price_change_24h":1.9e-06,"price_change_percentage_24h":7.27524,"market_cap_change_24h":28.26753,"market_cap_change_percentage_24h":7.30726,"circulating_supply":14808052.0,"total_supply":21000000.0,"max_supply":null,"ath":0.00013848,"ath_change_percentage":-79.75864,"ath_date":"2021-12-11T08:39:41.129Z","atl":5.74028e-07,"atl_change_percentage":4783.08078,"atl_date":"2020-03-13T16:55:01.177Z","roi":null,"last_updated":"2022-08-22T16:38:32.826Z"}]', + '[{"id":"bitcoin","symbol":"btc","name":com/coins/images/1/large/' + 'bitcoin.png?1547033579","current_price":1.0,"market_cap":19128800' + ',"market_cap_rank":1,"fully_diluted_valuation":21000000,"total_volum' + 'e":1272132,"high_24h":1.0,"low_24h":1.0,"price_change_24h":0.0,"pri' + 'ce_change_percentage_24h":0.0,"market_cap_change_24h":950.0,"market_' + 'cap_change_percentage_24h":0.00497,"circulating_supply":19128800.0,"t' + 'otal_supply":21000000.0,"max_supply":21000000.0,"ath":1.003301,"ath' + '_change_percentage":-0.32896,"ath_date":"2019-10-15T16:00:56.136Z",' + '"atl":0.99895134,"atl_change_percentage":0.10498,"atl_date":' + '"2019-10-21T00:00:00.000Z","roi":null,' + '"last_updated":"2022-08-22T16:37:59.237Z"},{"id":"dogecoin"' + ',"symbol":"doge","name":"Dogecoin","image":' + '"https://assets.coingecko.com/coins/images/5/large/dogecoin.png?1547792256",' + '"current_price":3.15e-06,"market_cap":417916,"market_cap_rank":10' + ',"fully_diluted_valuation":null,"total_volume":27498,"high_24h":3' + '.26e-06,"low_24h":3.13e-06,"price_change_24h":-8.6889947714e-08,"' + 'price_change_percentage_24h":-2.68533,"market_cap_change_24h":-11' + '370.894861206936,"market_cap_change_percentage_24h":-2.64879,"cir' + 'culating_supply":132670764299.894,"total_supply":null,"max_supply' + '":null,"ath":1.264e-05,"ath_change_percentage":-75.05046,"ath_date' + '":"2021-05-07T23:04:53.026Z","atl":1.50936e-07,"atl_change_percen' + 'tage":1989.69346,"atl_date":"2020-12-17T09:18:05.654Z","roi":null,' + '"last_updated":"2022-08-22T16:38:15.113Z"},{"id":"monero","symbol"' + ':"xmr","name":"Monero","image":"https://assets.coingecko.com/coins' + '/images/69/large/monero_logo.png?1547033729","current_price":0.007' + '17236,"market_cap":130002,"market_cap_rank":29,"fully_diluted_valu' + 'ation":null,"total_volume":4901,"high_24h":0.00731999,"low_24h":0.' + '00707511,"price_change_24h":-5.6133543212467e-05,"price_change_per' + 'centage_24h":-0.77656,"market_cap_change_24h":-1007.8447677436197' + ',"market_cap_change_percentage_24h":-0.76929,"circulating_supply":' + '18147820.3764146,"total_supply":null,"max_supply":null,"ath":0.034' + '75393,"ath_change_percentage":-79.32037,"ath_date":"2018-01-09T00:' + '00:00.000Z","atl":0.00101492,"atl_change_percentage":608.13327,"at' + 'l_date":"2014-12-18T00:00:00.000Z","roi":null,"last_updated":"2022' + '-08-22T16:38:26.347Z"},{"id":"zcoin","symbol":"firo","name":"Firo"' + ',"image":"https://assets.coingecko.com/coins/images/479/large/firo' + 'coingecko.png?1636537544","current_price":0.0001096,"market_cap":1' + '252,"market_cap_rank":604,"fully_diluted_valuation":2349,"total_vo' + 'lume":90.573,"high_24h":0.00011148,"low_24h":0.00010834,"price_chang' + 'e_24h":-9.87561775002e-07,"price_change_percentage_24h":-0.89304,' + '"market_cap_change_24h":-10.046635178462793,"market_cap_change_per' + 'centage_24h":-0.79578,"circulating_supply":11411043.8354697,"tota' + 'l_supply":21400000.0,"max_supply":21400000.0,"ath":0.01616272,"ath' + '_change_percentage":-99.3208,"ath_date":"2018-04-04T16:04:48.408Z"' + ',"atl":4.268e-05,"atl_change_percentage":157.22799,"atl_date":"202' + '2-05-12T07:28:47.088Z","roi":null,"last_updated":"2022-08-22T16:3' + '8:47.229Z"},{"id":"epic-cash","symbol":"epic","name":"Epic Cash",' + '"image":"https://assets.coingecko.com/coins/images/9520/large/' + 'Epic_Coin_NO_drop_shadow.png?1620122642","current_price":2.803e-0' + '5,"market_cap":415.109,"market_cap_rank":953,"fully_diluted_valuat' + 'ion":null,"total_volume":0.2371557,"high_24h":3.053e-05,"low_24h":' + '2.581e-05,"price_change_24h":1.9e-06,"price_change_percentage_24h"' + ':7.27524,"market_cap_change_24h":28.26753,"market_cap_change_per' + 'centage_24h":7.30726,"circulating_supply":14808052.0,"total_suppl' + 'y":21000000.0,"max_supply":null,"ath":0.00013848,"ath_change_perce' + 'ntage":-79.75864,"ath_date":"2021-12-11T08:39:41.129Z","atl":5.74' + '028e-07,"atl_change_percentage":4783.08078,"atl_date":"2020-03-13T' + '16:55:01.177Z","roi":null,"last_updated":"2022-08-22T16:38:32.826Z"}]', 200)); final priceAPI = PriceAPI(client); @@ -131,8 +303,15 @@ void main() { final price = await priceAPI.getPricesAnd24hChange(baseCurrency: "btc"); - expect(price.toString(), - '{Coin.bitcoin: [0, 0.0], Coin.bitcoincash: [0, 0.0], Coin.dogecoin: [0, 0.0], Coin.epicCash: [0, 0.0], Coin.ethereum: [0, 0.0], Coin.firo: [0, 0.0], Coin.litecoin: [0, 0.0], Coin.monero: [0, 0.0], Coin.namecoin: [0, 0.0], Coin.particl: [0, 0.0], Coin.wownero: [0, 0.0], Coin.bitcoinTestNet: [0, 0.0], Coin.litecoinTestNet: [0, 0.0], Coin.bitcoincashTestnet: [0, 0.0], Coin.dogecoinTestNet: [0, 0.0], Coin.firoTestNet: [0, 0.0]}'); + expect( + price.toString(), + '{Coin.bitcoin: [0, 0.0], Coin.bitcoincash: [0, 0.0], Coin.dogecoin: ' + '[0, 0.0], Coin.eCash: [0, 0.0], Coin.epicCash: [0, 0.0], Coin.ethereum: [0, 0.0],' + ' Coin.firo: [0, 0.0], Coin.litecoin: [0, 0.0], Coin.monero: [0, 0.0],' + ' Coin.namecoin: [0, 0.0], Coin.particl: [0, 0.0], Coin.wownero: [0, 0.0],' + ' Coin.bitcoinTestNet: [0, 0.0], Coin.litecoinTestNet: [0, 0.0], ' + 'Coin.bitcoincashTestnet: [0, 0.0], Coin.dogecoinTestNet: [0, 0.0],' + ' Coin.firoTestNet: [0, 0.0]}'); }); test("no internet available", () async { @@ -141,7 +320,7 @@ void main() { when(client.get( Uri.parse( "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc" - "&ids=monero,bitcoin,litecoin,epic-cash,zcoin,dogecoin," + "&ids=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin," "bitcoin-cash,namecoin,wownero,ethereum,particl" "&order=market_cap_desc&per_page=50&page=1&sparkline=false"), headers: { @@ -157,7 +336,7 @@ void main() { expect( price.toString(), '{Coin.bitcoin: [0, 0.0], Coin.bitcoincash: [0, 0.0], ' - 'Coin.dogecoin: [0, 0.0], Coin.epicCash: [0, 0.0], Coin.ethereum: [0, 0.0],' + 'Coin.dogecoin: [0, 0.0], Coin.eCash: [0, 0.0], Coin.epicCash: [0, 0.0], Coin.ethereum: [0, 0.0],' ' Coin.firo: [0, 0.0], Coin.litecoin: [0, 0.0], Coin.monero: [0, 0.0],' ' Coin.namecoin: [0, 0.0], Coin.particl: [0, 0.0], Coin.wownero: [0, 0.0],' ' Coin.bitcoinTestNet: [0, 0.0], Coin.litecoinTestNet: [0, 0.0], ' 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" + } +}; diff --git a/test/screen_tests/settings_view/settings_subviews/wallet_settings_view_screen_test.mocks.dart b/test/screen_tests/settings_view/settings_subviews/wallet_settings_view_screen_test.mocks.dart index 026d06a20..b63e29f34 100644 --- a/test/screen_tests/settings_view/settings_subviews/wallet_settings_view_screen_test.mocks.dart +++ b/test/screen_tests/settings_view/settings_subviews/wallet_settings_view_screen_test.mocks.dart @@ -175,7 +175,7 @@ class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { _i9.Future>.value({}), ) as _i9.Future>); @override - _i9.Future> getUsedCoinSerials({ + _i9.Future> getUsedCoinSerials({ required _i10.Coin? coin, int? startNumber = 0, }) => @@ -188,8 +188,8 @@ class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { #startNumber: startNumber, }, ), - returnValue: _i9.Future>.value([]), - ) as _i9.Future>); + returnValue: _i9.Future>.value([]), + ) as _i9.Future>); @override _i9.Future clearSharedTransactionCache({required _i10.Coin? coin}) => (super.noSuchMethod( diff --git a/test/services/coins/bitcoin/bitcoin_wallet_test.mocks.dart b/test/services/coins/bitcoin/bitcoin_wallet_test.mocks.dart index 12ae0ae62..93eb0e1c8 100644 --- a/test/services/coins/bitcoin/bitcoin_wallet_test.mocks.dart +++ b/test/services/coins/bitcoin/bitcoin_wallet_test.mocks.dart @@ -487,7 +487,7 @@ class MockCachedElectrumX extends _i1.Mock implements _i6.CachedElectrumX { _i5.Future>.value({}), ) as _i5.Future>); @override - _i5.Future> getUsedCoinSerials({ + _i5.Future> getUsedCoinSerials({ required _i7.Coin? coin, int? startNumber = 0, }) => @@ -500,8 +500,8 @@ class MockCachedElectrumX extends _i1.Mock implements _i6.CachedElectrumX { #startNumber: startNumber, }, ), - returnValue: _i5.Future>.value([]), - ) as _i5.Future>); + returnValue: _i5.Future>.value([]), + ) as _i5.Future>); @override _i5.Future clearSharedTransactionCache({required _i7.Coin? coin}) => (super.noSuchMethod( diff --git a/test/services/coins/bitcoincash/bitcoincash_wallet_test.mocks.dart b/test/services/coins/bitcoincash/bitcoincash_wallet_test.mocks.dart index 699278ce1..05cd24ebd 100644 --- a/test/services/coins/bitcoincash/bitcoincash_wallet_test.mocks.dart +++ b/test/services/coins/bitcoincash/bitcoincash_wallet_test.mocks.dart @@ -487,7 +487,7 @@ class MockCachedElectrumX extends _i1.Mock implements _i6.CachedElectrumX { _i5.Future>.value({}), ) as _i5.Future>); @override - _i5.Future> getUsedCoinSerials({ + _i5.Future> getUsedCoinSerials({ required _i7.Coin? coin, int? startNumber = 0, }) => @@ -500,8 +500,8 @@ class MockCachedElectrumX extends _i1.Mock implements _i6.CachedElectrumX { #startNumber: startNumber, }, ), - returnValue: _i5.Future>.value([]), - ) as _i5.Future>); + returnValue: _i5.Future>.value([]), + ) as _i5.Future>); @override _i5.Future clearSharedTransactionCache({required _i7.Coin? coin}) => (super.noSuchMethod( diff --git a/test/services/coins/dogecoin/dogecoin_wallet_test.mocks.dart b/test/services/coins/dogecoin/dogecoin_wallet_test.mocks.dart index 3c78f6dc6..ff04b9f73 100644 --- a/test/services/coins/dogecoin/dogecoin_wallet_test.mocks.dart +++ b/test/services/coins/dogecoin/dogecoin_wallet_test.mocks.dart @@ -487,7 +487,7 @@ class MockCachedElectrumX extends _i1.Mock implements _i6.CachedElectrumX { _i5.Future>.value({}), ) as _i5.Future>); @override - _i5.Future> getUsedCoinSerials({ + _i5.Future> getUsedCoinSerials({ required _i7.Coin? coin, int? startNumber = 0, }) => @@ -500,8 +500,8 @@ class MockCachedElectrumX extends _i1.Mock implements _i6.CachedElectrumX { #startNumber: startNumber, }, ), - returnValue: _i5.Future>.value([]), - ) as _i5.Future>); + returnValue: _i5.Future>.value([]), + ) as _i5.Future>); @override _i5.Future clearSharedTransactionCache({required _i7.Coin? coin}) => (super.noSuchMethod( diff --git a/test/services/coins/firo/firo_wallet_test.dart b/test/services/coins/firo/firo_wallet_test.dart index d7b23e51e..766df279f 100644 --- a/test/services/coins/firo/firo_wallet_test.dart +++ b/test/services/coins/firo/firo_wallet_test.dart @@ -8,6 +8,7 @@ import 'package:hive/hive.dart'; import 'package:hive_test/hive_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.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/models/isar/models/blockchain_data/transaction.dart'; @@ -17,7 +18,6 @@ import 'package:stackwallet/models/lelantus_fee_data.dart'; import 'package:stackwallet/models/paymint/transactions_model.dart' as old; import 'package:stackwallet/services/coins/firo/firo_wallet.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/enums/coin_enum.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; @@ -34,16 +34,10 @@ import 'sample_data/transaction_data_samples.dart'; ElectrumX, CachedElectrumX, TransactionNotificationTracker, + MainDB, ]) void main() { group("isolate functions", () { - test("isolateDerive", () async { - final result = await isolateDerive( - IsolateDeriveParams.mnemonic, "", 0, 2, firoNetwork); - expect(result, isA>()); - expect(result.toString(), IsolateDeriveParams.expected); - }); - test("isolateRestore success", () async { final cachedClient = MockCachedElectrumX(); final txDataOLD = old.TransactionData.fromJson(dateTimeChunksJson); @@ -76,7 +70,7 @@ void main() { Coin.firo, 1, setData, - usedSerials, + List.from(usedSerials), firoNetwork, ); const currentHeight = 100000000000; @@ -136,7 +130,7 @@ void main() { Coin.firo, 1, setData, - usedSerials, + List.from(usedSerials), firoNetwork, ), throwsA(isA())); @@ -1169,42 +1163,6 @@ void main() { "b36161c6e619395b3d40a851c45c1fef7a5c541eed911b5524a66c5703a689c9"); }); - test("fillAddresses", () async { - final client = MockElectrumX(); - final cachedClient = MockCachedElectrumX(); - final secureStore = FakeSecureStorage(); - - final firo = FiroWallet( - walletName: testWalletName, - walletId: "${testWalletId}fillAddresses", - coin: Coin.firo, - client: client, - cachedClient: cachedClient, - secureStore: secureStore, - tracker: MockTransactionNotificationTracker(), - ); - - await firo.fillAddresses( - FillAddressesParams.mnemonic, - "", - ); - - final rcv = await secureStore.read( - key: "${testWalletId}fillAddresses_receiveDerivations"); - final chg = await secureStore.read( - key: "${testWalletId}fillAddresses_changeDerivations"); - final receiveDerivations = - Map.from(jsonDecode(rcv as String) as Map); - final changeDerivations = - Map.from(jsonDecode(chg as String) as Map); - - expect(receiveDerivations.toString(), - FillAddressesParams.expectedReceiveDerivationsString); - - expect(changeDerivations.toString(), - FillAddressesParams.expectedChangeDerivationsString); - }); - // the above test needs to pass in order for this test to pass test("buildMintTransaction", () async { TestWidgetsFlutterBinding.ensureInitialized(); @@ -1230,6 +1188,7 @@ void main() { final client = MockElectrumX(); final cachedClient = MockCachedElectrumX(); final secureStore = FakeSecureStorage(); + final mainDB = MockMainDB(); await secureStore.write( key: "${testWalletId}buildMintTransaction_mnemonic", @@ -1246,6 +1205,9 @@ void main() { when(client.getBlockHeadTip()).thenAnswer( (_) async => {"height": 455873, "hex": "this value not used here"}); + when(mainDB.getAddress("${testWalletId}buildMintTransaction", any)) + .thenAnswer((realInvocation) async => null); + final firo = FiroWallet( walletName: testWalletName, walletId: "${testWalletId}buildMintTransaction", @@ -1254,6 +1216,7 @@ void main() { cachedClient: cachedClient, secureStore: secureStore, tracker: MockTransactionNotificationTracker(), + mockableOverride: mainDB, ); final wallet = @@ -2314,8 +2277,8 @@ void main() { groupId: "1", blockhash: "", coin: Coin.firo)) .thenAnswer((_) async => GetAnonymitySetSampleData.data); when(cachedClient.getUsedCoinSerials(startNumber: 0, coin: Coin.firo)) - .thenAnswer( - (_) async => GetUsedSerialsSampleData.serials['serials'] as List); + .thenAnswer((_) async => List.from( + GetUsedSerialsSampleData.serials['serials'] as List)); final firo = FiroWallet( walletId: "${testWalletId}getUsedCoinSerials", @@ -2811,169 +2774,6 @@ void main() { // throwsA(isA())); // }, timeout: const Timeout(Duration(minutes: 3))); - test("send fails due to bad transaction created", () async { - TestWidgetsFlutterBinding.ensureInitialized(); - const MethodChannel('uk.spiralarm.flutter/devicelocale') - .setMockMethodCallHandler((methodCall) async => 'en_US'); - - final client = MockElectrumX(); - final cachedClient = MockCachedElectrumX(); - final secureStore = FakeSecureStorage(); - - when(client.getLatestCoinId()).thenAnswer((_) async => 1); - when(client.getBlockHeadTip()).thenAnswer( - (_) async => {"height": 459185, "hex": "... some block hex ..."}); - - when(client.broadcastTransaction(rawTx: anyNamed("rawTx"))) - .thenAnswer((_) async { - return "some bad txid"; - }); - - when(client.getBatchHistory(args: batchHistoryRequest0)) - .thenAnswer((realInvocation) async => batchHistoryResponse0); - - when(cachedClient.getAnonymitySet( - groupId: "1", - coin: Coin.firo, - )).thenAnswer((_) async => GetAnonymitySetSampleData.data); - - // mock transaction calls - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash0, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData0); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash1, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData1); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash2, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData2); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash3, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData3); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash4, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData4); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash5, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData5); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash6, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData6); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash7, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData7); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash8, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData8); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash9, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData9); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash10, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData10); - - final firo = FiroWallet( - walletId: "${testWalletId}send", - coin: Coin.firo, - walletName: testWalletName, - client: client, - cachedClient: cachedClient, - secureStore: secureStore, - tracker: MockTransactionNotificationTracker(), - ); - - // set mnemonic - await secureStore.write( - key: "${testWalletId}send_mnemonic", value: TEST_MNEMONIC); - - // set timer to non null so a periodic timer isn't created - firo.timer = Timer(const Duration(), () {}); - - // build sending wallet - await firo.fillAddresses( - TEST_MNEMONIC, - "", - ); - final wallet = await Hive.openBox("${testWalletId}send"); - - final rcv = - await secureStore.read(key: "${testWalletId}send_receiveDerivations"); - final chg = - await secureStore.read(key: "${testWalletId}send_changeDerivations"); - final receiveDerivations = - Map.from(jsonDecode(rcv as String) as Map); - final changeDerivations = - Map.from(jsonDecode(chg as String) as Map); - - for (int i = 0; i < receiveDerivations.length; i++) { - final receiveHash = AddressUtils.convertToScriptHash( - receiveDerivations["$i"]!["address"] as String, firoNetwork); - final changeHash = AddressUtils.convertToScriptHash( - changeDerivations["$i"]!["address"] as String, firoNetwork); - List> data; - switch (receiveHash) { - case SampleGetHistoryData.scripthash0: - data = SampleGetHistoryData.data0; - break; - case SampleGetHistoryData.scripthash1: - data = SampleGetHistoryData.data1; - break; - case SampleGetHistoryData.scripthash2: - data = SampleGetHistoryData.data2; - break; - case SampleGetHistoryData.scripthash3: - data = SampleGetHistoryData.data3; - break; - default: - data = []; - } - when(client.getHistory(scripthash: receiveHash)) - .thenAnswer((_) async => data); - - switch (changeHash) { - case SampleGetHistoryData.scripthash0: - data = SampleGetHistoryData.data0; - break; - case SampleGetHistoryData.scripthash1: - data = SampleGetHistoryData.data1; - break; - case SampleGetHistoryData.scripthash2: - data = SampleGetHistoryData.data2; - break; - case SampleGetHistoryData.scripthash3: - data = SampleGetHistoryData.data3; - break; - default: - data = []; - } - - when(client.getHistory(scripthash: changeHash)) - .thenAnswer((_) async => data); - } - - await wallet.put('_lelantus_coins', SampleLelantus.lelantusCoins); - await wallet.put('jindex', [2, 4, 6]); - await wallet.put('mintIndex', 8); - await wallet.put('receivingAddresses', [ - "a8VV7vMzJdTQj1eLEJNskhLEBUxfNWhpAg", - "aPjLWDTPQsoPHUTxKBNRzoebDALj3eTcfh", - "aKmXfS7nEZdqWBGRdAXcyMoEoKhZQDPBoq" - ]); - await wallet - .put('changeAddresses', ["a5V5r6We6mNZzWJwGwEeRML3mEYLjvK39w"]); - }, timeout: const Timeout(Duration(minutes: 3))); - // test("wallet balances", () async { // TestWidgetsFlutterBinding.ensureInitialized(); // const MethodChannel('uk.spiralarm.flutter/devicelocale') diff --git a/test/services/coins/firo/firo_wallet_test.mocks.dart b/test/services/coins/firo/firo_wallet_test.mocks.dart index 0e3549d87..bb6d13ca7 100644 --- a/test/services/coins/firo/firo_wallet_test.mocks.dart +++ b/test/services/coins/firo/firo_wallet_test.mocks.dart @@ -3,16 +3,22 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i5; +import 'dart:async' as _i6; import 'package:decimal/decimal.dart' as _i2; +import 'package:isar/isar.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; -import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart' as _i6; -import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i4; +import 'package:stackwallet/db/isar/main_db.dart' as _i10; +import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart' as _i7; +import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i5; +import 'package:stackwallet/models/isar/models/block_explorer.dart' as _i12; +import 'package:stackwallet/models/isar/models/contact_entry.dart' as _i11; +import 'package:stackwallet/models/isar/models/isar_models.dart' as _i13; import 'package:stackwallet/services/transaction_notification_tracker.dart' - as _i8; -import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i7; + as _i9; +import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i8; import 'package:stackwallet/utilities/prefs.dart' as _i3; +import 'package:tuple/tuple.dart' as _i14; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -45,16 +51,37 @@ class _FakePrefs_1 extends _i1.SmartFake implements _i3.Prefs { ); } +class _FakeIsar_2 extends _i1.SmartFake implements _i4.Isar { + _FakeIsar_2( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeQueryBuilder_3 extends _i1.SmartFake + implements _i4.QueryBuilder { + _FakeQueryBuilder_3( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + /// A class which mocks [ElectrumX]. /// /// See the documentation for Mockito's code generation for more information. -class MockElectrumX extends _i1.Mock implements _i4.ElectrumX { +class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { MockElectrumX() { _i1.throwOnMissingStub(this); } @override - set failovers(List<_i4.ElectrumXNode>? _failovers) => super.noSuchMethod( + set failovers(List<_i5.ElectrumXNode>? _failovers) => super.noSuchMethod( Invocation.setter( #failovers, _failovers, @@ -90,7 +117,7 @@ class MockElectrumX extends _i1.Mock implements _i4.ElectrumX { returnValue: false, ) as bool); @override - _i5.Future request({ + _i6.Future request({ required String? command, List? args = const [], Duration? connectionTimeout = const Duration(seconds: 60), @@ -109,10 +136,10 @@ class MockElectrumX extends _i1.Mock implements _i4.ElectrumX { #retries: retries, }, ), - returnValue: _i5.Future.value(), - ) as _i5.Future); + returnValue: _i6.Future.value(), + ) as _i6.Future); @override - _i5.Future>> batchRequest({ + _i6.Future>> batchRequest({ required String? command, required Map>? args, Duration? connectionTimeout = const Duration(seconds: 60), @@ -129,11 +156,11 @@ class MockElectrumX extends _i1.Mock implements _i4.ElectrumX { #retries: retries, }, ), - returnValue: _i5.Future>>.value( + returnValue: _i6.Future>>.value( >[]), - ) as _i5.Future>>); + ) as _i6.Future>>); @override - _i5.Future ping({ + _i6.Future ping({ String? requestID, int? retryCount = 1, }) => @@ -146,10 +173,10 @@ class MockElectrumX extends _i1.Mock implements _i4.ElectrumX { #retryCount: retryCount, }, ), - returnValue: _i5.Future.value(false), - ) as _i5.Future); + returnValue: _i6.Future.value(false), + ) as _i6.Future); @override - _i5.Future> getBlockHeadTip({String? requestID}) => + _i6.Future> getBlockHeadTip({String? requestID}) => (super.noSuchMethod( Invocation.method( #getBlockHeadTip, @@ -157,10 +184,10 @@ class MockElectrumX extends _i1.Mock implements _i4.ElectrumX { {#requestID: requestID}, ), returnValue: - _i5.Future>.value({}), - ) as _i5.Future>); + _i6.Future>.value({}), + ) as _i6.Future>); @override - _i5.Future> getServerFeatures({String? requestID}) => + _i6.Future> getServerFeatures({String? requestID}) => (super.noSuchMethod( Invocation.method( #getServerFeatures, @@ -168,10 +195,10 @@ class MockElectrumX extends _i1.Mock implements _i4.ElectrumX { {#requestID: requestID}, ), returnValue: - _i5.Future>.value({}), - ) as _i5.Future>); + _i6.Future>.value({}), + ) as _i6.Future>); @override - _i5.Future broadcastTransaction({ + _i6.Future broadcastTransaction({ required String? rawTx, String? requestID, }) => @@ -184,10 +211,10 @@ class MockElectrumX extends _i1.Mock implements _i4.ElectrumX { #requestID: requestID, }, ), - returnValue: _i5.Future.value(''), - ) as _i5.Future); + returnValue: _i6.Future.value(''), + ) as _i6.Future); @override - _i5.Future> getBalance({ + _i6.Future> getBalance({ required String? scripthash, String? requestID, }) => @@ -201,10 +228,10 @@ class MockElectrumX extends _i1.Mock implements _i4.ElectrumX { }, ), returnValue: - _i5.Future>.value({}), - ) as _i5.Future>); + _i6.Future>.value({}), + ) as _i6.Future>); @override - _i5.Future>> getHistory({ + _i6.Future>> getHistory({ required String? scripthash, String? requestID, }) => @@ -217,11 +244,11 @@ class MockElectrumX extends _i1.Mock implements _i4.ElectrumX { #requestID: requestID, }, ), - returnValue: _i5.Future>>.value( + returnValue: _i6.Future>>.value( >[]), - ) as _i5.Future>>); + ) as _i6.Future>>); @override - _i5.Future>>> getBatchHistory( + _i6.Future>>> getBatchHistory( {required Map>? args}) => (super.noSuchMethod( Invocation.method( @@ -229,11 +256,11 @@ class MockElectrumX extends _i1.Mock implements _i4.ElectrumX { [], {#args: args}, ), - returnValue: _i5.Future>>>.value( + returnValue: _i6.Future>>>.value( >>{}), - ) as _i5.Future>>>); + ) as _i6.Future>>>); @override - _i5.Future>> getUTXOs({ + _i6.Future>> getUTXOs({ required String? scripthash, String? requestID, }) => @@ -246,11 +273,11 @@ class MockElectrumX extends _i1.Mock implements _i4.ElectrumX { #requestID: requestID, }, ), - returnValue: _i5.Future>>.value( + returnValue: _i6.Future>>.value( >[]), - ) as _i5.Future>>); + ) as _i6.Future>>); @override - _i5.Future>>> getBatchUTXOs( + _i6.Future>>> getBatchUTXOs( {required Map>? args}) => (super.noSuchMethod( Invocation.method( @@ -258,11 +285,11 @@ class MockElectrumX extends _i1.Mock implements _i4.ElectrumX { [], {#args: args}, ), - returnValue: _i5.Future>>>.value( + returnValue: _i6.Future>>>.value( >>{}), - ) as _i5.Future>>>); + ) as _i6.Future>>>); @override - _i5.Future> getTransaction({ + _i6.Future> getTransaction({ required String? txHash, bool? verbose = true, String? requestID, @@ -278,10 +305,10 @@ class MockElectrumX extends _i1.Mock implements _i4.ElectrumX { }, ), returnValue: - _i5.Future>.value({}), - ) as _i5.Future>); + _i6.Future>.value({}), + ) as _i6.Future>); @override - _i5.Future> getAnonymitySet({ + _i6.Future> getAnonymitySet({ String? groupId = r'1', String? blockhash = r'', String? requestID, @@ -297,10 +324,10 @@ class MockElectrumX extends _i1.Mock implements _i4.ElectrumX { }, ), returnValue: - _i5.Future>.value({}), - ) as _i5.Future>); + _i6.Future>.value({}), + ) as _i6.Future>); @override - _i5.Future getMintData({ + _i6.Future getMintData({ dynamic mints, String? requestID, }) => @@ -313,10 +340,10 @@ class MockElectrumX extends _i1.Mock implements _i4.ElectrumX { #requestID: requestID, }, ), - returnValue: _i5.Future.value(), - ) as _i5.Future); + returnValue: _i6.Future.value(), + ) as _i6.Future); @override - _i5.Future> getUsedCoinSerials({ + _i6.Future> getUsedCoinSerials({ String? requestID, required int? startNumber, }) => @@ -330,19 +357,19 @@ class MockElectrumX extends _i1.Mock implements _i4.ElectrumX { }, ), returnValue: - _i5.Future>.value({}), - ) as _i5.Future>); + _i6.Future>.value({}), + ) as _i6.Future>); @override - _i5.Future getLatestCoinId({String? requestID}) => (super.noSuchMethod( + _i6.Future getLatestCoinId({String? requestID}) => (super.noSuchMethod( Invocation.method( #getLatestCoinId, [], {#requestID: requestID}, ), - returnValue: _i5.Future.value(0), - ) as _i5.Future); + returnValue: _i6.Future.value(0), + ) as _i6.Future); @override - _i5.Future> getFeeRate({String? requestID}) => + _i6.Future> getFeeRate({String? requestID}) => (super.noSuchMethod( Invocation.method( #getFeeRate, @@ -350,10 +377,10 @@ class MockElectrumX extends _i1.Mock implements _i4.ElectrumX { {#requestID: requestID}, ), returnValue: - _i5.Future>.value({}), - ) as _i5.Future>); + _i6.Future>.value({}), + ) as _i6.Future>); @override - _i5.Future<_i2.Decimal> estimateFee({ + _i6.Future<_i2.Decimal> estimateFee({ String? requestID, required int? blocks, }) => @@ -366,7 +393,7 @@ class MockElectrumX extends _i1.Mock implements _i4.ElectrumX { #blocks: blocks, }, ), - returnValue: _i5.Future<_i2.Decimal>.value(_FakeDecimal_0( + returnValue: _i6.Future<_i2.Decimal>.value(_FakeDecimal_0( this, Invocation.method( #estimateFee, @@ -377,15 +404,15 @@ class MockElectrumX extends _i1.Mock implements _i4.ElectrumX { }, ), )), - ) as _i5.Future<_i2.Decimal>); + ) as _i6.Future<_i2.Decimal>); @override - _i5.Future<_i2.Decimal> relayFee({String? requestID}) => (super.noSuchMethod( + _i6.Future<_i2.Decimal> relayFee({String? requestID}) => (super.noSuchMethod( Invocation.method( #relayFee, [], {#requestID: requestID}, ), - returnValue: _i5.Future<_i2.Decimal>.value(_FakeDecimal_0( + returnValue: _i6.Future<_i2.Decimal>.value(_FakeDecimal_0( this, Invocation.method( #relayFee, @@ -393,13 +420,13 @@ class MockElectrumX extends _i1.Mock implements _i4.ElectrumX { {#requestID: requestID}, ), )), - ) as _i5.Future<_i2.Decimal>); + ) as _i6.Future<_i2.Decimal>); } /// A class which mocks [CachedElectrumX]. /// /// See the documentation for Mockito's code generation for more information. -class MockCachedElectrumX extends _i1.Mock implements _i6.CachedElectrumX { +class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { MockCachedElectrumX() { _i1.throwOnMissingStub(this); } @@ -428,15 +455,15 @@ class MockCachedElectrumX extends _i1.Mock implements _i6.CachedElectrumX { ), ) as _i3.Prefs); @override - List<_i4.ElectrumXNode> get failovers => (super.noSuchMethod( + List<_i5.ElectrumXNode> get failovers => (super.noSuchMethod( Invocation.getter(#failovers), - returnValue: <_i4.ElectrumXNode>[], - ) as List<_i4.ElectrumXNode>); + returnValue: <_i5.ElectrumXNode>[], + ) as List<_i5.ElectrumXNode>); @override - _i5.Future> getAnonymitySet({ + _i6.Future> getAnonymitySet({ required String? groupId, String? blockhash = r'', - required _i7.Coin? coin, + required _i8.Coin? coin, }) => (super.noSuchMethod( Invocation.method( @@ -449,8 +476,8 @@ class MockCachedElectrumX extends _i1.Mock implements _i6.CachedElectrumX { }, ), returnValue: - _i5.Future>.value({}), - ) as _i5.Future>); + _i6.Future>.value({}), + ) as _i6.Future>); @override String base64ToHex(String? source) => (super.noSuchMethod( Invocation.method( @@ -468,9 +495,9 @@ class MockCachedElectrumX extends _i1.Mock implements _i6.CachedElectrumX { returnValue: '', ) as String); @override - _i5.Future> getTransaction({ + _i6.Future> getTransaction({ required String? txHash, - required _i7.Coin? coin, + required _i8.Coin? coin, bool? verbose = true, }) => (super.noSuchMethod( @@ -484,11 +511,11 @@ class MockCachedElectrumX extends _i1.Mock implements _i6.CachedElectrumX { }, ), returnValue: - _i5.Future>.value({}), - ) as _i5.Future>); + _i6.Future>.value({}), + ) as _i6.Future>); @override - _i5.Future> getUsedCoinSerials({ - required _i7.Coin? coin, + _i6.Future> getUsedCoinSerials({ + required _i8.Coin? coin, int? startNumber = 0, }) => (super.noSuchMethod( @@ -500,26 +527,26 @@ class MockCachedElectrumX extends _i1.Mock implements _i6.CachedElectrumX { #startNumber: startNumber, }, ), - returnValue: _i5.Future>.value([]), - ) as _i5.Future>); + returnValue: _i6.Future>.value([]), + ) as _i6.Future>); @override - _i5.Future clearSharedTransactionCache({required _i7.Coin? coin}) => + _i6.Future clearSharedTransactionCache({required _i8.Coin? coin}) => (super.noSuchMethod( Invocation.method( #clearSharedTransactionCache, [], {#coin: coin}, ), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value(), - ) as _i5.Future); + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); } /// A class which mocks [TransactionNotificationTracker]. /// /// See the documentation for Mockito's code generation for more information. class MockTransactionNotificationTracker extends _i1.Mock - implements _i8.TransactionNotificationTracker { + implements _i9.TransactionNotificationTracker { MockTransactionNotificationTracker() { _i1.throwOnMissingStub(this); } @@ -548,14 +575,14 @@ class MockTransactionNotificationTracker extends _i1.Mock returnValue: false, ) as bool); @override - _i5.Future addNotifiedPending(String? txid) => (super.noSuchMethod( + _i6.Future addNotifiedPending(String? txid) => (super.noSuchMethod( Invocation.method( #addNotifiedPending, [txid], ), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value(), - ) as _i5.Future); + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); @override bool wasNotifiedConfirmed(String? txid) => (super.noSuchMethod( Invocation.method( @@ -565,21 +592,582 @@ class MockTransactionNotificationTracker extends _i1.Mock returnValue: false, ) as bool); @override - _i5.Future addNotifiedConfirmed(String? txid) => (super.noSuchMethod( + _i6.Future addNotifiedConfirmed(String? txid) => (super.noSuchMethod( Invocation.method( #addNotifiedConfirmed, [txid], ), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value(), - ) as _i5.Future); + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); @override - _i5.Future deleteTransaction(String? txid) => (super.noSuchMethod( + _i6.Future deleteTransaction(String? txid) => (super.noSuchMethod( Invocation.method( #deleteTransaction, [txid], ), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value(), - ) as _i5.Future); + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); +} + +/// A class which mocks [MainDB]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockMainDB extends _i1.Mock implements _i10.MainDB { + MockMainDB() { + _i1.throwOnMissingStub(this); + } + + @override + _i4.Isar get isar => (super.noSuchMethod( + Invocation.getter(#isar), + returnValue: _FakeIsar_2( + this, + Invocation.getter(#isar), + ), + ) as _i4.Isar); + @override + _i6.Future initMainDB({_i4.Isar? mock}) => (super.noSuchMethod( + Invocation.method( + #initMainDB, + [], + {#mock: mock}, + ), + returnValue: _i6.Future.value(false), + ) as _i6.Future); + @override + List<_i11.ContactEntry> getContactEntries() => (super.noSuchMethod( + Invocation.method( + #getContactEntries, + [], + ), + returnValue: <_i11.ContactEntry>[], + ) as List<_i11.ContactEntry>); + @override + _i6.Future deleteContactEntry({required String? id}) => + (super.noSuchMethod( + Invocation.method( + #deleteContactEntry, + [], + {#id: id}, + ), + returnValue: _i6.Future.value(false), + ) as _i6.Future); + @override + _i6.Future isContactEntryExists({required String? id}) => + (super.noSuchMethod( + Invocation.method( + #isContactEntryExists, + [], + {#id: id}, + ), + returnValue: _i6.Future.value(false), + ) as _i6.Future); + @override + _i11.ContactEntry? getContactEntry({required String? id}) => + (super.noSuchMethod(Invocation.method( + #getContactEntry, + [], + {#id: id}, + )) as _i11.ContactEntry?); + @override + _i6.Future putContactEntry( + {required _i11.ContactEntry? contactEntry}) => + (super.noSuchMethod( + Invocation.method( + #putContactEntry, + [], + {#contactEntry: contactEntry}, + ), + returnValue: _i6.Future.value(false), + ) as _i6.Future); + @override + _i12.TransactionBlockExplorer? getTransactionBlockExplorer( + {required _i8.Coin? coin}) => + (super.noSuchMethod(Invocation.method( + #getTransactionBlockExplorer, + [], + {#coin: coin}, + )) as _i12.TransactionBlockExplorer?); + @override + _i6.Future putTransactionBlockExplorer( + _i12.TransactionBlockExplorer? explorer) => + (super.noSuchMethod( + Invocation.method( + #putTransactionBlockExplorer, + [explorer], + ), + returnValue: _i6.Future.value(0), + ) as _i6.Future); + @override + _i4.QueryBuilder<_i13.Address, _i13.Address, _i4.QAfterWhereClause> + getAddresses(String? walletId) => (super.noSuchMethod( + Invocation.method( + #getAddresses, + [walletId], + ), + returnValue: _FakeQueryBuilder_3<_i13.Address, _i13.Address, + _i4.QAfterWhereClause>( + this, + Invocation.method( + #getAddresses, + [walletId], + ), + ), + ) as _i4 + .QueryBuilder<_i13.Address, _i13.Address, _i4.QAfterWhereClause>); + @override + _i6.Future putAddress(_i13.Address? address) => (super.noSuchMethod( + Invocation.method( + #putAddress, + [address], + ), + returnValue: _i6.Future.value(0), + ) as _i6.Future); + @override + _i6.Future> putAddresses(List<_i13.Address>? addresses) => + (super.noSuchMethod( + Invocation.method( + #putAddresses, + [addresses], + ), + returnValue: _i6.Future>.value([]), + ) as _i6.Future>); + @override + _i6.Future> updateOrPutAddresses(List<_i13.Address>? addresses) => + (super.noSuchMethod( + Invocation.method( + #updateOrPutAddresses, + [addresses], + ), + returnValue: _i6.Future>.value([]), + ) as _i6.Future>); + @override + _i6.Future<_i13.Address?> getAddress( + String? walletId, + String? address, + ) => + (super.noSuchMethod( + Invocation.method( + #getAddress, + [ + walletId, + address, + ], + ), + returnValue: _i6.Future<_i13.Address?>.value(), + ) as _i6.Future<_i13.Address?>); + @override + _i6.Future updateAddress( + _i13.Address? oldAddress, + _i13.Address? newAddress, + ) => + (super.noSuchMethod( + Invocation.method( + #updateAddress, + [ + oldAddress, + newAddress, + ], + ), + returnValue: _i6.Future.value(0), + ) as _i6.Future); + @override + _i4.QueryBuilder<_i13.Transaction, _i13.Transaction, _i4.QAfterWhereClause> + getTransactions(String? walletId) => (super.noSuchMethod( + Invocation.method( + #getTransactions, + [walletId], + ), + returnValue: _FakeQueryBuilder_3<_i13.Transaction, _i13.Transaction, + _i4.QAfterWhereClause>( + this, + Invocation.method( + #getTransactions, + [walletId], + ), + ), + ) as _i4.QueryBuilder<_i13.Transaction, _i13.Transaction, + _i4.QAfterWhereClause>); + @override + _i6.Future putTransaction(_i13.Transaction? transaction) => + (super.noSuchMethod( + Invocation.method( + #putTransaction, + [transaction], + ), + returnValue: _i6.Future.value(0), + ) as _i6.Future); + @override + _i6.Future> putTransactions(List<_i13.Transaction>? transactions) => + (super.noSuchMethod( + Invocation.method( + #putTransactions, + [transactions], + ), + returnValue: _i6.Future>.value([]), + ) as _i6.Future>); + @override + _i6.Future<_i13.Transaction?> getTransaction( + String? walletId, + String? txid, + ) => + (super.noSuchMethod( + Invocation.method( + #getTransaction, + [ + walletId, + txid, + ], + ), + returnValue: _i6.Future<_i13.Transaction?>.value(), + ) as _i6.Future<_i13.Transaction?>); + @override + _i6.Stream<_i13.Transaction?> watchTransaction({ + required int? id, + bool? fireImmediately = false, + }) => + (super.noSuchMethod( + Invocation.method( + #watchTransaction, + [], + { + #id: id, + #fireImmediately: fireImmediately, + }, + ), + returnValue: _i6.Stream<_i13.Transaction?>.empty(), + ) as _i6.Stream<_i13.Transaction?>); + @override + _i4.QueryBuilder<_i13.UTXO, _i13.UTXO, _i4.QAfterWhereClause> getUTXOs( + String? walletId) => + (super.noSuchMethod( + Invocation.method( + #getUTXOs, + [walletId], + ), + returnValue: + _FakeQueryBuilder_3<_i13.UTXO, _i13.UTXO, _i4.QAfterWhereClause>( + this, + Invocation.method( + #getUTXOs, + [walletId], + ), + ), + ) as _i4.QueryBuilder<_i13.UTXO, _i13.UTXO, _i4.QAfterWhereClause>); + @override + _i6.Future putUTXO(_i13.UTXO? utxo) => (super.noSuchMethod( + Invocation.method( + #putUTXO, + [utxo], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future putUTXOs(List<_i13.UTXO>? utxos) => (super.noSuchMethod( + Invocation.method( + #putUTXOs, + [utxos], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future updateUTXOs( + String? walletId, + List<_i13.UTXO>? utxos, + ) => + (super.noSuchMethod( + Invocation.method( + #updateUTXOs, + [ + walletId, + utxos, + ], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Stream<_i13.UTXO?> watchUTXO({ + required int? id, + bool? fireImmediately = false, + }) => + (super.noSuchMethod( + Invocation.method( + #watchUTXO, + [], + { + #id: id, + #fireImmediately: fireImmediately, + }, + ), + returnValue: _i6.Stream<_i13.UTXO?>.empty(), + ) as _i6.Stream<_i13.UTXO?>); + @override + _i4.QueryBuilder<_i13.TransactionNote, _i13.TransactionNote, + _i4.QAfterWhereClause> getTransactionNotes( + String? walletId) => + (super.noSuchMethod( + Invocation.method( + #getTransactionNotes, + [walletId], + ), + returnValue: _FakeQueryBuilder_3<_i13.TransactionNote, + _i13.TransactionNote, _i4.QAfterWhereClause>( + this, + Invocation.method( + #getTransactionNotes, + [walletId], + ), + ), + ) as _i4.QueryBuilder<_i13.TransactionNote, _i13.TransactionNote, + _i4.QAfterWhereClause>); + @override + _i6.Future putTransactionNote(_i13.TransactionNote? transactionNote) => + (super.noSuchMethod( + Invocation.method( + #putTransactionNote, + [transactionNote], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future putTransactionNotes( + List<_i13.TransactionNote>? transactionNotes) => + (super.noSuchMethod( + Invocation.method( + #putTransactionNotes, + [transactionNotes], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future<_i13.TransactionNote?> getTransactionNote( + String? walletId, + String? txid, + ) => + (super.noSuchMethod( + Invocation.method( + #getTransactionNote, + [ + walletId, + txid, + ], + ), + returnValue: _i6.Future<_i13.TransactionNote?>.value(), + ) as _i6.Future<_i13.TransactionNote?>); + @override + _i6.Stream<_i13.TransactionNote?> watchTransactionNote({ + required int? id, + bool? fireImmediately = false, + }) => + (super.noSuchMethod( + Invocation.method( + #watchTransactionNote, + [], + { + #id: id, + #fireImmediately: fireImmediately, + }, + ), + returnValue: _i6.Stream<_i13.TransactionNote?>.empty(), + ) as _i6.Stream<_i13.TransactionNote?>); + @override + _i4.QueryBuilder<_i13.AddressLabel, _i13.AddressLabel, _i4.QAfterWhereClause> + getAddressLabels(String? walletId) => (super.noSuchMethod( + Invocation.method( + #getAddressLabels, + [walletId], + ), + returnValue: _FakeQueryBuilder_3<_i13.AddressLabel, + _i13.AddressLabel, _i4.QAfterWhereClause>( + this, + Invocation.method( + #getAddressLabels, + [walletId], + ), + ), + ) as _i4.QueryBuilder<_i13.AddressLabel, _i13.AddressLabel, + _i4.QAfterWhereClause>); + @override + _i6.Future putAddressLabel(_i13.AddressLabel? addressLabel) => + (super.noSuchMethod( + Invocation.method( + #putAddressLabel, + [addressLabel], + ), + returnValue: _i6.Future.value(0), + ) as _i6.Future); + @override + int putAddressLabelSync(_i13.AddressLabel? addressLabel) => + (super.noSuchMethod( + Invocation.method( + #putAddressLabelSync, + [addressLabel], + ), + returnValue: 0, + ) as int); + @override + _i6.Future putAddressLabels(List<_i13.AddressLabel>? addressLabels) => + (super.noSuchMethod( + Invocation.method( + #putAddressLabels, + [addressLabels], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future<_i13.AddressLabel?> getAddressLabel( + String? walletId, + String? addressString, + ) => + (super.noSuchMethod( + Invocation.method( + #getAddressLabel, + [ + walletId, + addressString, + ], + ), + returnValue: _i6.Future<_i13.AddressLabel?>.value(), + ) as _i6.Future<_i13.AddressLabel?>); + @override + _i13.AddressLabel? getAddressLabelSync( + String? walletId, + String? addressString, + ) => + (super.noSuchMethod(Invocation.method( + #getAddressLabelSync, + [ + walletId, + addressString, + ], + )) as _i13.AddressLabel?); + @override + _i6.Stream<_i13.AddressLabel?> watchAddressLabel({ + required int? id, + bool? fireImmediately = false, + }) => + (super.noSuchMethod( + Invocation.method( + #watchAddressLabel, + [], + { + #id: id, + #fireImmediately: fireImmediately, + }, + ), + returnValue: _i6.Stream<_i13.AddressLabel?>.empty(), + ) as _i6.Stream<_i13.AddressLabel?>); + @override + _i6.Future updateAddressLabel(_i13.AddressLabel? addressLabel) => + (super.noSuchMethod( + Invocation.method( + #updateAddressLabel, + [addressLabel], + ), + returnValue: _i6.Future.value(0), + ) as _i6.Future); + @override + _i6.Future deleteWalletBlockchainData(String? walletId) => + (super.noSuchMethod( + Invocation.method( + #deleteWalletBlockchainData, + [walletId], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future deleteAddressLabels(String? walletId) => (super.noSuchMethod( + Invocation.method( + #deleteAddressLabels, + [walletId], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future deleteTransactionNotes(String? walletId) => + (super.noSuchMethod( + Invocation.method( + #deleteTransactionNotes, + [walletId], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future addNewTransactionData( + List<_i14.Tuple2<_i13.Transaction, _i13.Address?>>? transactionsData, + String? walletId, + ) => + (super.noSuchMethod( + Invocation.method( + #addNewTransactionData, + [ + transactionsData, + walletId, + ], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i4.QueryBuilder<_i13.EthContract, _i13.EthContract, _i4.QWhere> + getEthContracts() => (super.noSuchMethod( + Invocation.method( + #getEthContracts, + [], + ), + returnValue: _FakeQueryBuilder_3<_i13.EthContract, _i13.EthContract, + _i4.QWhere>( + this, + Invocation.method( + #getEthContracts, + [], + ), + ), + ) as _i4 + .QueryBuilder<_i13.EthContract, _i13.EthContract, _i4.QWhere>); + @override + _i6.Future<_i13.EthContract?> getEthContract(String? contractAddress) => + (super.noSuchMethod( + Invocation.method( + #getEthContract, + [contractAddress], + ), + returnValue: _i6.Future<_i13.EthContract?>.value(), + ) as _i6.Future<_i13.EthContract?>); + @override + _i13.EthContract? getEthContractSync(String? contractAddress) => + (super.noSuchMethod(Invocation.method( + #getEthContractSync, + [contractAddress], + )) as _i13.EthContract?); + @override + _i6.Future putEthContract(_i13.EthContract? contract) => + (super.noSuchMethod( + Invocation.method( + #putEthContract, + [contract], + ), + returnValue: _i6.Future.value(0), + ) as _i6.Future); + @override + _i6.Future putEthContracts(List<_i13.EthContract>? contracts) => + (super.noSuchMethod( + Invocation.method( + #putEthContracts, + [contracts], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); } diff --git a/test/services/coins/manager_test.mocks.dart b/test/services/coins/manager_test.mocks.dart index 2319e31ba..8a607f14f 100644 --- a/test/services/coins/manager_test.mocks.dart +++ b/test/services/coins/manager_test.mocks.dart @@ -658,28 +658,6 @@ class MockFiroWallet extends _i1.Mock implements _i10.FiroWallet { returnValueForMissingStub: _i11.Future.value(), ) as _i11.Future); @override - _i11.Future fillAddresses( - String? suppliedMnemonic, - String? mnemonicPassphrase, { - int? perBatch = 50, - int? numberOfThreads = 4, - }) => - (super.noSuchMethod( - Invocation.method( - #fillAddresses, - [ - suppliedMnemonic, - mnemonicPassphrase, - ], - { - #perBatch: perBatch, - #numberOfThreads: numberOfThreads, - }, - ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); - @override _i11.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, @@ -728,6 +706,16 @@ class MockFiroWallet extends _i1.Mock implements _i10.FiroWallet { returnValue: _i11.Future>.value({}), ) as _i11.Future>); @override + _i11.Future getTransactionCacheEarly(List? allAddresses) => + (super.noSuchMethod( + Invocation.method( + #getTransactionCacheEarly, + [allAddresses], + ), + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); + @override _i11.Future>> fetchAnonymitySets() => (super.noSuchMethod( Invocation.method( @@ -746,13 +734,13 @@ class MockFiroWallet extends _i1.Mock implements _i10.FiroWallet { returnValue: _i11.Future.value(0), ) as _i11.Future); @override - _i11.Future> getUsedCoinSerials() => (super.noSuchMethod( + _i11.Future> getUsedCoinSerials() => (super.noSuchMethod( Invocation.method( #getUsedCoinSerials, [], ), - returnValue: _i11.Future>.value([]), - ) as _i11.Future>); + returnValue: _i11.Future>.value([]), + ) as _i11.Future>); @override _i11.Future exit() => (super.noSuchMethod( Invocation.method( diff --git a/test/services/coins/namecoin/namecoin_wallet_test.mocks.dart b/test/services/coins/namecoin/namecoin_wallet_test.mocks.dart index 28500d8af..3e9fd4fde 100644 --- a/test/services/coins/namecoin/namecoin_wallet_test.mocks.dart +++ b/test/services/coins/namecoin/namecoin_wallet_test.mocks.dart @@ -487,7 +487,7 @@ class MockCachedElectrumX extends _i1.Mock implements _i6.CachedElectrumX { _i5.Future>.value({}), ) as _i5.Future>); @override - _i5.Future> getUsedCoinSerials({ + _i5.Future> getUsedCoinSerials({ required _i7.Coin? coin, int? startNumber = 0, }) => @@ -500,8 +500,8 @@ class MockCachedElectrumX extends _i1.Mock implements _i6.CachedElectrumX { #startNumber: startNumber, }, ), - returnValue: _i5.Future>.value([]), - ) as _i5.Future>); + returnValue: _i5.Future>.value([]), + ) as _i5.Future>); @override _i5.Future clearSharedTransactionCache({required _i7.Coin? coin}) => (super.noSuchMethod( diff --git a/test/services/coins/particl/particl_wallet_test.mocks.dart b/test/services/coins/particl/particl_wallet_test.mocks.dart index 43a1473ce..11a8de944 100644 --- a/test/services/coins/particl/particl_wallet_test.mocks.dart +++ b/test/services/coins/particl/particl_wallet_test.mocks.dart @@ -487,7 +487,7 @@ class MockCachedElectrumX extends _i1.Mock implements _i6.CachedElectrumX { _i5.Future>.value({}), ) as _i5.Future>); @override - _i5.Future> getUsedCoinSerials({ + _i5.Future> getUsedCoinSerials({ required _i7.Coin? coin, int? startNumber = 0, }) => @@ -500,8 +500,8 @@ class MockCachedElectrumX extends _i1.Mock implements _i6.CachedElectrumX { #startNumber: startNumber, }, ), - returnValue: _i5.Future>.value([]), - ) as _i5.Future>); + returnValue: _i5.Future>.value([]), + ) as _i5.Future>); @override _i5.Future clearSharedTransactionCache({required _i7.Coin? coin}) => (super.noSuchMethod( diff --git a/test/widget_tests/custom_pin_put_test.dart b/test/widget_tests/custom_pin_put_test.dart index 28bc583d4..13b2bc892 100644 --- a/test/widget_tests/custom_pin_put_test.dart +++ b/test/widget_tests/custom_pin_put_test.dart @@ -3,20 +3,63 @@ import 'package:flutter_svg/svg.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:stackwallet/models/isar/stack_theme.dart'; import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/widgets/custom_pin_put/custom_pin_put.dart'; import 'package:stackwallet/widgets/custom_pin_put/pin_keyboard.dart'; import '../sample_data/theme_json.dart'; +class PinWidget extends StatefulWidget { + const PinWidget({ + super.key, + this.onSubmit, + this.controller, + required this.pinAnimation, + required this.isRandom, + }); + + final void Function(String)? onSubmit; + final TextEditingController? controller; + final bool isRandom; + final PinAnimationType pinAnimation; + + @override + PinWidgetState createState() => PinWidgetState(); +} + +class PinWidgetState extends State { + int pinCount = 1; + + @override + Widget build(BuildContext context) { + bool submittedPinMatches = false; + + return CustomPinPut( + fieldsCount: pinCount, + isRandom: widget.isRandom, + useNativeKeyboard: false, + eachFieldHeight: 12, + eachFieldWidth: 12, + textStyle: STextStyles.label(context).copyWith( + fontSize: 1, + ), + obscureText: "", + onPinLengthChanged: (newLength) { + setState(() { + pinCount = newLength; + }); + }, + onSubmit: widget.onSubmit, + controller: widget.controller, + pinAnimationType: widget.pinAnimation, + ); + } +} + void main() { group("CustomPinPut tests, non-random PIN", () { testWidgets("CustomPinPut with 4 fields builds correctly, non-random PIN", (tester) async { - const pinPut = CustomPinPut( - fieldsCount: 4, - isRandom: false, - ); - await tester.pumpWidget( MaterialApp( theme: ThemeData( @@ -30,13 +73,16 @@ void main() { ], ), home: const Material( - child: pinPut, + child: PinWidget( + pinAnimation: PinAnimationType.none, + isRandom: false, + ), ), ), ); // expects 5 here. Four + the actual text field text - expect(find.text(""), findsNWidgets(5)); + expect(find.text(""), findsNWidgets(1)); expect(find.byType(PinKeyboard), findsOneWidget); expect(find.byType(BackspaceKey), findsOneWidget); expect(find.byType(NumberKey), findsNWidgets(10)); @@ -45,15 +91,6 @@ void main() { testWidgets("CustomPinPut entering a pin successfully, non-random PIN", (tester) async { bool submittedPinMatches = false; - final pinPut = CustomPinPut( - fieldsCount: 4, - onSubmit: (pin) { - submittedPinMatches = pin == "1234"; - print("pin entered: $pin"); - }, - useNativeKeyboard: false, - isRandom: false, - ); await tester.pumpWidget( MaterialApp( @@ -68,7 +105,14 @@ void main() { ], ), home: Material( - child: pinPut, + child: PinWidget( + pinAnimation: PinAnimationType.none, + isRandom: false, + onSubmit: (pin) { + submittedPinMatches = pin == "1234"; + print("pin entered: $pin"); + }, + ), ), ), ); @@ -99,12 +143,6 @@ void main() { testWidgets("CustomPinPut pin enter fade animation, non-random PIN", (tester) async { final controller = TextEditingController(); - final pinPut = CustomPinPut( - fieldsCount: 4, - pinAnimationType: PinAnimationType.fade, - controller: controller, - isRandom: false, - ); await tester.pumpWidget( MaterialApp( @@ -119,7 +157,11 @@ void main() { ], ), home: Material( - child: pinPut, + child: PinWidget( + pinAnimation: PinAnimationType.none, + isRandom: false, + controller: controller, + ), ), ), ); @@ -137,12 +179,6 @@ void main() { testWidgets("CustomPinPut pin enter scale animation, non-random PIN", (tester) async { final controller = TextEditingController(); - final pinPut = CustomPinPut( - fieldsCount: 4, - pinAnimationType: PinAnimationType.scale, - controller: controller, - isRandom: false, - ); await tester.pumpWidget( MaterialApp( @@ -157,7 +193,11 @@ void main() { ], ), home: Material( - child: pinPut, + child: PinWidget( + pinAnimation: PinAnimationType.scale, + isRandom: false, + controller: controller, + ), ), ), ); @@ -175,12 +215,6 @@ void main() { testWidgets("CustomPinPut pin enter rotate animation, non-random PIN", (tester) async { final controller = TextEditingController(); - final pinPut = CustomPinPut( - fieldsCount: 4, - pinAnimationType: PinAnimationType.rotation, - controller: controller, - isRandom: false, - ); await tester.pumpWidget( MaterialApp( @@ -195,7 +229,11 @@ void main() { ], ), home: Material( - child: pinPut, + child: PinWidget( + pinAnimation: PinAnimationType.rotation, + isRandom: false, + controller: controller, + ), ), ), ); @@ -256,11 +294,6 @@ void main() { group("CustomPinPut tests, with random PIN", () { testWidgets("CustomPinPut with 4 fields builds correctly, with random PIN", (tester) async { - const pinPut = CustomPinPut( - fieldsCount: 4, - isRandom: true, - ); - await tester.pumpWidget( MaterialApp( theme: ThemeData( @@ -274,81 +307,76 @@ void main() { ], ), home: const Material( - child: pinPut, + child: PinWidget( + pinAnimation: PinAnimationType.none, + isRandom: true, + ), ), ), ); // expects 5 here. Four + the actual text field text - expect(find.text(""), findsNWidgets(5)); + expect(find.text(""), findsNWidgets(1)); expect(find.byType(PinKeyboard), findsOneWidget); expect(find.byType(BackspaceKey), findsOneWidget); expect(find.byType(NumberKey), findsNWidgets(10)); }); - testWidgets("CustomPinPut entering a pin successfully, with random PIN", - (tester) async { - bool submittedPinMatches = false; - final pinPut = CustomPinPut( - fieldsCount: 4, - onSubmit: (pin) { - submittedPinMatches = pin == "1234"; - print("pin entered: $pin"); - }, - useNativeKeyboard: false, - isRandom: true, - ); - - await tester.pumpWidget( - MaterialApp( - theme: ThemeData( - extensions: [ - StackColors.fromStackColorTheme( - StackTheme.fromJson( - json: lightThemeJsonMap, - applicationThemesDirectoryPath: "test", - ), - ), - ], - ), - home: Material( - child: pinPut, - ), - ), - ); - - await tester.tap(find.byWidgetPredicate( - (widget) => widget is NumberKey && widget.number == "1")); - await tester.pumpAndSettle(); - await tester.tap(find.byWidgetPredicate( - (widget) => widget is NumberKey && widget.number == "2")); - await tester.pumpAndSettle(); - await tester.tap(find.byWidgetPredicate( - (widget) => widget is NumberKey && widget.number == "6")); - await tester.pumpAndSettle(); - await tester.tap(find.byType(BackspaceKey)); - await tester.pumpAndSettle(); - await tester.tap(find.byWidgetPredicate( - (widget) => widget is NumberKey && widget.number == "3")); - await tester.pumpAndSettle(); - await tester.tap(find.byWidgetPredicate( - (widget) => widget is NumberKey && widget.number == "4")); - await tester.pumpAndSettle(); - await tester.tap(find.byType(SubmitKey)); - await tester.pumpAndSettle(); - - expect(submittedPinMatches, true); - }); + // testWidgets("CustomPinPut entering a pin successfully, with random PIN", + // (tester) async { + // bool submittedPinMatches = false; + // + // await tester.pumpWidget( + // MaterialApp( + // theme: ThemeData( + // extensions: [ + // StackColors.fromStackColorTheme( + // StackTheme.fromJson( + // json: lightThemeJsonMap, + // applicationThemesDirectoryPath: "test", + // ), + // ), + // ], + // ), + // home: Material( + // child: PinWidget( + // pinAnimation: PinAnimationType.none, + // isRandom: true, + // onSubmit: (pin) { + // submittedPinMatches = pin == "1234"; + // print("pin entered: $pin"); + // }, + // ), + // ), + // ), + // ); + // + // await tester.tap(find.byWidgetPredicate( + // (widget) => widget is NumberKey && widget.number == "1")); + // await tester.pumpAndSettle(); + // await tester.tap(find.byWidgetPredicate( + // (widget) => widget is NumberKey && widget.number == "2")); + // await tester.pumpAndSettle(); + // await tester.tap(find.byWidgetPredicate( + // (widget) => widget is NumberKey && widget.number == "6")); + // await tester.pumpAndSettle(); + // await tester.tap(find.byType(BackspaceKey)); + // await tester.pumpAndSettle(); + // await tester.tap(find.byWidgetPredicate( + // (widget) => widget is NumberKey && widget.number == "3")); + // await tester.pumpAndSettle(); + // await tester.tap(find.byWidgetPredicate( + // (widget) => widget is NumberKey && widget.number == "4")); + // await tester.pumpAndSettle(); + // await tester.tap(find.byType(SubmitKey)); + // await tester.pumpAndSettle(); + // + // expect(submittedPinMatches, true); + // }); testWidgets("CustomPinPut pin enter fade animation, with random PIN", (tester) async { final controller = TextEditingController(); - final pinPut = CustomPinPut( - fieldsCount: 4, - pinAnimationType: PinAnimationType.fade, - controller: controller, - isRandom: true, - ); await tester.pumpWidget( MaterialApp( @@ -363,7 +391,11 @@ void main() { ], ), home: Material( - child: pinPut, + child: PinWidget( + pinAnimation: PinAnimationType.fade, + isRandom: true, + controller: controller, + ), ), ), ); @@ -401,7 +433,11 @@ void main() { ], ), home: Material( - child: pinPut, + child: PinWidget( + isRandom: true, + controller: controller, + pinAnimation: PinAnimationType.scale, + ), ), ), ); @@ -439,7 +475,11 @@ void main() { ], ), home: Material( - child: pinPut, + child: PinWidget( + isRandom: true, + controller: controller, + pinAnimation: PinAnimationType.rotation, + ), ), ), ); diff --git a/test/widget_tests/transaction_card_test.mocks.dart b/test/widget_tests/transaction_card_test.mocks.dart index e35ea6a6d..67081a304 100644 --- a/test/widget_tests/transaction_card_test.mocks.dart +++ b/test/widget_tests/transaction_card_test.mocks.dart @@ -1647,28 +1647,6 @@ class MockFiroWallet extends _i1.Mock implements _i22.FiroWallet { returnValueForMissingStub: _i18.Future.value(), ) as _i18.Future); @override - _i18.Future fillAddresses( - String? suppliedMnemonic, - String? mnemonicPassphrase, { - int? perBatch = 50, - int? numberOfThreads = 4, - }) => - (super.noSuchMethod( - Invocation.method( - #fillAddresses, - [ - suppliedMnemonic, - mnemonicPassphrase, - ], - { - #perBatch: perBatch, - #numberOfThreads: numberOfThreads, - }, - ), - returnValue: _i18.Future.value(), - returnValueForMissingStub: _i18.Future.value(), - ) as _i18.Future); - @override _i18.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, @@ -1717,6 +1695,16 @@ class MockFiroWallet extends _i1.Mock implements _i22.FiroWallet { returnValue: _i18.Future>.value({}), ) as _i18.Future>); @override + _i18.Future getTransactionCacheEarly(List? allAddresses) => + (super.noSuchMethod( + Invocation.method( + #getTransactionCacheEarly, + [allAddresses], + ), + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); + @override _i18.Future>> fetchAnonymitySets() => (super.noSuchMethod( Invocation.method( @@ -1735,13 +1723,13 @@ class MockFiroWallet extends _i1.Mock implements _i22.FiroWallet { returnValue: _i18.Future.value(0), ) as _i18.Future); @override - _i18.Future> getUsedCoinSerials() => (super.noSuchMethod( + _i18.Future> getUsedCoinSerials() => (super.noSuchMethod( Invocation.method( #getUsedCoinSerials, [], ), - returnValue: _i18.Future>.value([]), - ) as _i18.Future>); + returnValue: _i18.Future>.value([]), + ) as _i18.Future>); @override _i18.Future exit() => (super.noSuchMethod( Invocation.method(