diff --git a/crypto_plugins/flutter_libmonero b/crypto_plugins/flutter_libmonero index f1cfc0cbb..dd2d49319 160000 --- a/crypto_plugins/flutter_libmonero +++ b/crypto_plugins/flutter_libmonero @@ -1 +1 @@ -Subproject commit f1cfc0cbb675c5e2d03c30152514fcbb28a465f4 +Subproject commit dd2d493199dd9c697abc8bc6bc94f466005bf70a diff --git a/lib/db/sqlite/firo_cache.dart b/lib/db/sqlite/firo_cache.dart index b30777643..eac511aaa 100644 --- a/lib/db/sqlite/firo_cache.dart +++ b/lib/db/sqlite/firo_cache.dart @@ -12,6 +12,7 @@ import '../../electrumx_rpc/electrumx_client.dart'; import '../../utilities/extensions/extensions.dart'; import '../../utilities/logger.dart'; import '../../utilities/stack_file_system.dart'; +import '../../wallets/crypto_currency/crypto_currency.dart'; part 'firo_cache_coordinator.dart'; part 'firo_cache_reader.dart'; @@ -31,29 +32,39 @@ void _debugLog(Object? object) { abstract class _FiroCache { static const int _setCacheVersion = 1; static const int _tagsCacheVersion = 2; - static const String sparkSetCacheFileName = - "spark_set_v$_setCacheVersion.sqlite3"; - static const String sparkUsedTagsCacheFileName = - "spark_tags_v$_tagsCacheVersion.sqlite3"; - static Database? _setCacheDB; - static Database? _usedTagsCacheDB; - static Database get setCacheDB { - if (_setCacheDB == null) { + static final networks = [ + CryptoCurrencyNetwork.main, + CryptoCurrencyNetwork.test, + ]; + + static String sparkSetCacheFileName(CryptoCurrencyNetwork network) => + network == CryptoCurrencyNetwork.main + ? "spark_set_v$_setCacheVersion.sqlite3" + : "spark_set_v${_setCacheVersion}_${network.name}.sqlite3"; + static String sparkUsedTagsCacheFileName(CryptoCurrencyNetwork network) => + network == CryptoCurrencyNetwork.main + ? "spark_tags_v$_tagsCacheVersion.sqlite3" + : "spark_tags_v${_tagsCacheVersion}_${network.name}.sqlite3"; + + static final Map _setCacheDB = {}; + static final Map _usedTagsCacheDB = {}; + static Database setCacheDB(CryptoCurrencyNetwork network) { + if (_setCacheDB[network] == null) { throw Exception( "FiroCache.init() must be called before accessing FiroCache.db!", ); } - return _setCacheDB!; + return _setCacheDB[network]!; } - static Database get usedTagsCacheDB { - if (_usedTagsCacheDB == null) { + static Database usedTagsCacheDB(CryptoCurrencyNetwork network) { + if (_usedTagsCacheDB[network] == null) { throw Exception( "FiroCache.init() must be called before accessing FiroCache.db!", ); } - return _usedTagsCacheDB!; + return _usedTagsCacheDB[network]!; } static Future? _initFuture; @@ -63,30 +74,34 @@ abstract class _FiroCache { final sqliteDir = await StackFileSystem.applicationFiroCacheSQLiteDirectory(); - final sparkSetCacheFile = File("${sqliteDir.path}/$sparkSetCacheFileName"); - final sparkUsedTagsCacheFile = - File("${sqliteDir.path}/$sparkUsedTagsCacheFileName"); + for (final network in networks) { + final sparkSetCacheFile = + File("${sqliteDir.path}/${sparkSetCacheFileName(network)}"); - if (!(await sparkSetCacheFile.exists())) { - await _createSparkSetCacheDb(sparkSetCacheFile.path); - } - if (!(await sparkUsedTagsCacheFile.exists())) { - await _createSparkUsedTagsCacheDb(sparkUsedTagsCacheFile.path); - } + final sparkUsedTagsCacheFile = + File("${sqliteDir.path}/${sparkUsedTagsCacheFileName(network)}"); - _setCacheDB = sqlite3.open( - sparkSetCacheFile.path, - mode: OpenMode.readWrite, - ); - _usedTagsCacheDB = sqlite3.open( - sparkUsedTagsCacheFile.path, - mode: OpenMode.readWrite, - ); + if (!(await sparkSetCacheFile.exists())) { + await _createSparkSetCacheDb(sparkSetCacheFile.path); + } + if (!(await sparkUsedTagsCacheFile.exists())) { + await _createSparkUsedTagsCacheDb(sparkUsedTagsCacheFile.path); + } + + _setCacheDB[network] = sqlite3.open( + sparkSetCacheFile.path, + mode: OpenMode.readWrite, + ); + _usedTagsCacheDB[network] = sqlite3.open( + sparkUsedTagsCacheFile.path, + mode: OpenMode.readWrite, + ); + } } - static Future _deleteAllCache() async { + static Future _deleteAllCache(CryptoCurrencyNetwork network) async { final start = DateTime.now(); - setCacheDB.execute( + setCacheDB(network).execute( """ DELETE FROM SparkSet; DELETE FROM SparkCoin; @@ -94,7 +109,7 @@ abstract class _FiroCache { VACUUM; """, ); - usedTagsCacheDB.execute( + usedTagsCacheDB(network).execute( """ DELETE FROM SparkUsedCoinTags; VACUUM; diff --git a/lib/db/sqlite/firo_cache_coordinator.dart b/lib/db/sqlite/firo_cache_coordinator.dart index b2b39916a..fe720f804 100644 --- a/lib/db/sqlite/firo_cache_coordinator.dart +++ b/lib/db/sqlite/firo_cache_coordinator.dart @@ -5,7 +5,7 @@ typedef LTagPair = ({String tag, String txid}); /// Wrapper class for [_FiroCache] as [_FiroCache] should eventually be handled in a /// background isolate and [FiroCacheCoordinator] should manage that isolate abstract class FiroCacheCoordinator { - static _FiroCacheWorker? _worker; + static final Map _workers = {}; static bool _init = false; static Future init() async { @@ -14,20 +14,22 @@ abstract class FiroCacheCoordinator { } _init = true; await _FiroCache.init(); - _worker = await _FiroCacheWorker.spawn(); + for (final network in _FiroCache.networks) { + _workers[network] = await _FiroCacheWorker.spawn(network); + } } - static Future clearSharedCache() async { - return await _FiroCache._deleteAllCache(); + static Future clearSharedCache(CryptoCurrencyNetwork network) async { + return await _FiroCache._deleteAllCache(network); } - static Future getSparkCacheSize() async { + static Future getSparkCacheSize(CryptoCurrencyNetwork network) async { final dir = await StackFileSystem.applicationFiroCacheSQLiteDirectory(); final setCacheFile = File( - "${dir.path}/${_FiroCache.sparkSetCacheFileName}", + "${dir.path}/${_FiroCache.sparkSetCacheFileName(network)}", ); final usedTagsCacheFile = File( - "${dir.path}/${_FiroCache.sparkUsedTagsCacheFileName}", + "${dir.path}/${_FiroCache.sparkUsedTagsCacheFileName(network)}", ); final int bytes = ((await setCacheFile.exists()) ? await setCacheFile.length() : 0) + @@ -51,13 +53,14 @@ abstract class FiroCacheCoordinator { static Future runFetchAndUpdateSparkUsedCoinTags( ElectrumXClient client, + CryptoCurrencyNetwork network, ) async { - final count = await FiroCacheCoordinator.getUsedCoinTagsCount(); + final count = await FiroCacheCoordinator.getUsedCoinTagsCount(network); final unhashedTags = await client.getSparkUnhashedUsedCoinsTagsWithTxHashes( startNumber: count, ); if (unhashedTags.isNotEmpty) { - await _worker!.runTask( + await _workers[network]!.runTask( FCTask( func: FCFuncName._updateSparkUsedTagsWith, data: unhashedTags, @@ -69,10 +72,12 @@ abstract class FiroCacheCoordinator { static Future runFetchAndUpdateSparkAnonSetCacheForGroupId( int groupId, ElectrumXClient client, + CryptoCurrencyNetwork network, ) async { final blockhashResult = await FiroCacheCoordinator.getLatestSetInfoForGroupId( groupId, + network, ); final blockHash = blockhashResult?.blockHash ?? ""; @@ -81,7 +86,7 @@ abstract class FiroCacheCoordinator { startBlockHash: blockHash.toHexReversedFromBase64, ); - await _worker!.runTask( + await _workers[network]!.runTask( FCTask( func: FCFuncName._updateSparkAnonSetCoinsWith, data: (groupId, json), @@ -91,17 +96,22 @@ abstract class FiroCacheCoordinator { // =========================================================================== - static Future> getUsedCoinTags(int startNumber) async { + static Future> getUsedCoinTags( + int startNumber, + CryptoCurrencyNetwork network, + ) async { final result = await _Reader._getSparkUsedCoinTags( startNumber, - db: _FiroCache.usedTagsCacheDB, + db: _FiroCache.usedTagsCacheDB(network), ); return result.map((e) => e["tag"] as String).toSet(); } - static Future getUsedCoinTagsCount() async { + static Future getUsedCoinTagsCount( + CryptoCurrencyNetwork network, + ) async { final result = await _Reader._getUsedCoinTagsCount( - db: _FiroCache.usedTagsCacheDB, + db: _FiroCache.usedTagsCacheDB(network), ); if (result.isEmpty) { return 0; @@ -111,13 +121,14 @@ abstract class FiroCacheCoordinator { static Future> getUsedCoinTxidsFor({ required List tags, + required CryptoCurrencyNetwork network, }) async { if (tags.isEmpty) { return []; } final result = await _Reader._getUsedCoinTxidsFor( tags, - db: _FiroCache.usedTagsCacheDB, + db: _FiroCache.usedTagsCacheDB(network), ); if (result.isEmpty) { @@ -135,20 +146,22 @@ abstract class FiroCacheCoordinator { static Future> getUsedCoinTagsFor({ required String txid, + required CryptoCurrencyNetwork network, }) async { final result = await _Reader._getUsedCoinTagsFor( txid, - db: _FiroCache.usedTagsCacheDB, + db: _FiroCache.usedTagsCacheDB(network), ); return result.map((e) => e["tag"] as String).toSet(); } static Future checkTagIsUsed( String tag, + CryptoCurrencyNetwork network, ) async { return await _Reader._checkTagIsUsed( tag, - db: _FiroCache.usedTagsCacheDB, + db: _FiroCache.usedTagsCacheDB(network), ); } @@ -161,10 +174,11 @@ abstract class FiroCacheCoordinator { })>> getSetCoinsForGroupId( int groupId, { int? newerThanTimeStamp, + required CryptoCurrencyNetwork network, }) async { final resultSet = await _Reader._getSetCoinsForGroupId( groupId, - db: _FiroCache.setCacheDB, + db: _FiroCache.setCacheDB(network), newerThanTimeStamp: newerThanTimeStamp, ); return resultSet @@ -187,10 +201,11 @@ abstract class FiroCacheCoordinator { int timestampUTC, })?> getLatestSetInfoForGroupId( int groupId, + CryptoCurrencyNetwork network, ) async { final result = await _Reader._getLatestSetInfoForGroupId( groupId, - db: _FiroCache.setCacheDB, + db: _FiroCache.setCacheDB(network), ); if (result.isEmpty) { @@ -206,10 +221,11 @@ abstract class FiroCacheCoordinator { static Future checkSetInfoForGroupIdExists( int groupId, + CryptoCurrencyNetwork network, ) async { return await _Reader._checkSetInfoForGroupIdExists( groupId, - db: _FiroCache.setCacheDB, + db: _FiroCache.setCacheDB(network), ); } } diff --git a/lib/db/sqlite/firo_cache_worker.dart b/lib/db/sqlite/firo_cache_worker.dart index f6bfe68f1..71e407992 100644 --- a/lib/db/sqlite/firo_cache_worker.dart +++ b/lib/db/sqlite/firo_cache_worker.dart @@ -25,11 +25,12 @@ class _FiroCacheWorker { return await completer.future; } - static Future<_FiroCacheWorker> spawn() async { + static Future<_FiroCacheWorker> spawn(CryptoCurrencyNetwork network) async { final dir = await StackFileSystem.applicationFiroCacheSQLiteDirectory(); - final setCacheFilePath = "${dir.path}/${_FiroCache.sparkSetCacheFileName}"; + final setCacheFilePath = + "${dir.path}/${_FiroCache.sparkSetCacheFileName(network)}"; final usedTagsCacheFilePath = - "${dir.path}/${_FiroCache.sparkUsedTagsCacheFileName}"; + "${dir.path}/${_FiroCache.sparkUsedTagsCacheFileName(network)}"; final initPort = RawReceivePort(); final connection = Completer<(ReceivePort, SendPort)>.sync(); diff --git a/lib/electrumx_rpc/electrumx_client.dart b/lib/electrumx_rpc/electrumx_client.dart index f08202612..c31a8eac5 100644 --- a/lib/electrumx_rpc/electrumx_client.dart +++ b/lib/electrumx_rpc/electrumx_client.dart @@ -1043,7 +1043,7 @@ class ElectrumXClient { final start = DateTime.now(); final response = await request( requestID: requestID, - command: "spark.getmempooltxids", + command: "spark.getmempoolsparktxids", ); final txids = List.from(response as List) @@ -1072,7 +1072,7 @@ class ElectrumXClient { final start = DateTime.now(); final response = await request( requestID: requestID, - command: "spark.getmempooltxs", + command: "spark.getmempoolsparktxs", args: [ { "txids": txids, @@ -1087,10 +1087,10 @@ class ElectrumXClient { ( txid: entry.key, serialContext: - List.from(entry.value["Serial_context"] as List), + List.from(entry.value["serial_context"] as List), // the space after lTags is required lol lTags: List.from(entry.value["lTags "] as List), - coins: List.from(entry.value["Coins"] as List), + coins: List.from(entry.value["coins"] as List), ), ); } @@ -1142,6 +1142,38 @@ class ElectrumXClient { } // =========================================================================== + Future isMasterNodeCollateral({ + String? requestID, + required String txid, + required int index, + }) async { + try { + final start = DateTime.now(); + final response = await request( + requestID: requestID, + command: "blockchain.checkifmncollateral", + args: [ + txid, + index.toString(), + ], + ); + + Logging.instance.log( + "Finished ElectrumXClient.isMasterNodeCollateral, " + "response: $response, " + "Duration=${DateTime.now().difference(start)}", + level: LogLevel.Info, + ); + + return response as bool; + } catch (e) { + Logging.instance.log(e, level: LogLevel.Error); + rethrow; + } + } + + // =========================================================================== + /// Get the current fee rate. /// /// Returns a map with the kay "rate" that corresponds to the free rate in satoshis diff --git a/lib/models/coinlib/exp2pkh_address.dart b/lib/models/coinlib/exp2pkh_address.dart new file mode 100644 index 000000000..839c5f124 --- /dev/null +++ b/lib/models/coinlib/exp2pkh_address.dart @@ -0,0 +1,87 @@ +import 'dart:typed_data'; + +import 'package:coinlib_flutter/coinlib_flutter.dart' as coinlib; + +const OP_EXCHANGEADDR = 0xe0; + +class EXP2PKHAddress implements coinlib.Address { + /// The 160bit public key or redeemScript hash for the base58 address + final Uint8List _hash; + + /// The network and address type version of the address + final Uint8List version; + + String? _encodedCache; + + EXP2PKHAddress._(Uint8List hash, this.version) : _hash = hash { + if (version.length != 3) { + throw ArgumentError( + "version bytes length must be 3", + ); + } + } + + factory EXP2PKHAddress.fromString(String encoded, Uint8List versionBytes) { + if (versionBytes.length != 3) { + throw ArgumentError( + "version bytes length must be 3", + ); + } + + final data = coinlib.base58Decode(encoded); + if (data.length != 23) throw coinlib.InvalidAddress(); + + final version = data.sublist(0, 3); + + for (int i = 0; i < 3; i++) { + if (version[i] != versionBytes[i]) { + throw Exception("EX address version bytes do not match"); + } + } + + final payload = data.sublist(3); + + final addr = EXP2PKHAddress._(payload, version); + + addr._encodedCache = encoded; + return addr; + } + + @override + String toString() => _encodedCache.toString(); + + @override + coinlib.Program get program => EXP2PKH.fromHash(_hash); +} + +class EXP2PKH implements coinlib.Program { + static const template = + "OP_EXCHANGEADDR OP_DUP OP_HASH160 <20-bytes> OP_EQUALVERIFY OP_CHECKSIG"; + + @override + final coinlib.Script script; + + EXP2PKH.fromScript(this.script); + + factory EXP2PKH.fromHash(Uint8List pkHash) { + final List ops = [ + coinlib.ScriptOpCode(OP_EXCHANGEADDR), + ]; + final parts = template.split(" ").sublist(1); + for (final name in parts) { + if (name.startsWith("OP_")) { + ops.add( + coinlib.ScriptOpCode( + coinlib.scriptOpNameToCode[name.substring(3)]!, + ), + ); + } else if (name == "<20-bytes>") { + ops.add(coinlib.ScriptPushData(pkHash)); + } else { + throw Exception("Something went wrong in this hacked code"); + } + } + + return EXP2PKH.fromScript(coinlib.Script(ops)); + } +} diff --git a/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_1a.dart b/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_1a.dart index cd2f0ea1d..7b9efb7aa 100644 --- a/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_1a.dart +++ b/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_1a.dart @@ -1,9 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:qr_flutter/qr_flutter.dart'; + import '../../../../../frost_route_generator.dart'; -import '../../../../wallet_view/transaction_views/transaction_details_view.dart'; import '../../../../../providers/frost_wallet/frost_wallet_providers.dart'; import '../../../../../services/frost.dart'; import '../../../../../themes/stack_colors.dart'; @@ -17,6 +16,8 @@ import '../../../../../widgets/desktop/secondary_button.dart'; import '../../../../../widgets/detail_item.dart'; import '../../../../../widgets/dialogs/simple_mobile_dialog.dart'; import '../../../../../widgets/frost_step_user_steps.dart'; +import '../../../../../widgets/qr.dart'; +import '../../../../wallet_view/transaction_views/transaction_details_view.dart'; class FrostCreateStep1a extends ConsumerStatefulWidget { const FrostCreateStep1a({super.key}); @@ -162,14 +163,9 @@ class _FrostCreateStep1aState extends ConsumerState { child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - QrImageView( + QR( data: ref.watch(pFrostMultisigConfig.state).state ?? "Error", size: 220, - backgroundColor: - Theme.of(context).extension()!.background, - foregroundColor: Theme.of(context) - .extension()! - .accentColorDark, ), ], ), diff --git a/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_1a.dart b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_1a.dart index ff8f03c71..a83e22d40 100644 --- a/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_1a.dart +++ b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_1a.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:qr_flutter/qr_flutter.dart'; import '../../../../frost_route_generator.dart'; import '../../../../providers/db/main_db_provider.dart'; @@ -23,6 +22,7 @@ import '../../../../widgets/detail_item.dart'; import '../../../../widgets/dialogs/frost/frost_error_dialog.dart'; import '../../../../widgets/dialogs/simple_mobile_dialog.dart'; import '../../../../widgets/frost_step_user_steps.dart'; +import '../../../../widgets/qr.dart'; import '../../../wallet_view/transaction_views/transaction_details_view.dart'; class FrostReshareStep1a extends ConsumerStatefulWidget { @@ -239,14 +239,9 @@ class _FrostReshareStep1aState extends ConsumerState { child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - QrImageView( + QR( data: ref.watch(pFrostResharingData).resharerRConfig!, size: 220, - backgroundColor: - Theme.of(context).extension()!.background, - foregroundColor: Theme.of(context) - .extension()! - .accentColorDark, ), ], ), diff --git a/lib/pages/exchange_view/exchange_step_views/step_4_view.dart b/lib/pages/exchange_view/exchange_step_views/step_4_view.dart index d992451a7..79655f8a6 100644 --- a/lib/pages/exchange_view/exchange_step_views/step_4_view.dart +++ b/lib/pages/exchange_view/exchange_step_views/step_4_view.dart @@ -14,7 +14,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:qr_flutter/qr_flutter.dart'; import 'package:tuple/tuple.dart'; import '../../../app_config.dart'; @@ -37,6 +36,7 @@ import '../../../wallets/wallet/impl/firo_wallet.dart'; import '../../../widgets/background.dart'; import '../../../widgets/custom_buttons/app_bar_icon_button.dart'; import '../../../widgets/desktop/secondary_button.dart'; +import '../../../widgets/qr.dart'; import '../../../widgets/rounded_container.dart'; import '../../../widgets/rounded_white_container.dart'; import '../../../widgets/stack_dialog.dart'; @@ -751,7 +751,7 @@ class _Step4ViewState extends ConsumerState { height: 24, ), Center( - child: QrImageView( + child: QR( // TODO: grab coin uri scheme from somewhere // data: "${coin.uriScheme}:$receivingAddress", data: model.trade!.payInAddress, @@ -759,9 +759,6 @@ class _Step4ViewState extends ConsumerState { .size .width / 2, - foregroundColor: Theme.of(context) - .extension()! - .accentColorDark, ), ), const SizedBox( diff --git a/lib/pages/exchange_view/trade_details_view.dart b/lib/pages/exchange_view/trade_details_view.dart index e64c1154b..fd56c4a59 100644 --- a/lib/pages/exchange_view/trade_details_view.dart +++ b/lib/pages/exchange_view/trade_details_view.dart @@ -16,7 +16,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:qr_flutter/qr_flutter.dart'; import 'package:tuple/tuple.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -50,6 +49,7 @@ import '../../widgets/custom_buttons/app_bar_icon_button.dart'; import '../../widgets/custom_buttons/blue_text_button.dart'; import '../../widgets/desktop/desktop_dialog.dart'; import '../../widgets/desktop/secondary_button.dart'; +import '../../widgets/qr.dart'; import '../../widgets/rounded_container.dart'; import '../../widgets/rounded_white_container.dart'; import '../../widgets/stack_dialog.dart'; @@ -808,15 +808,9 @@ class _TradeDetailsViewState extends ConsumerState { child: SizedBox( width: width + 20, height: width + 20, - child: QrImageView( + child: QR( data: trade.payInAddress, size: width, - backgroundColor: Theme.of(context) - .extension()! - .popupBG, - foregroundColor: Theme.of(context) - .extension()! - .accentColorDark, ), ), ), diff --git a/lib/pages/paynym/dialogs/paynym_details_popup.dart b/lib/pages/paynym/dialogs/paynym_details_popup.dart index 832f97422..693061482 100644 --- a/lib/pages/paynym/dialogs/paynym_details_popup.dart +++ b/lib/pages/paynym/dialogs/paynym_details_popup.dart @@ -14,15 +14,11 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:qr_flutter/qr_flutter.dart'; +import 'package:tuple/tuple.dart'; + import '../../../exceptions/wallet/insufficient_balance_exception.dart'; import '../../../models/paynym/paynym_account_lite.dart'; import '../../../notifications/show_flush_bar.dart'; -import 'confirm_paynym_connect_dialog.dart'; -import '../paynym_home_view.dart'; -import '../subwidgets/paynym_bot.dart'; -import '../../send_view/confirm_transaction_view.dart'; -import '../../send_view/send_view.dart'; import '../../../providers/global/locale_provider.dart'; import '../../../providers/global/wallets_provider.dart'; import '../../../route_generator.dart'; @@ -37,9 +33,14 @@ import '../../../widgets/desktop/desktop_dialog.dart'; import '../../../widgets/desktop/primary_button.dart'; import '../../../widgets/desktop/secondary_button.dart'; import '../../../widgets/loading_indicator.dart'; +import '../../../widgets/qr.dart'; import '../../../widgets/rounded_container.dart'; import '../../../widgets/stack_dialog.dart'; -import 'package:tuple/tuple.dart'; +import '../../send_view/confirm_transaction_view.dart'; +import '../../send_view/send_view.dart'; +import '../paynym_home_view.dart'; +import '../subwidgets/paynym_bot.dart'; +import 'confirm_paynym_connect_dialog.dart'; class PaynymDetailsPopup extends ConsumerStatefulWidget { const PaynymDetailsPopup({ @@ -365,12 +366,10 @@ class _PaynymDetailsPopupState extends ConsumerState { const SizedBox( width: 20, ), - QrImageView( + QR( padding: const EdgeInsets.all(0), size: 100, data: widget.accountLite.code, - foregroundColor: - Theme.of(context).extension()!.textDark, ), ], ), diff --git a/lib/pages/paynym/dialogs/paynym_qr_popup.dart b/lib/pages/paynym/dialogs/paynym_qr_popup.dart index b3cb3a1d0..520d6e16e 100644 --- a/lib/pages/paynym/dialogs/paynym_qr_popup.dart +++ b/lib/pages/paynym/dialogs/paynym_qr_popup.dart @@ -12,10 +12,9 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:qr_flutter/qr_flutter.dart'; + import '../../../models/paynym/paynym_account.dart'; import '../../../notifications/show_flush_bar.dart'; -import '../subwidgets/paynym_bot.dart'; import '../../../themes/stack_colors.dart'; import '../../../utilities/assets.dart'; import '../../../utilities/text_styles.dart'; @@ -23,6 +22,8 @@ import '../../../utilities/util.dart'; import '../../../widgets/custom_buttons/blue_text_button.dart'; import '../../../widgets/desktop/desktop_dialog.dart'; import '../../../widgets/desktop/desktop_dialog_close_button.dart'; +import '../../../widgets/qr.dart'; +import '../subwidgets/paynym_bot.dart'; class PaynymQrPopup extends StatelessWidget { const PaynymQrPopup({ @@ -157,12 +158,10 @@ class PaynymQrPopup extends StatelessWidget { const SizedBox( width: 20, ), - QrImageView( + QR( padding: const EdgeInsets.all(0), size: 130, data: paynymAccount.nonSegwitPaymentCode.code, - foregroundColor: - Theme.of(context).extension()!.textDark, ), ], ), diff --git a/lib/pages/paynym/subwidgets/desktop_paynym_details.dart b/lib/pages/paynym/subwidgets/desktop_paynym_details.dart index c33d5ed24..1c2b0cb81 100644 --- a/lib/pages/paynym/subwidgets/desktop_paynym_details.dart +++ b/lib/pages/paynym/subwidgets/desktop_paynym_details.dart @@ -14,19 +14,15 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:qr_flutter/qr_flutter.dart'; + import '../../../exceptions/wallet/insufficient_balance_exception.dart'; import '../../../models/paynym/paynym_account_lite.dart'; import '../../../notifications/show_flush_bar.dart'; -import '../dialogs/confirm_paynym_connect_dialog.dart'; -import 'paynym_bot.dart'; -import '../../send_view/confirm_transaction_view.dart'; import '../../../pages_desktop_specific/my_stack_view/paynym/desktop_paynym_send_dialog.dart'; import '../../../providers/global/locale_provider.dart'; import '../../../providers/global/wallets_provider.dart'; import '../../../themes/stack_colors.dart'; import '../../../utilities/assets.dart'; - import '../../../utilities/text_styles.dart'; import '../../../wallets/isar/providers/wallet_info_provider.dart'; import '../../../wallets/models/tx_data.dart'; @@ -36,8 +32,12 @@ import '../../../widgets/custom_buttons/paynym_follow_toggle_button.dart'; import '../../../widgets/desktop/desktop_dialog.dart'; import '../../../widgets/desktop/primary_button.dart'; import '../../../widgets/loading_indicator.dart'; +import '../../../widgets/qr.dart'; import '../../../widgets/rounded_container.dart'; import '../../../widgets/rounded_white_container.dart'; +import '../../send_view/confirm_transaction_view.dart'; +import '../dialogs/confirm_paynym_connect_dialog.dart'; +import 'paynym_bot.dart'; class DesktopPaynymDetails extends ConsumerStatefulWidget { const DesktopPaynymDetails({ @@ -359,12 +359,10 @@ class _PaynymDetailsPopupState extends ConsumerState { const SizedBox( width: 20, ), - QrImageView( + QR( padding: const EdgeInsets.all(0), size: 100, data: widget.accountLite.code, - foregroundColor: - Theme.of(context).extension()!.textDark, ), ], ), diff --git a/lib/pages/receive_view/addresses/address_card.dart b/lib/pages/receive_view/addresses/address_card.dart index a13ec17a7..db14fd979 100644 --- a/lib/pages/receive_view/addresses/address_card.dart +++ b/lib/pages/receive_view/addresses/address_card.dart @@ -20,7 +20,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:isar/isar.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:qr_flutter/qr_flutter.dart'; import 'package:share_plus/share_plus.dart'; import '../../../db/isar/main_db.dart'; @@ -39,6 +38,7 @@ import '../../../widgets/custom_buttons/blue_text_button.dart'; import '../../../widgets/custom_buttons/simple_edit_button.dart'; import '../../../widgets/desktop/primary_button.dart'; import '../../../widgets/desktop/secondary_button.dart'; +import '../../../widgets/qr.dart'; import '../../../widgets/rounded_white_container.dart'; import '../../../widgets/stack_dialog.dart'; @@ -302,19 +302,13 @@ class _AddressCardState extends ConsumerState { Center( child: RepaintBoundary( key: _qrKey, - child: QrImageView( + child: QR( data: AddressUtils.buildUriString( widget.coin, address.value, {}, ), size: 220, - backgroundColor: Theme.of(context) - .extension()! - .popupBG, - foregroundColor: Theme.of(context) - .extension()! - .accentColorDark, ), ), ), diff --git a/lib/pages/receive_view/addresses/address_details_view.dart b/lib/pages/receive_view/addresses/address_details_view.dart index 120bfa48b..103a5729d 100644 --- a/lib/pages/receive_view/addresses/address_details_view.dart +++ b/lib/pages/receive_view/addresses/address_details_view.dart @@ -11,7 +11,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:isar/isar.dart'; -import 'package:qr_flutter/qr_flutter.dart'; import '../../../db/isar/main_db.dart'; import '../../../models/isar/models/blockchain_data/v2/transaction_v2.dart'; @@ -31,6 +30,7 @@ import '../../../widgets/custom_buttons/simple_copy_button.dart'; import '../../../widgets/custom_buttons/simple_edit_button.dart'; import '../../../widgets/desktop/desktop_dialog.dart'; import '../../../widgets/desktop/desktop_dialog_close_button.dart'; +import '../../../widgets/qr.dart'; import '../../../widgets/rounded_white_container.dart'; import '../../../widgets/transaction_card.dart'; import '../../wallet_view/sub_widgets/no_transactions_found.dart'; @@ -92,18 +92,13 @@ class _AddressDetailsViewState extends ConsumerState { Center( child: RepaintBoundary( key: _qrKey, - child: QrImageView( + child: QR( data: AddressUtils.buildUriString( ref.watch(pWalletCoin(widget.walletId)), address.value, {}, ), size: 220, - backgroundColor: - Theme.of(context).extension()!.popupBG, - foregroundColor: Theme.of(context) - .extension()! - .accentColorDark, ), ), ), @@ -289,19 +284,13 @@ class _AddressDetailsViewState extends ConsumerState { Center( child: RepaintBoundary( key: _qrKey, - child: QrImageView( + child: QR( data: AddressUtils.buildUriString( coin, address.value, {}, ), size: 220, - backgroundColor: Theme.of(context) - .extension()! - .background, - foregroundColor: Theme.of(context) - .extension()! - .accentColorDark, ), ), ), diff --git a/lib/pages/receive_view/addresses/address_qr_popup.dart b/lib/pages/receive_view/addresses/address_qr_popup.dart index 7b25003b2..5a8bc1592 100644 --- a/lib/pages/receive_view/addresses/address_qr_popup.dart +++ b/lib/pages/receive_view/addresses/address_qr_popup.dart @@ -18,7 +18,6 @@ import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter_svg/svg.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:qr_flutter/qr_flutter.dart'; import 'package:share_plus/share_plus.dart'; import '../../../notifications/show_flush_bar.dart'; @@ -31,6 +30,7 @@ import '../../../utilities/util.dart'; import '../../../wallets/crypto_currency/crypto_currency.dart'; import '../../../widgets/desktop/primary_button.dart'; import '../../../widgets/desktop/secondary_button.dart'; +import '../../../widgets/qr.dart'; import '../../../widgets/stack_dialog.dart'; class AddressQrPopup extends StatefulWidget { @@ -140,17 +140,13 @@ class _AddressQrPopupState extends State { Center( child: RepaintBoundary( key: _qrKey, - child: QrImageView( + child: QR( data: AddressUtils.buildUriString( widget.coin, widget.addressString, {}, ), size: 220, - backgroundColor: - Theme.of(context).extension()!.popupBG, - foregroundColor: - Theme.of(context).extension()!.accentColorDark, ), ), ), diff --git a/lib/pages/receive_view/addresses/wallet_addresses_view.dart b/lib/pages/receive_view/addresses/wallet_addresses_view.dart index 620bfe244..f464d0e57 100644 --- a/lib/pages/receive_view/addresses/wallet_addresses_view.dart +++ b/lib/pages/receive_view/addresses/wallet_addresses_view.dart @@ -249,17 +249,15 @@ class _WalletAddressesViewState extends ConsumerState { walletId: widget.walletId, addressId: snapshot.data![index], coin: coin, - onPressed: !isDesktop - ? null - : () { - Navigator.of(context).pushNamed( - AddressDetailsView.routeName, - arguments: Tuple2( - snapshot.data![index], - widget.walletId, - ), - ); - }, + onPressed: () { + Navigator.of(context).pushNamed( + AddressDetailsView.routeName, + arguments: Tuple2( + snapshot.data![index], + widget.walletId, + ), + ); + }, ), ); } else { diff --git a/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart b/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart index e7d6d7e5f..7a7497d0e 100644 --- a/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart +++ b/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart @@ -20,7 +20,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_svg/svg.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:qr_flutter/qr_flutter.dart'; import 'package:share_plus/share_plus.dart'; import '../../notifications/show_flush_bar.dart'; @@ -39,6 +38,7 @@ import '../../widgets/custom_buttons/app_bar_icon_button.dart'; import '../../widgets/desktop/primary_button.dart'; import '../../widgets/desktop/secondary_button.dart'; import '../../widgets/icon_widgets/x_icon.dart'; +import '../../widgets/qr.dart'; import '../../widgets/rounded_white_container.dart'; import '../../widgets/stack_dialog.dart'; import '../../widgets/stack_text_field.dart'; @@ -215,14 +215,9 @@ class _GenerateUriQrCodeViewState extends State { child: SizedBox( width: width + 20, height: width + 20, - child: QrImageView( + child: QR( data: uriString, size: width, - backgroundColor: - Theme.of(context).extension()!.popupBG, - foregroundColor: Theme.of(context) - .extension()! - .accentColorDark, ), ), ), @@ -556,15 +551,9 @@ class _GenerateUriQrCodeViewState extends State { child: SizedBox( width: 234, height: 234, - child: QrImageView( + child: QR( data: _uriString, size: 220, - backgroundColor: Theme.of(context) - .extension()! - .popupBG, - foregroundColor: Theme.of(context) - .extension()! - .accentColorDark, ), ), ), diff --git a/lib/pages/receive_view/receive_view.dart b/lib/pages/receive_view/receive_view.dart index 2be05937f..2beaab5f2 100644 --- a/lib/pages/receive_view/receive_view.dart +++ b/lib/pages/receive_view/receive_view.dart @@ -16,7 +16,6 @@ import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:isar/isar.dart'; -import 'package:qr_flutter/qr_flutter.dart'; import '../../models/isar/models/isar_models.dart'; import '../../notifications/show_flush_bar.dart'; @@ -44,6 +43,7 @@ import '../../widgets/custom_buttons/blue_text_button.dart'; import '../../widgets/custom_loading_overlay.dart'; import '../../widgets/desktop/primary_button.dart'; import '../../widgets/desktop/secondary_button.dart'; +import '../../widgets/qr.dart'; import '../../widgets/rounded_white_container.dart'; import 'addresses/wallet_addresses_view.dart'; import 'generate_receiving_uri_qr_code_view.dart'; @@ -575,16 +575,13 @@ class _ReceiveViewState extends ConsumerState { child: Center( child: Column( children: [ - QrImageView( + QR( data: AddressUtils.buildUriString( coin, address, {}, ), size: MediaQuery.of(context).size.width / 2, - foregroundColor: Theme.of(context) - .extension()! - .accentColorDark, ), const SizedBox( height: 20, diff --git a/lib/pages/send_view/frost_ms/send_steps/frost_send_step_1a.dart b/lib/pages/send_view/frost_ms/send_steps/frost_send_step_1a.dart index c25090fca..0ec725d00 100644 --- a/lib/pages/send_view/frost_ms/send_steps/frost_send_step_1a.dart +++ b/lib/pages/send_view/frost_ms/send_steps/frost_send_step_1a.dart @@ -1,8 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:qr_flutter/qr_flutter.dart'; + import '../../../../frost_route_generator.dart'; -import '../../../wallet_view/transaction_views/transaction_details_view.dart'; import '../../../../providers/frost_wallet/frost_wallet_providers.dart'; import '../../../../providers/global/wallets_provider.dart'; import '../../../../themes/stack_colors.dart'; @@ -14,7 +13,9 @@ import '../../../../widgets/custom_buttons/checkbox_text_button.dart'; import '../../../../widgets/custom_buttons/simple_copy_button.dart'; import '../../../../widgets/desktop/primary_button.dart'; import '../../../../widgets/detail_item.dart'; +import '../../../../widgets/qr.dart'; import '../../../../widgets/rounded_white_container.dart'; +import '../../../wallet_view/transaction_views/transaction_details_view.dart'; class FrostSendStep1a extends ConsumerStatefulWidget { const FrostSendStep1a({super.key}); @@ -169,14 +170,9 @@ class _FrostSendStep1aState extends ConsumerState { child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - QrImageView( + QR( data: ref.watch(pFrostTxData.state).state!.frostMSConfig!, size: qrImageSize, - backgroundColor: - Theme.of(context).extension()!.background, - foregroundColor: Theme.of(context) - .extension()! - .accentColorDark, ), ], ), diff --git a/lib/pages/send_view/send_view.dart b/lib/pages/send_view/send_view.dart index 376f66d9d..60c6799a3 100644 --- a/lib/pages/send_view/send_view.dart +++ b/lib/pages/send_view/send_view.dart @@ -56,6 +56,7 @@ import '../../widgets/animated_text.dart'; import '../../widgets/background.dart'; import '../../widgets/custom_buttons/app_bar_icon_button.dart'; import '../../widgets/custom_buttons/blue_text_button.dart'; +import '../../widgets/dialogs/firo_exchange_address_dialog.dart'; import '../../widgets/fee_slider.dart'; import '../../widgets/icon_widgets/addressbook_icon.dart'; import '../../widgets/icon_widgets/clipboard_icon.dart'; @@ -127,6 +128,8 @@ class _SendViewState extends ConsumerState { bool _addressToggleFlag = false; + bool _isFiroExWarningDisplayed = false; + bool _cryptoAmountChangeLock = false; late VoidCallback onCryptoAmountChanged; @@ -394,6 +397,19 @@ class _SendViewState extends ConsumerState { address: address ?? "", isTestNet: wallet.cryptoCurrency.network.isTestNet, ); + + ref.read(pIsExchangeAddress.state).state = + (coin as Firo).isExchangeAddress(address ?? ""); + + if (ref.read(publicPrivateBalanceStateProvider) == FiroType.spark && + ref.read(pIsExchangeAddress) && + !_isFiroExWarningDisplayed) { + _isFiroExWarningDisplayed = true; + showFiroExchangeAddressWarning( + context, + () => _isFiroExWarningDisplayed = false, + ); + } } ref.read(pValidSendToAddress.notifier).state = @@ -875,7 +891,10 @@ class _SendViewState extends ConsumerState { @override void initState() { coin = widget.coin; - ref.refresh(feeSheetSessionCacheProvider); + WidgetsBinding.instance.addPostFrameCallback((_) { + ref.refresh(feeSheetSessionCacheProvider); + ref.refresh(pIsExchangeAddress); + }); _currentFee = 0.toAmountAsRaw(fractionDigits: coin.fractionDigits); _calculateFeesFuture = @@ -1003,6 +1022,8 @@ class _SendViewState extends ConsumerState { : true); if (isFiro) { + final isExchangeAddress = ref.watch(pIsExchangeAddress); + ref.listen(publicPrivateBalanceStateProvider, (previous, next) { selectedUTXOs = {}; @@ -1019,6 +1040,19 @@ class _SendViewState extends ConsumerState { ); }); } + + if (previous != next && + next == FiroType.spark && + isExchangeAddress && + !_isFiroExWarningDisplayed) { + _isFiroExWarningDisplayed = true; + WidgetsBinding.instance.addPostFrameCallback( + (_) => showFiroExchangeAddressWarning( + context, + () => _isFiroExWarningDisplayed = false, + ), + ); + } }); } diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_backup_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_backup_view.dart index e2b861db1..70bbdd294 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_backup_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_backup_view.dart @@ -14,7 +14,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:qr_flutter/qr_flutter.dart'; import '../../../../app_config.dart'; import '../../../../notifications/show_flush_bar.dart'; @@ -30,6 +29,7 @@ import '../../../../widgets/background.dart'; import '../../../../widgets/custom_buttons/app_bar_icon_button.dart'; import '../../../../widgets/custom_buttons/simple_copy_button.dart'; import '../../../../widgets/detail_item.dart'; +import '../../../../widgets/qr.dart'; import '../../../../widgets/rounded_white_container.dart'; import '../../../../widgets/stack_dialog.dart'; import '../../../add_wallet_views/new_wallet_recovery_phrase_view/sub_widgets/mnemonic_table.dart'; @@ -317,15 +317,9 @@ class WalletBackupView extends ConsumerWidget { child: SizedBox( width: width + 20, height: width + 20, - child: QrImageView( + child: QR( data: data, size: width, - backgroundColor: Theme.of(context) - .extension()! - .popupBG, - foregroundColor: Theme.of(context) - .extension()! - .accentColorDark, ), ), ), diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart index 22566804a..e15fad011 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart @@ -759,6 +759,32 @@ class _WalletNetworkSettingsViewState ), ), ), + SizedBox( + height: isDesktop ? 12 : 9, + ), + RoundedWhiteContainer( + borderColor: isDesktop + ? Theme.of(context).extension()!.background + : null, + padding: + isDesktop ? const EdgeInsets.all(16) : const EdgeInsets.all(12), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Current height", + textAlign: TextAlign.left, + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.smallMed12(context), + ), + Text( + ref.watch(pWalletChainHeight(widget.walletId)).toString(), + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + ], + ), + ), SizedBox( height: isDesktop ? 32 : 20, ), diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_settings_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_settings_view.dart index 004bd57df..098c1c285 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_settings_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_settings_view.dart @@ -430,7 +430,9 @@ class _WalletSettingsViewState extends ConsumerState { ), if (coin is Firo) FiroCacheCoordinator - .clearSharedCache(), + .clearSharedCache( + coin.network, + ), ], ), context: context, diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/spark_info.dart b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/spark_info.dart index 7cb9b91f3..934736311 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/spark_info.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/spark_info.dart @@ -4,6 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../db/sqlite/firo_cache.dart'; import '../../../../themes/stack_colors.dart'; import '../../../../utilities/text_styles.dart'; +import '../../../../wallets/isar/providers/wallet_info_provider.dart'; import '../../../../widgets/background.dart'; import '../../../../widgets/custom_buttons/app_bar_icon_button.dart'; import '../../../../widgets/detail_item.dart'; @@ -11,10 +12,13 @@ import '../../../../widgets/detail_item.dart'; class SparkInfoView extends ConsumerWidget { const SparkInfoView({ super.key, + required this.walletId, }); static const String routeName = "/sparkInfo"; + final String walletId; + @override Widget build(BuildContext context, WidgetRef ref) { return Background( @@ -37,7 +41,9 @@ class SparkInfoView extends ConsumerWidget { crossAxisAlignment: CrossAxisAlignment.stretch, children: [ FutureBuilder( - future: FiroCacheCoordinator.getSparkCacheSize(), + future: FiroCacheCoordinator.getSparkCacheSize( + ref.watch(pWalletCoin(walletId)).network, + ), builder: (_, snapshot) { String detail = "Loading..."; if (snapshot.connectionState == ConnectionState.done) { diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/wallet_settings_wallet_settings_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/wallet_settings_wallet_settings_view.dart index d7e58ece5..37040ab71 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/wallet_settings_wallet_settings_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/wallet_settings_wallet_settings_view.dart @@ -243,6 +243,7 @@ class WalletSettingsWalletSettingsView extends ConsumerWidget { onPressed: () { Navigator.of(context).pushNamed( SparkInfoView.routeName, + arguments: walletId, ); }, child: Padding( diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/xpub_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/xpub_view.dart index ad245d321..c8fa245f0 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/xpub_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/xpub_view.dart @@ -14,7 +14,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:qr_flutter/qr_flutter.dart'; import '../../../../notifications/show_flush_bar.dart'; import '../../../../providers/global/wallets_provider.dart'; @@ -32,6 +31,7 @@ import '../../../../widgets/desktop/desktop_dialog_close_button.dart'; import '../../../../widgets/desktop/primary_button.dart'; import '../../../../widgets/desktop/secondary_button.dart'; import '../../../../widgets/loading_indicator.dart'; +import '../../../../widgets/qr.dart'; import '../../../../widgets/rounded_white_container.dart'; class XPubView extends ConsumerStatefulWidget { @@ -256,11 +256,9 @@ class _XPub extends StatelessWidget { builder: (child) => RoundedWhiteContainer( child: child, ), - child: QrImageView( + child: QR( data: xpub, size: isDesktop ? 280 : MediaQuery.of(context).size.width / 1.5, - foregroundColor: - Theme.of(context).extension()!.accentColorDark, ), ), const SizedBox(height: 25), diff --git a/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_list_item.dart b/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_list_item.dart index f51c9f609..0ae641401 100644 --- a/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_list_item.dart +++ b/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_list_item.dart @@ -13,6 +13,7 @@ import '../../../../utilities/text_styles.dart'; import '../../../../utilities/util.dart'; import '../../../../wallets/crypto_currency/crypto_currency.dart'; import '../../../../wallets/isar/providers/wallet_info_provider.dart'; +import '../../../../widgets/breathing.dart'; import '../../../../widgets/desktop/desktop_dialog.dart'; import '../../../../widgets/desktop/desktop_dialog_close_button.dart'; import '../../../../widgets/trade_card.dart'; @@ -49,98 +50,100 @@ class TxListItem extends ConsumerWidget { color: Theme.of(context).extension()!.popupBG, borderRadius: radius, ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - TransactionCardV2( - key: UniqueKey(), - transaction: _tx, - ), - TradeCard( - key: Key( - _tx.txid + - _tx.type.name + - _tx.hashCode.toString() + - trade.uuid, - ), // - trade: trade, - onTap: () async { - if (Util.isDesktop) { - await showDialog( - context: context, - builder: (context) => Navigator( - initialRoute: TradeDetailsView.routeName, - onGenerateRoute: RouteGenerator.generateRoute, - onGenerateInitialRoutes: (_, __) { - return [ - FadePageRoute( - DesktopDialog( - maxHeight: null, - maxWidth: 580, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.only( - left: 32, - bottom: 16, + child: Breathing( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TransactionCardV2( + key: UniqueKey(), + transaction: _tx, + ), + TradeCard( + key: Key( + _tx.txid + + _tx.type.name + + _tx.hashCode.toString() + + trade.uuid, + ), // + trade: trade, + onTap: () async { + if (Util.isDesktop) { + await showDialog( + context: context, + builder: (context) => Navigator( + initialRoute: TradeDetailsView.routeName, + onGenerateRoute: RouteGenerator.generateRoute, + onGenerateInitialRoutes: (_, __) { + return [ + FadePageRoute( + DesktopDialog( + maxHeight: null, + maxWidth: 580, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + bottom: 16, + ), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + "Trade details", + style: STextStyles.desktopH3( + context), + ), + DesktopDialogCloseButton( + onPressedOverride: Navigator.of( + context, + rootNavigator: true, + ).pop, + ), + ], + ), ), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text( - "Trade details", - style: - STextStyles.desktopH3(context), - ), - DesktopDialogCloseButton( - onPressedOverride: Navigator.of( - context, - rootNavigator: true, - ).pop, - ), - ], + Flexible( + child: TradeDetailsView( + tradeId: trade.tradeId, + // TODO: [prio:med] + // transactionIfSentFromStack: tx, + transactionIfSentFromStack: null, + walletName: ref + .watch(pWalletName(_tx.walletId)), + walletId: _tx.walletId, + ), ), - ), - Flexible( - child: TradeDetailsView( - tradeId: trade.tradeId, - // TODO: [prio:med] - // transactionIfSentFromStack: tx, - transactionIfSentFromStack: null, - walletName: ref - .watch(pWalletName(_tx.walletId)), - walletId: _tx.walletId, - ), - ), - ], + ], + ), + ), + const RouteSettings( + name: TradeDetailsView.routeName, ), ), - const RouteSettings( - name: TradeDetailsView.routeName, - ), - ), - ]; - }, - ), - ); - } else { - unawaited( - Navigator.of(context).pushNamed( - TradeDetailsView.routeName, - arguments: Tuple4( - trade.tradeId, - _tx, - _tx.walletId, - ref.read(pWalletName(_tx.walletId)), + ]; + }, ), - ), - ); - } - }, - ), - ], + ); + } else { + unawaited( + Navigator.of(context).pushNamed( + TradeDetailsView.routeName, + arguments: Tuple4( + trade.tradeId, + _tx, + _tx.walletId, + ref.read(pWalletName(_tx.walletId)), + ), + ), + ); + } + }, + ), + ], + ), ), ); } else { @@ -149,10 +152,12 @@ class TxListItem extends ConsumerWidget { color: Theme.of(context).extension()!.popupBG, borderRadius: radius, ), - child: TransactionCardV2( - // this may mess with combined firo transactions - key: UniqueKey(), - transaction: _tx, + child: Breathing( + child: TransactionCardV2( + // this may mess with combined firo transactions + key: UniqueKey(), + transaction: _tx, + ), ), ); } @@ -165,9 +170,11 @@ class TxListItem extends ConsumerWidget { color: Theme.of(context).extension()!.popupBG, borderRadius: radius, ), - child: FusionTxGroupCard( - key: UniqueKey(), - group: group, + child: Breathing( + child: FusionTxGroupCard( + key: UniqueKey(), + group: group, + ), ), ); } diff --git a/lib/pages/wallet_view/wallet_view.dart b/lib/pages/wallet_view/wallet_view.dart index bfe2cb8c5..ea9ca50e4 100644 --- a/lib/pages/wallet_view/wallet_view.dart +++ b/lib/pages/wallet_view/wallet_view.dart @@ -80,7 +80,6 @@ import '../buy_view/buy_in_wallet_view.dart'; import '../cashfusion/cashfusion_view.dart'; import '../coin_control/coin_control_view.dart'; import '../exchange_view/wallet_initiated_exchange_view.dart'; -import '../home_view/home_view.dart'; import '../monkey/monkey_view.dart'; import '../notification_views/notifications_view.dart'; import '../ordinals/ordinals_view.dart'; @@ -257,40 +256,43 @@ class _WalletViewState extends ConsumerState { super.dispose(); } - DateTime? _cachedTime; + // DateTime? _cachedTime; Future _onWillPop() async { if (_rescanningOnOpen || _lelantusRescanRecovery) { return false; } - final now = DateTime.now(); - const timeout = Duration(milliseconds: 1500); - if (_cachedTime == null || now.difference(_cachedTime!) > timeout) { - _cachedTime = now; - unawaited( - showDialog( - context: context, - barrierDismissible: false, - builder: (_) => WillPopScope( - onWillPop: () async { - Navigator.of(context).popUntil( - ModalRoute.withName(HomeView.routeName), - ); - _logout(); - return false; - }, - child: const StackDialog(title: "Tap back again to exit wallet"), - ), - ).timeout( - timeout, - onTimeout: () => Navigator.of(context).popUntil( - ModalRoute.withName(WalletView.routeName), - ), - ), - ); - } - return false; + _logout(); + + return true; + // final now = DateTime.now(); + // const timeout = Duration(milliseconds: 1500); + // if (_cachedTime == null || now.difference(_cachedTime!) > timeout) { + // _cachedTime = now; + // unawaited( + // showDialog( + // context: context, + // barrierDismissible: false, + // builder: (_) => WillPopScope( + // onWillPop: () async { + // Navigator.of(context).popUntil( + // ModalRoute.withName(HomeView.routeName), + // ); + // _logout(); + // return false; + // }, + // child: const StackDialog(title: "Tap back again to exit wallet"), + // ), + // ).timeout( + // timeout, + // onTimeout: () => Navigator.of(context).popUntil( + // ModalRoute.withName(WalletView.routeName), + // ), + // ), + // ); + // } + // return false; } void _logout() async { diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart index 19eea020b..63275921d 100644 --- a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart @@ -13,7 +13,6 @@ import 'dart:async'; import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:qr_flutter/qr_flutter.dart'; import '../../../app_config.dart'; import '../../../models/exchange/incomplete_exchange.dart'; @@ -37,6 +36,7 @@ import '../../../widgets/desktop/primary_button.dart'; import '../../../widgets/desktop/secondary_button.dart'; import '../../../widgets/desktop/simple_desktop_dialog.dart'; import '../../../widgets/fade_stack.dart'; +import '../../../widgets/qr.dart'; import '../subwidgets/desktop_exchange_steps_indicator.dart'; import 'subwidgets/desktop_step_1.dart'; import 'subwidgets/desktop_step_2.dart'; @@ -397,7 +397,7 @@ class _StepScaffoldState extends ConsumerState { height: 48, ), Center( - child: QrImageView( + child: QR( // TODO: grab coin uri scheme from somewhere // data: "${coin.uriScheme}:$receivingAddress", data: ref.watch( @@ -406,9 +406,6 @@ class _StepScaffoldState extends ConsumerState { ), ), size: 290, - foregroundColor: Theme.of(context) - .extension()! - .accentColorDark, ), ), const SizedBox( diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_summary_table.dart b/lib/pages_desktop_specific/my_stack_view/wallet_summary_table.dart index f38296cd5..983f01a42 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_summary_table.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_summary_table.dart @@ -13,6 +13,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; + import '../../pages/wallets_view/wallets_overview.dart'; import '../../providers/providers.dart'; import '../../themes/coin_icon_provider.dart'; @@ -21,6 +22,7 @@ import '../../utilities/amount/amount.dart'; import '../../utilities/text_styles.dart'; import '../../wallets/crypto_currency/crypto_currency.dart'; import '../../wallets/isar/providers/all_wallets_info_provider.dart'; +import '../../widgets/breathing.dart'; import '../../widgets/conditional_parent.dart'; import '../../widgets/desktop/desktop_dialog.dart'; import '../../widgets/desktop/desktop_dialog_close_button.dart'; @@ -146,71 +148,56 @@ class _DesktopWalletSummaryRowState @override Widget build(BuildContext context) { - return MouseRegion( - onEnter: (_) => setState( - () => _hovering = true, - ), - onExit: (_) => setState( - () => _hovering = false, - ), - child: AnimatedScale( - scale: _hovering ? 1.00 : 0.98, - duration: const Duration( - milliseconds: 200, - ), - child: RoundedWhiteContainer( - padding: const EdgeInsets.all(20), - hoverColor: Colors.transparent, - onPressed: _onPressed, - child: Row( - children: [ - Expanded( - flex: 4, - child: Row( - children: [ - SvgPicture.file( - File( - ref.watch(coinIconProvider(widget.coin)), - ), - width: 28, - height: 28, + return Breathing( + child: RoundedWhiteContainer( + padding: const EdgeInsets.all(20), + hoverColor: Colors.transparent, + onPressed: _onPressed, + child: Row( + children: [ + Expanded( + flex: 4, + child: Row( + children: [ + SvgPicture.file( + File( + ref.watch(coinIconProvider(widget.coin)), ), - const SizedBox( - width: 10, - ), - Text( - widget.coin.prettyName, - style: - STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ), - ), - ], - ), - ), - Expanded( - flex: 4, - child: Text( - widget.walletCount == 1 - ? "${widget.walletCount} wallet" - : "${widget.walletCount} wallets", - style: STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, + width: 28, + height: 28, ), + const SizedBox( + width: 10, + ), + Text( + widget.coin.prettyName, + style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: + Theme.of(context).extension()!.textDark, + ), + ), + ], + ), + ), + Expanded( + flex: 4, + child: Text( + widget.walletCount == 1 + ? "${widget.walletCount} wallet" + : "${widget.walletCount} wallets", + style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: + Theme.of(context).extension()!.textSubtitle1, ), ), - Expanded( - flex: 6, - child: TablePriceInfo( - coin: widget.coin, - ), + ), + Expanded( + flex: 6, + child: TablePriceInfo( + coin: widget.coin, ), - ], - ), + ), + ], ), ), ); diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart index cf47215d1..0105c0006 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart @@ -294,7 +294,9 @@ class _DesktopWalletViewState extends ConsumerState { width: 2, ), FutureBuilder( - future: FiroCacheCoordinator.getSparkCacheSize(), + future: FiroCacheCoordinator.getSparkCacheSize( + wallet.cryptoCurrency.network, + ), builder: (_, snapshot) => Text( snapshot.data ?? "", ), diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart index 78031971e..0da2a6607 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart @@ -16,7 +16,6 @@ import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:isar/isar.dart'; -import 'package:qr_flutter/qr_flutter.dart'; import 'package:tuple/tuple.dart'; import '../../../../models/isar/models/isar_models.dart'; @@ -42,6 +41,7 @@ import '../../../../widgets/custom_buttons/app_bar_icon_button.dart'; import '../../../../widgets/custom_loading_overlay.dart'; import '../../../../widgets/desktop/desktop_dialog.dart'; import '../../../../widgets/desktop/secondary_button.dart'; +import '../../../../widgets/qr.dart'; import '../../../../widgets/rounded_white_container.dart'; class DesktopReceive extends ConsumerStatefulWidget { @@ -476,15 +476,13 @@ class _DesktopReceiveState extends ConsumerState { height: 32, ), Center( - child: QrImageView( + child: QR( data: AddressUtils.buildUriString( coin, _qrcodeContent ?? "", {}, ), size: 200, - foregroundColor: - Theme.of(context).extension()!.accentColorDark, ), ), const SizedBox( diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart index d54e262f6..e5cc93d92 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart @@ -60,6 +60,7 @@ import '../../../../widgets/desktop/desktop_dialog_close_button.dart'; import '../../../../widgets/desktop/desktop_fee_dialog.dart'; import '../../../../widgets/desktop/primary_button.dart'; import '../../../../widgets/desktop/secondary_button.dart'; +import '../../../../widgets/dialogs/firo_exchange_address_dialog.dart'; import '../../../../widgets/fee_slider.dart'; import '../../../../widgets/icon_widgets/addressbook_icon.dart'; import '../../../../widgets/icon_widgets/clipboard_icon.dart'; @@ -121,6 +122,8 @@ class _DesktopSendState extends ConsumerState { bool _addressToggleFlag = false; + bool _isFiroExWarningDisplayed = false; + bool _cryptoAmountChangeLock = false; late VoidCallback onCryptoAmountChanged; @@ -706,6 +709,19 @@ class _DesktopSendState extends ConsumerState { address: address ?? "", isTestNet: wallet.cryptoCurrency.network.isTestNet, ); + + ref.read(pIsExchangeAddress.state).state = + (coin as Firo).isExchangeAddress(address ?? ""); + + if (ref.read(publicPrivateBalanceStateProvider) == FiroType.spark && + ref.read(pIsExchangeAddress) && + !_isFiroExWarningDisplayed) { + _isFiroExWarningDisplayed = true; + showFiroExchangeAddressWarning( + context, + () => _isFiroExWarningDisplayed = false, + ); + } } ref.read(pValidSendToAddress.notifier).state = @@ -842,6 +858,7 @@ class _DesktopSendState extends ConsumerState { void initState() { WidgetsBinding.instance.addPostFrameCallback((_) { ref.refresh(feeSheetSessionCacheProvider); + ref.refresh(pIsExchangeAddress); ref.read(pValidSendToAddress.state).state = false; ref.read(pValidSparkSendToAddress.state).state = false; }); @@ -944,15 +961,31 @@ class _DesktopSendState extends ConsumerState { }); } + final firoType = ref.watch(publicPrivateBalanceStateProvider); + + final isExchangeAddress = ref.watch(pIsExchangeAddress); + ref.listen(publicPrivateBalanceStateProvider, (previous, next) { + if (previous != next && + next == FiroType.spark && + isExchangeAddress && + !_isFiroExWarningDisplayed) { + _isFiroExWarningDisplayed = true; + WidgetsBinding.instance.addPostFrameCallback( + (_) => showFiroExchangeAddressWarning( + context, + () => _isFiroExWarningDisplayed = false, + ), + ); + } + }); + final showCoinControl = ref.watch( prefsChangeNotifierProvider.select( (value) => value.enableCoinControl, ), ) && ref.watch(pWallets).getWallet(walletId) is CoinControlInterface && - (coin is Firo - ? ref.watch(publicPrivateBalanceStateProvider) == FiroType.public - : true); + (coin is Firo ? firoType == FiroType.public : true); return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -978,7 +1011,7 @@ class _DesktopSendState extends ConsumerState { DropdownButtonHideUnderline( child: DropdownButton2( isExpanded: true, - value: ref.watch(publicPrivateBalanceStateProvider.state).state, + value: firoType, items: [ DropdownMenuItem( value: FiroType.spark, @@ -1464,8 +1497,7 @@ class _DesktopSendState extends ConsumerState { if (_address == null || _address!.isEmpty) { error = null; } else if (coin is Firo) { - if (ref.watch(publicPrivateBalanceStateProvider) == - FiroType.lelantus) { + if (firoType == FiroType.lelantus) { if (_data != null && _data!.contactLabel == _address) { error = SparkInterface.validateSparkAddress( address: _data!.address, @@ -1526,15 +1558,13 @@ class _DesktopSendState extends ConsumerState { ), if (isStellar || (ref.watch(pValidSparkSendToAddress) && - ref.watch(publicPrivateBalanceStateProvider) != - FiroType.lelantus)) + firoType != FiroType.lelantus)) const SizedBox( height: 10, ), if (isStellar || (ref.watch(pValidSparkSendToAddress) && - ref.watch(publicPrivateBalanceStateProvider) != - FiroType.lelantus)) + firoType != FiroType.lelantus)) ClipRRect( borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, 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 d440b9c4e..76fa097a4 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 @@ -19,8 +19,7 @@ import '../../../../../providers/global/wallets_provider.dart'; import '../../../../../themes/stack_colors.dart'; import '../../../../../utilities/assets.dart'; import '../../../../../utilities/text_styles.dart'; -import '../../../../../wallets/crypto_currency/coins/banano.dart'; -import '../../../../../wallets/crypto_currency/coins/firo.dart'; +import '../../../../../wallets/crypto_currency/crypto_currency.dart'; import '../../../../../wallets/isar/models/wallet_info.dart'; import '../../../../../wallets/isar/providers/wallet_info_provider.dart'; import '../../../../../wallets/wallet/wallet_mixin_interfaces/cash_fusion_interface.dart'; @@ -187,7 +186,9 @@ class _MoreFeaturesDialogState extends ConsumerState { onPressed: () async => widget.onFusionPressed?.call(), ), if (wallet is SparkInterface) - const _MoreFeaturesClearSparkCacheItem(), + _MoreFeaturesClearSparkCacheItem( + cryptoCurrency: wallet.cryptoCurrency, + ), if (wallet is LelantusInterface) _MoreFeaturesItemBase( child: Row( @@ -371,10 +372,10 @@ class _MoreFeaturesItemBase extends StatelessWidget { class _MoreFeaturesClearSparkCacheItem extends StatefulWidget { const _MoreFeaturesClearSparkCacheItem({ super.key, + required this.cryptoCurrency, }); - static const double iconSizeBG = 46; - static const double iconSize = 24; + final CryptoCurrency cryptoCurrency; @override State<_MoreFeaturesClearSparkCacheItem> createState() => @@ -396,7 +397,9 @@ class _MoreFeaturesClearSparkCacheItemState } _onPressedLock = true; try { - await FiroCacheCoordinator.clearSharedCache(); + await FiroCacheCoordinator.clearSharedCache( + widget.cryptoCurrency.network, + ); setState(() { // trigger rebuild for cache size display }); @@ -434,7 +437,9 @@ class _MoreFeaturesClearSparkCacheItemState style: STextStyles.w600_20(context), ), FutureBuilder( - future: FiroCacheCoordinator.getSparkCacheSize(), + future: FiroCacheCoordinator.getSparkCacheSize( + widget.cryptoCurrency.network, + ), builder: (_, snapshot) { return Text( snapshot.data ?? "", diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/qr_code_desktop_popup_content.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/qr_code_desktop_popup_content.dart index 9fb269448..d1012f1e2 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/qr_code_desktop_popup_content.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/qr_code_desktop_popup_content.dart @@ -9,10 +9,10 @@ */ import 'package:flutter/material.dart'; -import 'package:qr_flutter/qr_flutter.dart'; -import '../../../../themes/stack_colors.dart'; + import '../../../../widgets/desktop/desktop_dialog.dart'; import '../../../../widgets/desktop/desktop_dialog_close_button.dart'; +import '../../../../widgets/qr.dart'; class QRCodeDesktopPopupContent extends StatelessWidget { const QRCodeDesktopPopupContent({ @@ -39,11 +39,9 @@ class QRCodeDesktopPopupContent extends StatelessWidget { const SizedBox( height: 14, ), - QrImageView( + QR( data: value, size: 300, - foregroundColor: - Theme.of(context).extension()!.accentColorDark, ), ], ), diff --git a/lib/providers/ui/preview_tx_button_state_provider.dart b/lib/providers/ui/preview_tx_button_state_provider.dart index 89d960743..196fe298d 100644 --- a/lib/providers/ui/preview_tx_button_state_provider.dart +++ b/lib/providers/ui/preview_tx_button_state_provider.dart @@ -9,27 +9,37 @@ */ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../wallet/public_private_balance_state_provider.dart'; + import '../../utilities/amount/amount.dart'; import '../../wallets/crypto_currency/crypto_currency.dart'; +import '../wallet/public_private_balance_state_provider.dart'; final pSendAmount = StateProvider.autoDispose((_) => null); final pValidSendToAddress = StateProvider.autoDispose((_) => false); final pValidSparkSendToAddress = StateProvider.autoDispose((_) => false); +final pIsExchangeAddress = StateProvider((_) => false); + final pPreviewTxButtonEnabled = Provider.autoDispose.family((ref, coin) { final amount = ref.watch(pSendAmount) ?? Amount.zero; if (coin is Firo) { - if (ref.watch(publicPrivateBalanceStateProvider) == FiroType.lelantus) { - return ref.watch(pValidSendToAddress) && - !ref.watch(pValidSparkSendToAddress) && - amount > Amount.zero; - } else { - return (ref.watch(pValidSendToAddress) || - ref.watch(pValidSparkSendToAddress)) && - amount > Amount.zero; + final firoType = ref.watch(publicPrivateBalanceStateProvider); + switch (firoType) { + case FiroType.lelantus: + return ref.watch(pValidSendToAddress) && + !ref.watch(pValidSparkSendToAddress) && + amount > Amount.zero; + + case FiroType.spark: + return (ref.watch(pValidSendToAddress) || + ref.watch(pValidSparkSendToAddress)) && + !ref.watch(pIsExchangeAddress) && + amount > Amount.zero; + + case FiroType.public: + return ref.watch(pValidSendToAddress) && amount > Amount.zero; } } else { return ref.watch(pValidSendToAddress) && amount > Amount.zero; diff --git a/lib/route_generator.dart b/lib/route_generator.dart index 593d9e511..be011c90f 100644 --- a/lib/route_generator.dart +++ b/lib/route_generator.dart @@ -1982,13 +1982,18 @@ class RouteGenerator { return _routeError("${settings.name} invalid args: ${args.toString()}"); case SparkInfoView.routeName: - return getRoute( - shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => const SparkInfoView(), - settings: RouteSettings( - name: settings.name, - ), - ); + if (args is String) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => SparkInfoView( + walletId: args, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); // == Desktop specific routes ============================================ case CreatePasswordView.routeName: diff --git a/lib/wallets/crypto_currency/coins/firo.dart b/lib/wallets/crypto_currency/coins/firo.dart index cf36840c7..9e4a3bddf 100644 --- a/lib/wallets/crypto_currency/coins/firo.dart +++ b/lib/wallets/crypto_currency/coins/firo.dart @@ -1,5 +1,8 @@ +import 'dart:typed_data'; + import 'package:coinlib_flutter/coinlib_flutter.dart' as coinlib; +import '../../../models/coinlib/exp2pkh_address.dart'; import '../../../models/isar/models/blockchain_data/address.dart'; import '../../../models/node_model.dart'; import '../../../utilities/amount/amount.dart'; @@ -77,6 +80,21 @@ class Firo extends Bip39HDCurrency with ElectrumXCurrencyInterface { fractionDigits: fractionDigits, ); + Uint8List get exAddressVersion { + switch (network) { + case CryptoCurrencyNetwork.main: + // https://github.com/firoorg/firo/blob/master/src/chainparams.cpp#L357 + return Uint8List.fromList([0x01, 0xb9, 0xbb]); + + case CryptoCurrencyNetwork.test: + // https://github.com/firoorg/firo/blob/master/src/chainparams.cpp#L669 + return Uint8List.fromList([0x01, 0xb9, 0xb1]); + + default: + throw Exception("Unsupported network: $network"); + } + } + @override coinlib.Network get networkParams { switch (network) { @@ -169,7 +187,11 @@ class Firo extends Bip39HDCurrency with ElectrumXCurrencyInterface { coinlib.Address.fromString(address, networkParams); return true; } catch (_) { - return validateSparkAddress(address); + if (validateSparkAddress(address)) { + return true; + } else { + return isExchangeAddress(address); + } } } @@ -180,6 +202,18 @@ class Firo extends Bip39HDCurrency with ElectrumXCurrencyInterface { ); } + bool isExchangeAddress(String address) { + try { + EXP2PKHAddress.fromString( + address, + exAddressVersion, + ); + return true; + } catch (_) { + return false; + } + } + @override NodeModel get defaultNode { switch (network) { diff --git a/lib/wallets/wallet/impl/firo_wallet.dart b/lib/wallets/wallet/impl/firo_wallet.dart index 156551be9..61c9346f7 100644 --- a/lib/wallets/wallet/impl/firo_wallet.dart +++ b/lib/wallets/wallet/impl/firo_wallet.dart @@ -387,6 +387,7 @@ class FiroWallet extends Bip39HDWallet parseAnonFees(); final tags = await FiroCacheCoordinator.getUsedCoinTagsFor( txid: txData["txid"] as String, + network: cryptoCurrency.network, ); spentSparkCoins = sparkCoinsInvolvedSpent .where( @@ -631,9 +632,23 @@ class FiroWallet extends Bip39HDWallet BigInt.from(jsonUTXO["value"] as int); if (blocked) { - blockedReason = "Possible masternode output. " + try { + blocked = await electrumXClient.isMasterNodeCollateral( + txid: jsonTX!["txid"] as String, + index: jsonUTXO["tx_pos"] as int, + ); + } catch (_) { + // call failed, lock utxo just in case + // it should logically already be blocked + // but just in case + blocked = true; + } + } + + if (blocked) { + blockedReason = "Possible masternode collateral. " "Unlock and spend at your own risk."; - label = "Possible masternode"; + label = "Possible masternode collateral"; } } @@ -698,12 +713,14 @@ class FiroWallet extends Bip39HDWallet FiroCacheCoordinator.runFetchAndUpdateSparkAnonSetCacheForGroupId( i, electrumXClient, + cryptoCurrency.network, ), ); } final sparkUsedCoinTagsFuture = FiroCacheCoordinator.runFetchAndUpdateSparkUsedCoinTags( electrumXClient, + cryptoCurrency.network, ); // receiving addresses diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart index 5858517fd..c7b8aa259 100644 --- a/lib/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart +++ b/lib/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart @@ -8,6 +8,7 @@ import 'package:isar/isar.dart'; import '../../../electrumx_rpc/cached_electrumx_client.dart'; import '../../../electrumx_rpc/client_manager.dart'; import '../../../electrumx_rpc/electrumx_client.dart'; +import '../../../models/coinlib/exp2pkh_address.dart'; import '../../../models/isar/models/blockchain_data/v2/input_v2.dart'; import '../../../models/isar/models/blockchain_data/v2/output_v2.dart'; import '../../../models/isar/models/blockchain_data/v2/transaction_v2.dart'; @@ -24,6 +25,7 @@ import '../../crypto_currency/coins/firo.dart'; import '../../crypto_currency/interfaces/electrumx_currency_interface.dart'; import '../../models/tx_data.dart'; import '../impl/bitcoin_wallet.dart'; +import '../impl/firo_wallet.dart'; import '../impl/peercoin_wallet.dart'; import '../intermediate/bip39_hd_wallet.dart'; import 'cpfp_interface.dart'; @@ -725,11 +727,23 @@ mixin ElectrumXInterface // Add transaction output for (var i = 0; i < txData.recipients!.length; i++) { - final address = coinlib.Address.fromString( - normalizeAddress(txData.recipients![i].address), - cryptoCurrency.networkParams, - ); + late final coinlib.Address address; + try { + address = coinlib.Address.fromString( + normalizeAddress(txData.recipients![i].address), + cryptoCurrency.networkParams, + ); + } catch (_) { + if (this is FiroWallet) { + address = EXP2PKHAddress.fromString( + normalizeAddress(txData.recipients![i].address), + (cryptoCurrency as Firo).exAddressVersion, + ); + } else { + rethrow; + } + } final output = coinlib.Output.fromAddress( txData.recipients![i].amount.raw, address, diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart index 90f7cfe31..09d4dc6f1 100644 --- a/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart +++ b/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart @@ -278,13 +278,17 @@ mixin SparkInterface final List> setMaps = []; final List<({int groupId, String blockHash})> idAndBlockHashes = []; for (int i = 1; i <= currentId; i++) { - final resultSet = await FiroCacheCoordinator.getSetCoinsForGroupId(i); + final resultSet = await FiroCacheCoordinator.getSetCoinsForGroupId( + i, + network: cryptoCurrency.network, + ); if (resultSet.isEmpty) { continue; } final info = await FiroCacheCoordinator.getLatestSetInfoForGroupId( i, + cryptoCurrency.network, ); if (info == null) { throw Exception("The `info` should never be null here"); @@ -716,13 +720,13 @@ mixin SparkInterface return result; } catch (e) { Logging.instance.log( - "refreshSparkMempoolData() failed: $e", + "_refreshSparkCoinsMempoolCheck() failed: $e", level: LogLevel.Error, ); return []; } finally { Logging.instance.log( - "$walletId ${info.name} refreshSparkCoinsMempoolCheck() run " + "$walletId ${info.name} _refreshSparkCoinsMempoolCheck() run " "duration: ${DateTime.now().difference(start)}", level: LogLevel.Debug, ); @@ -741,6 +745,7 @@ mixin SparkInterface final setExists = await FiroCacheCoordinator.checkSetInfoForGroupIdExists( id, + cryptoCurrency.network, ); if (!setExists) { groupIds.add(id); @@ -755,6 +760,7 @@ mixin SparkInterface FiroCacheCoordinator.runFetchAndUpdateSparkAnonSetCacheForGroupId( e, electrumXClient, + cryptoCurrency.network, ), ); @@ -763,6 +769,7 @@ mixin SparkInterface ...possibleFutures, FiroCacheCoordinator.runFetchAndUpdateSparkUsedCoinTags( electrumXClient, + cryptoCurrency.network, ), ]); @@ -782,11 +789,13 @@ mixin SparkInterface groupIdTimestampUTCMap[i.toString()] as int? ?? 0; final info = await FiroCacheCoordinator.getLatestSetInfoForGroupId( i, + cryptoCurrency.network, ); final anonymitySetResult = await FiroCacheCoordinator.getSetCoinsForGroupId( i, newerThanTimeStamp: lastCheckedTimeStampUTC, + network: cryptoCurrency.network, ); final coinsRaw = anonymitySetResult .map( @@ -882,7 +891,10 @@ mixin SparkInterface // only fetch tags from db if we need them to compare against any items // in coinsToCheck if (coinsToCheck.isNotEmpty) { - spentCoinTags = await FiroCacheCoordinator.getUsedCoinTags(0); + spentCoinTags = await FiroCacheCoordinator.getUsedCoinTags( + 0, + cryptoCurrency.network, + ); } // check and update coins if required @@ -992,6 +1004,7 @@ mixin SparkInterface final pairs = await FiroCacheCoordinator.getUsedCoinTxidsFor( tags: tags, + network: cryptoCurrency.network, ); pairs.removeWhere((e) => usedCoinTxidsFoundLocally.contains(e.txid)); diff --git a/lib/widgets/breathing.dart b/lib/widgets/breathing.dart new file mode 100644 index 000000000..af4721d90 --- /dev/null +++ b/lib/widgets/breathing.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; + +class Breathing extends StatefulWidget { + const Breathing({super.key, required this.child}); + + final Widget child; + + @override + State createState() => _BreathingState(); +} + +class _BreathingState extends State { + bool _hovering = false; + @override + Widget build(BuildContext context) { + return MouseRegion( + onEnter: (_) => setState( + () => _hovering = true, + ), + onExit: (_) => setState( + () => _hovering = false, + ), + child: AnimatedScale( + scale: _hovering ? 1.00 : 0.98, + duration: const Duration( + milliseconds: 200, + ), + child: widget.child, + ), + ); + } +} diff --git a/lib/widgets/dialogs/firo_exchange_address_dialog.dart b/lib/widgets/dialogs/firo_exchange_address_dialog.dart new file mode 100644 index 000000000..46ae13b88 --- /dev/null +++ b/lib/widgets/dialogs/firo_exchange_address_dialog.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; + +import '../../utilities/util.dart'; +import '../stack_dialog.dart'; + +class FiroExchangeAddressDialog extends StatelessWidget { + const FiroExchangeAddressDialog({super.key}); + + @override + Widget build(BuildContext context) { + return StackOkDialog( + title: "Firo exchange address detected", + message: "Sending to an exchange address from a Spark balance is not" + " allowed. Please send from your transparent balance.", + desktopPopRootNavigator: Util.isDesktop, + maxWidth: Util.isDesktop ? 500 : null, + ); + } +} + +Future showFiroExchangeAddressWarning( + BuildContext context, + VoidCallback onClosed, +) async { + await showDialog( + context: context, + builder: (_) => const FiroExchangeAddressDialog(), + ); + onClosed(); +} diff --git a/lib/widgets/dialogs/frost/frost_step_qr_dialog.dart b/lib/widgets/dialogs/frost/frost_step_qr_dialog.dart index c19eb2b2c..949e2fed7 100644 --- a/lib/widgets/dialogs/frost/frost_step_qr_dialog.dart +++ b/lib/widgets/dialogs/frost/frost_step_qr_dialog.dart @@ -7,7 +7,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:qr_flutter/qr_flutter.dart'; + import 'package:share_plus/share_plus.dart'; import '../../../notifications/show_flush_bar.dart'; @@ -17,6 +17,7 @@ import '../../../utilities/text_styles.dart'; import '../../../utilities/util.dart'; import '../../conditional_parent.dart'; import '../../desktop/secondary_button.dart'; +import '../../qr.dart'; import '../../rounded_container.dart'; import '../../rounded_white_container.dart'; import '../simple_mobile_dialog.dart'; @@ -154,18 +155,9 @@ class _FrostStepQrDialogState extends State { padding: const EdgeInsets.all(16), child: AspectRatio( aspectRatio: 1, - child: QrImageView( + child: QR( data: widget.data, padding: EdgeInsets.zero, - foregroundColor: Theme.of(context) - .extension()! - .accentColorDark, - // dataModuleStyle: QrDataModuleStyle( - // dataModuleShape: QrDataModuleShape.square, - // color: Theme.of(context) - // .extension()! - // .accentColorDark, - // ), ), ), ), diff --git a/lib/widgets/qr.dart b/lib/widgets/qr.dart new file mode 100644 index 000000000..39e874474 --- /dev/null +++ b/lib/widgets/qr.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:qr_flutter/qr_flutter.dart'; + +/// Centralised Qr code image widget +class QR extends StatelessWidget { + const QR({super.key, required this.data, this.size, this.padding}); + + final String data; + final double? size; + final EdgeInsets? padding; + + @override + Widget build(BuildContext context) { + return QrImageView( + data: data, + size: size, + padding: padding ?? const EdgeInsets.all(10), + backgroundColor: Colors.white, + foregroundColor: Colors.black, + // backgroundColor: + // Theme.of(context).extension()!.background, + // foregroundColor: Theme.of(context) + // .extension()! + // .accentColorDark, + ); + } +} diff --git a/lib/widgets/stack_dialog.dart b/lib/widgets/stack_dialog.dart index 1c189e660..1541256a0 100644 --- a/lib/widgets/stack_dialog.dart +++ b/lib/widgets/stack_dialog.dart @@ -188,14 +188,19 @@ class StackOkDialog extends StatelessWidget { height: 8, ), if (message != null) - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - message!, - style: STextStyles.smallMed14(context), - ), - ], + ConstrainedBox( + constraints: + BoxConstraints(maxWidth: maxWidth ?? double.infinity), + child: Row( + children: [ + Flexible( + child: Text( + message!, + style: STextStyles.smallMed14(context), + ), + ), + ], + ), ), const SizedBox( height: 20, diff --git a/pubspec.lock b/pubspec.lock index 47107034f..7a25ed30b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1807,8 +1807,8 @@ packages: dependency: "direct main" description: path: "." - ref: f1d02f7ad489df3119a540a7f31485db6d837843 - resolved-ref: f1d02f7ad489df3119a540a7f31485db6d837843 + ref: "647cadc3c82c276dc07915b02d24538fd610f220" + resolved-ref: "647cadc3c82c276dc07915b02d24538fd610f220" url: "https://github.com/cypherstack/tor.git" source: git version: "0.0.1"