Cw 155 monero synchronization (#1014)

* Run Monero Synchronization task in background on Android

* Add monero sync task in the load function to be registered/cancelled when user changes wallets

* Revert unused file changes

* Register Sync task on all monero wallets if any

* Add Sync Modes and change task frequency based on user's choice

* Register background task after current wallet is set

* Add Sync All toggle and change task wallets to sync accordingly

* Enable background notifications in release mode temporarily

* Disable constraints and increase the frequency of tasks

* Decrease frequency of background tasks

* Delay the background task thread till the syncing thread finish (Dummy Trial-1)

* Start Sync process and wait for it to finish

* Wait for synchronization to finish before ending the background thread
Add 10 minutes timeout duration for sync process

* Connect to node before syncing wallet

* replace testing configuration with the configurations agreed on

* Fix Conflicts with main

* Update and Migrate Background tasks to null safety

* Update workmanager version in pubspec_base also

* Move Sync options to Connection and sync page
Show Sync options only for Monero and Haven
Minor Enhancements

* Remove debugging notifications
Revert aggressive mode frequency to 6 hours [skip ci]

* Add iOS configs

* Revert debugging changes
Fix conflicts with main

* Add/Extract Sync configurations to/from backup file [skip ci]
This commit is contained in:
Omar Hatem 2023-08-04 20:55:56 +03:00 committed by GitHub
parent 68a057b91b
commit aedf310c9d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
48 changed files with 593 additions and 190 deletions

View file

@ -1,7 +1,9 @@
import 'package:flutter/services.dart';
const utils = const MethodChannel('com.cake_wallet/native_utils');
void setIsAppSecureNative(bool isAppSecure) {
utils.invokeMethod<Uint8List>('setIsAppSecure', {'isAppSecure': isAppSecure});
}
try {
final utils = const MethodChannel('com.cake_wallet/native_utils');
utils.invokeMethod<Uint8List>('setIsAppSecure', {'isAppSecure': isAppSecure});
} catch (_) {}
}

View file

@ -606,4 +606,9 @@
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
SystemCapabilities = {
com.apple.BackgroundModes = {
enabled = 1;
};
};
}

View file

@ -1,6 +1,7 @@
import UIKit
import Flutter
import UnstoppableDomainsResolution
import workmanager
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
@ -16,6 +17,15 @@ import UnstoppableDomainsResolution
UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
}
WorkmanagerPlugin.setPluginRegistrantCallback { registry in
// Registry in this case is the FlutterEngine that is created in Workmanager's
// performFetchWithCompletionHandler or BGAppRefreshTask.
// This will make other plugins available during a background operation.
GeneratedPluginRegistrant.register(with: registry)
}
WorkmanagerPlugin.registerTask(withIdentifier: "com.fotolockr.cakewallet.monero_sync_task")
makeSecure()
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController

View file

@ -2,6 +2,10 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.fotolockr.cakewallet.monero_sync_task</string>
</array>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
@ -113,6 +117,7 @@
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>processing</string>
<string>remote-notification</string>
</array>
<key>UILaunchStoryboardName</key>

View file

@ -243,6 +243,8 @@ class BackupService {
final sortBalanceTokensBy = data[PreferencesKey.sortBalanceBy] as int?;
final pinNativeTokenAtTop = data[PreferencesKey.pinNativeTokenAtTop] as bool?;
final useEtherscan = data[PreferencesKey.useEtherscan] as bool?;
final syncAll = data[PreferencesKey.syncAllKey] as bool?;
final syncMode = data[PreferencesKey.syncModeKey] as int?;
await _sharedPreferences.setString(PreferencesKey.currentWalletName, currentWalletName);
@ -361,6 +363,12 @@ class BackupService {
if (useEtherscan != null)
await _sharedPreferences.setBool(PreferencesKey.useEtherscan, useEtherscan);
if (syncAll != null)
await _sharedPreferences.setBool(PreferencesKey.syncAllKey, syncAll);
if (syncMode != null)
await _sharedPreferences.setInt(PreferencesKey.syncModeKey, syncMode);
await preferencesFile.delete();
}
@ -510,6 +518,10 @@ class BackupService {
_sharedPreferences.getBool(PreferencesKey.pinNativeTokenAtTop),
PreferencesKey.useEtherscan:
_sharedPreferences.getBool(PreferencesKey.useEtherscan),
PreferencesKey.syncModeKey:
_sharedPreferences.getInt(PreferencesKey.syncModeKey),
PreferencesKey.syncAllKey:
_sharedPreferences.getBool(PreferencesKey.syncAllKey),
};
return json.encode(preferences);

View file

@ -4,6 +4,7 @@ import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart';
import 'package:cake_wallet/buy/payfura/payfura_buy_provider.dart';
import 'package:cake_wallet/core/yat_service.dart';
import 'package:cake_wallet/entities/background_tasks.dart';
import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
import 'package:cake_wallet/entities/receive_page_option.dart';
@ -23,6 +24,7 @@ import 'package:cake_wallet/src/screens/dashboard/widgets/transactions_page.dart
import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart';
import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
import 'package:cake_wallet/src/screens/settings/display_settings_page.dart';
import 'package:cake_wallet/src/screens/settings/manage_nodes_page.dart';
import 'package:cake_wallet/src/screens/settings/other_settings_page.dart';
import 'package:cake_wallet/src/screens/settings/privacy_page.dart';
import 'package:cake_wallet/src/screens/settings/security_backup_page.dart';
@ -244,6 +246,8 @@ Future setup({
getIt.registerSingletonAsync<SharedPreferences>(() => SharedPreferences.getInstance());
}
getIt.registerFactory(() => BackgroundTasks());
final isBitcoinBuyEnabled = (secrets.wyreSecretKey.isNotEmpty) &&
(secrets.wyreApiKey.isNotEmpty) &&
(secrets.wyreAccountId.isNotEmpty);
@ -681,8 +685,7 @@ Future setup({
return NodeListViewModel(_nodeSource, appStore);
});
getIt.registerFactory(
() => ConnectionSyncPage(getIt.get<NodeListViewModel>(), getIt.get<DashboardViewModel>()));
getIt.registerFactory(() => ConnectionSyncPage(getIt.get<DashboardViewModel>()));
getIt.registerFactory(
() => SecurityBackupPage(getIt.get<SecuritySettingsViewModel>(), getIt.get<AuthService>()));
@ -1055,5 +1058,7 @@ Future setup({
),
);
getIt.registerFactory<ManageNodesPage>(() => ManageNodesPage(getIt.get<NodeListViewModel>()));
_isSetupFinished = true;
}

View file

@ -0,0 +1,164 @@
import 'dart:io';
import 'package:cake_wallet/core/wallet_loading_service.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/view_model/settings/sync_mode.dart';
import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart';
import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:workmanager/workmanager.dart';
import 'package:cake_wallet/main.dart';
import 'package:cake_wallet/di.dart';
const moneroSyncTaskKey = "com.fotolockr.cakewallet.monero_sync_task";
@pragma('vm:entry-point')
void callbackDispatcher() {
Workmanager().executeTask((task, inputData) async {
try {
switch (task) {
case moneroSyncTaskKey:
/// The work manager runs on a separate isolate from the main flutter isolate.
/// thus we initialize app configs first; hive, getIt, etc...
await initializeAppConfigs();
final walletLoadingService = getIt.get<WalletLoadingService>();
final node = getIt.get<SettingsStore>().getCurrentNode(WalletType.monero);
final typeRaw = getIt.get<SharedPreferences>().getInt(PreferencesKey.currentWalletType);
WalletBase? wallet;
if (inputData!['sync_all'] as bool) {
/// get all Monero wallets of the user and sync them
final List<WalletListItem> moneroWallets = getIt
.get<WalletListViewModel>()
.wallets
.where((element) => element.type == WalletType.monero)
.toList();
for (int i = 0; i < moneroWallets.length; i++) {
wallet = await walletLoadingService.load(WalletType.monero, moneroWallets[i].name);
await wallet.connectToNode(node: node);
await wallet.startSync();
}
} else {
/// if the user chose to sync only active wallet
/// if the current wallet is monero; sync it only
if (typeRaw == WalletType.monero.index) {
final name =
getIt.get<SharedPreferences>().getString(PreferencesKey.currentWalletName);
wallet = await walletLoadingService.load(WalletType.monero, name!);
await wallet.connectToNode(node: node);
await wallet.startSync();
}
}
if (wallet?.syncStatus.progress() == null) {
return Future.error("No Monero wallet found");
}
for (int i = 0;; i++) {
await Future<void>.delayed(const Duration(seconds: 1));
if (wallet?.syncStatus.progress() == 1.0) {
break;
}
if (i > 600) {
return Future.error("Synchronization Timed out");
}
}
break;
}
return Future.value(true);
} catch (error, stackTrace) {
print(error);
print(stackTrace);
return Future.error(error);
}
});
}
class BackgroundTasks {
void registerSyncTask({bool changeExisting = false}) async {
try {
bool hasMonero = getIt
.get<WalletListViewModel>()
.wallets
.any((element) => element.type == WalletType.monero);
/// if its not android nor ios, or the user has no monero wallets; exit
if (!DeviceInfo.instance.isMobile || !hasMonero) {
return;
}
final settingsStore = getIt.get<SettingsStore>();
final SyncMode syncMode = settingsStore.currentSyncMode;
final bool syncAll = settingsStore.currentSyncAll;
if (syncMode.type == SyncType.disabled) {
cancelSyncTask();
return;
}
await Workmanager().initialize(
callbackDispatcher,
isInDebugMode: kDebugMode,
);
final inputData = <String, dynamic>{"sync_all": syncAll};
final constraints = Constraints(
networkType:
syncMode.type == SyncType.unobtrusive ? NetworkType.unmetered : NetworkType.connected,
requiresBatteryNotLow: syncMode.type == SyncType.unobtrusive,
requiresCharging: syncMode.type == SyncType.unobtrusive,
requiresDeviceIdle: syncMode.type == SyncType.unobtrusive,
);
if (Platform.isIOS) {
await Workmanager().registerOneOffTask(
moneroSyncTaskKey,
moneroSyncTaskKey,
initialDelay: syncMode.frequency,
existingWorkPolicy: ExistingWorkPolicy.replace,
inputData: inputData,
constraints: constraints,
);
return;
}
await Workmanager().registerPeriodicTask(
moneroSyncTaskKey,
moneroSyncTaskKey,
initialDelay: syncMode.frequency,
frequency: syncMode.frequency,
existingWorkPolicy: changeExisting ? ExistingWorkPolicy.replace : ExistingWorkPolicy.keep,
inputData: inputData,
constraints: constraints,
);
} catch (error, stackTrace) {
print(error);
print(stackTrace);
}
}
void cancelSyncTask() {
try {
Workmanager().cancelByUniqueName(moneroSyncTaskKey);
} catch (error, stackTrace) {
print(error);
print(stackTrace);
}
}
}

View file

@ -1,8 +1,7 @@
import 'package:cake_wallet/di.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/core/key_service.dart';
import 'package:cw_core/wallet_service.dart';
import 'package:cake_wallet/entities/background_tasks.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/core/wallet_loading_service.dart';
@ -24,4 +23,6 @@ Future<void> loadCurrentWallet() async {
final walletLoadingService = getIt.get<WalletLoadingService>();
final wallet = await walletLoadingService.load(type, name);
appStore.changeCurrentWallet(wallet);
getIt.get<BackgroundTasks>().registerSyncTask();
}

View file

@ -36,6 +36,8 @@ 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 syncModeKey = 'sync_mode';
static const syncAllKey = 'sync_all';
static const pinTimeOutDuration = 'pin_timeout_duration';
static const lastAuthTimeMilliseconds = 'last_auth_time_milliseconds';
static const lastPopupDate = 'last_popup_date';

View file

@ -57,97 +57,103 @@ Future<void> main() async {
return true;
};
final appDir = await getApplicationDocumentsDirectory();
await Hive.close();
Hive.init(appDir.path);
if (!Hive.isAdapterRegistered(Contact.typeId)) {
Hive.registerAdapter(ContactAdapter());
}
await initializeAppConfigs();
if (!Hive.isAdapterRegistered(Node.typeId)) {
Hive.registerAdapter(NodeAdapter());
}
if (!Hive.isAdapterRegistered(TransactionDescription.typeId)) {
Hive.registerAdapter(TransactionDescriptionAdapter());
}
if (!Hive.isAdapterRegistered(Trade.typeId)) {
Hive.registerAdapter(TradeAdapter());
}
if (!Hive.isAdapterRegistered(WalletInfo.typeId)) {
Hive.registerAdapter(WalletInfoAdapter());
}
if (!Hive.isAdapterRegistered(walletTypeTypeId)) {
Hive.registerAdapter(WalletTypeAdapter());
}
if (!Hive.isAdapterRegistered(Template.typeId)) {
Hive.registerAdapter(TemplateAdapter());
}
if (!Hive.isAdapterRegistered(ExchangeTemplate.typeId)) {
Hive.registerAdapter(ExchangeTemplateAdapter());
}
if (!Hive.isAdapterRegistered(Order.typeId)) {
Hive.registerAdapter(OrderAdapter());
}
if (!isMoneroOnly && !Hive.isAdapterRegistered(UnspentCoinsInfo.typeId)) {
Hive.registerAdapter(UnspentCoinsInfoAdapter());
}
if (!Hive.isAdapterRegistered(AnonpayInvoiceInfo.typeId)) {
Hive.registerAdapter(AnonpayInvoiceInfoAdapter());
}
final secureStorage = FlutterSecureStorage();
final transactionDescriptionsBoxKey =
await getEncryptionKey(secureStorage: secureStorage, forKey: TransactionDescription.boxKey);
final tradesBoxKey = await getEncryptionKey(secureStorage: secureStorage, forKey: Trade.boxKey);
final ordersBoxKey = await getEncryptionKey(secureStorage: secureStorage, forKey: Order.boxKey);
final contacts = await Hive.openBox<Contact>(Contact.boxName);
final nodes = await Hive.openBox<Node>(Node.boxName);
final transactionDescriptions = await Hive.openBox<TransactionDescription>(
TransactionDescription.boxName,
encryptionKey: transactionDescriptionsBoxKey);
final trades = await Hive.openBox<Trade>(Trade.boxName, encryptionKey: tradesBoxKey);
final orders = await Hive.openBox<Order>(Order.boxName, encryptionKey: ordersBoxKey);
final walletInfoSource = await Hive.openBox<WalletInfo>(WalletInfo.boxName);
final templates = await Hive.openBox<Template>(Template.boxName);
final exchangeTemplates = await Hive.openBox<ExchangeTemplate>(ExchangeTemplate.boxName);
final anonpayInvoiceInfo = await Hive.openBox<AnonpayInvoiceInfo>(AnonpayInvoiceInfo.boxName);
Box<UnspentCoinsInfo>? unspentCoinsInfoSource;
if (!isMoneroOnly) {
unspentCoinsInfoSource = await Hive.openBox<UnspentCoinsInfo>(UnspentCoinsInfo.boxName);
}
await initialSetup(
sharedPreferences: await SharedPreferences.getInstance(),
nodes: nodes,
walletInfoSource: walletInfoSource,
contactSource: contacts,
tradesSource: trades,
ordersSource: orders,
unspentCoinsInfoSource: unspentCoinsInfoSource,
// fiatConvertationService: fiatConvertationService,
templates: templates,
exchangeTemplates: exchangeTemplates,
transactionDescriptions: transactionDescriptions,
secureStorage: secureStorage,
anonpayInvoiceInfo: anonpayInvoiceInfo,
initialMigrationVersion: 21);
runApp(App());
}, (error, stackTrace) async {
ExceptionHandler.onError(FlutterErrorDetails(exception: error, stack: stackTrace));
});
}
Future<void> initializeAppConfigs() async {
final appDir = await getApplicationDocumentsDirectory();
Hive.init(appDir.path);
if (!Hive.isAdapterRegistered(Contact.typeId)) {
Hive.registerAdapter(ContactAdapter());
}
if (!Hive.isAdapterRegistered(Node.typeId)) {
Hive.registerAdapter(NodeAdapter());
}
if (!Hive.isAdapterRegistered(TransactionDescription.typeId)) {
Hive.registerAdapter(TransactionDescriptionAdapter());
}
if (!Hive.isAdapterRegistered(Trade.typeId)) {
Hive.registerAdapter(TradeAdapter());
}
if (!Hive.isAdapterRegistered(WalletInfo.typeId)) {
Hive.registerAdapter(WalletInfoAdapter());
}
if (!Hive.isAdapterRegistered(walletTypeTypeId)) {
Hive.registerAdapter(WalletTypeAdapter());
}
if (!Hive.isAdapterRegistered(Template.typeId)) {
Hive.registerAdapter(TemplateAdapter());
}
if (!Hive.isAdapterRegistered(ExchangeTemplate.typeId)) {
Hive.registerAdapter(ExchangeTemplateAdapter());
}
if (!Hive.isAdapterRegistered(Order.typeId)) {
Hive.registerAdapter(OrderAdapter());
}
if (!isMoneroOnly && !Hive.isAdapterRegistered(UnspentCoinsInfo.typeId)) {
Hive.registerAdapter(UnspentCoinsInfoAdapter());
}
if (!Hive.isAdapterRegistered(AnonpayInvoiceInfo.typeId)) {
Hive.registerAdapter(AnonpayInvoiceInfoAdapter());
}
final secureStorage = FlutterSecureStorage();
final transactionDescriptionsBoxKey =
await getEncryptionKey(secureStorage: secureStorage, forKey: TransactionDescription.boxKey);
final tradesBoxKey = await getEncryptionKey(secureStorage: secureStorage, forKey: Trade.boxKey);
final ordersBoxKey = await getEncryptionKey(secureStorage: secureStorage, forKey: Order.boxKey);
final contacts = await Hive.openBox<Contact>(Contact.boxName);
final nodes = await Hive.openBox<Node>(Node.boxName);
final transactionDescriptions = await Hive.openBox<TransactionDescription>(
TransactionDescription.boxName,
encryptionKey: transactionDescriptionsBoxKey);
final trades = await Hive.openBox<Trade>(Trade.boxName, encryptionKey: tradesBoxKey);
final orders = await Hive.openBox<Order>(Order.boxName, encryptionKey: ordersBoxKey);
final walletInfoSource = await Hive.openBox<WalletInfo>(WalletInfo.boxName);
final templates = await Hive.openBox<Template>(Template.boxName);
final exchangeTemplates = await Hive.openBox<ExchangeTemplate>(ExchangeTemplate.boxName);
final anonpayInvoiceInfo = await Hive.openBox<AnonpayInvoiceInfo>(AnonpayInvoiceInfo.boxName);
Box<UnspentCoinsInfo>? unspentCoinsInfoSource;
if (!isMoneroOnly) {
unspentCoinsInfoSource = await Hive.openBox<UnspentCoinsInfo>(UnspentCoinsInfo.boxName);
}
await initialSetup(
sharedPreferences: await SharedPreferences.getInstance(),
nodes: nodes,
walletInfoSource: walletInfoSource,
contactSource: contacts,
tradesSource: trades,
ordersSource: orders,
unspentCoinsInfoSource: unspentCoinsInfoSource,
// fiatConvertationService: fiatConvertationService,
templates: templates,
exchangeTemplates: exchangeTemplates,
transactionDescriptions: transactionDescriptions,
secureStorage: secureStorage,
anonpayInvoiceInfo: anonpayInvoiceInfo,
initialMigrationVersion: 21);
}
Future<void> initialSetup(
{required SharedPreferences sharedPreferences,
required Box<Node> nodes,
@ -319,7 +325,7 @@ class _HomeState extends State<_Home> {
}
}
@override
Widget build(BuildContext context) {
return const SizedBox.shrink();

View file

@ -19,6 +19,7 @@ import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_dashbo
import 'package:cake_wallet/src/screens/dashboard/widgets/transactions_page.dart';
import 'package:cake_wallet/src/screens/settings/desktop_settings/desktop_settings_page.dart';
import 'package:cake_wallet/src/screens/settings/display_settings_page.dart';
import 'package:cake_wallet/src/screens/settings/manage_nodes_page.dart';
import 'package:cake_wallet/src/screens/settings/other_settings_page.dart';
import 'package:cake_wallet/src/screens/settings/privacy_page.dart';
import 'package:cake_wallet/src/screens/settings/security_backup_page.dart';
@ -605,6 +606,9 @@ Route<dynamic> createRoute(RouteSettings settings) {
),
);
case Routes.manageNodes:
return MaterialPageRoute<void>(builder: (_) => getIt.get<ManageNodesPage>());
default:
return MaterialPageRoute<void>(
builder: (_) => Scaffold(

View file

@ -90,4 +90,5 @@ class Routes {
static const modify2FAPage = '/modify_2fa_page';
static const homeSettings = '/home_settings';
static const editToken = '/edit_token';
static const manageNodes = '/manage_nodes';
}

View file

@ -1,25 +1,25 @@
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/utils/device_info.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cw_core/node.dart';
import 'package:cake_wallet/view_model/settings/sync_mode.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/nodes/widgets/node_list_row.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/view_model/node_list/node_list_view_model.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class ConnectionSyncPage extends BasePage {
ConnectionSyncPage(this.nodeListViewModel, this.dashboardViewModel);
ConnectionSyncPage(this.dashboardViewModel);
@override
String get title => S.current.connection_sync;
final NodeListViewModel nodeListViewModel;
final DashboardViewModel dashboardViewModel;
@override
@ -33,72 +33,39 @@ class ConnectionSyncPage extends BasePage {
title: S.current.reconnect,
handler: (context) => _presentReconnectAlert(context),
),
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
if (dashboardViewModel.hasRescan)
const StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
if (dashboardViewModel.hasRescan) ...[
SettingsCellWithArrow(
title: S.current.rescan,
handler: (context) => Navigator.of(context).pushNamed(Routes.rescan),
),
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
Semantics(
button: true,
child: NodeHeaderListRow(
title: S.of(context).add_new_node,
onTap: (_) async =>
await Navigator.of(context).pushNamed(Routes.newNode),
),
),
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
SizedBox(height: 100),
Observer(
builder: (BuildContext context) {
return Flexible(
child: SectionStandardList(
sectionCount: 1,
context: context,
dividerPadding: EdgeInsets.symmetric(horizontal: 24),
itemCounter: (int sectionIndex) {
return nodeListViewModel.nodes.length;
},
itemBuilder: (_, sectionIndex, index) {
final node = nodeListViewModel.nodes[index];
final isSelected = node.keyIndex == nodeListViewModel.currentNode.keyIndex;
final nodeListRow = NodeListRow(
title: node.uriRaw,
node: node,
isSelected: isSelected,
onTap: (_) async {
if (isSelected) {
return;
}
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle:
S.of(context).change_current_node_title,
alertContent: nodeListViewModel
.getAlertContent(node.uriRaw),
leftButtonText: S.of(context).cancel,
rightButtonText: S.of(context).change,
actionLeftButton: () =>
Navigator.of(context).pop(),
actionRightButton: () async {
await nodeListViewModel.setAsCurrent(node);
Navigator.of(context).pop();
},
);
});
},
);
return nodeListRow;
},
),
);
},
const StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
if (DeviceInfo.instance.isMobile) ...[
Observer(builder: (context) {
return SettingsPickerCell<SyncMode>(
title: S.current.background_sync_mode,
items: SyncMode.all,
displayItem: (SyncMode syncMode) => syncMode.name,
selectedItem: dashboardViewModel.syncMode,
onItemSelected: dashboardViewModel.setSyncMode,
);
}),
const StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
Observer(builder: (context) {
return SettingsSwitcherCell(
title: S.current.sync_all_wallets,
value: dashboardViewModel.syncAll,
onValueChange: (_, bool value) => dashboardViewModel.setSyncAll(value),
);
}),
const StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
],
],
SettingsCellWithArrow(
title: S.current.manage_nodes,
handler: (context) => Navigator.of(context).pushNamed(Routes.manageNodes),
),
const StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
],
),
);

