add bottombar
This commit is contained in:
@ -57,7 +57,6 @@ class MainActivity : ComponentActivity() {
|
||||
enableEdgeToEdge()
|
||||
|
||||
setContent {
|
||||
|
||||
OmnivoreTheme {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package app.omnivore.omnivore.core.designsystem
|
||||
package app.omnivore.omnivore.core.designsystem.icon
|
||||
|
||||
import app.omnivore.omnivore.R
|
||||
|
||||
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@ -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 ->
|
||||
|
||||
@ -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
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
@ -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) {
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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")
|
||||
}
|
||||
|
||||
@ -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
|
||||
),
|
||||
}
|
||||
Reference in New Issue
Block a user