fix(mobile): stale details after editing asset date (#28977)

This commit is contained in:
Santo Shakil
2026-06-11 08:32:02 +06:00
committed by GitHub
parent 9cb94343d1
commit d227ba2d51
3 changed files with 125 additions and 0 deletions
@@ -96,6 +96,11 @@ class _AssetPageState extends ConsumerState<AssetPage> {
switch (event) {
case ViewerShowDetailsEvent():
_showDetails();
case TimelineReloadEvent():
final asset = ref.read(timelineServiceProvider).getAssetSafe(widget.index);
if (asset != _asset) {
setState(() => _asset = asset);
}
default:
}
}
@@ -353,6 +353,10 @@ class ActionNotifier extends Notifier<void> {
return null;
}
if (source == ActionSource.viewer) {
ref.invalidate(assetExifProvider);
}
return ActionResult(count: ids.length, success: true);
} catch (error, stack) {
_logger.severe('Failed to edit date and time for assets', error, stack);
@@ -0,0 +1,116 @@
import 'package:flutter/widgets.dart';
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/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/domain/services/asset.service.dart';
import 'package:immich_mobile/domain/services/user.service.dart';
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
import 'package:immich_mobile/providers/infrastructure/asset_viewer/asset.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/services/action.service.dart';
import 'package:immich_mobile/services/download.service.dart';
import 'package:immich_mobile/services/foreground_upload.service.dart';
import 'package:mocktail/mocktail.dart';
class MockActionService extends Mock implements ActionService {}
class MockAssetService extends Mock implements AssetService {}
class MockDownloadService extends Mock implements DownloadService {}
class MockForegroundUploadService extends Mock implements ForegroundUploadService {}
class MockUserService extends Mock implements UserService {}
class FakeBuildContext extends Fake implements BuildContext {}
final _user = UserDto(id: 'user-1', email: 'user@test.dev', name: 'user', profileChangedAt: DateTime(2026));
final _asset = RemoteAsset(
id: 'asset-1',
name: 'photo.jpg',
ownerId: 'user-1',
checksum: 'checksum-1',
type: AssetType.image,
createdAt: DateTime(2026, 6, 10, 10, 27),
updatedAt: DateTime(2026, 6, 10, 10, 27),
isEdited: false,
);
void main() {
late ProviderContainer container;
late MockActionService actionService;
late MockAssetService assetService;
setUpAll(() {
registerFallbackValue(FakeBuildContext());
registerFallbackValue(_asset);
registerFallbackValue(<String>[]);
});
setUp(() {
actionService = MockActionService();
assetService = MockAssetService();
final userService = MockUserService();
when(() => actionService.editDateTime(any(), any())).thenAnswer((_) async => true);
when(() => assetService.watchAsset(any())).thenAnswer((_) => const Stream.empty());
when(() => assetService.getExif(any())).thenAnswer((_) async => null);
when(() => userService.tryGetMyUser()).thenReturn(_user);
when(() => userService.watchMyUser()).thenAnswer((_) => const Stream.empty());
container = ProviderContainer(
overrides: [
actionServiceProvider.overrideWithValue(actionService),
assetServiceProvider.overrideWithValue(assetService),
downloadServiceProvider.overrideWithValue(MockDownloadService()),
foregroundUploadServiceProvider.overrideWithValue(MockForegroundUploadService()),
currentUserProvider.overrideWith((ref) => CurrentUserProvider(userService)),
],
);
addTearDown(container.dispose);
});
group('editDateTime', () {
test('refreshes the exif provider when editing from the viewer', () async {
container.read(assetViewerProvider.notifier).setAsset(_asset);
container.listen(assetExifProvider(_asset), (_, __) {});
await container.read(assetExifProvider(_asset).future);
final result = await container.read(actionProvider.notifier).editDateTime(ActionSource.viewer, FakeBuildContext());
expect(result?.success, isTrue);
await container.read(assetExifProvider(_asset).future);
verify(() => assetService.getExif(_asset)).called(2);
});
test('leaves the exif provider cached when editing from the timeline', () async {
container.read(assetViewerProvider.notifier).setAsset(_asset);
container.listen(assetExifProvider(_asset), (_, __) {});
await container.read(assetExifProvider(_asset).future);
final result = await container.read(actionProvider.notifier).editDateTime(ActionSource.timeline, FakeBuildContext());
expect(result?.success, isTrue);
await container.read(assetExifProvider(_asset).future);
verify(() => assetService.getExif(_asset)).called(1);
});
test('does not refresh the exif provider when the edit is cancelled', () async {
when(() => actionService.editDateTime(any(), any())).thenAnswer((_) async => false);
container.read(assetViewerProvider.notifier).setAsset(_asset);
container.listen(assetExifProvider(_asset), (_, __) {});
await container.read(assetExifProvider(_asset).future);
final result = await container.read(actionProvider.notifier).editDateTime(ActionSource.viewer, FakeBuildContext());
expect(result, isNull);
await container.read(assetExifProvider(_asset).future);
verify(() => assetService.getExif(_asset)).called(1);
});
});
}