added thumbnail image provider, clarified image service, get unreadable video preview by Glide
This commit is contained in:
parent
0cedb70666
commit
fe0440f265
13 changed files with 190 additions and 184 deletions
|
@ -74,7 +74,7 @@ public class ImageDecodeTask extends AsyncTask<ImageDecodeTask.Params, Void, Ima
|
|||
bitmap = getThumbnailBytesByMediaStore(p);
|
||||
}
|
||||
} else {
|
||||
Log.d(LOG_TAG, "getImageBytes with uri=" + p.entry.uri + " cancelled");
|
||||
Log.d(LOG_TAG, "getThumbnail with uri=" + p.entry.uri + " cancelled");
|
||||
}
|
||||
byte[] data = null;
|
||||
if (bitmap != null) {
|
||||
|
@ -119,7 +119,7 @@ public class ImageDecodeTask extends AsyncTask<ImageDecodeTask.Params, Void, Ima
|
|||
return null;
|
||||
}
|
||||
|
||||
private Bitmap getImageBytesByGlide(Params params) {
|
||||
private Bitmap getThumbnailByGlide(Params params) {
|
||||
ImageEntry entry = params.entry;
|
||||
int width = params.width;
|
||||
int height = params.height;
|
||||
|
@ -151,7 +151,7 @@ public class ImageDecodeTask extends AsyncTask<ImageDecodeTask.Params, Void, Ima
|
|||
try {
|
||||
return target.get();
|
||||
} catch (InterruptedException e) {
|
||||
Log.d(LOG_TAG, "getImageBytes with uri=" + entry.uri + " interrupted");
|
||||
Log.d(LOG_TAG, "getThumbnail with uri=" + entry.uri + " interrupted");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
@ -167,7 +167,7 @@ public class ImageDecodeTask extends AsyncTask<ImageDecodeTask.Params, Void, Ima
|
|||
if (result.data != null) {
|
||||
r.success(result.data);
|
||||
} else {
|
||||
r.error("getImageBytes-null", "failed to get thumbnail for uri=" + uri, null);
|
||||
r.error("getThumbnail-null", "failed to get thumbnail for uri=" + uri, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,11 +11,18 @@ import android.os.Looper;
|
|||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.bumptech.glide.request.FutureTarget;
|
||||
import com.bumptech.glide.request.RequestOptions;
|
||||
import com.bumptech.glide.request.target.Target;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
|
||||
import deckers.thibault.aves.decoder.VideoThumbnail;
|
||||
import deckers.thibault.aves.model.ImageEntry;
|
||||
import deckers.thibault.aves.model.provider.ImageProvider;
|
||||
import deckers.thibault.aves.model.provider.ImageProviderFactory;
|
||||
|
@ -46,14 +53,14 @@ public class ImageFileHandler implements MethodChannel.MethodCallHandler {
|
|||
case "getImageEntry":
|
||||
new Thread(() -> getImageEntry(call, new MethodResultWrapper(result))).start();
|
||||
break;
|
||||
case "readAsBytes":
|
||||
new Thread(() -> readAsBytes(call, new MethodResultWrapper(result))).start();
|
||||
case "getImage":
|
||||
new Thread(() -> getImage(call, new MethodResultWrapper(result))).start();
|
||||
break;
|
||||
case "getImageBytes":
|
||||
new Thread(() -> getImageBytes(call, new MethodResultWrapper(result))).start();
|
||||
case "getThumbnail":
|
||||
new Thread(() -> getThumbnail(call, new MethodResultWrapper(result))).start();
|
||||
break;
|
||||
case "cancelGetImageBytes":
|
||||
new Thread(() -> cancelGetImageBytes(call, new MethodResultWrapper(result))).start();
|
||||
case "cancelGetThumbnail":
|
||||
new Thread(() -> cancelGetThumbnail(call, new MethodResultWrapper(result))).start();
|
||||
break;
|
||||
case "delete":
|
||||
new Thread(() -> delete(call, new MethodResultWrapper(result))).start();
|
||||
|
@ -70,52 +77,78 @@ public class ImageFileHandler implements MethodChannel.MethodCallHandler {
|
|||
}
|
||||
}
|
||||
|
||||
private void readAsBytes(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
|
||||
private void getImage(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
|
||||
String mimeType = call.argument("mimeType");
|
||||
String uriString = call.argument("uri");
|
||||
|
||||
byte[] data = null;
|
||||
ContentResolver cr = activity.getContentResolver();
|
||||
Uri uri = Uri.parse(uriString);
|
||||
try (InputStream is = cr.openInputStream(uri)) {
|
||||
if (is != null) {
|
||||
data = getBytes(is);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && (MimeTypes.HEIC.equals(mimeType) || MimeTypes.HEIF.equals(mimeType))) {
|
||||
// as of Flutter v1.15.17, Dart Image.memory cannot decode HEIF/HEIC images
|
||||
// so we convert the image using Android native decoder
|
||||
ImageDecoder.Source source = ImageDecoder.createSource(cr, uri);
|
||||
Bitmap bitmap = ImageDecoder.decodeBitmap(source);
|
||||
|
||||
byte[] data = null;
|
||||
if (mimeType != null && mimeType.startsWith(MimeTypes.VIDEO)) {
|
||||
RequestOptions options = new RequestOptions()
|
||||
.diskCacheStrategy(DiskCacheStrategy.RESOURCE);
|
||||
FutureTarget<Bitmap> target = Glide.with(activity)
|
||||
.asBitmap()
|
||||
.apply(options)
|
||||
.load(new VideoThumbnail(activity, uri))
|
||||
.submit(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL);
|
||||
try {
|
||||
Bitmap bitmap = target.get();
|
||||
if (bitmap != null) {
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
// we compress the bitmap because Dart Image.memory cannot decode the raw bytes
|
||||
// Bitmap.CompressFormat.PNG is slower than JPEG
|
||||
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, stream);
|
||||
data = stream.toByteArray();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
result.error("getImage-video-exception", "failed to get image from uri=" + uri, e.getMessage());
|
||||
return;
|
||||
}
|
||||
Glide.with(activity).clear(target);
|
||||
} else {
|
||||
ContentResolver cr = activity.getContentResolver();
|
||||
try (InputStream is = cr.openInputStream(uri)) {
|
||||
if (is != null) {
|
||||
data = getBytes(is);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && (MimeTypes.HEIC.equals(mimeType) || MimeTypes.HEIF.equals(mimeType))) {
|
||||
// as of Flutter v1.15.17, Dart Image.memory cannot decode HEIF/HEIC images
|
||||
// so we convert the image using Android native decoder
|
||||
ImageDecoder.Source source = ImageDecoder.createSource(cr, uri);
|
||||
Bitmap bitmap = ImageDecoder.decodeBitmap(source);
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
// we compress the bitmap because Dart Image.memory cannot decode the raw bytes
|
||||
// Bitmap.CompressFormat.PNG is slower than JPEG
|
||||
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, stream);
|
||||
data = stream.toByteArray();
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
result.error("getImage-image-exception", "failed to get image from uri=" + uri, e.getMessage());
|
||||
return;
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
if (data != null) {
|
||||
result.success(data);
|
||||
} else {
|
||||
result.error("readAsBytes-null", "failed to read bytes from uri=" + uri, null);
|
||||
result.error("getImage-null", "failed to get image from uri=" + uri, null);
|
||||
}
|
||||
}
|
||||
|
||||
private void getImageBytes(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
|
||||
private void getThumbnail(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
|
||||
Map entryMap = call.argument("entry");
|
||||
Integer width = call.argument("width");
|
||||
Integer height = call.argument("height");
|
||||
if (entryMap == null || width == null || height == null) {
|
||||
result.error("getImageBytes-args", "failed because of missing arguments", null);
|
||||
result.error("getThumbnail-args", "failed because of missing arguments", null);
|
||||
return;
|
||||
}
|
||||
ImageEntry entry = new ImageEntry(entryMap);
|
||||
imageDecodeTaskManager.fetch(result, entry, width, height);
|
||||
}
|
||||
|
||||
private void cancelGetImageBytes(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
|
||||
private void cancelGetThumbnail(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
|
||||
String uri = call.argument("uri");
|
||||
imageDecodeTaskManager.cancel(uri);
|
||||
result.success(null);
|
||||
|
|
|
@ -29,43 +29,43 @@ class ImageFileService {
|
|||
return null;
|
||||
}
|
||||
|
||||
static Future<Uint8List> readAsBytes(String uri, String mimeType) async {
|
||||
static Future<Uint8List> getImage(String uri, String mimeType) async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('readAsBytes', <String, dynamic>{
|
||||
final result = await platform.invokeMethod('getImage', <String, dynamic>{
|
||||
'uri': uri,
|
||||
'mimeType': mimeType,
|
||||
});
|
||||
return result as Uint8List;
|
||||
} on PlatformException catch (e) {
|
||||
debugPrint('readAsBytes failed with exception=${e.message}');
|
||||
debugPrint('getImage failed with exception=${e.message}');
|
||||
}
|
||||
return Uint8List(0);
|
||||
}
|
||||
|
||||
static Future<Uint8List> getImageBytes(ImageEntry entry, int width, int height) async {
|
||||
static Future<Uint8List> getThumbnail(ImageEntry entry, int width, int height) async {
|
||||
if (width > 0 && height > 0) {
|
||||
// debugPrint('getImageBytes width=$width path=${entry.path}');
|
||||
// debugPrint('getThumbnail width=$width path=${entry.path}');
|
||||
try {
|
||||
final result = await platform.invokeMethod('getImageBytes', <String, dynamic>{
|
||||
final result = await platform.invokeMethod('getThumbnail', <String, dynamic>{
|
||||
'entry': entry.toMap(),
|
||||
'width': width,
|
||||
'height': height,
|
||||
});
|
||||
return result as Uint8List;
|
||||
} on PlatformException catch (e) {
|
||||
debugPrint('getImageBytes failed with exception=${e.message}');
|
||||
debugPrint('getThumbnail failed with exception=${e.message}');
|
||||
}
|
||||
}
|
||||
return Uint8List(0);
|
||||
}
|
||||
|
||||
static Future<void> cancelGetImageBytes(String uri) async {
|
||||
static Future<void> cancelGetThumbnail(String uri) async {
|
||||
try {
|
||||
await platform.invokeMethod('cancelGetImageBytes', <String, dynamic>{
|
||||
await platform.invokeMethod('cancelGetThumbnail', <String, dynamic>{
|
||||
'uri': uri,
|
||||
});
|
||||
} on PlatformException catch (e) {
|
||||
debugPrint('cancelGetImageBytes failed with exception=${e.message}');
|
||||
debugPrint('cancelGetThumbnail failed with exception=${e.message}');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,9 @@ class Constants {
|
|||
// so we give it a `strutStyle` with a slightly larger height
|
||||
static const overflowStrutStyle = StrutStyle(height: 1.3);
|
||||
|
||||
// TODO TLAD smarter sizing, but shouldn't only depend on `extent` so that it doesn't reload during gridview scaling
|
||||
static const double thumbnailCacheExtent = 50;
|
||||
|
||||
static const svgBackground = Colors.white;
|
||||
static const svgColorFilter = ColorFilter.mode(svgBackground, BlendMode.dstOver);
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import 'dart:math';
|
|||
import 'package:aves/model/image_entry.dart';
|
||||
import 'package:aves/utils/constants.dart';
|
||||
import 'package:aves/widgets/common/icons.dart';
|
||||
import 'package:aves/widgets/common/image_preview.dart';
|
||||
import 'package:aves/widgets/common/image_providers/thumbnail_provider.dart';
|
||||
import 'package:aves/widgets/common/image_providers/uri_picture_provider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
@ -48,33 +48,31 @@ class Thumbnail extends StatelessWidget {
|
|||
}
|
||||
|
||||
Widget _buildRasterImage() {
|
||||
return ImagePreview(
|
||||
entry: entry,
|
||||
// TODO TLAD smarter sizing, but shouldn't only depend on `extent` so that it doesn't reload during gridview scaling
|
||||
width: 50,
|
||||
height: 50,
|
||||
builder: (bytes) {
|
||||
final imageBuilder = (bytes, dim) => Image.memory(
|
||||
bytes,
|
||||
width: dim,
|
||||
height: dim,
|
||||
fit: BoxFit.cover,
|
||||
);
|
||||
return heroTag == null
|
||||
? imageBuilder(bytes, extent)
|
||||
: Hero(
|
||||
tag: heroTag,
|
||||
flightShuttleBuilder: (flightContext, animation, flightDirection, fromHeroContext, toHeroContext) {
|
||||
// use LayoutBuilder only during hero animation
|
||||
return LayoutBuilder(builder: (context, constraints) {
|
||||
final dim = min(constraints.maxWidth, constraints.maxHeight);
|
||||
return imageBuilder(bytes, dim);
|
||||
});
|
||||
},
|
||||
child: imageBuilder(bytes, extent),
|
||||
);
|
||||
},
|
||||
final provider = ThumbnailProvider(entry: entry, extent: Constants.thumbnailCacheExtent);
|
||||
final image = Image(
|
||||
image: provider,
|
||||
width: extent,
|
||||
height: extent,
|
||||
fit: BoxFit.cover,
|
||||
);
|
||||
return heroTag == null
|
||||
? image
|
||||
: Hero(
|
||||
tag: heroTag,
|
||||
flightShuttleBuilder: (flightContext, animation, flightDirection, fromHeroContext, toHeroContext) {
|
||||
// use LayoutBuilder only during hero animation
|
||||
return LayoutBuilder(builder: (context, constraints) {
|
||||
final dim = min(constraints.maxWidth, constraints.maxHeight);
|
||||
return Image(
|
||||
image: provider,
|
||||
width: dim,
|
||||
height: dim,
|
||||
fit: BoxFit.cover,
|
||||
);
|
||||
});
|
||||
},
|
||||
child: image,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildVectorImage() {
|
||||
|
|
|
@ -1,94 +0,0 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:after_init/after_init.dart';
|
||||
import 'package:aves/model/image_entry.dart';
|
||||
import 'package:aves/model/image_file_service.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:outline_material_icons/outline_material_icons.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:transparent_image/transparent_image.dart';
|
||||
|
||||
class ImagePreview extends StatefulWidget {
|
||||
final ImageEntry entry;
|
||||
final double width, height;
|
||||
final Widget Function(Uint8List bytes) builder;
|
||||
|
||||
const ImagePreview({
|
||||
Key key,
|
||||
@required this.entry,
|
||||
@required this.width,
|
||||
@required this.height,
|
||||
@required this.builder,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => ImagePreviewState();
|
||||
}
|
||||
|
||||
class ImagePreviewState extends State<ImagePreview> with AfterInitMixin {
|
||||
Future<Uint8List> _byteLoader;
|
||||
Listenable _entryChangeNotifier;
|
||||
double _devicePixelRatio;
|
||||
|
||||
ImageEntry get entry => widget.entry;
|
||||
|
||||
String get uri => widget.entry.uri;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
// debugPrint('$runtimeType initState path=${entry.path}');
|
||||
super.initState();
|
||||
_entryChangeNotifier = Listenable.merge([
|
||||
entry.imageChangeNotifier,
|
||||
entry.metadataChangeNotifier,
|
||||
]);
|
||||
_entryChangeNotifier.addListener(_onEntryChange);
|
||||
}
|
||||
|
||||
@override
|
||||
void didInitState() {
|
||||
_devicePixelRatio = Provider.of<MediaQueryData>(context, listen: false).devicePixelRatio;
|
||||
_initByteLoader();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(ImagePreview old) {
|
||||
// debugPrint('$runtimeType didUpdateWidget from=${old.entry.path} to=${entry.path}');
|
||||
super.didUpdateWidget(old);
|
||||
if (widget.width == old.width && widget.height == old.height && uri == old.entry.uri && entry.width == old.entry.width && entry.height == old.entry.height && entry.orientationDegrees == old.entry.orientationDegrees) return;
|
||||
_initByteLoader();
|
||||
}
|
||||
|
||||
void _initByteLoader() {
|
||||
final width = (widget.width * _devicePixelRatio).round();
|
||||
final height = (widget.height * _devicePixelRatio).round();
|
||||
_byteLoader = ImageFileService.getImageBytes(entry, width, height);
|
||||
}
|
||||
|
||||
void _onEntryChange() => setState(() => _initByteLoader());
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
// debugPrint('$runtimeType dispose path=${entry.path}');
|
||||
_entryChangeNotifier.removeListener(_onEntryChange);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// debugPrint('$runtimeType build path=${entry.path}');
|
||||
return FutureBuilder(
|
||||
future: _byteLoader,
|
||||
builder: (futureContext, AsyncSnapshot<Uint8List> snapshot) {
|
||||
final bytes = (snapshot.connectionState == ConnectionState.done && !snapshot.hasError) ? snapshot.data : kTransparentImage;
|
||||
return bytes.isNotEmpty
|
||||
? widget.builder(bytes)
|
||||
: Center(
|
||||
child: Icon(
|
||||
OMIcons.error,
|
||||
color: Colors.blueGrey,
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -32,7 +32,7 @@ class AppIconImage extends ImageProvider<AppIconImageKey> {
|
|||
codec: _loadAsync(key, decode),
|
||||
scale: key.scale,
|
||||
informationCollector: () sync* {
|
||||
yield ErrorDescription('uri=$packageName, size=$size');
|
||||
yield ErrorDescription('packageName=$packageName, size=$size');
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
78
lib/widgets/common/image_providers/thumbnail_provider.dart
Normal file
78
lib/widgets/common/image_providers/thumbnail_provider.dart
Normal file
|
@ -0,0 +1,78 @@
|
|||
import 'dart:typed_data';
|
||||
import 'dart:ui' as ui show Codec;
|
||||
|
||||
import 'package:aves/model/image_entry.dart';
|
||||
import 'package:aves/model/image_file_service.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ThumbnailProvider extends ImageProvider<ThumbnailProviderKey> {
|
||||
const ThumbnailProvider({
|
||||
@required this.entry,
|
||||
@required this.extent,
|
||||
this.scale = 1.0,
|
||||
}) : assert(entry != null),
|
||||
assert(extent != null),
|
||||
assert(scale != null);
|
||||
|
||||
final ImageEntry entry;
|
||||
final double extent;
|
||||
final double scale;
|
||||
|
||||
@override
|
||||
Future<ThumbnailProviderKey> obtainKey(ImageConfiguration configuration) {
|
||||
// configuration can be empty (e.g. when obtaining key for eviction)
|
||||
// so we do not compute the target width/height here
|
||||
// and pass it to the key, to use it later for image loading
|
||||
return SynchronousFuture<ThumbnailProviderKey>(ThumbnailProviderKey(
|
||||
entry: entry,
|
||||
extent: extent,
|
||||
devicePixelRatio: configuration.devicePixelRatio,
|
||||
scale: scale,
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
ImageStreamCompleter load(ThumbnailProviderKey key, DecoderCallback decode) {
|
||||
return MultiFrameImageStreamCompleter(
|
||||
codec: _loadAsync(key, decode),
|
||||
scale: key.scale,
|
||||
informationCollector: () sync* {
|
||||
yield ErrorDescription('uri=${entry.uri}, extent=$extent');
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<ui.Codec> _loadAsync(ThumbnailProviderKey key, DecoderCallback decode) async {
|
||||
final dimPixels = (extent * key.devicePixelRatio).round();
|
||||
final Uint8List bytes = await ImageFileService.getThumbnail(key.entry, dimPixels, dimPixels);
|
||||
if (bytes.lengthInBytes == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await decode(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
class ThumbnailProviderKey {
|
||||
final ImageEntry entry;
|
||||
final double extent;
|
||||
final double devicePixelRatio; // do not include configuration in key hashcode or == operator
|
||||
final double scale;
|
||||
|
||||
const ThumbnailProviderKey({
|
||||
@required this.entry,
|
||||
@required this.extent,
|
||||
@required this.devicePixelRatio,
|
||||
this.scale,
|
||||
});
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (other.runtimeType != runtimeType) return false;
|
||||
return other is ThumbnailProviderKey && other.entry.uri == entry.uri && other.extent == extent && other.scale == scale;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => hashValues(entry.uri, extent, scale);
|
||||
}
|
|
@ -36,7 +36,7 @@ class UriImage extends ImageProvider<UriImage> {
|
|||
Future<ui.Codec> _loadAsync(UriImage key, DecoderCallback decode) async {
|
||||
assert(key == this);
|
||||
|
||||
final Uint8List bytes = await ImageFileService.readAsBytes(uri, mimeType);
|
||||
final Uint8List bytes = await ImageFileService.getImage(uri, mimeType);
|
||||
if (bytes.lengthInBytes == 0) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ class UriPicture extends PictureProvider<UriPicture> {
|
|||
Future<PictureInfo> _loadAsync(UriPicture key, {PictureErrorListener onError}) async {
|
||||
assert(key == this);
|
||||
|
||||
final data = await ImageFileService.readAsBytes(uri, mimeType);
|
||||
final data = await ImageFileService.getImage(uri, mimeType);
|
||||
if (data == null || data.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@ import 'dart:math';
|
|||
|
||||
import 'package:aves/model/collection_lens.dart';
|
||||
import 'package:aves/model/image_entry.dart';
|
||||
import 'package:aves/utils/constants.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/fullscreen/fullscreen_action_delegate.dart';
|
||||
import 'package:aves/widgets/fullscreen/image_page.dart';
|
||||
|
@ -418,10 +420,8 @@ class _FullscreenVerticalPageViewState extends State<FullscreenVerticalPageView>
|
|||
}
|
||||
|
||||
void _onImageChange() async {
|
||||
await UriImage(
|
||||
uri: entry.uri,
|
||||
mimeType: entry.mimeType,
|
||||
).evict();
|
||||
await UriImage(uri: entry.uri, mimeType: entry.mimeType).evict();
|
||||
await ThumbnailProvider(entry: entry, extent: Constants.thumbnailCacheExtent).evict();
|
||||
if (entry.path != null) await FileImage(File(entry.path)).evict();
|
||||
// rebuild to refresh the Image inside ImagePage
|
||||
setState(() {});
|
||||
|
|
|
@ -2,7 +2,7 @@ import 'package:aves/model/image_entry.dart';
|
|||
import 'package:aves/utils/constants.dart';
|
||||
import 'package:aves/widgets/common/image_providers/uri_image_provider.dart';
|
||||
import 'package:aves/widgets/common/image_providers/uri_picture_provider.dart';
|
||||
import 'package:aves/widgets/fullscreen/video.dart';
|
||||
import 'package:aves/widgets/fullscreen/video_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:photo_view/photo_view.dart';
|
||||
|
@ -84,10 +84,7 @@ class ImageView extends StatelessWidget {
|
|||
return PhotoView(
|
||||
// key includes size and orientation to refresh when the image is rotated
|
||||
key: ValueKey('${entry.orientationDegrees}_${entry.width}_${entry.height}_${entry.path}'),
|
||||
imageProvider: UriImage(
|
||||
uri: entry.uri,
|
||||
mimeType: entry.mimeType,
|
||||
),
|
||||
imageProvider: UriImage(uri: entry.uri, mimeType: entry.mimeType),
|
||||
loadingBuilder: (context, event) => placeholderBuilder(context),
|
||||
backgroundDecoration: backgroundDecoration,
|
||||
heroAttributes: heroAttributes,
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
import 'dart:math';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:aves/model/image_entry.dart';
|
||||
import 'package:aves/widgets/common/image_preview.dart';
|
||||
import 'package:aves/widgets/common/image_providers/uri_image_provider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
|
||||
class AvesVideo extends StatefulWidget {
|
||||
|
@ -58,17 +56,10 @@ class AvesVideoState extends State<AvesVideo> {
|
|||
Widget build(BuildContext context) {
|
||||
if (value == null) return const SizedBox();
|
||||
if (value.hasError) {
|
||||
return Selector<MediaQueryData, double>(
|
||||
selector: (c, mq) => mq.size.width,
|
||||
builder: (c, mqWidth, child) {
|
||||
final width = min<double>(mqWidth, entry.width.toDouble());
|
||||
return ImagePreview(
|
||||
entry: entry,
|
||||
width: width,
|
||||
height: width / entry.aspectRatio,
|
||||
builder: (bytes) => Image.memory(bytes),
|
||||
);
|
||||
},
|
||||
return Image(
|
||||
image: UriImage(uri: entry.uri, mimeType: entry.mimeType),
|
||||
width: entry.width.toDouble(),
|
||||
height: entry.height.toDouble(),
|
||||
);
|
||||
}
|
||||
return Center(
|
Loading…
Reference in a new issue