From efef30f8eba78ebd8ab5933098701bb2e3138dc1 Mon Sep 17 00:00:00 2001 From: Godwin Asuquo <41484542+godilite@users.noreply.github.com> Date: Thu, 20 Apr 2023 03:54:25 +0300 Subject: [PATCH 01/39] CW-278 Enhance PIN timeout feature code (#886) * CW-278 enhance pin timeout feature * CW-278 enhance pin timeout feature * Update flow to remove extension * Replace pin request on other instances --- lib/core/auth_service.dart | 39 +++++++- lib/di.dart | 6 +- .../desktop_wallet_selection_dropdown.dart | 32 +++---- .../settings/security_backup_page.dart | 45 ++++----- .../screens/wallet_list/wallet_list_page.dart | 91 +++++++------------ 5 files changed, 102 insertions(+), 111 deletions(-) diff --git a/lib/core/auth_service.dart b/lib/core/auth_service.dart index 54f89437a..d26fd17a3 100644 --- a/lib/core/auth_service.dart +++ b/lib/core/auth_service.dart @@ -1,10 +1,12 @@ +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/src/screens/auth/auth_page.dart'; +import 'package:flutter/material.dart'; import 'package:mobx/mobx.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/entities/secret_store_key.dart'; import 'package:cake_wallet/entities/encrypt.dart'; -import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/store/settings_store.dart'; class AuthService with Store { @@ -14,6 +16,12 @@ class AuthService with Store { required this.settingsStore, }); + static const List _alwaysAuthenticateRoutes = [ + Routes.showKeys, + Routes.backup, + Routes.setupPin, + ]; + final FlutterSecureStorage secureStorage; final SharedPreferences sharedPreferences; final SettingsStore settingsStore; @@ -66,4 +74,33 @@ class AuthService with Store { return timeDifference.inMinutes; } + + Future authenticateAction(BuildContext context, + {Function(bool)? onAuthSuccess, String? route, Object? arguments}) async { + assert(route != null || onAuthSuccess != null, + 'Either route or onAuthSuccess param must be passed.'); + if (!requireAuth() && !_alwaysAuthenticateRoutes.contains(route)) { + if (onAuthSuccess != null) { + onAuthSuccess(true); + } else { + Navigator.of(context).pushNamed( + route ?? '', + arguments: arguments, + ); + } + return; + } + Navigator.of(context).pushNamed(Routes.auth, + arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) async { + if (!isAuthenticatedSuccessfully) { + onAuthSuccess?.call(false); + return; + } + if (onAuthSuccess != null) { + auth.close().then((value) => onAuthSuccess.call(true)); + } else { + auth.close(route: route, arguments: arguments); + } + }); + } } diff --git a/lib/di.dart b/lib/di.dart index 2d6e1ec34..584bc8b8e 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -499,7 +499,7 @@ Future setup( } getIt.registerFactory(() => - WalletListPage(walletListViewModel: getIt.get())); + WalletListPage(walletListViewModel: getIt.get(), authService: getIt.get(),)); getIt.registerFactory(() { final wallet = getIt.get().wallet!; @@ -593,7 +593,7 @@ Future setup( getIt.registerFactory(() => ConnectionSyncPage(getIt.get(), getIt.get())); - getIt.registerFactory(() => SecurityBackupPage(getIt.get())); + getIt.registerFactory(() => SecurityBackupPage(getIt.get(), getIt.get())); getIt.registerFactory(() => PrivacyPage(getIt.get())); @@ -926,7 +926,7 @@ Future setup( wallet: getIt.get().wallet!) ); - getIt.registerFactory(() => DesktopWalletSelectionDropDown(getIt.get())); + getIt.registerFactory(() => DesktopWalletSelectionDropDown(getIt.get(), getIt.get())); getIt.registerFactory(() => DesktopSidebarViewModel()); diff --git a/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart b/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart index 1ad831b1b..0bfcb359e 100644 --- a/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart +++ b/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart @@ -1,8 +1,9 @@ import 'package:another_flushbar/flushbar.dart'; +import 'package:cake_wallet/core/auth_service.dart'; +import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/entities/desktop_dropdown_item.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/routes.dart'; -import 'package:cake_wallet/src/screens/auth/auth_page.dart'; import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/dropdown_item_widget.dart'; import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; import 'package:cake_wallet/utils/show_bar.dart'; @@ -16,8 +17,10 @@ import 'package:flutter_mobx/flutter_mobx.dart'; class DesktopWalletSelectionDropDown extends StatefulWidget { final WalletListViewModel walletListViewModel; + final AuthService _authService; - DesktopWalletSelectionDropDown(this.walletListViewModel, {Key? key}) : super(key: key); + DesktopWalletSelectionDropDown(this.walletListViewModel, this._authService, {Key? key}) + : super(key: key); @override State createState() => _DesktopWalletSelectionDropDownState(); @@ -140,25 +143,12 @@ class _DesktopWalletSelectionDropDownState extends State _loadWallet(WalletListItem wallet) async { - if (await widget.walletListViewModel.checkIfAuthRequired()) { - await Navigator.of(context).pushNamed(Routes.auth, - arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) async { - if (!isAuthenticatedSuccessfully) { - return; - } + widget._authService.authenticateAction(context, + onAuthSuccess: (isAuthenticatedSuccessfully) async { + if (!isAuthenticatedSuccessfully) { + return; + } - try { - auth.changeProcessText(S.of(context).wallet_list_loading_wallet(wallet.name)); - await widget.walletListViewModel.loadWallet(wallet); - auth.hideProgressText(); - auth.close(); - setState(() {}); - } catch (e) { - auth.changeProcessText( - S.of(context).wallet_list_failed_to_load(wallet.name, e.toString())); - } - }); - } else { try { changeProcessText(S.of(context).wallet_list_loading_wallet(wallet.name)); await widget.walletListViewModel.loadWallet(wallet); @@ -167,7 +157,7 @@ class _DesktopWalletSelectionDropDownState extends State S.current.security_and_backup; @@ -27,35 +29,24 @@ class SecurityBackupPage extends BasePage { child: Column(mainAxisSize: MainAxisSize.min, children: [ SettingsCellWithArrow( title: S.current.show_keys, - handler: (_) => Navigator.of(context).pushNamed(Routes.auth, - arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) { - if (isAuthenticatedSuccessfully) { - auth.close(route: Routes.showKeys); - } - }), + handler: (_) => _authService.authenticateAction(context, route: Routes.showKeys), ), StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)), SettingsCellWithArrow( title: S.current.create_backup, - handler: (_) => Navigator.of(context).pushNamed(Routes.auth, - arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) { - if (isAuthenticatedSuccessfully) { - auth.close(route: Routes.backup); - } - }), + handler: (_) => _authService.authenticateAction(context, route: Routes.backup), ), StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)), SettingsCellWithArrow( - title: S.current.settings_change_pin, - handler: (_) => Navigator.of(context).pushNamed(Routes.auth, - arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) { - auth.close( - route: isAuthenticatedSuccessfully ? Routes.setupPin : null, - arguments: (PinCodeState setupPinContext, String _) { - setupPinContext.close(); - }, - ); - })), + title: S.current.settings_change_pin, + handler: (_) => _authService.authenticateAction( + context, + route: Routes.setupPin, + arguments: (PinCodeState setupPinContext, String _) { + setupPinContext.close(); + }, + ), + ), StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)), Observer(builder: (_) { return SettingsSwitcherCell( @@ -63,8 +54,8 @@ class SecurityBackupPage extends BasePage { value: _securitySettingsViewModel.allowBiometricalAuthentication, onValueChange: (BuildContext context, bool value) { if (value) { - Navigator.of(context).pushNamed(Routes.auth, - arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) async { + _authService.authenticateAction(context, + onAuthSuccess: (isAuthenticatedSuccessfully) async { if (isAuthenticatedSuccessfully) { if (await _securitySettingsViewModel.biometricAuthenticated()) { _securitySettingsViewModel @@ -74,8 +65,6 @@ class SecurityBackupPage extends BasePage { _securitySettingsViewModel .setAllowBiometricalAuthentication(isAuthenticatedSuccessfully); } - - auth.close(); }); } else { _securitySettingsViewModel.setAllowBiometricalAuthentication(value); diff --git a/lib/src/screens/wallet_list/wallet_list_page.dart b/lib/src/screens/wallet_list/wallet_list_page.dart index a1e8c51b7..316203ddd 100644 --- a/lib/src/screens/wallet_list/wallet_list_page.dart +++ b/lib/src/screens/wallet_list/wallet_list_page.dart @@ -1,4 +1,4 @@ -import 'package:cake_wallet/src/screens/auth/auth_page.dart'; +import 'package:cake_wallet/core/auth_service.dart'; import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; import 'package:cake_wallet/utils/device_info.dart'; import 'package:cake_wallet/utils/show_bar.dart'; @@ -19,18 +19,21 @@ import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:cake_wallet/wallet_type_utils.dart'; class WalletListPage extends BasePage { - WalletListPage({required this.walletListViewModel}); + WalletListPage({required this.walletListViewModel, required this.authService}); final WalletListViewModel walletListViewModel; + final AuthService authService; @override - Widget body(BuildContext context) => WalletListBody(walletListViewModel: walletListViewModel); + Widget body(BuildContext context) => + WalletListBody(walletListViewModel: walletListViewModel, authService: authService); } class WalletListBody extends StatefulWidget { - WalletListBody({required this.walletListViewModel}); + WalletListBody({required this.walletListViewModel, required this.authService}); final WalletListViewModel walletListViewModel; + final AuthService authService; @override WalletListBodyState createState() => WalletListBodyState(); @@ -129,7 +132,8 @@ class WalletListBodyState extends State { fontSize: 22, fontWeight: FontWeight.w500, color: Theme.of(context) - .primaryTextTheme.headline6! + .primaryTextTheme + .headline6! .color!), ) ], @@ -201,61 +205,40 @@ class WalletListBodyState extends State { } Future _loadWallet(WalletListItem wallet) async { - if (await widget.walletListViewModel.checkIfAuthRequired()) { - await Navigator.of(context).pushNamed(Routes.auth, - arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) async { - if (!isAuthenticatedSuccessfully) { - return; - } + await widget.authService.authenticateAction(context, + onAuthSuccess: (isAuthenticatedSuccessfully) async { + if (!isAuthenticatedSuccessfully) { + return; + } - try { - auth.changeProcessText(S.of(context).wallet_list_loading_wallet(wallet.name)); - await widget.walletListViewModel.loadWallet(wallet); - auth.hideProgressText(); - auth.close(); - // only pop the wallets route in mobile as it will go back to dashboard page - // in desktop platforms the navigation tree is different - if (DeviceInfo.instance.isMobile) { - WidgetsBinding.instance.addPostFrameCallback((_) { - Navigator.of(context).pop(); - }); - } - } catch (e) { - auth.changeProcessText( - S.of(context).wallet_list_failed_to_load(wallet.name, e.toString())); - } - }); - } else { try { changeProcessText(S.of(context).wallet_list_loading_wallet(wallet.name)); await widget.walletListViewModel.loadWallet(wallet); - hideProgressText(); + await hideProgressText(); // only pop the wallets route in mobile as it will go back to dashboard page // in desktop platforms the navigation tree is different if (DeviceInfo.instance.isMobile) { - Navigator.of(context).pop(); + WidgetsBinding.instance.addPostFrameCallback((_) { + Navigator.of(context).pop(); + }); } } catch (e) { changeProcessText(S.of(context).wallet_list_failed_to_load(wallet.name, e.toString())); } - } + }); } Future _removeWallet(WalletListItem wallet) async { - if (widget.walletListViewModel.checkIfAuthRequired()) { - await Navigator.of(context).pushNamed(Routes.auth, - arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) async { - if (!isAuthenticatedSuccessfully) { - return; - } - _onSuccessfulAuth(wallet, auth); - }); - } else { - _onSuccessfulAuth(wallet, null); - } + widget.authService.authenticateAction(context, + onAuthSuccess: (isAuthenticatedSuccessfully) async { + if (!isAuthenticatedSuccessfully) { + return; + } + _onSuccessfulAuth(wallet); + }); } - void _onSuccessfulAuth(WalletListItem wallet, AuthPageState? auth) async { + void _onSuccessfulAuth(WalletListItem wallet) async { bool confirmed = false; await showPopUp( context: context, @@ -275,31 +258,23 @@ class WalletListBodyState extends State { if (confirmed) { try { - auth != null - ? auth.changeProcessText(S.of(context).wallet_list_removing_wallet(wallet.name)) - : changeProcessText(S.of(context).wallet_list_removing_wallet(wallet.name)); + changeProcessText(S.of(context).wallet_list_removing_wallet(wallet.name)); await widget.walletListViewModel.remove(wallet); hideProgressText(); } catch (e) { - auth != null - ? auth.changeProcessText( - S.of(context).wallet_list_failed_to_remove(wallet.name, e.toString()), - ) - : changeProcessText( - S.of(context).wallet_list_failed_to_remove(wallet.name, e.toString()), - ); + changeProcessText( + S.of(context).wallet_list_failed_to_remove(wallet.name, e.toString()), + ); } } - - auth?.close(); } void changeProcessText(String text) { _progressBar = createBar(text, duration: null)..show(context); } - void hideProgressText() { - Future.delayed(Duration(milliseconds: 50), () { + Future hideProgressText() async { + await Future.delayed(Duration(milliseconds: 50), () { _progressBar?.dismiss(); _progressBar = null; }); From 7b91b0e938c6f5c3707ecf86b33dd3ecd3b1e4d5 Mon Sep 17 00:00:00 2001 From: Adegoke David <64401859+Blazebrain@users.noreply.github.com> Date: Thu, 20 Apr 2023 02:13:37 +0100 Subject: [PATCH 02/39] Cw 262 better handle user exchange amount below minimum or maximum trade size (#868) * CW-262-Better-handle-user-exchange-amount-below-minimum-or-maximum-trade-size * fix: App should compute conversion even if it's not within the limits --- lib/core/amount_validator.dart | 100 +++++++++++++++++- lib/src/screens/exchange/exchange_page.dart | 30 ++++-- .../exchange/exchange_view_model.dart | 21 +++- res/values/strings_ar.arb | 2 + res/values/strings_bg.arb | 2 + res/values/strings_cs.arb | 2 + res/values/strings_de.arb | 2 + res/values/strings_en.arb | 2 + res/values/strings_es.arb | 2 + res/values/strings_fr.arb | 2 + res/values/strings_hi.arb | 2 + res/values/strings_hr.arb | 2 + res/values/strings_id.arb | 2 + res/values/strings_it.arb | 2 + res/values/strings_ja.arb | 2 + res/values/strings_ko.arb | 2 + res/values/strings_my.arb | 2 + res/values/strings_nl.arb | 2 + res/values/strings_pl.arb | 2 + res/values/strings_pt.arb | 2 + res/values/strings_ru.arb | 2 + res/values/strings_th.arb | 2 + res/values/strings_tr.arb | 2 + res/values/strings_uk.arb | 2 + res/values/strings_ur.arb | 2 + res/values/strings_zh.arb | 2 + 26 files changed, 185 insertions(+), 12 deletions(-) diff --git a/lib/core/amount_validator.dart b/lib/core/amount_validator.dart index 42eb3c32d..acd0ab135 100644 --- a/lib/core/amount_validator.dart +++ b/lib/core/amount_validator.dart @@ -3,17 +3,45 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cw_core/crypto_currency.dart'; class AmountValidator extends TextValidator { - AmountValidator({required CryptoCurrency currency, bool isAutovalidate = false}) { + AmountValidator({ + required CryptoCurrency currency, + bool isAutovalidate = false, + String? minValue, + String? maxValue, + }) { symbolsAmountValidator = SymbolsAmountValidator(isAutovalidate: isAutovalidate); decimalAmountValidator = DecimalAmountValidator(currency: currency,isAutovalidate: isAutovalidate); + + amountMinValidator = AmountMinValidator( + minValue: minValue, + isAutovalidate: isAutovalidate, + ); + + amountMaxValidator = AmountMaxValidator( + maxValue: maxValue, + isAutovalidate: isAutovalidate, + ); } + late final AmountMinValidator amountMinValidator; + + late final AmountMaxValidator amountMaxValidator; + late final SymbolsAmountValidator symbolsAmountValidator; late final DecimalAmountValidator decimalAmountValidator; - String? call(String? value) => symbolsAmountValidator(value) ?? decimalAmountValidator(value); + String? call(String? value) { + //* Validate for Text(length, symbols, decimals etc) + + final textValidation = symbolsAmountValidator(value) ?? decimalAmountValidator(value); + + //* Validate for Comparison(Value greater than min and less than ) + final comparisonValidation = amountMinValidator(value) ?? amountMaxValidator(value); + + return textValidation ?? comparisonValidation; + } } class SymbolsAmountValidator extends TextValidator { @@ -57,3 +85,71 @@ class AllAmountValidator extends TextValidator { minLength: 0, maxLength: 0); } + +class AmountMinValidator extends Validator { + final String? minValue; + final bool isAutovalidate; + + AmountMinValidator({ + this.minValue, + required this.isAutovalidate, + }) : super(errorMessage: S.current.error_text_input_below_minimum_limit); + + @override + bool isValid(String? value) { + if (value == null || value.isEmpty) { + return isAutovalidate ? true : false; + } + + if (minValue == null || minValue == "null") { + return true; + } + + final valueInDouble = parseToDouble(value); + final minInDouble = parseToDouble(minValue ?? ''); + + if (valueInDouble == null || minInDouble == null) { + return false; + } + + return valueInDouble > minInDouble; + } + + double? parseToDouble(String value) { + final data = double.tryParse(value.replaceAll(',', '.')); + return data; + } +} + +class AmountMaxValidator extends Validator { + final String? maxValue; + final bool isAutovalidate; + + AmountMaxValidator({ + this.maxValue, + required this.isAutovalidate, + }) : super(errorMessage: S.current.error_text_input_above_maximum_limit); + + @override + bool isValid(String? value) { + if (value == null || value.isEmpty) { + return isAutovalidate ? true : false; + } + + if (maxValue == null || maxValue == "null") { + return true; + } + + final valueInDouble = parseToDouble(value); + final maxInDouble = parseToDouble(maxValue ?? ''); + + if (valueInDouble == null || maxInDouble == null) { + return false; + } + return valueInDouble < maxInDouble; + } + + double? parseToDouble(String value) { + return double.tryParse(value.replaceAll(',', '.')); + } +} diff --git a/lib/src/screens/exchange/exchange_page.dart b/lib/src/screens/exchange/exchange_page.dart index 99eeae7dc..310e40cd8 100644 --- a/lib/src/screens/exchange/exchange_page.dart +++ b/lib/src/screens/exchange/exchange_page.dart @@ -456,8 +456,7 @@ class ExchangePage extends BasePage { depositAmountController.addListener(() { if (depositAmountController.text != exchangeViewModel.depositAmount) { _depositAmountDebounce.run(() { - exchangeViewModel.changeDepositAmount( - amount: depositAmountController.text); + exchangeViewModel.changeDepositAmount(amount: depositAmountController.text); exchangeViewModel.isReceiveAmountEntered = false; }); } @@ -469,8 +468,7 @@ class ExchangePage extends BasePage { receiveAmountController.addListener(() { if (receiveAmountController.text != exchangeViewModel.receiveAmount) { _receiveAmountDebounce.run(() { - exchangeViewModel.changeReceiveAmount( - amount: receiveAmountController.text); + exchangeViewModel.changeReceiveAmount(amount: receiveAmountController.text); exchangeViewModel.isReceiveAmountEntered = true; }); } @@ -626,8 +624,16 @@ class ExchangePage extends BasePage { currencyButtonColor: Colors.transparent, addressButtonsColor: Theme.of(context).focusColor!, borderColor: Theme.of(context).primaryTextTheme!.bodyText1!.color!, - currencyValueValidator: - AmountValidator(currency: exchangeViewModel.depositCurrency), + currencyValueValidator: (value) { + return !exchangeViewModel.isFixedRateMode + ? AmountValidator( + isAutovalidate: true, + currency: exchangeViewModel.depositCurrency, + minValue: exchangeViewModel.limits.min.toString(), + maxValue: exchangeViewModel.limits.max.toString(), + ).call(value) + : null; + }, addressTextFieldValidator: AddressValidator(type: exchangeViewModel.depositCurrency), onPushPasteButton: (context) async { @@ -668,8 +674,16 @@ class ExchangePage extends BasePage { addressButtonsColor: Theme.of(context).focusColor!, borderColor: Theme.of(context).primaryTextTheme!.bodyText1!.decorationColor!, - currencyValueValidator: - AmountValidator(currency: exchangeViewModel.receiveCurrency), + currencyValueValidator: (value) { + return exchangeViewModel.isFixedRateMode + ? AmountValidator( + isAutovalidate: true, + currency: exchangeViewModel.receiveCurrency, + minValue: exchangeViewModel.limits.min.toString(), + maxValue: exchangeViewModel.limits.max.toString(), + ).call(value) + : null; + }, addressTextFieldValidator: AddressValidator(type: exchangeViewModel.receiveCurrency), onPushPasteButton: (context) async { diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart index bf0d88cd0..1e25f1353 100644 --- a/lib/view_model/exchange/exchange_view_model.dart +++ b/lib/view_model/exchange/exchange_view_model.dart @@ -198,6 +198,9 @@ abstract class ExchangeViewModelBase with Store { @observable bool isFixedRateMode; + @observable + Limits limits; + @computed SyncStatus get status => wallet.syncStatus; @@ -241,8 +244,6 @@ abstract class ExchangeViewModelBase with Store { List depositCurrencies; - Limits limits; - NumberFormat _cryptoNumberFormat; final SettingsStore _settingsStore; @@ -320,6 +321,22 @@ abstract class ExchangeViewModelBase with Store { .replaceAll(RegExp('\\,'), ''); } + bool checkIfInputMeetsMinOrMaxCondition(String input) { + final _enteredAmount = double.tryParse(input.replaceAll(',', '.')) ?? 0; + double minLimit = limits.min ?? 0; + double? maxLimit = limits.max; + + if (_enteredAmount < minLimit) { + return false; + } + + if (maxLimit != null && _enteredAmount > maxLimit) { + return false; + } + + return true; + } + Future _calculateBestRate() async { final amount = double.tryParse(isFixedRateMode ? receiveAmount : depositAmount) ?? 1; diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 1bf0a27b1..8dbf1b900 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -697,5 +697,7 @@ "onion_link": "رابط البصل", "settings": "إعدادات", "sell_monero_com_alert_content": "بيع Monero غير مدعوم حتى الآن", + "error_text_input_below_minimum_limit":" المبلغ أقل من الحد الأدنى", + "error_text_input_above_maximum_limit":"المبلغ أكبر من الحد الأقصى", "show_market_place": "إظهار السوق" } diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 0929ad181..2a95efeee 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -698,5 +698,7 @@ "clearnet_link": "Clearnet връзка", "onion_link": "Лукова връзка", "sell_monero_com_alert_content": "Продажбата на Monero все още не се поддържа", + "error_text_input_below_minimum_limit" : "Сумата е по-малко от минималната", + "error_text_input_above_maximum_limit" : "Сумата надвишава максималната", "show_market_place":"Покажи пазар" } diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index dbfa086ce..ddd22eb61 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -698,5 +698,7 @@ "clearnet_link": "Odkaz na Clearnet", "onion_link": "Cibulový odkaz", "sell_monero_com_alert_content": "Prodej Monero zatím není podporován", + "error_text_input_below_minimum_limit" : "Částka je menší než minimální hodnota", + "error_text_input_above_maximum_limit" : "Částka je větší než maximální hodnota", "show_market_place": "Zobrazit trh" } diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 1d016a716..d29a641e2 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -699,5 +699,7 @@ "onion_link": "Zwiebel-Link", "settings": "Einstellungen", "sell_monero_com_alert_content": "Der Verkauf von Monero wird noch nicht unterstützt", + "error_text_input_below_minimum_limit" : "Menge ist unter dem Minimum", + "error_text_input_above_maximum_limit" : "Menge ist über dem Maximum", "show_market_place": "Marktplatz anzeigen" } diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 316f49f1c..f4d693e8f 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -699,5 +699,7 @@ "edit_node": "Edit Node", "settings": "Settings", "sell_monero_com_alert_content": "Selling Monero is not supported yet", + "error_text_input_below_minimum_limit" : "Amount is less than the minimum", + "error_text_input_above_maximum_limit" : "Amount is more than the maximum", "show_market_place" :"Show Marketplace" } diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index c376b869d..cee502fd3 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -699,5 +699,7 @@ "onion_link": "Enlace de cebolla", "settings": "Configuraciones", "sell_monero_com_alert_content": "Aún no se admite la venta de Monero", + "error_text_input_below_minimum_limit" : "La cantidad es menos que mínima", + "error_text_input_above_maximum_limit" : "La cantidad es más que el máximo", "show_market_place": "Mostrar mercado" } diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 4dd2b74a9..c4a44895a 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -699,5 +699,7 @@ "settings": "Paramètres", "onion_link": "Lien .onion", "sell_monero_com_alert_content": "La vente de Monero n'est pas encore prise en charge", + "error_text_input_below_minimum_limit" : "Le montant est inférieur au minimum", + "error_text_input_above_maximum_limit" : "Le montant est supérieur au maximum", "show_market_place" :"Afficher la place de marché" } diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 164b8a4a6..2669af5dc 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -699,5 +699,7 @@ "onion_link": "प्याज का लिंक", "settings": "समायोजन", "sell_monero_com_alert_content": "मोनेरो बेचना अभी तक समर्थित नहीं है", + "error_text_input_below_minimum_limit" : "राशि न्यूनतम से कम है", + "error_text_input_above_maximum_limit" : "राशि अधिकतम से अधिक है", "show_market_place":"बाज़ार दिखाएँ" } diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index f0d04ed36..865494c8d 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -699,5 +699,7 @@ "onion_link": "Poveznica luka", "settings": "Postavke", "sell_monero_com_alert_content": "Prodaja Monera još nije podržana", + "error_text_input_below_minimum_limit" : "Iznos je manji od minimalnog", + "error_text_input_above_maximum_limit" : "Iznos je veći od maskimalnog", "show_market_place" : "Prikaži tržište" } diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index 501280351..12e1f2cfa 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -680,5 +680,7 @@ "clearnet_link": "Tautan clearnet", "onion_link": "Tautan bawang", "sell_monero_com_alert_content": "Menjual Monero belum didukung", + "error_text_input_below_minimum_limit" : "Jumlah kurang dari minimal", + "error_text_input_above_maximum_limit" : "Jumlah lebih dari maksimal", "show_market_place": "Tampilkan Pasar" } diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index bbc3a4f3f..dce7df8e3 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -699,5 +699,7 @@ "onion_link": "Collegamento a cipolla", "settings": "Impostazioni", "sell_monero_com_alert_content": "La vendita di Monero non è ancora supportata", + "error_text_input_below_minimum_limit" : "L'ammontare è inferiore al minimo", + "error_text_input_above_maximum_limit" : "L'ammontare è superiore al massimo", "show_market_place":"Mostra mercato" } diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 2c3b1c841..674457116 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -699,5 +699,7 @@ "onion_link": "オニオンリンク", "settings": "設定", "sell_monero_com_alert_content": "モネロの販売はまだサポートされていません", + "error_text_input_below_minimum_limit" : "金額は最小額より少ない", + "error_text_input_above_maximum_limit" : "金額は最大値を超えています", "show_market_place":"マーケットプレイスを表示" } diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 41d5ba014..4b35b313b 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -699,5 +699,7 @@ "onion_link": "양파 링크", "settings": "설정", "sell_monero_com_alert_content": "지원되지 않습니다.", + "error_text_input_below_minimum_limit" : "금액이 최소보다 적습니다.", + "error_text_input_above_maximum_limit" : "금액이 최대 값보다 많습니다.", "show_market_place":"마켓플레이스 표시" } diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 1ff29dd8f..f08634f48 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -699,5 +699,7 @@ "onion_link": "ကြက်သွန်လင့်", "settings": "ဆက်တင်များ", "sell_monero_com_alert_content": "Monero ရောင်းချခြင်းကို မပံ့ပိုးရသေးပါ။", + "error_text_input_below_minimum_limit" : "ပမာဏသည် အနိမ့်ဆုံးထက်နည်းသည်။", + "error_text_input_above_maximum_limit" : "ပမာဏသည် အများဆုံးထက် ပိုများသည်။", "show_market_place":"စျေးကွက်ကိုပြသပါ။" } diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index e01bf15d7..6e416b004 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -699,5 +699,7 @@ "onion_link": "Ui koppeling", "settings": "Instellingen", "sell_monero_com_alert_content": "Het verkopen van Monero wordt nog niet ondersteund", + "error_text_input_below_minimum_limit" : "Bedrag is minder dan minimaal", + "error_text_input_above_maximum_limit" : "Bedrag is meer dan maximaal", "show_market_place":"Toon Marktplaats" } diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index aba2bf1fb..357edc2e3 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -699,5 +699,7 @@ "onion_link": "Łącznik cebulowy", "settings": "Ustawienia", "sell_monero_com_alert_content": "Sprzedaż Monero nie jest jeszcze obsługiwana", + "error_text_input_below_minimum_limit" : "Kwota jest mniejsza niż minimalna", + "error_text_input_above_maximum_limit" : "Kwota jest większa niż maksymalna", "show_market_place" : "Pokaż rynek" } diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index a3308f1ce..d02147050 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -698,5 +698,7 @@ "onion_link": "ligação de cebola", "settings": "Configurações", "sell_monero_com_alert_content": "A venda de Monero ainda não é suportada", + "error_text_input_below_minimum_limit" : "O valor é menor que o mínimo", + "error_text_input_above_maximum_limit" : "O valor é superior ao máximo", "show_market_place":"Mostrar mercado" } diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index f7202b472..ad14f4cb0 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -699,5 +699,7 @@ "onion_link": "Луковая ссылка", "settings": "Настройки", "sell_monero_com_alert_content": "Продажа Monero пока не поддерживается", + "error_text_input_below_minimum_limit" : "Сумма меньше минимальной", + "error_text_input_above_maximum_limit" : "Сумма больше максимальной", "show_market_place":"Показать торговую площадку" } diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 88d12e091..b8f229a09 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -697,5 +697,7 @@ "onion_link": "ลิงค์หัวหอม", "settings": "การตั้งค่า", "sell_monero_com_alert_content": "ยังไม่รองรับการขาย Monero", + "error_text_input_below_minimum_limit" : "จำนวนเงินน้อยกว่าขั้นต่ำ", + "error_text_input_above_maximum_limit" : "จำนวนเงินสูงกว่าค่าสูงสุด", "show_market_place":"แสดงตลาดกลาง" } diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index fcd09dda2..2ef0b9302 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -699,5 +699,7 @@ "onion_link": "soğan bağlantısı", "settings": "ayarlar", "sell_monero_com_alert_content": "Monero satışı henüz desteklenmiyor", + "error_text_input_below_minimum_limit" : "Miktar minimumdan daha azdır", + "error_text_input_above_maximum_limit" : "Miktar maksimumdan daha fazla", "show_market_place":"Pazar Yerini Göster" } diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 4698ac46c..72cccf43b 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -698,5 +698,7 @@ "onion_link": "Посилання на цибулю", "settings": "Налаштування", "sell_monero_com_alert_content": "Продаж Monero ще не підтримується", + "error_text_input_below_minimum_limit" : "Сума менша мінімальної", + "error_text_input_above_maximum_limit" : "Сума більше максимальної", "show_market_place":"Шоу Ринок" } diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index 3f3b5efc4..000ce76b4 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -699,5 +699,7 @@ "clearnet_link": "کلیرنیٹ لنک", "onion_link": "پیاز کا لنک", "sell_monero_com_alert_content": "Monero فروخت کرنا ابھی تک تعاون یافتہ نہیں ہے۔", + "error_text_input_below_minimum_limit" : "رقم کم از کم سے کم ہے۔", + "error_text_input_above_maximum_limit" : "رقم زیادہ سے زیادہ سے زیادہ ہے۔", "show_market_place":"بازار دکھائیں۔" } diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index c0fa68d72..e95e854d9 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -698,5 +698,7 @@ "onion_link": "洋葱链接", "settings": "设置", "sell_monero_com_alert_content": "尚不支持出售门罗币", + "error_text_input_below_minimum_limit" : "金额小于最小值", + "error_text_input_above_maximum_limit" : "金额大于最大值", "show_market_place" :"显示市场" } From f26815efb80a735b12bbf52b4d1e515cb8cb5c13 Mon Sep 17 00:00:00 2001 From: Serhii Date: Thu, 20 Apr 2023 12:59:59 +0300 Subject: [PATCH 03/39] CW-351-Add-option-in-Privacy-settings-to-enable-disable-screenshots (#885) * add prevent screenshots option * fix prevent screen recording * update localization * Update strings_ja.arb --- .../cakewallet/cake_wallet/MainActivity.java | 15 ++++++----- cw_core/lib/set_app_secure_native.dart | 6 +++++ cw_monero/example/pubspec.lock | 14 +++++------ lib/core/backup_service.dart | 6 +++++ lib/entities/preferences_key.dart | 1 + lib/src/screens/settings/privacy_page.dart | 8 ++++++ lib/store/settings_store.dart | 25 ++++++++++++++++++- .../settings/privacy_settings_view_model.dart | 6 +++++ res/values/strings_ar.arb | 3 ++- res/values/strings_bg.arb | 3 ++- res/values/strings_cs.arb | 3 ++- res/values/strings_de.arb | 3 ++- res/values/strings_en.arb | 3 ++- res/values/strings_es.arb | 3 ++- res/values/strings_fr.arb | 3 ++- res/values/strings_hi.arb | 3 ++- res/values/strings_hr.arb | 3 ++- res/values/strings_id.arb | 3 ++- res/values/strings_it.arb | 3 ++- res/values/strings_ja.arb | 3 ++- res/values/strings_ko.arb | 3 ++- res/values/strings_my.arb | 3 ++- res/values/strings_nl.arb | 3 ++- res/values/strings_pl.arb | 3 ++- res/values/strings_pt.arb | 3 ++- res/values/strings_ru.arb | 3 ++- res/values/strings_th.arb | 3 ++- res/values/strings_tr.arb | 3 ++- res/values/strings_uk.arb | 3 ++- res/values/strings_ur.arb | 3 ++- res/values/strings_zh.arb | 3 ++- 31 files changed, 113 insertions(+), 37 deletions(-) create mode 100644 cw_core/lib/set_app_secure_native.dart diff --git a/android/app/src/main/java/com/cakewallet/cake_wallet/MainActivity.java b/android/app/src/main/java/com/cakewallet/cake_wallet/MainActivity.java index 1c1e42df8..edaaefeb2 100644 --- a/android/app/src/main/java/com/cakewallet/cake_wallet/MainActivity.java +++ b/android/app/src/main/java/com/cakewallet/cake_wallet/MainActivity.java @@ -24,6 +24,7 @@ import java.security.SecureRandom; public class MainActivity extends FlutterFragmentActivity { final String UTILS_CHANNEL = "com.cake_wallet/native_utils"; final int UNSTOPPABLE_DOMAIN_MIN_VERSION_SDK = 24; + boolean isAppSecure = false; @Override public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { @@ -56,6 +57,14 @@ public class MainActivity extends FlutterFragmentActivity { handler.post(() -> result.success("")); } break; + case "setIsAppSecure": + isAppSecure = call.argument("isAppSecure"); + if (isAppSecure) { + getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE); + } else { + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE); + } + break; default: handler.post(() -> result.notImplemented()); } @@ -80,10 +89,4 @@ public class MainActivity extends FlutterFragmentActivity { } }); } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE); - } } \ No newline at end of file diff --git a/cw_core/lib/set_app_secure_native.dart b/cw_core/lib/set_app_secure_native.dart new file mode 100644 index 000000000..2f2e9a9c9 --- /dev/null +++ b/cw_core/lib/set_app_secure_native.dart @@ -0,0 +1,6 @@ +import 'package:flutter/services.dart'; + +const utils = const MethodChannel('com.cake_wallet/native_utils'); + +void setIsAppSecureNative(bool isAppSecure) => + utils.invokeMethod('setIsAppSecure', {'isAppSecure': isAppSecure}); diff --git a/cw_monero/example/pubspec.lock b/cw_monero/example/pubspec.lock index 772ff47bd..19d9cef8f 100644 --- a/cw_monero/example/pubspec.lock +++ b/cw_monero/example/pubspec.lock @@ -115,10 +115,10 @@ packages: dependency: transitive description: name: ffi - sha256: "13a6ccf6a459a125b3fcdb6ec73bd5ff90822e071207c663bfd1f70062d51d18" + sha256: a38574032c5f1dd06c4aee541789906c12ccaab8ba01446e800d9c5b79c4a978 url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "2.0.1" file: dependency: transitive description: @@ -277,10 +277,10 @@ packages: dependency: transitive description: name: path_provider_windows - sha256: a34ecd7fb548f8e57321fd8e50d865d266941b54e6c3b7758cf8f37c24116905 + sha256: f53720498d5a543f9607db4b0e997c4b5438884de25b0f73098cc2671a51b130 url: "https://pub.dev" source: hosted - version: "2.0.7" + version: "2.1.5" platform: dependency: transitive description: @@ -386,10 +386,10 @@ packages: dependency: transitive description: name: win32 - sha256: c0e3a4f7be7dae51d8f152230b86627e3397c1ba8c3fa58e63d44a9f3edc9cef + sha256: a6f0236dbda0f63aa9a25ad1ff9a9d8a4eaaa5012da0dc59d21afdb1dc361ca4 url: "https://pub.dev" source: hosted - version: "2.6.1" + version: "3.1.4" xdg_directories: dependency: transitive description: @@ -399,5 +399,5 @@ packages: source: hosted version: "0.2.0+3" sdks: - dart: ">=2.18.1 <3.0.0" + dart: ">=2.18.1 <4.0.0" flutter: ">=3.0.0" diff --git a/lib/core/backup_service.dart b/lib/core/backup_service.dart index 3cb434efe..20fd753d8 100644 --- a/lib/core/backup_service.dart +++ b/lib/core/backup_service.dart @@ -209,6 +209,7 @@ class BackupService { final currentBalanceDisplayMode = data[PreferencesKey.currentBalanceDisplayModeKey] as int?; final currentFiatCurrency = data[PreferencesKey.currentFiatCurrencyKey] as String?; final shouldSaveRecipientAddress = data[PreferencesKey.shouldSaveRecipientAddressKey] as bool?; + final isAppSecure = data[PreferencesKey.isAppSecureKey] as bool?; final currentTransactionPriorityKeyLegacy = data[PreferencesKey.currentTransactionPriorityKeyLegacy] as int?; final allowBiometricalAuthentication = data[PreferencesKey.allowBiometricalAuthenticationKey] as bool?; final currentBitcoinElectrumSererId = data[PreferencesKey.currentBitcoinElectrumSererIdKey] as int?; @@ -245,6 +246,11 @@ class BackupService { PreferencesKey.shouldSaveRecipientAddressKey, shouldSaveRecipientAddress); + if (isAppSecure != null) + await _sharedPreferences.setBool( + PreferencesKey.isAppSecureKey, + isAppSecure); + if (currentTransactionPriorityKeyLegacy != null) await _sharedPreferences.setInt( PreferencesKey.currentTransactionPriorityKeyLegacy, diff --git a/lib/entities/preferences_key.dart b/lib/entities/preferences_key.dart index be93300a7..f5741a98b 100644 --- a/lib/entities/preferences_key.dart +++ b/lib/entities/preferences_key.dart @@ -9,6 +9,7 @@ class PreferencesKey { static const currentTransactionPriorityKeyLegacy = 'current_fee_priority'; static const currentBalanceDisplayModeKey = 'current_balance_display_mode'; static const shouldSaveRecipientAddressKey = 'save_recipient_address'; + static const isAppSecureKey = 'is_app_secure'; static const currentFiatApiModeKey = 'current_fiat_api_mode'; static const allowBiometricalAuthenticationKey = 'allow_biometrical_authentication'; diff --git a/lib/src/screens/settings/privacy_page.dart b/lib/src/screens/settings/privacy_page.dart index b11b41199..81e2715f2 100644 --- a/lib/src/screens/settings/privacy_page.dart +++ b/lib/src/screens/settings/privacy_page.dart @@ -8,6 +8,7 @@ import 'package:cake_wallet/view_model/settings/choices_list_item.dart'; import 'package:cake_wallet/view_model/settings/privacy_settings_view_model.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; +import 'dart:io' show Platform; class PrivacyPage extends BasePage { PrivacyPage(this._privacySettingsViewModel); @@ -48,6 +49,13 @@ class PrivacyPage extends BasePage { onValueChange: (BuildContext _, bool value) { _privacySettingsViewModel.setShouldSaveRecipientAddress(value); }), + if (Platform.isAndroid) + SettingsSwitcherCell( + title: S.current.prevent_screenshots, + value: _privacySettingsViewModel.isAppSecure, + onValueChange: (BuildContext _, bool value) { + _privacySettingsViewModel.setIsAppSecure(value); + }), ], ); }), diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index 2080c0a7c..400f7ac88 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -19,6 +19,8 @@ import 'package:cw_core/node.dart'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/entities/action_list_display_mode.dart'; import 'package:cake_wallet/entities/fiat_api_mode.dart'; +import 'package:cw_core/set_app_secure_native.dart'; +import 'dart:io' show Platform; part 'settings_store.g.dart'; @@ -31,6 +33,7 @@ abstract class SettingsStoreBase with Store { required FiatCurrency initialFiatCurrency, required BalanceDisplayMode initialBalanceDisplayMode, required bool initialSaveRecipientAddress, + required bool initialAppSecure, required FiatApiMode initialFiatMode, required bool initialAllowBiometricalAuthentication, required ExchangeApiMode initialExchangeStatus, @@ -53,6 +56,7 @@ abstract class SettingsStoreBase with Store { fiatCurrency = initialFiatCurrency, balanceDisplayMode = initialBalanceDisplayMode, shouldSaveRecipientAddress = initialSaveRecipientAddress, + isAppSecure = initialAppSecure, fiatApiMode = initialFiatMode, allowBiometricalAuthentication = initialAllowBiometricalAuthentication, shouldShowMarketPlaceInDashboard = initialShouldShowMarketPlaceInDashboard, @@ -119,6 +123,17 @@ abstract class SettingsStoreBase with Store { PreferencesKey.shouldSaveRecipientAddressKey, shouldSaveRecipientAddress)); + reaction((_) => isAppSecure, (bool isAppSecure) { + sharedPreferences.setBool(PreferencesKey.isAppSecureKey, isAppSecure); + if (Platform.isAndroid) { + setIsAppSecureNative(isAppSecure); + } + }); + + if (Platform.isAndroid) { + setIsAppSecureNative(isAppSecure); + } + reaction( (_) => fiatApiMode, (FiatApiMode mode) => sharedPreferences.setInt( @@ -199,6 +214,9 @@ abstract class SettingsStoreBase with Store { @observable bool shouldSaveRecipientAddress; + @observable + bool isAppSecure; + @observable bool allowBiometricalAuthentication; @@ -289,6 +307,8 @@ abstract class SettingsStoreBase with Store { // FIX-ME: Check for which default value we should have here final shouldSaveRecipientAddress = sharedPreferences.getBool(PreferencesKey.shouldSaveRecipientAddressKey) ?? false; + final isAppSecure = + sharedPreferences.getBool(PreferencesKey.isAppSecureKey) ?? false; final currentFiatApiMode = FiatApiMode.deserialize( raw: sharedPreferences .getInt(PreferencesKey.currentFiatApiModeKey) ?? FiatApiMode.enabled.raw); @@ -316,7 +336,7 @@ abstract class SettingsStoreBase with Store { final pinCodeTimeOutDuration = timeOutDuration != null ? PinCodeRequiredDuration.deserialize(raw: timeOutDuration) : defaultPinCodeTimeOutDuration; - + // If no value if (pinLength == null || pinLength == 0) { pinLength = defaultPinLength; @@ -367,6 +387,7 @@ abstract class SettingsStoreBase with Store { initialFiatCurrency: currentFiatCurrency, initialBalanceDisplayMode: currentBalanceDisplayMode, initialSaveRecipientAddress: shouldSaveRecipientAddress, + initialAppSecure: isAppSecure, initialFiatMode: currentFiatApiMode, initialAllowBiometricalAuthentication: allowBiometricalAuthentication, initialExchangeStatus: exchangeStatus, @@ -412,6 +433,8 @@ abstract class SettingsStoreBase with Store { .getInt(PreferencesKey.currentBalanceDisplayModeKey)!); shouldSaveRecipientAddress = sharedPreferences.getBool(PreferencesKey.shouldSaveRecipientAddressKey) ?? shouldSaveRecipientAddress; + isAppSecure = + sharedPreferences.getBool(PreferencesKey.isAppSecureKey) ?? isAppSecure; allowBiometricalAuthentication = sharedPreferences .getBool(PreferencesKey.allowBiometricalAuthenticationKey) ?? allowBiometricalAuthentication; diff --git a/lib/view_model/settings/privacy_settings_view_model.dart b/lib/view_model/settings/privacy_settings_view_model.dart index 110afb07a..91ddd2f34 100644 --- a/lib/view_model/settings/privacy_settings_view_model.dart +++ b/lib/view_model/settings/privacy_settings_view_model.dart @@ -21,6 +21,9 @@ abstract class PrivacySettingsViewModelBase with Store { @computed FiatApiMode get fiatApiMode => _settingsStore.fiatApiMode; + @computed + bool get isAppSecure => _settingsStore.isAppSecure; + @action void setShouldSaveRecipientAddress(bool value) => _settingsStore.shouldSaveRecipientAddress = value; @@ -30,4 +33,7 @@ abstract class PrivacySettingsViewModelBase with Store { @action void setFiatMode(FiatApiMode fiatApiMode) => _settingsStore.fiatApiMode = fiatApiMode; + @action + void setIsAppSecure(bool value) => _settingsStore.isAppSecure = value; + } diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 8dbf1b900..1a16e2443 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -699,5 +699,6 @@ "sell_monero_com_alert_content": "بيع Monero غير مدعوم حتى الآن", "error_text_input_below_minimum_limit":" المبلغ أقل من الحد الأدنى", "error_text_input_above_maximum_limit":"المبلغ أكبر من الحد الأقصى", - "show_market_place": "إظهار السوق" + "show_market_place": "إظهار السوق", + "prevent_screenshots": "منع لقطات الشاشة وتسجيل الشاشة" } diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 2a95efeee..706e37799 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -700,5 +700,6 @@ "sell_monero_com_alert_content": "Продажбата на Monero все още не се поддържа", "error_text_input_below_minimum_limit" : "Сумата е по-малко от минималната", "error_text_input_above_maximum_limit" : "Сумата надвишава максималната", - "show_market_place":"Покажи пазар" + "show_market_place":"Покажи пазар", + "prevent_screenshots": "Предотвратете екранни снимки и запис на екрана" } diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index ddd22eb61..66006fd8b 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -700,5 +700,6 @@ "sell_monero_com_alert_content": "Prodej Monero zatím není podporován", "error_text_input_below_minimum_limit" : "Částka je menší než minimální hodnota", "error_text_input_above_maximum_limit" : "Částka je větší než maximální hodnota", - "show_market_place": "Zobrazit trh" + "show_market_place": "Zobrazit trh", + "prevent_screenshots": "Zabránit vytváření snímků obrazovky a nahrávání obrazovky" } diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index d29a641e2..f79fb8b4a 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -701,5 +701,6 @@ "sell_monero_com_alert_content": "Der Verkauf von Monero wird noch nicht unterstützt", "error_text_input_below_minimum_limit" : "Menge ist unter dem Minimum", "error_text_input_above_maximum_limit" : "Menge ist über dem Maximum", - "show_market_place": "Marktplatz anzeigen" + "show_market_place": "Marktplatz anzeigen", + "prevent_screenshots": "Verhindern Sie Screenshots und Bildschirmaufzeichnungen" } diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index f4d693e8f..2fe1875c5 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -701,5 +701,6 @@ "sell_monero_com_alert_content": "Selling Monero is not supported yet", "error_text_input_below_minimum_limit" : "Amount is less than the minimum", "error_text_input_above_maximum_limit" : "Amount is more than the maximum", - "show_market_place" :"Show Marketplace" + "show_market_place" :"Show Marketplace", + "prevent_screenshots": "Prevent screenshots and screen recording" } diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index cee502fd3..11153e3ff 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -701,5 +701,6 @@ "sell_monero_com_alert_content": "Aún no se admite la venta de Monero", "error_text_input_below_minimum_limit" : "La cantidad es menos que mínima", "error_text_input_above_maximum_limit" : "La cantidad es más que el máximo", - "show_market_place": "Mostrar mercado" + "show_market_place": "Mostrar mercado", + "prevent_screenshots": "Evitar capturas de pantalla y grabación de pantalla" } diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index c4a44895a..47abb1f2f 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -701,5 +701,6 @@ "sell_monero_com_alert_content": "La vente de Monero n'est pas encore prise en charge", "error_text_input_below_minimum_limit" : "Le montant est inférieur au minimum", "error_text_input_above_maximum_limit" : "Le montant est supérieur au maximum", - "show_market_place" :"Afficher la place de marché" + "show_market_place" :"Afficher la place de marché", + "prevent_screenshots": "Empêcher les captures d'écran et l'enregistrement d'écran" } diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 2669af5dc..d4512c370 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -701,5 +701,6 @@ "sell_monero_com_alert_content": "मोनेरो बेचना अभी तक समर्थित नहीं है", "error_text_input_below_minimum_limit" : "राशि न्यूनतम से कम है", "error_text_input_above_maximum_limit" : "राशि अधिकतम से अधिक है", - "show_market_place":"बाज़ार दिखाएँ" + "show_market_place":"बाज़ार दिखाएँ", + "prevent_screenshots": "स्क्रीनशॉट और स्क्रीन रिकॉर्डिंग रोकें" } diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 865494c8d..9d5e85fc9 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -701,5 +701,6 @@ "sell_monero_com_alert_content": "Prodaja Monera još nije podržana", "error_text_input_below_minimum_limit" : "Iznos je manji od minimalnog", "error_text_input_above_maximum_limit" : "Iznos je veći od maskimalnog", - "show_market_place" : "Prikaži tržište" + "show_market_place" : "Prikaži tržište", + "prevent_screenshots": "Spriječite snimke zaslona i snimanje zaslona" } diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index 12e1f2cfa..4ade62ce8 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -682,5 +682,6 @@ "sell_monero_com_alert_content": "Menjual Monero belum didukung", "error_text_input_below_minimum_limit" : "Jumlah kurang dari minimal", "error_text_input_above_maximum_limit" : "Jumlah lebih dari maksimal", - "show_market_place": "Tampilkan Pasar" + "show_market_place": "Tampilkan Pasar", + "prevent_screenshots": "Cegah tangkapan layar dan perekaman layar" } diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index dce7df8e3..8cef4e216 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -701,5 +701,6 @@ "sell_monero_com_alert_content": "La vendita di Monero non è ancora supportata", "error_text_input_below_minimum_limit" : "L'ammontare è inferiore al minimo", "error_text_input_above_maximum_limit" : "L'ammontare è superiore al massimo", - "show_market_place":"Mostra mercato" + "show_market_place":"Mostra mercato", + "prevent_screenshots": "Impedisci screenshot e registrazione dello schermo" } diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 674457116..ac2eb8642 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -701,5 +701,6 @@ "sell_monero_com_alert_content": "モネロの販売はまだサポートされていません", "error_text_input_below_minimum_limit" : "金額は最小額より少ない", "error_text_input_above_maximum_limit" : "金額は最大値を超えています", - "show_market_place":"マーケットプレイスを表示" + "show_market_place":"マーケットプレイスを表示", + "prevent_screenshots": "スクリーンショットと画面録画を防止する" } diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 4b35b313b..cf829bb46 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -701,5 +701,6 @@ "sell_monero_com_alert_content": "지원되지 않습니다.", "error_text_input_below_minimum_limit" : "금액이 최소보다 적습니다.", "error_text_input_above_maximum_limit" : "금액이 최대 값보다 많습니다.", - "show_market_place":"마켓플레이스 표시" + "show_market_place":"마켓플레이스 표시", + "prevent_screenshots": "스크린샷 및 화면 녹화 방지" } diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index f08634f48..ac2b056ee 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -701,5 +701,6 @@ "sell_monero_com_alert_content": "Monero ရောင်းချခြင်းကို မပံ့ပိုးရသေးပါ။", "error_text_input_below_minimum_limit" : "ပမာဏသည် အနိမ့်ဆုံးထက်နည်းသည်။", "error_text_input_above_maximum_limit" : "ပမာဏသည် အများဆုံးထက် ပိုများသည်။", - "show_market_place":"စျေးကွက်ကိုပြသပါ။" + "show_market_place":"စျေးကွက်ကိုပြသပါ။", + "prevent_screenshots": "ဖန်သားပြင်ဓာတ်ပုံများနှင့် မျက်နှာပြင်ရိုက်ကူးခြင်းကို တားဆီးပါ။" } diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 6e416b004..371df6f62 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -701,5 +701,6 @@ "sell_monero_com_alert_content": "Het verkopen van Monero wordt nog niet ondersteund", "error_text_input_below_minimum_limit" : "Bedrag is minder dan minimaal", "error_text_input_above_maximum_limit" : "Bedrag is meer dan maximaal", - "show_market_place":"Toon Marktplaats" + "show_market_place":"Toon Marktplaats", + "prevent_screenshots": "Voorkom screenshots en schermopname" } diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 357edc2e3..b5e0a0bfd 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -701,5 +701,6 @@ "sell_monero_com_alert_content": "Sprzedaż Monero nie jest jeszcze obsługiwana", "error_text_input_below_minimum_limit" : "Kwota jest mniejsza niż minimalna", "error_text_input_above_maximum_limit" : "Kwota jest większa niż maksymalna", - "show_market_place" : "Pokaż rynek" + "show_market_place" : "Pokaż rynek", + "prevent_screenshots": "Zapobiegaj zrzutom ekranu i nagrywaniu ekranu" } diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index d02147050..633d2e833 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -700,5 +700,6 @@ "sell_monero_com_alert_content": "A venda de Monero ainda não é suportada", "error_text_input_below_minimum_limit" : "O valor é menor que o mínimo", "error_text_input_above_maximum_limit" : "O valor é superior ao máximo", - "show_market_place":"Mostrar mercado" + "show_market_place":"Mostrar mercado", + "prevent_screenshots": "Evite capturas de tela e gravação de tela" } diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index ad14f4cb0..c9230219c 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -701,5 +701,6 @@ "sell_monero_com_alert_content": "Продажа Monero пока не поддерживается", "error_text_input_below_minimum_limit" : "Сумма меньше минимальной", "error_text_input_above_maximum_limit" : "Сумма больше максимальной", - "show_market_place":"Показать торговую площадку" + "show_market_place":"Показать торговую площадку", + "prevent_screenshots": "Предотвратить скриншоты и запись экрана" } diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index b8f229a09..1233ecba4 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -699,5 +699,6 @@ "sell_monero_com_alert_content": "ยังไม่รองรับการขาย Monero", "error_text_input_below_minimum_limit" : "จำนวนเงินน้อยกว่าขั้นต่ำ", "error_text_input_above_maximum_limit" : "จำนวนเงินสูงกว่าค่าสูงสุด", - "show_market_place":"แสดงตลาดกลาง" + "show_market_place":"แสดงตลาดกลาง", + "prevent_screenshots": "ป้องกันภาพหน้าจอและการบันทึกหน้าจอ" } diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 2ef0b9302..01ba57fbe 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -701,5 +701,6 @@ "sell_monero_com_alert_content": "Monero satışı henüz desteklenmiyor", "error_text_input_below_minimum_limit" : "Miktar minimumdan daha azdır", "error_text_input_above_maximum_limit" : "Miktar maksimumdan daha fazla", - "show_market_place":"Pazar Yerini Göster" + "show_market_place":"Pazar Yerini Göster", + "prevent_screenshots": "Ekran görüntülerini ve ekran kaydını önleyin" } diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 72cccf43b..9d8010eca 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -700,5 +700,6 @@ "sell_monero_com_alert_content": "Продаж Monero ще не підтримується", "error_text_input_below_minimum_limit" : "Сума менша мінімальної", "error_text_input_above_maximum_limit" : "Сума більше максимальної", - "show_market_place":"Шоу Ринок" + "show_market_place":"Відображати маркетплейс", + "prevent_screenshots": "Запобігати знімкам екрана та запису екрана" } diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index 000ce76b4..15ca6833b 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -701,5 +701,6 @@ "sell_monero_com_alert_content": "Monero فروخت کرنا ابھی تک تعاون یافتہ نہیں ہے۔", "error_text_input_below_minimum_limit" : "رقم کم از کم سے کم ہے۔", "error_text_input_above_maximum_limit" : "رقم زیادہ سے زیادہ سے زیادہ ہے۔", - "show_market_place":"بازار دکھائیں۔" + "show_market_place":"بازار دکھائیں۔", + "prevent_screenshots": "اسکرین شاٹس اور اسکرین ریکارڈنگ کو روکیں۔" } diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index e95e854d9..d906ebf5b 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -700,5 +700,6 @@ "sell_monero_com_alert_content": "尚不支持出售门罗币", "error_text_input_below_minimum_limit" : "金额小于最小值", "error_text_input_above_maximum_limit" : "金额大于最大值", - "show_market_place" :"显示市场" + "show_market_place" :"显示市场", + "prevent_screenshots": "防止截屏和录屏" } From 315c4c911c1c6af214ac3e7a59a1acbb04a19ed5 Mon Sep 17 00:00:00 2001 From: Serhii Date: Thu, 20 Apr 2023 16:46:41 +0300 Subject: [PATCH 04/39] CW-325-Coin-Control-enhancements (#846) * fix checkbox * save the output state * add note as a header * Allow copy the Amount and Address * add frozen balance to dashboard * add block explorer * fix url launcher * code formatting * minor fixes * Revert "minor fixes" This reverts commit d230b6a07bc3855407251991926ab0f257c55a5a. * fix missing implementations error * [skip ci] update localization * fix unspent with same txid * add amount check * add vout check * remove formattedTotalAvailableBalance * remove unrelated mac os files --- cw_bitcoin/lib/electrum_balance.dart | 19 +- cw_bitcoin/lib/electrum_wallet.dart | 31 +- cw_bitcoin/lib/electrum_wallet_snapshot.dart | 2 +- cw_core/lib/unspent_coins_info.dart | 14 +- cw_core/lib/wallet_base.dart | 2 + cw_haven/lib/haven_wallet.dart | 3 + cw_monero/lib/monero_wallet.dart | 2 + .../dashboard/widgets/balance_page.dart | 274 ++++++++++-------- .../unspent_coins_details_page.dart | 32 +- .../widgets/unspent_coins_list_item.dart | 131 ++++----- .../dashboard/balance_view_model.dart | 76 ++++- .../unspent_coins_details_view_model.dart | 76 +++-- .../unspent_coins/unspent_coins_item.dart | 10 +- .../unspent_coins_list_view_model.dart | 75 +++-- res/values/strings_ar.arb | 5 +- res/values/strings_bg.arb | 5 +- res/values/strings_cs.arb | 5 +- res/values/strings_de.arb | 5 +- res/values/strings_en.arb | 5 +- res/values/strings_es.arb | 7 +- res/values/strings_fr.arb | 5 +- res/values/strings_hi.arb | 5 +- res/values/strings_hr.arb | 5 +- res/values/strings_id.arb | 5 +- res/values/strings_it.arb | 5 +- res/values/strings_ja.arb | 5 +- res/values/strings_ko.arb | 5 +- res/values/strings_my.arb | 5 +- res/values/strings_nl.arb | 5 +- res/values/strings_pl.arb | 5 +- res/values/strings_pt.arb | 5 +- res/values/strings_ru.arb | 5 +- res/values/strings_th.arb | 5 +- res/values/strings_tr.arb | 5 +- res/values/strings_uk.arb | 5 +- res/values/strings_ur.arb | 5 +- res/values/strings_zh.arb | 5 +- 37 files changed, 525 insertions(+), 339 deletions(-) diff --git a/cw_bitcoin/lib/electrum_balance.dart b/cw_bitcoin/lib/electrum_balance.dart index a26b79ddb..0a9a33d54 100644 --- a/cw_bitcoin/lib/electrum_balance.dart +++ b/cw_bitcoin/lib/electrum_balance.dart @@ -4,7 +4,7 @@ import 'package:cw_bitcoin/bitcoin_amount_format.dart'; import 'package:cw_core/balance.dart'; class ElectrumBalance extends Balance { - const ElectrumBalance({required this.confirmed, required this.unconfirmed}) + const ElectrumBalance({required this.confirmed, required this.unconfirmed, required this.frozen}) : super(confirmed, unconfirmed); static ElectrumBalance? fromJSON(String? jsonSource) { @@ -16,20 +16,25 @@ class ElectrumBalance extends Balance { return ElectrumBalance( confirmed: decoded['confirmed'] as int? ?? 0, - unconfirmed: decoded['unconfirmed'] as int? ?? 0); + unconfirmed: decoded['unconfirmed'] as int? ?? 0, + frozen: decoded['frozen'] as int? ?? 0); } final int confirmed; final int unconfirmed; + final int frozen; @override - String get formattedAvailableBalance => - bitcoinAmountToString(amount: confirmed); + String get formattedAvailableBalance => bitcoinAmountToString(amount: confirmed - frozen); @override - String get formattedAdditionalBalance => - bitcoinAmountToString(amount: unconfirmed); + String get formattedAdditionalBalance => bitcoinAmountToString(amount: unconfirmed); + + String get formattedFrozenBalance { + final frozenFormatted = bitcoinAmountToString(amount: frozen); + return frozenFormatted == '0.0' ? '' : frozenFormatted; + } String toJSON() => - json.encode({'confirmed': confirmed, 'unconfirmed': unconfirmed}); + json.encode({'confirmed': confirmed, 'unconfirmed': unconfirmed, 'frozen': frozen}); } diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index f31db7bb8..4984ad28e 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -63,7 +63,8 @@ abstract class ElectrumWalletBase extends WalletBase.of( currency != null - ? {currency: initialBalance ?? const ElectrumBalance(confirmed: 0, unconfirmed: 0)} + ? {currency: initialBalance ?? const ElectrumBalance(confirmed: 0, unconfirmed: 0, + frozen: 0)} : {}), this.unspentCoinsInfo = unspentCoinsInfo, super(walletInfo) { @@ -133,8 +134,8 @@ abstract class ElectrumWalletBase extends WalletBase _updateBalance() async { + Future updateBalance() async { balance[currency] = await _fetchBalances(); await save(); } diff --git a/cw_bitcoin/lib/electrum_wallet_snapshot.dart b/cw_bitcoin/lib/electrum_wallet_snapshot.dart index 3755e7d18..6db0c23f2 100644 --- a/cw_bitcoin/lib/electrum_wallet_snapshot.dart +++ b/cw_bitcoin/lib/electrum_wallet_snapshot.dart @@ -37,7 +37,7 @@ class ElectrumWallletSnapshot { .map((addr) => BitcoinAddressRecord.fromJSON(addr)) .toList(); final balance = ElectrumBalance.fromJSON(data['balance'] as String) ?? - ElectrumBalance(confirmed: 0, unconfirmed: 0); + ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0); var regularAddressIndex = 0; var changeAddressIndex = 0; diff --git a/cw_core/lib/unspent_coins_info.dart b/cw_core/lib/unspent_coins_info.dart index d825db279..75c13f2cd 100644 --- a/cw_core/lib/unspent_coins_info.dart +++ b/cw_core/lib/unspent_coins_info.dart @@ -9,7 +9,10 @@ class UnspentCoinsInfo extends HiveObject { required this.hash, required this.isFrozen, required this.isSending, - required this.noteRaw}); + required this.noteRaw, + required this.address, + required this.vout, + required this.value}); static const typeId = 9; static const boxName = 'Unspent'; @@ -30,6 +33,15 @@ class UnspentCoinsInfo extends HiveObject { @HiveField(4) String? noteRaw; + @HiveField(5, defaultValue: '') + String address; + + @HiveField(6, defaultValue: 0) + int value; + + @HiveField(7, defaultValue: 0) + int vout; + String get note => noteRaw ?? ''; set note(String value) => noteRaw = value; diff --git a/cw_core/lib/wallet_base.dart b/cw_core/lib/wallet_base.dart index 1983e62b7..93821448c 100644 --- a/cw_core/lib/wallet_base.dart +++ b/cw_core/lib/wallet_base.dart @@ -71,4 +71,6 @@ abstract class WalletBase< void close(); Future changePassword(String password); + + Future? updateBalance(); } diff --git a/cw_haven/lib/haven_wallet.dart b/cw_haven/lib/haven_wallet.dart index e761d21fa..2a72f078f 100644 --- a/cw_haven/lib/haven_wallet.dart +++ b/cw_haven/lib/haven_wallet.dart @@ -104,6 +104,9 @@ abstract class HavenWalletBase extends WalletBase await save()); } + @override + Future? updateBalance() => null; + @override void close() { _listener?.stop(); diff --git a/cw_monero/lib/monero_wallet.dart b/cw_monero/lib/monero_wallet.dart index cb9cb0ceb..eea490ba9 100644 --- a/cw_monero/lib/monero_wallet.dart +++ b/cw_monero/lib/monero_wallet.dart @@ -118,6 +118,8 @@ abstract class MoneroWalletBase extends WalletBase await save()); } + @override + Future? updateBalance() => null; @override void close() { diff --git a/lib/src/screens/dashboard/widgets/balance_page.dart b/lib/src/screens/dashboard/widgets/balance_page.dart index bf8b1ae17..14933e7c5 100644 --- a/lib/src/screens/dashboard/widgets/balance_page.dart +++ b/lib/src/screens/dashboard/widgets/balance_page.dart @@ -8,187 +8,207 @@ import 'package:auto_size_text/auto_size_text.dart'; import 'package:cake_wallet/src/widgets/introducing_card.dart'; import 'package:cake_wallet/generated/i18n.dart'; - -class BalancePage extends StatelessWidget{ +class BalancePage extends StatelessWidget { BalancePage({required this.dashboardViewModel, required this.settingsStore}); final DashboardViewModel dashboardViewModel; final SettingsStore settingsStore; + Color get backgroundLightColor => + settingsStore.currentTheme.type == ThemeType.bright ? Colors.transparent : Colors.white; + @override Widget build(BuildContext context) { return GestureDetector( - onLongPress: () => dashboardViewModel.balanceViewModel.isReversing = !dashboardViewModel.balanceViewModel.isReversing, - onLongPressUp: () => dashboardViewModel.balanceViewModel.isReversing = !dashboardViewModel.balanceViewModel.isReversing, - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: ResponsiveLayoutUtil.instance.isMobile(context) ? 56 : 16), + onLongPress: () => dashboardViewModel.balanceViewModel.isReversing = + !dashboardViewModel.balanceViewModel.isReversing, + onLongPressUp: () => dashboardViewModel.balanceViewModel.isReversing = + !dashboardViewModel.balanceViewModel.isReversing, + child: SingleChildScrollView( + child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + SizedBox(height: 56), Container( - margin: const EdgeInsets.only(left: 24, bottom: 16), - child: Observer(builder: (_) { - return Text( - dashboardViewModel.balanceViewModel.asset, - style: TextStyle( - fontSize: 24, - fontFamily: 'Lato', - fontWeight: FontWeight.w600, - color: Theme.of(context) - .accentTextTheme! - .headline2! - .backgroundColor!, - height: 1), - maxLines: 1, - textAlign: TextAlign.center); - })), + margin: const EdgeInsets.only(left: 24, bottom: 16), + child: Observer(builder: (_) { + return Text(dashboardViewModel.balanceViewModel.asset, + style: TextStyle( + fontSize: 24, + fontFamily: 'Lato', + fontWeight: FontWeight.w600, + color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!, + height: 1), + maxLines: 1, + textAlign: TextAlign.center); + })), Observer(builder: (_) { - if (dashboardViewModel.balanceViewModel.isShowCard){ + if (dashboardViewModel.balanceViewModel.isShowCard) { return IntroducingCard( - title: S.of(context).introducing_cake_pay, + title: S.of(context).introducing_cake_pay, subTitle: S.of(context).cake_pay_learn_more, borderColor: settingsStore.currentTheme.type == ThemeType.bright ? Color.fromRGBO(255, 255, 255, 0.2) : Colors.transparent, - closeCard: dashboardViewModel.balanceViewModel.disableIntroCakePayCard - ); + closeCard: dashboardViewModel.balanceViewModel.disableIntroCakePayCard); } - return Container (); + return Container(); }), Observer(builder: (_) { return ListView.separated( - physics: NeverScrollableScrollPhysics(), - shrinkWrap: true, - separatorBuilder: (_, __) => Container(padding: EdgeInsets.only(bottom: 8)), - itemCount: dashboardViewModel.balanceViewModel.formattedBalances.length, - itemBuilder: (__, index) { - final balance = dashboardViewModel.balanceViewModel.formattedBalances.elementAt(index); - return buildBalanceRow(context, - availableBalanceLabel: '${dashboardViewModel.balanceViewModel.availableBalanceLabel}', + physics: NeverScrollableScrollPhysics(), + shrinkWrap: true, + separatorBuilder: (_, __) => Container(padding: EdgeInsets.only(bottom: 8)), + itemCount: dashboardViewModel.balanceViewModel.formattedBalances.length, + itemBuilder: (__, index) { + final balance = + dashboardViewModel.balanceViewModel.formattedBalances.elementAt(index); + return buildBalanceRow(context, + availableBalanceLabel: + '${dashboardViewModel.balanceViewModel.availableBalanceLabel}', availableBalance: balance.availableBalance, availableFiatBalance: balance.fiatAvailableBalance, - additionalBalanceLabel: '${dashboardViewModel.balanceViewModel.additionalBalanceLabel}', + additionalBalanceLabel: + '${dashboardViewModel.balanceViewModel.additionalBalanceLabel}', additionalBalance: balance.additionalBalance, additionalFiatBalance: balance.fiatAdditionalBalance, + frozenBalance: balance.frozenBalance, + frozenFiatBalance: balance.fiatFrozenBalance, currency: balance.formattedAssetTitle); - }); - }) - ]))); + }); + }) + ]))); } Widget buildBalanceRow(BuildContext context, - {required String availableBalanceLabel, + {required String availableBalanceLabel, required String availableBalance, required String availableFiatBalance, required String additionalBalanceLabel, required String additionalBalance, required String additionalFiatBalance, + required String frozenBalance, + required String frozenFiatBalance, required String currency}) { - return Container( + return Container( margin: const EdgeInsets.only(left: 16, right: 16), decoration: BoxDecoration( - borderRadius: BorderRadius.circular(30.0), - border: Border.all(color: settingsStore.currentTheme.type == ThemeType.bright ? Color.fromRGBO(255, 255, 255, 0.2): Colors.transparent, width: 1, ), - color:Theme.of(context).textTheme!.headline6!.backgroundColor! - ), + borderRadius: BorderRadius.circular(30.0), + border: Border.all( + color: settingsStore.currentTheme.type == ThemeType.bright + ? Color.fromRGBO(255, 255, 255, 0.2) + : Colors.transparent, + width: 1, + ), + color: Theme.of(context).textTheme!.headline6!.backgroundColor!), child: Container( - margin: const EdgeInsets.only(top: 16, left: 24, right: 24, bottom: 24), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 4,), - Text('${availableBalanceLabel}', - textAlign: TextAlign.center, - style: TextStyle( + margin: const EdgeInsets.only(top: 16, left: 24, right: 24, bottom: 24), + child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + SizedBox( + height: 4, + ), + Text('${availableBalanceLabel}', + textAlign: TextAlign.center, + style: TextStyle( fontSize: 12, fontFamily: 'Lato', fontWeight: FontWeight.w400, - color: Theme.of(context) - .accentTextTheme! - .headline3! - .backgroundColor!, + color: Theme.of(context).accentTextTheme!.headline3!.backgroundColor!, height: 1)), - SizedBox(height: 5), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - AutoSizeText( - availableBalance, - style: TextStyle( - fontSize: 24, - fontFamily: 'Lato', - fontWeight: FontWeight.w900, - color: Theme.of(context) - .accentTextTheme! - .headline2! - .backgroundColor!, - height: 1), - maxLines: 1, - textAlign: TextAlign.center), - Text(currency, - style: TextStyle( - fontSize: 28, - fontFamily: 'Lato', - fontWeight: FontWeight.w800, - color: Theme.of(context) - .accentTextTheme! - .headline2! - .backgroundColor!, - height: 1)), - ]), - SizedBox(height: 4,), - Text('${availableFiatBalance}', + SizedBox(height: 5), + Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + AutoSizeText(availableBalance, + style: TextStyle( + fontSize: 24, + fontFamily: 'Lato', + fontWeight: FontWeight.w900, + color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!, + height: 1), + maxLines: 1, + textAlign: TextAlign.center), + Text(currency, + style: TextStyle( + fontSize: 28, + fontFamily: 'Lato', + fontWeight: FontWeight.w800, + color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!, + height: 1)), + ]), + SizedBox( + height: 4, + ), + Text('${availableFiatBalance}', textAlign: TextAlign.center, style: TextStyle( - fontSize: 16, - fontFamily: 'Lato', - fontWeight: FontWeight.w500, - color: Theme.of(context) - .accentTextTheme! - .headline2! - .backgroundColor!, - height: 1)), - SizedBox(height: 26), - Text('${additionalBalanceLabel}', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, - fontFamily: 'Lato', - fontWeight: FontWeight.w400, - color: Theme.of(context) - .accentTextTheme! - .headline3! - .backgroundColor!, - height: 1)), - SizedBox(height: 8), - AutoSizeText( - additionalBalance, + fontSize: 16, + fontFamily: 'Lato', + fontWeight: FontWeight.w500, + color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!, + height: 1)), + SizedBox(height: 26), + if (frozenBalance.isNotEmpty) + Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text(S.current.frozen_balance, + textAlign: TextAlign.center, style: TextStyle( - fontSize: 20, + fontSize: 12, fontFamily: 'Lato', fontWeight: FontWeight.w400, - color: Theme.of(context) - .accentTextTheme! - .headline2! - .backgroundColor!, + color: Theme.of(context).accentTextTheme!.headline3!.backgroundColor!, + height: 1)), + SizedBox(height: 8), + AutoSizeText(frozenBalance, + style: TextStyle( + fontSize: 20, + fontFamily: 'Lato', + fontWeight: FontWeight.w400, + color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!, height: 1), maxLines: 1, textAlign: TextAlign.center), - SizedBox(height: 4,), - Text('${additionalFiatBalance}', + SizedBox(height: 4), + Text( + frozenFiatBalance, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + fontFamily: 'Lato', + fontWeight: FontWeight.w400, + color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!, + height: 1), + ), + SizedBox(height: 24) + ]), + Text('${additionalBalanceLabel}', textAlign: TextAlign.center, style: TextStyle( + fontSize: 12, + fontFamily: 'Lato', + fontWeight: FontWeight.w400, + color: Theme.of(context).accentTextTheme!.headline3!.backgroundColor!, + height: 1)), + SizedBox(height: 8), + AutoSizeText(additionalBalance, + style: TextStyle( + fontSize: 20, + fontFamily: 'Lato', + fontWeight: FontWeight.w400, + color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!, + height: 1), + maxLines: 1, + textAlign: TextAlign.center), + SizedBox( + height: 4, + ), + Text( + '${additionalFiatBalance}', + textAlign: TextAlign.center, + style: TextStyle( fontSize: 12, fontFamily: 'Lato', fontWeight: FontWeight.w400, - color: Theme.of(context) - .accentTextTheme! - .headline2! - .backgroundColor!, + color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!, height: 1), - ) - ])), + ) + ])), ); } } - diff --git a/lib/src/screens/unspent_coins/unspent_coins_details_page.dart b/lib/src/screens/unspent_coins/unspent_coins_details_page.dart index d8ce24d88..00c7b9796 100644 --- a/lib/src/screens/unspent_coins/unspent_coins_details_page.dart +++ b/lib/src/screens/unspent_coins/unspent_coins_details_page.dart @@ -1,13 +1,16 @@ +import 'package:cake_wallet/src/screens/transaction_details/blockexplorer_list_item.dart'; import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart'; import 'package:cake_wallet/src/screens/transaction_details/widgets/textfield_list_row.dart'; import 'package:cake_wallet/src/screens/unspent_coins/widgets/unspent_coins_switch_row.dart'; import 'package:cake_wallet/src/widgets/standard_list.dart'; +import 'package:cake_wallet/utils/show_bar.dart'; import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_details_view_model.dart'; import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_switch_item.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/src/widgets/list_row.dart'; import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:cake_wallet/generated/i18n.dart'; @@ -30,9 +33,13 @@ class UnspentCoinsDetailsPage extends BasePage { final item = unspentCoinsDetailsViewModel.items[index]; if (item is StandartListItem) { - return ListRow( - title: '${item.title}:', - value: item.value); + return GestureDetector( + onTap: () { + Clipboard.setData(ClipboardData(text: item.value)); + showBar(context, S.of(context).transaction_details_copied(item.title)); + }, + child: ListRow(title: '${item.title}:', value: item.value), + ); } if (item is TextFieldListItem) { @@ -44,14 +51,21 @@ class UnspentCoinsDetailsPage extends BasePage { } if (item is UnspentCoinsSwitchItem) { - return Observer(builder: (_) => UnspentCoinsSwitchRow( - title: item.title, - switchValue: item.switchValue(), - onSwitchValueChange: item.onSwitchValueChange - )); + return Observer( + builder: (_) => UnspentCoinsSwitchRow( + title: item.title, + switchValue: item.switchValue(), + onSwitchValueChange: item.onSwitchValueChange)); + } + + if (item is BlockExplorerListItem) { + return GestureDetector( + onTap: item.onTap, + child: ListRow(title: '${item.title}:', value: item.value), + ); } return Container(); }); } -} \ No newline at end of file +} diff --git a/lib/src/screens/unspent_coins/widgets/unspent_coins_list_item.dart b/lib/src/screens/unspent_coins/widgets/unspent_coins_list_item.dart index fac036f0e..b1916d06c 100644 --- a/lib/src/screens/unspent_coins/widgets/unspent_coins_list_item.dart +++ b/lib/src/screens/unspent_coins/widgets/unspent_coins_list_item.dart @@ -1,5 +1,6 @@ import 'package:auto_size_text/auto_size_text.dart'; import 'package:cake_wallet/palette.dart'; +import 'package:cake_wallet/src/widgets/standard_checkbox.dart'; import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; import 'package:cake_wallet/generated/i18n.dart'; @@ -28,99 +29,77 @@ class UnspentCoinsListItem extends StatelessWidget { @override Widget build(BuildContext context) { - final itemColor = isSending? selectedItemColor : unselectedItemColor; - final _note = (note?.isNotEmpty ?? false) ? note : address; - + final itemColor = isSending ? selectedItemColor : unselectedItemColor; return Container( - height: 62, - padding: EdgeInsets.all(12), - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(12)), - color: itemColor), + height: 70, + padding: EdgeInsets.symmetric(vertical: 6, horizontal: 12), + decoration: + BoxDecoration(borderRadius: BorderRadius.all(Radius.circular(12)), color: itemColor), child: Row( - mainAxisSize: MainAxisSize.max, crossAxisAlignment: CrossAxisAlignment.center, children: [ Padding( padding: EdgeInsets.only(right: 12), - child: GestureDetector( - onTap: () => onCheckBoxTap?.call(), - child: Container( - height: 24.0, - width: 24.0, - decoration: BoxDecoration( - border: Border.all( - color: Theme.of(context) - .primaryTextTheme! - .caption! - .color!, - width: 1.0), - borderRadius: BorderRadius.all( - Radius.circular(8.0)), - color: itemColor), - child: isSending - ? Icon( - Icons.check, - color: Colors.blue, - size: 20.0, - ) - : Offstage(), - ) - ) - ), + child: StandardCheckbox( + value: isSending, onChanged: (value) => onCheckBoxTap?.call())), Expanded( child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Expanded( - child: AutoSizeText( - amount, - style: TextStyle( - color: amountColor, - fontSize: 16, - fontWeight: FontWeight.w600 - ), - maxLines: 1, - ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (note.isNotEmpty) + AutoSizeText( + note, + style: TextStyle( + color: amountColor, fontSize: 15, fontWeight: FontWeight.w600), + maxLines: 1, ), - if (isFrozen) Container( + AutoSizeText( + amount, + style: + TextStyle(color: amountColor, fontSize: 15, fontWeight: FontWeight.w600), + maxLines: 1, + ) + ]), + if (isFrozen) + Container( height: 17, padding: EdgeInsets.only(left: 6, right: 6), decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(8.5)), - color: Colors.white), + borderRadius: BorderRadius.all(Radius.circular(8.5)), + color: Colors.white), alignment: Alignment.center, child: Text( - S.of(context).frozen, - style: TextStyle( - color: amountColor, - fontSize: 7, - fontWeight: FontWeight.w600 - ), - ) - ) - ], - ), - Text( - _note, - style: TextStyle( - color: addressColor, - fontSize: 12, + S.of(context).frozen, + style: + TextStyle(color: amountColor, fontSize: 7, fontWeight: FontWeight.w600), + )) + ], + ), + Expanded( + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + AutoSizeText( + address, + style: TextStyle( + color: addressColor, + fontSize: 12, + ), + maxLines: 1, ), - maxLines: 1, - overflow: TextOverflow.ellipsis - ) - ] - ) - ) + ], + ), + ), + ])), ], - ) - ); + )); } -} \ No newline at end of file +} diff --git a/lib/view_model/dashboard/balance_view_model.dart b/lib/view_model/dashboard/balance_view_model.dart index 33fe01ba9..3b9dcce55 100644 --- a/lib/view_model/dashboard/balance_view_model.dart +++ b/lib/view_model/dashboard/balance_view_model.dart @@ -1,5 +1,9 @@ +import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/entities/fiat_api_mode.dart'; +import 'package:cw_bitcoin/bitcoin_amount_format.dart'; +import 'package:cw_bitcoin/electrum_balance.dart'; import 'package:cw_core/transaction_history.dart'; +import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/balance.dart'; import 'package:cw_core/crypto_currency.dart'; @@ -11,6 +15,7 @@ import 'package:cake_wallet/entities/calculate_fiat_amount.dart'; import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart'; +import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; part 'balance_view_model.g.dart'; @@ -19,14 +24,18 @@ class BalanceRecord { const BalanceRecord({ required this.availableBalance, required this.additionalBalance, + required this.frozenBalance, required this.fiatAvailableBalance, required this.fiatAdditionalBalance, + required this.fiatFrozenBalance, required this.asset, required this.formattedAssetTitle}); final String fiatAdditionalBalance; final String fiatAvailableBalance; + final String fiatFrozenBalance; final String additionalBalance; final String availableBalance; + final String frozenBalance; final CryptoCurrency asset; final String formattedAssetTitle; } @@ -135,6 +144,32 @@ abstract class BalanceViewModelBase with Store { return walletBalance.formattedAvailableBalance; } + @computed + String get frozenBalance { + final walletBalance = _walletBalance; + + if (displayMode == BalanceDisplayMode.hiddenBalance) { + return '---'; + } + + return getFormattedFrozenBalance(walletBalance); + } + + @computed + String get frozenFiatBalance { + final walletBalance = _walletBalance; + final fiatCurrency = settingsStore.fiatCurrency; + + if (displayMode == BalanceDisplayMode.hiddenBalance) { + return '---'; + } + + return _getFiatBalance( + price: price, + cryptoAmount: getFormattedFrozenBalance(walletBalance)) + ' ' + fiatCurrency.toString(); + + } + @computed String get additionalBalance { final walletBalance = _walletBalance; @@ -158,7 +193,7 @@ abstract class BalanceViewModelBase with Store { return _getFiatBalance( price: price, cryptoAmount: walletBalance.formattedAvailableBalance) + ' ' + fiatCurrency.toString(); - + } @computed @@ -173,7 +208,7 @@ abstract class BalanceViewModelBase with Store { return _getFiatBalance( price: price, cryptoAmount: walletBalance.formattedAdditionalBalance) + ' ' + fiatCurrency.toString(); - + } @computed @@ -183,8 +218,10 @@ abstract class BalanceViewModelBase with Store { return MapEntry(key, BalanceRecord( availableBalance: '---', additionalBalance: '---', + frozenBalance: '---', fiatAdditionalBalance: isFiatDisabled ? '' : '---', fiatAvailableBalance: isFiatDisabled ? '' : '---', + fiatFrozenBalance: isFiatDisabled ? '' : '---', asset: key, formattedAssetTitle: _formatterAsset(key))); } @@ -207,13 +244,25 @@ abstract class BalanceViewModelBase with Store { price: price, cryptoAmount: value.formattedAvailableBalance)); - return MapEntry(key, BalanceRecord( - availableBalance: value.formattedAvailableBalance, - additionalBalance: value.formattedAdditionalBalance, - fiatAdditionalBalance: additionalFiatBalance, - fiatAvailableBalance: availableFiatBalance, - asset: key, - formattedAssetTitle: _formatterAsset(key))); + + final frozenFiatBalance = isFiatDisabled ? '' : (fiatCurrency.toString() + + ' ' + + _getFiatBalance( + price: price, + cryptoAmount: getFormattedFrozenBalance(value))); + + + return MapEntry( + key, + BalanceRecord( + availableBalance: value.formattedAvailableBalance, + additionalBalance: value.formattedAdditionalBalance, + frozenBalance: getFormattedFrozenBalance(value), + fiatAdditionalBalance: additionalFiatBalance, + fiatAvailableBalance: availableFiatBalance, + fiatFrozenBalance: frozenFiatBalance, + asset: key, + formattedAssetTitle: _formatterAsset(key))); }); } @@ -290,7 +339,7 @@ abstract class BalanceViewModelBase with Store { } String _getFiatBalance({required double price, String? cryptoAmount}) { - if (cryptoAmount == null) { + if (cryptoAmount == null || cryptoAmount.isEmpty) { return '0.00'; } @@ -306,10 +355,15 @@ abstract class BalanceViewModelBase with Store { return assetStringified.replaceFirst('X', 'x'); } - return asset.toString(); + return asset.toString(); default: return asset.toString(); } } + + + String getFormattedFrozenBalance(Balance walletBalance) => + walletBalance is ElectrumBalance ? walletBalance.formattedFrozenBalance : ''; + } diff --git a/lib/view_model/unspent_coins/unspent_coins_details_view_model.dart b/lib/view_model/unspent_coins/unspent_coins_details_view_model.dart index ed8687c87..098296036 100644 --- a/lib/view_model/unspent_coins/unspent_coins_details_view_model.dart +++ b/lib/view_model/unspent_coins/unspent_coins_details_view_model.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/src/screens/transaction_details/blockexplorer_list_item.dart'; import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart'; import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart'; import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart'; @@ -5,7 +6,9 @@ import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart'; import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_switch_item.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:mobx/mobx.dart'; +import 'package:url_launcher/url_launcher.dart'; part 'unspent_coins_details_view_model.g.dart'; @@ -13,21 +16,14 @@ class UnspentCoinsDetailsViewModel = UnspentCoinsDetailsViewModelBase with _$UnspentCoinsDetailsViewModel; abstract class UnspentCoinsDetailsViewModelBase with Store { - UnspentCoinsDetailsViewModelBase({ - required this.unspentCoinsItem, - required this.unspentCoinsListViewModel}) + UnspentCoinsDetailsViewModelBase( + {required this.unspentCoinsItem, required this.unspentCoinsListViewModel}) : items = [], isFrozen = unspentCoinsItem.isFrozen, note = unspentCoinsItem.note { items = [ - StandartListItem( - title: S.current.transaction_details_amount, - value: unspentCoinsItem.amount - ), - StandartListItem( - title: S.current.widgets_address, - value: unspentCoinsItem.address - ), + StandartListItem(title: S.current.transaction_details_amount, value: unspentCoinsItem.amount), + StandartListItem(title: S.current.widgets_address, value: unspentCoinsItem.address), TextFieldListItem( title: S.current.note_tap_to_change, value: note, @@ -36,21 +32,53 @@ abstract class UnspentCoinsDetailsViewModelBase with Store { unspentCoinsListViewModel.saveUnspentCoinInfo(unspentCoinsItem); }), UnspentCoinsSwitchItem( - title: S.current.freeze, - value: '', - switchValue: () => isFrozen, - onSwitchValueChange: (value) async { - isFrozen = value; - unspentCoinsItem.isFrozen = value; - if (value) { - unspentCoinsItem.isSending = !value; - } - await unspentCoinsListViewModel.saveUnspentCoinInfo(unspentCoinsItem); - } - ) + title: S.current.freeze, + value: '', + switchValue: () => isFrozen, + onSwitchValueChange: (value) async { + isFrozen = value; + unspentCoinsItem.isFrozen = value; + if (value) { + unspentCoinsItem.isSending = !value; + } + await unspentCoinsListViewModel.saveUnspentCoinInfo(unspentCoinsItem); + }), + BlockExplorerListItem( + title: S.current.view_in_block_explorer, + value: _explorerDescription(unspentCoinsListViewModel.wallet.type), + onTap: () { + try { + final url = Uri.parse( + _explorerUrl(unspentCoinsListViewModel.wallet.type, unspentCoinsItem.hash)); + return launchUrl(url); + } catch (e) {} + + }) ]; } + String _explorerUrl(WalletType type, String txId) { + switch (type) { + case WalletType.bitcoin: + return 'https://ordinals.com/tx/${txId}'; + case WalletType.litecoin: + return 'https://litecoin.earlyordies.com/tx/${txId}'; + default: + return ''; + } + } + + String _explorerDescription(WalletType type) { + switch (type) { + case WalletType.bitcoin: + return S.current.view_transaction_on + 'Ordinals.com'; + case WalletType.litecoin: + return S.current.view_transaction_on + 'Earlyordies.com'; + default: + return ''; + } + } + @observable bool isFrozen; @@ -60,4 +88,4 @@ abstract class UnspentCoinsDetailsViewModelBase with Store { final UnspentCoinsItem unspentCoinsItem; final UnspentCoinsListViewModel unspentCoinsListViewModel; List items; -} \ No newline at end of file +} diff --git a/lib/view_model/unspent_coins/unspent_coins_item.dart b/lib/view_model/unspent_coins/unspent_coins_item.dart index c6b9eb375..2f0d75571 100644 --- a/lib/view_model/unspent_coins/unspent_coins_item.dart +++ b/lib/view_model/unspent_coins/unspent_coins_item.dart @@ -11,7 +11,9 @@ abstract class UnspentCoinsItemBase with Store { required this.hash, required this.isFrozen, required this.note, - required this.isSending}); + required this.isSending, + required this.amountRaw, + required this.vout}); @observable String address; @@ -30,4 +32,10 @@ abstract class UnspentCoinsItemBase with Store { @observable bool isSending; + + @observable + int amountRaw; + + @observable + int vout; } \ No newline at end of file diff --git a/lib/view_model/unspent_coins/unspent_coins_list_view_model.dart b/lib/view_model/unspent_coins/unspent_coins_list_view_model.dart index dbd43fdac..4df4b9a66 100644 --- a/lib/view_model/unspent_coins/unspent_coins_list_view_model.dart +++ b/lib/view_model/unspent_coins/unspent_coins_list_view_model.dart @@ -1,60 +1,81 @@ -//import 'package:cw_bitcoin/bitcoin_amount_format.dart'; -//import 'package:cw_bitcoin/electrum_wallet.dart'; import 'package:cw_core/unspent_coins_info.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart'; -import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; +import 'package:collection/collection.dart'; part 'unspent_coins_list_view_model.g.dart'; class UnspentCoinsListViewModel = UnspentCoinsListViewModelBase with _$UnspentCoinsListViewModel; abstract class UnspentCoinsListViewModelBase with Store { - UnspentCoinsListViewModelBase({ - required this.wallet, - required Box unspentCoinsInfo}) - : _unspentCoinsInfo = unspentCoinsInfo { + UnspentCoinsListViewModelBase( + {required this.wallet, required Box unspentCoinsInfo}) + : _unspentCoinsInfo = unspentCoinsInfo { bitcoin!.updateUnspents(wallet); } WalletBase wallet; - Box _unspentCoinsInfo; + final Box _unspentCoinsInfo; @computed - ObservableList get items => ObservableList.of(bitcoin!.getUnspents(wallet).map((elem) { - final amount = bitcoin!.formatterBitcoinAmountToString(amount: elem.value) + - ' ${wallet.currency.title}'; - - final info = _unspentCoinsInfo.values - .firstWhere((element) => element.walletId == wallet.id && element.hash == elem.hash); + ObservableList get items => + ObservableList.of(bitcoin!.getUnspents(wallet).map((elem) { + final amount = bitcoin!.formatterBitcoinAmountToString(amount: elem.value) + + ' ${wallet.currency.title}'; - return UnspentCoinsItem( - address: elem.address, - amount: amount, - hash: elem.hash, - isFrozen: elem.isFrozen, - note: info.note, - isSending: elem.isSending - ); - })); + final info = getUnspentCoinInfo(elem.hash, elem.address, elem.value, elem.vout); + + return UnspentCoinsItem( + address: elem.address, + amount: amount, + hash: elem.hash, + isFrozen: info?.isFrozen ?? false, + note: info?.note ?? '', + isSending: info?.isSending ?? true, + amountRaw: elem.value, + vout: elem.vout); + })); Future saveUnspentCoinInfo(UnspentCoinsItem item) async { try { - final info = _unspentCoinsInfo.values - .firstWhere((element) => element.walletId.contains(wallet.id) && - element.hash.contains(item.hash)); + final info = getUnspentCoinInfo(item.hash, item.address, item.amountRaw, item.vout); + if (info == null) { + final newInfo = UnspentCoinsInfo( + walletId: wallet.id, + hash: item.hash, + address: item.address, + value: item.amountRaw, + vout: item.vout, + isFrozen: item.isFrozen, + isSending: item.isSending, + noteRaw: item.note); + await _unspentCoinsInfo.add(newInfo); + bitcoin!.updateUnspents(wallet); + wallet.updateBalance(); + return; + } info.isFrozen = item.isFrozen; info.isSending = item.isSending; info.note = item.note; await info.save(); bitcoin!.updateUnspents(wallet); + wallet.updateBalance(); } catch (e) { print(e.toString()); } } -} \ No newline at end of file + + UnspentCoinsInfo? getUnspentCoinInfo(String hash, String address, int value, int vout) { + return _unspentCoinsInfo.values.firstWhereOrNull((element) => + element.walletId == wallet.id && + element.hash == hash && + element.address == address && + element.value == value && + element.vout == vout); + } +} diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 1a16e2443..23686e451 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -479,8 +479,8 @@ "xrp_extra_info":"من فضلك لا تنس تحديد علامة الوجهة أثناء إرسال معاملة XRP للتبادل", "exchange_incorrect_current_wallet_for_xmr":"إذا كنت ترغب في استبدال XMR من رصيد Cake Wallet Monero ، فيرجى التبديل إلى محفظة Monero أولاً.", - "confirmed":"مؤكد", - "unconfirmed":"غير مؤكد", + "confirmed":"رصيد مؤكد", + "unconfirmed":"رصيد غير مؤكد", "displayable":"قابل للعرض", "submit_request":"تقديم طلب", @@ -685,6 +685,7 @@ "error_dialog_content": "عفوًا ، لقد حصلنا على بعض الخطأ.\n\nيرجى إرسال تقرير التعطل إلى فريق الدعم لدينا لتحسين التطبيق.", "decimal_places_error": "عدد كبير جدًا من المنازل العشرية", "edit_node": "تحرير العقدة", + "frozen_balance": "الرصيد المجمد", "invoice_details": "تفاصيل الفاتورة", "donation_link_details": "تفاصيل رابط التبرع", "anonpay_description": "توليد ${type}. يمكن للمستلم ${method} بأي عملة مشفرة مدعومة ، وستتلقى أموالاً في هذه", diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 706e37799..66a211ed9 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -479,8 +479,8 @@ "xrp_extra_info" : "Не забравяйте да дадете Destination Tag-а, когато изпращате XRP транзакцията за обмена", "exchange_incorrect_current_wallet_for_xmr" : "Ако искате да обмените XMR от своя Cake Wallet Monero баланс, първо изберете своя Monero портфейл.", - "confirmed" : "Потвърдено", - "unconfirmed" : "Непотвърдено", + "confirmed" : "Потвърден баланс", + "unconfirmed" : "Непотвърден баланс", "displayable" : "Възможност за показване", "submit_request" : "изпращане на заявка", @@ -687,6 +687,7 @@ "error_dialog_content": "Получихме грешка.\n\nМоля, изпратете доклада до нашия отдел поддръжка, за да подобрим приложението.", "decimal_places_error": "Твърде много знаци след десетичната запетая", "edit_node": "Редактиране на възел", + "frozen_balance": "Замразен баланс", "invoice_details": "IДанни за фактура", "donation_link_details": "Подробности за връзката за дарение", "anonpay_description": "Генерирайте ${type}. Получателят може да ${method} с всяка поддържана криптовалута и вие ще получите средства в този портфейл.", diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 66006fd8b..381e7c18e 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -479,8 +479,8 @@ "xrp_extra_info" : "Prosím nezapomeňte zadat Destination Tag, když posíláte XRP transakce ke směně", "exchange_incorrect_current_wallet_for_xmr" : "Pokud chcete směnit XMR z Monero částky v Cake Wallet, prosím přepněte se nejprve do své Monero peněženky.", - "confirmed" : "Potvrzeno", - "unconfirmed" : "Nepotvrzeno", + "confirmed" : "Potvrzený zůstatek", + "unconfirmed" : "Nepotvrzený zůstatek", "displayable" : "Zobrazitelné", "submit_request" : "odeslat požadavek", @@ -687,6 +687,7 @@ "error_dialog_content": "Nastala chyba.\n\nProsím odešlete zprávu o chybě naší podpoře, aby mohli zajistit opravu.", "decimal_places_error": "Příliš mnoho desetinných míst", "edit_node": "Upravit uzel", + "frozen_balance": "Zmrazená bilance", "invoice_details": "detaily faktury", "donation_link_details": "Podrobnosti odkazu na darování", "anonpay_description": "Vygenerujte ${type}. Příjemce může ${method} s jakoukoli podporovanou kryptoměnou a vy obdržíte prostředky v této peněžence.", diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index f79fb8b4a..9fd3432bd 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -479,8 +479,8 @@ "xrp_extra_info" : "Bitte vergessen Sie nicht, das Ziel-Tag anzugeben, während Sie die XRP-Transaktion für den Austausch senden", "exchange_incorrect_current_wallet_for_xmr" : "Wenn Sie XMR von Ihrem Cake Wallet Monero-Guthaben umtauschen möchten, wechseln Sie bitte zuerst zu Ihrer Monero-Wallet.", - "confirmed" : "Bestätigt", - "unconfirmed" : "Unbestätigt", + "confirmed" : "Bestätigter Saldo", + "unconfirmed" : "Unbestätigter Saldo", "displayable" : "Anzeigebar", "submit_request" : "Eine Anfrage stellen", @@ -687,6 +687,7 @@ "error_dialog_content": "Hoppla, wir haben einen Fehler.\n\nBitte senden Sie den Absturzbericht an unser Support-Team, um die Anwendung zu verbessern.", "decimal_places_error": "Zu viele Nachkommastellen", "edit_node": "Knoten bearbeiten", + "frozen_balance": "Gefrorenes Gleichgewicht", "invoice_details": "Rechnungs-Details", "donation_link_details": "Details zum Spendenlink", "anonpay_description": "Generieren Sie ${type}. Der Empfänger kann ${method} mit jeder unterstützten Kryptowährung verwenden, und Sie erhalten Geld in dieser Brieftasche.", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 2fe1875c5..7f03b96e9 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -479,8 +479,8 @@ "xrp_extra_info" : "Please don’t forget to specify the Destination Tag while sending the XRP transaction for the exchange", "exchange_incorrect_current_wallet_for_xmr" : "If you want to exchange XMR from your Cake Wallet Monero balance, please switch to your Monero wallet first.", - "confirmed" : "Confirmed", - "unconfirmed" : "Unconfirmed", + "confirmed" : "Confirmed Balance", + "unconfirmed" : "Unconfirmed Balance", "displayable" : "Displayable", "submit_request" : "submit a request", @@ -697,6 +697,7 @@ "onion_link": "Onion link", "decimal_places_error": "Too many decimal places", "edit_node": "Edit Node", + "frozen_balance": "Frozen Balance", "settings": "Settings", "sell_monero_com_alert_content": "Selling Monero is not supported yet", "error_text_input_below_minimum_limit" : "Amount is less than the minimum", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 11153e3ff..4b649e12a 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -479,8 +479,8 @@ "xrp_extra_info" : "No olvide especificar la etiqueta de destino al enviar la transacción XRP para el intercambio", "exchange_incorrect_current_wallet_for_xmr" : "Si desea intercambiar XMR de su saldo de Cake Wallet Monero, primero cambie a su billetera Monero.", - "confirmed" : "Confirmada", - "unconfirmed" : "Inconfirmado", + "confirmed" : "Saldo confirmado", + "unconfirmed" : "Saldo no confirmado", "displayable" : "Visualizable", "submit_request" : "presentar una solicitud", @@ -686,7 +686,8 @@ "do_not_send": "no enviar", "error_dialog_content": "Vaya, tenemos un error.\n\nEnvíe el informe de bloqueo a nuestro equipo de soporte para mejorar la aplicación.", "decimal_places_error": "Demasiados lugares decimales", - "edit_node": "Edit Node", + "edit_node": "Editar nodo", + "frozen_balance": "Balance congelado", "invoice_details": "Detalles de la factura", "donation_link_details": "Detalles del enlace de donación", "anonpay_description": "Genera ${type}. El destinatario puede ${method} con cualquier criptomoneda admitida, y recibirá fondos en esta billetera.", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 47abb1f2f..5250ed88d 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -479,8 +479,8 @@ "xrp_extra_info" : "Merci de ne pas oublier de spécifier le tag de destination lors de l'envoi de la transaction XRP de l'échange", "exchange_incorrect_current_wallet_for_xmr" : "Si vous souhaitez échanger des XMR du solde Monero de votre Cake Wallet, merci de sélectionner votre portefeuille (wallet) Monero au préalable.", - "confirmed" : "Confirmé", - "unconfirmed" : "Non confirmé", + "confirmed" : "Solde confirmé", + "unconfirmed" : "Solde non confirmé", "displayable" : "Visible", "submit_request" : "soumettre une requête", @@ -687,6 +687,7 @@ "error_dialog_content": "Oups, nous avons rencontré une erreur.\n\nMerci d'envoyer le rapport d'erreur à notre équipe d'assistance afin de nous permettre d'améliorer l'application.", "decimal_places_error": "Trop de décimales", "edit_node": "Modifier le nœud", + "frozen_balance": "Équilibre gelé", "invoice_details": "Détails de la facture", "donation_link_details": "Détails du lien de don", "anonpay_description": "Générez ${type}. Le destinataire peut ${method} avec n'importe quelle crypto-monnaie prise en charge, et vous recevrez des fonds dans ce portefeuille (wallet).", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index d4512c370..6a9a97880 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -479,8 +479,8 @@ "xrp_extra_info" : "एक्सचेंज के लिए एक्सआरपी लेनदेन भेजते समय कृपया गंतव्य टैग निर्दिष्ट करना न भूलें", "exchange_incorrect_current_wallet_for_xmr" : "यदि आप अपने केक वॉलेट मोनेरो बैलेंस से एक्सएमआर का आदान-प्रदान करना चाहते हैं, तो कृपया अपने मोनेरो वॉलेट में जाएं।", - "confirmed" : "की पुष्टि की", - "unconfirmed" : "अपुष्ट", + "confirmed" : "पुष्टि की गई शेष राशिी", + "unconfirmed" : "अपुष्ट शेष राशि", "displayable" : "प्रदर्शन योग्य", "submit_request" : "एक अनुरोध सबमिट करें", @@ -687,6 +687,7 @@ "error_dialog_content": "ओह, हमसे कुछ गड़बड़ी हुई है.\n\nएप्लिकेशन को बेहतर बनाने के लिए कृपया क्रैश रिपोर्ट हमारी सहायता टीम को भेजें।", "decimal_places_error": "बहुत अधिक दशमलव स्थान", "edit_node": "नोड संपादित करें", + "frozen_balance": "जमे हुए संतुलन", "invoice_details": "चालान विवरण", "donation_link_details": "दान लिंक विवरण", "anonpay_description": "${type} उत्पन्न करें। प्राप्तकर्ता किसी भी समर्थित क्रिप्टोकरेंसी के साथ ${method} कर सकता है, और आपको इस वॉलेट में धन प्राप्त होगा।", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 9d5e85fc9..10974efa2 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -479,8 +479,8 @@ "xrp_extra_info" : "Molimo ne zaboravite navesti odredišnu oznaku prilikom slanja XRP transakcije na razmjenu", "exchange_incorrect_current_wallet_for_xmr" : "Ako želite razmijeniti XMR s vlastitog Monero računa na Cake Wallet novčaniku, molimo prvo se prebacite na svoj Monero novčanik.", - "confirmed" : "Potvrđeno", - "unconfirmed" : "Nepotvrđeno", + "confirmed" : "Potvrđeno stanje", + "unconfirmed" : "Nepotvrđeno stanje", "displayable" : "Dostupno za prikaz", "submit_request" : "podnesi zahtjev", @@ -687,6 +687,7 @@ "error_dialog_content": "Ups, imamo grešku.\n\nPošaljite izvješće o padu našem timu za podršku kako bismo poboljšali aplikaciju.", "decimal_places_error": "Previše decimalnih mjesta", "edit_node": "Uredi čvor", + "frozen_balance": "Zamrznuti saldo", "invoice_details": "Podaci o fakturi", "donation_link_details": "Detalji veza za donacije", "anonpay_description": "Generiraj ${type}. Primatelj može ${method} s bilo kojom podržanom kriptovalutom, a vi ćete primiti sredstva u ovaj novčanik.", diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index 4ade62ce8..b4dd13356 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -466,8 +466,8 @@ "xrp_extra_info" : "Jangan lupa untuk menentukan Tag Tujuan saat mengirim transaksi XRP untuk pertukaran", "exchange_incorrect_current_wallet_for_xmr" : "Jika Anda ingin menukar XMR dari saldo Monero Cake Wallet Anda, silakan beralih ke dompet Monero Anda terlebih dahulu.", - "confirmed" : "Dikonfirmasi", - "unconfirmed" : "Tidak dikonfirmasi", + "confirmed" : "Saldo Terkonfirmasi", + "unconfirmed" : "Saldo Belum Dikonfirmasi", "displayable" : "Dapat ditampilkan", "submit_request" : "kirim permintaan", @@ -669,6 +669,7 @@ "contact_list_wallets": "Dompet Saya", "decimal_places_error": "Terlalu banyak tempat desimal", "edit_node": "Sunting Node", + "frozen_balance": "Saldo Beku", "invoice_details": "Detail faktur", "donation_link_details": "Detail tautan donasi", "anonpay_description": "Hasilkan ${type}. Penerima dapat ${method} dengan cryptocurrency apa pun yang didukung, dan Anda akan menerima dana di dompet ini.", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 8cef4e216..b8881e2f6 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -479,8 +479,8 @@ "xrp_extra_info" : "Gentilmente ricorda di indicare il Tag di Destinazione quando invii una transazione XRP per lo scambio", "exchange_incorrect_current_wallet_for_xmr" : "Se vuoi scambiare XMR dal tuo saldo Cake Wallet Monero, gentilmente passa al tuo portafoglio Monero.", - "confirmed" : "Confermato", - "unconfirmed" : "Non confermato", + "confirmed" : "Saldo confermato", + "unconfirmed" : "Saldo non confermato", "displayable" : "Visualizzabile", "submit_request" : "invia una richiesta", @@ -687,6 +687,7 @@ "error_dialog_content": "Spiacenti, abbiamo riscontrato un errore.\n\nSi prega di inviare il rapporto sull'arresto anomalo al nostro team di supporto per migliorare l'applicazione.", "decimal_places_error": "Troppe cifre decimali", "edit_node": "Modifica nodo", + "frozen_balance": "Equilibrio congelato", "invoice_details": "Dettagli della fattura", "donation_link_details": "Dettagli del collegamento alla donazione", "anonpay_description": "Genera ${type}. Il destinatario può ${method} con qualsiasi criptovaluta supportata e riceverai fondi in questo portafoglio.", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index ac2eb8642..97baf1281 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -479,8 +479,8 @@ "xrp_extra_info" : "取引所のXRPトランザクションを送信するときに、宛先タグを指定することを忘れないでください", "exchange_incorrect_current_wallet_for_xmr" : "Cake Wallet Moneroの残高からXMRを交換する場合は、最初にMoneroウォレットに切り替えてください。", - "confirmed" : "確認済み", - "unconfirmed" : "未確認", + "confirmed" : "確認済み残高", + "unconfirmed" : "残高未確認", "displayable" : "表示可能", "submit_request" : "リクエストを送信する", @@ -687,6 +687,7 @@ "error_dialog_content": "エラーが発生しました。\n\nアプリケーションを改善するために、クラッシュ レポートをサポート チームに送信してください。", "decimal_places_error": "小数点以下の桁数が多すぎる", "edit_node": "ノードを編集", + "frozen_balance": "冷凍残高", "invoice_details": "請求の詳細", "donation_link_details": "寄付リンクの詳細", "anonpay_description": "${type} を生成します。受取人はサポートされている任意の暗号通貨で ${method} でき、あなたはこのウォレットで資金を受け取ります。", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index cf829bb46..50994a752 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -479,8 +479,8 @@ "xrp_extra_info" : "교환을 위해 XRP 트랜잭션을 보내는 동안 대상 태그를 지정하는 것을 잊지 마십시오", "exchange_incorrect_current_wallet_for_xmr" : "Cake Wallet Monero 잔액에서 XMR을 교환하려면 먼저 Monero 지갑으로 전환하십시오.", - "confirmed" : "확인", - "unconfirmed" : "미확인", + "confirmed" : "확인된 잔액", + "unconfirmed" : "확인되지 않은 잔액", "displayable" : "표시 가능", "submit_request" : "요청을 제출", @@ -687,6 +687,7 @@ "error_dialog_content": "죄송합니다. 오류가 발생했습니다.\n\n응용 프로그램을 개선하려면 지원 팀에 충돌 보고서를 보내주십시오.", "decimal_places_error": "소수점 이하 자릿수가 너무 많습니다.", "edit_node": "노드 편집", + "frozen_balance": "얼어붙은 균형", "invoice_details": "인보이스 세부정보", "donation_link_details": "기부 링크 세부정보", "anonpay_description": "${type} 생성. 수신자는 지원되는 모든 암호화폐로 ${method}할 수 있으며 이 지갑에서 자금을 받게 됩니다.", diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index ac2b056ee..e93b8a0b9 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -479,8 +479,8 @@ "xrp_extra_info" : "လဲလှယ်မှုအတွက် XRP ငွေလွှဲပို့နေစဉ် Destination Tag ကို သတ်မှတ်ရန် မမေ့ပါနှင့်", "exchange_incorrect_current_wallet_for_xmr" : "သင်၏ Cake Wallet Monero လက်ကျန်မှ XMR ကိုလဲလှယ်လိုပါက၊ သင်၏ Monero ပိုက်ဆံအိတ်သို့ ဦးစွာပြောင်းပါ။", - "confirmed" : "အတည်ပြုခဲ့သည်။", - "unconfirmed" : "အတည်မပြုနိုင်ပါ။", + "confirmed" : "အတည်ပြုထားသော လက်ကျန်ငွေ", + "unconfirmed" : "အတည်မပြုနိုင်သော လက်ကျန်ငွေ", "displayable" : "ပြသနိုင်သည်။", "submit_request" : "တောင်းဆိုချက်တစ်ခုတင်ပြပါ။", @@ -687,6 +687,7 @@ "error_dialog_content": "အိုး၊ ကျွန်ုပ်တို့တွင် အမှားအယွင်းအချို့ရှိသည်။\n\nအပလီကေးရှင်းကို ပိုမိုကောင်းမွန်စေရန်အတွက် ပျက်စီးမှုအစီရင်ခံစာကို ကျွန်ုပ်တို့၏ပံ့ပိုးကူညီရေးအဖွဲ့ထံ ပေးပို့ပါ။", "decimal_places_error": "ဒဿမနေရာများ များလွန်းသည်။", "edit_node": "Node ကို တည်းဖြတ်ပါ။", + "frozen_balance": "ေးခဲမှူ", "invoice_details": "ပြေစာအသေးစိတ်", "donation_link_details": "လှူဒါန်းရန်လင့်ခ်အသေးစိတ်", "anonpay_description": "${type} ကို ဖန်တီးပါ။ လက်ခံသူက ${method} ကို ပံ့ပိုးပေးထားသည့် cryptocurrency တစ်ခုခုဖြင့် လုပ်ဆောင်နိုင်ပြီး၊ သင်သည် ဤပိုက်ဆံအိတ်တွင် ရံပုံငွေများ ရရှိမည်ဖြစ်သည်။", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 371df6f62..1561bb3d4 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -479,8 +479,8 @@ "xrp_extra_info" : "Vergeet niet om de Destination Tag op te geven tijdens het verzenden van de XRP-transactie voor de uitwisseling", "exchange_incorrect_current_wallet_for_xmr" : "Als u XMR wilt omwisselen van uw Cake Wallet Monero-saldo, moet u eerst overschakelen naar uw Monero-portemonnee.", - "confirmed" : "Bevestigd", - "unconfirmed" : "Niet bevestigd", + "confirmed" : "Bevestigd saldo", + "unconfirmed" : "Onbevestigd saldo", "displayable" : "Weer te geven", "submit_request" : "een verzoek indienen", @@ -687,6 +687,7 @@ "error_dialog_content": "Oeps, er is een fout opgetreden.\n\nStuur het crashrapport naar ons ondersteuningsteam om de applicatie te verbeteren.", "decimal_places_error": "Te veel decimalen", "edit_node": "Knooppunt bewerken", + "frozen_balance": "Bevroren saldo", "invoice_details": "Factuurgegevens", "donation_link_details": "Details van de donatielink", "anonpay_description": "Genereer ${type}. De ontvanger kan ${method} gebruiken met elke ondersteunde cryptocurrency en u ontvangt geld in deze portemonnee", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index b5e0a0bfd..ea20d4da7 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -479,8 +479,8 @@ "xrp_extra_info" : "Nie zapomnij podać tagu docelowego podczas wysyłania transakcji XRP do wymiany", "exchange_incorrect_current_wallet_for_xmr" : "Jeśli chcesz wymienić XMR z salda Cake Wallet Monero, najpierw przełącz się na portfel Monero.", - "confirmed" : "Potwierdzony", - "unconfirmed" : "Niepotwierdzony", + "confirmed" : "Potwierdzone saldo", + "unconfirmed" : "Niepotwierdzone saldo", "displayable" : "Wyświetlane", "submit_request" : "Złóż wniosek", @@ -687,6 +687,7 @@ "error_dialog_content": "Ups, wystąpił błąd.\n\nPrześlij raport o awarii do naszego zespołu wsparcia, aby ulepszyć aplikację.", "decimal_places_error": "Za dużo miejsc dziesiętnych", "edit_node": "Edytuj węzeł", + "frozen_balance": "Zamrożona równowaga", "invoice_details": "Dane do faktury", "donation_link_details": "Szczegóły linku darowizny", "anonpay_description": "Wygeneruj ${type}. Odbiorca może ${method} z dowolną obsługiwaną kryptowalutą, a Ty otrzymasz środki w tym portfelu.", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 633d2e833..9f088fcef 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -479,8 +479,8 @@ "xrp_extra_info" : "Não se esqueça de especificar a etiqueta de destino ao enviar a transação XRP para a troca", "exchange_incorrect_current_wallet_for_xmr" : "Se você deseja trocar o XMR de seu saldo da Carteira Monero Cake, troque primeiro para sua carteira Monero.", - "confirmed" : "Confirmada", - "unconfirmed" : "Não confirmado", + "confirmed" : "Saldo Confirmado", + "unconfirmed" : "Saldo não confirmado", "displayable" : "Exibível", "submit_request" : "enviar um pedido", @@ -686,6 +686,7 @@ "error_dialog_content": "Ops, houve algum erro.\n\nPor favor, envie o relatório de falha para nossa equipe de suporte para melhorar o aplicativo.", "decimal_places_error": "Muitas casas decimais", "edit_node": "Editar nó", + "frozen_balance": "Saldo Congelado", "invoice_details": "Detalhes da fatura", "donation_link_details": "Detalhes do link de doação", "anonpay_description": "Gere ${type}. O destinatário pode ${method} com qualquer criptomoeda suportada e você receberá fundos nesta carteira.", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index c9230219c..53dc31b21 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -479,8 +479,8 @@ "xrp_extra_info" : "Не забудьте указать целевой тег при отправке транзакции XRP для обмена", "exchange_incorrect_current_wallet_for_xmr" : "Если вы хотите обменять XMR со своего баланса Monero в Cake Wallet, сначала переключитесь на свой кошелек Monero.", - "confirmed" : "Подтверждено", - "unconfirmed" : "Неподтвержденный", + "confirmed" : "Подтвержденный баланс", + "unconfirmed" : "Неподтвержденный баланс", "displayable" : "Отображаемый", "submit_request" : "отправить запрос", @@ -687,6 +687,7 @@ "error_dialog_content": "Ой, у нас какая-то ошибка.\n\nПожалуйста, отправьте отчет о сбое в нашу службу поддержки, чтобы сделать приложение лучше.", "decimal_places_error": "Слишком много десятичных знаков", "edit_node": "Редактировать узел", + "frozen_balance": "Замороженный баланс", "invoice_details": "Детали счета", "donation_link_details": "Информация о ссылке для пожертвований", "anonpay_description": "Создайте ${type}. Получатель может использовать ${method} с любой поддерживаемой криптовалютой, и вы получите средства на этот кошелек.", diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 1233ecba4..ea7b2afde 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -477,8 +477,8 @@ "xrp_extra_info": "โปรดอย่าลืมระบุ Destination Tag ในขณะที่ส่งธุรกรรม XRP สำหรับการแลกเปลี่ยน", "exchange_incorrect_current_wallet_for_xmr" : "หากคุณต้องการแลกเปลี่ยน XMR จากยอดคงเหลือ Monero ใน Cake Wallet ของคุณ กรุณาเปลี่ยนเป็นกระเป๋า Monero ก่อน", - "confirmed" : "ได้รับการยืนยัน", - "unconfirmed" : "ยังไม่ได้รับการยืนยัน", + "confirmed" : "ยอดคงเหลือที่ยืนยันแล้ว", + "unconfirmed" : "ยอดคงเหลือที่ไม่ได้รับการยืนยัน", "displayable" : "สามารถแสดงได้", "submit_request" : "ส่งคำขอ", @@ -685,6 +685,7 @@ "error_dialog_content": "อ๊ะ เราพบข้อผิดพลาดบางอย่าง\n\nโปรดส่งรายงานข้อขัดข้องไปยังทีมสนับสนุนของเราเพื่อปรับปรุงแอปพลิเคชันให้ดียิ่งขึ้น", "decimal_places_error": "ทศนิยมมากเกินไป", "edit_node": "แก้ไขโหนด", + "frozen_balance": "ยอดคงเหลือแช่แข็ง", "invoice_details": "รายละเอียดใบแจ้งหนี้", "donation_link_details": "รายละเอียดลิงค์บริจาค", "anonpay_description": "สร้าง ${type} ผู้รับสามารถ ${method} ด้วยสกุลเงินดิจิทัลที่รองรับ และคุณจะได้รับเงินในกระเป๋าสตางค์นี้", diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 01ba57fbe..7cb72e57a 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -479,8 +479,8 @@ "xrp_extra_info" : "Lütfen takas için XRP işlemi gönderirken Hedef Etiketi (Destination Tag) belirtmeyi unutmayın", "exchange_incorrect_current_wallet_for_xmr" : "Cake Wallet'daki Monero bakiyenizi kullanarak takas yapmak istiyorsan, lütfen önce Monero cüzdanına geç.", - "confirmed" : "Onaylı", - "unconfirmed" : "Onaylanmamış", + "confirmed" : "Onaylanmış Bakiye", + "unconfirmed" : "Onaylanmamış Bakiye", "displayable" : "Gösterilebilir", "submit_request" : "talep gönder", @@ -687,6 +687,7 @@ "error_dialog_content": "Hay aksi, bir hatamız var.\n\nUygulamayı daha iyi hale getirmek için lütfen kilitlenme raporunu destek ekibimize gönderin.", "decimal_places_error": "Çok fazla ondalık basamak", "edit_node": "Düğümü Düzenle", + "frozen_balance": "Dondurulmuş Bakiye", "invoice_details": "fatura detayları", "donation_link_details": "Bağış bağlantısı ayrıntıları", "anonpay_description": "${type} oluşturun. Alıcı, desteklenen herhangi bir kripto para birimi ile ${method} yapabilir ve bu cüzdanda para alırsınız.", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 9d8010eca..ac3d32dfd 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -478,8 +478,8 @@ "xrp_extra_info" : "Будь ласка, не забудьте вказати тег призначення під час надсилання XRP-транзакції для обміну", "exchange_incorrect_current_wallet_for_xmr" : "Якщо ви хочете обміняти XMR із вашого балансу Cake Wallet Monero, спочатку перейдіть на свій гаманець Monero.", - "confirmed" : "Підтверджено", - "unconfirmed" : "Непідтверджений", + "confirmed" : "Підтверджений баланс", + "unconfirmed" : "Непідтверджений баланс", "displayable" : "Відображуваний", "submit_request" : "надіслати запит", @@ -686,6 +686,7 @@ "error_dialog_content": "На жаль, ми отримали помилку.\n\nБудь ласка, надішліть звіт про збій нашій команді підтримки, щоб покращити додаток.", "decimal_places_error": "Забагато знаків після коми", "edit_node": "Редагувати вузол", + "frozen_balance": "Заморожений баланс", "invoice_details": "Реквізити рахунку-фактури", "donation_link_details": "Деталі посилання для пожертв", "anonpay_description": "Згенерувати ${type}. Одержувач може ${method} будь-якою підтримуваною криптовалютою, і ви отримаєте кошти на цей гаманець.", diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index 15ca6833b..2e88a0bd6 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -481,8 +481,8 @@ "xrp_extra_info" : "ایکسچینج کے لیے XRP ٹرانزیکشن بھیجتے وقت ڈیسٹینیشن ٹیگ بتانا نہ بھولیں۔", "exchange_incorrect_current_wallet_for_xmr" : "اگر آپ اپنے Cake والیٹ Monero بیلنس سے XMR کا تبادلہ کرنا چاہتے ہیں، تو براہ کرم پہلے اپنے Monero والیٹ پر جائیں۔", - "confirmed" : "تصدیق شدہ", - "unconfirmed" : "غیر تصدیق شدہ", + "confirmed" : "تصدیق شدہ بیلنس", + "unconfirmed" : "غیر تصدیق شدہ بیلنس", "displayable" : "قابل نمائش", "submit_request" : "درخواست بھیج دو", @@ -688,6 +688,7 @@ "error_dialog_content" : "افوہ، ہمیں کچھ خرابی ملی۔\n\nایپلی کیشن کو بہتر بنانے کے لیے براہ کرم کریش رپورٹ ہماری سپورٹ ٹیم کو بھیجیں۔", "decimal_places_error": "بہت زیادہ اعشاریہ جگہیں۔", "edit_node": "نوڈ میں ترمیم کریں۔", + "frozen_balance": "منجمد بیلنس", "invoice_details": "رسید کی تفصیلات", "donation_link_details": "عطیہ کے لنک کی تفصیلات", "anonpay_description": "${type} بنائیں۔ وصول کنندہ کسی بھی تعاون یافتہ کرپٹو کرنسی کے ساتھ ${method} کرسکتا ہے، اور آپ کو اس بٹوے میں فنڈز موصول ہوں گے۔", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index d906ebf5b..ea1804b58 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -478,8 +478,8 @@ "xrp_extra_info" : "发送用于交换的XRP交易时,请不要忘记指定目标Tag", "exchange_incorrect_current_wallet_for_xmr" : "如果要从Cake Wallet Monero余额中兑换XMR,请先切换到Monero钱包。", - "confirmed" : "已确认", - "unconfirmed" : "未经证实", + "confirmed" : "确认余额", + "unconfirmed" : "未确认余额", "displayable" : "可显示", "submit_request" : "提交请求", @@ -686,6 +686,7 @@ "error_dialog_content": "糟糕,我们遇到了一些错误。\n\n请将崩溃报告发送给我们的支持团队,以改进应用程序。", "decimal_places_error": "小数位太多", "edit_node": "编辑节点", + "frozen_balance": "冻结余额", "invoice_details": "发票明细", "donation_link_details": "捐赠链接详情", "anonpay_description": "生成 ${type}。收款人可以使用任何受支持的加密货币 ${method},您将在此钱包中收到资金。", From 8f16af4748446c4f0ae83b09f1266740ccd64b3e Mon Sep 17 00:00:00 2001 From: Adegoke David <64401859+Blazebrain@users.noreply.github.com> Date: Thu, 20 Apr 2023 18:14:11 +0100 Subject: [PATCH 05/39] CW-344-Exchanging-TO-the-current-wallet-currency-should-allow-changing-the-address (#880) * CW-344-Exchanging-TO-the-current-wallet-currency-should-allow-changing-the-address * Delete widget_test.dart * fix: Revert unneeded changes made * CW-344-Exchanging-TO-the-current-wallet-currency-should-allow-changing-the-address * CW-344-Exchanging-TO-the-current-wallet-currency-should-allow-changing-the-address * fix: Requested changes * Update * CW-344-Exchanging-TO-the-current-wallet-currency-should-allow-changing-the-address * fix: Filter address in address book * Update local branch * CW-344-Exchanging-TO-the-current-wallet-currency-should-allow-changing-the-address * CW-344-Exchanging-TO-the-current-wallet-currency-should-allow-changing-the-address --- lib/src/screens/contact/contact_list_page.dart | 1 + lib/src/screens/exchange/widgets/exchange_card.dart | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/src/screens/contact/contact_list_page.dart b/lib/src/screens/contact/contact_list_page.dart index f8ccf5807..96887ff5f 100644 --- a/lib/src/screens/contact/contact_list_page.dart +++ b/lib/src/screens/contact/contact_list_page.dart @@ -120,6 +120,7 @@ class ContactListPage extends BasePage { if (isCopied) { await Clipboard.setData(ClipboardData(text: contact.address)); await showBar(context, S.of(context).copied_to_clipboard); + } }, child: Container( diff --git a/lib/src/screens/exchange/widgets/exchange_card.dart b/lib/src/screens/exchange/widgets/exchange_card.dart index 0ba112215..ae92fa0c1 100644 --- a/lib/src/screens/exchange/widgets/exchange_card.dart +++ b/lib/src/screens/exchange/widgets/exchange_card.dart @@ -406,7 +406,6 @@ class ExchangeCardState extends State { order: NumericFocusOrder(3), child: BaseTextFormField( controller: addressController, - readOnly: true, borderColor: Colors.transparent, suffixIcon: SizedBox(width: _isMoneroWallet ? 80 : 36), @@ -433,7 +432,10 @@ class ExchangeCardState extends State { onTap: () async { final contact = await Navigator.of(context) - .pushNamed(Routes.pickerAddressBook); + .pushNamed( + Routes.pickerAddressBook, + arguments: widget.initialCurrency, + ); if (contact is ContactBase && contact.address != null) { From ab20312e61a0fca0335cbba6b9f82bc2bf8d0915 Mon Sep 17 00:00:00 2001 From: Justin Ehrenhofer Date: Thu, 20 Apr 2023 13:55:44 -0500 Subject: [PATCH 06/39] Add btcln; add 2 assets to SideShift (#890) * Add btcln; add 2 assets to SideShift * fix regex line * change ordering --- cw_core/lib/crypto_currency.dart | 3 +++ lib/core/address_validator.dart | 4 ++++ lib/exchange/sideshift/sideshift_exchange_provider.dart | 4 ++-- lib/exchange/trocador/trocador_exchange_provider.dart | 2 ++ 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/cw_core/lib/crypto_currency.dart b/cw_core/lib/crypto_currency.dart index 3d076f6e9..da9b7f9e9 100644 --- a/cw_core/lib/crypto_currency.dart +++ b/cw_core/lib/crypto_currency.dart @@ -66,6 +66,7 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen CryptoCurrency.scrt, CryptoCurrency.uni, CryptoCurrency.stx, + CryptoCurrency.btcln, ]; static const havenCurrencies = [ @@ -150,6 +151,8 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen static const scrt = CryptoCurrency(title: 'SCRT', fullName: 'Secret Network', raw: 59, name: 'scrt', iconPath: 'assets/images/scrt_icon.png'); static const uni = CryptoCurrency(title: 'UNI', tag: 'ETH', fullName: 'Uniswap', raw: 60, name: 'uni', iconPath: 'assets/images/uni_icon.png'); static const stx = CryptoCurrency(title: 'STX', fullName: 'Stacks', raw: 61, name: 'stx', iconPath: 'assets/images/stx_icon.png'); + static const btcln = CryptoCurrency(title: 'BTC', tag: 'LN', fullName: 'Bitcoin Lightning Network', raw: 62, name: 'btcln', iconPath: 'assets/images/btc.png'); + static final Map _rawCurrencyMap = [...all, ...havenCurrencies].fold>({}, (acc, item) { diff --git a/lib/core/address_validator.dart b/lib/core/address_validator.dart index 22ad6a9d8..0bed0611c 100644 --- a/lib/core/address_validator.dart +++ b/lib/core/address_validator.dart @@ -85,6 +85,8 @@ class AddressValidator extends TextValidator { return 'R[0-9a-zA-Z]{33}'; case CryptoCurrency.pivx: return 'D([1-9a-km-zA-HJ-NP-Z]){33}'; + case CryptoCurrency.btcln: + return '^(lnbc|LNBC)([0-9]{1,}[a-zA-Z0-9]+)'; default: return '[0-9a-zA-Z]'; } @@ -194,6 +196,8 @@ class AddressValidator extends TextValidator { return [45]; case CryptoCurrency.near: return [64]; + case CryptoCurrency.btcln: + return null; default: return []; } diff --git a/lib/exchange/sideshift/sideshift_exchange_provider.dart b/lib/exchange/sideshift/sideshift_exchange_provider.dart index 2fc593988..a5b0c990f 100644 --- a/lib/exchange/sideshift/sideshift_exchange_provider.dart +++ b/lib/exchange/sideshift/sideshift_exchange_provider.dart @@ -29,9 +29,7 @@ class SideShiftExchangeProvider extends ExchangeProvider { CryptoCurrency.dcr, CryptoCurrency.kmd, CryptoCurrency.mkr, - CryptoCurrency.near, CryptoCurrency.oxt, - CryptoCurrency.paxg, CryptoCurrency.pivx, CryptoCurrency.rune, CryptoCurrency.rvn, @@ -300,6 +298,8 @@ class SideShiftExchangeProvider extends ExchangeProvider { return 'usdcsol'; case CryptoCurrency.maticpoly: return 'polygon'; + case CryptoCurrency.btcln: + return 'ln'; default: return currency.title.toLowerCase(); } diff --git a/lib/exchange/trocador/trocador_exchange_provider.dart b/lib/exchange/trocador/trocador_exchange_provider.dart index 39b4638f4..fb6109bdf 100644 --- a/lib/exchange/trocador/trocador_exchange_provider.dart +++ b/lib/exchange/trocador/trocador_exchange_provider.dart @@ -285,6 +285,8 @@ class TrocadorExchangeProvider extends ExchangeProvider { return 'ERC20'; case 'TRX': return 'TRC20'; + case 'LN': + return 'Lightning'; default: return tag.toLowerCase(); } From 3fc927f742c1217cbfdffcb6306fedcf173050c7 Mon Sep 17 00:00:00 2001 From: Godwin Asuquo <41484542+godilite@users.noreply.github.com> Date: Thu, 20 Apr 2023 23:20:37 +0300 Subject: [PATCH 07/39] CW-289-Fix bitcoin pending send receive transactions not showing until confirmed (#878) * Fix error in rendering pending transactions in different device * Show pending status * Fix broken subscription stream --- cw_bitcoin/lib/electrum.dart | 10 ++++++---- cw_bitcoin/lib/electrum_transaction_info.dart | 2 +- cw_bitcoin/lib/electrum_wallet.dart | 4 ++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/cw_bitcoin/lib/electrum.dart b/cw_bitcoin/lib/electrum.dart index d50f280f6..81f2da161 100644 --- a/cw_bitcoin/lib/electrum.dart +++ b/cw_bitcoin/lib/electrum.dart @@ -410,7 +410,7 @@ class ElectrumClient { switch (method) { case 'blockchain.scripthash.subscribe': final params = request['params'] as List; - final scripthash = params.first as String; + final scripthash = params.first as String?; final id = 'blockchain.scripthash.subscribe:$scripthash'; _tasks[id]?.subject?.add(params.last); @@ -430,15 +430,17 @@ class ElectrumClient { void _handleResponse(Map response) { final method = response['method']; - final id = response['id'] as String; + final id = response['id'] as String?; final result = response['result']; if (method is String) { _methodHandler(method: method, request: response); return; } - - _finish(id, result); + + if (id != null){ + _finish(id, result); + } } } diff --git a/cw_bitcoin/lib/electrum_transaction_info.dart b/cw_bitcoin/lib/electrum_transaction_info.dart index 8d6ef0fea..b034c06b1 100644 --- a/cw_bitcoin/lib/electrum_transaction_info.dart +++ b/cw_bitcoin/lib/electrum_transaction_info.dart @@ -136,7 +136,7 @@ class ElectrumTransactionInfo extends TransactionInfo { return ElectrumTransactionInfo(type, id: bundle.originalTransaction.getId(), height: height, - isPending: false, + isPending: bundle.confirmations == 0, fee: fee, direction: direction, amount: amount, diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 4984ad28e..eac05378f 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -537,8 +537,8 @@ abstract class ElectrumWalletBase extends WalletBase[]; - final time = verboseTransaction['time'] as int; - final confirmations = verboseTransaction['confirmations'] as int ?? 0; + final time = verboseTransaction['time'] as int?; + final confirmations = verboseTransaction['confirmations'] as int? ?? 0; for (final vin in original.ins) { final id = HEX.encode(vin.hash!.reversed.toList()); From 8ffac75e8c939c9dffc54c5b4744b0571a146920 Mon Sep 17 00:00:00 2001 From: Serhii Date: Fri, 21 Apr 2023 18:21:31 +0300 Subject: [PATCH 08/39] CW-59-New-update-highlight-popup (#863) * add update pop up * add release notes for monero com * PR comments fixes * Pr coments fixes * minor fixes * update from main * [skip ci] remove unrelated mac os files * add check isNewInstall * update pop-up UI * fix size * Add update popup to desktop dashboard page [skip ci] --------- Co-authored-by: OmarHatem --- assets/text/Monerocom_Release_Notes.txt | 13 ++ assets/text/Release_Notes.txt | 13 ++ lib/di.dart | 3 +- lib/entities/default_settings_migration.dart | 7 + lib/entities/preferences_key.dart | 4 +- lib/main.dart | 3 +- lib/src/screens/dashboard/dashboard_page.dart | 29 +++- .../dashboard/desktop_dashboard_page.dart | 23 +++ .../dashboard/widgets/filter_widget.dart | 3 +- .../monero_account_list_page.dart | 5 +- .../release_notes/release_notes_screen.dart | 141 ++++++++++++++++++ .../widgets/seed_language_picker.dart | 12 +- lib/src/widgets/alert_close_button.dart | 28 ++-- lib/utils/version_comparator.dart | 13 ++ .../dashboard/dashboard_view_model.dart | 3 +- 15 files changed, 263 insertions(+), 37 deletions(-) create mode 100644 assets/text/Monerocom_Release_Notes.txt create mode 100644 assets/text/Release_Notes.txt create mode 100644 lib/src/screens/release_notes/release_notes_screen.dart create mode 100644 lib/utils/version_comparator.dart diff --git a/assets/text/Monerocom_Release_Notes.txt b/assets/text/Monerocom_Release_Notes.txt new file mode 100644 index 000000000..6a091fc90 --- /dev/null +++ b/assets/text/Monerocom_Release_Notes.txt @@ -0,0 +1,13 @@ +Added Fixed Rate for exchanges (enter the "receive" amount on the exchange page to get the fixed rate)Added Fixed Rate for exchanges +WWEE(enter the "receive" amount on the exchange page to get the fixed rate)Added Fixed Rate for exchanges (enter the "receive" amount on the exchange page to get the fixed rate)Added Fixed Rate for exchanges (enter the "receive" amount on the exchange page to get the fixed rate) +Changed algorithm for choosing of change address for BTC and LTC electrum wallets +Changed algorithm for choosing of change address for BTC and LTC electrum wallets +Keep screen awake while the synchronization function +Added Fixed Rate for exchanges (enter the "receive" amount on the exchange page to get the fixed rate) +Changed algorithm for choosing of change address for BTC and LTC electrum wallets +Changed algorithm for choosing of change address for BTC and LTC electrum wallets +Keep screen awake while the synchronization function +Added Fixed Rate for exchanges (enter the "receive" amount on the exchange page to get the fixed rate) +Changed algorithm for choosing of change address for BTC and LTC electrum wallets +Changed algorithm for choosing of change address for BTC and LTC electrum wallets +Keep screen awake while the synchronizatio \ No newline at end of file diff --git a/assets/text/Release_Notes.txt b/assets/text/Release_Notes.txt new file mode 100644 index 000000000..6a091fc90 --- /dev/null +++ b/assets/text/Release_Notes.txt @@ -0,0 +1,13 @@ +Added Fixed Rate for exchanges (enter the "receive" amount on the exchange page to get the fixed rate)Added Fixed Rate for exchanges +WWEE(enter the "receive" amount on the exchange page to get the fixed rate)Added Fixed Rate for exchanges (enter the "receive" amount on the exchange page to get the fixed rate)Added Fixed Rate for exchanges (enter the "receive" amount on the exchange page to get the fixed rate) +Changed algorithm for choosing of change address for BTC and LTC electrum wallets +Changed algorithm for choosing of change address for BTC and LTC electrum wallets +Keep screen awake while the synchronization function +Added Fixed Rate for exchanges (enter the "receive" amount on the exchange page to get the fixed rate) +Changed algorithm for choosing of change address for BTC and LTC electrum wallets +Changed algorithm for choosing of change address for BTC and LTC electrum wallets +Keep screen awake while the synchronization function +Added Fixed Rate for exchanges (enter the "receive" amount on the exchange page to get the fixed rate) +Changed algorithm for choosing of change address for BTC and LTC electrum wallets +Changed algorithm for choosing of change address for BTC and LTC electrum wallets +Keep screen awake while the synchronizatio \ No newline at end of file diff --git a/lib/di.dart b/lib/di.dart index 584bc8b8e..d1b4bda42 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -208,7 +208,7 @@ Future setup( required Box transactionDescriptionBox, required Box ordersSource, Box? unspentCoinsInfoSource, - required Box anonpayInvoiceInfoSource + required Box anonpayInvoiceInfoSource, }) async { _walletInfoSource = walletInfoSource; _nodeSource = nodeSource; @@ -396,6 +396,7 @@ Future setup( dashboardViewModel: getIt.get(), addressListViewModel: getIt.get(), )); + getIt.registerFactory(() { final GlobalKey _navigatorKey = GlobalKey(); return DesktopSidebarWrapper( diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index a1a35125b..77298c2b5 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -42,6 +42,13 @@ Future defaultSettingsMigration( // check current nodes for nullability regardless of the version await checkCurrentNodes(nodes, sharedPreferences); + final isNewInstall = sharedPreferences + .getInt(PreferencesKey.currentDefaultSettingsMigrationVersion) == null; + + await sharedPreferences.setBool( + PreferencesKey.isNewInstall, isNewInstall); + + final currentVersion = sharedPreferences .getInt(PreferencesKey.currentDefaultSettingsMigrationVersion) ?? 0; diff --git a/lib/entities/preferences_key.dart b/lib/entities/preferences_key.dart index f5741a98b..90b57668d 100644 --- a/lib/entities/preferences_key.dart +++ b/lib/entities/preferences_key.dart @@ -41,6 +41,8 @@ class PreferencesKey { static const exchangeProvidersSelection = 'exchange-providers-selection'; static const clearnetDonationLink = 'clearnet_donation_link'; - static const onionDonationLink = 'onion_donation_link'; + static const onionDonationLink = 'onion_donation_link'; + static const lastSeenAppVersion = 'last_seen_app_version'; static const shouldShowMarketPlaceInDashboard = 'should_show_marketplace_in_dashboard'; + static const isNewInstall = 'is_new_install'; } diff --git a/lib/main.dart b/lib/main.dart index 8cc2629c9..53ec3fb65 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -187,8 +187,7 @@ Future initialSetup( transactionDescriptionBox: transactionDescriptions, ordersSource: ordersSource, anonpayInvoiceInfoSource: anonpayInvoiceInfo, - unspentCoinsInfoSource: unspentCoinsInfoSource, - ); + unspentCoinsInfoSource: unspentCoinsInfoSource); await bootstrap(navigatorKey); monero?.onStartup(); } diff --git a/lib/src/screens/dashboard/dashboard_page.dart b/lib/src/screens/dashboard/dashboard_page.dart index fbd976aa8..a76b93fc0 100644 --- a/lib/src/screens/dashboard/dashboard_page.dart +++ b/lib/src/screens/dashboard/dashboard_page.dart @@ -1,8 +1,10 @@ import 'dart:async'; +import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/entities/main_actions.dart'; import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_sidebar_wrapper.dart'; import 'package:cake_wallet/src/screens/dashboard/widgets/market_place_page.dart'; +import 'package:cake_wallet/utils/version_comparator.dart'; import 'package:cake_wallet/wallet_type_utils.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/routes.dart'; @@ -22,8 +24,12 @@ import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator.dart'; import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:mobx/mobx.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:smooth_page_indicator/smooth_page_indicator.dart'; import 'package:cake_wallet/main.dart'; +import 'package:cake_wallet/buy/moonpay/moonpay_buy_provider.dart'; +import 'package:url_launcher/url_launcher.dart'; +import 'package:cake_wallet/src/screens/release_notes/release_notes_screen.dart'; class DashboardPage extends StatelessWidget { DashboardPage({ @@ -117,7 +123,7 @@ class _DashboardPageView extends BasePage { @override Widget body(BuildContext context) { final controller = PageController(initialPage: initialPage); - + reaction((_) => dashboardViewModel.shouldShowMarketPlaceInDashboard, (bool value) { if (!dashboardViewModel.shouldShowMarketPlaceInDashboard) { controller.jumpToPage(0); @@ -131,7 +137,7 @@ class _DashboardPageView extends BasePage { } else { controller.jumpToPage(0); } - }); + }); _setEffects(context); return SafeArea( @@ -266,6 +272,25 @@ class _DashboardPageView extends BasePage { } }); + final sharedPrefs = await SharedPreferences.getInstance(); + final currentAppVersion = + VersionComparator.getExtendedVersionNumber(dashboardViewModel.settingsStore.appVersion); + final lastSeenAppVersion = sharedPrefs.getInt(PreferencesKey.lastSeenAppVersion); + final isNewInstall = sharedPrefs.getBool(PreferencesKey.isNewInstall); + + if (currentAppVersion != lastSeenAppVersion && !isNewInstall!) { + await Future.delayed(Duration(seconds: 1)); + await showPopUp( + context: context, + builder: (BuildContext context) { + return ReleaseNotesScreen( + title: 'Version ${dashboardViewModel.settingsStore.appVersion}'); + }); + sharedPrefs.setInt(PreferencesKey.lastSeenAppVersion, currentAppVersion); + } else if (isNewInstall!) { + sharedPrefs.setInt(PreferencesKey.lastSeenAppVersion, currentAppVersion); + } + var needToPresentYat = false; var isInactive = false; diff --git a/lib/src/screens/dashboard/desktop_dashboard_page.dart b/lib/src/screens/dashboard/desktop_dashboard_page.dart index 64f8a9aac..df74a3f6f 100644 --- a/lib/src/screens/dashboard/desktop_dashboard_page.dart +++ b/lib/src/screens/dashboard/desktop_dashboard_page.dart @@ -1,9 +1,12 @@ import 'dart:async'; +import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/src/screens/release_notes/release_notes_screen.dart'; import 'package:cake_wallet/src/screens/yat_emoji_id.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cake_wallet/utils/version_comparator.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; import 'package:cake_wallet/src/screens/dashboard/widgets/balance_page.dart'; @@ -11,6 +14,7 @@ import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_v import 'package:mobx/mobx.dart'; import 'package:cake_wallet/main.dart'; import 'package:cake_wallet/router.dart' as Router; +import 'package:shared_preferences/shared_preferences.dart'; class DesktopDashboardPage extends StatelessWidget { DesktopDashboardPage({ @@ -107,5 +111,24 @@ class DesktopDashboardPage extends StatelessWidget { needToPresentYat = true; }); + + final sharedPrefs = await SharedPreferences.getInstance(); + final currentAppVersion = + VersionComparator.getExtendedVersionNumber(dashboardViewModel.settingsStore.appVersion); + final lastSeenAppVersion = sharedPrefs.getInt(PreferencesKey.lastSeenAppVersion); + final isNewInstall = sharedPrefs.getBool(PreferencesKey.isNewInstall); + + if (currentAppVersion != lastSeenAppVersion && !isNewInstall!) { + await Future.delayed(Duration(seconds: 1)); + await showPopUp( + context: context, + builder: (BuildContext context) { + return ReleaseNotesScreen( + title: 'Version ${dashboardViewModel.settingsStore.appVersion}'); + }); + sharedPrefs.setInt(PreferencesKey.lastSeenAppVersion, currentAppVersion); + } else if (isNewInstall!) { + sharedPrefs.setInt(PreferencesKey.lastSeenAppVersion, currentAppVersion); + } } } diff --git a/lib/src/screens/dashboard/widgets/filter_widget.dart b/lib/src/screens/dashboard/widgets/filter_widget.dart index 17df0bc5e..9b8c87ea3 100644 --- a/lib/src/screens/dashboard/widgets/filter_widget.dart +++ b/lib/src/screens/dashboard/widgets/filter_widget.dart @@ -16,7 +16,6 @@ class FilterWidget extends StatelessWidget { FilterWidget({required this.dashboardViewModel}); final DashboardViewModel dashboardViewModel; - final closeIcon = Image.asset('assets/images/close.png', color: Palette.darkBlueCraiola); @override Widget build(BuildContext context) { @@ -101,7 +100,7 @@ class FilterWidget extends StatelessWidget { ), ], ), - AlertCloseButton(image: closeIcon) + AlertCloseButton() ], ), ); diff --git a/lib/src/screens/monero_accounts/monero_account_list_page.dart b/lib/src/screens/monero_accounts/monero_account_list_page.dart index 7fe15948f..145a2d8a4 100644 --- a/lib/src/screens/monero_accounts/monero_account_list_page.dart +++ b/lib/src/screens/monero_accounts/monero_account_list_page.dart @@ -27,9 +27,6 @@ class MoneroAccountListPage extends StatelessWidget { } final MoneroAccountListViewModel accountListViewModel; - final closeIcon = Image.asset('assets/images/close.png', - color: Palette.darkBlueCraiola, - ); ScrollController controller; double backgroundHeight; @@ -163,7 +160,7 @@ class MoneroAccountListPage extends StatelessWidget { ) ], ), - AlertCloseButton(image: closeIcon) + AlertCloseButton() ], ), ); diff --git a/lib/src/screens/release_notes/release_notes_screen.dart b/lib/src/screens/release_notes/release_notes_screen.dart new file mode 100644 index 000000000..f8b3730fb --- /dev/null +++ b/lib/src/screens/release_notes/release_notes_screen.dart @@ -0,0 +1,141 @@ +import 'dart:convert'; +import 'package:cake_wallet/src/widgets/alert_background.dart'; +import 'package:cake_wallet/src/widgets/alert_close_button.dart'; +import 'package:cake_wallet/wallet_type_utils.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class ReleaseNotesScreen extends StatelessWidget { + const ReleaseNotesScreen({ + required this.title, + }); + + final String title; + + Future> _loadStrings() async { + String notesContent = await rootBundle.loadString( + isMoneroOnly ? 'assets/text/Monerocom_Release_Notes.txt' : 'assets/text/Release_Notes.txt'); + return LineSplitter().convert(notesContent); + } + + @override + Widget build(BuildContext context) { + return Stack( + alignment: Alignment.center, + children: [ + AlertBackground( + child: AlertDialog( + insetPadding: EdgeInsets.only(left: 16, right: 16, bottom: 48), + elevation: 0.0, + contentPadding: EdgeInsets.zero, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(30))), + content: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(30.0), + gradient: LinearGradient(colors: [ + Theme.of(context).colorScheme.secondary, + Theme.of(context).scaffoldBackgroundColor, + ], begin: Alignment.centerLeft, end: Alignment.centerRight)), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 24.0), + child: Stack( + children: [ + SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.only(top: 16.0), + child: Container( + alignment: Alignment.bottomCenter, + child: DefaultTextStyle( + style: TextStyle( + decoration: TextDecoration.none, + fontSize: 24.0, + fontWeight: FontWeight.bold, + fontFamily: 'Lato', + color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!, + ), + child: Text(title), + ), + ), + ), + ), + SingleChildScrollView( + child: Padding( + padding: EdgeInsets.only(top: 48, bottom: 16), + child: Container( + width: double.maxFinite, + child: Column( + children: [ + ConstrainedBox( + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context).size.height * 0.7, + ), + child: _getNotesWidget(), + ) + ], + ), + ), + ), + ), + ], + ), + ), + ), + ), + ), + AlertCloseButton( + bottom: 30, + ) + ], + ); + } + + Widget _getNotesWidget() { + return FutureBuilder>( + future: _loadStrings(), + builder: (BuildContext context, AsyncSnapshot> snapshot) { + if (snapshot.hasData) { + return ListView.builder( + shrinkWrap: true, + itemCount: snapshot.data!.length, + itemBuilder: (BuildContext context, int index) { + return _getNoteItemWidget(snapshot.data![index], context); + }, + ); + } else if (snapshot.hasError) { + return Text('Error: ${snapshot.error}'); + } else { + return Center(child: CircularProgressIndicator()); + } + }, + ); + } + + Widget _getNoteItemWidget(String myString, BuildContext context) { + return Column( + children: [ + DefaultTextStyle( + style: TextStyle( + decoration: TextDecoration.none, + fontSize: 16.0, + fontFamily: 'Lato', + color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(right: 8.0), + child: Text('•'), + ), + Expanded( + child: Text(myString), + ), + ], + )), + SizedBox( + height: 16.0, + ) + ], + ); + } +} diff --git a/lib/src/screens/seed_language/widgets/seed_language_picker.dart b/lib/src/screens/seed_language/widgets/seed_language_picker.dart index 0e1e63f57..0aa22088f 100644 --- a/lib/src/screens/seed_language/widgets/seed_language_picker.dart +++ b/lib/src/screens/seed_language/widgets/seed_language_picker.dart @@ -47,23 +47,19 @@ const List seedLanguages = [ enum Places { topLeft, topRight, bottomLeft, bottomRight, inside } class SeedLanguagePicker extends StatefulWidget { - SeedLanguagePicker( - {Key? key, - this.selected = defaultSeedLanguage, - required this.onItemSelected}) + SeedLanguagePicker({Key? key, this.selected = defaultSeedLanguage, required this.onItemSelected}) : super(key: key); final String selected; final Function(String) onItemSelected; @override - SeedLanguagePickerState createState() => SeedLanguagePickerState( - selected: selected, onItemSelected: onItemSelected); + SeedLanguagePickerState createState() => + SeedLanguagePickerState(selected: selected, onItemSelected: onItemSelected); } class SeedLanguagePickerState extends State { - SeedLanguagePickerState( - {required this.selected, required this.onItemSelected}); + SeedLanguagePickerState({required this.selected, required this.onItemSelected}); final String selected; final Function(String) onItemSelected; diff --git a/lib/src/widgets/alert_close_button.dart b/lib/src/widgets/alert_close_button.dart index 1aa8277f3..a3657190a 100644 --- a/lib/src/widgets/alert_close_button.dart +++ b/lib/src/widgets/alert_close_button.dart @@ -15,20 +15,18 @@ class AlertCloseButton extends StatelessWidget { @override Widget build(BuildContext context) { return Positioned( - bottom: bottom ?? 60, - child: GestureDetector( - onTap: () => Navigator.of(context).pop(), - child: Container( - height: 42, - width: 42, - decoration: BoxDecoration( - color: Colors.white, - shape: BoxShape.circle - ), - child: Center( - child: image ?? closeButton, - ), + bottom: bottom ?? 60, + child: GestureDetector( + onTap: () => Navigator.of(context).pop(), + child: Container( + height: 42, + width: 42, + decoration: BoxDecoration(color: Colors.white, shape: BoxShape.circle), + child: Center( + child: image ?? closeButton, ), - )); + ), + ), + ); } -} \ No newline at end of file +} diff --git a/lib/utils/version_comparator.dart b/lib/utils/version_comparator.dart new file mode 100644 index 000000000..e0864568a --- /dev/null +++ b/lib/utils/version_comparator.dart @@ -0,0 +1,13 @@ +class VersionComparator { + static bool isVersion1Greater({required String v1, required String v2}) { + int v1Number = getExtendedVersionNumber(v1); + int v2Number = getExtendedVersionNumber(v2); + return v1Number > v2Number; + } + + static int getExtendedVersionNumber(String version) { + List stringVersionCells = version.split('.'); + List intVersionCells = stringVersionCells.map((i) => int.parse(i)).toList(); + return intVersionCells[0] * 100000 + intVersionCells[1] * 1000 + intVersionCells[2]; + } +} diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index 6cfbe1455..c28603a51 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -43,8 +43,7 @@ abstract class DashboardViewModelBase with Store { required this.settingsStore, required this.yatStore, required this.ordersStore, - required this.anonpayTransactionsStore, - }) + required this.anonpayTransactionsStore}) : isOutdatedElectrumWallet = false, hasSellAction = false, isEnabledSellAction = false, From f2b8dd21a1acd4e5eedfe314782780b21d022217 Mon Sep 17 00:00:00 2001 From: Godwin Asuquo <41484542+godilite@users.noreply.github.com> Date: Fri, 21 Apr 2023 21:03:42 +0300 Subject: [PATCH 09/39] CW-240 Receive fiat currency amount and receive animations (#877) * Redesign receive amount field * Fix issues with animations * Fix issues with animations * Fix max fraction digit to 8 * add another 0 * Update amount when currency is changed --------- Co-authored-by: Justin Ehrenhofer Co-authored-by: OmarHatem --- lib/core/amount_validator.dart | 5 +- lib/di.dart | 9 +- lib/entities/qr_view_data.dart | 11 + lib/router.dart | 9 +- .../dashboard/widgets/address_page.dart | 75 +++-- lib/src/screens/exchange/exchange_page.dart | 8 +- .../screens/receive/anonpay_receive_page.dart | 8 +- .../screens/receive/fullscreen_qr_page.dart | 10 +- lib/src/screens/receive/receive_page.dart | 299 +++++++++--------- .../receive/widgets/currency_input_field.dart | 120 +++++++ .../screens/receive/widgets/qr_widget.dart | 158 ++++----- .../screens/wallet_keys/wallet_keys_page.dart | 5 +- .../wallet_address_list_view_model.dart | 127 ++++---- 13 files changed, 504 insertions(+), 340 deletions(-) create mode 100644 lib/entities/qr_view_data.dart create mode 100644 lib/src/screens/receive/widgets/currency_input_field.dart diff --git a/lib/core/amount_validator.dart b/lib/core/amount_validator.dart index acd0ab135..fb5214d54 100644 --- a/lib/core/amount_validator.dart +++ b/lib/core/amount_validator.dart @@ -1,6 +1,7 @@ import 'package:cake_wallet/core/validator.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/currency.dart'; class AmountValidator extends TextValidator { AmountValidator({ @@ -57,7 +58,7 @@ class SymbolsAmountValidator extends TextValidator { } class DecimalAmountValidator extends TextValidator { - DecimalAmountValidator({required CryptoCurrency currency, required bool isAutovalidate }) + DecimalAmountValidator({required Currency currency, required bool isAutovalidate }) : super( errorMessage: S.current.decimal_places_error, pattern: _pattern(currency), @@ -65,7 +66,7 @@ class DecimalAmountValidator extends TextValidator { minLength: 0, maxLength: 0); - static String _pattern(CryptoCurrency currency) { + static String _pattern(Currency currency) { switch (currency) { case CryptoCurrency.xmr: return '^([0-9]+([.\,][0-9]{1,12})?|[.\,][0-9]{1,12})\$'; diff --git a/lib/di.dart b/lib/di.dart index d1b4bda42..78a9b7802 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -183,6 +183,7 @@ import 'package:cake_wallet/ionia/ionia_any_pay_payment_info.dart'; import 'package:cake_wallet/src/screens/receive/fullscreen_qr_page.dart'; import 'package:cake_wallet/core/wallet_loading_service.dart'; import 'package:cw_core/crypto_currency.dart'; +import 'package:cake_wallet/entities/qr_view_data.dart'; final getIt = GetIt.instance; @@ -321,7 +322,9 @@ Future setup( getIt.registerFactory(() => WalletAddressListViewModel( - appStore: getIt.get(), yatStore: getIt.get())); + appStore: getIt.get(), yatStore: getIt.get(), + fiatConversionStore: getIt.get() + )); getIt.registerFactory(() => BalanceViewModel( appStore: getIt.get(), @@ -815,8 +818,8 @@ Future setup( getIt.registerFactory(() => AddressResolver(yatService: getIt.get(), walletType: getIt.get().wallet!.type)); - getIt.registerFactoryParam( - (String qrData, int? version) => FullscreenQRPage(qrData: qrData, version: version,)); + getIt.registerFactoryParam( + (QrViewData viewData, _) => FullscreenQRPage(qrViewData: viewData)); getIt.registerFactory(() => IoniaApi()); diff --git a/lib/entities/qr_view_data.dart b/lib/entities/qr_view_data.dart new file mode 100644 index 000000000..a975d137b --- /dev/null +++ b/lib/entities/qr_view_data.dart @@ -0,0 +1,11 @@ +class QrViewData { + final int? version; + final String? heroTag; + final String data; + + QrViewData({ + this.version, + this.heroTag, + required this.data, + }); +} \ No newline at end of file diff --git a/lib/router.dart b/lib/router.dart index aebee0942..103a0889e 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -2,6 +2,7 @@ import 'package:cake_wallet/anonpay/anonpay_info_base.dart'; import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart'; import 'package:cake_wallet/entities/contact_record.dart'; import 'package:cake_wallet/buy/order.dart'; +import 'package:cake_wallet/entities/qr_view_data.dart'; import 'package:cake_wallet/src/screens/anonpay_details/anonpay_details_page.dart'; import 'package:cake_wallet/src/screens/backup/backup_page.dart'; import 'package:cake_wallet/src/screens/backup/edit_backup_password_page.dart'; @@ -242,7 +243,7 @@ Route createRoute(RouteSettings settings) { case Routes.receive: return CupertinoPageRoute( - fullscreenDialog: true, builder: (_) => getIt.get()); + builder: (_) => getIt.get()); case Routes.addressPage: return CupertinoPageRoute( @@ -451,14 +452,10 @@ Route createRoute(RouteSettings settings) { param1: args)); case Routes.fullscreenQR: - final args = settings.arguments as Map; - return MaterialPageRoute( builder: (_) => getIt.get( - param1: args['qrData'] as String, - param2: args['version'] as int?, - + param1: settings.arguments as QrViewData, )); case Routes.ioniaWelcomePage: diff --git a/lib/src/screens/dashboard/widgets/address_page.dart b/lib/src/screens/dashboard/widgets/address_page.dart index ebfabcd02..cdaa22673 100644 --- a/lib/src/screens/dashboard/widgets/address_page.dart +++ b/lib/src/screens/dashboard/widgets/address_page.dart @@ -26,11 +26,23 @@ class AddressPage extends BasePage { required this.addressListViewModel, required this.dashboardViewModel, required this.receiveOptionViewModel, - }) : _cryptoAmountFocus = FocusNode(); + }) : _cryptoAmountFocus = FocusNode(), + _formKey = GlobalKey(), + _amountController = TextEditingController(){ + _amountController.addListener(() { + if (_formKey.currentState!.validate()) { + addressListViewModel.changeAmount( + _amountController.text, + ); + } + }); + } final WalletAddressListViewModel addressListViewModel; final DashboardViewModel dashboardViewModel; final ReceiveOptionViewModel receiveOptionViewModel; + final TextEditingController _amountController; + final GlobalKey _formKey; final FocusNode _cryptoAmountFocus; @@ -69,28 +81,27 @@ class AddressPage extends BasePage { @override Widget? trailing(BuildContext context) { - final shareImage = Image.asset('assets/images/share.png', - color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!); - - return !addressListViewModel.hasAddressList - ? Material( - color: Colors.transparent, - child: IconButton( - padding: EdgeInsets.zero, - constraints: BoxConstraints(), - highlightColor: Colors.transparent, - splashColor: Colors.transparent, - iconSize: 25, - onPressed: () { - ShareUtil.share( - text: addressListViewModel.address.address, - context: context, - ); - }, - icon: shareImage, - ), - ) - : null; + return Material( + color: Colors.transparent, + child: IconButton( + padding: EdgeInsets.zero, + constraints: BoxConstraints(), + highlightColor: Colors.transparent, + splashColor: Colors.transparent, + iconSize: 25, + onPressed: () { + ShareUtil.share( + text: addressListViewModel.uri.toString(), + context: context, + ); + }, + icon: Icon( + Icons.share, + size: 20, + color: Theme.of(context).accentTextTheme.headline2!.backgroundColor!, + ), + ), + ); } @override @@ -137,16 +148,18 @@ class AddressPage extends BasePage { ) ]), child: Container( - padding: EdgeInsets.fromLTRB(24, 24, 24, 32), + padding: EdgeInsets.fromLTRB(24, 0, 24, 32), child: Column( children: [ - Expanded( - child: Observer(builder: (_) => QRWidget( - addressListViewModel: addressListViewModel, - amountTextFieldFocusNode: _cryptoAmountFocus, - isAmountFieldShow: !addressListViewModel.hasAccounts, - isLight: dashboardViewModel.settingsStore.currentTheme.type == ThemeType.light)) - ), + Expanded( + child: Observer( + builder: (_) => QRWidget( + formKey: _formKey, + addressListViewModel: addressListViewModel, + amountTextFieldFocusNode: _cryptoAmountFocus, + amountController: _amountController, + isLight: dashboardViewModel.settingsStore.currentTheme.type == + ThemeType.light))), Observer(builder: (_) { return addressListViewModel.hasAddressList ? GestureDetector( diff --git a/lib/src/screens/exchange/exchange_page.dart b/lib/src/screens/exchange/exchange_page.dart index 310e40cd8..a434ed13b 100644 --- a/lib/src/screens/exchange/exchange_page.dart +++ b/lib/src/screens/exchange/exchange_page.dart @@ -115,10 +115,6 @@ class ExchangePage extends BasePage { WidgetsBinding.instance .addPostFrameCallback((_) => _setReactions(context, exchangeViewModel)); - if (exchangeViewModel.isLowFee) { - _showFeeAlert(context); - } - return KeyboardActions( disableScroll: true, config: KeyboardActionsConfig( @@ -319,6 +315,10 @@ class ExchangePage extends BasePage { return; } + if (exchangeViewModel.isLowFee) { + _showFeeAlert(context); + } + final depositAddressController = depositKey.currentState!.addressController; final depositAmountController = depositKey.currentState!.amountController; final receiveAddressController = receiveKey.currentState!.addressController; diff --git a/lib/src/screens/receive/anonpay_receive_page.dart b/lib/src/screens/receive/anonpay_receive_page.dart index 27b5d41a3..1ee947d49 100644 --- a/lib/src/screens/receive/anonpay_receive_page.dart +++ b/lib/src/screens/receive/anonpay_receive_page.dart @@ -1,5 +1,6 @@ import 'package:cake_wallet/anonpay/anonpay_info_base.dart'; import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart'; +import 'package:cake_wallet/entities/qr_view_data.dart'; import 'package:cake_wallet/entities/receive_page_option.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/routes.dart'; @@ -133,10 +134,9 @@ class AnonPayReceivePage extends BasePage { await Navigator.pushNamed( context, Routes.fullscreenQR, - arguments: { - 'qrData': invoiceInfo.clearnetUrl, - 'version': qr.QrVersions.auto, - }, + arguments: QrViewData(data: invoiceInfo.clearnetUrl, + version: qr.QrVersions.auto, + ) ); // ignore: unawaited_futures DeviceDisplayBrightness.setBrightness(brightness); diff --git a/lib/src/screens/receive/fullscreen_qr_page.dart b/lib/src/screens/receive/fullscreen_qr_page.dart index 885c548f0..4bde38710 100644 --- a/lib/src/screens/receive/fullscreen_qr_page.dart +++ b/lib/src/screens/receive/fullscreen_qr_page.dart @@ -1,13 +1,13 @@ +import 'package:cake_wallet/entities/qr_view_data.dart'; import 'package:cake_wallet/src/screens/receive/widgets/qr_image.dart'; import 'package:cake_wallet/themes/theme_base.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; class FullscreenQRPage extends BasePage { - FullscreenQRPage({required this.qrData, int? this.version}); + FullscreenQRPage({required this.qrViewData}); - final String qrData; - final int? version; + final QrViewData qrViewData; @override Color get backgroundLightColor => currentTheme.type == ThemeType.bright ? Colors.transparent : Colors.white; @@ -63,7 +63,7 @@ class FullscreenQRPage extends BasePage { return Padding( padding: EdgeInsets.symmetric(horizontal: MediaQuery.of(context).size.width * 0.05), child: Hero( - tag: Key(qrData), + tag: Key(qrViewData.heroTag ?? qrViewData.data), child: Center( child: AspectRatio( aspectRatio: 1.0, @@ -71,7 +71,7 @@ class FullscreenQRPage extends BasePage { padding: EdgeInsets.all(10), decoration: BoxDecoration( border: Border.all(width: 3, color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!)), - child: QrImage(data: qrData, version: version), + child: QrImage(data: qrViewData.data, version: qrViewData.version), ), ), ), diff --git a/lib/src/screens/receive/receive_page.dart b/lib/src/screens/receive/receive_page.dart index 53bda35d8..00a157d97 100644 --- a/lib/src/screens/receive/receive_page.dart +++ b/lib/src/screens/receive/receive_page.dart @@ -21,16 +21,28 @@ import 'package:cake_wallet/src/screens/receive/widgets/qr_widget.dart'; import 'package:keyboard_actions/keyboard_actions.dart'; class ReceivePage extends BasePage { - ReceivePage({required this.addressListViewModel}) : _cryptoAmountFocus = FocusNode(); + ReceivePage({required this.addressListViewModel}) + : _cryptoAmountFocus = FocusNode(), + _amountController = TextEditingController(), + _formKey = GlobalKey() { + _amountController.addListener(() { + if (_formKey.currentState!.validate()) { + addressListViewModel.changeAmount(_amountController.text); + } + }); + } final WalletAddressListViewModel addressListViewModel; + final TextEditingController _amountController; + final GlobalKey _formKey; + static const _heroTag = 'receive_page'; @override String get title => S.current.receive; @override - Color get backgroundLightColor => currentTheme.type == ThemeType.bright - ? Colors.transparent : Colors.white; + Color get backgroundLightColor => + currentTheme.type == ThemeType.bright ? Colors.transparent : Colors.white; @override Color get backgroundDarkColor => Colors.transparent; @@ -68,162 +80,153 @@ class ReceivePage extends BasePage { @override Widget trailing(BuildContext context) { - final shareImage = - Image.asset('assets/images/share.png', - color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!); - return Material( color: Colors.transparent, child: Semantics( label: 'Share', child: IconButton( - padding: EdgeInsets.zero, - constraints: BoxConstraints(), - highlightColor: Colors.transparent, - splashColor: Colors.transparent, - iconSize: 25, - onPressed: () { - ShareUtil.share( - text: addressListViewModel.address.address, - context: context, - ); - }, - icon: shareImage + padding: EdgeInsets.zero, + constraints: BoxConstraints(), + highlightColor: Colors.transparent, + splashColor: Colors.transparent, + iconSize: 25, + onPressed: () { + ShareUtil.share( + text: addressListViewModel.uri.toString(), + context: context, + ); + }, + icon: Icon( + Icons.share, + size: 20, + color: Theme.of(context).accentTextTheme.headline2!.backgroundColor!, + ), ), - ) - ); + )); } @override Widget body(BuildContext context) { - return (addressListViewModel.type == WalletType.monero || addressListViewModel.type == WalletType.haven) + return (addressListViewModel.type == WalletType.monero || + addressListViewModel.type == WalletType.haven) ? KeyboardActions( - config: KeyboardActionsConfig( - keyboardActionsPlatform: KeyboardActionsPlatform.IOS, - keyboardBarColor: Theme.of(context).accentTextTheme!.bodyText1! - .backgroundColor!, - nextFocus: false, - actions: [ - KeyboardActionsItem( - focusNode: _cryptoAmountFocus, - toolbarButtons: [(_) => KeyboardDoneButton()], - ) - ]), - child: SingleChildScrollView( - child: Column( - children: [ - Padding( - padding: EdgeInsets.fromLTRB(24, 80, 24, 24), - child: QRWidget( - addressListViewModel: addressListViewModel, - isAmountFieldShow: true, - amountTextFieldFocusNode: _cryptoAmountFocus, - isLight: currentTheme.type == ThemeType.light), + config: KeyboardActionsConfig( + keyboardActionsPlatform: KeyboardActionsPlatform.IOS, + keyboardBarColor: Theme.of(context).accentTextTheme!.bodyText1!.backgroundColor!, + nextFocus: false, + actions: [ + KeyboardActionsItem( + focusNode: _cryptoAmountFocus, + toolbarButtons: [(_) => KeyboardDoneButton()], + ) + ]), + child: SingleChildScrollView( + child: Column( + children: [ + Padding( + padding: EdgeInsets.fromLTRB(24, 50, 24, 24), + child: QRWidget( + addressListViewModel: addressListViewModel, + formKey: _formKey, + heroTag: _heroTag, + amountTextFieldFocusNode: _cryptoAmountFocus, + amountController: _amountController, + isLight: currentTheme.type == ThemeType.light), + ), + Observer( + builder: (_) => ListView.separated( + padding: EdgeInsets.all(0), + separatorBuilder: (context, _) => const SectionDivider(), + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: addressListViewModel.items.length, + itemBuilder: (context, index) { + final item = addressListViewModel.items[index]; + Widget cell = Container(); + + if (item is WalletAccountListHeader) { + cell = HeaderTile( + onTap: () async => await showPopUp( + context: context, + builder: (_) => getIt.get()), + title: S.of(context).accounts, + icon: Icon( + Icons.arrow_forward_ios, + size: 14, + color: Theme.of(context).textTheme!.headline4!.color!, + )); + } + + if (item is WalletAddressListHeader) { + cell = HeaderTile( + onTap: () => + Navigator.of(context).pushNamed(Routes.newSubaddress), + title: S.of(context).addresses, + icon: Icon( + Icons.add, + size: 20, + color: Theme.of(context).textTheme!.headline4!.color!, + )); + } + + if (item is WalletAddressListItem) { + cell = Observer(builder: (_) { + final isCurrent = + item.address == addressListViewModel.address.address; + final backgroundColor = isCurrent + ? Theme.of(context).textTheme!.headline2!.decorationColor! + : Theme.of(context).textTheme!.headline3!.decorationColor!; + final textColor = isCurrent + ? Theme.of(context).textTheme!.headline2!.color! + : Theme.of(context).textTheme!.headline3!.color!; + + return AddressCell.fromItem(item, + isCurrent: isCurrent, + backgroundColor: backgroundColor, + textColor: textColor, + onTap: (_) => addressListViewModel.setAddress(item), + onEdit: () => Navigator.of(context) + .pushNamed(Routes.newSubaddress, arguments: item)); + }); + } + + return index != 0 + ? cell + : ClipRRect( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(30), + topRight: Radius.circular(30)), + child: cell, + ); + })), + ], ), - Observer( - builder: (_) => ListView.separated( - padding: EdgeInsets.all(0), - separatorBuilder: (context, _) => const SectionDivider(), - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemCount: addressListViewModel.items.length, - itemBuilder: (context, index) { - final item = addressListViewModel.items[index]; - Widget cell = Container(); - - if (item is WalletAccountListHeader) { - cell = HeaderTile( - onTap: () async => await showPopUp( - context: context, - builder: (_) => - getIt.get()), - title: S.of(context).accounts, - icon: Icon( - Icons.arrow_forward_ios, - size: 14, - color: - Theme.of(context).textTheme!.headline4!.color!, - )); - } - - if (item is WalletAddressListHeader) { - cell = HeaderTile( - onTap: () => Navigator.of(context) - .pushNamed(Routes.newSubaddress), - title: S.of(context).addresses, - icon: Icon( - Icons.add, - size: 20, - color: - Theme.of(context).textTheme!.headline4!.color!, - )); - } - - if (item is WalletAddressListItem) { - cell = Observer(builder: (_) { - final isCurrent = item.address == - addressListViewModel.address.address; - final backgroundColor = isCurrent - ? Theme.of(context) - .textTheme! - .headline2! - .decorationColor! - : Theme.of(context) - .textTheme! - .headline3! - .decorationColor!; - final textColor = isCurrent - ? Theme.of(context).textTheme!.headline2!.color! - : Theme.of(context).textTheme!.headline3!.color!; - - return AddressCell.fromItem(item, - isCurrent: isCurrent, - backgroundColor: backgroundColor, - textColor: textColor, - onTap: (_) => addressListViewModel.setAddress(item), - onEdit: () => Navigator.of(context).pushNamed( - Routes.newSubaddress, - arguments: item)); - }); - } - - return index != 0 - ? cell - : ClipRRect( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(30), - topRight: Radius.circular(30)), - child: cell, - ); - })), - ], - ), - )) : Padding( - padding: EdgeInsets.fromLTRB(24, 24, 24, 32), - child: Column( - children: [ - Expanded( - flex: 7, - child: QRWidget( - addressListViewModel: addressListViewModel, - isAmountFieldShow: true, - amountTextFieldFocusNode: _cryptoAmountFocus, - isLight: currentTheme.type == ThemeType.light), - ), - Expanded( - flex: 2, - child: SizedBox(), - ), - Text(S.of(context).electrum_address_disclaimer, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 15, - color: Theme.of(context) - .accentTextTheme! - .headline3! - .backgroundColor!)), - ], - ), - ); + )) + : Padding( + padding: EdgeInsets.fromLTRB(24, 24, 24, 32), + child: Column( + children: [ + Expanded( + flex: 7, + child: QRWidget( + formKey: _formKey, + heroTag: _heroTag, + addressListViewModel: addressListViewModel, + amountTextFieldFocusNode: _cryptoAmountFocus, + amountController: _amountController, + isLight: currentTheme.type == ThemeType.light), + ), + Expanded( + flex: 2, + child: SizedBox(), + ), + Text(S.of(context).electrum_address_disclaimer, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 15, + color: Theme.of(context).accentTextTheme!.headline3!.backgroundColor!)), + ], + ), + ); } } diff --git a/lib/src/screens/receive/widgets/currency_input_field.dart b/lib/src/screens/receive/widgets/currency_input_field.dart new file mode 100644 index 000000000..286c6f1cd --- /dev/null +++ b/lib/src/screens/receive/widgets/currency_input_field.dart @@ -0,0 +1,120 @@ +import 'package:cake_wallet/core/amount_validator.dart'; +import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; +import 'package:cw_core/currency.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class CurrencyInputField extends StatelessWidget { + const CurrencyInputField({ + super.key, + required this.onTapPicker, + required this.selectedCurrency, + this.focusNode, + required this.controller, + }); + final Function() onTapPicker; + final Currency selectedCurrency; + final FocusNode? focusNode; + final TextEditingController controller; + + @override + Widget build(BuildContext context) { + final arrowBottomPurple = Image.asset( + 'assets/images/arrow_bottom_purple_icon.png', + color: Colors.white, + height: 8, + ); + final _width = MediaQuery.of(context).size.width; + + return Column( + children: [ + Padding( + padding: EdgeInsets.only(top: 20), + child: SizedBox( + height: 40, + child: BaseTextFormField( + focusNode: focusNode, + controller: controller, + keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true), + inputFormatters: [FilteringTextInputFormatter.allow(RegExp(r'^\d+(\.|\,)?\d{0,8}'))], + hintText: '0.000', + placeholderTextStyle: TextStyle( + color: Theme.of(context).primaryTextTheme.headline5!.color!, + fontWeight: FontWeight.w600, + ), + borderColor: Theme.of(context).accentTextTheme.headline6!.backgroundColor!, + textColor: Colors.white, + textStyle: TextStyle( + color: Colors.white, + ), + prefixIcon: Padding( + padding: EdgeInsets.only( + left: _width / 4, + ), + child: Container( + padding: EdgeInsets.only(right: 8), + child: InkWell( + onTap: onTapPicker, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: EdgeInsets.only(right: 5), + child: arrowBottomPurple, + ), + Text( + selectedCurrency.name.toUpperCase(), + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16, + color: Colors.white, + ), + ), + if (selectedCurrency.tag != null) + Padding( + padding: const EdgeInsets.only(right: 3.0), + child: Container( + decoration: BoxDecoration( + color: Theme.of(context).primaryTextTheme.headline4!.color!, + borderRadius: BorderRadius.all( + Radius.circular(6), + ), + ), + child: Center( + child: Text( + selectedCurrency.tag!, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: Theme.of(context) + .primaryTextTheme + .headline4! + .decorationColor!, + ), + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.only(bottom: 3.0), + child: Text( + ':', + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 20, + color: Colors.white, + ), + ), + ), + ]), + ), + ), + ), + ), + ), + ), + ], + ); + } +} diff --git a/lib/src/screens/receive/widgets/qr_widget.dart b/lib/src/screens/receive/widgets/qr_widget.dart index 9e68ff0e1..fc58d4cec 100644 --- a/lib/src/screens/receive/widgets/qr_widget.dart +++ b/lib/src/screens/receive/widgets/qr_widget.dart @@ -1,37 +1,36 @@ +import 'package:cake_wallet/entities/qr_view_data.dart'; import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart'; +import 'package:cake_wallet/src/screens/receive/widgets/currency_input_field.dart'; import 'package:cake_wallet/utils/device_info.dart'; import 'package:cake_wallet/utils/show_bar.dart'; -import 'package:cw_core/wallet_type.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:device_display_brightness/device_display_brightness.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/screens/receive/widgets/qr_image.dart'; -import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; -import 'package:cake_wallet/core/amount_validator.dart'; import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart'; class QRWidget extends StatelessWidget { - QRWidget( - {required this.addressListViewModel, - required this.isLight, - this.qrVersion, - this.isAmountFieldShow = false, - this.amountTextFieldFocusNode}) - : amountController = TextEditingController(), - _formKey = GlobalKey() { - amountController.addListener(() => addressListViewModel?.amount = - _formKey.currentState!.validate() ? amountController.text : ''); - } + QRWidget({ + required this.addressListViewModel, + required this.isLight, + this.qrVersion, + this.heroTag, + required this.amountController, + required this.formKey, + this.amountTextFieldFocusNode, + }); final WalletAddressListViewModel addressListViewModel; - final bool isAmountFieldShow; final TextEditingController amountController; final FocusNode? amountTextFieldFocusNode; - final GlobalKey _formKey; + final GlobalKey formKey; final bool isLight; final int? qrVersion; + final String? heroTag; @override Widget build(BuildContext context) { @@ -40,7 +39,7 @@ class QRWidget extends StatelessWidget { return Column( mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.spaceEvenly, + mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Column( @@ -63,18 +62,18 @@ class QRWidget extends StatelessWidget { flex: 5, child: GestureDetector( onTap: () { - changeBrightnessForRoute(() async { - await Navigator.pushNamed( - context, - Routes.fullscreenQR, - arguments: { - 'qrData': addressListViewModel.uri.toString(), - }, - ); - }); + changeBrightnessForRoute( + () async { + await Navigator.pushNamed(context, Routes.fullscreenQR, + arguments: QrViewData( + data: addressListViewModel.uri.toString(), + heroTag: heroTag, + )); + }, + ); }, child: Hero( - tag: Key(addressListViewModel.uri.toString()), + tag: Key(heroTag ?? addressListViewModel.uri.toString()), child: Center( child: AspectRatio( aspectRatio: 1.0, @@ -83,7 +82,8 @@ class QRWidget extends StatelessWidget { decoration: BoxDecoration( border: Border.all( width: 3, - color: Theme.of(context).accentTextTheme.headline2!.backgroundColor!, + color: + Theme.of(context).accentTextTheme.headline2!.backgroundColor!, ), ), child: QrImage(data: addressListViewModel.uri.toString()), @@ -99,77 +99,77 @@ class QRWidget extends StatelessWidget { ), ], ), - if (isAmountFieldShow) - Padding( + Observer(builder: (_) { + return Padding( padding: EdgeInsets.only(top: 10), child: Row( children: [ Expanded( child: Form( - key: _formKey, - child: BaseTextFormField( + key: formKey, + child: CurrencyInputField( focusNode: amountTextFieldFocusNode, controller: amountController, - keyboardType: TextInputType.numberWithOptions(decimal: true), - inputFormatters: [FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]'))], - textAlign: TextAlign.center, - hintText: S.of(context).receive_amount, - textColor: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!, - borderColor: Theme.of(context).textTheme!.headline5!.decorationColor!, - validator: AmountValidator( - currency: walletTypeToCryptoCurrency(addressListViewModel!.type), - isAutovalidate: true), - // FIX-ME: Check does it equal to autovalidate: true, - autovalidateMode: AutovalidateMode.always, - placeholderTextStyle: TextStyle( - color: Theme.of(context).hoverColor, - fontSize: 18, - fontWeight: FontWeight.w500, - ), + onTapPicker: () => _presentPicker(context), + selectedCurrency: addressListViewModel.selectedCurrency, ), ), ), ], ), - ), - Padding( - padding: EdgeInsets.only(top: 8, bottom: 8), - child: Builder( - builder: (context) => Observer( - builder: (context) => GestureDetector( - onTap: () { - Clipboard.setData(ClipboardData(text: addressListViewModel!.address.address)); - showBar(context, S.of(context).copied_to_clipboard); - }, - child: Row( - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: Text( - addressListViewModel!.address.address, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 15, - fontWeight: FontWeight.w500, - color: - Theme.of(context).accentTextTheme!.headline2!.backgroundColor!), - ), + ); + }), + Padding( + padding: EdgeInsets.only(top: 20, bottom: 8), + child: Builder( + builder: (context) => Observer( + builder: (context) => GestureDetector( + onTap: () { + Clipboard.setData(ClipboardData(text: addressListViewModel.address.address)); + showBar(context, S.of(context).copied_to_clipboard); + }, + child: Row( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Text( + addressListViewModel.address.address, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.w500, + color: Theme.of(context).accentTextTheme.headline2!.backgroundColor!), ), - Padding( - padding: EdgeInsets.only(left: 12), - child: copyImage, - ) - ], - ), + ), + Padding( + padding: EdgeInsets.only(left: 12), + child: copyImage, + ) + ], ), ), ), - ) + ), + ) ], ); } + void _presentPicker(BuildContext context) async { + await showPopUp( + builder: (_) => CurrencyPicker( + selectedAtIndex: addressListViewModel.selectedCurrencyIndex, + items: addressListViewModel.currencies, + hintText: S.of(context).search_currency, + onItemSelected: addressListViewModel.selectCurrency, + ), + context: context, + ); + // update amount if currency changed + addressListViewModel.changeAmount(amountController.text); + } + Future changeBrightnessForRoute(Future Function() navigation) async { // if not mobile, just navigate if (!DeviceInfo.instance.isMobile) { diff --git a/lib/src/screens/wallet_keys/wallet_keys_page.dart b/lib/src/screens/wallet_keys/wallet_keys_page.dart index 8c378d174..eb34393cf 100644 --- a/lib/src/screens/wallet_keys/wallet_keys_page.dart +++ b/lib/src/screens/wallet_keys/wallet_keys_page.dart @@ -1,4 +1,5 @@ import 'package:auto_size_text/auto_size_text.dart'; +import 'package:cake_wallet/entities/qr_view_data.dart'; import 'package:cake_wallet/src/widgets/section_divider.dart'; import 'package:cake_wallet/utils/show_bar.dart'; import 'package:device_display_brightness/device_display_brightness.dart'; @@ -31,9 +32,7 @@ class WalletKeysPage extends BasePage { await Navigator.pushNamed( context, Routes.fullscreenQR, - arguments: { - 'qrData': (await walletKeysViewModel.url).toString(), - }, + arguments: QrViewData(data: await walletKeysViewModel.url.toString()), ); // ignore: unawaited_futures DeviceDisplayBrightness.setBrightness(brightness); diff --git a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart index b861261a6..a5e3a6ca7 100644 --- a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart +++ b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart @@ -1,5 +1,8 @@ +import 'package:cake_wallet/entities/fiat_currency.dart'; +import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart'; import 'package:cake_wallet/store/yat/yat_store.dart'; -import 'package:flutter/foundation.dart'; +import 'package:cw_core/currency.dart'; +import 'package:intl/intl.dart'; import 'package:mobx/mobx.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cake_wallet/utils/list_item.dart'; @@ -11,37 +14,30 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cw_core/transaction_history.dart'; import 'package:cw_core/balance.dart'; import 'package:cw_core/transaction_info.dart'; -import 'package:cw_core/wallet_type.dart'; import 'package:cake_wallet/store/app_store.dart'; -import 'dart:async'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/haven/haven.dart'; part 'wallet_address_list_view_model.g.dart'; -class WalletAddressListViewModel = WalletAddressListViewModelBase - with _$WalletAddressListViewModel; +class WalletAddressListViewModel = WalletAddressListViewModelBase with _$WalletAddressListViewModel; abstract class PaymentURI { - PaymentURI({ - required this.amount, - required this.address}); + PaymentURI({required this.amount, required this.address}); final String amount; final String address; } class MoneroURI extends PaymentURI { - MoneroURI({ - required String amount, - required String address}) + MoneroURI({required String amount, required String address}) : super(amount: amount, address: address); @override String toString() { var base = 'monero:' + address; - if (amount?.isNotEmpty ?? false) { + if (amount.isNotEmpty) { base += '?tx_amount=${amount.replaceAll(',', '.')}'; } @@ -50,16 +46,14 @@ class MoneroURI extends PaymentURI { } class HavenURI extends PaymentURI { - HavenURI({ - required String amount, - required String address}) + HavenURI({required String amount, required String address}) : super(amount: amount, address: address); @override String toString() { var base = 'haven:' + address; - if (amount?.isNotEmpty ?? false) { + if (amount.isNotEmpty) { base += '?tx_amount=${amount.replaceAll(',', '.')}'; } @@ -68,16 +62,14 @@ class HavenURI extends PaymentURI { } class BitcoinURI extends PaymentURI { - BitcoinURI({ - required String amount, - required String address}) + BitcoinURI({required String amount, required String address}) : super(amount: amount, address: address); @override String toString() { var base = 'bitcoin:' + address; - if (amount?.isNotEmpty ?? false) { + if (amount.isNotEmpty) { base += '?amount=${amount.replaceAll(',', '.')}'; } @@ -86,16 +78,14 @@ class BitcoinURI extends PaymentURI { } class LitecoinURI extends PaymentURI { - LitecoinURI({ - required String amount, - required String address}) + LitecoinURI({required String amount, required String address}) : super(amount: amount, address: address); @override String toString() { var base = 'litecoin:' + address; - if (amount?.isNotEmpty ?? false) { + if (amount.isNotEmpty) { base += '?amount=${amount.replaceAll(',', '.')}'; } @@ -106,24 +96,33 @@ class LitecoinURI extends PaymentURI { abstract class WalletAddressListViewModelBase with Store { WalletAddressListViewModelBase({ required AppStore appStore, - required this.yatStore - }) : _appStore = appStore, - _baseItems = [], - _wallet = appStore.wallet!, - hasAccounts = appStore.wallet!.type == WalletType.monero || appStore.wallet!.type == WalletType.haven, - amount = '' { - _onWalletChangeReaction = reaction((_) => _appStore.wallet, (WalletBase< - Balance, TransactionHistoryBase, TransactionInfo>? - wallet) { - if (wallet == null) { - return; - } - _wallet = wallet; - hasAccounts = _wallet.type == WalletType.monero; - }); + required this.yatStore, + required this.fiatConversionStore, + }) : _appStore = appStore, + _baseItems = [], + _wallet = appStore.wallet!, + selectedCurrency = walletTypeToCryptoCurrency(appStore.wallet!.type), + _cryptoNumberFormat = NumberFormat(_cryptoNumberPattern), + hasAccounts = + appStore.wallet!.type == WalletType.monero || appStore.wallet!.type == WalletType.haven, + amount = '' { _init(); } + static const String _cryptoNumberPattern = '0.00000000'; + + final NumberFormat _cryptoNumberFormat; + + final FiatConversionStore fiatConversionStore; + + List get currencies => [walletTypeToCryptoCurrency(_wallet.type), ...FiatCurrency.all]; + + @observable + Currency selectedCurrency; + + @computed + int get selectedCurrencyIndex => currencies.indexOf(selectedCurrency); + @observable String amount; @@ -156,8 +155,9 @@ abstract class WalletAddressListViewModelBase with Store { } @computed - ObservableList get items => - ObservableList()..addAll(_baseItems)..addAll(addressList); + ObservableList get items => ObservableList() + ..addAll(_baseItems) + ..addAll(addressList); @computed ObservableList get addressList { @@ -166,10 +166,7 @@ abstract class WalletAddressListViewModelBase with Store { if (wallet.type == WalletType.monero) { final primaryAddress = monero!.getSubaddressList(wallet).subaddresses.first; - final addressItems = monero - !.getSubaddressList(wallet) - .subaddresses - .map((subaddress) { + final addressItems = monero!.getSubaddressList(wallet).subaddresses.map((subaddress) { final isPrimary = subaddress == primaryAddress; return WalletAddressListItem( @@ -183,10 +180,7 @@ abstract class WalletAddressListViewModelBase with Store { if (wallet.type == WalletType.haven) { final primaryAddress = haven!.getSubaddressList(wallet).subaddresses.first; - final addressItems = haven - !.getSubaddressList(wallet) - .subaddresses - .map((subaddress) { + final addressItems = haven!.getSubaddressList(wallet).subaddresses.map((subaddress) { final isPrimary = subaddress == primaryAddress; return WalletAddressListItem( @@ -203,8 +197,7 @@ abstract class WalletAddressListViewModelBase with Store { final bitcoinAddresses = bitcoin!.getAddresses(wallet).map((addr) { final isPrimary = addr == primaryAddress; - return WalletAddressListItem( - isPrimary: isPrimary, name: null, address: addr); + return WalletAddressListItem(isPrimary: isPrimary, name: null, address: addr); }); addressList.addAll(bitcoinAddresses); } @@ -234,8 +227,7 @@ abstract class WalletAddressListViewModelBase with Store { bool get hasAddressList => _wallet.type == WalletType.monero || _wallet.type == WalletType.haven; @observable - WalletBase, TransactionInfo> - _wallet; + WalletBase, TransactionInfo> _wallet; List _baseItems; @@ -243,8 +235,6 @@ abstract class WalletAddressListViewModelBase with Store { final YatStore yatStore; - ReactionDisposer? _onWalletChangeReaction; - @action void setAddress(WalletAddressListItem address) => _wallet.walletAddresses.address = address.address; @@ -258,4 +248,31 @@ abstract class WalletAddressListViewModelBase with Store { _baseItems.add(WalletAddressListHeader()); } + + @action + void selectCurrency(Currency currency) { + selectedCurrency = currency; + } + + @action + void changeAmount(String amount) { + this.amount = amount; + if (selectedCurrency is FiatCurrency) { + _convertAmountToCrypto(); + } + } + + void _convertAmountToCrypto() { + final cryptoCurrency = walletTypeToCryptoCurrency(_wallet.type); + try { + final crypto = + double.parse(amount.replaceAll(',', '.')) / fiatConversionStore.prices[cryptoCurrency]!; + final cryptoAmountTmp = _cryptoNumberFormat.format(crypto); + if (amount != cryptoAmountTmp) { + amount = cryptoAmountTmp; + } + } catch (e) { + amount = ''; + } + } } From 1eb8d0c698e9a61a8670be876ece665b3bd3a95f Mon Sep 17 00:00:00 2001 From: Serhii Date: Fri, 21 Apr 2023 21:36:47 +0300 Subject: [PATCH 10/39] CW-229 Improved restore options from QR code (#793) * add restoring wallet from qr * add restore mode * add alert for exceptions * add restore from seed * add check for create wallet state * convert sweeping page into stateful * fix parsing url * restoration flow update * update restoring from key mode * update config * fix restor of BTC and LTC wallets * fix pin code issue * wallet Seed/keys uri or code fix * fix key restore credentials * update the restore workflow * update from main * PR coments fixes * update * update * PR fixes --- lib/di.dart | 13 +- lib/entities/parse_address_from_domain.dart | 2 +- lib/router.dart | 20 ++- lib/routes.dart | 1 + .../screens/restore/restore_options_page.dart | 78 +++++++-- .../screens/restore/sweeping_wallet_page.dart | 123 ++++++++++++++ .../screens/restore/wallet_restore_page.dart | 1 + .../screens/wallet_list/wallet_list_page.dart | 2 +- lib/src/screens/welcome/welcome_page.dart | 3 +- .../restore/restore_from_qr_vm.dart | 106 ++++++++++++ lib/view_model/restore/restore_mode.dart | 1 + lib/view_model/restore/restore_wallet.dart | 66 ++++++++ .../restore/wallet_restore_from_qr_code.dart | 159 ++++++++++++++++++ lib/view_model/wallet_creation_vm.dart | 20 ++- lib/view_model/wallet_keys_view_model.dart | 13 +- lib/view_model/wallet_new_vm.dart | 1 + .../wallet_restoration_from_keys_vm.dart | 3 +- .../wallet_restoration_from_seed_vm.dart | 1 + lib/view_model/wallet_restore_view_model.dart | 3 +- res/values/strings_ar.arb | 7 +- res/values/strings_de.arb | 7 +- res/values/strings_en.arb | 7 +- res/values/strings_es.arb | 7 +- res/values/strings_fr.arb | 7 +- res/values/strings_hi.arb | 7 +- res/values/strings_hr.arb | 7 +- res/values/strings_it.arb | 7 +- res/values/strings_ja.arb | 7 +- res/values/strings_ko.arb | 7 +- res/values/strings_my.arb | 7 +- res/values/strings_nl.arb | 7 +- res/values/strings_pl.arb | 7 +- res/values/strings_pt.arb | 7 +- res/values/strings_ru.arb | 7 +- res/values/strings_th.arb | 7 +- res/values/strings_tr.arb | 7 +- res/values/strings_uk.arb | 7 +- res/values/strings_zh.arb | 7 +- 38 files changed, 698 insertions(+), 51 deletions(-) create mode 100644 lib/src/screens/restore/sweeping_wallet_page.dart create mode 100644 lib/view_model/restore/restore_from_qr_vm.dart create mode 100644 lib/view_model/restore/restore_mode.dart create mode 100644 lib/view_model/restore/restore_wallet.dart create mode 100644 lib/view_model/restore/wallet_restore_from_qr_code.dart diff --git a/lib/di.dart b/lib/di.dart index 78a9b7802..1667fd1d2 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -53,6 +53,8 @@ import 'package:cake_wallet/src/screens/dashboard/widgets/balance_page.dart'; import 'package:cake_wallet/view_model/ionia/ionia_account_view_model.dart'; import 'package:cake_wallet/view_model/ionia/ionia_gift_cards_list_view_model.dart'; import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dart'; +import 'package:cake_wallet/view_model/restore/restore_from_qr_vm.dart'; +import 'package:cake_wallet/view_model/restore/restore_wallet.dart'; import 'package:cake_wallet/view_model/settings/display_settings_view_model.dart'; import 'package:cake_wallet/view_model/settings/other_settings_view_model.dart'; import 'package:cake_wallet/view_model/settings/privacy_settings_view_model.dart'; @@ -320,6 +322,13 @@ Future setup( type: type, language: language); }); + getIt + .registerFactoryParam((WalletType type, _) { + return WalletRestorationFromQRVM(getIt.get(), + getIt.get(param1: type), + _walletInfoSource, type); + }); + getIt.registerFactory(() => WalletAddressListViewModel( appStore: getIt.get(), yatStore: getIt.get(), @@ -743,7 +752,9 @@ Future setup( getIt.registerFactory( () => EditBackupPasswordPage(getIt.get())); - getIt.registerFactory(() => RestoreOptionsPage()); + getIt.registerFactoryParam((bool isNewInstall, _) => + RestoreOptionsPage(isNewInstall: isNewInstall)); + getIt.registerFactory( () => RestoreFromBackupViewModel(getIt.get())); diff --git a/lib/entities/parse_address_from_domain.dart b/lib/entities/parse_address_from_domain.dart index 0aefa5afe..8ac9bb51f 100644 --- a/lib/entities/parse_address_from_domain.dart +++ b/lib/entities/parse_address_from_domain.dart @@ -33,7 +33,7 @@ class AddressResolver { final addressPattern = AddressValidator.getAddressFromStringPattern(type); if (addressPattern == null) { - throw 'Unexpected token: $type for getAddressFromStringPattern'; + throw Exception('Unexpected token: $type for getAddressFromStringPattern'); } final match = RegExp(addressPattern).firstMatch(raw); diff --git a/lib/router.dart b/lib/router.dart index 103a0889e..5a657a9ca 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -10,6 +10,7 @@ import 'package:cake_wallet/src/screens/buy/buy_webview_page.dart'; import 'package:cake_wallet/src/screens/buy/onramper_page.dart'; import 'package:cake_wallet/src/screens/buy/payfura_page.dart'; import 'package:cake_wallet/src/screens/buy/pre_order_page.dart'; +import 'package:cake_wallet/src/screens/restore/sweeping_wallet_page.dart'; import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart'; import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart'; import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_dashboard_actions.dart'; @@ -40,6 +41,8 @@ import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart'; import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart'; import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart'; +import 'package:cake_wallet/view_model/restore/restore_from_qr_vm.dart'; +import 'package:cake_wallet/view_model/restore/restore_wallet.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/routes.dart'; @@ -158,8 +161,9 @@ Route createRoute(RouteSettings settings) { param2: false)); case Routes.restoreOptions: + final isNewInstall = settings.arguments as bool; return CupertinoPageRoute( - builder: (_) => getIt.get()); + builder: (_) => getIt.get(param1: isNewInstall)); case Routes.restoreWalletOptions: final type = WalletType.monero; //settings.arguments as WalletType; @@ -189,12 +193,18 @@ Route createRoute(RouteSettings settings) { })); case Routes.restoreWalletOptionsFromWelcome: - return CupertinoPageRoute( + final isNewInstall = settings.arguments as bool; + return isNewInstall ? CupertinoPageRoute( builder: (_) => getIt.get( param1: (PinCodeState context, dynamic _) => Navigator.pushNamed( context.context, Routes.restoreWalletType)), - fullscreenDialog: true); + fullscreenDialog: true) : CupertinoPageRoute( + builder: (_) => getIt.get( + param1: (BuildContext context, WalletType type) => + Navigator.of(context) + .pushNamed(Routes.restoreWallet, arguments: type), + param2: false)); case Routes.seed: return MaterialPageRoute( @@ -224,6 +234,10 @@ Route createRoute(RouteSettings settings) { builder: (_) => RestoreWalletFromKeysPage( walletRestorationFromKeysVM: walletRestorationFromKeysVM)); + case Routes.sweepingWalletPage: + return CupertinoPageRoute( + builder: (_) => getIt.get()); + case Routes.dashboard: return CupertinoPageRoute( builder: (_) => getIt.get()); diff --git a/lib/routes.dart b/lib/routes.dart index 295b55ae0..823febe78 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -84,6 +84,7 @@ class Routes { static const displaySettingsPage = '/display_settings_page'; static const otherSettingsPage = '/other_settings_page'; static const advancedPrivacySettings = '/advanced_privacy_settings'; + static const sweepingWalletPage = '/sweeping_wallet_page'; static const anonPayInvoicePage = '/anon_pay_invoice_page'; static const anonPayReceivePage = '/anon_pay_receive_page'; static const anonPayDetailsPage = '/anon_pay_details_page'; diff --git a/lib/src/screens/restore/restore_options_page.dart b/lib/src/screens/restore/restore_options_page.dart index a7eb03778..8025ebd85 100644 --- a/lib/src/screens/restore/restore_options_page.dart +++ b/lib/src/screens/restore/restore_options_page.dart @@ -1,3 +1,11 @@ +import 'package:cake_wallet/core/execution_state.dart'; +import 'package:cake_wallet/di.dart'; +import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart'; +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; +import 'package:cake_wallet/utils/language_list.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cake_wallet/view_model/restore/restore_from_qr_vm.dart'; +import 'package:cake_wallet/view_model/restore/wallet_restore_from_qr_code.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/routes.dart'; @@ -7,15 +15,16 @@ import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/generated/i18n.dart'; class RestoreOptionsPage extends BasePage { - RestoreOptionsPage(); - - static const _aspectRatioImage = 2.086; + RestoreOptionsPage({required this.isNewInstall}); @override String get title => S.current.restore_restore_wallet; + + final bool isNewInstall; final imageSeedKeys = Image.asset('assets/images/restore_wallet_image.png'); final imageBackup = Image.asset('assets/images/backup.png'); + final qrCode = Image.asset('assets/images/qr_code_icon.png'); @override Widget body(BuildContext context) { @@ -28,24 +37,69 @@ class RestoreOptionsPage extends BasePage { child: Column( children: [ RestoreButton( - onPressed: () => - Navigator.pushNamed(context, Routes.restoreWalletOptionsFromWelcome), + onPressed: () => Navigator.pushNamed( + context, Routes.restoreWalletOptionsFromWelcome, + arguments: isNewInstall), image: imageSeedKeys, title: S.of(context).restore_title_from_seed_keys, - description: - S.of(context).restore_description_from_seed_keys), + description: S.of(context).restore_description_from_seed_keys), + if (isNewInstall) + Padding( + padding: EdgeInsets.only(top: 24), + child: RestoreButton( + onPressed: () => Navigator.pushNamed(context, Routes.restoreFromBackup), + image: imageBackup, + title: S.of(context).restore_title_from_backup, + description: S.of(context).restore_description_from_backup), + ), Padding( padding: EdgeInsets.only(top: 24), child: RestoreButton( - onPressed: () => - Navigator.pushNamed(context, Routes.restoreFromBackup), - image: imageBackup, - title: S.of(context).restore_title_from_backup, - description: S.of(context).restore_description_from_backup), + onPressed: () async { + bool isPinSet = false; + if (isNewInstall) { + await Navigator.pushNamed(context, Routes.setupPin, + arguments: (PinCodeState setupPinContext, String _) { + setupPinContext.close(); + isPinSet = true; + }); + } + if (!isNewInstall || isPinSet) { + try { + final restoreWallet = + await WalletRestoreFromQRCode.scanQRCodeForRestoring(context); + + final restoreFromQRViewModel = getIt.get(param1: restoreWallet.type); + + await restoreFromQRViewModel.create(restoreWallet: restoreWallet); + if (restoreFromQRViewModel.state is FailureState) { + _onWalletCreateFailure(context, + 'Create wallet state: ${restoreFromQRViewModel.state.runtimeType.toString()}'); + } + } catch (e) { + _onWalletCreateFailure(context, e.toString()); + } + } + }, + image: qrCode, + title: S.of(context).scan_qr_code, + description: S.of(context).cold_or_recover_wallet), ) ], ), )), ); } + + void _onWalletCreateFailure(BuildContext context, String error) { + showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithOneAction( + alertTitle: S.current.error, + alertContent: error, + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop()); + }); + } } diff --git a/lib/src/screens/restore/sweeping_wallet_page.dart b/lib/src/screens/restore/sweeping_wallet_page.dart new file mode 100644 index 000000000..a7828b385 --- /dev/null +++ b/lib/src/screens/restore/sweeping_wallet_page.dart @@ -0,0 +1,123 @@ +import 'package:cake_wallet/themes/theme_base.dart'; +import 'package:flutter/material.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:flutter/scheduler.dart'; + +class SweepingWalletPage extends BasePage { + SweepingWalletPage(); + + static const aspectRatioImage = 1.25; + final welcomeImageLight = Image.asset('assets/images/welcome_light.png'); + final welcomeImageDark = Image.asset('assets/images/welcome.png'); + + + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Theme.of(context).backgroundColor, + resizeToAvoidBottomInset: false, + body: body(context)); + } + + @override + Widget body(BuildContext context) { + final welcomeImage = currentTheme.type == ThemeType.dark ? welcomeImageDark : welcomeImageLight; + + return SweepingWalletWidget( + aspectRatioImage: aspectRatioImage, + welcomeImage: welcomeImage, + ); + } +} + +class SweepingWalletWidget extends StatefulWidget { + const SweepingWalletWidget({ + required this.aspectRatioImage, + required this.welcomeImage, + }); + + final double aspectRatioImage; + final Image welcomeImage; + + @override + State createState() => _SweepingWalletWidgetState(); +} + +class _SweepingWalletWidgetState extends State { + @override + void initState() { + SchedulerBinding.instance.addPostFrameCallback((_) async { + + }); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return WillPopScope( + onWillPop: () async => false, + child: Container( + padding: EdgeInsets.only(top: 64, bottom: 24, left: 24, right: 24), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + flex: 2, + child: AspectRatio( + aspectRatio: widget.aspectRatioImage, + child: FittedBox(child: widget.welcomeImage, fit: BoxFit.fill))), + Flexible( + flex: 3, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + children: [ + Padding( + padding: EdgeInsets.only(top: 24), + child: Text( + S.of(context).please_wait, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500, + color: Theme.of(context).accentTextTheme!.headline2!.color!, + ), + textAlign: TextAlign.center, + ), + ), + Padding( + padding: EdgeInsets.only(top: 5), + child: Text( + S.of(context).sweeping_wallet, + style: TextStyle( + fontSize: 36, + fontWeight: FontWeight.bold, + color: Theme.of(context).primaryTextTheme!.headline6!.color!, + ), + textAlign: TextAlign.center, + ), + ), + Padding( + padding: EdgeInsets.only(top: 5), + child: Text( + S.of(context).sweeping_wallet_alert, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Theme.of(context).accentTextTheme!.headline2!.color!, + ), + textAlign: TextAlign.center, + ), + ), + ], + ), + ], + )) + ], + ))); + } +} + + diff --git a/lib/src/screens/restore/wallet_restore_page.dart b/lib/src/screens/restore/wallet_restore_page.dart index 7288d624b..aa9d31f1b 100644 --- a/lib/src/screens/restore/wallet_restore_page.dart +++ b/lib/src/screens/restore/wallet_restore_page.dart @@ -21,6 +21,7 @@ import 'package:cake_wallet/core/validator.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; import 'package:cake_wallet/core/seed_validator.dart'; +import 'package:cake_wallet/view_model/restore/restore_mode.dart'; class WalletRestorePage extends BasePage { WalletRestorePage(this.walletRestoreViewModel) diff --git a/lib/src/screens/wallet_list/wallet_list_page.dart b/lib/src/screens/wallet_list/wallet_list_page.dart index 316203ddd..e1c4c48e5 100644 --- a/lib/src/screens/wallet_list/wallet_list_page.dart +++ b/lib/src/screens/wallet_list/wallet_list_page.dart @@ -178,7 +178,7 @@ class WalletListBodyState extends State { Navigator.of(context).pushNamed(Routes.restoreWallet, arguments: widget.walletListViewModel.currentWalletType); } else { - Navigator.of(context).pushNamed(Routes.restoreWalletType); + Navigator.of(context).pushNamed(Routes.restoreOptions, arguments: false); } }, image: restoreWalletImage, diff --git a/lib/src/screens/welcome/welcome_page.dart b/lib/src/screens/welcome/welcome_page.dart index 86e1cbcf1..0249093bd 100644 --- a/lib/src/screens/welcome/welcome_page.dart +++ b/lib/src/screens/welcome/welcome_page.dart @@ -148,7 +148,8 @@ class WelcomePage extends BasePage { padding: EdgeInsets.only(top: 10), child: PrimaryImageButton( onPressed: () { - Navigator.pushNamed(context, Routes.restoreOptions); + Navigator.pushNamed(context, Routes.restoreOptions, + arguments: true); }, image: restoreWalletImage, text: S.of(context).restore_wallet, diff --git a/lib/view_model/restore/restore_from_qr_vm.dart b/lib/view_model/restore/restore_from_qr_vm.dart new file mode 100644 index 000000000..7efb92e69 --- /dev/null +++ b/lib/view_model/restore/restore_from_qr_vm.dart @@ -0,0 +1,106 @@ +import 'package:cake_wallet/bitcoin/bitcoin.dart'; +import 'package:cake_wallet/view_model/restore/restore_mode.dart'; +import 'package:cake_wallet/view_model/restore/restore_wallet.dart'; +import 'package:hive/hive.dart'; +import 'package:mobx/mobx.dart'; +import 'package:cake_wallet/monero/monero.dart'; +import 'package:cake_wallet/store/app_store.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cake_wallet/core/generate_wallet_password.dart'; +import 'package:cake_wallet/core/wallet_creation_service.dart'; +import 'package:cw_core/wallet_credentials.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:cake_wallet/view_model/wallet_creation_vm.dart'; +import 'package:cw_core/wallet_info.dart'; + +part 'restore_from_qr_vm.g.dart'; + +class WalletRestorationFromQRVM = WalletRestorationFromQRVMBase with _$WalletRestorationFromQRVM; + +abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store { + WalletRestorationFromQRVMBase(AppStore appStore, WalletCreationService walletCreationService, + Box walletInfoSource, WalletType type) + : height = 0, + viewKey = '', + spendKey = '', + wif = '', + address = '', + super(appStore, walletInfoSource, walletCreationService, + type: type, isRecovery: true); + + @observable + int height; + + @observable + String viewKey; + + @observable + String spendKey; + + @observable + String wif; + + @observable + String address; + + bool get hasRestorationHeight => type == WalletType.monero; + + @override + WalletCredentials getCredentialsFromRestoredWallet(dynamic options, RestoredWallet restoreWallet) { + final password = generateWalletPassword(); + + switch (restoreWallet.restoreMode) { + case WalletRestoreMode.keys: + switch (restoreWallet.type) { + case WalletType.monero: + return monero!.createMoneroRestoreWalletFromKeysCredentials( + name: name, + password: password, + language: 'English', + address: restoreWallet.address ?? '', + viewKey: restoreWallet.viewKey ?? '', + spendKey: restoreWallet.spendKey ?? '', + height: restoreWallet.height ?? 0); + case WalletType.bitcoin: + case WalletType.litecoin: + return bitcoin!.createBitcoinRestoreWalletFromWIFCredentials( + name: name, password: password, wif: wif); + default: + throw Exception('Unexpected type: ${restoreWallet.type.toString()}'); + } + case WalletRestoreMode.seed: + switch (restoreWallet.type) { + case WalletType.monero: + return monero!.createMoneroRestoreWalletFromSeedCredentials( + name: name, + height: restoreWallet.height ?? 0, + mnemonic: restoreWallet.mnemonicSeed ?? '', + password: password); + case WalletType.bitcoin: + case WalletType.litecoin: + return bitcoin!.createBitcoinRestoreWalletFromSeedCredentials( + name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password); + default: + throw Exception('Unexpected type: ${type.toString()}'); + } + default: + throw Exception('Unexpected type: ${type.toString()}'); + } + } + + @override + Future processFromRestoredWallet(WalletCredentials credentials, RestoredWallet restoreWallet) async { + try { + switch (restoreWallet.restoreMode) { + case WalletRestoreMode.keys: + return walletCreationService.restoreFromKeys(credentials); + case WalletRestoreMode.seed: + return walletCreationService.restoreFromSeed(credentials); + default: + throw Exception('Unexpected restore mode: ${restoreWallet.restoreMode.toString()}'); + } + } catch (e) { + throw Exception('Unexpected restore mode: ${e.toString()}'); + } + } +} diff --git a/lib/view_model/restore/restore_mode.dart b/lib/view_model/restore/restore_mode.dart new file mode 100644 index 000000000..d8344841d --- /dev/null +++ b/lib/view_model/restore/restore_mode.dart @@ -0,0 +1 @@ +enum WalletRestoreMode { seed, keys, txids } \ No newline at end of file diff --git a/lib/view_model/restore/restore_wallet.dart b/lib/view_model/restore/restore_wallet.dart new file mode 100644 index 000000000..0f872d8cc --- /dev/null +++ b/lib/view_model/restore/restore_wallet.dart @@ -0,0 +1,66 @@ +import 'package:cake_wallet/view_model/restore/restore_mode.dart'; +import 'package:cw_core/wallet_type.dart'; + +class RestoredWallet { + RestoredWallet( + {required this.restoreMode, + required this.type, + required this.address, + this.txId, + this.spendKey, + this.viewKey, + this.mnemonicSeed, + this.txAmount, + this.txDescription, + this.recipientName, + this.height}); + + final WalletRestoreMode restoreMode; + final WalletType type; + final String? address; + final String? txId; + final String? spendKey; + final String? viewKey; + final String? mnemonicSeed; + final String? txAmount; + final String? txDescription; + final String? recipientName; + final int? height; + + factory RestoredWallet.fromKey(Map json) { + final height = json['height'] as String?; + return RestoredWallet( + restoreMode: json['mode'] as WalletRestoreMode, + type: json['type'] as WalletType, + address: json['address'] as String?, + spendKey: json['spend_key'] as String?, + viewKey: json['view_key'] as String?, + height: height != null ? int.parse(height) : 0, + ); + } + + factory RestoredWallet.fromSeed(Map json) { + final height = json['height'] as String?; + final mnemonic_seed = json['mnemonic_seed'] as String?; + final seed = json['seed'] as String?; + return RestoredWallet( + restoreMode: json['mode'] as WalletRestoreMode, + type: json['type'] as WalletType, + address: json['address'] as String?, + mnemonicSeed: mnemonic_seed ?? seed, + height: height != null ? int.parse(height) : 0, + ); + } + + factory RestoredWallet.fromTxIds(Map json) { + return RestoredWallet( + restoreMode: json['mode'] as WalletRestoreMode, + type: json['type'] as WalletType, + address: json['address'] as String?, + txId: json['tx_payment_id'] as String, + txAmount: json['tx_amount'] as String, + txDescription: json['tx_description'] as String?, + recipientName: json['recipient_name'] as String?, + ); + } +} diff --git a/lib/view_model/restore/wallet_restore_from_qr_code.dart b/lib/view_model/restore/wallet_restore_from_qr_code.dart new file mode 100644 index 000000000..241b2d3fd --- /dev/null +++ b/lib/view_model/restore/wallet_restore_from_qr_code.dart @@ -0,0 +1,159 @@ +import 'package:cake_wallet/core/address_validator.dart'; +import 'package:cake_wallet/core/seed_validator.dart'; +import 'package:cake_wallet/entities/mnemonic_item.dart'; +import 'package:cake_wallet/entities/parse_address_from_domain.dart'; +import 'package:cake_wallet/entities/qr_scanner.dart'; +import 'package:cake_wallet/view_model/restore/restore_mode.dart'; +import 'package:cake_wallet/view_model/restore/restore_wallet.dart'; +import 'package:cw_bitcoin/bitcoin_mnemonic.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:flutter/cupertino.dart'; + +class WalletRestoreFromQRCode { + WalletRestoreFromQRCode(); + + static Future scanQRCodeForRestoring(BuildContext context) async { + String code = await presentQRScanner(); + Map credentials = {}; + + if (code.isEmpty) { + throw Exception('Unexpected scan QR code value: value is empty'); + } + final formattedUri = getFormattedUri(code); + final uri = Uri.parse(formattedUri); + final queryParameters = uri.queryParameters; + credentials['type'] = getWalletTypeFromUrl(uri.scheme); + + final address = getAddressFromUrl( + type: credentials['type'] as WalletType, + rawString: queryParameters.toString(), + ); + if (address != null) { + credentials['address'] = address; + } + + final seed = + getSeedPhraseFromUrl(queryParameters.toString(), credentials['type'] as WalletType); + if (seed != null) { + credentials['seed'] = seed; + } + credentials.addAll(queryParameters); + credentials['mode'] = getWalletRestoreMode(credentials); + + switch (credentials['mode']) { + case WalletRestoreMode.txids: + return RestoredWallet.fromTxIds(credentials); + case WalletRestoreMode.seed: + return RestoredWallet.fromSeed(credentials); + case WalletRestoreMode.keys: + return RestoredWallet.fromKey(credentials); + default: + throw Exception('Unexpected restore mode: ${credentials['mode']}'); + } + } + + static String getFormattedUri(String code) { + final index = code.indexOf(':'); + final scheme = code.substring(0, index).replaceAll('_', '-'); + final query = code.substring(index + 1).replaceAll('?', '&'); + final formattedUri = '$scheme:?$query'; + return formattedUri; + } + + static WalletType getWalletTypeFromUrl(String scheme) { + switch (scheme) { + case 'monero': + case 'monero-wallet': + return WalletType.monero; + case 'bitcoin': + case 'bitcoin-wallet': + return WalletType.bitcoin; + case 'litecoin': + case 'litecoin-wallet': + return WalletType.litecoin; + default: + throw Exception('Unexpected wallet type: ${scheme.toString()}'); + } + } + + static String? getAddressFromUrl({required WalletType type, required String rawString}) { + return AddressResolver.extractAddressByType( + raw: rawString, type: walletTypeToCryptoCurrency(type)); + } + + static String? getSeedPhraseFromUrl(String rawString, WalletType walletType) { + switch (walletType) { + case WalletType.monero: + RegExp regex25 = RegExp(r'\b(\S+\b\s+){24}\S+\b'); + RegExp regex14 = RegExp(r'\b(\S+\b\s+){13}\S+\b'); + RegExp regex13 = RegExp(r'\b(\S+\b\s+){12}\S+\b'); + + if (regex25.firstMatch(rawString) == null) { + if (regex14.firstMatch(rawString) == null) { + if (regex13.firstMatch(rawString) == null) { + return null; + } else { + return regex13.firstMatch(rawString)!.group(0)!; + } + } else { + return regex14.firstMatch(rawString)!.group(0)!; + } + } else { + return regex25.firstMatch(rawString)!.group(0)!; + } + case WalletType.bitcoin: + case WalletType.litecoin: + RegExp regex24 = RegExp(r'\b(\S+\b\s+){23}\S+\b'); + RegExp regex18 = RegExp(r'\b(\S+\b\s+){17}\S+\b'); + RegExp regex12 = RegExp(r'\b(\S+\b\s+){11}\S+\b'); + + if (regex24.firstMatch(rawString) == null) { + if (regex18.firstMatch(rawString) == null) { + if (regex12.firstMatch(rawString) == null) { + return null; + } else { + return regex12.firstMatch(rawString)!.group(0)!; + } + } else { + return regex18.firstMatch(rawString)!.group(0)!; + } + } else { + return regex24.firstMatch(rawString)!.group(0)!; + } + default: + return null; + } + } + + static WalletRestoreMode getWalletRestoreMode(Map credentials) { + final type = credentials['type'] as WalletType; + if (credentials.containsKey('tx_payment_id')) { + final txIdValue = credentials['tx_payment_id'] as String? ?? ''; + return txIdValue.isNotEmpty + ? WalletRestoreMode.txids + : throw Exception('Unexpected restore mode: tx_payment_id is invalid'); + } + + if (credentials.containsKey('seed')) { + final seedValue = credentials['seed'] as String; + final words = SeedValidator.getWordList(type: type, language: 'english'); + seedValue.split(' ').forEach((element) { + if (!words.contains(element)) { + throw Exception('Unexpected restore mode: mnemonic_seed is invalid'); + } + }); + return WalletRestoreMode.seed; + } + + if (credentials.containsKey('spend_key') || credentials.containsKey('view_key')) { + final spendKeyValue = credentials['spend_key'] as String? ?? ''; + final viewKeyValue = credentials['view_key'] as String? ?? ''; + + return spendKeyValue.isNotEmpty || viewKeyValue.isNotEmpty + ? WalletRestoreMode.keys + : throw Exception('Unexpected restore mode: spend_key or view_key is invalid'); + } + + throw Exception('Unexpected restore mode: restore params are invalid'); + } +} diff --git a/lib/view_model/wallet_creation_vm.dart b/lib/view_model/wallet_creation_vm.dart index 2609aed7d..323d1f911 100644 --- a/lib/view_model/wallet_creation_vm.dart +++ b/lib/view_model/wallet_creation_vm.dart @@ -1,5 +1,5 @@ import 'package:cake_wallet/core/wallet_creation_service.dart'; -import 'package:flutter/foundation.dart'; +import 'package:cake_wallet/view_model/restore/restore_wallet.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; import 'package:cake_wallet/core/execution_state.dart'; @@ -39,7 +39,8 @@ abstract class WalletCreationVMBase with Store { bool typeExists(WalletType type) => walletCreationService.typeExists(type); - Future create({dynamic options}) async { + Future create({dynamic options, RestoredWallet? restoreWallet}) async { + final type = restoreWallet?.type ?? this.type; try { state = IsExecutingState(); if (name.isEmpty) { @@ -49,7 +50,9 @@ abstract class WalletCreationVMBase with Store { walletCreationService.checkIfExists(name); final dirPath = await pathForWalletDir(name: name, type: type); final path = await pathForWallet(name: name, type: type); - final credentials = getCredentials(options); + final credentials = restoreWallet != null + ? getCredentialsFromRestoredWallet(options, restoreWallet) + : getCredentials(options); final walletInfo = WalletInfo.external( id: WalletBase.idFor(name, type), name: name, @@ -62,7 +65,9 @@ abstract class WalletCreationVMBase with Store { address: '', showIntroCakePayCard: (!walletCreationService.typeExists(type)) && type != WalletType.haven); credentials.walletInfo = walletInfo; - final wallet = await process(credentials); + final wallet = restoreWallet != null + ? await processFromRestoredWallet(credentials, restoreWallet) + : await process(credentials); walletInfo.address = wallet.walletAddresses.address; await _walletInfoSource.add(walletInfo); _appStore.changeCurrentWallet(wallet); @@ -72,10 +77,15 @@ abstract class WalletCreationVMBase with Store { state = FailureState(e.toString()); } } - WalletCredentials getCredentials(dynamic options) => throw UnimplementedError(); Future process(WalletCredentials credentials) => throw UnimplementedError(); + + WalletCredentials getCredentialsFromRestoredWallet(dynamic options, RestoredWallet restoreWallet) => + throw UnimplementedError(); + + Future processFromRestoredWallet(WalletCredentials credentials, RestoredWallet restoreWallet) => + throw UnimplementedError(); } diff --git a/lib/view_model/wallet_keys_view_model.dart b/lib/view_model/wallet_keys_view_model.dart index e20089915..0a9ae60a7 100644 --- a/lib/view_model/wallet_keys_view_model.dart +++ b/lib/view_model/wallet_keys_view_model.dart @@ -88,16 +88,17 @@ abstract class WalletKeysViewModelBase with Store { return null; } - String get _path { + + String get _scheme { switch (_appStore.wallet!.type) { case WalletType.monero: - return 'monero_wallet:'; + return 'monero-wallet'; case WalletType.bitcoin: - return 'bitcoin_wallet:'; + return 'bitcoin-wallet'; case WalletType.litecoin: - return 'litecoin_wallet:'; + return 'litecoin-wallet'; case WalletType.haven: - return 'haven_wallet:'; + return 'haven-wallet'; default: throw Exception('Unexpected wallet type: ${_appStore.wallet!.toString()}'); } @@ -124,7 +125,7 @@ abstract class WalletKeysViewModelBase with Store { Future get url async { return Uri( - path: _path, + scheme: _scheme, queryParameters: await _queryParams, ); } diff --git a/lib/view_model/wallet_new_vm.dart b/lib/view_model/wallet_new_vm.dart index e505cff77..dcd57b3ff 100644 --- a/lib/view_model/wallet_new_vm.dart +++ b/lib/view_model/wallet_new_vm.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/view_model/restore/restore_wallet.dart'; import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; diff --git a/lib/view_model/wallet_restoration_from_keys_vm.dart b/lib/view_model/wallet_restoration_from_keys_vm.dart index f7195c240..97cb8d519 100644 --- a/lib/view_model/wallet_restoration_from_keys_vm.dart +++ b/lib/view_model/wallet_restoration_from_keys_vm.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/view_model/restore/restore_wallet.dart'; import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; @@ -66,7 +67,7 @@ abstract class WalletRestorationFromKeysVMBase extends WalletCreationVM return bitcoin!.createBitcoinRestoreWalletFromWIFCredentials( name: name, password: password, wif: wif); default: - throw Exception('Unexpected type: ${type.toString()}');; + throw Exception('Unexpected type: ${type.toString()}'); } } diff --git a/lib/view_model/wallet_restoration_from_seed_vm.dart b/lib/view_model/wallet_restoration_from_seed_vm.dart index ef584db08..0caa7f37d 100644 --- a/lib/view_model/wallet_restoration_from_seed_vm.dart +++ b/lib/view_model/wallet_restoration_from_seed_vm.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/view_model/restore/restore_wallet.dart'; import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; diff --git a/lib/view_model/wallet_restore_view_model.dart b/lib/view_model/wallet_restore_view_model.dart index f122da3ba..7af653cf1 100644 --- a/lib/view_model/wallet_restore_view_model.dart +++ b/lib/view_model/wallet_restore_view_model.dart @@ -1,5 +1,6 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/core/mnemonic_length.dart'; +import 'package:cake_wallet/view_model/restore/restore_wallet.dart'; import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; @@ -13,10 +14,10 @@ import 'package:cw_core/wallet_info.dart'; import 'package:cake_wallet/view_model/wallet_creation_vm.dart'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/haven/haven.dart'; +import 'package:cake_wallet/view_model/restore/restore_mode.dart'; part 'wallet_restore_view_model.g.dart'; -enum WalletRestoreMode { seed, keys } class WalletRestoreViewModel = WalletRestoreViewModelBase with _$WalletRestoreViewModel; diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 23686e451..0caa02094 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -150,7 +150,7 @@ "receive_amount":"المقدار", "subaddresses":"العناوين الفرعية", "addresses":"عناوين", - "scan_qr_code":"امسح ال QR للحصول على العنوان", + "scan_qr_code_to_get_address":"امسح ال QR للحصول على العنوان", "qr_fullscreen":"انقر لفتح ال QR بملء الشاشة", "rename":"إعادة تسمية", "choose_account":"اختر حساب", @@ -683,6 +683,11 @@ "arrive_in_this_address" : "سيصل ${currency} ${tag}إلى هذا العنوان", "do_not_send": "لا ترسل", "error_dialog_content": "عفوًا ، لقد حصلنا على بعض الخطأ.\n\nيرجى إرسال تقرير التعطل إلى فريق الدعم لدينا لتحسين التطبيق.", + "scan_qr_code": "امسح رمز QR ضوئيًا", + "cold_or_recover_wallet": "أضف محفظة باردة أو استعد محفظة ورقية", + "please_wait": "انتظر من فضلك", + "sweeping_wallet": "كنس المحفظة", + "sweeping_wallet_alert": "لن يستغرق هذا وقتًا طويلاً. لا تترك هذه الشاشة وإلا فقد يتم فقد أموال سويبت", "decimal_places_error": "عدد كبير جدًا من المنازل العشرية", "edit_node": "تحرير العقدة", "frozen_balance": "الرصيد المجمد", diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 9fd3432bd..930ed4bac 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -150,7 +150,7 @@ "receive_amount" : "Betrag", "subaddresses" : "Unteradressen", "addresses" : "Adressen", - "scan_qr_code" : "Scannen Sie den QR-Code, um die Adresse zu erhalten", + "scan_qr_code_to_get_address" : "Scannen Sie den QR-Code, um die Adresse zu erhalten", "qr_fullscreen" : "Tippen Sie hier, um den QR-Code im Vollbildmodus zu öffnen", "rename" : "Umbenennen", "choose_account" : "Konto auswählen", @@ -685,6 +685,11 @@ "arrive_in_this_address" : "${currency} ${tag}wird an dieser Adresse ankommen", "do_not_send": "Nicht senden", "error_dialog_content": "Hoppla, wir haben einen Fehler.\n\nBitte senden Sie den Absturzbericht an unser Support-Team, um die Anwendung zu verbessern.", + "scan_qr_code": "QR-Code scannen", + "cold_or_recover_wallet": "Fügen Sie eine Cold Wallet hinzu oder stellen Sie eine Paper Wallet wieder her", + "please_wait": "Warten Sie mal", + "sweeping_wallet": "Kehre Geldbörse", + "sweeping_wallet_alert": "Das sollte nicht lange dauern. VERLASSEN SIE DIESEN BILDSCHIRM NICHT, ANDERNFALLS KÖNNEN DIE SWEPT-GELDER VERLOREN GEHEN", "decimal_places_error": "Zu viele Nachkommastellen", "edit_node": "Knoten bearbeiten", "frozen_balance": "Gefrorenes Gleichgewicht", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 7f03b96e9..22cc9b950 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -150,7 +150,7 @@ "receive_amount" : "Amount", "subaddresses" : "Subaddresses", "addresses" : "Addresses", - "scan_qr_code" : "Scan the QR code to get the address", + "scan_qr_code_to_get_address" : "Scan the QR code to get the address", "qr_fullscreen" : "Tap to open full screen QR code", "rename" : "Rename", "choose_account" : "Choose account", @@ -685,6 +685,11 @@ "arrive_in_this_address" : "${currency} ${tag}will arrive in this address", "do_not_send": "Don't send", "error_dialog_content": "Oops, we got some error.\n\nPlease send the crash report to our support team to make the application better.", + "scan_qr_code": "Scan QR code", + "cold_or_recover_wallet": "Add a cold wallet or recover a paper wallet", + "please_wait": "Please wait", + "sweeping_wallet": "Sweeping wallet", + "sweeping_wallet_alert": "This shouldn’t take long. DO NOT LEAVE THIS SCREEN OR THE SWEPT FUNDS MAY BE LOST.", "invoice_details": "Invoice details", "donation_link_details": "Donation link details", "anonpay_description": "Generate ${type}. The recipient can ${method} with any supported cryptocurrency, and you will receive funds in this wallet.", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 4b649e12a..9159d7a12 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -150,7 +150,7 @@ "receive_amount" : "Cantidad", "subaddresses" : "Subdirecciones", "addresses" : "Direcciones", - "scan_qr_code" : "Escanee el código QR para obtener la dirección", + "scan_qr_code_to_get_address" : "Escanee el código QR para obtener la dirección", "qr_fullscreen" : "Toque para abrir el código QR en pantalla completa", "rename" : "Rebautizar", "choose_account" : "Elegir cuenta", @@ -685,6 +685,11 @@ "arrive_in_this_address" : "${currency} ${tag}llegará a esta dirección", "do_not_send": "no enviar", "error_dialog_content": "Vaya, tenemos un error.\n\nEnvíe el informe de bloqueo a nuestro equipo de soporte para mejorar la aplicación.", + "scan_qr_code": "Escanear código QR", + "cold_or_recover_wallet": "Agregue una billetera fría o recupere una billetera de papel", + "please_wait": "Espere por favor", + "sweeping_wallet": "Billetera de barrido", + "sweeping_wallet_alert": "Esto no debería llevar mucho tiempo. NO DEJES ESTA PANTALLA O SE PUEDEN PERDER LOS FONDOS BARRIDOS", "decimal_places_error": "Demasiados lugares decimales", "edit_node": "Editar nodo", "frozen_balance": "Balance congelado", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 5250ed88d..983fc5f46 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -150,7 +150,7 @@ "receive_amount" : "Montant", "subaddresses" : "Sous-adresses", "addresses" : "Adresses", - "scan_qr_code" : "Scannez le QR code pour obtenir l'adresse", + "scan_qr_code_to_get_address" : "Scannez le QR code pour obtenir l'adresse", "qr_fullscreen" : "Appuyez pour ouvrir le QR code en mode plein écran", "rename" : "Renommer", "choose_account" : "Choisir le compte", @@ -685,6 +685,11 @@ "arrive_in_this_address" : "${currency} ${tag}arrivera à cette adresse", "do_not_send": "Ne pas envoyer", "error_dialog_content": "Oups, nous avons rencontré une erreur.\n\nMerci d'envoyer le rapport d'erreur à notre équipe d'assistance afin de nous permettre d'améliorer l'application.", + "scan_qr_code": "Scannez le code QR", + "cold_or_recover_wallet": "Ajoutez un cold wallet ou récupérez un paper wallet", + "please_wait": "S'il vous plaît, attendez", + "sweeping_wallet": "Portefeuille de balayage", + "sweeping_wallet_alert": "Cela ne devrait pas prendre longtemps. NE QUITTEZ PAS CET ÉCRAN OU LES FONDS BALAYÉS POURRAIENT ÊTRE PERDUS", "decimal_places_error": "Trop de décimales", "edit_node": "Modifier le nœud", "frozen_balance": "Équilibre gelé", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 6a9a97880..8b505cff2 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -150,7 +150,7 @@ "receive_amount" : "रकम", "subaddresses" : "उप पते", "addresses" : "पतों", - "scan_qr_code" : "पता प्राप्त करने के लिए QR कोड स्कैन करें", + "scan_qr_code_to_get_address" : "पता प्राप्त करने के लिए QR कोड स्कैन करें", "qr_fullscreen" : "फ़ुल स्क्रीन क्यूआर कोड खोलने के लिए टैप करें", "rename" : "नाम बदलें", "choose_account" : "खाता चुनें", @@ -685,6 +685,11 @@ "arrive_in_this_address" : "${currency} ${tag}इस पते पर पहुंचेंगे", "do_not_send": "मत भेजो", "error_dialog_content": "ओह, हमसे कुछ गड़बड़ी हुई है.\n\nएप्लिकेशन को बेहतर बनाने के लिए कृपया क्रैश रिपोर्ट हमारी सहायता टीम को भेजें।", + "scan_qr_code": "स्कैन क्यू आर कोड", + "cold_or_recover_wallet": "कोल्ड वॉलेट जोड़ें या पेपर वॉलेट पुनर्प्राप्त करें", + "please_wait": "कृपया प्रतीक्षा करें", + "sweeping_wallet": "स्वीपिंग वॉलेट", + "sweeping_wallet_alert": "इसमें अधिक समय नहीं लगना चाहिए। इस स्क्रीन को न छोड़ें या स्वैप्ट फंड खो सकते हैं", "decimal_places_error": "बहुत अधिक दशमलव स्थान", "edit_node": "नोड संपादित करें", "frozen_balance": "जमे हुए संतुलन", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 10974efa2..f9b5fd0f2 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -150,7 +150,7 @@ "receive_amount" : "Iznos", "subaddresses" : "Podadrese", "addresses" : "Adrese", - "scan_qr_code" : "Skeniraj QR kod za dobivanje adrese", + "scan_qr_code_to_get_address" : "Skeniraj QR kod za dobivanje adrese", "qr_fullscreen" : "Dodirnite za otvaranje QR koda preko cijelog zaslona", "rename" : "Preimenuj", "choose_account" : "Odaberi račun", @@ -685,6 +685,11 @@ "arrive_in_this_address" : "${currency} ${tag}će stići na ovu adresu", "do_not_send": "Ne šalji", "error_dialog_content": "Ups, imamo grešku.\n\nPošaljite izvješće o padu našem timu za podršku kako bismo poboljšali aplikaciju.", + "scan_qr_code": "Skenirajte QR kod", + "cold_or_recover_wallet": "Dodajte hladni novčanik ili povratite papirnati novčanik", + "please_wait": "Molimo pričekajte", + "sweeping_wallet": "Čisti novčanik", + "sweeping_wallet_alert": "Ovo ne bi trebalo dugo trajati. NE NAPUŠTAJTE OVAJ ZASLON INAČE SE POBREŠENA SREDSTVA MOGU IZGUBITI", "decimal_places_error": "Previše decimalnih mjesta", "edit_node": "Uredi čvor", "frozen_balance": "Zamrznuti saldo", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index b8881e2f6..95ce251bd 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -150,7 +150,7 @@ "receive_amount" : "Ammontare", "subaddresses" : "Sottoindirizzi", "addresses" : "Indirizzi", - "scan_qr_code" : "Scansiona il codice QR per ottenere l'indirizzo", + "scan_qr_code_to_get_address" : "Scansiona il codice QR per ottenere l'indirizzo", "qr_fullscreen" : "Tocca per aprire il codice QR a schermo intero", "rename" : "Rinomina", "choose_account" : "Scegli account", @@ -685,6 +685,11 @@ "arrive_in_this_address" : "${currency} ${tag}arriverà a questo indirizzo", "do_not_send": "Non inviare", "error_dialog_content": "Spiacenti, abbiamo riscontrato un errore.\n\nSi prega di inviare il rapporto sull'arresto anomalo al nostro team di supporto per migliorare l'applicazione.", + "scan_qr_code": "Scansiona il codice QR", + "cold_or_recover_wallet": "Aggiungi un cold wallet o recupera un paper wallet", + "please_wait": "Attendere prego", + "sweeping_wallet": "Portafoglio ampio", + "sweeping_wallet_alert": "Questo non dovrebbe richiedere molto tempo. NON LASCIARE QUESTA SCHERMATA O I FONDI SPAZZATI POTREBBERO ANDARE PERSI", "decimal_places_error": "Troppe cifre decimali", "edit_node": "Modifica nodo", "frozen_balance": "Equilibrio congelato", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 97baf1281..c1b78cc64 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -150,7 +150,7 @@ "receive_amount" : "量", "subaddresses" : "サブアドレス", "addresses" : "住所", - "scan_qr_code" : "QRコードをスキャンして住所を取得します", + "scan_qr_code_to_get_address" : "QRコードをスキャンして住所を取得します", "qr_fullscreen" : "タップして全画面QRコードを開く", "rename" : "リネーム", "choose_account" : "アカウントを選択", @@ -685,6 +685,11 @@ "arrive_in_this_address" : "${currency} ${tag}はこの住所に到着します", "do_not_send": "送信しない", "error_dialog_content": "エラーが発生しました。\n\nアプリケーションを改善するために、クラッシュ レポートをサポート チームに送信してください。", + "scan_qr_code": "QRコードをスキャン", + "cold_or_recover_wallet": "コールド ウォレットを追加するか、ペーパー ウォレットを復元する", + "please_wait": "お待ちください", + "sweeping_wallet": "スイープウォレット", + "sweeping_wallet_alert": "これには時間がかかりません。この画面から離れないでください。そうしないと、スイープ ファンドが失われる可能性があります", "decimal_places_error": "小数点以下の桁数が多すぎる", "edit_node": "ノードを編集", "frozen_balance": "冷凍残高", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 50994a752..f2e03ed55 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -150,7 +150,7 @@ "receive_amount" : "양", "subaddresses" : "하위 주소", "addresses" : "구애", - "scan_qr_code" : "QR 코드를 스캔하여 주소를 얻습니다.", + "scan_qr_code_to_get_address" : "QR 코드를 스캔하여 주소를 얻습니다.", "qr_fullscreen" : "전체 화면 QR 코드를 열려면 탭하세요.", "rename" : "이름 바꾸기", "choose_account" : "계정을 선택하십시오", @@ -685,6 +685,11 @@ "arrive_in_this_address" : "${currency} ${tag}이(가) 이 주소로 도착합니다", "do_not_send": "보내지 마세요", "error_dialog_content": "죄송합니다. 오류가 발생했습니다.\n\n응용 프로그램을 개선하려면 지원 팀에 충돌 보고서를 보내주십시오.", + "scan_qr_code": "QR 코드 스캔", + "cold_or_recover_wallet": "콜드 지갑 추가 또는 종이 지갑 복구", + "please_wait": "기다리세요", + "sweeping_wallet": "스위핑 지갑", + "sweeping_wallet_alert": "오래 걸리지 않습니다. 이 화면을 떠나지 마십시오. 그렇지 않으면 스웹트 자금이 손실될 수 있습니다.", "decimal_places_error": "소수점 이하 자릿수가 너무 많습니다.", "edit_node": "노드 편집", "frozen_balance": "얼어붙은 균형", diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index e93b8a0b9..c314dfb65 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -150,7 +150,7 @@ "receive_amount" : "ပမာဏ", "subaddresses" : "လိပ်စာများ", "addresses" : "လိပ်စာများ", - "scan_qr_code" : "လိပ်စာရယူရန် QR ကုဒ်ကို စကင်န်ဖတ်ပါ။", + "scan_qr_code_to_get_address" : "လိပ်စာရယူရန် QR ကုဒ်ကို စကင်န်ဖတ်ပါ။", "qr_fullscreen" : "မျက်နှာပြင်အပြည့် QR ကုဒ်ကိုဖွင့်ရန် တို့ပါ။", "rename" : "အမည်ပြောင်းပါ။", "choose_account" : "အကောင့်ကို ရွေးပါ။", @@ -685,6 +685,11 @@ "arrive_in_this_address" : "${currency} ${tag}ဤလိပ်စာသို့ ရောက်ရှိပါမည်။", "do_not_send": "မပို့ပါနှင့်", "error_dialog_content": "အိုး၊ ကျွန်ုပ်တို့တွင် အမှားအယွင်းအချို့ရှိသည်။\n\nအပလီကေးရှင်းကို ပိုမိုကောင်းမွန်စေရန်အတွက် ပျက်စီးမှုအစီရင်ခံစာကို ကျွန်ုပ်တို့၏ပံ့ပိုးကူညီရေးအဖွဲ့ထံ ပေးပို့ပါ။", + "scan_qr_code": "QR ကုဒ်ကို စကင်န်ဖတ်ပါ။", + "cold_or_recover_wallet": "အေးသောပိုက်ဆံအိတ်ထည့်ပါ သို့မဟုတ် စက္ကူပိုက်ဆံအိတ်ကို ပြန်ယူပါ။", + "please_wait": "ကျေးဇူးပြုပြီးခဏစောင့်ပါ", + "sweeping_wallet": "ိုက်ဆံအိတ် တံမြက်လှည်း", + "sweeping_wallet_alert": "ဒါက ကြာကြာမခံသင့်ပါဘူး။ ဤစခရင်ကို ချန်မထားပါနှင့် သို့မဟုတ် ထုတ်ယူထားသော ရန်ပုံငွေများ ဆုံးရှုံးနိုင်သည်", "decimal_places_error": "ဒဿမနေရာများ များလွန်းသည်။", "edit_node": "Node ကို တည်းဖြတ်ပါ။", "frozen_balance": "ေးခဲမှူ", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 1561bb3d4..734630c96 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -151,7 +151,7 @@ "subaddresses" : "Subadressen", "rename" : "Hernoemen", "addresses" : "Adressen", - "scan_qr_code" : "Scan de QR-code om het adres te krijgen", + "scan_qr_code_to_get_address" : "Scan de QR-code om het adres te krijgen", "qr_fullscreen" : "Tik om de QR-code op volledig scherm te openen", "choose_account" : "Kies account", "create_new_account" : "Creëer een nieuw account", @@ -685,6 +685,11 @@ "arrive_in_this_address" : "${currency} ${tag}komt aan op dit adres", "do_not_send": "Niet sturen", "error_dialog_content": "Oeps, er is een fout opgetreden.\n\nStuur het crashrapport naar ons ondersteuningsteam om de applicatie te verbeteren.", + "scan_qr_code": "Scan QR-code", + "cold_or_recover_wallet": "Voeg een cold wallet toe of herstel een paper wallet", + "please_wait": "Even geduld aub", + "sweeping_wallet": "Vegende portemonnee", + "sweeping_wallet_alert": "Dit duurt niet lang. VERLAAT DIT SCHERM NIET, ANDERS KAN HET SWEPT-GELD VERLOREN WORDEN", "decimal_places_error": "Te veel decimalen", "edit_node": "Knooppunt bewerken", "frozen_balance": "Bevroren saldo", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index ea20d4da7..23b7bbb61 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -150,7 +150,7 @@ "receive_amount" : "Ilość", "subaddresses" : "Podadresy", "addresses" : "Adresy", - "scan_qr_code" : "Zeskanuj kod QR, aby uzyskać adres", + "scan_qr_code_to_get_address" : "Zeskanuj kod QR, aby uzyskać adres", "qr_fullscreen" : "Dotknij, aby otworzyć pełnoekranowy kod QR", "rename" : "Zmień nazwę", "choose_account" : "Wybierz konto", @@ -685,6 +685,11 @@ "arrive_in_this_address" : "${currency} ${tag}dotrze na ten adres", "do_not_send": "Nie wysyłaj", "error_dialog_content": "Ups, wystąpił błąd.\n\nPrześlij raport o awarii do naszego zespołu wsparcia, aby ulepszyć aplikację.", + "scan_qr_code": "Skanowania QR code", + "cold_or_recover_wallet": "Dodaj zimny portfel lub odzyskaj portfel papierowy", + "please_wait": "Proszę czekać", + "sweeping_wallet": "Zamiatanie portfela", + "sweeping_wallet_alert": "To nie powinno zająć dużo czasu. NIE WYCHODŹ Z TEGO EKRANU, W PRZECIWNYM WYPADKU MOŻE ZOSTAĆ UTRACONA ŚRODKI", "decimal_places_error": "Za dużo miejsc dziesiętnych", "edit_node": "Edytuj węzeł", "frozen_balance": "Zamrożona równowaga", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 9f088fcef..0346b1ff4 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -150,7 +150,7 @@ "receive_amount" : "Quantia", "subaddresses" : "Sub-endereços", "addresses" : "Endereços", - "scan_qr_code" : "Digitalize o código QR para obter o endereço", + "scan_qr_code_to_get_address" : "Digitalize o código QR para obter o endereço", "qr_fullscreen" : "Toque para abrir o código QR em tela cheia", "rename" : "Renomear", "choose_account" : "Escolha uma conta", @@ -684,6 +684,11 @@ "arrive_in_this_address" : "${currency} ${tag}chegará neste endereço", "do_not_send": "não envie", "error_dialog_content": "Ops, houve algum erro.\n\nPor favor, envie o relatório de falha para nossa equipe de suporte para melhorar o aplicativo.", + "scan_qr_code": "Escanear código QR", + "cold_or_recover_wallet": "Adicione uma cold wallet ou recupere uma paper wallet", + "please_wait": "Por favor, aguarde", + "sweeping_wallet": "Carteira varrendo", + "sweeping_wallet_alert": "To nie powinno zająć dużo czasu. NIE WYCHODŹ Z TEGO EKRANU, W PRZECIWNYM WYPADKU MOŻE ZOSTAĆ UTRACONA ŚRODKI", "decimal_places_error": "Muitas casas decimais", "edit_node": "Editar nó", "frozen_balance": "Saldo Congelado", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 53dc31b21..33058a13e 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -150,7 +150,7 @@ "receive_amount" : "Сумма", "subaddresses" : "Субадреса", "addresses" : "Адреса", - "scan_qr_code" : "Отсканируйте QR-код для получения адреса", + "scan_qr_code_to_get_address" : "Отсканируйте QR-код для получения адреса", "qr_fullscreen" : "Нажмите, чтобы открыть полноэкранный QR-код", "rename" : "Переименовать", "choose_account" : "Выберите аккаунт", @@ -685,6 +685,11 @@ "arrive_in_this_address" : "${currency} ${tag}придет на этот адрес", "do_not_send": "Не отправлять", "error_dialog_content": "Ой, у нас какая-то ошибка.\n\nПожалуйста, отправьте отчет о сбое в нашу службу поддержки, чтобы сделать приложение лучше.", + "scan_qr_code": "Сканировать QR-код", + "cold_or_recover_wallet": "Добавьте холодный кошелек или восстановите бумажный кошелек", + "please_wait": "Пожалуйста, подождите", + "sweeping_wallet": "Подметание кошелька", + "sweeping_wallet_alert": "Это не должно занять много времени. НЕ ПОКИДАЙТЕ ЭТОТ ЭКРАН, ИНАЧЕ ВЫЧИСЛЕННЫЕ СРЕДСТВА МОГУТ БЫТЬ ПОТЕРЯНЫ", "decimal_places_error": "Слишком много десятичных знаков", "edit_node": "Редактировать узел", "frozen_balance": "Замороженный баланс", diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index ea7b2afde..f1cc320ac 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -150,7 +150,7 @@ "receive_amount" : "จำนวน", "subaddresses" : "ที่อยู่ย่อย", "addresses" : "ที่อยู่", - "scan_qr_code" : "สแกน QR code เพื่อรับที่อยู่", + "scan_qr_code_to_get_address" : "สแกน QR code เพื่อรับที่อยู่", "qr_fullscreen" : "แตะเพื่อเปิดหน้าจอ QR code แบบเต็มจอ", "rename" : "เปลี่ยนชื่อ", "choose_account" : "เลือกบัญชี", @@ -683,6 +683,11 @@ "arrive_in_this_address" : "${currency} ${tag}จะมาถึงที่อยู่นี้", "do_not_send": "อย่าส่ง", "error_dialog_content": "อ๊ะ เราพบข้อผิดพลาดบางอย่าง\n\nโปรดส่งรายงานข้อขัดข้องไปยังทีมสนับสนุนของเราเพื่อปรับปรุงแอปพลิเคชันให้ดียิ่งขึ้น", + "scan_qr_code": "สแกนรหัส QR", + "cold_or_recover_wallet": "เพิ่มกระเป๋าเงินเย็นหรือกู้คืนกระเป๋าเงินกระดาษ", + "please_wait": "โปรดรอ", + "sweeping_wallet": "กวาดกระเป๋าสตางค์", + "sweeping_wallet_alert": "การดำเนินการนี้ใช้เวลาไม่นาน อย่าออกจากหน้าจอนี้ มิฉะนั้นเงินที่กวาดไปอาจสูญหาย", "decimal_places_error": "ทศนิยมมากเกินไป", "edit_node": "แก้ไขโหนด", "frozen_balance": "ยอดคงเหลือแช่แข็ง", diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 7cb72e57a..675fbb5aa 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -150,7 +150,7 @@ "receive_amount" : "Miktar", "subaddresses" : "Alt adresler", "addresses" : "Adresler", - "scan_qr_code" : "Adresi getirmek için QR kodunu tara", + "scan_qr_code_to_get_address" : "Adresi getirmek için QR kodunu tara", "qr_fullscreen" : "QR kodunu tam ekranda açmak için dokun", "rename" : "Yeniden adlandır", "choose_account" : "Hesabı seç", @@ -685,6 +685,11 @@ "arrive_in_this_address" : "${currency} ${tag}bu adrese ulaşacak", "do_not_send": "Gönderme", "error_dialog_content": "Hay aksi, bir hatamız var.\n\nUygulamayı daha iyi hale getirmek için lütfen kilitlenme raporunu destek ekibimize gönderin.", + "scan_qr_code": "QR kodunu tarayın", + "cold_or_recover_wallet": "Soğuk bir cüzdan ekleyin veya bir kağıt cüzdanı kurtarın", + "please_wait": "Lütfen bekleyin", + "sweeping_wallet": "Süpürme cüzdanı", + "sweeping_wallet_alert": "Bu uzun sürmemeli. BU EKRANDAN BIRAKMAYIN YOKSA SÜPÜRÜLEN FONLAR KAYBOLABİLİR", "decimal_places_error": "Çok fazla ondalık basamak", "edit_node": "Düğümü Düzenle", "frozen_balance": "Dondurulmuş Bakiye", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index ac3d32dfd..affdda645 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -150,7 +150,7 @@ "receive_amount" : "Сума", "subaddresses" : "Субадреси", "addresses" : "Адреси", - "scan_qr_code" : "Скануйте QR-код для одержання адреси", + "scan_qr_code_to_get_address" : "Скануйте QR-код для одержання адреси", "qr_fullscreen" : "Торкніться, щоб відкрити QR-код на весь екран", "rename" : "Перейменувати", "choose_account" : "Оберіть акаунт", @@ -684,6 +684,11 @@ "arrive_in_this_address" : "${currency} ${tag}надійде на цю адресу", "do_not_send": "Не надсилайте", "error_dialog_content": "На жаль, ми отримали помилку.\n\nБудь ласка, надішліть звіт про збій нашій команді підтримки, щоб покращити додаток.", + "scan_qr_code": "Відскануйте QR-код", + "cold_or_recover_wallet": "Додайте холодний гаманець або відновіть паперовий гаманець", + "please_wait": "Будь ласка, зачекайте", + "sweeping_wallet": "Підмітаня гаманця", + "sweeping_wallet_alert": "Це не повинно зайняти багато часу. НЕ ЗАЛИШАЙТЕ ЦЬОГО ЕКРАНУ, АБО КОШТИ МОЖУТЬ БУТИ ВТРАЧЕНІ", "decimal_places_error": "Забагато знаків після коми", "edit_node": "Редагувати вузол", "frozen_balance": "Заморожений баланс", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index ea1804b58..10a7d89f2 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -150,7 +150,7 @@ "receive_amount" : "金额", "subaddresses" : "子地址", "addresses" : "地址", - "scan_qr_code" : "扫描二维码获取地址", + "scan_qr_code_to_get_address" : "扫描二维码获取地址", "qr_fullscreen" : "点击打开全屏二维码", "rename" : "重命名", "choose_account" : "选择账户", @@ -684,6 +684,11 @@ "arrive_in_this_address" : "${currency} ${tag}将到达此地址", "do_not_send": "不要发送", "error_dialog_content": "糟糕,我们遇到了一些错误。\n\n请将崩溃报告发送给我们的支持团队,以改进应用程序。", + "scan_qr_code": "扫描二维码", + "cold_or_recover_wallet": "添加冷钱包或恢复纸钱包", + "please_wait": "请稍等", + "sweeping_wallet": "扫一扫钱包", + "sweeping_wallet_alert": "\n这应该不会花很长时间。请勿离开此屏幕,否则可能会丢失所掠取的资金", "decimal_places_error": "小数位太多", "edit_node": "编辑节点", "frozen_balance": "冻结余额", From 9d47e0e67ca429fee6fde61ade98b1eb2e8ac4bd Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Mon, 24 Apr 2023 14:02:17 +0200 Subject: [PATCH 11/39] V4.6.3_1.3.3 (#893) * - Update App versions - Add release notes * Modify Monero.com release notes * Fix issues with Frozen balance for Monero.com build * Update cake wallet app build number --- assets/text/Monerocom_Release_Notes.txt | 26 ++++++++--------- assets/text/Release_Notes.txt | 29 ++++++++++--------- cw_core/lib/balance.dart | 2 ++ .../dashboard/balance_view_model.dart | 10 +------ .../restore/wallet_restore_from_qr_code.dart | 3 -- scripts/android/app_env.sh | 8 ++--- scripts/ios/app_env.sh | 6 ++-- scripts/macos/app_env.sh | 2 +- 8 files changed, 40 insertions(+), 46 deletions(-) diff --git a/assets/text/Monerocom_Release_Notes.txt b/assets/text/Monerocom_Release_Notes.txt index 6a091fc90..6c1994ede 100644 --- a/assets/text/Monerocom_Release_Notes.txt +++ b/assets/text/Monerocom_Release_Notes.txt @@ -1,13 +1,13 @@ -Added Fixed Rate for exchanges (enter the "receive" amount on the exchange page to get the fixed rate)Added Fixed Rate for exchanges -WWEE(enter the "receive" amount on the exchange page to get the fixed rate)Added Fixed Rate for exchanges (enter the "receive" amount on the exchange page to get the fixed rate)Added Fixed Rate for exchanges (enter the "receive" amount on the exchange page to get the fixed rate) -Changed algorithm for choosing of change address for BTC and LTC electrum wallets -Changed algorithm for choosing of change address for BTC and LTC electrum wallets -Keep screen awake while the synchronization function -Added Fixed Rate for exchanges (enter the "receive" amount on the exchange page to get the fixed rate) -Changed algorithm for choosing of change address for BTC and LTC electrum wallets -Changed algorithm for choosing of change address for BTC and LTC electrum wallets -Keep screen awake while the synchronization function -Added Fixed Rate for exchanges (enter the "receive" amount on the exchange page to get the fixed rate) -Changed algorithm for choosing of change address for BTC and LTC electrum wallets -Changed algorithm for choosing of change address for BTC and LTC electrum wallets -Keep screen awake while the synchronizatio \ No newline at end of file +This update includes a HUGE number of usability improvements! +Type a fiat amount in the receive screen +Easily restore wallets from QR code +Pay Bitcoin Lightning invoices in exchange +Optionally disable the marketplace in display settings +Better warning messages if trying to exchange outside the min/max limits +More address flexibility when exchanging the currently-active wallet asset +Modernized the seed language selection picker +Adjusted pickers to resize if the keyboard is active +Improved accessibility +Click to copy additional fields in the exchange checkout +Fix padding on some devices +Bug fixes and code refactoring \ No newline at end of file diff --git a/assets/text/Release_Notes.txt b/assets/text/Release_Notes.txt index 6a091fc90..6e63d3372 100644 --- a/assets/text/Release_Notes.txt +++ b/assets/text/Release_Notes.txt @@ -1,13 +1,16 @@ -Added Fixed Rate for exchanges (enter the "receive" amount on the exchange page to get the fixed rate)Added Fixed Rate for exchanges -WWEE(enter the "receive" amount on the exchange page to get the fixed rate)Added Fixed Rate for exchanges (enter the "receive" amount on the exchange page to get the fixed rate)Added Fixed Rate for exchanges (enter the "receive" amount on the exchange page to get the fixed rate) -Changed algorithm for choosing of change address for BTC and LTC electrum wallets -Changed algorithm for choosing of change address for BTC and LTC electrum wallets -Keep screen awake while the synchronization function -Added Fixed Rate for exchanges (enter the "receive" amount on the exchange page to get the fixed rate) -Changed algorithm for choosing of change address for BTC and LTC electrum wallets -Changed algorithm for choosing of change address for BTC and LTC electrum wallets -Keep screen awake while the synchronization function -Added Fixed Rate for exchanges (enter the "receive" amount on the exchange page to get the fixed rate) -Changed algorithm for choosing of change address for BTC and LTC electrum wallets -Changed algorithm for choosing of change address for BTC and LTC electrum wallets -Keep screen awake while the synchronizatio \ No newline at end of file +This update includes a HUGE number of usability improvements! +BTC/LTC coin control enhancements and bugfixes; easily look up Ordinals +Type a fiat amount in the receive screen +New Onramper Buy widget +Easily restore wallets from QR code +Substantially better reliability for seeing incoming, unconfirmed BTC/LTC transactions +Pay Bitcoin Lightning invoices in exchange +Optionally disable the marketplace in display settings +Better warning messages if trying to exchange outside the min/max limits +More address flexibility when exchanging the currently-active wallet asset +Modernized the seed language selection picker +Adjusted pickers to resize if the keyboard is active +Improved accessibility +Click to copy additional fields in the exchange checkout +Fix padding on some devices +Bug fixes and code refactoring \ No newline at end of file diff --git a/cw_core/lib/balance.dart b/cw_core/lib/balance.dart index cf98f9e0f..6145411c4 100644 --- a/cw_core/lib/balance.dart +++ b/cw_core/lib/balance.dart @@ -8,4 +8,6 @@ abstract class Balance { String get formattedAvailableBalance; String get formattedAdditionalBalance; + + String get formattedFrozenBalance => ''; } diff --git a/lib/view_model/dashboard/balance_view_model.dart b/lib/view_model/dashboard/balance_view_model.dart index 3b9dcce55..810d8e415 100644 --- a/lib/view_model/dashboard/balance_view_model.dart +++ b/lib/view_model/dashboard/balance_view_model.dart @@ -1,9 +1,5 @@ -import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/entities/fiat_api_mode.dart'; -import 'package:cw_bitcoin/bitcoin_amount_format.dart'; -import 'package:cw_bitcoin/electrum_balance.dart'; import 'package:cw_core/transaction_history.dart'; -import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/balance.dart'; import 'package:cw_core/crypto_currency.dart'; @@ -15,7 +11,6 @@ import 'package:cake_wallet/entities/calculate_fiat_amount.dart'; import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart'; -import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; part 'balance_view_model.g.dart'; @@ -361,9 +356,6 @@ abstract class BalanceViewModelBase with Store { } } - - String getFormattedFrozenBalance(Balance walletBalance) => - walletBalance is ElectrumBalance ? walletBalance.formattedFrozenBalance : ''; - + String getFormattedFrozenBalance(Balance walletBalance) => walletBalance.formattedFrozenBalance; } diff --git a/lib/view_model/restore/wallet_restore_from_qr_code.dart b/lib/view_model/restore/wallet_restore_from_qr_code.dart index 241b2d3fd..9ebf01429 100644 --- a/lib/view_model/restore/wallet_restore_from_qr_code.dart +++ b/lib/view_model/restore/wallet_restore_from_qr_code.dart @@ -1,11 +1,8 @@ -import 'package:cake_wallet/core/address_validator.dart'; import 'package:cake_wallet/core/seed_validator.dart'; -import 'package:cake_wallet/entities/mnemonic_item.dart'; import 'package:cake_wallet/entities/parse_address_from_domain.dart'; import 'package:cake_wallet/entities/qr_scanner.dart'; import 'package:cake_wallet/view_model/restore/restore_mode.dart'; import 'package:cake_wallet/view_model/restore/restore_wallet.dart'; -import 'package:cw_bitcoin/bitcoin_mnemonic.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/cupertino.dart'; diff --git a/scripts/android/app_env.sh b/scripts/android/app_env.sh index 298bb69f4..fa822f0af 100644 --- a/scripts/android/app_env.sh +++ b/scripts/android/app_env.sh @@ -14,14 +14,14 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN) APP_ANDROID_TYPE=$1 MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="1.3.2" -MONERO_COM_BUILD_NUMBER=42 +MONERO_COM_VERSION="1.3.3" +MONERO_COM_BUILD_NUMBER=43 MONERO_COM_BUNDLE_ID="com.monero.app" MONERO_COM_PACKAGE="com.monero.app" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="4.6.2" -CAKEWALLET_BUILD_NUMBER=151 +CAKEWALLET_VERSION="4.6.3" +CAKEWALLET_BUILD_NUMBER=154 CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet" CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet" diff --git a/scripts/ios/app_env.sh b/scripts/ios/app_env.sh index 3b700df46..ca2bf1f12 100644 --- a/scripts/ios/app_env.sh +++ b/scripts/ios/app_env.sh @@ -13,13 +13,13 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN) APP_IOS_TYPE=$1 MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="1.3.2" -MONERO_COM_BUILD_NUMBER=40 +MONERO_COM_VERSION="1.3.3" +MONERO_COM_BUILD_NUMBER=41 MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" CAKEWALLET_VERSION="4.6.3" -CAKEWALLET_BUILD_NUMBER=146 +CAKEWALLET_BUILD_NUMBER=148 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" HAVEN_NAME="Haven" diff --git a/scripts/macos/app_env.sh b/scripts/macos/app_env.sh index 8cb2d86b9..d788fed1c 100755 --- a/scripts/macos/app_env.sh +++ b/scripts/macos/app_env.sh @@ -16,7 +16,7 @@ fi CAKEWALLET_NAME="Cake Wallet" CAKEWALLET_VERSION="1.0.2" -CAKEWALLET_BUILD_NUMBER=12 +CAKEWALLET_BUILD_NUMBER=14 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" if ! [[ " ${TYPES[*]} " =~ " ${APP_MACOS_TYPE} " ]]; then From 82b513d1f8c0a253ede740843bd5c07f8d887c65 Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Wed, 26 Apr 2023 14:59:27 +0200 Subject: [PATCH 12/39] V4.6.3 bug fixes (#896) * Add blocking screenshots native function to Monero.com [skip ci] * Fix seeds QR data * Update app versions and release notes [skip ci] * Update only build number for macos since it wasn't released yet [skip ci] * Fix adding sub-address * Fix Accounts Popup UI * Update app versions and release notes [skip ci] * Fix add/edit node issue * Update app versions and release notes [skip ci] * Fix input fields focus/keyboard issues * Update app versions and release notes [skip ci] --- .../java/com/monero/app/MainActivity.java | 11 +- assets/text/Monerocom_Release_Notes.txt | 17 +- assets/text/Release_Notes.txt | 20 +- ios/Podfile.lock | 6 + lib/src/screens/base_page.dart | 16 +- .../dashboard/widgets/address_page.dart | 33 ++- lib/src/screens/exchange/exchange_page.dart | 33 ++- .../monero_account_list_page.dart | 253 +++++++++--------- lib/src/screens/send/send_page.dart | 33 ++- .../address_edit_or_create_page.dart | 26 +- .../screens/wallet_keys/wallet_keys_page.dart | 4 +- scripts/android/app_env.sh | 8 +- scripts/ios/app_env.sh | 8 +- scripts/macos/app_env.sh | 2 +- 14 files changed, 282 insertions(+), 188 deletions(-) diff --git a/android/app/src/main/java/com/monero/app/MainActivity.java b/android/app/src/main/java/com/monero/app/MainActivity.java index f9e4f0882..73914c43c 100644 --- a/android/app/src/main/java/com/monero/app/MainActivity.java +++ b/android/app/src/main/java/com/monero/app/MainActivity.java @@ -23,6 +23,7 @@ import java.security.SecureRandom; public class MainActivity extends FlutterFragmentActivity { final String UTILS_CHANNEL = "com.cake_wallet/native_utils"; final int UNSTOPPABLE_DOMAIN_MIN_VERSION_SDK = 24; + boolean isAppSecure = false; @Override public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { @@ -48,13 +49,21 @@ public class MainActivity extends FlutterFragmentActivity { handler.post(() -> result.success(bytes)); break; case "getUnstoppableDomainAddress": - int version = Build.VERSION.SDK_INT; + int version = Build.VERSION.SDK_INT; if (version >= UNSTOPPABLE_DOMAIN_MIN_VERSION_SDK) { getUnstoppableDomainAddress(call, result); } else { handler.post(() -> result.success("")); } break; + case "setIsAppSecure": + isAppSecure = call.argument("isAppSecure"); + if (isAppSecure) { + getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE); + } else { + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE); + } + break; default: handler.post(() -> result.notImplemented()); } diff --git a/assets/text/Monerocom_Release_Notes.txt b/assets/text/Monerocom_Release_Notes.txt index 6c1994ede..d9acd464f 100644 --- a/assets/text/Monerocom_Release_Notes.txt +++ b/assets/text/Monerocom_Release_Notes.txt @@ -1,13 +1,4 @@ -This update includes a HUGE number of usability improvements! -Type a fiat amount in the receive screen -Easily restore wallets from QR code -Pay Bitcoin Lightning invoices in exchange -Optionally disable the marketplace in display settings -Better warning messages if trying to exchange outside the min/max limits -More address flexibility when exchanging the currently-active wallet asset -Modernized the seed language selection picker -Adjusted pickers to resize if the keyboard is active -Improved accessibility -Click to copy additional fields in the exchange checkout -Fix padding on some devices -Bug fixes and code refactoring \ No newline at end of file +Fix for QR codes +Fix for creating sub-addresses +Fix Add/Edit nodes +Fix issues with text/amount fields \ No newline at end of file diff --git a/assets/text/Release_Notes.txt b/assets/text/Release_Notes.txt index 6e63d3372..d9acd464f 100644 --- a/assets/text/Release_Notes.txt +++ b/assets/text/Release_Notes.txt @@ -1,16 +1,4 @@ -This update includes a HUGE number of usability improvements! -BTC/LTC coin control enhancements and bugfixes; easily look up Ordinals -Type a fiat amount in the receive screen -New Onramper Buy widget -Easily restore wallets from QR code -Substantially better reliability for seeing incoming, unconfirmed BTC/LTC transactions -Pay Bitcoin Lightning invoices in exchange -Optionally disable the marketplace in display settings -Better warning messages if trying to exchange outside the min/max limits -More address flexibility when exchanging the currently-active wallet asset -Modernized the seed language selection picker -Adjusted pickers to resize if the keyboard is active -Improved accessibility -Click to copy additional fields in the exchange checkout -Fix padding on some devices -Bug fixes and code refactoring \ No newline at end of file +Fix for QR codes +Fix for creating sub-addresses +Fix Add/Edit nodes +Fix issues with text/amount fields \ No newline at end of file diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 3a23117b2..d0e05f5dd 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -115,6 +115,8 @@ PODS: - Flutter - flutter_secure_storage (3.3.1): - Flutter + - in_app_review (0.2.0): + - Flutter - local_auth_ios (0.0.1): - Flutter - MTBBarcodeScanner (5.0.11) @@ -165,6 +167,7 @@ DEPENDENCIES: - flutter_inappwebview (from `.symlinks/plugins/flutter_inappwebview/ios`) - flutter_mailer (from `.symlinks/plugins/flutter_mailer/ios`) - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) + - in_app_review (from `.symlinks/plugins/in_app_review/ios`) - local_auth_ios (from `.symlinks/plugins/local_auth_ios/ios`) - package_info (from `.symlinks/plugins/package_info/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/ios`) @@ -220,6 +223,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/flutter_mailer/ios" flutter_secure_storage: :path: ".symlinks/plugins/flutter_secure_storage/ios" + in_app_review: + :path: ".symlinks/plugins/in_app_review/ios" local_auth_ios: :path: ".symlinks/plugins/local_auth_ios/ios" package_info: @@ -260,6 +265,7 @@ SPEC CHECKSUMS: flutter_inappwebview: bfd58618f49dc62f2676de690fc6dcda1d6c3721 flutter_mailer: 2ef5a67087bc8c6c4cefd04a178bf1ae2c94cd83 flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec + in_app_review: 318597b3a06c22bb46dc454d56828c85f444f99d local_auth_ios: c6cf091ded637a88f24f86a8875d8b0f526e2605 MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c diff --git a/lib/src/screens/base_page.dart b/lib/src/screens/base_page.dart index 28327ad39..51f8c1ad2 100644 --- a/lib/src/screens/base_page.dart +++ b/lib/src/screens/base_page.dart @@ -1,5 +1,4 @@ import 'package:cake_wallet/themes/theme_base.dart'; -import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/palette.dart'; @@ -21,8 +20,6 @@ abstract class BasePage extends StatelessWidget { String? get title => null; - bool get canUseCloseIcon => false; - Color get backgroundLightColor => Colors.white; Color get backgroundDarkColor => PaletteDark.backgroundColor; @@ -53,27 +50,22 @@ abstract class BasePage extends StatelessWidget { final _backButton = Icon(Icons.arrow_back_ios, color: titleColor ?? Theme.of(context).primaryTextTheme.headline6!.color!, size: 16,); - final _closeButton = currentTheme.type == ThemeType.dark - ? closeButtonImageDarkTheme : closeButtonImage; - - bool isMobileView = ResponsiveLayoutUtil.instance.isMobile(context); return MergeSemantics( child: SizedBox( - height: isMobileView ? 37 : 45, - width: isMobileView ? 37 : 45, + height: 37, + width: 37, child: ButtonTheme( minWidth: double.minPositive, child: Semantics( - label: canUseCloseIcon && !isMobileView ? 'Close' : 'Back', + label: 'Back', child: TextButton( style: ButtonStyle( overlayColor: MaterialStateColor.resolveWith( (states) => Colors.transparent), ), onPressed: () => onClose(context), - child: - canUseCloseIcon && !isMobileView ? _closeButton : _backButton, + child: _backButton, ), ), ), diff --git a/lib/src/screens/dashboard/widgets/address_page.dart b/lib/src/screens/dashboard/widgets/address_page.dart index cdaa22673..82430f0c6 100644 --- a/lib/src/screens/dashboard/widgets/address_page.dart +++ b/lib/src/screens/dashboard/widgets/address_page.dart @@ -6,6 +6,7 @@ import 'package:cake_wallet/src/screens/dashboard/widgets/present_receive_option import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; import 'package:cake_wallet/src/widgets/keyboard_done_button.dart'; import 'package:cake_wallet/themes/theme_base.dart'; +import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/utils/share_util.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart'; @@ -62,7 +63,37 @@ class AddressPage extends BasePage { Color get titleColor => Colors.white; @override - bool get canUseCloseIcon => true; + Widget? leading(BuildContext context) { + final _backButton = Icon(Icons.arrow_back_ios, + color: titleColor, + size: 16, + ); + final _closeButton = currentTheme.type == ThemeType.dark + ? closeButtonImageDarkTheme : closeButtonImage; + + bool isMobileView = ResponsiveLayoutUtil.instance.isMobile(context); + + return MergeSemantics( + child: SizedBox( + height: isMobileView ? 37 : 45, + width: isMobileView ? 37 : 45, + child: ButtonTheme( + minWidth: double.minPositive, + child: Semantics( + label: !isMobileView ? 'Close' : 'Back', + child: TextButton( + style: ButtonStyle( + overlayColor: MaterialStateColor.resolveWith( + (states) => Colors.transparent), + ), + onPressed: () => onClose(context), + child: !isMobileView ? _closeButton : _backButton, + ), + ), + ), + ), + ); + } @override Widget middle(BuildContext context) => diff --git a/lib/src/screens/exchange/exchange_page.dart b/lib/src/screens/exchange/exchange_page.dart index a434ed13b..fa3b3b825 100644 --- a/lib/src/screens/exchange/exchange_page.dart +++ b/lib/src/screens/exchange/exchange_page.dart @@ -2,6 +2,7 @@ import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/src/screens/exchange/widgets/desktop_exchange_cards_section.dart'; import 'package:cake_wallet/src/screens/exchange/widgets/mobile_exchange_cards_section.dart'; import 'package:cake_wallet/src/widgets/add_template_button.dart'; +import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cake_wallet/utils/debounce.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cw_core/sync_status.dart'; @@ -108,7 +109,37 @@ class ExchangePage extends BasePage { }); @override - bool get canUseCloseIcon => true; + Widget? leading(BuildContext context) { + final _backButton = Icon(Icons.arrow_back_ios, + color: titleColor, + size: 16, + ); + final _closeButton = currentTheme.type == ThemeType.dark + ? closeButtonImageDarkTheme : closeButtonImage; + + bool isMobileView = ResponsiveLayoutUtil.instance.isMobile(context); + + return MergeSemantics( + child: SizedBox( + height: isMobileView ? 37 : 45, + width: isMobileView ? 37 : 45, + child: ButtonTheme( + minWidth: double.minPositive, + child: Semantics( + label: !isMobileView ? 'Close' : 'Back', + child: TextButton( + style: ButtonStyle( + overlayColor: MaterialStateColor.resolveWith( + (states) => Colors.transparent), + ), + onPressed: () => onClose(context), + child: !isMobileView ? _closeButton : _backButton, + ), + ), + ), + ), + ); + } @override Widget body(BuildContext context) { diff --git a/lib/src/screens/monero_accounts/monero_account_list_page.dart b/lib/src/screens/monero_accounts/monero_account_list_page.dart index 145a2d8a4..cb2fe0f2d 100644 --- a/lib/src/screens/monero_accounts/monero_account_list_page.dart +++ b/lib/src/screens/monero_accounts/monero_account_list_page.dart @@ -1,9 +1,7 @@ -import 'dart:ui'; import 'package:cake_wallet/src/widgets/section_divider.dart'; +import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; -import 'package:cake_wallet/palette.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/view_model/monero_account_list/monero_account_list_view_model.dart'; @@ -36,131 +34,138 @@ class MoneroAccountListPage extends StatelessWidget { @override Widget build(BuildContext context) { return AlertBackground( - child: Stack( - alignment: Alignment.center, - children: [ - Column( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - padding: EdgeInsets.only(left: 24, right: 24), - child: Text( - S.of(context).choose_account, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - fontFamily: 'Lato', - decoration: TextDecoration.none, - color: Colors.white - ), - ), - ), - Padding( - padding: EdgeInsets.only(left: 24, right: 24, top: 24), - child: GestureDetector( - onTap: () => null, - child: ClipRRect( - borderRadius: BorderRadius.all(Radius.circular(14)), - child: Container( - height: 296, - color: Theme.of(context).textTheme!.headline1!.decorationColor!, - child: Column( - children: [ - Expanded( - child: Observer( - builder: (_) { - final accounts = accountListViewModel.accounts; - isAlwaysShowScrollThumb = accounts == null - ? false - : accounts.length > 3; - - return Stack( - alignment: Alignment.center, - children: [ - ListView.separated( - padding: EdgeInsets.zero, - controller: controller, - separatorBuilder: (context, index) => - const SectionDivider(), - itemCount: accounts.length ?? 0, - itemBuilder: (context, index) { - final account = accounts[index]; - - return AccountTile( - isCurrent: account.isSelected, - accountName: account.label, - onTap: () { - if (account.isSelected) { - return; - } - - accountListViewModel - .select(account); - Navigator.of(context).pop(); - }, - onEdit: () async => - await Navigator.of(context) - .pushNamed( - Routes.accountCreation, - arguments: account)); - }, - ), - isAlwaysShowScrollThumb - ? CakeScrollbar( - backgroundHeight: backgroundHeight, - thumbHeight: thumbHeight, - fromTop: accountListViewModel - .scrollOffsetFromTop - ) - : Offstage(), - ], - ); - } - ) - ), - GestureDetector( - onTap: () async => await Navigator.of(context) - .pushNamed(Routes.accountCreation), - child: Container( - height: 62, - color: Theme.of(context).cardColor, - padding: EdgeInsets.only(left: 24, right: 24), - child: Center( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - Icons.add, - color: Colors.white, - ), - Padding( - padding: EdgeInsets.only(left: 5), - child: Text( - S.of(context).create_new_account, - style: TextStyle( - fontSize: 15, - fontWeight: FontWeight.w600, - fontFamily: 'Lato', - color: Colors.white, - decoration: TextDecoration.none, - ), - ), - ) - ], - ), - ), - ), - ) - ], + child: Column( + children: [ + Expanded( + child: Stack( + alignment: Alignment.center, + children: [ + Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: EdgeInsets.only(left: 24, right: 24), + child: Text( + S.of(context).choose_account, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + fontFamily: 'Lato', + decoration: TextDecoration.none, + color: Colors.white + ), ), ), - ), + Padding( + padding: EdgeInsets.only(left: 24, right: 24, top: 24), + child: GestureDetector( + onTap: () => null, + child: ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(14)), + child: Container( + height: 296, + color: Theme.of(context).textTheme!.headline1!.decorationColor!, + child: Column( + children: [ + Expanded( + child: Observer( + builder: (_) { + final accounts = accountListViewModel.accounts; + isAlwaysShowScrollThumb = accounts == null + ? false + : accounts.length > 3; + + return Stack( + alignment: Alignment.center, + children: [ + ListView.separated( + padding: EdgeInsets.zero, + controller: controller, + separatorBuilder: (context, index) => + const SectionDivider(), + itemCount: accounts.length ?? 0, + itemBuilder: (context, index) { + final account = accounts[index]; + + return AccountTile( + isCurrent: account.isSelected, + accountName: account.label, + onTap: () { + if (account.isSelected) { + return; + } + + accountListViewModel + .select(account); + Navigator.of(context).pop(); + }, + onEdit: () async => + await Navigator.of(context) + .pushNamed( + Routes.accountCreation, + arguments: account)); + }, + ), + isAlwaysShowScrollThumb + ? CakeScrollbar( + backgroundHeight: backgroundHeight, + thumbHeight: thumbHeight, + fromTop: accountListViewModel + .scrollOffsetFromTop + ) + : Offstage(), + ], + ); + } + ) + ), + GestureDetector( + onTap: () async => await Navigator.of(context) + .pushNamed(Routes.accountCreation), + child: Container( + height: 62, + color: Theme.of(context).cardColor, + padding: EdgeInsets.only(left: 24, right: 24), + child: Center( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.add, + color: Colors.white, + ), + Padding( + padding: EdgeInsets.only(left: 5), + child: Text( + S.of(context).create_new_account, + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.w600, + fontFamily: 'Lato', + color: Colors.white, + decoration: TextDecoration.none, + ), + ), + ) + ], + ), + ), + ), + ) + ], + ), + ), + ), + ), + ) + ], ), - ) - ], + SizedBox(height: ResponsiveLayoutUtil.kPopupSpaceHeight), + AlertCloseButton() + ], + ), ), - AlertCloseButton() ], ), ); diff --git a/lib/src/screens/send/send_page.dart b/lib/src/screens/send/send_page.dart index 544bed39c..ee0d8431c 100644 --- a/lib/src/screens/send/send_page.dart +++ b/lib/src/screens/send/send_page.dart @@ -5,6 +5,7 @@ import 'package:cake_wallet/src/widgets/add_template_button.dart'; import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; import 'package:cake_wallet/src/widgets/picker.dart'; import 'package:cake_wallet/src/widgets/template_tile.dart'; +import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cake_wallet/utils/payment_request.dart'; import 'package:cake_wallet/utils/request_review_handler.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; @@ -53,7 +54,37 @@ class SendPage extends BasePage { bool get extendBodyBehindAppBar => true; @override - bool get canUseCloseIcon => true; + Widget? leading(BuildContext context) { + final _backButton = Icon(Icons.arrow_back_ios, + color: titleColor, + size: 16, + ); + final _closeButton = currentTheme.type == ThemeType.dark + ? closeButtonImageDarkTheme : closeButtonImage; + + bool isMobileView = ResponsiveLayoutUtil.instance.isMobile(context); + + return MergeSemantics( + child: SizedBox( + height: isMobileView ? 37 : 45, + width: isMobileView ? 37 : 45, + child: ButtonTheme( + minWidth: double.minPositive, + child: Semantics( + label: !isMobileView ? 'Close' : 'Back', + child: TextButton( + style: ButtonStyle( + overlayColor: MaterialStateColor.resolveWith( + (states) => Colors.transparent), + ), + onPressed: () => onClose(context), + child: !isMobileView ? _closeButton : _backButton, + ), + ), + ), + ), + ); + } @override AppBarStyle get appBarStyle => AppBarStyle.transparent; diff --git a/lib/src/screens/subaddress/address_edit_or_create_page.dart b/lib/src/screens/subaddress/address_edit_or_create_page.dart index b7394182c..c0b003dec 100644 --- a/lib/src/screens/subaddress/address_edit_or_create_page.dart +++ b/lib/src/screens/subaddress/address_edit_or_create_page.dart @@ -1,5 +1,4 @@ import 'package:mobx/mobx.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:cake_wallet/generated/i18n.dart'; @@ -23,18 +22,14 @@ class AddressEditOrCreatePage extends BasePage { final GlobalKey _formKey; final TextEditingController _labelController; + bool _isEffectsInstalled = false; + @override String get title => S.current.new_subaddress_title; @override Widget body(BuildContext context) { - reaction((_) => addressEditOrCreateViewModel.state, - (AddressEditOrCreateState state) { - if (state is AddressSavedSuccessfully) { - WidgetsBinding.instance - .addPostFrameCallback((_) => Navigator.of(context).pop()); - } - }); + _setEffects(context); return Form( key: _formKey, @@ -70,4 +65,19 @@ class AddressEditOrCreatePage extends BasePage { ), )); } + + void _setEffects(BuildContext context) { + if (_isEffectsInstalled) { + return; + } + reaction((_) => addressEditOrCreateViewModel.state, + (AddressEditOrCreateState state) { + if (state is AddressSavedSuccessfully) { + WidgetsBinding.instance + .addPostFrameCallback((_) => Navigator.of(context).pop()); + } + }); + + _isEffectsInstalled = true; + } } \ No newline at end of file diff --git a/lib/src/screens/wallet_keys/wallet_keys_page.dart b/lib/src/screens/wallet_keys/wallet_keys_page.dart index eb34393cf..da58c4b31 100644 --- a/lib/src/screens/wallet_keys/wallet_keys_page.dart +++ b/lib/src/screens/wallet_keys/wallet_keys_page.dart @@ -4,7 +4,6 @@ import 'package:cake_wallet/src/widgets/section_divider.dart'; import 'package:cake_wallet/utils/show_bar.dart'; import 'package:device_display_brightness/device_display_brightness.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/services.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:cake_wallet/generated/i18n.dart'; @@ -26,13 +25,14 @@ class WalletKeysPage extends BasePage { onPressed: () async { // Get the current brightness: final double brightness = await DeviceDisplayBrightness.getBrightness(); + final url = await walletKeysViewModel.url; // ignore: unawaited_futures DeviceDisplayBrightness.setBrightness(1.0); await Navigator.pushNamed( context, Routes.fullscreenQR, - arguments: QrViewData(data: await walletKeysViewModel.url.toString()), + arguments: QrViewData(data: url.toString()), ); // ignore: unawaited_futures DeviceDisplayBrightness.setBrightness(brightness); diff --git a/scripts/android/app_env.sh b/scripts/android/app_env.sh index fa822f0af..b08d55580 100644 --- a/scripts/android/app_env.sh +++ b/scripts/android/app_env.sh @@ -14,14 +14,14 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN) APP_ANDROID_TYPE=$1 MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="1.3.3" -MONERO_COM_BUILD_NUMBER=43 +MONERO_COM_VERSION="1.3.4" +MONERO_COM_BUILD_NUMBER=47 MONERO_COM_BUNDLE_ID="com.monero.app" MONERO_COM_PACKAGE="com.monero.app" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="4.6.3" -CAKEWALLET_BUILD_NUMBER=154 +CAKEWALLET_VERSION="4.6.4" +CAKEWALLET_BUILD_NUMBER=158 CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet" CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet" diff --git a/scripts/ios/app_env.sh b/scripts/ios/app_env.sh index ca2bf1f12..c874a14c3 100644 --- a/scripts/ios/app_env.sh +++ b/scripts/ios/app_env.sh @@ -13,13 +13,13 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN) APP_IOS_TYPE=$1 MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="1.3.3" -MONERO_COM_BUILD_NUMBER=41 +MONERO_COM_VERSION="1.3.4" +MONERO_COM_BUILD_NUMBER=45 MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="4.6.3" -CAKEWALLET_BUILD_NUMBER=148 +CAKEWALLET_VERSION="4.6.4" +CAKEWALLET_BUILD_NUMBER=153 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" HAVEN_NAME="Haven" diff --git a/scripts/macos/app_env.sh b/scripts/macos/app_env.sh index d788fed1c..76a32903b 100755 --- a/scripts/macos/app_env.sh +++ b/scripts/macos/app_env.sh @@ -16,7 +16,7 @@ fi CAKEWALLET_NAME="Cake Wallet" CAKEWALLET_VERSION="1.0.2" -CAKEWALLET_BUILD_NUMBER=14 +CAKEWALLET_BUILD_NUMBER=18 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" if ! [[ " ${TYPES[*]} " =~ " ${APP_MACOS_TYPE} " ]]; then From 367efb3caed487ed10a3fe7b27ff568a00cf0c70 Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Thu, 27 Apr 2023 17:06:09 +0200 Subject: [PATCH 13/39] V4.6.4 fixes (#904) * Check if context is still mounted or not before showing popup * - Refactor Restore route flow - Fix Monero.com restore from QR - Remove deprecated restore classes - Update Monero.com app version and notes * Update Macos version and release notes * Fixate android plugin versions as Flutter published new fail packages version * Revert desktop changes as it's not supported yet to scan QR code on Desktop [skip ci] * Revert macos version update [skip ci] --- assets/text/Monerocom_Release_Notes.txt | 5 +- lib/router.dart | 101 +++------ lib/routes.dart | 9 +- .../desktop_wallet_selection_dropdown.dart | 1 - .../screens/restore/restore_options_page.dart | 3 +- .../restore_wallet_from_keys_page.dart | 212 ------------------ .../restore_wallet_from_seed_page.dart | 199 ---------------- .../screens/wallet_list/wallet_list_page.dart | 7 +- lib/utils/show_pop_up.dart | 37 +-- pubspec_base.yaml | 4 + scripts/android/app_env.sh | 4 +- scripts/ios/app_env.sh | 4 +- 12 files changed, 62 insertions(+), 524 deletions(-) delete mode 100644 lib/src/screens/restore/restore_wallet_from_keys_page.dart delete mode 100644 lib/src/screens/restore/restore_wallet_from_seed_page.dart diff --git a/assets/text/Monerocom_Release_Notes.txt b/assets/text/Monerocom_Release_Notes.txt index d9acd464f..be218630d 100644 --- a/assets/text/Monerocom_Release_Notes.txt +++ b/assets/text/Monerocom_Release_Notes.txt @@ -1,4 +1 @@ -Fix for QR codes -Fix for creating sub-addresses -Fix Add/Edit nodes -Fix issues with text/amount fields \ No newline at end of file +Fix Restore from QR code \ No newline at end of file diff --git a/lib/router.dart b/lib/router.dart index 5a657a9ca..661a827e7 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -41,17 +41,14 @@ import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart'; import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart'; import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart'; -import 'package:cake_wallet/view_model/restore/restore_from_qr_vm.dart'; -import 'package:cake_wallet/view_model/restore/restore_wallet.dart'; +import 'package:cake_wallet/wallet_type_utils.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/di.dart'; -import 'package:cake_wallet/utils/language_list.dart'; import 'package:cake_wallet/view_model/wallet_new_vm.dart'; import 'package:cake_wallet/view_model/wallet_restoration_from_seed_vm.dart'; -import 'package:cake_wallet/view_model/wallet_restoration_from_keys_vm.dart'; import 'package:cake_wallet/exchange/trade.dart'; import 'package:cw_core/transaction_info.dart'; import 'package:cw_core/wallet_type.dart'; @@ -65,9 +62,6 @@ import 'package:cake_wallet/src/screens/wallet_list/wallet_list_page.dart'; import 'package:cake_wallet/src/screens/new_wallet/new_wallet_page.dart'; import 'package:cake_wallet/src/screens/setup_pin_code/setup_pin_code.dart'; import 'package:cake_wallet/src/screens/restore/restore_options_page.dart'; -import 'package:cake_wallet/src/screens/restore/restore_wallet_options_page.dart'; -import 'package:cake_wallet/src/screens/restore/restore_wallet_from_seed_page.dart'; -import 'package:cake_wallet/src/screens/restore/restore_wallet_from_keys_page.dart'; import 'package:cake_wallet/src/screens/send/send_page.dart'; import 'package:cake_wallet/src/screens/disclaimer/disclaimer_page.dart'; import 'package:cake_wallet/src/screens/seed_language/seed_language_page.dart'; @@ -144,14 +138,6 @@ Route createRoute(RouteSettings settings) { return CupertinoPageRoute( builder: (_) => getIt.get(param1: callback)); - case Routes.moneroRestoreWalletFromWelcome: - return CupertinoPageRoute( - builder: (_) => getIt.get( - param1: (PinCodeState context, dynamic _) => - Navigator.pushNamed( - context.context, Routes.restoreWallet, arguments: WalletType.monero)), - fullscreenDialog: true); - case Routes.restoreWalletType: return CupertinoPageRoute( builder: (_) => getIt.get( @@ -165,46 +151,35 @@ Route createRoute(RouteSettings settings) { return CupertinoPageRoute( builder: (_) => getIt.get(param1: isNewInstall)); - case Routes.restoreWalletOptions: - final type = WalletType.monero; //settings.arguments as WalletType; - - return CupertinoPageRoute( - builder: (_) => RestoreWalletOptionsPage( - type: type, - onRestoreFromSeed: (context) { - final route = type == WalletType.monero - ? Routes.seedLanguage - : Routes.restoreWalletFromSeed; - final args = type == WalletType.monero - ? [type, Routes.restoreWalletFromSeed] - : [type]; - - Navigator.of(context).pushNamed(route, arguments: args); - }, - onRestoreFromKeys: (context) { - final route = type == WalletType.monero - ? Routes.seedLanguage - : Routes.restoreWalletFromKeys; - final args = type == WalletType.monero - ? [type, Routes.restoreWalletFromKeys] - : [type]; - - Navigator.of(context).pushNamed(route, arguments: args); - })); - - case Routes.restoreWalletOptionsFromWelcome: + case Routes.restoreWalletFromSeedKeys: final isNewInstall = settings.arguments as bool; - return isNewInstall ? CupertinoPageRoute( - builder: (_) => getIt.get( - param1: (PinCodeState context, dynamic _) => - Navigator.pushNamed( - context.context, Routes.restoreWalletType)), - fullscreenDialog: true) : CupertinoPageRoute( - builder: (_) => getIt.get( - param1: (BuildContext context, WalletType type) => - Navigator.of(context) - .pushNamed(Routes.restoreWallet, arguments: type), - param2: false)); + + if (isNewInstall) { + return CupertinoPageRoute( + builder: (_) => getIt.get( + param1: (PinCodeState context, dynamic _) { + if (isSingleCoin) { + return Navigator.of(context.context) + .pushNamed(Routes.restoreWallet, arguments: availableWalletTypes.first); + } + + return Navigator.pushNamed( + context.context, Routes.restoreWalletType); + }), + fullscreenDialog: true); + } else if (isSingleCoin) { + return MaterialPageRoute( + builder: (_) => getIt.get( + param1: availableWalletTypes.first + )); + } else { + return CupertinoPageRoute( + builder: (_) => getIt.get( + param1: (BuildContext context, WalletType type) => + Navigator.of(context) + .pushNamed(Routes.restoreWallet, arguments: type), + param2: false)); + } case Routes.seed: return MaterialPageRoute( @@ -216,24 +191,6 @@ Route createRoute(RouteSettings settings) { builder: (_) => getIt.get( param1: settings.arguments as WalletType)); - case Routes.restoreWalletFromSeed: - final type = settings.arguments as WalletType; - return CupertinoPageRoute( - builder: (_) => RestoreWalletFromSeedPage(type: type)); - - case Routes.restoreWalletFromKeys: - final args = settings.arguments as List; - final type = args.first as WalletType; - final language = - type == WalletType.monero ? args[1] as String : LanguageList.english; - - final walletRestorationFromKeysVM = - getIt.get(param1: [type, language]); - - return CupertinoPageRoute( - builder: (_) => RestoreWalletFromKeysPage( - walletRestorationFromKeysVM: walletRestorationFromKeysVM)); - case Routes.sweepingWalletPage: return CupertinoPageRoute( builder: (_) => getIt.get()); diff --git a/lib/routes.dart b/lib/routes.dart index 823febe78..a0490ec28 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -3,14 +3,9 @@ class Routes { static const newWallet = '/new_wallet'; static const setupPin = '/setup_pin_code'; static const newWalletFromWelcome = '/new_wallet_from_welcome'; - static const restoreFromWelcome = '/restore_from_welcome'; static const seed = '/seed'; static const restoreOptions = '/restore_options'; - static const restoreOptionsFromWelcome = '/restore_options_from_welcome'; - static const restoreWalletOptions = '/restore_seed_keys'; - static const restoreWalletOptionsFromWelcome = '/restore_wallet_options_from_welcome'; - static const restoreWalletFromSeed = '/restore_wallet_from_seed'; - static const restoreWalletFromKeys = '/restore_wallet_from_keys'; + static const restoreWalletFromSeedKeys = '/restore_wallet_from_seeds_keys'; static const dashboard = '/dashboard'; static const send = '/send'; static const transactionDetails = '/transaction_info'; @@ -57,8 +52,6 @@ class Routes { static const buyWebView = '/buy_web_view'; static const unspentCoinsList = '/unspent_coins_list'; static const unspentCoinsDetails = '/unspent_coins_details'; - static const moneroRestoreWalletFromWelcome = '/monero_restore_wallet'; - static const moneroNewWalletFromWelcome = '/monero_new_wallet'; static const addressPage = '/address_page'; static const fullscreenQR = '/fullscreen_qr'; static const ioniaWelcomePage = '/cake_pay_welcome_page'; diff --git a/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart b/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart index 0bfcb359e..a8f050002 100644 --- a/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart +++ b/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart @@ -1,6 +1,5 @@ import 'package:another_flushbar/flushbar.dart'; import 'package:cake_wallet/core/auth_service.dart'; -import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/entities/desktop_dropdown_item.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/routes.dart'; diff --git a/lib/src/screens/restore/restore_options_page.dart b/lib/src/screens/restore/restore_options_page.dart index 8025ebd85..c08d2b7e4 100644 --- a/lib/src/screens/restore/restore_options_page.dart +++ b/lib/src/screens/restore/restore_options_page.dart @@ -7,6 +7,7 @@ import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/restore/restore_from_qr_vm.dart'; import 'package:cake_wallet/view_model/restore/wallet_restore_from_qr_code.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; +import 'package:cake_wallet/wallet_type_utils.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/routes.dart'; import 'package:flutter/cupertino.dart'; @@ -38,7 +39,7 @@ class RestoreOptionsPage extends BasePage { children: [ RestoreButton( onPressed: () => Navigator.pushNamed( - context, Routes.restoreWalletOptionsFromWelcome, + context, Routes.restoreWalletFromSeedKeys, arguments: isNewInstall), image: imageSeedKeys, title: S.of(context).restore_title_from_seed_keys, diff --git a/lib/src/screens/restore/restore_wallet_from_keys_page.dart b/lib/src/screens/restore/restore_wallet_from_keys_page.dart deleted file mode 100644 index 85243870a..000000000 --- a/lib/src/screens/restore/restore_wallet_from_keys_page.dart +++ /dev/null @@ -1,212 +0,0 @@ -import 'package:cake_wallet/core/wallet_name_validator.dart'; -import 'package:cake_wallet/palette.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_mobx/flutter_mobx.dart'; -import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/src/screens/base_page.dart'; -import 'package:cake_wallet/src/widgets/primary_button.dart'; -import 'package:cake_wallet/src/widgets/blockchain_height_widget.dart'; -import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; -import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; -import 'package:cake_wallet/view_model/wallet_restoration_from_keys_vm.dart'; -import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; - -class RestoreWalletFromKeysPage extends BasePage { - RestoreWalletFromKeysPage( - {required this.walletRestorationFromKeysVM}); - - final WalletRestorationFromKeysVM walletRestorationFromKeysVM; - - @override - String get title => S.current.restore_title_from_keys; - - @override - Widget body(BuildContext context) => RestoreFromKeysFrom(walletRestorationFromKeysVM); -} - -class RestoreFromKeysFrom extends StatefulWidget { - RestoreFromKeysFrom(this.walletRestorationFromKeysVM); - - final WalletRestorationFromKeysVM walletRestorationFromKeysVM; - - @override - _RestoreFromKeysFromState createState() => _RestoreFromKeysFromState(); -} - -class _RestoreFromKeysFromState extends State { - final _formKey = GlobalKey(); - final _blockchainHeightKey = GlobalKey(); - final _nameController = TextEditingController(); - final _addressController = TextEditingController(); - final _viewKeyController = TextEditingController(); - final _spendKeyController = TextEditingController(); - final _wifController = TextEditingController(); - - @override - void initState() { - _nameController.addListener(() => - widget.walletRestorationFromKeysVM.name = _nameController.text); - _addressController.addListener(() => - widget.walletRestorationFromKeysVM.address = _addressController.text); - _viewKeyController.addListener(() => - widget.walletRestorationFromKeysVM.viewKey = _viewKeyController.text); - _spendKeyController.addListener(() => - widget.walletRestorationFromKeysVM.spendKey = _spendKeyController.text); - _wifController.addListener(() => - widget.walletRestorationFromKeysVM.wif = _wifController.text); - - super.initState(); - } - - @override - void dispose() { - _nameController.dispose(); - _addressController.dispose(); - _viewKeyController.dispose(); - _spendKeyController.dispose(); - _wifController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - - /*reaction((_) => walletRestorationStore.state, (WalletRestorationState state) { - if (state is WalletRestoredSuccessfully) { - Navigator.of(context).popUntil((route) => route.isFirst); - } - - if (state is WalletRestorationFailure) { - WidgetsBinding.instance.addPostFrameCallback((_) { - showPopUp( - context: context, - builder: (BuildContext context) { - return AlertWithOneAction( - alertTitle: S.current.restore_title_from_keys, - alertContent: state.error, - buttonText: S.of(context).ok, - buttonAction: () => Navigator.of(context).pop() - ); - }); - }); - } - });*/ - - return Container( - padding: EdgeInsets.only(left: 24, right: 24), - child: ScrollableWithBottomSection( - contentPadding: EdgeInsets.only(bottom: 24.0), - content: Form( - key: _formKey, - child: Column(children: [ - Row( - children: [ - Flexible( - child: Container( - padding: EdgeInsets.only(top: 20.0), - child: BaseTextFormField( - controller: _nameController, - hintText: S.of(context).restore_wallet_name, - validator: WalletNameValidator(), - ) - )) - ], - ), - if (!widget.walletRestorationFromKeysVM.hasRestorationHeight) - Row( - children: [ - Flexible( - child: Container( - padding: EdgeInsets.only(top: 20.0), - child: BaseTextFormField( - controller: _wifController, - hintText: 'WIF', - ) - )) - ], - ), - if (widget.walletRestorationFromKeysVM.hasRestorationHeight) ... [ - Row( - children: [ - Flexible( - child: Container( - padding: EdgeInsets.only(top: 20.0), - child: BaseTextFormField( - controller: _addressController, - keyboardType: TextInputType.multiline, - maxLines: null, - hintText: S.of(context).restore_address, - ) - )) - ], - ), - Row( - children: [ - Flexible( - child: Container( - padding: EdgeInsets.only(top: 20.0), - child: BaseTextFormField( - controller: _viewKeyController, - hintText: S.of(context).restore_view_key_private, - ) - )) - ], - ), - Row( - children: [ - Flexible( - child: Container( - padding: EdgeInsets.only(top: 20.0), - child: BaseTextFormField( - controller: _spendKeyController, - hintText: S.of(context).restore_spend_key_private, - ) - )) - ], - ), - BlockchainHeightWidget( - key: _blockchainHeightKey, - onHeightChange: (height) { - widget.walletRestorationFromKeysVM.height = height; - print(height); - }), - Padding( - padding: EdgeInsets.only(left: 40, right: 40, top: 24), - child: Text( - S.of(context).restore_from_date_or_blockheight, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.normal, - color: Theme.of(context).hintColor - ), - ), - )], - ]), - ), - bottomSectionPadding: EdgeInsets.only(bottom: 24), - bottomSection: Observer(builder: (_) { - return LoadingPrimaryButton( - onPressed: () { - if (_formKey.currentState != null && _formKey.currentState!.validate()) { - /*walletRestorationStore.restoreFromKeys( - name: _nameController.text, - language: seedLanguageStore.selectedSeedLanguage, - address: _addressController.text, - viewKey: _viewKeyController.text, - spendKey: _spendKeyController.text, - restoreHeight: _blockchainHeightKey.currentState.height);*/ - } - }, - text: S.of(context).restore_recover, - color: Theme.of(context).accentTextTheme!.bodyText1!.color!, - textColor: Colors.white, - //isDisabled: walletRestorationStore.disabledState, - ); - }), - ), - ); - } -} diff --git a/lib/src/screens/restore/restore_wallet_from_seed_page.dart b/lib/src/screens/restore/restore_wallet_from_seed_page.dart deleted file mode 100644 index 31a854049..000000000 --- a/lib/src/screens/restore/restore_wallet_from_seed_page.dart +++ /dev/null @@ -1,199 +0,0 @@ -import 'package:cake_wallet/src/screens/restore/restore_from_keys.dart'; -import 'package:cake_wallet/src/screens/restore/wallet_restore_from_seed_form.dart'; -import 'package:cake_wallet/src/screens/seed_language/widgets/seed_language_picker.dart'; -import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; -import 'package:cake_wallet/src/widgets/blockchain_height_widget.dart'; -import 'package:cake_wallet/src/widgets/primary_button.dart'; -import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; -import 'package:cake_wallet/utils/show_pop_up.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:cake_wallet/routes.dart'; -import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/src/screens/base_page.dart'; -import 'package:cake_wallet/src/widgets/seed_widget.dart'; -import 'package:cw_core/wallet_type.dart'; -import 'package:cake_wallet/core/seed_validator.dart'; -import 'package:cake_wallet/core/mnemonic_length.dart'; -import 'package:smooth_page_indicator/smooth_page_indicator.dart'; - -class RestoreWalletFromSeedPage extends BasePage { - RestoreWalletFromSeedPage({required this.type}) - : _pages = []; - - final WalletType type; - final String language = 'en'; - - // final formKey = GlobalKey<_RestoreFromSeedFormState>(); - // final formKey = GlobalKey<_RestoreFromSeedFormState>(); - - @override - String get title => S.current.restore_title_from_seed; - - final controller = PageController(initialPage: 0); - List _pages; - - Widget _page(BuildContext context, int index) { - if (_pages == null || _pages.isEmpty) { - _setPages(context); - } - - return _pages[index]; - } - - int _pageLength(BuildContext context) { - if (_pages == null || _pages.isEmpty) { - _setPages(context); - } - - return _pages.length; - } - - void _setPages(BuildContext context) { - _pages = [ - // FIX-ME: Added args (displayBlockHeightSelector: true, displayLanguageSelector: true, type: type) - WalletRestoreFromSeedForm(displayBlockHeightSelector: true, displayLanguageSelector: true, type: type), - RestoreFromKeysFrom(), - ]; - } - - @override - Widget body(BuildContext context) { - return Column(mainAxisAlignment: MainAxisAlignment.center, children: [ - Expanded( - child: PageView.builder( - onPageChanged: (page) { - print('Page index $page'); - }, - controller: controller, - itemCount: _pageLength(context), - itemBuilder: (context, index) => _page(context, index))), - Padding( - padding: EdgeInsets.only(top: 10), - child: SmoothPageIndicator( - controller: controller, - count: _pageLength(context), - effect: ColorTransitionEffect( - spacing: 6.0, - radius: 6.0, - dotWidth: 6.0, - dotHeight: 6.0, - dotColor: Theme.of(context).hintColor.withOpacity(0.5), - activeDotColor: Theme.of(context).hintColor), - )), - Padding( - padding: EdgeInsets.only(top: 20, bottom: 24, left: 24, right: 24), - child: PrimaryButton( - text: S.of(context).restore_recover, - isDisabled: false, - onPressed: () => null, - color: Theme.of(context).accentTextTheme!.bodyText1!.color!, - textColor: Colors.white)), - ]); - - // return GestureDetector( - // onTap: () => - // SystemChannels.textInput.invokeMethod('TextInput.hide'), - // child: ScrollableWithBottomSection( - // bottomSection: Column(children: [ - // GestureDetector( - // onTap: () {}, - // child: Text('Switch to restore from keys', - // style: TextStyle(fontSize: 15, color: Theme.of(context).hintColor))), - // SizedBox(height: 30), - // PrimaryButton( - // text: S.of(context).restore_next, - // isDisabled: false, - // onPressed: () => null, - // color: Theme.of(context).accentTextTheme!.bodyText1!.color!, - // textColor: Colors.white) - // ]), - // contentPadding: EdgeInsets.only(bottom: 24), - // content: Container( - // padding: EdgeInsets.only(left: 25, right: 25), - // child: Column(children: [ - // SeedWidget( - // maxLength: mnemonicLength(type), - // onMnemonicChange: (seed) => null, - // onFinish: () => Navigator.of(context).pushNamed( - // Routes.restoreWalletFromSeedDetails, - // arguments: [type, language, '']), - // validator: SeedValidator(type: type, language: language), - // ), - // // SizedBox(height: 15), - // // BaseTextFormField(hintText: 'Language', initialValue: 'English'), - // BlockchainHeightWidget( - // // key: _blockchainHeightKey, - // onHeightChange: (height) { - // // widget.walletRestorationFromKeysVM.height = height; - // print(height); - // }) - // ]))), - // ); - } -} - -class RestoreFromSeedForm extends StatefulWidget { - RestoreFromSeedForm( - {Key? key, - required this.type, - this.language, - this.leading, - this.middle}) - : super(key: key); - final WalletType type; - final String? language; - final Widget? leading; - final Widget? middle; - - @override - _RestoreFromSeedFormState createState() => _RestoreFromSeedFormState(); -} - -class _RestoreFromSeedFormState extends State { - // final _seedKey = GlobalKey(); - - String mnemonic() => - ''; // _seedKey.currentState.items.map((e) => e.text).join(' '); - - @override - Widget build(BuildContext context) { - return GestureDetector( - onTap: () => - SystemChannels.textInput.invokeMethod('TextInput.hide'), - child: Container( - padding: EdgeInsets.only(left: 24, right: 24), - // color: Colors.blue, - // height: 300, - child: Column(children: [ - SeedWidget( - type: widget.type, - language: widget.language ?? '', - // key: _seedKey, - // maxLength: mnemonicLength(widget.type), - // onMnemonicChange: (seed) => null, - // onFinish: () => Navigator.of(context).pushNamed( - // Routes.restoreWalletFromSeedDetails, - // arguments: [widget.type, widget.language, mnemonic()]), - // leading: widget.leading, - // middle: widget.middle, - // validator: - // SeedValidator(type: widget.type, language: widget.language), - ), - BlockchainHeightWidget( - // key: _blockchainHeightKey, - onHeightChange: (height) { - // widget.walletRestorationFromKeysVM.height = height; - print(height); - }), - Container( - color: Colors.green, - width: 100, - height: 56, - child: BaseTextFormField( - hintText: 'Language', initialValue: 'English')), - ])), - ); - } -} diff --git a/lib/src/screens/wallet_list/wallet_list_page.dart b/lib/src/screens/wallet_list/wallet_list_page.dart index e1c4c48e5..bf36b129b 100644 --- a/lib/src/screens/wallet_list/wallet_list_page.dart +++ b/lib/src/screens/wallet_list/wallet_list_page.dart @@ -174,12 +174,7 @@ class WalletListBodyState extends State { SizedBox(height: 10.0), PrimaryImageButton( onPressed: () { - if (isSingleCoin) { - Navigator.of(context).pushNamed(Routes.restoreWallet, - arguments: widget.walletListViewModel.currentWalletType); - } else { - Navigator.of(context).pushNamed(Routes.restoreOptions, arguments: false); - } + Navigator.of(context).pushNamed(Routes.restoreOptions, arguments: false); }, image: restoreWalletImage, text: S.of(context).wallet_list_restore_wallet, diff --git a/lib/utils/show_pop_up.dart b/lib/utils/show_pop_up.dart index 190b2a6d7..76114cc80 100644 --- a/lib/utils/show_pop_up.dart +++ b/lib/utils/show_pop_up.dart @@ -1,20 +1,23 @@ import 'package:flutter/material.dart'; -Future showPopUp({ - required BuildContext context, - required WidgetBuilder builder, - bool barrierDismissible = true, - Color? barrierColor, - bool useSafeArea = false, - bool useRootNavigator = true, - RouteSettings? routeSettings -}) { - return showDialog( - context: context, - builder: builder, - barrierDismissible: barrierDismissible, - barrierColor: barrierColor, - useSafeArea: useSafeArea, - useRootNavigator: useRootNavigator, - routeSettings: routeSettings); +Future showPopUp( + {required BuildContext context, + required WidgetBuilder builder, + bool barrierDismissible = true, + Color? barrierColor, + bool useSafeArea = false, + bool useRootNavigator = true, + RouteSettings? routeSettings}) async { + if (context.mounted) { + return showDialog( + context: context, + builder: builder, + barrierDismissible: barrierDismissible, + barrierColor: barrierColor, + useSafeArea: useSafeArea, + useRootNavigator: useRootNavigator, + routeSettings: routeSettings); + } + + return null; } diff --git a/pubspec_base.yaml b/pubspec_base.yaml index 8beb79116..58de6edab 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -73,6 +73,10 @@ dependencies: url: https://github.com/cake-tech/cake_backup.git ref: main version: 1.0.0 + flutter_plugin_android_lifecycle: 2.0.9 + path_provider_android: 2.0.24 + shared_preferences_android: 2.0.17 + url_launcher_android: 6.0.24 dev_dependencies: flutter_test: diff --git a/scripts/android/app_env.sh b/scripts/android/app_env.sh index b08d55580..72d3aabfc 100644 --- a/scripts/android/app_env.sh +++ b/scripts/android/app_env.sh @@ -14,8 +14,8 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN) APP_ANDROID_TYPE=$1 MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="1.3.4" -MONERO_COM_BUILD_NUMBER=47 +MONERO_COM_VERSION="1.3.5" +MONERO_COM_BUILD_NUMBER=48 MONERO_COM_BUNDLE_ID="com.monero.app" MONERO_COM_PACKAGE="com.monero.app" diff --git a/scripts/ios/app_env.sh b/scripts/ios/app_env.sh index c874a14c3..412ec5aa9 100644 --- a/scripts/ios/app_env.sh +++ b/scripts/ios/app_env.sh @@ -13,8 +13,8 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN) APP_IOS_TYPE=$1 MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="1.3.4" -MONERO_COM_BUILD_NUMBER=45 +MONERO_COM_VERSION="1.3.5" +MONERO_COM_BUILD_NUMBER=46 MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" From b2c603a47b127cb5eb48d3a600f77b08a36666aa Mon Sep 17 00:00:00 2001 From: mkyq <53115730+mkyq@users.noreply.github.com> Date: Mon, 1 May 2023 09:36:46 -0400 Subject: [PATCH 14/39] Fix gen scripts. Change default arch for macos application. (#901) * Fix gen scripts. Change default arch for macos application. * Add combine script for combine arm and x86_64 libs. * - Update Mac version for release - Update branch with main [skip ci] --------- Co-authored-by: OmarHatem --- macos/Podfile.lock | 2 +- macos/Runner.xcodeproj/project.pbxproj | 6 +++--- scripts/macos/app_env.sh | 4 ++-- scripts/macos/combine.sh | 17 +++++++++++++++++ scripts/macos/gen_common.sh | 6 +----- scripts/macos/gen_universal.sh | 2 +- 6 files changed, 25 insertions(+), 12 deletions(-) create mode 100755 scripts/macos/combine.sh diff --git a/macos/Podfile.lock b/macos/Podfile.lock index a56166e28..ea15b9495 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -103,7 +103,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: connectivity_macos: 5dae6ee11d320fac7c05f0d08bd08fc32b5514d9 - cw_monero: f8b7f104508efba2591548e76b5c058d05cba3f0 + cw_monero: ec03de55a19c4a2b174ea687e0f4202edc716fa4 device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f devicelocale: 9f0f36ac651cabae2c33f32dcff4f32b61c38225 flutter_secure_storage_macos: 6ceee8fbc7f484553ad17f79361b556259df89aa diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index 06558bf57..a7d9e2807 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -423,7 +423,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { - ARCHS = arm64; + ARCHS = "$(ARCHS_STANDARD)"; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; @@ -557,7 +557,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { - ARCHS = arm64; + ARCHS = "$(ARCHS_STANDARD)"; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; @@ -585,7 +585,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { - ARCHS = arm64; + ARCHS = "$(ARCHS_STANDARD)"; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; diff --git a/scripts/macos/app_env.sh b/scripts/macos/app_env.sh index 76a32903b..785dc9e24 100755 --- a/scripts/macos/app_env.sh +++ b/scripts/macos/app_env.sh @@ -15,8 +15,8 @@ if [ -n "$1" ]; then fi CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="1.0.2" -CAKEWALLET_BUILD_NUMBER=18 +CAKEWALLET_VERSION="1.0.3" +CAKEWALLET_BUILD_NUMBER=20 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" if ! [[ " ${TYPES[*]} " =~ " ${APP_MACOS_TYPE} " ]]; then diff --git a/scripts/macos/combine.sh b/scripts/macos/combine.sh new file mode 100755 index 000000000..37c2c85fb --- /dev/null +++ b/scripts/macos/combine.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +. ./config.sh + +cd $EXTERNAL_DIR +mkdir -p $EXTERNAL_MACOS_LIB_DIR + +EXTERNAL_DIR_MACOS_ARM=${EXTERNAL_DIR}/macos-arm/lib +EXTERNAL_DIR_MACOS_X86_64=${EXTERNAL_DIR}/macos-x86_64/lib + +LIBS=(libboost.a libcrypto.a libssl.a libsodium.a libunbound.a libmonero.a) + +for lib in ${LIBS[@]}; do + echo ${EXTERNAL_DIR_MACOS_ARM}/$lib + echo ${EXTERNAL_DIR_MACOS_X86_64}/$lib + lipo -create -arch arm64 ${EXTERNAL_DIR_MACOS_ARM}/$lib -arch x86_64 ${EXTERNAL_DIR_MACOS_X86_64}/$lib -output ${EXTERNAL_MACOS_LIB_DIR}/$lib; +done \ No newline at end of file diff --git a/scripts/macos/gen_common.sh b/scripts/macos/gen_common.sh index 62f4effab..87fd54b43 100755 --- a/scripts/macos/gen_common.sh +++ b/scripts/macos/gen_common.sh @@ -15,13 +15,9 @@ gen_podspec() { gen_project() { ARCH=$1 CW_DIR="`pwd`/../../macos/Runner.xcodeproj" - BASE_FILENAME="project_base.pbxproj" - BASE_FILE_PATH="${CW_DIR}/${BASE_FILENAME}" DEFAULT_FILENAME="project.pbxproj" DEFAULT_FILE_PATH="${CW_DIR}/${DEFAULT_FILENAME}" - rm -f $DEFAULT_FILE_PATH - cp $BASE_FILE_PATH $DEFAULT_FILE_PATH - sed -i '' "s/ARCHS =.*/ARCHS = ${ARCH};/g" $DEFAULT_FILE_PATH + sed -i '' "s/ARCHS =.*/ARCHS = \"${ARCH}\";/g" $DEFAULT_FILE_PATH } gen() { diff --git a/scripts/macos/gen_universal.sh b/scripts/macos/gen_universal.sh index 6056053a5..283411804 100755 --- a/scripts/macos/gen_universal.sh +++ b/scripts/macos/gen_universal.sh @@ -2,4 +2,4 @@ . ./gen_common.sh -gen "arm64, x86_64" \ No newline at end of file +gen "\$(ARCHS_STANDARD)" \ No newline at end of file From 0a35e5c3e17c50bcfe40bb7eac6671299d075848 Mon Sep 17 00:00:00 2001 From: Godwin Asuquo <41484542+godilite@users.noreply.github.com> Date: Tue, 2 May 2023 16:30:19 +0300 Subject: [PATCH 15/39] Dismiss ios keyboard before app goes to background (#911) --- ios/Runner/AppDelegate.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 6d5f51aa3..ec95060d1 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -107,6 +107,7 @@ import UnstoppableDomainsResolution } override func applicationWillResignActive(_: UIApplication ) { + self.window?.rootViewController?.view.endEditing(true) self.window?.isHidden = true; } From f47f69080391a858eeaa836c492d2ebede106b60 Mon Sep 17 00:00:00 2001 From: Serhii Date: Wed, 3 May 2023 10:01:51 +0300 Subject: [PATCH 16/39] fix getting correct currency from template (#898) --- lib/exchange/exchange_template.dart | 29 ++++++++++++------ lib/src/screens/exchange/exchange_page.dart | 4 +-- .../exchange/exchange_template_page.dart | 8 +++-- .../templates/exchange_template_store.dart | 30 +++++++++++-------- .../exchange/exchange_view_model.dart | 8 +++-- 5 files changed, 51 insertions(+), 28 deletions(-) diff --git a/lib/exchange/exchange_template.dart b/lib/exchange/exchange_template.dart index 26e1fa761..dcfd8d8e8 100644 --- a/lib/exchange/exchange_template.dart +++ b/lib/exchange/exchange_template.dart @@ -4,14 +4,15 @@ part 'exchange_template.g.dart'; @HiveType(typeId: ExchangeTemplate.typeId) class ExchangeTemplate extends HiveObject { - ExchangeTemplate({ - required this.amountRaw, - required this.depositCurrencyRaw, - required this.receiveCurrencyRaw, - required this.providerRaw, - required this.depositAddressRaw, - required this.receiveAddressRaw - }); + ExchangeTemplate( + {required this.amountRaw, + required this.depositCurrencyRaw, + required this.receiveCurrencyRaw, + required this.providerRaw, + required this.depositAddressRaw, + required this.receiveAddressRaw, + required this.depositCurrencyTitleRaw, + required this.receiveCurrencyTitleRaw}); static const typeId = 7; static const boxName = 'ExchangeTemplate'; @@ -34,6 +35,12 @@ class ExchangeTemplate extends HiveObject { @HiveField(5) String? receiveAddressRaw; + @HiveField(6) + String? depositCurrencyTitleRaw; + + @HiveField(7) + String? receiveCurrencyTitleRaw; + String get amount => amountRaw ?? ''; String get depositCurrency => depositCurrencyRaw ?? ''; @@ -45,4 +52,8 @@ class ExchangeTemplate extends HiveObject { String get depositAddress => depositAddressRaw ?? ''; String get receiveAddress => receiveAddressRaw ?? ''; -} \ No newline at end of file + + String get depositCurrencyTitle => depositCurrencyTitleRaw ?? ''; + + String get receiveCurrencyTitle => receiveCurrencyTitleRaw ?? ''; +} diff --git a/lib/src/screens/exchange/exchange_page.dart b/lib/src/screens/exchange/exchange_page.dart index fa3b3b825..0254ef0d4 100644 --- a/lib/src/screens/exchange/exchange_page.dart +++ b/lib/src/screens/exchange/exchange_page.dart @@ -280,8 +280,8 @@ class ExchangePage extends BasePage { return TemplateTile( key: UniqueKey(), amount: template.amount, - from: template.depositCurrency, - to: template.receiveCurrency, + from: template.depositCurrencyTitle, + to: template.receiveCurrencyTitle, onTap: () { applyTemplate(context, exchangeViewModel, template); }, diff --git a/lib/src/screens/exchange/exchange_template_page.dart b/lib/src/screens/exchange/exchange_template_page.dart index 50faf7eb2..00eee49c2 100644 --- a/lib/src/screens/exchange/exchange_template_page.dart +++ b/lib/src/screens/exchange/exchange_template_page.dart @@ -239,9 +239,13 @@ class ExchangeTemplatePage extends BasePage { exchangeViewModel.addTemplate( amount: exchangeViewModel.depositAmount, depositCurrency: - exchangeViewModel.depositCurrency.toString(), + exchangeViewModel.depositCurrency.name, + depositCurrencyTitle: exchangeViewModel + .depositCurrency.title + ' ${exchangeViewModel.depositCurrency.tag ?? ''}', receiveCurrency: - exchangeViewModel.receiveCurrency.toString(), + exchangeViewModel.receiveCurrency.name, + receiveCurrencyTitle: exchangeViewModel + .receiveCurrency.title + ' ${exchangeViewModel.receiveCurrency.tag ?? ''}', provider: exchangeViewModel.provider.toString(), depositAddress: exchangeViewModel.depositAddress, receiveAddress: exchangeViewModel.receiveAddress); diff --git a/lib/store/templates/exchange_template_store.dart b/lib/store/templates/exchange_template_store.dart index a63c2f56e..97e1f41dc 100644 --- a/lib/store/templates/exchange_template_store.dart +++ b/lib/store/templates/exchange_template_store.dart @@ -8,8 +8,8 @@ part 'exchange_template_store.g.dart'; class ExchangeTemplateStore = ExchangeTemplateBase with _$ExchangeTemplateStore; abstract class ExchangeTemplateBase with Store { - ExchangeTemplateBase({required this.templateSource}) - : templates = ObservableList() { + ExchangeTemplateBase({required this.templateSource}) + : templates = ObservableList() { templates = ObservableList(); update(); } @@ -20,27 +20,31 @@ abstract class ExchangeTemplateBase with Store { Box templateSource; @action - void update() => - templates.replaceRange(0, templates.length, templateSource.values.toList()); + void update() => templates.replaceRange(0, templates.length, templateSource.values.toList()); @action Future addTemplate({ - required String amount, - required String depositCurrency, - required String receiveCurrency, - required String provider, - required String depositAddress, - required String receiveAddress}) async { + required String amount, + required String depositCurrency, + required String receiveCurrency, + required String provider, + required String depositAddress, + required String receiveAddress, + required String depositCurrencyTitle, + required String receiveCurrencyTitle, + }) async { final template = ExchangeTemplate( amountRaw: amount, depositCurrencyRaw: depositCurrency, receiveCurrencyRaw: receiveCurrency, providerRaw: provider, depositAddressRaw: depositAddress, - receiveAddressRaw: receiveAddress); + receiveAddressRaw: receiveAddress, + depositCurrencyTitleRaw: depositCurrencyTitle, + receiveCurrencyTitleRaw: receiveCurrencyTitle); await templateSource.add(template); } @action - Future remove({required ExchangeTemplate template}) async => await template.delete(); -} \ No newline at end of file + Future remove({required ExchangeTemplate template}) async => await template.delete(); +} diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart index 1e25f1353..3c016d4b0 100644 --- a/lib/view_model/exchange/exchange_view_model.dart +++ b/lib/view_model/exchange/exchange_view_model.dart @@ -578,14 +578,18 @@ abstract class ExchangeViewModelBase with Store { required String receiveCurrency, required String provider, required String depositAddress, - required String receiveAddress}) => + required String receiveAddress, + required String depositCurrencyTitle, + required String receiveCurrencyTitle}) => _exchangeTemplateStore.addTemplate( amount: amount, depositCurrency: depositCurrency, receiveCurrency: receiveCurrency, provider: provider, depositAddress: depositAddress, - receiveAddress: receiveAddress); + receiveAddress: receiveAddress, + depositCurrencyTitle: depositCurrencyTitle, + receiveCurrencyTitle: receiveCurrencyTitle); void removeTemplate({required ExchangeTemplate template}) => _exchangeTemplateStore.remove(template: template); From f012aaebf479309fac1a19daddac73eed6175625 Mon Sep 17 00:00:00 2001 From: Rafael Saes <76502841+saltrafael@users.noreply.github.com> Date: Wed, 3 May 2023 21:33:05 -0300 Subject: [PATCH 17/39] Cw 363 back button on receive screen in bright theme is white (#910) * feat: Add missing title on wallet_list_page.dart * feat: add bool lightTitleColor value when theme needs lighter color title, instead of hardcoding to Colors.white - Light color theme taken from Share button from receive page * fix: revert changes & use currentTheme.type to use proper theme style * fix: use share button color logic also on overriden leading Widget on address_page * refactor: remove unnecessary titleColor --- lib/src/screens/dashboard/widgets/address_page.dart | 13 ++++++------- lib/src/screens/receive/receive_page.dart | 3 ++- lib/src/screens/wallet_list/wallet_list_page.dart | 3 +++ 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/src/screens/dashboard/widgets/address_page.dart b/lib/src/screens/dashboard/widgets/address_page.dart index 82430f0c6..465c494d1 100644 --- a/lib/src/screens/dashboard/widgets/address_page.dart +++ b/lib/src/screens/dashboard/widgets/address_page.dart @@ -59,17 +59,16 @@ class AddressPage extends BasePage { bool effectsInstalled = false; - @override - Color get titleColor => Colors.white; - @override Widget? leading(BuildContext context) { - final _backButton = Icon(Icons.arrow_back_ios, - color: titleColor, + final _backButton = Icon( + Icons.arrow_back_ios, + color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!, size: 16, ); final _closeButton = currentTheme.type == ThemeType.dark - ? closeButtonImageDarkTheme : closeButtonImage; + ? closeButtonImageDarkTheme + : closeButtonImage; bool isMobileView = ResponsiveLayoutUtil.instance.isMobile(context); @@ -84,7 +83,7 @@ class AddressPage extends BasePage { child: TextButton( style: ButtonStyle( overlayColor: MaterialStateColor.resolveWith( - (states) => Colors.transparent), + (states) => Colors.transparent), ), onPressed: () => onClose(context), child: !isMobileView ? _closeButton : _backButton, diff --git a/lib/src/screens/receive/receive_page.dart b/lib/src/screens/receive/receive_page.dart index 00a157d97..1dd4b8585 100644 --- a/lib/src/screens/receive/receive_page.dart +++ b/lib/src/screens/receive/receive_page.dart @@ -53,7 +53,8 @@ class ReceivePage extends BasePage { final FocusNode _cryptoAmountFocus; @override - Color get titleColor => Colors.white; + Color? get titleColor => + currentTheme.type == ThemeType.bright ? Colors.white : null; @override Widget middle(BuildContext context) { diff --git a/lib/src/screens/wallet_list/wallet_list_page.dart b/lib/src/screens/wallet_list/wallet_list_page.dart index bf36b129b..dce7e2ccf 100644 --- a/lib/src/screens/wallet_list/wallet_list_page.dart +++ b/lib/src/screens/wallet_list/wallet_list_page.dart @@ -24,6 +24,9 @@ class WalletListPage extends BasePage { final WalletListViewModel walletListViewModel; final AuthService authService; + @override + String get title => S.current.wallets; + @override Widget body(BuildContext context) => WalletListBody(walletListViewModel: walletListViewModel, authService: authService); From 25b743df47d6ac6949ea0feaad6fa691796da093 Mon Sep 17 00:00:00 2001 From: Godwin Asuquo <41484542+godilite@users.noreply.github.com> Date: Fri, 5 May 2023 03:15:35 +0300 Subject: [PATCH 18/39] CW-368 Fix Range error accessing outputs (#912) --- .../screens/send/widgets/confirm_sending_alert.dart | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/lib/src/screens/send/widgets/confirm_sending_alert.dart b/lib/src/screens/send/widgets/confirm_sending_alert.dart index a034d801e..3d5f5c928 100644 --- a/lib/src/screens/send/widgets/confirm_sending_alert.dart +++ b/lib/src/screens/send/widgets/confirm_sending_alert.dart @@ -142,10 +142,8 @@ class ConfirmSendingAlertContentState extends State required this.feeValue, required this.feeFiatAmount, required this.outputs}) - : itemCount = 0, - recipientTitle = '' { - itemCount = outputs.length; - recipientTitle = itemCount > 1 + : recipientTitle = '' { + recipientTitle = outputs.length > 1 ? S.current.transaction_details_recipient_address : S.current.recipient_address; } @@ -165,7 +163,6 @@ class ConfirmSendingAlertContentState extends State ScrollController controller = ScrollController(); double fromTop = 0; String recipientTitle; - int itemCount; bool showScrollbar = false; @override @@ -342,12 +339,12 @@ class ConfirmSendingAlertContentState extends State decoration: TextDecoration.none, ), ), - itemCount > 1 + outputs.length > 1 ? ListView.builder( padding: EdgeInsets.only(top: 0), shrinkWrap: true, physics: NeverScrollableScrollPhysics(), - itemCount: itemCount, + itemCount: outputs.length, itemBuilder: (context, index) { final item = outputs[index]; final _address = item.isParsedAddress From 971496129802e37d8f348aac35e7d908c4fee019 Mon Sep 17 00:00:00 2001 From: Godwin Asuquo <41484542+godilite@users.noreply.github.com> Date: Fri, 5 May 2023 15:58:41 +0300 Subject: [PATCH 19/39] CW-260 show amount received for each account (#907) * Show amount received for each account * Enable litcoin send all * disable litcoin send all * Indent code * Fix large font resolution for account title --- cw_core/lib/account.dart | 6 +- cw_monero/lib/monero_account_list.dart | 21 ++++--- lib/monero/cw_monero.dart | 12 ++-- .../monero_account_list_page.dart | 17 +++--- .../monero_accounts/widgets/account_tile.dart | 55 +++++++++++++------ .../account_list_item.dart | 5 +- .../monero_account_list_view_model.dart | 8 ++- tool/configure.dart | 5 +- 8 files changed, 84 insertions(+), 45 deletions(-) diff --git a/cw_core/lib/account.dart b/cw_core/lib/account.dart index 1633ee189..5e7df4ae7 100644 --- a/cw_core/lib/account.dart +++ b/cw_core/lib/account.dart @@ -1,10 +1,12 @@ class Account { - Account({required this.id, required this.label}); + Account({required this.id, required this.label, this.balance}); Account.fromMap(Map map) : this.id = map['id'] == null ? 0 : int.parse(map['id'] as String), - this.label = (map['label'] ?? '') as String; + this.label = (map['label'] ?? '') as String, + this.balance = (map['balance'] ?? '0.00') as String; final int id; final String label; + final String? balance; } \ No newline at end of file diff --git a/cw_monero/lib/monero_account_list.dart b/cw_monero/lib/monero_account_list.dart index f618cf57a..2fd11b3ba 100644 --- a/cw_monero/lib/monero_account_list.dart +++ b/cw_monero/lib/monero_account_list.dart @@ -1,6 +1,8 @@ +import 'package:cw_core/monero_amount_format.dart'; import 'package:mobx/mobx.dart'; import 'package:cw_core/account.dart'; import 'package:cw_monero/api/account_list.dart' as account_list; +import 'package:cw_monero/api/wallet.dart' as monero_wallet; part 'monero_account_list.g.dart'; @@ -41,12 +43,16 @@ abstract class MoneroAccountListBase with Store { } } - List getAll() => account_list - .getAllAccount() - .map((accountRow) => Account( - id: accountRow.getId(), - label: accountRow.getLabel())) - .toList(); + List getAll() => account_list.getAllAccount().map((accountRow) { + final accountIndex = accountRow.getId(); + final balance = monero_wallet.getFullBalance(accountIndex: accountIndex); + + return Account( + id: accountRow.getId(), + label: accountRow.getLabel(), + balance: moneroAmountToString(amount: balance), + ); + }).toList(); Future addAccount({required String label}) async { await account_list.addAccount(label: label); @@ -54,8 +60,7 @@ abstract class MoneroAccountListBase with Store { } Future setLabelAccount({required int accountIndex, required String label}) async { - await account_list.setLabelForAccount( - accountIndex: accountIndex, label: label); + await account_list.setLabelForAccount(accountIndex: accountIndex, label: label); update(); } diff --git a/lib/monero/cw_monero.dart b/lib/monero/cw_monero.dart index 48c0c51a4..5dfe2e467 100644 --- a/lib/monero/cw_monero.dart +++ b/lib/monero/cw_monero.dart @@ -10,7 +10,7 @@ class CWMoneroAccountList extends MoneroAccountList { final moneroWallet = _wallet as MoneroWallet; final accounts = moneroWallet.walletAddresses.accountList .accounts - .map((acc) => Account(id: acc.id, label: acc.label)) + .map((acc) => Account(id: acc.id, label: acc.label, balance: acc.balance)) .toList(); return ObservableList.of(accounts); } @@ -32,7 +32,7 @@ class CWMoneroAccountList extends MoneroAccountList { final moneroWallet = wallet as MoneroWallet; return moneroWallet.walletAddresses.accountList .getAll() - .map((acc) => Account(id: acc.id, label: acc.label)) + .map((acc) => Account(id: acc.id, label: acc.label, balance: acc.balance)) .toList(); } @@ -122,7 +122,7 @@ class CWMoneroWalletDetails extends MoneroWalletDetails { Account get account { final moneroWallet = _wallet as MoneroWallet; final acc = moneroWallet.walletAddresses.account; - return Account(id: acc!.id, label: acc.label); + return Account(id: acc!.id, label: acc.label, balance: acc.balance); } @computed @@ -316,13 +316,13 @@ class CWMonero extends Monero { Account getCurrentAccount(Object wallet) { final moneroWallet = wallet as MoneroWallet; final acc = moneroWallet.walletAddresses.account; - return Account(id: acc!.id, label: acc.label); + return Account(id: acc!.id, label: acc.label, balance: acc.balance); } @override - void setCurrentAccount(Object wallet, int id, String label) { + void setCurrentAccount(Object wallet, int id, String label, String? balance) { final moneroWallet = wallet as MoneroWallet; - moneroWallet.walletAddresses.account = monero_account.Account(id: id, label: label); + moneroWallet.walletAddresses.account = monero_account.Account(id: id, label: label, balance: balance); } @override diff --git a/lib/src/screens/monero_accounts/monero_account_list_page.dart b/lib/src/screens/monero_accounts/monero_account_list_page.dart index cb2fe0f2d..09a08a425 100644 --- a/lib/src/screens/monero_accounts/monero_account_list_page.dart +++ b/lib/src/screens/monero_accounts/monero_account_list_page.dart @@ -88,13 +88,16 @@ class MoneroAccountListPage extends StatelessWidget { itemBuilder: (context, index) { final account = accounts[index]; - return AccountTile( - isCurrent: account.isSelected, - accountName: account.label, - onTap: () { - if (account.isSelected) { - return; - } + return AccountTile( + isCurrent: account.isSelected, + accountName: account.label, + accountBalance: account.balance ?? '0.00', + currency: accountListViewModel + .currency.toString(), + onTap: () { + if (account.isSelected) { + return; + } accountListViewModel .select(account); diff --git a/lib/src/screens/monero_accounts/widgets/account_tile.dart b/lib/src/screens/monero_accounts/widgets/account_tile.dart index 5e50c7f75..c214fa4e2 100644 --- a/lib/src/screens/monero_accounts/widgets/account_tile.dart +++ b/lib/src/screens/monero_accounts/widgets/account_tile.dart @@ -1,45 +1,68 @@ import 'package:flutter/material.dart'; -import 'package:flutter_slidable/flutter_slidable.dart'; -import 'package:cake_wallet/generated/i18n.dart'; class AccountTile extends StatelessWidget { AccountTile({ required this.isCurrent, required this.accountName, + this.accountBalance, + required this.currency, required this.onTap, required this.onEdit }); final bool isCurrent; final String accountName; + final String? accountBalance; + final String currency; final Function() onTap; final Function() onEdit; @override Widget build(BuildContext context) { final color = isCurrent - ? Theme.of(context).textTheme!.subtitle2!.decorationColor! - : Theme.of(context).textTheme!.headline1!.decorationColor!; + ? Theme.of(context).textTheme.subtitle2!.decorationColor! + : Theme.of(context).textTheme.headline1!.decorationColor!; final textColor = isCurrent - ? Theme.of(context).textTheme!.subtitle2!.color! - : Theme.of(context).textTheme!.headline1!.color!; + ? Theme.of(context).textTheme.subtitle2!.color! + : Theme.of(context).textTheme.headline1!.color!; final Widget cell = GestureDetector( onTap: onTap, child: Container( height: 77, padding: EdgeInsets.only(left: 24, right: 24), - alignment: Alignment.centerLeft, color: color, - child: Text( - accountName, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - fontFamily: 'Lato', - color: textColor, - decoration: TextDecoration.none, - ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + flex: 2, + child: Text( + accountName, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + fontFamily: 'Lato', + color: textColor, + decoration: TextDecoration.none, + ), + ), + ), + if (accountBalance != null) + Expanded( + child: Text( + '${accountBalance.toString()} $currency', + textAlign: TextAlign.end, + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.w600, + fontFamily: 'Lato', + color: Theme.of(context).textTheme.headline4!.color!, + decoration: TextDecoration.none, + ), + ), + ), + ], ), ), ); diff --git a/lib/view_model/monero_account_list/account_list_item.dart b/lib/view_model/monero_account_list/account_list_item.dart index e6fc17252..10b2480c3 100644 --- a/lib/view_model/monero_account_list/account_list_item.dart +++ b/lib/view_model/monero_account_list/account_list_item.dart @@ -1,10 +1,9 @@ -import 'package:flutter/foundation.dart'; - class AccountListItem { AccountListItem( - {required this.label, required this.id, this.isSelected = false}); + {required this.label, required this.id, this.balance, this.isSelected = false}); final String label; final int id; final bool isSelected; + final String? balance; } diff --git a/lib/view_model/monero_account_list/monero_account_list_view_model.dart b/lib/view_model/monero_account_list/monero_account_list_view_model.dart index c9caa118e..4cbf95bab 100644 --- a/lib/view_model/monero_account_list/monero_account_list_view_model.dart +++ b/lib/view_model/monero_account_list/monero_account_list_view_model.dart @@ -1,3 +1,4 @@ +import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:mobx/mobx.dart'; import 'package:cw_core/wallet_base.dart'; @@ -21,6 +22,8 @@ abstract class MoneroAccountListViewModelBase with Store { this.scrollOffsetFromTop = scrollOffsetFromTop; } + CryptoCurrency get currency => _wallet.currency; + @computed List get accounts { if (_wallet.type == WalletType.haven) { @@ -39,6 +42,7 @@ abstract class MoneroAccountListViewModelBase with Store { .accounts.map((acc) => AccountListItem( label: acc.label, id: acc.id, + balance: acc.balance, isSelected: acc.id == monero!.getCurrentAccount(_wallet).id)) .toList(); } @@ -53,7 +57,9 @@ abstract class MoneroAccountListViewModelBase with Store { monero!.setCurrentAccount( _wallet, item.id, - item.label); + item.label, + item.balance, + ); } if (_wallet.type == WalletType.haven) { diff --git a/tool/configure.dart b/tool/configure.dart index c556f2f86..8dcb0f0d7 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -158,9 +158,10 @@ import 'package:cw_monero/pending_monero_transaction.dart'; const moneroCwPart = "part 'cw_monero.dart';"; const moneroContent = """ class Account { - Account({required this.id, required this.label}); + Account({required this.id, required this.label, this.balance}); final int id; final String label; + final String? balance; } class Subaddress { @@ -246,7 +247,7 @@ abstract class Monero { double formatterMoneroAmountToDouble({required int amount}); int formatterMoneroParseAmount({required String amount}); Account getCurrentAccount(Object wallet); - void setCurrentAccount(Object wallet, int id, String label); + void setCurrentAccount(Object wallet, int id, String label, String? balance); void onStartup(); int getTransactionInfoAccountId(TransactionInfo tx); WalletService createMoneroWalletService(Box walletInfoSource); From 086019d926110a809bb029da6a639035af3b0bd0 Mon Sep 17 00:00:00 2001 From: Serhii Date: Mon, 8 May 2023 23:30:14 +0300 Subject: [PATCH 20/39] CW-365-Wallet-QR-codes-not-working-properly-on-some-devices (#909) * add exception for range error * add app links for wallets * Update reference to qr.flutter clone * QrImage update * Update fullscreen_qr_page.dart * Add automatic version for wallet QR --------- Co-authored-by: OmarHatem --- android/app/src/main/AndroidManifestBase.xml | 6 ++ assets/images/restore_qr.png | Bin 0 -> 1085 bytes ios/Runner/InfoBase.plist | 60 ++++++++++++++++++ .../screens/receive/fullscreen_qr_page.dart | 5 +- lib/src/screens/receive/widgets/qr_image.dart | 2 +- .../screens/receive/widgets/qr_widget.dart | 9 ++- .../screens/restore/restore_options_page.dart | 2 +- .../screens/wallet_keys/wallet_keys_page.dart | 3 +- .../restore/wallet_restore_from_qr_code.dart | 1 + pubspec_base.yaml | 6 +- 10 files changed, 88 insertions(+), 6 deletions(-) create mode 100644 assets/images/restore_qr.png diff --git a/android/app/src/main/AndroidManifestBase.xml b/android/app/src/main/AndroidManifestBase.xml index 2dbedcba9..64adea1e7 100644 --- a/android/app/src/main/AndroidManifestBase.xml +++ b/android/app/src/main/AndroidManifestBase.xml @@ -46,8 +46,14 @@ + + + + + + Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D1JX%EK~z{r%~@SY z6j2mD_s;HWnqnzML_tD9(xT8yg_#nVkV{FF{a_ndw)R615kB-TJ@gd4)Jyabg)fyo zlogq64?zz}5fn&8L>WqD>h9{!oX)v3lWRM=S?1mk&YnAS&%N`_{W!^)gCQoHmPi2E+_?Th>+>g`V?#rony(Pc^1e4;} zGq^54iI7)eK+zFD1XlXQB!cxJpm5tHS2$i$LqW!6{AF)EJA=+@bN zFx+}c9SEvN>X1Eyj?|6DyG*e9EGW5{3vlngg@+EB*l~?+TO;&0j^NPwILaHh5klJp z?A)!!MH>XlY2ts-G8v5tC@$8Dn*vWgn;bR(#+^l~71Z{i5d>PqI>MzTcC}h~qzRyU zL;4!L+4UKFuN2evchbXeDJIg9GpWthhB&T2=7SZReUr}LuaDtWX{Nj@-*bHpw8zk( z@e_>>!A%|n+rmlQ-OQ;XJ;n4~NUE4rl3oPwgkl&x?(2l0aU$@aXl-dGPkx*XWBbhg?^VaUUsp zaM?*eEUXKpUE57F&=j$-=ZURk9!9CvKY$6oeL#0J3t)n5G{y8#raQWH`CE=trxj%0 zJOqmfmXzcy<74}@J`g9`Ol+tJ_&ziiGz{Zie!g#{rc&aw$Mz;*#+elLsv0|K^As1Z zdk?|2r882~#dZF)rRAO z#Osmwi{Rd47DgKB#BgFY2>GF4zJHVns;Df&B`9<0y~HH`LW%@hKx3j8WNd`V5y>RR zzkZF6>o9t$5PK*OwuS`^3?}9+C<-J}31(BUPOL!^tPv8dRoV(xx*NXWRLHtu%_J74 z!W;@#N|rhcR#vRq3s!x{PZ4xVQy}Ytbw3H#780zLvmjW{?0tjH&84#bitcoin + + CFBundleTypeRole + Editor + CFBundleURLName + bitcoin-wallet + CFBundleURLSchemes + + bitcoin-wallet + + + + CFBundleTypeRole + Editor + CFBundleURLName + bitcoin_wallet + CFBundleURLSchemes + + bitcoin_wallet + + CFBundleTypeRole Editor @@ -52,6 +72,26 @@ monero + + CFBundleTypeRole + Editor + CFBundleURLName + monero-wallet + CFBundleURLSchemes + + monero-wallet + + + + CFBundleTypeRole + Editor + CFBundleURLName + monero_wallet + CFBundleURLSchemes + + monero_wallet + + CFBundleTypeRole Editor @@ -62,6 +102,26 @@ litecoin + + CFBundleTypeRole + Viewer + CFBundleURLName + litecoin-wallet + CFBundleURLSchemes + + litecoin-wallet + + + + CFBundleTypeRole + Viewer + CFBundleURLName + litecoin_wallet + CFBundleURLSchemes + + litecoin_wallet + + CFBundleVersion $(CURRENT_PROJECT_VERSION) diff --git a/lib/src/screens/receive/fullscreen_qr_page.dart b/lib/src/screens/receive/fullscreen_qr_page.dart index 4bde38710..2fcc24e2c 100644 --- a/lib/src/screens/receive/fullscreen_qr_page.dart +++ b/lib/src/screens/receive/fullscreen_qr_page.dart @@ -71,7 +71,10 @@ class FullscreenQRPage extends BasePage { padding: EdgeInsets.all(10), decoration: BoxDecoration( border: Border.all(width: 3, color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!)), - child: QrImage(data: qrViewData.data, version: qrViewData.version), + child: Container( + decoration: BoxDecoration( + border: Border.all(width: 3, color: Colors.white)), + child: QrImage(data: qrViewData.data, version: qrViewData.version)), ), ), ), diff --git a/lib/src/screens/receive/widgets/qr_image.dart b/lib/src/screens/receive/widgets/qr_image.dart index c8cc622c4..c624ad3c0 100644 --- a/lib/src/screens/receive/widgets/qr_image.dart +++ b/lib/src/screens/receive/widgets/qr_image.dart @@ -16,7 +16,7 @@ class QrImage extends StatelessWidget { @override Widget build(BuildContext context) { - return qr.QrImage( + return qr.QrImageView( data: data, errorCorrectionLevel: errorCorrectionLevel, version: version ?? 9, // Previous value: 7 something happened after flutter upgrade monero wallets addresses are longer than ver. 7 ??? diff --git a/lib/src/screens/receive/widgets/qr_widget.dart b/lib/src/screens/receive/widgets/qr_widget.dart index fc58d4cec..33cc3d598 100644 --- a/lib/src/screens/receive/widgets/qr_widget.dart +++ b/lib/src/screens/receive/widgets/qr_widget.dart @@ -86,7 +86,14 @@ class QRWidget extends StatelessWidget { Theme.of(context).accentTextTheme.headline2!.backgroundColor!, ), ), - child: QrImage(data: addressListViewModel.uri.toString()), + child: Container( + decoration: BoxDecoration( + border: Border.all( + width: 3, + color:Colors.white, + ), + ), + child: QrImage(data: addressListViewModel.uri.toString())), ), ), ), diff --git a/lib/src/screens/restore/restore_options_page.dart b/lib/src/screens/restore/restore_options_page.dart index c08d2b7e4..93a44c752 100644 --- a/lib/src/screens/restore/restore_options_page.dart +++ b/lib/src/screens/restore/restore_options_page.dart @@ -25,7 +25,7 @@ class RestoreOptionsPage extends BasePage { final bool isNewInstall; final imageSeedKeys = Image.asset('assets/images/restore_wallet_image.png'); final imageBackup = Image.asset('assets/images/backup.png'); - final qrCode = Image.asset('assets/images/qr_code_icon.png'); + final qrCode = Image.asset('assets/images/restore_qr.png'); @override Widget body(BuildContext context) { diff --git a/lib/src/screens/wallet_keys/wallet_keys_page.dart b/lib/src/screens/wallet_keys/wallet_keys_page.dart index da58c4b31..c1c2e6fb5 100644 --- a/lib/src/screens/wallet_keys/wallet_keys_page.dart +++ b/lib/src/screens/wallet_keys/wallet_keys_page.dart @@ -11,6 +11,7 @@ import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/widgets/list_row.dart'; import 'package:cake_wallet/view_model/wallet_keys_view_model.dart'; import 'package:cake_wallet/routes.dart'; +import 'package:qr_flutter/qr_flutter.dart'; class WalletKeysPage extends BasePage { WalletKeysPage(this.walletKeysViewModel); @@ -32,7 +33,7 @@ class WalletKeysPage extends BasePage { await Navigator.pushNamed( context, Routes.fullscreenQR, - arguments: QrViewData(data: url.toString()), + arguments: QrViewData(data: url.toString(), version: QrVersions.auto), ); // ignore: unawaited_futures DeviceDisplayBrightness.setBrightness(brightness); diff --git a/lib/view_model/restore/wallet_restore_from_qr_code.dart b/lib/view_model/restore/wallet_restore_from_qr_code.dart index 9ebf01429..c4b772f9c 100644 --- a/lib/view_model/restore/wallet_restore_from_qr_code.dart +++ b/lib/view_model/restore/wallet_restore_from_qr_code.dart @@ -51,6 +51,7 @@ class WalletRestoreFromQRCode { static String getFormattedUri(String code) { final index = code.indexOf(':'); + if (index == -1) return throw Exception('Unexpected wallet type: $code, try to scan again'); final scheme = code.substring(0, index).replaceAll('_', '-'); final query = code.substring(index + 1).replaceAll('?', '&'); final formattedUri = '$scheme:?$query'; diff --git a/pubspec_base.yaml b/pubspec_base.yaml index 58de6edab..0d6aa36e8 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -6,7 +6,11 @@ dependencies: flutter_cupertino_localizations: ^1.0.1 intl: ^0.17.0 url_launcher: ^6.1.4 - qr_flutter: ^4.0.0 + qr_flutter: + git: + url: https://github.com/cake-tech/qr.flutter.git + ref: cake-4.0.2 + version: 4.0.2 uuid: 3.0.6 shared_preferences: ^2.0.15 flutter_secure_storage: From 9b0b1e37c95b2167d6293e9ca07f8662b9a2d5e1 Mon Sep 17 00:00:00 2001 From: Serhii Date: Mon, 8 May 2023 23:30:45 +0300 Subject: [PATCH 21/39] CW-365-Wallet-QR-codes-not-working-properly-on-some-devices (#909) * add exception for range error * add app links for wallets * Update reference to qr.flutter clone * QrImage update * Update fullscreen_qr_page.dart * Add automatic version for wallet QR --------- Co-authored-by: OmarHatem From 759e61f67ef9869642c99e2872654f30a5655fbc Mon Sep 17 00:00:00 2001 From: Godwin Asuquo <41484542+godilite@users.noreply.github.com> Date: Wed, 10 May 2023 14:35:41 +0300 Subject: [PATCH 22/39] Fix keyboard is not entering text in fields until focus (#918) --- lib/src/screens/send/widgets/send_card.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/src/screens/send/widgets/send_card.dart b/lib/src/screens/send/widgets/send_card.dart index cdae9a8df..297e7d660 100644 --- a/lib/src/screens/send/widgets/send_card.dart +++ b/lib/src/screens/send/widgets/send_card.dart @@ -548,6 +548,10 @@ class SendCardState extends State } void _setEffects(BuildContext context) { + if (_effectsInstalled) { + return; + } + if (output.address.isNotEmpty) { addressController.text = output.address; } @@ -558,10 +562,6 @@ class SendCardState extends State noteController.text = output.note; extractedAddressController.text = output.extractedAddress; - if (_effectsInstalled) { - return; - } - cryptoAmountController.addListener(() { final amount = cryptoAmountController.text; From e28e2fbdde43dd6a00910c51c485fb7c8cc52ecd Mon Sep 17 00:00:00 2001 From: Rafael Saes <76502841+saltrafael@users.noreply.github.com> Date: Wed, 10 May 2023 09:19:38 -0300 Subject: [PATCH 23/39] CW-376-picker-ui-issue (#919) * feat: use common modal widget for repeated picker logic and display * refactor: rename widget * refactor: clear wrapper logic from picker widget and move title to hasTitle * Minor code readability enhancements [skip ci] --------- Co-authored-by: OmarHatem --- .../dashboard/widgets/filter_widget.dart | 168 +++++------ lib/src/widgets/check_box_picker.dart | 112 ++++--- lib/src/widgets/picker.dart | 274 ++++++++---------- lib/src/widgets/picker_wrapper_widget.dart | 61 ++++ 4 files changed, 310 insertions(+), 305 deletions(-) create mode 100644 lib/src/widgets/picker_wrapper_widget.dart diff --git a/lib/src/screens/dashboard/widgets/filter_widget.dart b/lib/src/screens/dashboard/widgets/filter_widget.dart index 9b8c87ea3..456ed95f7 100644 --- a/lib/src/screens/dashboard/widgets/filter_widget.dart +++ b/lib/src/screens/dashboard/widgets/filter_widget.dart @@ -1,13 +1,9 @@ -import 'dart:ui'; -import 'package:cake_wallet/palette.dart'; import 'package:cake_wallet/src/screens/dashboard/widgets/filter_tile.dart'; import 'package:cake_wallet/src/widgets/section_divider.dart'; import 'package:cake_wallet/src/widgets/standard_checkbox.dart'; import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:cake_wallet/src/widgets/alert_background.dart'; -import 'package:cake_wallet/src/widgets/alert_close_button.dart'; +import 'package:cake_wallet/src/widgets/picker_wrapper_widget.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; //import 'package:date_range_picker/date_range_picker.dart' as date_rage_picker; @@ -20,89 +16,93 @@ class FilterWidget extends StatelessWidget { @override Widget build(BuildContext context) { const sectionDivider = const SectionDivider(); - return AlertBackground( - child: Stack( - alignment: Alignment.center, - children: [ - Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: EdgeInsets.only(left: 24, right: 24, top: 24), - child: ClipRRect( - borderRadius: BorderRadius.all(Radius.circular(24)), - child: Container( - color: Theme.of(context).textTheme!.bodyText1!.decorationColor!, - child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Padding( - padding: EdgeInsets.all(24.0), - child: Text( - S.of(context).filter_by, - style: TextStyle( - color: Theme.of(context).primaryTextTheme.overline!.color!, - fontSize: 16, - fontFamily: 'Lato', - decoration: TextDecoration.none, - ), + return PickerWrapperWidget( + children: [ + Padding( + padding: EdgeInsets.only(left: 24, right: 24, top: 24), + child: ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(24)), + child: Container( + color: Theme.of(context).textTheme!.bodyText1!.decorationColor!, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.all(24.0), + child: Text( + S.of(context).filter_by, + style: TextStyle( + color: Theme.of(context) + .primaryTextTheme + .overline! + .color!, + fontSize: 16, + fontFamily: 'Lato', + decoration: TextDecoration.none, ), ), - sectionDivider, - ListView.separated( - padding: EdgeInsets.zero, - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: dashboardViewModel.filterItems.length, - separatorBuilder: (context, _) => sectionDivider, - itemBuilder: (_, index1) { - final title = dashboardViewModel.filterItems.keys.elementAt(index1); - final section = dashboardViewModel.filterItems.values.elementAt(index1); - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: EdgeInsets.only(top: 20, left: 24, right: 24), - child: Text( - title, - style: TextStyle( - color: Theme.of(context).primaryTextTheme!.headline6!.color!, - fontSize: 16, - fontFamily: 'Lato', - fontWeight: FontWeight.bold, - decoration: TextDecoration.none), - ), + ), + sectionDivider, + ListView.separated( + padding: EdgeInsets.zero, + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: dashboardViewModel.filterItems.length, + separatorBuilder: (context, _) => sectionDivider, + itemBuilder: (_, index1) { + final title = dashboardViewModel.filterItems.keys + .elementAt(index1); + final section = dashboardViewModel.filterItems.values + .elementAt(index1); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: + EdgeInsets.only(top: 20, left: 24, right: 24), + child: Text( + title, + style: TextStyle( + color: Theme.of(context) + .primaryTextTheme! + .headline6! + .color!, + fontSize: 16, + fontFamily: 'Lato', + fontWeight: FontWeight.bold, + decoration: TextDecoration.none), ), - ListView.builder( - padding: EdgeInsets.symmetric(vertical: 8.0), - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: section.length, - itemBuilder: (_, index2) { - final item = section[index2]; - final content = Observer( - builder: (_) => StandardCheckbox( - value: item.value(), - caption: item.caption, - gradientBackground: true, - borderColor: Theme.of(context).dividerColor, - iconColor: Colors.white, - onChanged: (value) => item.onChanged(), - )); - return FilterTile(child: content); - }, - ) - ], - ); - }, - ), - ]), - ), - ), - ), - ], + ), + ListView.builder( + padding: EdgeInsets.symmetric(vertical: 8.0), + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: section.length, + itemBuilder: (_, index2) { + final item = section[index2]; + final content = Observer( + builder: (_) => StandardCheckbox( + value: item.value(), + caption: item.caption, + gradientBackground: true, + borderColor: + Theme.of(context).dividerColor, + iconColor: Colors.white, + onChanged: (value) => + item.onChanged(), + )); + return FilterTile(child: content); + }, + ) + ], + ); + }, + ), + ]), + ), ), - AlertCloseButton() - ], - ), + ) + ], ); } } diff --git a/lib/src/widgets/check_box_picker.dart b/lib/src/widgets/check_box_picker.dart index e874f587a..47b0be041 100644 --- a/lib/src/widgets/check_box_picker.dart +++ b/lib/src/widgets/check_box_picker.dart @@ -1,8 +1,7 @@ import 'package:cake_wallet/palette.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:flutter/material.dart'; -import 'package:cake_wallet/src/widgets/alert_background.dart'; -import 'package:cake_wallet/src/widgets/alert_close_button.dart'; +import 'package:cake_wallet/src/widgets/picker_wrapper_widget.dart'; class CheckBoxPicker extends StatefulWidget { CheckBoxPicker({ @@ -32,73 +31,57 @@ class CheckBoxPickerState extends State { @override Widget build(BuildContext context) { - return AlertBackground( - child: Column( - children: [ - Expanded( - child: Stack( - alignment: Alignment.center, - children: [ - Column( + return PickerWrapperWidget( + children: [ + if (widget.title.isNotEmpty) + Container( + padding: EdgeInsets.symmetric(horizontal: 24), + child: Text( + widget.title, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 18, + fontFamily: 'Lato', + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + color: Colors.white, + ), + ), + ), + Padding( + padding: EdgeInsets.only(left: 24, right: 24, top: 24), + child: ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(30)), + child: Container( + color: Theme.of(context).accentTextTheme.headline6!.color!, + child: ConstrainedBox( + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context).size.height * 0.65, + maxWidth: ResponsiveLayoutUtil.kPopupWidth, + ), + child: Column( mainAxisSize: MainAxisSize.min, - children: [ - if (widget.title.isNotEmpty) - Container( - padding: EdgeInsets.symmetric(horizontal: 24), - child: Text( - widget.title, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 18, - fontFamily: 'Lato', - fontWeight: FontWeight.bold, - decoration: TextDecoration.none, - color: Colors.white, - ), - ), - ), - Padding( - padding: EdgeInsets.only(left: 24, right: 24, top: 24), - child: ClipRRect( - borderRadius: BorderRadius.all(Radius.circular(30)), - child: Container( - color: Theme.of(context).accentTextTheme.headline6!.color!, - child: ConstrainedBox( - constraints: BoxConstraints( - maxHeight: MediaQuery.of(context).size.height * 0.65, - maxWidth: ResponsiveLayoutUtil.kPopupWidth, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Flexible( - child: Stack( - alignment: Alignment.center, - children: [ - items.length > 3 - ? Scrollbar( - controller: controller, - child: itemsList(), - ) - : itemsList(), - ], - ), - ), - ], - ), - ), - ), + children: [ + Flexible( + child: Stack( + alignment: Alignment.center, + children: [ + items.length > 3 + ? Scrollbar( + controller: controller, + child: itemsList(), + ) + : itemsList(), + ], ), ), ], ), - SizedBox(height: ResponsiveLayoutUtil.kPopupSpaceHeight), - AlertCloseButton(), - ], + ), ), ), - ], - ), + ), + ], ); } @@ -111,7 +94,10 @@ class CheckBoxPickerState extends State { shrinkWrap: true, separatorBuilder: (context, index) => widget.isSeparated ? Divider( - color: Theme.of(context).accentTextTheme.headline6!.backgroundColor!, + color: Theme.of(context) + .accentTextTheme + .headline6! + .backgroundColor!, height: 1, ) : const SizedBox(), diff --git a/lib/src/widgets/picker.dart b/lib/src/widgets/picker.dart index 34ff10316..e160a083b 100644 --- a/lib/src/widgets/picker.dart +++ b/lib/src/widgets/picker.dart @@ -2,9 +2,8 @@ import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:flutter/material.dart'; -import 'package:cake_wallet/src/widgets/alert_background.dart'; -import 'package:cake_wallet/src/widgets/alert_close_button.dart'; import 'package:cw_core/currency.dart'; +import 'package:cake_wallet/src/widgets/picker_wrapper_widget.dart'; class Picker extends StatefulWidget { Picker({ @@ -114,171 +113,130 @@ class _PickerState extends State> { final mq = MediaQuery.of(context); final bottom = mq.viewInsets.bottom; final height = mq.size.height - bottom; - final screenCenter = height / 2; - double closeButtonBottom = 60; double containerHeight = height * 0.65; if (bottom > 0) { // increase a bit or it gets too squished in the top containerHeight = height * 0.75; - - final containerCenter = containerHeight / 2; - final containerBottom = screenCenter - containerCenter; - - final hasTitle = widget.title == null || widget.title!.isEmpty; - - // position the close button right below the search container - closeButtonBottom = closeButtonBottom - - containerBottom + - (hasTitle ? padding : padding / 1.5); } - return AlertBackground( - child: Column( - children: [ - Expanded( - flex: 1, - child: Stack( - alignment: Alignment.center, - children: [ - Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - if (widget.title?.isNotEmpty ?? false) - Container( - padding: EdgeInsets.symmetric(horizontal: padding), - child: Text( - widget.title!, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 18, - fontFamily: 'Lato', - fontWeight: FontWeight.bold, - decoration: TextDecoration.none, - color: Colors.white, - ), - ), - ), - Padding( - padding: EdgeInsets.symmetric(horizontal: padding), - child: ClipRRect( - borderRadius: BorderRadius.all(Radius.circular(30)), - child: Container( - color: Theme.of(context) - .accentTextTheme - .headline6! - .color!, - child: ConstrainedBox( - constraints: BoxConstraints( - maxHeight: containerHeight, - maxWidth: ResponsiveLayoutUtil.kPopupWidth, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - if (widget.hintText != null) - Padding( - padding: const EdgeInsets.all(16), - child: TextFormField( - controller: searchController, - style: TextStyle( - color: Theme.of(context) - .primaryTextTheme - .headline6! - .color!), - decoration: InputDecoration( - hintText: widget.hintText, - prefixIcon: Image.asset( - "assets/images/search_icon.png"), - filled: true, - fillColor: Theme.of(context) - .accentTextTheme - .headline3! - .color!, - alignLabelWithHint: false, - contentPadding: - const EdgeInsets.symmetric( - vertical: 4, horizontal: 16), - enabledBorder: OutlineInputBorder( - borderRadius: - BorderRadius.circular(14), - borderSide: const BorderSide( - color: Colors.transparent, - )), - focusedBorder: OutlineInputBorder( - borderRadius: - BorderRadius.circular(14), - borderSide: const BorderSide( - color: Colors.transparent, - )), - ), - ), - ), - Divider( - color: Theme.of(context) - .accentTextTheme - .headline6! - .backgroundColor!, - height: 1, - ), - if (widget.selectedAtIndex != -1) - buildSelectedItem(widget.selectedAtIndex), - Flexible( - child: Stack( - alignment: Alignment.center, - children: [ - filteredItems.length > 3 - ? Scrollbar( - controller: controller, - child: itemsList(), - ) - : itemsList(), - (widget.description?.isNotEmpty ?? false) - ? Positioned( - bottom: padding, - left: padding, - right: padding, - child: Text( - widget.description!, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w500, - fontFamily: 'Lato', - decoration: - TextDecoration.none, - color: Theme.of(context) - .primaryTextTheme - .headline6! - .color!, - ), - ), - ) - : Offstage(), - ], - ), - ), - ], - ), - ), - ), - ), - ) - ], - ), - SizedBox(height: ResponsiveLayoutUtil.kPopupSpaceHeight), - AlertCloseButton(bottom: closeButtonBottom), - ], + return PickerWrapperWidget( + hasTitle: widget.title?.isNotEmpty ?? false, + children: [ + if (widget.title?.isNotEmpty ?? false) + Container( + padding: EdgeInsets.symmetric(horizontal: padding), + child: Text( + widget.title!, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 18, + fontFamily: 'Lato', + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + color: Colors.white, + ), ), ), - // gives the extra spacing using MediaQuery.viewInsets.bottom - // to simulate a keyboard area - SizedBox( - height: bottom, - ) - ], - ), + Padding( + padding: EdgeInsets.symmetric(horizontal: padding), + child: ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(30)), + child: Container( + color: Theme.of(context).accentTextTheme.headline6!.color!, + child: ConstrainedBox( + constraints: BoxConstraints( + maxHeight: containerHeight, + maxWidth: ResponsiveLayoutUtil.kPopupWidth, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (widget.hintText != null) + Padding( + padding: const EdgeInsets.all(16), + child: TextFormField( + controller: searchController, + style: TextStyle( + color: Theme.of(context) + .primaryTextTheme + .headline6! + .color!), + decoration: InputDecoration( + hintText: widget.hintText, + prefixIcon: + Image.asset("assets/images/search_icon.png"), + filled: true, + fillColor: Theme.of(context) + .accentTextTheme + .headline3! + .color!, + alignLabelWithHint: false, + contentPadding: const EdgeInsets.symmetric( + vertical: 4, horizontal: 16), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(14), + borderSide: const BorderSide( + color: Colors.transparent, + )), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(14), + borderSide: const BorderSide( + color: Colors.transparent, + )), + ), + ), + ), + Divider( + color: Theme.of(context) + .accentTextTheme + .headline6! + .backgroundColor!, + height: 1, + ), + if (widget.selectedAtIndex != -1) + buildSelectedItem(widget.selectedAtIndex), + Flexible( + child: Stack( + alignment: Alignment.center, + children: [ + filteredItems.length > 3 + ? Scrollbar( + controller: controller, + child: itemsList(), + ) + : itemsList(), + (widget.description?.isNotEmpty ?? false) + ? Positioned( + bottom: padding, + left: padding, + right: padding, + child: Text( + widget.description!, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + fontFamily: 'Lato', + decoration: TextDecoration.none, + color: Theme.of(context) + .primaryTextTheme + .headline6! + .color!, + ), + ), + ) + : Offstage(), + ], + ), + ), + ], + ), + ), + ), + ), + ) + ], ); } diff --git a/lib/src/widgets/picker_wrapper_widget.dart b/lib/src/widgets/picker_wrapper_widget.dart new file mode 100644 index 000000000..244199936 --- /dev/null +++ b/lib/src/widgets/picker_wrapper_widget.dart @@ -0,0 +1,61 @@ +import 'package:cake_wallet/utils/responsive_layout_util.dart'; +import 'package:flutter/material.dart'; +import 'package:cake_wallet/src/widgets/alert_background.dart'; +import 'package:cake_wallet/src/widgets/alert_close_button.dart'; + +class PickerWrapperWidget extends StatelessWidget { + PickerWrapperWidget({required this.children, this.hasTitle = false}); + + final List children; + final bool hasTitle; + + @override + Widget build(BuildContext context) { + final double padding = 24; + + final mq = MediaQuery.of(context); + final bottom = mq.viewInsets.bottom; + final height = mq.size.height - bottom; + final screenCenter = height / 2; + + double closeButtonBottom = 60; + double containerHeight = height * 0.65; + if (bottom > 0) { + // increase a bit or it gets too squished in the top + containerHeight = height * 0.75; + + final containerCenter = containerHeight / 2; + final containerBottom = screenCenter - containerCenter; + + // position the close button right below the search container + closeButtonBottom = closeButtonBottom - + containerBottom + (!hasTitle ? padding : padding / 1.5); + } + + return AlertBackground( + child: Column( + children: [ + Expanded( + flex: 1, + child: Stack( + alignment: Alignment.center, + children: [ + Column( + mainAxisSize: MainAxisSize.min, + children: children, + ), + SizedBox(height: ResponsiveLayoutUtil.kPopupSpaceHeight), + AlertCloseButton(bottom: closeButtonBottom), + ], + ), + ), + // gives the extra spacing using MediaQuery.viewInsets.bottom + // to simulate a keyboard area + SizedBox( + height: bottom, + ) + ], + ), + ); + } +} From 1a3d47748dd89fbb323d00a36b04198bc8a6b566 Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Wed, 10 May 2023 16:58:31 +0300 Subject: [PATCH 24/39] V4.6.4 bug fixes (#922) * Fix Concurrent modification exception * Fix minor UI issues * Change onramper crypto asset name for Litcoin * Fix secure storage issue, fetching password/PIN with null * - Fix Navigation issue while keyboard is displaying - Remove deprecated screen * Take currency From/To info from our trade not the returned one * Fix anon pay fields UI * Fix Anonpay border/icons UI * Add extra padding in QR image as a safe layer * Generalize ignored connection error * Remove Bio Auth option from desktop * Fix some Transaction info not parsed correctly --- cw_bitcoin/lib/electrum.dart | 2 +- lib/buy/onramper/onramper_buy_provider.dart | 14 +++- lib/router.dart | 6 +- .../dashboard/widgets/address_page.dart | 5 +- .../present_receive_option_picker.dart | 18 ++-- .../widgets/anonpay_currency_input_field.dart | 2 +- .../receive/widgets/anonpay_input_form.dart | 6 +- .../receive/widgets/currency_input_field.dart | 18 ++-- lib/src/screens/receive/widgets/qr_image.dart | 2 +- .../screens/receive/widgets/qr_widget.dart | 1 + .../screens/restore/restore_from_keys.dart | 83 ------------------- .../settings/security_backup_page.dart | 42 +++++----- lib/utils/exception_handler.dart | 9 +- .../exchange/exchange_trade_view_model.dart | 8 +- .../exchange/exchange_view_model.dart | 50 ++++++----- pubspec_base.yaml | 4 +- 16 files changed, 113 insertions(+), 157 deletions(-) delete mode 100644 lib/src/screens/restore/restore_from_keys.dart diff --git a/cw_bitcoin/lib/electrum.dart b/cw_bitcoin/lib/electrum.dart index 81f2da161..70b072f7b 100644 --- a/cw_bitcoin/lib/electrum.dart +++ b/cw_bitcoin/lib/electrum.dart @@ -88,7 +88,7 @@ class ElectrumClient { unterminatedString = ''; } } on TypeError catch (e) { - if (!e.toString().contains('Map') || !e.toString().contains('Map')) { + if (!e.toString().contains('Map') && !e.toString().contains('Map')) { return; } diff --git a/lib/buy/onramper/onramper_buy_provider.dart b/lib/buy/onramper/onramper_buy_provider.dart index a887f98dc..faf2e6da7 100644 --- a/lib/buy/onramper/onramper_buy_provider.dart +++ b/lib/buy/onramper/onramper_buy_provider.dart @@ -1,6 +1,7 @@ import 'package:cake_wallet/.secrets.g.dart' as secrets; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/themes/theme_base.dart'; +import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/wallet_base.dart'; class OnRamperBuyProvider { @@ -13,7 +14,16 @@ class OnRamperBuyProvider { static const _baseUrl = 'buy.onramper.com'; - static String get _apiKey => secrets.onramperApiKey; + String get _apiKey => secrets.onramperApiKey; + + String get _normalizeCryptoCurrency { + switch (_wallet.currency) { + case CryptoCurrency.ltc: + return "LTC_LITECOIN"; + default: + return _wallet.currency.title; + } + } Uri requestUrl() { String primaryColor, @@ -53,7 +63,7 @@ class OnRamperBuyProvider { return Uri.https(_baseUrl, '', { 'apiKey': _apiKey, - 'defaultCrypto': _wallet.currency.title, + 'defaultCrypto': _normalizeCryptoCurrency, 'defaultFiat': _settingsStore.fiatCurrency.title, 'wallets': '${_wallet.currency.title}:${_wallet.walletAddresses.address}', 'supportSell': "false", diff --git a/lib/router.dart b/lib/router.dart index 661a827e7..ccafe6abc 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -169,6 +169,7 @@ Route createRoute(RouteSettings settings) { fullscreenDialog: true); } else if (isSingleCoin) { return MaterialPageRoute( + fullscreenDialog: true, builder: (_) => getIt.get( param1: availableWalletTypes.first )); @@ -188,6 +189,7 @@ Route createRoute(RouteSettings settings) { case Routes.restoreWallet: return MaterialPageRoute( + fullscreenDialog: true, builder: (_) => getIt.get( param1: settings.arguments as WalletType)); @@ -509,7 +511,9 @@ Route createRoute(RouteSettings settings) { case Routes.anonPayInvoicePage: final args = settings.arguments as List; - return CupertinoPageRoute(builder: (_) => getIt.get(param1: args)); + return CupertinoPageRoute( + fullscreenDialog: true, + builder: (_) => getIt.get(param1: args)); case Routes.anonPayReceivePage: final anonInvoiceViewData = settings.arguments as AnonpayInfoBase; diff --git a/lib/src/screens/dashboard/widgets/address_page.dart b/lib/src/screens/dashboard/widgets/address_page.dart index 465c494d1..975887a7a 100644 --- a/lib/src/screens/dashboard/widgets/address_page.dart +++ b/lib/src/screens/dashboard/widgets/address_page.dart @@ -96,7 +96,10 @@ class AddressPage extends BasePage { @override Widget middle(BuildContext context) => - PresentReceiveOptionPicker(receiveOptionViewModel: receiveOptionViewModel); + PresentReceiveOptionPicker( + receiveOptionViewModel: receiveOptionViewModel, + hasWhiteBackground: currentTheme.type == ThemeType.light, + ); @override Widget Function(BuildContext, Widget) get rootWrapper => diff --git a/lib/src/screens/dashboard/widgets/present_receive_option_picker.dart b/lib/src/screens/dashboard/widgets/present_receive_option_picker.dart index 8b3ffb894..7b02765b3 100644 --- a/lib/src/screens/dashboard/widgets/present_receive_option_picker.dart +++ b/lib/src/screens/dashboard/widgets/present_receive_option_picker.dart @@ -9,14 +9,22 @@ import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:cake_wallet/generated/i18n.dart'; class PresentReceiveOptionPicker extends StatelessWidget { - PresentReceiveOptionPicker({required this.receiveOptionViewModel}); + PresentReceiveOptionPicker( + {required this.receiveOptionViewModel, this.hasWhiteBackground = false}); final ReceiveOptionViewModel receiveOptionViewModel; + final bool hasWhiteBackground; @override Widget build(BuildContext context) { - final arrowBottom = - Image.asset('assets/images/arrow_bottom_purple_icon.png', color: Colors.white, height: 6); + final textIconTheme = hasWhiteBackground + ? Theme.of(context).accentTextTheme.headline2!.backgroundColor! + : Colors.white; + final arrowBottom = Image.asset( + 'assets/images/arrow_bottom_purple_icon.png', + color: textIconTheme, + height: 6, + ); return TextButton( onPressed: () => _showPicker(context), @@ -40,14 +48,14 @@ class PresentReceiveOptionPicker extends StatelessWidget { fontSize: 18.0, fontWeight: FontWeight.bold, fontFamily: 'Lato', - color: Theme.of(context).accentTextTheme.headline2!.backgroundColor!), + color: textIconTheme), ), Observer( builder: (_) => Text(receiveOptionViewModel.selectedReceiveOption.toString(), style: TextStyle( fontSize: 10.0, fontWeight: FontWeight.w500, - color: Theme.of(context).textTheme.headline5!.color!))) + color: textIconTheme))) ], ), SizedBox(width: 5), diff --git a/lib/src/screens/receive/widgets/anonpay_currency_input_field.dart b/lib/src/screens/receive/widgets/anonpay_currency_input_field.dart index 6f2d5179c..824044333 100644 --- a/lib/src/screens/receive/widgets/anonpay_currency_input_field.dart +++ b/lib/src/screens/receive/widgets/anonpay_currency_input_field.dart @@ -32,7 +32,7 @@ class AnonpayCurrencyInputField extends StatelessWidget { decoration: BoxDecoration( border: Border( bottom: BorderSide( - color: Theme.of(context).accentTextTheme.headline6!.backgroundColor!, + color: Theme.of(context).primaryTextTheme.bodyText1!.color!, width: 1)), ), child: Padding( diff --git a/lib/src/screens/receive/widgets/anonpay_input_form.dart b/lib/src/screens/receive/widgets/anonpay_input_form.dart index 974601baa..bd8a23d9c 100644 --- a/lib/src/screens/receive/widgets/anonpay_input_form.dart +++ b/lib/src/screens/receive/widgets/anonpay_input_form.dart @@ -69,7 +69,7 @@ class AnonInvoiceForm extends StatelessWidget { BaseTextFormField( controller: nameController, focusNode: _nameFocusNode, - borderColor: Theme.of(context).accentTextTheme.headline6!.backgroundColor, + borderColor: Theme.of(context).primaryTextTheme.bodyText1!.color!, suffixIcon: SizedBox(width: 36), hintText: S.of(context).optional_name, textInputAction: TextInputAction.next, @@ -88,7 +88,7 @@ class AnonInvoiceForm extends StatelessWidget { controller: descriptionController, focusNode: _descriptionFocusNode, textInputAction: TextInputAction.next, - borderColor: Theme.of(context).accentTextTheme.headline6!.backgroundColor, + borderColor: Theme.of(context).primaryTextTheme.bodyText1!.color!, suffixIcon: SizedBox(width: 36), hintText: S.of(context).optional_description, placeholderTextStyle: TextStyle( @@ -104,7 +104,7 @@ class AnonInvoiceForm extends StatelessWidget { controller: emailController, textInputAction: TextInputAction.next, focusNode: _emailFocusNode, - borderColor: Theme.of(context).accentTextTheme.headline6!.backgroundColor, + borderColor: Theme.of(context).primaryTextTheme.bodyText1!.color!, suffixIcon: SizedBox(width: 36), keyboardType: TextInputType.emailAddress, hintText: S.of(context).optional_email_hint, diff --git a/lib/src/screens/receive/widgets/currency_input_field.dart b/lib/src/screens/receive/widgets/currency_input_field.dart index 286c6f1cd..273bbb46b 100644 --- a/lib/src/screens/receive/widgets/currency_input_field.dart +++ b/lib/src/screens/receive/widgets/currency_input_field.dart @@ -1,4 +1,3 @@ -import 'package:cake_wallet/core/amount_validator.dart'; import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; import 'package:cw_core/currency.dart'; import 'package:flutter/material.dart'; @@ -10,18 +9,20 @@ class CurrencyInputField extends StatelessWidget { required this.onTapPicker, required this.selectedCurrency, this.focusNode, - required this.controller, + required this.controller, required this.isLight, }); + final Function() onTapPicker; final Currency selectedCurrency; final FocusNode? focusNode; final TextEditingController controller; + final bool isLight; @override Widget build(BuildContext context) { final arrowBottomPurple = Image.asset( 'assets/images/arrow_bottom_purple_icon.png', - color: Colors.white, + color: Theme.of(context).accentTextTheme.headline2!.backgroundColor!, height: 8, ); final _width = MediaQuery.of(context).size.width; @@ -38,14 +39,14 @@ class CurrencyInputField extends StatelessWidget { keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true), inputFormatters: [FilteringTextInputFormatter.allow(RegExp(r'^\d+(\.|\,)?\d{0,8}'))], hintText: '0.000', - placeholderTextStyle: TextStyle( + placeholderTextStyle: isLight ? null : TextStyle( color: Theme.of(context).primaryTextTheme.headline5!.color!, fontWeight: FontWeight.w600, ), borderColor: Theme.of(context).accentTextTheme.headline6!.backgroundColor!, - textColor: Colors.white, + textColor: Theme.of(context).accentTextTheme.headline2!.backgroundColor!, textStyle: TextStyle( - color: Colors.white, + color: Theme.of(context).accentTextTheme.headline2!.backgroundColor!, ), prefixIcon: Padding( padding: EdgeInsets.only( @@ -68,7 +69,7 @@ class CurrencyInputField extends StatelessWidget { style: TextStyle( fontWeight: FontWeight.w600, fontSize: 16, - color: Colors.white, + color: Theme.of(context).accentTextTheme.headline2!.backgroundColor!, ), ), if (selectedCurrency.tag != null) @@ -103,7 +104,8 @@ class CurrencyInputField extends StatelessWidget { style: TextStyle( fontWeight: FontWeight.w600, fontSize: 20, - color: Colors.white, + color: + Theme.of(context).accentTextTheme.headline2!.backgroundColor!, ), ), ), diff --git a/lib/src/screens/receive/widgets/qr_image.dart b/lib/src/screens/receive/widgets/qr_image.dart index c624ad3c0..f4c1eb177 100644 --- a/lib/src/screens/receive/widgets/qr_image.dart +++ b/lib/src/screens/receive/widgets/qr_image.dart @@ -23,7 +23,7 @@ class QrImage extends StatelessWidget { size: size, foregroundColor: Colors.black, backgroundColor: Colors.white, - padding: EdgeInsets.zero, + padding: const EdgeInsets.all(8.0), ); } } diff --git a/lib/src/screens/receive/widgets/qr_widget.dart b/lib/src/screens/receive/widgets/qr_widget.dart index 33cc3d598..610b0d245 100644 --- a/lib/src/screens/receive/widgets/qr_widget.dart +++ b/lib/src/screens/receive/widgets/qr_widget.dart @@ -119,6 +119,7 @@ class QRWidget extends StatelessWidget { controller: amountController, onTapPicker: () => _presentPicker(context), selectedCurrency: addressListViewModel.selectedCurrency, + isLight: isLight, ), ), ), diff --git a/lib/src/screens/restore/restore_from_keys.dart b/lib/src/screens/restore/restore_from_keys.dart deleted file mode 100644 index cd0116e56..000000000 --- a/lib/src/screens/restore/restore_from_keys.dart +++ /dev/null @@ -1,83 +0,0 @@ -import 'package:flutter/services.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/src/widgets/blockchain_height_widget.dart'; -import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; - -class RestoreFromKeysFrom extends StatefulWidget { - @override - _RestoreFromKeysFromState createState() => _RestoreFromKeysFromState(); -} - -class _RestoreFromKeysFromState extends State { - final _formKey = GlobalKey(); - final _blockchainHeightKey = GlobalKey(); - final _nameController = TextEditingController(); - final _addressController = TextEditingController(); - final _viewKeyController = TextEditingController(); - final _spendKeyController = TextEditingController(); - final _wifController = TextEditingController(); - - @override - void initState() { - // _nameController.addListener(() => - // widget.walletRestorationFromKeysVM.name = _nameController.text); - // _addressController.addListener(() => - // widget.walletRestorationFromKeysVM.address = _addressController.text); - // _viewKeyController.addListener(() => - // widget.walletRestorationFromKeysVM.viewKey = _viewKeyController.text); - // _spendKeyController.addListener(() => - // widget.walletRestorationFromKeysVM.spendKey = _spendKeyController.text); - // _wifController.addListener(() => - // widget.walletRestorationFromKeysVM.wif = _wifController.text); - - super.initState(); - } - - @override - void dispose() { - _nameController.dispose(); - _addressController.dispose(); - _viewKeyController.dispose(); - _spendKeyController.dispose(); - _wifController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Container( - padding: EdgeInsets.only(left: 24, right: 24), - child: Form( - key: _formKey, - child: Column(children: [ - BaseTextFormField( - controller: _addressController, - keyboardType: TextInputType.multiline, - maxLines: null, - hintText: S.of(context).restore_address, - ), - Container( - padding: EdgeInsets.only(top: 20.0), - child: BaseTextFormField( - controller: _viewKeyController, - hintText: S.of(context).restore_view_key_private, - )), - Container( - padding: EdgeInsets.only(top: 20.0), - child: BaseTextFormField( - controller: _spendKeyController, - hintText: S.of(context).restore_spend_key_private, - )), - BlockchainHeightWidget( - key: _blockchainHeightKey, - onHeightChange: (height) { - // widget.walletRestorationFromKeysVM.height = height; - print(height); - }), - ]), - ), - ); - } -} diff --git a/lib/src/screens/settings/security_backup_page.dart b/lib/src/screens/settings/security_backup_page.dart index 0933560f7..24c597a00 100644 --- a/lib/src/screens/settings/security_backup_page.dart +++ b/lib/src/screens/settings/security_backup_page.dart @@ -8,6 +8,7 @@ import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arro import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.dart'; import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart'; import 'package:cake_wallet/src/widgets/standard_list.dart'; +import 'package:cake_wallet/utils/device_info.dart'; import 'package:cake_wallet/view_model/settings/security_settings_view_model.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; @@ -48,29 +49,30 @@ class SecurityBackupPage extends BasePage { ), ), StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)), - Observer(builder: (_) { - return SettingsSwitcherCell( - title: S.current.settings_allow_biometrical_authentication, - value: _securitySettingsViewModel.allowBiometricalAuthentication, - onValueChange: (BuildContext context, bool value) { - if (value) { - _authService.authenticateAction(context, - onAuthSuccess: (isAuthenticatedSuccessfully) async { - if (isAuthenticatedSuccessfully) { - if (await _securitySettingsViewModel.biometricAuthenticated()) { + if (DeviceInfo.instance.isMobile) + Observer(builder: (_) { + return SettingsSwitcherCell( + title: S.current.settings_allow_biometrical_authentication, + value: _securitySettingsViewModel.allowBiometricalAuthentication, + onValueChange: (BuildContext context, bool value) { + if (value) { + _authService.authenticateAction(context, + onAuthSuccess: (isAuthenticatedSuccessfully) async { + if (isAuthenticatedSuccessfully) { + if (await _securitySettingsViewModel.biometricAuthenticated()) { + _securitySettingsViewModel + .setAllowBiometricalAuthentication(isAuthenticatedSuccessfully); + } + } else { _securitySettingsViewModel .setAllowBiometricalAuthentication(isAuthenticatedSuccessfully); } - } else { - _securitySettingsViewModel - .setAllowBiometricalAuthentication(isAuthenticatedSuccessfully); - } - }); - } else { - _securitySettingsViewModel.setAllowBiometricalAuthentication(value); - } - }); - }), + }); + } else { + _securitySettingsViewModel.setAllowBiometricalAuthentication(value); + } + }); + }), Observer(builder: (_) { return SettingsPickerCell( title: S.current.require_pin_after, diff --git a/lib/utils/exception_handler.dart b/lib/utils/exception_handler.dart index d3689e7e0..4b156ade1 100644 --- a/lib/utils/exception_handler.dart +++ b/lib/utils/exception_handler.dart @@ -141,9 +141,9 @@ class ExceptionHandler { "errno = 103", // SocketException: Software caused connection abort "errno = 104", // SocketException: Connection reset by peer "errno = 110", // SocketException: Connection timed out - "HttpException: Connection reset by peer", - "HttpException: Connection closed before full header was received", - "HandshakeException: Connection terminated during handshake", + "Connection reset by peer", + "Connection closed before full header was received", + "Connection terminated during handshake", "PERMISSION_NOT_GRANTED", ]; @@ -172,7 +172,7 @@ class ExceptionHandler { } await file.writeAsString( - "App Version: $currentVersion\n\nDevice Info $deviceInfo", + "App Version: $currentVersion\n\nDevice Info $deviceInfo\n\n", mode: FileMode.append, ); } @@ -193,6 +193,7 @@ class ExceptionHandler { 'systemVersion': data.systemVersion, 'model': data.model, 'localizedModel': data.localizedModel, + 'isPhysicalDevice': data.isPhysicalDevice, }; } diff --git a/lib/view_model/exchange/exchange_trade_view_model.dart b/lib/view_model/exchange/exchange_trade_view_model.dart index 194dc9d45..1d9f4f582 100644 --- a/lib/view_model/exchange/exchange_trade_view_model.dart +++ b/lib/view_model/exchange/exchange_trade_view_model.dart @@ -123,8 +123,8 @@ abstract class ExchangeTradeViewModelBase with Store { } void _updateItems() { - final tagFrom = trade.from.tag != null ? '${trade.from.tag}' + ' ' : ''; - final tagTo = trade.to.tag != null ? '${trade.to.tag}' + ' ' : ''; + final tagFrom = tradesStore.trade!.from.tag != null ? '${tradesStore.trade!.from.tag}' + ' ' : ''; + final tagTo = tradesStore.trade!.to.tag != null ? '${tradesStore.trade!.to.tag}' + ' ' : ''; items.clear(); items.add(ExchangeTradeItem( title: "${trade.provider.title} ${S.current.id}", data: '${trade.id}', isCopied: true)); @@ -142,11 +142,11 @@ abstract class ExchangeTradeViewModelBase with Store { items.addAll([ ExchangeTradeItem(title: S.current.amount, data: '${trade.amount}', isCopied: true), ExchangeTradeItem( - title: S.current.send_to_this_address('${trade.from}', tagFrom) + ':', + title: S.current.send_to_this_address('${tradesStore.trade!.from}', tagFrom) + ':', data: trade.inputAddress ?? '', isCopied: true), ExchangeTradeItem( - title: S.current.arrive_in_this_address('${trade.to}', tagTo) + ':', + title: S.current.arrive_in_this_address('${tradesStore.trade!.to}', tagTo) + ':', data: trade.payoutAddress ?? '', isCopied: true), ]); diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart index 3c016d4b0..77fb9a3d4 100644 --- a/lib/view_model/exchange/exchange_view_model.dart +++ b/lib/view_model/exchange/exchange_view_model.dart @@ -3,7 +3,6 @@ import 'dart:collection'; import 'dart:convert'; import 'package:cake_wallet/entities/exchange_api_mode.dart'; -import 'package:cake_wallet/entities/fiat_api_mode.dart'; import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart'; import 'package:cake_wallet/exchange/sideshift/sideshift_request.dart'; @@ -244,7 +243,7 @@ abstract class ExchangeViewModelBase with Store { List depositCurrencies; - NumberFormat _cryptoNumberFormat; + final NumberFormat _cryptoNumberFormat; final SettingsStore _settingsStore; @@ -388,27 +387,36 @@ abstract class ExchangeViewModelBase with Store { double? lowestMin = double.maxFinite; double? highestMax = 0.0; - for (var provider in selectedProviders) { - /// if this provider is not valid for the current pair, skip it - if (!providersForCurrentPair().contains(provider)) { - continue; - } - - try { - final tempLimits = await provider.fetchLimits( - from: from, - to: to, - isFixedRateMode: isFixedRateMode); - - if (lowestMin != null && (tempLimits.min ?? -1) < lowestMin) { - lowestMin = tempLimits.min; + try { + for (var provider in selectedProviders) { + /// if this provider is not valid for the current pair, skip it + if (!providersForCurrentPair().contains(provider)) { + continue; } - if (highestMax != null && (tempLimits.max ?? double.maxFinite) > highestMax) { - highestMax = tempLimits.max; + + try { + final tempLimits = await provider.fetchLimits( + from: from, + to: to, + isFixedRateMode: isFixedRateMode); + + if (lowestMin != null && (tempLimits.min ?? -1) < lowestMin) { + lowestMin = tempLimits.min; + } + if (highestMax != null && (tempLimits.max ?? double.maxFinite) > highestMax) { + highestMax = tempLimits.max; + } + } catch (e) { + continue; } - } catch (e) { - continue; } + } on ConcurrentModificationError { + /// if user changed the selected providers while fetching limits + /// then delay the fetching limits a bit and try again + /// + /// this is because the limitation of collections that + /// you can't modify it while iterating through it + Future.delayed(Duration(milliseconds: 200), loadLimits); } if (lowestMin != double.maxFinite) { @@ -534,7 +542,7 @@ abstract class ExchangeViewModelBase with Store { /// /// this is because the limitation of the SplayTreeMap that /// you can't modify it while iterating through it - Future.delayed(Duration(milliseconds: 500), createTrade); + Future.delayed(Duration(milliseconds: 200), createTrade); } } diff --git a/pubspec_base.yaml b/pubspec_base.yaml index 0d6aa36e8..ebe276a54 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -17,8 +17,8 @@ dependencies: git: url: https://github.com/cake-tech/flutter_secure_storage.git path: flutter_secure_storage - ref: cake-6.0.0 - version: 6.0.0 + ref: cake-8.0.0 + version: 8.0.0 # provider: ^6.0.3 rxdart: ^0.27.4 yaml: ^3.1.1 From a0117096a820d5170f3356212020d5ddfd5a92fd Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Thu, 11 May 2023 22:55:07 +0300 Subject: [PATCH 25/39] - New version update (#924) - Remove unAccepted URI schemes --- assets/text/Monerocom_Release_Notes.txt | 4 ++- assets/text/Release_Notes.txt | 7 ++--- ios/Podfile.lock | 8 ++--- ios/Runner/InfoBase.plist | 30 ------------------- macos/Flutter/GeneratedPluginRegistrant.swift | 2 +- macos/Podfile.lock | 8 ++--- scripts/android/app_env.sh | 8 ++--- scripts/ios/app_env.sh | 8 ++--- scripts/macos/app_env.sh | 4 +-- 9 files changed, 25 insertions(+), 54 deletions(-) diff --git a/assets/text/Monerocom_Release_Notes.txt b/assets/text/Monerocom_Release_Notes.txt index be218630d..56fb3deb7 100644 --- a/assets/text/Monerocom_Release_Notes.txt +++ b/assets/text/Monerocom_Release_Notes.txt @@ -1 +1,3 @@ -Fix Restore from QR code \ No newline at end of file +Reliability fixes for PIN login, transaction appearance, keyboard inputs, and QR codes +Show amount received by each Monero account in account overview +Other bugfixes \ No newline at end of file diff --git a/assets/text/Release_Notes.txt b/assets/text/Release_Notes.txt index d9acd464f..56fb3deb7 100644 --- a/assets/text/Release_Notes.txt +++ b/assets/text/Release_Notes.txt @@ -1,4 +1,3 @@ -Fix for QR codes -Fix for creating sub-addresses -Fix Add/Edit nodes -Fix issues with text/amount fields \ No newline at end of file +Reliability fixes for PIN login, transaction appearance, keyboard inputs, and QR codes +Show amount received by each Monero account in account overview +Other bugfixes \ No newline at end of file diff --git a/ios/Podfile.lock b/ios/Podfile.lock index d0e05f5dd..62074faed 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -113,7 +113,7 @@ PODS: - OrderedSet (~> 5.0) - flutter_mailer (0.0.1): - Flutter - - flutter_secure_storage (3.3.1): + - flutter_secure_storage (6.0.0): - Flutter - in_app_review (0.2.0): - Flutter @@ -264,19 +264,19 @@ SPEC CHECKSUMS: Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 flutter_inappwebview: bfd58618f49dc62f2676de690fc6dcda1d6c3721 flutter_mailer: 2ef5a67087bc8c6c4cefd04a178bf1ae2c94cd83 - flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec + flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be in_app_review: 318597b3a06c22bb46dc454d56828c85f444f99d local_auth_ios: c6cf091ded637a88f24f86a8875d8b0f526e2605 MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62 - path_provider_foundation: c68054786f1b4f3343858c1e1d0caaded73f0be9 + path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8 permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce platform_device_id: 81b3e2993881f87d0c82ef151dc274df4869aef5 Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96 SDWebImage: fd7e1a22f00303e058058278639bf6196ee431fe share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68 - shared_preferences_foundation: 986fc17f3d3251412d18b0265f9c64113a8c2472 + shared_preferences_foundation: e2dae3258e06f44cc55f49d42024fd8dd03c590c SwiftProtobuf: afced68785854575756db965e9da52bbf3dc45e7 SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f uni_links: d97da20c7701486ba192624d99bffaaffcfc298a diff --git a/ios/Runner/InfoBase.plist b/ios/Runner/InfoBase.plist index 13a2c24df..29cd24cb4 100644 --- a/ios/Runner/InfoBase.plist +++ b/ios/Runner/InfoBase.plist @@ -52,16 +52,6 @@ bitcoin-wallet - - CFBundleTypeRole - Editor - CFBundleURLName - bitcoin_wallet - CFBundleURLSchemes - - bitcoin_wallet - - CFBundleTypeRole Editor @@ -82,16 +72,6 @@ monero-wallet - - CFBundleTypeRole - Editor - CFBundleURLName - monero_wallet - CFBundleURLSchemes - - monero_wallet - - CFBundleTypeRole Editor @@ -112,16 +92,6 @@ litecoin-wallet - - CFBundleTypeRole - Viewer - CFBundleURLName - litecoin_wallet - CFBundleURLSchemes - - litecoin_wallet - - CFBundleVersion $(CURRENT_PROJECT_VERSION) diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 437237153..c7f78b30f 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -25,7 +25,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { CwMoneroPlugin.register(with: registry.registrar(forPlugin: "CwMoneroPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) DevicelocalePlugin.register(with: registry.registrar(forPlugin: "DevicelocalePlugin")) - FlutterSecureStorageMacosPlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStorageMacosPlugin")) + FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) InAppReviewPlugin.register(with: registry.registrar(forPlugin: "InAppReviewPlugin")) FLTPackageInfoPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) diff --git a/macos/Podfile.lock b/macos/Podfile.lock index ea15b9495..3564ab712 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -23,7 +23,7 @@ PODS: - FlutterMacOS - devicelocale (0.0.1): - FlutterMacOS - - flutter_secure_storage_macos (3.3.1): + - flutter_secure_storage_macos (6.1.1): - FlutterMacOS - FlutterMacOS (1.0.0) - in_app_review (0.2.0): @@ -106,16 +106,16 @@ SPEC CHECKSUMS: cw_monero: ec03de55a19c4a2b174ea687e0f4202edc716fa4 device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f devicelocale: 9f0f36ac651cabae2c33f32dcff4f32b61c38225 - flutter_secure_storage_macos: 6ceee8fbc7f484553ad17f79361b556259df89aa + flutter_secure_storage_macos: d56e2d218c1130b262bef8b4a7d64f88d7f9c9ea FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 in_app_review: a850789fad746e89bce03d4aeee8078b45a53fd0 package_info: 6eba2fd8d3371dda2d85c8db6fe97488f24b74b2 - path_provider_foundation: c68054786f1b4f3343858c1e1d0caaded73f0be9 + path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8 platform_device_id: 3e414428f45df149bbbfb623e2c0ca27c545b763 platform_device_id_macos: f763bb55f088be804d61b96eb4710b8ab6598e94 Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96 share_plus_macos: 853ee48e7dce06b633998ca0735d482dd671ade4 - shared_preferences_foundation: 986fc17f3d3251412d18b0265f9c64113a8c2472 + shared_preferences_foundation: e2dae3258e06f44cc55f49d42024fd8dd03c590c url_launcher_macos: 5335912b679c073563f29d89d33d10d459f95451 wakelock_macos: bc3f2a9bd8d2e6c89fee1e1822e7ddac3bd004a9 diff --git a/scripts/android/app_env.sh b/scripts/android/app_env.sh index 72d3aabfc..98e5762f2 100644 --- a/scripts/android/app_env.sh +++ b/scripts/android/app_env.sh @@ -14,14 +14,14 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN) APP_ANDROID_TYPE=$1 MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="1.3.5" -MONERO_COM_BUILD_NUMBER=48 +MONERO_COM_VERSION="1.3.6" +MONERO_COM_BUILD_NUMBER=49 MONERO_COM_BUNDLE_ID="com.monero.app" MONERO_COM_PACKAGE="com.monero.app" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="4.6.4" -CAKEWALLET_BUILD_NUMBER=158 +CAKEWALLET_VERSION="4.6.5" +CAKEWALLET_BUILD_NUMBER=159 CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet" CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet" diff --git a/scripts/ios/app_env.sh b/scripts/ios/app_env.sh index 412ec5aa9..0ee76e82d 100644 --- a/scripts/ios/app_env.sh +++ b/scripts/ios/app_env.sh @@ -13,13 +13,13 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN) APP_IOS_TYPE=$1 MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="1.3.5" -MONERO_COM_BUILD_NUMBER=46 +MONERO_COM_VERSION="1.3.6" +MONERO_COM_BUILD_NUMBER=47 MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="4.6.4" -CAKEWALLET_BUILD_NUMBER=153 +CAKEWALLET_VERSION="4.6.5" +CAKEWALLET_BUILD_NUMBER=154 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" HAVEN_NAME="Haven" diff --git a/scripts/macos/app_env.sh b/scripts/macos/app_env.sh index 785dc9e24..09a077303 100755 --- a/scripts/macos/app_env.sh +++ b/scripts/macos/app_env.sh @@ -15,8 +15,8 @@ if [ -n "$1" ]; then fi CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="1.0.3" -CAKEWALLET_BUILD_NUMBER=20 +CAKEWALLET_VERSION="1.0.4" +CAKEWALLET_BUILD_NUMBER=22 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" if ! [[ " ${TYPES[*]} " =~ " ${APP_MACOS_TYPE} " ]]; then From 0f44bc094f2128286941e71f78055f39cf5ce985 Mon Sep 17 00:00:00 2001 From: Justin Ehrenhofer Date: Mon, 15 May 2023 07:02:29 -0500 Subject: [PATCH 26/39] Update readme (#927) --- README.md | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 3077366ac..0fb45f6de 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,10 @@ ## Links * Website: https://cakewallet.com -* App Store: https://cakewallet.com/ios +* App Store (iOS / MacOS): https://cakewallet.com/ios * Google Play: https://cakewallet.com/gp * APK: https://github.com/cake-tech/cake_wallet/releases +* Linux: https://github.com/cake-tech/cake_wallet/releases ## Features @@ -15,18 +16,22 @@ * Completely noncustodial. *Your keys, your coins.* * Built-in exchange for dozens of pairs -* Buy cryptocurrency with credit/debit/bank +* Easily pay cryptocurrency invoices with fixed rate exchanges +* Buy cryptocurrency (BTC/LTC/XMR) with credit/debit/bank * Sell cryptocurrency by bank transfer +* Purchase gift cards at a discount using only an email with [Cake Pay](https://cakepay.com), available in-app * Scan QR codes for easy cryptocurrency transfers * Create several wallets * Select your own custom nodes/servers * Address book * Backup to external location or iCloud * Send to OpenAlias, Unstoppable Domains, Yats, and FIO Crypto Handles -* Set custom fee levels +* Set desired network fee level * Store local transaction notes * Extremely simple user experience * Convenient exchange and sending templates for recurring payments +* Create donation links and invoices in the receive screen +* Robust privacy settings (eg: Tor-only connections) ### Monero Specific Features @@ -34,13 +39,13 @@ * Full support for Monero subaddresses and accounts * Specify restore height for faster syncing * Specify multiple recipients for batch sending +* Optionally set Monero nodes as trusted for faster syncing ### Bitcoin Specific Features * Bitcoin coin control (specify specific outputs to spend) * Automatically generate new addresses * Specify multiple recipients for batch sending -* Buy BTC with over a dozen fiat currencies * Sell BTC for USD ### Litecoin Specific Features @@ -48,7 +53,6 @@ * Litecoin coin control (specify specific outputs to spend) * Automatically generate new addresses * Specify multiple recipients for batch sending -* Buy LTC with over a dozen fiat currencies ### Haven Specific Features @@ -63,7 +67,7 @@ ## Links * Website: https://monero.com -* App Store: https://apps.apple.com/app/id1601990386 +* App Store (iOS): https://apps.apple.com/app/id1601990386 * Google Play: https://play.google.com/store/apps/details?id=com.monero.app * APK: https://github.com/cake-tech/cake_wallet/releases @@ -71,6 +75,8 @@ We have 24/7 free support. Please contact support@cakewallet.com +We have excellent user guides, which are also open-source and open for contributions: https://guides.cakewallet.com + # Build Instructions More instructions to follow @@ -136,3 +142,7 @@ The only parts to be translated, if needed, are the values m and s after the var 3. Add the raw mapping underneath in `lib/entities/fiat_currency.dart` following the same format as the others. 4. Add a flag of the issuing country or organization to `assets/images/flags/XXXX.png`, replacing XXXX with the ISO 3166-1 alpha-3 code used above (eg: `usa.png`, `eur.png`). Do not add this if the flag with the same name already exists. The image must be 42x26 pixels with a 3 pixels of transparent margin on all 4 sides. + +--- + +Copyright (C) 2018-2023 Cake Labs LLC From 7fa7c45c0cd54006a4c89d3fbebd25bc86f45f0d Mon Sep 17 00:00:00 2001 From: Rafael Saes <76502841+saltrafael@users.noreply.github.com> Date: Mon, 15 May 2023 09:26:56 -0300 Subject: [PATCH 27/39] CW-370-add-options-to-enable-disable-buy-and-sell-actions (#920) * feat: In the privacy settings screen, add 2 options to Enable/Disable Buy and Sell actions * fix: add strings * fix: add the rest of the strings * fix: disable instead of hide buy & sell buttons * fix: use disableSell * fix: main actions able to be onTapped even when disabled --- lib/core/backup_service.dart | 16 +++++++ lib/entities/main_actions.dart | 42 ++++++++++--------- lib/entities/preferences_key.dart | 2 + lib/src/screens/settings/privacy_page.dart | 18 ++++++-- lib/store/settings_store.dart | 30 +++++++++++++ .../dashboard/dashboard_view_model.dart | 19 ++++----- .../settings/privacy_settings_view_model.dart | 12 ++++++ res/values/strings_ar.arb | 4 +- res/values/strings_bg.arb | 4 +- res/values/strings_cs.arb | 4 +- res/values/strings_de.arb | 4 +- res/values/strings_en.arb | 4 +- res/values/strings_es.arb | 4 +- res/values/strings_fr.arb | 4 +- res/values/strings_hi.arb | 4 +- res/values/strings_hr.arb | 4 +- res/values/strings_id.arb | 4 +- res/values/strings_it.arb | 4 +- res/values/strings_ja.arb | 4 +- res/values/strings_ko.arb | 4 +- res/values/strings_my.arb | 4 +- res/values/strings_nl.arb | 4 +- res/values/strings_pl.arb | 4 +- res/values/strings_pt.arb | 4 +- res/values/strings_ru.arb | 4 +- res/values/strings_th.arb | 4 +- res/values/strings_tr.arb | 4 +- res/values/strings_uk.arb | 4 +- res/values/strings_ur.arb | 4 +- res/values/strings_zh.arb | 4 +- 30 files changed, 175 insertions(+), 56 deletions(-) diff --git a/lib/core/backup_service.dart b/lib/core/backup_service.dart index 20fd753d8..2870c4488 100644 --- a/lib/core/backup_service.dart +++ b/lib/core/backup_service.dart @@ -210,6 +210,8 @@ class BackupService { final currentFiatCurrency = data[PreferencesKey.currentFiatCurrencyKey] as String?; final shouldSaveRecipientAddress = data[PreferencesKey.shouldSaveRecipientAddressKey] as bool?; final isAppSecure = data[PreferencesKey.isAppSecureKey] as bool?; + final disableBuy = data[PreferencesKey.disableBuyKey] as bool?; + final disableSell = data[PreferencesKey.disableSellKey] as bool?; final currentTransactionPriorityKeyLegacy = data[PreferencesKey.currentTransactionPriorityKeyLegacy] as int?; final allowBiometricalAuthentication = data[PreferencesKey.allowBiometricalAuthenticationKey] as bool?; final currentBitcoinElectrumSererId = data[PreferencesKey.currentBitcoinElectrumSererIdKey] as int?; @@ -251,6 +253,16 @@ class BackupService { PreferencesKey.isAppSecureKey, isAppSecure); + if (disableBuy != null) + await _sharedPreferences.setBool( + PreferencesKey.disableBuyKey, + disableBuy); + + if (disableSell != null) + await _sharedPreferences.setBool( + PreferencesKey.disableSellKey, + disableSell); + if (currentTransactionPriorityKeyLegacy != null) await _sharedPreferences.setInt( PreferencesKey.currentTransactionPriorityKeyLegacy, @@ -421,6 +433,10 @@ class BackupService { _sharedPreferences.getString(PreferencesKey.currentFiatCurrencyKey), PreferencesKey.shouldSaveRecipientAddressKey: _sharedPreferences .getBool(PreferencesKey.shouldSaveRecipientAddressKey), + PreferencesKey.disableBuyKey: _sharedPreferences + .getBool(PreferencesKey.disableBuyKey), + PreferencesKey.disableSellKey: _sharedPreferences + .getBool(PreferencesKey.disableSellKey), PreferencesKey.isDarkThemeLegacy: _sharedPreferences.getBool(PreferencesKey.isDarkThemeLegacy), PreferencesKey.currentPinLength: diff --git a/lib/entities/main_actions.dart b/lib/entities/main_actions.dart index ec70b95d9..f7a322096 100644 --- a/lib/entities/main_actions.dart +++ b/lib/entities/main_actions.dart @@ -47,23 +47,23 @@ class MainActions { switch (walletType) { case WalletType.bitcoin: case WalletType.litecoin: - if (DeviceInfo.instance.isMobile) { - Navigator.of(context).pushNamed(Routes.onramperPage); - } else { - final uri = getIt - .get() - .requestUrl(); - await launchUrl(uri); + if (viewModel.isEnabledBuyAction) { + if (DeviceInfo.instance.isMobile) { + Navigator.of(context).pushNamed(Routes.onramperPage); + } else { + final uri = getIt.get().requestUrl(); + await launchUrl(uri); + } } break; case WalletType.monero: - if (DeviceInfo.instance.isMobile) { - Navigator.of(context).pushNamed(Routes.payfuraPage); - } else { - final uri = getIt - .get() - .requestUrl(); - await launchUrl(uri); + if (viewModel.isEnabledBuyAction) { + if (DeviceInfo.instance.isMobile) { + Navigator.of(context).pushNamed(Routes.payfuraPage); + } else { + final uri = getIt.get().requestUrl(); + await launchUrl(uri); + } } break; default: @@ -118,12 +118,14 @@ class MainActions { switch (walletType) { case WalletType.bitcoin: - final moonPaySellProvider = MoonPaySellProvider(); - final uri = await moonPaySellProvider.requestUrl( - currency: viewModel.wallet.currency, - refundWalletAddress: viewModel.wallet.walletAddresses.address, - ); - await launchUrl(uri); + if (viewModel.isEnabledSellAction) { + final moonPaySellProvider = MoonPaySellProvider(); + final uri = await moonPaySellProvider.requestUrl( + currency: viewModel.wallet.currency, + refundWalletAddress: viewModel.wallet.walletAddresses.address, + ); + await launchUrl(uri); + } break; default: await showPopUp( diff --git a/lib/entities/preferences_key.dart b/lib/entities/preferences_key.dart index 90b57668d..21966a06a 100644 --- a/lib/entities/preferences_key.dart +++ b/lib/entities/preferences_key.dart @@ -10,6 +10,8 @@ class PreferencesKey { static const currentBalanceDisplayModeKey = 'current_balance_display_mode'; static const shouldSaveRecipientAddressKey = 'save_recipient_address'; static const isAppSecureKey = 'is_app_secure'; + static const disableBuyKey = 'disable_buy'; + static const disableSellKey = 'disable_sell'; static const currentFiatApiModeKey = 'current_fiat_api_mode'; static const allowBiometricalAuthenticationKey = 'allow_biometrical_authentication'; diff --git a/lib/src/screens/settings/privacy_page.dart b/lib/src/screens/settings/privacy_page.dart index 81e2715f2..d21ae0ae8 100644 --- a/lib/src/screens/settings/privacy_page.dart +++ b/lib/src/screens/settings/privacy_page.dart @@ -50,11 +50,23 @@ class PrivacyPage extends BasePage { _privacySettingsViewModel.setShouldSaveRecipientAddress(value); }), if (Platform.isAndroid) + SettingsSwitcherCell( + title: S.current.prevent_screenshots, + value: _privacySettingsViewModel.isAppSecure, + onValueChange: (BuildContext _, bool value) { + _privacySettingsViewModel.setIsAppSecure(value); + }), SettingsSwitcherCell( - title: S.current.prevent_screenshots, - value: _privacySettingsViewModel.isAppSecure, + title: S.current.disable_buy, + value: _privacySettingsViewModel.disableBuy, onValueChange: (BuildContext _, bool value) { - _privacySettingsViewModel.setIsAppSecure(value); + _privacySettingsViewModel.setDisableBuy(value); + }), + SettingsSwitcherCell( + title: S.current.disable_sell, + value: _privacySettingsViewModel.disableSell, + onValueChange: (BuildContext _, bool value) { + _privacySettingsViewModel.setDisableSell(value); }), ], ); diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index 400f7ac88..7ec784d93 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -34,6 +34,8 @@ abstract class SettingsStoreBase with Store { required BalanceDisplayMode initialBalanceDisplayMode, required bool initialSaveRecipientAddress, required bool initialAppSecure, + required bool initialDisableBuy, + required bool initialDisableSell, required FiatApiMode initialFiatMode, required bool initialAllowBiometricalAuthentication, required ExchangeApiMode initialExchangeStatus, @@ -57,6 +59,8 @@ abstract class SettingsStoreBase with Store { balanceDisplayMode = initialBalanceDisplayMode, shouldSaveRecipientAddress = initialSaveRecipientAddress, isAppSecure = initialAppSecure, + disableBuy = initialDisableBuy, + disableSell = initialDisableSell, fiatApiMode = initialFiatMode, allowBiometricalAuthentication = initialAllowBiometricalAuthentication, shouldShowMarketPlaceInDashboard = initialShouldShowMarketPlaceInDashboard, @@ -130,6 +134,16 @@ abstract class SettingsStoreBase with Store { } }); + reaction( + (_) => disableBuy, + (bool disableBuy) => sharedPreferences.setBool( + PreferencesKey.disableBuyKey, disableBuy)); + + reaction( + (_) => disableSell, + (bool disableSell) => sharedPreferences.setBool( + PreferencesKey.disableSellKey, disableSell)); + if (Platform.isAndroid) { setIsAppSecureNative(isAppSecure); } @@ -217,6 +231,12 @@ abstract class SettingsStoreBase with Store { @observable bool isAppSecure; + @observable + bool disableBuy; + + @observable + bool disableSell; + @observable bool allowBiometricalAuthentication; @@ -309,6 +329,10 @@ abstract class SettingsStoreBase with Store { sharedPreferences.getBool(PreferencesKey.shouldSaveRecipientAddressKey) ?? false; final isAppSecure = sharedPreferences.getBool(PreferencesKey.isAppSecureKey) ?? false; + final disableBuy = + sharedPreferences.getBool(PreferencesKey.disableBuyKey) ?? false; + final disableSell = + sharedPreferences.getBool(PreferencesKey.disableSellKey) ?? false; final currentFiatApiMode = FiatApiMode.deserialize( raw: sharedPreferences .getInt(PreferencesKey.currentFiatApiModeKey) ?? FiatApiMode.enabled.raw); @@ -388,6 +412,8 @@ abstract class SettingsStoreBase with Store { initialBalanceDisplayMode: currentBalanceDisplayMode, initialSaveRecipientAddress: shouldSaveRecipientAddress, initialAppSecure: isAppSecure, + initialDisableBuy: disableBuy, + initialDisableSell: disableSell, initialFiatMode: currentFiatApiMode, initialAllowBiometricalAuthentication: allowBiometricalAuthentication, initialExchangeStatus: exchangeStatus, @@ -435,6 +461,10 @@ abstract class SettingsStoreBase with Store { sharedPreferences.getBool(PreferencesKey.shouldSaveRecipientAddressKey) ?? shouldSaveRecipientAddress; isAppSecure = sharedPreferences.getBool(PreferencesKey.isAppSecureKey) ?? isAppSecure; + disableBuy = + sharedPreferences.getBool(PreferencesKey.disableBuyKey) ?? disableBuy; + disableSell = + sharedPreferences.getBool(PreferencesKey.disableSellKey) ?? disableSell; allowBiometricalAuthentication = sharedPreferences .getBool(PreferencesKey.allowBiometricalAuthenticationKey) ?? allowBiometricalAuthentication; diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index c28603a51..cfb72cb9e 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -46,9 +46,7 @@ abstract class DashboardViewModelBase with Store { required this.anonpayTransactionsStore}) : isOutdatedElectrumWallet = false, hasSellAction = false, - isEnabledSellAction = false, hasBuyAction = false, - isEnabledBuyAction = false, hasExchangeAction = false, isShowFirstYatIntroduction = false, isShowSecondYatIntroduction = false, @@ -287,14 +285,19 @@ abstract class DashboardViewModelBase with Store { @observable bool hasExchangeAction; - @observable - bool isEnabledBuyAction; + @computed + bool get isEnabledBuyAction => + !settingsStore.disableBuy && wallet.type != WalletType.haven; @observable bool hasBuyAction; - @observable - bool isEnabledSellAction; + @computed + bool get isEnabledSellAction => + !settingsStore.disableSell && + wallet.type != WalletType.haven && + wallet.type != WalletType.monero && + wallet.type != WalletType.litecoin; @observable bool hasSellAction; @@ -398,11 +401,7 @@ abstract class DashboardViewModelBase with Store { void updateActions() { hasExchangeAction = !isHaven; - isEnabledBuyAction = wallet.type != WalletType.haven; hasBuyAction = !isHaven; - isEnabledSellAction = wallet.type != WalletType.haven - && wallet.type != WalletType.monero - && wallet.type != WalletType.litecoin; hasSellAction = !isHaven; } } diff --git a/lib/view_model/settings/privacy_settings_view_model.dart b/lib/view_model/settings/privacy_settings_view_model.dart index 91ddd2f34..912864e4a 100644 --- a/lib/view_model/settings/privacy_settings_view_model.dart +++ b/lib/view_model/settings/privacy_settings_view_model.dart @@ -24,6 +24,12 @@ abstract class PrivacySettingsViewModelBase with Store { @computed bool get isAppSecure => _settingsStore.isAppSecure; + @computed + bool get disableBuy => _settingsStore.disableBuy; + + @computed + bool get disableSell => _settingsStore.disableSell; + @action void setShouldSaveRecipientAddress(bool value) => _settingsStore.shouldSaveRecipientAddress = value; @@ -36,4 +42,10 @@ abstract class PrivacySettingsViewModelBase with Store { @action void setIsAppSecure(bool value) => _settingsStore.isAppSecure = value; + @action + void setDisableBuy(bool value) => _settingsStore.disableBuy = value; + + @action + void setDisableSell(bool value) => _settingsStore.disableSell = value; + } diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 0caa02094..c2706a4d7 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -706,5 +706,7 @@ "error_text_input_below_minimum_limit":" المبلغ أقل من الحد الأدنى", "error_text_input_above_maximum_limit":"المبلغ أكبر من الحد الأقصى", "show_market_place": "إظهار السوق", - "prevent_screenshots": "منع لقطات الشاشة وتسجيل الشاشة" + "prevent_screenshots": "منع لقطات الشاشة وتسجيل الشاشة", + "disable_buy": "تعطيل إجراء الشراء", + "disable_sell": "قم بتعطيل إجراء البيع" } diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 66a211ed9..3ab4732d6 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -702,5 +702,7 @@ "error_text_input_below_minimum_limit" : "Сумата е по-малко от минималната", "error_text_input_above_maximum_limit" : "Сумата надвишава максималната", "show_market_place":"Покажи пазар", - "prevent_screenshots": "Предотвратете екранни снимки и запис на екрана" + "prevent_screenshots": "Предотвратете екранни снимки и запис на екрана", + "disable_buy": "Деактивирайте действието за покупка", + "disable_sell": "Деактивирайте действието за продажба" } diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 381e7c18e..9cb449265 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -702,5 +702,7 @@ "error_text_input_below_minimum_limit" : "Částka je menší než minimální hodnota", "error_text_input_above_maximum_limit" : "Částka je větší než maximální hodnota", "show_market_place": "Zobrazit trh", - "prevent_screenshots": "Zabránit vytváření snímků obrazovky a nahrávání obrazovky" + "prevent_screenshots": "Zabránit vytváření snímků obrazovky a nahrávání obrazovky", + "disable_buy": "Zakázat akci nákupu", + "disable_sell": "Zakázat akci prodeje" } diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 930ed4bac..3a64b2029 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -708,5 +708,7 @@ "error_text_input_below_minimum_limit" : "Menge ist unter dem Minimum", "error_text_input_above_maximum_limit" : "Menge ist über dem Maximum", "show_market_place": "Marktplatz anzeigen", - "prevent_screenshots": "Verhindern Sie Screenshots und Bildschirmaufzeichnungen" + "prevent_screenshots": "Verhindern Sie Screenshots und Bildschirmaufzeichnungen", + "disable_buy": "Kaufaktion deaktivieren", + "disable_sell": "Verkaufsaktion deaktivieren" } diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 22cc9b950..7a0bfd975 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -708,5 +708,7 @@ "error_text_input_below_minimum_limit" : "Amount is less than the minimum", "error_text_input_above_maximum_limit" : "Amount is more than the maximum", "show_market_place" :"Show Marketplace", - "prevent_screenshots": "Prevent screenshots and screen recording" + "prevent_screenshots": "Prevent screenshots and screen recording", + "disable_buy": "Disable buy action", + "disable_sell": "Disable sell action" } diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 9159d7a12..edde8c15e 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -708,5 +708,7 @@ "error_text_input_below_minimum_limit" : "La cantidad es menos que mínima", "error_text_input_above_maximum_limit" : "La cantidad es más que el máximo", "show_market_place": "Mostrar mercado", - "prevent_screenshots": "Evitar capturas de pantalla y grabación de pantalla" + "prevent_screenshots": "Evitar capturas de pantalla y grabación de pantalla", + "disable_buy": "Desactivar acción de compra", + "disable_sell": "Desactivar acción de venta" } diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 983fc5f46..7ef121ea3 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -708,5 +708,7 @@ "error_text_input_below_minimum_limit" : "Le montant est inférieur au minimum", "error_text_input_above_maximum_limit" : "Le montant est supérieur au maximum", "show_market_place" :"Afficher la place de marché", - "prevent_screenshots": "Empêcher les captures d'écran et l'enregistrement d'écran" + "prevent_screenshots": "Empêcher les captures d'écran et l'enregistrement d'écran", + "disable_buy": "Désactiver l'action d'achat", + "disable_sell": "Désactiver l'action de vente" } diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 8b505cff2..f5292dfd0 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -708,5 +708,7 @@ "error_text_input_below_minimum_limit" : "राशि न्यूनतम से कम है", "error_text_input_above_maximum_limit" : "राशि अधिकतम से अधिक है", "show_market_place":"बाज़ार दिखाएँ", - "prevent_screenshots": "स्क्रीनशॉट और स्क्रीन रिकॉर्डिंग रोकें" + "prevent_screenshots": "स्क्रीनशॉट और स्क्रीन रिकॉर्डिंग रोकें", + "disable_buy": "खरीद कार्रवाई अक्षम करें", + "disable_sell": "बेचने की कार्रवाई अक्षम करें" } diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index f9b5fd0f2..d0fb4cc7a 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -708,5 +708,7 @@ "error_text_input_below_minimum_limit" : "Iznos je manji od minimalnog", "error_text_input_above_maximum_limit" : "Iznos je veći od maskimalnog", "show_market_place" : "Prikaži tržište", - "prevent_screenshots": "Spriječite snimke zaslona i snimanje zaslona" + "prevent_screenshots": "Spriječite snimke zaslona i snimanje zaslona", + "disable_buy": "Onemogući kupnju", + "disable_sell": "Onemogući akciju prodaje" } diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index b4dd13356..121650b9c 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -684,5 +684,7 @@ "error_text_input_below_minimum_limit" : "Jumlah kurang dari minimal", "error_text_input_above_maximum_limit" : "Jumlah lebih dari maksimal", "show_market_place": "Tampilkan Pasar", - "prevent_screenshots": "Cegah tangkapan layar dan perekaman layar" + "prevent_screenshots": "Cegah tangkapan layar dan perekaman layar", + "disable_buy": "Nonaktifkan tindakan beli", + "disable_sell": "Nonaktifkan aksi jual" } diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 95ce251bd..f327310b2 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -708,5 +708,7 @@ "error_text_input_below_minimum_limit" : "L'ammontare è inferiore al minimo", "error_text_input_above_maximum_limit" : "L'ammontare è superiore al massimo", "show_market_place":"Mostra mercato", - "prevent_screenshots": "Impedisci screenshot e registrazione dello schermo" + "prevent_screenshots": "Impedisci screenshot e registrazione dello schermo", + "disable_buy": "Disabilita l'azione di acquisto", + "disable_sell": "Disabilita l'azione di vendita" } diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index c1b78cc64..f13e84407 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -708,5 +708,7 @@ "error_text_input_below_minimum_limit" : "金額は最小額より少ない", "error_text_input_above_maximum_limit" : "金額は最大値を超えています", "show_market_place":"マーケットプレイスを表示", - "prevent_screenshots": "スクリーンショットと画面録画を防止する" + "prevent_screenshots": "スクリーンショットと画面録画を防止する", + "disable_buy": "購入アクションを無効にする", + "disable_sell": "販売アクションを無効にする" } diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index f2e03ed55..f38f9e0c2 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -708,5 +708,7 @@ "error_text_input_below_minimum_limit" : "금액이 최소보다 적습니다.", "error_text_input_above_maximum_limit" : "금액이 최대 값보다 많습니다.", "show_market_place":"마켓플레이스 표시", - "prevent_screenshots": "스크린샷 및 화면 녹화 방지" + "prevent_screenshots": "스크린샷 및 화면 녹화 방지", + "disable_buy": "구매 행동 비활성화", + "disable_sell": "판매 조치 비활성화" } diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index c314dfb65..73e282b7c 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -708,5 +708,7 @@ "error_text_input_below_minimum_limit" : "ပမာဏသည် အနိမ့်ဆုံးထက်နည်းသည်။", "error_text_input_above_maximum_limit" : "ပမာဏသည် အများဆုံးထက် ပိုများသည်။", "show_market_place":"စျေးကွက်ကိုပြသပါ။", - "prevent_screenshots": "ဖန်သားပြင်ဓာတ်ပုံများနှင့် မျက်နှာပြင်ရိုက်ကူးခြင်းကို တားဆီးပါ။" + "prevent_screenshots": "ဖန်သားပြင်ဓာတ်ပုံများနှင့် မျက်နှာပြင်ရိုက်ကူးခြင်းကို တားဆီးပါ။", + "disable_buy": "ဝယ်ယူမှု လုပ်ဆောင်ချက်ကို ပိတ်ပါ။", + "disable_sell": "ရောင်းချခြင်းလုပ်ဆောင်ချက်ကို ပိတ်ပါ။" } diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 734630c96..a25a536cf 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -708,5 +708,7 @@ "error_text_input_below_minimum_limit" : "Bedrag is minder dan minimaal", "error_text_input_above_maximum_limit" : "Bedrag is meer dan maximaal", "show_market_place":"Toon Marktplaats", - "prevent_screenshots": "Voorkom screenshots en schermopname" + "prevent_screenshots": "Voorkom screenshots en schermopname", + "disable_buy": "Koopactie uitschakelen", + "disable_sell": "Verkoopactie uitschakelen" } diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 23b7bbb61..a35d90429 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -708,5 +708,7 @@ "error_text_input_below_minimum_limit" : "Kwota jest mniejsza niż minimalna", "error_text_input_above_maximum_limit" : "Kwota jest większa niż maksymalna", "show_market_place" : "Pokaż rynek", - "prevent_screenshots": "Zapobiegaj zrzutom ekranu i nagrywaniu ekranu" + "prevent_screenshots": "Zapobiegaj zrzutom ekranu i nagrywaniu ekranu", + "disable_buy": "Wyłącz akcję kupna", + "disable_sell": "Wyłącz akcję sprzedaży" } diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 0346b1ff4..69dc9437f 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -707,5 +707,7 @@ "error_text_input_below_minimum_limit" : "O valor é menor que o mínimo", "error_text_input_above_maximum_limit" : "O valor é superior ao máximo", "show_market_place":"Mostrar mercado", - "prevent_screenshots": "Evite capturas de tela e gravação de tela" + "prevent_screenshots": "Evite capturas de tela e gravação de tela", + "disable_buy": "Desativar ação de compra", + "disable_sell": "Desativar ação de venda" } diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 33058a13e..67caa1776 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -708,5 +708,7 @@ "error_text_input_below_minimum_limit" : "Сумма меньше минимальной", "error_text_input_above_maximum_limit" : "Сумма больше максимальной", "show_market_place":"Показать торговую площадку", - "prevent_screenshots": "Предотвратить скриншоты и запись экрана" + "prevent_screenshots": "Предотвратить скриншоты и запись экрана", + "disable_buy": "Отключить действие покупки", + "disable_sell": "Отключить действие продажи" } diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index f1cc320ac..02e60abaf 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -706,5 +706,7 @@ "error_text_input_below_minimum_limit" : "จำนวนเงินน้อยกว่าขั้นต่ำ", "error_text_input_above_maximum_limit" : "จำนวนเงินสูงกว่าค่าสูงสุด", "show_market_place":"แสดงตลาดกลาง", - "prevent_screenshots": "ป้องกันภาพหน้าจอและการบันทึกหน้าจอ" + "prevent_screenshots": "ป้องกันภาพหน้าจอและการบันทึกหน้าจอ", + "disable_buy": "ปิดการใช้งานการซื้อ", + "disable_sell": "ปิดการใช้งานการขาย" } diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 675fbb5aa..30524d4d7 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -708,5 +708,7 @@ "error_text_input_below_minimum_limit" : "Miktar minimumdan daha azdır", "error_text_input_above_maximum_limit" : "Miktar maksimumdan daha fazla", "show_market_place":"Pazar Yerini Göster", - "prevent_screenshots": "Ekran görüntülerini ve ekran kaydını önleyin" + "prevent_screenshots": "Ekran görüntülerini ve ekran kaydını önleyin", + "disable_buy": "Satın alma işlemini devre dışı bırak", + "disable_sell": "Satış işlemini devre dışı bırak" } diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index affdda645..a4a65eccc 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -707,5 +707,7 @@ "error_text_input_below_minimum_limit" : "Сума менша мінімальної", "error_text_input_above_maximum_limit" : "Сума більше максимальної", "show_market_place":"Відображати маркетплейс", - "prevent_screenshots": "Запобігати знімкам екрана та запису екрана" + "prevent_screenshots": "Запобігати знімкам екрана та запису екрана", + "disable_buy": "Вимкнути дію покупки", + "disable_sell": "Вимкнути дію продажу" } diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index 2e88a0bd6..38deabb0d 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -703,5 +703,7 @@ "error_text_input_below_minimum_limit" : "رقم کم از کم سے کم ہے۔", "error_text_input_above_maximum_limit" : "رقم زیادہ سے زیادہ سے زیادہ ہے۔", "show_market_place":"بازار دکھائیں۔", - "prevent_screenshots": "اسکرین شاٹس اور اسکرین ریکارڈنگ کو روکیں۔" + "prevent_screenshots": "اسکرین شاٹس اور اسکرین ریکارڈنگ کو روکیں۔", + "disable_buy": "خرید ایکشن کو غیر فعال کریں۔", + "disable_sell": "فروخت کی کارروائی کو غیر فعال کریں۔" } diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 10a7d89f2..17f7799bc 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -707,5 +707,7 @@ "error_text_input_below_minimum_limit" : "金额小于最小值", "error_text_input_above_maximum_limit" : "金额大于最大值", "show_market_place" :"显示市场", - "prevent_screenshots": "防止截屏和录屏" + "prevent_screenshots": "防止截屏和录屏", + "disable_buy": "禁用购买操作", + "disable_sell": "禁用卖出操作" } From 0231298381ac6ca739ba773ffd41407e682c8761 Mon Sep 17 00:00:00 2001 From: Rafael Saes <76502841+saltrafael@users.noreply.github.com> Date: Mon, 15 May 2023 09:39:00 -0300 Subject: [PATCH 28/39] CW-254-proper-bitcoin-address-validation-in-exchange-screen (#900) * feat: Proper Bitcoin address validation in exchange screen * fix: use custom validation in addition to RegEx to make sure all address cases are validated - for cases like P2SH addresses starting with a 3, which are not validated by bitcoin_flutter functions * feat: add bitcoin_flutter to root project * refactor: improve conditional isValid return chain --- lib/core/address_validator.dart | 14 +++++---- lib/core/validator.dart | 51 ++++++++++++++++++++++----------- pubspec_base.yaml | 4 +++ 3 files changed, 46 insertions(+), 23 deletions(-) diff --git a/lib/core/address_validator.dart b/lib/core/address_validator.dart index 0bed0611c..57318254a 100644 --- a/lib/core/address_validator.dart +++ b/lib/core/address_validator.dart @@ -1,4 +1,4 @@ -import 'package:flutter/foundation.dart'; +import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/core/validator.dart'; import 'package:cw_core/crypto_currency.dart'; @@ -7,6 +7,9 @@ class AddressValidator extends TextValidator { AddressValidator({required CryptoCurrency type}) : super( errorMessage: S.current.error_text_address, + useAdditionalValidation: type == CryptoCurrency.btc + ? bitcoin.Address.validateAddress + : null, pattern: getPattern(type), length: getLength(type)); @@ -18,8 +21,7 @@ class AddressValidator extends TextValidator { return '^[0-9a-zA-Z]{59}\$|^[0-9a-zA-Z]{92}\$|^[0-9a-zA-Z]{104}\$' '|^[0-9a-zA-Z]{105}\$|^addr1[0-9a-zA-Z]{98}\$'; case CryptoCurrency.btc: - return '^1[0-9a-zA-Z]{32}\$|^1[0-9a-zA-Z]{33}\$|^3[0-9a-zA-Z]{32}\$' - '|^3[0-9a-zA-Z]{33}\$|^bc1[0-9a-zA-Z]{39}\$|^bc1[0-9a-zA-Z]{59}\$'; + return '^3[0-9a-zA-Z]{32}\$|^3[0-9a-zA-Z]{33}\$|^bc1[0-9a-zA-Z]{59}\$'; case CryptoCurrency.nano: return '[0-9a-zA-Z_]'; case CryptoCurrency.usdc: @@ -63,7 +65,7 @@ class AddressValidator extends TextValidator { case CryptoCurrency.bch: case CryptoCurrency.bnb: return '[0-9a-zA-Z]'; - case CryptoCurrency.hbar: + case CryptoCurrency.hbar: return '[0-9a-zA-Z.]'; case CryptoCurrency.zaddr: return '^zs[0-9a-zA-Z]{75}'; @@ -165,9 +167,9 @@ class AddressValidator extends TextValidator { return [34]; case CryptoCurrency.hbar: return [4, 5, 6, 7, 8, 9, 10, 11]; - case CryptoCurrency.xvg: + case CryptoCurrency.xvg: return [34]; - case CryptoCurrency.zen: + case CryptoCurrency.zen: return [35]; case CryptoCurrency.zaddr: return null; diff --git a/lib/core/validator.dart b/lib/core/validator.dart index 96c382b7d..34e985991 100644 --- a/lib/core/validator.dart +++ b/lib/core/validator.dart @@ -1,24 +1,26 @@ -import 'package:flutter/foundation.dart'; - abstract class Validator { - Validator({required this.errorMessage}); + Validator({required this.errorMessage, this.useAdditionalValidation}); final String errorMessage; + final bool Function(T)? useAdditionalValidation; bool isValid(T? value); - String? call(T? value) => !isValid(value) ? errorMessage : null; + String? call(T? value) => !isValid(value) ? errorMessage : null; } class TextValidator extends Validator { - TextValidator( - {this.minLength, - this.maxLength, - this.pattern, - String errorMessage = '', - this.length, - this.isAutovalidate = false}) - : super(errorMessage: errorMessage); + TextValidator({ + bool Function(String)? useAdditionalValidation, + this.minLength, + this.maxLength, + this.pattern, + String errorMessage = '', + this.length, + this.isAutovalidate = false, + }) : super( + errorMessage: errorMessage, + useAdditionalValidation: useAdditionalValidation); final int? minLength; final int? maxLength; @@ -32,11 +34,26 @@ class TextValidator extends Validator { return isAutovalidate ? true : false; } - return value.length > (minLength ?? 0) && - (length?.contains(value.length) ?? true) && - ((maxLength ?? 0) > 0 ? (value.length <= maxLength!) : true) && - (pattern != null ? match(value) : true); + final greaterThanMinLength = value.length > (minLength ?? 0); + if (!greaterThanMinLength) return false; + + final lengthMatched = length?.contains(value.length) ?? true; + if (!lengthMatched) return false; + + final lowerThanMaxLength = + (maxLength ?? 0) > 0 ? (value.length <= maxLength!) : true; + if (!lowerThanMaxLength) return false; + + if (pattern == null) return true; + + final valueMatched = match(value); + final valueValidated = useAdditionalValidation != null + ? useAdditionalValidation!(value) || valueMatched + : valueMatched; + + return valueValidated; } - bool match(String value) => pattern != null ? RegExp(pattern!).hasMatch(value) : false; + bool match(String value) => + pattern != null ? RegExp(pattern!).hasMatch(value) : false; } diff --git a/pubspec_base.yaml b/pubspec_base.yaml index ebe276a54..8c25bd80c 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -81,6 +81,10 @@ dependencies: path_provider_android: 2.0.24 shared_preferences_android: 2.0.17 url_launcher_android: 6.0.24 + bitcoin_flutter: + git: + url: https://github.com/cake-tech/bitcoin_flutter.git + ref: cake-update-v2 dev_dependencies: flutter_test: From 40f3ccbe42da8de3623b5913ef77f4e308eee5c7 Mon Sep 17 00:00:00 2001 From: Rafael Saes <76502841+saltrafael@users.noreply.github.com> Date: Mon, 15 May 2023 09:43:52 -0300 Subject: [PATCH 29/39] Cw 268 contact address validation (#905) * feat: Proper Bitcoin address validation in exchange screen * fix: use custom validation in addition to RegEx to make sure all address cases are validated - for cases like P2SH addresses starting with a 3, which are not validated by bitcoin_flutter functions * feat: add bitcoin_flutter to root project * refactor: improve conditional isValid return chain * feat: enhance contact page address validation - only shows address box when a currency is selected, to prevent the selected value from being null - changes TextValidator() for the new AddressValidator() * fix: use -1 for initial selectedAtIndex, since there is no currency yet selected --- lib/src/screens/contact/contact_page.dart | 167 +++++++++++----------- 1 file changed, 87 insertions(+), 80 deletions(-) diff --git a/lib/src/screens/contact/contact_page.dart b/lib/src/screens/contact/contact_page.dart index abc39bda8..09dc5efc3 100644 --- a/lib/src/screens/contact/contact_page.dart +++ b/lib/src/screens/contact/contact_page.dart @@ -1,13 +1,11 @@ -import 'package:cake_wallet/core/validator.dart'; +import 'package:cake_wallet/core/address_validator.dart'; import 'package:cake_wallet/palette.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cw_core/currency.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:mobx/mobx.dart'; import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/core/address_validator.dart'; import 'package:cake_wallet/core/contact_name_validator.dart'; import 'package:cake_wallet/core/execution_state.dart'; import 'package:cake_wallet/view_model/contact_list/contact_view_model.dart'; @@ -33,8 +31,8 @@ class ContactPage extends BasePage { _addressController .addListener(() => contactViewModel.address = _addressController.text); - autorun((_) => - _currencyTypeController.text = contactViewModel.currency?.toString()??''); + autorun((_) => _currencyTypeController.text = + contactViewModel.currency?.toString() ?? ''); } @override @@ -61,96 +59,105 @@ class ContactPage extends BasePage { } }); - return ScrollableWithBottomSection( - contentPadding: EdgeInsets.all(24), - content: Form( - key: _formKey, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - BaseTextFormField( - controller: _nameController, - hintText: S.of(context).contact_name, - validator: ContactNameValidator()), - Padding( - padding: EdgeInsets.only(top: 20), - child: Container( - child: InkWell( - onTap: () => _presentCurrencyPicker(context), - child: IgnorePointer( - child: BaseTextFormField( - controller: _currencyTypeController, - hintText: S.of(context).settings_currency, - suffixIcon: Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.end, - children: [downArrow], - ), - )), + return Observer( + builder: (_) => ScrollableWithBottomSection( + contentPadding: EdgeInsets.all(24), + content: Form( + key: _formKey, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + BaseTextFormField( + controller: _nameController, + hintText: S.of(context).contact_name, + validator: ContactNameValidator()), + Padding( + padding: EdgeInsets.only(top: 20), + child: Container( + child: InkWell( + onTap: () => _presentCurrencyPicker(context), + child: IgnorePointer( + child: BaseTextFormField( + controller: _currencyTypeController, + hintText: S.of(context).settings_currency, + suffixIcon: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.end, + children: [downArrow], + ), + )), + ), ), ), - ), - Padding( - padding: EdgeInsets.only(top: 20), - child: Observer( - builder: (_) => AddressTextField( + if (contactViewModel.currency != null) + Padding( + padding: EdgeInsets.only(top: 20), + child: AddressTextField( controller: _addressController, options: [ AddressTextFieldOption.paste, AddressTextFieldOption.qrCode, ], - buttonColor: Theme.of(context).accentTextTheme!.headline3!.color!, + buttonColor: + Theme.of(context).accentTextTheme!.headline3!.color!, iconColor: PaletteDark.gray, - borderColor: Theme.of(context).primaryTextTheme!.headline6!.backgroundColor!, - validator: TextValidator() - // AddressValidator( - // type: contactViewModel.currency), - )), - ) - ], - ), - ), - bottomSectionPadding: - EdgeInsets.only(left: 24, right: 24, bottom: 24), - bottomSection: Row( - children: [ - Expanded( - child: PrimaryButton( - onPressed: () { - contactViewModel.reset(); - _nameController.text = ''; - _addressController.text = ''; - }, - text: S.of(context).reset, - color: Colors.orange, - textColor: Colors.white), + borderColor: Theme.of(context) + .primaryTextTheme! + .headline6! + .backgroundColor!, + validator: + AddressValidator(type: contactViewModel.currency!), + ), + ) + ], ), - SizedBox(width: 20), - Expanded( - child: Observer( - builder: (_) => PrimaryButton( - onPressed: () async { - if (_formKey.currentState != null && !_formKey.currentState!.validate()) { - return; - } + ), + bottomSectionPadding: + EdgeInsets.only(left: 24, right: 24, bottom: 24), + bottomSection: Row( + children: [ + Expanded( + child: PrimaryButton( + onPressed: () { + contactViewModel.reset(); + _nameController.text = ''; + _addressController.text = ''; + }, + text: S.of(context).reset, + color: Colors.orange, + textColor: Colors.white), + ), + SizedBox(width: 20), + Expanded( + child: Observer( + builder: (_) => PrimaryButton( + onPressed: () async { + if (_formKey.currentState != null && + !_formKey.currentState!.validate()) { + return; + } - await contactViewModel.save(); - }, - text: S.of(context).save, - color: Theme.of(context).accentTextTheme!.bodyText1!.color!, - textColor: Colors.white, - isDisabled: !contactViewModel.isReady))) - ], - )); + await contactViewModel.save(); + }, + text: S.of(context).save, + color: Theme.of(context) + .accentTextTheme! + .bodyText1! + .color!, + textColor: Colors.white, + isDisabled: !contactViewModel.isReady))) + ], + )), + ); } void _presentCurrencyPicker(BuildContext context) { showPopUp( builder: (_) => CurrencyPicker( - selectedAtIndex: - contactViewModel.currency != null - ? contactViewModel.currencies.indexOf(contactViewModel.currency!) - : 0, + selectedAtIndex: contactViewModel.currency != null + ? contactViewModel.currencies + .indexOf(contactViewModel.currency!) + : -1, items: contactViewModel.currencies, title: S.of(context).please_select, hintText: S.of(context).search_currency, From 43e062d1ac319ed127a0f656b68958de9e453107 Mon Sep 17 00:00:00 2001 From: Adegoke David <64401859+Blazebrain@users.noreply.github.com> Date: Wed, 17 May 2023 15:43:23 +0100 Subject: [PATCH 30/39] Cw-263-TOTP-2FA-In-Security-Settings (#892) * CW-263-TOTP-2FA-in-security-settings WIP * Implement TOTP 2FA WIP * Implement TOTP 2FA Authentication * chore: Remove unneeded formatting * revert formatting * fixes * CW-263-TOTP-2FA-in-security-settings WIP * Setup TOTP Complete, left with Modify TOTF * CW-263-TOTP-2FA-in-security-settings * CW-263-TOTP-2FA-in-security-settings * CW-263-TOTP-2FA-in-security-settings * fix: Add copy-to-clipboard for qr secret key * fix: Translation * chore: Move strings into translation files * feat: End to end flow for TOTP * hotfix: Switch totp to use sha512 * Update strings; 8 digits and error explanation * fix: Totp 2fa implementation feedback * hotfix: same action for button and alert close * feat: App should show both normal and totp auths when totp is enabled * hotfix: prevent barrier from dismissing * fix: Changes requested during PR review * - Minor Enhancements - Minor UI fixes --------- Co-authored-by: Justin Ehrenhofer Co-authored-by: OmarHatem --- lib/core/auth_service.dart | 42 +- lib/core/totp_request_details.dart | 13 + lib/di.dart | 613 +++++++++--------- lib/entities/preferences_key.dart | 3 + lib/router.dart | 48 +- lib/routes.dart | 4 + lib/src/screens/root/root.dart | 55 +- .../settings/security_backup_page.dart | 121 ++-- .../screens/setup_2fa/modify_2fa_page.dart | 55 ++ lib/src/screens/setup_2fa/setup_2fa.dart | 62 ++ .../setup_2fa/setup_2fa_enter_code_page.dart | 221 +++++++ .../screens/setup_2fa/setup_2fa_qr_page.dart | 145 +++++ .../widgets/popup_cancellable_alert.dart | 92 +++ lib/src/widgets/alert_close_button.dart | 5 +- lib/store/settings_store.dart | 297 +++++---- lib/utils/totp_utils.dart | 83 +++ lib/view_model/set_up_2fa_viewmodel.dart | 159 +++++ .../security_settings_view_model.dart | 3 + macos/Flutter/GeneratedPluginRegistrant.swift | 2 +- pubspec_base.yaml | 1 + res/values/strings_ar.arb | 17 + res/values/strings_bg.arb | 17 + res/values/strings_cs.arb | 17 + res/values/strings_de.arb | 17 + res/values/strings_en.arb | 17 + res/values/strings_es.arb | 17 + res/values/strings_fr.arb | 17 + res/values/strings_hi.arb | 17 + res/values/strings_hr.arb | 22 + res/values/strings_id.arb | 28 + res/values/strings_it.arb | 17 + res/values/strings_ja.arb | 19 +- res/values/strings_ko.arb | 17 + res/values/strings_my.arb | 29 + res/values/strings_nl.arb | 17 + res/values/strings_pl.arb | 17 + res/values/strings_pt.arb | 17 + res/values/strings_ru.arb | 17 + res/values/strings_th.arb | 17 + res/values/strings_tr.arb | 17 + res/values/strings_uk.arb | 17 + res/values/strings_ur.arb | 17 + res/values/strings_zh.arb | 17 + 43 files changed, 1951 insertions(+), 494 deletions(-) create mode 100644 lib/core/totp_request_details.dart create mode 100644 lib/src/screens/setup_2fa/modify_2fa_page.dart create mode 100644 lib/src/screens/setup_2fa/setup_2fa.dart create mode 100644 lib/src/screens/setup_2fa/setup_2fa_enter_code_page.dart create mode 100644 lib/src/screens/setup_2fa/setup_2fa_qr_page.dart create mode 100644 lib/src/screens/setup_2fa/widgets/popup_cancellable_alert.dart create mode 100644 lib/utils/totp_utils.dart create mode 100644 lib/view_model/set_up_2fa_viewmodel.dart diff --git a/lib/core/auth_service.dart b/lib/core/auth_service.dart index d26fd17a3..8091740e6 100644 --- a/lib/core/auth_service.dart +++ b/lib/core/auth_service.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/core/totp_request_details.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/auth/auth_page.dart'; import 'package:flutter/material.dart'; @@ -9,6 +10,8 @@ import 'package:cake_wallet/entities/secret_store_key.dart'; import 'package:cake_wallet/entities/encrypt.dart'; import 'package:cake_wallet/store/settings_store.dart'; +import '../src/screens/setup_2fa/setup_2fa_enter_code_page.dart'; + class AuthService with Store { AuthService({ required this.secureStorage, @@ -20,6 +23,8 @@ class AuthService with Store { Routes.showKeys, Routes.backup, Routes.setupPin, + Routes.setup_2faPage, + Routes.modify2FAPage, ]; final FlutterSecureStorage secureStorage; @@ -79,6 +84,7 @@ class AuthService with Store { {Function(bool)? onAuthSuccess, String? route, Object? arguments}) async { assert(route != null || onAuthSuccess != null, 'Either route or onAuthSuccess param must be passed.'); + if (!requireAuth() && !_alwaysAuthenticateRoutes.contains(route)) { if (onAuthSuccess != null) { onAuthSuccess(true); @@ -90,17 +96,43 @@ class AuthService with Store { } return; } + + Navigator.of(context).pushNamed(Routes.auth, arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) async { if (!isAuthenticatedSuccessfully) { onAuthSuccess?.call(false); return; - } - if (onAuthSuccess != null) { - auth.close().then((value) => onAuthSuccess.call(true)); } else { - auth.close(route: route, arguments: arguments); + if (settingsStore.useTOTP2FA) { + auth.close( + route: Routes.totpAuthCodePage, + arguments: TotpAuthArgumentsModel( + isForSetup: !settingsStore.useTOTP2FA, + onTotpAuthenticationFinished: + (bool isAuthenticatedSuccessfully, TotpAuthCodePageState totpAuth) async { + if (!isAuthenticatedSuccessfully) { + onAuthSuccess?.call(false); + return; + } + if (onAuthSuccess != null) { + totpAuth.close().then((value) => onAuthSuccess.call(true)); + } else { + totpAuth.close(route: route, arguments: arguments); + } + }, + ), + ); + } else { + if (onAuthSuccess != null) { + auth.close().then((value) => onAuthSuccess.call(true)); + } else { + auth.close(route: route, arguments: arguments); + } + } } - }); + + }); + } } diff --git a/lib/core/totp_request_details.dart b/lib/core/totp_request_details.dart new file mode 100644 index 000000000..63da06455 --- /dev/null +++ b/lib/core/totp_request_details.dart @@ -0,0 +1,13 @@ +import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_enter_code_page.dart'; + +class TotpAuthArgumentsModel { + final bool? isForSetup; + final bool? isClosable; + final OnTotpAuthenticationFinished? onTotpAuthenticationFinished; + + TotpAuthArgumentsModel({ + this.isForSetup, + this.isClosable, + this.onTotpAuthenticationFinished, + }); +} diff --git a/lib/di.dart b/lib/di.dart index 1667fd1d2..0f59c702f 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -10,6 +10,7 @@ import 'package:cake_wallet/entities/receive_page_option.dart'; import 'package:cake_wallet/ionia/ionia_anypay.dart'; import 'package:cake_wallet/ionia/ionia_gift_card.dart'; import 'package:cake_wallet/ionia/ionia_tip.dart'; +import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/anonpay_details/anonpay_details_page.dart'; import 'package:cake_wallet/src/screens/buy/onramper_page.dart'; import 'package:cake_wallet/src/screens/buy/payfura_page.dart'; @@ -27,6 +28,10 @@ import 'package:cake_wallet/src/screens/ionia/cards/ionia_custom_redeem_page.dar import 'package:cake_wallet/src/screens/ionia/cards/ionia_gift_card_detail_page.dart'; import 'package:cake_wallet/src/screens/ionia/cards/ionia_more_options_page.dart'; import 'package:cake_wallet/src/screens/settings/connection_sync_page.dart'; +import 'package:cake_wallet/src/screens/setup_2fa/modify_2fa_page.dart'; +import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_qr_page.dart'; +import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa.dart'; +import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_enter_code_page.dart'; import 'package:cake_wallet/themes/theme_list.dart'; import 'package:cake_wallet/utils/device_info.dart'; import 'package:cake_wallet/store/anonpay/anonpay_transactions_store.dart'; @@ -53,8 +58,8 @@ import 'package:cake_wallet/src/screens/dashboard/widgets/balance_page.dart'; import 'package:cake_wallet/view_model/ionia/ionia_account_view_model.dart'; import 'package:cake_wallet/view_model/ionia/ionia_gift_cards_list_view_model.dart'; import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dart'; +import 'package:cake_wallet/view_model/set_up_2fa_viewmodel.dart'; import 'package:cake_wallet/view_model/restore/restore_from_qr_vm.dart'; -import 'package:cake_wallet/view_model/restore/restore_wallet.dart'; import 'package:cake_wallet/view_model/settings/display_settings_view_model.dart'; import 'package:cake_wallet/view_model/settings/other_settings_view_model.dart'; import 'package:cake_wallet/view_model/settings/privacy_settings_view_model.dart'; @@ -187,6 +192,8 @@ import 'package:cake_wallet/core/wallet_loading_service.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cake_wallet/entities/qr_view_data.dart'; +import 'core/totp_request_details.dart'; + final getIt = GetIt.instance; var _isSetupFinished = false; @@ -201,18 +208,18 @@ late Box _ordersSource; late Box? _unspentCoinsInfoSource; late Box _anonpayInvoiceInfoSource; -Future setup( - {required Box walletInfoSource, - required Box nodeSource, - required Box contactSource, - required Box tradesSource, - required Box