View file

@ -0,0 +1,85 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/nodes/widgets/node_list_row.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/node_list/node_list_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class ManageNodesPage extends BasePage {
ManageNodesPage(this.nodeListViewModel);
final NodeListViewModel nodeListViewModel;
@override
String get title => S.current.manage_nodes;
@override
Widget body(BuildContext context) {
return Padding(
padding: EdgeInsets.only(top: 10),
child: Column(
children: [
Semantics(
button: true,
child: NodeHeaderListRow(
title: S.of(context).add_new_node,
onTap: (_) async => await Navigator.of(context).pushNamed(Routes.newNode),
),
),
const StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
SizedBox(height: 20),
Observer(
builder: (BuildContext context) {
return Flexible(
child: SectionStandardList(
sectionCount: 1,
context: context,
dividerPadding: EdgeInsets.symmetric(horizontal: 24),
itemCounter: (int sectionIndex) {
return nodeListViewModel.nodes.length;
},
itemBuilder: (_, sectionIndex, index) {
final node = nodeListViewModel.nodes[index];
final isSelected = node.keyIndex == nodeListViewModel.currentNode.keyIndex;
final nodeListRow = NodeListRow(
title: node.uriRaw,
node: node,
isSelected: isSelected,
onTap: (_) async {
if (isSelected) {
return;
}
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: S.of(context).change_current_node_title,
alertContent: nodeListViewModel.getAlertContent(node.uriRaw),
leftButtonText: S.of(context).cancel,
rightButtonText: S.of(context).change,
actionLeftButton: () => Navigator.of(context).pop(),
actionRightButton: () async {
await nodeListViewModel.setAsCurrent(node);
Navigator.of(context).pop();
},
);
});
},
);
return nodeListRow;
},
),
);
},
),
],
),
);
}
}

View file

@ -20,26 +20,32 @@ class OtherSettingsPage extends BasePage {
@override
Widget body(BuildContext context) {
return Observer(builder: (_) {
return Container(
padding: EdgeInsets.only(top: 10),
child: Column(children: [
SettingsPickerCell(
title: S.current.settings_fee_priority,
items: priorityForWalletType(_otherSettingsViewModel.walletType),
displayItem: _otherSettingsViewModel.getDisplayPriority,
selectedItem: _otherSettingsViewModel.transactionPriority,
onItemSelected: _otherSettingsViewModel.onDisplayPrioritySelected,
return Observer(
builder: (_) {
return Container(
padding: EdgeInsets.only(top: 10),
child: Column(
children: [
SettingsPickerCell(
title: S.current.settings_fee_priority,
items: priorityForWalletType(_otherSettingsViewModel.walletType),
displayItem: _otherSettingsViewModel.getDisplayPriority,
selectedItem: _otherSettingsViewModel.transactionPriority,
onItemSelected: _otherSettingsViewModel.onDisplayPrioritySelected,
),
SettingsCellWithArrow(
title: S.current.settings_terms_and_conditions,
handler: (BuildContext context) =>
Navigator.of(context).pushNamed(Routes.readDisclaimer),
),
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
Spacer(),
SettingsVersionCell(
title: S.of(context).version(_otherSettingsViewModel.currentVersion))
],
),
SettingsCellWithArrow(
title: S.current.settings_terms_and_conditions,
handler: (BuildContext context) => Navigator.of(context).pushNamed(Routes.readDisclaimer),
),
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
Spacer(),
SettingsVersionCell(title: S.of(context).version(_otherSettingsViewModel.currentVersion))
]),
);
});
);
},
);
}
}

View file

@ -78,7 +78,7 @@ class SectionHeaderListRow extends StatelessWidget {
class StandardListSeparator extends StatelessWidget {
StandardListSeparator({this.padding, this.height = 1});
const StandardListSeparator({this.padding, this.height = 1});
final EdgeInsets? padding;
final double height;

View file

@ -2,10 +2,12 @@ import 'dart:io';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/entities/cake_2fa_preset_options.dart';
import 'package:cake_wallet/entities/background_tasks.dart';
import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/entities/pin_code_required_duration.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/entities/sort_balance_types.dart';
import 'package:cake_wallet/view_model/settings/sync_mode.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cw_core/transaction_priority.dart';
@ -34,7 +36,8 @@ class SettingsStore = SettingsStoreBase with _$SettingsStore;
abstract class SettingsStoreBase with Store {
SettingsStoreBase(
{required SharedPreferences sharedPreferences,
{required BackgroundTasks backgroundTasks,
required SharedPreferences sharedPreferences,
required bool initialShouldShowMarketPlaceInDashboard,
required FiatCurrency initialFiatCurrency,
required BalanceDisplayMode initialBalanceDisplayMode,
@ -51,6 +54,8 @@ abstract class SettingsStoreBase with Store {
required ThemeBase initialTheme,
required int initialPinLength,
required String initialLanguageCode,
required SyncMode initialSyncMode,
required bool initialSyncAll,
// required String initialCurrentLocale,
required this.appVersion,
required this.deviceName,
@ -78,6 +83,7 @@ abstract class SettingsStoreBase with Store {
TransactionPriority? initialEthereumTransactionPriority})
: nodes = ObservableMap<WalletType, Node>.of(nodes),
_sharedPreferences = sharedPreferences,
_backgroundTasks = backgroundTasks,
fiatCurrency = initialFiatCurrency,
balanceDisplayMode = initialBalanceDisplayMode,
shouldSaveRecipientAddress = initialSaveRecipientAddress,
@ -107,6 +113,8 @@ abstract class SettingsStoreBase with Store {
initialShouldRequireTOTP2FAForCreatingNewWallets,
shouldRequireTOTP2FAForAllSecurityAndBackupSettings =
initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings,
currentSyncMode = initialSyncMode,
currentSyncAll = initialSyncAll,
priority = ObservableMap<WalletType, TransactionPriority>() {
//this.nodes = ObservableMap<WalletType, Node>.of(nodes);
@ -287,6 +295,18 @@ abstract class SettingsStoreBase with Store {
(BalanceDisplayMode mode) => sharedPreferences.setInt(
PreferencesKey.currentBalanceDisplayModeKey, mode.serialize()));
reaction((_) => currentSyncMode, (SyncMode syncMode) {
sharedPreferences.setInt(PreferencesKey.syncModeKey, syncMode.type.index);
_backgroundTasks.registerSyncTask(changeExisting: true);
});
reaction((_) => currentSyncAll, (bool syncAll) {
sharedPreferences.setBool(PreferencesKey.syncAllKey, syncAll);
_backgroundTasks.registerSyncTask(changeExisting: true);
});
reaction(
(_) => exchangeStatus,
(ExchangeApiMode mode) =>
@ -422,11 +442,18 @@ abstract class SettingsStoreBase with Store {
@observable
bool useEtherscan;
@observable
SyncMode currentSyncMode;
@observable
bool currentSyncAll;
String appVersion;
String deviceName;
SharedPreferences _sharedPreferences;
final SharedPreferences _sharedPreferences;
final BackgroundTasks _backgroundTasks;
ObservableMap<WalletType, Node> nodes;
@ -455,6 +482,7 @@ abstract class SettingsStoreBase with Store {
BalanceDisplayMode initialBalanceDisplayMode = BalanceDisplayMode.availableBalance,
ThemeBase? initialTheme}) async {
final sharedPreferences = await getIt.getAsync<SharedPreferences>();
final backgroundTasks = getIt.get<BackgroundTasks>();
final currentFiatCurrency = FiatCurrency.deserialize(
raw: sharedPreferences.getString(PreferencesKey.currentFiatCurrencyKey)!);
@ -597,6 +625,11 @@ abstract class SettingsStoreBase with Store {
nodes[WalletType.ethereum] = ethereumNode;
}
final savedSyncMode = SyncMode.all.firstWhere((element) {
return element.type.index == (sharedPreferences.getInt(PreferencesKey.syncModeKey) ?? 1);
});
final savedSyncAll = sharedPreferences.getBool(PreferencesKey.syncAllKey) ?? true;
return SettingsStore(
sharedPreferences: sharedPreferences,
initialShouldShowMarketPlaceInDashboard: shouldShowMarketPlaceInDashboard,
@ -641,6 +674,9 @@ abstract class SettingsStoreBase with Store {
initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings:
shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
initialEthereumTransactionPriority: ethereumTransactionPriority,
backgroundTasks: backgroundTasks,
initialSyncMode: savedSyncMode,
initialSyncAll: savedSyncAll,
shouldShowYatPopup: shouldShowYatPopup);
}

View file

@ -1,6 +1,7 @@
import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/store/anonpay/anonpay_transactions_store.dart';
import 'package:cake_wallet/view_model/dashboard/anonpay_transaction_list_item.dart';
import 'package:cake_wallet/view_model/settings/sync_mode.dart';
import 'package:cake_wallet/wallet_type_utils.dart';
import 'package:cw_core/transaction_history.dart';
import 'package:cw_core/balance.dart';
@ -403,4 +404,16 @@ abstract class DashboardViewModelBase with Store {
hasBuyAction = !isHaven;
hasSellAction = !isHaven;
}
@computed
SyncMode get syncMode => settingsStore.currentSyncMode;
@action
void setSyncMode(SyncMode syncMode) => settingsStore.currentSyncMode = syncMode;
@computed
bool get syncAll => settingsStore.currentSyncAll;
@action
void setSyncAll(bool value) => settingsStore.currentSyncAll = value;
}

View file

@ -1,20 +1,19 @@
import 'package:flutter/foundation.dart';
import 'package:cake_wallet/view_model/settings/settings_list_item.dart';
import 'package:flutter/material.dart';
class ChoicesListItem<ItemType> extends SettingsListItem {
ChoicesListItem(
{required String title,
required this.selectedItem,
required this.items,
this.displayItem,
String Function(ItemType item)? displayItem,
void Function(ItemType item)? onItemSelected})
: _onItemSelected = onItemSelected,
_displayItem = displayItem,
super(title);
final ItemType selectedItem;
final List<ItemType> items;
final String Function(ItemType item)? displayItem;
final String Function(ItemType item)? _displayItem;
final void Function(ItemType item)? _onItemSelected;
void onItemSelected(dynamic item) {
@ -22,4 +21,11 @@ class ChoicesListItem<ItemType> extends SettingsListItem {
_onItemSelected?.call(item);
}
}
String displayItem(dynamic item) {
if (item is ItemType && _displayItem != null) {
return _displayItem!.call(item);
}
return item.toString();
}
}

View file

@ -0,0 +1,15 @@
enum SyncType { disabled, unobtrusive, aggressive }
class SyncMode {
SyncMode(this.name, this.type, this.frequency);
final String name;
final SyncType type;
final Duration frequency;
static final all = [
SyncMode("Disabled", SyncType.disabled, Duration.zero),
SyncMode("Unobtrusive", SyncType.unobtrusive, Duration(days: 1)),
SyncMode("Aggressive", SyncType.aggressive, Duration(hours: 6)),
];
}

View file

@ -1,4 +1,6 @@
import 'package:cake_wallet/core/wallet_creation_service.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/background_tasks.dart';
import 'package:cake_wallet/view_model/restore/restore_wallet.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
@ -71,6 +73,7 @@ abstract class WalletCreationVMBase with Store {
walletInfo.address = wallet.walletAddresses.address;
await _walletInfoSource.add(walletInfo);
_appStore.changeCurrentWallet(wallet);
getIt.get<BackgroundTasks>().registerSyncTask();
_appStore.authenticationStore.allowed();
state = ExecutedSuccessfullyState();
} catch (e) {

View file

@ -1,6 +1,5 @@
import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/core/wallet_loading_service.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/store/app_store.dart';
@ -58,8 +57,8 @@ abstract class WalletListViewModelBase with Store {
name: info.name,
type: info.type,
key: info.key,
isCurrent: info.name == _appStore.wallet!.name &&
info.type == _appStore.wallet!.type,
isCurrent: info.name == _appStore.wallet?.name &&
info.type == _appStore.wallet?.type,
isEnabled: availableWalletTypes.contains(info.type),
),
),

View file

@ -66,6 +66,7 @@ dependencies:
# check unorm_dart for usage and for replace
permission_handler: ^10.0.0
device_display_brightness: ^0.0.6
workmanager: ^0.5.1
platform_device_id: ^1.0.1
wakelock: ^0.6.2
flutter_mailer: ^2.0.2

View file

@ -667,5 +667,6 @@
"share": "يشارك",
"slidable": "قابل للانزلاق",
"etherscan_history": "Etherscan تاريخ",
"manage_nodes": "ﺪﻘﻌﻟﺍ ﺓﺭﺍﺩﺇ",
"template_name": "اسم القالب"
}

View file

@ -663,5 +663,6 @@
"share": "Дял",
"slidable": "Плъзгащ се",
"etherscan_history": "История на Etherscan",
"manage_nodes": "Управление на възли",
"template_name": "Име на шаблон"
}

View file

@ -662,6 +662,7 @@
"balance_page": "Stránka zůstatku",
"share": "Podíl",
"slidable": "Posuvné",
"manage_nodes": "Spravovat uzly",
"etherscan_history": "Historie Etherscanu",
"template_name": "Název šablony"
}

View file

@ -538,6 +538,8 @@
"open_gift_card": "Geschenkkarte öffnen",
"contact_support": "Support kontaktieren",
"gift_cards_unavailable": "Geschenkkarten können derzeit nur über Monero, Bitcoin und Litecoin erworben werden",
"background_sync_mode": "Hintergrundsynchronisierungsmodus",
"sync_all_wallets": "Alle Wallets synchronisieren",
"introducing_cake_pay": "Einführung von Cake Pay!",
"cake_pay_learn_more": "Kaufen und lösen Sie Geschenkkarten sofort in der App ein!\nWischen Sie von links nach rechts, um mehr zu erfahren.",
"automatic": "Automatisch",
@ -668,6 +670,7 @@
"balance_page": "Balance-Seite",
"share": "Aktie",
"slidable": "Verschiebbar",
"manage_nodes": "Knoten verwalten",
"etherscan_history": "Etherscan-Geschichte",
"template_name": "Vorlagenname"
}

View file

@ -538,6 +538,8 @@
"open_gift_card": "Open Gift Card",
"contact_support": "Contact Support",
"gift_cards_unavailable": "Gift cards are available for purchase only with Monero, Bitcoin, and Litecoin at this time",
"background_sync_mode": "Background sync mode",
"sync_all_wallets": "Sync all wallets",
"introducing_cake_pay": "Introducing Cake Pay!",
"cake_pay_learn_more": "Instantly purchase and redeem gift cards in the app!\nSwipe left to right to learn more.",
"automatic": "Automatic",
@ -668,6 +670,7 @@
"balance_page": "Balance Page",
"share": "Share",
"slidable": "Slidable",
"manage_nodes": "Manage nodes",
"etherscan_history": "Etherscan history",
"template_name": "Template Name"
}

View file

@ -538,6 +538,8 @@
"open_gift_card": "Abrir tarjeta de regalo",
"contact_support": "Contactar con Soporte",
"gift_cards_unavailable": "Las tarjetas de regalo están disponibles para comprar solo a través de Monero, Bitcoin y Litecoin en este momento",
"background_sync_mode": "Modo de sincronización en segundo plano",
"sync_all_wallets": "Sincronizar todas las billeteras",
"introducing_cake_pay": "¡Presentamos Cake Pay!",
"cake_pay_learn_more": "¡Compre y canjee tarjetas de regalo al instante en la aplicación!\nDeslice el dedo de izquierda a derecha para obtener más información.",
"automatic": "Automático",
@ -668,6 +670,7 @@
"balance_page": "Página de saldo",
"share": "Compartir",
"slidable": "deslizable",
"manage_nodes": "Administrar nodos",
"etherscan_history": "historia de etherscan",
"template_name": "Nombre de la plantilla"
}

View file

@ -536,8 +536,10 @@
"gift_card_is_generated": "La carte-cadeau est générée",
"open_gift_card": "Ouvrir la carte-cadeau",
"contact_support": "Contacter l'assistance",
"gift_cards": "Cartes-Cadeaux",
"gift_cards_unavailable": "Les cartes-cadeaux ne sont disponibles à l'achat que via Monero, Bitcoin et Litecoin pour le moment",
"background_sync_mode": "Mode de synchronisation en arrière-plan",
"sync_all_wallets": "Synchroniser tous les portefeuilles",
"gift_cards": "Cartes-Cadeaux",
"introducing_cake_pay": "Présentation de Cake Pay !",
"cake_pay_learn_more": "Achetez et utilisez instantanément des cartes-cadeaux dans l'application !\nBalayer de gauche à droite pour en savoir plus.",
"automatic": "Automatique",
@ -668,6 +670,7 @@
"balance_page": "Page Solde",
"share": "Partager",
"slidable": "Glissable",
"manage_nodes": "Gérer les nœuds",
"etherscan_history": "Historique d'Etherscan",
"template_name": "Nom du modèle"
}

View file

@ -649,5 +649,6 @@
"share": "Raba",
"slidable": "Mai iya zamewa",
"etherscan_history": "Etherscan tarihin kowane zamani",
"manage_nodes": "Sarrafa nodes",
"template_name": "Sunan Samfura"
}

View file

@ -538,6 +538,8 @@
"open_gift_card": "गिफ्ट कार्ड खोलें",
"contact_support": "सहायता से संपर्क करें",
"gift_cards_unavailable": "उपहार कार्ड इस समय केवल मोनेरो, बिटकॉइन और लिटकोइन के माध्यम से खरीदने के लिए उपलब्ध हैं",
"background_sync_mode": "बैकग्राउंड सिंक मोड",
"sync_all_wallets": "सभी वॉलेट सिंक करें",
"introducing_cake_pay": "परिचय Cake Pay!",
"cake_pay_learn_more": "ऐप में उपहार कार्ड तुरंत खरीदें और रिडीम करें!\nअधिक जानने के लिए बाएं से दाएं स्वाइप करें।",
"automatic": "स्वचालित",
@ -668,6 +670,7 @@
"balance_page": "बैलेंस पेज",
"share": "शेयर करना",
"slidable": "फिसलने लायक",
"manage_nodes": "नोड्स प्रबंधित करें",
"etherscan_history": "इथरस्कैन इतिहास",
"template_name": "टेम्पलेट नाम"
}

View file

@ -538,6 +538,8 @@
"open_gift_card": "Otvori darovnu karticu",
"contact_support": "Kontaktirajte podršku",
"gift_cards_unavailable": "Poklon kartice trenutno su dostupne za kupnju samo putem Monera, Bitcoina i Litecoina",
"background_sync_mode": "Sinkronizacija u pozadini",
"sync_all_wallets": "Sinkronizirajte sve novčanike",
"introducing_cake_pay": "Predstavljamo Cake Pay!",
"cake_pay_learn_more": "Azonnal vásárolhat és válthat be ajándékutalványokat az alkalmazásban!\nTovábbi információért csúsztassa balról jobbra az ujját.",
"automatic": "Automatski",
@ -668,6 +670,7 @@
"balance_page": "Stranica sa stanjem",
"share": "Udio",
"slidable": "Klizna",
"manage_nodes": "Upravljanje čvorovima",
"etherscan_history": "Etherscan povijest",
"template_name": "Naziv predloška"
}

View file

@ -658,6 +658,7 @@
"balance_page": "Halaman Saldo",
"share": "Membagikan",
"slidable": "Dapat digeser",
"manage_nodes": "Kelola node",
"etherscan_history": "Sejarah Etherscan",
"template_name": "Nama Templat"
}

View file

@ -538,6 +538,8 @@
"open_gift_card": "Apri carta regalo",
"contact_support": "Contatta l'assistenza",
"gift_cards_unavailable": "Le carte regalo sono disponibili per l'acquisto solo tramite Monero, Bitcoin e Litecoin in questo momento",
"background_sync_mode": "Modalità di sincronizzazione in background",
"sync_all_wallets": "Sincronizza tutti i portafogli",
"introducing_cake_pay": "Presentazione di Cake Pay!",
"cake_pay_learn_more": "Acquista e riscatta istantaneamente carte regalo nell'app!\nScorri da sinistra a destra per saperne di più.",
"automatic": "Automatico",
@ -668,6 +670,7 @@
"balance_page": "Pagina di equilibrio",
"share": "Condividere",
"slidable": "Scorrevole",
"manage_nodes": "Gestisci i nodi",
"etherscan_history": "Storia Etherscan",
"template_name": "Nome modello"
}

View file

@ -538,6 +538,8 @@
"open_gift_card": "オープンギフトカード",
"contact_support": "サポートに連絡する",
"gift_cards_unavailable": "現時点では、ギフトカードはMonero、Bitcoin、Litecoinからのみ購入できます。",
"background_sync_mode": "バックグラウンド同期モード",
"sync_all_wallets": "すべてのウォレットを同期",
"introducing_cake_pay": "序章Cake Pay",
"cake_pay_learn_more": "アプリですぐにギフトカードを購入して引き換えましょう!\n左から右にスワイプして詳細をご覧ください。",
"automatic": "自動",
@ -668,6 +670,7 @@
"balance_page": "残高ページ",
"share": "共有",
"slidable": "スライド可能",
"manage_nodes": "ノードの管理",
"etherscan_history": "イーサスキャンの歴史",
"template_name": "テンプレート名"
}

