Fix scroll-back placeholders: in-memory bitmap cache in DensePhotoGrid
bitmapCache (MutableMap keyed on devicePath) lives at DensePhotoGrid level and survives Compose cell recycling. DenseThumb checks memory cache first — scroll-back is now instant for already-loaded cells. Disk cache (ThumbnailCache) is only hit when bitmap not in memory.
This commit is contained in:
@@ -53,6 +53,11 @@ fun DensePhotoGrid(
|
||||
onDeselectAllFolder: (List<PhonePhoto>) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
// In-memory bitmap cache — survives cell recycling during scroll
|
||||
// keyed on cacheKey so it resets when device changes
|
||||
val bitmapCache = remember(cacheKey) {
|
||||
mutableMapOf<String, ImageBitmap>()
|
||||
}
|
||||
if (folderSections.isEmpty() || folderSections.all { it.allPhotos.isEmpty() }) {
|
||||
Box(modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
Column(
|
||||
@@ -144,6 +149,7 @@ fun DensePhotoGrid(
|
||||
cacheKey = cacheKey,
|
||||
pullFn = pullFn,
|
||||
thumbnailVersion = thumbnailVersion,
|
||||
bitmapCache = bitmapCache,
|
||||
onToggle = { onTogglePhoto(labeled.photo) },
|
||||
dateLabel = labeled.dateLabel,
|
||||
modifier = Modifier.weight(1f),
|
||||
@@ -167,13 +173,16 @@ private fun DenseThumb(
|
||||
cacheKey: String,
|
||||
pullFn: suspend (devicePath: String, localDestPath: Path) -> Boolean,
|
||||
thumbnailVersion: Int,
|
||||
bitmapCache: MutableMap<String, ImageBitmap>,
|
||||
onToggle: () -> Unit,
|
||||
dateLabel: String?,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
var thumbnail by remember(photo.devicePath) { mutableStateOf<ImageBitmap?>(null) }
|
||||
// Check in-memory cache first (survives Compose cell recycling during scroll)
|
||||
var thumbnail by remember(photo.devicePath) {
|
||||
mutableStateOf(bitmapCache[photo.devicePath])
|
||||
}
|
||||
|
||||
// Re-run when thumbnailVersion increments (new batch cached) or on first composition
|
||||
LaunchedEffect(photo.devicePath, thumbnailVersion) {
|
||||
if (thumbnail == null) {
|
||||
val bytes = withContext(Dispatchers.IO) {
|
||||
@@ -186,7 +195,9 @@ private fun DenseThumb(
|
||||
}
|
||||
if (bytes != null && bytes.isNotEmpty()) {
|
||||
runCatching {
|
||||
thumbnail = SkiaImage.makeFromEncoded(bytes).toComposeImageBitmap()
|
||||
val bitmap = SkiaImage.makeFromEncoded(bytes).toComposeImageBitmap()
|
||||
bitmapCache[photo.devicePath] = bitmap // store in memory cache
|
||||
thumbnail = bitmap
|
||||
}.onFailure { e ->
|
||||
thumbLog.warn("Decode failed for ${photo.filename} (${bytes.size} bytes): ${e.message}")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user