diff --git a/lib/electrumx_rpc/electrumx_client.dart b/lib/electrumx_rpc/electrumx_client.dart index 2691adf5a..9df7ed791 100644 --- a/lib/electrumx_rpc/electrumx_client.dart +++ b/lib/electrumx_rpc/electrumx_client.dart @@ -93,11 +93,8 @@ class ElectrumXClient { // StreamChannel<dynamic>? get electrumAdapterChannel => _electrumAdapterChannel; StreamChannel<dynamic>? _electrumAdapterChannel; - ElectrumClient? getElectrumAdapter() => - ClientManager.sharedInstance.getClient( - cryptoCurrency: cryptoCurrency, - netType: netType, - ); + ElectrumClient? getElectrumAdapter() => ClientManager.sharedInstance + .getClient(cryptoCurrency: cryptoCurrency, netType: netType); late Prefs _prefs; late TorService _torService; @@ -109,12 +106,10 @@ class ElectrumXClient { // add finalizer to cancel stream subscription when all references to an // instance of ElectrumX becomes inaccessible - static final Finalizer<ElectrumXClient> _finalizer = Finalizer( - (p0) { - p0._torPreferenceListener?.cancel(); - p0._torStatusListener?.cancel(); - }, - ); + static final Finalizer<ElectrumXClient> _finalizer = Finalizer((p0) { + p0._torPreferenceListener?.cancel(); + p0._torStatusListener?.cancel(); + }); StreamSubscription<TorPreferenceChangedEvent>? _torPreferenceListener; StreamSubscription<TorConnectionStatusChangedEvent>? _torStatusListener; @@ -129,8 +124,9 @@ class ElectrumXClient { required this.netType, required List<ElectrumXNode> failovers, required this.cryptoCurrency, - this.connectionTimeoutForSpecialCaseJsonRPCClients = - const Duration(seconds: 60), + this.connectionTimeoutForSpecialCaseJsonRPCClients = const Duration( + seconds: 60, + ), TorService? torService, EventBus? globalEventBusForTesting, }) { @@ -144,46 +140,45 @@ class ElectrumXClient { final bus = globalEventBusForTesting ?? GlobalEventBus.instance; // Listen for tor status changes. - _torStatusListener = bus.on<TorConnectionStatusChangedEvent>().listen( - (event) async { - switch (event.newStatus) { - case TorConnectionStatus.connecting: - await _torConnectingLock.acquire(); - _requireMutex = true; - break; + _torStatusListener = bus.on<TorConnectionStatusChangedEvent>().listen(( + event, + ) async { + switch (event.newStatus) { + case TorConnectionStatus.connecting: + await _torConnectingLock.acquire(); + _requireMutex = true; + break; - case TorConnectionStatus.connected: - case TorConnectionStatus.disconnected: - if (_torConnectingLock.isLocked) { - _torConnectingLock.release(); - } - _requireMutex = false; - break; - } - }, - ); + case TorConnectionStatus.connected: + case TorConnectionStatus.disconnected: + if (_torConnectingLock.isLocked) { + _torConnectingLock.release(); + } + _requireMutex = false; + break; + } + }); // Listen for tor preference changes. - _torPreferenceListener = bus.on<TorPreferenceChangedEvent>().listen( - (event) async { - // not sure if we need to do anything specific here - // switch (event.status) { - // case TorStatus.enabled: - // case TorStatus.disabled: - // } + _torPreferenceListener = bus.on<TorPreferenceChangedEvent>().listen(( + event, + ) async { + // not sure if we need to do anything specific here + // switch (event.status) { + // case TorStatus.enabled: + // case TorStatus.disabled: + // } - // setting to null should force the creation of a new json rpc client - // on the next request sent through this electrumx instance - _electrumAdapterChannel = null; - await (await ClientManager.sharedInstance - .remove(cryptoCurrency: cryptoCurrency)) - .$1 - ?.close(); + // setting to null should force the creation of a new json rpc client + // on the next request sent through this electrumx instance + _electrumAdapterChannel = null; + await (await ClientManager.sharedInstance.remove( + cryptoCurrency: cryptoCurrency, + )).$1?.close(); - // Also close any chain height services that are currently open. - // await ChainHeightServiceManager.dispose(); - }, - ); + // Also close any chain height services that are currently open. + // await ChainHeightServiceManager.dispose(); + }); } factory ElectrumXClient.from({ @@ -252,14 +247,16 @@ class ElectrumXClient { if (netType == TorPlainNetworkOption.clear) { _electrumAdapterChannel = null; - await ClientManager.sharedInstance - .remove(cryptoCurrency: cryptoCurrency); + await ClientManager.sharedInstance.remove( + cryptoCurrency: cryptoCurrency, + ); } } else { if (netType == TorPlainNetworkOption.tor) { _electrumAdapterChannel = null; - await ClientManager.sharedInstance - .remove(cryptoCurrency: cryptoCurrency); + await ClientManager.sharedInstance.remove( + cryptoCurrency: cryptoCurrency, + ); } } @@ -338,24 +335,22 @@ class ElectrumXClient { } if (_requireMutex) { - await _torConnectingLock - .protect(() async => await checkElectrumAdapter()); + await _torConnectingLock.protect( + () async => await checkElectrumAdapter(), + ); } else { await checkElectrumAdapter(); } try { - final response = await getElectrumAdapter()!.request( - command, - args, - ); + final response = await getElectrumAdapter()!.request(command, args); if (response is Map && response.keys.contains("error") && response["error"] != null) { - if (response["error"] - .toString() - .contains("No such mempool or blockchain transaction")) { + if (response["error"].toString().contains( + "No such mempool or blockchain transaction", + )) { throw NoSuchTransactionException( "No such mempool or blockchain transaction", args.first.toString(), @@ -399,11 +394,7 @@ class ElectrumXClient { } } catch (e, s) { final errorMessage = e.toString(); - Logging.instance.w( - "$host $e", - error: e, - stackTrace: s, - ); + Logging.instance.w("$host $e", error: e, stackTrace: s); if (errorMessage.contains("JSON-RPC error")) { currentFailoverIndex = _failovers.length; } @@ -437,8 +428,9 @@ class ElectrumXClient { } if (_requireMutex) { - await _torConnectingLock - .protect(() async => await checkElectrumAdapter()); + await _torConnectingLock.protect( + () async => await checkElectrumAdapter(), + ); } else { await checkElectrumAdapter(); } @@ -531,18 +523,19 @@ class ElectrumXClient { // electrum_adapter returns the result of the request, request() has been // updated to return a bool on a server.ping command as a special case. return await request( - requestID: requestID, - command: 'server.ping', - requestTimeout: const Duration(seconds: 30), - retries: retryCount, - ).timeout( - const Duration(seconds: 30), - onTimeout: () { - Logging.instance.d( - "ElectrumxClient.ping timed out with retryCount=$retryCount, host=$_host", - ); - }, - ) as bool; + requestID: requestID, + command: 'server.ping', + requestTimeout: const Duration(seconds: 30), + retries: retryCount, + ).timeout( + const Duration(seconds: 30), + onTimeout: () { + Logging.instance.d( + "ElectrumxClient.ping timed out with retryCount=$retryCount, host=$_host", + ); + }, + ) + as bool; } catch (e) { rethrow; } @@ -609,9 +602,7 @@ class ElectrumXClient { final response = await request( requestID: requestID, command: 'blockchain.transaction.broadcast', - args: [ - rawTx, - ], + args: [rawTx], ); return response as String; } catch (e) { @@ -636,9 +627,7 @@ class ElectrumXClient { final response = await request( requestID: requestID, command: 'blockchain.scripthash.get_balance', - args: [ - scripthash, - ], + args: [scripthash], ); return Map<String, dynamic>.from(response as Map); } catch (e) { @@ -673,9 +662,7 @@ class ElectrumXClient { requestID: requestID, command: 'blockchain.scripthash.get_history', requestTimeout: const Duration(minutes: 5), - args: [ - scripthash, - ], + args: [scripthash], ); result = response; retryCount--; @@ -731,9 +718,7 @@ class ElectrumXClient { final response = await request( requestID: requestID, command: 'blockchain.scripthash.listunspent', - args: [ - scripthash, - ], + args: [scripthash], ); return List<Map<String, dynamic>>.from(response as List); } catch (e) { @@ -826,14 +811,10 @@ class ElectrumXClient { bool verbose = true, String? requestID, }) async { - Logging.instance.d( - "attempting to fetch blockchain.transaction.get...", - ); + Logging.instance.d("attempting to fetch blockchain.transaction.get..."); await checkElectrumAdapter(); final dynamic response = await getElectrumAdapter()!.getTransaction(txHash); - Logging.instance.d( - "Fetching blockchain.transaction.get finished", - ); + Logging.instance.d("Fetching blockchain.transaction.get finished"); if (!verbose) { return {"rawtx": response as String}; @@ -861,16 +842,12 @@ class ElectrumXClient { String blockhash = "", String? requestID, }) async { - Logging.instance.d( - "attempting to fetch lelantus.getanonymityset...", - ); + Logging.instance.d("attempting to fetch lelantus.getanonymityset..."); await checkElectrumAdapter(); - final Map<String, dynamic> response = - await (getElectrumAdapter() as FiroElectrumClient) - .getLelantusAnonymitySet(groupId: groupId, blockHash: blockhash); - Logging.instance.d( - "Fetching lelantus.getanonymityset finished", - ); + final Map<String, dynamic> response = await (getElectrumAdapter() + as FiroElectrumClient) + .getLelantusAnonymitySet(groupId: groupId, blockHash: blockhash); + Logging.instance.d("Fetching lelantus.getanonymityset finished"); return response; } @@ -882,15 +859,11 @@ class ElectrumXClient { dynamic mints, String? requestID, }) async { - Logging.instance.d( - "attempting to fetch lelantus.getmintmetadata...", - ); + Logging.instance.d("attempting to fetch lelantus.getmintmetadata..."); await checkElectrumAdapter(); final dynamic response = await (getElectrumAdapter() as FiroElectrumClient) .getLelantusMintData(mints: mints); - Logging.instance.d( - "Fetching lelantus.getmintmetadata finished", - ); + Logging.instance.d("Fetching lelantus.getmintmetadata finished"); return response; } @@ -900,9 +873,7 @@ class ElectrumXClient { String? requestID, required int startNumber, }) async { - Logging.instance.d( - "attempting to fetch lelantus.getusedcoinserials...", - ); + Logging.instance.d("attempting to fetch lelantus.getusedcoinserials..."); await checkElectrumAdapter(); int retryCount = 3; @@ -912,9 +883,7 @@ class ElectrumXClient { response = await (getElectrumAdapter() as FiroElectrumClient) .getLelantusUsedCoinSerials(startNumber: startNumber); // TODO add 2 minute timeout. - Logging.instance.d( - "Fetching lelantus.getusedcoinserials finished", - ); + Logging.instance.d("Fetching lelantus.getusedcoinserials finished"); retryCount--; } @@ -926,15 +895,11 @@ class ElectrumXClient { /// /// ex: 1 Future<int> getLelantusLatestCoinId({String? requestID}) async { - Logging.instance.d( - "attempting to fetch lelantus.getlatestcoinid...", - ); + Logging.instance.d("attempting to fetch lelantus.getlatestcoinid..."); await checkElectrumAdapter(); final int response = await (getElectrumAdapter() as FiroElectrumClient).getLatestCoinId(); - Logging.instance.d( - "Fetching lelantus.getlatestcoinid finished", - ); + Logging.instance.d("Fetching lelantus.getlatestcoinid finished"); return response; } @@ -961,12 +926,12 @@ class ElectrumXClient { try { final start = DateTime.now(); await checkElectrumAdapter(); - final Map<String, dynamic> response = - await (getElectrumAdapter() as FiroElectrumClient) - .getSparkAnonymitySet( - coinGroupId: coinGroupId, - startBlockHash: startBlockHash, - ); + final Map<String, dynamic> response = await (getElectrumAdapter() + as FiroElectrumClient) + .getSparkAnonymitySet( + coinGroupId: coinGroupId, + startBlockHash: startBlockHash, + ); Logging.instance.d( "Finished ElectrumXClient.getSparkAnonymitySet(coinGroupId" "=$coinGroupId, startBlockHash=$startBlockHash). " @@ -1053,34 +1018,23 @@ class ElectrumXClient { /// Returns the latest Spark set id /// /// ex: 1 - Future<int> getSparkLatestCoinId({ - String? requestID, - }) async { + Future<int> getSparkLatestCoinId({String? requestID}) async { try { - Logging.instance.d( - "attempting to fetch spark.getsparklatestcoinid...", - ); + Logging.instance.d("attempting to fetch spark.getsparklatestcoinid..."); await checkElectrumAdapter(); - final int response = await (getElectrumAdapter() as FiroElectrumClient) - .getSparkLatestCoinId(); - Logging.instance.d( - "Fetching spark.getsparklatestcoinid finished", - ); + final int response = + await (getElectrumAdapter() as FiroElectrumClient) + .getSparkLatestCoinId(); + Logging.instance.d("Fetching spark.getsparklatestcoinid finished"); return response; } catch (e, s) { - Logging.instance.e( - e, - error: e, - stackTrace: s, - ); + Logging.instance.e(e, error: e, stackTrace: s); rethrow; } } /// Returns the txids of the current transactions found in the mempool - Future<Set<String>> getMempoolTxids({ - String? requestID, - }) async { + Future<Set<String>> getMempoolTxids({String? requestID}) async { try { final start = DateTime.now(); final response = await request( @@ -1088,9 +1042,10 @@ class ElectrumXClient { command: "spark.getmempoolsparktxids", ); - final txids = List<String>.from(response as List) - .map((e) => e.toHexReversedFromBase64) - .toSet(); + final txids = + List<String>.from( + response as List, + ).map((e) => e.toHexReversedFromBase64).toSet(); Logging.instance.d( "Finished ElectrumXClient.getMempoolTxids(). " @@ -1099,11 +1054,7 @@ class ElectrumXClient { return txids; } catch (e, s) { - Logging.instance.e( - e, - error: e, - stackTrace: s, - ); + Logging.instance.e(e, error: e, stackTrace: s); rethrow; } } @@ -1119,9 +1070,7 @@ class ElectrumXClient { requestID: requestID, command: "spark.getmempoolsparktxs", args: [ - { - "txids": txids, - }, + {"txids": txids}, ], ); @@ -1131,8 +1080,9 @@ class ElectrumXClient { result.add( SparkMempoolData( txid: entry.key, - serialContext: - List<String>.from(entry.value["serial_context"] as List), + serialContext: List<String>.from( + entry.value["serial_context"] as List, + ), // the space after lTags is required lol lTags: List<String>.from(entry.value["lTags "] as List), coins: List<String>.from(entry.value["coins"] as List), @@ -1163,9 +1113,7 @@ class ElectrumXClient { final response = await request( requestID: requestID, command: "spark.getusedcoinstagstxhashes", - args: [ - "$startNumber", - ], + args: ["$startNumber"], ); final map = Map<String, dynamic>.from(response as Map); @@ -1179,14 +1127,34 @@ class ElectrumXClient { return tags; } catch (e, s) { - Logging.instance.e( - e, - error: e, - stackTrace: s, - ); + Logging.instance.e(e, error: e, stackTrace: s); rethrow; } } + + Future<List<String>> getSparkNames({String? requestID}) async { + try { + final start = DateTime.now(); + await checkElectrumAdapter(); + const command = "spark.getsparknames"; + Logging.instance.d( + "[${getElectrumAdapter()?.host}] => attempting to fetch $command...", + ); + + final response = await request(requestID: requestID, command: command); + + Logging.instance.d( + "Finished ElectrumXClient.getSparkNames(). " + "coins.length: ${(response as List).length}" + "Duration=${DateTime.now().difference(start)}", + ); + + return response.cast(); + } catch (e) { + rethrow; + } + } + // ======== New Paginated Endpoints ========================================== Future<SparkAnonymitySetMeta> getSparkAnonymitySetMeta({ @@ -1203,9 +1171,7 @@ class ElectrumXClient { final response = await request( requestID: requestID, command: command, - args: [ - "$coinGroupId", - ], + args: ["$coinGroupId"], ); final map = Map<String, dynamic>.from(response as Map); @@ -1227,11 +1193,7 @@ class ElectrumXClient { return result; } catch (e, s) { - Logging.instance.e( - e, - error: e, - stackTrace: s, - ); + Logging.instance.e(e, error: e, stackTrace: s); rethrow; } } @@ -1250,12 +1212,7 @@ class ElectrumXClient { final response = await request( requestID: requestID, command: command, - args: [ - "$coinGroupId", - latestBlock, - "$startIndex", - "$endIndex", - ], + args: ["$coinGroupId", latestBlock, "$startIndex", "$endIndex"], ); final map = Map<String, dynamic>.from(response as Map); @@ -1275,11 +1232,7 @@ class ElectrumXClient { return result; } catch (e, s) { - Logging.instance.e( - e, - error: e, - stackTrace: s, - ); + Logging.instance.e(e, error: e, stackTrace: s); rethrow; } } @@ -1296,10 +1249,7 @@ class ElectrumXClient { final response = await request( requestID: requestID, command: "blockchain.checkifmncollateral", - args: [ - txid, - index.toString(), - ], + args: [txid, index.toString()], ); Logging.instance.d( @@ -1310,11 +1260,7 @@ class ElectrumXClient { return response as bool; } catch (e, s) { - Logging.instance.e( - e, - error: e, - stackTrace: s, - ); + Logging.instance.e(e, error: e, stackTrace: s); rethrow; } } @@ -1344,9 +1290,7 @@ class ElectrumXClient { final response = await request( requestID: requestID, command: 'blockchain.estimatefee', - args: [ - blocks, - ], + args: [blocks], ); try { if (response == null || @@ -1371,7 +1315,8 @@ class ElectrumXClient { } return Decimal.parse(response.toString()); } catch (e, s) { - final String msg = "Error parsing fee rate. Response: $response" + final String msg = + "Error parsing fee rate. Response: $response" "\nResult: $response\nError: $e\nStack trace: $s"; Logging.instance.e(msg, error: e, stackTrace: s); throw Exception(msg); diff --git a/lib/pages/spark_names/buy_spark_name_view.dart b/lib/pages/spark_names/buy_spark_name_view.dart new file mode 100644 index 000000000..0371ec485 --- /dev/null +++ b/lib/pages/spark_names/buy_spark_name_view.dart @@ -0,0 +1,423 @@ +import 'dart:async'; + +import 'package:decimal/decimal.dart'; +import 'package:dropdown_button2/dropdown_button2.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_libsparkmobile/flutter_libsparkmobile.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; + +import '../../../providers/providers.dart'; +import '../../../utilities/amount/amount.dart'; +import '../../../utilities/logger.dart'; +import '../../../utilities/util.dart'; +import '../../../wallets/models/tx_data.dart'; +import '../../../widgets/desktop/primary_button.dart'; +import '../../../widgets/stack_dialog.dart'; +import '../../themes/stack_colors.dart'; +import '../../utilities/amount/amount_formatter.dart'; +import '../../utilities/assets.dart'; +import '../../utilities/constants.dart'; +import '../../utilities/show_loading.dart'; +import '../../utilities/text_styles.dart'; +import '../../wallets/isar/providers/wallet_info_provider.dart'; +import '../../wallets/wallet/wallet_mixin_interfaces/spark_interface.dart'; +import '../../widgets/background.dart'; +import '../../widgets/conditional_parent.dart'; +import '../../widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../widgets/dialogs/s_dialog.dart'; +import '../../widgets/rounded_white_container.dart'; +import 'confirm_spark_name_transaction_view.dart'; + +class BuySparkNameView extends ConsumerStatefulWidget { + const BuySparkNameView({ + super.key, + required this.walletId, + required this.name, + }); + + final String walletId; + final String name; + + static const routeName = "/buySparkNameView"; + + @override + ConsumerState<BuySparkNameView> createState() => _BuySparkNameViewState(); +} + +class _BuySparkNameViewState extends ConsumerState<BuySparkNameView> { + final additionalInfoController = TextEditingController(); + + int _years = 1; + + Future<TxData> _preRegFuture() async { + final wallet = + ref.read(pWallets).getWallet(widget.walletId) as SparkInterface; + final myAddress = await wallet.getCurrentReceivingSparkAddress(); + if (myAddress == null) { + throw Exception("No spark address found"); + } + + final txData = await wallet.prepareSparkNameTransaction( + name: widget.name, + address: myAddress.value, + years: _years, + additionalInfo: additionalInfoController.text, + ); + return txData; + } + + bool _preRegLock = false; + Future<void> _prepareNameTx() async { + if (_preRegLock) return; + _preRegLock = true; + try { + final txData = + (await showLoading( + whileFuture: _preRegFuture(), + context: context, + message: "Preparing transaction...", + onException: (e) { + throw e; + }, + ))!; + + if (mounted) { + if (Util.isDesktop) { + await showDialog<void>( + context: context, + builder: + (context) => SDialog( + child: SizedBox( + width: 580, + child: ConfirmSparkNameTransactionView( + txData: txData, + walletId: widget.walletId, + ), + ), + ), + ); + } else { + await Navigator.of(context).pushNamed( + ConfirmSparkNameTransactionView.routeName, + arguments: (txData, widget.walletId), + ); + } + } + } catch (e, s) { + Logging.instance.e("_prepareNameTx failed", error: e, stackTrace: s); + + if (mounted) { + String err = e.toString(); + if (err.startsWith("Exception: ")) { + err = err.replaceFirst("Exception: ", ""); + } + + await showDialog<void>( + context: context, + builder: + (_) => StackOkDialog( + title: "Error", + message: err, + desktopPopRootNavigator: Util.isDesktop, + maxWidth: Util.isDesktop ? 600 : null, + ), + ); + } + } finally { + _preRegLock = false; + } + } + + @override + void dispose() { + additionalInfoController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final coin = ref.watch(pWalletCoin(widget.walletId)); + return ConditionalParent( + condition: !Util.isDesktop, + builder: (child) { + return Background( + child: Scaffold( + backgroundColor: Colors.transparent, + appBar: AppBar( + leading: const AppBarBackButton(), + titleSpacing: 0, + title: Text( + "Buy name", + style: STextStyles.navBarTitle(context), + overflow: TextOverflow.ellipsis, + ), + ), + body: SafeArea( + child: LayoutBuilder( + builder: (ctx, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: child, + ), + ), + ), + ); + }, + ), + ), + ), + ); + }, + child: Column( + crossAxisAlignment: + Util.isDesktop + ? CrossAxisAlignment.start + : CrossAxisAlignment.stretch, + children: [ + if (!Util.isDesktop) + Text( + "Buy name", + style: + Util.isDesktop + ? STextStyles.desktopH3(context) + : STextStyles.pageTitleH2(context), + ), + SizedBox(height: Util.isDesktop ? 24 : 16), + // Row( + // mainAxisAlignment: + // Util.isDesktop + // ? MainAxisAlignment.center + // : MainAxisAlignment.start, + // children: [ + // Text( + // "Name registration will take approximately 2 to 4 hours.", + // style: + // Util.isDesktop + // ? STextStyles.w500_14(context).copyWith( + // color: + // Theme.of( + // context, + // ).extension<StackColors>()!.textDark3, + // ) + // : STextStyles.w500_12(context).copyWith( + // color: + // Theme.of( + // context, + // ).extension<StackColors>()!.textDark3, + // ), + // ), + // ], + // ), + // SizedBox(height: Util.isDesktop ? 24 : 16), + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Name", + style: + Util.isDesktop + ? STextStyles.w500_14(context).copyWith( + color: + Theme.of( + context, + ).extension<StackColors>()!.infoItemLabel, + ) + : STextStyles.w500_12(context).copyWith( + color: + Theme.of( + context, + ).extension<StackColors>()!.infoItemLabel, + ), + ), + Text( + widget.name, + style: + Util.isDesktop + ? STextStyles.w500_14(context) + : STextStyles.w500_12(context), + ), + ], + ), + ), + SizedBox(height: Util.isDesktop ? 16 : 8), + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Amount", + style: + Util.isDesktop + ? STextStyles.w500_14(context).copyWith( + color: + Theme.of( + context, + ).extension<StackColors>()!.infoItemLabel, + ) + : STextStyles.w500_12(context).copyWith( + color: + Theme.of( + context, + ).extension<StackColors>()!.infoItemLabel, + ), + ), + Text( + ref + .watch(pAmountFormatter(coin)) + .format( + Amount.fromDecimal( + Decimal.fromInt( + kStandardSparkNamesFee[widget.name.length] * _years, + ), + fractionDigits: coin.fractionDigits, + ), + ), + style: + Util.isDesktop + ? STextStyles.w500_14(context) + : STextStyles.w500_12(context), + ), + ], + ), + ), + SizedBox(height: Util.isDesktop ? 16 : 8), + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Register for", + style: + Util.isDesktop + ? STextStyles.w500_14(context).copyWith( + color: + Theme.of( + context, + ).extension<StackColors>()!.infoItemLabel, + ) + : STextStyles.w500_12(context).copyWith( + color: + Theme.of( + context, + ).extension<StackColors>()!.infoItemLabel, + ), + ), + SizedBox( + width: Util.isDesktop ? 180 : 140, + child: DropdownButtonHideUnderline( + child: DropdownButton2<int>( + value: _years, + items: [ + ...List.generate(10, (i) => i + 1).map( + (e) => DropdownMenuItem( + value: e, + child: Text( + "$e years", + style: STextStyles.w500_14(context), + ), + ), + ), + ], + onChanged: (value) { + if (value is int) { + setState(() { + _years = value; + }); + } + }, + isExpanded: true, + buttonStyleData: ButtonStyleData( + decoration: BoxDecoration( + color: + Theme.of( + context, + ).extension<StackColors>()!.textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + ), + iconStyleData: IconStyleData( + icon: Padding( + padding: const EdgeInsets.only(right: 10), + child: SvgPicture.asset( + Assets.svg.chevronDown, + width: 12, + height: 6, + color: + Theme.of(context) + .extension<StackColors>()! + .textFieldActiveSearchIconRight, + ), + ), + ), + dropdownStyleData: DropdownStyleData( + offset: const Offset(0, -10), + elevation: 0, + maxHeight: 250, + decoration: BoxDecoration( + color: + Theme.of( + context, + ).extension<StackColors>()!.textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + ), + menuItemStyleData: const MenuItemStyleData( + padding: EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + ), + ), + ), + ), + ], + ), + ), + SizedBox(height: Util.isDesktop ? 16 : 8), + RoundedWhiteContainer( + child: ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + controller: additionalInfoController, + textAlignVertical: TextAlignVertical.center, + decoration: InputDecoration( + isDense: true, + contentPadding: const EdgeInsets.all(16), + hintStyle: STextStyles.fieldLabel(context), + hintText: "Additional info", + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + ), + ), + ), + ), + + SizedBox(height: Util.isDesktop ? 24 : 16), + if (!Util.isDesktop) const Spacer(), + PrimaryButton( + label: "Buy", + // width: Util.isDesktop ? 160 : double.infinity, + buttonHeight: Util.isDesktop ? ButtonHeight.l : null, + onPressed: _prepareNameTx, + ), + SizedBox(height: Util.isDesktop ? 32 : 16), + ], + ), + ); + } +} diff --git a/lib/pages/spark_names/confirm_spark_name_transaction_view.dart b/lib/pages/spark_names/confirm_spark_name_transaction_view.dart new file mode 100644 index 000000000..6efd2a8fd --- /dev/null +++ b/lib/pages/spark_names/confirm_spark_name_transaction_view.dart @@ -0,0 +1,975 @@ +/* + * This file is part of Stack Wallet. + * + * Copyright (c) 2023 Cypher Stack + * All Rights Reserved. + * The code is distributed under GPLv3 license, see LICENSE file for details. + * Generated by Cypher Stack on 2023-05-26 + * + */ + +import 'dart:async'; +import 'dart:io'; + +import 'package:decimal/decimal.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; + +import '../../models/isar/models/transaction_note.dart'; +import '../../notifications/show_flush_bar.dart'; +import '../../pages_desktop_specific/coin_control/desktop_coin_control_use_dialog.dart'; +import '../../pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart'; +import '../../providers/db/main_db_provider.dart'; +import '../../providers/providers.dart'; +import '../../route_generator.dart'; +import '../../themes/stack_colors.dart'; +import '../../themes/theme_providers.dart'; +import '../../utilities/amount/amount.dart'; +import '../../utilities/amount/amount_formatter.dart'; +import '../../utilities/constants.dart'; +import '../../utilities/logger.dart'; +import '../../utilities/text_styles.dart'; +import '../../utilities/util.dart'; +import '../../wallets/isar/providers/wallet_info_provider.dart'; +import '../../wallets/models/tx_data.dart'; +import '../../widgets/background.dart'; +import '../../widgets/conditional_parent.dart'; +import '../../widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../widgets/desktop/desktop_dialog.dart'; +import '../../widgets/desktop/desktop_dialog_close_button.dart'; +import '../../widgets/desktop/primary_button.dart'; +import '../../widgets/icon_widgets/x_icon.dart'; +import '../../widgets/rounded_container.dart'; +import '../../widgets/rounded_white_container.dart'; +import '../../widgets/stack_dialog.dart'; +import '../../widgets/stack_text_field.dart'; +import '../../widgets/textfield_icon_button.dart'; +import '../pinpad_views/lock_screen_view.dart'; +import '../send_view/sub_widgets/sending_transaction_dialog.dart'; + +class ConfirmSparkNameTransactionView extends ConsumerStatefulWidget { + const ConfirmSparkNameTransactionView({ + super.key, + required this.txData, + required this.walletId, + }); + + static const String routeName = "/confirmSparkNameTransactionView"; + + final TxData txData; + final String walletId; + + @override + ConsumerState<ConfirmSparkNameTransactionView> createState() => + _ConfirmSparkNameTransactionViewState(); +} + +class _ConfirmSparkNameTransactionViewState + extends ConsumerState<ConfirmSparkNameTransactionView> { + late final String walletId; + late final bool isDesktop; + + late final FocusNode _noteFocusNode; + late final TextEditingController noteController; + + Future<void> _attemptSend() async { + final wallet = ref.read(pWallets).getWallet(walletId); + final coin = wallet.info.coin; + + final sendProgressController = ProgressAndSuccessController(); + + unawaited( + showDialog<dynamic>( + context: context, + useSafeArea: false, + barrierDismissible: false, + builder: (context) { + return SendingTransactionDialog( + coin: coin, + controller: sendProgressController, + ); + }, + ), + ); + + final time = Future<dynamic>.delayed(const Duration(milliseconds: 2500)); + + final List<String> txids = []; + Future<TxData> txDataFuture; + + final note = noteController.text; + + try { + txDataFuture = wallet.confirmSend(txData: widget.txData); + + // await futures in parallel + final futureResults = await Future.wait([txDataFuture, time]); + + final txData = (futureResults.first as TxData); + + sendProgressController.triggerSuccess?.call(); + + // await futures in parallel + await Future.wait([ + // wait for animation + Future<void>.delayed(const Duration(seconds: 5)), + ]); + + txids.add(txData.txid!); + ref.refresh(desktopUseUTXOs); + + // save note + for (final txid in txids) { + await ref + .read(mainDBProvider) + .putTransactionNote( + TransactionNote(walletId: walletId, txid: txid, value: note), + ); + } + + unawaited(wallet.refresh()); + + if (mounted) { + // pop sending dialog + Navigator.of(context, rootNavigator: Util.isDesktop).pop(); + // pop confirm send view + Navigator.of(context, rootNavigator: Util.isDesktop).pop(); + // pop buy popup + Navigator.of(context, rootNavigator: Util.isDesktop).pop(); + } + } catch (e, s) { + const niceError = "Broadcast name transaction failed"; + + Logging.instance.e(niceError, error: e, stackTrace: s); + + if (mounted) { + // pop sending dialog + Navigator.of(context, rootNavigator: Util.isDesktop).pop(); + + await showDialog<void>( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (context) { + if (isDesktop) { + return DesktopDialog( + maxWidth: 450, + child: Padding( + padding: const EdgeInsets.all(32), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(niceError, style: STextStyles.desktopH3(context)), + const SizedBox(height: 24), + Flexible( + child: SingleChildScrollView( + child: SelectableText( + e.toString(), + style: STextStyles.smallMed14(context), + ), + ), + ), + const SizedBox(height: 56), + Row( + children: [ + const Spacer(), + Expanded( + child: PrimaryButton( + buttonHeight: ButtonHeight.l, + label: "Ok", + onPressed: Navigator.of(context).pop, + ), + ), + ], + ), + ], + ), + ), + ); + } else { + return StackDialog( + title: niceError, + message: e.toString(), + rightButton: TextButton( + style: Theme.of(context) + .extension<StackColors>()! + .getSecondaryEnabledButtonStyle(context), + child: Text( + "Ok", + style: STextStyles.button(context).copyWith( + color: + Theme.of( + context, + ).extension<StackColors>()!.accentColorDark, + ), + ), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ); + } + }, + ); + } + } + } + + @override + void initState() { + isDesktop = Util.isDesktop; + walletId = widget.walletId; + _noteFocusNode = FocusNode(); + noteController = TextEditingController(); + noteController.text = widget.txData.note ?? ""; + + super.initState(); + } + + @override + void dispose() { + noteController.dispose(); + + _noteFocusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final coin = ref.watch(pWalletCoin(walletId)); + + final unit = coin.ticker; + + final fee = widget.txData.fee; + final amountWithoutChange = widget.txData.amountWithoutChange!; + + return ConditionalParent( + condition: !isDesktop, + builder: + (child) => Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension<StackColors>()!.background, + appBar: AppBar( + backgroundColor: + Theme.of(context).extension<StackColors>()!.background, + leading: AppBarBackButton( + onPressed: () async { + // if (FocusScope.of(context).hasFocus) { + // FocusScope.of(context).unfocus(); + // await Future<void>.delayed(Duration(milliseconds: 50)); + // } + Navigator.of(context).pop(); + }, + ), + title: Text( + "Confirm transaction", + style: STextStyles.navBarTitle(context), + ), + ), + body: LayoutBuilder( + builder: (builderContext, constraints) { + return Padding( + padding: const EdgeInsets.only( + left: 12, + top: 12, + right: 12, + ), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight - 24, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.all(4), + child: child, + ), + ), + ), + ), + ); + }, + ), + ), + ), + child: ConditionalParent( + condition: isDesktop, + builder: + (child) => Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + AppBarBackButton( + size: 40, + iconSize: 24, + onPressed: + () => + Navigator.of(context, rootNavigator: true).pop(), + ), + Text( + "Confirm transaction", + style: STextStyles.desktopH3(context), + ), + ], + ), + Flexible(child: SingleChildScrollView(child: child)), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: isDesktop ? MainAxisSize.min : MainAxisSize.max, + children: [ + if (!isDesktop) + Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + "Confirm Name transaction", + style: STextStyles.pageTitleH1(context), + ), + const SizedBox(height: 12), + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text("Name", style: STextStyles.smallMed12(context)), + const SizedBox(height: 4), + Text( + widget.txData.sparkNameInfo!.name, + style: STextStyles.itemSubtitle12(context), + ), + ], + ), + ), + const SizedBox(height: 12), + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + "Additional info", + style: STextStyles.smallMed12(context), + ), + const SizedBox(height: 4), + Text( + widget.txData.sparkNameInfo!.additionalInfo, + style: STextStyles.itemSubtitle12(context), + ), + ], + ), + ), + const SizedBox(height: 12), + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + "Recipient", + style: STextStyles.smallMed12(context), + ), + const SizedBox(height: 4), + Text( + widget.txData.recipients!.first.address, + style: STextStyles.itemSubtitle12(context), + ), + ], + ), + ), + const SizedBox(height: 12), + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Registration fee", + style: STextStyles.smallMed12(context), + ), + SelectableText( + ref + .watch(pAmountFormatter(coin)) + .format(amountWithoutChange), + style: STextStyles.itemSubtitle12(context), + textAlign: TextAlign.right, + ), + ], + ), + ), + const SizedBox(height: 12), + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Transaction fee", + style: STextStyles.smallMed12(context), + ), + SelectableText( + ref.watch(pAmountFormatter(coin)).format(fee!), + style: STextStyles.itemSubtitle12(context), + textAlign: TextAlign.right, + ), + ], + ), + ), + if (widget.txData.fee != null && widget.txData.vSize != null) + const SizedBox(height: 12), + if (widget.txData.fee != null && widget.txData.vSize != null) + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "sats/vByte", + style: STextStyles.smallMed12(context), + ), + const SizedBox(height: 4), + SelectableText( + "~${fee.raw.toInt() ~/ widget.txData.vSize!}", + style: STextStyles.itemSubtitle12(context), + ), + ], + ), + ), + if (widget.txData.note != null && + widget.txData.note!.isNotEmpty) + const SizedBox(height: 12), + if (widget.txData.note != null && + widget.txData.note!.isNotEmpty) + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text("Note", style: STextStyles.smallMed12(context)), + const SizedBox(height: 4), + SelectableText( + widget.txData.note!, + style: STextStyles.itemSubtitle12(context), + ), + ], + ), + ), + ], + ), + if (isDesktop) + Padding( + padding: const EdgeInsets.only( + top: 16, + left: 32, + right: 32, + bottom: 50, + ), + child: RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + borderColor: + Theme.of(context).extension<StackColors>()!.background, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Container( + decoration: BoxDecoration( + color: + Theme.of( + context, + ).extension<StackColors>()!.background, + borderRadius: BorderRadius.only( + topLeft: Radius.circular( + Constants.size.circularBorderRadius, + ), + topRight: Radius.circular( + Constants.size.circularBorderRadius, + ), + ), + ), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 22, + ), + child: Row( + children: [ + SvgPicture.file( + File( + ref.watch( + themeProvider.select( + (value) => value.assets.send, + ), + ), + ), + width: 32, + height: 32, + ), + const SizedBox(width: 16), + Text( + "Send $unit Name transaction", + style: STextStyles.desktopTextMedium(context), + ), + ], + ), + ), + ), + Padding( + padding: const EdgeInsets.all(12), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Name", + style: STextStyles.desktopTextExtraExtraSmall( + context, + ), + ), + const SizedBox(height: 2), + SelectableText( + widget.txData.sparkNameInfo!.name, + style: STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of( + context, + ).extension<StackColors>()!.textDark, + ), + ), + ], + ), + ), + Container( + height: 1, + color: + Theme.of( + context, + ).extension<StackColors>()!.background, + ), + Padding( + padding: const EdgeInsets.all(12), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Additional info", + style: STextStyles.desktopTextExtraExtraSmall( + context, + ), + ), + const SizedBox(height: 2), + SelectableText( + widget.txData.sparkNameInfo!.additionalInfo, + style: STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of( + context, + ).extension<StackColors>()!.textDark, + ), + ), + ], + ), + ), + ], + ), + ), + ), + if (isDesktop) + Padding( + padding: const EdgeInsets.only(left: 32, right: 32), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SelectableText( + "Note (optional)", + style: STextStyles.desktopTextExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension<StackColors>()! + .textFieldActiveSearchIconRight, + ), + textAlign: TextAlign.left, + ), + const SizedBox(height: 10), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + minLines: 1, + maxLines: 5, + autocorrect: isDesktop ? false : true, + enableSuggestions: isDesktop ? false : true, + controller: noteController, + focusNode: _noteFocusNode, + style: STextStyles.desktopTextExtraSmall( + context, + ).copyWith( + color: + Theme.of( + context, + ).extension<StackColors>()!.textFieldActiveText, + height: 1.8, + ), + onChanged: (_) => setState(() {}), + decoration: standardInputDecoration( + "Type something...", + _noteFocusNode, + context, + desktopMed: true, + ).copyWith( + contentPadding: const EdgeInsets.only( + left: 16, + top: 11, + bottom: 12, + right: 5, + ), + suffixIcon: + noteController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState( + () => noteController.text = "", + ); + }, + ), + ], + ), + ), + ) + : null, + ), + ), + ), + const SizedBox(height: 20), + ], + ), + ), + + if (isDesktop) + Padding( + padding: const EdgeInsets.only(top: 16, left: 32), + child: Text( + "Registration fee", + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + ), + if (isDesktop) + Padding( + padding: const EdgeInsets.only(top: 10, left: 32, right: 32), + child: RoundedContainer( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 18, + ), + color: + Theme.of( + context, + ).extension<StackColors>()!.textFieldDefaultBG, + child: Builder( + builder: (context) { + final externalCalls = ref.watch( + prefsChangeNotifierProvider.select( + (value) => value.externalCalls, + ), + ); + String fiatAmount = "N/A"; + + if (externalCalls) { + final price = + ref + .read(priceAnd24hChangeNotifierProvider) + .getPrice(coin) + .item1; + if (price > Decimal.zero) { + fiatAmount = (amountWithoutChange.decimal * price) + .toAmount(fractionDigits: 2) + .fiatString( + locale: + ref + .read( + localeServiceChangeNotifierProvider, + ) + .locale, + ); + } + } + + return Row( + children: [ + SelectableText( + ref + .watch(pAmountFormatter(coin)) + .format(amountWithoutChange), + style: STextStyles.itemSubtitle(context), + ), + if (externalCalls) + Text( + " | ", + style: STextStyles.itemSubtitle(context), + ), + if (externalCalls) + SelectableText( + "~$fiatAmount ${ref.watch(prefsChangeNotifierProvider.select((value) => value.currency))}", + style: STextStyles.itemSubtitle(context), + ), + ], + ); + }, + ), + ), + ), + if (isDesktop) + Padding( + padding: const EdgeInsets.only(top: 16, left: 32), + child: Text( + "Recipient", + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + ), + if (isDesktop) + Padding( + padding: const EdgeInsets.only(top: 10, left: 32, right: 32), + child: RoundedContainer( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 18, + ), + color: + Theme.of( + context, + ).extension<StackColors>()!.textFieldDefaultBG, + child: SelectableText( + widget.txData.recipients!.first.address, + style: STextStyles.itemSubtitle(context), + ), + ), + ), + + if (isDesktop) + Padding( + padding: const EdgeInsets.only(top: 16, left: 32), + child: Text( + "Transaction fee", + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + ), + if (isDesktop) + Padding( + padding: const EdgeInsets.only(top: 10, left: 32, right: 32), + child: RoundedContainer( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 18, + ), + color: + Theme.of( + context, + ).extension<StackColors>()!.textFieldDefaultBG, + child: SelectableText( + ref.watch(pAmountFormatter(coin)).format(fee!), + style: STextStyles.itemSubtitle(context), + ), + ), + ), + if (isDesktop && + widget.txData.fee != null && + widget.txData.vSize != null) + Padding( + padding: const EdgeInsets.only(top: 16, left: 32), + child: Text( + "sats/vByte", + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + ), + if (isDesktop && + widget.txData.fee != null && + widget.txData.vSize != null) + Padding( + padding: const EdgeInsets.only(top: 10, left: 32, right: 32), + child: RoundedContainer( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 18, + ), + color: + Theme.of( + context, + ).extension<StackColors>()!.textFieldDefaultBG, + child: SelectableText( + "~${fee!.raw.toInt() ~/ widget.txData.vSize!}", + style: STextStyles.itemSubtitle(context), + ), + ), + ), + if (!isDesktop) const Spacer(), + SizedBox(height: isDesktop ? 23 : 12), + Padding( + padding: + isDesktop + ? const EdgeInsets.symmetric(horizontal: 32) + : const EdgeInsets.all(0), + child: RoundedContainer( + padding: + isDesktop + ? const EdgeInsets.symmetric( + horizontal: 16, + vertical: 18, + ) + : const EdgeInsets.all(12), + color: + Theme.of( + context, + ).extension<StackColors>()!.snackBarBackSuccess, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + isDesktop ? "Total amount to send" : "Total amount", + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension<StackColors>()! + .textConfirmTotalAmount, + ) + : STextStyles.titleBold12(context).copyWith( + color: + Theme.of(context) + .extension<StackColors>()! + .textConfirmTotalAmount, + ), + ), + SelectableText( + ref + .watch(pAmountFormatter(coin)) + .format(amountWithoutChange + fee!), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension<StackColors>()! + .textConfirmTotalAmount, + ) + : STextStyles.itemSubtitle12(context).copyWith( + color: + Theme.of(context) + .extension<StackColors>()! + .textConfirmTotalAmount, + ), + textAlign: TextAlign.right, + ), + ], + ), + ), + ), + SizedBox(height: isDesktop ? 28 : 16), + Padding( + padding: + isDesktop + ? const EdgeInsets.symmetric(horizontal: 32) + : const EdgeInsets.all(0), + child: PrimaryButton( + label: "Send", + buttonHeight: isDesktop ? ButtonHeight.l : null, + onPressed: () async { + final dynamic unlocked; + + if (isDesktop) { + unlocked = await showDialog<bool?>( + context: context, + builder: + (context) => DesktopDialog( + maxWidth: 580, + maxHeight: double.infinity, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [DesktopDialogCloseButton()], + ), + Padding( + padding: const EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, + ), + child: DesktopAuthSend(coin: coin), + ), + ], + ), + ), + ); + } else { + unlocked = await Navigator.push( + context, + RouteGenerator.getRoute( + shouldUseMaterialRoute: + RouteGenerator.useMaterialPageRoute, + builder: + (_) => const LockscreenView( + showBackButton: true, + popOnSuccess: true, + routeOnSuccessArguments: true, + routeOnSuccess: "", + biometricsCancelButtonString: "CANCEL", + biometricsLocalizedReason: + "Authenticate to send transaction", + biometricsAuthenticationTitle: + "Confirm Transaction", + ), + settings: const RouteSettings( + name: "/confirmsendlockscreen", + ), + ), + ); + } + + if (mounted) { + if (unlocked == true) { + unawaited(_attemptSend()); + } else { + if (context.mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: + Util.isDesktop + ? "Invalid passphrase" + : "Invalid PIN", + context: context, + ), + ); + } + } + } + }, + ), + ), + if (isDesktop) const SizedBox(height: 32), + ], + ), + ), + ); + } +} diff --git a/lib/pages/spark_names/spark_names_home_view.dart b/lib/pages/spark_names/spark_names_home_view.dart new file mode 100644 index 000000000..d4dbef2f9 --- /dev/null +++ b/lib/pages/spark_names/spark_names_home_view.dart @@ -0,0 +1,231 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; + +import '../../themes/stack_colors.dart'; +import '../../utilities/assets.dart'; +import '../../utilities/constants.dart'; +import '../../utilities/text_styles.dart'; +import '../../utilities/util.dart'; +import '../../widgets/conditional_parent.dart'; +import '../../widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../widgets/desktop/desktop_app_bar.dart'; +import '../../widgets/desktop/desktop_scaffold.dart'; +import '../../widgets/toggle.dart'; +import 'sub_widgets/buy_spark_name_option_widget.dart'; +import 'sub_widgets/manage_spark_names_option_widget.dart'; + +class SparkNamesHomeView extends ConsumerStatefulWidget { + const SparkNamesHomeView({super.key, required this.walletId}); + + final String walletId; + + static const String routeName = "/sparkNamesHomeView"; + + @override + ConsumerState<SparkNamesHomeView> createState() => + _NamecoinNamesHomeViewState(); +} + +class _NamecoinNamesHomeViewState extends ConsumerState<SparkNamesHomeView> { + bool _onManage = true; + + @override + Widget build(BuildContext context) { + debugPrint("BUILD: $runtimeType"); + final isDesktop = Util.isDesktop; + + return MasterScaffold( + isDesktop: isDesktop, + appBar: + isDesktop + ? DesktopAppBar( + isCompactHeight: true, + background: Theme.of(context).extension<StackColors>()!.popupBG, + leading: Row( + children: [ + Padding( + padding: const EdgeInsets.only(left: 24, right: 20), + child: AppBarIconButton( + size: 32, + color: + Theme.of( + context, + ).extension<StackColors>()!.textFieldDefaultBG, + shadows: const [], + icon: SvgPicture.asset( + Assets.svg.arrowLeft, + width: 18, + height: 18, + color: + Theme.of( + context, + ).extension<StackColors>()!.topNavIconPrimary, + ), + onPressed: Navigator.of(context).pop, + ), + ), + SvgPicture.asset( + Assets.svg.robotHead, + width: 32, + height: 32, + color: + Theme.of(context).extension<StackColors>()!.textDark, + ), + const SizedBox(width: 10), + Text("Names", style: STextStyles.desktopH3(context)), + ], + ), + ) + : AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + titleSpacing: 0, + title: Text( + "Names", + style: STextStyles.navBarTitle(context), + overflow: TextOverflow.ellipsis, + ), + ), + body: ConditionalParent( + condition: !isDesktop, + builder: + (child) => SafeArea( + child: Padding( + padding: const EdgeInsets.only(top: 16, left: 16, right: 16), + child: child, + ), + ), + child: + Util.isDesktop + ? Padding( + padding: const EdgeInsets.only(top: 24, left: 24, right: 24), + child: Row( + children: [ + SizedBox( + width: 460, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Row( + children: [ + Text( + "Register", + style: STextStyles.desktopTextExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension<StackColors>()! + .textFieldActiveSearchIconLeft, + ), + ), + ], + ), + const SizedBox(height: 14), + Flexible( + child: BuySparkNameOptionWidget( + walletId: widget.walletId, + ), + ), + ], + ), + ), + const SizedBox(width: 24), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Row( + children: [ + Text( + "Names", + style: STextStyles.desktopTextExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension<StackColors>()! + .textFieldActiveSearchIconLeft, + ), + ), + ], + ), + const SizedBox(height: 14), + Flexible( + child: SingleChildScrollView( + child: ManageSparkNamesOptionWidget( + walletId: widget.walletId, + ), + ), + ), + ], + ), + ), + ], + ), + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SizedBox( + height: 48, + child: Toggle( + key: UniqueKey(), + onColor: + Theme.of(context).extension<StackColors>()!.popupBG, + offColor: + Theme.of( + context, + ).extension<StackColors>()!.textFieldDefaultBG, + onText: "Register", + offText: "Names", + isOn: !_onManage, + onValueChanged: (value) { + FocusManager.instance.primaryFocus?.unfocus(); + setState(() { + _onManage = !value; + }); + }, + decoration: BoxDecoration( + color: Colors.transparent, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + ), + ), + const SizedBox(height: 16), + Expanded( + child: IndexedStack( + index: _onManage ? 0 : 1, + children: [ + BuySparkNameOptionWidget(walletId: widget.walletId), + LayoutBuilder( + builder: (context, constraints) { + return ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: SingleChildScrollView( + child: IntrinsicHeight( + child: ManageSparkNamesOptionWidget( + walletId: widget.walletId, + ), + ), + ), + ); + }, + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/spark_names/sub_widgets/buy_spark_name_option_widget.dart b/lib/pages/spark_names/sub_widgets/buy_spark_name_option_widget.dart new file mode 100644 index 000000000..b0ad03a38 --- /dev/null +++ b/lib/pages/spark_names/sub_widgets/buy_spark_name_option_widget.dart @@ -0,0 +1,352 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_libsparkmobile/flutter_libsparkmobile.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; + +import '../../../providers/providers.dart'; +import '../../../themes/stack_colors.dart'; +import '../../../utilities/assets.dart'; +import '../../../utilities/constants.dart'; +import '../../../utilities/logger.dart'; +import '../../../utilities/show_loading.dart'; +import '../../../utilities/text_styles.dart'; +import '../../../utilities/util.dart'; +import '../../../wallets/wallet/wallet_mixin_interfaces/spark_interface.dart'; +import '../../../widgets/desktop/desktop_dialog_close_button.dart'; +import '../../../widgets/desktop/primary_button.dart'; +import '../../../widgets/desktop/secondary_button.dart'; +import '../../../widgets/dialogs/s_dialog.dart'; +import '../../../widgets/rounded_white_container.dart'; +import '../../../widgets/stack_dialog.dart'; +import '../buy_spark_name_view.dart'; + +class BuySparkNameOptionWidget extends ConsumerStatefulWidget { + const BuySparkNameOptionWidget({super.key, required this.walletId}); + + final String walletId; + + @override + ConsumerState<BuySparkNameOptionWidget> createState() => + _BuySparkNameWidgetState(); +} + +class _BuySparkNameWidgetState extends ConsumerState<BuySparkNameOptionWidget> { + final _nameController = TextEditingController(); + final _nameFieldFocus = FocusNode(); + + bool _isAvailable = false; + String? _lastLookedUpName; + + Future<bool> _checkIsAvailable(String name) async { + final wallet = + ref.read(pWallets).getWallet(widget.walletId) as SparkInterface; + + final names = await wallet.electrumXClient.getSparkNames(); + + return !names.map((e) => e.toLowerCase()).contains(name.toLowerCase()); + } + + bool _lookupLock = false; + Future<void> _lookup() async { + if (_lookupLock) return; + _lookupLock = true; + try { + _isAvailable = false; + + _lastLookedUpName = _nameController.text; + final result = await showLoading( + whileFuture: _checkIsAvailable(_lastLookedUpName!), + context: context, + message: "Searching...", + onException: (e) => throw e, + rootNavigator: Util.isDesktop, + delay: const Duration(seconds: 2), + ); + + _isAvailable = result == true; + + if (mounted) { + setState(() {}); + } + + Logging.instance.i("LOOKUP RESULT: $result"); + } catch (e, s) { + Logging.instance.e("_lookup failed", error: e, stackTrace: s); + + String? err; + if (e.toString().contains("Contains invalid characters")) { + err = "Contains invalid characters"; + } + + if (mounted) { + await showDialog<void>( + context: context, + builder: + (_) => StackOkDialog( + title: "Spark name lookup failed", + message: err, + desktopPopRootNavigator: Util.isDesktop, + maxWidth: Util.isDesktop ? 600 : null, + ), + ); + } + } finally { + _lookupLock = false; + } + } + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + _nameFieldFocus.requestFocus(); + } + }); + } + + @override + void dispose() { + _nameController.dispose(); + _nameFieldFocus.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final double dotBitBoxLength = Util.isDesktop ? 100 : 74; + return Column( + crossAxisAlignment: + Util.isDesktop ? CrossAxisAlignment.start : CrossAxisAlignment.center, + children: [ + SizedBox( + height: 48, + child: Row( + children: [ + Expanded( + child: Container( + height: 48, + width: 100, + decoration: BoxDecoration( + color: + Theme.of( + context, + ).extension<StackColors>()!.textFieldDefaultBG, + borderRadius: BorderRadius.only( + topLeft: Radius.circular( + Constants.size.circularBorderRadius, + ), // Adjust radius as needed + bottomLeft: Radius.circular( + Constants.size.circularBorderRadius, + ), + ), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: TextField( + inputFormatters: [ + LengthLimitingTextInputFormatter(kMaxNameLength), + ], + textInputAction: TextInputAction.search, + focusNode: _nameFieldFocus, + controller: _nameController, + textAlignVertical: TextAlignVertical.center, + decoration: InputDecoration( + isDense: true, + contentPadding: EdgeInsets.zero, + prefixIcon: Padding( + padding: const EdgeInsets.all(14), + child: SvgPicture.asset( + Assets.svg.search, + width: 20, + height: 20, + color: + Theme.of(context) + .extension<StackColors>()! + .textFieldDefaultSearchIconLeft, + ), + ), + fillColor: Colors.transparent, + hintText: "Find a spark name", + hintStyle: STextStyles.fieldLabel(context), + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + ), + onSubmitted: (_) { + if (_nameController.text.isNotEmpty) { + _lookup(); + } + }, + onChanged: (value) { + // trigger look up button enabled/disabled state change + setState(() {}); + }, + ), + ), + ], + ), + ), + ), + ], + ), + ), + const SizedBox(height: 4), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Padding( + padding: EdgeInsets.only(right: dotBitBoxLength), + child: Builder( + builder: (context) { + final length = _nameController.text.length; + return Text( + "$length/$kMaxNameLength", + style: STextStyles.w500_10(context).copyWith( + color: + Theme.of( + context, + ).extension<StackColors>()!.textSubtitle2, + ), + ); + }, + ), + ), + ], + ), + SizedBox(height: Util.isDesktop ? 24 : 16), + SecondaryButton( + label: "Lookup", + enabled: _nameController.text.isNotEmpty, + // width: Util.isDesktop ? 160 : double.infinity, + buttonHeight: Util.isDesktop ? ButtonHeight.l : null, + onPressed: _lookup, + ), + const SizedBox(height: 32), + if (_lastLookedUpName != null) + _NameCard( + walletId: widget.walletId, + isAvailable: _isAvailable, + name: _lastLookedUpName!, + ), + ], + ); + } +} + +class _NameCard extends ConsumerWidget { + const _NameCard({ + super.key, + required this.walletId, + required this.isAvailable, + required this.name, + }); + + final String walletId; + final bool isAvailable; + final String name; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final availability = isAvailable ? "Available" : "Unavailable"; + final color = + isAvailable + ? Theme.of(context).extension<StackColors>()!.accentColorGreen + : Theme.of(context).extension<StackColors>()!.accentColorRed; + + final style = + (Util.isDesktop + ? STextStyles.w500_16(context) + : STextStyles.w500_12(context)); + + return RoundedWhiteContainer( + padding: EdgeInsets.all(Util.isDesktop ? 24 : 16), + child: IntrinsicHeight( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(name, style: style), + const SizedBox(height: 4), + Text(availability, style: style.copyWith(color: color)), + ], + ), + ), + Column( + children: [ + PrimaryButton( + label: "Buy name", + enabled: isAvailable, + buttonHeight: + Util.isDesktop ? ButtonHeight.m : ButtonHeight.l, + width: Util.isDesktop ? 140 : 120, + onPressed: () async { + if (context.mounted) { + if (Util.isDesktop) { + await showDialog<void>( + context: context, + builder: + (context) => SDialog( + child: SizedBox( + width: 580, + child: Column( + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + ), + child: Text( + "Buy name", + style: STextStyles.desktopH3( + context, + ), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, + ), + child: BuySparkNameView( + walletId: walletId, + name: name, + ), + ), + ], + ), + ), + ), + ); + } else { + await Navigator.of(context).pushNamed( + BuySparkNameView.routeName, + arguments: (walletId: walletId, name: name), + ); + } + } + }, + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/spark_names/sub_widgets/manage_spark_names_option_widget.dart b/lib/pages/spark_names/sub_widgets/manage_spark_names_option_widget.dart new file mode 100644 index 000000000..deece1a4e --- /dev/null +++ b/lib/pages/spark_names/sub_widgets/manage_spark_names_option_widget.dart @@ -0,0 +1,98 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:isar/isar.dart'; +import 'package:namecoin/namecoin.dart'; + +import '../../../models/isar/models/blockchain_data/utxo.dart'; +import '../../../providers/db/main_db_provider.dart'; +import '../../../utilities/util.dart'; +import '../../../wallets/isar/providers/wallet_info_provider.dart'; +import 'owned_spark_name_card.dart'; + +class ManageSparkNamesOptionWidget extends ConsumerStatefulWidget { + const ManageSparkNamesOptionWidget({super.key, required this.walletId}); + + final String walletId; + + @override + ConsumerState<ManageSparkNamesOptionWidget> createState() => + _ManageSparkNamesWidgetState(); +} + +class _ManageSparkNamesWidgetState + extends ConsumerState<ManageSparkNamesOptionWidget> { + double _tempWidth = 0; + double? _width; + int _count = 0; + + void _sillyHack(double value, int length) { + if (value > _tempWidth) _tempWidth = value; + _count++; + if (_count == length) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + setState(() { + _width = _tempWidth; + _tempWidth = 0; + }); + } + }); + } + } + + @override + Widget build(BuildContext context) { + final height = ref.watch(pWalletChainHeight(widget.walletId)); + return StreamBuilder( + stream: ref.watch( + mainDBProvider.select( + (s) => s.isar.utxos + .where() + .walletIdEqualTo(widget.walletId) + .filter() + .otherDataIsNotNull() + .watch(fireImmediately: true), + ), + ), + builder: (context, snapshot) { + List<(UTXO, OpNameData)> list = []; + if (snapshot.hasData) { + list = snapshot.data! + .map((utxo) { + final data = jsonDecode(utxo.otherData!) as Map; + + final nameData = + jsonDecode(data["nameOpData"] as String) as Map; + + return ( + utxo, + OpNameData(nameData.cast(), utxo.blockHeight ?? height), + ); + }) + .toList(growable: false); + } + + return Column( + children: [ + ...list.map( + (e) => Padding( + padding: const EdgeInsets.only(bottom: 10), + child: OwnedSparkNameCard( + key: ValueKey(e), + utxo: e.$1, + opNameData: e.$2, + firstColWidth: _width, + calculatedFirstColWidth: + (value) => _sillyHack(value, list.length), + ), + ), + ), + SizedBox(height: Util.isDesktop ? 14 : 6), + ], + ); + }, + ); + } +} diff --git a/lib/pages/spark_names/sub_widgets/owned_spark_name_card.dart b/lib/pages/spark_names/sub_widgets/owned_spark_name_card.dart new file mode 100644 index 000000000..f49a5f904 --- /dev/null +++ b/lib/pages/spark_names/sub_widgets/owned_spark_name_card.dart @@ -0,0 +1,225 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:namecoin/namecoin.dart'; + +import '../../../models/isar/models/isar_models.dart'; +import '../../../providers/global/secure_store_provider.dart'; +import '../../../themes/stack_colors.dart'; +import '../../../utilities/text_styles.dart'; +import '../../../utilities/util.dart'; +import '../../../wallets/isar/providers/wallet_info_provider.dart'; +import '../../../wallets/wallet/impl/namecoin_wallet.dart'; +import '../../../widgets/conditional_parent.dart'; +import '../../../widgets/desktop/primary_button.dart'; +import '../../../widgets/dialogs/s_dialog.dart'; +import '../../../widgets/rounded_white_container.dart'; +import 'spark_name_details.dart'; + +class OwnedSparkNameCard extends ConsumerStatefulWidget { + const OwnedSparkNameCard({ + super.key, + required this.opNameData, + required this.utxo, + this.firstColWidth, + this.calculatedFirstColWidth, + }); + + final OpNameData opNameData; + final UTXO utxo; + + final double? firstColWidth; + final void Function(double)? calculatedFirstColWidth; + + @override + ConsumerState<OwnedSparkNameCard> createState() => _OwnedSparkNameCardState(); +} + +class _OwnedSparkNameCardState extends ConsumerState<OwnedSparkNameCard> { + String? constructedName, value; + + (String, Color) _getExpiry(int currentChainHeight, StackColors theme) { + final String message; + final Color color; + + if (widget.utxo.blockHash == null) { + message = "Expires in $blocksNameExpiration+ blocks"; + color = theme.accentColorGreen; + } else { + final remaining = widget.opNameData.expiredBlockLeft( + currentChainHeight, + false, + ); + final semiRemaining = widget.opNameData.expiredBlockLeft( + currentChainHeight, + true, + ); + + if (remaining == null) { + color = theme.accentColorRed; + message = "Expired"; + } else { + message = "Expires in $remaining blocks"; + if (semiRemaining == null) { + color = theme.accentColorYellow; + } else { + color = theme.accentColorGreen; + } + } + } + + return (message, color); + } + + bool _lock = false; + + Future<void> _showDetails() async { + if (_lock) return; + _lock = true; + try { + if (Util.isDesktop) { + await showDialog<void>( + context: context, + builder: + (context) => SDialog( + child: SparkNameDetailsView( + utxoId: widget.utxo.id, + walletId: widget.utxo.walletId, + ), + ), + ); + } else { + await Navigator.of(context).pushNamed( + SparkNameDetailsView.routeName, + arguments: (widget.utxo.id, widget.utxo.walletId), + ); + } + } finally { + _lock = false; + } + } + + void _setName() { + try { + constructedName = widget.opNameData.constructedName; + value = widget.opNameData.value; + } catch (_) { + if (widget.opNameData.op == OpName.nameNew) { + ref + .read(secureStoreProvider) + .read( + key: nameSaltKeyBuilder( + widget.utxo.txid, + widget.utxo.walletId, + widget.utxo.vout, + ), + ) + .then((onValue) { + if (onValue != null) { + final data = + (jsonDecode(onValue) as Map).cast<String, String>(); + WidgetsBinding.instance.addPostFrameCallback((_) { + constructedName = data["name"]!; + value = data["value"]!; + if (mounted) { + setState(() {}); + } + }); + } else { + WidgetsBinding.instance.addPostFrameCallback((_) { + constructedName = "UNKNOWN"; + value = ""; + if (mounted) { + setState(() {}); + } + }); + } + }); + } + } + } + + @override + void initState() { + super.initState(); + _setName(); + } + + double _callbackWidth = 0; + + @override + Widget build(BuildContext context) { + debugPrint("BUILD: $runtimeType"); + + final (message, color) = _getExpiry( + ref.watch(pWalletChainHeight(widget.utxo.walletId)), + Theme.of(context).extension<StackColors>()!, + ); + + return RoundedWhiteContainer( + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ConditionalParent( + condition: widget.firstColWidth != null && Util.isDesktop, + builder: + (child) => ConstrainedBox( + constraints: BoxConstraints(maxWidth: widget.firstColWidth!), + child: child, + ), + child: ConditionalParent( + condition: widget.firstColWidth == null && Util.isDesktop, + builder: + (child) => LayoutBuilder( + builder: (context, constraints) { + if (widget.firstColWidth == null && + _callbackWidth != constraints.maxWidth) { + _callbackWidth = constraints.maxWidth; + widget.calculatedFirstColWidth?.call(_callbackWidth); + } + return ConstrainedBox( + constraints: BoxConstraints( + maxWidth: constraints.maxWidth, + ), + child: child, + ); + }, + ), + child: Padding( + padding: const EdgeInsets.only(right: 12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SelectableText(constructedName ?? ""), + const SizedBox(height: 8), + SelectableText( + message, + style: STextStyles.w500_12( + context, + ).copyWith(color: color), + ), + ], + ), + ), + ), + ), + if (Util.isDesktop) + Expanded( + child: SelectableText( + value ?? "", + style: STextStyles.w500_12(context), + ), + ), + if (Util.isDesktop) const SizedBox(width: 12), + PrimaryButton( + label: "Details", + buttonHeight: Util.isDesktop ? ButtonHeight.xs : ButtonHeight.l, + onPressed: _showDetails, + ), + ], + ), + ); + } +} diff --git a/lib/pages/spark_names/sub_widgets/spark_name_details.dart b/lib/pages/spark_names/sub_widgets/spark_name_details.dart new file mode 100644 index 000000000..3fe469cc8 --- /dev/null +++ b/lib/pages/spark_names/sub_widgets/spark_name_details.dart @@ -0,0 +1,565 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:isar/isar.dart'; +import 'package:namecoin/namecoin.dart'; + +import '../../../models/isar/models/isar_models.dart'; +import '../../../providers/db/main_db_provider.dart'; +import '../../../providers/global/secure_store_provider.dart'; +import '../../../providers/global/wallets_provider.dart'; +import '../../../themes/stack_colors.dart'; +import '../../../utilities/text_styles.dart'; +import '../../../utilities/util.dart'; +import '../../../wallets/isar/providers/wallet_info_provider.dart'; +import '../../../wallets/wallet/impl/namecoin_wallet.dart'; +import '../../../widgets/background.dart'; +import '../../../widgets/conditional_parent.dart'; +import '../../../widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../../widgets/custom_buttons/simple_copy_button.dart'; +import '../../../widgets/desktop/desktop_dialog_close_button.dart'; +import '../../../widgets/rounded_container.dart'; +import '../../wallet_view/transaction_views/transaction_details_view.dart'; + +class SparkNameDetailsView extends ConsumerStatefulWidget { + const SparkNameDetailsView({ + super.key, + required this.utxoId, + required this.walletId, + }); + + static const routeName = "/sparkNameDetails"; + + final Id utxoId; + final String walletId; + + @override + ConsumerState<SparkNameDetailsView> createState() => + _SparkNameDetailsViewState(); +} + +class _SparkNameDetailsViewState extends ConsumerState<SparkNameDetailsView> { + late Stream<UTXO?> streamUTXO; + UTXO? utxo; + OpNameData? opNameData; + + String? constructedName, value; + + Stream<AddressLabel?>? streamLabel; + AddressLabel? label; + + void setUtxo(UTXO? utxo, int currentHeight) { + if (utxo != null) { + this.utxo = utxo; + final data = jsonDecode(utxo.otherData!) as Map; + + final nameData = jsonDecode(data["nameOpData"] as String) as Map; + opNameData = OpNameData( + nameData.cast(), + utxo.blockHeight ?? currentHeight, + ); + + _setName(); + } + } + + void _setName() { + try { + constructedName = opNameData!.constructedName; + value = opNameData!.value; + } catch (_) { + if (opNameData?.op == OpName.nameNew) { + ref + .read(secureStoreProvider) + .read( + key: nameSaltKeyBuilder(utxo!.txid, widget.walletId, utxo!.vout), + ) + .then((onValue) { + if (onValue != null) { + final data = + (jsonDecode(onValue) as Map).cast<String, String>(); + WidgetsBinding.instance.addPostFrameCallback((_) { + constructedName = data["name"]!; + value = data["value"]!; + if (mounted) { + setState(() {}); + } + }); + } else { + WidgetsBinding.instance.addPostFrameCallback((_) { + constructedName = "UNKNOWN"; + value = ""; + if (mounted) { + setState(() {}); + } + }); + } + }); + } + } + } + + (String, Color) _getExpiry(int currentChainHeight, StackColors theme) { + final String message; + final Color color; + + if (utxo?.blockHash == null) { + message = "Expires in $blocksNameExpiration+ blocks"; + color = theme.accentColorGreen; + } else { + final remaining = opNameData?.expiredBlockLeft(currentChainHeight, false); + final semiRemaining = opNameData?.expiredBlockLeft( + currentChainHeight, + true, + ); + + if (remaining == null) { + color = theme.accentColorRed; + message = "Expired"; + } else { + message = "Expires in $remaining blocks"; + if (semiRemaining == null) { + color = theme.accentColorYellow; + } else { + color = theme.accentColorGreen; + } + } + } + + return (message, color); + } + + bool _checkConfirmedUtxo(int currentHeight) { + return (ref.read(pWallets).getWallet(widget.walletId) as NamecoinWallet) + .checkUtxoConfirmed(utxo!, currentHeight); + } + + @override + void initState() { + super.initState(); + + setUtxo( + ref + .read(mainDBProvider) + .isar + .utxos + .where() + .idEqualTo(widget.utxoId) + .findFirstSync(), + ref.read(pWalletChainHeight(widget.walletId)), + ); + + _setName(); + + if (utxo?.address != null) { + label = ref + .read(mainDBProvider) + .getAddressLabelSync(widget.walletId, utxo!.address!); + + if (label != null) { + streamLabel = ref.read(mainDBProvider).watchAddressLabel(id: label!.id); + } + } + + streamUTXO = ref.read(mainDBProvider).watchUTXO(id: widget.utxoId); + } + + @override + Widget build(BuildContext context) { + final currentHeight = ref.watch(pWalletChainHeight(widget.walletId)); + + final (message, color) = _getExpiry( + currentHeight, + Theme.of(context).extension<StackColors>()!, + ); + + return ConditionalParent( + condition: !Util.isDesktop, + builder: + (child) => Background( + child: Scaffold( + backgroundColor: Colors.transparent, + appBar: AppBar( + backgroundColor: Colors.transparent, + // Theme.of(context).extension<StackColors>()!.background, + leading: const AppBarBackButton(), + title: Text( + "Domain details", + style: STextStyles.navBarTitle(context), + ), + ), + body: SafeArea( + child: LayoutBuilder( + builder: (context, constraints) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight(child: child), + ), + ), + ); + }, + ), + ), + ), + ), + child: ConditionalParent( + condition: Util.isDesktop, + builder: (child) { + return SizedBox( + width: 641, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 32), + child: Text( + "Domain details", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Padding( + padding: const EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, + top: 10, + ), + child: RoundedContainer( + padding: EdgeInsets.zero, + color: Colors.transparent, + borderColor: + Theme.of( + context, + ).extension<StackColors>()!.textFieldDefaultBG, + child: child, + ), + ), + ], + ), + ); + }, + child: StreamBuilder( + stream: streamUTXO, + builder: (context, snapshot) { + if (snapshot.hasData) { + setUtxo(snapshot.data!, currentHeight); + } + + return utxo == null + ? Center( + child: Text( + "Missing output. Was it used recently?", + style: STextStyles.w500_14(context).copyWith( + color: + Theme.of( + context, + ).extension<StackColors>()!.accentColorRed, + ), + ), + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // if (!isDesktop) + // const SizedBox( + // height: 10, + // ), + RoundedContainer( + padding: const EdgeInsets.all(12), + color: + Util.isDesktop + ? Colors.transparent + : Theme.of( + context, + ).extension<StackColors>()!.popupBG, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SelectableText( + constructedName ?? "", + style: STextStyles.pageTitleH2(context), + ), + if (Util.isDesktop) + SelectableText( + opNameData!.op.name, + style: STextStyles.w500_14(context), + ), + ], + ), + if (!Util.isDesktop) + SelectableText( + opNameData!.op.name, + style: STextStyles.w500_14(context), + ), + ], + ), + ), + const _Div(), + RoundedContainer( + padding: + Util.isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + color: + Util.isDesktop + ? Colors.transparent + : Theme.of( + context, + ).extension<StackColors>()!.popupBG, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Value", + style: STextStyles.w500_14(context).copyWith( + color: + Theme.of( + context, + ).extension<StackColors>()!.textSubtitle1, + ), + ), + ], + ), + const SizedBox(height: 4), + SelectableText( + value ?? "", + style: STextStyles.w500_14(context), + ), + ], + ), + ), + const _Div(), + RoundedContainer( + padding: + Util.isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + color: + Util.isDesktop + ? Colors.transparent + : Theme.of( + context, + ).extension<StackColors>()!.popupBG, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Address", + style: STextStyles.w500_14(context).copyWith( + color: + Theme.of( + context, + ).extension<StackColors>()!.textSubtitle1, + ), + ), + Util.isDesktop + ? IconCopyButton(data: utxo!.address!) + : SimpleCopyButton(data: utxo!.address!), + ], + ), + const SizedBox(height: 4), + SelectableText( + utxo!.address!, + style: STextStyles.w500_14(context), + ), + ], + ), + ), + if (label != null && label!.value.isNotEmpty) const _Div(), + if (label != null && label!.value.isNotEmpty) + RoundedContainer( + padding: + Util.isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + color: + Util.isDesktop + ? Colors.transparent + : Theme.of( + context, + ).extension<StackColors>()!.popupBG, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Address label", + style: STextStyles.w500_14(context).copyWith( + color: + Theme.of(context) + .extension<StackColors>()! + .textSubtitle1, + ), + ), + Util.isDesktop + ? IconCopyButton(data: label!.value) + : SimpleCopyButton(data: label!.value), + ], + ), + const SizedBox(height: 4), + SelectableText( + label!.value, + style: STextStyles.w500_14(context), + ), + ], + ), + ), + const _Div(), + RoundedContainer( + padding: + Util.isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + color: + Util.isDesktop + ? Colors.transparent + : Theme.of( + context, + ).extension<StackColors>()!.popupBG, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Transaction ID", + style: STextStyles.w500_14(context).copyWith( + color: + Theme.of( + context, + ).extension<StackColors>()!.textSubtitle1, + ), + ), + Util.isDesktop + ? IconCopyButton(data: utxo!.txid) + : SimpleCopyButton(data: utxo!.txid), + ], + ), + const SizedBox(height: 4), + SelectableText( + utxo!.txid, + style: STextStyles.w500_14(context), + ), + ], + ), + ), + const _Div(), + RoundedContainer( + padding: + Util.isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + color: + Util.isDesktop + ? Colors.transparent + : Theme.of( + context, + ).extension<StackColors>()!.popupBG, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Expiry", + style: STextStyles.w500_14(context).copyWith( + color: + Theme.of( + context, + ).extension<StackColors>()!.textSubtitle1, + ), + ), + const SizedBox(height: 4), + SelectableText( + message, + style: STextStyles.w500_14( + context, + ).copyWith(color: color), + ), + ], + ), + ), + const _Div(), + RoundedContainer( + padding: + Util.isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + color: + Util.isDesktop + ? Colors.transparent + : Theme.of( + context, + ).extension<StackColors>()!.popupBG, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Confirmations", + style: STextStyles.w500_14(context).copyWith( + color: + Theme.of( + context, + ).extension<StackColors>()!.textSubtitle1, + ), + ), + const SizedBox(height: 4), + SelectableText( + "${utxo!.getConfirmations(currentHeight)}", + style: STextStyles.w500_14(context), + ), + ], + ), + ), + ], + ); + }, + ), + ), + ); + } +} + +class _Div extends StatelessWidget { + const _Div({super.key}); + + @override + Widget build(BuildContext context) { + if (Util.isDesktop) { + return Container( + width: double.infinity, + height: 1.0, + color: Theme.of(context).extension<StackColors>()!.textFieldDefaultBG, + ); + } else { + return const SizedBox(height: 12); + } + } +} diff --git a/lib/pages/wallet_view/wallet_view.dart b/lib/pages/wallet_view/wallet_view.dart index 41422c7e0..7356e07e6 100644 --- a/lib/pages/wallet_view/wallet_view.dart +++ b/lib/pages/wallet_view/wallet_view.dart @@ -98,6 +98,7 @@ import '../send_view/frost_ms/frost_send_view.dart'; import '../send_view/send_view.dart'; import '../settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart'; import '../settings_views/wallet_settings_view/wallet_settings_view.dart'; +import '../spark_names/spark_names_home_view.dart'; import '../special/firo_rescan_recovery_error_dialog.dart'; import '../token_view/my_tokens_view.dart'; import 'sub_widgets/transactions_list.dart'; @@ -146,8 +147,9 @@ class _WalletViewState extends ConsumerState<WalletView> { bool _lelantusRescanRecovery = false; Future<void> _firoRescanRecovery() async { - final success = await (ref.read(pWallets).getWallet(walletId) as FiroWallet) - .firoRescanRecovery(); + final success = + await (ref.read(pWallets).getWallet(walletId) as FiroWallet) + .firoRescanRecovery(); if (success) { // go into wallet @@ -160,10 +162,9 @@ class _WalletViewState extends ConsumerState<WalletView> { } else { // show error message dialog w/ options if (mounted) { - final shouldRetry = await Navigator.of(context).pushNamed( - FiroRescanRecoveryErrorView.routeName, - arguments: walletId, - ); + final shouldRetry = await Navigator.of( + context, + ).pushNamed(FiroRescanRecoveryErrorView.routeName, arguments: walletId); if (shouldRetry is bool && shouldRetry) { await _firoRescanRecovery(); @@ -218,41 +219,39 @@ class _WalletViewState extends ConsumerState<WalletView> { eventBus = widget.eventBus != null ? widget.eventBus! : GlobalEventBus.instance; - _syncStatusSubscription = - eventBus.on<WalletSyncStatusChangedEvent>().listen( - (event) async { - if (event.walletId == widget.walletId) { - // switch (event.newStatus) { - // case WalletSyncStatus.unableToSync: - // break; - // case WalletSyncStatus.synced: - // break; - // case WalletSyncStatus.syncing: - // break; - // } - setState(() { - _currentSyncStatus = event.newStatus; - }); - } - }, - ); + _syncStatusSubscription = eventBus + .on<WalletSyncStatusChangedEvent>() + .listen((event) async { + if (event.walletId == widget.walletId) { + // switch (event.newStatus) { + // case WalletSyncStatus.unableToSync: + // break; + // case WalletSyncStatus.synced: + // break; + // case WalletSyncStatus.syncing: + // break; + // } + setState(() { + _currentSyncStatus = event.newStatus; + }); + } + }); - _nodeStatusSubscription = - eventBus.on<NodeConnectionStatusChangedEvent>().listen( - (event) async { - if (event.walletId == widget.walletId) { - // switch (event.newStatus) { - // case NodeConnectionStatus.disconnected: - // break; - // case NodeConnectionStatus.connected: - // break; - // } - setState(() { - _currentNodeStatus = event.newStatus; - }); - } - }, - ); + _nodeStatusSubscription = eventBus + .on<NodeConnectionStatusChangedEvent>() + .listen((event) async { + if (event.walletId == widget.walletId) { + // switch (event.newStatus) { + // case NodeConnectionStatus.disconnected: + // break; + // case NodeConnectionStatus.connected: + // break; + // } + setState(() { + _currentNodeStatus = event.newStatus; + }); + } + }); super.initState(); } @@ -379,9 +378,7 @@ class _WalletViewState extends ConsumerState<WalletView> { callerRouteName: WalletView.routeName, ); - await Navigator.of(context).pushNamed( - FrostStepScaffold.routeName, - ); + await Navigator.of(context).pushNamed(FrostStepScaffold.routeName); } Future<void> _onExchangePressed(BuildContext context) async { @@ -390,24 +387,27 @@ class _WalletViewState extends ConsumerState<WalletView> { if (coin.network.isTestNet) { await showDialog<void>( context: context, - builder: (_) => const StackOkDialog( - title: "Exchange not available for test net coins", - ), + builder: + (_) => const StackOkDialog( + title: "Exchange not available for test net coins", + ), ); } else { Future<Currency?> _future; try { - _future = ExchangeDataLoadingService.instance.isar.currencies - .where() - .tickerEqualToAnyExchangeNameName(coin.ticker) - .findFirst(); + _future = + ExchangeDataLoadingService.instance.isar.currencies + .where() + .tickerEqualToAnyExchangeNameName(coin.ticker) + .findFirst(); } catch (_) { _future = ExchangeDataLoadingService.instance.loadAll().then( - (_) => ExchangeDataLoadingService.instance.isar.currencies + (_) => + ExchangeDataLoadingService.instance.isar.currencies .where() .tickerEqualToAnyExchangeNameName(coin.ticker) .findFirst(), - ); + ); } final currency = await showLoading( @@ -436,9 +436,10 @@ class _WalletViewState extends ConsumerState<WalletView> { if (coin.network.isTestNet) { await showDialog<void>( context: context, - builder: (_) => const StackOkDialog( - title: "Buy not available for test net coins", - ), + builder: + (_) => const StackOkDialog( + title: "Buy not available for test net coins", + ), ); } else { if (mounted) { @@ -458,13 +459,14 @@ class _WalletViewState extends ConsumerState<WalletView> { unawaited( showDialog( context: context, - builder: (context) => WillPopScope( - child: const CustomLoadingOverlay( - message: "Anonymizing balance", - eventBus: null, - ), - onWillPop: () async => shouldPop, - ), + builder: + (context) => WillPopScope( + child: const CustomLoadingOverlay( + message: "Anonymizing balance", + eventBus: null, + ), + onWillPop: () async => shouldPop, + ), ), ); final firoWallet = ref.read(pWallets).getWallet(walletId) as FiroWallet; @@ -473,9 +475,9 @@ class _WalletViewState extends ConsumerState<WalletView> { if (publicBalance <= Amount.zero) { shouldPop = true; if (mounted) { - Navigator.of(context).popUntil( - ModalRoute.withName(WalletView.routeName), - ); + Navigator.of( + context, + ).popUntil(ModalRoute.withName(WalletView.routeName)); unawaited( showFloatingFlushBar( type: FlushBarType.info, @@ -492,9 +494,9 @@ class _WalletViewState extends ConsumerState<WalletView> { await firoWallet.anonymizeAllSpark(); shouldPop = true; if (mounted) { - Navigator.of(context).popUntil( - ModalRoute.withName(WalletView.routeName), - ); + Navigator.of( + context, + ).popUntil(ModalRoute.withName(WalletView.routeName)); unawaited( showFloatingFlushBar( type: FlushBarType.success, @@ -506,15 +508,16 @@ class _WalletViewState extends ConsumerState<WalletView> { } catch (e) { shouldPop = true; if (mounted) { - Navigator.of(context).popUntil( - ModalRoute.withName(WalletView.routeName), - ); + Navigator.of( + context, + ).popUntil(ModalRoute.withName(WalletView.routeName)); await showDialog<dynamic>( context: context, - builder: (_) => StackOkDialog( - title: "Anonymize all failed", - message: "Reason: $e", - ), + builder: + (_) => StackOkDialog( + title: "Anonymize all failed", + message: "Reason: $e", + ), ); } } @@ -549,37 +552,46 @@ class _WalletViewState extends ConsumerState<WalletView> { eventBus: null, textColor: Theme.of(context).extension<StackColors>()!.textDark, - actionButton: _lelantusRescanRecovery - ? null - : SecondaryButton( - label: "Cancel", - onPressed: () async { - await showDialog<void>( - context: context, - builder: (context) => StackDialog( - title: "Warning!", - message: "Skipping this process can completely" - " break your wallet. It is only meant to be done in" - " emergency situations where the migration fails" - " and will not let you continue. Still skip?", - leftButton: SecondaryButton( - label: "Cancel", - onPressed: - Navigator.of(context, rootNavigator: true) - .pop, - ), - rightButton: SecondaryButton( - label: "Ok", - onPressed: () { - Navigator.of(context, rootNavigator: true) - .pop(); - setState(() => _rescanningOnOpen = false); - }, - ), - ), - ); - }, - ), + actionButton: + _lelantusRescanRecovery + ? null + : SecondaryButton( + label: "Cancel", + onPressed: () async { + await showDialog<void>( + context: context, + builder: + (context) => StackDialog( + title: "Warning!", + message: + "Skipping this process can completely" + " break your wallet. It is only meant to be done in" + " emergency situations where the migration fails" + " and will not let you continue. Still skip?", + leftButton: SecondaryButton( + label: "Cancel", + onPressed: + Navigator.of( + context, + rootNavigator: true, + ).pop, + ), + rightButton: SecondaryButton( + label: "Ok", + onPressed: () { + Navigator.of( + context, + rootNavigator: true, + ).pop(); + setState( + () => _rescanningOnOpen = false, + ); + }, + ), + ), + ); + }, + ), ), ), ], @@ -605,15 +617,11 @@ class _WalletViewState extends ConsumerState<WalletView> { title: Row( children: [ SvgPicture.file( - File( - ref.watch(coinIconProvider(coin)), - ), + File(ref.watch(coinIconProvider(coin))), width: 24, height: 24, ), - const SizedBox( - width: 16, - ), + const SizedBox(width: 16), Expanded( child: Text( ref.watch(pWalletName(walletId)), @@ -625,15 +633,8 @@ class _WalletViewState extends ConsumerState<WalletView> { ), actions: [ const Padding( - padding: EdgeInsets.only( - top: 10, - bottom: 10, - right: 10, - ), - child: AspectRatio( - aspectRatio: 1, - child: SmallTorIcon(), - ), + padding: EdgeInsets.only(top: 10, bottom: 10, right: 10), + child: AspectRatio(aspectRatio: 1, child: SmallTorIcon()), ), Padding( padding: const EdgeInsets.only( @@ -649,9 +650,10 @@ class _WalletViewState extends ConsumerState<WalletView> { key: const Key("walletViewRadioButton"), size: 36, shadows: const [], - color: Theme.of(context) - .extension<StackColors>()! - .background, + color: + Theme.of( + context, + ).extension<StackColors>()!.background, icon: _buildNetworkIcon(_currentSyncStatus), onPressed: () { Navigator.of(context).pushNamed( @@ -680,91 +682,105 @@ class _WalletViewState extends ConsumerState<WalletView> { key: const Key("walletViewAlertsButton"), size: 36, shadows: const [], - color: Theme.of(context) - .extension<StackColors>()! - .background, - icon: ref.watch( - notificationsProvider.select( - (value) => - value.hasUnreadNotificationsFor(walletId), - ), - ) - ? SvgPicture.file( - File( - ref.watch( - themeProvider.select( - (value) => value.assets.bellNew, + color: + Theme.of( + context, + ).extension<StackColors>()!.background, + icon: + ref.watch( + notificationsProvider.select( + (value) => value + .hasUnreadNotificationsFor(walletId), + ), + ) + ? SvgPicture.file( + File( + ref.watch( + themeProvider.select( + (value) => value.assets.bellNew, + ), ), ), + width: 20, + height: 20, + color: + ref.watch( + notificationsProvider.select( + (value) => value + .hasUnreadNotificationsFor( + walletId, + ), + ), + ) + ? null + : Theme.of(context) + .extension<StackColors>()! + .topNavIconPrimary, + ) + : SvgPicture.asset( + Assets.svg.bell, + width: 20, + height: 20, + color: + ref.watch( + notificationsProvider.select( + (value) => value + .hasUnreadNotificationsFor( + walletId, + ), + ), + ) + ? null + : Theme.of(context) + .extension<StackColors>()! + .topNavIconPrimary, ), - width: 20, - height: 20, - color: ref.watch( - notificationsProvider.select( - (value) => - value.hasUnreadNotificationsFor( - walletId, - ), - ), - ) - ? null - : Theme.of(context) - .extension<StackColors>()! - .topNavIconPrimary, - ) - : SvgPicture.asset( - Assets.svg.bell, - width: 20, - height: 20, - color: ref.watch( - notificationsProvider.select( - (value) => - value.hasUnreadNotificationsFor( - walletId, - ), - ), - ) - ? null - : Theme.of(context) - .extension<StackColors>()! - .topNavIconPrimary, - ), onPressed: () { // reset unread state ref.refresh(unreadNotificationsStateProvider); Navigator.of(context) .pushNamed( - NotificationsView.routeName, - arguments: walletId, - ) + NotificationsView.routeName, + arguments: walletId, + ) .then((_) { - final Set<int> unreadNotificationIds = ref - .read(unreadNotificationsStateProvider.state) - .state; - if (unreadNotificationIds.isEmpty) return; + final Set<int> unreadNotificationIds = + ref + .read( + unreadNotificationsStateProvider + .state, + ) + .state; + if (unreadNotificationIds.isEmpty) return; - final List<Future<dynamic>> futures = []; - for (int i = 0; - i < unreadNotificationIds.length - 1; - i++) { - futures.add( - ref.read(notificationsProvider).markAsRead( - unreadNotificationIds.elementAt(i), - false, - ), - ); - } - - // wait for multiple to update if any - Future.wait(futures).then((_) { - // only notify listeners once - ref.read(notificationsProvider).markAsRead( - unreadNotificationIds.last, - true, + final List<Future<dynamic>> futures = []; + for ( + int i = 0; + i < unreadNotificationIds.length - 1; + i++ + ) { + futures.add( + ref + .read(notificationsProvider) + .markAsRead( + unreadNotificationIds.elementAt(i), + false, + ), ); - }); - }); + } + + // wait for multiple to update if any + Future.wait(futures).then((_) { + // only notify listeners once + ref + .read(notificationsProvider) + .markAsRead( + unreadNotificationIds.last, + true, + ); + }); + }); }, ), ), @@ -783,14 +799,16 @@ class _WalletViewState extends ConsumerState<WalletView> { key: const Key("walletViewSettingsButton"), size: 36, shadows: const [], - color: Theme.of(context) - .extension<StackColors>()! - .background, + color: + Theme.of( + context, + ).extension<StackColors>()!.background, icon: SvgPicture.asset( Assets.svg.bars, - color: Theme.of(context) - .extension<StackColors>()! - .accentColorDark, + color: + Theme.of( + context, + ).extension<StackColors>()!.accentColorDark, width: 20, height: 20, ), @@ -818,29 +836,25 @@ class _WalletViewState extends ConsumerState<WalletView> { Theme.of(context).extension<StackColors>()!.background, child: Column( children: [ - const SizedBox( - height: 10, - ), + const SizedBox(height: 10), Center( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: WalletSummary( walletId: walletId, aspectRatio: 1.75, - initialSyncStatus: ref - .watch(pWallets) - .getWallet(walletId) - .refreshMutex - .isLocked - ? WalletSyncStatus.syncing - : WalletSyncStatus.synced, + initialSyncStatus: + ref + .watch(pWallets) + .getWallet(walletId) + .refreshMutex + .isLocked + ? WalletSyncStatus.syncing + : WalletSyncStatus.synced, ), ), ), - if (isSparkWallet) - const SizedBox( - height: 10, - ), + if (isSparkWallet) const SizedBox(height: 10), if (isSparkWallet) Padding( padding: const EdgeInsets.symmetric(horizontal: 16), @@ -856,51 +870,59 @@ class _WalletViewState extends ConsumerState<WalletView> { onPressed: () async { await showDialog<void>( context: context, - builder: (context) => StackDialog( - title: "Attention!", - message: - "You're about to anonymize all of your public funds.", - leftButton: TextButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: Text( - "Cancel", - style: STextStyles.button(context) - .copyWith( - color: Theme.of(context) + builder: + (context) => StackDialog( + title: "Attention!", + message: + "You're about to anonymize all of your public funds.", + leftButton: TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text( + "Cancel", + style: STextStyles.button( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .accentColorDark, + ), + ), + ), + rightButton: TextButton( + onPressed: () async { + Navigator.of(context).pop(); + + unawaited(attemptAnonymize()); + }, + style: Theme.of(context) .extension<StackColors>()! - .accentColorDark, + .getPrimaryEnabledButtonStyle( + context, + ), + child: Text( + "Continue", + style: STextStyles.button( + context, + ), + ), ), ), - ), - rightButton: TextButton( - onPressed: () async { - Navigator.of(context).pop(); - - unawaited(attemptAnonymize()); - }, - style: Theme.of(context) - .extension<StackColors>()! - .getPrimaryEnabledButtonStyle( - context, - ), - child: Text( - "Continue", - style: - STextStyles.button(context), - ), - ), - ), ); }, child: Text( "Anonymize funds", - style: - STextStyles.button(context).copyWith( - color: Theme.of(context) - .extension<StackColors>()! - .buttonTextSecondary, + style: STextStyles.button( + context, + ).copyWith( + color: + Theme.of(context) + .extension<StackColors>()! + .buttonTextSecondary, ), ), ), @@ -908,9 +930,7 @@ class _WalletViewState extends ConsumerState<WalletView> { ], ), ), - const SizedBox( - height: 20, - ), + const SizedBox(height: 20), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( @@ -918,11 +938,13 @@ class _WalletViewState extends ConsumerState<WalletView> { children: [ Text( "Transactions", - style: - STextStyles.itemSubtitle(context).copyWith( - color: Theme.of(context) - .extension<StackColors>()! - .textDark3, + style: STextStyles.itemSubtitle( + context, + ).copyWith( + color: + Theme.of( + context, + ).extension<StackColors>()!.textDark3, ), ), CustomTextButton( @@ -943,9 +965,7 @@ class _WalletViewState extends ConsumerState<WalletView> { ], ), ), - const SizedBox( - height: 12, - ), + const SizedBox(height: 12), Expanded( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), @@ -970,11 +990,7 @@ class _WalletViewState extends ConsumerState<WalletView> { Colors.transparent, Colors.white, ], - stops: [ - 0.0, - 0.8, - 1.0, - ], + stops: [0.0, 0.8, 1.0], ).createShader(bounds); }, child: Container( @@ -989,17 +1005,20 @@ class _WalletViewState extends ConsumerState<WalletView> { CrossAxisAlignment.stretch, children: [ Expanded( - child: ref - .read(pWallets) - .getWallet(widget.walletId) - .isarTransactionVersion == - 2 - ? TransactionsV2List( - walletId: widget.walletId, - ) - : TransactionsList( - walletId: walletId, - ), + child: + ref + .read(pWallets) + .getWallet( + widget.walletId, + ) + .isarTransactionVersion == + 2 + ? TransactionsV2List( + walletId: widget.walletId, + ) + : TransactionsList( + walletId: walletId, + ), ), ], ), @@ -1059,10 +1078,7 @@ class _WalletViewState extends ConsumerState<WalletView> { wallet is BitcoinFrostWallet ? FrostSendView.routeName : SendView.routeName, - arguments: ( - walletId: walletId, - coin: coin, - ), + arguments: (walletId: walletId, coin: coin), ); }, ), @@ -1089,10 +1105,11 @@ class _WalletViewState extends ConsumerState<WalletView> { moreItems: [ if (ref.watch( pWallets.select( - (value) => value - .getWallet(widget.walletId) - .cryptoCurrency - .hasTokenSupport, + (value) => + value + .getWallet(widget.walletId) + .cryptoCurrency + .hasTokenSupport, ), )) WalletNavigationBarItemData( @@ -1111,9 +1128,10 @@ class _WalletViewState extends ConsumerState<WalletView> { Assets.svg.monkey, height: 20, width: 20, - color: Theme.of(context) - .extension<StackColors>()! - .bottomNavIconIcon, + color: + Theme.of( + context, + ).extension<StackColors>()!.bottomNavIconIcon, ), label: "MonKey", onTap: () { @@ -1185,6 +1203,17 @@ class _WalletViewState extends ConsumerState<WalletView> { ); }, ), + if (wallet is SparkInterface) + WalletNavigationBarItemData( + label: "Names", + icon: const PaynymNavIcon(), + onTap: () { + Navigator.of(context).pushNamed( + SparkNamesHomeView.routeName, + arguments: widget.walletId, + ); + }, + ), if (!viewOnly && wallet is PaynymInterface) WalletNavigationBarItemData( label: "PayNym", @@ -1193,14 +1222,14 @@ class _WalletViewState extends ConsumerState<WalletView> { unawaited( showDialog( context: context, - builder: (context) => const LoadingIndicator( - width: 100, - ), + builder: + (context) => const LoadingIndicator(width: 100), ), ); - final wallet = - ref.read(pWallets).getWallet(widget.walletId); + final wallet = ref + .read(pWallets) + .getWallet(widget.walletId); final paynymInterface = wallet as PaynymInterface; @@ -1219,10 +1248,10 @@ class _WalletViewState extends ConsumerState<WalletView> { // check if account exists and for matching code to see if claimed if (account.value != null && - account.value!.nonSegwitPaymentCode.claimed - // && - // account.value!.segwit - ) { + account.value!.nonSegwitPaymentCode.claimed + // && + // account.value!.segwit + ) { ref.read(myPaynymAccountStateProvider.state).state = account.value!; diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_features.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_features.dart index 78a36d8d7..0cfab7c16 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_features.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_features.dart @@ -21,6 +21,7 @@ import '../../../../pages/monkey/monkey_view.dart'; import '../../../../pages/namecoin_names/namecoin_names_home_view.dart'; import '../../../../pages/paynym/paynym_claim_view.dart'; import '../../../../pages/paynym/paynym_home_view.dart'; +import '../../../../pages/spark_names/spark_names_home_view.dart'; import '../../../../providers/desktop/current_desktop_menu_item.dart'; import '../../../../providers/global/paynym_api_provider.dart'; import '../../../../providers/providers.dart'; @@ -99,6 +100,7 @@ class _DesktopWalletFeaturesState extends ConsumerState<DesktopWalletFeatures> { onFusionPressed: _onFusionPressed, onChurnPressed: _onChurnPressed, onNamesPressed: _onNamesPressed, + onSparkNamesPressed: _onSparkNamesPressed, ), ); } @@ -371,6 +373,14 @@ class _DesktopWalletFeaturesState extends ConsumerState<DesktopWalletFeatures> { ).pushNamed(NamecoinNamesHomeView.routeName, arguments: widget.walletId); } + void _onSparkNamesPressed() { + Navigator.of(context, rootNavigator: true).pop(); + + Navigator.of( + context, + ).pushNamed(SparkNamesHomeView.routeName, arguments: widget.walletId); + } + @override Widget build(BuildContext context) { final wallet = ref.watch(pWallets).getWallet(widget.walletId); 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 1ee0447b3..e16766deb 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 @@ -67,6 +67,7 @@ class MoreFeaturesDialog extends ConsumerStatefulWidget { required this.onFusionPressed, required this.onChurnPressed, required this.onNamesPressed, + required this.onSparkNamesPressed, }); final String walletId; @@ -82,6 +83,7 @@ class MoreFeaturesDialog extends ConsumerStatefulWidget { final VoidCallback? onFusionPressed; final VoidCallback? onChurnPressed; final VoidCallback? onNamesPressed; + final VoidCallback? onSparkNamesPressed; @override ConsumerState<MoreFeaturesDialog> createState() => _MoreFeaturesDialogState(); @@ -492,6 +494,13 @@ class _MoreFeaturesDialogState extends ConsumerState<MoreFeaturesDialog> { iconAsset: Assets.svg.robotHead, onPressed: () async => widget.onNamesPressed?.call(), ), + if (wallet is SparkInterface) + _MoreFeaturesItem( + label: "Names", + detail: "Spark names", + iconAsset: Assets.svg.robotHead, + onPressed: () async => widget.onSparkNamesPressed?.call(), + ), if (wallet is SparkInterface && !isViewOnly) _MoreFeaturesClearSparkCacheItem( cryptoCurrency: wallet.cryptoCurrency, diff --git a/lib/route_generator.dart b/lib/route_generator.dart index 6695a53b8..a07ebf7e1 100644 --- a/lib/route_generator.dart +++ b/lib/route_generator.dart @@ -147,6 +147,8 @@ import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_setting import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/spark_info.dart'; import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/wallet_settings_wallet_settings_view.dart'; import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/xpub_view.dart'; +import 'pages/spark_names/buy_spark_name_view.dart'; +import 'pages/spark_names/spark_names_home_view.dart'; import 'pages/special/firo_rescan_recovery_error_dialog.dart'; import 'pages/stack_privacy_calls.dart'; import 'pages/token_view/my_tokens_view.dart'; @@ -253,12 +255,8 @@ class RouteGenerator { if (args is bool) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => CreatePinView( - popOnSuccess: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => CreatePinView(popOnSuccess: args), + settings: RouteSettings(name: settings.name), ); } return getRoute( @@ -285,14 +283,13 @@ class RouteGenerator { if (args is Tuple3<String, String, String>) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => ChooseCoinView( - title: args.item1, - coinAdditional: args.item2, - nextRouteName: args.item3, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => ChooseCoinView( + title: args.item1, + coinAdditional: args.item2, + nextRouteName: args.item3, + ), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -301,12 +298,8 @@ class RouteGenerator { if (args is CryptoCurrency) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => ManageExplorerView( - coin: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => ManageExplorerView(coin: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -315,12 +308,8 @@ class RouteGenerator { if (args is String) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => FiroRescanRecoveryErrorView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => FiroRescanRecoveryErrorView(walletId: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -343,23 +332,18 @@ class RouteGenerator { if (args is String) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => EditWalletTokensView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => EditWalletTokensView(walletId: args), + settings: RouteSettings(name: settings.name), ); } else if (args is Tuple2<String, List<String>>) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => EditWalletTokensView( - walletId: args.item1, - contractsToMarkSelected: args.item2, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => EditWalletTokensView( + walletId: args.item1, + contractsToMarkSelected: args.item2, + ), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -368,12 +352,8 @@ class RouteGenerator { if (args is String) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => DesktopTokenView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => DesktopTokenView(walletId: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -382,12 +362,8 @@ class RouteGenerator { if (args is EthTokenEntity) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => SelectWalletForTokenView( - entity: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => SelectWalletForTokenView(entity: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -396,21 +372,15 @@ class RouteGenerator { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, builder: (_) => const AddCustomTokenView(), - settings: RouteSettings( - name: settings.name, - ), + settings: RouteSettings(name: settings.name), ); case WalletsOverview.routeName: if (args is CryptoCurrency) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => WalletsOverview( - coin: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => WalletsOverview(coin: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -419,13 +389,12 @@ class RouteGenerator { if (args is Tuple2<String, String>) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => TokenContractDetailsView( - contractAddress: args.item1, - walletId: args.item2, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => TokenContractDetailsView( + contractAddress: args.item1, + walletId: args.item2, + ), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -434,13 +403,12 @@ class RouteGenerator { if (args is Tuple2<String, String>) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => SingleFieldEditView( - initialValue: args.item1, - label: args.item2, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => SingleFieldEditView( + initialValue: args.item1, + label: args.item2, + ), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -449,66 +417,50 @@ class RouteGenerator { if (args is String) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => MonkeyView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => MonkeyView(walletId: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); case CreateNewFrostMsWalletView.routeName: - if (args is ({ - String walletName, - FrostCurrency frostCurrency, - })) { + if (args is ({String walletName, FrostCurrency frostCurrency})) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => CreateNewFrostMsWalletView( - walletName: args.walletName, - frostCurrency: args.frostCurrency, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => CreateNewFrostMsWalletView( + walletName: args.walletName, + frostCurrency: args.frostCurrency, + ), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); case RestoreFrostMsWalletView.routeName: - if (args is ({ - String walletName, - FrostCurrency frostCurrency, - })) { + if (args is ({String walletName, FrostCurrency frostCurrency})) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => RestoreFrostMsWalletView( - walletName: args.walletName, - frostCurrency: args.frostCurrency, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => RestoreFrostMsWalletView( + walletName: args.walletName, + frostCurrency: args.frostCurrency, + ), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); case SelectNewFrostImportTypeView.routeName: - if (args is ({ - String walletName, - FrostCurrency frostCurrency, - })) { + if (args is ({String walletName, FrostCurrency frostCurrency})) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => SelectNewFrostImportTypeView( - walletName: args.walletName, - frostCurrency: args.frostCurrency, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => SelectNewFrostImportTypeView( + walletName: args.walletName, + frostCurrency: args.frostCurrency, + ), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -517,21 +469,15 @@ class RouteGenerator { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, builder: (_) => const FrostStepScaffold(), - settings: RouteSettings( - name: settings.name, - ), + settings: RouteSettings(name: settings.name), ); case FrostMSWalletOptionsView.routeName: if (args is String) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => FrostMSWalletOptionsView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => FrostMSWalletOptionsView(walletId: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -540,12 +486,8 @@ class RouteGenerator { if (args is String) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => FrostParticipantsView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => FrostParticipantsView(walletId: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -554,12 +496,8 @@ class RouteGenerator { if (args is String) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => InitiateResharingView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => InitiateResharingView(walletId: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -568,31 +506,23 @@ class RouteGenerator { if (args is ({String walletId, Map<String, int> resharers})) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => CompleteReshareConfigView( - walletId: args.walletId, - resharers: args.resharers, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => CompleteReshareConfigView( + walletId: args.walletId, + resharers: args.resharers, + ), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); case FrostSendView.routeName: - if (args is ({ - String walletId, - CryptoCurrency coin, - })) { + if (args is ({String walletId, CryptoCurrency coin})) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => FrostSendView( - walletId: args.walletId, - coin: args.coin, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => FrostSendView(walletId: args.walletId, coin: args.coin), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -616,27 +546,22 @@ class RouteGenerator { if (args is Tuple2<String, CoinControlViewType>) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => CoinControlView( - walletId: args.item1, - type: args.item2, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => CoinControlView(walletId: args.item1, type: args.item2), + settings: RouteSettings(name: settings.name), ); } else if (args is Tuple4<String, CoinControlViewType, Amount?, Set<UTXO>?>) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => CoinControlView( - walletId: args.item1, - type: args.item2, - requestedTotal: args.item3, - selectedUTXOs: args.item4, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => CoinControlView( + walletId: args.item1, + type: args.item2, + requestedTotal: args.item3, + selectedUTXOs: args.item4, + ), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -645,12 +570,8 @@ class RouteGenerator { if (args is String) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => OrdinalsView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => OrdinalsView(walletId: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -659,12 +580,8 @@ class RouteGenerator { if (args is String) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => DesktopOrdinalsView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => DesktopOrdinalsView(walletId: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -673,13 +590,12 @@ class RouteGenerator { if (args is ({Ordinal ordinal, String walletId})) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => OrdinalDetailsView( - walletId: args.walletId, - ordinal: args.ordinal, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => OrdinalDetailsView( + walletId: args.walletId, + ordinal: args.ordinal, + ), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -688,13 +604,12 @@ class RouteGenerator { if (args is ({Ordinal ordinal, String walletId})) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => DesktopOrdinalDetailsView( - walletId: args.walletId, - ordinal: args.ordinal, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => DesktopOrdinalDetailsView( + walletId: args.walletId, + ordinal: args.ordinal, + ), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -710,13 +625,10 @@ class RouteGenerator { if (args is Tuple2<Id, String>) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => UtxoDetailsView( - walletId: args.item2, - utxoId: args.item1, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => + UtxoDetailsView(walletId: args.item2, utxoId: args.item1), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -725,13 +637,8 @@ class RouteGenerator { if (args is (Id, String)) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => NameDetailsView( - walletId: args.$2, - utxoId: args.$1, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => NameDetailsView(walletId: args.$2, utxoId: args.$1), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -740,12 +647,8 @@ class RouteGenerator { if (args is String) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => PaynymClaimView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => PaynymClaimView(walletId: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -754,12 +657,8 @@ class RouteGenerator { if (args is String) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => PaynymHomeView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => PaynymHomeView(walletId: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -768,12 +667,8 @@ class RouteGenerator { if (args is String) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => AddNewPaynymFollowView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => AddNewPaynymFollowView(walletId: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -782,12 +677,8 @@ class RouteGenerator { if (args is String) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => CashFusionView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => CashFusionView(walletId: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -796,12 +687,8 @@ class RouteGenerator { if (args is String) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => NamecoinNamesHomeView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => NamecoinNamesHomeView(walletId: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -810,13 +697,132 @@ class RouteGenerator { if (args is ({String walletId, UTXO utxo})) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => ManageDomainView( - walletId: args.walletId, - utxo: args.utxo, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => + ManageDomainView(walletId: args.walletId, utxo: args.utxo), + settings: RouteSettings(name: settings.name), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + case SparkNamesHomeView.routeName: + if (args is String) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => SparkNamesHomeView(walletId: args), + settings: RouteSettings(name: settings.name), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + + case BuySparkNameView.routeName: + if (args is ({String walletId, String name})) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: + (_) => + BuySparkNameView(walletId: args.walletId, name: args.name), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -825,12 +831,8 @@ class RouteGenerator { if (args is String) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => FusionProgressView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => FusionProgressView(walletId: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -839,12 +841,8 @@ class RouteGenerator { if (args is String) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => ChurningView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => ChurningView(walletId: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -853,12 +851,8 @@ class RouteGenerator { if (args is String) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => ChurningProgressView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => ChurningProgressView(walletId: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -867,12 +861,8 @@ class RouteGenerator { if (args is String) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => DesktopCashFusionView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => DesktopCashFusionView(walletId: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -881,12 +871,8 @@ class RouteGenerator { if (args is String) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => DesktopChurningView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => DesktopChurningView(walletId: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -902,12 +888,8 @@ class RouteGenerator { if (args is CryptoCurrency) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => AddressBookView( - coin: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => AddressBookView(coin: args), + settings: RouteSettings(name: settings.name), ); } return getRoute( @@ -1011,13 +993,8 @@ class RouteGenerator { if (args is (String, ({List<XPub> xpubs, String fingerprint}))) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => XPubView( - walletId: args.$1, - xpubData: args.$2, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => XPubView(walletId: args.$1, xpubData: args.$2), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -1026,12 +1003,8 @@ class RouteGenerator { if (args is String) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => ChangeRepresentativeView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => ChangeRepresentativeView(walletId: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -1117,12 +1090,8 @@ class RouteGenerator { if (args is String) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => RestoreFromEncryptedStringView( - encrypted: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => RestoreFromEncryptedStringView(encrypted: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -1138,12 +1107,8 @@ class RouteGenerator { if (args is CryptoCurrency) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => EditCoinUnitsView( - coin: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => EditCoinUnitsView(coin: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -1173,12 +1138,8 @@ class RouteGenerator { if (args is CryptoCurrency) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => CoinNodesView( - coin: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => CoinNodesView(coin: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -1187,14 +1148,13 @@ class RouteGenerator { if (args is Tuple3<CryptoCurrency, String, String>) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => NodeDetailsView( - coin: args.item1, - nodeId: args.item2, - popRouteName: args.item3, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => NodeDetailsView( + coin: args.item1, + nodeId: args.item2, + popRouteName: args.item3, + ), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -1203,13 +1163,9 @@ class RouteGenerator { if (args is Tuple2<String, String>) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => EditNoteView( - txid: args.item1, - walletId: args.item2, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => EditNoteView(txid: args.item1, walletId: args.item2), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -1218,12 +1174,8 @@ class RouteGenerator { if (args is int) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => EditAddressLabelView( - addressLabelId: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => EditAddressLabelView(addressLabelId: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -1232,13 +1184,9 @@ class RouteGenerator { if (args is Tuple2<String, String>) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => EditTradeNoteView( - tradeId: args.item1, - note: args.item2, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => EditTradeNoteView(tradeId: args.item1, note: args.item2), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -1248,15 +1196,14 @@ class RouteGenerator { is Tuple4<AddEditNodeViewType, CryptoCurrency, String?, String>) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => AddEditNodeView( - viewType: args.item1, - coin: args.item2, - nodeId: args.item3, - routeOnSuccessOrDelete: args.item4, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => AddEditNodeView( + viewType: args.item1, + coin: args.item2, + nodeId: args.item3, + routeOnSuccessOrDelete: args.item4, + ), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -1265,12 +1212,8 @@ class RouteGenerator { if (args is String) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => ContactDetailsView( - contactId: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => ContactDetailsView(contactId: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -1279,12 +1222,8 @@ class RouteGenerator { if (args is String) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => AddNewContactAddressView( - contactId: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => AddNewContactAddressView(contactId: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -1293,12 +1232,8 @@ class RouteGenerator { if (args is String) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => EditContactNameEmojiView( - contactId: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => EditContactNameEmojiView(contactId: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -1307,13 +1242,12 @@ class RouteGenerator { if (args is Tuple2<String, ContactAddressEntry>) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => EditContactAddressView( - contactId: args.item1, - addressEntry: args.item2, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => EditContactAddressView( + contactId: args.item1, + addressEntry: args.item2, + ), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -1322,23 +1256,20 @@ class RouteGenerator { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, builder: (_) => const SystemBrightnessThemeSelectionView(), - settings: RouteSettings( - name: settings.name, - ), + settings: RouteSettings(name: settings.name), ); case WalletNetworkSettingsView.routeName: if (args is Tuple3<String, WalletSyncStatus, NodeConnectionStatus>) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => WalletNetworkSettingsView( - walletId: args.item1, - initialSyncStatus: args.item2, - initialNodeStatus: args.item3, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => WalletNetworkSettingsView( + walletId: args.item1, + initialSyncStatus: args.item2, + initialNodeStatus: args.item3, + ), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -1347,91 +1278,88 @@ class RouteGenerator { if (args is ({String walletId, List<String> mnemonic})) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => WalletBackupView( - walletId: args.walletId, - mnemonic: args.mnemonic, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => WalletBackupView( + walletId: args.walletId, + mnemonic: args.mnemonic, + ), + settings: RouteSettings(name: settings.name), ); - } else if (args is ({ - String walletId, - List<String> mnemonic, - ({ - String myName, - String config, - String keys, - ({String config, String keys})? prevGen, - })? frostWalletData, - })) { + } else if (args + is ({ + String walletId, + List<String> mnemonic, + ({ + String myName, + String config, + String keys, + ({String config, String keys})? prevGen, + })? + frostWalletData, + })) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => WalletBackupView( - walletId: args.walletId, - mnemonic: args.mnemonic, - frostWalletData: args.frostWalletData, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => WalletBackupView( + walletId: args.walletId, + mnemonic: args.mnemonic, + frostWalletData: args.frostWalletData, + ), + settings: RouteSettings(name: settings.name), ); - } else if (args is ({ - String walletId, - List<String> mnemonic, - KeyDataInterface? keyData, - })) { + } else if (args + is ({ + String walletId, + List<String> mnemonic, + KeyDataInterface? keyData, + })) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => WalletBackupView( - walletId: args.walletId, - mnemonic: args.mnemonic, - keyData: args.keyData, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => WalletBackupView( + walletId: args.walletId, + mnemonic: args.mnemonic, + keyData: args.keyData, + ), + settings: RouteSettings(name: settings.name), ); - } else if (args is ({ - String walletId, - List<String> mnemonic, - KeyDataInterface? keyData, - ({ - String myName, - String config, - String keys, - ({String config, String keys})? prevGen, - })? frostWalletData, - })) { + } else if (args + is ({ + String walletId, + List<String> mnemonic, + KeyDataInterface? keyData, + ({ + String myName, + String config, + String keys, + ({String config, String keys})? prevGen, + })? + frostWalletData, + })) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => WalletBackupView( - walletId: args.walletId, - mnemonic: args.mnemonic, - frostWalletData: args.frostWalletData, - keyData: args.keyData, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => WalletBackupView( + walletId: args.walletId, + mnemonic: args.mnemonic, + frostWalletData: args.frostWalletData, + keyData: args.keyData, + ), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); case MobileKeyDataView.routeName: - if (args is ({ - String walletId, - KeyDataInterface keyData, - })) { + if (args is ({String walletId, KeyDataInterface keyData})) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => MobileKeyDataView( - walletId: args.walletId, - keyData: args.keyData, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => MobileKeyDataView( + walletId: args.walletId, + keyData: args.keyData, + ), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -1440,12 +1368,8 @@ class RouteGenerator { if (args is String) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => WalletSettingsWalletSettingsView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => WalletSettingsWalletSettingsView(walletId: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -1454,12 +1378,8 @@ class RouteGenerator { if (args is String) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => RenameWalletView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => RenameWalletView(walletId: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -1468,12 +1388,8 @@ class RouteGenerator { if (args is String) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => DeleteWalletWarningView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => DeleteWalletWarningView(walletId: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -1482,12 +1398,8 @@ class RouteGenerator { if (args is AddWalletListEntity) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => CreateOrRestoreWalletView( - entity: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => CreateOrRestoreWalletView(entity: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -1496,13 +1408,12 @@ class RouteGenerator { if (args is Tuple2<AddWalletType, CryptoCurrency>) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => NameYourWalletView( - addWalletType: args.item1, - coin: args.item2, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => NameYourWalletView( + addWalletType: args.item1, + coin: args.item2, + ), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -1511,13 +1422,12 @@ class RouteGenerator { if (args is Tuple2<String, CryptoCurrency>) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => NewWalletRecoveryPhraseWarningView( - walletName: args.item1, - coin: args.item2, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => NewWalletRecoveryPhraseWarningView( + walletName: args.item1, + coin: args.item2, + ), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -1526,13 +1436,12 @@ class RouteGenerator { if (args is Tuple2<String, CryptoCurrency>) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => RestoreOptionsView( - walletName: args.item1, - coin: args.item2, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => RestoreOptionsView( + walletName: args.item1, + coin: args.item2, + ), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -1541,13 +1450,12 @@ class RouteGenerator { if (args is Tuple2<String, CryptoCurrency>) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => NewWalletOptionsView( - walletName: args.item1, - coin: args.item2, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => NewWalletOptionsView( + walletName: args.item1, + coin: args.item2, + ), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -1557,39 +1465,38 @@ class RouteGenerator { is Tuple6<String, CryptoCurrency, int, DateTime?, String, bool>) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => RestoreWalletView( - walletName: args.item1, - coin: args.item2, - seedWordsLength: args.item3, - restoreFromDate: args.item4, - mnemonicPassphrase: args.item5, - enableLelantusScanning: args.item6 ?? false, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => RestoreWalletView( + walletName: args.item1, + coin: args.item2, + seedWordsLength: args.item3, + restoreFromDate: args.item4, + mnemonicPassphrase: args.item5, + enableLelantusScanning: args.item6 ?? false, + ), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); case RestoreViewOnlyWalletView.routeName: - if (args is ({ - String walletName, - CryptoCurrency coin, - DateTime? restoreFromDate, - bool enableLelantusScanning, - })) { + if (args + is ({ + String walletName, + CryptoCurrency coin, + DateTime? restoreFromDate, + bool enableLelantusScanning, + })) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => RestoreViewOnlyWalletView( - walletName: args.walletName, - coin: args.coin, - restoreFromDate: args.restoreFromDate, - enableLelantusScanning: args.enableLelantusScanning, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => RestoreViewOnlyWalletView( + walletName: args.walletName, + coin: args.coin, + restoreFromDate: args.restoreFromDate, + enableLelantusScanning: args.enableLelantusScanning, + ), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -1598,13 +1505,12 @@ class RouteGenerator { if (args is Tuple2<Wallet, List<String>>) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => NewWalletRecoveryPhraseView( - wallet: args.item1, - mnemonic: args.item2, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => NewWalletRecoveryPhraseView( + wallet: args.item1, + mnemonic: args.item2, + ), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -1613,13 +1519,12 @@ class RouteGenerator { if (args is Tuple2<Wallet, List<String>>) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => VerifyRecoveryPhraseView( - wallet: args.item1, - mnemonic: args.item2, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => VerifyRecoveryPhraseView( + wallet: args.item1, + mnemonic: args.item2, + ), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -1634,12 +1539,8 @@ class RouteGenerator { if (args is String) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => WalletView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => WalletView(walletId: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -1648,54 +1549,49 @@ class RouteGenerator { if (args is Tuple3<Transaction, CryptoCurrency, String>) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => TransactionDetailsView( - transaction: args.item1, - coin: args.item2, - walletId: args.item3, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => TransactionDetailsView( + transaction: args.item1, + coin: args.item2, + walletId: args.item3, + ), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); case TransactionV2DetailsView.routeName: - if (args is ({ - TransactionV2 tx, - CryptoCurrency coin, - String walletId - })) { + if (args + is ({TransactionV2 tx, CryptoCurrency coin, String walletId})) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => TransactionV2DetailsView( - transaction: args.tx, - coin: args.coin, - walletId: args.walletId, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => TransactionV2DetailsView( + transaction: args.tx, + coin: args.coin, + walletId: args.walletId, + ), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); case FusionGroupDetailsView.routeName: - if (args is ({ - List<TransactionV2> transactions, - CryptoCurrency coin, - String walletId - })) { + if (args + is ({ + List<TransactionV2> transactions, + CryptoCurrency coin, + String walletId, + })) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => FusionGroupDetailsView( - transactions: args.transactions, - coin: args.coin, - walletId: args.walletId, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => FusionGroupDetailsView( + transactions: args.transactions, + coin: args.coin, + walletId: args.walletId, + ), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -1704,12 +1600,8 @@ class RouteGenerator { if (args is String) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => AllTransactionsView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => AllTransactionsView(walletId: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -1718,24 +1610,19 @@ class RouteGenerator { if (args is String) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => AllTransactionsV2View( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => AllTransactionsV2View(walletId: args), + settings: RouteSettings(name: settings.name), ); } if (args is ({String walletId, String contractAddress})) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => AllTransactionsV2View( - walletId: args.walletId, - contractAddress: args.contractAddress, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => AllTransactionsV2View( + walletId: args.walletId, + contractAddress: args.contractAddress, + ), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -1744,12 +1631,8 @@ class RouteGenerator { if (args is CryptoCurrency) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => TransactionSearchFilterView( - coin: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => TransactionSearchFilterView(coin: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -1758,23 +1641,18 @@ class RouteGenerator { if (args is String) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => ReceiveView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => ReceiveView(walletId: args), + settings: RouteSettings(name: settings.name), ); } else if (args is Tuple2<String, EthContract?>) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => ReceiveView( - walletId: args.item1, - tokenContract: args.item2, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => ReceiveView( + walletId: args.item1, + tokenContract: args.item2, + ), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -1783,12 +1661,8 @@ class RouteGenerator { if (args is String) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => WalletAddressesView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => WalletAddressesView(walletId: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -1797,13 +1671,12 @@ class RouteGenerator { if (args is Tuple2<Id, String>) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => AddressDetailsView( - walletId: args.item2, - addressId: args.item1, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => AddressDetailsView( + walletId: args.item2, + addressId: args.item1, + ), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -1812,49 +1685,37 @@ class RouteGenerator { if (args is Tuple2<String, CryptoCurrency>) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => SendView( - walletId: args.item1, - coin: args.item2, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => SendView(walletId: args.item1, coin: args.item2), + settings: RouteSettings(name: settings.name), ); } else if (args is Tuple3<String, CryptoCurrency, SendViewAutoFillData>) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => SendView( - walletId: args.item1, - coin: args.item2, - autoFillData: args.item3, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => SendView( + walletId: args.item1, + coin: args.item2, + autoFillData: args.item3, + ), + settings: RouteSettings(name: settings.name), ); } else if (args is Tuple3<String, CryptoCurrency, PaynymAccountLite>) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => SendView( - walletId: args.item1, - coin: args.item2, - accountLite: args.item3, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => SendView( + walletId: args.item1, + coin: args.item2, + accountLite: args.item3, + ), + settings: RouteSettings(name: settings.name), ); } else if (args is ({CryptoCurrency coin, String walletId})) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => SendView( - walletId: args.walletId, - coin: args.coin, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => SendView(walletId: args.walletId, coin: args.coin), + settings: RouteSettings(name: settings.name), ); } @@ -1864,14 +1725,13 @@ class RouteGenerator { if (args is Tuple3<String, CryptoCurrency, EthContract>) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => TokenSendView( - walletId: args.item1, - coin: args.item2, - tokenContract: args.item3, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => TokenSendView( + walletId: args.item1, + coin: args.item2, + tokenContract: args.item3, + ), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -1880,14 +1740,13 @@ class RouteGenerator { if (args is (TxData, String, VoidCallback)) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => ConfirmTransactionView( - txData: args.$1, - walletId: args.$2, - onSuccess: args.$3, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => ConfirmTransactionView( + txData: args.$1, + walletId: args.$2, + onSuccess: args.$3, + ), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -1896,13 +1755,12 @@ class RouteGenerator { if (args is (TxData, String)) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => ConfirmNameTransactionView( - txData: args.$1, - walletId: args.$2, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => ConfirmNameTransactionView( + txData: args.$1, + walletId: args.$2, + ), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -1911,40 +1769,38 @@ class RouteGenerator { if (args is Tuple2<String, CryptoCurrency>) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => Stack( - children: [ - WalletInitiatedExchangeView( - walletId: args.item1, - coin: args.item2, + builder: + (_) => Stack( + children: [ + WalletInitiatedExchangeView( + walletId: args.item1, + coin: args.item2, + ), + // ExchangeLoadingOverlayView( + // unawaitedLoad: args.item3, + // ), + ], ), - // ExchangeLoadingOverlayView( - // unawaitedLoad: args.item3, - // ), - ], - ), - settings: RouteSettings( - name: settings.name, - ), + settings: RouteSettings(name: settings.name), ); } if (args is Tuple3<String, CryptoCurrency, EthContract?>) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => Stack( - children: [ - WalletInitiatedExchangeView( - walletId: args.item1, - coin: args.item2, - contract: args.item3, + builder: + (_) => Stack( + children: [ + WalletInitiatedExchangeView( + walletId: args.item1, + coin: args.item2, + contract: args.item3, + ), + // ExchangeLoadingOverlayView( + // unawaitedLoad: args.item3, + // ), + ], ), - // ExchangeLoadingOverlayView( - // unawaitedLoad: args.item3, - // ), - ], - ), - settings: RouteSettings( - name: settings.name, - ), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -1953,30 +1809,30 @@ class RouteGenerator { if (args is String?) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => NotificationsView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => NotificationsView(walletId: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); case WalletSettingsView.routeName: - if (args is Tuple4<String, CryptoCurrency, WalletSyncStatus, - NodeConnectionStatus>) { + if (args + is Tuple4< + String, + CryptoCurrency, + WalletSyncStatus, + NodeConnectionStatus + >) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => WalletSettingsView( - walletId: args.item1, - coin: args.item2, - initialSyncStatus: args.item3, - initialNodeStatus: args.item4, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => WalletSettingsView( + walletId: args.item1, + coin: args.item2, + initialSyncStatus: args.item3, + initialNodeStatus: args.item4, + ), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -1985,34 +1841,34 @@ class RouteGenerator { if (args is ({String walletId, List<String> mnemonicWords})) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => DeleteWalletRecoveryPhraseView( - mnemonic: args.mnemonicWords, - walletId: args.walletId, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => DeleteWalletRecoveryPhraseView( + mnemonic: args.mnemonicWords, + walletId: args.walletId, + ), + settings: RouteSettings(name: settings.name), ); - } else if (args is ({ - String walletId, - List<String> mnemonicWords, - ({ - String myName, - String config, - String keys, - ({String config, String keys})? prevGen, - })? frostWalletData, - })) { + } else if (args + is ({ + String walletId, + List<String> mnemonicWords, + ({ + String myName, + String config, + String keys, + ({String config, String keys})? prevGen, + })? + frostWalletData, + })) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => DeleteWalletRecoveryPhraseView( - mnemonic: args.mnemonicWords, - walletId: args.walletId, - frostWalletData: args.frostWalletData, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => DeleteWalletRecoveryPhraseView( + mnemonic: args.mnemonicWords, + walletId: args.walletId, + frostWalletData: args.frostWalletData, + ), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -2021,13 +1877,12 @@ class RouteGenerator { if (args is ({String walletId, ViewOnlyWalletData data})) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => DeleteViewOnlyWalletKeysView( - data: args.data, - walletId: args.walletId, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => DeleteViewOnlyWalletKeysView( + data: args.data, + walletId: args.walletId, + ), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -2038,12 +1893,8 @@ class RouteGenerator { if (args is IncompleteExchangeModel) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => Step1View( - model: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => Step1View(model: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -2052,12 +1903,8 @@ class RouteGenerator { if (args is IncompleteExchangeModel) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => Step2View( - model: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => Step2View(model: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -2066,12 +1913,8 @@ class RouteGenerator { if (args is IncompleteExchangeModel) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => Step3View( - model: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => Step3View(model: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -2080,12 +1923,8 @@ class RouteGenerator { if (args is IncompleteExchangeModel) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => Step4View( - model: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => Step4View(model: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -2094,15 +1933,14 @@ class RouteGenerator { if (args is Tuple4<String, Transaction?, String?, String?>) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => TradeDetailsView( - tradeId: args.item1, - transactionIfSentFromStack: args.item2, - walletId: args.item3, - walletName: args.item4, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => TradeDetailsView( + tradeId: args.item1, + transactionIfSentFromStack: args.item2, + walletId: args.item3, + walletName: args.item4, + ), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -2111,12 +1949,8 @@ class RouteGenerator { if (args is CryptoCurrency) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => ChooseFromStackView( - coin: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => ChooseFromStackView(coin: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -2125,15 +1959,14 @@ class RouteGenerator { if (args is Tuple4<CryptoCurrency, Amount, String, Trade>) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => SendFromView( - coin: args.item1, - amount: args.item2, - trade: args.item4, - address: args.item3, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => SendFromView( + coin: args.item1, + amount: args.item2, + trade: args.item4, + address: args.item3, + ), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -2142,13 +1975,12 @@ class RouteGenerator { if (args is Tuple2<CryptoCurrency, String>) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => GenerateUriQrCodeView( - coin: args.item1, - receivingAddress: args.item2, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => GenerateUriQrCodeView( + coin: args.item1, + receivingAddress: args.item2, + ), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -2157,12 +1989,8 @@ class RouteGenerator { if (args is SimplexQuote) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => BuyQuotePreviewView( - quote: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => BuyQuotePreviewView(quote: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -2172,9 +2000,7 @@ class RouteGenerator { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, builder: (_) => LelantusSettingsView(walletId: args), - settings: RouteSettings( - name: settings.name, - ), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -2184,9 +2010,7 @@ class RouteGenerator { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, builder: (_) => RbfSettingsView(walletId: args), - settings: RouteSettings( - name: settings.name, - ), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -2195,12 +2019,8 @@ class RouteGenerator { if (args is String) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => SparkInfoView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => SparkInfoView(walletId: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -2209,12 +2029,8 @@ class RouteGenerator { if (args is String) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => EditRefreshHeightView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => EditRefreshHeightView(walletId: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -2223,13 +2039,12 @@ class RouteGenerator { if (args is ({String walletId, String domainName})) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => BuyDomainView( - walletId: args.walletId, - domainName: args.domainName, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => BuyDomainView( + walletId: args.walletId, + domainName: args.domainName, + ), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -2239,12 +2054,8 @@ class RouteGenerator { if (args is bool) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => CreatePasswordView( - restoreFromSWB: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => CreatePasswordView(restoreFromSWB: args), + settings: RouteSettings(name: settings.name), ); } return getRoute( @@ -2271,12 +2082,8 @@ class RouteGenerator { if (args is bool) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => DeletePasswordWarningView( - shouldCreateNew: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => DeletePasswordWarningView(shouldCreateNew: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -2314,21 +2121,15 @@ class RouteGenerator { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, builder: (_) => BuyInWalletView(coin: args), - settings: RouteSettings( - name: settings.name, - ), + settings: RouteSettings(name: settings.name), ); } if (args is Tuple2<CryptoCurrency, EthContract?>) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => BuyInWalletView( - coin: args.item1, - contract: args.item2, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => BuyInWalletView(coin: args.item1, contract: args.item2), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -2365,12 +2166,8 @@ class RouteGenerator { if (args is String) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => DesktopWalletView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => DesktopWalletView(walletId: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -2379,12 +2176,8 @@ class RouteGenerator { if (args is String) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => DesktopWalletAddressesView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => DesktopWalletAddressesView(walletId: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -2393,12 +2186,8 @@ class RouteGenerator { if (args is String) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => LelantusCoinsView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => LelantusCoinsView(walletId: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -2407,12 +2196,8 @@ class RouteGenerator { if (args is String) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => SparkCoinsView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => SparkCoinsView(walletId: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -2421,12 +2206,8 @@ class RouteGenerator { if (args is String) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => DesktopCoinControlView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => DesktopCoinControlView(walletId: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -2435,12 +2216,8 @@ class RouteGenerator { if (args is TransactionV2) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => BoostTransactionView( - transaction: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => BoostTransactionView(transaction: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -2530,27 +2307,27 @@ class RouteGenerator { ); case WalletKeysDesktopPopup.routeName: - if (args is ({ - List<String> mnemonic, - String walletId, - ({String keys, String config})? frostData - })) { + if (args + is ({ + List<String> mnemonic, + String walletId, + ({String keys, String config})? frostData, + })) { return FadePageRoute( WalletKeysDesktopPopup( words: args.mnemonic, walletId: args.walletId, frostData: args.frostData, ), - RouteSettings( - name: settings.name, - ), + RouteSettings(name: settings.name), ); - } else if (args is ({ - List<String> mnemonic, - String walletId, - ({String keys, String config})? frostData, - KeyDataInterface? keyData, - })) { + } else if (args + is ({ + List<String> mnemonic, + String walletId, + ({String keys, String config})? frostData, + KeyDataInterface? keyData, + })) { return FadePageRoute( WalletKeysDesktopPopup( words: args.mnemonic, @@ -2558,24 +2335,21 @@ class RouteGenerator { frostData: args.frostData, keyData: args.keyData, ), - RouteSettings( - name: settings.name, - ), + RouteSettings(name: settings.name), ); - } else if (args is ({ - List<String> mnemonic, - String walletId, - KeyDataInterface? keyData, - })) { + } else if (args + is ({ + List<String> mnemonic, + String walletId, + KeyDataInterface? keyData, + })) { return FadePageRoute( WalletKeysDesktopPopup( words: args.mnemonic, walletId: args.walletId, keyData: args.keyData, ), - RouteSettings( - name: settings.name, - ), + RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -2583,12 +2357,8 @@ class RouteGenerator { case UnlockWalletKeysDesktop.routeName: if (args is String) { return FadePageRoute( - UnlockWalletKeysDesktop( - walletId: args, - ), - RouteSettings( - name: settings.name, - ), + UnlockWalletKeysDesktop(walletId: args), + RouteSettings(name: settings.name), ); // return getRoute( // shouldUseMaterialRoute: useMaterialPageRoute, @@ -2605,12 +2375,8 @@ class RouteGenerator { case DesktopDeleteWalletDialog.routeName: if (args is String) { return FadePageRoute( - DesktopDeleteWalletDialog( - walletId: args, - ), - RouteSettings( - name: settings.name, - ), + DesktopDeleteWalletDialog(walletId: args), + RouteSettings(name: settings.name), ); // return getRoute( // shouldUseMaterialRoute: useMaterialPageRoute, @@ -2627,12 +2393,8 @@ class RouteGenerator { case DesktopAttentionDeleteWallet.routeName: if (args is String) { return FadePageRoute( - DesktopAttentionDeleteWallet( - walletId: args, - ), - RouteSettings( - name: settings.name, - ), + DesktopAttentionDeleteWallet(walletId: args), + RouteSettings(name: settings.name), ); // return getRoute( // shouldUseMaterialRoute: useMaterialPageRoute, @@ -2649,13 +2411,8 @@ class RouteGenerator { case DeleteWalletKeysPopup.routeName: if (args is Tuple2<String, List<String>>) { return FadePageRoute( - DeleteWalletKeysPopup( - walletId: args.item1, - words: args.item2, - ), - RouteSettings( - name: settings.name, - ), + DeleteWalletKeysPopup(walletId: args.item1, words: args.item2), + RouteSettings(name: settings.name), ); // return getRoute( // shouldUseMaterialRoute: useMaterialPageRoute, @@ -2672,12 +2429,8 @@ class RouteGenerator { case QRCodeDesktopPopupContent.routeName: if (args is String) { return FadePageRoute( - QRCodeDesktopPopupContent( - value: args, - ), - RouteSettings( - name: settings.name, - ), + QRCodeDesktopPopupContent(value: args), + RouteSettings(name: settings.name), ); // return getRoute( // shouldUseMaterialRoute: useMaterialPageRoute, @@ -2695,12 +2448,8 @@ class RouteGenerator { if (args is String) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => MyTokensView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => MyTokensView(walletId: args), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -2723,23 +2472,18 @@ class RouteGenerator { if (args is String) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => TokenView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: (_) => TokenView(walletId: args), + settings: RouteSettings(name: settings.name), ); } else if (args is ({String walletId, bool popPrevious})) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => TokenView( - walletId: args.walletId, - popPrevious: args.popPrevious, - ), - settings: RouteSettings( - name: settings.name, - ), + builder: + (_) => TokenView( + walletId: args.walletId, + popPrevious: args.popPrevious, + ), + settings: RouteSettings(name: settings.name), ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -2785,13 +2529,12 @@ class RouteGenerator { final end = Offset.zero; final curve = Curves.easeInOut; - final tween = - Tween(begin: begin, end: end).chain(CurveTween(curve: curve)); + final tween = Tween( + begin: begin, + end: end, + ).chain(CurveTween(curve: curve)); - return SlideTransition( - position: animation.drive(tween), - child: child, - ); + return SlideTransition(position: animation.drive(tween), child: child); }, ); } @@ -2835,10 +2578,7 @@ class FadePageRoute<T> extends PageRoute<T> { Animation<double> animation, Animation<double> secondaryAnimation, ) { - return FadeTransition( - opacity: animation, - child: child, - ); + return FadeTransition(opacity: animation, child: child); } @override diff --git a/lib/wallets/crypto_currency/coins/firo.dart b/lib/wallets/crypto_currency/coins/firo.dart index 26957600d..79ed783f8 100644 --- a/lib/wallets/crypto_currency/coins/firo.dart +++ b/lib/wallets/crypto_currency/coins/firo.dart @@ -236,22 +236,9 @@ class Firo extends Bip39HDCurrency with ElectrumXCurrencyInterface { ); case CryptoCurrencyNetwork.test: - // NodeModel( - // host: "firo-testnet.stackwallet.com", - // port: 50002, - // name: DefaultNodes.defaultName, - // id: _nodeId(Coin.firoTestNet), - // useSSL: true, - // enabled: true, - // coinName: Coin.firoTestNet.name, - // isFailover: true, - // isDown: false, - // ); - - // TODO revert to above eventually return NodeModel( - host: "95.179.164.13", - port: 51002, + host: "firo-testnet.stackwallet.com", + port: 50002, name: DefaultNodes.defaultName, id: DefaultNodes.buildId(this), useSSL: true, diff --git a/lib/wallets/models/tx_data.dart b/lib/wallets/models/tx_data.dart index 94474e390..13430294b 100644 --- a/lib/wallets/models/tx_data.dart +++ b/lib/wallets/models/tx_data.dart @@ -64,15 +64,17 @@ class TxData { final tezart.OperationsList? tezosOperationsList; // firo spark specific - final List< - ({ - String address, - Amount amount, - String memo, - bool isChange, - })>? sparkRecipients; + final List<({String address, Amount amount, String memo, bool isChange})>? + sparkRecipients; final List<TxData>? sparkMints; final List<SparkCoin>? usedSparkCoins; + final ({ + String additionalInfo, + String name, + Address sparkAddress, + int validBlocks, + })? + sparkNameInfo; // xelis specific final String? otherData; @@ -122,6 +124,7 @@ class TxData { this.tempTx, this.ignoreCachedBalanceChecks = false, this.opNameState, + this.sparkNameInfo, }); Amount? get amount { @@ -201,9 +204,10 @@ class TxData { } } - int? get estimatedSatsPerVByte => fee != null && vSize != null - ? (fee!.raw ~/ BigInt.from(vSize!)).toInt() - : null; + int? get estimatedSatsPerVByte => + fee != null && vSize != null + ? (fee!.raw ~/ BigInt.from(vSize!)).toInt() + : null; TxData copyWith({ FeeRateType? feeRateType, @@ -237,19 +241,20 @@ class TxData { TransactionSubType? txSubType, List<Map<String, dynamic>>? mintsMapLelantus, tezart.OperationsList? tezosOperationsList, - List< - ({ - String address, - Amount amount, - String memo, - bool isChange, - })>? - sparkRecipients, + List<({String address, Amount amount, String memo, bool isChange})>? + sparkRecipients, List<TxData>? sparkMints, List<SparkCoin>? usedSparkCoins, TransactionV2? tempTx, bool? ignoreCachedBalanceChecks, NameOpState? opNameState, + ({ + String additionalInfo, + String name, + Address sparkAddress, + int validBlocks, + })? + sparkNameInfo, }) { return TxData( feeRateType: feeRateType ?? this.feeRateType, @@ -290,11 +295,13 @@ class TxData { ignoreCachedBalanceChecks: ignoreCachedBalanceChecks ?? this.ignoreCachedBalanceChecks, opNameState: opNameState ?? this.opNameState, + sparkNameInfo: sparkNameInfo ?? this.sparkNameInfo, ); } @override - String toString() => 'TxData{' + String toString() => + 'TxData{' 'feeRateType: $feeRateType, ' 'feeRateAmount: $feeRateAmount, ' 'satsPerVByte: $satsPerVByte, ' @@ -331,5 +338,6 @@ class TxData { 'tempTx: $tempTx, ' 'ignoreCachedBalanceChecks: $ignoreCachedBalanceChecks, ' 'opNameState: $opNameState, ' + 'sparkNameInfo: $sparkNameInfo, ' '}'; } diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart index 102d6a753..d2d0a058c 100644 --- a/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart +++ b/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart @@ -26,6 +26,7 @@ import '../../../utilities/enums/derive_path_type_enum.dart'; import '../../../utilities/extensions/extensions.dart'; import '../../../utilities/logger.dart'; import '../../../utilities/prefs.dart'; +import '../../crypto_currency/crypto_currency.dart'; import '../../crypto_currency/interfaces/electrumx_currency_interface.dart'; import '../../isar/models/spark_coin.dart'; import '../../isar/models/wallet_info.dart'; @@ -639,6 +640,26 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface> } extractedTx.setPayload(spend.serializedSpendPayload); + + if (txData.sparkNameInfo != null) { + // this is name reg tx + + final nameScript = LibSpark.createSparkNameScript( + sparkNameValidityBlocks: txData.sparkNameInfo!.validBlocks, + name: txData.sparkNameInfo!.name, + additionalInfo: txData.sparkNameInfo!.additionalInfo, + scalarHex: extractedTx.getHash().toHex, + privateKeyHex: privateKey.toHex, + spendKeyIndex: kDefaultSparkIndex, + diversifier: txData.sparkNameInfo!.sparkAddress.derivationIndex, + isTestNet: cryptoCurrency.network != CryptoCurrencyNetwork.main, + ); + + extractedTx.setPayload( + Uint8List.fromList([...spend.serializedSpendPayload, ...nameScript]), + ); + } + final rawTxHex = extractedTx.toHex(); if (isSendAll) { @@ -1975,6 +1996,80 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface> return txData.copyWith(sparkMints: await Future.wait(futures)); } + Future<TxData> prepareSparkNameTransaction({ + required String name, + required String address, + required int years, + required String additionalInfo, + }) async { + if (years < 1 || years > kMaxNameRegistrationLengthYears) { + throw Exception("Invalid spark name registration period years: $years"); + } + + if (name.isEmpty || name.length > kMaxNameLength) { + throw Exception("Invalid spark name length: ${name.length}"); + } + if (!RegExp(kNameRegexString).hasMatch(name)) { + throw Exception("Invalid symbols found in spark name: $name"); + } + + if (additionalInfo.toUint8ListFromUtf8.length > + kMaxAdditionalInfoLengthBytes) { + throw Exception( + "Additional info exceeds $kMaxAdditionalInfoLengthBytes bytes.", + ); + } + + final sparkAddress = await mainDB.getAddress(walletId, address); + if (sparkAddress == null) { + throw Exception("Address '$address' not found in local DB."); + } + if (sparkAddress.type != AddressType.spark) { + throw Exception("Address '$address' is not a spark address."); + } + + final data = ( + name: name, + additionalInfo: additionalInfo, + validBlocks: years * 365 * 24 * 24, + sparkAddress: sparkAddress, + ); + + final String destinationAddress; + switch (cryptoCurrency.network) { + case CryptoCurrencyNetwork.main: + destinationAddress = kStage3DevelopmentFundAddressMainNet; + break; + + case CryptoCurrencyNetwork.test: + destinationAddress = kStage3DevelopmentFundAddressTestNet; + break; + + default: + throw Exception( + "Invalid network '${cryptoCurrency.network}' for spark name registration.", + ); + } + + final txData = await prepareSendSpark( + txData: TxData( + sparkNameInfo: data, + recipients: [ + ( + address: destinationAddress, + amount: Amount.fromDecimal( + Decimal.fromInt(kStandardSparkNamesFee[name.length] * years), + fractionDigits: cryptoCurrency.fractionDigits, + ), + isChange: false, + ), + ], + ), + ); + + return txData; + } + @override Future<void> updateBalance() async { // call to super to update transparent balance (and lelantus balance if