diff --git a/app-tv/src/main/java/hu/bbara/purefin/tv/TvActivity.kt b/app-tv/src/main/java/hu/bbara/purefin/tv/TvActivity.kt index 840258a..890e0ff 100644 --- a/app-tv/src/main/java/hu/bbara/purefin/tv/TvActivity.kt +++ b/app-tv/src/main/java/hu/bbara/purefin/tv/TvActivity.kt @@ -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() }, diff --git a/app/src/main/java/hu/bbara/purefin/PurefinActivity.kt b/app/src/main/java/hu/bbara/purefin/PurefinActivity.kt index 222072a..49e5eec 100644 --- a/app/src/main/java/hu/bbara/purefin/PurefinActivity.kt +++ b/app/src/main/java/hu/bbara/purefin/PurefinActivity.kt @@ -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() }, diff --git a/app/src/main/java/hu/bbara/purefin/app/content/episode/EpisodeComponents.kt b/app/src/main/java/hu/bbara/purefin/app/content/episode/EpisodeComponents.kt index 84d65e9..36ee293 100644 --- a/app/src/main/java/hu/bbara/purefin/app/content/episode/EpisodeComponents.kt +++ b/app/src/main/java/hu/bbara/purefin/app/content/episode/EpisodeComponents.kt @@ -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 + .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 - .weight(1f) - .padding(horizontal = 12.dp), + .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 = { }) diff --git a/app/src/main/java/hu/bbara/purefin/app/content/episode/EpisodeScreen.kt b/app/src/main/java/hu/bbara/purefin/app/content/episode/EpisodeScreen.kt index 0b35f12..bed2efd 100644 --- a/app/src/main/java/hu/bbara/purefin/app/content/episode/EpisodeScreen.kt +++ b/app/src/main/java/hu/bbara/purefin/app/content/episode/EpisodeScreen.kt @@ -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( } } } + + diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/navigation/NavigationManager.kt b/core/data/src/main/java/hu/bbara/purefin/core/data/navigation/NavigationManager.kt index 7d9b4a9..13f9237 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/navigation/NavigationManager.kt +++ b/core/data/src/main/java/hu/bbara/purefin/core/data/navigation/NavigationManager.kt @@ -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 = staticCompositionLocalOf { error("NavigationManager not provided") } + +val LocalNavigationBackStack = compositionLocalOf> { emptyList() } diff --git a/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/content/episode/EpisodeScreenViewModel.kt b/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/content/episode/EpisodeScreenViewModel.kt index cb8eeda..390e626 100644 --- a/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/content/episode/EpisodeScreenViewModel.kt +++ b/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/content/episode/EpisodeScreenViewModel.kt @@ -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) {