From 8107a82a6b3ef220ef79ab39145df68bc71a5c45 Mon Sep 17 00:00:00 2001 From: Damontecres Date: Wed, 6 May 2026 11:49:27 -0400 Subject: [PATCH] Fixes/improves showing delete button (#1353) ## Description Fixes the delete button not showing on the movie detail page. Also fixes some edge cases where button may not appear when toggling on the media management setting. Adds the delete button to album & artist pages. Previously, it was only in the context menu. ### Related issues Fixes #1343 ### Testing Emulator ## Screenshots N/A ## AI or LLM usage None --- .../wholphin/services/MediaManagementService.kt | 13 +++++++++++++ .../ui/detail/collection/CollectionDetails.kt | 5 ++++- .../ui/detail/episode/EpisodeViewModel.kt | 14 +++----------- .../wholphin/ui/detail/movie/MovieViewModel.kt | 15 +++------------ .../wholphin/ui/detail/music/AlbumDetailsPage.kt | 12 ++++++++++++ .../wholphin/ui/detail/music/ArtistDetailsPage.kt | 11 +++++++++++ .../ui/detail/music/MusicExpandableButtons.kt | 13 +++++++++++++ .../wholphin/ui/detail/series/SeriesViewModel.kt | 7 ++++++- 8 files changed, 65 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/MediaManagementService.kt b/app/src/main/java/com/github/damontecres/wholphin/services/MediaManagementService.kt index beab6e64..5a6d8ff0 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/MediaManagementService.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/MediaManagementService.kt @@ -6,12 +6,15 @@ import androidx.lifecycle.viewModelScope import com.github.damontecres.wholphin.data.ServerRepository import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.preferences.AppPreferences +import com.github.damontecres.wholphin.ui.combinePair import com.github.damontecres.wholphin.ui.launchIO import com.github.damontecres.wholphin.ui.showToast import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.collectLatest import org.jellyfin.sdk.api.client.ApiClient import org.jellyfin.sdk.api.client.extensions.libraryApi import org.jellyfin.sdk.model.api.BaseItemKind @@ -68,6 +71,16 @@ class MediaManagementService } } + suspend fun collectCanDelete( + itemFlow: Flow, + update: (Boolean) -> Unit, + ) { + itemFlow.combinePair(userPreferencesService.flow).collectLatest { (item, prefs) -> + val canDelete = item?.let { canDelete(item, prefs.appPreferences) } ?: false + update.invoke(canDelete) + } + } + /** * Delete the item. * diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/collection/CollectionDetails.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/collection/CollectionDetails.kt index 332e2a3f..4da00ef7 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/collection/CollectionDetails.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/collection/CollectionDetails.kt @@ -207,7 +207,10 @@ fun CollectionDetails( } }, canDelete = - remember(state.collection) { + remember( + state.collection, + preferences.appPreferences.interfacePreferences.enableMediaManagement, + ) { state.collection?.let { viewModel.canDelete(it, preferences.appPreferences) } ?: false 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 df3b180a..2110a3a5 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 @@ -20,7 +20,6 @@ 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 @@ -38,9 +37,6 @@ 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 @@ -81,13 +77,9 @@ class EpisodeViewModel init { init() viewModelScope.launchDefault { - item - .asFlow() - .filterNotNull() - .combinePair(userPreferencesService.flow.map { it.appPreferences }) - .collectLatest { (item, preferences) -> - canDelete.update { mediaManagementService.canDelete(item, preferences) } - } + mediaManagementService.collectCanDelete(item.asFlow()) { canDelete -> + this@EpisodeViewModel.canDelete.update { canDelete } + } } } 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 82518409..bd2cc373 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 @@ -42,7 +42,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.update import org.jellyfin.sdk.api.client.ApiClient import org.jellyfin.sdk.api.client.extensions.libraryApi @@ -85,18 +85,9 @@ class MovieViewModel init { init() viewModelScope.launchDefault { - userPreferencesService.flow.collectLatest { preferences -> + mediaManagementService.collectCanDelete(state.map { it.movie }) { canDelete -> _state.update { - val canDelete = - it.movie?.let { - mediaManagementService.canDelete( - it, - preferences.appPreferences, - ) - } - it.copy( - canDelete = canDelete ?: false, - ) + it.copy(canDelete = canDelete) } } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/music/AlbumDetailsPage.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/music/AlbumDetailsPage.kt index 173ba49b..8d374a0c 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/music/AlbumDetailsPage.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/music/AlbumDetailsPage.kt @@ -94,6 +94,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import org.jellyfin.sdk.api.client.ApiClient @@ -136,6 +137,11 @@ class AlbumViewModel init { init() + viewModelScope.launchDefault { + mediaManagementService.collectCanDelete(state.map { it.album }) { canDelete -> + _state.update { it.copy(canDelete = canDelete) } + } + } } override fun init() { @@ -275,6 +281,7 @@ data class AlbumState( val similar: List, val loading: LoadingState, val musicVideos: List = emptyList(), + val canDelete: Boolean = false, ) { companion object { val EMPTY = AlbumState(null, false, null, emptyList(), emptyList(), LoadingState.Pending) @@ -389,6 +396,7 @@ fun AlbumDetailsPage( modifier = Modifier.fillMaxWidth(), ) MusicExpandableButtons( + title = album.title ?: "", actions = remember { MusicButtonActions( @@ -415,9 +423,13 @@ fun AlbumDetailsPage( actions = moreDialogActions, ) }, + onConfirmDelete = { + viewModel.deleteItem(album) + }, ) }, favorite = album.favorite, + canDelete = state.canDelete, buttonOnFocusChanged = { if (it.isFocused) { position = RowColumn(HEADER_ROW, 0) diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/music/ArtistDetailsPage.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/music/ArtistDetailsPage.kt index 77f2e063..1f693322 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/music/ArtistDetailsPage.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/music/ArtistDetailsPage.kt @@ -92,6 +92,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import org.jellyfin.sdk.api.client.ApiClient @@ -135,6 +136,11 @@ class ArtistViewModel init { init() + viewModelScope.launchDefault { + mediaManagementService.collectCanDelete(state.map { it.artist }) { canDelete -> + _state.update { it.copy(canDelete = canDelete) } + } + } } override fun init() { @@ -269,6 +275,7 @@ data class ArtistState( val similar: List, val loading: LoadingState, val musicVideos: List, + val canDelete: Boolean, ) { companion object { val EMPTY = @@ -280,6 +287,7 @@ data class ArtistState( emptyList(), LoadingState.Pending, emptyList(), + false, ) } } @@ -378,6 +386,7 @@ fun ArtistDetailsPage( modifier = Modifier.fillMaxWidth(), ) MusicExpandableButtons( + title = artist.title ?: "", actions = remember { MusicButtonActions( @@ -406,9 +415,11 @@ fun ArtistDetailsPage( actions = moreDialogActions, ) }, + onConfirmDelete = { viewModel.deleteItem(artist) }, ) }, favorite = artist.favorite, + canDelete = state.canDelete, buttonOnFocusChanged = { if (it.isFocused) { position = RowColumn(HEADER_ROW, 0) diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/music/MusicExpandableButtons.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/music/MusicExpandableButtons.kt index 78a5603b..b93483c9 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/music/MusicExpandableButtons.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/music/MusicExpandableButtons.kt @@ -18,14 +18,17 @@ import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import com.github.damontecres.wholphin.R +import com.github.damontecres.wholphin.ui.components.DeleteButton import com.github.damontecres.wholphin.ui.components.ExpandableFaButton import com.github.damontecres.wholphin.ui.components.ExpandablePlayButton import kotlin.time.Duration @Composable fun MusicExpandableButtons( + title: String, actions: MusicButtonActions, favorite: Boolean, + canDelete: Boolean, buttonOnFocusChanged: (FocusState) -> Unit, modifier: Modifier = Modifier, ) { @@ -75,6 +78,15 @@ fun MusicExpandableButtons( modifier = Modifier.onFocusChanged(buttonOnFocusChanged), ) } + if (canDelete) { + item("delete") { + DeleteButton( + title = title, + onConfirmDelete = actions.onConfirmDelete, + modifier = Modifier.onFocusChanged(buttonOnFocusChanged), + ) + } + } item("more") { ExpandablePlayButton( title = R.string.more, @@ -92,4 +104,5 @@ data class MusicButtonActions( val onClickInstantMix: () -> Unit, val onClickFavorite: () -> Unit, val onClickMore: () -> Unit, + val onConfirmDelete: () -> Unit, ) diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesViewModel.kt index c84f9b8d..4d0ce9e2 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesViewModel.kt @@ -59,6 +59,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.update @@ -138,7 +139,11 @@ class SeriesViewModel Timber.v("Start") addCloseable { themeSongPlayer.stop() } val item = fetchItem(seriesId) - canDeleteSeries.update { mediaManagementService.canDelete(item) } + viewModelScope.launchDefault { + mediaManagementService.collectCanDelete(flowOf(item)) { canDelete -> + canDeleteSeries.update { canDelete } + } + } backdropService.submit(item) val seasonsDeferred = getSeasons(item, seasonEpisodeIds?.seasonNumber)