SVG migration: thumbnails
This commit is contained in:
parent
e76376be91
commit
92178ca409
13 changed files with 186 additions and 160 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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(
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in a new issue