mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
Add long clicking context menus everywhere (#202)
Adds long click context menus to just about every card throughout the app. Previously only a few pages had this. Closes #196
This commit is contained in:
parent
5e2c75bb70
commit
2e3e4e3f5f
29 changed files with 766 additions and 289 deletions
|
|
@ -11,6 +11,7 @@ import org.jellyfin.sdk.model.api.BaseItemDto
|
|||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import org.jellyfin.sdk.model.api.ImageType
|
||||
import org.jellyfin.sdk.model.extensions.ticks
|
||||
import kotlin.time.Duration
|
||||
|
||||
@Serializable
|
||||
data class BaseItem(
|
||||
|
|
@ -18,29 +19,29 @@ data class BaseItem(
|
|||
val imageUrl: String?,
|
||||
val backdropImageUrl: String? = null,
|
||||
) {
|
||||
@Transient val id = data.id
|
||||
val id get() = data.id
|
||||
|
||||
@Transient val type = data.type
|
||||
val type get() = data.type
|
||||
|
||||
@Transient val name = data.name
|
||||
val name get() = data.name
|
||||
|
||||
@Transient
|
||||
val title = if (type == BaseItemKind.EPISODE) data.seriesName else name
|
||||
val title get() = if (type == BaseItemKind.EPISODE) data.seriesName else name
|
||||
|
||||
@Transient
|
||||
val subtitle =
|
||||
if (type == BaseItemKind.EPISODE) data.seasonEpisode + " - " + name else data.productionYear?.toString()
|
||||
|
||||
@Transient
|
||||
val resumeMs =
|
||||
data.userData
|
||||
?.playbackPositionTicks
|
||||
?.ticks
|
||||
?.inWholeMilliseconds
|
||||
val subtitle
|
||||
get() =
|
||||
if (type == BaseItemKind.EPISODE) data.seasonEpisode + " - " + name else data.productionYear?.toString()
|
||||
|
||||
@Transient
|
||||
val indexNumber = data.indexNumber ?: dateAsIndex()
|
||||
|
||||
val playbackPosition get() = data.userData?.playbackPositionTicks?.ticks ?: Duration.ZERO
|
||||
|
||||
val resumeMs get() = playbackPosition.inWholeMilliseconds
|
||||
|
||||
val played get() = data.userData?.played ?: false
|
||||
|
||||
val favorite get() = data.userData?.isFavorite ?: false
|
||||
|
||||
private fun dateAsIndex(): Int? =
|
||||
data.premiereDate
|
||||
?.let {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ import com.github.damontecres.wholphin.preferences.AppPreferences
|
|||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.preferences.updateInterfacePreferences
|
||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.FavoriteWatchManager
|
||||
import com.github.damontecres.wholphin.util.FavoriteWatchManagerImpl
|
||||
import com.github.damontecres.wholphin.util.RememberTabManager
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -23,6 +25,7 @@ import kotlinx.coroutines.SupervisorJob
|
|||
import kotlinx.coroutines.launch
|
||||
import okhttp3.OkHttpClient
|
||||
import org.jellyfin.sdk.Jellyfin
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
import org.jellyfin.sdk.api.client.util.AuthorizationHeaderBuilder
|
||||
import org.jellyfin.sdk.api.okhttp.OkHttpFactory
|
||||
import org.jellyfin.sdk.createJellyfin
|
||||
|
|
@ -182,4 +185,8 @@ object AppModule {
|
|||
@Singleton
|
||||
@IoCoroutineScope
|
||||
fun ioCoroutineScope(): CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun favoriteWatchManager(api: ApiClient): FavoriteWatchManager = FavoriteWatchManagerImpl(api)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,16 +21,21 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.RectangleShape
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.tv.material3.Card
|
||||
import androidx.tv.material3.CardDefaults
|
||||
import androidx.tv.material3.MaterialTheme
|
||||
import androidx.tv.material3.Text
|
||||
import coil3.compose.AsyncImage
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.ui.AppColors
|
||||
import com.github.damontecres.wholphin.ui.Cards
|
||||
import com.github.damontecres.wholphin.ui.FontAwesome
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
|
||||
/**
|
||||
|
|
@ -45,6 +50,7 @@ fun BannerCard(
|
|||
modifier: Modifier = Modifier,
|
||||
cornerText: String? = null,
|
||||
played: Boolean = false,
|
||||
favorite: Boolean = false,
|
||||
playPercent: Double = 0.0,
|
||||
cardHeight: Dp = 140.dp * .85f,
|
||||
aspectRatio: Float = 16f / 9,
|
||||
|
|
@ -118,6 +124,18 @@ fun BannerCard(
|
|||
}
|
||||
}
|
||||
}
|
||||
if (favorite) {
|
||||
Text(
|
||||
modifier =
|
||||
Modifier
|
||||
.align(Alignment.TopStart)
|
||||
.padding(8.dp),
|
||||
color = colorResource(android.R.color.holo_red_light),
|
||||
text = stringResource(R.string.fa_heart),
|
||||
fontSize = 16.sp,
|
||||
fontFamily = FontAwesome,
|
||||
)
|
||||
}
|
||||
if (playPercent > 0 && playPercent < 100) {
|
||||
Box(
|
||||
modifier =
|
||||
|
|
|
|||
|
|
@ -28,8 +28,8 @@ import com.github.damontecres.wholphin.util.FocusPair
|
|||
fun ItemRow(
|
||||
title: String,
|
||||
items: List<BaseItem?>,
|
||||
onClickItem: (BaseItem) -> Unit,
|
||||
onLongClickItem: (BaseItem) -> Unit,
|
||||
onClickItem: (Int, BaseItem) -> Unit,
|
||||
onLongClickItem: (Int, BaseItem) -> Unit,
|
||||
cardContent: @Composable (
|
||||
index: Int,
|
||||
item: BaseItem?,
|
||||
|
|
@ -83,8 +83,8 @@ fun ItemRow(
|
|||
index,
|
||||
item,
|
||||
cardModifier,
|
||||
{ if (item != null) onClickItem.invoke(item) },
|
||||
{ if (item != null) onLongClickItem.invoke(item) },
|
||||
{ if (item != null) onClickItem.invoke(index, item) },
|
||||
{ if (item != null) onLongClickItem.invoke(index, item) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -95,8 +95,8 @@ fun ItemRow(
|
|||
fun BannerItemRow(
|
||||
title: String,
|
||||
items: List<BaseItem?>,
|
||||
onClickItem: (BaseItem) -> Unit,
|
||||
onLongClickItem: (BaseItem) -> Unit,
|
||||
onClickItem: (Int, BaseItem) -> Unit,
|
||||
onLongClickItem: (Int, BaseItem) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
focusPair: FocusPair? = null,
|
||||
cardOnFocus: ((isFocused: Boolean, index: Int) -> Unit)? = null,
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ fun PersonRow(
|
|||
people: List<Person>,
|
||||
onClick: (Person) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
onLongClick: ((Person) -> Unit)? = null,
|
||||
onLongClick: ((Int, Person) -> Unit)? = null,
|
||||
) {
|
||||
val firstFocus = remember { FocusRequester() }
|
||||
Column(
|
||||
|
|
@ -54,7 +54,7 @@ fun PersonRow(
|
|||
PersonCard(
|
||||
item = item,
|
||||
onClick = { onClick.invoke(item) },
|
||||
onLongClick = { onLongClick?.invoke(item) },
|
||||
onLongClick = { onLongClick?.invoke(index, item) },
|
||||
modifier =
|
||||
Modifier
|
||||
.width(120.dp)
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
|
@ -37,12 +38,20 @@ import com.github.damontecres.wholphin.data.model.LibraryDisplayInfo
|
|||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect
|
||||
import com.github.damontecres.wholphin.ui.SlimItemFields
|
||||
import com.github.damontecres.wholphin.ui.data.AddPlaylistViewModel
|
||||
import com.github.damontecres.wholphin.ui.data.SortAndDirection
|
||||
import com.github.damontecres.wholphin.ui.detail.CardGrid
|
||||
import com.github.damontecres.wholphin.ui.detail.ItemViewModel
|
||||
import com.github.damontecres.wholphin.ui.detail.MoreDialogActions
|
||||
import com.github.damontecres.wholphin.ui.detail.PlaylistDialog
|
||||
import com.github.damontecres.wholphin.ui.detail.PlaylistLoadingState
|
||||
import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItemsForHome
|
||||
import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.toServerString
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import com.github.damontecres.wholphin.util.ApiRequestPager
|
||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.FavoriteWatchManager
|
||||
import com.github.damontecres.wholphin.util.GetItemsRequestHandler
|
||||
import com.github.damontecres.wholphin.util.LoadingExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
|
|
@ -57,6 +66,7 @@ import org.jellyfin.sdk.model.api.BaseItemKind
|
|||
import org.jellyfin.sdk.model.api.CollectionType
|
||||
import org.jellyfin.sdk.model.api.ImageType
|
||||
import org.jellyfin.sdk.model.api.ItemSortBy
|
||||
import org.jellyfin.sdk.model.api.MediaType
|
||||
import org.jellyfin.sdk.model.api.SortOrder
|
||||
import org.jellyfin.sdk.model.api.request.GetItemsRequest
|
||||
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
|
||||
|
|
@ -71,6 +81,8 @@ class CollectionFolderViewModel
|
|||
@param:ApplicationContext private val context: Context,
|
||||
private val serverRepository: ServerRepository,
|
||||
private val libraryDisplayInfoDao: LibraryDisplayInfoDao,
|
||||
private val favoriteWatchManager: FavoriteWatchManager,
|
||||
val navigationManager: NavigationManager,
|
||||
) : ItemViewModel(api) {
|
||||
val loading = MutableLiveData<LoadingState>(LoadingState.Loading)
|
||||
val pager = MutableLiveData<List<BaseItem?>>(listOf())
|
||||
|
|
@ -228,6 +240,24 @@ class CollectionFolderViewModel
|
|||
result.totalRecordCount
|
||||
}
|
||||
}
|
||||
|
||||
fun setWatched(
|
||||
position: Int,
|
||||
itemId: UUID,
|
||||
played: Boolean,
|
||||
) = viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
|
||||
favoriteWatchManager.setWatched(itemId, played)
|
||||
(pager.value as? ApiRequestPager<*>)?.refreshItem(position, itemId)
|
||||
}
|
||||
|
||||
fun setFavorite(
|
||||
position: Int,
|
||||
itemId: UUID,
|
||||
favorite: Boolean,
|
||||
) = viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
|
||||
favoriteWatchManager.setFavorite(itemId, favorite)
|
||||
(pager.value as? ApiRequestPager<*>)?.refreshItem(position, itemId)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -241,7 +271,7 @@ fun CollectionFolderGrid(
|
|||
itemId: UUID,
|
||||
initialFilter: GetItemsFilter,
|
||||
recursive: Boolean,
|
||||
onClickItem: (BaseItem) -> Unit,
|
||||
onClickItem: (Int, BaseItem) -> Unit,
|
||||
sortOptions: List<ItemSortBy>,
|
||||
modifier: Modifier = Modifier,
|
||||
initialSortAndDirection: SortAndDirection? = null,
|
||||
|
|
@ -266,14 +296,16 @@ fun CollectionFolderGrid(
|
|||
itemId: String,
|
||||
initialFilter: GetItemsFilter,
|
||||
recursive: Boolean,
|
||||
onClickItem: (BaseItem) -> Unit,
|
||||
onClickItem: (Int, BaseItem) -> Unit,
|
||||
sortOptions: List<ItemSortBy>,
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: CollectionFolderViewModel = hiltViewModel(),
|
||||
playlistViewModel: AddPlaylistViewModel = hiltViewModel(),
|
||||
initialSortAndDirection: SortAndDirection? = null,
|
||||
showTitle: Boolean = true,
|
||||
positionCallback: ((columns: Int, position: Int) -> Unit)? = null,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
OneTimeLaunchedEffect {
|
||||
viewModel.init(itemId, initialSortAndDirection, recursive, initialFilter)
|
||||
}
|
||||
|
|
@ -283,6 +315,10 @@ fun CollectionFolderGrid(
|
|||
val item by viewModel.item.observeAsState()
|
||||
val pager by viewModel.pager.observeAsState()
|
||||
|
||||
var moreDialog by remember { mutableStateOf<Optional<PositionItem>>(Optional.absent()) }
|
||||
var showPlaylistDialog by remember { mutableStateOf<Optional<UUID>>(Optional.absent()) }
|
||||
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
||||
|
||||
when (val state = loading) {
|
||||
is LoadingState.Error -> ErrorMessage(state)
|
||||
LoadingState.Loading,
|
||||
|
|
@ -297,6 +333,9 @@ fun CollectionFolderGrid(
|
|||
sortAndDirection = sortAndDirection!!,
|
||||
modifier = modifier,
|
||||
onClickItem = onClickItem,
|
||||
onLongClickItem = { position, item ->
|
||||
moreDialog.makePresent(PositionItem(position, item))
|
||||
},
|
||||
onSortChange = {
|
||||
viewModel.onSortChange(it, recursive, filter)
|
||||
},
|
||||
|
|
@ -308,6 +347,55 @@ fun CollectionFolderGrid(
|
|||
}
|
||||
}
|
||||
}
|
||||
moreDialog.compose { (position, item) ->
|
||||
DialogPopup(
|
||||
showDialog = true,
|
||||
title = item.title ?: "",
|
||||
dialogItems =
|
||||
buildMoreDialogItemsForHome(
|
||||
context = context,
|
||||
item = item,
|
||||
seriesId = null,
|
||||
playbackPosition = item.playbackPosition,
|
||||
watched = item.played,
|
||||
favorite = item.favorite,
|
||||
actions =
|
||||
MoreDialogActions(
|
||||
navigateTo = { viewModel.navigationManager.navigateTo(it) },
|
||||
onClickWatch = { itemId, watched ->
|
||||
viewModel.setWatched(position, itemId, watched)
|
||||
},
|
||||
onClickFavorite = { itemId, watched ->
|
||||
viewModel.setFavorite(position, itemId, watched)
|
||||
},
|
||||
onClickAddPlaylist = {
|
||||
playlistViewModel.loadPlaylists(MediaType.VIDEO)
|
||||
showPlaylistDialog.makePresent(it)
|
||||
},
|
||||
),
|
||||
),
|
||||
onDismissRequest = { moreDialog.makeAbsent() },
|
||||
dismissOnClick = true,
|
||||
waitToLoad = true,
|
||||
)
|
||||
}
|
||||
showPlaylistDialog.compose { itemId ->
|
||||
PlaylistDialog(
|
||||
title = stringResource(R.string.add_to_playlist),
|
||||
state = playlistState,
|
||||
onDismissRequest = { showPlaylistDialog.makeAbsent() },
|
||||
onClick = {
|
||||
playlistViewModel.addToPlaylist(it.id, itemId)
|
||||
showPlaylistDialog.makeAbsent()
|
||||
},
|
||||
createEnabled = true,
|
||||
onCreatePlaylist = {
|
||||
playlistViewModel.createPlaylistAndAddItem(it, itemId)
|
||||
showPlaylistDialog.makeAbsent()
|
||||
},
|
||||
elevation = 3.dp,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
@ -316,7 +404,8 @@ fun CollectionFolderGridContent(
|
|||
item: BaseItem?,
|
||||
pager: List<BaseItem?>,
|
||||
sortAndDirection: SortAndDirection,
|
||||
onClickItem: (BaseItem) -> Unit,
|
||||
onClickItem: (Int, BaseItem) -> Unit,
|
||||
onLongClickItem: (Int, BaseItem) -> Unit,
|
||||
onSortChange: (SortAndDirection) -> Unit,
|
||||
letterPosition: suspend (Char) -> Int,
|
||||
sortOptions: List<ItemSortBy>,
|
||||
|
|
@ -358,7 +447,7 @@ fun CollectionFolderGridContent(
|
|||
CardGrid(
|
||||
pager = pager,
|
||||
onClickItem = onClickItem,
|
||||
onLongClickItem = {},
|
||||
onLongClickItem = onLongClickItem,
|
||||
letterPosition = letterPosition,
|
||||
gridFocusRequester = gridFocusRequester,
|
||||
showJumpButtons = false, // TODO add preference
|
||||
|
|
@ -372,3 +461,8 @@ fun CollectionFolderGridContent(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class PositionItem(
|
||||
val position: Int,
|
||||
val item: BaseItem,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ fun GenreCardGrid(
|
|||
LaunchedEffect(Unit) { gridFocusRequester.tryRequestFocus() }
|
||||
CardGrid(
|
||||
pager = genres,
|
||||
onClickItem = { genre ->
|
||||
onClickItem = { _, genre ->
|
||||
viewModel.navigationManager.navigateTo(
|
||||
Destination.FilteredCollection(
|
||||
itemId = itemId,
|
||||
|
|
@ -112,7 +112,7 @@ fun GenreCardGrid(
|
|||
),
|
||||
)
|
||||
},
|
||||
onLongClickItem = {},
|
||||
onLongClickItem = { _, _ -> },
|
||||
letterPosition = { viewModel.positionOfLetter(it) },
|
||||
gridFocusRequester = gridFocusRequester,
|
||||
showJumpButtons = false,
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ fun LoadingRow(
|
|||
rowIndex: Int,
|
||||
position: RowColumn,
|
||||
focusRequester: FocusRequester,
|
||||
onClickItem: (BaseItem) -> Unit,
|
||||
onClickItem: (Int, BaseItem) -> Unit,
|
||||
onClickPosition: (RowColumn) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
showIfEmpty: Boolean = true,
|
||||
|
|
@ -82,7 +82,7 @@ fun LoadingRow(
|
|||
title = title,
|
||||
items = r.items,
|
||||
onClickItem = onClickItem,
|
||||
onLongClickItem = {},
|
||||
onLongClickItem = { _, _ -> },
|
||||
modifier = modifier,
|
||||
cardContent = cardContent,
|
||||
horizontalPadding = horizontalPadding,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
package com.github.damontecres.wholphin.ui.components
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
|
||||
class Optional<T> private constructor(
|
||||
initialValue: T?,
|
||||
) {
|
||||
private var value by mutableStateOf(initialValue)
|
||||
|
||||
@Composable
|
||||
fun compose(run: @Composable (T) -> Unit) {
|
||||
value?.let { run.invoke(it) }
|
||||
}
|
||||
|
||||
fun makeAbsent() {
|
||||
value = null
|
||||
}
|
||||
|
||||
fun makePresent(value: T) {
|
||||
this.value = value
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun <T> absent() = Optional<T>(null)
|
||||
|
||||
fun <T> present(value: T) = Optional(value)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,178 @@
|
|||
package com.github.damontecres.wholphin.ui.components
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect
|
||||
import com.github.damontecres.wholphin.ui.data.AddPlaylistViewModel
|
||||
import com.github.damontecres.wholphin.ui.data.RowColumn
|
||||
import com.github.damontecres.wholphin.ui.detail.MoreDialogActions
|
||||
import com.github.damontecres.wholphin.ui.detail.PlaylistDialog
|
||||
import com.github.damontecres.wholphin.ui.detail.PlaylistLoadingState
|
||||
import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItemsForHome
|
||||
import com.github.damontecres.wholphin.ui.launchIO
|
||||
import com.github.damontecres.wholphin.ui.main.HomePageContent
|
||||
import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
||||
import com.github.damontecres.wholphin.util.ApiRequestPager
|
||||
import com.github.damontecres.wholphin.util.FavoriteWatchManager
|
||||
import com.github.damontecres.wholphin.util.HomeRowLoadingState
|
||||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import org.jellyfin.sdk.model.api.MediaType
|
||||
import java.util.UUID
|
||||
|
||||
abstract class RecommendedViewModel(
|
||||
val navigationManager: NavigationManager,
|
||||
val favoriteWatchManager: FavoriteWatchManager,
|
||||
) : ViewModel() {
|
||||
abstract fun init()
|
||||
|
||||
abstract val rows: MutableStateFlow<MutableList<HomeRowLoadingState>>
|
||||
|
||||
val loading = MutableLiveData<LoadingState>(LoadingState.Loading)
|
||||
|
||||
fun refreshItem(
|
||||
position: RowColumn,
|
||||
itemId: UUID,
|
||||
) {
|
||||
viewModelScope.launchIO {
|
||||
val row = rows.value.getOrNull(position.row)
|
||||
if (row is HomeRowLoadingState.Success) {
|
||||
(row.items as? ApiRequestPager<*>)?.refreshItem(position.column, itemId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setWatched(
|
||||
position: RowColumn,
|
||||
itemId: UUID,
|
||||
watched: Boolean,
|
||||
) {
|
||||
viewModelScope.launchIO {
|
||||
favoriteWatchManager.setWatched(itemId, watched)
|
||||
refreshItem(position, itemId)
|
||||
}
|
||||
}
|
||||
|
||||
fun setFavorite(
|
||||
position: RowColumn,
|
||||
itemId: UUID,
|
||||
watched: Boolean,
|
||||
) {
|
||||
viewModelScope.launchIO {
|
||||
favoriteWatchManager.setFavorite(itemId, watched)
|
||||
refreshItem(position, itemId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RecommendedContent(
|
||||
preferences: UserPreferences,
|
||||
viewModel: RecommendedViewModel,
|
||||
modifier: Modifier = Modifier,
|
||||
playlistViewModel: AddPlaylistViewModel = hiltViewModel(),
|
||||
onFocusPosition: ((RowColumn) -> Unit)? = null,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
var moreDialog by remember { mutableStateOf<Optional<RowColumnItem>>(Optional.absent()) }
|
||||
var showPlaylistDialog by remember { mutableStateOf<Optional<UUID>>(Optional.absent()) }
|
||||
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
||||
|
||||
OneTimeLaunchedEffect {
|
||||
viewModel.init()
|
||||
}
|
||||
val loading by viewModel.loading.observeAsState(LoadingState.Loading)
|
||||
val rows by viewModel.rows.collectAsState()
|
||||
|
||||
when (val state = loading) {
|
||||
is LoadingState.Error -> ErrorMessage(state)
|
||||
|
||||
LoadingState.Loading,
|
||||
LoadingState.Pending,
|
||||
-> LoadingPage()
|
||||
|
||||
LoadingState.Success ->
|
||||
HomePageContent(
|
||||
homeRows = rows,
|
||||
onClickItem = { _, item ->
|
||||
viewModel.navigationManager.navigateTo(item.destination())
|
||||
},
|
||||
onLongClickItem = { position, item ->
|
||||
moreDialog.makePresent(RowColumnItem(position, item))
|
||||
},
|
||||
onFocusPosition = onFocusPosition,
|
||||
showClock = preferences.appPreferences.interfacePreferences.showClock,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
moreDialog.compose { (position, item) ->
|
||||
DialogPopup(
|
||||
showDialog = true,
|
||||
title = item.title ?: "",
|
||||
dialogItems =
|
||||
buildMoreDialogItemsForHome(
|
||||
context = context,
|
||||
item = item,
|
||||
seriesId = null,
|
||||
playbackPosition = item.playbackPosition,
|
||||
watched = item.played,
|
||||
favorite = item.favorite,
|
||||
actions =
|
||||
MoreDialogActions(
|
||||
navigateTo = { viewModel.navigationManager.navigateTo(it) },
|
||||
onClickWatch = { itemId, watched ->
|
||||
viewModel.setWatched(position, itemId, watched)
|
||||
},
|
||||
onClickFavorite = { itemId, watched ->
|
||||
viewModel.setFavorite(position, itemId, watched)
|
||||
},
|
||||
onClickAddPlaylist = {
|
||||
playlistViewModel.loadPlaylists(MediaType.VIDEO)
|
||||
showPlaylistDialog.makePresent(it)
|
||||
},
|
||||
),
|
||||
),
|
||||
onDismissRequest = { moreDialog.makeAbsent() },
|
||||
dismissOnClick = true,
|
||||
waitToLoad = true,
|
||||
)
|
||||
}
|
||||
showPlaylistDialog.compose { itemId ->
|
||||
PlaylistDialog(
|
||||
title = stringResource(R.string.add_to_playlist),
|
||||
state = playlistState,
|
||||
onDismissRequest = { showPlaylistDialog.makeAbsent() },
|
||||
onClick = {
|
||||
playlistViewModel.addToPlaylist(it.id, itemId)
|
||||
showPlaylistDialog.makeAbsent()
|
||||
},
|
||||
createEnabled = true,
|
||||
onCreatePlaylist = {
|
||||
playlistViewModel.createPlaylistAndAddItem(it, itemId)
|
||||
showPlaylistDialog.makeAbsent()
|
||||
},
|
||||
elevation = 3.dp,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private data class RowColumnItem(
|
||||
val position: RowColumn,
|
||||
val item: BaseItem,
|
||||
)
|
||||
|
|
@ -2,25 +2,19 @@ package com.github.damontecres.wholphin.ui.components
|
|||
|
||||
import android.content.Context
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.ServerRepository
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect
|
||||
import com.github.damontecres.wholphin.ui.SlimItemFields
|
||||
import com.github.damontecres.wholphin.ui.data.RowColumn
|
||||
import com.github.damontecres.wholphin.ui.launchIO
|
||||
import com.github.damontecres.wholphin.ui.main.HomePageContent
|
||||
import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
||||
import com.github.damontecres.wholphin.util.ApiRequestPager
|
||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.FavoriteWatchManager
|
||||
import com.github.damontecres.wholphin.util.GetItemsRequestHandler
|
||||
import com.github.damontecres.wholphin.util.GetResumeItemsRequestHandler
|
||||
import com.github.damontecres.wholphin.util.GetSuggestionsRequestHandler
|
||||
|
|
@ -54,15 +48,15 @@ class RecommendedMovieViewModel
|
|||
private val api: ApiClient,
|
||||
private val serverRepository: ServerRepository,
|
||||
@Assisted val parentId: UUID,
|
||||
) : ViewModel() {
|
||||
navigationManager: NavigationManager,
|
||||
favoriteWatchManager: FavoriteWatchManager,
|
||||
) : RecommendedViewModel(navigationManager, favoriteWatchManager) {
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(parentId: UUID): RecommendedMovieViewModel
|
||||
}
|
||||
|
||||
val loading = MutableLiveData<LoadingState>(LoadingState.Loading)
|
||||
|
||||
val rows =
|
||||
override val rows =
|
||||
MutableStateFlow<MutableList<HomeRowLoadingState>>(
|
||||
rowTitles
|
||||
.map {
|
||||
|
|
@ -72,7 +66,7 @@ class RecommendedMovieViewModel
|
|||
}.toMutableList(),
|
||||
)
|
||||
|
||||
fun init() {
|
||||
override fun init() {
|
||||
viewModelScope.launch(Dispatchers.IO + ExceptionHandler()) {
|
||||
try {
|
||||
val resumeItemsRequest =
|
||||
|
|
@ -211,7 +205,6 @@ class RecommendedMovieViewModel
|
|||
fun RecommendedMovie(
|
||||
preferences: UserPreferences,
|
||||
parentId: UUID,
|
||||
onClickItem: (BaseItem) -> Unit,
|
||||
onFocusPosition: (RowColumn) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: RecommendedMovieViewModel =
|
||||
|
|
@ -219,27 +212,10 @@ fun RecommendedMovie(
|
|||
creationCallback = { it.create(parentId) },
|
||||
),
|
||||
) {
|
||||
OneTimeLaunchedEffect {
|
||||
viewModel.init()
|
||||
}
|
||||
val loading by viewModel.loading.observeAsState(LoadingState.Loading)
|
||||
val rows by viewModel.rows.collectAsState()
|
||||
|
||||
when (val state = loading) {
|
||||
is LoadingState.Error -> ErrorMessage(state)
|
||||
|
||||
LoadingState.Loading,
|
||||
LoadingState.Pending,
|
||||
-> LoadingPage()
|
||||
|
||||
LoadingState.Success ->
|
||||
HomePageContent(
|
||||
homeRows = rows,
|
||||
onClickItem = onClickItem,
|
||||
onLongClickItem = {},
|
||||
onFocusPosition = onFocusPosition,
|
||||
showClock = preferences.appPreferences.interfacePreferences.showClock,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
RecommendedContent(
|
||||
preferences = preferences,
|
||||
viewModel = viewModel,
|
||||
onFocusPosition = onFocusPosition,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,25 +2,19 @@ package com.github.damontecres.wholphin.ui.components
|
|||
|
||||
import android.content.Context
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.ServerRepository
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect
|
||||
import com.github.damontecres.wholphin.ui.SlimItemFields
|
||||
import com.github.damontecres.wholphin.ui.data.RowColumn
|
||||
import com.github.damontecres.wholphin.ui.launchIO
|
||||
import com.github.damontecres.wholphin.ui.main.HomePageContent
|
||||
import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
||||
import com.github.damontecres.wholphin.util.ApiRequestPager
|
||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.FavoriteWatchManager
|
||||
import com.github.damontecres.wholphin.util.GetItemsRequestHandler
|
||||
import com.github.damontecres.wholphin.util.GetNextUpRequestHandler
|
||||
import com.github.damontecres.wholphin.util.GetResumeItemsRequestHandler
|
||||
|
|
@ -56,15 +50,15 @@ class RecommendedTvShowViewModel
|
|||
private val api: ApiClient,
|
||||
private val serverRepository: ServerRepository,
|
||||
@Assisted val parentId: UUID,
|
||||
) : ViewModel() {
|
||||
navigationManager: NavigationManager,
|
||||
favoriteWatchManager: FavoriteWatchManager,
|
||||
) : RecommendedViewModel(navigationManager, favoriteWatchManager) {
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(parentId: UUID): RecommendedTvShowViewModel
|
||||
}
|
||||
|
||||
val loading = MutableLiveData<LoadingState>(LoadingState.Loading)
|
||||
|
||||
val rows =
|
||||
override val rows =
|
||||
MutableStateFlow<MutableList<HomeRowLoadingState>>(
|
||||
rowTitles
|
||||
.map {
|
||||
|
|
@ -74,7 +68,7 @@ class RecommendedTvShowViewModel
|
|||
}.toMutableList(),
|
||||
)
|
||||
|
||||
fun init() {
|
||||
override fun init() {
|
||||
viewModelScope.launch(Dispatchers.IO + ExceptionHandler()) {
|
||||
try {
|
||||
val resumeItemsRequest =
|
||||
|
|
@ -240,7 +234,6 @@ class RecommendedTvShowViewModel
|
|||
fun RecommendedTvShow(
|
||||
preferences: UserPreferences,
|
||||
parentId: UUID,
|
||||
onClickItem: (BaseItem) -> Unit,
|
||||
onFocusPosition: (RowColumn) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: RecommendedTvShowViewModel =
|
||||
|
|
@ -248,28 +241,10 @@ fun RecommendedTvShow(
|
|||
creationCallback = { it.create(parentId) },
|
||||
),
|
||||
) {
|
||||
OneTimeLaunchedEffect {
|
||||
viewModel.init()
|
||||
}
|
||||
|
||||
val loading by viewModel.loading.observeAsState(LoadingState.Loading)
|
||||
val rows by viewModel.rows.collectAsState()
|
||||
|
||||
when (val state = loading) {
|
||||
is LoadingState.Error -> ErrorMessage(state)
|
||||
|
||||
LoadingState.Loading,
|
||||
LoadingState.Pending,
|
||||
-> LoadingPage()
|
||||
|
||||
LoadingState.Success ->
|
||||
HomePageContent(
|
||||
homeRows = rows,
|
||||
onClickItem = onClickItem,
|
||||
onLongClickItem = {},
|
||||
onFocusPosition = onFocusPosition,
|
||||
showClock = preferences.appPreferences.interfacePreferences.showClock,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
RecommendedContent(
|
||||
preferences = preferences,
|
||||
viewModel = viewModel,
|
||||
onFocusPosition = onFocusPosition,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,8 +71,8 @@ private const val DEBUG = false
|
|||
@Composable
|
||||
fun CardGrid(
|
||||
pager: List<BaseItem?>,
|
||||
onClickItem: (BaseItem) -> Unit,
|
||||
onLongClickItem: (BaseItem) -> Unit,
|
||||
onClickItem: (Int, BaseItem) -> Unit,
|
||||
onLongClickItem: (Int, BaseItem) -> Unit,
|
||||
letterPosition: suspend (Char) -> Int,
|
||||
gridFocusRequester: FocusRequester,
|
||||
showJumpButtons: Boolean,
|
||||
|
|
@ -270,10 +270,10 @@ fun CardGrid(
|
|||
{
|
||||
if (item != null) {
|
||||
focusedIndex = index
|
||||
onClickItem.invoke(item)
|
||||
onClickItem.invoke(index, item)
|
||||
}
|
||||
},
|
||||
{ if (item != null) onLongClickItem.invoke(item) },
|
||||
{ if (item != null) onLongClickItem.invoke(index, item) },
|
||||
mod
|
||||
.ifElse(index == 0, Modifier.focusRequester(zeroFocus))
|
||||
.onFocusChanged { focusState ->
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ fun CollectionFolderGeneric(
|
|||
var showHeader by remember { mutableStateOf(true) }
|
||||
CollectionFolderGrid(
|
||||
preferences = preferences,
|
||||
onClickItem = { preferencesViewModel.navigationManager.navigateTo(it.destination()) },
|
||||
onClickItem = { _, item -> preferencesViewModel.navigationManager.navigateTo(item.destination()) },
|
||||
itemId = itemId,
|
||||
initialFilter = filter,
|
||||
showTitle = showHeader,
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ fun CollectionFolderLiveTv(
|
|||
logTab("livetv", selectedTabIndex)
|
||||
viewModel.saveRememberedTab(preferences, destination.itemId, selectedTabIndex)
|
||||
}
|
||||
val onClickItem = { item: BaseItem ->
|
||||
val onClickItem = { position: Int, item: BaseItem ->
|
||||
viewModel.navigationManager.navigateTo(item.destination())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.data.model.GetItemsFilter
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.ui.components.CollectionFolderGrid
|
||||
|
|
@ -69,10 +68,6 @@ fun CollectionFolderMovie(
|
|||
|
||||
var showHeader by rememberSaveable { mutableStateOf(true) }
|
||||
|
||||
val onClickItem = { item: BaseItem ->
|
||||
preferencesViewModel.navigationManager.navigateTo(item.destination())
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) { focusRequester.tryRequestFocus() }
|
||||
Column(
|
||||
modifier = modifier,
|
||||
|
|
@ -96,7 +91,6 @@ fun CollectionFolderMovie(
|
|||
0 -> {
|
||||
RecommendedMovie(
|
||||
preferences = preferences,
|
||||
onClickItem = onClickItem,
|
||||
parentId = destination.itemId,
|
||||
onFocusPosition = { pos ->
|
||||
showHeader = pos.row < 1
|
||||
|
|
@ -111,7 +105,9 @@ fun CollectionFolderMovie(
|
|||
1 -> {
|
||||
CollectionFolderGrid(
|
||||
preferences = preferences,
|
||||
onClickItem = onClickItem,
|
||||
onClickItem = { _, item ->
|
||||
preferencesViewModel.navigationManager.navigateTo(item.destination())
|
||||
},
|
||||
itemId = destination.itemId,
|
||||
initialFilter =
|
||||
GetItemsFilter(
|
||||
|
|
@ -133,7 +129,9 @@ fun CollectionFolderMovie(
|
|||
2 -> {
|
||||
CollectionFolderGrid(
|
||||
preferences = preferences,
|
||||
onClickItem = onClickItem,
|
||||
onClickItem = { _, item ->
|
||||
preferencesViewModel.navigationManager.navigateTo(item.destination())
|
||||
},
|
||||
itemId = destination.itemId,
|
||||
initialFilter =
|
||||
GetItemsFilter(
|
||||
|
|
|
|||
|
|
@ -95,7 +95,6 @@ fun CollectionFolderTv(
|
|||
RecommendedTvShow(
|
||||
preferences = preferences,
|
||||
parentId = destination.itemId,
|
||||
onClickItem = onClickItem,
|
||||
onFocusPosition = { pos ->
|
||||
showHeader = pos.row < 1
|
||||
},
|
||||
|
|
@ -125,7 +124,9 @@ fun CollectionFolderTv(
|
|||
positionCallback = { columns, position ->
|
||||
showHeader = position < columns
|
||||
},
|
||||
onClickItem = onClickItem,
|
||||
onClickItem = { _, item ->
|
||||
preferencesViewModel.navigationManager.navigateTo(item.destination())
|
||||
},
|
||||
)
|
||||
}
|
||||
2 -> {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import androidx.compose.material.icons.filled.Refresh
|
|||
import androidx.compose.ui.graphics.Color
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.data.model.Person
|
||||
import com.github.damontecres.wholphin.ui.components.DialogItem
|
||||
import com.github.damontecres.wholphin.ui.letNotEmpty
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
|
|
@ -20,6 +21,13 @@ import org.jellyfin.sdk.model.serializer.toUUIDOrNull
|
|||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
data class MoreDialogActions(
|
||||
val navigateTo: (Destination) -> Unit,
|
||||
var onClickWatch: (UUID, Boolean) -> Unit,
|
||||
var onClickFavorite: (UUID, Boolean) -> Unit,
|
||||
var onClickAddPlaylist: (UUID) -> Unit,
|
||||
)
|
||||
|
||||
/**
|
||||
* Build the [DialogItem]s when clicking "More"
|
||||
*
|
||||
|
|
@ -43,12 +51,9 @@ fun buildMoreDialogItems(
|
|||
sourceId: UUID?,
|
||||
watched: Boolean,
|
||||
favorite: Boolean,
|
||||
navigateTo: (Destination) -> Unit,
|
||||
onClickWatch: (Boolean) -> Unit,
|
||||
onClickFavorite: (Boolean) -> Unit,
|
||||
actions: MoreDialogActions,
|
||||
onChooseVersion: () -> Unit,
|
||||
onChooseTracks: (MediaStreamType) -> Unit,
|
||||
onClickAddPlaylist: () -> Unit,
|
||||
): List<DialogItem> =
|
||||
buildList {
|
||||
add(
|
||||
|
|
@ -57,7 +62,7 @@ fun buildMoreDialogItems(
|
|||
Icons.Default.PlayArrow,
|
||||
iconColor = Color.Green.copy(alpha = .8f),
|
||||
) {
|
||||
navigateTo(
|
||||
actions.navigateTo(
|
||||
Destination.Playback(
|
||||
item.id,
|
||||
item.resumeMs ?: 0L,
|
||||
|
|
@ -119,7 +124,7 @@ fun buildMoreDialogItems(
|
|||
text = R.string.add_to_playlist,
|
||||
iconStringRes = R.string.fa_list_ul,
|
||||
) {
|
||||
onClickAddPlaylist.invoke()
|
||||
actions.onClickAddPlaylist.invoke(item.id)
|
||||
},
|
||||
)
|
||||
add(
|
||||
|
|
@ -127,7 +132,7 @@ fun buildMoreDialogItems(
|
|||
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(!watched)
|
||||
actions.onClickWatch.invoke(item.id, !watched)
|
||||
},
|
||||
)
|
||||
add(
|
||||
|
|
@ -136,7 +141,7 @@ fun buildMoreDialogItems(
|
|||
iconStringRes = R.string.fa_heart,
|
||||
iconColor = if (favorite) Color.Red else Color.Unspecified,
|
||||
) {
|
||||
onClickFavorite.invoke(!favorite)
|
||||
actions.onClickFavorite.invoke(item.id, !favorite)
|
||||
},
|
||||
)
|
||||
series?.let {
|
||||
|
|
@ -145,7 +150,7 @@ fun buildMoreDialogItems(
|
|||
context.getString(R.string.go_to_series),
|
||||
Icons.AutoMirrored.Filled.ArrowForward,
|
||||
) {
|
||||
navigateTo(
|
||||
actions.navigateTo(
|
||||
Destination.MediaItem(
|
||||
series.id,
|
||||
BaseItemKind.SERIES,
|
||||
|
|
@ -160,7 +165,7 @@ fun buildMoreDialogItems(
|
|||
context.getString(R.string.play_with_transcoding),
|
||||
Icons.Default.PlayArrow,
|
||||
) {
|
||||
navigateTo(
|
||||
actions.navigateTo(
|
||||
Destination.Playback(
|
||||
item.id,
|
||||
item.resumeMs ?: 0L,
|
||||
|
|
@ -179,10 +184,7 @@ fun buildMoreDialogItemsForHome(
|
|||
playbackPosition: Duration,
|
||||
watched: Boolean,
|
||||
favorite: Boolean,
|
||||
navigateTo: (Destination) -> Unit,
|
||||
onClickWatch: (UUID, Boolean) -> Unit,
|
||||
onClickFavorite: (UUID, Boolean) -> Unit,
|
||||
onClickAddPlaylist: (UUID) -> Unit,
|
||||
actions: MoreDialogActions,
|
||||
): List<DialogItem> =
|
||||
buildList {
|
||||
val itemId = item.id
|
||||
|
|
@ -191,7 +193,7 @@ fun buildMoreDialogItemsForHome(
|
|||
context.getString(R.string.go_to),
|
||||
Icons.Default.ArrowForward,
|
||||
) {
|
||||
navigateTo(item.destination())
|
||||
actions.navigateTo(item.destination())
|
||||
},
|
||||
)
|
||||
if (item.type in supportedPlayableTypes) {
|
||||
|
|
@ -202,7 +204,7 @@ fun buildMoreDialogItemsForHome(
|
|||
Icons.Default.PlayArrow,
|
||||
iconColor = Color.Green.copy(alpha = .8f),
|
||||
) {
|
||||
navigateTo(
|
||||
actions.navigateTo(
|
||||
Destination.Playback(
|
||||
itemId,
|
||||
playbackPosition.inWholeMilliseconds,
|
||||
|
|
@ -216,7 +218,7 @@ fun buildMoreDialogItemsForHome(
|
|||
Icons.Default.Refresh,
|
||||
// iconColor = Color.Green.copy(alpha = .8f),
|
||||
) {
|
||||
navigateTo(
|
||||
actions.navigateTo(
|
||||
Destination.Playback(
|
||||
itemId,
|
||||
0L,
|
||||
|
|
@ -231,7 +233,7 @@ fun buildMoreDialogItemsForHome(
|
|||
Icons.Default.PlayArrow,
|
||||
iconColor = Color.Green.copy(alpha = .8f),
|
||||
) {
|
||||
navigateTo(
|
||||
actions.navigateTo(
|
||||
Destination.Playback(
|
||||
itemId,
|
||||
0L,
|
||||
|
|
@ -246,7 +248,7 @@ fun buildMoreDialogItemsForHome(
|
|||
text = R.string.add_to_playlist,
|
||||
iconStringRes = R.string.fa_list_ul,
|
||||
) {
|
||||
onClickAddPlaylist.invoke(itemId)
|
||||
actions.onClickAddPlaylist.invoke(itemId)
|
||||
},
|
||||
)
|
||||
add(
|
||||
|
|
@ -254,7 +256,7 @@ fun buildMoreDialogItemsForHome(
|
|||
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)
|
||||
actions.onClickWatch.invoke(itemId, !watched)
|
||||
},
|
||||
)
|
||||
add(
|
||||
|
|
@ -263,7 +265,7 @@ fun buildMoreDialogItemsForHome(
|
|||
iconStringRes = R.string.fa_heart,
|
||||
iconColor = if (favorite) Color.Red else Color.Unspecified,
|
||||
) {
|
||||
onClickFavorite.invoke(itemId, !favorite)
|
||||
actions.onClickFavorite.invoke(itemId, !favorite)
|
||||
},
|
||||
)
|
||||
seriesId?.let {
|
||||
|
|
@ -272,7 +274,7 @@ fun buildMoreDialogItemsForHome(
|
|||
context.getString(R.string.go_to_series),
|
||||
Icons.AutoMirrored.Filled.ArrowForward,
|
||||
) {
|
||||
navigateTo(
|
||||
actions.navigateTo(
|
||||
Destination.MediaItem(
|
||||
it,
|
||||
BaseItemKind.SERIES,
|
||||
|
|
@ -282,3 +284,31 @@ fun buildMoreDialogItemsForHome(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun buildMoreDialogItemsForPerson(
|
||||
context: Context,
|
||||
person: Person,
|
||||
// favorite: Boolean,
|
||||
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))
|
||||
},
|
||||
)
|
||||
// TODO need way to get person's favorite status
|
||||
// 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(itemId, !favorite)
|
||||
// },
|
||||
// )
|
||||
}
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ fun FavoritesPage(
|
|||
0 -> {
|
||||
CollectionFolderGrid(
|
||||
preferences = preferences,
|
||||
onClickItem = onClickItem,
|
||||
onClickItem = { _, item -> onClickItem.invoke(item) },
|
||||
itemId = "${NavDrawerItem.Favorites.id}_movies",
|
||||
initialFilter =
|
||||
GetItemsFilter(
|
||||
|
|
@ -122,7 +122,7 @@ fun FavoritesPage(
|
|||
1 -> {
|
||||
CollectionFolderGrid(
|
||||
preferences = preferences,
|
||||
onClickItem = onClickItem,
|
||||
onClickItem = { _, item -> onClickItem.invoke(item) },
|
||||
itemId = "${NavDrawerItem.Favorites.id}_series",
|
||||
initialFilter =
|
||||
GetItemsFilter(
|
||||
|
|
@ -145,7 +145,7 @@ fun FavoritesPage(
|
|||
2 -> {
|
||||
CollectionFolderGrid(
|
||||
preferences = preferences,
|
||||
onClickItem = onClickItem,
|
||||
onClickItem = { _, item -> onClickItem.invoke(item) },
|
||||
itemId = "${NavDrawerItem.Favorites.id}_episodes",
|
||||
initialFilter =
|
||||
GetItemsFilter(
|
||||
|
|
|
|||
|
|
@ -26,9 +26,6 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Shadow
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
|
@ -186,8 +183,8 @@ fun PersonPage(
|
|||
movies = movies,
|
||||
series = series,
|
||||
episodes = episodes,
|
||||
onClickItem = {
|
||||
viewModel.navigationManager.navigateTo(it.destination())
|
||||
onClickItem = { index, item ->
|
||||
viewModel.navigationManager.navigateTo(item.destination())
|
||||
},
|
||||
overviewOnClick = { showOverviewDialog = true },
|
||||
modifier = modifier,
|
||||
|
|
@ -226,7 +223,7 @@ fun PersonPageContent(
|
|||
movies: RowLoadingState,
|
||||
series: RowLoadingState,
|
||||
episodes: RowLoadingState,
|
||||
onClickItem: (BaseItem) -> Unit,
|
||||
onClickItem: (Int, BaseItem) -> Unit,
|
||||
overviewOnClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -64,14 +64,18 @@ import com.github.damontecres.wholphin.ui.components.DialogPopup
|
|||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||
import com.github.damontecres.wholphin.ui.components.ExpandablePlayButtons
|
||||
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
||||
import com.github.damontecres.wholphin.ui.components.Optional
|
||||
import com.github.damontecres.wholphin.ui.components.chooseStream
|
||||
import com.github.damontecres.wholphin.ui.components.chooseVersionParams
|
||||
import com.github.damontecres.wholphin.ui.data.AddPlaylistViewModel
|
||||
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialog
|
||||
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialogInfo
|
||||
import com.github.damontecres.wholphin.ui.detail.MoreDialogActions
|
||||
import com.github.damontecres.wholphin.ui.detail.PlaylistDialog
|
||||
import com.github.damontecres.wholphin.ui.detail.PlaylistLoadingState
|
||||
import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItems
|
||||
import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItemsForHome
|
||||
import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItemsForPerson
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.ui.rememberInt
|
||||
|
|
@ -83,6 +87,7 @@ import org.jellyfin.sdk.model.api.BaseItemKind
|
|||
import org.jellyfin.sdk.model.api.MediaType
|
||||
import org.jellyfin.sdk.model.extensions.ticks
|
||||
import org.jellyfin.sdk.model.serializer.toUUID
|
||||
import java.util.UUID
|
||||
import kotlin.time.Duration
|
||||
|
||||
@Composable
|
||||
|
|
@ -112,9 +117,24 @@ fun MovieDetails(
|
|||
var overviewDialog by remember { mutableStateOf<ItemDetailsDialogInfo?>(null) }
|
||||
var moreDialog by remember { mutableStateOf<DialogParams?>(null) }
|
||||
var chooseVersion by remember { mutableStateOf<DialogParams?>(null) }
|
||||
var showPlaylistDialog by remember { mutableStateOf(false) }
|
||||
var showPlaylistDialog by remember { mutableStateOf<Optional<UUID>>(Optional.absent()) }
|
||||
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
||||
|
||||
val moreActions =
|
||||
MoreDialogActions(
|
||||
navigateTo = viewModel::navigateTo,
|
||||
onClickWatch = { itemId, watched ->
|
||||
viewModel.setWatched(itemId, watched)
|
||||
},
|
||||
onClickFavorite = { itemId, favorite ->
|
||||
viewModel.setFavorite(itemId, favorite)
|
||||
},
|
||||
onClickAddPlaylist = { itemId ->
|
||||
playlistViewModel.loadPlaylists(MediaType.VIDEO)
|
||||
showPlaylistDialog.makePresent(itemId)
|
||||
},
|
||||
)
|
||||
|
||||
when (val state = loading) {
|
||||
is LoadingState.Error -> ErrorMessage(state)
|
||||
LoadingState.Loading,
|
||||
|
|
@ -139,8 +159,8 @@ fun MovieDetails(
|
|||
chapters = chapters,
|
||||
trailers = trailers,
|
||||
similar = similar,
|
||||
onClickItem = {
|
||||
viewModel.navigateTo(it.destination())
|
||||
onClickItem = { index, item ->
|
||||
viewModel.navigateTo(item.destination())
|
||||
},
|
||||
onClickPerson = {
|
||||
viewModel.navigateTo(
|
||||
|
|
@ -180,9 +200,7 @@ fun MovieDetails(
|
|||
favorite = movie.data.userData?.isFavorite ?: false,
|
||||
series = null,
|
||||
sourceId = chosenStreams?.sourceId,
|
||||
navigateTo = viewModel::navigateTo,
|
||||
onClickWatch = viewModel::setWatched,
|
||||
onClickFavorite = viewModel::setFavorite,
|
||||
actions = moreActions,
|
||||
onChooseVersion = {
|
||||
chooseVersion =
|
||||
chooseVersionParams(
|
||||
|
|
@ -218,18 +236,46 @@ fun MovieDetails(
|
|||
)
|
||||
}
|
||||
},
|
||||
onClickAddPlaylist = {
|
||||
playlistViewModel.loadPlaylists(MediaType.VIDEO)
|
||||
showPlaylistDialog = true
|
||||
},
|
||||
),
|
||||
)
|
||||
},
|
||||
watchOnClick = {
|
||||
viewModel.setWatched((movie.data.userData?.played ?: false).not())
|
||||
viewModel.setWatched(movie.id, !movie.played)
|
||||
},
|
||||
favoriteOnClick = {
|
||||
viewModel.setFavorite((movie.data.userData?.isFavorite ?: false).not())
|
||||
viewModel.setFavorite(movie.id, !movie.favorite)
|
||||
},
|
||||
onLongClickPerson = { index, person ->
|
||||
val items =
|
||||
buildMoreDialogItemsForPerson(
|
||||
context = context,
|
||||
person = person,
|
||||
actions = moreActions,
|
||||
)
|
||||
moreDialog =
|
||||
DialogParams(
|
||||
fromLongClick = true,
|
||||
title = person.name ?: "",
|
||||
items = items,
|
||||
)
|
||||
},
|
||||
onLongClickSimilar = { index, similar ->
|
||||
val items =
|
||||
buildMoreDialogItemsForHome(
|
||||
context = context,
|
||||
item = similar,
|
||||
seriesId = null,
|
||||
playbackPosition = similar.playbackPosition,
|
||||
watched = similar.played,
|
||||
favorite = similar.favorite,
|
||||
actions = moreActions,
|
||||
)
|
||||
moreDialog =
|
||||
DialogParams(
|
||||
fromLongClick = true,
|
||||
title = similar.title ?: "",
|
||||
items = items,
|
||||
)
|
||||
},
|
||||
trailerOnClick = { trailer ->
|
||||
when (trailer) {
|
||||
|
|
@ -283,19 +329,19 @@ fun MovieDetails(
|
|||
waitToLoad = params.fromLongClick,
|
||||
)
|
||||
}
|
||||
if (showPlaylistDialog) {
|
||||
showPlaylistDialog.compose { itemId ->
|
||||
PlaylistDialog(
|
||||
title = stringResource(R.string.add_to_playlist),
|
||||
state = playlistState,
|
||||
onDismissRequest = { showPlaylistDialog = false },
|
||||
onDismissRequest = { showPlaylistDialog.makeAbsent() },
|
||||
onClick = {
|
||||
playlistViewModel.addToPlaylist(it.id, destination.itemId)
|
||||
showPlaylistDialog = false
|
||||
playlistViewModel.addToPlaylist(it.id, itemId)
|
||||
showPlaylistDialog.makeAbsent()
|
||||
},
|
||||
createEnabled = true,
|
||||
onCreatePlaylist = {
|
||||
playlistViewModel.createPlaylistAndAddItem(it, destination.itemId)
|
||||
showPlaylistDialog = false
|
||||
playlistViewModel.createPlaylistAndAddItem(it, itemId)
|
||||
showPlaylistDialog.makeAbsent()
|
||||
},
|
||||
elevation = 3.dp,
|
||||
)
|
||||
|
|
@ -323,8 +369,10 @@ fun MovieDetailsContent(
|
|||
watchOnClick: () -> Unit,
|
||||
favoriteOnClick: () -> Unit,
|
||||
moreOnClick: () -> Unit,
|
||||
onClickItem: (BaseItem) -> Unit,
|
||||
onClickItem: (Int, BaseItem) -> Unit,
|
||||
onClickPerson: (Person) -> Unit,
|
||||
onLongClickPerson: (Int, Person) -> Unit,
|
||||
onLongClickSimilar: (Int, BaseItem) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
|
|
@ -422,7 +470,10 @@ fun MovieDetailsContent(
|
|||
position = PEOPLE_ROW
|
||||
onClickPerson.invoke(it)
|
||||
},
|
||||
onLongClick = {},
|
||||
onLongClick = { index, person ->
|
||||
position = PEOPLE_ROW
|
||||
onLongClickPerson.invoke(index, person)
|
||||
},
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
|
|
@ -466,11 +517,14 @@ fun MovieDetailsContent(
|
|||
ItemRow(
|
||||
title = stringResource(R.string.more_like_this),
|
||||
items = similar,
|
||||
onClickItem = {
|
||||
onClickItem = { index, item ->
|
||||
position = SIMILAR_ROW
|
||||
onClickItem.invoke(it)
|
||||
onClickItem.invoke(index, item)
|
||||
},
|
||||
onLongClickItem = { index, similar ->
|
||||
position = SIMILAR_ROW
|
||||
onLongClickSimilar.invoke(index, similar)
|
||||
},
|
||||
onLongClickItem = {},
|
||||
cardContent = { index, item, mod, onClick, onLongClick ->
|
||||
SeasonCard(
|
||||
item = item,
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import com.github.damontecres.wholphin.ui.nav.Destination
|
|||
import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.setValueOnMain
|
||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.FavoriteWatchManager
|
||||
import com.github.damontecres.wholphin.util.LoadingExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
import com.github.damontecres.wholphin.util.ThemeSongPlayer
|
||||
|
|
@ -39,7 +40,6 @@ import kotlinx.coroutines.launch
|
|||
import kotlinx.coroutines.withContext
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
import org.jellyfin.sdk.api.client.extensions.libraryApi
|
||||
import org.jellyfin.sdk.api.client.extensions.playStateApi
|
||||
import org.jellyfin.sdk.api.client.extensions.userLibraryApi
|
||||
import org.jellyfin.sdk.model.api.MediaStreamType
|
||||
import org.jellyfin.sdk.model.api.request.GetSimilarItemsRequest
|
||||
|
|
@ -55,6 +55,7 @@ class MovieViewModel
|
|||
val serverRepository: ServerRepository,
|
||||
val itemPlaybackRepository: ItemPlaybackRepository,
|
||||
private val themeSongPlayer: ThemeSongPlayer,
|
||||
private val favoriteWatchManager: FavoriteWatchManager,
|
||||
@Assisted val itemId: UUID,
|
||||
) : ViewModel() {
|
||||
@AssistedFactory
|
||||
|
|
@ -157,25 +158,21 @@ class MovieViewModel
|
|||
}
|
||||
}
|
||||
|
||||
fun setWatched(played: Boolean) =
|
||||
viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
|
||||
if (played) {
|
||||
api.playStateApi.markPlayedItem(itemId)
|
||||
} else {
|
||||
api.playStateApi.markUnplayedItem(itemId)
|
||||
}
|
||||
fetchAndSetItem()
|
||||
}
|
||||
fun setWatched(
|
||||
itemId: UUID,
|
||||
played: Boolean,
|
||||
) = viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
|
||||
favoriteWatchManager.setWatched(itemId, played)
|
||||
fetchAndSetItem()
|
||||
}
|
||||
|
||||
fun setFavorite(favorite: Boolean) =
|
||||
viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
|
||||
if (favorite) {
|
||||
api.userLibraryApi.markFavoriteItem(itemId)
|
||||
} else {
|
||||
api.userLibraryApi.unmarkFavoriteItem(itemId)
|
||||
}
|
||||
fetchAndSetItem()
|
||||
}
|
||||
fun setFavorite(
|
||||
itemId: UUID,
|
||||
favorite: Boolean,
|
||||
) = viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
|
||||
favoriteWatchManager.setFavorite(itemId, favorite)
|
||||
fetchAndSetItem()
|
||||
}
|
||||
|
||||
fun savePlayVersion(
|
||||
item: BaseItem,
|
||||
|
|
|
|||
|
|
@ -60,10 +60,17 @@ import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
|||
import com.github.damontecres.wholphin.ui.components.ExpandableFaButton
|
||||
import com.github.damontecres.wholphin.ui.components.ExpandablePlayButton
|
||||
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
||||
import com.github.damontecres.wholphin.ui.components.Optional
|
||||
import com.github.damontecres.wholphin.ui.components.OverviewText
|
||||
import com.github.damontecres.wholphin.ui.components.SimpleStarRating
|
||||
import com.github.damontecres.wholphin.ui.data.AddPlaylistViewModel
|
||||
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialog
|
||||
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialogInfo
|
||||
import com.github.damontecres.wholphin.ui.detail.MoreDialogActions
|
||||
import com.github.damontecres.wholphin.ui.detail.PlaylistDialog
|
||||
import com.github.damontecres.wholphin.ui.detail.PlaylistLoadingState
|
||||
import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItemsForHome
|
||||
import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItemsForPerson
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
import com.github.damontecres.wholphin.ui.letNotEmpty
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
|
|
@ -74,7 +81,9 @@ import com.github.damontecres.wholphin.util.ExceptionHandler
|
|||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import org.jellyfin.sdk.model.api.MediaType
|
||||
import org.jellyfin.sdk.model.extensions.ticks
|
||||
import java.util.UUID
|
||||
import kotlin.time.Duration
|
||||
|
||||
@Composable
|
||||
|
|
@ -83,6 +92,7 @@ fun SeriesDetails(
|
|||
destination: Destination.MediaItem,
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: SeriesViewModel = hiltViewModel(),
|
||||
playlistViewModel: AddPlaylistViewModel = hiltViewModel(),
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
LaunchedEffect(Unit) {
|
||||
|
|
@ -98,6 +108,8 @@ fun SeriesDetails(
|
|||
var overviewDialog by remember { mutableStateOf<ItemDetailsDialogInfo?>(null) }
|
||||
var showWatchConfirmation by remember { mutableStateOf(false) }
|
||||
var seasonDialog by remember { mutableStateOf<DialogParams?>(null) }
|
||||
var showPlaylistDialog by remember { mutableStateOf<Optional<UUID>>(Optional.absent()) }
|
||||
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
||||
|
||||
when (val state = loading) {
|
||||
is LoadingState.Error -> ErrorMessage(state)
|
||||
|
|
@ -126,7 +138,9 @@ fun SeriesDetails(
|
|||
played = played,
|
||||
favorite = item.data.userData?.isFavorite ?: false,
|
||||
modifier = modifier,
|
||||
onClickItem = { viewModel.navigateTo(it.destination()) },
|
||||
onClickItem = { index, item ->
|
||||
viewModel.navigateTo(item.destination())
|
||||
},
|
||||
onClickPerson = {
|
||||
viewModel.navigateTo(
|
||||
Destination.MediaItem(
|
||||
|
|
@ -135,7 +149,7 @@ fun SeriesDetails(
|
|||
),
|
||||
)
|
||||
},
|
||||
onLongClickItem = { season ->
|
||||
onLongClickItem = { index, season ->
|
||||
seasonDialog =
|
||||
buildDialogForSeason(
|
||||
context = context,
|
||||
|
|
@ -160,6 +174,20 @@ fun SeriesDetails(
|
|||
val favorite = item.data.userData?.isFavorite ?: false
|
||||
viewModel.setFavorite(item.id, !favorite, null)
|
||||
},
|
||||
moreActions =
|
||||
MoreDialogActions(
|
||||
navigateTo = { viewModel.navigateTo(it) },
|
||||
onClickWatch = { itemId, played ->
|
||||
viewModel.setWatched(itemId, played, null)
|
||||
},
|
||||
onClickFavorite = { itemId, played ->
|
||||
viewModel.setFavorite(itemId, played, null)
|
||||
},
|
||||
onClickAddPlaylist = { itemId ->
|
||||
playlistViewModel.loadPlaylists(MediaType.VIDEO)
|
||||
showPlaylistDialog.makePresent(itemId)
|
||||
},
|
||||
),
|
||||
)
|
||||
if (showWatchConfirmation) {
|
||||
ConfirmDialog(
|
||||
|
|
@ -194,6 +222,23 @@ fun SeriesDetails(
|
|||
onDismissRequest = { seasonDialog = null },
|
||||
)
|
||||
}
|
||||
showPlaylistDialog.compose { itemId ->
|
||||
PlaylistDialog(
|
||||
title = stringResource(R.string.add_to_playlist),
|
||||
state = playlistState,
|
||||
onDismissRequest = { showPlaylistDialog.makeAbsent() },
|
||||
onClick = {
|
||||
playlistViewModel.addToPlaylist(it.id, itemId)
|
||||
showPlaylistDialog.makeAbsent()
|
||||
},
|
||||
createEnabled = true,
|
||||
onCreatePlaylist = {
|
||||
playlistViewModel.createPlaylistAndAddItem(it, itemId)
|
||||
showPlaylistDialog.makeAbsent()
|
||||
},
|
||||
elevation = 3.dp,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private const val HEADER_ROW = 0
|
||||
|
|
@ -210,15 +255,17 @@ fun SeriesDetailsContent(
|
|||
people: List<Person>,
|
||||
played: Boolean,
|
||||
favorite: Boolean,
|
||||
onClickItem: (BaseItem) -> Unit,
|
||||
onClickItem: (Int, BaseItem) -> Unit,
|
||||
onClickPerson: (Person) -> Unit,
|
||||
onLongClickItem: (BaseItem) -> Unit,
|
||||
onLongClickItem: (Int, BaseItem) -> Unit,
|
||||
overviewOnClick: () -> Unit,
|
||||
playOnClick: () -> Unit,
|
||||
watchOnClick: () -> Unit,
|
||||
favoriteOnClick: () -> Unit,
|
||||
moreActions: MoreDialogActions,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val bringIntoViewRequester = remember { BringIntoViewRequester() }
|
||||
|
||||
|
|
@ -227,6 +274,7 @@ fun SeriesDetailsContent(
|
|||
LaunchedEffect(Unit) {
|
||||
focusRequesters.getOrNull(position)?.tryRequestFocus()
|
||||
}
|
||||
var moreDialog by remember { mutableStateOf<DialogParams?>(null) }
|
||||
|
||||
Box(
|
||||
modifier = modifier,
|
||||
|
|
@ -349,11 +397,14 @@ fun SeriesDetailsContent(
|
|||
ItemRow(
|
||||
title = stringResource(R.string.tv_seasons),
|
||||
items = seasons,
|
||||
onClickItem = {
|
||||
onClickItem = { index, item ->
|
||||
position = SEASONS_ROW
|
||||
onClickItem.invoke(it)
|
||||
onClickItem.invoke(index, item)
|
||||
},
|
||||
onLongClickItem = { index, item ->
|
||||
position = SEASONS_ROW
|
||||
onLongClickItem.invoke(index, item)
|
||||
},
|
||||
onLongClickItem = onLongClickItem,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
|
|
@ -379,7 +430,21 @@ fun SeriesDetailsContent(
|
|||
position = PEOPLE_ROW
|
||||
onClickPerson.invoke(it)
|
||||
},
|
||||
onLongClick = {},
|
||||
onLongClick = { index, person ->
|
||||
position = PEOPLE_ROW
|
||||
val items =
|
||||
buildMoreDialogItemsForPerson(
|
||||
context = context,
|
||||
person = person,
|
||||
actions = moreActions,
|
||||
)
|
||||
moreDialog =
|
||||
DialogParams(
|
||||
fromLongClick = true,
|
||||
title = person.name ?: "",
|
||||
items = items,
|
||||
)
|
||||
},
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
|
|
@ -392,11 +457,29 @@ fun SeriesDetailsContent(
|
|||
ItemRow(
|
||||
title = stringResource(R.string.more_like_this),
|
||||
items = similar,
|
||||
onClickItem = {
|
||||
onClickItem = { index, item ->
|
||||
position = SIMILAR_ROW
|
||||
onClickItem.invoke(it)
|
||||
onClickItem.invoke(index, item)
|
||||
},
|
||||
onLongClickItem = { index, item ->
|
||||
position = SIMILAR_ROW
|
||||
val items =
|
||||
buildMoreDialogItemsForHome(
|
||||
context = context,
|
||||
item = item,
|
||||
seriesId = null,
|
||||
playbackPosition = item.playbackPosition,
|
||||
watched = item.played,
|
||||
favorite = item.favorite,
|
||||
actions = moreActions,
|
||||
)
|
||||
moreDialog =
|
||||
DialogParams(
|
||||
fromLongClick = true,
|
||||
title = item.name ?: "",
|
||||
items = items,
|
||||
)
|
||||
},
|
||||
onLongClickItem = {},
|
||||
cardContent = { index, item, mod, onClick, onLongClick ->
|
||||
SeasonCard(
|
||||
item = item,
|
||||
|
|
@ -418,6 +501,16 @@ fun SeriesDetailsContent(
|
|||
}
|
||||
}
|
||||
}
|
||||
moreDialog?.let { params ->
|
||||
DialogPopup(
|
||||
showDialog = true,
|
||||
title = params.title,
|
||||
dialogItems = params.items,
|
||||
onDismissRequest = { moreDialog = null },
|
||||
dismissOnClick = true,
|
||||
waitToLoad = params.fromLongClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import com.github.damontecres.wholphin.ui.components.chooseVersionParams
|
|||
import com.github.damontecres.wholphin.ui.data.AddPlaylistViewModel
|
||||
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialog
|
||||
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialogInfo
|
||||
import com.github.damontecres.wholphin.ui.detail.MoreDialogActions
|
||||
import com.github.damontecres.wholphin.ui.detail.PlaylistDialog
|
||||
import com.github.damontecres.wholphin.ui.detail.PlaylistLoadingState
|
||||
import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItems
|
||||
|
|
@ -196,29 +197,28 @@ fun SeriesOverview(
|
|||
favorite = ep.data.userData?.isFavorite ?: false,
|
||||
series = series,
|
||||
sourceId = chosenStreams?.sourceId,
|
||||
navigateTo = viewModel::navigateTo,
|
||||
onClickWatch = { played ->
|
||||
episodeList
|
||||
?.getOrNull(position.episodeRowIndex)
|
||||
?.let {
|
||||
actions =
|
||||
MoreDialogActions(
|
||||
navigateTo = viewModel::navigateTo,
|
||||
onClickWatch = { itemId, watched ->
|
||||
viewModel.setWatched(
|
||||
it.id,
|
||||
played,
|
||||
itemId,
|
||||
watched,
|
||||
position.episodeRowIndex,
|
||||
)
|
||||
}
|
||||
},
|
||||
onClickFavorite = { favorite ->
|
||||
episodeList
|
||||
?.getOrNull(position.episodeRowIndex)
|
||||
?.let {
|
||||
},
|
||||
onClickFavorite = { itemId, favorite ->
|
||||
viewModel.setFavorite(
|
||||
it.id,
|
||||
itemId,
|
||||
favorite,
|
||||
position.episodeRowIndex,
|
||||
)
|
||||
}
|
||||
},
|
||||
},
|
||||
onClickAddPlaylist = {
|
||||
playlistViewModel.loadPlaylists(MediaType.VIDEO)
|
||||
showPlaylistDialog = it
|
||||
},
|
||||
),
|
||||
onChooseVersion = {
|
||||
chooseVersion =
|
||||
chooseVersionParams(
|
||||
|
|
@ -254,10 +254,6 @@ fun SeriesOverview(
|
|||
)
|
||||
}
|
||||
},
|
||||
onClickAddPlaylist = {
|
||||
playlistViewModel.loadPlaylists(MediaType.VIDEO)
|
||||
showPlaylistDialog = ep.id
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import com.github.damontecres.wholphin.ui.setValueOnMain
|
|||
import com.github.damontecres.wholphin.ui.showToast
|
||||
import com.github.damontecres.wholphin.util.ApiRequestPager
|
||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.FavoriteWatchManager
|
||||
import com.github.damontecres.wholphin.util.GetEpisodesRequestHandler
|
||||
import com.github.damontecres.wholphin.util.GetItemsRequestHandler
|
||||
import com.github.damontecres.wholphin.util.LoadingExceptionHandler
|
||||
|
|
@ -36,9 +37,7 @@ import kotlinx.coroutines.launch
|
|||
import kotlinx.coroutines.withContext
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
import org.jellyfin.sdk.api.client.extensions.libraryApi
|
||||
import org.jellyfin.sdk.api.client.extensions.playStateApi
|
||||
import org.jellyfin.sdk.api.client.extensions.tvShowsApi
|
||||
import org.jellyfin.sdk.api.client.extensions.userLibraryApi
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import org.jellyfin.sdk.model.api.ItemFields
|
||||
import org.jellyfin.sdk.model.api.ItemSortBy
|
||||
|
|
@ -61,6 +60,7 @@ class SeriesViewModel
|
|||
private val navigationManager: NavigationManager,
|
||||
private val itemPlaybackRepository: ItemPlaybackRepository,
|
||||
private val themeSongPlayer: ThemeSongPlayer,
|
||||
private val favoriteWatchManager: FavoriteWatchManager,
|
||||
) : ItemViewModel(api) {
|
||||
private lateinit var seriesId: UUID
|
||||
private lateinit var prefs: UserPreferences
|
||||
|
|
@ -240,11 +240,7 @@ class SeriesViewModel
|
|||
played: Boolean,
|
||||
listIndex: Int?,
|
||||
) = viewModelScope.launch(Dispatchers.IO + ExceptionHandler()) {
|
||||
if (played) {
|
||||
api.playStateApi.markPlayedItem(itemId)
|
||||
} else {
|
||||
api.playStateApi.markUnplayedItem(itemId)
|
||||
}
|
||||
favoriteWatchManager.setWatched(itemId, played)
|
||||
listIndex?.let {
|
||||
refreshEpisode(itemId, listIndex)
|
||||
}
|
||||
|
|
@ -255,11 +251,7 @@ class SeriesViewModel
|
|||
favorite: Boolean,
|
||||
listIndex: Int?,
|
||||
) = viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
|
||||
if (favorite) {
|
||||
api.userLibraryApi.markFavoriteItem(itemId)
|
||||
} else {
|
||||
api.userLibraryApi.unmarkFavoriteItem(itemId)
|
||||
}
|
||||
favoriteWatchManager.setFavorite(itemId, favorite)
|
||||
if (listIndex != null) {
|
||||
refreshEpisode(itemId, listIndex)
|
||||
} else {
|
||||
|
|
@ -279,11 +271,7 @@ class SeriesViewModel
|
|||
|
||||
fun setWatchedSeries(played: Boolean) =
|
||||
viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
|
||||
if (played) {
|
||||
api.playStateApi.markPlayedItem(seriesId)
|
||||
} else {
|
||||
api.playStateApi.markUnplayedItem(seriesId)
|
||||
}
|
||||
favoriteWatchManager.setWatched(seriesId, played)
|
||||
val series = fetchItem(seriesId)
|
||||
val seasons = getSeasons(series)
|
||||
this@SeriesViewModel.seasons.setValueOnMain(seasons)
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ import com.github.damontecres.wholphin.ui.components.LoadingPage
|
|||
import com.github.damontecres.wholphin.ui.data.AddPlaylistViewModel
|
||||
import com.github.damontecres.wholphin.ui.data.RowColumn
|
||||
import com.github.damontecres.wholphin.ui.data.RowColumnSaver
|
||||
import com.github.damontecres.wholphin.ui.detail.MoreDialogActions
|
||||
import com.github.damontecres.wholphin.ui.detail.PlaylistDialog
|
||||
import com.github.damontecres.wholphin.ui.detail.PlaylistLoadingState
|
||||
import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItemsForHome
|
||||
|
|
@ -75,7 +76,6 @@ import org.jellyfin.sdk.model.api.BaseItemKind
|
|||
import org.jellyfin.sdk.model.api.MediaType
|
||||
import org.jellyfin.sdk.model.extensions.ticks
|
||||
import java.util.UUID
|
||||
import kotlin.time.Duration
|
||||
|
||||
@Composable
|
||||
fun HomePage(
|
||||
|
|
@ -121,37 +121,36 @@ fun HomePage(
|
|||
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
||||
HomePageContent(
|
||||
watchingRows + latestRows,
|
||||
onClickItem = {
|
||||
viewModel.navigationManager.navigateTo(it.destination())
|
||||
onClickItem = { position, item ->
|
||||
viewModel.navigationManager.navigateTo(item.destination())
|
||||
},
|
||||
onLongClickItem = {
|
||||
onLongClickItem = { position, item ->
|
||||
val dialogItems =
|
||||
buildMoreDialogItemsForHome(
|
||||
context = context,
|
||||
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)
|
||||
},
|
||||
onClickAddPlaylist = { itemId ->
|
||||
playlistViewModel.loadPlaylists(MediaType.VIDEO)
|
||||
showPlaylistDialog = itemId
|
||||
},
|
||||
item = item,
|
||||
seriesId = item.data.seriesId,
|
||||
playbackPosition = item.playbackPosition,
|
||||
watched = item.played,
|
||||
favorite = item.favorite,
|
||||
actions =
|
||||
MoreDialogActions(
|
||||
navigateTo = viewModel.navigationManager::navigateTo,
|
||||
onClickWatch = { itemId, played ->
|
||||
viewModel.setWatched(itemId, played)
|
||||
},
|
||||
onClickFavorite = { itemId, favorite ->
|
||||
viewModel.setFavorite(itemId, favorite)
|
||||
},
|
||||
onClickAddPlaylist = { itemId ->
|
||||
playlistViewModel.loadPlaylists(MediaType.VIDEO)
|
||||
showPlaylistDialog = itemId
|
||||
},
|
||||
),
|
||||
)
|
||||
dialog =
|
||||
DialogParams(
|
||||
title = it.title ?: "",
|
||||
title = item.title ?: "",
|
||||
fromLongClick = true,
|
||||
items = dialogItems,
|
||||
)
|
||||
|
|
@ -190,8 +189,8 @@ fun HomePage(
|
|||
@Composable
|
||||
fun HomePageContent(
|
||||
homeRows: List<HomeRowLoadingState>,
|
||||
onClickItem: (BaseItem) -> Unit,
|
||||
onLongClickItem: (BaseItem) -> Unit,
|
||||
onClickItem: (RowColumn, BaseItem) -> Unit,
|
||||
onLongClickItem: (RowColumn, BaseItem) -> Unit,
|
||||
showClock: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
onFocusPosition: ((RowColumn) -> Unit)? = null,
|
||||
|
|
@ -317,14 +316,18 @@ fun HomePageContent(
|
|||
ItemRow(
|
||||
title = row.title,
|
||||
items = row.items,
|
||||
onClickItem = onClickItem,
|
||||
onClickItem = { index, item ->
|
||||
onClickItem.invoke(RowColumn(rowIndex, index), item)
|
||||
},
|
||||
cardOnFocus = { isFocused, index ->
|
||||
if (isFocused) {
|
||||
focusedItem = row.items.getOrNull(index)
|
||||
position = RowColumn(rowIndex, index)
|
||||
}
|
||||
},
|
||||
onLongClickItem = onLongClickItem,
|
||||
onLongClickItem = { index, item ->
|
||||
onLongClickItem.invoke(RowColumn(rowIndex, index), item)
|
||||
},
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
|
|
@ -339,6 +342,7 @@ fun HomePageContent(
|
|||
item?.data?.indexNumber?.let { "E$it" }
|
||||
?: item?.data?.childCount?.let { if (it > 0) it.toString() else null },
|
||||
played = item?.data?.userData?.played ?: false,
|
||||
favorite = item?.favorite ?: false,
|
||||
playPercent =
|
||||
item?.data?.userData?.playedPercentage
|
||||
?: 0.0,
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
|||
import com.github.damontecres.wholphin.ui.nav.ServerNavDrawerItem
|
||||
import com.github.damontecres.wholphin.ui.setValueOnMain
|
||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.FavoriteWatchManager
|
||||
import com.github.damontecres.wholphin.util.HomeRowLoadingState
|
||||
import com.github.damontecres.wholphin.util.LoadingExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
|
|
@ -27,7 +28,6 @@ import kotlinx.coroutines.withContext
|
|||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
import org.jellyfin.sdk.api.client.extensions.itemsApi
|
||||
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.userLibraryApi
|
||||
import org.jellyfin.sdk.api.client.extensions.userViewsApi
|
||||
|
|
@ -50,6 +50,7 @@ class HomeViewModel
|
|||
val navigationManager: NavigationManager,
|
||||
val serverRepository: ServerRepository,
|
||||
val navDrawerItemRepository: NavDrawerItemRepository,
|
||||
private val favoriteWatchManager: FavoriteWatchManager,
|
||||
) : ViewModel() {
|
||||
val loadingState = MutableLiveData<LoadingState>(LoadingState.Pending)
|
||||
val refreshState = MutableLiveData<LoadingState>(LoadingState.Pending)
|
||||
|
|
@ -258,11 +259,7 @@ class HomeViewModel
|
|||
itemId: UUID,
|
||||
played: Boolean,
|
||||
) = viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
|
||||
if (played) {
|
||||
api.playStateApi.markPlayedItem(itemId)
|
||||
} else {
|
||||
api.playStateApi.markUnplayedItem(itemId)
|
||||
}
|
||||
favoriteWatchManager.setWatched(itemId, played)
|
||||
withContext(Dispatchers.Main) {
|
||||
init(preferences)
|
||||
}
|
||||
|
|
@ -272,11 +269,7 @@ class HomeViewModel
|
|||
itemId: UUID,
|
||||
favorite: Boolean,
|
||||
) = viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
|
||||
if (favorite) {
|
||||
api.userLibraryApi.markFavoriteItem(itemId)
|
||||
} else {
|
||||
api.userLibraryApi.unmarkFavoriteItem(itemId)
|
||||
}
|
||||
favoriteWatchManager.setFavorite(itemId, favorite)
|
||||
withContext(Dispatchers.Main) {
|
||||
init(preferences)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -174,6 +174,9 @@ fun SearchPage(
|
|||
LaunchedEffect(Unit) {
|
||||
focusRequester.tryRequestFocus()
|
||||
}
|
||||
val onClickItem = { index: Int, item: BaseItem ->
|
||||
viewModel.navigationManager.navigateTo(item.destination())
|
||||
}
|
||||
|
||||
LazyColumn(
|
||||
contentPadding = PaddingValues(start = 16.dp, end = 16.dp, top = 16.dp, bottom = 44.dp),
|
||||
|
|
@ -207,7 +210,7 @@ fun SearchPage(
|
|||
rowIndex = MOVIE_ROW,
|
||||
position = position,
|
||||
focusRequester = focusRequester,
|
||||
onClickItem = { viewModel.navigationManager.navigateTo(it.destination()) },
|
||||
onClickItem = onClickItem,
|
||||
onClickPosition = { position = it },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
|
|
@ -217,7 +220,7 @@ fun SearchPage(
|
|||
rowIndex = COLLECTION_ROW,
|
||||
position = position,
|
||||
focusRequester = focusRequester,
|
||||
onClickItem = { viewModel.navigationManager.navigateTo(it.destination()) },
|
||||
onClickItem = onClickItem,
|
||||
onClickPosition = { position = it },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
|
|
@ -227,7 +230,7 @@ fun SearchPage(
|
|||
rowIndex = SERIES_ROW,
|
||||
position = position,
|
||||
focusRequester = focusRequester,
|
||||
onClickItem = { viewModel.navigationManager.navigateTo(it.destination()) },
|
||||
onClickItem = onClickItem,
|
||||
onClickPosition = { position = it },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
|
|
@ -237,7 +240,7 @@ fun SearchPage(
|
|||
rowIndex = EPISODE_ROW,
|
||||
position = position,
|
||||
focusRequester = focusRequester,
|
||||
onClickItem = { viewModel.navigationManager.navigateTo(it.destination()) },
|
||||
onClickItem = onClickItem,
|
||||
onClickPosition = { position = it },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
cardContent = @Composable { index, item, mod, onClick, onLongClick ->
|
||||
|
|
@ -268,7 +271,7 @@ fun LazyListScope.searchResultRow(
|
|||
rowIndex: Int,
|
||||
position: RowColumn,
|
||||
focusRequester: FocusRequester,
|
||||
onClickItem: (BaseItem) -> Unit,
|
||||
onClickItem: (Int, BaseItem) -> Unit,
|
||||
onClickPosition: (RowColumn) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
cardContent: @Composable (
|
||||
|
|
@ -328,7 +331,7 @@ fun LazyListScope.searchResultRow(
|
|||
title = title,
|
||||
items = r.items,
|
||||
onClickItem = onClickItem,
|
||||
onLongClickItem = {},
|
||||
onLongClickItem = { _, _ -> },
|
||||
modifier = modifier,
|
||||
cardContent = cardContent,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
package com.github.damontecres.wholphin.util
|
||||
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
import org.jellyfin.sdk.api.client.extensions.playStateApi
|
||||
import org.jellyfin.sdk.api.client.extensions.userLibraryApi
|
||||
import org.jellyfin.sdk.model.api.UserItemDataDto
|
||||
import java.util.UUID
|
||||
|
||||
interface FavoriteWatchManager {
|
||||
suspend fun setWatched(
|
||||
itemId: UUID,
|
||||
played: Boolean,
|
||||
): UserItemDataDto
|
||||
|
||||
suspend fun setFavorite(
|
||||
itemId: UUID,
|
||||
favorite: Boolean,
|
||||
): UserItemDataDto
|
||||
}
|
||||
|
||||
class FavoriteWatchManagerImpl(
|
||||
private val api: ApiClient,
|
||||
) : FavoriteWatchManager {
|
||||
override suspend fun setWatched(
|
||||
itemId: UUID,
|
||||
played: Boolean,
|
||||
): UserItemDataDto =
|
||||
if (played) {
|
||||
api.playStateApi.markPlayedItem(itemId).content
|
||||
} else {
|
||||
api.playStateApi.markUnplayedItem(itemId).content
|
||||
}
|
||||
|
||||
override suspend fun setFavorite(
|
||||
itemId: UUID,
|
||||
favorite: Boolean,
|
||||
): UserItemDataDto =
|
||||
if (favorite) {
|
||||
api.userLibraryApi.markFavoriteItem(itemId).content
|
||||
} else {
|
||||
api.userLibraryApi.unmarkFavoriteItem(itemId).content
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue