feat(mobile): Precaches next image in memories (#3365)

* Precaches images in memories

* Fixes jumps and precaches images

* refactors to move precacheAsset over to ImmichImage to keep logic in same place

---------

Co-authored-by: Alex Tran <Alex.Tran@conductix.com>
This commit is contained in:
martyfuhry 2023-07-22 15:51:25 -04:00 committed by GitHub
parent ace755f264
commit 7f35583c2c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 113 additions and 5 deletions

View file

@ -5,7 +5,10 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/memories/models/memory.dart';
import 'package:immich_mobile/modules/memories/ui/memory_card.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/ui/immich_image.dart';
import 'package:intl/intl.dart';
import 'package:openapi/api.dart' as api;
class MemoryPage extends HookConsumerWidget {
final List<Memory> memories;
@ -37,11 +40,16 @@ class MemoryPage extends HookConsumerWidget {
}
toNextAsset(int currentAssetIndex) {
(currentAssetIndex + 1 < currentMemory.value.assets.length)
? memoryAssetPageController.jumpToPage(
(currentAssetIndex + 1),
)
: toNextMemory();
if (currentAssetIndex + 1 < currentMemory.value.assets.length) {
// Go to the next asset
memoryAssetPageController.nextPage(
curve: Curves.easeInOut,
duration: const Duration(milliseconds: 500),
);
} else {
// Go to the next memory since we are at the end of our assets
toNextMemory();
}
}
updateProgressText() {
@ -49,9 +57,67 @@ class MemoryPage extends HookConsumerWidget {
"${currentAssetPage.value + 1}|${currentMemory.value.assets.length}";
}
/// Downloads and caches the image for the asset at this [currentMemory]'s index
precacheAsset(int index) async {
// Guard index out of range
if (index < 0) {
return;
}
late Asset asset;
if (index < currentMemory.value.assets.length) {
// Uses the next asset in this current memory
asset = currentMemory.value.assets[index];
} else {
// Precache the first asset in the next memory if available
final currentMemoryIndex = memories.indexOf(currentMemory.value);
// Guard no memory found
if (currentMemoryIndex == -1) {
return;
}
final nextMemoryIndex = currentMemoryIndex + 1;
// Guard no next memory
if (nextMemoryIndex >= memories.length) {
return;
}
// Get the first asset from the next memory
asset = memories[nextMemoryIndex].assets.first;
}
// Gets the thumbnail url and precaches it
final precaches = <Future<dynamic>>[];
precaches.add(
ImmichImage.precacheAsset(
asset,
context,
type: api.ThumbnailFormat.WEBP,
),
);
precaches.add(
ImmichImage.precacheAsset(
asset,
context,
type: api.ThumbnailFormat.JPEG,
),
);
await Future.wait(precaches);
}
// Precache the next page right away if we are on the first page
if (currentAssetPage.value == 0) {
Future.delayed(const Duration(milliseconds: 200))
.then((_) => precacheAsset(1));
}
onAssetChanged(int otherIndex) {
HapticFeedback.selectionClick();
currentAssetPage.value = otherIndex;
precacheAsset(otherIndex + 1);
updateProgressText();
}

View file

@ -147,4 +147,46 @@ class ImmichImage extends StatelessWidget {
},
);
}
/// 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);
} else {
// 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(provider, context);
}
}
}