feat: add pull-to-refresh gesture to HomeScreen

Wrap HomeContent with PullToRefreshBox to allow refreshing library,
continue watching, and next up sections by pulling down. Also fix
refreshHomeData to suspend until loading completes so the refresh
indicator dismisses properly.
This commit is contained in:
2026-03-03 21:18:50 +01:00
parent cc972e0e89
commit 5ca127434d
4 changed files with 38 additions and 2 deletions

View File

@@ -47,6 +47,7 @@ fun HomePage(
val continueWatching = viewModel.continueWatching.collectAsState()
val nextUp = viewModel.nextUp.collectAsState()
val latestLibraryContent = viewModel.latestLibraryContent.collectAsState()
val isRefreshing = viewModel.isRefreshing.collectAsState()
LifecycleResumeEffect(Unit) {
viewModel.onResumed()
@@ -89,6 +90,8 @@ fun HomePage(
libraryContent = latestLibraryContent.value,
continueWatching = continueWatching.value,
nextUp = nextUp.value,
isRefreshing = isRefreshing.value,
onRefresh = viewModel::onRefresh,
onMovieSelected = viewModel::onMovieSelected,
onSeriesSelected = viewModel::onSeriesSelected,
onEpisodeSelected = viewModel::onEpisodeSelected,

View File

@@ -6,7 +6,9 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@@ -16,21 +18,29 @@ import hu.bbara.purefin.feature.shared.home.NextUpItem
import hu.bbara.purefin.feature.shared.home.PosterItem
import org.jellyfin.sdk.model.UUID
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun HomeContent(
libraries: List<LibraryItem>,
libraryContent: Map<UUID, List<PosterItem>>,
continueWatching: List<ContinueWatchingItem>,
nextUp: List<NextUpItem>,
isRefreshing: Boolean,
onRefresh: () -> Unit,
onMovieSelected: (UUID) -> Unit,
onSeriesSelected: (UUID) -> Unit,
onEpisodeSelected: (UUID, UUID, UUID) -> Unit,
modifier: Modifier = Modifier
) {
LazyColumn(
PullToRefreshBox(
isRefreshing = isRefreshing,
onRefresh = onRefresh,
modifier = modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.background)
) {
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
item {
Spacer(modifier = Modifier.height(8.dp))
@@ -70,3 +80,4 @@ fun HomeContent(
}
}
}
}

View File

@@ -297,15 +297,18 @@ class InMemoryAppContentRepository @Inject constructor(
if (!isOnline) return
if(loadJob?.isActive == true) {
loadJob?.join()
return
}
loadJob = scope.launch {
val job = scope.launch {
loadLibraries()
loadContinueWatching()
loadNextUp()
loadLatestLibraryContent()
persistHomeCache()
}
loadJob = job
job.join()
}
private suspend fun serverUrl(): String {

View File

@@ -13,7 +13,10 @@ import hu.bbara.purefin.core.data.navigation.Route
import hu.bbara.purefin.core.data.navigation.SeriesDto
import hu.bbara.purefin.core.data.session.UserSessionRepository
import hu.bbara.purefin.core.model.Media
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
@@ -31,6 +34,9 @@ class HomePageViewModel @Inject constructor(
private val refreshHomeDataUseCase: RefreshHomeDataUseCase
) : ViewModel() {
private val _isRefreshing = MutableStateFlow(false)
val isRefreshing: StateFlow<Boolean> = _isRefreshing.asStateFlow()
private val _url = userSessionRepository.serverUrl.stateIn(
scope = viewModelScope,
started = SharingStarted.Eagerly,
@@ -195,6 +201,19 @@ class HomePageViewModel @Inject constructor(
}
}
fun onRefresh() {
viewModelScope.launch {
_isRefreshing.value = true
try {
refreshHomeDataUseCase()
} catch (e: Exception) {
// Refresh is best-effort; don't crash on failure
} finally {
_isRefreshing.value = false
}
}
}
fun logout() {
viewModelScope.launch {
userSessionRepository.setLoggedIn(false)