mirror of
https://github.com/immich-app/immich.git
synced 2024-12-25 10:43:13 +02:00
feat(mobile): iOS background sync notifications (#1811)
* adds notification handling logic * notification on background updates for iOS * fixed regression where i accidentally removed load translations from the background sync * fixed ios translations --------- Co-authored-by: Marty Fuhry <marty@fuhry.farm> Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
parent
2d2cfb0349
commit
e9c9b7a3e2
@ -214,5 +214,11 @@
|
||||
"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_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",
|
||||
"notification_permission_dialog_content": "To enable notifications, go to Settings and select allow.",
|
||||
"notification_permission_dialog_cancel": "Cancel",
|
||||
"notification_permission_dialog_settings": "Settings",
|
||||
"notification_permission_list_tile_title": "Notification Permission",
|
||||
"notification_permission_list_tile_content": "Grant permission to enable notifications.",
|
||||
"notification_permission_list_tile_enable_button": "Enable Notifications"
|
||||
}
|
||||
|
@ -37,5 +37,66 @@ end
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
flutter_additional_ios_build_settings(target)
|
||||
|
||||
# Permission macros for the permission_handler (https://pub.dev/packages/permission_handler)
|
||||
# Start of the permission_handler configuration
|
||||
# Remove the # character in front of the permission you do want to use.
|
||||
target.build_configurations.each do |config|
|
||||
|
||||
# You can enable the permissions needed here. For example to enable camera
|
||||
# permission, just remove the `#` character in front so it looks like this:
|
||||
#
|
||||
# ## dart: PermissionGroup.camera
|
||||
# 'PERMISSION_CAMERA=1'
|
||||
#
|
||||
# Preprocessor definitions can be found in: https://github.com/Baseflow/flutter-permission-handler/blob/master/permission_handler_apple/ios/Classes/PermissionHandlerEnums.h
|
||||
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
|
||||
'$(inherited)',
|
||||
|
||||
## dart: PermissionGroup.calendar
|
||||
# 'PERMISSION_EVENTS=1',
|
||||
|
||||
## dart: PermissionGroup.reminders
|
||||
# 'PERMISSION_REMINDERS=1',
|
||||
|
||||
## dart: PermissionGroup.contacts
|
||||
# 'PERMISSION_CONTACTS=1',
|
||||
|
||||
## dart: PermissionGroup.camera
|
||||
# 'PERMISSION_CAMERA=1',
|
||||
|
||||
## dart: PermissionGroup.microphone
|
||||
# 'PERMISSION_MICROPHONE=1',
|
||||
|
||||
## dart: PermissionGroup.speech
|
||||
# 'PERMISSION_SPEECH_RECOGNIZER=1',
|
||||
|
||||
## dart: PermissionGroup.photos
|
||||
# 'PERMISSION_PHOTOS=1',
|
||||
|
||||
## dart: [PermissionGroup.location, PermissionGroup.locationAlways, PermissionGroup.locationWhenInUse]
|
||||
# 'PERMISSION_LOCATION=1',
|
||||
|
||||
## dart: PermissionGroup.notification
|
||||
'PERMISSION_NOTIFICATIONS=1',
|
||||
|
||||
## dart: PermissionGroup.mediaLibrary
|
||||
# 'PERMISSION_MEDIA_LIBRARY=1',
|
||||
|
||||
## dart: PermissionGroup.sensors
|
||||
# 'PERMISSION_SENSORS=1',
|
||||
|
||||
## dart: PermissionGroup.bluetooth
|
||||
# 'PERMISSION_BLUETOOTH=1',
|
||||
|
||||
## dart: PermissionGroup.appTrackingTransparency
|
||||
# 'PERMISSION_APP_TRACKING_TRANSPARENCY=1',
|
||||
|
||||
## dart: PermissionGroup.criticalAlerts
|
||||
# 'PERMISSION_CRITICAL_ALERTS=1'
|
||||
]
|
||||
|
||||
end
|
||||
# End of the permission_handler configuration
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -26,6 +26,8 @@ PODS:
|
||||
- FlutterMacOS
|
||||
- path_provider_ios (0.0.1):
|
||||
- Flutter
|
||||
- permission_handler_apple (9.0.4):
|
||||
- Flutter
|
||||
- photo_manager (2.0.0):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
@ -58,6 +60,7 @@ DEPENDENCIES:
|
||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/ios`)
|
||||
- path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
|
||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||
- photo_manager (from `.symlinks/plugins/photo_manager/ios`)
|
||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/ios`)
|
||||
@ -95,6 +98,8 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/path_provider_foundation/ios"
|
||||
path_provider_ios:
|
||||
:path: ".symlinks/plugins/path_provider_ios/ios"
|
||||
permission_handler_apple:
|
||||
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
||||
photo_manager:
|
||||
:path: ".symlinks/plugins/photo_manager/ios"
|
||||
share_plus:
|
||||
@ -123,6 +128,7 @@ SPEC CHECKSUMS:
|
||||
package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e
|
||||
path_provider_foundation: 37748e03f12783f9de2cb2c4eadfaa25fe6d4852
|
||||
path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02
|
||||
permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce
|
||||
photo_manager: 4f6810b7dfc4feb03b461ac1a70dacf91fba7604
|
||||
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
|
||||
share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68
|
||||
@ -133,6 +139,6 @@ SPEC CHECKSUMS:
|
||||
video_player_avfoundation: e489aac24ef5cf7af82702979ed16f2a5ef84cff
|
||||
wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f
|
||||
|
||||
PODFILE CHECKSUM: c798208781ca5116c4a3d5927d689946791f0189
|
||||
PODFILE CHECKSUM: 4a7e0475ea85ab7bf89955bc4c7ea9d18b54dfd8
|
||||
|
||||
COCOAPODS: 1.11.3
|
||||
|
@ -1,4 +1,5 @@
|
||||
import UIKit
|
||||
import shared_preferences_foundation
|
||||
import Flutter
|
||||
import BackgroundTasks
|
||||
import path_provider_ios
|
||||
@ -25,6 +26,10 @@ import photo_manager
|
||||
if !registry.hasPlugin("org.cocoapods.photo-manager") {
|
||||
PhotoManagerPlugin.register(with: registry.registrar(forPlugin: "org.cocoapods.photo-manager")!)
|
||||
}
|
||||
|
||||
if !registry.hasPlugin("org.cocoapods.shared-preferences-foundation") {
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "org.cocoapods.shared-preferences-foundation")!)
|
||||
}
|
||||
}
|
||||
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
|
@ -25,6 +25,7 @@ class BackgroundSyncWorker {
|
||||
name: "BackgroundImmich"
|
||||
)
|
||||
|
||||
let notificationId = "com.alextran.immich/backgroundNotifications"
|
||||
// The background message passing channel
|
||||
var channel: FlutterMethodChannel?
|
||||
|
||||
@ -67,15 +68,15 @@ class BackgroundSyncWorker {
|
||||
})
|
||||
break
|
||||
case "updateNotification":
|
||||
// TODO: implement update notification
|
||||
result(true)
|
||||
let handled = self.handleNotification(call)
|
||||
result(handled)
|
||||
break
|
||||
case "showError":
|
||||
// TODO: implement show error
|
||||
result(true)
|
||||
let handled = self.handleError(call)
|
||||
result(handled)
|
||||
break
|
||||
case "clearErrorNotifications":
|
||||
// TODO: implement clear error notifications
|
||||
self.handleClearErrorNotifications()
|
||||
result(true)
|
||||
break
|
||||
case "hasContentChanged":
|
||||
@ -184,5 +185,87 @@ class BackgroundSyncWorker {
|
||||
channel = nil
|
||||
completionHandler(fetchResult)
|
||||
}
|
||||
|
||||
private func handleNotification(_ call: FlutterMethodCall) -> Bool {
|
||||
|
||||
// Parse the arguments as an array list
|
||||
guard let args = call.arguments as? Array<Any> else {
|
||||
print("Failed to parse \(call.arguments) as array")
|
||||
return false;
|
||||
}
|
||||
|
||||
// Requires 7 arguments passed or else fail
|
||||
guard args.count == 7 else {
|
||||
print("Needs 7 arguments, but was only passed \(args.count)")
|
||||
return false
|
||||
}
|
||||
|
||||
// Parse the arguments to send the notification update
|
||||
let title = args[0] as? String
|
||||
let content = args[1] as? String
|
||||
let progress = args[2] as? Int
|
||||
let maximum = args[3] as? Int
|
||||
let indeterminate = args[4] as? Bool
|
||||
let isDetail = args[5] as? Bool
|
||||
let onlyIfForeground = args[6] as? Bool
|
||||
|
||||
// Build the notification
|
||||
let notificationContent = UNMutableNotificationContent()
|
||||
notificationContent.body = content ?? "Uploading..."
|
||||
notificationContent.title = title ?? "Immich"
|
||||
|
||||
// Add it to the Notification center
|
||||
let notification = UNNotificationRequest(
|
||||
identifier: notificationId,
|
||||
content: notificationContent,
|
||||
trigger: nil
|
||||
)
|
||||
let center = UNUserNotificationCenter.current()
|
||||
center.add(notification) { (error: Error?) in
|
||||
if let theError = error {
|
||||
print("Error showing notifications: \(theError)")
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private func handleError(_ call: FlutterMethodCall) -> Bool {
|
||||
// Parse the arguments as an array list
|
||||
guard let args = call.arguments as? Array<Any> else {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Requires 7 arguments passed or else fail
|
||||
guard args.count == 3 else {
|
||||
return false
|
||||
}
|
||||
|
||||
let title = args[0] as? String
|
||||
let content = args[1] as? String
|
||||
let individualTag = args[2] as? String
|
||||
|
||||
// Build the notification
|
||||
let notificationContent = UNMutableNotificationContent()
|
||||
notificationContent.body = content ?? "Error running the backup job."
|
||||
notificationContent.title = title ?? "Immich"
|
||||
|
||||
// Add it to the Notification center
|
||||
let notification = UNNotificationRequest(
|
||||
identifier: notificationId,
|
||||
content: notificationContent,
|
||||
trigger: nil
|
||||
)
|
||||
let center = UNUserNotificationCenter.current()
|
||||
center.add(notification)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private func handleClearErrorNotifications() {
|
||||
let center = UNUserNotificationCenter.current()
|
||||
center.removeDeliveredNotifications(withIdentifiers: [notificationId])
|
||||
center.removePendingNotificationRequests(withIdentifiers: [notificationId])
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,7 @@ import 'package:immich_mobile/modules/backup/models/hive_duplicated_assets.model
|
||||
import 'package:immich_mobile/modules/backup/providers/backup.provider.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/settings/providers/permission.provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/routing/tab_navigation_observer.dart';
|
||||
import 'package:immich_mobile/shared/models/immich_logger_message.model.dart';
|
||||
@ -126,6 +127,9 @@ class ImmichAppState extends ConsumerState<ImmichApp>
|
||||
|
||||
ref.watch(releaseInfoProvider.notifier).checkGithubReleaseInfo();
|
||||
|
||||
ref.watch(notificationPermissionProvider.notifier)
|
||||
.getNotificationPermission();
|
||||
|
||||
break;
|
||||
|
||||
case AppLifecycleState.inactive:
|
||||
|
@ -314,10 +314,9 @@ class BackgroundService {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Notifications aren't enabled in iOS yet, and this line
|
||||
// below crashes the iOS background service
|
||||
if (Platform.isAndroid) {
|
||||
await loadTranslations();
|
||||
final translationsOk = await loadTranslations();
|
||||
if (!translationsOk) {
|
||||
debugPrint("[_callHandler] could not load translations");
|
||||
}
|
||||
|
||||
final bool ok = await _onAssetsChanged();
|
||||
|
@ -0,0 +1,44 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
class NotificationPermissionNotifier extends StateNotifier<PermissionStatus> {
|
||||
NotificationPermissionNotifier() :
|
||||
super(Platform.isAndroid
|
||||
? PermissionStatus.granted
|
||||
: PermissionStatus.restricted,
|
||||
) {
|
||||
// Sets the initial state
|
||||
getNotificationPermission().then((p) => state = p);
|
||||
}
|
||||
|
||||
/// Requests the notification permission
|
||||
/// Note: In Android, this is always granted
|
||||
Future<PermissionStatus> requestNotificationPermission() async {
|
||||
final permission = await Permission.notification.request();
|
||||
state = permission;
|
||||
return permission;
|
||||
}
|
||||
|
||||
/// Whether the user has the permission or not
|
||||
/// Note: In Android, this is always true
|
||||
Future<bool> hasNotificationPermission() {
|
||||
return Permission.notification.isGranted;
|
||||
}
|
||||
|
||||
Future<PermissionStatus> getNotificationPermission() async {
|
||||
final status = await Permission.notification.status;
|
||||
state = status;
|
||||
return status;
|
||||
}
|
||||
|
||||
/// Either the permission was granted already or else ask for the permission
|
||||
Future<bool> hasOrAskForNotificationPermission() {
|
||||
return requestNotificationPermission().then((p) => p.isGranted);
|
||||
}
|
||||
|
||||
}
|
||||
final notificationPermissionProvider
|
||||
= StateNotifierProvider<NotificationPermissionNotifier, PermissionStatus>
|
||||
((ref) => NotificationPermissionNotifier());
|
21
mobile/lib/modules/settings/services/permission.service.dart
Normal file
21
mobile/lib/modules/settings/services/permission.service.dart
Normal file
@ -0,0 +1,21 @@
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
/// This class is for requesting permissions in the app
|
||||
class PermissionService {
|
||||
/// Requests the notification permission
|
||||
/// Note: In Android, this is always granted
|
||||
Future<PermissionStatus> requestNotificationPermission() {
|
||||
return Permission.notification.request();
|
||||
}
|
||||
|
||||
/// Whether the user has the permission or not
|
||||
/// Note: In Android, this is always true
|
||||
Future<bool> hasNotificationPermission() {
|
||||
return Permission.notification.isGranted;
|
||||
}
|
||||
|
||||
/// Either the permission was granted already or else ask for the permission
|
||||
Future<bool> hasOrAskForNotificationPermission() {
|
||||
return requestNotificationPermission().then((p) => p.isGranted);
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
|
||||
|
||||
SwitchListTile buildSwitchListTile(
|
||||
BuildContext context,
|
||||
AppSettingsService appSettingService,
|
||||
ValueNotifier<bool> valueNotifier,
|
||||
AppSettingsEnum settingsEnum, {
|
||||
required String title,
|
||||
String? subtitle,
|
||||
}) {
|
||||
return SwitchListTile.adaptive(
|
||||
key: Key(settingsEnum.name),
|
||||
value: valueNotifier.value,
|
||||
onChanged: (value) {
|
||||
valueNotifier.value = value;
|
||||
appSettingService.setSetting(settingsEnum, value);
|
||||
},
|
||||
activeColor: Theme.of(context).primaryColor,
|
||||
dense: true,
|
||||
title: Text(title, style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||
subtitle: subtitle != null ? Text(subtitle) : null,
|
||||
);
|
||||
}
|
@ -4,7 +4,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
|
||||
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
|
||||
import 'package:immich_mobile/modules/settings/ui/common.dart';
|
||||
import 'package:immich_mobile/modules/settings/ui/settings_switch_list_tile.dart';
|
||||
|
||||
class ImageViewerQualitySetting extends HookConsumerWidget {
|
||||
const ImageViewerQualitySetting({
|
||||
@ -44,19 +44,17 @@ class ImageViewerQualitySetting extends HookConsumerWidget {
|
||||
title: const Text('setting_image_viewer_help').tr(),
|
||||
dense: true,
|
||||
),
|
||||
buildSwitchListTile(
|
||||
context,
|
||||
settings,
|
||||
isPreview,
|
||||
AppSettingsEnum.loadPreview,
|
||||
SettingsSwitchListTile(
|
||||
appSettingService: settings,
|
||||
valueNotifier: isPreview,
|
||||
settingsEnum: AppSettingsEnum.loadPreview,
|
||||
title: "setting_image_viewer_preview_title".tr(),
|
||||
subtitle: "setting_image_viewer_preview_subtitle".tr(),
|
||||
),
|
||||
buildSwitchListTile(
|
||||
context,
|
||||
settings,
|
||||
isOriginal,
|
||||
AppSettingsEnum.loadOriginal,
|
||||
SettingsSwitchListTile(
|
||||
appSettingService: settings,
|
||||
valueNotifier: isOriginal,
|
||||
settingsEnum: AppSettingsEnum.loadOriginal,
|
||||
title: "setting_image_viewer_original_title".tr(),
|
||||
subtitle: "setting_image_viewer_original_subtitle".tr(),
|
||||
),
|
||||
|
@ -3,8 +3,10 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
|
||||
import 'package:immich_mobile/modules/settings/providers/permission.provider.dart';
|
||||
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
|
||||
import 'package:immich_mobile/modules/settings/ui/common.dart';
|
||||
import 'package:immich_mobile/modules/settings/ui/settings_switch_list_tile.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
class NotificationSetting extends HookConsumerWidget {
|
||||
const NotificationSetting({
|
||||
@ -14,12 +16,14 @@ class NotificationSetting extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final appSettingService = ref.watch(appSettingsServiceProvider);
|
||||
final permissionService = ref.watch(notificationPermissionProvider);
|
||||
|
||||
final sliderValue = useState(0.0);
|
||||
final totalProgressValue =
|
||||
useState(AppSettingsEnum.backgroundBackupTotalProgress.defaultValue);
|
||||
final singleProgressValue =
|
||||
useState(AppSettingsEnum.backgroundBackupSingleProgress.defaultValue);
|
||||
final hasPermission = permissionService == PermissionStatus.granted;
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
@ -35,6 +39,30 @@ class NotificationSetting extends HookConsumerWidget {
|
||||
[],
|
||||
);
|
||||
|
||||
// When permissions are permanently denied, you need to go to settings to
|
||||
// allow them
|
||||
showPermissionsDialog() {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
content: const Text('notification_permission_dialog_content').tr(),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: const Text('notification_permission_dialog_cancel').tr(),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
TextButton(
|
||||
child: const Text('notification_permission_dialog_settings').tr(),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
openAppSettings();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final String formattedValue = _formatSliderValue(sliderValue.value);
|
||||
return ExpansionTile(
|
||||
textColor: Theme.of(context).primaryColor,
|
||||
@ -51,23 +79,49 @@ class NotificationSetting extends HookConsumerWidget {
|
||||
),
|
||||
).tr(),
|
||||
children: [
|
||||
buildSwitchListTile(
|
||||
context,
|
||||
appSettingService,
|
||||
totalProgressValue,
|
||||
AppSettingsEnum.backgroundBackupTotalProgress,
|
||||
if (!hasPermission)
|
||||
ListTile(
|
||||
leading: const Icon(Icons.notifications_outlined),
|
||||
title: const Text('notification_permission_list_tile_title').tr(),
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text('notification_permission_list_tile_content').tr(),
|
||||
const SizedBox(height: 8),
|
||||
ElevatedButton(
|
||||
onPressed: ()
|
||||
=> ref.watch(notificationPermissionProvider.notifier)
|
||||
.requestNotificationPermission().then((permission) {
|
||||
if (permission == PermissionStatus.permanentlyDenied) {
|
||||
showPermissionsDialog();
|
||||
}
|
||||
}),
|
||||
child:
|
||||
const Text('notification_permission_list_tile_enable_button')
|
||||
.tr(),
|
||||
),
|
||||
],
|
||||
),
|
||||
isThreeLine: true,
|
||||
),
|
||||
SettingsSwitchListTile(
|
||||
enabled: hasPermission,
|
||||
appSettingService: appSettingService,
|
||||
valueNotifier: totalProgressValue,
|
||||
settingsEnum: AppSettingsEnum.backgroundBackupTotalProgress,
|
||||
title: 'setting_notifications_total_progress_title'.tr(),
|
||||
subtitle: 'setting_notifications_total_progress_subtitle'.tr(),
|
||||
),
|
||||
buildSwitchListTile(
|
||||
context,
|
||||
appSettingService,
|
||||
singleProgressValue,
|
||||
AppSettingsEnum.backgroundBackupSingleProgress,
|
||||
SettingsSwitchListTile(
|
||||
enabled: hasPermission,
|
||||
appSettingService: appSettingService,
|
||||
valueNotifier: singleProgressValue,
|
||||
settingsEnum: AppSettingsEnum.backgroundBackupSingleProgress,
|
||||
title: 'setting_notifications_single_progress_title'.tr(),
|
||||
subtitle: 'setting_notifications_single_progress_subtitle'.tr(),
|
||||
),
|
||||
ListTile(
|
||||
enabled: hasPermission,
|
||||
isThreeLine: false,
|
||||
dense: true,
|
||||
title: const Text(
|
||||
@ -76,7 +130,7 @@ class NotificationSetting extends HookConsumerWidget {
|
||||
).tr(args: [formattedValue]),
|
||||
subtitle: Slider(
|
||||
value: sliderValue.value,
|
||||
onChanged: (double v) => sliderValue.value = v,
|
||||
onChanged: !hasPermission ? null : (double v) => sliderValue.value = v,
|
||||
onChangeEnd: (double v) => appSettingService.setSetting(
|
||||
AppSettingsEnum.uploadErrorNotificationGracePeriod,
|
||||
v.toInt(),
|
||||
|
@ -0,0 +1,37 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
|
||||
|
||||
class SettingsSwitchListTile extends StatelessWidget {
|
||||
final AppSettingsService appSettingService;
|
||||
final ValueNotifier<bool> valueNotifier;
|
||||
final AppSettingsEnum settingsEnum;
|
||||
final String title;
|
||||
final bool enabled;
|
||||
final String? subtitle;
|
||||
|
||||
SettingsSwitchListTile({
|
||||
required this.appSettingService,
|
||||
required this.valueNotifier,
|
||||
required this.settingsEnum,
|
||||
required this.title,
|
||||
this.subtitle,
|
||||
this.enabled = true,
|
||||
}) : super(key: Key(settingsEnum.name));
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SwitchListTile.adaptive(
|
||||
value: valueNotifier.value,
|
||||
onChanged: !enabled ? null : (value) {
|
||||
valueNotifier.value = value;
|
||||
appSettingService.setSetting(settingsEnum, value);
|
||||
},
|
||||
activeColor: Theme
|
||||
.of(context)
|
||||
.primaryColor,
|
||||
dense: true,
|
||||
title: Text(title, style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||
subtitle: subtitle != null ? Text(subtitle!) : null,
|
||||
);
|
||||
}
|
||||
}
|
@ -1,5 +1,3 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
@ -41,7 +39,7 @@ class SettingsPage extends HookConsumerWidget {
|
||||
const ImageViewerQualitySetting(),
|
||||
const ThemeSetting(),
|
||||
const AssetListSettings(),
|
||||
if (Platform.isAndroid) const NotificationSetting(),
|
||||
const NotificationSetting(),
|
||||
//const ExperimentalSettings(),
|
||||
],
|
||||
).toList(),
|
||||
|
@ -891,6 +891,46 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.11.1"
|
||||
permission_handler:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: permission_handler
|
||||
sha256: "33c6a1253d1f95fd06fa74b65b7ba907ae9811f9d5c1d3150e51417d04b8d6a8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.2.0"
|
||||
permission_handler_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_android
|
||||
sha256: "8028362b40c4a45298f1cbfccd227c8dd6caf0e27088a69f2ba2ab15464159e2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.2.0"
|
||||
permission_handler_apple:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_apple
|
||||
sha256: "9c370ef6a18b1c4b2f7f35944d644a56aa23576f23abee654cf73968de93f163"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.0.7"
|
||||
permission_handler_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_platform_interface
|
||||
sha256: "68abbc472002b5e6dfce47fe9898c6b7d8328d58b5d2524f75e277c07a97eb84"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.9.0"
|
||||
permission_handler_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_windows
|
||||
sha256: f67cab14b4328574938ecea2db3475dad7af7ead6afab6338772c5f88963e38b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.2"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -45,6 +45,7 @@ dependencies:
|
||||
easy_image_viewer: ^1.2.0
|
||||
isar: *isar_version
|
||||
isar_flutter_libs: *isar_version # contains Isar Core
|
||||
permission_handler: ^10.2.0
|
||||
|
||||
openapi:
|
||||
path: openapi
|
||||
|
Loading…
Reference in New Issue
Block a user