From 92178ca4092a9450904d166418161838caf5e25a Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Tue, 29 Jun 2021 11:32:54 +0900 Subject: [PATCH] SVG migration: thumbnails --- android/app/build.gradle | 1 + .../calls/fetchers/ThumbnailFetcher.kt | 6 +- .../thibault/aves/decoder/SvgGlideModule.kt | 80 +++++++++++++++ .../deckers/thibault/aves/utils/MimeTypes.kt | 2 +- lib/services/image_file_service.dart | 4 - lib/utils/constants.dart | 6 ++ .../collection/thumbnail/decorated.dart | 21 ++-- .../thumbnail/{raster.dart => image.dart} | 99 +++++++++++++------ lib/widgets/collection/thumbnail/vector.dart | 75 -------------- lib/widgets/dialogs/add_shortcut_dialog.dart | 16 +-- .../common/decorated_filter_chip.dart | 16 +-- lib/widgets/viewer/info/maps/marker.dart | 16 +-- .../viewer/visual/entry_page_view.dart | 4 +- 13 files changed, 186 insertions(+), 160 deletions(-) create mode 100644 android/app/src/main/kotlin/deckers/thibault/aves/decoder/SvgGlideModule.kt rename lib/widgets/collection/thumbnail/{raster.dart => image.dart} (64%) delete mode 100644 lib/widgets/collection/thumbnail/vector.dart diff --git a/android/app/build.gradle b/android/app/build.gradle index 16db454f8..921eef776 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -108,6 +108,7 @@ dependencies { implementation 'androidx.core:core-ktx:1.5.0' implementation 'androidx.exifinterface:exifinterface:1.3.2' implementation 'androidx.multidex:multidex:2.0.1' + implementation 'com.caverock:androidsvg-aar:1.4' implementation 'com.commonsware.cwac:document:0.4.1' implementation 'com.drewnoakes:metadata-extractor:2.16.0' implementation 'com.github.deckerst:Android-TiffBitmapFactory:876e53870a' // forked, built by JitPack diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/ThumbnailFetcher.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/ThumbnailFetcher.kt index 2696f59b7..c10ba3e55 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/ThumbnailFetcher.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/ThumbnailFetcher.kt @@ -13,11 +13,13 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.signature.ObjectKey import deckers.thibault.aves.decoder.MultiTrackImage +import deckers.thibault.aves.decoder.SvgThumbnail import deckers.thibault.aves.decoder.TiffImage import deckers.thibault.aves.decoder.VideoThumbnail import deckers.thibault.aves.utils.BitmapUtils.applyExifOrientation import deckers.thibault.aves.utils.BitmapUtils.getBytes import deckers.thibault.aves.utils.MimeTypes +import deckers.thibault.aves.utils.MimeTypes.SVG import deckers.thibault.aves.utils.MimeTypes.isHeic import deckers.thibault.aves.utils.MimeTypes.isVideo import deckers.thibault.aves.utils.MimeTypes.needRotationAfterContentResolverThumbnail @@ -41,9 +43,10 @@ class ThumbnailFetcher internal constructor( private val uri: Uri = Uri.parse(uri) private val width: Int = if (width?.takeIf { it > 0 } != null) width else defaultSize private val height: Int = if (height?.takeIf { it > 0 } != null) height else defaultSize + private val svgFetch = mimeType == SVG private val tiffFetch = mimeType == MimeTypes.TIFF private val multiTrackFetch = isHeic(mimeType) && pageId != null - private val customFetch = tiffFetch || multiTrackFetch + private val customFetch = svgFetch || tiffFetch || multiTrackFetch suspend fun fetch() { var bitmap: Bitmap? = null @@ -124,6 +127,7 @@ class ThumbnailFetcher internal constructor( .submit(width, height) } else { val model: Any = when { + svgFetch -> SvgThumbnail(context, uri) tiffFetch -> TiffImage(context, uri, pageId) multiTrackFetch -> MultiTrackImage(context, uri, pageId) else -> uri diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/decoder/SvgGlideModule.kt b/android/app/src/main/kotlin/deckers/thibault/aves/decoder/SvgGlideModule.kt new file mode 100644 index 000000000..550085f47 --- /dev/null +++ b/android/app/src/main/kotlin/deckers/thibault/aves/decoder/SvgGlideModule.kt @@ -0,0 +1,80 @@ +package deckers.thibault.aves.decoder + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Canvas +import android.net.Uri +import com.bumptech.glide.Glide +import com.bumptech.glide.Priority +import com.bumptech.glide.Registry +import com.bumptech.glide.annotation.GlideModule +import com.bumptech.glide.load.DataSource +import com.bumptech.glide.load.Options +import com.bumptech.glide.load.data.DataFetcher +import com.bumptech.glide.load.model.ModelLoader +import com.bumptech.glide.load.model.ModelLoaderFactory +import com.bumptech.glide.load.model.MultiModelLoaderFactory +import com.bumptech.glide.module.LibraryGlideModule +import com.bumptech.glide.signature.ObjectKey +import com.caverock.androidsvg.SVG +import com.caverock.androidsvg.SVGParseException +import deckers.thibault.aves.utils.StorageUtils +import kotlin.math.ceil + +@GlideModule +class SvgGlideModule : LibraryGlideModule() { + override fun registerComponents(context: Context, glide: Glide, registry: Registry) { + registry.append(SvgThumbnail::class.java, Bitmap::class.java, SvgLoader.Factory()) + } +} + +class SvgThumbnail(val context: Context, val uri: Uri) + +internal class SvgLoader : ModelLoader { + override fun buildLoadData(model: SvgThumbnail, width: Int, height: Int, options: Options): ModelLoader.LoadData { + return ModelLoader.LoadData(ObjectKey(model.uri), SvgFetcher(model, width, height)) + } + + override fun handles(model: SvgThumbnail): Boolean = true + + internal class Factory : ModelLoaderFactory { + override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader = SvgLoader() + + override fun teardown() {} + } +} + +internal class SvgFetcher(val model: SvgThumbnail, val width: Int, val height: Int) : DataFetcher { + override fun loadData(priority: Priority, callback: DataFetcher.DataCallback) { + val context = model.context + val uri = model.uri + + StorageUtils.openInputStream(context, uri)?.use { input -> + try { + SVG.getFromInputStream(input)?.let { svg -> + val svgWidth = svg.documentWidth + val svgHeight = svg.documentHeight + + val bitmapWidth = if (svgWidth > 0) ceil(svgWidth).toInt() else width + val bitmapHeight = if (svgHeight > 0) ceil(svgHeight).toInt() else height + val bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888) + + val canvas = Canvas(bitmap) + svg.renderToCanvas(canvas) + callback.onDataReady(bitmap) + } + } catch (ex: SVGParseException) { + callback.onLoadFailed(ex) + } + } + } + + override fun cleanup() {} + + // cannot cancel + override fun cancel() {} + + override fun getDataClass(): Class = Bitmap::class.java + + override fun getDataSource(): DataSource = DataSource.LOCAL +} diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/utils/MimeTypes.kt b/android/app/src/main/kotlin/deckers/thibault/aves/utils/MimeTypes.kt index 5deae04ed..64b605da8 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/utils/MimeTypes.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/utils/MimeTypes.kt @@ -59,7 +59,7 @@ object MimeTypes { // returns whether the specified MIME type represents // a raster image format that allows an alpha channel fun canHaveAlpha(mimeType: String?) = when (mimeType) { - BMP, GIF, ICO, PNG, TIFF, WEBP -> true + BMP, GIF, ICO, PNG, SVG, TIFF, WEBP -> true else -> false } diff --git a/lib/services/image_file_service.dart b/lib/services/image_file_service.dart index 6cc8eb19f..8b1f24362 100644 --- a/lib/services/image_file_service.dart +++ b/lib/services/image_file_service.dart @@ -4,7 +4,6 @@ import 'dart:math'; import 'dart:typed_data'; import 'package:aves/model/entry.dart'; -import 'package:aves/ref/mime_types.dart'; import 'package:aves/services/image_op_events.dart'; import 'package:aves/services/service_policy.dart'; import 'package:flutter/foundation.dart'; @@ -245,9 +244,6 @@ class PlatformImageFileService implements ImageFileService { Object? taskKey, int? priority, }) { - if (mimeType == MimeTypes.svg) { - return Future.sync(() => Uint8List(0)); - } return servicePolicy.call( () async { try { diff --git a/lib/utils/constants.dart b/lib/utils/constants.dart index 95aabe5d0..a0b131b4e 100644 --- a/lib/utils/constants.dart +++ b/lib/utils/constants.dart @@ -41,6 +41,12 @@ class Constants { licenseUrl: 'https://android.googlesource.com/platform/frameworks/support/+/androidx-main/LICENSE.txt', sourceUrl: 'https://android.googlesource.com/platform/frameworks/support/+/androidx-main/exifinterface/exifinterface', ), + Dependency( + name: 'AndroidSVG', + license: 'Apache 2.0', + licenseUrl: 'https://github.com/BigBadaboom/androidsvg/blob/master/LICENSE', + sourceUrl: 'https://github.com/BigBadaboom/androidsvg', + ), Dependency( name: 'Android-TiffBitmapFactory (Aves fork)', license: 'MIT', diff --git a/lib/widgets/collection/thumbnail/decorated.dart b/lib/widgets/collection/thumbnail/decorated.dart index 442a067b7..1bccf98ab 100644 --- a/lib/widgets/collection/thumbnail/decorated.dart +++ b/lib/widgets/collection/thumbnail/decorated.dart @@ -1,8 +1,7 @@ import 'package:aves/model/entry.dart'; import 'package:aves/model/source/collection_lens.dart'; +import 'package:aves/widgets/collection/thumbnail/image.dart'; import 'package:aves/widgets/collection/thumbnail/overlay.dart'; -import 'package:aves/widgets/collection/thumbnail/raster.dart'; -import 'package:aves/widgets/collection/thumbnail/vector.dart'; import 'package:aves/widgets/common/fx/borders.dart'; import 'package:flutter/material.dart'; @@ -35,18 +34,12 @@ class DecoratedThumbnail extends StatelessWidget { // but not between different collection instances, even with the same attributes (e.g. reloading collection page via drawer) final heroTag = hashValues(collection?.id, entry); final isSvg = entry.isSvg; - var child = isSvg - ? VectorImageThumbnail( - entry: entry, - extent: imageExtent, - heroTag: heroTag, - ) - : RasterImageThumbnail( - entry: entry, - extent: imageExtent, - cancellableNotifier: cancellableNotifier, - heroTag: heroTag, - ); + Widget child = ThumbnailImage( + entry: entry, + extent: imageExtent, + cancellableNotifier: cancellableNotifier, + heroTag: heroTag, + ); child = Stack( alignment: isSvg ? Alignment.center : AlignmentDirectional.bottomStart, diff --git a/lib/widgets/collection/thumbnail/raster.dart b/lib/widgets/collection/thumbnail/image.dart similarity index 64% rename from lib/widgets/collection/thumbnail/raster.dart rename to lib/widgets/collection/thumbnail/image.dart index 779e2f57d..e0da142a4 100644 --- a/lib/widgets/collection/thumbnail/raster.dart +++ b/lib/widgets/collection/thumbnail/image.dart @@ -4,41 +4,46 @@ import 'dart:ui'; import 'package:aves/image_providers/thumbnail_provider.dart'; import 'package:aves/model/entry.dart'; import 'package:aves/model/entry_images.dart'; +import 'package:aves/model/settings/entry_background.dart'; +import 'package:aves/model/settings/enums.dart'; +import 'package:aves/model/settings/settings.dart'; import 'package:aves/services/services.dart'; import 'package:aves/widgets/collection/thumbnail/error.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/common/fx/checkered_decoration.dart'; import 'package:aves/widgets/common/fx/transition_image.dart'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; -class RasterImageThumbnail extends StatefulWidget { +class ThumbnailImage extends StatefulWidget { final AvesEntry entry; final double extent; - final BoxFit fit; + final BoxFit? fit; final bool showLoadingBackground; final ValueNotifier? cancellableNotifier; final Object? heroTag; - const RasterImageThumbnail({ + const ThumbnailImage({ Key? key, required this.entry, required this.extent, - this.fit = BoxFit.cover, + this.fit, this.showLoadingBackground = true, this.cancellableNotifier, this.heroTag, }) : super(key: key); @override - _RasterImageThumbnailState createState() => _RasterImageThumbnailState(); + _ThumbnailImageState createState() => _ThumbnailImageState(); } -class _RasterImageThumbnailState extends State { +class _ThumbnailImageState extends State { final _providers = <_ConditionalImageProvider>[]; _ProviderStream? _currentProviderStream; ImageInfo? _lastImageInfo; Object? _lastException; late final ImageStreamListener _streamListener; - late DisposableBuildContext> _scrollAwareContext; + late DisposableBuildContext> _scrollAwareContext; AvesEntry get entry => widget.entry; @@ -48,12 +53,12 @@ class _RasterImageThumbnailState extends State { void initState() { super.initState(); _streamListener = ImageStreamListener(_onImageLoad, onError: _onError); - _scrollAwareContext = DisposableBuildContext>(this); + _scrollAwareContext = DisposableBuildContext>(this); _registerWidget(widget); } @override - void didUpdateWidget(covariant RasterImageThumbnail oldWidget) { + void didUpdateWidget(covariant ThumbnailImage oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.entry != entry) { _unregisterWidget(oldWidget); @@ -68,12 +73,12 @@ class _RasterImageThumbnailState extends State { super.dispose(); } - void _registerWidget(RasterImageThumbnail widget) { + void _registerWidget(ThumbnailImage widget) { widget.entry.imageChangeNotifier.addListener(_onImageChanged); _initProvider(); } - void _unregisterWidget(RasterImageThumbnail widget) { + void _unregisterWidget(ThumbnailImage widget) { widget.entry.imageChangeNotifier.removeListener(_onImageChanged); _pauseProvider(); _currentProviderStream?.stopListening(); @@ -87,12 +92,13 @@ class _RasterImageThumbnailState extends State { _lastException = null; _providers.clear(); _providers.addAll([ - _ConditionalImageProvider( - ScrollAwareImageProvider( - context: _scrollAwareContext, - imageProvider: entry.getThumbnail(), + if (!entry.isSvg) + _ConditionalImageProvider( + ScrollAwareImageProvider( + context: _scrollAwareContext, + imageProvider: entry.getThumbnail(), + ), ), - ), _ConditionalImageProvider( ScrollAwareImageProvider( context: _scrollAwareContext, @@ -152,14 +158,14 @@ class _RasterImageThumbnailState extends State { } } - Color? _backgroundColor; + Color? _loadingBackgroundColor; - Color get backgroundColor { - if (_backgroundColor == null) { + Color get loadingBackgroundColor { + if (_loadingBackgroundColor == null) { final rgb = 0x30 + entry.uri.hashCode % 0x20; - _backgroundColor = Color.fromARGB(0xFF, rgb, rgb, rgb); + _loadingBackgroundColor = Color.fromARGB(0xFF, rgb, rgb, rgb); } - return _backgroundColor!; + return _loadingBackgroundColor!; } @override @@ -173,21 +179,54 @@ class _RasterImageThumbnailState extends State { // use `RawImage` instead of `Image`, using `ImageInfo` to check dimensions // and have more control when chaining image providers + final fit = widget.fit ?? (entry.isSvg ? BoxFit.contain : BoxFit.cover); final imageInfo = _lastImageInfo; final image = imageInfo == null ? Container( - color: widget.showLoadingBackground ? backgroundColor : Colors.transparent, + color: widget.showLoadingBackground ? loadingBackgroundColor : Colors.transparent, width: extent, height: extent, ) - : RawImage( - image: imageInfo.image, - debugImageLabel: imageInfo.debugLabel, - width: extent, - height: extent, - scale: imageInfo.scale, - fit: widget.fit, - ); + : Selector( + selector: (context, s) => entry.isSvg ? s.vectorBackground : s.rasterBackground, + builder: (context, background, child) { + final backgroundColor = background.isColor ? background.color : null; + + if (background == EntryBackground.checkered) { + return LayoutBuilder( + builder: (context, constraints) { + final availableSize = constraints.biggest; + final fitSize = applyBoxFit(fit, entry.displaySize, availableSize).destination; + final offset = (fitSize / 2 - availableSize / 2) as Offset; + final child = CustomPaint( + painter: CheckeredPainter(checkSize: extent / 8, offset: offset), + child: RawImage( + image: imageInfo.image, + debugImageLabel: imageInfo.debugLabel, + width: fitSize.width, + height: fitSize.height, + scale: imageInfo.scale, + fit: BoxFit.cover, + ), + ); + // the thumbnail is centered for correct decoration sizing + // when constraints are tight during hero animation + return constraints.isTight ? Center(child: child) : child; + }, + ); + } + + return RawImage( + image: imageInfo.image, + debugImageLabel: imageInfo.debugLabel, + width: extent, + height: extent, + scale: imageInfo.scale, + color: backgroundColor, + colorBlendMode: BlendMode.dstOver, + fit: fit, + ); + }); return widget.heroTag != null ? Hero( diff --git a/lib/widgets/collection/thumbnail/vector.dart b/lib/widgets/collection/thumbnail/vector.dart deleted file mode 100644 index 2c71b3593..000000000 --- a/lib/widgets/collection/thumbnail/vector.dart +++ /dev/null @@ -1,75 +0,0 @@ -import 'package:aves/image_providers/uri_picture_provider.dart'; -import 'package:aves/model/entry.dart'; -import 'package:aves/model/settings/entry_background.dart'; -import 'package:aves/model/settings/enums.dart'; -import 'package:aves/model/settings/settings.dart'; -import 'package:aves/widgets/common/fx/checkered_decoration.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:provider/provider.dart'; - -class VectorImageThumbnail extends StatelessWidget { - final AvesEntry entry; - final double extent; - final Object? heroTag; - - const VectorImageThumbnail({ - Key? key, - required this.entry, - required this.extent, - this.heroTag, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - final child = Selector( - selector: (context, s) => s.vectorBackground, - builder: (context, background, child) { - const fit = BoxFit.contain; - if (background == EntryBackground.checkered) { - return LayoutBuilder( - builder: (context, constraints) { - final availableSize = constraints.biggest; - final fitSize = applyBoxFit(fit, entry.displaySize, availableSize).destination; - final offset = (fitSize / 2 - availableSize / 2) as Offset; - final child = CustomPaint( - painter: CheckeredPainter(checkSize: extent / 8, offset: offset), - child: SvgPicture( - UriPicture( - uri: entry.uri, - mimeType: entry.mimeType, - ), - width: fitSize.width, - height: fitSize.height, - fit: fit, - ), - ); - // the thumbnail is centered for correct decoration sizing - // when constraints are tight during hero animation - return constraints.isTight ? Center(child: child) : child; - }, - ); - } - - final colorFilter = background.isColor ? ColorFilter.mode(background.color, BlendMode.dstOver) : null; - return SvgPicture( - UriPicture( - uri: entry.uri, - mimeType: entry.mimeType, - colorFilter: colorFilter, - ), - width: extent, - height: extent, - fit: fit, - ); - }, - ); - return heroTag != null - ? Hero( - tag: heroTag!, - transitionOnUserGestures: true, - child: child, - ) - : child; - } -} diff --git a/lib/widgets/dialogs/add_shortcut_dialog.dart b/lib/widgets/dialogs/add_shortcut_dialog.dart index 3806617d4..81c4178d9 100644 --- a/lib/widgets/dialogs/add_shortcut_dialog.dart +++ b/lib/widgets/dialogs/add_shortcut_dialog.dart @@ -2,8 +2,7 @@ import 'package:aves/model/covers.dart'; import 'package:aves/model/entry.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/source/collection_lens.dart'; -import 'package:aves/widgets/collection/thumbnail/raster.dart'; -import 'package:aves/widgets/collection/thumbnail/vector.dart'; +import 'package:aves/widgets/collection/thumbnail/image.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; import 'package:aves/widgets/dialogs/item_pick_dialog.dart'; @@ -114,15 +113,10 @@ class _AddShortcutDialogState extends State { child: SizedBox( width: extent, height: extent, - child: entry.isSvg - ? VectorImageThumbnail( - entry: entry, - extent: extent, - ) - : RasterImageThumbnail( - entry: entry, - extent: extent, - ), + child: ThumbnailImage( + entry: entry, + extent: extent, + ), ), ), ); diff --git a/lib/widgets/filter_grids/common/decorated_filter_chip.dart b/lib/widgets/filter_grids/common/decorated_filter_chip.dart index 5ff7ffe83..a2b4f235a 100644 --- a/lib/widgets/filter_grids/common/decorated_filter_chip.dart +++ b/lib/widgets/filter_grids/common/decorated_filter_chip.dart @@ -13,8 +13,7 @@ import 'package:aves/theme/durations.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/utils/constants.dart'; -import 'package:aves/widgets/collection/thumbnail/raster.dart'; -import 'package:aves/widgets/collection/thumbnail/vector.dart'; +import 'package:aves/widgets/collection/thumbnail/image.dart'; import 'package:aves/widgets/common/identity/aves_filter_chip.dart'; import 'package:aves/widgets/filter_grids/common/filter_grid_page.dart'; import 'package:aves/widgets/filter_grids/common/overlay.dart'; @@ -85,15 +84,10 @@ class DecoratedFilterChip extends StatelessWidget { final entry = coverEntry ?? source.coverEntry(filter); final backgroundImage = entry == null ? Container(color: Colors.white) - : entry.isSvg - ? VectorImageThumbnail( - entry: entry, - extent: extent, - ) - : RasterImageThumbnail( - entry: entry, - extent: thumbnailExtent, - ); + : ThumbnailImage( + entry: entry, + extent: thumbnailExtent, + ); final titlePadding = min(4.0, extent / 32); final borderRadius = BorderRadius.all(radius(extent)); Widget child = AvesFilterChip( diff --git a/lib/widgets/viewer/info/maps/marker.dart b/lib/widgets/viewer/info/maps/marker.dart index fe8a8f277..d883d4067 100644 --- a/lib/widgets/viewer/info/maps/marker.dart +++ b/lib/widgets/viewer/info/maps/marker.dart @@ -2,8 +2,7 @@ import 'dart:typed_data'; import 'dart:ui' as ui; import 'package:aves/model/entry.dart'; -import 'package:aves/widgets/collection/thumbnail/raster.dart'; -import 'package:aves/widgets/collection/thumbnail/vector.dart'; +import 'package:aves/widgets/collection/thumbnail/image.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:provider/provider.dart'; @@ -30,15 +29,10 @@ class ImageMarker extends StatelessWidget { @override Widget build(BuildContext context) { - final thumbnail = entry.isSvg - ? VectorImageThumbnail( - entry: entry, - extent: extent, - ) - : RasterImageThumbnail( - entry: entry, - extent: extent, - ); + final thumbnail = ThumbnailImage( + entry: entry, + extent: extent, + ); const outerDecoration = BoxDecoration( border: Border.fromBorderSide(BorderSide( diff --git a/lib/widgets/viewer/visual/entry_page_view.dart b/lib/widgets/viewer/visual/entry_page_view.dart index 51c59ec56..9a50dd384 100644 --- a/lib/widgets/viewer/visual/entry_page_view.dart +++ b/lib/widgets/viewer/visual/entry_page_view.dart @@ -6,7 +6,7 @@ import 'package:aves/model/settings/entry_background.dart'; import 'package:aves/model/settings/enums.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/theme/durations.dart'; -import 'package:aves/widgets/collection/thumbnail/raster.dart'; +import 'package:aves/widgets/collection/thumbnail/image.dart'; import 'package:aves/widgets/common/magnifier/controller/controller.dart'; import 'package:aves/widgets/common/magnifier/controller/state.dart'; import 'package:aves/widgets/common/magnifier/magnifier.dart'; @@ -232,7 +232,7 @@ class _EntryPageViewState extends State { duration: Durations.viewerVideoPlayerTransition, child: GestureDetector( onTap: _onTap, - child: RasterImageThumbnail( + child: ThumbnailImage( entry: entry, extent: context.select((mq) => mq.size.shortestSide), fit: BoxFit.contain,