fix(mobile): stale details after editing asset date (#28977)
This commit is contained in:
@@ -96,6 +96,11 @@ class _AssetPageState extends ConsumerState<AssetPage> {
|
|||||||
switch (event) {
|
switch (event) {
|
||||||
case ViewerShowDetailsEvent():
|
case ViewerShowDetailsEvent():
|
||||||
_showDetails();
|
_showDetails();
|
||||||
|
case TimelineReloadEvent():
|
||||||
|
final asset = ref.read(timelineServiceProvider).getAssetSafe(widget.index);
|
||||||
|
if (asset != _asset) {
|
||||||
|
setState(() => _asset = asset);
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -353,6 +353,10 @@ class ActionNotifier extends Notifier<void> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (source == ActionSource.viewer) {
|
||||||
|
ref.invalidate(assetExifProvider);
|
||||||
|
}
|
||||||
|
|
||||||
return ActionResult(count: ids.length, success: true);
|
return ActionResult(count: ids.length, success: true);
|
||||||
} catch (error, stack) {
|
} catch (error, stack) {
|
||||||
_logger.severe('Failed to edit date and time for assets', 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user