Merge branch 'main' into CW-122-Rework-filter-on-the-transactions-list-screen

This commit is contained in:
Serhii 2022-12-16 15:14:13 +02:00
commit 00c008ed8b
52 changed files with 738 additions and 338 deletions

View file

@ -16,7 +16,7 @@
android:requestLegacyExternalStorage="true"> android:requestLegacyExternalStorage="true">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:launchMode="singleTop" android:launchMode="singleInstance"
android:theme="@style/LaunchTheme" android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
@ -39,6 +39,15 @@
android:scheme="cakewallet" android:scheme="cakewallet"
android:host="y.at" /> android:host="y.at" />
</intent-filter> </intent-filter>
<!-- currencies qr code scheme -->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="bitcoin" />
<data android:scheme="monero" />
<data android:scheme="litecoin" />
</intent-filter>
</activity> </activity>
<meta-data <meta-data
android:name="flutterEmbedding" android:name="flutterEmbedding"

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 548 B

View file

@ -303,7 +303,7 @@ class ElectrumClient {
Future<List<int>> feeRates() async { Future<List<int>> feeRates() async {
try { try {
final topDoubleString = await estimatefee(p: 1); 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 bottomDoubleString = await estimatefee(p: 100);
final top = final top =
(stringDoubleToBitcoinAmount(topDoubleString.toString()) / 1000) (stringDoubleToBitcoinAmount(topDoubleString.toString()) / 1000)

View file

@ -21,22 +21,6 @@ class MoneroTransactionPriority extends TransactionPriority {
static const fastest = MoneroTransactionPriority(title: 'Fastest', raw: 4); static const fastest = MoneroTransactionPriority(title: 'Fastest', raw: 4);
static const standard = slow; static const standard = slow;
static List<MoneroTransactionPriority> 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}) { static MoneroTransactionPriority deserialize({required int raw}) {
switch (raw) { switch (raw) {
case 0: case 0:

View file

@ -53,7 +53,7 @@ class Node extends HiveObject with Keyable {
@HiveField(4) @HiveField(4)
bool? useSSL; bool? useSSL;
@HiveField(5) @HiveField(5, defaultValue: false)
bool trusted; bool trusted;
bool get isSSL => useSSL ?? false; bool get isSSL => useSSL ?? false;

View file

@ -21,18 +21,48 @@
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleURLTypes</key> <key>CFBundleURLTypes</key>
<array> <array>
<dict> <dict>
<key>CFBundleTypeRole</key> <key>CFBundleTypeRole</key>
<string>Editor</string> <string>Editor</string>
<key>CFBundleURLName</key> <key>CFBundleURLName</key>
<string>y.at</string> <string>y.at</string>
<key>CFBundleURLSchemes</key> <key>CFBundleURLSchemes</key>
<array> <array>
<string>cakewallet</string> <string>cakewallet</string>
</array> </array>
</dict> </dict>
</array> <dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>bitcoin</string>
<key>CFBundleURLSchemes</key>
<array>
<string>bitcoin</string>
</array>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>monero</string>
<key>CFBundleURLSchemes</key>
<array>
<string>monero</string>
</array>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>litecoin</string>
<key>CFBundleURLSchemes</key>
<array>
<string>litecoin</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string> <string>$(CURRENT_PROJECT_VERSION)</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>

View file

@ -4,12 +4,19 @@ import 'package:shared_preferences/shared_preferences.dart';
import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/entities/secret_store_key.dart'; import 'package:cake_wallet/entities/secret_store_key.dart';
import 'package:cake_wallet/entities/encrypt.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 { 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 FlutterSecureStorage secureStorage;
final SharedPreferences sharedPreferences; final SharedPreferences sharedPreferences;
final SettingsStore settingsStore;
Future<void> setPassword(String password) async { Future<void> setPassword(String password) async {
final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword); final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword);
@ -19,8 +26,7 @@ class AuthService with Store {
Future<bool> canAuthenticate() async { Future<bool> canAuthenticate() async {
final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword); final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword);
final walletName = final walletName = sharedPreferences.getString(PreferencesKey.currentWalletName) ?? '';
sharedPreferences.getString(PreferencesKey.currentWalletName) ?? '';
var password = ''; var password = '';
try { try {
@ -39,4 +45,25 @@ class AuthService with Store {
return decodedPin == pin; 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;
}
} }

View file

@ -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_gift_card_detail_page.dart';
import 'package:cake_wallet/src/screens/ionia/cards/ionia_more_options_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/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_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_buy_card_view_model.dart';
import 'package:cake_wallet/view_model/ionia/ionia_custom_tip_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>(() => AuthService( getIt.registerFactory<AuthService>(() => AuthService(
secureStorage: getIt.get<FlutterSecureStorage>(), secureStorage: getIt.get<FlutterSecureStorage>(),
sharedPreferences: getIt.get<SharedPreferences>())); sharedPreferences: getIt.get<SharedPreferences>(),
settingsStore: getIt.get<SettingsStore>(),
),
);
getIt.registerFactory<AuthViewModel>(() => AuthViewModel( getIt.registerFactory<AuthViewModel>(() => AuthViewModel(
getIt.get<AuthService>(), getIt.get<AuthService>(),
@ -384,8 +388,11 @@ Future setup(
getIt.get<BalanceViewModel>(), getIt.get<BalanceViewModel>(),
_transactionDescriptionBox)); _transactionDescriptionBox));
getIt.registerFactory( getIt.registerFactoryParam<SendPage, PaymentRequest?, void>(
() => SendPage(sendViewModel: getIt.get<SendViewModel>())); (PaymentRequest? initialPaymentRequest, _) => SendPage(
sendViewModel: getIt.get<SendViewModel>(),
initialPaymentRequest: initialPaymentRequest,
));
getIt.registerFactory(() => SendTemplatePage( getIt.registerFactory(() => SendTemplatePage(
sendTemplateViewModel: getIt.get<SendTemplateViewModel>())); sendTemplateViewModel: getIt.get<SendTemplateViewModel>()));
@ -393,7 +400,10 @@ Future setup(
getIt.registerFactory(() => WalletListViewModel( getIt.registerFactory(() => WalletListViewModel(
_walletInfoSource, _walletInfoSource,
getIt.get<AppStore>(), getIt.get<AppStore>(),
getIt.get<WalletLoadingService>())); getIt.get<WalletLoadingService>(),
getIt.get<AuthService>(),
),
);
getIt.registerFactory(() => getIt.registerFactory(() =>
WalletListPage(walletListViewModel: getIt.get<WalletListViewModel>())); WalletListPage(walletListViewModel: getIt.get<WalletListViewModel>()));
@ -453,7 +463,7 @@ Future setup(
}); });
getIt.registerFactory(() { getIt.registerFactory(() {
return SecuritySettingsViewModel(getIt.get<SettingsStore>()); return SecuritySettingsViewModel(getIt.get<SettingsStore>(), getIt.get<AuthService>());
}); });
getIt getIt
@ -785,7 +795,8 @@ Future setup(
return IoniaMoreOptionsPage(giftCard); return IoniaMoreOptionsPage(giftCard);
}); });
getIt.registerFactoryParam<IoniaCustomRedeemViewModel, IoniaGiftCard, void>((IoniaGiftCard giftCard, _) => IoniaCustomRedeemViewModel(giftCard)); getIt.registerFactoryParam<IoniaCustomRedeemViewModel, IoniaGiftCard, void>((IoniaGiftCard giftCard, _)
=> IoniaCustomRedeemViewModel(giftCard: giftCard, ioniaService: getIt.get<IoniaService>()));
getIt.registerFactoryParam<IoniaCustomRedeemPage, List, void>((List args, _){ getIt.registerFactoryParam<IoniaCustomRedeemPage, List, void>((List args, _){
final giftCard = args.first as IoniaGiftCard; final giftCard = args.first as IoniaGiftCard;
@ -823,4 +834,4 @@ Future setup(
AdvancedPrivacySettingsViewModel(type, getIt.get<SettingsStore>())); AdvancedPrivacySettingsViewModel(type, getIt.get<SettingsStore>()));
_isSetupFinished = true; _isSetupFinished = true;
} }

View file

@ -139,6 +139,10 @@ Future defaultSettingsMigration(
await addOnionNode(nodes); await addOnionNode(nodes);
break; break;
case 19:
await validateBitcoinSavedTransactionPriority(sharedPreferences);
break;
default: default:
break; break;
} }
@ -154,6 +158,18 @@ Future defaultSettingsMigration(
PreferencesKey.currentDefaultSettingsMigrationVersion, version); PreferencesKey.currentDefaultSettingsMigrationVersion, version);
} }
Future<void> 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<void> addOnionNode(Box<Node> nodes) async { Future<void> addOnionNode(Box<Node> nodes) async {
final onionNodeUri = "cakexmrl7bonq7ovjka5kuwuyd3f7qnkz6z6s6dmsy3uckwra7bvggyd.onion:18081"; final onionNodeUri = "cakexmrl7bonq7ovjka5kuwuyd3f7qnkz6z6s6dmsy3uckwra7bvggyd.onion:18081";

View file

@ -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;
}
}

View file

@ -27,6 +27,9 @@ class PreferencesKey {
static const shouldShowReceiveWarning = 'should_show_receive_warning'; static const shouldShowReceiveWarning = 'should_show_receive_warning';
static const shouldShowYatPopup = 'should_show_yat_popup'; static const shouldShowYatPopup = 'should_show_yat_popup';
static const moneroWalletPasswordUpdateV1Base = 'monero_wallet_update_v1'; 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) static String moneroWalletUpdateV1Key(String name)
=> '${PreferencesKey.moneroWalletPasswordUpdateV1Base}_${name}'; => '${PreferencesKey.moneroWalletPasswordUpdateV1Base}_${name}';

View file

