mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 23:51:21 +02:00
parent
82111b99e4
commit
bb19ba316b
10 changed files with 80 additions and 16 deletions
|
|
@ -12,6 +12,7 @@ import com.github.damontecres.wholphin.data.model.BaseItem
|
||||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||||
import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect
|
import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect
|
||||||
import com.github.damontecres.wholphin.ui.SlimItemFields
|
import com.github.damontecres.wholphin.ui.SlimItemFields
|
||||||
|
import com.github.damontecres.wholphin.ui.data.RowColumn
|
||||||
import com.github.damontecres.wholphin.ui.main.HomePageContent
|
import com.github.damontecres.wholphin.ui.main.HomePageContent
|
||||||
import com.github.damontecres.wholphin.ui.main.HomeRow
|
import com.github.damontecres.wholphin.ui.main.HomeRow
|
||||||
import com.github.damontecres.wholphin.ui.main.HomeSection
|
import com.github.damontecres.wholphin.ui.main.HomeSection
|
||||||
|
|
@ -114,7 +115,7 @@ class RecommendedMovieViewModel
|
||||||
HomeRow(HomeSection.LATEST_MEDIA, recentlyAddedItems, "Recently Added"),
|
HomeRow(HomeSection.LATEST_MEDIA, recentlyAddedItems, "Recently Added"),
|
||||||
HomeRow(HomeSection.NONE, suggestedItems, "Suggestions"),
|
HomeRow(HomeSection.NONE, suggestedItems, "Suggestions"),
|
||||||
HomeRow(HomeSection.NONE, unwatchedTopRatedItems, "Top Rated Unwatched"),
|
HomeRow(HomeSection.NONE, unwatchedTopRatedItems, "Top Rated Unwatched"),
|
||||||
)
|
).filter { it.items.isNotEmpty() }
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
this@RecommendedMovieViewModel.rows.value = homeRows
|
this@RecommendedMovieViewModel.rows.value = homeRows
|
||||||
loading.value = LoadingState.Success
|
loading.value = LoadingState.Success
|
||||||
|
|
@ -131,6 +132,7 @@ fun RecommendedMovie(
|
||||||
preferences: UserPreferences,
|
preferences: UserPreferences,
|
||||||
parentId: UUID,
|
parentId: UUID,
|
||||||
onClickItem: (BaseItem) -> Unit,
|
onClickItem: (BaseItem) -> Unit,
|
||||||
|
onFocusPosition: (RowColumn) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
viewModel: RecommendedMovieViewModel = hiltViewModel(),
|
viewModel: RecommendedMovieViewModel = hiltViewModel(),
|
||||||
) {
|
) {
|
||||||
|
|
@ -151,6 +153,7 @@ fun RecommendedMovie(
|
||||||
HomePageContent(
|
HomePageContent(
|
||||||
homeRows = rows,
|
homeRows = rows,
|
||||||
onClickItem = onClickItem,
|
onClickItem = onClickItem,
|
||||||
|
onFocusPosition = onFocusPosition,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import com.github.damontecres.wholphin.data.model.BaseItem
|
||||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||||
import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect
|
import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect
|
||||||
import com.github.damontecres.wholphin.ui.SlimItemFields
|
import com.github.damontecres.wholphin.ui.SlimItemFields
|
||||||
|
import com.github.damontecres.wholphin.ui.data.RowColumn
|
||||||
import com.github.damontecres.wholphin.ui.main.HomePageContent
|
import com.github.damontecres.wholphin.ui.main.HomePageContent
|
||||||
import com.github.damontecres.wholphin.ui.main.HomeRow
|
import com.github.damontecres.wholphin.ui.main.HomeRow
|
||||||
import com.github.damontecres.wholphin.ui.main.HomeSection
|
import com.github.damontecres.wholphin.ui.main.HomeSection
|
||||||
|
|
@ -126,7 +127,7 @@ class RecommendedTvShowViewModel
|
||||||
HomeRow(HomeSection.LATEST_MEDIA, recentlyAddedItems, "Recently Added"),
|
HomeRow(HomeSection.LATEST_MEDIA, recentlyAddedItems, "Recently Added"),
|
||||||
HomeRow(HomeSection.NONE, suggestedItems, "Suggestions"),
|
HomeRow(HomeSection.NONE, suggestedItems, "Suggestions"),
|
||||||
HomeRow(HomeSection.NONE, unwatchedTopRatedItems, "Top Rated Unwatched"),
|
HomeRow(HomeSection.NONE, unwatchedTopRatedItems, "Top Rated Unwatched"),
|
||||||
)
|
).filter { it.items.isNotEmpty() }
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
this@RecommendedTvShowViewModel.rows.value = homeRows
|
this@RecommendedTvShowViewModel.rows.value = homeRows
|
||||||
loading.value = LoadingState.Success
|
loading.value = LoadingState.Success
|
||||||
|
|
@ -143,6 +144,7 @@ fun RecommendedTvShow(
|
||||||
preferences: UserPreferences,
|
preferences: UserPreferences,
|
||||||
parentId: UUID,
|
parentId: UUID,
|
||||||
onClickItem: (BaseItem) -> Unit,
|
onClickItem: (BaseItem) -> Unit,
|
||||||
|
onFocusPosition: (RowColumn) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
viewModel: RecommendedTvShowViewModel = hiltViewModel(),
|
viewModel: RecommendedTvShowViewModel = hiltViewModel(),
|
||||||
) {
|
) {
|
||||||
|
|
@ -163,6 +165,7 @@ fun RecommendedTvShow(
|
||||||
HomePageContent(
|
HomePageContent(
|
||||||
homeRows = rows,
|
homeRows = rows,
|
||||||
onClickItem = onClickItem,
|
onClickItem = onClickItem,
|
||||||
|
onFocusPosition = onFocusPosition,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -145,6 +145,9 @@ fun CollectionFolderMovie(
|
||||||
preferences = preferences,
|
preferences = preferences,
|
||||||
onClickItem = onClickItem,
|
onClickItem = onClickItem,
|
||||||
parentId = destination.itemId,
|
parentId = destination.itemId,
|
||||||
|
onFocusPosition = { pos ->
|
||||||
|
showHeader = pos.row < 1
|
||||||
|
},
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.padding(start = 16.dp)
|
.padding(start = 16.dp)
|
||||||
|
|
|
||||||
|
|
@ -145,6 +145,9 @@ fun CollectionFolderTv(
|
||||||
preferences = preferences,
|
preferences = preferences,
|
||||||
parentId = destination.itemId,
|
parentId = destination.itemId,
|
||||||
onClickItem = onClickItem,
|
onClickItem = onClickItem,
|
||||||
|
onFocusPosition = { pos ->
|
||||||
|
showHeader = pos.row < 1
|
||||||
|
},
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.padding(start = 16.dp)
|
.padding(start = 16.dp)
|
||||||
|
|
|
||||||
|
|
@ -75,11 +75,7 @@ abstract class LoadingItemViewModel(
|
||||||
) + Dispatchers.IO,
|
) + Dispatchers.IO,
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
val fetchedItem = api.userLibraryApi.getItem(itemId).content
|
fetchAndSetItem(itemId)
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
item.value = BaseItem.from(fetchedItem, api)
|
|
||||||
loading.value = LoadingState.Success
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.e(e, "Failed to load item $itemId")
|
Timber.e(e, "Failed to load item $itemId")
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
|
|
@ -89,4 +85,13 @@ abstract class LoadingItemViewModel(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open suspend fun fetchAndSetItem(itemId: UUID) =
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
val fetchedItem = api.userLibraryApi.getItem(itemId).content
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
item.value = BaseItem.from(fetchedItem, api)
|
||||||
|
loading.value = LoadingState.Success
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|
@ -28,10 +29,10 @@ import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.draw.drawWithContent
|
import androidx.compose.ui.draw.drawWithContent
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
|
import androidx.compose.ui.focus.onFocusChanged
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||||
|
|
@ -68,7 +69,9 @@ import com.github.damontecres.wholphin.ui.nav.Destination
|
||||||
import com.github.damontecres.wholphin.ui.rememberPosition
|
import com.github.damontecres.wholphin.ui.rememberPosition
|
||||||
import com.github.damontecres.wholphin.ui.roundMinutes
|
import com.github.damontecres.wholphin.ui.roundMinutes
|
||||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||||
|
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||||
import com.github.damontecres.wholphin.util.LoadingState
|
import com.github.damontecres.wholphin.util.LoadingState
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.jellyfin.sdk.model.extensions.ticks
|
import org.jellyfin.sdk.model.extensions.ticks
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
|
|
||||||
|
|
@ -237,6 +240,7 @@ fun SeriesDetailsContent(
|
||||||
overviewOnClick = overviewOnClick,
|
overviewOnClick = overviewOnClick,
|
||||||
playOnClick = playOnClick,
|
playOnClick = playOnClick,
|
||||||
watchOnClick = watchOnClick,
|
watchOnClick = watchOnClick,
|
||||||
|
bringIntoViewRequester = bringIntoViewRequester,
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth(.6f)
|
.fillMaxWidth(.6f)
|
||||||
|
|
@ -301,12 +305,13 @@ fun SeriesDetailsContent(
|
||||||
fun SeriesDetailsHeader(
|
fun SeriesDetailsHeader(
|
||||||
series: BaseItem,
|
series: BaseItem,
|
||||||
played: Boolean,
|
played: Boolean,
|
||||||
|
bringIntoViewRequester: BringIntoViewRequester,
|
||||||
overviewOnClick: () -> Unit,
|
overviewOnClick: () -> Unit,
|
||||||
playOnClick: () -> Unit,
|
playOnClick: () -> Unit,
|
||||||
watchOnClick: () -> Unit,
|
watchOnClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val scope = rememberCoroutineScope()
|
||||||
val dto = series.data
|
val dto = series.data
|
||||||
val details =
|
val details =
|
||||||
buildList {
|
buildList {
|
||||||
|
|
@ -370,13 +375,27 @@ fun SeriesDetailsHeader(
|
||||||
resume = Duration.ZERO,
|
resume = Duration.ZERO,
|
||||||
icon = Icons.Default.PlayArrow,
|
icon = Icons.Default.PlayArrow,
|
||||||
onClick = { playOnClick.invoke() },
|
onClick = { playOnClick.invoke() },
|
||||||
modifier = Modifier,
|
modifier =
|
||||||
|
Modifier.onFocusChanged {
|
||||||
|
if (it.isFocused) {
|
||||||
|
scope.launch(ExceptionHandler()) {
|
||||||
|
bringIntoViewRequester.bringIntoView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
ExpandableFaButton(
|
ExpandableFaButton(
|
||||||
title = if (played) R.string.mark_unwatched else R.string.mark_watched,
|
title = if (played) R.string.mark_unwatched else R.string.mark_watched,
|
||||||
iconStringRes = if (played) R.string.fa_eye else R.string.fa_eye_slash,
|
iconStringRes = if (played) R.string.fa_eye else R.string.fa_eye_slash,
|
||||||
onClick = watchOnClick,
|
onClick = watchOnClick,
|
||||||
modifier = Modifier,
|
modifier =
|
||||||
|
Modifier.onFocusChanged {
|
||||||
|
if (it.isFocused) {
|
||||||
|
scope.launch(ExceptionHandler()) {
|
||||||
|
bringIntoViewRequester.bringIntoView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -235,7 +235,7 @@ class SeriesViewModel
|
||||||
} else {
|
} else {
|
||||||
api.playStateApi.markUnplayedItem(seriesId)
|
api.playStateApi.markUnplayedItem(seriesId)
|
||||||
}
|
}
|
||||||
init(prefs, seriesId, null, null, null)
|
fetchItem(seriesId, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun refreshEpisode(
|
fun refreshEpisode(
|
||||||
|
|
|
||||||
|
|
@ -123,7 +123,7 @@ class MovieViewModel
|
||||||
} else {
|
} else {
|
||||||
api.playStateApi.markUnplayedItem(itemId)
|
api.playStateApi.markUnplayedItem(itemId)
|
||||||
}
|
}
|
||||||
init(itemId, null)
|
fetchAndSetItem(itemId)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun savePlayVersion(
|
fun savePlayVersion(
|
||||||
|
|
@ -377,7 +377,7 @@ fun MovieDetailsContent(
|
||||||
bringIntoViewRequester = bringIntoViewRequester,
|
bringIntoViewRequester = bringIntoViewRequester,
|
||||||
overviewOnClick = overviewOnClick,
|
overviewOnClick = overviewOnClick,
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth(.7f)
|
.fillMaxWidth(.75f)
|
||||||
.padding(bottom = 8.dp),
|
.padding(bottom = 8.dp),
|
||||||
)
|
)
|
||||||
ExpandablePlayButtons(
|
ExpandablePlayButtons(
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
package com.github.damontecres.wholphin.ui.detail.movie
|
package com.github.damontecres.wholphin.ui.detail.movie
|
||||||
|
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.interaction.collectIsFocusedAsState
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
|
@ -10,6 +12,8 @@ import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.widthIn
|
import androidx.compose.foundation.layout.widthIn
|
||||||
import androidx.compose.foundation.relocation.BringIntoViewRequester
|
import androidx.compose.foundation.relocation.BringIntoViewRequester
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.alpha
|
import androidx.compose.ui.draw.alpha
|
||||||
|
|
@ -63,7 +67,7 @@ fun MovieDetailsHeader(
|
||||||
text = movie.name ?: "",
|
text = movie.name ?: "",
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
style =
|
style =
|
||||||
MaterialTheme.typography.displayLarge.copy(
|
MaterialTheme.typography.displayMedium.copy(
|
||||||
shadow =
|
shadow =
|
||||||
Shadow(
|
Shadow(
|
||||||
color = Color.DarkGray,
|
color = Color.DarkGray,
|
||||||
|
|
@ -116,13 +120,26 @@ fun MovieDetailsHeader(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dto.taglines?.firstOrNull()?.let { tagline ->
|
||||||
|
Text(
|
||||||
|
text = tagline,
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Description
|
// Description
|
||||||
dto.overview?.let { overview ->
|
dto.overview?.let { overview ->
|
||||||
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
|
val focused = interactionSource.collectIsFocusedAsState().value
|
||||||
|
LaunchedEffect(focused) {
|
||||||
|
if (focused) bringIntoViewRequester.bringIntoView()
|
||||||
|
}
|
||||||
OverviewText(
|
OverviewText(
|
||||||
overview = overview,
|
overview = overview,
|
||||||
maxLines = 3,
|
maxLines = 3,
|
||||||
onClick = overviewOnClick,
|
onClick = overviewOnClick,
|
||||||
textBoxHeight = Dp.Unspecified,
|
textBoxHeight = Dp.Unspecified,
|
||||||
|
interactionSource = interactionSource,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
movie.data.people
|
movie.data.people
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.draw.drawWithContent
|
import androidx.compose.ui.draw.drawWithContent
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
|
import androidx.compose.ui.focus.onFocusChanged
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
|
@ -99,6 +100,7 @@ fun HomePageContent(
|
||||||
homeRows: List<HomeRow>,
|
homeRows: List<HomeRow>,
|
||||||
onClickItem: (BaseItem) -> Unit,
|
onClickItem: (BaseItem) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
onFocusPosition: ((RowColumn) -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
var position by rememberSaveable(stateSaver = RowColumnSaver) {
|
var position by rememberSaveable(stateSaver = RowColumnSaver) {
|
||||||
mutableStateOf(RowColumn(0, 0))
|
mutableStateOf(RowColumn(0, 0))
|
||||||
|
|
@ -198,7 +200,16 @@ fun HomePageContent(
|
||||||
).ifElse(
|
).ifElse(
|
||||||
RowColumn(rowIndex, index) == position,
|
RowColumn(rowIndex, index) == position,
|
||||||
Modifier.focusRequester(positionFocusRequester),
|
Modifier.focusRequester(positionFocusRequester),
|
||||||
),
|
).onFocusChanged {
|
||||||
|
if (it.isFocused) {
|
||||||
|
onFocusPosition?.invoke(
|
||||||
|
RowColumn(
|
||||||
|
rowIndex,
|
||||||
|
index,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
interactionSource = null,
|
interactionSource = null,
|
||||||
cardHeight = Cards.height2x3,
|
cardHeight = Cards.height2x3,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue