mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 23:51:21 +02:00
Refactor context menu code (#1302)
## Description This PR refactors most of the context menu dialog code moving more of the display into a Composable. This then allows for embedding the confirm delete or choose version sub-dialogs. This will allow for easier future expansion of the context menu. Now instead of the page using a combination of `DiaglogPopup` & `buildMoreFor` functions, pages create a `ContextMenu` object and pass it to a composable. There's no significant user facing changes. ### Related issues None ### Testing Emulator ## Screenshots N/A ## AI or LLM usage None
This commit is contained in:
parent
a159526296
commit
c718d903a4
25 changed files with 1707 additions and 1647 deletions
|
|
@ -66,6 +66,7 @@ import com.github.damontecres.wholphin.services.MediaManagementService
|
|||
import com.github.damontecres.wholphin.services.MediaReportService
|
||||
import com.github.damontecres.wholphin.services.MusicService
|
||||
import com.github.damontecres.wholphin.services.NavigationManager
|
||||
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
|
||||
|
|
@ -74,13 +75,13 @@ import com.github.damontecres.wholphin.ui.RequestOrRestoreFocus
|
|||
import com.github.damontecres.wholphin.ui.SlimItemFields
|
||||
import com.github.damontecres.wholphin.ui.cards.GridCard
|
||||
import com.github.damontecres.wholphin.ui.data.AddPlaylistViewModel
|
||||
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialog
|
||||
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialogInfo
|
||||
import com.github.damontecres.wholphin.ui.data.SortAndDirection
|
||||
import com.github.damontecres.wholphin.ui.detail.CardGrid
|
||||
import com.github.damontecres.wholphin.ui.detail.ItemViewModel
|
||||
import com.github.damontecres.wholphin.ui.detail.MoreDialogActions
|
||||
import com.github.damontecres.wholphin.ui.detail.PlaylistDialog
|
||||
import com.github.damontecres.wholphin.ui.detail.PlaylistLoadingState
|
||||
import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItemsForHome
|
||||
import com.github.damontecres.wholphin.ui.detail.music.addToQueue
|
||||
import com.github.damontecres.wholphin.ui.equalsNotNull
|
||||
import com.github.damontecres.wholphin.ui.launchDefault
|
||||
|
|
@ -133,7 +134,7 @@ class CollectionFolderViewModel
|
|||
private val savedStateHandle: SavedStateHandle,
|
||||
api: ApiClient,
|
||||
@param:ApplicationContext private val context: Context,
|
||||
private val serverRepository: ServerRepository,
|
||||
val serverRepository: ServerRepository,
|
||||
private val libraryDisplayInfoDao: LibraryDisplayInfoDao,
|
||||
private val favoriteWatchManager: FavoriteWatchManager,
|
||||
private val backdropService: BackdropService,
|
||||
|
|
@ -142,6 +143,7 @@ class CollectionFolderViewModel
|
|||
private val userPreferencesService: UserPreferencesService,
|
||||
private val mediaManagementService: MediaManagementService,
|
||||
private val musicService: MusicService,
|
||||
val streamChoiceService: StreamChoiceService,
|
||||
val mediaReportService: MediaReportService,
|
||||
@Assisted itemId: String,
|
||||
@Assisted initialSortAndDirection: SortAndDirection?,
|
||||
|
|
@ -662,18 +664,58 @@ fun CollectionFolderGrid(
|
|||
val item by viewModel.item.observeAsState()
|
||||
val viewOptions by viewModel.viewOptions.observeAsState(defaultViewOptions)
|
||||
|
||||
var moreDialog by remember { mutableStateOf<Optional<PositionItem>>(Optional.absent()) }
|
||||
var showContextMenu by remember { mutableStateOf<ContextMenu?>(null) }
|
||||
var overviewDialog by remember { mutableStateOf<ItemDetailsDialogInfo?>(null) }
|
||||
var showPlaylistDialog by remember { mutableStateOf<Optional<UUID>>(Optional.absent()) }
|
||||
var showDeleteDialog by remember { mutableStateOf<PositionItem?>(null) }
|
||||
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
||||
|
||||
val contextActions =
|
||||
remember {
|
||||
ContextMenuActions(
|
||||
navigateTo = viewModel::navigateTo,
|
||||
onClickWatch = { itemId, watched ->
|
||||
viewModel.setWatched(viewModel.position, itemId, watched)
|
||||
},
|
||||
onClickFavorite = { itemId, favorite ->
|
||||
viewModel.setFavorite(viewModel.position, itemId, favorite)
|
||||
},
|
||||
onClickAddPlaylist = { itemId ->
|
||||
playlistViewModel.loadPlaylists(MediaType.VIDEO)
|
||||
showPlaylistDialog.makePresent(itemId)
|
||||
},
|
||||
onSendMediaInfo = viewModel.mediaReportService::sendReportFor,
|
||||
onDeleteItem = { viewModel.deleteItem(viewModel.position, it) },
|
||||
onShowOverview = { overviewDialog = ItemDetailsDialogInfo(it) },
|
||||
onChooseVersion = { _, _ ->
|
||||
// Not supported on this page
|
||||
},
|
||||
onChooseTracks = { result ->
|
||||
// Not supported on this page
|
||||
},
|
||||
onClearChosenStreams = {
|
||||
// Not supported on this page
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
val gridActions =
|
||||
remember(actions) {
|
||||
GridClickActions(
|
||||
onClickItem = actions.onClickItem,
|
||||
onLongClickItem =
|
||||
actions.onLongClickItem ?: { position, item ->
|
||||
moreDialog.makePresent(PositionItem(position, item))
|
||||
showContextMenu =
|
||||
ContextMenu.ForBaseItem(
|
||||
fromLongClick = true,
|
||||
item = item,
|
||||
chosenStreams = null,
|
||||
showGoTo = true,
|
||||
showStreamChoices = false,
|
||||
canDelete = viewModel.canDelete(item, preferences.appPreferences),
|
||||
canRemoveContinueWatching = false,
|
||||
canRemoveNextUp = false,
|
||||
actions = contextActions,
|
||||
)
|
||||
},
|
||||
onClickPlayAll =
|
||||
actions.onClickPlayAll ?: { shuffle ->
|
||||
|
|
@ -802,47 +844,22 @@ fun CollectionFolderGrid(
|
|||
}
|
||||
}
|
||||
}
|
||||
moreDialog.compose { (position, item) ->
|
||||
DialogPopup(
|
||||
showDialog = true,
|
||||
title = item.title ?: "",
|
||||
dialogItems =
|
||||
buildMoreDialogItemsForHome(
|
||||
context = context,
|
||||
item = item,
|
||||
seriesId = null,
|
||||
playbackPosition = item.playbackPosition,
|
||||
watched = item.played,
|
||||
favorite = item.favorite,
|
||||
canDelete = viewModel.canDelete(item, preferences.appPreferences),
|
||||
actions =
|
||||
MoreDialogActions(
|
||||
navigateTo = { viewModel.navigateTo(it) },
|
||||
onClickWatch = { itemId, watched ->
|
||||
viewModel.setWatched(position, itemId, watched)
|
||||
},
|
||||
onClickFavorite = { itemId, watched ->
|
||||
viewModel.setFavorite(position, itemId, watched)
|
||||
},
|
||||
onClickAddPlaylist = {
|
||||
playlistViewModel.loadPlaylists(MediaType.VIDEO)
|
||||
showPlaylistDialog.makePresent(it)
|
||||
},
|
||||
onSendMediaInfo = viewModel.mediaReportService::sendReportFor,
|
||||
onClickDelete = {
|
||||
showDeleteDialog = PositionItem(position, item)
|
||||
},
|
||||
onClickGoTo = {
|
||||
gridActions.onClickItem.invoke(position, it)
|
||||
},
|
||||
onClickAddToQueue = {
|
||||
viewModel.addToQueue(it, 0)
|
||||
},
|
||||
),
|
||||
),
|
||||
onDismissRequest = { moreDialog.makeAbsent() },
|
||||
dismissOnClick = true,
|
||||
waitToLoad = true,
|
||||
overviewDialog?.let { info ->
|
||||
ItemDetailsDialog(
|
||||
info = info,
|
||||
showFilePath =
|
||||
viewModel.serverRepository.currentUserDto.value
|
||||
?.policy
|
||||
?.isAdministrator == true,
|
||||
onDismissRequest = { overviewDialog = null },
|
||||
)
|
||||
}
|
||||
showContextMenu?.let { contextMenu ->
|
||||
ContextMenuDialog(
|
||||
onDismissRequest = { showContextMenu = null },
|
||||
getMediaSource = null,
|
||||
contextMenu = contextMenu,
|
||||
preferredSubtitleLanguage = null,
|
||||
)
|
||||
}
|
||||
showPlaylistDialog.compose { itemId ->
|
||||
|
|
@ -862,16 +879,6 @@ fun CollectionFolderGrid(
|
|||
elevation = 3.dp,
|
||||
)
|
||||
}
|
||||
showDeleteDialog?.let { (position, item) ->
|
||||
ConfirmDeleteDialog(
|
||||
itemTitle = listOfNotNull(item.title, item.subtitle).joinToString(" - "),
|
||||
onCancel = { showDeleteDialog = null },
|
||||
onConfirm = {
|
||||
viewModel.deleteItem(position, item)
|
||||
showDeleteDialog = null
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,861 @@
|
|||
package com.github.damontecres.wholphin.ui.components
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowForward
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.ArrowForward
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.Info
|
||||
import androidx.compose.material.icons.filled.PlayArrow
|
||||
import androidx.compose.material.icons.filled.Refresh
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.ChosenStreams
|
||||
import com.github.damontecres.wholphin.data.model.AudioItem
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.data.model.ItemPlayback
|
||||
import com.github.damontecres.wholphin.data.model.Person
|
||||
import com.github.damontecres.wholphin.ui.letNotEmpty
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.util.supportedPlayableTypes
|
||||
import org.jellyfin.sdk.model.UUID
|
||||
import org.jellyfin.sdk.model.api.BaseItemDto
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import org.jellyfin.sdk.model.api.MediaSourceInfo
|
||||
import org.jellyfin.sdk.model.api.MediaStreamType
|
||||
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
|
||||
import kotlin.time.Duration
|
||||
|
||||
sealed interface ContextMenu {
|
||||
data class ForBaseItem(
|
||||
val fromLongClick: Boolean,
|
||||
val item: BaseItem,
|
||||
val chosenStreams: ChosenStreams?,
|
||||
val showGoTo: Boolean,
|
||||
val showStreamChoices: Boolean,
|
||||
val canDelete: Boolean,
|
||||
val canRemoveContinueWatching: Boolean,
|
||||
val canRemoveNextUp: Boolean,
|
||||
val actions: ContextMenuActions,
|
||||
) : ContextMenu
|
||||
|
||||
data class ForPerson(
|
||||
val fromLongClick: Boolean,
|
||||
val person: Person,
|
||||
val actions: PersonContextActions,
|
||||
) : ContextMenu
|
||||
|
||||
data class ForMusic(
|
||||
val fromLongClick: Boolean,
|
||||
val item: BaseItem,
|
||||
val index: Int,
|
||||
val canDelete: Boolean,
|
||||
val canRemoveFromQueue: Boolean,
|
||||
val actions: MusicContextActions,
|
||||
) : ContextMenu
|
||||
|
||||
data class ForQueue(
|
||||
val fromLongClick: Boolean,
|
||||
val item: AudioItem,
|
||||
val index: Int,
|
||||
val actions: QueueContextActions,
|
||||
) : ContextMenu
|
||||
}
|
||||
|
||||
data class ContextMenuActions(
|
||||
val navigateTo: (Destination) -> Unit,
|
||||
val onShowOverview: (BaseItem) -> Unit,
|
||||
val onClickWatch: (UUID, Boolean) -> Unit,
|
||||
val onClickFavorite: (UUID, Boolean) -> Unit,
|
||||
val onClickAddPlaylist: (UUID) -> Unit,
|
||||
val onSendMediaInfo: (UUID) -> Unit,
|
||||
val onDeleteItem: (BaseItem) -> Unit,
|
||||
val onChooseVersion: (BaseItem, MediaSourceInfo) -> Unit,
|
||||
val onChooseTracks: (ChosenTrackResult) -> Unit,
|
||||
val onClearChosenStreams: (ChosenStreams?) -> Unit,
|
||||
val onClickGoTo: (BaseItem) -> Unit = { navigateTo(it.destination()) },
|
||||
val onClickRemoveFromNextUp: (BaseItem) -> Unit = {},
|
||||
val onClickAddToQueue: (BaseItem) -> Unit = {},
|
||||
)
|
||||
|
||||
data class PersonContextActions(
|
||||
val navigateTo: (Destination) -> Unit,
|
||||
val onClickFavorite: (UUID, Boolean) -> Unit,
|
||||
)
|
||||
|
||||
data class MusicContextActions(
|
||||
val navigateTo: (Destination) -> Unit,
|
||||
val onClickPlay: (Int, BaseItem) -> Unit,
|
||||
val onClickPlayNext: (Int, BaseItem) -> Unit,
|
||||
val onClickFavorite: (UUID, Boolean) -> Unit,
|
||||
val onClickAddPlaylist: (UUID) -> Unit,
|
||||
val onDeleteItem: (BaseItem) -> Unit,
|
||||
val onClickAddToQueue: (BaseItem) -> Unit,
|
||||
val onClickRemoveFromQueue: (Int, BaseItem) -> Unit,
|
||||
val onClickGoToAlbum: (UUID) -> Unit = {
|
||||
navigateTo.invoke(Destination.MediaItem(itemId = it, type = BaseItemKind.MUSIC_ALBUM))
|
||||
},
|
||||
val onClickGoToArtist: (UUID) -> Unit = {
|
||||
navigateTo.invoke(Destination.MediaItem(itemId = it, type = BaseItemKind.MUSIC_ARTIST))
|
||||
},
|
||||
)
|
||||
|
||||
data class QueueContextActions(
|
||||
val onNavigate: (Destination) -> Unit,
|
||||
val onClickPlay: (Int, AudioItem) -> Unit,
|
||||
val onClickPlayNext: (Int, AudioItem) -> Unit,
|
||||
val onClickGoToAlbum: (java.util.UUID) -> Unit = {
|
||||
onNavigate.invoke(Destination.MediaItem(itemId = it, type = BaseItemKind.MUSIC_ALBUM))
|
||||
},
|
||||
val onClickGoToArtist: (java.util.UUID) -> Unit = {
|
||||
onNavigate.invoke(Destination.MediaItem(itemId = it, type = BaseItemKind.MUSIC_ARTIST))
|
||||
},
|
||||
val onClickRemoveFromQueue: (Int, AudioItem) -> Unit,
|
||||
)
|
||||
|
||||
data class ChosenTrackResult(
|
||||
val item: BaseItem,
|
||||
val streamType: MediaStreamType,
|
||||
val trackIndex: Int,
|
||||
val itemPlayback: ItemPlayback?,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun ContextMenuDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
contextMenu: ContextMenu,
|
||||
getMediaSource: ((dto: BaseItemDto, itemPlayback: ItemPlayback?) -> MediaSourceInfo?)?,
|
||||
preferredSubtitleLanguage: String?,
|
||||
) {
|
||||
when (contextMenu) {
|
||||
is ContextMenu.ForBaseItem -> {
|
||||
ContextMenu(
|
||||
onDismissRequest,
|
||||
contextMenu,
|
||||
getMediaSource,
|
||||
preferredSubtitleLanguage,
|
||||
contextMenu.actions,
|
||||
)
|
||||
}
|
||||
|
||||
is ContextMenu.ForPerson -> {
|
||||
ContextMenu(
|
||||
onDismissRequest = onDismissRequest,
|
||||
item = contextMenu,
|
||||
actions = contextMenu.actions,
|
||||
)
|
||||
}
|
||||
|
||||
is ContextMenu.ForMusic -> {
|
||||
ContextMenu(
|
||||
onDismissRequest = onDismissRequest,
|
||||
item = contextMenu,
|
||||
actions = contextMenu.actions,
|
||||
)
|
||||
}
|
||||
|
||||
is ContextMenu.ForQueue -> {
|
||||
ContextMenu(
|
||||
onDismissRequest = onDismissRequest,
|
||||
item = contextMenu,
|
||||
actions = contextMenu.actions,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ContextMenu(
|
||||
onDismissRequest: () -> Unit,
|
||||
contextMenu: ContextMenu.ForBaseItem,
|
||||
getMediaSource: ((dto: BaseItemDto, itemPlayback: ItemPlayback?) -> MediaSourceInfo?)?,
|
||||
preferredSubtitleLanguage: String?,
|
||||
actions: ContextMenuActions,
|
||||
) {
|
||||
val item = contextMenu.item
|
||||
val chosenStreams = contextMenu.chosenStreams
|
||||
val context = LocalContext.current
|
||||
var chooseVersion by remember { mutableStateOf<DialogParams?>(null) }
|
||||
var showDeleteDialog by remember { mutableStateOf(false) }
|
||||
|
||||
val dialogItems =
|
||||
remember(context, item, chosenStreams) {
|
||||
buildContextMenuItems(
|
||||
context = context,
|
||||
item = item,
|
||||
watched = item.played,
|
||||
favorite = item.favorite,
|
||||
seriesId = item.data.seriesId,
|
||||
sourceId = chosenStreams?.source?.id?.toUUIDOrNull(),
|
||||
canClearChosenStreams = chosenStreams.let { it?.itemPlayback != null || it?.plc != null },
|
||||
showGoTo = contextMenu.showGoTo,
|
||||
showStreamChoices = contextMenu.showStreamChoices,
|
||||
canDelete = contextMenu.canDelete,
|
||||
canRemoveContinueWatching = contextMenu.canRemoveContinueWatching,
|
||||
canRemoveNextUp = contextMenu.canRemoveNextUp,
|
||||
actions = actions,
|
||||
onChooseVersion = {
|
||||
chooseVersion =
|
||||
chooseVersionParams(
|
||||
context,
|
||||
item.data.mediaSources.orEmpty(),
|
||||
chosenStreams?.source?.id?.toUUIDOrNull(),
|
||||
) { idx ->
|
||||
val source = item.data.mediaSources!![idx]
|
||||
actions.onChooseVersion.invoke(item, source)
|
||||
}
|
||||
},
|
||||
onChooseTracks = { type ->
|
||||
getMediaSource
|
||||
?.invoke(
|
||||
item.data,
|
||||
chosenStreams?.itemPlayback,
|
||||
)?.let { source ->
|
||||
chooseVersion =
|
||||
chooseStream(
|
||||
context = context,
|
||||
streams = source.mediaStreams.orEmpty(),
|
||||
type = type,
|
||||
currentIndex =
|
||||
if (type == MediaStreamType.AUDIO) {
|
||||
chosenStreams?.audioStream?.index
|
||||
} else {
|
||||
chosenStreams?.subtitleStream?.index
|
||||
},
|
||||
onClick = { trackIndex ->
|
||||
actions.onChooseTracks.invoke(
|
||||
ChosenTrackResult(
|
||||
item = item,
|
||||
streamType = type,
|
||||
trackIndex = trackIndex,
|
||||
itemPlayback = chosenStreams?.itemPlayback,
|
||||
),
|
||||
)
|
||||
},
|
||||
preferredSubtitleLanguage = preferredSubtitleLanguage,
|
||||
)
|
||||
}
|
||||
},
|
||||
onShowOverview = { actions.onShowOverview.invoke(item) },
|
||||
onClearChosenStreams = {
|
||||
actions.onClearChosenStreams.invoke(chosenStreams)
|
||||
},
|
||||
onClickDelete = { showDeleteDialog = true },
|
||||
)
|
||||
}
|
||||
DialogPopup(
|
||||
showDialog = true,
|
||||
title = item.title ?: "",
|
||||
dialogItems = dialogItems,
|
||||
onDismissRequest = onDismissRequest,
|
||||
dismissOnClick = false,
|
||||
waitToLoad = contextMenu.fromLongClick,
|
||||
)
|
||||
if (chooseVersion != null) {
|
||||
chooseVersion?.let { params ->
|
||||
DialogPopup(
|
||||
showDialog = true,
|
||||
title = params.title,
|
||||
dialogItems = params.items,
|
||||
onDismissRequest = { chooseVersion = null },
|
||||
dismissOnClick = true,
|
||||
waitToLoad = params.fromLongClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
if (showDeleteDialog) {
|
||||
ConfirmDeleteDialog(
|
||||
itemTitle = item.title ?: "",
|
||||
onCancel = { showDeleteDialog = false },
|
||||
onConfirm = {
|
||||
actions.onDeleteItem.invoke(item)
|
||||
onDismissRequest.invoke()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildContextMenuItems(
|
||||
context: Context,
|
||||
item: BaseItem,
|
||||
seriesId: UUID?,
|
||||
sourceId: UUID?,
|
||||
watched: Boolean,
|
||||
favorite: Boolean,
|
||||
canClearChosenStreams: Boolean,
|
||||
showGoTo: Boolean,
|
||||
showStreamChoices: Boolean,
|
||||
canDelete: Boolean,
|
||||
canRemoveContinueWatching: Boolean,
|
||||
canRemoveNextUp: Boolean,
|
||||
actions: ContextMenuActions,
|
||||
onChooseVersion: () -> Unit,
|
||||
onChooseTracks: (MediaStreamType) -> Unit,
|
||||
onShowOverview: () -> Unit,
|
||||
onClearChosenStreams: () -> Unit,
|
||||
onClickDelete: () -> Unit,
|
||||
): List<DialogItem> =
|
||||
buildList {
|
||||
if (showGoTo) {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.go_to),
|
||||
Icons.Default.ArrowForward,
|
||||
dismissOnClick = true,
|
||||
) {
|
||||
actions.onClickGoTo(item)
|
||||
},
|
||||
)
|
||||
}
|
||||
if (item.type in supportedPlayableTypes) {
|
||||
if (item.playbackPosition >= Duration.ZERO) {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.resume),
|
||||
Icons.Default.PlayArrow,
|
||||
iconColor = Color.Green.copy(alpha = .8f),
|
||||
dismissOnClick = true,
|
||||
) {
|
||||
actions.navigateTo(
|
||||
Destination.Playback(
|
||||
item.id,
|
||||
item.playbackPosition.inWholeMilliseconds,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.restart),
|
||||
Icons.Default.Refresh,
|
||||
dismissOnClick = true,
|
||||
) {
|
||||
actions.navigateTo(
|
||||
Destination.Playback(
|
||||
item.id,
|
||||
0L,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
} else {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.play),
|
||||
Icons.Default.PlayArrow,
|
||||
iconColor = Color.Green.copy(alpha = .8f),
|
||||
dismissOnClick = true,
|
||||
) {
|
||||
actions.navigateTo(
|
||||
Destination.Playback(
|
||||
item.id,
|
||||
0L,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
if (showStreamChoices) {
|
||||
item.data.mediaSources?.letNotEmpty { sources ->
|
||||
val source =
|
||||
sourceId?.let { sources.firstOrNull { it.id?.toUUIDOrNull() == sourceId } }
|
||||
?: sources.firstOrNull()
|
||||
source?.mediaStreams?.letNotEmpty { streams ->
|
||||
val audioCount = streams.count { it.type == MediaStreamType.AUDIO }
|
||||
val subtitleCount = streams.count { it.type == MediaStreamType.SUBTITLE }
|
||||
if (audioCount > 1) {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(
|
||||
R.string.choose_stream,
|
||||
context.getString(R.string.audio),
|
||||
),
|
||||
R.string.fa_volume_low,
|
||||
dismissOnClick = false,
|
||||
) {
|
||||
onChooseTracks.invoke(MediaStreamType.AUDIO)
|
||||
},
|
||||
)
|
||||
}
|
||||
if (subtitleCount > 0) {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(
|
||||
R.string.choose_stream,
|
||||
context.getString(R.string.subtitles),
|
||||
),
|
||||
R.string.fa_closed_captioning,
|
||||
dismissOnClick = false,
|
||||
) {
|
||||
onChooseTracks.invoke(MediaStreamType.SUBTITLE)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
if (sources.size > 1) {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(
|
||||
R.string.choose_stream,
|
||||
context.getString(R.string.version),
|
||||
),
|
||||
R.string.fa_file_video,
|
||||
dismissOnClick = false,
|
||||
) {
|
||||
onChooseVersion.invoke()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (item.type == BaseItemKind.MUSIC_ALBUM) {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.add_to_queue),
|
||||
Icons.Default.Add,
|
||||
dismissOnClick = true,
|
||||
) {
|
||||
actions.onClickAddToQueue(item)
|
||||
},
|
||||
)
|
||||
}
|
||||
add(
|
||||
DialogItem(
|
||||
text = R.string.add_to_playlist,
|
||||
iconStringRes = R.string.fa_list_ul,
|
||||
dismissOnClick = true,
|
||||
) {
|
||||
actions.onClickAddPlaylist.invoke(item.id)
|
||||
},
|
||||
)
|
||||
if (canDelete) {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.delete),
|
||||
Icons.Default.Delete,
|
||||
iconColor = Color.Red.copy(alpha = .8f),
|
||||
dismissOnClick = false,
|
||||
) {
|
||||
onClickDelete.invoke()
|
||||
},
|
||||
)
|
||||
}
|
||||
if (canRemoveContinueWatching && !watched && item.playbackPosition > Duration.ZERO) {
|
||||
add(
|
||||
DialogItem(
|
||||
text = R.string.remove_continue_watching,
|
||||
iconStringRes = R.string.fa_eye,
|
||||
dismissOnClick = true,
|
||||
) {
|
||||
actions.onClickWatch.invoke(item.id, false)
|
||||
},
|
||||
)
|
||||
}
|
||||
if (canRemoveNextUp && item.type == BaseItemKind.EPISODE && item.data.seriesId != null) {
|
||||
add(
|
||||
DialogItem(
|
||||
text = R.string.remove_next_up,
|
||||
iconStringRes = R.string.fa_tag,
|
||||
dismissOnClick = true,
|
||||
) {
|
||||
actions.onClickRemoveFromNextUp.invoke(item)
|
||||
},
|
||||
)
|
||||
}
|
||||
add(
|
||||
DialogItem(
|
||||
text = if (watched) R.string.mark_unwatched else R.string.mark_watched,
|
||||
iconStringRes = if (watched) R.string.fa_eye else R.string.fa_eye_slash,
|
||||
dismissOnClick = true,
|
||||
) {
|
||||
actions.onClickWatch.invoke(item.id, !watched)
|
||||
},
|
||||
)
|
||||
add(
|
||||
DialogItem(
|
||||
text = if (favorite) R.string.remove_favorite else R.string.add_favorite,
|
||||
iconStringRes = R.string.fa_heart,
|
||||
iconColor = if (favorite) Color.Red else Color.Unspecified,
|
||||
dismissOnClick = true,
|
||||
) {
|
||||
actions.onClickFavorite.invoke(item.id, !favorite)
|
||||
},
|
||||
)
|
||||
item.data.albumId?.let { albumId ->
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.go_to_album),
|
||||
R.string.fa_compact_disc,
|
||||
dismissOnClick = true,
|
||||
) {
|
||||
actions.navigateTo(
|
||||
Destination.MediaItem(
|
||||
albumId,
|
||||
BaseItemKind.MUSIC_ALBUM,
|
||||
null,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
item.data.artistItems?.firstOrNull()?.id?.let { artistId ->
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.go_to_artist),
|
||||
R.string.fa_user,
|
||||
dismissOnClick = true,
|
||||
) {
|
||||
actions.navigateTo(
|
||||
Destination.MediaItem(
|
||||
artistId,
|
||||
BaseItemKind.MUSIC_ARTIST,
|
||||
null,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
seriesId?.let {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.go_to_series),
|
||||
Icons.AutoMirrored.Filled.ArrowForward,
|
||||
dismissOnClick = true,
|
||||
) {
|
||||
actions.navigateTo(
|
||||
Destination.MediaItem(
|
||||
seriesId,
|
||||
BaseItemKind.SERIES,
|
||||
null,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
if (item.data.mediaSources?.isNotEmpty() == true) {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.media_information),
|
||||
Icons.Default.Info,
|
||||
dismissOnClick = true,
|
||||
) {
|
||||
onShowOverview.invoke()
|
||||
},
|
||||
)
|
||||
}
|
||||
if (showStreamChoices && canClearChosenStreams) {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.clear_track_choices),
|
||||
Icons.Default.Delete,
|
||||
dismissOnClick = true,
|
||||
) {
|
||||
onClearChosenStreams()
|
||||
},
|
||||
)
|
||||
}
|
||||
if (item.type in supportedPlayableTypes) {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.play_with_transcoding),
|
||||
Icons.Default.PlayArrow,
|
||||
dismissOnClick = true,
|
||||
) {
|
||||
actions.navigateTo(
|
||||
Destination.Playback(
|
||||
item.id,
|
||||
item.resumeMs ?: 0L,
|
||||
forceTranscoding = true,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
if (item.data.mediaSources?.isNotEmpty() == true) {
|
||||
add(
|
||||
DialogItem(
|
||||
text = R.string.send_media_info_log_to_server,
|
||||
iconStringRes = R.string.fa_file_video,
|
||||
dismissOnClick = true,
|
||||
) {
|
||||
actions.onSendMediaInfo.invoke(item.id)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ContextMenu(
|
||||
onDismissRequest: () -> Unit,
|
||||
item: ContextMenu.ForPerson,
|
||||
actions: PersonContextActions,
|
||||
) {
|
||||
val person = item.person
|
||||
val dialogItems =
|
||||
buildList {
|
||||
val itemId = person.id
|
||||
add(
|
||||
DialogItem(
|
||||
stringResource(R.string.go_to),
|
||||
Icons.Default.ArrowForward,
|
||||
) {
|
||||
actions.navigateTo(Destination.MediaItem(itemId, BaseItemKind.PERSON, null))
|
||||
},
|
||||
)
|
||||
add(
|
||||
DialogItem(
|
||||
text = if (person.favorite) R.string.remove_favorite else R.string.add_favorite,
|
||||
iconStringRes = R.string.fa_heart,
|
||||
iconColor = if (person.favorite) Color.Red else Color.Unspecified,
|
||||
) {
|
||||
actions.onClickFavorite.invoke(itemId, !person.favorite)
|
||||
},
|
||||
)
|
||||
}
|
||||
DialogPopup(
|
||||
showDialog = true,
|
||||
title = person.name ?: "",
|
||||
dialogItems = dialogItems,
|
||||
onDismissRequest = onDismissRequest,
|
||||
dismissOnClick = true,
|
||||
waitToLoad = item.fromLongClick,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ContextMenu(
|
||||
onDismissRequest: () -> Unit,
|
||||
item: ContextMenu.ForMusic,
|
||||
actions: MusicContextActions,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
var showDeleteDialog by remember { mutableStateOf(false) }
|
||||
val dialogItems =
|
||||
remember(context, item, actions) {
|
||||
buildContextForMusic(context, item, actions, onClickDelete = {
|
||||
showDeleteDialog = true
|
||||
})
|
||||
}
|
||||
DialogPopup(
|
||||
showDialog = true,
|
||||
title = item.item.title ?: "",
|
||||
dialogItems = dialogItems,
|
||||
onDismissRequest = onDismissRequest,
|
||||
dismissOnClick = false,
|
||||
waitToLoad = item.fromLongClick,
|
||||
)
|
||||
if (showDeleteDialog) {
|
||||
ConfirmDeleteDialog(
|
||||
itemTitle = item.item.title ?: "",
|
||||
onCancel = { showDeleteDialog = false },
|
||||
onConfirm = {
|
||||
actions.onDeleteItem.invoke(item.item)
|
||||
onDismissRequest.invoke()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun buildContextForMusic(
|
||||
context: Context,
|
||||
music: ContextMenu.ForMusic,
|
||||
actions: MusicContextActions,
|
||||
onClickDelete: () -> Unit,
|
||||
): List<DialogItem> =
|
||||
buildList {
|
||||
val item = music.item
|
||||
val index = music.index
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.play),
|
||||
Icons.Default.PlayArrow,
|
||||
iconColor = Color.Green.copy(alpha = .8f),
|
||||
dismissOnClick = true,
|
||||
) {
|
||||
actions.onClickPlay(index, item)
|
||||
},
|
||||
)
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.play_next),
|
||||
Icons.Default.PlayArrow,
|
||||
iconColor = Color.Green.copy(alpha = .8f),
|
||||
dismissOnClick = true,
|
||||
) {
|
||||
actions.onClickPlayNext(index, item)
|
||||
},
|
||||
)
|
||||
if (music.canRemoveFromQueue) {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.remove_from_queue),
|
||||
Icons.Default.Delete,
|
||||
dismissOnClick = true,
|
||||
) {
|
||||
actions.onClickRemoveFromQueue(index, item)
|
||||
},
|
||||
)
|
||||
} else {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.add_to_queue),
|
||||
Icons.Default.Add,
|
||||
dismissOnClick = true,
|
||||
) {
|
||||
actions.onClickAddToQueue(item)
|
||||
},
|
||||
)
|
||||
}
|
||||
add(
|
||||
DialogItem(
|
||||
text = R.string.add_to_playlist,
|
||||
iconStringRes = R.string.fa_list_ul,
|
||||
dismissOnClick = true,
|
||||
) {
|
||||
actions.onClickAddPlaylist.invoke(item.id)
|
||||
},
|
||||
)
|
||||
if (music.canDelete) {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.delete),
|
||||
Icons.Default.Delete,
|
||||
iconColor = Color.Red.copy(alpha = .8f),
|
||||
dismissOnClick = false,
|
||||
) {
|
||||
onClickDelete.invoke()
|
||||
},
|
||||
)
|
||||
}
|
||||
add(
|
||||
DialogItem(
|
||||
text = if (item.favorite) R.string.remove_favorite else R.string.add_favorite,
|
||||
iconStringRes = R.string.fa_heart,
|
||||
iconColor = if (item.favorite) Color.Red else Color.Unspecified,
|
||||
dismissOnClick = true,
|
||||
) {
|
||||
actions.onClickFavorite.invoke(item.id, !item.favorite)
|
||||
},
|
||||
)
|
||||
if (item.type == BaseItemKind.AUDIO && item.data.albumId != null) {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.go_to_album),
|
||||
R.string.fa_compact_disc,
|
||||
dismissOnClick = true,
|
||||
) {
|
||||
actions.onClickGoToAlbum.invoke(item.data.albumId!!)
|
||||
},
|
||||
)
|
||||
}
|
||||
if ((item.type == BaseItemKind.AUDIO || item.type == BaseItemKind.MUSIC_ALBUM) && item.data.artistItems?.isNotEmpty() == true) {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.go_to_artist),
|
||||
R.string.fa_user,
|
||||
dismissOnClick = true,
|
||||
) {
|
||||
actions.onClickGoToArtist.invoke(
|
||||
item.data.artistItems!!
|
||||
.first()
|
||||
.id,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ContextMenu(
|
||||
onDismissRequest: () -> Unit,
|
||||
item: ContextMenu.ForQueue,
|
||||
actions: QueueContextActions,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val dialogItems =
|
||||
remember(context, item, actions) {
|
||||
buildContextForMusicQueue(
|
||||
context,
|
||||
item.item,
|
||||
item.index,
|
||||
actions,
|
||||
)
|
||||
}
|
||||
DialogPopup(
|
||||
showDialog = true,
|
||||
title = item.item.title ?: "",
|
||||
dialogItems = dialogItems,
|
||||
onDismissRequest = onDismissRequest,
|
||||
dismissOnClick = false,
|
||||
waitToLoad = item.fromLongClick,
|
||||
)
|
||||
}
|
||||
|
||||
fun buildContextForMusicQueue(
|
||||
context: Context,
|
||||
item: AudioItem,
|
||||
index: Int,
|
||||
actions: QueueContextActions,
|
||||
): List<DialogItem> =
|
||||
buildList {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.play),
|
||||
Icons.Default.PlayArrow,
|
||||
iconColor = Color.Green.copy(alpha = .8f),
|
||||
dismissOnClick = true,
|
||||
) {
|
||||
actions.onClickPlay(index, item)
|
||||
},
|
||||
)
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.play_next),
|
||||
Icons.Default.PlayArrow,
|
||||
iconColor = Color.Green.copy(alpha = .8f),
|
||||
dismissOnClick = true,
|
||||
) {
|
||||
actions.onClickPlayNext(index, item)
|
||||
},
|
||||
)
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.remove_from_queue),
|
||||
Icons.Default.Delete,
|
||||
dismissOnClick = true,
|
||||
) {
|
||||
actions.onClickRemoveFromQueue(index, item)
|
||||
},
|
||||
)
|
||||
if (item.albumId != null) {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.go_to_album),
|
||||
Icons.Default.ArrowForward,
|
||||
dismissOnClick = true,
|
||||
) {
|
||||
actions.onClickGoToAlbum.invoke(item.albumId)
|
||||
},
|
||||
)
|
||||
}
|
||||
if (item.artistId != null) {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.go_to_artist),
|
||||
Icons.Default.ArrowForward,
|
||||
dismissOnClick = true,
|
||||
) {
|
||||
actions.onClickGoToArtist.invoke(item.artistId)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -104,11 +104,13 @@ data class DialogItem(
|
|||
val trailingContent: @Composable (() -> Unit)? = null,
|
||||
val enabled: Boolean = true,
|
||||
val selected: Boolean = false,
|
||||
val dismissOnClick: Boolean = false,
|
||||
) : DialogItemEntry {
|
||||
constructor(
|
||||
@StringRes text: Int,
|
||||
@StringRes iconStringRes: Int,
|
||||
iconColor: Color = Color.Unspecified,
|
||||
dismissOnClick: Boolean = false,
|
||||
onClick: () -> Unit,
|
||||
) : this(
|
||||
headlineContent = {
|
||||
|
|
@ -124,11 +126,13 @@ data class DialogItem(
|
|||
)
|
||||
},
|
||||
onClick = onClick,
|
||||
dismissOnClick = dismissOnClick,
|
||||
)
|
||||
|
||||
constructor(
|
||||
text: String,
|
||||
@StringRes iconStringRes: Int,
|
||||
dismissOnClick: Boolean = false,
|
||||
onClick: () -> Unit,
|
||||
) : this(
|
||||
headlineContent = {
|
||||
|
|
@ -145,12 +149,14 @@ data class DialogItem(
|
|||
)
|
||||
},
|
||||
onClick = onClick,
|
||||
dismissOnClick = dismissOnClick,
|
||||
)
|
||||
|
||||
constructor(
|
||||
text: String,
|
||||
icon: ImageVector,
|
||||
iconColor: Color? = null,
|
||||
dismissOnClick: Boolean = false,
|
||||
onClick: () -> Unit,
|
||||
) : this(
|
||||
headlineContent = {
|
||||
|
|
@ -167,10 +173,12 @@ data class DialogItem(
|
|||
)
|
||||
},
|
||||
onClick = onClick,
|
||||
dismissOnClick = dismissOnClick,
|
||||
)
|
||||
|
||||
constructor(
|
||||
text: String,
|
||||
dismissOnClick: Boolean = false,
|
||||
onClick: () -> Unit,
|
||||
) : this(
|
||||
headlineContent = {
|
||||
|
|
@ -180,6 +188,7 @@ data class DialogItem(
|
|||
)
|
||||
},
|
||||
onClick = onClick,
|
||||
dismissOnClick = dismissOnClick,
|
||||
)
|
||||
|
||||
companion object {
|
||||
|
|
@ -201,7 +210,7 @@ fun DialogPopup(
|
|||
dismissOnClick: Boolean = true,
|
||||
waitToLoad: Boolean = true,
|
||||
properties: DialogProperties = DialogProperties(),
|
||||
elevation: Dp = 8.dp,
|
||||
elevation: Dp = 1.dp,
|
||||
) {
|
||||
var waiting by remember { mutableStateOf(waitToLoad) }
|
||||
if (showDialog) {
|
||||
|
|
@ -296,7 +305,7 @@ fun DialogPopupContent(
|
|||
selected = item.selected,
|
||||
enabled = !waiting && item.enabled,
|
||||
onClick = {
|
||||
if (dismissOnClick) {
|
||||
if (dismissOnClick || item.dismissOnClick) {
|
||||
onDismissRequest.invoke()
|
||||
}
|
||||
item.onClick.invoke()
|
||||
|
|
@ -349,7 +358,7 @@ fun DialogPopup(
|
|||
onDismissRequest: () -> Unit,
|
||||
dismissOnClick: Boolean = true,
|
||||
properties: DialogProperties = DialogProperties(),
|
||||
elevation: Dp = 8.dp,
|
||||
elevation: Dp = 1.dp,
|
||||
) = DialogPopup(
|
||||
showDialog = true,
|
||||
waitToLoad = params.fromLongClick,
|
||||
|
|
@ -427,7 +436,7 @@ fun ScrollableDialog(
|
|||
fun BasicDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
properties: DialogProperties = DialogProperties(),
|
||||
elevation: Dp = 8.dp,
|
||||
elevation: Dp = 1.dp,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
Dialog(
|
||||
|
|
@ -458,7 +467,7 @@ fun ConfirmDialog(
|
|||
onCancel: () -> Unit,
|
||||
onConfirm: () -> Unit,
|
||||
properties: DialogProperties = DialogProperties(),
|
||||
elevation: Dp = 8.dp,
|
||||
elevation: Dp = 1.dp,
|
||||
bodyColor: Color = MaterialTheme.colorScheme.onSurface,
|
||||
) = BasicDialog(
|
||||
onDismissRequest = onCancel,
|
||||
|
|
@ -527,10 +536,12 @@ fun ConfirmDeleteDialog(
|
|||
itemTitle: String,
|
||||
onCancel: () -> Unit,
|
||||
onConfirm: () -> Unit,
|
||||
elevation: Dp = 3.dp,
|
||||
) {
|
||||
BasicDialog(
|
||||
onDismissRequest = onCancel,
|
||||
properties = DialogProperties(usePlatformDefaultWidth = false),
|
||||
elevation = elevation,
|
||||
) {
|
||||
LazyColumn(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ import kotlin.time.Duration.Companion.seconds
|
|||
*/
|
||||
@Composable
|
||||
fun ExpandablePlayButtons(
|
||||
title: String,
|
||||
resumePosition: Duration,
|
||||
watched: Boolean,
|
||||
favorite: Boolean,
|
||||
|
|
@ -79,7 +80,7 @@ fun ExpandablePlayButtons(
|
|||
favoriteOnClick: () -> Unit,
|
||||
moreOnClick: () -> Unit,
|
||||
trailerOnClick: (Trailer) -> Unit,
|
||||
deleteOnClick: () -> Unit,
|
||||
onConfirmDelete: () -> Unit,
|
||||
buttonOnFocusChanged: (FocusState) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
|
|
@ -163,7 +164,8 @@ fun ExpandablePlayButtons(
|
|||
if (canDelete) {
|
||||
item("delete") {
|
||||
DeleteButton(
|
||||
onClick = deleteOnClick,
|
||||
title = title,
|
||||
onConfirmDelete = onConfirmDelete,
|
||||
modifier =
|
||||
Modifier
|
||||
.onFocusChanged(buttonOnFocusChanged),
|
||||
|
|
@ -381,10 +383,12 @@ fun TrailerButton(
|
|||
|
||||
@Composable
|
||||
fun DeleteButton(
|
||||
onClick: () -> Unit,
|
||||
onConfirmDelete: () -> Unit,
|
||||
title: String,
|
||||
modifier: Modifier = Modifier,
|
||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||
) {
|
||||
var showDeleteDialog by remember { mutableStateOf(false) }
|
||||
val focused by interactionSource.collectIsFocusedAsState()
|
||||
val iconTint by
|
||||
animateColorAsState(
|
||||
|
|
@ -415,10 +419,20 @@ fun DeleteButton(
|
|||
.size(24.dp),
|
||||
)
|
||||
},
|
||||
onClick = { onClick.invoke() },
|
||||
onClick = { showDeleteDialog = true },
|
||||
interactionSource = interactionSource,
|
||||
modifier = modifier,
|
||||
)
|
||||
if (showDeleteDialog) {
|
||||
ConfirmDeleteDialog(
|
||||
itemTitle = title,
|
||||
onCancel = { showDeleteDialog = false },
|
||||
onConfirm = {
|
||||
onConfirmDelete.invoke()
|
||||
showDeleteDialog = false
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewTvSpec
|
||||
|
|
@ -426,6 +440,7 @@ fun DeleteButton(
|
|||
private fun ExpandablePlayButtonsPreview() {
|
||||
WholphinTheme(true) {
|
||||
ExpandablePlayButtons(
|
||||
title = "Movie",
|
||||
resumePosition = 10.seconds,
|
||||
watched = false,
|
||||
favorite = false,
|
||||
|
|
@ -437,7 +452,7 @@ private fun ExpandablePlayButtonsPreview() {
|
|||
trailers = listOf(),
|
||||
trailerOnClick = {},
|
||||
canDelete = true,
|
||||
deleteOnClick = {},
|
||||
onConfirmDelete = {},
|
||||
modifier = Modifier,
|
||||
)
|
||||
}
|
||||
|
|
@ -482,7 +497,8 @@ private fun ViewOptionsPreview() {
|
|||
modifier = Modifier,
|
||||
)
|
||||
DeleteButton(
|
||||
onClick = {},
|
||||
onConfirmDelete = {},
|
||||
title = "Movie",
|
||||
)
|
||||
SortByButton(
|
||||
sortOptions = listOf(),
|
||||
|
|
@ -491,7 +507,8 @@ private fun ViewOptionsPreview() {
|
|||
)
|
||||
}
|
||||
DeleteButton(
|
||||
onClick = {},
|
||||
onConfirmDelete = {},
|
||||
title = "Movie",
|
||||
interactionSource = source,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
|
|
@ -18,6 +17,7 @@ import androidx.lifecycle.MutableLiveData
|
|||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.ServerRepository
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.data.model.HomeRowViewOptions
|
||||
import com.github.damontecres.wholphin.preferences.AppPreferences
|
||||
|
|
@ -31,11 +31,11 @@ import com.github.damontecres.wholphin.services.NavigationManager
|
|||
import com.github.damontecres.wholphin.services.deleteItem
|
||||
import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect
|
||||
import com.github.damontecres.wholphin.ui.data.AddPlaylistViewModel
|
||||
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialog
|
||||
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialogInfo
|
||||
import com.github.damontecres.wholphin.ui.data.RowColumn
|
||||
import com.github.damontecres.wholphin.ui.detail.MoreDialogActions
|
||||
import com.github.damontecres.wholphin.ui.detail.PlaylistDialog
|
||||
import com.github.damontecres.wholphin.ui.detail.PlaylistLoadingState
|
||||
import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItemsForHome
|
||||
import com.github.damontecres.wholphin.ui.detail.music.addToQueue
|
||||
import com.github.damontecres.wholphin.ui.launchDefault
|
||||
import com.github.damontecres.wholphin.ui.launchIO
|
||||
|
|
@ -60,6 +60,7 @@ import java.util.UUID
|
|||
abstract class RecommendedViewModel(
|
||||
@param:ApplicationContext val context: Context,
|
||||
val api: ApiClient,
|
||||
val serverRepository: ServerRepository,
|
||||
val navigationManager: NavigationManager,
|
||||
val favoriteWatchManager: FavoriteWatchManager,
|
||||
val mediaReportService: MediaReportService,
|
||||
|
|
@ -167,10 +168,9 @@ fun RecommendedContent(
|
|||
playlistViewModel: AddPlaylistViewModel = hiltViewModel(),
|
||||
onFocusPosition: ((RowColumn) -> Unit)? = null,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
var moreDialog by remember { mutableStateOf<Optional<RowColumnItem>>(Optional.absent()) }
|
||||
var showContextMenu by remember { mutableStateOf<ContextMenu?>(null) }
|
||||
var overviewDialog by remember { mutableStateOf<ItemDetailsDialogInfo?>(null) }
|
||||
var showPlaylistDialog by remember { mutableStateOf<Optional<UUID>>(Optional.absent()) }
|
||||
var showDeleteDialog by remember { mutableStateOf<RowColumnItem?>(null) }
|
||||
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
||||
|
||||
OneTimeLaunchedEffect {
|
||||
|
|
@ -192,6 +192,35 @@ fun RecommendedContent(
|
|||
|
||||
LoadingState.Success -> {
|
||||
var position by rememberPosition()
|
||||
val contextActions =
|
||||
remember {
|
||||
ContextMenuActions(
|
||||
navigateTo = viewModel.navigationManager::navigateTo,
|
||||
onClickWatch = { itemId, watched ->
|
||||
viewModel.setWatched(position, itemId, watched)
|
||||
},
|
||||
onClickFavorite = { itemId, favorite ->
|
||||
viewModel.setFavorite(position, itemId, favorite)
|
||||
},
|
||||
onClickAddPlaylist = { itemId ->
|
||||
playlistViewModel.loadPlaylists(MediaType.VIDEO)
|
||||
showPlaylistDialog.makePresent(itemId)
|
||||
},
|
||||
onSendMediaInfo = viewModel.mediaReportService::sendReportFor,
|
||||
onDeleteItem = { viewModel.deleteItem(position, it) },
|
||||
onShowOverview = { overviewDialog = ItemDetailsDialogInfo(it) },
|
||||
onChooseVersion = { _, _ ->
|
||||
// Not supported on this page
|
||||
},
|
||||
onChooseTracks = { result ->
|
||||
// Not supported on this page
|
||||
},
|
||||
onClearChosenStreams = {
|
||||
// Not supported on this page
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
HomePageContent(
|
||||
homeRows = rows,
|
||||
position = position,
|
||||
|
|
@ -199,7 +228,18 @@ fun RecommendedContent(
|
|||
viewModel.navigationManager.navigateTo(item.destination())
|
||||
},
|
||||
onLongClickItem = { position, item ->
|
||||
moreDialog.makePresent(RowColumnItem(position, item))
|
||||
showContextMenu =
|
||||
ContextMenu.ForBaseItem(
|
||||
fromLongClick = true,
|
||||
item = item,
|
||||
chosenStreams = null,
|
||||
showGoTo = true,
|
||||
showStreamChoices = false,
|
||||
canDelete = viewModel.canDelete(item, preferences.appPreferences),
|
||||
canRemoveContinueWatching = false,
|
||||
canRemoveNextUp = false,
|
||||
actions = contextActions,
|
||||
)
|
||||
},
|
||||
onClickPlay = { _, item ->
|
||||
viewModel.navigationManager.navigateTo(Destination.Playback(item))
|
||||
|
|
@ -226,42 +266,22 @@ fun RecommendedContent(
|
|||
)
|
||||
}
|
||||
}
|
||||
moreDialog.compose { (position, item) ->
|
||||
DialogPopup(
|
||||
showDialog = true,
|
||||
title = item.title ?: "",
|
||||
dialogItems =
|
||||
buildMoreDialogItemsForHome(
|
||||
context = context,
|
||||
item = item,
|
||||
seriesId = null,
|
||||
playbackPosition = item.playbackPosition,
|
||||
watched = item.played,
|
||||
favorite = item.favorite,
|
||||
canDelete = viewModel.canDelete(item, preferences.appPreferences),
|
||||
actions =
|
||||
MoreDialogActions(
|
||||
navigateTo = { viewModel.navigationManager.navigateTo(it) },
|
||||
onClickWatch = { itemId, watched ->
|
||||
viewModel.setWatched(position, itemId, watched)
|
||||
},
|
||||
onClickFavorite = { itemId, watched ->
|
||||
viewModel.setFavorite(position, itemId, watched)
|
||||
},
|
||||
onClickAddPlaylist = {
|
||||
playlistViewModel.loadPlaylists(MediaType.VIDEO)
|
||||
showPlaylistDialog.makePresent(it)
|
||||
},
|
||||
onSendMediaInfo = viewModel.mediaReportService::sendReportFor,
|
||||
onClickDelete = { showDeleteDialog = RowColumnItem(position, item) },
|
||||
onClickAddToQueue = {
|
||||
viewModel.addToQueue(it, 0)
|
||||
},
|
||||
),
|
||||
),
|
||||
onDismissRequest = { moreDialog.makeAbsent() },
|
||||
dismissOnClick = true,
|
||||
waitToLoad = true,
|
||||
overviewDialog?.let { info ->
|
||||
ItemDetailsDialog(
|
||||
info = info,
|
||||
showFilePath =
|
||||
viewModel.serverRepository.currentUserDto.value
|
||||
?.policy
|
||||
?.isAdministrator == true,
|
||||
onDismissRequest = { overviewDialog = null },
|
||||
)
|
||||
}
|
||||
showContextMenu?.let { contextMenu ->
|
||||
ContextMenuDialog(
|
||||
onDismissRequest = { showContextMenu = null },
|
||||
getMediaSource = null,
|
||||
contextMenu = contextMenu,
|
||||
preferredSubtitleLanguage = null,
|
||||
)
|
||||
}
|
||||
showPlaylistDialog.compose { itemId ->
|
||||
|
|
@ -281,16 +301,6 @@ fun RecommendedContent(
|
|||
elevation = 3.dp,
|
||||
)
|
||||
}
|
||||
showDeleteDialog?.let { (position, item) ->
|
||||
ConfirmDeleteDialog(
|
||||
itemTitle = listOfNotNull(item.title, item.subtitle).joinToString(" - "),
|
||||
onCancel = { showDeleteDialog = null },
|
||||
onConfirm = {
|
||||
viewModel.deleteItem(position, item)
|
||||
showDeleteDialog = null
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class RowColumnItem(
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ class RecommendedMovieViewModel
|
|||
@ApplicationContext context: Context,
|
||||
api: ApiClient,
|
||||
musicService: MusicService,
|
||||
private val serverRepository: ServerRepository,
|
||||
serverRepository: ServerRepository,
|
||||
private val preferencesDataStore: DataStore<AppPreferences>,
|
||||
private val suggestionService: SuggestionService,
|
||||
@Assisted val parentId: UUID,
|
||||
|
|
@ -70,6 +70,7 @@ class RecommendedMovieViewModel
|
|||
) : RecommendedViewModel(
|
||||
context,
|
||||
api,
|
||||
serverRepository,
|
||||
navigationManager,
|
||||
favoriteWatchManager,
|
||||
mediaReportService,
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ class RecommendedMusicViewModel
|
|||
@ApplicationContext context: Context,
|
||||
api: ApiClient,
|
||||
musicService: MusicService,
|
||||
private val serverRepository: ServerRepository,
|
||||
serverRepository: ServerRepository,
|
||||
private val preferencesDataStore: DataStore<AppPreferences>,
|
||||
private val suggestionService: SuggestionService,
|
||||
@Assisted val parentId: UUID,
|
||||
|
|
@ -69,6 +69,7 @@ class RecommendedMusicViewModel
|
|||
) : RecommendedViewModel(
|
||||
context,
|
||||
api,
|
||||
serverRepository,
|
||||
navigationManager,
|
||||
favoriteWatchManager,
|
||||
mediaReportService,
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ class RecommendedTvShowViewModel
|
|||
@ApplicationContext context: Context,
|
||||
api: ApiClient,
|
||||
musicService: MusicService,
|
||||
private val serverRepository: ServerRepository,
|
||||
serverRepository: ServerRepository,
|
||||
private val preferencesDataStore: DataStore<AppPreferences>,
|
||||
private val lastestNextUpService: LatestNextUpService,
|
||||
private val suggestionService: SuggestionService,
|
||||
|
|
@ -74,6 +74,7 @@ class RecommendedTvShowViewModel
|
|||
) : RecommendedViewModel(
|
||||
context,
|
||||
api,
|
||||
serverRepository,
|
||||
navigationManager,
|
||||
favoriteWatchManager,
|
||||
mediaReportService,
|
||||
|
|
|
|||
|
|
@ -6,21 +6,16 @@ import androidx.compose.material.icons.automirrored.filled.ArrowForward
|
|||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.ArrowForward
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.Info
|
||||
import androidx.compose.material.icons.filled.PlayArrow
|
||||
import androidx.compose.material.icons.filled.Refresh
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.data.model.Person
|
||||
import com.github.damontecres.wholphin.ui.components.DialogItem
|
||||
import com.github.damontecres.wholphin.ui.letNotEmpty
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.util.supportedPlayableTypes
|
||||
import org.jellyfin.sdk.model.UUID
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import org.jellyfin.sdk.model.api.MediaStreamType
|
||||
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
|
|
@ -36,234 +31,6 @@ data class MoreDialogActions(
|
|||
val onClickAddToQueue: (BaseItem) -> Unit = {},
|
||||
)
|
||||
|
||||
enum class ClearChosenStreams {
|
||||
NONE,
|
||||
ITEM_AND_SERIES,
|
||||
SERIES,
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the [DialogItem]s when clicking "More"
|
||||
*
|
||||
* If there are multiple versions, adds an option to pick one
|
||||
*
|
||||
* If there is more than one (ie two or more) audio track, adds an option to pick one
|
||||
*
|
||||
* If there are any (ie one or more) subtitle tracks, adds an option to disable or pick one
|
||||
*
|
||||
* @param item the media item to build for, typically an Episode or Movie
|
||||
* @param seriesId the item's series or null if not a TV episode; a non-null value will include a "Go to Series" option
|
||||
* @param sourceId the item's media source UUID
|
||||
* @param onChooseVersion callback to pick a version of the item
|
||||
* @param onChooseTracks callback to pick a track for the given type of the item
|
||||
* @param onShowOverview callback to show overview dialog with media information
|
||||
*/
|
||||
fun buildMoreDialogItems(
|
||||
context: Context,
|
||||
item: BaseItem,
|
||||
seriesId: UUID?,
|
||||
sourceId: UUID?,
|
||||
watched: Boolean,
|
||||
favorite: Boolean,
|
||||
canClearChosenStreams: Boolean,
|
||||
canDelete: Boolean,
|
||||
actions: MoreDialogActions,
|
||||
onChooseVersion: () -> Unit,
|
||||
onChooseTracks: (MediaStreamType) -> Unit,
|
||||
onShowOverview: () -> Unit,
|
||||
onClearChosenStreams: () -> Unit,
|
||||
): List<DialogItem> =
|
||||
buildList {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.play),
|
||||
Icons.Default.PlayArrow,
|
||||
iconColor = Color.Green.copy(alpha = .8f),
|
||||
) {
|
||||
actions.navigateTo(
|
||||
Destination.Playback(
|
||||
item.id,
|
||||
item.resumeMs ?: 0L,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
item.data.mediaSources?.letNotEmpty { sources ->
|
||||
val source =
|
||||
sourceId?.let { sources.firstOrNull { it.id?.toUUIDOrNull() == sourceId } }
|
||||
?: sources.firstOrNull()
|
||||
source?.mediaStreams?.letNotEmpty { streams ->
|
||||
val audioCount = streams.count { it.type == MediaStreamType.AUDIO }
|
||||
val subtitleCount = streams.count { it.type == MediaStreamType.SUBTITLE }
|
||||
if (audioCount > 1) {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(
|
||||
R.string.choose_stream,
|
||||
context.getString(R.string.audio),
|
||||
),
|
||||
R.string.fa_volume_low,
|
||||
) {
|
||||
onChooseTracks.invoke(MediaStreamType.AUDIO)
|
||||
},
|
||||
)
|
||||
}
|
||||
if (subtitleCount > 0) {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(
|
||||
R.string.choose_stream,
|
||||
context.getString(R.string.subtitles),
|
||||
),
|
||||
R.string.fa_closed_captioning,
|
||||
) {
|
||||
onChooseTracks.invoke(MediaStreamType.SUBTITLE)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
if (sources.size > 1) {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(
|
||||
R.string.choose_stream,
|
||||
context.getString(R.string.version),
|
||||
),
|
||||
R.string.fa_file_video,
|
||||
) {
|
||||
onChooseVersion.invoke()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
add(
|
||||
DialogItem(
|
||||
text = R.string.add_to_playlist,
|
||||
iconStringRes = R.string.fa_list_ul,
|
||||
) {
|
||||
actions.onClickAddPlaylist.invoke(item.id)
|
||||
},
|
||||
)
|
||||
if (canDelete) {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.delete),
|
||||
Icons.Default.Delete,
|
||||
iconColor = Color.Red.copy(alpha = .8f),
|
||||
) {
|
||||
actions.onClickDelete.invoke(item)
|
||||
},
|
||||
)
|
||||
}
|
||||
add(
|
||||
DialogItem(
|
||||
text = if (watched) R.string.mark_unwatched else R.string.mark_watched,
|
||||
iconStringRes = if (watched) R.string.fa_eye else R.string.fa_eye_slash,
|
||||
) {
|
||||
actions.onClickWatch.invoke(item.id, !watched)
|
||||
},
|
||||
)
|
||||
add(
|
||||
DialogItem(
|
||||
text = if (favorite) R.string.remove_favorite else R.string.add_favorite,
|
||||
iconStringRes = R.string.fa_heart,
|
||||
iconColor = if (favorite) Color.Red else Color.Unspecified,
|
||||
) {
|
||||
actions.onClickFavorite.invoke(item.id, !favorite)
|
||||
},
|
||||
)
|
||||
item.data.albumId?.let { albumId ->
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.go_to_album),
|
||||
R.string.fa_compact_disc,
|
||||
) {
|
||||
actions.navigateTo(
|
||||
Destination.MediaItem(
|
||||
albumId,
|
||||
BaseItemKind.MUSIC_ALBUM,
|
||||
null,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
item.data.artistItems?.firstOrNull()?.id?.let { artistId ->
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.go_to_artist),
|
||||
R.string.fa_user,
|
||||
) {
|
||||
actions.navigateTo(
|
||||
Destination.MediaItem(
|
||||
artistId,
|
||||
BaseItemKind.MUSIC_ARTIST,
|
||||
null,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
seriesId?.let {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.go_to_series),
|
||||
Icons.AutoMirrored.Filled.ArrowForward,
|
||||
) {
|
||||
actions.navigateTo(
|
||||
Destination.MediaItem(
|
||||
seriesId,
|
||||
BaseItemKind.SERIES,
|
||||
null,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
if (item.data.mediaSources?.isNotEmpty() == true) {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.media_information),
|
||||
Icons.Default.Info,
|
||||
) {
|
||||
onShowOverview.invoke()
|
||||
},
|
||||
)
|
||||
}
|
||||
if (canClearChosenStreams) {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.clear_track_choices),
|
||||
Icons.Default.Delete,
|
||||
) {
|
||||
onClearChosenStreams()
|
||||
},
|
||||
)
|
||||
}
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.play_with_transcoding),
|
||||
Icons.Default.PlayArrow,
|
||||
) {
|
||||
actions.navigateTo(
|
||||
Destination.Playback(
|
||||
item.id,
|
||||
item.resumeMs ?: 0L,
|
||||
forceTranscoding = true,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
add(
|
||||
DialogItem(
|
||||
text = R.string.send_media_info_log_to_server,
|
||||
iconStringRes = R.string.fa_file_video,
|
||||
) {
|
||||
actions.onSendMediaInfo.invoke(item.id)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fun buildMoreDialogItemsForHome(
|
||||
context: Context,
|
||||
item: BaseItem,
|
||||
|
|
@ -424,29 +191,3 @@ fun buildMoreDialogItemsForHome(
|
|||
},
|
||||
)
|
||||
}
|
||||
|
||||
fun buildMoreDialogItemsForPerson(
|
||||
context: Context,
|
||||
person: Person,
|
||||
actions: MoreDialogActions,
|
||||
): List<DialogItem> =
|
||||
buildList {
|
||||
val itemId = person.id
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.go_to),
|
||||
Icons.Default.ArrowForward,
|
||||
) {
|
||||
actions.navigateTo(Destination.MediaItem(itemId, BaseItemKind.PERSON, null))
|
||||
},
|
||||
)
|
||||
add(
|
||||
DialogItem(
|
||||
text = if (person.favorite) R.string.remove_favorite else R.string.add_favorite,
|
||||
iconStringRes = R.string.fa_heart,
|
||||
iconColor = if (person.favorite) Color.Red else Color.Unspecified,
|
||||
) {
|
||||
actions.onClickFavorite.invoke(itemId, !person.favorite)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,14 +72,15 @@ import com.github.damontecres.wholphin.services.NavigationManager
|
|||
import com.github.damontecres.wholphin.ui.DefaultItemFields
|
||||
import com.github.damontecres.wholphin.ui.cards.ItemCardImage
|
||||
import com.github.damontecres.wholphin.ui.components.BasicDialog
|
||||
import com.github.damontecres.wholphin.ui.components.ConfirmDeleteDialog
|
||||
import com.github.damontecres.wholphin.ui.components.DialogParams
|
||||
import com.github.damontecres.wholphin.ui.components.DialogPopup
|
||||
import com.github.damontecres.wholphin.ui.components.ContextMenu
|
||||
import com.github.damontecres.wholphin.ui.components.ContextMenuActions
|
||||
import com.github.damontecres.wholphin.ui.components.ContextMenuDialog
|
||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||
import com.github.damontecres.wholphin.ui.components.ExpandableFaButton
|
||||
import com.github.damontecres.wholphin.ui.components.ExpandablePlayButton
|
||||
import com.github.damontecres.wholphin.ui.components.FilterByButton
|
||||
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
||||
import com.github.damontecres.wholphin.ui.components.MusicContextActions
|
||||
import com.github.damontecres.wholphin.ui.components.Optional
|
||||
import com.github.damontecres.wholphin.ui.components.OverviewText
|
||||
import com.github.damontecres.wholphin.ui.components.SortByButton
|
||||
|
|
@ -87,10 +88,8 @@ import com.github.damontecres.wholphin.ui.components.TextButton
|
|||
import com.github.damontecres.wholphin.ui.data.AddPlaylistViewModel
|
||||
import com.github.damontecres.wholphin.ui.data.BoxSetSortOptions
|
||||
import com.github.damontecres.wholphin.ui.data.SortAndDirection
|
||||
import com.github.damontecres.wholphin.ui.detail.music.MusicMoreDialogActions
|
||||
import com.github.damontecres.wholphin.ui.detail.music.MusicQueueMarker
|
||||
import com.github.damontecres.wholphin.ui.detail.music.MusicViewModel
|
||||
import com.github.damontecres.wholphin.ui.detail.music.buildMoreDialogForMusic
|
||||
import com.github.damontecres.wholphin.ui.enableMarquee
|
||||
import com.github.damontecres.wholphin.ui.equalsNotNull
|
||||
import com.github.damontecres.wholphin.ui.formatDateTime
|
||||
|
|
@ -371,7 +370,7 @@ fun PlaylistDetails(
|
|||
val state by viewModel.state.collectAsState()
|
||||
val musicState by viewModel.musicState.collectAsState()
|
||||
|
||||
var longClickDialog by remember { mutableStateOf<DialogParams?>(null) }
|
||||
var showContextMenu by remember { mutableStateOf<ContextMenu?>(null) }
|
||||
var showConfirmTypeDialog by remember { mutableStateOf<Triple<Int, BaseItem, Boolean>?>(null) }
|
||||
var showPlaylistDialog by remember { mutableStateOf<Optional<UUID>>(Optional.absent()) }
|
||||
val addPlaylistState by addToPlaylistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
||||
|
|
@ -404,34 +403,40 @@ fun PlaylistDetails(
|
|||
}
|
||||
}
|
||||
}
|
||||
var showDeleteDialog by remember { mutableStateOf<BaseItem?>(null) }
|
||||
val musicMoreActions =
|
||||
MusicMoreDialogActions(
|
||||
onNavigate = { viewModel.navigationManager.navigateTo(it) },
|
||||
val musicContextActions =
|
||||
MusicContextActions(
|
||||
navigateTo = { viewModel.navigationManager.navigateTo(it) },
|
||||
onClickPlay = { index, item -> play(index, item, false, MediaType.AUDIO) },
|
||||
onClickPlayNext = { index, item -> viewModel.playNext(item) },
|
||||
onClickAddToQueue = { index, item -> viewModel.addToQueue(item, Int.MAX_VALUE) },
|
||||
onClickAddToQueue = { item -> viewModel.addToQueue(item, Int.MAX_VALUE) },
|
||||
onClickFavorite = { id, favorite -> viewModel.setFavorite(id, favorite) },
|
||||
onClickAddPlaylist = { itemId ->
|
||||
addToPlaylistViewModel.loadPlaylists(MediaType.AUDIO)
|
||||
showPlaylistDialog.makePresent(itemId)
|
||||
},
|
||||
onClickRemoveFromQueue = {},
|
||||
onClickDelete = { showDeleteDialog = it },
|
||||
)
|
||||
val moreActions =
|
||||
MoreDialogActions(
|
||||
navigateTo = { viewModel.navigationManager.navigateTo(it) },
|
||||
onClickWatch = { id, watched -> viewModel.setWatched(id, watched) },
|
||||
onClickFavorite = { id, favorite -> viewModel.setFavorite(id, favorite) },
|
||||
onClickAddPlaylist = { itemId ->
|
||||
addToPlaylistViewModel.loadPlaylists(MediaType.VIDEO)
|
||||
showPlaylistDialog.makePresent(itemId)
|
||||
},
|
||||
onSendMediaInfo = viewModel::sendMediaReport,
|
||||
onClickDelete = { showDeleteDialog = it },
|
||||
onClickAddToQueue = { viewModel.addToQueue(it, 0) },
|
||||
onClickRemoveFromQueue = { _, _ -> },
|
||||
onDeleteItem = viewModel::deleteItem,
|
||||
)
|
||||
val contextActions =
|
||||
remember {
|
||||
ContextMenuActions(
|
||||
navigateTo = { viewModel.navigationManager.navigateTo(it) },
|
||||
onClickWatch = { id, watched -> viewModel.setWatched(id, watched) },
|
||||
onClickFavorite = { id, favorite -> viewModel.setFavorite(id, favorite) },
|
||||
onClickAddPlaylist = { itemId ->
|
||||
addToPlaylistViewModel.loadPlaylists(MediaType.VIDEO)
|
||||
showPlaylistDialog.makePresent(itemId)
|
||||
},
|
||||
onSendMediaInfo = viewModel::sendMediaReport,
|
||||
onDeleteItem = viewModel::deleteItem,
|
||||
onClickAddToQueue = { viewModel.addToQueue(it, 0) },
|
||||
onShowOverview = {},
|
||||
onChooseVersion = { _, _ -> },
|
||||
onChooseTracks = {},
|
||||
onClearChosenStreams = {},
|
||||
onClickRemoveFromNextUp = {},
|
||||
)
|
||||
}
|
||||
|
||||
PlaylistDetailsContent(
|
||||
loadingState = state.loading,
|
||||
|
|
@ -448,43 +453,41 @@ fun PlaylistDetails(
|
|||
}
|
||||
},
|
||||
onLongClickIndex = { index, item ->
|
||||
longClickDialog =
|
||||
DialogParams(
|
||||
fromLongClick = true,
|
||||
title = item.name ?: "",
|
||||
items =
|
||||
if (item.type == BaseItemKind.AUDIO) {
|
||||
buildMoreDialogForMusic(
|
||||
context = context,
|
||||
actions = musicMoreActions,
|
||||
item = item,
|
||||
index = index,
|
||||
canRemove = false,
|
||||
canDelete = viewModel.canDelete(item, preferences.appPreferences),
|
||||
)
|
||||
} else {
|
||||
buildMoreDialogItemsForHome(
|
||||
context = context,
|
||||
item = item,
|
||||
seriesId = item.data.seriesId,
|
||||
playbackPosition = item.playbackPosition,
|
||||
watched = item.played,
|
||||
favorite = item.favorite,
|
||||
canDelete = viewModel.canDelete(item, preferences.appPreferences),
|
||||
actions = moreActions,
|
||||
)
|
||||
},
|
||||
)
|
||||
showContextMenu =
|
||||
if (item.type == BaseItemKind.AUDIO) {
|
||||
ContextMenu.ForMusic(
|
||||
fromLongClick = true,
|
||||
item = item,
|
||||
index = index,
|
||||
canDelete = viewModel.canDelete(item, preferences.appPreferences),
|
||||
canRemoveFromQueue = false,
|
||||
actions = musicContextActions,
|
||||
)
|
||||
} else {
|
||||
ContextMenu.ForBaseItem(
|
||||
fromLongClick = true,
|
||||
item = item,
|
||||
chosenStreams = null,
|
||||
showGoTo = true,
|
||||
showStreamChoices = false,
|
||||
canDelete = viewModel.canDelete(item, preferences.appPreferences),
|
||||
canRemoveContinueWatching = false,
|
||||
canRemoveNextUp = false,
|
||||
actions = contextActions,
|
||||
)
|
||||
}
|
||||
},
|
||||
filterAndSort = state.filterAndSort,
|
||||
onFilterAndSortChange = viewModel::loadItems,
|
||||
getPossibleFilterValues = viewModel::getFilterOptionValues,
|
||||
modifier = modifier,
|
||||
)
|
||||
longClickDialog?.let { params ->
|
||||
DialogPopup(
|
||||
params = params,
|
||||
onDismissRequest = { longClickDialog = null },
|
||||
showContextMenu?.let { contextMenu ->
|
||||
ContextMenuDialog(
|
||||
onDismissRequest = { showContextMenu = null },
|
||||
getMediaSource = null,
|
||||
contextMenu = contextMenu,
|
||||
preferredSubtitleLanguage = null,
|
||||
)
|
||||
}
|
||||
showConfirmTypeDialog?.let { (index, item, shuffle) ->
|
||||
|
|
@ -510,16 +513,6 @@ fun PlaylistDetails(
|
|||
elevation = 3.dp,
|
||||
)
|
||||
}
|
||||
showDeleteDialog?.let { item ->
|
||||
ConfirmDeleteDialog(
|
||||
itemTitle = item.title ?: "",
|
||||
onCancel = { showDeleteDialog = null },
|
||||
onConfirm = {
|
||||
viewModel.deleteItem(item)
|
||||
showDeleteDialog = null
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ fun CollectionButtons(
|
|||
getPossibleFilterValues: suspend (ItemFilterBy<*>) -> List<FilterValueOption>,
|
||||
onClickViewOptions: () -> Unit,
|
||||
favoriteOnClick: () -> Unit,
|
||||
deleteOnClick: () -> Unit,
|
||||
onConfirmDelete: () -> Unit,
|
||||
canDelete: Boolean,
|
||||
moreOnClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
|
|
@ -91,9 +91,9 @@ fun CollectionButtons(
|
|||
if (canDelete) {
|
||||
item("delete") {
|
||||
DeleteButton(
|
||||
onClick = deleteOnClick,
|
||||
modifier =
|
||||
Modifier,
|
||||
title = state.collection?.title ?: "",
|
||||
onConfirmDelete = onConfirmDelete,
|
||||
modifier = Modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package com.github.damontecres.wholphin.ui.detail.collection
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.SharedTransitionLayout
|
||||
import androidx.compose.animation.fadeIn
|
||||
|
|
@ -15,9 +14,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.relocation.BringIntoViewRequester
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.PlayArrow
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
|
|
@ -32,7 +28,6 @@ import androidx.compose.ui.focus.FocusRequester
|
|||
import androidx.compose.ui.focus.focusProperties
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
|
@ -43,10 +38,9 @@ import com.github.damontecres.wholphin.data.filter.ItemFilterBy
|
|||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.data.model.GetItemsFilter
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.ui.components.ConfirmDeleteDialog
|
||||
import com.github.damontecres.wholphin.ui.components.DialogItem
|
||||
import com.github.damontecres.wholphin.ui.components.DialogParams
|
||||
import com.github.damontecres.wholphin.ui.components.DialogPopup
|
||||
import com.github.damontecres.wholphin.ui.components.ContextMenu
|
||||
import com.github.damontecres.wholphin.ui.components.ContextMenuActions
|
||||
import com.github.damontecres.wholphin.ui.components.ContextMenuDialog
|
||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||
import com.github.damontecres.wholphin.ui.components.HeaderUtils
|
||||
import com.github.damontecres.wholphin.ui.components.HiddenFocusBox
|
||||
|
|
@ -57,10 +51,8 @@ import com.github.damontecres.wholphin.ui.data.ItemDetailsDialog
|
|||
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialogInfo
|
||||
import com.github.damontecres.wholphin.ui.data.RowColumn
|
||||
import com.github.damontecres.wholphin.ui.data.SortAndDirection
|
||||
import com.github.damontecres.wholphin.ui.detail.MoreDialogActions
|
||||
import com.github.damontecres.wholphin.ui.detail.PlaylistDialog
|
||||
import com.github.damontecres.wholphin.ui.detail.PlaylistLoadingState
|
||||
import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItemsForHome
|
||||
import com.github.damontecres.wholphin.ui.main.HomePageHeader
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
|
|
@ -85,53 +77,58 @@ fun CollectionDetails(
|
|||
val state by viewModel.state.collectAsState()
|
||||
|
||||
// Dialogs
|
||||
var moreDialog by remember { mutableStateOf<DialogParams?>(null) }
|
||||
var showContextMenu by remember { mutableStateOf<ContextMenu?>(null) }
|
||||
var showPlaylistDialog by remember { mutableStateOf<Optional<UUID>>(Optional.absent()) }
|
||||
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
||||
var showDeleteDialog by remember { mutableStateOf<Pair<RowColumn?, BaseItem>?>(null) }
|
||||
var showViewOptionsDialog by remember { mutableStateOf(false) }
|
||||
var overviewDialog by remember { mutableStateOf<ItemDetailsDialogInfo?>(null) }
|
||||
|
||||
fun contextActionsFor(position: RowColumn?) =
|
||||
ContextMenuActions(
|
||||
navigateTo = viewModel::navigateTo,
|
||||
onClickWatch = { itemId, watched ->
|
||||
viewModel.setWatched(itemId, watched, position)
|
||||
},
|
||||
onClickFavorite = { itemId, favorite ->
|
||||
viewModel.setFavorite(itemId, favorite, position)
|
||||
},
|
||||
onClickAddPlaylist = { itemId ->
|
||||
playlistViewModel.loadPlaylists(MediaType.VIDEO)
|
||||
showPlaylistDialog.makePresent(itemId)
|
||||
},
|
||||
onSendMediaInfo = viewModel.mediaReportService::sendReportFor,
|
||||
onDeleteItem = { viewModel.deleteItem(it, position) },
|
||||
onShowOverview = { overviewDialog = ItemDetailsDialogInfo(it) },
|
||||
onChooseVersion = { _, _ ->
|
||||
// Not supported on this page
|
||||
},
|
||||
onChooseTracks = { result ->
|
||||
// Not supported on this page
|
||||
},
|
||||
onClearChosenStreams = {
|
||||
// Not supported on this page
|
||||
},
|
||||
)
|
||||
|
||||
// Actions
|
||||
val onClickItem =
|
||||
remember {
|
||||
{ _: RowColumn, item: BaseItem -> viewModel.navigate(item.destination()) }
|
||||
{ _: RowColumn, item: BaseItem -> viewModel.navigateTo(item.destination()) }
|
||||
}
|
||||
val onLongClickItem =
|
||||
remember {
|
||||
{ position: RowColumn, item: BaseItem ->
|
||||
val dialogItems =
|
||||
buildMoreDialogItemsForHome(
|
||||
context = context,
|
||||
item = item,
|
||||
seriesId = item.data.seriesId,
|
||||
playbackPosition = item.playbackPosition,
|
||||
watched = item.played,
|
||||
favorite = item.favorite,
|
||||
canDelete = viewModel.canDelete(item, preferences.appPreferences),
|
||||
actions =
|
||||
MoreDialogActions(
|
||||
navigateTo = viewModel::navigate,
|
||||
onClickWatch = { itemId, watched ->
|
||||
viewModel.setWatched(itemId, watched, position)
|
||||
},
|
||||
onClickFavorite = { itemId, favorite ->
|
||||
viewModel.setFavorite(itemId, favorite, position)
|
||||
},
|
||||
onClickAddPlaylist = { itemId ->
|
||||
playlistViewModel.loadPlaylists(MediaType.VIDEO)
|
||||
showPlaylistDialog.makePresent(itemId)
|
||||
},
|
||||
onSendMediaInfo = viewModel.mediaReportService::sendReportFor,
|
||||
onClickDelete = { item -> showDeleteDialog = Pair(position, item) },
|
||||
onClickAddToQueue = { viewModel.addToQueue(it, 0) },
|
||||
),
|
||||
)
|
||||
moreDialog =
|
||||
DialogParams(
|
||||
showContextMenu =
|
||||
ContextMenu.ForBaseItem(
|
||||
fromLongClick = true,
|
||||
title = item.title ?: "",
|
||||
items = dialogItems,
|
||||
item = item,
|
||||
chosenStreams = null,
|
||||
showGoTo = true,
|
||||
showStreamChoices = false,
|
||||
canDelete = viewModel.canDelete(item, preferences.appPreferences),
|
||||
canRemoveContinueWatching = false,
|
||||
canRemoveNextUp = false,
|
||||
actions = contextActionsFor(position),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -144,7 +141,7 @@ fun CollectionDetails(
|
|||
{ filter: GetItemsFilter -> viewModel.changeFilter(filter) }
|
||||
}
|
||||
val onClickPlay = { _: RowColumn, item: BaseItem ->
|
||||
viewModel.navigate(Destination.Playback(item = item))
|
||||
viewModel.navigateTo(Destination.Playback(item = item))
|
||||
}
|
||||
val onClickPlayAll =
|
||||
remember {
|
||||
|
|
@ -158,7 +155,7 @@ fun CollectionDetails(
|
|||
sortAndDirection = state.sortAndDirection,
|
||||
filter = state.itemFilter,
|
||||
)
|
||||
viewModel.navigate(dest)
|
||||
viewModel.navigateTo(dest)
|
||||
}
|
||||
}
|
||||
val onClickViewOptions = remember { { showViewOptionsDialog = true } }
|
||||
|
|
@ -201,7 +198,7 @@ fun CollectionDetails(
|
|||
}
|
||||
}
|
||||
},
|
||||
deleteOnClick =
|
||||
onConfirmDelete =
|
||||
remember {
|
||||
{
|
||||
state.collection?.let {
|
||||
|
|
@ -216,37 +213,21 @@ fun CollectionDetails(
|
|||
} ?: false
|
||||
},
|
||||
moreOnClick = {
|
||||
val collection = state.collection!!
|
||||
val items =
|
||||
buildMoreDialogItemsForCollection(
|
||||
context = context,
|
||||
item = collection,
|
||||
favorite = collection.favorite,
|
||||
canDelete = viewModel.canDelete(collection, preferences.appPreferences),
|
||||
onClickPlayAll = onClickPlayAll,
|
||||
actions =
|
||||
MoreDialogActions(
|
||||
navigateTo = viewModel::navigate,
|
||||
onClickWatch = { itemId, watched ->
|
||||
viewModel.setWatched(itemId, watched, null)
|
||||
},
|
||||
onClickFavorite = { itemId, favorite ->
|
||||
viewModel.setFavorite(itemId, favorite, null)
|
||||
},
|
||||
onClickAddPlaylist = { itemId ->
|
||||
playlistViewModel.loadPlaylists(MediaType.VIDEO)
|
||||
showPlaylistDialog.makePresent(itemId)
|
||||
},
|
||||
onSendMediaInfo = viewModel.mediaReportService::sendReportFor,
|
||||
onClickDelete = { item -> showDeleteDialog = Pair(null, item) },
|
||||
onClickAddToQueue = { viewModel.addToQueue(it, 0) },
|
||||
),
|
||||
)
|
||||
moreDialog =
|
||||
DialogParams(
|
||||
showContextMenu =
|
||||
ContextMenu.ForBaseItem(
|
||||
fromLongClick = false,
|
||||
title = collection.title ?: "",
|
||||
items = items,
|
||||
item = state.collection!!,
|
||||
chosenStreams = null,
|
||||
showGoTo = false,
|
||||
showStreamChoices = false,
|
||||
canDelete =
|
||||
viewModel.canDelete(
|
||||
state.collection!!,
|
||||
preferences.appPreferences,
|
||||
),
|
||||
canRemoveContinueWatching = false,
|
||||
canRemoveNextUp = false,
|
||||
actions = contextActionsFor(null),
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
@ -259,14 +240,22 @@ fun CollectionDetails(
|
|||
onViewOptionsChange = viewModel::changeViewOptions,
|
||||
)
|
||||
}
|
||||
moreDialog?.let { params ->
|
||||
DialogPopup(
|
||||
showDialog = true,
|
||||
title = params.title,
|
||||
dialogItems = params.items,
|
||||
onDismissRequest = { moreDialog = null },
|
||||
dismissOnClick = true,
|
||||
waitToLoad = params.fromLongClick,
|
||||
overviewDialog?.let { info ->
|
||||
ItemDetailsDialog(
|
||||
info = info,
|
||||
showFilePath =
|
||||
viewModel.serverRepository.currentUserDto.value
|
||||
?.policy
|
||||
?.isAdministrator == true,
|
||||
onDismissRequest = { overviewDialog = null },
|
||||
)
|
||||
}
|
||||
showContextMenu?.let { contextMenu ->
|
||||
ContextMenuDialog(
|
||||
onDismissRequest = { showContextMenu = null },
|
||||
getMediaSource = null,
|
||||
contextMenu = contextMenu,
|
||||
preferredSubtitleLanguage = null,
|
||||
)
|
||||
}
|
||||
showPlaylistDialog.compose { itemId ->
|
||||
|
|
@ -286,26 +275,6 @@ fun CollectionDetails(
|
|||
elevation = 3.dp,
|
||||
)
|
||||
}
|
||||
showDeleteDialog?.let { (position, item) ->
|
||||
ConfirmDeleteDialog(
|
||||
itemTitle = item.title ?: "",
|
||||
onCancel = { showDeleteDialog = null },
|
||||
onConfirm = {
|
||||
viewModel.deleteItem(item, position)
|
||||
showDeleteDialog = null
|
||||
},
|
||||
)
|
||||
}
|
||||
overviewDialog?.let { info ->
|
||||
ItemDetailsDialog(
|
||||
info = info,
|
||||
showFilePath =
|
||||
viewModel.serverRepository.currentUserDto.value
|
||||
?.policy
|
||||
?.isAdministrator == true,
|
||||
onDismissRequest = { overviewDialog = null },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
@ -324,7 +293,7 @@ fun CollectionDetailsContent(
|
|||
onClickViewOptions: () -> Unit,
|
||||
overviewOnClick: () -> Unit,
|
||||
favoriteOnClick: () -> Unit,
|
||||
deleteOnClick: () -> Unit,
|
||||
onConfirmDelete: () -> Unit,
|
||||
canDelete: Boolean,
|
||||
moreOnClick: () -> Unit,
|
||||
modifier: Modifier,
|
||||
|
|
@ -423,7 +392,7 @@ fun CollectionDetailsContent(
|
|||
getPossibleFilterValues = getPossibleFilterValues,
|
||||
onClickViewOptions = onClickViewOptions,
|
||||
favoriteOnClick = favoriteOnClick,
|
||||
deleteOnClick = deleteOnClick,
|
||||
onConfirmDelete = onConfirmDelete,
|
||||
canDelete = canDelete,
|
||||
moreOnClick = moreOnClick,
|
||||
modifier =
|
||||
|
|
@ -485,60 +454,3 @@ fun CollectionDetailsContent(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun buildMoreDialogItemsForCollection(
|
||||
context: Context,
|
||||
item: BaseItem,
|
||||
favorite: Boolean,
|
||||
canDelete: Boolean,
|
||||
onClickPlayAll: (shuffle: Boolean) -> Unit,
|
||||
actions: MoreDialogActions,
|
||||
): List<DialogItem> =
|
||||
buildList {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.play),
|
||||
Icons.Default.PlayArrow,
|
||||
iconColor = Color.Green.copy(alpha = .8f),
|
||||
) {
|
||||
onClickPlayAll.invoke(false)
|
||||
},
|
||||
)
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.shuffle),
|
||||
R.string.fa_shuffle,
|
||||
) {
|
||||
onClickPlayAll.invoke(true)
|
||||
},
|
||||
)
|
||||
|
||||
add(
|
||||
DialogItem(
|
||||
text = R.string.add_to_playlist,
|
||||
iconStringRes = R.string.fa_list_ul,
|
||||
) {
|
||||
actions.onClickAddPlaylist.invoke(item.id)
|
||||
},
|
||||
)
|
||||
if (canDelete) {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.delete),
|
||||
Icons.Default.Delete,
|
||||
iconColor = Color.Red.copy(alpha = .8f),
|
||||
) {
|
||||
actions.onClickDelete.invoke(item)
|
||||
},
|
||||
)
|
||||
}
|
||||
add(
|
||||
DialogItem(
|
||||
text = if (favorite) R.string.remove_favorite else R.string.add_favorite,
|
||||
iconStringRes = R.string.fa_heart,
|
||||
iconColor = if (favorite) Color.Red else Color.Unspecified,
|
||||
) {
|
||||
actions.onClickFavorite.invoke(item.id, !favorite)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -382,7 +382,7 @@ class CollectionViewModel
|
|||
result.totalRecordCount
|
||||
}
|
||||
|
||||
fun navigate(destination: Destination) {
|
||||
fun navigateTo(destination: Destination) {
|
||||
release()
|
||||
navigationManager.navigateTo(destination)
|
||||
}
|
||||
|
|
@ -393,7 +393,9 @@ class CollectionViewModel
|
|||
position: RowColumn?,
|
||||
) = viewModelScope.launch(Dispatchers.IO + ExceptionHandler()) {
|
||||
favoriteWatchManager.setWatched(itemId, played)
|
||||
if (position != null) {
|
||||
if (itemId == state.value.collection?.id) {
|
||||
refreshCollection()
|
||||
} else if (position != null) {
|
||||
refreshItem(itemId, position, false)
|
||||
}
|
||||
}
|
||||
|
|
@ -404,11 +406,24 @@ class CollectionViewModel
|
|||
position: RowColumn?,
|
||||
) = viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
|
||||
favoriteWatchManager.setFavorite(itemId, favorite)
|
||||
if (position != null) {
|
||||
if (itemId == state.value.collection?.id) {
|
||||
refreshCollection()
|
||||
} else if (position != null) {
|
||||
refreshItem(itemId, position, false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshCollection() {
|
||||
viewModelScope.launchDefault {
|
||||
val collection =
|
||||
api.userLibraryApi
|
||||
.getItem(itemId)
|
||||
.content
|
||||
.let { BaseItem(it, false) }
|
||||
_state.update { it.copy(collection = collection) }
|
||||
}
|
||||
}
|
||||
|
||||
fun canDelete(
|
||||
item: BaseItem,
|
||||
appPreferences: AppPreferences,
|
||||
|
|
|
|||
|
|
@ -31,33 +31,27 @@ import com.github.damontecres.wholphin.data.ChosenStreams
|
|||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.ui.RequestOrRestoreFocus
|
||||
import com.github.damontecres.wholphin.ui.components.ConfirmDeleteDialog
|
||||
import com.github.damontecres.wholphin.ui.components.DialogParams
|
||||
import com.github.damontecres.wholphin.ui.components.DialogPopup
|
||||
import com.github.damontecres.wholphin.ui.components.ContextMenu
|
||||
import com.github.damontecres.wholphin.ui.components.ContextMenuActions
|
||||
import com.github.damontecres.wholphin.ui.components.ContextMenuDialog
|
||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||
import com.github.damontecres.wholphin.ui.components.ExpandablePlayButtons
|
||||
import com.github.damontecres.wholphin.ui.components.HeaderUtils
|
||||
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
||||
import com.github.damontecres.wholphin.ui.components.Optional
|
||||
import com.github.damontecres.wholphin.ui.components.chooseStream
|
||||
import com.github.damontecres.wholphin.ui.components.chooseVersionParams
|
||||
import com.github.damontecres.wholphin.ui.data.AddPlaylistViewModel
|
||||
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialog
|
||||
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialogInfo
|
||||
import com.github.damontecres.wholphin.ui.detail.MoreDialogActions
|
||||
import com.github.damontecres.wholphin.ui.detail.PlaylistDialog
|
||||
import com.github.damontecres.wholphin.ui.detail.PlaylistLoadingState
|
||||
import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItems
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.ui.rememberInt
|
||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jellyfin.sdk.model.api.MediaStreamType
|
||||
import org.jellyfin.sdk.model.api.MediaType
|
||||
import org.jellyfin.sdk.model.extensions.ticks
|
||||
import org.jellyfin.sdk.model.serializer.toUUID
|
||||
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
|
||||
import java.util.UUID
|
||||
import kotlin.time.Duration
|
||||
|
||||
|
|
@ -82,10 +76,8 @@ fun EpisodeDetails(
|
|||
val chosenStreams by viewModel.chosenStreams.observeAsState(null)
|
||||
|
||||
var overviewDialog by remember { mutableStateOf<ItemDetailsDialogInfo?>(null) }
|
||||
var moreDialog by remember { mutableStateOf<DialogParams?>(null) }
|
||||
var chooseVersion by remember { mutableStateOf<DialogParams?>(null) }
|
||||
var showContextMenu by remember { mutableStateOf<ContextMenu?>(null) }
|
||||
var showPlaylistDialog by remember { mutableStateOf<Optional<UUID>>(Optional.absent()) }
|
||||
var showDeleteDialog by remember { mutableStateOf<BaseItem?>(null) }
|
||||
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
||||
val canDelete by viewModel.canDelete.collectAsState()
|
||||
|
||||
|
|
@ -96,22 +88,36 @@ fun EpisodeDetails(
|
|||
?.configuration
|
||||
?.subtitleLanguagePreference
|
||||
|
||||
val moreActions =
|
||||
MoreDialogActions(
|
||||
navigateTo = viewModel::navigateTo,
|
||||
onClickWatch = { itemId, watched ->
|
||||
viewModel.setWatched(itemId, watched)
|
||||
},
|
||||
onClickFavorite = { itemId, favorite ->
|
||||
viewModel.setFavorite(itemId, favorite)
|
||||
},
|
||||
onClickAddPlaylist = { itemId ->
|
||||
playlistViewModel.loadPlaylists(MediaType.VIDEO)
|
||||
showPlaylistDialog.makePresent(itemId)
|
||||
},
|
||||
onSendMediaInfo = viewModel.mediaReportService::sendReportFor,
|
||||
onClickDelete = { showDeleteDialog = it },
|
||||
)
|
||||
val contextActions =
|
||||
remember {
|
||||
ContextMenuActions(
|
||||
navigateTo = viewModel::navigateTo,
|
||||
onClickWatch = viewModel::setWatched,
|
||||
onClickFavorite = viewModel::setFavorite,
|
||||
onClickAddPlaylist = { itemId ->
|
||||
playlistViewModel.loadPlaylists(MediaType.VIDEO)
|
||||
showPlaylistDialog.makePresent(itemId)
|
||||
},
|
||||
onSendMediaInfo = viewModel.mediaReportService::sendReportFor,
|
||||
onDeleteItem = viewModel::deleteItem,
|
||||
onShowOverview = { overviewDialog = ItemDetailsDialogInfo(it) },
|
||||
onChooseVersion = { item, source ->
|
||||
viewModel.savePlayVersion(
|
||||
item,
|
||||
source.id!!.toUUID(),
|
||||
)
|
||||
},
|
||||
onChooseTracks = { result ->
|
||||
viewModel.saveTrackSelection(
|
||||
result.item,
|
||||
result.itemPlayback,
|
||||
result.trackIndex,
|
||||
result.streamType,
|
||||
)
|
||||
},
|
||||
onClearChosenStreams = { viewModel.clearChosenStreams(it) },
|
||||
)
|
||||
}
|
||||
|
||||
when (val state = loading) {
|
||||
is LoadingState.Error -> {
|
||||
|
|
@ -152,75 +158,17 @@ fun EpisodeDetails(
|
|||
ItemDetailsDialogInfo(ep)
|
||||
},
|
||||
moreOnClick = {
|
||||
moreDialog =
|
||||
DialogParams(
|
||||
showContextMenu =
|
||||
ContextMenu.ForBaseItem(
|
||||
fromLongClick = false,
|
||||
title = ep.name + " (${ep.data.productionYear ?: ""})",
|
||||
items =
|
||||
buildMoreDialogItems(
|
||||
context = context,
|
||||
item = ep,
|
||||
watched = ep.data.userData?.played ?: false,
|
||||
favorite = ep.data.userData?.isFavorite ?: false,
|
||||
seriesId = ep.data.seriesId,
|
||||
sourceId = chosenStreams?.source?.id?.toUUIDOrNull(),
|
||||
canClearChosenStreams = chosenStreams?.itemPlayback != null || chosenStreams?.plc != null,
|
||||
actions = moreActions,
|
||||
onChooseVersion = {
|
||||
chooseVersion =
|
||||
chooseVersionParams(
|
||||
context,
|
||||
ep.data.mediaSources!!,
|
||||
chosenStreams?.source?.id?.toUUIDOrNull(),
|
||||
) { idx ->
|
||||
val source = ep.data.mediaSources!![idx]
|
||||
viewModel.savePlayVersion(
|
||||
ep,
|
||||
source.id!!.toUUID(),
|
||||
)
|
||||
}
|
||||
moreDialog = null
|
||||
},
|
||||
onChooseTracks = { type ->
|
||||
viewModel.streamChoiceService
|
||||
.chooseSource(
|
||||
ep.data,
|
||||
chosenStreams?.itemPlayback,
|
||||
)?.let { source ->
|
||||
chooseVersion =
|
||||
chooseStream(
|
||||
context = context,
|
||||
streams = source.mediaStreams.orEmpty(),
|
||||
currentIndex =
|
||||
if (type == MediaStreamType.AUDIO) {
|
||||
chosenStreams?.audioStream?.index
|
||||
} else {
|
||||
chosenStreams?.subtitleStream?.index
|
||||
},
|
||||
type = type,
|
||||
onClick = { trackIndex ->
|
||||
viewModel.saveTrackSelection(
|
||||
ep,
|
||||
chosenStreams?.itemPlayback,
|
||||
trackIndex,
|
||||
type,
|
||||
)
|
||||
},
|
||||
preferredSubtitleLanguage = preferredSubtitleLanguage,
|
||||
)
|
||||
}
|
||||
},
|
||||
onShowOverview = {
|
||||
val source = chosenStreams?.source ?: ep.data.mediaSources?.firstOrNull()
|
||||
if (source != null) {
|
||||
overviewDialog = ItemDetailsDialogInfo(ep)
|
||||
}
|
||||
},
|
||||
onClearChosenStreams = {
|
||||
viewModel.clearChosenStreams(chosenStreams)
|
||||
},
|
||||
canDelete = canDelete,
|
||||
),
|
||||
item = ep,
|
||||
chosenStreams = chosenStreams,
|
||||
showGoTo = false,
|
||||
showStreamChoices = true,
|
||||
canDelete = canDelete,
|
||||
canRemoveContinueWatching = false,
|
||||
canRemoveNextUp = false,
|
||||
actions = contextActions,
|
||||
)
|
||||
},
|
||||
watchOnClick = {
|
||||
|
|
@ -230,7 +178,7 @@ fun EpisodeDetails(
|
|||
viewModel.setFavorite(ep.id, !ep.favorite)
|
||||
},
|
||||
canDelete = canDelete,
|
||||
deleteOnClick = { showDeleteDialog = ep },
|
||||
onConfirmDelete = { viewModel.deleteItem(ep) },
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
|
@ -246,24 +194,12 @@ fun EpisodeDetails(
|
|||
onDismissRequest = { overviewDialog = null },
|
||||
)
|
||||
}
|
||||
moreDialog?.let { params ->
|
||||
DialogPopup(
|
||||
showDialog = true,
|
||||
title = params.title,
|
||||
dialogItems = params.items,
|
||||
onDismissRequest = { moreDialog = null },
|
||||
dismissOnClick = true,
|
||||
waitToLoad = params.fromLongClick,
|
||||
)
|
||||
}
|
||||
chooseVersion?.let { params ->
|
||||
DialogPopup(
|
||||
showDialog = true,
|
||||
title = params.title,
|
||||
dialogItems = params.items,
|
||||
onDismissRequest = { chooseVersion = null },
|
||||
dismissOnClick = true,
|
||||
waitToLoad = params.fromLongClick,
|
||||
showContextMenu?.let { contextMenu ->
|
||||
ContextMenuDialog(
|
||||
onDismissRequest = { showContextMenu = null },
|
||||
getMediaSource = viewModel.streamChoiceService::chooseSource,
|
||||
contextMenu = contextMenu,
|
||||
preferredSubtitleLanguage = preferredSubtitleLanguage,
|
||||
)
|
||||
}
|
||||
showPlaylistDialog.compose { itemId ->
|
||||
|
|
@ -283,16 +219,6 @@ fun EpisodeDetails(
|
|||
elevation = 3.dp,
|
||||
)
|
||||
}
|
||||
showDeleteDialog?.let { item ->
|
||||
ConfirmDeleteDialog(
|
||||
itemTitle = listOfNotNull(item.title, item.subtitle).joinToString(" - "),
|
||||
onCancel = { showDeleteDialog = null },
|
||||
onConfirm = {
|
||||
viewModel.deleteItem(item)
|
||||
showDeleteDialog = null
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private const val HEADER_ROW = 0
|
||||
|
|
@ -308,7 +234,7 @@ fun EpisodeDetailsContent(
|
|||
favoriteOnClick: () -> Unit,
|
||||
moreOnClick: () -> Unit,
|
||||
canDelete: Boolean,
|
||||
deleteOnClick: () -> Unit,
|
||||
onConfirmDelete: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
|
@ -346,6 +272,7 @@ fun EpisodeDetailsContent(
|
|||
.padding(top = HeaderUtils.topPadding, bottom = 16.dp),
|
||||
)
|
||||
ExpandablePlayButtons(
|
||||
title = ep.title ?: "",
|
||||
resumePosition = resumePosition,
|
||||
watched = dto.userData?.played ?: false,
|
||||
favorite = dto.userData?.isFavorite ?: false,
|
||||
|
|
@ -367,7 +294,7 @@ fun EpisodeDetailsContent(
|
|||
trailers = null,
|
||||
trailerOnClick = {},
|
||||
canDelete = canDelete,
|
||||
deleteOnClick = deleteOnClick,
|
||||
onConfirmDelete = onConfirmDelete,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
|
|
|
|||
|
|
@ -45,25 +45,20 @@ import com.github.damontecres.wholphin.ui.cards.ExtrasRow
|
|||
import com.github.damontecres.wholphin.ui.cards.ItemRow
|
||||
import com.github.damontecres.wholphin.ui.cards.PersonRow
|
||||
import com.github.damontecres.wholphin.ui.cards.SeasonCard
|
||||
import com.github.damontecres.wholphin.ui.components.ConfirmDeleteDialog
|
||||
import com.github.damontecres.wholphin.ui.components.DialogParams
|
||||
import com.github.damontecres.wholphin.ui.components.DialogPopup
|
||||
import com.github.damontecres.wholphin.ui.components.ContextMenu
|
||||
import com.github.damontecres.wholphin.ui.components.ContextMenuActions
|
||||
import com.github.damontecres.wholphin.ui.components.ContextMenuDialog
|
||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||
import com.github.damontecres.wholphin.ui.components.ExpandablePlayButtons
|
||||
import com.github.damontecres.wholphin.ui.components.HeaderUtils
|
||||
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
||||
import com.github.damontecres.wholphin.ui.components.Optional
|
||||
import com.github.damontecres.wholphin.ui.components.chooseStream
|
||||
import com.github.damontecres.wholphin.ui.components.chooseVersionParams
|
||||
import com.github.damontecres.wholphin.ui.components.PersonContextActions
|
||||
import com.github.damontecres.wholphin.ui.data.AddPlaylistViewModel
|
||||
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialog
|
||||
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialogInfo
|
||||
import com.github.damontecres.wholphin.ui.detail.MoreDialogActions
|
||||
import com.github.damontecres.wholphin.ui.detail.PlaylistDialog
|
||||
import com.github.damontecres.wholphin.ui.detail.PlaylistLoadingState
|
||||
import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItems
|
||||
import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItemsForHome
|
||||
import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItemsForPerson
|
||||
import com.github.damontecres.wholphin.ui.discover.DiscoverRow
|
||||
import com.github.damontecres.wholphin.ui.discover.DiscoverRowData
|
||||
import com.github.damontecres.wholphin.ui.letNotEmpty
|
||||
|
|
@ -74,11 +69,9 @@ import com.github.damontecres.wholphin.util.DiscoverRequestType
|
|||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import org.jellyfin.sdk.model.api.MediaStreamType
|
||||
import org.jellyfin.sdk.model.api.MediaType
|
||||
import org.jellyfin.sdk.model.extensions.ticks
|
||||
import org.jellyfin.sdk.model.serializer.toUUID
|
||||
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
|
||||
import java.util.UUID
|
||||
import kotlin.time.Duration
|
||||
|
||||
|
|
@ -101,11 +94,9 @@ fun MovieDetails(
|
|||
val state by viewModel.state.collectAsState()
|
||||
|
||||
var overviewDialog by remember { mutableStateOf<ItemDetailsDialogInfo?>(null) }
|
||||
var moreDialog by remember { mutableStateOf<DialogParams?>(null) }
|
||||
var chooseVersion by remember { mutableStateOf<DialogParams?>(null) }
|
||||
var showContextMenu by remember { mutableStateOf<ContextMenu?>(null) }
|
||||
var showPlaylistDialog by remember { mutableStateOf<Optional<UUID>>(Optional.absent()) }
|
||||
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
||||
var showDeleteDialog by remember { mutableStateOf<BaseItem?>(null) }
|
||||
|
||||
val preferredSubtitleLanguage =
|
||||
viewModel.serverRepository.currentUserDto
|
||||
|
|
@ -114,22 +105,36 @@ fun MovieDetails(
|
|||
?.configuration
|
||||
?.subtitleLanguagePreference
|
||||
|
||||
val moreActions =
|
||||
MoreDialogActions(
|
||||
navigateTo = viewModel::navigateTo,
|
||||
onClickWatch = { itemId, watched ->
|
||||
viewModel.setWatched(itemId, watched)
|
||||
},
|
||||
onClickFavorite = { itemId, favorite ->
|
||||
viewModel.setFavorite(itemId, favorite)
|
||||
},
|
||||
onClickAddPlaylist = { itemId ->
|
||||
playlistViewModel.loadPlaylists(MediaType.VIDEO)
|
||||
showPlaylistDialog.makePresent(itemId)
|
||||
},
|
||||
onSendMediaInfo = viewModel.mediaReportService::sendReportFor,
|
||||
onClickDelete = { showDeleteDialog = it },
|
||||
)
|
||||
val contextActions =
|
||||
remember {
|
||||
ContextMenuActions(
|
||||
navigateTo = viewModel::navigateTo,
|
||||
onClickWatch = viewModel::setWatched,
|
||||
onClickFavorite = viewModel::setFavorite,
|
||||
onClickAddPlaylist = { itemId ->
|
||||
playlistViewModel.loadPlaylists(MediaType.VIDEO)
|
||||
showPlaylistDialog.makePresent(itemId)
|
||||
},
|
||||
onSendMediaInfo = viewModel.mediaReportService::sendReportFor,
|
||||
onDeleteItem = viewModel::deleteItem,
|
||||
onChooseVersion = { item, source ->
|
||||
viewModel.savePlayVersion(
|
||||
item,
|
||||
source.id!!.toUUID(),
|
||||
)
|
||||
},
|
||||
onChooseTracks = { result ->
|
||||
viewModel.saveTrackSelection(
|
||||
result.item,
|
||||
result.itemPlayback,
|
||||
result.trackIndex,
|
||||
result.streamType,
|
||||
)
|
||||
},
|
||||
onShowOverview = { overviewDialog = ItemDetailsDialogInfo(it) },
|
||||
onClearChosenStreams = { viewModel.clearChosenStreams(it) },
|
||||
)
|
||||
}
|
||||
|
||||
when (val s = state.loading) {
|
||||
is DataLoadingState.Error -> {
|
||||
|
|
@ -183,72 +188,17 @@ fun MovieDetails(
|
|||
ItemDetailsDialogInfo(movie)
|
||||
},
|
||||
moreOnClick = {
|
||||
moreDialog =
|
||||
DialogParams(
|
||||
showContextMenu =
|
||||
ContextMenu.ForBaseItem(
|
||||
fromLongClick = false,
|
||||
title = movie.name + " (${movie.data.productionYear ?: ""})",
|
||||
items =
|
||||
buildMoreDialogItems(
|
||||
context = context,
|
||||
item = movie,
|
||||
watched = movie.data.userData?.played ?: false,
|
||||
favorite = movie.data.userData?.isFavorite ?: false,
|
||||
seriesId = null,
|
||||
sourceId = chosenStreams?.source?.id?.toUUIDOrNull(),
|
||||
canClearChosenStreams = chosenStreams.let { it?.itemPlayback != null || it?.plc != null },
|
||||
canDelete = state.canDelete,
|
||||
actions = moreActions,
|
||||
onChooseVersion = {
|
||||
chooseVersion =
|
||||
chooseVersionParams(
|
||||
context,
|
||||
movie.data.mediaSources!!,
|
||||
chosenStreams?.source?.id?.toUUIDOrNull(),
|
||||
) { idx ->
|
||||
val source = movie.data.mediaSources!![idx]
|
||||
viewModel.savePlayVersion(
|
||||
movie,
|
||||
source.id!!.toUUID(),
|
||||
)
|
||||
}
|
||||
moreDialog = null
|
||||
},
|
||||
onChooseTracks = { type ->
|
||||
viewModel.streamChoiceService
|
||||
.chooseSource(
|
||||
movie.data,
|
||||
chosenStreams?.itemPlayback,
|
||||
)?.let { source ->
|
||||
chooseVersion =
|
||||
chooseStream(
|
||||
context = context,
|
||||
streams = source.mediaStreams.orEmpty(),
|
||||
type = type,
|
||||
currentIndex =
|
||||
if (type == MediaStreamType.AUDIO) {
|
||||
chosenStreams?.audioStream?.index
|
||||
} else {
|
||||
chosenStreams?.subtitleStream?.index
|
||||
},
|
||||
onClick = { trackIndex ->
|
||||
viewModel.saveTrackSelection(
|
||||
movie,
|
||||
chosenStreams?.itemPlayback,
|
||||
trackIndex,
|
||||
type,
|
||||
)
|
||||
},
|
||||
preferredSubtitleLanguage = preferredSubtitleLanguage,
|
||||
)
|
||||
}
|
||||
},
|
||||
onShowOverview = {
|
||||
overviewDialog = ItemDetailsDialogInfo(movie)
|
||||
},
|
||||
onClearChosenStreams = {
|
||||
viewModel.clearChosenStreams(chosenStreams)
|
||||
},
|
||||
),
|
||||
item = movie,
|
||||
chosenStreams = chosenStreams,
|
||||
showGoTo = false,
|
||||
showStreamChoices = true,
|
||||
canDelete = state.canDelete,
|
||||
canRemoveContinueWatching = false,
|
||||
canRemoveNextUp = false,
|
||||
actions = contextActions,
|
||||
)
|
||||
},
|
||||
watchOnClick = {
|
||||
|
|
@ -258,36 +208,29 @@ fun MovieDetails(
|
|||
viewModel.setFavorite(movie.id, !movie.favorite)
|
||||
},
|
||||
onLongClickPerson = { index, person ->
|
||||
val items =
|
||||
buildMoreDialogItemsForPerson(
|
||||
context = context,
|
||||
person = person,
|
||||
actions = moreActions,
|
||||
)
|
||||
moreDialog =
|
||||
DialogParams(
|
||||
showContextMenu =
|
||||
ContextMenu.ForPerson(
|
||||
fromLongClick = true,
|
||||
title = person.name ?: "",
|
||||
items = items,
|
||||
person = person,
|
||||
actions =
|
||||
PersonContextActions(
|
||||
navigateTo = viewModel::navigateTo,
|
||||
onClickFavorite = viewModel::setFavorite,
|
||||
),
|
||||
)
|
||||
},
|
||||
onLongClickSimilar = { index, similar ->
|
||||
val items =
|
||||
buildMoreDialogItemsForHome(
|
||||
context = context,
|
||||
item = similar,
|
||||
seriesId = null,
|
||||
playbackPosition = similar.playbackPosition,
|
||||
watched = similar.played,
|
||||
favorite = similar.favorite,
|
||||
canDelete = false,
|
||||
actions = moreActions,
|
||||
)
|
||||
moreDialog =
|
||||
DialogParams(
|
||||
onLongClickSimilar = { _, similar ->
|
||||
showContextMenu =
|
||||
ContextMenu.ForBaseItem(
|
||||
fromLongClick = true,
|
||||
title = similar.title ?: "",
|
||||
items = items,
|
||||
item = similar,
|
||||
chosenStreams = null,
|
||||
showGoTo = true,
|
||||
showStreamChoices = false,
|
||||
canDelete = false,
|
||||
canRemoveContinueWatching = false,
|
||||
canRemoveNextUp = false,
|
||||
actions = contextActions,
|
||||
)
|
||||
},
|
||||
trailerOnClick = {
|
||||
|
|
@ -300,11 +243,19 @@ fun MovieDetails(
|
|||
viewModel.navigateTo(item.destination)
|
||||
},
|
||||
canDelete = state.canDelete,
|
||||
deleteOnClick = { showDeleteDialog = state.movie },
|
||||
onConfirmDelete = { state.movie?.let { viewModel.deleteItem(it) } },
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
showContextMenu?.let { contextMenu ->
|
||||
ContextMenuDialog(
|
||||
onDismissRequest = { showContextMenu = null },
|
||||
getMediaSource = viewModel.streamChoiceService::chooseSource,
|
||||
contextMenu = contextMenu,
|
||||
preferredSubtitleLanguage = preferredSubtitleLanguage,
|
||||
)
|
||||
}
|
||||
overviewDialog?.let { info ->
|
||||
ItemDetailsDialog(
|
||||
info = info,
|
||||
|
|
@ -315,26 +266,6 @@ fun MovieDetails(
|
|||
onDismissRequest = { overviewDialog = null },
|
||||
)
|
||||
}
|
||||
moreDialog?.let { params ->
|
||||
DialogPopup(
|
||||
showDialog = true,
|
||||
title = params.title,
|
||||
dialogItems = params.items,
|
||||
onDismissRequest = { moreDialog = null },
|
||||
dismissOnClick = true,
|
||||
waitToLoad = params.fromLongClick,
|
||||
)
|
||||
}
|
||||
chooseVersion?.let { params ->
|
||||
DialogPopup(
|
||||
showDialog = true,
|
||||
title = params.title,
|
||||
dialogItems = params.items,
|
||||
onDismissRequest = { chooseVersion = null },
|
||||
dismissOnClick = true,
|
||||
waitToLoad = params.fromLongClick,
|
||||
)
|
||||
}
|
||||
showPlaylistDialog.compose { itemId ->
|
||||
PlaylistDialog(
|
||||
title = stringResource(R.string.add_to_playlist),
|
||||
|
|
@ -352,16 +283,6 @@ fun MovieDetails(
|
|||
elevation = 3.dp,
|
||||
)
|
||||
}
|
||||
showDeleteDialog?.let { item ->
|
||||
ConfirmDeleteDialog(
|
||||
itemTitle = item.title ?: "",
|
||||
onCancel = { showDeleteDialog = null },
|
||||
onConfirm = {
|
||||
viewModel.deleteItem(item)
|
||||
showDeleteDialog = null
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private const val HEADER_ROW = 0
|
||||
|
|
@ -390,7 +311,7 @@ fun MovieDetailsContent(
|
|||
onClickExtra: (Int, ExtrasItem) -> Unit,
|
||||
onClickDiscover: (Int, DiscoverItem) -> Unit,
|
||||
canDelete: Boolean,
|
||||
deleteOnClick: () -> Unit,
|
||||
onConfirmDelete: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
|
@ -430,6 +351,7 @@ fun MovieDetailsContent(
|
|||
.padding(top = HeaderUtils.topPadding, bottom = 16.dp),
|
||||
)
|
||||
ExpandablePlayButtons(
|
||||
title = movie.title ?: "",
|
||||
resumePosition = resumePosition,
|
||||
watched = dto.userData?.played ?: false,
|
||||
favorite = dto.userData?.isFavorite ?: false,
|
||||
|
|
@ -454,7 +376,7 @@ fun MovieDetailsContent(
|
|||
trailerOnClick.invoke(it)
|
||||
},
|
||||
canDelete = canDelete,
|
||||
deleteOnClick = deleteOnClick,
|
||||
onConfirmDelete = onConfirmDelete,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
|
|
|
|||
|
|
@ -61,12 +61,12 @@ import com.github.damontecres.wholphin.ui.DefaultItemFields
|
|||
import com.github.damontecres.wholphin.ui.SlimItemFields
|
||||
import com.github.damontecres.wholphin.ui.cards.BannerCardWithTitle
|
||||
import com.github.damontecres.wholphin.ui.cards.ItemRow
|
||||
import com.github.damontecres.wholphin.ui.components.ConfirmDeleteDialog
|
||||
import com.github.damontecres.wholphin.ui.components.DialogParams
|
||||
import com.github.damontecres.wholphin.ui.components.DialogPopup
|
||||
import com.github.damontecres.wholphin.ui.components.ContextMenu
|
||||
import com.github.damontecres.wholphin.ui.components.ContextMenuDialog
|
||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||
import com.github.damontecres.wholphin.ui.components.GenreText
|
||||
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
||||
import com.github.damontecres.wholphin.ui.components.MusicContextActions
|
||||
import com.github.damontecres.wholphin.ui.components.Optional
|
||||
import com.github.damontecres.wholphin.ui.components.OverviewText
|
||||
import com.github.damontecres.wholphin.ui.components.QuickDetails
|
||||
|
|
@ -308,22 +308,21 @@ fun AlbumDetailsPage(
|
|||
val focusManager = LocalFocusManager.current
|
||||
var showPlaylistDialog by remember { mutableStateOf<Optional<UUID>>(Optional.absent()) }
|
||||
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
||||
var moreDialog by remember { mutableStateOf<DialogParams?>(null) }
|
||||
var showDeleteDialog by remember { mutableStateOf<BaseItem?>(null) }
|
||||
var showContextMenu by remember { mutableStateOf<ContextMenu?>(null) }
|
||||
val moreDialogActions =
|
||||
remember {
|
||||
MusicMoreDialogActions(
|
||||
onNavigate = { viewModel.navigationManager.navigateTo(it) },
|
||||
MusicContextActions(
|
||||
navigateTo = { viewModel.navigationManager.navigateTo(it) },
|
||||
onClickPlay = { index, _ -> viewModel.play(false, index) },
|
||||
onClickPlayNext = { _, song -> viewModel.playNext(song) },
|
||||
onClickAddToQueue = { index, item -> viewModel.addToQueue(item, index) },
|
||||
onClickAddToQueue = { item -> viewModel.addToQueue(item, -1) },
|
||||
onClickFavorite = { itemId, favorite -> viewModel.setFavorite(itemId, favorite) },
|
||||
onClickAddPlaylist = { itemId ->
|
||||
playlistViewModel.loadPlaylists(MediaType.AUDIO)
|
||||
showPlaylistDialog.makePresent(itemId)
|
||||
},
|
||||
onClickRemoveFromQueue = {},
|
||||
onClickDelete = { showDeleteDialog = it },
|
||||
onClickRemoveFromQueue = { _, _ -> },
|
||||
onDeleteItem = viewModel::deleteItem,
|
||||
)
|
||||
}
|
||||
when (val loading = state.loading) {
|
||||
|
|
@ -402,23 +401,18 @@ fun AlbumDetailsPage(
|
|||
)
|
||||
},
|
||||
onClickMore = {
|
||||
moreDialog =
|
||||
DialogParams(
|
||||
showContextMenu =
|
||||
ContextMenu.ForMusic(
|
||||
fromLongClick = false,
|
||||
title = album.name + " (${album.data.productionYear ?: ""})",
|
||||
items =
|
||||
buildMoreDialogForMusic(
|
||||
context = context,
|
||||
actions = moreDialogActions,
|
||||
item = album,
|
||||
index = 0,
|
||||
canRemove = false,
|
||||
canDelete =
|
||||
viewModel.canDelete(
|
||||
album,
|
||||
preferences.appPreferences,
|
||||
),
|
||||
item = album,
|
||||
index = 0,
|
||||
canDelete =
|
||||
viewModel.canDelete(
|
||||
album,
|
||||
preferences.appPreferences,
|
||||
),
|
||||
canRemoveFromQueue = false,
|
||||
actions = moreDialogActions,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
@ -459,45 +453,35 @@ fun AlbumDetailsPage(
|
|||
},
|
||||
onLongClick = {
|
||||
if (song != null) {
|
||||
moreDialog =
|
||||
DialogParams(
|
||||
showContextMenu =
|
||||
ContextMenu.ForMusic(
|
||||
fromLongClick = true,
|
||||
title = song.name ?: "",
|
||||
items =
|
||||
buildMoreDialogForMusic(
|
||||
context = context,
|
||||
actions = moreDialogActions,
|
||||
item = song,
|
||||
index = index,
|
||||
canRemove = false,
|
||||
canDelete =
|
||||
viewModel.canDelete(
|
||||
song,
|
||||
preferences.appPreferences,
|
||||
),
|
||||
item = song,
|
||||
index = index,
|
||||
canDelete =
|
||||
viewModel.canDelete(
|
||||
song,
|
||||
preferences.appPreferences,
|
||||
),
|
||||
canRemoveFromQueue = false,
|
||||
actions = moreDialogActions,
|
||||
)
|
||||
}
|
||||
},
|
||||
onClickMore = {
|
||||
if (song != null) {
|
||||
moreDialog =
|
||||
DialogParams(
|
||||
showContextMenu =
|
||||
ContextMenu.ForMusic(
|
||||
fromLongClick = false,
|
||||
title = song.name ?: "",
|
||||
items =
|
||||
buildMoreDialogForMusic(
|
||||
context = context,
|
||||
actions = moreDialogActions,
|
||||
item = song,
|
||||
index = index,
|
||||
canRemove = false,
|
||||
canDelete =
|
||||
viewModel.canDelete(
|
||||
song,
|
||||
preferences.appPreferences,
|
||||
),
|
||||
item = song,
|
||||
index = index,
|
||||
canDelete =
|
||||
viewModel.canDelete(
|
||||
song,
|
||||
preferences.appPreferences,
|
||||
),
|
||||
canRemoveFromQueue = false,
|
||||
actions = moreDialogActions,
|
||||
)
|
||||
}
|
||||
},
|
||||
|
|
@ -559,23 +543,18 @@ fun AlbumDetailsPage(
|
|||
viewModel.navigationManager.navigateTo(item.destination())
|
||||
},
|
||||
onLongClickItem = { index, item ->
|
||||
moreDialog =
|
||||
DialogParams(
|
||||
showContextMenu =
|
||||
ContextMenu.ForMusic(
|
||||
fromLongClick = true,
|
||||
title = item.name ?: "",
|
||||
items =
|
||||
buildMoreDialogForMusic(
|
||||
context = context,
|
||||
actions = moreDialogActions,
|
||||
item = item,
|
||||
index = index,
|
||||
canRemove = false,
|
||||
canDelete =
|
||||
viewModel.canDelete(
|
||||
item,
|
||||
preferences.appPreferences,
|
||||
),
|
||||
item = item,
|
||||
index = index,
|
||||
canDelete =
|
||||
viewModel.canDelete(
|
||||
item,
|
||||
preferences.appPreferences,
|
||||
),
|
||||
canRemoveFromQueue = false,
|
||||
actions = moreDialogActions,
|
||||
)
|
||||
},
|
||||
cardContent = { index: Int, item: BaseItem?, mod: Modifier, onClick: () -> Unit, onLongClick: () -> Unit ->
|
||||
|
|
@ -597,14 +576,12 @@ fun AlbumDetailsPage(
|
|||
}
|
||||
}
|
||||
}
|
||||
moreDialog?.let { params ->
|
||||
DialogPopup(
|
||||
showDialog = true,
|
||||
title = params.title,
|
||||
dialogItems = params.items,
|
||||
onDismissRequest = { moreDialog = null },
|
||||
dismissOnClick = true,
|
||||
waitToLoad = params.fromLongClick,
|
||||
showContextMenu?.let { contextMenu ->
|
||||
ContextMenuDialog(
|
||||
onDismissRequest = { showContextMenu = null },
|
||||
getMediaSource = null,
|
||||
contextMenu = contextMenu,
|
||||
preferredSubtitleLanguage = null,
|
||||
)
|
||||
}
|
||||
showPlaylistDialog.compose { itemId ->
|
||||
|
|
@ -624,17 +601,6 @@ fun AlbumDetailsPage(
|
|||
elevation = 3.dp,
|
||||
)
|
||||
}
|
||||
showDeleteDialog?.let { item ->
|
||||
ConfirmDeleteDialog(
|
||||
itemTitle = item.title ?: "",
|
||||
onCancel = { showDeleteDialog = null },
|
||||
onConfirm = {
|
||||
viewModel.deleteItem(item)
|
||||
focusRequesters.getOrNull(position.row)?.tryRequestFocus()
|
||||
showDeleteDialog = null
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -59,12 +59,12 @@ import com.github.damontecres.wholphin.ui.DefaultItemFields
|
|||
import com.github.damontecres.wholphin.ui.SlimItemFields
|
||||
import com.github.damontecres.wholphin.ui.cards.BannerCardWithTitle
|
||||
import com.github.damontecres.wholphin.ui.cards.ItemRow
|
||||
import com.github.damontecres.wholphin.ui.components.ConfirmDeleteDialog
|
||||
import com.github.damontecres.wholphin.ui.components.DialogParams
|
||||
import com.github.damontecres.wholphin.ui.components.DialogPopup
|
||||
import com.github.damontecres.wholphin.ui.components.ContextMenu
|
||||
import com.github.damontecres.wholphin.ui.components.ContextMenuDialog
|
||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||
import com.github.damontecres.wholphin.ui.components.GenreText
|
||||
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
||||
import com.github.damontecres.wholphin.ui.components.MusicContextActions
|
||||
import com.github.damontecres.wholphin.ui.components.Optional
|
||||
import com.github.damontecres.wholphin.ui.components.OverviewText
|
||||
import com.github.damontecres.wholphin.ui.components.QuickDetails
|
||||
|
|
@ -312,22 +312,21 @@ fun ArtistDetailsPage(
|
|||
|
||||
var showPlaylistDialog by remember { mutableStateOf<Optional<UUID>>(Optional.absent()) }
|
||||
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
||||
var moreDialog by remember { mutableStateOf<DialogParams?>(null) }
|
||||
var showDeleteDialog by remember { mutableStateOf<BaseItem?>(null) }
|
||||
var showContextMenu by remember { mutableStateOf<ContextMenu?>(null) }
|
||||
val moreDialogActions =
|
||||
remember {
|
||||
MusicMoreDialogActions(
|
||||
onNavigate = { viewModel.navigationManager.navigateTo(it) },
|
||||
MusicContextActions(
|
||||
navigateTo = { viewModel.navigationManager.navigateTo(it) },
|
||||
onClickPlay = { index, item -> viewModel.play(item) },
|
||||
onClickPlayNext = { _, item -> viewModel.playNext(item) },
|
||||
onClickAddToQueue = { index, item -> viewModel.addToQueue(item, index) },
|
||||
onClickAddToQueue = { item -> viewModel.addToQueue(item, -1) },
|
||||
onClickFavorite = { itemId, favorite -> viewModel.setFavorite(itemId, favorite) },
|
||||
onClickAddPlaylist = { itemId ->
|
||||
playlistViewModel.loadPlaylists(MediaType.AUDIO)
|
||||
showPlaylistDialog.makePresent(itemId)
|
||||
},
|
||||
onClickRemoveFromQueue = {},
|
||||
onClickDelete = { showDeleteDialog = it },
|
||||
onClickRemoveFromQueue = { _, _ -> },
|
||||
onDeleteItem = viewModel::deleteItem,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -393,23 +392,18 @@ fun ArtistDetailsPage(
|
|||
)
|
||||
},
|
||||
onClickMore = {
|
||||
moreDialog =
|
||||
DialogParams(
|
||||
showContextMenu =
|
||||
ContextMenu.ForMusic(
|
||||
fromLongClick = false,
|
||||
title = artist.name ?: "",
|
||||
items =
|
||||
buildMoreDialogForMusic(
|
||||
context = context,
|
||||
actions = moreDialogActions,
|
||||
item = artist,
|
||||
index = 0,
|
||||
canRemove = false,
|
||||
canDelete =
|
||||
viewModel.canDelete(
|
||||
artist,
|
||||
preferences.appPreferences,
|
||||
),
|
||||
item = artist,
|
||||
index = 0,
|
||||
canDelete =
|
||||
viewModel.canDelete(
|
||||
artist,
|
||||
preferences.appPreferences,
|
||||
),
|
||||
canRemoveFromQueue = false,
|
||||
actions = moreDialogActions,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
@ -451,45 +445,35 @@ fun ArtistDetailsPage(
|
|||
},
|
||||
onLongClick = {
|
||||
if (song != null) {
|
||||
moreDialog =
|
||||
DialogParams(
|
||||
showContextMenu =
|
||||
ContextMenu.ForMusic(
|
||||
fromLongClick = true,
|
||||
title = song.name ?: "",
|
||||
items =
|
||||
buildMoreDialogForMusic(
|
||||
context = context,
|
||||
actions = moreDialogActions,
|
||||
item = song,
|
||||
index = index,
|
||||
canRemove = false,
|
||||
canDelete =
|
||||
viewModel.canDelete(
|
||||
song,
|
||||
preferences.appPreferences,
|
||||
),
|
||||
item = song,
|
||||
index = index,
|
||||
canDelete =
|
||||
viewModel.canDelete(
|
||||
song,
|
||||
preferences.appPreferences,
|
||||
),
|
||||
canRemoveFromQueue = false,
|
||||
actions = moreDialogActions,
|
||||
)
|
||||
}
|
||||
},
|
||||
onClickMore = {
|
||||
if (song != null) {
|
||||
moreDialog =
|
||||
DialogParams(
|
||||
showContextMenu =
|
||||
ContextMenu.ForMusic(
|
||||
fromLongClick = false,
|
||||
title = song.name ?: "",
|
||||
items =
|
||||
buildMoreDialogForMusic(
|
||||
context = context,
|
||||
actions = moreDialogActions,
|
||||
item = song,
|
||||
index = index,
|
||||
canRemove = false,
|
||||
canDelete =
|
||||
viewModel.canDelete(
|
||||
song,
|
||||
preferences.appPreferences,
|
||||
),
|
||||
item = song,
|
||||
index = index,
|
||||
canDelete =
|
||||
viewModel.canDelete(
|
||||
song,
|
||||
preferences.appPreferences,
|
||||
),
|
||||
canRemoveFromQueue = false,
|
||||
actions = moreDialogActions,
|
||||
)
|
||||
}
|
||||
},
|
||||
|
|
@ -516,23 +500,18 @@ fun ArtistDetailsPage(
|
|||
viewModel.navigationManager.navigateTo(album.destination())
|
||||
},
|
||||
onLongClickItem = { index, album ->
|
||||
moreDialog =
|
||||
DialogParams(
|
||||
showContextMenu =
|
||||
ContextMenu.ForMusic(
|
||||
fromLongClick = true,
|
||||
title = album.name ?: "",
|
||||
items =
|
||||
buildMoreDialogForMusic(
|
||||
context = context,
|
||||
actions = moreDialogActions,
|
||||
item = album,
|
||||
index = index,
|
||||
canRemove = false,
|
||||
canDelete =
|
||||
viewModel.canDelete(
|
||||
album,
|
||||
preferences.appPreferences,
|
||||
),
|
||||
item = album,
|
||||
index = index,
|
||||
canDelete =
|
||||
viewModel.canDelete(
|
||||
album,
|
||||
preferences.appPreferences,
|
||||
),
|
||||
canRemoveFromQueue = false,
|
||||
actions = moreDialogActions,
|
||||
)
|
||||
},
|
||||
cardContent = { index: Int, album: BaseItem?, mod: Modifier, onClick: () -> Unit, onLongClick: () -> Unit ->
|
||||
|
|
@ -588,23 +567,18 @@ fun ArtistDetailsPage(
|
|||
viewModel.navigationManager.navigateTo(item.destination())
|
||||
},
|
||||
onLongClickItem = { index, item ->
|
||||
moreDialog =
|
||||
DialogParams(
|
||||
showContextMenu =
|
||||
ContextMenu.ForMusic(
|
||||
fromLongClick = true,
|
||||
title = item.name ?: "",
|
||||
items =
|
||||
buildMoreDialogForMusic(
|
||||
context = context,
|
||||
actions = moreDialogActions,
|
||||
item = item,
|
||||
index = index,
|
||||
canRemove = false,
|
||||
canDelete =
|
||||
viewModel.canDelete(
|
||||
item,
|
||||
preferences.appPreferences,
|
||||
),
|
||||
item = item,
|
||||
index = index,
|
||||
canDelete =
|
||||
viewModel.canDelete(
|
||||
item,
|
||||
preferences.appPreferences,
|
||||
),
|
||||
canRemoveFromQueue = false,
|
||||
actions = moreDialogActions,
|
||||
)
|
||||
},
|
||||
cardContent = { index: Int, item: BaseItem?, mod: Modifier, onClick: () -> Unit, onLongClick: () -> Unit ->
|
||||
|
|
@ -626,14 +600,12 @@ fun ArtistDetailsPage(
|
|||
}
|
||||
}
|
||||
}
|
||||
moreDialog?.let { params ->
|
||||
DialogPopup(
|
||||
showDialog = true,
|
||||
title = params.title,
|
||||
dialogItems = params.items,
|
||||
onDismissRequest = { moreDialog = null },
|
||||
dismissOnClick = true,
|
||||
waitToLoad = params.fromLongClick,
|
||||
showContextMenu?.let { contextMenu ->
|
||||
ContextMenuDialog(
|
||||
onDismissRequest = { showContextMenu = null },
|
||||
getMediaSource = null,
|
||||
contextMenu = contextMenu,
|
||||
preferredSubtitleLanguage = null,
|
||||
)
|
||||
}
|
||||
showPlaylistDialog.compose { itemId ->
|
||||
|
|
@ -653,17 +625,6 @@ fun ArtistDetailsPage(
|
|||
elevation = 3.dp,
|
||||
)
|
||||
}
|
||||
showDeleteDialog?.let { item ->
|
||||
ConfirmDeleteDialog(
|
||||
itemTitle = item.title ?: "",
|
||||
onCancel = { showDeleteDialog = null },
|
||||
onConfirm = {
|
||||
viewModel.deleteItem(item)
|
||||
focusRequesters.getOrNull(position.row)?.tryRequestFocus()
|
||||
showDeleteDialog = null
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -60,9 +60,10 @@ import com.github.damontecres.wholphin.preferences.BackdropStyle
|
|||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.services.rememberQueue
|
||||
import com.github.damontecres.wholphin.ui.components.BasicDialog
|
||||
import com.github.damontecres.wholphin.ui.components.DialogParams
|
||||
import com.github.damontecres.wholphin.ui.components.DialogPopup
|
||||
import com.github.damontecres.wholphin.ui.components.ContextMenu
|
||||
import com.github.damontecres.wholphin.ui.components.ContextMenuDialog
|
||||
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
||||
import com.github.damontecres.wholphin.ui.components.QueueContextActions
|
||||
import com.github.damontecres.wholphin.ui.findActivity
|
||||
import com.github.damontecres.wholphin.ui.nav.Backdrop
|
||||
import com.github.damontecres.wholphin.ui.playback.PlaybackKeyHandler
|
||||
|
|
@ -129,18 +130,9 @@ fun NowPlayingPage(
|
|||
)
|
||||
}
|
||||
|
||||
val actions =
|
||||
remember {
|
||||
MusicQueueDialogActions(
|
||||
onNavigate = { viewModel.navigationManager.navigateTo(it) },
|
||||
onClickPlay = { index, _ -> viewModel.play(index) },
|
||||
onClickPlayNext = { index, _ -> viewModel.playNext(index) },
|
||||
onClickRemoveFromQueue = { index, _ -> viewModel.removeFromQueue(index) },
|
||||
)
|
||||
}
|
||||
|
||||
var showViewOptionsDialog by remember { mutableStateOf(false) }
|
||||
var itemMoreDialog by remember { mutableStateOf<DialogParams?>(null) }
|
||||
var showContextMenu by remember { mutableStateOf<ContextMenu.ForQueue?>(null) }
|
||||
|
||||
var lyricsHaveFocus by remember { mutableStateOf(false) }
|
||||
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
|
|
@ -209,6 +201,7 @@ fun NowPlayingPage(
|
|||
remember {
|
||||
shrinkHorizontally(shrinkTowards = Alignment.Start) + fadeOut()
|
||||
}
|
||||
//noinspection UnnecessaryFullyQualifiedName
|
||||
androidx.compose.animation.AnimatedVisibility(
|
||||
visible = musicPrefs.showAlbumArt,
|
||||
enter = enter,
|
||||
|
|
@ -250,6 +243,7 @@ fun NowPlayingPage(
|
|||
}
|
||||
}
|
||||
}
|
||||
//noinspection UnnecessaryFullyQualifiedName
|
||||
androidx.compose.animation.AnimatedVisibility(
|
||||
visible = musicPrefs.showVisualizer,
|
||||
enter = enter,
|
||||
|
|
@ -308,17 +302,21 @@ fun NowPlayingPage(
|
|||
val showContextForItem =
|
||||
remember {
|
||||
{ fromLongClick: Boolean, index: Int, song: AudioItem ->
|
||||
itemMoreDialog =
|
||||
DialogParams(
|
||||
title = song.title ?: "",
|
||||
showContextMenu =
|
||||
ContextMenu.ForQueue(
|
||||
fromLongClick = fromLongClick,
|
||||
items =
|
||||
buildMoreDialogForMusicQueue(
|
||||
context = context,
|
||||
actions = actions,
|
||||
item = song,
|
||||
index = index,
|
||||
canRemove = true,
|
||||
item = song,
|
||||
index = index,
|
||||
actions =
|
||||
QueueContextActions(
|
||||
onNavigate = { viewModel.navigationManager.navigateTo(it) },
|
||||
onClickPlay = { index, _ -> viewModel.play(index) },
|
||||
onClickPlayNext = { index, _ -> viewModel.playNext(index) },
|
||||
onClickRemoveFromQueue = { index, _ ->
|
||||
viewModel.removeFromQueue(
|
||||
index,
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -368,14 +366,12 @@ fun NowPlayingPage(
|
|||
LoadingPage(focusEnabled = false)
|
||||
}
|
||||
}
|
||||
itemMoreDialog?.let { params ->
|
||||
DialogPopup(
|
||||
showDialog = true,
|
||||
title = params.title,
|
||||
dialogItems = params.items,
|
||||
onDismissRequest = { itemMoreDialog = null },
|
||||
dismissOnClick = true,
|
||||
waitToLoad = params.fromLongClick,
|
||||
showContextMenu?.let { contextMenu ->
|
||||
ContextMenuDialog(
|
||||
onDismissRequest = { showContextMenu = null },
|
||||
getMediaSource = null,
|
||||
contextMenu = contextMenu,
|
||||
preferredSubtitleLanguage = null,
|
||||
)
|
||||
}
|
||||
if (showViewOptionsDialog) {
|
||||
|
|
|
|||
|
|
@ -1,20 +1,8 @@
|
|||
package com.github.damontecres.wholphin.ui.detail.music
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.ArrowForward
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.PlayArrow
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.model.AudioItem
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.ui.DefaultItemFields
|
||||
import com.github.damontecres.wholphin.ui.components.DialogItem
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.util.ApiRequestPager
|
||||
import com.github.damontecres.wholphin.util.GetItemsRequestHandler
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
|
|
@ -23,193 +11,6 @@ import org.jellyfin.sdk.model.api.ItemSortBy
|
|||
import org.jellyfin.sdk.model.api.request.GetItemsRequest
|
||||
import java.util.UUID
|
||||
|
||||
data class MusicMoreDialogActions(
|
||||
val onNavigate: (Destination) -> Unit,
|
||||
val onClickPlay: (Int, BaseItem) -> Unit,
|
||||
val onClickPlayNext: (Int, BaseItem) -> Unit,
|
||||
val onClickAddToQueue: (Int, BaseItem) -> Unit,
|
||||
val onClickFavorite: (UUID, Boolean) -> Unit,
|
||||
val onClickAddPlaylist: (UUID) -> Unit,
|
||||
val onClickGoToAlbum: (UUID) -> Unit = {
|
||||
onNavigate.invoke(Destination.MediaItem(itemId = it, type = BaseItemKind.MUSIC_ALBUM))
|
||||
},
|
||||
val onClickGoToArtist: (UUID) -> Unit = {
|
||||
onNavigate.invoke(Destination.MediaItem(itemId = it, type = BaseItemKind.MUSIC_ARTIST))
|
||||
},
|
||||
val onClickRemoveFromQueue: (Int) -> Unit,
|
||||
val onClickDelete: (BaseItem) -> Unit,
|
||||
)
|
||||
|
||||
fun buildMoreDialogForMusic(
|
||||
context: Context,
|
||||
actions: MusicMoreDialogActions,
|
||||
item: BaseItem,
|
||||
index: Int,
|
||||
canRemove: Boolean,
|
||||
canDelete: Boolean,
|
||||
): List<DialogItem> =
|
||||
buildList {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.play),
|
||||
Icons.Default.PlayArrow,
|
||||
iconColor = Color.Green.copy(alpha = .8f),
|
||||
) {
|
||||
actions.onClickPlay(index, item)
|
||||
},
|
||||
)
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.play_next),
|
||||
Icons.Default.PlayArrow,
|
||||
iconColor = Color.Green.copy(alpha = .8f),
|
||||
) {
|
||||
actions.onClickPlayNext(index, item)
|
||||
},
|
||||
)
|
||||
if (canRemove) {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.remove_from_queue),
|
||||
Icons.Default.Delete,
|
||||
) {
|
||||
actions.onClickRemoveFromQueue(index)
|
||||
},
|
||||
)
|
||||
}
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.add_to_queue),
|
||||
Icons.Default.Add,
|
||||
) {
|
||||
actions.onClickAddToQueue(index, item)
|
||||
},
|
||||
)
|
||||
add(
|
||||
DialogItem(
|
||||
text = R.string.add_to_playlist,
|
||||
iconStringRes = R.string.fa_list_ul,
|
||||
) {
|
||||
actions.onClickAddPlaylist.invoke(item.id)
|
||||
},
|
||||
)
|
||||
if (canDelete) {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.delete),
|
||||
Icons.Default.Delete,
|
||||
iconColor = Color.Red.copy(alpha = .8f),
|
||||
) {
|
||||
actions.onClickDelete.invoke(item)
|
||||
},
|
||||
)
|
||||
}
|
||||
add(
|
||||
DialogItem(
|
||||
text = if (item.favorite) R.string.remove_favorite else R.string.add_favorite,
|
||||
iconStringRes = R.string.fa_heart,
|
||||
iconColor = if (item.favorite) Color.Red else Color.Unspecified,
|
||||
) {
|
||||
actions.onClickFavorite.invoke(item.id, !item.favorite)
|
||||
},
|
||||
)
|
||||
if (item.type == BaseItemKind.AUDIO && item.data.albumId != null) {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.go_to_album),
|
||||
R.string.fa_compact_disc,
|
||||
) {
|
||||
actions.onClickGoToAlbum.invoke(item.data.albumId!!)
|
||||
},
|
||||
)
|
||||
}
|
||||
if ((item.type == BaseItemKind.AUDIO || item.type == BaseItemKind.MUSIC_ALBUM) && item.data.artistItems?.isNotEmpty() == true) {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.go_to_artist),
|
||||
R.string.fa_user,
|
||||
) {
|
||||
actions.onClickGoToArtist.invoke(
|
||||
item.data.artistItems!!
|
||||
.first()
|
||||
.id,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class MusicQueueDialogActions(
|
||||
val onNavigate: (Destination) -> Unit,
|
||||
val onClickPlay: (Int, AudioItem) -> Unit,
|
||||
val onClickPlayNext: (Int, AudioItem) -> Unit,
|
||||
val onClickGoToAlbum: (UUID) -> Unit = {
|
||||
onNavigate.invoke(Destination.MediaItem(itemId = it, type = BaseItemKind.MUSIC_ALBUM))
|
||||
},
|
||||
val onClickGoToArtist: (UUID) -> Unit = {
|
||||
onNavigate.invoke(Destination.MediaItem(itemId = it, type = BaseItemKind.MUSIC_ARTIST))
|
||||
},
|
||||
val onClickRemoveFromQueue: (Int, AudioItem) -> Unit,
|
||||
)
|
||||
|
||||
fun buildMoreDialogForMusicQueue(
|
||||
context: Context,
|
||||
actions: MusicQueueDialogActions,
|
||||
item: AudioItem,
|
||||
index: Int,
|
||||
canRemove: Boolean,
|
||||
): List<DialogItem> =
|
||||
buildList {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.play),
|
||||
Icons.Default.PlayArrow,
|
||||
iconColor = Color.Green.copy(alpha = .8f),
|
||||
) {
|
||||
actions.onClickPlay(index, item)
|
||||
},
|
||||
)
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.play_next),
|
||||
Icons.Default.PlayArrow,
|
||||
iconColor = Color.Green.copy(alpha = .8f),
|
||||
) {
|
||||
actions.onClickPlayNext(index, item)
|
||||
},
|
||||
)
|
||||
if (canRemove) {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.remove_from_queue),
|
||||
Icons.Default.Delete,
|
||||
) {
|
||||
actions.onClickRemoveFromQueue(index, item)
|
||||
},
|
||||
)
|
||||
}
|
||||
if (item.albumId != null) {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.go_to_album),
|
||||
Icons.Default.ArrowForward,
|
||||
) {
|
||||
actions.onClickGoToAlbum.invoke(item.albumId)
|
||||
},
|
||||
)
|
||||
}
|
||||
if (item.artistId != null) {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.go_to_artist),
|
||||
Icons.Default.ArrowForward,
|
||||
) {
|
||||
actions.onClickGoToArtist.invoke(item.artistId)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun ViewModel.getPagerForAlbum(
|
||||
api: ApiClient,
|
||||
albumId: UUID,
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ fun FocusedEpisodeFooter(
|
|||
watchOnClick: () -> Unit,
|
||||
favoriteOnClick: () -> Unit,
|
||||
canDelete: Boolean,
|
||||
deleteOnClick: () -> Unit,
|
||||
onConfirmDelete: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
buttonOnFocusChanged: (FocusState) -> Unit = {},
|
||||
) {
|
||||
|
|
@ -39,6 +39,7 @@ fun FocusedEpisodeFooter(
|
|||
modifier = modifier,
|
||||
) {
|
||||
ExpandablePlayButtons(
|
||||
title = ep.title ?: "",
|
||||
resumePosition = resumePosition,
|
||||
watched = dto.userData?.played ?: false,
|
||||
favorite = dto.userData?.isFavorite ?: false,
|
||||
|
|
@ -50,7 +51,7 @@ fun FocusedEpisodeFooter(
|
|||
trailers = null,
|
||||
trailerOnClick = {},
|
||||
canDelete = canDelete,
|
||||
deleteOnClick = deleteOnClick,
|
||||
onConfirmDelete = onConfirmDelete,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusDirection
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.focus.focusRestorer
|
||||
|
|
@ -57,12 +56,13 @@ import com.github.damontecres.wholphin.ui.cards.ExtrasRow
|
|||
import com.github.damontecres.wholphin.ui.cards.ItemRow
|
||||
import com.github.damontecres.wholphin.ui.cards.PersonRow
|
||||
import com.github.damontecres.wholphin.ui.cards.SeasonCard
|
||||
import com.github.damontecres.wholphin.ui.components.ConfirmDeleteDialog
|
||||
import com.github.damontecres.wholphin.ui.components.ConfirmDialog
|
||||
import com.github.damontecres.wholphin.ui.components.ContextMenu
|
||||
import com.github.damontecres.wholphin.ui.components.ContextMenuActions
|
||||
import com.github.damontecres.wholphin.ui.components.ContextMenuDialog
|
||||
import com.github.damontecres.wholphin.ui.components.DeleteButton
|
||||
import com.github.damontecres.wholphin.ui.components.DialogItem
|
||||
import com.github.damontecres.wholphin.ui.components.DialogParams
|
||||
import com.github.damontecres.wholphin.ui.components.DialogPopup
|
||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||
import com.github.damontecres.wholphin.ui.components.ExpandableFaButton
|
||||
import com.github.damontecres.wholphin.ui.components.ExpandablePlayButton
|
||||
|
|
@ -71,20 +71,17 @@ import com.github.damontecres.wholphin.ui.components.HeaderUtils
|
|||
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
||||
import com.github.damontecres.wholphin.ui.components.Optional
|
||||
import com.github.damontecres.wholphin.ui.components.OverviewText
|
||||
import com.github.damontecres.wholphin.ui.components.PersonContextActions
|
||||
import com.github.damontecres.wholphin.ui.components.QuickDetails
|
||||
import com.github.damontecres.wholphin.ui.components.TitleOrLogo
|
||||
import com.github.damontecres.wholphin.ui.components.TrailerButton
|
||||
import com.github.damontecres.wholphin.ui.data.AddPlaylistViewModel
|
||||
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialog
|
||||
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialogInfo
|
||||
import com.github.damontecres.wholphin.ui.detail.MoreDialogActions
|
||||
import com.github.damontecres.wholphin.ui.detail.PlaylistDialog
|
||||
import com.github.damontecres.wholphin.ui.detail.PlaylistLoadingState
|
||||
import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItemsForHome
|
||||
import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItemsForPerson
|
||||
import com.github.damontecres.wholphin.ui.discover.DiscoverRow
|
||||
import com.github.damontecres.wholphin.ui.discover.DiscoverRowData
|
||||
import com.github.damontecres.wholphin.ui.launchDefault
|
||||
import com.github.damontecres.wholphin.ui.letNotEmpty
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.ui.rememberInt
|
||||
|
|
@ -95,6 +92,7 @@ import com.github.damontecres.wholphin.util.LoadingState
|
|||
import kotlinx.coroutines.launch
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import org.jellyfin.sdk.model.api.MediaType
|
||||
import org.jellyfin.sdk.model.serializer.toUUID
|
||||
import java.util.UUID
|
||||
import kotlin.time.Duration
|
||||
|
||||
|
|
@ -128,10 +126,50 @@ fun SeriesDetails(
|
|||
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
||||
|
||||
var overviewDialog by remember { mutableStateOf<ItemDetailsDialogInfo?>(null) }
|
||||
var showContextMenu by remember { mutableStateOf<ContextMenu?>(null) }
|
||||
|
||||
var showWatchConfirmation by remember { mutableStateOf(false) }
|
||||
var seasonDialog by remember { mutableStateOf<DialogParams?>(null) }
|
||||
var showPlaylistDialog by remember { mutableStateOf<Optional<UUID>>(Optional.absent()) }
|
||||
var showDeleteDialog by remember { mutableStateOf<BaseItem?>(null) }
|
||||
|
||||
val contextActions =
|
||||
remember {
|
||||
ContextMenuActions(
|
||||
navigateTo = viewModel::navigateTo,
|
||||
onClickWatch = { itemId, watched ->
|
||||
if (itemId == destination.itemId) {
|
||||
// Confirm if marking whole series
|
||||
showWatchConfirmation = true
|
||||
} else {
|
||||
viewModel.setWatched(itemId, watched, null)
|
||||
}
|
||||
},
|
||||
onClickFavorite = { itemId, favorite ->
|
||||
viewModel.setFavorite(itemId, favorite, null)
|
||||
},
|
||||
onClickAddPlaylist = { itemId ->
|
||||
playlistViewModel.loadPlaylists(MediaType.VIDEO)
|
||||
showPlaylistDialog = Optional.present(itemId)
|
||||
},
|
||||
onSendMediaInfo = viewModel.mediaReportService::sendReportFor,
|
||||
onDeleteItem = viewModel::deleteItem,
|
||||
onChooseVersion = { item, source ->
|
||||
viewModel.savePlayVersion(
|
||||
item,
|
||||
source.id!!.toUUID(),
|
||||
)
|
||||
},
|
||||
onChooseTracks = { result ->
|
||||
viewModel.saveTrackSelection(
|
||||
result.item,
|
||||
result.itemPlayback,
|
||||
result.trackIndex,
|
||||
result.streamType,
|
||||
)
|
||||
},
|
||||
onShowOverview = { overviewDialog = ItemDetailsDialogInfo(it) },
|
||||
onClearChosenStreams = {},
|
||||
)
|
||||
}
|
||||
|
||||
LifecycleResumeEffect(destination.itemId) {
|
||||
viewModel.refresh()
|
||||
|
|
@ -185,29 +223,18 @@ fun SeriesDetails(
|
|||
)
|
||||
},
|
||||
onLongClickItem = { index, season ->
|
||||
scope.launchDefault {
|
||||
seasonDialog =
|
||||
buildDialogForSeason(
|
||||
context = context,
|
||||
s = season,
|
||||
canDelete = viewModel.canDelete(season),
|
||||
onClickItem = { viewModel.navigateTo(it.destination()) },
|
||||
markPlayed = { played ->
|
||||
viewModel.setSeasonWatched(season.id, played)
|
||||
},
|
||||
onClickPlay = { shuffle ->
|
||||
viewModel.navigateTo(
|
||||
Destination.PlaybackList(
|
||||
itemId = season.id,
|
||||
shuffle = shuffle,
|
||||
),
|
||||
)
|
||||
},
|
||||
onClickDelete = {
|
||||
showDeleteDialog = it
|
||||
},
|
||||
)
|
||||
}
|
||||
showContextMenu =
|
||||
ContextMenu.ForBaseItem(
|
||||
fromLongClick = true,
|
||||
item = season,
|
||||
chosenStreams = null,
|
||||
showGoTo = true,
|
||||
showStreamChoices = false,
|
||||
canDelete = viewModel.canDelete(season, preferences.appPreferences),
|
||||
canRemoveContinueWatching = false,
|
||||
canRemoveNextUp = false,
|
||||
actions = contextActions,
|
||||
)
|
||||
},
|
||||
overviewOnClick = {
|
||||
overviewDialog = ItemDetailsDialogInfo(item)
|
||||
|
|
@ -245,24 +272,10 @@ fun SeriesDetails(
|
|||
onClickDiscover = { index, item ->
|
||||
viewModel.navigateTo(item.destination)
|
||||
},
|
||||
moreActions =
|
||||
MoreDialogActions(
|
||||
navigateTo = { viewModel.navigateTo(it) },
|
||||
onClickWatch = { itemId, played ->
|
||||
viewModel.setWatched(itemId, played, null)
|
||||
},
|
||||
onClickFavorite = { itemId, played ->
|
||||
viewModel.setFavorite(itemId, played, null)
|
||||
},
|
||||
onClickAddPlaylist = { itemId ->
|
||||
playlistViewModel.loadPlaylists(MediaType.VIDEO)
|
||||
showPlaylistDialog.makePresent(itemId)
|
||||
},
|
||||
onSendMediaInfo = viewModel.mediaReportService::sendReportFor,
|
||||
onClickDelete = {
|
||||
showDeleteDialog = it
|
||||
},
|
||||
),
|
||||
onShowContextMenu = {
|
||||
showContextMenu = it
|
||||
},
|
||||
actions = contextActions,
|
||||
)
|
||||
if (showWatchConfirmation) {
|
||||
ConfirmDialog(
|
||||
|
|
@ -281,6 +294,14 @@ fun SeriesDetails(
|
|||
}
|
||||
}
|
||||
}
|
||||
showContextMenu?.let { contextMenu ->
|
||||
ContextMenuDialog(
|
||||
onDismissRequest = { showContextMenu = null },
|
||||
getMediaSource = viewModel.streamChoiceService::chooseSource,
|
||||
contextMenu = contextMenu,
|
||||
preferredSubtitleLanguage = null,
|
||||
)
|
||||
}
|
||||
overviewDialog?.let { info ->
|
||||
ItemDetailsDialog(
|
||||
info = info,
|
||||
|
|
@ -288,15 +309,6 @@ fun SeriesDetails(
|
|||
onDismissRequest = { overviewDialog = null },
|
||||
)
|
||||
}
|
||||
seasonDialog?.let { params ->
|
||||
DialogPopup(
|
||||
showDialog = true,
|
||||
title = params.title,
|
||||
dialogItems = params.items,
|
||||
waitToLoad = params.fromLongClick,
|
||||
onDismissRequest = { seasonDialog = null },
|
||||
)
|
||||
}
|
||||
showPlaylistDialog.compose { itemId ->
|
||||
PlaylistDialog(
|
||||
title = stringResource(R.string.add_to_playlist),
|
||||
|
|
@ -314,19 +326,6 @@ fun SeriesDetails(
|
|||
elevation = 3.dp,
|
||||
)
|
||||
}
|
||||
showDeleteDialog?.let { item ->
|
||||
ConfirmDeleteDialog(
|
||||
itemTitle = item.title ?: "",
|
||||
onCancel = { showDeleteDialog = null },
|
||||
onConfirm = {
|
||||
if (seasons?.lastOrNull()?.id == item.id) {
|
||||
focusManager.moveFocus(FocusDirection.Previous)
|
||||
}
|
||||
viewModel.deleteItem(item)
|
||||
showDeleteDialog = null
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private const val HEADER_ROW = 0
|
||||
|
|
@ -359,7 +358,8 @@ fun SeriesDetailsContent(
|
|||
favoriteOnClick: () -> Unit,
|
||||
trailerOnClick: (Trailer) -> Unit,
|
||||
onClickExtra: (Int, ExtrasItem) -> Unit,
|
||||
moreActions: MoreDialogActions,
|
||||
onShowContextMenu: (ContextMenu) -> Unit,
|
||||
actions: ContextMenuActions,
|
||||
onClickDiscover: (Int, DiscoverItem) -> Unit,
|
||||
discoverSeries: DiscoverItem?,
|
||||
onClickDiscoverSeries: () -> Unit,
|
||||
|
|
@ -373,7 +373,6 @@ fun SeriesDetailsContent(
|
|||
val focusRequesters = remember { List(DISCOVER_ROW + 1) { FocusRequester() } }
|
||||
val playFocusRequester = remember { FocusRequester() }
|
||||
RequestOrRestoreFocus(focusRequesters.getOrNull(position))
|
||||
var moreDialog by remember { mutableStateOf<DialogParams?>(null) }
|
||||
|
||||
Box(
|
||||
modifier = modifier,
|
||||
|
|
@ -486,9 +485,10 @@ fun SeriesDetailsContent(
|
|||
)
|
||||
if (canDelete) {
|
||||
DeleteButton(
|
||||
onClick = {
|
||||
title = series.title ?: "",
|
||||
onConfirmDelete = {
|
||||
position = HEADER_ROW
|
||||
moreActions.onClickDelete.invoke(series)
|
||||
actions.onDeleteItem.invoke(series)
|
||||
},
|
||||
modifier =
|
||||
Modifier
|
||||
|
|
@ -561,18 +561,17 @@ fun SeriesDetailsContent(
|
|||
},
|
||||
onLongClick = { index, person ->
|
||||
position = PEOPLE_ROW
|
||||
val items =
|
||||
buildMoreDialogItemsForPerson(
|
||||
context = context,
|
||||
person = person,
|
||||
actions = moreActions,
|
||||
)
|
||||
moreDialog =
|
||||
DialogParams(
|
||||
onShowContextMenu.invoke(
|
||||
ContextMenu.ForPerson(
|
||||
fromLongClick = true,
|
||||
title = person.name ?: "",
|
||||
items = items,
|
||||
)
|
||||
person = person,
|
||||
actions =
|
||||
PersonContextActions(
|
||||
navigateTo = actions.navigateTo,
|
||||
onClickFavorite = actions.onClickFavorite,
|
||||
),
|
||||
),
|
||||
)
|
||||
},
|
||||
modifier =
|
||||
Modifier
|
||||
|
|
@ -608,23 +607,19 @@ fun SeriesDetailsContent(
|
|||
},
|
||||
onLongClickItem = { index, item ->
|
||||
position = SIMILAR_ROW
|
||||
val items =
|
||||
buildMoreDialogItemsForHome(
|
||||
context = context,
|
||||
item = item,
|
||||
seriesId = null,
|
||||
playbackPosition = item.playbackPosition,
|
||||
watched = item.played,
|
||||
favorite = item.favorite,
|
||||
actions = moreActions,
|
||||
canDelete = false,
|
||||
)
|
||||
moreDialog =
|
||||
DialogParams(
|
||||
onShowContextMenu.invoke(
|
||||
ContextMenu.ForBaseItem(
|
||||
fromLongClick = true,
|
||||
title = item.name ?: "",
|
||||
items = items,
|
||||
)
|
||||
item = item,
|
||||
chosenStreams = null,
|
||||
showGoTo = true,
|
||||
showStreamChoices = false,
|
||||
canDelete = false,
|
||||
canRemoveContinueWatching = false,
|
||||
canRemoveNextUp = false,
|
||||
actions = actions,
|
||||
),
|
||||
)
|
||||
},
|
||||
cardContent = { index, item, mod, onClick, onLongClick ->
|
||||
SeasonCard(
|
||||
|
|
@ -666,16 +661,6 @@ fun SeriesDetailsContent(
|
|||
}
|
||||
}
|
||||
}
|
||||
moreDialog?.let { params ->
|
||||
DialogPopup(
|
||||
showDialog = true,
|
||||
title = params.title,
|
||||
dialogItems = params.items,
|
||||
onDismissRequest = { moreDialog = null },
|
||||
dismissOnClick = true,
|
||||
waitToLoad = params.fromLongClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import androidx.compose.runtime.livedata.observeAsState
|
|||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
|
|
@ -20,41 +21,30 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
|||
import androidx.lifecycle.compose.LifecycleResumeEffect
|
||||
import androidx.lifecycle.map
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.ChosenStreams
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.ui.RequestOrRestoreFocus
|
||||
import com.github.damontecres.wholphin.ui.components.ConfirmDeleteDialog
|
||||
import com.github.damontecres.wholphin.ui.components.DialogParams
|
||||
import com.github.damontecres.wholphin.ui.components.DialogPopup
|
||||
import com.github.damontecres.wholphin.ui.components.ContextMenu
|
||||
import com.github.damontecres.wholphin.ui.components.ContextMenuActions
|
||||
import com.github.damontecres.wholphin.ui.components.ContextMenuDialog
|
||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
||||
import com.github.damontecres.wholphin.ui.components.chooseStream
|
||||
import com.github.damontecres.wholphin.ui.components.chooseVersionParams
|
||||
import com.github.damontecres.wholphin.ui.data.AddPlaylistViewModel
|
||||
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialog
|
||||
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialogInfo
|
||||
import com.github.damontecres.wholphin.ui.detail.MoreDialogActions
|
||||
import com.github.damontecres.wholphin.ui.detail.PlaylistDialog
|
||||
import com.github.damontecres.wholphin.ui.detail.PlaylistLoadingState
|
||||
import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItems
|
||||
import com.github.damontecres.wholphin.ui.launchDefault
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.ui.rememberInt
|
||||
import com.github.damontecres.wholphin.ui.seasonEpisode
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import org.jellyfin.sdk.model.api.MediaStreamType
|
||||
import org.jellyfin.sdk.model.api.MediaType
|
||||
import org.jellyfin.sdk.model.api.PersonKind
|
||||
import org.jellyfin.sdk.model.extensions.ticks
|
||||
import org.jellyfin.sdk.model.serializer.UUIDSerializer
|
||||
import org.jellyfin.sdk.model.serializer.toUUID
|
||||
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
|
||||
import java.util.UUID
|
||||
import kotlin.time.Duration
|
||||
|
||||
|
|
@ -110,6 +100,7 @@ fun SeriesOverview(
|
|||
val episodeList = (episodes as? EpisodeList.Success)?.episodes
|
||||
|
||||
val position by viewModel.position.collectAsState(SeriesOverviewPosition(0, 0))
|
||||
val currentPosition by rememberUpdatedState(position)
|
||||
LaunchedEffect(Unit) {
|
||||
if (seasons.isNotEmpty()) {
|
||||
seasons.getOrNull(position.seasonTabIndex)?.let {
|
||||
|
|
@ -119,13 +110,54 @@ fun SeriesOverview(
|
|||
}
|
||||
|
||||
var overviewDialog by remember { mutableStateOf<ItemDetailsDialogInfo?>(null) }
|
||||
var moreDialog by remember { mutableStateOf<DialogParams?>(null) }
|
||||
var chooseVersion by remember { mutableStateOf<DialogParams?>(null) }
|
||||
var showContextMenu by remember { mutableStateOf<ContextMenu?>(null) }
|
||||
var showPlaylistDialog by remember { mutableStateOf<UUID?>(null) }
|
||||
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
||||
|
||||
var rowFocused by rememberInt()
|
||||
var showDeleteDialog by remember { mutableStateOf<BaseItem?>(null) }
|
||||
|
||||
val contextActions =
|
||||
remember {
|
||||
ContextMenuActions(
|
||||
navigateTo = viewModel::navigateTo,
|
||||
onClickWatch = { itemId, watched ->
|
||||
viewModel.setWatched(itemId, watched, currentPosition.episodeRowIndex)
|
||||
},
|
||||
onClickFavorite = { itemId, favorite ->
|
||||
viewModel.setFavorite(itemId, favorite, currentPosition.episodeRowIndex)
|
||||
},
|
||||
onClickAddPlaylist = { itemId ->
|
||||
playlistViewModel.loadPlaylists(MediaType.VIDEO)
|
||||
showPlaylistDialog = itemId
|
||||
},
|
||||
onSendMediaInfo = viewModel.mediaReportService::sendReportFor,
|
||||
onDeleteItem = viewModel::deleteItem,
|
||||
onChooseVersion = { item, source ->
|
||||
viewModel.savePlayVersion(
|
||||
item,
|
||||
source.id!!.toUUID(),
|
||||
)
|
||||
},
|
||||
onChooseTracks = { result ->
|
||||
viewModel.saveTrackSelection(
|
||||
result.item,
|
||||
result.itemPlayback,
|
||||
result.trackIndex,
|
||||
result.streamType,
|
||||
)
|
||||
},
|
||||
onShowOverview = { overviewDialog = ItemDetailsDialogInfo(it) },
|
||||
onClearChosenStreams = {
|
||||
val focusedEpisode =
|
||||
(episodes as? EpisodeList.Success)
|
||||
?.episodes
|
||||
?.getOrNull(currentPosition.episodeRowIndex)
|
||||
if (focusedEpisode != null) {
|
||||
viewModel.clearChosenStreams(focusedEpisode, it)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
LaunchedEffect(position, episodes) {
|
||||
val focusedEpisode =
|
||||
|
|
@ -179,103 +211,6 @@ fun SeriesOverview(
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun buildMoreForEpisode(
|
||||
ep: BaseItem,
|
||||
chosenStreams: ChosenStreams?,
|
||||
fromLongClick: Boolean,
|
||||
): DialogParams =
|
||||
DialogParams(
|
||||
fromLongClick = fromLongClick,
|
||||
title = series.name + " - " + ep.data.seasonEpisode,
|
||||
items =
|
||||
buildMoreDialogItems(
|
||||
context = context,
|
||||
item = ep,
|
||||
watched = ep.data.userData?.played ?: false,
|
||||
favorite = ep.data.userData?.isFavorite ?: false,
|
||||
seriesId = series.id,
|
||||
sourceId = chosenStreams?.source?.id?.toUUIDOrNull(),
|
||||
canClearChosenStreams = chosenStreams?.itemPlayback != null || chosenStreams?.plc != null,
|
||||
canDelete = viewModel.canDelete(ep),
|
||||
actions =
|
||||
MoreDialogActions(
|
||||
navigateTo = viewModel::navigateTo,
|
||||
onClickWatch = { itemId, watched ->
|
||||
viewModel.setWatched(
|
||||
itemId,
|
||||
watched,
|
||||
position.episodeRowIndex,
|
||||
)
|
||||
},
|
||||
onClickFavorite = { itemId, favorite ->
|
||||
viewModel.setFavorite(
|
||||
itemId,
|
||||
favorite,
|
||||
position.episodeRowIndex,
|
||||
)
|
||||
},
|
||||
onClickAddPlaylist = {
|
||||
playlistViewModel.loadPlaylists(MediaType.VIDEO)
|
||||
showPlaylistDialog = it
|
||||
},
|
||||
onSendMediaInfo = viewModel.mediaReportService::sendReportFor,
|
||||
onClickDelete = {
|
||||
showDeleteDialog = it
|
||||
},
|
||||
),
|
||||
onChooseVersion = {
|
||||
chooseVersion =
|
||||
chooseVersionParams(
|
||||
context,
|
||||
ep.data.mediaSources!!,
|
||||
chosenStreams?.source?.id?.toUUIDOrNull(),
|
||||
) { idx ->
|
||||
val source = ep.data.mediaSources!![idx]
|
||||
viewModel.savePlayVersion(
|
||||
ep,
|
||||
source.id!!.toUUID(),
|
||||
)
|
||||
}
|
||||
moreDialog = null
|
||||
},
|
||||
onChooseTracks = { type ->
|
||||
viewModel.streamChoiceService
|
||||
.chooseSource(
|
||||
ep.data,
|
||||
chosenStreams?.itemPlayback,
|
||||
)?.let { source ->
|
||||
chooseVersion =
|
||||
chooseStream(
|
||||
context = context,
|
||||
streams = source.mediaStreams.orEmpty(),
|
||||
type = type,
|
||||
currentIndex =
|
||||
if (type == MediaStreamType.AUDIO) {
|
||||
chosenStreams?.audioStream?.index
|
||||
} else {
|
||||
chosenStreams?.subtitleStream?.index
|
||||
},
|
||||
onClick = { trackIndex ->
|
||||
viewModel.saveTrackSelection(
|
||||
ep,
|
||||
chosenStreams?.itemPlayback,
|
||||
trackIndex,
|
||||
type,
|
||||
)
|
||||
},
|
||||
preferredSubtitleLanguage = preferredSubtitleLanguage,
|
||||
)
|
||||
}
|
||||
},
|
||||
onShowOverview = {
|
||||
overviewDialog = ItemDetailsDialogInfo(ep)
|
||||
},
|
||||
onClearChosenStreams = {
|
||||
viewModel.clearChosenStreams(ep, chosenStreams)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
SeriesOverviewContent(
|
||||
preferences = preferences,
|
||||
series = series,
|
||||
|
|
@ -319,9 +254,18 @@ fun SeriesOverview(
|
|||
)
|
||||
},
|
||||
onLongClick = { ep ->
|
||||
scope.launchDefault {
|
||||
moreDialog = buildMoreForEpisode(ep, chosenStreams, true)
|
||||
}
|
||||
showContextMenu =
|
||||
ContextMenu.ForBaseItem(
|
||||
fromLongClick = true,
|
||||
item = ep,
|
||||
chosenStreams = chosenStreams,
|
||||
showGoTo = false,
|
||||
showStreamChoices = true,
|
||||
canDelete = viewModel.canDelete(ep, preferences.appPreferences),
|
||||
canRemoveContinueWatching = false,
|
||||
canRemoveNextUp = false,
|
||||
actions = contextActions,
|
||||
)
|
||||
},
|
||||
playOnClick = { resume ->
|
||||
rowFocused = EPISODE_ROW
|
||||
|
|
@ -349,9 +293,18 @@ fun SeriesOverview(
|
|||
},
|
||||
moreOnClick = {
|
||||
episodeList?.getOrNull(position.episodeRowIndex)?.let { ep ->
|
||||
scope.launchDefault {
|
||||
moreDialog = buildMoreForEpisode(ep, chosenStreams, false)
|
||||
}
|
||||
showContextMenu =
|
||||
ContextMenu.ForBaseItem(
|
||||
fromLongClick = false,
|
||||
item = ep,
|
||||
chosenStreams = chosenStreams,
|
||||
showGoTo = false,
|
||||
showStreamChoices = true,
|
||||
canDelete = viewModel.canDelete(ep, preferences.appPreferences),
|
||||
canRemoveContinueWatching = false,
|
||||
canRemoveNextUp = false,
|
||||
actions = contextActions,
|
||||
)
|
||||
}
|
||||
},
|
||||
overviewOnClick = {
|
||||
|
|
@ -374,13 +327,20 @@ fun SeriesOverview(
|
|||
viewModel.navigateTo(extra.destination)
|
||||
},
|
||||
canDelete = { viewModel.canDelete(it, preferences.appPreferences) },
|
||||
deleteOnClick = { showDeleteDialog = it },
|
||||
onConfirmDelete = viewModel::deleteItem,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
showContextMenu?.let { contextMenu ->
|
||||
ContextMenuDialog(
|
||||
onDismissRequest = { showContextMenu = null },
|
||||
getMediaSource = viewModel.streamChoiceService::chooseSource,
|
||||
contextMenu = contextMenu,
|
||||
preferredSubtitleLanguage = preferredSubtitleLanguage,
|
||||
)
|
||||
}
|
||||
overviewDialog?.let { info ->
|
||||
ItemDetailsDialog(
|
||||
info = info,
|
||||
|
|
@ -391,26 +351,6 @@ fun SeriesOverview(
|
|||
onDismissRequest = { overviewDialog = null },
|
||||
)
|
||||
}
|
||||
moreDialog?.let { params ->
|
||||
DialogPopup(
|
||||
showDialog = true,
|
||||
title = params.title,
|
||||
dialogItems = params.items,
|
||||
onDismissRequest = { moreDialog = null },
|
||||
dismissOnClick = true,
|
||||
waitToLoad = params.fromLongClick,
|
||||
)
|
||||
}
|
||||
chooseVersion?.let { params ->
|
||||
DialogPopup(
|
||||
showDialog = true,
|
||||
title = params.title,
|
||||
dialogItems = params.items,
|
||||
onDismissRequest = { chooseVersion = null },
|
||||
dismissOnClick = true,
|
||||
waitToLoad = params.fromLongClick,
|
||||
)
|
||||
}
|
||||
showPlaylistDialog?.let { itemId ->
|
||||
PlaylistDialog(
|
||||
title = stringResource(R.string.add_to_playlist),
|
||||
|
|
@ -428,17 +368,6 @@ fun SeriesOverview(
|
|||
elevation = 3.dp,
|
||||
)
|
||||
}
|
||||
showDeleteDialog?.let { item ->
|
||||
ConfirmDeleteDialog(
|
||||
itemTitle = item.subtitle ?: "",
|
||||
onCancel = { showDeleteDialog = null },
|
||||
onConfirm = {
|
||||
viewModel.deleteItem(item)
|
||||
episodeRowFocusRequester.tryRequestFocus()
|
||||
showDeleteDialog = null
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private const val EPISODE_ROW = 0
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ fun SeriesOverviewContent(
|
|||
overviewOnClick: () -> Unit,
|
||||
personOnClick: (Person) -> Unit,
|
||||
canDelete: (BaseItem) -> Boolean,
|
||||
deleteOnClick: (BaseItem) -> Unit,
|
||||
onConfirmDelete: (BaseItem) -> Unit,
|
||||
onClickExtra: (Int, ExtrasItem) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
|
|
@ -322,7 +322,7 @@ fun SeriesOverviewContent(
|
|||
}
|
||||
},
|
||||
canDelete = canDelete.invoke(ep),
|
||||
deleteOnClick = { deleteOnClick.invoke(ep) },
|
||||
onConfirmDelete = { onConfirmDelete.invoke(ep) },
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(top = 4.dp)
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import androidx.compose.ui.focus.FocusRequester
|
|||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.focus.focusRestorer
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.input.key.KeyEvent
|
||||
import androidx.compose.ui.input.key.onKeyEvent
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
|
|
@ -56,24 +57,23 @@ import com.github.damontecres.wholphin.ui.cards.GenreCard
|
|||
import com.github.damontecres.wholphin.ui.cards.ItemRow
|
||||
import com.github.damontecres.wholphin.ui.cards.StudioCard
|
||||
import com.github.damontecres.wholphin.ui.components.CircularProgress
|
||||
import com.github.damontecres.wholphin.ui.components.ConfirmDeleteDialog
|
||||
import com.github.damontecres.wholphin.ui.components.DialogParams
|
||||
import com.github.damontecres.wholphin.ui.components.DialogPopup
|
||||
import com.github.damontecres.wholphin.ui.components.ContextMenu
|
||||
import com.github.damontecres.wholphin.ui.components.ContextMenuActions
|
||||
import com.github.damontecres.wholphin.ui.components.ContextMenuDialog
|
||||
import com.github.damontecres.wholphin.ui.components.EpisodeName
|
||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||
import com.github.damontecres.wholphin.ui.components.FocusableItemRow
|
||||
import com.github.damontecres.wholphin.ui.components.HeaderUtils
|
||||
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
||||
import com.github.damontecres.wholphin.ui.components.QuickDetails
|
||||
import com.github.damontecres.wholphin.ui.components.RowColumnItem
|
||||
import com.github.damontecres.wholphin.ui.components.TitleOrLogo
|
||||
import com.github.damontecres.wholphin.ui.components.rememberLogoUrl
|
||||
import com.github.damontecres.wholphin.ui.data.AddPlaylistViewModel
|
||||
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialog
|
||||
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialogInfo
|
||||
import com.github.damontecres.wholphin.ui.data.RowColumn
|
||||
import com.github.damontecres.wholphin.ui.detail.MoreDialogActions
|
||||
import com.github.damontecres.wholphin.ui.detail.PlaylistDialog
|
||||
import com.github.damontecres.wholphin.ui.detail.PlaylistLoadingState
|
||||
import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItemsForHome
|
||||
import com.github.damontecres.wholphin.ui.indexOfFirstOrNull
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
|
|
@ -120,9 +120,10 @@ fun HomePage(
|
|||
}
|
||||
|
||||
LoadingState.Success -> {
|
||||
var dialog by remember { mutableStateOf<DialogParams?>(null) }
|
||||
var showContextMenu by remember { mutableStateOf<ContextMenu?>(null) }
|
||||
var showPlaylistDialog by remember { mutableStateOf<UUID?>(null) }
|
||||
var showDeleteDialog by remember { mutableStateOf<RowColumnItem?>(null) }
|
||||
var overviewDialog by remember { mutableStateOf<ItemDetailsDialogInfo?>(null) }
|
||||
|
||||
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
||||
var position by rememberPosition()
|
||||
|
||||
|
|
@ -144,43 +145,45 @@ fun HomePage(
|
|||
row?.rowType is HomeRowConfig.ContinueWatching || row?.rowType is HomeRowConfig.ContinueWatchingCombined
|
||||
val canRemoveNextUp =
|
||||
row?.rowType is HomeRowConfig.NextUp || row?.rowType is HomeRowConfig.ContinueWatchingCombined
|
||||
val dialogItems =
|
||||
buildMoreDialogItemsForHome(
|
||||
context = context,
|
||||
showContextMenu =
|
||||
ContextMenu.ForBaseItem(
|
||||
fromLongClick = true,
|
||||
item = item,
|
||||
seriesId = item.data.seriesId,
|
||||
playbackPosition = item.playbackPosition,
|
||||
watched = item.played,
|
||||
favorite = item.favorite,
|
||||
canDelete = viewModel.canDelete(item, preferences.appPreferences),
|
||||
canRemoveNextUp = canRemoveNextUp,
|
||||
chosenStreams = null,
|
||||
showGoTo = true,
|
||||
showStreamChoices = false,
|
||||
canDelete =
|
||||
viewModel.canDelete(
|
||||
item,
|
||||
preferences.appPreferences,
|
||||
),
|
||||
canRemoveContinueWatching = canRemoveContinueWatching,
|
||||
canRemoveNextUp = canRemoveNextUp,
|
||||
actions =
|
||||
MoreDialogActions(
|
||||
ContextMenuActions(
|
||||
navigateTo = viewModel.navigationManager::navigateTo,
|
||||
onClickWatch = { itemId, played ->
|
||||
viewModel.setWatched(itemId, played)
|
||||
},
|
||||
onClickFavorite = { itemId, favorite ->
|
||||
viewModel.setFavorite(itemId, favorite)
|
||||
},
|
||||
onClickWatch = viewModel::setWatched,
|
||||
onClickFavorite = viewModel::setFavorite,
|
||||
onClickAddPlaylist = { itemId ->
|
||||
playlistViewModel.loadPlaylists(MediaType.VIDEO)
|
||||
showPlaylistDialog = itemId
|
||||
},
|
||||
onSendMediaInfo = viewModel.mediaReportService::sendReportFor,
|
||||
onClickDelete = {
|
||||
showDeleteDialog = RowColumnItem(position, item)
|
||||
onDeleteItem = {
|
||||
viewModel.deleteItem(position, it)
|
||||
},
|
||||
onClickRemoveFromNextUp = viewModel::removeFromNextUp,
|
||||
onChooseVersion = { _, _ ->
|
||||
// Not supported on this page
|
||||
},
|
||||
onChooseTracks = {
|
||||
// Not supported on this page
|
||||
},
|
||||
onShowOverview = {
|
||||
overviewDialog = ItemDetailsDialogInfo(it)
|
||||
},
|
||||
onClearChosenStreams = {},
|
||||
),
|
||||
)
|
||||
dialog =
|
||||
DialogParams(
|
||||
title = item.title ?: "",
|
||||
fromLongClick = true,
|
||||
items = dialogItems,
|
||||
)
|
||||
}
|
||||
}
|
||||
val onClickPlay =
|
||||
|
|
@ -203,10 +206,19 @@ fun HomePage(
|
|||
showLogo = preferences.appPreferences.interfacePreferences.showLogos,
|
||||
modifier = modifier,
|
||||
)
|
||||
dialog?.let { params ->
|
||||
DialogPopup(
|
||||
params = params,
|
||||
onDismissRequest = { dialog = null },
|
||||
overviewDialog?.let { info ->
|
||||
ItemDetailsDialog(
|
||||
info = info,
|
||||
showFilePath = false,
|
||||
onDismissRequest = { overviewDialog = null },
|
||||
)
|
||||
}
|
||||
showContextMenu?.let { contextMenu ->
|
||||
ContextMenuDialog(
|
||||
onDismissRequest = { showContextMenu = null },
|
||||
getMediaSource = null,
|
||||
contextMenu = contextMenu,
|
||||
preferredSubtitleLanguage = null,
|
||||
)
|
||||
}
|
||||
showPlaylistDialog?.let { itemId ->
|
||||
|
|
@ -226,19 +238,7 @@ fun HomePage(
|
|||
elevation = 3.dp,
|
||||
)
|
||||
}
|
||||
showDeleteDialog?.let { (position, item) ->
|
||||
ConfirmDeleteDialog(
|
||||
itemTitle = listOfNotNull(item.title, item.subtitle).joinToString(" - "),
|
||||
onCancel = { showDeleteDialog = null },
|
||||
onConfirm = {
|
||||
viewModel.deleteItem(position, item)
|
||||
showDeleteDialog = null
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -406,7 +406,7 @@ fun HomePageContent(
|
|||
}
|
||||
val onKey =
|
||||
remember(item) {
|
||||
{ event: androidx.compose.ui.input.key.KeyEvent ->
|
||||
{ event: KeyEvent ->
|
||||
if (isPlayKeyUp(event) && item?.type?.playable == true) {
|
||||
Timber.v("Clicked play on ${item.id}")
|
||||
currentOnClickPlay(
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import com.github.damontecres.wholphin.data.model.CollectionFolderFilter
|
|||
import com.github.damontecres.wholphin.data.model.DiscoverItem
|
||||
import com.github.damontecres.wholphin.data.model.GetItemsFilter
|
||||
import com.github.damontecres.wholphin.data.model.ItemPlayback
|
||||
import com.github.damontecres.wholphin.preferences.PlayerBackend
|
||||
import com.github.damontecres.wholphin.ui.data.SortAndDirection
|
||||
import com.github.damontecres.wholphin.ui.detail.series.SeasonEpisodeIds
|
||||
import com.github.damontecres.wholphin.ui.preferences.PreferenceScreenOption
|
||||
|
|
@ -80,6 +81,7 @@ sealed class Destination(
|
|||
val positionMs: Long,
|
||||
val itemPlayback: ItemPlayback? = null,
|
||||
val forceTranscoding: Boolean = false,
|
||||
val backend: PlayerBackend? = null,
|
||||
) : Destination(true) {
|
||||
constructor(item: BaseItem) : this(item.id, item.resumeMs)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue