mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
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:
parent
461bad1ddd
commit
8107a82a6b
8 changed files with 65 additions and 25 deletions
|
|
@ -6,12 +6,15 @@ import androidx.lifecycle.viewModelScope
|
||||||
import com.github.damontecres.wholphin.data.ServerRepository
|
import com.github.damontecres.wholphin.data.ServerRepository
|
||||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||||
import com.github.damontecres.wholphin.preferences.AppPreferences
|
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.launchIO
|
||||||
import com.github.damontecres.wholphin.ui.showToast
|
import com.github.damontecres.wholphin.ui.showToast
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import kotlinx.coroutines.channels.BufferOverflow
|
import kotlinx.coroutines.channels.BufferOverflow
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.SharedFlow
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import org.jellyfin.sdk.api.client.ApiClient
|
import org.jellyfin.sdk.api.client.ApiClient
|
||||||
import org.jellyfin.sdk.api.client.extensions.libraryApi
|
import org.jellyfin.sdk.api.client.extensions.libraryApi
|
||||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
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.
|
* Delete the item.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -207,7 +207,10 @@ fun CollectionDetails(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
canDelete =
|
canDelete =
|
||||||
remember(state.collection) {
|
remember(
|
||||||
|
state.collection,
|
||||||
|
preferences.appPreferences.interfacePreferences.enableMediaManagement,
|
||||||
|
) {
|
||||||
state.collection?.let {
|
state.collection?.let {
|
||||||
viewModel.canDelete(it, preferences.appPreferences)
|
viewModel.canDelete(it, preferences.appPreferences)
|
||||||
} ?: false
|
} ?: false
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ import com.github.damontecres.wholphin.services.StreamChoiceService
|
||||||
import com.github.damontecres.wholphin.services.ThemeSongPlayer
|
import com.github.damontecres.wholphin.services.ThemeSongPlayer
|
||||||
import com.github.damontecres.wholphin.services.UserPreferencesService
|
import com.github.damontecres.wholphin.services.UserPreferencesService
|
||||||
import com.github.damontecres.wholphin.services.deleteItem
|
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.launchDefault
|
||||||
import com.github.damontecres.wholphin.ui.launchIO
|
import com.github.damontecres.wholphin.ui.launchIO
|
||||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||||
|
|
@ -38,9 +37,6 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
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.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
@ -81,12 +77,8 @@ class EpisodeViewModel
|
||||||
init {
|
init {
|
||||||
init()
|
init()
|
||||||
viewModelScope.launchDefault {
|
viewModelScope.launchDefault {
|
||||||
item
|
mediaManagementService.collectCanDelete(item.asFlow()) { canDelete ->
|
||||||
.asFlow()
|
this@EpisodeViewModel.canDelete.update { canDelete }
|
||||||
.filterNotNull()
|
|
||||||
.combinePair(userPreferencesService.flow.map { it.appPreferences })
|
|
||||||
.collectLatest { (item, preferences) ->
|
|
||||||
canDelete.update { mediaManagementService.canDelete(item, preferences) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import org.jellyfin.sdk.api.client.ApiClient
|
import org.jellyfin.sdk.api.client.ApiClient
|
||||||
import org.jellyfin.sdk.api.client.extensions.libraryApi
|
import org.jellyfin.sdk.api.client.extensions.libraryApi
|
||||||
|
|
@ -85,18 +85,9 @@ class MovieViewModel
|
||||||
init {
|
init {
|
||||||
init()
|
init()
|
||||||
viewModelScope.launchDefault {
|
viewModelScope.launchDefault {
|
||||||
userPreferencesService.flow.collectLatest { preferences ->
|
mediaManagementService.collectCanDelete(state.map { it.movie }) { canDelete ->
|
||||||
_state.update {
|
_state.update {
|
||||||
val canDelete =
|
it.copy(canDelete = canDelete)
|
||||||
it.movie?.let {
|
|
||||||
mediaManagementService.canDelete(
|
|
||||||
it,
|
|
||||||
preferences.appPreferences,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
it.copy(
|
|
||||||
canDelete = canDelete ?: false,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,7 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.jellyfin.sdk.api.client.ApiClient
|
import org.jellyfin.sdk.api.client.ApiClient
|
||||||
|
|
@ -136,6 +137,11 @@ class AlbumViewModel
|
||||||
|
|
||||||
init {
|
init {
|
||||||
init()
|
init()
|
||||||
|
viewModelScope.launchDefault {
|
||||||
|
mediaManagementService.collectCanDelete(state.map { it.album }) { canDelete ->
|
||||||
|
_state.update { it.copy(canDelete = canDelete) }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun init() {
|
override fun init() {
|
||||||
|
|
@ -275,6 +281,7 @@ data class AlbumState(
|
||||||
val similar: List<BaseItem>,
|
val similar: List<BaseItem>,
|
||||||
val loading: LoadingState,
|
val loading: LoadingState,
|
||||||
val musicVideos: List<BaseItem?> = emptyList(),
|
val musicVideos: List<BaseItem?> = emptyList(),
|
||||||
|
val canDelete: Boolean = false,
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
val EMPTY = AlbumState(null, false, null, emptyList(), emptyList(), LoadingState.Pending)
|
val EMPTY = AlbumState(null, false, null, emptyList(), emptyList(), LoadingState.Pending)
|
||||||
|
|
@ -389,6 +396,7 @@ fun AlbumDetailsPage(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
MusicExpandableButtons(
|
MusicExpandableButtons(
|
||||||
|
title = album.title ?: "",
|
||||||
actions =
|
actions =
|
||||||
remember {
|
remember {
|
||||||
MusicButtonActions(
|
MusicButtonActions(
|
||||||
|
|
@ -415,9 +423,13 @@ fun AlbumDetailsPage(
|
||||||
actions = moreDialogActions,
|
actions = moreDialogActions,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
onConfirmDelete = {
|
||||||
|
viewModel.deleteItem(album)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
favorite = album.favorite,
|
favorite = album.favorite,
|
||||||
|
canDelete = state.canDelete,
|
||||||
buttonOnFocusChanged = {
|
buttonOnFocusChanged = {
|
||||||
if (it.isFocused) {
|
if (it.isFocused) {
|
||||||
position = RowColumn(HEADER_ROW, 0)
|
position = RowColumn(HEADER_ROW, 0)
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,7 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.jellyfin.sdk.api.client.ApiClient
|
import org.jellyfin.sdk.api.client.ApiClient
|
||||||
|
|
@ -135,6 +136,11 @@ class ArtistViewModel
|
||||||
|
|
||||||
init {
|
init {
|
||||||
init()
|
init()
|
||||||
|
viewModelScope.launchDefault {
|
||||||
|
mediaManagementService.collectCanDelete(state.map { it.artist }) { canDelete ->
|
||||||
|
_state.update { it.copy(canDelete = canDelete) }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun init() {
|
override fun init() {
|
||||||
|
|
@ -269,6 +275,7 @@ data class ArtistState(
|
||||||
val similar: List<BaseItem>,
|
val similar: List<BaseItem>,
|
||||||
val loading: LoadingState,
|
val loading: LoadingState,
|
||||||
val musicVideos: List<BaseItem?>,
|
val musicVideos: List<BaseItem?>,
|
||||||
|
val canDelete: Boolean,
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
val EMPTY =
|
val EMPTY =
|
||||||
|
|
@ -280,6 +287,7 @@ data class ArtistState(
|
||||||
emptyList(),
|
emptyList(),
|
||||||
LoadingState.Pending,
|
LoadingState.Pending,
|
||||||
emptyList(),
|
emptyList(),
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -378,6 +386,7 @@ fun ArtistDetailsPage(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
MusicExpandableButtons(
|
MusicExpandableButtons(
|
||||||
|
title = artist.title ?: "",
|
||||||
actions =
|
actions =
|
||||||
remember {
|
remember {
|
||||||
MusicButtonActions(
|
MusicButtonActions(
|
||||||
|
|
@ -406,9 +415,11 @@ fun ArtistDetailsPage(
|
||||||
actions = moreDialogActions,
|
actions = moreDialogActions,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
onConfirmDelete = { viewModel.deleteItem(artist) },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
favorite = artist.favorite,
|
favorite = artist.favorite,
|
||||||
|
canDelete = state.canDelete,
|
||||||
buttonOnFocusChanged = {
|
buttonOnFocusChanged = {
|
||||||
if (it.isFocused) {
|
if (it.isFocused) {
|
||||||
position = RowColumn(HEADER_ROW, 0)
|
position = RowColumn(HEADER_ROW, 0)
|
||||||
|
|
|
||||||
|
|
@ -18,14 +18,17 @@ import androidx.compose.ui.focus.onFocusChanged
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.github.damontecres.wholphin.R
|
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.ExpandableFaButton
|
||||||
import com.github.damontecres.wholphin.ui.components.ExpandablePlayButton
|
import com.github.damontecres.wholphin.ui.components.ExpandablePlayButton
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MusicExpandableButtons(
|
fun MusicExpandableButtons(
|
||||||
|
title: String,
|
||||||
actions: MusicButtonActions,
|
actions: MusicButtonActions,
|
||||||
favorite: Boolean,
|
favorite: Boolean,
|
||||||
|
canDelete: Boolean,
|
||||||
buttonOnFocusChanged: (FocusState) -> Unit,
|
buttonOnFocusChanged: (FocusState) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
|
|
@ -75,6 +78,15 @@ fun MusicExpandableButtons(
|
||||||
modifier = Modifier.onFocusChanged(buttonOnFocusChanged),
|
modifier = Modifier.onFocusChanged(buttonOnFocusChanged),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (canDelete) {
|
||||||
|
item("delete") {
|
||||||
|
DeleteButton(
|
||||||
|
title = title,
|
||||||
|
onConfirmDelete = actions.onConfirmDelete,
|
||||||
|
modifier = Modifier.onFocusChanged(buttonOnFocusChanged),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
item("more") {
|
item("more") {
|
||||||
ExpandablePlayButton(
|
ExpandablePlayButton(
|
||||||
title = R.string.more,
|
title = R.string.more,
|
||||||
|
|
@ -92,4 +104,5 @@ data class MusicButtonActions(
|
||||||
val onClickInstantMix: () -> Unit,
|
val onClickInstantMix: () -> Unit,
|
||||||
val onClickFavorite: () -> Unit,
|
val onClickFavorite: () -> Unit,
|
||||||
val onClickMore: () -> Unit,
|
val onClickMore: () -> Unit,
|
||||||
|
val onConfirmDelete: () -> Unit,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,7 @@ import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.catch
|
import kotlinx.coroutines.flow.catch
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
|
|
@ -138,7 +139,11 @@ class SeriesViewModel
|
||||||
Timber.v("Start")
|
Timber.v("Start")
|
||||||
addCloseable { themeSongPlayer.stop() }
|
addCloseable { themeSongPlayer.stop() }
|
||||||
val item = fetchItem(seriesId)
|
val item = fetchItem(seriesId)
|
||||||
canDeleteSeries.update { mediaManagementService.canDelete(item) }
|
viewModelScope.launchDefault {
|
||||||
|
mediaManagementService.collectCanDelete(flowOf(item)) { canDelete ->
|
||||||
|
canDeleteSeries.update { canDelete }
|
||||||
|
}
|
||||||
|
}
|
||||||
backdropService.submit(item)
|
backdropService.submit(item)
|
||||||
|
|
||||||
val seasonsDeferred = getSeasons(item, seasonEpisodeIds?.seasonNumber)
|
val seasonsDeferred = getSeasons(item, seasonEpisodeIds?.seasonNumber)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue