mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-12-22 11:39:22 +00:00
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:
parent
68a057b91b
commit
aedf310c9d
48 changed files with 593 additions and 190 deletions
|
@ -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 (_) {}
|
||||
}
|
|
@ -606,4 +606,9 @@
|
|||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||
SystemCapabilities = {
|
||||
com.apple.BackgroundModes = {
|
||||
enabled = 1;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
164
lib/entities/background_tasks.dart
Normal file
164
lib/entities/background_tasks.dart
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
|
170
lib/main.dart
170
lib/main.dart
|
@ -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,
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
|
|
|
@ -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)),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
85
lib/src/screens/settings/manage_nodes_page.dart
Normal file
85
lib/src/screens/settings/manage_nodes_page.dart
Normal 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;
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
]),
|
||||
);
|
||||
});
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
15
lib/view_model/settings/sync_mode.dart
Normal file
15
lib/view_model/settings/sync_mode.dart
Normal 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)),
|
||||
];
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -667,5 +667,6 @@
|
|||
"share": "يشارك",
|
||||
"slidable": "قابل للانزلاق",
|
||||
"etherscan_history": "Etherscan تاريخ",
|
||||
"manage_nodes": "ﺪﻘﻌﻟﺍ ﺓﺭﺍﺩﺇ",
|
||||
"template_name": "اسم القالب"
|
||||
}
|
||||
|
|
|
@ -663,5 +663,6 @@
|
|||
"share": "Дял",
|
||||
"slidable": "Плъзгащ се",
|
||||
"etherscan_history": "История на Etherscan",
|
||||
"manage_nodes": "Управление на възли",
|
||||
"template_name": "Име на шаблон"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -649,5 +649,6 @@
|
|||
"share": "Raba",
|
||||
"slidable": "Mai iya zamewa",
|
||||
"etherscan_history": "Etherscan tarihin kowane zamani",
|
||||
"manage_nodes": "Sarrafa nodes",
|
||||
"template_name": "Sunan Samfura"
|
||||
}
|
||||
|
|
|
@ -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": "टेम्पलेट नाम"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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": "テンプレート名"
|
||||
}
|
||||
|
|
|
@ -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": "템플릿 이름"
|
||||
}
|
||||
|
|
|
@ -668,6 +668,7 @@
|
|||
"balance_page": "လက်ကျန်စာမျက်နှာ",
|
||||
"share": "မျှဝေပါ။",
|
||||
"slidable": "လျှောချနိုင်သည်။",
|
||||
"manage_nodes": "ဆုံမှတ်များကို စီမံပါ။",
|
||||
"etherscan_history": "Etherscan သမိုင်း",
|
||||
"template_name": "နမူနာပုံစံ"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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": "Имя Шаблона"
|
||||
}
|
||||
|
|
|
@ -668,6 +668,7 @@
|
|||
"balance_page": "หน้ายอดคงเหลือ",
|
||||
"share": "แบ่งปัน",
|
||||
"slidable": "เลื่อนได้",
|
||||
"manage_nodes": "จัดการโหนด",
|
||||
"etherscan_history": "ประวัติอีเธอร์สแกน",
|
||||
"template_name": "ชื่อแม่แบบ"
|
||||
}
|
||||
|
|
|
@ -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ı"
|
||||
}
|
||||
|
|
|
@ -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": "Назва шаблону"
|
||||
}
|
||||
|
|
|
@ -662,6 +662,7 @@
|
|||
"balance_page": "بیلنس صفحہ",
|
||||
"share": "بانٹیں",
|
||||
"slidable": "سلائیڈ ایبل",
|
||||
"manage_nodes": "۔ﮟﯾﺮﮐ ﻢﻈﻧ ﺎﮐ ﺱﮈﻮﻧ",
|
||||
"etherscan_history": "ﺦﯾﺭﺎﺗ ﯽﮐ ﻦﯿﮑﺳﺍ ﺮﮭﺘﯾﺍ",
|
||||
"template_name": "ٹیمپلیٹ کا نام"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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": "模板名称"
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue