Bladeren bron

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>
martyfuhry 2 jaren geleden
bovenliggende
commit
7f35583c2c
2 gewijzigde bestanden met toevoegingen van 113 en 5 verwijderingen
  1. 71 5
      mobile/lib/modules/memories/views/memory_page.dart
  2. 42 0
      mobile/lib/shared/ui/immich_image.dart

+ 71 - 5
mobile/lib/modules/memories/views/memory_page.dart

@@ -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();
     }
 

+ 42 - 0
mobile/lib/shared/ui/immich_image.dart

@@ -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);
+    }
+  }
 }