From 87dc1768ddbb2e29368f725ca8bdb650acddb5a2 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Wed, 21 Oct 2020 12:16:27 +0900 Subject: [PATCH] avoid loading images of unsupported types --- lib/model/image_entry.dart | 2 ++ lib/model/mime_types.dart | 4 +++ lib/widgets/collection/thumbnail/error.dart | 24 ++++++++++++++++++ lib/widgets/collection/thumbnail/raster.dart | 26 +++++++++++--------- lib/widgets/fullscreen/image_view.dart | 24 +++++++++++++----- 5 files changed, 63 insertions(+), 17 deletions(-) create mode 100644 lib/widgets/collection/thumbnail/error.dart diff --git a/lib/model/image_entry.dart b/lib/model/image_entry.dart index 0315ea393..b9573346d 100644 --- a/lib/model/image_entry.dart +++ b/lib/model/image_entry.dart @@ -56,6 +56,8 @@ class ImageEntry { this.dateModifiedSecs = dateModifiedSecs; } + bool get canDecode => !MimeTypes.undecodable.contains(mimeType); + ImageEntry copyWith({ @required String uri, @required String path, diff --git a/lib/model/mime_types.dart b/lib/model/mime_types.dart index dbe344c59..9d6483da2 100644 --- a/lib/model/mime_types.dart +++ b/lib/model/mime_types.dart @@ -9,6 +9,9 @@ class MimeTypes { static const String svg = 'image/svg+xml'; static const String webp = 'image/webp'; + static const String tiff = 'image/tiff'; + static const String psd = 'image/vnd.adobe.photoshop'; + static const String arw = 'image/x-sony-arw'; static const String cr2 = 'image/x-canon-cr2'; static const String crw = 'image/x-canon-crw'; @@ -38,4 +41,5 @@ class MimeTypes { // groups static const List rawImages = [arw, cr2, crw, dcr, dng, erf, k25, kdc, mrw, nef, nrw, orf, pef, raf, raw, rw2, sr2, srf, srw, x3f]; + static const List undecodable = [crw, psd, tiff]; // TODO TLAD make it dynamic if it depends on OS/lib versions } diff --git a/lib/widgets/collection/thumbnail/error.dart b/lib/widgets/collection/thumbnail/error.dart new file mode 100644 index 000000000..ae0d2bdf5 --- /dev/null +++ b/lib/widgets/collection/thumbnail/error.dart @@ -0,0 +1,24 @@ +import 'package:aves/widgets/common/icons.dart'; +import 'package:flutter/material.dart'; + +class ErrorThumbnail extends StatelessWidget { + final double extent; + final String tooltip; + + const ErrorThumbnail({@required this.extent, @required this.tooltip}); + + @override + Widget build(BuildContext context) { + return Center( + child: Tooltip( + message: tooltip, + preferBelow: false, + child: Icon( + AIcons.error, + size: extent / 2, + color: Colors.blueGrey, + ), + ), + ); + } +} diff --git a/lib/widgets/collection/thumbnail/raster.dart b/lib/widgets/collection/thumbnail/raster.dart index e9749dd9b..0cc0b9e35 100644 --- a/lib/widgets/collection/thumbnail/raster.dart +++ b/lib/widgets/collection/thumbnail/raster.dart @@ -2,7 +2,7 @@ import 'dart:math'; import 'package:aves/model/image_entry.dart'; import 'package:aves/utils/durations.dart'; -import 'package:aves/widgets/common/icons.dart'; +import 'package:aves/widgets/collection/thumbnail/error.dart'; import 'package:aves/widgets/common/image_providers/thumbnail_provider.dart'; import 'package:aves/widgets/common/image_providers/uri_image_provider.dart'; import 'package:aves/widgets/common/transition_image.dart'; @@ -71,7 +71,11 @@ class _ThumbnailRasterImageState extends State { _pauseProvider(); } + bool get isSupported => entry.canDecode; + void _initProvider() { + if (!entry.canDecode) return; + _fastThumbnailProvider = ThumbnailProvider( ThumbnailProviderKey.fromEntry(entry), ); @@ -95,6 +99,13 @@ class _ThumbnailRasterImageState extends State { @override Widget build(BuildContext context) { + if (!entry.canDecode) { + return ErrorThumbnail( + extent: extent, + tooltip: '${entry.mimeType} not supported', + ); + } + final fastImage = Image( key: ValueKey('LQ'), image: _fastThumbnailProvider, @@ -127,16 +138,9 @@ class _ThumbnailRasterImageState extends State { child: frame == null ? fastImage : child, ); }, - errorBuilder: (context, error, stackTrace) => Center( - child: Tooltip( - message: error.toString(), - preferBelow: false, - child: Icon( - AIcons.error, - size: extent / 2, - color: Colors.blueGrey, - ), - ), + errorBuilder: (context, error, stackTrace) => ErrorThumbnail( + extent: extent, + tooltip: error.toString(), ), width: extent, height: extent, diff --git a/lib/widgets/fullscreen/image_view.dart b/lib/widgets/fullscreen/image_view.dart index e92c2b61a..8d62ffc16 100644 --- a/lib/widgets/fullscreen/image_view.dart +++ b/lib/widgets/fullscreen/image_view.dart @@ -93,7 +93,7 @@ class ImageView extends StatelessWidget { initialScale: PhotoViewComputedScale.contained, onTapUp: (tapContext, details, value) => onTap?.call(), ); - } else { + } else if (entry.canDecode) { final uriImage = UriImage( uri: entry.uri, mimeType: entry.mimeType, @@ -111,11 +111,7 @@ class ImageView extends StatelessWidget { context, imageCache.statusForKey(uriImage).keepAlive ? uriImage : fastThumbnailProvider, ), - loadFailedChild: EmptyContent( - icon: AIcons.error, - text: 'Oops!', - alignment: Alignment.center, - ), + loadFailedChild: _buildError(), backgroundDecoration: backgroundDecoration, scaleStateChangedCallback: onScaleChanged, minScale: PhotoViewComputedScale.contained, @@ -123,6 +119,8 @@ class ImageView extends StatelessWidget { onTapUp: (tapContext, details, value) => onTap?.call(), filterQuality: FilterQuality.low, ); + } else { + child = _buildError(); } return heroTag != null @@ -133,4 +131,18 @@ class ImageView extends StatelessWidget { ) : child; } + + Widget _buildError() => GestureDetector( + onTap: () => onTap?.call(), + // use a `Container` with a dummy color to make it expand + // so that we can also detect taps around the title `Text` + child: Container( + color: Colors.transparent, + child: EmptyContent( + icon: AIcons.error, + text: 'Oops!', + alignment: Alignment.center, + ), + ), + ); }