Compare commits

..

3 Commits

Author SHA1 Message Date
4c986432f6 refactor: extract common part from the HomeTopBar and make component logic reusable 2026-04-27 21:11:25 +02:00
afdd9b860f feat: integrate dynamic library navigation in TV drawer
Refactors the TV navigation drawer to use specific library routes instead
of a static libraries overview. Added a `NavDisplay` with a backstack to
manage navigation between the Home screen and individual Library screens
directly from the drawer.

- Replace `TvDrawerDestination` enum with `Route` in the navigation drawer
- Dynamically populate drawer items based on available libraries
- Add `NavDisplay` to `TvAppScreen` for nested TV navigation
- Connect `TvLibraryScreen` to `AppViewModel` for media selection handling
- Remove back button from `TvLibraryScreen` in favor of drawer navigation
- Update `TvNavigationDrawerTest` to reflect routing changes
2026-04-26 18:08:40 +02:00
51ffd14804 refactor: reduce maxLines for primary text in HomeBrowseCard 2026-04-26 14:53:57 +02:00
14 changed files with 234 additions and 269 deletions

View File

@@ -24,9 +24,12 @@ import androidx.compose.ui.test.performKeyInput
import androidx.compose.ui.test.performSemanticsAction import androidx.compose.ui.test.performSemanticsAction
import androidx.compose.ui.test.pressKey import androidx.compose.ui.test.pressKey
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import hu.bbara.purefin.navigation.LibraryDto
import hu.bbara.purefin.navigation.Route
import hu.bbara.purefin.ui.theme.AppTheme import hu.bbara.purefin.ui.theme.AppTheme
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import java.util.UUID
@OptIn(ExperimentalTestApi::class) @OptIn(ExperimentalTestApi::class)
class TvNavigationDrawerTest { class TvNavigationDrawerTest {
@@ -56,20 +59,26 @@ class TvNavigationDrawerTest {
@Test @Test
fun tvNavigationDrawer_updatesSelectedDestination() { fun tvNavigationDrawer_updatesSelectedDestination() {
var selectedDestination by mutableStateOf(TvDrawerDestination.HOME) val libraryRoute = Route.LibraryRoute(
library = LibraryDto(
id = UUID.fromString("22222222-2222-2222-2222-222222222222"),
name = "Movies"
)
)
var selectedDestination by mutableStateOf<Route>(Route.Home)
composeRule.setContent { composeRule.setContent {
AppTheme { AppTheme {
TvNavigationDrawer( TvNavigationDrawer(
destinations = listOf( destinations = listOf(
TvDrawerDestinationItem( TvDrawerDestinationItem(
destination = TvDrawerDestination.HOME, destination = Route.Home,
label = "Home", label = "Home",
icon = Icons.Outlined.Home icon = Icons.Outlined.Home
), ),
TvDrawerDestinationItem( TvDrawerDestinationItem(
destination = TvDrawerDestination.LIBRARIES, destination = libraryRoute,
label = "Libraries", label = "Movies",
icon = Icons.Outlined.Collections icon = Icons.Outlined.Collections
) )
), ),

View File

@@ -1,6 +1,8 @@
package hu.bbara.purefin.navigation package hu.bbara.purefin.navigation
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation3.runtime.EntryProviderScope import androidx.navigation3.runtime.EntryProviderScope
import hu.bbara.purefin.feature.browse.home.AppViewModel
import hu.bbara.purefin.ui.screen.episode.TvEpisodeScreen import hu.bbara.purefin.ui.screen.episode.TvEpisodeScreen
import hu.bbara.purefin.ui.screen.movie.TvMovieScreen import hu.bbara.purefin.ui.screen.movie.TvMovieScreen
import hu.bbara.purefin.ui.screen.series.TvSeriesScreen import hu.bbara.purefin.ui.screen.series.TvSeriesScreen
@@ -51,6 +53,10 @@ fun EntryProviderScope<Route>.tvPlayerSection() {
fun EntryProviderScope<Route>.tvLibrarySection() { fun EntryProviderScope<Route>.tvLibrarySection() {
entry<Route.LibraryRoute> { route -> entry<Route.LibraryRoute> { route ->
TvLibraryScreen(library = route.library) val viewModel: AppViewModel = hiltViewModel()
TvLibraryScreen(
library = route.library,
onMediaSelected = viewModel::onMediaSelected
)
} }
} }

View File

@@ -2,7 +2,6 @@ package hu.bbara.purefin.ui.screen
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Collections
import androidx.compose.material.icons.outlined.Home import androidx.compose.material.icons.outlined.Home
import androidx.compose.material.icons.outlined.Movie import androidx.compose.material.icons.outlined.Movie
import androidx.compose.material.icons.outlined.Tv import androidx.compose.material.icons.outlined.Tv
@@ -10,18 +9,24 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.LifecycleResumeEffect import androidx.lifecycle.compose.LifecycleResumeEffect
import hu.bbara.purefin.model.LibraryKind import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator
import androidx.navigation3.runtime.NavBackStack
import androidx.navigation3.runtime.entryProvider
import androidx.navigation3.runtime.rememberNavBackStack
import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
import androidx.navigation3.ui.NavDisplay
import hu.bbara.purefin.feature.browse.home.AppViewModel import hu.bbara.purefin.feature.browse.home.AppViewModel
import hu.bbara.purefin.model.LibraryKind
import hu.bbara.purefin.navigation.LibraryDto
import hu.bbara.purefin.navigation.LocalNavigationManager
import hu.bbara.purefin.navigation.Route
import hu.bbara.purefin.ui.screen.home.TvHomeScreen import hu.bbara.purefin.ui.screen.home.TvHomeScreen
import hu.bbara.purefin.ui.screen.home.components.TvDrawerDestination
import hu.bbara.purefin.ui.screen.home.components.TvDrawerDestinationItem import hu.bbara.purefin.ui.screen.home.components.TvDrawerDestinationItem
import hu.bbara.purefin.ui.screen.home.components.TvNavigationDrawer import hu.bbara.purefin.ui.screen.home.components.TvNavigationDrawer
import hu.bbara.purefin.ui.screen.libraries.TvLibrariesOverviewScreen import hu.bbara.purefin.ui.screen.library.TvLibraryScreen
@Composable @Composable
fun TvAppScreen( fun TvAppScreen(
@@ -32,45 +37,41 @@ fun TvAppScreen(
val continueWatching by viewModel.continueWatching.collectAsState() val continueWatching by viewModel.continueWatching.collectAsState()
val nextUp by viewModel.nextUp.collectAsState() val nextUp by viewModel.nextUp.collectAsState()
val latestLibraryContent by viewModel.latestLibraryContent.collectAsState() val latestLibraryContent by viewModel.latestLibraryContent.collectAsState()
val navigationManager = LocalNavigationManager.current
var selectedDestination by rememberSaveable { androidx.compose.runtime.mutableStateOf(TvDrawerDestination.HOME) } @Suppress("UNCHECKED_CAST")
val backStack = rememberNavBackStack(Route.Home) as NavBackStack<Route>
val selectedDestination = backStack.lastOrNull() ?: Route.Home
val destinations = remember(libraries, selectedDestination) { val destinations = remember(libraries) {
listOf( listOf(
TvDrawerDestinationItem( TvDrawerDestinationItem(
destination = TvDrawerDestination.HOME, destination = Route.Home,
label = "Home", label = "Home",
icon = Icons.Outlined.Home, icon = Icons.Outlined.Home
selected = selectedDestination == TvDrawerDestination.HOME )
), ) + libraries.map { library ->
val destination = Route.LibraryRoute(
library = LibraryDto(id = library.id, name = library.name)
)
TvDrawerDestinationItem( TvDrawerDestinationItem(
destination = TvDrawerDestination.LIBRARIES, destination = destination,
label = "Libraries", label = library.name,
icon = when { icon = when (library.type) {
libraries.any { it.type == LibraryKind.MOVIES } -> Icons.Outlined.Movie LibraryKind.MOVIES -> Icons.Outlined.Movie
libraries.any { it.type == LibraryKind.SERIES } -> Icons.Outlined.Tv LibraryKind.SERIES -> Icons.Outlined.Tv
else -> Icons.Outlined.Collections }
},
selected = selectedDestination == TvDrawerDestination.LIBRARIES
)
) )
} }
}
LifecycleResumeEffect(Unit) { LifecycleResumeEffect(Unit) {
viewModel.onResumed() viewModel.onResumed()
onPauseOrDispose { } onPauseOrDispose { }
} }
TvNavigationDrawer( val tvEntryProvider = entryProvider {
destinations = destinations, entry<Route.Home> {
selectedDestination = selectedDestination,
onDestinationSelected = { destination ->
selectedDestination = destination
},
modifier = modifier.fillMaxSize()
) {
when (selectedDestination) {
TvDrawerDestination.HOME -> {
TvHomeScreen( TvHomeScreen(
libraries = libraries, libraries = libraries,
libraryContent = latestLibraryContent, libraryContent = latestLibraryContent,
@@ -80,15 +81,44 @@ fun TvAppScreen(
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
) )
} }
TvDrawerDestination.LIBRARIES -> { entry<Route.LibraryRoute> { route ->
TvLibrariesOverviewScreen( TvLibraryScreen(
libraries = libraries, library = route.library,
onLibrarySelected = { library -> onMediaSelected = viewModel::onMediaSelected,
viewModel.onLibrarySelected(library.id, library.name)
},
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
) )
} }
} }
TvNavigationDrawer(
destinations = destinations,
selectedDestination = selectedDestination,
onDestinationSelected = { destination ->
if (selectedDestination != destination) {
backStack.clear()
backStack.add(Route.Home)
if (destination is Route.LibraryRoute) {
backStack.add(destination)
}
}
},
modifier = modifier.fillMaxSize()
) {
NavDisplay(
backStack = backStack,
onBack = {
if (backStack.size > 1) {
backStack.removeLastOrNull()
} else {
navigationManager.pop()
}
},
entryDecorators = listOf(
rememberSaveableStateHolderNavEntryDecorator(),
rememberViewModelStoreNavEntryDecorator()
),
entryProvider = tvEntryProvider,
modifier = Modifier.fillMaxSize()
)
} }
} }

View File

@@ -1,15 +1,10 @@
package hu.bbara.purefin.ui.screen.home.components package hu.bbara.purefin.ui.screen.home.components
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import hu.bbara.purefin.navigation.Route
enum class TvDrawerDestination {
HOME,
LIBRARIES
}
data class TvDrawerDestinationItem( data class TvDrawerDestinationItem(
val destination: TvDrawerDestination, val destination: Route,
val label: String, val label: String,
val icon: ImageVector, val icon: ImageVector
val selected: Boolean = false
) )

View File

@@ -30,9 +30,10 @@ import androidx.tv.material3.Icon
import androidx.tv.material3.NavigationDrawer import androidx.tv.material3.NavigationDrawer
import androidx.tv.material3.NavigationDrawerItem import androidx.tv.material3.NavigationDrawerItem
import androidx.tv.material3.Text import androidx.tv.material3.Text
import androidx.tv.material3.MaterialTheme as TvMaterialTheme
import androidx.tv.material3.darkColorScheme import androidx.tv.material3.darkColorScheme
import hu.bbara.purefin.navigation.Route
import hu.bbara.purefin.tv.R import hu.bbara.purefin.tv.R
import androidx.tv.material3.MaterialTheme as TvMaterialTheme
internal const val TvDrawerItemTagPrefix = "tv-drawer-item-" internal const val TvDrawerItemTagPrefix = "tv-drawer-item-"
internal const val TvDrawerTitleTag = "tv-drawer-title" internal const val TvDrawerTitleTag = "tv-drawer-title"
@@ -43,8 +44,8 @@ private val TvDrawerExpandedWidth = 280.dp
@Composable @Composable
fun TvNavigationDrawer( fun TvNavigationDrawer(
destinations: List<TvDrawerDestinationItem>, destinations: List<TvDrawerDestinationItem>,
selectedDestination: TvDrawerDestination, selectedDestination: Route,
onDestinationSelected: (TvDrawerDestination) -> Unit, onDestinationSelected: (Route) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
content: @Composable () -> Unit content: @Composable () -> Unit
) { ) {
@@ -132,15 +133,15 @@ fun TvDrawerHeader(
private fun androidx.tv.material3.NavigationDrawerScope.TvNavigationDrawerRail( private fun androidx.tv.material3.NavigationDrawerScope.TvNavigationDrawerRail(
drawerValue: DrawerValue, drawerValue: DrawerValue,
destinations: List<TvDrawerDestinationItem>, destinations: List<TvDrawerDestinationItem>,
selectedDestination: TvDrawerDestination, selectedDestination: Route,
onDestinationSelected: (TvDrawerDestination) -> Unit, onDestinationSelected: (Route) -> Unit,
) { ) {
val expanded = drawerValue == DrawerValue.Open val expanded = drawerValue == DrawerValue.Open
val drawerWidth = animateDpAsState( val drawerWidth = animateDpAsState(
targetValue = if (expanded) TvDrawerExpandedWidth else TvDrawerCollapsedWidth, targetValue = if (expanded) TvDrawerExpandedWidth else TvDrawerCollapsedWidth,
label = "tv-drawer-width" label = "tv-drawer-width"
) )
val scheme = androidx.tv.material3.MaterialTheme.colorScheme val scheme = MaterialTheme.colorScheme
Box( Box(
modifier = Modifier modifier = Modifier

View File

@@ -4,15 +4,12 @@ import androidx.compose.foundation.background
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.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.grid.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.ArrowBack
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
@@ -22,16 +19,15 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import hu.bbara.purefin.navigation.LibraryDto
import hu.bbara.purefin.ui.model.MediaUiModel
import hu.bbara.purefin.feature.browse.library.LibraryViewModel import hu.bbara.purefin.feature.browse.library.LibraryViewModel
import hu.bbara.purefin.ui.common.button.PurefinIconButton import hu.bbara.purefin.navigation.LibraryDto
import hu.bbara.purefin.ui.common.card.PosterCard import hu.bbara.purefin.ui.common.card.PosterCard
import java.util.UUID import hu.bbara.purefin.ui.model.MediaUiModel
@Composable @Composable
fun TvLibraryScreen( fun TvLibraryScreen(
library: LibraryDto, library: LibraryDto,
onMediaSelected: (MediaUiModel) -> Unit,
viewModel: LibraryViewModel = hiltViewModel(), viewModel: LibraryViewModel = hiltViewModel(),
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
@@ -45,15 +41,13 @@ fun TvLibraryScreen(
modifier = modifier, modifier = modifier,
topBar = { topBar = {
TvLibraryTopBar( TvLibraryTopBar(
title = library.name, title = library.name
onBack = viewModel::onBack
) )
} }
) { innerPadding -> ) { innerPadding ->
TvLibraryContent( TvLibraryContent(
libraryItems = libraryItems.value, libraryItems = libraryItems.value,
onMovieSelected = viewModel::onMovieSelected, onMediaSelected = onMediaSelected,
onSeriesSelected = viewModel::onSeriesSelected,
modifier = Modifier.padding(innerPadding) modifier = Modifier.padding(innerPadding)
) )
} }
@@ -61,40 +55,23 @@ fun TvLibraryScreen(
@Composable @Composable
private fun TvLibraryTopBar( private fun TvLibraryTopBar(
title: String, title: String
onBack: () -> Unit
) { ) {
val scheme = MaterialTheme.colorScheme
Row(
modifier = Modifier
.fillMaxWidth()
.statusBarsPadding()
.padding(horizontal = 16.dp, vertical = 12.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
PurefinIconButton(
icon = Icons.AutoMirrored.Outlined.ArrowBack,
contentDescription = "Back",
onClick = onBack,
focusedScale = 1.1f,
focusedBorderWidth = 2.5.dp,
focusedBorderColor = scheme.onPrimary,
focusedBackgroundColor = scheme.primary
)
Text( Text(
text = title, text = title,
style = MaterialTheme.typography.titleLarge, style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.onBackground, color = MaterialTheme.colorScheme.onBackground,
modifier = Modifier.padding(top = 12.dp) modifier = Modifier
.fillMaxWidth()
.statusBarsPadding()
.padding(horizontal = 16.dp, vertical = 12.dp)
) )
}
} }
@Composable @Composable
fun TvLibraryContent( fun TvLibraryContent(
libraryItems: List<MediaUiModel>, libraryItems: List<MediaUiModel>,
onMovieSelected: (UUID) -> Unit, onMediaSelected: (MediaUiModel) -> Unit,
onSeriesSelected: (UUID) -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
Column(modifier = modifier) { Column(modifier = modifier) {
@@ -108,9 +85,9 @@ fun TvLibraryContent(
items(libraryItems, key = { item -> item.id }) { item -> items(libraryItems, key = { item -> item.id }) { item ->
PosterCard( PosterCard(
item = item, item = item,
onMovieSelected = onMovieSelected, onMovieSelected = { onMediaSelected(item) },
onSeriesSelected = onSeriesSelected, onSeriesSelected = { onMediaSelected(item) },
onEpisodeSelected = { _, _, _ -> } onEpisodeSelected = { _, _, _ -> onMediaSelected(item) }
) )
} }
} }

View File

@@ -1,102 +0,0 @@
package hu.bbara.purefin.ui.common
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Person
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import androidx.hilt.navigation.compose.hiltViewModel
import hu.bbara.purefin.feature.search.SearchViewModel
@Composable
fun DefaultTopBar(
modifier: Modifier = Modifier,
searchViewModel: SearchViewModel = hiltViewModel(),
onProfileClick: () -> Unit = {},
onSettingsClick: () -> Unit = {},
onLogoutClick: () -> Unit = {},
) {
val scheme = MaterialTheme.colorScheme
val searchResult = searchViewModel.searchResult.collectAsState()
var isProfileMenuExpanded by remember { mutableStateOf(false) }
var isSearchExpanded by remember { mutableStateOf(false) }
Box(
modifier = modifier
.fillMaxWidth()
.background(scheme.background.copy(alpha = 0.95f))
.zIndex(1f)
) {
Box(
modifier = Modifier
.statusBarsPadding()
.padding(horizontal = 16.dp, vertical = 16.dp)
.fillMaxWidth(),
contentAlignment = Alignment.CenterEnd
) {
if (!isSearchExpanded) {
Box {
IconButton(
onClick = { isProfileMenuExpanded = true },
modifier = Modifier
.size(56.dp)
.clip(CircleShape),
) {
Icon(
imageVector = Icons.Outlined.Person,
contentDescription = "Profile",
modifier = Modifier.size(30.dp),
)
}
DropdownMenu(
expanded = isProfileMenuExpanded,
onDismissRequest = { isProfileMenuExpanded = false },
) {
DropdownMenuItem(
text = { Text("Profile") },
onClick = {
isProfileMenuExpanded = false
onProfileClick()
}
)
DropdownMenuItem(
text = { Text("Settings") },
onClick = {
isProfileMenuExpanded = false
onSettingsClick()
}
)
DropdownMenuItem(
text = { Text("Logout") },
onClick = {
isProfileMenuExpanded = false
onLogoutClick()
}
)
}
}
}
}
}
}

View File

@@ -3,6 +3,9 @@ package hu.bbara.purefin.ui.screen
import androidx.compose.animation.AnimatedContentTransitionScope import androidx.compose.animation.AnimatedContentTransitionScope
import androidx.compose.animation.ContentTransform import androidx.compose.animation.ContentTransform
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
@@ -26,9 +29,6 @@ import hu.bbara.purefin.ui.screen.home.HomeScreen
import hu.bbara.purefin.ui.screen.home.components.HomeNavItem import hu.bbara.purefin.ui.screen.home.components.HomeNavItem
import hu.bbara.purefin.ui.screen.libraries.LibrariesScreen import hu.bbara.purefin.ui.screen.libraries.LibrariesScreen
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.togetherWith
@Composable @Composable
fun AppScreen( fun AppScreen(
@@ -99,9 +99,6 @@ fun AppScreen(
LibrariesScreen( LibrariesScreen(
items = libraryNavItems, items = libraryNavItems,
onLibrarySelected = { item -> viewModel.onLibrarySelected(item.id, item.label) }, onLibrarySelected = { item -> viewModel.onLibrarySelected(item.id, item.label) },
onProfileClick = {},
onSettingsClick = {},
onLogoutClick = viewModel::logout,
selectedTab = selectedTab, selectedTab = selectedTab,
onTabSelected = onTabSelected, onTabSelected = onTabSelected,
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()

View File

@@ -12,8 +12,8 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import hu.bbara.purefin.ui.model.MediaUiModel
import hu.bbara.purefin.feature.browse.home.LibraryItem import hu.bbara.purefin.feature.browse.home.LibraryItem
import hu.bbara.purefin.ui.model.MediaUiModel
import hu.bbara.purefin.ui.screen.AppBottomBar import hu.bbara.purefin.ui.screen.AppBottomBar
import hu.bbara.purefin.ui.screen.home.components.HomeContent import hu.bbara.purefin.ui.screen.home.components.HomeContent
import hu.bbara.purefin.ui.screen.home.components.HomeTopBar import hu.bbara.purefin.ui.screen.home.components.HomeTopBar

View File

@@ -0,0 +1,60 @@
package hu.bbara.purefin.ui.screen.home.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import hu.bbara.purefin.ui.common.image.PurefinLogo
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DefaultTopBar(
actions: @Composable RowScope.() -> Unit
) {
val scheme = MaterialTheme.colorScheme
TopAppBar(
title = {
Row(
modifier = Modifier,
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.CenterVertically
) {
PurefinLogo(
contentDescription = "Purefin",
modifier = Modifier.size(48.dp),
contentScale = ContentScale.Fit,
)
Text(
text = "PureFin",
fontSize = 32.sp,
fontWeight = FontWeight.Bold,
fontStyle = FontStyle.Italic,
color = scheme.onSecondary
)
}
},
actions = actions,
colors = TopAppBarDefaults.largeTopAppBarColors(
containerColor = scheme.background,
navigationIconContentColor = scheme.onSurface,
actionIconContentColor = scheme.onSurface,
titleContentColor = scheme.onSurface
),
modifier = Modifier.padding(end = 12.dp)
)
}

View File

@@ -1,7 +1,5 @@
package hu.bbara.purefin.ui.screen.home.components package hu.bbara.purefin.ui.screen.home.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
@@ -10,63 +8,33 @@ import androidx.compose.material.icons.outlined.Person
import androidx.compose.material.icons.outlined.Search import androidx.compose.material.icons.outlined.Search
import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.IconButtonColors import androidx.compose.material3.IconButtonColors
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
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
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
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.layout.ContentScale
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import hu.bbara.purefin.ui.common.image.PurefinLogo
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun HomeTopBar( fun HomeTopBar(
onSearchClick: () -> Unit, onSearchClick: () -> Unit,
onProfileClick: () -> Unit, onProfileClick: () -> Unit,
onSettingsClick: () -> Unit, onSettingsClick: () -> Unit,
onLogoutClick: () -> Unit, onLogoutClick: () -> Unit,
modifier: Modifier = Modifier,
) { ) {
val scheme = MaterialTheme.colorScheme val scheme = MaterialTheme.colorScheme
var isProfileMenuExpanded by remember { mutableStateOf(false) } var isProfileMenuExpanded by remember { mutableStateOf(false) }
TopAppBar( DefaultTopBar()
title = { {
Row(
modifier = Modifier,
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.CenterVertically
) {
PurefinLogo(
contentDescription = "Purefin",
modifier = Modifier.size(48.dp),
contentScale = ContentScale.Fit,
)
Text(
text = "PureFin",
fontSize = 32.sp,
fontWeight = FontWeight.Bold,
fontStyle = FontStyle.Italic,
color = scheme.onSecondary
)
}
},
actions = {
IconButton( IconButton(
onClick = onSearchClick, onClick = onSearchClick,
colors = IconButtonColors( colors = IconButtonColors(
@@ -126,14 +94,5 @@ fun HomeTopBar(
} }
) )
} }
}, }
colors = TopAppBarDefaults.largeTopAppBarColors(
containerColor = scheme.background,
scrolledContainerColor = scheme.surface.copy(alpha = 0.96f),
navigationIconContentColor = scheme.onSurface,
actionIconContentColor = scheme.onSurface,
titleContentColor = scheme.onSurface
),
modifier = modifier
)
} }

View File

@@ -23,11 +23,11 @@ import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import hu.bbara.purefin.ui.common.badge.WatchStateBadge
import hu.bbara.purefin.ui.common.image.PurefinAsyncImage
import hu.bbara.purefin.ui.model.EpisodeUiModel import hu.bbara.purefin.ui.model.EpisodeUiModel
import hu.bbara.purefin.ui.model.MediaUiModel import hu.bbara.purefin.ui.model.MediaUiModel
import hu.bbara.purefin.ui.model.MovieUiModel import hu.bbara.purefin.ui.model.MovieUiModel
import hu.bbara.purefin.ui.common.badge.WatchStateBadge
import hu.bbara.purefin.ui.common.image.PurefinAsyncImage
@Composable @Composable
internal fun HomeBrowseCard( internal fun HomeBrowseCard(
@@ -80,7 +80,7 @@ internal fun HomeBrowseCard(
text = uiModel.primaryText, text = uiModel.primaryText,
style = MaterialTheme.typography.bodyLarge, style = MaterialTheme.typography.bodyLarge,
fontWeight = FontWeight.SemiBold, fontWeight = FontWeight.SemiBold,
maxLines = 2, maxLines = 1,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
) )
Spacer(modifier = Modifier.height(4.dp)) Spacer(modifier = Modifier.height(4.dp))

View File

@@ -6,18 +6,15 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import hu.bbara.purefin.ui.common.DefaultTopBar
import hu.bbara.purefin.ui.screen.home.components.HomeNavItem
import hu.bbara.purefin.ui.screen.AppBottomBar import hu.bbara.purefin.ui.screen.AppBottomBar
import hu.bbara.purefin.ui.screen.home.components.HomeNavItem
import hu.bbara.purefin.ui.screen.libraries.components.LibrariesContent import hu.bbara.purefin.ui.screen.libraries.components.LibrariesContent
import hu.bbara.purefin.ui.screen.library.components.LibraryTopBar
@Composable @Composable
fun LibrariesScreen( fun LibrariesScreen(
items: List<HomeNavItem>, items: List<HomeNavItem>,
onLibrarySelected: (HomeNavItem) -> Unit, onLibrarySelected: (HomeNavItem) -> Unit,
onProfileClick: () -> Unit,
onSettingsClick: () -> Unit,
onLogoutClick: () -> Unit,
selectedTab: Int, selectedTab: Int,
onTabSelected: (Int) -> Unit, onTabSelected: (Int) -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
@@ -27,10 +24,8 @@ fun LibrariesScreen(
containerColor = MaterialTheme.colorScheme.background, containerColor = MaterialTheme.colorScheme.background,
contentColor = MaterialTheme.colorScheme.onBackground, contentColor = MaterialTheme.colorScheme.onBackground,
topBar = { topBar = {
DefaultTopBar( LibraryTopBar(
onProfileClick = onProfileClick, onSearchClick = {},
onSettingsClick = onSettingsClick,
onLogoutClick = onLogoutClick
) )
}, },
bottomBar = { bottomBar = {

View File

@@ -0,0 +1,38 @@
package hu.bbara.purefin.ui.screen.library.components
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Search
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.IconButtonColors
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import hu.bbara.purefin.ui.screen.home.components.DefaultTopBar
@Composable
fun LibraryTopBar(
onSearchClick: () -> Unit,
) {
val scheme = MaterialTheme.colorScheme
DefaultTopBar {
IconButton(
onClick = onSearchClick,
colors = IconButtonColors(
containerColor = scheme.secondary,
contentColor = scheme.onSecondary,
disabledContainerColor = scheme.secondary,
disabledContentColor = scheme.onSecondary),
modifier = Modifier.size(50.dp),
) {
Icon(
imageVector = Icons.Outlined.Search,
contentDescription = "Search",
modifier = Modifier.size(30.dp),
)
}
}
}