Implement background sync for xmr using flutter_daemon (#2094)

* Implement background sync for xmr using flutter_daemon * - initialize app config in background thread - initializeAppConfigs without loading the wallet. * - properly do awaited calls in methodChannel - prevent locking main thread during background sync * add back background sync debug page fix issues caused by xmr wallet being view only (and read only) * changes from review improve starting of bgsync task * update stopBackgroundSync, await listener functions, ensure that listener always start (call _start in constructor) * DO-NOT-MERGE: extre verbose monero logs * stop background service when app is being opened * improve monitoring of background sync * update flutter_daemon to ensure network constraint prevent throwing errors on isBackgroundSyncEnabled check network before syncing * Update lib/main.dart * revert Update main.dart [skip ci] * continously run network check * disable charging requirement, fix status reporting of background sync in UI * Refactor background sync logic, and add UI notifications for battery optimization. Updated flutter_daemon version modified build.gradle for signing config to allow testing in both release and debug modes. * verbose monero only when requested in code. Do not start background sync when battery optimization is on * fix background sync mode not properly reflecting state changes * drop unnecessary dependency --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

cyan committed Mar 21, 2025 at 18:22 UTC 686580ff7872745e8319e9b853fc6a065f8995d8
60 files changed +853 -352
android/app/build.gradle
+4 -1
@@ -37,7 +37,7 @@ if (appPropertiesFile.exists()) {
37 }
38
39 android {
40 - compileSdkVersion 34
40 + compileSdkVersion 35
41 buildToolsVersion "34.0.0"
42
43 lintOptions {
@@ -81,6 +81,9 @@ android {
81
82 proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
83 }
84 + debug {
85 + signingConfig signingConfigs.release
86 + }
87 }
88
89 ndkVersion "27.0.12077973"
cw_core/lib/set_app_secure_native.dart
+2 -2
@@ -1,9 +1,9 @@
1 import 'package:flutter/services.dart';
2
3 -void setIsAppSecureNative(bool isAppSecure) {
3 +Future<void> setIsAppSecureNative(bool isAppSecure) async {
4 try {
5 final utils = const MethodChannel('com.cake_wallet/native_utils');
6
7 - utils.invokeMethod<Uint8List>('setIsAppSecure', {'isAppSecure': isAppSecure});
7 + await utils.invokeMethod<Uint8List>('setIsAppSecure', {'isAppSecure': isAppSecure});
8 } catch (_) {}
9 }
cw_core/lib/wallet_base.dart
+6
@@ -67,6 +67,12 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
67 // there is a default definition here because only coins with a pow node (nano based) need to override this
68 Future<void> connectToPowNode({required Node node}) async {}
69
70 + // startBackgroundSync is used to start sync in the background, without doing any
71 + // extra things in the background.
72 + // startSync is used as a fallback.
73 + Future<void> startBackgroundSync() => startSync();
74 + Future<void> stopBackgroundSync(String password) => stopSync();
75 +
76 Future<void> startSync();
77
78 Future<void> stopSync() async {}
cw_monero/lib/api/account_list.dart
+2 -1
@@ -37,7 +37,8 @@ List<monero.SubaddressAccountRow> getAllAccount() {
37 int size = monero.SubaddressAccount_getAll_size(subaddressAccount!);
38 if (size == 0) {
39 monero.Wallet_addSubaddressAccount(wptr!);
40 - return getAllAccount();
40 + monero.Wallet_status(wptr!);
41 + return [];
42 }
43 return List.generate(size, (index) {
44 return monero.SubaddressAccount_getAll_byIndex(subaddressAccount!, index: index);
cw_monero/lib/api/wallet.dart
+17 -8
@@ -2,6 +2,7 @@ import 'dart:async';
2 import 'dart:ffi';
3 import 'dart:isolate';
4
5 +import 'package:cw_core/root_dir.dart';
6 import 'package:cw_core/utils/print_verbose.dart';
7 import 'package:cw_monero/api/account_list.dart';
8 import 'package:cw_monero/api/exceptions/setup_wallet_exception.dart';
@@ -108,9 +109,13 @@ Map<int, Map<int, Map<int, String>>> addressCache = {};
109
110 String getAddress({int accountIndex = 0, int addressIndex = 0}) {
111 // printV("getaddress: ${accountIndex}/${addressIndex}: ${monero.Wallet_numSubaddresses(wptr!, accountIndex: accountIndex)}: ${monero.Wallet_address(wptr!, accountIndex: accountIndex, addressIndex: addressIndex)}");
111 - while (monero.Wallet_numSubaddresses(wptr!, accountIndex: accountIndex)-1 < addressIndex) {
112 - printV("adding subaddress");
113 - monero.Wallet_addSubaddress(wptr!, accountIndex: accountIndex);
112 + // this could be a while loop, but I'm in favor of making it if to not cause freezes
113 + if (monero.Wallet_numSubaddresses(wptr!, accountIndex: accountIndex)-1 < addressIndex) {
114 + if (monero.Wallet_numSubaddressAccounts(wptr!) < accountIndex) {
115 + monero.Wallet_addSubaddressAccount(wptr!);
116 + } else {
117 + monero.Wallet_addSubaddress(wptr!, accountIndex: accountIndex);
118 + }
119 }
120 addressCache[wptr!.address] ??= {};
121 addressCache[wptr!.address]![accountIndex] ??= {};
@@ -149,6 +154,7 @@ Future<bool> setupNodeSync(
154 }
155 ''');
156 final addr = wptr!.address;
157 + printV("init: start");
158 await Isolate.run(() {
159 monero.Wallet_init(Pointer.fromAddress(addr),
160 daemonAddress: address,
@@ -157,6 +163,7 @@ Future<bool> setupNodeSync(
163 daemonUsername: login ?? '',
164 daemonPassword: password ?? '');
165 });
166 + printV("init: end");
167
168 final status = monero.Wallet_status(wptr!);
169
@@ -168,7 +175,7 @@ Future<bool> setupNodeSync(
175 }
176 }
177
171 - if (kDebugMode && debugMonero) {
178 + if (true) {
179 monero.Wallet_init3(
180 wptr!, argv0: '',
181 defaultLogBaseName: 'moneroc',
@@ -243,7 +250,9 @@ class SyncListener {
250 SyncListener(this.onNewBlock, this.onNewTransaction)
251 : _cachedBlockchainHeight = 0,
252 _lastKnownBlockHeight = 0,
246 - _initialSyncHeight = 0;
253 + _initialSyncHeight = 0 {
254 + _start();
255 + }
256
257 void Function(int, int, double) onNewBlock;
258 void Function() onNewTransaction;
@@ -261,7 +270,7 @@ class SyncListener {
270 return _cachedBlockchainHeight;
271 }
272
264 - void start() {
273 + void _start() {
274 _cachedBlockchainHeight = 0;
275 _lastKnownBlockHeight = 0;
276 _initialSyncHeight = 0;
@@ -282,7 +291,7 @@ class SyncListener {
291 }
292
293 final bchHeight = await getNodeHeightOrUpdate(syncHeight);
285 -
294 + // printV("syncHeight: $syncHeight, _lastKnownBlockHeight: $_lastKnownBlockHeight, bchHeight: $bchHeight");
295 if (_lastKnownBlockHeight == syncHeight) {
296 return;
297 }
@@ -379,4 +388,4 @@ String signMessage(String message, {String address = ""}) {
388
389 bool verifyMessage(String message, String address, String signature) {
390 return monero.Wallet_verifySignedMessage(wptr!, message: message, address: address, signature: signature);
382 -}
391 +}
\ No newline at end of file
cw_monero/lib/api/wallet_manager.dart
+60 -6
@@ -11,6 +11,7 @@ import 'package:cw_monero/api/exceptions/wallet_restore_from_seed_exception.dart
11 import 'package:cw_monero/api/transaction_history.dart';
12 import 'package:cw_monero/api/wallet.dart';
13 import 'package:cw_monero/ledger.dart';
14 +import 'package:flutter/foundation.dart';
15 import 'package:monero/monero.dart' as monero;
16
17 class MoneroCException implements Exception {
@@ -50,7 +51,13 @@ final monero.WalletManager wmPtr = Pointer.fromAddress((() {
51 // codebase, so it will be easier to debug what happens. At least easier
52 // than plugging gdb in. Especially on windows/android.
53 monero.printStarts = false;
54 + if (kDebugMode && debugMonero) {
55 + monero.WalletManagerFactory_setLogLevel(4);
56 + }
57 _wmPtr ??= monero.WalletManagerFactory_getWalletManager();
58 + if (kDebugMode && debugMonero) {
59 + monero.WalletManagerFactory_setLogLevel(4);
60 + }
61 printV("ptr: $_wmPtr");
62 } catch (e) {
63 printV(e);
@@ -77,10 +84,17 @@ void createWalletSync(
84 final newWptr = monero.WalletManager_createWallet(wmPtr,
85 path: path, password: password, language: language, networkType: 0);
86
80 - final status = monero.Wallet_status(newWptr);
87 + int status = monero.Wallet_status(newWptr);
88 + if (status != 0) {
89 + throw WalletCreationException(message: monero.Wallet_errorString(newWptr));
90 + }
91 +
92 + monero.Wallet_setupBackgroundSync(newWptr, backgroundSyncType: 2, walletPassword: password, backgroundCachePassword: '');
93 + status = monero.Wallet_status(newWptr);
94 if (status != 0) {
95 throw WalletCreationException(message: monero.Wallet_errorString(newWptr));
96 }
97 +
98 wptr = newWptr;
99 monero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.passphrase", value: passphrase);
100 monero.Wallet_store(wptr!, path: path);
@@ -166,12 +180,19 @@ void restoreWalletFromKeysSync(
180 nettype: 0,
181 );
182
169 - final status = monero.Wallet_status(newWptr);
183 + int status = monero.Wallet_status(newWptr);
184 if (status != 0) {
185 throw WalletRestoreFromKeysException(
186 message: monero.Wallet_errorString(newWptr));
187 }
188
189 +
190 + monero.Wallet_setupBackgroundSync(newWptr, backgroundSyncType: 2, walletPassword: password, backgroundCachePassword: '');
191 + status = monero.Wallet_status(newWptr);
192 + if (status != 0) {
193 + throw WalletCreationException(message: monero.Wallet_errorString(newWptr));
194 + }
195 +
196 // CW-712 - Try to restore deterministic wallet first, if the view key doesn't
197 // match the view key provided
198 if (spendKey != "") {
@@ -190,11 +211,17 @@ void restoreWalletFromKeysSync(
211 spendKeyString: spendKey,
212 nettype: 0,
213 );
193 - final status = monero.Wallet_status(newWptr);
214 + int status = monero.Wallet_status(newWptr);
215 if (status != 0) {
216 throw WalletRestoreFromKeysException(
217 message: monero.Wallet_errorString(newWptr));
218 }
219 +
220 + monero.Wallet_setupBackgroundSync(newWptr, backgroundSyncType: 2, walletPassword: password, backgroundCachePassword: '');
221 + status = monero.Wallet_status(newWptr);
222 + if (status != 0) {
223 + throw WalletCreationException(message: monero.Wallet_errorString(newWptr));
224 + }
225 }
226 }
227
@@ -227,7 +254,7 @@ void restoreWalletFromPolyseedWithOffset(
254 kdfRounds: 1,
255 );
256
230 - final status = monero.Wallet_status(newWptr);
257 + int status = monero.Wallet_status(newWptr);
258
259 if (status != 0) {
260 final err = monero.Wallet_errorString(newWptr);
@@ -240,6 +267,12 @@ void restoreWalletFromPolyseedWithOffset(
267 monero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.seed", value: seed);
268 monero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.passphrase", value: seedOffset);
269 monero.Wallet_store(wptr!);
270 +
271 + monero.Wallet_setupBackgroundSync(newWptr, backgroundSyncType: 2, walletPassword: password, backgroundCachePassword: '');
272 + status = monero.Wallet_status(newWptr);
273 + if (status != 0) {
274 + throw WalletCreationException(message: monero.Wallet_errorString(newWptr));
275 + }
276 storeSync();
277
278 openedWalletsByPath[path] = wptr!;
@@ -277,7 +310,7 @@ void restoreWalletFromSpendKeySync(
310 restoreHeight: restoreHeight,
311 );
312
280 - final status = monero.Wallet_status(newWptr);
313 + int status = monero.Wallet_status(newWptr);
314
315 if (status != 0) {
316 final err = monero.Wallet_errorString(newWptr);
@@ -290,6 +323,12 @@ void restoreWalletFromSpendKeySync(
323 monero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.seed", value: seed);
324
325 storeSync();
326 +
327 + monero.Wallet_setupBackgroundSync(newWptr, backgroundSyncType: 2, walletPassword: password, backgroundCachePassword: '');
328 + status = monero.Wallet_status(newWptr);
329 + if (status != 0) {
330 + throw WalletCreationException(message: monero.Wallet_errorString(newWptr));
331 + }
332
333 openedWalletsByPath[path] = wptr!;
334 _lastOpenedWallet = path;
@@ -321,6 +360,14 @@ Future<void> restoreWalletFromHardwareWallet(
360 final error = monero.Wallet_errorString(newWptr);
361 throw WalletRestoreFromSeedException(message: error);
362 }
363 +
364 + // TODO: Check with upstream if we can use background sync here
365 + // monero.Wallet_setupBackgroundSync(newWptr, backgroundSyncType: 2, walletPassword: password, backgroundCachePassword: '');
366 + // status = monero.Wallet_status(newWptr);
367 + // if (status != 0) {
368 + // throw WalletCreationException(message: monero.Wallet_errorString(newWptr));
369 + // }
370 +
371 wptr = newWptr;
372 _lastOpenedWallet = path;
373 openedWalletsByPath[path] = wptr!;
@@ -384,7 +431,14 @@ Future<void> loadWallet(
431
432 final newWptr = Pointer<Void>.fromAddress(newWptrAddr);
433
387 - final status = monero.Wallet_status(newWptr);
434 + int status = monero.Wallet_status(newWptr);
435 + if (status != 0) {
436 + final err = monero.Wallet_errorString(newWptr);
437 + printV("loadWallet:"+err);
438 + throw WalletOpeningException(message: err);
439 + }
440 + monero.Wallet_setupBackgroundSync(newWptr, backgroundSyncType: 2, walletPassword: password, backgroundCachePassword: '');
441 + status = monero.Wallet_status(newWptr);
442 if (status != 0) {
443 final err = monero.Wallet_errorString(newWptr);
444 printV("loadWallet:"+err);
cw_monero/lib/monero_wallet.dart
+53 -2
@@ -218,7 +218,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
218 // FIXME: hardcoded value
219 socksProxyAddress: node.socksProxyAddress);
220
221 - monero_wallet.setTrustedDaemon(node.trusted);
221 + await monero_wallet.setTrustedDaemon(node.trusted);
222 syncStatus = ConnectedSyncStatus();
223 } catch (e) {
224 syncStatus = FailedSyncStatus();
@@ -226,6 +226,57 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
226 }
227 }
228
229 + @override
230 + Future<void> startBackgroundSync() async {
231 + if (isBackgroundSyncRunning) {
232 + printV("Background sync already running");
233 + return;
234 + }
235 + isBackgroundSyncRunning = true;
236 + int status = monero.Wallet_status(wptr!);
237 + if (status != 0) {
238 + final err = monero.Wallet_errorString(wptr!);
239 + throw Exception("unable to setup background sync: $err");
240 + }
241 + await save();
242 +
243 + monero.Wallet_startBackgroundSync(wptr!);
244 + status = monero.Wallet_status(wptr!);
245 + if (status != 0) {
246 + final err = monero.Wallet_errorString(wptr!);
247 + throw Exception("unable to start background sync: $err");
248 + }
249 + await save();
250 + await init();
251 + await startSync();
252 + }
253 +
254 + bool isBackgroundSyncRunning = false;
255 +
256 + @action
257 + @override
258 + Future<void> stopSync() async {
259 + if (isBackgroundSyncRunning) {
260 + printV("Stopping background sync");
261 + await save();
262 + monero.Wallet_stopBackgroundSync(wptr!, '');
263 + await save();
264 + isBackgroundSyncRunning = false;
265 + }
266 + }
267 +
268 + @action
269 + @override
270 + Future<void> stopBackgroundSync(String password) async {
271 + if (isBackgroundSyncRunning) {
272 + printV("Stopping background sync");
273 + await save();
274 + monero.Wallet_stopBackgroundSync(wptr!, password);
275 + await save();
276 + isBackgroundSyncRunning = false;
277 + }
278 + }
279 +
280 @override
281 Future<void> startSync() async {
282 try {
@@ -250,7 +301,6 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
301 syncStatus = AttemptingSyncStatus();
302 monero_wallet.startRefresh();
303 _setListeners();
253 - _listener?.start();
304 } catch (e) {
305 syncStatus = FailedSyncStatus();
306 printV(e);
@@ -782,6 +832,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
832 }
833
834 void _onNewBlock(int height, int blocksLeft, double ptc) async {
835 + printV("onNewBlock: $height, $blocksLeft, $ptc");
836 try {
837 if (walletInfo.isRecovery) {
838 await _askForUpdateTransactionHistory();
cw_wownero/lib/api/wallet_manager.dart
+1
@@ -349,6 +349,7 @@ void loadWallet(
349 txhistory = null;
350 final newWptr = wownero.WalletManager_openWallet(wmPtr,
351 path: path, password: password);
352 +
353 _lastOpenedWallet = path;
354 final status = wownero.Wallet_status(newWptr);
355 if (status != 0) {
ios/Podfile.lock
+30 -73
@@ -3,38 +3,9 @@ PODS:
3 - Flutter
4 - ReachabilitySwift
5 - CryptoSwift (1.8.3)
6 - - cw_haven (0.0.1):
7 - - cw_haven/Boost (= 0.0.1)
8 - - cw_haven/Haven (= 0.0.1)
9 - - cw_haven/OpenSSL (= 0.0.1)
10 - - cw_haven/Sodium (= 0.0.1)
11 - - cw_shared_external
12 - - Flutter
13 - - cw_haven/Boost (0.0.1):
14 - - cw_shared_external
15 - - Flutter
16 - - cw_haven/Haven (0.0.1):
17 - - cw_shared_external
18 - - Flutter
19 - - cw_haven/OpenSSL (0.0.1):
20 - - cw_shared_external
21 - - Flutter
22 - - cw_haven/Sodium (0.0.1):
23 - - cw_shared_external
24 - - Flutter
25 - - cw_mweb (0.0.1):
26 - - Flutter
6 - cw_decred (0.0.1):
28 - - cw_shared_external (0.0.1):
29 - - cw_shared_external/Boost (= 0.0.1)
30 - - cw_shared_external/OpenSSL (= 0.0.1)
31 - - cw_shared_external/Sodium (= 0.0.1)
32 - - Flutter
33 - - cw_shared_external/Boost (0.0.1):
34 - - Flutter
35 - - cw_shared_external/OpenSSL (0.0.1):
7 - Flutter
37 - - cw_shared_external/Sodium (0.0.1):
8 + - cw_mweb (0.0.1):
9 - Flutter
10 - device_display_brightness (0.0.1):
11 - Flutter
@@ -131,16 +102,12 @@ PODS:
102 - Flutter
103 - wakelock_plus (0.0.1):
104 - Flutter
134 - - workmanager (0.0.1):
135 - - Flutter
105
106 DEPENDENCIES:
107 - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
108 - CryptoSwift
140 - - cw_haven (from `.symlinks/plugins/cw_haven/ios`)
141 - - cw_mweb (from `.symlinks/plugins/cw_mweb/ios`)
142 - - cw_shared_external (from `.symlinks/plugins/cw_shared_external/ios`)
109 - cw_decred (from `.symlinks/plugins/cw_decred/ios`)
110 + - cw_mweb (from `.symlinks/plugins/cw_mweb/ios`)
111 - device_display_brightness (from `.symlinks/plugins/device_display_brightness/ios`)
112 - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
113 - devicelocale (from `.symlinks/plugins/devicelocale/ios`)
@@ -165,7 +132,6 @@ DEPENDENCIES:
132 - universal_ble (from `.symlinks/plugins/universal_ble/darwin`)
133 - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
134 - wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
168 - - workmanager (from `.symlinks/plugins/workmanager/ios`)
135
136 SPEC REPOS:
137 https://github.com/CocoaPods/Specs.git:
@@ -181,14 +147,10 @@ SPEC REPOS:
147 EXTERNAL SOURCES:
148 connectivity_plus:
149 :path: ".symlinks/plugins/connectivity_plus/ios"
184 - cw_haven:
185 - :path: ".symlinks/plugins/cw_haven/ios"
186 - cw_mweb:
187 - :path: ".symlinks/plugins/cw_mweb/ios"
188 - cw_shared_external:
189 - :path: ".symlinks/plugins/cw_shared_external/ios"
150 cw_decred:
151 :path: ".symlinks/plugins/cw_decred/ios"
152 + cw_mweb:
153 + :path: ".symlinks/plugins/cw_mweb/ios"
154 device_display_brightness:
155 :path: ".symlinks/plugins/device_display_brightness/ios"
156 device_info_plus:
@@ -237,48 +199,43 @@ EXTERNAL SOURCES:
199 :path: ".symlinks/plugins/url_launcher_ios/ios"
200 wakelock_plus:
201 :path: ".symlinks/plugins/wakelock_plus/ios"
240 - workmanager:
241 - :path: ".symlinks/plugins/workmanager/ios"
202
203 SPEC CHECKSUMS:
244 - connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d
204 + connectivity_plus: 481668c94744c30c53b8895afb39159d1e619bdf
205 CryptoSwift: 967f37cea5a3294d9cce358f78861652155be483
246 - cw_haven: b3e54e1fbe7b8e6fda57a93206bc38f8e89b898a
247 - cw_mweb: 22cd01dfb8ad2d39b15332006f22046aaa8352a3
248 - cw_shared_external: 2972d872b8917603478117c9957dfca611845a92
249 - cw_decred: 9c0e1df74745b51a1289ec5e91fb9e24b68fa14a
250 - device_display_brightness: 1510e72c567a1f6ce6ffe393dcd9afd1426034f7
251 - device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
252 - devicelocale: 35ba84dc7f45f527c3001535d8c8d104edd5d926
206 + cw_decred: a02cf30175a46971c1e2fa22c48407534541edc6
207 + cw_mweb: 3aea2fb35b2bd04d8b2d21b83216f3b8fb768d85
208 + device_display_brightness: 04374ebd653619292c1d996f00f42877ea19f17f
209 + device_info_plus: 335f3ce08d2e174b9fdc3db3db0f4e3b1f66bd89
210 + devicelocale: bd64aa714485a8afdaded0892c1e7d5b7f680cf8
211 DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
212 DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
255 - fast_scanner: 44c00940355a51258cd6c2085734193cd23d95bc
256 - file_picker: 15fd9539e4eb735dc54bae8c0534a7a9511a03de
213 + fast_scanner: 2cb1ad3e69e645e9980fb4961396ce5804caa3e3
214 + file_picker: 07c75322ede1d47ec9bb4ac82b27c94d3598251a
215 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
258 - flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4
259 - flutter_local_authentication: 1172a4dd88f6306dadce067454e2c4caf07977bb
260 - flutter_mailer: 2ef5a67087bc8c6c4cefd04a178bf1ae2c94cd83
261 - flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
262 - fluttertoast: e9a18c7be5413da53898f660530c56f35edfba9c
263 - in_app_review: a31b5257259646ea78e0e35fc914979b0031d011
264 - integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573
216 + flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99
217 + flutter_local_authentication: 989278c681612f1ee0e36019e149137f114b9d7f
218 + flutter_mailer: 3a8cd4f36c960fb04528d5471097270c19fec1c4
219 + flutter_secure_storage: 2c2ff13db9e0a5647389bff88b0ecac56e3f3418
220 + fluttertoast: 76fea30fcf04176325f6864c87306927bd7d2038
221 + in_app_review: 5596fe56fab799e8edb3561c03d053363ab13457
222 + integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e
223 OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
266 - package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4
267 - path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
268 - permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6
224 + package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
225 + path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
226 + permission_handler_apple: 3787117e48f80715ff04a3830ca039283d6a4f29
227 ReachabilitySwift: 32793e867593cfc1177f5d16491e3a197d2fccda
228 SDWebImage: 8a6b7b160b4d710e2a22b6900e25301075c34cb3
271 - sensitive_clipboard: d4866e5d176581536c27bb1618642ee83adca986
272 - share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f
273 - shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
274 - sp_scanner: eaa617fa827396b967116b7f1f43549ca62e9a12
229 + sensitive_clipboard: 161e9abc3d56b3131309d8a321eb4690a803c16b
230 + share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
231 + shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
232 + sp_scanner: b1bc9321690980bdb44bba7ec85d5543e716d1b5
233 SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
234 Toast: 1f5ea13423a1e6674c4abdac5be53587ae481c4e
277 - uni_links: d97da20c7701486ba192624d99bffaaffcfc298a
278 - universal_ble: cf52a7b3fd2e7c14d6d7262e9fdadb72ab6b88a6
279 - url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
280 - wakelock_plus: 373cfe59b235a6dd5837d0fb88791d2f13a90d56
281 - workmanager: 0afdcf5628bbde6924c21af7836fed07b42e30e6
235 + uni_links: ed8c961e47ed9ce42b6d91e1de8049e38a4b3152
236 + universal_ble: ff19787898040d721109c6324472e5dd4bc86adc
237 + url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
238 + wakelock_plus: 04623e3f525556020ebd4034310f20fe7fda8b49
239
240 PODFILE CHECKSUM: e448f662d4c41f0c0b1ccbb78afd57dbf895a597
241
ios/Runner/AppDelegate.swift
-10
@@ -1,6 +1,5 @@
1 import UIKit
2 import Flutter
3 -import workmanager
3
4 @main
5 @objc class AppDelegate: FlutterAppDelegate {
@@ -12,15 +11,6 @@ import workmanager
11 UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
12 }
13
15 - WorkmanagerPlugin.setPluginRegistrantCallback { registry in
16 - // Registry in this case is the FlutterEngine that is created in Workmanager's
17 - // performFetchWithCompletionHandler or BGAppRefreshTask.
18 - // This will make other plugins available during a background operation.
19 - GeneratedPluginRegistrant.register(with: registry)
20 - }
21 -
22 - WorkmanagerPlugin.registerTask(withIdentifier: "com.fotolockr.cakewallet.monero_sync_task")
23 -
14 makeSecure()
15
16 let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
lib/core/background_sync.dart new
+107
@@ -0,0 +1,107 @@
1 +import 'dart:async';
2 +import 'dart:math';
3 +
4 +import 'package:cake_wallet/core/key_service.dart';
5 +import 'package:cake_wallet/core/wallet_loading_service.dart';
6 +import 'package:cake_wallet/di.dart';
7 +import 'package:cake_wallet/store/settings_store.dart';
8 +import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart';
9 +import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart';
10 +import 'package:cw_core/sync_status.dart';
11 +import 'package:cw_core/utils/print_verbose.dart';
12 +import 'package:cw_core/wallet_type.dart';
13 +import 'package:flutter/foundation.dart';
14 +import 'package:http/http.dart' as http;
15 +
16 +class BackgroundSync {
17 + Future<void> sync() async {
18 + printV("Background sync started");
19 + await _syncMonero();
20 + printV("Background sync completed");
21 + }
22 +
23 + Future<void> _syncMonero() async {
24 + final walletLoadingService = getIt.get<WalletLoadingService>();
25 + final walletListViewModel = getIt.get<WalletListViewModel>();
26 + final settingsStore = getIt.get<SettingsStore>();
27 +
28 +
29 + final List<WalletListItem> moneroWallets = walletListViewModel.wallets
30 + .where((element) => !element.isHardware)
31 + .where((element) => [WalletType.monero].contains(element.type))
32 + .toList();
33 + for (int i = 0; i < moneroWallets.length; i++) {
34 + final wallet = await walletLoadingService.load(moneroWallets[i].type, moneroWallets[i].name);
35 + int syncedTicks = 0;
36 + final keyService = getIt.get<KeyService>();
37 +
38 + int stuckTicks = 0;
39 +
40 + inner:
41 + while (true) {
42 + await Future.delayed(const Duration(seconds: 1));
43 + final syncStatus = wallet.syncStatus;
44 + final progress = syncStatus.progress();
45 + if (syncStatus is ConnectedSyncStatus || syncStatus is AttemptingSyncStatus || syncStatus is NotConnectedSyncStatus) {
46 + stuckTicks++;
47 + if (stuckTicks > 30) {
48 + printV("${wallet.name} STUCK SYNCING");
49 + break inner;
50 + }
51 + } else {
52 + stuckTicks = 0;
53 + }
54 + if (syncStatus is NotConnectedSyncStatus) {
55 + printV("${wallet.name} NOT CONNECTED");
56 + final node = settingsStore.getCurrentNode(wallet.type);
57 + await wallet.connectToNode(node: node);
58 + await wallet.startBackgroundSync();
59 + printV("STARTED SYNC");
60 + continue inner;
61 + }
62 +
63 + if (progress > 0.999 || syncStatus is SyncedSyncStatus) {
64 + syncedTicks++;
65 + if (syncedTicks > 5) {
66 + syncedTicks = 0;
67 + printV("WALLET $i SYNCED");
68 + try {
69 + await wallet.stopBackgroundSync((await keyService.getWalletPassword(walletName: wallet.name)));
70 + } catch (e) {
71 + printV("error stopping sync: $e");
72 + }
73 + break inner;
74 + }
75 + } else {
76 + syncedTicks = 0;
77 + }
78 + if (kDebugMode) {
79 + if (syncStatus is SyncingSyncStatus) {
80 + final blocksLeft = syncStatus.blocksLeft;
81 + printV("$blocksLeft Blocks Left");
82 + } else if (syncStatus is SyncedSyncStatus) {
83 + printV("Synced");
84 + } else if (syncStatus is SyncedTipSyncStatus) {
85 + printV("Scanned Tip: ${syncStatus.tip}");
86 + } else if (syncStatus is NotConnectedSyncStatus) {
87 + printV("Still Not Connected");
88 + } else if (syncStatus is AttemptingSyncStatus) {
89 + printV("Attempting Sync");
90 + } else if (syncStatus is StartingScanSyncStatus) {
91 + printV("Starting Scan");
92 + } else if (syncStatus is SyncronizingSyncStatus) {
93 + printV("Syncronizing");
94 + } else if (syncStatus is FailedSyncStatus) {
95 + printV("Failed Sync");
96 + } else if (syncStatus is ConnectingSyncStatus) {
97 + printV("Connecting");
98 + } else {
99 + printV("Unknown Sync Status ${syncStatus.runtimeType}");
100 + }
101 + }
102 + }
103 + await wallet.stopBackgroundSync(await keyService.getWalletPassword(walletName: wallet.name));
104 + await wallet.close(shouldCleanup: true);
105 + }
106 + }
107 +}
\ No newline at end of file
lib/di.dart
+9 -5
@@ -26,7 +26,6 @@ import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart';
26 import 'package:cake_wallet/core/wallet_creation_service.dart';
27 import 'package:cake_wallet/core/wallet_loading_service.dart';
28 import 'package:cake_wallet/core/yat_service.dart';
29 -import 'package:cake_wallet/entities/background_tasks.dart';
29 import 'package:cake_wallet/entities/biometric_auth.dart';
30 import 'package:cake_wallet/entities/contact.dart';
31 import 'package:cake_wallet/entities/contact_record.dart';
@@ -34,6 +33,9 @@ import 'package:cake_wallet/entities/exchange_api_mode.dart';
33 import 'package:cake_wallet/entities/hardware_wallet/require_hardware_wallet_connection.dart';
34 import 'package:cake_wallet/entities/parse_address_from_domain.dart';
35 import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart';
36 +import 'package:cake_wallet/src/screens/dev/monero_background_sync.dart';
37 +import 'package:cake_wallet/src/screens/settings/background_sync_page.dart';
38 +import 'package:cake_wallet/view_model/dev/monero_background_sync.dart';
39 import 'package:cake_wallet/view_model/link_view_model.dart';
40 import 'package:cake_wallet/tron/tron.dart';
41 import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart';
@@ -309,9 +311,6 @@ Future<void> setup({
311 getIt.registerSingletonAsync<SharedPreferences>(() => SharedPreferences.getInstance());
312 getIt.registerSingleton<SecureStorage>(secureStorage);
313 }
312 - if (!_isSetupFinished) {
313 - getIt.registerFactory(() => BackgroundTasks());
314 - }
314
315 final isBitcoinBuyEnabled = (secrets.wyreSecretKey.isNotEmpty) &&
316 (secrets.wyreApiKey.isNotEmpty) &&
@@ -909,6 +908,8 @@ Future<void> setup({
908
909 getIt.registerFactory<SeedSettingsViewModel>(() => SeedSettingsViewModel(getIt.get<AppStore>(), getIt.get<SeedSettingsStore>()));
910
911 + getIt.registerFactory(() => DevMoneroBackgroundSync(getIt.get<AppStore>().wallet!));
912 +
913 getIt.registerFactoryParam<WalletSeedPage, bool, void>((bool isWalletCreated, _) =>
914 WalletSeedPage(getIt.get<WalletSeedViewModel>(), isNewWalletCreated: isWalletCreated));
915
@@ -1068,6 +1069,8 @@ Future<void> setup({
1069 getIt.registerFactory(
1070 () => ExchangeTradeExternalSendPage(exchangeTradeViewModel: getIt.get<ExchangeTradeViewModel>()));
1071
1072 + getIt.registerFactory(() => BackgroundSyncPage(getIt.get<DashboardViewModel>()));
1073 +
1074 getIt.registerFactory(() => ExchangeTemplatePage(getIt.get<ExchangeViewModel>()));
1075
1076 getIt.registerFactoryParam<WalletService, WalletType, void>((WalletType param1, __) {
@@ -1443,7 +1446,8 @@ Future<void> setup({
1446
1447 getIt.registerFactory(() => SignViewModel(getIt.get<AppStore>().wallet!));
1448
1446 - getIt.registerFactory(() => SeedVerificationPage(getIt.get<WalletSeedViewModel>()));
1449 + getIt.registerFactory(() => SeedVerificationPage(getIt.get<WalletSeedViewModel>()));
1450
1451 + getIt.registerFactory(() => DevMoneroBackgroundSyncPage(getIt.get<DevMoneroBackgroundSync>()));
1452 _isSetupFinished = true;
1453 }
lib/entities/background_tasks.dart deleted
-166
@@ -1,166 +0,0 @@
1 -import 'dart:io';
2 -
3 -import 'package:cake_wallet/core/wallet_loading_service.dart';
4 -import 'package:cake_wallet/entities/preferences_key.dart';
5 -import 'package:cake_wallet/store/settings_store.dart';
6 -import 'package:cake_wallet/utils/device_info.dart';
7 -import 'package:cake_wallet/utils/feature_flag.dart';
8 -import 'package:cake_wallet/view_model/settings/sync_mode.dart';
9 -import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart';
10 -import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart';
11 -import 'package:cw_core/utils/print_verbose.dart';
12 -import 'package:cw_core/wallet_base.dart';
13 -import 'package:cw_core/wallet_type.dart';
14 -import 'package:flutter/foundation.dart';
15 -import 'package:shared_preferences/shared_preferences.dart';
16 -import 'package:workmanager/workmanager.dart';
17 -import 'package:cake_wallet/main.dart';
18 -import 'package:cake_wallet/di.dart';
19 -
20 -const moneroSyncTaskKey = "com.fotolockr.cakewallet.monero_sync_task";
21 -
22 -@pragma('vm:entry-point')
23 -void callbackDispatcher() {
24 - Workmanager().executeTask((task, inputData) async {
25 - try {
26 - switch (task) {
27 - case moneroSyncTaskKey:
28 -
29 - /// The work manager runs on a separate isolate from the main flutter isolate.
30 - /// thus we initialize app configs first; hive, getIt, etc...
31 - await initializeAppConfigs();
32 -
33 - final walletLoadingService = getIt.get<WalletLoadingService>();
34 -
35 - final typeRaw = getIt.get<SharedPreferences>().getInt(PreferencesKey.currentWalletType);
36 -
37 - WalletBase? wallet;
38 -
39 - if (inputData!['sync_all'] as bool) {
40 - /// get all Monero wallets of the user and sync them
41 - final List<WalletListItem> moneroWallets = getIt
42 - .get<WalletListViewModel>()
43 - .wallets
44 - .where((element) => [WalletType.monero, WalletType.wownero].contains(element.type))
45 - .toList();
46 -
47 - for (int i = 0; i < moneroWallets.length; i++) {
48 - wallet =
49 - await walletLoadingService.load(moneroWallets[i].type, moneroWallets[i].name);
50 - final node = getIt.get<SettingsStore>().getCurrentNode(moneroWallets[i].type);
51 - await wallet.connectToNode(node: node);
52 - await wallet.startSync();
53 - }
54 - } else {
55 - /// if the user chose to sync only active wallet
56 - /// if the current wallet is monero; sync it only
57 - if (typeRaw == WalletType.monero.index || typeRaw == WalletType.wownero.index) {
58 - final name =
59 - getIt.get<SharedPreferences>().getString(PreferencesKey.currentWalletName);
60 -
61 - wallet = await walletLoadingService.load(WalletType.values[typeRaw!], name!);
62 - final node = getIt.get<SettingsStore>().getCurrentNode(WalletType.values[typeRaw]);
63 -
64 - await wallet.connectToNode(node: node);
65 - await wallet.startSync();
66 - }
67 - }
68 -
69 - if (wallet?.syncStatus.progress() == null) {
70 - return Future.error("No Monero/Wownero wallet found");
71 - }
72 -
73 - for (int i = 0;; i++) {
74 - await Future<void>.delayed(const Duration(seconds: 1));
75 - if (wallet?.syncStatus.progress() == 1.0) {
76 - break;
77 - }
78 - if (i > 600) {
79 - return Future.error("Synchronization Timed out");
80 - }
81 - }
82 - break;
83 - }
84 -
85 - return Future.value(true);
86 - } catch (error, stackTrace) {
87 - printV(error);
88 - printV(stackTrace);
89 - return Future.error(error);
90 - }
91 - });
92 -}
93 -
94 -class BackgroundTasks {
95 - void registerSyncTask({bool changeExisting = false}) async {
96 - try {
97 - bool hasMonero = getIt
98 - .get<WalletListViewModel>()
99 - .wallets
100 - .any((element) => element.type == WalletType.monero);
101 -
102 - /// if its not android nor ios, or the user has no monero wallets; exit
103 - if (!DeviceInfo.instance.isMobile || !hasMonero) {
104 - return;
105 - }
106 -
107 - final settingsStore = getIt.get<SettingsStore>();
108 -
109 - final SyncMode syncMode = settingsStore.currentSyncMode;
110 - final bool syncAll = settingsStore.currentSyncAll;
111 -
112 - if (syncMode.type == SyncType.disabled || !FeatureFlag.isBackgroundSyncEnabled) {
113 - cancelSyncTask();
114 - return;
115 - }
116 -
117 - await Workmanager().initialize(
118 - callbackDispatcher,
119 - isInDebugMode: kDebugMode,
120 - );
121 -
122 - final inputData = <String, dynamic>{"sync_all": syncAll};
123 - final constraints = Constraints(
124 - networkType:
125 - syncMode.type == SyncType.unobtrusive ? NetworkType.unmetered : NetworkType.connected,
126 - requiresBatteryNotLow: syncMode.type == SyncType.unobtrusive,
127 - requiresCharging: syncMode.type == SyncType.unobtrusive,
128 - requiresDeviceIdle: syncMode.type == SyncType.unobtrusive,
129 - );
130 -
131 - if (Platform.isIOS) {
132 - await Workmanager().registerOneOffTask(
133 - moneroSyncTaskKey,
134 - moneroSyncTaskKey,
135 - initialDelay: syncMode.frequency,
136 - existingWorkPolicy: ExistingWorkPolicy.replace,
137 - inputData: inputData,
138 - constraints: constraints,
139 - );
140 - return;
141 - }
142 -
143 - await Workmanager().registerPeriodicTask(
144 - moneroSyncTaskKey,
145 - moneroSyncTaskKey,
146 - initialDelay: syncMode.frequency,
147 - frequency: syncMode.frequency,
148 - existingWorkPolicy: changeExisting ? ExistingWorkPolicy.replace : ExistingWorkPolicy.keep,
149 - inputData: inputData,
150 - constraints: constraints,
151 - );
152 - } catch (error, stackTrace) {
153 - printV(error);
154 - printV(stackTrace);
155 - }
156 - }
157 -
158 - void cancelSyncTask() {
159 - try {
160 - Workmanager().cancelByUniqueName(moneroSyncTaskKey);
161 - } catch (error, stackTrace) {
162 - printV(error);
163 - printV(stackTrace);
164 - }
165 - }
166 -}
lib/entities/load_current_wallet.dart
-3
@@ -1,7 +1,6 @@
1 import 'package:cake_wallet/di.dart';
2 import 'package:shared_preferences/shared_preferences.dart';
3 import 'package:cake_wallet/store/app_store.dart';
4 -import 'package:cake_wallet/entities/background_tasks.dart';
4 import 'package:cake_wallet/entities/preferences_key.dart';
5 import 'package:cw_core/wallet_type.dart';
6 import 'package:cake_wallet/core/wallet_loading_service.dart';
@@ -26,6 +25,4 @@ Future<void> loadCurrentWallet({String? password}) async {
25 name,
26 password: password);
27 await appStore.changeCurrentWallet(wallet);
29 -
30 - getIt.get<BackgroundTasks>().registerSyncTask();
28 }
lib/main.dart
+41 -3
@@ -1,9 +1,11 @@
1 import 'dart:async';
2 import 'dart:io';
3 +import 'dart:ui';
4 import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
5 import 'package:cake_wallet/app_scroll_behavior.dart';
6 import 'package:cake_wallet/buy/order.dart';
7 import 'package:cake_wallet/core/auth_service.dart';
8 +import 'package:cake_wallet/core/background_sync.dart';
9 import 'package:cake_wallet/di.dart';
10 import 'package:cake_wallet/entities/contact.dart';
11 import 'package:cake_wallet/entities/default_settings_migration.dart';
@@ -34,11 +36,13 @@ import 'package:cw_core/hive_type_ids.dart';
36 import 'package:cw_core/mweb_utxo.dart';
37 import 'package:cw_core/node.dart';
38 import 'package:cw_core/unspent_coins_info.dart';
39 +import 'package:cw_core/utils/print_verbose.dart';
40 import 'package:cw_core/wallet_info.dart';
41 import 'package:cw_core/wallet_type.dart';
42 import 'package:flutter/foundation.dart';
43 import 'package:flutter/material.dart';
44 import 'package:flutter/services.dart';
45 +import 'package:flutter_daemon/flutter_daemon.dart';
46 import 'package:flutter_mobx/flutter_mobx.dart';
47 import 'package:hive/hive.dart';
48 import 'package:cw_core/root_dir.dart';
@@ -68,6 +72,7 @@ Future<void> runAppWithZone({Key? topLevelKey}) async {
72
73 return true;
74 };
75 + await FlutterDaemon().unmarkBackgroundSync();
76 await initializeAppAtRoot();
77
78 if (kDebugMode) {
@@ -100,7 +105,7 @@ Future<void> initializeAppAtRoot({bool reInitializing = false}) async {
105 await initializeAppConfigs();
106 }
107
103 -Future<void> initializeAppConfigs() async {
108 +Future<void> initializeAppConfigs({bool loadWallet = true}) async {
109 setRootDirFromEnv();
110 final appDir = await getAppDir();
111 CakeHive.init(appDir.path);
@@ -200,6 +205,7 @@ Future<void> initializeAppConfigs() async {
205 encryptionKey: havenSeedStoreBoxKey);
206
207 await initialSetup(
208 + loadWallet: loadWallet,
209 sharedPreferences: await SharedPreferences.getInstance(),
210 nodes: nodes,
211 powNodes: powNodes,
@@ -220,7 +226,8 @@ Future<void> initializeAppConfigs() async {
226 }
227
228 Future<void> initialSetup(
223 - {required SharedPreferences sharedPreferences,
229 + {required bool loadWallet,
230 + required SharedPreferences sharedPreferences,
231 required Box<Node> nodes,
232 required Box<Node> powNodes,
233 required Box<WalletInfo> walletInfoSource,
@@ -262,7 +269,7 @@ Future<void> initialSetup(
269 navigatorKey: navigatorKey,
270 secureStorage: secureStorage,
271 );
265 - await bootstrap(navigatorKey);
272 + await bootstrap(navigatorKey, loadWallet: loadWallet);
273 }
274
275 class App extends StatefulWidget {
@@ -390,3 +397,34 @@ class TopLevelErrorWidget extends StatelessWidget {
397 );
398 }
399 }
400 +
401 +@pragma('vm:entry-point')
402 +Future<void> backgroundSync() async {
403 + bool shouldUnmark = false;
404 + try {
405 + printV("Background sync triggered");
406 + printV("- WidgetsFlutterBinding.ensureInitialized()");
407 + WidgetsFlutterBinding.ensureInitialized();
408 + printV("- DartPluginRegistrant.ensureInitialized()");
409 + DartPluginRegistrant.ensureInitialized();
410 + printV("- FlutterDaemon.markBackgroundSync()");
411 + final val = await FlutterDaemon().markBackgroundSync();
412 + if (val) {
413 + printV("Background sync already in progress");
414 + return;
415 + }
416 + shouldUnmark = true;
417 + printV("Starting background sync");
418 + final backgroundSync = BackgroundSync();
419 + await initializeAppConfigs(loadWallet: false);
420 + await backgroundSync.sync();
421 + printV("Background sync completed");
422 + } finally {
423 + if (shouldUnmark) {
424 + printV("Unmarking background sync");
425 + await FlutterDaemon().unmarkBackgroundSync();
426 + } else {
427 + printV("Not unmarking background sync");
428 + }
429 + }
430 +}
lib/reactions/bootstrap.dart
+4 -2
@@ -15,7 +15,7 @@ import 'package:cake_wallet/store/settings_store.dart';
15 import 'package:cake_wallet/store/authentication_store.dart';
16 import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
17
18 -Future<void> bootstrap(GlobalKey<NavigatorState> navigatorKey) async {
18 +Future<void> bootstrap(GlobalKey<NavigatorState> navigatorKey, {required bool loadWallet}) async {
19 final appStore = getIt.get<AppStore>();
20 final authenticationStore = getIt.get<AuthenticationStore>();
21 final settingsStore = getIt.get<SettingsStore>();
@@ -27,7 +27,9 @@ Future<void> bootstrap(GlobalKey<NavigatorState> navigatorKey) async {
27 authenticationStore.installed();
28 }
29
30 - startAuthenticationStateChange(authenticationStore, navigatorKey);
30 + if (loadWallet) {
31 + startAuthenticationStateChange(authenticationStore, navigatorKey);
32 + }
33 startCurrentWalletChangeReaction(appStore, settingsStore, fiatConversionStore);
34 startCurrentFiatChangeReaction(appStore, settingsStore, fiatConversionStore);
35 startCurrentFiatApiModeChangeReaction(appStore, settingsStore, fiatConversionStore);
lib/router.dart
+10
@@ -36,6 +36,7 @@ import 'package:cake_wallet/src/screens/dashboard/pages/address_page.dart';
36 import 'package:cake_wallet/src/screens/dashboard/pages/nft_details_page.dart';
37 import 'package:cake_wallet/src/screens/dashboard/pages/transactions_page.dart';
38 import 'package:cake_wallet/src/screens/dashboard/sign_page.dart';
39 +import 'package:cake_wallet/src/screens/dev/monero_background_sync.dart';
40 import 'package:cake_wallet/src/screens/disclaimer/disclaimer_page.dart';
41 import 'package:cake_wallet/src/screens/exchange/exchange_page.dart';
42 import 'package:cake_wallet/src/screens/exchange/exchange_template_page.dart';
@@ -73,6 +74,7 @@ import 'package:cake_wallet/src/screens/seed/wallet_seed_page.dart';
74 import 'package:cake_wallet/src/screens/send/send_page.dart';
75 import 'package:cake_wallet/src/screens/send/send_template_page.dart';
76 import 'package:cake_wallet/src/screens/send/transaction_success_info_page.dart';
77 +import 'package:cake_wallet/src/screens/settings/background_sync_page.dart';
78 import 'package:cake_wallet/src/screens/settings/connection_sync_page.dart';
79 import 'package:cake_wallet/src/screens/settings/desktop_settings/desktop_settings_page.dart';
80 import 'package:cake_wallet/src/screens/settings/display_settings_page.dart';
@@ -824,6 +826,14 @@ Route<dynamic> createRoute(RouteSettings settings) {
826 case Routes.exchangeTradeExternalSendPage:
827 return MaterialPageRoute<void>(builder: (_) => getIt.get<ExchangeTradeExternalSendPage>(),);
828
829 + case Routes.backgroundSync:
830 + return CupertinoPageRoute<void>(
831 + fullscreenDialog: true, builder: (_) => getIt.get<BackgroundSyncPage>());
832 + case Routes.devMoneroBackgroundSync:
833 + return MaterialPageRoute<void>(
834 + builder: (_) => getIt.get<DevMoneroBackgroundSyncPage>(),
835 + );
836 +
837 default:
838 return MaterialPageRoute<void>(
839 builder: (_) => Scaffold(
lib/routes.dart
+2
@@ -110,6 +110,8 @@ class Routes {
110 static const nftDetailsPage = '/nft_details_page';
111 static const importNFTPage = '/import_nft_page';
112 static const torPage = '/tor_page';
113 + static const backgroundSync = '/background_sync';
114 + static const devMoneroBackgroundSync = '/dev/monero_background_sync';
115
116 static const signPage = '/sign_page';
117 static const connectDevices = '/device/connect';
lib/src/screens/dev/monero_background_sync.dart new
+112
@@ -0,0 +1,112 @@
1 +import 'package:cake_wallet/src/screens/base_page.dart';
2 +import 'package:cake_wallet/src/widgets/primary_button.dart';
3 +import 'package:cake_wallet/view_model/dev/monero_background_sync.dart';
4 +import 'package:flutter/material.dart';
5 +import 'package:flutter_mobx/flutter_mobx.dart';
6 +
7 +class DevMoneroBackgroundSyncPage extends BasePage {
8 + final DevMoneroBackgroundSync viewModel;
9 +
10 + DevMoneroBackgroundSyncPage(this.viewModel);
11 +
12 + @override
13 + String? get title => "[dev] xmr background sync";
14 +
15 + Widget _buildSingleCell(String title, String value) {
16 + return Container(
17 + decoration: BoxDecoration(
18 + border: Border.all(color: Colors.grey),
19 + borderRadius: BorderRadius.circular(8),
20 + ),
21 + padding: const EdgeInsets.all(8),
22 + child: Column(
23 + mainAxisAlignment: MainAxisAlignment.center,
24 + children: [
25 + Text(title, style: TextStyle(fontWeight: FontWeight.bold)),
26 + Text(value, maxLines: 1, overflow: TextOverflow.ellipsis),
27 + ],
28 + ),
29 + );
30 + }
31 +
32 + @override
33 + Widget body(BuildContext context) {
34 + return Observer(
35 + builder: (_) {
36 + return GridView.count(
37 + padding: const EdgeInsets.all(16),
38 + crossAxisCount: 2,
39 + childAspectRatio: 25/9,
40 + crossAxisSpacing: 16,
41 + mainAxisSpacing: 16,
42 + children: [
43 + _buildSingleCell('Height (local)', viewModel.localBlockHeight ?? ''),
44 + _buildSingleCell('Height (node)', viewModel.nodeBlockHeight ?? ''),
45 + _buildSingleCell('Time', viewModel.tick.toString()),
46 + _buildSingleCell('Background Sync', viewModel.isBackgroundSyncing ? 'Enabled' : 'Disabled'),
47 + _buildSingleCell('Public View Key', viewModel.publicViewKey ?? ''),
48 + _buildSingleCell('Private View Key', viewModel.privateViewKey ?? ''),
49 + _buildSingleCell('Public Spend Key', viewModel.publicSpendKey ?? ''),
50 + _buildSingleCell('Private Spend Key', viewModel.privateSpendKey ?? ''),
51 + _buildSingleCell('Primary Address', viewModel.primaryAddress ?? ''),
52 + _buildSingleCell('Passphrase', viewModel.passphrase ?? ''),
53 + _buildSingleCell('Seed', viewModel.seed ?? ''),
54 + _buildSingleCell('Seed Legacy', viewModel.seedLegacy ?? ''),
55 + _enableBackgroundSyncButton(),
56 + _disableBackgroundSyncButton(),
57 + _refreshButton(),
58 + _manualRescanButton(),
59 + ],
60 + );
61 + },
62 + );
63 + }
64 +
65 + PrimaryButton _enableBackgroundSyncButton() {
66 + return PrimaryButton(
67 + text: "Enable background sync",
68 + color: Colors.purple,
69 + textColor: Colors.white,
70 + onPressed: () {
71 + viewModel.startBackgroundSync();
72 + },
73 + );
74 + }
75 +
76 + PrimaryButton _disableBackgroundSyncButton() {
77 + return PrimaryButton(
78 + text: "Disable background sync",
79 + color: Colors.purple,
80 + textColor: Colors.white,
81 + onPressed: () {
82 + viewModel.stopBackgroundSync();
83 + },
84 + );
85 + }
86 +
87 + PrimaryButton _refreshButton() {
88 + return PrimaryButton(
89 + text: viewModel.refreshTimer == null ? "Enable refresh" : "Disable refresh",
90 + color: Colors.purple,
91 + textColor: Colors.white,
92 + onPressed: () {
93 + if (viewModel.refreshTimer == null) {
94 + viewModel.startRefreshTimer();
95 + } else {
96 + viewModel.stopRefreshTimer();
97 + }
98 + },
99 + );
100 + }
101 +
102 + PrimaryButton _manualRescanButton() {
103 + return PrimaryButton(
104 + text: "Manual rescan",
105 + color: Colors.purple,
106 + textColor: Colors.white,
107 + onPressed: () {
108 + viewModel.manualRescan();
109 + },
110 + );
111 + }
112 +}
lib/src/screens/settings/background_sync_page.dart new
+91
@@ -0,0 +1,91 @@
1 +import 'dart:async';
2 +import 'dart:io';
3 +
4 +import 'package:cake_wallet/generated/i18n.dart';
5 +import 'package:cake_wallet/src/screens/base_page.dart';
6 +import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.dart';
7 +import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
8 +import 'package:cake_wallet/src/widgets/alert_with_no_action.dart.dart';
9 +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
10 +import 'package:cake_wallet/utils/show_pop_up.dart';
11 +import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
12 +import 'package:cake_wallet/view_model/settings/sync_mode.dart';
13 +import 'package:flutter/material.dart';
14 +import 'package:flutter_mobx/flutter_mobx.dart';
15 +
16 +class BackgroundSyncPage extends BasePage {
17 + BackgroundSyncPage(this.dashboardViewModel);
18 +
19 + @override
20 + String get title => S.current.background_sync;
21 +
22 + final DashboardViewModel dashboardViewModel;
23 +
24 + @override
25 + Widget body(BuildContext context) {
26 + return Container(
27 + padding: EdgeInsets.only(top: 10),
28 + child: Column(
29 + mainAxisSize: MainAxisSize.min,
30 + children: [
31 + if (dashboardViewModel.hasBatteryOptimization)
32 + Observer(builder: (context) {
33 + return SettingsSwitcherCell(
34 + title: S.current.unrestricted_background_service,
35 + value: !dashboardViewModel.batteryOptimizationEnabled,
36 + onValueChange: (_, bool value) {
37 + dashboardViewModel.disableBatteryOptimization();
38 + },
39 + );
40 + }),
41 + Observer(builder: (context) {
42 + return SettingsSwitcherCell(
43 + title: S.current.background_sync,
44 + value: dashboardViewModel.backgroundSyncEnabled,
45 + onValueChange: (dashboardViewModel.batteryOptimizationEnabled && dashboardViewModel.hasBatteryOptimization) ? (_, bool value) {
46 + unawaited(showPopUp(context: context, builder: (context) => AlertWithOneAction(
47 + alertTitle: S.current.background_sync,
48 + alertContent: S.current.unrestricted_background_service_notice,
49 + buttonText: S.current.ok,
50 + buttonAction: () => Navigator.of(context).pop(),
51 + )));
52 + } : (_, bool value) {
53 + if (value) {
54 + dashboardViewModel.enableBackgroundSync();
55 + } else {
56 + dashboardViewModel.disableBackgroundSync();
57 + }
58 + },
59 + );
60 + }),
61 + Observer(builder: (context) {
62 + return SettingsPickerCell<SyncMode>(
63 + title: S.current.background_sync_mode,
64 + items: SyncMode.all,
65 + displayItem: (SyncMode syncMode) => syncMode.name,
66 + selectedItem: dashboardViewModel.settingsStore.currentSyncMode,
67 + onItemSelected: (dashboardViewModel.batteryOptimizationEnabled && dashboardViewModel.hasBatteryOptimization) ? null : (syncMode) async {
68 + dashboardViewModel.setSyncMode(syncMode);
69 + });
70 + }),
71 +
72 + // Observer(builder: (context) {
73 + // return SettingsSwitcherCell(
74 + // title: S.current.background_sync_on_battery,
75 + // value: dashboardViewModel.backgroundSyncOnBattery,
76 + // onValueChange: (_, bool value) =>
77 + // dashboardViewModel.setBackgroundSyncOnBattery(value),
78 + // );
79 + // }),
80 + // Observer(builder: (context) {
81 + // return SettingsSwitcherCell(
82 + // title: S.current.background_sync_on_data,
83 + // value: dashboardViewModel.backgroundSyncOnData,
84 + // onValueChange: (_, bool value) => dashboardViewModel.setBackgroundSyncOnData(value),
85 + // );
86 + // }),
87 + ],
88 + ),
89 + );
90 + }
91 +}
lib/src/screens/settings/connection_sync_page.dart
+6 -45
@@ -44,56 +44,17 @@ class ConnectionSyncPage extends BasePage {
44 : S.current.rescan,
45 handler: (context) => Navigator.of(context).pushNamed(Routes.rescan),
46 ),
47 - if (DeviceInfo.instance.isMobile && FeatureFlag.isBackgroundSyncEnabled) ...[
48 - Observer(builder: (context) {
49 - return SettingsPickerCell<SyncMode>(
50 - title: S.current.background_sync_mode,
51 - items: SyncMode.all,
52 - displayItem: (SyncMode syncMode) => syncMode.name,
53 - selectedItem: dashboardViewModel.syncMode,
54 - onItemSelected: (syncMode) async {
55 - dashboardViewModel.setSyncMode(syncMode);
56 -
57 - if (Platform.isIOS) return;
58 -
59 - if (syncMode.type != SyncType.disabled) {
60 - final isDisabled = await isBatteryOptimizationDisabled();
61 -
62 - if (isDisabled) return;
63 -
64 - await showPopUp<void>(
65 - context: context,
66 - builder: (BuildContext dialogContext) {
67 - return AlertWithTwoActions(
68 - alertTitle: S.current.disableBatteryOptimization,
69 - alertContent: S.current.disableBatteryOptimizationDescription,
70 - leftButtonText: S.of(context).cancel,
71 - rightButtonText: S.of(context).ok,
72 - actionLeftButton: () => Navigator.of(dialogContext).pop(),
73 - actionRightButton: () async {
74 - await requestDisableBatteryOptimization();
75 -
76 - Navigator.of(dialogContext).pop();
77 - },
78 - );
79 - },
80 - );
81 - }
82 - });
83 - }),
84 - Observer(builder: (context) {
85 - return SettingsSwitcherCell(
86 - title: S.current.sync_all_wallets,
87 - value: dashboardViewModel.syncAll,
88 - onValueChange: (_, bool value) => dashboardViewModel.setSyncAll(value),
89 - );
90 - }),
91 - ],
47 ],
48 SettingsCellWithArrow(
49 title: S.current.manage_nodes,
50 handler: (context) => Navigator.of(context).pushNamed(Routes.manageNodes),
51 ),
52 + if (dashboardViewModel.hasBackgroundSync && Platform.isAndroid && FeatureFlag.isBackgroundSyncEnabled) ...[
53 + SettingsCellWithArrow(
54 + title: S.current.background_sync,
55 + handler: (context) => Navigator.of(context).pushNamed(Routes.backgroundSync),
56 + ),
57 + ],
58 Observer(
59 builder: (context) {
60 if (!dashboardViewModel.hasPowNodes) return const SizedBox();
lib/src/screens/settings/other_settings_page.dart
+7
@@ -10,6 +10,7 @@ import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.
10 import 'package:cake_wallet/src/screens/settings/widgets/settings_version_cell.dart';
11 import 'package:cake_wallet/view_model/settings/other_settings_view_model.dart';
12 import 'package:cw_core/wallet_type.dart';
13 +import 'package:flutter/foundation.dart';
14 import 'package:flutter/material.dart';
15 import 'package:flutter_mobx/flutter_mobx.dart';
16
@@ -63,6 +64,12 @@ class OtherSettingsPage extends BasePage {
64 handler: (BuildContext context) =>
65 Navigator.of(context).pushNamed(Routes.readDisclaimer),
66 ),
67 + if (kDebugMode && _otherSettingsViewModel.walletType == WalletType.monero)
68 + SettingsCellWithArrow(
69 + title: '[dev] monero background sync',
70 + handler: (BuildContext context) =>
71 + Navigator.of(context).pushNamed(Routes.devMoneroBackgroundSync),
72 + ),
73 Spacer(),
74 SettingsVersionCell(
75 title: S.of(context).version(_otherSettingsViewModel.currentVersion)),
lib/store/settings_store.dart
+7 -13
@@ -1,3 +1,4 @@
1 +import 'dart:async';
2 import 'dart:convert';
3 import 'dart:io';
4
@@ -8,7 +9,6 @@ import 'package:cake_wallet/core/secure_storage.dart';
9 import 'package:cake_wallet/di.dart';
10 import 'package:cake_wallet/entities/action_list_display_mode.dart';
11 import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
11 -import 'package:cake_wallet/entities/background_tasks.dart';
12 import 'package:cake_wallet/entities/balance_display_mode.dart';
13 import 'package:cake_wallet/entities/cake_2fa_preset_options.dart';
14 import 'package:cake_wallet/entities/country.dart';
@@ -46,6 +46,7 @@ import 'package:cw_core/utils/print_verbose.dart';
46 import 'package:cw_core/wallet_type.dart';
47 import 'package:device_info_plus/device_info_plus.dart';
48 import 'package:flutter/material.dart';
49 +import 'package:flutter_daemon/flutter_daemon.dart';
50 import 'package:hive/hive.dart';
51 import 'package:mobx/mobx.dart';
52 import 'package:shared_preferences/shared_preferences.dart';
@@ -57,7 +58,6 @@ class SettingsStore = SettingsStoreBase with _$SettingsStore;
58 abstract class SettingsStoreBase with Store {
59 SettingsStoreBase(
60 {required SecureStorage secureStorage,
60 - required BackgroundTasks backgroundTasks,
61 required SharedPreferences sharedPreferences,
62 required bool initialShouldShowMarketPlaceInDashboard,
63 required bool initialShowAddressBookPopupEnabled,
@@ -146,7 +146,6 @@ abstract class SettingsStoreBase with Store {
146 powNodes = ObservableMap<WalletType, Node>.of(powNodes),
147 _secureStorage = secureStorage,
148 _sharedPreferences = sharedPreferences,
149 - _backgroundTasks = backgroundTasks,
149 fiatCurrency = initialFiatCurrency,
150 balanceDisplayMode = initialBalanceDisplayMode,
151 shouldSaveRecipientAddress = initialSaveRecipientAddress,
@@ -303,11 +302,11 @@ abstract class SettingsStoreBase with Store {
302 PreferencesKey.shouldSaveRecipientAddressKey, shouldSaveRecipientAddress));
303
304 if (DeviceInfo.instance.isMobile) {
306 - setIsAppSecureNative(isAppSecure);
305 + unawaited(setIsAppSecureNative(isAppSecure));
306
307 reaction((_) => isAppSecure, (bool isAppSecure) {
308 sharedPreferences.setBool(PreferencesKey.isAppSecureKey, isAppSecure);
310 - setIsAppSecureNative(isAppSecure);
309 + unawaited(setIsAppSecureNative(isAppSecure));
310 });
311 }
312
@@ -402,14 +401,11 @@ abstract class SettingsStoreBase with Store {
401
402 reaction((_) => currentSyncMode, (SyncMode syncMode) {
403 sharedPreferences.setInt(PreferencesKey.syncModeKey, syncMode.type.index);
405 -
406 - _backgroundTasks.registerSyncTask(changeExisting: true);
404 + FlutterDaemon().startBackgroundSync(syncMode.frequency.inMinutes);
405 });
406
407 reaction((_) => currentSyncAll, (bool syncAll) {
408 sharedPreferences.setBool(PreferencesKey.syncAllKey, syncAll);
411 -
412 - _backgroundTasks.registerSyncTask(changeExisting: true);
409 });
410
411 reaction(
@@ -807,6 +803,7 @@ abstract class SettingsStoreBase with Store {
803
804 @observable
805 bool lookupsWellKnown;
806 +
807 @observable
808 SyncMode currentSyncMode;
809
@@ -843,7 +840,6 @@ abstract class SettingsStoreBase with Store {
840
841 final SecureStorage _secureStorage;
842 final SharedPreferences _sharedPreferences;
846 - final BackgroundTasks _backgroundTasks;
843
844 ObservableMap<WalletType, Node> nodes;
845 ObservableMap<WalletType, Node> powNodes;
@@ -885,7 +881,6 @@ abstract class SettingsStoreBase with Store {
881 ThemeBase? initialTheme}) async {
882 final sharedPreferences = await getIt.getAsync<SharedPreferences>();
883 final secureStorage = await getIt.get<SecureStorage>();
888 - final backgroundTasks = getIt.get<BackgroundTasks>();
884 final currentFiatCurrency = FiatCurrency.deserialize(
885 raw: sharedPreferences.getString(PreferencesKey.currentFiatCurrencyKey)!);
886 final savedCakePayCountryRaw = sharedPreferences.getString(PreferencesKey.currentCakePayCountry);
@@ -1157,7 +1152,7 @@ abstract class SettingsStoreBase with Store {
1152 }
1153
1154 final savedSyncMode = SyncMode.all.firstWhere((element) {
1160 - return element.type.index == (sharedPreferences.getInt(PreferencesKey.syncModeKey) ?? 0);
1155 + return element.type.index == (sharedPreferences.getInt(PreferencesKey.syncModeKey) ?? 2); // default to 2 - daily sync
1156 });
1157 final savedSyncAll = sharedPreferences.getBool(PreferencesKey.syncAllKey) ?? true;
1158
@@ -1339,7 +1334,6 @@ abstract class SettingsStoreBase with Store {
1334 shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
1335 initialEthereumTransactionPriority: ethereumTransactionPriority,
1336 initialPolygonTransactionPriority: polygonTransactionPriority,
1342 - backgroundTasks: backgroundTasks,
1337 initialSyncMode: savedSyncMode,
1338 initialSyncAll: savedSyncAll,
1339 shouldShowYatPopup: shouldShowYatPopup,
lib/utils/feature_flag.dart
+1 -1
@@ -4,6 +4,6 @@ class FeatureFlag {
4 static const bool isCakePayEnabled = false;
5 static const bool isExolixEnabled = true;
6 static const bool isInAppTorEnabled = false;
7 - static const bool isBackgroundSyncEnabled = false;
7 + static const bool isBackgroundSyncEnabled = true;
8 static const int verificationWordsCount = kDebugMode ? 0 : 2;
9 }
\ No newline at end of file
lib/view_model/dashboard/dashboard_view_model.dart
+77 -4
@@ -4,6 +4,7 @@ import 'dart:io' show Platform;
4
5 import 'package:cake_wallet/.secrets.g.dart' as secrets;
6 import 'package:cake_wallet/bitcoin/bitcoin.dart';
7 +import 'package:cake_wallet/core/background_sync.dart';
8 import 'package:cake_wallet/core/key_service.dart';
9 import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
10 import 'package:cake_wallet/entities/balance_display_mode.dart';
@@ -40,12 +41,14 @@ import 'package:cw_core/sync_status.dart';
41 import 'package:cw_core/transaction_history.dart';
42 import 'package:cw_core/transaction_info.dart';
43 import 'package:cw_core/utils/file.dart';
44 +import 'package:cw_core/utils/print_verbose.dart';
45 import 'package:cw_core/wallet_base.dart';
46 import 'package:cw_core/wallet_info.dart';
47 import 'package:cw_core/wallet_type.dart';
48 import 'package:eth_sig_util/util/utils.dart';
49 import 'package:flutter/foundation.dart';
50 import 'package:flutter/services.dart';
51 +import 'package:flutter_daemon/flutter_daemon.dart';
52 import 'package:http/http.dart' as http;
53 import 'package:mobx/mobx.dart';
54 import 'package:shared_preferences/shared_preferences.dart';
@@ -175,6 +178,8 @@ abstract class DashboardViewModelBase with Store {
178 isShowFirstYatIntroduction = false;
179 isShowSecondYatIntroduction = false;
180 isShowThirdYatIntroduction = false;
181 + unawaited(isBackgroundSyncEnabled());
182 + unawaited(isBatteryOptimizationEnabled());
183
184 final _wallet = wallet;
185
@@ -406,6 +411,11 @@ abstract class DashboardViewModelBase with Store {
411 @computed
412 bool get hasRescan => wallet.hasRescan;
413
414 + @computed
415 + bool get hasBackgroundSync => [
416 + WalletType.monero,
417 + ].contains(wallet.type);
418 +
419 @computed
420 bool get isMoneroViewOnly {
421 if (wallet.type != WalletType.monero) return false;
@@ -492,6 +502,69 @@ abstract class DashboardViewModelBase with Store {
502 @observable
503 late bool showDecredInfoCard;
504
505 + @observable
506 + bool backgroundSyncEnabled = false;
507 +
508 + @action
509 + Future<bool> isBackgroundSyncEnabled() async {
510 + if (!Platform.isAndroid) {
511 + return false;
512 + }
513 + final resp = await FlutterDaemon().getBackgroundSyncStatus();
514 + backgroundSyncEnabled = resp;
515 + return resp;
516 + }
517 +
518 + bool get hasBatteryOptimization => Platform.isAndroid;
519 +
520 + @observable
521 + bool batteryOptimizationEnabled = false;
522 +
523 + @action
524 + Future<bool> isBatteryOptimizationEnabled() async {
525 + if (!hasBatteryOptimization) {
526 + return false;
527 + }
528 + final resp = await FlutterDaemon().isBatteryOptimizationDisabled();
529 + batteryOptimizationEnabled = !resp;
530 + if (batteryOptimizationEnabled && await isBackgroundSyncEnabled()) {
531 + // If the battery optimization is enabled, we need to disable the background sync
532 + await disableBackgroundSync();
533 + }
534 + return resp;
535 + }
536 +
537 + @action
538 + Future<void> disableBatteryOptimization() async {
539 + final resp = await FlutterDaemon().requestDisableBatteryOptimization();
540 + unawaited((() async {
541 + // android doesn't return if the permission was granted, so we need to poll it,
542 + // minute should be enough for the fallback method (opening settings and changing the permission)
543 + for (var i = 0; i < 4 * 60; i++) {
544 + await Future.delayed(Duration(milliseconds: 250));
545 + await isBatteryOptimizationEnabled();
546 + }
547 + })());
548 + }
549 +
550 + @action
551 + Future<void> enableBackgroundSync() async {
552 + if (hasBatteryOptimization && batteryOptimizationEnabled) {
553 + disableBackgroundSync();
554 + return;
555 + }
556 + final resp = await FlutterDaemon().startBackgroundSync(settingsStore.currentSyncMode.frequency.inMinutes);
557 + printV("Background sync enabled: $resp");
558 + backgroundSyncEnabled = true;
559 + }
560 +
561 + @action
562 + Future<void> disableBackgroundSync() async {
563 + final resp = await FlutterDaemon().stopBackgroundSync();
564 + printV("Background sync disabled: $resp");
565 + backgroundSyncEnabled = false;
566 + }
567 +
568 @computed
569 bool get hasEnabledMwebBefore => settingsStore.hasEnabledMwebBefore;
570
@@ -797,11 +870,11 @@ abstract class DashboardViewModelBase with Store {
870 }
871 }
872
800 - @computed
801 - SyncMode get syncMode => settingsStore.currentSyncMode;
802 -
873 @action
804 - void setSyncMode(SyncMode syncMode) => settingsStore.currentSyncMode = syncMode;
874 + Future<void> setSyncMode(SyncMode syncMode) async {
875 + settingsStore.currentSyncMode = syncMode;
876 + await enableBackgroundSync();
877 + }
878
879 @computed
880 bool get syncAll => settingsStore.currentSyncAll;
lib/view_model/dev/monero_background_sync.dart new
+106
@@ -0,0 +1,106 @@
1 +import 'dart:async';
2 +
3 +import 'package:cake_wallet/core/key_service.dart';
4 +import 'package:cake_wallet/di.dart';
5 +import 'package:cake_wallet/monero/monero.dart';
6 +import 'package:cw_monero/monero_wallet.dart';
7 +import 'package:mobx/mobx.dart';
8 +import 'package:cw_core/wallet_base.dart';
9 +
10 +part 'monero_background_sync.g.dart';
11 +
12 +class DevMoneroBackgroundSync = DevMoneroBackgroundSyncBase with _$DevMoneroBackgroundSync;
13 +
14 +abstract class DevMoneroBackgroundSyncBase with Store {
15 + DevMoneroBackgroundSyncBase(WalletBase wallet) : wallet = wallet;
16 +
17 + final WalletBase wallet;
18 +
19 + @observable
20 + Timer? refreshTimer;
21 +
22 + @observable
23 + String? localBlockHeight;
24 +
25 + @observable
26 + String? nodeBlockHeight;
27 +
28 + @observable
29 + String? primaryAddress;
30 +
31 + @observable
32 + String? publicViewKey;
33 +
34 + @observable
35 + String? privateViewKey;
36 +
37 + @observable
38 + String? publicSpendKey;
39 +
40 + @observable
41 + String? privateSpendKey;
42 +
43 + @observable
44 + String? passphrase;
45 +
46 + @observable
47 + String? seed;
48 +
49 + @observable
50 + String? seedLegacy;
51 +
52 + @observable
53 + int tick = -1;
54 +
55 + @observable
56 + bool isBackgroundSyncing = false;
57 +
58 + Future<void> _setValues() async {
59 + final w = (wallet as MoneroWallet);
60 + localBlockHeight = (await monero!.getCurrentHeight()).toString();
61 + nodeBlockHeight = (await w.getNodeHeight()).toString();
62 + final keys = w.keys;
63 + primaryAddress = keys.primaryAddress;
64 + publicViewKey = keys.publicViewKey;
65 + privateViewKey = keys.privateViewKey;
66 + publicSpendKey = keys.publicSpendKey;
67 + privateSpendKey = keys.privateSpendKey;
68 + passphrase = keys.passphrase;
69 + seed = w.seed;
70 + seedLegacy = w.seedLegacy("English");
71 + tick = refreshTimer?.tick ?? -1;
72 + isBackgroundSyncing = w.isBackgroundSyncRunning;
73 + }
74 +
75 + @action
76 + Future<void> manualRescan() async {
77 + final w = (wallet as MoneroWallet);
78 + await wallet.rescan(height: await w.getNodeHeight() - 10000);
79 + }
80 +
81 + @action
82 + void startRefreshTimer() {
83 + refreshTimer = Timer.periodic(Duration(seconds: 1), (timer) async {
84 + await _setValues();
85 + });
86 + }
87 +
88 + @action
89 + void stopRefreshTimer() {
90 + refreshTimer?.cancel();
91 + refreshTimer = null;
92 + }
93 +
94 + @action
95 + void startBackgroundSync() {
96 + final w = (wallet as MoneroWallet);
97 + w.startBackgroundSync();
98 + }
99 +
100 + @action
101 + Future<void> stopBackgroundSync() async {
102 + final w = (wallet as MoneroWallet);
103 + final keyService = getIt.get<KeyService>();
104 + await w.stopBackgroundSync(await keyService.getWalletPassword(walletName: wallet.name));
105 + }
106 +}
lib/view_model/settings/sync_mode.dart
+6 -4
@@ -1,4 +1,4 @@
1 -enum SyncType { disabled, unobtrusive, aggressive }
1 +enum SyncType { aggresive, hourly, daily }
2
3 class SyncMode {
4 SyncMode(this.name, this.type, this.frequency);
@@ -8,8 +8,10 @@ class SyncMode {
8 final Duration frequency;
9
10 static final all = [
11 - SyncMode("Disabled", SyncType.disabled, Duration.zero),
12 - SyncMode("Unobtrusive", SyncType.unobtrusive, Duration(hours: 12)),
13 - SyncMode("Aggressive", SyncType.aggressive, Duration(hours: 3)),
11 + // **Technically** we could call aggressive option "15 minutes" but OS may "not feel like it",
12 + // so instead we will call it aggressive so user knows that it will be as frequent as possible.
13 + SyncMode("Aggressive", SyncType.aggresive, Duration(minutes: 15)),
14 + SyncMode("Hourly", SyncType.hourly, Duration(hours: 1)),
15 + SyncMode("Daily", SyncType.daily, Duration(hours: 18)), // yes this is straight up lie.
16 ];
17 }
lib/view_model/wallet_creation_vm.dart
-2
@@ -2,7 +2,6 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart';
2 import 'package:cake_wallet/core/execution_state.dart';
3 import 'package:cake_wallet/core/wallet_creation_service.dart';
4 import 'package:cake_wallet/di.dart';
5 -import 'package:cake_wallet/entities/background_tasks.dart';
5 import 'package:cake_wallet/entities/generate_name.dart';
6 import 'package:cake_wallet/entities/hash_wallet_identifier.dart';
7 import 'package:cake_wallet/generated/i18n.dart';
@@ -113,7 +112,6 @@ abstract class WalletCreationVMBase with Store {
112 walletInfo.address = wallet.walletAddresses.address;
113 await _walletInfoSource.add(walletInfo);
114 await _appStore.changeCurrentWallet(wallet);
116 - getIt.get<BackgroundTasks>().registerSyncTask();
115 _appStore.authenticationStore.allowedCreate();
116 state = ExecutedSuccessfullyState();
117 } catch (e, s) {
lib/view_model/wallet_groups_display_view_model.dart
+1
@@ -158,6 +158,7 @@ abstract class WalletGroupsDisplayViewModelBase with Store {
158 isCurrent: info.name == _appStore.wallet?.name && info.type == _appStore.wallet?.type,
159 isEnabled: availableWalletTypes.contains(info.type),
160 isTestnet: info.network?.toLowerCase().contains('testnet') ?? false,
161 + isHardware: info.isHardwareWallet,
162 );
163 }
164 }
lib/view_model/wallet_list/wallet_list_item.dart
+2
@@ -5,6 +5,7 @@ class WalletListItem {
5 required this.name,
6 required this.type,
7 required this.key,
8 + required this.isHardware,
9 this.isCurrent = false,
10 this.isEnabled = true,
11 this.isTestnet = false,
@@ -16,4 +17,5 @@ class WalletListItem {
17 final dynamic key;
18 final bool isEnabled;
19 final bool isTestnet;
20 + final bool isHardware;
21 }
lib/view_model/wallet_list/wallet_list_view_model.dart
+1
@@ -265,6 +265,7 @@ abstract class WalletListViewModelBase with Store {
265 info.type == _appStore.wallet?.type,
266 isEnabled: availableWalletTypes.contains(info.type),
267 isTestnet: info.network?.toLowerCase().contains('testnet') ?? false,
268 + isHardware: info.isHardwareWallet,
269 );
270 }
271 }
pubspec_base.yaml
+4 -1
@@ -68,7 +68,6 @@ dependencies:
68 git:
69 url: https://github.com/MrCyjaneK/device_display_brightness.git
70 ref: 4cac18c446ce686f3d75b1565badbd7da439bbd9
71 - workmanager: ^0.5.2
71 wakelock_plus: ^1.2.5
72 flutter_mailer:
73 git:
@@ -119,6 +118,10 @@ dependencies:
118 git:
119 url: https://github.com/cake-tech/blockchain_utils
120 ref: cake-update-v2
121 + flutter_daemon:
122 + git:
123 + url: https://github.com/MrCyjaneK/flutter_daemon
124 + ref: 5c369e0e69e6f459357b9802bc694a221397298a
125
126 dev_dependencies:
127 flutter_test:
res/values/strings_ar.arb
+3
@@ -69,6 +69,7 @@
69 "avg_savings": "متوسط مدخرات",
70 "awaitDAppProcessing": ".ﺔﺠﻟﺎﻌﻤﻟﺍ ﻦﻣ dApp ﻲﻬﺘﻨﻳ ﻰﺘﺣ ﺭﺎﻈﺘﻧﻻﺍ ﻰﺟﺮﻳ",
71 "awaiting_payment_confirmation": "في انتظار تأكيد الدفع",
72 + "background_sync": "مزامنة الخلفية",
73 "background_sync_mode": "وضع مزامنة الخلفية",
74 "backup": "نسخ الاحتياطي",
75 "backup_file": "ملف النسخ الاحتياطي",
@@ -922,6 +923,8 @@
923 "understand": "لقد فهمت",
924 "unlock": "الغاء القفل",
925 "unmatched_currencies": "عملة محفظتك الحالية لا تتطابق مع عملة QR الممسوحة ضوئيًا",
926 + "unrestricted_background_service": "خدمة خلفية غير مقيدة",
927 + "unrestricted_background_service_notice": "من أجل تمكين مزامنة الخلفية ، تحتاج إلى تمكين خدمة الخلفية غير المقيدة",
928 "unspent_change": "يتغير",
929 "unspent_coins_details_title": "تفاصيل العملات الغير المنفقة",
930 "unspent_coins_title": "العملات الغير المنفقة",
res/values/strings_bg.arb
+3
@@ -69,6 +69,7 @@
69 "avg_savings": "Средни спестявания",
70 "awaitDAppProcessing": "Моля, изчакайте dApp да завърши обработката.",
71 "awaiting_payment_confirmation": "Чака се потвърждение на плащането",
72 + "background_sync": "Фон Синхх",
73 "background_sync_mode": "Режим на синхронизиране на фона",
74 "backup": "Резервно копие",
75 "backup_file": "Резервно копие",
@@ -922,6 +923,8 @@
923 "understand": "Разбирам",
924 "unlock": "Отключване",
925 "unmatched_currencies": "Валутата на този портфейл не съвпада с тази от сканирания QR код",
926 + "unrestricted_background_service": "Неограничена фонова услуга",
927 + "unrestricted_background_service_notice": "За да активирате синхронизирането на фона, трябва да активирате неограничена фонова услуга",
928 "unspent_change": "Промяна",
929 "unspent_coins_details_title": "Подробности за неизползваните монети",
930 "unspent_coins_title": "Неизползвани монети",
res/values/strings_cs.arb
+3
@@ -69,6 +69,7 @@
69 "avg_savings": "Prům. ušetřeno",
70 "awaitDAppProcessing": "Počkejte, až dApp dokončí zpracování.",
71 "awaiting_payment_confirmation": "Čeká se na potvrzení platby",
72 + "background_sync": "Synchronizace pozadí",
73 "background_sync_mode": "Režim synchronizace pozadí",
74 "backup": "Záloha",
75 "backup_file": "Soubor se zálohou",
@@ -922,6 +923,8 @@
923 "understand": "Rozumím",
924 "unlock": "Odemknout",
925 "unmatched_currencies": "Měna vaší současné peněženky neodpovídá té v naskenovaném QR kódu",
926 + "unrestricted_background_service": "Neomezená služba na pozadí",
927 + "unrestricted_background_service_notice": "Chcete -li povolit synchronizaci pozadí, musíte povolit neomezenou službu na pozadí",
928 "unspent_change": "Změna",
929 "unspent_coins_details_title": "Podrobnosti o neutracených mincích",
930 "unspent_coins_title": "Neutracené mince",
res/values/strings_de.arb
+3
@@ -69,6 +69,7 @@
69 "avg_savings": "Durchschn. Einsparungen",
70 "awaitDAppProcessing": "Bitte warten Sie, bis die dApp die Verarbeitung abgeschlossen hat.",
71 "awaiting_payment_confirmation": "Warten auf Zahlungsbestätigung",
72 + "background_sync": "Hintergrundsynchronisation",
73 "background_sync_mode": "Hintergrundsynchronisierungsmodus",
74 "backup": "Sicherung",
75 "backup_file": "Sicherungsdatei",
@@ -924,6 +925,8 @@
925 "understand": "Ich verstehe",
926 "unlock": "Freischalten",
927 "unmatched_currencies": "Die Währung Ihres aktuellen Wallets stimmt nicht mit der des gescannten QR überein",
928 + "unrestricted_background_service": "Uneingeschränkter Hintergrunddienst",
929 + "unrestricted_background_service_notice": "Um die Hintergrundsynchronisierung zu ermöglichen, müssen Sie einen uneingeschränkten Hintergrundservice aktivieren",
930 "unspent_change": "Wechselgeld",
931 "unspent_coins_details_title": "Details zu nicht ausgegebenen Coins",
932 "unspent_coins_title": "Nicht ausgegebene Coins",
res/values/strings_en.arb
+3
@@ -69,6 +69,7 @@
69 "avg_savings": "Avg. Savings",
70 "awaitDAppProcessing": "Kindly wait for the dApp to finish processing.",
71 "awaiting_payment_confirmation": "Awaiting Payment Confirmation",
72 + "background_sync": "Background sync",
73 "background_sync_mode": "Background sync mode",
74 "backup": "Backup",
75 "backup_file": "Backup file",
@@ -923,6 +924,8 @@
924 "understand": "I understand",
925 "unlock": "Unlock",
926 "unmatched_currencies": "Your current wallet's currency does not match that of the scanned QR",
927 + "unrestricted_background_service": "Unrestricted background service",
928 + "unrestricted_background_service_notice": "In order to enable background sync you need to enable unrestricted background service",
929 "unspent_change": "Change",
930 "unspent_coins_details_title": "Unspent coins details",
931 "unspent_coins_title": "Unspent coins",
res/values/strings_es.arb
+3
@@ -69,6 +69,7 @@
69 "avg_savings": "Ahorro promedio",
70 "awaitDAppProcessing": "Espere a que la dApp termine de procesarse.",
71 "awaiting_payment_confirmation": "Esperando confirmación de pago",
72 + "background_sync": "Sincronización de fondo",
73 "background_sync_mode": "Modo de sincronización en segundo plano",
74 "backup": "Apoyo",
75 "backup_file": "Archivo de respaldo",
@@ -923,6 +924,8 @@
924 "understand": "Entiendo",
925 "unlock": "desbloquear",
926 "unmatched_currencies": "La moneda de tu billetera actual no coincide con la del QR escaneado",
927 + "unrestricted_background_service": "Servicio de antecedentes sin restricciones",
928 + "unrestricted_background_service_notice": "Para habilitar la sincronización de antecedentes, debe habilitar el servicio de fondo sin restricciones",
929 "unspent_change": "Cambiar",
930 "unspent_coins_details_title": "Detalles de monedas no gastadas",
931 "unspent_coins_title": "Monedas no gastadas",
res/values/strings_fr.arb
+3
@@ -69,6 +69,7 @@
69 "avg_savings": "Économies moy.",
70 "awaitDAppProcessing": "Veuillez attendre que l'application décentralisée (dApp) termine le traitement.",
71 "awaiting_payment_confirmation": "En attente de confirmation de paiement",
72 + "background_sync": "Synchronisation de fond",
73 "background_sync_mode": "Mode de synchronisation en arrière-plan",
74 "backup": "Sauvegarde",
75 "backup_file": "Fichier de sauvegarde",
@@ -922,6 +923,8 @@
923 "understand": "J'ai compris",
924 "unlock": "Ouvrir",
925 "unmatched_currencies": "La devise de votre portefeuille (wallet) actuel ne correspond pas à celle du QR code scanné",
926 + "unrestricted_background_service": "Service de fond sans restriction",
927 + "unrestricted_background_service_notice": "Afin d'activer la synchronisation des antécédents, vous devez activer le service de fond sans restriction",
928 "unspent_change": "Monnaie",
929 "unspent_coins_details_title": "Détails des pièces (coins) non dépensées",
930 "unspent_coins_title": "Pièces (coins) non dépensées",
res/values/strings_ha.arb
+3
@@ -69,6 +69,7 @@
69 "avg_savings": "Matsakaici Adana",
70 "awaitDAppProcessing": "Da fatan za a jira dApp ya gama aiki.",
71 "awaiting_payment_confirmation": "Ana jiran Tabbacin Biyan Kuɗi",
72 + "background_sync": "Tunawa da Setc",
73 "background_sync_mode": "Yanayin Sync",
74 "backup": "Ajiyayyen",
75 "backup_file": "Ajiyayyen fayil",
@@ -924,6 +925,8 @@
925 "understand": "na gane",
926 "unlock": "Buɗe",
927 "unmatched_currencies": "Nau'in walat ɗin ku na yanzu bai dace da na lambar QR da aka bincika ba",
928 + "unrestricted_background_service": "Sabis na baya",
929 + "unrestricted_background_service_notice": "Don ba da damar Sync na asali kuna buƙatar kunna sabis na baya da ba a santa ba",
930 "unspent_change": "Canza",
931 "unspent_coins_details_title": "Bayanan tsabar kudi da ba a kashe ba",
932 "unspent_coins_title": "Tsabar da ba a kashe ba",
res/values/strings_hi.arb
+3
@@ -69,6 +69,7 @@
69 "avg_savings": "औसत बचत",
70 "awaitDAppProcessing": "कृपया डीएपी की प्रोसेसिंग पूरी होने तक प्रतीक्षा करें।",
71 "awaiting_payment_confirmation": "भुगतान की पुष्टि की प्रतीक्षा में",
72 + "background_sync": "पृष्ठभूमि सिंक",
73 "background_sync_mode": "बैकग्राउंड सिंक मोड",
74 "backup": "बैकअप",
75 "backup_file": "बैकअपफ़ाइल",
@@ -924,6 +925,8 @@
925 "understand": "मुझे समझ",
926 "unlock": "अनलॉक",
927 "unmatched_currencies": "आपके वर्तमान वॉलेट की मुद्रा स्कैन किए गए क्यूआर से मेल नहीं खाती",
928 + "unrestricted_background_service": "अप्रतिबंधित पृष्ठभूमि सेवा",
929 + "unrestricted_background_service_notice": "पृष्ठभूमि सिंक को सक्षम करने के लिए आपको अप्रतिबंधित पृष्ठभूमि सेवा को सक्षम करने की आवश्यकता है",
930 "unspent_change": "परिवर्तन",
931 "unspent_coins_details_title": "अव्ययित सिक्कों का विवरण",
932 "unspent_coins_title": "खर्च न किए गए सिक्के",
res/values/strings_hr.arb
+3
@@ -69,6 +69,7 @@
69 "avg_savings": "Prosj. ušteda",
70 "awaitDAppProcessing": "Molimo pričekajte da dApp završi obradu.",
71 "awaiting_payment_confirmation": "Čeka se potvrda plaćanja",
72 + "background_sync": "Sinkronizacija pozadine",
73 "background_sync_mode": "Sinkronizacija u pozadini",
74 "backup": "Sigurnosna kopija",
75 "backup_file": "Sigurnosna kopija datoteke",
@@ -922,6 +923,8 @@
923 "understand": "Razumijem",
924 "unlock": "Otključati",
925 "unmatched_currencies": "Valuta vašeg trenutnog novčanika ne odgovara onoj na skeniranom QR-u",
926 + "unrestricted_background_service": "Neograničena pozadinska usluga",
927 + "unrestricted_background_service_notice": "Da biste omogućili sinkronizaciju pozadine, morate omogućiti neograničenu pozadinsku uslugu",
928 "unspent_change": "Promijeniti",
929 "unspent_coins_details_title": "Nepotrošeni detalji o novčićima",
930 "unspent_coins_title": "Nepotrošeni novčići",
res/values/strings_hy.arb
+3
@@ -69,6 +69,7 @@
69 "avg_savings": "Միջին խնայողություն",
70 "awaitDAppProcessing": "Խնդրեմ սպասեք, մինչև դիմումը կավարտի մշակումը։",
71 "awaiting_payment_confirmation": "Վճարման հաստատման սպասում",
72 + "background_sync": "Ֆոնային համաժամեցում",
73 "background_sync_mode": "Հետին պլանի համաժամացման ռեժիմ",
74 "backup": "Կրկնօրինակ",
75 "backup_file": "Կրկնօրինակի ֆայլ",
@@ -920,6 +921,8 @@
921 "understand": "Ես հասկանում եմ",
922 "unlock": "Բացել",
923 "unmatched_currencies": "Ձեր ընթացիկ դրամապանակի արժույթը չի համապատասխանում սկանավորված QR կոդի արժույթին",
924 + "unrestricted_background_service": "Անսահմանափակ ֆոնային ծառայություն",
925 + "unrestricted_background_service_notice": "Ֆոնային համաժամացման համար անհրաժեշտ է միացնել անսահմանափակ ֆոնային ծառայություն",
926 "unspent_change": "Մնացորդ",
927 "unspent_coins_details_title": "Չծախսված արժույթների մանրամասները",
928 "unspent_coins_title": "Չծախսված արժույթներ",
res/values/strings_id.arb
+3
@@ -69,6 +69,7 @@
69 "avg_savings": "Rata-rata Pembayaran",
70 "awaitDAppProcessing": "Mohon tunggu hingga dApp menyelesaikan pemrosesan.",
71 "awaiting_payment_confirmation": "Menunggu Konfirmasi Pembayaran",
72 + "background_sync": "Sinkronisasi Latar Belakang",
73 "background_sync_mode": "Mode Sinkronisasi Latar Belakang",
74 "backup": "Cadangan",
75 "backup_file": "File cadangan",
@@ -925,6 +926,8 @@
926 "understand": "Saya mengerti",
927 "unlock": "Membuka kunci",
928 "unmatched_currencies": "Mata uang dompet Anda saat ini tidak cocok dengan yang ditandai QR",
929 + "unrestricted_background_service": "Layanan latar belakang tidak terbatas",
930 + "unrestricted_background_service_notice": "Untuk mengaktifkan sinkronisasi latar belakang, Anda perlu mengaktifkan layanan latar belakang yang tidak dibatasi",
931 "unspent_change": "Mengubah",
932 "unspent_coins_details_title": "Rincian koin yang tidak terpakai",
933 "unspent_coins_title": "Koin yang tidak terpakai",
res/values/strings_it.arb
+3
@@ -69,6 +69,7 @@
69 "avg_savings": "Risparmio medio",
70 "awaitDAppProcessing": "Attendi gentilmente che la dApp termini l'elaborazione.",
71 "awaiting_payment_confirmation": "In attesa di conferma del pagamento",
72 + "background_sync": "Sincronizzazione in background",
73 "background_sync_mode": "Modalità di sincronizzazione in background",
74 "backup": "Backup",
75 "backup_file": "Backup file",
@@ -923,6 +924,8 @@
924 "understand": "Capisco",
925 "unlock": "Sblocca",
926 "unmatched_currencies": "La valuta del tuo portafoglio attuale non corrisponde a quella del QR scansionato",
927 + "unrestricted_background_service": "Servizio di background senza restrizioni",
928 + "unrestricted_background_service_notice": "Per abilitare la sincronizzazione in background è necessario abilitare il servizio di background senza restrizioni",
929 "unspent_change": "Resto",
930 "unspent_coins_details_title": "Dettagli sulle monete non spese",
931 "unspent_coins_title": "Monete non spese",
res/values/strings_ja.arb
+3
@@ -69,6 +69,7 @@
69 "avg_savings": "平均節約額",
70 "awaitDAppProcessing": "dAppの処理が完了するまでお待ちください。",
71 "awaiting_payment_confirmation": "支払い確認を待っています",
72 + "background_sync": "背景同期",
73 "background_sync_mode": "バックグラウンド同期モード",
74 "backup": "バックアップ",
75 "backup_file": "バックアップファイル",
@@ -923,6 +924,8 @@
924 "understand": "わかります",
925 "unlock": "ロックを解除します",
926 "unmatched_currencies": "現在のウォレットの通貨がスキャンされたQRの通貨と一致しません",
927 + "unrestricted_background_service": "無制限のバックグラウンドサービス",
928 + "unrestricted_background_service_notice": "バックグラウンドの同期を有​​効にするには、無制限のバックグラウンドサービスを有効にする必要があります",
929 "unspent_change": "変化",
930 "unspent_coins_details_title": "未使用のコインの詳細",
931 "unspent_coins_title": "未使用のコイン",
res/values/strings_ko.arb
+3
@@ -69,6 +69,7 @@
69 "avg_savings": "평균 절감액",
70 "awaitDAppProcessing": "dApp이 처리를 마칠 때까지 기다려주세요.",
71 "awaiting_payment_confirmation": "결제 확인 대기 중",
72 + "background_sync": "배경 동기화",
73 "background_sync_mode": "백그라운드 동기화 모드",
74 "backup": "지원",
75 "backup_file": "백업 파일",
@@ -922,6 +923,8 @@
923 "understand": "이해 했어요",
924 "unlock": "터놓다",
925 "unmatched_currencies": "현재 지갑의 통화가 스캔한 QR의 통화와 일치하지 않습니다.",
926 + "unrestricted_background_service": "무제한 배경 서비스",
927 + "unrestricted_background_service_notice": "배경 동기화를 활성화하려면 무제한 배경 서비스를 활성화해야합니다.",
928 "unspent_change": "변화",
929 "unspent_coins_details_title": "사용하지 않은 동전 세부 정보",
930 "unspent_coins_title": "사용하지 않은 동전",
res/values/strings_my.arb
+3
@@ -69,6 +69,7 @@
69 "avg_savings": "ပျမ်းမျှ စုဆောင်းငွေ",
70 "awaitDAppProcessing": "ကျေးဇူးပြု၍ dApp ကို စီမံလုပ်ဆောင်ခြင်း အပြီးသတ်ရန် စောင့်ပါ။",
71 "awaiting_payment_confirmation": "ငွေပေးချေမှု အတည်ပြုချက်ကို စောင့်မျှော်နေပါသည်။",
72 + "background_sync": "နောက်ခံထပ်တူပြုခြင်း",
73 "background_sync_mode": "နောက်ခံထပ်တူပြုခြင်း mode ကို",
74 "backup": "မိတ္တူ",
75 "backup_file": "အရန်ဖိုင်",
@@ -922,6 +923,8 @@
923 "understand": "ကျွန်တော်နားလည်ပါတယ်",
924 "unlock": "သော့ဖွင့်",
925 "unmatched_currencies": "သင့်လက်ရှိပိုက်ဆံအိတ်၏ငွေကြေးသည် စကင်ဖတ်ထားသော QR နှင့် မကိုက်ညီပါ။",
926 + "unrestricted_background_service": "အကန့်အသတ်မရှိနောက်ခံဝန်ဆောင်မှု",
927 + "unrestricted_background_service_notice": "နောက်ခံထပ်တူပြုခြင်းကို Enable လုပ်ရန်သင်ကန့်သတ်ထားသောနောက်ခံဝန်ဆောင်မှုကိုဖွင့်ရန်လိုအပ်သည်",
928 "unspent_change": "ပေြာင်းလဲခြင်း",
929 "unspent_coins_details_title": "အသုံးမဝင်သော အကြွေစေ့အသေးစိတ်များ",
930 "unspent_coins_title": "အသုံးမဝင်သော အကြွေစေ့များ",
res/values/strings_nl.arb
+3
@@ -69,6 +69,7 @@
69 "avg_savings": "Gem. besparingen",
70 "awaitDAppProcessing": "Wacht tot de dApp klaar is met verwerken.",
71 "awaiting_payment_confirmation": "In afwachting van betalingsbevestiging",
72 + "background_sync": "Achtergrondsynchronisatie",
73 "background_sync_mode": "Achtergrondsynchronisatiemodus",
74 "backup": "Back-up",
75 "backup_file": "Backup bestand",
@@ -922,6 +923,8 @@
923 "understand": "Ik begrijp het",
924 "unlock": "Ontgrendelen",
925 "unmatched_currencies": "De valuta van uw huidige portemonnee komt niet overeen met die van de gescande QR",
926 + "unrestricted_background_service": "Onbeperkte achtergrondservice",
927 + "unrestricted_background_service_notice": "Om achtergrondsynchronisatie in te schakelen, moet u onbeperkte achtergrondservice inschakelen",
928 "unspent_change": "Wijziging",
929 "unspent_coins_details_title": "Details van niet-uitgegeven munten",
930 "unspent_coins_title": "Ongebruikte munten",
res/values/strings_pl.arb
+3
@@ -69,6 +69,7 @@
69 "avg_savings": "Śr. oszczędności",
70 "awaitDAppProcessing": "Poczekaj, aż dApp zakończy przetwarzanie.",
71 "awaiting_payment_confirmation": "Oczekiwanie na potwierdzenie płatności",
72 + "background_sync": "Synchronizacja tła",
73 "background_sync_mode": "Tryb synchronizacji w tle",
74 "backup": "Kopia zapasowa",
75 "backup_file": "Plik kopii zapasowej",
@@ -922,6 +923,8 @@
923 "understand": "Rozumiem",
924 "unlock": "Odblokować",
925 "unmatched_currencies": "Waluta Twojego obecnego portfela nie zgadza się z waluctą zeskanowanego kodu QR",
926 + "unrestricted_background_service": "Nieograniczona usługa w tle",
927 + "unrestricted_background_service_notice": "Aby włączyć synchronizację tła, musisz włączyć nieograniczoną usługę w tle",
928 "unspent_change": "Zmiana",
929 "unspent_coins_details_title": "Szczegóły niewydanych monet",
930 "unspent_coins_title": "Niewydane monety",
res/values/strings_pt.arb
+3
@@ -69,6 +69,7 @@
69 "avg_savings": "Poupança média",
70 "awaitDAppProcessing": "Aguarde até que o dApp termine o processamento.",
71 "awaiting_payment_confirmation": "Aguardando confirmação de pagamento",
72 + "background_sync": "Sincronização de fundo",
73 "background_sync_mode": "Modo de sincronização em segundo plano",
74 "backup": "Cópia de segurança",
75 "backup_file": "Arquivo de backup",
@@ -924,6 +925,8 @@
925 "understand": "Entendo",
926 "unlock": "Desbloquear",
927 "unmatched_currencies": "A moeda da sua carteira atual não corresponde à do QR digitalizado",
928 + "unrestricted_background_service": "Serviço de fundo irrestrito",
929 + "unrestricted_background_service_notice": "Para ativar a sincronização de fundo, você precisa ativar o serviço de fundo irrestrito",
930 "unspent_change": "Troco",
931 "unspent_coins_details_title": "Detalhes de moedas não gastas",
932 "unspent_coins_title": "Moedas não gastas",
res/values/strings_ru.arb
+3
@@ -69,6 +69,7 @@
69 "avg_savings": "Средняя экономия",
70 "awaitDAppProcessing": "Пожалуйста, подождите, пока dApp завершит обработку.",
71 "awaiting_payment_confirmation": "Ожидается подтверждения платежа",
72 + "background_sync": "Фоновая синхронизация",
73 "background_sync_mode": "Режим фоновой синхронизации",
74 "backup": "Резервная копия",
75 "backup_file": "Файл резервной копии",
@@ -923,6 +924,8 @@
924 "understand": "Понятно",
925 "unlock": "Разблокировать",
926 "unmatched_currencies": "Валюта вашего текущего кошелька не соответствует валюте отсканированного QR-кода.",
927 + "unrestricted_background_service": "Неограниченная фоновая служба",
928 + "unrestricted_background_service_notice": "Чтобы включить фона синхронизации, необходимо включить неограниченную фоновую службу",
929 "unspent_change": "Изменять",
930 "unspent_coins_details_title": "Сведения о неизрасходованных монетах",
931 "unspent_coins_title": "Неизрасходованные монеты",
res/values/strings_th.arb
+3
@@ -69,6 +69,7 @@
69 "avg_savings": "ประหยัดเฉลี่ย",
70 "awaitDAppProcessing": "โปรดรอให้ dApp ประมวลผลเสร็จสิ้น",
71 "awaiting_payment_confirmation": "รอการยืนยันการชำระเงิน",
72 + "background_sync": "การซิงค์พื้นหลัง",
73 "background_sync_mode": "โหมดซิงค์พื้นหลัง",
74 "backup": "สำรองข้อมูล",
75 "backup_file": "ไฟล์สำรองข้อมูล",
@@ -922,6 +923,8 @@
923 "understand": "ฉันเข้าใจ",
924 "unlock": "ปลดล็อค",
925 "unmatched_currencies": "สกุลเงินของกระเป๋าปัจจุบันของคุณไม่ตรงกับของ QR ที่สแกน",
926 + "unrestricted_background_service": "บริการพื้นหลังที่ไม่ จำกัด",
927 + "unrestricted_background_service_notice": "ในการเปิดใช้งานการซิงค์พื้นหลังคุณต้องเปิดใช้งานบริการพื้นหลังที่ไม่ จำกัด",
928 "unspent_change": "เปลี่ยน",
929 "unspent_coins_details_title": "รายละเอียดเหรียญที่ไม่ได้ใช้",
930 "unspent_coins_title": "เหรียญที่ไม่ได้ใช้",
res/values/strings_tl.arb
+3
@@ -69,6 +69,7 @@
69 "avg_savings": "Avg. Matitipid",
70 "awaitDAppProcessing": "Pakihintay na matapos ang pagproseso ng dApp.",
71 "awaiting_payment_confirmation": "Nanghihintay ng Kumpirmasyon sa Pagbabayad",
72 + "background_sync": "Pag -sync ng background",
73 "background_sync_mode": "Background sync mode",
74 "backup": "Backup",
75 "backup_file": "Backup na file",
@@ -922,6 +923,8 @@
923 "understand": "Naiitindihan ko",
924 "unlock": "I-unlock",
925 "unmatched_currencies": "Hindi tumutugma ang pera ng iyong kasalukuyang wallet sa na-scan na QR",
926 + "unrestricted_background_service": "Hindi pinigilan na serbisyo sa background",
927 + "unrestricted_background_service_notice": "Upang paganahin ang pag -sync ng background kailangan mong paganahin ang hindi pinigilan na serbisyo sa background",
928 "unspent_change": "Sukli",
929 "unspent_coins_details_title": "Mga detalye ng mga hindi nagastos na barya",
930 "unspent_coins_title": "Mga hindi nagamit na barya",
res/values/strings_tr.arb
+3
@@ -69,6 +69,7 @@
69 "avg_savings": "Ortalama Tasarruf",
70 "awaitDAppProcessing": "Lütfen dApp'in işlemeyi bitirmesini bekleyin.",
71 "awaiting_payment_confirmation": "Ödemenin onaylanması bekleniyor",
72 + "background_sync": "Arka plan senkronizasyonu",
73 "background_sync_mode": "Arka Plan Senkronizasyon Modu",
74 "backup": "Yedek",
75 "backup_file": "Yedek dosyası",
@@ -922,6 +923,8 @@
923 "understand": "Anladım",
924 "unlock": "Kilidini aç",
925 "unmatched_currencies": "Mevcut cüzdanınızın para birimi taranan QR ile eşleşmiyor",
926 + "unrestricted_background_service": "Sınırsız arka plan hizmeti",
927 + "unrestricted_background_service_notice": "Arka plan senkronizasyonunu etkinleştirmek için sınırsız arka plan hizmetini etkinleştirmeniz gerekir",
928 "unspent_change": "Değiştirmek",
929 "unspent_coins_details_title": "Harcanmamış koin detayları",
930 "unspent_coins_title": "Harcanmamış koinler",
res/values/strings_uk.arb
+3
@@ -69,6 +69,7 @@
69 "avg_savings": "Середня економія",
70 "awaitDAppProcessing": "Зачекайте, доки dApp завершить обробку.",
71 "awaiting_payment_confirmation": "Очікується підтвердження платежу",
72 + "background_sync": "Фонове синхронізація",
73 "background_sync_mode": "Фоновий режим синхронізації",
74 "backup": "Резервна копія",
75 "backup_file": "Файл резервної копії",
@@ -923,6 +924,8 @@
924 "understand": "Зрозуміло",
925 "unlock": "Розблокувати",
926 "unmatched_currencies": "Валюта вашого гаманця не збігається з валютою сканованого QR-коду",
927 + "unrestricted_background_service": "Необмежена фонова послуга",
928 + "unrestricted_background_service_notice": "Для того, щоб увімкнути фонову синхронізацію, вам потрібно ввімкнути необмежену фонову послугу",
929 "unspent_change": "Зміна",
930 "unspent_coins_details_title": "Відомості про невитрачені монети",
931 "unspent_coins_title": "Невитрачені монети",
res/values/strings_ur.arb
+3
@@ -69,6 +69,7 @@
69 "avg_savings": "اوسط بچت",
70 "awaitDAppProcessing": "۔ﮟﯾﺮﮐ ﺭﺎﻈﺘﻧﺍ ﺎﮐ ﮯﻧﻮﮨ ﻞﻤﮑﻣ ﮓﻨﺴﯿﺳﻭﺮﭘ ﮯﮐ dApp ﻡﺮﮐ ﮦﺍﺮﺑ",
71 "awaiting_payment_confirmation": "ادائیگی کی تصدیق کے منتظر",
72 + "background_sync": "پس منظر کی ہم آہنگی",
73 "background_sync_mode": "پس منظر کی مطابقت پذیری کا موڈ",
74 "backup": "بیک اپ",
75 "backup_file": "بیک اپ فائل",
@@ -924,6 +925,8 @@
925 "understand": "میں سمجھتا ہوں۔",
926 "unlock": "غیر مقفل",
927 "unmatched_currencies": "آپ کے پرس کی موجودہ کرنسی اسکین شدہ QR سے مماثل نہیں ہے۔",
928 + "unrestricted_background_service": "غیر محدود پس منظر کی خدمت",
929 + "unrestricted_background_service_notice": "پس منظر کی مطابقت پذیری کو قابل بنانے کے ل you آپ کو غیر محدود پس منظر کی خدمت کو فعال کرنے کی ضرورت ہے",
930 "unspent_change": "تبدیل کریں",
931 "unspent_coins_details_title": "غیر خرچ شدہ سککوں کی تفصیلات",
932 "unspent_coins_title": "غیر خرچ شدہ سکے ۔",
res/values/strings_vi.arb
+3
@@ -69,6 +69,7 @@
69 "avg_savings": "Tiết kiệm trung bình",
70 "awaitDAppProcessing": "Vui lòng đợi ứng dụng phi tập trung hoàn thành xử lý.",
71 "awaiting_payment_confirmation": "Đang chờ xác nhận thanh toán",
72 + "background_sync": "Đồng bộ nền",
73 "background_sync_mode": "Chế độ đồng bộ nền",
74 "backup": "Sao lưu",
75 "backup_file": "Tập tin sao lưu",
@@ -919,6 +920,8 @@
920 "understand": "Tôi hiểu",
921 "unlock": "Mở khóa",
922 "unmatched_currencies": "Tiền tệ của ví hiện tại của bạn không khớp với QR đã quét",
923 + "unrestricted_background_service": "Dịch vụ nền không giới hạn",
924 + "unrestricted_background_service_notice": "Để cho phép đồng bộ hóa nền, bạn cần bật dịch vụ nền không giới hạn",
925 "unspent_change": "Tiền thối",
926 "unspent_coins_details_title": "Chi tiết các đồng tiền chưa chi tiêu",
927 "unspent_coins_title": "Các đồng tiền chưa chi tiêu",
res/values/strings_yo.arb
+3
@@ -69,6 +69,7 @@
69 "avg_savings": "Ìpamọ́ l’óòrèkóòrè",
70 "awaitDAppProcessing": "Fi inurere duro fun dApp lati pari sisẹ.",
71 "awaiting_payment_confirmation": "À ń dúró de ìjẹ́rìísí àránṣẹ́",
72 + "background_sync": "Imuṣiṣẹ Labẹ",
73 "background_sync_mode": "Ipo amuṣiṣẹpọ abẹlẹ",
74 "backup": "Ṣẹ̀dà",
75 "backup_file": "Ṣẹ̀dà akọsílẹ̀",
@@ -923,6 +924,8 @@
924 "understand": "Ó ye mi",
925 "unlock": "Sisalẹ",
926 "unmatched_currencies": "Irú owó ti àpamọ́wọ́ yín kì í ṣe irú ti yíya àmì ìlujá",
927 + "unrestricted_background_service": "Iṣẹ ipilẹṣẹ ti ko nilẹ",
928 + "unrestricted_background_service_notice": "Ni ibere lati mu ṣiṣẹpọ lẹhin ti o nilo lati ṣiṣẹ iṣẹ iṣẹ ti ko ni ibatan",
929 "unspent_change": "Yipada",
930 "unspent_coins_details_title": "Àwọn owó ẹyọ t'á kò tí ì san",
931 "unspent_coins_title": "Àwọn owó ẹyọ t'á kò tí ì san",
res/values/strings_zh.arb
+3
@@ -69,6 +69,7 @@
69 "avg_savings": "平均储蓄",
70 "awaitDAppProcessing": "请等待 dApp 处理完成。",
71 "awaiting_payment_confirmation": "等待付款确认",
72 + "background_sync": "背景同步",
73 "background_sync_mode": "后台同步模式",
74 "backup": "备份",
75 "backup_file": "备份文件",
@@ -922,6 +923,8 @@
923 "understand": "我已知晓",
924 "unlock": "开锁",
925 "unmatched_currencies": "您当前钱包的货币与扫描的 QR 的货币不匹配",
926 + "unrestricted_background_service": "不受限制的背景服务",
927 + "unrestricted_background_service_notice": "为了启用背景同步,您需要启用无限制的背景服务",
928 "unspent_change": "改变",
929 "unspent_coins_details_title": "未使用代幣詳情",
930 "unspent_coins_title": "未使用的硬幣",