Fix interactive episode navigation buttons

This commit is contained in:
2026-03-24 21:34:38 +01:00
parent 5137fd9893
commit f85624b159
6 changed files with 82 additions and 12 deletions

View File

@@ -34,6 +34,7 @@ import dagger.hilt.android.AndroidEntryPoint
import hu.bbara.purefin.common.ui.PurefinWaitingScreen
import hu.bbara.purefin.core.data.client.JellyfinApiClient
import hu.bbara.purefin.core.data.client.JellyfinAuthInterceptor
import hu.bbara.purefin.core.data.navigation.LocalNavigationBackStack
import hu.bbara.purefin.core.data.navigation.LocalNavigationManager
import hu.bbara.purefin.core.data.navigation.NavigationCommand
import hu.bbara.purefin.core.data.navigation.NavigationManager
@@ -164,7 +165,10 @@ class TvActivity : ComponentActivity() {
}
}
CompositionLocalProvider(LocalNavigationManager provides navigationManager) {
CompositionLocalProvider(
LocalNavigationManager provides navigationManager,
LocalNavigationBackStack provides backStack.toList()
) {
NavDisplay(
backStack = backStack,
onBack = { navigationManager.pop() },

View File

@@ -34,6 +34,7 @@ import dagger.hilt.android.AndroidEntryPoint
import hu.bbara.purefin.common.ui.PurefinWaitingScreen
import hu.bbara.purefin.core.data.client.JellyfinApiClient
import hu.bbara.purefin.core.data.client.JellyfinAuthInterceptor
import hu.bbara.purefin.core.data.navigation.LocalNavigationBackStack
import hu.bbara.purefin.core.data.navigation.LocalNavigationManager
import hu.bbara.purefin.core.data.navigation.NavigationCommand
import hu.bbara.purefin.core.data.navigation.NavigationManager
@@ -165,7 +166,10 @@ class PurefinActivity : ComponentActivity() {
}
}
CompositionLocalProvider(LocalNavigationManager provides navigationManager) {
CompositionLocalProvider(
LocalNavigationManager provides navigationManager,
LocalNavigationBackStack provides backStack.toList()
) {
NavDisplay(
backStack = backStack,
onBack = { navigationManager.pop() },

View File

@@ -16,6 +16,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Add
@@ -49,9 +50,23 @@ import hu.bbara.purefin.core.model.Episode
import hu.bbara.purefin.feature.download.DownloadState
import hu.bbara.purefin.player.PlayerActivity
internal sealed interface EpisodeTopBarShortcut {
val label: String
val onClick: () -> Unit
data class Series(override val onClick: () -> Unit) : EpisodeTopBarShortcut {
override val label: String = "Series"
}
data class Home(override val onClick: () -> Unit) : EpisodeTopBarShortcut {
override val label: String = "Home"
}
}
@Composable
internal fun EpisodeTopBar(
seriesTitle: String?,
shortcut: EpisodeTopBarShortcut?,
onBack: () -> Unit,
onSeriesClick: () -> Unit,
modifier: Modifier = Modifier
@@ -70,29 +85,50 @@ internal fun EpisodeTopBar(
contentDescription = "Back",
onClick = onBack
)
if (!seriesTitle.isNullOrBlank()) {
when {
shortcut != null -> {
Box(
modifier = Modifier
.weight(1f)
.padding(horizontal = 12.dp),
.height(52.dp)
.clickable(onClick = shortcut.onClick),
contentAlignment = Alignment.Center
) {
Text(
text = shortcut.label,
color = scheme.onBackground,
fontSize = 16.sp,
fontWeight = FontWeight.SemiBold,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier
.clip(CircleShape)
.background(scheme.background.copy(alpha = 0.65f))
.padding(horizontal = 16.dp, vertical = 10.dp)
)
}
}
!seriesTitle.isNullOrBlank() -> {
Box(
modifier = Modifier
.height(52.dp)
.clickable(onClick = onSeriesClick),
contentAlignment = Alignment.Center
) {
Text(
text = seriesTitle,
color = scheme.onBackground,
fontSize = 14.sp,
fontSize = 16.sp,
fontWeight = FontWeight.SemiBold,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier
.clip(RoundedCornerShape(999.dp))
.clip(CircleShape)
.background(scheme.background.copy(alpha = 0.65f))
.clickable(onClick = onSeriesClick)
.padding(horizontal = 16.dp, vertical = 10.dp)
)
}
} else {
Spacer(modifier = Modifier.weight(1f))
}
else -> Spacer(modifier = Modifier.weight(1f))
}
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
GhostIconButton(icon = Icons.Outlined.Cast, contentDescription = "Cast", onClick = { })

View File

@@ -15,12 +15,16 @@ import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import hu.bbara.purefin.common.ui.PurefinWaitingScreen
import hu.bbara.purefin.common.ui.components.MediaHero
import hu.bbara.purefin.core.data.navigation.EpisodeDto
import hu.bbara.purefin.core.data.navigation.LocalNavigationBackStack
import hu.bbara.purefin.core.data.navigation.LocalNavigationManager
import hu.bbara.purefin.core.data.navigation.Route
import hu.bbara.purefin.core.model.Episode
import hu.bbara.purefin.feature.download.DownloadState
import hu.bbara.purefin.feature.shared.content.episode.EpisodeScreenViewModel
@@ -31,6 +35,9 @@ fun EpisodeScreen(
viewModel: EpisodeScreenViewModel = hiltViewModel(),
modifier: Modifier = Modifier
) {
val navigationManager = LocalNavigationManager.current
val backStack = LocalNavigationBackStack.current
val previousRoute = remember(backStack) { backStack.getOrNull(backStack.lastIndex - 1) }
LaunchedEffect(episode) {
viewModel.selectEpisode(
@@ -69,6 +76,15 @@ fun EpisodeScreen(
EpisodeScreenInternal(
episode = episode.value!!,
seriesTitle = seriesTitle.value,
topBarShortcut = remember(previousRoute) {
when (previousRoute) {
is Route.SeriesRoute -> EpisodeTopBarShortcut.Home {
navigationManager.replaceAll(Route.Home)
}
Route.Home -> EpisodeTopBarShortcut.Series(viewModel::onSeriesClick)
else -> null
}
},
downloadState = downloadState.value,
onBack = viewModel::onBack,
onSeriesClick = viewModel::onSeriesClick,
@@ -81,6 +97,7 @@ fun EpisodeScreen(
private fun EpisodeScreenInternal(
episode: Episode,
seriesTitle: String?,
topBarShortcut: EpisodeTopBarShortcut?,
downloadState: DownloadState,
onBack: () -> Unit,
onSeriesClick: () -> Unit,
@@ -94,6 +111,7 @@ private fun EpisodeScreenInternal(
topBar = {
EpisodeTopBar(
seriesTitle = seriesTitle,
shortcut = topBarShortcut,
onBack = onBack,
onSeriesClick = onSeriesClick,
modifier = Modifier
@@ -123,3 +141,5 @@ private fun EpisodeScreenInternal(
}
}
}

View File

@@ -1,6 +1,7 @@
package hu.bbara.purefin.core.data.navigation
import androidx.compose.runtime.ProvidableCompositionLocal
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.staticCompositionLocalOf
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
@@ -43,3 +44,5 @@ class DefaultNavigationManager : NavigationManager {
val LocalNavigationManager: ProvidableCompositionLocal<NavigationManager> =
staticCompositionLocalOf { error("NavigationManager not provided") }
val LocalNavigationBackStack = compositionLocalOf<List<Route>> { emptyList() }

View File

@@ -5,6 +5,8 @@ import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import hu.bbara.purefin.core.data.AppContentRepository
import hu.bbara.purefin.core.data.navigation.NavigationManager
import hu.bbara.purefin.core.data.navigation.Route
import hu.bbara.purefin.core.data.navigation.SeriesDto
import hu.bbara.purefin.core.model.Episode
import hu.bbara.purefin.feature.download.DownloadState
import hu.bbara.purefin.feature.download.MediaDownloadManager
@@ -50,7 +52,8 @@ class EpisodeScreenViewModel @Inject constructor(
}
fun onSeriesClick() {
navigationManager.pop()
val seriesId = _seriesId.value ?: return
navigationManager.navigate(Route.SeriesRoute(SeriesDto(id = seriesId)))
}
fun selectEpisode(seriesId: UUID, seasonId: UUID, episodeId: UUID) {