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.
This commit is contained in:
2026-02-21 11:30:29 +01:00
parent 7d5eeaf7fa
commit b21454c764
5 changed files with 147 additions and 257 deletions

View File

@@ -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(
onMenuClick = { coroutineScope.launch { drawerState.open() } },
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") }
)
}
}
) { innerPadding ->
HomeContent(
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)
)
}
}
}

View File

@@ -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
)
}
}

View File

@@ -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<HomeNavItem>,
secondaryNavItems: List<HomeNavItem>,
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<HomeNavItem>,
secondaryItems: List<HomeNavItem>,
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
)
}
}
}

View File

@@ -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(
}
}
}

View File

@@ -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<HomeNavItem>,
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) }
)
}
}
}