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
This commit is contained in:
Damontecres 2026-05-06 11:49:27 -04:00 committed by GitHub
parent 461bad1ddd
commit 8107a82a6b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 65 additions and 25 deletions

View file

@ -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<BaseItem?>,
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.
*

View file

@ -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

View file

@ -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 }
}
}
}

View file

@ -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)
}
}
}

View file

@ -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<BaseItem>,
val loading: LoadingState,
val musicVideos: List<BaseItem?> = 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)

View file

@ -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<BaseItem>,
val loading: LoadingState,
val musicVideos: List<BaseItem?>,
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)

View file

@ -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,
)

View file

@ -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)