add bottombar

This commit is contained in:
Stefano Sansone
2024-04-08 10:42:01 +02:00
parent f50209b54c
commit 962c049cf3
14 changed files with 162 additions and 65 deletions

View File

@ -57,7 +57,6 @@ class MainActivity : ComponentActivity() {
enableEdgeToEdge()
setContent {
OmnivoreTheme {
Box(
modifier = Modifier

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.core.designsystem
package app.omnivore.omnivore.core.designsystem.icon
import app.omnivore.omnivore.R

View File

@ -39,14 +39,15 @@ import androidx.compose.ui.text.input.ImeAction
import androidx.navigation.NavHostController
import app.omnivore.omnivore.R
import app.omnivore.omnivore.core.database.entities.SavedItemWithLabelsAndHighlights
import app.omnivore.omnivore.navigation.TopLevelDestination
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun LibraryNavigationBar(
currentDestination: TopLevelDestination?,
savedItemViewModel: SavedItemViewModel,
onSearchClicked: () -> Unit,
onAddLinkClicked: () -> Unit,
onSettingsIconClick: () -> Unit
onAddLinkClicked: () -> Unit
) {
val actionsMenuItem: SavedItemWithLabelsAndHighlights? by savedItemViewModel.actionsMenuItemLiveData.observeAsState(
null
@ -57,9 +58,11 @@ fun LibraryNavigationBar(
TopAppBar(
title = {
Text(
if (actionsMenuItem == null)
stringResource(R.string.library_nav_bar_title) else
if (actionsMenuItem == null) {
currentDestination?.titleTextId?.let { stringResource(it) } ?: ""
} else {
stringResource(R.string.library_nav_bar_title_alt)
}
)
},
colors = TopAppBarDefaults.topAppBarColors(
@ -159,13 +162,6 @@ fun LibraryNavigationBar(
contentDescription = null
)
}
IconButton(onClick = onSettingsIconClick) {
Icon(
imageVector = Icons.Default.MoreVert,
contentDescription = null
)
}
}
}
)

View File

@ -73,6 +73,7 @@ import app.omnivore.omnivore.feature.save.SaveState
import app.omnivore.omnivore.feature.save.SaveViewModel
import app.omnivore.omnivore.feature.savedItemViews.SavedItemCard
import app.omnivore.omnivore.navigation.Routes
import app.omnivore.omnivore.navigation.TopLevelDestination
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
@ -131,13 +132,15 @@ internal fun LibraryView(
}
}
val currentTopLevelDestination = TopLevelDestination.entries.find { it.route == navController.currentDestination?.route }
Scaffold(
topBar = {
LibraryNavigationBar(
currentDestination = currentTopLevelDestination,
savedItemViewModel = viewModel,
onSearchClicked = { navController.navigate(Routes.Search.route) },
onAddLinkClicked = { showAddLinkBottomSheet(viewModel) },
onSettingsIconClick = { navController.navigate(Routes.Settings.route) }
onAddLinkClicked = { showAddLinkBottomSheet(viewModel) }
)
},
) { paddingValues ->

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.feature.settings
package app.omnivore.omnivore.feature.profile
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ButtonDefaults

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.feature.settings
package app.omnivore.omnivore.feature.profile
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
@ -8,11 +8,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.ArrowBack
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
@ -41,16 +37,7 @@ internal fun SettingsScreen(
) {
Scaffold(topBar = {
TopAppBar(
title = { Text(stringResource(R.string.settings_view_title)) },
navigationIcon = {
IconButton(onClick = { navController.navigateUp() }) {
Icon(
imageVector = Icons.AutoMirrored.Outlined.ArrowBack,
contentDescription = null
)
}
},
title = { Text(stringResource(R.string.profile_view_title)) },
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.background
),
@ -80,14 +67,14 @@ fun SettingsViewContent(
) {
item {
SettingRow(title = stringResource(R.string.settings_view_setting_row_manage_account)) {
SettingRow(title = stringResource(R.string.about_manage_account)) {
navController.navigate(Routes.Account.route)
}
}
item {
SettingRow(
title = stringResource(R.string.settings_view_setting_row_logout)
title = stringResource(R.string.about_logout)
) {
showLogoutDialog.value = true
}

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.feature.settings
package app.omnivore.omnivore.feature.profile
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@ -18,7 +18,7 @@ import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class SettingsViewModel @Inject constructor(
class ProfileViewModel @Inject constructor(
private val networker: Networker,
private val dataService: DataService,
private val datastoreRepo: DatastoreRepository

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.feature.settings.about
package app.omnivore.omnivore.feature.profile.about
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
@ -28,16 +28,16 @@ import androidx.navigation.NavHostController
import app.omnivore.omnivore.BuildConfig
import app.omnivore.omnivore.R
import app.omnivore.omnivore.core.ui.LinkIcon
import app.omnivore.omnivore.feature.settings.RELEASE_URL
import app.omnivore.omnivore.feature.settings.SettingRow
import app.omnivore.omnivore.feature.settings.SettingsViewModel
import app.omnivore.omnivore.feature.profile.RELEASE_URL
import app.omnivore.omnivore.feature.profile.SettingRow
import app.omnivore.omnivore.feature.profile.ProfileViewModel
import app.omnivore.omnivore.navigation.Routes
@OptIn(ExperimentalMaterial3Api::class)
@Composable
internal fun AboutScreen(
navController: NavHostController,
settingsViewModel: SettingsViewModel = hiltViewModel()
settingsViewModel: ProfileViewModel = hiltViewModel()
) {
val uriHandler = LocalUriHandler.current
@ -75,25 +75,25 @@ internal fun AboutScreen(
}
item {
SettingRow(title = stringResource(R.string.settings_view_setting_row_documentation)) {
SettingRow(title = stringResource(R.string.about_documentation)) {
navController.navigate(Routes.Documentation.route)
}
}
item {
SettingRow(title = stringResource(R.string.settings_view_setting_row_feedback)) {
SettingRow(title = stringResource(R.string.about_feedback)) {
settingsViewModel.presentIntercom()
}
}
item {
SettingRow(title = stringResource(R.string.settings_view_setting_row_privacy_policy)) {
SettingRow(title = stringResource(R.string.about_privacy_policy)) {
navController.navigate(Routes.PrivacyPolicy.route)
}
}
item {
SettingRow(title = stringResource(R.string.settings_view_setting_row_terms_and_conditions)) {
SettingRow(title = stringResource(R.string.about_terms_and_conditions)) {
navController.navigate(Routes.TermsAndConditions.route)
}
}

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.feature.settings.about
package app.omnivore.omnivore.feature.profile.about
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.feature.settings.account
package app.omnivore.omnivore.feature.profile.account
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
@ -20,15 +20,15 @@ import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavHostController
import app.omnivore.omnivore.R
import app.omnivore.omnivore.feature.settings.SettingRow
import app.omnivore.omnivore.feature.settings.SettingsViewModel
import app.omnivore.omnivore.feature.profile.SettingRow
import app.omnivore.omnivore.feature.profile.ProfileViewModel
@OptIn(ExperimentalMaterial3Api::class)
@Composable
internal fun AccountScreen(
navController: NavHostController,
snackbarHostState: SnackbarHostState,
settingsViewModel: SettingsViewModel = hiltViewModel()
settingsViewModel: ProfileViewModel = hiltViewModel()
) {
LaunchedEffect(settingsViewModel.isResettingData) {
if (settingsViewModel.isResettingData) {

View File

@ -10,6 +10,10 @@ import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
@ -20,8 +24,16 @@ import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.navigation.NavDestination
import androidx.navigation.NavDestination.Companion.hierarchy
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import app.omnivore.omnivore.feature.auth.LoginViewModel
import app.omnivore.omnivore.feature.auth.WelcomeScreen
@ -30,12 +42,13 @@ import app.omnivore.omnivore.feature.editinfo.EditInfoViewModel
import app.omnivore.omnivore.feature.library.LibraryView
import app.omnivore.omnivore.feature.library.SearchView
import app.omnivore.omnivore.feature.library.SearchViewModel
import app.omnivore.omnivore.feature.profile.SettingsScreen
import app.omnivore.omnivore.feature.profile.about.AboutScreen
import app.omnivore.omnivore.feature.profile.account.AccountScreen
import app.omnivore.omnivore.feature.save.SaveViewModel
import app.omnivore.omnivore.feature.settings.SettingsScreen
import app.omnivore.omnivore.feature.settings.about.AboutScreen
import app.omnivore.omnivore.feature.settings.account.AccountScreen
import app.omnivore.omnivore.feature.web.WebViewScreen
import app.omnivore.omnivore.navigation.Routes
import app.omnivore.omnivore.navigation.TopLevelDestination
@Composable
fun RootView(
@ -47,9 +60,19 @@ fun RootView(
) {
val hasAuthToken: Boolean by loginViewModel.hasAuthTokenLiveData.observeAsState(false)
val snackbarHostState = remember { SnackbarHostState() }
val navController = rememberNavController()
Scaffold(
snackbarHost = { SnackbarHost(snackbarHostState) },
bottomBar = {
if (navController.currentBackStackEntryAsState().value?.destination?.route in TopLevelDestination.entries.map { it.route }) {
OmnivoreBottomBar(
navController,
TopLevelDestination.entries,
navController.currentBackStackEntryAsState().value?.destination
)
}
}
) { padding ->
Box(
modifier = if (!hasAuthToken) Modifier.background(Color(0xFFFCEBA8)) else Modifier
@ -64,6 +87,7 @@ fun RootView(
){
if (hasAuthToken) {
PrimaryNavigator(
navController = navController,
loginViewModel = loginViewModel,
searchViewModel = searchViewModel,
labelsViewModel = labelsViewModel,
@ -88,6 +112,7 @@ fun RootView(
@Composable
fun PrimaryNavigator(
navController: NavHostController,
loginViewModel: LoginViewModel,
searchViewModel: SearchViewModel,
labelsViewModel: LabelsViewModel,
@ -95,13 +120,21 @@ fun PrimaryNavigator(
editInfoViewModel: EditInfoViewModel,
snackbarHostState: SnackbarHostState
) {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = Routes.Library.route
startDestination = Routes.Inbox.route
) {
composable(Routes.Library.route) {
composable(Routes.Inbox.route) {
LibraryView(
navController = navController,
labelsViewModel = labelsViewModel,
saveViewModel = saveViewModel,
editInfoViewModel = editInfoViewModel,
)
}
composable(Routes.Following.route) {
LibraryView(
navController = navController,
labelsViewModel = labelsViewModel,
@ -149,3 +182,47 @@ fun PrimaryNavigator(
}
}
}
@Composable
private fun OmnivoreBottomBar(
navController: NavHostController,
destinations: List<TopLevelDestination>,
currentDestination: NavDestination?
) {
NavigationBar (
containerColor = MaterialTheme.colorScheme.background
) {
destinations.forEach { screen ->
val icon = if (screen.route == currentDestination?.route) {
ImageVector.vectorResource(id = screen.selectedIcon)
} else {
ImageVector.vectorResource(id = screen.unselectedIcon)
}
NavigationBarItem(
icon = { Icon(icon, contentDescription = stringResource(id = screen.iconTextId)) },
selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true,
onClick = {
navController.navigate(screen.route) {
// Pop up to the start destination of the graph to
// avoid building up a large stack of destinations
// on the back stack as users select items
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
// Avoid multiple copies of the same destination when
// reselecting the same item
launchSingleTop = true
// Restore state when reselecting a previously selected item
restoreState = true
}
}
)
}
}
}
private fun NavDestination?.isTopLevelDestinationInHierarchy(destination: TopLevelDestination) =
this?.hierarchy?.any {
it.route?.contains(destination.name, true) ?: false
} ?: false

View File

@ -73,7 +73,7 @@ fun SavedItemCard(
modifier = Modifier
.weight(1f, fill = false)
.padding(end = 20.dp)
.defaultMinSize(minHeight = 55.dp)
.defaultMinSize(minHeight = 50.dp)
) {
ReadInfo(item = savedItem)

View File

@ -1,13 +1,14 @@
package app.omnivore.omnivore.navigation
sealed class Routes(val route: String) {
object Library : Routes("Library")
object Settings : Routes("Settings")
object About : Routes("About")
object Account : Routes("Account")
object Search : Routes("Search")
object Documentation : Routes("Documentation")
object PrivacyPolicy : Routes("PrivacyPolicy")
object TermsAndConditions : Routes("TermsAndConditions")
object Notebook : Routes("Notebook")
data object Following : Routes("Following")
data object Inbox : Routes("Inbox")
data object Settings : Routes("Settings")
data object About : Routes("About")
data object Account : Routes("Account")
data object Search : Routes("Search")
data object Documentation : Routes("Documentation")
data object PrivacyPolicy : Routes("PrivacyPolicy")
data object TermsAndConditions : Routes("TermsAndConditions")
data object Notebook : Routes("Notebook")
}

View File

@ -0,0 +1,34 @@
package app.omnivore.omnivore.navigation
import app.omnivore.omnivore.R
import app.omnivore.omnivore.core.designsystem.icon.OmnivoreIcons
enum class TopLevelDestination(
val selectedIcon: Int,
val unselectedIcon: Int,
val iconTextId: Int,
val titleTextId: Int,
val route: String,
) {
FOLLOWING(
selectedIcon = OmnivoreIcons.Following,
unselectedIcon = OmnivoreIcons.FollowingEmpty,
iconTextId = R.string.following,
titleTextId = R.string.following,
route = Routes.Following.route
),
INBOX(
selectedIcon = OmnivoreIcons.Inbox,
unselectedIcon = OmnivoreIcons.InboxEmpty,
iconTextId = R.string.inbox,
titleTextId = R.string.inbox,
route = Routes.Inbox.route
),
PROFILE(
selectedIcon = OmnivoreIcons.Profile,
unselectedIcon = OmnivoreIcons.ProfileEmpty,
iconTextId = R.string.profile,
titleTextId = R.string.profile,
route = Routes.Settings.route
),
}