import 'package:flutter/foundation.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/modules/backup/providers/manual_upload.provider.dart'; import 'package:immich_mobile/modules/settings/providers/notification_permission.provider.dart'; import 'package:permission_handler/permission_handler.dart'; final localNotificationService = Provider( (ref) => LocalNotificationService( ref.watch(notificationPermissionProvider), ref, ), ); class LocalNotificationService { final FlutterLocalNotificationsPlugin _localNotificationPlugin = FlutterLocalNotificationsPlugin(); final PermissionStatus _permissionStatus; final Ref ref; LocalNotificationService(this._permissionStatus, this.ref); static const manualUploadNotificationID = 4; static const manualUploadDetailedNotificationID = 5; static const manualUploadChannelName = 'Manual Asset Upload'; static const manualUploadChannelID = 'immich/manualUpload'; static const manualUploadChannelNameDetailed = 'Manual Asset Upload Detailed'; static const manualUploadDetailedChannelID = 'immich/manualUploadDetailed'; static const cancelUploadActionID = 'cancel_upload'; Future setup() async { const androidSetting = AndroidInitializationSettings('notification_icon'); const iosSetting = DarwinInitializationSettings(); const initSettings = InitializationSettings(android: androidSetting, iOS: iosSetting); await _localNotificationPlugin.initialize( initSettings, onDidReceiveNotificationResponse: _onDidReceiveForegroundNotificationResponse, ); } Future _showOrUpdateNotification( int id, String title, String body, AndroidNotificationDetails androidNotificationDetails, DarwinNotificationDetails iosNotificationDetails, ) async { final notificationDetails = NotificationDetails( android: androidNotificationDetails, iOS: iosNotificationDetails, ); if (_permissionStatus == PermissionStatus.granted) { await _localNotificationPlugin.show(id, title, body, notificationDetails); } } Future closeNotification(int id) { return _localNotificationPlugin.cancel(id); } Future showOrUpdateManualUploadStatus( String title, String body, { bool? isDetailed, bool? presentBanner, bool? showActions, int? maxProgress, int? progress, }) { var notificationlId = manualUploadNotificationID; var androidChannelID = manualUploadChannelID; var androidChannelName = manualUploadChannelName; // Separate Notification for Info/Alerts and Progress if (isDetailed != null && isDetailed) { notificationlId = manualUploadDetailedNotificationID; androidChannelID = manualUploadDetailedChannelID; androidChannelName = manualUploadChannelNameDetailed; } // Progress notification final androidNotificationDetails = (maxProgress != null && progress != null) ? AndroidNotificationDetails( androidChannelID, androidChannelName, ticker: title, showProgress: true, onlyAlertOnce: true, maxProgress: maxProgress, progress: progress, indeterminate: false, playSound: false, priority: Priority.low, importance: Importance.low, ongoing: true, actions: (showActions ?? false) ? [ const AndroidNotificationAction( cancelUploadActionID, 'Cancel', showsUserInterface: true, ), ] : null, ) // Non-progress notification : AndroidNotificationDetails( androidChannelID, androidChannelName, playSound: false, ); final iosNotificationDetails = DarwinNotificationDetails( presentBadge: true, presentList: true, presentBanner: presentBanner, ); return _showOrUpdateNotification( notificationlId, title, body, androidNotificationDetails, iosNotificationDetails, ); } void _onDidReceiveForegroundNotificationResponse( NotificationResponse notificationResponse, ) { // Handle notification actions switch (notificationResponse.actionId) { case cancelUploadActionID: { debugPrint("User cancelled manual upload operation"); ref.read(manualUploadProvider.notifier).cancelBackup(); } } } }