mirror of
https://github.com/immich-app/immich.git
synced 2026-05-23 08:02:29 -04:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fc40c223f4 | |||
| c2779258b4 | |||
| 76e9aa012e | |||
| cb328c63cf | |||
| afc470e8f4 | |||
| b156ae46b6 | |||
| 67b1a9d99e | |||
| d4f7c2ae0a |
@@ -1,20 +1,28 @@
|
|||||||
class ImageConfig {
|
class ImageConfig {
|
||||||
final bool preferRemote;
|
final bool preferRemote;
|
||||||
|
final bool loadPreview;
|
||||||
final bool loadOriginal;
|
final bool loadOriginal;
|
||||||
|
|
||||||
const ImageConfig({this.preferRemote = false, this.loadOriginal = false});
|
const ImageConfig({this.preferRemote = false, this.loadPreview = true, this.loadOriginal = false});
|
||||||
|
|
||||||
ImageConfig copyWith({bool? preferRemote, bool? loadOriginal}) =>
|
ImageConfig copyWith({bool? preferRemote, bool? loadPreview, bool? loadOriginal}) => ImageConfig(
|
||||||
ImageConfig(preferRemote: preferRemote ?? this.preferRemote, loadOriginal: loadOriginal ?? this.loadOriginal);
|
preferRemote: preferRemote ?? this.preferRemote,
|
||||||
|
loadPreview: loadPreview ?? this.loadPreview,
|
||||||
|
loadOriginal: loadOriginal ?? this.loadOriginal,
|
||||||
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) =>
|
||||||
identical(this, other) ||
|
identical(this, other) ||
|
||||||
(other is ImageConfig && other.preferRemote == preferRemote && other.loadOriginal == loadOriginal);
|
(other is ImageConfig &&
|
||||||
|
other.preferRemote == preferRemote &&
|
||||||
|
other.loadPreview == loadPreview &&
|
||||||
|
other.loadOriginal == loadOriginal);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(preferRemote, loadOriginal);
|
int get hashCode => Object.hash(preferRemote, loadPreview, loadOriginal);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'ImageConfig(preferRemoteImage: $preferRemote, loadOriginal: $loadOriginal)';
|
String toString() =>
|
||||||
|
'ImageConfig(preferRemoteImage: $preferRemote, loadPreview: $loadPreview, loadOriginal: $loadOriginal)';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ enum MetadataKey<T extends Object> {
|
|||||||
|
|
||||||
// Image
|
// Image
|
||||||
imagePreferRemote<bool>(.appConfig, 'image.preferRemote', false),
|
imagePreferRemote<bool>(.appConfig, 'image.preferRemote', false),
|
||||||
|
imageLoadPreview<bool>(.appConfig, 'image.loadPreview', true),
|
||||||
imageLoadOriginal<bool>(.appConfig, 'image.loadOriginal', false),
|
imageLoadOriginal<bool>(.appConfig, 'image.loadOriginal', false),
|
||||||
|
|
||||||
// Viewer
|
// Viewer
|
||||||
|
|||||||
@@ -133,7 +133,11 @@ extension<T extends Object> on MetadataDomain<T> {
|
|||||||
groupAssetsBy: repo._read(.timelineGroupAssetsBy),
|
groupAssetsBy: repo._read(.timelineGroupAssetsBy),
|
||||||
storageIndicator: repo._read(.timelineStorageIndicator),
|
storageIndicator: repo._read(.timelineStorageIndicator),
|
||||||
),
|
),
|
||||||
image: .new(preferRemote: repo._read(.imagePreferRemote), loadOriginal: repo._read(.imageLoadOriginal)),
|
image: .new(
|
||||||
|
preferRemote: repo._read(.imagePreferRemote),
|
||||||
|
loadPreview: repo._read(.imageLoadPreview),
|
||||||
|
loadOriginal: repo._read(.imageLoadOriginal),
|
||||||
|
),
|
||||||
viewer: .new(
|
viewer: .new(
|
||||||
loopVideo: repo._read(.viewerLoopVideo),
|
loopVideo: repo._read(.viewerLoopVideo),
|
||||||
loadOriginalVideo: repo._read(.viewerLoadOriginalVideo),
|
loadOriginalVideo: repo._read(.viewerLoadOriginalVideo),
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ mixin CancellableImageProviderMixin<T extends Object> on CancellableImageProvide
|
|||||||
|
|
||||||
completer.operation.valueOrCancellation().whenComplete(() {
|
completer.operation.valueOrCancellation().whenComplete(() {
|
||||||
cachedStream.removeListener(listener);
|
cachedStream.removeListener(listener);
|
||||||
cachedOperation = null;
|
|
||||||
});
|
});
|
||||||
cachedOperation = completer.operation;
|
cachedOperation = completer.operation;
|
||||||
return null;
|
return null;
|
||||||
@@ -94,6 +93,9 @@ mixin CancellableImageProviderMixin<T extends Object> on CancellableImageProvide
|
|||||||
isFinished = isFinal;
|
isFinished = isFinal;
|
||||||
return codec;
|
return codec;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
if (isCancelled) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
if (isFinal) {
|
if (isFinal) {
|
||||||
isFinished = true;
|
isFinished = true;
|
||||||
PaintingBinding.instance.imageCache.evict(this);
|
PaintingBinding.instance.imageCache.evict(this);
|
||||||
@@ -105,18 +107,33 @@ mixin CancellableImageProviderMixin<T extends Object> on CancellableImageProvide
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<ImageInfo> initialImageStream() async* {
|
Stream<ImageInfo> initialImageStream({required bool isFinal}) async* {
|
||||||
final cachedOperation = this.cachedOperation;
|
final cachedOperation = this.cachedOperation;
|
||||||
|
if (isCancelled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (cachedOperation == null) {
|
if (cachedOperation == null) {
|
||||||
|
// image resolved synchronously
|
||||||
|
isFinished = isFinal;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final cachedImage = await cachedOperation.valueOrCancellation();
|
final cachedImage = await cachedOperation.valueOrCancellation();
|
||||||
if (cachedImage != null && !isCancelled) {
|
if (isCancelled || cachedImage == null) {
|
||||||
yield cachedImage;
|
return;
|
||||||
}
|
}
|
||||||
|
isFinished = isFinal;
|
||||||
|
yield cachedImage;
|
||||||
} catch (e, stack) {
|
} catch (e, stack) {
|
||||||
|
if (isCancelled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isFinal) {
|
||||||
|
isFinished = true;
|
||||||
|
PaintingBinding.instance.imageCache.evict(this);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
_log.severe('Error loading initial image', e, stack);
|
_log.severe('Error loading initial image', e, stack);
|
||||||
} finally {
|
} finally {
|
||||||
this.cachedOperation = null;
|
this.cachedOperation = null;
|
||||||
|
|||||||
@@ -98,51 +98,55 @@ class LocalFullImageProvider extends CancellableImageProvider<LocalFullImageProv
|
|||||||
}
|
}
|
||||||
|
|
||||||
Stream<ImageInfo> _codec(LocalFullImageProvider key, ImageDecoderCallback decode) async* {
|
Stream<ImageInfo> _codec(LocalFullImageProvider key, ImageDecoderCallback decode) async* {
|
||||||
yield* initialImageStream();
|
final loadOriginal = MetadataRepository.instance.appConfig.image.loadOriginal;
|
||||||
|
final loadPreview = MetadataRepository.instance.appConfig.image.loadPreview;
|
||||||
|
yield* initialImageStream(isFinal: !loadOriginal && !loadPreview);
|
||||||
|
|
||||||
if (isCancelled) {
|
if (isCancelled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final loadOriginal = MetadataRepository.instance.appConfig.image.loadOriginal;
|
if (loadPreview) {
|
||||||
final devicePixelRatio = PlatformDispatcher.instance.views.first.devicePixelRatio;
|
final devicePixelRatio = PlatformDispatcher.instance.views.first.devicePixelRatio;
|
||||||
var request = this.request = LocalImageRequest(
|
final previewRequest = request = LocalImageRequest(
|
||||||
localId: key.id,
|
localId: key.id,
|
||||||
size: Size(size.width * devicePixelRatio, size.height * devicePixelRatio),
|
size: Size(size.width * devicePixelRatio, size.height * devicePixelRatio),
|
||||||
assetType: key.assetType,
|
assetType: key.assetType,
|
||||||
);
|
);
|
||||||
yield* loadRequest(request, decode, isFinal: !loadOriginal);
|
yield* loadRequest(previewRequest, decode, isFinal: !loadOriginal);
|
||||||
|
|
||||||
|
if (isCancelled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!loadOriginal) {
|
if (!loadOriginal) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isCancelled) {
|
final originalRequest = request = LocalImageRequest(localId: key.id, assetType: key.assetType, size: Size.zero);
|
||||||
return;
|
yield* loadRequest(originalRequest, decode, isFinal: true);
|
||||||
}
|
|
||||||
|
|
||||||
request = this.request = LocalImageRequest(localId: key.id, assetType: key.assetType, size: Size.zero);
|
|
||||||
|
|
||||||
yield* loadRequest(request, decode, isFinal: true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<Object> _animatedCodec(LocalFullImageProvider key, ImageDecoderCallback decode) async* {
|
Stream<Object> _animatedCodec(LocalFullImageProvider key, ImageDecoderCallback decode) async* {
|
||||||
yield* initialImageStream();
|
yield* initialImageStream(isFinal: false);
|
||||||
|
|
||||||
if (isCancelled) {
|
if (isCancelled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final devicePixelRatio = PlatformDispatcher.instance.views.first.devicePixelRatio;
|
if (MetadataRepository.instance.appConfig.image.loadPreview) {
|
||||||
final previewRequest = request = LocalImageRequest(
|
final devicePixelRatio = PlatformDispatcher.instance.views.first.devicePixelRatio;
|
||||||
localId: key.id,
|
final previewRequest = request = LocalImageRequest(
|
||||||
size: Size(size.width * devicePixelRatio, size.height * devicePixelRatio),
|
localId: key.id,
|
||||||
assetType: key.assetType,
|
size: Size(size.width * devicePixelRatio, size.height * devicePixelRatio),
|
||||||
);
|
assetType: key.assetType,
|
||||||
yield* loadRequest(previewRequest, decode, isFinal: false);
|
);
|
||||||
|
yield* loadRequest(previewRequest, decode, isFinal: false);
|
||||||
|
|
||||||
if (isCancelled) {
|
if (isCancelled) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// always try original for animated, since previews don't support animation
|
// always try original for animated, since previews don't support animation
|
||||||
|
|||||||
@@ -108,31 +108,35 @@ class RemoteFullImageProvider extends CancellableImageProvider<RemoteFullImagePr
|
|||||||
}
|
}
|
||||||
|
|
||||||
Stream<ImageInfo> _codec(RemoteFullImageProvider key, ImageDecoderCallback decode) async* {
|
Stream<ImageInfo> _codec(RemoteFullImageProvider key, ImageDecoderCallback decode) async* {
|
||||||
yield* initialImageStream();
|
final isImage = assetType == AssetType.image;
|
||||||
|
final loadOriginal = isImage && MetadataRepository.instance.appConfig.image.loadOriginal;
|
||||||
|
final loadPreview = isImage && MetadataRepository.instance.appConfig.image.loadPreview;
|
||||||
|
yield* initialImageStream(isFinal: !loadOriginal && !loadPreview);
|
||||||
|
|
||||||
if (isCancelled) {
|
if (isCancelled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final previewRequest = request = RemoteImageRequest(
|
if (loadPreview) {
|
||||||
uri: getThumbnailUrlForRemoteId(
|
final previewRequest = request = RemoteImageRequest(
|
||||||
key.assetId,
|
uri: getThumbnailUrlForRemoteId(
|
||||||
type: AssetMediaSize.preview,
|
key.assetId,
|
||||||
thumbhash: key.thumbhash,
|
type: AssetMediaSize.preview,
|
||||||
edited: key.edited,
|
thumbhash: key.thumbhash,
|
||||||
),
|
edited: key.edited,
|
||||||
);
|
),
|
||||||
final loadOriginal = assetType == AssetType.image && MetadataRepository.instance.appConfig.image.loadOriginal;
|
);
|
||||||
yield* loadRequest(previewRequest, decode, isFinal: !loadOriginal);
|
yield* loadRequest(previewRequest, decode, isFinal: !loadOriginal);
|
||||||
|
|
||||||
|
if (isCancelled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!loadOriginal) {
|
if (!loadOriginal) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isCancelled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final originalRequest = request = RemoteImageRequest(
|
final originalRequest = request = RemoteImageRequest(
|
||||||
uri: getOriginalUrlForRemoteId(key.assetId, edited: key.edited),
|
uri: getOriginalUrlForRemoteId(key.assetId, edited: key.edited),
|
||||||
);
|
);
|
||||||
@@ -140,24 +144,26 @@ class RemoteFullImageProvider extends CancellableImageProvider<RemoteFullImagePr
|
|||||||
}
|
}
|
||||||
|
|
||||||
Stream<Object> _animatedCodec(RemoteFullImageProvider key, ImageDecoderCallback decode) async* {
|
Stream<Object> _animatedCodec(RemoteFullImageProvider key, ImageDecoderCallback decode) async* {
|
||||||
yield* initialImageStream();
|
yield* initialImageStream(isFinal: false);
|
||||||
|
|
||||||
if (isCancelled) {
|
if (isCancelled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final previewRequest = request = RemoteImageRequest(
|
if (MetadataRepository.instance.appConfig.image.loadPreview) {
|
||||||
uri: getThumbnailUrlForRemoteId(
|
final previewRequest = request = RemoteImageRequest(
|
||||||
key.assetId,
|
uri: getThumbnailUrlForRemoteId(
|
||||||
type: AssetMediaSize.preview,
|
key.assetId,
|
||||||
thumbhash: key.thumbhash,
|
type: AssetMediaSize.preview,
|
||||||
edited: key.edited,
|
thumbhash: key.thumbhash,
|
||||||
),
|
edited: key.edited,
|
||||||
);
|
),
|
||||||
yield* loadRequest(previewRequest, decode, isFinal: false);
|
);
|
||||||
|
yield* loadRequest(previewRequest, decode, isFinal: false);
|
||||||
|
|
||||||
if (isCancelled) {
|
if (isCancelled) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// always try original for animated, since previews don't support animation
|
// always try original for animated, since previews don't support animation
|
||||||
|
|||||||
@@ -12,7 +12,11 @@ class ImageViewerQualitySetting extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final isPreview = useState(ref.read(appConfigProvider).image.loadPreview);
|
||||||
final isOriginal = useState(ref.read(appConfigProvider).image.loadOriginal);
|
final isOriginal = useState(ref.read(appConfigProvider).image.loadOriginal);
|
||||||
|
useValueChanged<bool, void>(isPreview.value, (_, __) {
|
||||||
|
ref.read(metadataProvider).write(.imageLoadPreview, isPreview.value);
|
||||||
|
});
|
||||||
useValueChanged<bool, void>(isOriginal.value, (_, __) {
|
useValueChanged<bool, void>(isOriginal.value, (_, __) {
|
||||||
ref.read(metadataProvider).write(.imageLoadOriginal, isOriginal.value);
|
ref.read(metadataProvider).write(.imageLoadOriginal, isOriginal.value);
|
||||||
});
|
});
|
||||||
@@ -25,6 +29,12 @@ class ImageViewerQualitySetting extends HookConsumerWidget {
|
|||||||
icon: Icons.image_outlined,
|
icon: Icons.image_outlined,
|
||||||
subtitle: "setting_image_viewer_help".t(context: context),
|
subtitle: "setting_image_viewer_help".t(context: context),
|
||||||
),
|
),
|
||||||
|
SettingsSwitchListTile(
|
||||||
|
valueNotifier: isPreview,
|
||||||
|
title: "setting_image_viewer_preview_title".t(context: context),
|
||||||
|
subtitle: "setting_image_viewer_preview_subtitle".t(context: context),
|
||||||
|
onChanged: (_) => ref.invalidate(appSettingsServiceProvider),
|
||||||
|
),
|
||||||
SettingsSwitchListTile(
|
SettingsSwitchListTile(
|
||||||
valueNotifier: isOriginal,
|
valueNotifier: isOriginal,
|
||||||
title: "setting_image_viewer_original_title".t(context: context),
|
title: "setting_image_viewer_original_title".t(context: context),
|
||||||
|
|||||||
Reference in New Issue
Block a user