From 70f4ef25b3a4bd393007765bf17ff9fac8eb3f81 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 13 Aug 2025 12:38:40 -0600 Subject: [PATCH 1/3] pre cache mobile settings icons --- lib/pages/home_view/home_view.dart | 36 ++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/lib/pages/home_view/home_view.dart b/lib/pages/home_view/home_view.dart index 126a16bfb..4beb06663 100644 --- a/lib/pages/home_view/home_view.dart +++ b/lib/pages/home_view/home_view.dart @@ -29,6 +29,7 @@ import '../../utilities/constants.dart'; import '../../utilities/idle_monitor.dart'; import '../../utilities/prefs.dart'; import '../../utilities/text_styles.dart'; +import '../../utilities/util.dart'; import '../../widgets/animated_widgets/rotate_icon.dart'; import '../../widgets/app_icon.dart'; import '../../widgets/background.dart'; @@ -171,6 +172,41 @@ class _HomeViewState extends ConsumerState { ); } + Future precacheSettingsIcons(BuildContext context) async { + if (Util.isDesktop) return; + + final icons = [ + Assets.svg.addressBook, + Assets.svg.downloadFolder, + Assets.svg.lock, + Assets.svg.dollarSign, + Assets.svg.language, + Assets.svg.node, + Assets.svg.arrowRotate, + Assets.svg.arrowUpRight, + Assets.svg.sun, + Assets.svg.circleAlert, + Assets.svg.ellipsis, + Assets.svg.solidSliders, + Assets.svg.questionMessage, + ]; + + for (final asset in icons) { + final loader = SvgAssetLoader(asset); + await svg.cache.putIfAbsent( + loader.cacheKey(context), + () => loader.loadBytes(context), + ); + } + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + + precacheSettingsIcons(context); + } + @override void initState() { _autoLockInfo = ref.read(prefsChangeNotifierProvider).autoLockInfo; From e8117528904b1691b5d23156568819724878e582 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 13 Aug 2025 19:11:30 -0600 Subject: [PATCH 2/3] auto one time clear of possible bad used tags cache. --- lib/db/sqlite/firo_cache.dart | 57 +++++++-------- lib/db/sqlite/firo_cache_coordinator.dart | 69 +++++++++---------- lib/wallets/isar/models/wallet_info.dart | 2 + lib/wallets/wallet/wallet.dart | 7 ++ .../spark_interface.dart | 15 ++++ 5 files changed, 85 insertions(+), 65 deletions(-) diff --git a/lib/db/sqlite/firo_cache.dart b/lib/db/sqlite/firo_cache.dart index c2ebbfe66..4478f421f 100644 --- a/lib/db/sqlite/firo_cache.dart +++ b/lib/db/sqlite/firo_cache.dart @@ -65,11 +65,13 @@ abstract class _FiroCache { await StackFileSystem.applicationFiroCacheSQLiteDirectory(); for (final network in networks) { - final sparkSetCacheFile = - File("${sqliteDir.path}/${sparkSetCacheFileName(network)}"); + final sparkSetCacheFile = File( + "${sqliteDir.path}/${sparkSetCacheFileName(network)}", + ); - final sparkUsedTagsCacheFile = - File("${sqliteDir.path}/${sparkUsedTagsCacheFileName(network)}"); + final sparkUsedTagsCacheFile = File( + "${sqliteDir.path}/${sparkUsedTagsCacheFileName(network)}", + ); if (!(await sparkSetCacheFile.exists())) { await _createSparkSetCacheDb(sparkSetCacheFile.path); @@ -91,35 +93,40 @@ abstract class _FiroCache { static Future _deleteAllCache(CryptoCurrencyNetwork network) async { final start = DateTime.now(); - setCacheDB(network).execute( - """ + setCacheDB(network).execute(""" DELETE FROM SparkSet; DELETE FROM SparkCoin; DELETE FROM SparkSetCoins; VACUUM; - """, + """); + await _deleteUsedTagsCache(network); + + Logging.instance.d( + "_deleteAllCache() " + "duration = ${DateTime.now().difference(start)}", ); - usedTagsCacheDB(network).execute( - """ + } + + static Future _deleteUsedTagsCache( + CryptoCurrencyNetwork network, + ) async { + final start = DateTime.now(); + + usedTagsCacheDB(network).execute(""" DELETE FROM SparkUsedCoinTags; VACUUM; - """, - ); + """); Logging.instance.d( - "_deleteAllCache() " + "_deleteUsedTagsCache() " "duration = ${DateTime.now().difference(start)}", ); } static Future _createSparkSetCacheDb(String file) async { - final db = sqlite3.open( - file, - mode: OpenMode.readWriteCreate, - ); + final db = sqlite3.open(file, mode: OpenMode.readWriteCreate); - db.execute( - """ + db.execute(""" CREATE TABLE SparkSet ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, blockHash TEXT NOT NULL, @@ -145,27 +152,21 @@ abstract class _FiroCache { FOREIGN KEY (setId) REFERENCES SparkSet(id), FOREIGN KEY (coinId) REFERENCES SparkCoin(id) ); - """, - ); + """); db.dispose(); } static Future _createSparkUsedTagsCacheDb(String file) async { - final db = sqlite3.open( - file, - mode: OpenMode.readWriteCreate, - ); + final db = sqlite3.open(file, mode: OpenMode.readWriteCreate); - db.execute( - """ + db.execute(""" CREATE TABLE SparkUsedCoinTags ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, tag TEXT NOT NULL UNIQUE, txid TEXT NOT NULL ); - """, - ); + """); db.dispose(); } diff --git a/lib/db/sqlite/firo_cache_coordinator.dart b/lib/db/sqlite/firo_cache_coordinator.dart index 6adeba520..71f01e92b 100644 --- a/lib/db/sqlite/firo_cache_coordinator.dart +++ b/lib/db/sqlite/firo_cache_coordinator.dart @@ -23,7 +23,13 @@ abstract class FiroCacheCoordinator { } } - static Future clearSharedCache(CryptoCurrencyNetwork network) async { + static Future clearSharedCache( + CryptoCurrencyNetwork network, { + bool clearOnlyUsedTagsCache = false, + }) async { + if (clearOnlyUsedTagsCache) { + return await _FiroCache._deleteUsedTagsCache(network); + } return await _FiroCache._deleteAllCache(network); } @@ -38,9 +44,10 @@ abstract class FiroCacheCoordinator { final setSize = (await setCacheFile.exists()) ? await setCacheFile.length() : 0; - final tagsSize = (await usedTagsCacheFile.exists()) - ? await usedTagsCacheFile.length() - : 0; + final tagsSize = + (await usedTagsCacheFile.exists()) + ? await usedTagsCacheFile.length() + : 0; Logging.instance.d("Spark cache used tags size: $tagsSize"); Logging.instance.d("Spark cache anon set size: $setSize"); @@ -67,16 +74,11 @@ abstract class FiroCacheCoordinator { ) async { await _tagLocks[network]!.protect(() async { final count = await FiroCacheCoordinator.getUsedCoinTagsCount(network); - final unhashedTags = - await client.getSparkUnhashedUsedCoinsTagsWithTxHashes( - startNumber: count, - ); + final unhashedTags = await client + .getSparkUnhashedUsedCoinsTagsWithTxHashes(startNumber: count); if (unhashedTags.isNotEmpty) { await _workers[network]!.runTask( - FCTask( - func: FCFuncName._updateSparkUsedTagsWith, - data: unhashedTags, - ), + FCTask(func: FCFuncName._updateSparkUsedTagsWith, data: unhashedTags), ); } }); @@ -98,9 +100,7 @@ abstract class FiroCacheCoordinator { final prevSize = prevMeta?.size ?? 0; - final meta = await client.getSparkAnonymitySetMeta( - coinGroupId: groupId, - ); + final meta = await client.getSparkAnonymitySetMeta(coinGroupId: groupId); progressUpdated?.call(prevSize, meta.size); @@ -141,9 +141,10 @@ abstract class FiroCacheCoordinator { coins.addAll(data); } - final result = coins - .map((e) => RawSparkCoin.fromRPCResponse(e as List, groupId)) - .toList(); + final result = + coins + .map((e) => RawSparkCoin.fromRPCResponse(e as List, groupId)) + .toList(); await _workers[network]!.runTask( FCTask( @@ -167,9 +168,7 @@ abstract class FiroCacheCoordinator { return result.map((e) => e["tag"] as String).toSet(); } - static Future getUsedCoinTagsCount( - CryptoCurrencyNetwork network, - ) async { + static Future getUsedCoinTagsCount(CryptoCurrencyNetwork network) async { final result = await _Reader._getUsedCoinTagsCount( db: _FiroCache.usedTagsCacheDB(network), ); @@ -195,12 +194,7 @@ abstract class FiroCacheCoordinator { return []; } return result.rows - .map( - (e) => ( - tag: e[0] as String, - txid: e[1] as String, - ), - ) + .map((e) => (tag: e[0] as String, txid: e[1] as String)) .toList(); } @@ -230,16 +224,17 @@ abstract class FiroCacheCoordinator { String? afterBlockHash, required CryptoCurrencyNetwork network, }) async { - final resultSet = afterBlockHash == null - ? await _Reader._getSetCoinsForGroupId( - groupId, - db: _FiroCache.setCacheDB(network), - ) - : await _Reader._getSetCoinsForGroupIdAndBlockHash( - groupId, - afterBlockHash, - db: _FiroCache.setCacheDB(network), - ); + final resultSet = + afterBlockHash == null + ? await _Reader._getSetCoinsForGroupId( + groupId, + db: _FiroCache.setCacheDB(network), + ) + : await _Reader._getSetCoinsForGroupIdAndBlockHash( + groupId, + afterBlockHash, + db: _FiroCache.setCacheDB(network), + ); return resultSet .map( diff --git a/lib/wallets/isar/models/wallet_info.dart b/lib/wallets/isar/models/wallet_info.dart index 6df8dc456..9d790a830 100644 --- a/lib/wallets/isar/models/wallet_info.dart +++ b/lib/wallets/isar/models/wallet_info.dart @@ -521,4 +521,6 @@ abstract class WalletInfoKeys { "duressMarkedVisibleWalletKey"; static const String mwebEnabled = "mwebEnabledKey"; static const String mwebScanHeight = "mwebScanHeightKey"; + static const String firoSparkUsedTagsCacheResetVersion = + "firoSparkUsedTagsCacheResetVersionKey"; } diff --git a/lib/wallets/wallet/wallet.dart b/lib/wallets/wallet/wallet.dart index 554e04313..55319d12e 100644 --- a/lib/wallets/wallet/wallet.dart +++ b/lib/wallets/wallet/wallet.dart @@ -225,6 +225,13 @@ abstract class Wallet { await wallet.mainDB.isar.walletInfo.put(walletInfo); }); + if (wallet is SparkInterface) { + await walletInfo.updateOtherData( + newEntries: {WalletInfoKeys.firoSparkUsedTagsCacheResetVersion: 1}, + isar: mainDB.isar, + ); + } + return wallet; } diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart index 671c79770..6a5b6c4fc 100644 --- a/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart +++ b/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart @@ -148,6 +148,21 @@ mixin SparkInterface @override Future init() async { try { + final sparkUsedTagsResetVersion = + info.otherData[WalletInfoKeys.firoSparkUsedTagsCacheResetVersion] + as int? ?? + 0; + if (sparkUsedTagsResetVersion == 0) { + await info.updateOtherData( + newEntries: {WalletInfoKeys.firoSparkUsedTagsCacheResetVersion: 1}, + isar: mainDB.isar, + ); + await FiroCacheCoordinator.clearSharedCache( + cryptoCurrency.network, + clearOnlyUsedTagsCache: true, + ); + } + Address? address = await getCurrentReceivingSparkAddress(); if (address == null) { address = await generateNextSparkAddress(); From 7bf0f903808620ef511b0457c2e8127e9ff3b561 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 15 Aug 2025 10:50:11 -0600 Subject: [PATCH 3/3] various firo tweaks --- lib/db/sqlite/firo_cache_coordinator.dart | 8 ++++---- lib/db/sqlite/firo_cache_writer.dart | 19 ++++++------------ lib/electrumx_rpc/electrumx_client.dart | 2 +- .../spark_names/buy_spark_name_view.dart | 15 ++++++++++++-- .../confirm_spark_name_transaction_view.dart | 20 +++++++++++++++++++ .../buy_spark_name_option_widget.dart | 10 ++++++++++ .../sub_widgets/owned_spark_name_card.dart | 5 ++++- .../sub_widgets/spark_name_details.dart | 5 ++++- .../spark_interface.dart | 2 +- pubspec.lock | 4 ++-- scripts/app_config/templates/pubspec.template | 2 +- 11 files changed, 66 insertions(+), 26 deletions(-) diff --git a/lib/db/sqlite/firo_cache_coordinator.dart b/lib/db/sqlite/firo_cache_coordinator.dart index 71f01e92b..5ecd7534b 100644 --- a/lib/db/sqlite/firo_cache_coordinator.dart +++ b/lib/db/sqlite/firo_cache_coordinator.dart @@ -157,7 +157,7 @@ abstract class FiroCacheCoordinator { // =========================================================================== - static Future> getUsedCoinTags( + static Future> getUsedCoinTags( int startNumber, CryptoCurrencyNetwork network, ) async { @@ -165,7 +165,7 @@ abstract class FiroCacheCoordinator { startNumber, db: _FiroCache.usedTagsCacheDB(network), ); - return result.map((e) => e["tag"] as String).toSet(); + return result.map((e) => e["tag"] as String).toList(); } static Future getUsedCoinTagsCount(CryptoCurrencyNetwork network) async { @@ -198,7 +198,7 @@ abstract class FiroCacheCoordinator { .toList(); } - static Future> getUsedCoinTagsFor({ + static Future> getUsedCoinTagsFor({ required String txid, required CryptoCurrencyNetwork network, }) async { @@ -206,7 +206,7 @@ abstract class FiroCacheCoordinator { txid, db: _FiroCache.usedTagsCacheDB(network), ); - return result.map((e) => e["tag"] as String).toSet(); + return result.map((e) => e["tag"] as String).toList(); } static Future checkTagIsUsed( diff --git a/lib/db/sqlite/firo_cache_writer.dart b/lib/db/sqlite/firo_cache_writer.dart index 63192d964..09383601e 100644 --- a/lib/db/sqlite/firo_cache_writer.dart +++ b/lib/db/sqlite/firo_cache_writer.dart @@ -13,15 +13,12 @@ class FCResult { /// update the sqlite cache /// Expected json format: /// returns true if successful, otherwise some exception -FCResult _updateSparkUsedTagsWith( - Database db, - List> tags, -) { +FCResult _updateSparkUsedTagsWith(Database db, List> tags) { // hash the tags here since this function is called in a background isolate - final hashedTags = LibSpark.hashTags( - base64Tags: tags.map((e) => e[0] as String).toSet(), - ).toList(); - + final hashedTags = + LibSpark.hashTags( + base64Tags: tags.map((e) => e[0] as String).toList(), + ).toList(); if (hashedTags.isEmpty) { // nothing to add, return early return FCResult(success: true); @@ -70,11 +67,7 @@ FCResult _updateSparkAnonSetCoinsWith( FROM SparkSet WHERE blockHash = ? AND setHash = ? AND groupId = ?; """, - [ - meta.blockHash, - meta.setHash, - meta.coinGroupId, - ], + [meta.blockHash, meta.setHash, meta.coinGroupId], ); if (checkResult.isNotEmpty) { diff --git a/lib/electrumx_rpc/electrumx_client.dart b/lib/electrumx_rpc/electrumx_client.dart index befe70fc3..c0d497a9e 100644 --- a/lib/electrumx_rpc/electrumx_client.dart +++ b/lib/electrumx_rpc/electrumx_client.dart @@ -1129,7 +1129,7 @@ class ElectrumXClient { "Duration=${DateTime.now().difference(start)}", ); - return tags; + return tags.reversed.toList(); } catch (e, s) { Logging.instance.e(e, error: e, stackTrace: s); rethrow; diff --git a/lib/pages/spark_names/buy_spark_name_view.dart b/lib/pages/spark_names/buy_spark_name_view.dart index 036a551d3..fa2cae828 100644 --- a/lib/pages/spark_names/buy_spark_name_view.dart +++ b/lib/pages/spark_names/buy_spark_name_view.dart @@ -69,11 +69,22 @@ class _BuySparkNameViewState extends ConsumerState { try { final wallet = ref.read(pWallets).getWallet(widget.walletId) as SparkInterface; - final myAddress = await wallet.getCurrentReceivingSparkAddress(); + Address? myAddress = await wallet.getCurrentReceivingSparkAddress(); if (myAddress == null) { throw Exception("No spark address found"); } - addressController.text = myAddress.value; + + final db = ref.read(pDrift(widget.walletId)); + final myNames = await db.select(db.sparkNames).get(); + while (myNames.where((e) => e.address == myAddress!.value).isNotEmpty) { + Logging.instance.t( + "Found address that already has a spark name. Generating next address...", + ); + myAddress = await wallet.generateNextSparkAddress(); + await ref.read(mainDBProvider).updateOrPutAddresses([myAddress]); + } + + addressController.text = myAddress!.value; } catch (e, s) { Logging.instance.e("_fillCurrentReceiving", error: e, stackTrace: s); } finally { diff --git a/lib/pages/spark_names/confirm_spark_name_transaction_view.dart b/lib/pages/spark_names/confirm_spark_name_transaction_view.dart index c98409dfd..3282b1f1f 100644 --- a/lib/pages/spark_names/confirm_spark_name_transaction_view.dart +++ b/lib/pages/spark_names/confirm_spark_name_transaction_view.dart @@ -16,6 +16,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; +import '../../models/isar/models/blockchain_data/address.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'; @@ -128,6 +129,25 @@ class _ConfirmSparkNameTransactionViewState ); } + final address = txData.sparkNameInfo?.sparkAddress; + final currentReceiving = await wallet.getCurrentReceivingSparkAddress(); + if (currentReceiving?.value == address?.value) { + final address = await wallet.generateNextSparkAddress(); + await ref.read(mainDBProvider).isar.writeTxn(() async { + await ref.read(mainDBProvider).isar.addresses.put(address); + }); + } + + final db = ref.read(pDrift(walletId)); + await db.upsertSparkNames([ + ( + name: txData.sparkNameInfo!.name, + address: txData.sparkNameInfo!.sparkAddress.value, + validUntil: -99999, + additionalInfo: txData.sparkNameInfo!.additionalInfo, + ), + ]); + unawaited(wallet.refresh()); if (mounted) { 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 index 18b6bb6f4..cc7fcfa0e 100644 --- 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 @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:drift/drift.dart' as drift; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_libsparkmobile/flutter_libsparkmobile.dart'; @@ -55,6 +56,15 @@ class _BuySparkNameWidgetState extends ConsumerState { )) { rethrow; } + final db = ref.read(pDrift(widget.walletId)); + final results = + await (db.select(db.sparkNames) + ..where((e) => e.name.lower().equals(name))).get(); + + if (results.isNotEmpty) { + return false; + } + // name not found return true; } 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 index fc813efb9..3971fdd4e 100644 --- a/lib/pages/spark_names/sub_widgets/owned_spark_name_card.dart +++ b/lib/pages/spark_names/sub_widgets/owned_spark_name_card.dart @@ -32,7 +32,10 @@ class _OwnedSparkNameCardState extends ConsumerState { final remaining = widget.name.validUntil - currentChainHeight; - if (remaining <= 0) { + if (widget.name.validUntil == -99999) { + color = theme.accentColorYellow; + message = "Pending"; + } else if (remaining <= 0) { color = theme.accentColorRed; message = "Expired"; } else { diff --git a/lib/pages/spark_names/sub_widgets/spark_name_details.dart b/lib/pages/spark_names/sub_widgets/spark_name_details.dart index 3acaad8f5..97983735a 100644 --- a/lib/pages/spark_names/sub_widgets/spark_name_details.dart +++ b/lib/pages/spark_names/sub_widgets/spark_name_details.dart @@ -53,7 +53,10 @@ class _SparkNameDetailsViewState extends ConsumerState { final remaining = name.validUntil - currentChainHeight; - if (remaining <= 0) { + if (widget.name.validUntil == -99999) { + color = theme.accentColorYellow; + message = "Pending"; + } else if (remaining <= 0) { color = theme.accentColorRed; message = "Expired"; } else { diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart index 6a5b6c4fc..c3aff94fd 100644 --- a/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart +++ b/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart @@ -1118,7 +1118,7 @@ mixin SparkInterface .isUsedEqualTo(false) .findAll(); - Set? spentCoinTags; + List? spentCoinTags; // only fetch tags from db if we need them to compare against any items // in coinsToCheck if (coinsToCheck.isNotEmpty) { diff --git a/pubspec.lock b/pubspec.lock index ab6a39710..4ef194c2b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -944,8 +944,8 @@ packages: dependency: "direct main" description: path: "." - ref: f5fd2238fca4ffe82f7e14646a613a04d2c243d6 - resolved-ref: f5fd2238fca4ffe82f7e14646a613a04d2c243d6 + ref: "5f0dde009c969bf6425da35382305b2c0b65bc46" + resolved-ref: "5f0dde009c969bf6425da35382305b2c0b65bc46" url: "https://github.com/cypherstack/flutter_libsparkmobile.git" source: git version: "0.1.0" diff --git a/scripts/app_config/templates/pubspec.template b/scripts/app_config/templates/pubspec.template index ef0f72984..27bdc1990 100644 --- a/scripts/app_config/templates/pubspec.template +++ b/scripts/app_config/templates/pubspec.template @@ -35,7 +35,7 @@ dependencies: flutter_libsparkmobile: git: url: https://github.com/cypherstack/flutter_libsparkmobile.git - ref: f5fd2238fca4ffe82f7e14646a613a04d2c243d6 + ref: 5f0dde009c969bf6425da35382305b2c0b65bc46 # cs_monero compat (unpublished) compat: