diff --git a/android/app/src/main/AndroidManifestBase.xml b/android/app/src/main/AndroidManifestBase.xml index 22278d5f1..9f6b65785 100644 --- a/android/app/src/main/AndroidManifestBase.xml +++ b/android/app/src/main/AndroidManifestBase.xml @@ -16,7 +16,7 @@ android:requestLegacyExternalStorage="true"> + + + + + + + + + > feeRates() async { try { final topDoubleString = await estimatefee(p: 1); - final middleDoubleString = await estimatefee(p: 20); + final middleDoubleString = await estimatefee(p: 5); final bottomDoubleString = await estimatefee(p: 100); final top = (stringDoubleToBitcoinAmount(topDoubleString.toString()) / 1000) diff --git a/cw_core/lib/monero_transaction_priority.dart b/cw_core/lib/monero_transaction_priority.dart index cca887398..f5c00ecc7 100644 --- a/cw_core/lib/monero_transaction_priority.dart +++ b/cw_core/lib/monero_transaction_priority.dart @@ -21,22 +21,6 @@ class MoneroTransactionPriority extends TransactionPriority { static const fastest = MoneroTransactionPriority(title: 'Fastest', raw: 4); static const standard = slow; - - static List forWalletType(WalletType type) { - switch (type) { - case WalletType.monero: - return MoneroTransactionPriority.all; - case WalletType.bitcoin: - return [ - MoneroTransactionPriority.slow, - MoneroTransactionPriority.automatic, - MoneroTransactionPriority.fast - ]; - default: - return []; - } - } - static MoneroTransactionPriority deserialize({required int raw}) { switch (raw) { case 0: diff --git a/cw_core/lib/node.dart b/cw_core/lib/node.dart index ff1d6b051..1322c5b78 100644 --- a/cw_core/lib/node.dart +++ b/cw_core/lib/node.dart @@ -53,7 +53,7 @@ class Node extends HiveObject with Keyable { @HiveField(4) bool? useSSL; - @HiveField(5) + @HiveField(5, defaultValue: false) bool trusted; bool get isSSL => useSSL ?? false; diff --git a/ios/Runner/InfoBase.plist b/ios/Runner/InfoBase.plist index ad8816ca3..794391665 100644 --- a/ios/Runner/InfoBase.plist +++ b/ios/Runner/InfoBase.plist @@ -21,18 +21,48 @@ CFBundleSignature ???? CFBundleURLTypes - - - CFBundleTypeRole - Editor - CFBundleURLName - y.at - CFBundleURLSchemes - - cakewallet - - - + + + CFBundleTypeRole + Editor + CFBundleURLName + y.at + CFBundleURLSchemes + + cakewallet + + + + CFBundleTypeRole + Editor + CFBundleURLName + bitcoin + CFBundleURLSchemes + + bitcoin + + + + CFBundleTypeRole + Editor + CFBundleURLName + monero + CFBundleURLSchemes + + monero + + + + CFBundleTypeRole + Editor + CFBundleURLName + litecoin + CFBundleURLSchemes + + litecoin + + + CFBundleVersion $(CURRENT_PROJECT_VERSION) LSRequiresIPhoneOS diff --git a/lib/core/auth_service.dart b/lib/core/auth_service.dart index 2ae37e2b0..54f89437a 100644 --- a/lib/core/auth_service.dart +++ b/lib/core/auth_service.dart @@ -4,12 +4,19 @@ 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 { - AuthService({required this.secureStorage, required this.sharedPreferences}); + AuthService({ + required this.secureStorage, + required this.sharedPreferences, + required this.settingsStore, + }); final FlutterSecureStorage secureStorage; final SharedPreferences sharedPreferences; + final SettingsStore settingsStore; Future setPassword(String password) async { final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword); @@ -19,8 +26,7 @@ class AuthService with Store { Future canAuthenticate() async { final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword); - final walletName = - sharedPreferences.getString(PreferencesKey.currentWalletName) ?? ''; + final walletName = sharedPreferences.getString(PreferencesKey.currentWalletName) ?? ''; var password = ''; try { @@ -39,4 +45,25 @@ class AuthService with Store { return decodedPin == pin; } + + void saveLastAuthTime() { + int timestamp = DateTime.now().millisecondsSinceEpoch; + sharedPreferences.setInt(PreferencesKey.lastAuthTimeMilliseconds, timestamp); + } + + bool requireAuth() { + final timestamp = sharedPreferences.getInt(PreferencesKey.lastAuthTimeMilliseconds); + final duration = _durationToRequireAuth(timestamp ?? 0); + final requiredPinInterval = settingsStore.pinTimeOutDuration; + + return duration >= requiredPinInterval.value; + } + + int _durationToRequireAuth(int timestamp) { + DateTime before = DateTime.fromMillisecondsSinceEpoch(timestamp); + DateTime now = DateTime.now(); + Duration timeDifference = now.difference(before); + + return timeDifference.inMinutes; + } } diff --git a/lib/di.dart b/lib/di.dart index 1febcd9e5..1acdad2dd 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -13,6 +13,7 @@ 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/utils/payment_request.dart'; import 'package:cake_wallet/view_model/ionia/ionia_auth_view_model.dart'; import 'package:cake_wallet/view_model/ionia/ionia_buy_card_view_model.dart'; import 'package:cake_wallet/view_model/ionia/ionia_custom_tip_view_model.dart'; @@ -308,7 +309,10 @@ Future setup( getIt.registerFactory(() => AuthService( secureStorage: getIt.get(), - sharedPreferences: getIt.get())); + sharedPreferences: getIt.get(), + settingsStore: getIt.get(), + ), + ); getIt.registerFactory(() => AuthViewModel( getIt.get(), @@ -384,8 +388,11 @@ Future setup( getIt.get(), _transactionDescriptionBox)); - getIt.registerFactory( - () => SendPage(sendViewModel: getIt.get())); + getIt.registerFactoryParam( + (PaymentRequest? initialPaymentRequest, _) => SendPage( + sendViewModel: getIt.get(), + initialPaymentRequest: initialPaymentRequest, + )); getIt.registerFactory(() => SendTemplatePage( sendTemplateViewModel: getIt.get())); @@ -393,7 +400,10 @@ Future setup( getIt.registerFactory(() => WalletListViewModel( _walletInfoSource, getIt.get(), - getIt.get())); + getIt.get(), + getIt.get(), + ), + ); getIt.registerFactory(() => WalletListPage(walletListViewModel: getIt.get())); @@ -453,7 +463,7 @@ Future setup( }); getIt.registerFactory(() { - return SecuritySettingsViewModel(getIt.get()); + return SecuritySettingsViewModel(getIt.get(), getIt.get()); }); getIt @@ -785,7 +795,8 @@ Future setup( return IoniaMoreOptionsPage(giftCard); }); - getIt.registerFactoryParam((IoniaGiftCard giftCard, _) => IoniaCustomRedeemViewModel(giftCard)); + getIt.registerFactoryParam((IoniaGiftCard giftCard, _) + => IoniaCustomRedeemViewModel(giftCard: giftCard, ioniaService: getIt.get())); getIt.registerFactoryParam((List args, _){ final giftCard = args.first as IoniaGiftCard; @@ -823,4 +834,4 @@ Future setup( AdvancedPrivacySettingsViewModel(type, getIt.get())); _isSetupFinished = true; -} +} \ No newline at end of file diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index 9aff05500..3c8d9fbbe 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -139,6 +139,10 @@ Future defaultSettingsMigration( await addOnionNode(nodes); break; + case 19: + await validateBitcoinSavedTransactionPriority(sharedPreferences); + break; + default: break; } @@ -154,6 +158,18 @@ Future defaultSettingsMigration( PreferencesKey.currentDefaultSettingsMigrationVersion, version); } +Future validateBitcoinSavedTransactionPriority(SharedPreferences sharedPreferences) async { + if (bitcoin == null) { + return; + } + final int? savedBitcoinPriority = + sharedPreferences.getInt(PreferencesKey.bitcoinTransactionPriority); + if (!bitcoin!.getTransactionPriorities().any((element) => element.raw == savedBitcoinPriority)) { + await sharedPreferences.setInt( + PreferencesKey.bitcoinTransactionPriority, bitcoin!.getMediumTransactionPriority().serialize()); + } +} + Future addOnionNode(Box nodes) async { final onionNodeUri = "cakexmrl7bonq7ovjka5kuwuyd3f7qnkz6z6s6dmsy3uckwra7bvggyd.onion:18081"; diff --git a/lib/entities/pin_code_required_duration.dart b/lib/entities/pin_code_required_duration.dart new file mode 100644 index 000000000..fef5715b5 --- /dev/null +++ b/lib/entities/pin_code_required_duration.dart @@ -0,0 +1,32 @@ +import 'package:cake_wallet/generated/i18n.dart'; + +enum PinCodeRequiredDuration { + always(0), + tenminutes(10), + onehour(60); + + const PinCodeRequiredDuration(this.value); + final int value; + + static PinCodeRequiredDuration deserialize({required int raw}) => + PinCodeRequiredDuration.values.firstWhere((e) => e.value == raw); + + @override + String toString(){ + String label = ''; + switch (this) { + case PinCodeRequiredDuration.always: + label = S.current.always; + break; + case PinCodeRequiredDuration.tenminutes: + label = S.current.minutes_to_pin_code('10'); + break; + case PinCodeRequiredDuration.onehour: + label = S.current.minutes_to_pin_code('60'); + break; + } + return label; + + } + +} \ No newline at end of file diff --git a/lib/entities/preferences_key.dart b/lib/entities/preferences_key.dart index 6d9e93de6..90d4dcad7 100644 --- a/lib/entities/preferences_key.dart +++ b/lib/entities/preferences_key.dart @@ -27,6 +27,9 @@ class PreferencesKey { static const shouldShowReceiveWarning = 'should_show_receive_warning'; static const shouldShowYatPopup = 'should_show_yat_popup'; static const moneroWalletPasswordUpdateV1Base = 'monero_wallet_update_v1'; + static const pinTimeOutDuration = 'pin_timeout_duration'; + static const lastAuthTimeMilliseconds = 'last_auth_time_milliseconds'; + static String moneroWalletUpdateV1Key(String name) => '${PreferencesKey.moneroWalletPasswordUpdateV1Base}_${name}'; diff --git a/lib/ionia/ionia_gift_card.dart b/lib/ionia/ionia_gift_card.dart index b729e261f..2ba0fd136 100644 --- a/lib/ionia/ionia_gift_card.dart +++ b/lib/ionia/ionia_gift_card.dart @@ -37,7 +37,7 @@ class IoniaGiftCard { purchaseAmount: element['PurchaseAmount'] as double, actualAmount: element['ActualAmount'] as double, totalTransactionAmount: element['TotalTransactionAmount'] as double, - totalDashTransactionAmount: element['TotalDashTransactionAmount'] as double, + totalDashTransactionAmount: (element['TotalDashTransactionAmount'] as double?) ?? 0.0, remainingAmount: element['RemainingAmount'] as double, isActive: element['IsActive'] as bool, isEmpty: element['IsEmpty'] as bool, diff --git a/lib/ionia/ionia_service.dart b/lib/ionia/ionia_service.dart index 942bc25b5..51e23ad28 100644 --- a/lib/ionia/ionia_service.dart +++ b/lib/ionia/ionia_service.dart @@ -148,8 +148,8 @@ class IoniaService { // Redeem - Future redeem(IoniaGiftCard giftCard) async { - await chargeGiftCard(giftCardId: giftCard.id, amount: giftCard.remainingAmount); + Future redeem({required int giftCardId, required double amount}) async { + await chargeGiftCard(giftCardId: giftCardId, amount: amount); } // Get Gift Card diff --git a/lib/main.dart b/lib/main.dart index 11eee146b..19f89feb3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; +import 'package:cake_wallet/core/auth_service.dart'; import 'package:cake_wallet/entities/language_service.dart'; import 'package:cake_wallet/buy/order.dart'; import 'package:cake_wallet/ionia/ionia_category.dart'; @@ -128,7 +129,7 @@ Future main() async { exchangeTemplates: exchangeTemplates, transactionDescriptions: transactionDescriptions, secureStorage: secureStorage, - initialMigrationVersion: 18); + initialMigrationVersion: 19); runApp(App()); } catch (e, stacktrace) { runApp(MaterialApp( @@ -204,12 +205,6 @@ class AppState extends State with SingleTickerProviderStateMixin { //_handleInitialUri(); } - @override - void dispose() { - stream?.cancel(); - super.dispose(); - } - Future _handleInitialUri() async { try { final uri = await getInitialUri(); @@ -257,6 +252,7 @@ class AppState extends State with SingleTickerProviderStateMixin { Widget build(BuildContext context) { return Observer(builder: (BuildContext context) { final appStore = getIt.get(); + final authService = getIt.get(); final settingsStore = appStore.settingsStore; final statusBarColor = Colors.transparent; final authenticationStore = getIt.get(); @@ -281,6 +277,7 @@ class AppState extends State with SingleTickerProviderStateMixin { appStore: appStore, authenticationStore: authenticationStore, navigatorKey: navigatorKey, + authService: authService, child: MaterialApp( navigatorKey: navigatorKey, debugShowCheckedModeBanner: false, diff --git a/lib/router.dart b/lib/router.dart index b59294428..896ddd99b 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -25,6 +25,7 @@ import 'package:cake_wallet/src/screens/settings/connection_sync_page.dart'; import 'package:cake_wallet/src/screens/support/support_page.dart'; import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_details_page.dart'; import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart'; +import 'package:cake_wallet/utils/payment_request.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'; @@ -216,8 +217,12 @@ Route createRoute(RouteSettings settings) { builder: (_) => getIt.get()); case Routes.send: + final initialPaymentRequest = settings.arguments as PaymentRequest?; + return CupertinoPageRoute( - fullscreenDialog: true, builder: (_) => getIt.get()); + fullscreenDialog: true, builder: (_) => getIt.get( + param1: initialPaymentRequest, + )); case Routes.sendTemplate: return CupertinoPageRoute( diff --git a/lib/src/screens/dashboard/wallet_menu.dart b/lib/src/screens/dashboard/wallet_menu.dart index bc1f20a5d..1638c0bc4 100644 --- a/lib/src/screens/dashboard/wallet_menu.dart +++ b/lib/src/screens/dashboard/wallet_menu.dart @@ -21,6 +21,12 @@ class WalletMenu { height: 16, width: 16), handler: () => Navigator.of(context).pushNamed(Routes.walletList), ), + WalletMenuItem( + title: S.current.address_book_menu, + image: Image.asset('assets/images/open_book_menu.png', + height: 16, width: 16), + handler: () => Navigator.of(context).pushNamed(Routes.addressBook), + ), WalletMenuItem( title: S.current.security_and_backup, image: @@ -29,18 +35,12 @@ class WalletMenu { Navigator.of(context).pushNamed(Routes.securityBackupPage); }), WalletMenuItem( - title: S.current.privacy, + title: S.current.privacy_settings, image: - Image.asset('assets/images/eye_menu.png', height: 16, width: 16), + Image.asset('assets/images/privacy_menu.png', height: 16, width: 16), handler: () { Navigator.of(context).pushNamed(Routes.privacyPage); }), - WalletMenuItem( - title: S.current.address_book_menu, - image: Image.asset('assets/images/open_book_menu.png', - height: 16, width: 16), - handler: () => Navigator.of(context).pushNamed(Routes.addressBook), - ), WalletMenuItem( title: S.current.display_settings, image: Image.asset('assets/images/eye_menu.png', diff --git a/lib/src/screens/ionia/cards/ionia_custom_redeem_page.dart b/lib/src/screens/ionia/cards/ionia_custom_redeem_page.dart index dae5413a4..f2702d791 100644 --- a/lib/src/screens/ionia/cards/ionia_custom_redeem_page.dart +++ b/lib/src/screens/ionia/cards/ionia_custom_redeem_page.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/core/execution_state.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/screens/ionia/widgets/card_item.dart'; import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; @@ -18,12 +19,11 @@ class IoniaCustomRedeemPage extends BasePage { ) : _amountFieldFocus = FocusNode(), _amountController = TextEditingController() { _amountController.addListener(() { - ioniaCustomRedeemViewModel.updateAmount(_amountController.text); + ioniaCustomRedeemViewModel.updateAmount(_amountController.text); }); } final IoniaCustomRedeemViewModel ioniaCustomRedeemViewModel; - @override String get title => S.current.custom_redeem_amount; @@ -50,7 +50,7 @@ class IoniaCustomRedeemPage extends BasePage { disableScroll: true, config: KeyboardActionsConfig( keyboardActionsPlatform: KeyboardActionsPlatform.IOS, - keyboardBarColor: Theme.of(context).accentTextTheme!.bodyText1!.backgroundColor!, + keyboardBarColor: Theme.of(context).accentTextTheme.bodyText1!.backgroundColor!, nextFocus: false, actions: [ KeyboardActionsItem( @@ -67,10 +67,11 @@ class IoniaCustomRedeemPage extends BasePage { Container( padding: EdgeInsets.symmetric(horizontal: 25), decoration: BoxDecoration( - borderRadius: BorderRadius.only(bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)), + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)), gradient: LinearGradient(colors: [ - Theme.of(context).primaryTextTheme!.subtitle1!.color!, - Theme.of(context).primaryTextTheme!.subtitle1!.decorationColor!, + Theme.of(context).primaryTextTheme.subtitle1!.color!, + Theme.of(context).primaryTextTheme.subtitle1!.decorationColor!, ], begin: Alignment.topLeft, end: Alignment.bottomRight), ), child: Column( @@ -85,11 +86,11 @@ class IoniaCustomRedeemPage extends BasePage { inputFormatters: [FilteringTextInputFormatter.deny(RegExp('[\-|\ ]'))], hintText: '1000', placeholderTextStyle: TextStyle( - color: Theme.of(context).primaryTextTheme!.headline5!.color!, + color: Theme.of(context).primaryTextTheme.headline5!.color!, fontWeight: FontWeight.w500, fontSize: 36, ), - borderColor: Theme.of(context).primaryTextTheme!.headline5!.color!, + borderColor: Theme.of(context).primaryTextTheme.headline5!.color!, textColor: Colors.white, textStyle: TextStyle( color: Colors.white, @@ -114,14 +115,17 @@ class IoniaCustomRedeemPage extends BasePage { ), ), SizedBox(height: 8), - Observer(builder: (_)=> - !ioniaCustomRedeemViewModel.disableRedeem ? - Center( - child: Text('\$${giftCard.remainingAmount} - \$${ioniaCustomRedeemViewModel.amount} = \$${ioniaCustomRedeemViewModel.formattedRemaining} ${S.of(context).remaining}', - style: TextStyle( - color: Theme.of(context).primaryTextTheme!.headline5!.color!, - ),), - ) : SizedBox.shrink(), + Observer( + builder: (_) => !ioniaCustomRedeemViewModel.disableRedeem + ? Center( + child: Text( + '\$${giftCard.remainingAmount} - \$${ioniaCustomRedeemViewModel.amount} = \$${ioniaCustomRedeemViewModel.formattedRemaining} ${S.of(context).remaining}', + style: TextStyle( + color: Theme.of(context).primaryTextTheme.headline5!.color!, + ), + ), + ) + : SizedBox.shrink(), ), SizedBox(height: 24), ], @@ -131,30 +135,37 @@ class IoniaCustomRedeemPage extends BasePage { padding: const EdgeInsets.all(24.0), child: CardItem( title: giftCard.legalName, - backgroundColor: Theme.of(context).accentTextTheme!.headline1!.backgroundColor!.withOpacity(0.1), + backgroundColor: Theme.of(context) + .accentTextTheme + .headline1! + .backgroundColor! + .withOpacity(0.1), discount: giftCard.remainingAmount, isAmount: true, discountBackground: AssetImage('assets/images/red_badge_discount.png'), - titleColor: Theme.of(context).accentTextTheme!.headline1!.backgroundColor!, + titleColor: Theme.of(context).accentTextTheme.headline1!.backgroundColor!, subtitleColor: Theme.of(context).hintColor, subTitle: S.of(context).online, logoUrl: giftCard.logoUrl, ), - ), + ), ], ), bottomSection: Column( children: [ - Padding( - padding: EdgeInsets.only(bottom: 12), - child: PrimaryButton( - onPressed: () { - Navigator.of(context).pop(_amountController.text); - }, - isDisabled: ioniaCustomRedeemViewModel.disableRedeem, - text: S.of(context).add_custom_redemption, - color: Theme.of(context).accentTextTheme!.bodyText1!.color!, - textColor: Colors.white, + Observer( + builder: (_) => Padding( + padding: EdgeInsets.only(bottom: 12), + child: LoadingPrimaryButton( + isLoading: ioniaCustomRedeemViewModel.redeemState is IsExecutingState, + isDisabled: ioniaCustomRedeemViewModel.disableRedeem, + text: S.of(context).add_custom_redemption, + color: Theme.of(context).accentTextTheme.bodyText1!.color!, + textColor: Colors.white, + onPressed: () => ioniaCustomRedeemViewModel.addCustomRedeem().then((value) { + Navigator.of(context).pop(ioniaCustomRedeemViewModel.remaining.toString()); + }), + ), ), ), SizedBox(height: 30), diff --git a/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart b/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart index 1c768fa17..5b3c980ec 100644 --- a/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart +++ b/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart @@ -32,7 +32,7 @@ class IoniaGiftCardDetailPage extends BasePage { final _backButton = Icon( Icons.arrow_back_ios, - color: Theme.of(context).primaryTextTheme!.headline6!.color!, + color: Theme.of(context).primaryTextTheme.headline6!.color!, size: 16, ); return Padding( @@ -43,7 +43,7 @@ class IoniaGiftCardDetailPage extends BasePage { child: ButtonTheme( minWidth: double.minPositive, child: TextButton( - // FIX-ME: Style + // FIX-ME: Style //highlightColor: Colors.transparent, //splashColor: Colors.transparent, //padding: EdgeInsets.all(0), @@ -61,7 +61,8 @@ class IoniaGiftCardDetailPage extends BasePage { Widget middle(BuildContext context) { return Text( viewModel.giftCard.legalName, - style: textMediumSemiBold(color: Theme.of(context).accentTextTheme!.headline1!.backgroundColor!), + style: + textMediumSemiBold(color: Theme.of(context).accentTextTheme.headline1!.backgroundColor!), ); } @@ -102,20 +103,21 @@ class IoniaGiftCardDetailPage extends BasePage { title: S.of(context).gift_card_number, subTitle: viewModel.giftCard.cardNumber, ), - if (viewModel.giftCard.cardPin?.isNotEmpty ?? false) - ...[Divider(height: 30), + if (viewModel.giftCard.cardPin.isNotEmpty) ...[ + Divider(height: 30), buildIoniaTile( context, title: S.of(context).pin_number, subTitle: viewModel.giftCard.cardPin, - )], + ) + ], Divider(height: 30), - Observer(builder: (_) => - buildIoniaTile( - context, - title: S.of(context).amount, - subTitle: viewModel.remainingAmount.toStringAsFixed(2) ?? '0.00', - )), + Observer( + builder: (_) => buildIoniaTile( + context, + title: S.of(context).amount, + subTitle: viewModel.remainingAmount.toStringAsFixed(2), + )), Divider(height: 50), TextIconButton( label: S.of(context).how_to_use_card, @@ -130,29 +132,28 @@ class IoniaGiftCardDetailPage extends BasePage { if (!viewModel.giftCard.isEmpty) { return Column( children: [ - //PrimaryButton( - // onPressed: () async { - // final amount = await Navigator.of(context) - // .pushNamed(Routes.ioniaMoreOptionsPage, arguments: [viewModel.giftCard]) as String; - // if (amount != null) { - // viewModel.updateRemaining(double.parse(amount)); - // } - // }, - // text: S.of(context).more_options, - // color: Theme.of(context).accentTextTheme!.caption!.color!, - // textColor: Theme.of(context).primaryTextTheme!.headline6!.color!, - //), - //SizedBox(height: 12), + PrimaryButton( + onPressed: () async { + await Navigator.of(context).pushNamed( + Routes.ioniaMoreOptionsPage, + arguments: [viewModel.giftCard]) as String?; + viewModel.refeshCard(); + }, + text: S.of(context).more_options, + color: Theme.of(context).accentTextTheme.caption!.color!, + textColor: Theme.of(context).primaryTextTheme.headline6!.color!, + ), + SizedBox(height: 12), LoadingPrimaryButton( isLoading: viewModel.redeemState is IsExecutingState, onPressed: () => viewModel.redeem().then( (_) { - Navigator.of(context) - .pushNamedAndRemoveUntil(Routes.ioniaManageCardsPage, (route) => route.isFirst); + Navigator.of(context).pushNamedAndRemoveUntil( + Routes.ioniaManageCardsPage, (route) => route.isFirst); }, ), text: S.of(context).mark_as_redeemed, - color: Theme.of(context).accentTextTheme!.bodyText1!.color!, + color: Theme.of(context).accentTextTheme.bodyText1!.color!, textColor: Colors.white, ), ], @@ -168,12 +169,11 @@ class IoniaGiftCardDetailPage extends BasePage { Widget buildIoniaTile(BuildContext context, {required String title, required String subTitle}) { return IoniaTile( - title: title, - subTitle: subTitle, - onTap: () { - Clipboard.setData(ClipboardData(text: subTitle)); - showBar(context, - S.of(context).transaction_details_copied(title)); + title: title, + subTitle: subTitle, + onTap: () { + Clipboard.setData(ClipboardData(text: subTitle)); + showBar(context, S.of(context).transaction_details_copied(title)); }); } @@ -184,10 +184,10 @@ class IoniaGiftCardDetailPage extends BasePage { showPopUp( context: context, builder: (BuildContext context) { - return IoniaAlertModal( + return IoniaAlertModal( title: S.of(context).how_to_use_card, content: Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, children: viewModel.giftCard.instructions .map((instruction) { return [ @@ -196,13 +196,13 @@ class IoniaGiftCardDetailPage extends BasePage { child: Text( instruction.header, style: textLargeSemiBold( - color: Theme.of(context).textTheme!.headline3!.color!, + color: Theme.of(context).textTheme.headline3!.color!, ), )), Text( instruction.body, style: textMedium( - color: Theme.of(context).textTheme!.headline3!.color!, + color: Theme.of(context).textTheme.headline3!.color!, ), ) ]; @@ -210,7 +210,7 @@ class IoniaGiftCardDetailPage extends BasePage { .expand((e) => e) .toList()), actionTitle: S.of(context).send_got_it, - ); + ); }); } } diff --git a/lib/src/screens/ionia/cards/ionia_more_options_page.dart b/lib/src/screens/ionia/cards/ionia_more_options_page.dart index 84f0bed30..7d38c5e1e 100644 --- a/lib/src/screens/ionia/cards/ionia_more_options_page.dart +++ b/lib/src/screens/ionia/cards/ionia_more_options_page.dart @@ -5,7 +5,6 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/typography.dart'; import 'package:flutter/material.dart'; - class IoniaMoreOptionsPage extends BasePage { IoniaMoreOptionsPage(this.giftCard); @@ -16,7 +15,7 @@ class IoniaMoreOptionsPage extends BasePage { return Text( S.current.more_options, style: textMediumSemiBold( - color: Theme.of(context).accentTextTheme!.headline1!.backgroundColor!, + color: Theme.of(context).accentTextTheme.headline1!.backgroundColor!, ), ); } @@ -27,40 +26,45 @@ class IoniaMoreOptionsPage extends BasePage { padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - SizedBox(height: 10,), - Center(child: Text(S.of(context).choose_from_available_options, style: textMedium( - color: Theme.of(context).primaryTextTheme!.headline6!.color!, - ),)), - SizedBox(height: 40,), - InkWell( - onTap: () async { - final amount = await Navigator.of(context).pushNamed(Routes.ioniaCustomRedeemPage, arguments: [giftCard]) as String; - if(amount.isNotEmpty){ - Navigator.pop(context, amount); - } - }, - child: _GradiantContainer( - content: Padding( - padding: const EdgeInsets.only(top: 24, left: 20, right: 24, bottom: 50), - child: Text( - S.of(context).custom_redeem_amount, - style: textXLargeSemiBold(), - ), - ), - ), - ) - ], - ), + children: [ + SizedBox( + height: 10, + ), + Center( + child: Text( + S.of(context).choose_from_available_options, + style: textMedium( + color: Theme.of(context).primaryTextTheme.headline6!.color!, + ), + ), + ), + SizedBox(height: 40), + InkWell( + onTap: () async { + final amount = await Navigator.of(context) + .pushNamed(Routes.ioniaCustomRedeemPage, arguments: [giftCard]) as String?; + if (amount != null && amount.isNotEmpty) { + Navigator.pop(context); + } + }, + child: _GradiantContainer( + content: Padding( + padding: const EdgeInsets.only(top: 24, left: 20, right: 24, bottom: 50), + child: Text( + S.of(context).custom_redeem_amount, + style: textXLargeSemiBold(), + ), + ), + ), + ) + ], + ), ); } } class _GradiantContainer extends StatelessWidget { - const _GradiantContainer({ - Key? key, - required this.content - }) : super(key: key); + const _GradiantContainer({Key? key, required this.content}) : super(key: key); final Widget content; diff --git a/lib/src/screens/root/root.dart b/lib/src/screens/root/root.dart index c507f6e1f..2526d15f3 100644 --- a/lib/src/screens/root/root.dart +++ b/lib/src/screens/root/root.dart @@ -1,23 +1,28 @@ import 'dart:async'; +import 'package:cake_wallet/core/auth_service.dart'; +import 'package:cake_wallet/utils/payment_request.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/auth/auth_page.dart'; import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/store/authentication_store.dart'; import 'package:cake_wallet/entities/qr_scanner.dart'; +import 'package:uni_links/uni_links.dart'; class Root extends StatefulWidget { - Root( - {required Key key, - required this.authenticationStore, - required this.appStore, - required this.child, - required this.navigatorKey}) - : super(key: key); + Root({ + required Key key, + required this.authenticationStore, + required this.appStore, + required this.child, + required this.navigatorKey, + required this.authService, + }) : super(key: key); final AuthenticationStore authenticationStore; final AppStore appStore; final GlobalKey navigatorKey; + final AuthService authService; final Widget child; @override @@ -26,22 +31,56 @@ class Root extends StatefulWidget { class RootState extends State with WidgetsBindingObserver { RootState() - : _isInactiveController = StreamController.broadcast(), - _isInactive = false, - _postFrameCallback = false; + : _isInactiveController = StreamController.broadcast(), + _isInactive = false, + _requestAuth = true, + _postFrameCallback = false; Stream get isInactive => _isInactiveController.stream; StreamController _isInactiveController; bool _isInactive; bool _postFrameCallback; + bool _requestAuth; + + StreamSubscription? stream; + Uri? launchUri; @override void initState() { + _requestAuth = widget.authService.requireAuth(); _isInactiveController = StreamController.broadcast(); _isInactive = false; _postFrameCallback = false; WidgetsBinding.instance.addObserver(this); super.initState(); + + initUniLinks(); + } + + @override + void dispose() { + stream?.cancel(); + super.dispose(); + } + + /// handle app links while the app is already started + /// whether its in the foreground or in the background. + Future initUniLinks() async { + try { + stream = uriLinkStream.listen((Uri? uri) { + handleDeepLinking(uri); + }); + + handleDeepLinking(await getInitialUri()); + } catch (e) { + print(e); + } + } + + void handleDeepLinking(Uri? uri) { + if (uri == null || !mounted) return; + + launchUri = uri; } @override @@ -52,11 +91,15 @@ class RootState extends State with WidgetsBindingObserver { return; } - if (!_isInactive && - widget.authenticationStore.state == AuthenticationState.allowed) { + if (!_isInactive && widget.authenticationStore.state == AuthenticationState.allowed) { setState(() => _setInactive(true)); } + break; + case AppLifecycleState.resumed: + setState(() { + _requestAuth = widget.authService.requireAuth(); + }); break; default: break; @@ -65,7 +108,7 @@ class RootState extends State with WidgetsBindingObserver { @override Widget build(BuildContext context) { - if (_isInactive && !_postFrameCallback) { + if (_isInactive && !_postFrameCallback && _requestAuth) { _postFrameCallback = true; WidgetsBinding.instance.addPostFrameCallback((_) { widget.navigatorKey.currentState?.pushNamed(Routes.unlock, @@ -75,9 +118,19 @@ class RootState extends State with WidgetsBindingObserver { } _reset(); - auth.close(); + auth.close( + route: launchUri != null ? Routes.send : null, + arguments: PaymentRequest.fromUri(launchUri), + ); + launchUri = null; }); }); + } else if (launchUri != null) { + widget.navigatorKey.currentState?.pushNamed( + Routes.send, + arguments: PaymentRequest.fromUri(launchUri), + ); + launchUri = null; } return WillPopScope(onWillPop: () async => false, child: widget.child); diff --git a/lib/src/screens/send/send_page.dart b/lib/src/screens/send/send_page.dart index 190127c0c..2cd849f34 100644 --- a/lib/src/screens/send/send_page.dart +++ b/lib/src/screens/send/send_page.dart @@ -4,6 +4,7 @@ import 'package:cake_wallet/src/screens/send/widgets/send_card.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/utils/payment_request.dart'; import 'package:cake_wallet/view_model/send/output.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; @@ -25,11 +26,15 @@ import 'package:smooth_page_indicator/smooth_page_indicator.dart'; import 'package:cw_core/crypto_currency.dart'; class SendPage extends BasePage { - SendPage({required this.sendViewModel}) : _formKey = GlobalKey(); + SendPage({ + required this.sendViewModel, + this.initialPaymentRequest, + }) : _formKey = GlobalKey(); final SendViewModel sendViewModel; final GlobalKey _formKey; final controller = PageController(initialPage: 0); + final PaymentRequest? initialPaymentRequest; bool _effectsInstalled = false; @@ -116,6 +121,7 @@ class SendPage extends BasePage { key: output.key, output: output, sendViewModel: sendViewModel, + initialPaymentRequest: initialPaymentRequest, ); }); }, diff --git a/lib/src/screens/send/widgets/send_card.dart b/lib/src/screens/send/widgets/send_card.dart index 8f0d6675d..082067d95 100644 --- a/lib/src/screens/send/widgets/send_card.dart +++ b/lib/src/screens/send/widgets/send_card.dart @@ -1,4 +1,5 @@ import 'package:cake_wallet/entities/priority_for_wallet_type.dart'; +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/utils/payment_request.dart'; import 'package:cw_core/transaction_priority.dart'; import 'package:cake_wallet/routes.dart'; @@ -20,21 +21,28 @@ class SendCard extends StatefulWidget { SendCard({ Key? key, required this.output, - required this.sendViewModel}) : super(key: key); + required this.sendViewModel, + this.initialPaymentRequest, + }) : super(key: key); final Output output; final SendViewModel sendViewModel; + final PaymentRequest? initialPaymentRequest; @override SendCardState createState() => SendCardState( output: output, - sendViewModel: sendViewModel + sendViewModel: sendViewModel, + initialPaymentRequest: initialPaymentRequest, ); } class SendCardState extends State with AutomaticKeepAliveClientMixin { - SendCardState({required this.output, required this.sendViewModel}) + SendCardState({ + required this.output, + required this.sendViewModel, + this.initialPaymentRequest}) : addressController = TextEditingController(), cryptoAmountController = TextEditingController(), fiatAmountController = TextEditingController(), @@ -49,6 +57,7 @@ class SendCardState extends State final Output output; final SendViewModel sendViewModel; + final PaymentRequest? initialPaymentRequest; final TextEditingController addressController; final TextEditingController cryptoAmountController; @@ -61,6 +70,27 @@ class SendCardState extends State bool _effectsInstalled = false; + @override + void initState() { + super.initState(); + + /// if the current wallet doesn't match the one in the qr code + if (initialPaymentRequest != null && + sendViewModel.walletCurrencyName != initialPaymentRequest!.scheme.toLowerCase()) { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithOneAction( + alertTitle: S.of(context).error, + alertContent: S.of(context).unmatched_currencies, + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop()); + }); + }); + } + } + @override Widget build(BuildContext context) { super.build(context); @@ -512,8 +542,12 @@ class SendCardState extends State } void _setEffects(BuildContext context) { - addressController.text = output.address; - cryptoAmountController.text = output.cryptoAmount; + if (output.address.isNotEmpty) { + addressController.text = output.address; + } + if (output.cryptoAmount.isNotEmpty) { + cryptoAmountController.text = output.cryptoAmount; + } fiatAmountController.text = output.fiatAmount; noteController.text = output.note; extractedAddressController.text = output.extractedAddress; @@ -605,6 +639,13 @@ class SendCardState extends State extractedAddressController.text = extractedAddress; }); + if (initialPaymentRequest != null && + sendViewModel.walletCurrencyName == initialPaymentRequest!.scheme.toLowerCase()) { + addressController.text = initialPaymentRequest!.address; + cryptoAmountController.text = initialPaymentRequest!.amount; + noteController.text = initialPaymentRequest!.note; + } + _effectsInstalled = true; } diff --git a/lib/src/screens/settings/security_backup_page.dart b/lib/src/screens/settings/security_backup_page.dart index 224c941ee..102a94b36 100644 --- a/lib/src/screens/settings/security_backup_page.dart +++ b/lib/src/screens/settings/security_backup_page.dart @@ -1,9 +1,11 @@ +import 'package:cake_wallet/entities/pin_code_required_duration.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/auth/auth_page.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart'; import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart'; +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/view_model/settings/security_settings_view_model.dart'; @@ -25,22 +27,26 @@ 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: (_) => _securitySettingsViewModel.checkPinCodeRiquired() + ? Navigator.of(context).pushNamed(Routes.auth, + arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) { + if (isAuthenticatedSuccessfully) { + auth.close(route: Routes.showKeys); + } + }) + : Navigator.of(context).pushNamed(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: (_) => _securitySettingsViewModel.checkPinCodeRiquired() + ? Navigator.of(context).pushNamed(Routes.auth, + arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) { + if (isAuthenticatedSuccessfully) { + auth.close(route: Routes.backup); + } + }) + : Navigator.of(context).pushNamed(Routes.backup), ), StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)), SettingsCellWithArrow( @@ -65,10 +71,12 @@ class SecurityBackupPage extends BasePage { arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) async { if (isAuthenticatedSuccessfully) { if (await _securitySettingsViewModel.biometricAuthenticated()) { - _securitySettingsViewModel.setAllowBiometricalAuthentication(isAuthenticatedSuccessfully); + _securitySettingsViewModel + .setAllowBiometricalAuthentication(isAuthenticatedSuccessfully); } } else { - _securitySettingsViewModel.setAllowBiometricalAuthentication(isAuthenticatedSuccessfully); + _securitySettingsViewModel + .setAllowBiometricalAuthentication(isAuthenticatedSuccessfully); } auth.close(); @@ -78,6 +86,16 @@ class SecurityBackupPage extends BasePage { } }); }), + Observer(builder: (_) { + return SettingsPickerCell( + title: S.current.require_pin_after, + items: PinCodeRequiredDuration.values, + selectedItem: _securitySettingsViewModel.pinCodeRequiredDuration, + onItemSelected: (PinCodeRequiredDuration code) { + _securitySettingsViewModel.setPinCodeRequiredDuration(code); + }, + ); + }), ]), ); } diff --git a/lib/src/screens/wallet_list/wallet_list_page.dart b/lib/src/screens/wallet_list/wallet_list_page.dart index c1a7ea953..d76631b3f 100644 --- a/lib/src/screens/wallet_list/wallet_list_page.dart +++ b/lib/src/screens/wallet_list/wallet_list_page.dart @@ -220,68 +220,88 @@ class WalletListBodyState extends State { } Future _loadWallet(WalletListItem wallet) async { - await Navigator.of(context).pushNamed(Routes.auth, arguments: - (bool isAuthenticatedSuccessfully, AuthPageState auth) async { - if (!isAuthenticatedSuccessfully) { - return; - } + if (await widget.walletListViewModel.checkIfAuthRequired()) { + await Navigator.of(context).pushNamed(Routes.auth, + arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) 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(); + 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 { - auth.changeProcessText( - S.of(context).wallet_list_loading_wallet(wallet.name)); + changeProcessText(S.of(context).wallet_list_loading_wallet(wallet.name)); await widget.walletListViewModel.loadWallet(wallet); - auth.hideProgressText(); - auth.close(); - WidgetsBinding.instance.addPostFrameCallback((_) { - Navigator.of(context).pop(); - }); + hideProgressText(); + Navigator.of(context).pop(); } catch (e) { - auth.changeProcessText(S - .of(context) - .wallet_list_failed_to_load(wallet.name, e.toString())); + changeProcessText(S.of(context).wallet_list_failed_to_load(wallet.name, e.toString())); } - }); + } } Future _removeWallet(WalletListItem wallet) async { - await Navigator.of(context).pushNamed(Routes.auth, arguments: - (bool isAuthenticatedSuccessfully, AuthPageState auth) async { - if (!isAuthenticatedSuccessfully) { - return; - } - - bool confirmed = false; - - await showPopUp( - context: context, - builder: (BuildContext context) { - return AlertWithTwoActions( - alertTitle: S.of(context).delete_wallet, - alertContent: S.of(context).delete_wallet_confirm_message(wallet.name), - leftButtonText: S.of(context).cancel, - rightButtonText: S.of(context).delete, - actionLeftButton: () => Navigator.of(context).pop(), - actionRightButton: () { - confirmed = true; - Navigator.of(context).pop(); - }, - ); - }); - - if (confirmed) { - try { - auth.changeProcessText( - S.of(context).wallet_list_removing_wallet(wallet.name)); - await widget.walletListViewModel.remove(wallet); - } catch (e) { - auth.changeProcessText(S - .of(context) - .wallet_list_failed_to_remove(wallet.name, e.toString())); + 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); + } + } - auth.close(); - }); + void _onSuccessfulAuth(WalletListItem wallet, AuthPageState? auth) async { + bool confirmed = false; + await showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithTwoActions( + alertTitle: S.of(context).delete_wallet, + alertContent: S.of(context).delete_wallet_confirm_message(wallet.name), + leftButtonText: S.of(context).cancel, + rightButtonText: S.of(context).delete, + actionLeftButton: () => Navigator.of(context).pop(), + actionRightButton: () { + confirmed = true; + Navigator.of(context).pop(); + }, + ); + }); + + 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)); + await widget.walletListViewModel.remove(wallet); + } 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()), + ); + } + } + + auth?.close(); } void changeProcessText(String text) { @@ -294,16 +314,16 @@ class WalletListBodyState extends State { } ActionPane _actionPane(WalletListItem wallet) => ActionPane( - motion: const ScrollMotion(), - extentRatio: 0.3, - children: [ - SlidableAction( - onPressed: (_) => _removeWallet(wallet), - backgroundColor: Colors.red, - foregroundColor: Colors.white, - icon: CupertinoIcons.delete, - label: S.of(context).delete, - ), - ], - ); + motion: const ScrollMotion(), + extentRatio: 0.3, + children: [ + SlidableAction( + onPressed: (_) => _removeWallet(wallet), + backgroundColor: Colors.red, + foregroundColor: Colors.white, + icon: CupertinoIcons.delete, + label: S.of(context).delete, + ), + ], + ); } diff --git a/lib/store/dashboard/trades_store.dart b/lib/store/dashboard/trades_store.dart index 6e763196b..72442b46f 100644 --- a/lib/store/dashboard/trades_store.dart +++ b/lib/store/dashboard/trades_store.dart @@ -31,13 +31,8 @@ abstract class TradesStoreBase with Store { void setTrade(Trade trade) => this.trade = trade; @action - Future updateTradeList() async { - if (trade == null) { - return; - } - - trades = tradesSource.values.map((trade) => TradeListItem( - trade: trade!, - settingsStore: settingsStore)).toList(); - } + Future updateTradeList() async => trades = + tradesSource.values.map((trade) => TradeListItem( + trade: trade, + settingsStore: settingsStore)).toList(); } \ No newline at end of file diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index 96d74a23a..b10e0d08d 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -1,4 +1,5 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart'; +import 'package:cake_wallet/entities/pin_code_required_duration.dart'; import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cw_core/transaction_priority.dart'; import 'package:cake_wallet/themes/theme_base.dart'; @@ -17,7 +18,6 @@ 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:cake_wallet/.secrets.g.dart' as secrets; part 'settings_store.g.dart'; @@ -41,6 +41,7 @@ abstract class SettingsStoreBase with Store { required this.shouldShowYatPopup, required this.isBitcoinBuyEnabled, required this.actionlistDisplayMode, + required this.pinTimeOutDuration, TransactionPriority? initialBitcoinTransactionPriority, TransactionPriority? initialMoneroTransactionPriority, TransactionPriority? initialHavenTransactionPriority, @@ -141,6 +142,11 @@ abstract class SettingsStoreBase with Store { (String languageCode) => sharedPreferences.setString( PreferencesKey.currentLanguageCode, languageCode)); + reaction( + (_) => pinTimeOutDuration, + (PinCodeRequiredDuration pinCodeInterval) => sharedPreferences.setInt( + PreferencesKey.pinTimeOutDuration, pinCodeInterval.value)); + reaction( (_) => balanceDisplayMode, (BalanceDisplayMode mode) => sharedPreferences.setInt( @@ -162,6 +168,7 @@ abstract class SettingsStoreBase with Store { static const defaultPinLength = 4; static const defaultActionsMode = 11; + static const defaultPinCodeTimeOutDuration = PinCodeRequiredDuration.tenminutes; @observable FiatCurrency fiatCurrency; @@ -193,6 +200,9 @@ abstract class SettingsStoreBase with Store { @observable int pinCodeLength; + @observable + PinCodeRequiredDuration pinTimeOutDuration; + @computed ThemeData get theme => currentTheme.themeData; @@ -288,6 +298,11 @@ abstract class SettingsStoreBase with Store { sharedPreferences.getInt(PreferencesKey.displayActionListModeKey) ?? defaultActionsMode)); var pinLength = sharedPreferences.getInt(PreferencesKey.currentPinLength); + final timeOutDuration = sharedPreferences.getInt(PreferencesKey.pinTimeOutDuration); + final pinCodeTimeOutDuration = timeOutDuration != null + ? PinCodeRequiredDuration.deserialize(raw: timeOutDuration) + : defaultPinCodeTimeOutDuration; + // If no value if (pinLength == null || pinLength == 0) { pinLength = defaultPinLength; @@ -343,6 +358,7 @@ abstract class SettingsStoreBase with Store { initialTheme: savedTheme, actionlistDisplayMode: actionListDisplayMode, initialPinLength: pinLength, + pinTimeOutDuration: pinCodeTimeOutDuration, initialLanguageCode: savedLanguageCode, initialMoneroTransactionPriority: moneroTransactionPriority, initialBitcoinTransactionPriority: bitcoinTransactionPriority, diff --git a/lib/utils/payment_request.dart b/lib/utils/payment_request.dart index 26244059f..88730f037 100644 --- a/lib/utils/payment_request.dart +++ b/lib/utils/payment_request.dart @@ -1,22 +1,25 @@ class PaymentRequest { - PaymentRequest(this.address, this.amount, this.note); + PaymentRequest(this.address, this.amount, this.note, this.scheme); - factory PaymentRequest.fromUri(Uri uri) { + factory PaymentRequest.fromUri(Uri? uri) { var address = ""; var amount = ""; var note = ""; + var scheme = ""; if (uri != null) { address = uri.path; amount = uri.queryParameters['tx_amount'] ?? uri.queryParameters['amount'] ?? ""; note = uri.queryParameters['tx_description'] ?? uri.queryParameters['message'] ?? ""; + scheme = uri.scheme; } - return PaymentRequest(address, amount, note); + return PaymentRequest(address, amount, note, scheme); } final String address; final String amount; final String note; + final String scheme; } \ No newline at end of file diff --git a/lib/view_model/auth_view_model.dart b/lib/view_model/auth_view_model.dart index 29ef46b47..e50f4db0c 100644 --- a/lib/view_model/auth_view_model.dart +++ b/lib/view_model/auth_view_model.dart @@ -14,10 +14,12 @@ part 'auth_view_model.g.dart'; class AuthViewModel = AuthViewModelBase with _$AuthViewModel; abstract class AuthViewModelBase with Store { - AuthViewModelBase(this._authService, this._sharedPreferences, - this._settingsStore, this._biometricAuth) + AuthViewModelBase( + this._authService, this._sharedPreferences, this._settingsStore, this._biometricAuth) : _failureCounter = 0, - state = InitialExecutionState(); + state = InitialExecutionState() { + reaction((_) => state, _saveLastAuthTime); + } static const maxFailedLogins = 3; static const banTimeout = 180; // 3 minutes @@ -28,8 +30,7 @@ abstract class AuthViewModelBase with Store { int get pinLength => _settingsStore.pinCodeLength; - bool get isBiometricalAuthenticationAllowed => - _settingsStore.allowBiometricalAuthentication; + bool get isBiometricalAuthenticationAllowed => _settingsStore.allowBiometricalAuthentication; @observable int _failureCounter; @@ -114,8 +115,14 @@ abstract class AuthViewModelBase with Store { state = ExecutedSuccessfullyState(); } } - } catch(e) { + } catch (e) { state = FailureState(e.toString()); } } + + void _saveLastAuthTime(ExecutionState state) { + if (state is ExecutedSuccessfullyState) { + _authService.saveLastAuthTime(); + } + } } diff --git a/lib/view_model/ionia/ionia_custom_redeem_view_model.dart b/lib/view_model/ionia/ionia_custom_redeem_view_model.dart index 963604c15..5776443ee 100644 --- a/lib/view_model/ionia/ionia_custom_redeem_view_model.dart +++ b/lib/view_model/ionia/ionia_custom_redeem_view_model.dart @@ -1,29 +1,51 @@ +import 'package:cake_wallet/core/execution_state.dart'; import 'package:cake_wallet/ionia/ionia_gift_card.dart'; +import 'package:cake_wallet/ionia/ionia_service.dart'; import 'package:mobx/mobx.dart'; part 'ionia_custom_redeem_view_model.g.dart'; + class IoniaCustomRedeemViewModel = IoniaCustomRedeemViewModelBase with _$IoniaCustomRedeemViewModel; abstract class IoniaCustomRedeemViewModelBase with Store { - IoniaCustomRedeemViewModelBase(this.giftCard) - : amount = 0; + IoniaCustomRedeemViewModelBase({ + required this.giftCard, + required this.ioniaService, + }) : amount = 0, + redeemState = InitialExecutionState(); final IoniaGiftCard giftCard; + final IoniaService ioniaService; + + @observable + ExecutionState redeemState; + @observable double amount; @computed - double get remaining => amount <= giftCard.remainingAmount ? giftCard.remainingAmount - amount : 0; + double get remaining => + amount <= giftCard.remainingAmount ? giftCard.remainingAmount - amount : 0; @computed - String get formattedRemaining => remaining.toStringAsFixed(2); + String get formattedRemaining => remaining.toStringAsFixed(2); @computed bool get disableRedeem => amount > giftCard.remainingAmount; @action - void updateAmount(String text){ - amount = text.isEmpty ? 0 : (double.parse(text.replaceAll(',', '.')) ?? 0); + void updateAmount(String text) { + amount = double.tryParse(text.replaceAll(',', '.')) ?? 0; } -} \ No newline at end of file + @action + Future addCustomRedeem() async { + try { + redeemState = IsExecutingState(); + await ioniaService.redeem(giftCardId: giftCard.id, amount: amount); + redeemState = ExecutedSuccessfullyState(); + } catch (e) { + redeemState = FailureState(e.toString()); + } + } +} diff --git a/lib/view_model/ionia/ionia_gift_card_details_view_model.dart b/lib/view_model/ionia/ionia_gift_card_details_view_model.dart index 8e0a2795b..cbf5ebc78 100644 --- a/lib/view_model/ionia/ionia_gift_card_details_view_model.dart +++ b/lib/view_model/ionia/ionia_gift_card_details_view_model.dart @@ -6,21 +6,19 @@ import 'package:device_display_brightness/device_display_brightness.dart'; part 'ionia_gift_card_details_view_model.g.dart'; -class IoniaGiftCardDetailsViewModel = IoniaGiftCardDetailsViewModelBase with _$IoniaGiftCardDetailsViewModel; +class IoniaGiftCardDetailsViewModel = IoniaGiftCardDetailsViewModelBase + with _$IoniaGiftCardDetailsViewModel; abstract class IoniaGiftCardDetailsViewModelBase with Store { - - IoniaGiftCardDetailsViewModelBase({ - required this.ioniaService, - required this.giftCard}) - : redeemState = InitialExecutionState(), - remainingAmount = giftCard.remainingAmount, - brightness = 0; + IoniaGiftCardDetailsViewModelBase({required this.ioniaService, required this.giftCard}) + : redeemState = InitialExecutionState(), + remainingAmount = giftCard.remainingAmount, + brightness = 0; final IoniaService ioniaService; - + double brightness; - + @observable IoniaGiftCard giftCard; @@ -35,21 +33,22 @@ abstract class IoniaGiftCardDetailsViewModelBase with Store { giftCard.remainingAmount = remainingAmount; try { redeemState = IsExecutingState(); - await ioniaService.redeem(giftCard); + await ioniaService.redeem(giftCardId: giftCard.id, amount: giftCard.remainingAmount); giftCard = await ioniaService.getGiftCard(id: giftCard.id); redeemState = ExecutedSuccessfullyState(); - } catch(e) { + } catch (e) { redeemState = FailureState(e.toString()); } } @action - void updateRemaining(double amount){ - remainingAmount = amount; + Future refeshCard() async { + giftCard = await ioniaService.getGiftCard(id: giftCard.id); + remainingAmount = giftCard.remainingAmount; } void increaseBrightness() async { brightness = await DeviceDisplayBrightness.getBrightness(); await DeviceDisplayBrightness.setBrightness(1.0); } -} \ No newline at end of file +} diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index fd783cf6f..b6604789a 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -53,7 +53,7 @@ abstract class SendViewModelBase with Store { outputs.add(Output(_wallet, _settingsStore, _fiatConversationStore, () => selectedCryptoCurrency)); } - + @observable ExecutionState state; @@ -180,6 +180,8 @@ abstract class SendViewModelBase with Store { WalletType get walletType => _wallet.type; + String? get walletCurrencyName => _wallet.currency.name?.toLowerCase(); + bool get hasCurrecyChanger => walletType == WalletType.haven; @computed @@ -306,7 +308,7 @@ abstract class SendViewModelBase with Store { @action void onClose() => _settingsStore.fiatCurrency = fiatFromSettings; - + @action void setFiatCurrency(FiatCurrency fiat) => _settingsStore.fiatCurrency = fiat; diff --git a/lib/view_model/settings/security_settings_view_model.dart b/lib/view_model/settings/security_settings_view_model.dart index 4a88b633f..c48223af6 100644 --- a/lib/view_model/settings/security_settings_view_model.dart +++ b/lib/view_model/settings/security_settings_view_model.dart @@ -1,4 +1,6 @@ +import 'package:cake_wallet/core/auth_service.dart'; import 'package:cake_wallet/entities/biometric_auth.dart'; +import 'package:cake_wallet/entities/pin_code_required_duration.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:mobx/mobx.dart'; @@ -7,19 +9,33 @@ part 'security_settings_view_model.g.dart'; class SecuritySettingsViewModel = SecuritySettingsViewModelBase with _$SecuritySettingsViewModel; abstract class SecuritySettingsViewModelBase with Store { - SecuritySettingsViewModelBase(this._settingsStore) : _biometricAuth = BiometricAuth(); + SecuritySettingsViewModelBase( + this._settingsStore, + this._authService, + ) : _biometricAuth = BiometricAuth(); final BiometricAuth _biometricAuth; final SettingsStore _settingsStore; + final AuthService _authService; @computed bool get allowBiometricalAuthentication => _settingsStore.allowBiometricalAuthentication; + @computed + PinCodeRequiredDuration get pinCodeRequiredDuration => _settingsStore.pinTimeOutDuration; + @action Future biometricAuthenticated() async { return await _biometricAuth.canCheckBiometrics() && await _biometricAuth.isAuthenticated(); } @action - void setAllowBiometricalAuthentication(bool value) => _settingsStore.allowBiometricalAuthentication = value; + void setAllowBiometricalAuthentication(bool value) => + _settingsStore.allowBiometricalAuthentication = value; + + @action + setPinCodeRequiredDuration(PinCodeRequiredDuration duration) => + _settingsStore.pinTimeOutDuration = duration; + + bool checkPinCodeRiquired() => _authService.requireAuth(); } diff --git a/lib/view_model/wallet_list/wallet_list_view_model.dart b/lib/view_model/wallet_list/wallet_list_view_model.dart index 0bbc68748..6d63675ba 100644 --- a/lib/view_model/wallet_list/wallet_list_view_model.dart +++ b/lib/view_model/wallet_list/wallet_list_view_model.dart @@ -1,5 +1,5 @@ +import 'package:cake_wallet/core/auth_service.dart'; import 'package:cake_wallet/core/wallet_loading_service.dart'; -import 'package:cake_wallet/view_model/wallet_new_vm.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; import 'package:cake_wallet/di.dart'; @@ -15,9 +15,12 @@ part 'wallet_list_view_model.g.dart'; class WalletListViewModel = WalletListViewModelBase with _$WalletListViewModel; abstract class WalletListViewModelBase with Store { - WalletListViewModelBase(this._walletInfoSource, this._appStore, - this._walletLoadingService) - : wallets = ObservableList() { + WalletListViewModelBase( + this._walletInfoSource, + this._appStore, + this._walletLoadingService, + this._authService, + ) : wallets = ObservableList() { _updateList(); } @@ -27,6 +30,7 @@ abstract class WalletListViewModelBase with Store { final AppStore _appStore; final Box _walletInfoSource; final WalletLoadingService _walletLoadingService; + final AuthService _authService; WalletType get currentWalletType => _appStore.wallet!.type; @@ -47,12 +51,20 @@ abstract class WalletListViewModelBase with Store { void _updateList() { wallets.clear(); - wallets.addAll(_walletInfoSource.values.map((info) => WalletListItem( - name: info.name, - type: info.type, - key: info.key, - isCurrent: info.name == _appStore.wallet!.name && - info.type == _appStore.wallet!.type, - isEnabled: availableWalletTypes.contains(info.type)))); + wallets.addAll( + _walletInfoSource.values.map( + (info) => WalletListItem( + name: info.name, + type: info.type, + key: info.key, + isCurrent: info.name == _appStore.wallet!.name && info.type == _appStore.wallet!.type, + isEnabled: availableWalletTypes.contains(info.type), + ), + ), + ); + } + + bool checkIfAuthRequired() { + return _authService.requireAuth(); } } diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 404e92282..bb01cdaba 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -663,6 +663,9 @@ "privacy": "Datenschutz", "display_settings": "Anzeigeeinstellungen", "other_settings": "Andere Einstellungen", + "require_pin_after": "PIN anfordern nach", + "always": "immer", + "minutes_to_pin_code": "${minute} Minuten", "disable_exchange": "Exchange deaktivieren", "advanced_privacy_settings": "Erweiterte Datenschutzeinstellungen", "settings_can_be_changed_later": "Diese Einstellungen können später in den App-Einstellungen geändert werden", @@ -671,5 +674,6 @@ "fiat_api": "Fiat API", "disabled": "Deaktiviert", "enabled": "Ermöglicht", - "tor_only": "Nur Tor" + "tor_only": "Nur Tor", + "unmatched_currencies": "Die Währung Ihres aktuellen Wallets stimmt nicht mit der des gescannten QR überein" } diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index e405dd57b..8cd6a3942 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -663,6 +663,9 @@ "privacy": "Privacy", "display_settings": "Display settings", "other_settings": "Other settings", + "require_pin_after": "Require PIN after", + "always": "Always", + "minutes_to_pin_code": "${minute} minutes", "disable_exchange": "Disable exchange", "advanced_privacy_settings": "Advanced Privacy Settings", "settings_can_be_changed_later": "These settings can be changed later in the app settings", @@ -671,5 +674,6 @@ "fiat_api": "Fiat API", "disabled": "Disabled", "enabled": "Enabled", - "tor_only": "Tor only" + "tor_only": "Tor only", + "unmatched_currencies": "Your current wallet's currency does not match that of the scanned QR" } diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index b9691d8c1..63fca7fbf 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -663,6 +663,9 @@ "privacy": "Privacidad", "display_settings": "Configuración de pantalla", "other_settings": "Otras configuraciones", + "require_pin_after": "Requerir PIN después de", + "always": "siempre", + "minutes_to_pin_code": "${minute} minutos", "disable_exchange": "Deshabilitar intercambio", "advanced_privacy_settings": "Configuración avanzada de privacidad", "settings_can_be_changed_later": "Estas configuraciones se pueden cambiar más tarde en la configuración de la aplicación", @@ -671,5 +674,6 @@ "fiat_api": "Fiat API", "disabled": "Desactivado", "enabled": "Activado", - "tor_only": "solo Tor" + "tor_only": "solo Tor", + "unmatched_currencies": "La moneda de su billetera actual no coincide con la del QR escaneado" } diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index ac1c11a4a..dc975ac24 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -661,6 +661,9 @@ "privacy": "Confidentialité", "display_settings": "Paramètres d'affichage", "other_settings": "Autres paramètres", + "require_pin_after": "NIP requis après", + "always": "toujours", + "minutes_to_pin_code": "${minute} minutes", "disable_exchange": "Désactiver l'échange", "advanced_privacy_settings": "Paramètres de confidentialité avancés", "settings_can_be_changed_later": "Ces paramètres peuvent être modifiés ultérieurement dans les paramètres de l'application", @@ -669,5 +672,6 @@ "fiat_api": "Fiat API", "disabled": "Handicapé", "enabled": "Activé", - "tor_only": "Tor uniquement" + "tor_only": "Tor uniquement", + "unmatched_currencies": "La devise de votre portefeuille actuel ne correspond pas à celle du QR scanné" } diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 3d984decb..7cb5ff132 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -663,12 +663,17 @@ "privacy": "गोपनीयता", "display_settings": "प्रदर्शन सेटिंग्स", "other_settings": "अन्य सेटिंग्स", + "require_pin_after": "इसके बाद पिन आवश्यक है", + "always": "हमेशा", + "minutes_to_pin_code": "${minute} मिनट", + "disable_exchange": "एक्सचेंज अक्षम करें", "advanced_privacy_settings": "उन्नत गोपनीयता सेटिंग्स", "settings_can_be_changed_later": "इन सेटिंग्स को बाद में ऐप सेटिंग में बदला जा सकता है", "add_custom_node": "नया कस्टम नोड जोड़ें", - "disable_exchange": "एक्सचेंज अक्षम करें", + "disable_fiat": "िएट को अक्षम करें", "fiat_api": "फिएट पैसे API", "disabled": "अक्षम", "enabled": "सक्रिय", - "tor_only": "Tor केवल" + "tor_only": "Tor केवल", + "unmatched_currencies": "आपके वर्तमान वॉलेट की मुद्रा स्कैन किए गए क्यूआर से मेल नहीं खाती" } diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 048c9fb85..b2696a3df 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -663,6 +663,9 @@ "privacy": "Privatnost", "display_settings": "Postavke zaslona", "other_settings": "Ostale postavke", + "require_pin_after": "Zahtijevaj PIN nakon", + "always": "Uvijek", + "minutes_to_pin_code": "${minute} minuta", "disable_exchange": "Onemogući exchange", "advanced_privacy_settings": "Napredne postavke privatnosti", "settings_can_be_changed_later": "Te se postavke mogu promijeniti kasnije u postavkama aplikacije", @@ -671,5 +674,6 @@ "fiat_api": "Fiat API", "disabled": "Onemogućeno", "enabled": "Omogućeno", - "tor_only": "Samo Tor" + "tor_only": "Samo Tor", + "unmatched_currencies": "Valuta vašeg trenutnog novčanika ne odgovara onoj na skeniranom QR-u" } diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 0d8aeca13..cb68d9ea6 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -663,6 +663,9 @@ "privacy": "Privacy", "display_settings": "Impostazioni di visualizzazione", "other_settings": "Altre impostazioni", + "require_pin_after": "Richiedi PIN dopo", + "always": "sempre", + "minutes_to_pin_code": "${minute} minuti", "disable_exchange": "Disabilita scambio", "advanced_privacy_settings": "Impostazioni avanzate sulla privacy", "settings_can_be_changed_later": "Queste impostazioni possono essere modificate in seguito nelle impostazioni dell'app", @@ -671,5 +674,6 @@ "fiat_api": "Fiat API", "disabled": "Disabilitato", "enabled": "Abilitato", - "tor_only": "Solo Tor" + "tor_only": "Solo Tor", + "unmatched_currencies": "La valuta del tuo portafoglio attuale non corrisponde a quella del QR scansionato" } diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index ad16cd80b..f0d595c6e 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -663,6 +663,9 @@ "privacy": "プライバシー", "display_settings": "表示設定", "other_settings": "その他の設定", + "require_pin_after": "後に PIN が必要", + "always": "いつも", + "minutes_to_pin_code": "${minute} 分", "disable_exchange": "交換を無効にする", "advanced_privacy_settings": "高度なプライバシー設定", "settings_can_be_changed_later": "これらの設定は、後でアプリの設定で変更できます", @@ -671,5 +674,6 @@ "fiat_api": "不換紙幣 API", "disabled": "無効", "enabled": "有効", - "tor_only": "Torのみ" + "tor_only": "Torのみ", + "unmatched_currencies": "現在のウォレットの通貨がスキャンされたQRの通貨と一致しません" } diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index c0daae349..f4d289287 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -663,6 +663,9 @@ "privacy": "프라이버시", "display_settings": "디스플레이 설정", "other_settings": "기타 설정", + "require_pin_after": "다음 이후에 PIN 필요", + "always": "언제나", + "minutes_to_pin_code": "${minute}분", "disable_exchange": "교환 비활성화", "advanced_privacy_settings": "고급 개인 정보 설정", "settings_can_be_changed_later": "이 설정은 나중에 앱 설정에서 변경할 수 있습니다.", @@ -671,5 +674,6 @@ "fiat_api": "명목 화폐 API", "disabled": "장애가 있는", "enabled": "사용", - "tor_only": "Tor 뿐" + "tor_only": "Tor 뿐", + "unmatched_currencies": "현재 지갑의 통화가 스캔한 QR의 통화와 일치하지 않습니다." } diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 6db822576..06c075092 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -663,6 +663,9 @@ "privacy": "Privacy", "display_settings": "Weergave-instellingen", "other_settings": "Andere instellingen", + "require_pin_after": "Pincode vereist na", + "always": "altijd", + "minutes_to_pin_code": "${minute} minuten", "disable_exchange": "Uitwisseling uitschakelen", "advanced_privacy_settings": "Geavanceerde privacy-instellingen", "settings_can_be_changed_later": "Deze instellingen kunnen later worden gewijzigd in de app-instellingen", @@ -671,5 +674,6 @@ "fiat_api": "Fiat API", "disabled": "Gehandicapt", "enabled": "Ingeschakeld", - "tor_only": "Alleen Tor" + "tor_only": "Alleen Tor", + "unmatched_currencies": "De valuta van uw huidige portemonnee komt niet overeen met die van de gescande QR" } diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index bb27cb0d9..64e5f508b 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -663,6 +663,9 @@ "privacy": "Prywatność", "display_settings": "Ustawienia wyświetlania", "other_settings": "Inne ustawienia", + "require_pin_after": "Wymagaj kodu PIN po", + "always": "zawsze", + "minutes_to_pin_code": "${minute} minut", "disable_exchange": "Wyłącz wymianę", "advanced_privacy_settings": "Zaawansowane ustawienia prywatności", "settings_can_be_changed_later": "Te ustawienia można później zmienić w ustawieniach aplikacji", @@ -671,5 +674,6 @@ "fiat_api": "API Fiata", "disabled": "Wyłączone", "enabled": "Włączony", - "tor_only": "Tylko Tor" + "tor_only": "Tylko Tor", + "unmatched_currencies": "Waluta Twojego obecnego portfela nie odpowiada walucie zeskanowanego kodu QR" } diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index cb2f3b694..b23aa20e8 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -662,6 +662,9 @@ "privacy": "Privacidade", "display_settings": "Configurações de exibição", "other_settings": "Outras configurações", + "require_pin_after": "Exigir PIN após", + "always": "sempre", + "minutes_to_pin_code": "${minute} minutos", "disable_exchange": "Desativar troca", "advanced_privacy_settings": "Configurações de privacidade avançadas", "settings_can_be_changed_later": "Essas configurações podem ser alteradas posteriormente nas configurações do aplicativo", @@ -670,5 +673,6 @@ "fiat_api": "API da Fiat", "disabled": "Desabilitado", "enabled": "Habilitado", - "tor_only": "Tor apenas" + "tor_only": "Tor apenas", + "unmatched_currencies": "A moeda da sua carteira atual não corresponde à do QR digitalizado" } diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 7496d6fbb..40f6ccdf0 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -369,7 +369,7 @@ "trade_for_not_created" : "Сделка для ${title} не создана.", "trade_not_created" : "Сделка не создана", "trade_id_not_found" : "Сделка ${tradeId} ${title} не найдена.", - "trade_not_found" : "Trade not found.", + "trade_not_found" : "Торговля не найдена.", "trade_state_pending" : "Ожидание", @@ -663,6 +663,9 @@ "privacy": "Конфиденциальность", "display_settings": "Настройки отображения", "other_settings": "Другие настройки", + "require_pin_after": "Требовать ПИН после", + "always": "всегда", + "minutes_to_pin_code": "${minute} минут", "disable_exchange": "Отключить обмен", "advanced_privacy_settings": "Расширенные настройки конфиденциальности", "settings_can_be_changed_later": "Эти настройки можно изменить позже в настройках приложения.", @@ -671,5 +674,6 @@ "fiat_api": "Фиат API", "disabled": "Отключено", "enabled": "Включено", - "tor_only": "Только Tor" + "tor_only": "Только Tor", + "unmatched_currencies": "Валюта вашего текущего кошелька не соответствует валюте отсканированного QR-кода." } diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index e3b12a738..9108c700d 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -662,6 +662,9 @@ "privacy": "Конфіденційність", "display_settings": "Налаштування дисплея", "other_settings": "Інші налаштування", + "require_pin_after": "Вимагати PIN після", + "always": "Завжди", + "minutes_to_pin_code": "${minute} хвилин", "disable_exchange": "Вимкнути exchange", "advanced_privacy_settings": "Розширені налаштування конфіденційності", "settings_can_be_changed_later": "Ці параметри можна змінити пізніше в налаштуваннях програми", @@ -670,5 +673,6 @@ "fiat_api": "Фіат API", "disabled": "Вимкнено", "enabled": "Увімкнено", - "tor_only": "Тільки Tor" + "tor_only": "Тільки Tor", + "unmatched_currencies": "Валюта вашого гаманця не збігається з валютою сканованого QR-коду" } diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index e47d88186..4683bb068 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -661,6 +661,9 @@ "privacy":"隐私", "display_settings": "显示设置", "other_settings": "其他设置", + "require_pin_after": "之后需要 PIN", + "always": "总是", + "minutes_to_pin_code": "${minute} 分钟", "disable_exchange": "禁用交换", "advanced_privacy_settings": "高级隐私设置", "settings_can_be_changed_later": "稍后可以在应用设置中更改这些设置", @@ -669,5 +672,6 @@ "fiat_api": "法币API", "disabled": "禁用", "enabled": "启用", - "tor_only": "仅限 Tor" + "tor_only": "仅限 Tor", + "unmatched_currencies": "您当前钱包的货币与扫描的 QR 的货币不匹配" } diff --git a/scripts/android/app_env.sh b/scripts/android/app_env.sh index d361c8dfa..5b704eea1 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.2.1" -MONERO_COM_BUILD_NUMBER=32 +MONERO_COM_VERSION="1.2.2" +MONERO_COM_BUILD_NUMBER=33 MONERO_COM_BUNDLE_ID="com.monero.app" MONERO_COM_PACKAGE="com.monero.app" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="4.5.1" -CAKEWALLET_BUILD_NUMBER=136 +CAKEWALLET_VERSION="4.5.3" +CAKEWALLET_BUILD_NUMBER=138 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 8bb945680..fbf8233e5 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.2.1" -MONERO_COM_BUILD_NUMBER=29 +MONERO_COM_VERSION="1.2.2" +MONERO_COM_BUILD_NUMBER=30 MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="4.5.1" -CAKEWALLET_BUILD_NUMBER=133 +CAKEWALLET_VERSION="4.5.3" +CAKEWALLET_BUILD_NUMBER=135 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" HAVEN_NAME="Haven"