1
0
mirror of https://github.com/immich-app/immich.git synced 2025-10-31 00:18:28 +02:00

fix(mobile): manual asset upload - app state handling + cancel button (#3611)

* feat(mobile): Cancel manual asset upload

* fix(mobile): re-add the missing translation keys

* feat(mobile): show manual upload error in backup page

* refactor: manual upload in-progress count

* fix(mobile): handle app state properly during manual asset upload
This commit is contained in:
shalong-tanwen
2023-08-12 21:02:58 +00:00
committed by GitHub
parent b790354f9a
commit 77a5820c3c
8 changed files with 318 additions and 201 deletions

View File

@@ -1,4 +1,19 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/backup/background_service/background.service.dart';
import 'package:immich_mobile/modules/backup/models/backup_state.model.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/backup/providers/manual_upload.provider.dart';
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
import 'package:immich_mobile/modules/memories/providers/memory.provider.dart';
import 'package:immich_mobile/modules/onboarding/providers/gallery_permission.provider.dart';
import 'package:immich_mobile/modules/settings/providers/notification_permission.provider.dart';
import 'package:immich_mobile/shared/providers/asset.provider.dart';
import 'package:immich_mobile/shared/providers/release_info.provider.dart';
import 'package:immich_mobile/shared/providers/server_info.provider.dart';
import 'package:immich_mobile/shared/providers/websocket.provider.dart';
import 'package:immich_mobile/shared/services/immich_logger.service.dart';
import 'package:permission_handler/permission_handler.dart';
enum AppStateEnum {
active,
@@ -8,6 +23,70 @@ enum AppStateEnum {
detached,
}
final appStateProvider = StateProvider<AppStateEnum>((ref) {
return AppStateEnum.active;
class AppStateNotiifer extends StateNotifier<AppStateEnum> {
final Ref ref;
AppStateNotiifer(this.ref) : super(AppStateEnum.active);
void updateAppState(AppStateEnum appState) {
state = appState;
}
AppStateEnum getAppState() {
return state;
}
void handleAppResume() {
state = AppStateEnum.resumed;
var isAuthenticated = ref.watch(authenticationProvider).isAuthenticated;
final permission = ref.watch(galleryPermissionNotifier);
// Needs to be logged in and have gallery permissions
if (isAuthenticated && (permission.isGranted || permission.isLimited)) {
ref.read(backupProvider.notifier).resumeBackup();
ref.read(backgroundServiceProvider).resumeServiceIfEnabled();
ref.watch(assetProvider.notifier).getAllAsset();
ref.watch(serverInfoProvider.notifier).getServerVersion();
}
ref.watch(websocketProvider.notifier).connect();
ref.watch(releaseInfoProvider.notifier).checkGithubReleaseInfo();
ref
.watch(notificationPermissionProvider.notifier)
.getNotificationPermission();
ref.watch(galleryPermissionNotifier.notifier).getGalleryPermissionStatus();
ref.read(iOSBackgroundSettingsProvider.notifier).refresh();
ref.invalidate(memoryFutureProvider);
}
void handleAppInactivity() {
state = AppStateEnum.inactive;
// Do not handle inactivity if manual upload is in progress
if (ref.watch(backupProvider.notifier).backupProgress !=
BackUpProgressEnum.manualInProgress) {
ImmichLogger().flush();
ref.read(websocketProvider.notifier).disconnect();
ref.read(backupProvider.notifier).cancelBackup();
}
}
void handleAppPause() {
state = AppStateEnum.paused;
}
void handleAppDetached() {
state = AppStateEnum.detached;
ref.watch(manualUploadProvider.notifier).cancelBackup();
}
}
final appStateProvider =
StateNotifierProvider<AppStateNotiifer, AppStateEnum>((ref) {
return AppStateNotiifer(ref);
});

View File

@@ -1,13 +1,24 @@
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());
final localNotificationService = Provider(
(ref) => LocalNotificationService(
ref.watch(notificationPermissionProvider),
ref,
),
);
class LocalNotificationService {
static final LocalNotificationService _instance =
LocalNotificationService._internal();
final FlutterLocalNotificationsPlugin _localNotificationPlugin =
FlutterLocalNotificationsPlugin();
final PermissionStatus _permissionStatus;
final Ref ref;
LocalNotificationService(this._permissionStatus, this.ref);
static const manualUploadNotificationID = 4;
static const manualUploadDetailedNotificationID = 5;
@@ -15,9 +26,7 @@ class LocalNotificationService {
static const manualUploadChannelID = 'immich/manualUpload';
static const manualUploadChannelNameDetailed = 'Manual Asset Upload Detailed';
static const manualUploadDetailedChannelID = 'immich/manualUploadDetailed';
factory LocalNotificationService() => _instance;
LocalNotificationService._internal();
static const cancelUploadActionID = 'cancel_upload';
Future<void> setup() async {
const androidSetting = AndroidInitializationSettings('notification_icon');
@@ -26,56 +35,28 @@ class LocalNotificationService {
const initSettings =
InitializationSettings(android: androidSetting, iOS: iosSetting);
await _localNotificationPlugin.initialize(initSettings);
await _localNotificationPlugin.initialize(
initSettings,
onDidReceiveNotificationResponse:
_onDidReceiveForegroundNotificationResponse,
);
}
Future<void> _showOrUpdateNotification(
int id,
String channelId,
String channelName,
String title,
String body, {
bool? ongoing,
bool? playSound,
bool? showProgress,
Priority? priority,
Importance? importance,
bool? onlyAlertOnce,
int? maxProgress,
int? progress,
bool? indeterminate,
bool? presentBadge,
bool? presentBanner,
bool? presentList,
}) async {
var androidNotificationDetails = AndroidNotificationDetails(
channelId,
channelName,
ticker: title,
playSound: playSound ?? false,
showProgress: showProgress ?? false,
maxProgress: maxProgress ?? 0,
progress: progress ?? 0,
onlyAlertOnce: onlyAlertOnce ?? false,
indeterminate: indeterminate ?? false,
priority: priority ?? Priority.defaultPriority,
importance: importance ?? Importance.defaultImportance,
ongoing: ongoing ?? false,
);
var iosNotificationDetails = DarwinNotificationDetails(
presentBadge: presentBadge ?? false,
presentBanner: presentBanner ?? false,
presentList: presentList ?? false,
);
String body,
AndroidNotificationDetails androidNotificationDetails,
DarwinNotificationDetails iosNotificationDetails,
) async {
final notificationDetails = NotificationDetails(
android: androidNotificationDetails,
iOS: iosNotificationDetails,
);
await _localNotificationPlugin.show(id, title, body, notificationDetails);
if (_permissionStatus == PermissionStatus.granted) {
await _localNotificationPlugin.show(id, title, body, notificationDetails);
}
}
Future<void> closeNotification(int id) {
@@ -87,46 +68,76 @@ class LocalNotificationService {
String body, {
bool? isDetailed,
bool? presentBanner,
bool? showActions,
int? maxProgress,
int? progress,
}) {
var notificationlId = manualUploadNotificationID;
var channelId = manualUploadChannelID;
var channelName = manualUploadChannelName;
var androidChannelID = manualUploadChannelID;
var androidChannelName = manualUploadChannelName;
// Separate Notification for Info/Alerts and Progress
if (isDetailed != null && isDetailed) {
notificationlId = manualUploadDetailedNotificationID;
channelId = manualUploadDetailedChannelID;
channelName = manualUploadChannelNameDetailed;
androidChannelID = manualUploadDetailedChannelID;
androidChannelName = manualUploadChannelNameDetailed;
}
final isProgressNotification = maxProgress != null && progress != null;
return isProgressNotification
? _showOrUpdateNotification(
notificationlId,
channelId,
channelName,
title,
body,
// Progress notification
final androidNotificationDetails = (maxProgress != null && progress != null)
? AndroidNotificationDetails(
androidChannelID,
androidChannelName,
ticker: title,
showProgress: true,
onlyAlertOnce: true,
maxProgress: maxProgress,
progress: progress,
indeterminate: false,
presentList: true,
playSound: false,
priority: Priority.low,
importance: Importance.low,
presentBadge: true,
ongoing: true,
actions: (showActions ?? false)
? <AndroidNotificationAction>[
const AndroidNotificationAction(
cancelUploadActionID,
'Cancel',
showsUserInterface: true,
)
]
: null,
)
: _showOrUpdateNotification(
notificationlId,
channelId,
channelName,
title,
body,
presentList: true,
presentBadge: true,
presentBanner: presentBanner,
// 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();
}
}
}
}