CW-1000 Background sync improvements (#2142)

* feat: background sync improvements

- dev options on ci build cherrypick
- add permissions for background sync to AndroidManifestBase
- enable desugaring + update java compatibility to 17
- update walletconnect_flutter_v2
- update ens_dart
- update nostr_tools
- add notification for new transactions found in background
- expose more settings from flutter_daemon in UI
- remove battery optimization setting when it's already disabled
- fix notification permission handling
- fix background sync last trigger saving
- prevent notifications from being duplicated

* potential fix for multiple notifications firing for the same tx

* improve logging in background sync

* ui improvements to ignore battery optimization popup

* feat: logs for bg sync
disable decred bgsync

* fix: call store() directly to be sure that it is writing the data

* chore: rename logs to background sync logs

* Update lib/view_model/dashboard/dashboard_view_model.dart

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

* chore: remove unused key

---------

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
cyan 2025-04-24 19:06:43 +02:00 committed by GitHub
parent e6c9cf54fb
commit 02e74b5997
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
48 changed files with 1373 additions and 68 deletions

View file

@ -51,6 +51,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_daemon/flutter_daemon.dart';
import 'package:http/http.dart' as http;
import 'package:mobx/mobx.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../themes/theme_base.dart';
@ -180,7 +181,7 @@ abstract class DashboardViewModelBase with Store {
isShowThirdYatIntroduction = false;
unawaited(isBackgroundSyncEnabled());
unawaited(isBatteryOptimizationEnabled());
unawaited(_loadConstraints());
final _wallet = wallet;
if (_wallet.type == WalletType.monero) {
@ -536,6 +537,88 @@ abstract class DashboardViewModelBase with Store {
return resp;
}
@observable
late bool backgroundSyncNotificationsEnabled = sharedPreferences.getBool(PreferencesKey.backgroundSyncNotificationsEnabled) ?? false;
@action
Future<void> setBackgroundSyncNotificationsEnabled(bool value) async {
if (!value) {
backgroundSyncNotificationsEnabled = false;
sharedPreferences.setBool(PreferencesKey.backgroundSyncNotificationsEnabled, false);
return;
}
PermissionStatus permissionStatus = await Permission.notification.status;
if (permissionStatus != PermissionStatus.granted) {
final resp = await Permission.notification.request();
if (resp == PermissionStatus.denied) {
throw Exception("Notification permission denied");
}
}
backgroundSyncNotificationsEnabled = value;
await sharedPreferences.setBool(PreferencesKey.backgroundSyncNotificationsEnabled, value);
}
bool get hasBgsyncNetworkConstraints => Platform.isAndroid;
bool get hasBgsyncBatteryNotLowConstraints => Platform.isAndroid;
bool get hasBgsyncChargingConstraints => Platform.isAndroid;
bool get hasBgsyncDeviceIdleConstraints => Platform.isAndroid;
@observable
bool backgroundSyncNetworkUnmetered = false;
@observable
bool backgroundSyncBatteryNotLow = false;
@observable
bool backgroundSyncCharging = false;
@observable
bool backgroundSyncDeviceIdle = false;
Future<void> _loadConstraints() async {
backgroundSyncNetworkUnmetered = await FlutterDaemon().getNetworkType();
backgroundSyncBatteryNotLow = await FlutterDaemon().getBatteryNotLow();
backgroundSyncCharging = await FlutterDaemon().getRequiresCharging();
backgroundSyncDeviceIdle = await FlutterDaemon().getDeviceIdle();
}
@action
Future<void> setBackgroundSyncNetworkUnmetered(bool value) async {
backgroundSyncNetworkUnmetered = value;
await FlutterDaemon().setNetworkType(value);
if (await isBackgroundSyncEnabled()) {
await enableBackgroundSync();
}
}
@action
Future<void> setBackgroundSyncBatteryNotLow(bool value) async {
backgroundSyncBatteryNotLow = value;
await FlutterDaemon().setBatteryNotLow(value);
if (await isBackgroundSyncEnabled()) {
await enableBackgroundSync();
}
}
@action
Future<void> setBackgroundSyncCharging(bool value) async {
backgroundSyncCharging = value;
await FlutterDaemon().setRequiresCharging(value);
if (await isBackgroundSyncEnabled()) {
await enableBackgroundSync();
}
}
@action
Future<void> setBackgroundSyncDeviceIdle(bool value) async {
backgroundSyncDeviceIdle = value;
await FlutterDaemon().setDeviceIdle(value);
if (await isBackgroundSyncEnabled()) {
await enableBackgroundSync();
}
}
bool get hasBatteryOptimization => Platform.isAndroid;
@observable

View file

@ -0,0 +1,44 @@
import 'package:flutter_daemon/flutter_daemon.dart';
import 'package:mobx/mobx.dart';
part 'background_sync_logs_view_model.g.dart';
class BackgroundSyncLogsViewModel = BackgroundSyncLogsViewModelBase with _$BackgroundSyncLogsViewModel;
abstract class BackgroundSyncLogsViewModelBase with Store {
final FlutterDaemon _daemon = FlutterDaemon();
@observable
LogData? logData;
@observable
bool isLoading = false;
@observable
String? error;
@computed
List<LogEntry> get logs => logData?.logs ?? [];
@computed
List<LogSession> get sessions => logData?.sessions ?? [];
@action
Future<void> loadLogs() async {
isLoading = true;
error = null;
try {
logData = await _daemon.getLogs();
} catch (e) {
error = e.toString();
} finally {
isLoading = false;
}
}
@action
Future<void> clearLogs() async {
await _daemon.clearLogs();
await loadLogs();
}
}

View file

@ -0,0 +1,92 @@
import 'package:mobx/mobx.dart';
import 'package:shared_preferences/shared_preferences.dart';
part 'shared_preferences.g.dart';
class DevSharedPreferences = DevSharedPreferencesBase with _$DevSharedPreferences;
enum PreferenceType {
unknown,
string,
int,
double,
bool,
listString
}
abstract class DevSharedPreferencesBase with Store {
DevSharedPreferencesBase() {
SharedPreferences.getInstance().then((value) {
sharedPreferences = value;
});
}
@observable
SharedPreferences? sharedPreferences;
@computed
List<String> get keys => (sharedPreferences?.getKeys().toList()?..sort()) ?? [];
@action
Future<void> delete(String key) async {
if (sharedPreferences == null) {
return;
}
await sharedPreferences!.remove(key);
}
dynamic get(String key) {
if (sharedPreferences == null) {
return null;
}
return sharedPreferences!.get(key);
}
Future<void> set(String key, PreferenceType type, dynamic value) async {
if (sharedPreferences == null) {
return;
}
switch (type) {
case PreferenceType.string:
await sharedPreferences!.setString(key, value as String);
break;
case PreferenceType.bool:
await sharedPreferences!.setBool(key, value as bool);
break;
case PreferenceType.int:
await sharedPreferences!.setInt(key, value as int);
break;
case PreferenceType.double:
await sharedPreferences!.setDouble(key, value as double);
break;
case PreferenceType.listString:
await sharedPreferences!.setStringList(key, List<String>.from(value as Iterable<dynamic>));
break;
default:
throw Exception("Unknown preference type: $type");
}
}
PreferenceType getPreferenceType(String key) {
if (sharedPreferences == null) {
return PreferenceType.unknown;
}
final value = sharedPreferences!.get(key);
if (value is String) {
return PreferenceType.string;
}
if (value is bool) {
return PreferenceType.bool;
}
if (value is int) {
return PreferenceType.int;
}
if (value is double) {
return PreferenceType.double;
}
if (value is List<String>) {
return PreferenceType.listString;
}
return PreferenceType.unknown;
}
}

View file

@ -14,6 +14,7 @@ import 'package:cake_wallet/entities/evm_transaction_error_fees_handler.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/entities/parsed_address.dart';
import 'package:cake_wallet/entities/template.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/entities/transaction_description.dart';
import 'package:cake_wallet/entities/wallet_contact.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
@ -52,6 +53,7 @@ import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:shared_preferences/shared_preferences.dart';
part 'send_view_model.g.dart';
@ -587,6 +589,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
transactionNote: note,
));
}
final sharedPreferences = await SharedPreferences.getInstance();
await sharedPreferences.setString(PreferencesKey.backgroundSyncLastTrigger(wallet.name), DateTime.now().add(Duration(minutes: 1)).toIso8601String());
state = TransactionCommitted();
} catch (e) {