Preload next image during slideshow (#1847)

## Description
When viewing images in a slideshow, this PR enqueues the next image so
it will likely be cached and ready to display when the slideshow
advances.

This works for both automatic and manual scrolling as well as advancing
next or previous images.

### Related issues
Closes #1835 

### Testing
Emulator with large photos

## Screenshots
N/A

## AI or LLM usage
None
This commit is contained in:
Damontecres 2026-08-16 10:20:46 -04:00 • committed by GitHub
parent 22a949bc76
commit 4e53cbaa7f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -6,6 +6,9 @@ import androidx.compose.runtime.Stable
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.media3.common.Player import androidx.media3.common.Player
import coil3.imageLoader
import coil3.request.ImageRequest
import coil3.size.Size
import com.github.damontecres.wholphin.data.ChosenStreams import com.github.damontecres.wholphin.data.ChosenStreams
import com.github.damontecres.wholphin.data.PlaybackEffectDao import com.github.damontecres.wholphin.data.PlaybackEffectDao
import com.github.damontecres.wholphin.data.ServerRepository import com.github.damontecres.wholphin.data.ServerRepository
@ -172,7 +175,7 @@ class SlideshowViewModel
val size = state.value.items.size val size = state.value.items.size
val newPosition = state.value.position + 1 val newPosition = state.value.position + 1
return if (newPosition < size) { return if (newPosition < size) {
updatePosition(newPosition) updatePosition(newPosition, true)
true true
} else { } else {
false false
@ -182,14 +185,23 @@ class SlideshowViewModel
fun previousImage(): Boolean { fun previousImage(): Boolean {
val newPosition = state.value.position - 1 val newPosition = state.value.position - 1
return if (newPosition >= 0) { return if (newPosition >= 0) {
updatePosition(newPosition) updatePosition(newPosition, false)
true true
} else { } else {
false false
} }
} }
fun updatePosition(position: Int): Job? = /**
* Update image state to the specified position
*
* @param position the index to use
* @param isNext whether moving to the next or previous image for prefetching purposes
*/
fun updatePosition(
position: Int,
isNext: Boolean = true,
): Job? =
(state.value.items as? ApiRequestPager<*>)?.let { pager -> (state.value.items as? ApiRequestPager<*>)?.let { pager ->
viewModelScope.launchIO { viewModelScope.launchIO {
try { try {
@ -286,6 +298,31 @@ class SlideshowViewModel
) )
} }
} }
try {
// If there is a next/previous image, fetch it into the image cache
val nextIndex = position + if (isNext) 1 else -1
if (nextIndex in pager.indices) {
pager.getBlocking(nextIndex)?.let { nextImage ->
if (nextImage.data.mediaType == MediaType.PHOTO) {
val url = api.libraryApi.getDownloadUrl(nextImage.id)
val request =
ImageRequest
.Builder(context)
.data(url)
.size(Size.ORIGINAL)
.build()
context.imageLoader.enqueue(request)
Timber.v(
"Prefetching index=%s, id=%s",
nextIndex,
nextImage.id,
)
}
}
}
} catch (ex: Exception) {
Timber.e(ex, "Error preloading image")
}
} }
} }
@ -339,7 +376,7 @@ class SlideshowViewModel
fun pulseSlideshow() = pulseSlideshow(slideshowDelay) fun pulseSlideshow() = pulseSlideshow(slideshowDelay)
fun pulseSlideshow(milliseconds: Long) { fun pulseSlideshow(milliseconds: Long) {
Timber.v("pulseSlideshow $milliseconds") // Timber.v("pulseSlideshow $milliseconds")
slideshowJob?.cancel() slideshowJob?.cancel()
slideshowJob = slideshowJob =
viewModelScope viewModelScope