From b1a93688d3c159c70cca861dc51c1e2dd9859e36 Mon Sep 17 00:00:00 2001 From: Santo Shakil Date: Fri, 4 Sep 2026 07:00:22 +0600 Subject: [PATCH] fix(mobile): refresh the memory lane after resume (#31239) --- .../providers/app_life_cycle.provider.dart | 1 + .../app_life_cycle_provider_test.dart | 54 ++++++++++++++++++- 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/mobile/lib/providers/app_life_cycle.provider.dart b/mobile/lib/providers/app_life_cycle.provider.dart index 0869cf1ff95234..c5702c4b5b9147 100644 --- a/mobile/lib/providers/app_life_cycle.provider.dart +++ b/mobile/lib/providers/app_life_cycle.provider.dart @@ -130,6 +130,7 @@ class AppLifeCycleNotifier extends StateNotifier { syncSuccess = await backgroundManager.syncRemote(); }, "syncRemote"), ]); + _ref.invalidate(memoryLaneProvider); _ref.invalidate(allMemoriesProvider); if (syncSuccess) { await Future.wait([ diff --git a/mobile/test/providers/app_life_cycle_provider_test.dart b/mobile/test/providers/app_life_cycle_provider_test.dart index 12db286ff4676e..bf05e5671fb4ed 100644 --- a/mobile/test/providers/app_life_cycle_provider_test.dart +++ b/mobile/test/providers/app_life_cycle_provider_test.dart @@ -2,21 +2,30 @@ import 'dart:async'; import 'package:flutter_test/flutter_test.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/config/app_config.dart'; import 'package:immich_mobile/domain/models/log.model.dart'; +import 'package:immich_mobile/domain/services/device_permission.service.dart'; import 'package:immich_mobile/domain/services/log.service.dart'; import 'package:immich_mobile/models/auth/auth_state.model.dart'; import 'package:immich_mobile/models/server_info/server_version.model.dart'; import 'package:immich_mobile/providers/app_life_cycle.provider.dart'; import 'package:immich_mobile/providers/auth.provider.dart'; +import 'package:immich_mobile/providers/background_sync.provider.dart'; import 'package:immich_mobile/providers/backup/backup.provider.dart'; +import 'package:immich_mobile/providers/gallery_permission.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/memory.provider.dart'; import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; +import 'package:immich_mobile/providers/permission.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/providers/websocket.provider.dart'; import 'package:immich_mobile/utils/upload_speed_calculator.dart'; import 'package:mocktail/mocktail.dart'; +import 'package:permission_handler/permission_handler.dart'; import '../infrastructure/repository.mock.dart'; +import '../repository.mocks.dart'; import '../service.mocks.dart'; class FakeLogMessage extends Fake implements LogMessage {} @@ -51,6 +60,7 @@ class TestWebsocketNotifier extends WebsocketNotifier { int connectCount = 0; int disconnectCount = 0; + bool throwOnConnect = true; final connectCalled = Completer(); @override @@ -59,7 +69,9 @@ class TestWebsocketNotifier extends WebsocketNotifier { if (!connectCalled.isCompleted) { connectCalled.complete(); } - throw StateError('unexpected websocket connection'); + if (throwOnConnect) { + throw StateError('unexpected websocket connection'); + } } @override @@ -70,6 +82,18 @@ class TestDriftBackupNotifier extends BackupNotifier { TestDriftBackupNotifier() : super(MockForegroundUploadService(), MockBackgroundUploadService(), UploadSpeedManager()); } +class TestNotificationPermissionNotifier extends NotificationPermissionNotifier { + @override + Future getNotificationPermission() async => state; +} + +class TestGalleryPermissionNotifier extends GalleryPermissionNotifier { + TestGalleryPermissionNotifier() : super(DevicePermissionService(MockPermissionRepository())); + + @override + Future getGalleryPermissionStatus() async => state; +} + void main() { late LogService logService; late Completer serverVersion; @@ -79,6 +103,7 @@ void main() { late TestWebsocketNotifier websocket; late AppLifeCycleNotifier lifeCycle; late int serverVersionCount; + late int memoryLaneBuilds; setUpAll(() async { final logRepository = MockLogRepository(); @@ -100,7 +125,9 @@ void main() { serverVersion = Completer(); serverInfoService = MockServerInfoService(); lockService = MockBackgroundWorkerLockService(); + final backgroundSync = MockBackgroundSyncManager(); serverVersionCount = 0; + memoryLaneBuilds = 0; when(() => serverInfoService.getServerVersion()).thenAnswer((_) { serverVersionCount++; @@ -108,6 +135,10 @@ void main() { }); when(() => lockService.lock()).thenAnswer((_) async {}); when(() => lockService.unlock()).thenAnswer((_) async {}); + when(() => backgroundSync.cancelResumeSyncs()).thenAnswer((_) async {}); + when(() => backgroundSync.syncLocal(full: any(named: 'full'))).thenAnswer((_) async {}); + when(() => backgroundSync.syncRemote()).thenAnswer((_) async => true); + when(() => backgroundSync.hashAssets()).thenAnswer((_) async {}); container = ProviderContainer( overrides: [ @@ -118,6 +149,14 @@ void main() { }), backupProvider.overrideWith((_) => TestDriftBackupNotifier()), backgroundWorkerLockServiceProvider.overrideWithValue(lockService), + backgroundSyncProvider.overrideWithValue(backgroundSync), + appConfigProvider.overrideWithValue(defaultConfig), + notificationPermissionProvider.overrideWith((_) => TestNotificationPermissionNotifier()), + galleryPermissionNotifier.overrideWith((_) => TestGalleryPermissionNotifier()), + memoryLaneProvider.overrideWith((_) { + memoryLaneBuilds++; + return const []; + }), ], ); lifeCycle = container.read(appStateProvider.notifier); @@ -177,4 +216,17 @@ void main() { expect(websocket.disconnectCount, 2); expect(websocket.connectCount, 1); }); + + test('resume re-queries the memory lane', () async { + container.listen(memoryLaneProvider, (_, _) {}); + expect(memoryLaneBuilds, 1); + + await lifeCycle.handleAppPause(); + websocket.throwOnConnect = false; + serverVersion.complete(); + await lifeCycle.handleAppResume(); + await Future.delayed(Duration.zero); + + expect(memoryLaneBuilds, 2); + }); }