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.MediaReportService
|
||||||
import com.github.damontecres.wholphin.services.MusicService
|
import com.github.damontecres.wholphin.services.MusicService
|
||||||
import com.github.damontecres.wholphin.services.NavigationManager
|
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.ThemeSongPlayer
|
||||||
import com.github.damontecres.wholphin.services.UserPreferencesService
|
import com.github.damontecres.wholphin.services.UserPreferencesService
|
||||||
import com.github.damontecres.wholphin.services.deleteItem
|
import com.github.damontecres.wholphin.services.deleteItem
|
||||||
|
|
@ -74,13 +75,13 @@ import com.github.damontecres.wholphin.ui.RequestOrRestoreFocus
|
||||||
import com.github.damontecres.wholphin.ui.SlimItemFields
|
import com.github.damontecres.wholphin.ui.SlimItemFields
|
||||||
import com.github.damontecres.wholphin.ui.cards.GridCard
|
import com.github.damontecres.wholphin.ui.cards.GridCard
|
||||||
import com.github.damontecres.wholphin.ui.data.AddPlaylistViewModel
|
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.data.SortAndDirection
|
||||||
import com.github.damontecres.wholphin.ui.detail.CardGrid
|
import com.github.damontecres.wholphin.ui.detail.CardGrid
|
||||||
import com.github.damontecres.wholphin.ui.detail.ItemViewModel
|
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.PlaylistDialog
|
||||||
import com.github.damontecres.wholphin.ui.detail.PlaylistLoadingState
|
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.detail.music.addToQueue
|
||||||
import com.github.damontecres.wholphin.ui.equalsNotNull
|
import com.github.damontecres.wholphin.ui.equalsNotNull
|
||||||
import com.github.damontecres.wholphin.ui.launchDefault
|
import com.github.damontecres.wholphin.ui.launchDefault
|
||||||
|
|
@ -133,7 +134,7 @@ class CollectionFolderViewModel
|
||||||
private val savedStateHandle: SavedStateHandle,
|
private val savedStateHandle: SavedStateHandle,
|
||||||
api: ApiClient,
|
api: ApiClient,
|
||||||
@param:ApplicationContext private val context: Context,
|
@param:ApplicationContext private val context: Context,
|
||||||
private val serverRepository: ServerRepository,
|
val serverRepository: ServerRepository,
|
||||||
private val libraryDisplayInfoDao: LibraryDisplayInfoDao,
|
private val libraryDisplayInfoDao: LibraryDisplayInfoDao,
|
||||||
private val favoriteWatchManager: FavoriteWatchManager,
|
private val favoriteWatchManager: FavoriteWatchManager,
|
||||||
private val backdropService: BackdropService,
|
private val backdropService: BackdropService,
|
||||||
|
|
@ -142,6 +143,7 @@ class CollectionFolderViewModel
|
||||||
private val userPreferencesService: UserPreferencesService,
|
private val userPreferencesService: UserPreferencesService,
|
||||||
private val mediaManagementService: MediaManagementService,
|
private val mediaManagementService: MediaManagementService,
|
||||||
private val musicService: MusicService,
|
private val musicService: MusicService,
|
||||||
|
val streamChoiceService: StreamChoiceService,
|
||||||
val mediaReportService: MediaReportService,
|
val mediaReportService: MediaReportService,
|
||||||
@Assisted itemId: String,
|
@Assisted itemId: String,
|
||||||
@Assisted initialSortAndDirection: SortAndDirection?,
|
@Assisted initialSortAndDirection: SortAndDirection?,
|
||||||
|
|
@ -662,18 +664,58 @@ fun CollectionFolderGrid(
|
||||||
val item by viewModel.item.observeAsState()
|
val item by viewModel.item.observeAsState()
|
||||||
val viewOptions by viewModel.viewOptions.observeAsState(defaultViewOptions)
|
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 showPlaylistDialog by remember { mutableStateOf<Optional<UUID>>(Optional.absent()) }
|
||||||
var showDeleteDialog by remember { mutableStateOf<PositionItem?>(null) }
|
|
||||||
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
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 =
|
val gridActions =
|
||||||
remember(actions) {
|
remember(actions) {
|
||||||
GridClickActions(
|
GridClickActions(
|
||||||
onClickItem = actions.onClickItem,
|
onClickItem = actions.onClickItem,
|
||||||
onLongClickItem =
|
onLongClickItem =
|
||||||
actions.onLongClickItem ?: { position, item ->
|
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 =
|
onClickPlayAll =
|
||||||
actions.onClickPlayAll ?: { shuffle ->
|
actions.onClickPlayAll ?: { shuffle ->
|
||||||
|
|
@ -802,47 +844,22 @@ fun CollectionFolderGrid(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
moreDialog.compose { (position, item) ->
|
overviewDialog?.let { info ->
|
||||||
DialogPopup(
|
ItemDetailsDialog(
|
||||||
showDialog = true,
|
info = info,
|
||||||
title = item.title ?: "",
|
showFilePath =
|
||||||
dialogItems =
|
viewModel.serverRepository.currentUserDto.value
|
||||||
buildMoreDialogItemsForHome(
|
?.policy
|
||||||
context = context,
|
?.isAdministrator == true,
|
||||||
item = item,
|
onDismissRequest = { overviewDialog = null },
|
||||||
seriesId = null,
|
)
|
||||||
playbackPosition = item.playbackPosition,
|
}
|
||||||
watched = item.played,
|
showContextMenu?.let { contextMenu ->
|
||||||
favorite = item.favorite,
|
ContextMenuDialog(
|
||||||
canDelete = viewModel.canDelete(item, preferences.appPreferences),
|
onDismissRequest = { showContextMenu = null },
|
||||||
actions =
|
getMediaSource = null,
|
||||||
MoreDialogActions(
|
contextMenu = contextMenu,
|
||||||
navigateTo = { viewModel.navigateTo(it) },
|
preferredSubtitleLanguage = null,
|
||||||
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,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
showPlaylistDialog.compose { itemId ->
|
showPlaylistDialog.compose { itemId ->
|
||||||
|
|
@ -862,16 +879,6 @@ fun CollectionFolderGrid(
|
||||||
elevation = 3.dp,
|
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)
|
@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 trailingContent: @Composable (() -> Unit)? = null,
|
||||||
val enabled: Boolean = true,
|
val enabled: Boolean = true,
|
||||||
val selected: Boolean = false,
|
val selected: Boolean = false,
|
||||||
|
val dismissOnClick: Boolean = false,
|
||||||
) : DialogItemEntry {
|
) : DialogItemEntry {
|
||||||
constructor(
|
constructor(
|
||||||
@StringRes text: Int,
|
@StringRes text: Int,
|
||||||
@StringRes iconStringRes: Int,
|
@StringRes iconStringRes: Int,
|
||||||
iconColor: Color = Color.Unspecified,
|
iconColor: Color = Color.Unspecified,
|
||||||
|
dismissOnClick: Boolean = false,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
) : this(
|
) : this(
|
||||||
headlineContent = {
|
headlineContent = {
|
||||||
|
|
@ -124,11 +126,13 @@ data class DialogItem(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
|
dismissOnClick = dismissOnClick,
|
||||||
)
|
)
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
text: String,
|
text: String,
|
||||||
@StringRes iconStringRes: Int,
|
@StringRes iconStringRes: Int,
|
||||||
|
dismissOnClick: Boolean = false,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
) : this(
|
) : this(
|
||||||
headlineContent = {
|
headlineContent = {
|
||||||
|
|
@ -145,12 +149,14 @@ data class DialogItem(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
|
dismissOnClick = dismissOnClick,
|
||||||
)
|
)
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
text: String,
|
text: String,
|
||||||
icon: ImageVector,
|
icon: ImageVector,
|
||||||
iconColor: Color? = null,
|
iconColor: Color? = null,
|
||||||
|
dismissOnClick: Boolean = false,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
) : this(
|
) : this(
|
||||||
headlineContent = {
|
headlineContent = {
|
||||||
|
|
@ -167,10 +173,12 @@ data class DialogItem(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
|
dismissOnClick = dismissOnClick,
|
||||||
)
|
)
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
text: String,
|
text: String,
|
||||||
|
dismissOnClick: Boolean = false,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
) : this(
|
) : this(
|
||||||
headlineContent = {
|
headlineContent = {
|
||||||
|
|
@ -180,6 +188,7 @@ data class DialogItem(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
|
dismissOnClick = dismissOnClick,
|
||||||
)
|
)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
@ -201,7 +210,7 @@ fun DialogPopup(
|
||||||
dismissOnClick: Boolean = true,
|
dismissOnClick: Boolean = true,
|
||||||
waitToLoad: Boolean = true,
|
waitToLoad: Boolean = true,
|
||||||
properties: DialogProperties = DialogProperties(),
|
properties: DialogProperties = DialogProperties(),
|
||||||
elevation: Dp = 8.dp,
|
elevation: Dp = 1.dp,
|
||||||
) {
|
) {
|
||||||
var waiting by remember { mutableStateOf(waitToLoad) }
|
var waiting by remember { mutableStateOf(waitToLoad) }
|
||||||
if (showDialog) {
|
if (showDialog) {
|
||||||
|
|
@ -296,7 +305,7 @@ fun DialogPopupContent(
|
||||||
selected = item.selected,
|
selected = item.selected,
|
||||||
enabled = !waiting && item.enabled,
|
enabled = !waiting && item.enabled,
|
||||||
onClick = {
|
onClick = {
|
||||||
if (dismissOnClick) {
|
if (dismissOnClick || item.dismissOnClick) {
|
||||||
onDismissRequest.invoke()
|
onDismissRequest.invoke()
|
||||||
}
|
}
|
||||||
item.onClick.invoke()
|
item.onClick.invoke()
|
||||||
|
|
@ -349,7 +358,7 @@ fun DialogPopup(
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
dismissOnClick: Boolean = true,
|
dismissOnClick: Boolean = true,
|
||||||
properties: DialogProperties = DialogProperties(),
|
properties: DialogProperties = DialogProperties(),
|
||||||
elevation: Dp = 8.dp,
|
elevation: Dp = 1.dp,
|
||||||
) = DialogPopup(
|
) = DialogPopup(
|
||||||
showDialog = true,
|
showDialog = true,
|
||||||
waitToLoad = params.fromLongClick,
|
waitToLoad = params.fromLongClick,
|
||||||
|
|
@ -427,7 +436,7 @@ fun ScrollableDialog(
|
||||||
fun BasicDialog(
|
fun BasicDialog(
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
properties: DialogProperties = DialogProperties(),
|
properties: DialogProperties = DialogProperties(),
|
||||||
elevation: Dp = 8.dp,
|
elevation: Dp = 1.dp,
|
||||||
content: @Composable () -> Unit,
|
content: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
Dialog(
|
Dialog(
|
||||||
|
|
@ -458,7 +467,7 @@ fun ConfirmDialog(
|
||||||
onCancel: () -> Unit,
|
onCancel: () -> Unit,
|
||||||
onConfirm: () -> Unit,
|
onConfirm: () -> Unit,
|
||||||
properties: DialogProperties = DialogProperties(),
|
properties: DialogProperties = DialogProperties(),
|
||||||
elevation: Dp = 8.dp,
|
elevation: Dp = 1.dp,
|
||||||
bodyColor: Color = MaterialTheme.colorScheme.onSurface,
|
bodyColor: Color = MaterialTheme.colorScheme.onSurface,
|
||||||
) = BasicDialog(
|
) = BasicDialog(
|
||||||
onDismissRequest = onCancel,
|
onDismissRequest = onCancel,
|
||||||
|
|
@ -527,10 +536,12 @@ fun ConfirmDeleteDialog(
|
||||||
itemTitle: String,
|
itemTitle: String,
|
||||||
onCancel: () -> Unit,
|
onCancel: () -> Unit,
|
||||||
onConfirm: () -> Unit,
|
onConfirm: () -> Unit,
|
||||||
|
elevation: Dp = 3.dp,
|
||||||
) {
|
) {
|
||||||
BasicDialog(
|
BasicDialog(
|
||||||
onDismissRequest = onCancel,
|
onDismissRequest = onCancel,
|
||||||
properties = DialogProperties(usePlatformDefaultWidth = false),
|
properties = DialogProperties(usePlatformDefaultWidth = false),
|
||||||
|
elevation = elevation,
|
||||||
) {
|
) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,7 @@ import kotlin.time.Duration.Companion.seconds
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun ExpandablePlayButtons(
|
fun ExpandablePlayButtons(
|
||||||
|
title: String,
|
||||||
resumePosition: Duration,
|
resumePosition: Duration,
|
||||||
watched: Boolean,
|
watched: Boolean,
|
||||||
favorite: Boolean,
|
favorite: Boolean,
|
||||||
|
|
@ -79,7 +80,7 @@ fun ExpandablePlayButtons(
|
||||||
favoriteOnClick: () -> Unit,
|
favoriteOnClick: () -> Unit,
|
||||||
moreOnClick: () -> Unit,
|
moreOnClick: () -> Unit,
|
||||||
trailerOnClick: (Trailer) -> Unit,
|
trailerOnClick: (Trailer) -> Unit,
|
||||||
deleteOnClick: () -> Unit,
|
onConfirmDelete: () -> Unit,
|
||||||
buttonOnFocusChanged: (FocusState) -> Unit,
|
buttonOnFocusChanged: (FocusState) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
|
|
@ -163,7 +164,8 @@ fun ExpandablePlayButtons(
|
||||||
if (canDelete) {
|
if (canDelete) {
|
||||||
item("delete") {
|
item("delete") {
|
||||||
DeleteButton(
|
DeleteButton(
|
||||||
onClick = deleteOnClick,
|
title = title,
|
||||||
|
onConfirmDelete = onConfirmDelete,
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.onFocusChanged(buttonOnFocusChanged),
|
.onFocusChanged(buttonOnFocusChanged),
|
||||||
|
|
@ -381,10 +383,12 @@ fun TrailerButton(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DeleteButton(
|
fun DeleteButton(
|
||||||
onClick: () -> Unit,
|
onConfirmDelete: () -> Unit,
|
||||||
|
title: String,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||||
) {
|
) {
|
||||||
|
var showDeleteDialog by remember { mutableStateOf(false) }
|
||||||
val focused by interactionSource.collectIsFocusedAsState()
|
val focused by interactionSource.collectIsFocusedAsState()
|
||||||
val iconTint by
|
val iconTint by
|
||||||
animateColorAsState(
|
animateColorAsState(
|
||||||
|
|
@ -415,10 +419,20 @@ fun DeleteButton(
|
||||||
.size(24.dp),
|
.size(24.dp),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClick = { onClick.invoke() },
|
onClick = { showDeleteDialog = true },
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
|
if (showDeleteDialog) {
|
||||||
|
ConfirmDeleteDialog(
|
||||||
|
itemTitle = title,
|
||||||
|
onCancel = { showDeleteDialog = false },
|
||||||
|
onConfirm = {
|
||||||
|
onConfirmDelete.invoke()
|
||||||
|
showDeleteDialog = false
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreviewTvSpec
|
@PreviewTvSpec
|
||||||
|
|
@ -426,6 +440,7 @@ fun DeleteButton(
|
||||||
private fun ExpandablePlayButtonsPreview() {
|
private fun ExpandablePlayButtonsPreview() {
|
||||||
WholphinTheme(true) {
|
WholphinTheme(true) {
|
||||||
ExpandablePlayButtons(
|
ExpandablePlayButtons(
|
||||||
|
title = "Movie",
|
||||||
resumePosition = 10.seconds,
|
resumePosition = 10.seconds,
|
||||||
watched = false,
|
watched = false,
|
||||||
favorite = false,
|
favorite = false,
|
||||||
|
|
@ -437,7 +452,7 @@ private fun ExpandablePlayButtonsPreview() {
|
||||||
trailers = listOf(),
|
trailers = listOf(),
|
||||||
trailerOnClick = {},
|
trailerOnClick = {},
|
||||||
canDelete = true,
|
canDelete = true,
|
||||||
deleteOnClick = {},
|
onConfirmDelete = {},
|
||||||
modifier = Modifier,
|
modifier = Modifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -482,7 +497,8 @@ private fun ViewOptionsPreview() {
|
||||||
modifier = Modifier,
|
modifier = Modifier,
|
||||||
)
|
)
|
||||||
DeleteButton(
|
DeleteButton(
|
||||||
onClick = {},
|
onConfirmDelete = {},
|
||||||
|
title = "Movie",
|
||||||
)
|
)
|
||||||
SortByButton(
|
SortByButton(
|
||||||
sortOptions = listOf(),
|
sortOptions = listOf(),
|
||||||
|
|
@ -491,7 +507,8 @@ private fun ViewOptionsPreview() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
DeleteButton(
|
DeleteButton(
|
||||||
onClick = {},
|
onConfirmDelete = {},
|
||||||
|
title = "Movie",
|
||||||
interactionSource = source,
|
interactionSource = source,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||||
|
|
@ -18,6 +17,7 @@ import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.github.damontecres.wholphin.R
|
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.BaseItem
|
||||||
import com.github.damontecres.wholphin.data.model.HomeRowViewOptions
|
import com.github.damontecres.wholphin.data.model.HomeRowViewOptions
|
||||||
import com.github.damontecres.wholphin.preferences.AppPreferences
|
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.services.deleteItem
|
||||||
import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect
|
import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect
|
||||||
import com.github.damontecres.wholphin.ui.data.AddPlaylistViewModel
|
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.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.PlaylistDialog
|
||||||
import com.github.damontecres.wholphin.ui.detail.PlaylistLoadingState
|
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.detail.music.addToQueue
|
||||||
import com.github.damontecres.wholphin.ui.launchDefault
|
import com.github.damontecres.wholphin.ui.launchDefault
|
||||||
import com.github.damontecres.wholphin.ui.launchIO
|
import com.github.damontecres.wholphin.ui.launchIO
|
||||||
|
|
@ -60,6 +60,7 @@ import java.util.UUID
|
||||||
abstract class RecommendedViewModel(
|
abstract class RecommendedViewModel(
|
||||||
@param:ApplicationContext val context: Context,
|
@param:ApplicationContext val context: Context,
|
||||||
val api: ApiClient,
|
val api: ApiClient,
|
||||||
|
val serverRepository: ServerRepository,
|
||||||
val navigationManager: NavigationManager,
|
val navigationManager: NavigationManager,
|
||||||
val favoriteWatchManager: FavoriteWatchManager,
|
val favoriteWatchManager: FavoriteWatchManager,
|
||||||
val mediaReportService: MediaReportService,
|
val mediaReportService: MediaReportService,
|
||||||
|
|
@ -167,10 +168,9 @@ fun RecommendedContent(
|
||||||
playlistViewModel: AddPlaylistViewModel = hiltViewModel(),
|
playlistViewModel: AddPlaylistViewModel = hiltViewModel(),
|
||||||
onFocusPosition: ((RowColumn) -> Unit)? = null,
|
onFocusPosition: ((RowColumn) -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
var showContextMenu by remember { mutableStateOf<ContextMenu?>(null) }
|
||||||
var moreDialog by remember { mutableStateOf<Optional<RowColumnItem>>(Optional.absent()) }
|
var overviewDialog by remember { mutableStateOf<ItemDetailsDialogInfo?>(null) }
|
||||||
var showPlaylistDialog by remember { mutableStateOf<Optional<UUID>>(Optional.absent()) }
|
var showPlaylistDialog by remember { mutableStateOf<Optional<UUID>>(Optional.absent()) }
|
||||||
var showDeleteDialog by remember { mutableStateOf<RowColumnItem?>(null) }
|
|
||||||
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
||||||
|
|
||||||
OneTimeLaunchedEffect {
|
OneTimeLaunchedEffect {
|
||||||
|
|
@ -192,6 +192,35 @@ fun RecommendedContent(
|
||||||
|
|
||||||
LoadingState.Success -> {
|
LoadingState.Success -> {
|
||||||
var position by rememberPosition()
|
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(
|
HomePageContent(
|
||||||
homeRows = rows,
|
homeRows = rows,
|
||||||
position = position,
|
position = position,
|
||||||
|
|
@ -199,7 +228,18 @@ fun RecommendedContent(
|
||||||
viewModel.navigationManager.navigateTo(item.destination())
|
viewModel.navigationManager.navigateTo(item.destination())
|
||||||
},
|
},
|
||||||
onLongClickItem = { position, item ->
|
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 ->
|
onClickPlay = { _, item ->
|
||||||
viewModel.navigationManager.navigateTo(Destination.Playback(item))
|
viewModel.navigationManager.navigateTo(Destination.Playback(item))
|
||||||
|
|
@ -226,42 +266,22 @@ fun RecommendedContent(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
moreDialog.compose { (position, item) ->
|
overviewDialog?.let { info ->
|
||||||
DialogPopup(
|
ItemDetailsDialog(
|
||||||
showDialog = true,
|
info = info,
|
||||||
title = item.title ?: "",
|
showFilePath =
|
||||||
dialogItems =
|
viewModel.serverRepository.currentUserDto.value
|
||||||
buildMoreDialogItemsForHome(
|
?.policy
|
||||||
context = context,
|
?.isAdministrator == true,
|
||||||
item = item,
|
onDismissRequest = { overviewDialog = null },
|
||||||
seriesId = null,
|
)
|
||||||
playbackPosition = item.playbackPosition,
|
}
|
||||||
watched = item.played,
|
showContextMenu?.let { contextMenu ->
|
||||||
favorite = item.favorite,
|
ContextMenuDialog(
|
||||||
canDelete = viewModel.canDelete(item, preferences.appPreferences),
|
onDismissRequest = { showContextMenu = null },
|
||||||
actions =
|
getMediaSource = null,
|
||||||
MoreDialogActions(
|
contextMenu = contextMenu,
|
||||||
navigateTo = { viewModel.navigationManager.navigateTo(it) },
|
preferredSubtitleLanguage = null,
|
||||||
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,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
showPlaylistDialog.compose { itemId ->
|
showPlaylistDialog.compose { itemId ->
|
||||||
|
|
@ -281,16 +301,6 @@ fun RecommendedContent(
|
||||||
elevation = 3.dp,
|
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(
|
data class RowColumnItem(
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ class RecommendedMovieViewModel
|
||||||
@ApplicationContext context: Context,
|
@ApplicationContext context: Context,
|
||||||
api: ApiClient,
|
api: ApiClient,
|
||||||
musicService: MusicService,
|
musicService: MusicService,
|
||||||
private val serverRepository: ServerRepository,
|
serverRepository: ServerRepository,
|
||||||
private val preferencesDataStore: DataStore<AppPreferences>,
|
private val preferencesDataStore: DataStore<AppPreferences>,
|
||||||
private val suggestionService: SuggestionService,
|
private val suggestionService: SuggestionService,
|
||||||
@Assisted val parentId: UUID,
|
@Assisted val parentId: UUID,
|
||||||
|
|
@ -70,6 +70,7 @@ class RecommendedMovieViewModel
|
||||||
) : RecommendedViewModel(
|
) : RecommendedViewModel(
|
||||||
context,
|
context,
|
||||||
api,
|
api,
|
||||||
|
serverRepository,
|
||||||
navigationManager,
|
navigationManager,
|
||||||
favoriteWatchManager,
|
favoriteWatchManager,
|
||||||
mediaReportService,
|
mediaReportService,
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ class RecommendedMusicViewModel
|
||||||
@ApplicationContext context: Context,
|
@ApplicationContext context: Context,
|
||||||
api: ApiClient,
|
api: ApiClient,
|
||||||
musicService: MusicService,
|
musicService: MusicService,
|
||||||
private val serverRepository: ServerRepository,
|
serverRepository: ServerRepository,
|
||||||
private val preferencesDataStore: DataStore<AppPreferences>,
|
private val preferencesDataStore: DataStore<AppPreferences>,
|
||||||
private val suggestionService: SuggestionService,
|
private val suggestionService: SuggestionService,
|
||||||
@Assisted val parentId: UUID,
|
@Assisted val parentId: UUID,
|
||||||
|
|
@ -69,6 +69,7 @@ class RecommendedMusicViewModel
|
||||||
) : RecommendedViewModel(
|
) : RecommendedViewModel(
|
||||||
context,
|
context,
|
||||||
api,
|
api,
|
||||||
|
serverRepository,
|
||||||
navigationManager,
|
navigationManager,
|
||||||
favoriteWatchManager,
|
favoriteWatchManager,
|
||||||
mediaReportService,
|
mediaReportService,
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ class RecommendedTvShowViewModel
|
||||||
@ApplicationContext context: Context,
|
@ApplicationContext context: Context,
|
||||||
api: ApiClient,
|
api: ApiClient,
|
||||||
musicService: MusicService,
|
musicService: MusicService,
|
||||||
private val serverRepository: ServerRepository,
|
serverRepository: ServerRepository,
|
||||||
private val preferencesDataStore: DataStore<AppPreferences>,
|
private val preferencesDataStore: DataStore<AppPreferences>,
|
||||||
private val lastestNextUpService: LatestNextUpService,
|
private val lastestNextUpService: LatestNextUpService,
|
||||||
private val suggestionService: SuggestionService,
|
private val suggestionService: SuggestionService,
|
||||||
|
|
@ -74,6 +74,7 @@ class RecommendedTvShowViewModel
|
||||||
) : RecommendedViewModel(
|
) : RecommendedViewModel(
|
||||||
context,
|
context,
|
||||||
api,
|
api,
|
||||||
|
serverRepository,
|
||||||
navigationManager,
|
navigationManager,
|
||||||
favoriteWatchManager,
|
favoriteWatchManager,
|
||||||
mediaReportService,
|
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.Add
|
||||||
import androidx.compose.material.icons.filled.ArrowForward
|
import androidx.compose.material.icons.filled.ArrowForward
|
||||||
import androidx.compose.material.icons.filled.Delete
|
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.PlayArrow
|
||||||
import androidx.compose.material.icons.filled.Refresh
|
import androidx.compose.material.icons.filled.Refresh
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import com.github.damontecres.wholphin.R
|
import com.github.damontecres.wholphin.R
|
||||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
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.components.DialogItem
|
||||||
import com.github.damontecres.wholphin.ui.letNotEmpty
|
|
||||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||||
import com.github.damontecres.wholphin.util.supportedPlayableTypes
|
import com.github.damontecres.wholphin.util.supportedPlayableTypes
|
||||||
import org.jellyfin.sdk.model.UUID
|
import org.jellyfin.sdk.model.UUID
|
||||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
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
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
|
|
@ -36,234 +31,6 @@ data class MoreDialogActions(
|
||||||
val onClickAddToQueue: (BaseItem) -> Unit = {},
|
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(
|
fun buildMoreDialogItemsForHome(
|
||||||
context: Context,
|
context: Context,
|
||||||
item: BaseItem,
|
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.DefaultItemFields
|
||||||
import com.github.damontecres.wholphin.ui.cards.ItemCardImage
|
import com.github.damontecres.wholphin.ui.cards.ItemCardImage
|
||||||
import com.github.damontecres.wholphin.ui.components.BasicDialog
|
import com.github.damontecres.wholphin.ui.components.BasicDialog
|
||||||
import com.github.damontecres.wholphin.ui.components.ConfirmDeleteDialog
|
import com.github.damontecres.wholphin.ui.components.ContextMenu
|
||||||
import com.github.damontecres.wholphin.ui.components.DialogParams
|
import com.github.damontecres.wholphin.ui.components.ContextMenuActions
|
||||||
import com.github.damontecres.wholphin.ui.components.DialogPopup
|
import com.github.damontecres.wholphin.ui.components.ContextMenuDialog
|
||||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||||
import com.github.damontecres.wholphin.ui.components.ExpandableFaButton
|
import com.github.damontecres.wholphin.ui.components.ExpandableFaButton
|
||||||
import com.github.damontecres.wholphin.ui.components.ExpandablePlayButton
|
import com.github.damontecres.wholphin.ui.components.ExpandablePlayButton
|
||||||
import com.github.damontecres.wholphin.ui.components.FilterByButton
|
import com.github.damontecres.wholphin.ui.components.FilterByButton
|
||||||
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
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.Optional
|
||||||
import com.github.damontecres.wholphin.ui.components.OverviewText
|
import com.github.damontecres.wholphin.ui.components.OverviewText
|
||||||
import com.github.damontecres.wholphin.ui.components.SortByButton
|
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.AddPlaylistViewModel
|
||||||
import com.github.damontecres.wholphin.ui.data.BoxSetSortOptions
|
import com.github.damontecres.wholphin.ui.data.BoxSetSortOptions
|
||||||
import com.github.damontecres.wholphin.ui.data.SortAndDirection
|
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.MusicQueueMarker
|
||||||
import com.github.damontecres.wholphin.ui.detail.music.MusicViewModel
|
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.enableMarquee
|
||||||
import com.github.damontecres.wholphin.ui.equalsNotNull
|
import com.github.damontecres.wholphin.ui.equalsNotNull
|
||||||
import com.github.damontecres.wholphin.ui.formatDateTime
|
import com.github.damontecres.wholphin.ui.formatDateTime
|
||||||
|
|
@ -371,7 +370,7 @@ fun PlaylistDetails(
|
||||||
val state by viewModel.state.collectAsState()
|
val state by viewModel.state.collectAsState()
|
||||||
val musicState by viewModel.musicState.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 showConfirmTypeDialog by remember { mutableStateOf<Triple<Int, BaseItem, Boolean>?>(null) }
|
||||||
var showPlaylistDialog by remember { mutableStateOf<Optional<UUID>>(Optional.absent()) }
|
var showPlaylistDialog by remember { mutableStateOf<Optional<UUID>>(Optional.absent()) }
|
||||||
val addPlaylistState by addToPlaylistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
val addPlaylistState by addToPlaylistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
||||||
|
|
@ -404,34 +403,40 @@ fun PlaylistDetails(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var showDeleteDialog by remember { mutableStateOf<BaseItem?>(null) }
|
val musicContextActions =
|
||||||
val musicMoreActions =
|
MusicContextActions(
|
||||||
MusicMoreDialogActions(
|
navigateTo = { viewModel.navigationManager.navigateTo(it) },
|
||||||
onNavigate = { viewModel.navigationManager.navigateTo(it) },
|
|
||||||
onClickPlay = { index, item -> play(index, item, false, MediaType.AUDIO) },
|
onClickPlay = { index, item -> play(index, item, false, MediaType.AUDIO) },
|
||||||
onClickPlayNext = { index, item -> viewModel.playNext(item) },
|
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) },
|
onClickFavorite = { id, favorite -> viewModel.setFavorite(id, favorite) },
|
||||||
onClickAddPlaylist = { itemId ->
|
onClickAddPlaylist = { itemId ->
|
||||||
addToPlaylistViewModel.loadPlaylists(MediaType.AUDIO)
|
addToPlaylistViewModel.loadPlaylists(MediaType.AUDIO)
|
||||||
showPlaylistDialog.makePresent(itemId)
|
showPlaylistDialog.makePresent(itemId)
|
||||||
},
|
},
|
||||||
onClickRemoveFromQueue = {},
|
onClickRemoveFromQueue = { _, _ -> },
|
||||||
onClickDelete = { showDeleteDialog = it },
|
onDeleteItem = viewModel::deleteItem,
|
||||||
)
|
|
||||||
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) },
|
|
||||||
)
|
)
|
||||||
|
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(
|
PlaylistDetailsContent(
|
||||||
loadingState = state.loading,
|
loadingState = state.loading,
|
||||||
|
|
@ -448,43 +453,41 @@ fun PlaylistDetails(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLongClickIndex = { index, item ->
|
onLongClickIndex = { index, item ->
|
||||||
longClickDialog =
|
showContextMenu =
|
||||||
DialogParams(
|
if (item.type == BaseItemKind.AUDIO) {
|
||||||
fromLongClick = true,
|
ContextMenu.ForMusic(
|
||||||
title = item.name ?: "",
|
fromLongClick = true,
|
||||||
items =
|
item = item,
|
||||||
if (item.type == BaseItemKind.AUDIO) {
|
index = index,
|
||||||
buildMoreDialogForMusic(
|
canDelete = viewModel.canDelete(item, preferences.appPreferences),
|
||||||
context = context,
|
canRemoveFromQueue = false,
|
||||||
actions = musicMoreActions,
|
actions = musicContextActions,
|
||||||
item = item,
|
)
|
||||||
index = index,
|
} else {
|
||||||
canRemove = false,
|
ContextMenu.ForBaseItem(
|
||||||
canDelete = viewModel.canDelete(item, preferences.appPreferences),
|
fromLongClick = true,
|
||||||
)
|
item = item,
|
||||||
} else {
|
chosenStreams = null,
|
||||||
buildMoreDialogItemsForHome(
|
showGoTo = true,
|
||||||
context = context,
|
showStreamChoices = false,
|
||||||
item = item,
|
canDelete = viewModel.canDelete(item, preferences.appPreferences),
|
||||||
seriesId = item.data.seriesId,
|
canRemoveContinueWatching = false,
|
||||||
playbackPosition = item.playbackPosition,
|
canRemoveNextUp = false,
|
||||||
watched = item.played,
|
actions = contextActions,
|
||||||
favorite = item.favorite,
|
)
|
||||||
canDelete = viewModel.canDelete(item, preferences.appPreferences),
|
}
|
||||||
actions = moreActions,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
filterAndSort = state.filterAndSort,
|
filterAndSort = state.filterAndSort,
|
||||||
onFilterAndSortChange = viewModel::loadItems,
|
onFilterAndSortChange = viewModel::loadItems,
|
||||||
getPossibleFilterValues = viewModel::getFilterOptionValues,
|
getPossibleFilterValues = viewModel::getFilterOptionValues,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
longClickDialog?.let { params ->
|
showContextMenu?.let { contextMenu ->
|
||||||
DialogPopup(
|
ContextMenuDialog(
|
||||||
params = params,
|
onDismissRequest = { showContextMenu = null },
|
||||||
onDismissRequest = { longClickDialog = null },
|
getMediaSource = null,
|
||||||
|
contextMenu = contextMenu,
|
||||||
|
preferredSubtitleLanguage = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
showConfirmTypeDialog?.let { (index, item, shuffle) ->
|
showConfirmTypeDialog?.let { (index, item, shuffle) ->
|
||||||
|
|
@ -510,16 +513,6 @@ fun PlaylistDetails(
|
||||||
elevation = 3.dp,
|
elevation = 3.dp,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
showDeleteDialog?.let { item ->
|
|
||||||
ConfirmDeleteDialog(
|
|
||||||
itemTitle = item.title ?: "",
|
|
||||||
onCancel = { showDeleteDialog = null },
|
|
||||||
onConfirm = {
|
|
||||||
viewModel.deleteItem(item)
|
|
||||||
showDeleteDialog = null
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ fun CollectionButtons(
|
||||||
getPossibleFilterValues: suspend (ItemFilterBy<*>) -> List<FilterValueOption>,
|
getPossibleFilterValues: suspend (ItemFilterBy<*>) -> List<FilterValueOption>,
|
||||||
onClickViewOptions: () -> Unit,
|
onClickViewOptions: () -> Unit,
|
||||||
favoriteOnClick: () -> Unit,
|
favoriteOnClick: () -> Unit,
|
||||||
deleteOnClick: () -> Unit,
|
onConfirmDelete: () -> Unit,
|
||||||
canDelete: Boolean,
|
canDelete: Boolean,
|
||||||
moreOnClick: () -> Unit,
|
moreOnClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
|
@ -91,9 +91,9 @@ fun CollectionButtons(
|
||||||
if (canDelete) {
|
if (canDelete) {
|
||||||
item("delete") {
|
item("delete") {
|
||||||
DeleteButton(
|
DeleteButton(
|
||||||
onClick = deleteOnClick,
|
title = state.collection?.title ?: "",
|
||||||
modifier =
|
onConfirmDelete = onConfirmDelete,
|
||||||
Modifier,
|
modifier = Modifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package com.github.damontecres.wholphin.ui.detail.collection
|
package com.github.damontecres.wholphin.ui.detail.collection
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.compose.animation.AnimatedContent
|
import androidx.compose.animation.AnimatedContent
|
||||||
import androidx.compose.animation.SharedTransitionLayout
|
import androidx.compose.animation.SharedTransitionLayout
|
||||||
import androidx.compose.animation.fadeIn
|
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.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.relocation.BringIntoViewRequester
|
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.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
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.focusProperties
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.focus.onFocusChanged
|
import androidx.compose.ui.focus.onFocusChanged
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
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.BaseItem
|
||||||
import com.github.damontecres.wholphin.data.model.GetItemsFilter
|
import com.github.damontecres.wholphin.data.model.GetItemsFilter
|
||||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||||
import com.github.damontecres.wholphin.ui.components.ConfirmDeleteDialog
|
import com.github.damontecres.wholphin.ui.components.ContextMenu
|
||||||
import com.github.damontecres.wholphin.ui.components.DialogItem
|
import com.github.damontecres.wholphin.ui.components.ContextMenuActions
|
||||||
import com.github.damontecres.wholphin.ui.components.DialogParams
|
import com.github.damontecres.wholphin.ui.components.ContextMenuDialog
|
||||||
import com.github.damontecres.wholphin.ui.components.DialogPopup
|
|
||||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||||
import com.github.damontecres.wholphin.ui.components.HeaderUtils
|
import com.github.damontecres.wholphin.ui.components.HeaderUtils
|
||||||
import com.github.damontecres.wholphin.ui.components.HiddenFocusBox
|
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.ItemDetailsDialogInfo
|
||||||
import com.github.damontecres.wholphin.ui.data.RowColumn
|
import com.github.damontecres.wholphin.ui.data.RowColumn
|
||||||
import com.github.damontecres.wholphin.ui.data.SortAndDirection
|
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.PlaylistDialog
|
||||||
import com.github.damontecres.wholphin.ui.detail.PlaylistLoadingState
|
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.main.HomePageHeader
|
||||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||||
|
|
@ -85,53 +77,58 @@ fun CollectionDetails(
|
||||||
val state by viewModel.state.collectAsState()
|
val state by viewModel.state.collectAsState()
|
||||||
|
|
||||||
// Dialogs
|
// Dialogs
|
||||||
var moreDialog by remember { mutableStateOf<DialogParams?>(null) }
|
var showContextMenu by remember { mutableStateOf<ContextMenu?>(null) }
|
||||||
var showPlaylistDialog by remember { mutableStateOf<Optional<UUID>>(Optional.absent()) }
|
var showPlaylistDialog by remember { mutableStateOf<Optional<UUID>>(Optional.absent()) }
|
||||||
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
||||||
var showDeleteDialog by remember { mutableStateOf<Pair<RowColumn?, BaseItem>?>(null) }
|
|
||||||
var showViewOptionsDialog by remember { mutableStateOf(false) }
|
var showViewOptionsDialog by remember { mutableStateOf(false) }
|
||||||
var overviewDialog by remember { mutableStateOf<ItemDetailsDialogInfo?>(null) }
|
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
|
// Actions
|
||||||
val onClickItem =
|
val onClickItem =
|
||||||
remember {
|
remember {
|
||||||
{ _: RowColumn, item: BaseItem -> viewModel.navigate(item.destination()) }
|
{ _: RowColumn, item: BaseItem -> viewModel.navigateTo(item.destination()) }
|
||||||
}
|
}
|
||||||
val onLongClickItem =
|
val onLongClickItem =
|
||||||
remember {
|
remember {
|
||||||
{ position: RowColumn, item: BaseItem ->
|
{ position: RowColumn, item: BaseItem ->
|
||||||
val dialogItems =
|
showContextMenu =
|
||||||
buildMoreDialogItemsForHome(
|
ContextMenu.ForBaseItem(
|
||||||
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(
|
|
||||||
fromLongClick = true,
|
fromLongClick = true,
|
||||||
title = item.title ?: "",
|
item = item,
|
||||||
items = dialogItems,
|
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) }
|
{ filter: GetItemsFilter -> viewModel.changeFilter(filter) }
|
||||||
}
|
}
|
||||||
val onClickPlay = { _: RowColumn, item: BaseItem ->
|
val onClickPlay = { _: RowColumn, item: BaseItem ->
|
||||||
viewModel.navigate(Destination.Playback(item = item))
|
viewModel.navigateTo(Destination.Playback(item = item))
|
||||||
}
|
}
|
||||||
val onClickPlayAll =
|
val onClickPlayAll =
|
||||||
remember {
|
remember {
|
||||||
|
|
@ -158,7 +155,7 @@ fun CollectionDetails(
|
||||||
sortAndDirection = state.sortAndDirection,
|
sortAndDirection = state.sortAndDirection,
|
||||||
filter = state.itemFilter,
|
filter = state.itemFilter,
|
||||||
)
|
)
|
||||||
viewModel.navigate(dest)
|
viewModel.navigateTo(dest)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val onClickViewOptions = remember { { showViewOptionsDialog = true } }
|
val onClickViewOptions = remember { { showViewOptionsDialog = true } }
|
||||||
|
|
@ -201,7 +198,7 @@ fun CollectionDetails(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
deleteOnClick =
|
onConfirmDelete =
|
||||||
remember {
|
remember {
|
||||||
{
|
{
|
||||||
state.collection?.let {
|
state.collection?.let {
|
||||||
|
|
@ -216,37 +213,21 @@ fun CollectionDetails(
|
||||||
} ?: false
|
} ?: false
|
||||||
},
|
},
|
||||||
moreOnClick = {
|
moreOnClick = {
|
||||||
val collection = state.collection!!
|
showContextMenu =
|
||||||
val items =
|
ContextMenu.ForBaseItem(
|
||||||
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(
|
|
||||||
fromLongClick = false,
|
fromLongClick = false,
|
||||||
title = collection.title ?: "",
|
item = state.collection!!,
|
||||||
items = items,
|
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,
|
onViewOptionsChange = viewModel::changeViewOptions,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
moreDialog?.let { params ->
|
overviewDialog?.let { info ->
|
||||||
DialogPopup(
|
ItemDetailsDialog(
|
||||||
showDialog = true,
|
info = info,
|
||||||
title = params.title,
|
showFilePath =
|
||||||
dialogItems = params.items,
|
viewModel.serverRepository.currentUserDto.value
|
||||||
onDismissRequest = { moreDialog = null },
|
?.policy
|
||||||
dismissOnClick = true,
|
?.isAdministrator == true,
|
||||||
waitToLoad = params.fromLongClick,
|
onDismissRequest = { overviewDialog = null },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
showContextMenu?.let { contextMenu ->
|
||||||
|
ContextMenuDialog(
|
||||||
|
onDismissRequest = { showContextMenu = null },
|
||||||
|
getMediaSource = null,
|
||||||
|
contextMenu = contextMenu,
|
||||||
|
preferredSubtitleLanguage = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
showPlaylistDialog.compose { itemId ->
|
showPlaylistDialog.compose { itemId ->
|
||||||
|
|
@ -286,26 +275,6 @@ fun CollectionDetails(
|
||||||
elevation = 3.dp,
|
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
|
@Composable
|
||||||
|
|
@ -324,7 +293,7 @@ fun CollectionDetailsContent(
|
||||||
onClickViewOptions: () -> Unit,
|
onClickViewOptions: () -> Unit,
|
||||||
overviewOnClick: () -> Unit,
|
overviewOnClick: () -> Unit,
|
||||||
favoriteOnClick: () -> Unit,
|
favoriteOnClick: () -> Unit,
|
||||||
deleteOnClick: () -> Unit,
|
onConfirmDelete: () -> Unit,
|
||||||
canDelete: Boolean,
|
canDelete: Boolean,
|
||||||
moreOnClick: () -> Unit,
|
moreOnClick: () -> Unit,
|
||||||
modifier: Modifier,
|
modifier: Modifier,
|
||||||
|
|
@ -423,7 +392,7 @@ fun CollectionDetailsContent(
|
||||||
getPossibleFilterValues = getPossibleFilterValues,
|
getPossibleFilterValues = getPossibleFilterValues,
|
||||||
onClickViewOptions = onClickViewOptions,
|
onClickViewOptions = onClickViewOptions,
|
||||||
favoriteOnClick = favoriteOnClick,
|
favoriteOnClick = favoriteOnClick,
|
||||||
deleteOnClick = deleteOnClick,
|
onConfirmDelete = onConfirmDelete,
|
||||||
canDelete = canDelete,
|
canDelete = canDelete,
|
||||||
moreOnClick = moreOnClick,
|
moreOnClick = moreOnClick,
|
||||||
modifier =
|
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
|
result.totalRecordCount
|
||||||
}
|
}
|
||||||
|
|
||||||
fun navigate(destination: Destination) {
|
fun navigateTo(destination: Destination) {
|
||||||
release()
|
release()
|
||||||
navigationManager.navigateTo(destination)
|
navigationManager.navigateTo(destination)
|
||||||
}
|
}
|
||||||
|
|
@ -393,7 +393,9 @@ class CollectionViewModel
|
||||||
position: RowColumn?,
|
position: RowColumn?,
|
||||||
) = viewModelScope.launch(Dispatchers.IO + ExceptionHandler()) {
|
) = viewModelScope.launch(Dispatchers.IO + ExceptionHandler()) {
|
||||||
favoriteWatchManager.setWatched(itemId, played)
|
favoriteWatchManager.setWatched(itemId, played)
|
||||||
if (position != null) {
|
if (itemId == state.value.collection?.id) {
|
||||||
|
refreshCollection()
|
||||||
|
} else if (position != null) {
|
||||||
refreshItem(itemId, position, false)
|
refreshItem(itemId, position, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -404,11 +406,24 @@ class CollectionViewModel
|
||||||
position: RowColumn?,
|
position: RowColumn?,
|
||||||
) = viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
|
) = viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
|
||||||
favoriteWatchManager.setFavorite(itemId, favorite)
|
favoriteWatchManager.setFavorite(itemId, favorite)
|
||||||
if (position != null) {
|
if (itemId == state.value.collection?.id) {
|
||||||
|
refreshCollection()
|
||||||
|
} else if (position != null) {
|
||||||
refreshItem(itemId, position, false)
|
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(
|
fun canDelete(
|
||||||
item: BaseItem,
|
item: BaseItem,
|
||||||
appPreferences: AppPreferences,
|
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.data.model.BaseItem
|
||||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||||
import com.github.damontecres.wholphin.ui.RequestOrRestoreFocus
|
import com.github.damontecres.wholphin.ui.RequestOrRestoreFocus
|
||||||
import com.github.damontecres.wholphin.ui.components.ConfirmDeleteDialog
|
import com.github.damontecres.wholphin.ui.components.ContextMenu
|
||||||
import com.github.damontecres.wholphin.ui.components.DialogParams
|
import com.github.damontecres.wholphin.ui.components.ContextMenuActions
|
||||||
import com.github.damontecres.wholphin.ui.components.DialogPopup
|
import com.github.damontecres.wholphin.ui.components.ContextMenuDialog
|
||||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||||
import com.github.damontecres.wholphin.ui.components.ExpandablePlayButtons
|
import com.github.damontecres.wholphin.ui.components.ExpandablePlayButtons
|
||||||
import com.github.damontecres.wholphin.ui.components.HeaderUtils
|
import com.github.damontecres.wholphin.ui.components.HeaderUtils
|
||||||
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
||||||
import com.github.damontecres.wholphin.ui.components.Optional
|
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.AddPlaylistViewModel
|
||||||
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialog
|
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialog
|
||||||
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialogInfo
|
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.PlaylistDialog
|
||||||
import com.github.damontecres.wholphin.ui.detail.PlaylistLoadingState
|
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.nav.Destination
|
||||||
import com.github.damontecres.wholphin.ui.rememberInt
|
import com.github.damontecres.wholphin.ui.rememberInt
|
||||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||||
import com.github.damontecres.wholphin.util.LoadingState
|
import com.github.damontecres.wholphin.util.LoadingState
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.jellyfin.sdk.model.api.MediaStreamType
|
|
||||||
import org.jellyfin.sdk.model.api.MediaType
|
import org.jellyfin.sdk.model.api.MediaType
|
||||||
import org.jellyfin.sdk.model.extensions.ticks
|
import org.jellyfin.sdk.model.extensions.ticks
|
||||||
import org.jellyfin.sdk.model.serializer.toUUID
|
import org.jellyfin.sdk.model.serializer.toUUID
|
||||||
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
|
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
|
|
||||||
|
|
@ -82,10 +76,8 @@ fun EpisodeDetails(
|
||||||
val chosenStreams by viewModel.chosenStreams.observeAsState(null)
|
val chosenStreams by viewModel.chosenStreams.observeAsState(null)
|
||||||
|
|
||||||
var overviewDialog by remember { mutableStateOf<ItemDetailsDialogInfo?>(null) }
|
var overviewDialog by remember { mutableStateOf<ItemDetailsDialogInfo?>(null) }
|
||||||
var moreDialog by remember { mutableStateOf<DialogParams?>(null) }
|
var showContextMenu by remember { mutableStateOf<ContextMenu?>(null) }
|
||||||
var chooseVersion by remember { mutableStateOf<DialogParams?>(null) }
|
|
||||||
var showPlaylistDialog by remember { mutableStateOf<Optional<UUID>>(Optional.absent()) }
|
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 playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
||||||
val canDelete by viewModel.canDelete.collectAsState()
|
val canDelete by viewModel.canDelete.collectAsState()
|
||||||
|
|
||||||
|
|
@ -96,22 +88,36 @@ fun EpisodeDetails(
|
||||||
?.configuration
|
?.configuration
|
||||||
?.subtitleLanguagePreference
|
?.subtitleLanguagePreference
|
||||||
|
|
||||||
val moreActions =
|
val contextActions =
|
||||||
MoreDialogActions(
|
remember {
|
||||||
navigateTo = viewModel::navigateTo,
|
ContextMenuActions(
|
||||||
onClickWatch = { itemId, watched ->
|
navigateTo = viewModel::navigateTo,
|
||||||
viewModel.setWatched(itemId, watched)
|
onClickWatch = viewModel::setWatched,
|
||||||
},
|
onClickFavorite = viewModel::setFavorite,
|
||||||
onClickFavorite = { itemId, favorite ->
|
onClickAddPlaylist = { itemId ->
|
||||||
viewModel.setFavorite(itemId, favorite)
|
playlistViewModel.loadPlaylists(MediaType.VIDEO)
|
||||||
},
|
showPlaylistDialog.makePresent(itemId)
|
||||||
onClickAddPlaylist = { itemId ->
|
},
|
||||||
playlistViewModel.loadPlaylists(MediaType.VIDEO)
|
onSendMediaInfo = viewModel.mediaReportService::sendReportFor,
|
||||||
showPlaylistDialog.makePresent(itemId)
|
onDeleteItem = viewModel::deleteItem,
|
||||||
},
|
onShowOverview = { overviewDialog = ItemDetailsDialogInfo(it) },
|
||||||
onSendMediaInfo = viewModel.mediaReportService::sendReportFor,
|
onChooseVersion = { item, source ->
|
||||||
onClickDelete = { showDeleteDialog = it },
|
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) {
|
when (val state = loading) {
|
||||||
is LoadingState.Error -> {
|
is LoadingState.Error -> {
|
||||||
|
|
@ -152,75 +158,17 @@ fun EpisodeDetails(
|
||||||
ItemDetailsDialogInfo(ep)
|
ItemDetailsDialogInfo(ep)
|
||||||
},
|
},
|
||||||
moreOnClick = {
|
moreOnClick = {
|
||||||
moreDialog =
|
showContextMenu =
|
||||||
DialogParams(
|
ContextMenu.ForBaseItem(
|
||||||
fromLongClick = false,
|
fromLongClick = false,
|
||||||
title = ep.name + " (${ep.data.productionYear ?: ""})",
|
item = ep,
|
||||||
items =
|
chosenStreams = chosenStreams,
|
||||||
buildMoreDialogItems(
|
showGoTo = false,
|
||||||
context = context,
|
showStreamChoices = true,
|
||||||
item = ep,
|
canDelete = canDelete,
|
||||||
watched = ep.data.userData?.played ?: false,
|
canRemoveContinueWatching = false,
|
||||||
favorite = ep.data.userData?.isFavorite ?: false,
|
canRemoveNextUp = false,
|
||||||
seriesId = ep.data.seriesId,
|
actions = contextActions,
|
||||||
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,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
watchOnClick = {
|
watchOnClick = {
|
||||||
|
|
@ -230,7 +178,7 @@ fun EpisodeDetails(
|
||||||
viewModel.setFavorite(ep.id, !ep.favorite)
|
viewModel.setFavorite(ep.id, !ep.favorite)
|
||||||
},
|
},
|
||||||
canDelete = canDelete,
|
canDelete = canDelete,
|
||||||
deleteOnClick = { showDeleteDialog = ep },
|
onConfirmDelete = { viewModel.deleteItem(ep) },
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -246,24 +194,12 @@ fun EpisodeDetails(
|
||||||
onDismissRequest = { overviewDialog = null },
|
onDismissRequest = { overviewDialog = null },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
moreDialog?.let { params ->
|
showContextMenu?.let { contextMenu ->
|
||||||
DialogPopup(
|
ContextMenuDialog(
|
||||||
showDialog = true,
|
onDismissRequest = { showContextMenu = null },
|
||||||
title = params.title,
|
getMediaSource = viewModel.streamChoiceService::chooseSource,
|
||||||
dialogItems = params.items,
|
contextMenu = contextMenu,
|
||||||
onDismissRequest = { moreDialog = null },
|
preferredSubtitleLanguage = preferredSubtitleLanguage,
|
||||||
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 ->
|
showPlaylistDialog.compose { itemId ->
|
||||||
|
|
@ -283,16 +219,6 @@ fun EpisodeDetails(
|
||||||
elevation = 3.dp,
|
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
|
private const val HEADER_ROW = 0
|
||||||
|
|
@ -308,7 +234,7 @@ fun EpisodeDetailsContent(
|
||||||
favoriteOnClick: () -> Unit,
|
favoriteOnClick: () -> Unit,
|
||||||
moreOnClick: () -> Unit,
|
moreOnClick: () -> Unit,
|
||||||
canDelete: Boolean,
|
canDelete: Boolean,
|
||||||
deleteOnClick: () -> Unit,
|
onConfirmDelete: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
@ -346,6 +272,7 @@ fun EpisodeDetailsContent(
|
||||||
.padding(top = HeaderUtils.topPadding, bottom = 16.dp),
|
.padding(top = HeaderUtils.topPadding, bottom = 16.dp),
|
||||||
)
|
)
|
||||||
ExpandablePlayButtons(
|
ExpandablePlayButtons(
|
||||||
|
title = ep.title ?: "",
|
||||||
resumePosition = resumePosition,
|
resumePosition = resumePosition,
|
||||||
watched = dto.userData?.played ?: false,
|
watched = dto.userData?.played ?: false,
|
||||||
favorite = dto.userData?.isFavorite ?: false,
|
favorite = dto.userData?.isFavorite ?: false,
|
||||||
|
|
@ -367,7 +294,7 @@ fun EpisodeDetailsContent(
|
||||||
trailers = null,
|
trailers = null,
|
||||||
trailerOnClick = {},
|
trailerOnClick = {},
|
||||||
canDelete = canDelete,
|
canDelete = canDelete,
|
||||||
deleteOnClick = deleteOnClick,
|
onConfirmDelete = onConfirmDelete,
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.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.ItemRow
|
||||||
import com.github.damontecres.wholphin.ui.cards.PersonRow
|
import com.github.damontecres.wholphin.ui.cards.PersonRow
|
||||||
import com.github.damontecres.wholphin.ui.cards.SeasonCard
|
import com.github.damontecres.wholphin.ui.cards.SeasonCard
|
||||||
import com.github.damontecres.wholphin.ui.components.ConfirmDeleteDialog
|
import com.github.damontecres.wholphin.ui.components.ContextMenu
|
||||||
import com.github.damontecres.wholphin.ui.components.DialogParams
|
import com.github.damontecres.wholphin.ui.components.ContextMenuActions
|
||||||
import com.github.damontecres.wholphin.ui.components.DialogPopup
|
import com.github.damontecres.wholphin.ui.components.ContextMenuDialog
|
||||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||||
import com.github.damontecres.wholphin.ui.components.ExpandablePlayButtons
|
import com.github.damontecres.wholphin.ui.components.ExpandablePlayButtons
|
||||||
import com.github.damontecres.wholphin.ui.components.HeaderUtils
|
import com.github.damontecres.wholphin.ui.components.HeaderUtils
|
||||||
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
||||||
import com.github.damontecres.wholphin.ui.components.Optional
|
import com.github.damontecres.wholphin.ui.components.Optional
|
||||||
import com.github.damontecres.wholphin.ui.components.chooseStream
|
import com.github.damontecres.wholphin.ui.components.PersonContextActions
|
||||||
import com.github.damontecres.wholphin.ui.components.chooseVersionParams
|
|
||||||
import com.github.damontecres.wholphin.ui.data.AddPlaylistViewModel
|
import com.github.damontecres.wholphin.ui.data.AddPlaylistViewModel
|
||||||
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialog
|
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialog
|
||||||
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialogInfo
|
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.PlaylistDialog
|
||||||
import com.github.damontecres.wholphin.ui.detail.PlaylistLoadingState
|
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.DiscoverRow
|
||||||
import com.github.damontecres.wholphin.ui.discover.DiscoverRowData
|
import com.github.damontecres.wholphin.ui.discover.DiscoverRowData
|
||||||
import com.github.damontecres.wholphin.ui.letNotEmpty
|
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 com.github.damontecres.wholphin.util.ExceptionHandler
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
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.MediaType
|
||||||
import org.jellyfin.sdk.model.extensions.ticks
|
import org.jellyfin.sdk.model.extensions.ticks
|
||||||
import org.jellyfin.sdk.model.serializer.toUUID
|
import org.jellyfin.sdk.model.serializer.toUUID
|
||||||
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
|
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
|
|
||||||
|
|
@ -101,11 +94,9 @@ fun MovieDetails(
|
||||||
val state by viewModel.state.collectAsState()
|
val state by viewModel.state.collectAsState()
|
||||||
|
|
||||||
var overviewDialog by remember { mutableStateOf<ItemDetailsDialogInfo?>(null) }
|
var overviewDialog by remember { mutableStateOf<ItemDetailsDialogInfo?>(null) }
|
||||||
var moreDialog by remember { mutableStateOf<DialogParams?>(null) }
|
var showContextMenu by remember { mutableStateOf<ContextMenu?>(null) }
|
||||||
var chooseVersion by remember { mutableStateOf<DialogParams?>(null) }
|
|
||||||
var showPlaylistDialog by remember { mutableStateOf<Optional<UUID>>(Optional.absent()) }
|
var showPlaylistDialog by remember { mutableStateOf<Optional<UUID>>(Optional.absent()) }
|
||||||
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
||||||
var showDeleteDialog by remember { mutableStateOf<BaseItem?>(null) }
|
|
||||||
|
|
||||||
val preferredSubtitleLanguage =
|
val preferredSubtitleLanguage =
|
||||||
viewModel.serverRepository.currentUserDto
|
viewModel.serverRepository.currentUserDto
|
||||||
|
|
@ -114,22 +105,36 @@ fun MovieDetails(
|
||||||
?.configuration
|
?.configuration
|
||||||
?.subtitleLanguagePreference
|
?.subtitleLanguagePreference
|
||||||
|
|
||||||
val moreActions =
|
val contextActions =
|
||||||
MoreDialogActions(
|
remember {
|
||||||
navigateTo = viewModel::navigateTo,
|
ContextMenuActions(
|
||||||
onClickWatch = { itemId, watched ->
|
navigateTo = viewModel::navigateTo,
|
||||||
viewModel.setWatched(itemId, watched)
|
onClickWatch = viewModel::setWatched,
|
||||||
},
|
onClickFavorite = viewModel::setFavorite,
|
||||||
onClickFavorite = { itemId, favorite ->
|
onClickAddPlaylist = { itemId ->
|
||||||
viewModel.setFavorite(itemId, favorite)
|
playlistViewModel.loadPlaylists(MediaType.VIDEO)
|
||||||
},
|
showPlaylistDialog.makePresent(itemId)
|
||||||
onClickAddPlaylist = { itemId ->
|
},
|
||||||
playlistViewModel.loadPlaylists(MediaType.VIDEO)
|
onSendMediaInfo = viewModel.mediaReportService::sendReportFor,
|
||||||
showPlaylistDialog.makePresent(itemId)
|
onDeleteItem = viewModel::deleteItem,
|
||||||
},
|
onChooseVersion = { item, source ->
|
||||||
onSendMediaInfo = viewModel.mediaReportService::sendReportFor,
|
viewModel.savePlayVersion(
|
||||||
onClickDelete = { showDeleteDialog = it },
|
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) {
|
when (val s = state.loading) {
|
||||||
is DataLoadingState.Error -> {
|
is DataLoadingState.Error -> {
|
||||||
|
|
@ -183,72 +188,17 @@ fun MovieDetails(
|
||||||
ItemDetailsDialogInfo(movie)
|
ItemDetailsDialogInfo(movie)
|
||||||
},
|
},
|
||||||
moreOnClick = {
|
moreOnClick = {
|
||||||
moreDialog =
|
showContextMenu =
|
||||||
DialogParams(
|
ContextMenu.ForBaseItem(
|
||||||
fromLongClick = false,
|
fromLongClick = false,
|
||||||
title = movie.name + " (${movie.data.productionYear ?: ""})",
|
item = movie,
|
||||||
items =
|
chosenStreams = chosenStreams,
|
||||||
buildMoreDialogItems(
|
showGoTo = false,
|
||||||
context = context,
|
showStreamChoices = true,
|
||||||
item = movie,
|
canDelete = state.canDelete,
|
||||||
watched = movie.data.userData?.played ?: false,
|
canRemoveContinueWatching = false,
|
||||||
favorite = movie.data.userData?.isFavorite ?: false,
|
canRemoveNextUp = false,
|
||||||
seriesId = null,
|
actions = contextActions,
|
||||||
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)
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
watchOnClick = {
|
watchOnClick = {
|
||||||
|
|
@ -258,36 +208,29 @@ fun MovieDetails(
|
||||||
viewModel.setFavorite(movie.id, !movie.favorite)
|
viewModel.setFavorite(movie.id, !movie.favorite)
|
||||||
},
|
},
|
||||||
onLongClickPerson = { index, person ->
|
onLongClickPerson = { index, person ->
|
||||||
val items =
|
showContextMenu =
|
||||||
buildMoreDialogItemsForPerson(
|
ContextMenu.ForPerson(
|
||||||
context = context,
|
|
||||||
person = person,
|
|
||||||
actions = moreActions,
|
|
||||||
)
|
|
||||||
moreDialog =
|
|
||||||
DialogParams(
|
|
||||||
fromLongClick = true,
|
fromLongClick = true,
|
||||||
title = person.name ?: "",
|
person = person,
|
||||||
items = items,
|
actions =
|
||||||
|
PersonContextActions(
|
||||||
|
navigateTo = viewModel::navigateTo,
|
||||||
|
onClickFavorite = viewModel::setFavorite,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onLongClickSimilar = { index, similar ->
|
onLongClickSimilar = { _, similar ->
|
||||||
val items =
|
showContextMenu =
|
||||||
buildMoreDialogItemsForHome(
|
ContextMenu.ForBaseItem(
|
||||||
context = context,
|
|
||||||
item = similar,
|
|
||||||
seriesId = null,
|
|
||||||
playbackPosition = similar.playbackPosition,
|
|
||||||
watched = similar.played,
|
|
||||||
favorite = similar.favorite,
|
|
||||||
canDelete = false,
|
|
||||||
actions = moreActions,
|
|
||||||
)
|
|
||||||
moreDialog =
|
|
||||||
DialogParams(
|
|
||||||
fromLongClick = true,
|
fromLongClick = true,
|
||||||
title = similar.title ?: "",
|
item = similar,
|
||||||
items = items,
|
chosenStreams = null,
|
||||||
|
showGoTo = true,
|
||||||
|
showStreamChoices = false,
|
||||||
|
canDelete = false,
|
||||||
|
canRemoveContinueWatching = false,
|
||||||
|
canRemoveNextUp = false,
|
||||||
|
actions = contextActions,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
trailerOnClick = {
|
trailerOnClick = {
|
||||||
|
|
@ -300,11 +243,19 @@ fun MovieDetails(
|
||||||
viewModel.navigateTo(item.destination)
|
viewModel.navigateTo(item.destination)
|
||||||
},
|
},
|
||||||
canDelete = state.canDelete,
|
canDelete = state.canDelete,
|
||||||
deleteOnClick = { showDeleteDialog = state.movie },
|
onConfirmDelete = { state.movie?.let { viewModel.deleteItem(it) } },
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
showContextMenu?.let { contextMenu ->
|
||||||
|
ContextMenuDialog(
|
||||||
|
onDismissRequest = { showContextMenu = null },
|
||||||
|
getMediaSource = viewModel.streamChoiceService::chooseSource,
|
||||||
|
contextMenu = contextMenu,
|
||||||
|
preferredSubtitleLanguage = preferredSubtitleLanguage,
|
||||||
|
)
|
||||||
|
}
|
||||||
overviewDialog?.let { info ->
|
overviewDialog?.let { info ->
|
||||||
ItemDetailsDialog(
|
ItemDetailsDialog(
|
||||||
info = info,
|
info = info,
|
||||||
|
|
@ -315,26 +266,6 @@ fun MovieDetails(
|
||||||
onDismissRequest = { overviewDialog = null },
|
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 ->
|
showPlaylistDialog.compose { itemId ->
|
||||||
PlaylistDialog(
|
PlaylistDialog(
|
||||||
title = stringResource(R.string.add_to_playlist),
|
title = stringResource(R.string.add_to_playlist),
|
||||||
|
|
@ -352,16 +283,6 @@ fun MovieDetails(
|
||||||
elevation = 3.dp,
|
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
|
private const val HEADER_ROW = 0
|
||||||
|
|
@ -390,7 +311,7 @@ fun MovieDetailsContent(
|
||||||
onClickExtra: (Int, ExtrasItem) -> Unit,
|
onClickExtra: (Int, ExtrasItem) -> Unit,
|
||||||
onClickDiscover: (Int, DiscoverItem) -> Unit,
|
onClickDiscover: (Int, DiscoverItem) -> Unit,
|
||||||
canDelete: Boolean,
|
canDelete: Boolean,
|
||||||
deleteOnClick: () -> Unit,
|
onConfirmDelete: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
@ -430,6 +351,7 @@ fun MovieDetailsContent(
|
||||||
.padding(top = HeaderUtils.topPadding, bottom = 16.dp),
|
.padding(top = HeaderUtils.topPadding, bottom = 16.dp),
|
||||||
)
|
)
|
||||||
ExpandablePlayButtons(
|
ExpandablePlayButtons(
|
||||||
|
title = movie.title ?: "",
|
||||||
resumePosition = resumePosition,
|
resumePosition = resumePosition,
|
||||||
watched = dto.userData?.played ?: false,
|
watched = dto.userData?.played ?: false,
|
||||||
favorite = dto.userData?.isFavorite ?: false,
|
favorite = dto.userData?.isFavorite ?: false,
|
||||||
|
|
@ -454,7 +376,7 @@ fun MovieDetailsContent(
|
||||||
trailerOnClick.invoke(it)
|
trailerOnClick.invoke(it)
|
||||||
},
|
},
|
||||||
canDelete = canDelete,
|
canDelete = canDelete,
|
||||||
deleteOnClick = deleteOnClick,
|
onConfirmDelete = onConfirmDelete,
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.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.SlimItemFields
|
||||||
import com.github.damontecres.wholphin.ui.cards.BannerCardWithTitle
|
import com.github.damontecres.wholphin.ui.cards.BannerCardWithTitle
|
||||||
import com.github.damontecres.wholphin.ui.cards.ItemRow
|
import com.github.damontecres.wholphin.ui.cards.ItemRow
|
||||||
import com.github.damontecres.wholphin.ui.components.ConfirmDeleteDialog
|
import com.github.damontecres.wholphin.ui.components.ContextMenu
|
||||||
import com.github.damontecres.wholphin.ui.components.DialogParams
|
import com.github.damontecres.wholphin.ui.components.ContextMenuDialog
|
||||||
import com.github.damontecres.wholphin.ui.components.DialogPopup
|
|
||||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||||
import com.github.damontecres.wholphin.ui.components.GenreText
|
import com.github.damontecres.wholphin.ui.components.GenreText
|
||||||
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
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.Optional
|
||||||
import com.github.damontecres.wholphin.ui.components.OverviewText
|
import com.github.damontecres.wholphin.ui.components.OverviewText
|
||||||
import com.github.damontecres.wholphin.ui.components.QuickDetails
|
import com.github.damontecres.wholphin.ui.components.QuickDetails
|
||||||
|
|
@ -308,22 +308,21 @@ fun AlbumDetailsPage(
|
||||||
val focusManager = LocalFocusManager.current
|
val focusManager = LocalFocusManager.current
|
||||||
var showPlaylistDialog by remember { mutableStateOf<Optional<UUID>>(Optional.absent()) }
|
var showPlaylistDialog by remember { mutableStateOf<Optional<UUID>>(Optional.absent()) }
|
||||||
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
||||||
var moreDialog by remember { mutableStateOf<DialogParams?>(null) }
|
var showContextMenu by remember { mutableStateOf<ContextMenu?>(null) }
|
||||||
var showDeleteDialog by remember { mutableStateOf<BaseItem?>(null) }
|
|
||||||
val moreDialogActions =
|
val moreDialogActions =
|
||||||
remember {
|
remember {
|
||||||
MusicMoreDialogActions(
|
MusicContextActions(
|
||||||
onNavigate = { viewModel.navigationManager.navigateTo(it) },
|
navigateTo = { viewModel.navigationManager.navigateTo(it) },
|
||||||
onClickPlay = { index, _ -> viewModel.play(false, index) },
|
onClickPlay = { index, _ -> viewModel.play(false, index) },
|
||||||
onClickPlayNext = { _, song -> viewModel.playNext(song) },
|
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) },
|
onClickFavorite = { itemId, favorite -> viewModel.setFavorite(itemId, favorite) },
|
||||||
onClickAddPlaylist = { itemId ->
|
onClickAddPlaylist = { itemId ->
|
||||||
playlistViewModel.loadPlaylists(MediaType.AUDIO)
|
playlistViewModel.loadPlaylists(MediaType.AUDIO)
|
||||||
showPlaylistDialog.makePresent(itemId)
|
showPlaylistDialog.makePresent(itemId)
|
||||||
},
|
},
|
||||||
onClickRemoveFromQueue = {},
|
onClickRemoveFromQueue = { _, _ -> },
|
||||||
onClickDelete = { showDeleteDialog = it },
|
onDeleteItem = viewModel::deleteItem,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
when (val loading = state.loading) {
|
when (val loading = state.loading) {
|
||||||
|
|
@ -402,23 +401,18 @@ fun AlbumDetailsPage(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClickMore = {
|
onClickMore = {
|
||||||
moreDialog =
|
showContextMenu =
|
||||||
DialogParams(
|
ContextMenu.ForMusic(
|
||||||
fromLongClick = false,
|
fromLongClick = false,
|
||||||
title = album.name + " (${album.data.productionYear ?: ""})",
|
item = album,
|
||||||
items =
|
index = 0,
|
||||||
buildMoreDialogForMusic(
|
canDelete =
|
||||||
context = context,
|
viewModel.canDelete(
|
||||||
actions = moreDialogActions,
|
album,
|
||||||
item = album,
|
preferences.appPreferences,
|
||||||
index = 0,
|
|
||||||
canRemove = false,
|
|
||||||
canDelete =
|
|
||||||
viewModel.canDelete(
|
|
||||||
album,
|
|
||||||
preferences.appPreferences,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
canRemoveFromQueue = false,
|
||||||
|
actions = moreDialogActions,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -459,45 +453,35 @@ fun AlbumDetailsPage(
|
||||||
},
|
},
|
||||||
onLongClick = {
|
onLongClick = {
|
||||||
if (song != null) {
|
if (song != null) {
|
||||||
moreDialog =
|
showContextMenu =
|
||||||
DialogParams(
|
ContextMenu.ForMusic(
|
||||||
fromLongClick = true,
|
fromLongClick = true,
|
||||||
title = song.name ?: "",
|
item = song,
|
||||||
items =
|
index = index,
|
||||||
buildMoreDialogForMusic(
|
canDelete =
|
||||||
context = context,
|
viewModel.canDelete(
|
||||||
actions = moreDialogActions,
|
song,
|
||||||
item = song,
|
preferences.appPreferences,
|
||||||
index = index,
|
|
||||||
canRemove = false,
|
|
||||||
canDelete =
|
|
||||||
viewModel.canDelete(
|
|
||||||
song,
|
|
||||||
preferences.appPreferences,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
canRemoveFromQueue = false,
|
||||||
|
actions = moreDialogActions,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onClickMore = {
|
onClickMore = {
|
||||||
if (song != null) {
|
if (song != null) {
|
||||||
moreDialog =
|
showContextMenu =
|
||||||
DialogParams(
|
ContextMenu.ForMusic(
|
||||||
fromLongClick = false,
|
fromLongClick = false,
|
||||||
title = song.name ?: "",
|
item = song,
|
||||||
items =
|
index = index,
|
||||||
buildMoreDialogForMusic(
|
canDelete =
|
||||||
context = context,
|
viewModel.canDelete(
|
||||||
actions = moreDialogActions,
|
song,
|
||||||
item = song,
|
preferences.appPreferences,
|
||||||
index = index,
|
|
||||||
canRemove = false,
|
|
||||||
canDelete =
|
|
||||||
viewModel.canDelete(
|
|
||||||
song,
|
|
||||||
preferences.appPreferences,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
canRemoveFromQueue = false,
|
||||||
|
actions = moreDialogActions,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -559,23 +543,18 @@ fun AlbumDetailsPage(
|
||||||
viewModel.navigationManager.navigateTo(item.destination())
|
viewModel.navigationManager.navigateTo(item.destination())
|
||||||
},
|
},
|
||||||
onLongClickItem = { index, item ->
|
onLongClickItem = { index, item ->
|
||||||
moreDialog =
|
showContextMenu =
|
||||||
DialogParams(
|
ContextMenu.ForMusic(
|
||||||
fromLongClick = true,
|
fromLongClick = true,
|
||||||
title = item.name ?: "",
|
item = item,
|
||||||
items =
|
index = index,
|
||||||
buildMoreDialogForMusic(
|
canDelete =
|
||||||
context = context,
|
viewModel.canDelete(
|
||||||
actions = moreDialogActions,
|
item,
|
||||||
item = item,
|
preferences.appPreferences,
|
||||||
index = index,
|
|
||||||
canRemove = false,
|
|
||||||
canDelete =
|
|
||||||
viewModel.canDelete(
|
|
||||||
item,
|
|
||||||
preferences.appPreferences,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
canRemoveFromQueue = false,
|
||||||
|
actions = moreDialogActions,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
cardContent = { index: Int, item: BaseItem?, mod: Modifier, onClick: () -> Unit, onLongClick: () -> Unit ->
|
cardContent = { index: Int, item: BaseItem?, mod: Modifier, onClick: () -> Unit, onLongClick: () -> Unit ->
|
||||||
|
|
@ -597,14 +576,12 @@ fun AlbumDetailsPage(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
moreDialog?.let { params ->
|
showContextMenu?.let { contextMenu ->
|
||||||
DialogPopup(
|
ContextMenuDialog(
|
||||||
showDialog = true,
|
onDismissRequest = { showContextMenu = null },
|
||||||
title = params.title,
|
getMediaSource = null,
|
||||||
dialogItems = params.items,
|
contextMenu = contextMenu,
|
||||||
onDismissRequest = { moreDialog = null },
|
preferredSubtitleLanguage = null,
|
||||||
dismissOnClick = true,
|
|
||||||
waitToLoad = params.fromLongClick,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
showPlaylistDialog.compose { itemId ->
|
showPlaylistDialog.compose { itemId ->
|
||||||
|
|
@ -624,17 +601,6 @@ fun AlbumDetailsPage(
|
||||||
elevation = 3.dp,
|
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
|
@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.SlimItemFields
|
||||||
import com.github.damontecres.wholphin.ui.cards.BannerCardWithTitle
|
import com.github.damontecres.wholphin.ui.cards.BannerCardWithTitle
|
||||||
import com.github.damontecres.wholphin.ui.cards.ItemRow
|
import com.github.damontecres.wholphin.ui.cards.ItemRow
|
||||||
import com.github.damontecres.wholphin.ui.components.ConfirmDeleteDialog
|
import com.github.damontecres.wholphin.ui.components.ContextMenu
|
||||||
import com.github.damontecres.wholphin.ui.components.DialogParams
|
import com.github.damontecres.wholphin.ui.components.ContextMenuDialog
|
||||||
import com.github.damontecres.wholphin.ui.components.DialogPopup
|
|
||||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||||
import com.github.damontecres.wholphin.ui.components.GenreText
|
import com.github.damontecres.wholphin.ui.components.GenreText
|
||||||
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
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.Optional
|
||||||
import com.github.damontecres.wholphin.ui.components.OverviewText
|
import com.github.damontecres.wholphin.ui.components.OverviewText
|
||||||
import com.github.damontecres.wholphin.ui.components.QuickDetails
|
import com.github.damontecres.wholphin.ui.components.QuickDetails
|
||||||
|
|
@ -312,22 +312,21 @@ fun ArtistDetailsPage(
|
||||||
|
|
||||||
var showPlaylistDialog by remember { mutableStateOf<Optional<UUID>>(Optional.absent()) }
|
var showPlaylistDialog by remember { mutableStateOf<Optional<UUID>>(Optional.absent()) }
|
||||||
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
||||||
var moreDialog by remember { mutableStateOf<DialogParams?>(null) }
|
var showContextMenu by remember { mutableStateOf<ContextMenu?>(null) }
|
||||||
var showDeleteDialog by remember { mutableStateOf<BaseItem?>(null) }
|
|
||||||
val moreDialogActions =
|
val moreDialogActions =
|
||||||
remember {
|
remember {
|
||||||
MusicMoreDialogActions(
|
MusicContextActions(
|
||||||
onNavigate = { viewModel.navigationManager.navigateTo(it) },
|
navigateTo = { viewModel.navigationManager.navigateTo(it) },
|
||||||
onClickPlay = { index, item -> viewModel.play(item) },
|
onClickPlay = { index, item -> viewModel.play(item) },
|
||||||
onClickPlayNext = { _, item -> viewModel.playNext(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) },
|
onClickFavorite = { itemId, favorite -> viewModel.setFavorite(itemId, favorite) },
|
||||||
onClickAddPlaylist = { itemId ->
|
onClickAddPlaylist = { itemId ->
|
||||||
playlistViewModel.loadPlaylists(MediaType.AUDIO)
|
playlistViewModel.loadPlaylists(MediaType.AUDIO)
|
||||||
showPlaylistDialog.makePresent(itemId)
|
showPlaylistDialog.makePresent(itemId)
|
||||||
},
|
},
|
||||||
onClickRemoveFromQueue = {},
|
onClickRemoveFromQueue = { _, _ -> },
|
||||||
onClickDelete = { showDeleteDialog = it },
|
onDeleteItem = viewModel::deleteItem,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -393,23 +392,18 @@ fun ArtistDetailsPage(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClickMore = {
|
onClickMore = {
|
||||||
moreDialog =
|
showContextMenu =
|
||||||
DialogParams(
|
ContextMenu.ForMusic(
|
||||||
fromLongClick = false,
|
fromLongClick = false,
|
||||||
title = artist.name ?: "",
|
item = artist,
|
||||||
items =
|
index = 0,
|
||||||
buildMoreDialogForMusic(
|
canDelete =
|
||||||
context = context,
|
viewModel.canDelete(
|
||||||
actions = moreDialogActions,
|
artist,
|
||||||
item = artist,
|
preferences.appPreferences,
|
||||||
index = 0,
|
|
||||||
canRemove = false,
|
|
||||||
canDelete =
|
|
||||||
viewModel.canDelete(
|
|
||||||
artist,
|
|
||||||
preferences.appPreferences,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
canRemoveFromQueue = false,
|
||||||
|
actions = moreDialogActions,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -451,45 +445,35 @@ fun ArtistDetailsPage(
|
||||||
},
|
},
|
||||||
onLongClick = {
|
onLongClick = {
|
||||||
if (song != null) {
|
if (song != null) {
|
||||||
moreDialog =
|
showContextMenu =
|
||||||
DialogParams(
|
ContextMenu.ForMusic(
|
||||||
fromLongClick = true,
|
fromLongClick = true,
|
||||||
title = song.name ?: "",
|
item = song,
|
||||||
items =
|
index = index,
|
||||||
buildMoreDialogForMusic(
|
canDelete =
|
||||||
context = context,
|
viewModel.canDelete(
|
||||||
actions = moreDialogActions,
|
song,
|
||||||
item = song,
|
preferences.appPreferences,
|
||||||
index = index,
|
|
||||||
canRemove = false,
|
|
||||||
canDelete =
|
|
||||||
viewModel.canDelete(
|
|
||||||
song,
|
|
||||||
preferences.appPreferences,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
canRemoveFromQueue = false,
|
||||||
|
actions = moreDialogActions,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onClickMore = {
|
onClickMore = {
|
||||||
if (song != null) {
|
if (song != null) {
|
||||||
moreDialog =
|
showContextMenu =
|
||||||
DialogParams(
|
ContextMenu.ForMusic(
|
||||||
fromLongClick = false,
|
fromLongClick = false,
|
||||||
title = song.name ?: "",
|
item = song,
|
||||||
items =
|
index = index,
|
||||||
buildMoreDialogForMusic(
|
canDelete =
|
||||||
context = context,
|
viewModel.canDelete(
|
||||||
actions = moreDialogActions,
|
song,
|
||||||
item = song,
|
preferences.appPreferences,
|
||||||
index = index,
|
|
||||||
canRemove = false,
|
|
||||||
canDelete =
|
|
||||||
viewModel.canDelete(
|
|
||||||
song,
|
|
||||||
preferences.appPreferences,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
canRemoveFromQueue = false,
|
||||||
|
actions = moreDialogActions,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -516,23 +500,18 @@ fun ArtistDetailsPage(
|
||||||
viewModel.navigationManager.navigateTo(album.destination())
|
viewModel.navigationManager.navigateTo(album.destination())
|
||||||
},
|
},
|
||||||
onLongClickItem = { index, album ->
|
onLongClickItem = { index, album ->
|
||||||
moreDialog =
|
showContextMenu =
|
||||||
DialogParams(
|
ContextMenu.ForMusic(
|
||||||
fromLongClick = true,
|
fromLongClick = true,
|
||||||
title = album.name ?: "",
|
item = album,
|
||||||
items =
|
index = index,
|
||||||
buildMoreDialogForMusic(
|
canDelete =
|
||||||
context = context,
|
viewModel.canDelete(
|
||||||
actions = moreDialogActions,
|
album,
|
||||||
item = album,
|
preferences.appPreferences,
|
||||||
index = index,
|
|
||||||
canRemove = false,
|
|
||||||
canDelete =
|
|
||||||
viewModel.canDelete(
|
|
||||||
album,
|
|
||||||
preferences.appPreferences,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
canRemoveFromQueue = false,
|
||||||
|
actions = moreDialogActions,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
cardContent = { index: Int, album: BaseItem?, mod: Modifier, onClick: () -> Unit, onLongClick: () -> Unit ->
|
cardContent = { index: Int, album: BaseItem?, mod: Modifier, onClick: () -> Unit, onLongClick: () -> Unit ->
|
||||||
|
|
@ -588,23 +567,18 @@ fun ArtistDetailsPage(
|
||||||
viewModel.navigationManager.navigateTo(item.destination())
|
viewModel.navigationManager.navigateTo(item.destination())
|
||||||
},
|
},
|
||||||
onLongClickItem = { index, item ->
|
onLongClickItem = { index, item ->
|
||||||
moreDialog =
|
showContextMenu =
|
||||||
DialogParams(
|
ContextMenu.ForMusic(
|
||||||
fromLongClick = true,
|
fromLongClick = true,
|
||||||
title = item.name ?: "",
|
item = item,
|
||||||
items =
|
index = index,
|
||||||
buildMoreDialogForMusic(
|
canDelete =
|
||||||
context = context,
|
viewModel.canDelete(
|
||||||
actions = moreDialogActions,
|
item,
|
||||||
item = item,
|
preferences.appPreferences,
|
||||||
index = index,
|
|
||||||
canRemove = false,
|
|
||||||
canDelete =
|
|
||||||
viewModel.canDelete(
|
|
||||||
item,
|
|
||||||
preferences.appPreferences,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
canRemoveFromQueue = false,
|
||||||
|
actions = moreDialogActions,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
cardContent = { index: Int, item: BaseItem?, mod: Modifier, onClick: () -> Unit, onLongClick: () -> Unit ->
|
cardContent = { index: Int, item: BaseItem?, mod: Modifier, onClick: () -> Unit, onLongClick: () -> Unit ->
|
||||||
|
|
@ -626,14 +600,12 @@ fun ArtistDetailsPage(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
moreDialog?.let { params ->
|
showContextMenu?.let { contextMenu ->
|
||||||
DialogPopup(
|
ContextMenuDialog(
|
||||||
showDialog = true,
|
onDismissRequest = { showContextMenu = null },
|
||||||
title = params.title,
|
getMediaSource = null,
|
||||||
dialogItems = params.items,
|
contextMenu = contextMenu,
|
||||||
onDismissRequest = { moreDialog = null },
|
preferredSubtitleLanguage = null,
|
||||||
dismissOnClick = true,
|
|
||||||
waitToLoad = params.fromLongClick,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
showPlaylistDialog.compose { itemId ->
|
showPlaylistDialog.compose { itemId ->
|
||||||
|
|
@ -653,17 +625,6 @@ fun ArtistDetailsPage(
|
||||||
elevation = 3.dp,
|
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
|
@Composable
|
||||||
|
|
|
||||||
|
|
@ -60,9 +60,10 @@ import com.github.damontecres.wholphin.preferences.BackdropStyle
|
||||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||||
import com.github.damontecres.wholphin.services.rememberQueue
|
import com.github.damontecres.wholphin.services.rememberQueue
|
||||||
import com.github.damontecres.wholphin.ui.components.BasicDialog
|
import com.github.damontecres.wholphin.ui.components.BasicDialog
|
||||||
import com.github.damontecres.wholphin.ui.components.DialogParams
|
import com.github.damontecres.wholphin.ui.components.ContextMenu
|
||||||
import com.github.damontecres.wholphin.ui.components.DialogPopup
|
import com.github.damontecres.wholphin.ui.components.ContextMenuDialog
|
||||||
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
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.findActivity
|
||||||
import com.github.damontecres.wholphin.ui.nav.Backdrop
|
import com.github.damontecres.wholphin.ui.nav.Backdrop
|
||||||
import com.github.damontecres.wholphin.ui.playback.PlaybackKeyHandler
|
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 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) }
|
var lyricsHaveFocus by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
|
@ -209,6 +201,7 @@ fun NowPlayingPage(
|
||||||
remember {
|
remember {
|
||||||
shrinkHorizontally(shrinkTowards = Alignment.Start) + fadeOut()
|
shrinkHorizontally(shrinkTowards = Alignment.Start) + fadeOut()
|
||||||
}
|
}
|
||||||
|
//noinspection UnnecessaryFullyQualifiedName
|
||||||
androidx.compose.animation.AnimatedVisibility(
|
androidx.compose.animation.AnimatedVisibility(
|
||||||
visible = musicPrefs.showAlbumArt,
|
visible = musicPrefs.showAlbumArt,
|
||||||
enter = enter,
|
enter = enter,
|
||||||
|
|
@ -250,6 +243,7 @@ fun NowPlayingPage(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//noinspection UnnecessaryFullyQualifiedName
|
||||||
androidx.compose.animation.AnimatedVisibility(
|
androidx.compose.animation.AnimatedVisibility(
|
||||||
visible = musicPrefs.showVisualizer,
|
visible = musicPrefs.showVisualizer,
|
||||||
enter = enter,
|
enter = enter,
|
||||||
|
|
@ -308,17 +302,21 @@ fun NowPlayingPage(
|
||||||
val showContextForItem =
|
val showContextForItem =
|
||||||
remember {
|
remember {
|
||||||
{ fromLongClick: Boolean, index: Int, song: AudioItem ->
|
{ fromLongClick: Boolean, index: Int, song: AudioItem ->
|
||||||
itemMoreDialog =
|
showContextMenu =
|
||||||
DialogParams(
|
ContextMenu.ForQueue(
|
||||||
title = song.title ?: "",
|
|
||||||
fromLongClick = fromLongClick,
|
fromLongClick = fromLongClick,
|
||||||
items =
|
item = song,
|
||||||
buildMoreDialogForMusicQueue(
|
index = index,
|
||||||
context = context,
|
actions =
|
||||||
actions = actions,
|
QueueContextActions(
|
||||||
item = song,
|
onNavigate = { viewModel.navigationManager.navigateTo(it) },
|
||||||
index = index,
|
onClickPlay = { index, _ -> viewModel.play(index) },
|
||||||
canRemove = true,
|
onClickPlayNext = { index, _ -> viewModel.playNext(index) },
|
||||||
|
onClickRemoveFromQueue = { index, _ ->
|
||||||
|
viewModel.removeFromQueue(
|
||||||
|
index,
|
||||||
|
)
|
||||||
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -368,14 +366,12 @@ fun NowPlayingPage(
|
||||||
LoadingPage(focusEnabled = false)
|
LoadingPage(focusEnabled = false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
itemMoreDialog?.let { params ->
|
showContextMenu?.let { contextMenu ->
|
||||||
DialogPopup(
|
ContextMenuDialog(
|
||||||
showDialog = true,
|
onDismissRequest = { showContextMenu = null },
|
||||||
title = params.title,
|
getMediaSource = null,
|
||||||
dialogItems = params.items,
|
contextMenu = contextMenu,
|
||||||
onDismissRequest = { itemMoreDialog = null },
|
preferredSubtitleLanguage = null,
|
||||||
dismissOnClick = true,
|
|
||||||
waitToLoad = params.fromLongClick,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (showViewOptionsDialog) {
|
if (showViewOptionsDialog) {
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,8 @@
|
||||||
package com.github.damontecres.wholphin.ui.detail.music
|
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.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
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.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.ApiRequestPager
|
||||||
import com.github.damontecres.wholphin.util.GetItemsRequestHandler
|
import com.github.damontecres.wholphin.util.GetItemsRequestHandler
|
||||||
import org.jellyfin.sdk.api.client.ApiClient
|
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 org.jellyfin.sdk.model.api.request.GetItemsRequest
|
||||||
import java.util.UUID
|
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(
|
suspend fun ViewModel.getPagerForAlbum(
|
||||||
api: ApiClient,
|
api: ApiClient,
|
||||||
albumId: UUID,
|
albumId: UUID,
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ fun FocusedEpisodeFooter(
|
||||||
watchOnClick: () -> Unit,
|
watchOnClick: () -> Unit,
|
||||||
favoriteOnClick: () -> Unit,
|
favoriteOnClick: () -> Unit,
|
||||||
canDelete: Boolean,
|
canDelete: Boolean,
|
||||||
deleteOnClick: () -> Unit,
|
onConfirmDelete: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
buttonOnFocusChanged: (FocusState) -> Unit = {},
|
buttonOnFocusChanged: (FocusState) -> Unit = {},
|
||||||
) {
|
) {
|
||||||
|
|
@ -39,6 +39,7 @@ fun FocusedEpisodeFooter(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
) {
|
) {
|
||||||
ExpandablePlayButtons(
|
ExpandablePlayButtons(
|
||||||
|
title = ep.title ?: "",
|
||||||
resumePosition = resumePosition,
|
resumePosition = resumePosition,
|
||||||
watched = dto.userData?.played ?: false,
|
watched = dto.userData?.played ?: false,
|
||||||
favorite = dto.userData?.isFavorite ?: false,
|
favorite = dto.userData?.isFavorite ?: false,
|
||||||
|
|
@ -50,7 +51,7 @@ fun FocusedEpisodeFooter(
|
||||||
trailers = null,
|
trailers = null,
|
||||||
trailerOnClick = {},
|
trailerOnClick = {},
|
||||||
canDelete = canDelete,
|
canDelete = canDelete,
|
||||||
deleteOnClick = deleteOnClick,
|
onConfirmDelete = onConfirmDelete,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,6 @@ import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
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.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.focus.focusRestorer
|
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.ItemRow
|
||||||
import com.github.damontecres.wholphin.ui.cards.PersonRow
|
import com.github.damontecres.wholphin.ui.cards.PersonRow
|
||||||
import com.github.damontecres.wholphin.ui.cards.SeasonCard
|
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.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.DeleteButton
|
||||||
import com.github.damontecres.wholphin.ui.components.DialogItem
|
import com.github.damontecres.wholphin.ui.components.DialogItem
|
||||||
import com.github.damontecres.wholphin.ui.components.DialogParams
|
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.ErrorMessage
|
||||||
import com.github.damontecres.wholphin.ui.components.ExpandableFaButton
|
import com.github.damontecres.wholphin.ui.components.ExpandableFaButton
|
||||||
import com.github.damontecres.wholphin.ui.components.ExpandablePlayButton
|
import com.github.damontecres.wholphin.ui.components.ExpandablePlayButton
|
||||||
|
|
@ -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.LoadingPage
|
||||||
import com.github.damontecres.wholphin.ui.components.Optional
|
import com.github.damontecres.wholphin.ui.components.Optional
|
||||||
import com.github.damontecres.wholphin.ui.components.OverviewText
|
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.QuickDetails
|
||||||
import com.github.damontecres.wholphin.ui.components.TitleOrLogo
|
import com.github.damontecres.wholphin.ui.components.TitleOrLogo
|
||||||
import com.github.damontecres.wholphin.ui.components.TrailerButton
|
import com.github.damontecres.wholphin.ui.components.TrailerButton
|
||||||
import com.github.damontecres.wholphin.ui.data.AddPlaylistViewModel
|
import com.github.damontecres.wholphin.ui.data.AddPlaylistViewModel
|
||||||
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialog
|
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialog
|
||||||
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialogInfo
|
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.PlaylistDialog
|
||||||
import com.github.damontecres.wholphin.ui.detail.PlaylistLoadingState
|
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.DiscoverRow
|
||||||
import com.github.damontecres.wholphin.ui.discover.DiscoverRowData
|
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.letNotEmpty
|
||||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||||
import com.github.damontecres.wholphin.ui.rememberInt
|
import com.github.damontecres.wholphin.ui.rememberInt
|
||||||
|
|
@ -95,6 +92,7 @@ import com.github.damontecres.wholphin.util.LoadingState
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||||
import org.jellyfin.sdk.model.api.MediaType
|
import org.jellyfin.sdk.model.api.MediaType
|
||||||
|
import org.jellyfin.sdk.model.serializer.toUUID
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
|
|
||||||
|
|
@ -128,10 +126,50 @@ fun SeriesDetails(
|
||||||
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
||||||
|
|
||||||
var overviewDialog by remember { mutableStateOf<ItemDetailsDialogInfo?>(null) }
|
var overviewDialog by remember { mutableStateOf<ItemDetailsDialogInfo?>(null) }
|
||||||
|
var showContextMenu by remember { mutableStateOf<ContextMenu?>(null) }
|
||||||
|
|
||||||
var showWatchConfirmation by remember { mutableStateOf(false) }
|
var showWatchConfirmation by remember { mutableStateOf(false) }
|
||||||
var seasonDialog by remember { mutableStateOf<DialogParams?>(null) }
|
|
||||||
var showPlaylistDialog by remember { mutableStateOf<Optional<UUID>>(Optional.absent()) }
|
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) {
|
LifecycleResumeEffect(destination.itemId) {
|
||||||
viewModel.refresh()
|
viewModel.refresh()
|
||||||
|
|
@ -185,29 +223,18 @@ fun SeriesDetails(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onLongClickItem = { index, season ->
|
onLongClickItem = { index, season ->
|
||||||
scope.launchDefault {
|
showContextMenu =
|
||||||
seasonDialog =
|
ContextMenu.ForBaseItem(
|
||||||
buildDialogForSeason(
|
fromLongClick = true,
|
||||||
context = context,
|
item = season,
|
||||||
s = season,
|
chosenStreams = null,
|
||||||
canDelete = viewModel.canDelete(season),
|
showGoTo = true,
|
||||||
onClickItem = { viewModel.navigateTo(it.destination()) },
|
showStreamChoices = false,
|
||||||
markPlayed = { played ->
|
canDelete = viewModel.canDelete(season, preferences.appPreferences),
|
||||||
viewModel.setSeasonWatched(season.id, played)
|
canRemoveContinueWatching = false,
|
||||||
},
|
canRemoveNextUp = false,
|
||||||
onClickPlay = { shuffle ->
|
actions = contextActions,
|
||||||
viewModel.navigateTo(
|
)
|
||||||
Destination.PlaybackList(
|
|
||||||
itemId = season.id,
|
|
||||||
shuffle = shuffle,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
onClickDelete = {
|
|
||||||
showDeleteDialog = it
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
overviewOnClick = {
|
overviewOnClick = {
|
||||||
overviewDialog = ItemDetailsDialogInfo(item)
|
overviewDialog = ItemDetailsDialogInfo(item)
|
||||||
|
|
@ -245,24 +272,10 @@ fun SeriesDetails(
|
||||||
onClickDiscover = { index, item ->
|
onClickDiscover = { index, item ->
|
||||||
viewModel.navigateTo(item.destination)
|
viewModel.navigateTo(item.destination)
|
||||||
},
|
},
|
||||||
moreActions =
|
onShowContextMenu = {
|
||||||
MoreDialogActions(
|
showContextMenu = it
|
||||||
navigateTo = { viewModel.navigateTo(it) },
|
},
|
||||||
onClickWatch = { itemId, played ->
|
actions = contextActions,
|
||||||
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
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
if (showWatchConfirmation) {
|
if (showWatchConfirmation) {
|
||||||
ConfirmDialog(
|
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 ->
|
overviewDialog?.let { info ->
|
||||||
ItemDetailsDialog(
|
ItemDetailsDialog(
|
||||||
info = info,
|
info = info,
|
||||||
|
|
@ -288,15 +309,6 @@ fun SeriesDetails(
|
||||||
onDismissRequest = { overviewDialog = null },
|
onDismissRequest = { overviewDialog = null },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
seasonDialog?.let { params ->
|
|
||||||
DialogPopup(
|
|
||||||
showDialog = true,
|
|
||||||
title = params.title,
|
|
||||||
dialogItems = params.items,
|
|
||||||
waitToLoad = params.fromLongClick,
|
|
||||||
onDismissRequest = { seasonDialog = null },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
showPlaylistDialog.compose { itemId ->
|
showPlaylistDialog.compose { itemId ->
|
||||||
PlaylistDialog(
|
PlaylistDialog(
|
||||||
title = stringResource(R.string.add_to_playlist),
|
title = stringResource(R.string.add_to_playlist),
|
||||||
|
|
@ -314,19 +326,6 @@ fun SeriesDetails(
|
||||||
elevation = 3.dp,
|
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
|
private const val HEADER_ROW = 0
|
||||||
|
|
@ -359,7 +358,8 @@ fun SeriesDetailsContent(
|
||||||
favoriteOnClick: () -> Unit,
|
favoriteOnClick: () -> Unit,
|
||||||
trailerOnClick: (Trailer) -> Unit,
|
trailerOnClick: (Trailer) -> Unit,
|
||||||
onClickExtra: (Int, ExtrasItem) -> Unit,
|
onClickExtra: (Int, ExtrasItem) -> Unit,
|
||||||
moreActions: MoreDialogActions,
|
onShowContextMenu: (ContextMenu) -> Unit,
|
||||||
|
actions: ContextMenuActions,
|
||||||
onClickDiscover: (Int, DiscoverItem) -> Unit,
|
onClickDiscover: (Int, DiscoverItem) -> Unit,
|
||||||
discoverSeries: DiscoverItem?,
|
discoverSeries: DiscoverItem?,
|
||||||
onClickDiscoverSeries: () -> Unit,
|
onClickDiscoverSeries: () -> Unit,
|
||||||
|
|
@ -373,7 +373,6 @@ fun SeriesDetailsContent(
|
||||||
val focusRequesters = remember { List(DISCOVER_ROW + 1) { FocusRequester() } }
|
val focusRequesters = remember { List(DISCOVER_ROW + 1) { FocusRequester() } }
|
||||||
val playFocusRequester = remember { FocusRequester() }
|
val playFocusRequester = remember { FocusRequester() }
|
||||||
RequestOrRestoreFocus(focusRequesters.getOrNull(position))
|
RequestOrRestoreFocus(focusRequesters.getOrNull(position))
|
||||||
var moreDialog by remember { mutableStateOf<DialogParams?>(null) }
|
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
|
@ -486,9 +485,10 @@ fun SeriesDetailsContent(
|
||||||
)
|
)
|
||||||
if (canDelete) {
|
if (canDelete) {
|
||||||
DeleteButton(
|
DeleteButton(
|
||||||
onClick = {
|
title = series.title ?: "",
|
||||||
|
onConfirmDelete = {
|
||||||
position = HEADER_ROW
|
position = HEADER_ROW
|
||||||
moreActions.onClickDelete.invoke(series)
|
actions.onDeleteItem.invoke(series)
|
||||||
},
|
},
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
|
|
@ -561,18 +561,17 @@ fun SeriesDetailsContent(
|
||||||
},
|
},
|
||||||
onLongClick = { index, person ->
|
onLongClick = { index, person ->
|
||||||
position = PEOPLE_ROW
|
position = PEOPLE_ROW
|
||||||
val items =
|
onShowContextMenu.invoke(
|
||||||
buildMoreDialogItemsForPerson(
|
ContextMenu.ForPerson(
|
||||||
context = context,
|
|
||||||
person = person,
|
|
||||||
actions = moreActions,
|
|
||||||
)
|
|
||||||
moreDialog =
|
|
||||||
DialogParams(
|
|
||||||
fromLongClick = true,
|
fromLongClick = true,
|
||||||
title = person.name ?: "",
|
person = person,
|
||||||
items = items,
|
actions =
|
||||||
)
|
PersonContextActions(
|
||||||
|
navigateTo = actions.navigateTo,
|
||||||
|
onClickFavorite = actions.onClickFavorite,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
},
|
},
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
|
|
@ -608,23 +607,19 @@ fun SeriesDetailsContent(
|
||||||
},
|
},
|
||||||
onLongClickItem = { index, item ->
|
onLongClickItem = { index, item ->
|
||||||
position = SIMILAR_ROW
|
position = SIMILAR_ROW
|
||||||
val items =
|
onShowContextMenu.invoke(
|
||||||
buildMoreDialogItemsForHome(
|
ContextMenu.ForBaseItem(
|
||||||
context = context,
|
|
||||||
item = item,
|
|
||||||
seriesId = null,
|
|
||||||
playbackPosition = item.playbackPosition,
|
|
||||||
watched = item.played,
|
|
||||||
favorite = item.favorite,
|
|
||||||
actions = moreActions,
|
|
||||||
canDelete = false,
|
|
||||||
)
|
|
||||||
moreDialog =
|
|
||||||
DialogParams(
|
|
||||||
fromLongClick = true,
|
fromLongClick = true,
|
||||||
title = item.name ?: "",
|
item = item,
|
||||||
items = items,
|
chosenStreams = null,
|
||||||
)
|
showGoTo = true,
|
||||||
|
showStreamChoices = false,
|
||||||
|
canDelete = false,
|
||||||
|
canRemoveContinueWatching = false,
|
||||||
|
canRemoveNextUp = false,
|
||||||
|
actions = actions,
|
||||||
|
),
|
||||||
|
)
|
||||||
},
|
},
|
||||||
cardContent = { index, item, mod, onClick, onLongClick ->
|
cardContent = { index, item, mod, onClick, onLongClick ->
|
||||||
SeasonCard(
|
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
|
@Composable
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
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.compose.LifecycleResumeEffect
|
||||||
import androidx.lifecycle.map
|
import androidx.lifecycle.map
|
||||||
import com.github.damontecres.wholphin.R
|
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.preferences.UserPreferences
|
||||||
import com.github.damontecres.wholphin.ui.RequestOrRestoreFocus
|
import com.github.damontecres.wholphin.ui.RequestOrRestoreFocus
|
||||||
import com.github.damontecres.wholphin.ui.components.ConfirmDeleteDialog
|
import com.github.damontecres.wholphin.ui.components.ContextMenu
|
||||||
import com.github.damontecres.wholphin.ui.components.DialogParams
|
import com.github.damontecres.wholphin.ui.components.ContextMenuActions
|
||||||
import com.github.damontecres.wholphin.ui.components.DialogPopup
|
import com.github.damontecres.wholphin.ui.components.ContextMenuDialog
|
||||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||||
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
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.AddPlaylistViewModel
|
||||||
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialog
|
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialog
|
||||||
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialogInfo
|
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.PlaylistDialog
|
||||||
import com.github.damontecres.wholphin.ui.detail.PlaylistLoadingState
|
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.nav.Destination
|
||||||
import com.github.damontecres.wholphin.ui.rememberInt
|
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 com.github.damontecres.wholphin.util.LoadingState
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.UseSerializers
|
import kotlinx.serialization.UseSerializers
|
||||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
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.MediaType
|
||||||
import org.jellyfin.sdk.model.api.PersonKind
|
import org.jellyfin.sdk.model.api.PersonKind
|
||||||
import org.jellyfin.sdk.model.extensions.ticks
|
import org.jellyfin.sdk.model.extensions.ticks
|
||||||
import org.jellyfin.sdk.model.serializer.UUIDSerializer
|
import org.jellyfin.sdk.model.serializer.UUIDSerializer
|
||||||
import org.jellyfin.sdk.model.serializer.toUUID
|
import org.jellyfin.sdk.model.serializer.toUUID
|
||||||
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
|
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
|
|
||||||
|
|
@ -110,6 +100,7 @@ fun SeriesOverview(
|
||||||
val episodeList = (episodes as? EpisodeList.Success)?.episodes
|
val episodeList = (episodes as? EpisodeList.Success)?.episodes
|
||||||
|
|
||||||
val position by viewModel.position.collectAsState(SeriesOverviewPosition(0, 0))
|
val position by viewModel.position.collectAsState(SeriesOverviewPosition(0, 0))
|
||||||
|
val currentPosition by rememberUpdatedState(position)
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
if (seasons.isNotEmpty()) {
|
if (seasons.isNotEmpty()) {
|
||||||
seasons.getOrNull(position.seasonTabIndex)?.let {
|
seasons.getOrNull(position.seasonTabIndex)?.let {
|
||||||
|
|
@ -119,13 +110,54 @@ fun SeriesOverview(
|
||||||
}
|
}
|
||||||
|
|
||||||
var overviewDialog by remember { mutableStateOf<ItemDetailsDialogInfo?>(null) }
|
var overviewDialog by remember { mutableStateOf<ItemDetailsDialogInfo?>(null) }
|
||||||
var moreDialog by remember { mutableStateOf<DialogParams?>(null) }
|
var showContextMenu by remember { mutableStateOf<ContextMenu?>(null) }
|
||||||
var chooseVersion by remember { mutableStateOf<DialogParams?>(null) }
|
|
||||||
var showPlaylistDialog by remember { mutableStateOf<UUID?>(null) }
|
var showPlaylistDialog by remember { mutableStateOf<UUID?>(null) }
|
||||||
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
||||||
|
|
||||||
var rowFocused by rememberInt()
|
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) {
|
LaunchedEffect(position, episodes) {
|
||||||
val focusedEpisode =
|
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(
|
SeriesOverviewContent(
|
||||||
preferences = preferences,
|
preferences = preferences,
|
||||||
series = series,
|
series = series,
|
||||||
|
|
@ -319,9 +254,18 @@ fun SeriesOverview(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onLongClick = { ep ->
|
onLongClick = { ep ->
|
||||||
scope.launchDefault {
|
showContextMenu =
|
||||||
moreDialog = buildMoreForEpisode(ep, chosenStreams, true)
|
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 ->
|
playOnClick = { resume ->
|
||||||
rowFocused = EPISODE_ROW
|
rowFocused = EPISODE_ROW
|
||||||
|
|
@ -349,9 +293,18 @@ fun SeriesOverview(
|
||||||
},
|
},
|
||||||
moreOnClick = {
|
moreOnClick = {
|
||||||
episodeList?.getOrNull(position.episodeRowIndex)?.let { ep ->
|
episodeList?.getOrNull(position.episodeRowIndex)?.let { ep ->
|
||||||
scope.launchDefault {
|
showContextMenu =
|
||||||
moreDialog = buildMoreForEpisode(ep, chosenStreams, false)
|
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 = {
|
overviewOnClick = {
|
||||||
|
|
@ -374,13 +327,20 @@ fun SeriesOverview(
|
||||||
viewModel.navigateTo(extra.destination)
|
viewModel.navigateTo(extra.destination)
|
||||||
},
|
},
|
||||||
canDelete = { viewModel.canDelete(it, preferences.appPreferences) },
|
canDelete = { viewModel.canDelete(it, preferences.appPreferences) },
|
||||||
deleteOnClick = { showDeleteDialog = it },
|
onConfirmDelete = viewModel::deleteItem,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
showContextMenu?.let { contextMenu ->
|
||||||
|
ContextMenuDialog(
|
||||||
|
onDismissRequest = { showContextMenu = null },
|
||||||
|
getMediaSource = viewModel.streamChoiceService::chooseSource,
|
||||||
|
contextMenu = contextMenu,
|
||||||
|
preferredSubtitleLanguage = preferredSubtitleLanguage,
|
||||||
|
)
|
||||||
|
}
|
||||||
overviewDialog?.let { info ->
|
overviewDialog?.let { info ->
|
||||||
ItemDetailsDialog(
|
ItemDetailsDialog(
|
||||||
info = info,
|
info = info,
|
||||||
|
|
@ -391,26 +351,6 @@ fun SeriesOverview(
|
||||||
onDismissRequest = { overviewDialog = null },
|
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 ->
|
showPlaylistDialog?.let { itemId ->
|
||||||
PlaylistDialog(
|
PlaylistDialog(
|
||||||
title = stringResource(R.string.add_to_playlist),
|
title = stringResource(R.string.add_to_playlist),
|
||||||
|
|
@ -428,17 +368,6 @@ fun SeriesOverview(
|
||||||
elevation = 3.dp,
|
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
|
private const val EPISODE_ROW = 0
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@ fun SeriesOverviewContent(
|
||||||
overviewOnClick: () -> Unit,
|
overviewOnClick: () -> Unit,
|
||||||
personOnClick: (Person) -> Unit,
|
personOnClick: (Person) -> Unit,
|
||||||
canDelete: (BaseItem) -> Boolean,
|
canDelete: (BaseItem) -> Boolean,
|
||||||
deleteOnClick: (BaseItem) -> Unit,
|
onConfirmDelete: (BaseItem) -> Unit,
|
||||||
onClickExtra: (Int, ExtrasItem) -> Unit,
|
onClickExtra: (Int, ExtrasItem) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
|
|
@ -322,7 +322,7 @@ fun SeriesOverviewContent(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
canDelete = canDelete.invoke(ep),
|
canDelete = canDelete.invoke(ep),
|
||||||
deleteOnClick = { deleteOnClick.invoke(ep) },
|
onConfirmDelete = { onConfirmDelete.invoke(ep) },
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.padding(top = 4.dp)
|
.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.focusRequester
|
||||||
import androidx.compose.ui.focus.focusRestorer
|
import androidx.compose.ui.focus.focusRestorer
|
||||||
import androidx.compose.ui.focus.onFocusChanged
|
import androidx.compose.ui.focus.onFocusChanged
|
||||||
|
import androidx.compose.ui.input.key.KeyEvent
|
||||||
import androidx.compose.ui.input.key.onKeyEvent
|
import androidx.compose.ui.input.key.onKeyEvent
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
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.ItemRow
|
||||||
import com.github.damontecres.wholphin.ui.cards.StudioCard
|
import com.github.damontecres.wholphin.ui.cards.StudioCard
|
||||||
import com.github.damontecres.wholphin.ui.components.CircularProgress
|
import com.github.damontecres.wholphin.ui.components.CircularProgress
|
||||||
import com.github.damontecres.wholphin.ui.components.ConfirmDeleteDialog
|
import com.github.damontecres.wholphin.ui.components.ContextMenu
|
||||||
import com.github.damontecres.wholphin.ui.components.DialogParams
|
import com.github.damontecres.wholphin.ui.components.ContextMenuActions
|
||||||
import com.github.damontecres.wholphin.ui.components.DialogPopup
|
import com.github.damontecres.wholphin.ui.components.ContextMenuDialog
|
||||||
import com.github.damontecres.wholphin.ui.components.EpisodeName
|
import com.github.damontecres.wholphin.ui.components.EpisodeName
|
||||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||||
import com.github.damontecres.wholphin.ui.components.FocusableItemRow
|
import com.github.damontecres.wholphin.ui.components.FocusableItemRow
|
||||||
import com.github.damontecres.wholphin.ui.components.HeaderUtils
|
import com.github.damontecres.wholphin.ui.components.HeaderUtils
|
||||||
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
||||||
import com.github.damontecres.wholphin.ui.components.QuickDetails
|
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.TitleOrLogo
|
||||||
import com.github.damontecres.wholphin.ui.components.rememberLogoUrl
|
import com.github.damontecres.wholphin.ui.components.rememberLogoUrl
|
||||||
import com.github.damontecres.wholphin.ui.data.AddPlaylistViewModel
|
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.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.PlaylistDialog
|
||||||
import com.github.damontecres.wholphin.ui.detail.PlaylistLoadingState
|
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.indexOfFirstOrNull
|
||||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||||
|
|
@ -120,9 +120,10 @@ fun HomePage(
|
||||||
}
|
}
|
||||||
|
|
||||||
LoadingState.Success -> {
|
LoadingState.Success -> {
|
||||||
var dialog by remember { mutableStateOf<DialogParams?>(null) }
|
var showContextMenu by remember { mutableStateOf<ContextMenu?>(null) }
|
||||||
var showPlaylistDialog by remember { mutableStateOf<UUID?>(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)
|
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
||||||
var position by rememberPosition()
|
var position by rememberPosition()
|
||||||
|
|
||||||
|
|
@ -144,43 +145,45 @@ fun HomePage(
|
||||||
row?.rowType is HomeRowConfig.ContinueWatching || row?.rowType is HomeRowConfig.ContinueWatchingCombined
|
row?.rowType is HomeRowConfig.ContinueWatching || row?.rowType is HomeRowConfig.ContinueWatchingCombined
|
||||||
val canRemoveNextUp =
|
val canRemoveNextUp =
|
||||||
row?.rowType is HomeRowConfig.NextUp || row?.rowType is HomeRowConfig.ContinueWatchingCombined
|
row?.rowType is HomeRowConfig.NextUp || row?.rowType is HomeRowConfig.ContinueWatchingCombined
|
||||||
val dialogItems =
|
showContextMenu =
|
||||||
buildMoreDialogItemsForHome(
|
ContextMenu.ForBaseItem(
|
||||||
context = context,
|
fromLongClick = true,
|
||||||
item = item,
|
item = item,
|
||||||
seriesId = item.data.seriesId,
|
chosenStreams = null,
|
||||||
playbackPosition = item.playbackPosition,
|
showGoTo = true,
|
||||||
watched = item.played,
|
showStreamChoices = false,
|
||||||
favorite = item.favorite,
|
canDelete =
|
||||||
canDelete = viewModel.canDelete(item, preferences.appPreferences),
|
viewModel.canDelete(
|
||||||
canRemoveNextUp = canRemoveNextUp,
|
item,
|
||||||
|
preferences.appPreferences,
|
||||||
|
),
|
||||||
canRemoveContinueWatching = canRemoveContinueWatching,
|
canRemoveContinueWatching = canRemoveContinueWatching,
|
||||||
|
canRemoveNextUp = canRemoveNextUp,
|
||||||
actions =
|
actions =
|
||||||
MoreDialogActions(
|
ContextMenuActions(
|
||||||
navigateTo = viewModel.navigationManager::navigateTo,
|
navigateTo = viewModel.navigationManager::navigateTo,
|
||||||
onClickWatch = { itemId, played ->
|
onClickWatch = viewModel::setWatched,
|
||||||
viewModel.setWatched(itemId, played)
|
onClickFavorite = viewModel::setFavorite,
|
||||||
},
|
|
||||||
onClickFavorite = { itemId, favorite ->
|
|
||||||
viewModel.setFavorite(itemId, favorite)
|
|
||||||
},
|
|
||||||
onClickAddPlaylist = { itemId ->
|
onClickAddPlaylist = { itemId ->
|
||||||
playlistViewModel.loadPlaylists(MediaType.VIDEO)
|
playlistViewModel.loadPlaylists(MediaType.VIDEO)
|
||||||
showPlaylistDialog = itemId
|
showPlaylistDialog = itemId
|
||||||
},
|
},
|
||||||
onSendMediaInfo = viewModel.mediaReportService::sendReportFor,
|
onSendMediaInfo = viewModel.mediaReportService::sendReportFor,
|
||||||
onClickDelete = {
|
onDeleteItem = {
|
||||||
showDeleteDialog = RowColumnItem(position, item)
|
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 =
|
val onClickPlay =
|
||||||
|
|
@ -203,10 +206,19 @@ fun HomePage(
|
||||||
showLogo = preferences.appPreferences.interfacePreferences.showLogos,
|
showLogo = preferences.appPreferences.interfacePreferences.showLogos,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
dialog?.let { params ->
|
overviewDialog?.let { info ->
|
||||||
DialogPopup(
|
ItemDetailsDialog(
|
||||||
params = params,
|
info = info,
|
||||||
onDismissRequest = { dialog = null },
|
showFilePath = false,
|
||||||
|
onDismissRequest = { overviewDialog = null },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
showContextMenu?.let { contextMenu ->
|
||||||
|
ContextMenuDialog(
|
||||||
|
onDismissRequest = { showContextMenu = null },
|
||||||
|
getMediaSource = null,
|
||||||
|
contextMenu = contextMenu,
|
||||||
|
preferredSubtitleLanguage = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
showPlaylistDialog?.let { itemId ->
|
showPlaylistDialog?.let { itemId ->
|
||||||
|
|
@ -226,19 +238,7 @@ fun HomePage(
|
||||||
elevation = 3.dp,
|
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 =
|
val onKey =
|
||||||
remember(item) {
|
remember(item) {
|
||||||
{ event: androidx.compose.ui.input.key.KeyEvent ->
|
{ event: KeyEvent ->
|
||||||
if (isPlayKeyUp(event) && item?.type?.playable == true) {
|
if (isPlayKeyUp(event) && item?.type?.playable == true) {
|
||||||
Timber.v("Clicked play on ${item.id}")
|
Timber.v("Clicked play on ${item.id}")
|
||||||
currentOnClickPlay(
|
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.DiscoverItem
|
||||||
import com.github.damontecres.wholphin.data.model.GetItemsFilter
|
import com.github.damontecres.wholphin.data.model.GetItemsFilter
|
||||||
import com.github.damontecres.wholphin.data.model.ItemPlayback
|
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.data.SortAndDirection
|
||||||
import com.github.damontecres.wholphin.ui.detail.series.SeasonEpisodeIds
|
import com.github.damontecres.wholphin.ui.detail.series.SeasonEpisodeIds
|
||||||
import com.github.damontecres.wholphin.ui.preferences.PreferenceScreenOption
|
import com.github.damontecres.wholphin.ui.preferences.PreferenceScreenOption
|
||||||
|
|
@ -80,6 +81,7 @@ sealed class Destination(
|
||||||
val positionMs: Long,
|
val positionMs: Long,
|
||||||
val itemPlayback: ItemPlayback? = null,
|
val itemPlayback: ItemPlayback? = null,
|
||||||
val forceTranscoding: Boolean = false,
|
val forceTranscoding: Boolean = false,
|
||||||
|
val backend: PlayerBackend? = null,
|
||||||
) : Destination(true) {
|
) : Destination(true) {
|
||||||
constructor(item: BaseItem) : this(item.id, item.resumeMs)
|
constructor(item: BaseItem) : this(item.id, item.resumeMs)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue