feat(mobile): image caching & viewer improvements (#4095)

This commit is contained in:
Fynn Petersen-Frey 2023-09-18 05:57:05 +02:00 committed by GitHub
parent 63b6a71ebd
commit 1c02e1dadf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 283 additions and 212 deletions

View file

@ -45,14 +45,9 @@ class ImmichImage extends StatelessWidget {
);
}
final Asset asset = this.asset!;
if (!asset.isRemote ||
(asset.isLocal && !Store.get(StoreKey.preferRemoteImage, false))) {
if (useLocal(asset)) {
return Image(
image: AssetEntityImageProvider(
asset.local!,
isOriginal: false,
thumbnailSize: const ThumbnailSize.square(250), // like server thumbs
),
image: localThumbnailProvider(asset),
width: width,
height: height,
fit: fit,
@ -148,45 +143,44 @@ class ImmichImage extends StatelessWidget {
);
}
static AssetEntityImageProvider localThumbnailProvider(Asset asset) =>
AssetEntityImageProvider(
asset.local!,
isOriginal: false,
thumbnailSize: const ThumbnailSize.square(250),
);
static CachedNetworkImageProvider remoteThumbnailProvider(
Asset asset,
api.ThumbnailFormat type,
Map<String, String> authHeader,
) =>
CachedNetworkImageProvider(
getThumbnailUrl(asset, type: type),
cacheKey: getThumbnailCacheKey(asset, type: type),
headers: authHeader,
);
/// Precaches this asset for instant load the next time it is shown
static Future<void> precacheAsset(
Asset asset,
BuildContext context, {
type = api.ThumbnailFormat.WEBP,
}) {
final authToken = 'Bearer ${Store.get(StoreKey.accessToken)}';
if (type == api.ThumbnailFormat.WEBP) {
final thumbnailUrl = getThumbnailUrl(asset);
final thumbnailCacheKey = getThumbnailCacheKey(asset);
final thumbnailProvider = CachedNetworkImageProvider(
thumbnailUrl,
cacheKey: thumbnailCacheKey,
headers: {"Authorization": authToken},
);
return precacheImage(thumbnailProvider, context);
}
// Precache the local image
if (!asset.isRemote &&
(asset.isLocal || !Store.get(StoreKey.preferRemoteImage, false))) {
final provider = AssetEntityImageProvider(
asset.local!,
isOriginal: false,
thumbnailSize: const ThumbnailSize.square(250), // like server thumbs
);
return precacheImage(provider, context);
if (useLocal(asset)) {
// Precache the local image
return precacheImage(localThumbnailProvider(asset), context);
} else {
final authToken = 'Bearer ${Store.get(StoreKey.accessToken)}';
// Precache the remote image since we are not using local images
final url = getThumbnailUrl(asset, type: api.ThumbnailFormat.JPEG);
final cacheKey =
getThumbnailCacheKey(asset, type: api.ThumbnailFormat.JPEG);
final provider = CachedNetworkImageProvider(
url,
cacheKey: cacheKey,
headers: {"Authorization": authToken},
return precacheImage(
remoteThumbnailProvider(asset, type, {"Authorization": authToken}),
context,
);
return precacheImage(provider, context);
}
}
static bool useLocal(Asset asset) =>
!asset.isRemote ||
asset.isLocal && !Store.get(StoreKey.preferRemoteImage, false);
}

View file

@ -235,6 +235,7 @@ class PhotoView extends StatefulWidget {
const PhotoView({
Key? key,
required this.imageProvider,
required this.index,
this.loadingBuilder,
this.backgroundDecoration,
this.wantKeepAlive = false,
@ -304,6 +305,7 @@ class PhotoView extends StatefulWidget {
imageProvider = null,
gaplessPlayback = false,
loadingBuilder = null,
index = 0,
super(key: key);
/// Given a [imageProvider] it resolves into an zoomable image widget using. It
@ -419,6 +421,8 @@ class PhotoView extends StatefulWidget {
/// Useful when you want to drag a widget without restrictions.
final bool? enablePanAlways;
final int index;
bool get _isCustomChild {
return child != null;
}
@ -571,6 +575,7 @@ class _PhotoViewState extends State<PhotoView>
disableGestures: widget.disableGestures,
errorBuilder: widget.errorBuilder,
enablePanAlways: widget.enablePanAlways,
index: widget.index,
);
},
);
@ -625,7 +630,7 @@ typedef PhotoViewImageDragStartCallback = Function(
PhotoViewControllerValue controllerValue,
);
/// A type definition for a callback when the user drags
/// A type definition for a callback when the user drags
typedef PhotoViewImageDragUpdateCallback = Function(
BuildContext context,
DragUpdateDetails details,
@ -650,4 +655,5 @@ typedef PhotoViewImageScaleEndCallback = Function(
typedef LoadingBuilder = Widget Function(
BuildContext context,
ImageChunkEvent? event,
int index,
);

View file

@ -281,6 +281,7 @@ class _PhotoViewGalleryState extends State<PhotoViewGallery> {
)
: PhotoView(
key: ObjectKey(index),
index: index,
imageProvider: pageOption.imageProvider,
loadingBuilder: widget.loadingBuilder,
backgroundDecoration: widget.backgroundDecoration,
@ -315,7 +316,10 @@ class _PhotoViewGalleryState extends State<PhotoViewGallery> {
);
}
PhotoViewGalleryPageOptions _buildPageOption(BuildContext context, int index) {
PhotoViewGalleryPageOptions _buildPageOption(
BuildContext context,
int index,
) {
if (widget._isBuilder) {
return widget.builder!(context, index);
}

View file

@ -35,6 +35,7 @@ class ImageWrapper extends StatefulWidget {
required this.disableGestures,
required this.errorBuilder,
required this.enablePanAlways,
required this.index,
}) : super(key: key);
final ImageProvider imageProvider;
@ -64,6 +65,7 @@ class ImageWrapper extends StatefulWidget {
final FilterQuality? filterQuality;
final bool? disableGestures;
final bool? enablePanAlways;
final int index;
@override
createState() => _ImageWrapperState();
@ -128,6 +130,7 @@ class _ImageWrapperState extends State<ImageWrapper> {
_lastException = null;
_lastStack = null;
}
synchronousCall ? setupCB() : setState(setupCB);
}
@ -212,7 +215,7 @@ class _ImageWrapperState extends State<ImageWrapper> {
Widget _buildLoading(BuildContext context) {
if (widget.loadingBuilder != null) {
return widget.loadingBuilder!(context, _loadingProgress);
return widget.loadingBuilder!(context, _loadingProgress, widget.index);
}
return PhotoViewDefaultLoading(