mirror of
https://github.com/immich-app/immich.git
synced 2024-12-25 10:43:13 +02:00
feat(mobile): Background app refresh status (#1839)
* adds background app refresh message * fixes ios background settings provider * styling * capitalization * changed to watch * uses settings notifier now * forgot to commit this file * changed to watch and added more clarification --------- Co-authored-by: Marty Fuhry <marty@fuhry.farm>
This commit is contained in:
parent
8bcb2558b6
commit
2b988e1d5d
@ -226,5 +226,8 @@
|
|||||||
"version_announcement_overlay_text_1": "Hi friend, there is a new release of",
|
"version_announcement_overlay_text_1": "Hi friend, there is a new release of",
|
||||||
"version_announcement_overlay_text_2": "please take your time to visit the ",
|
"version_announcement_overlay_text_2": "please take your time to visit the ",
|
||||||
"version_announcement_overlay_text_3": " and ensure your docker-compose and .env setup is up-to-date to prevent any misconfigurations, especially if you use WatchTower or any mechanism that handles updating your server application automatically.",
|
"version_announcement_overlay_text_3": " and ensure your docker-compose and .env setup is up-to-date to prevent any misconfigurations, especially if you use WatchTower or any mechanism that handles updating your server application automatically.",
|
||||||
"version_announcement_overlay_title": "New Server Version Available \uD83C\uDF89"
|
"version_announcement_overlay_title": "New Server Version Available \uD83C\uDF89",
|
||||||
}
|
"backup_controller_page_background_app_refresh_disabled_title": "Background app refresh disabled",
|
||||||
|
"backup_controller_page_background_app_refresh_disabled_content": "Enable background app refresh in Settings > General > Background App Refresh in order to use background backup.",
|
||||||
|
"backup_controller_page_background_app_refresh_enable_button_text": "Go to settings"
|
||||||
|
}
|
||||||
|
@ -90,12 +90,18 @@ class BackgroundServicePlugin: NSObject, FlutterPlugin {
|
|||||||
let defaults = UserDefaults.standard
|
let defaults = UserDefaults.standard
|
||||||
let lastRunTime = defaults.value(forKey: "last_background_fetch_run_time")
|
let lastRunTime = defaults.value(forKey: "last_background_fetch_run_time")
|
||||||
result(lastRunTime)
|
result(lastRunTime)
|
||||||
|
break
|
||||||
case "lastBackgroundProcessingTime":
|
case "lastBackgroundProcessingTime":
|
||||||
let defaults = UserDefaults.standard
|
let defaults = UserDefaults.standard
|
||||||
let lastRunTime = defaults.value(forKey: "last_background_processing_run_time")
|
let lastRunTime = defaults.value(forKey: "last_background_processing_run_time")
|
||||||
result(lastRunTime)
|
result(lastRunTime)
|
||||||
|
break
|
||||||
case "numberOfBackgroundProcesses":
|
case "numberOfBackgroundProcesses":
|
||||||
handleNumberOfProcesses(call: call, result: result)
|
handleNumberOfProcesses(call: call, result: result)
|
||||||
|
break
|
||||||
|
case "backgroundAppRefreshEnabled":
|
||||||
|
handleBackgroundRefreshStatus(call: call, result: result)
|
||||||
|
break
|
||||||
default:
|
default:
|
||||||
result(FlutterMethodNotImplemented)
|
result(FlutterMethodNotImplemented)
|
||||||
break
|
break
|
||||||
@ -138,11 +144,10 @@ class BackgroundServicePlugin: NSObject, FlutterPlugin {
|
|||||||
// This is not used yet and will need to be implemented
|
// This is not used yet and will need to be implemented
|
||||||
defaults.set(notificationTitle, forKey: "notification_title")
|
defaults.set(notificationTitle, forKey: "notification_title")
|
||||||
|
|
||||||
// Schedule the background services if instant
|
// Schedule the background services
|
||||||
if (instant ?? true) {
|
BackgroundServicePlugin.scheduleBackgroundSync()
|
||||||
BackgroundServicePlugin.scheduleBackgroundSync()
|
BackgroundServicePlugin.scheduleBackgroundFetch()
|
||||||
BackgroundServicePlugin.scheduleBackgroundFetch()
|
|
||||||
}
|
|
||||||
result(true)
|
result(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,15 +214,31 @@ class BackgroundServicePlugin: NSObject, FlutterPlugin {
|
|||||||
result(true)
|
result(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Checks the status of the Background App Refresh from the system
|
||||||
|
// Returns true if the service is enabled for Immich, and false otherwise
|
||||||
|
func handleBackgroundRefreshStatus(call: FlutterMethodCall, result: FlutterResult) {
|
||||||
|
switch UIApplication.shared.backgroundRefreshStatus {
|
||||||
|
case .available:
|
||||||
|
result(true)
|
||||||
|
break
|
||||||
|
case .denied:
|
||||||
|
result(false)
|
||||||
|
break
|
||||||
|
case .restricted:
|
||||||
|
result(false)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
result(false)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Schedules a short-running background sync to sync only a few photos
|
// Schedules a short-running background sync to sync only a few photos
|
||||||
static func scheduleBackgroundFetch() {
|
static func scheduleBackgroundFetch() {
|
||||||
// We will only schedule this task to run if the user has explicitely allowed us to backup while
|
// We will schedule this task to run no matter the charging or wifi requirents from the end user
|
||||||
// not connected to power
|
// 1. They can set Background App Refresh to Off / Wi-Fi / Wi-Fi & Cellular Data from Settings
|
||||||
let defaults = UserDefaults.standard
|
// 2. We will check the battery connectivity when we begin running the background activity
|
||||||
if defaults.value(forKey: "require_charging") as? Bool == true {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let backgroundFetch = BGAppRefreshTaskRequest(identifier: BackgroundServicePlugin.backgroundFetchTaskID)
|
let backgroundFetch = BGAppRefreshTaskRequest(identifier: BackgroundServicePlugin.backgroundFetchTaskID)
|
||||||
|
|
||||||
// Use 5 minutes from now as earliest begin date
|
// Use 5 minutes from now as earliest begin date
|
||||||
@ -255,10 +276,26 @@ class BackgroundServicePlugin: NSObject, FlutterPlugin {
|
|||||||
|
|
||||||
// This function runs when the system kicks off the BGAppRefreshTask from the Background Task Scheduler
|
// This function runs when the system kicks off the BGAppRefreshTask from the Background Task Scheduler
|
||||||
static func handleBackgroundFetch(task: BGAppRefreshTask) {
|
static func handleBackgroundFetch(task: BGAppRefreshTask) {
|
||||||
|
// Schedule the next sync task so we can run this again later
|
||||||
|
scheduleBackgroundFetch()
|
||||||
|
|
||||||
// Log the time of last background processing to now
|
// Log the time of last background processing to now
|
||||||
let defaults = UserDefaults.standard
|
let defaults = UserDefaults.standard
|
||||||
defaults.set(Date().timeIntervalSince1970, forKey: "last_background_fetch_run_time")
|
defaults.set(Date().timeIntervalSince1970, forKey: "last_background_fetch_run_time")
|
||||||
|
|
||||||
|
// If we have required charging, we should check the charging status
|
||||||
|
let requireCharging = defaults.value(forKey: "require_charging") as? Bool
|
||||||
|
if (requireCharging ?? false) {
|
||||||
|
UIDevice.current.isBatteryMonitoringEnabled = true
|
||||||
|
if (UIDevice.current.batteryState == .unplugged) {
|
||||||
|
// The device is unplugged and we have required charging
|
||||||
|
// Therefore, we will simply complete the task without
|
||||||
|
// running it.
|
||||||
|
task.setTaskCompleted(success: true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Schedule the next sync task so we can run this again later
|
// Schedule the next sync task so we can run this again later
|
||||||
scheduleBackgroundFetch()
|
scheduleBackgroundFetch()
|
||||||
|
|
||||||
@ -268,13 +305,13 @@ class BackgroundServicePlugin: NSObject, FlutterPlugin {
|
|||||||
|
|
||||||
// This function runs when the system kicks off the BGProcessingTask from the Background Task Scheduler
|
// This function runs when the system kicks off the BGProcessingTask from the Background Task Scheduler
|
||||||
static func handleBackgroundProcessing(task: BGProcessingTask) {
|
static func handleBackgroundProcessing(task: BGProcessingTask) {
|
||||||
|
// Schedule the next sync task so we run this again later
|
||||||
|
scheduleBackgroundSync()
|
||||||
|
|
||||||
// Log the time of last background processing to now
|
// Log the time of last background processing to now
|
||||||
let defaults = UserDefaults.standard
|
let defaults = UserDefaults.standard
|
||||||
defaults.set(Date().timeIntervalSince1970, forKey: "last_background_processing_run_time")
|
defaults.set(Date().timeIntervalSince1970, forKey: "last_background_processing_run_time")
|
||||||
|
|
||||||
// Schedule the next sync task so we run this again later
|
|
||||||
scheduleBackgroundSync()
|
|
||||||
|
|
||||||
// We won't specify a max time for the background sync service, so this can run for longer
|
// We won't specify a max time for the background sync service, so this can run for longer
|
||||||
BackgroundServicePlugin.runBackgroundSync(task, maxSeconds: nil)
|
BackgroundServicePlugin.runBackgroundSync(task, maxSeconds: nil)
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import 'package:immich_mobile/modules/backup/background_service/background.servi
|
|||||||
import 'package:immich_mobile/modules/backup/models/hive_backup_albums.model.dart';
|
import 'package:immich_mobile/modules/backup/models/hive_backup_albums.model.dart';
|
||||||
import 'package:immich_mobile/modules/backup/models/hive_duplicated_assets.model.dart';
|
import 'package:immich_mobile/modules/backup/models/hive_duplicated_assets.model.dart';
|
||||||
import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
|
import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
|
||||||
|
import 'package:immich_mobile/modules/backup/providers/ios_background_settings.provider.dart';
|
||||||
import 'package:immich_mobile/modules/login/models/hive_saved_login_info.model.dart';
|
import 'package:immich_mobile/modules/login/models/hive_saved_login_info.model.dart';
|
||||||
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
|
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
|
||||||
import 'package:immich_mobile/modules/settings/providers/permission.provider.dart';
|
import 'package:immich_mobile/modules/settings/providers/permission.provider.dart';
|
||||||
@ -144,6 +145,8 @@ class ImmichAppState extends ConsumerState<ImmichApp>
|
|||||||
.watch(notificationPermissionProvider.notifier)
|
.watch(notificationPermissionProvider.notifier)
|
||||||
.getNotificationPermission();
|
.getNotificationPermission();
|
||||||
|
|
||||||
|
ref.read(iOSBackgroundSettingsProvider.notifier).refresh();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AppLifecycleState.inactive:
|
case AppLifecycleState.inactive:
|
||||||
|
@ -574,6 +574,10 @@ class BackgroundService {
|
|||||||
Future<int> getIOSBackupNumberOfProcesses() async {
|
Future<int> getIOSBackupNumberOfProcesses() async {
|
||||||
return await _foregroundChannel.invokeMethod('numberOfBackgroundProcesses');
|
return await _foregroundChannel.invokeMethod('numberOfBackgroundProcesses');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> getIOSBackgroundAppRefreshEnabled() async {
|
||||||
|
return await _foregroundChannel.invokeMethod('backgroundAppRefreshEnabled');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum IosBackgroundTask { fetch, processing }
|
enum IosBackgroundTask { fetch, processing }
|
||||||
|
@ -0,0 +1,57 @@
|
|||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/modules/backup/background_service/background.service.dart';
|
||||||
|
|
||||||
|
class IOSBackgroundSettings {
|
||||||
|
final bool appRefreshEnabled;
|
||||||
|
final int numberOfBackgroundTasksQueued;
|
||||||
|
final DateTime? timeOfLastFetch;
|
||||||
|
final DateTime? timeOfLastProcessing;
|
||||||
|
|
||||||
|
IOSBackgroundSettings({
|
||||||
|
required this.appRefreshEnabled,
|
||||||
|
required this.numberOfBackgroundTasksQueued,
|
||||||
|
this.timeOfLastFetch,
|
||||||
|
this.timeOfLastProcessing,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class IOSBackgroundSettingsNotifier extends StateNotifier<IOSBackgroundSettings?> {
|
||||||
|
final BackgroundService _service;
|
||||||
|
IOSBackgroundSettingsNotifier(this._service) : super(null);
|
||||||
|
|
||||||
|
IOSBackgroundSettings? get settings => state;
|
||||||
|
|
||||||
|
Future<IOSBackgroundSettings> refresh() async {
|
||||||
|
final lastFetchTime = await _service.getIOSBackupLastRun(IosBackgroundTask.fetch);
|
||||||
|
final lastProcessingTime = await _service.getIOSBackupLastRun(IosBackgroundTask.processing);
|
||||||
|
int numberOfProcesses = await _service.getIOSBackupNumberOfProcesses();
|
||||||
|
final appRefreshEnabled = await _service.getIOSBackgroundAppRefreshEnabled();
|
||||||
|
|
||||||
|
// If this is enabled and there are no background processes,
|
||||||
|
// the user just enabled app refresh in Settings.
|
||||||
|
// But we don't have any background services running, since it was disabled
|
||||||
|
// before.
|
||||||
|
if (await _service.isBackgroundBackupEnabled() &&
|
||||||
|
numberOfProcesses == 0) {
|
||||||
|
// We need to restart the background service
|
||||||
|
await _service.enableService();
|
||||||
|
numberOfProcesses = await _service.getIOSBackupNumberOfProcesses();
|
||||||
|
}
|
||||||
|
|
||||||
|
final settings = IOSBackgroundSettings(
|
||||||
|
appRefreshEnabled: appRefreshEnabled,
|
||||||
|
numberOfBackgroundTasksQueued: numberOfProcesses,
|
||||||
|
timeOfLastFetch: lastFetchTime,
|
||||||
|
timeOfLastProcessing: lastProcessingTime,
|
||||||
|
);
|
||||||
|
|
||||||
|
state = settings;
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
final iOSBackgroundSettingsProvider = StateNotifierProvider<IOSBackgroundSettingsNotifier, IOSBackgroundSettings?>(
|
||||||
|
(ref) => IOSBackgroundSettingsNotifier(ref.watch(backgroundServiceProvider)),
|
||||||
|
);
|
||||||
|
|
@ -1,78 +1,61 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/modules/backup/background_service/background.service.dart';
|
import 'package:immich_mobile/modules/backup/providers/ios_background_settings.provider.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
/// This is a simple debug widget which should be removed later on when we are
|
/// This is a simple debug widget which should be removed later on when we are
|
||||||
/// more confident about background sync
|
/// more confident about background sync
|
||||||
class IosDebugInfoTile extends HookConsumerWidget {
|
class IosDebugInfoTile extends HookConsumerWidget {
|
||||||
const IosDebugInfoTile({super.key});
|
final IOSBackgroundSettings settings;
|
||||||
|
const IosDebugInfoTile({
|
||||||
|
super.key,
|
||||||
|
required this.settings,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final futures = [
|
final fetch = settings.timeOfLastFetch;
|
||||||
ref
|
final processing = settings.timeOfLastProcessing;
|
||||||
.read(backgroundServiceProvider)
|
final processes = settings.numberOfBackgroundTasksQueued;
|
||||||
.getIOSBackupLastRun(IosBackgroundTask.fetch),
|
|
||||||
ref
|
|
||||||
.read(backgroundServiceProvider)
|
|
||||||
.getIOSBackupLastRun(IosBackgroundTask.processing),
|
|
||||||
ref.read(backgroundServiceProvider).getIOSBackupNumberOfProcesses(),
|
|
||||||
];
|
|
||||||
return FutureBuilder<List<dynamic>>(
|
|
||||||
future: Future.wait(futures),
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
String? title;
|
|
||||||
String? subtitle;
|
|
||||||
if (snapshot.hasData) {
|
|
||||||
final results = snapshot.data as List<dynamic>;
|
|
||||||
final fetch = results[0] as DateTime?;
|
|
||||||
final processing = results[1] as DateTime?;
|
|
||||||
final processes = results[2] as int;
|
|
||||||
|
|
||||||
final processOrProcesses = processes == 1 ? 'process' : 'processes';
|
final processOrProcesses = processes == 1 ? 'process' : 'processes';
|
||||||
final numberOrZero = processes == 0 ? 'No' : processes.toString();
|
final numberOrZero = processes == 0 ? 'No' : processes.toString();
|
||||||
title = '$numberOrZero background $processOrProcesses queued';
|
final title = '$numberOrZero background $processOrProcesses queued';
|
||||||
|
|
||||||
final df = DateFormat.yMd().add_jm();
|
final df = DateFormat.yMd().add_jm();
|
||||||
if (fetch == null && processing == null) {
|
final String subtitle;
|
||||||
subtitle = 'No background sync job has run yet';
|
if (fetch == null && processing == null) {
|
||||||
} else if (fetch != null && processing == null) {
|
subtitle = 'No background sync job has run yet';
|
||||||
subtitle = 'Fetch ran ${df.format(fetch)}';
|
} else if (fetch != null && processing == null) {
|
||||||
} else if (processing != null && fetch == null) {
|
subtitle = 'Fetch ran ${df.format(fetch)}';
|
||||||
subtitle = 'Processing ran ${df.format(processing)}';
|
} else if (processing != null && fetch == null) {
|
||||||
} else {
|
subtitle = 'Processing ran ${df.format(processing)}';
|
||||||
final fetchOrProcessing =
|
} else {
|
||||||
fetch!.isAfter(processing!) ? fetch : processing;
|
final fetchOrProcessing =
|
||||||
subtitle = 'Last sync ${df.format(fetchOrProcessing)}';
|
fetch!.isAfter(processing!) ? fetch : processing;
|
||||||
}
|
subtitle = 'Last sync ${df.format(fetchOrProcessing)}';
|
||||||
}
|
}
|
||||||
|
|
||||||
return AnimatedSwitcher(
|
return ListTile(
|
||||||
duration: const Duration(milliseconds: 200),
|
key: ValueKey(title),
|
||||||
child: ListTile(
|
title: Text(
|
||||||
key: ValueKey(title),
|
title,
|
||||||
title: Text(
|
style: TextStyle(
|
||||||
title ?? '',
|
fontWeight: FontWeight.bold,
|
||||||
style: TextStyle(
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.bold,
|
color: Theme.of(context).primaryColor,
|
||||||
fontSize: 14,
|
),
|
||||||
color: Theme.of(context).primaryColor,
|
),
|
||||||
),
|
subtitle: Text(
|
||||||
),
|
subtitle,
|
||||||
subtitle: Text(
|
style: const TextStyle(
|
||||||
subtitle ?? '',
|
fontSize: 14,
|
||||||
style: const TextStyle(
|
),
|
||||||
fontSize: 14,
|
),
|
||||||
),
|
leading: Icon(
|
||||||
),
|
Icons.bug_report,
|
||||||
leading: Icon(
|
color: Theme.of(context).primaryColor,
|
||||||
Icons.bug_report,
|
),
|
||||||
color: Theme.of(context).primaryColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,9 @@ import 'package:easy_localization/easy_localization.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/modules/backup/background_service/background.service.dart';
|
||||||
import 'package:immich_mobile/modules/backup/providers/error_backup_list.provider.dart';
|
import 'package:immich_mobile/modules/backup/providers/error_backup_list.provider.dart';
|
||||||
|
import 'package:immich_mobile/modules/backup/providers/ios_background_settings.provider.dart';
|
||||||
import 'package:immich_mobile/modules/backup/ui/current_backup_asset_info_box.dart';
|
import 'package:immich_mobile/modules/backup/ui/current_backup_asset_info_box.dart';
|
||||||
import 'package:immich_mobile/modules/backup/ui/ios_debug_info_tile.dart';
|
import 'package:immich_mobile/modules/backup/ui/ios_debug_info_tile.dart';
|
||||||
import 'package:immich_mobile/modules/login/models/authentication_state.model.dart';
|
import 'package:immich_mobile/modules/login/models/authentication_state.model.dart';
|
||||||
@ -15,6 +17,7 @@ import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
|
|||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
import 'package:immich_mobile/shared/providers/websocket.provider.dart';
|
import 'package:immich_mobile/shared/providers/websocket.provider.dart';
|
||||||
import 'package:immich_mobile/modules/backup/ui/backup_info_card.dart';
|
import 'package:immich_mobile/modules/backup/ui/backup_info_card.dart';
|
||||||
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
class BackupControllerPage extends HookConsumerWidget {
|
class BackupControllerPage extends HookConsumerWidget {
|
||||||
@ -24,6 +27,10 @@ class BackupControllerPage extends HookConsumerWidget {
|
|||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
BackUpState backupState = ref.watch(backupProvider);
|
BackUpState backupState = ref.watch(backupProvider);
|
||||||
AuthenticationState authenticationState = ref.watch(authenticationProvider);
|
AuthenticationState authenticationState = ref.watch(authenticationProvider);
|
||||||
|
final settings = ref.watch(iOSBackgroundSettingsProvider.notifier).settings;
|
||||||
|
|
||||||
|
final appRefreshDisabled = Platform.isIOS &&
|
||||||
|
settings?.appRefreshEnabled != true;
|
||||||
bool hasExclusiveAccess =
|
bool hasExclusiveAccess =
|
||||||
backupState.backupProgress != BackUpProgressEnum.inBackground;
|
backupState.backupProgress != BackUpProgressEnum.inBackground;
|
||||||
bool shouldBackup = backupState.allUniqueAssets.length -
|
bool shouldBackup = backupState.allUniqueAssets.length -
|
||||||
@ -40,6 +47,13 @@ class BackupControllerPage extends HookConsumerWidget {
|
|||||||
ref.watch(backupProvider.notifier).getBackupInfo();
|
ref.watch(backupProvider.notifier).getBackupInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the background settings information just to make sure we
|
||||||
|
// have the latest, since the platform channel will not update
|
||||||
|
// automatically
|
||||||
|
if (Platform.isIOS) {
|
||||||
|
ref.watch(iOSBackgroundSettingsProvider.notifier).refresh();
|
||||||
|
}
|
||||||
|
|
||||||
ref
|
ref
|
||||||
.watch(websocketProvider.notifier)
|
.watch(websocketProvider.notifier)
|
||||||
.stopListenToEvent('on_upload_success');
|
.stopListenToEvent('on_upload_success');
|
||||||
@ -362,14 +376,65 @@ class BackupControllerPage extends HookConsumerWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (isBackgroundEnabled)
|
if (isBackgroundEnabled && Platform.isIOS)
|
||||||
IosDebugInfoTile(
|
FutureBuilder(
|
||||||
key: ValueKey(isChargingRequired),
|
future: ref
|
||||||
|
.read(backgroundServiceProvider)
|
||||||
|
.getIOSBackgroundAppRefreshEnabled(),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
final enabled = snapshot.data as bool?;
|
||||||
|
// If it's not enabled, show them some kind of alert that says
|
||||||
|
// background refresh is not enabled
|
||||||
|
if (enabled != null && !enabled) {
|
||||||
|
|
||||||
|
}
|
||||||
|
// If it's enabled, no need to bother them
|
||||||
|
return Container();
|
||||||
|
},
|
||||||
),
|
),
|
||||||
|
if (isBackgroundEnabled && settings != null)
|
||||||
|
IosDebugInfoTile(
|
||||||
|
settings: settings,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget buildBackgroundAppRefreshWarning() {
|
||||||
|
return ListTile(
|
||||||
|
isThreeLine: true,
|
||||||
|
leading: const Icon(Icons.task_outlined,),
|
||||||
|
title: const Text(
|
||||||
|
'backup_controller_page_background_app_refresh_disabled_title',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
).tr(),
|
||||||
|
subtitle: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||||
|
child: const Text(
|
||||||
|
'backup_controller_page_background_app_refresh_disabled_content',
|
||||||
|
).tr(),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () => openAppSettings(),
|
||||||
|
child: const Text(
|
||||||
|
'backup_controller_page_background_app_refresh_enable_button_text',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
).tr(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget buildSelectedAlbumName() {
|
Widget buildSelectedAlbumName() {
|
||||||
var text = "backup_controller_page_backup_selected".tr();
|
var text = "backup_controller_page_backup_selected".tr();
|
||||||
var albums = ref.watch(backupProvider).selectedBackupAlbums;
|
var albums = ref.watch(backupProvider).selectedBackupAlbums;
|
||||||
@ -613,7 +678,15 @@ class BackupControllerPage extends HookConsumerWidget {
|
|||||||
const Divider(),
|
const Divider(),
|
||||||
buildAutoBackupController(),
|
buildAutoBackupController(),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
buildBackgroundBackupController(),
|
AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 500),
|
||||||
|
child: Platform.isIOS
|
||||||
|
? (
|
||||||
|
appRefreshDisabled
|
||||||
|
? buildBackgroundAppRefreshWarning()
|
||||||
|
: buildBackgroundBackupController()
|
||||||
|
) : buildBackgroundBackupController(),
|
||||||
|
),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
buildStorageInformation(),
|
buildStorageInformation(),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
@ -624,4 +697,6 @@ class BackupControllerPage extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user