mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
Add people to movie details
This commit is contained in:
parent
0595c7174c
commit
ba92001412
8 changed files with 286 additions and 50 deletions
|
|
@ -0,0 +1,30 @@
|
||||||
|
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.UUID
|
||||||
|
import org.jellyfin.sdk.model.api.BaseItemPerson
|
||||||
|
import org.jellyfin.sdk.model.api.ImageType
|
||||||
|
import org.jellyfin.sdk.model.api.PersonKind
|
||||||
|
|
||||||
|
data class Person(
|
||||||
|
val id: UUID,
|
||||||
|
val name: String?,
|
||||||
|
val role: String?,
|
||||||
|
val type: PersonKind,
|
||||||
|
val imageUrl: String?,
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
fun fromDto(
|
||||||
|
dto: BaseItemPerson,
|
||||||
|
api: ApiClient,
|
||||||
|
): Person =
|
||||||
|
Person(
|
||||||
|
id = dto.id,
|
||||||
|
name = dto.name,
|
||||||
|
role = dto.role,
|
||||||
|
type = dto.type,
|
||||||
|
imageUrl = api.imageApi.getItemImageUrl(dto.id, ImageType.PRIMARY),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -32,11 +32,12 @@ fun ItemRow(
|
||||||
onLongClickItem: (BaseItem) -> Unit,
|
onLongClickItem: (BaseItem) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
cardContent: @Composable (
|
cardContent: @Composable (
|
||||||
|
index: Int,
|
||||||
item: BaseItem?,
|
item: BaseItem?,
|
||||||
modifier: Modifier,
|
modifier: Modifier,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
onLongClick: () -> Unit,
|
onLongClick: () -> Unit,
|
||||||
) -> Unit = { item, modifier, onClick, onLongClick ->
|
) -> Unit = { index, item, modifier, onClick, onLongClick ->
|
||||||
ItemCard(
|
ItemCard(
|
||||||
item = item,
|
item = item,
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
|
|
@ -86,6 +87,7 @@ fun ItemRow(
|
||||||
cardOnFocus?.invoke(it.isFocused, index)
|
cardOnFocus?.invoke(it.isFocused, index)
|
||||||
}
|
}
|
||||||
cardContent.invoke(
|
cardContent.invoke(
|
||||||
|
index,
|
||||||
item,
|
item,
|
||||||
cardModifier,
|
cardModifier,
|
||||||
{ if (item != null) onClickItem.invoke(item) },
|
{ if (item != null) onClickItem.invoke(item) },
|
||||||
|
|
@ -112,7 +114,7 @@ fun BannerItemRow(
|
||||||
onClickItem = onClickItem,
|
onClickItem = onClickItem,
|
||||||
onLongClickItem = onLongClickItem,
|
onLongClickItem = onLongClickItem,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
cardContent = { item, modifier, onClick, onLongClick ->
|
cardContent = { index, item, modifier, onClick, onLongClick ->
|
||||||
BannerCard(
|
BannerCard(
|
||||||
imageUrl = item?.imageUrl,
|
imageUrl = item?.imageUrl,
|
||||||
aspectRatio =
|
aspectRatio =
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
package com.github.damontecres.dolphin.ui.cards
|
||||||
|
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.interaction.collectIsFocusedAsState
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
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.data.model.Person
|
||||||
|
import com.github.damontecres.dolphin.ui.enableMarquee
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PersonCard(
|
||||||
|
item: Person,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
onLongClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
cardWidth: Dp = 150.dp,
|
||||||
|
cardHeight: Dp = 200.dp,
|
||||||
|
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||||
|
) {
|
||||||
|
val hideOverlayDelay = 1_000L
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = modifier,
|
||||||
|
onClick = onClick,
|
||||||
|
onLongClick = onLongClick,
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
modifier = Modifier.width(cardWidth),
|
||||||
|
) {
|
||||||
|
ItemCardImage(
|
||||||
|
imageUrl = item.imageUrl,
|
||||||
|
name = item.name,
|
||||||
|
showOverlay = true,
|
||||||
|
favorite = false,
|
||||||
|
watched = false,
|
||||||
|
unwatchedCount = -1,
|
||||||
|
watchedPercent = null,
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(cardHeight),
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = item.name ?: "",
|
||||||
|
maxLines = 1,
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.enableMarquee(focusedAfterDelay),
|
||||||
|
)
|
||||||
|
item.role?.let {
|
||||||
|
Text(
|
||||||
|
text = item.role,
|
||||||
|
maxLines = 1,
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.enableMarquee(focusedAfterDelay),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -137,7 +137,6 @@ fun ExpandablePlayButtons(
|
||||||
) {
|
) {
|
||||||
if (resumePosition > Duration.ZERO) {
|
if (resumePosition > Duration.ZERO) {
|
||||||
item {
|
item {
|
||||||
// LaunchedEffect(Unit) { firstFocus.tryRequestFocus() }
|
|
||||||
ExpandablePlayButton(
|
ExpandablePlayButton(
|
||||||
R.string.resume,
|
R.string.resume,
|
||||||
resumePosition,
|
resumePosition,
|
||||||
|
|
@ -170,6 +169,7 @@ fun ExpandablePlayButtons(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Watched button
|
// Watched button
|
||||||
item {
|
item {
|
||||||
ExpandableFaButton(
|
ExpandableFaButton(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package com.github.damontecres.dolphin.ui.data
|
||||||
|
|
||||||
|
import androidx.compose.runtime.saveable.Saver
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class RowColumn(
|
||||||
|
val row: Int,
|
||||||
|
val column: Int,
|
||||||
|
)
|
||||||
|
|
||||||
|
val RowColumnSaver =
|
||||||
|
Saver<RowColumn, List<Int>>(
|
||||||
|
save = { listOf(it.row, it.column) },
|
||||||
|
restore = { RowColumn(it[0], it[1]) },
|
||||||
|
)
|
||||||
|
|
@ -2,11 +2,17 @@ package com.github.damontecres.dolphin.ui.detail.movie
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
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.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.relocation.BringIntoViewRequester
|
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.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
|
@ -19,30 +25,39 @@ 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.focusRestorer
|
||||||
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.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.tv.material3.MaterialTheme
|
import androidx.tv.material3.MaterialTheme
|
||||||
|
import androidx.tv.material3.Text
|
||||||
import coil3.compose.AsyncImage
|
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.Person
|
||||||
import com.github.damontecres.dolphin.data.model.Video
|
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.PersonCard
|
||||||
import com.github.damontecres.dolphin.ui.components.ErrorMessage
|
import com.github.damontecres.dolphin.ui.components.ErrorMessage
|
||||||
import com.github.damontecres.dolphin.ui.components.ExpandablePlayButtons
|
import com.github.damontecres.dolphin.ui.components.ExpandablePlayButtons
|
||||||
import com.github.damontecres.dolphin.ui.components.LoadingPage
|
import com.github.damontecres.dolphin.ui.components.LoadingPage
|
||||||
import com.github.damontecres.dolphin.ui.detail.LoadingItemViewModel
|
import com.github.damontecres.dolphin.ui.detail.LoadingItemViewModel
|
||||||
import com.github.damontecres.dolphin.ui.isNotNullOrBlank
|
import com.github.damontecres.dolphin.ui.isNotNullOrBlank
|
||||||
|
import com.github.damontecres.dolphin.ui.letNotEmpty
|
||||||
import com.github.damontecres.dolphin.ui.nav.Destination
|
import com.github.damontecres.dolphin.ui.nav.Destination
|
||||||
import com.github.damontecres.dolphin.ui.nav.NavigationManager
|
import com.github.damontecres.dolphin.ui.nav.NavigationManager
|
||||||
import com.github.damontecres.dolphin.ui.tryRequestFocus
|
import com.github.damontecres.dolphin.ui.tryRequestFocus
|
||||||
import com.github.damontecres.dolphin.util.ExceptionHandler
|
import com.github.damontecres.dolphin.util.ExceptionHandler
|
||||||
import com.github.damontecres.dolphin.util.LoadingState
|
import com.github.damontecres.dolphin.util.LoadingState
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.jellyfin.sdk.api.client.ApiClient
|
import org.jellyfin.sdk.api.client.ApiClient
|
||||||
import org.jellyfin.sdk.model.extensions.ticks
|
import org.jellyfin.sdk.model.extensions.ticks
|
||||||
|
import java.util.UUID
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
|
|
||||||
|
|
@ -51,7 +66,23 @@ class MovieViewModel
|
||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(
|
||||||
api: ApiClient,
|
api: ApiClient,
|
||||||
) : LoadingItemViewModel<Video>(api)
|
) : LoadingItemViewModel<Video>(api) {
|
||||||
|
val people = MutableLiveData<List<Person>>(listOf())
|
||||||
|
|
||||||
|
override fun init(
|
||||||
|
itemId: UUID,
|
||||||
|
potential: BaseItem?,
|
||||||
|
): Job? =
|
||||||
|
viewModelScope.launch(ExceptionHandler()) {
|
||||||
|
super.init(itemId, potential)?.join()
|
||||||
|
item.value?.let { item ->
|
||||||
|
people.value =
|
||||||
|
item.data.people?.letNotEmpty { people ->
|
||||||
|
people.map { Person.fromDto(it, api) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MovieDetails(
|
fun MovieDetails(
|
||||||
|
|
@ -65,6 +96,7 @@ fun MovieDetails(
|
||||||
viewModel.init(destination.itemId, destination.item)
|
viewModel.init(destination.itemId, destination.item)
|
||||||
}
|
}
|
||||||
val item by viewModel.item.observeAsState()
|
val item by viewModel.item.observeAsState()
|
||||||
|
val people by viewModel.people.observeAsState(listOf())
|
||||||
val loading by viewModel.loading.observeAsState(LoadingState.Loading)
|
val loading by viewModel.loading.observeAsState(LoadingState.Loading)
|
||||||
when (val state = loading) {
|
when (val state = loading) {
|
||||||
is LoadingState.Error -> ErrorMessage(state)
|
is LoadingState.Error -> ErrorMessage(state)
|
||||||
|
|
@ -72,10 +104,14 @@ fun MovieDetails(
|
||||||
LoadingState.Success -> {
|
LoadingState.Success -> {
|
||||||
item?.let {
|
item?.let {
|
||||||
MovieDetailsContent(
|
MovieDetailsContent(
|
||||||
preferences,
|
preferences = preferences,
|
||||||
navigationManager,
|
navigationManager = navigationManager,
|
||||||
it,
|
movie = it,
|
||||||
modifier,
|
people = people,
|
||||||
|
overviewOnClick = {},
|
||||||
|
moreOnClick = {},
|
||||||
|
watchOnClick = {},
|
||||||
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -87,6 +123,10 @@ fun MovieDetailsContent(
|
||||||
preferences: UserPreferences,
|
preferences: UserPreferences,
|
||||||
navigationManager: NavigationManager,
|
navigationManager: NavigationManager,
|
||||||
movie: BaseItem,
|
movie: BaseItem,
|
||||||
|
people: List<Person>,
|
||||||
|
overviewOnClick: () -> Unit,
|
||||||
|
watchOnClick: () -> Unit,
|
||||||
|
moreOnClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
@ -97,7 +137,7 @@ fun MovieDetailsContent(
|
||||||
|
|
||||||
val bringIntoViewRequester = remember { BringIntoViewRequester() }
|
val bringIntoViewRequester = remember { BringIntoViewRequester() }
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
bringIntoViewRequester.bringIntoView()
|
// bringIntoViewRequester.bringIntoView()
|
||||||
focusRequester.tryRequestFocus()
|
focusRequester.tryRequestFocus()
|
||||||
}
|
}
|
||||||
Box(modifier = modifier) {
|
Box(modifier = modifier) {
|
||||||
|
|
@ -136,40 +176,77 @@ fun MovieDetailsContent(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
) {
|
) {
|
||||||
item {
|
item {
|
||||||
MovieDetailsHeader(
|
Column(
|
||||||
movie = movie,
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
bringIntoViewRequester = bringIntoViewRequester,
|
modifier = Modifier.bringIntoViewRequester(bringIntoViewRequester),
|
||||||
overviewOnClick = {},
|
) {
|
||||||
Modifier
|
MovieDetailsHeader(
|
||||||
.fillMaxWidth(.7f),
|
movie = movie,
|
||||||
)
|
bringIntoViewRequester = bringIntoViewRequester,
|
||||||
|
overviewOnClick = overviewOnClick,
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth(.7f),
|
||||||
|
)
|
||||||
|
ExpandablePlayButtons(
|
||||||
|
resumePosition = resumePosition,
|
||||||
|
watched = dto.userData?.played ?: false,
|
||||||
|
playOnClick = {
|
||||||
|
navigationManager.navigateTo(
|
||||||
|
Destination.Playback(
|
||||||
|
movie.id,
|
||||||
|
it.inWholeMilliseconds,
|
||||||
|
movie,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
moreOnClick = moreOnClick,
|
||||||
|
watchOnClick = watchOnClick,
|
||||||
|
buttonOnFocusChanged = {
|
||||||
|
if (it.isFocused) {
|
||||||
|
scope.launch(ExceptionHandler()) {
|
||||||
|
bringIntoViewRequester.bringIntoView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.focusRequester(focusRequester),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
item {
|
if (people.isNotEmpty()) {
|
||||||
ExpandablePlayButtons(
|
item {
|
||||||
resumePosition = resumePosition,
|
Column(
|
||||||
watched = dto.userData?.played ?: false,
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
playOnClick = {
|
modifier =
|
||||||
navigationManager.navigateTo(
|
Modifier
|
||||||
Destination.Playback(
|
.fillMaxWidth()
|
||||||
movie.id,
|
.padding(top = 120.dp),
|
||||||
it.inWholeMilliseconds,
|
) {
|
||||||
movie,
|
Text(
|
||||||
),
|
text = "People",
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
color = MaterialTheme.colorScheme.onBackground,
|
||||||
)
|
)
|
||||||
},
|
LazyRow(
|
||||||
moreOnClick = {
|
state = rememberLazyListState(),
|
||||||
// TODO
|
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
},
|
contentPadding = PaddingValues(8.dp),
|
||||||
watchOnClick = {
|
modifier =
|
||||||
// TODO
|
Modifier
|
||||||
},
|
.padding(start = 16.dp)
|
||||||
buttonOnFocusChanged = {
|
.fillMaxWidth()
|
||||||
scope.launch(ExceptionHandler()) {
|
.focusRestorer(),
|
||||||
bringIntoViewRequester.bringIntoView()
|
) {
|
||||||
|
itemsIndexed(people) { index, item ->
|
||||||
|
PersonCard(
|
||||||
|
item = item,
|
||||||
|
onClick = {},
|
||||||
|
onLongClick = {},
|
||||||
|
modifier = Modifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
modifier = Modifier.focusRequester(focusRequester),
|
}
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,13 +11,14 @@ 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.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
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.saveable.rememberSaveable
|
||||||
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
|
||||||
|
|
@ -44,6 +45,8 @@ import com.github.damontecres.dolphin.ui.cards.ItemRow
|
||||||
import com.github.damontecres.dolphin.ui.components.DotSeparatedRow
|
import com.github.damontecres.dolphin.ui.components.DotSeparatedRow
|
||||||
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
|
||||||
|
import com.github.damontecres.dolphin.ui.data.RowColumn
|
||||||
|
import com.github.damontecres.dolphin.ui.data.RowColumnSaver
|
||||||
import com.github.damontecres.dolphin.ui.ifElse
|
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.nav.NavigationManager
|
import com.github.damontecres.dolphin.ui.nav.NavigationManager
|
||||||
|
|
@ -90,6 +93,10 @@ fun MainPageContent(
|
||||||
homeRows: List<HomeRow>,
|
homeRows: List<HomeRow>,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
|
var position by rememberSaveable(stateSaver = RowColumnSaver) {
|
||||||
|
mutableStateOf(RowColumn(0, 0))
|
||||||
|
}
|
||||||
|
// TODO use position instead?
|
||||||
var focusedItem by remember {
|
var focusedItem by remember {
|
||||||
mutableStateOf<BaseItem?>(
|
mutableStateOf<BaseItem?>(
|
||||||
homeRows.getOrNull(0)?.items?.getOrNull(
|
homeRows.getOrNull(0)?.items?.getOrNull(
|
||||||
|
|
@ -98,7 +105,10 @@ fun MainPageContent(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
LaunchedEffect(Unit) { if (focusedItem != null) focusRequester.tryRequestFocus() }
|
val positionFocusRequester = remember { FocusRequester() }
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
positionFocusRequester.tryRequestFocus()
|
||||||
|
}
|
||||||
Box(modifier = modifier) {
|
Box(modifier = modifier) {
|
||||||
if (focusedItem?.backdropImageUrl.isNotNullOrBlank()) {
|
if (focusedItem?.backdropImageUrl.isNotNullOrBlank()) {
|
||||||
val gradientColor = MaterialTheme.colorScheme.background
|
val gradientColor = MaterialTheme.colorScheme.background
|
||||||
|
|
@ -152,7 +162,7 @@ fun MainPageContent(
|
||||||
),
|
),
|
||||||
modifier = Modifier,
|
modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
items(homeRows) { row ->
|
itemsIndexed(homeRows) { rowIndex, row ->
|
||||||
ItemRow(
|
ItemRow(
|
||||||
title = row.title ?: stringResource(row.section.nameRes),
|
title = row.title ?: stringResource(row.section.nameRes),
|
||||||
items = row.items,
|
items = row.items,
|
||||||
|
|
@ -160,11 +170,14 @@ fun MainPageContent(
|
||||||
navigationManager.navigateTo(it.destination())
|
navigationManager.navigateTo(it.destination())
|
||||||
},
|
},
|
||||||
cardOnFocus = { isFocused, index ->
|
cardOnFocus = { isFocused, index ->
|
||||||
focusedItem = row.items.getOrNull(index)
|
if (isFocused) {
|
||||||
|
focusedItem = row.items.getOrNull(index)
|
||||||
|
position = RowColumn(rowIndex, index)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onLongClickItem = {},
|
onLongClickItem = {},
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
cardContent = { item, modifier, onClick, onLongClick ->
|
cardContent = { index, item, modifier, onClick, onLongClick ->
|
||||||
// TODO better aspect ration handling?
|
// TODO better aspect ration handling?
|
||||||
BannerCard(
|
BannerCard(
|
||||||
imageUrl = item?.imageUrl,
|
imageUrl = item?.imageUrl,
|
||||||
|
|
@ -175,10 +188,14 @@ fun MainPageContent(
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
onLongClick = onLongClick,
|
onLongClick = onLongClick,
|
||||||
modifier =
|
modifier =
|
||||||
modifier.ifElse(
|
modifier
|
||||||
focusedItem == item,
|
.ifElse(
|
||||||
Modifier.focusRequester(focusRequester),
|
focusedItem == item,
|
||||||
),
|
Modifier.focusRequester(focusRequester),
|
||||||
|
).ifElse(
|
||||||
|
RowColumn(rowIndex, index) == position,
|
||||||
|
Modifier.focusRequester(positionFocusRequester),
|
||||||
|
),
|
||||||
interactionSource = null,
|
interactionSource = null,
|
||||||
cardHeight = 200.dp,
|
cardHeight = 200.dp,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ private val darkScheme =
|
||||||
inverseSurface = inverseSurfaceDark,
|
inverseSurface = inverseSurfaceDark,
|
||||||
inverseOnSurface = inverseOnSurfaceDark,
|
inverseOnSurface = inverseOnSurfaceDark,
|
||||||
inversePrimary = inversePrimaryDark,
|
inversePrimary = inversePrimaryDark,
|
||||||
border = inversePrimaryDark.copy(alpha = .5f),
|
border = inversePrimaryDark.copy(alpha = .75f),
|
||||||
)
|
)
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue