From c92de3c33e0157a6d136c598f3a46f7ad1814512 Mon Sep 17 00:00:00 2001 From: Ray <154766448+damontecres@users.noreply.github.com> Date: Wed, 25 Mar 2026 21:55:00 -0400 Subject: [PATCH] Fix some UI preference changes not being reflected immediately (#1146) --- .../damontecres/wholphin/MainContent.kt | 6 ++---- .../damontecres/wholphin/ui/Extensions.kt | 7 +++++++ .../ui/detail/episode/EpisodeDetails.kt | 6 ++++-- .../ui/detail/episode/EpisodeViewModel.kt | 21 ++++++++++++++++--- .../wholphin/ui/detail/movie/MovieDetails.kt | 5 +++-- .../ui/detail/movie/MovieViewModel.kt | 19 ++++++++++++++--- 6 files changed, 50 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/github/damontecres/wholphin/MainContent.kt b/app/src/main/java/com/github/damontecres/wholphin/MainContent.kt index 0b32b9ed..0794da67 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/MainContent.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/MainContent.kt @@ -12,6 +12,7 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -45,6 +46,7 @@ fun MainContent( screensaverService: ScreensaverService, modifier: Modifier = Modifier, ) { + val preferences by rememberUpdatedState(UserPreferences(appPreferences)) Surface( modifier = modifier @@ -93,10 +95,6 @@ fun MainContent( backdropService.clearBackdrop() } val current = key.current - val preferences = - remember(appPreferences) { - UserPreferences(appPreferences) - } var showContent by remember { mutableStateOf(true) } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/Extensions.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/Extensions.kt index 865c6d29..a42d027f 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/Extensions.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/Extensions.kt @@ -38,6 +38,8 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.acra.ACRA @@ -434,3 +436,8 @@ fun Response.toBaseItems( fun Int?.gt(that: Int) = (this ?: 0) > that fun Int?.lt(that: Int) = (this ?: 0) < that + +/** + * Easy way to combine two flows into a [Pair] + */ +fun Flow.combinePair(flow: Flow): Flow> = combine(flow) { t1, t2 -> Pair(t1, t2) } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/episode/EpisodeDetails.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/episode/EpisodeDetails.kt index e341923b..a89db27b 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/episode/EpisodeDetails.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/episode/EpisodeDetails.kt @@ -11,6 +11,7 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.relocation.BringIntoViewRequester import androidx.compose.foundation.relocation.bringIntoViewRequester import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf @@ -85,6 +86,7 @@ fun EpisodeDetails( var showPlaylistDialog by remember { mutableStateOf>(Optional.absent()) } var showDeleteDialog by remember { mutableStateOf(null) } val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending) + val canDelete by viewModel.canDelete.collectAsState() val preferredSubtitleLanguage = viewModel.serverRepository.currentUserDto @@ -226,7 +228,7 @@ fun EpisodeDetails( onClearChosenStreams = { viewModel.clearChosenStreams(chosenStreams) }, - canDelete = viewModel.canDelete, + canDelete = canDelete, ), ) }, @@ -236,7 +238,7 @@ fun EpisodeDetails( favoriteOnClick = { viewModel.setFavorite(ep.id, !ep.favorite) }, - canDelete = viewModel.canDelete, + canDelete = canDelete, deleteOnClick = { showDeleteDialog = ep }, modifier = modifier, ) diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/episode/EpisodeViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/episode/EpisodeViewModel.kt index 9b8bda5c..df3b180a 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/episode/EpisodeViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/episode/EpisodeViewModel.kt @@ -3,6 +3,7 @@ package com.github.damontecres.wholphin.ui.detail.episode import android.content.Context import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.asFlow import androidx.lifecycle.viewModelScope import com.github.damontecres.wholphin.data.ChosenStreams import com.github.damontecres.wholphin.data.ItemPlaybackRepository @@ -19,6 +20,8 @@ import com.github.damontecres.wholphin.services.StreamChoiceService import com.github.damontecres.wholphin.services.ThemeSongPlayer import com.github.damontecres.wholphin.services.UserPreferencesService import com.github.damontecres.wholphin.services.deleteItem +import com.github.damontecres.wholphin.ui.combinePair +import com.github.damontecres.wholphin.ui.launchDefault import com.github.damontecres.wholphin.ui.launchIO import com.github.damontecres.wholphin.ui.nav.Destination import com.github.damontecres.wholphin.ui.setValueOnMain @@ -34,6 +37,11 @@ import kotlinx.coroutines.Deferred import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.async +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.jellyfin.sdk.api.client.ApiClient @@ -68,11 +76,19 @@ class EpisodeViewModel val item = MutableLiveData(null) val chosenStreams = MutableLiveData(null) - var canDelete: Boolean = false - private set + val canDelete = MutableStateFlow(false) init { init() + viewModelScope.launchDefault { + item + .asFlow() + .filterNotNull() + .combinePair(userPreferencesService.flow.map { it.appPreferences }) + .collectLatest { (item, preferences) -> + canDelete.update { mediaManagementService.canDelete(item, preferences) } + } + } } private fun fetchAndSetItem(): Deferred = @@ -101,7 +117,6 @@ class EpisodeViewModel ) { val prefs = userPreferencesService.getCurrent() val item = fetchAndSetItem().await() - canDelete = mediaManagementService.canDelete(item) val result = itemPlaybackRepository.getSelectedTracks(item.id, item, prefs) withContext(Dispatchers.Main) { this@EpisodeViewModel.item.value = item diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/movie/MovieDetails.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/movie/MovieDetails.kt index 87a5dd75..4195e9a8 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/movie/MovieDetails.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/movie/MovieDetails.kt @@ -106,6 +106,7 @@ fun MovieDetails( val loading by viewModel.loading.observeAsState(LoadingState.Loading) val chosenStreams by viewModel.chosenStreams.observeAsState(null) val discovered by viewModel.discovered.collectAsState() + val canDelete by viewModel.canDelete.collectAsState() var overviewDialog by remember { mutableStateOf(null) } var moreDialog by remember { mutableStateOf(null) } @@ -211,7 +212,7 @@ fun MovieDetails( seriesId = null, sourceId = chosenStreams?.source?.id?.toUUIDOrNull(), canClearChosenStreams = chosenStreams?.itemPlayback != null || chosenStreams?.plc != null, - canDelete = viewModel.canDelete, + canDelete = canDelete, actions = moreActions, onChooseVersion = { chooseVersion = @@ -323,7 +324,7 @@ fun MovieDetails( onClickDiscover = { index, item -> viewModel.navigateTo(item.destination) }, - canDelete = viewModel.canDelete, + canDelete = canDelete, deleteOnClick = { showDeleteDialog = movie }, modifier = modifier, ) diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/movie/MovieViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/movie/MovieViewModel.kt index 993260dc..131c0cd6 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/movie/MovieViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/movie/MovieViewModel.kt @@ -3,6 +3,7 @@ package com.github.damontecres.wholphin.ui.detail.movie import android.content.Context import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.asFlow import androidx.lifecycle.viewModelScope import com.github.damontecres.wholphin.data.ChosenStreams import com.github.damontecres.wholphin.data.ExtrasItem @@ -29,6 +30,8 @@ import com.github.damontecres.wholphin.services.TrailerService import com.github.damontecres.wholphin.services.UserPreferencesService import com.github.damontecres.wholphin.services.deleteItem import com.github.damontecres.wholphin.ui.SlimItemFields +import com.github.damontecres.wholphin.ui.combinePair +import com.github.damontecres.wholphin.ui.launchDefault import com.github.damontecres.wholphin.ui.launchIO import com.github.damontecres.wholphin.ui.letNotEmpty import com.github.damontecres.wholphin.ui.nav.Destination @@ -46,6 +49,9 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.async import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -93,11 +99,19 @@ class MovieViewModel val chosenStreams = MutableLiveData(null) val discovered = MutableStateFlow>(listOf()) - var canDelete: Boolean = false - private set + val canDelete = MutableStateFlow(false) init { init() + viewModelScope.launchDefault { + item + .asFlow() + .filterNotNull() + .combinePair(userPreferencesService.flow.map { it.appPreferences }) + .collectLatest { (item, preferences) -> + canDelete.update { mediaManagementService.canDelete(item, preferences) } + } + } } private fun fetchAndSetItem(): Deferred = @@ -112,7 +126,6 @@ class MovieViewModel api.userLibraryApi.getItem(itemId).content.let { BaseItem.from(it, api) } - canDelete = mediaManagementService.canDelete(item) this@MovieViewModel.item.setValueOnMain(item) item }