@ -37,7 +37,7 @@ class IoniaGiftCard {
purchaseAmount: element['PurchaseAmount'] as double, purchaseAmount: element['PurchaseAmount'] as double,
actualAmount: element['ActualAmount'] as double, actualAmount: element['ActualAmount'] as double,
totalTransactionAmount: element['TotalTransactionAmount'] as double, totalTransactionAmount: element['TotalTransactionAmount'] as double,
totalDashTransactionAmount: element['TotalDashTransactionAmount'] as double, totalDashTransactionAmount: (element['TotalDashTransactionAmount'] as double?) ?? 0.0,
remainingAmount: element['RemainingAmount'] as double, remainingAmount: element['RemainingAmount'] as double,
isActive: element['IsActive'] as bool, isActive: element['IsActive'] as bool,
isEmpty: element['IsEmpty'] as bool, isEmpty: element['IsEmpty'] as bool,

View file

@ -148,8 +148,8 @@ class IoniaService {
// Redeem // Redeem
Future<void> redeem(IoniaGiftCard giftCard) async { Future<void> redeem({required int giftCardId, required double amount}) async {
await chargeGiftCard(giftCardId: giftCard.id, amount: giftCard.remainingAmount); await chargeGiftCard(giftCardId: giftCardId, amount: amount);
} }
// Get Gift Card // Get Gift Card

View file

@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:cake_wallet/bitcoin/bitcoin.dart'; 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/entities/language_service.dart';
import 'package:cake_wallet/buy/order.dart'; import 'package:cake_wallet/buy/order.dart';
import 'package:cake_wallet/ionia/ionia_category.dart'; import 'package:cake_wallet/ionia/ionia_category.dart';
@ -128,7 +129,7 @@ Future<void> main() async {
exchangeTemplates: exchangeTemplates, exchangeTemplates: exchangeTemplates,
transactionDescriptions: transactionDescriptions, transactionDescriptions: transactionDescriptions,
secureStorage: secureStorage, secureStorage: secureStorage,
initialMigrationVersion: 18); initialMigrationVersion: 19);
runApp(App()); runApp(App());
} catch (e, stacktrace) { } catch (e, stacktrace) {
runApp(MaterialApp( runApp(MaterialApp(
@ -204,12 +205,6 @@ class AppState extends State<App> with SingleTickerProviderStateMixin {
//_handleInitialUri(); //_handleInitialUri();
} }
@override
void dispose() {
stream?.cancel();
super.dispose();
}
Future<void> _handleInitialUri() async { Future<void> _handleInitialUri() async {
try { try {
final uri = await getInitialUri(); final uri = await getInitialUri();
@ -257,6 +252,7 @@ class AppState extends State<App> with SingleTickerProviderStateMixin {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Observer(builder: (BuildContext context) { return Observer(builder: (BuildContext context) {
final appStore = getIt.get<AppStore>(); final appStore = getIt.get<AppStore>();
final authService = getIt.get<AuthService>();
final settingsStore = appStore.settingsStore; final settingsStore = appStore.settingsStore;
final statusBarColor = Colors.transparent; final statusBarColor = Colors.transparent;
final authenticationStore = getIt.get<AuthenticationStore>(); final authenticationStore = getIt.get<AuthenticationStore>();
@ -281,6 +277,7 @@ class AppState extends State<App> with SingleTickerProviderStateMixin {
appStore: appStore, appStore: appStore,
authenticationStore: authenticationStore, authenticationStore: authenticationStore,
navigatorKey: navigatorKey, navigatorKey: navigatorKey,
authService: authService,
child: MaterialApp( child: MaterialApp(
navigatorKey: navigatorKey, navigatorKey: navigatorKey,
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,

View file

@ -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/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_details_page.dart';
import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_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/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/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/advanced_privacy_settings_view_model.dart';
@ -216,8 +217,12 @@ Route<dynamic> createRoute(RouteSettings settings) {
builder: (_) => getIt.get<DashboardPage>()); builder: (_) => getIt.get<DashboardPage>());
case Routes.send: case Routes.send:
final initialPaymentRequest = settings.arguments as PaymentRequest?;
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(
fullscreenDialog: true, builder: (_) => getIt.get<SendPage>()); fullscreenDialog: true, builder: (_) => getIt.get<SendPage>(
param1: initialPaymentRequest,
));
case Routes.sendTemplate: case Routes.sendTemplate:
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(

View file

@ -21,6 +21,12 @@ class WalletMenu {
height: 16, width: 16), height: 16, width: 16),
handler: () => Navigator.of(context).pushNamed(Routes.walletList), 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( WalletMenuItem(
title: S.current.security_and_backup, title: S.current.security_and_backup,
image: image:
@ -29,18 +35,12 @@ class WalletMenu {
Navigator.of(context).pushNamed(Routes.securityBackupPage); Navigator.of(context).pushNamed(Routes.securityBackupPage);
}), }),
WalletMenuItem( WalletMenuItem(
title: S.current.privacy, title: S.current.privacy_settings,
image: image:
Image.asset('assets/images/eye_menu.png', height: 16, width: 16), Image.asset('assets/images/privacy_menu.png', height: 16, width: 16),
handler: () { handler: () {
Navigator.of(context).pushNamed(Routes.privacyPage); 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( WalletMenuItem(
title: S.current.display_settings, title: S.current.display_settings,
image: Image.asset('assets/images/eye_menu.png', image: Image.asset('assets/images/eye_menu.png',

View file

@ -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/base_page.dart';
import 'package:cake_wallet/src/screens/ionia/widgets/card_item.dart'; import 'package:cake_wallet/src/screens/ionia/widgets/card_item.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
@ -18,12 +19,11 @@ class IoniaCustomRedeemPage extends BasePage {
) : _amountFieldFocus = FocusNode(), ) : _amountFieldFocus = FocusNode(),
_amountController = TextEditingController() { _amountController = TextEditingController() {
_amountController.addListener(() { _amountController.addListener(() {
ioniaCustomRedeemViewModel.updateAmount(_amountController.text); ioniaCustomRedeemViewModel.updateAmount(_amountController.text);
}); });
} }
final IoniaCustomRedeemViewModel ioniaCustomRedeemViewModel; final IoniaCustomRedeemViewModel ioniaCustomRedeemViewModel;
@override @override
String get title => S.current.custom_redeem_amount; String get title => S.current.custom_redeem_amount;
@ -50,7 +50,7 @@ class IoniaCustomRedeemPage extends BasePage {
disableScroll: true, disableScroll: true,
config: KeyboardActionsConfig( config: KeyboardActionsConfig(
keyboardActionsPlatform: KeyboardActionsPlatform.IOS, keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
keyboardBarColor: Theme.of(context).accentTextTheme!.bodyText1!.backgroundColor!, keyboardBarColor: Theme.of(context).accentTextTheme.bodyText1!.backgroundColor!,
nextFocus: false, nextFocus: false,
actions: [ actions: [
KeyboardActionsItem( KeyboardActionsItem(
@ -67,10 +67,11 @@ class IoniaCustomRedeemPage extends BasePage {
Container( Container(
padding: EdgeInsets.symmetric(horizontal: 25), padding: EdgeInsets.symmetric(horizontal: 25),
decoration: BoxDecoration( 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: [ gradient: LinearGradient(colors: [
Theme.of(context).primaryTextTheme!.subtitle1!.color!, Theme.of(context).primaryTextTheme.subtitle1!.color!,
Theme.of(context).primaryTextTheme!.subtitle1!.decorationColor!, Theme.of(context).primaryTextTheme.subtitle1!.decorationColor!,
], begin: Alignment.topLeft, end: Alignment.bottomRight), ], begin: Alignment.topLeft, end: Alignment.bottomRight),
), ),
child: Column( child: Column(
@ -85,11 +86,11 @@ class IoniaCustomRedeemPage extends BasePage {
inputFormatters: [FilteringTextInputFormatter.deny(RegExp('[\-|\ ]'))], inputFormatters: [FilteringTextInputFormatter.deny(RegExp('[\-|\ ]'))],
hintText: '1000', hintText: '1000',
placeholderTextStyle: TextStyle( placeholderTextStyle: TextStyle(
color: Theme.of(context).primaryTextTheme!.headline5!.color!, color: Theme.of(context).primaryTextTheme.headline5!.color!,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
fontSize: 36, fontSize: 36,
), ),
borderColor: Theme.of(context).primaryTextTheme!.headline5!.color!, borderColor: Theme.of(context).primaryTextTheme.headline5!.color!,
textColor: Colors.white, textColor: Colors.white,
textStyle: TextStyle( textStyle: TextStyle(
color: Colors.white, color: Colors.white,
@ -114,14 +115,17 @@ class IoniaCustomRedeemPage extends BasePage {
), ),
), ),
SizedBox(height: 8), SizedBox(height: 8),
Observer(builder: (_)=> Observer(
!ioniaCustomRedeemViewModel.disableRedeem ? builder: (_) => !ioniaCustomRedeemViewModel.disableRedeem
Center( ? Center(
child: Text('\$${giftCard.remainingAmount} - \$${ioniaCustomRedeemViewModel.amount} = \$${ioniaCustomRedeemViewModel.formattedRemaining} ${S.of(context).remaining}', child: Text(
style: TextStyle( '\$${giftCard.remainingAmount} - \$${ioniaCustomRedeemViewModel.amount} = \$${ioniaCustomRedeemViewModel.formattedRemaining} ${S.of(context).remaining}',
color: Theme.of(context).primaryTextTheme!.headline5!.color!, style: TextStyle(
),), color: Theme.of(context).primaryTextTheme.headline5!.color!,
) : SizedBox.shrink(), ),
),
)
: SizedBox.shrink(),
), ),
SizedBox(height: 24), SizedBox(height: 24),
], ],
@ -131,30 +135,37 @@ class IoniaCustomRedeemPage extends BasePage {
padding: const EdgeInsets.all(24.0), padding: const EdgeInsets.all(24.0),
child: CardItem( child: CardItem(
title: giftCard.legalName, 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, discount: giftCard.remainingAmount,
isAmount: true, isAmount: true,
discountBackground: AssetImage('assets/images/red_badge_discount.png'), 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, subtitleColor: Theme.of(context).hintColor,
subTitle: S.of(context).online, subTitle: S.of(context).online,
logoUrl: giftCard.logoUrl, logoUrl: giftCard.logoUrl,
), ),
), ),
], ],
), ),
bottomSection: Column( bottomSection: Column(
children: [ children: [
Padding( Observer(
padding: EdgeInsets.only(bottom: 12), builder: (_) => Padding(
child: PrimaryButton( padding: EdgeInsets.only(bottom: 12),
onPressed: () { child: LoadingPrimaryButton(
Navigator.of(context).pop(_amountController.text); isLoading: ioniaCustomRedeemViewModel.redeemState is IsExecutingState,
}, isDisabled: ioniaCustomRedeemViewModel.disableRedeem,
isDisabled: ioniaCustomRedeemViewModel.disableRedeem, text: S.of(context).add_custom_redemption,
text: S.of(context).add_custom_redemption, color: Theme.of(context).accentTextTheme.bodyText1!.color!,
color: Theme.of(context).accentTextTheme!.bodyText1!.color!, textColor: Colors.white,
textColor: Colors.white, onPressed: () => ioniaCustomRedeemViewModel.addCustomRedeem().then((value) {
Navigator.of(context).pop(ioniaCustomRedeemViewModel.remaining.toString());
}),
),
), ),
), ),
SizedBox(height: 30), SizedBox(height: 30),

View file

@ -32,7 +32,7 @@ class IoniaGiftCardDetailPage extends BasePage {
final _backButton = Icon( final _backButton = Icon(
Icons.arrow_back_ios, Icons.arrow_back_ios,
color: Theme.of(context).primaryTextTheme!.headline6!.color!, color: Theme.of(context).primaryTextTheme.headline6!.color!,
size: 16, size: 16,
); );
return Padding( return Padding(
@ -43,7 +43,7 @@ class IoniaGiftCardDetailPage extends BasePage {
child: ButtonTheme( child: ButtonTheme(
minWidth: double.minPositive, minWidth: double.minPositive,
child: TextButton( child: TextButton(
// FIX-ME: Style // FIX-ME: Style
//highlightColor: Colors.transparent, //highlightColor: Colors.transparent,
//splashColor: Colors.transparent, //splashColor: Colors.transparent,
//padding: EdgeInsets.all(0), //padding: EdgeInsets.all(0),
@ -61,7 +61,8 @@ class IoniaGiftCardDetailPage extends BasePage {
Widget middle(BuildContext context) { Widget middle(BuildContext context) {
return Text( return Text(
viewModel.giftCard.legalName, 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, title: S.of(context).gift_card_number,
subTitle: viewModel.giftCard.cardNumber, subTitle: viewModel.giftCard.cardNumber,
), ),
if (viewModel.giftCard.cardPin?.isNotEmpty ?? false) if (viewModel.giftCard.cardPin.isNotEmpty) ...[
...[Divider(height: 30), Divider(height: 30),
buildIoniaTile( buildIoniaTile(
context, context,
title: S.of(context).pin_number, title: S.of(context).pin_number,
subTitle: viewModel.giftCard.cardPin, subTitle: viewModel.giftCard.cardPin,
)], )
],
Divider(height: 30), Divider(height: 30),
Observer(builder: (_) => Observer(
buildIoniaTile( builder: (_) => buildIoniaTile(
context, context,
title: S.of(context).amount, title: S.of(context).amount,
subTitle: viewModel.remainingAmount.toStringAsFixed(2) ?? '0.00', subTitle: viewModel.remainingAmount.toStringAsFixed(2),
)), )),
Divider(height: 50), Divider(height: 50),
TextIconButton( TextIconButton(
label: S.of(context).how_to_use_card, label: S.of(context).how_to_use_card,
@ -130,29 +132,28 @@ class IoniaGiftCardDetailPage extends BasePage {
if (!viewModel.giftCard.isEmpty) { if (!viewModel.giftCard.isEmpty) {
return Column( return Column(
children: [ children: [
//PrimaryButton( PrimaryButton(
// onPressed: () async { onPressed: () async {
// final amount = await Navigator.of(context) await Navigator.of(context).pushNamed(
// .pushNamed(Routes.ioniaMoreOptionsPage, arguments: [viewModel.giftCard]) as String; Routes.ioniaMoreOptionsPage,
// if (amount != null) { arguments: [viewModel.giftCard]) as String?;
// viewModel.updateRemaining(double.parse(amount)); viewModel.refeshCard();
// } },
// }, text: S.of(context).more_options,
// text: S.of(context).more_options, color: Theme.of(context).accentTextTheme.caption!.color!,
// color: Theme.of(context).accentTextTheme!.caption!.color!, textColor: Theme.of(context).primaryTextTheme.headline6!.color!,
// textColor: Theme.of(context).primaryTextTheme!.headline6!.color!, ),
//), SizedBox(height: 12),
//SizedBox(height: 12),
LoadingPrimaryButton( LoadingPrimaryButton(
isLoading: viewModel.redeemState is IsExecutingState, isLoading: viewModel.redeemState is IsExecutingState,
onPressed: () => viewModel.redeem().then( onPressed: () => viewModel.redeem().then(
(_) { (_) {
Navigator.of(context) Navigator.of(context).pushNamedAndRemoveUntil(
.pushNamedAndRemoveUntil(Routes.ioniaManageCardsPage, (route) => route.isFirst); Routes.ioniaManageCardsPage, (route) => route.isFirst);
}, },
), ),
text: S.of(context).mark_as_redeemed, text: S.of(context).mark_as_redeemed,
color: Theme.of(context).accentTextTheme!.bodyText1!.color!, color: Theme.of(context).accentTextTheme.bodyText1!.color!,
textColor: Colors.white, textColor: Colors.white,
), ),
], ],
@ -168,12 +169,11 @@ class IoniaGiftCardDetailPage extends BasePage {
Widget buildIoniaTile(BuildContext context, {required String title, required String subTitle}) { Widget buildIoniaTile(BuildContext context, {required String title, required String subTitle}) {
return IoniaTile( return IoniaTile(
title: title, title: title,
subTitle: subTitle, subTitle: subTitle,
onTap: () { onTap: () {
Clipboard.setData(ClipboardData(text: subTitle)); Clipboard.setData(ClipboardData(text: subTitle));
showBar<void>(context, showBar<void>(context, S.of(context).transaction_details_copied(title));
S.of(context).transaction_details_copied(title));
}); });
} }
@ -184,10 +184,10 @@ class IoniaGiftCardDetailPage extends BasePage {
showPopUp<void>( showPopUp<void>(
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
return IoniaAlertModal( return IoniaAlertModal(
title: S.of(context).how_to_use_card, title: S.of(context).how_to_use_card,
content: Column( content: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: viewModel.giftCard.instructions children: viewModel.giftCard.instructions
.map((instruction) { .map((instruction) {
return [ return [
@ -196,13 +196,13 @@ class IoniaGiftCardDetailPage extends BasePage {
child: Text( child: Text(
instruction.header, instruction.header,
style: textLargeSemiBold( style: textLargeSemiBold(
color: Theme.of(context).textTheme!.headline3!.color!, color: Theme.of(context).textTheme.headline3!.color!,
), ),
)), )),
Text( Text(
instruction.body, instruction.body,
style: textMedium( 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) .expand((e) => e)
.toList()), .toList()),
actionTitle: S.of(context).send_got_it, actionTitle: S.of(context).send_got_it,
); );
}); });
} }
} }

View file

@ -5,7 +5,6 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/typography.dart'; import 'package:cake_wallet/typography.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class IoniaMoreOptionsPage extends BasePage { class IoniaMoreOptionsPage extends BasePage {
IoniaMoreOptionsPage(this.giftCard); IoniaMoreOptionsPage(this.giftCard);
@ -16,7 +15,7 @@ class IoniaMoreOptionsPage extends BasePage {
return Text( return Text(
S.current.more_options, S.current.more_options,
style: textMediumSemiBold( 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), padding: const EdgeInsets.all(16.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
SizedBox(height: 10,), SizedBox(
Center(child: Text(S.of(context).choose_from_available_options, style: textMedium( height: 10,
color: Theme.of(context).primaryTextTheme!.headline6!.color!, ),
),)), Center(
SizedBox(height: 40,), child: Text(
InkWell( S.of(context).choose_from_available_options,
onTap: () async { style: textMedium(
final amount = await Navigator.of(context).pushNamed(Routes.ioniaCustomRedeemPage, arguments: [giftCard]) as String; color: Theme.of(context).primaryTextTheme.headline6!.color!,
if(amount.isNotEmpty){ ),
Navigator.pop(context, amount); ),
} ),
}, SizedBox(height: 40),
child: _GradiantContainer( InkWell(
content: Padding( onTap: () async {
padding: const EdgeInsets.only(top: 24, left: 20, right: 24, bottom: 50), final amount = await Navigator.of(context)
child: Text( .pushNamed(Routes.ioniaCustomRedeemPage, arguments: [giftCard]) as String?;
S.of(context).custom_redeem_amount, if (amount != null && amount.isNotEmpty) {
style: textXLargeSemiBold(), 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 { class _GradiantContainer extends StatelessWidget {
const _GradiantContainer({ const _GradiantContainer({Key? key, required this.content}) : super(key: key);
Key? key,
required this.content
}) : super(key: key);
final Widget content; final Widget content;

View file

@ -1,23 +1,28 @@
import 'dart:async'; 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:flutter/material.dart';
import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/auth/auth_page.dart'; import 'package:cake_wallet/src/screens/auth/auth_page.dart';
import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/store/authentication_store.dart'; import 'package:cake_wallet/store/authentication_store.dart';
import 'package:cake_wallet/entities/qr_scanner.dart'; import 'package:cake_wallet/entities/qr_scanner.dart';
import 'package:uni_links/uni_links.dart';
class Root extends StatefulWidget { class Root extends StatefulWidget {
Root( Root({
{required Key key, required Key key,
required this.authenticationStore, required this.authenticationStore,
required this.appStore, required this.appStore,
required this.child, required this.child,
required this.navigatorKey}) required this.navigatorKey,
: super(key: key); required this.authService,
}) : super(key: key);
final AuthenticationStore authenticationStore; final AuthenticationStore authenticationStore;
final AppStore appStore; final AppStore appStore;
final GlobalKey<NavigatorState> navigatorKey; final GlobalKey<NavigatorState> navigatorKey;
final AuthService authService;
final Widget child; final Widget child;
@override @override
@ -26,22 +31,56 @@ class Root extends StatefulWidget {
class RootState extends State<Root> with WidgetsBindingObserver { class RootState extends State<Root> with WidgetsBindingObserver {
RootState() RootState()
: _isInactiveController = StreamController<bool>.broadcast(), : _isInactiveController = StreamController<bool>.broadcast(),
_isInactive = false, _isInactive = false,
_postFrameCallback = false; _requestAuth = true,
_postFrameCallback = false;
Stream<bool> get isInactive => _isInactiveController.stream; Stream<bool> get isInactive => _isInactiveController.stream;
StreamController<bool> _isInactiveController; StreamController<bool> _isInactiveController;
bool _isInactive; bool _isInactive;
bool _postFrameCallback; bool _postFrameCallback;
bool _requestAuth;
StreamSubscription<Uri?>? stream;
Uri? launchUri;
@override @override
void initState() { void initState() {
_requestAuth = widget.authService.requireAuth();
_isInactiveController = StreamController<bool>.broadcast(); _isInactiveController = StreamController<bool>.broadcast();
_isInactive = false; _isInactive = false;
_postFrameCallback = false; _postFrameCallback = false;
WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addObserver(this);
super.initState(); 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<void> 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 @override
@ -52,11 +91,15 @@ class RootState extends State<Root> with WidgetsBindingObserver {
return; return;
} }
if (!_isInactive && if (!_isInactive && widget.authenticationStore.state == AuthenticationState.allowed) {
widget.authenticationStore.state == AuthenticationState.allowed) {
setState(() => _setInactive(true)); setState(() => _setInactive(true));
} }
break;
case AppLifecycleState.resumed:
setState(() {
_requestAuth = widget.authService.requireAuth();
});
break; break;
default: default:
break; break;
@ -65,7 +108,7 @@ class RootState extends State<Root> with WidgetsBindingObserver {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (_isInactive && !_postFrameCallback) { if (_isInactive && !_postFrameCallback && _requestAuth) {
_postFrameCallback = true; _postFrameCallback = true;
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
widget.navigatorKey.currentState?.pushNamed(Routes.unlock, widget.navigatorKey.currentState?.pushNamed(Routes.unlock,
@ -75,9 +118,19 @@ class RootState extends State<Root> with WidgetsBindingObserver {
} }
_reset(); _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); return WillPopScope(onWillPop: () async => false, child: widget.child);

View file

@ -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/alert_with_two_actions.dart';
import 'package:cake_wallet/src/widgets/picker.dart'; import 'package:cake_wallet/src/widgets/picker.dart';
import 'package:cake_wallet/src/widgets/template_tile.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:cake_wallet/view_model/send/output.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.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'; import 'package:cw_core/crypto_currency.dart';
class SendPage extends BasePage { class SendPage extends BasePage {
SendPage({required this.sendViewModel}) : _formKey = GlobalKey<FormState>(); SendPage({
required this.sendViewModel,
this.initialPaymentRequest,
}) : _formKey = GlobalKey<FormState>();
final SendViewModel sendViewModel; final SendViewModel sendViewModel;
final GlobalKey<FormState> _formKey; final GlobalKey<FormState> _formKey;
final controller = PageController(initialPage: 0); final controller = PageController(initialPage: 0);
final PaymentRequest? initialPaymentRequest;
bool _effectsInstalled = false; bool _effectsInstalled = false;
@ -116,6 +121,7 @@ class SendPage extends BasePage {
key: output.key, key: output.key,
output: output, output: output,
sendViewModel: sendViewModel, sendViewModel: sendViewModel,
initialPaymentRequest: initialPaymentRequest,
); );
}); });
}, },

View file

@ -1,4 +1,5 @@
import 'package:cake_wallet/entities/priority_for_wallet_type.dart'; 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:cake_wallet/utils/payment_request.dart';
import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/transaction_priority.dart';
import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/routes.dart';
@ -20,21 +21,28 @@ class SendCard extends StatefulWidget {
SendCard({ SendCard({
Key? key, Key? key,
required this.output, required this.output,
required this.sendViewModel}) : super(key: key); required this.sendViewModel,
this.initialPaymentRequest,
}) : super(key: key);
final Output output; final Output output;
final SendViewModel sendViewModel; final SendViewModel sendViewModel;
final PaymentRequest? initialPaymentRequest;
@override @override
SendCardState createState() => SendCardState( SendCardState createState() => SendCardState(
output: output, output: output,
sendViewModel: sendViewModel sendViewModel: sendViewModel,
initialPaymentRequest: initialPaymentRequest,
); );
} }
class SendCardState extends State<SendCard> class SendCardState extends State<SendCard>
with AutomaticKeepAliveClientMixin<SendCard> { with AutomaticKeepAliveClientMixin<SendCard> {
SendCardState({required this.output, required this.sendViewModel}) SendCardState({
required this.output,
required this.sendViewModel,
this.initialPaymentRequest})
: addressController = TextEditingController(), : addressController = TextEditingController(),
cryptoAmountController = TextEditingController(), cryptoAmountController = TextEditingController(),
fiatAmountController = TextEditingController(), fiatAmountController = TextEditingController(),
@ -49,6 +57,7 @@ class SendCardState extends State<SendCard>
final Output output; final Output output;
final SendViewModel sendViewModel; final SendViewModel sendViewModel;
final PaymentRequest? initialPaymentRequest;
final TextEditingController addressController; final TextEditingController addressController;
final TextEditingController cryptoAmountController; final TextEditingController cryptoAmountController;
@ -61,6 +70,27 @@ class SendCardState extends State<SendCard>
bool _effectsInstalled = false; 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<void>(
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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context); super.build(context);
@ -512,8 +542,12 @@ class SendCardState extends State<SendCard>
} }
void _setEffects(BuildContext context) { void _setEffects(BuildContext context) {
addressController.text = output.address; if (output.address.isNotEmpty) {
cryptoAmountController.text = output.cryptoAmount; addressController.text = output.address;
}
if (output.cryptoAmount.isNotEmpty) {
cryptoAmountController.text = output.cryptoAmount;
}
fiatAmountController.text = output.fiatAmount; fiatAmountController.text = output.fiatAmount;
noteController.text = output.note; noteController.text = output.note;
extractedAddressController.text = output.extractedAddress; extractedAddressController.text = output.extractedAddress;
@ -605,6 +639,13 @@ class SendCardState extends State<SendCard>
extractedAddressController.text = extractedAddress; 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; _effectsInstalled = true;
} }

View file

@ -1,9 +1,11 @@
import 'package:cake_wallet/entities/pin_code_required_duration.dart';
import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/auth/auth_page.dart'; import 'package:cake_wallet/src/screens/auth/auth_page.dart';
import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/generated/i18n.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/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_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/screens/settings/widgets/settings_switcher_cell.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart'; import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:cake_wallet/view_model/settings/security_settings_view_model.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: [ child: Column(mainAxisSize: MainAxisSize.min, children: [
SettingsCellWithArrow( SettingsCellWithArrow(
title: S.current.show_keys, title: S.current.show_keys,
handler: (_) => Navigator.of(context).pushNamed(Routes.auth, handler: (_) => _securitySettingsViewModel.checkPinCodeRiquired()
arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) { ? Navigator.of(context).pushNamed(Routes.auth,
if (isAuthenticatedSuccessfully) { arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) {
auth.close(route: Routes.showKeys); if (isAuthenticatedSuccessfully) {
} auth.close(route: Routes.showKeys);
}), }
})
: Navigator.of(context).pushNamed(Routes.showKeys),
), ),
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)), StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
SettingsCellWithArrow( SettingsCellWithArrow(
title: S.current.create_backup, title: S.current.create_backup,
handler: (_) => Navigator.of(context).pushNamed(Routes.auth, handler: (_) => _securitySettingsViewModel.checkPinCodeRiquired()
arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) { ? Navigator.of(context).pushNamed(Routes.auth,
if (isAuthenticatedSuccessfully) { arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) {
auth.close(route: Routes.backup); if (isAuthenticatedSuccessfully) {
} auth.close(route: Routes.backup);
}), }
})
: Navigator.of(context).pushNamed(Routes.backup),
), ),
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)), StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
SettingsCellWithArrow( SettingsCellWithArrow(
@ -65,10 +71,12 @@ class SecurityBackupPage extends BasePage {
arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) async { arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) async {
if (isAuthenticatedSuccessfully) { if (isAuthenticatedSuccessfully) {
if (await _securitySettingsViewModel.biometricAuthenticated()) { if (await _securitySettingsViewModel.biometricAuthenticated()) {
_securitySettingsViewModel.setAllowBiometricalAuthentication(isAuthenticatedSuccessfully); _securitySettingsViewModel
.setAllowBiometricalAuthentication(isAuthenticatedSuccessfully);
} }
} else { } else {
_securitySettingsViewModel.setAllowBiometricalAuthentication(isAuthenticatedSuccessfully); _securitySettingsViewModel
.setAllowBiometricalAuthentication(isAuthenticatedSuccessfully);
} }
auth.close(); auth.close();
@ -78,6 +86,16 @@ class SecurityBackupPage extends BasePage {
} }
}); });
}), }),
Observer(builder: (_) {
return SettingsPickerCell<PinCodeRequiredDuration>(
title: S.current.require_pin_after,
items: PinCodeRequiredDuration.values,
selectedItem: _securitySettingsViewModel.pinCodeRequiredDuration,
onItemSelected: (PinCodeRequiredDuration code) {
_securitySettingsViewModel.setPinCodeRequiredDuration(code);
},
);
}),
]), ]),
); );
} }

View file

@ -220,68 +220,88 @@ class WalletListBodyState extends State<WalletListBody> {
} }
Future<void> _loadWallet(WalletListItem wallet) async { Future<void> _loadWallet(WalletListItem wallet) async {
await Navigator.of(context).pushNamed(Routes.auth, arguments: if (await widget.walletListViewModel.checkIfAuthRequired()) {
(bool isAuthenticatedSuccessfully, AuthPageState auth) async { await Navigator.of(context).pushNamed(Routes.auth,
if (!isAuthenticatedSuccessfully) { arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) async {
return; 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 { try {
auth.changeProcessText( changeProcessText(S.of(context).wallet_list_loading_wallet(wallet.name));
S.of(context).wallet_list_loading_wallet(wallet.name));
await widget.walletListViewModel.loadWallet(wallet); await widget.walletListViewModel.loadWallet(wallet);
auth.hideProgressText(); hideProgressText();
auth.close(); Navigator.of(context).pop();
WidgetsBinding.instance.addPostFrameCallback((_) {
Navigator.of(context).pop();
});
} catch (e) { } catch (e) {
auth.changeProcessText(S changeProcessText(S.of(context).wallet_list_failed_to_load(wallet.name, e.toString()));
.of(context)
.wallet_list_failed_to_load(wallet.name, e.toString()));
} }
}); }
} }
Future<void> _removeWallet(WalletListItem wallet) async { Future<void> _removeWallet(WalletListItem wallet) async {
await Navigator.of(context).pushNamed(Routes.auth, arguments: if (widget.walletListViewModel.checkIfAuthRequired()) {
(bool isAuthenticatedSuccessfully, AuthPageState auth) async { await Navigator.of(context).pushNamed(Routes.auth,
if (!isAuthenticatedSuccessfully) { arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) async {
return; if (!isAuthenticatedSuccessfully) {
} return;
bool confirmed = false;
await showPopUp<void>(
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()));
} }
} _onSuccessfulAuth(wallet, auth);
});
} else {
_onSuccessfulAuth(wallet, null);
}
}
auth.close(); void _onSuccessfulAuth(WalletListItem wallet, AuthPageState? auth) async {
}); bool confirmed = false;
await showPopUp<void>(
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) { void changeProcessText(String text) {
@ -294,16 +314,16 @@ class WalletListBodyState extends State<WalletListBody> {
} }
ActionPane _actionPane(WalletListItem wallet) => ActionPane( ActionPane _actionPane(WalletListItem wallet) => ActionPane(
motion: const ScrollMotion(), motion: const ScrollMotion(),
extentRatio: 0.3, extentRatio: 0.3,
children: [ children: [
SlidableAction( SlidableAction(
onPressed: (_) => _removeWallet(wallet), onPressed: (_) => _removeWallet(wallet),
backgroundColor: Colors.red, backgroundColor: Colors.red,
foregroundColor: Colors.white, foregroundColor: Colors.white,
icon: CupertinoIcons.delete, icon: CupertinoIcons.delete,
label: S.of(context).delete, label: S.of(context).delete,
), ),
], ],
); );
} }

View file

@ -31,13 +31,8 @@ abstract class TradesStoreBase with Store {
void setTrade(Trade trade) => this.trade = trade; void setTrade(Trade trade) => this.trade = trade;
@action @action
Future<void> updateTradeList() async { Future<void> updateTradeList() async => trades =
if (trade == null) { tradesSource.values.map((trade) => TradeListItem(
return; trade: trade,
} settingsStore: settingsStore)).toList();
trades = tradesSource.values.map((trade) => TradeListItem(
trade: trade!,
settingsStore: settingsStore)).toList();
}
} }

View file

@ -1,4 +1,5 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart'; 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:cake_wallet/entities/preferences_key.dart';
import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/transaction_priority.dart';
import 'package:cake_wallet/themes/theme_base.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/monero/monero.dart';
import 'package:cake_wallet/entities/action_list_display_mode.dart'; import 'package:cake_wallet/entities/action_list_display_mode.dart';
import 'package:cake_wallet/entities/fiat_api_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'; part 'settings_store.g.dart';
@ -41,6 +41,7 @@ abstract class SettingsStoreBase with Store {
required this.shouldShowYatPopup, required this.shouldShowYatPopup,
required this.isBitcoinBuyEnabled, required this.isBitcoinBuyEnabled,
required this.actionlistDisplayMode, required this.actionlistDisplayMode,
required this.pinTimeOutDuration,
TransactionPriority? initialBitcoinTransactionPriority, TransactionPriority? initialBitcoinTransactionPriority,
TransactionPriority? initialMoneroTransactionPriority, TransactionPriority? initialMoneroTransactionPriority,
TransactionPriority? initialHavenTransactionPriority, TransactionPriority? initialHavenTransactionPriority,
@ -141,6 +142,11 @@ abstract class SettingsStoreBase with Store {
(String languageCode) => sharedPreferences.setString( (String languageCode) => sharedPreferences.setString(
PreferencesKey.currentLanguageCode, languageCode)); PreferencesKey.currentLanguageCode, languageCode));
reaction(
(_) => pinTimeOutDuration,
(PinCodeRequiredDuration pinCodeInterval) => sharedPreferences.setInt(
PreferencesKey.pinTimeOutDuration, pinCodeInterval.value));
reaction( reaction(
(_) => balanceDisplayMode, (_) => balanceDisplayMode,
(BalanceDisplayMode mode) => sharedPreferences.setInt( (BalanceDisplayMode mode) => sharedPreferences.setInt(
@ -162,6 +168,7 @@ abstract class SettingsStoreBase with Store {
static const defaultPinLength = 4; static const defaultPinLength = 4;
static const defaultActionsMode = 11; static const defaultActionsMode = 11;
static const defaultPinCodeTimeOutDuration = PinCodeRequiredDuration.tenminutes;
@observable @observable
FiatCurrency fiatCurrency; FiatCurrency fiatCurrency;
@ -193,6 +200,9 @@ abstract class SettingsStoreBase with Store {
@observable @observable
int pinCodeLength; int pinCodeLength;
@observable
PinCodeRequiredDuration pinTimeOutDuration;
@computed @computed
ThemeData get theme => currentTheme.themeData; ThemeData get theme => currentTheme.themeData;
@ -288,6 +298,11 @@ abstract class SettingsStoreBase with Store {
sharedPreferences.getInt(PreferencesKey.displayActionListModeKey) ?? sharedPreferences.getInt(PreferencesKey.displayActionListModeKey) ??
defaultActionsMode)); defaultActionsMode));
var pinLength = sharedPreferences.getInt(PreferencesKey.currentPinLength); 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 no value
if (pinLength == null || pinLength == 0) { if (pinLength == null || pinLength == 0) {
pinLength = defaultPinLength; pinLength = defaultPinLength;
@ -343,6 +358,7 @@ abstract class SettingsStoreBase with Store {
initialTheme: savedTheme, initialTheme: savedTheme,
actionlistDisplayMode: actionListDisplayMode, actionlistDisplayMode: actionListDisplayMode,
initialPinLength: pinLength, initialPinLength: pinLength,
pinTimeOutDuration: pinCodeTimeOutDuration,
initialLanguageCode: savedLanguageCode, initialLanguageCode: savedLanguageCode,
initialMoneroTransactionPriority: moneroTransactionPriority, initialMoneroTransactionPriority: moneroTransactionPriority,
initialBitcoinTransactionPriority: bitcoinTransactionPriority, initialBitcoinTransactionPriority: bitcoinTransactionPriority,

View file

@ -1,22 +1,25 @@
class PaymentRequest { 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 address = "";
var amount = ""; var amount = "";
var note = ""; var note = "";
var scheme = "";
if (uri != null) { if (uri != null) {
address = uri.path; address = uri.path;
amount = uri.queryParameters['tx_amount'] ?? uri.queryParameters['amount'] ?? ""; amount = uri.queryParameters['tx_amount'] ?? uri.queryParameters['amount'] ?? "";
note = uri.queryParameters['tx_description'] note = uri.queryParameters['tx_description']
?? uri.queryParameters['message'] ?? ""; ?? uri.queryParameters['message'] ?? "";
scheme = uri.scheme;
} }
return PaymentRequest(address, amount, note); return PaymentRequest(address, amount, note, scheme);
} }
final String address; final String address;
final String amount; final String amount;
final String note; final String note;
final String scheme;
} }

View file

@ -14,10 +14,12 @@ part 'auth_view_model.g.dart';
class AuthViewModel = AuthViewModelBase with _$AuthViewModel; class AuthViewModel = AuthViewModelBase with _$AuthViewModel;
abstract class AuthViewModelBase with Store { abstract class AuthViewModelBase with Store {
AuthViewModelBase(this._authService, this._sharedPreferences, AuthViewModelBase(
this._settingsStore, this._biometricAuth) this._authService, this._sharedPreferences, this._settingsStore, this._biometricAuth)
: _failureCounter = 0, : _failureCounter = 0,
state = InitialExecutionState(); state = InitialExecutionState() {
reaction((_) => state, _saveLastAuthTime);
}
static const maxFailedLogins = 3; static const maxFailedLogins = 3;
static const banTimeout = 180; // 3 minutes static const banTimeout = 180; // 3 minutes
@ -28,8 +30,7 @@ abstract class AuthViewModelBase with Store {
int get pinLength => _settingsStore.pinCodeLength; int get pinLength => _settingsStore.pinCodeLength;
bool get isBiometricalAuthenticationAllowed => bool get isBiometricalAuthenticationAllowed => _settingsStore.allowBiometricalAuthentication;
_settingsStore.allowBiometricalAuthentication;
@observable @observable
int _failureCounter; int _failureCounter;
@ -114,8 +115,14 @@ abstract class AuthViewModelBase with Store {
state = ExecutedSuccessfullyState(); state = ExecutedSuccessfullyState();
} }
} }
} catch(e) { } catch (e) {
state = FailureState(e.toString()); state = FailureState(e.toString());
} }
} }
void _saveLastAuthTime(ExecutionState state) {
if (state is ExecutedSuccessfullyState) {
_authService.saveLastAuthTime();
}
}
} }

View file

@ -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_gift_card.dart';
import 'package:cake_wallet/ionia/ionia_service.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
part 'ionia_custom_redeem_view_model.g.dart'; part 'ionia_custom_redeem_view_model.g.dart';
class IoniaCustomRedeemViewModel = IoniaCustomRedeemViewModelBase with _$IoniaCustomRedeemViewModel; class IoniaCustomRedeemViewModel = IoniaCustomRedeemViewModelBase with _$IoniaCustomRedeemViewModel;
abstract class IoniaCustomRedeemViewModelBase with Store { abstract class IoniaCustomRedeemViewModelBase with Store {
IoniaCustomRedeemViewModelBase(this.giftCard) IoniaCustomRedeemViewModelBase({
: amount = 0; required this.giftCard,
required this.ioniaService,
}) : amount = 0,
redeemState = InitialExecutionState();
final IoniaGiftCard giftCard; final IoniaGiftCard giftCard;
final IoniaService ioniaService;
@observable
ExecutionState redeemState;
@observable @observable
double amount; double amount;
@computed @computed
double get remaining => amount <= giftCard.remainingAmount ? giftCard.remainingAmount - amount : 0; double get remaining =>
amount <= giftCard.remainingAmount ? giftCard.remainingAmount - amount : 0;
@computed @computed
String get formattedRemaining => remaining.toStringAsFixed(2); String get formattedRemaining => remaining.toStringAsFixed(2);
@computed @computed
bool get disableRedeem => amount > giftCard.remainingAmount; bool get disableRedeem => amount > giftCard.remainingAmount;
@action @action
void updateAmount(String text){ void updateAmount(String text) {
amount = text.isEmpty ? 0 : (double.parse(text.replaceAll(',', '.')) ?? 0); amount = double.tryParse(text.replaceAll(',', '.')) ?? 0;
} }
} @action
Future<void> addCustomRedeem() async {
try {
redeemState = IsExecutingState();
await ioniaService.redeem(giftCardId: giftCard.id, amount: amount);
redeemState = ExecutedSuccessfullyState();
} catch (e) {
redeemState = FailureState(e.toString());
}
}
}

View file

@ -6,21 +6,19 @@ import 'package:device_display_brightness/device_display_brightness.dart';
part 'ionia_gift_card_details_view_model.g.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 { abstract class IoniaGiftCardDetailsViewModelBase with Store {
IoniaGiftCardDetailsViewModelBase({required this.ioniaService, required this.giftCard})
IoniaGiftCardDetailsViewModelBase({ : redeemState = InitialExecutionState(),
required this.ioniaService, remainingAmount = giftCard.remainingAmount,
required this.giftCard}) brightness = 0;
: redeemState = InitialExecutionState(),
remainingAmount = giftCard.remainingAmount,
brightness = 0;
final IoniaService ioniaService; final IoniaService ioniaService;
double brightness; double brightness;
@observable @observable
IoniaGiftCard giftCard; IoniaGiftCard giftCard;
@ -35,21 +33,22 @@ abstract class IoniaGiftCardDetailsViewModelBase with Store {
giftCard.remainingAmount = remainingAmount; giftCard.remainingAmount = remainingAmount;
try { try {
redeemState = IsExecutingState(); redeemState = IsExecutingState();
await ioniaService.redeem(giftCard); await ioniaService.redeem(giftCardId: giftCard.id, amount: giftCard.remainingAmount);
giftCard = await ioniaService.getGiftCard(id: giftCard.id); giftCard = await ioniaService.getGiftCard(id: giftCard.id);
redeemState = ExecutedSuccessfullyState(); redeemState = ExecutedSuccessfullyState();
} catch(e) { } catch (e) {
redeemState = FailureState(e.toString()); redeemState = FailureState(e.toString());
} }
} }
@action @action
void updateRemaining(double amount){ Future<void> refeshCard() async {
remainingAmount = amount; giftCard = await ioniaService.getGiftCard(id: giftCard.id);
remainingAmount = giftCard.remainingAmount;
} }
void increaseBrightness() async { void increaseBrightness() async {
brightness = await DeviceDisplayBrightness.getBrightness(); brightness = await DeviceDisplayBrightness.getBrightness();
await DeviceDisplayBrightness.setBrightness(1.0); await DeviceDisplayBrightness.setBrightness(1.0);
} }
} }

View file

@ -53,7 +53,7 @@ abstract class SendViewModelBase with Store {
outputs.add(Output(_wallet, _settingsStore, _fiatConversationStore, () => selectedCryptoCurrency)); outputs.add(Output(_wallet, _settingsStore, _fiatConversationStore, () => selectedCryptoCurrency));
} }
@observable @observable
ExecutionState state; ExecutionState state;
@ -180,6 +180,8 @@ abstract class SendViewModelBase with Store {
WalletType get walletType => _wallet.type; WalletType get walletType => _wallet.type;
String? get walletCurrencyName => _wallet.currency.name?.toLowerCase();
bool get hasCurrecyChanger => walletType == WalletType.haven; bool get hasCurrecyChanger => walletType == WalletType.haven;
@computed @computed
@ -306,7 +308,7 @@ abstract class SendViewModelBase with Store {
@action @action
void onClose() => void onClose() =>
_settingsStore.fiatCurrency = fiatFromSettings; _settingsStore.fiatCurrency = fiatFromSettings;
@action @action
void setFiatCurrency(FiatCurrency fiat) => void setFiatCurrency(FiatCurrency fiat) =>
_settingsStore.fiatCurrency = fiat; _settingsStore.fiatCurrency = fiat;

View file

@ -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/biometric_auth.dart';
import 'package:cake_wallet/entities/pin_code_required_duration.dart';
import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/store/settings_store.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
@ -7,19 +9,33 @@ part 'security_settings_view_model.g.dart';
class SecuritySettingsViewModel = SecuritySettingsViewModelBase with _$SecuritySettingsViewModel; class SecuritySettingsViewModel = SecuritySettingsViewModelBase with _$SecuritySettingsViewModel;
abstract class SecuritySettingsViewModelBase with Store { abstract class SecuritySettingsViewModelBase with Store {
SecuritySettingsViewModelBase(this._settingsStore) : _biometricAuth = BiometricAuth(); SecuritySettingsViewModelBase(
this._settingsStore,
this._authService,
) : _biometricAuth = BiometricAuth();
final BiometricAuth _biometricAuth; final BiometricAuth _biometricAuth;
final SettingsStore _settingsStore; final SettingsStore _settingsStore;
final AuthService _authService;
@computed @computed
bool get allowBiometricalAuthentication => _settingsStore.allowBiometricalAuthentication; bool get allowBiometricalAuthentication => _settingsStore.allowBiometricalAuthentication;
@computed
PinCodeRequiredDuration get pinCodeRequiredDuration => _settingsStore.pinTimeOutDuration;
@action @action
Future<bool> biometricAuthenticated() async { Future<bool> biometricAuthenticated() async {
return await _biometricAuth.canCheckBiometrics() && await _biometricAuth.isAuthenticated(); return await _biometricAuth.canCheckBiometrics() && await _biometricAuth.isAuthenticated();
} }
@action @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();
} }

View file

@ -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/core/wallet_loading_service.dart';
import 'package:cake_wallet/view_model/wallet_new_vm.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/di.dart';
@ -15,9 +15,12 @@ part 'wallet_list_view_model.g.dart';
class WalletListViewModel = WalletListViewModelBase with _$WalletListViewModel; class WalletListViewModel = WalletListViewModelBase with _$WalletListViewModel;
abstract class WalletListViewModelBase with Store { abstract class WalletListViewModelBase with Store {
WalletListViewModelBase(this._walletInfoSource, this._appStore, WalletListViewModelBase(
this._walletLoadingService) this._walletInfoSource,
: wallets = ObservableList<WalletListItem>() { this._appStore,
this._walletLoadingService,
this._authService,
) : wallets = ObservableList<WalletListItem>() {
_updateList(); _updateList();
} }
@ -27,6 +30,7 @@ abstract class WalletListViewModelBase with Store {
final AppStore _appStore; final AppStore _appStore;
final Box<WalletInfo> _walletInfoSource; final Box<WalletInfo> _walletInfoSource;
final WalletLoadingService _walletLoadingService; final WalletLoadingService _walletLoadingService;
final AuthService _authService;
WalletType get currentWalletType => _appStore.wallet!.type; WalletType get currentWalletType => _appStore.wallet!.type;
@ -47,12 +51,20 @@ abstract class WalletListViewModelBase with Store {
void _updateList() { void _updateList() {
wallets.clear(); wallets.clear();
wallets.addAll(_walletInfoSource.values.map((info) => WalletListItem( wallets.addAll(
name: info.name, _walletInfoSource.values.map(
type: info.type, (info) => WalletListItem(
key: info.key, name: info.name,
isCurrent: info.name == _appStore.wallet!.name && type: info.type,
info.type == _appStore.wallet!.type, key: info.key,
isEnabled: availableWalletTypes.contains(info.type)))); isCurrent: info.name == _appStore.wallet!.name && info.type == _appStore.wallet!.type,
isEnabled: availableWalletTypes.contains(info.type),
),
),
);
}
bool checkIfAuthRequired() {
return _authService.requireAuth();
} }
} }

View file

@ -663,6 +663,9 @@
"privacy": "Datenschutz", "privacy": "Datenschutz",
"display_settings": "Anzeigeeinstellungen", "display_settings": "Anzeigeeinstellungen",
"other_settings": "Andere Einstellungen", "other_settings": "Andere Einstellungen",
"require_pin_after": "PIN anfordern nach",
"always": "immer",
"minutes_to_pin_code": "${minute} Minuten",
"disable_exchange": "Exchange deaktivieren", "disable_exchange": "Exchange deaktivieren",
"advanced_privacy_settings": "Erweiterte Datenschutzeinstellungen", "advanced_privacy_settings": "Erweiterte Datenschutzeinstellungen",
"settings_can_be_changed_later": "Diese Einstellungen können später in den App-Einstellungen geändert werden", "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", "fiat_api": "Fiat API",
"disabled": "Deaktiviert", "disabled": "Deaktiviert",
"enabled": "Ermöglicht", "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"
} }

View file

@ -663,6 +663,9 @@
"privacy": "Privacy", "privacy": "Privacy",
"display_settings": "Display settings", "display_settings": "Display settings",
"other_settings": "Other settings", "other_settings": "Other settings",
"require_pin_after": "Require PIN after",
"always": "Always",
"minutes_to_pin_code": "${minute} minutes",
"disable_exchange": "Disable exchange", "disable_exchange": "Disable exchange",
"advanced_privacy_settings": "Advanced Privacy Settings", "advanced_privacy_settings": "Advanced Privacy Settings",
"settings_can_be_changed_later": "These settings can be changed later in the app settings", "settings_can_be_changed_later": "These settings can be changed later in the app settings",
@ -671,5 +674,6 @@
"fiat_api": "Fiat API", "fiat_api": "Fiat API",
"disabled": "Disabled", "disabled": "Disabled",
"enabled": "Enabled", "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"
} }

View file

@ -663,6 +663,9 @@
"privacy": "Privacidad", "privacy": "Privacidad",
"display_settings": "Configuración de pantalla", "display_settings": "Configuración de pantalla",
"other_settings": "Otras configuraciones", "other_settings": "Otras configuraciones",
"require_pin_after": "Requerir PIN después de",
"always": "siempre",
"minutes_to_pin_code": "${minute} minutos",
"disable_exchange": "Deshabilitar intercambio", "disable_exchange": "Deshabilitar intercambio",
"advanced_privacy_settings": "Configuración avanzada de privacidad", "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", "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", "fiat_api": "Fiat API",
"disabled": "Desactivado", "disabled": "Desactivado",
"enabled": "Activado", "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"
} }

View file

@ -661,6 +661,9 @@
"privacy": "Confidentialité", "privacy": "Confidentialité",
"display_settings": "Paramètres d'affichage", "display_settings": "Paramètres d'affichage",
"other_settings": "Autres paramètres", "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", "disable_exchange": "Désactiver l'échange",
"advanced_privacy_settings": "Paramètres de confidentialité avancés", "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", "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", "fiat_api": "Fiat API",
"disabled": "Handicapé", "disabled": "Handicapé",
"enabled": "Activé", "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é"
} }

View file

@ -663,12 +663,17 @@
"privacy": "गोपनीयता", "privacy": "गोपनीयता",
"display_settings": "प्रदर्शन सेटिंग्स", "display_settings": "प्रदर्शन सेटिंग्स",
"other_settings": "अन्य सेटिंग्स", "other_settings": "अन्य सेटिंग्स",
"require_pin_after": "इसके बाद पिन आवश्यक है",
"always": "हमेशा",
"minutes_to_pin_code": "${minute} मिनट",
"disable_exchange": "एक्सचेंज अक्षम करें",
"advanced_privacy_settings": "उन्नत गोपनीयता सेटिंग्स", "advanced_privacy_settings": "उन्नत गोपनीयता सेटिंग्स",
"settings_can_be_changed_later": "इन सेटिंग्स को बाद में ऐप सेटिंग में बदला जा सकता है", "settings_can_be_changed_later": "इन सेटिंग्स को बाद में ऐप सेटिंग में बदला जा सकता है",
"add_custom_node": "नया कस्टम नोड जोड़ें", "add_custom_node": "नया कस्टम नोड जोड़ें",
"disable_exchange": "एक्सचेंज अक्षम करें", "disable_fiat": "िएट को अक्षम करें",
"fiat_api": "फिएट पैसे API", "fiat_api": "फिएट पैसे API",
"disabled": "अक्षम", "disabled": "अक्षम",
"enabled": "सक्रिय", "enabled": "सक्रिय",
"tor_only": "Tor केवल" "tor_only": "Tor केवल",
"unmatched_currencies": "आपके वर्तमान वॉलेट की मुद्रा स्कैन किए गए क्यूआर से मेल नहीं खाती"
} }

View file

@ -663,6 +663,9 @@
"privacy": "Privatnost", "privacy": "Privatnost",
"display_settings": "Postavke zaslona", "display_settings": "Postavke zaslona",
"other_settings": "Ostale postavke", "other_settings": "Ostale postavke",
"require_pin_after": "Zahtijevaj PIN nakon",
"always": "Uvijek",
"minutes_to_pin_code": "${minute} minuta",
"disable_exchange": "Onemogući exchange", "disable_exchange": "Onemogući exchange",
"advanced_privacy_settings": "Napredne postavke privatnosti", "advanced_privacy_settings": "Napredne postavke privatnosti",
"settings_can_be_changed_later": "Te se postavke mogu promijeniti kasnije u postavkama aplikacije", "settings_can_be_changed_later": "Te se postavke mogu promijeniti kasnije u postavkama aplikacije",
@ -671,5 +674,6 @@
"fiat_api": "Fiat API", "fiat_api": "Fiat API",
"disabled": "Onemogućeno", "disabled": "Onemogućeno",
"enabled": "Omoguć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"
} }

View file

@ -663,6 +663,9 @@
"privacy": "Privacy", "privacy": "Privacy",
"display_settings": "Impostazioni di visualizzazione", "display_settings": "Impostazioni di visualizzazione",
"other_settings": "Altre impostazioni", "other_settings": "Altre impostazioni",
"require_pin_after": "Richiedi PIN dopo",
"always": "sempre",
"minutes_to_pin_code": "${minute} minuti",
"disable_exchange": "Disabilita scambio", "disable_exchange": "Disabilita scambio",
"advanced_privacy_settings": "Impostazioni avanzate sulla privacy", "advanced_privacy_settings": "Impostazioni avanzate sulla privacy",
"settings_can_be_changed_later": "Queste impostazioni possono essere modificate in seguito nelle impostazioni dell'app", "settings_can_be_changed_later": "Queste impostazioni possono essere modificate in seguito nelle impostazioni dell'app",
@ -671,5 +674,6 @@
"fiat_api": "Fiat API", "fiat_api": "Fiat API",
"disabled": "Disabilitato", "disabled": "Disabilitato",
"enabled": "Abilitato", "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"
} }

View file

@ -663,6 +663,9 @@
"privacy": "プライバシー", "privacy": "プライバシー",
"display_settings": "表示設定", "display_settings": "表示設定",
"other_settings": "その他の設定", "other_settings": "その他の設定",
"require_pin_after": "後に PIN が必要",
"always": "いつも",
"minutes_to_pin_code": "${minute} 分",
"disable_exchange": "交換を無効にする", "disable_exchange": "交換を無効にする",
"advanced_privacy_settings": "高度なプライバシー設定", "advanced_privacy_settings": "高度なプライバシー設定",
"settings_can_be_changed_later": "これらの設定は、後でアプリの設定で変更できます", "settings_can_be_changed_later": "これらの設定は、後でアプリの設定で変更できます",
@ -671,5 +674,6 @@
"fiat_api": "不換紙幣 API", "fiat_api": "不換紙幣 API",
"disabled": "無効", "disabled": "無効",
"enabled": "有効", "enabled": "有効",
"tor_only": "Torのみ" "tor_only": "Torのみ",
"unmatched_currencies": "現在のウォレットの通貨がスキャンされたQRの通貨と一致しません"
} }

View file

@ -663,6 +663,9 @@
"privacy": "프라이버시", "privacy": "프라이버시",
"display_settings": "디스플레이 설정", "display_settings": "디스플레이 설정",
"other_settings": "기타 설정", "other_settings": "기타 설정",
"require_pin_after": "다음 이후에 PIN 필요",
"always": "언제나",
"minutes_to_pin_code": "${minute}분",
"disable_exchange": "교환 비활성화", "disable_exchange": "교환 비활성화",
"advanced_privacy_settings": "고급 개인 정보 설정", "advanced_privacy_settings": "고급 개인 정보 설정",
"settings_can_be_changed_later": "이 설정은 나중에 앱 설정에서 변경할 수 있습니다.", "settings_can_be_changed_later": "이 설정은 나중에 앱 설정에서 변경할 수 있습니다.",
@ -671,5 +674,6 @@
"fiat_api": "명목 화폐 API", "fiat_api": "명목 화폐 API",
"disabled": "장애가 있는", "disabled": "장애가 있는",
"enabled": "사용", "enabled": "사용",
"tor_only": "Tor 뿐" "tor_only": "Tor 뿐",
"unmatched_currencies": "현재 지갑의 통화가 스캔한 QR의 통화와 일치하지 않습니다."
} }

View file

@ -663,6 +663,9 @@
"privacy": "Privacy", "privacy": "Privacy",
"display_settings": "Weergave-instellingen", "display_settings": "Weergave-instellingen",
"other_settings": "Andere instellingen", "other_settings": "Andere instellingen",
"require_pin_after": "Pincode vereist na",
"always": "altijd",
"minutes_to_pin_code": "${minute} minuten",
"disable_exchange": "Uitwisseling uitschakelen", "disable_exchange": "Uitwisseling uitschakelen",
"advanced_privacy_settings": "Geavanceerde privacy-instellingen", "advanced_privacy_settings": "Geavanceerde privacy-instellingen",
"settings_can_be_changed_later": "Deze instellingen kunnen later worden gewijzigd in de app-instellingen", "settings_can_be_changed_later": "Deze instellingen kunnen later worden gewijzigd in de app-instellingen",
@ -671,5 +674,6 @@
"fiat_api": "Fiat API", "fiat_api": "Fiat API",
"disabled": "Gehandicapt", "disabled": "Gehandicapt",
"enabled": "Ingeschakeld", "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"
} }

View file

@ -663,6 +663,9 @@
"privacy": "Prywatność", "privacy": "Prywatność",
"display_settings": "Ustawienia wyświetlania", "display_settings": "Ustawienia wyświetlania",
"other_settings": "Inne ustawienia", "other_settings": "Inne ustawienia",
"require_pin_after": "Wymagaj kodu PIN po",
"always": "zawsze",
"minutes_to_pin_code": "${minute} minut",
"disable_exchange": "Wyłącz wymianę", "disable_exchange": "Wyłącz wymianę",
"advanced_privacy_settings": "Zaawansowane ustawienia prywatności", "advanced_privacy_settings": "Zaawansowane ustawienia prywatności",
"settings_can_be_changed_later": "Te ustawienia można później zmienić w ustawieniach aplikacji", "settings_can_be_changed_later": "Te ustawienia można później zmienić w ustawieniach aplikacji",
@ -671,5 +674,6 @@
"fiat_api": "API Fiata", "fiat_api": "API Fiata",
"disabled": "Wyłączone", "disabled": "Wyłączone",
"enabled": "Włączony", "enabled": "Włączony",
"tor_only": "Tylko Tor" "tor_only": "Tylko Tor",
"unmatched_currencies": "Waluta Twojego obecnego portfela nie odpowiada walucie zeskanowanego kodu QR"
} }

View file

@ -662,6 +662,9 @@
"privacy": "Privacidade", "privacy": "Privacidade",
"display_settings": "Configurações de exibição", "display_settings": "Configurações de exibição",
"other_settings": "Outras configurações", "other_settings": "Outras configurações",
"require_pin_after": "Exigir PIN após",
"always": "sempre",
"minutes_to_pin_code": "${minute} minutos",
"disable_exchange": "Desativar troca", "disable_exchange": "Desativar troca",
"advanced_privacy_settings": "Configurações de privacidade avançadas", "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", "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", "fiat_api": "API da Fiat",
"disabled": "Desabilitado", "disabled": "Desabilitado",
"enabled": "Habilitado", "enabled": "Habilitado",
"tor_only": "Tor apenas" "tor_only": "Tor apenas",
"unmatched_currencies": "A moeda da sua carteira atual não corresponde à do QR digitalizado"
} }

View file

@ -369,7 +369,7 @@
"trade_for_not_created" : "Сделка для ${title} не создана.", "trade_for_not_created" : "Сделка для ${title} не создана.",
"trade_not_created" : "Сделка не создана", "trade_not_created" : "Сделка не создана",
"trade_id_not_found" : "Сделка ${tradeId} ${title} не найдена.", "trade_id_not_found" : "Сделка ${tradeId} ${title} не найдена.",
"trade_not_found" : "Trade not found.", "trade_not_found" : "Торговля не найдена.",
"trade_state_pending" : "Ожидание", "trade_state_pending" : "Ожидание",
@ -663,6 +663,9 @@
"privacy": "Конфиденциальность", "privacy": "Конфиденциальность",
"display_settings": "Настройки отображения", "display_settings": "Настройки отображения",
"other_settings": "Другие настройки", "other_settings": "Другие настройки",
"require_pin_after": "Требовать ПИН после",
"always": "всегда",
"minutes_to_pin_code": "${minute} минут",
"disable_exchange": "Отключить обмен", "disable_exchange": "Отключить обмен",
"advanced_privacy_settings": "Расширенные настройки конфиденциальности", "advanced_privacy_settings": "Расширенные настройки конфиденциальности",
"settings_can_be_changed_later": "Эти настройки можно изменить позже в настройках приложения.", "settings_can_be_changed_later": "Эти настройки можно изменить позже в настройках приложения.",
@ -671,5 +674,6 @@
"fiat_api": "Фиат API", "fiat_api": "Фиат API",
"disabled": "Отключено", "disabled": "Отключено",
"enabled": "Включено", "enabled": "Включено",
"tor_only": "Только Tor" "tor_only": "Только Tor",
"unmatched_currencies": "Валюта вашего текущего кошелька не соответствует валюте отсканированного QR-кода."
} }

View file

@ -662,6 +662,9 @@
"privacy": "Конфіденційність", "privacy": "Конфіденційність",
"display_settings": "Налаштування дисплея", "display_settings": "Налаштування дисплея",
"other_settings": "Інші налаштування", "other_settings": "Інші налаштування",
"require_pin_after": "Вимагати PIN після",
"always": "Завжди",
"minutes_to_pin_code": "${minute} хвилин",
"disable_exchange": "Вимкнути exchange", "disable_exchange": "Вимкнути exchange",
"advanced_privacy_settings": "Розширені налаштування конфіденційності", "advanced_privacy_settings": "Розширені налаштування конфіденційності",
"settings_can_be_changed_later": "Ці параметри можна змінити пізніше в налаштуваннях програми", "settings_can_be_changed_later": "Ці параметри можна змінити пізніше в налаштуваннях програми",
@ -670,5 +673,6 @@
"fiat_api": "Фіат API", "fiat_api": "Фіат API",
"disabled": "Вимкнено", "disabled": "Вимкнено",
"enabled": "Увімкнено", "enabled": "Увімкнено",
"tor_only": "Тільки Tor" "tor_only": "Тільки Tor",
"unmatched_currencies": "Валюта вашого гаманця не збігається з валютою сканованого QR-коду"
} }

View file

@ -661,6 +661,9 @@
"privacy":"隐私", "privacy":"隐私",
"display_settings": "显示设置", "display_settings": "显示设置",
"other_settings": "其他设置", "other_settings": "其他设置",
"require_pin_after": "之后需要 PIN",
"always": "总是",
"minutes_to_pin_code": "${minute} 分钟",
"disable_exchange": "禁用交换", "disable_exchange": "禁用交换",
"advanced_privacy_settings": "高级隐私设置", "advanced_privacy_settings": "高级隐私设置",
"settings_can_be_changed_later": "稍后可以在应用设置中更改这些设置", "settings_can_be_changed_later": "稍后可以在应用设置中更改这些设置",
@ -669,5 +672,6 @@
"fiat_api": "法币API", "fiat_api": "法币API",
"disabled": "禁用", "disabled": "禁用",
"enabled": "启用", "enabled": "启用",
"tor_only": "仅限 Tor" "tor_only": "仅限 Tor",
"unmatched_currencies": "您当前钱包的货币与扫描的 QR 的货币不匹配"
} }

View file

@ -14,14 +14,14 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN)
APP_ANDROID_TYPE=$1 APP_ANDROID_TYPE=$1
MONERO_COM_NAME="Monero.com" MONERO_COM_NAME="Monero.com"
MONERO_COM_VERSION="1.2.1" MONERO_COM_VERSION="1.2.2"
MONERO_COM_BUILD_NUMBER=32 MONERO_COM_BUILD_NUMBER=33
MONERO_COM_BUNDLE_ID="com.monero.app" MONERO_COM_BUNDLE_ID="com.monero.app"
MONERO_COM_PACKAGE="com.monero.app" MONERO_COM_PACKAGE="com.monero.app"
CAKEWALLET_NAME="Cake Wallet" CAKEWALLET_NAME="Cake Wallet"
CAKEWALLET_VERSION="4.5.1" CAKEWALLET_VERSION="4.5.3"
CAKEWALLET_BUILD_NUMBER=136 CAKEWALLET_BUILD_NUMBER=138
CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet" CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet"
CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet" CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet"

View file

@ -13,13 +13,13 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN)
APP_IOS_TYPE=$1 APP_IOS_TYPE=$1
MONERO_COM_NAME="Monero.com" MONERO_COM_NAME="Monero.com"
MONERO_COM_VERSION="1.2.1" MONERO_COM_VERSION="1.2.2"
MONERO_COM_BUILD_NUMBER=29 MONERO_COM_BUILD_NUMBER=30
MONERO_COM_BUNDLE_ID="com.cakewallet.monero" MONERO_COM_BUNDLE_ID="com.cakewallet.monero"
CAKEWALLET_NAME="Cake Wallet" CAKEWALLET_NAME="Cake Wallet"
CAKEWALLET_VERSION="4.5.1" CAKEWALLET_VERSION="4.5.3"
CAKEWALLET_BUILD_NUMBER=133 CAKEWALLET_BUILD_NUMBER=135
CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet"
HAVEN_NAME="Haven" HAVEN_NAME="Haven"