mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-05-05 10:12:15 +00:00
CW-1000 Background sync improvements (#2142)
* feat: background sync improvements - dev options on ci build cherrypick - add permissions for background sync to AndroidManifestBase - enable desugaring + update java compatibility to 17 - update walletconnect_flutter_v2 - update ens_dart - update nostr_tools - add notification for new transactions found in background - expose more settings from flutter_daemon in UI - remove battery optimization setting when it's already disabled - fix notification permission handling - fix background sync last trigger saving - prevent notifications from being duplicated * potential fix for multiple notifications firing for the same tx * improve logging in background sync * ui improvements to ignore battery optimization popup * feat: logs for bg sync disable decred bgsync * fix: call store() directly to be sure that it is writing the data * chore: rename logs to background sync logs * Update lib/view_model/dashboard/dashboard_view_model.dart Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * chore: remove unused key --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
parent
e6c9cf54fb
commit
02e74b5997
48 changed files with 1373 additions and 68 deletions
android/app
cw_monero/lib
cw_shared_external/android
lib
pubspec_base.yamlres/values
strings_ar.arbstrings_bg.arbstrings_cs.arbstrings_de.arbstrings_en.arbstrings_es.arbstrings_fr.arbstrings_ha.arbstrings_hi.arbstrings_hr.arbstrings_hy.arbstrings_id.arbstrings_it.arbstrings_ja.arbstrings_ko.arbstrings_my.arbstrings_nl.arbstrings_pl.arbstrings_pt.arbstrings_ru.arbstrings_th.arbstrings_tl.arbstrings_tr.arbstrings_uk.arbstrings_ur.arbstrings_vi.arbstrings_yo.arbstrings_zh.arb
|
@ -42,6 +42,14 @@ android {
|
|||
disable 'InvalidPackage'
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
coreLibraryDesugaringEnabled true
|
||||
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
|
||||
namespace "com.cakewallet.cake_wallet"
|
||||
|
||||
defaultConfig {
|
||||
|
@ -91,6 +99,7 @@ dependencies {
|
|||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'androidx.test:runner:1.3.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5'
|
||||
}
|
||||
configurations {
|
||||
implementation.exclude module:'proto-google-common-protos'
|
||||
|
|
|
@ -24,6 +24,10 @@
|
|||
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
|
||||
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||
|
||||
<application
|
||||
android:name=".Application"
|
||||
|
@ -35,6 +39,10 @@
|
|||
android:versionName="__versionName__"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:extractNativeLibs="true">
|
||||
<service
|
||||
android:name="androidx.work.impl.foreground.SystemForegroundService"
|
||||
android:foregroundServiceType="dataSync"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:launchMode="singleInstance"
|
||||
|
|
|
@ -256,8 +256,9 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
Future<void> stopSync() async {
|
||||
if (isBackgroundSyncRunning) {
|
||||
printV("Stopping background sync");
|
||||
await save();
|
||||
monero.Wallet_store(wptr!);
|
||||
monero.Wallet_stopBackgroundSync(wptr!, '');
|
||||
monero_wallet.store();
|
||||
isBackgroundSyncRunning = false;
|
||||
}
|
||||
await save();
|
||||
|
@ -268,9 +269,9 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
Future<void> stopBackgroundSync(String password) async {
|
||||
if (isBackgroundSyncRunning) {
|
||||
printV("Stopping background sync");
|
||||
await save();
|
||||
monero.Wallet_store(wptr!);
|
||||
monero.Wallet_stopBackgroundSync(wptr!, password);
|
||||
await save();
|
||||
monero.Wallet_store(wptr!);
|
||||
isBackgroundSyncRunning = false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ buildscript {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.3.0'
|
||||
classpath 'com.android.tools.build:gradle:8.7.1'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,26 +1,101 @@
|
|||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cake_wallet/core/key_service.dart';
|
||||
import 'package:cake_wallet/core/wallet_loading_service.dart';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/entities/preferences_key.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cake_wallet/utils/feature_flag.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/sync_status.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class BackgroundSync {
|
||||
final FlutterLocalNotificationsPlugin _notificationsPlugin = FlutterLocalNotificationsPlugin();
|
||||
bool _isInitialized = false;
|
||||
|
||||
Future<void> _initializeNotifications() async {
|
||||
if (_isInitialized) return;
|
||||
|
||||
const androidSettings = AndroidInitializationSettings('@mipmap/ic_launcher');
|
||||
|
||||
const iosSettings = DarwinInitializationSettings(
|
||||
requestAlertPermission: true,
|
||||
requestBadgePermission: true,
|
||||
requestSoundPermission: true,
|
||||
);
|
||||
|
||||
const initializationSettings = InitializationSettings(
|
||||
android: androidSettings,
|
||||
iOS: iosSettings,
|
||||
);
|
||||
|
||||
await _notificationsPlugin.initialize(initializationSettings);
|
||||
_isInitialized = true;
|
||||
}
|
||||
|
||||
Future<bool> requestPermissions() async {
|
||||
if (Platform.isIOS || Platform.isMacOS) {
|
||||
return await _notificationsPlugin
|
||||
.resolvePlatformSpecificImplementation<IOSFlutterLocalNotificationsPlugin>()
|
||||
?.requestPermissions(
|
||||
alert: true,
|
||||
badge: true,
|
||||
sound: true,
|
||||
) ?? false;
|
||||
} else if (Platform.isAndroid) {
|
||||
return await _notificationsPlugin
|
||||
.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
|
||||
?.areNotificationsEnabled() ?? false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<void> showNotification(String title, String content) async {
|
||||
await _initializeNotifications();
|
||||
final hasPermission = await requestPermissions();
|
||||
|
||||
if (!hasPermission) {
|
||||
printV('Notification permissions not granted');
|
||||
return;
|
||||
}
|
||||
|
||||
const androidDetails = AndroidNotificationDetails(
|
||||
'transactions',
|
||||
'Transactions',
|
||||
channelDescription: 'Channel for notifications about transactions',
|
||||
importance: Importance.defaultImportance,
|
||||
priority: Priority.defaultPriority,
|
||||
);
|
||||
|
||||
const iosDetails = DarwinNotificationDetails();
|
||||
|
||||
const notificationDetails = NotificationDetails(
|
||||
android: androidDetails,
|
||||
iOS: iosDetails,
|
||||
);
|
||||
|
||||
await _notificationsPlugin.show(
|
||||
DateTime.now().millisecondsSinceEpoch.hashCode,
|
||||
title,
|
||||
content,
|
||||
notificationDetails,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> sync() async {
|
||||
printV("Background sync started");
|
||||
await _syncMonero();
|
||||
await _syncWallets();
|
||||
printV("Background sync completed");
|
||||
}
|
||||
|
||||
Future<void> _syncMonero() async {
|
||||
Future<void> _syncWallets() async {
|
||||
final walletLoadingService = getIt.get<WalletLoadingService>();
|
||||
final walletListViewModel = getIt.get<WalletListViewModel>();
|
||||
final settingsStore = getIt.get<SettingsStore>();
|
||||
|
@ -28,10 +103,10 @@ class BackgroundSync {
|
|||
|
||||
final List<WalletListItem> moneroWallets = walletListViewModel.wallets
|
||||
.where((element) => !element.isHardware)
|
||||
.where((element) => [WalletType.monero].contains(element.type))
|
||||
.where((element) => ![WalletType.haven, WalletType.decred].contains(element.type))
|
||||
.toList();
|
||||
for (int i = 0; i < moneroWallets.length; i++) {
|
||||
final wallet = await walletLoadingService.load(moneroWallets[i].type, moneroWallets[i].name);
|
||||
final wallet = await walletLoadingService.load(moneroWallets[i].type, moneroWallets[i].name, isBackground: true);
|
||||
int syncedTicks = 0;
|
||||
final keyService = getIt.get<KeyService>();
|
||||
|
||||
|
@ -75,7 +150,7 @@ class BackgroundSync {
|
|||
} else {
|
||||
syncedTicks = 0;
|
||||
}
|
||||
if (kDebugMode) {
|
||||
if (FeatureFlag.hasDevOptions) {
|
||||
if (syncStatus is SyncingSyncStatus) {
|
||||
final blocksLeft = syncStatus.blocksLeft;
|
||||
printV("$blocksLeft Blocks Left");
|
||||
|
@ -100,6 +175,27 @@ class BackgroundSync {
|
|||
}
|
||||
}
|
||||
}
|
||||
final txs = wallet.transactionHistory;
|
||||
final sortedTxs = txs.transactions.values.toList()..sort((a, b) => a.date.compareTo(b.date));
|
||||
final sharedPreferences = await SharedPreferences.getInstance();
|
||||
for (final tx in sortedTxs) {
|
||||
final lastTriggerString = sharedPreferences.getString(PreferencesKey.backgroundSyncLastTrigger(wallet.name));
|
||||
final lastTriggerDate = lastTriggerString != null
|
||||
? DateTime.parse(lastTriggerString)
|
||||
: DateTime.now();
|
||||
final keys = sharedPreferences.getKeys();
|
||||
if (tx.date.isBefore(lastTriggerDate)) {
|
||||
printV("w: ${wallet.name}, tx: ${tx.date} is before $lastTriggerDate (lastTriggerString: $lastTriggerString) (k: ${keys.length})");
|
||||
continue;
|
||||
}
|
||||
await sharedPreferences.setString(PreferencesKey.backgroundSyncLastTrigger(wallet.name), tx.date.add(Duration(minutes: 1)).toIso8601String());
|
||||
final action = tx.direction == TransactionDirection.incoming ? "Received" : "Sent";
|
||||
if (sharedPreferences.getBool(PreferencesKey.backgroundSyncNotificationsEnabled) ?? false) {
|
||||
await showNotification("$action ${wallet.currency.fullName} in ${wallet.name}", "${tx.amountFormatted()}");
|
||||
}
|
||||
printV("${wallet.currency.fullName} in ${wallet.name}: TX: ${tx.date} ${tx.amount} ${tx.direction}");
|
||||
}
|
||||
wallet.id;
|
||||
await wallet.stopBackgroundSync(await keyService.getWalletPassword(walletName: wallet.name));
|
||||
await wallet.close(shouldCleanup: true);
|
||||
}
|
||||
|
|
|
@ -52,8 +52,11 @@ class WalletLoadingService {
|
|||
}
|
||||
}
|
||||
|
||||
Future<WalletBase> load(WalletType type, String name, {String? password}) async {
|
||||
Future<WalletBase> load(WalletType type, String name, {String? password, bool isBackground = false}) async {
|
||||
try {
|
||||
if (!isBackground) {
|
||||
await sharedPreferences.setString(PreferencesKey.backgroundSyncLastTrigger(name), DateTime.now().toIso8601String());
|
||||
}
|
||||
final walletService = walletServiceFactory.call(type);
|
||||
final walletPassword = password ?? (await keyService.getWalletPassword(walletName: name));
|
||||
final wallet = await walletService.openWallet(name, walletPassword);
|
||||
|
|
35
lib/di.dart
35
lib/di.dart
|
@ -33,11 +33,13 @@ import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart';
|
|||
import 'package:cake_wallet/haven/cw_haven.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/monero_background_sync.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/moneroc_call_profiler.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/shared_preferences_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/background_sync_page.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/services/walletkit_service.dart';
|
||||
import 'package:cake_wallet/view_model/dev/monero_background_sync.dart';
|
||||
import 'package:cake_wallet/view_model/dev/shared_preferences.dart';
|
||||
import 'package:cake_wallet/view_model/link_view_model.dart';
|
||||
import 'package:cake_wallet/tron/tron.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart';
|
||||
|
@ -266,6 +268,8 @@ import 'buy/kryptonim/kryptonim.dart';
|
|||
import 'buy/meld/meld_buy_provider.dart';
|
||||
import 'src/screens/buy/buy_sell_page.dart';
|
||||
import 'cake_pay/cake_pay_payment_credantials.dart';
|
||||
import 'package:cake_wallet/view_model/dev/background_sync_logs_view_model.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/background_sync_logs_page.dart';
|
||||
|
||||
final getIt = GetIt.instance;
|
||||
|
||||
|
@ -883,9 +887,8 @@ Future<void> setup({
|
|||
nanoAccountCreationViewModel:
|
||||
getIt.get<NanoAccountEditOrCreateViewModel>(param1: account)));
|
||||
|
||||
getIt.registerFactory(() {
|
||||
return DisplaySettingsViewModel(getIt.get<SettingsStore>());
|
||||
});
|
||||
getIt.registerFactory(() =>
|
||||
DisplaySettingsViewModel(getIt.get<SettingsStore>()));
|
||||
|
||||
getIt.registerFactory(() =>
|
||||
SilentPaymentsSettingsViewModel(getIt.get<SettingsStore>(), getIt.get<AppStore>().wallet!));
|
||||
|
@ -893,22 +896,20 @@ Future<void> setup({
|
|||
getIt.registerFactory(
|
||||
() => MwebSettingsViewModel(getIt.get<SettingsStore>(), getIt.get<AppStore>().wallet!));
|
||||
|
||||
getIt.registerFactory(() {
|
||||
return PrivacySettingsViewModel(getIt.get<SettingsStore>(), getIt.get<AppStore>().wallet!);
|
||||
});
|
||||
getIt.registerFactory(() =>
|
||||
PrivacySettingsViewModel(getIt.get<SettingsStore>(), getIt.get<AppStore>().wallet!));
|
||||
|
||||
getIt.registerFactory(() => TrocadorExchangeProvider());
|
||||
|
||||
getIt.registerFactory(() => TrocadorProvidersViewModel(
|
||||
getIt.get<SettingsStore>(), getIt.get<TrocadorExchangeProvider>()));
|
||||
|
||||
getIt.registerFactory(() {
|
||||
return OtherSettingsViewModel(getIt.get<SettingsStore>(), getIt.get<AppStore>().wallet!,
|
||||
getIt.get<SendViewModel>());});
|
||||
getIt.registerFactory(() =>
|
||||
OtherSettingsViewModel(getIt.get<SettingsStore>(), getIt.get<AppStore>().wallet!,
|
||||
getIt.get<SendViewModel>()));
|
||||
|
||||
getIt.registerFactory(() {
|
||||
return SecuritySettingsViewModel(getIt.get<SettingsStore>());
|
||||
});
|
||||
getIt.registerFactory(() =>
|
||||
SecuritySettingsViewModel(getIt.get<SettingsStore>()));
|
||||
|
||||
getIt.registerFactory(() => WalletSeedViewModel(getIt.get<AppStore>().wallet!));
|
||||
|
||||
|
@ -916,6 +917,8 @@ Future<void> setup({
|
|||
|
||||
getIt.registerFactory(() => DevMoneroBackgroundSync(getIt.get<AppStore>().wallet!));
|
||||
|
||||
getIt.registerFactory(() => DevSharedPreferences());
|
||||
|
||||
getIt.registerFactoryParam<WalletSeedPage, bool, void>((bool isWalletCreated, _) =>
|
||||
WalletSeedPage(getIt.get<WalletSeedViewModel>(), isNewWalletCreated: isWalletCreated));
|
||||
|
||||
|
@ -1456,6 +1459,14 @@ Future<void> setup({
|
|||
getIt.registerFactory(() => SeedVerificationPage(getIt.get<WalletSeedViewModel>()));
|
||||
|
||||
getIt.registerFactory(() => DevMoneroBackgroundSyncPage(getIt.get<DevMoneroBackgroundSync>()));
|
||||
|
||||
getIt.registerFactory(() => DevMoneroCallProfilerPage());
|
||||
|
||||
getIt.registerFactory(() => DevSharedPreferencesPage(getIt.get<DevSharedPreferences>()));
|
||||
|
||||
getIt.registerFactory(() => BackgroundSyncLogsViewModel());
|
||||
|
||||
getIt.registerFactory(() => DevBackgroundSyncLogsPage(getIt.get<BackgroundSyncLogsViewModel>()));
|
||||
|
||||
_isSetupFinished = true;
|
||||
}
|
||||
|
|
|
@ -105,4 +105,6 @@ class PreferencesKey {
|
|||
static const walletConnectPairingTopicsList = 'wallet_connect_pairing_topics_list';
|
||||
static String walletConnectPairingTopicsListForWallet(String publicKey) =>
|
||||
'${PreferencesKey.walletConnectPairingTopicsList}_${publicKey}';
|
||||
static String backgroundSyncLastTrigger(String walletId) => 'background_sync_last_trigger_${walletId}';
|
||||
static const backgroundSyncNotificationsEnabled = 'background_sync_notifications_enabled';
|
||||
}
|
||||
|
|
|
@ -36,6 +36,8 @@ import 'package:cake_wallet/src/screens/dashboard/pages/transactions_page.dart';
|
|||
import 'package:cake_wallet/src/screens/dashboard/sign_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/monero_background_sync.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/moneroc_call_profiler.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/shared_preferences_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/background_sync_logs_page.dart';
|
||||
import 'package:cake_wallet/src/screens/disclaimer/disclaimer_page.dart';
|
||||
import 'package:cake_wallet/src/screens/exchange/exchange_page.dart';
|
||||
import 'package:cake_wallet/src/screens/exchange/exchange_template_page.dart';
|
||||
|
@ -836,6 +838,15 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
|||
return MaterialPageRoute<void>(
|
||||
builder: (_) => getIt.get<DevMoneroBackgroundSyncPage>(),
|
||||
);
|
||||
case Routes.devSharedPreferences:
|
||||
return MaterialPageRoute<void>(
|
||||
builder: (_) => getIt.get<DevSharedPreferencesPage>(),
|
||||
);
|
||||
|
||||
case Routes.devBackgroundSyncLogs:
|
||||
return MaterialPageRoute<void>(
|
||||
builder: (_) => getIt.get<DevBackgroundSyncLogsPage>(),
|
||||
);
|
||||
|
||||
case Routes.devMoneroCallProfiler:
|
||||
return MaterialPageRoute<void>(
|
||||
|
|
|
@ -111,8 +111,12 @@ class Routes {
|
|||
static const importNFTPage = '/import_nft_page';
|
||||
static const torPage = '/tor_page';
|
||||
static const backgroundSync = '/background_sync';
|
||||
|
||||
static const devMoneroBackgroundSync = '/dev/monero_background_sync';
|
||||
static const devMoneroCallProfiler = '/dev/monero_call_profiler';
|
||||
static const devSharedPreferences = '/dev/shared_preferences';
|
||||
static const devBackgroundSyncLogs = '/dev/background_sync_logs';
|
||||
|
||||
static const signPage = '/sign_page';
|
||||
static const connectDevices = '/device/connect';
|
||||
static const urqrAnimatedPage = '/urqr/animated_page';
|
||||
|
|
314
lib/src/screens/dev/background_sync_logs_page.dart
Normal file
314
lib/src/screens/dev/background_sync_logs_page.dart
Normal file
|
@ -0,0 +1,314 @@
|
|||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/view_model/dev/background_sync_logs_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class DevBackgroundSyncLogsPage extends BasePage {
|
||||
final BackgroundSyncLogsViewModel viewModel;
|
||||
|
||||
DevBackgroundSyncLogsPage(this.viewModel) {
|
||||
viewModel.loadLogs();
|
||||
}
|
||||
|
||||
@override
|
||||
String? get title => "[dev] background sync logs";
|
||||
|
||||
@override
|
||||
Widget? trailing(BuildContext context) {
|
||||
return IconButton(
|
||||
icon: Icon(Icons.refresh),
|
||||
onPressed: () => viewModel.loadLogs(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
return Observer(
|
||||
builder: (_) {
|
||||
if (viewModel.isLoading) {
|
||||
return Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
if (viewModel.error != null) {
|
||||
return Center(child: Text("Error: ${viewModel.error}"));
|
||||
}
|
||||
|
||||
if (viewModel.logData == null) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text("No logs loaded"),
|
||||
SizedBox(height: 16),
|
||||
ElevatedButton(
|
||||
onPressed: () => viewModel.loadLogs(),
|
||||
child: Text("Load Logs"),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return DefaultTabController(
|
||||
length: 2,
|
||||
child: Column(
|
||||
children: [
|
||||
TabBar(
|
||||
tabs: [
|
||||
Tab(text: "Logs (${viewModel.logs.length})"),
|
||||
Tab(text: "Sessions (${viewModel.sessions.length})"),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
children: [
|
||||
_buildLogsTab(context),
|
||||
_buildSessionsTab(context),
|
||||
],
|
||||
),
|
||||
),
|
||||
_buildActionButtons(context),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLogsTab(BuildContext context) {
|
||||
final logs = viewModel.logs;
|
||||
if (logs.isEmpty) {
|
||||
return Center(child: Text("No logs available"));
|
||||
}
|
||||
|
||||
final dateFormat = DateFormat('yyyy-MM-dd HH:mm:ss.SSS');
|
||||
|
||||
return ListView.builder(
|
||||
itemCount: logs.length,
|
||||
itemBuilder: (context, index) {
|
||||
final log = logs[index];
|
||||
return ListTile(
|
||||
title: Text(
|
||||
log.message,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontFamily: 'Monospace',
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
'${dateFormat.format(log.timestamp)} | ${log.level}' +
|
||||
(log.sessionId != null ? ' | Session: ${log.sessionId}' : ''),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: _getLevelColor(log.level),
|
||||
),
|
||||
),
|
||||
dense: true,
|
||||
onTap: () {
|
||||
Clipboard.setData(ClipboardData(text: log.message));
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Log message copied to clipboard')),
|
||||
);
|
||||
},
|
||||
onLongPress: () {
|
||||
Clipboard.setData(ClipboardData(
|
||||
text: '${dateFormat.format(log.timestamp)} [${log.level}] ${log.message}'));
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Full log entry copied to clipboard')),
|
||||
);
|
||||
},
|
||||
tileColor: index % 2 == 0 ? Colors.transparent : Colors.black.withOpacity(0.03),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSessionsTab(BuildContext context) {
|
||||
final sessions = viewModel.sessions;
|
||||
if (sessions.isEmpty) {
|
||||
return Center(child: Text("No sessions available"));
|
||||
}
|
||||
|
||||
final dateFormat = DateFormat('yyyy-MM-dd HH:mm:ss');
|
||||
|
||||
return ListView.builder(
|
||||
itemCount: sessions.length,
|
||||
itemBuilder: (context, index) {
|
||||
final session = sessions[index];
|
||||
final isActive = session.endTime == null;
|
||||
|
||||
return ExpansionTile(
|
||||
title: Text(
|
||||
session.name,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: isActive ? Colors.green : null,
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
'ID: ${session.id} | Started: ${dateFormat.format(session.startTime)}',
|
||||
style: TextStyle(fontSize: 12),
|
||||
),
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Start: ${session.startTime.toString()}'),
|
||||
if (session.endTime != null)
|
||||
Text('End: ${session.endTime.toString()}'),
|
||||
if (session.duration != null)
|
||||
Text('Duration: ${_formatDuration(session.duration!)}'),
|
||||
SizedBox(height: 8),
|
||||
_buildSessionLogs(context, session.id),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSessionLogs(BuildContext context, int sessionId) {
|
||||
final sessionLogs = viewModel.logs
|
||||
.where((log) => log.sessionId == sessionId)
|
||||
.toList();
|
||||
|
||||
if (sessionLogs.isEmpty) {
|
||||
return Text('No logs for this session');
|
||||
}
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Session Logs (${sessionLogs.length}):',
|
||||
style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
SizedBox(height: 8),
|
||||
Container(
|
||||
height: 200,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: ListView.builder(
|
||||
itemCount: sessionLogs.length,
|
||||
itemBuilder: (context, index) {
|
||||
final log = sessionLogs[index];
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
|
||||
child: Text(
|
||||
'[${log.level}] ${log.message}',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'Monospace',
|
||||
color: _getLevelColor(log.level),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildActionButtons(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
ElevatedButton.icon(
|
||||
icon: Icon(Icons.refresh),
|
||||
label: Text('Refresh'),
|
||||
onPressed: () => viewModel.loadLogs(),
|
||||
),
|
||||
ElevatedButton.icon(
|
||||
icon: Icon(Icons.copy),
|
||||
label: Text('Copy All'),
|
||||
onPressed: () => _copyAllLogs(context),
|
||||
),
|
||||
ElevatedButton.icon(
|
||||
icon: Icon(Icons.delete),
|
||||
label: Text('Clear'),
|
||||
onPressed: () => _confirmClearLogs(context),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.red,
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _copyAllLogs(BuildContext context) {
|
||||
if (viewModel.logData == null) return;
|
||||
|
||||
final buffer = StringBuffer();
|
||||
final dateFormat = DateFormat('yyyy-MM-dd HH:mm:ss.SSS');
|
||||
|
||||
for (final log in viewModel.logs) {
|
||||
buffer.writeln('${dateFormat.format(log.timestamp)} [${log.level}] ${log.message}');
|
||||
}
|
||||
|
||||
Clipboard.setData(ClipboardData(text: buffer.toString()));
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('All logs copied to clipboard')),
|
||||
);
|
||||
}
|
||||
|
||||
void _confirmClearLogs(BuildContext context) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text('Clear Logs'),
|
||||
content: Text('Are you sure you want to clear the logs display?'),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
child: Text('Cancel'),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
TextButton(
|
||||
child: Text('Clear'),
|
||||
style: TextButton.styleFrom(foregroundColor: Colors.red),
|
||||
onPressed: () {
|
||||
viewModel.clearLogs();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Color _getLevelColor(String level) {
|
||||
switch (level.toLowerCase()) {
|
||||
case 'error':
|
||||
return Colors.red;
|
||||
case 'warning':
|
||||
return Colors.orange;
|
||||
case 'info':
|
||||
return Colors.blue;
|
||||
case 'debug':
|
||||
return Colors.green;
|
||||
case 'trace':
|
||||
return Colors.purple;
|
||||
default:
|
||||
return Colors.grey;
|
||||
}
|
||||
}
|
||||
|
||||
String _formatDuration(Duration duration) {
|
||||
String twoDigits(int n) => n.toString().padLeft(2, '0');
|
||||
String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60));
|
||||
String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60));
|
||||
return '${twoDigits(duration.inHours)}:$twoDigitMinutes:$twoDigitSeconds';
|
||||
}
|
||||
}
|
404
lib/src/screens/dev/shared_preferences_page.dart
Normal file
404
lib/src/screens/dev/shared_preferences_page.dart
Normal file
|
@ -0,0 +1,404 @@
|
|||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/view_model/dev/shared_preferences.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
class DevSharedPreferencesPage extends BasePage {
|
||||
final DevSharedPreferences viewModel;
|
||||
|
||||
DevSharedPreferencesPage(this.viewModel);
|
||||
|
||||
@override
|
||||
String? get title => "[dev] shared preferences";
|
||||
|
||||
@override
|
||||
Widget? trailing(BuildContext context) {
|
||||
return IconButton(
|
||||
icon: Icon(Icons.add),
|
||||
onPressed: () => _showCreateDialog(context),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
return Observer(
|
||||
builder: (_) {
|
||||
if (viewModel.sharedPreferences == null) {
|
||||
return Center(child: Text("No shared preferences found"));
|
||||
}
|
||||
final keys = viewModel.keys;
|
||||
Map<String, dynamic> values = {};
|
||||
for (final key in keys) {
|
||||
values[key] = viewModel.get(key);
|
||||
}
|
||||
Map<String, PreferenceType> types = {};
|
||||
for (final key in keys) {
|
||||
types[key] = viewModel.getPreferenceType(key);
|
||||
}
|
||||
return ListView.builder(
|
||||
itemCount: keys.length,
|
||||
itemBuilder: (context, index) {
|
||||
final key = keys[index];
|
||||
final type = types[key]!;
|
||||
return ListTile(
|
||||
onTap: () {
|
||||
Clipboard.setData(ClipboardData(text: key + ": " + values[key].toString()));
|
||||
},
|
||||
onLongPress: () {
|
||||
_showEditDialog(context, key, type, values[key]);
|
||||
},
|
||||
title: switch (type) {
|
||||
PreferenceType.bool => Text(key, style: TextStyle(color: Colors.blue)),
|
||||
PreferenceType.int => Text(key, style: TextStyle(color: Colors.green)),
|
||||
PreferenceType.double => Text(key, style: TextStyle(color: Colors.yellow)),
|
||||
PreferenceType.listString => Text(key, style: TextStyle(color: Colors.purple)),
|
||||
PreferenceType.string => Text(key),
|
||||
PreferenceType.unknown => Text(key),
|
||||
},
|
||||
subtitle: switch (type) {
|
||||
PreferenceType.bool => Text("bool: ${values[key]}"),
|
||||
PreferenceType.int => Text("int: ${values[key]}"),
|
||||
PreferenceType.double => Text("double: ${values[key]}"),
|
||||
PreferenceType.listString => values[key].isEmpty as bool ? Text("listString: []") : Text("listString:\n- ${values[key].join("\n- ")}"),
|
||||
PreferenceType.string => Text("string: ${values[key]}"),
|
||||
PreferenceType.unknown => Text("UNKNOWN(${values[key].runtimeType}): ${values[key]}"),
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _showEditDialog(BuildContext context, String key, PreferenceType type, dynamic currentValue) {
|
||||
dynamic newValue = currentValue;
|
||||
bool isListString = type == PreferenceType.listString;
|
||||
List<String> listItems = isListString ? List<String>.from(currentValue as Iterable<dynamic>) : [];
|
||||
TextEditingController textController = TextEditingController(
|
||||
text: isListString ? '' : currentValue?.toString() ?? '');
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return StatefulBuilder(
|
||||
builder: (context, setState) {
|
||||
return AlertDialog(
|
||||
title: Text('Edit $key'),
|
||||
content: SizedBox(
|
||||
width: double.maxFinite,
|
||||
height: double.maxFinite,
|
||||
child: SingleChildScrollView(
|
||||
child: _buildDialogContent(
|
||||
type,
|
||||
newValue,
|
||||
listItems,
|
||||
textController,
|
||||
(value) => setState(() => newValue = value),
|
||||
(items) => setState(() => listItems = items),
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
child: Text('Delete'),
|
||||
style: TextButton.styleFrom(foregroundColor: Colors.red),
|
||||
onPressed: () {
|
||||
_showDeleteConfirmation(context, key);
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: Text('Cancel'),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
TextButton(
|
||||
child: Text('Save'),
|
||||
onPressed: () async {
|
||||
if (_validateAndUpdateValue(
|
||||
context,
|
||||
type,
|
||||
textController,
|
||||
listItems,
|
||||
(value) => newValue = value
|
||||
)) {
|
||||
await viewModel.set(key, type, newValue);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _showDeleteConfirmation(BuildContext context, String key) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text('Delete Preference'),
|
||||
content: Text('Are you sure you want to delete "$key"?'),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
child: Text('Cancel'),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
TextButton(
|
||||
child: Text('Delete'),
|
||||
style: TextButton.styleFrom(foregroundColor: Colors.red),
|
||||
onPressed: () {
|
||||
viewModel.delete(key);
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDialogContent(
|
||||
PreferenceType type,
|
||||
dynamic value,
|
||||
List<String> listItems,
|
||||
TextEditingController textController,
|
||||
Function(dynamic) onValueChanged,
|
||||
Function(List<String>) onListChanged,
|
||||
) {
|
||||
return switch (type) {
|
||||
PreferenceType.bool => _buildBoolEditor(value as bool, onValueChanged),
|
||||
PreferenceType.int => _buildNumberEditor(textController, 'Integer value', true),
|
||||
PreferenceType.double => _buildNumberEditor(textController, 'Double value', false),
|
||||
PreferenceType.string => _buildTextEditor(textController),
|
||||
PreferenceType.listString => _buildListEditor(listItems, textController, onListChanged),
|
||||
PreferenceType.unknown => Text('Cannot edit unknown type'),
|
||||
};
|
||||
}
|
||||
|
||||
Widget _buildBoolEditor(bool value, Function(bool) onChanged) {
|
||||
return CheckboxListTile(
|
||||
title: Text('Value'),
|
||||
value: value,
|
||||
onChanged: (newValue) {
|
||||
if (newValue != null) onChanged(newValue);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTextEditor(TextEditingController controller) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextField(
|
||||
controller: controller,
|
||||
decoration: InputDecoration(labelText: 'String value'),
|
||||
maxLines: null,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildNumberEditor(TextEditingController controller, String label, bool isInteger) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextField(
|
||||
controller: controller,
|
||||
decoration: InputDecoration(labelText: label),
|
||||
keyboardType: isInteger
|
||||
? TextInputType.number
|
||||
: TextInputType.numberWithOptions(decimal: true),
|
||||
inputFormatters: isInteger
|
||||
? [FilteringTextInputFormatter.digitsOnly]
|
||||
: [FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d*$'))],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildListEditor(
|
||||
List<String> items,
|
||||
TextEditingController controller,
|
||||
Function(List<String>) onListChanged,
|
||||
) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 200,
|
||||
child: ReorderableListView(
|
||||
shrinkWrap: true,
|
||||
children: [
|
||||
for (int i = 0; i < items.length; i++)
|
||||
ListTile(
|
||||
key: Key('$i'),
|
||||
title: Text(items[i]),
|
||||
trailing: IconButton(
|
||||
icon: Icon(Icons.delete),
|
||||
onPressed: () {
|
||||
final newList = List<String>.from(items);
|
||||
newList.removeAt(i);
|
||||
onListChanged(newList);
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
onReorder: (int oldIndex, int newIndex) {
|
||||
final newList = List<String>.from(items);
|
||||
if (oldIndex < newIndex) {
|
||||
newIndex -= 1;
|
||||
}
|
||||
final item = newList.removeAt(oldIndex);
|
||||
newList.insert(newIndex, item);
|
||||
onListChanged(newList);
|
||||
},
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: controller,
|
||||
decoration: InputDecoration(labelText: 'New item'),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.add),
|
||||
onPressed: () {
|
||||
if (controller.text.isNotEmpty) {
|
||||
final newList = List<String>.from(items);
|
||||
newList.add(controller.text);
|
||||
onListChanged(newList);
|
||||
controller.clear();
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
bool _validateAndUpdateValue(
|
||||
BuildContext context,
|
||||
PreferenceType type,
|
||||
TextEditingController controller,
|
||||
List<String> listItems,
|
||||
Function(dynamic) setNewValue,
|
||||
) {
|
||||
switch (type) {
|
||||
case PreferenceType.int:
|
||||
if (controller.text.isNotEmpty) {
|
||||
try {
|
||||
setNewValue(int.parse(controller.text));
|
||||
} catch (e) {
|
||||
_showErrorMessage(context, 'Invalid integer value');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PreferenceType.double:
|
||||
if (controller.text.isNotEmpty) {
|
||||
try {
|
||||
setNewValue(double.parse(controller.text));
|
||||
} catch (e) {
|
||||
_showErrorMessage(context, 'Invalid double value');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PreferenceType.string:
|
||||
setNewValue(controller.text);
|
||||
break;
|
||||
case PreferenceType.listString:
|
||||
setNewValue(listItems);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void _showErrorMessage(BuildContext context, String message) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(message)),
|
||||
);
|
||||
}
|
||||
|
||||
void _showCreateDialog(BuildContext context) {
|
||||
PreferenceType selectedType = PreferenceType.string;
|
||||
TextEditingController keyController = TextEditingController();
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return StatefulBuilder(
|
||||
builder: (context, setState) {
|
||||
return AlertDialog(
|
||||
title: Text('Create Preference'),
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextField(
|
||||
controller: keyController,
|
||||
decoration: InputDecoration(labelText: 'Preference Key'),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
DropdownButtonFormField<PreferenceType>(
|
||||
value: selectedType,
|
||||
decoration: InputDecoration(labelText: 'Type'),
|
||||
items: [
|
||||
DropdownMenuItem(value: PreferenceType.string, child: Text('String')),
|
||||
DropdownMenuItem(value: PreferenceType.bool, child: Text('Boolean')),
|
||||
DropdownMenuItem(value: PreferenceType.int, child: Text('Integer')),
|
||||
DropdownMenuItem(value: PreferenceType.double, child: Text('Double')),
|
||||
DropdownMenuItem(value: PreferenceType.listString, child: Text('List of Strings')),
|
||||
],
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
setState(() {
|
||||
selectedType = value;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
child: Text('Cancel'),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
TextButton(
|
||||
child: Text('Create'),
|
||||
onPressed: () {
|
||||
if (keyController.text.isEmpty) {
|
||||
_showErrorMessage(context, 'Key cannot be empty');
|
||||
return;
|
||||
}
|
||||
|
||||
viewModel.set(keyController.text, selectedType, switch (selectedType) {
|
||||
PreferenceType.bool => false,
|
||||
PreferenceType.int => 0,
|
||||
PreferenceType.double => 0.0,
|
||||
PreferenceType.string => '',
|
||||
PreferenceType.listString => [],
|
||||
PreferenceType.unknown => null,
|
||||
});
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -12,6 +12,7 @@ import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
|||
import 'package:cake_wallet/view_model/settings/sync_mode.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
class BackgroundSyncPage extends BasePage {
|
||||
BackgroundSyncPage(this.dashboardViewModel);
|
||||
|
@ -28,30 +29,30 @@ class BackgroundSyncPage extends BasePage {
|
|||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (dashboardViewModel.hasBatteryOptimization)
|
||||
Observer(builder: (context) {
|
||||
return SettingsSwitcherCell(
|
||||
title: S.current.unrestricted_background_service,
|
||||
value: !dashboardViewModel.batteryOptimizationEnabled,
|
||||
onValueChange: (_, bool value) {
|
||||
dashboardViewModel.disableBatteryOptimization();
|
||||
},
|
||||
);
|
||||
}),
|
||||
Observer(builder: (context) {
|
||||
return SettingsSwitcherCell(
|
||||
title: S.current.background_sync,
|
||||
value: dashboardViewModel.backgroundSyncEnabled,
|
||||
onValueChange: (dashboardViewModel.batteryOptimizationEnabled && dashboardViewModel.hasBatteryOptimization) ? (_, bool value) {
|
||||
unawaited(showPopUp(context: context, builder: (context) => AlertWithOneAction(
|
||||
alertTitle: S.current.background_sync,
|
||||
alertContent: S.current.unrestricted_background_service_notice,
|
||||
buttonText: S.current.ok,
|
||||
buttonAction: () => Navigator.of(context).pop(),
|
||||
)));
|
||||
} : (_, bool value) {
|
||||
onValueChange: (_, bool value) async {
|
||||
if (value) {
|
||||
dashboardViewModel.enableBackgroundSync();
|
||||
if (dashboardViewModel.batteryOptimizationEnabled) {
|
||||
await showPopUp(context: context, builder: (context) => AlertWithOneAction(
|
||||
alertTitle: S.current.background_sync,
|
||||
alertContent: S.current.unrestricted_background_service_notice,
|
||||
buttonText: S.current.ok,
|
||||
buttonAction: () => Navigator.of(context).pop(),
|
||||
));
|
||||
await dashboardViewModel.disableBatteryOptimization();
|
||||
for (var i = 0; i < 4 * 60; i++) {
|
||||
await Future.delayed(Duration(milliseconds: 250));
|
||||
if (!dashboardViewModel.batteryOptimizationEnabled) {
|
||||
await dashboardViewModel.enableBackgroundSync();
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dashboardViewModel.enableBackgroundSync();
|
||||
}
|
||||
} else {
|
||||
dashboardViewModel.disableBackgroundSync();
|
||||
}
|
||||
|
@ -68,22 +69,58 @@ class BackgroundSyncPage extends BasePage {
|
|||
dashboardViewModel.setSyncMode(syncMode);
|
||||
});
|
||||
}),
|
||||
|
||||
// Observer(builder: (context) {
|
||||
// return SettingsSwitcherCell(
|
||||
// title: S.current.background_sync_on_battery,
|
||||
// value: dashboardViewModel.backgroundSyncOnBattery,
|
||||
// onValueChange: (_, bool value) =>
|
||||
// dashboardViewModel.setBackgroundSyncOnBattery(value),
|
||||
// );
|
||||
// }),
|
||||
// Observer(builder: (context) {
|
||||
// return SettingsSwitcherCell(
|
||||
// title: S.current.background_sync_on_data,
|
||||
// value: dashboardViewModel.backgroundSyncOnData,
|
||||
// onValueChange: (_, bool value) => dashboardViewModel.setBackgroundSyncOnData(value),
|
||||
// );
|
||||
// }),
|
||||
if (dashboardViewModel.hasBgsyncNetworkConstraints)
|
||||
Observer(builder: (context) {
|
||||
return SettingsSwitcherCell(
|
||||
title: S.current.background_sync_on_unmetered_network,
|
||||
value: dashboardViewModel.backgroundSyncNetworkUnmetered,
|
||||
onValueChange: (_, bool value) => dashboardViewModel.setBackgroundSyncNetworkUnmetered(value),
|
||||
);
|
||||
}),
|
||||
if (dashboardViewModel.hasBgsyncBatteryNotLowConstraints)
|
||||
Observer(builder: (context) {
|
||||
return SettingsSwitcherCell(
|
||||
title: S.current.background_sync_on_battery_low,
|
||||
value: !dashboardViewModel.backgroundSyncBatteryNotLow,
|
||||
onValueChange: (_, bool value) => dashboardViewModel.setBackgroundSyncBatteryNotLow(!value),
|
||||
);
|
||||
}),
|
||||
if (dashboardViewModel.hasBgsyncChargingConstraints)
|
||||
Observer(builder: (context) {
|
||||
return SettingsSwitcherCell(
|
||||
title: S.current.background_sync_on_charging,
|
||||
value: dashboardViewModel.backgroundSyncCharging,
|
||||
onValueChange: (_, bool value) => dashboardViewModel.setBackgroundSyncCharging(value),
|
||||
);
|
||||
}),
|
||||
if (dashboardViewModel.hasBgsyncDeviceIdleConstraints)
|
||||
Observer(builder: (context) {
|
||||
return SettingsSwitcherCell(
|
||||
title: S.current.background_sync_on_device_idle,
|
||||
value: dashboardViewModel.backgroundSyncDeviceIdle,
|
||||
onValueChange: (_, bool value) => dashboardViewModel.setBackgroundSyncDeviceIdle(value),
|
||||
);
|
||||
}),
|
||||
Observer(builder: (context) {
|
||||
return SettingsSwitcherCell(
|
||||
title: S.current.new_transactions_notifications,
|
||||
value: dashboardViewModel.backgroundSyncNotificationsEnabled,
|
||||
onValueChange: (_, bool value) {
|
||||
try {
|
||||
dashboardViewModel.setBackgroundSyncNotificationsEnabled(value);
|
||||
} catch (e) {
|
||||
showPopUp(context: context, builder: (context) => AlertWithOneAction(
|
||||
alertTitle: S.current.error,
|
||||
alertContent: S.current.notification_permission_denied,
|
||||
buttonText: S.current.ok,
|
||||
buttonAction: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
));
|
||||
}
|
||||
},
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
@ -49,7 +49,7 @@ class ConnectionSyncPage extends BasePage {
|
|||
title: S.current.manage_nodes,
|
||||
handler: (context) => Navigator.of(context).pushNamed(Routes.manageNodes),
|
||||
),
|
||||
if (dashboardViewModel.hasBackgroundSync && Platform.isAndroid && FeatureFlag.isBackgroundSyncEnabled) ...[
|
||||
if (Platform.isAndroid && FeatureFlag.isBackgroundSyncEnabled) ...[
|
||||
SettingsCellWithArrow(
|
||||
title: S.current.background_sync,
|
||||
handler: (context) => Navigator.of(context).pushNamed(Routes.backgroundSync),
|
||||
|
|
|
@ -6,12 +6,10 @@ import 'package:cake_wallet/src/screens/base_page.dart';
|
|||
import 'package:cake_wallet/src/screens/settings/widgets/setting_priority_picker_cell.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/widgets/settings_version_cell.dart';
|
||||
import 'package:cake_wallet/utils/feature_flag.dart';
|
||||
import 'package:cake_wallet/view_model/settings/other_settings_view_model.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
|
@ -77,6 +75,18 @@ class OtherSettingsPage extends BasePage {
|
|||
handler: (BuildContext context) =>
|
||||
Navigator.of(context).pushNamed(Routes.devMoneroCallProfiler),
|
||||
),
|
||||
if (FeatureFlag.hasDevOptions)
|
||||
SettingsCellWithArrow(
|
||||
title: '[dev] shared preferences',
|
||||
handler: (BuildContext context) =>
|
||||
Navigator.of(context).pushNamed(Routes.devSharedPreferences),
|
||||
),
|
||||
if (FeatureFlag.hasDevOptions)
|
||||
SettingsCellWithArrow(
|
||||
title: '[dev] background sync logs',
|
||||
handler: (BuildContext context) =>
|
||||
Navigator.of(context).pushNamed(Routes.devBackgroundSyncLogs),
|
||||
),
|
||||
Spacer(),
|
||||
SettingsVersionCell(
|
||||
title: S.of(context).version(_otherSettingsViewModel.currentVersion)),
|
||||
|
|
|
@ -51,6 +51,7 @@ import 'package:flutter/services.dart';
|
|||
import 'package:flutter_daemon/flutter_daemon.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../../themes/theme_base.dart';
|
||||
|
@ -180,7 +181,7 @@ abstract class DashboardViewModelBase with Store {
|
|||
isShowThirdYatIntroduction = false;
|
||||
unawaited(isBackgroundSyncEnabled());
|
||||
unawaited(isBatteryOptimizationEnabled());
|
||||
|
||||
unawaited(_loadConstraints());
|
||||
final _wallet = wallet;
|
||||
|
||||
if (_wallet.type == WalletType.monero) {
|
||||
|
@ -536,6 +537,88 @@ abstract class DashboardViewModelBase with Store {
|
|||
return resp;
|
||||
}
|
||||
|
||||
@observable
|
||||
late bool backgroundSyncNotificationsEnabled = sharedPreferences.getBool(PreferencesKey.backgroundSyncNotificationsEnabled) ?? false;
|
||||
|
||||
@action
|
||||
Future<void> setBackgroundSyncNotificationsEnabled(bool value) async {
|
||||
if (!value) {
|
||||
backgroundSyncNotificationsEnabled = false;
|
||||
sharedPreferences.setBool(PreferencesKey.backgroundSyncNotificationsEnabled, false);
|
||||
return;
|
||||
}
|
||||
PermissionStatus permissionStatus = await Permission.notification.status;
|
||||
if (permissionStatus != PermissionStatus.granted) {
|
||||
final resp = await Permission.notification.request();
|
||||
if (resp == PermissionStatus.denied) {
|
||||
throw Exception("Notification permission denied");
|
||||
}
|
||||
}
|
||||
backgroundSyncNotificationsEnabled = value;
|
||||
await sharedPreferences.setBool(PreferencesKey.backgroundSyncNotificationsEnabled, value);
|
||||
}
|
||||
|
||||
|
||||
bool get hasBgsyncNetworkConstraints => Platform.isAndroid;
|
||||
bool get hasBgsyncBatteryNotLowConstraints => Platform.isAndroid;
|
||||
bool get hasBgsyncChargingConstraints => Platform.isAndroid;
|
||||
bool get hasBgsyncDeviceIdleConstraints => Platform.isAndroid;
|
||||
|
||||
@observable
|
||||
bool backgroundSyncNetworkUnmetered = false;
|
||||
|
||||
@observable
|
||||
bool backgroundSyncBatteryNotLow = false;
|
||||
|
||||
@observable
|
||||
bool backgroundSyncCharging = false;
|
||||
|
||||
@observable
|
||||
bool backgroundSyncDeviceIdle = false;
|
||||
|
||||
Future<void> _loadConstraints() async {
|
||||
backgroundSyncNetworkUnmetered = await FlutterDaemon().getNetworkType();
|
||||
backgroundSyncBatteryNotLow = await FlutterDaemon().getBatteryNotLow();
|
||||
backgroundSyncCharging = await FlutterDaemon().getRequiresCharging();
|
||||
backgroundSyncDeviceIdle = await FlutterDaemon().getDeviceIdle();
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> setBackgroundSyncNetworkUnmetered(bool value) async {
|
||||
backgroundSyncNetworkUnmetered = value;
|
||||
await FlutterDaemon().setNetworkType(value);
|
||||
if (await isBackgroundSyncEnabled()) {
|
||||
await enableBackgroundSync();
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> setBackgroundSyncBatteryNotLow(bool value) async {
|
||||
backgroundSyncBatteryNotLow = value;
|
||||
await FlutterDaemon().setBatteryNotLow(value);
|
||||
if (await isBackgroundSyncEnabled()) {
|
||||
await enableBackgroundSync();
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> setBackgroundSyncCharging(bool value) async {
|
||||
backgroundSyncCharging = value;
|
||||
await FlutterDaemon().setRequiresCharging(value);
|
||||
if (await isBackgroundSyncEnabled()) {
|
||||
await enableBackgroundSync();
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> setBackgroundSyncDeviceIdle(bool value) async {
|
||||
backgroundSyncDeviceIdle = value;
|
||||
await FlutterDaemon().setDeviceIdle(value);
|
||||
if (await isBackgroundSyncEnabled()) {
|
||||
await enableBackgroundSync();
|
||||
}
|
||||
}
|
||||
|
||||
bool get hasBatteryOptimization => Platform.isAndroid;
|
||||
|
||||
@observable
|
||||
|
|
44
lib/view_model/dev/background_sync_logs_view_model.dart
Normal file
44
lib/view_model/dev/background_sync_logs_view_model.dart
Normal file
|
@ -0,0 +1,44 @@
|
|||
import 'package:flutter_daemon/flutter_daemon.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'background_sync_logs_view_model.g.dart';
|
||||
class BackgroundSyncLogsViewModel = BackgroundSyncLogsViewModelBase with _$BackgroundSyncLogsViewModel;
|
||||
|
||||
abstract class BackgroundSyncLogsViewModelBase with Store {
|
||||
final FlutterDaemon _daemon = FlutterDaemon();
|
||||
|
||||
@observable
|
||||
LogData? logData;
|
||||
|
||||
@observable
|
||||
bool isLoading = false;
|
||||
|
||||
@observable
|
||||
String? error;
|
||||
|
||||
@computed
|
||||
List<LogEntry> get logs => logData?.logs ?? [];
|
||||
|
||||
@computed
|
||||
List<LogSession> get sessions => logData?.sessions ?? [];
|
||||
|
||||
@action
|
||||
Future<void> loadLogs() async {
|
||||
isLoading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
logData = await _daemon.getLogs();
|
||||
} catch (e) {
|
||||
error = e.toString();
|
||||
} finally {
|
||||
isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> clearLogs() async {
|
||||
await _daemon.clearLogs();
|
||||
await loadLogs();
|
||||
}
|
||||
}
|
92
lib/view_model/dev/shared_preferences.dart
Normal file
92
lib/view_model/dev/shared_preferences.dart
Normal file
|
@ -0,0 +1,92 @@
|
|||
import 'package:mobx/mobx.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
part 'shared_preferences.g.dart';
|
||||
|
||||
class DevSharedPreferences = DevSharedPreferencesBase with _$DevSharedPreferences;
|
||||
|
||||
enum PreferenceType {
|
||||
unknown,
|
||||
string,
|
||||
int,
|
||||
double,
|
||||
bool,
|
||||
listString
|
||||
}
|
||||
|
||||
abstract class DevSharedPreferencesBase with Store {
|
||||
DevSharedPreferencesBase() {
|
||||
SharedPreferences.getInstance().then((value) {
|
||||
sharedPreferences = value;
|
||||
});
|
||||
}
|
||||
|
||||
@observable
|
||||
SharedPreferences? sharedPreferences;
|
||||
|
||||
@computed
|
||||
List<String> get keys => (sharedPreferences?.getKeys().toList()?..sort()) ?? [];
|
||||
|
||||
@action
|
||||
Future<void> delete(String key) async {
|
||||
if (sharedPreferences == null) {
|
||||
return;
|
||||
}
|
||||
await sharedPreferences!.remove(key);
|
||||
}
|
||||
|
||||
dynamic get(String key) {
|
||||
if (sharedPreferences == null) {
|
||||
return null;
|
||||
}
|
||||
return sharedPreferences!.get(key);
|
||||
}
|
||||
|
||||
Future<void> set(String key, PreferenceType type, dynamic value) async {
|
||||
if (sharedPreferences == null) {
|
||||
return;
|
||||
}
|
||||
switch (type) {
|
||||
case PreferenceType.string:
|
||||
await sharedPreferences!.setString(key, value as String);
|
||||
break;
|
||||
case PreferenceType.bool:
|
||||
await sharedPreferences!.setBool(key, value as bool);
|
||||
break;
|
||||
case PreferenceType.int:
|
||||
await sharedPreferences!.setInt(key, value as int);
|
||||
break;
|
||||
case PreferenceType.double:
|
||||
await sharedPreferences!.setDouble(key, value as double);
|
||||
break;
|
||||
case PreferenceType.listString:
|
||||
await sharedPreferences!.setStringList(key, List<String>.from(value as Iterable<dynamic>));
|
||||
break;
|
||||
default:
|
||||
throw Exception("Unknown preference type: $type");
|
||||
}
|
||||
}
|
||||
|
||||
PreferenceType getPreferenceType(String key) {
|
||||
if (sharedPreferences == null) {
|
||||
return PreferenceType.unknown;
|
||||
}
|
||||
final value = sharedPreferences!.get(key);
|
||||
if (value is String) {
|
||||
return PreferenceType.string;
|
||||
}
|
||||
if (value is bool) {
|
||||
return PreferenceType.bool;
|
||||
}
|
||||
if (value is int) {
|
||||
return PreferenceType.int;
|
||||
}
|
||||
if (value is double) {
|
||||
return PreferenceType.double;
|
||||
}
|
||||
if (value is List<String>) {
|
||||
return PreferenceType.listString;
|
||||
}
|
||||
return PreferenceType.unknown;
|
||||
}
|
||||
}
|
|
@ -14,6 +14,7 @@ import 'package:cake_wallet/entities/evm_transaction_error_fees_handler.dart';
|
|||
import 'package:cake_wallet/entities/fiat_currency.dart';
|
||||
import 'package:cake_wallet/entities/parsed_address.dart';
|
||||
import 'package:cake_wallet/entities/template.dart';
|
||||
import 'package:cake_wallet/entities/preferences_key.dart';
|
||||
import 'package:cake_wallet/entities/transaction_description.dart';
|
||||
import 'package:cake_wallet/entities/wallet_contact.dart';
|
||||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||
|
@ -52,6 +53,7 @@ import 'package:cw_core/wallet_type.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
part 'send_view_model.g.dart';
|
||||
|
||||
|
@ -587,6 +589,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
|||
transactionNote: note,
|
||||
));
|
||||
}
|
||||
final sharedPreferences = await SharedPreferences.getInstance();
|
||||
await sharedPreferences.setString(PreferencesKey.backgroundSyncLastTrigger(wallet.name), DateTime.now().add(Duration(minutes: 1)).toIso8601String());
|
||||
|
||||
state = TransactionCommitted();
|
||||
} catch (e) {
|
||||
|
|
|
@ -93,8 +93,8 @@ dependencies:
|
|||
eth_sig_util: ^0.0.9
|
||||
ens_dart:
|
||||
git:
|
||||
url: https://github.com/cake-tech/ens_dart.git
|
||||
ref: main
|
||||
url: https://github.com/MrCyjaneK/ens_dart.git
|
||||
ref: 9fa09b9db69b8645d5d50a844652aa570451d101
|
||||
fluttertoast: 8.2.12
|
||||
# tor:
|
||||
# git:
|
||||
|
@ -103,7 +103,10 @@ dependencies:
|
|||
socks5_proxy: ^1.0.4
|
||||
flutter_svg: ^2.0.9
|
||||
polyseed: ^0.0.7
|
||||
nostr_tools: ^1.0.9
|
||||
nostr_tools:
|
||||
git:
|
||||
url: https://github.com/MrCyjaneK/nostr_tools.git
|
||||
ref: 089d5a2dd751429a040ba10fb24fcbae564053e5
|
||||
ledger_flutter_plus:
|
||||
git:
|
||||
url: https://github.com/vespr-wallet/ledger-flutter-plus
|
||||
|
@ -121,7 +124,8 @@ dependencies:
|
|||
flutter_daemon:
|
||||
git:
|
||||
url: https://github.com/MrCyjaneK/flutter_daemon
|
||||
ref: 5c369e0e69e6f459357b9802bc694a221397298a
|
||||
ref: 6d5270d64b5dd588fce12fd0a0c7314c37e6cff1
|
||||
flutter_local_notifications: ^19.0.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
|
@ -73,6 +73,10 @@
|
|||
"awaiting_payment_confirmation": "في انتظار تأكيد الدفع",
|
||||
"background_sync": "مزامنة الخلفية",
|
||||
"background_sync_mode": "وضع مزامنة الخلفية",
|
||||
"background_sync_on_battery_low": "تزامن على البطارية المنخفضة",
|
||||
"background_sync_on_charging": "تزامن فقط عند الشحن",
|
||||
"background_sync_on_device_idle": "تزامن فقط عند عدم استخدام الجهاز",
|
||||
"background_sync_on_unmetered_network": "تتطلب شبكة غير مستوفاة",
|
||||
"backup": "نسخ الاحتياطي",
|
||||
"backup_file": "ملف النسخ الاحتياطي",
|
||||
"backup_password": "كلمة مرور النسخ الاحتياطي",
|
||||
|
@ -483,6 +487,7 @@
|
|||
"new_subaddress_label_name": "تسمية",
|
||||
"new_subaddress_title": "عنوان جديد",
|
||||
"new_template": "قالب جديد",
|
||||
"new_transactions_notifications": "إرسال إشعارات حول المعاملات الجديدة",
|
||||
"new_wallet": "إنشاء محفظة جديدة",
|
||||
"newConnection": "ﺪﻳﺪﺟ ﻝﺎﺼﺗﺍ",
|
||||
"no_cards_found": "لم يتم العثور على بطاقات",
|
||||
|
@ -507,6 +512,7 @@
|
|||
"normal": "طبيعي",
|
||||
"note_optional": "ملاحظة (اختياري)",
|
||||
"note_tap_to_change": "ملاحظة (انقر للتغيير)",
|
||||
"notification_permission_denied": "تم رفض إذن الإخطار بشكل جيد ، يرجى تمكينه يدويًا في الإعدادات",
|
||||
"nullURIError": "ﻍﺭﺎﻓ (URI) ﻢﻈﺘﻨﻤﻟﺍ ﺩﺭﺍﻮﻤﻟﺍ ﻑﺮﻌﻣ",
|
||||
"offer_expires_in": "ينتهي العرض في:",
|
||||
"offline": "غير متصل على الانترنت",
|
||||
|
|
|
@ -73,6 +73,10 @@
|
|||
"awaiting_payment_confirmation": "Чака се потвърждение на плащането",
|
||||
"background_sync": "Фон Синхх",
|
||||
"background_sync_mode": "Режим на синхронизиране на фона",
|
||||
"background_sync_on_battery_low": "Синхронизирайте на ниска батерия",
|
||||
"background_sync_on_charging": "Синхронизирайте само при зареждане",
|
||||
"background_sync_on_device_idle": "Синхронизирайте само когато устройството не се използва",
|
||||
"background_sync_on_unmetered_network": "Изисквайте незадоволена мрежа",
|
||||
"backup": "Резервно копие",
|
||||
"backup_file": "Резервно копие",
|
||||
"backup_password": "Парола за възстановяване",
|
||||
|
@ -483,6 +487,7 @@
|
|||
"new_subaddress_label_name": "Име на Label",
|
||||
"new_subaddress_title": "Нов адрес",
|
||||
"new_template": "Нов шаблон",
|
||||
"new_transactions_notifications": "Изпратете известия за нови транзакции",
|
||||
"new_wallet": "Нов портфейл",
|
||||
"newConnection": "Нова връзка",
|
||||
"no_cards_found": "Не са намерени карти",
|
||||
|
@ -507,6 +512,7 @@
|
|||
"normal": "нормално",
|
||||
"note_optional": "Бележка (не е задължително)",
|
||||
"note_tap_to_change": "Бележка (натиснете за промяна)",
|
||||
"notification_permission_denied": "Разрешението за уведомяване е отказано, моля, моля, активирайте го в настройки",
|
||||
"nullURIError": "URI е нула",
|
||||
"offer_expires_in": "Предложението изтича след: ",
|
||||
"offline": "Офлайн",
|
||||
|
|
|
@ -73,6 +73,10 @@
|
|||
"awaiting_payment_confirmation": "Čeká se na potvrzení platby",
|
||||
"background_sync": "Synchronizace pozadí",
|
||||
"background_sync_mode": "Režim synchronizace pozadí",
|
||||
"background_sync_on_battery_low": "Synchronizace na nízké baterii",
|
||||
"background_sync_on_charging": "Synchronizovat pouze při nabíjení",
|
||||
"background_sync_on_device_idle": "Synchronizujte pouze tehdy, když se zařízení nepoužívá",
|
||||
"background_sync_on_unmetered_network": "Vyžadovat nemetrovou síť",
|
||||
"backup": "Záloha",
|
||||
"backup_file": "Soubor se zálohou",
|
||||
"backup_password": "Heslo pro zálohy",
|
||||
|
@ -483,6 +487,7 @@
|
|||
"new_subaddress_label_name": "Popisek",
|
||||
"new_subaddress_title": "Nová adresa",
|
||||
"new_template": "Nová šablona",
|
||||
"new_transactions_notifications": "Zašlete oznámení o nových transakcích",
|
||||
"new_wallet": "Nová peněženka",
|
||||
"newConnection": "Nové připojení",
|
||||
"no_cards_found": "Žádné karty nenalezeny",
|
||||
|
@ -507,6 +512,7 @@
|
|||
"normal": "Normální",
|
||||
"note_optional": "Poznámka (nepovinné)",
|
||||
"note_tap_to_change": "Poznámka (poklepáním upravit)",
|
||||
"notification_permission_denied": "Oznámení o oznámení bylo oprávněně zamítnuto, prosím ručně jej povolte v nastavení",
|
||||
"nullURIError": "URI je nulové",
|
||||
"offer_expires_in": "Nabídka vyprší: ",
|
||||
"offline": "Offline",
|
||||
|
|
|
@ -73,6 +73,10 @@
|
|||
"awaiting_payment_confirmation": "Warten auf Zahlungsbestätigung",
|
||||
"background_sync": "Hintergrundsynchronisation",
|
||||
"background_sync_mode": "Hintergrundsynchronisierungsmodus",
|
||||
"background_sync_on_battery_low": "Synchronisieren Sie einen niedrigen Akku",
|
||||
"background_sync_on_charging": "Nur beim Laden synchronisieren",
|
||||
"background_sync_on_device_idle": "Nur dann synchronisieren, wenn das Gerät nicht verwendet wird",
|
||||
"background_sync_on_unmetered_network": "Erfordern ein nicht modisches Netzwerk",
|
||||
"backup": "Sicherung",
|
||||
"backup_file": "Sicherungsdatei",
|
||||
"backup_password": "Passwort sichern",
|
||||
|
@ -483,6 +487,7 @@
|
|||
"new_subaddress_label_name": "Bezeichnung",
|
||||
"new_subaddress_title": "Neue Adresse",
|
||||
"new_template": "neue Vorlage",
|
||||
"new_transactions_notifications": "Senden Sie Benachrichtigungen über neue Transaktionen",
|
||||
"new_wallet": "Neue Wallet",
|
||||
"newConnection": "Neue Verbindung",
|
||||
"no_cards_found": "Keine Karten gefunden",
|
||||
|
@ -507,6 +512,7 @@
|
|||
"normal": "Normal",
|
||||
"note_optional": "Bemerkung (optional)",
|
||||
"note_tap_to_change": "Bemerkung (zum Ändern tippen)",
|
||||
"notification_permission_denied": "Die Benachrichtigungsgenehmigung wurde verweigert verweigert. Bitte ermöglichen Sie dies manuell in Einstellungen",
|
||||
"nullURIError": "URI ist null",
|
||||
"offer_expires_in": "Angebot läuft ab in: ",
|
||||
"offline": "offline",
|
||||
|
|
|
@ -73,6 +73,10 @@
|
|||
"awaiting_payment_confirmation": "Awaiting Payment Confirmation",
|
||||
"background_sync": "Background sync",
|
||||
"background_sync_mode": "Background sync mode",
|
||||
"background_sync_on_battery_low": "Synchronize on low battery",
|
||||
"background_sync_on_charging": "Synchronize only when charging",
|
||||
"background_sync_on_device_idle": "Synchronize only when device is not being used",
|
||||
"background_sync_on_unmetered_network": "Require unmetred network",
|
||||
"backup": "Backup",
|
||||
"backup_file": "Backup file",
|
||||
"backup_password": "Backup password",
|
||||
|
@ -483,6 +487,7 @@
|
|||
"new_subaddress_label_name": "Label name",
|
||||
"new_subaddress_title": "New address",
|
||||
"new_template": "New Template",
|
||||
"new_transactions_notifications": "Send notifications about new transactions",
|
||||
"new_wallet": "New Wallet",
|
||||
"newConnection": "New Connection",
|
||||
"no_cards_found": "No cards found",
|
||||
|
@ -507,6 +512,7 @@
|
|||
"normal": "Normal",
|
||||
"note_optional": "Note (optional)",
|
||||
"note_tap_to_change": "Note (tap to change)",
|
||||
"notification_permission_denied": "Notification permission got permamently denied, please manually enable it in settings",
|
||||
"nullURIError": "URI is null",
|
||||
"offer_expires_in": "Offer expires in: ",
|
||||
"offline": "Offline",
|
||||
|
|
|
@ -73,6 +73,10 @@
|
|||
"awaiting_payment_confirmation": "Esperando confirmación de pago",
|
||||
"background_sync": "Sincronización en segundo plano",
|
||||
"background_sync_mode": "Modo de sincronización en segundo plano",
|
||||
"background_sync_on_battery_low": "Sincronizar con batería baja",
|
||||
"background_sync_on_charging": "Sincronizar solo al cargar",
|
||||
"background_sync_on_device_idle": "Sincronizar solo cuando el dispositivo no se usa",
|
||||
"background_sync_on_unmetered_network": "Requerir una red no metida",
|
||||
"backup": "Apoyo",
|
||||
"backup_file": "Archivo de respaldo",
|
||||
"backup_password": "Contraseña de respaldo",
|
||||
|
@ -483,6 +487,7 @@
|
|||
"new_subaddress_label_name": "Nombre de etiqueta",
|
||||
"new_subaddress_title": "Nueva direccion",
|
||||
"new_template": "Nueva plantilla",
|
||||
"new_transactions_notifications": "Enviar notificaciones sobre nuevas transacciones",
|
||||
"new_wallet": "Nueva billetera",
|
||||
"newConnection": "Nueva conexión",
|
||||
"no_cards_found": "No se encuentran cartas",
|
||||
|
@ -507,6 +512,7 @@
|
|||
"normal": "Normal",
|
||||
"note_optional": "Nota (opcional)",
|
||||
"note_tap_to_change": "Nota (toque para cambiar)",
|
||||
"notification_permission_denied": "El permiso de notificación se negó de manera permanente, por favor, habilite manualmente en la configuración",
|
||||
"nullURIError": "URI es nula",
|
||||
"offer_expires_in": "Oferta expira en: ",
|
||||
"offline": "fuera de línea",
|
||||
|
|
|
@ -73,6 +73,10 @@
|
|||
"awaiting_payment_confirmation": "En attente de confirmation de paiement",
|
||||
"background_sync": "Synchronisation de fond",
|
||||
"background_sync_mode": "Mode de synchronisation en arrière-plan",
|
||||
"background_sync_on_battery_low": "Synchroniser sur une batterie basse",
|
||||
"background_sync_on_charging": "Synchroniser uniquement lors de la charge",
|
||||
"background_sync_on_device_idle": "Synchroniser uniquement lorsque l'appareil n'est pas utilisé",
|
||||
"background_sync_on_unmetered_network": "Exiger un réseau non métallique",
|
||||
"backup": "Sauvegarde",
|
||||
"backup_file": "Fichier de sauvegarde",
|
||||
"backup_password": "Mot de passe de sauvegarde",
|
||||
|
@ -483,6 +487,7 @@
|
|||
"new_subaddress_label_name": "Nom",
|
||||
"new_subaddress_title": "Nouvelle adresse",
|
||||
"new_template": "Nouveau Modèle",
|
||||
"new_transactions_notifications": "Envoyer des notifications sur les nouvelles transactions",
|
||||
"new_wallet": "Nouveau Portefeuille (Wallet)",
|
||||
"newConnection": "Nouvelle connexion",
|
||||
"no_cards_found": "Pas de cartes trouvées",
|
||||
|
@ -507,6 +512,7 @@
|
|||
"normal": "Normal",
|
||||
"note_optional": "Note (optionnelle)",
|
||||
"note_tap_to_change": "Note (appuyez pour changer)",
|
||||
"notification_permission_denied": "L'autorisation de notification a été refusée permanente, veuillez l'activer manuellement dans les paramètres",
|
||||
"nullURIError": "L'URI est nul",
|
||||
"offer_expires_in": "L'Offre expire dans: ",
|
||||
"offline": "Hors ligne",
|
||||
|
|
|
@ -73,6 +73,10 @@
|
|||
"awaiting_payment_confirmation": "Ana jiran Tabbacin Biyan Kuɗi",
|
||||
"background_sync": "Tunawa da Setc",
|
||||
"background_sync_mode": "Yanayin Sync",
|
||||
"background_sync_on_battery_low": "Aiki tare a kan baturin",
|
||||
"background_sync_on_charging": "Aiki tare kawai lokacin caji",
|
||||
"background_sync_on_device_idle": "Aiki tare kawai lokacin da ba a amfani da na'urar",
|
||||
"background_sync_on_unmetered_network": "Bukatar cibiyar sadarwar da ba ta dace ba",
|
||||
"backup": "Ajiyayyen",
|
||||
"backup_file": "Ajiyayyen fayil",
|
||||
"backup_password": "Ajiyayyen kalmar sirri",
|
||||
|
@ -483,6 +487,7 @@
|
|||
"new_subaddress_label_name": "Lakabin suna",
|
||||
"new_subaddress_title": "Adireshin sabuwa",
|
||||
"new_template": "Sabon Samfura",
|
||||
"new_transactions_notifications": "Aika sanarwa game da sababbin ma'amaloli",
|
||||
"new_wallet": "Sabuwar Wallet",
|
||||
"newConnection": "Sabuwar Haɗi",
|
||||
"no_cards_found": "Babu katunan da aka samo",
|
||||
|
@ -507,6 +512,7 @@
|
|||
"normal": "Na al'ada",
|
||||
"note_optional": "Bayani (optional)",
|
||||
"note_tap_to_change": "Bayani (tap don canja)",
|
||||
"notification_permission_denied": "Izinin sanarwar da aka samu an ƙaryata game da shi, don Allah a kunna shi a cikin saiti",
|
||||
"nullURIError": "URI banza ne",
|
||||
"offer_expires_in": "tayin zai ƙare a:",
|
||||
"offline": "Offline",
|
||||
|
|
|
@ -73,6 +73,10 @@
|
|||
"awaiting_payment_confirmation": "भुगतान की पुष्टि की प्रतीक्षा में",
|
||||
"background_sync": "पृष्ठभूमि सिंक",
|
||||
"background_sync_mode": "बैकग्राउंड सिंक मोड",
|
||||
"background_sync_on_battery_low": "कम बैटरी पर सिंक्रनाइज़ करें",
|
||||
"background_sync_on_charging": "चार्ज करते समय केवल सिंक्रनाइज़ करें",
|
||||
"background_sync_on_device_idle": "केवल तब सिंक्रनाइज़ करें जब डिवाइस का उपयोग नहीं किया जा रहा है",
|
||||
"background_sync_on_unmetered_network": "अनमेट्रेड नेटवर्क की आवश्यकता है",
|
||||
"backup": "बैकअप",
|
||||
"backup_file": "बैकअपफ़ाइल",
|
||||
"backup_password": "बैकअप पासवर्ड",
|
||||
|
@ -483,6 +487,7 @@
|
|||
"new_subaddress_label_name": "लेबल का नाम",
|
||||
"new_subaddress_title": "नया पता",
|
||||
"new_template": "नया टेम्पलेट",
|
||||
"new_transactions_notifications": "नए लेनदेन के बारे में सूचनाएं भेजें",
|
||||
"new_wallet": "नया बटुआ",
|
||||
"newConnection": "नया कनेक्शन",
|
||||
"no_cards_found": "कोई कार्ड नहीं मिला",
|
||||
|
@ -507,6 +512,7 @@
|
|||
"normal": "सामान्य",
|
||||
"note_optional": "नोट (वैकल्पिक)",
|
||||
"note_tap_to_change": "नोट (टैप टू चेंज)",
|
||||
"notification_permission_denied": "अधिसूचना की अनुमति को पारगम्य रूप से अस्वीकार कर दिया गया, कृपया इसे मैन्युअल रूप से सेटिंग्स में सक्षम करें",
|
||||
"nullURIError": "यूआरआई शून्य है",
|
||||
"offer_expires_in": "में ऑफर समाप्त हो रहा है: ",
|
||||
"offline": "ऑफ़लाइन",
|
||||
|
|
|
@ -73,6 +73,10 @@
|
|||
"awaiting_payment_confirmation": "Čeka se potvrda plaćanja",
|
||||
"background_sync": "Sinkronizacija pozadine",
|
||||
"background_sync_mode": "Sinkronizacija u pozadini",
|
||||
"background_sync_on_battery_low": "Sinkronizirati na niskoj bateriji",
|
||||
"background_sync_on_charging": "Sinkronizirati samo prilikom punjenja",
|
||||
"background_sync_on_device_idle": "Sinkronizirati samo kada se uređaj ne koristi",
|
||||
"background_sync_on_unmetered_network": "Zahtijevaju nezadovoljnu mrežu",
|
||||
"backup": "Sigurnosna kopija",
|
||||
"backup_file": "Sigurnosna kopija datoteke",
|
||||
"backup_password": "Lozinka za sigurnosnu kopiju",
|
||||
|
@ -483,6 +487,7 @@
|
|||
"new_subaddress_label_name": "Oznaka",
|
||||
"new_subaddress_title": "Nova adresa",
|
||||
"new_template": "novi predložak",
|
||||
"new_transactions_notifications": "Pošaljite obavijesti o novim transakcijama",
|
||||
"new_wallet": "Novi novčanik",
|
||||
"newConnection": "Nova veza",
|
||||
"no_cards_found": "Nisu pronađene kartice",
|
||||
|
@ -507,6 +512,7 @@
|
|||
"normal": "Normalno",
|
||||
"note_optional": "Poruka (nije obvezno)",
|
||||
"note_tap_to_change": "Poruka (dodirnite za promjenu)",
|
||||
"notification_permission_denied": "Dozvola za obavijest ostalo je odbijeno, molimo vas da ga ručno omogućite u postavkama",
|
||||
"nullURIError": "URI je nula",
|
||||
"offer_expires_in": "Ponuda istječe za: ",
|
||||
"offline": "izvan mreže",
|
||||
|
|
|
@ -73,6 +73,10 @@
|
|||
"awaiting_payment_confirmation": "Վճարման հաստատման սպասում",
|
||||
"background_sync": "Ֆոնային համաժամեցում",
|
||||
"background_sync_mode": "Հետին պլանի համաժամացման ռեժիմ",
|
||||
"background_sync_on_battery_low": "Համաժամեցրեք ցածր մարտկոցի վրա",
|
||||
"background_sync_on_charging": "Համաժամացրեք միայն լիցքավորելու ժամանակ",
|
||||
"background_sync_on_device_idle": "Համաժամացրեք միայն այն ժամանակ, երբ սարքը չի օգտագործվում",
|
||||
"background_sync_on_unmetered_network": "Պահանջում են չմշակված ցանց",
|
||||
"backup": "Կրկնօրինակ",
|
||||
"backup_file": "Կրկնօրինակի ֆայլ",
|
||||
"backup_password": "Կրկնօրինակի գաղտնաբառ",
|
||||
|
@ -483,6 +487,7 @@
|
|||
"new_subaddress_label_name": "Պիտակի անուն",
|
||||
"new_subaddress_title": "Նոր հասցե",
|
||||
"new_template": "Նոր նմուշ",
|
||||
"new_transactions_notifications": "Ուղարկեք ծանուցումներ նոր գործարքների վերաբերյալ",
|
||||
"new_wallet": "Նոր դրամապանակ",
|
||||
"newConnection": "Նոր կապ",
|
||||
"no_cards_found": "Ոչ մի քարտ չի գտնվել",
|
||||
|
@ -506,6 +511,7 @@
|
|||
"normal": "Նորմալ",
|
||||
"note_optional": "Նշում (ոչ պարտադիր)",
|
||||
"note_tap_to_change": "Նշում (սեղմեք փոխելու համար)",
|
||||
"notification_permission_denied": "Տեղեկացման թույլտվությունը թափանցում է, խնդրում ենք ձեռքով միացնել այն պարամետրերում",
|
||||
"nullURIError": "URI-ն դատարկ է",
|
||||
"offer_expires_in": "Առաջարկը վաղեմության է հասնում ",
|
||||
"offline": "Անցանց",
|
||||
|
|
|
@ -73,6 +73,10 @@
|
|||
"awaiting_payment_confirmation": "Menunggu Konfirmasi Pembayaran",
|
||||
"background_sync": "Sinkronisasi Latar Belakang",
|
||||
"background_sync_mode": "Mode Sinkronisasi Latar Belakang",
|
||||
"background_sync_on_battery_low": "Sinkronisasi pada baterai rendah",
|
||||
"background_sync_on_charging": "Menyinkronkan hanya saat pengisian",
|
||||
"background_sync_on_device_idle": "Menyinkronkan hanya jika perangkat tidak digunakan",
|
||||
"background_sync_on_unmetered_network": "Membutuhkan jaringan yang belum diproduksi",
|
||||
"backup": "Cadangan",
|
||||
"backup_file": "File cadangan",
|
||||
"backup_password": "Kata sandi cadangan",
|
||||
|
@ -483,6 +487,7 @@
|
|||
"new_subaddress_label_name": "Nama label",
|
||||
"new_subaddress_title": "Alamat baru",
|
||||
"new_template": "Template Baru",
|
||||
"new_transactions_notifications": "Kirim pemberitahuan tentang transaksi baru",
|
||||
"new_wallet": "Dompet Baru",
|
||||
"newConnection": "Koneksi Baru",
|
||||
"no_cards_found": "Tidak ada kartu yang ditemukan",
|
||||
|
@ -507,6 +512,7 @@
|
|||
"normal": "Normal",
|
||||
"note_optional": "Catatan (opsional)",
|
||||
"note_tap_to_change": "Catatan (tap untuk mengubah)",
|
||||
"notification_permission_denied": "Izin pemberitahuan ditolak secara permanen, mohon aktifkan secara manual dalam pengaturan",
|
||||
"nullURIError": "URI adalah nol",
|
||||
"offer_expires_in": "Penawaran kedaluwarsa dalam: ",
|
||||
"offline": "Offline",
|
||||
|
|
|
@ -73,6 +73,10 @@
|
|||
"awaiting_payment_confirmation": "In attesa di conferma del pagamento",
|
||||
"background_sync": "Sincronizzazione in background",
|
||||
"background_sync_mode": "Modalità di sincronizzazione in background",
|
||||
"background_sync_on_battery_low": "Sincronizza sulla batteria bassa",
|
||||
"background_sync_on_charging": "Sincronizzare solo quando si carica",
|
||||
"background_sync_on_device_idle": "Sincronizzare solo quando il dispositivo non viene utilizzato",
|
||||
"background_sync_on_unmetered_network": "Richiedono una rete non riservata",
|
||||
"backup": "Backup",
|
||||
"backup_file": "Backup file",
|
||||
"backup_password": "Backup password",
|
||||
|
@ -483,6 +487,7 @@
|
|||
"new_subaddress_label_name": "Nome etichetta",
|
||||
"new_subaddress_title": "Nuovo indirizzo",
|
||||
"new_template": "Nuovo modello",
|
||||
"new_transactions_notifications": "Invia notifiche su nuove transazioni",
|
||||
"new_wallet": "Nuovo portafoglio",
|
||||
"newConnection": "Nuova connessione",
|
||||
"no_cards_found": "Nessuna carta trovata",
|
||||
|
@ -507,6 +512,7 @@
|
|||
"normal": "Normale",
|
||||
"note_optional": "Nota (opzionale)",
|
||||
"note_tap_to_change": "Nota (clicca per cambiare)",
|
||||
"notification_permission_denied": "L'autorizzazione alla notifica è stata negato per via per via per via per via per via per via per via per via, per favore, abilitalo manualmente",
|
||||
"nullURIError": "L'URI è nullo",
|
||||
"offer_expires_in": "L'offerta termina tra: ",
|
||||
"offline": "Offline",
|
||||
|
|
|
@ -73,6 +73,10 @@
|
|||
"awaiting_payment_confirmation": "支払い確認を待っています",
|
||||
"background_sync": "背景同期",
|
||||
"background_sync_mode": "バックグラウンド同期モード",
|
||||
"background_sync_on_battery_low": "低いバッテリーで同期します",
|
||||
"background_sync_on_charging": "充電の場合にのみ同期します",
|
||||
"background_sync_on_device_idle": "デバイスが使用されていない場合にのみ同期します",
|
||||
"background_sync_on_unmetered_network": "未成年のネットワークが必要です",
|
||||
"backup": "バックアップ",
|
||||
"backup_file": "バックアップファイル",
|
||||
"backup_password": "バックアップパスワード",
|
||||
|
@ -484,6 +488,7 @@
|
|||
"new_subaddress_label_name": "ラベル名",
|
||||
"new_subaddress_title": "新しいアドレス",
|
||||
"new_template": "新しいテンプレート",
|
||||
"new_transactions_notifications": "新しいトランザクションに関する通知を送信します",
|
||||
"new_wallet": "新しいウォレット",
|
||||
"newConnection": "新しい接続",
|
||||
"no_cards_found": "カードは見つかりません",
|
||||
|
@ -508,6 +513,7 @@
|
|||
"normal": "普通",
|
||||
"note_optional": "注(オプション)",
|
||||
"note_tap_to_change": "注(タップして変更)",
|
||||
"notification_permission_denied": "通知の許可はまったく拒否されました。設定で手動で有効にしてください",
|
||||
"nullURIError": "URIがnullです",
|
||||
"offer_expires_in": "で有効期限が切れます: ",
|
||||
"offline": "オフライン",
|
||||
|
|
|
@ -73,6 +73,10 @@
|
|||
"awaiting_payment_confirmation": "결제 확인 대기 중",
|
||||
"background_sync": "배경 동기화",
|
||||
"background_sync_mode": "백그라운드 동기화 모드",
|
||||
"background_sync_on_battery_low": "낮은 배터리에서 동기화하십시오",
|
||||
"background_sync_on_charging": "충전 할 때만 동기화하십시오",
|
||||
"background_sync_on_device_idle": "장치를 사용하지 않을 때만 동기화하십시오",
|
||||
"background_sync_on_unmetered_network": "충족되지 않은 네트워크가 필요합니다",
|
||||
"backup": "지원",
|
||||
"backup_file": "백업 파일",
|
||||
"backup_password": "백업 비밀번호",
|
||||
|
@ -483,6 +487,7 @@
|
|||
"new_subaddress_label_name": "라벨 이름",
|
||||
"new_subaddress_title": "새 주소",
|
||||
"new_template": "새 템플릿",
|
||||
"new_transactions_notifications": "새로운 거래에 대한 알림을 보냅니다",
|
||||
"new_wallet": "새 월렛",
|
||||
"newConnection": "새로운 연결",
|
||||
"no_cards_found": "카드를 찾지 못했습니다",
|
||||
|
@ -507,6 +512,7 @@
|
|||
"normal": "정상",
|
||||
"note_optional": "참고 (선택 사항)",
|
||||
"note_tap_to_change": "메모 (변경하려면 탭하세요)",
|
||||
"notification_permission_denied": "알림 허가가 부패하게 거부되었습니다. 설정에서 수동으로 활성화하십시오.",
|
||||
"nullURIError": "URI가 null입니다.",
|
||||
"offer_expires_in": "쿠폰 만료일: ",
|
||||
"offline": "오프라인",
|
||||
|
|
|
@ -73,6 +73,10 @@
|
|||
"awaiting_payment_confirmation": "ငွေပေးချေမှု အတည်ပြုချက်ကို စောင့်မျှော်နေပါသည်။",
|
||||
"background_sync": "နောက်ခံထပ်တူပြုခြင်း",
|
||||
"background_sync_mode": "နောက်ခံထပ်တူပြုခြင်း mode ကို",
|
||||
"background_sync_on_battery_low": "အနိမ့်ဘက်ထရီအပေါ်တစ်ပြိုင်တည်းချိန်ကိုက်",
|
||||
"background_sync_on_charging": "အားသွင်းသည့်အခါသာထပ်တူပြုခြင်း",
|
||||
"background_sync_on_device_idle": "စက်ကိုအသုံးမပြုသည့်စက်ကိုသာတစ်ပြိုင်တည်းချိန်ကိုက်ပါ",
|
||||
"background_sync_on_unmetered_network": "unmetred ကွန်ယက်လိုအပ်သည်",
|
||||
"backup": "မိတ္တူ",
|
||||
"backup_file": "အရန်ဖိုင်",
|
||||
"backup_password": "စကားဝှက်ကို အရန်သိမ်းဆည်းပါ။",
|
||||
|
@ -483,6 +487,7 @@
|
|||
"new_subaddress_label_name": "အညွှန်းအမည်",
|
||||
"new_subaddress_title": "လိပ်စာအသစ်",
|
||||
"new_template": "ပုံစံအသစ်",
|
||||
"new_transactions_notifications": "အသစ်သောအရောင်းအဝယ်အကြောင်းသတိပေးချက်များပေးပို့ပါ",
|
||||
"new_wallet": "ပိုက်ဆံအိတ်အသစ်",
|
||||
"newConnection": "ချိတ်ဆက်မှုအသစ်",
|
||||
"no_cards_found": "ကဒ်များမရှိပါ",
|
||||
|
@ -507,6 +512,7 @@
|
|||
"normal": "ပုံမှန်",
|
||||
"note_optional": "မှတ်ချက် (ချန်လှပ်ထားနိုင်သည်)",
|
||||
"note_tap_to_change": "မှတ်ချက် (ပြောင်းလဲရန် တို့ပါ)",
|
||||
"notification_permission_denied": "အသိပေးချက်ခွင့်ပြုချက်ကိုအစက်အပြောက်ကိုငြင်းဆိုခဲ့သည်, ကျေးဇူးပြု. ၎င်းကိုချိန်ညှိချက်များတွင်လက်ဖြင့်ပြုလုပ်ပါ",
|
||||
"nullURIError": "URI သည် null ဖြစ်သည်။",
|
||||
"offer_expires_in": "ကမ်းလှမ်းချက် သက်တမ်းကုန်သည်:",
|
||||
"offline": "အော့ဖ်လိုင်း",
|
||||
|
|
|
@ -73,6 +73,10 @@
|
|||
"awaiting_payment_confirmation": "In afwachting van betalingsbevestiging",
|
||||
"background_sync": "Achtergrondsynchronisatie",
|
||||
"background_sync_mode": "Achtergrondsynchronisatiemodus",
|
||||
"background_sync_on_battery_low": "Synchroniseren op lage batterij",
|
||||
"background_sync_on_charging": "Synchroniseer alleen bij het opladen",
|
||||
"background_sync_on_device_idle": "Synchroniseer alleen wanneer het apparaat niet wordt gebruikt",
|
||||
"background_sync_on_unmetered_network": "Vereist een onvermekte netwerk",
|
||||
"backup": "Back-up",
|
||||
"backup_file": "Backup bestand",
|
||||
"backup_password": "Reservewachtwoord",
|
||||
|
@ -483,6 +487,7 @@
|
|||
"new_subaddress_label_name": "Label naam",
|
||||
"new_subaddress_title": "Nieuw adres",
|
||||
"new_template": "Nieuwe sjabloon",
|
||||
"new_transactions_notifications": "Stuur meldingen over nieuwe transacties",
|
||||
"new_wallet": "Nieuwe portemonnee",
|
||||
"newConnection": "Nieuwe verbinding",
|
||||
"no_cards_found": "Geen kaarten gevonden",
|
||||
|
@ -507,6 +512,7 @@
|
|||
"normal": "Normaal",
|
||||
"note_optional": "Opmerking (optioneel)",
|
||||
"note_tap_to_change": "Opmerking (tik om te wijzigen)",
|
||||
"notification_permission_denied": "Meldingstoestemming is permanent geweigerd, schakel het handmatig in instellingen in",
|
||||
"nullURIError": "URI is nul",
|
||||
"offer_expires_in": "Aanbieding verloopt over: ",
|
||||
"offline": "Offline",
|
||||
|
|
|
@ -73,6 +73,10 @@
|
|||
"awaiting_payment_confirmation": "Oczekiwanie na potwierdzenie płatności",
|
||||
"background_sync": "Synchronizacja w tle",
|
||||
"background_sync_mode": "Tryb synchronizacji w tle",
|
||||
"background_sync_on_battery_low": "Synchronizować na niskiej baterii",
|
||||
"background_sync_on_charging": "Synchronizować tylko podczas ładowania",
|
||||
"background_sync_on_device_idle": "Synchronizować tylko wtedy, gdy urządzenie nie jest używane",
|
||||
"background_sync_on_unmetered_network": "Wymagaj niezametrowanej sieci",
|
||||
"backup": "Kopia zapasowa",
|
||||
"backup_file": "Plik kopii zapasowej",
|
||||
"backup_password": "Hasło kpoii zapasowej",
|
||||
|
@ -483,6 +487,7 @@
|
|||
"new_subaddress_label_name": "Etykieta nazwy adresu",
|
||||
"new_subaddress_title": "Nowy adres",
|
||||
"new_template": "Nowy szablon",
|
||||
"new_transactions_notifications": "Wyślij powiadomienia o nowych transakcjach",
|
||||
"new_wallet": "Nowy portfel",
|
||||
"newConnection": "Nowe połączenie",
|
||||
"no_cards_found": "Nie znaleziono żadnych kart",
|
||||
|
@ -507,6 +512,7 @@
|
|||
"normal": "Normalna",
|
||||
"note_optional": "Notatka (opcjonalnie)",
|
||||
"note_tap_to_change": "Notatka (dotknij, aby zmienić)",
|
||||
"notification_permission_denied": "Zezwolenie na powiadomienie zostało odrzucone, prosimy ręcznie włączyć go w ustawieniach",
|
||||
"nullURIError": "URI ma wartość zerową",
|
||||
"offer_expires_in": "Oferta wygasa za ",
|
||||
"offline": "Offline",
|
||||
|
|
|
@ -73,6 +73,10 @@
|
|||
"awaiting_payment_confirmation": "Aguardando confirmação de pagamento",
|
||||
"background_sync": "Sincronização de fundo",
|
||||
"background_sync_mode": "Modo de sincronização em segundo plano",
|
||||
"background_sync_on_battery_low": "Sincronizar com bateria baixa",
|
||||
"background_sync_on_charging": "Sincronize apenas ao carregar",
|
||||
"background_sync_on_device_idle": "Sincronize apenas quando o dispositivo não está sendo usado",
|
||||
"background_sync_on_unmetered_network": "Requer rede não meta",
|
||||
"backup": "Cópia de segurança",
|
||||
"backup_file": "Arquivo de backup",
|
||||
"backup_password": "Senha de backup",
|
||||
|
@ -484,6 +488,7 @@
|
|||
"new_subaddress_label_name": "Nome",
|
||||
"new_subaddress_title": "Novo endereço",
|
||||
"new_template": "Novo modelo",
|
||||
"new_transactions_notifications": "Envie notificações sobre novas transações",
|
||||
"new_wallet": "Nova carteira",
|
||||
"newConnection": "Nova conexão",
|
||||
"no_cards_found": "Nenhum cartão encontrado",
|
||||
|
@ -508,6 +513,7 @@
|
|||
"normal": "Normal",
|
||||
"note_optional": "Nota (opcional)",
|
||||
"note_tap_to_change": "Nota (toque para alterar)",
|
||||
"notification_permission_denied": "A permissão de notificação foi negada com permamer, por favor, ativá -la manualmente em configurações",
|
||||
"nullURIError": "URI é nulo",
|
||||
"offer_expires_in": "A oferta expira em: ",
|
||||
"offline": "offline",
|
||||
|
|
|
@ -73,6 +73,10 @@
|
|||
"awaiting_payment_confirmation": "Ожидается подтверждения платежа",
|
||||
"background_sync": "Фоновая синхронизация",
|
||||
"background_sync_mode": "Режим фоновой синхронизации",
|
||||
"background_sync_on_battery_low": "Синхронизировать на низкой батареи",
|
||||
"background_sync_on_charging": "Синхронизировать только при зарядке",
|
||||
"background_sync_on_device_idle": "Синхронизировать только тогда, когда устройство не используется",
|
||||
"background_sync_on_unmetered_network": "Требуется незамеченная сеть",
|
||||
"backup": "Резервная копия",
|
||||
"backup_file": "Файл резервной копии",
|
||||
"backup_password": "Пароль резервной копии",
|
||||
|
@ -483,6 +487,7 @@
|
|||
"new_subaddress_label_name": "Имя",
|
||||
"new_subaddress_title": "Новый адрес",
|
||||
"new_template": "Новый шаблон",
|
||||
"new_transactions_notifications": "Отправить уведомления о новых транзакциях",
|
||||
"new_wallet": "Новый кошелёк",
|
||||
"newConnection": "Новое соединение",
|
||||
"no_cards_found": "Карт не найдено",
|
||||
|
@ -507,6 +512,7 @@
|
|||
"normal": "Нормальный",
|
||||
"note_optional": "Примечание (необязательно)",
|
||||
"note_tap_to_change": "Примечание (нажмите для изменения)",
|
||||
"notification_permission_denied": "Разрешение уведомления было отклонено, пожалуйста, вручную включить его в настройках",
|
||||
"nullURIError": "URI имеет значение null",
|
||||
"offer_expires_in": "Предложение истекает через: ",
|
||||
"offline": "Не в сети",
|
||||
|
|
|
@ -73,6 +73,10 @@
|
|||
"awaiting_payment_confirmation": "รอการยืนยันการชำระเงิน",
|
||||
"background_sync": "การซิงค์พื้นหลัง",
|
||||
"background_sync_mode": "โหมดซิงค์พื้นหลัง",
|
||||
"background_sync_on_battery_low": "ซิงโครไนซ์กับแบตเตอรี่ต่ำ",
|
||||
"background_sync_on_charging": "ซิงโครไนซ์เฉพาะเมื่อชาร์จ",
|
||||
"background_sync_on_device_idle": "ซิงโครไนซ์เฉพาะเมื่อไม่ใช้อุปกรณ์",
|
||||
"background_sync_on_unmetered_network": "ต้องการเครือข่ายที่ไม่ได้รับการแก้ไข",
|
||||
"backup": "สำรองข้อมูล",
|
||||
"backup_file": "ไฟล์สำรองข้อมูล",
|
||||
"backup_password": "รหัสผ่านสำรองข้อมูล",
|
||||
|
@ -483,6 +487,7 @@
|
|||
"new_subaddress_label_name": "ชื่อป้ายกำกับ",
|
||||
"new_subaddress_title": "ที่อยู่ใหม่",
|
||||
"new_template": "แม่แบบใหม่",
|
||||
"new_transactions_notifications": "ส่งการแจ้งเตือนเกี่ยวกับธุรกรรมใหม่",
|
||||
"new_wallet": "กระเป๋าใหม่",
|
||||
"newConnection": "การเชื่อมต่อใหม่",
|
||||
"no_cards_found": "ไม่พบการ์ด",
|
||||
|
@ -507,6 +512,7 @@
|
|||
"normal": "ปกติ",
|
||||
"note_optional": "บันทึก (ไม่จำเป็น)",
|
||||
"note_tap_to_change": "หมายเหตุ (กดเพื่อเปลี่ยน)",
|
||||
"notification_permission_denied": "การอนุญาตการแจ้งเตือนได้รับการปฏิเสธอย่างอนุญาตโปรดเปิดใช้งานด้วยตนเองในการตั้งค่า",
|
||||
"nullURIError": "URI เป็นโมฆะ",
|
||||
"offer_expires_in": "ข้อเสนอจะหมดอายุใน: ",
|
||||
"offline": "ออฟไลน์",
|
||||
|
|
|
@ -73,6 +73,10 @@
|
|||
"awaiting_payment_confirmation": "Nanghihintay ng Kumpirmasyon sa Pagbabayad",
|
||||
"background_sync": "Pag -sync ng background",
|
||||
"background_sync_mode": "Background sync mode",
|
||||
"background_sync_on_battery_low": "Mag -synchronize sa mababang baterya",
|
||||
"background_sync_on_charging": "Mag -synchronize lamang kapag singilin",
|
||||
"background_sync_on_device_idle": "Mag -synchronize lamang kapag hindi ginagamit ang aparato",
|
||||
"background_sync_on_unmetered_network": "Nangangailangan ng hindi natukoy na network",
|
||||
"backup": "Backup",
|
||||
"backup_file": "Backup na file",
|
||||
"backup_password": "Backup na password",
|
||||
|
@ -483,6 +487,7 @@
|
|||
"new_subaddress_label_name": "Pangalan ng label",
|
||||
"new_subaddress_title": "Bagong address",
|
||||
"new_template": "Bagong Template",
|
||||
"new_transactions_notifications": "Magpadala ng mga abiso tungkol sa mga bagong transaksyon",
|
||||
"new_wallet": "Bagong Wallet",
|
||||
"newConnection": "Bagong Koneksyon",
|
||||
"no_cards_found": "Walang nahanap na mga card",
|
||||
|
@ -507,6 +512,7 @@
|
|||
"normal": "Normal",
|
||||
"note_optional": "Tala (opsyonal)",
|
||||
"note_tap_to_change": "Tala (i-tap para baguhin)",
|
||||
"notification_permission_denied": "Ang pahintulot ng abiso ay pinahihintulutan na tumanggi, mangyaring manu -manong paganahin ito sa mga setting",
|
||||
"nullURIError": "Ang URI ay null",
|
||||
"offer_expires_in": "Mag-expire ang alok sa: ",
|
||||
"offline": "Offline",
|
||||
|
|
|
@ -73,6 +73,10 @@
|
|||
"awaiting_payment_confirmation": "Ödemenin onaylanması bekleniyor",
|
||||
"background_sync": "Arka plan senkronizasyonu",
|
||||
"background_sync_mode": "Arka Plan Senkronizasyon Modu",
|
||||
"background_sync_on_battery_low": "Düşük pille senkronize edin",
|
||||
"background_sync_on_charging": "Yalnızca şarj ederken senkronize edin",
|
||||
"background_sync_on_device_idle": "Yalnızca cihaz kullanılmadığında senkronize edin",
|
||||
"background_sync_on_unmetered_network": "Karşıdamlanmamış ağ gerektirir",
|
||||
"backup": "Yedek",
|
||||
"backup_file": "Yedek dosyası",
|
||||
"backup_password": "Yedek parolası",
|
||||
|
@ -483,6 +487,7 @@
|
|||
"new_subaddress_label_name": "Etiket ismi",
|
||||
"new_subaddress_title": "Yeni adres",
|
||||
"new_template": "Yeni Şablon",
|
||||
"new_transactions_notifications": "Yeni işlemler hakkında bildirimler gönderin",
|
||||
"new_wallet": "Yeni Cüzdan",
|
||||
"newConnection": "Yeni bağlantı",
|
||||
"no_cards_found": "Kart bulunamadı",
|
||||
|
@ -507,6 +512,7 @@
|
|||
"normal": "Normal",
|
||||
"note_optional": "Not (isteğe bağlı)",
|
||||
"note_tap_to_change": "Not (değiştirmek için dokunun)",
|
||||
"notification_permission_denied": "Bildirim izni perdence reddedildi, lütfen ayarlarda manuel olarak etkinleştirin",
|
||||
"nullURIError": "URI boş",
|
||||
"offer_expires_in": "Teklifin bitmesine kalan: ",
|
||||
"offline": "Çevrimdışı",
|
||||
|
|
|
@ -73,6 +73,10 @@
|
|||
"awaiting_payment_confirmation": "Очікується підтвердження платежу",
|
||||
"background_sync": "Фонове синхронізація",
|
||||
"background_sync_mode": "Фоновий режим синхронізації",
|
||||
"background_sync_on_battery_low": "Синхронізувати на низькому батареї",
|
||||
"background_sync_on_charging": "Синхронізуватися лише при зарядці",
|
||||
"background_sync_on_device_idle": "Синхронізувати лише тоді, коли пристрій не використовується",
|
||||
"background_sync_on_unmetered_network": "Вимагати незадоволеної мережі",
|
||||
"backup": "Резервна копія",
|
||||
"backup_file": "Файл резервної копії",
|
||||
"backup_password": "Пароль резервної копії",
|
||||
|
@ -483,6 +487,7 @@
|
|||
"new_subaddress_label_name": "Ім'я",
|
||||
"new_subaddress_title": "Нова адреса",
|
||||
"new_template": "Новий шаблон",
|
||||
"new_transactions_notifications": "Надішліть сповіщення про нові транзакції",
|
||||
"new_wallet": "Новий гаманець",
|
||||
"newConnection": "Нове підключення",
|
||||
"no_cards_found": "Карт не знайдено",
|
||||
|
@ -507,6 +512,7 @@
|
|||
"normal": "нормальний",
|
||||
"note_optional": "Примітка (необов’язково)",
|
||||
"note_tap_to_change": "Примітка (натисніть для зміни)",
|
||||
"notification_permission_denied": "Повідомлення дозволу отримали безперервно, будь ласка, вручну ввімкніть його в налаштуваннях",
|
||||
"nullURIError": "URI нульовий",
|
||||
"offer_expires_in": "Пропозиція закінчиться через: ",
|
||||
"offline": "Офлайн",
|
||||
|
|
|
@ -73,6 +73,10 @@
|
|||
"awaiting_payment_confirmation": "ادائیگی کی تصدیق کے منتظر",
|
||||
"background_sync": "پس منظر کی ہم آہنگی",
|
||||
"background_sync_mode": "پس منظر کی مطابقت پذیری کا موڈ",
|
||||
"background_sync_on_battery_low": "کم بیٹری پر ہم وقت سازی کریں",
|
||||
"background_sync_on_charging": "چارج کرتے وقت صرف ہم وقت سازی کریں",
|
||||
"background_sync_on_device_idle": "صرف اس وقت مطابقت پذیر کریں جب آلہ استعمال نہ ہو",
|
||||
"background_sync_on_unmetered_network": "بے ساختہ نیٹ ورک کی ضرورت ہے",
|
||||
"backup": "بیک اپ",
|
||||
"backup_file": "بیک اپ فائل",
|
||||
"backup_password": "بیک اپ پاس ورڈ",
|
||||
|
@ -483,6 +487,7 @@
|
|||
"new_subaddress_label_name": "لیبل کا نام",
|
||||
"new_subaddress_title": "نیا پتہ",
|
||||
"new_template": "نیا سانچہ",
|
||||
"new_transactions_notifications": "نئے لین دین کے بارے میں اطلاعات بھیجیں",
|
||||
"new_wallet": "نیا پرس",
|
||||
"newConnection": "ﻦﺸﮑﻨﮐ ﺎﯿﻧ",
|
||||
"no_cards_found": "کوئی کارڈ نہیں ملا",
|
||||
|
@ -507,6 +512,7 @@
|
|||
"normal": "نارمل",
|
||||
"note_optional": "نوٹ (اختیاری)",
|
||||
"note_tap_to_change": "نوٹ (تبدیل کرنے کے لیے تھپتھپائیں)",
|
||||
"notification_permission_denied": "نوٹیفکیشن کی اجازت کو یقینی طور پر انکار کردیا گیا ، براہ کرم اسے دستی طور پر ترتیبات میں اہل بنائیں",
|
||||
"nullURIError": "URI ۔ﮯﮨ ﻡﺪﻌﻟﺎﮐ",
|
||||
"offer_expires_in": "پیشکش کی میعاد اس وقت ختم ہو جاتی ہے:",
|
||||
"offline": "آف لائن",
|
||||
|
|
|
@ -73,6 +73,10 @@
|
|||
"awaiting_payment_confirmation": "Đang chờ xác nhận thanh toán",
|
||||
"background_sync": "Đồng bộ nền",
|
||||
"background_sync_mode": "Chế độ đồng bộ nền",
|
||||
"background_sync_on_battery_low": "Đồng bộ hóa trên pin thấp",
|
||||
"background_sync_on_charging": "Chỉ đồng bộ hóa khi sạc",
|
||||
"background_sync_on_device_idle": "Chỉ đồng bộ hóa khi thiết bị không được sử dụng",
|
||||
"background_sync_on_unmetered_network": "Yêu cầu mạng không được thiết kế",
|
||||
"backup": "Sao lưu",
|
||||
"backup_file": "Tập tin sao lưu",
|
||||
"backup_password": "Mật khẩu sao lưu",
|
||||
|
@ -482,6 +486,7 @@
|
|||
"new_subaddress_label_name": "Tên nhãn",
|
||||
"new_subaddress_title": "Địa chỉ mới",
|
||||
"new_template": "Mẫu mới",
|
||||
"new_transactions_notifications": "Gửi thông báo về các giao dịch mới",
|
||||
"new_wallet": "Ví mới",
|
||||
"newConnection": "Kết nối mới",
|
||||
"no_cards_found": "Không tìm thấy thẻ",
|
||||
|
@ -505,6 +510,7 @@
|
|||
"normal": "Bình thường",
|
||||
"note_optional": "Ghi chú (tùy chọn)",
|
||||
"note_tap_to_change": "Ghi chú (nhấn để thay đổi)",
|
||||
"notification_permission_denied": "Giấy phép thông báo bị từ chối, vui lòng bật thủ công nó trong cài đặt",
|
||||
"nullURIError": "URI là null",
|
||||
"offer_expires_in": "Ưu đãi hết hạn trong: ",
|
||||
"offline": "Ngoại tuyến",
|
||||
|
|
|
@ -73,6 +73,10 @@
|
|||
"awaiting_payment_confirmation": "À ń dúró de ìjẹ́rìísí àránṣẹ́",
|
||||
"background_sync": "Imuṣiṣẹ Labẹ",
|
||||
"background_sync_mode": "Ipo amuṣiṣẹpọ abẹlẹ",
|
||||
"background_sync_on_battery_low": "Muuṣiṣẹ lori batiri kekere",
|
||||
"background_sync_on_charging": "Muṣiṣẹpọ nikan nigbati gbigba agbara",
|
||||
"background_sync_on_device_idle": "Muṣiṣẹpọ nikan nigbati ẹrọ ko lo",
|
||||
"background_sync_on_unmetered_network": "Nilo nẹtiwọki ti ko ni aabo",
|
||||
"backup": "Ṣẹ̀dà",
|
||||
"backup_file": "Ṣẹ̀dà akọsílẹ̀",
|
||||
"backup_password": "Ṣẹ̀dà ọ̀rọ̀ aṣínà",
|
||||
|
@ -484,6 +488,7 @@
|
|||
"new_subaddress_label_name": "Orúkọ",
|
||||
"new_subaddress_title": "Àdírẹ́sì títun",
|
||||
"new_template": "Àwòṣe títun",
|
||||
"new_transactions_notifications": "Firanṣẹ awọn iwifunni nipa awọn iṣowo tuntun",
|
||||
"new_wallet": "Àpamọ́wọ́ títun",
|
||||
"newConnection": "Tuntun Asopọ",
|
||||
"no_cards_found": "Ko si awọn kaadi ti a rii",
|
||||
|
@ -508,6 +513,7 @@
|
|||
"normal": "Deede",
|
||||
"note_optional": "Àkọsílẹ̀ (ìyàn nìyí)",
|
||||
"note_tap_to_change": "Àkọsílẹ̀ (ẹ tẹ̀ láti pààrọ̀)",
|
||||
"notification_permission_denied": "Igbanilaaye iwifunni ni sẹsẹ sẹsẹ, jọwọ jẹ ki o mu ṣiṣẹ ni awọn eto",
|
||||
"nullURIError": "URI jẹ asan",
|
||||
"offer_expires_in": "Ìrònúdábàá máa gbẹ́mìí mì ní: ",
|
||||
"offline": "kò wà lórí ayélujára",
|
||||
|
|
|
@ -73,6 +73,10 @@
|
|||
"awaiting_payment_confirmation": "等待付款确认",
|
||||
"background_sync": "背景同步",
|
||||
"background_sync_mode": "后台同步模式",
|
||||
"background_sync_on_battery_low": "在低电池上同步",
|
||||
"background_sync_on_charging": "仅在充电时同步",
|
||||
"background_sync_on_device_idle": "仅在不使用设备时同步",
|
||||
"background_sync_on_unmetered_network": "需要未经许可的网络",
|
||||
"backup": "备份",
|
||||
"backup_file": "备份文件",
|
||||
"backup_password": "备份密码",
|
||||
|
@ -483,6 +487,7 @@
|
|||
"new_subaddress_label_name": "标签名称",
|
||||
"new_subaddress_title": "新地址",
|
||||
"new_template": "新模板",
|
||||
"new_transactions_notifications": "发送有关新交易的通知",
|
||||
"new_wallet": "新钱包",
|
||||
"newConnection": "新连接",
|
||||
"no_cards_found": "找不到卡",
|
||||
|
@ -507,6 +512,7 @@
|
|||
"normal": "普通的",
|
||||
"note_optional": "注释(可选)",
|
||||
"note_tap_to_change": "注释(轻按即可更改)",
|
||||
"notification_permission_denied": "通知许可被e opply拒绝,请在设置中手动启用它",
|
||||
"nullURIError": "URI 为空",
|
||||
"offer_expires_in": "优惠有效期至 ",
|
||||
"offline": "离线",
|
||||
|
|
Loading…
Reference in a new issue