From 862a8003fad3a4804bd6cf415bd2ef2ccf3c1dc6 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Wed, 5 Jan 2022 13:33:43 +0900 Subject: [PATCH] various fixes --- lib/image_providers/region_provider.dart | 1 + lib/image_providers/thumbnail_provider.dart | 3 ++- lib/image_providers/uri_image_provider.dart | 1 + lib/model/entry.dart | 14 +++++----- lib/model/entry_cache.dart | 19 ++++++++------ lib/ref/mime_types.dart | 29 +++++++++++++++++++-- lib/widgets/viewer/info/info_search.dart | 2 +- 7 files changed, 51 insertions(+), 18 deletions(-) diff --git a/lib/image_providers/region_provider.dart b/lib/image_providers/region_provider.dart index e583467f7..97faccc96 100644 --- a/lib/image_providers/region_provider.dart +++ b/lib/image_providers/region_provider.dart @@ -49,6 +49,7 @@ class RegionProvider extends ImageProvider { } return await decode(bytes); } catch (error) { + // loading may fail if the provided MIME type is incorrect (e.g. the Media Store may report a JPEG as a TIFF) debugPrint('$runtimeType _loadAsync failed with mimeType=$mimeType, uri=$uri, error=$error'); throw StateError('$mimeType region decoding failed (page $pageId)'); } diff --git a/lib/image_providers/thumbnail_provider.dart b/lib/image_providers/thumbnail_provider.dart index 57fe937bd..3c5b5699c 100644 --- a/lib/image_providers/thumbnail_provider.dart +++ b/lib/image_providers/thumbnail_provider.dart @@ -50,7 +50,8 @@ class ThumbnailProvider extends ImageProvider { } return await decode(bytes); } catch (error) { - debugPrint('$runtimeType _loadAsync failed with uri=$uri, error=$error'); + // loading may fail if the provided MIME type is incorrect (e.g. the Media Store may report a JPEG as a TIFF) + debugPrint('$runtimeType _loadAsync failed with mimeType=$mimeType, uri=$uri, error=$error'); throw StateError('$mimeType decoding failed (page $pageId)'); } } diff --git a/lib/image_providers/uri_image_provider.dart b/lib/image_providers/uri_image_provider.dart index 416d407f1..1aeca959e 100644 --- a/lib/image_providers/uri_image_provider.dart +++ b/lib/image_providers/uri_image_provider.dart @@ -68,6 +68,7 @@ class UriImage extends ImageProvider with EquatableMixin { } return await decode(bytes); } catch (error) { + // loading may fail if the provided MIME type is incorrect (e.g. the Media Store may report a JPEG as a TIFF) debugPrint('$runtimeType _loadAsync failed with mimeType=$mimeType, uri=$uri, error=$error'); throw StateError('$mimeType decoding failed (page $pageId)'); } finally { diff --git a/lib/model/entry.dart b/lib/model/entry.dart index 2e071117e..812d3fad0 100644 --- a/lib/model/entry.dart +++ b/lib/model/entry.dart @@ -449,6 +449,7 @@ class AvesEntry { CatalogMetadata? get catalogMetadata => _catalogMetadata; set catalogMetadata(CatalogMetadata? newMetadata) { + final oldMimeType = mimeType; final oldDateModifiedSecs = dateModifiedSecs; final oldRotationDegrees = rotationDegrees; final oldIsFlipped = isFlipped; @@ -459,7 +460,7 @@ class AvesEntry { _tags = null; metadataChangeNotifier.notify(); - _onVisualFieldChanged(oldDateModifiedSecs, oldRotationDegrees, oldIsFlipped); + _onVisualFieldChanged(oldMimeType, oldDateModifiedSecs, oldRotationDegrees, oldIsFlipped); } void clearMetadata() { @@ -583,6 +584,7 @@ class AvesEntry { } Future applyNewFields(Map newFields, {required bool persist}) async { + final oldMimeType = mimeType; final oldDateModifiedSecs = this.dateModifiedSecs; final oldRotationDegrees = this.rotationDegrees; final oldIsFlipped = this.isFlipped; @@ -622,7 +624,7 @@ class AvesEntry { if (catalogMetadata != null) await metadataDb.saveMetadata({catalogMetadata!}); } - await _onVisualFieldChanged(oldDateModifiedSecs, oldRotationDegrees, oldIsFlipped); + await _onVisualFieldChanged(oldMimeType, oldDateModifiedSecs, oldRotationDegrees, oldIsFlipped); metadataChangeNotifier.notify(); } @@ -663,10 +665,10 @@ class AvesEntry { return completer.future; } - // when the entry image itself changed (e.g. after rotation) - Future _onVisualFieldChanged(int? oldDateModifiedSecs, int oldRotationDegrees, bool oldIsFlipped) async { - if (oldDateModifiedSecs != dateModifiedSecs || oldRotationDegrees != rotationDegrees || oldIsFlipped != isFlipped) { - await EntryCache.evict(uri, mimeType, oldDateModifiedSecs, oldRotationDegrees, oldIsFlipped); + // when the MIME type or the image itself changed (e.g. after rotation) + Future _onVisualFieldChanged(String oldMimeType, int? oldDateModifiedSecs, int oldRotationDegrees, bool oldIsFlipped) async { + if ((!MimeTypes.refersToSameType(oldMimeType, mimeType) && !MimeTypes.isVideo(oldMimeType)) || oldDateModifiedSecs != dateModifiedSecs || oldRotationDegrees != rotationDegrees || oldIsFlipped != isFlipped) { + await EntryCache.evict(uri, oldMimeType, oldDateModifiedSecs, oldRotationDegrees, oldIsFlipped); imageChangeNotifier.notify(); } } diff --git a/lib/model/entry_cache.dart b/lib/model/entry_cache.dart index a70fd6d63..c0ff892a6 100644 --- a/lib/model/entry_cache.dart +++ b/lib/model/entry_cache.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:aves/image_providers/thumbnail_provider.dart'; import 'package:aves/image_providers/uri_image_provider.dart'; +import 'package:flutter/foundation.dart'; class EntryCache { // ordered descending @@ -19,9 +20,11 @@ class EntryCache { String uri, String mimeType, int? dateModifiedSecs, - int oldRotationDegrees, - bool oldIsFlipped, + int rotationDegrees, + bool isFlipped, ) async { + debugPrint('Evict cached images for uri=$uri, mimeType=$mimeType, dateModifiedSecs=$dateModifiedSecs, rotationDegrees=$rotationDegrees, isFlipped=$isFlipped'); + // TODO TLAD provide pageId parameter for multi page items, if someday image editing features are added for them int? pageId; @@ -30,8 +33,8 @@ class EntryCache { uri: uri, mimeType: mimeType, pageId: pageId, - rotationDegrees: oldRotationDegrees, - isFlipped: oldIsFlipped, + rotationDegrees: rotationDegrees, + isFlipped: isFlipped, ).evict(); // evict low quality thumbnail (without specified extents) @@ -40,8 +43,8 @@ class EntryCache { mimeType: mimeType, pageId: pageId, dateModifiedSecs: dateModifiedSecs ?? 0, - rotationDegrees: oldRotationDegrees, - isFlipped: oldIsFlipped, + rotationDegrees: rotationDegrees, + isFlipped: isFlipped, )).evict(); await Future.forEach( @@ -51,8 +54,8 @@ class EntryCache { mimeType: mimeType, pageId: pageId, dateModifiedSecs: dateModifiedSecs ?? 0, - rotationDegrees: oldRotationDegrees, - isFlipped: oldIsFlipped, + rotationDegrees: rotationDegrees, + isFlipped: isFlipped, extent: extent, )).evict()); } diff --git a/lib/ref/mime_types.dart b/lib/ref/mime_types.dart index 99f98642f..2b43ebf5e 100644 --- a/lib/ref/mime_types.dart +++ b/lib/ref/mime_types.dart @@ -2,6 +2,7 @@ class MimeTypes { static const anyImage = 'image/*'; static const bmp = 'image/bmp'; + static const bmpX = 'image/x-ms-bmp'; static const gif = 'image/gif'; static const heic = 'image/heic'; static const heif = 'image/heif'; @@ -43,6 +44,8 @@ class MimeTypes { static const avi = 'video/avi'; static const aviVnd = 'video/vnd.avi'; + static const flv = 'video/flv'; + static const flvX = 'video/x-flv'; static const mkv = 'video/x-matroska'; static const mov = 'video/quicktime'; static const mp2t = 'video/mp2t'; // .m2ts, .ts @@ -62,7 +65,7 @@ class MimeTypes { // groups // formats that support transparency - static const Set alphaImages = {bmp, gif, ico, png, svg, tiff, webp}; + static const Set alphaImages = {bmp, bmpX, gif, ico, png, svg, tiff, webp}; static const Set rawImages = {arw, cr2, crw, dcr, dng, erf, k25, kdc, mrw, nef, nrw, orf, pef, raf, raw, rw2, sr2, srf, srw, x3f}; @@ -71,11 +74,33 @@ class MimeTypes { static const Set _knownOpaqueImages = {heic, heif, jpeg}; - static const Set _knownVideos = {avi, aviVnd, mkv, mov, mp2t, mp2ts, mp4, mpeg, ogv, webm}; + static const Set _knownVideos = {avi, aviVnd, flv, flvX, mkv, mov, mp2t, mp2ts, mp4, mpeg, ogv, webm}; static final Set knownMediaTypes = {..._knownOpaqueImages, ...alphaImages, ...rawImages, ...undecodableImages, ..._knownVideos}; static bool isImage(String mimeType) => mimeType.startsWith('image'); static bool isVideo(String mimeType) => mimeType.startsWith('video'); + + static bool refersToSameType(String a, b) { + switch (a) { + case avi: + case aviVnd: + return [avi, aviVnd].contains(b); + case bmp: + case bmpX: + return [bmp, bmpX].contains(b); + case flv: + case flvX: + return [flv, flvX].contains(b); + case heic: + case heif: + return [heic, heif].contains(b); + case psdVnd: + case psdX: + return [psdVnd, psdX].contains(b); + default: + return a == b; + } + } } diff --git a/lib/widgets/viewer/info/info_search.dart b/lib/widgets/viewer/info/info_search.dart index 8f0bd4c2b..6dbb9200a 100644 --- a/lib/widgets/viewer/info/info_search.dart +++ b/lib/widgets/viewer/info/info_search.dart @@ -55,7 +55,7 @@ class InfoSearchDelegate extends SearchDelegate { final l10n = context.l10n; final suggestions = { l10n.viewerInfoSearchSuggestionDate: 'date or time or when -timer -uptime -exposure -timeline -verbatim', - l10n.viewerInfoSearchSuggestionDescription: 'abstract or description or comment or textual or title', + l10n.viewerInfoSearchSuggestionDescription: 'abstract or description or comment or textual or title -line', l10n.viewerInfoSearchSuggestionDimensions: 'width or height or dimension or framesize or imagelength', l10n.viewerInfoSearchSuggestionResolution: 'resolution', l10n.viewerInfoSearchSuggestionRights: 'rights or copyright or attribution or license or artist or creator or by-line or credit -tool',