View file

@ -538,6 +538,8 @@
"open_gift_card": "기프트 카드 열기",
"contact_support": "지원팀에 문의",
"gift_cards_unavailable": "기프트 카드는 현재 Monero, Bitcoin 및 Litecoin을 통해서만 구매할 수 있습니다.",
"background_sync_mode": "백그라운드 동기화 모드",
"sync_all_wallets": "모든 지갑 동기화",
"introducing_cake_pay": "소개 Cake Pay!",
"cake_pay_learn_more": "앱에서 즉시 기프트 카드를 구매하고 사용하세요!\n자세히 알아보려면 왼쪽에서 오른쪽으로 스와이프하세요.",
"automatic": "자동적 인",
@ -668,6 +670,7 @@
"balance_page": "잔액 페이지",
"share": "공유하다",
"slidable": "슬라이딩 가능",
"manage_nodes": "노드 관리",
"etherscan_history": "이더스캔 역사",
"template_name": "템플릿 이름"
}

View file

@ -668,6 +668,7 @@
"balance_page": "လက်ကျန်စာမျက်နှာ",
"share": "မျှဝေပါ။",
"slidable": "လျှောချနိုင်သည်။",
"manage_nodes": "ဆုံမှတ်များကို စီမံပါ။",
"etherscan_history": "Etherscan သမိုင်း",
"template_name": "နမူနာပုံစံ"
}

