Merge branch 'staging' into docs

This commit is contained in:
julian-CStack 2024-07-01 09:54:09 -06:00 committed by GitHub
commit dc2a341e6b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
52 changed files with 925 additions and 462 deletions

@ -1 +1 @@
Subproject commit adc7bf50abe4bbe90d5050b82fb5751937cbae4e Subproject commit 982f5ab19fe0dd3dd3f6be2c46f8dff13d49027c

View file

@ -12,6 +12,7 @@ import '../../electrumx_rpc/electrumx_client.dart';
import '../../utilities/extensions/extensions.dart'; import '../../utilities/extensions/extensions.dart';
import '../../utilities/logger.dart'; import '../../utilities/logger.dart';
import '../../utilities/stack_file_system.dart'; import '../../utilities/stack_file_system.dart';
import '../../wallets/crypto_currency/crypto_currency.dart';
part 'firo_cache_coordinator.dart'; part 'firo_cache_coordinator.dart';
part 'firo_cache_reader.dart'; part 'firo_cache_reader.dart';
@ -31,29 +32,39 @@ void _debugLog(Object? object) {
abstract class _FiroCache { abstract class _FiroCache {
static const int _setCacheVersion = 1; static const int _setCacheVersion = 1;
static const int _tagsCacheVersion = 2; 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 final networks = [
static Database? _usedTagsCacheDB; CryptoCurrencyNetwork.main,
static Database get setCacheDB { CryptoCurrencyNetwork.test,
if (_setCacheDB == null) { ];
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<CryptoCurrencyNetwork, Database> _setCacheDB = {};
static final Map<CryptoCurrencyNetwork, Database> _usedTagsCacheDB = {};
static Database setCacheDB(CryptoCurrencyNetwork network) {
if (_setCacheDB[network] == null) {
throw Exception( throw Exception(
"FiroCache.init() must be called before accessing FiroCache.db!", "FiroCache.init() must be called before accessing FiroCache.db!",
); );
} }
return _setCacheDB!; return _setCacheDB[network]!;
} }
static Database get usedTagsCacheDB { static Database usedTagsCacheDB(CryptoCurrencyNetwork network) {
if (_usedTagsCacheDB == null) { if (_usedTagsCacheDB[network] == null) {
throw Exception( throw Exception(
"FiroCache.init() must be called before accessing FiroCache.db!", "FiroCache.init() must be called before accessing FiroCache.db!",
); );
} }
return _usedTagsCacheDB!; return _usedTagsCacheDB[network]!;
} }
static Future<void>? _initFuture; static Future<void>? _initFuture;
@ -63,30 +74,34 @@ abstract class _FiroCache {
final sqliteDir = final sqliteDir =
await StackFileSystem.applicationFiroCacheSQLiteDirectory(); await StackFileSystem.applicationFiroCacheSQLiteDirectory();
final sparkSetCacheFile = File("${sqliteDir.path}/$sparkSetCacheFileName"); for (final network in networks) {
final sparkUsedTagsCacheFile = final sparkSetCacheFile =
File("${sqliteDir.path}/$sparkUsedTagsCacheFileName"); File("${sqliteDir.path}/${sparkSetCacheFileName(network)}");
if (!(await sparkSetCacheFile.exists())) { final sparkUsedTagsCacheFile =
await _createSparkSetCacheDb(sparkSetCacheFile.path); File("${sqliteDir.path}/${sparkUsedTagsCacheFileName(network)}");
}
if (!(await sparkUsedTagsCacheFile.exists())) {
await _createSparkUsedTagsCacheDb(sparkUsedTagsCacheFile.path);
}
_setCacheDB = sqlite3.open( if (!(await sparkSetCacheFile.exists())) {
sparkSetCacheFile.path, await _createSparkSetCacheDb(sparkSetCacheFile.path);
mode: OpenMode.readWrite, }
); if (!(await sparkUsedTagsCacheFile.exists())) {
_usedTagsCacheDB = sqlite3.open( await _createSparkUsedTagsCacheDb(sparkUsedTagsCacheFile.path);
sparkUsedTagsCacheFile.path, }
mode: OpenMode.readWrite,
); _setCacheDB[network] = sqlite3.open(
sparkSetCacheFile.path,
mode: OpenMode.readWrite,
);
_usedTagsCacheDB[network] = sqlite3.open(
sparkUsedTagsCacheFile.path,
mode: OpenMode.readWrite,
);
}
} }
static Future<void> _deleteAllCache() async { static Future<void> _deleteAllCache(CryptoCurrencyNetwork network) async {
final start = DateTime.now(); final start = DateTime.now();
setCacheDB.execute( setCacheDB(network).execute(
""" """
DELETE FROM SparkSet; DELETE FROM SparkSet;
DELETE FROM SparkCoin; DELETE FROM SparkCoin;
@ -94,7 +109,7 @@ abstract class _FiroCache {
VACUUM; VACUUM;
""", """,
); );
usedTagsCacheDB.execute( usedTagsCacheDB(network).execute(
""" """
DELETE FROM SparkUsedCoinTags; DELETE FROM SparkUsedCoinTags;
VACUUM; VACUUM;

View file

@ -5,7 +5,7 @@ typedef LTagPair = ({String tag, String txid});
/// Wrapper class for [_FiroCache] as [_FiroCache] should eventually be handled in a /// Wrapper class for [_FiroCache] as [_FiroCache] should eventually be handled in a
/// background isolate and [FiroCacheCoordinator] should manage that isolate /// background isolate and [FiroCacheCoordinator] should manage that isolate
abstract class FiroCacheCoordinator { abstract class FiroCacheCoordinator {
static _FiroCacheWorker? _worker; static final Map<CryptoCurrencyNetwork, _FiroCacheWorker> _workers = {};
static bool _init = false; static bool _init = false;
static Future<void> init() async { static Future<void> init() async {
@ -14,20 +14,22 @@ abstract class FiroCacheCoordinator {
} }
_init = true; _init = true;
await _FiroCache.init(); await _FiroCache.init();
_worker = await _FiroCacheWorker.spawn(); for (final network in _FiroCache.networks) {
_workers[network] = await _FiroCacheWorker.spawn(network);
}
} }
static Future<void> clearSharedCache() async { static Future<void> clearSharedCache(CryptoCurrencyNetwork network) async {
return await _FiroCache._deleteAllCache(); return await _FiroCache._deleteAllCache(network);
} }
static Future<String> getSparkCacheSize() async { static Future<String> getSparkCacheSize(CryptoCurrencyNetwork network) async {
final dir = await StackFileSystem.applicationFiroCacheSQLiteDirectory(); final dir = await StackFileSystem.applicationFiroCacheSQLiteDirectory();
final setCacheFile = File( final setCacheFile = File(
"${dir.path}/${_FiroCache.sparkSetCacheFileName}", "${dir.path}/${_FiroCache.sparkSetCacheFileName(network)}",
); );
final usedTagsCacheFile = File( final usedTagsCacheFile = File(
"${dir.path}/${_FiroCache.sparkUsedTagsCacheFileName}", "${dir.path}/${_FiroCache.sparkUsedTagsCacheFileName(network)}",
); );
final int bytes = final int bytes =
((await setCacheFile.exists()) ? await setCacheFile.length() : 0) + ((await setCacheFile.exists()) ? await setCacheFile.length() : 0) +
@ -51,13 +53,14 @@ abstract class FiroCacheCoordinator {
static Future<void> runFetchAndUpdateSparkUsedCoinTags( static Future<void> runFetchAndUpdateSparkUsedCoinTags(
ElectrumXClient client, ElectrumXClient client,
CryptoCurrencyNetwork network,
) async { ) async {
final count = await FiroCacheCoordinator.getUsedCoinTagsCount(); final count = await FiroCacheCoordinator.getUsedCoinTagsCount(network);
final unhashedTags = await client.getSparkUnhashedUsedCoinsTagsWithTxHashes( final unhashedTags = await client.getSparkUnhashedUsedCoinsTagsWithTxHashes(
startNumber: count, startNumber: count,
); );
if (unhashedTags.isNotEmpty) { if (unhashedTags.isNotEmpty) {
await _worker!.runTask( await _workers[network]!.runTask(
FCTask( FCTask(
func: FCFuncName._updateSparkUsedTagsWith, func: FCFuncName._updateSparkUsedTagsWith,
data: unhashedTags, data: unhashedTags,
@ -69,10 +72,12 @@ abstract class FiroCacheCoordinator {
static Future<void> runFetchAndUpdateSparkAnonSetCacheForGroupId( static Future<void> runFetchAndUpdateSparkAnonSetCacheForGroupId(
int groupId, int groupId,
ElectrumXClient client, ElectrumXClient client,
CryptoCurrencyNetwork network,
) async { ) async {
final blockhashResult = final blockhashResult =
await FiroCacheCoordinator.getLatestSetInfoForGroupId( await FiroCacheCoordinator.getLatestSetInfoForGroupId(
groupId, groupId,
network,
); );
final blockHash = blockhashResult?.blockHash ?? ""; final blockHash = blockhashResult?.blockHash ?? "";
@ -81,7 +86,7 @@ abstract class FiroCacheCoordinator {
startBlockHash: blockHash.toHexReversedFromBase64, startBlockHash: blockHash.toHexReversedFromBase64,
); );
await _worker!.runTask( await _workers[network]!.runTask(
FCTask( FCTask(
func: FCFuncName._updateSparkAnonSetCoinsWith, func: FCFuncName._updateSparkAnonSetCoinsWith,
data: (groupId, json), data: (groupId, json),
@ -91,17 +96,22 @@ abstract class FiroCacheCoordinator {
// =========================================================================== // ===========================================================================
static Future<Set<String>> getUsedCoinTags(int startNumber) async { static Future<Set<String>> getUsedCoinTags(
int startNumber,
CryptoCurrencyNetwork network,
) async {
final result = await _Reader._getSparkUsedCoinTags( final result = await _Reader._getSparkUsedCoinTags(
startNumber, startNumber,
db: _FiroCache.usedTagsCacheDB, db: _FiroCache.usedTagsCacheDB(network),
); );
return result.map((e) => e["tag"] as String).toSet(); return result.map((e) => e["tag"] as String).toSet();
} }
static Future<int> getUsedCoinTagsCount() async { static Future<int> getUsedCoinTagsCount(
CryptoCurrencyNetwork network,
) async {
final result = await _Reader._getUsedCoinTagsCount( final result = await _Reader._getUsedCoinTagsCount(
db: _FiroCache.usedTagsCacheDB, db: _FiroCache.usedTagsCacheDB(network),
); );
if (result.isEmpty) { if (result.isEmpty) {
return 0; return 0;
@ -111,13 +121,14 @@ abstract class FiroCacheCoordinator {
static Future<List<LTagPair>> getUsedCoinTxidsFor({ static Future<List<LTagPair>> getUsedCoinTxidsFor({
required List<String> tags, required List<String> tags,
required CryptoCurrencyNetwork network,
}) async { }) async {
if (tags.isEmpty) { if (tags.isEmpty) {
return []; return [];
} }
final result = await _Reader._getUsedCoinTxidsFor( final result = await _Reader._getUsedCoinTxidsFor(
tags, tags,
db: _FiroCache.usedTagsCacheDB, db: _FiroCache.usedTagsCacheDB(network),
); );
if (result.isEmpty) { if (result.isEmpty) {
@ -135,20 +146,22 @@ abstract class FiroCacheCoordinator {
static Future<Set<String>> getUsedCoinTagsFor({ static Future<Set<String>> getUsedCoinTagsFor({
required String txid, required String txid,
required CryptoCurrencyNetwork network,
}) async { }) async {
final result = await _Reader._getUsedCoinTagsFor( final result = await _Reader._getUsedCoinTagsFor(
txid, txid,
db: _FiroCache.usedTagsCacheDB, db: _FiroCache.usedTagsCacheDB(network),
); );
return result.map((e) => e["tag"] as String).toSet(); return result.map((e) => e["tag"] as String).toSet();
} }
static Future<bool> checkTagIsUsed( static Future<bool> checkTagIsUsed(
String tag, String tag,
CryptoCurrencyNetwork network,
) async { ) async {
return await _Reader._checkTagIsUsed( return await _Reader._checkTagIsUsed(
tag, tag,
db: _FiroCache.usedTagsCacheDB, db: _FiroCache.usedTagsCacheDB(network),
); );
} }
@ -161,10 +174,11 @@ abstract class FiroCacheCoordinator {
})>> getSetCoinsForGroupId( })>> getSetCoinsForGroupId(
int groupId, { int groupId, {
int? newerThanTimeStamp, int? newerThanTimeStamp,
required CryptoCurrencyNetwork network,
}) async { }) async {
final resultSet = await _Reader._getSetCoinsForGroupId( final resultSet = await _Reader._getSetCoinsForGroupId(
groupId, groupId,
db: _FiroCache.setCacheDB, db: _FiroCache.setCacheDB(network),
newerThanTimeStamp: newerThanTimeStamp, newerThanTimeStamp: newerThanTimeStamp,
); );
return resultSet return resultSet
@ -187,10 +201,11 @@ abstract class FiroCacheCoordinator {
int timestampUTC, int timestampUTC,
})?> getLatestSetInfoForGroupId( })?> getLatestSetInfoForGroupId(
int groupId, int groupId,
CryptoCurrencyNetwork network,
) async { ) async {
final result = await _Reader._getLatestSetInfoForGroupId( final result = await _Reader._getLatestSetInfoForGroupId(
groupId, groupId,
db: _FiroCache.setCacheDB, db: _FiroCache.setCacheDB(network),
); );
if (result.isEmpty) { if (result.isEmpty) {
@ -206,10 +221,11 @@ abstract class FiroCacheCoordinator {
static Future<bool> checkSetInfoForGroupIdExists( static Future<bool> checkSetInfoForGroupIdExists(
int groupId, int groupId,
CryptoCurrencyNetwork network,
) async { ) async {
return await _Reader._checkSetInfoForGroupIdExists( return await _Reader._checkSetInfoForGroupIdExists(
groupId, groupId,
db: _FiroCache.setCacheDB, db: _FiroCache.setCacheDB(network),
); );
} }
} }

View file

@ -25,11 +25,12 @@ class _FiroCacheWorker {
return await completer.future; return await completer.future;
} }
static Future<_FiroCacheWorker> spawn() async { static Future<_FiroCacheWorker> spawn(CryptoCurrencyNetwork network) async {
final dir = await StackFileSystem.applicationFiroCacheSQLiteDirectory(); final dir = await StackFileSystem.applicationFiroCacheSQLiteDirectory();
final setCacheFilePath = "${dir.path}/${_FiroCache.sparkSetCacheFileName}"; final setCacheFilePath =
"${dir.path}/${_FiroCache.sparkSetCacheFileName(network)}";
final usedTagsCacheFilePath = final usedTagsCacheFilePath =
"${dir.path}/${_FiroCache.sparkUsedTagsCacheFileName}"; "${dir.path}/${_FiroCache.sparkUsedTagsCacheFileName(network)}";
final initPort = RawReceivePort(); final initPort = RawReceivePort();
final connection = Completer<(ReceivePort, SendPort)>.sync(); final connection = Completer<(ReceivePort, SendPort)>.sync();

View file

@ -1043,7 +1043,7 @@ class ElectrumXClient {
final start = DateTime.now(); final start = DateTime.now();
final response = await request( final response = await request(
requestID: requestID, requestID: requestID,
command: "spark.getmempooltxids", command: "spark.getmempoolsparktxids",
); );
final txids = List<String>.from(response as List) final txids = List<String>.from(response as List)
@ -1072,7 +1072,7 @@ class ElectrumXClient {
final start = DateTime.now(); final start = DateTime.now();
final response = await request( final response = await request(
requestID: requestID, requestID: requestID,
command: "spark.getmempooltxs", command: "spark.getmempoolsparktxs",
args: [ args: [
{ {
"txids": txids, "txids": txids,
@ -1087,10 +1087,10 @@ class ElectrumXClient {
( (
txid: entry.key, txid: entry.key,
serialContext: serialContext:
List<String>.from(entry.value["Serial_context"] as List), List<String>.from(entry.value["serial_context"] as List),
// the space after lTags is required lol // the space after lTags is required lol
lTags: List<String>.from(entry.value["lTags "] as List), lTags: List<String>.from(entry.value["lTags "] as List),
coins: List<String>.from(entry.value["Coins"] as List), coins: List<String>.from(entry.value["coins"] as List),
), ),
); );
} }
@ -1142,6 +1142,38 @@ class ElectrumXClient {
} }
// =========================================================================== // ===========================================================================
Future<bool> 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. /// Get the current fee rate.
/// ///
/// Returns a map with the kay "rate" that corresponds to the free rate in satoshis /// Returns a map with the kay "rate" that corresponds to the free rate in satoshis

View file

@ -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<coinlib.ScriptOp> 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));
}
}

View file

@ -1,9 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:qr_flutter/qr_flutter.dart';
import '../../../../../frost_route_generator.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/frost_wallet/frost_wallet_providers.dart';
import '../../../../../services/frost.dart'; import '../../../../../services/frost.dart';
import '../../../../../themes/stack_colors.dart'; import '../../../../../themes/stack_colors.dart';
@ -17,6 +16,8 @@ import '../../../../../widgets/desktop/secondary_button.dart';
import '../../../../../widgets/detail_item.dart'; import '../../../../../widgets/detail_item.dart';
import '../../../../../widgets/dialogs/simple_mobile_dialog.dart'; import '../../../../../widgets/dialogs/simple_mobile_dialog.dart';
import '../../../../../widgets/frost_step_user_steps.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 { class FrostCreateStep1a extends ConsumerStatefulWidget {
const FrostCreateStep1a({super.key}); const FrostCreateStep1a({super.key});
@ -162,14 +163,9 @@ class _FrostCreateStep1aState extends ConsumerState<FrostCreateStep1a> {
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
QrImageView( QR(
data: ref.watch(pFrostMultisigConfig.state).state ?? "Error", data: ref.watch(pFrostMultisigConfig.state).state ?? "Error",
size: 220, size: 220,
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
), ),
], ],
), ),

View file

@ -1,7 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:qr_flutter/qr_flutter.dart';
import '../../../../frost_route_generator.dart'; import '../../../../frost_route_generator.dart';
import '../../../../providers/db/main_db_provider.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/frost/frost_error_dialog.dart';
import '../../../../widgets/dialogs/simple_mobile_dialog.dart'; import '../../../../widgets/dialogs/simple_mobile_dialog.dart';
import '../../../../widgets/frost_step_user_steps.dart'; import '../../../../widgets/frost_step_user_steps.dart';
import '../../../../widgets/qr.dart';
import '../../../wallet_view/transaction_views/transaction_details_view.dart'; import '../../../wallet_view/transaction_views/transaction_details_view.dart';
class FrostReshareStep1a extends ConsumerStatefulWidget { class FrostReshareStep1a extends ConsumerStatefulWidget {
@ -239,14 +239,9 @@ class _FrostReshareStep1aState extends ConsumerState<FrostReshareStep1a> {
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
QrImageView( QR(
data: ref.watch(pFrostResharingData).resharerRConfig!, data: ref.watch(pFrostResharingData).resharerRConfig!,
size: 220, size: 220,
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
), ),
], ],
), ),

View file

@ -14,7 +14,6 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
import '../../../app_config.dart'; import '../../../app_config.dart';
@ -37,6 +36,7 @@ import '../../../wallets/wallet/impl/firo_wallet.dart';
import '../../../widgets/background.dart'; import '../../../widgets/background.dart';
import '../../../widgets/custom_buttons/app_bar_icon_button.dart'; import '../../../widgets/custom_buttons/app_bar_icon_button.dart';
import '../../../widgets/desktop/secondary_button.dart'; import '../../../widgets/desktop/secondary_button.dart';
import '../../../widgets/qr.dart';
import '../../../widgets/rounded_container.dart'; import '../../../widgets/rounded_container.dart';
import '../../../widgets/rounded_white_container.dart'; import '../../../widgets/rounded_white_container.dart';
import '../../../widgets/stack_dialog.dart'; import '../../../widgets/stack_dialog.dart';
@ -751,7 +751,7 @@ class _Step4ViewState extends ConsumerState<Step4View> {
height: 24, height: 24,
), ),
Center( Center(
child: QrImageView( child: QR(
// TODO: grab coin uri scheme from somewhere // TODO: grab coin uri scheme from somewhere
// data: "${coin.uriScheme}:$receivingAddress", // data: "${coin.uriScheme}:$receivingAddress",
data: model.trade!.payInAddress, data: model.trade!.payInAddress,
@ -759,9 +759,6 @@ class _Step4ViewState extends ConsumerState<Step4View> {
.size .size
.width / .width /
2, 2,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
), ),
), ),
const SizedBox( const SizedBox(

View file

@ -16,7 +16,6 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
import 'package:url_launcher/url_launcher.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/custom_buttons/blue_text_button.dart';
import '../../widgets/desktop/desktop_dialog.dart'; import '../../widgets/desktop/desktop_dialog.dart';
import '../../widgets/desktop/secondary_button.dart'; import '../../widgets/desktop/secondary_button.dart';
import '../../widgets/qr.dart';
import '../../widgets/rounded_container.dart'; import '../../widgets/rounded_container.dart';
import '../../widgets/rounded_white_container.dart'; import '../../widgets/rounded_white_container.dart';
import '../../widgets/stack_dialog.dart'; import '../../widgets/stack_dialog.dart';
@ -808,15 +808,9 @@ class _TradeDetailsViewState extends ConsumerState<TradeDetailsView> {
child: SizedBox( child: SizedBox(
width: width + 20, width: width + 20,
height: width + 20, height: width + 20,
child: QrImageView( child: QR(
data: trade.payInAddress, data: trade.payInAddress,
size: width, size: width,
backgroundColor: Theme.of(context)
.extension<StackColors>()!
.popupBG,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
), ),
), ),
), ),

View file

@ -14,15 +14,11 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.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 '../../../exceptions/wallet/insufficient_balance_exception.dart';
import '../../../models/paynym/paynym_account_lite.dart'; import '../../../models/paynym/paynym_account_lite.dart';
import '../../../notifications/show_flush_bar.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/locale_provider.dart';
import '../../../providers/global/wallets_provider.dart'; import '../../../providers/global/wallets_provider.dart';
import '../../../route_generator.dart'; import '../../../route_generator.dart';
@ -37,9 +33,14 @@ import '../../../widgets/desktop/desktop_dialog.dart';
import '../../../widgets/desktop/primary_button.dart'; import '../../../widgets/desktop/primary_button.dart';
import '../../../widgets/desktop/secondary_button.dart'; import '../../../widgets/desktop/secondary_button.dart';
import '../../../widgets/loading_indicator.dart'; import '../../../widgets/loading_indicator.dart';
import '../../../widgets/qr.dart';
import '../../../widgets/rounded_container.dart'; import '../../../widgets/rounded_container.dart';
import '../../../widgets/stack_dialog.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 { class PaynymDetailsPopup extends ConsumerStatefulWidget {
const PaynymDetailsPopup({ const PaynymDetailsPopup({
@ -365,12 +366,10 @@ class _PaynymDetailsPopupState extends ConsumerState<PaynymDetailsPopup> {
const SizedBox( const SizedBox(
width: 20, width: 20,
), ),
QrImageView( QR(
padding: const EdgeInsets.all(0), padding: const EdgeInsets.all(0),
size: 100, size: 100,
data: widget.accountLite.code, data: widget.accountLite.code,
foregroundColor:
Theme.of(context).extension<StackColors>()!.textDark,
), ),
], ],
), ),

View file

@ -12,10 +12,9 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:qr_flutter/qr_flutter.dart';
import '../../../models/paynym/paynym_account.dart'; import '../../../models/paynym/paynym_account.dart';
import '../../../notifications/show_flush_bar.dart'; import '../../../notifications/show_flush_bar.dart';
import '../subwidgets/paynym_bot.dart';
import '../../../themes/stack_colors.dart'; import '../../../themes/stack_colors.dart';
import '../../../utilities/assets.dart'; import '../../../utilities/assets.dart';
import '../../../utilities/text_styles.dart'; import '../../../utilities/text_styles.dart';
@ -23,6 +22,8 @@ import '../../../utilities/util.dart';
import '../../../widgets/custom_buttons/blue_text_button.dart'; import '../../../widgets/custom_buttons/blue_text_button.dart';
import '../../../widgets/desktop/desktop_dialog.dart'; import '../../../widgets/desktop/desktop_dialog.dart';
import '../../../widgets/desktop/desktop_dialog_close_button.dart'; import '../../../widgets/desktop/desktop_dialog_close_button.dart';
import '../../../widgets/qr.dart';
import '../subwidgets/paynym_bot.dart';
class PaynymQrPopup extends StatelessWidget { class PaynymQrPopup extends StatelessWidget {
const PaynymQrPopup({ const PaynymQrPopup({
@ -157,12 +158,10 @@ class PaynymQrPopup extends StatelessWidget {
const SizedBox( const SizedBox(
width: 20, width: 20,
), ),
QrImageView( QR(
padding: const EdgeInsets.all(0), padding: const EdgeInsets.all(0),
size: 130, size: 130,
data: paynymAccount.nonSegwitPaymentCode.code, data: paynymAccount.nonSegwitPaymentCode.code,
foregroundColor:
Theme.of(context).extension<StackColors>()!.textDark,
), ),
], ],
), ),

View file

@ -14,19 +14,15 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:qr_flutter/qr_flutter.dart';
import '../../../exceptions/wallet/insufficient_balance_exception.dart'; import '../../../exceptions/wallet/insufficient_balance_exception.dart';
import '../../../models/paynym/paynym_account_lite.dart'; import '../../../models/paynym/paynym_account_lite.dart';
import '../../../notifications/show_flush_bar.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 '../../../pages_desktop_specific/my_stack_view/paynym/desktop_paynym_send_dialog.dart';
import '../../../providers/global/locale_provider.dart'; import '../../../providers/global/locale_provider.dart';
import '../../../providers/global/wallets_provider.dart'; import '../../../providers/global/wallets_provider.dart';
import '../../../themes/stack_colors.dart'; import '../../../themes/stack_colors.dart';
import '../../../utilities/assets.dart'; import '../../../utilities/assets.dart';
import '../../../utilities/text_styles.dart'; import '../../../utilities/text_styles.dart';
import '../../../wallets/isar/providers/wallet_info_provider.dart'; import '../../../wallets/isar/providers/wallet_info_provider.dart';
import '../../../wallets/models/tx_data.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/desktop_dialog.dart';
import '../../../widgets/desktop/primary_button.dart'; import '../../../widgets/desktop/primary_button.dart';
import '../../../widgets/loading_indicator.dart'; import '../../../widgets/loading_indicator.dart';
import '../../../widgets/qr.dart';
import '../../../widgets/rounded_container.dart'; import '../../../widgets/rounded_container.dart';
import '../../../widgets/rounded_white_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 { class DesktopPaynymDetails extends ConsumerStatefulWidget {
const DesktopPaynymDetails({ const DesktopPaynymDetails({
@ -359,12 +359,10 @@ class _PaynymDetailsPopupState extends ConsumerState<DesktopPaynymDetails> {
const SizedBox( const SizedBox(
width: 20, width: 20,
), ),
QrImageView( QR(
padding: const EdgeInsets.all(0), padding: const EdgeInsets.all(0),
size: 100, size: 100,
data: widget.accountLite.code, data: widget.accountLite.code,
foregroundColor:
Theme.of(context).extension<StackColors>()!.textDark,
), ),
], ],
), ),

View file

@ -188,12 +188,14 @@ class _LockscreenViewState extends ConsumerState<LockscreenView> {
_timeout = Duration.zero; _timeout = Duration.zero;
_checkUseBiometrics(); _checkUseBiometrics();
_pinTextController.addListener(_onPinChanged);
super.initState(); super.initState();
} }
@override @override
dispose() { dispose() {
// _shakeController.dispose(); // _shakeController.dispose();
_pinTextController.removeListener(_onPinChanged);
super.dispose(); super.dispose();
} }
@ -208,13 +210,27 @@ class _LockscreenViewState extends ConsumerState<LockscreenView> {
); );
} }
final _pinTextController = TextEditingController();
final FocusNode _pinFocusNode = FocusNode(); final FocusNode _pinFocusNode = FocusNode();
late SecureStorageInterface _secureStore; late SecureStorageInterface _secureStore;
late Biometrics biometrics; late Biometrics biometrics;
int pinCount = 1; int pinCount = 1;
final _pinTextController = TextEditingController();
void _onPinChanged() async {
String enteredPin = _pinTextController.text;
final storedPin = await _secureStore.read(key: 'stack_pin');
final autoPin = ref.read(prefsChangeNotifierProvider).autoPin;
if (enteredPin.length >= 4 && autoPin && enteredPin == storedPin) {
await Future<void>.delayed(
const Duration(milliseconds: 200),
);
unawaited(_onUnlock());
}
}
Widget get _body => Background( Widget get _body => Background(
child: SafeArea( child: SafeArea(
child: Scaffold( child: Scaffold(

View file

@ -20,7 +20,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:isar/isar.dart'; import 'package:isar/isar.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:share_plus/share_plus.dart'; import 'package:share_plus/share_plus.dart';
import '../../../db/isar/main_db.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/custom_buttons/simple_edit_button.dart';
import '../../../widgets/desktop/primary_button.dart'; import '../../../widgets/desktop/primary_button.dart';
import '../../../widgets/desktop/secondary_button.dart'; import '../../../widgets/desktop/secondary_button.dart';
import '../../../widgets/qr.dart';
import '../../../widgets/rounded_white_container.dart'; import '../../../widgets/rounded_white_container.dart';
import '../../../widgets/stack_dialog.dart'; import '../../../widgets/stack_dialog.dart';
@ -302,19 +302,13 @@ class _AddressCardState extends ConsumerState<AddressCard> {
Center( Center(
child: RepaintBoundary( child: RepaintBoundary(
key: _qrKey, key: _qrKey,
child: QrImageView( child: QR(
data: AddressUtils.buildUriString( data: AddressUtils.buildUriString(
widget.coin, widget.coin,
address.value, address.value,
{}, {},
), ),
size: 220, size: 220,
backgroundColor: Theme.of(context)
.extension<StackColors>()!
.popupBG,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
), ),
), ),
), ),

View file

@ -11,7 +11,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:isar/isar.dart'; import 'package:isar/isar.dart';
import 'package:qr_flutter/qr_flutter.dart';
import '../../../db/isar/main_db.dart'; import '../../../db/isar/main_db.dart';
import '../../../models/isar/models/blockchain_data/v2/transaction_v2.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/custom_buttons/simple_edit_button.dart';
import '../../../widgets/desktop/desktop_dialog.dart'; import '../../../widgets/desktop/desktop_dialog.dart';
import '../../../widgets/desktop/desktop_dialog_close_button.dart'; import '../../../widgets/desktop/desktop_dialog_close_button.dart';
import '../../../widgets/qr.dart';
import '../../../widgets/rounded_white_container.dart'; import '../../../widgets/rounded_white_container.dart';
import '../../../widgets/transaction_card.dart'; import '../../../widgets/transaction_card.dart';
import '../../wallet_view/sub_widgets/no_transactions_found.dart'; import '../../wallet_view/sub_widgets/no_transactions_found.dart';
@ -92,18 +92,13 @@ class _AddressDetailsViewState extends ConsumerState<AddressDetailsView> {
Center( Center(
child: RepaintBoundary( child: RepaintBoundary(
key: _qrKey, key: _qrKey,
child: QrImageView( child: QR(
data: AddressUtils.buildUriString( data: AddressUtils.buildUriString(
ref.watch(pWalletCoin(widget.walletId)), ref.watch(pWalletCoin(widget.walletId)),
address.value, address.value,
{}, {},
), ),
size: 220, size: 220,
backgroundColor:
Theme.of(context).extension<StackColors>()!.popupBG,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
), ),
), ),
), ),
@ -289,19 +284,13 @@ class _AddressDetailsViewState extends ConsumerState<AddressDetailsView> {
Center( Center(
child: RepaintBoundary( child: RepaintBoundary(
key: _qrKey, key: _qrKey,
child: QrImageView( child: QR(
data: AddressUtils.buildUriString( data: AddressUtils.buildUriString(
coin, coin,
address.value, address.value,
{}, {},
), ),
size: 220, size: 220,
backgroundColor: Theme.of(context)
.extension<StackColors>()!
.background,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
), ),
), ),
), ),

View file

@ -18,7 +18,6 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:share_plus/share_plus.dart'; import 'package:share_plus/share_plus.dart';
import '../../../notifications/show_flush_bar.dart'; import '../../../notifications/show_flush_bar.dart';
@ -31,6 +30,7 @@ import '../../../utilities/util.dart';
import '../../../wallets/crypto_currency/crypto_currency.dart'; import '../../../wallets/crypto_currency/crypto_currency.dart';
import '../../../widgets/desktop/primary_button.dart'; import '../../../widgets/desktop/primary_button.dart';
import '../../../widgets/desktop/secondary_button.dart'; import '../../../widgets/desktop/secondary_button.dart';
import '../../../widgets/qr.dart';
import '../../../widgets/stack_dialog.dart'; import '../../../widgets/stack_dialog.dart';
class AddressQrPopup extends StatefulWidget { class AddressQrPopup extends StatefulWidget {
@ -140,17 +140,13 @@ class _AddressQrPopupState extends State<AddressQrPopup> {
Center( Center(
child: RepaintBoundary( child: RepaintBoundary(
key: _qrKey, key: _qrKey,
child: QrImageView( child: QR(
data: AddressUtils.buildUriString( data: AddressUtils.buildUriString(
widget.coin, widget.coin,
widget.addressString, widget.addressString,
{}, {},
), ),
size: 220, size: 220,
backgroundColor:
Theme.of(context).extension<StackColors>()!.popupBG,
foregroundColor:
Theme.of(context).extension<StackColors>()!.accentColorDark,
), ),
), ),
), ),

View file

@ -249,17 +249,15 @@ class _WalletAddressesViewState extends ConsumerState<WalletAddressesView> {
walletId: widget.walletId, walletId: widget.walletId,
addressId: snapshot.data![index], addressId: snapshot.data![index],
coin: coin, coin: coin,
onPressed: !isDesktop onPressed: () {
? null Navigator.of(context).pushNamed(
: () { AddressDetailsView.routeName,
Navigator.of(context).pushNamed( arguments: Tuple2(
AddressDetailsView.routeName, snapshot.data![index],
arguments: Tuple2( widget.walletId,
snapshot.data![index], ),
widget.walletId, );
), },
);
},
), ),
); );
} else { } else {

View file

@ -20,7 +20,6 @@ import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:share_plus/share_plus.dart'; import 'package:share_plus/share_plus.dart';
import '../../notifications/show_flush_bar.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/primary_button.dart';
import '../../widgets/desktop/secondary_button.dart'; import '../../widgets/desktop/secondary_button.dart';
import '../../widgets/icon_widgets/x_icon.dart'; import '../../widgets/icon_widgets/x_icon.dart';
import '../../widgets/qr.dart';
import '../../widgets/rounded_white_container.dart'; import '../../widgets/rounded_white_container.dart';
import '../../widgets/stack_dialog.dart'; import '../../widgets/stack_dialog.dart';
import '../../widgets/stack_text_field.dart'; import '../../widgets/stack_text_field.dart';
@ -215,14 +215,9 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
child: SizedBox( child: SizedBox(
width: width + 20, width: width + 20,
height: width + 20, height: width + 20,
child: QrImageView( child: QR(
data: uriString, data: uriString,
size: width, size: width,
backgroundColor:
Theme.of(context).extension<StackColors>()!.popupBG,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
), ),
), ),
), ),
@ -556,15 +551,9 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
child: SizedBox( child: SizedBox(
width: 234, width: 234,
height: 234, height: 234,
child: QrImageView( child: QR(
data: _uriString, data: _uriString,
size: 220, size: 220,
backgroundColor: Theme.of(context)
.extension<StackColors>()!
.popupBG,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
), ),
), ),
), ),

View file

@ -16,7 +16,6 @@ import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:isar/isar.dart'; import 'package:isar/isar.dart';
import 'package:qr_flutter/qr_flutter.dart';
import '../../models/isar/models/isar_models.dart'; import '../../models/isar/models/isar_models.dart';
import '../../notifications/show_flush_bar.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/custom_loading_overlay.dart';
import '../../widgets/desktop/primary_button.dart'; import '../../widgets/desktop/primary_button.dart';
import '../../widgets/desktop/secondary_button.dart'; import '../../widgets/desktop/secondary_button.dart';
import '../../widgets/qr.dart';
import '../../widgets/rounded_white_container.dart'; import '../../widgets/rounded_white_container.dart';
import 'addresses/wallet_addresses_view.dart'; import 'addresses/wallet_addresses_view.dart';
import 'generate_receiving_uri_qr_code_view.dart'; import 'generate_receiving_uri_qr_code_view.dart';
@ -575,16 +575,13 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
child: Center( child: Center(
child: Column( child: Column(
children: [ children: [
QrImageView( QR(
data: AddressUtils.buildUriString( data: AddressUtils.buildUriString(
coin, coin,
address, address,
{}, {},
), ),
size: MediaQuery.of(context).size.width / 2, size: MediaQuery.of(context).size.width / 2,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
), ),
const SizedBox( const SizedBox(
height: 20, height: 20,

View file

@ -1,8 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:qr_flutter/qr_flutter.dart';
import '../../../../frost_route_generator.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/frost_wallet/frost_wallet_providers.dart';
import '../../../../providers/global/wallets_provider.dart'; import '../../../../providers/global/wallets_provider.dart';
import '../../../../themes/stack_colors.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/custom_buttons/simple_copy_button.dart';
import '../../../../widgets/desktop/primary_button.dart'; import '../../../../widgets/desktop/primary_button.dart';
import '../../../../widgets/detail_item.dart'; import '../../../../widgets/detail_item.dart';
import '../../../../widgets/qr.dart';
import '../../../../widgets/rounded_white_container.dart'; import '../../../../widgets/rounded_white_container.dart';
import '../../../wallet_view/transaction_views/transaction_details_view.dart';
class FrostSendStep1a extends ConsumerStatefulWidget { class FrostSendStep1a extends ConsumerStatefulWidget {
const FrostSendStep1a({super.key}); const FrostSendStep1a({super.key});
@ -169,14 +170,9 @@ class _FrostSendStep1aState extends ConsumerState<FrostSendStep1a> {
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
QrImageView( QR(
data: ref.watch(pFrostTxData.state).state!.frostMSConfig!, data: ref.watch(pFrostTxData.state).state!.frostMSConfig!,
size: qrImageSize, size: qrImageSize,
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
), ),
], ],
), ),

View file

@ -56,6 +56,7 @@ import '../../widgets/animated_text.dart';
import '../../widgets/background.dart'; import '../../widgets/background.dart';
import '../../widgets/custom_buttons/app_bar_icon_button.dart'; import '../../widgets/custom_buttons/app_bar_icon_button.dart';
import '../../widgets/custom_buttons/blue_text_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/fee_slider.dart';
import '../../widgets/icon_widgets/addressbook_icon.dart'; import '../../widgets/icon_widgets/addressbook_icon.dart';
import '../../widgets/icon_widgets/clipboard_icon.dart'; import '../../widgets/icon_widgets/clipboard_icon.dart';
@ -127,6 +128,8 @@ class _SendViewState extends ConsumerState<SendView> {
bool _addressToggleFlag = false; bool _addressToggleFlag = false;
bool _isFiroExWarningDisplayed = false;
bool _cryptoAmountChangeLock = false; bool _cryptoAmountChangeLock = false;
late VoidCallback onCryptoAmountChanged; late VoidCallback onCryptoAmountChanged;
@ -394,6 +397,19 @@ class _SendViewState extends ConsumerState<SendView> {
address: address ?? "", address: address ?? "",
isTestNet: wallet.cryptoCurrency.network.isTestNet, 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 = ref.read(pValidSendToAddress.notifier).state =
@ -875,7 +891,10 @@ class _SendViewState extends ConsumerState<SendView> {
@override @override
void initState() { void initState() {
coin = widget.coin; coin = widget.coin;
ref.refresh(feeSheetSessionCacheProvider); WidgetsBinding.instance.addPostFrameCallback((_) {
ref.refresh(feeSheetSessionCacheProvider);
ref.refresh(pIsExchangeAddress);
});
_currentFee = 0.toAmountAsRaw(fractionDigits: coin.fractionDigits); _currentFee = 0.toAmountAsRaw(fractionDigits: coin.fractionDigits);
_calculateFeesFuture = _calculateFeesFuture =
@ -1003,6 +1022,8 @@ class _SendViewState extends ConsumerState<SendView> {
: true); : true);
if (isFiro) { if (isFiro) {
final isExchangeAddress = ref.watch(pIsExchangeAddress);
ref.listen(publicPrivateBalanceStateProvider, (previous, next) { ref.listen(publicPrivateBalanceStateProvider, (previous, next) {
selectedUTXOs = {}; selectedUTXOs = {};
@ -1019,6 +1040,19 @@ class _SendViewState extends ConsumerState<SendView> {
); );
}); });
} }
if (previous != next &&
next == FiroType.spark &&
isExchangeAddress &&
!_isFiroExWarningDisplayed) {
_isFiroExWarningDisplayed = true;
WidgetsBinding.instance.addPostFrameCallback(
(_) => showFiroExchangeAddressWarning(
context,
() => _isFiroExWarningDisplayed = false,
),
);
}
}); });
} }

View file

@ -61,9 +61,12 @@ class _ChangePinViewState extends ConsumerState<ChangePinView> {
int pinCount = 1; int pinCount = 1;
final TextEditingController _pinTextController = TextEditingController();
@override @override
void initState() { void initState() {
_secureStore = ref.read(secureStoreProvider); _secureStore = ref.read(secureStoreProvider);
_pinTextController.addListener(_onPinChanged);
super.initState(); super.initState();
} }
@ -74,9 +77,23 @@ class _ChangePinViewState extends ConsumerState<ChangePinView> {
_pinPutController2.dispose(); _pinPutController2.dispose();
_pinPutFocusNode1.dispose(); _pinPutFocusNode1.dispose();
_pinPutFocusNode2.dispose(); _pinPutFocusNode2.dispose();
_pinTextController.removeListener(_onPinChanged);
super.dispose(); super.dispose();
} }
void _onPinChanged() async {
String enteredPin = _pinTextController.text;
final storedPin = await _secureStore.read(key: 'stack_pin');
final autoPin = ref.read(prefsChangeNotifierProvider).autoPin;
if (enteredPin.length >= 4 && autoPin && enteredPin == storedPin) {
await _pageController.nextPage(
duration: const Duration(milliseconds: 300),
curve: Curves.linear,
);
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Background( return Background(

View file

@ -10,8 +10,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../pinpad_views/lock_screen_view.dart';
import 'change_pin_view/change_pin_view.dart';
import '../../../../providers/global/prefs_provider.dart'; import '../../../../providers/global/prefs_provider.dart';
import '../../../../route_generator.dart'; import '../../../../route_generator.dart';
import '../../../../themes/stack_colors.dart'; import '../../../../themes/stack_colors.dart';
@ -21,6 +20,8 @@ import '../../../../widgets/background.dart';
import '../../../../widgets/custom_buttons/app_bar_icon_button.dart'; import '../../../../widgets/custom_buttons/app_bar_icon_button.dart';
import '../../../../widgets/custom_buttons/draggable_switch_button.dart'; import '../../../../widgets/custom_buttons/draggable_switch_button.dart';
import '../../../../widgets/rounded_white_container.dart'; import '../../../../widgets/rounded_white_container.dart';
import '../../../pinpad_views/lock_screen_view.dart';
import 'change_pin_view/change_pin_view.dart';
class SecurityView extends StatelessWidget { class SecurityView extends StatelessWidget {
const SecurityView({ const SecurityView({
@ -203,6 +204,54 @@ class SecurityView extends StatelessWidget {
}, },
), ),
), ),
// The "autoPin" preference (whether to automatically accept a correct PIN).
const SizedBox(
height: 8,
),
RoundedWhiteContainer(
child: Consumer(
builder: (_, ref, __) {
return RawMaterialButton(
// splashColor: Theme.of(context).extension<StackColors>()!.highlight,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
onPressed: null,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Auto-accept correct PIN",
style: STextStyles.titleBold12(context),
textAlign: TextAlign.left,
),
SizedBox(
height: 20,
width: 40,
child: DraggableSwitchButton(
isOn: ref.watch(
prefsChangeNotifierProvider
.select((value) => value.autoPin),
),
onValueChanged: (newValue) {
ref
.read(prefsChangeNotifierProvider)
.autoPin = newValue;
},
),
),
],
),
),
);
},
),
),
], ],
), ),
), ),

View file

@ -14,7 +14,6 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:qr_flutter/qr_flutter.dart';
import '../../../../app_config.dart'; import '../../../../app_config.dart';
import '../../../../notifications/show_flush_bar.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/app_bar_icon_button.dart';
import '../../../../widgets/custom_buttons/simple_copy_button.dart'; import '../../../../widgets/custom_buttons/simple_copy_button.dart';
import '../../../../widgets/detail_item.dart'; import '../../../../widgets/detail_item.dart';
import '../../../../widgets/qr.dart';
import '../../../../widgets/rounded_white_container.dart'; import '../../../../widgets/rounded_white_container.dart';
import '../../../../widgets/stack_dialog.dart'; import '../../../../widgets/stack_dialog.dart';
import '../../../add_wallet_views/new_wallet_recovery_phrase_view/sub_widgets/mnemonic_table.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( child: SizedBox(
width: width + 20, width: width + 20,
height: width + 20, height: width + 20,
child: QrImageView( child: QR(
data: data, data: data,
size: width, size: width,
backgroundColor: Theme.of(context)
.extension<StackColors>()!
.popupBG,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
), ),
), ),
), ),

View file

@ -759,6 +759,32 @@ class _WalletNetworkSettingsViewState
), ),
), ),
), ),
SizedBox(
height: isDesktop ? 12 : 9,
),
RoundedWhiteContainer(
borderColor: isDesktop
? Theme.of(context).extension<StackColors>()!.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( SizedBox(
height: isDesktop ? 32 : 20, height: isDesktop ? 32 : 20,
), ),

View file

@ -430,7 +430,9 @@ class _WalletSettingsViewState extends ConsumerState<WalletSettingsView> {
), ),
if (coin is Firo) if (coin is Firo)
FiroCacheCoordinator FiroCacheCoordinator
.clearSharedCache(), .clearSharedCache(
coin.network,
),
], ],
), ),
context: context, context: context,

View file

@ -4,6 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../db/sqlite/firo_cache.dart'; import '../../../../db/sqlite/firo_cache.dart';
import '../../../../themes/stack_colors.dart'; import '../../../../themes/stack_colors.dart';
import '../../../../utilities/text_styles.dart'; import '../../../../utilities/text_styles.dart';
import '../../../../wallets/isar/providers/wallet_info_provider.dart';
import '../../../../widgets/background.dart'; import '../../../../widgets/background.dart';
import '../../../../widgets/custom_buttons/app_bar_icon_button.dart'; import '../../../../widgets/custom_buttons/app_bar_icon_button.dart';
import '../../../../widgets/detail_item.dart'; import '../../../../widgets/detail_item.dart';
@ -11,10 +12,13 @@ import '../../../../widgets/detail_item.dart';
class SparkInfoView extends ConsumerWidget { class SparkInfoView extends ConsumerWidget {
const SparkInfoView({ const SparkInfoView({
super.key, super.key,
required this.walletId,
}); });
static const String routeName = "/sparkInfo"; static const String routeName = "/sparkInfo";
final String walletId;
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
return Background( return Background(
@ -37,7 +41,9 @@ class SparkInfoView extends ConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
FutureBuilder( FutureBuilder(
future: FiroCacheCoordinator.getSparkCacheSize(), future: FiroCacheCoordinator.getSparkCacheSize(
ref.watch(pWalletCoin(walletId)).network,
),
builder: (_, snapshot) { builder: (_, snapshot) {
String detail = "Loading..."; String detail = "Loading...";
if (snapshot.connectionState == ConnectionState.done) { if (snapshot.connectionState == ConnectionState.done) {

View file

@ -243,6 +243,7 @@ class WalletSettingsWalletSettingsView extends ConsumerWidget {
onPressed: () { onPressed: () {
Navigator.of(context).pushNamed( Navigator.of(context).pushNamed(
SparkInfoView.routeName, SparkInfoView.routeName,
arguments: walletId,
); );
}, },
child: Padding( child: Padding(

View file

@ -14,7 +14,6 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:qr_flutter/qr_flutter.dart';
import '../../../../notifications/show_flush_bar.dart'; import '../../../../notifications/show_flush_bar.dart';
import '../../../../providers/global/wallets_provider.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/primary_button.dart';
import '../../../../widgets/desktop/secondary_button.dart'; import '../../../../widgets/desktop/secondary_button.dart';
import '../../../../widgets/loading_indicator.dart'; import '../../../../widgets/loading_indicator.dart';
import '../../../../widgets/qr.dart';
import '../../../../widgets/rounded_white_container.dart'; import '../../../../widgets/rounded_white_container.dart';
class XPubView extends ConsumerStatefulWidget { class XPubView extends ConsumerStatefulWidget {
@ -256,11 +256,9 @@ class _XPub extends StatelessWidget {
builder: (child) => RoundedWhiteContainer( builder: (child) => RoundedWhiteContainer(
child: child, child: child,
), ),
child: QrImageView( child: QR(
data: xpub, data: xpub,
size: isDesktop ? 280 : MediaQuery.of(context).size.width / 1.5, size: isDesktop ? 280 : MediaQuery.of(context).size.width / 1.5,
foregroundColor:
Theme.of(context).extension<StackColors>()!.accentColorDark,
), ),
), ),
const SizedBox(height: 25), const SizedBox(height: 25),

View file

@ -13,6 +13,7 @@ import '../../../../utilities/text_styles.dart';
import '../../../../utilities/util.dart'; import '../../../../utilities/util.dart';
import '../../../../wallets/crypto_currency/crypto_currency.dart'; import '../../../../wallets/crypto_currency/crypto_currency.dart';
import '../../../../wallets/isar/providers/wallet_info_provider.dart'; import '../../../../wallets/isar/providers/wallet_info_provider.dart';
import '../../../../widgets/breathing.dart';
import '../../../../widgets/desktop/desktop_dialog.dart'; import '../../../../widgets/desktop/desktop_dialog.dart';
import '../../../../widgets/desktop/desktop_dialog_close_button.dart'; import '../../../../widgets/desktop/desktop_dialog_close_button.dart';
import '../../../../widgets/trade_card.dart'; import '../../../../widgets/trade_card.dart';
@ -49,98 +50,100 @@ class TxListItem extends ConsumerWidget {
color: Theme.of(context).extension<StackColors>()!.popupBG, color: Theme.of(context).extension<StackColors>()!.popupBG,
borderRadius: radius, borderRadius: radius,
), ),
child: Column( child: Breathing(
mainAxisSize: MainAxisSize.min, child: Column(
children: [ mainAxisSize: MainAxisSize.min,
TransactionCardV2( children: [
key: UniqueKey(), TransactionCardV2(
transaction: _tx, key: UniqueKey(),
), transaction: _tx,
TradeCard( ),
key: Key( TradeCard(
_tx.txid + key: Key(
_tx.type.name + _tx.txid +
_tx.hashCode.toString() + _tx.type.name +
trade.uuid, _tx.hashCode.toString() +
), // trade.uuid,
trade: trade, ), //
onTap: () async { trade: trade,
if (Util.isDesktop) { onTap: () async {
await showDialog<void>( if (Util.isDesktop) {
context: context, await showDialog<void>(
builder: (context) => Navigator( context: context,
initialRoute: TradeDetailsView.routeName, builder: (context) => Navigator(
onGenerateRoute: RouteGenerator.generateRoute, initialRoute: TradeDetailsView.routeName,
onGenerateInitialRoutes: (_, __) { onGenerateRoute: RouteGenerator.generateRoute,
return [ onGenerateInitialRoutes: (_, __) {
FadePageRoute( return [
DesktopDialog( FadePageRoute(
maxHeight: null, DesktopDialog(
maxWidth: 580, maxHeight: null,
child: Column( maxWidth: 580,
mainAxisSize: MainAxisSize.min, child: Column(
children: [ mainAxisSize: MainAxisSize.min,
Padding( children: [
padding: const EdgeInsets.only( Padding(
left: 32, padding: const EdgeInsets.only(
bottom: 16, 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( Flexible(
mainAxisAlignment: child: TradeDetailsView(
MainAxisAlignment.spaceBetween, tradeId: trade.tradeId,
children: [ // TODO: [prio:med]
Text( // transactionIfSentFromStack: tx,
"Trade details", transactionIfSentFromStack: null,
style: walletName: ref
STextStyles.desktopH3(context), .watch(pWalletName(_tx.walletId)),
), walletId: _tx.walletId,
DesktopDialogCloseButton( ),
onPressedOverride: Navigator.of(
context,
rootNavigator: true,
).pop,
),
],
), ),
), ],
Flexible( ),
child: TradeDetailsView( ),
tradeId: trade.tradeId, const RouteSettings(
// TODO: [prio:med] name: TradeDetailsView.routeName,
// transactionIfSentFromStack: tx,
transactionIfSentFromStack: null,
walletName: ref
.watch(pWalletName(_tx.walletId)),
walletId: _tx.walletId,
),
),
],
), ),
), ),
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 { } else {
@ -149,10 +152,12 @@ class TxListItem extends ConsumerWidget {
color: Theme.of(context).extension<StackColors>()!.popupBG, color: Theme.of(context).extension<StackColors>()!.popupBG,
borderRadius: radius, borderRadius: radius,
), ),
child: TransactionCardV2( child: Breathing(
// this may mess with combined firo transactions child: TransactionCardV2(
key: UniqueKey(), // this may mess with combined firo transactions
transaction: _tx, key: UniqueKey(),
transaction: _tx,
),
), ),
); );
} }
@ -165,9 +170,11 @@ class TxListItem extends ConsumerWidget {
color: Theme.of(context).extension<StackColors>()!.popupBG, color: Theme.of(context).extension<StackColors>()!.popupBG,
borderRadius: radius, borderRadius: radius,
), ),
child: FusionTxGroupCard( child: Breathing(
key: UniqueKey(), child: FusionTxGroupCard(
group: group, key: UniqueKey(),
group: group,
),
), ),
); );
} }

View file

@ -80,7 +80,6 @@ import '../buy_view/buy_in_wallet_view.dart';
import '../cashfusion/cashfusion_view.dart'; import '../cashfusion/cashfusion_view.dart';
import '../coin_control/coin_control_view.dart'; import '../coin_control/coin_control_view.dart';
import '../exchange_view/wallet_initiated_exchange_view.dart'; import '../exchange_view/wallet_initiated_exchange_view.dart';
import '../home_view/home_view.dart';
import '../monkey/monkey_view.dart'; import '../monkey/monkey_view.dart';
import '../notification_views/notifications_view.dart'; import '../notification_views/notifications_view.dart';
import '../ordinals/ordinals_view.dart'; import '../ordinals/ordinals_view.dart';
@ -257,40 +256,43 @@ class _WalletViewState extends ConsumerState<WalletView> {
super.dispose(); super.dispose();
} }
DateTime? _cachedTime; // DateTime? _cachedTime;
Future<bool> _onWillPop() async { Future<bool> _onWillPop() async {
if (_rescanningOnOpen || _lelantusRescanRecovery) { if (_rescanningOnOpen || _lelantusRescanRecovery) {
return false; return false;
} }
final now = DateTime.now(); _logout();
const timeout = Duration(milliseconds: 1500);
if (_cachedTime == null || now.difference(_cachedTime!) > timeout) { return true;
_cachedTime = now; // final now = DateTime.now();
unawaited( // const timeout = Duration(milliseconds: 1500);
showDialog<dynamic>( // if (_cachedTime == null || now.difference(_cachedTime!) > timeout) {
context: context, // _cachedTime = now;
barrierDismissible: false, // unawaited(
builder: (_) => WillPopScope( // showDialog<dynamic>(
onWillPop: () async { // context: context,
Navigator.of(context).popUntil( // barrierDismissible: false,
ModalRoute.withName(HomeView.routeName), // builder: (_) => WillPopScope(
); // onWillPop: () async {
_logout(); // Navigator.of(context).popUntil(
return false; // ModalRoute.withName(HomeView.routeName),
}, // );
child: const StackDialog(title: "Tap back again to exit wallet"), // _logout();
), // return false;
).timeout( // },
timeout, // child: const StackDialog(title: "Tap back again to exit wallet"),
onTimeout: () => Navigator.of(context).popUntil( // ),
ModalRoute.withName(WalletView.routeName), // ).timeout(
), // timeout,
), // onTimeout: () => Navigator.of(context).popUntil(
); // ModalRoute.withName(WalletView.routeName),
} // ),
return false; // ),
// );
// }
// return false;
} }
void _logout() async { void _logout() async {

View file

@ -13,7 +13,6 @@ import 'dart:async';
import 'package:decimal/decimal.dart'; import 'package:decimal/decimal.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:qr_flutter/qr_flutter.dart';
import '../../../app_config.dart'; import '../../../app_config.dart';
import '../../../models/exchange/incomplete_exchange.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/secondary_button.dart';
import '../../../widgets/desktop/simple_desktop_dialog.dart'; import '../../../widgets/desktop/simple_desktop_dialog.dart';
import '../../../widgets/fade_stack.dart'; import '../../../widgets/fade_stack.dart';
import '../../../widgets/qr.dart';
import '../subwidgets/desktop_exchange_steps_indicator.dart'; import '../subwidgets/desktop_exchange_steps_indicator.dart';
import 'subwidgets/desktop_step_1.dart'; import 'subwidgets/desktop_step_1.dart';
import 'subwidgets/desktop_step_2.dart'; import 'subwidgets/desktop_step_2.dart';
@ -397,7 +397,7 @@ class _StepScaffoldState extends ConsumerState<StepScaffold> {
height: 48, height: 48,
), ),
Center( Center(
child: QrImageView( child: QR(
// TODO: grab coin uri scheme from somewhere // TODO: grab coin uri scheme from somewhere
// data: "${coin.uriScheme}:$receivingAddress", // data: "${coin.uriScheme}:$receivingAddress",
data: ref.watch( data: ref.watch(
@ -406,9 +406,6 @@ class _StepScaffoldState extends ConsumerState<StepScaffold> {
), ),
), ),
size: 290, size: 290,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
), ),
), ),
const SizedBox( const SizedBox(

View file

@ -13,6 +13,7 @@ import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import '../../pages/wallets_view/wallets_overview.dart'; import '../../pages/wallets_view/wallets_overview.dart';
import '../../providers/providers.dart'; import '../../providers/providers.dart';
import '../../themes/coin_icon_provider.dart'; import '../../themes/coin_icon_provider.dart';
@ -21,6 +22,7 @@ import '../../utilities/amount/amount.dart';
import '../../utilities/text_styles.dart'; import '../../utilities/text_styles.dart';
import '../../wallets/crypto_currency/crypto_currency.dart'; import '../../wallets/crypto_currency/crypto_currency.dart';
import '../../wallets/isar/providers/all_wallets_info_provider.dart'; import '../../wallets/isar/providers/all_wallets_info_provider.dart';
import '../../widgets/breathing.dart';
import '../../widgets/conditional_parent.dart'; import '../../widgets/conditional_parent.dart';
import '../../widgets/desktop/desktop_dialog.dart'; import '../../widgets/desktop/desktop_dialog.dart';
import '../../widgets/desktop/desktop_dialog_close_button.dart'; import '../../widgets/desktop/desktop_dialog_close_button.dart';
@ -146,71 +148,56 @@ class _DesktopWalletSummaryRowState
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MouseRegion( return Breathing(
onEnter: (_) => setState( child: RoundedWhiteContainer(
() => _hovering = true, padding: const EdgeInsets.all(20),
), hoverColor: Colors.transparent,
onExit: (_) => setState( onPressed: _onPressed,
() => _hovering = false, child: Row(
), children: [
child: AnimatedScale( Expanded(
scale: _hovering ? 1.00 : 0.98, flex: 4,
duration: const Duration( child: Row(
milliseconds: 200, children: [
), SvgPicture.file(
child: RoundedWhiteContainer( File(
padding: const EdgeInsets.all(20), ref.watch(coinIconProvider(widget.coin)),
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,
), ),
const SizedBox( width: 28,
width: 10, height: 28,
),
Text(
widget.coin.prettyName,
style:
STextStyles.desktopTextExtraSmall(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.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<StackColors>()!
.textSubtitle1,
), ),
const SizedBox(
width: 10,
),
Text(
widget.coin.prettyName,
style: STextStyles.desktopTextExtraSmall(context).copyWith(
color:
Theme.of(context).extension<StackColors>()!.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<StackColors>()!.textSubtitle1,
), ),
), ),
Expanded( ),
flex: 6, Expanded(
child: TablePriceInfo( flex: 6,
coin: widget.coin, child: TablePriceInfo(
), coin: widget.coin,
), ),
], ),
), ],
), ),
), ),
); );

View file

@ -294,7 +294,9 @@ class _DesktopWalletViewState extends ConsumerState<DesktopWalletView> {
width: 2, width: 2,
), ),
FutureBuilder( FutureBuilder(
future: FiroCacheCoordinator.getSparkCacheSize(), future: FiroCacheCoordinator.getSparkCacheSize(
wallet.cryptoCurrency.network,
),
builder: (_, snapshot) => Text( builder: (_, snapshot) => Text(
snapshot.data ?? "", snapshot.data ?? "",
), ),

View file

@ -16,7 +16,6 @@ import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:isar/isar.dart'; import 'package:isar/isar.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
import '../../../../models/isar/models/isar_models.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/custom_loading_overlay.dart';
import '../../../../widgets/desktop/desktop_dialog.dart'; import '../../../../widgets/desktop/desktop_dialog.dart';
import '../../../../widgets/desktop/secondary_button.dart'; import '../../../../widgets/desktop/secondary_button.dart';
import '../../../../widgets/qr.dart';
import '../../../../widgets/rounded_white_container.dart'; import '../../../../widgets/rounded_white_container.dart';
class DesktopReceive extends ConsumerStatefulWidget { class DesktopReceive extends ConsumerStatefulWidget {
@ -476,15 +476,13 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
height: 32, height: 32,
), ),
Center( Center(
child: QrImageView( child: QR(
data: AddressUtils.buildUriString( data: AddressUtils.buildUriString(
coin, coin,
_qrcodeContent ?? "", _qrcodeContent ?? "",
{}, {},
), ),
size: 200, size: 200,
foregroundColor:
Theme.of(context).extension<StackColors>()!.accentColorDark,
), ),
), ),
const SizedBox( const SizedBox(

View file

@ -60,6 +60,7 @@ import '../../../../widgets/desktop/desktop_dialog_close_button.dart';
import '../../../../widgets/desktop/desktop_fee_dialog.dart'; import '../../../../widgets/desktop/desktop_fee_dialog.dart';
import '../../../../widgets/desktop/primary_button.dart'; import '../../../../widgets/desktop/primary_button.dart';
import '../../../../widgets/desktop/secondary_button.dart'; import '../../../../widgets/desktop/secondary_button.dart';
import '../../../../widgets/dialogs/firo_exchange_address_dialog.dart';
import '../../../../widgets/fee_slider.dart'; import '../../../../widgets/fee_slider.dart';
import '../../../../widgets/icon_widgets/addressbook_icon.dart'; import '../../../../widgets/icon_widgets/addressbook_icon.dart';
import '../../../../widgets/icon_widgets/clipboard_icon.dart'; import '../../../../widgets/icon_widgets/clipboard_icon.dart';
@ -121,6 +122,8 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
bool _addressToggleFlag = false; bool _addressToggleFlag = false;
bool _isFiroExWarningDisplayed = false;
bool _cryptoAmountChangeLock = false; bool _cryptoAmountChangeLock = false;
late VoidCallback onCryptoAmountChanged; late VoidCallback onCryptoAmountChanged;
@ -706,6 +709,19 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
address: address ?? "", address: address ?? "",
isTestNet: wallet.cryptoCurrency.network.isTestNet, 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 = ref.read(pValidSendToAddress.notifier).state =
@ -842,6 +858,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
void initState() { void initState() {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
ref.refresh(feeSheetSessionCacheProvider); ref.refresh(feeSheetSessionCacheProvider);
ref.refresh(pIsExchangeAddress);
ref.read(pValidSendToAddress.state).state = false; ref.read(pValidSendToAddress.state).state = false;
ref.read(pValidSparkSendToAddress.state).state = false; ref.read(pValidSparkSendToAddress.state).state = false;
}); });
@ -944,15 +961,31 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
}); });
} }
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( final showCoinControl = ref.watch(
prefsChangeNotifierProvider.select( prefsChangeNotifierProvider.select(
(value) => value.enableCoinControl, (value) => value.enableCoinControl,
), ),
) && ) &&
ref.watch(pWallets).getWallet(walletId) is CoinControlInterface && ref.watch(pWallets).getWallet(walletId) is CoinControlInterface &&
(coin is Firo (coin is Firo ? firoType == FiroType.public : true);
? ref.watch(publicPrivateBalanceStateProvider) == FiroType.public
: true);
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -978,7 +1011,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
DropdownButtonHideUnderline( DropdownButtonHideUnderline(
child: DropdownButton2( child: DropdownButton2(
isExpanded: true, isExpanded: true,
value: ref.watch(publicPrivateBalanceStateProvider.state).state, value: firoType,
items: [ items: [
DropdownMenuItem( DropdownMenuItem(
value: FiroType.spark, value: FiroType.spark,
@ -1464,8 +1497,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
if (_address == null || _address!.isEmpty) { if (_address == null || _address!.isEmpty) {
error = null; error = null;
} else if (coin is Firo) { } else if (coin is Firo) {
if (ref.watch(publicPrivateBalanceStateProvider) == if (firoType == FiroType.lelantus) {
FiroType.lelantus) {
if (_data != null && _data!.contactLabel == _address) { if (_data != null && _data!.contactLabel == _address) {
error = SparkInterface.validateSparkAddress( error = SparkInterface.validateSparkAddress(
address: _data!.address, address: _data!.address,
@ -1526,15 +1558,13 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
), ),
if (isStellar || if (isStellar ||
(ref.watch(pValidSparkSendToAddress) && (ref.watch(pValidSparkSendToAddress) &&
ref.watch(publicPrivateBalanceStateProvider) != firoType != FiroType.lelantus))
FiroType.lelantus))
const SizedBox( const SizedBox(
height: 10, height: 10,
), ),
if (isStellar || if (isStellar ||
(ref.watch(pValidSparkSendToAddress) && (ref.watch(pValidSparkSendToAddress) &&
ref.watch(publicPrivateBalanceStateProvider) != firoType != FiroType.lelantus))
FiroType.lelantus))
ClipRRect( ClipRRect(
borderRadius: BorderRadius.circular( borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius, Constants.size.circularBorderRadius,

View file

@ -19,8 +19,7 @@ import '../../../../../providers/global/wallets_provider.dart';
import '../../../../../themes/stack_colors.dart'; import '../../../../../themes/stack_colors.dart';
import '../../../../../utilities/assets.dart'; import '../../../../../utilities/assets.dart';
import '../../../../../utilities/text_styles.dart'; import '../../../../../utilities/text_styles.dart';
import '../../../../../wallets/crypto_currency/coins/banano.dart'; import '../../../../../wallets/crypto_currency/crypto_currency.dart';
import '../../../../../wallets/crypto_currency/coins/firo.dart';
import '../../../../../wallets/isar/models/wallet_info.dart'; import '../../../../../wallets/isar/models/wallet_info.dart';
import '../../../../../wallets/isar/providers/wallet_info_provider.dart'; import '../../../../../wallets/isar/providers/wallet_info_provider.dart';
import '../../../../../wallets/wallet/wallet_mixin_interfaces/cash_fusion_interface.dart'; import '../../../../../wallets/wallet/wallet_mixin_interfaces/cash_fusion_interface.dart';
@ -187,7 +186,9 @@ class _MoreFeaturesDialogState extends ConsumerState<MoreFeaturesDialog> {
onPressed: () async => widget.onFusionPressed?.call(), onPressed: () async => widget.onFusionPressed?.call(),
), ),
if (wallet is SparkInterface) if (wallet is SparkInterface)
const _MoreFeaturesClearSparkCacheItem(), _MoreFeaturesClearSparkCacheItem(
cryptoCurrency: wallet.cryptoCurrency,
),
if (wallet is LelantusInterface) if (wallet is LelantusInterface)
_MoreFeaturesItemBase( _MoreFeaturesItemBase(
child: Row( child: Row(
@ -371,10 +372,10 @@ class _MoreFeaturesItemBase extends StatelessWidget {
class _MoreFeaturesClearSparkCacheItem extends StatefulWidget { class _MoreFeaturesClearSparkCacheItem extends StatefulWidget {
const _MoreFeaturesClearSparkCacheItem({ const _MoreFeaturesClearSparkCacheItem({
super.key, super.key,
required this.cryptoCurrency,
}); });
static const double iconSizeBG = 46; final CryptoCurrency cryptoCurrency;
static const double iconSize = 24;
@override @override
State<_MoreFeaturesClearSparkCacheItem> createState() => State<_MoreFeaturesClearSparkCacheItem> createState() =>
@ -396,7 +397,9 @@ class _MoreFeaturesClearSparkCacheItemState
} }
_onPressedLock = true; _onPressedLock = true;
try { try {
await FiroCacheCoordinator.clearSharedCache(); await FiroCacheCoordinator.clearSharedCache(
widget.cryptoCurrency.network,
);
setState(() { setState(() {
// trigger rebuild for cache size display // trigger rebuild for cache size display
}); });
@ -434,7 +437,9 @@ class _MoreFeaturesClearSparkCacheItemState
style: STextStyles.w600_20(context), style: STextStyles.w600_20(context),
), ),
FutureBuilder( FutureBuilder(
future: FiroCacheCoordinator.getSparkCacheSize(), future: FiroCacheCoordinator.getSparkCacheSize(
widget.cryptoCurrency.network,
),
builder: (_, snapshot) { builder: (_, snapshot) {
return Text( return Text(
snapshot.data ?? "", snapshot.data ?? "",

View file

@ -9,10 +9,10 @@
*/ */
import 'package:flutter/material.dart'; 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.dart';
import '../../../../widgets/desktop/desktop_dialog_close_button.dart'; import '../../../../widgets/desktop/desktop_dialog_close_button.dart';
import '../../../../widgets/qr.dart';
class QRCodeDesktopPopupContent extends StatelessWidget { class QRCodeDesktopPopupContent extends StatelessWidget {
const QRCodeDesktopPopupContent({ const QRCodeDesktopPopupContent({
@ -39,11 +39,9 @@ class QRCodeDesktopPopupContent extends StatelessWidget {
const SizedBox( const SizedBox(
height: 14, height: 14,
), ),
QrImageView( QR(
data: value, data: value,
size: 300, size: 300,
foregroundColor:
Theme.of(context).extension<StackColors>()!.accentColorDark,
), ),
], ],
), ),

View file

@ -9,27 +9,37 @@
*/ */
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../wallet/public_private_balance_state_provider.dart';
import '../../utilities/amount/amount.dart'; import '../../utilities/amount/amount.dart';
import '../../wallets/crypto_currency/crypto_currency.dart'; import '../../wallets/crypto_currency/crypto_currency.dart';
import '../wallet/public_private_balance_state_provider.dart';
final pSendAmount = StateProvider.autoDispose<Amount?>((_) => null); final pSendAmount = StateProvider.autoDispose<Amount?>((_) => null);
final pValidSendToAddress = StateProvider.autoDispose<bool>((_) => false); final pValidSendToAddress = StateProvider.autoDispose<bool>((_) => false);
final pValidSparkSendToAddress = StateProvider.autoDispose<bool>((_) => false); final pValidSparkSendToAddress = StateProvider.autoDispose<bool>((_) => false);
final pIsExchangeAddress = StateProvider<bool>((_) => false);
final pPreviewTxButtonEnabled = final pPreviewTxButtonEnabled =
Provider.autoDispose.family<bool, CryptoCurrency>((ref, coin) { Provider.autoDispose.family<bool, CryptoCurrency>((ref, coin) {
final amount = ref.watch(pSendAmount) ?? Amount.zero; final amount = ref.watch(pSendAmount) ?? Amount.zero;
if (coin is Firo) { if (coin is Firo) {
if (ref.watch(publicPrivateBalanceStateProvider) == FiroType.lelantus) { final firoType = ref.watch(publicPrivateBalanceStateProvider);
return ref.watch(pValidSendToAddress) && switch (firoType) {
!ref.watch(pValidSparkSendToAddress) && case FiroType.lelantus:
amount > Amount.zero; return ref.watch(pValidSendToAddress) &&
} else { !ref.watch(pValidSparkSendToAddress) &&
return (ref.watch(pValidSendToAddress) || amount > Amount.zero;
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 { } else {
return ref.watch(pValidSendToAddress) && amount > Amount.zero; return ref.watch(pValidSendToAddress) && amount > Amount.zero;

View file

@ -1982,13 +1982,18 @@ class RouteGenerator {
return _routeError("${settings.name} invalid args: ${args.toString()}"); return _routeError("${settings.name} invalid args: ${args.toString()}");
case SparkInfoView.routeName: case SparkInfoView.routeName:
return getRoute( if (args is String) {
shouldUseMaterialRoute: useMaterialPageRoute, return getRoute(
builder: (_) => const SparkInfoView(), shouldUseMaterialRoute: useMaterialPageRoute,
settings: RouteSettings( builder: (_) => SparkInfoView(
name: settings.name, walletId: args,
), ),
); settings: RouteSettings(
name: settings.name,
),
);
}
return _routeError("${settings.name} invalid args: ${args.toString()}");
// == Desktop specific routes ============================================ // == Desktop specific routes ============================================
case CreatePasswordView.routeName: case CreatePasswordView.routeName:

View file

@ -11,18 +11,19 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:uuid/uuid.dart';
import '../app_config.dart';
import '../db/hive/db.dart'; import '../db/hive/db.dart';
import '../services/event_bus/events/global/tor_status_changed_event.dart'; import '../services/event_bus/events/global/tor_status_changed_event.dart';
import '../services/event_bus/global_event_bus.dart'; import '../services/event_bus/global_event_bus.dart';
import '../app_config.dart'; import '../wallets/crypto_currency/crypto_currency.dart';
import '../wallets/wallet/wallet_mixin_interfaces/cash_fusion_interface.dart';
import 'amount/amount_unit.dart'; import 'amount/amount_unit.dart';
import 'constants.dart'; import 'constants.dart';
import 'enums/backup_frequency_type.dart'; import 'enums/backup_frequency_type.dart';
import 'enums/languages_enum.dart'; import 'enums/languages_enum.dart';
import 'enums/sync_type_enum.dart'; import 'enums/sync_type_enum.dart';
import '../wallets/crypto_currency/crypto_currency.dart';
import '../wallets/wallet/wallet_mixin_interfaces/cash_fusion_interface.dart';
import 'package:uuid/uuid.dart';
class Prefs extends ChangeNotifier { class Prefs extends ChangeNotifier {
Prefs._(); Prefs._();
@ -1103,4 +1104,30 @@ class Prefs extends ChangeNotifier {
return actualMap; return actualMap;
} }
// Automatic PIN entry.
bool _autoPin = false;
bool get autoPin => _autoPin;
set autoPin(bool autoPin) {
if (_autoPin != autoPin) {
DB.instance.put<dynamic>(
boxName: DB.boxNamePrefs,
key: "autoPin",
value: autoPin,
);
_autoPin = autoPin;
notifyListeners();
}
}
Future<bool> _getAutoPin() async {
return await DB.instance.get<dynamic>(
boxName: DB.boxNamePrefs,
key: "autoPin",
) as bool? ??
false;
}
} }

View file

@ -1,5 +1,8 @@
import 'dart:typed_data';
import 'package:coinlib_flutter/coinlib_flutter.dart' as coinlib; 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/isar/models/blockchain_data/address.dart';
import '../../../models/node_model.dart'; import '../../../models/node_model.dart';
import '../../../utilities/amount/amount.dart'; import '../../../utilities/amount/amount.dart';
@ -77,6 +80,21 @@ class Firo extends Bip39HDCurrency with ElectrumXCurrencyInterface {
fractionDigits: fractionDigits, 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 @override
coinlib.Network get networkParams { coinlib.Network get networkParams {
switch (network) { switch (network) {
@ -169,7 +187,11 @@ class Firo extends Bip39HDCurrency with ElectrumXCurrencyInterface {
coinlib.Address.fromString(address, networkParams); coinlib.Address.fromString(address, networkParams);
return true; return true;
} catch (_) { } 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 @override
NodeModel get defaultNode { NodeModel get defaultNode {
switch (network) { switch (network) {

View file

@ -387,6 +387,7 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
parseAnonFees(); parseAnonFees();
final tags = await FiroCacheCoordinator.getUsedCoinTagsFor( final tags = await FiroCacheCoordinator.getUsedCoinTagsFor(
txid: txData["txid"] as String, txid: txData["txid"] as String,
network: cryptoCurrency.network,
); );
spentSparkCoins = sparkCoinsInvolvedSpent spentSparkCoins = sparkCoinsInvolvedSpent
.where( .where(
@ -631,9 +632,23 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
BigInt.from(jsonUTXO["value"] as int); BigInt.from(jsonUTXO["value"] as int);
if (blocked) { 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."; "Unlock and spend at your own risk.";
label = "Possible masternode"; label = "Possible masternode collateral";
} }
} }
@ -698,12 +713,14 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
FiroCacheCoordinator.runFetchAndUpdateSparkAnonSetCacheForGroupId( FiroCacheCoordinator.runFetchAndUpdateSparkAnonSetCacheForGroupId(
i, i,
electrumXClient, electrumXClient,
cryptoCurrency.network,
), ),
); );
} }
final sparkUsedCoinTagsFuture = final sparkUsedCoinTagsFuture =
FiroCacheCoordinator.runFetchAndUpdateSparkUsedCoinTags( FiroCacheCoordinator.runFetchAndUpdateSparkUsedCoinTags(
electrumXClient, electrumXClient,
cryptoCurrency.network,
); );
// receiving addresses // receiving addresses

View file

@ -8,6 +8,7 @@ import 'package:isar/isar.dart';
import '../../../electrumx_rpc/cached_electrumx_client.dart'; import '../../../electrumx_rpc/cached_electrumx_client.dart';
import '../../../electrumx_rpc/client_manager.dart'; import '../../../electrumx_rpc/client_manager.dart';
import '../../../electrumx_rpc/electrumx_client.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/input_v2.dart';
import '../../../models/isar/models/blockchain_data/v2/output_v2.dart'; import '../../../models/isar/models/blockchain_data/v2/output_v2.dart';
import '../../../models/isar/models/blockchain_data/v2/transaction_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 '../../crypto_currency/interfaces/electrumx_currency_interface.dart';
import '../../models/tx_data.dart'; import '../../models/tx_data.dart';
import '../impl/bitcoin_wallet.dart'; import '../impl/bitcoin_wallet.dart';
import '../impl/firo_wallet.dart';
import '../impl/peercoin_wallet.dart'; import '../impl/peercoin_wallet.dart';
import '../intermediate/bip39_hd_wallet.dart'; import '../intermediate/bip39_hd_wallet.dart';
import 'cpfp_interface.dart'; import 'cpfp_interface.dart';
@ -725,11 +727,23 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
// Add transaction output // Add transaction output
for (var i = 0; i < txData.recipients!.length; i++) { for (var i = 0; i < txData.recipients!.length; i++) {
final address = coinlib.Address.fromString( late final coinlib.Address address;
normalizeAddress(txData.recipients![i].address),
cryptoCurrency.networkParams,
);
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( final output = coinlib.Output.fromAddress(
txData.recipients![i].amount.raw, txData.recipients![i].amount.raw,
address, address,

View file

@ -278,13 +278,17 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
final List<Map<String, dynamic>> setMaps = []; final List<Map<String, dynamic>> setMaps = [];
final List<({int groupId, String blockHash})> idAndBlockHashes = []; final List<({int groupId, String blockHash})> idAndBlockHashes = [];
for (int i = 1; i <= currentId; i++) { 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) { if (resultSet.isEmpty) {
continue; continue;
} }
final info = await FiroCacheCoordinator.getLatestSetInfoForGroupId( final info = await FiroCacheCoordinator.getLatestSetInfoForGroupId(
i, i,
cryptoCurrency.network,
); );
if (info == null) { if (info == null) {
throw Exception("The `info` should never be null here"); throw Exception("The `info` should never be null here");
@ -716,13 +720,13 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
return result; return result;
} catch (e) { } catch (e) {
Logging.instance.log( Logging.instance.log(
"refreshSparkMempoolData() failed: $e", "_refreshSparkCoinsMempoolCheck() failed: $e",
level: LogLevel.Error, level: LogLevel.Error,
); );
return []; return [];
} finally { } finally {
Logging.instance.log( Logging.instance.log(
"$walletId ${info.name} refreshSparkCoinsMempoolCheck() run " "$walletId ${info.name} _refreshSparkCoinsMempoolCheck() run "
"duration: ${DateTime.now().difference(start)}", "duration: ${DateTime.now().difference(start)}",
level: LogLevel.Debug, level: LogLevel.Debug,
); );
@ -741,6 +745,7 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
final setExists = final setExists =
await FiroCacheCoordinator.checkSetInfoForGroupIdExists( await FiroCacheCoordinator.checkSetInfoForGroupIdExists(
id, id,
cryptoCurrency.network,
); );
if (!setExists) { if (!setExists) {
groupIds.add(id); groupIds.add(id);
@ -755,6 +760,7 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
FiroCacheCoordinator.runFetchAndUpdateSparkAnonSetCacheForGroupId( FiroCacheCoordinator.runFetchAndUpdateSparkAnonSetCacheForGroupId(
e, e,
electrumXClient, electrumXClient,
cryptoCurrency.network,
), ),
); );
@ -763,6 +769,7 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
...possibleFutures, ...possibleFutures,
FiroCacheCoordinator.runFetchAndUpdateSparkUsedCoinTags( FiroCacheCoordinator.runFetchAndUpdateSparkUsedCoinTags(
electrumXClient, electrumXClient,
cryptoCurrency.network,
), ),
]); ]);
@ -782,11 +789,13 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
groupIdTimestampUTCMap[i.toString()] as int? ?? 0; groupIdTimestampUTCMap[i.toString()] as int? ?? 0;
final info = await FiroCacheCoordinator.getLatestSetInfoForGroupId( final info = await FiroCacheCoordinator.getLatestSetInfoForGroupId(
i, i,
cryptoCurrency.network,
); );
final anonymitySetResult = final anonymitySetResult =
await FiroCacheCoordinator.getSetCoinsForGroupId( await FiroCacheCoordinator.getSetCoinsForGroupId(
i, i,
newerThanTimeStamp: lastCheckedTimeStampUTC, newerThanTimeStamp: lastCheckedTimeStampUTC,
network: cryptoCurrency.network,
); );
final coinsRaw = anonymitySetResult final coinsRaw = anonymitySetResult
.map( .map(
@ -882,7 +891,10 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
// only fetch tags from db if we need them to compare against any items // only fetch tags from db if we need them to compare against any items
// in coinsToCheck // in coinsToCheck
if (coinsToCheck.isNotEmpty) { if (coinsToCheck.isNotEmpty) {
spentCoinTags = await FiroCacheCoordinator.getUsedCoinTags(0); spentCoinTags = await FiroCacheCoordinator.getUsedCoinTags(
0,
cryptoCurrency.network,
);
} }
// check and update coins if required // check and update coins if required
@ -992,6 +1004,7 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
final pairs = await FiroCacheCoordinator.getUsedCoinTxidsFor( final pairs = await FiroCacheCoordinator.getUsedCoinTxidsFor(
tags: tags, tags: tags,
network: cryptoCurrency.network,
); );
pairs.removeWhere((e) => usedCoinTxidsFoundLocally.contains(e.txid)); pairs.removeWhere((e) => usedCoinTxidsFoundLocally.contains(e.txid));

View file

@ -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<Breathing> createState() => _BreathingState();
}
class _BreathingState extends State<Breathing> {
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,
),
);
}
}

View file

@ -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<void> showFiroExchangeAddressWarning(
BuildContext context,
VoidCallback onClosed,
) async {
await showDialog<void>(
context: context,
builder: (_) => const FiroExchangeAddressDialog(),
);
onClosed();
}

View file

@ -7,7 +7,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:share_plus/share_plus.dart'; import 'package:share_plus/share_plus.dart';
import '../../../notifications/show_flush_bar.dart'; import '../../../notifications/show_flush_bar.dart';
@ -17,6 +17,7 @@ import '../../../utilities/text_styles.dart';
import '../../../utilities/util.dart'; import '../../../utilities/util.dart';
import '../../conditional_parent.dart'; import '../../conditional_parent.dart';
import '../../desktop/secondary_button.dart'; import '../../desktop/secondary_button.dart';
import '../../qr.dart';
import '../../rounded_container.dart'; import '../../rounded_container.dart';
import '../../rounded_white_container.dart'; import '../../rounded_white_container.dart';
import '../simple_mobile_dialog.dart'; import '../simple_mobile_dialog.dart';
@ -154,18 +155,9 @@ class _FrostStepQrDialogState extends State<FrostStepQrDialog> {
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: AspectRatio( child: AspectRatio(
aspectRatio: 1, aspectRatio: 1,
child: QrImageView( child: QR(
data: widget.data, data: widget.data,
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
// dataModuleStyle: QrDataModuleStyle(
// dataModuleShape: QrDataModuleShape.square,
// color: Theme.of(context)
// .extension<StackColors>()!
// .accentColorDark,
// ),
), ),
), ),
), ),

27
lib/widgets/qr.dart Normal file
View file

@ -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<StackColors>()!.background,
// foregroundColor: Theme.of(context)
// .extension<StackColors>()!
// .accentColorDark,
);
}
}

View file

@ -188,14 +188,19 @@ class StackOkDialog extends StatelessWidget {
height: 8, height: 8,
), ),
if (message != null) if (message != null)
Column( ConstrainedBox(
crossAxisAlignment: CrossAxisAlignment.start, constraints:
children: [ BoxConstraints(maxWidth: maxWidth ?? double.infinity),
Text( child: Row(
message!, children: [
style: STextStyles.smallMed14(context), Flexible(
), child: Text(
], message!,
style: STextStyles.smallMed14(context),
),
),
],
),
), ),
const SizedBox( const SizedBox(
height: 20, height: 20,

View file

@ -1807,8 +1807,8 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
path: "." path: "."
ref: f1d02f7ad489df3119a540a7f31485db6d837843 ref: "647cadc3c82c276dc07915b02d24538fd610f220"
resolved-ref: f1d02f7ad489df3119a540a7f31485db6d837843 resolved-ref: "647cadc3c82c276dc07915b02d24538fd610f220"
url: "https://github.com/cypherstack/tor.git" url: "https://github.com/cypherstack/tor.git"
source: git source: git
version: "0.0.1" version: "0.0.1"