mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 23:51:21 +02:00
Add long click to home page (#66)
Add a popup when long clicking items on the home page Also fixes a bug where the latest Recorded TV row would show non-Live TV items if nothing has ever been recorded.
This commit is contained in:
parent
2667090485
commit
143fc03d9b
6 changed files with 212 additions and 20 deletions
|
|
@ -156,6 +156,7 @@ fun RecommendedMovie(
|
||||||
HomePageContent(
|
HomePageContent(
|
||||||
homeRows = rows,
|
homeRows = rows,
|
||||||
onClickItem = onClickItem,
|
onClickItem = onClickItem,
|
||||||
|
onLongClickItem = {},
|
||||||
onFocusPosition = onFocusPosition,
|
onFocusPosition = onFocusPosition,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -168,6 +168,7 @@ fun RecommendedTvShow(
|
||||||
HomePageContent(
|
HomePageContent(
|
||||||
homeRows = rows,
|
homeRows = rows,
|
||||||
onClickItem = onClickItem,
|
onClickItem = onClickItem,
|
||||||
|
onLongClickItem = {},
|
||||||
onFocusPosition = onFocusPosition,
|
onFocusPosition = onFocusPosition,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,22 @@ package com.github.damontecres.wholphin.ui.detail
|
||||||
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowForward
|
import androidx.compose.material.icons.automirrored.filled.ArrowForward
|
||||||
|
import androidx.compose.material.icons.filled.ArrowForward
|
||||||
import androidx.compose.material.icons.filled.PlayArrow
|
import androidx.compose.material.icons.filled.PlayArrow
|
||||||
|
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.ui.components.DialogItem
|
import com.github.damontecres.wholphin.ui.components.DialogItem
|
||||||
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.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.api.MediaStreamType
|
||||||
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
|
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
|
||||||
|
import kotlin.time.Duration
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build the [DialogItem]s when clicking "More"
|
* Build the [DialogItem]s when clicking "More"
|
||||||
|
|
@ -131,3 +136,104 @@ fun buildMoreDialogItems(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun buildMoreDialogItemsForHome(
|
||||||
|
item: BaseItem,
|
||||||
|
seriesId: UUID?,
|
||||||
|
playbackPosition: Duration,
|
||||||
|
watched: Boolean,
|
||||||
|
favorite: Boolean,
|
||||||
|
navigateTo: (Destination) -> Unit,
|
||||||
|
onClickWatch: (UUID, Boolean) -> Unit,
|
||||||
|
onClickFavorite: (UUID, Boolean) -> Unit,
|
||||||
|
): List<DialogItem> =
|
||||||
|
buildList {
|
||||||
|
val itemId = item.id
|
||||||
|
add(
|
||||||
|
DialogItem(
|
||||||
|
"Go To",
|
||||||
|
Icons.Default.ArrowForward,
|
||||||
|
) {
|
||||||
|
navigateTo(item.destination())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if (item.type in supportedPlayableTypes) {
|
||||||
|
if (playbackPosition >= 1.seconds) {
|
||||||
|
add(
|
||||||
|
DialogItem(
|
||||||
|
"Resume",
|
||||||
|
Icons.Default.PlayArrow,
|
||||||
|
iconColor = Color.Green.copy(alpha = .8f),
|
||||||
|
) {
|
||||||
|
navigateTo(
|
||||||
|
Destination.Playback(
|
||||||
|
itemId,
|
||||||
|
playbackPosition.inWholeMilliseconds,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
add(
|
||||||
|
DialogItem(
|
||||||
|
"Restart",
|
||||||
|
Icons.Default.Refresh,
|
||||||
|
// iconColor = Color.Green.copy(alpha = .8f),
|
||||||
|
) {
|
||||||
|
navigateTo(
|
||||||
|
Destination.Playback(
|
||||||
|
itemId,
|
||||||
|
0L,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
add(
|
||||||
|
DialogItem(
|
||||||
|
"Play",
|
||||||
|
Icons.Default.PlayArrow,
|
||||||
|
iconColor = Color.Green.copy(alpha = .8f),
|
||||||
|
) {
|
||||||
|
navigateTo(
|
||||||
|
Destination.Playback(
|
||||||
|
itemId,
|
||||||
|
0L,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
) {
|
||||||
|
onClickWatch.invoke(itemId, !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,
|
||||||
|
) {
|
||||||
|
onClickFavorite.invoke(itemId, !favorite)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
seriesId?.let {
|
||||||
|
add(
|
||||||
|
DialogItem(
|
||||||
|
"Go to series",
|
||||||
|
Icons.AutoMirrored.Filled.ArrowForward,
|
||||||
|
) {
|
||||||
|
navigateTo(
|
||||||
|
Destination.MediaItem(
|
||||||
|
it,
|
||||||
|
BaseItemKind.SERIES,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,11 +45,14 @@ import com.github.damontecres.wholphin.ui.Cards
|
||||||
import com.github.damontecres.wholphin.ui.cards.BannerCard
|
import com.github.damontecres.wholphin.ui.cards.BannerCard
|
||||||
import com.github.damontecres.wholphin.ui.cards.ItemRow
|
import com.github.damontecres.wholphin.ui.cards.ItemRow
|
||||||
import com.github.damontecres.wholphin.ui.components.CircularProgress
|
import com.github.damontecres.wholphin.ui.components.CircularProgress
|
||||||
|
import com.github.damontecres.wholphin.ui.components.DialogParams
|
||||||
|
import com.github.damontecres.wholphin.ui.components.DialogPopup
|
||||||
import com.github.damontecres.wholphin.ui.components.DotSeparatedRow
|
import com.github.damontecres.wholphin.ui.components.DotSeparatedRow
|
||||||
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.data.RowColumn
|
import com.github.damontecres.wholphin.ui.data.RowColumn
|
||||||
import com.github.damontecres.wholphin.ui.data.RowColumnSaver
|
import com.github.damontecres.wholphin.ui.data.RowColumnSaver
|
||||||
|
import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItemsForHome
|
||||||
import com.github.damontecres.wholphin.ui.ifElse
|
import com.github.damontecres.wholphin.ui.ifElse
|
||||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||||
import com.github.damontecres.wholphin.ui.roundMinutes
|
import com.github.damontecres.wholphin.ui.roundMinutes
|
||||||
|
|
@ -60,6 +63,7 @@ import com.github.damontecres.wholphin.util.formatDateTime
|
||||||
import com.github.damontecres.wholphin.util.seasonEpisode
|
import com.github.damontecres.wholphin.util.seasonEpisode
|
||||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||||
import org.jellyfin.sdk.model.extensions.ticks
|
import org.jellyfin.sdk.model.extensions.ticks
|
||||||
|
import kotlin.time.Duration
|
||||||
|
|
||||||
data class HomeRow(
|
data class HomeRow(
|
||||||
val section: HomeSection,
|
val section: HomeSection,
|
||||||
|
|
@ -88,14 +92,48 @@ fun HomePage(
|
||||||
-> LoadingPage()
|
-> LoadingPage()
|
||||||
|
|
||||||
LoadingState.Success -> {
|
LoadingState.Success -> {
|
||||||
|
var dialog by remember { mutableStateOf<DialogParams?>(null) }
|
||||||
HomePageContent(
|
HomePageContent(
|
||||||
homeRows,
|
homeRows,
|
||||||
onClickItem = {
|
onClickItem = {
|
||||||
viewModel.navigationManager.navigateTo(it.destination())
|
viewModel.navigationManager.navigateTo(it.destination())
|
||||||
},
|
},
|
||||||
|
onLongClickItem = {
|
||||||
|
val dialogItems =
|
||||||
|
buildMoreDialogItemsForHome(
|
||||||
|
item = it,
|
||||||
|
seriesId = it.data.seriesId,
|
||||||
|
playbackPosition =
|
||||||
|
it.data.userData
|
||||||
|
?.playbackPositionTicks
|
||||||
|
?.ticks
|
||||||
|
?: Duration.ZERO,
|
||||||
|
watched = it.data.userData?.played ?: false,
|
||||||
|
favorite = it.data.userData?.isFavorite ?: false,
|
||||||
|
navigateTo = viewModel.navigationManager::navigateTo,
|
||||||
|
onClickWatch = { itemId, played ->
|
||||||
|
viewModel.setWatched(itemId, played)
|
||||||
|
},
|
||||||
|
onClickFavorite = { itemId, favorite ->
|
||||||
|
viewModel.setFavorite(itemId, favorite)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
dialog =
|
||||||
|
DialogParams(
|
||||||
|
title = it.title ?: "",
|
||||||
|
fromLongClick = true,
|
||||||
|
items = dialogItems,
|
||||||
|
)
|
||||||
|
},
|
||||||
loadingState = loading,
|
loadingState = loading,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
|
dialog?.let { params ->
|
||||||
|
DialogPopup(
|
||||||
|
params = params,
|
||||||
|
onDismissRequest = { dialog = null },
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -104,6 +142,7 @@ fun HomePage(
|
||||||
fun HomePageContent(
|
fun HomePageContent(
|
||||||
homeRows: List<HomeRow>,
|
homeRows: List<HomeRow>,
|
||||||
onClickItem: (BaseItem) -> Unit,
|
onClickItem: (BaseItem) -> Unit,
|
||||||
|
onLongClickItem: (BaseItem) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onFocusPosition: ((RowColumn) -> Unit)? = null,
|
onFocusPosition: ((RowColumn) -> Unit)? = null,
|
||||||
loadingState: LoadingState? = null,
|
loadingState: LoadingState? = null,
|
||||||
|
|
@ -183,7 +222,7 @@ fun HomePageContent(
|
||||||
position = RowColumn(rowIndex, index)
|
position = RowColumn(rowIndex, index)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLongClickItem = {},
|
onLongClickItem = onLongClickItem,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
cardContent = { index, item, cardModifier, onClick, onLongClick ->
|
cardContent = { index, item, cardModifier, onClick, onLongClick ->
|
||||||
// TODO better aspect ration handling?
|
// TODO better aspect ration handling?
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||||
import com.github.damontecres.wholphin.ui.SlimItemFields
|
import com.github.damontecres.wholphin.ui.SlimItemFields
|
||||||
import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
||||||
import com.github.damontecres.wholphin.ui.nav.ServerNavDrawerItem
|
import com.github.damontecres.wholphin.ui.nav.ServerNavDrawerItem
|
||||||
|
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||||
import com.github.damontecres.wholphin.util.LoadingExceptionHandler
|
import com.github.damontecres.wholphin.util.LoadingExceptionHandler
|
||||||
import com.github.damontecres.wholphin.util.LoadingState
|
import com.github.damontecres.wholphin.util.LoadingState
|
||||||
import com.github.damontecres.wholphin.util.supportItemKinds
|
import com.github.damontecres.wholphin.util.supportItemKinds
|
||||||
|
|
@ -21,6 +22,7 @@ import kotlinx.coroutines.withContext
|
||||||
import org.jellyfin.sdk.api.client.ApiClient
|
import org.jellyfin.sdk.api.client.ApiClient
|
||||||
import org.jellyfin.sdk.api.client.extensions.itemsApi
|
import org.jellyfin.sdk.api.client.extensions.itemsApi
|
||||||
import org.jellyfin.sdk.api.client.extensions.liveTvApi
|
import org.jellyfin.sdk.api.client.extensions.liveTvApi
|
||||||
|
import org.jellyfin.sdk.api.client.extensions.playStateApi
|
||||||
import org.jellyfin.sdk.api.client.extensions.tvShowsApi
|
import org.jellyfin.sdk.api.client.extensions.tvShowsApi
|
||||||
import org.jellyfin.sdk.api.client.extensions.userLibraryApi
|
import org.jellyfin.sdk.api.client.extensions.userLibraryApi
|
||||||
import org.jellyfin.sdk.api.client.extensions.userViewsApi
|
import org.jellyfin.sdk.api.client.extensions.userViewsApi
|
||||||
|
|
@ -46,8 +48,11 @@ class HomeViewModel
|
||||||
val loadingState = MutableLiveData<LoadingState>(LoadingState.Pending)
|
val loadingState = MutableLiveData<LoadingState>(LoadingState.Pending)
|
||||||
val homeRows = MutableLiveData<List<HomeRow>>()
|
val homeRows = MutableLiveData<List<HomeRow>>()
|
||||||
|
|
||||||
|
private lateinit var preferences: UserPreferences
|
||||||
|
|
||||||
fun init(preferences: UserPreferences): Job {
|
fun init(preferences: UserPreferences): Job {
|
||||||
loadingState.value = LoadingState.Loading
|
loadingState.value = LoadingState.Loading
|
||||||
|
this.preferences = preferences
|
||||||
val prefs = preferences.appPreferences.homePagePreferences
|
val prefs = preferences.appPreferences.homePagePreferences
|
||||||
val limit = prefs.maxItemsPerRow
|
val limit = prefs.maxItemsPerRow
|
||||||
return viewModelScope.launch(
|
return viewModelScope.launch(
|
||||||
|
|
@ -171,7 +176,7 @@ class HomeViewModel
|
||||||
latestMediaIncludes
|
latestMediaIncludes
|
||||||
.mapNotNull { viewId -> views.items.firstOrNull { it.id == viewId } }
|
.mapNotNull { viewId -> views.items.firstOrNull { it.id == viewId } }
|
||||||
.filter { it.collectionType in supportedLatestCollectionTypes }
|
.filter { it.collectionType in supportedLatestCollectionTypes }
|
||||||
.map { view ->
|
.mapNotNull { view ->
|
||||||
val title =
|
val title =
|
||||||
if (view.collectionType == CollectionType.LIVETV) {
|
if (view.collectionType == CollectionType.LIVETV) {
|
||||||
"Recently Recorded"
|
"Recently Recorded"
|
||||||
|
|
@ -189,28 +194,58 @@ class HomeViewModel
|
||||||
} else {
|
} else {
|
||||||
view.id
|
view.id
|
||||||
}
|
}
|
||||||
val request =
|
viewId?.let {
|
||||||
GetLatestMediaRequest(
|
val request =
|
||||||
fields = SlimItemFields,
|
GetLatestMediaRequest(
|
||||||
imageTypeLimit = 1,
|
fields = SlimItemFields,
|
||||||
parentId = viewId,
|
imageTypeLimit = 1,
|
||||||
groupItems = true,
|
parentId = viewId,
|
||||||
limit = limit,
|
groupItems = true,
|
||||||
isPlayed = null, // Server will handle user's preference
|
limit = limit,
|
||||||
|
isPlayed = null, // Server will handle user's preference
|
||||||
|
)
|
||||||
|
val latest =
|
||||||
|
api.userLibraryApi
|
||||||
|
.getLatestMedia(request)
|
||||||
|
.content
|
||||||
|
.map { BaseItem.from(it, api, true) }
|
||||||
|
HomeRow(
|
||||||
|
section = HomeSection.LATEST_MEDIA,
|
||||||
|
items = latest,
|
||||||
|
title = title,
|
||||||
)
|
)
|
||||||
val latest =
|
}
|
||||||
api.userLibraryApi
|
|
||||||
.getLatestMedia(request)
|
|
||||||
.content
|
|
||||||
.map { BaseItem.from(it, api, true) }
|
|
||||||
HomeRow(
|
|
||||||
section = HomeSection.LATEST_MEDIA,
|
|
||||||
items = latest,
|
|
||||||
title = title,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
return rows
|
return rows
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setWatched(
|
||||||
|
itemId: UUID,
|
||||||
|
played: Boolean,
|
||||||
|
) = viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
|
||||||
|
if (played) {
|
||||||
|
api.playStateApi.markPlayedItem(itemId)
|
||||||
|
} else {
|
||||||
|
api.playStateApi.markUnplayedItem(itemId)
|
||||||
|
}
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
init(preferences)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setFavorite(
|
||||||
|
itemId: UUID,
|
||||||
|
favorite: Boolean,
|
||||||
|
) = viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
|
||||||
|
if (favorite) {
|
||||||
|
api.userLibraryApi.markFavoriteItem(itemId)
|
||||||
|
} else {
|
||||||
|
api.userLibraryApi.unmarkFavoriteItem(itemId)
|
||||||
|
}
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
init(preferences)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val supportedLatestCollectionTypes =
|
val supportedLatestCollectionTypes =
|
||||||
|
|
|
||||||
|
|
@ -38,3 +38,13 @@ val supportedCollectionTypes =
|
||||||
CollectionType.BOXSETS,
|
CollectionType.BOXSETS,
|
||||||
CollectionType.LIVETV,
|
CollectionType.LIVETV,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val supportedPlayableTypes =
|
||||||
|
setOf(
|
||||||
|
BaseItemKind.MOVIE,
|
||||||
|
BaseItemKind.EPISODE,
|
||||||
|
BaseItemKind.VIDEO,
|
||||||
|
BaseItemKind.TV_CHANNEL,
|
||||||
|
BaseItemKind.TV_PROGRAM,
|
||||||
|
BaseItemKind.RECORDING,
|
||||||
|
)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue