mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-09 08:01:20 +02:00
Remove unused code
This commit is contained in:
parent
c6b7e0f4de
commit
9d67b9a3ca
15 changed files with 12 additions and 570 deletions
|
|
@ -1,30 +0,0 @@
|
||||||
package com.github.damontecres.dolphin.data.model
|
|
||||||
|
|
||||||
import org.jellyfin.sdk.api.client.ApiClient
|
|
||||||
import org.jellyfin.sdk.model.api.BaseItemDto
|
|
||||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
|
||||||
import java.util.UUID
|
|
||||||
|
|
||||||
sealed interface DolphinModel {
|
|
||||||
val id: UUID
|
|
||||||
val name: String?
|
|
||||||
val type: BaseItemKind
|
|
||||||
val imageUrl: String?
|
|
||||||
}
|
|
||||||
|
|
||||||
fun convertModel(
|
|
||||||
dto: BaseItemDto,
|
|
||||||
api: ApiClient,
|
|
||||||
): DolphinModel =
|
|
||||||
when (dto.type) {
|
|
||||||
BaseItemKind.COLLECTION_FOLDER -> Library.fromDto(dto, api)
|
|
||||||
BaseItemKind.USER_VIEW -> Library.fromDto(dto, api)
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
BaseItemKind.VIDEO -> Video.fromDto(dto, api)
|
|
||||||
BaseItemKind.SERIES -> Video.fromDto(dto, api)
|
|
||||||
BaseItemKind.MOVIE -> Video.fromDto(dto, api)
|
|
||||||
BaseItemKind.SEASON -> Video.fromDto(dto, api)
|
|
||||||
BaseItemKind.EPISODE -> Video.fromDto(dto, api)
|
|
||||||
else -> throw IllegalArgumentException("Unsupported item type: ${dto.type}")
|
|
||||||
}
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
package com.github.damontecres.dolphin.data.model
|
|
||||||
|
|
||||||
import org.jellyfin.sdk.api.client.ApiClient
|
|
||||||
import org.jellyfin.sdk.api.client.extensions.imageApi
|
|
||||||
import org.jellyfin.sdk.model.api.BaseItemDto
|
|
||||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
|
||||||
import org.jellyfin.sdk.model.api.CollectionType
|
|
||||||
import org.jellyfin.sdk.model.api.ImageType
|
|
||||||
import java.util.UUID
|
|
||||||
|
|
||||||
data class Library(
|
|
||||||
override val id: UUID,
|
|
||||||
override val name: String?,
|
|
||||||
override val type: BaseItemKind,
|
|
||||||
override val imageUrl: String?,
|
|
||||||
val collectionType: CollectionType,
|
|
||||||
) : DolphinModel {
|
|
||||||
companion object {
|
|
||||||
fun fromDto(
|
|
||||||
dto: BaseItemDto,
|
|
||||||
api: ApiClient,
|
|
||||||
): Library =
|
|
||||||
Library(
|
|
||||||
id = dto.id,
|
|
||||||
name = dto.name,
|
|
||||||
type = dto.type,
|
|
||||||
imageUrl = api.imageApi.getItemImageUrl(dto.id, ImageType.PRIMARY),
|
|
||||||
collectionType = dto.collectionType ?: CollectionType.UNKNOWN,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
package com.github.damontecres.dolphin.data.model
|
|
||||||
|
|
||||||
import org.jellyfin.sdk.api.client.ApiClient
|
|
||||||
import org.jellyfin.sdk.api.client.extensions.imageApi
|
|
||||||
import org.jellyfin.sdk.model.api.BaseItemDto
|
|
||||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
|
||||||
import org.jellyfin.sdk.model.api.ImageType
|
|
||||||
import java.util.UUID
|
|
||||||
|
|
||||||
data class Video(
|
|
||||||
override val id: UUID,
|
|
||||||
override val name: String?,
|
|
||||||
override val type: BaseItemKind,
|
|
||||||
override val imageUrl: String?,
|
|
||||||
) : DolphinModel {
|
|
||||||
companion object {
|
|
||||||
fun fromDto(
|
|
||||||
dto: BaseItemDto,
|
|
||||||
api: ApiClient,
|
|
||||||
): Video =
|
|
||||||
Video(
|
|
||||||
id = dto.id,
|
|
||||||
name = dto.name,
|
|
||||||
type = dto.type,
|
|
||||||
imageUrl = api.imageApi.getItemImageUrl(dto.id, ImageType.PRIMARY),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -5,22 +5,16 @@ import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
|
||||||
import androidx.compose.foundation.interaction.collectIsFocusedAsState
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.CheckCircle
|
import androidx.compose.material.icons.filled.CheckCircle
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
|
@ -29,134 +23,23 @@ import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.BlendMode
|
import androidx.compose.ui.graphics.BlendMode
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.graphics.ColorFilter
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
import androidx.compose.ui.graphics.RectangleShape
|
import androidx.compose.ui.graphics.RectangleShape
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.res.colorResource
|
import androidx.compose.ui.res.colorResource
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
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.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.tv.material3.Card
|
|
||||||
import androidx.tv.material3.CardDefaults
|
|
||||||
import androidx.tv.material3.Icon
|
import androidx.tv.material3.Icon
|
||||||
import androidx.tv.material3.MaterialTheme
|
import androidx.tv.material3.MaterialTheme
|
||||||
import androidx.tv.material3.Text
|
import androidx.tv.material3.Text
|
||||||
import coil3.compose.AsyncImage
|
import coil3.compose.AsyncImage
|
||||||
import com.github.damontecres.dolphin.R
|
import com.github.damontecres.dolphin.R
|
||||||
import com.github.damontecres.dolphin.data.model.BaseItem
|
|
||||||
import com.github.damontecres.dolphin.ui.Cards
|
import com.github.damontecres.dolphin.ui.Cards
|
||||||
import com.github.damontecres.dolphin.ui.FontAwesome
|
import com.github.damontecres.dolphin.ui.FontAwesome
|
||||||
import com.github.damontecres.dolphin.ui.enableMarquee
|
|
||||||
import com.github.damontecres.dolphin.ui.ifElse
|
|
||||||
import com.github.damontecres.dolphin.ui.isNotNullOrBlank
|
import com.github.damontecres.dolphin.ui.isNotNullOrBlank
|
||||||
import com.github.damontecres.dolphin.ui.logCoilError
|
import com.github.damontecres.dolphin.ui.logCoilError
|
||||||
import com.github.damontecres.dolphin.util.seasonEpisode
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
@Deprecated("Old style, prefer SeasonCard or other Card")
|
|
||||||
fun ItemCard(
|
|
||||||
item: BaseItem?,
|
|
||||||
onClick: () -> Unit,
|
|
||||||
onLongClick: () -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
cardWidth: Dp? = null,
|
|
||||||
cardHeight: Dp? = 200.dp * .85f,
|
|
||||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
|
||||||
) {
|
|
||||||
val hideOverlayDelay = 750L
|
|
||||||
|
|
||||||
val focused = interactionSource.collectIsFocusedAsState().value
|
|
||||||
var focusedAfterDelay by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
if (focused) {
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
delay(hideOverlayDelay)
|
|
||||||
if (focused) {
|
|
||||||
focusedAfterDelay = true
|
|
||||||
} else {
|
|
||||||
focusedAfterDelay = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
focusedAfterDelay = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item == null) {
|
|
||||||
NullCard(modifier, cardWidth, cardHeight, interactionSource)
|
|
||||||
} else {
|
|
||||||
val dto = item.data
|
|
||||||
// TODO better aspect ratio handling
|
|
||||||
// val height =
|
|
||||||
// if (dto.primaryImageAspectRatio != null && dto.primaryImageAspectRatio!! > 1) cardWidth else cardHeight
|
|
||||||
Card(
|
|
||||||
modifier = modifier,
|
|
||||||
onClick = onClick,
|
|
||||||
onLongClick = onLongClick,
|
|
||||||
interactionSource = interactionSource,
|
|
||||||
colors =
|
|
||||||
CardDefaults.colors(
|
|
||||||
containerColor = Color.Transparent,
|
|
||||||
),
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
|
||||||
modifier = Modifier.ifElse(cardWidth != null, { Modifier.width(cardWidth!!) }),
|
|
||||||
) {
|
|
||||||
ItemCardImage(
|
|
||||||
imageUrl = item.imageUrl,
|
|
||||||
name = item.name,
|
|
||||||
showOverlay = !focusedAfterDelay,
|
|
||||||
favorite = dto.userData?.isFavorite ?: false,
|
|
||||||
watched = dto.userData?.played ?: false,
|
|
||||||
unwatchedCount = dto.userData?.unplayedItemCount ?: -1,
|
|
||||||
watchedPercent = dto.userData?.playedPercentage,
|
|
||||||
modifier =
|
|
||||||
Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.ifElse(cardHeight != null, { Modifier.height(cardHeight!!) }),
|
|
||||||
)
|
|
||||||
Column(
|
|
||||||
verticalArrangement = Arrangement.spacedBy(0.dp),
|
|
||||||
modifier = Modifier.padding(bottom = 4.dp),
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = item.name ?: "",
|
|
||||||
maxLines = 1,
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
modifier =
|
|
||||||
Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = 4.dp)
|
|
||||||
.enableMarquee(focusedAfterDelay),
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = item.data.productionYear?.toString() ?: "",
|
|
||||||
maxLines = 1,
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
modifier =
|
|
||||||
Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = 4.dp)
|
|
||||||
.enableMarquee(focusedAfterDelay),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (dto.type == BaseItemKind.EPISODE) {
|
|
||||||
dto.seasonEpisode?.let {
|
|
||||||
Text(
|
|
||||||
text = it,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ItemCardImage(
|
fun ItemCardImage(
|
||||||
|
|
@ -29,21 +29,14 @@ fun ItemRow(
|
||||||
items: List<BaseItem?>,
|
items: List<BaseItem?>,
|
||||||
onClickItem: (BaseItem) -> Unit,
|
onClickItem: (BaseItem) -> Unit,
|
||||||
onLongClickItem: (BaseItem) -> Unit,
|
onLongClickItem: (BaseItem) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
cardContent: @Composable (
|
cardContent: @Composable (
|
||||||
index: Int,
|
index: Int,
|
||||||
item: BaseItem?,
|
item: BaseItem?,
|
||||||
modifier: Modifier,
|
modifier: Modifier,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
onLongClick: () -> Unit,
|
onLongClick: () -> Unit,
|
||||||
) -> Unit = { index, item, mod, onClick, onLongClick ->
|
) -> Unit,
|
||||||
ItemCard(
|
modifier: Modifier = Modifier,
|
||||||
item = item,
|
|
||||||
onClick = onClick,
|
|
||||||
onLongClick = onLongClick,
|
|
||||||
modifier = mod,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
focusPair: FocusPair? = null,
|
focusPair: FocusPair? = null,
|
||||||
cardOnFocus: ((isFocused: Boolean, index: Int) -> Unit)? = null,
|
cardOnFocus: ((isFocused: Boolean, index: Int) -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
package com.github.damontecres.dolphin.ui.cards
|
|
||||||
|
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.unit.Dp
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.tv.material3.Card
|
|
||||||
import androidx.tv.material3.Text
|
|
||||||
import com.github.damontecres.dolphin.ui.ifElse
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
@Deprecated("Cards should handle nulls natively")
|
|
||||||
fun NullCard(
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
cardWidth: Dp? = null,
|
|
||||||
cardHeight: Dp? = 200.dp * .75f,
|
|
||||||
interactionSource: MutableInteractionSource? = null,
|
|
||||||
) {
|
|
||||||
Card(
|
|
||||||
modifier = modifier,
|
|
||||||
onClick = {},
|
|
||||||
interactionSource = interactionSource,
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier =
|
|
||||||
Modifier
|
|
||||||
.ifElse(cardHeight != null, { Modifier.height(cardHeight!!) })
|
|
||||||
.ifElse(cardWidth != null, { Modifier.width(cardWidth!!) }),
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = "Loading...",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -27,7 +27,6 @@ import androidx.lifecycle.viewModelScope
|
||||||
import androidx.tv.material3.MaterialTheme
|
import androidx.tv.material3.MaterialTheme
|
||||||
import androidx.tv.material3.Text
|
import androidx.tv.material3.Text
|
||||||
import com.github.damontecres.dolphin.data.model.BaseItem
|
import com.github.damontecres.dolphin.data.model.BaseItem
|
||||||
import com.github.damontecres.dolphin.data.model.Library
|
|
||||||
import com.github.damontecres.dolphin.preferences.UserPreferences
|
import com.github.damontecres.dolphin.preferences.UserPreferences
|
||||||
import com.github.damontecres.dolphin.ui.DefaultItemFields
|
import com.github.damontecres.dolphin.ui.DefaultItemFields
|
||||||
import com.github.damontecres.dolphin.ui.OneTimeLaunchedEffect
|
import com.github.damontecres.dolphin.ui.OneTimeLaunchedEffect
|
||||||
|
|
@ -63,7 +62,7 @@ class CollectionFolderViewModel
|
||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(
|
||||||
api: ApiClient,
|
api: ApiClient,
|
||||||
) : ItemViewModel<Library>(api) {
|
) : ItemViewModel(api) {
|
||||||
val loading = MutableLiveData<LoadingState>(LoadingState.Loading)
|
val loading = MutableLiveData<LoadingState>(LoadingState.Loading)
|
||||||
val pager = MutableLiveData<List<BaseItem?>>(listOf())
|
val pager = MutableLiveData<List<BaseItem?>>(listOf())
|
||||||
val sortAndDirection = MutableLiveData<SortAndDirection>()
|
val sortAndDirection = MutableLiveData<SortAndDirection>()
|
||||||
|
|
|
||||||
|
|
@ -1,166 +0,0 @@
|
||||||
package com.github.damontecres.dolphin.ui.detail
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.relocation.BringIntoViewRequester
|
|
||||||
import androidx.compose.foundation.relocation.bringIntoViewRequester
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.draw.drawWithContent
|
|
||||||
import androidx.compose.ui.graphics.Brush
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.layout.ContentScale
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
|
||||||
import androidx.tv.material3.MaterialTheme
|
|
||||||
import coil3.compose.AsyncImage
|
|
||||||
import com.github.damontecres.dolphin.data.model.BaseItem
|
|
||||||
import com.github.damontecres.dolphin.data.model.Video
|
|
||||||
import com.github.damontecres.dolphin.preferences.UserPreferences
|
|
||||||
import com.github.damontecres.dolphin.ui.components.ErrorMessage
|
|
||||||
import com.github.damontecres.dolphin.ui.components.LoadingPage
|
|
||||||
import com.github.damontecres.dolphin.ui.components.details.VideoDetailsHeader
|
|
||||||
import com.github.damontecres.dolphin.ui.isNotNullOrBlank
|
|
||||||
import com.github.damontecres.dolphin.ui.nav.Destination
|
|
||||||
import com.github.damontecres.dolphin.util.LoadingState
|
|
||||||
import com.github.damontecres.dolphin.util.seasonEpisode
|
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
|
||||||
import org.jellyfin.sdk.api.client.ApiClient
|
|
||||||
import org.jellyfin.sdk.model.api.ImageType
|
|
||||||
import org.jellyfin.sdk.model.extensions.ticks
|
|
||||||
import timber.log.Timber
|
|
||||||
import javax.inject.Inject
|
|
||||||
import kotlin.time.Duration.Companion.seconds
|
|
||||||
|
|
||||||
@HiltViewModel
|
|
||||||
class EpisodeViewModel
|
|
||||||
@Inject
|
|
||||||
constructor(
|
|
||||||
api: ApiClient,
|
|
||||||
) : LoadingItemViewModel<Video>(api)
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun EpisodeDetails(
|
|
||||||
preferences: UserPreferences,
|
|
||||||
destination: Destination.MediaItem,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
viewModel: EpisodeViewModel = hiltViewModel(),
|
|
||||||
) {
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
viewModel.init(destination.itemId, destination.item)
|
|
||||||
}
|
|
||||||
val item by viewModel.item.observeAsState()
|
|
||||||
val loading by viewModel.loading.observeAsState(LoadingState.Loading)
|
|
||||||
when (val state = loading) {
|
|
||||||
is LoadingState.Error -> ErrorMessage(state)
|
|
||||||
LoadingState.Loading,
|
|
||||||
LoadingState.Pending,
|
|
||||||
-> LoadingPage()
|
|
||||||
LoadingState.Success -> {
|
|
||||||
item?.let { item ->
|
|
||||||
EpisodeDetailsContent(
|
|
||||||
item = item,
|
|
||||||
backdropImageUrl =
|
|
||||||
remember {
|
|
||||||
item.data.parentBackdropItemId?.let {
|
|
||||||
viewModel.imageUrl(it, ImageType.BACKDROP)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
modifier = modifier,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun EpisodeDetailsContent(
|
|
||||||
item: BaseItem,
|
|
||||||
backdropImageUrl: String?,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
val context = LocalContext.current
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
val bringIntoViewRequester = remember { BringIntoViewRequester() }
|
|
||||||
|
|
||||||
val dto = item.data
|
|
||||||
val title = item.name ?: "Unknown"
|
|
||||||
val subtitle = dto.seriesName
|
|
||||||
val description = dto.overview
|
|
||||||
|
|
||||||
val details =
|
|
||||||
buildList {
|
|
||||||
dto.seasonEpisode?.let(::add)
|
|
||||||
dto.mediaSources?.firstOrNull()?.runTimeTicks?.ticks?.inWholeMinutes?.toString()?.let {
|
|
||||||
add(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Box(
|
|
||||||
modifier =
|
|
||||||
modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
// .fillMaxHeight(.33f)
|
|
||||||
.height(460.dp)
|
|
||||||
.bringIntoViewRequester(bringIntoViewRequester),
|
|
||||||
) {
|
|
||||||
if (backdropImageUrl.isNotNullOrBlank()) {
|
|
||||||
Timber.v("Banner image url: $backdropImageUrl")
|
|
||||||
val gradientColor = MaterialTheme.colorScheme.background
|
|
||||||
AsyncImage(
|
|
||||||
model = backdropImageUrl,
|
|
||||||
contentDescription = null,
|
|
||||||
contentScale = ContentScale.Crop,
|
|
||||||
alignment = Alignment.TopEnd,
|
|
||||||
modifier =
|
|
||||||
Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.drawWithContent {
|
|
||||||
drawContent()
|
|
||||||
drawRect(
|
|
||||||
Brush.verticalGradient(
|
|
||||||
colors = listOf(Color.Transparent, gradientColor),
|
|
||||||
startY = 500f,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
drawRect(
|
|
||||||
Brush.horizontalGradient(
|
|
||||||
colors = listOf(gradientColor, Color.Transparent),
|
|
||||||
endX = 400f,
|
|
||||||
startX = 100f,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Column(modifier = Modifier.fillMaxWidth(0.8f)) {
|
|
||||||
Spacer(modifier = Modifier.height(60.dp))
|
|
||||||
VideoDetailsHeader(
|
|
||||||
title = title,
|
|
||||||
subtitle = subtitle,
|
|
||||||
description = description,
|
|
||||||
details = details,
|
|
||||||
moreDetails = sortedMapOf(),
|
|
||||||
rating = 0f,
|
|
||||||
resumeTime = dto.userData?.playbackPositionTicks?.ticks ?: 0.seconds,
|
|
||||||
watched = dto.userData?.played ?: false,
|
|
||||||
favorite = dto.userData?.isFavorite ?: false,
|
|
||||||
bringIntoViewRequester = bringIntoViewRequester,
|
|
||||||
descriptionOnClick = {},
|
|
||||||
moreOnClick = {},
|
|
||||||
modifier = Modifier,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -20,7 +20,7 @@ import java.util.UUID
|
||||||
/**
|
/**
|
||||||
* Basic [ViewModel] for a single fetchable item from the API
|
* Basic [ViewModel] for a single fetchable item from the API
|
||||||
*/
|
*/
|
||||||
abstract class ItemViewModel<T>(
|
abstract class ItemViewModel(
|
||||||
val api: ApiClient,
|
val api: ApiClient,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
val item = MutableLiveData<BaseItem?>(null)
|
val item = MutableLiveData<BaseItem?>(null)
|
||||||
|
|
@ -58,9 +58,9 @@ abstract class ItemViewModel<T>(
|
||||||
/**
|
/**
|
||||||
* Extends [ItemViewModel] to include a loading state tracking when the item has been fetched or if an error occurred
|
* Extends [ItemViewModel] to include a loading state tracking when the item has been fetched or if an error occurred
|
||||||
*/
|
*/
|
||||||
abstract class LoadingItemViewModel<T>(
|
abstract class LoadingItemViewModel(
|
||||||
api: ApiClient,
|
api: ApiClient,
|
||||||
) : ItemViewModel<T>(api) {
|
) : ItemViewModel(api) {
|
||||||
val loading = MutableLiveData<LoadingState>(LoadingState.Pending)
|
val loading = MutableLiveData<LoadingState>(LoadingState.Pending)
|
||||||
|
|
||||||
open fun init(
|
open fun init(
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,6 @@ import androidx.tv.material3.surfaceColorAtElevation
|
||||||
import coil3.compose.AsyncImage
|
import coil3.compose.AsyncImage
|
||||||
import com.github.damontecres.dolphin.R
|
import com.github.damontecres.dolphin.R
|
||||||
import com.github.damontecres.dolphin.data.model.BaseItem
|
import com.github.damontecres.dolphin.data.model.BaseItem
|
||||||
import com.github.damontecres.dolphin.data.model.Library
|
|
||||||
import com.github.damontecres.dolphin.ui.DefaultItemFields
|
import com.github.damontecres.dolphin.ui.DefaultItemFields
|
||||||
import com.github.damontecres.dolphin.ui.cards.ItemCardImage
|
import com.github.damontecres.dolphin.ui.cards.ItemCardImage
|
||||||
import com.github.damontecres.dolphin.ui.components.DialogItem
|
import com.github.damontecres.dolphin.ui.components.DialogItem
|
||||||
|
|
@ -93,7 +92,7 @@ class PlaylistViewModel
|
||||||
constructor(
|
constructor(
|
||||||
api: ApiClient,
|
api: ApiClient,
|
||||||
val navigationManager: NavigationManager,
|
val navigationManager: NavigationManager,
|
||||||
) : ItemViewModel<Library>(api) {
|
) : ItemViewModel(api) {
|
||||||
val loading = MutableLiveData<LoadingState>(LoadingState.Pending)
|
val loading = MutableLiveData<LoadingState>(LoadingState.Pending)
|
||||||
val items = MutableLiveData<List<BaseItem?>>(listOf())
|
val items = MutableLiveData<List<BaseItem?>>(listOf())
|
||||||
|
|
||||||
|
|
@ -174,13 +173,7 @@ fun PlaylistDetails(
|
||||||
"Go to",
|
"Go to",
|
||||||
Icons.Default.ArrowForward,
|
Icons.Default.ArrowForward,
|
||||||
) {
|
) {
|
||||||
viewModel.navigationManager.navigateTo(
|
viewModel.navigationManager.navigateTo(item.destination())
|
||||||
Destination.MediaItem(
|
|
||||||
itemId = item.id,
|
|
||||||
type = item.type,
|
|
||||||
item = item,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
DialogItem(
|
DialogItem(
|
||||||
"Play from here",
|
"Play from here",
|
||||||
|
|
@ -189,7 +182,7 @@ fun PlaylistDetails(
|
||||||
viewModel.navigationManager.navigateTo(
|
viewModel.navigationManager.navigateTo(
|
||||||
Destination.Playback(
|
Destination.Playback(
|
||||||
itemId = it.id,
|
itemId = it.id,
|
||||||
positionMs = 0L,
|
positionMs = it.resumeMs ?: 0L,
|
||||||
startIndex = index,
|
startIndex = index,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,113 +0,0 @@
|
||||||
package com.github.damontecres.dolphin.ui.detail
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
|
||||||
import androidx.lifecycle.MutableLiveData
|
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import androidx.tv.material3.Text
|
|
||||||
import com.github.damontecres.dolphin.data.model.BaseItem
|
|
||||||
import com.github.damontecres.dolphin.data.model.Video
|
|
||||||
import com.github.damontecres.dolphin.preferences.UserPreferences
|
|
||||||
import com.github.damontecres.dolphin.ui.cards.ItemRow
|
|
||||||
import com.github.damontecres.dolphin.ui.nav.Destination
|
|
||||||
import com.github.damontecres.dolphin.ui.nav.NavigationManager
|
|
||||||
import com.github.damontecres.dolphin.util.ApiRequestPager
|
|
||||||
import com.github.damontecres.dolphin.util.ExceptionHandler
|
|
||||||
import com.github.damontecres.dolphin.util.GetItemsRequestHandler
|
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.jellyfin.sdk.api.client.ApiClient
|
|
||||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
|
||||||
import org.jellyfin.sdk.model.api.ItemFields
|
|
||||||
import org.jellyfin.sdk.model.api.ItemSortBy
|
|
||||||
import org.jellyfin.sdk.model.api.SortOrder
|
|
||||||
import org.jellyfin.sdk.model.api.request.GetItemsRequest
|
|
||||||
import java.util.UUID
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@HiltViewModel
|
|
||||||
class SeasonViewModel
|
|
||||||
@Inject
|
|
||||||
constructor(
|
|
||||||
api: ApiClient,
|
|
||||||
val navigationManager: NavigationManager,
|
|
||||||
) : ItemViewModel<Video>(api) {
|
|
||||||
val episodes = MutableLiveData<List<BaseItem?>>(listOf())
|
|
||||||
|
|
||||||
fun init(
|
|
||||||
itemId: UUID,
|
|
||||||
potential: BaseItem?,
|
|
||||||
): Job? =
|
|
||||||
viewModelScope.launch(ExceptionHandler()) {
|
|
||||||
fetchItem(itemId, potential)?.let { item ->
|
|
||||||
val request =
|
|
||||||
GetItemsRequest(
|
|
||||||
parentId = item.id,
|
|
||||||
recursive = false,
|
|
||||||
includeItemTypes = listOf(BaseItemKind.EPISODE),
|
|
||||||
sortBy = listOf(ItemSortBy.INDEX_NUMBER),
|
|
||||||
sortOrder = listOf(SortOrder.ASCENDING),
|
|
||||||
fields = listOf(ItemFields.PRIMARY_IMAGE_ASPECT_RATIO, ItemFields.CHILD_COUNT),
|
|
||||||
)
|
|
||||||
val pager =
|
|
||||||
ApiRequestPager(
|
|
||||||
api,
|
|
||||||
request,
|
|
||||||
GetItemsRequestHandler,
|
|
||||||
viewModelScope,
|
|
||||||
)
|
|
||||||
pager.init()
|
|
||||||
episodes.value = pager
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun SeasonDetails(
|
|
||||||
preferences: UserPreferences,
|
|
||||||
destination: Destination.MediaItem,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
viewModel: SeasonViewModel = hiltViewModel(),
|
|
||||||
) {
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
viewModel.init(destination.itemId, destination.item)
|
|
||||||
}
|
|
||||||
val item by viewModel.item.observeAsState()
|
|
||||||
val episodes by viewModel.episodes.observeAsState(listOf())
|
|
||||||
if (item == null) {
|
|
||||||
Text(text = "Loading...")
|
|
||||||
} else {
|
|
||||||
item?.let { item ->
|
|
||||||
var focusedChild by remember { mutableIntStateOf(0) }
|
|
||||||
LazyColumn(modifier = modifier) {
|
|
||||||
item {
|
|
||||||
Text(text = item.name ?: "Unknown")
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
ItemRow(
|
|
||||||
title = "Episodes",
|
|
||||||
items = episodes,
|
|
||||||
onClickItem = { viewModel.navigationManager.navigateTo(it.destination()) },
|
|
||||||
onLongClickItem = { },
|
|
||||||
cardOnFocus = { isFocused, index ->
|
|
||||||
if (isFocused) focusedChild = index
|
|
||||||
},
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -13,7 +13,6 @@ import androidx.media3.exoplayer.ExoPlayer
|
||||||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
||||||
import com.github.damontecres.dolphin.data.model.BaseItem
|
import com.github.damontecres.dolphin.data.model.BaseItem
|
||||||
import com.github.damontecres.dolphin.data.model.Person
|
import com.github.damontecres.dolphin.data.model.Person
|
||||||
import com.github.damontecres.dolphin.data.model.Video
|
|
||||||
import com.github.damontecres.dolphin.hilt.AuthOkHttpClient
|
import com.github.damontecres.dolphin.hilt.AuthOkHttpClient
|
||||||
import com.github.damontecres.dolphin.preferences.ThemeSongVolume
|
import com.github.damontecres.dolphin.preferences.ThemeSongVolume
|
||||||
import com.github.damontecres.dolphin.preferences.UserPreferences
|
import com.github.damontecres.dolphin.preferences.UserPreferences
|
||||||
|
|
@ -58,7 +57,7 @@ class SeriesViewModel
|
||||||
@param:ApplicationContext val context: Context,
|
@param:ApplicationContext val context: Context,
|
||||||
@param:AuthOkHttpClient private val okHttpClient: OkHttpClient,
|
@param:AuthOkHttpClient private val okHttpClient: OkHttpClient,
|
||||||
private val navigationManager: NavigationManager,
|
private val navigationManager: NavigationManager,
|
||||||
) : ItemViewModel<Video>(api) {
|
) : ItemViewModel(api) {
|
||||||
private var player: Player? = null
|
private var player: Player? = null
|
||||||
private lateinit var seriesId: UUID
|
private lateinit var seriesId: UUID
|
||||||
private lateinit var prefs: UserPreferences
|
private lateinit var prefs: UserPreferences
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||||
import androidx.tv.material3.Button
|
import androidx.tv.material3.Button
|
||||||
import androidx.tv.material3.Text
|
import androidx.tv.material3.Text
|
||||||
import com.github.damontecres.dolphin.data.model.Video
|
|
||||||
import com.github.damontecres.dolphin.preferences.UserPreferences
|
import com.github.damontecres.dolphin.preferences.UserPreferences
|
||||||
import com.github.damontecres.dolphin.ui.components.ErrorMessage
|
import com.github.damontecres.dolphin.ui.components.ErrorMessage
|
||||||
import com.github.damontecres.dolphin.ui.components.LoadingPage
|
import com.github.damontecres.dolphin.ui.components.LoadingPage
|
||||||
|
|
@ -32,7 +31,7 @@ class VideoViewModel
|
||||||
constructor(
|
constructor(
|
||||||
api: ApiClient,
|
api: ApiClient,
|
||||||
val navigationManager: NavigationManager,
|
val navigationManager: NavigationManager,
|
||||||
) : LoadingItemViewModel<Video>(api)
|
) : LoadingItemViewModel(api)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun VideoDetails(
|
fun VideoDetails(
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,6 @@ import coil3.compose.AsyncImage
|
||||||
import com.github.damontecres.dolphin.data.model.BaseItem
|
import com.github.damontecres.dolphin.data.model.BaseItem
|
||||||
import com.github.damontecres.dolphin.data.model.Chapter
|
import com.github.damontecres.dolphin.data.model.Chapter
|
||||||
import com.github.damontecres.dolphin.data.model.Person
|
import com.github.damontecres.dolphin.data.model.Person
|
||||||
import com.github.damontecres.dolphin.data.model.Video
|
|
||||||
import com.github.damontecres.dolphin.preferences.UserPreferences
|
import com.github.damontecres.dolphin.preferences.UserPreferences
|
||||||
import com.github.damontecres.dolphin.ui.cards.ChapterRow
|
import com.github.damontecres.dolphin.ui.cards.ChapterRow
|
||||||
import com.github.damontecres.dolphin.ui.cards.PersonRow
|
import com.github.damontecres.dolphin.ui.cards.PersonRow
|
||||||
|
|
@ -76,7 +75,7 @@ class MovieViewModel
|
||||||
constructor(
|
constructor(
|
||||||
api: ApiClient,
|
api: ApiClient,
|
||||||
val navigationManager: NavigationManager,
|
val navigationManager: NavigationManager,
|
||||||
) : LoadingItemViewModel<Video>(api) {
|
) : LoadingItemViewModel(api) {
|
||||||
private lateinit var itemId: UUID
|
private lateinit var itemId: UUID
|
||||||
val people = MutableLiveData<List<Person>>(listOf())
|
val people = MutableLiveData<List<Person>>(listOf())
|
||||||
val chapters = MutableLiveData<List<Chapter>>(listOf())
|
val chapters = MutableLiveData<List<Chapter>>(listOf())
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,7 @@ import com.github.damontecres.dolphin.ui.components.LicenseInfo
|
||||||
import com.github.damontecres.dolphin.ui.detail.CollectionFolderGeneric
|
import com.github.damontecres.dolphin.ui.detail.CollectionFolderGeneric
|
||||||
import com.github.damontecres.dolphin.ui.detail.CollectionFolderMovie
|
import com.github.damontecres.dolphin.ui.detail.CollectionFolderMovie
|
||||||
import com.github.damontecres.dolphin.ui.detail.CollectionFolderTv
|
import com.github.damontecres.dolphin.ui.detail.CollectionFolderTv
|
||||||
import com.github.damontecres.dolphin.ui.detail.EpisodeDetails
|
|
||||||
import com.github.damontecres.dolphin.ui.detail.PlaylistDetails
|
import com.github.damontecres.dolphin.ui.detail.PlaylistDetails
|
||||||
import com.github.damontecres.dolphin.ui.detail.SeasonDetails
|
|
||||||
import com.github.damontecres.dolphin.ui.detail.SeriesDetails
|
import com.github.damontecres.dolphin.ui.detail.SeriesDetails
|
||||||
import com.github.damontecres.dolphin.ui.detail.movie.MovieDetails
|
import com.github.damontecres.dolphin.ui.detail.movie.MovieDetails
|
||||||
import com.github.damontecres.dolphin.ui.detail.series.SeriesOverview
|
import com.github.damontecres.dolphin.ui.detail.series.SeriesOverview
|
||||||
|
|
@ -77,20 +75,6 @@ fun DestinationContent(
|
||||||
modifier,
|
modifier,
|
||||||
)
|
)
|
||||||
|
|
||||||
BaseItemKind.SEASON ->
|
|
||||||
SeasonDetails(
|
|
||||||
preferences,
|
|
||||||
destination,
|
|
||||||
modifier,
|
|
||||||
)
|
|
||||||
|
|
||||||
BaseItemKind.EPISODE ->
|
|
||||||
EpisodeDetails(
|
|
||||||
preferences,
|
|
||||||
destination,
|
|
||||||
modifier,
|
|
||||||
)
|
|
||||||
|
|
||||||
BaseItemKind.MOVIE ->
|
BaseItemKind.MOVIE ->
|
||||||
MovieDetails(
|
MovieDetails(
|
||||||
preferences,
|
preferences,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue