From d3d316c14db8c11f210899a6332eb079a2fa1343 Mon Sep 17 00:00:00 2001 From: Czarek Nakamoto Date: Fri, 31 Jan 2025 15:04:01 +0100 Subject: [PATCH 01/13] one class per file, minor cleanups --- .fvmrc | 4 +- .gitignore | 5 +- .vscode/settings.json | 3 + ios/.gitignore | 1 + lib/coins/abstract.dart | 160 ------------- lib/coins/abstract/coin.dart | 32 +++ lib/coins/abstract/coin_exception.dart | 11 + lib/coins/abstract/coin_strings.dart | 12 + lib/coins/abstract/coin_wallet.dart | 45 ++++ lib/coins/abstract/coin_wallet_info.dart | 47 ++++ lib/coins/abstract/wallet_seed_detail.dart | 13 ++ lib/coins/list.dart | 2 +- lib/coins/monero/coin.dart | 107 +-------- lib/coins/monero/monero_strings.dart | 21 ++ lib/coins/monero/monero_wallet_info.dart | 86 +++++++ lib/coins/monero/wallet.dart | 8 +- lib/coins/types.dart | 6 + lib/main.dart | 2 +- lib/utils/alert.dart | 99 -------- lib/utils/alerts/basic.dart | 38 +++ lib/utils/alerts/widget.dart | 31 +++ lib/utils/alerts/widget_minimal.dart | 32 +++ lib/utils/call_throwable.dart | 4 +- lib/utils/config.dart | 2 +- lib/utils/form/abstract_form_element.dart | 5 + lib/utils/form/abstract_value_outcome.dart | 6 + lib/utils/form/default_validator.dart | 3 + .../flutter_secure_storage_value_outcome.dart | 72 ++++++ lib/utils/form/pin_form_element.dart | 74 ++++++ lib/utils/form/plain_value_outcome.dart | 12 + .../form/single_choice_form_element.dart | 16 ++ lib/utils/form/string_form_element.dart | 31 +++ lib/utils/urqr.dart | 62 +++++ .../barcode_scanner_view_model.dart | 70 +----- lib/view_model/create_wallet_view_model.dart | 216 +----------------- lib/view_model/home_screen_view_model.dart | 2 +- lib/view_model/open_wallet_view_model.dart | 5 +- lib/view_model/receive_view_model.dart | 2 +- .../security_backup_view_model.dart | 6 +- .../unconfirmed_transaction_view_model.dart | 2 +- lib/view_model/wallet_edit_view_model.dart | 7 +- lib/view_model/wallet_home_view_model.dart | 3 +- lib/views/barcode_scanner.dart | 2 +- lib/views/create_wallet.dart | 12 +- lib/views/home_screen.dart | 2 +- lib/views/new_wallet_info.dart | 4 +- lib/views/open_wallet.dart | 4 +- lib/views/receive.dart | 2 +- lib/views/security_backup.dart | 8 +- lib/views/settings.dart | 3 +- lib/views/wallet_edit.dart | 4 +- lib/views/wallet_home.dart | 4 +- lib/views/widgets/drawer_element.dart | 65 ------ lib/views/widgets/drawer_elements.dart | 67 ++++++ lib/{ => views}/widgets/form_builder.dart | 7 +- pubspec.lock | 48 ++-- 56 files changed, 829 insertions(+), 768 deletions(-) create mode 100644 .vscode/settings.json delete mode 100644 lib/coins/abstract.dart create mode 100644 lib/coins/abstract/coin.dart create mode 100644 lib/coins/abstract/coin_exception.dart create mode 100644 lib/coins/abstract/coin_strings.dart create mode 100644 lib/coins/abstract/coin_wallet.dart create mode 100644 lib/coins/abstract/coin_wallet_info.dart create mode 100644 lib/coins/abstract/wallet_seed_detail.dart create mode 100644 lib/coins/monero/monero_strings.dart create mode 100644 lib/coins/monero/monero_wallet_info.dart create mode 100644 lib/coins/types.dart delete mode 100644 lib/utils/alert.dart create mode 100644 lib/utils/alerts/basic.dart create mode 100644 lib/utils/alerts/widget.dart create mode 100644 lib/utils/alerts/widget_minimal.dart create mode 100644 lib/utils/form/abstract_form_element.dart create mode 100644 lib/utils/form/abstract_value_outcome.dart create mode 100644 lib/utils/form/default_validator.dart create mode 100644 lib/utils/form/flutter_secure_storage_value_outcome.dart create mode 100644 lib/utils/form/pin_form_element.dart create mode 100644 lib/utils/form/plain_value_outcome.dart create mode 100644 lib/utils/form/single_choice_form_element.dart create mode 100644 lib/utils/form/string_form_element.dart create mode 100644 lib/utils/urqr.dart create mode 100644 lib/views/widgets/drawer_elements.dart rename lib/{ => views}/widgets/form_builder.dart (96%) diff --git a/.fvmrc b/.fvmrc index 7395731..d7891c2 100644 --- a/.fvmrc +++ b/.fvmrc @@ -1,3 +1,3 @@ { - "flutter": "3.24.4" -} + "flutter": "3.27.3" +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 4c5dd11..a053f4e 100644 --- a/.gitignore +++ b/.gitignore @@ -46,4 +46,7 @@ app.*.map.json lib/l10n/*.dart lib/generated/ lib/gen/*.dart -missing_translations.txt \ No newline at end of file +missing_translations.txt + +# FVM Version Cache +.fvm/ \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..cd2c40b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "dart.flutterSdkPath": ".fvm/versions/3.27.3" +} \ No newline at end of file diff --git a/ios/.gitignore b/ios/.gitignore index eb2e981..3bfe9f3 100644 --- a/ios/.gitignore +++ b/ios/.gitignore @@ -35,3 +35,4 @@ Runner/GeneratedPluginRegistrant.* libwallet2_api_c.dylib MoneroWallet.framework/MoneroWallet +monero_libwallet2_api_c.dylib \ No newline at end of file diff --git a/lib/coins/abstract.dart b/lib/coins/abstract.dart deleted file mode 100644 index 0305ad5..0000000 --- a/lib/coins/abstract.dart +++ /dev/null @@ -1,160 +0,0 @@ -import 'dart:core'; - -import 'package:cupcake/coins/monero/coin.dart'; -import 'package:cupcake/l10n/app_localizations.dart'; -import 'package:cupcake/utils/config.dart'; -import 'package:cupcake/view_model/barcode_scanner_view_model.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:path/path.dart' as p; - -class CoinException implements Exception { - CoinException(this.exception, {this.details}); - - String exception; - String? details; - - @override - String toString() { - return "$exception\n$details"; - } -} - -typedef ProgressCallback = int Function({String? title, String? description}); - -enum Coins { monero, unknown } - -abstract class Coin { - Coins get type => Coins.unknown; - - CoinStrings get strings; - - bool get isEnabled; - - Future> get coinWallets; - - Future createNewWallet( - String walletName, - String walletPassword, { - ProgressCallback? progressCallback, - required bool? createWallet, - required String? seed, - required int? restoreHeight, - required String? primaryAddress, - required String? viewKey, - required String? spendKey, - required String? seedOffsetOrEncryption, - }); - - Future openWallet(CoinWalletInfo walletInfo, - {required String password}); -} - -abstract class CoinWalletInfo { - String get walletName; - - Coins get type => coin.type; - - Coin get coin; - - void openUI(BuildContext context); - - Future checkWalletPassword(String password); - - bool exists(); - - Future openWallet(BuildContext context, - {required String password}); - - Map toJson() { - return { - "typeIndex": type.index, - if (config.debug) "typeIndex__debug": type.toString(), - "walletName": p.basename(walletName), - }; - } - - static CoinWalletInfo? fromJson(Map? json) { - if (json == null) return null; - final type = Coins.values[json["typeIndex"] as int]; - final walletName = (json["walletName"] as String); - switch (type) { - case Coins.monero: - return MoneroWalletInfo(walletName); - case Coins.unknown: - throw UnimplementedError("unknown coin"); - } - } - - Future deleteWallet(); - - Future renameWallet(String newName); -} - -abstract class CoinStrings { - String get nameLowercase => "coin"; - String get nameCapitalized => "Coin"; - String get nameUppercase => "COIN"; - String get symbolLowercase => "coin"; - String get symbolUppercase => "COIN"; - String get nameFull => "$nameCapitalized ($symbolUppercase)"; - - SvgPicture get svg; -} - -enum WalletSeedDetailType { - text, - qr, -} - -class WalletSeedDetail { - WalletSeedDetail({ - required this.type, - required this.name, - required this.value, - }); - - final WalletSeedDetailType type; - final String name; - final String value; -} - -abstract class CoinWallet { - CoinWallet(); - - Coin get coin; - - Future handleUR(BuildContext context, URQRData ur) => - throw UnimplementedError(); - - bool get hasAccountSupport => false; - - bool get hasAddressesSupport => false; - - int getAccountsCount(); - - void setAccount(int accountIndex); - - int getAccountId(); - - int get addressIndex; - - String get getAccountLabel; - - String get getCurrentAddress; - - String get seed; - - String get primaryAddress; - - String get walletName; - - int getBalance(); - - String getBalanceString(); - - Future close(); - - Future> seedDetails(AppLocalizations L) => - throw UnimplementedError(); -} diff --git a/lib/coins/abstract/coin.dart b/lib/coins/abstract/coin.dart new file mode 100644 index 0000000..477cfa1 --- /dev/null +++ b/lib/coins/abstract/coin.dart @@ -0,0 +1,32 @@ +import 'package:cupcake/coins/abstract/coin_strings.dart'; +import 'package:cupcake/coins/abstract/coin_wallet.dart'; +import 'package:cupcake/coins/abstract/coin_wallet_info.dart'; +import 'package:cupcake/coins/types.dart'; + +enum Coins { monero, unknown } + +abstract class Coin { + Coins get type => Coins.unknown; + + CoinStrings get strings; + + bool get isEnabled; + + Future> get coinWallets; + + Future createNewWallet( + String walletName, + String walletPassword, { + ProgressCallback? progressCallback, + required bool? createWallet, + required String? seed, + required int? restoreHeight, + required String? primaryAddress, + required String? viewKey, + required String? spendKey, + required String? seedOffsetOrEncryption, + }); + + Future openWallet(CoinWalletInfo walletInfo, + {required String password}); +} diff --git a/lib/coins/abstract/coin_exception.dart b/lib/coins/abstract/coin_exception.dart new file mode 100644 index 0000000..3905589 --- /dev/null +++ b/lib/coins/abstract/coin_exception.dart @@ -0,0 +1,11 @@ +class CoinException implements Exception { + CoinException(this.exception, {this.details}); + + String exception; + String? details; + + @override + String toString() { + return "$exception\n$details"; + } +} diff --git a/lib/coins/abstract/coin_strings.dart b/lib/coins/abstract/coin_strings.dart new file mode 100644 index 0000000..df2dde6 --- /dev/null +++ b/lib/coins/abstract/coin_strings.dart @@ -0,0 +1,12 @@ +import 'package:flutter_svg/flutter_svg.dart'; + +abstract class CoinStrings { + String get nameLowercase; + String get nameCapitalized; + String get nameUppercase; + String get symbolLowercase; + String get symbolUppercase; + String get nameFull; + + SvgPicture get svg; +} diff --git a/lib/coins/abstract/coin_wallet.dart b/lib/coins/abstract/coin_wallet.dart new file mode 100644 index 0000000..428f958 --- /dev/null +++ b/lib/coins/abstract/coin_wallet.dart @@ -0,0 +1,45 @@ +import 'package:cupcake/coins/abstract/coin.dart'; +import 'package:cupcake/coins/abstract/wallet_seed_detail.dart'; +import 'package:cupcake/l10n/app_localizations.dart'; +import 'package:cupcake/utils/urqr.dart'; +import 'package:flutter/widgets.dart'; + +abstract class CoinWallet { + CoinWallet(); + + Coin get coin; + + Future handleUR(BuildContext context, URQRData ur) => + throw UnimplementedError(); + + bool get hasAccountSupport => false; + + bool get hasAddressesSupport => false; + + int getAccountsCount(); + + void setAccount(int accountIndex); + + int getAccountId(); + + int get addressIndex; + + String get getAccountLabel; + + String get getCurrentAddress; + + String get seed; + + String get primaryAddress; + + String get walletName; + + int getBalance(); + + String getBalanceString(); + + Future close(); + + Future> seedDetails(AppLocalizations L) => + throw UnimplementedError(); +} diff --git a/lib/coins/abstract/coin_wallet_info.dart b/lib/coins/abstract/coin_wallet_info.dart new file mode 100644 index 0000000..dabc358 --- /dev/null +++ b/lib/coins/abstract/coin_wallet_info.dart @@ -0,0 +1,47 @@ +import 'package:cupcake/coins/abstract/coin.dart'; +import 'package:cupcake/coins/abstract/coin_wallet.dart'; +import 'package:cupcake/coins/monero/monero_wallet_info.dart'; +import 'package:cupcake/utils/config.dart'; +import 'package:flutter/widgets.dart'; +import 'package:path/path.dart' as p; + +abstract class CoinWalletInfo { + String get walletName; + + Coins get type => coin.type; + + Coin get coin; + + void openUI(BuildContext context); + + Future checkWalletPassword(String password); + + bool exists(); + + Future openWallet(BuildContext context, + {required String password}); + + Map toJson() { + return { + "typeIndex": type.index, + if (config.debug) "typeIndex__debug": type.toString(), + "walletName": p.basename(walletName), + }; + } + + static CoinWalletInfo? fromJson(Map? json) { + if (json == null) return null; + final type = Coins.values[json["typeIndex"] as int]; + final walletName = (json["walletName"] as String); + switch (type) { + case Coins.monero: + return MoneroWalletInfo(walletName); + case Coins.unknown: + throw UnimplementedError("unknown coin"); + } + } + + Future deleteWallet(); + + Future renameWallet(String newName); +} diff --git a/lib/coins/abstract/wallet_seed_detail.dart b/lib/coins/abstract/wallet_seed_detail.dart new file mode 100644 index 0000000..142600a --- /dev/null +++ b/lib/coins/abstract/wallet_seed_detail.dart @@ -0,0 +1,13 @@ +import 'package:cupcake/coins/types.dart'; + +class WalletSeedDetail { + WalletSeedDetail({ + required this.type, + required this.name, + required this.value, + }); + + final WalletSeedDetailType type; + final String name; + final String value; +} diff --git a/lib/coins/list.dart b/lib/coins/list.dart index ca60b90..866583b 100644 --- a/lib/coins/list.dart +++ b/lib/coins/list.dart @@ -1,4 +1,4 @@ -import 'package:cupcake/coins/abstract.dart'; +import 'package:cupcake/coins/abstract/coin.dart'; import 'package:cupcake/coins/monero/coin.dart'; const moneroEnabled = bool.fromEnvironment("COIN_MONERO", defaultValue: true); diff --git a/lib/coins/monero/coin.dart b/lib/coins/monero/coin.dart index 31abd1b..c8bdc12 100644 --- a/lib/coins/monero/coin.dart +++ b/lib/coins/monero/coin.dart @@ -1,15 +1,18 @@ import 'dart:io'; -import 'package:cupcake/coins/abstract.dart'; +import 'package:cupcake/coins/abstract/coin.dart'; +import 'package:cupcake/coins/abstract/coin_exception.dart'; +import 'package:cupcake/coins/abstract/coin_strings.dart'; +import 'package:cupcake/coins/abstract/coin_wallet.dart'; +import 'package:cupcake/coins/abstract/coin_wallet_info.dart'; +import 'package:cupcake/coins/monero/monero_strings.dart'; +import 'package:cupcake/coins/monero/monero_wallet_info.dart'; import 'package:cupcake/coins/monero/wallet.dart'; +import 'package:cupcake/coins/types.dart'; import 'package:cupcake/utils/config.dart'; import 'package:cupcake/utils/filesystem.dart'; -import 'package:cupcake/views/open_wallet.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter_svg/flutter_svg.dart'; import 'package:monero/monero.dart' as monero; import 'package:path/path.dart' as p; -import 'package:cupcake/gen/assets.gen.dart'; import 'package:polyseed/polyseed.dart'; List wPtrList = []; @@ -315,97 +318,3 @@ class Monero implements Coin { static monero.WalletManager wmPtr = monero.WalletManagerFactory_getWalletManager(); } - -class MoneroStrings implements CoinStrings { - @override - String get nameLowercase => "monero"; - @override - String get nameCapitalized => "Monero"; - @override - String get nameUppercase => "MONERO"; - @override - String get symbolLowercase => "xmr"; - @override - String get symbolUppercase => "XMR"; - @override - String get nameFull => "$nameCapitalized ($symbolUppercase)"; - - @override - SvgPicture get svg => Assets.coins.xmr.svg(); -} - -class MoneroWalletInfo extends CoinWalletInfo { - MoneroWalletInfo(String walletName) - : _walletName = (() { - if (walletName == p.basename(walletName)) { - walletName = p.join(Monero.baseDir.path, walletName); - } - return walletName; - }()); - - @override - Coin get coin => Monero(); - - @override - Future checkWalletPassword(String password) async { - return monero.WalletManager_verifyWalletPassword( - Monero.wmPtr, - keysFileName: "$walletName.keys", - password: password, - noSpendKey: false, - kdfRounds: 0, - ); - } - - @override - String get walletName => _walletName; - - String _walletName; - - @override - Coins get type => coin.type; - - @override - void openUI(BuildContext context) { - OpenWallet.pushStatic(context, this); - } - - @override - Future openWallet(BuildContext context, - {required String password}) async { - return await coin.openWallet( - this, - password: password, - ); - } - - @override - Future deleteWallet() async { - for (var element in wPtrList) { - monero.WalletManager_closeWallet(Monero.wmPtr, element, true); - } - wPtrList.clear(); - File(walletName).deleteSync(); - File("$walletName.keys").deleteSync(); - } - - @override - Future renameWallet(String newName) async { - if (p.basename(walletName) == newName) { - throw Exception("Wallet wasn't renamed"); - } - for (var element in wPtrList) { - monero.WalletManager_closeWallet(Monero.wmPtr, element, true); - } - wPtrList.clear(); - final basePath = p.dirname(walletName); - File(walletName).copySync(p.join(basePath, newName)); - File("$walletName.keys").copySync(p.join(basePath, "$newName.keys")); - File(walletName).deleteSync(); - File("$walletName.keys").deleteSync(); - _walletName = newName; - } - - @override - bool exists() => File("$walletName.keys").existsSync(); -} diff --git a/lib/coins/monero/monero_strings.dart b/lib/coins/monero/monero_strings.dart new file mode 100644 index 0000000..6b618e4 --- /dev/null +++ b/lib/coins/monero/monero_strings.dart @@ -0,0 +1,21 @@ +import 'package:cupcake/coins/abstract/coin_strings.dart'; +import 'package:cupcake/gen/assets.gen.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +class MoneroStrings implements CoinStrings { + @override + String get nameLowercase => "monero"; + @override + String get nameCapitalized => "Monero"; + @override + String get nameUppercase => "MONERO"; + @override + String get symbolLowercase => "xmr"; + @override + String get symbolUppercase => "XMR"; + @override + String get nameFull => "$nameCapitalized ($symbolUppercase)"; + + @override + SvgPicture get svg => Assets.coins.xmr.svg(); +} diff --git a/lib/coins/monero/monero_wallet_info.dart b/lib/coins/monero/monero_wallet_info.dart new file mode 100644 index 0000000..0913385 --- /dev/null +++ b/lib/coins/monero/monero_wallet_info.dart @@ -0,0 +1,86 @@ +import 'dart:io'; + +import 'package:cupcake/coins/abstract/coin.dart'; +import 'package:cupcake/coins/abstract/coin_wallet.dart'; +import 'package:cupcake/coins/abstract/coin_wallet_info.dart'; +import 'package:cupcake/coins/monero/coin.dart'; +import 'package:cupcake/views/open_wallet.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:monero/monero.dart' as monero; +import 'package:path/path.dart' as p; + +class MoneroWalletInfo extends CoinWalletInfo { + MoneroWalletInfo(String walletName) + : _walletName = (() { + if (walletName == p.basename(walletName)) { + walletName = p.join(Monero.baseDir.path, walletName); + } + return walletName; + }()); + + @override + Coin get coin => Monero(); + + @override + Future checkWalletPassword(String password) async { + return monero.WalletManager_verifyWalletPassword( + Monero.wmPtr, + keysFileName: "$walletName.keys", + password: password, + noSpendKey: false, + kdfRounds: 0, + ); + } + + @override + String get walletName => _walletName; + + String _walletName; + + @override + Coins get type => coin.type; + + @override + void openUI(BuildContext context) { + OpenWallet.pushStatic(context, this); + } + + @override + Future openWallet(BuildContext context, + {required String password}) async { + return await coin.openWallet( + this, + password: password, + ); + } + + @override + Future deleteWallet() async { + for (var element in wPtrList) { + monero.WalletManager_closeWallet(Monero.wmPtr, element, true); + } + wPtrList.clear(); + File(walletName).deleteSync(); + File("$walletName.keys").deleteSync(); + } + + @override + Future renameWallet(String newName) async { + if (p.basename(walletName) == newName) { + throw Exception("Wallet wasn't renamed"); + } + for (var element in wPtrList) { + monero.WalletManager_closeWallet(Monero.wmPtr, element, true); + } + wPtrList.clear(); + final basePath = p.dirname(walletName); + File(walletName).copySync(p.join(basePath, newName)); + File("$walletName.keys").copySync(p.join(basePath, "$newName.keys")); + File(walletName).deleteSync(); + File("$walletName.keys").deleteSync(); + _walletName = newName; + } + + @override + bool exists() => File("$walletName.keys").existsSync(); +} diff --git a/lib/coins/monero/wallet.dart b/lib/coins/monero/wallet.dart index c0640ad..278ef3c 100644 --- a/lib/coins/monero/wallet.dart +++ b/lib/coins/monero/wallet.dart @@ -1,12 +1,16 @@ import 'dart:convert'; -import 'package:cupcake/coins/abstract.dart'; +import 'package:cupcake/coins/abstract/coin.dart'; +import 'package:cupcake/coins/abstract/coin_exception.dart'; +import 'package:cupcake/coins/abstract/coin_wallet.dart'; +import 'package:cupcake/coins/abstract/wallet_seed_detail.dart'; import 'package:cupcake/coins/monero/coin.dart'; +import 'package:cupcake/coins/types.dart'; import 'package:cupcake/l10n/app_localizations.dart'; import 'package:cupcake/utils/config.dart'; import 'package:cupcake/utils/null_if_empty.dart'; import 'package:cupcake/utils/secure_storage.dart'; -import 'package:cupcake/view_model/barcode_scanner_view_model.dart'; +import 'package:cupcake/utils/urqr.dart'; import 'package:cupcake/view_model/unconfirmed_transaction_view_model.dart'; import 'package:cupcake/view_model/urqr_view_model.dart'; import 'package:cupcake/views/unconfirmed_transaction.dart'; diff --git a/lib/coins/types.dart b/lib/coins/types.dart new file mode 100644 index 0000000..adecd2d --- /dev/null +++ b/lib/coins/types.dart @@ -0,0 +1,6 @@ +typedef ProgressCallback = int Function({String? title, String? description}); + +enum WalletSeedDetailType { + text, + qr, +} diff --git a/lib/main.dart b/lib/main.dart index aabb733..7b28f01 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -10,7 +10,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; const String signingKeyExpected = "Please Fill Me On Release :)"; -late String signingKeyFound = ""; +String signingKeyFound = ""; Future appInit() async { WidgetsFlutterBinding.ensureInitialized(); diff --git a/lib/utils/alert.dart b/lib/utils/alert.dart deleted file mode 100644 index 77002d3..0000000 --- a/lib/utils/alert.dart +++ /dev/null @@ -1,99 +0,0 @@ -import 'package:flutter/material.dart'; - -Future showAlert({ - required BuildContext context, - required String title, - required List body, - String ok = "ok", -}) async { - return showDialog( - context: context, - barrierDismissible: false, // user must tap button! - builder: (BuildContext context) { - return AlertDialog( - title: Text(title), - content: SingleChildScrollView( - child: ListBody( - children: body - .map( - (e) => Text( - e, - style: const TextStyle(color: Colors.white), - ), - ) - .toList(), - ), - ), - actions: [ - TextButton( - child: Text(ok), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - ); - }, - ); -} - -Future showAlertWidget({ - required BuildContext context, - required String title, - required List body, - String ok = "ok", -}) async { - return showDialog( - context: context, - barrierDismissible: false, // user must tap button! - builder: (BuildContext context) { - return AlertDialog( - title: Text(title), - content: SingleChildScrollView( - child: ListBody( - children: body, - ), - ), - actions: [ - TextButton( - child: Text(ok), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - ); - }, - ); -} - -Future showAlertWidgetMinimal({ - required BuildContext context, - required List body, -}) async { - return showDialog( - context: context, - barrierDismissible: true, - builder: (BuildContext context) { - return SizedBox( - height: 360, - child: Material( - color: Colors.transparent, - child: Column( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - color: Colors.transparent, - child: Column( - mainAxisSize: MainAxisSize.min, - children: body, - ), - ), - ], - ), - ), - ); - }, - ); -} diff --git a/lib/utils/alerts/basic.dart b/lib/utils/alerts/basic.dart new file mode 100644 index 0000000..72bceaa --- /dev/null +++ b/lib/utils/alerts/basic.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; + +Future showAlert({ + required BuildContext context, + required String title, + required List body, + String ok = "ok", +}) async { + return showDialog( + context: context, + barrierDismissible: false, // user must tap button! + builder: (BuildContext context) { + return AlertDialog( + title: Text(title), + content: SingleChildScrollView( + child: ListBody( + children: body + .map( + (e) => Text( + e, + style: const TextStyle(color: Colors.white), + ), + ) + .toList(), + ), + ), + actions: [ + TextButton( + child: Text(ok), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); +} diff --git a/lib/utils/alerts/widget.dart b/lib/utils/alerts/widget.dart new file mode 100644 index 0000000..dec1473 --- /dev/null +++ b/lib/utils/alerts/widget.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; + +Future showAlertWidget({ + required BuildContext context, + required String title, + required List body, + String ok = "ok", +}) async { + return showDialog( + context: context, + barrierDismissible: false, // user must tap button! + builder: (BuildContext context) { + return AlertDialog( + title: Text(title), + content: SingleChildScrollView( + child: ListBody( + children: body, + ), + ), + actions: [ + TextButton( + child: Text(ok), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); +} diff --git a/lib/utils/alerts/widget_minimal.dart b/lib/utils/alerts/widget_minimal.dart new file mode 100644 index 0000000..c2d3506 --- /dev/null +++ b/lib/utils/alerts/widget_minimal.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; + +Future showAlertWidgetMinimal({ + required BuildContext context, + required List body, +}) async { + return showDialog( + context: context, + barrierDismissible: true, + builder: (BuildContext context) { + return SizedBox( + height: 360, + child: Material( + color: Colors.transparent, + child: Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + color: Colors.transparent, + child: Column( + mainAxisSize: MainAxisSize.min, + children: body, + ), + ), + ], + ), + ), + ); + }, + ); +} diff --git a/lib/utils/call_throwable.dart b/lib/utils/call_throwable.dart index 84e8084..3d2e385 100644 --- a/lib/utils/call_throwable.dart +++ b/lib/utils/call_throwable.dart @@ -1,7 +1,7 @@ import 'dart:async'; -import 'package:cupcake/coins/abstract.dart'; -import 'package:cupcake/utils/alert.dart'; +import 'package:cupcake/coins/abstract/coin_exception.dart'; +import 'package:cupcake/utils/alerts/basic.dart'; import 'package:flutter/cupertino.dart'; Future callThrowable(BuildContext context, diff --git a/lib/utils/config.dart b/lib/utils/config.dart index a969957..9726cbf 100644 --- a/lib/utils/config.dart +++ b/lib/utils/config.dart @@ -1,7 +1,7 @@ import 'dart:convert'; import 'dart:io'; -import 'package:cupcake/coins/abstract.dart'; +import 'package:cupcake/coins/abstract/coin_wallet_info.dart'; import 'package:cupcake/utils/filesystem.dart'; import 'package:flutter/foundation.dart'; import 'package:path/path.dart' as p; diff --git a/lib/utils/form/abstract_form_element.dart b/lib/utils/form/abstract_form_element.dart new file mode 100644 index 0000000..1435071 --- /dev/null +++ b/lib/utils/form/abstract_form_element.dart @@ -0,0 +1,5 @@ +abstract class FormElement { + bool get isOk => true; + String get label => throw UnimplementedError(); + Future get value => throw UnimplementedError(); +} diff --git a/lib/utils/form/abstract_value_outcome.dart b/lib/utils/form/abstract_value_outcome.dart new file mode 100644 index 0000000..2be6570 --- /dev/null +++ b/lib/utils/form/abstract_value_outcome.dart @@ -0,0 +1,6 @@ +abstract class ValueOutcome { + Future encode(String input); + Future decode(String output); + + String get uniqueId => throw UnimplementedError(); +} diff --git a/lib/utils/form/default_validator.dart b/lib/utils/form/default_validator.dart new file mode 100644 index 0000000..ce45073 --- /dev/null +++ b/lib/utils/form/default_validator.dart @@ -0,0 +1,3 @@ +String? defaultFormValidator(String? input) { + return null; +} diff --git a/lib/utils/form/flutter_secure_storage_value_outcome.dart b/lib/utils/form/flutter_secure_storage_value_outcome.dart new file mode 100644 index 0000000..4bababa --- /dev/null +++ b/lib/utils/form/flutter_secure_storage_value_outcome.dart @@ -0,0 +1,72 @@ +import 'dart:convert'; +import 'dart:math'; + +import 'package:crypto/crypto.dart'; +import 'package:cupcake/utils/config.dart'; +import 'package:cupcake/utils/form/abstract_value_outcome.dart'; +import 'package:cupcake/utils/secure_storage.dart'; + +class FlutterSecureStorageValueOutcome implements ValueOutcome { + FlutterSecureStorageValueOutcome(this.key, + {required this.canWrite, required this.verifyMatching}); + + final String key; + final bool canWrite; + final bool verifyMatching; + + @override + Future encode(String input) async { + List bytes = utf8.encode(input); + Digest sha512Hash = sha512.convert(bytes); + var valInput = + await secureStorage.read(key: "FlutterSecureStorageValueOutcome._$key"); + if (valInput == null) { + await secureStorage.write( + key: "FlutterSecureStorageValueOutcome._$key", + value: sha512Hash.toString()); + valInput = await secureStorage.read( + key: "FlutterSecureStorageValueOutcome._$key"); + } + if (sha512Hash.toString() != valInput && verifyMatching) { + throw Exception("Input doesn't match the secure element value"); + } + + final input_ = await secureStorage.read(key: key); + // Do not update secret if it is already set. + if (input_ != null) { + return; + } + if (!canWrite) { + if (config.debug) { + throw Exception( + "DEBUG_ONLY: canWrite is false but we tried to flush the value"); + } + return; + } + var random = Random.secure(); + var values = List.generate(64, (i) => random.nextInt(256)); + final pass = base64Url.encode(values); + await secureStorage.write(key: key, value: pass); + return; + } + + @override + Future decode(String output) async { + List bytes = utf8.encode(output); + Digest sha512Hash = sha512.convert(bytes); + var valInput = + await secureStorage.read(key: "FlutterSecureStorageValueOutcome._$key"); + if (sha512Hash.toString() != valInput && verifyMatching) { + throw Exception("Input doesn't match the secure element value"); + } + final input = await secureStorage.read(key: key); + if (input == null) { + throw Exception("no secure storage $key found"); + } + return "$input/$output"; + } + + @override + // TODO: implement uniqueId + String get uniqueId => key; +} diff --git a/lib/utils/form/pin_form_element.dart b/lib/utils/form/pin_form_element.dart new file mode 100644 index 0000000..aa30246 --- /dev/null +++ b/lib/utils/form/pin_form_element.dart @@ -0,0 +1,74 @@ +import 'package:cupcake/utils/config.dart'; +import 'package:cupcake/utils/form/abstract_form_element.dart'; +import 'package:cupcake/utils/form/abstract_value_outcome.dart'; +import 'package:cupcake/utils/secure_storage.dart'; +import 'package:cupcake/utils/form/default_validator.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:local_auth/local_auth.dart'; + +final auth = LocalAuthentication(); + +class PinFormElement extends FormElement { + PinFormElement({ + String initialText = "", + this.password = false, + this.validator = defaultFormValidator, + required this.valueOutcome, + this.onChanged, + this.onConfirm, + this.showNumboard = true, + required this.label, + }) : ctrl = TextEditingController(text: initialText); + + Future loadSecureStorageValue(VoidCallback callback) async { + if (ctrl.text.isNotEmpty) return; + if (!config.biometricEnabled) return; + final List availableBiometrics = + await auth.getAvailableBiometrics(); + final bool canAuthenticateWithBiometrics = await auth.canCheckBiometrics; + final bool canAuthenticate = + canAuthenticateWithBiometrics || await auth.isDeviceSupported(); + if (!canAuthenticate) return; + if (!availableBiometrics.contains(BiometricType.fingerprint) && + !availableBiometrics.contains(BiometricType.face)) { + return; + } + + final bool didAuthenticate = await auth.authenticate( + localizedReason: 'Authenticate...', + options: const AuthenticationOptions( + useErrorDialogs: true, biometricOnly: true), + ); + if (!didAuthenticate) return; + final value = await secureStorage.read(key: "UI.${valueOutcome.uniqueId}"); + if (value == null) return; + ctrl.text = value; + await Future.delayed(Duration.zero); + callback(); + } + + TextEditingController ctrl; + bool password; + bool showNumboard; + + @override + String label; + + ValueOutcome valueOutcome; + + @override + Future get value async => await valueOutcome.decode(ctrl.text); + + @override + bool get isOk => validator(ctrl.text) == null; + + Future Function(BuildContext context)? onChanged; + Future Function(BuildContext context)? onConfirm; + Future onConfirmInternal(BuildContext context) async { + await valueOutcome.encode(ctrl.text); + isConfirmed = true; + } + + bool isConfirmed = false; + String? Function(String? input) validator; +} diff --git a/lib/utils/form/plain_value_outcome.dart b/lib/utils/form/plain_value_outcome.dart new file mode 100644 index 0000000..4c24c63 --- /dev/null +++ b/lib/utils/form/plain_value_outcome.dart @@ -0,0 +1,12 @@ +import 'package:cupcake/utils/form/abstract_value_outcome.dart'; + +class PlainValueOutcome implements ValueOutcome { + @override + Future decode(String output) => Future.value(output); + + @override + Future encode(String input) => Future.value(); + + @override + String get uniqueId => "undefined"; +} diff --git a/lib/utils/form/single_choice_form_element.dart b/lib/utils/form/single_choice_form_element.dart new file mode 100644 index 0000000..e254572 --- /dev/null +++ b/lib/utils/form/single_choice_form_element.dart @@ -0,0 +1,16 @@ +import 'package:cupcake/utils/form/abstract_form_element.dart'; + +class SingleChoiceFormElement extends FormElement { + SingleChoiceFormElement({required this.title, required this.elements}); + String title; + List elements; + + int currentSelection = 0; + + @override + Future get value => Future.value(valueSync); + String get valueSync => elements[currentSelection]; + + @override + bool get isOk => true; +} diff --git a/lib/utils/form/string_form_element.dart b/lib/utils/form/string_form_element.dart new file mode 100644 index 0000000..d75265e --- /dev/null +++ b/lib/utils/form/string_form_element.dart @@ -0,0 +1,31 @@ +import 'package:cupcake/utils/form/abstract_form_element.dart'; +import 'package:cupcake/utils/form/default_validator.dart'; +import 'package:flutter/cupertino.dart'; + +class StringFormElement extends FormElement { + StringFormElement( + this.label, { + String initialText = "", + this.password = false, + this.validator = defaultFormValidator, + this.isExtra = false, + this.showIf, + this.randomNameGenerator = false, + }) : ctrl = TextEditingController(text: initialText); + + bool Function()? showIf; + TextEditingController ctrl; + bool password; + @override + String label; + @override + Future get value => Future.value(ctrl.text); + + bool isExtra; + bool randomNameGenerator; + + @override + bool get isOk => validator(ctrl.text) == null; + + String? Function(String? input) validator; +} diff --git a/lib/utils/urqr.dart b/lib/utils/urqr.dart new file mode 100644 index 0000000..fec6222 --- /dev/null +++ b/lib/utils/urqr.dart @@ -0,0 +1,62 @@ +URQRData URQRToURQRData(List urqr_) { + final urqr = urqr_.toSet().toList(); + urqr.sort((s1, s2) { + final s1s = s1.split("/"); + final s1frameStr = s1s[1].split("-"); + final s1curFrame = int.parse(s1frameStr[0]); + final s2s = s2.split("/"); + final s2frameStr = s2s[1].split("-"); + final s2curFrame = int.parse(s2frameStr[0]); + return s1curFrame - s2curFrame; + }); + + String tag = ''; + int count = 0; + String bw = ''; + for (var elm in urqr) { + final s = elm.substring(elm.indexOf(":") + 1); // strip down ur: prefix + final s2 = s.split("/"); + tag = s2[0]; + final frameStr = s2[1].split("-"); + // final curFrame = int.parse(frameStr[0]); + count = int.parse(frameStr[1]); + final byteWords = s2[2]; + bw += byteWords; + } + String? error; + + return URQRData( + tag: tag, + str: bw, + progress: count == 0 ? 0 : (urqr.length / count), + count: count, + error: error ?? "", + inputs: urqr, + ); +} + +class URQRData { + URQRData( + {required this.tag, + required this.str, + required this.progress, + required this.count, + required this.error, + required this.inputs}); + final String tag; + final String str; + final double progress; + final int count; + final String error; + final List inputs; + Map toJson() { + return { + "tag": tag, + "str": str, + "progress": progress, + "count": count, + "error": error, + "inputs": inputs, + }; + } +} diff --git a/lib/view_model/barcode_scanner_view_model.dart b/lib/view_model/barcode_scanner_view_model.dart index b633067..3e6b1b8 100644 --- a/lib/view_model/barcode_scanner_view_model.dart +++ b/lib/view_model/barcode_scanner_view_model.dart @@ -1,5 +1,6 @@ -import 'package:cupcake/coins/abstract.dart'; +import 'package:cupcake/coins/abstract/coin_wallet.dart'; import 'package:cupcake/utils/call_throwable.dart'; +import 'package:cupcake/utils/urqr.dart'; import 'package:cupcake/view_model/abstract.dart'; import 'package:cupcake/views/widgets/barcode_scanner/progress_painter.dart'; import 'package:fast_scanner/fast_scanner.dart'; @@ -67,71 +68,10 @@ class BarcodeScannerViewModel extends ViewModel { for (var inp in ur.inputs) { try { l.add(int.parse(inp.split("/")[1].split("-")[0])); - } catch (e) {} + } catch (e) { + print(e); + } } return l; } } - -class URQRData { - URQRData( - {required this.tag, - required this.str, - required this.progress, - required this.count, - required this.error, - required this.inputs}); - final String tag; - final String str; - final double progress; - final int count; - final String error; - final List inputs; - Map toJson() { - return { - "tag": tag, - "str": str, - "progress": progress, - "count": count, - "error": error, - "inputs": inputs, - }; - } -} - -URQRData URQRToURQRData(List urqr_) { - final urqr = urqr_.toSet().toList(); - urqr.sort((s1, s2) { - final s1s = s1.split("/"); - final s1frameStr = s1s[1].split("-"); - final s1curFrame = int.parse(s1frameStr[0]); - final s2s = s2.split("/"); - final s2frameStr = s2s[1].split("-"); - final s2curFrame = int.parse(s2frameStr[0]); - return s1curFrame - s2curFrame; - }); - - String tag = ''; - int count = 0; - String bw = ''; - for (var elm in urqr) { - final s = elm.substring(elm.indexOf(":") + 1); // strip down ur: prefix - final s2 = s.split("/"); - tag = s2[0]; - final frameStr = s2[1].split("-"); - // final curFrame = int.parse(frameStr[0]); - count = int.parse(frameStr[1]); - final byteWords = s2[2]; - bw += byteWords; - } - String? error; - - return URQRData( - tag: tag, - str: bw, - progress: count == 0 ? 0 : (urqr.length / count), - count: count, - error: error ?? "", - inputs: urqr, - ); -} diff --git a/lib/view_model/create_wallet_view_model.dart b/lib/view_model/create_wallet_view_model.dart index 935a93c..30c07c3 100644 --- a/lib/view_model/create_wallet_view_model.dart +++ b/lib/view_model/create_wallet_view_model.dart @@ -1,13 +1,15 @@ import 'dart:async'; -import 'dart:convert'; -import 'dart:math'; -import 'package:crypto/crypto.dart'; -import 'package:cupcake/coins/abstract.dart'; +import 'package:cupcake/coins/abstract/coin.dart'; import 'package:cupcake/coins/list.dart'; import 'package:cupcake/utils/config.dart'; +import 'package:cupcake/utils/form/abstract_form_element.dart'; +import 'package:cupcake/utils/form/flutter_secure_storage_value_outcome.dart'; +import 'package:cupcake/utils/form/pin_form_element.dart'; +import 'package:cupcake/utils/form/plain_value_outcome.dart'; +import 'package:cupcake/utils/form/single_choice_form_element.dart'; +import 'package:cupcake/utils/form/string_form_element.dart'; import 'package:cupcake/utils/null_if_empty.dart'; -import 'package:cupcake/utils/secure_storage.dart'; import 'package:cupcake/view_model/abstract.dart'; import 'package:cupcake/view_model/new_wallet_info_view_model.dart'; import 'package:cupcake/views/new_wallet_info.dart'; @@ -16,7 +18,6 @@ import 'package:cupcake/views/wallet_home.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:share_plus/share_plus.dart'; -import 'package:local_auth/local_auth.dart'; enum CreateMethod { any, @@ -393,206 +394,3 @@ class CreateWalletViewModel extends ViewModel { markNeedsBuild(); } } - -String? _defaultValidator(String? input) { - return null; -} - -class StringFormElement extends FormElement { - StringFormElement( - this.label, { - String initialText = "", - this.password = false, - this.validator = _defaultValidator, - this.isExtra = false, - this.showIf, - this.randomNameGenerator = false, - }) : ctrl = TextEditingController(text: initialText); - - bool Function()? showIf; - TextEditingController ctrl; - bool password; - @override - String label; - @override - Future get value => Future.value(ctrl.text); - - bool isExtra; - bool randomNameGenerator; - - @override - bool get isOk => validator(ctrl.text) == null; - - String? Function(String? input) validator; -} - -abstract class ValueOutcome { - Future encode(String input); - Future decode(String output); - - String get uniqueId => throw UnimplementedError(); -} - -class PlainValueOutcome implements ValueOutcome { - @override - Future decode(String output) => Future.value(output); - - @override - Future encode(String input) => Future.value(); - - @override - String get uniqueId => "undefined"; -} - -class FlutterSecureStorageValueOutcome implements ValueOutcome { - FlutterSecureStorageValueOutcome(this.key, - {required this.canWrite, required this.verifyMatching}); - - final String key; - final bool canWrite; - final bool verifyMatching; - - @override - Future encode(String input) async { - List bytes = utf8.encode(input); - Digest sha512Hash = sha512.convert(bytes); - var valInput = - await secureStorage.read(key: "FlutterSecureStorageValueOutcome._$key"); - if (valInput == null) { - await secureStorage.write( - key: "FlutterSecureStorageValueOutcome._$key", - value: sha512Hash.toString()); - valInput = await secureStorage.read( - key: "FlutterSecureStorageValueOutcome._$key"); - } - if (sha512Hash.toString() != valInput && verifyMatching) { - throw Exception("Input doesn't match the secure element value"); - } - - final input_ = await secureStorage.read(key: key); - // Do not update secret if it is already set. - if (input_ != null) { - return; - } - if (!canWrite) { - if (config.debug) { - throw Exception( - "DEBUG_ONLY: canWrite is false but we tried to flush the value"); - } - return; - } - var random = Random.secure(); - var values = List.generate(64, (i) => random.nextInt(256)); - final pass = base64Url.encode(values); - await secureStorage.write(key: key, value: pass); - return; - } - - @override - Future decode(String output) async { - List bytes = utf8.encode(output); - Digest sha512Hash = sha512.convert(bytes); - var valInput = - await secureStorage.read(key: "FlutterSecureStorageValueOutcome._$key"); - if (sha512Hash.toString() != valInput && verifyMatching) { - throw Exception("Input doesn't match the secure element value"); - } - final input = await secureStorage.read(key: key); - if (input == null) { - throw Exception("no secure storage $key found"); - } - return "$input/$output"; - } - - @override - // TODO: implement uniqueId - String get uniqueId => key; -} - -final LocalAuthentication auth = LocalAuthentication(); - -class PinFormElement extends FormElement { - PinFormElement({ - String initialText = "", - this.password = false, - this.validator = _defaultValidator, - required this.valueOutcome, - this.onChanged, - this.onConfirm, - this.showNumboard = true, - required this.label, - }) : ctrl = TextEditingController(text: initialText); - - Future loadSecureStorageValue(VoidCallback callback) async { - if (ctrl.text.isNotEmpty) return; - if (!config.biometricEnabled) return; - final List availableBiometrics = - await auth.getAvailableBiometrics(); - final bool canAuthenticateWithBiometrics = await auth.canCheckBiometrics; - final bool canAuthenticate = - canAuthenticateWithBiometrics || await auth.isDeviceSupported(); - if (!canAuthenticate) return; - if (!availableBiometrics.contains(BiometricType.fingerprint) && - !availableBiometrics.contains(BiometricType.face)) { - return; - } - - final bool didAuthenticate = await auth.authenticate( - localizedReason: 'Authenticate...', - options: const AuthenticationOptions( - useErrorDialogs: true, biometricOnly: true), - ); - if (!didAuthenticate) return; - final value = await secureStorage.read(key: "UI.${valueOutcome.uniqueId}"); - if (value == null) return; - ctrl.text = value; - await Future.delayed(Duration.zero); - callback(); - } - - TextEditingController ctrl; - bool password; - bool showNumboard; - - @override - String label; - - ValueOutcome valueOutcome; - - @override - Future get value async => await valueOutcome.decode(ctrl.text); - - @override - bool get isOk => validator(ctrl.text) == null; - - Future Function(BuildContext context)? onChanged; - Future Function(BuildContext context)? onConfirm; - Future onConfirmInternal(BuildContext context) async { - await valueOutcome.encode(ctrl.text); - isConfirmed = true; - } - - bool isConfirmed = false; - String? Function(String? input) validator; -} - -class SingleChoiceFormElement extends FormElement { - SingleChoiceFormElement({required this.title, required this.elements}); - String title; - List elements; - - int currentSelection = 0; - - @override - Future get value => Future.value(valueSync); - String get valueSync => elements[currentSelection]; - - @override - bool get isOk => true; -} - -class FormElement { - bool get isOk => true; - String get label => throw UnimplementedError(); - Future get value => throw UnimplementedError(); -} diff --git a/lib/view_model/home_screen_view_model.dart b/lib/view_model/home_screen_view_model.dart index d62fd4a..d652ce8 100644 --- a/lib/view_model/home_screen_view_model.dart +++ b/lib/view_model/home_screen_view_model.dart @@ -1,4 +1,4 @@ -import 'package:cupcake/coins/abstract.dart'; +import 'package:cupcake/coins/abstract/coin_wallet_info.dart'; import 'package:cupcake/coins/list.dart'; import 'package:cupcake/utils/config.dart'; import 'package:cupcake/view_model/abstract.dart'; diff --git a/lib/view_model/open_wallet_view_model.dart b/lib/view_model/open_wallet_view_model.dart index a6ca931..905252e 100644 --- a/lib/view_model/open_wallet_view_model.dart +++ b/lib/view_model/open_wallet_view_model.dart @@ -1,7 +1,8 @@ -import 'package:cupcake/coins/abstract.dart'; +import 'package:cupcake/coins/abstract/coin_wallet_info.dart'; import 'package:cupcake/utils/call_throwable.dart'; +import 'package:cupcake/utils/form/flutter_secure_storage_value_outcome.dart'; +import 'package:cupcake/utils/form/pin_form_element.dart'; import 'package:cupcake/view_model/abstract.dart'; -import 'package:cupcake/view_model/create_wallet_view_model.dart'; import 'package:cupcake/views/wallet_home.dart'; import 'package:flutter/cupertino.dart'; diff --git a/lib/view_model/receive_view_model.dart b/lib/view_model/receive_view_model.dart index 25a820f..53779b6 100644 --- a/lib/view_model/receive_view_model.dart +++ b/lib/view_model/receive_view_model.dart @@ -1,4 +1,4 @@ -import 'package:cupcake/coins/abstract.dart'; +import 'package:cupcake/coins/abstract/coin_wallet.dart'; import 'package:cupcake/view_model/abstract.dart'; class ReceiveViewModel extends ViewModel { diff --git a/lib/view_model/security_backup_view_model.dart b/lib/view_model/security_backup_view_model.dart index 102baae..6c540bb 100644 --- a/lib/view_model/security_backup_view_model.dart +++ b/lib/view_model/security_backup_view_model.dart @@ -1,6 +1,8 @@ -import 'package:cupcake/coins/abstract.dart'; +import 'package:cupcake/coins/abstract/coin_wallet.dart'; +import 'package:cupcake/utils/form/abstract_form_element.dart'; +import 'package:cupcake/utils/form/flutter_secure_storage_value_outcome.dart'; +import 'package:cupcake/utils/form/pin_form_element.dart'; import 'package:cupcake/view_model/abstract.dart'; -import 'package:cupcake/view_model/create_wallet_view_model.dart'; import 'package:flutter/cupertino.dart'; class SecurityBackupViewModel extends ViewModel { diff --git a/lib/view_model/unconfirmed_transaction_view_model.dart b/lib/view_model/unconfirmed_transaction_view_model.dart index 0f99f6f..50a68a0 100644 --- a/lib/view_model/unconfirmed_transaction_view_model.dart +++ b/lib/view_model/unconfirmed_transaction_view_model.dart @@ -1,6 +1,6 @@ import 'dart:async'; -import 'package:cupcake/coins/abstract.dart'; +import 'package:cupcake/coins/abstract/coin_wallet.dart'; import 'package:cupcake/view_model/abstract.dart'; import 'package:flutter/cupertino.dart'; diff --git a/lib/view_model/wallet_edit_view_model.dart b/lib/view_model/wallet_edit_view_model.dart index c775f58..e1fa9d6 100644 --- a/lib/view_model/wallet_edit_view_model.dart +++ b/lib/view_model/wallet_edit_view_model.dart @@ -1,5 +1,8 @@ -import 'package:cupcake/coins/abstract.dart'; -import 'package:cupcake/view_model/create_wallet_view_model.dart'; +import 'package:cupcake/coins/abstract/coin_wallet_info.dart'; +import 'package:cupcake/utils/form/abstract_form_element.dart'; +import 'package:cupcake/utils/form/flutter_secure_storage_value_outcome.dart'; +import 'package:cupcake/utils/form/pin_form_element.dart'; +import 'package:cupcake/utils/form/string_form_element.dart'; import 'package:flutter/material.dart'; import 'package:cupcake/view_model/abstract.dart'; import 'package:path/path.dart' as p; diff --git a/lib/view_model/wallet_home_view_model.dart b/lib/view_model/wallet_home_view_model.dart index dc2e7e5..7f8cd08 100644 --- a/lib/view_model/wallet_home_view_model.dart +++ b/lib/view_model/wallet_home_view_model.dart @@ -1,4 +1,5 @@ -import 'package:cupcake/coins/abstract.dart'; +import 'package:cupcake/coins/abstract/coin.dart'; +import 'package:cupcake/coins/abstract/coin_wallet.dart'; import 'package:cupcake/view_model/abstract.dart'; import 'package:cupcake/views/barcode_scanner.dart'; import 'package:flutter/cupertino.dart'; diff --git a/lib/views/barcode_scanner.dart b/lib/views/barcode_scanner.dart index ab6cf29..74850e8 100644 --- a/lib/views/barcode_scanner.dart +++ b/lib/views/barcode_scanner.dart @@ -1,4 +1,4 @@ -import 'package:cupcake/coins/abstract.dart'; +import 'package:cupcake/coins/abstract/coin_wallet.dart'; import 'package:cupcake/view_model/barcode_scanner_view_model.dart'; import 'package:cupcake/views/abstract.dart'; import 'package:cupcake/views/widgets/barcode_scanner/progress_painter.dart'; diff --git a/lib/views/create_wallet.dart b/lib/views/create_wallet.dart index e348c12..db16e0f 100644 --- a/lib/views/create_wallet.dart +++ b/lib/views/create_wallet.dart @@ -1,10 +1,11 @@ import 'package:cupcake/gen/assets.gen.dart'; import 'package:cupcake/utils/call_throwable.dart'; import 'package:cupcake/utils/config.dart'; +import 'package:cupcake/utils/form/abstract_form_element.dart'; import 'package:cupcake/view_model/create_wallet_view_model.dart'; import 'package:cupcake/views/abstract.dart'; import 'package:cupcake/views/initial_setup_screen.dart'; -import 'package:cupcake/widgets/form_builder.dart'; +import 'package:cupcake/views/widgets/form_builder.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -139,15 +140,6 @@ class CreateWallet extends AbstractView { FormBuilder? formBuilder; - // @override - // Widget? floatingActionButton(BuildContext context) { - // if (viewModel.selectedCoin == null) return null; - // return FloatingActionButton( - // child: const Icon(Icons.navigate_next), - // onPressed: () => _createWallet(context), - // ); - // } - bool isFormBad(List form) { for (var element in form) { if (!element.isOk) { diff --git a/lib/views/home_screen.dart b/lib/views/home_screen.dart index fb4858c..4187017 100644 --- a/lib/views/home_screen.dart +++ b/lib/views/home_screen.dart @@ -1,4 +1,4 @@ -import 'package:cupcake/coins/abstract.dart'; +import 'package:cupcake/coins/abstract/coin_wallet_info.dart'; import 'package:cupcake/utils/call_throwable.dart'; import 'package:cupcake/utils/config.dart'; import 'package:cupcake/view_model/create_wallet_view_model.dart'; diff --git a/lib/views/new_wallet_info.dart b/lib/views/new_wallet_info.dart index 59660cf..4bd33d4 100644 --- a/lib/views/new_wallet_info.dart +++ b/lib/views/new_wallet_info.dart @@ -33,7 +33,9 @@ class NewWalletInfoScreen extends AbstractView { List? _getActionButton() { if (viewModel.page.topAction == null && - viewModel.page.topActionText == null) return null; + viewModel.page.topActionText == null) { + return null; + } if (viewModel.page.topActionText != null && viewModel.page.topAction == null) { return [ diff --git a/lib/views/open_wallet.dart b/lib/views/open_wallet.dart index c6e59a4..5f99c42 100644 --- a/lib/views/open_wallet.dart +++ b/lib/views/open_wallet.dart @@ -1,7 +1,7 @@ -import 'package:cupcake/coins/abstract.dart'; +import 'package:cupcake/coins/abstract/coin_wallet_info.dart'; import 'package:cupcake/view_model/open_wallet_view_model.dart'; import 'package:cupcake/views/abstract.dart'; -import 'package:cupcake/widgets/form_builder.dart'; +import 'package:cupcake/views/widgets/form_builder.dart'; import 'package:flutter/cupertino.dart'; // ignore: must_be_immutable diff --git a/lib/views/receive.dart b/lib/views/receive.dart index e10ceb0..3219126 100644 --- a/lib/views/receive.dart +++ b/lib/views/receive.dart @@ -1,4 +1,4 @@ -import 'package:cupcake/coins/abstract.dart'; +import 'package:cupcake/coins/abstract/coin_wallet.dart'; import 'package:cupcake/view_model/receive_view_model.dart'; import 'package:cupcake/views/abstract.dart'; import 'package:flutter/cupertino.dart'; diff --git a/lib/views/security_backup.dart b/lib/views/security_backup.dart index 18dcb37..a3a3224 100644 --- a/lib/views/security_backup.dart +++ b/lib/views/security_backup.dart @@ -1,9 +1,11 @@ -import 'package:cupcake/coins/abstract.dart'; -import 'package:cupcake/utils/alert.dart'; +import 'package:cupcake/coins/abstract/coin_wallet.dart'; +import 'package:cupcake/coins/abstract/wallet_seed_detail.dart'; +import 'package:cupcake/coins/types.dart'; +import 'package:cupcake/utils/alerts/widget.dart'; import 'package:cupcake/view_model/security_backup_view_model.dart'; import 'package:cupcake/views/abstract.dart'; import 'package:cupcake/views/initial_setup_screen.dart'; -import 'package:cupcake/widgets/form_builder.dart'; +import 'package:cupcake/views/widgets/form_builder.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; diff --git a/lib/views/settings.dart b/lib/views/settings.dart index 0c87bd6..7e72d72 100644 --- a/lib/views/settings.dart +++ b/lib/views/settings.dart @@ -1,6 +1,5 @@ -import 'package:cupcake/utils/alert.dart'; +import 'package:cupcake/utils/alerts/widget.dart'; import 'package:cupcake/utils/config.dart'; -import 'package:cupcake/utils/secure_storage.dart'; import 'package:cupcake/view_model/settings_view_model.dart'; import 'package:cupcake/views/abstract.dart'; import 'package:flutter/cupertino.dart'; diff --git a/lib/views/wallet_edit.dart b/lib/views/wallet_edit.dart index 41548c0..ffb1832 100644 --- a/lib/views/wallet_edit.dart +++ b/lib/views/wallet_edit.dart @@ -1,9 +1,9 @@ -import 'package:cupcake/coins/abstract.dart'; +import 'package:cupcake/coins/abstract/coin_wallet_info.dart'; import 'package:cupcake/utils/call_throwable.dart'; import 'package:cupcake/view_model/wallet_edit_view_model.dart'; import 'package:cupcake/views/abstract.dart'; import 'package:cupcake/views/initial_setup_screen.dart'; -import 'package:cupcake/widgets/form_builder.dart'; +import 'package:cupcake/views/widgets/form_builder.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; diff --git a/lib/views/wallet_home.dart b/lib/views/wallet_home.dart index 678ef0c..4adbce0 100644 --- a/lib/views/wallet_home.dart +++ b/lib/views/wallet_home.dart @@ -1,10 +1,10 @@ -import 'package:cupcake/coins/abstract.dart'; +import 'package:cupcake/coins/abstract/coin_wallet.dart'; import 'package:cupcake/view_model/wallet_home_view_model.dart'; import 'package:cupcake/views/abstract.dart'; import 'package:cupcake/views/barcode_scanner.dart'; import 'package:cupcake/views/receive.dart'; import 'package:cupcake/views/widgets/cake_card.dart'; -import 'package:cupcake/views/widgets/drawer_element.dart'; +import 'package:cupcake/views/widgets/drawer_elements.dart'; import 'package:flutter/material.dart'; import 'package:cupcake/gen/assets.gen.dart'; diff --git a/lib/views/widgets/drawer_element.dart b/lib/views/widgets/drawer_element.dart index 4d96970..ceb529c 100644 --- a/lib/views/widgets/drawer_element.dart +++ b/lib/views/widgets/drawer_element.dart @@ -1,13 +1,6 @@ -import 'package:cupcake/coins/abstract.dart'; -import 'package:cupcake/coins/monero/wallet.dart'; -import 'package:cupcake/l10n/app_localizations.dart'; import 'package:cupcake/utils/call_throwable.dart'; -import 'package:cupcake/views/home_screen.dart'; -import 'package:cupcake/views/security_backup.dart'; -import 'package:cupcake/views/settings.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:cupcake/gen/assets.gen.dart'; class DrawerElement extends StatelessWidget { const DrawerElement({ @@ -60,61 +53,3 @@ class DrawerElement extends StatelessWidget { )); } } - -class DrawerElements extends StatelessWidget { - const DrawerElements({super.key, required this.coinWallet}); - - final CoinWallet coinWallet; - - Future _walletList(BuildContext context) async { - await HomeScreen.staticPush( - context, - openLastWallet: false, - lastOpenedWallet: coinWallet.walletName, - ); - } - - Future _securityBackup(BuildContext context) async { - await SecurityBackup.staticPush(context, coinWallet); - } - - Future _exportKeyImages(BuildContext context) async { - if (coinWallet is! MoneroWallet) { - throw Exception("coinWallet is not monero - we can't export key images"); - } - await (coinWallet as MoneroWallet).exportKeyImagesUR(context); - } - - Future _otherSettings(BuildContext context) async { - await SettingsView.staticPush(context); - } - - @override - Widget build(BuildContext context) { - final L = AppLocalizations.of(context)!; - return Column( - children: [ - DrawerElement( - svg: Assets.drawerIcons.wallets.svg(), - text: L.wallets, - action: _walletList, - ), - DrawerElement( - svg: Assets.drawerIcons.securityAndBackup.svg(), - text: L.security_and_backup, - action: _securityBackup, - ), - DrawerElement( - svg: Assets.drawerIcons.exportKeyImages.svg(), - text: L.export_key_images, - action: _exportKeyImages, - ), - DrawerElement( - svg: Assets.drawerIcons.otherSettings.svg(), - text: L.other_settings, - action: _otherSettings, - ), - ], - ); - } -} diff --git a/lib/views/widgets/drawer_elements.dart b/lib/views/widgets/drawer_elements.dart new file mode 100644 index 0000000..b765925 --- /dev/null +++ b/lib/views/widgets/drawer_elements.dart @@ -0,0 +1,67 @@ +import 'package:cupcake/coins/abstract/coin_wallet.dart'; +import 'package:cupcake/coins/monero/wallet.dart'; +import 'package:cupcake/gen/assets.gen.dart'; +import 'package:cupcake/l10n/app_localizations.dart'; +import 'package:cupcake/views/home_screen.dart'; +import 'package:cupcake/views/security_backup.dart'; +import 'package:cupcake/views/settings.dart'; +import 'package:cupcake/views/widgets/drawer_element.dart'; +import 'package:flutter/cupertino.dart'; + +class DrawerElements extends StatelessWidget { + const DrawerElements({super.key, required this.coinWallet}); + + final CoinWallet coinWallet; + + Future _walletList(BuildContext context) async { + await HomeScreen.staticPush( + context, + openLastWallet: false, + lastOpenedWallet: coinWallet.walletName, + ); + } + + Future _securityBackup(BuildContext context) async { + await SecurityBackup.staticPush(context, coinWallet); + } + + Future _exportKeyImages(BuildContext context) async { + if (coinWallet is! MoneroWallet) { + throw Exception("coinWallet is not monero - we can't export key images"); + } + await (coinWallet as MoneroWallet).exportKeyImagesUR(context); + } + + Future _otherSettings(BuildContext context) async { + await SettingsView.staticPush(context); + } + + @override + Widget build(BuildContext context) { + final L = AppLocalizations.of(context)!; + return Column( + children: [ + DrawerElement( + svg: Assets.drawerIcons.wallets.svg(), + text: L.wallets, + action: _walletList, + ), + DrawerElement( + svg: Assets.drawerIcons.securityAndBackup.svg(), + text: L.security_and_backup, + action: _securityBackup, + ), + DrawerElement( + svg: Assets.drawerIcons.exportKeyImages.svg(), + text: L.export_key_images, + action: _exportKeyImages, + ), + DrawerElement( + svg: Assets.drawerIcons.otherSettings.svg(), + text: L.other_settings, + action: _otherSettings, + ), + ], + ); + } +} diff --git a/lib/widgets/form_builder.dart b/lib/views/widgets/form_builder.dart similarity index 96% rename from lib/widgets/form_builder.dart rename to lib/views/widgets/form_builder.dart index ecc89ae..3073148 100644 --- a/lib/widgets/form_builder.dart +++ b/lib/views/widgets/form_builder.dart @@ -1,11 +1,14 @@ import 'dart:async'; -import 'package:cupcake/utils/alert.dart'; +import 'package:cupcake/utils/alerts/widget_minimal.dart'; import 'package:cupcake/utils/call_throwable.dart'; import 'package:cupcake/utils/config.dart'; +import 'package:cupcake/utils/form/abstract_form_element.dart'; +import 'package:cupcake/utils/form/pin_form_element.dart'; +import 'package:cupcake/utils/form/single_choice_form_element.dart'; +import 'package:cupcake/utils/form/string_form_element.dart'; import 'package:cupcake/utils/random_name.dart'; import 'package:cupcake/utils/secure_storage.dart'; -import 'package:cupcake/view_model/create_wallet_view_model.dart'; import 'package:cupcake/views/initial_setup_screen.dart'; import 'package:cupcake/views/widgets/numerical_keyboard/main.dart'; import 'package:flutter/material.dart'; diff --git a/pubspec.lock b/pubspec.lock index 62c1e48..f291d4d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,23 +5,23 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834 + sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" url: "https://pub.dev" source: hosted - version: "72.0.0" + version: "76.0.0" _macros: dependency: transitive description: dart source: sdk - version: "0.3.2" + version: "0.3.3" analyzer: dependency: transitive description: name: analyzer - sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139 + sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" url: "https://pub.dev" source: hosted - version: "6.7.0" + version: "6.11.0" archive: dependency: transitive description: @@ -154,10 +154,10 @@ packages: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.0" color: dependency: transitive description: @@ -510,18 +510,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" url: "https://pub.dev" source: hosted - version: "10.0.5" + version: "10.0.7" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.8" leak_tracker_testing: dependency: transitive description: @@ -598,10 +598,10 @@ packages: dependency: transitive description: name: macros - sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" + sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" url: "https://pub.dev" source: hosted - version: "0.1.2-main.4" + version: "0.1.3-main.0" matcher: dependency: transitive description: @@ -855,7 +855,7 @@ packages: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_span: dependency: transitive description: @@ -876,10 +876,10 @@ packages: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.0" stream_channel: dependency: transitive description: @@ -900,10 +900,10 @@ packages: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" sync_http: dependency: transitive description: @@ -924,10 +924,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" url: "https://pub.dev" source: hosted - version: "0.7.2" + version: "0.7.3" time: dependency: transitive description: @@ -1036,10 +1036,10 @@ packages: dependency: transitive description: name: vm_service - sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc + sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b url: "https://pub.dev" source: hosted - version: "14.2.4" + version: "14.3.0" watcher: dependency: transitive description: @@ -1076,10 +1076,10 @@ packages: dependency: transitive description: name: webdriver - sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e" + sha256: "3d773670966f02a646319410766d3b5e1037efb7f07cc68f844d5e06cd4d61c8" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.4" win32: dependency: transitive description: From cd10204804248f3d55bd7ab49ed4ea5a84e00e6c Mon Sep 17 00:00:00 2001 From: Czarek Nakamoto Date: Fri, 31 Jan 2025 15:10:52 +0100 Subject: [PATCH 02/13] fix format script --- .tooling/format.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tooling/format.sh b/.tooling/format.sh index e1c9b82..6d19ee2 100755 --- a/.tooling/format.sh +++ b/.tooling/format.sh @@ -4,7 +4,7 @@ cd "$(dirname "$0")" cd .. dart run build_runner build pushd lib - for dir in coins themes utils view_model views widgets; + for dir in coins themes utils view_model views; do pushd $dir dart fix --apply From 121a469fb7f121fb4d82f9e8ba17d635b415aa9e Mon Sep 17 00:00:00 2001 From: Czarek Nakamoto Date: Fri, 31 Jan 2025 17:20:14 +0100 Subject: [PATCH 03/13] further cleanup, remove global variables fix build issue, update deps bump monero_c --- .env | 2 +- .../com/cakewallet/cupcake/MainActivity.kt | 6 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- android/settings.gradle | 2 +- lib/coins/abstract/address.dart | 9 + lib/coins/abstract/amount.dart | 7 + lib/coins/abstract/coin.dart | 6 +- .../{coin_exception.dart => exception.dart} | 0 .../{coin_strings.dart => strings.dart} | 0 .../{coin_wallet.dart => wallet.dart} | 0 ...coin_wallet_info.dart => wallet_info.dart} | 6 +- lib/coins/list.dart | 11 +- lib/coins/monero/amount.dart | 11 + lib/coins/monero/cache_keys.dart | 4 + lib/coins/monero/coin.dart | 27 ++- .../{monero_strings.dart => strings.dart} | 2 +- lib/coins/monero/wallet.dart | 52 ++-- ...nero_wallet_info.dart => wallet_info.dart} | 12 +- lib/coins/types.dart | 6 + lib/main.dart | 12 +- lib/themes/base_theme.dart | 89 +++---- lib/utils/call_throwable.dart | 2 +- lib/utils/config.dart | 34 +-- .../flutter_secure_storage_value_outcome.dart | 3 +- lib/utils/form/pin_form_element.dart | 6 +- lib/utils/secure_storage.dart | 5 +- lib/utils/secure_storage_key.dart | 3 + lib/utils/urqr.dart | 74 +++--- lib/utils/urqrdetails.dart | 7 + .../barcode_scanner_view_model.dart | 8 +- lib/view_model/create_wallet_view_model.dart | 15 +- lib/view_model/home_screen_view_model.dart | 11 +- lib/view_model/open_wallet_view_model.dart | 2 +- lib/view_model/receive_view_model.dart | 2 +- .../security_backup_view_model.dart | 2 +- lib/view_model/settings_view_model.dart | 2 +- .../unconfirmed_transaction_view_model.dart | 22 +- lib/view_model/urqr_view_model.dart | 8 - lib/view_model/wallet_edit_view_model.dart | 2 +- lib/view_model/wallet_home_view_model.dart | 2 +- lib/views/abstract.dart | 32 +-- .../{urqr.dart => animated_qr_page.dart} | 66 +----- lib/views/barcode_scanner.dart | 2 +- lib/views/create_wallet.dart | 2 +- lib/views/home_screen.dart | 9 +- lib/views/initial_setup_screen.dart | 3 +- lib/views/open_wallet.dart | 2 +- lib/views/receive.dart | 2 +- lib/views/security_backup.dart | 2 +- lib/views/settings.dart | 12 +- lib/views/wallet_edit.dart | 2 +- lib/views/wallet_home.dart | 2 +- .../barcode_scanner/progress_painter.dart | 22 +- .../barcode_scanner/urqr_progress.dart | 20 ++ lib/views/widgets/cupcake_appbar_title.dart | 32 +++ lib/views/widgets/drawer_element.dart | 59 ++--- lib/views/widgets/drawer_elements.dart | 2 +- lib/views/widgets/form_builder.dart | 6 +- lib/views/widgets/urqr.dart | 67 ++++++ pubspec.lock | 224 +++++++++--------- pubspec.yaml | 18 +- 61 files changed, 547 insertions(+), 515 deletions(-) create mode 100644 lib/coins/abstract/address.dart create mode 100644 lib/coins/abstract/amount.dart rename lib/coins/abstract/{coin_exception.dart => exception.dart} (100%) rename lib/coins/abstract/{coin_strings.dart => strings.dart} (100%) rename lib/coins/abstract/{coin_wallet.dart => wallet.dart} (100%) rename lib/coins/abstract/{coin_wallet_info.dart => wallet_info.dart} (85%) create mode 100644 lib/coins/monero/amount.dart create mode 100644 lib/coins/monero/cache_keys.dart rename lib/coins/monero/{monero_strings.dart => strings.dart} (90%) rename lib/coins/monero/{monero_wallet_info.dart => wallet_info.dart} (89%) create mode 100644 lib/utils/secure_storage_key.dart create mode 100644 lib/utils/urqrdetails.dart rename lib/views/{urqr.dart => animated_qr_page.dart} (54%) create mode 100644 lib/views/widgets/barcode_scanner/urqr_progress.dart create mode 100644 lib/views/widgets/cupcake_appbar_title.dart create mode 100644 lib/views/widgets/urqr.dart diff --git a/.env b/.env index 86de819..2fc8d80 100644 --- a/.env +++ b/.env @@ -1,2 +1,2 @@ -MONERO_C_TAG=v0.18.3.4-RC5 +MONERO_C_TAG=v0.18.3.4-RC9 COIN=monero diff --git a/android/app/src/main/kotlin/com/cakewallet/cupcake/MainActivity.kt b/android/app/src/main/kotlin/com/cakewallet/cupcake/MainActivity.kt index 393d5ac..0343cbf 100644 --- a/android/app/src/main/kotlin/com/cakewallet/cupcake/MainActivity.kt +++ b/android/app/src/main/kotlin/com/cakewallet/cupcake/MainActivity.kt @@ -35,8 +35,8 @@ class MainActivity : FlutterActivity() { val packageInfo: PackageInfo if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { packageInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNING_CERTIFICATES) - val signatures = packageInfo.signingInfo.apkContentsSigners - if (signatures.isNotEmpty()) { + val signatures = packageInfo.signingInfo?.apkContentsSigners + if (signatures?.isNotEmpty() == true) { val md = MessageDigest.getInstance("SHA-1") val publicKey = md.digest(signatures[0].toByteArray()) return Base64.encodeToString(publicKey, Base64.DEFAULT) @@ -45,7 +45,7 @@ class MainActivity : FlutterActivity() { @Suppress("DEPRECATION") packageInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES) val signatures = packageInfo.signatures - if (signatures.isNotEmpty()) { + if (signatures?.isNotEmpty() == true) { val md = MessageDigest.getInstance("SHA-1") val publicKey = md.digest(signatures[0].toByteArray()) return Base64.encodeToString(publicKey, Base64.DEFAULT) diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index b0c51f6..afa1e8e 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip diff --git a/android/settings.gradle b/android/settings.gradle index a8c03d6..d4383de 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -19,7 +19,7 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "8.7.0" apply false + id "com.android.application" version '8.8.0' apply false id "org.jetbrains.kotlin.android" version "2.0.21" apply false } diff --git a/lib/coins/abstract/address.dart b/lib/coins/abstract/address.dart new file mode 100644 index 0000000..ca988dc --- /dev/null +++ b/lib/coins/abstract/address.dart @@ -0,0 +1,9 @@ +class Address { + Address(this.address); + final String address; + + @override + String toString() { + return address; + } +} diff --git a/lib/coins/abstract/amount.dart b/lib/coins/abstract/amount.dart new file mode 100644 index 0000000..432a877 --- /dev/null +++ b/lib/coins/abstract/amount.dart @@ -0,0 +1,7 @@ +class Amount { + Amount(this.amount); + final int amount; + + @override + String toString() => "$amount"; +} diff --git a/lib/coins/abstract/coin.dart b/lib/coins/abstract/coin.dart index 477cfa1..48f835b 100644 --- a/lib/coins/abstract/coin.dart +++ b/lib/coins/abstract/coin.dart @@ -1,6 +1,6 @@ -import 'package:cupcake/coins/abstract/coin_strings.dart'; -import 'package:cupcake/coins/abstract/coin_wallet.dart'; -import 'package:cupcake/coins/abstract/coin_wallet_info.dart'; +import 'package:cupcake/coins/abstract/strings.dart'; +import 'package:cupcake/coins/abstract/wallet.dart'; +import 'package:cupcake/coins/abstract/wallet_info.dart'; import 'package:cupcake/coins/types.dart'; enum Coins { monero, unknown } diff --git a/lib/coins/abstract/coin_exception.dart b/lib/coins/abstract/exception.dart similarity index 100% rename from lib/coins/abstract/coin_exception.dart rename to lib/coins/abstract/exception.dart diff --git a/lib/coins/abstract/coin_strings.dart b/lib/coins/abstract/strings.dart similarity index 100% rename from lib/coins/abstract/coin_strings.dart rename to lib/coins/abstract/strings.dart diff --git a/lib/coins/abstract/coin_wallet.dart b/lib/coins/abstract/wallet.dart similarity index 100% rename from lib/coins/abstract/coin_wallet.dart rename to lib/coins/abstract/wallet.dart diff --git a/lib/coins/abstract/coin_wallet_info.dart b/lib/coins/abstract/wallet_info.dart similarity index 85% rename from lib/coins/abstract/coin_wallet_info.dart rename to lib/coins/abstract/wallet_info.dart index dabc358..da15ba8 100644 --- a/lib/coins/abstract/coin_wallet_info.dart +++ b/lib/coins/abstract/wallet_info.dart @@ -1,6 +1,6 @@ import 'package:cupcake/coins/abstract/coin.dart'; -import 'package:cupcake/coins/abstract/coin_wallet.dart'; -import 'package:cupcake/coins/monero/monero_wallet_info.dart'; +import 'package:cupcake/coins/abstract/wallet.dart'; +import 'package:cupcake/coins/monero/wallet_info.dart'; import 'package:cupcake/utils/config.dart'; import 'package:flutter/widgets.dart'; import 'package:path/path.dart' as p; @@ -24,7 +24,7 @@ abstract class CoinWalletInfo { Map toJson() { return { "typeIndex": type.index, - if (config.debug) "typeIndex__debug": type.toString(), + if (CupcakeConfig.instance.debug) "typeIndex__debug": type.toString(), "walletName": p.basename(walletName), }; } diff --git a/lib/coins/list.dart b/lib/coins/list.dart index 866583b..ee78942 100644 --- a/lib/coins/list.dart +++ b/lib/coins/list.dart @@ -3,8 +3,9 @@ import 'package:cupcake/coins/monero/coin.dart'; const moneroEnabled = bool.fromEnvironment("COIN_MONERO", defaultValue: true); -List get walletCoins { - return [ - if (moneroEnabled) Monero(), - ]; -} +// This needs to be a global variable, I'm hoping for it to be tree-shaken, and save us some time +// on generating imports dynamically. + +final List walletCoins = [ + if (moneroEnabled) Monero(), +]; diff --git a/lib/coins/monero/amount.dart b/lib/coins/monero/amount.dart new file mode 100644 index 0000000..639ad6c --- /dev/null +++ b/lib/coins/monero/amount.dart @@ -0,0 +1,11 @@ +import 'package:cupcake/coins/abstract/amount.dart'; +import 'package:monero/monero.dart' as monero; + +class MoneroAmount implements Amount { + MoneroAmount(this.amount); + @override + final int amount; + + @override + String toString() => monero.Wallet_displayAmount(amount); +} diff --git a/lib/coins/monero/cache_keys.dart b/lib/coins/monero/cache_keys.dart new file mode 100644 index 0000000..eaa4d6d --- /dev/null +++ b/lib/coins/monero/cache_keys.dart @@ -0,0 +1,4 @@ +class MoneroCacheKeys { + static const seedOffsetCacheKey = "cakewallet.passphrase"; + static const seedCacheKey = "cakewallet.seed"; +} diff --git a/lib/coins/monero/coin.dart b/lib/coins/monero/coin.dart index c8bdc12..c91607b 100644 --- a/lib/coins/monero/coin.dart +++ b/lib/coins/monero/coin.dart @@ -1,12 +1,13 @@ import 'dart:io'; import 'package:cupcake/coins/abstract/coin.dart'; -import 'package:cupcake/coins/abstract/coin_exception.dart'; -import 'package:cupcake/coins/abstract/coin_strings.dart'; -import 'package:cupcake/coins/abstract/coin_wallet.dart'; -import 'package:cupcake/coins/abstract/coin_wallet_info.dart'; -import 'package:cupcake/coins/monero/monero_strings.dart'; -import 'package:cupcake/coins/monero/monero_wallet_info.dart'; +import 'package:cupcake/coins/abstract/exception.dart'; +import 'package:cupcake/coins/abstract/strings.dart'; +import 'package:cupcake/coins/abstract/wallet.dart'; +import 'package:cupcake/coins/abstract/wallet_info.dart'; +import 'package:cupcake/coins/monero/cache_keys.dart'; +import 'package:cupcake/coins/monero/strings.dart'; +import 'package:cupcake/coins/monero/wallet_info.dart'; import 'package:cupcake/coins/monero/wallet.dart'; import 'package:cupcake/coins/types.dart'; import 'package:cupcake/utils/config.dart'; @@ -15,16 +16,16 @@ import 'package:monero/monero.dart' as monero; import 'package:path/path.dart' as p; import 'package:polyseed/polyseed.dart'; -List wPtrList = []; - class Monero implements Coin { + static List wPtrList = []; + @override bool get isEnabled { try { monero.isLibOk(); return true; } catch (e) { - if (config.debug) { + if (CupcakeConfig.instance.debug) { print("monero.dart: isLibOk failed: $e"); return false; } @@ -95,7 +96,7 @@ class Monero implements Coin { ); } monero.Wallet_setCacheAttribute(newWptr, - key: seedOffsetCacheKey, value: seedOffsetOrEncryption); + key: MoneroCacheKeys.seedOffsetCacheKey, value: seedOffsetOrEncryption); monero.Wallet_store(newWptr); monero.Wallet_store(newWptr); wPtrList.add(newWptr); @@ -145,7 +146,7 @@ class Monero implements Coin { ); } monero.Wallet_setCacheAttribute(newWptr, - key: seedOffsetCacheKey, value: seedOffsetOrEncryption); + key: MoneroCacheKeys.seedOffsetCacheKey, value: seedOffsetOrEncryption); monero.Wallet_store(newWptr); monero.Wallet_store(newWptr); progressCallback?.call(description: "Wallet created"); @@ -306,8 +307,8 @@ class Monero implements Coin { final error = monero.Wallet_errorString(wptr); throw Exception(error); } - config.lastWallet = walletInfo; - config.save(); + CupcakeConfig.instance.lastWallet = walletInfo; + CupcakeConfig.instance.save(); return MoneroWallet(wptr); } diff --git a/lib/coins/monero/monero_strings.dart b/lib/coins/monero/strings.dart similarity index 90% rename from lib/coins/monero/monero_strings.dart rename to lib/coins/monero/strings.dart index 6b618e4..2639107 100644 --- a/lib/coins/monero/monero_strings.dart +++ b/lib/coins/monero/strings.dart @@ -1,4 +1,4 @@ -import 'package:cupcake/coins/abstract/coin_strings.dart'; +import 'package:cupcake/coins/abstract/strings.dart'; import 'package:cupcake/gen/assets.gen.dart'; import 'package:flutter_svg/flutter_svg.dart'; diff --git a/lib/coins/monero/wallet.dart b/lib/coins/monero/wallet.dart index 278ef3c..c262685 100644 --- a/lib/coins/monero/wallet.dart +++ b/lib/coins/monero/wallet.dart @@ -1,28 +1,28 @@ import 'dart:convert'; import 'package:cupcake/coins/abstract/coin.dart'; -import 'package:cupcake/coins/abstract/coin_exception.dart'; -import 'package:cupcake/coins/abstract/coin_wallet.dart'; +import 'package:cupcake/coins/abstract/exception.dart'; +import 'package:cupcake/coins/abstract/wallet.dart'; import 'package:cupcake/coins/abstract/wallet_seed_detail.dart'; import 'package:cupcake/coins/monero/coin.dart'; +import 'package:cupcake/coins/monero/amount.dart'; +import 'package:cupcake/coins/monero/cache_keys.dart'; import 'package:cupcake/coins/types.dart'; import 'package:cupcake/l10n/app_localizations.dart'; import 'package:cupcake/utils/config.dart'; import 'package:cupcake/utils/null_if_empty.dart'; import 'package:cupcake/utils/secure_storage.dart'; +import 'package:cupcake/coins/abstract/address.dart'; import 'package:cupcake/utils/urqr.dart'; import 'package:cupcake/view_model/unconfirmed_transaction_view_model.dart'; import 'package:cupcake/view_model/urqr_view_model.dart'; +import 'package:cupcake/views/animated_qr_page.dart'; import 'package:cupcake/views/unconfirmed_transaction.dart'; -import 'package:cupcake/views/urqr.dart'; import 'package:flutter/cupertino.dart'; import 'package:path/path.dart' as p; import 'package:monero/monero.dart' as monero; import 'package:polyseed/polyseed.dart'; -const seedOffsetCacheKey = "cakewallet.passphrase"; -const seedCacheKey = "cakewallet.seed"; - class MoneroWallet implements CoinWallet { MoneroWallet(this.wptr); monero.wallet wptr; @@ -74,10 +74,12 @@ class MoneroWallet implements CoinWallet { Future exportKeyImagesUR(BuildContext context) async { final allImages = monero.Wallet_exportKeyImagesUR(wptr, - max_fragment_length: config.maxFragmentLength, all: true) + max_fragment_length: CupcakeConfig.instance.maxFragmentLength, + all: true) .split("\n"); final someImages = monero.Wallet_exportKeyImagesUR(wptr, - max_fragment_length: config.maxFragmentLength, all: false) + max_fragment_length: CupcakeConfig.instance.maxFragmentLength, + all: false) .split("\n"); await AnimatedURPage.staticPush( context, @@ -142,7 +144,7 @@ class MoneroWallet implements CoinWallet { fee: fee, confirmCallback: (BuildContext context) async { final signedTx = monero.UnsignedTransaction_signUR( - txptr, config.maxFragmentLength) + txptr, CupcakeConfig.instance.maxFragmentLength) .split("\n"); var status = monero.Wallet_status(wptr); if (status != 0) { @@ -167,12 +169,12 @@ class MoneroWallet implements CoinWallet { } // TODO: make this match the offset used in cake wallet, and define const - String get seedOffset => - monero.Wallet_getCacheAttribute(wptr, key: seedOffsetCacheKey); + String get seedOffset => monero.Wallet_getCacheAttribute(wptr, + key: MoneroCacheKeys.seedOffsetCacheKey); set seedOffset(String newSeedOffset) => monero.Wallet_setCacheAttribute( wptr, - key: seedOffsetCacheKey, + key: MoneroCacheKeys.seedOffsetCacheKey, value: newSeedOffset, ); @@ -190,8 +192,9 @@ class MoneroWallet implements CoinWallet { const coin = PolyseedCoin.POLYSEED_MONERO; var lang = PolyseedLang.getByName("English"); - var polyseedString = - polyseed ?? monero.Wallet_getCacheAttribute(wptr, key: seedCacheKey); + var polyseedString = polyseed ?? + monero.Wallet_getCacheAttribute(wptr, + key: MoneroCacheKeys.seedCacheKey); var seed = Polyseed.decode(polyseedString, lang, coin); if (seedOffset.isNotEmpty) { @@ -213,7 +216,7 @@ class MoneroWallet implements CoinWallet { @override Future close() { monero.WalletManager_closeWallet(Monero.wmPtr, wptr, true); - wPtrList.removeWhere((element) => element.address == wptr.address); + Monero.wPtrList.removeWhere((element) => element.address == wptr.address); return Future.value(); } @@ -292,7 +295,7 @@ class MoneroWallet implements CoinWallet { "restoreHeight": monero.Wallet_getRefreshFromBlockHeight(wptr), }), ), - if (config.debug) + if (CupcakeConfig.instance.debug) ...List.generate( secrets.keys.length, (index) { @@ -303,27 +306,18 @@ class MoneroWallet implements CoinWallet { value: secrets[key] ?? "unknown"); }, ), - if (config.debug) + if (CupcakeConfig.instance.debug) ...List.generate( - config.toJson().keys.length, + CupcakeConfig.instance.toJson().keys.length, (index) { - final key = config.toJson().keys.elementAt(index); + final key = CupcakeConfig.instance.toJson().keys.elementAt(index); return WalletSeedDetail( type: WalletSeedDetailType.text, name: key, value: const JsonEncoder.withIndent(' ') - .convert(config.toJson()[key])); + .convert(CupcakeConfig.instance.toJson()[key])); }, ), ]; } } - -class MoneroAmount implements Amount { - MoneroAmount(this.amount); - @override - final int amount; - - @override - String toString() => monero.Wallet_displayAmount(amount); -} diff --git a/lib/coins/monero/monero_wallet_info.dart b/lib/coins/monero/wallet_info.dart similarity index 89% rename from lib/coins/monero/monero_wallet_info.dart rename to lib/coins/monero/wallet_info.dart index 0913385..170119f 100644 --- a/lib/coins/monero/monero_wallet_info.dart +++ b/lib/coins/monero/wallet_info.dart @@ -1,8 +1,8 @@ import 'dart:io'; import 'package:cupcake/coins/abstract/coin.dart'; -import 'package:cupcake/coins/abstract/coin_wallet.dart'; -import 'package:cupcake/coins/abstract/coin_wallet_info.dart'; +import 'package:cupcake/coins/abstract/wallet.dart'; +import 'package:cupcake/coins/abstract/wallet_info.dart'; import 'package:cupcake/coins/monero/coin.dart'; import 'package:cupcake/views/open_wallet.dart'; import 'package:flutter/cupertino.dart'; @@ -56,10 +56,10 @@ class MoneroWalletInfo extends CoinWalletInfo { @override Future deleteWallet() async { - for (var element in wPtrList) { + for (var element in Monero.wPtrList) { monero.WalletManager_closeWallet(Monero.wmPtr, element, true); } - wPtrList.clear(); + Monero.wPtrList.clear(); File(walletName).deleteSync(); File("$walletName.keys").deleteSync(); } @@ -69,10 +69,10 @@ class MoneroWalletInfo extends CoinWalletInfo { if (p.basename(walletName) == newName) { throw Exception("Wallet wasn't renamed"); } - for (var element in wPtrList) { + for (var element in Monero.wPtrList) { monero.WalletManager_closeWallet(Monero.wmPtr, element, true); } - wPtrList.clear(); + Monero.wPtrList.clear(); final basePath = p.dirname(walletName); File(walletName).copySync(p.join(basePath, newName)); File("$walletName.keys").copySync(p.join(basePath, "$newName.keys")); diff --git a/lib/coins/types.dart b/lib/coins/types.dart index adecd2d..01af7bb 100644 --- a/lib/coins/types.dart +++ b/lib/coins/types.dart @@ -4,3 +4,9 @@ enum WalletSeedDetailType { text, qr, } + +enum CreateMethod { + any, + create, + restore, +} diff --git a/lib/main.dart b/lib/main.dart index 7b28f01..d9947d4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -15,11 +15,11 @@ String signingKeyFound = ""; Future appInit() async { WidgetsFlutterBinding.ensureInitialized(); await initializeBaseStoragePath(); - if (config.initialSetupComplete == false) { + if (CupcakeConfig.instance.initialSetupComplete == false) { final oldSecureStorage = await secureStorage.readAll(); final date = DateTime.now().toIso8601String(); - config.oldSecureStorage[date] = oldSecureStorage; - config.save(); + CupcakeConfig.instance.oldSecureStorage[date] = oldSecureStorage; + CupcakeConfig.instance.save(); await secureStorage.deleteAll(); } } @@ -37,9 +37,9 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, - title: 'Cup Cake', + title: 'Cupcake', themeMode: ThemeMode.dark, - darkTheme: darkBaseTheme, + darkTheme: BaseTheme.darkBaseTheme, localizationsDelegates: const [ AppLocalizations.delegate, GlobalMaterialLocalizations.delegate, @@ -58,7 +58,7 @@ class MyApp extends StatelessWidget { ], ); }, - home: config.initialSetupComplete + home: CupcakeConfig.instance.initialSetupComplete ? HomeScreen( viewModel: HomeScreenViewModel(openLastWallet: true), ) diff --git a/lib/themes/base_theme.dart b/lib/themes/base_theme.dart index 9b7114e..0843914 100644 --- a/lib/themes/base_theme.dart +++ b/lib/themes/base_theme.dart @@ -1,46 +1,49 @@ import 'package:flutter/material.dart'; -const baseBackgroundColor = Color.fromRGBO(25, 35, 60, 1); -const onBackgroundColor = Color.fromRGBO(35, 44, 79, 1); -const onOnBackgroundColor = Color.fromRGBO(35, 55, 93, 1); -const buttonBorderColor = Color.fromRGBO(79, 91, 121, 1); +class BaseTheme { + static const baseBackgroundColor = Color.fromRGBO(25, 35, 60, 1); + static const onBackgroundColor = Color.fromRGBO(35, 44, 79, 1); + static const onOnBackgroundColor = Color.fromRGBO(35, 55, 93, 1); + static const buttonBorderColor = Color.fromRGBO(79, 91, 121, 1); -final darkBaseTheme = ThemeData.dark(useMaterial3: true).copyWith( - scaffoldBackgroundColor: baseBackgroundColor, - cardTheme: const CardTheme( - color: onBackgroundColor, - ), - elevatedButtonTheme: ElevatedButtonThemeData( - style: ElevatedButton.styleFrom( - backgroundColor: onOnBackgroundColor, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30)), - textStyle: const TextStyle( - color: Colors.white, - )), - ), - drawerTheme: const DrawerThemeData( - backgroundColor: baseBackgroundColor, - ), - textTheme: const TextTheme( - displayLarge: TextStyle(color: Colors.white), - displayMedium: TextStyle(color: Colors.white), - displaySmall: TextStyle(color: Colors.white), - headlineLarge: TextStyle(color: Colors.white), - headlineMedium: TextStyle(color: Colors.white), - headlineSmall: TextStyle(color: Colors.white), - titleLarge: TextStyle(color: Colors.white), - titleMedium: TextStyle(color: Colors.white), - titleSmall: TextStyle(color: Colors.white), - bodyLarge: TextStyle(color: Colors.white), - bodyMedium: TextStyle(color: Color.fromRGBO(99, 113, 150, 1)), - bodySmall: TextStyle(color: Colors.white), - labelLarge: TextStyle(color: Colors.white), - labelMedium: TextStyle(color: Colors.white), - labelSmall: TextStyle(color: Colors.white), - ).apply(fontFamily: "Lato"), - dividerColor: onOnBackgroundColor, - appBarTheme: const AppBarTheme( - centerTitle: true, - backgroundColor: Colors.transparent, - ), -); + static final darkBaseTheme = ThemeData.dark(useMaterial3: true).copyWith( + scaffoldBackgroundColor: baseBackgroundColor, + cardTheme: const CardTheme( + color: onBackgroundColor, + ), + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + backgroundColor: onOnBackgroundColor, + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(30)), + textStyle: const TextStyle( + color: Colors.white, + )), + ), + drawerTheme: const DrawerThemeData( + backgroundColor: baseBackgroundColor, + ), + textTheme: const TextTheme( + displayLarge: TextStyle(color: Colors.white), + displayMedium: TextStyle(color: Colors.white), + displaySmall: TextStyle(color: Colors.white), + headlineLarge: TextStyle(color: Colors.white), + headlineMedium: TextStyle(color: Colors.white), + headlineSmall: TextStyle(color: Colors.white), + titleLarge: TextStyle(color: Colors.white), + titleMedium: TextStyle(color: Colors.white), + titleSmall: TextStyle(color: Colors.white), + bodyLarge: TextStyle(color: Colors.white), + bodyMedium: TextStyle(color: Color.fromRGBO(99, 113, 150, 1)), + bodySmall: TextStyle(color: Colors.white), + labelLarge: TextStyle(color: Colors.white), + labelMedium: TextStyle(color: Colors.white), + labelSmall: TextStyle(color: Colors.white), + ).apply(fontFamily: "Lato"), + dividerColor: onOnBackgroundColor, + appBarTheme: const AppBarTheme( + centerTitle: true, + backgroundColor: Colors.transparent, + ), + ); +} diff --git a/lib/utils/call_throwable.dart b/lib/utils/call_throwable.dart index 3d2e385..b765214 100644 --- a/lib/utils/call_throwable.dart +++ b/lib/utils/call_throwable.dart @@ -1,6 +1,6 @@ import 'dart:async'; -import 'package:cupcake/coins/abstract/coin_exception.dart'; +import 'package:cupcake/coins/abstract/exception.dart'; import 'package:cupcake/utils/alerts/basic.dart'; import 'package:flutter/cupertino.dart'; diff --git a/lib/utils/config.dart b/lib/utils/config.dart index 9726cbf..9541bb8 100644 --- a/lib/utils/config.dart +++ b/lib/utils/config.dart @@ -1,7 +1,7 @@ import 'dart:convert'; import 'dart:io'; -import 'package:cupcake/coins/abstract/coin_wallet_info.dart'; +import 'package:cupcake/coins/abstract/wallet_info.dart'; import 'package:cupcake/utils/filesystem.dart'; import 'package:flutter/foundation.dart'; import 'package:path/path.dart' as p; @@ -59,21 +59,21 @@ class CupcakeConfig { void save() { File(configPath).writeAsStringSync(json.encode(toJson())); } -} -final configPath = p.join(baseStoragePath, "config.json"); -final config = (() { - try { - return CupcakeConfig.fromJson( - json.decode( - File(configPath).readAsStringSync(), - ), - ); - } catch (e) { - if (kDebugMode) { - print("failed getting wallet config: $e"); - print("don't worry tho - I'll create config with defaults"); + static final configPath = p.join(baseStoragePath, "config.json"); + static final instance = (() { + try { + return CupcakeConfig.fromJson( + json.decode( + File(configPath).readAsStringSync(), + ), + ); + } catch (e) { + if (kDebugMode) { + print("failed getting wallet config: $e"); + print("don't worry tho - I'll create config with defaults"); + } + return CupcakeConfig.fromJson({}); } - return CupcakeConfig.fromJson({}); - } -})(); + })(); +} diff --git a/lib/utils/form/flutter_secure_storage_value_outcome.dart b/lib/utils/form/flutter_secure_storage_value_outcome.dart index 4bababa..d8d21d2 100644 --- a/lib/utils/form/flutter_secure_storage_value_outcome.dart +++ b/lib/utils/form/flutter_secure_storage_value_outcome.dart @@ -37,7 +37,7 @@ class FlutterSecureStorageValueOutcome implements ValueOutcome { return; } if (!canWrite) { - if (config.debug) { + if (CupcakeConfig.instance.debug) { throw Exception( "DEBUG_ONLY: canWrite is false but we tried to flush the value"); } @@ -67,6 +67,5 @@ class FlutterSecureStorageValueOutcome implements ValueOutcome { } @override - // TODO: implement uniqueId String get uniqueId => key; } diff --git a/lib/utils/form/pin_form_element.dart b/lib/utils/form/pin_form_element.dart index aa30246..1e660eb 100644 --- a/lib/utils/form/pin_form_element.dart +++ b/lib/utils/form/pin_form_element.dart @@ -6,8 +6,6 @@ import 'package:cupcake/utils/form/default_validator.dart'; import 'package:flutter/cupertino.dart'; import 'package:local_auth/local_auth.dart'; -final auth = LocalAuthentication(); - class PinFormElement extends FormElement { PinFormElement({ String initialText = "", @@ -22,7 +20,9 @@ class PinFormElement extends FormElement { Future loadSecureStorageValue(VoidCallback callback) async { if (ctrl.text.isNotEmpty) return; - if (!config.biometricEnabled) return; + if (!CupcakeConfig.instance.biometricEnabled) return; + final auth = LocalAuthentication(); + final List availableBiometrics = await auth.getAvailableBiometrics(); final bool canAuthenticateWithBiometrics = await auth.canCheckBiometrics; diff --git a/lib/utils/secure_storage.dart b/lib/utils/secure_storage.dart index 9d0a706..b5fc4fb 100644 --- a/lib/utils/secure_storage.dart +++ b/lib/utils/secure_storage.dart @@ -1,3 +1,4 @@ +import 'package:cupcake/utils/secure_storage_key.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; AndroidOptions _getAndroidOptions() => const AndroidOptions( @@ -7,10 +8,6 @@ AndroidOptions _getAndroidOptions() => const AndroidOptions( final FlutterSecureStorage secureStorage = FlutterSecureStorage(aOptions: _getAndroidOptions()); -class SecureStorageKey { - static String pin = "secret.pin"; -} - Future setWalletPin(String password) async { final pin = await secureStorage.read(key: SecureStorageKey.pin); if (pin != null) throw Exception("${SecureStorageKey.pin} is not null"); diff --git a/lib/utils/secure_storage_key.dart b/lib/utils/secure_storage_key.dart new file mode 100644 index 0000000..6824513 --- /dev/null +++ b/lib/utils/secure_storage_key.dart @@ -0,0 +1,3 @@ +class SecureStorageKey { + static String pin = "secret.pin"; +} diff --git a/lib/utils/urqr.dart b/lib/utils/urqr.dart index fec6222..da5d264 100644 --- a/lib/utils/urqr.dart +++ b/lib/utils/urqr.dart @@ -1,40 +1,3 @@ -URQRData URQRToURQRData(List urqr_) { - final urqr = urqr_.toSet().toList(); - urqr.sort((s1, s2) { - final s1s = s1.split("/"); - final s1frameStr = s1s[1].split("-"); - final s1curFrame = int.parse(s1frameStr[0]); - final s2s = s2.split("/"); - final s2frameStr = s2s[1].split("-"); - final s2curFrame = int.parse(s2frameStr[0]); - return s1curFrame - s2curFrame; - }); - - String tag = ''; - int count = 0; - String bw = ''; - for (var elm in urqr) { - final s = elm.substring(elm.indexOf(":") + 1); // strip down ur: prefix - final s2 = s.split("/"); - tag = s2[0]; - final frameStr = s2[1].split("-"); - // final curFrame = int.parse(frameStr[0]); - count = int.parse(frameStr[1]); - final byteWords = s2[2]; - bw += byteWords; - } - String? error; - - return URQRData( - tag: tag, - str: bw, - progress: count == 0 ? 0 : (urqr.length / count), - count: count, - error: error ?? "", - inputs: urqr, - ); -} - class URQRData { URQRData( {required this.tag, @@ -59,4 +22,41 @@ class URQRData { "inputs": inputs, }; } + + static URQRData parse(List urqr_) { + final urqr = urqr_.toSet().toList(); + urqr.sort((s1, s2) { + final s1s = s1.split("/"); + final s1frameStr = s1s[1].split("-"); + final s1curFrame = int.parse(s1frameStr[0]); + final s2s = s2.split("/"); + final s2frameStr = s2s[1].split("-"); + final s2curFrame = int.parse(s2frameStr[0]); + return s1curFrame - s2curFrame; + }); + + String tag = ''; + int count = 0; + String bw = ''; + for (var elm in urqr) { + final s = elm.substring(elm.indexOf(":") + 1); // strip down ur: prefix + final s2 = s.split("/"); + tag = s2[0]; + final frameStr = s2[1].split("-"); + // final curFrame = int.parse(frameStr[0]); + count = int.parse(frameStr[1]); + final byteWords = s2[2]; + bw += byteWords; + } + String? error; + + return URQRData( + tag: tag, + str: bw, + progress: count == 0 ? 0 : (urqr.length / count), + count: count, + error: error ?? "", + inputs: urqr, + ); + } } diff --git a/lib/utils/urqrdetails.dart b/lib/utils/urqrdetails.dart new file mode 100644 index 0000000..ae83d82 --- /dev/null +++ b/lib/utils/urqrdetails.dart @@ -0,0 +1,7 @@ +class URQRDetails { + URQRDetails( + {required this.tag, required this.description, required this.values}); + String tag; + String description; + List values; +} diff --git a/lib/view_model/barcode_scanner_view_model.dart b/lib/view_model/barcode_scanner_view_model.dart index 3e6b1b8..17d5562 100644 --- a/lib/view_model/barcode_scanner_view_model.dart +++ b/lib/view_model/barcode_scanner_view_model.dart @@ -1,8 +1,8 @@ -import 'package:cupcake/coins/abstract/coin_wallet.dart'; +import 'package:cupcake/coins/abstract/wallet.dart'; import 'package:cupcake/utils/call_throwable.dart'; import 'package:cupcake/utils/urqr.dart'; import 'package:cupcake/view_model/abstract.dart'; -import 'package:cupcake/views/widgets/barcode_scanner/progress_painter.dart'; +import 'package:cupcake/views/widgets/barcode_scanner/urqr_progress.dart'; import 'package:fast_scanner/fast_scanner.dart'; import 'package:flutter/cupertino.dart'; @@ -14,7 +14,7 @@ class BarcodeScannerViewModel extends ViewModel { bool popped = false; List urCodes = []; - late var ur = URQRToURQRData(urCodes); + late var ur = URQRData.parse(urCodes); final CoinWallet wallet; @@ -48,7 +48,7 @@ class BarcodeScannerViewModel extends ViewModel { } if (urCodes.contains(barcode.rawValue)) return; urCodes.add(barcode.rawValue!); - ur = URQRToURQRData(urCodes); + ur = URQRData.parse(urCodes); markNeedsBuild(); } diff --git a/lib/view_model/create_wallet_view_model.dart b/lib/view_model/create_wallet_view_model.dart index 30c07c3..cf85e00 100644 --- a/lib/view_model/create_wallet_view_model.dart +++ b/lib/view_model/create_wallet_view_model.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:cupcake/coins/abstract/coin.dart'; import 'package:cupcake/coins/list.dart'; +import 'package:cupcake/coins/types.dart'; import 'package:cupcake/utils/config.dart'; import 'package:cupcake/utils/form/abstract_form_element.dart'; import 'package:cupcake/utils/form/flutter_secure_storage_value_outcome.dart'; @@ -19,12 +20,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:share_plus/share_plus.dart'; -enum CreateMethod { - any, - create, - restore, -} - class CreateWalletViewModel extends ViewModel { CreateWalletViewModel({ required this.createMethod, @@ -282,8 +277,8 @@ class CreateWalletViewModel extends ViewModel { topAction: seedOffset.ctrl.text.isNotEmpty ? null : () { - config.initialSetupComplete = true; - config.save(); + CupcakeConfig.instance.initialSetupComplete = true; + CupcakeConfig.instance.save(); WalletHome.pushStatic(context, cw); }, topActionText: Text(L.next), @@ -329,8 +324,8 @@ class CreateWalletViewModel extends ViewModel { NewWalletInfoPage( topText: L.wallet_passphrase, topAction: () { - config.initialSetupComplete = true; - config.save(); + CupcakeConfig.instance.initialSetupComplete = true; + CupcakeConfig.instance.save(); WalletHome.pushStatic(context, cw); }, topActionText: Text(L.next), diff --git a/lib/view_model/home_screen_view_model.dart b/lib/view_model/home_screen_view_model.dart index d652ce8..7333585 100644 --- a/lib/view_model/home_screen_view_model.dart +++ b/lib/view_model/home_screen_view_model.dart @@ -1,4 +1,4 @@ -import 'package:cupcake/coins/abstract/coin_wallet_info.dart'; +import 'package:cupcake/coins/abstract/wallet_info.dart'; import 'package:cupcake/coins/list.dart'; import 'package:cupcake/utils/config.dart'; import 'package:cupcake/view_model/abstract.dart'; @@ -20,17 +20,18 @@ class HomeScreenViewModel extends ViewModel { wallets.addAll(toAdd); } } - if (config.walletSort == 0) { + if (CupcakeConfig.instance.walletSort == 0) { wallets.sort((a, b) => b.walletName.compareTo(a.walletName)); - } else if (config.walletSort == 1) { + } else if (CupcakeConfig.instance.walletSort == 1) { wallets.sort((a, b) => a.walletName.compareTo(b.walletName)); } return wallets; } void toggleSort() { - config.walletSort = (config.walletSort + 1) % 2; - config.save(); + CupcakeConfig.instance.walletSort = + (CupcakeConfig.instance.walletSort + 1) % 2; + CupcakeConfig.instance.save(); markNeedsBuild(); } diff --git a/lib/view_model/open_wallet_view_model.dart b/lib/view_model/open_wallet_view_model.dart index 905252e..18f1115 100644 --- a/lib/view_model/open_wallet_view_model.dart +++ b/lib/view_model/open_wallet_view_model.dart @@ -1,4 +1,4 @@ -import 'package:cupcake/coins/abstract/coin_wallet_info.dart'; +import 'package:cupcake/coins/abstract/wallet_info.dart'; import 'package:cupcake/utils/call_throwable.dart'; import 'package:cupcake/utils/form/flutter_secure_storage_value_outcome.dart'; import 'package:cupcake/utils/form/pin_form_element.dart'; diff --git a/lib/view_model/receive_view_model.dart b/lib/view_model/receive_view_model.dart index 53779b6..6d7edd6 100644 --- a/lib/view_model/receive_view_model.dart +++ b/lib/view_model/receive_view_model.dart @@ -1,4 +1,4 @@ -import 'package:cupcake/coins/abstract/coin_wallet.dart'; +import 'package:cupcake/coins/abstract/wallet.dart'; import 'package:cupcake/view_model/abstract.dart'; class ReceiveViewModel extends ViewModel { diff --git a/lib/view_model/security_backup_view_model.dart b/lib/view_model/security_backup_view_model.dart index 6c540bb..506d6b9 100644 --- a/lib/view_model/security_backup_view_model.dart +++ b/lib/view_model/security_backup_view_model.dart @@ -1,4 +1,4 @@ -import 'package:cupcake/coins/abstract/coin_wallet.dart'; +import 'package:cupcake/coins/abstract/wallet.dart'; import 'package:cupcake/utils/form/abstract_form_element.dart'; import 'package:cupcake/utils/form/flutter_secure_storage_value_outcome.dart'; import 'package:cupcake/utils/form/pin_form_element.dart'; diff --git a/lib/view_model/settings_view_model.dart b/lib/view_model/settings_view_model.dart index b954d40..57b711c 100644 --- a/lib/view_model/settings_view_model.dart +++ b/lib/view_model/settings_view_model.dart @@ -7,5 +7,5 @@ class SettingsViewModel extends ViewModel { @override String get screenName => "Settings"; - CupcakeConfig get appConfig => config; + CupcakeConfig get appConfig => CupcakeConfig.instance; } diff --git a/lib/view_model/unconfirmed_transaction_view_model.dart b/lib/view_model/unconfirmed_transaction_view_model.dart index 50a68a0..a8e5755 100644 --- a/lib/view_model/unconfirmed_transaction_view_model.dart +++ b/lib/view_model/unconfirmed_transaction_view_model.dart @@ -1,27 +1,11 @@ import 'dart:async'; -import 'package:cupcake/coins/abstract/coin_wallet.dart'; +import 'package:cupcake/coins/abstract/address.dart'; +import 'package:cupcake/coins/abstract/wallet.dart'; +import 'package:cupcake/coins/abstract/amount.dart'; import 'package:cupcake/view_model/abstract.dart'; import 'package:flutter/cupertino.dart'; -class Address { - Address(this.address); - final String address; - - @override - String toString() { - return address; - } -} - -class Amount { - Amount(this.amount); - final int amount; - - @override - String toString() => "$amount"; -} - class UnconfirmedTransactionViewModel extends ViewModel { UnconfirmedTransactionViewModel( {required this.wallet, diff --git a/lib/view_model/urqr_view_model.dart b/lib/view_model/urqr_view_model.dart index 455e5f2..f151986 100644 --- a/lib/view_model/urqr_view_model.dart +++ b/lib/view_model/urqr_view_model.dart @@ -1,13 +1,5 @@ import 'package:cupcake/view_model/abstract.dart'; -class URQRDetails { - URQRDetails( - {required this.tag, required this.description, required this.values}); - String tag; - String description; - List values; -} - class URQRViewModel extends ViewModel { URQRViewModel({ required this.urqrList, diff --git a/lib/view_model/wallet_edit_view_model.dart b/lib/view_model/wallet_edit_view_model.dart index e1fa9d6..6a68a2b 100644 --- a/lib/view_model/wallet_edit_view_model.dart +++ b/lib/view_model/wallet_edit_view_model.dart @@ -1,4 +1,4 @@ -import 'package:cupcake/coins/abstract/coin_wallet_info.dart'; +import 'package:cupcake/coins/abstract/wallet_info.dart'; import 'package:cupcake/utils/form/abstract_form_element.dart'; import 'package:cupcake/utils/form/flutter_secure_storage_value_outcome.dart'; import 'package:cupcake/utils/form/pin_form_element.dart'; diff --git a/lib/view_model/wallet_home_view_model.dart b/lib/view_model/wallet_home_view_model.dart index 7f8cd08..21cd13a 100644 --- a/lib/view_model/wallet_home_view_model.dart +++ b/lib/view_model/wallet_home_view_model.dart @@ -1,5 +1,5 @@ import 'package:cupcake/coins/abstract/coin.dart'; -import 'package:cupcake/coins/abstract/coin_wallet.dart'; +import 'package:cupcake/coins/abstract/wallet.dart'; import 'package:cupcake/view_model/abstract.dart'; import 'package:cupcake/views/barcode_scanner.dart'; import 'package:flutter/cupertino.dart'; diff --git a/lib/views/abstract.dart b/lib/views/abstract.dart index 7ba34f8..e4c6a78 100644 --- a/lib/views/abstract.dart +++ b/lib/views/abstract.dart @@ -2,8 +2,8 @@ import 'dart:async'; import 'package:cupcake/l10n/app_localizations.dart'; import 'package:cupcake/view_model/abstract.dart'; +import 'package:cupcake/views/widgets/cupcake_appbar_title.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; // Since there is no performance penalty for using stateful widgets I would just // use them everywhere, but honestly all I need in stateless widgets is easy @@ -95,33 +95,3 @@ class AbstractView extends StatefulWidget { state!.setState(() {}); } } - -class CupcakeAppbarTitle extends StatelessWidget { - const CupcakeAppbarTitle({super.key}); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - SvgPicture.asset("assets/icons/icon-white.svg", - height: 32, width: 32, color: Colors.white), - const SizedBox( - width: 12, - ), - const Text( - "Cupcake", - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 28, - color: Colors.white, - ), - ), - const Spacer(), - ], - ), - ); - } -} diff --git a/lib/views/urqr.dart b/lib/views/animated_qr_page.dart similarity index 54% rename from lib/views/urqr.dart rename to lib/views/animated_qr_page.dart index 0b7e43e..80a2c8a 100644 --- a/lib/views/urqr.dart +++ b/lib/views/animated_qr_page.dart @@ -1,11 +1,8 @@ -import 'dart:async'; - -import 'package:cupcake/utils/config.dart'; import 'package:cupcake/view_model/urqr_view_model.dart'; import 'package:cupcake/views/abstract.dart'; +import 'package:cupcake/views/widgets/urqr.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:qr_flutter/qr_flutter.dart'; //ignore: must_be_immutable class AnimatedURPage extends AbstractView { @@ -62,64 +59,3 @@ class AnimatedURPage extends AbstractView { child: Text(key)); } } - -class URQR extends StatefulWidget { - URQR({super.key, required this.frames}); - - List frames; - - @override - // ignore: library_private_types_in_public_api - _URQRState createState() => _URQRState(); -} - -class _URQRState extends State { - Timer? t; - int frame = 0; - @override - void initState() { - super.initState(); - setState(() { - t = Timer.periodic(Duration(milliseconds: config.msForQrCode), (timer) { - _nextFrame(); - }); - }); - } - - void _nextFrame() { - setState(() { - frame++; - }); - } - - @override - void dispose() { - t?.cancel(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Center( - child: Container( - padding: const EdgeInsets.all(17.0), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(30.0), - color: Colors.white, - ), - child: QrImageView( - foregroundColor: Colors.black, - data: widget.frames[frame % widget.frames.length], - version: -1, - size: 275, - ), - ), - ), - ], - ); - } -} diff --git a/lib/views/barcode_scanner.dart b/lib/views/barcode_scanner.dart index 74850e8..7fcd92b 100644 --- a/lib/views/barcode_scanner.dart +++ b/lib/views/barcode_scanner.dart @@ -1,4 +1,4 @@ -import 'package:cupcake/coins/abstract/coin_wallet.dart'; +import 'package:cupcake/coins/abstract/wallet.dart'; import 'package:cupcake/view_model/barcode_scanner_view_model.dart'; import 'package:cupcake/views/abstract.dart'; import 'package:cupcake/views/widgets/barcode_scanner/progress_painter.dart'; diff --git a/lib/views/create_wallet.dart b/lib/views/create_wallet.dart index db16e0f..8e1408f 100644 --- a/lib/views/create_wallet.dart +++ b/lib/views/create_wallet.dart @@ -143,7 +143,7 @@ class CreateWallet extends AbstractView { bool isFormBad(List form) { for (var element in form) { if (!element.isOk) { - if (config.debug) { + if (CupcakeConfig.instance.debug) { print("${element.label} is not valid: "); } return true; diff --git a/lib/views/home_screen.dart b/lib/views/home_screen.dart index 4187017..bdfa47d 100644 --- a/lib/views/home_screen.dart +++ b/lib/views/home_screen.dart @@ -1,4 +1,5 @@ -import 'package:cupcake/coins/abstract/coin_wallet_info.dart'; +import 'package:cupcake/coins/abstract/wallet_info.dart'; +import 'package:cupcake/coins/types.dart'; import 'package:cupcake/utils/call_throwable.dart'; import 'package:cupcake/utils/config.dart'; import 'package:cupcake/view_model/create_wallet_view_model.dart'; @@ -158,10 +159,10 @@ class HomeScreen extends AbstractView { @override Future initState(BuildContext context) async { await Future.delayed(Duration.zero); // load the screen - if (config.lastWallet == null) return; + if (CupcakeConfig.instance.lastWallet == null) return; if (!context.mounted) return; if (!viewModel.openLastWallet) return; - if (config.lastWallet?.exists() != true) return; - config.lastWallet!.openUI(context); + if (CupcakeConfig.instance.lastWallet?.exists() != true) return; + CupcakeConfig.instance.lastWallet!.openUI(context); } } diff --git a/lib/views/initial_setup_screen.dart b/lib/views/initial_setup_screen.dart index a8dbc50..52305f7 100644 --- a/lib/views/initial_setup_screen.dart +++ b/lib/views/initial_setup_screen.dart @@ -1,3 +1,4 @@ +import 'package:cupcake/coins/types.dart'; import 'package:cupcake/themes/base_theme.dart'; import 'package:cupcake/view_model/create_wallet_view_model.dart'; import 'package:cupcake/view_model/initial_setup_view_model.dart'; @@ -85,7 +86,7 @@ class LongSecondaryButton extends LongPrimaryButton { const WidgetStatePropertyAll(Colors.white); @override - Color get textColor => onBackgroundColor; + Color get textColor => BaseTheme.onBackgroundColor; } class LongPrimaryButton extends StatelessWidget { diff --git a/lib/views/open_wallet.dart b/lib/views/open_wallet.dart index 5f99c42..bb4132e 100644 --- a/lib/views/open_wallet.dart +++ b/lib/views/open_wallet.dart @@ -1,4 +1,4 @@ -import 'package:cupcake/coins/abstract/coin_wallet_info.dart'; +import 'package:cupcake/coins/abstract/wallet_info.dart'; import 'package:cupcake/view_model/open_wallet_view_model.dart'; import 'package:cupcake/views/abstract.dart'; import 'package:cupcake/views/widgets/form_builder.dart'; diff --git a/lib/views/receive.dart b/lib/views/receive.dart index 3219126..170978b 100644 --- a/lib/views/receive.dart +++ b/lib/views/receive.dart @@ -1,4 +1,4 @@ -import 'package:cupcake/coins/abstract/coin_wallet.dart'; +import 'package:cupcake/coins/abstract/wallet.dart'; import 'package:cupcake/view_model/receive_view_model.dart'; import 'package:cupcake/views/abstract.dart'; import 'package:flutter/cupertino.dart'; diff --git a/lib/views/security_backup.dart b/lib/views/security_backup.dart index a3a3224..3a1fc76 100644 --- a/lib/views/security_backup.dart +++ b/lib/views/security_backup.dart @@ -1,4 +1,4 @@ -import 'package:cupcake/coins/abstract/coin_wallet.dart'; +import 'package:cupcake/coins/abstract/wallet.dart'; import 'package:cupcake/coins/abstract/wallet_seed_detail.dart'; import 'package:cupcake/coins/types.dart'; import 'package:cupcake/utils/alerts/widget.dart'; diff --git a/lib/views/settings.dart b/lib/views/settings.dart index 7e72d72..7c8bc4e 100644 --- a/lib/views/settings.dart +++ b/lib/views/settings.dart @@ -29,7 +29,7 @@ class SettingsView extends AbstractView { Widget? body(BuildContext context) { return Column( children: [ - if (config.debug) + if (CupcakeConfig.instance.debug) BooleanConfigElement( title: "Debug", subtitleEnabled: "Debug options are enabled", @@ -138,9 +138,9 @@ class _VersionWidgetState extends State { Future _debugTrigger() async { if (easterEgg.isEmpty) { - if (config.debug) return; - config.debug = true; - config.save(); + if (CupcakeConfig.instance.debug) return; + CupcakeConfig.instance.debug = true; + CupcakeConfig.instance.save(); setState(() { subtitle = "debug options enabled"; }); @@ -183,8 +183,8 @@ class IntegerConfigElement extends StatelessWidget { return ListTile( title: Text(title), onLongPress: () { - config.debug = true; - config.save(); + CupcakeConfig.instance.debug = true; + CupcakeConfig.instance.save(); }, subtitle: TextField( controller: ctrl, diff --git a/lib/views/wallet_edit.dart b/lib/views/wallet_edit.dart index ffb1832..9bfcac5 100644 --- a/lib/views/wallet_edit.dart +++ b/lib/views/wallet_edit.dart @@ -1,4 +1,4 @@ -import 'package:cupcake/coins/abstract/coin_wallet_info.dart'; +import 'package:cupcake/coins/abstract/wallet_info.dart'; import 'package:cupcake/utils/call_throwable.dart'; import 'package:cupcake/view_model/wallet_edit_view_model.dart'; import 'package:cupcake/views/abstract.dart'; diff --git a/lib/views/wallet_home.dart b/lib/views/wallet_home.dart index 4adbce0..9780ebc 100644 --- a/lib/views/wallet_home.dart +++ b/lib/views/wallet_home.dart @@ -1,4 +1,4 @@ -import 'package:cupcake/coins/abstract/coin_wallet.dart'; +import 'package:cupcake/coins/abstract/wallet.dart'; import 'package:cupcake/view_model/wallet_home_view_model.dart'; import 'package:cupcake/views/abstract.dart'; import 'package:cupcake/views/barcode_scanner.dart'; diff --git a/lib/views/widgets/barcode_scanner/progress_painter.dart b/lib/views/widgets/barcode_scanner/progress_painter.dart index f201d34..f68e769 100644 --- a/lib/views/widgets/barcode_scanner/progress_painter.dart +++ b/lib/views/widgets/barcode_scanner/progress_painter.dart @@ -1,5 +1,6 @@ import 'dart:math'; +import 'package:cupcake/views/widgets/barcode_scanner/urqr_progress.dart'; import 'package:flutter/material.dart'; class ProgressPainter extends CustomPainter { @@ -39,24 +40,3 @@ class ProgressPainter extends CustomPainter { return urQrProgress != oldDelegate.urQrProgress; } } - -class URQrProgress { - int expectedPartCount; - int processedPartsCount; - List receivedPartIndexes; - double percentage; - - URQrProgress({ - required this.expectedPartCount, - required this.processedPartsCount, - required this.receivedPartIndexes, - required this.percentage, - }); - - bool equals(URQrProgress? progress) { - if (progress == null) { - return false; - } - return processedPartsCount == progress.processedPartsCount; - } -} diff --git a/lib/views/widgets/barcode_scanner/urqr_progress.dart b/lib/views/widgets/barcode_scanner/urqr_progress.dart new file mode 100644 index 0000000..839c71a --- /dev/null +++ b/lib/views/widgets/barcode_scanner/urqr_progress.dart @@ -0,0 +1,20 @@ +class URQrProgress { + int expectedPartCount; + int processedPartsCount; + List receivedPartIndexes; + double percentage; + + URQrProgress({ + required this.expectedPartCount, + required this.processedPartsCount, + required this.receivedPartIndexes, + required this.percentage, + }); + + bool equals(URQrProgress? progress) { + if (progress == null) { + return false; + } + return processedPartsCount == progress.processedPartsCount; + } +} diff --git a/lib/views/widgets/cupcake_appbar_title.dart b/lib/views/widgets/cupcake_appbar_title.dart new file mode 100644 index 0000000..a472aa5 --- /dev/null +++ b/lib/views/widgets/cupcake_appbar_title.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +class CupcakeAppbarTitle extends StatelessWidget { + const CupcakeAppbarTitle({super.key}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + SvgPicture.asset("assets/icons/icon-white.svg", + height: 32, width: 32, color: Colors.white), + const SizedBox( + width: 12, + ), + const Text( + "Cupcake", + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 28, + color: Colors.white, + ), + ), + const Spacer(), + ], + ), + ); + } +} diff --git a/lib/views/widgets/drawer_element.dart b/lib/views/widgets/drawer_element.dart index ceb529c..72ebf93 100644 --- a/lib/views/widgets/drawer_element.dart +++ b/lib/views/widgets/drawer_element.dart @@ -17,39 +17,40 @@ class DrawerElement extends StatelessWidget { @override Widget build(BuildContext context) { return Container( - margin: const EdgeInsets.only(top: 5, left: 20, bottom: 5), - child: TextButton( - style: ButtonStyle( - backgroundColor: WidgetStateProperty.all(Colors.black12), - shape: WidgetStateProperty.all( - const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(20), - bottomLeft: Radius.circular(20), - ), + margin: const EdgeInsets.only(top: 5, left: 20, bottom: 5), + child: TextButton( + style: ButtonStyle( + backgroundColor: WidgetStateProperty.all(Colors.black12), + shape: WidgetStateProperty.all( + const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20), + bottomLeft: Radius.circular(20), ), ), ), - onPressed: () { - callThrowable(context, () async => await action(context), text); - }, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Row( - children: [ - svg, - const SizedBox(width: 8), - Text( - text, - style: const TextStyle( - color: Colors.white, - fontSize: 18, - fontWeight: FontWeight.w700, - ), + ), + onPressed: () { + callThrowable(context, () async => await action(context), text); + }, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + children: [ + svg, + const SizedBox(width: 8), + Text( + text, + style: const TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.w700, ), - ], - ), + ), + ], ), - )); + ), + ), + ); } } diff --git a/lib/views/widgets/drawer_elements.dart b/lib/views/widgets/drawer_elements.dart index b765925..0679f98 100644 --- a/lib/views/widgets/drawer_elements.dart +++ b/lib/views/widgets/drawer_elements.dart @@ -1,4 +1,4 @@ -import 'package:cupcake/coins/abstract/coin_wallet.dart'; +import 'package:cupcake/coins/abstract/wallet.dart'; import 'package:cupcake/coins/monero/wallet.dart'; import 'package:cupcake/gen/assets.gen.dart'; import 'package:cupcake/l10n/app_localizations.dart'; diff --git a/lib/views/widgets/form_builder.dart b/lib/views/widgets/form_builder.dart index 3073148..e9ec4ef 100644 --- a/lib/views/widgets/form_builder.dart +++ b/lib/views/widgets/form_builder.dart @@ -114,6 +114,8 @@ class _FormBuilderState extends State { onConfirmLongPress: () async { final b = await callThrowable(context, () async { await e.onConfirmInternal(context); + final auth = LocalAuthentication(); + final List availableBiometrics = await auth.getAvailableBiometrics(); final bool canAuthenticateWithBiometrics = @@ -137,8 +139,8 @@ class _FormBuilderState extends State { } await secureStorage.write( key: "UI.${e.valueOutcome.uniqueId}", value: e.ctrl.text); - config.biometricEnabled = true; - config.save(); + CupcakeConfig.instance.biometricEnabled = true; + CupcakeConfig.instance.save(); ScaffoldMessenger.of(context).showSnackBar(const SnackBar( content: Text("Biometric enabled!"), )); diff --git a/lib/views/widgets/urqr.dart b/lib/views/widgets/urqr.dart new file mode 100644 index 0000000..55e325b --- /dev/null +++ b/lib/views/widgets/urqr.dart @@ -0,0 +1,67 @@ +import 'dart:async'; + +import 'package:cupcake/utils/config.dart'; +import 'package:flutter/material.dart'; +import 'package:qr_flutter/qr_flutter.dart'; + +class URQR extends StatefulWidget { + URQR({super.key, required this.frames}); + + List frames; + + @override + // ignore: library_private_types_in_public_api + _URQRState createState() => _URQRState(); +} + +class _URQRState extends State { + Timer? t; + int frame = 0; + @override + void initState() { + super.initState(); + setState(() { + t = Timer.periodic( + Duration(milliseconds: CupcakeConfig.instance.msForQrCode), (timer) { + _nextFrame(); + }); + }); + } + + void _nextFrame() { + setState(() { + frame++; + }); + } + + @override + void dispose() { + t?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Center( + child: Container( + padding: const EdgeInsets.all(17.0), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(30.0), + color: Colors.white, + ), + child: QrImageView( + foregroundColor: Colors.black, + data: widget.frames[frame % widget.frames.length], + version: -1, + size: 275, + ), + ), + ), + ], + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index f291d4d..b3bbc90 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -26,10 +26,10 @@ packages: dependency: transitive description: name: archive - sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d + sha256: "6199c74e3db4fbfbd04f66d739e72fe11c8a8957d5f219f1f4482dbde6420b5a" url: "https://pub.dev" source: hosted - version: "3.6.1" + version: "4.0.2" args: dependency: transitive description: @@ -58,50 +58,50 @@ packages: dependency: transitive description: name: build - sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" build_config: dependency: transitive description: name: build_config - sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" build_daemon: dependency: transitive description: name: build_daemon - sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9" + sha256: "294a2edaf4814a378725bfe6358210196f5ea37af89ecd81bfa32960113d4948" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.0.3" build_resolvers: dependency: transitive description: name: build_resolvers - sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" + sha256: "99d3980049739a985cf9b21f30881f46db3ebc62c5b8d5e60e27440876b1ba1e" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.4.3" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d" + sha256: "74691599a5bc750dc96a6b4bfd48f7d9d66453eab04c7f4063134800d6a5c573" url: "https://pub.dev" source: hosted - version: "2.4.13" + version: "2.4.14" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0 + sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021" url: "https://pub.dev" source: hosted - version: "7.3.2" + version: "8.0.0" built_collection: dependency: transitive description: @@ -114,10 +114,10 @@ packages: dependency: transitive description: name: built_value - sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb + sha256: "28a712df2576b63c6c005c465989a348604960c0958d28be5303ba9baa841ac2" url: "https://pub.dev" source: hosted - version: "8.9.2" + version: "8.9.3" characters: dependency: transitive description: @@ -202,10 +202,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab" + sha256: "7306ab8a2359a48d22310ad823521d723acfed60ee1f7e37388e8986853b6820" url: "https://pub.dev" source: hosted - version: "2.3.7" + version: "2.3.8" dartx: dependency: transitive description: @@ -226,8 +226,8 @@ packages: dependency: "direct main" description: path: "." - ref: "0e398b047118f8734e1ded799ee0191d4232b1fe" - resolved-ref: "0e398b047118f8734e1ded799ee0191d4232b1fe" + ref: "69b3276b090fa6ac01b4483ca3adca93a8e615be" + resolved-ref: "69b3276b090fa6ac01b4483ca3adca93a8e615be" url: "https://github.com/MrCyjaneK/fast_scanner" source: git version: "5.1.1" @@ -301,10 +301,10 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1" + sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "5.0.0" flutter_localizations: dependency: "direct main" description: flutter @@ -314,34 +314,34 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "9b78450b89f059e96c9ebb355fa6b3df1d6b330436e0b885fb49594c41721398" + sha256: "615a505aef59b151b46bbeef55b36ce2b6ed299d160c51d84281946f0aa0ce0e" url: "https://pub.dev" source: hosted - version: "2.0.23" + version: "2.0.24" flutter_secure_storage: dependency: "direct main" description: name: flutter_secure_storage - sha256: "165164745e6afb5c0e3e3fcc72a012fb9e58496fb26ffb92cf22e16a821e85d0" + sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea" url: "https://pub.dev" source: hosted - version: "9.2.2" + version: "9.2.4" flutter_secure_storage_linux: dependency: transitive description: name: flutter_secure_storage_linux - sha256: "4d91bfc23047422cbcd73ac684bc169859ee766482517c22172c86596bf1464b" + sha256: bf7404619d7ab5c0a1151d7c4e802edad8f33535abfbeff2f9e1fe1274e2d705 url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" flutter_secure_storage_macos: dependency: transitive description: name: flutter_secure_storage_macos - sha256: "1693ab11121a5f925bbea0be725abfcfbbcf36c1e29e571f84a0c0f436147a81" + sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.3" flutter_secure_storage_platform_interface: dependency: transitive description: @@ -370,10 +370,10 @@ packages: dependency: "direct main" description: name: flutter_svg - sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2" + sha256: c200fd79c918a40c5cd50ea0877fa13f81bdaf6f0a5d3dbcc2a13e3285d6aa1b url: "https://pub.dev" source: hosted - version: "2.0.10+1" + version: "2.0.17" flutter_test: dependency: "direct dev" description: flutter @@ -401,10 +401,10 @@ packages: dependency: transitive description: name: glob - sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" graphs: dependency: transitive description: @@ -425,10 +425,10 @@ packages: dependency: transitive description: name: hashlib - sha256: f572f2abce09fc7aee53f15927052b9732ea1053e540af8cae211111ee0b99b1 + sha256: e13e8237d93fb275cd1c55fc339bb90638994d1a4f140c7ee270173b51f3d169 url: "https://pub.dev" source: hosted - version: "1.21.0" + version: "1.21.1" hashlib_codecs: dependency: transitive description: @@ -441,34 +441,34 @@ packages: dependency: transitive description: name: http - sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 + sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f url: "https://pub.dev" source: hosted - version: "1.2.2" + version: "1.3.0" http_multi_server: dependency: transitive description: name: http_multi_server - sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.2.2" http_parser: dependency: transitive description: name: http_parser - sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.1.2" image_size_getter: dependency: transitive description: name: image_size_getter - sha256: "0511799498340b70993d2dfb34b55a2247b5b801d75a6cdd4543acfcafdb12b0" + sha256: "9a299e3af2ebbcfd1baf21456c3c884037ff524316c97d8e56035ea8fdf35653" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.4.0" integration_test: dependency: "direct dev" description: flutter @@ -486,10 +486,10 @@ packages: dependency: transitive description: name: io - sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5" js: dependency: transitive description: @@ -534,10 +534,10 @@ packages: dependency: transitive description: name: lints - sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 + sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "5.1.1" local_auth: dependency: "direct main" description: @@ -558,10 +558,10 @@ packages: dependency: transitive description: name: local_auth_darwin - sha256: "6d2950da311d26d492a89aeb247c72b4653ddc93601ea36a84924a396806d49c" + sha256: "630996cd7b7f28f5ab92432c4b35d055dd03a747bc319e5ffbb3c4806a3e50d2" url: "https://pub.dev" source: hosted - version: "1.4.1" + version: "1.4.3" local_auth_platform_interface: dependency: transitive description: @@ -590,10 +590,10 @@ packages: dependency: "direct main" description: name: lottie - sha256: "7afc60865a2429d994144f7d66ced2ae4305fe35d82890b8766e3359872d872c" + sha256: c5fa04a80a620066c15cf19cc44773e19e9b38e989ff23ea32e5903ef1015950 url: "https://pub.dev" source: hosted - version: "3.1.3" + version: "3.3.1" macros: dependency: transitive description: @@ -630,16 +630,16 @@ packages: dependency: transitive description: name: mime - sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" url: "https://pub.dev" source: hosted - version: "1.0.6" + version: "2.0.0" monero: dependency: "direct main" description: path: "impls/monero.dart" - ref: "v0.18.3.4-RC2" - resolved-ref: f90b1f57b1337671b145e53e95ff5c2ecf67224d + ref: "v0.18.3.4-RC9" + resolved-ref: "127c54599c612d7d3d226e4c016d5812e7a0966d" url: "https://github.com/mrcyjanek/monero_c" source: git version: "0.0.0" @@ -647,26 +647,26 @@ packages: dependency: transitive description: name: package_config - sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" package_info_plus: dependency: "direct main" description: name: package_info_plus - sha256: df3eb3e0aed5c1107bb0fdb80a8e82e778114958b1c5ac5644fb1ac9cae8a998 + sha256: b15fad91c4d3d1f2b48c053dd41cb82da007c27407dc9ab5f9aa59881d0e39d4 url: "https://pub.dev" source: hosted - version: "8.1.0" + version: "8.1.4" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: ac1f4a4847f1ade8e6a87d1f39f5d7c67490738642e2542f559ec38c37489a66 + sha256: a5ef9986efc7bf772f2696183a3992615baa76c1ffb1189318dd8803778fb05b url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" path: dependency: "direct main" description: @@ -695,18 +695,18 @@ packages: dependency: "direct main" description: name: path_provider_android - sha256: c464428172cb986b758c6d1724c603097febb8fb855aa265aeecc9280c294d4a + sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" url: "https://pub.dev" source: hosted - version: "2.2.12" + version: "2.2.15" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" path_provider_linux: dependency: transitive description: @@ -779,6 +779,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" + posix: + dependency: transitive + description: + name: posix + sha256: a0117dc2167805aa9125b82eee515cc891819bac2f538c83646d355b16f58b9a + url: "https://pub.dev" + source: hosted + version: "6.0.1" process: dependency: transitive description: @@ -791,18 +799,18 @@ packages: dependency: transitive description: name: pub_semver - sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" pubspec_parse: dependency: transitive description: name: pubspec_parse - sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.5.0" qr: dependency: transitive description: @@ -823,34 +831,34 @@ packages: dependency: "direct main" description: name: share_plus - sha256: "59dfd53f497340a0c3a81909b220cfdb9b8973a91055c4e5ab9b9b9ad7c513c0" + sha256: fce43200aa03ea87b91ce4c3ac79f0cecd52e2a7a56c7a4185023c271fbfa6da url: "https://pub.dev" source: hosted - version: "10.0.0" + version: "10.1.4" share_plus_platform_interface: dependency: transitive description: name: share_plus_platform_interface - sha256: c57c0bbfec7142e3a0f55633be504b796af72e60e3c791b44d5a017b985f7a48 + sha256: cc012a23fc2d479854e6c80150696c4a5f5bb62cb89af4de1c505cf78d0a5d0b url: "https://pub.dev" source: hosted - version: "5.0.1" + version: "5.0.2" shelf: dependency: transitive description: name: shelf - sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 url: "https://pub.dev" source: hosted - version: "1.4.1" + version: "1.4.2" shelf_web_socket: dependency: transitive description: name: shelf_web_socket - sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611" + sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67 url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.0.1" sky_engine: dependency: transitive description: flutter @@ -892,10 +900,10 @@ packages: dependency: transitive description: name: stream_transform - sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" string_scanner: dependency: transitive description: @@ -932,18 +940,18 @@ packages: dependency: transitive description: name: time - sha256: ad8e018a6c9db36cb917a031853a1aae49467a93e0d464683e029537d848c221 + sha256: "370572cf5d1e58adcb3e354c47515da3f7469dac3a95b447117e728e7be6f461" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" timing: dependency: transitive description: name: timing - sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.0.2" typed_data: dependency: transitive description: @@ -964,10 +972,10 @@ packages: dependency: transitive description: name: url_launcher_linux - sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af + sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.2.1" url_launcher_platform_interface: dependency: transitive description: @@ -980,18 +988,18 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" + sha256: "3ba963161bd0fe395917ba881d320b9c4f6dd3c4a233da62ab18a5025c85f1e9" url: "https://pub.dev" source: hosted - version: "2.3.3" + version: "2.4.0" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "44cf3aabcedde30f2dba119a9dea3b0f2672fbe6fa96e85536251d678216b3c4" + sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" url: "https://pub.dev" source: hosted - version: "3.1.3" + version: "3.1.4" uuid: dependency: transitive description: @@ -1004,26 +1012,26 @@ packages: dependency: "direct main" description: name: vector_graphics - sha256: "542f79cb7849237fd04ce381fa48027c5b8074e9d8921d42a2efd74510cd5a79" + sha256: "7ed22c21d7fdcc88dd6ba7860384af438cd220b251ad65dfc142ab722fabef61" url: "https://pub.dev" source: hosted - version: "1.1.13" + version: "1.1.16" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec - sha256: "2430b973a4ca3c4dbc9999b62b8c719a160100dcbae5c819bae0cacce32c9cdb" + sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" url: "https://pub.dev" source: hosted - version: "1.1.12" + version: "1.1.13" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler - sha256: f3b9b6e4591c11394d4be4806c63e72d3a41778547b2c1e2a8a04fadcfd7d173 + sha256: "1b4b9e706a10294258727674a340ae0d6e64a7231980f9f9a3d12e4b42407aad" url: "https://pub.dev" source: hosted - version: "1.1.12" + version: "1.1.16" vector_math: dependency: transitive description: @@ -1044,18 +1052,18 @@ packages: dependency: transitive description: name: watcher - sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" web: dependency: transitive description: name: web - sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "1.1.0" web_socket: dependency: transitive description: @@ -1068,10 +1076,10 @@ packages: dependency: transitive description: name: web_socket_channel - sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" + sha256: "0b8e2457400d8a859b7b2030786835a28a8e80836ef64402abef392ff4f1d0e5" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" webdriver: dependency: transitive description: @@ -1084,10 +1092,10 @@ packages: dependency: transitive description: name: win32 - sha256: "84ba388638ed7a8cb3445a320c8273136ab2631cd5f2c57888335504ddab1bc2" + sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e url: "https://pub.dev" source: hosted - version: "5.8.0" + version: "5.10.1" xdg_directories: dependency: transitive description: @@ -1108,10 +1116,10 @@ packages: dependency: transitive description: name: yaml - sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.3" sdks: - dart: ">=3.5.0 <4.0.0" - flutter: ">=3.24.0" + dart: ">=3.6.0 <4.0.0" + flutter: ">=3.27.0" diff --git a/pubspec.yaml b/pubspec.yaml index d71351a..8ff6f60 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -38,17 +38,17 @@ dependencies: monero: git: url: https://github.com/mrcyjanek/monero_c - ref: v0.18.3.4-RC2 + ref: v0.18.3.4-RC9 path: impls/monero.dart - path: 1.9.0 - path_provider: 2.1.5 - path_provider_android: 2.2.12 - qr_flutter: 4.1.0 + path: ^1.9.0 + path_provider: ^2.1.5 + path_provider_android: ^2.2.12 + qr_flutter: ^4.1.0 fast_scanner: git: url: https://github.com/MrCyjaneK/fast_scanner - ref: 0e398b047118f8734e1ded799ee0191d4232b1fe - flutter_svg: 2.0.10+1 + ref: 69b3276b090fa6ac01b4483ca3adca93a8e615be + flutter_svg: ^2.0.17 lottie: ^3.1.2 share_plus: ^10.0.0 flutter_localizations: @@ -72,8 +72,8 @@ dev_dependencies: # activated in the `analysis_options.yaml` file located at the root of your # package. See that file for information about deactivating specific lint # rules and activating additional ones. - flutter_lints: ^3.0.0 - flutter_asset_generator: 1.2.0 + flutter_lints: ^5.0.0 + flutter_asset_generator: ^1.2.0 build_runner: ^2.4.9 flutter_gen_runner: ^5.8.0 integration_test: From 99ca8c6b7cd6842f99e5d3fad0235b1c807b0a0c Mon Sep 17 00:00:00 2001 From: Czarek Nakamoto Date: Fri, 31 Jan 2025 20:39:19 +0100 Subject: [PATCH 04/13] minor + ci --- .github/workflows/build_release.yaml | 2 +- lib/views/abstract.dart | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/build_release.yaml b/.github/workflows/build_release.yaml index 1a608b1..22f9bd2 100644 --- a/.github/workflows/build_release.yaml +++ b/.github/workflows/build_release.yaml @@ -24,7 +24,7 @@ jobs: - name: Install dependencies run: | sudo apt update - sudo apt install -y build-essential pkg-config autoconf libtool ccache make cmake gcc g++ git curl lbzip2 libtinfo5 gperf unzip python-is-python3 + sudo apt install -y build-essential pkg-config autoconf libtool ccache make cmake gcc g++ git curl lbzip2 libtinfo5 gperf unzip python-is-python3 llvm - name: setup ccache uses: hendrikmuhs/ccache-action@v1.2 with: diff --git a/lib/views/abstract.dart b/lib/views/abstract.dart index e4c6a78..43056f4 100644 --- a/lib/views/abstract.dart +++ b/lib/views/abstract.dart @@ -74,9 +74,6 @@ class AbstractView extends StatefulWidget { } return PopScope( canPop: canPop, - onPopInvoked: (bool pop) { - print(pop); - }, child: Scaffold( appBar: appBar, body: body(context), From 491ab4c8014beb61200a2f8b799d745c314d9934 Mon Sep 17 00:00:00 2001 From: Czarek Nakamoto Date: Mon, 3 Feb 2025 11:22:09 +0100 Subject: [PATCH 05/13] cleanup/unify push method in views update lints move settings widgets into separate files --- .gitignore | 2 + Makefile | 4 +- analysis_options.yaml | 29 +-- ios/Podfile.lock | 16 +- lib/coins/monero/wallet.dart | 66 +++--- lib/coins/monero/wallet_info.dart | 2 +- lib/main.dart | 2 +- lib/view_model/create_wallet_view_model.dart | 13 +- lib/view_model/open_wallet_view_model.dart | 11 +- lib/view_model/wallet_home_view_model.dart | 2 +- lib/views/abstract.dart | 14 +- lib/views/animated_qr_page.dart | 14 +- lib/views/barcode_scanner.dart | 13 -- lib/views/create_wallet.dart | 24 +-- lib/views/home_screen.dart | 37 +--- lib/views/initial_setup_screen.dart | 27 +-- lib/views/new_wallet_info.dart | 15 +- lib/views/open_wallet.dart | 18 +- lib/views/receive.dart | 16 +- lib/views/security_backup.dart | 23 +- lib/views/settings.dart | 203 ++---------------- lib/views/unconfirmed_transaction.dart | 31 +-- lib/views/wallet_edit.dart | 19 +- lib/views/wallet_home.dart | 15 +- lib/views/widgets/drawer_elements.dart | 9 +- .../settings/boolean_config_element.dart | 29 +++ .../settings/integer_config_element.dart | 46 ++++ .../widgets/settings/version_widget.dart | 84 ++++++++ 28 files changed, 321 insertions(+), 463 deletions(-) create mode 100644 lib/views/widgets/settings/boolean_config_element.dart create mode 100644 lib/views/widgets/settings/integer_config_element.dart create mode 100644 lib/views/widgets/settings/version_widget.dart diff --git a/.gitignore b/.gitignore index a053f4e..de5cb3f 100644 --- a/.gitignore +++ b/.gitignore @@ -5,9 +5,11 @@ *.swp .DS_Store .atom/ +.build/ .buildlog/ .history .svn/ +.swiftpm/ migrate_working_dir/ # IntelliJ related diff --git a/Makefile b/Makefile index 81c53b0..eb693f5 100644 --- a/Makefile +++ b/Makefile @@ -15,11 +15,11 @@ libs_android_build_ci: ./build_moneroc.sh --coin ${COIN} --tag ${MONERO_C_TAG} --triplet aarch64-linux-android --location android/app/src/main/jniLibs/arm64-v8a libs_ios_download: - ./build_moneroc.sh --prebuild --coin ${COIN} --tag ${MONERO_C_TAG} --triplet host-apple-ios --location ios + ./build_moneroc.sh --prebuild --coin ${COIN} --tag ${MONERO_C_TAG} --triplet aarch64-apple-ios --location ios cd ios && ./gen_framework.sh libs_ios_build: [[ "x$(shell uname)" == "xDarwin" ]] || exit 1 # Only Darwin hosts can build for iOS, try $(MAKE) libs_ios_download - ./build_moneroc.sh --coin ${COIN} --tag ${MONERO_C_TAG} --triplet host-apple-ios --location ios + ./build_moneroc.sh --coin ${COIN} --tag ${MONERO_C_TAG} --triplet aarch64-apple-ios --location ios cd ios && ./gen_framework.sh cupcake_android_monero: diff --git a/analysis_options.yaml b/analysis_options.yaml index 0d29021..a449cbd 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,28 +1,9 @@ -# This file configures the analyzer, which statically analyzes Dart code to -# check for errors, warnings, and lints. -# -# The issues identified by the analyzer are surfaced in the UI of Dart-enabled -# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be -# invoked from the command line by running `flutter analyze`. - -# The following line activates a set of recommended lints for Flutter apps, -# packages, and plugins designed to encourage good coding practices. +analyzer: + errors: + must_be_immutable: ignore + overridden_fields: ignore include: package:flutter_lints/flutter.yaml linter: - # The lint rules applied to this project can be customized in the - # section below to disable rules from the `package:flutter_lints/flutter.yaml` - # included above or to enable additional rules. A list of all available lints - # and their documentation is published at https://dart.dev/lints. - # - # Instead of disabling a lint rule for the entire project in the - # section below, it can also be suppressed for a single line of code - # or a specific dart file by using the `// ignore: name_of_lint` and - # `// ignore_for_file: name_of_lint` syntax on the line or in the file - # producing the lint. rules: - # avoid_print: false # Uncomment to disable the `avoid_print` rule - # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule - -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options + unawaited_futures: true diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 02838c9..223ddeb 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -46,15 +46,15 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/share_plus/ios" SPEC CHECKSUMS: - fast_scanner: 44c00940355a51258cd6c2085734193cd23d95bc + fast_scanner: 2cb1ad3e69e645e9980fb4961396ce5804caa3e3 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12 - integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573 - local_auth_darwin: 66e40372f1c29f383a314c738c7446e2f7fdadc3 - package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 - path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad + flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13 + integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e + local_auth_darwin: 553ce4f9b16d3fdfeafce9cf042e7c9f77c1c391 + package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 + path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 + share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796 -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/lib/coins/monero/wallet.dart b/lib/coins/monero/wallet.dart index c262685..a312263 100644 --- a/lib/coins/monero/wallet.dart +++ b/lib/coins/monero/wallet.dart @@ -14,8 +14,6 @@ import 'package:cupcake/utils/null_if_empty.dart'; import 'package:cupcake/utils/secure_storage.dart'; import 'package:cupcake/coins/abstract/address.dart'; import 'package:cupcake/utils/urqr.dart'; -import 'package:cupcake/view_model/unconfirmed_transaction_view_model.dart'; -import 'package:cupcake/view_model/urqr_view_model.dart'; import 'package:cupcake/views/animated_qr_page.dart'; import 'package:cupcake/views/unconfirmed_transaction.dart'; import 'package:flutter/cupertino.dart'; @@ -81,15 +79,12 @@ class MoneroWallet implements CoinWallet { max_fragment_length: CupcakeConfig.instance.maxFragmentLength, all: false) .split("\n"); - await AnimatedURPage.staticPush( - context, - URQRViewModel( - urqrList: { - "Partial Key Images": someImages, - "All Key Images": allImages, - }, - ), - ); + await AnimatedURPage( + urqrList: { + "Partial Key Images": someImages, + "All Key Images": allImages, + }, + ).push(context); } @override @@ -136,32 +131,29 @@ class MoneroWallet implements CoinWallet { } final fee = MoneroAmount(int.parse(monero.UnsignedTransaction_fee(txptr))); - await UnconfirmedTransactionView.staticPush( - context, - UnconfirmedTransactionViewModel( - wallet: this, - destMap: destMap, - fee: fee, - confirmCallback: (BuildContext context) async { - final signedTx = monero.UnsignedTransaction_signUR( - txptr, CupcakeConfig.instance.maxFragmentLength) - .split("\n"); - var status = monero.Wallet_status(wptr); - if (status != 0) { - final error = monero.Wallet_errorString(wptr); - throw CoinException(error); - } - status = monero.UnsignedTransaction_status(txptr); - if (status != 0) { - final error = monero.UnsignedTransaction_errorString(txptr); - throw CoinException(error); - } - await AnimatedURPage.staticPush( - context, URQRViewModel(urqrList: {"signedTx": signedTx})); - }, - cancelCallback: (BuildContext context) => {}, - ), - ); + await UnconfirmedTransactionView( + wallet: this, + destMap: destMap, + fee: fee, + confirmCallback: (BuildContext context) async { + final signedTx = monero.UnsignedTransaction_signUR( + txptr, CupcakeConfig.instance.maxFragmentLength) + .split("\n"); + var status = monero.Wallet_status(wptr); + if (status != 0) { + final error = monero.Wallet_errorString(wptr); + throw CoinException(error); + } + status = monero.UnsignedTransaction_status(txptr); + if (status != 0) { + final error = monero.UnsignedTransaction_errorString(txptr); + throw CoinException(error); + } + await AnimatedURPage(urqrList: {"signedTx": signedTx}) + .push(context); + }, + cancelCallback: (BuildContext context) => {}, + ).push(context); save(); default: throw UnimplementedError("Unable to handle ${ur.tag}."); diff --git a/lib/coins/monero/wallet_info.dart b/lib/coins/monero/wallet_info.dart index 170119f..a50fa44 100644 --- a/lib/coins/monero/wallet_info.dart +++ b/lib/coins/monero/wallet_info.dart @@ -42,7 +42,7 @@ class MoneroWalletInfo extends CoinWalletInfo { @override void openUI(BuildContext context) { - OpenWallet.pushStatic(context, this); + OpenWallet(coinWalletInfo: this).push(context); } @override diff --git a/lib/main.dart b/lib/main.dart index d9947d4..c08b749 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -60,7 +60,7 @@ class MyApp extends StatelessWidget { }, home: CupcakeConfig.instance.initialSetupComplete ? HomeScreen( - viewModel: HomeScreenViewModel(openLastWallet: true), + openLastWallet: true, ) : InitialSetupScreen(), ); diff --git a/lib/view_model/create_wallet_view_model.dart b/lib/view_model/create_wallet_view_model.dart index cf85e00..dee461a 100644 --- a/lib/view_model/create_wallet_view_model.dart +++ b/lib/view_model/create_wallet_view_model.dart @@ -279,7 +279,7 @@ class CreateWalletViewModel extends ViewModel { : () { CupcakeConfig.instance.initialSetupComplete = true; CupcakeConfig.instance.save(); - WalletHome.pushStatic(context, cw); + WalletHome(coinWallet: cw).push(context); }, topActionText: Text(L.next), lottieAnimation: Assets.shield.lottie(), @@ -326,7 +326,7 @@ class CreateWalletViewModel extends ViewModel { topAction: () { CupcakeConfig.instance.initialSetupComplete = true; CupcakeConfig.instance.save(); - WalletHome.pushStatic(context, cw); + WalletHome(coinWallet: cw).push(context); }, topActionText: Text(L.next), lottieAnimation: Assets.shield.lottie(), @@ -374,12 +374,11 @@ class CreateWalletViewModel extends ViewModel { throw Exception("context is not mounted, unable to show next screen"); } if (currentForm != _createForm) { - WalletHome.pushStatic(context, cw); + WalletHome(coinWallet: cw).push(context); } else { - NewWalletInfoScreen.staticPush( - context, - NewWalletInfoViewModel(pages), - ); + NewWalletInfoScreen( + pages: pages, + ).push(context); } } diff --git a/lib/view_model/open_wallet_view_model.dart b/lib/view_model/open_wallet_view_model.dart index 18f1115..c92e0e4 100644 --- a/lib/view_model/open_wallet_view_model.dart +++ b/lib/view_model/open_wallet_view_model.dart @@ -7,9 +7,9 @@ import 'package:cupcake/views/wallet_home.dart'; import 'package:flutter/cupertino.dart'; class OpenWalletViewModel extends ViewModel { - OpenWalletViewModel({required this.coinInfo}); + OpenWalletViewModel({required this.coinWalletInfo}); - CoinWalletInfo coinInfo; + CoinWalletInfo coinWalletInfo; @override String get screenName => L.enter_password; @@ -43,23 +43,22 @@ class OpenWalletViewModel extends ViewModel { } Future _openWallet(BuildContext context) async { - final coin = await coinInfo.openWallet( + final wallet = await coinWalletInfo.openWallet( context, password: await walletPassword.value, ); - WalletHome.pushStatic(context, coin); + WalletHome(coinWallet: wallet).push(context); } Future checkWalletPassword() async { try { - return coinInfo.checkWalletPassword(await walletPassword.value); + return coinWalletInfo.checkWalletPassword(await walletPassword.value); } catch (e) { return false; } } Future openWalletIfPasswordCorrect(BuildContext context) async { - print("called"); if (await checkWalletPassword()) { if (!context.mounted) return; openWallet(context); diff --git a/lib/view_model/wallet_home_view_model.dart b/lib/view_model/wallet_home_view_model.dart index 21cd13a..447a4e7 100644 --- a/lib/view_model/wallet_home_view_model.dart +++ b/lib/view_model/wallet_home_view_model.dart @@ -17,6 +17,6 @@ class WalletHomeViewModel extends ViewModel { String get currentAddress => wallet.getCurrentAddress; void showScanner(BuildContext context) { - BarcodeScanner.pushStatic(context, wallet); + BarcodeScanner(wallet: wallet).push(context); } } diff --git a/lib/views/abstract.dart b/lib/views/abstract.dart index 43056f4..546f473 100644 --- a/lib/views/abstract.dart +++ b/lib/views/abstract.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:cupcake/l10n/app_localizations.dart'; import 'package:cupcake/view_model/abstract.dart'; import 'package:cupcake/views/widgets/cupcake_appbar_title.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; // Since there is no performance penalty for using stateful widgets I would just @@ -27,8 +28,17 @@ class _AbstractViewState extends State { } } -// ignore: must_be_immutable class AbstractView extends StatefulWidget { + Future push(BuildContext context) async { + await Navigator.of(context).push(CupertinoPageRoute( + builder: (context) { + return this; + }, + )); + } + + final viewModel = ViewModel(); + @override // ignore: no_logic_in_create_state State createState() { @@ -44,8 +54,6 @@ class AbstractView extends StatefulWidget { AbstractView({super.key}); - final viewModel = ViewModel(); - get appBar => viewModel.screenName.isEmpty ? null : AppBar( diff --git a/lib/views/animated_qr_page.dart b/lib/views/animated_qr_page.dart index 80a2c8a..c05ffbd 100644 --- a/lib/views/animated_qr_page.dart +++ b/lib/views/animated_qr_page.dart @@ -1,21 +1,11 @@ import 'package:cupcake/view_model/urqr_view_model.dart'; import 'package:cupcake/views/abstract.dart'; import 'package:cupcake/views/widgets/urqr.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -//ignore: must_be_immutable class AnimatedURPage extends AbstractView { - static Future staticPush( - BuildContext context, URQRViewModel viewModel) async { - await Navigator.of(context).push( - CupertinoPageRoute( - builder: (context) => AnimatedURPage(viewModel: viewModel), - ), - ); - } - - AnimatedURPage({super.key, required this.viewModel}); + AnimatedURPage({super.key, required Map> urqrList}) + : viewModel = URQRViewModel(urqrList: urqrList); @override final URQRViewModel viewModel; diff --git a/lib/views/barcode_scanner.dart b/lib/views/barcode_scanner.dart index 7fcd92b..276eb6e 100644 --- a/lib/views/barcode_scanner.dart +++ b/lib/views/barcode_scanner.dart @@ -5,25 +5,12 @@ import 'package:cupcake/views/widgets/barcode_scanner/progress_painter.dart'; import 'package:cupcake/views/widgets/barcode_scanner/switch_camera.dart'; import 'package:cupcake/views/widgets/barcode_scanner/toggle_flashlight_button.dart'; import 'package:fast_scanner/fast_scanner.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -//ignore: must_be_immutable class BarcodeScanner extends AbstractView { BarcodeScanner({super.key, required CoinWallet wallet}) : viewModel = BarcodeScannerViewModel(wallet: wallet); - static Future pushStatic( - BuildContext context, CoinWallet wallet) async { - await Navigator.of(context).push( - CupertinoPageRoute( - builder: (BuildContext context) { - return BarcodeScanner(wallet: wallet); - }, - ), - ); - } - @override final BarcodeScannerViewModel viewModel; diff --git a/lib/views/create_wallet.dart b/lib/views/create_wallet.dart index 8e1408f..cf8e859 100644 --- a/lib/views/create_wallet.dart +++ b/lib/views/create_wallet.dart @@ -1,3 +1,4 @@ +import 'package:cupcake/coins/types.dart'; import 'package:cupcake/gen/assets.gen.dart'; import 'package:cupcake/utils/call_throwable.dart'; import 'package:cupcake/utils/config.dart'; @@ -6,25 +7,16 @@ import 'package:cupcake/view_model/create_wallet_view_model.dart'; import 'package:cupcake/views/abstract.dart'; import 'package:cupcake/views/initial_setup_screen.dart'; import 'package:cupcake/views/widgets/form_builder.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -// ignore: must_be_immutable class CreateWallet extends AbstractView { - CreateWallet({super.key, required this.viewModel}); - - static Future staticPush( - BuildContext context, CreateWalletViewModel viewModel) async { - await Navigator.of(context).push( - CupertinoPageRoute( - builder: (BuildContext context) { - return CreateWallet( - viewModel: viewModel, - ); - }, - ), - ); - } + CreateWallet( + {super.key, + required CreateMethod createMethod, + required bool needsPasswordConfirm}) + : viewModel = CreateWalletViewModel( + createMethod: createMethod, + needsPasswordConfirm: needsPasswordConfirm); @override final CreateWalletViewModel viewModel; diff --git a/lib/views/home_screen.dart b/lib/views/home_screen.dart index bdfa47d..8c7d954 100644 --- a/lib/views/home_screen.dart +++ b/lib/views/home_screen.dart @@ -2,35 +2,19 @@ import 'package:cupcake/coins/abstract/wallet_info.dart'; import 'package:cupcake/coins/types.dart'; import 'package:cupcake/utils/call_throwable.dart'; import 'package:cupcake/utils/config.dart'; -import 'package:cupcake/view_model/create_wallet_view_model.dart'; import 'package:cupcake/view_model/home_screen_view_model.dart'; import 'package:cupcake/views/abstract.dart'; import 'package:cupcake/views/create_wallet.dart'; import 'package:cupcake/views/initial_setup_screen.dart'; import 'package:cupcake/views/wallet_edit.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:path/path.dart' as p; -// ignore: must_be_immutable class HomeScreen extends AbstractView { - HomeScreen({super.key, required this.viewModel}); - - static Future staticPush(BuildContext context, - {bool openLastWallet = true, String? lastOpenedWallet}) async { - await Navigator.of(context).push( - CupertinoPageRoute( - builder: (BuildContext context) { - return HomeScreen( - viewModel: HomeScreenViewModel( - openLastWallet: openLastWallet, - lastOpenedWallet: lastOpenedWallet, - ), - ); - }, - ), - ); - } + HomeScreen( + {super.key, required bool openLastWallet, String? lastOpenedWallet}) + : viewModel = HomeScreenViewModel( + openLastWallet: openLastWallet, lastOpenedWallet: lastOpenedWallet); @override final HomeScreenViewModel viewModel; @@ -120,18 +104,15 @@ class HomeScreen extends AbstractView { canPop = false; // don't allow user to go back to previous wallet await viewModel.markNeedsBuild(); if (!context.mounted) return; - await WalletEdit.staticPush(context, walletInfo); + await WalletEdit(walletInfo: walletInfo).push(context); await viewModel.markNeedsBuild(); } Future createWallet(BuildContext context, CreateMethod method) async { - await CreateWallet.staticPush( - context, - CreateWalletViewModel( - createMethod: method, - needsPasswordConfirm: false, - ), - ); + await CreateWallet( + createMethod: method, + needsPasswordConfirm: false, + ).push(context); if (!context.mounted) return; markNeedsBuild(); } diff --git a/lib/views/initial_setup_screen.dart b/lib/views/initial_setup_screen.dart index 52305f7..3c246bd 100644 --- a/lib/views/initial_setup_screen.dart +++ b/lib/views/initial_setup_screen.dart @@ -1,18 +1,17 @@ import 'package:cupcake/coins/types.dart'; import 'package:cupcake/themes/base_theme.dart'; -import 'package:cupcake/view_model/create_wallet_view_model.dart'; import 'package:cupcake/view_model/initial_setup_view_model.dart'; import 'package:cupcake/views/abstract.dart'; import 'package:cupcake/views/create_wallet.dart'; import 'package:cupcake/gen/assets.gen.dart'; import 'package:flutter/material.dart'; -// ignore: must_be_immutable class InitialSetupScreen extends AbstractView { + InitialSetupScreen({super.key}); + @override InitialSetupViewModel viewModel = InitialSetupViewModel(); - InitialSetupScreen({super.key}); @override Widget? body(BuildContext context) { return SafeArea( @@ -50,24 +49,18 @@ class InitialSetupScreen extends AbstractView { LongSecondaryButton( text: L.create_new_wallet, icon: Icons.add, - onPressed: () => CreateWallet.staticPush( - context, - CreateWalletViewModel( - createMethod: CreateMethod.create, - needsPasswordConfirm: true, - ), - ), + onPressed: () => CreateWallet( + createMethod: CreateMethod.create, + needsPasswordConfirm: true, + ).push(context), ), LongPrimaryButton( text: L.restore_wallet, icon: Icons.restore, - onPressed: () => CreateWallet.staticPush( - context, - CreateWalletViewModel( - createMethod: CreateMethod.restore, - needsPasswordConfirm: true, - ), - ), + onPressed: () => CreateWallet( + createMethod: CreateMethod.restore, + needsPasswordConfirm: true, + ).push(context), ), ], )); diff --git a/lib/views/new_wallet_info.dart b/lib/views/new_wallet_info.dart index 4bd33d4..57ca60a 100644 --- a/lib/views/new_wallet_info.dart +++ b/lib/views/new_wallet_info.dart @@ -1,22 +1,11 @@ import 'package:cupcake/view_model/new_wallet_info_view_model.dart'; import 'package:cupcake/views/abstract.dart'; import 'package:cupcake/views/initial_setup_screen.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; class NewWalletInfoScreen extends AbstractView { - static void staticPush( - BuildContext context, NewWalletInfoViewModel viewModel) { - Navigator.of(context).push( - CupertinoPageRoute( - builder: (BuildContext context) { - return NewWalletInfoScreen(viewModel: viewModel); - }, - ), - ); - } - - NewWalletInfoScreen({super.key, required this.viewModel}); + NewWalletInfoScreen({super.key, required List pages}) + : viewModel = NewWalletInfoViewModel(pages); @override NewWalletInfoViewModel viewModel; diff --git a/lib/views/open_wallet.dart b/lib/views/open_wallet.dart index bb4132e..a829e1d 100644 --- a/lib/views/open_wallet.dart +++ b/lib/views/open_wallet.dart @@ -4,24 +4,10 @@ import 'package:cupcake/views/abstract.dart'; import 'package:cupcake/views/widgets/form_builder.dart'; import 'package:flutter/cupertino.dart'; -// ignore: must_be_immutable class OpenWallet extends AbstractView { - static Future pushStatic( - BuildContext context, CoinWalletInfo coin) async { - await Navigator.of(context).push( - CupertinoPageRoute( - builder: (BuildContext context) { - return OpenWallet( - OpenWalletViewModel( - coinInfo: coin, - ), - ); - }, - ), - ); - } + OpenWallet({super.key, required CoinWalletInfo coinWalletInfo}) + : viewModel = OpenWalletViewModel(coinWalletInfo: coinWalletInfo); - OpenWallet(this.viewModel, {super.key}); @override final OpenWalletViewModel viewModel; diff --git a/lib/views/receive.dart b/lib/views/receive.dart index 170978b..8ed0bf0 100644 --- a/lib/views/receive.dart +++ b/lib/views/receive.dart @@ -1,25 +1,13 @@ import 'package:cupcake/coins/abstract/wallet.dart'; import 'package:cupcake/view_model/receive_view_model.dart'; import 'package:cupcake/views/abstract.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:qr_flutter/qr_flutter.dart'; class Receive extends AbstractView { - static Future pushStatic(BuildContext context, CoinWallet coin) async { - await Navigator.of(context).push( - CupertinoPageRoute( - builder: (BuildContext context) { - return Receive( - ReceiveViewModel(coin), - ); - }, - ), - ); - } - - Receive(this.viewModel, {super.key}); + Receive({super.key, required CoinWallet coinWallet}) + : viewModel = ReceiveViewModel(coinWallet); @override final ReceiveViewModel viewModel; diff --git a/lib/views/security_backup.dart b/lib/views/security_backup.dart index 3a1fc76..b3fa660 100644 --- a/lib/views/security_backup.dart +++ b/lib/views/security_backup.dart @@ -6,28 +6,16 @@ import 'package:cupcake/view_model/security_backup_view_model.dart'; import 'package:cupcake/views/abstract.dart'; import 'package:cupcake/views/initial_setup_screen.dart'; import 'package:cupcake/views/widgets/form_builder.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:qr_flutter/qr_flutter.dart'; class SecurityBackup extends AbstractView { - SecurityBackup({super.key, required this.viewModel}); + SecurityBackup({super.key, required CoinWallet coinWallet}) + : viewModel = SecurityBackupViewModel(wallet: coinWallet); - static Future staticPush( - BuildContext context, CoinWallet coinWallet) async { - await Navigator.of(context).push( - CupertinoPageRoute( - builder: (BuildContext context) { - return SecurityBackup( - viewModel: SecurityBackupViewModel( - wallet: coinWallet, - ), - ); - }, - ), - ); - } + @override + SecurityBackupViewModel viewModel; void _copy(BuildContext context, String value, String key) { Clipboard.setData(ClipboardData(text: value)); @@ -104,7 +92,4 @@ class SecurityBackup extends AbstractView { ], ); } - - @override - SecurityBackupViewModel viewModel; } diff --git a/lib/views/settings.dart b/lib/views/settings.dart index 7c8bc4e..98c4d21 100644 --- a/lib/views/settings.dart +++ b/lib/views/settings.dart @@ -1,10 +1,12 @@ -import 'package:cupcake/utils/alerts/widget.dart'; import 'package:cupcake/utils/config.dart'; +import 'package:cupcake/utils/secure_storage.dart'; import 'package:cupcake/view_model/settings_view_model.dart'; import 'package:cupcake/views/abstract.dart'; +import 'package:cupcake/views/widgets/settings/boolean_config_element.dart'; +import 'package:cupcake/views/widgets/settings/integer_config_element.dart'; +import 'package:cupcake/views/widgets/settings/version_widget.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:package_info_plus/package_info_plus.dart'; class SettingsView extends AbstractView { SettingsView({super.key}); @@ -12,14 +14,6 @@ class SettingsView extends AbstractView { @override SettingsViewModel get viewModel => SettingsViewModel(); - static staticPush(BuildContext context) { - return Navigator.of(context).push( - CupertinoPageRoute( - builder: (BuildContext context) => SettingsView(), - ), - ); - } - Future postUpdate(BuildContext context) async { viewModel.appConfig.save(); markNeedsBuild(); @@ -48,24 +42,24 @@ class SettingsView extends AbstractView { viewModel.appConfig.msForQrCode = value; postUpdate(context); }), - // BooleanConfigElement( - // title: "Biometric auth", - // subtitleEnabled: "Biometrics are enabled", - // subtitleDisabled: - // "In order to enable biometrics long press confirm button when entering pin", - // value: config.biometricEnabled, - // onChange: (bool value) async { - // if (value) return; - // config.biometricEnabled = false; - // final map = await secureStorage.readAll(); - // for (var key in map.keys) { - // if (map[key]!.startsWith("UI.")) { - // await secureStorage.delete(key: key); - // } - // } - // config.save(); - // postUpdate(context); - // }), + BooleanConfigElement( + title: "Biometric auth", + subtitleEnabled: "Biometrics are enabled", + subtitleDisabled: + "In order to enable biometrics long press confirm button when entering pin", + value: viewModel.appConfig.biometricEnabled, + onChange: (bool value) async { + if (value) return; + viewModel.appConfig.biometricEnabled = false; + final map = await secureStorage.readAll(); + for (var key in map.keys) { + if (map[key]!.startsWith("UI.")) { + await secureStorage.delete(key: key); + } + } + viewModel.appConfig.save(); + postUpdate(context); + }), IntegerConfigElement( title: "Max fragment density", hint: @@ -81,156 +75,3 @@ class SettingsView extends AbstractView { ); } } - -class VersionWidget extends StatefulWidget { - const VersionWidget({super.key}); - - @override - State createState() => _VersionWidgetState(); -} - -class _VersionWidgetState extends State { - Future showWidget(BuildContext context) async { - PackageInfo packageInfo = await PackageInfo.fromPlatform(); - - String appName = packageInfo.appName; - String packageName = packageInfo.packageName; - String version = packageInfo.version; - String buildNumber = packageInfo.buildNumber; - showAboutDialog( - context: context, - applicationName: appName, - applicationVersion: "$version+$buildNumber"); - } - - List easterEgg = [ - "¯\\_(ツ)_/¯", // Shrug - "( ͡° ͜ʖ ͡°)", // Lenny Face - "(╯°□°)╯︵ ┻━┻", // Table Flip - "┬─┬ ノ( ゜-゜ノ)", // Table Unflip - "ಠ_ಠ", // Disapproval Look - "(ಥ﹏ಥ)", // Crying - "ʕ•ᴥ•ʔ", // Bear Hug - "( ^_^)o自自o(^_^ )", // Cheers! - "✨🌟✨", // Sparkles - // New ASCII-only additions - "+(._.)+", // Robot Face - "<(o_o<)", // Kirby Dance - "(>'-')> <('-'<)", // High Five! - "d(^_^)b", // Thumbs Up - "(* ^ ω ^)", // Cute Smiling Face - "(\\__/)", // Bunny - "(^._.^)ノ", // Cat waving - "ʕ •́؈•̀ ₎", // Angry Bear - "/ᐠ。‸。ᐟ\\", // Kitty Frustrated - "| (• ◡•)|", // Simple Smiling Face - "<('o'<)", // Happy Kirby - "(-'-)=o", // Fighting Stance - "/╲/( •̀ ω •́ )╯", // Action Pose - "ʕง•ᴥ•ʔง", // Flexing Bear - "(.❛ ᴗ ❛.)", // Smiling Face - "(╥_╥)", // Sad Crying - "(ง'̀-'́)ง", // Fight Me! - "(ノಠ益ಠ)ノ", // Rage Flip - ]; - - String? subtitle; - - Future _debugTrigger() async { - if (easterEgg.isEmpty) { - if (CupcakeConfig.instance.debug) return; - CupcakeConfig.instance.debug = true; - CupcakeConfig.instance.save(); - setState(() { - subtitle = "debug options enabled"; - }); - Navigator.of(context).pop(); - return; - } - easterEgg.shuffle(); - setState(() { - subtitle = easterEgg.removeAt(0); - }); - } - - @override - Widget build(BuildContext context) { - return ListTile( - title: const Text("About the app"), - subtitle: subtitle == null ? null : Text(subtitle ?? "..."), - onTap: subtitle != null ? _debugTrigger : () => showWidget(context), - onLongPress: _debugTrigger, - ); - } -} - -class IntegerConfigElement extends StatelessWidget { - IntegerConfigElement({ - super.key, - required this.title, - required this.hint, - required this.value, - required this.onChange, - }); - - final String title; - final String? hint; - final int value; - final Function(int val) onChange; - late final ctrl = TextEditingController(text: value.toString()); - @override - Widget build(BuildContext context) { - return ListTile( - title: Text(title), - onLongPress: () { - CupcakeConfig.instance.debug = true; - CupcakeConfig.instance.save(); - }, - subtitle: TextField( - controller: ctrl, - onSubmitted: (String value) { - final i = int.tryParse(value); - if (i == null) return; - onChange(i); - }, - ), - trailing: hint == null - ? null - : IconButton( - icon: const Icon(Icons.info), - onPressed: () { - showAlertWidget( - context: context, title: title, body: [Text(hint ?? "")]); - }, - ), - ); - } -} - -class BooleanConfigElement extends StatelessWidget { - const BooleanConfigElement({ - super.key, - required this.title, - required this.subtitleEnabled, - required this.subtitleDisabled, - required this.value, - required this.onChange, - }); - - final String title; - final String subtitleEnabled; - final String subtitleDisabled; - final bool value; - final Function(bool val) onChange; - @override - Widget build(BuildContext context) { - return CheckboxListTile( - title: Text(title), - subtitle: Text(value ? subtitleEnabled : subtitleDisabled), - value: value, - onChanged: (bool? value) { - onChange(value == true); - }, - ); - } -} diff --git a/lib/views/unconfirmed_transaction.dart b/lib/views/unconfirmed_transaction.dart index fcfb3e1..6d3772b 100644 --- a/lib/views/unconfirmed_transaction.dart +++ b/lib/views/unconfirmed_transaction.dart @@ -1,21 +1,28 @@ +import 'dart:async'; + +import 'package:cupcake/coins/abstract/address.dart'; +import 'package:cupcake/coins/abstract/amount.dart'; +import 'package:cupcake/coins/abstract/wallet.dart'; import 'package:cupcake/utils/call_throwable.dart'; import 'package:cupcake/view_model/unconfirmed_transaction_view_model.dart'; import 'package:cupcake/views/abstract.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -//ignore: must_be_immutable class UnconfirmedTransactionView extends AbstractView { - static Future staticPush( - BuildContext context, UnconfirmedTransactionViewModel viewModel) async { - await Navigator.of(context).push( - CupertinoPageRoute( - builder: (context) => UnconfirmedTransactionView(viewModel: viewModel), - ), - ); - } - - UnconfirmedTransactionView({super.key, required this.viewModel}); + UnconfirmedTransactionView({ + super.key, + required CoinWallet wallet, + required Amount fee, + required Map destMap, + required FutureOr Function(BuildContext) confirmCallback, + required FutureOr Function(BuildContext) cancelCallback, + }) : viewModel = UnconfirmedTransactionViewModel( + wallet: wallet, + fee: fee, + destMap: destMap, + confirmCallback: confirmCallback, + cancelCallback: cancelCallback, + ); @override final UnconfirmedTransactionViewModel viewModel; diff --git a/lib/views/wallet_edit.dart b/lib/views/wallet_edit.dart index 9bfcac5..5bed603 100644 --- a/lib/views/wallet_edit.dart +++ b/lib/views/wallet_edit.dart @@ -4,20 +4,14 @@ import 'package:cupcake/view_model/wallet_edit_view_model.dart'; import 'package:cupcake/views/abstract.dart'; import 'package:cupcake/views/initial_setup_screen.dart'; import 'package:cupcake/views/widgets/form_builder.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; class WalletEdit extends AbstractView { - WalletEdit({super.key, required this.viewModel}); - static Future staticPush( - BuildContext context, CoinWalletInfo walletInfo) async { - await Navigator.of(context).push( - CupertinoPageRoute( - builder: (context) => - WalletEdit(viewModel: WalletEditViewModel(walletInfo: walletInfo)), - ), - ); - } + WalletEdit({super.key, required CoinWalletInfo walletInfo}) + : viewModel = WalletEditViewModel(walletInfo: walletInfo); + + @override + WalletEditViewModel viewModel; @override Widget? body(BuildContext context) { @@ -67,7 +61,4 @@ class WalletEdit extends AbstractView { ], ); } - - @override - WalletEditViewModel viewModel; } diff --git a/lib/views/wallet_home.dart b/lib/views/wallet_home.dart index 9780ebc..145aa30 100644 --- a/lib/views/wallet_home.dart +++ b/lib/views/wallet_home.dart @@ -8,21 +8,10 @@ import 'package:cupcake/views/widgets/drawer_elements.dart'; import 'package:flutter/material.dart'; import 'package:cupcake/gen/assets.gen.dart'; -// ignore: must_be_immutable class WalletHome extends AbstractView { WalletHome({super.key, required CoinWallet coinWallet}) : viewModel = WalletHomeViewModel(wallet: coinWallet); - static Future pushStatic(BuildContext context, CoinWallet coin) async { - await Navigator.of(context).pushReplacement( - MaterialPageRoute( - builder: (BuildContext context) { - return WalletHome(coinWallet: coin); - }, - ), - ); - } - @override final WalletHomeViewModel viewModel; @@ -117,7 +106,7 @@ class WalletHome extends AbstractView { height: 64, child: ElevatedButton.icon( onPressed: () => - Receive.pushStatic(context, viewModel.wallet), + Receive(coinWallet: viewModel.wallet).push(context), icon: const Icon(Icons.call_received, size: 35, color: Colors.white), label: Text( @@ -133,7 +122,7 @@ class WalletHome extends AbstractView { height: 64, child: ElevatedButton.icon( onPressed: () => - BarcodeScanner.pushStatic(context, viewModel.wallet), + BarcodeScanner(wallet: viewModel.wallet).push(context), icon: const Icon( Icons.qr_code_rounded, size: 35, diff --git a/lib/views/widgets/drawer_elements.dart b/lib/views/widgets/drawer_elements.dart index 0679f98..3cc956f 100644 --- a/lib/views/widgets/drawer_elements.dart +++ b/lib/views/widgets/drawer_elements.dart @@ -14,15 +14,14 @@ class DrawerElements extends StatelessWidget { final CoinWallet coinWallet; Future _walletList(BuildContext context) async { - await HomeScreen.staticPush( - context, + await HomeScreen( openLastWallet: false, lastOpenedWallet: coinWallet.walletName, - ); + ).push(context); } Future _securityBackup(BuildContext context) async { - await SecurityBackup.staticPush(context, coinWallet); + await SecurityBackup(coinWallet: coinWallet).push(context); } Future _exportKeyImages(BuildContext context) async { @@ -33,7 +32,7 @@ class DrawerElements extends StatelessWidget { } Future _otherSettings(BuildContext context) async { - await SettingsView.staticPush(context); + await SettingsView().push(context); } @override diff --git a/lib/views/widgets/settings/boolean_config_element.dart b/lib/views/widgets/settings/boolean_config_element.dart new file mode 100644 index 0000000..078d558 --- /dev/null +++ b/lib/views/widgets/settings/boolean_config_element.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; + +class BooleanConfigElement extends StatelessWidget { + const BooleanConfigElement({ + super.key, + required this.title, + required this.subtitleEnabled, + required this.subtitleDisabled, + required this.value, + required this.onChange, + }); + + final String title; + final String subtitleEnabled; + final String subtitleDisabled; + final bool value; + final Function(bool val) onChange; + @override + Widget build(BuildContext context) { + return CheckboxListTile( + title: Text(title), + subtitle: Text(value ? subtitleEnabled : subtitleDisabled), + value: value, + onChanged: (bool? value) { + onChange(value == true); + }, + ); + } +} diff --git a/lib/views/widgets/settings/integer_config_element.dart b/lib/views/widgets/settings/integer_config_element.dart new file mode 100644 index 0000000..66f0cdc --- /dev/null +++ b/lib/views/widgets/settings/integer_config_element.dart @@ -0,0 +1,46 @@ +import 'package:cupcake/utils/alerts/widget.dart'; +import 'package:cupcake/utils/config.dart'; +import 'package:flutter/material.dart'; + +class IntegerConfigElement extends StatelessWidget { + IntegerConfigElement({ + super.key, + required this.title, + required this.hint, + required this.value, + required this.onChange, + }); + + final String title; + final String? hint; + final int value; + final Function(int val) onChange; + late final ctrl = TextEditingController(text: value.toString()); + @override + Widget build(BuildContext context) { + return ListTile( + title: Text(title), + onLongPress: () { + CupcakeConfig.instance.debug = true; + CupcakeConfig.instance.save(); + }, + subtitle: TextField( + controller: ctrl, + onSubmitted: (String value) { + final i = int.tryParse(value); + if (i == null) return; + onChange(i); + }, + ), + trailing: hint == null + ? null + : IconButton( + icon: const Icon(Icons.info), + onPressed: () { + showAlertWidget( + context: context, title: title, body: [Text(hint ?? "")]); + }, + ), + ); + } +} diff --git a/lib/views/widgets/settings/version_widget.dart b/lib/views/widgets/settings/version_widget.dart new file mode 100644 index 0000000..a2e0a3c --- /dev/null +++ b/lib/views/widgets/settings/version_widget.dart @@ -0,0 +1,84 @@ +import 'package:cupcake/utils/config.dart'; +import 'package:flutter/material.dart'; +import 'package:package_info_plus/package_info_plus.dart'; + +class VersionWidget extends StatefulWidget { + const VersionWidget({super.key}); + + @override + State createState() => _VersionWidgetState(); +} + +class _VersionWidgetState extends State { + Future showWidget(BuildContext context) async { + PackageInfo packageInfo = await PackageInfo.fromPlatform(); + + String appName = packageInfo.appName; + String packageName = packageInfo.packageName; + String version = packageInfo.version; + String buildNumber = packageInfo.buildNumber; + showAboutDialog( + context: context, + applicationName: appName, + applicationVersion: "$version+$buildNumber"); + } + + List easterEgg = [ + "¯\\_(ツ)_/¯", // Shrug + "( ͡° ͜ʖ ͡°)", // Lenny Face + "(╯°□°)╯︵ ┻━┻", // Table Flip + "┬─┬ ノ( ゜-゜ノ)", // Table Unflip + "ಠ_ಠ", // Disapproval Look + "(ಥ﹏ಥ)", // Crying + "ʕ•ᴥ•ʔ", // Bear Hug + "( ^_^)o自自o(^_^ )", // Cheers! + "✨🌟✨", // Sparkles + "+(._.)+", // Robot Face + "<(o_o<)", // Kirby Dance + "(>'-')> <('-'<)", // High Five! + "d(^_^)b", // Thumbs Up + "(* ^ ω ^)", // Cute Smiling Face + "(\\__/)", // Bunny + "(^._.^)ノ", // Cat waving + "ʕ •́؈•̀ ₎", // Angry Bear + "/ᐠ。‸。ᐟ\\", // Kitty Frustrated + "| (• ◡•)|", // Simple Smiling Face + "<('o'<)", // Happy Kirby + "(-'-)=o", // Fighting Stance + "/╲/( •̀ ω •́ )╯", // Action Pose + "ʕง•ᴥ•ʔง", // Flexing Bear + "(.❛ ᴗ ❛.)", // Smiling Face + "(╥_╥)", // Sad Crying + "(ง'̀-'́)ง", // Fight Me! + "(ノಠ益ಠ)ノ", // Rage Flip + ]; + + String? subtitle; + + Future _debugTrigger() async { + if (easterEgg.isEmpty) { + if (CupcakeConfig.instance.debug) return; + CupcakeConfig.instance.debug = true; + CupcakeConfig.instance.save(); + setState(() { + subtitle = "debug options enabled"; + }); + Navigator.of(context).pop(); + return; + } + easterEgg.shuffle(); + setState(() { + subtitle = easterEgg.removeAt(0); + }); + } + + @override + Widget build(BuildContext context) { + return ListTile( + title: const Text("About the app"), + subtitle: subtitle == null ? null : Text(subtitle ?? "..."), + onTap: subtitle != null ? _debugTrigger : () => showWidget(context), + onLongPress: _debugTrigger, + ); + } +} From 546159c86482fcd13bd9d05fde0aba263805a7b8 Mon Sep 17 00:00:00 2001 From: Czarek Nakamoto Date: Wed, 5 Feb 2025 11:37:02 +0100 Subject: [PATCH 06/13] remove marksNeedsBuild update to RC10 monero_c use xcframework instead of .framework --- .env | 2 +- Makefile | 13 +- ios/.gitignore | 3 +- ios/MoneroWallet.framework/Info.plist | Bin 794 -> 0 bytes ios/Runner.xcodeproj/project.pbxproj | 8 +- ios/gen_framework.sh | 177 +++++++++++++++++-- ios/native_libs/.gitignore | 1 + lib/view_model/abstract.dart | 18 +- lib/view_model/create_wallet_view_model.dart | 37 ++-- lib/view_model/home_screen_view_model.dart | 9 + lib/view_model/urqr_view_model.dart | 16 +- lib/views/abstract.dart | 4 - lib/views/animated_qr_page.dart | 14 +- lib/views/create_wallet.dart | 14 +- lib/views/home_screen.dart | 17 +- lib/views/settings.dart | 1 - pubspec.lock | 4 +- pubspec.yaml | 2 +- 18 files changed, 253 insertions(+), 87 deletions(-) delete mode 100644 ios/MoneroWallet.framework/Info.plist create mode 100644 ios/native_libs/.gitignore diff --git a/.env b/.env index 2fc8d80..cea5220 100644 --- a/.env +++ b/.env @@ -1,2 +1,2 @@ -MONERO_C_TAG=v0.18.3.4-RC9 +MONERO_C_TAG=v0.18.3.4-RC10 COIN=monero diff --git a/Makefile b/Makefile index eb693f5..6c4418e 100644 --- a/Makefile +++ b/Makefile @@ -15,11 +15,20 @@ libs_android_build_ci: ./build_moneroc.sh --coin ${COIN} --tag ${MONERO_C_TAG} --triplet aarch64-linux-android --location android/app/src/main/jniLibs/arm64-v8a libs_ios_download: - ./build_moneroc.sh --prebuild --coin ${COIN} --tag ${MONERO_C_TAG} --triplet aarch64-apple-ios --location ios + ./build_moneroc.sh --prebuild --coin ${COIN} --tag ${MONERO_C_TAG} --triplet aarch64-apple-ios --location ios/native_libs/ios-arm64 cd ios && ./gen_framework.sh libs_ios_build: [[ "x$(shell uname)" == "xDarwin" ]] || exit 1 # Only Darwin hosts can build for iOS, try $(MAKE) libs_ios_download - ./build_moneroc.sh --coin ${COIN} --tag ${MONERO_C_TAG} --triplet aarch64-apple-ios --location ios + ./build_moneroc.sh --coin ${COIN} --tag ${MONERO_C_TAG} --triplet aarch64-apple-ios --location ios/native_libs/ios-arm64 + cd ios && ./gen_framework.sh + +libs_iossimulator_download: + ./build_moneroc.sh --prebuild --coin ${COIN} --tag ${MONERO_C_TAG} --triplet aarch64-apple-iossimulator --location ios/native_libs/ios-arm64-simulator + cd ios && ./gen_framework.sh + +libs_iossimulator_build: + [[ "x$(shell uname)" == "xDarwin" ]] || exit 1 # Only Darwin hosts can build for iOS, try $(MAKE) libs_ios_download + ./build_moneroc.sh --coin ${COIN} --tag ${MONERO_C_TAG} --triplet aarch64-apple-iossimulator --location ios/native_libs/ios-arm64-simulator cd ios && ./gen_framework.sh cupcake_android_monero: diff --git a/ios/.gitignore b/ios/.gitignore index 3bfe9f3..96ff9bb 100644 --- a/ios/.gitignore +++ b/ios/.gitignore @@ -35,4 +35,5 @@ Runner/GeneratedPluginRegistrant.* libwallet2_api_c.dylib MoneroWallet.framework/MoneroWallet -monero_libwallet2_api_c.dylib \ No newline at end of file +monero_libwallet2_api_c.dylib +MoneroWallet.xcframework \ No newline at end of file diff --git a/ios/MoneroWallet.framework/Info.plist b/ios/MoneroWallet.framework/Info.plist deleted file mode 100644 index 4cf968f5edcfbdf9ba6a40d40f5c69264ca8794e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 794 zcmZXQ&rcIU6vtKmqO(I;_cIb_^2gz=eTPVnRHB*iMhiZTG8Uy zma11d30kgicYiU2bb?ihxl7tq-}j zX}KyhgGdVT_y36+N$Q0c)C>c%&AThExVFbQ^$j7rUaHY&A@ivfEy+x}LOim~I13n! z#tUWUFgvs>+W!+vR%8cjG3sxvmyY2gu^HbtgyEC5dt9^UZ_nA`M3%rmfq5YB1PWZq8nbUX5rnA7Ejd8w|*1x1!>)Ojq|mHC3Ab_G0u)OiXkMlkC#x zu@LdJs!w "$plist_path" < + + + + BuildMachineOSBuild + 23E224 + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${framework_name} + CFBundleIdentifier + com.fotolockr.${framework_name} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${framework_name} + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ??? + CFBundleSupportedPlatforms + + ${platform} + + CFBundleVersion + 1 + DTCompiler + com.apple.compilers.llvm.clang.1_0 + DTPlatformBuild + 21E210 + DTPlatformName + ${dtplatformname} + DTPlatformVersion + 17.4 + DTSDKBuild + 21E210 + DTSDKName + ${dtsdkname} + DTXcode + 1530 + DTXcodeBuild + 15E204a + MinimumOSVersion + 16.0 + UIDeviceFamily + + 1 + 2 + + UIRequiredDeviceCapabilities + + arm64 + + + +EOF + plutil -convert binary1 "$plist_path" +} + +create_framework() { + wallet="$1" + framework_name="$2" + target="$3" + out_dir="$4" + + echo "Creating ${framework_name}.framework for target ${target} in ${out_dir}..." + + framework_bundle="${out_dir}/${framework_name}.framework" + + rm -rf "$framework_bundle" + mkdir -p "$framework_bundle" + + input_dylib="${DYLIB_PATH}/${target}/${wallet}_libwallet2_api_c.dylib" + if [[ ! -f "$input_dylib" ]]; then + echo "Error: Input dylib not found: $input_dylib" + exit 1 + fi + + lipo -create "$input_dylib" -output "${framework_bundle}/${framework_name}" + echo "Created binary: ${framework_bundle}/${framework_name}" + + write_info_plist "$framework_bundle" "$framework_name" "$target" +} + +create_xcframework() { + framework_name="$1" + device_framework="$2" + simulator_framework="$3" + xcframework_output="$4" + + echo "Creating ${xcframework_output} by bundling:" + echo " Device framework: ${device_framework}" + echo " Simulator framework: ${simulator_framework}" + + xcodebuild -create-xcframework \ + -framework "$device_framework" \ + -framework "$simulator_framework" \ + -output "$xcframework_output" + + echo "Created XCFramework: ${xcframework_output}" +} + +wallets=("monero") # "wownero" "zano") +framework_names=("MoneroWallet") # "WowneroWallet" "ZanoWallet") + +for i in "${!wallets[@]}"; do + wallet="${wallets[$i]}" + framework_name="${framework_names[$i]}" + + device_out="${TMP_DIR}/${framework_name}_device" + simulator_out="${TMP_DIR}/${framework_name}_simulator" + rm -rf "$device_out" "$simulator_out" + mkdir -p "$device_out" "$simulator_out" + + create_framework "$wallet" "$framework_name" "ios-arm64" "$device_out" + sleep 5 + create_framework "$wallet" "$framework_name" "ios-arm64-simulator" "$simulator_out" + + device_framework="${device_out}/${framework_name}.framework" + simulator_framework="${simulator_out}/${framework_name}.framework" + xcframework_output="${IOS_DIR}/${framework_name}.xcframework" + rm -rf "$xcframework_output" + + create_xcframework "$framework_name" "$device_framework" "$simulator_framework" "$xcframework_output" +done + +echo "All XCFrameworks created successfully." + +rm -rf "$TMP_DIR" diff --git a/ios/native_libs/.gitignore b/ios/native_libs/.gitignore new file mode 100644 index 0000000..e5d0467 --- /dev/null +++ b/ios/native_libs/.gitignore @@ -0,0 +1 @@ +*.dylib diff --git a/lib/view_model/abstract.dart b/lib/view_model/abstract.dart index 5725134..1f583ab 100644 --- a/lib/view_model/abstract.dart +++ b/lib/view_model/abstract.dart @@ -5,29 +5,31 @@ class ViewModel { String get screenName => "screenName"; AppLocalizations get L { - if (_lcache == null && _context == null) { + if (_lcache == null && c == null) { throw Exception( "context is null in view model. Did you forget to register()?"); } - if (_lcache == null && _context?.mounted != true) { + if (_lcache == null && c?.mounted != true) { throw Exception( "context is not mounted. Did you register incorrect context?"); } - _lcache ??= AppLocalizations.of(_context!); + _lcache ??= AppLocalizations.of(c!); return _lcache!; } AppLocalizations? _lcache; - BuildContext? _context; + BuildContext? c; void register(BuildContext context) { - _context = context; + c = context; } + bool get mounted => c?.mounted??false; + markNeedsBuild() { - if (_context == null) { - throw Exception("_context is null, did you forget to register(context)?"); + if (c == null) { + throw Exception("c is null, did you forget to register(context)?"); } - (_context as Element).markNeedsBuild(); + (c as Element).markNeedsBuild(); } } diff --git a/lib/view_model/create_wallet_view_model.dart b/lib/view_model/create_wallet_view_model.dart index dee461a..8060faa 100644 --- a/lib/view_model/create_wallet_view_model.dart +++ b/lib/view_model/create_wallet_view_model.dart @@ -28,8 +28,20 @@ class CreateWalletViewModel extends ViewModel { final CreateMethod createMethod; - bool isPinSet = false; - bool showExtra = false; + bool _isPinSet = false; + bool get isPinSet => _isPinSet; + set isPinSet(bool newIsPinSet) { + _isPinSet = newIsPinSet; + markNeedsBuild(); + } + + bool _showExtra = false; + bool get showExtra => _showExtra; + set showExtra(bool newShowExtra) { + _showExtra = newShowExtra; + markNeedsBuild(); + } + @override late String screenName = screenNameOriginal; @@ -42,7 +54,12 @@ class CreateWalletViewModel extends ViewModel { List get coins => walletCoins; - bool isCreate = true; + bool _isCreate = false; + bool get isCreate => _isCreate; + set isCreate(bool newIsCreate) { + _isCreate = newIsCreate; + markNeedsBuild(); + } bool get hasAdvancedOptions { if (currentForm == null) return false; @@ -54,18 +71,17 @@ class CreateWalletViewModel extends ViewModel { return false; } - void toggleAdvancedOptions() { - print("toggling"); - showExtra = !showExtra; - markNeedsBuild(); - } - - late Coin? selectedCoin = () { + late Coin? _selectedCoin = () { if (coins.length == 1) { return coins[0]; } return null; }(); + Coin? get selectedCoin => _selectedCoin; + set selectedCoin(Coin? newSelectedCoin) { + _selectedCoin = newSelectedCoin; + markNeedsBuild(); + } late StringFormElement walletName = StringFormElement( L.wallet_name, @@ -234,7 +250,6 @@ class CreateWalletViewModel extends ViewModel { if ((await walletName.value).isEmpty) { throw Exception(L.warning_input_cannot_be_empty); } - print(currentForm == _createForm); final cw = await selectedCoin!.createNewWallet( await walletName.value, await walletPassword.value, diff --git a/lib/view_model/home_screen_view_model.dart b/lib/view_model/home_screen_view_model.dart index 7333585..dd6045b 100644 --- a/lib/view_model/home_screen_view_model.dart +++ b/lib/view_model/home_screen_view_model.dart @@ -12,6 +12,15 @@ class HomeScreenViewModel extends ViewModel { String? lastOpenedWallet; + Future loadInitialState() async { + await Future.delayed(Duration.zero); // load the screen + if (CupcakeConfig.instance.lastWallet == null) return; + if (mounted) return; + if (!openLastWallet) return; + if (CupcakeConfig.instance.lastWallet?.exists() != true) return; + CupcakeConfig.instance.lastWallet!.openUI(c!); + } + Future> get wallets async { List wallets = []; for (var coin in walletCoins) { diff --git a/lib/view_model/urqr_view_model.dart b/lib/view_model/urqr_view_model.dart index f151986..89ac66b 100644 --- a/lib/view_model/urqr_view_model.dart +++ b/lib/view_model/urqr_view_model.dart @@ -10,5 +10,19 @@ class URQRViewModel extends ViewModel { Map> urqrList; - late List urqr = urqrList[urqrList.keys.first]!; + late List _urqr = urqrList[urqrList.keys.first]!; + List get urqr => _urqr..removeWhere((elm) => elm.isEmpty); + set urqr(List newUrqr) { + _urqr = newUrqr; + markNeedsBuild(); + } + + List get alternativeCodes { +final Map> copiedList = {}; + copiedList.addAll(urqrList); + copiedList.removeWhere((key, value) => + value.join("\n").trim() == urqr.join("\n").trim()); + final keys = copiedList.keys; + return keys.toList(); + } } diff --git a/lib/views/abstract.dart b/lib/views/abstract.dart index 546f473..ebdebca 100644 --- a/lib/views/abstract.dart +++ b/lib/views/abstract.dart @@ -95,8 +95,4 @@ class AbstractView extends StatefulWidget { Widget? bottomNavigationBar(BuildContext context) => null; Widget? floatingActionButton(BuildContext context) => null; - - void markNeedsBuild() { - state!.setState(() {}); - } } diff --git a/lib/views/animated_qr_page.dart b/lib/views/animated_qr_page.dart index c05ffbd..ffbda39 100644 --- a/lib/views/animated_qr_page.dart +++ b/lib/views/animated_qr_page.dart @@ -18,7 +18,7 @@ class AnimatedURPage extends AbstractView { Padding( padding: const EdgeInsets.only(top: 64.0, left: 32, right: 32), child: URQR( - frames: viewModel.urqr..removeWhere((element) => element.isEmpty), + frames: viewModel.urqr, ), ), const SizedBox(height: 32), @@ -28,14 +28,9 @@ class AnimatedURPage extends AbstractView { } List _extraButtons() { - final Map> copiedList = {}; - copiedList.addAll(viewModel.urqrList); - copiedList.removeWhere((key, value) => - value.join("\n").trim() == viewModel.urqr.join("\n").trim()); final List toRet = []; - final keys = copiedList.keys; - for (var key in keys) { - toRet.add(_urqrSwitchButton(key, copiedList[key]!)); + for (var key in viewModel.alternativeCodes) { + toRet.add(_urqrSwitchButton(key, viewModel.urqrList[key]!)); } return toRet; } @@ -44,8 +39,7 @@ class AnimatedURPage extends AbstractView { return OutlinedButton( onPressed: () { viewModel.urqr = value; - viewModel.markNeedsBuild(); }, - child: Text(key)); + child: Text(key),); } } diff --git a/lib/views/create_wallet.dart b/lib/views/create_wallet.dart index cf8e859..3bb8374 100644 --- a/lib/views/create_wallet.dart +++ b/lib/views/create_wallet.dart @@ -21,11 +21,6 @@ class CreateWallet extends AbstractView { @override final CreateWalletViewModel viewModel; - void setPinSet(BuildContext context, bool val) { - viewModel.isPinSet = val; - markNeedsBuild(); - } - @override Widget? body(BuildContext context) { if (viewModel.selectedCoin == null) { @@ -35,7 +30,6 @@ class CreateWallet extends AbstractView { return InkWell( onTap: () { viewModel.selectedCoin = viewModel.coins[index]; - markNeedsBuild(); }, child: Card( child: ListTile( @@ -57,7 +51,6 @@ class CreateWallet extends AbstractView { return InkWell( onTap: () { viewModel.currentForm = value; - markNeedsBuild(); }, child: Card( child: ListTile( @@ -73,8 +66,7 @@ class CreateWallet extends AbstractView { formElements: viewModel.currentForm ?? [], scaffoldContext: context, rebuild: (bool val) { - setPinSet(context, val); - markNeedsBuild(); + viewModel.isPinSet = val; }, isPinSet: viewModel.isPinSet, showExtra: viewModel.showExtra, @@ -114,8 +106,8 @@ class CreateWallet extends AbstractView { text: L.advanced_options, icon: null, onPressed: () { - viewModel.toggleAdvancedOptions(); - }, // TODO: passphrase + viewModel.showExtra = true; + }, backgroundColor: const WidgetStatePropertyAll(Colors.transparent), ), const SizedBox(height: 16), diff --git a/lib/views/home_screen.dart b/lib/views/home_screen.dart index 8c7d954..14772f8 100644 --- a/lib/views/home_screen.dart +++ b/lib/views/home_screen.dart @@ -24,7 +24,7 @@ class HomeScreen extends AbstractView { return FutureBuilder( future: viewModel.showLandingInfo, builder: (BuildContext context, AsyncSnapshot value) { - if (!value.hasData) return Container(); // TODO: placeholder? + if (!value.hasData) return Container(); if (value.data!) { return Text(L.home_no_wallets); } @@ -52,7 +52,7 @@ class HomeScreen extends AbstractView { ); Widget walletsBody( BuildContext context, AsyncSnapshot> wallets) { - if (!wallets.hasData) return Container(); // TODO: placeholder? + if (!wallets.hasData) return Container(); return ListView.builder( itemCount: wallets.data!.length, itemBuilder: (BuildContext context, int index) { @@ -84,7 +84,7 @@ class HomeScreen extends AbstractView { () => renameWallet(context, wallets.data![index]), "Renaming wallet", ); - markNeedsBuild(); + viewModel.markNeedsBuild(); }, ), title: Text( @@ -114,7 +114,7 @@ class HomeScreen extends AbstractView { needsPasswordConfirm: false, ).push(context); if (!context.mounted) return; - markNeedsBuild(); + viewModel.markNeedsBuild(); } @override @@ -138,12 +138,7 @@ class HomeScreen extends AbstractView { } @override - Future initState(BuildContext context) async { - await Future.delayed(Duration.zero); // load the screen - if (CupcakeConfig.instance.lastWallet == null) return; - if (!context.mounted) return; - if (!viewModel.openLastWallet) return; - if (CupcakeConfig.instance.lastWallet?.exists() != true) return; - CupcakeConfig.instance.lastWallet!.openUI(context); + Future initState(BuildContext context) { + return viewModel.loadInitialState(); } } diff --git a/lib/views/settings.dart b/lib/views/settings.dart index 98c4d21..4c31a7f 100644 --- a/lib/views/settings.dart +++ b/lib/views/settings.dart @@ -16,7 +16,6 @@ class SettingsView extends AbstractView { Future postUpdate(BuildContext context) async { viewModel.appConfig.save(); - markNeedsBuild(); } @override diff --git a/pubspec.lock b/pubspec.lock index b3bbc90..a0db319 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -638,8 +638,8 @@ packages: dependency: "direct main" description: path: "impls/monero.dart" - ref: "v0.18.3.4-RC9" - resolved-ref: "127c54599c612d7d3d226e4c016d5812e7a0966d" + ref: "v0.18.3.4-RC10" + resolved-ref: "9301097ff504525070cc0cb915fe2f1bb0670345" url: "https://github.com/mrcyjanek/monero_c" source: git version: "0.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 8ff6f60..7f82b90 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -38,7 +38,7 @@ dependencies: monero: git: url: https://github.com/mrcyjanek/monero_c - ref: v0.18.3.4-RC9 + ref: v0.18.3.4-RC10 path: impls/monero.dart path: ^1.9.0 path_provider: ^2.1.5 From 72eaf70c504432ee34f5600247d5a1e64e171a16 Mon Sep 17 00:00:00 2001 From: Czarek Nakamoto Date: Fri, 7 Feb 2025 16:17:59 +0100 Subject: [PATCH 07/13] bump flutter to 3.27.4 add lints make all final variables final fix Makefile, use prebuilds on CI simplify format.sh script global fatal error handling add new method on coin to see if seed appears legit await all async calls make FormElement properly abstract and add errorHandler remove BuildContext from where it's not required simplify validator in FormElement, create templates move renameWallet and createWallet to ViewModel remove deprecated methods in QrImageView Add insecure biometric option add codegen @RebuildOnChange - can be user in ViewModel and automatically rebuilds the UI on change @ExposeRebuildableAccessors - makes it easy to expose all accessors of a class in ViewModel - used in SettingsViewModel to simlify code. @ThrowOnUI - show an error to the user when a function throws --- .fvmrc | 2 +- .gitignore | 3 +- .tooling/format.sh | 14 +- .vscode/settings.json | 2 +- Makefile | 25 +-- analysis_options.yaml | 7 + build.yaml | 16 ++ ios/native_libs/ios-arm64-simulator/.gitkeep | 0 ios/native_libs/ios-arm64/.gitkeep | 0 lib/coins/abstract/coin.dart | 26 +-- lib/coins/abstract/wallet.dart | 6 +- lib/coins/abstract/wallet_info.dart | 12 +- lib/coins/monero/coin.dart | 76 +++---- lib/coins/monero/wallet.dart | 36 ++-- lib/coins/monero/wallet_info.dart | 14 +- lib/dev/generate_rebuild.dart | 23 +++ lib/dev/rebuild_generator.dart | 185 ++++++++++++++++++ lib/l10n/app_en.arb | 1 + lib/main.dart | 37 +++- lib/panic_handler.dart | 100 ++++++++++ lib/utils/alerts/basic.dart | 12 +- lib/utils/alerts/widget.dart | 10 +- lib/utils/alerts/widget_minimal.dart | 6 +- lib/utils/call_throwable.dart | 29 --- lib/utils/capitalize.dart | 5 + lib/utils/config.dart | 11 +- lib/utils/form/abstract_form_element.dart | 5 +- lib/utils/form/abstract_value_outcome.dart | 4 +- lib/utils/form/default_validator.dart | 3 - .../flutter_secure_storage_value_outcome.dart | 18 +- lib/utils/form/pin_form_element.dart | 38 ++-- lib/utils/form/plain_value_outcome.dart | 4 +- .../form/single_choice_form_element.dart | 13 +- lib/utils/form/string_form_element.dart | 24 ++- lib/utils/form/validators.dart | 14 ++ lib/utils/random_name.dart | 2 +- lib/utils/secure_storage.dart | 4 +- lib/utils/urqr.dart | 6 +- lib/view_model/abstract.dart | 60 +++++- .../barcode_scanner_view_model.dart | 44 +++-- lib/view_model/create_wallet_view_model.dart | 162 +++++++-------- lib/view_model/home_screen_view_model.dart | 39 +++- .../new_wallet_info_view_model.dart | 15 +- lib/view_model/open_wallet_view_model.dart | 46 ++--- lib/view_model/receive_view_model.dart | 2 +- .../security_backup_view_model.dart | 60 +++--- lib/view_model/settings_view_model.dart | 8 +- .../unconfirmed_transaction_view_model.dart | 5 +- lib/view_model/urqr_view_model.dart | 11 +- lib/view_model/wallet_edit_view_model.dart | 48 ++--- lib/view_model/wallet_home_view_model.dart | 5 +- lib/views/abstract.dart | 19 +- lib/views/animated_qr_page.dart | 17 +- lib/views/barcode_scanner.dart | 7 +- lib/views/create_wallet.dart | 31 ++- lib/views/home_screen.dart | 55 ++---- lib/views/initial_setup_screen.dart | 6 +- lib/views/new_wallet_info.dart | 10 +- lib/views/open_wallet.dart | 4 +- lib/views/receive.dart | 13 +- lib/views/security_backup.dart | 25 ++- lib/views/settings.dart | 52 ++--- lib/views/unconfirmed_transaction.dart | 27 ++- lib/views/wallet_edit.dart | 22 +-- lib/views/wallet_home.dart | 5 +- .../barcode_scanner/progress_painter.dart | 10 +- .../barcode_scanner/switch_camera.dart | 4 +- .../toggle_flashlight_button.dart | 4 +- .../barcode_scanner/urqr_progress.dart | 2 +- lib/views/widgets/cake_card.dart | 2 +- lib/views/widgets/cupcake_appbar_title.dart | 10 +- lib/views/widgets/drawer_element.dart | 7 +- lib/views/widgets/drawer_elements.dart | 10 +- lib/views/widgets/form_builder.dart | 73 ++++--- .../widgets/numerical_keyboard/keyboard.dart | 6 +- .../widgets/numerical_keyboard/main.dart | 2 +- .../numerical_keyboard/single_key.dart | 2 +- .../settings/boolean_config_element.dart | 4 +- .../settings/integer_config_element.dart | 4 +- .../widgets/settings/version_widget.dart | 14 +- lib/views/widgets/urqr.dart | 14 +- pubspec.lock | 12 +- pubspec.yaml | 4 + 83 files changed, 1102 insertions(+), 673 deletions(-) create mode 100644 build.yaml create mode 100644 ios/native_libs/ios-arm64-simulator/.gitkeep create mode 100644 ios/native_libs/ios-arm64/.gitkeep create mode 100644 lib/dev/generate_rebuild.dart create mode 100644 lib/dev/rebuild_generator.dart create mode 100644 lib/panic_handler.dart create mode 100644 lib/utils/capitalize.dart delete mode 100644 lib/utils/form/default_validator.dart create mode 100644 lib/utils/form/validators.dart diff --git a/.fvmrc b/.fvmrc index d7891c2..c2783c6 100644 --- a/.fvmrc +++ b/.fvmrc @@ -1,3 +1,3 @@ { - "flutter": "3.27.3" + "flutter": "3.27.4" } \ No newline at end of file diff --git a/.gitignore b/.gitignore index de5cb3f..adb73b6 100644 --- a/.gitignore +++ b/.gitignore @@ -51,4 +51,5 @@ lib/gen/*.dart missing_translations.txt # FVM Version Cache -.fvm/ \ No newline at end of file +.fvm/ +*.g.dart \ No newline at end of file diff --git a/.tooling/format.sh b/.tooling/format.sh index 6d19ee2..14ddfff 100755 --- a/.tooling/format.sh +++ b/.tooling/format.sh @@ -2,15 +2,7 @@ set -x -e cd "$(dirname "$0")" cd .. -dart run build_runner build -pushd lib - for dir in coins themes utils view_model views; - do - pushd $dir - dart fix --apply - dart format . - popd - dart format main.dart - done -popd +dart run build_runner build --delete-conflicting-outputs +dart fix --apply . +dart format . flutter gen-l10n diff --git a/.vscode/settings.json b/.vscode/settings.json index cd2c40b..926bd45 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { - "dart.flutterSdkPath": ".fvm/versions/3.27.3" + "dart.flutterSdkPath": ".fvm/versions/3.27.4" } \ No newline at end of file diff --git a/Makefile b/Makefile index 6c4418e..987c4ea 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ include .env +UNAME := $(shell uname) libs_android_download: ./build_moneroc.sh --prebuild --coin ${COIN} --tag ${MONERO_C_TAG} --triplet x86_64-linux-android --location android/app/src/main/jniLibs/x86_64 @@ -6,33 +7,33 @@ libs_android_download: ./build_moneroc.sh --prebuild --coin ${COIN} --tag ${MONERO_C_TAG} --triplet armv7a-linux-androideabi --location android/app/src/main/jniLibs/armeabi-v7a libs_android_build: - [[ ! "x$(shell uname)" == "xLinux" ]] && exit 1 # Only Linux hosts can build for android, try $(MAKE) libs_android_download +ifneq ($(UNAME), Linux) + echo Only Linux hosts can build for android, try $(MAKE) libs_android_download + exit 1 +endif ./build_moneroc.sh --coin ${COIN} --tag ${MONERO_C_TAG} --triplet x86_64-linux-android --location android/app/src/main/jniLibs/x86_64 ./build_moneroc.sh --coin ${COIN} --tag ${MONERO_C_TAG} --triplet aarch64-linux-android --location android/app/src/main/jniLibs/arm64-v8a ./build_moneroc.sh --coin ${COIN} --tag ${MONERO_C_TAG} --triplet armv7a-linux-androideabi --location android/app/src/main/jniLibs/armeabi-v7a libs_android_build_ci: - ./build_moneroc.sh --coin ${COIN} --tag ${MONERO_C_TAG} --triplet aarch64-linux-android --location android/app/src/main/jniLibs/arm64-v8a + ./build_moneroc.sh --prebuild --coin ${COIN} --tag ${MONERO_C_TAG} --triplet aarch64-linux-android --location android/app/src/main/jniLibs/arm64-v8a libs_ios_download: ./build_moneroc.sh --prebuild --coin ${COIN} --tag ${MONERO_C_TAG} --triplet aarch64-apple-ios --location ios/native_libs/ios-arm64 + ./build_moneroc.sh --prebuild --coin ${COIN} --tag ${MONERO_C_TAG} --triplet aarch64-apple-ios --location ios/native_libs/ios-arm64-simulator cd ios && ./gen_framework.sh + libs_ios_build: - [[ "x$(shell uname)" == "xDarwin" ]] || exit 1 # Only Darwin hosts can build for iOS, try $(MAKE) libs_ios_download +ifneq ($(UNAME), Darwin) + echo Only Darwin hosts can build for iOS, try $(MAKE) libs_ios_download + exit 1 +endif ./build_moneroc.sh --coin ${COIN} --tag ${MONERO_C_TAG} --triplet aarch64-apple-ios --location ios/native_libs/ios-arm64 - cd ios && ./gen_framework.sh - -libs_iossimulator_download: - ./build_moneroc.sh --prebuild --coin ${COIN} --tag ${MONERO_C_TAG} --triplet aarch64-apple-iossimulator --location ios/native_libs/ios-arm64-simulator - cd ios && ./gen_framework.sh - -libs_iossimulator_build: - [[ "x$(shell uname)" == "xDarwin" ]] || exit 1 # Only Darwin hosts can build for iOS, try $(MAKE) libs_ios_download ./build_moneroc.sh --coin ${COIN} --tag ${MONERO_C_TAG} --triplet aarch64-apple-iossimulator --location ios/native_libs/ios-arm64-simulator cd ios && ./gen_framework.sh cupcake_android_monero: - dart run build_runner build + dart run build_runner build --delete-conflicting-outputs flutter build apk --dart-define=COIN_MONERO=true prepare_dev: diff --git a/analysis_options.yaml b/analysis_options.yaml index a449cbd..e4069a7 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -7,3 +7,10 @@ include: package:flutter_lints/flutter.yaml linter: rules: unawaited_futures: true + avoid_print: false + parameter_assignments: true + prefer_const_declarations: true + prefer_final_fields: true + prefer_final_in_for_each: true + prefer_final_locals: true + prefer_final_parameters: true diff --git a/build.yaml b/build.yaml new file mode 100644 index 0000000..990e53d --- /dev/null +++ b/build.yaml @@ -0,0 +1,16 @@ +targets: + $default: + builders: + cupcake|rebuild_generator: + generate_for: + include: + - lib/**.dart + - example/**.dart + +builders: + rebuild_generator: + import: "package:cupcake/dev/rebuild_generator.dart" + builder_factories: ["rebuildBuilder"] + build_extensions: {".dart": [".g.dart"]} + auto_apply: dependents + build_to: source diff --git a/ios/native_libs/ios-arm64-simulator/.gitkeep b/ios/native_libs/ios-arm64-simulator/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/ios/native_libs/ios-arm64/.gitkeep b/ios/native_libs/ios-arm64/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/coins/abstract/coin.dart b/lib/coins/abstract/coin.dart index 48f835b..0fd5dd1 100644 --- a/lib/coins/abstract/coin.dart +++ b/lib/coins/abstract/coin.dart @@ -15,18 +15,20 @@ abstract class Coin { Future> get coinWallets; Future createNewWallet( - String walletName, - String walletPassword, { - ProgressCallback? progressCallback, - required bool? createWallet, - required String? seed, - required int? restoreHeight, - required String? primaryAddress, - required String? viewKey, - required String? spendKey, - required String? seedOffsetOrEncryption, + final String walletName, + final String walletPassword, { + final ProgressCallback? progressCallback, + required final bool? createWallet, + required final String? seed, + required final int? restoreHeight, + required final String? primaryAddress, + required final String? viewKey, + required final String? spendKey, + required final String? seedOffsetOrEncryption, }); - Future openWallet(CoinWalletInfo walletInfo, - {required String password}); + bool isSeedSomewhatLegit(final String seed); + + Future openWallet(final CoinWalletInfo walletInfo, + {required final String password}); } diff --git a/lib/coins/abstract/wallet.dart b/lib/coins/abstract/wallet.dart index 428f958..9c19f87 100644 --- a/lib/coins/abstract/wallet.dart +++ b/lib/coins/abstract/wallet.dart @@ -9,7 +9,7 @@ abstract class CoinWallet { Coin get coin; - Future handleUR(BuildContext context, URQRData ur) => + Future handleUR(final BuildContext context, final URQRData ur) => throw UnimplementedError(); bool get hasAccountSupport => false; @@ -18,7 +18,7 @@ abstract class CoinWallet { int getAccountsCount(); - void setAccount(int accountIndex); + void setAccount(final int accountIndex); int getAccountId(); @@ -40,6 +40,6 @@ abstract class CoinWallet { Future close(); - Future> seedDetails(AppLocalizations L) => + Future> seedDetails(final AppLocalizations L) => throw UnimplementedError(); } diff --git a/lib/coins/abstract/wallet_info.dart b/lib/coins/abstract/wallet_info.dart index da15ba8..637dacc 100644 --- a/lib/coins/abstract/wallet_info.dart +++ b/lib/coins/abstract/wallet_info.dart @@ -12,14 +12,14 @@ abstract class CoinWalletInfo { Coin get coin; - void openUI(BuildContext context); + void openUI(final BuildContext context); - Future checkWalletPassword(String password); + Future checkWalletPassword(final String password); bool exists(); - Future openWallet(BuildContext context, - {required String password}); + Future openWallet(final BuildContext context, + {required final String password}); Map toJson() { return { @@ -29,7 +29,7 @@ abstract class CoinWalletInfo { }; } - static CoinWalletInfo? fromJson(Map? json) { + static CoinWalletInfo? fromJson(final Map? json) { if (json == null) return null; final type = Coins.values[json["typeIndex"] as int]; final walletName = (json["walletName"] as String); @@ -43,5 +43,5 @@ abstract class CoinWalletInfo { Future deleteWallet(); - Future renameWallet(String newName); + Future renameWallet(final String newName); } diff --git a/lib/coins/monero/coin.dart b/lib/coins/monero/coin.dart index c91607b..c138e2d 100644 --- a/lib/coins/monero/coin.dart +++ b/lib/coins/monero/coin.dart @@ -54,9 +54,9 @@ class Monero implements Coin { // } // final retWallets = wallets.map((e) => MoneroWalletInfo(e)).toList(); // retWallets.removeWhere((element) => element.walletName.trim().isEmpty); - List retWallets = []; + final List retWallets = []; final list = baseDir.listSync(recursive: true, followLinks: true); - for (var element in list) { + for (final element in list) { if (element.absolute.path.endsWith(".keys")) continue; if (!monero.WalletManager_walletExists(wmPtr, element.absolute.path)) { continue; @@ -67,10 +67,10 @@ class Monero implements Coin { } Future createMoneroWallet({ - required ProgressCallback? progressCallback, - required String walletPath, - required String walletPassword, - required String seedOffsetOrEncryption, + required final ProgressCallback? progressCallback, + required final String walletPath, + required final String walletPassword, + required final String seedOffsetOrEncryption, }) async { progressCallback?.call(description: "Generating polyseed"); final newSeed = monero.Wallet_createPolyseed(); @@ -106,11 +106,11 @@ class Monero implements Coin { } Future createMoneroWalletPolyseed({ - required ProgressCallback? progressCallback, - required String walletPath, - required String walletPassword, + required final ProgressCallback? progressCallback, + required final String walletPath, + required final String walletPassword, required String seed, - required String seedOffsetOrEncryption, + required final String seedOffsetOrEncryption, }) async { progressCallback?.call(description: "Creating wallet"); final lang = PolyseedLang.getByPhrase(seed); @@ -153,11 +153,11 @@ class Monero implements Coin { } Future createMoneroWalletSeed({ - required ProgressCallback? progressCallback, - required String walletPath, - required String walletPassword, - required String seed, - required String seedOffsetOrEncryption, + required final ProgressCallback? progressCallback, + required final String walletPath, + required final String walletPassword, + required final String seed, + required final String seedOffsetOrEncryption, }) async { progressCallback?.call(description: "Creating wallet"); final newWptr = monero.WalletManager_recoveryWallet( @@ -185,13 +185,13 @@ class Monero implements Coin { } Future createMoneroWalletKeys({ - required ProgressCallback? progressCallback, - required String walletPath, - required String walletPassword, - required String walletAddress, - required String secretSpendKey, - required String secretViewKey, - required int restoreHeight, + required final ProgressCallback? progressCallback, + required final String walletPath, + required final String walletPassword, + required final String walletAddress, + required final String secretSpendKey, + required final String secretViewKey, + required final int restoreHeight, }) async { progressCallback?.call(description: "Creating wallet"); final newWptr = monero.WalletManager_createWalletFromKeys(wmPtr, @@ -217,16 +217,16 @@ class Monero implements Coin { @override Future createNewWallet( - String walletName, - String walletPassword, { - ProgressCallback? progressCallback, - required bool? createWallet, - required String? seed, - required int? restoreHeight, - required String? primaryAddress, - required String? viewKey, - required String? spendKey, - required String? seedOffsetOrEncryption, + final String walletName, + final String walletPassword, { + final ProgressCallback? progressCallback, + required final bool? createWallet, + required final String? seed, + required final int? restoreHeight, + required final String? primaryAddress, + required final String? viewKey, + required final String? spendKey, + required final String? seedOffsetOrEncryption, }) async { progressCallback?.call( title: "Creating new wallet", description: "Initializing..."); @@ -289,9 +289,9 @@ class Monero implements Coin { } @override - Future openWallet(CoinWalletInfo walletInfo, - {required String password}) async { - for (var wptr in wPtrList) { + Future openWallet(final CoinWalletInfo walletInfo, + {required final String password}) async { + for (final wptr in wPtrList) { monero.WalletManager_closeWallet(wmPtr, wptr, true); } wPtrList.clear(); @@ -318,4 +318,10 @@ class Monero implements Coin { // monero.dart stuff static monero.WalletManager wmPtr = monero.WalletManagerFactory_getWalletManager(); + + @override + bool isSeedSomewhatLegit(final String seed) { + final length = seed.split(" ").length; + return [16, 25].contains(length); + } } diff --git a/lib/coins/monero/wallet.dart b/lib/coins/monero/wallet.dart index a312263..b6add36 100644 --- a/lib/coins/monero/wallet.dart +++ b/lib/coins/monero/wallet.dart @@ -41,7 +41,7 @@ class MoneroWallet implements CoinWallet { @override int getAccountsCount() => monero.Wallet_numSubaddressAccounts(wptr); @override - void setAccount(int accountIndex) { + void setAccount(final int accountIndex) { if (_accountIndex < getAccountsCount()) { throw Exception("Given index is larger than current account count"); } @@ -70,7 +70,7 @@ class MoneroWallet implements CoinWallet { @override String getBalanceString() => (getBalance() / 1e12).toStringAsFixed(12); - Future exportKeyImagesUR(BuildContext context) async { + Future exportKeyImagesUR(final BuildContext context) async { final allImages = monero.Wallet_exportKeyImagesUR(wptr, max_fragment_length: CupcakeConfig.instance.maxFragmentLength, all: true) @@ -88,19 +88,19 @@ class MoneroWallet implements CoinWallet { } @override - Future handleUR(BuildContext context, URQRData ur) async { + Future handleUR(final BuildContext context, final URQRData ur) async { print("handling: ${ur.tag}."); switch (ur.tag) { case "xmr-keyimage" || "xmr-txsigned": throw Exception("Unable to handle ${ur.tag}. This is a offline wallet"); case "xmr-output": monero.Wallet_importOutputsUR(wptr, ur.inputs.join("\n")); - var status = monero.Wallet_status(wptr); + final status = monero.Wallet_status(wptr); if (status != 0) { final error = monero.Wallet_errorString(wptr); throw CoinException(error); } - exportKeyImagesUR(context); + await exportKeyImagesUR(context); save(); case "xmr-txunsigned": print("handling tx-unsignex"); @@ -116,10 +116,10 @@ class MoneroWallet implements CoinWallet { final error = monero.UnsignedTransaction_errorString(txptr); throw CoinException(error); } - Map destMap = {}; + final Map destMap = {}; final amts = monero.UnsignedTransaction_amount(txptr) .split(";") - .map((e) => int.parse(e)) + .map((final e) => int.parse(e)) .toList(); final addrs = monero.UnsignedTransaction_recipientAddress(txptr).split(";"); @@ -135,7 +135,7 @@ class MoneroWallet implements CoinWallet { wallet: this, destMap: destMap, fee: fee, - confirmCallback: (BuildContext context) async { + confirmCallback: () async { final signedTx = monero.UnsignedTransaction_signUR( txptr, CupcakeConfig.instance.maxFragmentLength) .split("\n"); @@ -152,7 +152,7 @@ class MoneroWallet implements CoinWallet { await AnimatedURPage(urqrList: {"signedTx": signedTx}) .push(context); }, - cancelCallback: (BuildContext context) => {}, + cancelCallback: () => {}, ).push(context); save(); default: @@ -160,11 +160,10 @@ class MoneroWallet implements CoinWallet { } } - // TODO: make this match the offset used in cake wallet, and define const String get seedOffset => monero.Wallet_getCacheAttribute(wptr, key: MoneroCacheKeys.seedOffsetCacheKey); - set seedOffset(String newSeedOffset) => monero.Wallet_setCacheAttribute( + set seedOffset(final String newSeedOffset) => monero.Wallet_setCacheAttribute( wptr, key: MoneroCacheKeys.seedOffsetCacheKey, value: newSeedOffset, @@ -182,13 +181,13 @@ class MoneroWallet implements CoinWallet { String? get polyseedDart { try { const coin = PolyseedCoin.POLYSEED_MONERO; - var lang = PolyseedLang.getByName("English"); + final lang = PolyseedLang.getByName("English"); - var polyseedString = polyseed ?? + final polyseedString = polyseed ?? monero.Wallet_getCacheAttribute(wptr, key: MoneroCacheKeys.seedCacheKey); - var seed = Polyseed.decode(polyseedString, lang, coin); + final seed = Polyseed.decode(polyseedString, lang, coin); if (seedOffset.isNotEmpty) { seed.crypt(seedOffset); } @@ -208,7 +207,8 @@ class MoneroWallet implements CoinWallet { @override Future close() { monero.WalletManager_closeWallet(Monero.wmPtr, wptr, true); - Monero.wPtrList.removeWhere((element) => element.address == wptr.address); + Monero.wPtrList + .removeWhere((final element) => element.address == wptr.address); return Future.value(); } @@ -220,7 +220,7 @@ class MoneroWallet implements CoinWallet { ); @override - Future> seedDetails(AppLocalizations L) async { + Future> seedDetails(final AppLocalizations L) async { final secrets = await secureStorage.readAll(); return [ WalletSeedDetail( @@ -290,7 +290,7 @@ class MoneroWallet implements CoinWallet { if (CupcakeConfig.instance.debug) ...List.generate( secrets.keys.length, - (index) { + (final index) { final key = secrets.keys.elementAt(index); return WalletSeedDetail( type: WalletSeedDetailType.text, @@ -301,7 +301,7 @@ class MoneroWallet implements CoinWallet { if (CupcakeConfig.instance.debug) ...List.generate( CupcakeConfig.instance.toJson().keys.length, - (index) { + (final index) { final key = CupcakeConfig.instance.toJson().keys.elementAt(index); return WalletSeedDetail( type: WalletSeedDetailType.text, diff --git a/lib/coins/monero/wallet_info.dart b/lib/coins/monero/wallet_info.dart index a50fa44..f2f0eb8 100644 --- a/lib/coins/monero/wallet_info.dart +++ b/lib/coins/monero/wallet_info.dart @@ -22,7 +22,7 @@ class MoneroWalletInfo extends CoinWalletInfo { Coin get coin => Monero(); @override - Future checkWalletPassword(String password) async { + Future checkWalletPassword(final String password) async { return monero.WalletManager_verifyWalletPassword( Monero.wmPtr, keysFileName: "$walletName.keys", @@ -41,13 +41,13 @@ class MoneroWalletInfo extends CoinWalletInfo { Coins get type => coin.type; @override - void openUI(BuildContext context) { + void openUI(final BuildContext context) { OpenWallet(coinWalletInfo: this).push(context); } @override - Future openWallet(BuildContext context, - {required String password}) async { + Future openWallet(final BuildContext context, + {required final String password}) async { return await coin.openWallet( this, password: password, @@ -56,7 +56,7 @@ class MoneroWalletInfo extends CoinWalletInfo { @override Future deleteWallet() async { - for (var element in Monero.wPtrList) { + for (final element in Monero.wPtrList) { monero.WalletManager_closeWallet(Monero.wmPtr, element, true); } Monero.wPtrList.clear(); @@ -65,11 +65,11 @@ class MoneroWalletInfo extends CoinWalletInfo { } @override - Future renameWallet(String newName) async { + Future renameWallet(final String newName) async { if (p.basename(walletName) == newName) { throw Exception("Wallet wasn't renamed"); } - for (var element in Monero.wPtrList) { + for (final element in Monero.wPtrList) { monero.WalletManager_closeWallet(Monero.wmPtr, element, true); } Monero.wPtrList.clear(); diff --git a/lib/dev/generate_rebuild.dart b/lib/dev/generate_rebuild.dart new file mode 100644 index 0000000..4a2b3c1 --- /dev/null +++ b/lib/dev/generate_rebuild.dart @@ -0,0 +1,23 @@ +library; + +class GenerateRebuild { + const GenerateRebuild(); +} + +class RebuildOnChange { + static const name = 'RebuildOnChange'; + const RebuildOnChange(); +} + +class ThrowOnUI { + static const name = 'ThrowOnUI'; + final String? L; + final String? message; + const ThrowOnUI({this.message, this.L}); +} + +class ExposeRebuildableAccessors { + static const name = 'ExposeRebuildableAccessors'; + final String? extraCode; + const ExposeRebuildableAccessors({this.extraCode}); +} diff --git a/lib/dev/rebuild_generator.dart b/lib/dev/rebuild_generator.dart new file mode 100644 index 0000000..fd829e9 --- /dev/null +++ b/lib/dev/rebuild_generator.dart @@ -0,0 +1,185 @@ +// lib/rebuild_generator.dart +import 'dart:async'; + +import 'package:analyzer/dart/element/element.dart'; +import 'package:build/build.dart'; +// import 'package:cupcake/utils/config.dart'; +import 'package:source_gen/source_gen.dart'; + +import 'package:cupcake/dev/generate_rebuild.dart'; +import 'package:cupcake/utils/capitalize.dart'; + +class RebuildClassGenerator extends GeneratorForAnnotation { + @override + FutureOr generateForAnnotatedElement(final Element element, + final ConstantReader annotation, final BuildStep buildStep) async { + if (element is! ClassElement) { + throw InvalidGenerationSourceError( + 'Generator cannot target `${element.displayName}`. ' + '`@GenerateRebuild` can only be applied to classes.', + element: element, + ); + } + final classElement = element; + final className = classElement.displayName; + + final rebuildMethods = await _rebuildOnChangePart(classElement); + final throwOnUiMethods = await _throwOnUiPart(classElement); + final exposeRebuildableAccessorsMethods = + await _exposeRebuildableAccessorsPart(classElement); + final fileName = buildStep.inputId.uri.pathSegments.last; + + return ''' +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: unused_element + +part of '$fileName'; + +extension ${className}RebuildExtension on $className { + // Rebuild methods +${rebuildMethods.join()} + // Throw on UI methods +${throwOnUiMethods.join()} + // Expose Settings methods +${exposeRebuildableAccessorsMethods.join()} +} +'''; + } + + Future> _rebuildOnChangePart( + final ClassElement classElement) async { + final rebuildMethods = []; + for (final field in classElement.fields) { + final hasRebuildOnChange = field.metadata.any((final meta) { + final constantValue = meta.computeConstantValue(); + return constantValue != null && + constantValue.type?.getDisplayString() == RebuildOnChange.name; + }); + if (!hasRebuildOnChange) continue; + final fieldName = field.displayName; + final fieldType = field.type.getDisplayString(); + // Capitalize the field name for the method name. + final methodSuffix = fieldName[0].toUpperCase() + fieldName.substring(1); + final noPrefix = methodSuffix.substring(1); + rebuildMethods.add(''' + $fieldType get $noPrefix => \$$noPrefix; + set $noPrefix(final $fieldType new$noPrefix) { + \$$noPrefix = new$noPrefix; + markNeedsBuild(); + } +'''); + } + + return rebuildMethods; + } + + Future> _throwOnUiPart(final ClassElement classElement) async { + final throwOnUiMethods = []; + for (final field in classElement.methods) { + final hasThrowOnUI = field.metadata.any((final meta) { + final constantValue = meta.computeConstantValue(); + return constantValue != null && + constantValue.type?.getDisplayString() == ThrowOnUI.name; + }); + if (!hasThrowOnUI) continue; + final throwOnUIMeta = field.metadata.firstWhere((final meta) { + final constantValue = meta.computeConstantValue(); + return constantValue != null && + constantValue.type?.getDisplayString() == ThrowOnUI.name; + }); + final constantValue = throwOnUIMeta.computeConstantValue(); + + final messageField = constantValue?.getField('message')?.toStringValue(); + final translationField = constantValue?.getField('L')?.toStringValue(); + + if (messageField != null && translationField != null) { + throw Exception( + "ThrowOnUI() cannot have message and L at the same time"); + } + String? alertTitle; + if (messageField != null) { + alertTitle = "'''$messageField'''"; + } else if (translationField != null) { + alertTitle = 'L.$translationField'; + } else { + throw Exception("ThrowOnUI() requires either L or message"); + } + + final fieldName = field.displayName; + final fieldType = field.type.getDisplayString().split(" ")[0]; + final fieldArgs = field.type + .getDisplayString() + .split(" ")[1] + .replaceAll("Function", ""); + final methodSuffix = fieldName[0].toUpperCase() + fieldName.substring(1); + final noPrefix = methodSuffix.substring(1); + throwOnUiMethods.add(''' + $fieldType $noPrefix$fieldArgs async { + await callThrowable( + () async => await \$$noPrefix$fieldArgs, + $alertTitle, + ); + markNeedsBuild(); + } +'''); + } + + return throwOnUiMethods; + } + + Future> _exposeRebuildableAccessorsPart( + final ClassElement classElement) async { + final rebuildMethods = []; + // classElement.methods + for (final field in classElement.accessors) { + final hasExposeRebuildableAccessors = field.metadata.any((final meta) { + final constantValue = meta.computeConstantValue(); + return constantValue != null && + constantValue.type?.getDisplayString() == + ExposeRebuildableAccessors.name; + }); + if (!hasExposeRebuildableAccessors) continue; + final exposeRebuildableAccessorsMeta = + field.metadata.firstWhere((final meta) { + final constantValue = meta.computeConstantValue(); + return constantValue != null && + constantValue.type?.getDisplayString() == + ExposeRebuildableAccessors.name; + }); + final fieldName = field.displayName; + // Capitalize the field name for the method name. + final methodSuffix = fieldName[0].toUpperCase() + fieldName.substring(1); + final noPrefix = methodSuffix.substring(1); + final constantValue = + exposeRebuildableAccessorsMeta.computeConstantValue(); + + String? extraCode = constantValue?.getField('extraCode')?.toStringValue(); + + final elements = (field.returnType.element as ClassElement).accessors; + for (final elm in elements) { + if (elm.name.endsWith('=')) { + continue; // something that dart generates I think? + } + if (elm.isStatic) continue; + if (extraCode != null && !extraCode.endsWith(';')) { + extraCode = "$extraCode;"; + } + final capitalizedIfIfeelLikeIt = + noPrefix.isEmpty ? elm.name : elm.name.capitalize(); + rebuildMethods.add(''' + ${elm.returnType} get $noPrefix$capitalizedIfIfeelLikeIt => \$$noPrefix.${elm.name}; + set $noPrefix$capitalizedIfIfeelLikeIt(final ${elm.returnType} new$capitalizedIfIfeelLikeIt) { + \$$noPrefix.${elm.name} = new$capitalizedIfIfeelLikeIt; + ${(extraCode?.isNotEmpty ?? true) ? extraCode : '// no extraCode property'} + markNeedsBuild(); + } + '''); + } + } + return rebuildMethods; + } +} + +Builder rebuildBuilder(final BuilderOptions options) => + LibraryBuilder(RebuildClassGenerator(), generatedExtension: '.g.dart'); diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 9a8171b..a16aaa3 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -63,6 +63,7 @@ "restore_height": "Restore height", "seed_offset": "Seed offset", "view_only_restore_qr": "Restore View-Only Wallet", + "seed_length_16_word": "16 word", "seed_screen_wallet_seed_polyseed": "Wallet seed (Polyseed)", "seed_screen_wallet_seed_polyseed_encrypted": "Wallet seed (Polyseed [Encrypted])", "seed_screen_wallet_seed_legacy": "Wallet seed (Legacy)", diff --git a/lib/main.dart b/lib/main.dart index c08b749..7e8e969 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,9 +1,12 @@ +import 'dart:async'; +import 'dart:ui'; + import 'package:cupcake/l10n/app_localizations.dart'; +import 'package:cupcake/panic_handler.dart'; import 'package:cupcake/themes/base_theme.dart'; import 'package:cupcake/utils/config.dart'; import 'package:cupcake/utils/filesystem.dart'; import 'package:cupcake/utils/secure_storage.dart'; -import 'package:cupcake/view_model/home_screen_view_model.dart'; import 'package:cupcake/views/home_screen.dart'; import 'package:cupcake/views/initial_setup_screen.dart'; import 'package:flutter/material.dart'; @@ -25,6 +28,19 @@ Future appInit() async { } Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + FlutterError.onError = (final FlutterErrorDetails errorDetails) { + catchFatalError(errorDetails.exception, null); + }; + PlatformDispatcher.instance.onError = + (final Object error, final StackTrace stackTrace) { + catchFatalError(error, stackTrace); + return true; + }; + await _main(); +} + +Future _main() async { await appInit(); runApp(const MyApp()); } @@ -34,7 +50,7 @@ class MyApp extends StatelessWidget { // This widget is the root of your application. @override - Widget build(BuildContext context) { + Widget build(final BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, title: 'Cupcake', @@ -50,14 +66,15 @@ class MyApp extends StatelessWidget { Locale('en'), // English Locale('pl'), // Polish ], - builder: (BuildContext context, Widget? child) { - return Stack( - alignment: AlignmentDirectional.bottomStart, - children: [ - child ?? const Text("null"), - ], - ); - }, + // builder: (final BuildContext context, final Widget? child) { + // print("a"); + // return Stack( + // alignment: AlignmentDirectional.bottomStart, + // children: [ + // child ?? const Text("null"), + // ], + // ); + // }, home: CupcakeConfig.instance.initialSetupComplete ? HomeScreen( openLastWallet: true, diff --git a/lib/panic_handler.dart b/lib/panic_handler.dart new file mode 100644 index 0000000..58df402 --- /dev/null +++ b/lib/panic_handler.dart @@ -0,0 +1,100 @@ +import 'package:cupcake/themes/base_theme.dart'; +import 'package:cupcake/views/widgets/cupcake_appbar_title.dart'; +import 'package:flutter/material.dart'; +import 'package:package_info_plus/package_info_plus.dart'; + +void catchFatalError(final Object error, final StackTrace? stackTrace) async { + final PackageInfo packageInfo = await PackageInfo.fromPlatform(); + + runApp(ErrorHandlerApp( + error: error, stackTrace: stackTrace, packageInfo: packageInfo)); +} + +class ErrorHandlerApp extends StatelessWidget { + const ErrorHandlerApp({ + super.key, + required this.error, + required this.stackTrace, + required this.packageInfo, + }); + + final Object error; + final StackTrace? stackTrace; + final PackageInfo packageInfo; + // This widget is the root of your application. + @override + Widget build(final BuildContext context) { + return MaterialApp( + debugShowCheckedModeBanner: false, + title: 'Cupcake Crash', + themeMode: ThemeMode.dark, + darkTheme: BaseTheme.darkBaseTheme, + home: CupcakeFatalError( + error: error, + stackTrace: stackTrace, + packageInfo: packageInfo, + ), + ); + } +} + +class CupcakeFatalError extends StatelessWidget { + const CupcakeFatalError({ + super.key, + required this.error, + required this.stackTrace, + required this.packageInfo, + }); + + final Object error; + final StackTrace? stackTrace; + final PackageInfo packageInfo; + + @override + Widget build(final BuildContext context) { + return Scaffold( + appBar: AppBar( + title: CupcakeAppbarTitle(), + ), + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ..._buildNotice(), + ], + ), + ), + ), + ); + } + + Widget _text(final dynamic element) { + return SelectableText( + element.toString(), + style: TextStyle( + color: Colors.white, + ), + ); + } + + List _buildNotice() { + return [ + _text( + "Critical error occured and app cannot continue. Please take a screenshot of this" + " screen and send it to our support"), + Divider(), + _text(error), + Divider(), + _text("appName: ${packageInfo.appName}"), + _text("buildNumber: ${packageInfo.buildNumber}"), + _text("buildSignature: ${packageInfo.buildSignature}"), + _text("installerStore: ${packageInfo.installerStore}"), + _text("packageName: ${packageInfo.packageName}"), + _text("version: ${packageInfo.version}"), + Divider(), + _text(stackTrace), + ]; + } +} diff --git a/lib/utils/alerts/basic.dart b/lib/utils/alerts/basic.dart index 72bceaa..2ef7f96 100644 --- a/lib/utils/alerts/basic.dart +++ b/lib/utils/alerts/basic.dart @@ -1,22 +1,22 @@ import 'package:flutter/material.dart'; Future showAlert({ - required BuildContext context, - required String title, - required List body, - String ok = "ok", + required final BuildContext context, + required final String title, + required final List body, + final String ok = "ok", }) async { return showDialog( context: context, barrierDismissible: false, // user must tap button! - builder: (BuildContext context) { + builder: (final BuildContext context) { return AlertDialog( title: Text(title), content: SingleChildScrollView( child: ListBody( children: body .map( - (e) => Text( + (final e) => Text( e, style: const TextStyle(color: Colors.white), ), diff --git a/lib/utils/alerts/widget.dart b/lib/utils/alerts/widget.dart index dec1473..ad23529 100644 --- a/lib/utils/alerts/widget.dart +++ b/lib/utils/alerts/widget.dart @@ -1,15 +1,15 @@ import 'package:flutter/material.dart'; Future showAlertWidget({ - required BuildContext context, - required String title, - required List body, - String ok = "ok", + required final BuildContext context, + required final String title, + required final List body, + final String ok = "ok", }) async { return showDialog( context: context, barrierDismissible: false, // user must tap button! - builder: (BuildContext context) { + builder: (final BuildContext context) { return AlertDialog( title: Text(title), content: SingleChildScrollView( diff --git a/lib/utils/alerts/widget_minimal.dart b/lib/utils/alerts/widget_minimal.dart index c2d3506..c28b6bd 100644 --- a/lib/utils/alerts/widget_minimal.dart +++ b/lib/utils/alerts/widget_minimal.dart @@ -1,13 +1,13 @@ import 'package:flutter/material.dart'; Future showAlertWidgetMinimal({ - required BuildContext context, - required List body, + required final BuildContext context, + required final List body, }) async { return showDialog( context: context, barrierDismissible: true, - builder: (BuildContext context) { + builder: (final BuildContext context) { return SizedBox( height: 360, child: Material( diff --git a/lib/utils/call_throwable.dart b/lib/utils/call_throwable.dart index b765214..8b13789 100644 --- a/lib/utils/call_throwable.dart +++ b/lib/utils/call_throwable.dart @@ -1,30 +1 @@ -import 'dart:async'; -import 'package:cupcake/coins/abstract/exception.dart'; -import 'package:cupcake/utils/alerts/basic.dart'; -import 'package:flutter/cupertino.dart'; - -Future callThrowable(BuildContext context, - FutureOr Function() function, String title) async { - try { - await function.call(); - return true; - } on CoinException catch (e) { - print(e); - if (!context.mounted) return false; - await showAlert( - context: context, title: title, body: [e.details ?? "", e.toString()]); - } on TypeError catch (e) { - print(e); - if (!context.mounted) return false; - await showAlert( - context: context, - title: title, - body: [e.toString(), e.stackTrace.toString()]); - } catch (e) { - print(e); - if (!context.mounted) return false; - await showAlert(context: context, title: title, body: [e.toString()]); - } - return false; -} diff --git a/lib/utils/capitalize.dart b/lib/utils/capitalize.dart new file mode 100644 index 0000000..7540f2a --- /dev/null +++ b/lib/utils/capitalize.dart @@ -0,0 +1,5 @@ +extension StringExtension on String { + String capitalize() { + return "${this[0].toUpperCase()}${substring(1)}"; + } +} diff --git a/lib/utils/config.dart b/lib/utils/config.dart index 9541bb8..b5db469 100644 --- a/lib/utils/config.dart +++ b/lib/utils/config.dart @@ -4,6 +4,7 @@ import 'dart:io'; import 'package:cupcake/coins/abstract/wallet_info.dart'; import 'package:cupcake/utils/filesystem.dart'; import 'package:flutter/foundation.dart'; +// import 'package:flutter/foundation.dart'; import 'package:path/path.dart' as p; class CupcakeConfig { @@ -17,6 +18,8 @@ class CupcakeConfig { required this.biometricEnabled, required this.debug, required this.oldSecureStorage, + required this.didFoundInsecureBiometric, + required this.canUseInsecureBiometric, }); CoinWalletInfo? lastWallet; bool initialSetupComplete; @@ -27,8 +30,10 @@ class CupcakeConfig { bool biometricEnabled; bool debug; Map oldSecureStorage; + bool didFoundInsecureBiometric; + bool canUseInsecureBiometric; - factory CupcakeConfig.fromJson(Map json) { + factory CupcakeConfig.fromJson(final Map json) { return CupcakeConfig( lastWallet: CoinWalletInfo.fromJson(json['lastWallet']), initialSetupComplete: json['initialSetupComplete'] ?? false, @@ -39,6 +44,8 @@ class CupcakeConfig { biometricEnabled: json['biometricEnabled'] ?? false, debug: json['debug'] ?? false, oldSecureStorage: json['oldSecureStorage'] ?? {}, + didFoundInsecureBiometric: json['didFoundInsecureBiometric'] ?? false, + canUseInsecureBiometric: json['canUseInsecureBiometric'] ?? false, ); } @@ -53,6 +60,8 @@ class CupcakeConfig { 'biometricEnabled': biometricEnabled, 'oldSecureStorage': oldSecureStorage, 'debug': debug, + 'didFoundInsecureBiometric': didFoundInsecureBiometric, + 'canUseInsecureBiometric': canUseInsecureBiometric, }; } diff --git a/lib/utils/form/abstract_form_element.dart b/lib/utils/form/abstract_form_element.dart index 1435071..f866b6f 100644 --- a/lib/utils/form/abstract_form_element.dart +++ b/lib/utils/form/abstract_form_element.dart @@ -1,5 +1,6 @@ abstract class FormElement { bool get isOk => true; - String get label => throw UnimplementedError(); - Future get value => throw UnimplementedError(); + String get label; + Future get value; + Future errorHandler(final Object e); } diff --git a/lib/utils/form/abstract_value_outcome.dart b/lib/utils/form/abstract_value_outcome.dart index 2be6570..2c90e7b 100644 --- a/lib/utils/form/abstract_value_outcome.dart +++ b/lib/utils/form/abstract_value_outcome.dart @@ -1,6 +1,6 @@ abstract class ValueOutcome { - Future encode(String input); - Future decode(String output); + Future encode(final String input); + Future decode(final String output); String get uniqueId => throw UnimplementedError(); } diff --git a/lib/utils/form/default_validator.dart b/lib/utils/form/default_validator.dart deleted file mode 100644 index ce45073..0000000 --- a/lib/utils/form/default_validator.dart +++ /dev/null @@ -1,3 +0,0 @@ -String? defaultFormValidator(String? input) { - return null; -} diff --git a/lib/utils/form/flutter_secure_storage_value_outcome.dart b/lib/utils/form/flutter_secure_storage_value_outcome.dart index d8d21d2..6eb5cc8 100644 --- a/lib/utils/form/flutter_secure_storage_value_outcome.dart +++ b/lib/utils/form/flutter_secure_storage_value_outcome.dart @@ -15,9 +15,9 @@ class FlutterSecureStorageValueOutcome implements ValueOutcome { final bool verifyMatching; @override - Future encode(String input) async { - List bytes = utf8.encode(input); - Digest sha512Hash = sha512.convert(bytes); + Future encode(final String input) async { + final List bytes = utf8.encode(input); + final Digest sha512Hash = sha512.convert(bytes); var valInput = await secureStorage.read(key: "FlutterSecureStorageValueOutcome._$key"); if (valInput == null) { @@ -43,18 +43,18 @@ class FlutterSecureStorageValueOutcome implements ValueOutcome { } return; } - var random = Random.secure(); - var values = List.generate(64, (i) => random.nextInt(256)); + final random = Random.secure(); + final values = List.generate(64, (final i) => random.nextInt(256)); final pass = base64Url.encode(values); await secureStorage.write(key: key, value: pass); return; } @override - Future decode(String output) async { - List bytes = utf8.encode(output); - Digest sha512Hash = sha512.convert(bytes); - var valInput = + Future decode(final String output) async { + final List bytes = utf8.encode(output); + final Digest sha512Hash = sha512.convert(bytes); + final valInput = await secureStorage.read(key: "FlutterSecureStorageValueOutcome._$key"); if (sha512Hash.toString() != valInput && verifyMatching) { throw Exception("Input doesn't match the secure element value"); diff --git a/lib/utils/form/pin_form_element.dart b/lib/utils/form/pin_form_element.dart index 1e660eb..2f81957 100644 --- a/lib/utils/form/pin_form_element.dart +++ b/lib/utils/form/pin_form_element.dart @@ -2,23 +2,24 @@ import 'package:cupcake/utils/config.dart'; import 'package:cupcake/utils/form/abstract_form_element.dart'; import 'package:cupcake/utils/form/abstract_value_outcome.dart'; import 'package:cupcake/utils/secure_storage.dart'; -import 'package:cupcake/utils/form/default_validator.dart'; import 'package:flutter/cupertino.dart'; import 'package:local_auth/local_auth.dart'; class PinFormElement extends FormElement { - PinFormElement({ - String initialText = "", - this.password = false, - this.validator = defaultFormValidator, - required this.valueOutcome, - this.onChanged, - this.onConfirm, - this.showNumboard = true, - required this.label, - }) : ctrl = TextEditingController(text: initialText); - - Future loadSecureStorageValue(VoidCallback callback) async { + PinFormElement( + {final String initialText = "", + this.password = false, + required this.validator, + required this.valueOutcome, + this.onChanged, + this.onConfirm, + this.showNumboard = true, + required this.label, + required final Future Function(Object e) errorHandler}) + : ctrl = TextEditingController(text: initialText), + _errorHandler = errorHandler; + final Future Function(Object e) _errorHandler; + Future loadSecureStorageValue(final VoidCallback callback) async { if (ctrl.text.isNotEmpty) return; if (!CupcakeConfig.instance.biometricEnabled) return; final auth = LocalAuthentication(); @@ -62,13 +63,18 @@ class PinFormElement extends FormElement { @override bool get isOk => validator(ctrl.text) == null; - Future Function(BuildContext context)? onChanged; - Future Function(BuildContext context)? onConfirm; - Future onConfirmInternal(BuildContext context) async { + Future Function()? onChanged; + Future Function()? onConfirm; + Future onConfirmInternal(final BuildContext context) async { await valueOutcome.encode(ctrl.text); isConfirmed = true; } bool isConfirmed = false; String? Function(String? input) validator; + + @override + Future errorHandler(final Object e) async { + await _errorHandler(e); + } } diff --git a/lib/utils/form/plain_value_outcome.dart b/lib/utils/form/plain_value_outcome.dart index 4c24c63..424afb8 100644 --- a/lib/utils/form/plain_value_outcome.dart +++ b/lib/utils/form/plain_value_outcome.dart @@ -2,10 +2,10 @@ import 'package:cupcake/utils/form/abstract_value_outcome.dart'; class PlainValueOutcome implements ValueOutcome { @override - Future decode(String output) => Future.value(output); + Future decode(final String output) => Future.value(output); @override - Future encode(String input) => Future.value(); + Future encode(final String input) => Future.value(); @override String get uniqueId => "undefined"; diff --git a/lib/utils/form/single_choice_form_element.dart b/lib/utils/form/single_choice_form_element.dart index e254572..c6acd8e 100644 --- a/lib/utils/form/single_choice_form_element.dart +++ b/lib/utils/form/single_choice_form_element.dart @@ -1,7 +1,11 @@ import 'package:cupcake/utils/form/abstract_form_element.dart'; class SingleChoiceFormElement extends FormElement { - SingleChoiceFormElement({required this.title, required this.elements}); + SingleChoiceFormElement( + {required this.title, + required this.elements, + required final Future Function(Object e) errorHandler}) + : _errorHandler = errorHandler; String title; List elements; @@ -13,4 +17,11 @@ class SingleChoiceFormElement extends FormElement { @override bool get isOk => true; + + final Future Function(Object e) _errorHandler; + @override + Future errorHandler(final Object e) => _errorHandler(e); + + @override + String get label => valueSync; } diff --git a/lib/utils/form/string_form_element.dart b/lib/utils/form/string_form_element.dart index d75265e..054fa08 100644 --- a/lib/utils/form/string_form_element.dart +++ b/lib/utils/form/string_form_element.dart @@ -1,17 +1,17 @@ import 'package:cupcake/utils/form/abstract_form_element.dart'; -import 'package:cupcake/utils/form/default_validator.dart'; import 'package:flutter/cupertino.dart'; class StringFormElement extends FormElement { - StringFormElement( - this.label, { - String initialText = "", - this.password = false, - this.validator = defaultFormValidator, - this.isExtra = false, - this.showIf, - this.randomNameGenerator = false, - }) : ctrl = TextEditingController(text: initialText); + StringFormElement(this.label, + {final String initialText = "", + this.password = false, + required this.validator, + this.isExtra = false, + this.showIf, + this.randomNameGenerator = false, + required final Future Function(Object e) errorHandler}) + : ctrl = TextEditingController(text: initialText), + _errorHandler = errorHandler; bool Function()? showIf; TextEditingController ctrl; @@ -28,4 +28,8 @@ class StringFormElement extends FormElement { bool get isOk => validator(ctrl.text) == null; String? Function(String? input) validator; + + final Future Function(Object e) _errorHandler; + @override + Future errorHandler(final Object e) => _errorHandler(e); } diff --git a/lib/utils/form/validators.dart b/lib/utils/form/validators.dart new file mode 100644 index 0000000..23713b0 --- /dev/null +++ b/lib/utils/form/validators.dart @@ -0,0 +1,14 @@ +import 'package:cupcake/l10n/app_localizations.dart'; + +String? Function(String? input) doNothingValidator(final AppLocalizations L) { + return (final _) => null; +} + +String? Function(String? input) nonEmptyValidator(final AppLocalizations L, + {final String? Function(String input)? extra}) { + return (final String? input) { + if (input == null) return L.warning_input_cannot_be_null; + if (input == "") return L.warning_input_cannot_be_empty; + return extra?.call(input); + }; +} diff --git a/lib/utils/random_name.dart b/lib/utils/random_name.dart index 2047527..4a1e808 100644 --- a/lib/utils/random_name.dart +++ b/lib/utils/random_name.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; final random = Random.secure(); -void randomName(TextEditingController ctrl) { +void randomName(final TextEditingController ctrl) { ctrl.text = "${adjectives[random.nextInt(adjectives.length)]} ${nouns[random.nextInt(nouns.length)]}"; } diff --git a/lib/utils/secure_storage.dart b/lib/utils/secure_storage.dart index b5fc4fb..3cf6d14 100644 --- a/lib/utils/secure_storage.dart +++ b/lib/utils/secure_storage.dart @@ -8,13 +8,13 @@ AndroidOptions _getAndroidOptions() => const AndroidOptions( final FlutterSecureStorage secureStorage = FlutterSecureStorage(aOptions: _getAndroidOptions()); -Future setWalletPin(String password) async { +Future setWalletPin(final String password) async { final pin = await secureStorage.read(key: SecureStorageKey.pin); if (pin != null) throw Exception("${SecureStorageKey.pin} is not null"); await secureStorage.write(key: SecureStorageKey.pin, value: password); } -Future verifyWalletPin(String password) async { +Future verifyWalletPin(final String password) async { final pin = await secureStorage.read(key: SecureStorageKey.pin); if (pin == null) throw Exception("${SecureStorageKey.pin} is null"); if (pin == password) return true; diff --git a/lib/utils/urqr.dart b/lib/utils/urqr.dart index da5d264..953410e 100644 --- a/lib/utils/urqr.dart +++ b/lib/utils/urqr.dart @@ -23,9 +23,9 @@ class URQRData { }; } - static URQRData parse(List urqr_) { + static URQRData parse(final List urqr_) { final urqr = urqr_.toSet().toList(); - urqr.sort((s1, s2) { + urqr.sort((final s1, final s2) { final s1s = s1.split("/"); final s1frameStr = s1s[1].split("-"); final s1curFrame = int.parse(s1frameStr[0]); @@ -38,7 +38,7 @@ class URQRData { String tag = ''; int count = 0; String bw = ''; - for (var elm in urqr) { + for (final elm in urqr) { final s = elm.substring(elm.indexOf(":") + 1); // strip down ur: prefix final s2 = s.split("/"); tag = s2[0]; diff --git a/lib/view_model/abstract.dart b/lib/view_model/abstract.dart index 1f583ab..9426727 100644 --- a/lib/view_model/abstract.dart +++ b/lib/view_model/abstract.dart @@ -1,7 +1,12 @@ +import 'dart:async'; + +import 'package:cupcake/coins/abstract/exception.dart'; import 'package:cupcake/l10n/app_localizations.dart'; -import 'package:flutter/cupertino.dart'; +import 'package:cupcake/utils/alerts/basic.dart'; +import 'package:flutter/material.dart'; class ViewModel { + bool canPop = true; String get screenName => "screenName"; AppLocalizations get L { @@ -17,19 +22,58 @@ class ViewModel { return _lcache!; } - AppLocalizations? _lcache; + BuildContext? _c; - BuildContext? c; - void register(BuildContext context) { - c = context; + void register(final BuildContext context) { + _c = context; } - bool get mounted => c?.mounted??false; + AppLocalizations? _lcache; + + GlobalKey scaffoldKey = GlobalKey(); + + BuildContext? get c => _c ?? scaffoldKey.currentContext; + bool get mounted { + if (c == null) print("c is null"); + return c?.mounted ?? false; + } - markNeedsBuild() { + void markNeedsBuild() { if (c == null) { - throw Exception("c is null, did you forget to register(context)?"); + // throw Exception("c is null, did you forget to register(context)?"); + print("aaa"); + return; } (c as Element).markNeedsBuild(); } + + Future errorHandler(final Object e) => + callThrowable(() => throw e, L.create_wallet); + + Future callThrowable( + final FutureOr Function() function, final String title) async { + if (c == null) return false; + if (!mounted) return false; + try { + await function.call(); + return true; + } on CoinException catch (e) { + print(e); + await showAlert( + context: c!, + title: title, + body: [e.details ?? "", e.toString()], + ); + } on TypeError catch (e) { + print(e); + await showAlert( + context: c!, + title: title, + body: [e.toString(), e.stackTrace.toString()]); + } catch (e) { + print(e); + await showAlert(context: c!, title: title, body: [e.toString()]); + } + return false; + } } diff --git a/lib/view_model/barcode_scanner_view_model.dart b/lib/view_model/barcode_scanner_view_model.dart index 17d5562..f859f70 100644 --- a/lib/view_model/barcode_scanner_view_model.dart +++ b/lib/view_model/barcode_scanner_view_model.dart @@ -1,20 +1,29 @@ import 'package:cupcake/coins/abstract/wallet.dart'; -import 'package:cupcake/utils/call_throwable.dart'; +import 'package:cupcake/dev/generate_rebuild.dart'; import 'package:cupcake/utils/urqr.dart'; import 'package:cupcake/view_model/abstract.dart'; import 'package:cupcake/views/widgets/barcode_scanner/urqr_progress.dart'; import 'package:fast_scanner/fast_scanner.dart'; import 'package:flutter/cupertino.dart'; +part 'barcode_scanner_view_model.g.dart'; + +@GenerateRebuild() class BarcodeScannerViewModel extends ViewModel { BarcodeScannerViewModel({required this.wallet}); @override String get screenName => L.scan; - Barcode? barcode; - bool popped = false; - List urCodes = []; - late var ur = URQRData.parse(urCodes); + @RebuildOnChange() + Barcode? $barcode; + + @RebuildOnChange() + bool $popped = false; + + @RebuildOnChange() + List $urCodes = []; + + get ur => URQRData.parse(urCodes); final CoinWallet wallet; @@ -27,30 +36,24 @@ class BarcodeScannerViewModel extends ViewModel { percentage: ur.progress, ); - Future handleUR(BuildContext context) async { - callThrowable( - context, - () => wallet.handleUR(context, ur), - "Error handling URQR scan", - ); + @ThrowOnUI(message: "Error handling URQR scan") + Future $handleUR() async { + await wallet.handleUR(c!, ur); } - void handleBarcode(BuildContext context, BarcodeCapture barcodes) async { + void handleBarcode( + final BuildContext context, final BarcodeCapture barcodes) async { for (final barcode in barcodes.barcodes) { if (barcode.rawValue!.startsWith("ur:")) { print("handleUR: ${ur.progress} : $popped"); if (ur.progress == 1 && !popped) { print("handleUR called"); popped = true; - await handleUR(context); - markNeedsBuild(); + await handleUR(); return; } if (urCodes.contains(barcode.rawValue)) return; urCodes.add(barcode.rawValue!); - ur = URQRData.parse(urCodes); - - markNeedsBuild(); } } if (urCodes.isNotEmpty) return; @@ -58,14 +61,13 @@ class BarcodeScannerViewModel extends ViewModel { barcode = barcodes.barcodes.firstOrNull; if (barcode != null && popped != true) { popped = true; - await handleUR(context); + await handleUR(); } - markNeedsBuild(); } List urParts() { - List l = []; - for (var inp in ur.inputs) { + final List l = []; + for (final inp in ur.inputs) { try { l.add(int.parse(inp.split("/")[1].split("-")[0])); } catch (e) { diff --git a/lib/view_model/create_wallet_view_model.dart b/lib/view_model/create_wallet_view_model.dart index 8060faa..6d9e8db 100644 --- a/lib/view_model/create_wallet_view_model.dart +++ b/lib/view_model/create_wallet_view_model.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:cupcake/coins/abstract/coin.dart'; import 'package:cupcake/coins/list.dart'; import 'package:cupcake/coins/types.dart'; +import 'package:cupcake/dev/generate_rebuild.dart'; import 'package:cupcake/utils/config.dart'; import 'package:cupcake/utils/form/abstract_form_element.dart'; import 'package:cupcake/utils/form/flutter_secure_storage_value_outcome.dart'; @@ -10,6 +11,7 @@ import 'package:cupcake/utils/form/pin_form_element.dart'; import 'package:cupcake/utils/form/plain_value_outcome.dart'; import 'package:cupcake/utils/form/single_choice_form_element.dart'; import 'package:cupcake/utils/form/string_form_element.dart'; +import 'package:cupcake/utils/form/validators.dart'; import 'package:cupcake/utils/null_if_empty.dart'; import 'package:cupcake/view_model/abstract.dart'; import 'package:cupcake/view_model/new_wallet_info_view_model.dart'; @@ -20,6 +22,9 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:share_plus/share_plus.dart'; +part 'create_wallet_view_model.g.dart'; + +@GenerateRebuild() class CreateWalletViewModel extends ViewModel { CreateWalletViewModel({ required this.createMethod, @@ -28,20 +33,11 @@ class CreateWalletViewModel extends ViewModel { final CreateMethod createMethod; - bool _isPinSet = false; - bool get isPinSet => _isPinSet; - set isPinSet(bool newIsPinSet) { - _isPinSet = newIsPinSet; - markNeedsBuild(); - } - - bool _showExtra = false; - bool get showExtra => _showExtra; - set showExtra(bool newShowExtra) { - _showExtra = newShowExtra; - markNeedsBuild(); - } + @RebuildOnChange() + bool $isPinSet = false; + @RebuildOnChange() + bool $showExtra = false; @override late String screenName = screenNameOriginal; @@ -54,12 +50,8 @@ class CreateWalletViewModel extends ViewModel { List get coins => walletCoins; - bool _isCreate = false; - bool get isCreate => _isCreate; - set isCreate(bool newIsCreate) { - _isCreate = newIsCreate; - markNeedsBuild(); - } + @RebuildOnChange() + bool $isCreate = false; bool get hasAdvancedOptions { if (currentForm == null) return false; @@ -71,26 +63,19 @@ class CreateWalletViewModel extends ViewModel { return false; } - late Coin? _selectedCoin = () { + @RebuildOnChange() + late Coin? $selectedCoin = () { if (coins.length == 1) { return coins[0]; } return null; }(); - Coin? get selectedCoin => _selectedCoin; - set selectedCoin(Coin? newSelectedCoin) { - _selectedCoin = newSelectedCoin; - markNeedsBuild(); - } late StringFormElement walletName = StringFormElement( L.wallet_name, - validator: (String? input) { - if (input == null) return L.warning_input_cannot_be_null; - if (input == "") return L.warning_input_cannot_be_empty; - return null; - }, + validator: nonEmptyValidator(L), randomNameGenerator: true, + errorHandler: errorHandler, ); late SingleChoiceFormElement walletSeedType = SingleChoiceFormElement( @@ -99,20 +84,19 @@ class CreateWalletViewModel extends ViewModel { L.seed_type_polyseed, L.seed_type_legacy, ], + errorHandler: errorHandler, ); late PinFormElement walletPasswordInitial = PinFormElement( label: L.wallet_password, password: true, valueOutcome: PlainValueOutcome(), - validator: (String? input) { - if (input == null) return L.warning_input_cannot_be_null; - if (input == "") return L.warning_input_cannot_be_empty; - if (input.length < 4) { - return L.warning_password_too_short; - } - return null; - }, + validator: nonEmptyValidator( + L, + extra: (final input) => + (input.length < 4) ? L.warning_password_too_short : null, + ), + errorHandler: errorHandler, ); late PinFormElement walletPassword = PinFormElement( @@ -124,84 +108,72 @@ class CreateWalletViewModel extends ViewModel { canWrite: true, verifyMatching: true, ), - validator: (String? input) { - if (input == null) return L.warning_input_cannot_be_null; - if (input == "") return L.warning_input_cannot_be_empty; - if (input.length < 4) { - return L.warning_password_too_short; - } - if (input != walletPasswordInitial.ctrl.text && needsPasswordConfirm) { - return L.password_doesnt_match; - } - return null; - }, + validator: nonEmptyValidator( + L, + extra: (final String input) { + if (input.length < 4) { + return L.warning_password_too_short; + } + if (input != walletPasswordInitial.ctrl.text && needsPasswordConfirm) { + return L.password_doesnt_match; + } + return null; + }, + ), + errorHandler: errorHandler, ); late StringFormElement seed = StringFormElement( L.wallet_seed, password: false, - validator: (String? input) { - if (input == null) return L.warning_input_cannot_be_null; - if (input == "") return L.warning_input_cannot_be_empty; - if (input.split(" ").length != 16 && input.split(" ").length != 25) { - return L.warning_seed_incorrect_length; - } - return null; - }, + validator: nonEmptyValidator( + L, + extra: (final input) => + (selectedCoin?.isSeedSomewhatLegit(input) ?? false) + ? L.warning_seed_incorrect_length + : null, + ), + errorHandler: errorHandler, ); late StringFormElement walletAddress = StringFormElement( L.primary_address_label, password: true, - validator: (String? input) { - if (input == null) return L.warning_input_cannot_be_null; - if (input == "") return L.warning_input_cannot_be_empty; - return null; - }, + validator: nonEmptyValidator(L), + errorHandler: errorHandler, ); late StringFormElement secretSpendKey = StringFormElement( L.secret_spend_key, password: true, - validator: (String? input) { - if (input == null) return L.warning_input_cannot_be_null; - if (input == "") return L.warning_input_cannot_be_empty; - return null; - }, + validator: nonEmptyValidator(L), + errorHandler: errorHandler, ); late StringFormElement secretViewKey = StringFormElement( L.secret_view_key, password: true, - validator: (String? input) { - if (input == null) return L.warning_input_cannot_be_null; - if (input == "") return L.warning_input_cannot_be_empty; - return null; - }, + validator: nonEmptyValidator(L), + errorHandler: errorHandler, ); late StringFormElement restoreHeight = StringFormElement( L.restore_height, password: true, - validator: (String? input) { - if (input == null) return L.warning_input_cannot_be_null; - if (input == "") return L.warning_input_cannot_be_empty; - return null; - }, + validator: nonEmptyValidator(L), + errorHandler: errorHandler, ); late StringFormElement seedOffset = StringFormElement( L.seed_offset, password: true, isExtra: true, - validator: (String? input) { - if (input == null) return L.warning_input_cannot_be_null; - if (input == "") return L.warning_input_cannot_be_empty; - return null; - }, + validator: nonEmptyValidator(L), + errorHandler: errorHandler, ); - late List? currentForm = () { + @RebuildOnChange() + late List? $currentForm = () { if (createMethods.length == 1) { return createMethods[createMethods.keys.first]; } @@ -218,7 +190,7 @@ class CreateWalletViewModel extends ViewModel { }, }; - bool needsPasswordConfirm; + final bool needsPasswordConfirm; late final List _createForm = [ if (needsPasswordConfirm) walletPasswordInitial, @@ -245,7 +217,8 @@ class CreateWalletViewModel extends ViewModel { secretViewKey, ]; - Future createWallet(BuildContext context) async { + @ThrowOnUI(L: 'create_wallet') + Future $createWallet() async { if (selectedCoin == null) throw Exception("selectedCoin is null"); if ((await walletName.value).isEmpty) { throw Exception(L.warning_input_cannot_be_empty); @@ -281,8 +254,7 @@ class CreateWalletViewModel extends ViewModel { ], texts: [ Text( - L.important_seed_backup_info( - "16 word"), // TODO: translate it better? + L.important_seed_backup_info(L.seed_length_16_word), textAlign: TextAlign.center, ), ], @@ -294,7 +266,7 @@ class CreateWalletViewModel extends ViewModel { : () { CupcakeConfig.instance.initialSetupComplete = true; CupcakeConfig.instance.save(); - WalletHome(coinWallet: cw).push(context); + WalletHome(coinWallet: cw).push(c!); }, topActionText: Text(L.next), lottieAnimation: Assets.shield.lottie(), @@ -341,7 +313,7 @@ class CreateWalletViewModel extends ViewModel { topAction: () { CupcakeConfig.instance.initialSetupComplete = true; CupcakeConfig.instance.save(); - WalletHome(coinWallet: cw).push(context); + WalletHome(coinWallet: cw).push(c!); }, topActionText: Text(L.next), lottieAnimation: Assets.shield.lottie(), @@ -379,25 +351,25 @@ class CreateWalletViewModel extends ViewModel { textAlign: TextAlign.center, ), Text( - "${seedOffset.value}\n\n\n\n${L.write_down_notice}", + "${seedOffset.ctrl.text}\n\n\n\n${L.write_down_notice}", textAlign: TextAlign.center, ), ], ), ]; - if (!context.mounted) { + if (!mounted) { throw Exception("context is not mounted, unable to show next screen"); } if (currentForm != _createForm) { - WalletHome(coinWallet: cw).push(context); + await WalletHome(coinWallet: cw).push(c!); } else { - NewWalletInfoScreen( + await NewWalletInfoScreen( pages: pages, - ).push(context); + ).push(c!); } } - void titleUpdate(String? suggestedTitle) async { + void titleUpdate(final String? suggestedTitle) async { await Future.delayed(Duration.zero); // don't do it on build(); screenName = suggestedTitle ?? screenNameOriginal; markNeedsBuild(); diff --git a/lib/view_model/home_screen_view_model.dart b/lib/view_model/home_screen_view_model.dart index dd6045b..cf9f390 100644 --- a/lib/view_model/home_screen_view_model.dart +++ b/lib/view_model/home_screen_view_model.dart @@ -1,38 +1,59 @@ import 'package:cupcake/coins/abstract/wallet_info.dart'; import 'package:cupcake/coins/list.dart'; +import 'package:cupcake/coins/types.dart'; import 'package:cupcake/utils/config.dart'; import 'package:cupcake/view_model/abstract.dart'; +import 'package:cupcake/views/create_wallet.dart'; +import 'package:cupcake/views/wallet_edit.dart'; +import 'package:flutter/material.dart'; class HomeScreenViewModel extends ViewModel { HomeScreenViewModel({required this.openLastWallet, this.lastOpenedWallet}); @override String get screenName => L.select_wallet; - bool openLastWallet; + final bool openLastWallet; - String? lastOpenedWallet; + final String? lastOpenedWallet; - Future loadInitialState() async { + Future renameWallet(final CoinWalletInfo walletInfo) async { + canPop = false; // don't allow user to go back to previous wallet + markNeedsBuild(); + if (!mounted) return; + await WalletEdit(walletInfo: walletInfo).push(c!); + markNeedsBuild(); + } + + Future createWallet(final CreateMethod method) async { + if (!mounted) return; + await CreateWallet( + createMethod: method, + needsPasswordConfirm: false, + ).push(c!); + markNeedsBuild(); + } + + Future loadInitialState(final BuildContext context) async { await Future.delayed(Duration.zero); // load the screen if (CupcakeConfig.instance.lastWallet == null) return; - if (mounted) return; + if (!context.mounted) return; if (!openLastWallet) return; if (CupcakeConfig.instance.lastWallet?.exists() != true) return; - CupcakeConfig.instance.lastWallet!.openUI(c!); + CupcakeConfig.instance.lastWallet!.openUI(context); } Future> get wallets async { - List wallets = []; - for (var coin in walletCoins) { + final List wallets = []; + for (final coin in walletCoins) { final toAdd = await coin.coinWallets; if (toAdd.isNotEmpty) { wallets.addAll(toAdd); } } if (CupcakeConfig.instance.walletSort == 0) { - wallets.sort((a, b) => b.walletName.compareTo(a.walletName)); + wallets.sort((final a, final b) => b.walletName.compareTo(a.walletName)); } else if (CupcakeConfig.instance.walletSort == 1) { - wallets.sort((a, b) => a.walletName.compareTo(b.walletName)); + wallets.sort((final a, final b) => a.walletName.compareTo(b.walletName)); } return wallets; } diff --git a/lib/view_model/new_wallet_info_view_model.dart b/lib/view_model/new_wallet_info_view_model.dart index 4488778..4f0b609 100644 --- a/lib/view_model/new_wallet_info_view_model.dart +++ b/lib/view_model/new_wallet_info_view_model.dart @@ -1,7 +1,10 @@ +import 'package:cupcake/dev/generate_rebuild.dart'; import 'package:cupcake/view_model/abstract.dart'; import 'package:flutter/cupertino.dart'; import 'package:lottie/lottie.dart'; +part 'new_wallet_info_view_model.g.dart'; + enum NewWalletActionType { nextPage, function, @@ -40,19 +43,17 @@ class NewWalletInfoPage { List texts; } +@GenerateRebuild() class NewWalletInfoViewModel extends ViewModel { NewWalletInfoViewModel(this.pages); @override String get screenName => page.topText; - NewWalletInfoPage get page => pages[currentPageIndex]; + NewWalletInfoPage get page => pages[currentPageIndex % pages.length]; - List pages; - int currentPageIndex = 0; + final List pages; - void nextPage() { - currentPageIndex++; - markNeedsBuild(); - } + @RebuildOnChange() + int $currentPageIndex = 0; } diff --git a/lib/view_model/open_wallet_view_model.dart b/lib/view_model/open_wallet_view_model.dart index c92e0e4..b5a1270 100644 --- a/lib/view_model/open_wallet_view_model.dart +++ b/lib/view_model/open_wallet_view_model.dart @@ -1,15 +1,18 @@ import 'package:cupcake/coins/abstract/wallet_info.dart'; -import 'package:cupcake/utils/call_throwable.dart'; +import 'package:cupcake/dev/generate_rebuild.dart'; import 'package:cupcake/utils/form/flutter_secure_storage_value_outcome.dart'; import 'package:cupcake/utils/form/pin_form_element.dart'; +import 'package:cupcake/utils/form/validators.dart'; import 'package:cupcake/view_model/abstract.dart'; import 'package:cupcake/views/wallet_home.dart'; -import 'package:flutter/cupertino.dart'; +part 'open_wallet_view_model.g.dart'; + +@GenerateRebuild() class OpenWalletViewModel extends ViewModel { OpenWalletViewModel({required this.coinWalletInfo}); - CoinWalletInfo coinWalletInfo; + final CoinWalletInfo coinWalletInfo; @override String get screenName => L.enter_password; @@ -22,32 +25,23 @@ class OpenWalletViewModel extends ViewModel { canWrite: false, verifyMatching: true, ), - validator: (String? input) { - if (input == null) return L.warning_input_cannot_be_null; - if (input == "") return L.warning_input_cannot_be_empty; - if (input.length < 4) { - return L.warning_password_too_short; - } - return null; - }, + validator: nonEmptyValidator( + L, + extra: (final input) => + (input.length < 4) ? L.warning_password_too_short : null, + ), onChanged: openWalletIfPasswordCorrect, onConfirm: openWallet, + errorHandler: errorHandler, ); - Future openWallet(BuildContext context) async { - callThrowable( - context, - () async => await _openWallet(context), - L.opening_wallet, - ); - } - - Future _openWallet(BuildContext context) async { + @ThrowOnUI(message: "Opening wallet") + Future $openWallet() async { final wallet = await coinWalletInfo.openWallet( - context, + c!, password: await walletPassword.value, ); - WalletHome(coinWallet: wallet).push(context); + await WalletHome(coinWallet: wallet).push(c!); } Future checkWalletPassword() async { @@ -58,12 +52,12 @@ class OpenWalletViewModel extends ViewModel { } } - Future openWalletIfPasswordCorrect(BuildContext context) async { + Future openWalletIfPasswordCorrect() async { if (await checkWalletPassword()) { - if (!context.mounted) return; - openWallet(context); + if (!mounted) return; + return openWallet(); } } - void titleUpdate(String? suggestedTitle) {} + void titleUpdate(final String? suggestedTitle) {} } diff --git a/lib/view_model/receive_view_model.dart b/lib/view_model/receive_view_model.dart index 6d7edd6..3cf20cc 100644 --- a/lib/view_model/receive_view_model.dart +++ b/lib/view_model/receive_view_model.dart @@ -3,7 +3,7 @@ import 'package:cupcake/view_model/abstract.dart'; class ReceiveViewModel extends ViewModel { ReceiveViewModel(this.wallet); - CoinWallet wallet; + final CoinWallet wallet; @override String get screenName => L.receive; diff --git a/lib/view_model/security_backup_view_model.dart b/lib/view_model/security_backup_view_model.dart index 506d6b9..ace84e2 100644 --- a/lib/view_model/security_backup_view_model.dart +++ b/lib/view_model/security_backup_view_model.dart @@ -1,49 +1,51 @@ import 'package:cupcake/coins/abstract/wallet.dart'; +import 'package:cupcake/dev/generate_rebuild.dart'; import 'package:cupcake/utils/form/abstract_form_element.dart'; import 'package:cupcake/utils/form/flutter_secure_storage_value_outcome.dart'; import 'package:cupcake/utils/form/pin_form_element.dart'; +import 'package:cupcake/utils/form/validators.dart'; import 'package:cupcake/view_model/abstract.dart'; -import 'package:flutter/cupertino.dart'; +part 'security_backup_view_model.g.dart'; + +@GenerateRebuild() class SecurityBackupViewModel extends ViewModel { SecurityBackupViewModel({required this.wallet}); @override - // TODO: implement screenName String get screenName => L.security_and_backup; - bool isLocked = true; + @RebuildOnChange() + bool $isLocked = true; late List form = [ PinFormElement( - label: "Wallet password", - password: true, - valueOutcome: FlutterSecureStorageValueOutcome( - "secure.wallet_password", - canWrite: false, - verifyMatching: true, - ), - validator: (String? input) { - if (input == null) return L.warning_input_cannot_be_null; - if (input == "") return L.warning_input_cannot_be_empty; - if (input.length < 4) { - return L.warning_password_too_short; - } - return null; - }, - showNumboard: true, - onConfirm: (BuildContext context) async { - try { - await form.first.value; - } catch (e) { - print(e); - } - isLocked = false; - markNeedsBuild(); - }) + label: "Wallet password", + password: true, + valueOutcome: FlutterSecureStorageValueOutcome( + "secure.wallet_password", + canWrite: false, + verifyMatching: true, + ), + validator: nonEmptyValidator( + L, + extra: (final input) => + (input.length < 4) ? L.warning_password_too_short : null, + ), + showNumboard: true, + onConfirm: () async { + try { + await form.first.value; + } catch (e) { + print(e); + } + isLocked = false; + }, + errorHandler: errorHandler, + ) ]; CoinWallet wallet; - void titleUpdate(String? suggestedTitle) {} + void titleUpdate(final String? suggestedTitle) {} } diff --git a/lib/view_model/settings_view_model.dart b/lib/view_model/settings_view_model.dart index 57b711c..c5e0e44 100644 --- a/lib/view_model/settings_view_model.dart +++ b/lib/view_model/settings_view_model.dart @@ -1,11 +1,17 @@ +import 'package:cupcake/coins/abstract/wallet_info.dart'; +import 'package:cupcake/dev/generate_rebuild.dart'; import 'package:cupcake/utils/config.dart'; import 'package:cupcake/view_model/abstract.dart'; +part 'settings_view_model.g.dart'; + +@GenerateRebuild() class SettingsViewModel extends ViewModel { SettingsViewModel(); @override String get screenName => "Settings"; - CupcakeConfig get appConfig => CupcakeConfig.instance; + @ExposeRebuildableAccessors(extraCode: r'$config.save()') + CupcakeConfig get $config => CupcakeConfig.instance; } diff --git a/lib/view_model/unconfirmed_transaction_view_model.dart b/lib/view_model/unconfirmed_transaction_view_model.dart index a8e5755..4d8cda0 100644 --- a/lib/view_model/unconfirmed_transaction_view_model.dart +++ b/lib/view_model/unconfirmed_transaction_view_model.dart @@ -4,7 +4,6 @@ import 'package:cupcake/coins/abstract/address.dart'; import 'package:cupcake/coins/abstract/wallet.dart'; import 'package:cupcake/coins/abstract/amount.dart'; import 'package:cupcake/view_model/abstract.dart'; -import 'package:flutter/cupertino.dart'; class UnconfirmedTransactionViewModel extends ViewModel { UnconfirmedTransactionViewModel( @@ -19,8 +18,8 @@ class UnconfirmedTransactionViewModel extends ViewModel { @override late String screenName = wallet.coin.strings.nameFull; - final FutureOr Function(BuildContext context) confirmCallback; - final FutureOr Function(BuildContext context) cancelCallback; + final FutureOr Function() confirmCallback; + final FutureOr Function() cancelCallback; final Amount fee; final Map destMap; diff --git a/lib/view_model/urqr_view_model.dart b/lib/view_model/urqr_view_model.dart index 89ac66b..58f8574 100644 --- a/lib/view_model/urqr_view_model.dart +++ b/lib/view_model/urqr_view_model.dart @@ -8,19 +8,20 @@ class URQRViewModel extends ViewModel { @override String get screenName => "URQR"; - Map> urqrList; + final Map> urqrList; + // @RebuildOnChange() - not using here due to custom ..removeWhere late List _urqr = urqrList[urqrList.keys.first]!; - List get urqr => _urqr..removeWhere((elm) => elm.isEmpty); - set urqr(List newUrqr) { + List get urqr => _urqr..removeWhere((final elm) => elm.isEmpty); + set urqr(final List newUrqr) { _urqr = newUrqr; markNeedsBuild(); } List get alternativeCodes { -final Map> copiedList = {}; + final Map> copiedList = {}; copiedList.addAll(urqrList); - copiedList.removeWhere((key, value) => + copiedList.removeWhere((final key, final value) => value.join("\n").trim() == urqr.join("\n").trim()); final keys = copiedList.keys; return keys.toList(); diff --git a/lib/view_model/wallet_edit_view_model.dart b/lib/view_model/wallet_edit_view_model.dart index 6a68a2b..7b78406 100644 --- a/lib/view_model/wallet_edit_view_model.dart +++ b/lib/view_model/wallet_edit_view_model.dart @@ -1,28 +1,30 @@ import 'package:cupcake/coins/abstract/wallet_info.dart'; +import 'package:cupcake/dev/generate_rebuild.dart'; import 'package:cupcake/utils/form/abstract_form_element.dart'; import 'package:cupcake/utils/form/flutter_secure_storage_value_outcome.dart'; import 'package:cupcake/utils/form/pin_form_element.dart'; import 'package:cupcake/utils/form/string_form_element.dart'; +import 'package:cupcake/utils/form/validators.dart'; import 'package:flutter/material.dart'; import 'package:cupcake/view_model/abstract.dart'; import 'package:path/path.dart' as p; +part 'wallet_edit_view_model.g.dart'; + +@GenerateRebuild() class WalletEditViewModel extends ViewModel { WalletEditViewModel({ required this.walletInfo, }); - CoinWalletInfo walletInfo; + final CoinWalletInfo walletInfo; late StringFormElement walletName = StringFormElement( L.wallet_name, initialText: p.basename(walletInfo.walletName), - validator: (input) { - if (input == null) return L.warning_input_cannot_be_null; - if (input == "") return L.warning_input_cannot_be_empty; - return null; - }, + validator: nonEmptyValidator(L), randomNameGenerator: true, + errorHandler: errorHandler, ); late PinFormElement walletPassword = PinFormElement( @@ -33,15 +35,13 @@ class WalletEditViewModel extends ViewModel { canWrite: false, verifyMatching: true, ), - validator: (String? input) { - if (input == null) return L.warning_input_cannot_be_null; - if (input == "") return L.warning_input_cannot_be_empty; - if (input.length < 4) { - return L.warning_password_too_short; - } - return null; - }, + validator: nonEmptyValidator( + L, + extra: (final input) => + (input.length < 4) ? L.warning_password_too_short : null, + ), showNumboard: false, + errorHandler: errorHandler, ); late final List form = [ @@ -52,16 +52,18 @@ class WalletEditViewModel extends ViewModel { @override String get screenName => "Edit wallet"; - Future deleteWallet(BuildContext context) async { + @ThrowOnUI(message: "Delete wallet") + Future $deleteWallet() async { if (!(await walletInfo.checkWalletPassword(await walletPassword.value))) { throw Exception("Invalid wallet password"); } - walletInfo.deleteWallet(); - if (!context.mounted) return; - Navigator.of(context).pop(); + await walletInfo.deleteWallet(); + if (!mounted) return; + Navigator.of(c!).pop(); } - Future renameWallet(BuildContext context) async { + @ThrowOnUI(message: "Rename wallet") + Future $renameWallet() async { if (!(await walletInfo.checkWalletPassword(await walletPassword.value))) { throw Exception("Invalid wallet password"); } @@ -69,10 +71,8 @@ class WalletEditViewModel extends ViewModel { throw Exception("Wallet name is empty"); } await walletInfo.renameWallet(await walletName.value); - if (!context.mounted) return; - await markNeedsBuild(); - Navigator.of(context).pop(); + if (!mounted) return; + markNeedsBuild(); + Navigator.of(c!).pop(); } - - void titleUpdate(String? suggestedTitle) {} } diff --git a/lib/view_model/wallet_home_view_model.dart b/lib/view_model/wallet_home_view_model.dart index 447a4e7..588164c 100644 --- a/lib/view_model/wallet_home_view_model.dart +++ b/lib/view_model/wallet_home_view_model.dart @@ -2,7 +2,6 @@ import 'package:cupcake/coins/abstract/coin.dart'; import 'package:cupcake/coins/abstract/wallet.dart'; import 'package:cupcake/view_model/abstract.dart'; import 'package:cupcake/views/barcode_scanner.dart'; -import 'package:flutter/cupertino.dart'; class WalletHomeViewModel extends ViewModel { WalletHomeViewModel({required this.wallet}); @@ -16,7 +15,7 @@ class WalletHomeViewModel extends ViewModel { String get balance => wallet.getBalanceString(); String get currentAddress => wallet.getCurrentAddress; - void showScanner(BuildContext context) { - BarcodeScanner(wallet: wallet).push(context); + void showScanner() { + BarcodeScanner(wallet: wallet).push(c!); } } diff --git a/lib/views/abstract.dart b/lib/views/abstract.dart index ebdebca..81a9e19 100644 --- a/lib/views/abstract.dart +++ b/lib/views/abstract.dart @@ -23,15 +23,15 @@ class _AbstractViewState extends State { Widget Function(BuildContext context) realBuild; @override - Widget build(BuildContext context) { + Widget build(final BuildContext context) { return realBuild(context); } } class AbstractView extends StatefulWidget { - Future push(BuildContext context) async { + Future push(final BuildContext context) async { await Navigator.of(context).push(CupertinoPageRoute( - builder: (context) { + builder: (final context) { return this; }, )); @@ -48,7 +48,7 @@ class AbstractView extends StatefulWidget { AppLocalizations get L => viewModel.L; - Future initState(BuildContext context) async {} + Future initState(final BuildContext context) async {} State? state; @@ -66,15 +66,15 @@ class AbstractView extends StatefulWidget { automaticallyImplyLeading: canPop, ); - Widget? body(BuildContext context) => null; + Widget? body(final BuildContext context) => null; bool _internalIsInitStateCalled = false; - bool canPop = true; + bool get canPop => viewModel.canPop; Drawer? drawer; - Widget build(BuildContext context) { + Widget build(final BuildContext context) { viewModel.register(context); if (!_internalIsInitStateCalled) { _internalIsInitStateCalled = true; @@ -83,6 +83,7 @@ class AbstractView extends StatefulWidget { return PopScope( canPop: canPop, child: Scaffold( + key: viewModel.scaffoldKey, appBar: appBar, body: body(context), endDrawer: drawer, @@ -92,7 +93,7 @@ class AbstractView extends StatefulWidget { ); } - Widget? bottomNavigationBar(BuildContext context) => null; + Widget? bottomNavigationBar(final BuildContext context) => null; - Widget? floatingActionButton(BuildContext context) => null; + Widget? floatingActionButton(final BuildContext context) => null; } diff --git a/lib/views/animated_qr_page.dart b/lib/views/animated_qr_page.dart index ffbda39..ecd9f99 100644 --- a/lib/views/animated_qr_page.dart +++ b/lib/views/animated_qr_page.dart @@ -4,14 +4,14 @@ import 'package:cupcake/views/widgets/urqr.dart'; import 'package:flutter/material.dart'; class AnimatedURPage extends AbstractView { - AnimatedURPage({super.key, required Map> urqrList}) + AnimatedURPage({super.key, required final Map> urqrList}) : viewModel = URQRViewModel(urqrList: urqrList); @override final URQRViewModel viewModel; @override - Widget body(BuildContext context) { + Widget body(final BuildContext context) { return Column( mainAxisSize: MainAxisSize.min, children: [ @@ -29,17 +29,18 @@ class AnimatedURPage extends AbstractView { List _extraButtons() { final List toRet = []; - for (var key in viewModel.alternativeCodes) { + for (final key in viewModel.alternativeCodes) { toRet.add(_urqrSwitchButton(key, viewModel.urqrList[key]!)); } return toRet; } - Widget _urqrSwitchButton(String key, List value) { + Widget _urqrSwitchButton(final String key, final List value) { return OutlinedButton( - onPressed: () { - viewModel.urqr = value; - }, - child: Text(key),); + onPressed: () { + viewModel.urqr = value; + }, + child: Text(key), + ); } } diff --git a/lib/views/barcode_scanner.dart b/lib/views/barcode_scanner.dart index 276eb6e..88a479a 100644 --- a/lib/views/barcode_scanner.dart +++ b/lib/views/barcode_scanner.dart @@ -8,19 +8,20 @@ import 'package:fast_scanner/fast_scanner.dart'; import 'package:flutter/material.dart'; class BarcodeScanner extends AbstractView { - BarcodeScanner({super.key, required CoinWallet wallet}) + BarcodeScanner({super.key, required final CoinWallet wallet}) : viewModel = BarcodeScannerViewModel(wallet: wallet); @override final BarcodeScannerViewModel viewModel; @override - Widget? body(BuildContext context) { + Widget? body(final BuildContext context) { viewModel.register(context); return Stack( children: [ MobileScanner( - onDetect: (BarcodeCapture bc) => viewModel.handleBarcode(context, bc), + onDetect: (final BarcodeCapture bc) => + viewModel.handleBarcode(context, bc), controller: viewModel.mobileScannerCtrl, ), if (viewModel.ur.inputs.isNotEmpty) diff --git a/lib/views/create_wallet.dart b/lib/views/create_wallet.dart index 3bb8374..84352a7 100644 --- a/lib/views/create_wallet.dart +++ b/lib/views/create_wallet.dart @@ -1,6 +1,5 @@ import 'package:cupcake/coins/types.dart'; import 'package:cupcake/gen/assets.gen.dart'; -import 'package:cupcake/utils/call_throwable.dart'; import 'package:cupcake/utils/config.dart'; import 'package:cupcake/utils/form/abstract_form_element.dart'; import 'package:cupcake/view_model/create_wallet_view_model.dart'; @@ -12,8 +11,8 @@ import 'package:flutter/material.dart'; class CreateWallet extends AbstractView { CreateWallet( {super.key, - required CreateMethod createMethod, - required bool needsPasswordConfirm}) + required final CreateMethod createMethod, + required final bool needsPasswordConfirm}) : viewModel = CreateWalletViewModel( createMethod: createMethod, needsPasswordConfirm: needsPasswordConfirm); @@ -22,11 +21,11 @@ class CreateWallet extends AbstractView { final CreateWalletViewModel viewModel; @override - Widget? body(BuildContext context) { + Widget? body(final BuildContext context) { if (viewModel.selectedCoin == null) { return ListView.builder( itemCount: viewModel.coins.length, - itemBuilder: (BuildContext context, int index) { + itemBuilder: (final BuildContext context, final int index) { return InkWell( onTap: () { viewModel.selectedCoin = viewModel.coins[index]; @@ -45,7 +44,7 @@ class CreateWallet extends AbstractView { height: double.maxFinite, child: ListView.builder( itemCount: viewModel.createMethods.keys.length, - itemBuilder: (BuildContext context, int index) { + itemBuilder: (final BuildContext context, final int index) { final key = viewModel.createMethods.keys.elementAt(index); final value = viewModel.createMethods[key]; return InkWell( @@ -65,8 +64,8 @@ class CreateWallet extends AbstractView { formBuilder = FormBuilder( formElements: viewModel.currentForm ?? [], scaffoldContext: context, - rebuild: (bool val) { - viewModel.isPinSet = val; + rebuild: (final bool val) { + viewModel.isPinSet = val; }, isPinSet: viewModel.isPinSet, showExtra: viewModel.showExtra, @@ -88,7 +87,7 @@ class CreateWallet extends AbstractView { } @override - Widget? bottomNavigationBar(BuildContext context) { + Widget? bottomNavigationBar(final BuildContext context) { if (viewModel.isPinSet) { return SafeArea( child: Column( @@ -97,7 +96,7 @@ class CreateWallet extends AbstractView { LongPrimaryButton( text: L.next, icon: null, - onPressed: () => _next(context), + onPressed: () => _next(), backgroundColor: const WidgetStatePropertyAll(Colors.green), textColor: Colors.white, ), @@ -117,15 +116,14 @@ class CreateWallet extends AbstractView { return null; } - void _next(BuildContext context) async { - await callThrowable(context, - () async => await viewModel.createWallet(context), L.creating_wallet); + void _next() async { + await viewModel.createWallet(); } FormBuilder? formBuilder; - bool isFormBad(List form) { - for (var element in form) { + bool isFormBad(final List form) { + for (final element in form) { if (!element.isOk) { if (CupcakeConfig.instance.debug) { print("${element.label} is not valid: "); @@ -137,9 +135,10 @@ class CreateWallet extends AbstractView { } @override - Widget build(BuildContext context) { + Widget build(final BuildContext context) { viewModel.register(context); return Scaffold( + key: viewModel.scaffoldKey, appBar: appBar, body: SingleChildScrollView(child: body(context)), floatingActionButton: floatingActionButton(context), diff --git a/lib/views/home_screen.dart b/lib/views/home_screen.dart index 14772f8..8139d42 100644 --- a/lib/views/home_screen.dart +++ b/lib/views/home_screen.dart @@ -1,18 +1,16 @@ import 'package:cupcake/coins/abstract/wallet_info.dart'; import 'package:cupcake/coins/types.dart'; -import 'package:cupcake/utils/call_throwable.dart'; -import 'package:cupcake/utils/config.dart'; import 'package:cupcake/view_model/home_screen_view_model.dart'; import 'package:cupcake/views/abstract.dart'; -import 'package:cupcake/views/create_wallet.dart'; import 'package:cupcake/views/initial_setup_screen.dart'; -import 'package:cupcake/views/wallet_edit.dart'; import 'package:flutter/material.dart'; import 'package:path/path.dart' as p; class HomeScreen extends AbstractView { HomeScreen( - {super.key, required bool openLastWallet, String? lastOpenedWallet}) + {super.key, + required final bool openLastWallet, + final String? lastOpenedWallet}) : viewModel = HomeScreenViewModel( openLastWallet: openLastWallet, lastOpenedWallet: lastOpenedWallet); @@ -20,10 +18,10 @@ class HomeScreen extends AbstractView { final HomeScreenViewModel viewModel; @override - Widget? body(BuildContext context) { + Widget? body(final BuildContext context) { return FutureBuilder( future: viewModel.showLandingInfo, - builder: (BuildContext context, AsyncSnapshot value) { + builder: (final BuildContext context, final AsyncSnapshot value) { if (!value.hasData) return Container(); if (value.data!) { return Text(L.home_no_wallets); @@ -50,13 +48,13 @@ class HomeScreen extends AbstractView { ), ], ); - Widget walletsBody( - BuildContext context, AsyncSnapshot> wallets) { + Widget walletsBody(final BuildContext context, + final AsyncSnapshot> wallets) { if (!wallets.hasData) return Container(); return ListView.builder( itemCount: wallets.data!.length, - itemBuilder: (BuildContext context, int index) { - bool isOpen = (wallets.data![index].walletName) + itemBuilder: (final BuildContext context, final int index) { + final bool isOpen = (wallets.data![index].walletName) .contains(viewModel.lastOpenedWallet ?? ""); return Card( child: IntrinsicHeight( @@ -79,12 +77,7 @@ class HomeScreen extends AbstractView { trailing: IconButton( icon: const Icon(Icons.edit_rounded), onPressed: () async { - await callThrowable( - context, - () => renameWallet(context, wallets.data![index]), - "Renaming wallet", - ); - viewModel.markNeedsBuild(); + await viewModel.renameWallet(wallets.data![index]); }, ), title: Text( @@ -99,38 +92,20 @@ class HomeScreen extends AbstractView { }); } - Future renameWallet( - BuildContext context, CoinWalletInfo walletInfo) async { - canPop = false; // don't allow user to go back to previous wallet - await viewModel.markNeedsBuild(); - if (!context.mounted) return; - await WalletEdit(walletInfo: walletInfo).push(context); - await viewModel.markNeedsBuild(); - } - - Future createWallet(BuildContext context, CreateMethod method) async { - await CreateWallet( - createMethod: method, - needsPasswordConfirm: false, - ).push(context); - if (!context.mounted) return; - viewModel.markNeedsBuild(); - } - @override - Widget? bottomNavigationBar(BuildContext context) { + Widget? bottomNavigationBar(final BuildContext context) { return SafeArea( child: Column( mainAxisSize: MainAxisSize.min, children: [ LongPrimaryButton( icon: Icons.add, - onPressed: () => createWallet(context, CreateMethod.create), + onPressed: () => viewModel.createWallet(CreateMethod.create), text: L.create_new_wallet, ), LongSecondaryButton( icon: Icons.restore, - onPressed: () => createWallet(context, CreateMethod.restore), + onPressed: () => viewModel.createWallet(CreateMethod.restore), text: L.restore_wallet, ), ], @@ -138,7 +113,7 @@ class HomeScreen extends AbstractView { } @override - Future initState(BuildContext context) { - return viewModel.loadInitialState(); + Future initState(final BuildContext context) { + return viewModel.loadInitialState(context); } } diff --git a/lib/views/initial_setup_screen.dart b/lib/views/initial_setup_screen.dart index 3c246bd..90185a7 100644 --- a/lib/views/initial_setup_screen.dart +++ b/lib/views/initial_setup_screen.dart @@ -13,7 +13,7 @@ class InitialSetupScreen extends AbstractView { InitialSetupViewModel viewModel = InitialSetupViewModel(); @override - Widget? body(BuildContext context) { + Widget? body(final BuildContext context) { return SafeArea( minimum: const EdgeInsets.only(bottom: 16), child: Column( @@ -95,7 +95,7 @@ class LongPrimaryButton extends StatelessWidget { this.width = double.maxFinite, }); - final padding; + final EdgeInsets padding; final WidgetStateProperty? backgroundColor; final Color textColor; @@ -105,7 +105,7 @@ class LongPrimaryButton extends StatelessWidget { final VoidCallback? onPressed; final double? width; @override - Widget build(BuildContext context) { + Widget build(final BuildContext context) { return Padding( padding: padding, child: SizedBox( diff --git a/lib/views/new_wallet_info.dart b/lib/views/new_wallet_info.dart index 57ca60a..bbed504 100644 --- a/lib/views/new_wallet_info.dart +++ b/lib/views/new_wallet_info.dart @@ -4,7 +4,7 @@ import 'package:cupcake/views/initial_setup_screen.dart'; import 'package:flutter/material.dart'; class NewWalletInfoScreen extends AbstractView { - NewWalletInfoScreen({super.key, required List pages}) + NewWalletInfoScreen({super.key, required final List pages}) : viewModel = NewWalletInfoViewModel(pages); @override @@ -29,7 +29,7 @@ class NewWalletInfoScreen extends AbstractView { viewModel.page.topAction == null) { return [ TextButton( - onPressed: viewModel.nextPage, + onPressed: () => viewModel.currentPageIndex++, child: viewModel.page.topActionText!, ), ]; @@ -43,12 +43,12 @@ class NewWalletInfoScreen extends AbstractView { } List _getBottomActionButtons() { - return List.generate(viewModel.page.actions.length, (index) { + return List.generate(viewModel.page.actions.length, (final index) { final action = viewModel.page.actions[index]; final isLast = index + 1 == viewModel.page.actions.length; final callback = switch (action.type) { NewWalletActionType.function => action.function!, - NewWalletActionType.nextPage => viewModel.nextPage, + NewWalletActionType.nextPage => () => viewModel.currentPageIndex++, }; return Expanded( child: LongPrimaryButton( @@ -64,7 +64,7 @@ class NewWalletInfoScreen extends AbstractView { } @override - Widget? body(BuildContext context) { + Widget? body(final BuildContext context) { return SafeArea( child: Padding( padding: const EdgeInsets.only(left: 32, right: 32, top: 0, bottom: 16), diff --git a/lib/views/open_wallet.dart b/lib/views/open_wallet.dart index a829e1d..43f2ba9 100644 --- a/lib/views/open_wallet.dart +++ b/lib/views/open_wallet.dart @@ -5,14 +5,14 @@ import 'package:cupcake/views/widgets/form_builder.dart'; import 'package:flutter/cupertino.dart'; class OpenWallet extends AbstractView { - OpenWallet({super.key, required CoinWalletInfo coinWalletInfo}) + OpenWallet({super.key, required final CoinWalletInfo coinWalletInfo}) : viewModel = OpenWalletViewModel(coinWalletInfo: coinWalletInfo); @override final OpenWalletViewModel viewModel; @override - Widget body(BuildContext context) { + Widget body(final BuildContext context) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ diff --git a/lib/views/receive.dart b/lib/views/receive.dart index 8ed0bf0..ea1fd12 100644 --- a/lib/views/receive.dart +++ b/lib/views/receive.dart @@ -6,14 +6,14 @@ import 'package:flutter/services.dart'; import 'package:qr_flutter/qr_flutter.dart'; class Receive extends AbstractView { - Receive({super.key, required CoinWallet coinWallet}) + Receive({super.key, required final CoinWallet coinWallet}) : viewModel = ReceiveViewModel(coinWallet); @override final ReceiveViewModel viewModel; @override - Widget? body(BuildContext context) { + Widget? body(final BuildContext context) { return Padding( padding: const EdgeInsets.all(16), child: Column( @@ -29,7 +29,14 @@ class Receive extends AbstractView { ), child: QrImageView( data: "monero:${viewModel.address}", - foregroundColor: Colors.black, + dataModuleStyle: QrDataModuleStyle( + color: Colors.black, + dataModuleShape: QrDataModuleShape.square, + ), + eyeStyle: QrEyeStyle( + color: Colors.black, + eyeShape: QrEyeShape.square, + ), ), ), ), diff --git a/lib/views/security_backup.dart b/lib/views/security_backup.dart index b3fa660..b347884 100644 --- a/lib/views/security_backup.dart +++ b/lib/views/security_backup.dart @@ -11,13 +11,13 @@ import 'package:flutter/services.dart'; import 'package:qr_flutter/qr_flutter.dart'; class SecurityBackup extends AbstractView { - SecurityBackup({super.key, required CoinWallet coinWallet}) + SecurityBackup({super.key, required final CoinWallet coinWallet}) : viewModel = SecurityBackupViewModel(wallet: coinWallet); @override SecurityBackupViewModel viewModel; - void _copy(BuildContext context, String value, String key) { + void _copy(final BuildContext context, final String value, final String key) { Clipboard.setData(ClipboardData(text: value)); ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text("Copied $key"), @@ -25,7 +25,7 @@ class SecurityBackup extends AbstractView { } @override - Widget? body(BuildContext context) { + Widget? body(final BuildContext context) { if (viewModel.isLocked) { return FormBuilder( formElements: viewModel.form, @@ -38,11 +38,11 @@ class SecurityBackup extends AbstractView { final details = viewModel.wallet.seedDetails(L); return FutureBuilder( future: details, - builder: (BuildContext context, snapshot) { + builder: (final BuildContext context, final snapshot) { if (!snapshot.hasData) return Text(snapshot.error.toString()); return ListView.builder( itemCount: snapshot.data!.length, - itemBuilder: (BuildContext context, int index) { + itemBuilder: (final BuildContext context, final int index) { final d = snapshot.data![index]; switch (d.type) { case WalletSeedDetailType.text: @@ -73,9 +73,9 @@ class SecurityBackup extends AbstractView { } Future _showQrCode( - BuildContext context, - WalletSeedDetail d, { - Color color = Colors.black, + final BuildContext context, + final WalletSeedDetail d, { + final Color color = Colors.black, }) async { await showAlertWidget( context: context, @@ -86,7 +86,14 @@ class SecurityBackup extends AbstractView { child: QrImageView( data: d.value, backgroundColor: color, - foregroundColor: Colors.white, + dataModuleStyle: QrDataModuleStyle( + color: Colors.black, + dataModuleShape: QrDataModuleShape.square, + ), + eyeStyle: QrEyeStyle( + color: Colors.black, + eyeShape: QrEyeShape.square, + ), ), ), ], diff --git a/lib/views/settings.dart b/lib/views/settings.dart index 4c31a7f..1f2c4b0 100644 --- a/lib/views/settings.dart +++ b/lib/views/settings.dart @@ -12,14 +12,10 @@ class SettingsView extends AbstractView { SettingsView({super.key}); @override - SettingsViewModel get viewModel => SettingsViewModel(); - - Future postUpdate(BuildContext context) async { - viewModel.appConfig.save(); - } + SettingsViewModel viewModel = SettingsViewModel(); @override - Widget? body(BuildContext context) { + Widget? body(final BuildContext context) { return Column( children: [ if (CupcakeConfig.instance.debug) @@ -27,46 +23,54 @@ class SettingsView extends AbstractView { title: "Debug", subtitleEnabled: "Debug options are enabled", subtitleDisabled: "Debug options are disabled", - value: viewModel.appConfig.debug, - onChange: (bool value) { - viewModel.appConfig.debug = value; - postUpdate(context); + value: viewModel.configDebug, + onChange: (final bool value) { + viewModel.configDebug = value; }), IntegerConfigElement( title: "Milliseconds for qr code", hint: "How many milliseconds should one QR code last before switching to next one", - value: viewModel.appConfig.msForQrCode, - onChange: (int value) { - viewModel.appConfig.msForQrCode = value; - postUpdate(context); + value: viewModel.configMsForQrCode, + onChange: (final int value) { + viewModel.configMsForQrCode = value; }), BooleanConfigElement( title: "Biometric auth", subtitleEnabled: "Biometrics are enabled", subtitleDisabled: "In order to enable biometrics long press confirm button when entering pin", - value: viewModel.appConfig.biometricEnabled, - onChange: (bool value) async { + value: viewModel.configBiometricEnabled, + onChange: (final bool value) async { if (value) return; - viewModel.appConfig.biometricEnabled = false; + viewModel.configBiometricEnabled = false; final map = await secureStorage.readAll(); - for (var key in map.keys) { + for (final key in map.keys) { if (map[key]!.startsWith("UI.")) { await secureStorage.delete(key: key); } } - viewModel.appConfig.save(); - postUpdate(context); }), + if (viewModel.configDidFoundInsecureBiometric) + BooleanConfigElement( + title: "Insecure biometric auth", + subtitleEnabled: + "Insecure biometric authentication is enabled, it is not recommended" + " and could lead to loss of funds. Make sure that you understand the drawbacks," + " and when in doubt - keep this setting disabled.", + subtitleDisabled: + "Click to enable insecure biometric authentication.", + value: viewModel.configCanUseInsecureBiometric, + onChange: (final bool value) async { + viewModel.configCanUseInsecureBiometric = value; + }), IntegerConfigElement( title: "Max fragment density", hint: "How many characters of data should fit within a single QR code", - value: viewModel.appConfig.maxFragmentLength, - onChange: (int value) { - viewModel.appConfig.maxFragmentLength = value; - postUpdate(context); + value: viewModel.configMaxFragmentLength, + onChange: (final int value) async { + viewModel.configMaxFragmentLength = value; }, ), const VersionWidget(), diff --git a/lib/views/unconfirmed_transaction.dart b/lib/views/unconfirmed_transaction.dart index 6d3772b..11530c4 100644 --- a/lib/views/unconfirmed_transaction.dart +++ b/lib/views/unconfirmed_transaction.dart @@ -3,7 +3,6 @@ import 'dart:async'; import 'package:cupcake/coins/abstract/address.dart'; import 'package:cupcake/coins/abstract/amount.dart'; import 'package:cupcake/coins/abstract/wallet.dart'; -import 'package:cupcake/utils/call_throwable.dart'; import 'package:cupcake/view_model/unconfirmed_transaction_view_model.dart'; import 'package:cupcake/views/abstract.dart'; import 'package:flutter/material.dart'; @@ -11,11 +10,11 @@ import 'package:flutter/material.dart'; class UnconfirmedTransactionView extends AbstractView { UnconfirmedTransactionView({ super.key, - required CoinWallet wallet, - required Amount fee, - required Map destMap, - required FutureOr Function(BuildContext) confirmCallback, - required FutureOr Function(BuildContext) cancelCallback, + required final CoinWallet wallet, + required final Amount fee, + required final Map destMap, + required final FutureOr Function() confirmCallback, + required final FutureOr Function() cancelCallback, }) : viewModel = UnconfirmedTransactionViewModel( wallet: wallet, fee: fee, @@ -28,11 +27,11 @@ class UnconfirmedTransactionView extends AbstractView { final UnconfirmedTransactionViewModel viewModel; @override - Widget? body(BuildContext context) { + Widget? body(final BuildContext context) { final keys = viewModel.destMap.keys.toList(); return ListView.builder( itemCount: keys.length, - itemBuilder: (BuildContext context, int index) { + itemBuilder: (final BuildContext context, final int index) { final key = keys[index]; final value = viewModel.destMap[key]!; return ListTile( @@ -44,7 +43,7 @@ class UnconfirmedTransactionView extends AbstractView { } @override - Widget? bottomNavigationBar(BuildContext context) { + Widget? bottomNavigationBar(final BuildContext context) { return BottomNavigationBar( items: [ BottomNavigationBarItem( @@ -58,17 +57,13 @@ class UnconfirmedTransactionView extends AbstractView { icon: const Icon(Icons.check_circle, color: Colors.green), label: L.confirm), ], - onTap: (int index) async { + onTap: (final int index) async { if (index == 0) { - await callThrowable(context, - () async => await viewModel.cancelCallback(context), L.canceling); + viewModel.cancelCallback(); if (!context.mounted) return; Navigator.of(context).pop(); } else { - await callThrowable( - context, - () async => await viewModel.confirmCallback(context), - L.confirming); + await viewModel.confirmCallback(); if (!context.mounted) return; Navigator.of(context).pop(); } diff --git a/lib/views/wallet_edit.dart b/lib/views/wallet_edit.dart index 5bed603..c06063a 100644 --- a/lib/views/wallet_edit.dart +++ b/lib/views/wallet_edit.dart @@ -1,5 +1,4 @@ import 'package:cupcake/coins/abstract/wallet_info.dart'; -import 'package:cupcake/utils/call_throwable.dart'; import 'package:cupcake/view_model/wallet_edit_view_model.dart'; import 'package:cupcake/views/abstract.dart'; import 'package:cupcake/views/initial_setup_screen.dart'; @@ -7,14 +6,14 @@ import 'package:cupcake/views/widgets/form_builder.dart'; import 'package:flutter/material.dart'; class WalletEdit extends AbstractView { - WalletEdit({super.key, required CoinWalletInfo walletInfo}) + WalletEdit({super.key, required final CoinWalletInfo walletInfo}) : viewModel = WalletEditViewModel(walletInfo: walletInfo); @override WalletEditViewModel viewModel; @override - Widget? body(BuildContext context) { + Widget? body(final BuildContext context) { return Column( children: [ const Spacer(), @@ -23,7 +22,7 @@ class WalletEdit extends AbstractView { scaffoldContext: context, isPinSet: false, showExtra: true, - onLabelChange: viewModel.titleUpdate, + onLabelChange: (final _) {}, // do we need this? ), const Spacer(), Row( @@ -32,12 +31,8 @@ class WalletEdit extends AbstractView { child: LongPrimaryButton( backgroundColor: const WidgetStatePropertyAll(Colors.red), icon: null, - onPressed: () { - callThrowable( - context, - () => viewModel.deleteWallet(context), - "Deleting wallet", - ); + onPressed: () async { + await viewModel.deleteWallet(); }, text: "Delete", ), @@ -45,12 +40,11 @@ class WalletEdit extends AbstractView { Expanded( child: LongPrimaryButton( icon: null, - onPressed: () { - for (var element in viewModel.form) { + onPressed: () async { + for (final element in viewModel.form) { if (!element.isOk) continue; } - callThrowable(context, () => viewModel.renameWallet(context), - "Rename wallet"); + await viewModel.renameWallet(); }, text: "Rename", ), diff --git a/lib/views/wallet_home.dart b/lib/views/wallet_home.dart index 145aa30..bddd442 100644 --- a/lib/views/wallet_home.dart +++ b/lib/views/wallet_home.dart @@ -9,7 +9,7 @@ import 'package:flutter/material.dart'; import 'package:cupcake/gen/assets.gen.dart'; class WalletHome extends AbstractView { - WalletHome({super.key, required CoinWallet coinWallet}) + WalletHome({super.key, required final CoinWallet coinWallet}) : viewModel = WalletHomeViewModel(wallet: coinWallet); @override @@ -19,7 +19,6 @@ class WalletHome extends AbstractView { bool get canPop => false; @override - // TODO: implement drawer Drawer? get drawer => Drawer( child: SingleChildScrollView( child: SafeArea( @@ -64,7 +63,7 @@ class WalletHome extends AbstractView { ); @override - Widget? body(BuildContext context) { + Widget? body(final BuildContext context) { return Column( children: [ const SizedBox(height: 40), diff --git a/lib/views/widgets/barcode_scanner/progress_painter.dart b/lib/views/widgets/barcode_scanner/progress_painter.dart index f68e769..df3a618 100644 --- a/lib/views/widgets/barcode_scanner/progress_painter.dart +++ b/lib/views/widgets/barcode_scanner/progress_painter.dart @@ -9,14 +9,14 @@ class ProgressPainter extends CustomPainter { ProgressPainter({required this.urQrProgress}); @override - void paint(Canvas canvas, Size size) { + void paint(final Canvas canvas, final Size size) { final c = Offset(size.width / 2.0, size.height / 2.0); final radius = size.width * 0.9; final rect = Rect.fromCenter(center: c, width: radius, height: radius); const fullAngle = 360.0; var startAngle = 0.0; for (int i = 0; i < urQrProgress.expectedPartCount.toInt(); i++) { - var sweepAngle = + final sweepAngle = (1 / urQrProgress.expectedPartCount) * fullAngle * pi / 180.0; drawSector(canvas, urQrProgress.receivedPartIndexes.contains(i), rect, startAngle, sweepAngle); @@ -24,8 +24,8 @@ class ProgressPainter extends CustomPainter { } } - void drawSector(Canvas canvas, bool isActive, Rect rect, double startAngle, - double sweepAngle) { + void drawSector(final Canvas canvas, final bool isActive, final Rect rect, + final double startAngle, final double sweepAngle) { final paint = Paint() ..style = PaintingStyle.stroke ..strokeWidth = 8 @@ -36,7 +36,7 @@ class ProgressPainter extends CustomPainter { } @override - bool shouldRepaint(covariant ProgressPainter oldDelegate) { + bool shouldRepaint(covariant final ProgressPainter oldDelegate) { return urQrProgress != oldDelegate.urQrProgress; } } diff --git a/lib/views/widgets/barcode_scanner/switch_camera.dart b/lib/views/widgets/barcode_scanner/switch_camera.dart index 4f59427..41b9d7b 100644 --- a/lib/views/widgets/barcode_scanner/switch_camera.dart +++ b/lib/views/widgets/barcode_scanner/switch_camera.dart @@ -7,10 +7,10 @@ class SwitchCameraButton extends StatelessWidget { final MobileScannerController controller; @override - Widget build(BuildContext context) { + Widget build(final BuildContext context) { return ValueListenableBuilder( valueListenable: controller, - builder: (context, state, child) { + builder: (final context, final state, final child) { if (!state.isInitialized || !state.isRunning) { return const SizedBox.shrink(); } diff --git a/lib/views/widgets/barcode_scanner/toggle_flashlight_button.dart b/lib/views/widgets/barcode_scanner/toggle_flashlight_button.dart index f922be1..c00fd87 100644 --- a/lib/views/widgets/barcode_scanner/toggle_flashlight_button.dart +++ b/lib/views/widgets/barcode_scanner/toggle_flashlight_button.dart @@ -7,10 +7,10 @@ class ToggleFlashlightButton extends StatelessWidget { final MobileScannerController controller; @override - Widget build(BuildContext context) { + Widget build(final BuildContext context) { return ValueListenableBuilder( valueListenable: controller, - builder: (context, state, child) { + builder: (final context, final state, final child) { if (!state.isInitialized || !state.isRunning) { return const SizedBox.shrink(); } diff --git a/lib/views/widgets/barcode_scanner/urqr_progress.dart b/lib/views/widgets/barcode_scanner/urqr_progress.dart index 839c71a..86707aa 100644 --- a/lib/views/widgets/barcode_scanner/urqr_progress.dart +++ b/lib/views/widgets/barcode_scanner/urqr_progress.dart @@ -11,7 +11,7 @@ class URQrProgress { required this.percentage, }); - bool equals(URQrProgress? progress) { + bool equals(final URQrProgress? progress) { if (progress == null) { return false; } diff --git a/lib/views/widgets/cake_card.dart b/lib/views/widgets/cake_card.dart index 535b188..fb978a5 100644 --- a/lib/views/widgets/cake_card.dart +++ b/lib/views/widgets/cake_card.dart @@ -24,7 +24,7 @@ class CakeCard extends StatelessWidget { final EdgeInsets externalPadding; final EdgeInsets firmPadding; @override - Widget build(BuildContext context) { + Widget build(final BuildContext context) { return Padding( padding: externalPadding, child: SizedBox( diff --git a/lib/views/widgets/cupcake_appbar_title.dart b/lib/views/widgets/cupcake_appbar_title.dart index a472aa5..76ffb76 100644 --- a/lib/views/widgets/cupcake_appbar_title.dart +++ b/lib/views/widgets/cupcake_appbar_title.dart @@ -1,18 +1,20 @@ import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; +import 'package:cupcake/gen/assets.gen.dart'; class CupcakeAppbarTitle extends StatelessWidget { const CupcakeAppbarTitle({super.key}); @override - Widget build(BuildContext context) { + Widget build(final BuildContext context) { return Padding( padding: const EdgeInsets.all(8.0), child: Row( mainAxisSize: MainAxisSize.min, children: [ - SvgPicture.asset("assets/icons/icon-white.svg", - height: 32, width: 32, color: Colors.white), + Assets.icons.iconWhite.svg( + width: 32, + colorFilter: ColorFilter.mode(Colors.white, BlendMode.srcIn), + ), const SizedBox( width: 12, ), diff --git a/lib/views/widgets/drawer_element.dart b/lib/views/widgets/drawer_element.dart index 72ebf93..445b64e 100644 --- a/lib/views/widgets/drawer_element.dart +++ b/lib/views/widgets/drawer_element.dart @@ -1,4 +1,3 @@ -import 'package:cupcake/utils/call_throwable.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; @@ -15,7 +14,7 @@ class DrawerElement extends StatelessWidget { final Future Function(BuildContext context) action; @override - Widget build(BuildContext context) { + Widget build(final BuildContext context) { return Container( margin: const EdgeInsets.only(top: 5, left: 20, bottom: 5), child: TextButton( @@ -30,8 +29,8 @@ class DrawerElement extends StatelessWidget { ), ), ), - onPressed: () { - callThrowable(context, () async => await action(context), text); + onPressed: () async { + await action(context); }, child: Padding( padding: const EdgeInsets.all(16.0), diff --git a/lib/views/widgets/drawer_elements.dart b/lib/views/widgets/drawer_elements.dart index 3cc956f..3fdf248 100644 --- a/lib/views/widgets/drawer_elements.dart +++ b/lib/views/widgets/drawer_elements.dart @@ -13,30 +13,30 @@ class DrawerElements extends StatelessWidget { final CoinWallet coinWallet; - Future _walletList(BuildContext context) async { + Future _walletList(final BuildContext context) async { await HomeScreen( openLastWallet: false, lastOpenedWallet: coinWallet.walletName, ).push(context); } - Future _securityBackup(BuildContext context) async { + Future _securityBackup(final BuildContext context) async { await SecurityBackup(coinWallet: coinWallet).push(context); } - Future _exportKeyImages(BuildContext context) async { + Future _exportKeyImages(final BuildContext context) async { if (coinWallet is! MoneroWallet) { throw Exception("coinWallet is not monero - we can't export key images"); } await (coinWallet as MoneroWallet).exportKeyImagesUR(context); } - Future _otherSettings(BuildContext context) async { + Future _otherSettings(final BuildContext context) async { await SettingsView().push(context); } @override - Widget build(BuildContext context) { + Widget build(final BuildContext context) { final L = AppLocalizations.of(context)!; return Column( children: [ diff --git a/lib/views/widgets/form_builder.dart b/lib/views/widgets/form_builder.dart index e9ec4ef..b48e8ab 100644 --- a/lib/views/widgets/form_builder.dart +++ b/lib/views/widgets/form_builder.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'package:cupcake/utils/alerts/widget_minimal.dart'; -import 'package:cupcake/utils/call_throwable.dart'; import 'package:cupcake/utils/config.dart'; import 'package:cupcake/utils/form/abstract_form_element.dart'; import 'package:cupcake/utils/form/pin_form_element.dart'; @@ -41,27 +40,31 @@ class _FormBuilderState extends State { } String? lastSuggestedTitle = DateTime.now().toIso8601String(); - void _onLabelChange(String? suggestedTitle) { + void _onLabelChange(final String? suggestedTitle) { if (suggestedTitle == lastSuggestedTitle) return; lastSuggestedTitle = suggestedTitle; widget.onLabelChange(suggestedTitle); } - void _pinSet(bool val) { + void _pinSet(final bool val) { widget.rebuild?.call(val); _rebuild(); } - @override - Widget build(BuildContext context) { - if ((widget.formElements.isNotEmpty && + bool _displayPinFormElement() { + return (widget.formElements.isNotEmpty && (widget.formElements.first is PinFormElement && (widget.formElements.first as PinFormElement).showNumboard) && !(widget.formElements[0] as PinFormElement).isConfirmed) || widget.formElements.length >= 2 && (widget.formElements[1] is PinFormElement && (widget.formElements[1] as PinFormElement).showNumboard) && - !(widget.formElements[1] as PinFormElement).isConfirmed) { + !(widget.formElements[1] as PinFormElement).isConfirmed; + } + + @override + Widget build(final BuildContext context) { + if (_displayPinFormElement()) { var e = widget.formElements.first as PinFormElement; int i = 0; int count = 0; @@ -75,13 +78,15 @@ class _FormBuilderState extends State { } _onLabelChange(e.label); nextPageCallback() async { - final b = await callThrowable(context, () async { + try { await e.onConfirmInternal(context); - }, "Secure storage communication"); - if (b == false) return; - if (!context.mounted) return; - _pinSet(count == i); - e.onConfirm?.call(context); + if (!context.mounted) return; + _pinSet(count == i); + await e.onConfirm?.call(); + } catch (err) { + await e.errorHandler(err); + return; + } } unawaited(e.loadSecureStorageValue(nextPageCallback)); @@ -97,8 +102,8 @@ class _FormBuilderState extends State { decoration: const InputDecoration( border: InputBorder.none, ), - onChanged: (_) { - e.onChanged?.call(context); + onChanged: (final _) { + e.onChanged?.call(); }, style: const TextStyle( fontSize: 64, @@ -112,7 +117,7 @@ class _FormBuilderState extends State { showConfirm: () => e.isOk, nextPage: nextPageCallback, onConfirmLongPress: () async { - final b = await callThrowable(context, () async { + try { await e.onConfirmInternal(context); final auth = LocalAuthentication(); @@ -125,14 +130,19 @@ class _FormBuilderState extends State { if (!canAuthenticate) throw Exception("Can't authenticate"); if (!availableBiometrics .contains(BiometricType.fingerprint) && - !availableBiometrics.contains(BiometricType.face)) { - throw Exception("No biometric auth found"); + !availableBiometrics.contains(BiometricType.face) && + !CupcakeConfig.instance.canUseInsecureBiometric) { + CupcakeConfig.instance.didFoundInsecureBiometric = true; + CupcakeConfig.instance.save(); + throw Exception("No secure biometric auth found."); } final bool didAuthenticate = await auth.authenticate( localizedReason: 'Authenticate...', - options: const AuthenticationOptions( - useErrorDialogs: true, biometricOnly: true), + options: AuthenticationOptions( + useErrorDialogs: true, + biometricOnly: + !CupcakeConfig.instance.canUseInsecureBiometric), ); if (!didAuthenticate) { throw Exception("User didn't authenticate"); @@ -141,11 +151,16 @@ class _FormBuilderState extends State { key: "UI.${e.valueOutcome.uniqueId}", value: e.ctrl.text); CupcakeConfig.instance.biometricEnabled = true; CupcakeConfig.instance.save(); - ScaffoldMessenger.of(context).showSnackBar(const SnackBar( - content: Text("Biometric enabled!"), - )); - nextPageCallback(); - }, "Secure storage communication"); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(const SnackBar( + content: Text("Biometric enabled!"), + )); + } + await nextPageCallback(); + } catch (err) { + await e.errorHandler(err); + return; + } }, showComma: false, ), @@ -188,7 +203,7 @@ class _FormBuilderState extends State { ), autovalidateMode: AutovalidateMode.onUserInteraction, validator: e.validator, - onChanged: (_) { + onChanged: (final _) { _rebuild(); }, textAlign: TextAlign.center, @@ -230,7 +245,7 @@ class _FormBuilderState extends State { ), autovalidateMode: AutovalidateMode.onUserInteraction, validator: e.validator, - onChanged: (_) { + onChanged: (final _) { _rebuild(); }, textAlign: TextAlign.center, @@ -263,12 +278,12 @@ class _FormBuilderState extends State { } Future _changeSingleChoice( - BuildContext context, SingleChoiceFormElement e) async { + final BuildContext context, final SingleChoiceFormElement e) async { await showAlertWidgetMinimal( context: context, body: List.generate( e.elements.length, - (index) { + (final index) { return InkWell( child: LongPrimaryButton( text: e.elements[index], diff --git a/lib/views/widgets/numerical_keyboard/keyboard.dart b/lib/views/widgets/numerical_keyboard/keyboard.dart index cb7111a..abb315e 100644 --- a/lib/views/widgets/numerical_keyboard/keyboard.dart +++ b/lib/views/widgets/numerical_keyboard/keyboard.dart @@ -80,7 +80,7 @@ enum Keys { dot } -String getKeysChar(Keys key) { +String getKeysChar(final Keys key) { return switch (key) { Keys.a0 => "0", Keys.a1 => "1", @@ -162,7 +162,7 @@ String getKeysChar(Keys key) { }; } -Widget getKeyWidgetPinPad(Keys key) { +Widget getKeyWidgetPinPad(final Keys key) { return switch (key) { Keys.a0 || Keys.a1 || @@ -185,7 +185,7 @@ Widget getKeyWidgetPinPad(Keys key) { }; } -Widget getKeyWidgetKeyboard(Keys key, Color? color) { +Widget getKeyWidgetKeyboard(final Keys key, final Color? color) { return SizedBox( height: 32, child: switch (key) { diff --git a/lib/views/widgets/numerical_keyboard/main.dart b/lib/views/widgets/numerical_keyboard/main.dart index 111fc8c..81bb982 100644 --- a/lib/views/widgets/numerical_keyboard/main.dart +++ b/lib/views/widgets/numerical_keyboard/main.dart @@ -19,7 +19,7 @@ class NumericalKeyboard extends StatelessWidget { final VoidCallback? onConfirmLongPress; final bool showComma; @override - Widget build(BuildContext context) { + Widget build(final BuildContext context) { return Column( children: [ Row(children: [ diff --git a/lib/views/widgets/numerical_keyboard/single_key.dart b/lib/views/widgets/numerical_keyboard/single_key.dart index c3d944f..ee7337f 100644 --- a/lib/views/widgets/numerical_keyboard/single_key.dart +++ b/lib/views/widgets/numerical_keyboard/single_key.dart @@ -9,7 +9,7 @@ class SingleKey extends StatelessWidget { final VoidCallback? callback; final VoidCallback? longPress; @override - Widget build(BuildContext context) { + Widget build(final BuildContext context) { return Expanded( flex: 2, child: Container( diff --git a/lib/views/widgets/settings/boolean_config_element.dart b/lib/views/widgets/settings/boolean_config_element.dart index 078d558..2049b5f 100644 --- a/lib/views/widgets/settings/boolean_config_element.dart +++ b/lib/views/widgets/settings/boolean_config_element.dart @@ -16,12 +16,12 @@ class BooleanConfigElement extends StatelessWidget { final bool value; final Function(bool val) onChange; @override - Widget build(BuildContext context) { + Widget build(final BuildContext context) { return CheckboxListTile( title: Text(title), subtitle: Text(value ? subtitleEnabled : subtitleDisabled), value: value, - onChanged: (bool? value) { + onChanged: (final bool? value) { onChange(value == true); }, ); diff --git a/lib/views/widgets/settings/integer_config_element.dart b/lib/views/widgets/settings/integer_config_element.dart index 66f0cdc..b333c3a 100644 --- a/lib/views/widgets/settings/integer_config_element.dart +++ b/lib/views/widgets/settings/integer_config_element.dart @@ -17,7 +17,7 @@ class IntegerConfigElement extends StatelessWidget { final Function(int val) onChange; late final ctrl = TextEditingController(text: value.toString()); @override - Widget build(BuildContext context) { + Widget build(final BuildContext context) { return ListTile( title: Text(title), onLongPress: () { @@ -26,7 +26,7 @@ class IntegerConfigElement extends StatelessWidget { }, subtitle: TextField( controller: ctrl, - onSubmitted: (String value) { + onSubmitted: (final String value) { final i = int.tryParse(value); if (i == null) return; onChange(i); diff --git a/lib/views/widgets/settings/version_widget.dart b/lib/views/widgets/settings/version_widget.dart index a2e0a3c..02f5b26 100644 --- a/lib/views/widgets/settings/version_widget.dart +++ b/lib/views/widgets/settings/version_widget.dart @@ -10,13 +10,13 @@ class VersionWidget extends StatefulWidget { } class _VersionWidgetState extends State { - Future showWidget(BuildContext context) async { - PackageInfo packageInfo = await PackageInfo.fromPlatform(); + Future showWidget(final BuildContext context) async { + final PackageInfo packageInfo = await PackageInfo.fromPlatform(); - String appName = packageInfo.appName; - String packageName = packageInfo.packageName; - String version = packageInfo.version; - String buildNumber = packageInfo.buildNumber; + final String appName = packageInfo.appName; + final String version = packageInfo.version; + final String buildNumber = packageInfo.buildNumber; + if (!context.mounted) return; showAboutDialog( context: context, applicationName: appName, @@ -73,7 +73,7 @@ class _VersionWidgetState extends State { } @override - Widget build(BuildContext context) { + Widget build(final BuildContext context) { return ListTile( title: const Text("About the app"), subtitle: subtitle == null ? null : Text(subtitle ?? "..."), diff --git a/lib/views/widgets/urqr.dart b/lib/views/widgets/urqr.dart index 55e325b..dc5348a 100644 --- a/lib/views/widgets/urqr.dart +++ b/lib/views/widgets/urqr.dart @@ -22,7 +22,8 @@ class _URQRState extends State { super.initState(); setState(() { t = Timer.periodic( - Duration(milliseconds: CupcakeConfig.instance.msForQrCode), (timer) { + Duration(milliseconds: CupcakeConfig.instance.msForQrCode), + (final timer) { _nextFrame(); }); }); @@ -41,7 +42,7 @@ class _URQRState extends State { } @override - Widget build(BuildContext context) { + Widget build(final BuildContext context) { return Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, @@ -54,7 +55,14 @@ class _URQRState extends State { color: Colors.white, ), child: QrImageView( - foregroundColor: Colors.black, + dataModuleStyle: QrDataModuleStyle( + color: Colors.black, + dataModuleShape: QrDataModuleShape.square, + ), + eyeStyle: QrEyeStyle( + color: Colors.black, + eyeShape: QrEyeShape.square, + ), data: widget.frames[frame % widget.frames.length], version: -1, size: 275, diff --git a/pubspec.lock b/pubspec.lock index a0db319..72be206 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -15,7 +15,7 @@ packages: source: sdk version: "0.3.3" analyzer: - dependency: transitive + dependency: "direct main" description: name: analyzer sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" @@ -55,7 +55,7 @@ packages: source: hosted version: "2.1.1" build: - dependency: transitive + dependency: "direct main" description: name: build sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0 @@ -864,6 +864,14 @@ packages: description: flutter source: sdk version: "0.0.0" + source_gen: + dependency: "direct main" + description: + name: source_gen + sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b" + url: "https://pub.dev" + source: hosted + version: "2.0.0" source_span: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 7f82b90..27d3d1b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -62,6 +62,9 @@ dependencies: crypto: ^3.0.3 package_info_plus: ^8.1.0 local_auth: ^2.3.0 + source_gen: ^2.0.0 + analyzer: ^6.11.0 + build: ^2.4.2 dev_dependencies: flutter_test: @@ -78,6 +81,7 @@ dev_dependencies: flutter_gen_runner: ^5.8.0 integration_test: sdk: flutter + analyzer: exclude: - lib/l10n/*.dart From d260abf0404fc74e0e43e26ddb807658380d8d66 Mon Sep 17 00:00:00 2001 From: Czarek Nakamoto Date: Sat, 8 Feb 2025 15:32:37 +0100 Subject: [PATCH 08/13] properly check for null in codegen --- lib/dev/rebuild_generator.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dev/rebuild_generator.dart b/lib/dev/rebuild_generator.dart index fd829e9..d80dc30 100644 --- a/lib/dev/rebuild_generator.dart +++ b/lib/dev/rebuild_generator.dart @@ -171,7 +171,7 @@ ${exposeRebuildableAccessorsMethods.join()} ${elm.returnType} get $noPrefix$capitalizedIfIfeelLikeIt => \$$noPrefix.${elm.name}; set $noPrefix$capitalizedIfIfeelLikeIt(final ${elm.returnType} new$capitalizedIfIfeelLikeIt) { \$$noPrefix.${elm.name} = new$capitalizedIfIfeelLikeIt; - ${(extraCode?.isNotEmpty ?? true) ? extraCode : '// no extraCode property'} + ${(extraCode?.isNotEmpty ?? false) ? extraCode : '// no extraCode property'} markNeedsBuild(); } '''); From 5d3016cabecb7625c0e4d17e3881cfc1eaa8d9e9 Mon Sep 17 00:00:00 2001 From: Czarek Nakamoto Date: Mon, 10 Feb 2025 10:50:11 +0100 Subject: [PATCH 09/13] formatter: page_width: 100 remove button classes from view separate new_wallet data classes from view model extractract nested widgets into methods remove logic from ui --- Makefile | 2 +- analysis_options.yaml | 4 + lib/coins/types.dart | 5 + lib/panic_handler.dart | 3 +- lib/utils/new_wallet/action.dart | 15 ++ lib/utils/new_wallet/info_page.dart | 102 +++++++++++ lib/view_model/abstract.dart | 4 +- .../barcode_scanner_view_model.dart | 8 +- lib/view_model/create_wallet_view_model.dart | 157 ++++------------- lib/view_model/home_screen_view_model.dart | 1 + .../new_wallet_info_view_model.dart | 41 +---- .../security_backup_view_model.dart | 2 - .../unconfirmed_transaction_view_model.dart | 19 +- lib/views/barcode_scanner.dart | 4 +- lib/views/create_wallet.dart | 143 +++++++-------- lib/views/home_screen.dart | 70 ++++---- lib/views/initial_setup_screen.dart | 163 +++++------------- lib/views/new_wallet_info.dart | 4 +- lib/views/security_backup.dart | 57 +++--- lib/views/unconfirmed_transaction.dart | 11 +- lib/views/wallet_edit.dart | 13 +- lib/views/wallet_home.dart | 146 +++++++++------- lib/views/widgets/buttons/long_primary.dart | 60 +++++++ lib/views/widgets/buttons/long_secondary.dart | 18 ++ lib/views/widgets/form_builder.dart | 6 +- 25 files changed, 529 insertions(+), 529 deletions(-) create mode 100644 lib/utils/new_wallet/action.dart create mode 100644 lib/utils/new_wallet/info_page.dart create mode 100644 lib/views/widgets/buttons/long_primary.dart create mode 100644 lib/views/widgets/buttons/long_secondary.dart diff --git a/Makefile b/Makefile index 987c4ea..6f39057 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ endif ./build_moneroc.sh --coin ${COIN} --tag ${MONERO_C_TAG} --triplet armv7a-linux-androideabi --location android/app/src/main/jniLibs/armeabi-v7a libs_android_build_ci: - ./build_moneroc.sh --prebuild --coin ${COIN} --tag ${MONERO_C_TAG} --triplet aarch64-linux-android --location android/app/src/main/jniLibs/arm64-v8a + ./build_moneroc.sh --coin ${COIN} --tag ${MONERO_C_TAG} --triplet aarch64-linux-android --location android/app/src/main/jniLibs/arm64-v8a libs_ios_download: ./build_moneroc.sh --prebuild --coin ${COIN} --tag ${MONERO_C_TAG} --triplet aarch64-apple-ios --location ios/native_libs/ios-arm64 diff --git a/analysis_options.yaml b/analysis_options.yaml index e4069a7..45a5a8b 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -14,3 +14,7 @@ linter: prefer_final_in_for_each: true prefer_final_locals: true prefer_final_parameters: true + avoid_void_async: true + +formatter: + page_width: 100 \ No newline at end of file diff --git a/lib/coins/types.dart b/lib/coins/types.dart index 01af7bb..96f4904 100644 --- a/lib/coins/types.dart +++ b/lib/coins/types.dart @@ -10,3 +10,8 @@ enum CreateMethod { create, restore, } + +enum NewWalletActionType { + nextPage, + function, +} diff --git a/lib/panic_handler.dart b/lib/panic_handler.dart index 58df402..6945a02 100644 --- a/lib/panic_handler.dart +++ b/lib/panic_handler.dart @@ -3,7 +3,8 @@ import 'package:cupcake/views/widgets/cupcake_appbar_title.dart'; import 'package:flutter/material.dart'; import 'package:package_info_plus/package_info_plus.dart'; -void catchFatalError(final Object error, final StackTrace? stackTrace) async { +Future catchFatalError( + final Object error, final StackTrace? stackTrace) async { final PackageInfo packageInfo = await PackageInfo.fromPlatform(); runApp(ErrorHandlerApp( diff --git a/lib/utils/new_wallet/action.dart b/lib/utils/new_wallet/action.dart new file mode 100644 index 0000000..07acd92 --- /dev/null +++ b/lib/utils/new_wallet/action.dart @@ -0,0 +1,15 @@ +import 'package:cupcake/coins/types.dart'; +import 'package:flutter/widgets.dart'; + +class NewWalletAction { + NewWalletAction({ + required this.type, + required this.function, + required this.text, + required this.backgroundColor, + }); + final NewWalletActionType type; + final VoidCallback? function; + final Widget text; + final Color backgroundColor; +} diff --git a/lib/utils/new_wallet/info_page.dart b/lib/utils/new_wallet/info_page.dart new file mode 100644 index 0000000..55e0dcf --- /dev/null +++ b/lib/utils/new_wallet/info_page.dart @@ -0,0 +1,102 @@ +import 'package:cupcake/coins/types.dart'; +import 'package:cupcake/gen/assets.gen.dart'; +import 'package:cupcake/l10n/app_localizations.dart'; +import 'package:cupcake/utils/new_wallet/action.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:lottie/lottie.dart'; +import 'package:share_plus/share_plus.dart'; + +class NewWalletInfoPage { + static NewWalletInfoPage preShowSeedPage(final AppLocalizations L) => + NewWalletInfoPage( + topText: L.important, + topAction: null, + topActionText: null, + lottieAnimation: Assets.shield.lottie(), + actions: [ + NewWalletAction( + type: NewWalletActionType.nextPage, + function: null, + text: Text( + L.understand_show_seed, + style: const TextStyle(color: Colors.white), + ), + backgroundColor: Colors.blue, + ), + ], + texts: [ + Text( + L.important_seed_backup_info(L.seed_length_16_word), + textAlign: TextAlign.center, + ), + ], + ); + + static NewWalletInfoPage writeDownNotice( + final AppLocalizations L, { + required final Future Function()? nextCallback, + required final String text, + required final String title, + }) => + NewWalletInfoPage( + topText: L.seed, + topAction: nextCallback, + topActionText: Text(L.next), + lottieAnimation: Assets.shield.lottie(), + actions: [ + NewWalletAction( + type: NewWalletActionType.function, + function: () { + Share.share(text); + }, + text: Text( + L.save, + style: const TextStyle(color: Colors.white), + ), + backgroundColor: Colors.green, + ), + NewWalletAction( + type: NewWalletActionType.function, + function: () async { + await Clipboard.setData(ClipboardData(text: text)); + }, + text: Text( + L.copy, + style: const TextStyle(color: Colors.white), + ), + backgroundColor: Colors.blue, + ), + ], + texts: [ + Text( + title, + style: const TextStyle( + fontSize: 26, fontWeight: FontWeight.w500, color: Colors.white), + textAlign: TextAlign.center, + ), + Text( + "$text\n\n\n\n${L.write_down_notice}", + textAlign: TextAlign.center, + ), + ], + ); + + NewWalletInfoPage({ + required this.topText, + required this.topAction, + required this.topActionText, + required this.lottieAnimation, + required this.actions, + required this.texts, + }); + + final String topText; + final VoidCallback? topAction; + final Widget? topActionText; + + final LottieBuilder? lottieAnimation; + final List actions; + + List texts; +} diff --git a/lib/view_model/abstract.dart b/lib/view_model/abstract.dart index 9426727..5271e2a 100644 --- a/lib/view_model/abstract.dart +++ b/lib/view_model/abstract.dart @@ -40,9 +40,7 @@ class ViewModel { void markNeedsBuild() { if (c == null) { - // throw Exception("c is null, did you forget to register(context)?"); - print("aaa"); - return; + throw Exception("c is null, did you forget to register(context)?"); } (c as Element).markNeedsBuild(); } diff --git a/lib/view_model/barcode_scanner_view_model.dart b/lib/view_model/barcode_scanner_view_model.dart index f859f70..e3e6cfa 100644 --- a/lib/view_model/barcode_scanner_view_model.dart +++ b/lib/view_model/barcode_scanner_view_model.dart @@ -4,7 +4,6 @@ import 'package:cupcake/utils/urqr.dart'; import 'package:cupcake/view_model/abstract.dart'; import 'package:cupcake/views/widgets/barcode_scanner/urqr_progress.dart'; import 'package:fast_scanner/fast_scanner.dart'; -import 'package:flutter/cupertino.dart'; part 'barcode_scanner_view_model.g.dart'; @@ -23,7 +22,7 @@ class BarcodeScannerViewModel extends ViewModel { @RebuildOnChange() List $urCodes = []; - get ur => URQRData.parse(urCodes); + URQRData get ur => URQRData.parse(urCodes); final CoinWallet wallet; @@ -41,8 +40,7 @@ class BarcodeScannerViewModel extends ViewModel { await wallet.handleUR(c!, ur); } - void handleBarcode( - final BuildContext context, final BarcodeCapture barcodes) async { + Future handleBarcode(final BarcodeCapture barcodes) async { for (final barcode in barcodes.barcodes) { if (barcode.rawValue!.startsWith("ur:")) { print("handleUR: ${ur.progress} : $popped"); @@ -57,7 +55,7 @@ class BarcodeScannerViewModel extends ViewModel { } } if (urCodes.isNotEmpty) return; - if (!context.mounted) return; + if (!mounted) return; barcode = barcodes.barcodes.firstOrNull; if (barcode != null && popped != true) { popped = true; diff --git a/lib/view_model/create_wallet_view_model.dart b/lib/view_model/create_wallet_view_model.dart index 6d9e8db..0360de2 100644 --- a/lib/view_model/create_wallet_view_model.dart +++ b/lib/view_model/create_wallet_view_model.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:cupcake/coins/abstract/coin.dart'; +import 'package:cupcake/coins/abstract/wallet.dart'; import 'package:cupcake/coins/list.dart'; import 'package:cupcake/coins/types.dart'; import 'package:cupcake/dev/generate_rebuild.dart'; @@ -12,15 +13,12 @@ import 'package:cupcake/utils/form/plain_value_outcome.dart'; import 'package:cupcake/utils/form/single_choice_form_element.dart'; import 'package:cupcake/utils/form/string_form_element.dart'; import 'package:cupcake/utils/form/validators.dart'; +import 'package:cupcake/utils/new_wallet/info_page.dart'; import 'package:cupcake/utils/null_if_empty.dart'; import 'package:cupcake/view_model/abstract.dart'; -import 'package:cupcake/view_model/new_wallet_info_view_model.dart'; import 'package:cupcake/views/new_wallet_info.dart'; -import 'package:cupcake/gen/assets.gen.dart'; import 'package:cupcake/views/wallet_home.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:share_plus/share_plus.dart'; +import 'package:cupcake/views/widgets/form_builder.dart'; part 'create_wallet_view_model.g.dart'; @@ -217,6 +215,13 @@ class CreateWalletViewModel extends ViewModel { secretViewKey, ]; + @ThrowOnUI(message: "Failed to complete setup") + Future $completeSetup(final CoinWallet cw) async { + CupcakeConfig.instance.initialSetupComplete = true; + CupcakeConfig.instance.save(); + await WalletHome(coinWallet: cw).push(c!); + } + @ThrowOnUI(L: 'create_wallet') Future $createWallet() async { if (selectedCoin == null) throw Exception("selectedCoin is null"); @@ -236,125 +241,20 @@ class CreateWalletViewModel extends ViewModel { ); final List pages = [ - NewWalletInfoPage( - topText: L.important, - topAction: null, - topActionText: null, - lottieAnimation: Assets.shield.lottie(), - actions: [ - NewWalletAction( - type: NewWalletActionType.nextPage, - function: null, - text: Text( - L.understand_show_seed, - style: const TextStyle(color: Colors.white), - ), - backgroundColor: Colors.blue, - ), - ], - texts: [ - Text( - L.important_seed_backup_info(L.seed_length_16_word), - textAlign: TextAlign.center, - ), - ], - ), - NewWalletInfoPage( - topText: L.seed, - topAction: seedOffset.ctrl.text.isNotEmpty - ? null - : () { - CupcakeConfig.instance.initialSetupComplete = true; - CupcakeConfig.instance.save(); - WalletHome(coinWallet: cw).push(c!); - }, - topActionText: Text(L.next), - lottieAnimation: Assets.shield.lottie(), - actions: [ - NewWalletAction( - type: NewWalletActionType.function, - function: () { - Share.share(cw.seed); - }, - text: Text( - L.save, - style: const TextStyle(color: Colors.white), - ), - backgroundColor: Colors.green, - ), - NewWalletAction( - type: NewWalletActionType.function, - function: () { - Clipboard.setData(ClipboardData(text: cw.seed)); - }, - text: Text( - L.copy, - style: const TextStyle(color: Colors.white), - ), - backgroundColor: Colors.blue, - ), - ], - texts: [ - Text( - cw.walletName, - style: const TextStyle( - fontSize: 26, fontWeight: FontWeight.w500, color: Colors.white), - textAlign: TextAlign.center, - ), - Text( - "${cw.seed}\n\n\n\n${L.write_down_notice}", - textAlign: TextAlign.center, - ), - ], + NewWalletInfoPage.preShowSeedPage(L), + NewWalletInfoPage.writeDownNotice( + L, + nextCallback: + seedOffset.ctrl.text.isNotEmpty ? null : () => completeSetup(cw), + text: seed.ctrl.text, + title: L.seed, ), if (seedOffset.ctrl.text.isNotEmpty) - NewWalletInfoPage( - topText: L.wallet_passphrase, - topAction: () { - CupcakeConfig.instance.initialSetupComplete = true; - CupcakeConfig.instance.save(); - WalletHome(coinWallet: cw).push(c!); - }, - topActionText: Text(L.next), - lottieAnimation: Assets.shield.lottie(), - actions: [ - NewWalletAction( - type: NewWalletActionType.function, - function: () { - Share.share(cw.seed); - }, - text: Text( - L.save, - style: const TextStyle(color: Colors.white), - ), - backgroundColor: Colors.green, - ), - NewWalletAction( - type: NewWalletActionType.function, - function: () { - Clipboard.setData(ClipboardData(text: cw.seed)); - }, - text: Text( - L.copy, - style: const TextStyle(color: Colors.white), - ), - backgroundColor: Colors.blue, - ), - ], - texts: [ - Text( - cw.walletName, - style: const TextStyle( - fontSize: 26, - fontWeight: FontWeight.w500, - color: Colors.white), - textAlign: TextAlign.center, - ), - Text( - "${seedOffset.ctrl.text}\n\n\n\n${L.write_down_notice}", - textAlign: TextAlign.center, - ), - ], + NewWalletInfoPage.writeDownNotice( + L, + nextCallback: () => completeSetup(cw), + text: seed.ctrl.text, + title: L.wallet_passphrase, ), ]; if (!mounted) { @@ -369,7 +269,18 @@ class CreateWalletViewModel extends ViewModel { } } - void titleUpdate(final String? suggestedTitle) async { + FormBuilder get formBuilder => FormBuilder( + formElements: currentForm ?? [], + scaffoldContext: c!, + rebuild: (final bool val) { + isPinSet = val; + }, + isPinSet: isPinSet, + showExtra: showExtra, + onLabelChange: titleUpdate, + ); + + Future titleUpdate(final String? suggestedTitle) async { await Future.delayed(Duration.zero); // don't do it on build(); screenName = suggestedTitle ?? screenNameOriginal; markNeedsBuild(); diff --git a/lib/view_model/home_screen_view_model.dart b/lib/view_model/home_screen_view_model.dart index cf9f390..982b916 100644 --- a/lib/view_model/home_screen_view_model.dart +++ b/lib/view_model/home_screen_view_model.dart @@ -12,6 +12,7 @@ class HomeScreenViewModel extends ViewModel { @override String get screenName => L.select_wallet; + final bool openLastWallet; final String? lastOpenedWallet; diff --git a/lib/view_model/new_wallet_info_view_model.dart b/lib/view_model/new_wallet_info_view_model.dart index 4f0b609..1ce8443 100644 --- a/lib/view_model/new_wallet_info_view_model.dart +++ b/lib/view_model/new_wallet_info_view_model.dart @@ -1,48 +1,9 @@ import 'package:cupcake/dev/generate_rebuild.dart'; +import 'package:cupcake/utils/new_wallet/info_page.dart'; import 'package:cupcake/view_model/abstract.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:lottie/lottie.dart'; part 'new_wallet_info_view_model.g.dart'; -enum NewWalletActionType { - nextPage, - function, -} - -class NewWalletAction { - NewWalletAction({ - required this.type, - required this.function, - required this.text, - required this.backgroundColor, - }); - final NewWalletActionType type; - final VoidCallback? function; - final Widget text; - final Color backgroundColor; -} - -class NewWalletInfoPage { - NewWalletInfoPage({ - required this.topText, - required this.topAction, - required this.topActionText, - required this.lottieAnimation, - required this.actions, - required this.texts, - }); - - final String topText; - final VoidCallback? topAction; - final Widget? topActionText; - - final LottieBuilder? lottieAnimation; - final List actions; - - List texts; -} - @GenerateRebuild() class NewWalletInfoViewModel extends ViewModel { NewWalletInfoViewModel(this.pages); diff --git a/lib/view_model/security_backup_view_model.dart b/lib/view_model/security_backup_view_model.dart index ace84e2..45ad434 100644 --- a/lib/view_model/security_backup_view_model.dart +++ b/lib/view_model/security_backup_view_model.dart @@ -46,6 +46,4 @@ class SecurityBackupViewModel extends ViewModel { ]; CoinWallet wallet; - - void titleUpdate(final String? suggestedTitle) {} } diff --git a/lib/view_model/unconfirmed_transaction_view_model.dart b/lib/view_model/unconfirmed_transaction_view_model.dart index 4d8cda0..622eeb4 100644 --- a/lib/view_model/unconfirmed_transaction_view_model.dart +++ b/lib/view_model/unconfirmed_transaction_view_model.dart @@ -3,23 +3,34 @@ import 'dart:async'; import 'package:cupcake/coins/abstract/address.dart'; import 'package:cupcake/coins/abstract/wallet.dart'; import 'package:cupcake/coins/abstract/amount.dart'; +import 'package:cupcake/dev/generate_rebuild.dart'; import 'package:cupcake/view_model/abstract.dart'; +part 'unconfirmed_transaction_view_model.g.dart'; + +@GenerateRebuild() class UnconfirmedTransactionViewModel extends ViewModel { UnconfirmedTransactionViewModel( {required this.wallet, required this.fee, required this.destMap, - required this.confirmCallback, - required this.cancelCallback}); + required final FutureOr Function() confirmCallback, + required final FutureOr Function() cancelCallback}) + : _confirmCallback = confirmCallback, + _cancelCallback = cancelCallback; final CoinWallet wallet; @override late String screenName = wallet.coin.strings.nameFull; - final FutureOr Function() confirmCallback; - final FutureOr Function() cancelCallback; + final FutureOr Function() _confirmCallback; + final FutureOr Function() _cancelCallback; + + @ThrowOnUI(message: "Failed to confirm") + Future $confirmCallback() async => await _confirmCallback(); + @ThrowOnUI(message: "Failed to cancel") + Future $cancelCallback() async => await _cancelCallback(); final Amount fee; final Map destMap; diff --git a/lib/views/barcode_scanner.dart b/lib/views/barcode_scanner.dart index 88a479a..e3ab80b 100644 --- a/lib/views/barcode_scanner.dart +++ b/lib/views/barcode_scanner.dart @@ -16,12 +16,10 @@ class BarcodeScanner extends AbstractView { @override Widget? body(final BuildContext context) { - viewModel.register(context); return Stack( children: [ MobileScanner( - onDetect: (final BarcodeCapture bc) => - viewModel.handleBarcode(context, bc), + onDetect: (final BarcodeCapture bc) => viewModel.handleBarcode(bc), controller: viewModel.mobileScannerCtrl, ), if (viewModel.ur.inputs.isNotEmpty) diff --git a/lib/views/create_wallet.dart b/lib/views/create_wallet.dart index 84352a7..1040887 100644 --- a/lib/views/create_wallet.dart +++ b/lib/views/create_wallet.dart @@ -1,11 +1,8 @@ import 'package:cupcake/coins/types.dart'; import 'package:cupcake/gen/assets.gen.dart'; -import 'package:cupcake/utils/config.dart'; -import 'package:cupcake/utils/form/abstract_form_element.dart'; import 'package:cupcake/view_model/create_wallet_view_model.dart'; import 'package:cupcake/views/abstract.dart'; -import 'package:cupcake/views/initial_setup_screen.dart'; -import 'package:cupcake/views/widgets/form_builder.dart'; +import 'package:cupcake/views/widgets/buttons/long_primary.dart'; import 'package:flutter/material.dart'; class CreateWallet extends AbstractView { @@ -20,57 +17,55 @@ class CreateWallet extends AbstractView { @override final CreateWalletViewModel viewModel; - @override - Widget? body(final BuildContext context) { - if (viewModel.selectedCoin == null) { - return ListView.builder( - itemCount: viewModel.coins.length, + Widget _selectCoin(final BuildContext context) { + return ListView.builder( + itemCount: viewModel.coins.length, + itemBuilder: (final BuildContext context, final int index) { + return InkWell( + onTap: () { + viewModel.selectedCoin = viewModel.coins[index]; + }, + child: Card( + child: ListTile( + title: Text(viewModel.coins[index].strings.nameFull), + ), + ), + ); + }, + ); + } + + Widget _createMethod(final BuildContext context) { + return SizedBox( + height: double.maxFinite, + child: ListView.builder( + itemCount: viewModel.createMethods.keys.length, itemBuilder: (final BuildContext context, final int index) { + final key = viewModel.createMethods.keys.elementAt(index); + final value = viewModel.createMethods[key]; return InkWell( onTap: () { - viewModel.selectedCoin = viewModel.coins[index]; + viewModel.currentForm = value; }, child: Card( child: ListTile( - title: Text(viewModel.coins[index].strings.nameFull), + title: Text(key), ), ), ); }, - ); + ), + ); + } + + @override + Widget? body(final BuildContext context) { + if (viewModel.selectedCoin == null) { + return _selectCoin(context); } if (viewModel.currentForm == null) { - return SizedBox( - height: double.maxFinite, - child: ListView.builder( - itemCount: viewModel.createMethods.keys.length, - itemBuilder: (final BuildContext context, final int index) { - final key = viewModel.createMethods.keys.elementAt(index); - final value = viewModel.createMethods[key]; - return InkWell( - onTap: () { - viewModel.currentForm = value; - }, - child: Card( - child: ListTile( - title: Text(key), - ), - ), - ); - }, - ), - ); + return _createMethod(context); } - formBuilder = FormBuilder( - formElements: viewModel.currentForm ?? [], - scaffoldContext: context, - rebuild: (final bool val) { - viewModel.isPinSet = val; - }, - isPinSet: viewModel.isPinSet, - showExtra: viewModel.showExtra, - onLabelChange: viewModel.titleUpdate, - ); return Column( children: [ if (viewModel.isPinSet) @@ -80,7 +75,7 @@ class CreateWallet extends AbstractView { ), Padding( padding: const EdgeInsets.symmetric(horizontal: 24), - child: formBuilder, + child: viewModel.formBuilder, ), ], ); @@ -88,50 +83,30 @@ class CreateWallet extends AbstractView { @override Widget? bottomNavigationBar(final BuildContext context) { - if (viewModel.isPinSet) { - return SafeArea( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ + if (!viewModel.isPinSet) return null; + return SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + LongPrimaryButton( + text: L.next, + icon: null, + onPressed: viewModel.createWallet, + backgroundColor: const WidgetStatePropertyAll(Colors.green), + textColor: Colors.white, + ), + if (viewModel.hasAdvancedOptions) LongPrimaryButton( - text: L.next, + text: L.advanced_options, icon: null, - onPressed: () => _next(), - backgroundColor: const WidgetStatePropertyAll(Colors.green), - textColor: Colors.white, + onPressed: () { + viewModel.showExtra = true; + }, + backgroundColor: const WidgetStatePropertyAll(Colors.transparent), ), - if (viewModel.hasAdvancedOptions) - LongPrimaryButton( - text: L.advanced_options, - icon: null, - onPressed: () { - viewModel.showExtra = true; - }, - backgroundColor: const WidgetStatePropertyAll(Colors.transparent), - ), - const SizedBox(height: 16), - ], - )); - } - return null; - } - - void _next() async { - await viewModel.createWallet(); - } - - FormBuilder? formBuilder; - - bool isFormBad(final List form) { - for (final element in form) { - if (!element.isOk) { - if (CupcakeConfig.instance.debug) { - print("${element.label} is not valid: "); - } - return true; - } - } - return false; + const SizedBox(height: 16), + ], + )); } @override diff --git a/lib/views/home_screen.dart b/lib/views/home_screen.dart index 8139d42..1de01d9 100644 --- a/lib/views/home_screen.dart +++ b/lib/views/home_screen.dart @@ -2,7 +2,8 @@ import 'package:cupcake/coins/abstract/wallet_info.dart'; import 'package:cupcake/coins/types.dart'; import 'package:cupcake/view_model/home_screen_view_model.dart'; import 'package:cupcake/views/abstract.dart'; -import 'package:cupcake/views/initial_setup_screen.dart'; +import 'package:cupcake/views/widgets/buttons/long_primary.dart'; +import 'package:cupcake/views/widgets/buttons/long_secondary.dart'; import 'package:flutter/material.dart'; import 'package:path/path.dart' as p; @@ -56,40 +57,43 @@ class HomeScreen extends AbstractView { itemBuilder: (final BuildContext context, final int index) { final bool isOpen = (wallets.data![index].walletName) .contains(viewModel.lastOpenedWallet ?? ""); - return Card( - child: IntrinsicHeight( - child: Row( - children: [ - Container( - width: 3, - color: isOpen ? Colors.blue : Colors.transparent, - height: double.infinity, - ), - Expanded( - child: ListTile( - onTap: () { - wallets.data![index].openUI(context); - }, - leading: SizedBox( - width: 32, - child: wallets.data![index].coin.strings.svg, - ), - trailing: IconButton( - icon: const Icon(Icons.edit_rounded), - onPressed: () async { - await viewModel.renameWallet(wallets.data![index]); - }, - ), - title: Text( - p.basename(wallets.data![index].walletName), - ), - ), - ), - ], + return singleWalletWidget(context, isOpen, wallets.data![index]); + }); + } + + Card singleWalletWidget(final BuildContext context, final bool isOpen, + final CoinWalletInfo wallet) { + return Card( + child: IntrinsicHeight( + child: Row( + children: [ + Container( + width: 3, + color: isOpen ? Colors.blue : Colors.transparent, + height: double.infinity, + ), + Expanded( + child: ListTile( + onTap: () { + wallet.openUI(context); + }, + leading: SizedBox( + width: 32, + child: wallet.coin.strings.svg, + ), + trailing: IconButton( + icon: const Icon(Icons.edit_rounded), + onPressed: () async => await viewModel.renameWallet(wallet), + ), + title: Text( + p.basename(wallet.walletName), + ), ), ), - ); - }); + ], + ), + ), + ); } @override diff --git a/lib/views/initial_setup_screen.dart b/lib/views/initial_setup_screen.dart index 90185a7..241f634 100644 --- a/lib/views/initial_setup_screen.dart +++ b/lib/views/initial_setup_screen.dart @@ -1,9 +1,10 @@ import 'package:cupcake/coins/types.dart'; -import 'package:cupcake/themes/base_theme.dart'; import 'package:cupcake/view_model/initial_setup_view_model.dart'; import 'package:cupcake/views/abstract.dart'; import 'package:cupcake/views/create_wallet.dart'; import 'package:cupcake/gen/assets.gen.dart'; +import 'package:cupcake/views/widgets/buttons/long_primary.dart'; +import 'package:cupcake/views/widgets/buttons/long_secondary.dart'; import 'package:flutter/material.dart'; class InitialSetupScreen extends AbstractView { @@ -15,127 +16,53 @@ class InitialSetupScreen extends AbstractView { @override Widget? body(final BuildContext context) { return SafeArea( - minimum: const EdgeInsets.only(bottom: 16), - child: Column( - children: [ - Padding( - padding: - const EdgeInsets.symmetric(horizontal: 64.0, vertical: 24), - child: Assets.cakeLanding.lottie()), - Text(L.welcome_to, - style: Theme.of(context).textTheme.displaySmall?.copyWith( - fontSize: 18, - fontWeight: FontWeight.w500, - )), - const SizedBox(height: 8), - Text( - "Cupcake", - style: Theme.of(context).textTheme.displayLarge?.copyWith( - fontSize: 36, - fontWeight: FontWeight.bold, - ), - ), - //style: Theme.of(context).textTheme.displaySmall, - const SizedBox(height: 8), - Text( - L.cupcake_slogan, + minimum: const EdgeInsets.only(bottom: 16), + child: Column( + children: [ + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 64.0, vertical: 24), + child: Assets.cakeLanding.lottie()), + Text(L.welcome_to, style: Theme.of(context).textTheme.displaySmall?.copyWith( - fontSize: 16, + fontSize: 18, fontWeight: FontWeight.w500, - ), - ), - const Spacer(), - const SizedBox(height: 8), - LongSecondaryButton( - text: L.create_new_wallet, - icon: Icons.add, - onPressed: () => CreateWallet( - createMethod: CreateMethod.create, - needsPasswordConfirm: true, - ).push(context), - ), - LongPrimaryButton( - text: L.restore_wallet, - icon: Icons.restore, - onPressed: () => CreateWallet( - createMethod: CreateMethod.restore, - needsPasswordConfirm: true, - ).push(context), - ), - ], - )); - } -} - -class LongSecondaryButton extends LongPrimaryButton { - const LongSecondaryButton( - {super.key, - required super.text, - required super.icon, - required super.onPressed}); - - @override - WidgetStateProperty? get backgroundColor => - const WidgetStatePropertyAll(Colors.white); - - @override - Color get textColor => BaseTheme.onBackgroundColor; -} - -class LongPrimaryButton extends StatelessWidget { - const LongPrimaryButton({ - super.key, - this.padding = const EdgeInsets.only(left: 24, right: 24, bottom: 8), - this.backgroundColor, - this.textColor = Colors.white, - this.text = "", - this.textWidget, - required this.icon, - required this.onPressed, - this.width = double.maxFinite, - }); - - final EdgeInsets padding; - final WidgetStateProperty? backgroundColor; - final Color textColor; - - final String text; - final Widget? textWidget; - final IconData? icon; - final VoidCallback? onPressed; - final double? width; - @override - Widget build(final BuildContext context) { - return Padding( - padding: padding, - child: SizedBox( - height: 52, - width: width, - child: ElevatedButton.icon( - style: Theme.of(context).elevatedButtonTheme.style?.copyWith( - backgroundColor: backgroundColor, - elevation: const WidgetStatePropertyAll(0), - side: const WidgetStatePropertyAll( - BorderSide( - width: 0, - color: Colors.transparent, - ), - ), - ), - onPressed: onPressed, - label: textWidget != null - ? textWidget! - : Text( - text, - style: TextStyle(color: textColor), + )), + const SizedBox(height: 8), + Text( + "Cupcake", + style: Theme.of(context).textTheme.displayLarge?.copyWith( + fontSize: 36, + fontWeight: FontWeight.bold, ), - icon: icon == null - ? Container() - : Icon( - icon, - color: textColor, + ), + const SizedBox(height: 8), + Text( + L.cupcake_slogan, + style: Theme.of(context).textTheme.displaySmall?.copyWith( + fontSize: 16, + fontWeight: FontWeight.w500, ), - ), + ), + const Spacer(), + const SizedBox(height: 8), + LongSecondaryButton( + text: L.create_new_wallet, + icon: Icons.add, + onPressed: () => CreateWallet( + createMethod: CreateMethod.create, + needsPasswordConfirm: true, + ).push(context), + ), + LongPrimaryButton( + text: L.restore_wallet, + icon: Icons.restore, + onPressed: () => CreateWallet( + createMethod: CreateMethod.restore, + needsPasswordConfirm: true, + ).push(context), + ), + ], ), ); } diff --git a/lib/views/new_wallet_info.dart b/lib/views/new_wallet_info.dart index bbed504..5ae8a9a 100644 --- a/lib/views/new_wallet_info.dart +++ b/lib/views/new_wallet_info.dart @@ -1,6 +1,8 @@ +import 'package:cupcake/coins/types.dart'; +import 'package:cupcake/utils/new_wallet/info_page.dart'; import 'package:cupcake/view_model/new_wallet_info_view_model.dart'; import 'package:cupcake/views/abstract.dart'; -import 'package:cupcake/views/initial_setup_screen.dart'; +import 'package:cupcake/views/widgets/buttons/long_primary.dart'; import 'package:flutter/material.dart'; class NewWalletInfoScreen extends AbstractView { diff --git a/lib/views/security_backup.dart b/lib/views/security_backup.dart index b347884..d750dcb 100644 --- a/lib/views/security_backup.dart +++ b/lib/views/security_backup.dart @@ -4,7 +4,7 @@ import 'package:cupcake/coins/types.dart'; import 'package:cupcake/utils/alerts/widget.dart'; import 'package:cupcake/view_model/security_backup_view_model.dart'; import 'package:cupcake/views/abstract.dart'; -import 'package:cupcake/views/initial_setup_screen.dart'; +import 'package:cupcake/views/widgets/buttons/long_primary.dart'; import 'package:cupcake/views/widgets/form_builder.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -32,7 +32,7 @@ class SecurityBackup extends AbstractView { scaffoldContext: context, isPinSet: !viewModel.isLocked, showExtra: true, - onLabelChange: viewModel.titleUpdate, + onLabelChange: null, ); } final details = viewModel.wallet.seedDetails(L); @@ -43,35 +43,40 @@ class SecurityBackup extends AbstractView { return ListView.builder( itemCount: snapshot.data!.length, itemBuilder: (final BuildContext context, final int index) { - final d = snapshot.data![index]; - switch (d.type) { - case WalletSeedDetailType.text: - return ListTile( - onTap: () { - _copy(context, d.value, d.name); - }, - title: Text(d.name, - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - fontSize: 14, fontWeight: FontWeight.w700)), - subtitle: Text( - d.value, - style: const TextStyle(color: Colors.white), - ), - ); - case WalletSeedDetailType.qr: - return LongPrimaryButton( - text: d.name, - icon: Icons.qr_code_rounded, - onPressed: () async { - await _showQrCode(context, d, color: Colors.transparent); - }, - ); - } + return _buildElement(context, snapshot.data![index]); }, ); }); } + Widget _buildElement(final BuildContext context, final WalletSeedDetail d) { + switch (d.type) { + case WalletSeedDetailType.text: + return ListTile( + onTap: () { + _copy(context, d.value, d.name); + }, + title: Text(d.name, + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(fontSize: 14, fontWeight: FontWeight.w700)), + subtitle: Text( + d.value, + style: const TextStyle(color: Colors.white), + ), + ); + case WalletSeedDetailType.qr: + return LongPrimaryButton( + text: d.name, + icon: Icons.qr_code_rounded, + onPressed: () async { + await _showQrCode(context, d, color: Colors.transparent); + }, + ); + } + } + Future _showQrCode( final BuildContext context, final WalletSeedDetail d, { diff --git a/lib/views/unconfirmed_transaction.dart b/lib/views/unconfirmed_transaction.dart index 11530c4..8eb0200 100644 --- a/lib/views/unconfirmed_transaction.dart +++ b/lib/views/unconfirmed_transaction.dart @@ -54,18 +54,17 @@ class UnconfirmedTransactionView extends AbstractView { label: L.cancel, ), BottomNavigationBarItem( - icon: const Icon(Icons.check_circle, color: Colors.green), + icon: const Icon( + Icons.check_circle, + color: Colors.green, + ), label: L.confirm), ], onTap: (final int index) async { if (index == 0) { - viewModel.cancelCallback(); - if (!context.mounted) return; - Navigator.of(context).pop(); + await viewModel.cancelCallback(); } else { await viewModel.confirmCallback(); - if (!context.mounted) return; - Navigator.of(context).pop(); } }, ); diff --git a/lib/views/wallet_edit.dart b/lib/views/wallet_edit.dart index c06063a..99f165a 100644 --- a/lib/views/wallet_edit.dart +++ b/lib/views/wallet_edit.dart @@ -1,7 +1,7 @@ import 'package:cupcake/coins/abstract/wallet_info.dart'; import 'package:cupcake/view_model/wallet_edit_view_model.dart'; import 'package:cupcake/views/abstract.dart'; -import 'package:cupcake/views/initial_setup_screen.dart'; +import 'package:cupcake/views/widgets/buttons/long_primary.dart'; import 'package:cupcake/views/widgets/form_builder.dart'; import 'package:flutter/material.dart'; @@ -31,21 +31,14 @@ class WalletEdit extends AbstractView { child: LongPrimaryButton( backgroundColor: const WidgetStatePropertyAll(Colors.red), icon: null, - onPressed: () async { - await viewModel.deleteWallet(); - }, + onPressed: viewModel.deleteWallet, text: "Delete", ), ), Expanded( child: LongPrimaryButton( icon: null, - onPressed: () async { - for (final element in viewModel.form) { - if (!element.isOk) continue; - } - await viewModel.renameWallet(); - }, + onPressed: () => viewModel.renameWallet(), text: "Rename", ), ), diff --git a/lib/views/wallet_home.dart b/lib/views/wallet_home.dart index bddd442..8a7cdef 100644 --- a/lib/views/wallet_home.dart +++ b/lib/views/wallet_home.dart @@ -67,77 +67,91 @@ class WalletHome extends AbstractView { return Column( children: [ const SizedBox(height: 40), - CakeCard( - child: Row( + _currencyInfo(context), + _actions(context), + ], + ); + } + + CakeCard _actions(final BuildContext context) { + return CakeCard( + firmPadding: const EdgeInsets.all(12), + child: Row( + children: [ + _actionReceive(context), + const SizedBox(width: 8), + _actionScan(context), + ], + ), + ); + } + + Expanded _actionScan(final BuildContext context) { + return Expanded( + child: SizedBox( + height: 64, + child: ElevatedButton.icon( + onPressed: () => + BarcodeScanner(wallet: viewModel.wallet).push(context), + icon: const Icon( + Icons.qr_code_rounded, + size: 35, + color: Colors.white, + ), + label: Text( + L.scan, + style: const TextStyle(color: Colors.white, fontSize: 18), + ), + ), + ), + ); + } + + Expanded _actionReceive(final BuildContext context) { + return Expanded( + child: SizedBox( + height: 64, + child: ElevatedButton.icon( + onPressed: () => Receive(coinWallet: viewModel.wallet).push(context), + icon: const Icon(Icons.call_received, size: 35, color: Colors.white), + label: Text( + L.receive, + style: const TextStyle(color: Colors.white, fontSize: 18), + ), + ), + ), + ); + } + + CakeCard _currencyInfo(final BuildContext context) { + return CakeCard( + child: Row( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - L.balance, - style: Theme.of(context) - .textTheme - .displaySmall! - .copyWith(fontSize: 12, fontWeight: FontWeight.w400), - ), - Text( - viewModel.balance, - style: Theme.of(context) - .textTheme - .bodyLarge! - .copyWith(fontSize: 24, fontWeight: FontWeight.w900), - ), - ], + Text( + L.balance, + style: Theme.of(context) + .textTheme + .displaySmall! + .copyWith(fontSize: 12, fontWeight: FontWeight.w400), ), - const Spacer(), - SizedBox.square( - dimension: 42, - child: viewModel.coin.strings.svg, + Text( + viewModel.balance, + style: Theme.of(context) + .textTheme + .bodyLarge! + .copyWith(fontSize: 24, fontWeight: FontWeight.w900), ), ], - )), - CakeCard( - firmPadding: const EdgeInsets.all(12), - child: Row( - children: [ - Expanded( - child: SizedBox( - height: 64, - child: ElevatedButton.icon( - onPressed: () => - Receive(coinWallet: viewModel.wallet).push(context), - icon: const Icon(Icons.call_received, - size: 35, color: Colors.white), - label: Text( - L.receive, - style: const TextStyle(color: Colors.white, fontSize: 18), - ), - ), - ), - ), - const SizedBox(width: 8), - Expanded( - child: SizedBox( - height: 64, - child: ElevatedButton.icon( - onPressed: () => - BarcodeScanner(wallet: viewModel.wallet).push(context), - icon: const Icon( - Icons.qr_code_rounded, - size: 35, - color: Colors.white, - ), - label: Text( - L.scan, - style: const TextStyle(color: Colors.white, fontSize: 18), - ), - ), - ), - ), - ], - ), + ), + const Spacer(), + SizedBox.square( + dimension: 42, + child: viewModel.coin.strings.svg, ), ], - ); + )); } } diff --git a/lib/views/widgets/buttons/long_primary.dart b/lib/views/widgets/buttons/long_primary.dart new file mode 100644 index 0000000..a8e6e27 --- /dev/null +++ b/lib/views/widgets/buttons/long_primary.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; + +class LongPrimaryButton extends StatelessWidget { + const LongPrimaryButton({ + super.key, + this.padding = const EdgeInsets.only(left: 24, right: 24, bottom: 8), + this.backgroundColor, + this.textColor = Colors.white, + this.text = "", + this.textWidget, + required this.icon, + required this.onPressed, + this.width = double.maxFinite, + }); + + final EdgeInsets padding; + final WidgetStateProperty? backgroundColor; + final Color textColor; + + final String text; + final Widget? textWidget; + final IconData? icon; + final VoidCallback? onPressed; + final double? width; + @override + Widget build(final BuildContext context) { + return Padding( + padding: padding, + child: SizedBox( + height: 52, + width: width, + child: ElevatedButton.icon( + style: Theme.of(context).elevatedButtonTheme.style?.copyWith( + backgroundColor: backgroundColor, + elevation: const WidgetStatePropertyAll(0), + side: const WidgetStatePropertyAll( + BorderSide( + width: 0, + color: Colors.transparent, + ), + ), + ), + onPressed: onPressed, + label: textWidget != null + ? textWidget! + : Text( + text, + style: TextStyle(color: textColor), + ), + icon: icon == null + ? Container() + : Icon( + icon, + color: textColor, + ), + ), + ), + ); + } +} diff --git a/lib/views/widgets/buttons/long_secondary.dart b/lib/views/widgets/buttons/long_secondary.dart new file mode 100644 index 0000000..94bd75f --- /dev/null +++ b/lib/views/widgets/buttons/long_secondary.dart @@ -0,0 +1,18 @@ +import 'package:cupcake/themes/base_theme.dart'; +import 'package:cupcake/views/widgets/buttons/long_primary.dart'; +import 'package:flutter/material.dart'; + +class LongSecondaryButton extends LongPrimaryButton { + const LongSecondaryButton( + {super.key, + required super.text, + required super.icon, + required super.onPressed}); + + @override + WidgetStateProperty? get backgroundColor => + const WidgetStatePropertyAll(Colors.white); + + @override + Color get textColor => BaseTheme.onBackgroundColor; +} diff --git a/lib/views/widgets/form_builder.dart b/lib/views/widgets/form_builder.dart index b48e8ab..c338ea5 100644 --- a/lib/views/widgets/form_builder.dart +++ b/lib/views/widgets/form_builder.dart @@ -8,7 +8,7 @@ import 'package:cupcake/utils/form/single_choice_form_element.dart'; import 'package:cupcake/utils/form/string_form_element.dart'; import 'package:cupcake/utils/random_name.dart'; import 'package:cupcake/utils/secure_storage.dart'; -import 'package:cupcake/views/initial_setup_screen.dart'; +import 'package:cupcake/views/widgets/buttons/long_primary.dart'; import 'package:cupcake/views/widgets/numerical_keyboard/main.dart'; import 'package:flutter/material.dart'; import 'package:local_auth/local_auth.dart'; @@ -27,7 +27,7 @@ class FormBuilder extends StatefulWidget { final List formElements; final BuildContext scaffoldContext; final void Function(bool isPinSet)? rebuild; - final void Function(String? suggestedTitle) onLabelChange; + final void Function(String? suggestedTitle)? onLabelChange; final bool isPinSet; final bool showExtra; @override @@ -43,7 +43,7 @@ class _FormBuilderState extends State { void _onLabelChange(final String? suggestedTitle) { if (suggestedTitle == lastSuggestedTitle) return; lastSuggestedTitle = suggestedTitle; - widget.onLabelChange(suggestedTitle); + widget.onLabelChange?.call(suggestedTitle); } void _pinSet(final bool val) { From 0edf18be811eed5ccfd98f12d988cf5c6090d9ce Mon Sep 17 00:00:00 2001 From: Czarek Nakamoto Date: Thu, 13 Feb 2025 12:07:55 +0100 Subject: [PATCH 10/13] refactor wallet creation bump flutter version sneak in GPL-3.0 relicense little tiny start on docs --- .env | 2 +- .fvmrc | 2 +- .vscode/settings.json | 2 +- LICENSE | 22 +- Makefile | 4 + README.md | 6 +- docs/README.md | 67 +++++ .../xcshareddata/xcschemes/Runner.xcscheme | 1 + lib/coins/abstract/coin.dart | 23 +- lib/coins/abstract/wallet.dart | 2 + lib/coins/abstract/wallet_creation.dart | 47 ++++ lib/coins/abstract/wallet_info.dart | 3 +- lib/coins/abstract/wallet_seed_detail.dart | 2 +- lib/coins/monero/coin.dart | 244 ++---------------- lib/coins/monero/creation/common.dart | 171 ++++++++++++ lib/coins/monero/creation/new_wallet.dart | 65 +++++ lib/coins/monero/creation/restore_keys.dart | 93 +++++++ lib/coins/monero/creation/restore_legacy.dart | 58 +++++ .../monero/creation/restore_polyseed.dart | 79 ++++++ lib/coins/monero/wallet.dart | 69 ++--- lib/dev/rebuild_generator.dart | 46 ++-- lib/main.dart | 15 +- lib/panic_handler.dart | 9 +- lib/themes/base_theme.dart | 3 +- .../flutter_secure_storage_value_outcome.dart | 15 +- lib/utils/form/pin_form_element.dart | 9 +- lib/utils/new_wallet/action.dart | 2 +- lib/utils/new_wallet/info_page.dart | 8 +- lib/utils/secure_storage.dart | 3 +- lib/{coins => utils}/types.dart | 1 - lib/utils/urqrdetails.dart | 3 +- lib/view_model/abstract.dart | 17 +- lib/view_model/create_wallet_view_model.dart | 170 ++++-------- lib/view_model/home_screen_view_model.dart | 5 +- lib/view_model/open_wallet_view_model.dart | 3 +- .../security_backup_view_model.dart | 3 +- lib/view_model/urqr_view_model.dart | 4 +- lib/view_model/wallet_edit_view_model.dart | 3 +- lib/views/abstract.dart | 3 + lib/views/barcode_scanner.dart | 5 +- lib/views/create_wallet.dart | 5 +- lib/views/home_screen.dart | 23 +- lib/views/initial_setup_screen.dart | 5 +- lib/views/new_wallet_info.dart | 11 +- lib/views/receive.dart | 3 +- lib/views/security_backup.dart | 2 +- lib/views/settings.dart | 12 +- lib/views/wallet_home.dart | 10 +- .../barcode_scanner/progress_painter.dart | 7 +- lib/views/widgets/buttons/long_secondary.dart | 8 +- lib/views/widgets/cake_card.dart | 3 +- lib/views/widgets/form_builder.dart | 22 +- .../widgets/numerical_keyboard/keyboard.dart | 3 +- .../widgets/numerical_keyboard/main.dart | 3 +- .../numerical_keyboard/single_key.dart | 3 +- .../settings/integer_config_element.dart | 3 +- .../widgets/settings/version_widget.dart | 4 +- lib/views/widgets/urqr.dart | 4 +- pubspec.lock | 140 +++++----- pubspec.yaml | 6 +- 60 files changed, 875 insertions(+), 691 deletions(-) create mode 100644 docs/README.md create mode 100644 lib/coins/abstract/wallet_creation.dart create mode 100644 lib/coins/monero/creation/common.dart create mode 100644 lib/coins/monero/creation/new_wallet.dart create mode 100644 lib/coins/monero/creation/restore_keys.dart create mode 100644 lib/coins/monero/creation/restore_legacy.dart create mode 100644 lib/coins/monero/creation/restore_polyseed.dart rename lib/{coins => utils}/types.dart (96%) diff --git a/.env b/.env index cea5220..6186846 100644 --- a/.env +++ b/.env @@ -1,2 +1,2 @@ -MONERO_C_TAG=v0.18.3.4-RC10 +MONERO_C_TAG=v0.18.3.4-RC12 COIN=monero diff --git a/.fvmrc b/.fvmrc index c2783c6..4cfa3d5 100644 --- a/.fvmrc +++ b/.fvmrc @@ -1,3 +1,3 @@ { - "flutter": "3.27.4" + "flutter": "3.29.0" } \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 926bd45..44aaa76 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { - "dart.flutterSdkPath": ".fvm/versions/3.27.4" + "dart.flutterSdkPath": ".fvm/versions/3.29.0" } \ No newline at end of file diff --git a/LICENSE b/LICENSE index 7e504ac..cced538 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,7 @@ -MIT License +Copyright (C) 2025 Cake Labs LLC -Copyright (c) 2024 Cake Labs LLC +This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +You should have received a copy of the GNU General Public License along with this program. If not, see . \ No newline at end of file diff --git a/Makefile b/Makefile index 6f39057..44ea810 100644 --- a/Makefile +++ b/Makefile @@ -36,5 +36,9 @@ cupcake_android_monero: dart run build_runner build --delete-conflicting-outputs flutter build apk --dart-define=COIN_MONERO=true +cupcake_ios_monero: + dart run build_runner build --delete-conflicting-outputs + flutter build ios --no-codesign --dart-define=COIN_MONERO=true + prepare_dev: ./.tooling/prepare_dev.sh \ No newline at end of file diff --git a/README.md b/README.md index d66fbb3..0f955d0 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,9 @@ To build: ```bash -$ make libs_android_build # or libs_android_download -$ make prepare_dev # load dev signing key -$ make cupcake_android_monero +$ make libs_android_build # or libs_android_download, libs_ios_build, libs_ios_download +$ make prepare_dev # load dev signing key (not required on iOS) +$ make cupcake_android_monero # or cupcake_ ``` ### Adding other coins diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..838bdf2 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,67 @@ +# Cupcake Notes and Documentation + +> This document is intended for developers. If you just want to build Cupcake, please see the top-level `README.md` and `Makefile`. + +## Project Structure + +Cupcake adheres to a standard project structure; currently, everything resides inside the `lib` directory. This setup may change in the future. + +## `lib/coins` + +This directory contains the abstract `coin` definition. To add a new currency to Cupcake, simply implement the abstract `Coin` class found here and update `lib/coins/list.dart` with the new class. The app will automatically pick it up, and a new entry will appear on the coin selection screen during creation or restoration. + +> **NOTE:** Currently, only one coin is supported. To ensure a smooth user experience, Cupcake will not prompt the user to select an option if there is only one available. + +## `lib/dev` + +Cupcake's state management is implemented in a very simple way using MVVM. Since the app is designed to work entirely offline, its state doesn't change often. To keep the code simple, I opted to create an in-house state management solution instead of using an existing one-size-fits-all package. While those packages are great, I believe they would be overkill for an app with a minimal UI and limited processing. + +This directory contains three code generation utilities that, while entirely optional, make my life much easier and the code much simpler. For example, instead of converting between `ObservableList` and `List` or wrapping widgets to notify about state changes, we simply trigger a rebuild. + +### `@GenerateRebuild()` + +Place this annotation around a class to ensure that all other generation annotations work correctly. + +### `@RebuildOnChange()` + +This annotation wraps a variable in a getter and setter that triggers a rebuild when the variable changes. + +```dart +@RebuildOnChange() +Barcode? $barcode; +``` + +You can then use `barcode` (or `viewModel.barcode`), and updating this value will rebuild the UI. + +### `@ThrowOnUI(message: "message...", L: "translation_key")` + +If you have a function that may throw an error—and that error should be presented to the user in a dismissible manner—use this annotation. It indicates that something went wrong, allowing the user to retry or correct the issue without restarting the entire action. + +```dart +@ThrowOnUI(message: "Error handling URQR scan") +Future $handleUR() async { + if (formInvalid) { + throw Exception("The form is invalid"); + } + await wallet.handleUR(c!, ur); +} +``` + +You can use either `message:` or `L:`. The `message:` text will be displayed in plain text, whereas `L:` will use a translation key. The error message will appear as a dismissible alert. + +If you do not use `@ThrowOnUI()`, you must wrap the function in a try-catch block. If an error is thrown without being caught, the app will display a panic handler screen that prevents further use. + +### `@ExposeRebuildableAccessors()` + +This annotation is somewhat special and is designed for the settings page. It exposes all setters and getters from a class instance, making it possible to access them as elements of the ViewModel. + +```dart +@ExposeRebuildableAccessors(extraCode: r'$config.save()') +CupcakeConfig get $config => CupcakeConfig.instance; +``` + +This approach lets you access settings, for example, using `viewModel.configBiometricEnabled`. It may be overkill, but I like it because it essentially removes almost all logic from the settings view model. + +## `lib/gen` + +The `lib/gen` directory uses `flutter_gen` to access assets. This method is preferable to using string-based paths in UI code, as it prevents accidental typos that might not be caught by the linter or at compile time. \ No newline at end of file diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 8e3ca5d..15cada4 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -59,6 +59,7 @@ ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" debugServiceExtension = "internal" + enableGPUValidationMode = "1" allowLocationSimulation = "YES"> diff --git a/lib/coins/abstract/coin.dart b/lib/coins/abstract/coin.dart index 0fd5dd1..25b1bbf 100644 --- a/lib/coins/abstract/coin.dart +++ b/lib/coins/abstract/coin.dart @@ -1,7 +1,8 @@ import 'package:cupcake/coins/abstract/strings.dart'; import 'package:cupcake/coins/abstract/wallet.dart'; +import 'package:cupcake/coins/abstract/wallet_creation.dart'; import 'package:cupcake/coins/abstract/wallet_info.dart'; -import 'package:cupcake/coins/types.dart'; +import 'package:cupcake/l10n/app_localizations.dart'; enum Coins { monero, unknown } @@ -14,21 +15,11 @@ abstract class Coin { Future> get coinWallets; - Future createNewWallet( - final String walletName, - final String walletPassword, { - final ProgressCallback? progressCallback, - required final bool? createWallet, - required final String? seed, - required final int? restoreHeight, - required final String? primaryAddress, - required final String? viewKey, - required final String? spendKey, - required final String? seedOffsetOrEncryption, - }); - bool isSeedSomewhatLegit(final String seed); - Future openWallet(final CoinWalletInfo walletInfo, - {required final String password}); + Future openWallet(final CoinWalletInfo walletInfo, {required final String password}); + + WalletCreation creationMethod(final AppLocalizations L); + + String getPathForWallet(final String walletName); } diff --git a/lib/coins/abstract/wallet.dart b/lib/coins/abstract/wallet.dart index 9c19f87..7c846b6 100644 --- a/lib/coins/abstract/wallet.dart +++ b/lib/coins/abstract/wallet.dart @@ -30,6 +30,8 @@ abstract class CoinWallet { String get seed; + String get passphrase; + String get primaryAddress; String get walletName; diff --git a/lib/coins/abstract/wallet_creation.dart b/lib/coins/abstract/wallet_creation.dart new file mode 100644 index 0000000..8dce710 --- /dev/null +++ b/lib/coins/abstract/wallet_creation.dart @@ -0,0 +1,47 @@ +// creation = any action that results in new wallet being created, be that restore or generate +// To unify the process of wallet creation we provide multiple "generators". +// There is one class that defines inputs (FormElement) +// And multiple WalletCreation classes +// When creating wallet heuristic are being applied to see which creation method will be used, +// it is entirely possible for one input of restore/creation form to have multiple outputs, + +import 'package:cupcake/coins/abstract/coin.dart'; +import 'package:cupcake/coins/abstract/wallet.dart'; +import 'package:cupcake/utils/types.dart'; +import 'package:cupcake/utils/form/abstract_form_element.dart'; + +abstract class WalletCreation { + Coin get coin => throw UnimplementedError(); + Future create( + final CreateMethod createMethod, + final String walletName, + final String walletPassword, + ); + Map> createMethods(final CreateMethod createMethod); + + // wipe function clears all details from WalletCreation form + void wipe(); +} + +// No need for CreationMethod to exist but it makes sure that we follow some internal structure +// of wallet generation. +abstract class CreationMethod { + CreationMethod(); + + Future create(); +} + +class CreationOutcome { + CreationOutcome({ + required this.success, + required this.method, + this.wallet, + this.message, + }) : + // We must provide a detailed feedback regarding why creation failed + assert((success == false && message != null && message.isNotEmpty) || success); + final bool success; + final CoinWallet? wallet; + final CreateMethod method; + final String? message; +} diff --git a/lib/coins/abstract/wallet_info.dart b/lib/coins/abstract/wallet_info.dart index 637dacc..d715fe7 100644 --- a/lib/coins/abstract/wallet_info.dart +++ b/lib/coins/abstract/wallet_info.dart @@ -18,8 +18,7 @@ abstract class CoinWalletInfo { bool exists(); - Future openWallet(final BuildContext context, - {required final String password}); + Future openWallet(final BuildContext context, {required final String password}); Map toJson() { return { diff --git a/lib/coins/abstract/wallet_seed_detail.dart b/lib/coins/abstract/wallet_seed_detail.dart index 142600a..76bf5da 100644 --- a/lib/coins/abstract/wallet_seed_detail.dart +++ b/lib/coins/abstract/wallet_seed_detail.dart @@ -1,4 +1,4 @@ -import 'package:cupcake/coins/types.dart'; +import 'package:cupcake/utils/types.dart'; class WalletSeedDetail { WalletSeedDetail({ diff --git a/lib/coins/monero/coin.dart b/lib/coins/monero/coin.dart index c138e2d..ab4672c 100644 --- a/lib/coins/monero/coin.dart +++ b/lib/coins/monero/coin.dart @@ -1,20 +1,19 @@ import 'dart:io'; import 'package:cupcake/coins/abstract/coin.dart'; -import 'package:cupcake/coins/abstract/exception.dart'; import 'package:cupcake/coins/abstract/strings.dart'; import 'package:cupcake/coins/abstract/wallet.dart'; +import 'package:cupcake/coins/abstract/wallet_creation.dart'; import 'package:cupcake/coins/abstract/wallet_info.dart'; -import 'package:cupcake/coins/monero/cache_keys.dart'; +import 'package:cupcake/coins/monero/creation/common.dart'; import 'package:cupcake/coins/monero/strings.dart'; import 'package:cupcake/coins/monero/wallet_info.dart'; import 'package:cupcake/coins/monero/wallet.dart'; -import 'package:cupcake/coins/types.dart'; +import 'package:cupcake/l10n/app_localizations.dart'; import 'package:cupcake/utils/config.dart'; import 'package:cupcake/utils/filesystem.dart'; import 'package:monero/monero.dart' as monero; import 'package:path/path.dart' as p; -import 'package:polyseed/polyseed.dart'; class Monero implements Coin { static List wPtrList = []; @@ -36,8 +35,7 @@ class Monero implements Coin { @override CoinStrings get strings => MoneroStrings(); - static final baseDir = - Directory(p.join(baseStoragePath, MoneroStrings().symbolLowercase)); + static final baseDir = Directory(p.join(baseStoragePath, MoneroStrings().symbolLowercase)); @override Future> get coinWallets { @@ -46,7 +44,7 @@ class Monero implements Coin { } // NOTE: We cannot use findWallets on iOS because for some reason it crashes // It works on other operating systems somewhat good. I'm not debugging that - // right now.s + // right now. // final wallets = monero.WalletManager_findWallets(wmPtr, path: baseDir.path); // final status = monero.WalletManager_errorString(wmPtr); // if (status != "") { @@ -66,226 +64,19 @@ class Monero implements Coin { return Future.value(retWallets); } - Future createMoneroWallet({ - required final ProgressCallback? progressCallback, - required final String walletPath, - required final String walletPassword, - required final String seedOffsetOrEncryption, - }) async { - progressCallback?.call(description: "Generating polyseed"); - final newSeed = monero.Wallet_createPolyseed(); - progressCallback?.call(description: "Creating wallet"); - final newWptr = monero.WalletManager_createWalletFromPolyseed( - wmPtr, - path: walletPath, - password: walletPassword, - mnemonic: newSeed, - seedOffset: seedOffsetOrEncryption, - newWallet: true, - restoreHeight: 0, - kdfRounds: 1, - ); - progressCallback?.call(description: "Checking status"); - final status = monero.Wallet_status(newWptr); - if (status != 0) { - final error = monero.Wallet_errorString(newWptr); - print("error: $error"); - throw CoinException( - error, - details: "unable to create wallet, createWalletFromPolyseed failed.", - ); - } - monero.Wallet_setCacheAttribute(newWptr, - key: MoneroCacheKeys.seedOffsetCacheKey, value: seedOffsetOrEncryption); - monero.Wallet_store(newWptr); - monero.Wallet_store(newWptr); - wPtrList.add(newWptr); - print("wallet created in: $walletPath"); - progressCallback?.call(description: "Wallet created"); - await Future.delayed(Duration.zero); - } - - Future createMoneroWalletPolyseed({ - required final ProgressCallback? progressCallback, - required final String walletPath, - required final String walletPassword, - required String seed, - required final String seedOffsetOrEncryption, - }) async { - progressCallback?.call(description: "Creating wallet"); - final lang = PolyseedLang.getByPhrase(seed); - const coin = PolyseedCoin.POLYSEED_MONERO; - final dartPolyseed = Polyseed.decode(seed, lang, coin); - var offset = seedOffsetOrEncryption; - if (dartPolyseed.isEncrypted) { - if (seedOffsetOrEncryption.isEmpty) { - throw CoinException("seed offset is empty, but polyseed is encrypted"); - } - dartPolyseed.crypt(seedOffsetOrEncryption); - seed = dartPolyseed.encode(lang, coin); - offset = ""; - } - final newWptr = monero.WalletManager_createWalletFromPolyseed( - wmPtr, - path: walletPath, - password: walletPassword, - mnemonic: seed, - seedOffset: offset, - newWallet: true, - restoreHeight: 0, - kdfRounds: 1, - ); - wPtrList.add(newWptr); - progressCallback?.call(description: "Checking status"); - final status = monero.Wallet_status(newWptr); - if (status != 0) { - final error = monero.Wallet_errorString(newWptr); - throw CoinException( - error, - details: "unable to create wallet, createWalletFromPolyseed failed.", - ); - } - monero.Wallet_setCacheAttribute(newWptr, - key: MoneroCacheKeys.seedOffsetCacheKey, value: seedOffsetOrEncryption); - monero.Wallet_store(newWptr); - monero.Wallet_store(newWptr); - progressCallback?.call(description: "Wallet created"); - } - - Future createMoneroWalletSeed({ - required final ProgressCallback? progressCallback, - required final String walletPath, - required final String walletPassword, - required final String seed, - required final String seedOffsetOrEncryption, - }) async { - progressCallback?.call(description: "Creating wallet"); - final newWptr = monero.WalletManager_recoveryWallet( - wmPtr, - path: walletPath, - password: walletPassword, - mnemonic: seed, - seedOffset: seedOffsetOrEncryption, - restoreHeight: 0, - kdfRounds: 1, - ); - wPtrList.add(newWptr); - progressCallback?.call(description: "Checking status"); - final status = monero.Wallet_status(newWptr); - if (status != 0) { - final error = monero.Wallet_errorString(newWptr); - throw CoinException( - error, - details: "unable to create wallet, recoveryWallet failed.", - ); - } - monero.Wallet_store(newWptr); - monero.Wallet_store(newWptr); - progressCallback?.call(description: "Wallet created"); - } - - Future createMoneroWalletKeys({ - required final ProgressCallback? progressCallback, - required final String walletPath, - required final String walletPassword, - required final String walletAddress, - required final String secretSpendKey, - required final String secretViewKey, - required final int restoreHeight, - }) async { - progressCallback?.call(description: "Creating wallet"); - final newWptr = monero.WalletManager_createWalletFromKeys(wmPtr, - path: walletPath, - password: walletPassword, - restoreHeight: restoreHeight, - addressString: walletAddress, - viewKeyString: secretViewKey, - spendKeyString: secretSpendKey); - progressCallback?.call(description: "Checking status"); - final status = monero.Wallet_status(newWptr); - if (status != 0) { - final error = monero.Wallet_errorString(newWptr); - throw CoinException( - error, - details: "unable to create wallet, recoveryWallet failed.", - ); - } - monero.Wallet_store(newWptr); - monero.Wallet_store(newWptr); - progressCallback?.call(description: "Wallet created"); - } - @override - Future createNewWallet( - final String walletName, - final String walletPassword, { - final ProgressCallback? progressCallback, - required final bool? createWallet, - required final String? seed, - required final int? restoreHeight, - required final String? primaryAddress, - required final String? viewKey, - required final String? spendKey, - required final String? seedOffsetOrEncryption, - }) async { - progressCallback?.call( - title: "Creating new wallet", description: "Initializing..."); + String getPathForWallet(final String walletName) { final baseDir = Directory(p.join(baseStoragePath, strings.symbolLowercase)); if (!baseDir.existsSync()) { baseDir.createSync(recursive: true); } - final String walletPath = p.join(baseDir.path, walletName); - if (createWallet == true) { - // new wallet - // new wallet polyseed offset - // new wallet polyseed encrypt - // new wallet legacy offset - await createMoneroWallet( - progressCallback: progressCallback, - walletPath: walletPath, - walletPassword: walletPassword, - seedOffsetOrEncryption: seedOffsetOrEncryption ?? "", - ); - } else if (createWallet == false && - (seed ?? "").trim().split(" ").length == 16) { - await createMoneroWalletPolyseed( - progressCallback: progressCallback, - walletPath: walletPath, - walletPassword: walletPassword, - seed: seed!, - seedOffsetOrEncryption: seedOffsetOrEncryption ?? "", - ); - // polyseed - // polyseed encrypted - // polyseed offset - } else if (createWallet == false && - (seed ?? "").trim().split(" ").length == 25) { - await createMoneroWalletSeed( - progressCallback: progressCallback, - walletPath: walletPath, - walletPassword: walletPassword, - seed: seed!, - seedOffsetOrEncryption: seedOffsetOrEncryption ?? ""); - // legacy seed - // legacy seed offset - } else if (createWallet == false && spendKey != "") { - // keys deterministic - // keys non-deterministic - await createMoneroWalletKeys( - progressCallback: progressCallback, - walletPath: walletPath, - walletPassword: walletPassword, - walletAddress: primaryAddress!, - secretSpendKey: spendKey!, - secretViewKey: viewKey!, - restoreHeight: restoreHeight!, - ); - } else { - throw Exception("Unknown form used to create wallet"); + // Prevent user from slipping outside allowed directory + final String walletPath = p.join(baseDir.path, walletName); + if (!walletPath.startsWith(baseDir.path)) { + throw Exception("Illegal wallet name: $walletName"); } - - return openWallet(MoneroWalletInfo(walletPath), password: walletPassword); + return walletPath; } @override @@ -295,13 +86,12 @@ class Monero implements Coin { monero.WalletManager_closeWallet(wmPtr, wptr, true); } wPtrList.clear(); - final walletExist = - monero.WalletManager_walletExists(wmPtr, walletInfo.walletName); + final walletExist = monero.WalletManager_walletExists(wmPtr, walletInfo.walletName); if (!walletExist) { throw Exception("Given wallet doesn't exist (${walletInfo.walletName})"); } - final wptr = monero.WalletManager_openWallet(wmPtr, - path: walletInfo.walletName, password: password); + final wptr = + monero.WalletManager_openWallet(wmPtr, path: walletInfo.walletName, password: password); final status = monero.Wallet_status(wptr); if (status != 0) { final error = monero.Wallet_errorString(wptr); @@ -316,12 +106,14 @@ class Monero implements Coin { Coins get type => Coins.monero; // monero.dart stuff - static monero.WalletManager wmPtr = - monero.WalletManagerFactory_getWalletManager(); + static monero.WalletManager wmPtr = monero.WalletManagerFactory_getWalletManager(); @override bool isSeedSomewhatLegit(final String seed) { final length = seed.split(" ").length; return [16, 25].contains(length); } + + @override + WalletCreation creationMethod(final AppLocalizations L) => MoneroWalletCreation(L); } diff --git a/lib/coins/monero/creation/common.dart b/lib/coins/monero/creation/common.dart new file mode 100644 index 0000000..b038c26 --- /dev/null +++ b/lib/coins/monero/creation/common.dart @@ -0,0 +1,171 @@ +// creation = any action that results in new wallet being created, be that restore or generate +// To unify the process of wallet creation we provide multiple "generators". +// There is one class that defines inputs (FormElement) +// And multiple WalletCreation classes +// When creating wallet heuristic are being applied to see which creation method will be used, +// it is entirely possible for one input of restore/creation form to have multiple outputs, + +import 'package:cupcake/coins/abstract/wallet_creation.dart'; +import 'package:cupcake/coins/monero/coin.dart'; +import 'package:cupcake/coins/monero/creation/new_wallet.dart'; +import 'package:cupcake/coins/monero/creation/restore_keys.dart'; +import 'package:cupcake/coins/monero/creation/restore_legacy.dart'; +import 'package:cupcake/coins/monero/creation/restore_polyseed.dart'; +import 'package:cupcake/utils/types.dart'; +import 'package:cupcake/l10n/app_localizations.dart'; +import 'package:cupcake/utils/form/abstract_form_element.dart'; +import 'package:cupcake/utils/form/single_choice_form_element.dart'; +import 'package:cupcake/utils/form/string_form_element.dart'; +import 'package:cupcake/utils/form/validators.dart'; + +class MoneroWalletCreation extends WalletCreation { + static MoneroWalletCreation? _instance; + MoneroWalletCreation._internal(this.L); + factory MoneroWalletCreation(final AppLocalizations L) { + _instance ??= MoneroWalletCreation._internal(L); + return _instance!; + } + + @override + Future wipe() async { + await Future.delayed(Duration.zero); // do not call on build(); + seed.ctrl.clear(); + walletAddress.ctrl.clear(); + secretSpendKey.ctrl.clear(); + restoreHeight.ctrl.clear(); + seedOffset.ctrl.clear(); + walletSeedType.currentSelection = 0; + } + + AppLocalizations L; + + @override + final coin = Monero(); + + Future errorHandler(final Object error) async { + print("error: $error"); + return; + } + + late StringFormElement seed = StringFormElement( + L.wallet_seed, + password: false, + validator: nonEmptyValidator( + L, + extra: (final input) => + !(Monero().isSeedSomewhatLegit(input)) ? L.warning_seed_incorrect_length : null, + ), + errorHandler: errorHandler, + ); + + late StringFormElement walletAddress = StringFormElement( + L.primary_address_label, + validator: nonEmptyValidator(L), + errorHandler: errorHandler, + ); + + late StringFormElement secretSpendKey = StringFormElement( + L.secret_spend_key, + validator: nonEmptyValidator(L), + errorHandler: errorHandler, + ); + + late StringFormElement secretViewKey = StringFormElement( + L.secret_view_key, + validator: nonEmptyValidator(L), + errorHandler: errorHandler, + ); + + late StringFormElement restoreHeight = StringFormElement( + L.restore_height, + validator: nonEmptyValidator(L), + errorHandler: errorHandler, + ); + + late StringFormElement seedOffset = StringFormElement( + L.seed_offset, + password: true, + isExtra: true, + validator: nonEmptyValidator(L), + errorHandler: errorHandler, + ); + + late SingleChoiceFormElement walletSeedType = SingleChoiceFormElement( + title: L.seed_type, + elements: [ + L.seed_type_polyseed, + L.seed_type_legacy, + ], + errorHandler: errorHandler, + ); + + late List createForm = [walletSeedType, seedOffset]; + + late List restoreSeedForm = [seed, seedOffset]; + + late List restoreKeysForm = [ + walletAddress, + secretSpendKey, + secretViewKey, + ]; + + @override + Map> createMethods(final CreateMethod createMethod) => { + if ([CreateMethod.create].contains(createMethod)) L.option_create_new_wallet: createForm, + if ([CreateMethod.restore].contains(createMethod)) ...{ + L.option_create_seed: restoreSeedForm, + L.option_create_keys: restoreKeysForm, + }, + }; + + @override + Future create( + final CreateMethod createMethod, final String walletName, final String walletPassword) async { + if (createMethod == CreateMethod.create) { + return CreateMoneroWalletCreationMethod( + progressCallback: null, + walletPath: coin.getPathForWallet(walletName), + walletPassword: walletPassword, + seedOffsetOrEncryption: await seedOffset.value, + ).create(); + } + + if ((await seed.value).isNotEmpty && (await secretSpendKey.value).isNotEmpty) { + // This shouldn't happen because of how UI works, but I'd like to be extra sure about it + throw Exception("Tried to restore from seed and key at the same time"); + } + if ((await seed.value).isNotEmpty) { + switch ((await seed.value).split(" ").length) { + case 16: + return RestorePolyseedMoneroWalletCreationMethod( + walletPath: coin.getPathForWallet(walletName), + walletPassword: walletPassword, + seed: await seed.value, + seedOffsetOrEncryption: await seedOffset.value, + ).create(); + case 25: + return RestoreLegacyWalletCreationMethod( + walletPath: coin.getPathForWallet(walletName), + walletPassword: walletPassword, + seed: await seed.value, + seedOffsetOrEncryption: await seedOffset.value, + ).create(); + default: + throw Exception("Incorrent number of words present in the seed"); + } + } + + print(await walletAddress.value); + print(await secretSpendKey.value); + print(await secretViewKey.value); + + return RestoreFromKeysMoneroWalletCreationMethod( + walletPath: coin.getPathForWallet(walletName), + walletPassword: walletPassword, + walletAddress: await walletAddress.value, + secretSpendKey: await secretSpendKey.value, + secretViewKey: await secretViewKey.value, + restoreHeight: int.tryParse(await restoreHeight.value) ?? 1, + ).create(); + } +} diff --git a/lib/coins/monero/creation/new_wallet.dart b/lib/coins/monero/creation/new_wallet.dart new file mode 100644 index 0000000..99de5d2 --- /dev/null +++ b/lib/coins/monero/creation/new_wallet.dart @@ -0,0 +1,65 @@ +import 'package:cupcake/coins/abstract/wallet_creation.dart'; +import 'package:cupcake/coins/monero/cache_keys.dart'; +import 'package:cupcake/coins/monero/coin.dart'; +import 'package:cupcake/coins/monero/wallet_info.dart'; +import 'package:cupcake/utils/types.dart'; +import 'package:path/path.dart' as p; +import 'package:monero/monero.dart' as monero; + +class CreateMoneroWalletCreationMethod extends CreationMethod { + CreateMoneroWalletCreationMethod({ + required this.walletPath, + required this.walletPassword, + required this.seedOffsetOrEncryption, + this.progressCallback, + }); + final ProgressCallback? progressCallback; + final String walletPath; + final String walletPassword; + final String seedOffsetOrEncryption; + + @override + Future create() async { + progressCallback?.call(description: "Generating polyseed"); + final newSeed = monero.Wallet_createPolyseed(); + progressCallback?.call(description: "Creating wallet"); + final newWptr = monero.WalletManager_createWalletFromPolyseed( + Monero.wmPtr, + path: walletPath, + password: walletPassword, + mnemonic: newSeed, + seedOffset: seedOffsetOrEncryption, + newWallet: true, + restoreHeight: 0, + kdfRounds: 1, + ); + progressCallback?.call(description: "Checking status"); + final status = monero.Wallet_status(newWptr); + if (status != 0) { + final error = monero.Wallet_errorString(newWptr); + return CreationOutcome( + method: CreateMethod.create, + success: false, + message: error, + ); + } + monero.Wallet_setCacheAttribute( + newWptr, + key: MoneroCacheKeys.seedOffsetCacheKey, + value: seedOffsetOrEncryption, + ); + monero.Wallet_store(newWptr); + monero.Wallet_store(newWptr); + Monero.wPtrList.add(newWptr); + progressCallback?.call(description: "Wallet created"); + final wallet = await Monero().openWallet( + MoneroWalletInfo(p.basename(walletPath)), + password: walletPassword, + ); + return CreationOutcome( + method: CreateMethod.create, + success: true, + wallet: wallet, + ); + } +} diff --git a/lib/coins/monero/creation/restore_keys.dart b/lib/coins/monero/creation/restore_keys.dart new file mode 100644 index 0000000..35133c8 --- /dev/null +++ b/lib/coins/monero/creation/restore_keys.dart @@ -0,0 +1,93 @@ +import 'dart:ffi'; + +import 'package:cupcake/coins/abstract/wallet_creation.dart'; +import 'package:cupcake/coins/monero/coin.dart'; +import 'package:cupcake/coins/monero/wallet_info.dart'; +import 'package:cupcake/utils/types.dart'; +import 'package:path/path.dart' as p; +import 'package:monero/monero.dart' as monero; + +class RestoreFromKeysMoneroWalletCreationMethod extends CreationMethod { + RestoreFromKeysMoneroWalletCreationMethod({ + required this.walletPath, + required this.walletPassword, + required this.walletAddress, + required this.secretSpendKey, + required this.secretViewKey, + required this.restoreHeight, + this.progressCallback, + }); + final ProgressCallback? progressCallback; + final String walletPath; + final String walletPassword; + final String walletAddress; + final String secretSpendKey; + final String secretViewKey; + final int restoreHeight; + + @override + Future create() async { + progressCallback?.call(description: "Creating wallet"); + print("create: $walletAddress"); + print("create: $secretSpendKey"); + print("create: $secretViewKey"); + Pointer newWptr; + if (secretViewKey.isNotEmpty) { + newWptr = monero.WalletManager_createWalletFromKeys( + Monero.wmPtr, + path: walletPath, + password: walletPassword, + restoreHeight: restoreHeight, + addressString: walletAddress, + viewKeyString: secretViewKey, + spendKeyString: secretSpendKey, + ); + } else { + newWptr = monero.WalletManager_createDeterministicWalletFromSpendKey( + Monero.wmPtr, + path: walletPath, + password: walletPassword, + language: "English", + spendKeyString: secretSpendKey, + newWallet: true, + restoreHeight: restoreHeight, + ); + } + progressCallback?.call(description: "Checking status"); + int status = monero.Wallet_status(newWptr); + if (status != 0) { + // Fallback to createDeterministicWallet in case when createWalletFromKeys didn't work. + newWptr = monero.WalletManager_createDeterministicWalletFromSpendKey( + Monero.wmPtr, + path: walletPath, + password: walletPassword, + language: "English", + spendKeyString: secretSpendKey, + newWallet: true, + restoreHeight: restoreHeight, + ); + status = monero.Wallet_status(newWptr); + } + + if (status != 0) { + final error = monero.Wallet_errorString(newWptr); + return CreationOutcome( + method: CreateMethod.restore, + success: false, + message: error, + ); + } + monero.Wallet_store(newWptr); + monero.Wallet_store(newWptr); + progressCallback?.call(description: "Wallet created"); + final wallet = await Monero().openWallet( + MoneroWalletInfo(p.basename(walletPath)), + password: walletPassword, + ); + return CreationOutcome( + method: CreateMethod.restore, + success: true, + wallet: wallet, + ); + } +} diff --git a/lib/coins/monero/creation/restore_legacy.dart b/lib/coins/monero/creation/restore_legacy.dart new file mode 100644 index 0000000..5ad9876 --- /dev/null +++ b/lib/coins/monero/creation/restore_legacy.dart @@ -0,0 +1,58 @@ +import 'package:cupcake/coins/abstract/wallet_creation.dart'; +import 'package:cupcake/coins/monero/coin.dart'; +import 'package:cupcake/coins/monero/wallet_info.dart'; +import 'package:cupcake/utils/types.dart'; +import 'package:path/path.dart' as p; +import 'package:monero/monero.dart' as monero; + +class RestoreLegacyWalletCreationMethod extends CreationMethod { + RestoreLegacyWalletCreationMethod({ + required this.walletPath, + required this.walletPassword, + required this.seed, + required this.seedOffsetOrEncryption, + this.progressCallback, + }); + final ProgressCallback? progressCallback; + final String walletPath; + final String walletPassword; + String seed; + final String seedOffsetOrEncryption; + + @override + Future create() async { + progressCallback?.call(description: "Creating wallet"); + final newWptr = monero.WalletManager_recoveryWallet( + Monero.wmPtr, + path: walletPath, + password: walletPassword, + mnemonic: seed, + seedOffset: seedOffsetOrEncryption, + restoreHeight: 0, + kdfRounds: 1, + ); + Monero.wPtrList.add(newWptr); + progressCallback?.call(description: "Checking status"); + final status = monero.Wallet_status(newWptr); + if (status != 0) { + final error = monero.Wallet_errorString(newWptr); + return CreationOutcome( + method: CreateMethod.restore, + success: false, + message: error, + ); + } + monero.Wallet_store(newWptr); + monero.Wallet_store(newWptr); + progressCallback?.call(description: "Wallet created"); + final wallet = await Monero().openWallet( + MoneroWalletInfo(p.basename(walletPath)), + password: walletPassword, + ); + return CreationOutcome( + method: CreateMethod.restore, + success: true, + wallet: wallet, + ); + } +} diff --git a/lib/coins/monero/creation/restore_polyseed.dart b/lib/coins/monero/creation/restore_polyseed.dart new file mode 100644 index 0000000..e23cfb5 --- /dev/null +++ b/lib/coins/monero/creation/restore_polyseed.dart @@ -0,0 +1,79 @@ +import 'package:cupcake/coins/abstract/wallet_creation.dart'; +import 'package:cupcake/coins/monero/cache_keys.dart'; +import 'package:cupcake/coins/monero/coin.dart'; +import 'package:cupcake/coins/monero/wallet_info.dart'; +import 'package:cupcake/utils/types.dart'; +import 'package:path/path.dart' as p; +import 'package:monero/monero.dart' as monero; +import 'package:polyseed/polyseed.dart'; + +class RestorePolyseedMoneroWalletCreationMethod extends CreationMethod { + RestorePolyseedMoneroWalletCreationMethod({ + required this.walletPath, + required this.walletPassword, + required this.seed, + required this.seedOffsetOrEncryption, + this.progressCallback, + }); + final ProgressCallback? progressCallback; + final String walletPath; + final String walletPassword; + String seed; + final String seedOffsetOrEncryption; + + @override + Future create() async { + progressCallback?.call(description: "Restoring wallet"); + final lang = PolyseedLang.getByPhrase(seed); + const coin = PolyseedCoin.POLYSEED_MONERO; + final dartPolyseed = Polyseed.decode(seed, lang, coin); + var offset = seedOffsetOrEncryption; + if (dartPolyseed.isEncrypted) { + if (seedOffsetOrEncryption.isEmpty) { + return CreationOutcome( + method: CreateMethod.restore, + success: false, + message: "seed offset is empty, but polyseed is encrypted", + ); + } + dartPolyseed.crypt(seedOffsetOrEncryption); + seed = dartPolyseed.encode(lang, coin); + offset = ""; + } + final newWptr = monero.WalletManager_createWalletFromPolyseed( + Monero.wmPtr, + path: walletPath, + password: walletPassword, + mnemonic: seed, + seedOffset: offset, + newWallet: true, + restoreHeight: 0, + kdfRounds: 1, + ); + Monero.wPtrList.add(newWptr); + progressCallback?.call(description: "Checking status"); + final status = monero.Wallet_status(newWptr); + if (status != 0) { + final error = monero.Wallet_errorString(newWptr); + return CreationOutcome( + method: CreateMethod.restore, + success: false, + message: error, + ); + } + monero.Wallet_setCacheAttribute(newWptr, + key: MoneroCacheKeys.seedOffsetCacheKey, value: seedOffsetOrEncryption); + monero.Wallet_store(newWptr); + monero.Wallet_store(newWptr); + progressCallback?.call(description: "Wallet created"); + final wallet = await Monero().openWallet( + MoneroWalletInfo(p.basename(walletPath)), + password: walletPassword, + ); + return CreationOutcome( + method: CreateMethod.restore, + success: true, + wallet: wallet, + ); + } +} diff --git a/lib/coins/monero/wallet.dart b/lib/coins/monero/wallet.dart index b6add36..7764416 100644 --- a/lib/coins/monero/wallet.dart +++ b/lib/coins/monero/wallet.dart @@ -7,7 +7,7 @@ import 'package:cupcake/coins/abstract/wallet_seed_detail.dart'; import 'package:cupcake/coins/monero/coin.dart'; import 'package:cupcake/coins/monero/amount.dart'; import 'package:cupcake/coins/monero/cache_keys.dart'; -import 'package:cupcake/coins/types.dart'; +import 'package:cupcake/utils/types.dart'; import 'package:cupcake/l10n/app_localizations.dart'; import 'package:cupcake/utils/config.dart'; import 'package:cupcake/utils/null_if_empty.dart'; @@ -53,16 +53,15 @@ class MoneroWallet implements CoinWallet { int getAccountId() => _accountIndex; @override - int get addressIndex => - monero.Wallet_numSubaddresses(wptr, accountIndex: getAccountId()); + int get addressIndex => monero.Wallet_numSubaddresses(wptr, accountIndex: getAccountId()); @override - String get getAccountLabel => monero.Wallet_getSubaddressLabel(wptr, - accountIndex: _accountIndex, addressIndex: 0); + String get getAccountLabel => + monero.Wallet_getSubaddressLabel(wptr, accountIndex: _accountIndex, addressIndex: 0); @override - String get getCurrentAddress => monero.Wallet_address(wptr, - accountIndex: getAccountId(), addressIndex: addressIndex); + String get getCurrentAddress => + monero.Wallet_address(wptr, accountIndex: getAccountId(), addressIndex: addressIndex); @override int getBalance() => monero.Wallet_balance(wptr, accountIndex: getAccountId()); @@ -72,12 +71,10 @@ class MoneroWallet implements CoinWallet { Future exportKeyImagesUR(final BuildContext context) async { final allImages = monero.Wallet_exportKeyImagesUR(wptr, - max_fragment_length: CupcakeConfig.instance.maxFragmentLength, - all: true) + max_fragment_length: CupcakeConfig.instance.maxFragmentLength, all: true) .split("\n"); final someImages = monero.Wallet_exportKeyImagesUR(wptr, - max_fragment_length: CupcakeConfig.instance.maxFragmentLength, - all: false) + max_fragment_length: CupcakeConfig.instance.maxFragmentLength, all: false) .split("\n"); await AnimatedURPage( urqrList: { @@ -89,7 +86,7 @@ class MoneroWallet implements CoinWallet { @override Future handleUR(final BuildContext context, final URQRData ur) async { - print("handling: ${ur.tag}."); + print("handling: ${ur.tag}"); switch (ur.tag) { case "xmr-keyimage" || "xmr-txsigned": throw Exception("Unable to handle ${ur.tag}. This is a offline wallet"); @@ -103,9 +100,7 @@ class MoneroWallet implements CoinWallet { await exportKeyImagesUR(context); save(); case "xmr-txunsigned": - print("handling tx-unsignex"); - final txptr = - monero.Wallet_loadUnsignedTxUR(wptr, input: ur.inputs.join("\n")); + final txptr = monero.Wallet_loadUnsignedTxUR(wptr, input: ur.inputs.join("\n")); var status = monero.Wallet_status(wptr); if (status != 0) { final error = monero.Wallet_errorString(wptr); @@ -121,24 +116,22 @@ class MoneroWallet implements CoinWallet { .split(";") .map((final e) => int.parse(e)) .toList(); - final addrs = - monero.UnsignedTransaction_recipientAddress(txptr).split(";"); + final addrs = monero.UnsignedTransaction_recipientAddress(txptr).split(";"); if (amts.length != addrs.length) { throw CoinException("Amount and address length is not equal."); } for (int i = 0; i < amts.length; i++) { destMap[Address(addrs[i])] = MoneroAmount(amts[i]); } - final fee = - MoneroAmount(int.parse(monero.UnsignedTransaction_fee(txptr))); + final fee = MoneroAmount(int.parse(monero.UnsignedTransaction_fee(txptr))); await UnconfirmedTransactionView( wallet: this, destMap: destMap, fee: fee, confirmCallback: () async { - final signedTx = monero.UnsignedTransaction_signUR( - txptr, CupcakeConfig.instance.maxFragmentLength) - .split("\n"); + final signedTx = + monero.UnsignedTransaction_signUR(txptr, CupcakeConfig.instance.maxFragmentLength) + .split("\n"); var status = monero.Wallet_status(wptr); if (status != 0) { final error = monero.Wallet_errorString(wptr); @@ -149,8 +142,7 @@ class MoneroWallet implements CoinWallet { final error = monero.UnsignedTransaction_errorString(txptr); throw CoinException(error); } - await AnimatedURPage(urqrList: {"signedTx": signedTx}) - .push(context); + await AnimatedURPage(urqrList: {"signedTx": signedTx}).push(context); }, cancelCallback: () => {}, ).push(context); @@ -160,8 +152,11 @@ class MoneroWallet implements CoinWallet { } } - String get seedOffset => monero.Wallet_getCacheAttribute(wptr, - key: MoneroCacheKeys.seedOffsetCacheKey); + String get seedOffset => + monero.Wallet_getCacheAttribute(wptr, key: MoneroCacheKeys.seedOffsetCacheKey); + + @override + String get passphrase => seedOffset; set seedOffset(final String newSeedOffset) => monero.Wallet_setCacheAttribute( wptr, @@ -171,21 +166,17 @@ class MoneroWallet implements CoinWallet { @override String get seed => - (polyseed ?? "").nullIfEmpty() ?? - (polyseedDart ?? "").nullIfEmpty() ?? - legacySeed; + (polyseed ?? "").nullIfEmpty() ?? (polyseedDart ?? "").nullIfEmpty() ?? legacySeed; - String? get polyseed => - monero.Wallet_getPolyseed(wptr, passphrase: seedOffset); + String? get polyseed => monero.Wallet_getPolyseed(wptr, passphrase: seedOffset); String? get polyseedDart { try { const coin = PolyseedCoin.POLYSEED_MONERO; final lang = PolyseedLang.getByName("English"); - final polyseedString = polyseed ?? - monero.Wallet_getCacheAttribute(wptr, - key: MoneroCacheKeys.seedCacheKey); + final polyseedString = + polyseed ?? monero.Wallet_getCacheAttribute(wptr, key: MoneroCacheKeys.seedCacheKey); final seed = Polyseed.decode(polyseedString, lang, coin); if (seedOffset.isNotEmpty) { @@ -207,8 +198,7 @@ class MoneroWallet implements CoinWallet { @override Future close() { monero.WalletManager_closeWallet(Monero.wmPtr, wptr, true); - Monero.wPtrList - .removeWhere((final element) => element.address == wptr.address); + Monero.wPtrList.removeWhere((final element) => element.address == wptr.address); return Future.value(); } @@ -281,8 +271,7 @@ class MoneroWallet implements CoinWallet { name: L.view_only_restore_qr, value: const JsonEncoder.withIndent(' ').convert({ "version": 0, - "primaryAddress": - monero.Wallet_address(wptr, accountIndex: 0, addressIndex: 0), + "primaryAddress": monero.Wallet_address(wptr, accountIndex: 0, addressIndex: 0), "privateViewKey": monero.Wallet_secretViewKey(wptr), "restoreHeight": monero.Wallet_getRefreshFromBlockHeight(wptr), }), @@ -293,9 +282,7 @@ class MoneroWallet implements CoinWallet { (final index) { final key = secrets.keys.elementAt(index); return WalletSeedDetail( - type: WalletSeedDetailType.text, - name: key, - value: secrets[key] ?? "unknown"); + type: WalletSeedDetailType.text, name: key, value: secrets[key] ?? "unknown"); }, ), if (CupcakeConfig.instance.debug) diff --git a/lib/dev/rebuild_generator.dart b/lib/dev/rebuild_generator.dart index d80dc30..e9890f8 100644 --- a/lib/dev/rebuild_generator.dart +++ b/lib/dev/rebuild_generator.dart @@ -1,9 +1,9 @@ -// lib/rebuild_generator.dart +// ignore_for_file: depend_on_referenced_packages + import 'dart:async'; import 'package:analyzer/dart/element/element.dart'; import 'package:build/build.dart'; -// import 'package:cupcake/utils/config.dart'; import 'package:source_gen/source_gen.dart'; import 'package:cupcake/dev/generate_rebuild.dart'; @@ -11,8 +11,8 @@ import 'package:cupcake/utils/capitalize.dart'; class RebuildClassGenerator extends GeneratorForAnnotation { @override - FutureOr generateForAnnotatedElement(final Element element, - final ConstantReader annotation, final BuildStep buildStep) async { + FutureOr generateForAnnotatedElement( + final Element element, final ConstantReader annotation, final BuildStep buildStep) async { if (element is! ClassElement) { throw InvalidGenerationSourceError( 'Generator cannot target `${element.displayName}`. ' @@ -25,8 +25,7 @@ class RebuildClassGenerator extends GeneratorForAnnotation { final rebuildMethods = await _rebuildOnChangePart(classElement); final throwOnUiMethods = await _throwOnUiPart(classElement); - final exposeRebuildableAccessorsMethods = - await _exposeRebuildableAccessorsPart(classElement); + final exposeRebuildableAccessorsMethods = await _exposeRebuildableAccessorsPart(classElement); final fileName = buildStep.inputId.uri.pathSegments.last; return ''' @@ -47,8 +46,7 @@ ${exposeRebuildableAccessorsMethods.join()} '''; } - Future> _rebuildOnChangePart( - final ClassElement classElement) async { + Future> _rebuildOnChangePart(final ClassElement classElement) async { final rebuildMethods = []; for (final field in classElement.fields) { final hasRebuildOnChange = field.metadata.any((final meta) { @@ -79,14 +77,12 @@ ${exposeRebuildableAccessorsMethods.join()} for (final field in classElement.methods) { final hasThrowOnUI = field.metadata.any((final meta) { final constantValue = meta.computeConstantValue(); - return constantValue != null && - constantValue.type?.getDisplayString() == ThrowOnUI.name; + return constantValue != null && constantValue.type?.getDisplayString() == ThrowOnUI.name; }); if (!hasThrowOnUI) continue; final throwOnUIMeta = field.metadata.firstWhere((final meta) { final constantValue = meta.computeConstantValue(); - return constantValue != null && - constantValue.type?.getDisplayString() == ThrowOnUI.name; + return constantValue != null && constantValue.type?.getDisplayString() == ThrowOnUI.name; }); final constantValue = throwOnUIMeta.computeConstantValue(); @@ -94,8 +90,7 @@ ${exposeRebuildableAccessorsMethods.join()} final translationField = constantValue?.getField('L')?.toStringValue(); if (messageField != null && translationField != null) { - throw Exception( - "ThrowOnUI() cannot have message and L at the same time"); + throw Exception("ThrowOnUI() cannot have message and L at the same time"); } String? alertTitle; if (messageField != null) { @@ -108,10 +103,7 @@ ${exposeRebuildableAccessorsMethods.join()} final fieldName = field.displayName; final fieldType = field.type.getDisplayString().split(" ")[0]; - final fieldArgs = field.type - .getDisplayString() - .split(" ")[1] - .replaceAll("Function", ""); + final fieldArgs = field.type.getDisplayString().split(" ")[1].replaceAll("Function", ""); final methodSuffix = fieldName[0].toUpperCase() + fieldName.substring(1); final noPrefix = methodSuffix.substring(1); throwOnUiMethods.add(''' @@ -128,31 +120,26 @@ ${exposeRebuildableAccessorsMethods.join()} return throwOnUiMethods; } - Future> _exposeRebuildableAccessorsPart( - final ClassElement classElement) async { + Future> _exposeRebuildableAccessorsPart(final ClassElement classElement) async { final rebuildMethods = []; // classElement.methods for (final field in classElement.accessors) { final hasExposeRebuildableAccessors = field.metadata.any((final meta) { final constantValue = meta.computeConstantValue(); return constantValue != null && - constantValue.type?.getDisplayString() == - ExposeRebuildableAccessors.name; + constantValue.type?.getDisplayString() == ExposeRebuildableAccessors.name; }); if (!hasExposeRebuildableAccessors) continue; - final exposeRebuildableAccessorsMeta = - field.metadata.firstWhere((final meta) { + final exposeRebuildableAccessorsMeta = field.metadata.firstWhere((final meta) { final constantValue = meta.computeConstantValue(); return constantValue != null && - constantValue.type?.getDisplayString() == - ExposeRebuildableAccessors.name; + constantValue.type?.getDisplayString() == ExposeRebuildableAccessors.name; }); final fieldName = field.displayName; // Capitalize the field name for the method name. final methodSuffix = fieldName[0].toUpperCase() + fieldName.substring(1); final noPrefix = methodSuffix.substring(1); - final constantValue = - exposeRebuildableAccessorsMeta.computeConstantValue(); + final constantValue = exposeRebuildableAccessorsMeta.computeConstantValue(); String? extraCode = constantValue?.getField('extraCode')?.toStringValue(); @@ -165,8 +152,7 @@ ${exposeRebuildableAccessorsMethods.join()} if (extraCode != null && !extraCode.endsWith(';')) { extraCode = "$extraCode;"; } - final capitalizedIfIfeelLikeIt = - noPrefix.isEmpty ? elm.name : elm.name.capitalize(); + final capitalizedIfIfeelLikeIt = noPrefix.isEmpty ? elm.name : elm.name.capitalize(); rebuildMethods.add(''' ${elm.returnType} get $noPrefix$capitalizedIfIfeelLikeIt => \$$noPrefix.${elm.name}; set $noPrefix$capitalizedIfIfeelLikeIt(final ${elm.returnType} new$capitalizedIfIfeelLikeIt) { diff --git a/lib/main.dart b/lib/main.dart index 7e8e969..76769e1 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -12,9 +12,6 @@ import 'package:cupcake/views/initial_setup_screen.dart'; import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; -const String signingKeyExpected = "Please Fill Me On Release :)"; -String signingKeyFound = ""; - Future appInit() async { WidgetsFlutterBinding.ensureInitialized(); await initializeBaseStoragePath(); @@ -32,8 +29,7 @@ Future main() async { FlutterError.onError = (final FlutterErrorDetails errorDetails) { catchFatalError(errorDetails.exception, null); }; - PlatformDispatcher.instance.onError = - (final Object error, final StackTrace stackTrace) { + PlatformDispatcher.instance.onError = (final Object error, final StackTrace stackTrace) { catchFatalError(error, stackTrace); return true; }; @@ -66,15 +62,6 @@ class MyApp extends StatelessWidget { Locale('en'), // English Locale('pl'), // Polish ], - // builder: (final BuildContext context, final Widget? child) { - // print("a"); - // return Stack( - // alignment: AlignmentDirectional.bottomStart, - // children: [ - // child ?? const Text("null"), - // ], - // ); - // }, home: CupcakeConfig.instance.initialSetupComplete ? HomeScreen( openLastWallet: true, diff --git a/lib/panic_handler.dart b/lib/panic_handler.dart index 6945a02..a679b6e 100644 --- a/lib/panic_handler.dart +++ b/lib/panic_handler.dart @@ -3,12 +3,10 @@ import 'package:cupcake/views/widgets/cupcake_appbar_title.dart'; import 'package:flutter/material.dart'; import 'package:package_info_plus/package_info_plus.dart'; -Future catchFatalError( - final Object error, final StackTrace? stackTrace) async { +Future catchFatalError(final Object error, final StackTrace? stackTrace) async { final PackageInfo packageInfo = await PackageInfo.fromPlatform(); - runApp(ErrorHandlerApp( - error: error, stackTrace: stackTrace, packageInfo: packageInfo)); + runApp(ErrorHandlerApp(error: error, stackTrace: stackTrace, packageInfo: packageInfo)); } class ErrorHandlerApp extends StatelessWidget { @@ -82,8 +80,7 @@ class CupcakeFatalError extends StatelessWidget { List _buildNotice() { return [ - _text( - "Critical error occured and app cannot continue. Please take a screenshot of this" + _text("Critical error occured and app cannot continue. Please take a screenshot of this" " screen and send it to our support"), Divider(), _text(error), diff --git a/lib/themes/base_theme.dart b/lib/themes/base_theme.dart index 0843914..a4529f3 100644 --- a/lib/themes/base_theme.dart +++ b/lib/themes/base_theme.dart @@ -14,8 +14,7 @@ class BaseTheme { elevatedButtonTheme: ElevatedButtonThemeData( style: ElevatedButton.styleFrom( backgroundColor: onOnBackgroundColor, - shape: - RoundedRectangleBorder(borderRadius: BorderRadius.circular(30)), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30)), textStyle: const TextStyle( color: Colors.white, )), diff --git a/lib/utils/form/flutter_secure_storage_value_outcome.dart b/lib/utils/form/flutter_secure_storage_value_outcome.dart index 6eb5cc8..94296a5 100644 --- a/lib/utils/form/flutter_secure_storage_value_outcome.dart +++ b/lib/utils/form/flutter_secure_storage_value_outcome.dart @@ -18,14 +18,11 @@ class FlutterSecureStorageValueOutcome implements ValueOutcome { Future encode(final String input) async { final List bytes = utf8.encode(input); final Digest sha512Hash = sha512.convert(bytes); - var valInput = - await secureStorage.read(key: "FlutterSecureStorageValueOutcome._$key"); + var valInput = await secureStorage.read(key: "FlutterSecureStorageValueOutcome._$key"); if (valInput == null) { await secureStorage.write( - key: "FlutterSecureStorageValueOutcome._$key", - value: sha512Hash.toString()); - valInput = await secureStorage.read( - key: "FlutterSecureStorageValueOutcome._$key"); + key: "FlutterSecureStorageValueOutcome._$key", value: sha512Hash.toString()); + valInput = await secureStorage.read(key: "FlutterSecureStorageValueOutcome._$key"); } if (sha512Hash.toString() != valInput && verifyMatching) { throw Exception("Input doesn't match the secure element value"); @@ -38,8 +35,7 @@ class FlutterSecureStorageValueOutcome implements ValueOutcome { } if (!canWrite) { if (CupcakeConfig.instance.debug) { - throw Exception( - "DEBUG_ONLY: canWrite is false but we tried to flush the value"); + throw Exception("DEBUG_ONLY: canWrite is false but we tried to flush the value"); } return; } @@ -54,8 +50,7 @@ class FlutterSecureStorageValueOutcome implements ValueOutcome { Future decode(final String output) async { final List bytes = utf8.encode(output); final Digest sha512Hash = sha512.convert(bytes); - final valInput = - await secureStorage.read(key: "FlutterSecureStorageValueOutcome._$key"); + final valInput = await secureStorage.read(key: "FlutterSecureStorageValueOutcome._$key"); if (sha512Hash.toString() != valInput && verifyMatching) { throw Exception("Input doesn't match the secure element value"); } diff --git a/lib/utils/form/pin_form_element.dart b/lib/utils/form/pin_form_element.dart index 2f81957..8805da3 100644 --- a/lib/utils/form/pin_form_element.dart +++ b/lib/utils/form/pin_form_element.dart @@ -24,11 +24,9 @@ class PinFormElement extends FormElement { if (!CupcakeConfig.instance.biometricEnabled) return; final auth = LocalAuthentication(); - final List availableBiometrics = - await auth.getAvailableBiometrics(); + final List availableBiometrics = await auth.getAvailableBiometrics(); final bool canAuthenticateWithBiometrics = await auth.canCheckBiometrics; - final bool canAuthenticate = - canAuthenticateWithBiometrics || await auth.isDeviceSupported(); + final bool canAuthenticate = canAuthenticateWithBiometrics || await auth.isDeviceSupported(); if (!canAuthenticate) return; if (!availableBiometrics.contains(BiometricType.fingerprint) && !availableBiometrics.contains(BiometricType.face)) { @@ -37,8 +35,7 @@ class PinFormElement extends FormElement { final bool didAuthenticate = await auth.authenticate( localizedReason: 'Authenticate...', - options: const AuthenticationOptions( - useErrorDialogs: true, biometricOnly: true), + options: const AuthenticationOptions(useErrorDialogs: true, biometricOnly: true), ); if (!didAuthenticate) return; final value = await secureStorage.read(key: "UI.${valueOutcome.uniqueId}"); diff --git a/lib/utils/new_wallet/action.dart b/lib/utils/new_wallet/action.dart index 07acd92..f5d22ed 100644 --- a/lib/utils/new_wallet/action.dart +++ b/lib/utils/new_wallet/action.dart @@ -1,4 +1,4 @@ -import 'package:cupcake/coins/types.dart'; +import 'package:cupcake/utils/types.dart'; import 'package:flutter/widgets.dart'; class NewWalletAction { diff --git a/lib/utils/new_wallet/info_page.dart b/lib/utils/new_wallet/info_page.dart index 55e0dcf..934b946 100644 --- a/lib/utils/new_wallet/info_page.dart +++ b/lib/utils/new_wallet/info_page.dart @@ -1,4 +1,4 @@ -import 'package:cupcake/coins/types.dart'; +import 'package:cupcake/utils/types.dart'; import 'package:cupcake/gen/assets.gen.dart'; import 'package:cupcake/l10n/app_localizations.dart'; import 'package:cupcake/utils/new_wallet/action.dart'; @@ -8,8 +8,7 @@ import 'package:lottie/lottie.dart'; import 'package:share_plus/share_plus.dart'; class NewWalletInfoPage { - static NewWalletInfoPage preShowSeedPage(final AppLocalizations L) => - NewWalletInfoPage( + static NewWalletInfoPage preShowSeedPage(final AppLocalizations L) => NewWalletInfoPage( topText: L.important, topAction: null, topActionText: null, @@ -71,8 +70,7 @@ class NewWalletInfoPage { texts: [ Text( title, - style: const TextStyle( - fontSize: 26, fontWeight: FontWeight.w500, color: Colors.white), + style: const TextStyle(fontSize: 26, fontWeight: FontWeight.w500, color: Colors.white), textAlign: TextAlign.center, ), Text( diff --git a/lib/utils/secure_storage.dart b/lib/utils/secure_storage.dart index 3cf6d14..461f132 100644 --- a/lib/utils/secure_storage.dart +++ b/lib/utils/secure_storage.dart @@ -5,8 +5,7 @@ AndroidOptions _getAndroidOptions() => const AndroidOptions( encryptedSharedPreferences: true, ); -final FlutterSecureStorage secureStorage = - FlutterSecureStorage(aOptions: _getAndroidOptions()); +final FlutterSecureStorage secureStorage = FlutterSecureStorage(aOptions: _getAndroidOptions()); Future setWalletPin(final String password) async { final pin = await secureStorage.read(key: SecureStorageKey.pin); diff --git a/lib/coins/types.dart b/lib/utils/types.dart similarity index 96% rename from lib/coins/types.dart rename to lib/utils/types.dart index 96f4904..9b11c8b 100644 --- a/lib/coins/types.dart +++ b/lib/utils/types.dart @@ -6,7 +6,6 @@ enum WalletSeedDetailType { } enum CreateMethod { - any, create, restore, } diff --git a/lib/utils/urqrdetails.dart b/lib/utils/urqrdetails.dart index ae83d82..fb3e310 100644 --- a/lib/utils/urqrdetails.dart +++ b/lib/utils/urqrdetails.dart @@ -1,6 +1,5 @@ class URQRDetails { - URQRDetails( - {required this.tag, required this.description, required this.values}); + URQRDetails({required this.tag, required this.description, required this.values}); String tag; String description; List values; diff --git a/lib/view_model/abstract.dart b/lib/view_model/abstract.dart index 5271e2a..e89a9cd 100644 --- a/lib/view_model/abstract.dart +++ b/lib/view_model/abstract.dart @@ -11,12 +11,10 @@ class ViewModel { AppLocalizations get L { if (_lcache == null && c == null) { - throw Exception( - "context is null in view model. Did you forget to register()?"); + throw Exception("context is null in view model. Did you forget to register()?"); } if (_lcache == null && c?.mounted != true) { - throw Exception( - "context is not mounted. Did you register incorrect context?"); + throw Exception("context is not mounted. Did you register incorrect context?"); } _lcache ??= AppLocalizations.of(c!); return _lcache!; @@ -45,11 +43,9 @@ class ViewModel { (c as Element).markNeedsBuild(); } - Future errorHandler(final Object e) => - callThrowable(() => throw e, L.create_wallet); + Future errorHandler(final Object e) => callThrowable(() => throw e, L.create_wallet); - Future callThrowable( - final FutureOr Function() function, final String title) async { + Future callThrowable(final FutureOr Function() function, final String title) async { if (c == null) return false; if (!mounted) return false; try { @@ -64,10 +60,7 @@ class ViewModel { ); } on TypeError catch (e) { print(e); - await showAlert( - context: c!, - title: title, - body: [e.toString(), e.stackTrace.toString()]); + await showAlert(context: c!, title: title, body: [e.toString(), e.stackTrace.toString()]); } catch (e) { print(e); await showAlert(context: c!, title: title, body: [e.toString()]); diff --git a/lib/view_model/create_wallet_view_model.dart b/lib/view_model/create_wallet_view_model.dart index 0360de2..bfe5b5d 100644 --- a/lib/view_model/create_wallet_view_model.dart +++ b/lib/view_model/create_wallet_view_model.dart @@ -2,19 +2,18 @@ import 'dart:async'; import 'package:cupcake/coins/abstract/coin.dart'; import 'package:cupcake/coins/abstract/wallet.dart'; +import 'package:cupcake/coins/abstract/wallet_creation.dart'; import 'package:cupcake/coins/list.dart'; -import 'package:cupcake/coins/types.dart'; +import 'package:cupcake/utils/types.dart'; import 'package:cupcake/dev/generate_rebuild.dart'; import 'package:cupcake/utils/config.dart'; import 'package:cupcake/utils/form/abstract_form_element.dart'; import 'package:cupcake/utils/form/flutter_secure_storage_value_outcome.dart'; import 'package:cupcake/utils/form/pin_form_element.dart'; import 'package:cupcake/utils/form/plain_value_outcome.dart'; -import 'package:cupcake/utils/form/single_choice_form_element.dart'; import 'package:cupcake/utils/form/string_form_element.dart'; import 'package:cupcake/utils/form/validators.dart'; import 'package:cupcake/utils/new_wallet/info_page.dart'; -import 'package:cupcake/utils/null_if_empty.dart'; import 'package:cupcake/view_model/abstract.dart'; import 'package:cupcake/views/new_wallet_info.dart'; import 'package:cupcake/views/wallet_home.dart'; @@ -41,16 +40,12 @@ class CreateWalletViewModel extends ViewModel { late String screenName = screenNameOriginal; String get screenNameOriginal => switch (createMethod) { - CreateMethod.any => L.create_wallet, CreateMethod.create => L.create_wallet, CreateMethod.restore => L.restore_wallet, }; List get coins => walletCoins; - @RebuildOnChange() - bool $isCreate = false; - bool get hasAdvancedOptions { if (currentForm == null) return false; for (final elm in currentForm!) { @@ -76,30 +71,19 @@ class CreateWalletViewModel extends ViewModel { errorHandler: errorHandler, ); - late SingleChoiceFormElement walletSeedType = SingleChoiceFormElement( - title: L.seed_type, - elements: [ - L.seed_type_polyseed, - L.seed_type_legacy, - ], - errorHandler: errorHandler, - ); - late PinFormElement walletPasswordInitial = PinFormElement( label: L.wallet_password, password: true, valueOutcome: PlainValueOutcome(), validator: nonEmptyValidator( L, - extra: (final input) => - (input.length < 4) ? L.warning_password_too_short : null, + extra: (final input) => (input.length < 4) ? L.warning_password_too_short : null, ), errorHandler: errorHandler, ); late PinFormElement walletPassword = PinFormElement( - label: - (needsPasswordConfirm) ? L.wallet_password_repeat : L.wallet_password, + label: (needsPasswordConfirm) ? L.wallet_password_repeat : L.wallet_password, password: true, valueOutcome: FlutterSecureStorageValueOutcome( "secure.wallet_password", @@ -121,55 +105,6 @@ class CreateWalletViewModel extends ViewModel { errorHandler: errorHandler, ); - late StringFormElement seed = StringFormElement( - L.wallet_seed, - password: false, - validator: nonEmptyValidator( - L, - extra: (final input) => - (selectedCoin?.isSeedSomewhatLegit(input) ?? false) - ? L.warning_seed_incorrect_length - : null, - ), - errorHandler: errorHandler, - ); - - late StringFormElement walletAddress = StringFormElement( - L.primary_address_label, - password: true, - validator: nonEmptyValidator(L), - errorHandler: errorHandler, - ); - - late StringFormElement secretSpendKey = StringFormElement( - L.secret_spend_key, - password: true, - validator: nonEmptyValidator(L), - errorHandler: errorHandler, - ); - - late StringFormElement secretViewKey = StringFormElement( - L.secret_view_key, - password: true, - validator: nonEmptyValidator(L), - errorHandler: errorHandler, - ); - - late StringFormElement restoreHeight = StringFormElement( - L.restore_height, - password: true, - validator: nonEmptyValidator(L), - errorHandler: errorHandler, - ); - - late StringFormElement seedOffset = StringFormElement( - L.seed_offset, - password: true, - isExtra: true, - validator: nonEmptyValidator(L), - errorHandler: errorHandler, - ); - @RebuildOnChange() late List? $currentForm = () { if (createMethods.length == 1) { @@ -178,43 +113,27 @@ class CreateWalletViewModel extends ViewModel { return null; }(); - Map> get createMethods => { - if ([CreateMethod.any, CreateMethod.create].contains(createMethod)) - L.option_create_new_wallet: _createForm, - if ([CreateMethod.any, CreateMethod.restore] - .contains(createMethod)) ...{ - L.option_create_seed: _restoreSeedForm, - L.option_create_keys: _restoreFormKeysForm, - }, - }; + WalletCreation? creationMethod; + Map> get createMethods { + if (creationMethod == null || creationMethod!.coin != selectedCoin) { + creationMethod = selectedCoin!.creationMethod(L); + creationMethod!.wipe(); + } + final form = creationMethod!.createMethods(createMethod); + final Map> toRet = {}; + for (final key in form.keys) { + toRet[key] = [ + if (needsPasswordConfirm) walletPasswordInitial, + walletPassword, + walletName, + ]; + toRet[key]!.addAll(form[key]!); + } + return toRet; + } final bool needsPasswordConfirm; - late final List _createForm = [ - if (needsPasswordConfirm) walletPasswordInitial, - walletPassword, - walletName, - walletSeedType, - seedOffset - ]; - - late final List _restoreSeedForm = [ - if (needsPasswordConfirm) walletPasswordInitial, - walletPassword, - walletName, - seed, - seedOffset - ]; - - late final List _restoreFormKeysForm = [ - if (needsPasswordConfirm) walletPasswordInitial, - walletPassword, - walletName, - walletAddress, - secretSpendKey, - secretViewKey, - ]; - @ThrowOnUI(message: "Failed to complete setup") Future $completeSetup(final CoinWallet cw) async { CupcakeConfig.instance.initialSetupComplete = true; @@ -228,40 +147,55 @@ class CreateWalletViewModel extends ViewModel { if ((await walletName.value).isEmpty) { throw Exception(L.warning_input_cannot_be_empty); } - final cw = await selectedCoin!.createNewWallet( + + if (await walletPassword.value == await walletPasswordInitial.value) { + throw Exception("Wallet password doesn't match"); + } + + final outcome = await creationMethod!.create( + createMethod, await walletName.value, await walletPassword.value, - primaryAddress: (await walletAddress.value).nullIfEmpty(), - createWallet: (currentForm == _createForm), - seed: (await seed.value).nullIfEmpty(), - restoreHeight: int.tryParse(await restoreHeight.value), - viewKey: (await secretViewKey.value).nullIfEmpty(), - spendKey: (await secretSpendKey.value).nullIfEmpty(), - seedOffsetOrEncryption: (await seedOffset.value).nullIfEmpty(), ); + if (outcome == null) { + throw Exception("Unable to create wallet using any known methods"); + } + + if (!outcome.success) { + if (outcome.message == null || outcome.message?.isEmpty == true) { + throw Exception( + "Wallet creation failed, and status indicated failure but message was empty"); + } + throw Exception(outcome.message); + } + + if (outcome.wallet == null) { + throw Exception("Wallet is null but there is no indication of failure"); + } + final List pages = [ NewWalletInfoPage.preShowSeedPage(L), NewWalletInfoPage.writeDownNotice( L, nextCallback: - seedOffset.ctrl.text.isNotEmpty ? null : () => completeSetup(cw), - text: seed.ctrl.text, + outcome.wallet!.passphrase.isEmpty ? () => completeSetup(outcome.wallet) : null, + text: outcome.wallet!.seed, title: L.seed, ), - if (seedOffset.ctrl.text.isNotEmpty) + if (outcome.wallet!.passphrase.isNotEmpty) NewWalletInfoPage.writeDownNotice( L, - nextCallback: () => completeSetup(cw), - text: seed.ctrl.text, + nextCallback: () => completeSetup(outcome.wallet!), + text: outcome.wallet!.passphrase, title: L.wallet_passphrase, ), ]; if (!mounted) { throw Exception("context is not mounted, unable to show next screen"); } - if (currentForm != _createForm) { - await WalletHome(coinWallet: cw).push(c!); + if (outcome.method == CreateMethod.restore) { + await WalletHome(coinWallet: outcome.wallet!).push(c!); } else { await NewWalletInfoScreen( pages: pages, diff --git a/lib/view_model/home_screen_view_model.dart b/lib/view_model/home_screen_view_model.dart index 982b916..309815f 100644 --- a/lib/view_model/home_screen_view_model.dart +++ b/lib/view_model/home_screen_view_model.dart @@ -1,6 +1,6 @@ import 'package:cupcake/coins/abstract/wallet_info.dart'; import 'package:cupcake/coins/list.dart'; -import 'package:cupcake/coins/types.dart'; +import 'package:cupcake/utils/types.dart'; import 'package:cupcake/utils/config.dart'; import 'package:cupcake/view_model/abstract.dart'; import 'package:cupcake/views/create_wallet.dart'; @@ -60,8 +60,7 @@ class HomeScreenViewModel extends ViewModel { } void toggleSort() { - CupcakeConfig.instance.walletSort = - (CupcakeConfig.instance.walletSort + 1) % 2; + CupcakeConfig.instance.walletSort = (CupcakeConfig.instance.walletSort + 1) % 2; CupcakeConfig.instance.save(); markNeedsBuild(); } diff --git a/lib/view_model/open_wallet_view_model.dart b/lib/view_model/open_wallet_view_model.dart index b5a1270..683d4ad 100644 --- a/lib/view_model/open_wallet_view_model.dart +++ b/lib/view_model/open_wallet_view_model.dart @@ -27,8 +27,7 @@ class OpenWalletViewModel extends ViewModel { ), validator: nonEmptyValidator( L, - extra: (final input) => - (input.length < 4) ? L.warning_password_too_short : null, + extra: (final input) => (input.length < 4) ? L.warning_password_too_short : null, ), onChanged: openWalletIfPasswordCorrect, onConfirm: openWallet, diff --git a/lib/view_model/security_backup_view_model.dart b/lib/view_model/security_backup_view_model.dart index 45ad434..ce94924 100644 --- a/lib/view_model/security_backup_view_model.dart +++ b/lib/view_model/security_backup_view_model.dart @@ -29,8 +29,7 @@ class SecurityBackupViewModel extends ViewModel { ), validator: nonEmptyValidator( L, - extra: (final input) => - (input.length < 4) ? L.warning_password_too_short : null, + extra: (final input) => (input.length < 4) ? L.warning_password_too_short : null, ), showNumboard: true, onConfirm: () async { diff --git a/lib/view_model/urqr_view_model.dart b/lib/view_model/urqr_view_model.dart index 58f8574..7bdfeb5 100644 --- a/lib/view_model/urqr_view_model.dart +++ b/lib/view_model/urqr_view_model.dart @@ -21,8 +21,8 @@ class URQRViewModel extends ViewModel { List get alternativeCodes { final Map> copiedList = {}; copiedList.addAll(urqrList); - copiedList.removeWhere((final key, final value) => - value.join("\n").trim() == urqr.join("\n").trim()); + copiedList + .removeWhere((final key, final value) => value.join("\n").trim() == urqr.join("\n").trim()); final keys = copiedList.keys; return keys.toList(); } diff --git a/lib/view_model/wallet_edit_view_model.dart b/lib/view_model/wallet_edit_view_model.dart index 7b78406..c1b6a78 100644 --- a/lib/view_model/wallet_edit_view_model.dart +++ b/lib/view_model/wallet_edit_view_model.dart @@ -37,8 +37,7 @@ class WalletEditViewModel extends ViewModel { ), validator: nonEmptyValidator( L, - extra: (final input) => - (input.length < 4) ? L.warning_password_too_short : null, + extra: (final input) => (input.length < 4) ? L.warning_password_too_short : null, ), showNumboard: false, errorHandler: errorHandler, diff --git a/lib/views/abstract.dart b/lib/views/abstract.dart index 81a9e19..204d3f8 100644 --- a/lib/views/abstract.dart +++ b/lib/views/abstract.dart @@ -17,6 +17,8 @@ import 'package:flutter/material.dart'; // and due to the fact that we don't use stateful widget we can directly use // viewmodel, without any extra code involved in doing that. +int buildCount = 0; + class _AbstractViewState extends State { _AbstractViewState({required this.realBuild}); @@ -24,6 +26,7 @@ class _AbstractViewState extends State { @override Widget build(final BuildContext context) { + print("build count: ${++buildCount}"); return realBuild(context); } } diff --git a/lib/views/barcode_scanner.dart b/lib/views/barcode_scanner.dart index e3ab80b..d2d610f 100644 --- a/lib/views/barcode_scanner.dart +++ b/lib/views/barcode_scanner.dart @@ -25,10 +25,7 @@ class BarcodeScanner extends AbstractView { if (viewModel.ur.inputs.isNotEmpty) Center( child: Text("${viewModel.ur.inputs.length}/${viewModel.ur.count}", - style: Theme.of(context) - .textTheme - .displayLarge - ?.copyWith(color: Colors.white)), + style: Theme.of(context).textTheme.displayLarge?.copyWith(color: Colors.white)), ), SizedBox( child: Center( diff --git a/lib/views/create_wallet.dart b/lib/views/create_wallet.dart index 1040887..1b3643a 100644 --- a/lib/views/create_wallet.dart +++ b/lib/views/create_wallet.dart @@ -1,4 +1,4 @@ -import 'package:cupcake/coins/types.dart'; +import 'package:cupcake/utils/types.dart'; import 'package:cupcake/gen/assets.gen.dart'; import 'package:cupcake/view_model/create_wallet_view_model.dart'; import 'package:cupcake/views/abstract.dart'; @@ -11,8 +11,7 @@ class CreateWallet extends AbstractView { required final CreateMethod createMethod, required final bool needsPasswordConfirm}) : viewModel = CreateWalletViewModel( - createMethod: createMethod, - needsPasswordConfirm: needsPasswordConfirm); + createMethod: createMethod, needsPasswordConfirm: needsPasswordConfirm); @override final CreateWalletViewModel viewModel; diff --git a/lib/views/home_screen.dart b/lib/views/home_screen.dart index 1de01d9..68a48d1 100644 --- a/lib/views/home_screen.dart +++ b/lib/views/home_screen.dart @@ -1,5 +1,5 @@ import 'package:cupcake/coins/abstract/wallet_info.dart'; -import 'package:cupcake/coins/types.dart'; +import 'package:cupcake/utils/types.dart'; import 'package:cupcake/view_model/home_screen_view_model.dart'; import 'package:cupcake/views/abstract.dart'; import 'package:cupcake/views/widgets/buttons/long_primary.dart'; @@ -8,12 +8,9 @@ import 'package:flutter/material.dart'; import 'package:path/path.dart' as p; class HomeScreen extends AbstractView { - HomeScreen( - {super.key, - required final bool openLastWallet, - final String? lastOpenedWallet}) - : viewModel = HomeScreenViewModel( - openLastWallet: openLastWallet, lastOpenedWallet: lastOpenedWallet); + HomeScreen({super.key, required final bool openLastWallet, final String? lastOpenedWallet}) + : viewModel = + HomeScreenViewModel(openLastWallet: openLastWallet, lastOpenedWallet: lastOpenedWallet); @override final HomeScreenViewModel viewModel; @@ -49,20 +46,20 @@ class HomeScreen extends AbstractView { ), ], ); - Widget walletsBody(final BuildContext context, - final AsyncSnapshot> wallets) { + Widget walletsBody( + final BuildContext context, final AsyncSnapshot> wallets) { if (!wallets.hasData) return Container(); return ListView.builder( itemCount: wallets.data!.length, itemBuilder: (final BuildContext context, final int index) { - final bool isOpen = (wallets.data![index].walletName) - .contains(viewModel.lastOpenedWallet ?? ""); + final bool isOpen = + (wallets.data![index].walletName).contains(viewModel.lastOpenedWallet ?? ""); return singleWalletWidget(context, isOpen, wallets.data![index]); }); } - Card singleWalletWidget(final BuildContext context, final bool isOpen, - final CoinWalletInfo wallet) { + Card singleWalletWidget( + final BuildContext context, final bool isOpen, final CoinWalletInfo wallet) { return Card( child: IntrinsicHeight( child: Row( diff --git a/lib/views/initial_setup_screen.dart b/lib/views/initial_setup_screen.dart index 241f634..308d183 100644 --- a/lib/views/initial_setup_screen.dart +++ b/lib/views/initial_setup_screen.dart @@ -1,4 +1,4 @@ -import 'package:cupcake/coins/types.dart'; +import 'package:cupcake/utils/types.dart'; import 'package:cupcake/view_model/initial_setup_view_model.dart'; import 'package:cupcake/views/abstract.dart'; import 'package:cupcake/views/create_wallet.dart'; @@ -20,8 +20,7 @@ class InitialSetupScreen extends AbstractView { child: Column( children: [ Padding( - padding: - const EdgeInsets.symmetric(horizontal: 64.0, vertical: 24), + padding: const EdgeInsets.symmetric(horizontal: 64.0, vertical: 24), child: Assets.cakeLanding.lottie()), Text(L.welcome_to, style: Theme.of(context).textTheme.displaySmall?.copyWith( diff --git a/lib/views/new_wallet_info.dart b/lib/views/new_wallet_info.dart index 5ae8a9a..f2519b3 100644 --- a/lib/views/new_wallet_info.dart +++ b/lib/views/new_wallet_info.dart @@ -1,4 +1,4 @@ -import 'package:cupcake/coins/types.dart'; +import 'package:cupcake/utils/types.dart'; import 'package:cupcake/utils/new_wallet/info_page.dart'; import 'package:cupcake/view_model/new_wallet_info_view_model.dart'; import 'package:cupcake/views/abstract.dart'; @@ -23,12 +23,10 @@ class NewWalletInfoScreen extends AbstractView { ); List? _getActionButton() { - if (viewModel.page.topAction == null && - viewModel.page.topActionText == null) { + if (viewModel.page.topAction == null && viewModel.page.topActionText == null) { return null; } - if (viewModel.page.topActionText != null && - viewModel.page.topAction == null) { + if (viewModel.page.topActionText != null && viewModel.page.topAction == null) { return [ TextButton( onPressed: () => viewModel.currentPageIndex++, @@ -72,8 +70,7 @@ class NewWalletInfoScreen extends AbstractView { padding: const EdgeInsets.only(left: 32, right: 32, top: 0, bottom: 16), child: Column( children: [ - if (viewModel.page.lottieAnimation != null) - viewModel.page.lottieAnimation!, + if (viewModel.page.lottieAnimation != null) viewModel.page.lottieAnimation!, ...viewModel.page.texts, const Spacer(), SizedBox( diff --git a/lib/views/receive.dart b/lib/views/receive.dart index ea1fd12..f3a866d 100644 --- a/lib/views/receive.dart +++ b/lib/views/receive.dart @@ -19,8 +19,7 @@ class Receive extends AbstractView { child: Column( children: [ Padding( - padding: - const EdgeInsets.only(top: 8, left: 48, right: 48, bottom: 32), + padding: const EdgeInsets.only(top: 8, left: 48, right: 48, bottom: 32), child: Container( padding: const EdgeInsets.all(10.0), decoration: BoxDecoration( diff --git a/lib/views/security_backup.dart b/lib/views/security_backup.dart index d750dcb..bb0c538 100644 --- a/lib/views/security_backup.dart +++ b/lib/views/security_backup.dart @@ -1,6 +1,6 @@ import 'package:cupcake/coins/abstract/wallet.dart'; import 'package:cupcake/coins/abstract/wallet_seed_detail.dart'; -import 'package:cupcake/coins/types.dart'; +import 'package:cupcake/utils/types.dart'; import 'package:cupcake/utils/alerts/widget.dart'; import 'package:cupcake/view_model/security_backup_view_model.dart'; import 'package:cupcake/views/abstract.dart'; diff --git a/lib/views/settings.dart b/lib/views/settings.dart index 1f2c4b0..985bd2d 100644 --- a/lib/views/settings.dart +++ b/lib/views/settings.dart @@ -29,8 +29,7 @@ class SettingsView extends AbstractView { }), IntegerConfigElement( title: "Milliseconds for qr code", - hint: - "How many milliseconds should one QR code last before switching to next one", + hint: "How many milliseconds should one QR code last before switching to next one", value: viewModel.configMsForQrCode, onChange: (final int value) { viewModel.configMsForQrCode = value; @@ -54,20 +53,17 @@ class SettingsView extends AbstractView { if (viewModel.configDidFoundInsecureBiometric) BooleanConfigElement( title: "Insecure biometric auth", - subtitleEnabled: - "Insecure biometric authentication is enabled, it is not recommended" + subtitleEnabled: "Insecure biometric authentication is enabled, it is not recommended" " and could lead to loss of funds. Make sure that you understand the drawbacks," " and when in doubt - keep this setting disabled.", - subtitleDisabled: - "Click to enable insecure biometric authentication.", + subtitleDisabled: "Click to enable insecure biometric authentication.", value: viewModel.configCanUseInsecureBiometric, onChange: (final bool value) async { viewModel.configCanUseInsecureBiometric = value; }), IntegerConfigElement( title: "Max fragment density", - hint: - "How many characters of data should fit within a single QR code", + hint: "How many characters of data should fit within a single QR code", value: viewModel.configMaxFragmentLength, onChange: (final int value) async { viewModel.configMaxFragmentLength = value; diff --git a/lib/views/wallet_home.dart b/lib/views/wallet_home.dart index 8a7cdef..a2d5482 100644 --- a/lib/views/wallet_home.dart +++ b/lib/views/wallet_home.dart @@ -31,8 +31,7 @@ class WalletHome extends AbstractView { mainAxisSize: MainAxisSize.max, crossAxisAlignment: CrossAxisAlignment.center, children: [ - const Padding( - padding: EdgeInsets.only(top: 50, bottom: 24)), + const Padding(padding: EdgeInsets.only(top: 50, bottom: 24)), const SizedBox(width: 16), Assets.coins.xmr.svg(width: 50), const SizedBox(width: 16), @@ -43,9 +42,7 @@ class WalletHome extends AbstractView { Text( viewModel.wallet.walletName, style: const TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - fontSize: 16), + color: Colors.white, fontWeight: FontWeight.bold, fontSize: 16), ), Text(L.primary_account_label), ], @@ -91,8 +88,7 @@ class WalletHome extends AbstractView { child: SizedBox( height: 64, child: ElevatedButton.icon( - onPressed: () => - BarcodeScanner(wallet: viewModel.wallet).push(context), + onPressed: () => BarcodeScanner(wallet: viewModel.wallet).push(context), icon: const Icon( Icons.qr_code_rounded, size: 35, diff --git a/lib/views/widgets/barcode_scanner/progress_painter.dart b/lib/views/widgets/barcode_scanner/progress_painter.dart index df3a618..e5e8273 100644 --- a/lib/views/widgets/barcode_scanner/progress_painter.dart +++ b/lib/views/widgets/barcode_scanner/progress_painter.dart @@ -16,10 +16,9 @@ class ProgressPainter extends CustomPainter { const fullAngle = 360.0; var startAngle = 0.0; for (int i = 0; i < urQrProgress.expectedPartCount.toInt(); i++) { - final sweepAngle = - (1 / urQrProgress.expectedPartCount) * fullAngle * pi / 180.0; - drawSector(canvas, urQrProgress.receivedPartIndexes.contains(i), rect, - startAngle, sweepAngle); + final sweepAngle = (1 / urQrProgress.expectedPartCount) * fullAngle * pi / 180.0; + drawSector( + canvas, urQrProgress.receivedPartIndexes.contains(i), rect, startAngle, sweepAngle); startAngle += sweepAngle; } } diff --git a/lib/views/widgets/buttons/long_secondary.dart b/lib/views/widgets/buttons/long_secondary.dart index 94bd75f..1dba1b3 100644 --- a/lib/views/widgets/buttons/long_secondary.dart +++ b/lib/views/widgets/buttons/long_secondary.dart @@ -4,14 +4,10 @@ import 'package:flutter/material.dart'; class LongSecondaryButton extends LongPrimaryButton { const LongSecondaryButton( - {super.key, - required super.text, - required super.icon, - required super.onPressed}); + {super.key, required super.text, required super.icon, required super.onPressed}); @override - WidgetStateProperty? get backgroundColor => - const WidgetStatePropertyAll(Colors.white); + WidgetStateProperty? get backgroundColor => const WidgetStatePropertyAll(Colors.white); @override Color get textColor => BaseTheme.onBackgroundColor; diff --git a/lib/views/widgets/cake_card.dart b/lib/views/widgets/cake_card.dart index fb978a5..bb32b97 100644 --- a/lib/views/widgets/cake_card.dart +++ b/lib/views/widgets/cake_card.dart @@ -16,8 +16,7 @@ class CakeCard extends StatelessWidget { bottom: 4, right: 16, ), - this.firmPadding = - const EdgeInsets.only(top: 24.0, left: 24.0, bottom: 24, right: 24)}); + this.firmPadding = const EdgeInsets.only(top: 24.0, left: 24.0, bottom: 24, right: 24)}); final Widget child; final EdgeInsets internalPadding; diff --git a/lib/views/widgets/form_builder.dart b/lib/views/widgets/form_builder.dart index c338ea5..6163cc4 100644 --- a/lib/views/widgets/form_builder.dart +++ b/lib/views/widgets/form_builder.dart @@ -68,8 +68,7 @@ class _FormBuilderState extends State { var e = widget.formElements.first as PinFormElement; int i = 0; int count = 0; - if (widget.formElements.length >= 2 && - (widget.formElements[1] is PinFormElement)) { + if (widget.formElements.length >= 2 && (widget.formElements[1] is PinFormElement)) { count++; } if (e.isConfirmed) { @@ -123,13 +122,11 @@ class _FormBuilderState extends State { final List availableBiometrics = await auth.getAvailableBiometrics(); - final bool canAuthenticateWithBiometrics = - await auth.canCheckBiometrics; - final bool canAuthenticate = canAuthenticateWithBiometrics || - await auth.isDeviceSupported(); + final bool canAuthenticateWithBiometrics = await auth.canCheckBiometrics; + final bool canAuthenticate = + canAuthenticateWithBiometrics || await auth.isDeviceSupported(); if (!canAuthenticate) throw Exception("Can't authenticate"); - if (!availableBiometrics - .contains(BiometricType.fingerprint) && + if (!availableBiometrics.contains(BiometricType.fingerprint) && !availableBiometrics.contains(BiometricType.face) && !CupcakeConfig.instance.canUseInsecureBiometric) { CupcakeConfig.instance.didFoundInsecureBiometric = true; @@ -141,8 +138,7 @@ class _FormBuilderState extends State { localizedReason: 'Authenticate...', options: AuthenticationOptions( useErrorDialogs: true, - biometricOnly: - !CupcakeConfig.instance.canUseInsecureBiometric), + biometricOnly: !CupcakeConfig.instance.canUseInsecureBiometric), ); if (!didAuthenticate) { throw Exception("User didn't authenticate"); @@ -181,8 +177,7 @@ class _FormBuilderState extends State { } children.add( Padding( - padding: - const EdgeInsets.only(bottom: 16.0, left: 12.0, right: 12.0), + padding: const EdgeInsets.only(bottom: 16.0, left: 12.0, right: 12.0), child: Stack( alignment: AlignmentDirectional.topEnd, children: [ @@ -226,8 +221,7 @@ class _FormBuilderState extends State { if (e.showNumboard) continue; children.add( Padding( - padding: - const EdgeInsets.only(bottom: 16.0, left: 12.0, right: 12.0), + padding: const EdgeInsets.only(bottom: 16.0, left: 12.0, right: 12.0), child: TextFormField( controller: e.ctrl, obscureText: true, diff --git a/lib/views/widgets/numerical_keyboard/keyboard.dart b/lib/views/widgets/numerical_keyboard/keyboard.dart index abb315e..f6ac08d 100644 --- a/lib/views/widgets/numerical_keyboard/keyboard.dart +++ b/lib/views/widgets/numerical_keyboard/keyboard.dart @@ -177,8 +177,7 @@ Widget getKeyWidgetPinPad(final Keys key) { Keys.a0 || Keys.dot => Text(getKeysChar(key), - style: const TextStyle( - fontSize: 32, fontWeight: FontWeight.w400, color: Colors.white)), + style: const TextStyle(fontSize: 32, fontWeight: FontWeight.w400, color: Colors.white)), Keys.backspace => const Icon(Icons.backspace), Keys.next => const Icon(Icons.check), _ => Container(), diff --git a/lib/views/widgets/numerical_keyboard/main.dart b/lib/views/widgets/numerical_keyboard/main.dart index 81bb982..f37a2dd 100644 --- a/lib/views/widgets/numerical_keyboard/main.dart +++ b/lib/views/widgets/numerical_keyboard/main.dart @@ -47,8 +47,7 @@ class NumericalKeyboard extends StatelessWidget { const Spacer(), SingleKey(Keys.backspace, ctrl, rebuild), SingleKey(Keys.a0, ctrl, rebuild), - if (showConfirm() && - (!showComma || ctrl.text.contains(getKeysChar(Keys.dot)))) + if (showConfirm() && (!showComma || ctrl.text.contains(getKeysChar(Keys.dot)))) SingleKey(Keys.next, ctrl, nextPage, longPress: onConfirmLongPress), if (showComma && !ctrl.text.contains(getKeysChar(Keys.dot))) SingleKey(Keys.dot, ctrl, rebuild), diff --git a/lib/views/widgets/numerical_keyboard/single_key.dart b/lib/views/widgets/numerical_keyboard/single_key.dart index ee7337f..334c4b4 100644 --- a/lib/views/widgets/numerical_keyboard/single_key.dart +++ b/lib/views/widgets/numerical_keyboard/single_key.dart @@ -2,8 +2,7 @@ import 'package:cupcake/views/widgets/numerical_keyboard/keyboard.dart'; import 'package:flutter/material.dart'; class SingleKey extends StatelessWidget { - const SingleKey(this.keyId, this.ctrl, this.callback, - {super.key, this.longPress}); + const SingleKey(this.keyId, this.ctrl, this.callback, {super.key, this.longPress}); final Keys keyId; final TextEditingController ctrl; final VoidCallback? callback; diff --git a/lib/views/widgets/settings/integer_config_element.dart b/lib/views/widgets/settings/integer_config_element.dart index b333c3a..51b2a26 100644 --- a/lib/views/widgets/settings/integer_config_element.dart +++ b/lib/views/widgets/settings/integer_config_element.dart @@ -37,8 +37,7 @@ class IntegerConfigElement extends StatelessWidget { : IconButton( icon: const Icon(Icons.info), onPressed: () { - showAlertWidget( - context: context, title: title, body: [Text(hint ?? "")]); + showAlertWidget(context: context, title: title, body: [Text(hint ?? "")]); }, ), ); diff --git a/lib/views/widgets/settings/version_widget.dart b/lib/views/widgets/settings/version_widget.dart index 02f5b26..6f53d91 100644 --- a/lib/views/widgets/settings/version_widget.dart +++ b/lib/views/widgets/settings/version_widget.dart @@ -18,9 +18,7 @@ class _VersionWidgetState extends State { final String buildNumber = packageInfo.buildNumber; if (!context.mounted) return; showAboutDialog( - context: context, - applicationName: appName, - applicationVersion: "$version+$buildNumber"); + context: context, applicationName: appName, applicationVersion: "$version+$buildNumber"); } List easterEgg = [ diff --git a/lib/views/widgets/urqr.dart b/lib/views/widgets/urqr.dart index dc5348a..f720225 100644 --- a/lib/views/widgets/urqr.dart +++ b/lib/views/widgets/urqr.dart @@ -21,9 +21,7 @@ class _URQRState extends State { void initState() { super.initState(); setState(() { - t = Timer.periodic( - Duration(milliseconds: CupcakeConfig.instance.msForQrCode), - (final timer) { + t = Timer.periodic(Duration(milliseconds: CupcakeConfig.instance.msForQrCode), (final timer) { _nextFrame(); }); }); diff --git a/pubspec.lock b/pubspec.lock index 72be206..de47364 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -15,7 +15,7 @@ packages: source: sdk version: "0.3.3" analyzer: - dependency: "direct main" + dependency: "direct dev" description: name: analyzer sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" @@ -42,20 +42,20 @@ packages: dependency: transitive description: name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 url: "https://pub.dev" source: hosted - version: "2.11.0" + version: "2.12.0" boolean_selector: dependency: transitive description: name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" build: - dependency: "direct main" + dependency: "direct dev" description: name: build sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0 @@ -74,26 +74,26 @@ packages: dependency: transitive description: name: build_daemon - sha256: "294a2edaf4814a378725bfe6358210196f5ea37af89ecd81bfa32960113d4948" + sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa" url: "https://pub.dev" source: hosted - version: "4.0.3" + version: "4.0.4" build_resolvers: dependency: transitive description: name: build_resolvers - sha256: "99d3980049739a985cf9b21f30881f46db3ebc62c5b8d5e60e27440876b1ba1e" + sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0 url: "https://pub.dev" source: hosted - version: "2.4.3" + version: "2.4.4" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "74691599a5bc750dc96a6b4bfd48f7d9d66453eab04c7f4063134800d6a5c573" + sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99" url: "https://pub.dev" source: hosted - version: "2.4.14" + version: "2.4.15" build_runner_core: dependency: transitive description: @@ -122,10 +122,10 @@ packages: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" checked_yaml: dependency: transitive description: @@ -138,10 +138,10 @@ packages: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" code_builder: dependency: transitive description: @@ -154,10 +154,10 @@ packages: dependency: transitive description: name: collection - sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.19.0" + version: "1.19.1" color: dependency: transitive description: @@ -218,10 +218,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.2" fast_scanner: dependency: "direct main" description: @@ -243,10 +243,10 @@ packages: dependency: transitive description: name: file - sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "7.0.1" fixnum: dependency: transitive description: @@ -277,26 +277,26 @@ packages: dependency: "direct main" description: name: flutter_gen - sha256: d7e4e57f606d73628b97765a67fdfb5a97e24cd2183e170afa8d1f62e48a9d5c + sha256: a727fbe4d9443ac05258ef7a987650f8d8f16b4f8c22cf98c1ac9183ac7f3eff url: "https://pub.dev" source: hosted - version: "5.8.0" + version: "5.9.0" flutter_gen_core: dependency: transitive description: name: flutter_gen_core - sha256: "46ecf0e317413dd065547887c43f93f55e9653e83eb98dc13dd07d40dd225325" + sha256: "53890b653738f34363d9f0d40f82104c261716bd551d3ba65f648770b6764c21" url: "https://pub.dev" source: hosted - version: "5.8.0" + version: "5.9.0" flutter_gen_runner: dependency: "direct dev" description: name: flutter_gen_runner - sha256: "77f0a02fc30d9fcf2549fe874eb3fde091435724904bcbb1af60aa40cbfab1f4" + sha256: de70b42eb5329f712c8b041069d081ad5fb5109f32d6d1ea9c1b39596786215d url: "https://pub.dev" source: hosted - version: "5.8.0" + version: "5.9.0" flutter_lints: dependency: "direct dev" description: @@ -510,18 +510,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" + sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec url: "https://pub.dev" source: hosted - version: "10.0.7" + version: "10.0.8" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 url: "https://pub.dev" source: hosted - version: "3.0.8" + version: "3.0.9" leak_tracker_testing: dependency: transitive description: @@ -606,10 +606,10 @@ packages: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.17" material_color_utilities: dependency: transitive description: @@ -622,10 +622,10 @@ packages: dependency: transitive description: name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.16.0" mime: dependency: transitive description: @@ -638,8 +638,8 @@ packages: dependency: "direct main" description: path: "impls/monero.dart" - ref: "v0.18.3.4-RC10" - resolved-ref: "9301097ff504525070cc0cb915fe2f1bb0670345" + ref: "v0.18.3.4-RC12" + resolved-ref: "9d9b1f2f2373fc9a99c6556a93eea63fe343cf58" url: "https://github.com/mrcyjanek/monero_c" source: git version: "0.0.0" @@ -655,26 +655,26 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: b15fad91c4d3d1f2b48c053dd41cb82da007c27407dc9ab5f9aa59881d0e39d4 + sha256: "67eae327b1b0faf761964a1d2e5d323c797f3799db0e85aa232db8d9e922bc35" url: "https://pub.dev" source: hosted - version: "8.1.4" + version: "8.2.1" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: a5ef9986efc7bf772f2696183a3992615baa76c1ffb1189318dd8803778fb05b + sha256: "205ec83335c2ab9107bbba3f8997f9356d72ca3c715d2f038fc773d0366b4c76" url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.1.0" path: dependency: "direct main" description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" path_parsing: dependency: transitive description: @@ -735,18 +735,18 @@ packages: dependency: transitive description: name: petitparser - sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 + sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" url: "https://pub.dev" source: hosted - version: "6.0.2" + version: "6.1.0" platform: dependency: transitive description: name: platform - sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" url: "https://pub.dev" source: hosted - version: "3.1.5" + version: "3.1.6" plugin_platform_interface: dependency: transitive description: @@ -791,10 +791,10 @@ packages: dependency: transitive description: name: process - sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32" + sha256: "107d8be718f120bbba9dcd1e95e3bd325b1b4a4f07db64154635ba03f2567a0d" url: "https://pub.dev" source: hosted - version: "5.0.2" + version: "5.0.3" pub_semver: dependency: transitive description: @@ -855,17 +855,17 @@ packages: dependency: transitive description: name: shelf_web_socket - sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67 + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.0" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.0" source_gen: - dependency: "direct main" + dependency: "direct dev" description: name: source_gen sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b" @@ -876,10 +876,10 @@ packages: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.10.1" sprintf: dependency: transitive description: @@ -892,18 +892,18 @@ packages: dependency: transitive description: name: stack_trace - sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" stream_transform: dependency: transitive description: @@ -916,10 +916,10 @@ packages: dependency: transitive description: name: string_scanner - sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.1" sync_http: dependency: transitive description: @@ -932,18 +932,18 @@ packages: dependency: transitive description: name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" test_api: dependency: transitive description: name: test_api - sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd url: "https://pub.dev" source: hosted - version: "0.7.3" + version: "0.7.4" time: dependency: transitive description: @@ -1020,10 +1020,10 @@ packages: dependency: "direct main" description: name: vector_graphics - sha256: "7ed22c21d7fdcc88dd6ba7860384af438cd220b251ad65dfc142ab722fabef61" + sha256: "44cc7104ff32563122a929e4620cf3efd584194eec6d1d913eb5ba593dbcf6de" url: "https://pub.dev" source: hosted - version: "1.1.16" + version: "1.1.18" vector_graphics_codec: dependency: transitive description: @@ -1052,10 +1052,10 @@ packages: dependency: transitive description: name: vm_service - sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b + sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" url: "https://pub.dev" source: hosted - version: "14.3.0" + version: "14.3.1" watcher: dependency: transitive description: @@ -1129,5 +1129,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.6.0 <4.0.0" + dart: ">=3.7.0-0 <4.0.0" flutter: ">=3.27.0" diff --git a/pubspec.yaml b/pubspec.yaml index 27d3d1b..705e274 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -38,7 +38,7 @@ dependencies: monero: git: url: https://github.com/mrcyjanek/monero_c - ref: v0.18.3.4-RC10 + ref: v0.18.3.4-RC12 path: impls/monero.dart path: ^1.9.0 path_provider: ^2.1.5 @@ -62,11 +62,11 @@ dependencies: crypto: ^3.0.3 package_info_plus: ^8.1.0 local_auth: ^2.3.0 + +dev_dependencies: source_gen: ^2.0.0 analyzer: ^6.11.0 build: ^2.4.2 - -dev_dependencies: flutter_test: sdk: flutter From 6371bf291b2b6f54542790d0014470d8d25fecf3 Mon Sep 17 00:00:00 2001 From: Czarek Nakamoto Date: Thu, 13 Feb 2025 17:28:04 +0100 Subject: [PATCH 11/13] enforce coding style by lints use translations instead of hardcoded strings reorganize code cleanup all classes --- .tooling/format.sh | 13 ++ analysis_options.yaml | 11 ++ lib/coins/abstract/coin.dart | 3 + lib/coins/abstract/wallet.dart | 4 +- lib/coins/monero/coin.dart | 13 +- lib/coins/monero/creation/common.dart | 17 +- lib/coins/monero/creation/new_wallet.dart | 14 +- lib/coins/monero/creation/restore_keys.dart | 15 +- lib/coins/monero/creation/restore_legacy.dart | 12 +- .../monero/creation/restore_polyseed.dart | 21 ++- lib/coins/monero/wallet.dart | 66 +++---- lib/coins/monero/wallet_info.dart | 15 +- lib/dev/generate_rebuild.dart | 6 +- lib/dev/rebuild_generator.dart | 5 +- lib/l10n/app_en.arb | 161 +++++++++++------- lib/l10n/app_pl.arb | 100 +++++------ lib/panic_handler.dart | 5 + lib/themes/base_theme.dart | 11 +- lib/utils/alerts/basic.dart | 2 +- lib/utils/alerts/widget.dart | 2 +- lib/utils/alerts/widget_minimal.dart | 2 +- lib/utils/config.dart | 22 +-- .../flutter_secure_storage_value_outcome.dart | 11 +- lib/utils/form/pin_form_element.dart | 24 +-- .../form/single_choice_form_element.dart | 10 +- lib/utils/form/string_form_element.dart | 19 ++- lib/utils/form/validators.dart | 6 +- lib/utils/new_wallet/info_page.dart | 17 +- lib/utils/random_name.dart | 2 +- lib/utils/urqr.dart | 15 +- lib/view_model/abstract.dart | 4 +- .../barcode_scanner_view_model.dart | 4 +- lib/view_model/create_wallet_view_model.dart | 16 +- lib/view_model/open_wallet_view_model.dart | 4 +- lib/view_model/receive_view_model.dart | 1 + .../security_backup_view_model.dart | 4 +- lib/view_model/settings_view_model.dart | 2 +- .../unconfirmed_transaction_view_model.dart | 18 +- lib/view_model/wallet_edit_view_model.dart | 12 +- lib/views/abstract.dart | 20 +-- lib/views/animated_qr_page.dart | 8 +- lib/views/barcode_scanner.dart | 14 +- lib/views/create_wallet.dart | 53 +++--- lib/views/home_screen.dart | 66 ++++--- lib/views/initial_setup_screen.dart | 17 +- lib/views/new_wallet_info.dart | 37 ++-- lib/views/open_wallet.dart | 9 +- lib/views/receive.dart | 19 ++- lib/views/security_backup.dart | 52 +++--- lib/views/settings.dart | 79 ++++----- lib/views/unconfirmed_transaction.dart | 11 +- lib/views/wallet_edit.dart | 13 +- lib/views/wallet_home.dart | 72 ++++---- .../barcode_scanner/progress_painter.dart | 19 ++- .../barcode_scanner/urqr_progress.dart | 9 +- lib/views/widgets/buttons/long_secondary.dart | 8 +- lib/views/widgets/cake_card.dart | 33 ++-- lib/views/widgets/form_builder.dart | 41 +++-- .../widgets/numerical_keyboard/keyboard.dart | 10 +- .../widgets/numerical_keyboard/main.dart | 70 ++++---- .../widgets/settings/version_widget.dart | 9 +- lib/views/widgets/urqr.dart | 14 +- pubspec.lock | 6 +- pubspec.yaml | 6 +- 64 files changed, 815 insertions(+), 569 deletions(-) diff --git a/.tooling/format.sh b/.tooling/format.sh index 14ddfff..2605736 100755 --- a/.tooling/format.sh +++ b/.tooling/format.sh @@ -2,7 +2,20 @@ set -x -e cd "$(dirname "$0")" cd .. + +pushd lib/l10n + for file in *.arb; + do + jq 'to_entries + | group_by(.key | sub("^@"; "")) + | map( sort_by(.key | startswith("@")) | map({ (.key): .value }) | add ) + | add' $file > $file.tmp || rm $file.tmp + mv $file.tmp $file + done +popd + dart run build_runner build --delete-conflicting-outputs dart fix --apply . dart format . + flutter gen-l10n diff --git a/analysis_options.yaml b/analysis_options.yaml index 45a5a8b..6da29bd 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -2,6 +2,8 @@ analyzer: errors: must_be_immutable: ignore overridden_fields: ignore + exclude: + - '**.g.dart' include: package:flutter_lints/flutter.yaml linter: @@ -15,6 +17,15 @@ linter: prefer_final_locals: true prefer_final_parameters: true avoid_void_async: true + require_trailing_commas: true + sort_child_properties_last: true + sort_constructors_first: true + sort_unnamed_constructors_first: true + unnecessary_async: true + unnecessary_await_in_return: true + unnecessary_string_interpolations: true + always_declare_return_types: true + formatter: page_width: 100 \ No newline at end of file diff --git a/lib/coins/abstract/coin.dart b/lib/coins/abstract/coin.dart index 25b1bbf..f6f3699 100644 --- a/lib/coins/abstract/coin.dart +++ b/lib/coins/abstract/coin.dart @@ -7,10 +7,13 @@ import 'package:cupcake/l10n/app_localizations.dart'; enum Coins { monero, unknown } abstract class Coin { + static late AppLocalizations L; Coins get type => Coins.unknown; CoinStrings get strings; + String get uriScheme; + bool get isEnabled; Future> get coinWallets; diff --git a/lib/coins/abstract/wallet.dart b/lib/coins/abstract/wallet.dart index 7c846b6..5324292 100644 --- a/lib/coins/abstract/wallet.dart +++ b/lib/coins/abstract/wallet.dart @@ -1,6 +1,5 @@ import 'package:cupcake/coins/abstract/coin.dart'; import 'package:cupcake/coins/abstract/wallet_seed_detail.dart'; -import 'package:cupcake/l10n/app_localizations.dart'; import 'package:cupcake/utils/urqr.dart'; import 'package:flutter/widgets.dart'; @@ -42,6 +41,5 @@ abstract class CoinWallet { Future close(); - Future> seedDetails(final AppLocalizations L) => - throw UnimplementedError(); + Future> seedDetails() => throw UnimplementedError(); } diff --git a/lib/coins/monero/coin.dart b/lib/coins/monero/coin.dart index ab4672c..843bb23 100644 --- a/lib/coins/monero/coin.dart +++ b/lib/coins/monero/coin.dart @@ -18,6 +18,9 @@ import 'package:path/path.dart' as p; class Monero implements Coin { static List wPtrList = []; + @override + String get uriScheme => 'monero'; + @override bool get isEnabled { try { @@ -74,21 +77,23 @@ class Monero implements Coin { // Prevent user from slipping outside allowed directory final String walletPath = p.join(baseDir.path, walletName); if (!walletPath.startsWith(baseDir.path)) { - throw Exception("Illegal wallet name: $walletName"); + throw Exception(Coin.L.error_illegal_wallet_name(walletName)); } return walletPath; } @override - Future openWallet(final CoinWalletInfo walletInfo, - {required final String password}) async { + Future openWallet( + final CoinWalletInfo walletInfo, { + required final String password, + }) async { for (final wptr in wPtrList) { monero.WalletManager_closeWallet(wmPtr, wptr, true); } wPtrList.clear(); final walletExist = monero.WalletManager_walletExists(wmPtr, walletInfo.walletName); if (!walletExist) { - throw Exception("Given wallet doesn't exist (${walletInfo.walletName})"); + throw Exception(Coin.L.error_wallet_doesnt_exist(walletInfo.walletName)); } final wptr = monero.WalletManager_openWallet(wmPtr, path: walletInfo.walletName, password: password); diff --git a/lib/coins/monero/creation/common.dart b/lib/coins/monero/creation/common.dart index b038c26..314bd20 100644 --- a/lib/coins/monero/creation/common.dart +++ b/lib/coins/monero/creation/common.dart @@ -19,12 +19,12 @@ import 'package:cupcake/utils/form/string_form_element.dart'; import 'package:cupcake/utils/form/validators.dart'; class MoneroWalletCreation extends WalletCreation { - static MoneroWalletCreation? _instance; - MoneroWalletCreation._internal(this.L); factory MoneroWalletCreation(final AppLocalizations L) { _instance ??= MoneroWalletCreation._internal(L); return _instance!; } + MoneroWalletCreation._internal(this.L); + static MoneroWalletCreation? _instance; @override Future wipe() async { @@ -120,9 +120,13 @@ class MoneroWalletCreation extends WalletCreation { @override Future create( - final CreateMethod createMethod, final String walletName, final String walletPassword) async { + final CreateMethod createMethod, + final String walletName, + final String walletPassword, + ) async { if (createMethod == CreateMethod.create) { return CreateMoneroWalletCreationMethod( + L, progressCallback: null, walletPath: coin.getPathForWallet(walletName), walletPassword: walletPassword, @@ -132,12 +136,13 @@ class MoneroWalletCreation extends WalletCreation { if ((await seed.value).isNotEmpty && (await secretSpendKey.value).isNotEmpty) { // This shouldn't happen because of how UI works, but I'd like to be extra sure about it - throw Exception("Tried to restore from seed and key at the same time"); + throw Exception(L.warning_restore_from_seed_and_key); } if ((await seed.value).isNotEmpty) { switch ((await seed.value).split(" ").length) { case 16: return RestorePolyseedMoneroWalletCreationMethod( + L, walletPath: coin.getPathForWallet(walletName), walletPassword: walletPassword, seed: await seed.value, @@ -145,13 +150,14 @@ class MoneroWalletCreation extends WalletCreation { ).create(); case 25: return RestoreLegacyWalletCreationMethod( + L, walletPath: coin.getPathForWallet(walletName), walletPassword: walletPassword, seed: await seed.value, seedOffsetOrEncryption: await seedOffset.value, ).create(); default: - throw Exception("Incorrent number of words present in the seed"); + throw Exception(L.warning_input_seed_length_invalid); } } @@ -160,6 +166,7 @@ class MoneroWalletCreation extends WalletCreation { print(await secretViewKey.value); return RestoreFromKeysMoneroWalletCreationMethod( + L, walletPath: coin.getPathForWallet(walletName), walletPassword: walletPassword, walletAddress: await walletAddress.value, diff --git a/lib/coins/monero/creation/new_wallet.dart b/lib/coins/monero/creation/new_wallet.dart index 99de5d2..e3b647b 100644 --- a/lib/coins/monero/creation/new_wallet.dart +++ b/lib/coins/monero/creation/new_wallet.dart @@ -2,17 +2,21 @@ import 'package:cupcake/coins/abstract/wallet_creation.dart'; import 'package:cupcake/coins/monero/cache_keys.dart'; import 'package:cupcake/coins/monero/coin.dart'; import 'package:cupcake/coins/monero/wallet_info.dart'; +import 'package:cupcake/l10n/app_localizations.dart'; import 'package:cupcake/utils/types.dart'; import 'package:path/path.dart' as p; import 'package:monero/monero.dart' as monero; class CreateMoneroWalletCreationMethod extends CreationMethod { - CreateMoneroWalletCreationMethod({ + CreateMoneroWalletCreationMethod( + this.L, { required this.walletPath, required this.walletPassword, required this.seedOffsetOrEncryption, this.progressCallback, }); + final AppLocalizations L; + final ProgressCallback? progressCallback; final String walletPath; final String walletPassword; @@ -20,9 +24,9 @@ class CreateMoneroWalletCreationMethod extends CreationMethod { @override Future create() async { - progressCallback?.call(description: "Generating polyseed"); + progressCallback?.call(description: L.generating_polyseed); final newSeed = monero.Wallet_createPolyseed(); - progressCallback?.call(description: "Creating wallet"); + progressCallback?.call(description: L.creating_wallet); final newWptr = monero.WalletManager_createWalletFromPolyseed( Monero.wmPtr, path: walletPath, @@ -33,7 +37,7 @@ class CreateMoneroWalletCreationMethod extends CreationMethod { restoreHeight: 0, kdfRounds: 1, ); - progressCallback?.call(description: "Checking status"); + progressCallback?.call(description: L.checking_status); final status = monero.Wallet_status(newWptr); if (status != 0) { final error = monero.Wallet_errorString(newWptr); @@ -51,7 +55,7 @@ class CreateMoneroWalletCreationMethod extends CreationMethod { monero.Wallet_store(newWptr); monero.Wallet_store(newWptr); Monero.wPtrList.add(newWptr); - progressCallback?.call(description: "Wallet created"); + progressCallback?.call(description: L.wallet_created); final wallet = await Monero().openWallet( MoneroWalletInfo(p.basename(walletPath)), password: walletPassword, diff --git a/lib/coins/monero/creation/restore_keys.dart b/lib/coins/monero/creation/restore_keys.dart index 35133c8..25f85b8 100644 --- a/lib/coins/monero/creation/restore_keys.dart +++ b/lib/coins/monero/creation/restore_keys.dart @@ -3,12 +3,14 @@ import 'dart:ffi'; import 'package:cupcake/coins/abstract/wallet_creation.dart'; import 'package:cupcake/coins/monero/coin.dart'; import 'package:cupcake/coins/monero/wallet_info.dart'; +import 'package:cupcake/l10n/app_localizations.dart'; import 'package:cupcake/utils/types.dart'; import 'package:path/path.dart' as p; import 'package:monero/monero.dart' as monero; class RestoreFromKeysMoneroWalletCreationMethod extends CreationMethod { - RestoreFromKeysMoneroWalletCreationMethod({ + RestoreFromKeysMoneroWalletCreationMethod( + this.L, { required this.walletPath, required this.walletPassword, required this.walletAddress, @@ -17,6 +19,8 @@ class RestoreFromKeysMoneroWalletCreationMethod extends CreationMethod { required this.restoreHeight, this.progressCallback, }); + + final AppLocalizations L; final ProgressCallback? progressCallback; final String walletPath; final String walletPassword; @@ -27,10 +31,7 @@ class RestoreFromKeysMoneroWalletCreationMethod extends CreationMethod { @override Future create() async { - progressCallback?.call(description: "Creating wallet"); - print("create: $walletAddress"); - print("create: $secretSpendKey"); - print("create: $secretViewKey"); + progressCallback?.call(description: L.creating_wallet); Pointer newWptr; if (secretViewKey.isNotEmpty) { newWptr = monero.WalletManager_createWalletFromKeys( @@ -53,7 +54,7 @@ class RestoreFromKeysMoneroWalletCreationMethod extends CreationMethod { restoreHeight: restoreHeight, ); } - progressCallback?.call(description: "Checking status"); + progressCallback?.call(description: L.checking_status); int status = monero.Wallet_status(newWptr); if (status != 0) { // Fallback to createDeterministicWallet in case when createWalletFromKeys didn't work. @@ -79,7 +80,7 @@ class RestoreFromKeysMoneroWalletCreationMethod extends CreationMethod { } monero.Wallet_store(newWptr); monero.Wallet_store(newWptr); - progressCallback?.call(description: "Wallet created"); + progressCallback?.call(description: L.wallet_created); final wallet = await Monero().openWallet( MoneroWalletInfo(p.basename(walletPath)), password: walletPassword, diff --git a/lib/coins/monero/creation/restore_legacy.dart b/lib/coins/monero/creation/restore_legacy.dart index 5ad9876..6bb57e1 100644 --- a/lib/coins/monero/creation/restore_legacy.dart +++ b/lib/coins/monero/creation/restore_legacy.dart @@ -1,18 +1,22 @@ import 'package:cupcake/coins/abstract/wallet_creation.dart'; import 'package:cupcake/coins/monero/coin.dart'; import 'package:cupcake/coins/monero/wallet_info.dart'; +import 'package:cupcake/l10n/app_localizations.dart'; import 'package:cupcake/utils/types.dart'; import 'package:path/path.dart' as p; import 'package:monero/monero.dart' as monero; class RestoreLegacyWalletCreationMethod extends CreationMethod { - RestoreLegacyWalletCreationMethod({ + RestoreLegacyWalletCreationMethod( + this.L, { required this.walletPath, required this.walletPassword, required this.seed, required this.seedOffsetOrEncryption, this.progressCallback, }); + final AppLocalizations L; + final ProgressCallback? progressCallback; final String walletPath; final String walletPassword; @@ -21,7 +25,7 @@ class RestoreLegacyWalletCreationMethod extends CreationMethod { @override Future create() async { - progressCallback?.call(description: "Creating wallet"); + progressCallback?.call(description: L.creating_wallet); final newWptr = monero.WalletManager_recoveryWallet( Monero.wmPtr, path: walletPath, @@ -32,7 +36,7 @@ class RestoreLegacyWalletCreationMethod extends CreationMethod { kdfRounds: 1, ); Monero.wPtrList.add(newWptr); - progressCallback?.call(description: "Checking status"); + progressCallback?.call(description: L.checking_status); final status = monero.Wallet_status(newWptr); if (status != 0) { final error = monero.Wallet_errorString(newWptr); @@ -44,7 +48,7 @@ class RestoreLegacyWalletCreationMethod extends CreationMethod { } monero.Wallet_store(newWptr); monero.Wallet_store(newWptr); - progressCallback?.call(description: "Wallet created"); + progressCallback?.call(description: L.wallet_created); final wallet = await Monero().openWallet( MoneroWalletInfo(p.basename(walletPath)), password: walletPassword, diff --git a/lib/coins/monero/creation/restore_polyseed.dart b/lib/coins/monero/creation/restore_polyseed.dart index e23cfb5..9d2cf4e 100644 --- a/lib/coins/monero/creation/restore_polyseed.dart +++ b/lib/coins/monero/creation/restore_polyseed.dart @@ -2,19 +2,23 @@ import 'package:cupcake/coins/abstract/wallet_creation.dart'; import 'package:cupcake/coins/monero/cache_keys.dart'; import 'package:cupcake/coins/monero/coin.dart'; import 'package:cupcake/coins/monero/wallet_info.dart'; +import 'package:cupcake/l10n/app_localizations.dart'; import 'package:cupcake/utils/types.dart'; import 'package:path/path.dart' as p; import 'package:monero/monero.dart' as monero; import 'package:polyseed/polyseed.dart'; class RestorePolyseedMoneroWalletCreationMethod extends CreationMethod { - RestorePolyseedMoneroWalletCreationMethod({ + RestorePolyseedMoneroWalletCreationMethod( + this.L, { required this.walletPath, required this.walletPassword, required this.seed, required this.seedOffsetOrEncryption, this.progressCallback, }); + final AppLocalizations L; + final ProgressCallback? progressCallback; final String walletPath; final String walletPassword; @@ -23,7 +27,7 @@ class RestorePolyseedMoneroWalletCreationMethod extends CreationMethod { @override Future create() async { - progressCallback?.call(description: "Restoring wallet"); + progressCallback?.call(description: L.restoring_wallet); final lang = PolyseedLang.getByPhrase(seed); const coin = PolyseedCoin.POLYSEED_MONERO; final dartPolyseed = Polyseed.decode(seed, lang, coin); @@ -33,7 +37,7 @@ class RestorePolyseedMoneroWalletCreationMethod extends CreationMethod { return CreationOutcome( method: CreateMethod.restore, success: false, - message: "seed offset is empty, but polyseed is encrypted", + message: L.warning_seed_offset_empty_polyseed_encrypted, ); } dartPolyseed.crypt(seedOffsetOrEncryption); @@ -51,7 +55,7 @@ class RestorePolyseedMoneroWalletCreationMethod extends CreationMethod { kdfRounds: 1, ); Monero.wPtrList.add(newWptr); - progressCallback?.call(description: "Checking status"); + progressCallback?.call(description: L.checking_status); final status = monero.Wallet_status(newWptr); if (status != 0) { final error = monero.Wallet_errorString(newWptr); @@ -61,11 +65,14 @@ class RestorePolyseedMoneroWalletCreationMethod extends CreationMethod { message: error, ); } - monero.Wallet_setCacheAttribute(newWptr, - key: MoneroCacheKeys.seedOffsetCacheKey, value: seedOffsetOrEncryption); + monero.Wallet_setCacheAttribute( + newWptr, + key: MoneroCacheKeys.seedOffsetCacheKey, + value: seedOffsetOrEncryption, + ); monero.Wallet_store(newWptr); monero.Wallet_store(newWptr); - progressCallback?.call(description: "Wallet created"); + progressCallback?.call(description: L.wallet_created); final wallet = await Monero().openWallet( MoneroWalletInfo(p.basename(walletPath)), password: walletPassword, diff --git a/lib/coins/monero/wallet.dart b/lib/coins/monero/wallet.dart index 7764416..1d1911a 100644 --- a/lib/coins/monero/wallet.dart +++ b/lib/coins/monero/wallet.dart @@ -8,7 +8,6 @@ import 'package:cupcake/coins/monero/coin.dart'; import 'package:cupcake/coins/monero/amount.dart'; import 'package:cupcake/coins/monero/cache_keys.dart'; import 'package:cupcake/utils/types.dart'; -import 'package:cupcake/l10n/app_localizations.dart'; import 'package:cupcake/utils/config.dart'; import 'package:cupcake/utils/null_if_empty.dart'; import 'package:cupcake/utils/secure_storage.dart'; @@ -43,7 +42,7 @@ class MoneroWallet implements CoinWallet { @override void setAccount(final int accountIndex) { if (_accountIndex < getAccountsCount()) { - throw Exception("Given index is larger than current account count"); + throw Exception(Coin.L.error_account_index_higher_than_count); } _accountIndex = accountIndex; save(); @@ -70,16 +69,20 @@ class MoneroWallet implements CoinWallet { String getBalanceString() => (getBalance() / 1e12).toStringAsFixed(12); Future exportKeyImagesUR(final BuildContext context) async { - final allImages = monero.Wallet_exportKeyImagesUR(wptr, - max_fragment_length: CupcakeConfig.instance.maxFragmentLength, all: true) - .split("\n"); - final someImages = monero.Wallet_exportKeyImagesUR(wptr, - max_fragment_length: CupcakeConfig.instance.maxFragmentLength, all: false) - .split("\n"); + final allImages = monero.Wallet_exportKeyImagesUR( + wptr, + max_fragment_length: CupcakeConfig.instance.maxFragmentLength, + all: true, + ).split("\n"); + final someImages = monero.Wallet_exportKeyImagesUR( + wptr, + max_fragment_length: CupcakeConfig.instance.maxFragmentLength, + all: false, + ).split("\n"); await AnimatedURPage( urqrList: { - "Partial Key Images": someImages, - "All Key Images": allImages, + Coin.L.partial_key_images: someImages, + Coin.L.all_key_images: allImages, }, ).push(context); } @@ -118,7 +121,7 @@ class MoneroWallet implements CoinWallet { .toList(); final addrs = monero.UnsignedTransaction_recipientAddress(txptr).split(";"); if (amts.length != addrs.length) { - throw CoinException("Amount and address length is not equal."); + throw CoinException(Coin.L.error_amount_and_address_count_not_equal); } for (int i = 0; i < amts.length; i++) { destMap[Address(addrs[i])] = MoneroAmount(amts[i]); @@ -148,7 +151,7 @@ class MoneroWallet implements CoinWallet { ).push(context); save(); default: - throw UnimplementedError("Unable to handle ${ur.tag}."); + throw UnimplementedError(Coin.L.error_ur_tag_unsupported(ur.tag)); } } @@ -210,65 +213,65 @@ class MoneroWallet implements CoinWallet { ); @override - Future> seedDetails(final AppLocalizations L) async { + Future> seedDetails() async { final secrets = await secureStorage.readAll(); return [ WalletSeedDetail( type: WalletSeedDetailType.text, - name: L.primary_address_label, + name: Coin.L.primary_address_label, value: monero.Wallet_address(wptr, accountIndex: 0, addressIndex: 0), ), if ((polyseed ?? "").isNotEmpty) WalletSeedDetail( type: WalletSeedDetailType.text, - name: L.seed_screen_wallet_seed_polyseed, + name: Coin.L.seed_screen_wallet_seed_polyseed, value: polyseed!, ), if ((polyseedDart ?? "").isNotEmpty && polyseedDart != polyseed) WalletSeedDetail( type: WalletSeedDetailType.text, - name: L.seed_screen_wallet_seed_polyseed_encrypted, + name: Coin.L.seed_screen_wallet_seed_polyseed_encrypted, value: polyseedDart!, ), WalletSeedDetail( type: WalletSeedDetailType.text, - name: L.seed_screen_wallet_seed_legacy, + name: Coin.L.seed_screen_wallet_seed_legacy, value: legacySeed, ), if (seedOffset.isNotEmpty) WalletSeedDetail( type: WalletSeedDetailType.text, - name: L.seed_offset, + name: Coin.L.seed_offset, value: seedOffset, ), WalletSeedDetail( type: WalletSeedDetailType.text, - name: L.view_key, + name: Coin.L.view_key, value: monero.Wallet_publicViewKey(wptr), ), WalletSeedDetail( type: WalletSeedDetailType.text, - name: L.secret_view_key, + name: Coin.L.secret_view_key, value: monero.Wallet_secretViewKey(wptr), ), WalletSeedDetail( type: WalletSeedDetailType.text, - name: L.spend_key, + name: Coin.L.spend_key, value: monero.Wallet_publicSpendKey(wptr), ), WalletSeedDetail( type: WalletSeedDetailType.text, - name: L.secret_spend_key, + name: Coin.L.secret_spend_key, value: monero.Wallet_secretSpendKey(wptr), ), WalletSeedDetail( type: WalletSeedDetailType.text, - name: L.restore_height, + name: Coin.L.restore_height, value: monero.Wallet_getRefreshFromBlockHeight(wptr).toString(), ), WalletSeedDetail( type: WalletSeedDetailType.qr, - name: L.view_only_restore_qr, + name: Coin.L.view_only_restore_qr, value: const JsonEncoder.withIndent(' ').convert({ "version": 0, "primaryAddress": monero.Wallet_address(wptr, accountIndex: 0, addressIndex: 0), @@ -282,7 +285,10 @@ class MoneroWallet implements CoinWallet { (final index) { final key = secrets.keys.elementAt(index); return WalletSeedDetail( - type: WalletSeedDetailType.text, name: key, value: secrets[key] ?? "unknown"); + type: WalletSeedDetailType.text, + name: key, + value: secrets[key] ?? "unknown", + ); }, ), if (CupcakeConfig.instance.debug) @@ -291,10 +297,12 @@ class MoneroWallet implements CoinWallet { (final index) { final key = CupcakeConfig.instance.toJson().keys.elementAt(index); return WalletSeedDetail( - type: WalletSeedDetailType.text, - name: key, - value: const JsonEncoder.withIndent(' ') - .convert(CupcakeConfig.instance.toJson()[key])); + type: WalletSeedDetailType.text, + name: key, + value: const JsonEncoder.withIndent(' ').convert( + CupcakeConfig.instance.toJson()[key], + ), + ); }, ), ]; diff --git a/lib/coins/monero/wallet_info.dart b/lib/coins/monero/wallet_info.dart index f2f0eb8..06590ab 100644 --- a/lib/coins/monero/wallet_info.dart +++ b/lib/coins/monero/wallet_info.dart @@ -46,28 +46,31 @@ class MoneroWalletInfo extends CoinWalletInfo { } @override - Future openWallet(final BuildContext context, - {required final String password}) async { - return await coin.openWallet( + Future openWallet( + final BuildContext context, { + required final String password, + }) { + return coin.openWallet( this, password: password, ); } @override - Future deleteWallet() async { + Future deleteWallet() { for (final element in Monero.wPtrList) { monero.WalletManager_closeWallet(Monero.wmPtr, element, true); } Monero.wPtrList.clear(); File(walletName).deleteSync(); File("$walletName.keys").deleteSync(); + return Future.value(); } @override Future renameWallet(final String newName) async { if (p.basename(walletName) == newName) { - throw Exception("Wallet wasn't renamed"); + throw Exception(Coin.L.error_wallet_name_unchanged); } for (final element in Monero.wPtrList) { monero.WalletManager_closeWallet(Monero.wmPtr, element, true); @@ -76,6 +79,8 @@ class MoneroWalletInfo extends CoinWalletInfo { final basePath = p.dirname(walletName); File(walletName).copySync(p.join(basePath, newName)); File("$walletName.keys").copySync(p.join(basePath, "$newName.keys")); + // Copy and delete later, if anything throws below we end up with copied walled, + // instead of nuking the wallet File(walletName).deleteSync(); File("$walletName.keys").deleteSync(); _walletName = newName; diff --git a/lib/dev/generate_rebuild.dart b/lib/dev/generate_rebuild.dart index 4a2b3c1..45cace6 100644 --- a/lib/dev/generate_rebuild.dart +++ b/lib/dev/generate_rebuild.dart @@ -5,19 +5,19 @@ class GenerateRebuild { } class RebuildOnChange { - static const name = 'RebuildOnChange'; const RebuildOnChange(); + static const name = 'RebuildOnChange'; } class ThrowOnUI { + const ThrowOnUI({this.message, this.L}); static const name = 'ThrowOnUI'; final String? L; final String? message; - const ThrowOnUI({this.message, this.L}); } class ExposeRebuildableAccessors { + const ExposeRebuildableAccessors({this.extraCode}); static const name = 'ExposeRebuildableAccessors'; final String? extraCode; - const ExposeRebuildableAccessors({this.extraCode}); } diff --git a/lib/dev/rebuild_generator.dart b/lib/dev/rebuild_generator.dart index e9890f8..4dc28d2 100644 --- a/lib/dev/rebuild_generator.dart +++ b/lib/dev/rebuild_generator.dart @@ -12,7 +12,10 @@ import 'package:cupcake/utils/capitalize.dart'; class RebuildClassGenerator extends GeneratorForAnnotation { @override FutureOr generateForAnnotatedElement( - final Element element, final ConstantReader annotation, final BuildStep buildStep) async { + final Element element, + final ConstantReader annotation, + final BuildStep buildStep, + ) async { if (element is! ClassElement) { throw InvalidGenerationSourceError( 'Generator cannot target `${element.displayName}`. ' diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index a16aaa3..d56adf5 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1,71 +1,118 @@ { - "welcome_to": "Welcome to", - "cupcake_slogan": "Keep your crypto even safer", + "about_the_app": "About the app", + "address_book": "Address book", + "advanced_options": "Advanced Options", + "all_key_images": "All key images", + "balance": "Balance", + "biometric_authenticaion_reason": "Unlock cupcake", + "biometric_enabled": "Biometric authentication got enabled", + "cancel": "Cancel", + "canceling": "Canceling", + "checking_status": "Checking status", + "confirm": "Confirm", + "confirming": "Confirming", + "copied": "Copied {what}", + "copy": "Copy", "create_new_wallet": "Create new wallet", - "restore_wallet": "Restore wallet", "create_wallet": "Create Wallet", - "wallet_name": "Wallet name", - "polyseed": "Polyseed (16 word)", - "next": "Next", - "advanced_options": "Advanced Options", + "creating_wallet": "Creating wallet", + "cupcake_slogan": "Keep your crypto even safer", + "delete": "Delete", + "delete_wallet": "Delete wallet", + "edit_wallet": "Edit wallet", + "enter_password": "Enter Password", + "error_account_index_higher_than_count": "Given index is larger than current account count", + "error_amount_and_address_count_not_equal": "Amount and address length is not equal.", + "error_context_not_mounted": "Context is not mounted", + "error_didnt_authenticate": "User didn't authenticate", + "error_failed_to_setup": "Failed to complete setup", + "error_handling_urqr_scan": "Error handling URQR scan", + "error_illegal_wallet_name": "Illegal wallet name: {walletName}", + "error_no_biometric_authentication": "No usable biometric authentication found", + "error_no_secure_biometric": "No secure biometric authentication found", + "error_selected_coin_null": "Current selected coin is null", + "error_status_is_failure_no_message": "Wallet creation failed, status indicates failure but no message was provided", + "error_unable_to_cancel": "Unable to cancel transaction", + "error_unable_to_confirm_transaction": "Unable to confirm transaction", + "error_unable_to_create_wallets_using_any_known_methods": "Unable to create wallet using any known methods", + "error_unknown_form_element": "Unknown form element: {type}", + "error_ur_tag_unsupported": "Unable to handle {urTag}", + "error_wallet_doesnt_exist": "Given wallet doesn't exist {walletName}", + "error_wallet_is_null_no_indication_of_failure": "Wallet creation failed, and status indicated failure but message was empty", + "error_wallet_name_empty": "Wallet name is empty", + "error_wallet_name_unchanged": "Wallet wasn't renamed because the name is not different", + "export_key_images": "Export key images", + "generating_polyseed": "Generating polyseed", + "home_no_wallets": "You don't have any wallets, consider creating one.", "important": "IMPORTANT", "important_seed_backup_info": "On the next page you will see a series of {seedDescription}. This is your unique and private seed and it is the ONLY way to recover your wallet in case of loss or malfunction. It is YOUR responsibility to write it down and store it in a safe place outside of the Cupcake app.", - "@important_seed_backup_info": { - "description": "seedDescription should contain phrase like '16 words' or '64 characters", - "placeholder": { - "seedDescription": "String" - } - }, - "understand_show_seed": "I understand. Show me the seed.", - "write_down_notice": "Please write these down in case you lose or wipe your phone", - "save": "Save", - "copy": "Copy", + "invalid_password": "Invalid password", + "next": "Next", + "opening_wallet": "Opening wallet", + "option_create_keys": "Restore from keys", + "option_create_new_wallet": "Create new wallet", + "option_create_seed": "Restore from seed", + "other_settings": "Other settings", + "partial_key_images": "Partial Key Images", + "password_doesnt_match": "Passwords doesn't match", + "polyseed": "Polyseed (16 word)", + "primary_account_label": "Primary account", + "primary_address_label": "Primary address", "receive": "Receive", + "rename": "Rename", + "rename_wallet": "Rename wallet", + "restore_height": "Restore height", + "restore_wallet": "Restore wallet", + "restoring_wallet": "Restoring wallet", + "save": "Save", "scan": "Scan", - "enter_password": "Enter Password", - "wallets": "Wallets", - "address_book": "Address book", + "secret_spend_key": "Secret spend key", + "secret_view_key": "Secret view key", "security_and_backup": "Security and backup", - "export_key_images": "Export key images", - "other_settings": "Other settings", - "select_wallet": "Select wallet", - "warning_input_cannot_be_null": "Input cannot be null", - "warning_input_cannot_be_empty": "Input cannot be empty", - "warning_password_too_short": "Password needs to be at least 4 characters long", - "warning_seed_incorrect_length": "Seed needs to contain exactly 16 or 25 words", - "warning_must_be_a_number": "Input must be a number", + "seed": "Seed", + "seed_length_16_word": "16 word", + "seed_offset": "Seed offset", + "seed_screen_wallet_seed_legacy": "Wallet seed (Legacy)", + "seed_screen_wallet_seed_polyseed": "Wallet seed (Polyseed)", + "seed_screen_wallet_seed_polyseed_encrypted": "Wallet seed (Polyseed [Encrypted])", "seed_type": "Seed type", - "seed_type_polyseed": "Polyseed (16 word)", "seed_type_legacy": "Legacy (25 word)", - "option_create_new_wallet": "Create new wallet", - "option_create_keys": "Restore from keys", - "option_create_seed": "Restore from seed", - "seed": "Seed", - "opening_wallet": "Opening wallet", + "seed_type_polyseed": "Polyseed (16 word)", + "select_wallet": "Select wallet", + "settings": "Settings", + "settings_biometricsEnabled_disabled": "In order to enable biometrics long press confirm button when entering pin", + "settings_biometricsEnabled_enabled": "Biometrics are enabled", + "settings_biometricsEnabled_title": "Biometric authentication", + "settings_canUseInsecureBiometric_disabled": "Click to enable insecure biometric authentication.", + "settings_canUseInsecureBiometric_enabled": "Insecure biometric authentication is enabled, it is not recommended and could lead to loss of funds. Make sure that you understand the drawbacks, and when in doubt - keep this setting disabled.", + "settings_canUseInsecureBiometric_title": "Insecure biometric auth", + "settings_debug_disabled": "Debug options are disabled", + "settings_debug_enabled": "Debug options are enabled", + "settings_debug_title": "Debug", + "settings_maxFragmentLength_hint": "How many characters of data should fit within a single QR code", + "settings_maxFragmentLength_title": "Max data for QR code", + "settings_msForQrCode_hint": "How many milliseconds should one QR code last before switching to next one", + "settings_msForQrCode_title": "Duration of URQR code", + "spend_key": "Spend key", + "understand_show_seed": "I understand. Show me the seed.", + "view_key": "View Key", + "view_only_restore_qr": "Restore View-Only Wallet", + "wallet_created": "Wallet created", "wallet_details": "Wallet details", - "creating_wallet": "Creating wallet", - "home_no_wallets": "You don't have any wallets, consider creating one.", - "primary_address_label": "Primary address", - "primary_account_label": "Primary account", - "confirm": "Confirm", - "cancel": "Cancel", - "confirming": "Confirming", - "canceling": "Canceling", - "balance": "Balance", - "wallet_seed": "Wallet seed", + "wallet_name": "Wallet name", "wallet_passphrase": "Wallet passphrase", "wallet_password": "Wallet password", "wallet_password_repeat": "Wallet password (repeat)", - "secret_spend_key": "Secret spend key", - "spend_key": "Spend key", - "secret_view_key": "Secret view key", - "view_key": "View Key", - "restore_height": "Restore height", - "seed_offset": "Seed offset", - "view_only_restore_qr": "Restore View-Only Wallet", - "seed_length_16_word": "16 word", - "seed_screen_wallet_seed_polyseed": "Wallet seed (Polyseed)", - "seed_screen_wallet_seed_polyseed_encrypted": "Wallet seed (Polyseed [Encrypted])", - "seed_screen_wallet_seed_legacy": "Wallet seed (Legacy)", - "password_doesnt_match": "Passwords doesn't match" -} \ No newline at end of file + "wallet_seed": "Wallet seed", + "wallets": "Wallets", + "warning_input_cannot_be_empty": "Input cannot be empty", + "warning_input_cannot_be_null": "Input cannot be null", + "warning_input_seed_length_invalid": "Incorrent number of words present in the seed", + "warning_must_be_a_number": "Input must be a number", + "warning_password_too_short": "Password needs to be at least 4 characters long", + "warning_restore_from_seed_and_key": "Tried to restore from seed and key at the same time", + "warning_seed_incorrect_length": "Seed needs to contain exactly 16 or 25 words", + "warning_seed_offset_empty_polyseed_encrypted": "seed offset is empty, but polyseed is encrypted", + "welcome_to": "Welcome to", + "write_down_notice": "Please write these down in case you lose or wipe your phone" +} diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 20472a2..e60e587 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -1,13 +1,19 @@ { - "welcome_to": "Witaj w", - "cupcake_slogan": "Przechowuj swoje kryptowaluty jeszcze bezpieczniej", + "address_book": "Książka adresowa", + "advanced_options": "Opcje zaawansowane", + "balance": "Saldo", + "cancel": "Anuluj", + "canceling": "Anulowanie", + "confirm": "Potwierdź", + "confirming": "Potwierdzanie", + "copy": "Kopiuj", "create_new_wallet": "Stwórz nowy portfel", - "restore_wallet": "Przywróć portfel", "create_wallet": "Stwórz Portfel", - "wallet_name": "Nazwa portfela", - "polyseed": "Polyseed (16 słów)", - "next": "Następny", - "advanced_options": "Opcje zaawansowane", + "creating_wallet": "Tworzenie portfela", + "cupcake_slogan": "Przechowuj swoje kryptowaluty jeszcze bezpieczniej", + "enter_password": "Wpisz Hasło", + "export_key_images": "Exportuj \"key images\"", + "home_no_wallets": "Nie masz żadnego portfela, rozważ utworzenie nowego", "important": "WAŻNE", "important_seed_backup_info": "Na następnej stronie zobaczysz serię {seedDescription}. To jest twój unikalny oraz prywatny klucz, który jest JEDYNYM sposobem na odzyskanie portfela w wypadku uszkodzanie, utraty dostępu do urządzenia lub awarii. Upewnienie się, że ten klucz jest przechowywany w bezpiecznym miejscu poza aplikacją Cupcake jest TWOJĄ odpowiedzialnością.", "@important_seed_backup_info": { @@ -16,55 +22,49 @@ "seedDescription": "String" } }, - "understand_show_seed": "Rozumiem. Pokaż mi mój klucz.", - "write_down_notice": "Zapisz ten klucz na wypadek utraty dostępu do aplikacji", - "save": "Zapisz", - "copy": "Kopiuj", + "next": "Następny", + "opening_wallet": "Otwieranie portfela", + "option_create_keys": "Odzyskaj z kluczy", + "option_create_new_wallet": "Utwórz nowy portfel", + "option_create_seed": "Odzyskaj z frazy", + "other_settings": "Inne ustawienia", + "password_doesnt_match": "Hasła się różnią", + "polyseed": "Polyseed (16 słów)", + "primary_account_label": "Konto główne", + "primary_address_label": "Adres główny", "receive": "Wpłać", + "restore_height": "Wysokość przywracania", + "restore_wallet": "Przywróć portfel", + "save": "Zapisz", "scan": "Skanuj", - "enter_password": "Wpisz Hasło", - "wallets": "Portfele", - "address_book": "Książka adresowa", + "secret_spend_key": "Tajny klucz wydatków", + "secret_view_key": "Tajny klucz podglądu", "security_and_backup": "Bezpieczeństwo i kopia", - "export_key_images": "Exportuj \"key images\"", - "other_settings": "Inne ustawienia", - "select_wallet": "Wybierz portfel", - "warning_input_cannot_be_null": "Pole nie może być puste", - "warning_input_cannot_be_empty": "Pole nie może być puste", - "warning_password_too_short": "Hasło musi mieć conajmniej 4 znaki", - "warning_seed_incorrect_length": "Fraza odzyskiwania musi mieć dokładnie 16 lub 25 słów", - "warning_must_be_a_number": "W pole należy wprowadzić liczbę", + "seed": "Seed", + "seed_offset": "Szyfrowanie frazy odzyskiwania", + "seed_screen_wallet_seed_legacy": "Fraza odzyskiwania (Legacy)", + "seed_screen_wallet_seed_polyseed": "Fraza odzyskiwania (Polyseed)", + "seed_screen_wallet_seed_polyseed_encrypted": "Fraza odzyskiwania (Polyseed [Szyfrowana])", "seed_type": "Typ frazy odzyskiwania", - "seed_type_polyseed": "Polyseed (16 słów)", "seed_type_legacy": "Legacy (25 słów)", - "option_create_new_wallet": "Utwórz nowy portfel", - "option_create_keys": "Odzyskaj z kluczy", - "option_create_seed": "Odzyskaj z frazy", - "seed": "Seed", - "opening_wallet": "Otwieranie portfela", + "seed_type_polyseed": "Polyseed (16 słów)", + "select_wallet": "Wybierz portfel", + "spend_key": "Klucz wydatków", + "understand_show_seed": "Rozumiem. Pokaż mi mój klucz.", + "view_key": "Klucz podglądu", + "view_only_restore_qr": "Kod QR portfela podglądu", "wallet_details": "Szczegóły portfela", - "creating_wallet": "Tworzenie portfela", - "home_no_wallets": "Nie masz żadnego portfela, rozważ utworzenie nowego", - "primary_address_label": "Adres główny", - "primary_account_label": "Konto główne", - "confirm": "Potwierdź", - "cancel": "Anuluj", - "confirming": "Potwierdzanie", - "canceling": "Anulowanie", - "balance": "Saldo", - "wallet_seed": "Fraza odzyskiwania", + "wallet_name": "Nazwa portfela", "wallet_passphrase": "Hasło frazy odzyskiwania", "wallet_password": "Hasło portfela", "wallet_password_repeat": "Hasło portfela (ponownie)", - "secret_spend_key": "Tajny klucz wydatków", - "spend_key": "Klucz wydatków", - "secret_view_key": "Tajny klucz podglądu", - "view_key": "Klucz podglądu", - "restore_height": "Wysokość przywracania", - "seed_offset": "Szyfrowanie frazy odzyskiwania", - "view_only_restore_qr": "Kod QR portfela podglądu", - "seed_screen_wallet_seed_polyseed": "Fraza odzyskiwania (Polyseed)", - "seed_screen_wallet_seed_polyseed_encrypted": "Fraza odzyskiwania (Polyseed [Szyfrowana])", - "seed_screen_wallet_seed_legacy": "Fraza odzyskiwania (Legacy)", - "password_doesnt_match": "Hasła się różnią" -} \ No newline at end of file + "wallet_seed": "Fraza odzyskiwania", + "wallets": "Portfele", + "warning_input_cannot_be_empty": "Pole nie może być puste", + "warning_input_cannot_be_null": "Pole nie może być puste", + "warning_must_be_a_number": "W pole należy wprowadzić liczbę", + "warning_password_too_short": "Hasło musi mieć conajmniej 4 znaki", + "warning_seed_incorrect_length": "Fraza odzyskiwania musi mieć dokładnie 16 lub 25 słów", + "welcome_to": "Witaj w", + "write_down_notice": "Zapisz ten klucz na wypadek utraty dostępu do aplikacji" +} diff --git a/lib/panic_handler.dart b/lib/panic_handler.dart index a679b6e..5555378 100644 --- a/lib/panic_handler.dart +++ b/lib/panic_handler.dart @@ -3,6 +3,11 @@ import 'package:cupcake/views/widgets/cupcake_appbar_title.dart'; import 'package:flutter/material.dart'; import 'package:package_info_plus/package_info_plus.dart'; +// This page is intentionally not translated +// and it intentionally doesn't use MVVM or pretty much anything from the app, as the main goal of +// this page is to collect error when thing didn't go exactly as expected. +// Ideally users will nevel see this :) + Future catchFatalError(final Object error, final StackTrace? stackTrace) async { final PackageInfo packageInfo = await PackageInfo.fromPlatform(); diff --git a/lib/themes/base_theme.dart b/lib/themes/base_theme.dart index a4529f3..e99bda2 100644 --- a/lib/themes/base_theme.dart +++ b/lib/themes/base_theme.dart @@ -13,11 +13,12 @@ class BaseTheme { ), elevatedButtonTheme: ElevatedButtonThemeData( style: ElevatedButton.styleFrom( - backgroundColor: onOnBackgroundColor, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30)), - textStyle: const TextStyle( - color: Colors.white, - )), + backgroundColor: onOnBackgroundColor, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30)), + textStyle: const TextStyle( + color: Colors.white, + ), + ), ), drawerTheme: const DrawerThemeData( backgroundColor: baseBackgroundColor, diff --git a/lib/utils/alerts/basic.dart b/lib/utils/alerts/basic.dart index 2ef7f96..8ae9289 100644 --- a/lib/utils/alerts/basic.dart +++ b/lib/utils/alerts/basic.dart @@ -5,7 +5,7 @@ Future showAlert({ required final String title, required final List body, final String ok = "ok", -}) async { +}) { return showDialog( context: context, barrierDismissible: false, // user must tap button! diff --git a/lib/utils/alerts/widget.dart b/lib/utils/alerts/widget.dart index ad23529..c72b281 100644 --- a/lib/utils/alerts/widget.dart +++ b/lib/utils/alerts/widget.dart @@ -5,7 +5,7 @@ Future showAlertWidget({ required final String title, required final List body, final String ok = "ok", -}) async { +}) { return showDialog( context: context, barrierDismissible: false, // user must tap button! diff --git a/lib/utils/alerts/widget_minimal.dart b/lib/utils/alerts/widget_minimal.dart index c28b6bd..d3ec35b 100644 --- a/lib/utils/alerts/widget_minimal.dart +++ b/lib/utils/alerts/widget_minimal.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; Future showAlertWidgetMinimal({ required final BuildContext context, required final List body, -}) async { +}) { return showDialog( context: context, barrierDismissible: true, diff --git a/lib/utils/config.dart b/lib/utils/config.dart index b5db469..94e1ae5 100644 --- a/lib/utils/config.dart +++ b/lib/utils/config.dart @@ -21,17 +21,6 @@ class CupcakeConfig { required this.didFoundInsecureBiometric, required this.canUseInsecureBiometric, }); - CoinWalletInfo? lastWallet; - bool initialSetupComplete; - int walletMigrationLevel; - int msForQrCode; - int maxFragmentLength; - int walletSort; - bool biometricEnabled; - bool debug; - Map oldSecureStorage; - bool didFoundInsecureBiometric; - bool canUseInsecureBiometric; factory CupcakeConfig.fromJson(final Map json) { return CupcakeConfig( @@ -48,6 +37,17 @@ class CupcakeConfig { canUseInsecureBiometric: json['canUseInsecureBiometric'] ?? false, ); } + CoinWalletInfo? lastWallet; + bool initialSetupComplete; + int walletMigrationLevel; + int msForQrCode; + int maxFragmentLength; + int walletSort; + bool biometricEnabled; + bool debug; + Map oldSecureStorage; + bool didFoundInsecureBiometric; + bool canUseInsecureBiometric; Map toJson() { return { diff --git a/lib/utils/form/flutter_secure_storage_value_outcome.dart b/lib/utils/form/flutter_secure_storage_value_outcome.dart index 94296a5..6587764 100644 --- a/lib/utils/form/flutter_secure_storage_value_outcome.dart +++ b/lib/utils/form/flutter_secure_storage_value_outcome.dart @@ -7,8 +7,11 @@ import 'package:cupcake/utils/form/abstract_value_outcome.dart'; import 'package:cupcake/utils/secure_storage.dart'; class FlutterSecureStorageValueOutcome implements ValueOutcome { - FlutterSecureStorageValueOutcome(this.key, - {required this.canWrite, required this.verifyMatching}); + FlutterSecureStorageValueOutcome( + this.key, { + required this.canWrite, + required this.verifyMatching, + }); final String key; final bool canWrite; @@ -21,7 +24,9 @@ class FlutterSecureStorageValueOutcome implements ValueOutcome { var valInput = await secureStorage.read(key: "FlutterSecureStorageValueOutcome._$key"); if (valInput == null) { await secureStorage.write( - key: "FlutterSecureStorageValueOutcome._$key", value: sha512Hash.toString()); + key: "FlutterSecureStorageValueOutcome._$key", + value: sha512Hash.toString(), + ); valInput = await secureStorage.read(key: "FlutterSecureStorageValueOutcome._$key"); } if (sha512Hash.toString() != valInput && verifyMatching) { diff --git a/lib/utils/form/pin_form_element.dart b/lib/utils/form/pin_form_element.dart index 8805da3..04044f4 100644 --- a/lib/utils/form/pin_form_element.dart +++ b/lib/utils/form/pin_form_element.dart @@ -6,17 +6,17 @@ import 'package:flutter/cupertino.dart'; import 'package:local_auth/local_auth.dart'; class PinFormElement extends FormElement { - PinFormElement( - {final String initialText = "", - this.password = false, - required this.validator, - required this.valueOutcome, - this.onChanged, - this.onConfirm, - this.showNumboard = true, - required this.label, - required final Future Function(Object e) errorHandler}) - : ctrl = TextEditingController(text: initialText), + PinFormElement({ + final String initialText = "", + this.password = false, + required this.validator, + required this.valueOutcome, + this.onChanged, + this.onConfirm, + this.showNumboard = true, + required this.label, + required final Future Function(Object e) errorHandler, + }) : ctrl = TextEditingController(text: initialText), _errorHandler = errorHandler; final Future Function(Object e) _errorHandler; Future loadSecureStorageValue(final VoidCallback callback) async { @@ -55,7 +55,7 @@ class PinFormElement extends FormElement { ValueOutcome valueOutcome; @override - Future get value async => await valueOutcome.decode(ctrl.text); + Future get value => valueOutcome.decode(ctrl.text); @override bool get isOk => validator(ctrl.text) == null; diff --git a/lib/utils/form/single_choice_form_element.dart b/lib/utils/form/single_choice_form_element.dart index c6acd8e..31e8d95 100644 --- a/lib/utils/form/single_choice_form_element.dart +++ b/lib/utils/form/single_choice_form_element.dart @@ -1,11 +1,11 @@ import 'package:cupcake/utils/form/abstract_form_element.dart'; class SingleChoiceFormElement extends FormElement { - SingleChoiceFormElement( - {required this.title, - required this.elements, - required final Future Function(Object e) errorHandler}) - : _errorHandler = errorHandler; + SingleChoiceFormElement({ + required this.title, + required this.elements, + required final Future Function(Object e) errorHandler, + }) : _errorHandler = errorHandler; String title; List elements; diff --git a/lib/utils/form/string_form_element.dart b/lib/utils/form/string_form_element.dart index 054fa08..1183e93 100644 --- a/lib/utils/form/string_form_element.dart +++ b/lib/utils/form/string_form_element.dart @@ -2,15 +2,16 @@ import 'package:cupcake/utils/form/abstract_form_element.dart'; import 'package:flutter/cupertino.dart'; class StringFormElement extends FormElement { - StringFormElement(this.label, - {final String initialText = "", - this.password = false, - required this.validator, - this.isExtra = false, - this.showIf, - this.randomNameGenerator = false, - required final Future Function(Object e) errorHandler}) - : ctrl = TextEditingController(text: initialText), + StringFormElement( + this.label, { + final String initialText = "", + this.password = false, + required this.validator, + this.isExtra = false, + this.showIf, + this.randomNameGenerator = false, + required final Future Function(Object e) errorHandler, + }) : ctrl = TextEditingController(text: initialText), _errorHandler = errorHandler; bool Function()? showIf; diff --git a/lib/utils/form/validators.dart b/lib/utils/form/validators.dart index 23713b0..f42358b 100644 --- a/lib/utils/form/validators.dart +++ b/lib/utils/form/validators.dart @@ -4,8 +4,10 @@ String? Function(String? input) doNothingValidator(final AppLocalizations L) { return (final _) => null; } -String? Function(String? input) nonEmptyValidator(final AppLocalizations L, - {final String? Function(String input)? extra}) { +String? Function(String? input) nonEmptyValidator( + final AppLocalizations L, { + final String? Function(String input)? extra, +}) { return (final String? input) { if (input == null) return L.warning_input_cannot_be_null; if (input == "") return L.warning_input_cannot_be_empty; diff --git a/lib/utils/new_wallet/info_page.dart b/lib/utils/new_wallet/info_page.dart index 934b946..5aaeb4b 100644 --- a/lib/utils/new_wallet/info_page.dart +++ b/lib/utils/new_wallet/info_page.dart @@ -8,6 +8,14 @@ import 'package:lottie/lottie.dart'; import 'package:share_plus/share_plus.dart'; class NewWalletInfoPage { + NewWalletInfoPage({ + required this.topText, + required this.topAction, + required this.topActionText, + required this.lottieAnimation, + required this.actions, + required this.texts, + }); static NewWalletInfoPage preShowSeedPage(final AppLocalizations L) => NewWalletInfoPage( topText: L.important, topAction: null, @@ -80,15 +88,6 @@ class NewWalletInfoPage { ], ); - NewWalletInfoPage({ - required this.topText, - required this.topAction, - required this.topActionText, - required this.lottieAnimation, - required this.actions, - required this.texts, - }); - final String topText; final VoidCallback? topAction; final Widget? topActionText; diff --git a/lib/utils/random_name.dart b/lib/utils/random_name.dart index 4a1e808..05813fe 100644 --- a/lib/utils/random_name.dart +++ b/lib/utils/random_name.dart @@ -1356,7 +1356,7 @@ final adjectives = [ "zany", "zealous", "zesty", - "zigzag" + "zigzag", ]; final nouns = [ "aardvark", diff --git a/lib/utils/urqr.dart b/lib/utils/urqr.dart index 953410e..5cc5c8b 100644 --- a/lib/utils/urqr.dart +++ b/lib/utils/urqr.dart @@ -1,11 +1,12 @@ class URQRData { - URQRData( - {required this.tag, - required this.str, - required this.progress, - required this.count, - required this.error, - required this.inputs}); + URQRData({ + required this.tag, + required this.str, + required this.progress, + required this.count, + required this.error, + required this.inputs, + }); final String tag; final String str; final double progress; diff --git a/lib/view_model/abstract.dart b/lib/view_model/abstract.dart index e89a9cd..2608313 100644 --- a/lib/view_model/abstract.dart +++ b/lib/view_model/abstract.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:cupcake/coins/abstract/coin.dart'; import 'package:cupcake/coins/abstract/exception.dart'; import 'package:cupcake/l10n/app_localizations.dart'; import 'package:cupcake/utils/alerts/basic.dart'; @@ -17,6 +18,7 @@ class ViewModel { throw Exception("context is not mounted. Did you register incorrect context?"); } _lcache ??= AppLocalizations.of(c!); + Coin.L = _lcache!; return _lcache!; } @@ -45,7 +47,7 @@ class ViewModel { Future errorHandler(final Object e) => callThrowable(() => throw e, L.create_wallet); - Future callThrowable(final FutureOr Function() function, final String title) async { + Future callThrowable(final Future Function() function, final String title) async { if (c == null) return false; if (!mounted) return false; try { diff --git a/lib/view_model/barcode_scanner_view_model.dart b/lib/view_model/barcode_scanner_view_model.dart index e3e6cfa..6998850 100644 --- a/lib/view_model/barcode_scanner_view_model.dart +++ b/lib/view_model/barcode_scanner_view_model.dart @@ -35,7 +35,7 @@ class BarcodeScannerViewModel extends ViewModel { percentage: ur.progress, ); - @ThrowOnUI(message: "Error handling URQR scan") + @ThrowOnUI(L: "error_handling_urqr_scan") Future $handleUR() async { await wallet.handleUR(c!, ur); } @@ -43,9 +43,7 @@ class BarcodeScannerViewModel extends ViewModel { Future handleBarcode(final BarcodeCapture barcodes) async { for (final barcode in barcodes.barcodes) { if (barcode.rawValue!.startsWith("ur:")) { - print("handleUR: ${ur.progress} : $popped"); if (ur.progress == 1 && !popped) { - print("handleUR called"); popped = true; await handleUR(); return; diff --git a/lib/view_model/create_wallet_view_model.dart b/lib/view_model/create_wallet_view_model.dart index bfe5b5d..2dd41ec 100644 --- a/lib/view_model/create_wallet_view_model.dart +++ b/lib/view_model/create_wallet_view_model.dart @@ -134,7 +134,7 @@ class CreateWalletViewModel extends ViewModel { final bool needsPasswordConfirm; - @ThrowOnUI(message: "Failed to complete setup") + @ThrowOnUI(L: 'error_failed_to_setup') Future $completeSetup(final CoinWallet cw) async { CupcakeConfig.instance.initialSetupComplete = true; CupcakeConfig.instance.save(); @@ -143,13 +143,13 @@ class CreateWalletViewModel extends ViewModel { @ThrowOnUI(L: 'create_wallet') Future $createWallet() async { - if (selectedCoin == null) throw Exception("selectedCoin is null"); + if (selectedCoin == null) throw Exception(L.error_selected_coin_null); if ((await walletName.value).isEmpty) { throw Exception(L.warning_input_cannot_be_empty); } if (await walletPassword.value == await walletPasswordInitial.value) { - throw Exception("Wallet password doesn't match"); + throw Exception(L.password_doesnt_match); } final outcome = await creationMethod!.create( @@ -159,19 +159,18 @@ class CreateWalletViewModel extends ViewModel { ); if (outcome == null) { - throw Exception("Unable to create wallet using any known methods"); + throw Exception(L.error_unable_to_create_wallets_using_any_known_methods); } if (!outcome.success) { if (outcome.message == null || outcome.message?.isEmpty == true) { - throw Exception( - "Wallet creation failed, and status indicated failure but message was empty"); + throw Exception(L.error_status_is_failure_no_message); } throw Exception(outcome.message); } if (outcome.wallet == null) { - throw Exception("Wallet is null but there is no indication of failure"); + throw Exception(L.error_wallet_is_null_no_indication_of_failure); } final List pages = [ @@ -192,7 +191,7 @@ class CreateWalletViewModel extends ViewModel { ), ]; if (!mounted) { - throw Exception("context is not mounted, unable to show next screen"); + throw Exception(L.error_context_not_mounted); } if (outcome.method == CreateMethod.restore) { await WalletHome(coinWallet: outcome.wallet!).push(c!); @@ -204,6 +203,7 @@ class CreateWalletViewModel extends ViewModel { } FormBuilder get formBuilder => FormBuilder( + L, formElements: currentForm ?? [], scaffoldContext: c!, rebuild: (final bool val) { diff --git a/lib/view_model/open_wallet_view_model.dart b/lib/view_model/open_wallet_view_model.dart index 683d4ad..0dddc44 100644 --- a/lib/view_model/open_wallet_view_model.dart +++ b/lib/view_model/open_wallet_view_model.dart @@ -18,7 +18,7 @@ class OpenWalletViewModel extends ViewModel { String get screenName => L.enter_password; late PinFormElement walletPassword = PinFormElement( - label: "Wallet password", + label: L.wallet_password, password: true, valueOutcome: FlutterSecureStorageValueOutcome( "secure.wallet_password", @@ -34,7 +34,7 @@ class OpenWalletViewModel extends ViewModel { errorHandler: errorHandler, ); - @ThrowOnUI(message: "Opening wallet") + @ThrowOnUI(L: 'opening_wallet') Future $openWallet() async { final wallet = await coinWalletInfo.openWallet( c!, diff --git a/lib/view_model/receive_view_model.dart b/lib/view_model/receive_view_model.dart index 3cf20cc..6f3bab2 100644 --- a/lib/view_model/receive_view_model.dart +++ b/lib/view_model/receive_view_model.dart @@ -9,4 +9,5 @@ class ReceiveViewModel extends ViewModel { String get screenName => L.receive; String get address => wallet.getCurrentAddress; + String get uriScheme => wallet.coin.uriScheme; } diff --git a/lib/view_model/security_backup_view_model.dart b/lib/view_model/security_backup_view_model.dart index ce94924..25d20c1 100644 --- a/lib/view_model/security_backup_view_model.dart +++ b/lib/view_model/security_backup_view_model.dart @@ -20,7 +20,7 @@ class SecurityBackupViewModel extends ViewModel { late List form = [ PinFormElement( - label: "Wallet password", + label: L.wallet_password, password: true, valueOutcome: FlutterSecureStorageValueOutcome( "secure.wallet_password", @@ -41,7 +41,7 @@ class SecurityBackupViewModel extends ViewModel { isLocked = false; }, errorHandler: errorHandler, - ) + ), ]; CoinWallet wallet; diff --git a/lib/view_model/settings_view_model.dart b/lib/view_model/settings_view_model.dart index c5e0e44..7e42599 100644 --- a/lib/view_model/settings_view_model.dart +++ b/lib/view_model/settings_view_model.dart @@ -10,7 +10,7 @@ class SettingsViewModel extends ViewModel { SettingsViewModel(); @override - String get screenName => "Settings"; + String get screenName => L.settings; @ExposeRebuildableAccessors(extraCode: r'$config.save()') CupcakeConfig get $config => CupcakeConfig.instance; diff --git a/lib/view_model/unconfirmed_transaction_view_model.dart b/lib/view_model/unconfirmed_transaction_view_model.dart index 622eeb4..312c2af 100644 --- a/lib/view_model/unconfirmed_transaction_view_model.dart +++ b/lib/view_model/unconfirmed_transaction_view_model.dart @@ -10,13 +10,13 @@ part 'unconfirmed_transaction_view_model.g.dart'; @GenerateRebuild() class UnconfirmedTransactionViewModel extends ViewModel { - UnconfirmedTransactionViewModel( - {required this.wallet, - required this.fee, - required this.destMap, - required final FutureOr Function() confirmCallback, - required final FutureOr Function() cancelCallback}) - : _confirmCallback = confirmCallback, + UnconfirmedTransactionViewModel({ + required this.wallet, + required this.fee, + required this.destMap, + required final FutureOr Function() confirmCallback, + required final FutureOr Function() cancelCallback, + }) : _confirmCallback = confirmCallback, _cancelCallback = cancelCallback; final CoinWallet wallet; @@ -27,9 +27,9 @@ class UnconfirmedTransactionViewModel extends ViewModel { final FutureOr Function() _confirmCallback; final FutureOr Function() _cancelCallback; - @ThrowOnUI(message: "Failed to confirm") + @ThrowOnUI(L: 'error_unable_to_confirm_transaction') Future $confirmCallback() async => await _confirmCallback(); - @ThrowOnUI(message: "Failed to cancel") + @ThrowOnUI(L: 'error_unable_to_cancel') Future $cancelCallback() async => await _cancelCallback(); final Amount fee; diff --git a/lib/view_model/wallet_edit_view_model.dart b/lib/view_model/wallet_edit_view_model.dart index c1b6a78..451c911 100644 --- a/lib/view_model/wallet_edit_view_model.dart +++ b/lib/view_model/wallet_edit_view_model.dart @@ -49,25 +49,25 @@ class WalletEditViewModel extends ViewModel { ]; @override - String get screenName => "Edit wallet"; + String get screenName => L.edit_wallet; - @ThrowOnUI(message: "Delete wallet") + @ThrowOnUI(L: 'delete_wallet') Future $deleteWallet() async { if (!(await walletInfo.checkWalletPassword(await walletPassword.value))) { - throw Exception("Invalid wallet password"); + throw Exception(L.invalid_password); } await walletInfo.deleteWallet(); if (!mounted) return; Navigator.of(c!).pop(); } - @ThrowOnUI(message: "Rename wallet") + @ThrowOnUI(L: 'rename_wallet') Future $renameWallet() async { if (!(await walletInfo.checkWalletPassword(await walletPassword.value))) { - throw Exception("Invalid wallet password"); + throw Exception(L.invalid_password); } if ((await walletName.value).isEmpty) { - throw Exception("Wallet name is empty"); + throw Exception(L.error_wallet_name_empty); } await walletInfo.renameWallet(await walletName.value); if (!mounted) return; diff --git a/lib/views/abstract.dart b/lib/views/abstract.dart index 204d3f8..74ff7db 100644 --- a/lib/views/abstract.dart +++ b/lib/views/abstract.dart @@ -17,8 +17,6 @@ import 'package:flutter/material.dart'; // and due to the fact that we don't use stateful widget we can directly use // viewmodel, without any extra code involved in doing that. -int buildCount = 0; - class _AbstractViewState extends State { _AbstractViewState({required this.realBuild}); @@ -26,18 +24,20 @@ class _AbstractViewState extends State { @override Widget build(final BuildContext context) { - print("build count: ${++buildCount}"); return realBuild(context); } } class AbstractView extends StatefulWidget { + AbstractView({super.key}); Future push(final BuildContext context) async { - await Navigator.of(context).push(CupertinoPageRoute( - builder: (final context) { - return this; - }, - )); + await Navigator.of(context).push( + CupertinoPageRoute( + builder: (final context) { + return this; + }, + ), + ); } final viewModel = ViewModel(); @@ -55,9 +55,7 @@ class AbstractView extends StatefulWidget { State? state; - AbstractView({super.key}); - - get appBar => viewModel.screenName.isEmpty + AppBar? get appBar => viewModel.screenName.isEmpty ? null : AppBar( title: viewModel.screenName.toLowerCase() != "cupcake" diff --git a/lib/views/animated_qr_page.dart b/lib/views/animated_qr_page.dart index ecd9f99..e8cecbe 100644 --- a/lib/views/animated_qr_page.dart +++ b/lib/views/animated_qr_page.dart @@ -4,8 +4,12 @@ import 'package:cupcake/views/widgets/urqr.dart'; import 'package:flutter/material.dart'; class AnimatedURPage extends AbstractView { - AnimatedURPage({super.key, required final Map> urqrList}) - : viewModel = URQRViewModel(urqrList: urqrList); + AnimatedURPage({ + super.key, + required final Map> urqrList, + }) : viewModel = URQRViewModel( + urqrList: urqrList, + ); @override final URQRViewModel viewModel; diff --git a/lib/views/barcode_scanner.dart b/lib/views/barcode_scanner.dart index d2d610f..fcd7647 100644 --- a/lib/views/barcode_scanner.dart +++ b/lib/views/barcode_scanner.dart @@ -8,8 +8,12 @@ import 'package:fast_scanner/fast_scanner.dart'; import 'package:flutter/material.dart'; class BarcodeScanner extends AbstractView { - BarcodeScanner({super.key, required final CoinWallet wallet}) - : viewModel = BarcodeScannerViewModel(wallet: wallet); + BarcodeScanner({ + super.key, + required final CoinWallet wallet, + }) : viewModel = BarcodeScannerViewModel( + wallet: wallet, + ); @override final BarcodeScannerViewModel viewModel; @@ -24,8 +28,10 @@ class BarcodeScanner extends AbstractView { ), if (viewModel.ur.inputs.isNotEmpty) Center( - child: Text("${viewModel.ur.inputs.length}/${viewModel.ur.count}", - style: Theme.of(context).textTheme.displayLarge?.copyWith(color: Colors.white)), + child: Text( + "${viewModel.ur.inputs.length}/${viewModel.ur.count}", + style: Theme.of(context).textTheme.displayLarge?.copyWith(color: Colors.white), + ), ), SizedBox( child: Center( diff --git a/lib/views/create_wallet.dart b/lib/views/create_wallet.dart index 1b3643a..d70b289 100644 --- a/lib/views/create_wallet.dart +++ b/lib/views/create_wallet.dart @@ -6,12 +6,14 @@ import 'package:cupcake/views/widgets/buttons/long_primary.dart'; import 'package:flutter/material.dart'; class CreateWallet extends AbstractView { - CreateWallet( - {super.key, - required final CreateMethod createMethod, - required final bool needsPasswordConfirm}) - : viewModel = CreateWalletViewModel( - createMethod: createMethod, needsPasswordConfirm: needsPasswordConfirm); + CreateWallet({ + super.key, + required final CreateMethod createMethod, + required final bool needsPasswordConfirm, + }) : viewModel = CreateWalletViewModel( + createMethod: createMethod, + needsPasswordConfirm: needsPasswordConfirm, + ); @override final CreateWalletViewModel viewModel; @@ -84,28 +86,29 @@ class CreateWallet extends AbstractView { Widget? bottomNavigationBar(final BuildContext context) { if (!viewModel.isPinSet) return null; return SafeArea( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - LongPrimaryButton( - text: L.next, - icon: null, - onPressed: viewModel.createWallet, - backgroundColor: const WidgetStatePropertyAll(Colors.green), - textColor: Colors.white, - ), - if (viewModel.hasAdvancedOptions) + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ LongPrimaryButton( - text: L.advanced_options, + text: L.next, icon: null, - onPressed: () { - viewModel.showExtra = true; - }, - backgroundColor: const WidgetStatePropertyAll(Colors.transparent), + onPressed: viewModel.createWallet, + backgroundColor: const WidgetStatePropertyAll(Colors.green), + textColor: Colors.white, ), - const SizedBox(height: 16), - ], - )); + if (viewModel.hasAdvancedOptions) + LongPrimaryButton( + text: L.advanced_options, + icon: null, + onPressed: () { + viewModel.showExtra = true; + }, + backgroundColor: const WidgetStatePropertyAll(Colors.transparent), + ), + const SizedBox(height: 16), + ], + ), + ); } @override diff --git a/lib/views/home_screen.dart b/lib/views/home_screen.dart index 68a48d1..d60421f 100644 --- a/lib/views/home_screen.dart +++ b/lib/views/home_screen.dart @@ -8,9 +8,14 @@ import 'package:flutter/material.dart'; import 'package:path/path.dart' as p; class HomeScreen extends AbstractView { - HomeScreen({super.key, required final bool openLastWallet, final String? lastOpenedWallet}) - : viewModel = - HomeScreenViewModel(openLastWallet: openLastWallet, lastOpenedWallet: lastOpenedWallet); + HomeScreen({ + super.key, + required final bool openLastWallet, + final String? lastOpenedWallet, + }) : viewModel = HomeScreenViewModel( + openLastWallet: openLastWallet, + lastOpenedWallet: lastOpenedWallet, + ); @override final HomeScreenViewModel viewModel; @@ -47,19 +52,25 @@ class HomeScreen extends AbstractView { ], ); Widget walletsBody( - final BuildContext context, final AsyncSnapshot> wallets) { + final BuildContext context, + final AsyncSnapshot> wallets, + ) { if (!wallets.hasData) return Container(); return ListView.builder( - itemCount: wallets.data!.length, - itemBuilder: (final BuildContext context, final int index) { - final bool isOpen = - (wallets.data![index].walletName).contains(viewModel.lastOpenedWallet ?? ""); - return singleWalletWidget(context, isOpen, wallets.data![index]); - }); + itemCount: wallets.data!.length, + itemBuilder: (final BuildContext context, final int index) { + final bool isOpen = + (wallets.data![index].walletName).contains(viewModel.lastOpenedWallet ?? ""); + return singleWalletWidget(context, isOpen, wallets.data![index]); + }, + ); } Card singleWalletWidget( - final BuildContext context, final bool isOpen, final CoinWalletInfo wallet) { + final BuildContext context, + final bool isOpen, + final CoinWalletInfo wallet, + ) { return Card( child: IntrinsicHeight( child: Row( @@ -80,7 +91,7 @@ class HomeScreen extends AbstractView { ), trailing: IconButton( icon: const Icon(Icons.edit_rounded), - onPressed: () async => await viewModel.renameWallet(wallet), + onPressed: () => viewModel.renameWallet(wallet), ), title: Text( p.basename(wallet.walletName), @@ -96,21 +107,22 @@ class HomeScreen extends AbstractView { @override Widget? bottomNavigationBar(final BuildContext context) { return SafeArea( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - LongPrimaryButton( - icon: Icons.add, - onPressed: () => viewModel.createWallet(CreateMethod.create), - text: L.create_new_wallet, - ), - LongSecondaryButton( - icon: Icons.restore, - onPressed: () => viewModel.createWallet(CreateMethod.restore), - text: L.restore_wallet, - ), - ], - )); + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + LongPrimaryButton( + icon: Icons.add, + onPressed: () => viewModel.createWallet(CreateMethod.create), + text: L.create_new_wallet, + ), + LongSecondaryButton( + icon: Icons.restore, + onPressed: () => viewModel.createWallet(CreateMethod.restore), + text: L.restore_wallet, + ), + ], + ), + ); } @override diff --git a/lib/views/initial_setup_screen.dart b/lib/views/initial_setup_screen.dart index 308d183..ed23f4e 100644 --- a/lib/views/initial_setup_screen.dart +++ b/lib/views/initial_setup_screen.dart @@ -20,13 +20,16 @@ class InitialSetupScreen extends AbstractView { child: Column( children: [ Padding( - padding: const EdgeInsets.symmetric(horizontal: 64.0, vertical: 24), - child: Assets.cakeLanding.lottie()), - Text(L.welcome_to, - style: Theme.of(context).textTheme.displaySmall?.copyWith( - fontSize: 18, - fontWeight: FontWeight.w500, - )), + padding: const EdgeInsets.symmetric(horizontal: 64.0, vertical: 24), + child: Assets.cakeLanding.lottie(), + ), + Text( + L.welcome_to, + style: Theme.of(context).textTheme.displaySmall?.copyWith( + fontSize: 18, + fontWeight: FontWeight.w500, + ), + ), const SizedBox(height: 8), Text( "Cupcake", diff --git a/lib/views/new_wallet_info.dart b/lib/views/new_wallet_info.dart index f2519b3..30201d8 100644 --- a/lib/views/new_wallet_info.dart +++ b/lib/views/new_wallet_info.dart @@ -6,8 +6,10 @@ import 'package:cupcake/views/widgets/buttons/long_primary.dart'; import 'package:flutter/material.dart'; class NewWalletInfoScreen extends AbstractView { - NewWalletInfoScreen({super.key, required final List pages}) - : viewModel = NewWalletInfoViewModel(pages); + NewWalletInfoScreen({ + super.key, + required final List pages, + }) : viewModel = NewWalletInfoViewModel(pages); @override NewWalletInfoViewModel viewModel; @@ -66,22 +68,23 @@ class NewWalletInfoScreen extends AbstractView { @override Widget? body(final BuildContext context) { return SafeArea( - child: Padding( - padding: const EdgeInsets.only(left: 32, right: 32, top: 0, bottom: 16), - child: Column( - children: [ - if (viewModel.page.lottieAnimation != null) viewModel.page.lottieAnimation!, - ...viewModel.page.texts, - const Spacer(), - SizedBox( - width: double.maxFinite, - child: Row( - mainAxisSize: MainAxisSize.max, - children: _getBottomActionButtons(), + child: Padding( + padding: const EdgeInsets.only(left: 32, right: 32, top: 0, bottom: 16), + child: Column( + children: [ + if (viewModel.page.lottieAnimation != null) viewModel.page.lottieAnimation!, + ...viewModel.page.texts, + const Spacer(), + SizedBox( + width: double.maxFinite, + child: Row( + mainAxisSize: MainAxisSize.max, + children: _getBottomActionButtons(), + ), ), - ) - ], + ], + ), ), - )); + ); } } diff --git a/lib/views/open_wallet.dart b/lib/views/open_wallet.dart index 43f2ba9..bd6b4bb 100644 --- a/lib/views/open_wallet.dart +++ b/lib/views/open_wallet.dart @@ -5,8 +5,12 @@ import 'package:cupcake/views/widgets/form_builder.dart'; import 'package:flutter/cupertino.dart'; class OpenWallet extends AbstractView { - OpenWallet({super.key, required final CoinWalletInfo coinWalletInfo}) - : viewModel = OpenWalletViewModel(coinWalletInfo: coinWalletInfo); + OpenWallet({ + super.key, + required final CoinWalletInfo coinWalletInfo, + }) : viewModel = OpenWalletViewModel( + coinWalletInfo: coinWalletInfo, + ); @override final OpenWalletViewModel viewModel; @@ -17,6 +21,7 @@ class OpenWallet extends AbstractView { mainAxisAlignment: MainAxisAlignment.center, children: [ FormBuilder( + L, formElements: [ viewModel.walletPassword, ], diff --git a/lib/views/receive.dart b/lib/views/receive.dart index f3a866d..197637f 100644 --- a/lib/views/receive.dart +++ b/lib/views/receive.dart @@ -6,8 +6,12 @@ import 'package:flutter/services.dart'; import 'package:qr_flutter/qr_flutter.dart'; class Receive extends AbstractView { - Receive({super.key, required final CoinWallet coinWallet}) - : viewModel = ReceiveViewModel(coinWallet); + Receive({ + super.key, + required final CoinWallet coinWallet, + }) : viewModel = ReceiveViewModel( + coinWallet, + ); @override final ReceiveViewModel viewModel; @@ -27,7 +31,7 @@ class Receive extends AbstractView { color: Colors.white, ), child: QrImageView( - data: "monero:${viewModel.address}", + data: "${viewModel.uriScheme}:${viewModel.address}", dataModuleStyle: QrDataModuleStyle( color: Colors.black, dataModuleShape: QrDataModuleShape.square, @@ -59,10 +63,11 @@ class Receive extends AbstractView { mainAxisSize: MainAxisSize.min, children: [ Expanded( - child: SelectableText( - viewModel.address, - style: const TextStyle(color: Colors.white), - )), + child: SelectableText( + viewModel.address, + style: const TextStyle(color: Colors.white), + ), + ), const Icon(Icons.copy, color: Colors.grey), ], ), diff --git a/lib/views/security_backup.dart b/lib/views/security_backup.dart index bb0c538..8b915aa 100644 --- a/lib/views/security_backup.dart +++ b/lib/views/security_backup.dart @@ -11,23 +11,30 @@ import 'package:flutter/services.dart'; import 'package:qr_flutter/qr_flutter.dart'; class SecurityBackup extends AbstractView { - SecurityBackup({super.key, required final CoinWallet coinWallet}) - : viewModel = SecurityBackupViewModel(wallet: coinWallet); + SecurityBackup({ + super.key, + required final CoinWallet coinWallet, + }) : viewModel = SecurityBackupViewModel( + wallet: coinWallet, + ); @override SecurityBackupViewModel viewModel; void _copy(final BuildContext context, final String value, final String key) { Clipboard.setData(ClipboardData(text: value)); - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text("Copied $key"), - )); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(L.copied(key)), + ), + ); } @override Widget? body(final BuildContext context) { if (viewModel.isLocked) { return FormBuilder( + L, formElements: viewModel.form, scaffoldContext: context, isPinSet: !viewModel.isLocked, @@ -35,18 +42,19 @@ class SecurityBackup extends AbstractView { onLabelChange: null, ); } - final details = viewModel.wallet.seedDetails(L); + final details = viewModel.wallet.seedDetails(); return FutureBuilder( - future: details, - builder: (final BuildContext context, final snapshot) { - if (!snapshot.hasData) return Text(snapshot.error.toString()); - return ListView.builder( - itemCount: snapshot.data!.length, - itemBuilder: (final BuildContext context, final int index) { - return _buildElement(context, snapshot.data![index]); - }, - ); - }); + future: details, + builder: (final BuildContext context, final snapshot) { + if (!snapshot.hasData) return Text(snapshot.error.toString()); + return ListView.builder( + itemCount: snapshot.data!.length, + itemBuilder: (final BuildContext context, final int index) { + return _buildElement(context, snapshot.data![index]); + }, + ); + }, + ); } Widget _buildElement(final BuildContext context, final WalletSeedDetail d) { @@ -56,11 +64,13 @@ class SecurityBackup extends AbstractView { onTap: () { _copy(context, d.value, d.name); }, - title: Text(d.name, - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith(fontSize: 14, fontWeight: FontWeight.w700)), + title: Text( + d.name, + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(fontSize: 14, fontWeight: FontWeight.w700), + ), subtitle: Text( d.value, style: const TextStyle(color: Colors.white), diff --git a/lib/views/settings.dart b/lib/views/settings.dart index 985bd2d..13b6bd9 100644 --- a/lib/views/settings.dart +++ b/lib/views/settings.dart @@ -20,52 +20,53 @@ class SettingsView extends AbstractView { children: [ if (CupcakeConfig.instance.debug) BooleanConfigElement( - title: "Debug", - subtitleEnabled: "Debug options are enabled", - subtitleDisabled: "Debug options are disabled", - value: viewModel.configDebug, - onChange: (final bool value) { - viewModel.configDebug = value; - }), + title: L.settings_debug_title, + subtitleEnabled: L.settings_debug_enabled, + subtitleDisabled: L.settings_debug_disabled, + value: viewModel.configDebug, + onChange: (final bool value) { + viewModel.configDebug = value; + }, + ), IntegerConfigElement( - title: "Milliseconds for qr code", - hint: "How many milliseconds should one QR code last before switching to next one", - value: viewModel.configMsForQrCode, - onChange: (final int value) { - viewModel.configMsForQrCode = value; - }), + title: L.settings_msForQrCode_title, + hint: L.settings_msForQrCode_hint, + value: viewModel.configMsForQrCode, + onChange: (final int value) { + viewModel.configMsForQrCode = value; + }, + ), BooleanConfigElement( - title: "Biometric auth", - subtitleEnabled: "Biometrics are enabled", - subtitleDisabled: - "In order to enable biometrics long press confirm button when entering pin", - value: viewModel.configBiometricEnabled, - onChange: (final bool value) async { - if (value) return; - viewModel.configBiometricEnabled = false; - final map = await secureStorage.readAll(); - for (final key in map.keys) { - if (map[key]!.startsWith("UI.")) { - await secureStorage.delete(key: key); - } + title: L.settings_biometricsEnabled_title, + subtitleEnabled: L.settings_biometricsEnabled_enabled, + subtitleDisabled: L.settings_biometricsEnabled_disabled, + value: viewModel.configBiometricEnabled, + onChange: (final bool value) async { + if (value) return; + viewModel.configBiometricEnabled = false; + final map = await secureStorage.readAll(); + for (final key in map.keys) { + if (map[key]!.startsWith("UI.")) { + await secureStorage.delete(key: key); } - }), + } + }, + ), if (viewModel.configDidFoundInsecureBiometric) BooleanConfigElement( - title: "Insecure biometric auth", - subtitleEnabled: "Insecure biometric authentication is enabled, it is not recommended" - " and could lead to loss of funds. Make sure that you understand the drawbacks," - " and when in doubt - keep this setting disabled.", - subtitleDisabled: "Click to enable insecure biometric authentication.", - value: viewModel.configCanUseInsecureBiometric, - onChange: (final bool value) async { - viewModel.configCanUseInsecureBiometric = value; - }), + title: L.settings_canUseInsecureBiometric_title, + subtitleEnabled: L.settings_canUseInsecureBiometric_enabled, + subtitleDisabled: L.settings_canUseInsecureBiometric_disabled, + value: viewModel.configCanUseInsecureBiometric, + onChange: (final bool value) { + viewModel.configCanUseInsecureBiometric = value; + }, + ), IntegerConfigElement( - title: "Max fragment density", - hint: "How many characters of data should fit within a single QR code", + title: L.settings_maxFragmentLength_title, + hint: L.settings_maxFragmentLength_hint, value: viewModel.configMaxFragmentLength, - onChange: (final int value) async { + onChange: (final int value) { viewModel.configMaxFragmentLength = value; }, ), diff --git a/lib/views/unconfirmed_transaction.dart b/lib/views/unconfirmed_transaction.dart index 8eb0200..467117f 100644 --- a/lib/views/unconfirmed_transaction.dart +++ b/lib/views/unconfirmed_transaction.dart @@ -54,11 +54,12 @@ class UnconfirmedTransactionView extends AbstractView { label: L.cancel, ), BottomNavigationBarItem( - icon: const Icon( - Icons.check_circle, - color: Colors.green, - ), - label: L.confirm), + icon: const Icon( + Icons.check_circle, + color: Colors.green, + ), + label: L.confirm, + ), ], onTap: (final int index) async { if (index == 0) { diff --git a/lib/views/wallet_edit.dart b/lib/views/wallet_edit.dart index 99f165a..b2042a6 100644 --- a/lib/views/wallet_edit.dart +++ b/lib/views/wallet_edit.dart @@ -6,8 +6,12 @@ import 'package:cupcake/views/widgets/form_builder.dart'; import 'package:flutter/material.dart'; class WalletEdit extends AbstractView { - WalletEdit({super.key, required final CoinWalletInfo walletInfo}) - : viewModel = WalletEditViewModel(walletInfo: walletInfo); + WalletEdit({ + super.key, + required final CoinWalletInfo walletInfo, + }) : viewModel = WalletEditViewModel( + walletInfo: walletInfo, + ); @override WalletEditViewModel viewModel; @@ -18,6 +22,7 @@ class WalletEdit extends AbstractView { children: [ const Spacer(), FormBuilder( + L, formElements: viewModel.form, scaffoldContext: context, isPinSet: false, @@ -32,14 +37,14 @@ class WalletEdit extends AbstractView { backgroundColor: const WidgetStatePropertyAll(Colors.red), icon: null, onPressed: viewModel.deleteWallet, - text: "Delete", + text: L.delete, ), ), Expanded( child: LongPrimaryButton( icon: null, onPressed: () => viewModel.renameWallet(), - text: "Rename", + text: L.rename, ), ), ], diff --git a/lib/views/wallet_home.dart b/lib/views/wallet_home.dart index a2d5482..d201f77 100644 --- a/lib/views/wallet_home.dart +++ b/lib/views/wallet_home.dart @@ -9,8 +9,12 @@ import 'package:flutter/material.dart'; import 'package:cupcake/gen/assets.gen.dart'; class WalletHome extends AbstractView { - WalletHome({super.key, required final CoinWallet coinWallet}) - : viewModel = WalletHomeViewModel(wallet: coinWallet); + WalletHome({ + super.key, + required final CoinWallet coinWallet, + }) : viewModel = WalletHomeViewModel( + wallet: coinWallet, + ); @override final WalletHomeViewModel viewModel; @@ -42,11 +46,14 @@ class WalletHome extends AbstractView { Text( viewModel.wallet.walletName, style: const TextStyle( - color: Colors.white, fontWeight: FontWeight.bold, fontSize: 16), + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 16, + ), ), Text(L.primary_account_label), ], - ) + ), ], ), const SizedBox(height: 8), @@ -121,33 +128,34 @@ class WalletHome extends AbstractView { CakeCard _currencyInfo(final BuildContext context) { return CakeCard( - child: Row( - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - L.balance, - style: Theme.of(context) - .textTheme - .displaySmall! - .copyWith(fontSize: 12, fontWeight: FontWeight.w400), - ), - Text( - viewModel.balance, - style: Theme.of(context) - .textTheme - .bodyLarge! - .copyWith(fontSize: 24, fontWeight: FontWeight.w900), - ), - ], - ), - const Spacer(), - SizedBox.square( - dimension: 42, - child: viewModel.coin.strings.svg, - ), - ], - )); + child: Row( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + L.balance, + style: Theme.of(context) + .textTheme + .displaySmall! + .copyWith(fontSize: 12, fontWeight: FontWeight.w400), + ), + Text( + viewModel.balance, + style: Theme.of(context) + .textTheme + .bodyLarge! + .copyWith(fontSize: 24, fontWeight: FontWeight.w900), + ), + ], + ), + const Spacer(), + SizedBox.square( + dimension: 42, + child: viewModel.coin.strings.svg, + ), + ], + ), + ); } } diff --git a/lib/views/widgets/barcode_scanner/progress_painter.dart b/lib/views/widgets/barcode_scanner/progress_painter.dart index e5e8273..d4b6469 100644 --- a/lib/views/widgets/barcode_scanner/progress_painter.dart +++ b/lib/views/widgets/barcode_scanner/progress_painter.dart @@ -4,9 +4,8 @@ import 'package:cupcake/views/widgets/barcode_scanner/urqr_progress.dart'; import 'package:flutter/material.dart'; class ProgressPainter extends CustomPainter { - final URQrProgress urQrProgress; - ProgressPainter({required this.urQrProgress}); + final URQrProgress urQrProgress; @override void paint(final Canvas canvas, final Size size) { @@ -18,13 +17,23 @@ class ProgressPainter extends CustomPainter { for (int i = 0; i < urQrProgress.expectedPartCount.toInt(); i++) { final sweepAngle = (1 / urQrProgress.expectedPartCount) * fullAngle * pi / 180.0; drawSector( - canvas, urQrProgress.receivedPartIndexes.contains(i), rect, startAngle, sweepAngle); + canvas, + urQrProgress.receivedPartIndexes.contains(i), + rect, + startAngle, + sweepAngle, + ); startAngle += sweepAngle; } } - void drawSector(final Canvas canvas, final bool isActive, final Rect rect, - final double startAngle, final double sweepAngle) { + void drawSector( + final Canvas canvas, + final bool isActive, + final Rect rect, + final double startAngle, + final double sweepAngle, + ) { final paint = Paint() ..style = PaintingStyle.stroke ..strokeWidth = 8 diff --git a/lib/views/widgets/barcode_scanner/urqr_progress.dart b/lib/views/widgets/barcode_scanner/urqr_progress.dart index 86707aa..ad34319 100644 --- a/lib/views/widgets/barcode_scanner/urqr_progress.dart +++ b/lib/views/widgets/barcode_scanner/urqr_progress.dart @@ -1,15 +1,14 @@ class URQrProgress { - int expectedPartCount; - int processedPartsCount; - List receivedPartIndexes; - double percentage; - URQrProgress({ required this.expectedPartCount, required this.processedPartsCount, required this.receivedPartIndexes, required this.percentage, }); + int expectedPartCount; + int processedPartsCount; + List receivedPartIndexes; + double percentage; bool equals(final URQrProgress? progress) { if (progress == null) { diff --git a/lib/views/widgets/buttons/long_secondary.dart b/lib/views/widgets/buttons/long_secondary.dart index 1dba1b3..fa0c371 100644 --- a/lib/views/widgets/buttons/long_secondary.dart +++ b/lib/views/widgets/buttons/long_secondary.dart @@ -3,8 +3,12 @@ import 'package:cupcake/views/widgets/buttons/long_primary.dart'; import 'package:flutter/material.dart'; class LongSecondaryButton extends LongPrimaryButton { - const LongSecondaryButton( - {super.key, required super.text, required super.icon, required super.onPressed}); + const LongSecondaryButton({ + super.key, + required super.text, + required super.icon, + required super.onPressed, + }); @override WidgetStateProperty? get backgroundColor => const WidgetStatePropertyAll(Colors.white); diff --git a/lib/views/widgets/cake_card.dart b/lib/views/widgets/cake_card.dart index bb32b97..814a1f5 100644 --- a/lib/views/widgets/cake_card.dart +++ b/lib/views/widgets/cake_card.dart @@ -1,22 +1,23 @@ import 'package:flutter/material.dart'; class CakeCard extends StatelessWidget { - const CakeCard( - {super.key, - required this.child, - this.internalPadding = const EdgeInsets.only( - top: 24.0, - left: 24.0, - bottom: 24, - right: 24, - ), - this.externalPadding = const EdgeInsets.only( - top: 4, - left: 16.0, - bottom: 4, - right: 16, - ), - this.firmPadding = const EdgeInsets.only(top: 24.0, left: 24.0, bottom: 24, right: 24)}); + const CakeCard({ + super.key, + required this.child, + this.internalPadding = const EdgeInsets.only( + top: 24.0, + left: 24.0, + bottom: 24, + right: 24, + ), + this.externalPadding = const EdgeInsets.only( + top: 4, + left: 16.0, + bottom: 4, + right: 16, + ), + this.firmPadding = const EdgeInsets.only(top: 24.0, left: 24.0, bottom: 24, right: 24), + }); final Widget child; final EdgeInsets internalPadding; diff --git a/lib/views/widgets/form_builder.dart b/lib/views/widgets/form_builder.dart index 6163cc4..c10af44 100644 --- a/lib/views/widgets/form_builder.dart +++ b/lib/views/widgets/form_builder.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:cupcake/l10n/app_localizations.dart'; import 'package:cupcake/utils/alerts/widget_minimal.dart'; import 'package:cupcake/utils/config.dart'; import 'package:cupcake/utils/form/abstract_form_element.dart'; @@ -14,7 +15,8 @@ import 'package:flutter/material.dart'; import 'package:local_auth/local_auth.dart'; class FormBuilder extends StatefulWidget { - const FormBuilder({ + const FormBuilder( + this.L, { super.key, required this.formElements, required this.scaffoldContext, @@ -24,6 +26,8 @@ class FormBuilder extends StatefulWidget { required this.onLabelChange, }); + final AppLocalizations L; + final List formElements; final BuildContext scaffoldContext; final void Function(bool isPinSet)? rebuild; @@ -39,6 +43,8 @@ class _FormBuilderState extends State { setState(() {}); } + AppLocalizations get L => widget.L; + String? lastSuggestedTitle = DateTime.now().toIso8601String(); void _onLabelChange(final String? suggestedTitle) { if (suggestedTitle == lastSuggestedTitle) return; @@ -76,7 +82,7 @@ class _FormBuilderState extends State { e = widget.formElements[1] as PinFormElement; } _onLabelChange(e.label); - nextPageCallback() async { + Future nextPageCallback() async { try { await e.onConfirmInternal(context); if (!context.mounted) return; @@ -125,32 +131,37 @@ class _FormBuilderState extends State { final bool canAuthenticateWithBiometrics = await auth.canCheckBiometrics; final bool canAuthenticate = canAuthenticateWithBiometrics || await auth.isDeviceSupported(); - if (!canAuthenticate) throw Exception("Can't authenticate"); + if (!canAuthenticate) throw Exception(L.error_no_biometric_authentication); if (!availableBiometrics.contains(BiometricType.fingerprint) && !availableBiometrics.contains(BiometricType.face) && !CupcakeConfig.instance.canUseInsecureBiometric) { CupcakeConfig.instance.didFoundInsecureBiometric = true; CupcakeConfig.instance.save(); - throw Exception("No secure biometric auth found."); + throw Exception(L.error_no_secure_biometric); } final bool didAuthenticate = await auth.authenticate( - localizedReason: 'Authenticate...', + localizedReason: L.biometric_authenticaion_reason, options: AuthenticationOptions( - useErrorDialogs: true, - biometricOnly: !CupcakeConfig.instance.canUseInsecureBiometric), + useErrorDialogs: true, + biometricOnly: !CupcakeConfig.instance.canUseInsecureBiometric, + ), ); if (!didAuthenticate) { - throw Exception("User didn't authenticate"); + throw Exception(L.error_didnt_authenticate); } await secureStorage.write( - key: "UI.${e.valueOutcome.uniqueId}", value: e.ctrl.text); + key: "UI.${e.valueOutcome.uniqueId}", + value: e.ctrl.text, + ); CupcakeConfig.instance.biometricEnabled = true; CupcakeConfig.instance.save(); if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar( - content: Text("Biometric enabled!"), - )); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(L.biometric_enabled), + ), + ); } await nextPageCallback(); } catch (err) { @@ -262,7 +273,7 @@ class _FormBuilderState extends State { continue; } children.add( - Text("unknown form element: $e"), + Text(L.error_unknown_form_element(e)), ); } return Column( @@ -272,7 +283,9 @@ class _FormBuilderState extends State { } Future _changeSingleChoice( - final BuildContext context, final SingleChoiceFormElement e) async { + final BuildContext context, + final SingleChoiceFormElement e, + ) async { await showAlertWidgetMinimal( context: context, body: List.generate( diff --git a/lib/views/widgets/numerical_keyboard/keyboard.dart b/lib/views/widgets/numerical_keyboard/keyboard.dart index f6ac08d..925866f 100644 --- a/lib/views/widgets/numerical_keyboard/keyboard.dart +++ b/lib/views/widgets/numerical_keyboard/keyboard.dart @@ -176,8 +176,14 @@ Widget getKeyWidgetPinPad(final Keys key) { Keys.a9 || Keys.a0 || Keys.dot => - Text(getKeysChar(key), - style: const TextStyle(fontSize: 32, fontWeight: FontWeight.w400, color: Colors.white)), + Text( + getKeysChar(key), + style: const TextStyle( + fontSize: 32, + fontWeight: FontWeight.w400, + color: Colors.white, + ), + ), Keys.backspace => const Icon(Icons.backspace), Keys.next => const Icon(Icons.check), _ => Container(), diff --git a/lib/views/widgets/numerical_keyboard/main.dart b/lib/views/widgets/numerical_keyboard/main.dart index f37a2dd..6470f30 100644 --- a/lib/views/widgets/numerical_keyboard/main.dart +++ b/lib/views/widgets/numerical_keyboard/main.dart @@ -22,37 +22,45 @@ class NumericalKeyboard extends StatelessWidget { Widget build(final BuildContext context) { return Column( children: [ - Row(children: [ - const Spacer(), - SingleKey(Keys.a1, ctrl, rebuild), - SingleKey(Keys.a2, ctrl, rebuild), - SingleKey(Keys.a3, ctrl, rebuild), - const Spacer(), - ]), - Row(children: [ - const Spacer(), - SingleKey(Keys.a4, ctrl, rebuild), - SingleKey(Keys.a5, ctrl, rebuild), - SingleKey(Keys.a6, ctrl, rebuild), - const Spacer(), - ]), - Row(children: [ - const Spacer(), - SingleKey(Keys.a7, ctrl, rebuild), - SingleKey(Keys.a8, ctrl, rebuild), - SingleKey(Keys.a9, ctrl, rebuild), - const Spacer(), - ]), - Row(children: [ - const Spacer(), - SingleKey(Keys.backspace, ctrl, rebuild), - SingleKey(Keys.a0, ctrl, rebuild), - if (showConfirm() && (!showComma || ctrl.text.contains(getKeysChar(Keys.dot)))) - SingleKey(Keys.next, ctrl, nextPage, longPress: onConfirmLongPress), - if (showComma && !ctrl.text.contains(getKeysChar(Keys.dot))) - SingleKey(Keys.dot, ctrl, rebuild), - Spacer(flex: showConfirm() || showComma ? 1 : 3), - ]), + Row( + children: [ + const Spacer(), + SingleKey(Keys.a1, ctrl, rebuild), + SingleKey(Keys.a2, ctrl, rebuild), + SingleKey(Keys.a3, ctrl, rebuild), + const Spacer(), + ], + ), + Row( + children: [ + const Spacer(), + SingleKey(Keys.a4, ctrl, rebuild), + SingleKey(Keys.a5, ctrl, rebuild), + SingleKey(Keys.a6, ctrl, rebuild), + const Spacer(), + ], + ), + Row( + children: [ + const Spacer(), + SingleKey(Keys.a7, ctrl, rebuild), + SingleKey(Keys.a8, ctrl, rebuild), + SingleKey(Keys.a9, ctrl, rebuild), + const Spacer(), + ], + ), + Row( + children: [ + const Spacer(), + SingleKey(Keys.backspace, ctrl, rebuild), + SingleKey(Keys.a0, ctrl, rebuild), + if (showConfirm() && (!showComma || ctrl.text.contains(getKeysChar(Keys.dot)))) + SingleKey(Keys.next, ctrl, nextPage, longPress: onConfirmLongPress), + if (showComma && !ctrl.text.contains(getKeysChar(Keys.dot))) + SingleKey(Keys.dot, ctrl, rebuild), + Spacer(flex: showConfirm() || showComma ? 1 : 3), + ], + ), ], ); } diff --git a/lib/views/widgets/settings/version_widget.dart b/lib/views/widgets/settings/version_widget.dart index 6f53d91..cc8abe0 100644 --- a/lib/views/widgets/settings/version_widget.dart +++ b/lib/views/widgets/settings/version_widget.dart @@ -1,3 +1,4 @@ +import 'package:cupcake/l10n/app_localizations.dart'; import 'package:cupcake/utils/config.dart'; import 'package:flutter/material.dart'; import 'package:package_info_plus/package_info_plus.dart'; @@ -18,7 +19,10 @@ class _VersionWidgetState extends State { final String buildNumber = packageInfo.buildNumber; if (!context.mounted) return; showAboutDialog( - context: context, applicationName: appName, applicationVersion: "$version+$buildNumber"); + context: context, + applicationName: appName, + applicationVersion: "$version+$buildNumber", + ); } List easterEgg = [ @@ -72,8 +76,9 @@ class _VersionWidgetState extends State { @override Widget build(final BuildContext context) { + final L = AppLocalizations.of(context)!; return ListTile( - title: const Text("About the app"), + title: Text(L.about_the_app), subtitle: subtitle == null ? null : Text(subtitle ?? "..."), onTap: subtitle != null ? _debugTrigger : () => showWidget(context), onLongPress: _debugTrigger, diff --git a/lib/views/widgets/urqr.dart b/lib/views/widgets/urqr.dart index f720225..0135bd8 100644 --- a/lib/views/widgets/urqr.dart +++ b/lib/views/widgets/urqr.dart @@ -5,7 +5,10 @@ import 'package:flutter/material.dart'; import 'package:qr_flutter/qr_flutter.dart'; class URQR extends StatefulWidget { - URQR({super.key, required this.frames}); + URQR({ + super.key, + required this.frames, + }); List frames; @@ -21,9 +24,12 @@ class _URQRState extends State { void initState() { super.initState(); setState(() { - t = Timer.periodic(Duration(milliseconds: CupcakeConfig.instance.msForQrCode), (final timer) { - _nextFrame(); - }); + t = Timer.periodic( + Duration(milliseconds: CupcakeConfig.instance.msForQrCode), + (final timer) { + _nextFrame(); + }, + ); }); } diff --git a/pubspec.lock b/pubspec.lock index de47364..6b1ab02 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -15,7 +15,7 @@ packages: source: sdk version: "0.3.3" analyzer: - dependency: "direct dev" + dependency: "direct main" description: name: analyzer sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" @@ -55,7 +55,7 @@ packages: source: hosted version: "2.1.2" build: - dependency: "direct dev" + dependency: "direct main" description: name: build sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0 @@ -865,7 +865,7 @@ packages: source: sdk version: "0.0.0" source_gen: - dependency: "direct dev" + dependency: "direct main" description: name: source_gen sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b" diff --git a/pubspec.yaml b/pubspec.yaml index 705e274..b101f8c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -62,11 +62,11 @@ dependencies: crypto: ^3.0.3 package_info_plus: ^8.1.0 local_auth: ^2.3.0 - -dev_dependencies: - source_gen: ^2.0.0 analyzer: ^6.11.0 build: ^2.4.2 + source_gen: ^2.0.0 + +dev_dependencies: flutter_test: sdk: flutter From e62b933e25b9da186967a0d23794b2468e57ed2c Mon Sep 17 00:00:00 2001 From: cyan Date: Mon, 24 Feb 2025 06:22:13 +0100 Subject: [PATCH 12/13] Update lib/utils/form/abstract_form_element.dart Co-authored-by: Omar Hatem --- lib/utils/form/abstract_form_element.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utils/form/abstract_form_element.dart b/lib/utils/form/abstract_form_element.dart index 1435071..91b4010 100644 --- a/lib/utils/form/abstract_form_element.dart +++ b/lib/utils/form/abstract_form_element.dart @@ -1,5 +1,5 @@ abstract class FormElement { bool get isOk => true; - String get label => throw UnimplementedError(); + String get label => ""; Future get value => throw UnimplementedError(); } From de9b265052f0b5066f6c9f44c4827b4ffebb32a5 Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Mon, 24 Feb 2025 19:51:13 +0200 Subject: [PATCH 13/13] Update lib/views/widgets/drawer_elements.dart --- lib/views/widgets/drawer_elements.dart | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/views/widgets/drawer_elements.dart b/lib/views/widgets/drawer_elements.dart index 3fdf248..025743d 100644 --- a/lib/views/widgets/drawer_elements.dart +++ b/lib/views/widgets/drawer_elements.dart @@ -50,11 +50,12 @@ class DrawerElements extends StatelessWidget { text: L.security_and_backup, action: _securityBackup, ), - DrawerElement( - svg: Assets.drawerIcons.exportKeyImages.svg(), - text: L.export_key_images, - action: _exportKeyImages, - ), + if (coinWallet is MoneroWallet) + DrawerElement( + svg: Assets.drawerIcons.exportKeyImages.svg(), + text: L.export_key_images, + action: _exportKeyImages, + ), DrawerElement( svg: Assets.drawerIcons.otherSettings.svg(), text: L.other_settings,