SVG migration: thumbnails

This commit is contained in:
Thibault Deckers 2021-06-29 11:32:54 +09:00
parent e76376be91
commit 92178ca409
13 changed files with 186 additions and 160 deletions

View file

@ -108,6 +108,7 @@ dependencies {
implementation 'androidx.core:core-ktx:1.5.0' implementation 'androidx.core:core-ktx:1.5.0'
implementation 'androidx.exifinterface:exifinterface:1.3.2' implementation 'androidx.exifinterface:exifinterface:1.3.2'
implementation 'androidx.multidex:multidex:2.0.1' implementation 'androidx.multidex:multidex:2.0.1'
implementation 'com.caverock:androidsvg-aar:1.4'
implementation 'com.commonsware.cwac:document:0.4.1' implementation 'com.commonsware.cwac:document:0.4.1'
implementation 'com.drewnoakes:metadata-extractor:2.16.0' implementation 'com.drewnoakes:metadata-extractor:2.16.0'
implementation 'com.github.deckerst:Android-TiffBitmapFactory:876e53870a' // forked, built by JitPack implementation 'com.github.deckerst:Android-TiffBitmapFactory:876e53870a' // forked, built by JitPack

View file

@ -13,11 +13,13 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.signature.ObjectKey import com.bumptech.glide.signature.ObjectKey
import deckers.thibault.aves.decoder.MultiTrackImage import deckers.thibault.aves.decoder.MultiTrackImage
import deckers.thibault.aves.decoder.SvgThumbnail
import deckers.thibault.aves.decoder.TiffImage import deckers.thibault.aves.decoder.TiffImage
import deckers.thibault.aves.decoder.VideoThumbnail import deckers.thibault.aves.decoder.VideoThumbnail
import deckers.thibault.aves.utils.BitmapUtils.applyExifOrientation import deckers.thibault.aves.utils.BitmapUtils.applyExifOrientation
import deckers.thibault.aves.utils.BitmapUtils.getBytes import deckers.thibault.aves.utils.BitmapUtils.getBytes
import deckers.thibault.aves.utils.MimeTypes 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.isHeic
import deckers.thibault.aves.utils.MimeTypes.isVideo import deckers.thibault.aves.utils.MimeTypes.isVideo
import deckers.thibault.aves.utils.MimeTypes.needRotationAfterContentResolverThumbnail import deckers.thibault.aves.utils.MimeTypes.needRotationAfterContentResolverThumbnail
@ -41,9 +43,10 @@ class ThumbnailFetcher internal constructor(
private val uri: Uri = Uri.parse(uri) private val uri: Uri = Uri.parse(uri)
private val width: Int = if (width?.takeIf { it > 0 } != null) width else defaultSize 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 height: Int = if (height?.takeIf { it > 0 } != null) height else defaultSize
private val svgFetch = mimeType == SVG
private val tiffFetch = mimeType == MimeTypes.TIFF private val tiffFetch = mimeType == MimeTypes.TIFF
private val multiTrackFetch = isHeic(mimeType) && pageId != null private val multiTrackFetch = isHeic(mimeType) && pageId != null
private val customFetch = tiffFetch || multiTrackFetch private val customFetch = svgFetch || tiffFetch || multiTrackFetch
suspend fun fetch() { suspend fun fetch() {
var bitmap: Bitmap? = null var bitmap: Bitmap? = null
@ -124,6 +127,7 @@ class ThumbnailFetcher internal constructor(
.submit(width, height) .submit(width, height)
} else { } else {
val model: Any = when { val model: Any = when {
svgFetch -> SvgThumbnail(context, uri)
tiffFetch -> TiffImage(context, uri, pageId) tiffFetch -> TiffImage(context, uri, pageId)
multiTrackFetch -> MultiTrackImage(context, uri, pageId) multiTrackFetch -> MultiTrackImage(context, uri, pageId)
else -> uri else -> uri

View file

@ -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<SvgThumbnail, Bitmap> {
override fun buildLoadData(model: SvgThumbnail, width: Int, height: Int, options: Options): ModelLoader.LoadData<Bitmap> {
return ModelLoader.LoadData(ObjectKey(model.uri), SvgFetcher(model, width, height))
}
override fun handles(model: SvgThumbnail): Boolean = true
internal class Factory : ModelLoaderFactory<SvgThumbnail, Bitmap> {
override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<SvgThumbnail, Bitmap> = SvgLoader()
override fun teardown() {}
}
}
internal class SvgFetcher(val model: SvgThumbnail, val width: Int, val height: Int) : DataFetcher<Bitmap> {
override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in Bitmap>) {
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> = Bitmap::class.java
override fun getDataSource(): DataSource = DataSource.LOCAL
}

View file

@ -59,7 +59,7 @@ object MimeTypes {
// returns whether the specified MIME type represents // returns whether the specified MIME type represents
// a raster image format that allows an alpha channel // a raster image format that allows an alpha channel
fun canHaveAlpha(mimeType: String?) = when (mimeType) { fun canHaveAlpha(mimeType: String?) = when (mimeType) {
BMP, GIF, ICO, PNG, TIFF, WEBP -> true BMP, GIF, ICO, PNG, SVG, TIFF, WEBP -> true
else -> false else -> false
} }

View file

@ -4,7 +4,6 @@ import 'dart:math';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:aves/model/entry.dart'; 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/image_op_events.dart';
import 'package:aves/services/service_policy.dart'; import 'package:aves/services/service_policy.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@ -245,9 +244,6 @@ class PlatformImageFileService implements ImageFileService {
Object? taskKey, Object? taskKey,
int? priority, int? priority,
}) { }) {
if (mimeType == MimeTypes.svg) {
return Future.sync(() => Uint8List(0));
}
return servicePolicy.call( return servicePolicy.call(
() async { () async {
try { try {

View file

@ -41,6 +41,12 @@ class Constants {
licenseUrl: 'https://android.googlesource.com/platform/frameworks/support/+/androidx-main/LICENSE.txt', licenseUrl: 'https://android.googlesource.com/platform/frameworks/support/+/androidx-main/LICENSE.txt',
sourceUrl: 'https://android.googlesource.com/platform/frameworks/support/+/androidx-main/exifinterface/exifinterface', 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( Dependency(
name: 'Android-TiffBitmapFactory (Aves fork)', name: 'Android-TiffBitmapFactory (Aves fork)',
license: 'MIT', license: 'MIT',

View file

@ -1,8 +1,7 @@
import 'package:aves/model/entry.dart'; import 'package:aves/model/entry.dart';
import 'package:aves/model/source/collection_lens.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/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:aves/widgets/common/fx/borders.dart';
import 'package:flutter/material.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) // 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 heroTag = hashValues(collection?.id, entry);
final isSvg = entry.isSvg; final isSvg = entry.isSvg;
var child = isSvg Widget child = ThumbnailImage(
? VectorImageThumbnail( entry: entry,
entry: entry, extent: imageExtent,
extent: imageExtent, cancellableNotifier: cancellableNotifier,
heroTag: heroTag, heroTag: heroTag,
) );
: RasterImageThumbnail(
entry: entry,
extent: imageExtent,
cancellableNotifier: cancellableNotifier,
heroTag: heroTag,
);
child = Stack( child = Stack(
alignment: isSvg ? Alignment.center : AlignmentDirectional.bottomStart, alignment: isSvg ? Alignment.center : AlignmentDirectional.bottomStart,

View file

@ -4,41 +4,46 @@ import 'dart:ui';
import 'package:aves/image_providers/thumbnail_provider.dart'; import 'package:aves/image_providers/thumbnail_provider.dart';
import 'package:aves/model/entry.dart'; import 'package:aves/model/entry.dart';
import 'package:aves/model/entry_images.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/services/services.dart';
import 'package:aves/widgets/collection/thumbnail/error.dart'; import 'package:aves/widgets/collection/thumbnail/error.dart';
import 'package:aves/widgets/common/extensions/build_context.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:aves/widgets/common/fx/transition_image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class RasterImageThumbnail extends StatefulWidget { class ThumbnailImage extends StatefulWidget {
final AvesEntry entry; final AvesEntry entry;
final double extent; final double extent;
final BoxFit fit; final BoxFit? fit;
final bool showLoadingBackground; final bool showLoadingBackground;
final ValueNotifier<bool>? cancellableNotifier; final ValueNotifier<bool>? cancellableNotifier;
final Object? heroTag; final Object? heroTag;
const RasterImageThumbnail({ const ThumbnailImage({
Key? key, Key? key,
required this.entry, required this.entry,
required this.extent, required this.extent,
this.fit = BoxFit.cover, this.fit,
this.showLoadingBackground = true, this.showLoadingBackground = true,
this.cancellableNotifier, this.cancellableNotifier,
this.heroTag, this.heroTag,
}) : super(key: key); }) : super(key: key);
@override @override
_RasterImageThumbnailState createState() => _RasterImageThumbnailState(); _ThumbnailImageState createState() => _ThumbnailImageState();
} }
class _RasterImageThumbnailState extends State<RasterImageThumbnail> { class _ThumbnailImageState extends State<ThumbnailImage> {
final _providers = <_ConditionalImageProvider>[]; final _providers = <_ConditionalImageProvider>[];
_ProviderStream? _currentProviderStream; _ProviderStream? _currentProviderStream;
ImageInfo? _lastImageInfo; ImageInfo? _lastImageInfo;
Object? _lastException; Object? _lastException;
late final ImageStreamListener _streamListener; late final ImageStreamListener _streamListener;
late DisposableBuildContext<State<RasterImageThumbnail>> _scrollAwareContext; late DisposableBuildContext<State<ThumbnailImage>> _scrollAwareContext;
AvesEntry get entry => widget.entry; AvesEntry get entry => widget.entry;
@ -48,12 +53,12 @@ class _RasterImageThumbnailState extends State<RasterImageThumbnail> {
void initState() { void initState() {
super.initState(); super.initState();
_streamListener = ImageStreamListener(_onImageLoad, onError: _onError); _streamListener = ImageStreamListener(_onImageLoad, onError: _onError);
_scrollAwareContext = DisposableBuildContext<State<RasterImageThumbnail>>(this); _scrollAwareContext = DisposableBuildContext<State<ThumbnailImage>>(this);
_registerWidget(widget); _registerWidget(widget);
} }
@override @override
void didUpdateWidget(covariant RasterImageThumbnail oldWidget) { void didUpdateWidget(covariant ThumbnailImage oldWidget) {
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
if (oldWidget.entry != entry) { if (oldWidget.entry != entry) {
_unregisterWidget(oldWidget); _unregisterWidget(oldWidget);
@ -68,12 +73,12 @@ class _RasterImageThumbnailState extends State<RasterImageThumbnail> {
super.dispose(); super.dispose();
} }
void _registerWidget(RasterImageThumbnail widget) { void _registerWidget(ThumbnailImage widget) {
widget.entry.imageChangeNotifier.addListener(_onImageChanged); widget.entry.imageChangeNotifier.addListener(_onImageChanged);
_initProvider(); _initProvider();
} }
void _unregisterWidget(RasterImageThumbnail widget) { void _unregisterWidget(ThumbnailImage widget) {
widget.entry.imageChangeNotifier.removeListener(_onImageChanged); widget.entry.imageChangeNotifier.removeListener(_onImageChanged);
_pauseProvider(); _pauseProvider();
_currentProviderStream?.stopListening(); _currentProviderStream?.stopListening();
@ -87,12 +92,13 @@ class _RasterImageThumbnailState extends State<RasterImageThumbnail> {
_lastException = null; _lastException = null;
_providers.clear(); _providers.clear();
_providers.addAll([ _providers.addAll([
_ConditionalImageProvider( if (!entry.isSvg)
ScrollAwareImageProvider( _ConditionalImageProvider(
context: _scrollAwareContext, ScrollAwareImageProvider(
imageProvider: entry.getThumbnail(), context: _scrollAwareContext,
imageProvider: entry.getThumbnail(),
),
), ),
),
_ConditionalImageProvider( _ConditionalImageProvider(
ScrollAwareImageProvider( ScrollAwareImageProvider(
context: _scrollAwareContext, context: _scrollAwareContext,
@ -152,14 +158,14 @@ class _RasterImageThumbnailState extends State<RasterImageThumbnail> {
} }
} }
Color? _backgroundColor; Color? _loadingBackgroundColor;
Color get backgroundColor { Color get loadingBackgroundColor {
if (_backgroundColor == null) { if (_loadingBackgroundColor == null) {
final rgb = 0x30 + entry.uri.hashCode % 0x20; 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 @override
@ -173,21 +179,54 @@ class _RasterImageThumbnailState extends State<RasterImageThumbnail> {
// use `RawImage` instead of `Image`, using `ImageInfo` to check dimensions // use `RawImage` instead of `Image`, using `ImageInfo` to check dimensions
// and have more control when chaining image providers // and have more control when chaining image providers
final fit = widget.fit ?? (entry.isSvg ? BoxFit.contain : BoxFit.cover);
final imageInfo = _lastImageInfo; final imageInfo = _lastImageInfo;
final image = imageInfo == null final image = imageInfo == null
? Container( ? Container(
color: widget.showLoadingBackground ? backgroundColor : Colors.transparent, color: widget.showLoadingBackground ? loadingBackgroundColor : Colors.transparent,
width: extent, width: extent,
height: extent, height: extent,
) )
: RawImage( : Selector<Settings, EntryBackground>(
image: imageInfo.image, selector: (context, s) => entry.isSvg ? s.vectorBackground : s.rasterBackground,
debugImageLabel: imageInfo.debugLabel, builder: (context, background, child) {
width: extent, final backgroundColor = background.isColor ? background.color : null;
height: extent,
scale: imageInfo.scale, if (background == EntryBackground.checkered) {
fit: widget.fit, 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 return widget.heroTag != null
? Hero( ? Hero(

View file

@ -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<Settings, EntryBackground>(
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;
}
}

View file

@ -2,8 +2,7 @@ import 'package:aves/model/covers.dart';
import 'package:aves/model/entry.dart'; import 'package:aves/model/entry.dart';
import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/widgets/collection/thumbnail/raster.dart'; import 'package:aves/widgets/collection/thumbnail/image.dart';
import 'package:aves/widgets/collection/thumbnail/vector.dart';
import 'package:aves/widgets/common/extensions/build_context.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/common/providers/media_query_data_provider.dart';
import 'package:aves/widgets/dialogs/item_pick_dialog.dart'; import 'package:aves/widgets/dialogs/item_pick_dialog.dart';
@ -114,15 +113,10 @@ class _AddShortcutDialogState extends State<AddShortcutDialog> {
child: SizedBox( child: SizedBox(
width: extent, width: extent,
height: extent, height: extent,
child: entry.isSvg child: ThumbnailImage(
? VectorImageThumbnail( entry: entry,
entry: entry, extent: extent,
extent: extent, ),
)
: RasterImageThumbnail(
entry: entry,
extent: extent,
),
), ),
), ),
); );

View file

@ -13,8 +13,7 @@ import 'package:aves/theme/durations.dart';
import 'package:aves/theme/icons.dart'; import 'package:aves/theme/icons.dart';
import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/utils/android_file_utils.dart';
import 'package:aves/utils/constants.dart'; import 'package:aves/utils/constants.dart';
import 'package:aves/widgets/collection/thumbnail/raster.dart'; import 'package:aves/widgets/collection/thumbnail/image.dart';
import 'package:aves/widgets/collection/thumbnail/vector.dart';
import 'package:aves/widgets/common/identity/aves_filter_chip.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/filter_grid_page.dart';
import 'package:aves/widgets/filter_grids/common/overlay.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 entry = coverEntry ?? source.coverEntry(filter);
final backgroundImage = entry == null final backgroundImage = entry == null
? Container(color: Colors.white) ? Container(color: Colors.white)
: entry.isSvg : ThumbnailImage(
? VectorImageThumbnail( entry: entry,
entry: entry, extent: thumbnailExtent,
extent: extent, );
)
: RasterImageThumbnail(
entry: entry,
extent: thumbnailExtent,
);
final titlePadding = min<double>(4.0, extent / 32); final titlePadding = min<double>(4.0, extent / 32);
final borderRadius = BorderRadius.all(radius(extent)); final borderRadius = BorderRadius.all(radius(extent));
Widget child = AvesFilterChip( Widget child = AvesFilterChip(

View file

@ -2,8 +2,7 @@ import 'dart:typed_data';
import 'dart:ui' as ui; import 'dart:ui' as ui;
import 'package:aves/model/entry.dart'; import 'package:aves/model/entry.dart';
import 'package:aves/widgets/collection/thumbnail/raster.dart'; import 'package:aves/widgets/collection/thumbnail/image.dart';
import 'package:aves/widgets/collection/thumbnail/vector.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -30,15 +29,10 @@ class ImageMarker extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final thumbnail = entry.isSvg final thumbnail = ThumbnailImage(
? VectorImageThumbnail( entry: entry,
entry: entry, extent: extent,
extent: extent, );
)
: RasterImageThumbnail(
entry: entry,
extent: extent,
);
const outerDecoration = BoxDecoration( const outerDecoration = BoxDecoration(
border: Border.fromBorderSide(BorderSide( border: Border.fromBorderSide(BorderSide(

View file

@ -6,7 +6,7 @@ import 'package:aves/model/settings/entry_background.dart';
import 'package:aves/model/settings/enums.dart'; import 'package:aves/model/settings/enums.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/durations.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/controller.dart';
import 'package:aves/widgets/common/magnifier/controller/state.dart'; import 'package:aves/widgets/common/magnifier/controller/state.dart';
import 'package:aves/widgets/common/magnifier/magnifier.dart'; import 'package:aves/widgets/common/magnifier/magnifier.dart';
@ -232,7 +232,7 @@ class _EntryPageViewState extends State<EntryPageView> {
duration: Durations.viewerVideoPlayerTransition, duration: Durations.viewerVideoPlayerTransition,
child: GestureDetector( child: GestureDetector(
onTap: _onTap, onTap: _onTap,
child: RasterImageThumbnail( child: ThumbnailImage(
entry: entry, entry: entry,
extent: context.select<MediaQueryData, double>((mq) => mq.size.shortestSide), extent: context.select<MediaQueryData, double>((mq) => mq.size.shortestSide),
fit: BoxFit.contain, fit: BoxFit.contain,