View file

@ -538,6 +538,8 @@
"open_gift_card": "Geschenkkaart openen",
"contact_support": "Contact opnemen met ondersteuning",
"gift_cards_unavailable": "Cadeaubonnen kunnen momenteel alleen worden gekocht via Monero, Bitcoin en Litecoin",
"background_sync_mode": "Achtergrondsynchronisatiemodus",
"sync_all_wallets": "Alle portemonnees synchroniseren",
"introducing_cake_pay": "Introductie van Cake Pay!",
"cake_pay_learn_more": "Koop en wissel cadeaubonnen direct in de app in!\nSwipe van links naar rechts voor meer informatie.",
"automatic": "automatisch",
@ -668,6 +670,7 @@
"balance_page": "Saldo pagina",
"share": "Deel",
"slidable": "Verschuifbaar",
"manage_nodes": "Beheer knooppunten",
"etherscan_history": "Etherscan-geschiedenis",
"template_name": "Sjabloonnaam"
}

View file

@ -538,6 +538,8 @@
"open_gift_card": "Otwórz kartę podarunkową",
"contact_support": "Skontaktuj się z pomocą techniczną",
"gift_cards_unavailable": "Karty podarunkowe można obecnie kupić tylko za pośrednictwem Monero, Bitcoin i Litecoin",
"background_sync_mode": "Tryb synchronizacji w tle",
"sync_all_wallets": "Synchronizuj wszystkie portfele",
"introducing_cake_pay": "Przedstawiamy Cake Pay!",
"cake_pay_learn_more": "Kupuj i wykorzystuj karty podarunkowe od razu w aplikacji!\nPrzesuń od lewej do prawej, aby dowiedzieć się więcej.",
"automatic": "Automatyczny",
@ -668,6 +670,7 @@
"balance_page": "Strona salda",
"share": "Udział",
"slidable": "Przesuwne",
"manage_nodes": "Zarządzaj węzłami",
"etherscan_history": "Historia Etherscanu",
"template_name": "Nazwa szablonu"
}

