From 289f0b89597d7f5df29ae4b082d7684945c185ec Mon Sep 17 00:00:00 2001 From: sneurlax Date: Thu, 13 Oct 2022 22:13:42 -0500 Subject: [PATCH 01/76] Add mainnet tests for address generation from test mnemonic TODO: Clean up the wallet file as the tests conclude --- .../coins/monero/monero_wallet_test.dart | 207 ++++++++++++++++++ .../coins/monero/monero_wallet_test_data.dart | 14 ++ 2 files changed, 221 insertions(+) create mode 100644 test/services/coins/monero/monero_wallet_test.dart create mode 100644 test/services/coins/monero/monero_wallet_test_data.dart diff --git a/test/services/coins/monero/monero_wallet_test.dart b/test/services/coins/monero/monero_wallet_test.dart new file mode 100644 index 000000000..1ee2fc44a --- /dev/null +++ b/test/services/coins/monero/monero_wallet_test.dart @@ -0,0 +1,207 @@ +import 'dart:async'; +import 'dart:core'; +import 'dart:core' as core; +import 'dart:io'; +import 'dart:math'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:hive/hive.dart'; +import 'package:hive_test/hive_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'package:cw_core/monero_amount_format.dart'; +import 'package:cw_core/node.dart'; +import 'package:cw_core/pending_transaction.dart'; +import 'package:cw_core/unspent_coins_info.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_credentials.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_service.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:cw_monero/api/wallet.dart'; +import 'package:cw_monero/pending_monero_transaction.dart'; +import 'package:cw_monero/monero_wallet.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_libmonero/core/key_service.dart'; +import 'package:flutter_libmonero/core/wallet_creation_service.dart'; +import 'package:flutter_libmonero/view_model/send/output.dart'; +import 'package:flutter_libmonero/monero/monero.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:hive/hive.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; + +import 'dart:developer' as developer; + +// TODO trim down to the minimum imports above + +import 'monero_wallet_test_data.dart'; + +//FlutterSecureStorage? storage; +FakeSecureStorage? storage; +WalletService? walletService; +SharedPreferences? prefs; +KeyService? keysStorage; +MoneroWalletBase? walletBase; +late WalletCreationService _walletCreationService; +dynamic _walletInfoSource; + +String name = 'namee${Random().nextInt(10000000)}'; +int nettype = 0; +WalletType type = WalletType.monero; + +@GenerateMocks([]) +void main() async { + storage = FakeSecureStorage(); + prefs = await SharedPreferences.getInstance(); + keysStorage = KeyService(storage!); + WalletInfo walletInfo = WalletInfo.external( + id: '', + name: '', + type: type, + isRecovery: false, + restoreHeight: 0, + date: DateTime.now(), + path: '', + address: '', + dirPath: ''); + late WalletCredentials credentials; + + WidgetsFlutterBinding.ensureInitialized(); + Directory appDir = (await getApplicationDocumentsDirectory()); + if (Platform.isIOS) { + appDir = (await getLibraryDirectory()); + } + await Hive.close(); + Hive.init(appDir.path); + Hive.registerAdapter(NodeAdapter()); + Hive.registerAdapter(WalletInfoAdapter()); + Hive.registerAdapter(WalletTypeAdapter()); + Hive.registerAdapter(UnspentCoinsInfoAdapter()); + + monero.onStartup(); + _walletInfoSource = await Hive.openBox(WalletInfo.boxName); + walletService = monero.createMoneroWalletService(_walletInfoSource); + + group("Mainnet tests", () { + setUp(() async { + try { + // if (name?.isEmpty ?? true) { + // name = await generateName(); + // } + final dirPath = await pathForWalletDir(name: name, type: type); + final path = await pathForWallet(name: name, type: type); + credentials = + // // creating a new wallet + // monero.createMoneroNewWalletCredentials( + // name: name, language: "English"); + // restoring a previous wallet + monero.createMoneroRestoreWalletFromSeedCredentials( + name: name, height: 2580000, mnemonic: testMnemonic); + + walletInfo = WalletInfo.external( + id: WalletBase.idFor(name, type), + name: name, + type: type, + isRecovery: false, + restoreHeight: credentials.height ?? 0, + date: DateTime.now(), + path: path, + address: "", + dirPath: dirPath); + credentials.walletInfo = walletInfo; + + _walletCreationService = WalletCreationService( + secureStorage: storage, + sharedPreferences: prefs, + walletService: walletService, + keyService: keysStorage, + ); + _walletCreationService.changeWalletType(); + } catch (e, s) { + print(e); + print(s); + } + }); + + test("Test mainnet address generation from seed", () async { + final wallet = await + // _walletCreationService.create(credentials); + _walletCreationService.restoreFromSeed(credentials); + walletInfo.address = wallet.walletAddresses.address; + //print(walletInfo.address); + + await _walletInfoSource.add(walletInfo); + walletBase?.close(); + walletBase = wallet as MoneroWalletBase; + //print("${walletBase?.seed}"); + + // print(walletBase); + // loggerPrint(walletBase.toString()); + // loggerPrint("name: ${walletBase!.name} seed: ${walletBase!.seed} id: " + // "${walletBase!.id} walletinfo: ${toStringForinfo(walletBase!.walletInfo)} type: ${walletBase!.type} balance: " + // "${walletBase!.balance.entries.first.value.available} currency: ${walletBase!.currency}"); + + expect(walletInfo.address, mainnetTestData[0][0]); + expect( + await walletBase!.getTransactionAddress(0, 0), mainnetTestData[0][0]); + expect( + await walletBase!.getTransactionAddress(0, 1), mainnetTestData[0][1]); + expect( + await walletBase!.getTransactionAddress(0, 2), mainnetTestData[0][2]); + expect( + await walletBase!.getTransactionAddress(1, 0), mainnetTestData[1][0]); + expect( + await walletBase!.getTransactionAddress(1, 1), mainnetTestData[1][1]); + expect( + await walletBase!.getTransactionAddress(1, 2), mainnetTestData[1][2]); + }); + }); + /* + group("Mainnet node tests", () { + test("Test mainnet node connection", () async { + await walletBase?.connectToNode( + node: Node( + uri: "monero-stagenet.stackwallet.com:38081", + type: WalletType.moneroStageNet)); + await walletBase!.rescan( + height: + credentials.height); // Probably shouldn't be rescanning from 0... + await walletBase!.getNodeHeight(); + int height = await walletBase!.getNodeHeight(); + print('height: $height'); + bool connected = await walletBase!.isConnected(); + print('connected: $connected'); + + //expect... + }); + }); + */ + + // TODO test deletion of wallets ... and delete them +} + +Future pathForWalletDir( + {required String name, required WalletType type}) async { + Directory root = (await getApplicationDocumentsDirectory()); + if (Platform.isIOS) { + root = (await getLibraryDirectory()); + } + final prefix = walletTypeToString(type).toLowerCase(); + final walletsDir = Directory('${root.path}/wallets'); + final walletDire = Directory('${walletsDir.path}/$prefix/$name'); + + if (!walletDire.existsSync()) { + walletDire.createSync(recursive: true); + } + + return walletDire.path; +} + +Future pathForWallet( + {required String name, required WalletType type}) async => + await pathForWalletDir(name: name, type: type) + .then((path) => path + '/$name'); diff --git a/test/services/coins/monero/monero_wallet_test_data.dart b/test/services/coins/monero/monero_wallet_test_data.dart new file mode 100644 index 000000000..dc0a0f4cb --- /dev/null +++ b/test/services/coins/monero/monero_wallet_test_data.dart @@ -0,0 +1,14 @@ +String testMnemonic = + 'agreed aquarium wallets uptight karate wonders afoot guys itself nucleus reduce lamb fully fewest bimonthly dazed skulls magically mocked fugitive imbalance saga calamity dialect itself'; +var mainnetTestData = [ + [ + '4AeRgkWZsMJhAWKMeCZ3h4ZSPnAcW5VBtRFyLd6gBEf6GgJU2FHXDA6i1DnQTd6h8R3VU5AkbGcWSNhtSwNNPgaD48gp4nn', + '82WsoLmbZt3BPwJMF5PfT8GitThJzUq3FFoSQyr4fKfJdxZebgY3mHPcnAqTBA3FFwZRGxC4ZDwkfE1VVULPa55x3xXgCbj', + '84kYPuZ1eaVKGQhf26QPNWbSLQG16BywXdLYYShVrPNMLAUAWce5vcpRc78FxwRphrG6Cda7faCKdUMr8fUCH3peHPenvHy' + ], + [ + '86SF44CsTBYU3vk1X7nGBbQnrUSknGbd6Uw8a9hUUgy3KBeXTDvk3pm8upMzZKw17m3mLPEzbcPp5WLpYVoHR5PKNVtFrHH', + '8Aa9LNGdBHwYUMsy6M9ZVXMEkTBZyEDT7aQmY32trCxbU6dwkZJSCSbcpyL7UiTB9QXXosomZtJYvUJ296vTNX5yQ81KaA2', + '85C5zZRcaD89PKmXEwjcYMVAUqoH5rrAXe3GokvSupXnDmccYvZagz5Qem7bQLteEw4iFEJ9oRk9BNfjTi4K2cyTJbTMMPT' + ] +]; From a3a36d02c3a7e6f4ef6b166a5afd6cad55012a25 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Fri, 14 Oct 2022 10:09:17 -0500 Subject: [PATCH 02/76] Import wallets service and remove wallet at conclusion of tests --- .../coins/monero/monero_wallet_test.dart | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/services/coins/monero/monero_wallet_test.dart b/test/services/coins/monero/monero_wallet_test.dart index 1ee2fc44a..5b802a089 100644 --- a/test/services/coins/monero/monero_wallet_test.dart +++ b/test/services/coins/monero/monero_wallet_test.dart @@ -34,6 +34,8 @@ import 'package:path_provider/path_provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; +import 'package:stackwallet/services/wallets.dart'; + import 'dart:developer' as developer; // TODO trim down to the minimum imports above @@ -48,6 +50,7 @@ KeyService? keysStorage; MoneroWalletBase? walletBase; late WalletCreationService _walletCreationService; dynamic _walletInfoSource; +Wallets? walletsService; String name = 'namee${Random().nextInt(10000000)}'; int nettype = 0; @@ -160,6 +163,22 @@ void main() async { await walletBase!.getTransactionAddress(1, 2), mainnetTestData[1][2]); }); }); + + group("Mainnet wallet deletion test", () { + test("Test mainnet wallet deletion", () async { + // Remove wallet from wallet service + walletService?.remove(name); + walletsService?.removeWallet(walletId: name); + + // TODO test deletion, get code from generation for checking if it already exists + }); + + /* + // wait for widget tree to dispose of any widgets watching the manager + await Future.delayed(const Duration(seconds: 1)); + walletsInstance.removeWallet(walletId: walletId); + */ + }); /* group("Mainnet node tests", () { test("Test mainnet node connection", () async { From b6e51cb954ff1b90765b582487ecb3141fc5c840 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Fri, 14 Oct 2022 10:24:05 -0500 Subject: [PATCH 03/76] Add template wallet detection and removal code Not enabled as wallet files are not currently being saved; only an empty folder is created and left. TODO clean that up --- .../coins/monero/monero_wallet_test.dart | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/test/services/coins/monero/monero_wallet_test.dart b/test/services/coins/monero/monero_wallet_test.dart index 5b802a089..d54959ab2 100644 --- a/test/services/coins/monero/monero_wallet_test.dart +++ b/test/services/coins/monero/monero_wallet_test.dart @@ -20,6 +20,7 @@ import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_service.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:cw_monero/api/wallet.dart'; +import 'package:cw_monero/api/wallet_manager.dart' as monero_wallet_manager; import 'package:cw_monero/pending_monero_transaction.dart'; import 'package:cw_monero/monero_wallet.dart'; import 'package:flutter/material.dart'; @@ -52,6 +53,8 @@ late WalletCreationService _walletCreationService; dynamic _walletInfoSource; Wallets? walletsService; +String path = ''; + String name = 'namee${Random().nextInt(10000000)}'; int nettype = 0; WalletType type = WalletType.monero; @@ -96,7 +99,7 @@ void main() async { // name = await generateName(); // } final dirPath = await pathForWalletDir(name: name, type: type); - final path = await pathForWallet(name: name, type: type); + path = await pathForWallet(name: name, type: type); credentials = // // creating a new wallet // monero.createMoneroNewWalletCredentials( @@ -163,23 +166,21 @@ void main() async { await walletBase!.getTransactionAddress(1, 2), mainnetTestData[1][2]); }); }); - + /* + // Not needed; only folder created, wallet files not saved yet. TODO test saving and deleting wallet files and make sure to clean up leftover folder afterwards group("Mainnet wallet deletion test", () { - test("Test mainnet wallet deletion", () async { + test("Test mainnet wallet existence", () { + expect(monero_wallet_manager.isWalletExistSync(path: path), true); + }); + + test("Test mainnet wallet deletion", () { // Remove wallet from wallet service walletService?.remove(name); walletsService?.removeWallet(walletId: name); - - // TODO test deletion, get code from generation for checking if it already exists + expect(monero_wallet_manager.isWalletExistSync(path: path), false); }); - - /* - // wait for widget tree to dispose of any widgets watching the manager - await Future.delayed(const Duration(seconds: 1)); - walletsInstance.removeWallet(walletId: walletId); - */ }); - /* + group("Mainnet node tests", () { test("Test mainnet node connection", () async { await walletBase?.connectToNode( From c2aeb5bae831eaa6baa964b66d9a821063889c80 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Fri, 14 Oct 2022 18:38:46 -0500 Subject: [PATCH 04/76] Add Wownero mainnet wallet address generation test --- .../coins/wownero/wownero_wallet_test.dart | 164 ++++++++++++++++++ .../wownero/wownero_wallet_test_data.dart | 14 ++ 2 files changed, 178 insertions(+) create mode 100644 test/services/coins/wownero/wownero_wallet_test.dart create mode 100644 test/services/coins/wownero/wownero_wallet_test_data.dart diff --git a/test/services/coins/wownero/wownero_wallet_test.dart b/test/services/coins/wownero/wownero_wallet_test.dart new file mode 100644 index 000000000..e64dd772c --- /dev/null +++ b/test/services/coins/wownero/wownero_wallet_test.dart @@ -0,0 +1,164 @@ +import 'dart:async'; +import 'dart:core'; +import 'dart:core' as core; +import 'dart:io'; +import 'dart:math'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:hive/hive.dart'; +import 'package:hive_test/hive_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'package:cw_core/monero_amount_format.dart'; +import 'package:cw_core/node.dart'; +import 'package:cw_core/pending_transaction.dart'; +import 'package:cw_core/unspent_coins_info.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_credentials.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_service.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:cw_wownero/api/wallet.dart'; +import 'package:cw_wownero/pending_wownero_transaction.dart'; +import 'package:cw_wownero/wownero_wallet.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_libmonero/core/key_service.dart'; +import 'package:flutter_libmonero/core/wallet_creation_service.dart'; +import 'package:flutter_libmonero/view_model/send/output.dart'; +import 'package:flutter_libmonero/wownero/wownero.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; +import 'package:hive/hive.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import 'wownero_wallet_test_data.dart'; + +FakeSecureStorage? storage; +WalletService? walletService; +SharedPreferences? prefs; +KeyService? keysStorage; +WowneroWalletBase? walletBase; +late WalletCreationService _walletCreationService; +dynamic _walletInfoSource; + +String path = ''; + +String name = 'namee${Random().nextInt(10000000)}'; +int nettype = 0; +WalletType type = WalletType.wownero; + +@GenerateMocks([]) +void main() async { + storage = FakeSecureStorage(); + prefs = await SharedPreferences.getInstance(); + keysStorage = KeyService(storage!); + WalletInfo walletInfo = WalletInfo.external( + id: '', + name: '', + type: type, + isRecovery: false, + restoreHeight: 0, + date: DateTime.now(), + path: '', + address: '', + dirPath: ''); + late WalletCredentials credentials; + + WidgetsFlutterBinding.ensureInitialized(); + Directory appDir = (await getApplicationDocumentsDirectory()); + if (Platform.isIOS) { + appDir = (await getLibraryDirectory()); + } + await Hive.close(); + Hive.init(appDir.path); + Hive.registerAdapter(NodeAdapter()); + Hive.registerAdapter(WalletInfoAdapter()); + Hive.registerAdapter(WalletTypeAdapter()); + Hive.registerAdapter(UnspentCoinsInfoAdapter()); + + wownero.onStartup(); + _walletInfoSource = await Hive.openBox(WalletInfo.boxName); + walletService = wownero.createWowneroWalletService(_walletInfoSource); + + group("Wownero tests", () { + setUp(() async { + try { + final dirPath = await pathForWalletDir(name: name, type: type); + path = await pathForWallet(name: name, type: type); + credentials = wownero.createWowneroRestoreWalletFromSeedCredentials( + name: name, height: 465760, mnemonic: testMnemonic); + + walletInfo = WalletInfo.external( + id: WalletBase.idFor(name, type), + name: name, + type: type, + isRecovery: false, + restoreHeight: credentials.height ?? 0, + date: DateTime.now(), + path: path, + address: "", + dirPath: dirPath); + credentials.walletInfo = walletInfo; + + _walletCreationService = WalletCreationService( + secureStorage: storage, + sharedPreferences: prefs, + walletService: walletService, + keyService: keysStorage, + ); + _walletCreationService.changeWalletType(); + } catch (e, s) { + print(e); + print(s); + } + }); + + test("Test mainnet address generation from seed", () async { + final wallet = await _walletCreationService.restoreFromSeed(credentials); + walletInfo.address = wallet.walletAddresses.address; + + await _walletInfoSource.add(walletInfo); + walletBase?.close(); + walletBase = wallet as WowneroWalletBase; + + expect(walletInfo.address, mainnetTestData[0][0]); + expect( + await walletBase!.getTransactionAddress(0, 0), mainnetTestData[0][0]); + expect( + await walletBase!.getTransactionAddress(0, 1), mainnetTestData[0][1]); + expect( + await walletBase!.getTransactionAddress(0, 2), mainnetTestData[0][2]); + expect( + await walletBase!.getTransactionAddress(1, 0), mainnetTestData[1][0]); + expect( + await walletBase!.getTransactionAddress(1, 1), mainnetTestData[1][1]); + expect( + await walletBase!.getTransactionAddress(1, 2), mainnetTestData[1][2]); + }); + }); +} + +Future pathForWalletDir( + {required String name, required WalletType type}) async { + Directory root = (await getApplicationDocumentsDirectory()); + if (Platform.isIOS) { + root = (await getLibraryDirectory()); + } + final prefix = walletTypeToString(type).toLowerCase(); + final walletsDir = Directory('${root.path}/wallets'); + final walletDire = Directory('${walletsDir.path}/$prefix/$name'); + + if (!walletDire.existsSync()) { + walletDire.createSync(recursive: true); + } + + return walletDire.path; +} + +Future pathForWallet( + {required String name, required WalletType type}) async => + await pathForWalletDir(name: name, type: type) + .then((path) => path + '/$name'); diff --git a/test/services/coins/wownero/wownero_wallet_test_data.dart b/test/services/coins/wownero/wownero_wallet_test_data.dart new file mode 100644 index 000000000..b0d93a448 --- /dev/null +++ b/test/services/coins/wownero/wownero_wallet_test_data.dart @@ -0,0 +1,14 @@ +String testMnemonic = + 'weather cruise school such silly profit clerk wage reduce obtain ill sand episode shadow'; +var mainnetTestData = [ + [ + 'Wo3jmHvTMLwE6h29fpgcb8PbJSpaKuqM7XTXVfiiu8bLCZsJvrQCbQSJR48Vo3BWNQKsMsXZ4VixndXTH25QtorC27NCjmsEi', + 'WW3K54QzmMFB1uTZh3LVvgQYqANLmX1FkJHLJ4sU1E7BQmp8nGizyBnjNXSgsjCa4BQ3Rw3GG5jw1ByUkaUjSywm2KmHAbFvK', + 'WW3e3F51KAojcSW2G5WimmE1WVFsbBHc6HppZFBa6dNiEn21cThXzdGGDbpv89aTKXSRSPSFaetK6HgCozYawaYz2knUi9Hmn' + ], + [ + 'WW2nx7MFruyN2CcXnGnMbDdvqsyZUGQthLWKYPkQ4iM9XCE54RyWVjNjgopryUbyi9WKzYhHDai2wENbh1Jh1UHa28CL72TYt', + 'WW34p57QBMoD6MEZVTu5u9R7G3KeYqvN4eYbvHLYsgbWXpLe992fBvVB7ANJNvaGmPg2uwY5oKjwKbpo4fDU6cGS231PmvXrZ', + 'WW2KQLLt6gjC9gRsC4NGehbAZX6UPU7sK89UQFwSg3NKj3MXPwnjh5BiJVqYYNQb6JNsfa7oP7eDjLagtLa2H6YP11RhUNQqw' + ] +]; From a6c380592e7e9a335ef033a13d9d0b5e2d21d6b1 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Fri, 4 Nov 2022 14:18:54 -0600 Subject: [PATCH 05/76] added conditional for desktop manual and restore backup --- .../create_backup_view.dart | 821 +++++++++--------- .../restore_from_file_view.dart | 537 ++++++------ 2 files changed, 695 insertions(+), 663 deletions(-) diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart index 9242c0482..b710aacf4 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart @@ -15,6 +15,7 @@ import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/progress_bar.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; @@ -93,426 +94,436 @@ class _RestoreFromFileViewState extends State { @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed(const Duration(milliseconds: 75)); - } - if (mounted) { - Navigator.of(context).pop(); - } - }, - ), - title: Text( - "Create backup", - style: STextStyles.navBarTitle(context), - ), - ), - body: Padding( - padding: const EdgeInsets.all(16), - child: LayoutBuilder( - builder: (context, constraints) { - return SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight, - ), - child: IntrinsicHeight( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - if (!Platform.isAndroid) - Consumer(builder: (context, ref, __) { - return Container( - color: Colors.transparent, - child: TextField( - autocorrect: Util.isDesktop ? false : true, - enableSuggestions: Util.isDesktop ? false : true, - onTap: Platform.isAndroid - ? null - : () async { - try { - await stackFileSystem.prepareStorage(); + final isDesktop = Util.isDesktop; - if (mounted) { - await stackFileSystem - .pickDir(context); - } + return ConditionalParent( + condition: !isDesktop, + builder: (child) { + return Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed(const Duration(milliseconds: 75)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + title: Text( + "Create backup", + style: STextStyles.navBarTitle(context), + ), + ), + body: Padding( + padding: const EdgeInsets.all(16), + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( + child: child, + ), + ), + ); + }, + ), + ), + ); + }, + child: ConditionalParent( + condition: isDesktop, + builder: (child) { + return Column( + children: [ + Text( + "Choose file location", + style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: + Theme.of(context).extension()!.textDark3), + ), + // child, + ], + ); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (!Platform.isAndroid) + Consumer(builder: (context, ref, __) { + return Container( + color: Colors.transparent, + child: TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + onTap: Platform.isAndroid + ? null + : () async { + try { + await stackFileSystem.prepareStorage(); - if (mounted) { - setState(() { - fileLocationController.text = - stackFileSystem.dirPath ?? ""; - }); - } - } catch (e, s) { - Logging.instance.log("$e\n$s", - level: LogLevel.Error); - } - }, - controller: fileLocationController, - style: STextStyles.field(context), - decoration: InputDecoration( - hintText: "Save to...", - hintStyle: STextStyles.fieldLabel(context), - suffixIcon: UnconstrainedBox( - child: Row( - children: [ - const SizedBox( - width: 16, - ), - SvgPicture.asset( - Assets.svg.folder, - color: Theme.of(context) - .extension()! - .textDark3, - width: 16, - height: 16, - ), - const SizedBox( - width: 12, - ), - ], - ), - ), - ), - key: const Key( - "createBackupSaveToFileLocationTextFieldKey"), - readOnly: true, - toolbarOptions: const ToolbarOptions( - copy: true, - cut: false, - paste: false, - selectAll: false, - ), - onChanged: (newValue) { - // ref.read(addressEntryDataProvider(widget.id)).address = newValue; - }, + if (mounted) { + await stackFileSystem.pickDir(context); + } + + if (mounted) { + setState(() { + fileLocationController.text = + stackFileSystem.dirPath ?? ""; + }); + } + } catch (e, s) { + Logging.instance + .log("$e\n$s", level: LogLevel.Error); + } + }, + controller: fileLocationController, + style: STextStyles.field(context), + decoration: InputDecoration( + hintText: "Save to...", + hintStyle: STextStyles.fieldLabel(context), + suffixIcon: UnconstrainedBox( + child: Row( + children: [ + const SizedBox( + width: 16, ), - ); - }), - if (!Platform.isAndroid) + SvgPicture.asset( + Assets.svg.folder, + color: Theme.of(context) + .extension()! + .textDark3, + width: 16, + height: 16, + ), + const SizedBox( + width: 12, + ), + ], + ), + ), + ), + key: + const Key("createBackupSaveToFileLocationTextFieldKey"), + readOnly: true, + toolbarOptions: const ToolbarOptions( + copy: true, + cut: false, + paste: false, + selectAll: false, + ), + onChanged: (newValue) { + // ref.read(addressEntryDataProvider(widget.id)).address = newValue; + }, + ), + ); + }), + if (!Platform.isAndroid) + const SizedBox( + height: 8, + ), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + key: const Key("createBackupPasswordFieldKey1"), + focusNode: passwordFocusNode, + controller: passwordController, + style: STextStyles.field(context), + obscureText: hidePassword, + enableSuggestions: false, + autocorrect: false, + decoration: standardInputDecoration( + "Create passphrase", + passwordFocusNode, + context, + ).copyWith( + suffixIcon: UnconstrainedBox( + child: Row( + children: [ const SizedBox( - height: 8, + width: 16, ), - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - key: const Key("createBackupPasswordFieldKey1"), - focusNode: passwordFocusNode, - controller: passwordController, - style: STextStyles.field(context), - obscureText: hidePassword, - enableSuggestions: false, - autocorrect: false, - decoration: standardInputDecoration( - "Create passphrase", - passwordFocusNode, - context, - ).copyWith( - suffixIcon: UnconstrainedBox( - child: Row( - children: [ - const SizedBox( - width: 16, - ), - GestureDetector( - key: const Key( - "createBackupPasswordFieldShowPasswordButtonKey"), - onTap: () async { - setState(() { - hidePassword = !hidePassword; - }); - }, - child: SvgPicture.asset( - hidePassword - ? Assets.svg.eye - : Assets.svg.eyeSlash, - color: Theme.of(context) - .extension()! - .textDark3, - width: 16, - height: 16, - ), - ), - const SizedBox( - width: 12, - ), - ], - ), - ), - ), - onChanged: (newValue) { - if (newValue.isEmpty) { - setState(() { - passwordFeedback = ""; - }); - return; - } - final result = zxcvbn.evaluate(newValue); - String suggestionsAndTips = ""; - for (var sug - in result.feedback.suggestions!.toSet()) { - suggestionsAndTips += "$sug\n"; - } - suggestionsAndTips += result.feedback.warning!; - String feedback = - // "Password Strength: ${((result.score! / 4.0) * 100).toInt()}%\n" - suggestionsAndTips; - - passwordStrength = result.score! / 4; - - // hack fix to format back string returned from zxcvbn - if (feedback.contains("phrasesNo need")) { - feedback = feedback.replaceFirst( - "phrasesNo need", "phrases\nNo need"); - } - - if (feedback.endsWith("\n")) { - feedback = - feedback.substring(0, feedback.length - 2); - } - + GestureDetector( + key: const Key( + "createBackupPasswordFieldShowPasswordButtonKey"), + onTap: () async { setState(() { - passwordFeedback = feedback; + hidePassword = !hidePassword; }); }, - ), - ), - if (passwordFocusNode.hasFocus || - passwordRepeatFocusNode.hasFocus || - passwordController.text.isNotEmpty) - Padding( - padding: EdgeInsets.only( - left: 12, - right: 12, - top: passwordFeedback.isNotEmpty ? 4 : 0, - ), - child: passwordFeedback.isNotEmpty - ? Text( - passwordFeedback, - style: STextStyles.infoSmall(context), - ) - : null, - ), - if (passwordFocusNode.hasFocus || - passwordRepeatFocusNode.hasFocus || - passwordController.text.isNotEmpty) - Padding( - padding: const EdgeInsets.only( - left: 12, - right: 12, - top: 10, - ), - child: ProgressBar( - key: const Key("createStackBackUpProgressBar"), - width: MediaQuery.of(context).size.width - 32 - 24, - height: 5, - fillColor: passwordStrength < 0.51 - ? Theme.of(context) - .extension()! - .accentColorRed - : passwordStrength < 1 - ? Theme.of(context) - .extension()! - .accentColorYellow - : Theme.of(context) - .extension()! - .accentColorGreen, - backgroundColor: Theme.of(context) + child: SvgPicture.asset( + hidePassword ? Assets.svg.eye : Assets.svg.eyeSlash, + color: Theme.of(context) .extension()! - .buttonBackSecondary, - percent: passwordStrength < 0.25 - ? 0.03 - : passwordStrength, + .textDark3, + width: 16, + height: 16, ), ), - const SizedBox( - height: 10, - ), - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, + const SizedBox( + width: 12, ), - child: TextField( - key: const Key("createBackupPasswordFieldKey2"), - focusNode: passwordRepeatFocusNode, - controller: passwordRepeatController, - style: STextStyles.field(context), - obscureText: hidePassword, - enableSuggestions: false, - autocorrect: false, - decoration: standardInputDecoration( - "Confirm passphrase", - passwordRepeatFocusNode, - context, - ).copyWith( - suffixIcon: UnconstrainedBox( - child: Row( - children: [ - const SizedBox( - width: 16, - ), - GestureDetector( - key: const Key( - "createBackupPasswordFieldShowPasswordButtonKey"), - onTap: () async { - setState(() { - hidePassword = !hidePassword; - }); - }, - child: SvgPicture.asset( - hidePassword - ? Assets.svg.eye - : Assets.svg.eyeSlash, - color: Theme.of(context) - .extension()! - .textDark3, - width: 16, - height: 16, - ), - ), - const SizedBox( - width: 12, - ), - ], - ), - ), - ), - onChanged: (newValue) { - setState(() {}); - // TODO: ? check if passwords match? - }, - ), - ), - const SizedBox( - height: 16, - ), - const Spacer(), - TextButton( - style: shouldEnableCreate - ? Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context) - : Theme.of(context) - .extension()! - .getPrimaryDisabledButtonColor(context), - onPressed: !shouldEnableCreate - ? null - : () async { - final String pathToSave = - fileLocationController.text; - final String passphrase = - passwordController.text; - final String repeatPassphrase = - passwordRepeatController.text; - - if (pathToSave.isEmpty) { - unawaited(showFloatingFlushBar( - type: FlushBarType.warning, - message: "Directory not chosen", - context: context, - )); - return; - } - if (!(await Directory(pathToSave).exists())) { - unawaited(showFloatingFlushBar( - type: FlushBarType.warning, - message: "Directory does not exist", - context: context, - )); - return; - } - if (passphrase.isEmpty) { - unawaited(showFloatingFlushBar( - type: FlushBarType.warning, - message: "A passphrase is required", - context: context, - )); - return; - } - if (passphrase != repeatPassphrase) { - unawaited(showFloatingFlushBar( - type: FlushBarType.warning, - message: "Passphrase does not match", - context: context, - )); - return; - } - - unawaited(showDialog( - context: context, - barrierDismissible: false, - builder: (_) => const StackDialog( - title: "Encrypting backup", - message: "This shouldn't take long", - ), - )); - // make sure the dialog is able to be displayed for at least 1 second - await Future.delayed( - const Duration(seconds: 1)); - - final DateTime now = DateTime.now(); - final String fileToSave = - "$pathToSave/stackbackup_${now.year}_${now.month}_${now.day}_${now.hour}_${now.minute}_${now.second}.swb"; - - final backup = - await SWB.createStackWalletJSON(); - - bool result = - await SWB.encryptStackWalletWithPassphrase( - fileToSave, - passphrase, - jsonEncode(backup), - ); - - if (mounted) { - // pop encryption progress dialog - Navigator.of(context).pop(); - - if (result) { - await showDialog( - context: context, - barrierDismissible: false, - builder: (_) => Platform.isAndroid - ? StackOkDialog( - title: "Backup saved to:", - message: fileToSave, - ) - : const StackOkDialog( - title: - "Backup creation succeeded"), - ); - passwordController.text = ""; - passwordRepeatController.text = ""; - setState(() {}); - } else { - await showDialog( - context: context, - barrierDismissible: false, - builder: (_) => const StackOkDialog( - title: "Backup creation failed"), - ); - } - } - }, - child: Text( - "Create backup", - style: STextStyles.button(context), - ), - ), - ], + ], + ), ), ), + onChanged: (newValue) { + if (newValue.isEmpty) { + setState(() { + passwordFeedback = ""; + }); + return; + } + final result = zxcvbn.evaluate(newValue); + String suggestionsAndTips = ""; + for (var sug in result.feedback.suggestions!.toSet()) { + suggestionsAndTips += "$sug\n"; + } + suggestionsAndTips += result.feedback.warning!; + String feedback = + // "Password Strength: ${((result.score! / 4.0) * 100).toInt()}%\n" + suggestionsAndTips; + + passwordStrength = result.score! / 4; + + // hack fix to format back string returned from zxcvbn + if (feedback.contains("phrasesNo need")) { + feedback = feedback.replaceFirst( + "phrasesNo need", "phrases\nNo need"); + } + + if (feedback.endsWith("\n")) { + feedback = feedback.substring(0, feedback.length - 2); + } + + setState(() { + passwordFeedback = feedback; + }); + }, ), - ); - }, + ), + if (passwordFocusNode.hasFocus || + passwordRepeatFocusNode.hasFocus || + passwordController.text.isNotEmpty) + Padding( + padding: EdgeInsets.only( + left: 12, + right: 12, + top: passwordFeedback.isNotEmpty ? 4 : 0, + ), + child: passwordFeedback.isNotEmpty + ? Text( + passwordFeedback, + style: STextStyles.infoSmall(context), + ) + : null, + ), + if (passwordFocusNode.hasFocus || + passwordRepeatFocusNode.hasFocus || + passwordController.text.isNotEmpty) + Padding( + padding: const EdgeInsets.only( + left: 12, + right: 12, + top: 10, + ), + child: ProgressBar( + key: const Key("createStackBackUpProgressBar"), + width: MediaQuery.of(context).size.width - 32 - 24, + height: 5, + fillColor: passwordStrength < 0.51 + ? Theme.of(context) + .extension()! + .accentColorRed + : passwordStrength < 1 + ? Theme.of(context) + .extension()! + .accentColorYellow + : Theme.of(context) + .extension()! + .accentColorGreen, + backgroundColor: Theme.of(context) + .extension()! + .buttonBackSecondary, + percent: passwordStrength < 0.25 ? 0.03 : passwordStrength, + ), + ), + const SizedBox( + height: 10, + ), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + key: const Key("createBackupPasswordFieldKey2"), + focusNode: passwordRepeatFocusNode, + controller: passwordRepeatController, + style: STextStyles.field(context), + obscureText: hidePassword, + enableSuggestions: false, + autocorrect: false, + decoration: standardInputDecoration( + "Confirm passphrase", + passwordRepeatFocusNode, + context, + ).copyWith( + suffixIcon: UnconstrainedBox( + child: Row( + children: [ + const SizedBox( + width: 16, + ), + GestureDetector( + key: const Key( + "createBackupPasswordFieldShowPasswordButtonKey"), + onTap: () async { + setState(() { + hidePassword = !hidePassword; + }); + }, + child: SvgPicture.asset( + hidePassword ? Assets.svg.eye : Assets.svg.eyeSlash, + color: Theme.of(context) + .extension()! + .textDark3, + width: 16, + height: 16, + ), + ), + const SizedBox( + width: 12, + ), + ], + ), + ), + ), + onChanged: (newValue) { + setState(() {}); + // TODO: ? check if passwords match? + }, + ), + ), + const SizedBox( + height: 16, + ), + const Spacer(), + TextButton( + style: shouldEnableCreate + ? Theme.of(context) + .extension()! + .getPrimaryEnabledButtonColor(context) + : Theme.of(context) + .extension()! + .getPrimaryDisabledButtonColor(context), + onPressed: !shouldEnableCreate + ? null + : () async { + final String pathToSave = fileLocationController.text; + final String passphrase = passwordController.text; + final String repeatPassphrase = + passwordRepeatController.text; + + if (pathToSave.isEmpty) { + unawaited(showFloatingFlushBar( + type: FlushBarType.warning, + message: "Directory not chosen", + context: context, + )); + return; + } + if (!(await Directory(pathToSave).exists())) { + unawaited(showFloatingFlushBar( + type: FlushBarType.warning, + message: "Directory does not exist", + context: context, + )); + return; + } + if (passphrase.isEmpty) { + unawaited(showFloatingFlushBar( + type: FlushBarType.warning, + message: "A passphrase is required", + context: context, + )); + return; + } + if (passphrase != repeatPassphrase) { + unawaited(showFloatingFlushBar( + type: FlushBarType.warning, + message: "Passphrase does not match", + context: context, + )); + return; + } + + unawaited(showDialog( + context: context, + barrierDismissible: false, + builder: (_) => const StackDialog( + title: "Encrypting backup", + message: "This shouldn't take long", + ), + )); + // make sure the dialog is able to be displayed for at least 1 second + await Future.delayed(const Duration(seconds: 1)); + + final DateTime now = DateTime.now(); + final String fileToSave = + "$pathToSave/stackbackup_${now.year}_${now.month}_${now.day}_${now.hour}_${now.minute}_${now.second}.swb"; + + final backup = await SWB.createStackWalletJSON(); + + bool result = await SWB.encryptStackWalletWithPassphrase( + fileToSave, + passphrase, + jsonEncode(backup), + ); + + if (mounted) { + // pop encryption progress dialog + Navigator.of(context).pop(); + + if (result) { + await showDialog( + context: context, + barrierDismissible: false, + builder: (_) => Platform.isAndroid + ? StackOkDialog( + title: "Backup saved to:", + message: fileToSave, + ) + : const StackOkDialog( + title: "Backup creation succeeded"), + ); + passwordController.text = ""; + passwordRepeatController.text = ""; + setState(() {}); + } else { + await showDialog( + context: context, + barrierDismissible: false, + builder: (_) => const StackOkDialog( + title: "Backup creation failed"), + ); + } + } + }, + child: Text( + "Create backup", + style: STextStyles.button(context), + ), + ), + ], ), ), ); diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart index 232be9028..16c3ea8e3 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart @@ -15,13 +15,13 @@ import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/loading_indicator.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:tuple/tuple.dart'; -import 'package:stackwallet/utilities/util.dart'; - class RestoreFromFileView extends ConsumerStatefulWidget { const RestoreFromFileView({Key? key}) : super(key: key); @@ -65,275 +65,296 @@ class _RestoreFromFileViewState extends ConsumerState { @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed(const Duration(milliseconds: 75)); - } - if (mounted) { - Navigator.of(context).pop(); - } - }, - ), - title: Text( - "Restore from file", - style: STextStyles.navBarTitle(context), - ), - ), - body: Padding( - padding: const EdgeInsets.all(16), - child: LayoutBuilder( - builder: (context, constraints) { - return SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight, - ), - child: IntrinsicHeight( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - TextField( - autocorrect: Util.isDesktop ? false : true, - enableSuggestions: Util.isDesktop ? false : true, - onTap: () async { - try { - await stackFileSystem.prepareStorage(); - if (mounted) { - await stackFileSystem.openFile(context); - } + final isDesktop = Util.isDesktop; - if (mounted) { + return ConditionalParent( + condition: !isDesktop, + builder: (child) { + return Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration(milliseconds: 75)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + title: Text( + "Restore from file", + style: STextStyles.navBarTitle(context), + ), + ), + body: Padding( + padding: const EdgeInsets.all(16), + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( + child: child, + ), + ), + ); + }, + ), + ), + ); + }, + child: ConditionalParent( + condition: isDesktop, + builder: (child) { + return Column( + children: [ + Text( + "Choose file location", + style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textDark3), + ), + // child, + ], + ); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + onTap: () async { + try { + await stackFileSystem.prepareStorage(); + if (mounted) { + await stackFileSystem.openFile(context); + } + + if (mounted) { + setState(() { + fileLocationController.text = + stackFileSystem.filePath ?? ""; + }); + } + } catch (e, s) { + Logging.instance.log("$e\n$s", level: LogLevel.Error); + } + }, + controller: fileLocationController, + style: STextStyles.field(context), + decoration: InputDecoration( + hintText: "Choose file...", + hintStyle: STextStyles.fieldLabel(context), + suffixIcon: UnconstrainedBox( + child: Row( + children: [ + const SizedBox( + width: 16, + ), + SvgPicture.asset( + Assets.svg.folder, + color: Theme.of(context) + .extension()! + .textDark3, + width: 16, + height: 16, + ), + const SizedBox( + width: 12, + ), + ], + ), + ), + ), + key: const Key("restoreFromFileLocationTextFieldKey"), + readOnly: true, + toolbarOptions: const ToolbarOptions( + copy: true, + cut: false, + paste: false, + selectAll: false, + ), + onChanged: (newValue) {}, + ), + const SizedBox( + height: 8, + ), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + key: const Key("restoreFromFilePasswordFieldKey"), + focusNode: passwordFocusNode, + controller: passwordController, + style: STextStyles.field(context), + obscureText: hidePassword, + enableSuggestions: false, + autocorrect: false, + decoration: standardInputDecoration( + "Enter password", + passwordFocusNode, + context, + ).copyWith( + suffixIcon: UnconstrainedBox( + child: Row( + children: [ + const SizedBox( + width: 16, + ), + GestureDetector( + key: const Key( + "restoreFromFilePasswordFieldShowPasswordButtonKey"), + onTap: () async { setState(() { - fileLocationController.text = - stackFileSystem.filePath ?? ""; + hidePassword = !hidePassword; }); - } - } catch (e, s) { - Logging.instance - .log("$e\n$s", level: LogLevel.Error); - } - }, - controller: fileLocationController, - style: STextStyles.field(context), - decoration: InputDecoration( - hintText: "Choose file...", - hintStyle: STextStyles.fieldLabel(context), - suffixIcon: UnconstrainedBox( - child: Row( + }, + child: SvgPicture.asset( + hidePassword + ? Assets.svg.eye + : Assets.svg.eyeSlash, + color: Theme.of(context) + .extension()! + .textDark3, + width: 16, + height: 16, + ), + ), + const SizedBox( + width: 12, + ), + ], + ), + ), + ), + onChanged: (newValue) { + setState(() {}); + }, + ), + ), + const SizedBox( + height: 16, + ), + const Spacer(), + TextButton( + style: passwordController.text.isEmpty || + fileLocationController.text.isEmpty + ? Theme.of(context) + .extension()! + .getPrimaryDisabledButtonColor(context) + : Theme.of(context) + .extension()! + .getPrimaryEnabledButtonColor(context), + onPressed: passwordController.text.isEmpty || + fileLocationController.text.isEmpty + ? null + : () async { + final String fileToRestore = + fileLocationController.text; + final String passphrase = passwordController.text; + + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration(milliseconds: 75)); + } + + if (!(await File(fileToRestore).exists())) { + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Backup file does not exist", + context: context, + ); + return; + } + + bool shouldPop = false; + showDialog( + barrierDismissible: false, + context: context, + builder: (_) => WillPopScope( + onWillPop: () async { + return shouldPop; + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisAlignment: MainAxisAlignment.center, children: [ - const SizedBox( - width: 16, - ), - SvgPicture.asset( - Assets.svg.folder, - color: Theme.of(context) - .extension()! - .textDark3, - width: 16, - height: 16, + Material( + color: Colors.transparent, + child: Center( + child: Text( + "Decrypting Stack backup file", + style: STextStyles.pageTitleH2(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textWhite, + ), + ), + ), ), const SizedBox( - width: 12, + height: 64, + ), + const Center( + child: LoadingIndicator( + width: 100, + ), ), ], ), ), - ), - key: const Key("restoreFromFileLocationTextFieldKey"), - readOnly: true, - toolbarOptions: const ToolbarOptions( - copy: true, - cut: false, - paste: false, - selectAll: false, - ), - onChanged: (newValue) {}, - ), - const SizedBox( - height: 8, - ), - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - key: const Key("restoreFromFilePasswordFieldKey"), - focusNode: passwordFocusNode, - controller: passwordController, - style: STextStyles.field(context), - obscureText: hidePassword, - enableSuggestions: false, - autocorrect: false, - decoration: standardInputDecoration( - "Enter password", - passwordFocusNode, - context, - ).copyWith( - suffixIcon: UnconstrainedBox( - child: Row( - children: [ - const SizedBox( - width: 16, - ), - GestureDetector( - key: const Key( - "restoreFromFilePasswordFieldShowPasswordButtonKey"), - onTap: () async { - setState(() { - hidePassword = !hidePassword; - }); - }, - child: SvgPicture.asset( - hidePassword - ? Assets.svg.eye - : Assets.svg.eyeSlash, - color: Theme.of(context) - .extension()! - .textDark3, - width: 16, - height: 16, - ), - ), - const SizedBox( - width: 12, - ), - ], + ); + + final String? jsonString = await compute( + SWB.decryptStackWalletWithPassphrase, + Tuple2(fileToRestore, passphrase), + debugLabel: "stack wallet decryption compute", + ); + + if (mounted) { + // pop LoadingIndicator + shouldPop = true; + Navigator.of(context).pop(); + + passwordController.text = ""; + + if (jsonString == null) { + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Failed to decrypt backup file", + context: context, + ); + return; + } + + Navigator.of(context).push( + RouteGenerator.getRoute( + builder: (_) => StackRestoreProgressView( + jsonString: jsonString, ), ), - ), - onChanged: (newValue) { - setState(() {}); - }, - ), - ), - const SizedBox( - height: 16, - ), - const Spacer(), - TextButton( - style: passwordController.text.isEmpty || - fileLocationController.text.isEmpty - ? Theme.of(context) - .extension()! - .getPrimaryDisabledButtonColor(context) - : Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context), - onPressed: passwordController.text.isEmpty || - fileLocationController.text.isEmpty - ? null - : () async { - final String fileToRestore = - fileLocationController.text; - final String passphrase = - passwordController.text; - - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed( - const Duration(milliseconds: 75)); - } - - if (!(await File(fileToRestore).exists())) { - showFloatingFlushBar( - type: FlushBarType.warning, - message: "Backup file does not exist", - context: context, - ); - return; - } - - bool shouldPop = false; - showDialog( - barrierDismissible: false, - context: context, - builder: (_) => WillPopScope( - onWillPop: () async { - return shouldPop; - }, - child: Column( - crossAxisAlignment: - CrossAxisAlignment.stretch, - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - Material( - color: Colors.transparent, - child: Center( - child: Text( - "Decrypting Stack backup file", - style: STextStyles.pageTitleH2( - context) - .copyWith( - color: Theme.of(context) - .extension()! - .textWhite, - ), - ), - ), - ), - const SizedBox( - height: 64, - ), - const Center( - child: LoadingIndicator( - width: 100, - ), - ), - ], - ), - ), - ); - - final String? jsonString = await compute( - SWB.decryptStackWalletWithPassphrase, - Tuple2(fileToRestore, passphrase), - debugLabel: "stack wallet decryption compute", - ); - - if (mounted) { - // pop LoadingIndicator - shouldPop = true; - Navigator.of(context).pop(); - - passwordController.text = ""; - - if (jsonString == null) { - showFloatingFlushBar( - type: FlushBarType.warning, - message: "Failed to decrypt backup file", - context: context, - ); - return; - } - - Navigator.of(context).push( - RouteGenerator.getRoute( - builder: (_) => StackRestoreProgressView( - jsonString: jsonString, - ), - ), - ); - } - }, - child: Text( - "Restore", - style: STextStyles.button(context), - ), - ), - ], - ), + ); + } + }, + child: Text( + "Restore", + style: STextStyles.button(context), ), ), - ); - }, - ), - ), - ); + ], + ), + )); } } From 2c935d65b6380a34a174b32db287190e52dd20e1 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Fri, 4 Nov 2022 14:19:53 -0600 Subject: [PATCH 06/76] WIP: showing textfields for backups --- .../backup_and_restore_settings.dart | 341 +++++++++--------- 1 file changed, 179 insertions(+), 162 deletions(-) diff --git a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart index 8b74f8d40..d5ad6e6c7 100644 --- a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart @@ -2,6 +2,8 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart'; +import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/settings_menu/backup_and_restore/enable_backup_dialog.dart'; import 'package:stackwallet/pages_desktop_specific/home/settings_menu/backup_and_restore/restore_backup_dialog.dart'; import 'package:stackwallet/utilities/assets.dart'; @@ -24,193 +26,208 @@ class _BackupRestoreSettings extends ConsumerState { @override Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); - return ListView( - shrinkWrap: true, - scrollDirection: Axis.vertical, - children: [ - Padding( - padding: const EdgeInsets.only( - right: 30, - ), - child: RoundedWhiteContainer( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SvgPicture.asset( - Assets.svg.backupAuto, - width: 48, - height: 48, - ), - Center( - child: Padding( - padding: const EdgeInsets.all(10), - child: RichText( - textAlign: TextAlign.start, - text: TextSpan( + + return LayoutBuilder(builder: (context, constraints) { + return SingleChildScrollView( + scrollDirection: Axis.vertical, + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only( + right: 30, + ), + child: RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - TextSpan( - text: "Auto Backup", - style: STextStyles.desktopTextSmall(context), + SvgPicture.asset( + Assets.svg.backupAuto, + width: 48, + height: 48, ), - TextSpan( - text: - "\n\nAuto backup is a custom Stack Wallet feature that offers a convenient backup of your data." - "To ensure maximum security, we recommend using a unique password that you haven't used anywhere " - "else on the internet before. Your password is not stored.", - style: - STextStyles.desktopTextExtraExtraSmall(context), + Center( + child: Padding( + padding: const EdgeInsets.all(10), + child: RichText( + textAlign: TextAlign.start, + text: TextSpan( + children: [ + TextSpan( + text: "Auto Backup", + style: + STextStyles.desktopTextSmall(context), + ), + TextSpan( + text: + "\n\nAuto backup is a custom Stack Wallet feature that offers a convenient backup of your data." + "To ensure maximum security, we recommend using a unique password that you haven't used anywhere " + "else on the internet before. Your password is not stored.", + style: STextStyles + .desktopTextExtraExtraSmall(context), + ), + TextSpan( + text: + "\n\nFor more information, please see our website ", + style: STextStyles + .desktopTextExtraExtraSmall(context), + ), + TextSpan( + text: "stackwallet.com", + style: STextStyles.richLink(context) + .copyWith(fontSize: 14), + recognizer: TapGestureRecognizer() + ..onTap = () { + launchUrl( + Uri.parse( + "https://stackwallet.com/"), + mode: + LaunchMode.externalApplication, + ); + }, + ), + ], + ), + ), + ), ), - TextSpan( - text: - "\n\nFor more information, please see our website ", - style: - STextStyles.desktopTextExtraExtraSmall(context), - ), - TextSpan( - text: "stackwallet.com", - style: STextStyles.richLink(context) - .copyWith(fontSize: 14), - recognizer: TapGestureRecognizer() - ..onTap = () { - launchUrl( - Uri.parse("https://stackwallet.com/"), - mode: LaunchMode.externalApplication, - ); - }, + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: const [ + Padding( + padding: EdgeInsets.all( + 10, + ), + child: AutoBackupButton(), + ), + ], ), ], ), ), ), - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: const [ - Padding( - padding: EdgeInsets.all( - 10, - ), - child: AutoBackupButton(), + const SizedBox( + height: 25, + ), + Padding( + padding: const EdgeInsets.only( + right: 30, ), - ], - ), - ], - ), - ), - ), - const SizedBox( - height: 25, - ), - Padding( - padding: const EdgeInsets.only( - right: 30, - ), - child: RoundedWhiteContainer( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SvgPicture.asset( - Assets.svg.backupAdd, - width: 48, - height: 48, - alignment: Alignment.topLeft, - ), - Center( - child: Padding( - padding: const EdgeInsets.all(10), - child: RichText( - textAlign: TextAlign.start, - text: TextSpan( + child: RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - TextSpan( - text: "Manual Backup", - style: STextStyles.desktopTextSmall(context), + SvgPicture.asset( + Assets.svg.backupAdd, + width: 48, + height: 48, + alignment: Alignment.topLeft, ), - TextSpan( - text: - "\n\nCreate manual backup to easily transfer your data between devices. " - "You will create a backup file that can be later used in the Restore option. " - "Use a strong password to encrypt your data.", - style: - STextStyles.desktopTextExtraExtraSmall(context), + Center( + child: Padding( + padding: const EdgeInsets.all(10), + child: RichText( + textAlign: TextAlign.start, + text: TextSpan( + children: [ + TextSpan( + text: "Manual Backup", + style: + STextStyles.desktopTextSmall(context), + ), + TextSpan( + text: + "\n\nCreate manual backup to easily transfer your data between devices. " + "You will create a backup file that can be later used in the Restore option. " + "Use a strong password to encrypt your data.", + style: STextStyles + .desktopTextExtraExtraSmall(context), + ), + ], + ), + ), + ), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: const [ + Padding( + padding: EdgeInsets.all( + 10, + ), + child: CreateBackupView(), + ), + ], ), ], ), ), ), - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: const [ - Padding( - padding: EdgeInsets.all( - 10, - ), - child: ManualBackupButton(), + const SizedBox( + height: 25, + ), + Padding( + padding: const EdgeInsets.only( + right: 30, ), - ], - ), - ], - ), - ), - ), - const SizedBox( - height: 25, - ), - Padding( - padding: const EdgeInsets.only( - right: 30, - ), - child: RoundedWhiteContainer( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SvgPicture.asset( - Assets.svg.backupRestore, - width: 48, - height: 48, - alignment: Alignment.topLeft, - ), - Center( - child: Padding( - padding: const EdgeInsets.all(10), - child: RichText( - textAlign: TextAlign.start, - text: TextSpan( + child: RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - TextSpan( - text: "Restore Backup", - style: STextStyles.desktopTextSmall(context), + SvgPicture.asset( + Assets.svg.backupRestore, + width: 48, + height: 48, + alignment: Alignment.topLeft, ), - TextSpan( - text: - "\n\nUse your Stack Wallet backup file to restore your wallets, address book " - "and wallet preferences.", - style: - STextStyles.desktopTextExtraExtraSmall(context), + Center( + child: Padding( + padding: const EdgeInsets.all(10), + child: RichText( + textAlign: TextAlign.start, + text: TextSpan( + children: [ + TextSpan( + text: "Restore Backup", + style: + STextStyles.desktopTextSmall(context), + ), + TextSpan( + text: + "\n\nUse your Stack Wallet backup file to restore your wallets, address book " + "and wallet preferences.", + style: STextStyles + .desktopTextExtraExtraSmall(context), + ), + ], + ), + ), + ), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: const [ + Padding( + padding: EdgeInsets.all( + 10, + ), + child: RestoreFromFileView(), + ), + ], ), ], ), ), ), - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: const [ - Padding( - padding: EdgeInsets.all( - 10, - ), - child: RestoreBackupButton(), - ), - ], - ), - ], + ], + ), ), - ), - ), - ], - ); + )); + }); } } From 905e396a17f06813224dc2278df8a0ef37fdba20 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Fri, 4 Nov 2022 14:21:09 -0600 Subject: [PATCH 07/76] implemented clear logs on press --- .../advanced_settings/debug_info_dialog.dart | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/lib/pages_desktop_specific/home/settings_menu/advanced_settings/debug_info_dialog.dart b/lib/pages_desktop_specific/home/settings_menu/advanced_settings/debug_info_dialog.dart index 0406a059f..cf687e3e7 100644 --- a/lib/pages_desktop_specific/home/settings_menu/advanced_settings/debug_info_dialog.dart +++ b/lib/pages_desktop_specific/home/settings_menu/advanced_settings/debug_info_dialog.dart @@ -1,10 +1,14 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/models/isar/models/log.dart'; +import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/providers/global/debug_service_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/enums/log_level_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; @@ -105,7 +109,7 @@ class _DebugInfoDialog extends ConsumerState { ], ), Expanded( - flex: 24, + // flex: 24, child: NestedScrollView( floatHeaderSlivers: true, headerSliverBuilder: (context, innerBoxIsScrolled) { @@ -314,7 +318,7 @@ class _DebugInfoDialog extends ConsumerState { ), ), ), - const Spacer(), + // const Spacer(), Padding( padding: const EdgeInsets.all(32), child: Row( @@ -322,7 +326,18 @@ class _DebugInfoDialog extends ConsumerState { Expanded( child: SecondaryButton( label: "Clear logs", - onPressed: () {}, + onPressed: () async { + await ref.read(debugServiceProvider).deleteAllMessages(); + await ref.read(debugServiceProvider).updateRecentLogs(); + + if (mounted) { + Navigator.pop(context); + unawaited(showFloatingFlushBar( + type: FlushBarType.info, + context: context, + message: 'Logs cleared!')); + } + }, ), ), const SizedBox( From ac0d4191c6a2a6fd62da1a02b9fde9679be8aee9 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Fri, 4 Nov 2022 16:34:36 -0600 Subject: [PATCH 08/76] added button widgets and some containers to restore dialog --- assets/svg/framed-address-book.svg | 4 + assets/svg/framed-gear.svg | 4 + .../backup_and_restore_settings.dart | 154 ++++-------- .../restore_backup_dialog.dart | 230 ++++++++++++------ lib/utilities/assets.dart | 2 + pubspec.yaml | 2 + 6 files changed, 217 insertions(+), 179 deletions(-) create mode 100644 assets/svg/framed-address-book.svg create mode 100644 assets/svg/framed-gear.svg diff --git a/assets/svg/framed-address-book.svg b/assets/svg/framed-address-book.svg new file mode 100644 index 000000000..157117097 --- /dev/null +++ b/assets/svg/framed-address-book.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/svg/framed-gear.svg b/assets/svg/framed-gear.svg new file mode 100644 index 000000000..749d9803d --- /dev/null +++ b/assets/svg/framed-gear.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart index d5ad6e6c7..b59206f17 100644 --- a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart @@ -5,10 +5,9 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/settings_menu/backup_and_restore/enable_backup_dialog.dart'; -import 'package:stackwallet/pages_desktop_specific/home/settings_menu/backup_and_restore/restore_backup_dialog.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -23,6 +22,20 @@ class BackupRestoreSettings extends ConsumerStatefulWidget { } class _BackupRestoreSettings extends ConsumerState { + late bool createBackup = false; + late bool restoreBackup = false; + + Future enableAutoBackup(BuildContext context) async { + await showDialog( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (context) { + return const EnableBackupDialog(); + }, + ); + } + @override Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); @@ -97,12 +110,19 @@ class _BackupRestoreSettings extends ConsumerState { ), Column( crossAxisAlignment: CrossAxisAlignment.start, - children: const [ + children: [ Padding( padding: EdgeInsets.all( 10, ), - child: AutoBackupButton(), + child: PrimaryButton( + desktopMed: true, + width: 200, + label: "Enable auto backup", + onPressed: () { + enableAutoBackup(context); + }, + ), ), ], ), @@ -154,12 +174,23 @@ class _BackupRestoreSettings extends ConsumerState { ), Column( crossAxisAlignment: CrossAxisAlignment.start, - children: const [ + children: [ Padding( padding: EdgeInsets.all( 10, ), - child: CreateBackupView(), + child: createBackup + ? const CreateBackupView() + : PrimaryButton( + desktopMed: true, + width: 200, + label: "Create manual backup", + onPressed: () { + setState(() { + createBackup = true; + }); + }, + ), ), ], ), @@ -173,6 +204,7 @@ class _BackupRestoreSettings extends ConsumerState { Padding( padding: const EdgeInsets.only( right: 30, + bottom: 40, ), child: RoundedWhiteContainer( child: Column( @@ -210,12 +242,23 @@ class _BackupRestoreSettings extends ConsumerState { ), Column( crossAxisAlignment: CrossAxisAlignment.start, - children: const [ + children: [ Padding( padding: EdgeInsets.all( 10, ), - child: RestoreFromFileView(), + child: restoreBackup + ? RestoreFromFileView() + : PrimaryButton( + desktopMed: true, + width: 200, + label: "Restore backup", + onPressed: () { + setState(() { + restoreBackup = true; + }); + }, + ), ), ], ), @@ -230,98 +273,3 @@ class _BackupRestoreSettings extends ConsumerState { }); } } - -class AutoBackupButton extends ConsumerWidget { - const AutoBackupButton({ - Key? key, - }) : super(key: key); - @override - Widget build(BuildContext context, WidgetRef ref) { - Future enableAutoBackup() async { - await showDialog( - context: context, - useSafeArea: false, - barrierDismissible: true, - builder: (context) { - return const EnableBackupDialog(); - }, - ); - } - - return SizedBox( - width: 200, - height: 48, - child: TextButton( - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context), - onPressed: () { - enableAutoBackup(); - }, - child: Text( - "Enable auto backup", - style: STextStyles.button(context), - ), - ), - ); - } -} - -class ManualBackupButton extends ConsumerWidget { - const ManualBackupButton({ - Key? key, - }) : super(key: key); - @override - Widget build(BuildContext context, WidgetRef ref) { - return SizedBox( - width: 200, - height: 48, - child: TextButton( - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context), - onPressed: () {}, - child: Text( - "Create manual backup", - style: STextStyles.button(context), - ), - ), - ); - } -} - -class RestoreBackupButton extends ConsumerWidget { - const RestoreBackupButton({ - Key? key, - }) : super(key: key); - @override - Widget build(BuildContext context, WidgetRef ref) { - Future restoreBackup() async { - await showDialog( - context: context, - useSafeArea: false, - barrierDismissible: true, - builder: (context) { - return const RestoreBackupDialog(); - }, - ); - } - - return SizedBox( - width: 200, - height: 48, - child: TextButton( - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context), - onPressed: () { - restoreBackup(); - }, - child: Text( - "Restore", - style: STextStyles.button(context), - ), - ), - ); - } -} diff --git a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/restore_backup_dialog.dart b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/restore_backup_dialog.dart index 07d49274a..7f944847d 100644 --- a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/restore_backup_dialog.dart +++ b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/restore_backup_dialog.dart @@ -1,10 +1,12 @@ import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; -import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; class RestoreBackupDialog extends StatelessWidget { const RestoreBackupDialog({Key? key}) : super(key: key); @@ -12,82 +14,158 @@ class RestoreBackupDialog extends StatelessWidget { @override Widget build(BuildContext context) { return DesktopDialog( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Padding( - padding: const EdgeInsets.all(32), - child: Text( - "Restoring Stack Wallet", - style: STextStyles.desktopH3(context), - textAlign: TextAlign.center, + maxHeight: 750, + maxWidth: 600, + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.all(32), + child: Text( + "Restoring Stack Wallet", + style: STextStyles.desktopH3(context), + textAlign: TextAlign.center, + ), + ), + const DesktopDialogCloseButton(), + ], + ), + const SizedBox( + height: 30, + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 32), + child: Row( + children: [ + Text( + "Settings", + style: STextStyles.desktopTextExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark3, + ), + textAlign: TextAlign.left, + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, vertical: 12), + child: RoundedWhiteContainer( + borderColor: Theme.of(context) + .extension()! + .background, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + SvgPicture.asset( + Assets.svg.framedAddressBook, + width: 40, + height: 40, + ), + const SizedBox(width: 12), + Text( + "Address Book", + style: + STextStyles.desktopTextSmall(context), + ), + ], + ), + + ///TODO: CHECKMARK ANIMATION + ], + ), + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, vertical: 12), + child: RoundedWhiteContainer( + borderColor: Theme.of(context) + .extension()! + .background, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + SvgPicture.asset( + Assets.svg.framedGear, + width: 40, + height: 40, + ), + const SizedBox(width: 12), + Text( + "Preferences", + style: + STextStyles.desktopTextSmall(context), + ), + ], + ), + + ///TODO: CHECKMARK ANIMATION + ], + ), + ), + ), + const SizedBox( + height: 30, + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 32), + child: Row( + children: [ + Text( + "Wallets", + style: STextStyles.desktopTextExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark3, + ), + textAlign: TextAlign.left, + ), + ], + ), + ), + const Spacer(), + Padding( + padding: const EdgeInsets.all(32), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + SecondaryButton( + desktopMed: true, + width: 200, + label: "Cancel restore process", + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ), + ), + ], + ), ), ), - const DesktopDialogCloseButton(), - ], - ), - const SizedBox( - height: 30, - ), - Padding( - padding: const EdgeInsets.only( - left: 32, - right: 32, - ), - child: Row( - children: [ - Text( - "Settings", - style: STextStyles.desktopTextExtraSmall(context).copyWith( - color: - Theme.of(context).extension()!.textDark3, - ), - textAlign: TextAlign.left, - ), - ], - ), - ), - // RoundedWhiteContainer( - // child: Column( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // Row(), - // ], - // ), - // ), - const Spacer(), - Padding( - padding: const EdgeInsets.all(32), - child: Row( - children: [ - Expanded( - child: SecondaryButton( - label: "Cancel", - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ), - const SizedBox( - width: 16, - ), - Expanded( - child: PrimaryButton( - label: "Continue", - onPressed: () { - // Navigator.of(context).pop(); - // onConfirm.call(); - }, - ), - ) - ], - ), - ), - ], - ), - ); + ); + }, + )); } } diff --git a/lib/utilities/assets.dart b/lib/utilities/assets.dart index 386ea1cd8..b0c6b3bf9 100644 --- a/lib/utilities/assets.dart +++ b/lib/utilities/assets.dart @@ -59,6 +59,8 @@ class _SVG { String txExchangeFailed(BuildContext context) => "assets/svg/${Theme.of(context).extension()!.themeType.name}/tx-exchange-icon-failed.svg"; + String get framedGear => "assets/svg/framed-gear.svg"; + String get framedAddressBook => "assets/svg/framed-address-book.svg"; String get themeLight => "assets/svg/light/light-mode.svg"; String get themeDark => "assets/svg/dark/dark-theme.svg"; String get circleNode => "assets/svg/node-circle.svg"; diff --git a/pubspec.yaml b/pubspec.yaml index 9ba6d4eb9..1e5c80e76 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -338,6 +338,8 @@ flutter: - assets/svg/message-question-1.svg - assets/svg/drd-icon.svg - assets/svg/box-auto.svg + - assets/svg/framed-address-book.svg + - assets/svg/framed-gear.svg # exchange icons - assets/svg/exchange_icons/change_now_logo_1.svg - assets/svg/exchange_icons/simpleswap-icon.svg From 21f18326d80ba4a7572536f4f051528dee2b03a3 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 4 Nov 2022 10:08:29 -0600 Subject: [PATCH 09/76] update swb lib dependency --- crypto_plugins/flutter_libmonero | 2 +- pubspec.lock | 4 ++-- pubspec.yaml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crypto_plugins/flutter_libmonero b/crypto_plugins/flutter_libmonero index 51f74f05d..277d922c3 160000 --- a/crypto_plugins/flutter_libmonero +++ b/crypto_plugins/flutter_libmonero @@ -1 +1 @@ -Subproject commit 51f74f05d465a92e0118cf7c2bcfb049df21af42 +Subproject commit 277d922c3b1d637c1ccda25f51395c618d293015 diff --git a/pubspec.lock b/pubspec.lock index 20992d827..2f7770d3a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1378,8 +1378,8 @@ packages: dependency: "direct main" description: path: "." - ref: b7b184ec36466f2a24104a7056de88881cb0c1e9 - resolved-ref: b7b184ec36466f2a24104a7056de88881cb0c1e9 + ref: "011dc9ce3d29f5fdeeaf711d58b5122f055c146d" + resolved-ref: "011dc9ce3d29f5fdeeaf711d58b5122f055c146d" url: "https://github.com/cypherstack/stack_wallet_backup.git" source: git version: "0.0.1" diff --git a/pubspec.yaml b/pubspec.yaml index 1e5c80e76..86f24330b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -54,7 +54,7 @@ dependencies: stack_wallet_backup: git: url: https://github.com/cypherstack/stack_wallet_backup.git - ref: b7b184ec36466f2a24104a7056de88881cb0c1e9 + ref: 011dc9ce3d29f5fdeeaf711d58b5122f055c146d # Utility plugins # provider: ^6.0.1 From 4dd8ae23c5ab89d3baccec1053c347ca6bef7d17 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 4 Nov 2022 13:32:02 -0600 Subject: [PATCH 10/76] WIP: desktop password --- lib/main.dart | 29 +++--- .../create_password/create_password_view.dart | 20 +++-- .../desktop_login_view.dart | 47 ++++++++++ .../storage_crypto_handler_provider.dart | 4 + lib/utilities/desktop_password_service.dart | 89 +++++++++++++++++++ 5 files changed, 174 insertions(+), 15 deletions(-) create mode 100644 lib/pages_desktop_specific/desktop_login_view.dart create mode 100644 lib/providers/desktop/storage_crypto_handler_provider.dart create mode 100644 lib/utilities/desktop_password_service.dart diff --git a/lib/main.dart b/lib/main.dart index 58a287b31..77a8b1441 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -30,7 +30,8 @@ import 'package:stackwallet/pages/loading_view.dart'; import 'package:stackwallet/pages/pinpad_views/create_pin_view.dart'; import 'package:stackwallet/pages/pinpad_views/lock_screen_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/restore_from_encrypted_string_view.dart'; -import 'package:stackwallet/pages_desktop_specific/home/desktop_home_view.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_login_view.dart'; +import 'package:stackwallet/providers/desktop/storage_crypto_handler_provider.dart'; import 'package:stackwallet/providers/global/auto_swb_service_provider.dart'; import 'package:stackwallet/providers/global/base_currencies_provider.dart'; // import 'package:stackwallet/providers/global/has_authenticated_start_state_provider.dart'; @@ -207,6 +208,7 @@ class _MaterialAppWithThemeState extends ConsumerState late final Completer loadingCompleter; bool didLoad = false; + bool _desktopHasPassword = false; Future load() async { try { @@ -218,6 +220,11 @@ class _MaterialAppWithThemeState extends ConsumerState await DB.instance.init(); await _prefs.init(); + if (Util.isDesktop) { + _desktopHasPassword = + await ref.read(storageCryptoHandlerProvider).hasPassword(); + } + _notificationsService = ref.read(notificationsProvider); _nodeService = ref.read(nodeServiceChangeNotifierProvider); _tradesService = ref.read(tradesServiceProvider); @@ -545,21 +552,23 @@ class _MaterialAppWithThemeState extends ConsumerState builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.connectionState == ConnectionState.done) { // FlutterNativeSplash.remove(); - if (_wallets.hasWallets || _prefs.hasPin) { - // return HomeView(); - + if (Util.isDesktop && + (_wallets.hasWallets || _desktopHasPassword)) { String? startupWalletId; if (ref.read(prefsChangeNotifierProvider).gotoWalletOnStartup) { startupWalletId = ref.read(prefsChangeNotifierProvider).startupWalletId; } - // TODO proper desktop auth view - if (Util.isDesktop) { - Future.delayed(Duration.zero).then((value) => - Navigator.of(context).pushNamedAndRemoveUntil( - DesktopHomeView.routeName, (route) => false)); - return Container(); + return DesktopLoginView(startupWalletId: startupWalletId); + } else if (!Util.isDesktop && + (_wallets.hasWallets || _prefs.hasPin)) { + // return HomeView(); + + String? startupWalletId; + if (ref.read(prefsChangeNotifierProvider).gotoWalletOnStartup) { + startupWalletId = + ref.read(prefsChangeNotifierProvider).startupWalletId; } return LockscreenView( diff --git a/lib/pages_desktop_specific/create_password/create_password_view.dart b/lib/pages_desktop_specific/create_password/create_password_view.dart index 2391a22f6..0a8429058 100644 --- a/lib/pages_desktop_specific/create_password/create_password_view.dart +++ b/lib/pages_desktop_specific/create_password/create_password_view.dart @@ -1,10 +1,12 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages_desktop_specific/home/desktop_home_view.dart'; +import 'package:stackwallet/providers/desktop/storage_crypto_handler_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; @@ -18,7 +20,7 @@ import 'package:stackwallet/widgets/progress_bar.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:zxcvbn/zxcvbn.dart'; -class CreatePasswordView extends StatefulWidget { +class CreatePasswordView extends ConsumerStatefulWidget { const CreatePasswordView({ Key? key, this.secureStore = const SecureStorageWrapper( @@ -31,10 +33,10 @@ class CreatePasswordView extends StatefulWidget { final FlutterSecureStorageInterface secureStore; @override - State createState() => _CreatePasswordViewState(); + ConsumerState createState() => _CreatePasswordViewState(); } -class _CreatePasswordViewState extends State { +class _CreatePasswordViewState extends ConsumerState { late final TextEditingController passwordController; late final TextEditingController passwordRepeatController; @@ -76,8 +78,16 @@ class _CreatePasswordViewState extends State { return; } - await widget.secureStore - .write(key: "stackDesktopPassword", value: passphrase); + try { + await ref.read(storageCryptoHandlerProvider).initFromNew(passphrase); + } catch (e) { + unawaited(showFloatingFlushBar( + type: FlushBarType.warning, + message: "Error: $e", + context: context, + )); + return; + } if (mounted) { unawaited(Navigator.of(context) diff --git a/lib/pages_desktop_specific/desktop_login_view.dart b/lib/pages_desktop_specific/desktop_login_view.dart new file mode 100644 index 000000000..c986bffde --- /dev/null +++ b/lib/pages_desktop_specific/desktop_login_view.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; +import 'package:stackwallet/pages_desktop_specific/home/desktop_home_view.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; + +class DesktopLoginView extends StatefulWidget { + const DesktopLoginView({ + Key? key, + this.startupWalletId, + }) : super(key: key); + + static const String routeName = "/desktopLogin"; + + final String? startupWalletId; + + @override + State createState() => _DesktopLoginViewState(); +} + +class _DesktopLoginViewState extends State { + @override + Widget build(BuildContext context) { + return Material( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + "Login", + style: STextStyles.desktopH3(context), + ), + PrimaryButton( + label: "Login", + onPressed: () { + // todo auth + + Navigator.of(context).pushNamedAndRemoveUntil( + DesktopHomeView.routeName, + (route) => false, + ); + }, + ) + ], + ), + ); + } +} diff --git a/lib/providers/desktop/storage_crypto_handler_provider.dart b/lib/providers/desktop/storage_crypto_handler_provider.dart new file mode 100644 index 000000000..5b15ccaf3 --- /dev/null +++ b/lib/providers/desktop/storage_crypto_handler_provider.dart @@ -0,0 +1,4 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/utilities/desktop_password_service.dart'; + +final storageCryptoHandlerProvider = Provider((ref) => DPS()); diff --git a/lib/utilities/desktop_password_service.dart b/lib/utilities/desktop_password_service.dart new file mode 100644 index 000000000..da537b3c2 --- /dev/null +++ b/lib/utilities/desktop_password_service.dart @@ -0,0 +1,89 @@ +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:stack_wallet_backup/secure_storage.dart'; +import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; +import 'package:stackwallet/utilities/logger.dart'; + +const String _kKeyBlobKey = "swbKeyBlobKeyStringID"; + +String _getMessageFromException(Object exception) { + if (exception is IncorrectPassphrase) { + return exception.errMsg(); + } + if (exception is BadDecryption) { + return exception.errMsg(); + } + if (exception is InvalidLength) { + return exception.errMsg(); + } + if (exception is EncodingError) { + return exception.errMsg(); + } + + return exception.toString(); +} + +class DPS { + StorageCryptoHandler? _handler; + final SecureStorageWrapper secureStorageWrapper; + + StorageCryptoHandler get handler { + if (_handler == null) { + throw Exception( + "DPS: attempted to access handler without proper authentication"); + } + return _handler!; + } + + DPS({ + this.secureStorageWrapper = const SecureStorageWrapper( + FlutterSecureStorage(), + ), + }); + + Future initFromNew(String passphrase) async { + if (_handler != null) { + throw Exception("DPS: attempted to re initialize with new passphrase"); + } + + try { + _handler = await StorageCryptoHandler.fromNewPassphrase(passphrase); + await secureStorageWrapper.write( + key: _kKeyBlobKey, + value: await _handler!.getKeyBlob(), + ); + } catch (e, s) { + Logging.instance.log( + "${_getMessageFromException(e)}\n$s", + level: LogLevel.Error, + ); + rethrow; + } + } + + Future initFromExisting(String passphrase) async { + if (_handler != null) { + throw Exception( + "DPS: attempted to re initialize with existing passphrase"); + } + final keyBlob = await secureStorageWrapper.read(key: _kKeyBlobKey); + + if (keyBlob == null) { + throw Exception( + "DPS: failed to find keyBlob while attempting to initialize with existing passphrase"); + } + + try { + _handler = await StorageCryptoHandler.fromExisting(passphrase, keyBlob); + } catch (e, s) { + Logging.instance.log( + "${_getMessageFromException(e)}\n$s", + level: LogLevel.Error, + ); + rethrow; + } + } + + Future hasPassword() async { + return (await secureStorageWrapper.read(key: _kKeyBlobKey)) != null; + } +} From 039a9a68f6e96ae9b2033ac8279005a03f93fa97 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 4 Nov 2022 13:55:13 -0600 Subject: [PATCH 11/76] bch clean up linter warnings and unused variables --- .../coins/bitcoincash/bitcoincash_wallet.dart | 209 ++++++++++-------- 1 file changed, 119 insertions(+), 90 deletions(-) diff --git a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart index 3a5cebdec..c01ff6248 100644 --- a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart +++ b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart @@ -6,7 +6,7 @@ import 'dart:typed_data'; import 'package:bech32/bech32.dart'; import 'package:bip32/bip32.dart' as bip32; import 'package:bip39/bip39.dart' as bip39; -import 'package:bitbox/bitbox.dart' as Bitbox; +import 'package:bitbox/bitbox.dart' as bitbox; import 'package:bitcoindart/bitcoindart.dart'; import 'package:bs58check/bs58check.dart' as bs58check; import 'package:crypto/crypto.dart'; @@ -258,7 +258,7 @@ class BitcoinCashWallet extends CoinServiceAPI { } Future updateStoredChainHeight({required int newHeight}) async { - DB.instance.put( + await DB.instance.put( boxName: walletId, key: "storedChainHeight", value: newHeight); } @@ -266,8 +266,8 @@ class BitcoinCashWallet extends CoinServiceAPI { Uint8List? decodeBase58; Segwit? decodeBech32; try { - if (Bitbox.Address.detectFormat(address) == 0) { - address = Bitbox.Address.toLegacyAddress(address); + if (bitbox.Address.detectFormat(address) == 0) { + address = bitbox.Address.toLegacyAddress(address); } } catch (e, s) {} try { @@ -609,7 +609,9 @@ class BitcoinCashWallet extends CoinServiceAPI { // get address tx counts final counts = await _getBatchTxCount(addresses: txCountCallArgs); - print("Counts $counts"); + if (kDebugMode) { + print("Counts $counts"); + } // check and add appropriate addresses for (int k = 0; k < txCountBatchSize; k++) { int count = counts["${_id}_$k"]!; @@ -745,31 +747,35 @@ class BitcoinCashWallet extends CoinServiceAPI { // notify on new incoming transaction for (final tx in unconfirmedTxnsToNotifyPending) { if (tx.txType == "Received") { - NotificationApi.showNotification( - title: "Incoming transaction", - body: walletName, - walletId: walletId, - iconAssetName: Assets.svg.iconFor(coin: coin), - date: DateTime.now(), - shouldWatchForUpdates: tx.confirmations < MINIMUM_CONFIRMATIONS, - coinName: coin.name, - txid: tx.txid, - confirmations: tx.confirmations, - requiredConfirmations: MINIMUM_CONFIRMATIONS, + unawaited( + NotificationApi.showNotification( + title: "Incoming transaction", + body: walletName, + walletId: walletId, + iconAssetName: Assets.svg.iconFor(coin: coin), + date: DateTime.now(), + shouldWatchForUpdates: tx.confirmations < MINIMUM_CONFIRMATIONS, + coinName: coin.name, + txid: tx.txid, + confirmations: tx.confirmations, + requiredConfirmations: MINIMUM_CONFIRMATIONS, + ), ); await txTracker.addNotifiedPending(tx.txid); } else if (tx.txType == "Sent") { - NotificationApi.showNotification( - title: "Sending transaction", - body: walletName, - walletId: walletId, - iconAssetName: Assets.svg.iconFor(coin: coin), - date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), - shouldWatchForUpdates: tx.confirmations < MINIMUM_CONFIRMATIONS, - coinName: coin.name, - txid: tx.txid, - confirmations: tx.confirmations, - requiredConfirmations: MINIMUM_CONFIRMATIONS, + unawaited( + NotificationApi.showNotification( + title: "Sending transaction", + body: walletName, + walletId: walletId, + iconAssetName: Assets.svg.iconFor(coin: coin), + date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), + shouldWatchForUpdates: tx.confirmations < MINIMUM_CONFIRMATIONS, + coinName: coin.name, + txid: tx.txid, + confirmations: tx.confirmations, + requiredConfirmations: MINIMUM_CONFIRMATIONS, + ), ); await txTracker.addNotifiedPending(tx.txid); } @@ -778,26 +784,30 @@ class BitcoinCashWallet extends CoinServiceAPI { // notify on confirmed for (final tx in unconfirmedTxnsToNotifyConfirmed) { if (tx.txType == "Received") { - NotificationApi.showNotification( - title: "Incoming transaction confirmed", - body: walletName, - walletId: walletId, - iconAssetName: Assets.svg.iconFor(coin: coin), - date: DateTime.now(), - shouldWatchForUpdates: false, - coinName: coin.name, + unawaited( + NotificationApi.showNotification( + title: "Incoming transaction confirmed", + body: walletName, + walletId: walletId, + iconAssetName: Assets.svg.iconFor(coin: coin), + date: DateTime.now(), + shouldWatchForUpdates: false, + coinName: coin.name, + ), ); await txTracker.addNotifiedConfirmed(tx.txid); } else if (tx.txType == "Sent") { - NotificationApi.showNotification( - title: "Outgoing transaction confirmed", - body: walletName, - walletId: walletId, - iconAssetName: Assets.svg.iconFor(coin: coin), - date: DateTime.now(), - shouldWatchForUpdates: false, - coinName: coin.name, + unawaited( + NotificationApi.showNotification( + title: "Outgoing transaction confirmed", + body: walletName, + walletId: walletId, + iconAssetName: Assets.svg.iconFor(coin: coin), + date: DateTime.now(), + shouldWatchForUpdates: false, + coinName: coin.name, + ), ); await txTracker.addNotifiedConfirmed(tx.txid); } @@ -862,7 +872,7 @@ class BitcoinCashWallet extends CoinServiceAPI { if (currentHeight != storedHeight) { if (currentHeight != -1) { // -1 failed to fetch current height - updateStoredChainHeight(newHeight: currentHeight); + await updateStoredChainHeight(newHeight: currentHeight); } GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.2, walletId)); @@ -1147,10 +1157,12 @@ class BitcoinCashWallet extends CoinServiceAPI { bool validateAddress(String address) { try { // 0 for bitcoincash: address scheme, 1 for legacy address - final format = Bitbox.Address.detectFormat(address); - print("format $format"); + final format = bitbox.Address.detectFormat(address); + if (kDebugMode) { + print("format $format"); + } return true; - } catch (e, s) { + } catch (e) { return false; } } @@ -1226,7 +1238,7 @@ class BitcoinCashWallet extends CoinServiceAPI { ); if (shouldRefresh) { - refresh(); + unawaited(refresh()); } } @@ -1522,12 +1534,14 @@ class BitcoinCashWallet extends CoinServiceAPI { break; } - print("Array key is ${jsonEncode(arrayKey)}"); + if (kDebugMode) { + print("Array key is ${jsonEncode(arrayKey)}"); + } final internalChainArray = DB.instance.get(boxName: walletId, key: arrayKey); if (derivePathType == DerivePathType.bip44) { - if (Bitbox.Address.detectFormat(internalChainArray.last as String) == 1) { - return Bitbox.Address.toCashAddress(internalChainArray.last as String); + if (bitbox.Address.detectFormat(internalChainArray.last as String) == 1) { + return bitbox.Address.toCashAddress(internalChainArray.last as String); } } return internalChainArray.last as String; @@ -1642,7 +1656,9 @@ class BitcoinCashWallet extends CoinServiceAPI { batches[batchNumber] = {}; } final scripthash = _convertToScriptHash(allAddresses[i], _network); - print("SCRIPT_HASH_FOR_ADDRESS ${allAddresses[i]} IS $scripthash"); + if (kDebugMode) { + print("SCRIPT_HASH_FOR_ADDRESS ${allAddresses[i]} IS $scripthash"); + } batches[batchNumber]!.addAll({ scripthash: [scripthash] }); @@ -1818,20 +1834,28 @@ class BitcoinCashWallet extends CoinServiceAPI { }) async { try { final Map> args = {}; - print("Address $addresses"); + if (kDebugMode) { + print("Address $addresses"); + } for (final entry in addresses.entries) { args[entry.key] = [_convertToScriptHash(entry.value, _network)]; } - print("Args ${jsonEncode(args)}"); + if (kDebugMode) { + print("Args ${jsonEncode(args)}"); + } final response = await electrumXClient.getBatchHistory(args: args); - print("Response ${jsonEncode(response)}"); + if (kDebugMode) { + print("Response ${jsonEncode(response)}"); + } final Map result = {}; for (final entry in response.entries) { result[entry.key] = entry.value.length; } - print("result ${jsonEncode(result)}"); + if (kDebugMode) { + print("result ${jsonEncode(result)}"); + } return result; } catch (e, s) { Logging.instance.log( @@ -1995,8 +2019,8 @@ class BitcoinCashWallet extends CoinServiceAPI { /// Returns the scripthash or throws an exception on invalid bch address String _convertToScriptHash(String bchAddress, NetworkType network) { try { - if (Bitbox.Address.detectFormat(bchAddress) == 0) { - bchAddress = Bitbox.Address.toLegacyAddress(bchAddress); + if (bitbox.Address.detectFormat(bchAddress) == 0) { + bchAddress = bitbox.Address.toLegacyAddress(bchAddress); } final output = Address.addressToOutputScript(bchAddress, network); final hash = sha256.convert(output.toList(growable: false)).toString(); @@ -2073,8 +2097,8 @@ class BitcoinCashWallet extends CoinServiceAPI { List allAddressesOld = await _fetchAllOwnAddresses(); List allAddresses = []; for (String address in allAddressesOld) { - if (Bitbox.Address.detectFormat(address) == 1) { - allAddresses.add(Bitbox.Address.toCashAddress(address)); + if (bitbox.Address.detectFormat(address) == 1) { + allAddresses.add(bitbox.Address.toCashAddress(address)); } else { allAddresses.add(address); } @@ -2085,8 +2109,8 @@ class BitcoinCashWallet extends CoinServiceAPI { as List; List changeAddressesP2PKH = []; for (var address in changeAddressesP2PKHOld) { - if (Bitbox.Address.detectFormat(address as String) == 1) { - changeAddressesP2PKH.add(Bitbox.Address.toCashAddress(address)); + if (bitbox.Address.detectFormat(address as String) == 1) { + changeAddressesP2PKH.add(bitbox.Address.toCashAddress(address)); } else { changeAddressesP2PKH.add(address); } @@ -2108,21 +2132,26 @@ class BitcoinCashWallet extends CoinServiceAPI { unconfirmedCachedTransactions .removeWhere((key, value) => value.confirmedStatus); - print("CACHED_TRANSACTIONS_IS $cachedTransactions"); + if (kDebugMode) { + print("CACHED_TRANSACTIONS_IS $cachedTransactions"); + } if (cachedTransactions != null) { for (final tx in allTxHashes.toList(growable: false)) { final txHeight = tx["height"] as int; if (txHeight > 0 && txHeight < latestTxnBlockHeight - MINIMUM_CONFIRMATIONS) { if (unconfirmedCachedTransactions[tx["tx_hash"] as String] == null) { - print(cachedTransactions.findTransaction(tx["tx_hash"] as String)); - print(unconfirmedCachedTransactions[tx["tx_hash"] as String]); + if (kDebugMode) { + print( + cachedTransactions.findTransaction(tx["tx_hash"] as String)); + print(unconfirmedCachedTransactions[tx["tx_hash"] as String]); + } final cachedTx = cachedTransactions.findTransaction(tx["tx_hash"] as String); if (!(cachedTx != null && addressType(address: cachedTx.address) == DerivePathType.bip44 && - Bitbox.Address.detectFormat(cachedTx.address) == 1)) { + bitbox.Address.detectFormat(cachedTx.address) == 1)) { allTxHashes.remove(tx); } } @@ -2782,8 +2811,8 @@ class BitcoinCashWallet extends CoinServiceAPI { final n = output["n"]; if (n != null && n == utxosToUse[i].vout) { String address = output["scriptPubKey"]["addresses"][0] as String; - if (Bitbox.Address.detectFormat(address) == 0) { - address = Bitbox.Address.toLegacyAddress(address); + if (bitbox.Address.detectFormat(address) == 0) { + address = bitbox.Address.toLegacyAddress(address); } if (!addressTxid.containsKey(address)) { addressTxid[address] = []; @@ -2814,8 +2843,8 @@ class BitcoinCashWallet extends CoinServiceAPI { ); for (int i = 0; i < p2pkhLength; i++) { String address = addressesP2PKH[i]; - if (Bitbox.Address.detectFormat(address) == 0) { - address = Bitbox.Address.toLegacyAddress(address); + if (bitbox.Address.detectFormat(address) == 0) { + address = bitbox.Address.toLegacyAddress(address); } // receives @@ -2950,36 +2979,36 @@ class BitcoinCashWallet extends CoinServiceAPI { required List recipients, required List satoshiAmounts, }) async { - final builder = Bitbox.Bitbox.transactionBuilder(); + final builder = bitbox.Bitbox.transactionBuilder(); // retrieve address' utxos from the rest api - List _utxos = + List _utxos = []; // await Bitbox.Address.utxo(address) as List; - utxosToUse.forEach((element) { - _utxos.add(Bitbox.Utxo( + for (var element in utxosToUse) { + _utxos.add(bitbox.Utxo( element.txid, element.vout, - Bitbox.BitcoinCash.fromSatoshi(element.value), + bitbox.BitcoinCash.fromSatoshi(element.value), element.value, 0, MINIMUM_CONFIRMATIONS + 1)); - }); - Logger.print("bch utxos: ${_utxos}"); + } + Logger.print("bch utxos: $_utxos"); // placeholder for input signatures - final signatures = []; + final List> signatures = []; // placeholder for total input balance - int totalBalance = 0; + // int totalBalance = 0; // iterate through the list of address _utxos and use them as inputs for the // withdrawal transaction - _utxos.forEach((Bitbox.Utxo utxo) { + for (var utxo in _utxos) { // add the utxo as an input for the transaction builder.addInput(utxo.txid, utxo.vout); final ec = utxoSigningData[utxo.txid]["keyPair"] as ECPair; - final bitboxEC = Bitbox.ECPair.fromWIF(ec.toWIF()); + final bitboxEC = bitbox.ECPair.fromWIF(ec.toWIF()); // add a signature to the list to be used later signatures.add({ @@ -2988,15 +3017,15 @@ class BitcoinCashWallet extends CoinServiceAPI { "original_amount": utxo.satoshis }); - totalBalance += utxo.satoshis; - }); + // totalBalance += utxo.satoshis; + } // calculate the fee based on number of inputs and one expected output - final fee = - Bitbox.BitcoinCash.getByteCount(signatures.length, recipients.length); + // final fee = + // bitbox.BitcoinCash.getByteCount(signatures.length, recipients.length); // calculate how much balance will be left over to spend after the fee - final sendAmount = totalBalance - fee; + // final sendAmount = totalBalance - fee; // add the output based on the address provided in the testing data for (int i = 0; i < recipients.length; i++) { @@ -3006,12 +3035,12 @@ class BitcoinCashWallet extends CoinServiceAPI { } // sign all inputs - signatures.forEach((signature) { + for (var signature in signatures) { builder.sign( signature["vin"] as int, - signature["key_pair"] as Bitbox.ECPair, + signature["key_pair"] as bitbox.ECPair, signature["original_amount"] as int); - }); + } // build the transaction final tx = builder.build(); @@ -3038,7 +3067,7 @@ class BitcoinCashWallet extends CoinServiceAPI { ); // clear cache - _cachedElectrumXClient.clearSharedTransactionCache(coin: coin); + await _cachedElectrumXClient.clearSharedTransactionCache(coin: coin); // back up data await _rescanBackup(); From ccd94fcf86601e03d995d170611b5888694d38a0 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 4 Nov 2022 14:10:30 -0600 Subject: [PATCH 12/76] bch compare address type to constant names instead of int literals --- .../coins/bitcoincash/bitcoincash_wallet.dart | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart index c01ff6248..0a72cea99 100644 --- a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart +++ b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart @@ -266,7 +266,8 @@ class BitcoinCashWallet extends CoinServiceAPI { Uint8List? decodeBase58; Segwit? decodeBech32; try { - if (bitbox.Address.detectFormat(address) == 0) { + if (bitbox.Address.detectFormat(address) == + bitbox.Address.formatCashAddr) { address = bitbox.Address.toLegacyAddress(address); } } catch (e, s) {} @@ -1540,7 +1541,8 @@ class BitcoinCashWallet extends CoinServiceAPI { final internalChainArray = DB.instance.get(boxName: walletId, key: arrayKey); if (derivePathType == DerivePathType.bip44) { - if (bitbox.Address.detectFormat(internalChainArray.last as String) == 1) { + if (bitbox.Address.detectFormat(internalChainArray.last as String) == + bitbox.Address.formatLegacy) { return bitbox.Address.toCashAddress(internalChainArray.last as String); } } @@ -2019,7 +2021,8 @@ class BitcoinCashWallet extends CoinServiceAPI { /// Returns the scripthash or throws an exception on invalid bch address String _convertToScriptHash(String bchAddress, NetworkType network) { try { - if (bitbox.Address.detectFormat(bchAddress) == 0) { + if (bitbox.Address.detectFormat(bchAddress) == + bitbox.Address.formatCashAddr) { bchAddress = bitbox.Address.toLegacyAddress(bchAddress); } final output = Address.addressToOutputScript(bchAddress, network); @@ -2097,7 +2100,7 @@ class BitcoinCashWallet extends CoinServiceAPI { List allAddressesOld = await _fetchAllOwnAddresses(); List allAddresses = []; for (String address in allAddressesOld) { - if (bitbox.Address.detectFormat(address) == 1) { + if (bitbox.Address.detectFormat(address) == bitbox.Address.formatLegacy) { allAddresses.add(bitbox.Address.toCashAddress(address)); } else { allAddresses.add(address); @@ -2109,7 +2112,8 @@ class BitcoinCashWallet extends CoinServiceAPI { as List; List changeAddressesP2PKH = []; for (var address in changeAddressesP2PKHOld) { - if (bitbox.Address.detectFormat(address as String) == 1) { + if (bitbox.Address.detectFormat(address as String) == + bitbox.Address.formatLegacy) { changeAddressesP2PKH.add(bitbox.Address.toCashAddress(address)); } else { changeAddressesP2PKH.add(address); @@ -2151,7 +2155,8 @@ class BitcoinCashWallet extends CoinServiceAPI { if (!(cachedTx != null && addressType(address: cachedTx.address) == DerivePathType.bip44 && - bitbox.Address.detectFormat(cachedTx.address) == 1)) { + bitbox.Address.detectFormat(cachedTx.address) == + bitbox.Address.formatLegacy)) { allTxHashes.remove(tx); } } @@ -2811,7 +2816,8 @@ class BitcoinCashWallet extends CoinServiceAPI { final n = output["n"]; if (n != null && n == utxosToUse[i].vout) { String address = output["scriptPubKey"]["addresses"][0] as String; - if (bitbox.Address.detectFormat(address) == 0) { + if (bitbox.Address.detectFormat(address) == + bitbox.Address.formatCashAddr) { address = bitbox.Address.toLegacyAddress(address); } if (!addressTxid.containsKey(address)) { @@ -2843,7 +2849,8 @@ class BitcoinCashWallet extends CoinServiceAPI { ); for (int i = 0; i < p2pkhLength; i++) { String address = addressesP2PKH[i]; - if (bitbox.Address.detectFormat(address) == 0) { + if (bitbox.Address.detectFormat(address) == + bitbox.Address.formatCashAddr) { address = bitbox.Address.toLegacyAddress(address); } From 74b075328f856de25dca0a5f54d43f59627a07a0 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 4 Nov 2022 17:22:22 -0600 Subject: [PATCH 13/76] temp bch send fix --- .../coins/bitcoincash/bitcoincash_wallet.dart | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart index 0a72cea99..09578c1ca 100644 --- a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart +++ b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart @@ -1162,7 +1162,17 @@ class BitcoinCashWallet extends CoinServiceAPI { if (kDebugMode) { print("format $format"); } - return true; + + if (format == bitbox.Address.formatCashAddr) { + String addr = address; + if (address.contains(":")) { + addr = address.split(":").last; + } + + return addr.startsWith("q"); + } else { + return address.startsWith("1"); + } } catch (e) { return false; } From a5d7723deee4e932f3070cc3ec63dd9f8c5d92c4 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Fri, 4 Nov 2022 17:49:36 -0600 Subject: [PATCH 14/76] added buttons and restore dialog --- .../create_backup_view.dart | 36 +++++++++++-- .../restore_from_file_view.dart | 52 ++++++++++++++++--- 2 files changed, 77 insertions(+), 11 deletions(-) diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart index b710aacf4..2d2ed4960 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart @@ -17,6 +17,8 @@ import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/progress_bar.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; @@ -142,14 +144,38 @@ class _RestoreFromFileViewState extends State { condition: isDesktop, builder: (child) { return Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - "Choose file location", - style: STextStyles.desktopTextExtraSmall(context).copyWith( - color: - Theme.of(context).extension()!.textDark3), + Padding( + padding: const EdgeInsets.all(10.0), + child: Text( + "Choose file location", + style: STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark3), + ), ), // child, + const SizedBox(height: 20), + Row( + children: [ + PrimaryButton( + desktopMed: true, + width: 200, + label: "Create backup", + onPressed: () {}, + ), + const SizedBox(width: 16), + SecondaryButton( + desktopMed: true, + width: 200, + label: "Cancel", + onPressed: () {}, + ), + ], + ), ], ); }, diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart index 16c3ea8e3..9f2796415 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart @@ -8,6 +8,7 @@ import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/stack_file_system.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart'; +import 'package:stackwallet/pages_desktop_specific/home/settings_menu/backup_and_restore/restore_backup_dialog.dart'; import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; @@ -18,6 +19,8 @@ import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/loading_indicator.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:tuple/tuple.dart'; @@ -42,6 +45,17 @@ class _RestoreFromFileViewState extends ConsumerState { bool hidePassword = true; + Future restoreBackupPopup(BuildContext context) async { + await showDialog( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (context) { + return const RestoreBackupDialog(); + }, + ); + } + @override void initState() { stackFileSystem = StackFileSystem(); @@ -114,15 +128,41 @@ class _RestoreFromFileViewState extends ConsumerState { condition: isDesktop, builder: (child) { return Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - "Choose file location", - style: STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textDark3), + Padding( + padding: const EdgeInsets.all(10.0), + child: Text( + "Choose file location", + style: STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark3), + textAlign: TextAlign.left, + ), ), // child, + const SizedBox(height: 20), + Row( + children: [ + PrimaryButton( + desktopMed: true, + width: 200, + label: "Restore", + onPressed: () { + restoreBackupPopup(context); + }, + ), + const SizedBox(width: 16), + SecondaryButton( + desktopMed: true, + width: 200, + label: "Cancel", + onPressed: () {}, + ), + ], + ), ], ); }, From e06910a34a68451d00cb337dcf7d194eb19d34cd Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Fri, 4 Nov 2022 17:50:51 -0600 Subject: [PATCH 15/76] WIP: theme change --- .../settings_menu/appearance_settings.dart | 216 ++++++++++-------- 1 file changed, 125 insertions(+), 91 deletions(-) diff --git a/lib/pages_desktop_specific/home/settings_menu/appearance_settings.dart b/lib/pages_desktop_specific/home/settings_menu/appearance_settings.dart index d524453e2..b5f239ab1 100644 --- a/lib/pages_desktop_specific/home/settings_menu/appearance_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/appearance_settings.dart @@ -23,6 +23,19 @@ class AppearanceOptionSettings extends ConsumerStatefulWidget { class _AppearanceOptionSettings extends ConsumerState { + // late bool isLight; + + // @override + // void initState() { + // + // super.initState(); + // } + // + // @override + // void dispose() { + // super.dispose(); + // } + @override Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); @@ -127,13 +140,7 @@ class _AppearanceOptionSettings ], ), ), - const Padding( - padding: EdgeInsets.only( - left: 10, - right: 10, - ), - child: ThemeToggle(), - ), + ThemeToggle(), ], ), ), @@ -169,95 +176,38 @@ class _ThemeToggle extends State { elevation: 0, hoverColor: Colors.transparent, shape: RoundedRectangleBorder( + side: BorderSide( + color: + Theme.of(context).extension()!.infoItemIcons, + width: 2, + ), + // side: !externalCallsEnabled + // ? BorderSide.none + // : BorderSide( + // color: Theme.of(context) + // .extension()! + // .infoItemIcons, + // width: 2, + // ), borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius * 2, ), ), onPressed: () {}, //onPressed - child: Stack( - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.only( - left: 24, - ), - child: SvgPicture.asset( - Assets.svg.themeLight, - ), - ), - Padding( - padding: const EdgeInsets.only( - left: 50, - top: 12, - ), - child: Text( - "Light", - style: - STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ), - ), - ) - ], - ), - // if (externalCallsEnabled) - Positioned( - bottom: 0, - left: 6, - child: SvgPicture.asset( - Assets.svg.checkCircle, - width: 20, - height: 20, - color: Theme.of(context) - .extension()! - .infoItemIcons, - ), - ), - // if (!externalCallsEnabled) - // Positioned( - // top: 4, - // right: 4, - // child: Container( - // width: 20, - // height: 20, - // decoration: BoxDecoration( - // borderRadius: BorderRadius.circular(1000), - // color: Theme.of(context) - // .extension()! - // .textFieldDefaultBG, - // ), - // ), - // ), - ], - ), - ), - ), - const SizedBox( - width: 1, - ), - Expanded( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: RawMaterialButton( - elevation: 0, - hoverColor: Colors.transparent, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius * 2, - ), - ), - onPressed: () {}, //onPressed + child: Padding( + padding: const EdgeInsets.all(8.0), child: Stack( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SvgPicture.asset( - Assets.svg.themeDark, + Padding( + padding: const EdgeInsets.only( + left: 24, + ), + child: SvgPicture.asset( + Assets.svg.themeLight, + ), ), Padding( padding: const EdgeInsets.only( @@ -265,7 +215,7 @@ class _ThemeToggle extends State { top: 12, ), child: Text( - "Dark", + "Light", style: STextStyles.desktopTextExtraSmall(context) .copyWith( color: Theme.of(context) @@ -273,13 +223,13 @@ class _ThemeToggle extends State { .textDark, ), ), - ), + ) ], ), // if (externalCallsEnabled) Positioned( bottom: 0, - left: 0, + left: 6, child: SvgPicture.asset( Assets.svg.checkCircle, width: 20, @@ -291,8 +241,8 @@ class _ThemeToggle extends State { ), // if (!externalCallsEnabled) // Positioned( - // top: 4, - // right: 4, + // bottom: 0, + // left: 6, // child: Container( // width: 20, // height: 20, @@ -309,6 +259,90 @@ class _ThemeToggle extends State { ), ), ), + const SizedBox( + width: 1, + ), + Expanded( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: RawMaterialButton( + elevation: 0, + hoverColor: Colors.transparent, + shape: RoundedRectangleBorder( + // side: !externalCallsEnabled + // ? BorderSide.none + // : BorderSide( + // color: Theme.of(context) + // .extension()! + // .infoItemIcons, + // width: 2, + // ), + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius * 2, + ), + ), + onPressed: () {}, //onPressed + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Stack( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SvgPicture.asset( + Assets.svg.themeDark, + ), + Padding( + padding: const EdgeInsets.only( + left: 45, + top: 12, + ), + child: Text( + "Dark", + style: STextStyles.desktopTextExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + ), + ), + ], + ), + // if (externalCallsEnabled) + // Positioned( + // bottom: 0, + // left: 0, + // child: SvgPicture.asset( + // Assets.svg.checkCircle, + // width: 20, + // height: 20, + // color: Theme.of(context) + // .extension()! + // .infoItemIcons, + // ), + // ), + // if (!externalCallsEnabled) + Positioned( + bottom: 0, + left: 0, + child: Container( + width: 20, + height: 20, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(1000), + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + ), + ), + ), + ], + ), + ), + ), + ), + ), ], ); } From 7dbc9d270b1e3d62fed837179241b9350a7234d9 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 4 Nov 2022 18:18:25 -0600 Subject: [PATCH 16/76] temp bch send fix testnet and fix tests --- lib/services/coins/bitcoincash/bitcoincash_wallet.dart | 4 ++++ test/services/coins/bitcoincash/bitcoincash_wallet_test.dart | 3 +-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart index 09578c1ca..b96aa160c 100644 --- a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart +++ b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart @@ -1163,6 +1163,10 @@ class BitcoinCashWallet extends CoinServiceAPI { print("format $format"); } + if (_coin == Coin.bitcoincashTestnet) { + return true; + } + if (format == bitbox.Address.formatCashAddr) { String addr = address; if (address.contains(":")) { diff --git a/test/services/coins/bitcoincash/bitcoincash_wallet_test.dart b/test/services/coins/bitcoincash/bitcoincash_wallet_test.dart index 4c392fe81..50ff8f741 100644 --- a/test/services/coins/bitcoincash/bitcoincash_wallet_test.dart +++ b/test/services/coins/bitcoincash/bitcoincash_wallet_test.dart @@ -1,4 +1,3 @@ -import 'package:bitcoindart/bitcoindart.dart'; import 'package:decimal/decimal.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:hive/hive.dart'; @@ -140,7 +139,7 @@ void main() { test("invalid mainnet bitcoincash legacy/p2pkh address", () { expect( mainnetWallet?.validateAddress("mhqpGtwhcR6gFuuRjLTpHo41919QfuGy8Y"), - true); + false); expect(secureStore?.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); From b4d97e86cc1c6d3a00e62e0d614729aa1ea3c324 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 4 Nov 2022 18:26:23 -0600 Subject: [PATCH 17/76] long address fix --- .../transaction_details_view.dart | 141 +++++++++--------- 1 file changed, 73 insertions(+), 68 deletions(-) diff --git a/lib/pages/wallet_view/transaction_views/transaction_details_view.dart b/lib/pages/wallet_view/transaction_views/transaction_details_view.dart index 6f23f2e01..1c2fb8e5d 100644 --- a/lib/pages/wallet_view/transaction_views/transaction_details_view.dart +++ b/lib/pages/wallet_view/transaction_views/transaction_details_view.dart @@ -471,75 +471,80 @@ class _TransactionDetailsViewState MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + _transaction.txType.toLowerCase() == + "sent" + ? "Sent to" + : "Receiving address", + style: isDesktop + ? STextStyles + .desktopTextExtraExtraSmall( + context) + : STextStyles.itemSubtitle( + context), + ), + const SizedBox( + height: 8, + ), _transaction.txType.toLowerCase() == - "sent" - ? "Sent to" - : "Receiving address", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context) - : STextStyles.itemSubtitle(context), - ), - const SizedBox( - height: 8, - ), - _transaction.txType.toLowerCase() == - "received" - ? FutureBuilder( - future: fetchContactNameFor( - _transaction.address), - builder: (builderContext, - AsyncSnapshot - snapshot) { - String addressOrContactName = - _transaction.address; - if (snapshot.connectionState == - ConnectionState.done && - snapshot.hasData) { - addressOrContactName = - snapshot.data!; - } - return SelectableText( - addressOrContactName, - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context) - .copyWith( - color: Theme.of( - context) - .extension< - StackColors>()! - .textDark, - ) - : STextStyles - .itemSubtitle12( - context), - ); - }, - ) - : SelectableText( - _transaction.address, - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context) - .copyWith( - color: Theme.of(context) - .extension< - StackColors>()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context), - ), - ], + "received" + ? FutureBuilder( + future: fetchContactNameFor( + _transaction.address), + builder: (builderContext, + AsyncSnapshot + snapshot) { + String addressOrContactName = + _transaction.address; + if (snapshot.connectionState == + ConnectionState + .done && + snapshot.hasData) { + addressOrContactName = + snapshot.data!; + } + return SelectableText( + addressOrContactName, + style: isDesktop + ? STextStyles + .desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of( + context) + .extension< + StackColors>()! + .textDark, + ) + : STextStyles + .itemSubtitle12( + context), + ); + }, + ) + : SelectableText( + _transaction.address, + style: isDesktop + ? STextStyles + .desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension< + StackColors>()! + .textDark, + ) + : STextStyles + .itemSubtitle12( + context), + ), + ], + ), ), if (isDesktop) IconCopyButton( From e87aa64e1b79c9671f9ba888cd4188c78fca2fb1 Mon Sep 17 00:00:00 2001 From: julian Date: Sat, 5 Nov 2022 09:40:28 -0600 Subject: [PATCH 18/76] isFavorite bandaid fix for https://github.com/cypherstack/stack_wallet/issues/203 --- lib/services/coins/bitcoin/bitcoin_wallet.dart | 7 ++++--- lib/services/coins/bitcoincash/bitcoincash_wallet.dart | 7 ++++--- lib/services/coins/dogecoin/dogecoin_wallet.dart | 7 ++++--- lib/services/coins/epiccash/epiccash_wallet.dart | 7 ++++--- lib/services/coins/firo/firo_wallet.dart | 7 ++++--- lib/services/coins/litecoin/litecoin_wallet.dart | 7 ++++--- lib/services/coins/monero/monero_wallet.dart | 7 ++++--- lib/services/coins/namecoin/namecoin_wallet.dart | 7 ++++--- lib/services/coins/wownero/wownero_wallet.dart | 7 ++++--- 9 files changed, 36 insertions(+), 27 deletions(-) diff --git a/lib/services/coins/bitcoin/bitcoin_wallet.dart b/lib/services/coins/bitcoin/bitcoin_wallet.dart index da3bdfed0..391beb909 100644 --- a/lib/services/coins/bitcoin/bitcoin_wallet.dart +++ b/lib/services/coins/bitcoin/bitcoin_wallet.dart @@ -174,9 +174,10 @@ class BitcoinWallet extends CoinServiceAPI { return DB.instance.get(boxName: walletId, key: "isFavorite") as bool; } catch (e, s) { - Logging.instance - .log("isFavorite fetch failed: $e\n$s", level: LogLevel.Error); - rethrow; + Logging.instance.log( + "isFavorite fetch failed (returning false by default): $e\n$s", + level: LogLevel.Error); + return false; } } diff --git a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart index b96aa160c..fa88b3f2f 100644 --- a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart +++ b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart @@ -3376,9 +3376,10 @@ class BitcoinCashWallet extends CoinServiceAPI { return DB.instance.get(boxName: walletId, key: "isFavorite") as bool; } catch (e, s) { - Logging.instance - .log("isFavorite fetch failed: $e\n$s", level: LogLevel.Error); - rethrow; + Logging.instance.log( + "isFavorite fetch failed (returning false by default): $e\n$s", + level: LogLevel.Error); + return false; } } diff --git a/lib/services/coins/dogecoin/dogecoin_wallet.dart b/lib/services/coins/dogecoin/dogecoin_wallet.dart index 0235a0c02..fbb551dcd 100644 --- a/lib/services/coins/dogecoin/dogecoin_wallet.dart +++ b/lib/services/coins/dogecoin/dogecoin_wallet.dart @@ -2983,9 +2983,10 @@ class DogecoinWallet extends CoinServiceAPI { return DB.instance.get(boxName: walletId, key: "isFavorite") as bool; } catch (e, s) { - Logging.instance - .log("isFavorite fetch failed: $e\n$s", level: LogLevel.Error); - rethrow; + Logging.instance.log( + "isFavorite fetch failed (returning false by default): $e\n$s", + level: LogLevel.Error); + return false; } } diff --git a/lib/services/coins/epiccash/epiccash_wallet.dart b/lib/services/coins/epiccash/epiccash_wallet.dart index 7ccb7feaf..b98854e61 100644 --- a/lib/services/coins/epiccash/epiccash_wallet.dart +++ b/lib/services/coins/epiccash/epiccash_wallet.dart @@ -558,9 +558,10 @@ class EpicCashWallet extends CoinServiceAPI { return DB.instance.get(boxName: walletId, key: "isFavorite") as bool; } catch (e, s) { - Logging.instance - .log("isFavorite fetch failed: $e\n$s", level: LogLevel.Error); - rethrow; + Logging.instance.log( + "isFavorite fetch failed (returning false by default): $e\n$s", + level: LogLevel.Error); + return false; } } diff --git a/lib/services/coins/firo/firo_wallet.dart b/lib/services/coins/firo/firo_wallet.dart index 61ef2e9de..2a2102e7c 100644 --- a/lib/services/coins/firo/firo_wallet.dart +++ b/lib/services/coins/firo/firo_wallet.dart @@ -821,9 +821,10 @@ class FiroWallet extends CoinServiceAPI { return DB.instance.get(boxName: walletId, key: "isFavorite") as bool; } catch (e, s) { - Logging.instance - .log("isFavorite fetch failed: $e\n$s", level: LogLevel.Error); - rethrow; + Logging.instance.log( + "isFavorite fetch failed (returning false by default): $e\n$s", + level: LogLevel.Error); + return false; } } diff --git a/lib/services/coins/litecoin/litecoin_wallet.dart b/lib/services/coins/litecoin/litecoin_wallet.dart index 0ab3a92a8..c07cca1f3 100644 --- a/lib/services/coins/litecoin/litecoin_wallet.dart +++ b/lib/services/coins/litecoin/litecoin_wallet.dart @@ -174,9 +174,10 @@ class LitecoinWallet extends CoinServiceAPI { return DB.instance.get(boxName: walletId, key: "isFavorite") as bool; } catch (e, s) { - Logging.instance - .log("isFavorite fetch failed: $e\n$s", level: LogLevel.Error); - rethrow; + Logging.instance.log( + "isFavorite fetch failed (returning false by default): $e\n$s", + level: LogLevel.Error); + return false; } } diff --git a/lib/services/coins/monero/monero_wallet.dart b/lib/services/coins/monero/monero_wallet.dart index b0ebac4e6..9bcc3515f 100644 --- a/lib/services/coins/monero/monero_wallet.dart +++ b/lib/services/coins/monero/monero_wallet.dart @@ -1376,9 +1376,10 @@ class MoneroWallet extends CoinServiceAPI { return DB.instance.get(boxName: walletId, key: "isFavorite") as bool; } catch (e, s) { - Logging.instance - .log("isFavorite fetch failed: $e\n$s", level: LogLevel.Error); - rethrow; + Logging.instance.log( + "isFavorite fetch failed (returning false by default): $e\n$s", + level: LogLevel.Error); + return false; } } diff --git a/lib/services/coins/namecoin/namecoin_wallet.dart b/lib/services/coins/namecoin/namecoin_wallet.dart index e9cae1ab2..893db69e0 100644 --- a/lib/services/coins/namecoin/namecoin_wallet.dart +++ b/lib/services/coins/namecoin/namecoin_wallet.dart @@ -170,9 +170,10 @@ class NamecoinWallet extends CoinServiceAPI { return DB.instance.get(boxName: walletId, key: "isFavorite") as bool; } catch (e, s) { - Logging.instance - .log("isFavorite fetch failed: $e\n$s", level: LogLevel.Error); - rethrow; + Logging.instance.log( + "isFavorite fetch failed (returning false by default): $e\n$s", + level: LogLevel.Error); + return false; } } diff --git a/lib/services/coins/wownero/wownero_wallet.dart b/lib/services/coins/wownero/wownero_wallet.dart index d3aba5bbb..342e5d84a 100644 --- a/lib/services/coins/wownero/wownero_wallet.dart +++ b/lib/services/coins/wownero/wownero_wallet.dart @@ -1382,9 +1382,10 @@ class WowneroWallet extends CoinServiceAPI { return DB.instance.get(boxName: walletId, key: "isFavorite") as bool; } catch (e, s) { - Logging.instance - .log("isFavorite fetch failed: $e\n$s", level: LogLevel.Error); - rethrow; + Logging.instance.log( + "isFavorite fetch failed (returning false by default): $e\n$s", + level: LogLevel.Error); + return false; } } From 1e61a779ecd8620aa44c472dcd94ab49ba00d73a Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Mon, 7 Nov 2022 08:15:44 -0700 Subject: [PATCH 19/76] desktop support view route added --- lib/pages_desktop_specific/home/desktop_home_view.dart | 7 +++++-- .../home/support_and_about_view/desktop_support_view.dart | 0 lib/route_generator.dart | 7 +++++++ 3 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 lib/pages_desktop_specific/home/support_and_about_view/desktop_support_view.dart diff --git a/lib/pages_desktop_specific/home/desktop_home_view.dart b/lib/pages_desktop_specific/home/desktop_home_view.dart index 6aa104081..e9f6f2b4b 100644 --- a/lib/pages_desktop_specific/home/desktop_home_view.dart +++ b/lib/pages_desktop_specific/home/desktop_home_view.dart @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/pages_desktop_specific/home/desktop_menu.dart'; import 'package:stackwallet/pages_desktop_specific/home/desktop_settings_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/my_stack_view.dart'; +import 'package:stackwallet/pages_desktop_specific/home/support_and_about_view/desktop_support_view.dart'; import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; @@ -37,8 +38,10 @@ class _DesktopHomeViewState extends ConsumerState { onGenerateRoute: RouteGenerator.generateRoute, initialRoute: DesktopSettingsView.routeName, ), - Container( - color: Colors.blue, + const Navigator( + key: Key("desktopSupportHomeKey"), + onGenerateRoute: RouteGenerator.generateRoute, + initialRoute: DesktopSupportView.routeName, ), Container( color: Colors.pink, diff --git a/lib/pages_desktop_specific/home/support_and_about_view/desktop_support_view.dart b/lib/pages_desktop_specific/home/support_and_about_view/desktop_support_view.dart new file mode 100644 index 000000000..e69de29bb diff --git a/lib/route_generator.dart b/lib/route_generator.dart index a6f23ffdc..47a84f07c 100644 --- a/lib/route_generator.dart +++ b/lib/route_generator.dart @@ -99,6 +99,7 @@ import 'package:stackwallet/pages_desktop_specific/home/settings_menu/nodes_sett import 'package:stackwallet/pages_desktop_specific/home/settings_menu/security_settings.dart'; import 'package:stackwallet/pages_desktop_specific/home/settings_menu/settings_menu.dart'; import 'package:stackwallet/pages_desktop_specific/home/settings_menu/syncing_preferences_settings.dart'; +import 'package:stackwallet/pages_desktop_specific/home/support_and_about_view/desktop_support_view.dart'; import 'package:stackwallet/services/coins/manager.dart'; import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; @@ -1084,6 +1085,12 @@ class RouteGenerator { builder: (_) => const AdvancedSettings(), settings: RouteSettings(name: settings.name)); + case DesktopSupportView.routeName: + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => const DesktopSupportView(), + settings: RouteSettings(name: settings.name)); + case WalletKeysDesktopPopup.routeName: if (args is List) { return FadePageRoute( From dbb2b309ca243e650660bd13af1551cfe8f94f0c Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Mon, 7 Nov 2022 09:15:02 -0700 Subject: [PATCH 20/76] blue link text added to desktop --- .../global_settings_view/support_view.dart | 535 +++++++++++------- .../desktop_support_view.dart | 50 ++ 2 files changed, 366 insertions(+), 219 deletions(-) diff --git a/lib/pages/settings_views/global_settings_view/support_view.dart b/lib/pages/settings_views/global_settings_view/support_view.dart index fdfa6f404..20aeedf61 100644 --- a/lib/pages/settings_views/global_settings_view/support_view.dart +++ b/lib/pages/settings_views/global_settings_view/support_view.dart @@ -4,7 +4,10 @@ import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -18,269 +21,363 @@ class SupportView extends StatelessWidget { @override Widget build(BuildContext context) { + final isDesktop = Util.isDesktop; + debugPrint("BUILD: $runtimeType"); - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () { - Navigator.of(context).pop(); - }, - ), - title: Text( - "Support", - style: STextStyles.navBarTitle(context), - ), - ), - body: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - RoundedWhiteContainer( - child: Text( - "If you need support or want to report a bug, reach out to us on any of our socials!", - style: STextStyles.smallMed12(context), - ), + return ConditionalParent( + condition: !isDesktop, + builder: (child) { + return Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, ), - const SizedBox( - height: 12, + title: Text( + "Support", + style: STextStyles.navBarTitle(context), ), - RoundedWhiteContainer( - padding: const EdgeInsets.all(0), - child: RawMaterialButton( - // splashColor: Theme.of(context).extension()!.highlight, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), + ), + body: Padding( + padding: const EdgeInsets.all(16), + child: child, + ), + ); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + RoundedWhiteContainer( + child: Text( + "If you need support or want to report a bug, reach out to us on any of our socials!", + style: STextStyles.smallMed12(context), + ), + ), + isDesktop + ? const SizedBox( + height: 24, + ) + : const SizedBox( + height: 12, ), - onPressed: () { + RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: RawMaterialButton( + // splashColor: Theme.of(context).extension()!.highlight, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: () { + if (!isDesktop) { launchUrl( Uri.parse("https://t.me/stackwallet"), mode: LaunchMode.externalApplication, ); - }, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 20, - ), - child: Row( - children: [ - SvgPicture.asset( - Assets.socials.telegram, - width: iconSize, - height: iconSize, - color: Theme.of(context) - .extension()! - .accentColorDark, - ), - const SizedBox( - width: 12, - ), - Text( - "Telegram", - style: STextStyles.titleBold12(context), - textAlign: TextAlign.left, - ), - ], - ), + } + }, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 20, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + SvgPicture.asset( + Assets.socials.telegram, + width: iconSize, + height: iconSize, + color: Theme.of(context) + .extension()! + .accentColorDark, + ), + const SizedBox( + width: 12, + ), + Text( + "Telegram", + style: STextStyles.titleBold12(context), + textAlign: TextAlign.left, + ), + ], + ), + BlueTextButton( + text: isDesktop ? "@stackwallet" : "", + onTap: () { + launchUrl( + Uri.parse("https://t.me/stackwallet"), + mode: LaunchMode.externalApplication, + ); + }, + ), + ], ), ), ), - const SizedBox( - height: 8, - ), - RoundedWhiteContainer( - padding: const EdgeInsets.all(0), - child: RawMaterialButton( - // splashColor: Theme.of(context).extension()!.highlight, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), + ), + const SizedBox( + height: 8, + ), + RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: RawMaterialButton( + // splashColor: Theme.of(context).extension()!.highlight, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, ), - onPressed: () { + ), + onPressed: () { + if (!isDesktop) { launchUrl( Uri.parse("https://discord.gg/RZMG3yUm"), mode: LaunchMode.externalApplication, ); - }, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 20, - ), - child: Row( - children: [ - SvgPicture.asset( - Assets.socials.discord, - width: iconSize, - height: iconSize, - color: Theme.of(context) - .extension()! - .accentColorDark, - ), - const SizedBox( - width: 12, - ), - Text( - "Discord", - style: STextStyles.titleBold12(context), - textAlign: TextAlign.left, - ), - ], - ), + } + }, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 20, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + SvgPicture.asset( + Assets.socials.discord, + width: iconSize, + height: iconSize, + color: Theme.of(context) + .extension()! + .accentColorDark, + ), + const SizedBox( + width: 12, + ), + Text( + "Discord", + style: STextStyles.titleBold12(context), + textAlign: TextAlign.left, + ), + ], + ), + BlueTextButton( + text: isDesktop ? "Stack Wallet" : "", + onTap: () { + launchUrl( + Uri.parse( + "https://discord.gg/RZMG3yUm"), //expired link? + mode: LaunchMode.externalApplication, + ); + }, + ), + ], ), ), ), - const SizedBox( - height: 8, - ), - RoundedWhiteContainer( - padding: const EdgeInsets.all(0), - child: RawMaterialButton( - // splashColor: Theme.of(context).extension()!.highlight, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), + ), + const SizedBox( + height: 8, + ), + RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: RawMaterialButton( + // splashColor: Theme.of(context).extension()!.highlight, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, ), - onPressed: () { + ), + onPressed: () { + if (!isDesktop) { launchUrl( Uri.parse("https://www.reddit.com/r/stackwallet/"), mode: LaunchMode.externalApplication, ); - }, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 20, - ), - child: Row( - children: [ - SvgPicture.asset( - Assets.socials.reddit, - width: iconSize, - height: iconSize, - color: Theme.of(context) - .extension()! - .accentColorDark, - ), - const SizedBox( - width: 12, - ), - Text( - "Reddit", - style: STextStyles.titleBold12(context), - textAlign: TextAlign.left, - ), - ], - ), + } + }, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 20, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + SvgPicture.asset( + Assets.socials.reddit, + width: iconSize, + height: iconSize, + color: Theme.of(context) + .extension()! + .accentColorDark, + ), + const SizedBox( + width: 12, + ), + Text( + "Reddit", + style: STextStyles.titleBold12(context), + textAlign: TextAlign.left, + ), + ], + ), + BlueTextButton( + text: isDesktop ? "r/stackwallet" : "", + onTap: () { + launchUrl( + Uri.parse("https://www.reddit.com/r/stackwallet/"), + mode: LaunchMode.externalApplication, + ); + }, + ), + ], ), ), ), - const SizedBox( - height: 8, - ), - RoundedWhiteContainer( - padding: const EdgeInsets.all(0), - child: RawMaterialButton( - // splashColor: Theme.of(context).extension()!.highlight, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), + ), + const SizedBox( + height: 8, + ), + RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: RawMaterialButton( + // splashColor: Theme.of(context).extension()!.highlight, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, ), - onPressed: () { + ), + onPressed: () { + if (!isDesktop) { launchUrl( Uri.parse("https://twitter.com/stack_wallet"), mode: LaunchMode.externalApplication, ); - }, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 20, - ), - child: Row( - children: [ - SvgPicture.asset( - Assets.socials.twitter, - width: iconSize, - height: iconSize, - color: Theme.of(context) - .extension()! - .accentColorDark, - ), - const SizedBox( - width: 12, - ), - Text( - "Twitter", - style: STextStyles.titleBold12(context), - textAlign: TextAlign.left, - ), - ], - ), + } + }, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 20, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + SvgPicture.asset( + Assets.socials.twitter, + width: iconSize, + height: iconSize, + color: Theme.of(context) + .extension()! + .accentColorDark, + ), + const SizedBox( + width: 12, + ), + Text( + "Twitter", + style: STextStyles.titleBold12(context), + textAlign: TextAlign.left, + ), + ], + ), + BlueTextButton( + text: isDesktop ? "@stack_wallet" : "", + onTap: () { + launchUrl( + Uri.parse("https://twitter.com/stack_wallet"), + mode: LaunchMode.externalApplication, + ); + }, + ), + ], ), ), ), - const SizedBox( - height: 8, - ), - RoundedWhiteContainer( - padding: const EdgeInsets.all(0), - child: RawMaterialButton( - // splashColor: Theme.of(context).extension()!.highlight, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), + ), + const SizedBox( + height: 8, + ), + RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: RawMaterialButton( + // splashColor: Theme.of(context).extension()!.highlight, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, ), - onPressed: () { + ), + onPressed: () { + if (!isDesktop) { launchUrl( Uri.parse("mailto://support@stackwallet.com"), mode: LaunchMode.externalApplication, ); - }, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 20, - ), - child: Row( - children: [ - SvgPicture.asset( - Assets.svg.envelope, - width: iconSize, - height: iconSize, - color: Theme.of(context) - .extension()! - .accentColorDark, - ), - const SizedBox( - width: 12, - ), - Text( - "Email", - style: STextStyles.titleBold12(context), - textAlign: TextAlign.left, - ), - ], - ), + } + }, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 20, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + SvgPicture.asset( + Assets.svg.envelope, + width: iconSize, + height: iconSize, + color: Theme.of(context) + .extension()! + .accentColorDark, + ), + const SizedBox( + width: 12, + ), + Text( + "Email", + style: STextStyles.titleBold12(context), + textAlign: TextAlign.left, + ), + ], + ), + BlueTextButton( + text: isDesktop ? "support@stackwallet.com" : "", + onTap: () { + launchUrl( + Uri.parse("mailto://support@stackwallet.com"), + mode: LaunchMode.externalApplication, + ); + }, + ), + ], ), ), ), - ], - ), + ), + ], ), ); } diff --git a/lib/pages_desktop_specific/home/support_and_about_view/desktop_support_view.dart b/lib/pages_desktop_specific/home/support_and_about_view/desktop_support_view.dart index e69de29bb..8e9d709d1 100644 --- a/lib/pages_desktop_specific/home/support_and_about_view/desktop_support_view.dart +++ b/lib/pages_desktop_specific/home/support_and_about_view/desktop_support_view.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; +import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; + +import '../../../pages/settings_views/global_settings_view/support_view.dart'; + +class DesktopSupportView extends ConsumerStatefulWidget { + const DesktopSupportView({Key? key}) : super(key: key); + + static const String routeName = "/desktopSupportView"; + + @override + ConsumerState createState() => _DesktopSupportView(); +} + +class _DesktopSupportView extends ConsumerState { + @override + Widget build(BuildContext context) { + debugPrint("BUILD: $runtimeType"); + return DesktopScaffold( + background: Theme.of(context).extension()!.background, + appBar: DesktopAppBar( + isCompactHeight: true, + leading: Row( + children: [ + const SizedBox( + width: 24, + height: 24, + ), + Text( + "Support", + style: STextStyles.desktopH3(context), + ) + ], + ), + ), + body: Column( + children: const [ + Padding( + padding: EdgeInsets.fromLTRB(24, 10, 377, 270), + child: SupportView(), + ), + ], + ), + ); + } +} From 786831bcef80f10660f8f73d8708e1c57dffd497 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 7 Nov 2022 08:31:27 -0600 Subject: [PATCH 21/76] alphabetically sort contacts --- lib/services/address_book_service.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/services/address_book_service.dart b/lib/services/address_book_service.dart index 6f7d2b9bd..f51eefbba 100644 --- a/lib/services/address_book_service.dart +++ b/lib/services/address_book_service.dart @@ -20,10 +20,13 @@ class AddressBookService extends ChangeNotifier { List get contacts { final keys = List.from( DB.instance.keys(boxName: DB.boxNameAddressBook)); - return keys + final _contacts = keys .map((id) => Contact.fromJson(Map.from(DB.instance .get(boxName: DB.boxNameAddressBook, key: id) as Map))) .toList(growable: false); + _contacts + .sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase())); + return _contacts; } Future>? _addressBookEntries; From f6bad974e6915338ce94c22ea356833b8cb8c8c8 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 7 Nov 2022 10:14:47 -0600 Subject: [PATCH 22/76] address book tests updated --- test/address_book_service_test.dart | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/address_book_service_test.dart b/test/address_book_service_test.dart index 1059f7fc3..c5effd223 100644 --- a/test/address_book_service_test.dart +++ b/test/address_book_service_test.dart @@ -94,19 +94,19 @@ void main() { test("get contacts", () { final service = AddressBookService(); expect(service.contacts.toString(), - [contactA, contactB, contactC].toString()); + [contactC, contactB, contactA].toString()); }); test("get addressBookEntries", () async { final service = AddressBookService(); expect((await service.addressBookEntries).toString(), - [contactA, contactB, contactC].toString()); + [contactC, contactB, contactA].toString()); }); test("search contacts", () async { final service = AddressBookService(); final results = await service.search("j"); - expect(results.toString(), [contactA, contactB].toString()); + expect(results.toString(), [contactB, contactA].toString()); final results2 = await service.search("ja"); expect(results2.toString(), [contactB].toString()); @@ -118,7 +118,7 @@ void main() { expect(results4.toString(), [].toString()); final results5 = await service.search(""); - expect(results5.toString(), [contactA, contactB, contactC].toString()); + expect(results5.toString(), [contactC, contactB, contactA].toString()); final results6 = await service.search("epic address"); expect(results6.toString(), [contactC].toString()); @@ -140,7 +140,7 @@ void main() { expect(result, false); expect(service.contacts.length, 3); expect(service.contacts.toString(), - [contactA, contactB, contactC].toString()); + [contactC, contactB, contactA].toString()); }); test("edit contact", () async { @@ -149,14 +149,14 @@ void main() { expect(await service.editContact(editedContact), true); expect(service.contacts.length, 3); expect(service.contacts.toString(), - [contactA, editedContact, contactC].toString()); + [contactC, contactA, editedContact].toString()); }); test("remove existing contact", () async { final service = AddressBookService(); await service.removeContact(contactB.id); expect(service.contacts.length, 2); - expect(service.contacts.toString(), [contactA, contactC].toString()); + expect(service.contacts.toString(), [contactC, contactA].toString()); }); test("remove non existing contact", () async { @@ -164,7 +164,7 @@ void main() { await service.removeContact("some id"); expect(service.contacts.length, 3); expect(service.contacts.toString(), - [contactA, contactB, contactC].toString()); + [contactC, contactB, contactA].toString()); }); tearDown(() async { From bb260e3a23b7e0c1ddd4509d16a95db3a51305c0 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 7 Nov 2022 10:24:08 -0600 Subject: [PATCH 23/76] hacky fix (due to current persistence design) to get sent transactions showing up right away for electrumx coins --- .../send_view/confirm_transaction_view.dart | 4 +- .../coins/bitcoin/bitcoin_wallet.dart | 50 +++++++++++++++++++ .../coins/bitcoincash/bitcoincash_wallet.dart | 50 +++++++++++++++++++ lib/services/coins/coin_service.dart | 5 +- .../coins/dogecoin/dogecoin_wallet.dart | 50 +++++++++++++++++++ .../coins/epiccash/epiccash_wallet.dart | 8 +++ lib/services/coins/firo/firo_wallet.dart | 50 +++++++++++++++++++ .../coins/litecoin/litecoin_wallet.dart | 50 +++++++++++++++++++ lib/services/coins/manager.dart | 3 ++ lib/services/coins/monero/monero_wallet.dart | 8 +++ .../coins/namecoin/namecoin_wallet.dart | 50 +++++++++++++++++++ .../coins/wownero/wownero_wallet.dart | 8 +++ .../services/coins/fake_coin_service_api.dart | 6 +++ 13 files changed, 339 insertions(+), 3 deletions(-) diff --git a/lib/pages/send_view/confirm_transaction_view.dart b/lib/pages/send_view/confirm_transaction_view.dart index 81d5a3da2..26d1231f0 100644 --- a/lib/pages/send_view/confirm_transaction_view.dart +++ b/lib/pages/send_view/confirm_transaction_view.dart @@ -87,13 +87,13 @@ class _ConfirmTransactionViewState txid = await manager.confirmSend(txData: transactionInfo); } - unawaited(manager.refresh()); - // save note await ref .read(notesServiceChangeNotifierProvider(walletId)) .editOrAddNote(txid: txid, note: note); + unawaited(manager.refresh()); + // pop back to wallet if (mounted) { Navigator.of(context).popUntil(ModalRoute.withName(routeOnSuccessName)); diff --git a/lib/services/coins/bitcoin/bitcoin_wallet.dart b/lib/services/coins/bitcoin/bitcoin_wallet.dart index 391beb909..d0920075d 100644 --- a/lib/services/coins/bitcoin/bitcoin_wallet.dart +++ b/lib/services/coins/bitcoin/bitcoin_wallet.dart @@ -10,6 +10,7 @@ import 'package:bitcoindart/bitcoindart.dart'; import 'package:bs58check/bs58check.dart' as bs58check; import 'package:crypto/crypto.dart'; import 'package:decimal/decimal.dart'; +import 'package:devicelocale/devicelocale.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:http/http.dart'; @@ -1283,6 +1284,54 @@ class BitcoinWallet extends CoinServiceAPI { _transactionData ??= _fetchTransactionData(); Future? _transactionData; + TransactionData? cachedTxData; + + // hack to add tx to txData before refresh completes + // required based on current app architecture where we don't properly store + // transactions locally in a good way + @override + Future updateSentCachedTxData(Map txData) async { + final priceData = + await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); + Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; + final locale = await Devicelocale.currentLocale; + final String worthNow = Format.localizedStringAsFixed( + value: + ((currentPrice * Decimal.fromInt(txData["recipientAmt"] as int)) / + Decimal.fromInt(Constants.satsPerCoin)) + .toDecimal(scaleOnInfinitePrecision: 2), + decimalPlaces: 2, + locale: locale!); + + final tx = models.Transaction( + txid: txData["txid"] as String, + confirmedStatus: false, + timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, + txType: "Sent", + amount: txData["recipientAmt"] as int, + worthNow: worthNow, + worthAtBlockTimestamp: worthNow, + fees: txData["fee"] as int, + inputSize: 0, + outputSize: 0, + inputs: [], + outputs: [], + address: txData["address"] as String, + height: -1, + confirmations: 0, + ); + + if (cachedTxData == null) { + final data = await _fetchTransactionData(); + _transactionData = Future(() => data); + } + + final transactions = cachedTxData!.getAllTransactions(); + transactions[tx.txid] = tx; + cachedTxData = models.TransactionData.fromMap(transactions); + _transactionData = Future(() => cachedTxData!); + } + @override bool validateAddress(String address) { return Address.validateAddress(address, _network); @@ -2661,6 +2710,7 @@ class BitcoinWallet extends CoinServiceAPI { await DB.instance.put( boxName: walletId, key: 'latest_tx_model', value: txModel); + cachedTxData = txModel; return txModel; } diff --git a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart index fa88b3f2f..98a31ee0c 100644 --- a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart +++ b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart @@ -11,6 +11,7 @@ import 'package:bitcoindart/bitcoindart.dart'; import 'package:bs58check/bs58check.dart' as bs58check; import 'package:crypto/crypto.dart'; import 'package:decimal/decimal.dart'; +import 'package:devicelocale/devicelocale.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:http/http.dart'; @@ -1154,6 +1155,54 @@ class BitcoinCashWallet extends CoinServiceAPI { _transactionData ??= _fetchTransactionData(); Future? _transactionData; + TransactionData? cachedTxData; + + // hack to add tx to txData before refresh completes + // required based on current app architecture where we don't properly store + // transactions locally in a good way + @override + Future updateSentCachedTxData(Map txData) async { + final priceData = + await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); + Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; + final locale = await Devicelocale.currentLocale; + final String worthNow = Format.localizedStringAsFixed( + value: + ((currentPrice * Decimal.fromInt(txData["recipientAmt"] as int)) / + Decimal.fromInt(Constants.satsPerCoin)) + .toDecimal(scaleOnInfinitePrecision: 2), + decimalPlaces: 2, + locale: locale!); + + final tx = models.Transaction( + txid: txData["txid"] as String, + confirmedStatus: false, + timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, + txType: "Sent", + amount: txData["recipientAmt"] as int, + worthNow: worthNow, + worthAtBlockTimestamp: worthNow, + fees: txData["fee"] as int, + inputSize: 0, + outputSize: 0, + inputs: [], + outputs: [], + address: txData["address"] as String, + height: -1, + confirmations: 0, + ); + + if (cachedTxData == null) { + final data = await _fetchTransactionData(); + _transactionData = Future(() => data); + } + + final transactions = cachedTxData!.getAllTransactions(); + transactions[tx.txid] = tx; + cachedTxData = models.TransactionData.fromMap(transactions); + _transactionData = Future(() => cachedTxData!); + } + @override bool validateAddress(String address) { try { @@ -2449,6 +2498,7 @@ class BitcoinCashWallet extends CoinServiceAPI { await DB.instance.put( boxName: walletId, key: 'latest_tx_model', value: txModel); + cachedTxData = txModel; return txModel; } diff --git a/lib/services/coins/coin_service.dart b/lib/services/coins/coin_service.dart index c36fa9eee..655865494 100644 --- a/lib/services/coins/coin_service.dart +++ b/lib/services/coins/coin_service.dart @@ -9,8 +9,8 @@ import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart'; import 'package:stackwallet/services/coins/epiccash/epiccash_wallet.dart'; import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; import 'package:stackwallet/services/coins/monero/monero_wallet.dart'; -import 'package:stackwallet/services/coins/wownero/wownero_wallet.dart'; import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart'; +import 'package:stackwallet/services/coins/wownero/wownero_wallet.dart'; import 'package:stackwallet/services/transaction_notification_tracker.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/prefs.dart'; @@ -277,4 +277,7 @@ abstract class CoinServiceAPI { Future estimateFeeFor(int satoshiAmount, int feeRate); Future generateNewAddress(); + + // used for electrumx coins + Future updateSentCachedTxData(Map txData); } diff --git a/lib/services/coins/dogecoin/dogecoin_wallet.dart b/lib/services/coins/dogecoin/dogecoin_wallet.dart index fbb551dcd..67be291a2 100644 --- a/lib/services/coins/dogecoin/dogecoin_wallet.dart +++ b/lib/services/coins/dogecoin/dogecoin_wallet.dart @@ -10,6 +10,7 @@ import 'package:bitcoindart/bitcoindart.dart'; import 'package:bs58check/bs58check.dart' as bs58check; import 'package:crypto/crypto.dart'; import 'package:decimal/decimal.dart'; +import 'package:devicelocale/devicelocale.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:http/http.dart'; @@ -1051,6 +1052,54 @@ class DogecoinWallet extends CoinServiceAPI { _transactionData ??= _fetchTransactionData(); Future? _transactionData; + TransactionData? cachedTxData; + + // hack to add tx to txData before refresh completes + // required based on current app architecture where we don't properly store + // transactions locally in a good way + @override + Future updateSentCachedTxData(Map txData) async { + final priceData = + await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); + Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; + final locale = await Devicelocale.currentLocale; + final String worthNow = Format.localizedStringAsFixed( + value: + ((currentPrice * Decimal.fromInt(txData["recipientAmt"] as int)) / + Decimal.fromInt(Constants.satsPerCoin)) + .toDecimal(scaleOnInfinitePrecision: 2), + decimalPlaces: 2, + locale: locale!); + + final tx = models.Transaction( + txid: txData["txid"] as String, + confirmedStatus: false, + timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, + txType: "Sent", + amount: txData["recipientAmt"] as int, + worthNow: worthNow, + worthAtBlockTimestamp: worthNow, + fees: txData["fee"] as int, + inputSize: 0, + outputSize: 0, + inputs: [], + outputs: [], + address: txData["address"] as String, + height: -1, + confirmations: 0, + ); + + if (cachedTxData == null) { + final data = await _fetchTransactionData(); + _transactionData = Future(() => data); + } + + final transactions = cachedTxData!.getAllTransactions(); + transactions[tx.txid] = tx; + cachedTxData = models.TransactionData.fromMap(transactions); + _transactionData = Future(() => cachedTxData!); + } + @override bool validateAddress(String address) { return Address.validateAddress(address, _network); @@ -2273,6 +2322,7 @@ class DogecoinWallet extends CoinServiceAPI { await DB.instance.put( boxName: walletId, key: 'latest_tx_model', value: txModel); + cachedTxData = txModel; return txModel; } diff --git a/lib/services/coins/epiccash/epiccash_wallet.dart b/lib/services/coins/epiccash/epiccash_wallet.dart index b98854e61..3702a9158 100644 --- a/lib/services/coins/epiccash/epiccash_wallet.dart +++ b/lib/services/coins/epiccash/epiccash_wallet.dart @@ -2259,6 +2259,14 @@ class EpicCashWallet extends CoinServiceAPI { _transactionData ??= _fetchTransactionData(); Future? _transactionData; + // not used in epic + TransactionData? cachedTxData; + + @override + Future updateSentCachedTxData(Map txData) async { + // not used in epic + } + @override Future> get unspentOutputs => throw UnimplementedError(); diff --git a/lib/services/coins/firo/firo_wallet.dart b/lib/services/coins/firo/firo_wallet.dart index 2a2102e7c..d19c4f1ab 100644 --- a/lib/services/coins/firo/firo_wallet.dart +++ b/lib/services/coins/firo/firo_wallet.dart @@ -908,6 +908,52 @@ class FiroWallet extends CoinServiceAPI { Future get _txnData => _transactionData ??= _fetchTransactionData(); + models.TransactionData? cachedTxData; + + // hack to add tx to txData before refresh completes + // required based on current app architecture where we don't properly store + // transactions locally in a good way + @override + Future updateSentCachedTxData(Map txData) async { + final currentPrice = await firoPrice; + final locale = await Devicelocale.currentLocale; + final String worthNow = Format.localizedStringAsFixed( + value: + ((currentPrice * Decimal.fromInt(txData["recipientAmt"] as int)) / + Decimal.fromInt(Constants.satsPerCoin)) + .toDecimal(scaleOnInfinitePrecision: 2), + decimalPlaces: 2, + locale: locale!); + + final tx = models.Transaction( + txid: txData["txid"] as String, + confirmedStatus: false, + timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, + txType: "Sent", + amount: txData["recipientAmt"] as int, + worthNow: worthNow, + worthAtBlockTimestamp: worthNow, + fees: txData["fee"] as int, + inputSize: 0, + outputSize: 0, + inputs: [], + outputs: [], + address: txData["address"] as String, + height: -1, + confirmations: 0, + ); + + if (cachedTxData == null) { + final data = await _fetchTransactionData(); + _transactionData = Future(() => data); + } + + final transactions = cachedTxData!.getAllTransactions(); + transactions[tx.txid] = tx; + cachedTxData = models.TransactionData.fromMap(transactions); + _transactionData = Future(() => cachedTxData!); + } + /// Holds wallet lelantus transaction data Future? _lelantusTransactionData; Future get lelantusTransactionData => @@ -1110,6 +1156,9 @@ class FiroWallet extends CoinServiceAPI { final txHash = await _electrumXClient.broadcastTransaction( rawTx: txData["hex"] as String); Logging.instance.log("Sent txHash: $txHash", level: LogLevel.Info); + txData["txid"] = txHash; + // dirty ui update hack + await updateSentCachedTxData(txData as Map); return txHash; } catch (e, s) { Logging.instance.log("Exception rethrown from confirmSend(): $e\n$s", @@ -3465,6 +3514,7 @@ class FiroWallet extends CoinServiceAPI { await DB.instance.put( boxName: walletId, key: 'latest_tx_model', value: txModel); + cachedTxData = txModel; return txModel; } diff --git a/lib/services/coins/litecoin/litecoin_wallet.dart b/lib/services/coins/litecoin/litecoin_wallet.dart index c07cca1f3..4551325f7 100644 --- a/lib/services/coins/litecoin/litecoin_wallet.dart +++ b/lib/services/coins/litecoin/litecoin_wallet.dart @@ -10,6 +10,7 @@ import 'package:bitcoindart/bitcoindart.dart'; import 'package:bs58check/bs58check.dart' as bs58check; import 'package:crypto/crypto.dart'; import 'package:decimal/decimal.dart'; +import 'package:devicelocale/devicelocale.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:http/http.dart'; @@ -1285,6 +1286,54 @@ class LitecoinWallet extends CoinServiceAPI { _transactionData ??= _fetchTransactionData(); Future? _transactionData; + TransactionData? cachedTxData; + + // hack to add tx to txData before refresh completes + // required based on current app architecture where we don't properly store + // transactions locally in a good way + @override + Future updateSentCachedTxData(Map txData) async { + final priceData = + await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); + Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; + final locale = await Devicelocale.currentLocale; + final String worthNow = Format.localizedStringAsFixed( + value: + ((currentPrice * Decimal.fromInt(txData["recipientAmt"] as int)) / + Decimal.fromInt(Constants.satsPerCoin)) + .toDecimal(scaleOnInfinitePrecision: 2), + decimalPlaces: 2, + locale: locale!); + + final tx = models.Transaction( + txid: txData["txid"] as String, + confirmedStatus: false, + timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, + txType: "Sent", + amount: txData["recipientAmt"] as int, + worthNow: worthNow, + worthAtBlockTimestamp: worthNow, + fees: txData["fee"] as int, + inputSize: 0, + outputSize: 0, + inputs: [], + outputs: [], + address: txData["address"] as String, + height: -1, + confirmations: 0, + ); + + if (cachedTxData == null) { + final data = await _fetchTransactionData(); + _transactionData = Future(() => data); + } + + final transactions = cachedTxData!.getAllTransactions(); + transactions[tx.txid] = tx; + cachedTxData = models.TransactionData.fromMap(transactions); + _transactionData = Future(() => cachedTxData!); + } + @override bool validateAddress(String address) { return Address.validateAddress(address, _network, _network.bech32!); @@ -2673,6 +2722,7 @@ class LitecoinWallet extends CoinServiceAPI { await DB.instance.put( boxName: walletId, key: 'latest_tx_model', value: txModel); + cachedTxData = txModel; return txModel; } diff --git a/lib/services/coins/manager.dart b/lib/services/coins/manager.dart index c8329ec28..8054fe168 100644 --- a/lib/services/coins/manager.dart +++ b/lib/services/coins/manager.dart @@ -108,6 +108,9 @@ class Manager with ChangeNotifier { try { final txid = await _currentWallet.confirmSend(txData: txData); + txData["txid"] = txid; + await _currentWallet.updateSentCachedTxData(txData); + notifyListeners(); return txid; } catch (e) { diff --git a/lib/services/coins/monero/monero_wallet.dart b/lib/services/coins/monero/monero_wallet.dart index 9bcc3515f..662d4077b 100644 --- a/lib/services/coins/monero/monero_wallet.dart +++ b/lib/services/coins/monero/monero_wallet.dart @@ -1190,6 +1190,14 @@ class MoneroWallet extends CoinServiceAPI { _transactionData ??= _fetchTransactionData(); Future? _transactionData; + // not used in monero + TransactionData? cachedTxData; + + @override + Future updateSentCachedTxData(Map txData) async { + // not used in monero + } + Future _fetchTransactionData() async { final transactions = walletBase?.transactionHistory!.transactions; diff --git a/lib/services/coins/namecoin/namecoin_wallet.dart b/lib/services/coins/namecoin/namecoin_wallet.dart index 893db69e0..8a4b26012 100644 --- a/lib/services/coins/namecoin/namecoin_wallet.dart +++ b/lib/services/coins/namecoin/namecoin_wallet.dart @@ -10,6 +10,7 @@ import 'package:bitcoindart/bitcoindart.dart'; import 'package:bs58check/bs58check.dart' as bs58check; import 'package:crypto/crypto.dart'; import 'package:decimal/decimal.dart'; +import 'package:devicelocale/devicelocale.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:http/http.dart'; @@ -1276,6 +1277,54 @@ class NamecoinWallet extends CoinServiceAPI { _transactionData ??= _fetchTransactionData(); Future? _transactionData; + TransactionData? cachedTxData; + + // hack to add tx to txData before refresh completes + // required based on current app architecture where we don't properly store + // transactions locally in a good way + @override + Future updateSentCachedTxData(Map txData) async { + final priceData = + await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); + Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; + final locale = await Devicelocale.currentLocale; + final String worthNow = Format.localizedStringAsFixed( + value: + ((currentPrice * Decimal.fromInt(txData["recipientAmt"] as int)) / + Decimal.fromInt(Constants.satsPerCoin)) + .toDecimal(scaleOnInfinitePrecision: 2), + decimalPlaces: 2, + locale: locale!); + + final tx = models.Transaction( + txid: txData["txid"] as String, + confirmedStatus: false, + timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, + txType: "Sent", + amount: txData["recipientAmt"] as int, + worthNow: worthNow, + worthAtBlockTimestamp: worthNow, + fees: txData["fee"] as int, + inputSize: 0, + outputSize: 0, + inputs: [], + outputs: [], + address: txData["address"] as String, + height: -1, + confirmations: 0, + ); + + if (cachedTxData == null) { + final data = await _fetchTransactionData(); + _transactionData = Future(() => data); + } + + final transactions = cachedTxData!.getAllTransactions(); + transactions[tx.txid] = tx; + cachedTxData = models.TransactionData.fromMap(transactions); + _transactionData = Future(() => cachedTxData!); + } + @override bool validateAddress(String address) { return Address.validateAddress(address, _network, namecoin.bech32!); @@ -2673,6 +2722,7 @@ class NamecoinWallet extends CoinServiceAPI { await DB.instance.put( boxName: walletId, key: 'latest_tx_model', value: txModel); + cachedTxData = txModel; return txModel; } diff --git a/lib/services/coins/wownero/wownero_wallet.dart b/lib/services/coins/wownero/wownero_wallet.dart index 342e5d84a..72f43eac8 100644 --- a/lib/services/coins/wownero/wownero_wallet.dart +++ b/lib/services/coins/wownero/wownero_wallet.dart @@ -1195,6 +1195,14 @@ class WowneroWallet extends CoinServiceAPI { _transactionData ??= _fetchTransactionData(); Future? _transactionData; + // not used in wownero + TransactionData? cachedTxData; + + @override + Future updateSentCachedTxData(Map txData) async { + // not used in wownero + } + Future _fetchTransactionData() async { final transactions = walletBase?.transactionHistory!.transactions; diff --git a/test/services/coins/fake_coin_service_api.dart b/test/services/coins/fake_coin_service_api.dart index a3ae28a4b..c5f300c16 100644 --- a/test/services/coins/fake_coin_service_api.dart +++ b/test/services/coins/fake_coin_service_api.dart @@ -182,4 +182,10 @@ class FakeCoinServiceAPI extends CoinServiceAPI { // TODO: implement generateNewAddress throw UnimplementedError(); } + + @override + Future updateSentCachedTxData(Map txData) { + // TODO: implement updateSentCachedTxData + throw UnimplementedError(); + } } From 6223df54320afea50d258580d4780b820491d35d Mon Sep 17 00:00:00 2001 From: sneurlax Date: Mon, 7 Nov 2022 11:21:10 -0600 Subject: [PATCH 24/76] specify tests for 14 word seeds and add more error checking code to test and update ref to flutter_libmonero enabling tests (mocked storage, etc) --- crypto_plugins/flutter_libmonero | 2 +- .../coins/wownero/wownero_wallet_test.dart | 45 +++++++++++-------- .../wownero/wownero_wallet_test_data.dart | 4 +- 3 files changed, 30 insertions(+), 21 deletions(-) diff --git a/crypto_plugins/flutter_libmonero b/crypto_plugins/flutter_libmonero index 277d922c3..d92fea3e6 160000 --- a/crypto_plugins/flutter_libmonero +++ b/crypto_plugins/flutter_libmonero @@ -1 +1 @@ -Subproject commit 277d922c3b1d637c1ccda25f51395c618d293015 +Subproject commit d92fea3e6b915b79697deafbd5710fb4c15bfc76 diff --git a/test/services/coins/wownero/wownero_wallet_test.dart b/test/services/coins/wownero/wownero_wallet_test.dart index e64dd772c..83b4844c7 100644 --- a/test/services/coins/wownero/wownero_wallet_test.dart +++ b/test/services/coins/wownero/wownero_wallet_test.dart @@ -83,13 +83,13 @@ void main() async { _walletInfoSource = await Hive.openBox(WalletInfo.boxName); walletService = wownero.createWowneroWalletService(_walletInfoSource); - group("Wownero tests", () { + group("Wownero 14 word tests", () { setUp(() async { try { final dirPath = await pathForWalletDir(name: name, type: type); path = await pathForWallet(name: name, type: type); credentials = wownero.createWowneroRestoreWalletFromSeedCredentials( - name: name, height: 465760, mnemonic: testMnemonic); + name: name, height: 465760, mnemonic: testMnemonic14); walletInfo = WalletInfo.external( id: WalletBase.idFor(name, type), @@ -116,27 +116,36 @@ void main() async { } }); - test("Test mainnet address generation from seed", () async { + test("Test mainnet address generation from 14 word seed", () async { final wallet = await _walletCreationService.restoreFromSeed(credentials); walletInfo.address = wallet.walletAddresses.address; - await _walletInfoSource.add(walletInfo); + bool hasThrown = false; + try { + await _walletInfoSource.add(walletInfo); + walletBase?.close(); + walletBase = wallet as WowneroWalletBase; + + expect(walletInfo.address, mainnetTestData14[0][0]); + expect( + await walletBase!.getTransactionAddress(0, 0), mainnetTestData14[0][0]); + expect( + await walletBase!.getTransactionAddress(0, 1), mainnetTestData14[0][1]); + expect( + await walletBase!.getTransactionAddress(0, 2), mainnetTestData14[0][2]); + expect( + await walletBase!.getTransactionAddress(1, 0), mainnetTestData14[1][0]); + expect( + await walletBase!.getTransactionAddress(1, 1), mainnetTestData14[1][1]); + expect( + await walletBase!.getTransactionAddress(1, 2), mainnetTestData14[1][2]); + } catch (_) { + hasThrown = true; + } + expect(hasThrown, false); + walletBase?.close(); walletBase = wallet as WowneroWalletBase; - - expect(walletInfo.address, mainnetTestData[0][0]); - expect( - await walletBase!.getTransactionAddress(0, 0), mainnetTestData[0][0]); - expect( - await walletBase!.getTransactionAddress(0, 1), mainnetTestData[0][1]); - expect( - await walletBase!.getTransactionAddress(0, 2), mainnetTestData[0][2]); - expect( - await walletBase!.getTransactionAddress(1, 0), mainnetTestData[1][0]); - expect( - await walletBase!.getTransactionAddress(1, 1), mainnetTestData[1][1]); - expect( - await walletBase!.getTransactionAddress(1, 2), mainnetTestData[1][2]); }); }); } diff --git a/test/services/coins/wownero/wownero_wallet_test_data.dart b/test/services/coins/wownero/wownero_wallet_test_data.dart index b0d93a448..8ab825dfa 100644 --- a/test/services/coins/wownero/wownero_wallet_test_data.dart +++ b/test/services/coins/wownero/wownero_wallet_test_data.dart @@ -1,6 +1,6 @@ -String testMnemonic = +String testMnemonic14 = 'weather cruise school such silly profit clerk wage reduce obtain ill sand episode shadow'; -var mainnetTestData = [ +var mainnetTestData14 = [ [ 'Wo3jmHvTMLwE6h29fpgcb8PbJSpaKuqM7XTXVfiiu8bLCZsJvrQCbQSJR48Vo3BWNQKsMsXZ4VixndXTH25QtorC27NCjmsEi', 'WW3K54QzmMFB1uTZh3LVvgQYqANLmX1FkJHLJ4sU1E7BQmp8nGizyBnjNXSgsjCa4BQ3Rw3GG5jw1ByUkaUjSywm2KmHAbFvK', From c88971ebd6eaf7e278591eef2925c1fe6b8a2081 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 7 Nov 2022 11:46:17 -0600 Subject: [PATCH 25/76] firo pub/priv balance send from choice on exchange flow --- .../confirm_change_now_send.dart | 13 +- lib/pages/exchange_view/send_from_view.dart | 559 ++++++++++++------ 2 files changed, 404 insertions(+), 168 deletions(-) diff --git a/lib/pages/exchange_view/confirm_change_now_send.dart b/lib/pages/exchange_view/confirm_change_now_send.dart index d77ad6b8c..e99cf2df4 100644 --- a/lib/pages/exchange_view/confirm_change_now_send.dart +++ b/lib/pages/exchange_view/confirm_change_now_send.dart @@ -10,6 +10,7 @@ import 'package:stackwallet/pages/wallet_view/wallet_view.dart'; import 'package:stackwallet/providers/exchange/trade_sent_from_stack_lookup_provider.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/route_generator.dart'; +import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/text_styles.dart'; @@ -27,6 +28,7 @@ class ConfirmChangeNowSendView extends ConsumerStatefulWidget { required this.walletId, this.routeOnSuccessName = WalletView.routeName, required this.trade, + this.shouldSendPublicFiroFunds, }) : super(key: key); static const String routeName = "/confirmChangeNowSend"; @@ -35,6 +37,7 @@ class ConfirmChangeNowSendView extends ConsumerStatefulWidget { final String walletId; final String routeOnSuccessName; final Trade trade; + final bool? shouldSendPublicFiroFunds; @override ConsumerState createState() => @@ -63,7 +66,15 @@ class _ConfirmChangeNowSendViewState ref.read(walletsChangeNotifierProvider).getManager(walletId); try { - final txid = await manager.confirmSend(txData: transactionInfo); + late final String txid; + + if (widget.shouldSendPublicFiroFunds == true) { + txid = await (manager.wallet as FiroWallet) + .confirmSendPublic(txData: transactionInfo); + } else { + txid = await manager.confirmSend(txData: transactionInfo); + } + unawaited(manager.refresh()); // save note diff --git a/lib/pages/exchange_view/send_from_view.dart b/lib/pages/exchange_view/send_from_view.dart index 20fc81903..c87175955 100644 --- a/lib/pages/exchange_view/send_from_view.dart +++ b/lib/pages/exchange_view/send_from_view.dart @@ -10,6 +10,8 @@ import 'package:stackwallet/pages/home_view/home_view.dart'; import 'package:stackwallet/pages/send_view/sub_widgets/building_transaction_dialog.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/route_generator.dart'; +import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; +import 'package:stackwallet/services/coins/manager.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; @@ -18,7 +20,9 @@ import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/animated_text.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/expandable.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; @@ -162,6 +166,130 @@ class _SendFromCardState extends ConsumerState { late final String address; late final Trade trade; + Future _send(Manager manager, {bool? shouldSendPublicFiroFunds}) async { + final _amount = Format.decimalAmountToSatoshis(amount); + + try { + bool wasCancelled = false; + + unawaited( + showDialog( + context: context, + useSafeArea: false, + barrierDismissible: false, + builder: (context) { + return BuildingTransactionDialog( + onCancel: () { + wasCancelled = true; + + Navigator.of(context).pop(); + }, + ); + }, + ), + ); + + late Map txData; + + // if not firo then do normal send + if (shouldSendPublicFiroFunds == null) { + txData = await manager.prepareSend( + address: address, + satoshiAmount: _amount, + args: { + "feeRate": FeeRateType.average, + // ref.read(feeRateTypeStateProvider) + }, + ); + } else { + final firoWallet = manager.wallet as FiroWallet; + // otherwise do firo send based on balance selected + if (shouldSendPublicFiroFunds) { + txData = await firoWallet.prepareSendPublic( + address: address, + satoshiAmount: _amount, + args: { + "feeRate": FeeRateType.average, + // ref.read(feeRateTypeStateProvider) + }, + ); + } else { + txData = await firoWallet.prepareSend( + address: address, + satoshiAmount: _amount, + args: { + "feeRate": FeeRateType.average, + // ref.read(feeRateTypeStateProvider) + }, + ); + } + } + + if (!wasCancelled) { + // pop building dialog + + if (mounted) { + Navigator.of(context).pop(); + } + + txData["note"] = + "${trade.payInCurrency.toUpperCase()}/${trade.payOutCurrency.toUpperCase()} exchange"; + txData["address"] = address; + + if (mounted) { + await Navigator.of(context).push( + RouteGenerator.getRoute( + shouldUseMaterialRoute: RouteGenerator.useMaterialPageRoute, + builder: (_) => ConfirmChangeNowSendView( + transactionInfo: txData, + walletId: walletId, + routeOnSuccessName: HomeView.routeName, + trade: trade, + shouldSendPublicFiroFunds: shouldSendPublicFiroFunds, + ), + settings: const RouteSettings( + name: ConfirmChangeNowSendView.routeName, + ), + ), + ); + } + } + } catch (e) { + // if (mounted) { + // pop building dialog + Navigator.of(context).pop(); + + await showDialog( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (context) { + return StackDialog( + title: "Transaction failed", + message: e.toString(), + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonColor(context), + child: Text( + "Ok", + style: STextStyles.button(context).copyWith( + color: Theme.of(context) + .extension()! + .buttonTextSecondary, + ), + ), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ); + }, + ); + // } + } + } + @override void initState() { walletId = widget.walletId; @@ -182,181 +310,278 @@ class _SendFromCardState extends ConsumerState { final coin = manager.coin; + final isFiro = coin == Coin.firoTestNet || coin == Coin.firo; + return RoundedWhiteContainer( padding: const EdgeInsets.all(0), - child: MaterialButton( - splashColor: Theme.of(context).extension()!.highlight, - key: Key("walletsSheetItemButtonKey_$walletId"), - padding: const EdgeInsets.all(8), - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, + child: ConditionalParent( + condition: isFiro, + builder: (child) => Expandable( + header: Container( + color: Colors.transparent, + child: Padding( + padding: const EdgeInsets.all(12), + child: child, + ), + ), + body: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + MaterialButton( + splashColor: + Theme.of(context).extension()!.highlight, + key: Key("walletsSheetItemButtonFiroPrivateKey_$walletId"), + padding: const EdgeInsets.all(0), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: () => _send( + manager, + shouldSendPublicFiroFunds: false, + ), + child: Container( + color: Colors.transparent, + child: Padding( + padding: const EdgeInsets.only( + top: 6, + left: 16, + right: 16, + bottom: 6, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Use private balance", + style: STextStyles.itemSubtitle(context), + ), + FutureBuilder( + future: (manager.wallet as FiroWallet) + .availablePrivateBalance(), + builder: (builderContext, + AsyncSnapshot snapshot) { + if (snapshot.connectionState == + ConnectionState.done && + snapshot.hasData) { + return Text( + "${Format.localizedStringAsFixed( + value: snapshot.data!, + locale: locale, + decimalPlaces: Constants.decimalPlaces, + )} ${coin.ticker}", + style: STextStyles.itemSubtitle(context), + ); + } else { + return AnimatedText( + stringsToLoopThrough: const [ + "Loading balance", + "Loading balance.", + "Loading balance..", + "Loading balance..." + ], + style: STextStyles.itemSubtitle(context), + ); + } + }, + ), + ], + ), + SvgPicture.asset( + Assets.svg.chevronRight, + height: 14, + width: 7, + color: Theme.of(context) + .extension()! + .infoItemLabel, + ), + ], + ), + ), + ), + ), + MaterialButton( + splashColor: + Theme.of(context).extension()!.highlight, + key: Key("walletsSheetItemButtonFiroPublicKey_$walletId"), + padding: const EdgeInsets.all(0), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: () => _send( + manager, + shouldSendPublicFiroFunds: true, + ), + child: Container( + color: Colors.transparent, + child: Padding( + padding: const EdgeInsets.only( + top: 6, + left: 16, + right: 16, + bottom: 6, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Use public balance", + style: STextStyles.itemSubtitle(context), + ), + FutureBuilder( + future: (manager.wallet as FiroWallet) + .availablePublicBalance(), + builder: (builderContext, + AsyncSnapshot snapshot) { + if (snapshot.connectionState == + ConnectionState.done && + snapshot.hasData) { + return Text( + "${Format.localizedStringAsFixed( + value: snapshot.data!, + locale: locale, + decimalPlaces: Constants.decimalPlaces, + )} ${coin.ticker}", + style: STextStyles.itemSubtitle(context), + ); + } else { + return AnimatedText( + stringsToLoopThrough: const [ + "Loading balance", + "Loading balance.", + "Loading balance..", + "Loading balance..." + ], + style: STextStyles.itemSubtitle(context), + ); + } + }, + ), + ], + ), + SvgPicture.asset( + Assets.svg.chevronRight, + height: 14, + width: 7, + color: Theme.of(context) + .extension()! + .infoItemLabel, + ), + ], + ), + ), + ), + ), + const SizedBox( + height: 6, + ), + ], ), ), - onPressed: () async { - final _amount = Format.decimalAmountToSatoshis(amount); - - try { - bool wasCancelled = false; - - unawaited(showDialog( - context: context, - useSafeArea: false, - barrierDismissible: false, - builder: (context) { - return BuildingTransactionDialog( - onCancel: () { - wasCancelled = true; - - Navigator.of(context).pop(); - }, - ); - }, - )); - - final txData = await manager.prepareSend( - address: address, - satoshiAmount: _amount, - args: { - "feeRate": FeeRateType.average, - // ref.read(feeRateTypeStateProvider) - }, - ); - - if (!wasCancelled) { - // pop building dialog - - if (mounted) { - Navigator.of(context).pop(); - } - - txData["note"] = - "${trade.payInCurrency.toUpperCase()}/${trade.payOutCurrency.toUpperCase()} exchange"; - txData["address"] = address; - - if (mounted) { - await Navigator.of(context).push( - RouteGenerator.getRoute( - shouldUseMaterialRoute: RouteGenerator.useMaterialPageRoute, - builder: (_) => ConfirmChangeNowSendView( - transactionInfo: txData, - walletId: walletId, - routeOnSuccessName: HomeView.routeName, - trade: trade, - ), - settings: const RouteSettings( - name: ConfirmChangeNowSendView.routeName, - ), + child: ConditionalParent( + condition: !isFiro, + builder: (child) => MaterialButton( + splashColor: Theme.of(context).extension()!.highlight, + key: Key("walletsSheetItemButtonKey_$walletId"), + padding: const EdgeInsets.all(8), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: () => _send(manager), + child: child, + ), + child: Row( + children: [ + Container( + decoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .colorForCoin(manager.coin) + .withOpacity(0.5), + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, ), - ); - } - } - } catch (e) { - // if (mounted) { - // pop building dialog - Navigator.of(context).pop(); - - await showDialog( - context: context, - useSafeArea: false, - barrierDismissible: true, - builder: (context) { - return StackDialog( - title: "Transaction failed", - message: e.toString(), - rightButton: TextButton( - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonColor(context), - child: Text( - "Ok", - style: STextStyles.button(context).copyWith( - color: Theme.of(context) - .extension()! - .buttonTextSecondary, + ), + child: Padding( + padding: const EdgeInsets.all(6), + child: SvgPicture.asset( + Assets.svg.iconFor(coin: coin), + width: 24, + height: 24, + ), + ), + ), + const SizedBox( + width: 12, + ), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + manager.walletName, + style: STextStyles.titleBold12(context), + ), + if (!isFiro) + const SizedBox( + height: 2, ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ); - }, - ); - // } - } - }, - child: Row( - children: [ - Container( - decoration: BoxDecoration( - color: Theme.of(context) - .extension()! - .colorForCoin(manager.coin) - .withOpacity(0.5), - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, + if (!isFiro) + FutureBuilder( + future: manager.totalBalance, + builder: + (builderContext, AsyncSnapshot snapshot) { + if (snapshot.connectionState == + ConnectionState.done && + snapshot.hasData) { + return Text( + "${Format.localizedStringAsFixed( + value: snapshot.data!, + locale: locale, + decimalPlaces: coin == Coin.monero + ? Constants.decimalPlacesMonero + : coin == Coin.wownero + ? Constants.decimalPlacesWownero + : Constants.decimalPlaces, + )} ${coin.ticker}", + style: STextStyles.itemSubtitle(context), + ); + } else { + return AnimatedText( + stringsToLoopThrough: const [ + "Loading balance", + "Loading balance.", + "Loading balance..", + "Loading balance..." + ], + style: STextStyles.itemSubtitle(context), + ); + } + }, + ), + ], ), ), - child: Padding( - padding: const EdgeInsets.all(6), - child: SvgPicture.asset( - Assets.svg.iconFor(coin: coin), - width: 24, - height: 24, - ), - ), - ), - const SizedBox( - width: 12, - ), - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - manager.walletName, - style: STextStyles.titleBold12(context), - ), - const SizedBox( - height: 2, - ), - FutureBuilder( - future: manager.totalBalance, - builder: (builderContext, AsyncSnapshot snapshot) { - if (snapshot.connectionState == ConnectionState.done && - snapshot.hasData) { - return Text( - "${Format.localizedStringAsFixed( - value: snapshot.data!, - locale: locale, - decimalPlaces: coin == Coin.monero - ? Constants.decimalPlacesMonero - : coin == Coin.wownero - ? Constants.decimalPlacesWownero - : Constants.decimalPlaces, - )} ${coin.ticker}", - style: STextStyles.itemSubtitle(context), - ); - } else { - return AnimatedText( - stringsToLoopThrough: const [ - "Loading balance", - "Loading balance.", - "Loading balance..", - "Loading balance..." - ], - style: STextStyles.itemSubtitle(context), - ); - } - }, - ), - ], - ), - ), - ], + ], + ), ), ), ); From 9fe9ee3a1236011d2e6c96147af82b1aa8874390 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Mon, 7 Nov 2022 10:42:55 -0700 Subject: [PATCH 26/76] desktop about view route added --- lib/pages_desktop_specific/home/desktop_home_view.dart | 7 +++++-- .../home/support_and_about_view/desktop_about_view.dart | 0 lib/route_generator.dart | 7 +++++++ 3 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 lib/pages_desktop_specific/home/support_and_about_view/desktop_about_view.dart diff --git a/lib/pages_desktop_specific/home/desktop_home_view.dart b/lib/pages_desktop_specific/home/desktop_home_view.dart index e9f6f2b4b..14d2dae03 100644 --- a/lib/pages_desktop_specific/home/desktop_home_view.dart +++ b/lib/pages_desktop_specific/home/desktop_home_view.dart @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/pages_desktop_specific/home/desktop_menu.dart'; import 'package:stackwallet/pages_desktop_specific/home/desktop_settings_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/my_stack_view.dart'; +import 'package:stackwallet/pages_desktop_specific/home/support_and_about_view/desktop_about_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/support_and_about_view/desktop_support_view.dart'; import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; @@ -43,8 +44,10 @@ class _DesktopHomeViewState extends ConsumerState { onGenerateRoute: RouteGenerator.generateRoute, initialRoute: DesktopSupportView.routeName, ), - Container( - color: Colors.pink, + const Navigator( + key: Key("desktopAboutHomeKey"), + onGenerateRoute: RouteGenerator.generateRoute, + initialRoute: DesktopAboutView.routeName, ), ]; diff --git a/lib/pages_desktop_specific/home/support_and_about_view/desktop_about_view.dart b/lib/pages_desktop_specific/home/support_and_about_view/desktop_about_view.dart new file mode 100644 index 000000000..e69de29bb diff --git a/lib/route_generator.dart b/lib/route_generator.dart index 47a84f07c..40f11dc57 100644 --- a/lib/route_generator.dart +++ b/lib/route_generator.dart @@ -99,6 +99,7 @@ import 'package:stackwallet/pages_desktop_specific/home/settings_menu/nodes_sett import 'package:stackwallet/pages_desktop_specific/home/settings_menu/security_settings.dart'; import 'package:stackwallet/pages_desktop_specific/home/settings_menu/settings_menu.dart'; import 'package:stackwallet/pages_desktop_specific/home/settings_menu/syncing_preferences_settings.dart'; +import 'package:stackwallet/pages_desktop_specific/home/support_and_about_view/desktop_about_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/support_and_about_view/desktop_support_view.dart'; import 'package:stackwallet/services/coins/manager.dart'; import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart'; @@ -1091,6 +1092,12 @@ class RouteGenerator { builder: (_) => const DesktopSupportView(), settings: RouteSettings(name: settings.name)); + case DesktopAboutView.routeName: + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => const DesktopAboutView(), + settings: RouteSettings(name: settings.name)); + case WalletKeysDesktopPopup.routeName: if (args is List) { return FadePageRoute( From 48a0e3a5ca89528a1bdca69e8c027967c7957e1d Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Mon, 7 Nov 2022 12:31:49 -0700 Subject: [PATCH 27/76] desktop about view added --- .../desktop_about_view.dart | 655 ++++++++++++++++++ 1 file changed, 655 insertions(+) diff --git a/lib/pages_desktop_specific/home/support_and_about_view/desktop_about_view.dart b/lib/pages_desktop_specific/home/support_and_about_view/desktop_about_view.dart index e69de29bb..86fcf78e0 100644 --- a/lib/pages_desktop_specific/home/support_and_about_view/desktop_about_view.dart +++ b/lib/pages_desktop_specific/home/support_and_about_view/desktop_about_view.dart @@ -0,0 +1,655 @@ +import 'dart:convert'; + +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_libepiccash/git_versions.dart' as EPIC_VERSIONS; +import 'package:flutter_libmonero/git_versions.dart' as MONERO_VERSIONS; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:http/http.dart'; +import 'package:lelantus/git_versions.dart' as FIRO_VERSIONS; +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; +import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; +import 'package:url_launcher/url_launcher.dart'; + +const kGithubAPI = "https://api.github.com"; +const kGithubSearch = "/search/commits"; +const kGithubHead = "/repos"; + +enum CommitStatus { isHead, isOldCommit, notACommit, notLoaded } + +Future doesCommitExist( + String organization, + String project, + String commit, +) async { + Logging.instance.log("doesCommitExist", level: LogLevel.Info); + final Client client = Client(); + try { + final uri = Uri.parse( + "$kGithubAPI$kGithubHead/$organization/$project/commits/$commit"); + + final commitQuery = await client.get( + uri, + headers: {'Content-Type': 'application/json'}, + ); + + final response = jsonDecode(commitQuery.body.toString()); + Logging.instance.log("doesCommitExist $project $commit $response", + level: LogLevel.Info); + bool isThereCommit; + try { + isThereCommit = response['sha'] == commit; + Logging.instance + .log("isThereCommit $isThereCommit", level: LogLevel.Info); + return isThereCommit; + } catch (e, s) { + return false; + } + } catch (e, s) { + Logging.instance.log("$e $s", level: LogLevel.Error); + return false; + } +} + +Future isHeadCommit( + String organization, + String project, + String branch, + String commit, +) async { + Logging.instance.log("doesCommitExist", level: LogLevel.Info); + final Client client = Client(); + try { + final uri = Uri.parse( + "$kGithubAPI$kGithubHead/$organization/$project/commits/$branch"); + + final commitQuery = await client.get( + uri, + headers: {'Content-Type': 'application/json'}, + ); + + final response = jsonDecode(commitQuery.body.toString()); + Logging.instance.log("isHeadCommit $project $commit $branch $response", + level: LogLevel.Info); + bool isHead; + try { + isHead = response['sha'] == commit; + Logging.instance.log("isHead $isHead", level: LogLevel.Info); + return isHead; + } catch (e, s) { + return false; + } + } catch (e, s) { + Logging.instance.log("$e $s", level: LogLevel.Error); + return false; + } +} + +class DesktopAboutView extends ConsumerWidget { + const DesktopAboutView({Key? key}) : super(key: key); + + static const String routeName = "/desktopAboutView"; + + @override + Widget build(BuildContext context, WidgetRef ref) { + String firoCommit = FIRO_VERSIONS.getPluginVersion(); + String epicCashCommit = EPIC_VERSIONS.getPluginVersion(); + String moneroCommit = MONERO_VERSIONS.getPluginVersion(); + List futureFiroList = [ + doesCommitExist("cypherstack", "flutter_liblelantus", firoCommit), + isHeadCommit("cypherstack", "flutter_liblelantus", "main", firoCommit), + ]; + Future commitFiroFuture = Future.wait(futureFiroList); + List futureEpicList = [ + doesCommitExist("cypherstack", "flutter_libepiccash", epicCashCommit), + isHeadCommit( + "cypherstack", "flutter_libepiccash", "main", epicCashCommit), + ]; + Future commitEpicFuture = Future.wait(futureEpicList); + List futureMoneroList = [ + doesCommitExist("cypherstack", "flutter_libmonero", moneroCommit), + isHeadCommit("cypherstack", "flutter_libmonero", "main", moneroCommit), + ]; + Future commitMoneroFuture = Future.wait(futureMoneroList); + + debugPrint("BUILD: $runtimeType"); + return DesktopScaffold( + background: Theme.of(context).extension()!.background, + appBar: DesktopAppBar( + isCompactHeight: true, + leading: Row( + children: [ + const SizedBox( + width: 24, + height: 24, + ), + Text( + "About", + style: STextStyles.desktopH3(context), + ) + ], + ), + ), + body: Column( + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(24, 10, 24, 35), + child: RoundedWhiteContainer( + width: 929, + height: 411, + child: Padding( + padding: const EdgeInsets.only(left: 10, top: 10), + child: Column( + // mainAxisAlignment: MainAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + "Stack Wallet", + style: STextStyles.desktopH3(context), + textAlign: TextAlign.start, + ), + ], + ), + const SizedBox(height: 16), + Row( + children: [ + RichText( + textAlign: TextAlign.start, + text: TextSpan( + style: STextStyles.label(context), + children: [ + TextSpan( + text: + "By using Stack Wallet, you agree to the ", + style: STextStyles.desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark3), + ), + TextSpan( + text: "Terms of service", + style: STextStyles.richLink(context) + .copyWith(fontSize: 14), + recognizer: TapGestureRecognizer() + ..onTap = () { + launchUrl( + Uri.parse( + "https://stackwallet.com/terms-of-service.html"), + mode: LaunchMode.externalApplication, + ); + }, + ), + TextSpan( + text: " and ", + style: STextStyles.desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark3), + ), + TextSpan( + text: "Privacy policy", + style: STextStyles.richLink(context) + .copyWith(fontSize: 14), + recognizer: TapGestureRecognizer() + ..onTap = () { + launchUrl( + Uri.parse( + "https://stackwallet.com/privacy-policy.html"), + mode: LaunchMode.externalApplication, + ); + }, + ), + ], + ), + ), + ], + ), + const SizedBox(height: 32), + Padding( + padding: const EdgeInsets.only(right: 10, bottom: 10), + child: Column( + children: [ + FutureBuilder( + future: PackageInfo.fromPlatform(), + builder: + (context, AsyncSnapshot snapshot) { + String version = ""; + String signature = ""; + String build = ""; + + if (snapshot.connectionState == + ConnectionState.done && + snapshot.hasData) { + version = snapshot.data!.version; + build = snapshot.data!.buildNumber; + signature = snapshot.data!.buildSignature; + } + + return Column( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + "Version", + style: STextStyles + .desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension< + StackColors>()! + .textDark), + ), + const SizedBox( + height: 2, + ), + SelectableText( + version, + style: STextStyles.itemSubtitle( + context), + ), + ], + ), + const SizedBox( + width: 400, + ), + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + "Build number", + style: STextStyles + .desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension< + StackColors>()! + .textDark), + ), + const SizedBox( + height: 2, + ), + SelectableText( + build, + style: STextStyles.itemSubtitle( + context), + ), + ], + ), + ], + ), + const SizedBox(height: 32), + Row( + children: [ + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + "Build signature", + style: STextStyles + .desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension< + StackColors>()! + .textDark), + ), + const SizedBox( + height: 2, + ), + SelectableText( + signature, + style: STextStyles.itemSubtitle( + context), + ), + ], + ), + const SizedBox( + width: 350, + ), + FutureBuilder( + future: commitFiroFuture, + builder: (context, + AsyncSnapshot snapshot) { + bool commitExists = false; + bool isHead = false; + CommitStatus stateOfCommit = + CommitStatus.notLoaded; + + if (snapshot.connectionState == + ConnectionState.done && + snapshot.hasData) { + commitExists = + snapshot.data![0] as bool; + isHead = + snapshot.data![1] as bool; + if (commitExists && isHead) { + stateOfCommit = + CommitStatus.isHead; + } else if (commitExists) { + stateOfCommit = + CommitStatus.isOldCommit; + } else { + stateOfCommit = + CommitStatus.notACommit; + } + } + TextStyle indicationStyle = + STextStyles.itemSubtitle( + context); + switch (stateOfCommit) { + case CommitStatus.isHead: + indicationStyle = STextStyles + .itemSubtitle(context) + .copyWith( + color: Theme.of(context) + .extension< + StackColors>()! + .accentColorGreen); + break; + case CommitStatus.isOldCommit: + indicationStyle = STextStyles + .itemSubtitle(context) + .copyWith( + color: Theme.of(context) + .extension< + StackColors>()! + .accentColorYellow); + break; + case CommitStatus.notACommit: + indicationStyle = STextStyles + .itemSubtitle(context) + .copyWith( + color: Theme.of(context) + .extension< + StackColors>()! + .accentColorRed); + break; + default: + break; + } + return Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + "Firo Build Commit", + style: STextStyles + .desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of( + context) + .extension< + StackColors>()! + .textDark), + ), + const SizedBox( + height: 2, + ), + SelectableText( + firoCommit, + style: indicationStyle, + ), + ], + ); + }), + ], + ), + const SizedBox(height: 35), + Row( + children: [ + FutureBuilder( + future: commitEpicFuture, + builder: (context, + AsyncSnapshot snapshot) { + bool commitExists = false; + bool isHead = false; + CommitStatus stateOfCommit = + CommitStatus.notLoaded; + + if (snapshot.connectionState == + ConnectionState.done && + snapshot.hasData) { + commitExists = + snapshot.data![0] as bool; + isHead = + snapshot.data![1] as bool; + if (commitExists && isHead) { + stateOfCommit = + CommitStatus.isHead; + } else if (commitExists) { + stateOfCommit = + CommitStatus.isOldCommit; + } else { + stateOfCommit = + CommitStatus.notACommit; + } + } + TextStyle indicationStyle = + STextStyles.itemSubtitle( + context); + switch (stateOfCommit) { + case CommitStatus.isHead: + indicationStyle = STextStyles + .itemSubtitle(context) + .copyWith( + color: Theme.of(context) + .extension< + StackColors>()! + .accentColorGreen); + break; + case CommitStatus.isOldCommit: + indicationStyle = STextStyles + .itemSubtitle(context) + .copyWith( + color: Theme.of(context) + .extension< + StackColors>()! + .accentColorYellow); + break; + case CommitStatus.notACommit: + indicationStyle = STextStyles + .itemSubtitle(context) + .copyWith( + color: Theme.of(context) + .extension< + StackColors>()! + .accentColorRed); + break; + default: + break; + } + return Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + "Epic Cash Build Commit", + style: STextStyles + .desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of( + context) + .extension< + StackColors>()! + .textDark), + ), + const SizedBox( + height: 2, + ), + SelectableText( + epicCashCommit, + style: indicationStyle, + ), + ], + ); + }), + const SizedBox( + width: 105, + ), + FutureBuilder( + future: commitMoneroFuture, + builder: (context, + AsyncSnapshot snapshot) { + bool commitExists = false; + bool isHead = false; + CommitStatus stateOfCommit = + CommitStatus.notLoaded; + + if (snapshot.connectionState == + ConnectionState.done && + snapshot.hasData) { + commitExists = + snapshot.data![0] as bool; + isHead = + snapshot.data![1] as bool; + if (commitExists && isHead) { + stateOfCommit = + CommitStatus.isHead; + } else if (commitExists) { + stateOfCommit = + CommitStatus.isOldCommit; + } else { + stateOfCommit = + CommitStatus.notACommit; + } + } + TextStyle indicationStyle = + STextStyles.itemSubtitle( + context); + switch (stateOfCommit) { + case CommitStatus.isHead: + indicationStyle = STextStyles + .itemSubtitle(context) + .copyWith( + color: Theme.of(context) + .extension< + StackColors>()! + .accentColorGreen); + break; + case CommitStatus.isOldCommit: + indicationStyle = STextStyles + .itemSubtitle(context) + .copyWith( + color: Theme.of(context) + .extension< + StackColors>()! + .accentColorYellow); + break; + case CommitStatus.notACommit: + indicationStyle = STextStyles + .itemSubtitle(context) + .copyWith( + color: Theme.of(context) + .extension< + StackColors>()! + .accentColorRed); + break; + default: + break; + } + return Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + "Monero Build Commit", + style: STextStyles + .desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of( + context) + .extension< + StackColors>()! + .textDark), + ), + const SizedBox( + height: 2, + ), + SelectableText( + moneroCommit, + style: indicationStyle, + ), + ], + ); + }), + ], + ), + const SizedBox(height: 35), + Row( + children: [ + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + "Website", + style: STextStyles + .desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension< + StackColors>()! + .textDark), + ), + const SizedBox( + height: 2, + ), + BlueTextButton( + text: "https://stackwallet.com", + onTap: () { + launchUrl( + Uri.parse( + "https://stackwallet.com"), + mode: LaunchMode + .externalApplication, + ); + }, + ), + ], + ) + ], + ) + ], + ); + }, + ) + ], + ), + ) + ], + ), + ), + ), + ), + ], + ), + ); + } +} From cb83515dbc774abc9c9e49fbd9f18f265ec699a1 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Mon, 7 Nov 2022 13:45:25 -0600 Subject: [PATCH 28/76] do not use wownero-seed (wow_seed) --- crypto_plugins/flutter_libmonero | 2 +- .../coins/wownero/wownero_wallet_test.dart | 107 ++++++++++++++++++ .../wownero/wownero_wallet_test_data.dart | 8 ++ 3 files changed, 116 insertions(+), 1 deletion(-) diff --git a/crypto_plugins/flutter_libmonero b/crypto_plugins/flutter_libmonero index d92fea3e6..371443607 160000 --- a/crypto_plugins/flutter_libmonero +++ b/crypto_plugins/flutter_libmonero @@ -1 +1 @@ -Subproject commit d92fea3e6b915b79697deafbd5710fb4c15bfc76 +Subproject commit 371443607433a0ec509e2933ee21def29e9ad429 diff --git a/test/services/coins/wownero/wownero_wallet_test.dart b/test/services/coins/wownero/wownero_wallet_test.dart index 83b4844c7..945d8650f 100644 --- a/test/services/coins/wownero/wownero_wallet_test.dart +++ b/test/services/coins/wownero/wownero_wallet_test.dart @@ -83,8 +83,10 @@ void main() async { _walletInfoSource = await Hive.openBox(WalletInfo.boxName); walletService = wownero.createWowneroWalletService(_walletInfoSource); + /* group("Wownero 14 word tests", () { setUp(() async { + bool hasThrown = false; try { final dirPath = await pathForWalletDir(name: name, type: type); path = await pathForWallet(name: name, type: type); @@ -113,7 +115,9 @@ void main() async { } catch (e, s) { print(e); print(s); + hasThrown = true; } + expect(hasThrown, false); }); test("Test mainnet address generation from 14 word seed", () async { @@ -147,6 +151,109 @@ void main() async { walletBase?.close(); walletBase = wallet as WowneroWalletBase; }); + + // TODO delete left over wallet file with name: name + }); + */ + + group("Wownero 25 word tests", () { + setUp(() async { + bool hasThrown = false; + try { + final dirPath = await pathForWalletDir(name: name, type: type); + path = await pathForWallet(name: name, type: type); + credentials = wownero.createWowneroRestoreWalletFromSeedCredentials( + name: name, height: 465760, mnemonic: testMnemonic25); + + walletInfo = WalletInfo.external( + id: WalletBase.idFor(name, type), + name: name, + type: type, + isRecovery: false, + restoreHeight: credentials.height ?? 0, + date: DateTime.now(), + path: path, + address: "", + dirPath: dirPath); + credentials.walletInfo = walletInfo; + + _walletCreationService = WalletCreationService( + secureStorage: storage, + sharedPreferences: prefs, + walletService: walletService, + keyService: keysStorage, + ); + _walletCreationService.changeWalletType(); + } catch (e, s) { + print(e); + print(s); + hasThrown = true; + } + expect(hasThrown, false); + }); + + test("Test mainnet address generation from 25 word seed", () async { + bool hasThrown = false; + try { + name = 'namee${Random().nextInt(10000000)}'; + final dirPath = await pathForWalletDir(name: name, type: type); + path = await pathForWallet(name: name, type: type); + try { + credentials = wownero.createWowneroRestoreWalletFromSeedCredentials( + name: name, height: 465760, mnemonic: testMnemonic25); + } catch (e, s) { + print(e); + print(s); + hasThrown = true; + } + expect(hasThrown, false); + + walletInfo = WalletInfo.external( + id: WalletBase.idFor(name, type), + name: name, + type: type, + isRecovery: false, + restoreHeight: credentials.height ?? 0, + date: DateTime.now(), + path: path, + address: "", + dirPath: dirPath); + credentials.walletInfo = walletInfo; + + _walletCreationService = WalletCreationService( + secureStorage: storage, + sharedPreferences: prefs, + walletService: walletService, + keyService: keysStorage, + ); + _walletCreationService.changeWalletType(); + } catch (e, s) { + print(e); + print(s); + hasThrown = true; + } + expect(hasThrown, false); + + final wallet = await _walletCreationService.restoreFromSeed(credentials); + walletInfo.address = wallet.walletAddresses.address; + + hasThrown = false; + try { + await _walletInfoSource.add(walletInfo); + walletBase?.close(); + walletBase = wallet as WowneroWalletBase; + + expect(walletInfo.address, mainnetTestData25[0][0]); + } catch (_) { + hasThrown = true; + } + expect(hasThrown, false); + + walletBase?.close(); + walletBase = wallet as WowneroWalletBase; + }); + + // TODO delete left over wallet file with name: name }); } diff --git a/test/services/coins/wownero/wownero_wallet_test_data.dart b/test/services/coins/wownero/wownero_wallet_test_data.dart index 8ab825dfa..7f27fc486 100644 --- a/test/services/coins/wownero/wownero_wallet_test_data.dart +++ b/test/services/coins/wownero/wownero_wallet_test_data.dart @@ -12,3 +12,11 @@ var mainnetTestData14 = [ 'WW2KQLLt6gjC9gRsC4NGehbAZX6UPU7sK89UQFwSg3NKj3MXPwnjh5BiJVqYYNQb6JNsfa7oP7eDjLagtLa2H6YP11RhUNQqw' ] ]; + +String testMnemonic25 = + 'myth byline benches sadness nylon tamper guide giving match angled lurk rally makeup alarms river soapy dolphin woven ticket maul examine public luggage mammal alarms'; +var mainnetTestData25 = [ + [ + 'Wo3piMnt1ztjLktFJNsfs9ce6N1tyHk7DB93cNqTGPJ7To3RS7W2q5DdxgQAG5E6RQXQhchQD7ip8WWL3fD8Ww5K2XmAXYxta' + ] +]; From 6e5a0bad784831ba7c98db99b3291f6ab305ac48 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Mon, 7 Nov 2022 14:42:52 -0600 Subject: [PATCH 29/76] do not use wownero-seed (wow_seed) function for height, hardcoded POC --- crypto_plugins/flutter_libmonero | 2 +- lib/services/coins/wownero/wownero_wallet.dart | 7 ++++--- .../services/coins/wownero/wownero_wallet_test.dart | 13 ++----------- 3 files changed, 7 insertions(+), 15 deletions(-) diff --git a/crypto_plugins/flutter_libmonero b/crypto_plugins/flutter_libmonero index 371443607..da826f313 160000 --- a/crypto_plugins/flutter_libmonero +++ b/crypto_plugins/flutter_libmonero @@ -1 +1 @@ -Subproject commit 371443607433a0ec509e2933ee21def29e9ad429 +Subproject commit da826f31352c695942bc9b821d1d0c82a9267ade diff --git a/lib/services/coins/wownero/wownero_wallet.dart b/lib/services/coins/wownero/wownero_wallet.dart index d3aba5bbb..a069414b6 100644 --- a/lib/services/coins/wownero/wownero_wallet.dart +++ b/lib/services/coins/wownero/wownero_wallet.dart @@ -713,8 +713,8 @@ class WowneroWallet extends CoinServiceAPI { final wallet = await _walletCreationService?.create(credentials); // subtract a couple days to ensure we have a buffer for SWB - final bufferedCreateHeight = - getSeedHeightSync(wallet?.seed.trim() as String); + final bufferedCreateHeight = 0; + //final bufferedCreateHeight = getSeedHeightSync(wallet?.seed.trim() as String); // TODO use an alternative to wow_seed's get_seed_height await DB.instance.put( boxName: walletId, key: "restoreHeight", value: bufferedCreateHeight); @@ -969,7 +969,8 @@ class WowneroWallet extends CoinServiceAPI { await _secureStore.write( key: '${_walletId}_mnemonic', value: mnemonic.trim()); - height = getSeedHeightSync(mnemonic.trim()); + height = 0; + //height = getSeedHeightSync(mnemonic.trim()); // TODO use an alternative to wow_seed's get_seed_height await DB.instance .put(boxName: walletId, key: "restoreHeight", value: height); diff --git a/test/services/coins/wownero/wownero_wallet_test.dart b/test/services/coins/wownero/wownero_wallet_test.dart index 945d8650f..ca7540da5 100644 --- a/test/services/coins/wownero/wownero_wallet_test.dart +++ b/test/services/coins/wownero/wownero_wallet_test.dart @@ -91,7 +91,7 @@ void main() async { final dirPath = await pathForWalletDir(name: name, type: type); path = await pathForWallet(name: name, type: type); credentials = wownero.createWowneroRestoreWalletFromSeedCredentials( - name: name, height: 465760, mnemonic: testMnemonic14); + name: name, height: 465760, mnemonic: testMnemonic14); // TODO catch failure walletInfo = WalletInfo.external( id: WalletBase.idFor(name, type), @@ -163,7 +163,7 @@ void main() async { final dirPath = await pathForWalletDir(name: name, type: type); path = await pathForWallet(name: name, type: type); credentials = wownero.createWowneroRestoreWalletFromSeedCredentials( - name: name, height: 465760, mnemonic: testMnemonic25); + name: name, height: 465760, mnemonic: testMnemonic25); // TODO catch failure walletInfo = WalletInfo.external( id: WalletBase.idFor(name, type), @@ -198,15 +198,6 @@ void main() async { name = 'namee${Random().nextInt(10000000)}'; final dirPath = await pathForWalletDir(name: name, type: type); path = await pathForWallet(name: name, type: type); - try { - credentials = wownero.createWowneroRestoreWalletFromSeedCredentials( - name: name, height: 465760, mnemonic: testMnemonic25); - } catch (e, s) { - print(e); - print(s); - hasThrown = true; - } - expect(hasThrown, false); walletInfo = WalletInfo.external( id: WalletBase.idFor(name, type), From b41c4c37bd6c5756fe8be0f4011da685d1265bd8 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Mon, 7 Nov 2022 14:46:48 -0600 Subject: [PATCH 30/76] delineate divergence point more clearly --- lib/services/coins/wownero/wownero_wallet.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/services/coins/wownero/wownero_wallet.dart b/lib/services/coins/wownero/wownero_wallet.dart index a069414b6..1153a881a 100644 --- a/lib/services/coins/wownero/wownero_wallet.dart +++ b/lib/services/coins/wownero/wownero_wallet.dart @@ -713,8 +713,12 @@ class WowneroWallet extends CoinServiceAPI { final wallet = await _walletCreationService?.create(credentials); // subtract a couple days to ensure we have a buffer for SWB + // 14 words + //final bufferedCreateHeight = getSeedHeightSync(wallet?.seed.trim() as String); + + // 25 words final bufferedCreateHeight = 0; - //final bufferedCreateHeight = getSeedHeightSync(wallet?.seed.trim() as String); // TODO use an alternative to wow_seed's get_seed_height + // TODO use an alternative to wow_seed's get_seed_height await DB.instance.put( boxName: walletId, key: "restoreHeight", value: bufferedCreateHeight); From c962f597fdc9c696edbbdac655172ab9f5e217e7 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 7 Nov 2022 14:46:36 -0600 Subject: [PATCH 31/76] added extra checks to BCH as well as test cases --- .../coins/bitcoincash/bitcoincash_wallet.dart | 58 ++++--- .../bitcoincash/bitcoincash_wallet_test.dart | 164 +++++++++++++++++- 2 files changed, 199 insertions(+), 23 deletions(-) diff --git a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart index 98a31ee0c..5b3b54663 100644 --- a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart +++ b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart @@ -208,9 +208,9 @@ class BitcoinCashWallet extends CoinServiceAPI { _getCurrentAddressForChain(0, DerivePathType.bip44); Future? _currentReceivingAddressP2PKH; - Future get currentReceivingAddressP2SH => - _currentReceivingAddressP2SH ??= - _getCurrentAddressForChain(0, DerivePathType.bip49); + // Future get currentReceivingAddressP2SH => + // _currentReceivingAddressP2SH ??= + // _getCurrentAddressForChain(0, DerivePathType.bip49); Future? _currentReceivingAddressP2SH; @override @@ -269,7 +269,11 @@ class BitcoinCashWallet extends CoinServiceAPI { try { if (bitbox.Address.detectFormat(address) == bitbox.Address.formatCashAddr) { - address = bitbox.Address.toLegacyAddress(address); + if (validateCashAddr(address)) { + address = bitbox.Address.toLegacyAddress(address); + } else { + throw ArgumentError('$address is not currently supported'); + } } } catch (e, s) {} try { @@ -294,11 +298,14 @@ class BitcoinCashWallet extends CoinServiceAPI { } catch (err) { // Bech32 decode fail } - if (_network.bech32 != decodeBech32!.hrp) { - throw ArgumentError('Invalid prefix or Network mismatch'); - } - if (decodeBech32.version != 0) { - throw ArgumentError('Invalid address version'); + + if (decodeBech32 != null) { + if (_network.bech32 != decodeBech32.hrp) { + throw ArgumentError('Invalid prefix or Network mismatch'); + } + if (decodeBech32.version != 0) { + throw ArgumentError('Invalid address version'); + } } } throw ArgumentError('$address has no matching Script'); @@ -1203,6 +1210,15 @@ class BitcoinCashWallet extends CoinServiceAPI { _transactionData = Future(() => cachedTxData!); } + bool validateCashAddr(String cashAddr) { + String addr = cashAddr; + if (cashAddr.contains(":")) { + addr = cashAddr.split(":").last; + } + + return addr.startsWith("q"); + } + @override bool validateAddress(String address) { try { @@ -1217,12 +1233,7 @@ class BitcoinCashWallet extends CoinServiceAPI { } if (format == bitbox.Address.formatCashAddr) { - String addr = address; - if (address.contains(":")) { - addr = address.split(":").last; - } - - return addr.startsWith("q"); + return validateCashAddr(address); } else { return address.startsWith("1"); } @@ -2085,7 +2096,8 @@ class BitcoinCashWallet extends CoinServiceAPI { String _convertToScriptHash(String bchAddress, NetworkType network) { try { if (bitbox.Address.detectFormat(bchAddress) == - bitbox.Address.formatCashAddr) { + bitbox.Address.formatCashAddr && + validateCashAddr(bchAddress)) { bchAddress = bitbox.Address.toLegacyAddress(bchAddress); } final output = Address.addressToOutputScript(bchAddress, network); @@ -2163,7 +2175,8 @@ class BitcoinCashWallet extends CoinServiceAPI { List allAddressesOld = await _fetchAllOwnAddresses(); List allAddresses = []; for (String address in allAddressesOld) { - if (bitbox.Address.detectFormat(address) == bitbox.Address.formatLegacy) { + if (bitbox.Address.detectFormat(address) == bitbox.Address.formatLegacy && + addressType(address: address) == DerivePathType.bip44) { allAddresses.add(bitbox.Address.toCashAddress(address)); } else { allAddresses.add(address); @@ -2882,7 +2895,12 @@ class BitcoinCashWallet extends CoinServiceAPI { String address = output["scriptPubKey"]["addresses"][0] as String; if (bitbox.Address.detectFormat(address) == bitbox.Address.formatCashAddr) { - address = bitbox.Address.toLegacyAddress(address); + if (validateCashAddr(address)) { + address = bitbox.Address.toLegacyAddress(address); + } else { + throw Exception( + "Unsupported address found during fetchBuildTxData(): $address"); + } } if (!addressTxid.containsKey(address)) { addressTxid[address] = []; @@ -2913,10 +2931,6 @@ class BitcoinCashWallet extends CoinServiceAPI { ); for (int i = 0; i < p2pkhLength; i++) { String address = addressesP2PKH[i]; - if (bitbox.Address.detectFormat(address) == - bitbox.Address.formatCashAddr) { - address = bitbox.Address.toLegacyAddress(address); - } // receives final receiveDerivation = receiveDerivations[address]; diff --git a/test/services/coins/bitcoincash/bitcoincash_wallet_test.dart b/test/services/coins/bitcoincash/bitcoincash_wallet_test.dart index 50ff8f741..077163809 100644 --- a/test/services/coins/bitcoincash/bitcoincash_wallet_test.dart +++ b/test/services/coins/bitcoincash/bitcoincash_wallet_test.dart @@ -60,7 +60,7 @@ void main() { }); }); - group("validate mainnet bitcoincash addresses", () { + group("mainnet bitcoincash addressType", () { MockElectrumX? client; MockCachedElectrumX? cachedClient; MockPriceAPI? priceAPI; @@ -136,6 +136,168 @@ void main() { verifyNoMoreInteractions(priceAPI); }); + test("P2PKH cashaddr with prefix", () { + expect( + mainnetWallet?.addressType( + address: + "bitcoincash:qrwjyc4pewj9utzrtnh0whkzkuvy5q8wg52n254x6k"), + DerivePathType.bip44); + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + + test("P2PKH cashaddr without prefix", () { + expect( + mainnetWallet?.addressType( + address: "qrwjyc4pewj9utzrtnh0whkzkuvy5q8wg52n254x6k"), + DerivePathType.bip44); + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + + test("Multisig cashaddr with prefix", () { + expect( + () => mainnetWallet?.addressType( + address: + "bitcoincash:pzpp3nchmzzf0gr69lj82ymurg5u3ds6kcwr5m07np"), + throwsArgumentError); + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + + test("Multisig cashaddr without prefix", () { + expect( + () => mainnetWallet?.addressType( + address: "pzpp3nchmzzf0gr69lj82ymurg5u3ds6kcwr5m07np"), + throwsArgumentError); + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + + test("Multisig/P2SH address", () { + expect( + mainnetWallet?.addressType( + address: "3DYuVEmuKWQFxJcF7jDPhwPiXLTiNnyMFb"), + DerivePathType.bip49); + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + }); + + group("validate mainnet bitcoincash addresses", () { + MockElectrumX? client; + MockCachedElectrumX? cachedClient; + MockPriceAPI? priceAPI; + FakeSecureStorage? secureStore; + MockTransactionNotificationTracker? tracker; + + BitcoinCashWallet? mainnetWallet; + + setUp(() { + client = MockElectrumX(); + cachedClient = MockCachedElectrumX(); + priceAPI = MockPriceAPI(); + secureStore = FakeSecureStorage(); + tracker = MockTransactionNotificationTracker(); + + mainnetWallet = BitcoinCashWallet( + walletId: "validateAddressMainNet", + walletName: "validateAddressMainNet", + coin: Coin.bitcoincash, + client: client!, + cachedClient: cachedClient!, + tracker: tracker!, + priceAPI: priceAPI, + secureStore: secureStore, + ); + }); + + test("valid mainnet legacy/p2pkh address type", () { + expect( + mainnetWallet?.validateAddress("1DP3PUePwMa5CoZwzjznVKhzdLsZftjcAT"), + true); + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + + test("valid mainnet legacy/p2pkh cashaddr with prefix address type", () { + expect( + mainnetWallet?.validateAddress( + "bitcoincash:qrwjyc4pewj9utzrtnh0whkzkuvy5q8wg52n254x6k"), + true); + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + + test("valid mainnet legacy/p2pkh cashaddr without prefix address type", () { + expect( + mainnetWallet + ?.validateAddress("qrwjyc4pewj9utzrtnh0whkzkuvy5q8wg52n254x6k"), + true); + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + + test("invalid legacy/p2pkh address type", () { + expect( + mainnetWallet?.validateAddress("mhqpGtwhcR6gFuuRjLTpHo41919QfuGy8Y"), + false); + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + + test( + "invalid cashaddr (is valid multisig but bitbox is broken for multisig)", + () { + expect( + mainnetWallet + ?.validateAddress("pzpp3nchmzzf0gr69lj82ymurg5u3ds6kcwr5m07np"), + false); + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + + test("multisig address should fail for bitbox", () { + expect( + mainnetWallet?.validateAddress("3DYuVEmuKWQFxJcF7jDPhwPiXLTiNnyMFb"), + false); + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + test("invalid mainnet bitcoincash legacy/p2pkh address", () { expect( mainnetWallet?.validateAddress("mhqpGtwhcR6gFuuRjLTpHo41919QfuGy8Y"), From b3a343d28a29c6cc60e3f71d6d851b2aff27ebcc Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 7 Nov 2022 15:48:16 -0600 Subject: [PATCH 32/76] desktop theme toggle --- .../settings_menu/appearance_settings.dart | 391 ++++++++++-------- 1 file changed, 218 insertions(+), 173 deletions(-) diff --git a/lib/pages_desktop_specific/home/settings_menu/appearance_settings.dart b/lib/pages_desktop_specific/home/settings_menu/appearance_settings.dart index b5f239ab1..bf6fc81e2 100644 --- a/lib/pages_desktop_specific/home/settings_menu/appearance_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/appearance_settings.dart @@ -1,16 +1,19 @@ import 'package:flutter/material.dart'; -import 'package:flutter/src/widgets/framework.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/hive/db.dart'; +import 'package:stackwallet/providers/global/prefs_provider.dart'; +import 'package:stackwallet/providers/ui/color_theme_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/theme/color_theme.dart'; +import 'package:stackwallet/utilities/theme/dark_colors.dart'; +import 'package:stackwallet/utilities/theme/light_colors.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/custom_buttons/draggable_switch_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; -import '../../../providers/global/prefs_provider.dart'; -import '../../../utilities/constants.dart'; -import '../../../widgets/custom_buttons/draggable_switch_button.dart'; - class AppearanceOptionSettings extends ConsumerStatefulWidget { const AppearanceOptionSettings({Key? key}) : super(key: key); @@ -140,7 +143,10 @@ class _AppearanceOptionSettings ], ), ), - ThemeToggle(), + const Padding( + padding: EdgeInsets.all(10), + child: ThemeToggle(), + ), ], ), ), @@ -150,7 +156,7 @@ class _AppearanceOptionSettings } } -class ThemeToggle extends StatefulWidget { +class ThemeToggle extends ConsumerStatefulWidget { const ThemeToggle({ Key? key, }) : super(key: key); @@ -159,187 +165,226 @@ class ThemeToggle extends StatefulWidget { // final void Function(bool)? onChanged; @override - State createState() => _ThemeToggle(); + ConsumerState createState() => _ThemeToggle(); } -class _ThemeToggle extends State { +class _ThemeToggle extends ConsumerState { // late bool externalCallsEnabled; + late String _selectedTheme; + + @override + void initState() { + _selectedTheme = + DB.instance.get(boxName: DB.boxNameTheme, key: "colorScheme") + as String? ?? + "light"; + + super.initState(); + } + @override Widget build(BuildContext context) { return Row( - // mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: RawMaterialButton( - elevation: 0, - hoverColor: Colors.transparent, - shape: RoundedRectangleBorder( - side: BorderSide( - color: - Theme.of(context).extension()!.infoItemIcons, - width: 2, - ), - // side: !externalCallsEnabled - // ? BorderSide.none - // : BorderSide( - // color: Theme.of(context) - // .extension()! - // .infoItemIcons, - // width: 2, - // ), - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius * 2, - ), - ), - onPressed: () {}, //onPressed - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Stack( - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.only( - left: 24, - ), - child: SvgPicture.asset( - Assets.svg.themeLight, - ), - ), - Padding( - padding: const EdgeInsets.only( - left: 50, - top: 12, - ), - child: Text( - "Light", - style: STextStyles.desktopTextExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ), - ), - ) - ], - ), - // if (externalCallsEnabled) - Positioned( - bottom: 0, - left: 6, - child: SvgPicture.asset( - Assets.svg.checkCircle, - width: 20, - height: 20, - color: Theme.of(context) - .extension()! - .infoItemIcons, - ), - ), - // if (!externalCallsEnabled) - // Positioned( - // bottom: 0, - // left: 6, - // child: Container( - // width: 20, - // height: 20, - // decoration: BoxDecoration( - // borderRadius: BorderRadius.circular(1000), - // color: Theme.of(context) - // .extension()! - // .textFieldDefaultBG, - // ), - // ), - // ), - ], - ), + MaterialButton( + splashColor: Colors.transparent, + hoverColor: Colors.transparent, + padding: const EdgeInsets.all(0), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, ), ), - ), - const SizedBox( - width: 1, - ), - Expanded( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: RawMaterialButton( - elevation: 0, - hoverColor: Colors.transparent, - shape: RoundedRectangleBorder( - // side: !externalCallsEnabled - // ? BorderSide.none - // : BorderSide( - // color: Theme.of(context) - // .extension()! - // .infoItemIcons, - // width: 2, - // ), - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius * 2, - ), - ), - onPressed: () {}, //onPressed - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Stack( - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SvgPicture.asset( - Assets.svg.themeDark, - ), - Padding( - padding: const EdgeInsets.only( - left: 45, - top: 12, - ), - child: Text( - "Dark", - style: STextStyles.desktopTextExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ), - ), - ), - ], - ), - // if (externalCallsEnabled) - // Positioned( - // bottom: 0, - // left: 0, - // child: SvgPicture.asset( - // Assets.svg.checkCircle, - // width: 20, - // height: 20, - // color: Theme.of(context) - // .extension()! - // .infoItemIcons, - // ), - // ), - // if (!externalCallsEnabled) - Positioned( - bottom: 0, - left: 0, - child: Container( - width: 20, - height: 20, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(1000), - color: Theme.of(context) + onPressed: () { + DB.instance.put( + boxName: DB.boxNameTheme, + key: "colorScheme", + value: ThemeType.light.name, + ); + ref.read(colorThemeProvider.state).state = + StackColors.fromStackColorTheme( + LightColors(), + ); + + setState(() { + _selectedTheme = "light"; + }); + }, + child: SizedBox( + width: 200, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + decoration: BoxDecoration( + border: Border.all( + width: 2.5, + color: _selectedTheme == "light" + ? Theme.of(context) .extension()! - .textFieldDefaultBG, - ), + .infoItemIcons + : Theme.of(context).extension()!.popupBG, + ), + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + child: SvgPicture.asset( + Assets.svg.themeLight, + ), + ), + const SizedBox( + height: 12, + ), + Row( + children: [ + SizedBox( + width: 20, + height: 20, + child: Radio( + activeColor: Theme.of(context) + .extension()! + .radioButtonIconEnabled, + value: "light", + groupValue: _selectedTheme, + onChanged: (newValue) { + if (newValue is String && newValue == "light") { + DB.instance.put( + boxName: DB.boxNameTheme, + key: "colorScheme", + value: ThemeType.light.name, + ); + ref.read(colorThemeProvider.state).state = + StackColors.fromStackColorTheme( + LightColors(), + ); + + setState(() { + _selectedTheme = "light"; + }); + } + }, + ), + ), + const SizedBox( + width: 14, + ), + Text( + "Light", + style: + STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textDark, ), ), ], ), - ), + ], + ), + ), + ), + const SizedBox( + width: 20, + ), + MaterialButton( + splashColor: Colors.transparent, + hoverColor: Colors.transparent, + padding: const EdgeInsets.all(0), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: () { + DB.instance.put( + boxName: DB.boxNameTheme, + key: "colorScheme", + value: ThemeType.dark.name, + ); + ref.read(colorThemeProvider.state).state = + StackColors.fromStackColorTheme( + DarkColors(), + ); + + setState(() { + _selectedTheme = "dark"; + }); + }, + child: SizedBox( + width: 200, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + decoration: BoxDecoration( + border: Border.all( + width: 2.5, + color: _selectedTheme == "dark" + ? Theme.of(context) + .extension()! + .infoItemIcons + : Theme.of(context).extension()!.popupBG, + ), + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + child: SvgPicture.asset( + Assets.svg.themeDark, + ), + ), + const SizedBox( + height: 12, + ), + Row( + children: [ + SizedBox( + width: 20, + height: 20, + child: Radio( + activeColor: Theme.of(context) + .extension()! + .radioButtonIconEnabled, + value: "dark", + groupValue: _selectedTheme, + onChanged: (newValue) { + if (newValue is String && newValue == "dark") { + DB.instance.put( + boxName: DB.boxNameTheme, + key: "colorScheme", + value: ThemeType.dark.name, + ); + ref.read(colorThemeProvider.state).state = + StackColors.fromStackColorTheme( + DarkColors(), + ); + + setState(() { + _selectedTheme = "dark"; + }); + } + }, + ), + ), + const SizedBox( + width: 14, + ), + Text( + "Dark", + style: + STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + ), + ], + ), + ], ), ), ), From 3c627a5ddb36e93c229f9e96a9829df1664d700d Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 7 Nov 2022 15:57:04 -0600 Subject: [PATCH 33/76] support tweak --- .../desktop_support_view.dart | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/pages_desktop_specific/home/support_and_about_view/desktop_support_view.dart b/lib/pages_desktop_specific/home/support_and_about_view/desktop_support_view.dart index 8e9d709d1..ce3e3f3cc 100644 --- a/lib/pages_desktop_specific/home/support_and_about_view/desktop_support_view.dart +++ b/lib/pages_desktop_specific/home/support_and_about_view/desktop_support_view.dart @@ -1,12 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/pages/settings_views/global_settings_view/support_view.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; -import '../../../pages/settings_views/global_settings_view/support_view.dart'; - class DesktopSupportView extends ConsumerStatefulWidget { const DesktopSupportView({Key? key}) : super(key: key); @@ -38,10 +37,18 @@ class _DesktopSupportView extends ConsumerState { ), ), body: Column( - children: const [ + crossAxisAlignment: CrossAxisAlignment.start, + children: [ Padding( - padding: EdgeInsets.fromLTRB(24, 10, 377, 270), - child: SupportView(), + padding: const EdgeInsets.fromLTRB(24, 10, 0, 0), + child: Row( + children: const [ + SizedBox( + width: 576, + child: SupportView(), + ), + ], + ), ), ], ), From 2f6b1278fe59971855e9772de7e60961abf3a29e Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 7 Nov 2022 16:30:17 -0600 Subject: [PATCH 34/76] swb desktop layout tweaks --- .../create_backup_view.dart | 4 +- .../restore_from_file_view.dart | 4 +- .../backup_and_restore_settings.dart | 192 ++++++++++-------- .../enable_backup_dialog.dart | 3 +- 4 files changed, 115 insertions(+), 88 deletions(-) diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart index 2d2ed4960..30fcb7962 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart @@ -157,7 +157,7 @@ class _RestoreFromFileViewState extends State { .textDark3), ), ), - // child, + child, const SizedBox(height: 20), Row( children: [ @@ -442,7 +442,7 @@ class _RestoreFromFileViewState extends State { const SizedBox( height: 16, ), - const Spacer(), + if (!isDesktop) const Spacer(), TextButton( style: shouldEnableCreate ? Theme.of(context) diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart index 9f2796415..0c101d0b3 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart @@ -142,7 +142,7 @@ class _RestoreFromFileViewState extends ConsumerState { textAlign: TextAlign.left, ), ), - // child, + child, const SizedBox(height: 20), Row( children: [ @@ -285,7 +285,7 @@ class _RestoreFromFileViewState extends ConsumerState { const SizedBox( height: 16, ), - const Spacer(), + if (!isDesktop) const Spacer(), TextButton( style: passwordController.text.isEmpty || fileLocationController.text.isEmpty diff --git a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart index b59206f17..49debf22f 100644 --- a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart @@ -64,48 +64,56 @@ class _BackupRestoreSettings extends ConsumerState { height: 48, ), Center( - child: Padding( - padding: const EdgeInsets.all(10), - child: RichText( - textAlign: TextAlign.start, - text: TextSpan( - children: [ - TextSpan( - text: "Auto Backup", - style: - STextStyles.desktopTextSmall(context), + child: Row( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.all(10), + child: RichText( + textAlign: TextAlign.start, + text: TextSpan( + children: [ + TextSpan( + text: "Auto Backup", + style: STextStyles.desktopTextSmall( + context), + ), + TextSpan( + text: + "\n\nAuto backup is a custom Stack Wallet feature that offers a convenient backup of your data." + "To ensure maximum security, we recommend using a unique password that you haven't used anywhere " + "else on the internet before. Your password is not stored.", + style: STextStyles + .desktopTextExtraExtraSmall( + context), + ), + TextSpan( + text: + "\n\nFor more information, please see our website ", + style: STextStyles + .desktopTextExtraExtraSmall( + context), + ), + TextSpan( + text: "stackwallet.com", + style: STextStyles.richLink(context) + .copyWith(fontSize: 14), + recognizer: TapGestureRecognizer() + ..onTap = () { + launchUrl( + Uri.parse( + "https://stackwallet.com/"), + mode: LaunchMode + .externalApplication, + ); + }, + ), + ], + ), ), - TextSpan( - text: - "\n\nAuto backup is a custom Stack Wallet feature that offers a convenient backup of your data." - "To ensure maximum security, we recommend using a unique password that you haven't used anywhere " - "else on the internet before. Your password is not stored.", - style: STextStyles - .desktopTextExtraExtraSmall(context), - ), - TextSpan( - text: - "\n\nFor more information, please see our website ", - style: STextStyles - .desktopTextExtraExtraSmall(context), - ), - TextSpan( - text: "stackwallet.com", - style: STextStyles.richLink(context) - .copyWith(fontSize: 14), - recognizer: TapGestureRecognizer() - ..onTap = () { - launchUrl( - Uri.parse( - "https://stackwallet.com/"), - mode: - LaunchMode.externalApplication, - ); - }, - ), - ], + ), ), - ), + ], ), ), Column( @@ -148,39 +156,49 @@ class _BackupRestoreSettings extends ConsumerState { alignment: Alignment.topLeft, ), Center( - child: Padding( - padding: const EdgeInsets.all(10), - child: RichText( - textAlign: TextAlign.start, - text: TextSpan( - children: [ - TextSpan( - text: "Manual Backup", - style: - STextStyles.desktopTextSmall(context), + child: Row( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.all(10), + child: RichText( + textAlign: TextAlign.start, + text: TextSpan( + children: [ + TextSpan( + text: "Manual Backup", + style: STextStyles.desktopTextSmall( + context), + ), + TextSpan( + text: + "\n\nCreate manual backup to easily transfer your data between devices. " + "You will create a backup file that can be later used in the Restore option. " + "Use a strong password to encrypt your data.", + style: STextStyles + .desktopTextExtraExtraSmall( + context), + ), + ], + ), ), - TextSpan( - text: - "\n\nCreate manual backup to easily transfer your data between devices. " - "You will create a backup file that can be later used in the Restore option. " - "Use a strong password to encrypt your data.", - style: STextStyles - .desktopTextExtraExtraSmall(context), - ), - ], + ), ), - ), + ], ), ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( - padding: EdgeInsets.all( + padding: const EdgeInsets.all( 10, ), child: createBackup - ? const CreateBackupView() + ? const SizedBox( + width: 512, + child: CreateBackupView(), + ) : PrimaryButton( desktopMed: true, width: 200, @@ -217,27 +235,34 @@ class _BackupRestoreSettings extends ConsumerState { alignment: Alignment.topLeft, ), Center( - child: Padding( - padding: const EdgeInsets.all(10), - child: RichText( - textAlign: TextAlign.start, - text: TextSpan( - children: [ - TextSpan( - text: "Restore Backup", - style: - STextStyles.desktopTextSmall(context), + child: Row( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.all(10), + child: RichText( + textAlign: TextAlign.start, + text: TextSpan( + children: [ + TextSpan( + text: "Restore Backup", + style: STextStyles.desktopTextSmall( + context), + ), + TextSpan( + text: + "\n\nUse your Stack Wallet backup file to restore your wallets, address book " + "and wallet preferences.", + style: STextStyles + .desktopTextExtraExtraSmall( + context), + ), + ], + ), ), - TextSpan( - text: - "\n\nUse your Stack Wallet backup file to restore your wallets, address book " - "and wallet preferences.", - style: STextStyles - .desktopTextExtraExtraSmall(context), - ), - ], + ), ), - ), + ], ), ), Column( @@ -248,7 +273,10 @@ class _BackupRestoreSettings extends ConsumerState { 10, ), child: restoreBackup - ? RestoreFromFileView() + ? const SizedBox( + width: 512, + child: RestoreFromFileView(), + ) : PrimaryButton( desktopMed: true, width: 200, diff --git a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/enable_backup_dialog.dart b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/enable_backup_dialog.dart index 046d136a8..963fb4441 100644 --- a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/enable_backup_dialog.dart +++ b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/enable_backup_dialog.dart @@ -61,8 +61,7 @@ class EnableBackupDialog extends StatelessWidget { child: SecondaryButton( label: "Cancel", onPressed: () { - int count = 0; - Navigator.of(context).popUntil((_) => count++ >= 2); + Navigator.of(context).pop(); }, ), ), From 77aa3bc8e484534f893c3ee5f04820bca50918ad Mon Sep 17 00:00:00 2001 From: sneurlax Date: Mon, 7 Nov 2022 16:45:26 -0600 Subject: [PATCH 35/76] use wownero-seed for 14 word seed, use wownero wallet2 for 25 word seed and update tests showing examples of both. TODO proper validation, must eg calculate and check checksums etc --- crypto_plugins/flutter_libmonero | 2 +- .../coins/wownero/wownero_wallet_test.dart | 40 ++++++++++--------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/crypto_plugins/flutter_libmonero b/crypto_plugins/flutter_libmonero index da826f313..e95c19662 160000 --- a/crypto_plugins/flutter_libmonero +++ b/crypto_plugins/flutter_libmonero @@ -1 +1 @@ -Subproject commit da826f31352c695942bc9b821d1d0c82a9267ade +Subproject commit e95c19662ccf17d83109ab7b651cfbc0521deb47 diff --git a/test/services/coins/wownero/wownero_wallet_test.dart b/test/services/coins/wownero/wownero_wallet_test.dart index ca7540da5..2099df364 100644 --- a/test/services/coins/wownero/wownero_wallet_test.dart +++ b/test/services/coins/wownero/wownero_wallet_test.dart @@ -83,7 +83,6 @@ void main() async { _walletInfoSource = await Hive.openBox(WalletInfo.boxName); walletService = wownero.createWowneroWalletService(_walletInfoSource); - /* group("Wownero 14 word tests", () { setUp(() async { bool hasThrown = false; @@ -91,7 +90,9 @@ void main() async { final dirPath = await pathForWalletDir(name: name, type: type); path = await pathForWallet(name: name, type: type); credentials = wownero.createWowneroRestoreWalletFromSeedCredentials( - name: name, height: 465760, mnemonic: testMnemonic14); // TODO catch failure + name: name, + height: 465760, + mnemonic: testMnemonic14); // TODO catch failure walletInfo = WalletInfo.external( id: WalletBase.idFor(name, type), @@ -128,21 +129,21 @@ void main() async { try { await _walletInfoSource.add(walletInfo); walletBase?.close(); - walletBase = wallet as WowneroWalletBase; + walletBase = wallet as WowneroWalletBase; expect(walletInfo.address, mainnetTestData14[0][0]); - expect( - await walletBase!.getTransactionAddress(0, 0), mainnetTestData14[0][0]); - expect( - await walletBase!.getTransactionAddress(0, 1), mainnetTestData14[0][1]); - expect( - await walletBase!.getTransactionAddress(0, 2), mainnetTestData14[0][2]); - expect( - await walletBase!.getTransactionAddress(1, 0), mainnetTestData14[1][0]); - expect( - await walletBase!.getTransactionAddress(1, 1), mainnetTestData14[1][1]); - expect( - await walletBase!.getTransactionAddress(1, 2), mainnetTestData14[1][2]); + expect(await walletBase!.getTransactionAddress(0, 0), + mainnetTestData14[0][0]); + expect(await walletBase!.getTransactionAddress(0, 1), + mainnetTestData14[0][1]); + expect(await walletBase!.getTransactionAddress(0, 2), + mainnetTestData14[0][2]); + expect(await walletBase!.getTransactionAddress(1, 0), + mainnetTestData14[1][0]); + expect(await walletBase!.getTransactionAddress(1, 1), + mainnetTestData14[1][1]); + expect(await walletBase!.getTransactionAddress(1, 2), + mainnetTestData14[1][2]); } catch (_) { hasThrown = true; } @@ -151,19 +152,21 @@ void main() async { walletBase?.close(); walletBase = wallet as WowneroWalletBase; }); - + // TODO delete left over wallet file with name: name }); - */ group("Wownero 25 word tests", () { setUp(() async { bool hasThrown = false; try { + name = 'namee${Random().nextInt(10000000)}'; final dirPath = await pathForWalletDir(name: name, type: type); path = await pathForWallet(name: name, type: type); credentials = wownero.createWowneroRestoreWalletFromSeedCredentials( - name: name, height: 465760, mnemonic: testMnemonic25); // TODO catch failure + name: name, + height: 465760, + mnemonic: testMnemonic25); // TODO catch failure walletInfo = WalletInfo.external( id: WalletBase.idFor(name, type), @@ -195,7 +198,6 @@ void main() async { test("Test mainnet address generation from 25 word seed", () async { bool hasThrown = false; try { - name = 'namee${Random().nextInt(10000000)}'; final dirPath = await pathForWalletDir(name: name, type: type); path = await pathForWallet(name: name, type: type); From fa0c982274159d3d7be179ad48a88cf50faef62a Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 8 Nov 2022 07:35:28 -0600 Subject: [PATCH 36/76] Return what we internally consider the "txid" for epic transactions from the epic confirmSend to be consistent with all other coins confirmSend return value. This should fix the epic notes issue. --- .../coins/epiccash/epiccash_wallet.dart | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/services/coins/epiccash/epiccash_wallet.dart b/lib/services/coins/epiccash/epiccash_wallet.dart index 3702a9158..6c71f39e4 100644 --- a/lib/services/coins/epiccash/epiccash_wallet.dart +++ b/lib/services/coins/epiccash/epiccash_wallet.dart @@ -833,10 +833,16 @@ class EpicCashWallet extends CoinServiceAPI { final txLogEntryFirst = txLogEntry[0]; Logger.print("TX_LOG_ENTRY_IS $txLogEntryFirst"); final wallet = await Hive.openBox(_walletId); - final slateToAddresses = (await wallet.get("slate_to_address")) as Map?; - slateToAddresses?[txLogEntryFirst['tx_slate_id']] = txData['addresss']; + final slateToAddresses = + (await wallet.get("slate_to_address")) as Map? ?? {}; + final slateId = txLogEntryFirst['tx_slate_id'] as String; + slateToAddresses[slateId] = txData['addresss']; await wallet.put('slate_to_address', slateToAddresses); - return txLogEntryFirst['tx_slate_id'] as String; + final slatesToCommits = await getSlatesToCommits(); + String? commitId = slatesToCommits[slateId]?['commitId'] as String?; + Logging.instance.log("sent commitId: $commitId", level: LogLevel.Info); + return commitId!; + // return txLogEntryFirst['tx_slate_id'] as String; } } catch (e, s) { Logging.instance.log("Error sending $e - $s", level: LogLevel.Error); @@ -2155,8 +2161,9 @@ class EpicCashWallet extends CoinServiceAPI { as String? ?? ""; String? commitId = slatesToCommits[slateId]?['commitId'] as String?; - Logging.instance - .log("commitId: $commitId $slateId", level: LogLevel.Info); + Logging.instance.log( + "commitId: $commitId, slateId: $slateId, id: ${tx["id"]}", + level: LogLevel.Info); bool isCancelled = tx["tx_type"] == "TxSentCancelled" || tx["tx_type"] == "TxReceivedCancelled"; From 7c3d40782cbd024911e4626f6a0aa15d85290b79 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Tue, 8 Nov 2022 09:55:15 -0600 Subject: [PATCH 37/76] add generation tests and update flutter_libmonero ref change seedWords to SeedWordsLength to match rest of codebase --- crypto_plugins/flutter_libmonero | 2 +- .../coins/wownero/wownero_wallet.dart | 20 +- .../coins/wownero/wownero_wallet_test.dart | 172 ++++++++++++++---- 3 files changed, 147 insertions(+), 47 deletions(-) diff --git a/crypto_plugins/flutter_libmonero b/crypto_plugins/flutter_libmonero index e95c19662..afdee4b88 160000 --- a/crypto_plugins/flutter_libmonero +++ b/crypto_plugins/flutter_libmonero @@ -1 +1 @@ -Subproject commit e95c19662ccf17d83109ab7b651cfbc0521deb47 +Subproject commit afdee4b880202f39a2375afc320f0642e98a1827 diff --git a/lib/services/coins/wownero/wownero_wallet.dart b/lib/services/coins/wownero/wownero_wallet.dart index 1153a881a..a0dc7bfe0 100644 --- a/lib/services/coins/wownero/wownero_wallet.dart +++ b/lib/services/coins/wownero/wownero_wallet.dart @@ -647,7 +647,7 @@ class WowneroWallet extends CoinServiceAPI { } //TODO: take in the default language when creating wallet. - Future _generateNewWallet() async { + Future _generateNewWallet({int seedWordsLength = 14}) async { Logging.instance .log("IS_INTEGRATION_TEST: $integrationTestFlag", level: LogLevel.Info); // TODO: ping wownero server and make sure the genesis hash matches @@ -687,6 +687,7 @@ class WowneroWallet extends CoinServiceAPI { credentials = wownero.createWowneroNewWalletCredentials( name: name, language: "English", + seedWordsLength: seedWordsLength ); walletInfo = WalletInfo.external( @@ -713,12 +714,12 @@ class WowneroWallet extends CoinServiceAPI { final wallet = await _walletCreationService?.create(credentials); // subtract a couple days to ensure we have a buffer for SWB - // 14 words - //final bufferedCreateHeight = getSeedHeightSync(wallet?.seed.trim() as String); - - // 25 words - final bufferedCreateHeight = 0; - // TODO use an alternative to wow_seed's get_seed_height + if (seedWordsLength == 14) { + final bufferedCreateHeight = getSeedHeightSync(wallet?.seed.trim() as String); + } else { + final bufferedCreateHeight = 0; + // TODO use an alternative to wow_seed's get_seed_height + } await DB.instance.put( boxName: walletId, key: "restoreHeight", value: bufferedCreateHeight); @@ -726,6 +727,7 @@ class WowneroWallet extends CoinServiceAPI { await _secureStore.write( key: '${_walletId}_mnemonic', value: wallet?.seed.trim()); + walletInfo.address = wallet?.walletAddresses.address; await DB.instance .add(boxName: WalletInfo.boxName, value: walletInfo); @@ -782,7 +784,7 @@ class WowneroWallet extends CoinServiceAPI { @override // TODO: implement initializeWallet - Future initializeNew() async { + Future initializeNew({int seedWordsLength = 14}) async { await _prefs.init(); // TODO: ping actual wownero network // try { @@ -800,7 +802,7 @@ class WowneroWallet extends CoinServiceAPI { prefs = await SharedPreferences.getInstance(); keysStorage = KeyService(storage!); - await _generateNewWallet(); + await _generateNewWallet(seedWordsLength: seedWordsLength); // var password; // try { // password = diff --git a/test/services/coins/wownero/wownero_wallet_test.dart b/test/services/coins/wownero/wownero_wallet_test.dart index 2099df364..660bc1438 100644 --- a/test/services/coins/wownero/wownero_wallet_test.dart +++ b/test/services/coins/wownero/wownero_wallet_test.dart @@ -46,7 +46,7 @@ dynamic _walletInfoSource; String path = ''; -String name = 'namee${Random().nextInt(10000000)}'; +String name = ''; int nettype = 0; WalletType type = WalletType.wownero; @@ -83,10 +83,75 @@ void main() async { _walletInfoSource = await Hive.openBox(WalletInfo.boxName); walletService = wownero.createWowneroWalletService(_walletInfoSource); - group("Wownero 14 word tests", () { + group("Wownero 14 word seed generation", () { setUp(() async { bool hasThrown = false; try { + name = 'namee${Random().nextInt(10000000)}'; + final dirPath = await pathForWalletDir(name: name, type: type); + path = await pathForWallet(name: name, type: type); + credentials = wownero.createWowneroNewWalletCredentials( + name: name, + language: "English", + seedWordsLength: 14); // TODO catch failure + + walletInfo = WalletInfo.external( + id: WalletBase.idFor(name, type), + name: name, + type: type, + isRecovery: false, + restoreHeight: credentials.height ?? 0, + date: DateTime.now(), + path: path, + address: "", + dirPath: dirPath); + credentials.walletInfo = walletInfo; + + _walletCreationService = WalletCreationService( + secureStorage: storage, + sharedPreferences: prefs, + walletService: walletService, + keyService: keysStorage, + ); + _walletCreationService.changeWalletType(); + } catch (e, s) { + print(e); + print(s); + hasThrown = true; + } + expect(hasThrown, false); + }); + + test("Wownero 14 word seed address generation", () async { + final wallet = await _walletCreationService.create(credentials); + // TODO validate mnemonic + walletInfo.address = wallet.walletAddresses.address; + + bool hasThrown = false; + try { + await _walletInfoSource.add(walletInfo); + walletBase?.close(); + walletBase = wallet as WowneroWalletBase; + + // TODO validate + //expect(walletInfo.address, mainnetTestData14[0][0]); + } catch (_) { + hasThrown = true; + } + expect(hasThrown, false); + + walletBase?.close(); + walletBase = wallet as WowneroWalletBase; + }); + + // TODO delete left over wallet file with name: name + }); + + group("Wownero 14 word seed restoration", () { + setUp(() async { + bool hasThrown = false; + try { + name = 'namee${Random().nextInt(10000000)}'; final dirPath = await pathForWalletDir(name: name, type: type); path = await pathForWallet(name: name, type: type); credentials = wownero.createWowneroRestoreWalletFromSeedCredentials( @@ -121,7 +186,7 @@ void main() async { expect(hasThrown, false); }); - test("Test mainnet address generation from 14 word seed", () async { + test("Wownero 14 word seed address generation", () async { final wallet = await _walletCreationService.restoreFromSeed(credentials); walletInfo.address = wallet.walletAddresses.address; @@ -156,7 +221,71 @@ void main() async { // TODO delete left over wallet file with name: name }); - group("Wownero 25 word tests", () { + group("Wownero 25 word seed generation", () { + setUp(() async { + bool hasThrown = false; + try { + name = 'namee${Random().nextInt(10000000)}'; + final dirPath = await pathForWalletDir(name: name, type: type); + path = await pathForWallet(name: name, type: type); + credentials = wownero.createWowneroNewWalletCredentials( + name: name, + language: "English", + seedWordsLength: 25); // TODO catch failure + + walletInfo = WalletInfo.external( + id: WalletBase.idFor(name, type), + name: name, + type: type, + isRecovery: false, + restoreHeight: credentials.height ?? 0, + date: DateTime.now(), + path: path, + address: "", + dirPath: dirPath); + credentials.walletInfo = walletInfo; + + _walletCreationService = WalletCreationService( + secureStorage: storage, + sharedPreferences: prefs, + walletService: walletService, + keyService: keysStorage, + ); + _walletCreationService.changeWalletType(); + } catch (e, s) { + print(e); + print(s); + hasThrown = true; + } + expect(hasThrown, false); + }); + + test("Wownero 25 word seed address generation", () async { + final wallet = await _walletCreationService.create(credentials); + // TODO validate mnemonic + walletInfo.address = wallet.walletAddresses.address; + + bool hasThrown = false; + try { + await _walletInfoSource.add(walletInfo); + walletBase?.close(); + walletBase = wallet as WowneroWalletBase; + + // TODO validate + //expect(walletInfo.address, mainnetTestData14[0][0]); + } catch (_) { + hasThrown = true; + } + expect(hasThrown, false); + + walletBase?.close(); + walletBase = wallet as WowneroWalletBase; + }); + + // TODO delete left over wallet file with name: name + }); + + group("Wownero 25 word seed restoration", () { setUp(() async { bool hasThrown = false; try { @@ -195,42 +324,11 @@ void main() async { expect(hasThrown, false); }); - test("Test mainnet address generation from 25 word seed", () async { - bool hasThrown = false; - try { - final dirPath = await pathForWalletDir(name: name, type: type); - path = await pathForWallet(name: name, type: type); - - walletInfo = WalletInfo.external( - id: WalletBase.idFor(name, type), - name: name, - type: type, - isRecovery: false, - restoreHeight: credentials.height ?? 0, - date: DateTime.now(), - path: path, - address: "", - dirPath: dirPath); - credentials.walletInfo = walletInfo; - - _walletCreationService = WalletCreationService( - secureStorage: storage, - sharedPreferences: prefs, - walletService: walletService, - keyService: keysStorage, - ); - _walletCreationService.changeWalletType(); - } catch (e, s) { - print(e); - print(s); - hasThrown = true; - } - expect(hasThrown, false); - + test("Wownero 25 word seed address generation", () async { final wallet = await _walletCreationService.restoreFromSeed(credentials); walletInfo.address = wallet.walletAddresses.address; - hasThrown = false; + bool hasThrown = false; try { await _walletInfoSource.add(walletInfo); walletBase?.close(); From f17785ffc7e81f187239881eed0e72da6d7fb8b2 Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 8 Nov 2022 10:18:48 -0600 Subject: [PATCH 38/76] monero/wownero untrusted cert popup --- .../add_edit_node_view.dart | 24 +++- .../manage_nodes_views/node_details_view.dart | 24 +++- .../test_monero_node_connection.dart | 115 +++++++++++++++--- lib/widgets/node_card.dart | 24 +++- lib/widgets/node_options_sheet.dart | 24 +++- 5 files changed, 192 insertions(+), 19 deletions(-) diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart index 87aee413e..382e3f09e 100644 --- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart +++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart @@ -110,7 +110,29 @@ class _AddEditNodeViewState extends ConsumerState { ref.read(nodeFormDataProvider).useSSL = false; } - testPassed = await testMoneroNodeConnection(Uri.parse(uriString)); + final response = await testMoneroNodeConnection( + Uri.parse(uriString), + false, + ); + + if (response.cert != null) { + if (mounted) { + final shouldAllowBadCert = await showBadX509CertificateDialog( + response.cert!, + response.url!, + response.port!, + context, + ); + + if (shouldAllowBadCert) { + final response = await testMoneroNodeConnection( + Uri.parse(uriString), true); + testPassed = response.success; + } + } + } else { + testPassed = response.success; + } } } catch (e, s) { Logging.instance.log("$e\n$s", level: LogLevel.Warning); diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart index c5e666ce2..f9b64c460 100644 --- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart +++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart @@ -97,7 +97,29 @@ class _NodeDetailsViewState extends ConsumerState { String uriString = "${uri.scheme}://${uri.host}:${node.port}$path"; - testPassed = await testMoneroNodeConnection(Uri.parse(uriString)); + final response = await testMoneroNodeConnection( + Uri.parse(uriString), + false, + ); + + if (response.cert != null) { + if (mounted) { + final shouldAllowBadCert = await showBadX509CertificateDialog( + response.cert!, + response.url!, + response.port!, + context, + ); + + if (shouldAllowBadCert) { + final response = await testMoneroNodeConnection( + Uri.parse(uriString), true); + testPassed = response.success; + } + } + } else { + testPassed = response.success; + } } } catch (e, s) { Logging.instance.log("$e\n$s", level: LogLevel.Warning); diff --git a/lib/utilities/test_monero_node_connection.dart b/lib/utilities/test_monero_node_connection.dart index 7cb01e8b1..92b645141 100644 --- a/lib/utilities/test_monero_node_connection.dart +++ b/lib/utilities/test_monero_node_connection.dart @@ -1,26 +1,111 @@ import 'dart:convert'; +import 'dart:io'; -import 'package:http/http.dart' as http; +import 'package:flutter/material.dart'; +import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/stack_dialog.dart'; -Future testMoneroNodeConnection(Uri uri) async { +class MoneroNodeConnectionResponse { + final X509Certificate? cert; + final String? url; + final int? port; + final bool success; + + MoneroNodeConnectionResponse(this.cert, this.url, this.port, this.success); +} + +Future testMoneroNodeConnection( + Uri uri, + bool allowBadX509Certificate, +) async { + final client = HttpClient(); + MoneroNodeConnectionResponse? badCertResponse; try { - final client = http.Client(); - final response = await client - .post( - uri, - headers: {'Content-Type': 'application/json'}, - body: jsonEncode({"jsonrpc": "2.0", "id": "0", "method": "get_info"}), - ) - .timeout(const Duration(milliseconds: 1200), - onTimeout: () async => http.Response('Error', 408)); + client.badCertificateCallback = (cert, url, port) { + if (allowBadX509Certificate) { + return true; + } - final result = jsonDecode(response.body); + if (badCertResponse == null) { + badCertResponse = MoneroNodeConnectionResponse(cert, url, port, false); + } else { + return false; + } + + return false; + }; + + final request = await client.postUrl(uri); + + final body = utf8.encode( + jsonEncode({ + "jsonrpc": "2.0", + "id": "0", + "method": "get_info", + }), + ); + + request.headers.add( + 'Content-Length', + body.length.toString(), + preserveHeaderCase: true, + ); + request.headers.set( + 'Content-Type', + 'application/json', + preserveHeaderCase: true, + ); + + request.add(body); + + final response = await request.close(); + final result = await response.transform(utf8.decoder).join(); // TODO: json decoded without error so assume connection exists? // or we can check for certain values in the response to decide - return true; + return MoneroNodeConnectionResponse(null, null, null, true); } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Warning); - return false; + if (badCertResponse != null) { + return badCertResponse!; + } else { + Logging.instance.log("$e\n$s", level: LogLevel.Warning); + return MoneroNodeConnectionResponse(null, null, null, false); + } + } finally { + client.close(force: true); } } + +Future showBadX509CertificateDialog( + X509Certificate cert, + String url, + int port, + BuildContext context, +) async { + final result = await showDialog( + context: context, + barrierDismissible: false, + builder: (context) { + return StackDialog( + title: "Untrusted X509Certificate", + message: "SHA1: ${Format.uint8listToString(cert.sha1)}", + leftButton: SecondaryButton( + label: "Cancel", + onPressed: () { + Navigator.of(context).pop(false); + }, + ), + rightButton: PrimaryButton( + label: "Trust", + onPressed: () { + Navigator.of(context).pop(true); + }, + ), + ); + }, + ); + + return result ?? false; +} diff --git a/lib/widgets/node_card.dart b/lib/widgets/node_card.dart index bf9d2746e..1da7e9012 100644 --- a/lib/widgets/node_card.dart +++ b/lib/widgets/node_card.dart @@ -110,7 +110,29 @@ class _NodeCardState extends ConsumerState { String uriString = "${uri.scheme}://${uri.host}:${node.port}$path"; - testPassed = await testMoneroNodeConnection(Uri.parse(uriString)); + final response = await testMoneroNodeConnection( + Uri.parse(uriString), + false, + ); + + if (response.cert != null) { + if (mounted) { + final shouldAllowBadCert = await showBadX509CertificateDialog( + response.cert!, + response.url!, + response.port!, + context, + ); + + if (shouldAllowBadCert) { + final response = await testMoneroNodeConnection( + Uri.parse(uriString), true); + testPassed = response.success; + } + } + } else { + testPassed = response.success; + } } } catch (e, s) { Logging.instance.log("$e\n$s", level: LogLevel.Warning); diff --git a/lib/widgets/node_options_sheet.dart b/lib/widgets/node_options_sheet.dart index a5345161c..7ffd290f3 100644 --- a/lib/widgets/node_options_sheet.dart +++ b/lib/widgets/node_options_sheet.dart @@ -93,7 +93,29 @@ class NodeOptionsSheet extends ConsumerWidget { String uriString = "${uri.scheme}://${uri.host}:${node.port}$path"; - testPassed = await testMoneroNodeConnection(Uri.parse(uriString)); + final response = await testMoneroNodeConnection( + Uri.parse(uriString), + false, + ); + + if (response.cert != null) { + // if (mounted) { + final shouldAllowBadCert = await showBadX509CertificateDialog( + response.cert!, + response.url!, + response.port!, + context, + ); + + if (shouldAllowBadCert) { + final response = + await testMoneroNodeConnection(Uri.parse(uriString), true); + testPassed = response.success; + } + // } + } else { + testPassed = response.success; + } } } catch (e, s) { Logging.instance.log("$e\n$s", level: LogLevel.Warning); From a8c3d5f1042b6b70888acca8db0020928faa4684 Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 8 Nov 2022 11:41:12 -0600 Subject: [PATCH 39/76] format sha1 string --- lib/utilities/test_monero_node_connection.dart | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/utilities/test_monero_node_connection.dart b/lib/utilities/test_monero_node_connection.dart index 92b645141..5e35f9a03 100644 --- a/lib/utilities/test_monero_node_connection.dart +++ b/lib/utilities/test_monero_node_connection.dart @@ -84,13 +84,23 @@ Future showBadX509CertificateDialog( int port, BuildContext context, ) async { + final chars = Format.uint8listToString(cert.sha1) + .toUpperCase() + .characters + .toList(growable: false); + + String sha1 = chars.sublist(0, 2).join(); + for (int i = 2; i < chars.length; i += 2) { + sha1 += ":${chars.sublist(i, i + 2).join()}"; + } + final result = await showDialog( context: context, barrierDismissible: false, builder: (context) { return StackDialog( title: "Untrusted X509Certificate", - message: "SHA1: ${Format.uint8listToString(cert.sha1)}", + message: "SHA1:\n$sha1", leftButton: SecondaryButton( label: "Cancel", onPressed: () { From e41f8088b02c8aeaac8caaebac27dcbe7cc1d893 Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 8 Nov 2022 12:00:10 -0600 Subject: [PATCH 40/76] WIP: wownero 25 word seed option ui --- .../restore_options_view.dart | 36 +++++++++++++++---- .../restore_wallet_view.dart | 5 +++ .../coins/wownero/wownero_wallet.dart | 10 +++++- lib/utilities/constants.dart | 6 +--- 4 files changed, 45 insertions(+), 12 deletions(-) diff --git a/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart b/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart index 76e74fa14..1ce5d713a 100644 --- a/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart +++ b/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart @@ -252,7 +252,11 @@ class _RestoreOptionsViewState extends ConsumerState { SizedBox( height: isDesktop ? 40 : 24, ), - if (coin == Coin.monero || coin == Coin.epicCash) + if (coin == Coin.monero || + coin == Coin.epicCash || + (coin == Coin.wownero && + ref.watch(mnemonicWordCountStateProvider.state).state == + 25)) Text( "Choose start date", style: isDesktop @@ -264,11 +268,19 @@ class _RestoreOptionsViewState extends ConsumerState { : STextStyles.smallMed12(context), textAlign: TextAlign.left, ), - if (coin == Coin.monero || coin == Coin.epicCash) + if (coin == Coin.monero || + coin == Coin.epicCash || + (coin == Coin.wownero && + ref.watch(mnemonicWordCountStateProvider.state).state == + 25)) SizedBox( height: isDesktop ? 16 : 8, ), - if (coin == Coin.monero || coin == Coin.epicCash) + if (coin == Coin.monero || + coin == Coin.epicCash || + (coin == Coin.wownero && + ref.watch(mnemonicWordCountStateProvider.state).state == + 25)) // if (!isDesktop) RestoreFromDatePicker( @@ -278,11 +290,19 @@ class _RestoreOptionsViewState extends ConsumerState { // if (isDesktop) // // TODO desktop date picker - if (coin == Coin.monero || coin == Coin.epicCash) + if (coin == Coin.monero || + coin == Coin.epicCash || + (coin == Coin.wownero && + ref.watch(mnemonicWordCountStateProvider.state).state == + 25)) const SizedBox( height: 8, ), - if (coin == Coin.monero || coin == Coin.epicCash) + if (coin == Coin.monero || + coin == Coin.epicCash || + (coin == Coin.wownero && + ref.watch(mnemonicWordCountStateProvider.state).state == + 25)) RoundedWhiteContainer( child: Center( child: Text( @@ -299,7 +319,11 @@ class _RestoreOptionsViewState extends ConsumerState { ), ), ), - if (coin == Coin.monero || coin == Coin.epicCash) + if (coin == Coin.monero || + coin == Coin.epicCash || + (coin == Coin.wownero && + ref.watch(mnemonicWordCountStateProvider.state).state == + 25)) SizedBox( height: isDesktop ? 24 : 16, ), diff --git a/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart b/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart index def0724b5..a6b7e7e77 100644 --- a/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart +++ b/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart @@ -149,6 +149,7 @@ class _RestoreWalletViewState extends ConsumerState { super.dispose(); } + // TODO: check for wownero wordlist? bool _isValidMnemonicWord(String word) { // TODO: get the actual language if (widget.coin == Coin.monero) { @@ -181,6 +182,10 @@ class _RestoreWalletViewState extends ConsumerState { if (widget.coin == Coin.monero) { height = monero.getHeigthByDate(date: widget.restoreFromDate); } + // todo: wait until this implemented + // else if (widget.coin == Coin.wownero) { + // height = wownero.getHeightByDate(date: widget.restoreFromDate); + // } // TODO: make more robust estimate of date maybe using https://explorer.epic.tech/api-index if (widget.coin == Coin.epicCash) { diff --git a/lib/services/coins/wownero/wownero_wallet.dart b/lib/services/coins/wownero/wownero_wallet.dart index 72f43eac8..788f2f9d8 100644 --- a/lib/services/coins/wownero/wownero_wallet.dart +++ b/lib/services/coins/wownero/wownero_wallet.dart @@ -942,6 +942,11 @@ class WowneroWallet extends CoinServiceAPI { required int maxNumberOfIndexesToCheck, required int height, }) async { + final int seedLength = mnemonic.trim().split(" ").length; + if (!(seedLength == 14 || seedLength == 25)) { + throw Exception("Invalid wownero mnemonic length found: $seedLength"); + } + await _prefs.init(); longMutex = true; final start = DateTime.now(); @@ -969,7 +974,10 @@ class WowneroWallet extends CoinServiceAPI { await _secureStore.write( key: '${_walletId}_mnemonic', value: mnemonic.trim()); - height = getSeedHeightSync(mnemonic.trim()); + // extract seed height from 14 word seed + if (seedLength == 14) { + height = getSeedHeightSync(mnemonic.trim()); + } await DB.instance .put(boxName: walletId, key: "restoreHeight", value: height); diff --git a/lib/utilities/constants.dart b/lib/utilities/constants.dart index 4fb3fb54b..e27fbaa3d 100644 --- a/lib/utilities/constants.dart +++ b/lib/utilities/constants.dart @@ -35,10 +35,6 @@ abstract class Constants { static const int pinLength = 4; - // enable testnet - // TODO: currently unused - static const bool allowTestnets = true; - // Enable Logger.print statements static const bool disableLogger = false; @@ -66,7 +62,7 @@ abstract class Constants { values.addAll([25]); break; case Coin.wownero: - values.addAll([14]); + values.addAll([14, 25]); break; } return values; From 43deb9f81fa99d65318a116c1c835a0dc9f96f9e Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Mon, 7 Nov 2022 16:17:54 -0700 Subject: [PATCH 41/76] desktop about ui fix --- .../desktop_about_view.dart | 933 ++++++++++-------- 1 file changed, 497 insertions(+), 436 deletions(-) diff --git a/lib/pages_desktop_specific/home/support_and_about_view/desktop_about_view.dart b/lib/pages_desktop_specific/home/support_and_about_view/desktop_about_view.dart index 86fcf78e0..18988cb68 100644 --- a/lib/pages_desktop_specific/home/support_and_about_view/desktop_about_view.dart +++ b/lib/pages_desktop_specific/home/support_and_about_view/desktop_about_view.dart @@ -140,262 +140,123 @@ class DesktopAboutView extends ConsumerWidget { children: [ Padding( padding: const EdgeInsets.fromLTRB(24, 10, 24, 35), - child: RoundedWhiteContainer( - width: 929, - height: 411, - child: Padding( - padding: const EdgeInsets.only(left: 10, top: 10), - child: Column( - // mainAxisAlignment: MainAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - "Stack Wallet", - style: STextStyles.desktopH3(context), - textAlign: TextAlign.start, - ), - ], - ), - const SizedBox(height: 16), - Row( - children: [ - RichText( - textAlign: TextAlign.start, - text: TextSpan( - style: STextStyles.label(context), + child: Row( + children: [ + Expanded( + child: RoundedWhiteContainer( + width: 929, + height: 411, + child: Padding( + padding: const EdgeInsets.only(left: 10, top: 10), + child: Column( + // mainAxisAlignment: MainAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, children: [ - TextSpan( - text: - "By using Stack Wallet, you agree to the ", - style: STextStyles.desktopTextExtraExtraSmall( - context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark3), - ), - TextSpan( - text: "Terms of service", - style: STextStyles.richLink(context) - .copyWith(fontSize: 14), - recognizer: TapGestureRecognizer() - ..onTap = () { - launchUrl( - Uri.parse( - "https://stackwallet.com/terms-of-service.html"), - mode: LaunchMode.externalApplication, - ); - }, - ), - TextSpan( - text: " and ", - style: STextStyles.desktopTextExtraExtraSmall( - context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark3), - ), - TextSpan( - text: "Privacy policy", - style: STextStyles.richLink(context) - .copyWith(fontSize: 14), - recognizer: TapGestureRecognizer() - ..onTap = () { - launchUrl( - Uri.parse( - "https://stackwallet.com/privacy-policy.html"), - mode: LaunchMode.externalApplication, - ); - }, + Text( + "Stack Wallet", + style: STextStyles.desktopH3(context), + textAlign: TextAlign.start, ), ], ), - ), - ], - ), - const SizedBox(height: 32), - Padding( - padding: const EdgeInsets.only(right: 10, bottom: 10), - child: Column( - children: [ - FutureBuilder( - future: PackageInfo.fromPlatform(), - builder: - (context, AsyncSnapshot snapshot) { - String version = ""; - String signature = ""; - String build = ""; + const SizedBox(height: 16), + Row( + children: [ + RichText( + textAlign: TextAlign.start, + text: TextSpan( + style: STextStyles.label(context), + children: [ + TextSpan( + text: + "By using Stack Wallet, you agree to the ", + style: STextStyles + .desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark3), + ), + TextSpan( + text: "Terms of service", + style: STextStyles.richLink(context) + .copyWith(fontSize: 14), + recognizer: TapGestureRecognizer() + ..onTap = () { + launchUrl( + Uri.parse( + "https://stackwallet.com/terms-of-service.html"), + mode: + LaunchMode.externalApplication, + ); + }, + ), + TextSpan( + text: " and ", + style: STextStyles + .desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark3), + ), + TextSpan( + text: "Privacy policy", + style: STextStyles.richLink(context) + .copyWith(fontSize: 14), + recognizer: TapGestureRecognizer() + ..onTap = () { + launchUrl( + Uri.parse( + "https://stackwallet.com/privacy-policy.html"), + mode: + LaunchMode.externalApplication, + ); + }, + ), + ], + ), + ), + ], + ), + const SizedBox(height: 32), + Padding( + padding: + const EdgeInsets.only(right: 10, bottom: 10), + child: Column( + children: [ + FutureBuilder( + future: PackageInfo.fromPlatform(), + builder: (context, + AsyncSnapshot snapshot) { + String version = ""; + String signature = ""; + String build = ""; - if (snapshot.connectionState == - ConnectionState.done && - snapshot.hasData) { - version = snapshot.data!.version; - build = snapshot.data!.buildNumber; - signature = snapshot.data!.buildSignature; - } + if (snapshot.connectionState == + ConnectionState.done && + snapshot.hasData) { + version = snapshot.data!.version; + build = snapshot.data!.buildNumber; + signature = snapshot.data!.buildSignature; + } - return Column( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - "Version", - style: STextStyles - .desktopTextExtraExtraSmall( - context) - .copyWith( - color: Theme.of(context) - .extension< - StackColors>()! - .textDark), - ), - const SizedBox( - height: 2, - ), - SelectableText( - version, - style: STextStyles.itemSubtitle( - context), - ), - ], - ), - const SizedBox( - width: 400, - ), - Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - "Build number", - style: STextStyles - .desktopTextExtraExtraSmall( - context) - .copyWith( - color: Theme.of(context) - .extension< - StackColors>()! - .textDark), - ), - const SizedBox( - height: 2, - ), - SelectableText( - build, - style: STextStyles.itemSubtitle( - context), - ), - ], - ), - ], - ), - const SizedBox(height: 32), - Row( - children: [ - Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - "Build signature", - style: STextStyles - .desktopTextExtraExtraSmall( - context) - .copyWith( - color: Theme.of(context) - .extension< - StackColors>()! - .textDark), - ), - const SizedBox( - height: 2, - ), - SelectableText( - signature, - style: STextStyles.itemSubtitle( - context), - ), - ], - ), - const SizedBox( - width: 350, - ), - FutureBuilder( - future: commitFiroFuture, - builder: (context, - AsyncSnapshot snapshot) { - bool commitExists = false; - bool isHead = false; - CommitStatus stateOfCommit = - CommitStatus.notLoaded; - - if (snapshot.connectionState == - ConnectionState.done && - snapshot.hasData) { - commitExists = - snapshot.data![0] as bool; - isHead = - snapshot.data![1] as bool; - if (commitExists && isHead) { - stateOfCommit = - CommitStatus.isHead; - } else if (commitExists) { - stateOfCommit = - CommitStatus.isOldCommit; - } else { - stateOfCommit = - CommitStatus.notACommit; - } - } - TextStyle indicationStyle = - STextStyles.itemSubtitle( - context); - switch (stateOfCommit) { - case CommitStatus.isHead: - indicationStyle = STextStyles - .itemSubtitle(context) - .copyWith( - color: Theme.of(context) - .extension< - StackColors>()! - .accentColorGreen); - break; - case CommitStatus.isOldCommit: - indicationStyle = STextStyles - .itemSubtitle(context) - .copyWith( - color: Theme.of(context) - .extension< - StackColors>()! - .accentColorYellow); - break; - case CommitStatus.notACommit: - indicationStyle = STextStyles - .itemSubtitle(context) - .copyWith( - color: Theme.of(context) - .extension< - StackColors>()! - .accentColorRed); - break; - default: - break; - } - return Column( + return Column( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Firo Build Commit", + "Version", style: STextStyles .desktopTextExtraExtraSmall( context) @@ -410,84 +271,22 @@ class DesktopAboutView extends ConsumerWidget { height: 2, ), SelectableText( - firoCommit, - style: indicationStyle, + version, + style: + STextStyles.itemSubtitle( + context), ), ], - ); - }), - ], - ), - const SizedBox(height: 35), - Row( - children: [ - FutureBuilder( - future: commitEpicFuture, - builder: (context, - AsyncSnapshot snapshot) { - bool commitExists = false; - bool isHead = false; - CommitStatus stateOfCommit = - CommitStatus.notLoaded; - - if (snapshot.connectionState == - ConnectionState.done && - snapshot.hasData) { - commitExists = - snapshot.data![0] as bool; - isHead = - snapshot.data![1] as bool; - if (commitExists && isHead) { - stateOfCommit = - CommitStatus.isHead; - } else if (commitExists) { - stateOfCommit = - CommitStatus.isOldCommit; - } else { - stateOfCommit = - CommitStatus.notACommit; - } - } - TextStyle indicationStyle = - STextStyles.itemSubtitle( - context); - switch (stateOfCommit) { - case CommitStatus.isHead: - indicationStyle = STextStyles - .itemSubtitle(context) - .copyWith( - color: Theme.of(context) - .extension< - StackColors>()! - .accentColorGreen); - break; - case CommitStatus.isOldCommit: - indicationStyle = STextStyles - .itemSubtitle(context) - .copyWith( - color: Theme.of(context) - .extension< - StackColors>()! - .accentColorYellow); - break; - case CommitStatus.notACommit: - indicationStyle = STextStyles - .itemSubtitle(context) - .copyWith( - color: Theme.of(context) - .extension< - StackColors>()! - .accentColorRed); - break; - default: - break; - } - return Column( + ), + const SizedBox( + width: 400, + ), + Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Epic Cash Build Commit", + "Build number", style: STextStyles .desktopTextExtraExtraSmall( context) @@ -502,82 +301,24 @@ class DesktopAboutView extends ConsumerWidget { height: 2, ), SelectableText( - epicCashCommit, - style: indicationStyle, + build, + style: + STextStyles.itemSubtitle( + context), ), ], - ); - }), - const SizedBox( - width: 105, - ), - FutureBuilder( - future: commitMoneroFuture, - builder: (context, - AsyncSnapshot snapshot) { - bool commitExists = false; - bool isHead = false; - CommitStatus stateOfCommit = - CommitStatus.notLoaded; - - if (snapshot.connectionState == - ConnectionState.done && - snapshot.hasData) { - commitExists = - snapshot.data![0] as bool; - isHead = - snapshot.data![1] as bool; - if (commitExists && isHead) { - stateOfCommit = - CommitStatus.isHead; - } else if (commitExists) { - stateOfCommit = - CommitStatus.isOldCommit; - } else { - stateOfCommit = - CommitStatus.notACommit; - } - } - TextStyle indicationStyle = - STextStyles.itemSubtitle( - context); - switch (stateOfCommit) { - case CommitStatus.isHead: - indicationStyle = STextStyles - .itemSubtitle(context) - .copyWith( - color: Theme.of(context) - .extension< - StackColors>()! - .accentColorGreen); - break; - case CommitStatus.isOldCommit: - indicationStyle = STextStyles - .itemSubtitle(context) - .copyWith( - color: Theme.of(context) - .extension< - StackColors>()! - .accentColorYellow); - break; - case CommitStatus.notACommit: - indicationStyle = STextStyles - .itemSubtitle(context) - .copyWith( - color: Theme.of(context) - .extension< - StackColors>()! - .accentColorRed); - break; - default: - break; - } - return Column( + ), + ], + ), + const SizedBox(height: 32), + Row( + children: [ + Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Monero Build Commit", + "Build signature", style: STextStyles .desktopTextExtraExtraSmall( context) @@ -592,60 +333,380 @@ class DesktopAboutView extends ConsumerWidget { height: 2, ), SelectableText( - moneroCommit, - style: indicationStyle, + signature, + style: + STextStyles.itemSubtitle( + context), ), ], - ); - }), - ], - ), - const SizedBox(height: 35), - Row( - children: [ - Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - "Website", - style: STextStyles - .desktopTextExtraExtraSmall( - context) - .copyWith( - color: Theme.of(context) - .extension< - StackColors>()! - .textDark), - ), - const SizedBox( - height: 2, - ), - BlueTextButton( - text: "https://stackwallet.com", - onTap: () { - launchUrl( - Uri.parse( - "https://stackwallet.com"), - mode: LaunchMode - .externalApplication, - ); - }, - ), - ], - ) - ], - ) - ], - ); - }, + ), + const SizedBox( + width: 350, + ), + FutureBuilder( + future: commitFiroFuture, + builder: (context, + AsyncSnapshot + snapshot) { + bool commitExists = false; + bool isHead = false; + CommitStatus stateOfCommit = + CommitStatus.notLoaded; + + if (snapshot.connectionState == + ConnectionState + .done && + snapshot.hasData) { + commitExists = snapshot + .data![0] as bool; + isHead = snapshot.data![1] + as bool; + if (commitExists && + isHead) { + stateOfCommit = + CommitStatus.isHead; + } else if (commitExists) { + stateOfCommit = + CommitStatus + .isOldCommit; + } else { + stateOfCommit = + CommitStatus + .notACommit; + } + } + TextStyle indicationStyle = + STextStyles.itemSubtitle( + context); + switch (stateOfCommit) { + case CommitStatus.isHead: + indicationStyle = STextStyles + .itemSubtitle( + context) + .copyWith( + color: Theme.of( + context) + .extension< + StackColors>()! + .accentColorGreen); + break; + case CommitStatus + .isOldCommit: + indicationStyle = STextStyles + .itemSubtitle( + context) + .copyWith( + color: Theme.of( + context) + .extension< + StackColors>()! + .accentColorYellow); + break; + case CommitStatus + .notACommit: + indicationStyle = STextStyles + .itemSubtitle( + context) + .copyWith( + color: Theme.of( + context) + .extension< + StackColors>()! + .accentColorRed); + break; + default: + break; + } + return Column( + crossAxisAlignment: + CrossAxisAlignment + .start, + children: [ + Text( + "Firo Build Commit", + style: STextStyles + .desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of( + context) + .extension< + StackColors>()! + .textDark), + ), + const SizedBox( + height: 2, + ), + SelectableText( + firoCommit, + style: indicationStyle, + ), + ], + ); + }), + ], + ), + const SizedBox(height: 35), + Row( + children: [ + FutureBuilder( + future: commitEpicFuture, + builder: (context, + AsyncSnapshot + snapshot) { + bool commitExists = false; + bool isHead = false; + CommitStatus stateOfCommit = + CommitStatus.notLoaded; + + if (snapshot.connectionState == + ConnectionState + .done && + snapshot.hasData) { + commitExists = snapshot + .data![0] as bool; + isHead = snapshot.data![1] + as bool; + if (commitExists && + isHead) { + stateOfCommit = + CommitStatus.isHead; + } else if (commitExists) { + stateOfCommit = + CommitStatus + .isOldCommit; + } else { + stateOfCommit = + CommitStatus + .notACommit; + } + } + TextStyle indicationStyle = + STextStyles.itemSubtitle( + context); + switch (stateOfCommit) { + case CommitStatus.isHead: + indicationStyle = STextStyles + .itemSubtitle( + context) + .copyWith( + color: Theme.of( + context) + .extension< + StackColors>()! + .accentColorGreen); + break; + case CommitStatus + .isOldCommit: + indicationStyle = STextStyles + .itemSubtitle( + context) + .copyWith( + color: Theme.of( + context) + .extension< + StackColors>()! + .accentColorYellow); + break; + case CommitStatus + .notACommit: + indicationStyle = STextStyles + .itemSubtitle( + context) + .copyWith( + color: Theme.of( + context) + .extension< + StackColors>()! + .accentColorRed); + break; + default: + break; + } + return Column( + crossAxisAlignment: + CrossAxisAlignment + .start, + children: [ + Text( + "Epic Cash Build Commit", + style: STextStyles + .desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of( + context) + .extension< + StackColors>()! + .textDark), + ), + const SizedBox( + height: 2, + ), + SelectableText( + epicCashCommit, + style: indicationStyle, + ), + ], + ); + }), + const SizedBox( + width: 105, + ), + FutureBuilder( + future: commitMoneroFuture, + builder: (context, + AsyncSnapshot + snapshot) { + bool commitExists = false; + bool isHead = false; + CommitStatus stateOfCommit = + CommitStatus.notLoaded; + + if (snapshot.connectionState == + ConnectionState + .done && + snapshot.hasData) { + commitExists = snapshot + .data![0] as bool; + isHead = snapshot.data![1] + as bool; + if (commitExists && + isHead) { + stateOfCommit = + CommitStatus.isHead; + } else if (commitExists) { + stateOfCommit = + CommitStatus + .isOldCommit; + } else { + stateOfCommit = + CommitStatus + .notACommit; + } + } + TextStyle indicationStyle = + STextStyles.itemSubtitle( + context); + switch (stateOfCommit) { + case CommitStatus.isHead: + indicationStyle = STextStyles + .itemSubtitle( + context) + .copyWith( + color: Theme.of( + context) + .extension< + StackColors>()! + .accentColorGreen); + break; + case CommitStatus + .isOldCommit: + indicationStyle = STextStyles + .itemSubtitle( + context) + .copyWith( + color: Theme.of( + context) + .extension< + StackColors>()! + .accentColorYellow); + break; + case CommitStatus + .notACommit: + indicationStyle = STextStyles + .itemSubtitle( + context) + .copyWith( + color: Theme.of( + context) + .extension< + StackColors>()! + .accentColorRed); + break; + default: + break; + } + return Column( + crossAxisAlignment: + CrossAxisAlignment + .start, + children: [ + Text( + "Monero Build Commit", + style: STextStyles + .desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of( + context) + .extension< + StackColors>()! + .textDark), + ), + const SizedBox( + height: 2, + ), + SelectableText( + moneroCommit, + style: indicationStyle, + ), + ], + ); + }), + ], + ), + const SizedBox(height: 35), + Row( + children: [ + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + "Website", + style: STextStyles + .desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of( + context) + .extension< + StackColors>()! + .textDark), + ), + const SizedBox( + height: 2, + ), + BlueTextButton( + text: + "https://stackwallet.com", + onTap: () { + launchUrl( + Uri.parse( + "https://stackwallet.com"), + mode: LaunchMode + .externalApplication, + ); + }, + ), + ], + ) + ], + ) + ], + ); + }, + ) + ], + ), ) ], ), - ) - ], + ), + ), ), - ), + ], ), ), ], From 543f9631d845838c1cab2e39fc38e9f077e7d4a9 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Tue, 8 Nov 2022 09:34:47 -0700 Subject: [PATCH 42/76] changed desktop textbox fontsize --- .../stack_backup_views/create_backup_view.dart | 4 ++++ .../stack_backup_views/restore_from_file_view.dart | 2 ++ .../backup_and_restore/backup_and_restore_settings.dart | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart index 30fcb7962..48f1a1b7f 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart @@ -272,6 +272,8 @@ class _RestoreFromFileViewState extends State { passwordFocusNode, context, ).copyWith( + labelStyle: + isDesktop ? STextStyles.fieldLabel(context) : null, suffixIcon: UnconstrainedBox( child: Row( children: [ @@ -403,6 +405,8 @@ class _RestoreFromFileViewState extends State { passwordRepeatFocusNode, context, ).copyWith( + labelStyle: + isDesktop ? STextStyles.fieldLabel(context) : null, suffixIcon: UnconstrainedBox( child: Row( children: [ diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart index 0c101d0b3..c73d596f0 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart @@ -245,6 +245,8 @@ class _RestoreFromFileViewState extends ConsumerState { passwordFocusNode, context, ).copyWith( + labelStyle: + isDesktop ? STextStyles.fieldLabel(context) : null, suffixIcon: UnconstrainedBox( child: Row( children: [ diff --git a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart index 49debf22f..8928a268d 100644 --- a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart @@ -269,7 +269,7 @@ class _BackupRestoreSettings extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( - padding: EdgeInsets.all( + padding: const EdgeInsets.all( 10, ), child: restoreBackup From eea5225ba5c05a4c0cae4607cb8522b85a1cdc56 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Tue, 8 Nov 2022 10:07:38 -0700 Subject: [PATCH 43/76] button correction for desktop manual backup --- .../create_backup_view.dart | 356 ++++++++++++------ 1 file changed, 239 insertions(+), 117 deletions(-) diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart index 48f1a1b7f..51a1d7218 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart @@ -158,24 +158,24 @@ class _RestoreFromFileViewState extends State { ), ), child, - const SizedBox(height: 20), - Row( - children: [ - PrimaryButton( - desktopMed: true, - width: 200, - label: "Create backup", - onPressed: () {}, - ), - const SizedBox(width: 16), - SecondaryButton( - desktopMed: true, - width: 200, - label: "Cancel", - onPressed: () {}, - ), - ], - ), + // const SizedBox(height: 20), + // Row( + // children: [ + // PrimaryButton( + // desktopMed: true, + // width: 200, + // label: "Create backup", + // onPressed: () {}, + // ), + // const SizedBox(width: 16), + // SecondaryButton( + // desktopMed: true, + // width: 200, + // label: "Cancel", + // onPressed: () {}, + // ), + // ], + // ), ], ); }, @@ -447,112 +447,234 @@ class _RestoreFromFileViewState extends State { height: 16, ), if (!isDesktop) const Spacer(), - TextButton( - style: shouldEnableCreate - ? Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context) - : Theme.of(context) - .extension()! - .getPrimaryDisabledButtonColor(context), - onPressed: !shouldEnableCreate - ? null - : () async { - final String pathToSave = fileLocationController.text; - final String passphrase = passwordController.text; - final String repeatPassphrase = - passwordRepeatController.text; + !isDesktop + ? TextButton( + style: shouldEnableCreate + ? Theme.of(context) + .extension()! + .getPrimaryEnabledButtonColor(context) + : Theme.of(context) + .extension()! + .getPrimaryDisabledButtonColor(context), + onPressed: !shouldEnableCreate + ? null + : () async { + final String pathToSave = + fileLocationController.text; + final String passphrase = passwordController.text; + final String repeatPassphrase = + passwordRepeatController.text; - if (pathToSave.isEmpty) { - unawaited(showFloatingFlushBar( - type: FlushBarType.warning, - message: "Directory not chosen", - context: context, - )); - return; - } - if (!(await Directory(pathToSave).exists())) { - unawaited(showFloatingFlushBar( - type: FlushBarType.warning, - message: "Directory does not exist", - context: context, - )); - return; - } - if (passphrase.isEmpty) { - unawaited(showFloatingFlushBar( - type: FlushBarType.warning, - message: "A passphrase is required", - context: context, - )); - return; - } - if (passphrase != repeatPassphrase) { - unawaited(showFloatingFlushBar( - type: FlushBarType.warning, - message: "Passphrase does not match", - context: context, - )); - return; - } + if (pathToSave.isEmpty) { + unawaited(showFloatingFlushBar( + type: FlushBarType.warning, + message: "Directory not chosen", + context: context, + )); + return; + } + if (!(await Directory(pathToSave).exists())) { + unawaited(showFloatingFlushBar( + type: FlushBarType.warning, + message: "Directory does not exist", + context: context, + )); + return; + } + if (passphrase.isEmpty) { + unawaited(showFloatingFlushBar( + type: FlushBarType.warning, + message: "A passphrase is required", + context: context, + )); + return; + } + if (passphrase != repeatPassphrase) { + unawaited(showFloatingFlushBar( + type: FlushBarType.warning, + message: "Passphrase does not match", + context: context, + )); + return; + } - unawaited(showDialog( - context: context, - barrierDismissible: false, - builder: (_) => const StackDialog( - title: "Encrypting backup", - message: "This shouldn't take long", - ), - )); - // make sure the dialog is able to be displayed for at least 1 second - await Future.delayed(const Duration(seconds: 1)); + unawaited(showDialog( + context: context, + barrierDismissible: false, + builder: (_) => const StackDialog( + title: "Encrypting backup", + message: "This shouldn't take long", + ), + )); + // make sure the dialog is able to be displayed for at least 1 second + await Future.delayed( + const Duration(seconds: 1)); - final DateTime now = DateTime.now(); - final String fileToSave = - "$pathToSave/stackbackup_${now.year}_${now.month}_${now.day}_${now.hour}_${now.minute}_${now.second}.swb"; + final DateTime now = DateTime.now(); + final String fileToSave = + "$pathToSave/stackbackup_${now.year}_${now.month}_${now.day}_${now.hour}_${now.minute}_${now.second}.swb"; - final backup = await SWB.createStackWalletJSON(); + final backup = await SWB.createStackWalletJSON(); - bool result = await SWB.encryptStackWalletWithPassphrase( - fileToSave, - passphrase, - jsonEncode(backup), - ); + bool result = + await SWB.encryptStackWalletWithPassphrase( + fileToSave, + passphrase, + jsonEncode(backup), + ); - if (mounted) { - // pop encryption progress dialog - Navigator.of(context).pop(); + if (mounted) { + // pop encryption progress dialog + Navigator.of(context).pop(); - if (result) { - await showDialog( - context: context, - barrierDismissible: false, - builder: (_) => Platform.isAndroid - ? StackOkDialog( - title: "Backup saved to:", - message: fileToSave, - ) - : const StackOkDialog( - title: "Backup creation succeeded"), - ); - passwordController.text = ""; - passwordRepeatController.text = ""; - setState(() {}); - } else { - await showDialog( - context: context, - barrierDismissible: false, - builder: (_) => const StackOkDialog( - title: "Backup creation failed"), - ); - } - } - }, - child: Text( - "Create backup", - style: STextStyles.button(context), - ), - ), + if (result) { + await showDialog( + context: context, + barrierDismissible: false, + builder: (_) => Platform.isAndroid + ? StackOkDialog( + title: "Backup saved to:", + message: fileToSave, + ) + : const StackOkDialog( + title: "Backup creation succeeded"), + ); + passwordController.text = ""; + passwordRepeatController.text = ""; + setState(() {}); + } else { + await showDialog( + context: context, + barrierDismissible: false, + builder: (_) => const StackOkDialog( + title: "Backup creation failed"), + ); + } + } + }, + child: Text( + "Create backup", + style: STextStyles.button(context), + ), + ) + : Row( + children: [ + PrimaryButton( + width: 183, + desktopMed: true, + label: "Create backup", + enabled: shouldEnableCreate, + onPressed: !shouldEnableCreate + ? null + : () async { + final String pathToSave = + fileLocationController.text; + final String passphrase = + passwordController.text; + final String repeatPassphrase = + passwordRepeatController.text; + + if (pathToSave.isEmpty) { + unawaited(showFloatingFlushBar( + type: FlushBarType.warning, + message: "Directory not chosen", + context: context, + )); + return; + } + if (!(await Directory(pathToSave).exists())) { + unawaited(showFloatingFlushBar( + type: FlushBarType.warning, + message: "Directory does not exist", + context: context, + )); + return; + } + if (passphrase.isEmpty) { + unawaited(showFloatingFlushBar( + type: FlushBarType.warning, + message: "A passphrase is required", + context: context, + )); + return; + } + if (passphrase != repeatPassphrase) { + unawaited(showFloatingFlushBar( + type: FlushBarType.warning, + message: "Passphrase does not match", + context: context, + )); + return; + } + + unawaited(showDialog( + context: context, + barrierDismissible: false, + builder: (_) => const StackDialog( + title: "Encrypting backup", + message: "This shouldn't take long", + ), + )); + // make sure the dialog is able to be displayed for at least 1 second + await Future.delayed( + const Duration(seconds: 1)); + + final DateTime now = DateTime.now(); + final String fileToSave = + "$pathToSave/stackbackup_${now.year}_${now.month}_${now.day}_${now.hour}_${now.minute}_${now.second}.swb"; + + final backup = + await SWB.createStackWalletJSON(); + + bool result = + await SWB.encryptStackWalletWithPassphrase( + fileToSave, + passphrase, + jsonEncode(backup), + ); + + if (mounted) { + // pop encryption progress dialog + Navigator.of(context).pop(); + + if (result) { + await showDialog( + context: context, + barrierDismissible: false, + builder: (_) => Platform.isAndroid + ? StackOkDialog( + title: "Backup saved to:", + message: fileToSave, + ) + : const StackOkDialog( + title: + "Backup creation succeeded"), + ); + passwordController.text = ""; + passwordRepeatController.text = ""; + setState(() {}); + } else { + await showDialog( + context: context, + barrierDismissible: false, + builder: (_) => const StackOkDialog( + title: "Backup creation failed"), + ); + } + } + }, + ), + const SizedBox( + width: 16, + ), + SecondaryButton( + width: 183, + desktopMed: true, + label: "Cancel", + onPressed: () {}, + ), + ], + ), ], ), ), From 8af1350b95acc35ffb0e83e3ea67a32453b53a2f Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Tue, 8 Nov 2022 10:50:12 -0700 Subject: [PATCH 44/76] button correction for desktop restore backup and other ui fixes --- .../create_backup_view.dart | 37 +- .../restore_from_file_view.dart | 354 ++++++++++++------ 2 files changed, 251 insertions(+), 140 deletions(-) diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart index 51a1d7218..eacdda66a 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart @@ -147,7 +147,7 @@ class _RestoreFromFileViewState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( - padding: const EdgeInsets.all(10.0), + padding: const EdgeInsets.only(bottom: 10), child: Text( "Choose file location", style: STextStyles.desktopTextExtraExtraSmall(context) @@ -158,24 +158,6 @@ class _RestoreFromFileViewState extends State { ), ), child, - // const SizedBox(height: 20), - // Row( - // children: [ - // PrimaryButton( - // desktopMed: true, - // width: 200, - // label: "Create backup", - // onPressed: () {}, - // ), - // const SizedBox(width: 16), - // SecondaryButton( - // desktopMed: true, - // width: 200, - // label: "Cancel", - // onPressed: () {}, - // ), - // ], - // ), ], ); }, @@ -252,8 +234,21 @@ class _RestoreFromFileViewState extends State { ); }), if (!Platform.isAndroid) - const SizedBox( - height: 8, + SizedBox( + height: !isDesktop ? 8 : 24, + ), + if (isDesktop) + Padding( + padding: const EdgeInsets.only(bottom: 10.0), + child: Text( + "Create a passphrase", + style: STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark3), + textAlign: TextAlign.left, + ), ), ClipRRect( borderRadius: BorderRadius.circular( diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart index c73d596f0..f7a9883de 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart @@ -131,7 +131,7 @@ class _RestoreFromFileViewState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( - padding: const EdgeInsets.all(10.0), + padding: const EdgeInsets.only(bottom: 10.0), child: Text( "Choose file location", style: STextStyles.desktopTextExtraExtraSmall(context) @@ -143,26 +143,6 @@ class _RestoreFromFileViewState extends ConsumerState { ), ), child, - const SizedBox(height: 20), - Row( - children: [ - PrimaryButton( - desktopMed: true, - width: 200, - label: "Restore", - onPressed: () { - restoreBackupPopup(context); - }, - ), - const SizedBox(width: 16), - SecondaryButton( - desktopMed: true, - width: 200, - label: "Cancel", - onPressed: () {}, - ), - ], - ), ], ); }, @@ -225,9 +205,22 @@ class _RestoreFromFileViewState extends ConsumerState { ), onChanged: (newValue) {}, ), - const SizedBox( - height: 8, + SizedBox( + height: !isDesktop ? 8 : 24, ), + if (isDesktop) + Padding( + padding: const EdgeInsets.only(bottom: 10.0), + child: Text( + "Enter passphrase", + style: STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark3), + textAlign: TextAlign.left, + ), + ), ClipRRect( borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, @@ -288,113 +281,236 @@ class _RestoreFromFileViewState extends ConsumerState { height: 16, ), if (!isDesktop) const Spacer(), - TextButton( - style: passwordController.text.isEmpty || - fileLocationController.text.isEmpty - ? Theme.of(context) - .extension()! - .getPrimaryDisabledButtonColor(context) - : Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context), - onPressed: passwordController.text.isEmpty || - fileLocationController.text.isEmpty - ? null - : () async { - final String fileToRestore = - fileLocationController.text; - final String passphrase = passwordController.text; + !isDesktop + ? TextButton( + style: passwordController.text.isEmpty || + fileLocationController.text.isEmpty + ? Theme.of(context) + .extension()! + .getPrimaryDisabledButtonColor(context) + : Theme.of(context) + .extension()! + .getPrimaryEnabledButtonColor(context), + onPressed: passwordController.text.isEmpty || + fileLocationController.text.isEmpty + ? null + : () async { + final String fileToRestore = + fileLocationController.text; + final String passphrase = passwordController.text; - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed( - const Duration(milliseconds: 75)); - } + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration(milliseconds: 75)); + } - if (!(await File(fileToRestore).exists())) { - showFloatingFlushBar( - type: FlushBarType.warning, - message: "Backup file does not exist", - context: context, - ); - return; - } + if (!(await File(fileToRestore).exists())) { + await showFloatingFlushBar( + type: FlushBarType.warning, + message: "Backup file does not exist", + context: context, + ); + return; + } - bool shouldPop = false; - showDialog( - barrierDismissible: false, - context: context, - builder: (_) => WillPopScope( - onWillPop: () async { - return shouldPop; - }, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Material( - color: Colors.transparent, - child: Center( - child: Text( - "Decrypting Stack backup file", - style: STextStyles.pageTitleH2(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textWhite, + bool shouldPop = false; + await showDialog( + barrierDismissible: false, + context: context, + builder: (_) => WillPopScope( + onWillPop: () async { + return shouldPop; + }, + child: Column( + crossAxisAlignment: + CrossAxisAlignment.stretch, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Material( + color: Colors.transparent, + child: Center( + child: Text( + "Decrypting Stack backup file", + style: + STextStyles.pageTitleH2(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textWhite, + ), + ), + ), ), + const SizedBox( + height: 64, + ), + const Center( + child: LoadingIndicator( + width: 100, + ), + ), + ], + ), + ), + ); + + final String? jsonString = await compute( + SWB.decryptStackWalletWithPassphrase, + Tuple2(fileToRestore, passphrase), + debugLabel: "stack wallet decryption compute", + ); + + if (mounted) { + // pop LoadingIndicator + shouldPop = true; + Navigator.of(context).pop(); + + passwordController.text = ""; + + if (jsonString == null) { + await showFloatingFlushBar( + type: FlushBarType.warning, + message: "Failed to decrypt backup file", + context: context, + ); + return; + } + + await Navigator.of(context).push( + RouteGenerator.getRoute( + builder: (_) => StackRestoreProgressView( + jsonString: jsonString, ), ), - ), - const SizedBox( - height: 64, - ), - const Center( - child: LoadingIndicator( - width: 100, - ), - ), - ], - ), - ), - ); + ); + } + }, + child: Text( + "Restore", + style: STextStyles.button(context), + ), + ) + : Row( + children: [ + PrimaryButton( + width: 183, + desktopMed: true, + label: "Restore", + enabled: !(passwordController.text.isEmpty || + fileLocationController.text.isEmpty), + onPressed: passwordController.text.isEmpty || + fileLocationController.text.isEmpty + ? null + : () async { + final String fileToRestore = + fileLocationController.text; + final String passphrase = + passwordController.text; - final String? jsonString = await compute( - SWB.decryptStackWalletWithPassphrase, - Tuple2(fileToRestore, passphrase), - debugLabel: "stack wallet decryption compute", - ); + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration(milliseconds: 75)); + } - if (mounted) { - // pop LoadingIndicator - shouldPop = true; - Navigator.of(context).pop(); + if (!(await File(fileToRestore).exists())) { + await showFloatingFlushBar( + type: FlushBarType.warning, + message: "Backup file does not exist", + context: context, + ); + return; + } - passwordController.text = ""; + bool shouldPop = false; + await showDialog( + barrierDismissible: false, + context: context, + builder: (_) => WillPopScope( + onWillPop: () async { + return shouldPop; + }, + child: Column( + crossAxisAlignment: + CrossAxisAlignment.stretch, + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Material( + color: Colors.transparent, + child: Center( + child: Text( + "Decrypting Stack backup file", + style: STextStyles.pageTitleH2( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textWhite, + ), + ), + ), + ), + const SizedBox( + height: 64, + ), + const Center( + child: LoadingIndicator( + width: 100, + ), + ), + ], + ), + ), + ); - if (jsonString == null) { - showFloatingFlushBar( - type: FlushBarType.warning, - message: "Failed to decrypt backup file", - context: context, - ); - return; - } + final String? jsonString = await compute( + SWB.decryptStackWalletWithPassphrase, + Tuple2(fileToRestore, passphrase), + debugLabel: + "stack wallet decryption compute", + ); - Navigator.of(context).push( - RouteGenerator.getRoute( - builder: (_) => StackRestoreProgressView( - jsonString: jsonString, - ), - ), - ); - } - }, - child: Text( - "Restore", - style: STextStyles.button(context), - ), - ), + if (mounted) { + // pop LoadingIndicator + shouldPop = true; + Navigator.of(context).pop(); + + passwordController.text = ""; + + if (jsonString == null) { + await showFloatingFlushBar( + type: FlushBarType.warning, + message: + "Failed to decrypt backup file", + context: context, + ); + return; + } + + await Navigator.of(context).push( + RouteGenerator.getRoute( + builder: (_) => + StackRestoreProgressView( + jsonString: jsonString, + ), + ), + ); + } + }, + ), + const SizedBox( + width: 16, + ), + SecondaryButton( + width: 183, + desktopMed: true, + label: "Cancel", + onPressed: () {}, + ), + ], + ), ], ), )); From 95716bd0f6be7f5b7c6dcbb08b03efa6a5dfb545 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Tue, 8 Nov 2022 12:15:31 -0700 Subject: [PATCH 45/76] added textfield functionality to desktop create auto backup --- .../create_auto_backup.dart | 489 +++++++++++------- 1 file changed, 306 insertions(+), 183 deletions(-) diff --git a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart index f3e502bcb..57a8d7a64 100644 --- a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart +++ b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart @@ -1,14 +1,23 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/stack_file_system.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/enums/log_level_enum.dart'; +import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/progress_bar.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; +import 'package:zxcvbn/zxcvbn.dart'; class CreateAutoBackup extends StatefulWidget { const CreateAutoBackup({Key? key}) : super(key: key); @@ -22,13 +31,24 @@ class _CreateAutoBackup extends State { late final TextEditingController passphraseController; late final TextEditingController passphraseRepeatController; - late final FocusNode chooseFileLocation; + late final StackFileSystem stackFileSystem; late final FocusNode passphraseFocusNode; late final FocusNode passphraseRepeatFocusNode; + final zxcvbn = Zxcvbn(); bool shouldShowPasswordHint = true; bool hidePassword = true; + String passwordFeedback = + "Add another word or two. Uncommon words are better. Use a few words, avoid common phrases. No need for symbols, digits, or uppercase letters."; + double passwordStrength = 0.0; + + bool get shouldEnableCreate { + return fileLocationController.text.isNotEmpty && + passphraseController.text.isNotEmpty && + passphraseRepeatController.text.isNotEmpty; + } + bool get fieldsMatch => passphraseController.text == passphraseRepeatController.text; @@ -42,14 +62,26 @@ class _CreateAutoBackup extends State { @override void initState() { + stackFileSystem = StackFileSystem(); + fileLocationController = TextEditingController(); passphraseController = TextEditingController(); passphraseRepeatController = TextEditingController(); - chooseFileLocation = FocusNode(); passphraseFocusNode = FocusNode(); passphraseRepeatFocusNode = FocusNode(); + if (Platform.isAndroid) { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { + final dir = await stackFileSystem.prepareStorage(); + if (mounted) { + setState(() { + fileLocationController.text = dir.path; + }); + } + }); + } + super.initState(); } @@ -59,7 +91,6 @@ class _CreateAutoBackup extends State { passphraseController.dispose(); passphraseRepeatController.dispose(); - chooseFileLocation.dispose(); passphraseFocusNode.dispose(); passphraseRepeatFocusNode.dispose(); @@ -71,9 +102,9 @@ class _CreateAutoBackup extends State { debugPrint("BUILD: $runtimeType "); String? selectedItem = "Every 10 minutes"; - + final isDesktop = Util.isDesktop; return DesktopDialog( - maxHeight: 650, + maxHeight: 680, maxWidth: 600, child: Column( children: [ @@ -127,198 +158,289 @@ class _CreateAutoBackup extends State { height: 10, ), Padding( - padding: const EdgeInsets.only( - left: 32, - right: 32, - ), - child: ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - key: const Key("backupChooseFileLocation"), - focusNode: chooseFileLocation, - controller: fileLocationController, - style: STextStyles.desktopTextMedium(context).copyWith( - height: 2, - ), - textAlign: TextAlign.left, - enableSuggestions: false, - autocorrect: false, - decoration: standardInputDecoration( - "Save to...", - chooseFileLocation, - context, - ).copyWith( - labelStyle: - STextStyles.desktopTextExtraExtraSmall(context).copyWith( - color: - Theme.of(context).extension()!.textDark3, - ), - suffixIcon: Container( - decoration: BoxDecoration( + padding: const EdgeInsets.symmetric(horizontal: 32), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (!Platform.isAndroid) + Consumer(builder: (context, ref, __) { + return Container( color: Colors.transparent, - borderRadius: BorderRadius.circular(1000), - ), - height: 32, - width: 32, - child: Center( - child: SvgPicture.asset( - Assets.svg.folder, - color: Theme.of(context) - .extension()! - .textDark3, - width: 20, - height: 17.5, - ), - ), - ), - ), - ), - ), - ), - const SizedBox( - height: 24, - ), - Container( - alignment: Alignment.centerLeft, - padding: const EdgeInsets.only(left: 32), - child: Text( - "Create a passphrase", - style: STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of(context).extension()!.textDark3, - ), - textAlign: TextAlign.left, - ), - ), - const SizedBox( - height: 10, - ), - Padding( - padding: const EdgeInsets.only( - left: 32, - right: 32, - ), - child: ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - key: const Key("createBackupPassphrase"), - focusNode: passphraseFocusNode, - controller: passphraseController, - style: STextStyles.desktopTextMedium(context).copyWith( - height: 2, - ), - obscureText: hidePassword, - enableSuggestions: false, - autocorrect: false, - decoration: standardInputDecoration( - "Create passphrase", - passphraseFocusNode, - context, - ).copyWith( - labelStyle: - STextStyles.desktopTextExtraExtraSmall(context).copyWith( - color: - Theme.of(context).extension()!.textDark3, - ), - suffixIcon: UnconstrainedBox( - child: GestureDetector( - key: const Key( - "createDesktopAutoBackupShowPassphraseButton1"), - onTap: () async { - setState(() { - hidePassword = !hidePassword; - }); - }, - child: Container( - decoration: BoxDecoration( - color: Colors.transparent, - borderRadius: BorderRadius.circular(1000), - ), - height: 32, - width: 32, - child: Center( - child: SvgPicture.asset( - hidePassword ? Assets.svg.eye : Assets.svg.eyeSlash, - color: Theme.of(context) - .extension()! - .textDark3, - width: 20, - height: 17.5, + child: TextField( + autocorrect: false, + enableSuggestions: false, + onTap: Platform.isAndroid + ? null + : () async { + try { + await stackFileSystem.prepareStorage(); + + if (mounted) { + await stackFileSystem.pickDir(context); + } + + if (mounted) { + setState(() { + fileLocationController.text = + stackFileSystem.dirPath ?? ""; + }); + } + } catch (e, s) { + Logging.instance + .log("$e\n$s", level: LogLevel.Error); + } + }, + controller: fileLocationController, + style: STextStyles.field(context), + decoration: InputDecoration( + hintText: "Save to...", + hintStyle: STextStyles.fieldLabel(context), + suffixIcon: UnconstrainedBox( + child: Row( + children: [ + const SizedBox( + width: 16, + ), + SvgPicture.asset( + Assets.svg.folder, + color: Theme.of(context) + .extension()! + .textDark3, + width: 16, + height: 16, + ), + const SizedBox( + width: 12, + ), + ], + ), ), ), + key: const Key( + "createBackupSaveToFileLocationTextFieldKey"), + readOnly: true, + toolbarOptions: const ToolbarOptions( + copy: true, + cut: false, + paste: false, + selectAll: false, + ), + onChanged: (newValue) { + // ref.read(addressEntryDataProvider(widget.id)).address = newValue; + }, ), + ); + }), + if (!Platform.isAndroid) + const SizedBox( + height: 24, + ), + if (isDesktop) + Padding( + padding: const EdgeInsets.only(bottom: 10.0), + child: Text( + "Create a passphrase", + style: STextStyles.desktopTextExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark3), + textAlign: TextAlign.left, ), ), - ), - ), - ), - ), - const SizedBox( - height: 16, - ), - Padding( - padding: const EdgeInsets.only( - left: 32, - right: 32, - ), - child: ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - key: const Key("createBackupPassphrase"), - focusNode: passphraseRepeatFocusNode, - controller: passphraseRepeatController, - style: STextStyles.desktopTextMedium(context).copyWith( - height: 2, - ), - obscureText: hidePassword, - enableSuggestions: false, - autocorrect: false, - decoration: standardInputDecoration( - "Confirm passphrase", - passphraseRepeatFocusNode, - context, - ).copyWith( - labelStyle: - STextStyles.desktopTextExtraExtraSmall(context).copyWith( - color: - Theme.of(context).extension()!.textDark3, + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, ), - suffixIcon: UnconstrainedBox( - child: GestureDetector( - key: const Key( - "createDesktopAutoBackupShowPassphraseButton2"), - onTap: () async { + child: TextField( + key: const Key("createBackupPasswordFieldKey1"), + focusNode: passphraseFocusNode, + controller: passphraseController, + style: STextStyles.field(context), + obscureText: hidePassword, + enableSuggestions: false, + autocorrect: false, + decoration: standardInputDecoration( + "Create passphrase", + passphraseFocusNode, + context, + ).copyWith( + labelStyle: + isDesktop ? STextStyles.fieldLabel(context) : null, + suffixIcon: UnconstrainedBox( + child: Row( + children: [ + const SizedBox( + width: 16, + ), + GestureDetector( + key: const Key( + "createBackupPasswordFieldShowPasswordButtonKey"), + onTap: () async { + setState(() { + hidePassword = !hidePassword; + }); + }, + child: SvgPicture.asset( + hidePassword + ? Assets.svg.eye + : Assets.svg.eyeSlash, + color: Theme.of(context) + .extension()! + .textDark3, + width: 16, + height: 16, + ), + ), + const SizedBox( + width: 12, + ), + ], + ), + ), + ), + onChanged: (newValue) { + if (newValue.isEmpty) { setState(() { - hidePassword = !hidePassword; + passwordFeedback = ""; }); - }, - child: Container( - decoration: BoxDecoration( - color: Colors.transparent, - borderRadius: BorderRadius.circular(1000), - ), - height: 32, - width: 32, - child: Center( - child: SvgPicture.asset( - hidePassword ? Assets.svg.eye : Assets.svg.eyeSlash, - color: Theme.of(context) - .extension()! - .textDark3, - width: 20, - height: 17.5, - ), + return; + } + final result = zxcvbn.evaluate(newValue); + String suggestionsAndTips = ""; + for (var sug in result.feedback.suggestions!.toSet()) { + suggestionsAndTips += "$sug\n"; + } + suggestionsAndTips += result.feedback.warning!; + String feedback = + // "Password Strength: ${((result.score! / 4.0) * 100).toInt()}%\n" + suggestionsAndTips; + + passwordStrength = result.score! / 4; + + // hack fix to format back string returned from zxcvbn + if (feedback.contains("phrasesNo need")) { + feedback = feedback.replaceFirst( + "phrasesNo need", "phrases\nNo need"); + } + + if (feedback.endsWith("\n")) { + feedback = feedback.substring(0, feedback.length - 2); + } + + setState(() { + passwordFeedback = feedback; + }); + }, + ), + ), + if (passphraseFocusNode.hasFocus || + passphraseRepeatFocusNode.hasFocus || + passphraseController.text.isNotEmpty) + Padding( + padding: EdgeInsets.only( + left: 12, + right: 12, + top: passwordFeedback.isNotEmpty ? 4 : 0, + ), + child: passwordFeedback.isNotEmpty + ? Text( + passwordFeedback, + style: STextStyles.infoSmall(context), + ) + : null, + ), + if (passphraseFocusNode.hasFocus || + passphraseRepeatFocusNode.hasFocus || + passphraseController.text.isNotEmpty) + Padding( + padding: const EdgeInsets.only( + left: 12, + right: 12, + top: 10, + ), + child: ProgressBar( + key: const Key("createStackBackUpProgressBar"), + width: 510, + height: 5, + fillColor: passwordStrength < 0.51 + ? Theme.of(context) + .extension()! + .accentColorRed + : passwordStrength < 1 + ? Theme.of(context) + .extension()! + .accentColorYellow + : Theme.of(context) + .extension()! + .accentColorGreen, + backgroundColor: Theme.of(context) + .extension()! + .buttonBackSecondary, + percent: + passwordStrength < 0.25 ? 0.03 : passwordStrength, + ), + ), + const SizedBox( + height: 10, + ), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + key: const Key("createBackupPasswordFieldKey2"), + focusNode: passphraseRepeatFocusNode, + controller: passphraseRepeatController, + style: STextStyles.field(context), + obscureText: hidePassword, + enableSuggestions: false, + autocorrect: false, + decoration: standardInputDecoration( + "Confirm passphrase", + passphraseRepeatFocusNode, + context, + ).copyWith( + labelStyle: STextStyles.fieldLabel(context), + suffixIcon: UnconstrainedBox( + child: Row( + children: [ + const SizedBox( + width: 16, + ), + GestureDetector( + key: const Key( + "createBackupPasswordFieldShowPasswordButtonKey"), + onTap: () async { + setState(() { + hidePassword = !hidePassword; + }); + }, + child: SvgPicture.asset( + hidePassword + ? Assets.svg.eye + : Assets.svg.eyeSlash, + color: Theme.of(context) + .extension()! + .textDark3, + width: 16, + height: 16, + ), + ), + const SizedBox( + width: 12, + ), + ], ), ), ), + onChanged: (newValue) { + setState(() {}); + // TODO: ? check if passwords match? + }, ), ), - ), + ], ), ), const SizedBox( @@ -376,6 +498,7 @@ class _CreateAutoBackup extends State { }, ), ), + const Spacer(), Padding( padding: const EdgeInsets.all(32), child: Row( From 48e8501e27c8f2e8494c2746710c18be276dbce2 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Tue, 8 Nov 2022 13:35:27 -0600 Subject: [PATCH 46/76] cherrypick e41f8088b02c8aeaac8caaebac27dcbe7cc1d893 --- .../restore_options_view.dart | 36 +++++++++++++++---- .../restore_wallet_view.dart | 5 +++ .../coins/wownero/wownero_wallet.dart | 11 ++++-- lib/utilities/constants.dart | 6 +--- 4 files changed, 45 insertions(+), 13 deletions(-) diff --git a/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart b/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart index 76e74fa14..1ce5d713a 100644 --- a/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart +++ b/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart @@ -252,7 +252,11 @@ class _RestoreOptionsViewState extends ConsumerState { SizedBox( height: isDesktop ? 40 : 24, ), - if (coin == Coin.monero || coin == Coin.epicCash) + if (coin == Coin.monero || + coin == Coin.epicCash || + (coin == Coin.wownero && + ref.watch(mnemonicWordCountStateProvider.state).state == + 25)) Text( "Choose start date", style: isDesktop @@ -264,11 +268,19 @@ class _RestoreOptionsViewState extends ConsumerState { : STextStyles.smallMed12(context), textAlign: TextAlign.left, ), - if (coin == Coin.monero || coin == Coin.epicCash) + if (coin == Coin.monero || + coin == Coin.epicCash || + (coin == Coin.wownero && + ref.watch(mnemonicWordCountStateProvider.state).state == + 25)) SizedBox( height: isDesktop ? 16 : 8, ), - if (coin == Coin.monero || coin == Coin.epicCash) + if (coin == Coin.monero || + coin == Coin.epicCash || + (coin == Coin.wownero && + ref.watch(mnemonicWordCountStateProvider.state).state == + 25)) // if (!isDesktop) RestoreFromDatePicker( @@ -278,11 +290,19 @@ class _RestoreOptionsViewState extends ConsumerState { // if (isDesktop) // // TODO desktop date picker - if (coin == Coin.monero || coin == Coin.epicCash) + if (coin == Coin.monero || + coin == Coin.epicCash || + (coin == Coin.wownero && + ref.watch(mnemonicWordCountStateProvider.state).state == + 25)) const SizedBox( height: 8, ), - if (coin == Coin.monero || coin == Coin.epicCash) + if (coin == Coin.monero || + coin == Coin.epicCash || + (coin == Coin.wownero && + ref.watch(mnemonicWordCountStateProvider.state).state == + 25)) RoundedWhiteContainer( child: Center( child: Text( @@ -299,7 +319,11 @@ class _RestoreOptionsViewState extends ConsumerState { ), ), ), - if (coin == Coin.monero || coin == Coin.epicCash) + if (coin == Coin.monero || + coin == Coin.epicCash || + (coin == Coin.wownero && + ref.watch(mnemonicWordCountStateProvider.state).state == + 25)) SizedBox( height: isDesktop ? 24 : 16, ), diff --git a/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart b/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart index def0724b5..a6b7e7e77 100644 --- a/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart +++ b/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart @@ -149,6 +149,7 @@ class _RestoreWalletViewState extends ConsumerState { super.dispose(); } + // TODO: check for wownero wordlist? bool _isValidMnemonicWord(String word) { // TODO: get the actual language if (widget.coin == Coin.monero) { @@ -181,6 +182,10 @@ class _RestoreWalletViewState extends ConsumerState { if (widget.coin == Coin.monero) { height = monero.getHeigthByDate(date: widget.restoreFromDate); } + // todo: wait until this implemented + // else if (widget.coin == Coin.wownero) { + // height = wownero.getHeightByDate(date: widget.restoreFromDate); + // } // TODO: make more robust estimate of date maybe using https://explorer.epic.tech/api-index if (widget.coin == Coin.epicCash) { diff --git a/lib/services/coins/wownero/wownero_wallet.dart b/lib/services/coins/wownero/wownero_wallet.dart index a0dc7bfe0..5c419afd1 100644 --- a/lib/services/coins/wownero/wownero_wallet.dart +++ b/lib/services/coins/wownero/wownero_wallet.dart @@ -948,6 +948,11 @@ class WowneroWallet extends CoinServiceAPI { required int maxNumberOfIndexesToCheck, required int height, }) async { + final int seedLength = mnemonic.trim().split(" ").length; + if (!(seedLength == 14 || seedLength == 25)) { + throw Exception("Invalid wownero mnemonic length found: $seedLength"); + } + await _prefs.init(); longMutex = true; final start = DateTime.now(); @@ -975,8 +980,10 @@ class WowneroWallet extends CoinServiceAPI { await _secureStore.write( key: '${_walletId}_mnemonic', value: mnemonic.trim()); - height = 0; - //height = getSeedHeightSync(mnemonic.trim()); // TODO use an alternative to wow_seed's get_seed_height + // extract seed height from 14 word seed + if (seedLength == 14) { + height = getSeedHeightSync(mnemonic.trim()); + } await DB.instance .put(boxName: walletId, key: "restoreHeight", value: height); diff --git a/lib/utilities/constants.dart b/lib/utilities/constants.dart index 4fb3fb54b..e27fbaa3d 100644 --- a/lib/utilities/constants.dart +++ b/lib/utilities/constants.dart @@ -35,10 +35,6 @@ abstract class Constants { static const int pinLength = 4; - // enable testnet - // TODO: currently unused - static const bool allowTestnets = true; - // Enable Logger.print statements static const bool disableLogger = false; @@ -66,7 +62,7 @@ abstract class Constants { values.addAll([25]); break; case Coin.wownero: - values.addAll([14]); + values.addAll([14, 25]); break; } return values; From d23f6f2823a2d53b578f930c11fb4b41e0b25a27 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Tue, 8 Nov 2022 13:48:29 -0600 Subject: [PATCH 47/76] return to use of final for bufferedCreateHeight using inline if and use wowlet's height estimation function for 14 word seeds --- crypto_plugins/flutter_libmonero | 2 +- lib/services/coins/wownero/wownero_wallet.dart | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/crypto_plugins/flutter_libmonero b/crypto_plugins/flutter_libmonero index afdee4b88..2d5f5e563 160000 --- a/crypto_plugins/flutter_libmonero +++ b/crypto_plugins/flutter_libmonero @@ -1 +1 @@ -Subproject commit afdee4b880202f39a2375afc320f0642e98a1827 +Subproject commit 2d5f5e5636bdc4b211b2236492268167b5b969d0 diff --git a/lib/services/coins/wownero/wownero_wallet.dart b/lib/services/coins/wownero/wownero_wallet.dart index 5c419afd1..55f43cdd1 100644 --- a/lib/services/coins/wownero/wownero_wallet.dart +++ b/lib/services/coins/wownero/wownero_wallet.dart @@ -714,12 +714,8 @@ class WowneroWallet extends CoinServiceAPI { final wallet = await _walletCreationService?.create(credentials); // subtract a couple days to ensure we have a buffer for SWB - if (seedWordsLength == 14) { - final bufferedCreateHeight = getSeedHeightSync(wallet?.seed.trim() as String); - } else { - final bufferedCreateHeight = 0; - // TODO use an alternative to wow_seed's get_seed_height - } + final bufferedCreateHeight = (seedWordsLength == 14) ? getSeedHeightSync(wallet?.seed.trim() as String) : 0; + // TODO use an alternative to wow_seed's get_seed_height instead of 0 above await DB.instance.put( boxName: walletId, key: "restoreHeight", value: bufferedCreateHeight); From 015f33326948aff663299206ed47ba3157c31a42 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Tue, 8 Nov 2022 14:33:16 -0600 Subject: [PATCH 48/76] do not rely upon nullable variable --- crypto_plugins/flutter_libmonero | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto_plugins/flutter_libmonero b/crypto_plugins/flutter_libmonero index 2d5f5e563..0b355aee5 160000 --- a/crypto_plugins/flutter_libmonero +++ b/crypto_plugins/flutter_libmonero @@ -1 +1 @@ -Subproject commit 2d5f5e5636bdc4b211b2236492268167b5b969d0 +Subproject commit 0b355aee55608f497ca54aba151d0b3e9e2c4579 From cede571350db905f4f70ba532f758c8869a9e15c Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 8 Nov 2022 13:50:53 -0600 Subject: [PATCH 49/76] desktop login/password screen --- .../desktop_login_view.dart | 166 ++++++++++++++++-- lib/utilities/text_styles.dart | 19 ++ .../custom_buttons/blue_text_button.dart | 11 +- 3 files changed, 179 insertions(+), 17 deletions(-) diff --git a/lib/pages_desktop_specific/desktop_login_view.dart b/lib/pages_desktop_specific/desktop_login_view.dart index c986bffde..1c70a5d98 100644 --- a/lib/pages_desktop_specific/desktop_login_view.dart +++ b/lib/pages_desktop_specific/desktop_login_view.dart @@ -1,7 +1,14 @@ import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/pages_desktop_specific/home/desktop_home_view.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/stack_text_field.dart'; class DesktopLoginView extends StatefulWidget { const DesktopLoginView({ @@ -18,28 +25,155 @@ class DesktopLoginView extends StatefulWidget { } class _DesktopLoginViewState extends State { + late final TextEditingController passwordController; + + late final FocusNode passwordFocusNode; + + bool hidePassword = true; + bool _continueEnabled = false; + + @override + void initState() { + passwordController = TextEditingController(); + passwordFocusNode = FocusNode(); + + super.initState(); + } + + @override + void dispose() { + passwordController.dispose(); + passwordFocusNode.dispose(); + + super.dispose(); + } + @override Widget build(BuildContext context) { - return Material( - child: Column( + return DesktopScaffold( + body: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - Text( - "Login", - style: STextStyles.desktopH3(context), - ), - PrimaryButton( - label: "Login", - onPressed: () { - // todo auth + SizedBox( + width: 480, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SvgPicture.asset( + Assets.svg.stackIcon(context), + width: 100, + ), + const SizedBox( + height: 42, + ), + Text( + "Stack Wallet", + style: STextStyles.desktopH1(context), + ), + const SizedBox( + height: 24, + ), + SizedBox( + width: 350, + child: Text( + "Open source multicoin wallet for everyone", + textAlign: TextAlign.center, + style: STextStyles.desktopSubtitleH1(context), + ), + ), + const SizedBox( + height: 24, + ), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + key: const Key("desktopLoginPasswordFieldKey"), + focusNode: passwordFocusNode, + controller: passwordController, + style: STextStyles.desktopTextMedium(context).copyWith( + height: 2, + ), + obscureText: hidePassword, + enableSuggestions: false, + autocorrect: false, + decoration: standardInputDecoration( + "Enter password", + passwordFocusNode, + context, + ).copyWith( + suffixIcon: UnconstrainedBox( + child: SizedBox( + height: 70, + child: Row( + children: [ + const SizedBox( + width: 24, + ), + GestureDetector( + key: const Key( + "restoreFromFilePasswordFieldShowPasswordButtonKey"), + onTap: () async { + setState(() { + hidePassword = !hidePassword; + }); + }, + child: SvgPicture.asset( + hidePassword + ? Assets.svg.eye + : Assets.svg.eyeSlash, + color: Theme.of(context) + .extension()! + .textDark3, + width: 24, + height: 24, + ), + ), + const SizedBox( + width: 12, + ), + ], + ), + ), + ), + ), + onChanged: (newValue) { + setState(() { + _continueEnabled = passwordController.text.isNotEmpty; + }); + }, + ), + ), + const SizedBox( + height: 24, + ), + PrimaryButton( + label: "Continue", + enabled: _continueEnabled, + onPressed: () { + // todo auth - Navigator.of(context).pushNamedAndRemoveUntil( - DesktopHomeView.routeName, - (route) => false, - ); - }, - ) + Navigator.of(context).pushNamedAndRemoveUntil( + DesktopHomeView.routeName, + (route) => false, + ); + }, + ), + const SizedBox( + height: 60, + ), + BlueTextButton( + text: "Forgot password?", + textSize: 20, + onTap: () { + // todo: new screen + }, + ), + ], + ), + ), ], ), ); diff --git a/lib/utilities/text_styles.dart b/lib/utilities/text_styles.dart index 299ba5bec..63aa19afb 100644 --- a/lib/utilities/text_styles.dart +++ b/lib/utilities/text_styles.dart @@ -508,6 +508,25 @@ class STextStyles { // Desktop + static TextStyle desktopH1(BuildContext context) { + switch (_theme(context).themeType) { + case ThemeType.light: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w600, + fontSize: 40, + height: 40 / 40, + ); + case ThemeType.dark: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w600, + fontSize: 40, + height: 40 / 40, + ); + } + } + static TextStyle desktopH2(BuildContext context) { switch (_theme(context).themeType) { case ThemeType.light: diff --git a/lib/widgets/custom_buttons/blue_text_button.dart b/lib/widgets/custom_buttons/blue_text_button.dart index aa7f75b1f..a87d1e6b2 100644 --- a/lib/widgets/custom_buttons/blue_text_button.dart +++ b/lib/widgets/custom_buttons/blue_text_button.dart @@ -10,11 +10,13 @@ class BlueTextButton extends ConsumerStatefulWidget { required this.text, this.onTap, this.enabled = true, + this.textSize, }) : super(key: key); final String text; final VoidCallback? onTap; final bool enabled; + final double? textSize; @override ConsumerState createState() => _BlueTextButtonState(); @@ -67,7 +69,14 @@ class _BlueTextButtonState extends ConsumerState textAlign: TextAlign.center, text: TextSpan( text: widget.text, - style: STextStyles.link2(context).copyWith(color: color), + style: widget.textSize == null + ? STextStyles.link2(context).copyWith( + color: color, + ) + : STextStyles.link2(context).copyWith( + color: color, + fontSize: widget.textSize, + ), recognizer: widget.enabled ? (TapGestureRecognizer() ..onTap = () { From 97b4407957639d50a1cc9a52c710b6ef9a500baa Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 8 Nov 2022 14:04:00 -0600 Subject: [PATCH 50/76] desktop forgot password ui --- .../desktop_login_view.dart | 5 +- .../forgot_password_desktop_view.dart | 101 ++++++++++++++++++ lib/route_generator.dart | 7 ++ 3 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 lib/pages_desktop_specific/forgot_password_desktop_view.dart diff --git a/lib/pages_desktop_specific/desktop_login_view.dart b/lib/pages_desktop_specific/desktop_login_view.dart index 1c70a5d98..fe05d719f 100644 --- a/lib/pages_desktop_specific/desktop_login_view.dart +++ b/lib/pages_desktop_specific/desktop_login_view.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/pages_desktop_specific/forgot_password_desktop_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/desktop_home_view.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; @@ -168,7 +169,9 @@ class _DesktopLoginViewState extends State { text: "Forgot password?", textSize: 20, onTap: () { - // todo: new screen + Navigator.of(context).pushNamed( + ForgotPasswordDesktopView.routeName, + ); }, ), ], diff --git a/lib/pages_desktop_specific/forgot_password_desktop_view.dart b/lib/pages_desktop_specific/forgot_password_desktop_view.dart new file mode 100644 index 000000000..d501cbd38 --- /dev/null +++ b/lib/pages_desktop_specific/forgot_password_desktop_view.dart @@ -0,0 +1,101 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; +import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; + +class ForgotPasswordDesktopView extends StatefulWidget { + const ForgotPasswordDesktopView({ + Key? key, + }) : super(key: key); + + static const String routeName = "/forgotPasswordDesktop"; + + @override + State createState() => + _ForgotPasswordDesktopViewState(); +} + +class _ForgotPasswordDesktopViewState extends State { + @override + Widget build(BuildContext context) { + return DesktopScaffold( + appBar: DesktopAppBar( + leading: AppBarBackButton( + onPressed: () async { + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + isCompactHeight: false, + ), + body: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + width: 480, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SvgPicture.asset( + Assets.svg.stackIcon(context), + width: 100, + ), + const SizedBox( + height: 42, + ), + Text( + "Stack Wallet", + style: STextStyles.desktopH1(context), + ), + const SizedBox( + height: 24, + ), + SizedBox( + width: 400, + child: Text( + "Stack Wallet does not store your password. Create new wallet or use a Stack backup file to restore your wallet.", + textAlign: TextAlign.center, + style: STextStyles.desktopTextSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + ), + const SizedBox( + height: 48, + ), + PrimaryButton( + label: "Create new wallet", + onPressed: () { + // // todo delete everything and start fresh? + }, + ), + const SizedBox( + height: 24, + ), + SecondaryButton( + label: "Restore from backup", + onPressed: () { + // todo SWB restore + }, + ), + const SizedBox( + height: kDesktopAppBarHeight, + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/route_generator.dart b/lib/route_generator.dart index 40f11dc57..30963781b 100644 --- a/lib/route_generator.dart +++ b/lib/route_generator.dart @@ -85,6 +85,7 @@ import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_sear import 'package:stackwallet/pages/wallet_view/wallet_view.dart'; import 'package:stackwallet/pages/wallets_view/wallets_view.dart'; import 'package:stackwallet/pages_desktop_specific/create_password/create_password_view.dart'; +import 'package:stackwallet/pages_desktop_specific/forgot_password_desktop_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/desktop_home_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/desktop_settings_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/my_stack_view.dart'; @@ -998,6 +999,12 @@ class RouteGenerator { builder: (_) => const CreatePasswordView(), settings: RouteSettings(name: settings.name)); + case ForgotPasswordDesktopView.routeName: + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => const ForgotPasswordDesktopView(), + settings: RouteSettings(name: settings.name)); + case DesktopHomeView.routeName: return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, From c66e382fc380c45afb15690fbb69467ba1bde0d8 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Tue, 8 Nov 2022 17:30:09 -0600 Subject: [PATCH 51/76] get appropriate WowneroWordList based on seed length --- crypto_plugins/flutter_libmonero | 2 +- .../restore_wallet_view/restore_wallet_view.dart | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/crypto_plugins/flutter_libmonero b/crypto_plugins/flutter_libmonero index 0b355aee5..e440e9a3a 160000 --- a/crypto_plugins/flutter_libmonero +++ b/crypto_plugins/flutter_libmonero @@ -1 +1 @@ -Subproject commit 0b355aee55608f497ca54aba151d0b3e9e2c4579 +Subproject commit e440e9a3a125ee2030551ad7dea9114dd6a06aa0 diff --git a/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart b/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart index a6b7e7e77..7596d7ac8 100644 --- a/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart +++ b/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart @@ -8,6 +8,7 @@ import 'package:bip39/src/wordlists/english.dart' as bip39wordlist; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_libmonero/monero/monero.dart'; +import 'package:flutter_libmonero/wownero/wownero.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; @@ -149,13 +150,17 @@ class _RestoreWalletViewState extends ConsumerState { super.dispose(); } - // TODO: check for wownero wordlist? bool _isValidMnemonicWord(String word) { // TODO: get the actual language if (widget.coin == Coin.monero) { var moneroWordList = monero.getMoneroWordList("English"); return moneroWordList.contains(word); } + if (widget.coin == Coin.wownero) { + var wowneroWordList = wownero.getWowneroWordList("English", + seedWordsLength: widget.seedWordsLength); + return wowneroWordList.contains(word); + } return _wordListHashSet.contains(word); } From a94e66da9eec4ea809cc8b4a09e717c4fc852e62 Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 8 Nov 2022 19:07:18 -0600 Subject: [PATCH 52/76] temp disable wow 25 word option in ui --- lib/utilities/constants.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/utilities/constants.dart b/lib/utilities/constants.dart index e27fbaa3d..e170dad2a 100644 --- a/lib/utilities/constants.dart +++ b/lib/utilities/constants.dart @@ -62,7 +62,9 @@ abstract class Constants { values.addAll([25]); break; case Coin.wownero: - values.addAll([14, 25]); + values.addAll([14]); + // todo: uncomment when wownero 25 word seeds implemented + // values.addAll([14, 25]); break; } return values; From a54d9a561e50593b6fe13a204776d631f7545f77 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Tue, 8 Nov 2022 23:10:27 -0600 Subject: [PATCH 53/76] track changes in flutter_libmonero --- crypto_plugins/flutter_libmonero | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto_plugins/flutter_libmonero b/crypto_plugins/flutter_libmonero index e440e9a3a..9267fd0f0 160000 --- a/crypto_plugins/flutter_libmonero +++ b/crypto_plugins/flutter_libmonero @@ -1 +1 @@ -Subproject commit e440e9a3a125ee2030551ad7dea9114dd6a06aa0 +Subproject commit 9267fd0f0442a8d54b899473d77fc92ddc6d2391 From 357b93d6e897a2ece44eacdee8c32eac578177df Mon Sep 17 00:00:00 2001 From: sneurlax Date: Wed, 9 Nov 2022 00:16:21 -0600 Subject: [PATCH 54/76] use wownero.getHeightByDate and save bufferedHeight upon Monero wallet creation --- crypto_plugins/flutter_libmonero | 2 +- .../restore_wallet_view.dart | 6 ++--- lib/services/coins/monero/monero_wallet.dart | 2 +- .../coins/wownero/wownero_wallet.dart | 23 +++++++++++++------ 4 files changed, 20 insertions(+), 13 deletions(-) diff --git a/crypto_plugins/flutter_libmonero b/crypto_plugins/flutter_libmonero index 9267fd0f0..e705ba2d5 160000 --- a/crypto_plugins/flutter_libmonero +++ b/crypto_plugins/flutter_libmonero @@ -1 +1 @@ -Subproject commit 9267fd0f0442a8d54b899473d77fc92ddc6d2391 +Subproject commit e705ba2d5126685adae9367b62921b676d7126ed diff --git a/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart b/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart index 7596d7ac8..913302a98 100644 --- a/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart +++ b/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart @@ -186,11 +186,9 @@ class _RestoreWalletViewState extends ConsumerState { if (widget.coin == Coin.monero) { height = monero.getHeigthByDate(date: widget.restoreFromDate); + } else if (widget.coin == Coin.wownero) { + height = wownero.getHeightByDate(date: widget.restoreFromDate); } - // todo: wait until this implemented - // else if (widget.coin == Coin.wownero) { - // height = wownero.getHeightByDate(date: widget.restoreFromDate); - // } // TODO: make more robust estimate of date maybe using https://explorer.epic.tech/api-index if (widget.coin == Coin.epicCash) { diff --git a/lib/services/coins/monero/monero_wallet.dart b/lib/services/coins/monero/monero_wallet.dart index b0ebac4e6..8e7873014 100644 --- a/lib/services/coins/monero/monero_wallet.dart +++ b/lib/services/coins/monero/monero_wallet.dart @@ -699,7 +699,7 @@ class MoneroWallet extends CoinServiceAPI { name: name, type: WalletType.monero, isRecovery: false, - restoreHeight: credentials.height ?? 0, + restoreHeight: bufferedCreateHeight, date: DateTime.now(), path: path, dirPath: dirPath, diff --git a/lib/services/coins/wownero/wownero_wallet.dart b/lib/services/coins/wownero/wownero_wallet.dart index 55f43cdd1..0134cb1fe 100644 --- a/lib/services/coins/wownero/wownero_wallet.dart +++ b/lib/services/coins/wownero/wownero_wallet.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:io'; +import 'package:cw_core/get_height_by_date.dart'; import 'package:cw_core/monero_transaction_priority.dart'; import 'package:cw_core/node.dart'; import 'package:cw_core/pending_transaction.dart'; @@ -685,10 +686,7 @@ class WowneroWallet extends CoinServiceAPI { await pathForWalletDir(name: name, type: WalletType.wownero); final path = await pathForWallet(name: name, type: WalletType.wownero); credentials = wownero.createWowneroNewWalletCredentials( - name: name, - language: "English", - seedWordsLength: seedWordsLength - ); + name: name, language: "English", seedWordsLength: seedWordsLength); walletInfo = WalletInfo.external( id: WalletBase.idFor(name, WalletType.wownero), @@ -713,9 +711,12 @@ class WowneroWallet extends CoinServiceAPI { // To restore from a seed final wallet = await _walletCreationService?.create(credentials); - // subtract a couple days to ensure we have a buffer for SWB - final bufferedCreateHeight = (seedWordsLength == 14) ? getSeedHeightSync(wallet?.seed.trim() as String) : 0; - // TODO use an alternative to wow_seed's get_seed_height instead of 0 above + final bufferedCreateHeight = (seedWordsLength == 14) + ? getSeedHeightSync(wallet?.seed.trim() as String) + : wownero.getHeightByDate( + date: DateTime.now().subtract(const Duration( + days: + 2))); // subtract a couple days to ensure we have a buffer for SWB await DB.instance.put( boxName: walletId, key: "restoreHeight", value: bufferedCreateHeight); @@ -979,6 +980,14 @@ class WowneroWallet extends CoinServiceAPI { // extract seed height from 14 word seed if (seedLength == 14) { height = getSeedHeightSync(mnemonic.trim()); + } else { + // 25 word seed. TODO validate + if (height == 0) { + height = wownero.getHeightByDate( + date: DateTime.now().subtract(const Duration( + days: + 2))); // subtract a couple days to ensure we have a buffer for SWB\ + } } await DB.instance From 510233255f7a75b8f881754d5f60b2e0379272ff Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 9 Nov 2022 09:46:59 -0600 Subject: [PATCH 55/76] desktop swb restore fix --- .../restore_from_file_view.dart | 76 ++++++++++--------- 1 file changed, 42 insertions(+), 34 deletions(-) diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart index f7a9883de..014099abd 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:io'; import 'package:flutter/foundation.dart'; @@ -424,43 +425,47 @@ class _RestoreFromFileViewState extends ConsumerState { } bool shouldPop = false; - await showDialog( - barrierDismissible: false, - context: context, - builder: (_) => WillPopScope( - onWillPop: () async { - return shouldPop; - }, - child: Column( - crossAxisAlignment: - CrossAxisAlignment.stretch, - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - Material( - color: Colors.transparent, - child: Center( - child: Text( - "Decrypting Stack backup file", - style: STextStyles.pageTitleH2( - context) - .copyWith( - color: Theme.of(context) - .extension()! - .textWhite, + unawaited( + showDialog( + barrierDismissible: false, + context: context, + builder: (_) => WillPopScope( + onWillPop: () async { + return shouldPop; + }, + child: Column( + crossAxisAlignment: + CrossAxisAlignment.stretch, + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Material( + color: Colors.transparent, + child: Center( + child: Text( + "Decrypting Stack backup file", + style: + STextStyles.pageTitleH2( + context) + .copyWith( + color: Theme.of(context) + .extension< + StackColors>()! + .textWhite, + ), ), ), ), - ), - const SizedBox( - height: 64, - ), - const Center( - child: LoadingIndicator( - width: 100, + const SizedBox( + height: 64, ), - ), - ], + const Center( + child: LoadingIndicator( + width: 100, + ), + ), + ], + ), ), ), ); @@ -475,7 +480,10 @@ class _RestoreFromFileViewState extends ConsumerState { if (mounted) { // pop LoadingIndicator shouldPop = true; - Navigator.of(context).pop(); + Navigator.of( + context, + rootNavigator: true, + ).pop(); passwordController.text = ""; From 041e23a5a5a18682daf352bab79242fb654a6403 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Wed, 9 Nov 2022 08:57:41 -0700 Subject: [PATCH 56/76] resolved route_generator conflict --- .../desktop_address_book.dart | 139 ++++++++++++++++++ .../home/desktop_home_view.dart | 8 +- lib/route_generator.dart | 7 + 3 files changed, 152 insertions(+), 2 deletions(-) create mode 100644 lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart diff --git a/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart b/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart new file mode 100644 index 000000000..3622fcf1e --- /dev/null +++ b/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart @@ -0,0 +1,139 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; +import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; +import 'package:stackwallet/widgets/stack_text_field.dart'; +import 'package:stackwallet/widgets/textfield_icon_button.dart'; + +class DesktopAddressBook extends ConsumerStatefulWidget { + const DesktopAddressBook({Key? key}) : super(key: key); + + static const String routeName = "/desktopAddressBook"; + + @override + ConsumerState createState() => _DesktopAddressBook(); +} + +class _DesktopAddressBook extends ConsumerState { + late final TextEditingController _searchController; + + late final FocusNode _searchFocusNode; + + String filter = ""; + + @override + void initState() { + _searchController = TextEditingController(); + _searchFocusNode = FocusNode(); + + super.initState(); + } + + @override + void dispose() { + _searchController.dispose(); + _searchFocusNode.dispose(); + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + debugPrint("BUILD: $runtimeType"); + final hasWallets = ref.watch(walletsChangeNotifierProvider).hasWallets; + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + DesktopAppBar( + isCompactHeight: true, + leading: Row( + children: [ + const SizedBox( + width: 24, + ), + Text( + "Address Book", + style: STextStyles.desktopH3(context), + ) + ], + ), + ), + const SizedBox(height: 53), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Row( + children: [ + SizedBox( + height: 60, + width: 489, + child: ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + autocorrect: false, + enableSuggestions: false, + controller: _searchController, + focusNode: _searchFocusNode, + onChanged: (newString) { + setState(() => filter = newString); + }, + style: STextStyles.field(context), + decoration: standardInputDecoration( + "Search...", + _searchFocusNode, + context, + ).copyWith( + labelStyle: STextStyles.fieldLabel(context) + .copyWith(fontSize: 16), + prefixIcon: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 16, + ), + child: SvgPicture.asset( + Assets.svg.search, + width: 16, + height: 16, + ), + ), + suffixIcon: _searchController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + _searchController.text = ""; + filter = ""; + }); + }, + ), + ], + ), + ), + ) + : null, + ), + ), + ), + ), + ], + ), + ), + // Expanded( + // child: hasWallets ? const MyWallets() : const EmptyWallets(), + // ), + ], + ); + } +} diff --git a/lib/pages_desktop_specific/home/desktop_home_view.dart b/lib/pages_desktop_specific/home/desktop_home_view.dart index 14d2dae03..fab78e1f4 100644 --- a/lib/pages_desktop_specific/home/desktop_home_view.dart +++ b/lib/pages_desktop_specific/home/desktop_home_view.dart @@ -8,6 +8,8 @@ import 'package:stackwallet/pages_desktop_specific/home/support_and_about_view/d import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'address_book_view/desktop_address_book.dart'; + class DesktopHomeView extends ConsumerStatefulWidget { const DesktopHomeView({Key? key}) : super(key: key); @@ -31,8 +33,10 @@ class _DesktopHomeViewState extends ConsumerState { Container( color: Colors.red, ), - Container( - color: Colors.orange, + const Navigator( + key: Key("desktopAddressBookHomeKey"), + onGenerateRoute: RouteGenerator.generateRoute, + initialRoute: DesktopAddressBook.routeName, ), const Navigator( key: Key("desktopSettingHomeKey"), diff --git a/lib/route_generator.dart b/lib/route_generator.dart index 30963781b..f3e37e383 100644 --- a/lib/route_generator.dart +++ b/lib/route_generator.dart @@ -86,6 +86,7 @@ import 'package:stackwallet/pages/wallet_view/wallet_view.dart'; import 'package:stackwallet/pages/wallets_view/wallets_view.dart'; import 'package:stackwallet/pages_desktop_specific/create_password/create_password_view.dart'; import 'package:stackwallet/pages_desktop_specific/forgot_password_desktop_view.dart'; +import 'package:stackwallet/pages_desktop_specific/home/address_book_view/desktop_address_book.dart'; import 'package:stackwallet/pages_desktop_specific/home/desktop_home_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/desktop_settings_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/my_stack_view.dart'; @@ -1105,6 +1106,12 @@ class RouteGenerator { builder: (_) => const DesktopAboutView(), settings: RouteSettings(name: settings.name)); + case DesktopAddressBook.routeName: + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => const DesktopAddressBook(), + settings: RouteSettings(name: settings.name)); + case WalletKeysDesktopPopup.routeName: if (args is List) { return FadePageRoute( From 095f9c4ed9a7d692047131e1bdc043043822971e Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 9 Nov 2022 12:54:10 -0600 Subject: [PATCH 57/76] mobile swb restore unawaited --- .../restore_from_file_view.dart | 67 ++++++++++--------- 1 file changed, 35 insertions(+), 32 deletions(-) diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart index 014099abd..a237d9ea9 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart @@ -316,42 +316,45 @@ class _RestoreFromFileViewState extends ConsumerState { } bool shouldPop = false; - await showDialog( - barrierDismissible: false, - context: context, - builder: (_) => WillPopScope( - onWillPop: () async { - return shouldPop; - }, - child: Column( - crossAxisAlignment: - CrossAxisAlignment.stretch, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Material( - color: Colors.transparent, - child: Center( - child: Text( - "Decrypting Stack backup file", - style: - STextStyles.pageTitleH2(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textWhite, + unawaited( + showDialog( + barrierDismissible: false, + context: context, + builder: (_) => WillPopScope( + onWillPop: () async { + return shouldPop; + }, + child: Column( + crossAxisAlignment: + CrossAxisAlignment.stretch, + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Material( + color: Colors.transparent, + child: Center( + child: Text( + "Decrypting Stack backup file", + style: STextStyles.pageTitleH2( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textWhite, + ), ), ), ), - ), - const SizedBox( - height: 64, - ), - const Center( - child: LoadingIndicator( - width: 100, + const SizedBox( + height: 64, ), - ), - ], + const Center( + child: LoadingIndicator( + width: 100, + ), + ), + ], + ), ), ), ); From d15f022c4d6d86c5341972fb6c4824b24a01a503 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Wed, 9 Nov 2022 14:09:12 -0600 Subject: [PATCH 58/76] update wownero's first blocks per month --- crypto_plugins/flutter_libmonero | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto_plugins/flutter_libmonero b/crypto_plugins/flutter_libmonero index e705ba2d5..b9bc2dcc5 160000 --- a/crypto_plugins/flutter_libmonero +++ b/crypto_plugins/flutter_libmonero @@ -1 +1 @@ -Subproject commit e705ba2d5126685adae9367b62921b676d7126ed +Subproject commit b9bc2dcc56e13f235a6c5b0fc02c0e543eb87758 From 61f945aa98e8bb47858155556c37c1fd12585299 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Wed, 9 Nov 2022 13:13:42 -0700 Subject: [PATCH 59/76] dialog uses BackupFrequencyTypes and has offset for dropDownButton --- .../create_auto_backup.dart | 136 ++++++++++++------ 1 file changed, 93 insertions(+), 43 deletions(-) diff --git a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart index 57a8d7a64..07a1f1b78 100644 --- a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart +++ b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart @@ -1,11 +1,14 @@ import 'dart:io'; +import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/stack_file_system.dart'; +import 'package:stackwallet/providers/global/prefs_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/enums/backup_frequency_type.dart'; import 'package:stackwallet/utilities/enums/log_level_enum.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; @@ -19,14 +22,14 @@ import 'package:stackwallet/widgets/progress_bar.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:zxcvbn/zxcvbn.dart'; -class CreateAutoBackup extends StatefulWidget { +class CreateAutoBackup extends ConsumerStatefulWidget { const CreateAutoBackup({Key? key}) : super(key: key); @override - State createState() => _CreateAutoBackup(); + ConsumerState createState() => _CreateAutoBackup(); } -class _CreateAutoBackup extends State { +class _CreateAutoBackup extends ConsumerState { late final TextEditingController fileLocationController; late final TextEditingController passphraseController; late final TextEditingController passphraseRepeatController; @@ -52,12 +55,13 @@ class _CreateAutoBackup extends State { bool get fieldsMatch => passphraseController.text == passphraseRepeatController.text; - String _currentDropDownValue = "Every 10 minutes"; + BackupFrequencyType _currentDropDownValue = + BackupFrequencyType.everyTenMinutes; - final List _dropDownItems = [ - "Every 10 minutes", - "Every 20 minutes", - "Every 30 minutes", + final List _dropDownItems = [ + BackupFrequencyType.everyTenMinutes, + BackupFrequencyType.everyAppStart, + BackupFrequencyType.afterClosingAWallet, ]; @override @@ -101,6 +105,9 @@ class _CreateAutoBackup extends State { Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType "); + bool isEnabledAutoBackup = ref.watch(prefsChangeNotifierProvider + .select((value) => value.isAutoBackupEnabled)); + String? selectedItem = "Every 10 minutes"; final isDesktop = Util.isDesktop; return DesktopDialog( @@ -225,9 +232,7 @@ class _CreateAutoBackup extends State { paste: false, selectAll: false, ), - onChanged: (newValue) { - // ref.read(addressEntryDataProvider(widget.id)).address = newValue; - }, + onChanged: (newValue) {}, ), ); }), @@ -361,7 +366,7 @@ class _CreateAutoBackup extends State { ), child: ProgressBar( key: const Key("createStackBackUpProgressBar"), - width: 510, + width: 512, height: 5, fillColor: passwordStrength < 0.51 ? Theme.of(context) @@ -465,38 +470,83 @@ class _CreateAutoBackup extends State { left: 32, right: 32, ), - child: DropdownButtonFormField( - isExpanded: true, - elevation: 0, - style: STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of(context).extension()!.textDark, - ), - icon: SvgPicture.asset( - Assets.svg.chevronDown, - width: 10, - height: 5, - color: Theme.of(context).extension()!.textDark3, - ), - dropdownColor: - Theme.of(context).extension()!.textFieldActiveBG, - // focusColor: , - value: _currentDropDownValue, - items: _dropDownItems - .map( - (e) => DropdownMenuItem( - value: e, - child: Text(e), + child: isDesktop + ? DropdownButtonHideUnderline( + child: DropdownButton2( + offset: Offset(0, -10), + isExpanded: true, + dropdownElevation: 0, + value: _currentDropDownValue, + items: [ + ..._dropDownItems.map( + (e) { + String message = ""; + switch (e) { + case BackupFrequencyType.everyTenMinutes: + message = "Every 10 minutes"; + break; + case BackupFrequencyType.everyAppStart: + message = "Every app startup"; + break; + case BackupFrequencyType.afterClosingAWallet: + message = + "After closing a cryptocurrency wallet"; + break; + } + + return DropdownMenuItem( + value: e, + child: Text(message), + ); + }, + ), + ], + onChanged: (value) { + if (value is BackupFrequencyType) { + if (ref + .read(prefsChangeNotifierProvider) + .backupFrequencyType != + value) { + ref + .read(prefsChangeNotifierProvider) + .backupFrequencyType = value; + } + setState(() { + _currentDropDownValue = value; + }); + } + }, + icon: SvgPicture.asset( + Assets.svg.chevronDown, + width: 10, + height: 5, + color: Theme.of(context) + .extension()! + .textDark3, + ), + buttonPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + buttonDecoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + dropdownDecoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), ), ) - .toList(), - onChanged: (value) { - if (value is String) { - setState(() { - _currentDropDownValue = value; - }); - } - }, - ), + : null, ), const Spacer(), Padding( @@ -518,7 +568,7 @@ class _CreateAutoBackup extends State { Expanded( child: PrimaryButton( label: "Enable Auto Backup", - enabled: false, + enabled: shouldEnableCreate, onPressed: () {}, ), ) From cc779be46031b2491ad817af592d7d9266b1598b Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Wed, 9 Nov 2022 15:32:39 -0700 Subject: [PATCH 60/76] enable and disable auto back up --- .../backup_and_restore_settings.dart | 235 +++++++++++++++++- .../create_auto_backup.dart | 180 +++++++++++++- lib/widgets/stack_dialog.dart | 18 +- 3 files changed, 415 insertions(+), 18 deletions(-) diff --git a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart index 8928a268d..a444f4b51 100644 --- a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart @@ -2,15 +2,27 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:intl/intl.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/settings_menu/backup_and_restore/enable_backup_dialog.dart'; +import 'package:stackwallet/providers/global/locale_provider.dart'; +import 'package:stackwallet/providers/global/prefs_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/enums/backup_frequency_type.dart'; +import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/custom_buttons/draggable_switch_button.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; +import 'package:stackwallet/widgets/stack_dialog.dart'; import 'package:url_launcher/url_launcher.dart'; +import '../../../../providers/global/auto_swb_service_provider.dart'; +import '../../../../widgets/custom_buttons/blue_text_button.dart'; + class BackupRestoreSettings extends ConsumerStatefulWidget { const BackupRestoreSettings({Key? key}) : super(key: key); @@ -24,6 +36,49 @@ class BackupRestoreSettings extends ConsumerStatefulWidget { class _BackupRestoreSettings extends ConsumerState { late bool createBackup = false; late bool restoreBackup = false; + // late bool isEnabledAutoBackup; + + final toggleController = DSBController(); + + late final TextEditingController fileLocationController; + late final TextEditingController passwordController; + late final TextEditingController frequencyController; + + late final FocusNode fileLocationFocusNode; + late final FocusNode passwordFocusNode; + + String prettySinceLastBackupString(DateTime? time) { + if (time == null) { + return "-"; + } + final difference = DateTime.now().difference(time); + int value; + String postfix; + if (difference < const Duration(seconds: 60)) { + value = difference.inSeconds; + postfix = "seconds"; + } else if (difference < const Duration(minutes: 60)) { + value = difference.inMinutes; + postfix = "minutes"; + } else if (difference < const Duration(hours: 24)) { + value = difference.inHours; + postfix = "hours"; + } else if (difference.inDays < 8) { + value = difference.inDays; + postfix = "days"; + } else { + // if greater than a week return the actual date + return DateFormat.yMMMMd( + ref.read(localeServiceChangeNotifierProvider).locale) + .format(time); + } + + if (value == 1) { + postfix = postfix.substring(0, postfix.length - 1); + } + + return "$value $postfix ago"; + } Future enableAutoBackup(BuildContext context) async { await showDialog( @@ -36,10 +91,105 @@ class _BackupRestoreSettings extends ConsumerState { ); } + Future attemptDisable() async { + final result = await showDialog( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (context) { + return StackDialog( + title: "Disable Auto Backup", + message: + "You are turning off Auto Backup. You can turn it back on at any time. Your previous Auto Backup file will not be deleted. Remember to backup your wallets manually so you don't lose important information.", + leftButton: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonColor(context), + child: Text( + "Back", + style: STextStyles.button(context).copyWith( + color: + Theme.of(context).extension()!.accentColorDark, + ), + ), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonColor(context), + child: Text( + "Disable", + style: STextStyles.button(context), + ), + onPressed: () { + Navigator.of(context).pop(); + setState(() { + ref.watch(prefsChangeNotifierProvider).isAutoBackupEnabled = + false; + }); + }, + ), + ); + }, + ); + if (mounted) { + if (result is bool && result) { + ref.read(prefsChangeNotifierProvider).isAutoBackupEnabled = false; + Navigator.of(context).pop(); + } else { + toggleController.activate?.call(); + } + } + } + + @override + void initState() { + fileLocationController = TextEditingController(); + passwordController = TextEditingController(); + frequencyController = TextEditingController(); + + passwordController.text = "---------------"; + fileLocationController.text = + ref.read(prefsChangeNotifierProvider).autoBackupLocation ?? " "; + frequencyController.text = Format.prettyFrequencyType( + ref.read(prefsChangeNotifierProvider).backupFrequencyType); + + fileLocationFocusNode = FocusNode(); + passwordFocusNode = FocusNode(); + + // _toggle = ref.read(prefsChangeNotifierProvider).isAutoBackupEnabled; + super.initState(); + } + + @override + void dispose() { + fileLocationController.dispose(); + passwordController.dispose(); + frequencyController.dispose(); + + fileLocationFocusNode.dispose(); + passwordFocusNode.dispose(); + + super.dispose(); + } + @override Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); + bool isEnabledAutoBackup = ref.watch(prefsChangeNotifierProvider + .select((value) => value.isAutoBackupEnabled)); + + ref.listen( + prefsChangeNotifierProvider + .select((value) => value.backupFrequencyType), + (previous, BackupFrequencyType next) { + frequencyController.text = Format.prettyFrequencyType(next); + }); + return LayoutBuilder(builder: (context, constraints) { return SingleChildScrollView( scrollDirection: Axis.vertical, @@ -120,17 +270,80 @@ class _BackupRestoreSettings extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( - padding: EdgeInsets.all( - 10, - ), - child: PrimaryButton( - desktopMed: true, - width: 200, - label: "Enable auto backup", - onPressed: () { - enableAutoBackup(context); - }, - ), + padding: const EdgeInsets.all(10), + child: !isEnabledAutoBackup + ? PrimaryButton( + desktopMed: true, + width: 200, + label: "Enable auto backup", + onPressed: () { + enableAutoBackup(context); + }, + ) + : Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Container( + width: 403, + color: Theme.of(context) + .extension()! + .background, + child: Padding( + padding: + const EdgeInsets.all(8.0), + child: Column( + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, + children: [ + Text( + "Backed up ${prettySinceLastBackupString(ref.watch(prefsChangeNotifierProvider.select((value) => value.lastAutoBackup)))}", + style: STextStyles + .itemSubtitle( + context), + ), + BlueTextButton( + text: "Back up now", + onTap: () { + ref + .read( + autoSWBServiceProvider) + .doBackup(); + }, + ), + ], + ), + ], + ), + ), + ), + const SizedBox( + height: 20, + ), + Row( + children: [ + PrimaryButton( + desktopMed: true, + width: 190, + label: "Disable auto backup", + onPressed: () { + attemptDisable(); + }, + ), + const SizedBox(width: 16), + SecondaryButton( + desktopMed: true, + width: 190, + label: "Edit auto backup", + onPressed: () {}, + ), + ], + ) + ], + ), ), ], ), diff --git a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart index 07a1f1b78..e804071cc 100644 --- a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart +++ b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart @@ -1,15 +1,24 @@ +import 'dart:convert'; import 'dart:io'; import 'package:dropdown_button2/dropdown_button2.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:stack_wallet_backup/stack_wallet_backup.dart'; +import 'package:stackwallet/notifications/show_flush_bar.dart'; +import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/stack_file_system.dart'; import 'package:stackwallet/providers/global/prefs_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/backup_frequency_type.dart'; +import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/enums/log_level_enum.dart'; +import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; +import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; @@ -19,11 +28,19 @@ import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/progress_bar.dart'; +import 'package:stackwallet/widgets/stack_dialog.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:zxcvbn/zxcvbn.dart'; class CreateAutoBackup extends ConsumerStatefulWidget { - const CreateAutoBackup({Key? key}) : super(key: key); + const CreateAutoBackup({ + Key? key, + this.secureStore = const SecureStorageWrapper( + FlutterSecureStorage(), + ), + }) : super(key: key); + + final FlutterSecureStorageInterface secureStore; @override ConsumerState createState() => _CreateAutoBackup(); @@ -34,6 +51,8 @@ class _CreateAutoBackup extends ConsumerState { late final TextEditingController passphraseController; late final TextEditingController passphraseRepeatController; + late final FlutterSecureStorageInterface secureStore; + late final StackFileSystem stackFileSystem; late final FocusNode passphraseFocusNode; late final FocusNode passphraseRepeatFocusNode; @@ -66,6 +85,7 @@ class _CreateAutoBackup extends ConsumerState { @override void initState() { + secureStore = widget.secureStore; stackFileSystem = StackFileSystem(); fileLocationController = TextEditingController(); @@ -569,7 +589,163 @@ class _CreateAutoBackup extends ConsumerState { child: PrimaryButton( label: "Enable Auto Backup", enabled: shouldEnableCreate, - onPressed: () {}, + onPressed: !shouldEnableCreate + ? null + : () async { + final String pathToSave = + fileLocationController.text; + final String passphrase = passphraseController.text; + final String repeatPassphrase = + passphraseRepeatController.text; + + if (pathToSave.isEmpty) { + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Directory not chosen", + context: context, + ); + return; + } + if (!(await Directory(pathToSave).exists())) { + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Directory does not exist", + context: context, + ); + return; + } + if (passphrase.isEmpty) { + showFloatingFlushBar( + type: FlushBarType.warning, + message: "A passphrase is required", + context: context, + ); + return; + } + if (passphrase != repeatPassphrase) { + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Passphrase does not match", + context: context, + ); + return; + } + + showDialog( + context: context, + barrierDismissible: false, + builder: (_) => const StackDialog( + title: "Encrypting initial backup", + message: "This shouldn't take long", + ), + ); + + // make sure the dialog is able to be displayed for at least some time + final fut = Future.delayed( + const Duration(milliseconds: 300)); + + String adkString; + int adkVersion; + try { + final adk = + await compute(generateAdk, passphrase); + adkString = Format.uint8listToString(adk.item2); + adkVersion = adk.item1; + } on Exception catch (e, s) { + String err = getErrorMessageFromSWBException(e); + Logging.instance + .log("$err\n$s", level: LogLevel.Error); + // pop encryption progress dialog + Navigator.of(context).pop(); + showFloatingFlushBar( + type: FlushBarType.warning, + message: err, + context: context, + ); + return; + } catch (e, s) { + Logging.instance + .log("$e\n$s", level: LogLevel.Error); + // pop encryption progress dialog + Navigator.of(context).pop(); + showFloatingFlushBar( + type: FlushBarType.warning, + message: "$e", + context: context, + ); + return; + } + + await secureStore.write( + key: "auto_adk_string", value: adkString); + await secureStore.write( + key: "auto_adk_version_string", + value: adkVersion.toString()); + + final DateTime now = DateTime.now(); + final String fileToSave = + createAutoBackupFilename(pathToSave, now); + + final backup = await SWB.createStackWalletJSON(); + + bool result = await SWB.encryptStackWalletWithADK( + fileToSave, + adkString, + jsonEncode(backup), + adkVersion: adkVersion, + ); + + // this future should already be complete unless there was an error encrypting + await Future.wait([fut]); + + if (mounted) { + // pop encryption progress dialog + int count = 0; + Navigator.of(context) + .popUntil((_) => count++ >= 2); + + if (result) { + ref + .read(prefsChangeNotifierProvider) + .autoBackupLocation = pathToSave; + ref + .read(prefsChangeNotifierProvider) + .lastAutoBackup = now; + + ref + .read(prefsChangeNotifierProvider) + .isAutoBackupEnabled = true; + + await showDialog( + context: context, + barrierDismissible: false, + builder: (_) => Platform.isAndroid + ? StackOkDialog( + title: + "Stack Auto Backup enabled and saved to:", + message: fileToSave, + ) + : const StackOkDialog( + title: "Stack Auto Backup enabled!"), + ); + if (mounted) { + passphraseController.text = ""; + passphraseRepeatController.text = ""; + + int count = 0; + Navigator.of(context) + .popUntil((_) => count++ >= 2); + } + } else { + await showDialog( + context: context, + barrierDismissible: false, + builder: (_) => const StackOkDialog( + title: "Failed to enable Auto Backup"), + ); + } + } + }, ), ) ], diff --git a/lib/widgets/stack_dialog.dart b/lib/widgets/stack_dialog.dart index be1d51596..ea2638264 100644 --- a/lib/widgets/stack_dialog.dart +++ b/lib/widgets/stack_dialog.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/utilities/util.dart'; class StackDialogBase extends StatelessWidget { const StackDialogBase({ @@ -17,7 +18,8 @@ class StackDialogBase extends StatelessWidget { return Padding( padding: const EdgeInsets.all(16), child: Column( - mainAxisAlignment: MainAxisAlignment.end, + mainAxisAlignment: + !Util.isDesktop ? MainAxisAlignment.end : MainAxisAlignment.center, children: [ Material( borderRadius: BorderRadius.circular( @@ -179,10 +181,16 @@ class StackOkDialog extends StatelessWidget { ), Expanded( child: TextButton( - onPressed: () { - Navigator.of(context).pop(); - onOkPressed?.call("OK"); - }, + onPressed: !Util.isDesktop + ? () { + Navigator.of(context).pop(); + onOkPressed?.call("OK"); + } + : () { + int count = 0; + Navigator.of(context).popUntil((_) => count++ >= 2); + // onOkPressed?.call("OK"); + }, style: Theme.of(context) .extension()! .getPrimaryEnabledButtonColor(context), From 6d0452debb939df3e95f34082eacb4dc7a5712a7 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Wed, 9 Nov 2022 16:23:53 -0700 Subject: [PATCH 61/76] small import fix --- lib/pages_desktop_specific/home/desktop_home_view.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/pages_desktop_specific/home/desktop_home_view.dart b/lib/pages_desktop_specific/home/desktop_home_view.dart index fab78e1f4..cb8aba255 100644 --- a/lib/pages_desktop_specific/home/desktop_home_view.dart +++ b/lib/pages_desktop_specific/home/desktop_home_view.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/pages_desktop_specific/home/address_book_view/desktop_address_book.dart'; import 'package:stackwallet/pages_desktop_specific/home/desktop_menu.dart'; import 'package:stackwallet/pages_desktop_specific/home/desktop_settings_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/my_stack_view.dart'; @@ -8,8 +9,6 @@ import 'package:stackwallet/pages_desktop_specific/home/support_and_about_view/d import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; -import 'address_book_view/desktop_address_book.dart'; - class DesktopHomeView extends ConsumerStatefulWidget { const DesktopHomeView({Key? key}) : super(key: key); From 2d9cf9146331e6dc3aa819d2d24751fb24931f5c Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Wed, 9 Nov 2022 16:57:06 -0700 Subject: [PATCH 62/76] v1.5.16 build 88 --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 86f24330b..19d38ca4f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,7 +11,7 @@ description: Stack Wallet # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.5.14+86 +version: 1.5.16+88 environment: sdk: ">=2.17.0 <3.0.0" From 9d7a052ca0b0241834a54a70af953463c53fd22f Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 10 Nov 2022 09:42:05 -0600 Subject: [PATCH 63/76] qr uri fix --- .../generate_receiving_uri_qr_code_view.dart | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart b/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart index 4c3c4c968..0f699642e 100644 --- a/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart +++ b/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart @@ -11,6 +11,7 @@ import 'package:path_provider/path_provider.dart'; import 'package:qr_flutter/qr_flutter.dart'; import 'package:share_plus/share_plus.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; +import 'package:stackwallet/utilities/address_utils.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/clipboard_interface.dart'; import 'package:stackwallet/utilities/constants.dart'; @@ -101,26 +102,21 @@ class _GenerateUriQrCodeViewState extends State { return null; } - String query = ""; + Map queryParams = {}; if (amountString.isNotEmpty) { - query += "amount=$amountString"; + queryParams["amount"] = amountString; } if (noteString.isNotEmpty) { - if (query.isNotEmpty) { - query += "&"; - } - query += "message=$noteString"; + queryParams["message"] = noteString; } - final uri = Uri( - scheme: widget.coin.uriScheme, - host: widget.receivingAddress, - query: query.isNotEmpty ? query : null, + final uriString = AddressUtils.buildUriString( + widget.coin, + widget.receivingAddress, + queryParams, ); - final uriString = uri.toString().replaceFirst("://", ":"); - Logging.instance.log("Generated receiving QR code for: $uriString", level: LogLevel.Info); From be952d3e35bfbdb1939e7aded8dc8e983778e3c4 Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 10 Nov 2022 09:54:58 -0600 Subject: [PATCH 64/76] manually add bch uri fixes from https://github.com/cypherstack/stack_wallet/pull/214/commits/28da2b890076a1d679dccaa9970c98516d4ae2d9 --- .../generate_receiving_uri_qr_code_view.dart | 29 +++++++++++++++---- lib/utilities/enums/coin_enum.dart | 2 +- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart b/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart index 0f699642e..ae615bd96 100644 --- a/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart +++ b/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart @@ -111,9 +111,17 @@ class _GenerateUriQrCodeViewState extends State { queryParams["message"] = noteString; } + String receivingAddress = widget.receivingAddress; + if ((widget.coin == Coin.bitcoincash || + widget.coin == Coin.bitcoincashTestnet) && + receivingAddress.contains(":")) { + // remove cash addr prefix + receivingAddress = receivingAddress.split(":").sublist(1).join(); + } + final uriString = AddressUtils.buildUriString( widget.coin, - widget.receivingAddress, + receivingAddress, queryParams, ); @@ -225,10 +233,21 @@ class _GenerateUriQrCodeViewState extends State { @override void initState() { isDesktop = Util.isDesktop; - _uriString = Uri( - scheme: widget.coin.uriScheme, - host: widget.receivingAddress, - ).toString().replaceFirst("://", ":"); + + String receivingAddress = widget.receivingAddress; + if ((widget.coin == Coin.bitcoincash || + widget.coin == Coin.bitcoincashTestnet) && + receivingAddress.contains(":")) { + // remove cash addr prefix + receivingAddress = receivingAddress.split(":").sublist(1).join(); + } + + _uriString = AddressUtils.buildUriString( + widget.coin, + receivingAddress, + {}, + ); + amountController = TextEditingController(); noteController = TextEditingController(); super.initState(); diff --git a/lib/utilities/enums/coin_enum.dart b/lib/utilities/enums/coin_enum.dart index 95294c8aa..48212bde8 100644 --- a/lib/utilities/enums/coin_enum.dart +++ b/lib/utilities/enums/coin_enum.dart @@ -132,7 +132,7 @@ extension CoinExt on Coin { case Coin.litecoinTestNet: return "litecoin"; case Coin.bitcoincashTestnet: - return "bitcoincash"; + return "bchtest"; case Coin.firoTestNet: return "firo"; case Coin.dogecoinTestNet: From 74a04750765d26a592ed62b7e3bc408907da8122 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Thu, 10 Nov 2022 09:09:03 -0700 Subject: [PATCH 65/76] WIP: desktop restore dialog displays --- .../restore_from_file_view.dart | 77 ++- .../stack_restore_progress_view.dart | 556 +++++++++--------- .../restore_backup_dialog.dart | 171 ------ 3 files changed, 355 insertions(+), 449 deletions(-) delete mode 100644 lib/pages_desktop_specific/home/settings_menu/backup_and_restore/restore_backup_dialog.dart diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart index a237d9ea9..dbf46c729 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart @@ -20,6 +20,8 @@ import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/loading_indicator.dart'; @@ -500,14 +502,73 @@ class _RestoreFromFileViewState extends ConsumerState { return; } - await Navigator.of(context).push( - RouteGenerator.getRoute( - builder: (_) => - StackRestoreProgressView( - jsonString: jsonString, - ), - ), - ); + await showDialog( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (context) { + return DesktopDialog( + maxHeight: 750, + maxWidth: 600, + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: + constraints.maxHeight, + ), + child: IntrinsicHeight( + child: Column( + mainAxisAlignment: + MainAxisAlignment + .start, + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, + children: [ + Padding( + padding: + const EdgeInsets + .all(32), + child: Text( + "Restoring Stack Wallet", + style: STextStyles + .desktopH3( + context), + textAlign: + TextAlign + .center, + ), + ), + const DesktopDialogCloseButton(), + ], + ), + const SizedBox( + height: 30, + ), + Padding( + padding: EdgeInsets + .symmetric( + horizontal: + 32), + child: + StackRestoreProgressView( + jsonString: + jsonString, + ), + ), + ], + ), + ), + ), + ); + }, + ), + ); + }); } }, ), diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart index 5e5142425..7dec4e740 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart @@ -3,7 +3,6 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/pages/home_view/home_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/dialogs/cancel_stack_restore_dialog.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/restore_from_encrypted_string_view.dart'; @@ -17,6 +16,8 @@ import 'package:stackwallet/utilities/enums/stack_restoring_status.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/icon_widgets/addressbook_icon.dart'; import 'package:stackwallet/widgets/loading_indicator.dart'; @@ -39,6 +40,8 @@ class StackRestoreProgressView extends ConsumerStatefulWidget { class _StackRestoreProgressViewState extends ConsumerState { + bool isDesktop = Util.isDesktop; + Future _cancel() async { bool shouldPop = false; unawaited(showDialog( @@ -79,10 +82,15 @@ class _StackRestoreProgressViewState await SWB.cancelRestore(); shouldPop = true; + + int count = 0; + if (mounted) { - Navigator.of(context).popUntil(ModalRoute.withName(widget.fromFile - ? RestoreFromEncryptedStringView.routeName - : StackBackupView.routeName)); + !isDesktop + ? Navigator.of(context).popUntil(ModalRoute.withName(widget.fromFile + ? RestoreFromEncryptedStringView.routeName + : StackBackupView.routeName)) + : Navigator.of(context).popUntil((_) => count++ >= 2); } } @@ -179,281 +187,289 @@ class _StackRestoreProgressViewState @override Widget build(BuildContext context) { - return WillPopScope( - onWillPop: _onWillPop, - child: Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed(const Duration(milliseconds: 75)); - } - if (_success) { - _addWalletsToHomeView(); - if (mounted) { - Navigator.of(context).pop(); - } - } else { - if (await _requestCancel()) { - await _cancel(); - } - } - }, - ), - title: Text( - "Restoring Stack wallet", - style: STextStyles.navBarTitle(context), - ), - ), - body: Padding( - padding: const EdgeInsets.only( - left: 12, - top: 12, - right: 12, - ), - child: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.only( - left: 4, - top: 4, - right: 4, - bottom: 0, + bool isDesktop = Util.isDesktop; + + return ConditionalParent( + condition: !isDesktop, + builder: (child) { + return WillPopScope( + onWillPop: _onWillPop, + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration(milliseconds: 75)); + } + if (_success) { + _addWalletsToHomeView(); + if (mounted) { + Navigator.of(context).pop(); + } + } else { + if (await _requestCancel()) { + await _cancel(); + } + } + }, ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Settings", - style: STextStyles.itemSubtitle(context), - ), - const SizedBox( - height: 12, - ), - Consumer( - builder: (_, ref, __) { - final state = ref.watch(stackRestoringUIStateProvider - .select((value) => value.preferences)); - return RestoringItemCard( - left: SizedBox( - width: 32, - height: 32, - child: RoundedContainer( - padding: const EdgeInsets.all(0), + title: Text( + "Restoring Stack wallet", + style: STextStyles.navBarTitle(context), + ), + ), + body: Padding( + padding: const EdgeInsets.only( + left: 12, + top: 12, + right: 12, + ), + child: child, + ), + ), + ); + }, + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.only( + left: 4, + top: 4, + right: 4, + bottom: 0, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Settings", + style: STextStyles.itemSubtitle(context), + ), + const SizedBox( + height: 12, + ), + Consumer( + builder: (_, ref, __) { + final state = ref.watch(stackRestoringUIStateProvider + .select((value) => value.preferences)); + return RestoringItemCard( + left: SizedBox( + width: 32, + height: 32, + child: RoundedContainer( + padding: const EdgeInsets.all(0), + color: Theme.of(context) + .extension()! + .buttonBackSecondary, + child: Center( + child: SvgPicture.asset( + Assets.svg.gear, + width: 16, + height: 16, color: Theme.of(context) .extension()! - .buttonBackSecondary, - child: Center( - child: SvgPicture.asset( - Assets.svg.gear, - width: 16, - height: 16, - color: Theme.of(context) - .extension()! - .accentColorDark, - ), - ), - ), - ), - right: SizedBox( - width: 20, - height: 20, - child: _getIconForState(state), - ), - title: "Preferences", - subTitle: state == StackRestoringStatus.failed - ? Text( - "Something went wrong", - style: STextStyles.errorSmall(context), - ) - : null, - ); - }, - ), - const SizedBox( - height: 12, - ), - Consumer( - builder: (_, ref, __) { - final state = ref.watch(stackRestoringUIStateProvider - .select((value) => value.addressBook)); - return RestoringItemCard( - left: SizedBox( - width: 32, - height: 32, - child: RoundedContainer( - padding: const EdgeInsets.all(0), - color: Theme.of(context) - .extension()! - .buttonBackSecondary, - child: Center( - child: AddressBookIcon( - width: 16, - height: 16, - color: Theme.of(context) - .extension()! - .accentColorDark, - ), - ), - ), - ), - right: SizedBox( - width: 20, - height: 20, - child: _getIconForState(state), - ), - title: "Address book", - subTitle: state == StackRestoringStatus.failed - ? Text( - "Something went wrong", - style: STextStyles.errorSmall(context), - ) - : null, - ); - }, - ), - const SizedBox( - height: 12, - ), - Consumer( - builder: (_, ref, __) { - final state = ref.watch(stackRestoringUIStateProvider - .select((value) => value.nodes)); - return RestoringItemCard( - left: SizedBox( - width: 32, - height: 32, - child: RoundedContainer( - padding: const EdgeInsets.all(0), - color: Theme.of(context) - .extension()! - .buttonBackSecondary, - child: Center( - child: SvgPicture.asset( - Assets.svg.node, - width: 16, - height: 16, - color: Theme.of(context) - .extension()! - .accentColorDark, - ), - ), - ), - ), - right: SizedBox( - width: 20, - height: 20, - child: _getIconForState(state), - ), - title: "Nodes", - subTitle: state == StackRestoringStatus.failed - ? Text( - "Something went wrong", - style: STextStyles.errorSmall(context), - ) - : null, - ); - }, - ), - const SizedBox( - height: 12, - ), - Consumer( - builder: (_, ref, __) { - final state = ref.watch(stackRestoringUIStateProvider - .select((value) => value.trades)); - return RestoringItemCard( - left: SizedBox( - width: 32, - height: 32, - child: RoundedContainer( - padding: const EdgeInsets.all(0), - color: Theme.of(context) - .extension()! - .buttonBackSecondary, - child: Center( - child: SvgPicture.asset( - Assets.svg.arrowRotate2, - width: 16, - height: 16, - color: Theme.of(context) - .extension()! - .accentColorDark, - ), - ), - ), - ), - right: SizedBox( - width: 20, - height: 20, - child: _getIconForState(state), - ), - title: "Exchange history", - subTitle: state == StackRestoringStatus.failed - ? Text( - "Something went wrong", - style: STextStyles.errorSmall(context), - ) - : null, - ); - }, - ), - const SizedBox( - height: 16, - ), - Text( - "Wallets", - style: STextStyles.itemSubtitle(context), - ), - const SizedBox( - height: 8, - ), - ...ref - .watch(stackRestoringUIStateProvider - .select((value) => value.walletStateProviders)) - .values - .map( - (provider) => Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: RestoringWalletCard( - provider: provider, + .accentColorDark, ), ), ), - const SizedBox( - height: 80, + ), + right: SizedBox( + width: 20, + height: 20, + child: _getIconForState(state), + ), + title: "Preferences", + subTitle: state == StackRestoringStatus.failed + ? Text( + "Something went wrong", + style: STextStyles.errorSmall(context), + ) + : null, + ); + }, + ), + const SizedBox( + height: 12, + ), + Consumer( + builder: (_, ref, __) { + final state = ref.watch(stackRestoringUIStateProvider + .select((value) => value.addressBook)); + return RestoringItemCard( + left: SizedBox( + width: 32, + height: 32, + child: RoundedContainer( + padding: const EdgeInsets.all(0), + color: Theme.of(context) + .extension()! + .buttonBackSecondary, + child: Center( + child: AddressBookIcon( + width: 16, + height: 16, + color: Theme.of(context) + .extension()! + .accentColorDark, + ), + ), + ), + ), + right: SizedBox( + width: 20, + height: 20, + child: _getIconForState(state), + ), + title: "Address book", + subTitle: state == StackRestoringStatus.failed + ? Text( + "Something went wrong", + style: STextStyles.errorSmall(context), + ) + : null, + ); + }, + ), + const SizedBox( + height: 12, + ), + Consumer( + builder: (_, ref, __) { + final state = ref.watch(stackRestoringUIStateProvider + .select((value) => value.nodes)); + return RestoringItemCard( + left: SizedBox( + width: 32, + height: 32, + child: RoundedContainer( + padding: const EdgeInsets.all(0), + color: Theme.of(context) + .extension()! + .buttonBackSecondary, + child: Center( + child: SvgPicture.asset( + Assets.svg.node, + width: 16, + height: 16, + color: Theme.of(context) + .extension()! + .accentColorDark, + ), + ), + ), + ), + right: SizedBox( + width: 20, + height: 20, + child: _getIconForState(state), + ), + title: "Nodes", + subTitle: state == StackRestoringStatus.failed + ? Text( + "Something went wrong", + style: STextStyles.errorSmall(context), + ) + : null, + ); + }, + ), + const SizedBox( + height: 12, + ), + Consumer( + builder: (_, ref, __) { + final state = ref.watch(stackRestoringUIStateProvider + .select((value) => value.trades)); + return RestoringItemCard( + left: SizedBox( + width: 32, + height: 32, + child: RoundedContainer( + padding: const EdgeInsets.all(0), + color: Theme.of(context) + .extension()! + .buttonBackSecondary, + child: Center( + child: SvgPicture.asset( + Assets.svg.arrowRotate2, + width: 16, + height: 16, + color: Theme.of(context) + .extension()! + .accentColorDark, + ), + ), + ), + ), + right: SizedBox( + width: 20, + height: 20, + child: _getIconForState(state), + ), + title: "Exchange history", + subTitle: state == StackRestoringStatus.failed + ? Text( + "Something went wrong", + style: STextStyles.errorSmall(context), + ) + : null, + ); + }, + ), + const SizedBox( + height: 16, + ), + Text( + "Wallets", + style: STextStyles.itemSubtitle(context), + ), + const SizedBox( + height: 8, + ), + ...ref + .watch(stackRestoringUIStateProvider + .select((value) => value.walletStateProviders)) + .values + .map( + (provider) => Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: RestoringWalletCard( + provider: provider, + ), + ), ), - ], + const SizedBox( + height: 30, ), - ), - ), - ), - floatingActionButton: SizedBox( - width: MediaQuery.of(context).size.width - 32, - child: TextButton( - onPressed: () async { - if (_success) { - _addWalletsToHomeView(); - Navigator.of(context) - .popUntil(ModalRoute.withName(HomeView.routeName)); - } else { - if (await _requestCancel()) { - await _cancel(); - } - } - }, - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context), - child: Text( - _success ? "OK" : "Cancel restore process", - style: STextStyles.button(context).copyWith( - color: Theme.of(context) - .extension()! - .buttonTextPrimary, + SizedBox( + width: MediaQuery.of(context).size.width - 32, + child: TextButton( + onPressed: () async { + if (_success) { + Navigator.of(context).pop(); + } else { + if (await _requestCancel()) { + await _cancel(); + } + } + }, + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonColor(context), + child: Text( + _success ? "OK" : "Cancel restore process", + style: STextStyles.button(context).copyWith( + color: Theme.of(context) + .extension()! + .buttonTextPrimary, + ), + ), + ), ), - ), + ], ), ), ), diff --git a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/restore_backup_dialog.dart b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/restore_backup_dialog.dart deleted file mode 100644 index 7f944847d..000000000 --- a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/restore_backup_dialog.dart +++ /dev/null @@ -1,171 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/utilities/assets.dart'; -import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; -import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; -import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; -import 'package:stackwallet/widgets/desktop/secondary_button.dart'; -import 'package:stackwallet/widgets/rounded_white_container.dart'; - -class RestoreBackupDialog extends StatelessWidget { - const RestoreBackupDialog({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return DesktopDialog( - maxHeight: 750, - maxWidth: 600, - child: LayoutBuilder( - builder: (context, constraints) { - return SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight, - ), - child: IntrinsicHeight( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Padding( - padding: const EdgeInsets.all(32), - child: Text( - "Restoring Stack Wallet", - style: STextStyles.desktopH3(context), - textAlign: TextAlign.center, - ), - ), - const DesktopDialogCloseButton(), - ], - ), - const SizedBox( - height: 30, - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 32), - child: Row( - children: [ - Text( - "Settings", - style: STextStyles.desktopTextExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark3, - ), - textAlign: TextAlign.left, - ), - ], - ), - ), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 32, vertical: 12), - child: RoundedWhiteContainer( - borderColor: Theme.of(context) - .extension()! - .background, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - SvgPicture.asset( - Assets.svg.framedAddressBook, - width: 40, - height: 40, - ), - const SizedBox(width: 12), - Text( - "Address Book", - style: - STextStyles.desktopTextSmall(context), - ), - ], - ), - - ///TODO: CHECKMARK ANIMATION - ], - ), - ), - ), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 32, vertical: 12), - child: RoundedWhiteContainer( - borderColor: Theme.of(context) - .extension()! - .background, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - SvgPicture.asset( - Assets.svg.framedGear, - width: 40, - height: 40, - ), - const SizedBox(width: 12), - Text( - "Preferences", - style: - STextStyles.desktopTextSmall(context), - ), - ], - ), - - ///TODO: CHECKMARK ANIMATION - ], - ), - ), - ), - const SizedBox( - height: 30, - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 32), - child: Row( - children: [ - Text( - "Wallets", - style: STextStyles.desktopTextExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark3, - ), - textAlign: TextAlign.left, - ), - ], - ), - ), - const Spacer(), - Padding( - padding: const EdgeInsets.all(32), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - SecondaryButton( - desktopMed: true, - width: 200, - label: "Cancel restore process", - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - ), - ), - ], - ), - ), - ), - ); - }, - )); - } -} From 07c99309ff80e5428ba6888613d2bde20aef6a07 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Thu, 10 Nov 2022 10:21:07 -0600 Subject: [PATCH 66/76] use native address validation --- crypto_plugins/flutter_libmonero | 2 +- lib/services/coins/monero/monero_wallet.dart | 4 +--- lib/services/coins/wownero/wownero_wallet.dart | 4 +--- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/crypto_plugins/flutter_libmonero b/crypto_plugins/flutter_libmonero index b9bc2dcc5..4dd55584a 160000 --- a/crypto_plugins/flutter_libmonero +++ b/crypto_plugins/flutter_libmonero @@ -1 +1 @@ -Subproject commit b9bc2dcc56e13f235a6c5b0fc02c0e543eb87758 +Subproject commit 4dd55584a023bf6fd863c24ca8e1ffcedcc25162 diff --git a/lib/services/coins/monero/monero_wallet.dart b/lib/services/coins/monero/monero_wallet.dart index 4d29c405c..a96ecac83 100644 --- a/lib/services/coins/monero/monero_wallet.dart +++ b/lib/services/coins/monero/monero_wallet.dart @@ -1353,10 +1353,8 @@ class MoneroWallet extends CoinServiceAPI { Future> get unspentOutputs => throw UnimplementedError(); @override - // TODO: implement validateAddress bool validateAddress(String address) { - bool valid = RegExp("[a-zA-Z0-9]{95}").hasMatch(address) || - RegExp("[a-zA-Z0-9]{106}").hasMatch(address); + bool valid = walletBase!.validateAddress(address); return valid; } diff --git a/lib/services/coins/wownero/wownero_wallet.dart b/lib/services/coins/wownero/wownero_wallet.dart index e2c52c40a..4c02ec037 100644 --- a/lib/services/coins/wownero/wownero_wallet.dart +++ b/lib/services/coins/wownero/wownero_wallet.dart @@ -1378,10 +1378,8 @@ class WowneroWallet extends CoinServiceAPI { Future> get unspentOutputs => throw UnimplementedError(); @override - // TODO: implement validateAddress bool validateAddress(String address) { - bool valid = RegExp("[a-zA-Z0-9]{95}").hasMatch(address) || - RegExp("[a-zA-Z0-9]{106}").hasMatch(address); + bool valid = walletBase!.validateAddress(address); return valid; } From 6e48fc4ea6ce45b82ed5204ccba5ffd8773e9777 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Thu, 10 Nov 2022 10:46:09 -0600 Subject: [PATCH 67/76] use hive_test for monero and wownero tests --- .../coins/monero/monero_wallet_test.dart | 26 +++++++++++++------ .../coins/wownero/wownero_wallet_test.dart | 26 +++++++++++++------ 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/test/services/coins/monero/monero_wallet_test.dart b/test/services/coins/monero/monero_wallet_test.dart index d54959ab2..2c755b484 100644 --- a/test/services/coins/monero/monero_wallet_test.dart +++ b/test/services/coins/monero/monero_wallet_test.dart @@ -81,19 +81,29 @@ void main() async { if (Platform.isIOS) { appDir = (await getLibraryDirectory()); } - await Hive.close(); - Hive.init(appDir.path); - Hive.registerAdapter(NodeAdapter()); - Hive.registerAdapter(WalletInfoAdapter()); - Hive.registerAdapter(WalletTypeAdapter()); - Hive.registerAdapter(UnspentCoinsInfoAdapter()); monero.onStartup(); - _walletInfoSource = await Hive.openBox(WalletInfo.boxName); - walletService = monero.createMoneroWalletService(_walletInfoSource); + + bool hiveAdaptersRegistered = false; group("Mainnet tests", () { setUp(() async { + await setUpTestHive(); + if (!hiveAdaptersRegistered) { + hiveAdaptersRegistered = true; + + Hive.registerAdapter(NodeAdapter()); + Hive.registerAdapter(WalletInfoAdapter()); + Hive.registerAdapter(WalletTypeAdapter()); + Hive.registerAdapter(UnspentCoinsInfoAdapter()); + + final wallets = await Hive.openBox('wallets'); + await wallets.put('currentWalletName', name); + + _walletInfoSource = await Hive.openBox(WalletInfo.boxName); + walletService = monero.createMoneroWalletService(_walletInfoSource); + } + try { // if (name?.isEmpty ?? true) { // name = await generateName(); diff --git a/test/services/coins/wownero/wownero_wallet_test.dart b/test/services/coins/wownero/wownero_wallet_test.dart index 660bc1438..87229a63a 100644 --- a/test/services/coins/wownero/wownero_wallet_test.dart +++ b/test/services/coins/wownero/wownero_wallet_test.dart @@ -72,19 +72,29 @@ void main() async { if (Platform.isIOS) { appDir = (await getLibraryDirectory()); } - await Hive.close(); - Hive.init(appDir.path); - Hive.registerAdapter(NodeAdapter()); - Hive.registerAdapter(WalletInfoAdapter()); - Hive.registerAdapter(WalletTypeAdapter()); - Hive.registerAdapter(UnspentCoinsInfoAdapter()); wownero.onStartup(); - _walletInfoSource = await Hive.openBox(WalletInfo.boxName); - walletService = wownero.createWowneroWalletService(_walletInfoSource); + + bool hiveAdaptersRegistered = false; group("Wownero 14 word seed generation", () { setUp(() async { + await setUpTestHive(); + if (!hiveAdaptersRegistered) { + hiveAdaptersRegistered = true; + + Hive.registerAdapter(NodeAdapter()); + Hive.registerAdapter(WalletInfoAdapter()); + Hive.registerAdapter(WalletTypeAdapter()); + Hive.registerAdapter(UnspentCoinsInfoAdapter()); + + final wallets = await Hive.openBox('wallets'); + await wallets.put('currentWalletName', name); + + _walletInfoSource = await Hive.openBox(WalletInfo.boxName); + walletService = wownero.createWowneroWalletService(_walletInfoSource); + } + bool hasThrown = false; try { name = 'namee${Random().nextInt(10000000)}'; From 50f4f2ff64b28e7a9a18a2fb3a0fc6eeb1289a9f Mon Sep 17 00:00:00 2001 From: julian-CStack <97684800+julian-CStack@users.noreply.github.com> Date: Thu, 10 Nov 2022 11:23:49 -0600 Subject: [PATCH 68/76] Update test.yaml apt update --- .github/workflows/test.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index d72d33ac4..83de43779 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -23,6 +23,7 @@ jobs: run: | cargo install cargo-ndk rustup target add x86_64-unknown-linux-gnu + sudo apt update sudo apt install -y unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake openjdk-8-jre-headless libgit2-dev clang libncurses5-dev libncursesw5-dev zlib1g-dev llvm sudo apt install -y debhelper libclang-dev cargo rustc opencl-headers libssl-dev ocl-icd-opencl-dev sudo apt install -y libc6-dev-i386 From 15dc2512dbd431ac894169731a11220f97416dad Mon Sep 17 00:00:00 2001 From: sneurlax Date: Thu, 10 Nov 2022 12:19:51 -0600 Subject: [PATCH 69/76] update iOs headers --- crypto_plugins/flutter_libmonero | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto_plugins/flutter_libmonero b/crypto_plugins/flutter_libmonero index 4dd55584a..8a8c88cda 160000 --- a/crypto_plugins/flutter_libmonero +++ b/crypto_plugins/flutter_libmonero @@ -1 +1 @@ -Subproject commit 4dd55584a023bf6fd863c24ca8e1ffcedcc25162 +Subproject commit 8a8c88cdade6fe18529deea410f862b125167a3b From b635f1663b6a5699e0a24a73693eca1bc4cf3b34 Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 10 Nov 2022 15:07:44 -0600 Subject: [PATCH 70/76] comment out broken code in swb desktop restore + clean up unused imports and linter errors in wow/monero tests --- .../restore_from_file_view.dart | 18 +++++----- .../coins/monero/monero_wallet_test.dart | 33 ++++--------------- .../coins/wownero/wownero_wallet_test.dart | 29 ++++------------ 3 files changed, 21 insertions(+), 59 deletions(-) diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart index dbf46c729..ee1fcf666 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart @@ -9,7 +9,7 @@ import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/stack_file_system.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart'; -import 'package:stackwallet/pages_desktop_specific/home/settings_menu/backup_and_restore/restore_backup_dialog.dart'; +// import 'package:stackwallet/pages_desktop_specific/home/settings_menu/backup_and_restore/restore_backup_dialog.dart'; import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; @@ -49,14 +49,14 @@ class _RestoreFromFileViewState extends ConsumerState { bool hidePassword = true; Future restoreBackupPopup(BuildContext context) async { - await showDialog( - context: context, - useSafeArea: false, - barrierDismissible: true, - builder: (context) { - return const RestoreBackupDialog(); - }, - ); + // await showDialog( + // context: context, + // useSafeArea: false, + // barrierDismissible: true, + // builder: (context) { + // return const RestoreBackupDialog(); + // }, + // ); } @override diff --git a/test/services/coins/monero/monero_wallet_test.dart b/test/services/coins/monero/monero_wallet_test.dart index 2c755b484..7bbc88ed9 100644 --- a/test/services/coins/monero/monero_wallet_test.dart +++ b/test/services/coins/monero/monero_wallet_test.dart @@ -1,43 +1,27 @@ -import 'dart:async'; import 'dart:core'; import 'dart:core' as core; import 'dart:io'; import 'dart:math'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:hive/hive.dart'; -import 'package:hive_test/hive_test.dart'; -import 'package:mockito/annotations.dart'; -import 'package:mockito/mockito.dart'; - -import 'package:cw_core/monero_amount_format.dart'; import 'package:cw_core/node.dart'; -import 'package:cw_core/pending_transaction.dart'; import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_credentials.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_service.dart'; import 'package:cw_core/wallet_type.dart'; -import 'package:cw_monero/api/wallet.dart'; -import 'package:cw_monero/api/wallet_manager.dart' as monero_wallet_manager; -import 'package:cw_monero/pending_monero_transaction.dart'; import 'package:cw_monero/monero_wallet.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_libmonero/core/key_service.dart'; import 'package:flutter_libmonero/core/wallet_creation_service.dart'; -import 'package:flutter_libmonero/view_model/send/output.dart'; import 'package:flutter_libmonero/monero/monero.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:flutter_test/flutter_test.dart'; import 'package:hive/hive.dart'; +import 'package:hive_test/hive_test.dart'; +import 'package:mockito/annotations.dart'; import 'package:path_provider/path_provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; - import 'package:stackwallet/services/wallets.dart'; - -import 'dart:developer' as developer; +import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; // TODO trim down to the minimum imports above @@ -76,12 +60,6 @@ void main() async { dirPath: ''); late WalletCredentials credentials; - WidgetsFlutterBinding.ensureInitialized(); - Directory appDir = (await getApplicationDocumentsDirectory()); - if (Platform.isIOS) { - appDir = (await getLibraryDirectory()); - } - monero.onStartup(); bool hiveAdaptersRegistered = false; @@ -101,7 +79,8 @@ void main() async { await wallets.put('currentWalletName', name); _walletInfoSource = await Hive.openBox(WalletInfo.boxName); - walletService = monero.createMoneroWalletService(_walletInfoSource); + walletService = monero + .createMoneroWalletService(_walletInfoSource as Box); } try { diff --git a/test/services/coins/wownero/wownero_wallet_test.dart b/test/services/coins/wownero/wownero_wallet_test.dart index 87229a63a..c58654b4b 100644 --- a/test/services/coins/wownero/wownero_wallet_test.dart +++ b/test/services/coins/wownero/wownero_wallet_test.dart @@ -1,38 +1,26 @@ -import 'dart:async'; import 'dart:core'; import 'dart:core' as core; import 'dart:io'; import 'dart:math'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:hive/hive.dart'; -import 'package:hive_test/hive_test.dart'; -import 'package:mockito/annotations.dart'; -import 'package:mockito/mockito.dart'; - -import 'package:cw_core/monero_amount_format.dart'; import 'package:cw_core/node.dart'; -import 'package:cw_core/pending_transaction.dart'; import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_credentials.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_service.dart'; import 'package:cw_core/wallet_type.dart'; -import 'package:cw_wownero/api/wallet.dart'; -import 'package:cw_wownero/pending_wownero_transaction.dart'; import 'package:cw_wownero/wownero_wallet.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_libmonero/core/key_service.dart'; import 'package:flutter_libmonero/core/wallet_creation_service.dart'; -import 'package:flutter_libmonero/view_model/send/output.dart'; import 'package:flutter_libmonero/wownero/wownero.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; +import 'package:flutter_test/flutter_test.dart'; import 'package:hive/hive.dart'; +import 'package:hive_test/hive_test.dart'; +import 'package:mockito/annotations.dart'; import 'package:path_provider/path_provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'wownero_wallet_test_data.dart'; @@ -67,12 +55,6 @@ void main() async { dirPath: ''); late WalletCredentials credentials; - WidgetsFlutterBinding.ensureInitialized(); - Directory appDir = (await getApplicationDocumentsDirectory()); - if (Platform.isIOS) { - appDir = (await getLibraryDirectory()); - } - wownero.onStartup(); bool hiveAdaptersRegistered = false; @@ -92,7 +74,8 @@ void main() async { await wallets.put('currentWalletName', name); _walletInfoSource = await Hive.openBox(WalletInfo.boxName); - walletService = wownero.createWowneroWalletService(_walletInfoSource); + walletService = wownero + .createWowneroWalletService(_walletInfoSource as Box); } bool hasThrown = false; From 24bdc100fb479843132f8f6e577f69cf62341323 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Thu, 10 Nov 2022 15:49:19 -0600 Subject: [PATCH 71/76] add address validation tests --- crypto_plugins/flutter_libmonero | 2 +- .../coins/monero/monero_wallet_test.dart | 22 ++++++++++++++++++- .../coins/wownero/wownero_wallet_test.dart | 12 ++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/crypto_plugins/flutter_libmonero b/crypto_plugins/flutter_libmonero index 8a8c88cda..2da774385 160000 --- a/crypto_plugins/flutter_libmonero +++ b/crypto_plugins/flutter_libmonero @@ -1 +1 @@ -Subproject commit 8a8c88cdade6fe18529deea410f862b125167a3b +Subproject commit 2da77438527732dfaa5398aa391eab5253dabe19 diff --git a/test/services/coins/monero/monero_wallet_test.dart b/test/services/coins/monero/monero_wallet_test.dart index d54959ab2..4c4daa9ac 100644 --- a/test/services/coins/monero/monero_wallet_test.dart +++ b/test/services/coins/monero/monero_wallet_test.dart @@ -92,7 +92,7 @@ void main() async { _walletInfoSource = await Hive.openBox(WalletInfo.boxName); walletService = monero.createMoneroWalletService(_walletInfoSource); - group("Mainnet tests", () { + group("Mnemonic recovery", () { setUp(() async { try { // if (name?.isEmpty ?? true) { @@ -133,6 +133,26 @@ void main() async { } }); + test("Test address validation", () async { // TODO I'd like to refactor/separate this out so I can test addresses alone, without having to first create a wallet. + final wallet = await _walletCreationService.restoreFromSeed(credentials); + walletBase = wallet as MoneroWalletBase; + + expect( + await walletBase!.validateAddress(''), false); + expect( + await walletBase!.validateAddress('4AeRgkWZsMJhAWKMeCZ3h4ZSPnAcW5VBtRFyLd6gBEf6GgJU2FHXDA6i1DnQTd6h8R3VU5AkbGcWSNhtSwNNPgaD48gp4nn'), true); + expect( + await walletBase!.validateAddress('4asdfkWZsMJhAWKMeCZ3h4ZSPnAcW5VBtRFyLd6gBEf6GgJU2FHXDA6i1DnQTd6h8R3VU5AkbGcWSNhtSwNNPgaD48gpjkl'), false); + expect( + await walletBase!.validateAddress('8AeRgkWZsMJhAWKMeCZ3h4ZSPnAcW5VBtRFyLd6gBEf6GgJU2FHXDA6i1DnQTd6h8R3VU5AkbGcWSNhtSwNNPgaD48gp4nn'), false); + expect( + await walletBase!.validateAddress('84kYPuZ1eaVKGQhf26QPNWbSLQG16BywXdLYYShVrPNMLAUAWce5vcpRc78FxwRphrG6Cda7faCKdUMr8fUCH3peHPenvHy'), true); + expect( + await walletBase!.validateAddress('8asdfuZ1eaVKGQhf26QPNWbSLQG16BywXdLYYShVrPNMLAUAWce5vcpRc78FxwRphrG6Cda7faCKdUMr8fUCH3peHPenjkl'), false); + expect( + await walletBase!.validateAddress('44kYPuZ1eaVKGQhf26QPNWbSLQG16BywXdLYYShVrPNMLAUAWce5vcpRc78FxwRphrG6Cda7faCKdUMr8fUCH3peHPenvHy'), false); + }); + test("Test mainnet address generation from seed", () async { final wallet = await // _walletCreationService.create(credentials); diff --git a/test/services/coins/wownero/wownero_wallet_test.dart b/test/services/coins/wownero/wownero_wallet_test.dart index 660bc1438..8daa91755 100644 --- a/test/services/coins/wownero/wownero_wallet_test.dart +++ b/test/services/coins/wownero/wownero_wallet_test.dart @@ -122,6 +122,18 @@ void main() async { expect(hasThrown, false); }); + test("Test address validation", () async { // TODO I'd like to refactor/separate this out so I can test addresses alone, without having to first create a wallet. + final wallet = await _walletCreationService.restoreFromSeed(credentials); + walletBase = wallet as WowneroWalletBase; + + expect( + await walletBase!.validateAddress(''), false); + expect( + await walletBase!.validateAddress('Wo3jmHvTMLwE6h29fpgcb8PbJSpaKuqM7XTXVfiiu8bLCZsJvrQCbQSJR48Vo3BWNQKsMsXZ4VixndXTH25QtorC27NCjmsEi'), true); + expect( + await walletBase!.validateAddress('WasdfHvTMLwE6h29fpgcb8PbJSpaKuqM7XTXVfiiu8bLCZsJvrQCbQSJR48Vo3BWNQKsMsXZ4VixndXTH25QtorC27NCjmjkl'), false); + }); + test("Wownero 14 word seed address generation", () async { final wallet = await _walletCreationService.create(credentials); // TODO validate mnemonic From 5f106efa4bbd541561f2edd1e85275ff000f66f9 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Thu, 10 Nov 2022 15:11:17 -0700 Subject: [PATCH 72/76] desktop edit auto backup and enabled/disabled button --- assets/svg/enabled-button.svg | 4 ++ .../backup_and_restore_settings.dart | 66 ++++++++++++++----- .../create_auto_backup.dart | 4 +- lib/utilities/assets.dart | 1 + pubspec.yaml | 1 + 5 files changed, 59 insertions(+), 17 deletions(-) create mode 100644 assets/svg/enabled-button.svg diff --git a/assets/svg/enabled-button.svg b/assets/svg/enabled-button.svg new file mode 100644 index 000000000..a26359e81 --- /dev/null +++ b/assets/svg/enabled-button.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart index a444f4b51..3ea6cea6c 100644 --- a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart @@ -5,6 +5,7 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:intl/intl.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart'; +import 'package:stackwallet/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart'; import 'package:stackwallet/pages_desktop_specific/home/settings_menu/backup_and_restore/enable_backup_dialog.dart'; import 'package:stackwallet/providers/global/locale_provider.dart'; import 'package:stackwallet/providers/global/prefs_provider.dart'; @@ -36,7 +37,6 @@ class BackupRestoreSettings extends ConsumerStatefulWidget { class _BackupRestoreSettings extends ConsumerState { late bool createBackup = false; late bool restoreBackup = false; - // late bool isEnabledAutoBackup; final toggleController = DSBController(); @@ -91,6 +91,17 @@ class _BackupRestoreSettings extends ConsumerState { ); } + Future createAutoBackup() async { + await showDialog( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (context) { + return CreateAutoBackup(); + }, + ); + } + Future attemptDisable() async { final result = await showDialog( context: context, @@ -208,10 +219,25 @@ class _BackupRestoreSettings extends ConsumerState { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SvgPicture.asset( - Assets.svg.backupAuto, - width: 48, - height: 48, + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SvgPicture.asset( + Assets.svg.backupAuto, + width: 48, + height: 48, + ), + isEnabledAutoBackup + ? SvgPicture.asset( + Assets.svg.enableButton, + ) + : SvgPicture.asset( + Assets.svg.disableButton, + ), + ], + ), ), Center( child: Row( @@ -338,7 +364,9 @@ class _BackupRestoreSettings extends ConsumerState { desktopMed: true, width: 190, label: "Edit auto backup", - onPressed: () {}, + onPressed: () { + createAutoBackup(); + }, ), ], ) @@ -362,11 +390,14 @@ class _BackupRestoreSettings extends ConsumerState { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SvgPicture.asset( - Assets.svg.backupAdd, - width: 48, - height: 48, - alignment: Alignment.topLeft, + Padding( + padding: const EdgeInsets.all(8.0), + child: SvgPicture.asset( + Assets.svg.backupAdd, + width: 48, + height: 48, + alignment: Alignment.topLeft, + ), ), Center( child: Row( @@ -441,11 +472,14 @@ class _BackupRestoreSettings extends ConsumerState { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SvgPicture.asset( - Assets.svg.backupRestore, - width: 48, - height: 48, - alignment: Alignment.topLeft, + Padding( + padding: const EdgeInsets.all(8.0), + child: SvgPicture.asset( + Assets.svg.backupRestore, + width: 48, + height: 48, + alignment: Alignment.topLeft, + ), ), Center( child: Row( diff --git a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart index e804071cc..acd0e689c 100644 --- a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart +++ b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart @@ -578,7 +578,9 @@ class _CreateAutoBackup extends ConsumerState { label: "Cancel", onPressed: () { int count = 0; - Navigator.of(context).popUntil((_) => count++ >= 2); + !isEnabledAutoBackup + ? Navigator.of(context).popUntil((_) => count++ >= 2) + : Navigator.of(context).pop(); }, ), ), diff --git a/lib/utilities/assets.dart b/lib/utilities/assets.dart index b0c6b3bf9..f853a00d8 100644 --- a/lib/utilities/assets.dart +++ b/lib/utilities/assets.dart @@ -69,6 +69,7 @@ class _SVG { String get circleLanguage => "assets/svg/language-circle.svg"; String get circleDollarSign => "assets/svg/dollar-sign-circle.svg"; String get circleLock => "assets/svg/lock-circle.svg"; + String get enableButton => "assets/svg/enabled-button.svg"; String get disableButton => "assets/svg/Button.svg"; String get polygon => "assets/svg/Polygon.svg"; String get personaIncognito => "assets/svg/persona-incognito-1.svg"; diff --git a/pubspec.yaml b/pubspec.yaml index 19d38ca4f..8b03cd57e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -298,6 +298,7 @@ flutter: - assets/svg/persona-easy-1.svg - assets/svg/persona-incognito-1.svg - assets/svg/Button.svg + - assets/svg/enabled-button.svg - assets/svg/lock-circle.svg - assets/svg/dollar-sign-circle.svg - assets/svg/language-circle.svg From a6172f90a9e5024aad844be63e8952bdf98ae07d Mon Sep 17 00:00:00 2001 From: sneurlax Date: Thu, 10 Nov 2022 16:21:58 -0600 Subject: [PATCH 73/76] cherrypick Julian's test changes --- .../coins/monero/monero_wallet_test.dart | 57 ++++++++----------- .../coins/wownero/wownero_wallet_test.dart | 53 ++++++++--------- 2 files changed, 46 insertions(+), 64 deletions(-) diff --git a/test/services/coins/monero/monero_wallet_test.dart b/test/services/coins/monero/monero_wallet_test.dart index 4c4daa9ac..34024640f 100644 --- a/test/services/coins/monero/monero_wallet_test.dart +++ b/test/services/coins/monero/monero_wallet_test.dart @@ -1,43 +1,27 @@ -import 'dart:async'; import 'dart:core'; import 'dart:core' as core; import 'dart:io'; import 'dart:math'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:hive/hive.dart'; -import 'package:hive_test/hive_test.dart'; -import 'package:mockito/annotations.dart'; -import 'package:mockito/mockito.dart'; - -import 'package:cw_core/monero_amount_format.dart'; import 'package:cw_core/node.dart'; -import 'package:cw_core/pending_transaction.dart'; import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_credentials.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_service.dart'; import 'package:cw_core/wallet_type.dart'; -import 'package:cw_monero/api/wallet.dart'; -import 'package:cw_monero/api/wallet_manager.dart' as monero_wallet_manager; -import 'package:cw_monero/pending_monero_transaction.dart'; import 'package:cw_monero/monero_wallet.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_libmonero/core/key_service.dart'; import 'package:flutter_libmonero/core/wallet_creation_service.dart'; -import 'package:flutter_libmonero/view_model/send/output.dart'; import 'package:flutter_libmonero/monero/monero.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:flutter_test/flutter_test.dart'; import 'package:hive/hive.dart'; +import 'package:hive_test/hive_test.dart'; +import 'package:mockito/annotations.dart'; import 'package:path_provider/path_provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; - import 'package:stackwallet/services/wallets.dart'; - -import 'dart:developer' as developer; +import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; // TODO trim down to the minimum imports above @@ -76,24 +60,29 @@ void main() async { dirPath: ''); late WalletCredentials credentials; - WidgetsFlutterBinding.ensureInitialized(); - Directory appDir = (await getApplicationDocumentsDirectory()); - if (Platform.isIOS) { - appDir = (await getLibraryDirectory()); - } - await Hive.close(); - Hive.init(appDir.path); - Hive.registerAdapter(NodeAdapter()); - Hive.registerAdapter(WalletInfoAdapter()); - Hive.registerAdapter(WalletTypeAdapter()); - Hive.registerAdapter(UnspentCoinsInfoAdapter()); - monero.onStartup(); - _walletInfoSource = await Hive.openBox(WalletInfo.boxName); - walletService = monero.createMoneroWalletService(_walletInfoSource); + + bool hiveAdaptersRegistered = false; group("Mnemonic recovery", () { setUp(() async { + await setUpTestHive(); + if (!hiveAdaptersRegistered) { + hiveAdaptersRegistered = true; + + Hive.registerAdapter(NodeAdapter()); + Hive.registerAdapter(WalletInfoAdapter()); + Hive.registerAdapter(WalletTypeAdapter()); + Hive.registerAdapter(UnspentCoinsInfoAdapter()); + + final wallets = await Hive.openBox('wallets'); + await wallets.put('currentWalletName', name); + + _walletInfoSource = await Hive.openBox(WalletInfo.boxName); + walletService = monero + .createMoneroWalletService(_walletInfoSource as Box); + } + try { // if (name?.isEmpty ?? true) { // name = await generateName(); diff --git a/test/services/coins/wownero/wownero_wallet_test.dart b/test/services/coins/wownero/wownero_wallet_test.dart index 8daa91755..8ad730e13 100644 --- a/test/services/coins/wownero/wownero_wallet_test.dart +++ b/test/services/coins/wownero/wownero_wallet_test.dart @@ -1,38 +1,26 @@ -import 'dart:async'; import 'dart:core'; import 'dart:core' as core; import 'dart:io'; import 'dart:math'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:hive/hive.dart'; -import 'package:hive_test/hive_test.dart'; -import 'package:mockito/annotations.dart'; -import 'package:mockito/mockito.dart'; - -import 'package:cw_core/monero_amount_format.dart'; import 'package:cw_core/node.dart'; -import 'package:cw_core/pending_transaction.dart'; import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_credentials.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_service.dart'; import 'package:cw_core/wallet_type.dart'; -import 'package:cw_wownero/api/wallet.dart'; -import 'package:cw_wownero/pending_wownero_transaction.dart'; import 'package:cw_wownero/wownero_wallet.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_libmonero/core/key_service.dart'; import 'package:flutter_libmonero/core/wallet_creation_service.dart'; -import 'package:flutter_libmonero/view_model/send/output.dart'; import 'package:flutter_libmonero/wownero/wownero.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; +import 'package:flutter_test/flutter_test.dart'; import 'package:hive/hive.dart'; +import 'package:hive_test/hive_test.dart'; +import 'package:mockito/annotations.dart'; import 'package:path_provider/path_provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'wownero_wallet_test_data.dart'; @@ -67,24 +55,29 @@ void main() async { dirPath: ''); late WalletCredentials credentials; - WidgetsFlutterBinding.ensureInitialized(); - Directory appDir = (await getApplicationDocumentsDirectory()); - if (Platform.isIOS) { - appDir = (await getLibraryDirectory()); - } - await Hive.close(); - Hive.init(appDir.path); - Hive.registerAdapter(NodeAdapter()); - Hive.registerAdapter(WalletInfoAdapter()); - Hive.registerAdapter(WalletTypeAdapter()); - Hive.registerAdapter(UnspentCoinsInfoAdapter()); - wownero.onStartup(); - _walletInfoSource = await Hive.openBox(WalletInfo.boxName); - walletService = wownero.createWowneroWalletService(_walletInfoSource); + + bool hiveAdaptersRegistered = false; group("Wownero 14 word seed generation", () { setUp(() async { + await setUpTestHive(); + if (!hiveAdaptersRegistered) { + hiveAdaptersRegistered = true; + + Hive.registerAdapter(NodeAdapter()); + Hive.registerAdapter(WalletInfoAdapter()); + Hive.registerAdapter(WalletTypeAdapter()); + Hive.registerAdapter(UnspentCoinsInfoAdapter()); + + final wallets = await Hive.openBox('wallets'); + await wallets.put('currentWalletName', name); + + _walletInfoSource = await Hive.openBox(WalletInfo.boxName); + walletService = wownero + .createWowneroWalletService(_walletInfoSource as Box); + } + bool hasThrown = false; try { name = 'namee${Random().nextInt(10000000)}'; From f61b3857e6e40a45683f2233a34eb6f2087a2463 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Thu, 10 Nov 2022 17:03:01 -0600 Subject: [PATCH 74/76] add more address validation to tests --- .../coins/monero/monero_wallet_test.dart | 69 ++++++++----------- .../coins/wownero/wownero_wallet_test.dart | 35 ++++------ 2 files changed, 43 insertions(+), 61 deletions(-) diff --git a/test/services/coins/monero/monero_wallet_test.dart b/test/services/coins/monero/monero_wallet_test.dart index 6b134f302..79bf98a41 100644 --- a/test/services/coins/monero/monero_wallet_test.dart +++ b/test/services/coins/monero/monero_wallet_test.dart @@ -60,19 +60,11 @@ void main() async { dirPath: ''); late WalletCredentials credentials; - WidgetsFlutterBinding.ensureInitialized(); - Directory appDir = (await getApplicationDocumentsDirectory()); - if (Platform.isIOS) { - appDir = (await getLibraryDirectory()); - } - monero.onStartup(); bool hiveAdaptersRegistered = false; - bool hiveAdaptersRegistered = false; - - group("Mnemonic recovery", () { + group("Mainnet tests", () { setUp(() async { await setUpTestHive(); if (!hiveAdaptersRegistered) { @@ -87,7 +79,8 @@ void main() async { await wallets.put('currentWalletName', name); _walletInfoSource = await Hive.openBox(WalletInfo.boxName); - walletService = monero.createMoneroWalletService(_walletInfoSource); + walletService = monero + .createMoneroWalletService(_walletInfoSource as Box); } try { @@ -97,12 +90,12 @@ void main() async { final dirPath = await pathForWalletDir(name: name, type: type); path = await pathForWallet(name: name, type: type); credentials = - // // creating a new wallet - // monero.createMoneroNewWalletCredentials( - // name: name, language: "English"); - // restoring a previous wallet - monero.createMoneroRestoreWalletFromSeedCredentials( - name: name, height: 2580000, mnemonic: testMnemonic); + // // creating a new wallet + // monero.createMoneroNewWalletCredentials( + // name: name, language: "English"); + // restoring a previous wallet + monero.createMoneroRestoreWalletFromSeedCredentials( + name: name, height: 2580000, mnemonic: testMnemonic); walletInfo = WalletInfo.external( id: WalletBase.idFor(name, type), @@ -129,30 +122,10 @@ void main() async { } }); - test("Test address validation", () async { // TODO I'd like to refactor/separate this out so I can test addresses alone, without having to first create a wallet. - final wallet = await _walletCreationService.restoreFromSeed(credentials); - walletBase = wallet as MoneroWalletBase; - - expect( - await walletBase!.validateAddress(''), false); - expect( - await walletBase!.validateAddress('4AeRgkWZsMJhAWKMeCZ3h4ZSPnAcW5VBtRFyLd6gBEf6GgJU2FHXDA6i1DnQTd6h8R3VU5AkbGcWSNhtSwNNPgaD48gp4nn'), true); - expect( - await walletBase!.validateAddress('4asdfkWZsMJhAWKMeCZ3h4ZSPnAcW5VBtRFyLd6gBEf6GgJU2FHXDA6i1DnQTd6h8R3VU5AkbGcWSNhtSwNNPgaD48gpjkl'), false); - expect( - await walletBase!.validateAddress('8AeRgkWZsMJhAWKMeCZ3h4ZSPnAcW5VBtRFyLd6gBEf6GgJU2FHXDA6i1DnQTd6h8R3VU5AkbGcWSNhtSwNNPgaD48gp4nn'), false); - expect( - await walletBase!.validateAddress('84kYPuZ1eaVKGQhf26QPNWbSLQG16BywXdLYYShVrPNMLAUAWce5vcpRc78FxwRphrG6Cda7faCKdUMr8fUCH3peHPenvHy'), true); - expect( - await walletBase!.validateAddress('8asdfuZ1eaVKGQhf26QPNWbSLQG16BywXdLYYShVrPNMLAUAWce5vcpRc78FxwRphrG6Cda7faCKdUMr8fUCH3peHPenjkl'), false); - expect( - await walletBase!.validateAddress('44kYPuZ1eaVKGQhf26QPNWbSLQG16BywXdLYYShVrPNMLAUAWce5vcpRc78FxwRphrG6Cda7faCKdUMr8fUCH3peHPenvHy'), false); - }); - test("Test mainnet address generation from seed", () async { final wallet = await - // _walletCreationService.create(credentials); - _walletCreationService.restoreFromSeed(credentials); + // _walletCreationService.create(credentials); + _walletCreationService.restoreFromSeed(credentials); walletInfo.address = wallet.walletAddresses.address; //print(walletInfo.address); @@ -161,6 +134,9 @@ void main() async { walletBase = wallet as MoneroWalletBase; //print("${walletBase?.seed}"); + expect( + await walletBase!.validateAddress(walletInfo.address ?? ''), true); + // print(walletBase); // loggerPrint(walletBase.toString()); // loggerPrint("name: ${walletBase!.name} seed: ${walletBase!.seed} id: " @@ -180,6 +156,21 @@ void main() async { await walletBase!.getTransactionAddress(1, 1), mainnetTestData[1][1]); expect( await walletBase!.getTransactionAddress(1, 2), mainnetTestData[1][2]); + + expect( + await walletBase!.validateAddress(''), false); + expect( + await walletBase!.validateAddress('4AeRgkWZsMJhAWKMeCZ3h4ZSPnAcW5VBtRFyLd6gBEf6GgJU2FHXDA6i1DnQTd6h8R3VU5AkbGcWSNhtSwNNPgaD48gp4nn'), true); + expect( + await walletBase!.validateAddress('4asdfkWZsMJhAWKMeCZ3h4ZSPnAcW5VBtRFyLd6gBEf6GgJU2FHXDA6i1DnQTd6h8R3VU5AkbGcWSNhtSwNNPgaD48gpjkl'), false); + expect( + await walletBase!.validateAddress('8AeRgkWZsMJhAWKMeCZ3h4ZSPnAcW5VBtRFyLd6gBEf6GgJU2FHXDA6i1DnQTd6h8R3VU5AkbGcWSNhtSwNNPgaD48gp4nn'), false); + expect( + await walletBase!.validateAddress('84kYPuZ1eaVKGQhf26QPNWbSLQG16BywXdLYYShVrPNMLAUAWce5vcpRc78FxwRphrG6Cda7faCKdUMr8fUCH3peHPenvHy'), true); + expect( + await walletBase!.validateAddress('8asdfuZ1eaVKGQhf26QPNWbSLQG16BywXdLYYShVrPNMLAUAWce5vcpRc78FxwRphrG6Cda7faCKdUMr8fUCH3peHPenjkl'), false); + expect( + await walletBase!.validateAddress('44kYPuZ1eaVKGQhf26QPNWbSLQG16BywXdLYYShVrPNMLAUAWce5vcpRc78FxwRphrG6Cda7faCKdUMr8fUCH3peHPenvHy'), false); }); }); /* @@ -238,6 +229,6 @@ Future pathForWalletDir( } Future pathForWallet( - {required String name, required WalletType type}) async => + {required String name, required WalletType type}) async => await pathForWalletDir(name: name, type: type) .then((path) => path + '/$name'); diff --git a/test/services/coins/wownero/wownero_wallet_test.dart b/test/services/coins/wownero/wownero_wallet_test.dart index 71c11677a..78a9c56c1 100644 --- a/test/services/coins/wownero/wownero_wallet_test.dart +++ b/test/services/coins/wownero/wownero_wallet_test.dart @@ -55,12 +55,6 @@ void main() async { dirPath: ''); late WalletCredentials credentials; - WidgetsFlutterBinding.ensureInitialized(); - Directory appDir = (await getApplicationDocumentsDirectory()); - if (Platform.isIOS) { - appDir = (await getLibraryDirectory()); - } - wownero.onStartup(); bool hiveAdaptersRegistered = false; @@ -80,7 +74,8 @@ void main() async { await wallets.put('currentWalletName', name); _walletInfoSource = await Hive.openBox(WalletInfo.boxName); - walletService = wownero.createWowneroWalletService(_walletInfoSource); + walletService = wownero + .createWowneroWalletService(_walletInfoSource as Box); } bool hasThrown = false; @@ -120,18 +115,6 @@ void main() async { expect(hasThrown, false); }); - test("Test address validation", () async { // TODO I'd like to refactor/separate this out so I can test addresses alone, without having to first create a wallet. - final wallet = await _walletCreationService.restoreFromSeed(credentials); - walletBase = wallet as WowneroWalletBase; - - expect( - await walletBase!.validateAddress(''), false); - expect( - await walletBase!.validateAddress('Wo3jmHvTMLwE6h29fpgcb8PbJSpaKuqM7XTXVfiiu8bLCZsJvrQCbQSJR48Vo3BWNQKsMsXZ4VixndXTH25QtorC27NCjmsEi'), true); - expect( - await walletBase!.validateAddress('WasdfHvTMLwE6h29fpgcb8PbJSpaKuqM7XTXVfiiu8bLCZsJvrQCbQSJR48Vo3BWNQKsMsXZ4VixndXTH25QtorC27NCjmjkl'), false); - }); - test("Wownero 14 word seed address generation", () async { final wallet = await _walletCreationService.create(credentials); // TODO validate mnemonic @@ -143,13 +126,21 @@ void main() async { walletBase?.close(); walletBase = wallet as WowneroWalletBase; - // TODO validate - //expect(walletInfo.address, mainnetTestData14[0][0]); + expect( + await walletBase!.validateAddress(wallet.walletAddresses.address ?? ''), true); } catch (_) { hasThrown = true; } expect(hasThrown, false); + // Address validation + expect( + await walletBase!.validateAddress(''), false); + expect( + await walletBase!.validateAddress('Wo3jmHvTMLwE6h29fpgcb8PbJSpaKuqM7XTXVfiiu8bLCZsJvrQCbQSJR48Vo3BWNQKsMsXZ4VixndXTH25QtorC27NCjmsEi'), true); + expect( + await walletBase!.validateAddress('WasdfHvTMLwE6h29fpgcb8PbJSpaKuqM7XTXVfiiu8bLCZsJvrQCbQSJR48Vo3BWNQKsMsXZ4VixndXTH25QtorC27NCjmjkl'), false); + walletBase?.close(); walletBase = wallet as WowneroWalletBase; }); @@ -376,6 +367,6 @@ Future pathForWalletDir( } Future pathForWallet( - {required String name, required WalletType type}) async => + {required String name, required WalletType type}) async => await pathForWalletDir(name: name, type: type) .then((path) => path + '/$name'); From 7646cbe8cb28e6902c65fe181e94ea849033b872 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Fri, 11 Nov 2022 08:54:38 -0800 Subject: [PATCH 75/76] Handle some bash errors in build script. --- scripts/linux/build_secure_storage_deps.sh | 34 +++++++++++++++------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/scripts/linux/build_secure_storage_deps.sh b/scripts/linux/build_secure_storage_deps.sh index 5ed032b1a..378f7a604 100755 --- a/scripts/linux/build_secure_storage_deps.sh +++ b/scripts/linux/build_secure_storage_deps.sh @@ -1,25 +1,37 @@ #!/bin/bash LINUX_DIRECTORY=$(pwd) -mkdir build +mkdir -p build # Build JsonCPP -cd build -git clone https://github.com/open-source-parsers/jsoncpp.git -cd jsoncpp +cd build || exit +if ! [ -x "$(command -v git)" ]; then + echo 'Error: git is not installed.' >&2 + exit 1 +fi +git -C jsoncpp pull || git clone https://github.com/open-source-parsers/jsoncpp.git jsoncpp +cd jsoncpp || exit git checkout 1.7.4 -mkdir build -cd build +mkdir -p build +cd build || exit cmake -DCMAKE_BUILD_TYPE=release -DBUILD_STATIC_LIBS=ON -DBUILD_SHARED_LIBS=ON -DARCHIVE_INSTALL_DIR=. -G "Unix Makefiles" .. -make -j$(nproc) +make -j"$(nproc)" -cd $LINUX_DIRECTORY +cd "$LINUX_DIRECTORY" || exit # Build libSecret # sudo apt install meson libgirepository1.0-dev valac xsltproc gi-docgen docbook-xsl # sudo apt install python3-pip #pip3 install --user meson --upgrade # pip3 install --user gi-docgen -cd build -git clone https://gitlab.gnome.org/GNOME/libsecret.git -cd libsecret +cd build || exit +git -C libsecret pull || git clone https://gitlab.gnome.org/GNOME/libsecret.git libsecret +cd libsecret || exit +if ! [ -x "$(command -v meson)" ]; then + echo 'Error: meson is not installed.' >&2 + exit 1 +fi meson _build +if ! [ -x "$(command -v ninja)" ]; then + echo 'Error: ninja is not installed.' >&2 + exit 1 +fi ninja -C _build From cd1d6a5bc7af6f9103b9ddd2627a152ade2e45c1 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Fri, 11 Nov 2022 10:35:12 -0700 Subject: [PATCH 76/76] v1.5.17 build 89 --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 8b03cd57e..b7947a58d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,7 +11,7 @@ description: Stack Wallet # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.5.16+88 +version: 1.5.17+89 environment: sdk: ">=2.17.0 <3.0.0"