From c72283c566bd4a543d9db9e04ae3f222eb987b87 Mon Sep 17 00:00:00 2001 From: Barnabas Balogh Date: Sat, 21 Feb 2026 11:30:29 +0100 Subject: [PATCH] refactor: replace NavigationDrawer with NavigationBar on HomeScreen Converts the mobile home screen from a ModalNavigationDrawer to a bottom NavigationBar with three tabs: Home, Libraries, and Downloads. --- .../hu/bbara/purefin/app/home/HomePage.kt | 97 +++++---- .../purefin/app/home/ui/DownloadsContent.kt | 39 ++++ .../bbara/purefin/app/home/ui/HomeDrawer.kt | 204 ------------------ .../bbara/purefin/app/home/ui/HomeTopBar.kt | 9 - .../purefin/app/home/ui/LibrariesContent.kt | 55 +++++ 5 files changed, 147 insertions(+), 257 deletions(-) create mode 100644 app/src/main/java/hu/bbara/purefin/app/home/ui/DownloadsContent.kt delete mode 100644 app/src/main/java/hu/bbara/purefin/app/home/ui/HomeDrawer.kt create mode 100644 app/src/main/java/hu/bbara/purefin/app/home/ui/LibrariesContent.kt diff --git a/app/src/main/java/hu/bbara/purefin/app/home/HomePage.kt b/app/src/main/java/hu/bbara/purefin/app/home/HomePage.kt index 11eddfb..f94455e 100644 --- a/app/src/main/java/hu/bbara/purefin/app/home/HomePage.kt +++ b/app/src/main/java/hu/bbara/purefin/app/home/HomePage.kt @@ -2,31 +2,33 @@ package hu.bbara.purefin.app.home import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Collections +import androidx.compose.material.icons.outlined.Download +import androidx.compose.material.icons.outlined.Home import androidx.compose.material.icons.outlined.Movie import androidx.compose.material.icons.outlined.Tv -import androidx.compose.material3.DrawerValue +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.ModalDrawerSheet -import androidx.compose.material3.ModalNavigationDrawer +import androidx.compose.material3.NavigationBar +import androidx.compose.material3.NavigationBarItem import androidx.compose.material3.Scaffold -import androidx.compose.material3.rememberDrawerState +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.LifecycleResumeEffect +import hu.bbara.purefin.app.home.ui.DownloadsContent import hu.bbara.purefin.app.home.ui.HomeContent -import hu.bbara.purefin.app.home.ui.HomeDrawerContent -import hu.bbara.purefin.app.home.ui.HomeMockData import hu.bbara.purefin.app.home.ui.HomeNavItem import hu.bbara.purefin.app.home.ui.HomeTopBar +import hu.bbara.purefin.app.home.ui.LibrariesContent import hu.bbara.purefin.feature.shared.home.HomePageViewModel -import kotlinx.coroutines.launch import org.jellyfin.sdk.model.api.CollectionType @Composable @@ -34,8 +36,7 @@ fun HomePage( viewModel: HomePageViewModel = hiltViewModel(), modifier: Modifier = Modifier ) { - val drawerState = rememberDrawerState(DrawerValue.Closed) - val coroutineScope = rememberCoroutineScope() + var selectedTab by remember { mutableIntStateOf(0) } val libraries = viewModel.libraries.collectAsState().value val isOfflineMode = viewModel.isOfflineMode.collectAsState().value @@ -59,41 +60,41 @@ fun HomePage( onPauseOrDispose { } } - ModalNavigationDrawer( - drawerState = drawerState, - drawerContent = { - ModalDrawerSheet( - modifier = Modifier - .width(280.dp) - .fillMaxSize(), - drawerContainerColor = MaterialTheme.colorScheme.surface, - drawerContentColor = MaterialTheme.colorScheme.onBackground - ) { - HomeDrawerContent( - title = "Jellyfin", - subtitle = "Library Dashboard", - primaryNavItems = libraryNavItems, - secondaryNavItems = HomeMockData.secondaryNavItems, - user = HomeMockData.user, - onLibrarySelected = { item -> viewModel.onLibrarySelected(item.id, item.label) }, - onLogout = viewModel::logout + Scaffold( + modifier = modifier.fillMaxSize(), + containerColor = MaterialTheme.colorScheme.background, + contentColor = MaterialTheme.colorScheme.onBackground, + topBar = { + HomeTopBar( + isOfflineMode = isOfflineMode, + onToggleOfflineMode = viewModel::toggleOfflineMode + ) + }, + bottomBar = { + NavigationBar { + NavigationBarItem( + selected = selectedTab == 0, + onClick = { selectedTab = 0 }, + icon = { Icon(Icons.Outlined.Home, contentDescription = "Home") }, + label = { Text("Home") } + ) + NavigationBarItem( + selected = selectedTab == 1, + onClick = { selectedTab = 1 }, + icon = { Icon(Icons.Outlined.Collections, contentDescription = "Libraries") }, + label = { Text("Libraries") } + ) + NavigationBarItem( + selected = selectedTab == 2, + onClick = { selectedTab = 2 }, + icon = { Icon(Icons.Outlined.Download, contentDescription = "Downloads") }, + label = { Text("Downloads") } ) } } - ) { - Scaffold( - modifier = modifier.fillMaxSize(), - containerColor = MaterialTheme.colorScheme.background, - contentColor = MaterialTheme.colorScheme.onBackground, - topBar = { - HomeTopBar( - onMenuClick = { coroutineScope.launch { drawerState.open() } }, - isOfflineMode = isOfflineMode, - onToggleOfflineMode = viewModel::toggleOfflineMode - ) - } - ) { innerPadding -> - HomeContent( + ) { innerPadding -> + when (selectedTab) { + 0 -> HomeContent( libraries = libraries, libraryContent = latestLibraryContent.value, continueWatching = continueWatching.value, @@ -103,6 +104,14 @@ fun HomePage( onEpisodeSelected = viewModel::onEpisodeSelected, modifier = Modifier.padding(innerPadding) ) + 1 -> LibrariesContent( + items = libraryNavItems, + onLibrarySelected = { item -> viewModel.onLibrarySelected(item.id, item.label) }, + modifier = Modifier.padding(innerPadding) + ) + 2 -> DownloadsContent( + modifier = Modifier.padding(innerPadding) + ) } } } diff --git a/app/src/main/java/hu/bbara/purefin/app/home/ui/DownloadsContent.kt b/app/src/main/java/hu/bbara/purefin/app/home/ui/DownloadsContent.kt new file mode 100644 index 0000000..5ed31ed --- /dev/null +++ b/app/src/main/java/hu/bbara/purefin/app/home/ui/DownloadsContent.kt @@ -0,0 +1,39 @@ +package hu.bbara.purefin.app.home.ui + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Download +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + +@Composable +fun DownloadsContent( + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Icon( + imageVector = Icons.Outlined.Download, + contentDescription = null, + modifier = Modifier.size(64.dp), + tint = MaterialTheme.colorScheme.onSurfaceVariant + ) + Text( + text = "No downloads yet", + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier + ) + } +} diff --git a/app/src/main/java/hu/bbara/purefin/app/home/ui/HomeDrawer.kt b/app/src/main/java/hu/bbara/purefin/app/home/ui/HomeDrawer.kt deleted file mode 100644 index 4c34a2c..0000000 --- a/app/src/main/java/hu/bbara/purefin/app/home/ui/HomeDrawer.kt +++ /dev/null @@ -1,204 +0,0 @@ -package hu.bbara.purefin.app.home.ui - -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.PlayArrow -import androidx.compose.material.icons.outlined.Person -import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp - -@Composable -fun HomeDrawerContent( - title: String, - subtitle: String, - primaryNavItems: List, - secondaryNavItems: List, - user: HomeUser, - onLibrarySelected: (HomeNavItem) -> Unit, - onLogout: () -> Unit, - modifier: Modifier = Modifier, -) { - Column(modifier = modifier.fillMaxSize()) { - HomeDrawerHeader( - title = title, - subtitle = subtitle - ) - HomeDrawerNav( - primaryItems = primaryNavItems, - secondaryItems = secondaryNavItems, - onLibrarySelected = onLibrarySelected - ) - Spacer(modifier = Modifier.weight(1f)) - HomeDrawerFooter(user = user, onLogout = onLogout) - } -} - -@Composable -fun HomeDrawerHeader( - title: String, - subtitle: String, - modifier: Modifier = Modifier -) { - val scheme = MaterialTheme.colorScheme - - Row( - modifier = modifier - .fillMaxWidth() - .padding(start = 24.dp, end = 16.dp, top = 24.dp, bottom = 20.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Row( - modifier = Modifier - .size(40.dp) - .background(scheme.primary, RoundedCornerShape(12.dp)), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center - ) { - Icon( - imageVector = Icons.Filled.PlayArrow, - contentDescription = "Play", - tint = scheme.onPrimary - ) - } - Column(modifier = Modifier.padding(start = 12.dp)) { - Text( - text = title, - color = scheme.onBackground, - fontSize = 20.sp, - fontWeight = FontWeight.Bold - ) - Text( - text = subtitle, - color = scheme.onSurfaceVariant, - fontSize = 12.sp - ) - } - } - HorizontalDivider(color = scheme.onSurfaceVariant.copy(alpha = 0.2f)) -} - -@Composable -fun HomeDrawerNav( - primaryItems: List, - secondaryItems: List, - onLibrarySelected: (HomeNavItem) -> Unit, - modifier: Modifier = Modifier, -) { - Column( - modifier = modifier - .fillMaxWidth() - .padding(vertical = 16.dp) - ) { - primaryItems.forEach { item -> - HomeDrawerNavItem(item = item, onLibrarySelected = onLibrarySelected) - } - if (secondaryItems.isNotEmpty()) { - HorizontalDivider( - modifier = Modifier - .padding(horizontal = 20.dp, vertical = 12.dp), - color = MaterialTheme.colorScheme.outlineVariant - ) - secondaryItems.forEach { item -> - HomeDrawerNavItem(item = item, onLibrarySelected = onLibrarySelected) - } - } - } -} - -@Composable -fun HomeDrawerNavItem( - item: HomeNavItem, - modifier: Modifier = Modifier, - onLibrarySelected: (HomeNavItem) -> Unit -) { - val scheme = MaterialTheme.colorScheme - val background = if (item.selected) scheme.primary.copy(alpha = 0.12f) else Color.Transparent - val tint = if (item.selected) scheme.primary else scheme.onSurfaceVariant - Row( - modifier = modifier - .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 4.dp) - .background(background, RoundedCornerShape(12.dp)) - .clickable { onLibrarySelected(item) } - .padding(horizontal = 16.dp, vertical = 12.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - imageVector = item.icon, - contentDescription = item.label, - tint = tint - ) - Text( - text = item.label, - color = if (item.selected) scheme.primary else scheme.onBackground, - fontSize = 15.sp, - fontWeight = FontWeight.Medium, - modifier = Modifier.padding(start = 12.dp) - ) - } -} - -@Composable -fun HomeDrawerFooter ( - user: HomeUser, - onLogout: () -> Unit, - modifier: Modifier = Modifier, -) { - val scheme = MaterialTheme.colorScheme - - Row( - modifier = modifier - .fillMaxWidth() - .padding(16.dp) - .background(scheme.surfaceVariant, RoundedCornerShape(12.dp)) - .padding(12.dp), - verticalAlignment = Alignment.CenterVertically - ) { - HomeAvatar( - size = 32.dp, - borderWidth = 1.dp, - borderColor = scheme.outlineVariant, - backgroundColor = scheme.primaryContainer, - icon = Icons.Outlined.Person, - iconTint = scheme.onBackground - ) - Column(modifier = Modifier.padding(start = 12.dp) - .clickable { onLogout() }) { - Text( - text = user.name, - color = scheme.onBackground, - fontSize = 14.sp, - fontWeight = FontWeight.SemiBold, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - Text( - text = user.plan, - color = scheme.onSurfaceVariant, - fontSize = 11.sp, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - } - } -} diff --git a/app/src/main/java/hu/bbara/purefin/app/home/ui/HomeTopBar.kt b/app/src/main/java/hu/bbara/purefin/app/home/ui/HomeTopBar.kt index 9c18891..76833a6 100644 --- a/app/src/main/java/hu/bbara/purefin/app/home/ui/HomeTopBar.kt +++ b/app/src/main/java/hu/bbara/purefin/app/home/ui/HomeTopBar.kt @@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Cloud import androidx.compose.material.icons.outlined.CloudOff -import androidx.compose.material.icons.outlined.Menu import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -22,7 +21,6 @@ import hu.bbara.purefin.common.ui.components.SearchField @Composable fun HomeTopBar( - onMenuClick: () -> Unit, isOfflineMode: Boolean, onToggleOfflineMode: () -> Unit, modifier: Modifier = Modifier, @@ -43,11 +41,6 @@ fun HomeTopBar( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(16.dp, Alignment.CenterHorizontally), ) { - PurefinIconButton( - icon = Icons.Outlined.Menu, - contentDescription = "Menu", - onClick = onMenuClick, - ) SearchField( value = "", onValueChange = {}, @@ -65,5 +58,3 @@ fun HomeTopBar( } } } - - diff --git a/app/src/main/java/hu/bbara/purefin/app/home/ui/LibrariesContent.kt b/app/src/main/java/hu/bbara/purefin/app/home/ui/LibrariesContent.kt new file mode 100644 index 0000000..111538a --- /dev/null +++ b/app/src/main/java/hu/bbara/purefin/app/home/ui/LibrariesContent.kt @@ -0,0 +1,55 @@ +package hu.bbara.purefin.app.home.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.ListItem +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.unit.dp + +@Composable +fun LibrariesContent( + items: List, + onLibrarySelected: (HomeNavItem) -> Unit, + modifier: Modifier = Modifier, +) { + LazyColumn( + modifier = modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.background) + .padding(horizontal = 16.dp, vertical = 8.dp) + ) { + items(items, key = { it.id }) { item -> + ListItem( + headlineContent = { + Text( + text = item.label, + style = MaterialTheme.typography.titleMedium + ) + }, + leadingContent = { + Icon( + imageVector = item.icon, + contentDescription = item.label, + tint = MaterialTheme.colorScheme.primary + ) + }, + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 4.dp) + .clip(RoundedCornerShape(12.dp)) + .clickable { onLibrarySelected(item) } + ) + } + } +}