View file

@ -537,6 +537,8 @@
"open_gift_card": "Abrir vale-presente",
"contact_support": "Contatar Suporte",
"gift_cards_unavailable": "Os cartões-presente estão disponíveis para compra apenas através do Monero, Bitcoin e Litecoin no momento",
"background_sync_mode": "Modo de sincronização em segundo plano",
"sync_all_wallets": "Sincronize todas as carteiras",
"introducing_cake_pay": "Apresentando o Cake Pay!",
"cake_pay_learn_more": "Compre e resgate vales-presente instantaneamente no app!\nDeslize da esquerda para a direita para saber mais.",
"automatic": "Automático",
@ -667,6 +669,7 @@
"balance_page": "Página de saldo",
"share": "Compartilhar",
"slidable": "Deslizável",
"manage_nodes": "Gerenciar nós",
"etherscan_history": "história Etherscan",
"template_name": "Nome do modelo"
}

View file

@ -539,6 +539,8 @@
"open_gift_card": "Открыть подарочную карту",
"contact_support": "Связаться со службой поддержки",
"gift_cards_unavailable": "В настоящее время подарочные карты можно приобрести только через Monero, Bitcoin и Litecoin.",
"background_sync_mode": "Режим фоновой синхронизации",
"sync_all_wallets": "Синхронизировать все кошельки",
"introducing_cake_pay": "Представляем Cake Pay!",
"cake_pay_learn_more": "Мгновенно покупайте и используйте подарочные карты в приложении!\nПроведите по экрану слева направо, чтобы узнать больше.",
"automatic": "автоматический",
@ -669,6 +671,7 @@
"balance_page": "Страница баланса",
"share": "Делиться",
"slidable": "Скользящий",
"manage_nodes": "Управление узлами",
"etherscan_history": "История Эфириума",
"template_name": "Имя Шаблона"
}

View file

@ -668,6 +668,7 @@
"balance_page": "หน้ายอดคงเหลือ",
"share": "แบ่งปัน",
"slidable": "เลื่อนได้",
"manage_nodes": "จัดการโหนด",
"etherscan_history": "ประวัติอีเธอร์สแกน",
"template_name": "ชื่อแม่แบบ"
}

View file

@ -631,7 +631,7 @@
"setup_2fa_text": "Cake 2FA, soğuk hava deposu kadar güvenli DEĞİLDİR. 2FA, siz uyurken arkadaşınızın parmak izinizi sağlaması gibi temel saldırı türlerine karşı koruma sağlar.\n\n Cake 2FA, gelişmiş bir saldırgan tarafından güvenliği ihlal edilmiş bir cihaza karşı koruma SAĞLAMAZ.\n\n 2FA kodlarınıza erişimi kaybederseniz , BU CÜZDANA ERİŞİMİNİZİ KAYBEDECEKSİNİZ. Mnemonic seed'den cüzdanınızı geri yüklemeniz gerekecek. BU NEDENLE HATIRLAYICI TOHUMLARINIZI YEDEKLEMELİSİNİZ! Ayrıca anımsatıcı tohumlarınıza erişimi olan biri, Cake 2FA'yı atlayarak paranızı çalabilir.\n\n Cake, anımsatıcı tohumlarınıza erişimi kaybederseniz size yardımcı olamaz, çünkü Cake bir saklama dışı cüzdan.",
"setup_totp_recommended": "TOTP'yi kurun (Önerilir)",
"disable_buy": "Satın alma işlemini devre dışı bırak",
"disable_sell": "Satış işlemini devre dışı bırak",
"disable_sell": "Satış işlemini devre dışı bırak",
"cake_2fa_preset" : "Kek 2FA Ön Ayarı",
"narrow": "Dar",
"normal": "Normal",
@ -669,6 +669,7 @@
"balance_page": "Bakiye Sayfası",
"share": "Paylaşmak",
"slidable": "kaydırılabilir",
"manage_nodes": "Düğümleri yönet",
"etherscan_history": "Etherscan geçmişi",
"template_name": "şablon adı"
}

View file

@ -538,6 +538,8 @@
"open_gift_card": "Відкрити подарункову картку",
"contact_support": "Звернутися до служби підтримки",
"gift_cards_unavailable": "Наразі подарункові картки можна придбати лише через Monero, Bitcoin і Litecoin",
"background_sync_mode": "Фоновий режим синхронізації",
"sync_all_wallets": "Синхронізувати всі гаманці",
"introducing_cake_pay": "Представляємо Cake Pay!",
"cake_pay_learn_more": "Миттєво купуйте та активуйте подарункові картки в додатку!\nПроведіть пальцем зліва направо, щоб дізнатися більше.",
"automatic": "Автоматичний",
@ -668,6 +670,7 @@
"balance_page": "Сторінка балансу",
"share": "Поділіться",
"slidable": "Розсувний",
"manage_nodes": "Керуйте вузлами",
"etherscan_history": "Історія Etherscan",
"template_name": "Назва шаблону"
}

View file

@ -662,6 +662,7 @@
"balance_page": "بیلنس صفحہ",
"share": "بانٹیں",
"slidable": "سلائیڈ ایبل",
"manage_nodes": "۔ﮟﯾﺮﮐ ﻢﻈﻧ ﺎﮐ ﺱﮈﻮﻧ",
"etherscan_history": "ﺦﯾﺭﺎﺗ ﯽﮐ ﻦﯿﮑﺳﺍ ﺮﮭﺘﯾﺍ",
"template_name": "ٹیمپلیٹ کا نام"
}

View file

@ -664,6 +664,7 @@
"balance_page": "Oju-iwe iwọntunwọnsi",
"share": "Pinpin",
"slidable": "Slidable",
"manage_nodes": "Ṣakoso awọn apa",
"etherscan_history": "Etherscan itan",
"template_name": "Orukọ Awoṣe"
}

View file

@ -537,6 +537,8 @@
"open_gift_card": "打开礼品卡",
"contact_support": "联系支持",
"gift_cards_unavailable": "目前只能通过门罗币、比特币和莱特币购买礼品卡",
"background_sync_mode": "后台同步模式",
"sync_all_wallets": "同步所有钱包",
"introducing_cake_pay": "介绍 Cake Pay!",
"cake_pay_learn_more": "立即在应用中购买和兑换礼品卡!\n从左向右滑动以了解详情。",
"automatic": "自动的",
@ -667,6 +669,7 @@
"balance_page": "余额页",
"share": "分享",
"slidable": "可滑动",
"manage_nodes": "管理节点",
"etherscan_history": "以太扫描历史",
"template_name": "模板名称"
}