mirror of
https://github.com/msfjarvis/compose-lobsters
synced 2025-08-14 17:37:05 +05:30
feat: redesign bottom navigation bar
This commit is contained in:
parent
4f3bafc051
commit
1de4916c9c
14 changed files with 211 additions and 64 deletions
|
@ -19,7 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
- Change submitter text to 'authored' when applicable
|
||||
- Unread comments now have a brighter background rather than a text badge
|
||||
- Bottom navigation's visibility now interacts with the scroll behavior of the post lists
|
||||
- Bottom navigation bar has been redesigned
|
||||
|
||||
## [1.48.0] - 2024-06-05
|
||||
|
||||
|
|
|
@ -95,6 +95,8 @@ dependencies {
|
|||
implementation(libs.copydown)
|
||||
implementation(libs.dagger)
|
||||
implementation(libs.eithernet)
|
||||
implementation(libs.haze)
|
||||
implementation(libs.haze.materials)
|
||||
implementation(libs.javax.inject)
|
||||
implementation(libs.kotlinx.collections.immutable)
|
||||
implementation(libs.kotlinx.coroutines.core)
|
||||
|
|
|
@ -13,31 +13,37 @@ import androidx.compose.animation.core.LinearOutSlowInEasing
|
|||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.compose.material3.BottomAppBar
|
||||
import androidx.compose.material3.BottomAppBarScrollBehavior
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.NavigationBarItem
|
||||
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.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||
import dev.chrisbanes.haze.HazeState
|
||||
import dev.chrisbanes.haze.HazeStyle
|
||||
import dev.chrisbanes.haze.hazeChild
|
||||
import dev.msfjarvis.claw.android.ui.navigation.Destination
|
||||
import dev.msfjarvis.claw.android.ui.navigation.matches
|
||||
import dev.msfjarvis.claw.common.ui.FloatingNavigationBar
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
const val AnimationDuration = 100
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ClawNavigationBar(
|
||||
navController: NavController,
|
||||
items: ImmutableList<NavigationItem>,
|
||||
isVisible: Boolean,
|
||||
scrollBehavior: BottomAppBarScrollBehavior,
|
||||
hazeState: HazeState,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
|
@ -54,40 +60,60 @@ fun ClawNavigationBar(
|
|||
targetOffsetY = { fullHeight -> fullHeight },
|
||||
animationSpec = tween(durationMillis = AnimationDuration, easing = FastOutLinearInEasing),
|
||||
),
|
||||
modifier = Modifier,
|
||||
) {
|
||||
BottomAppBar(modifier = modifier, scrollBehavior = scrollBehavior) {
|
||||
val navBackStackEntry = navController.currentBackStackEntryAsState().value
|
||||
val currentDestination = navBackStackEntry?.destination
|
||||
items.forEach { navItem ->
|
||||
val isSelected = currentDestination.matches(navItem.destination)
|
||||
NavigationBarItem(
|
||||
icon = {
|
||||
Crossfade(isSelected, label = "nav-label") {
|
||||
Icon(
|
||||
imageVector = if (it) navItem.selectedIcon else navItem.icon,
|
||||
contentDescription = navItem.label.replaceFirstChar(Char::uppercase),
|
||||
)
|
||||
}
|
||||
},
|
||||
label = { Text(text = navItem.label) },
|
||||
selected = isSelected,
|
||||
onClick = {
|
||||
if (isSelected) {
|
||||
navItem.listStateResetCallback()
|
||||
} else {
|
||||
navController.navigate(navItem.destination) {
|
||||
popUpTo(navController.graph.startDestinationId) { saveState = true }
|
||||
launchSingleTop = true
|
||||
restoreState = true
|
||||
label = "",
|
||||
content = {
|
||||
FloatingNavigationBar(
|
||||
tonalElevation = 16.dp,
|
||||
shape = MaterialTheme.shapes.extraLarge,
|
||||
modifier =
|
||||
modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.navigationBarsPadding()
|
||||
.clip(MaterialTheme.shapes.extraLarge)
|
||||
.hazeChild(
|
||||
hazeState,
|
||||
style =
|
||||
HazeStyle(
|
||||
backgroundColor = MaterialTheme.colorScheme.surface,
|
||||
tints = emptyList(),
|
||||
blurRadius = 24.dp,
|
||||
noiseFactor = 0f,
|
||||
),
|
||||
),
|
||||
containerColor = Color.Transparent,
|
||||
) {
|
||||
val navBackStackEntry = navController.currentBackStackEntryAsState().value
|
||||
val currentDestination = navBackStackEntry?.destination
|
||||
items.forEach { navItem ->
|
||||
val isSelected = currentDestination.matches(navItem.destination)
|
||||
NavigationBarItem(
|
||||
icon = {
|
||||
Crossfade(isSelected, label = "nav-label") {
|
||||
Icon(
|
||||
imageVector = if (it) navItem.selectedIcon else navItem.icon,
|
||||
contentDescription = navItem.label.replaceFirstChar(Char::uppercase),
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = Modifier.testTag(navItem.label.uppercase()),
|
||||
)
|
||||
},
|
||||
label = { Text(text = navItem.label) },
|
||||
selected = isSelected,
|
||||
onClick = {
|
||||
if (isSelected) {
|
||||
navItem.listStateResetCallback()
|
||||
} else {
|
||||
navController.navigate(navItem.destination) {
|
||||
popUpTo(navController.graph.startDestinationId) { saveState = true }
|
||||
launchSingleTop = true
|
||||
restoreState = true
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = Modifier.testTag(navItem.label.uppercase()),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
class NavigationItem(
|
||||
|
|
|
@ -10,6 +10,7 @@ import androidx.activity.compose.ReportDrawn
|
|||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
|
@ -36,6 +37,7 @@ fun DatabasePosts(
|
|||
items: ImmutableMap<String, List<UIPost>>,
|
||||
listState: LazyListState,
|
||||
postActions: PostActions,
|
||||
contentPadding: PaddingValues,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
ReportDrawn()
|
||||
|
@ -50,7 +52,7 @@ fun DatabasePosts(
|
|||
Text(text = "No saved posts", style = MaterialTheme.typography.headlineSmall)
|
||||
}
|
||||
} else {
|
||||
LazyColumn(state = listState) {
|
||||
LazyColumn(state = listState, contentPadding = contentPadding) {
|
||||
items.forEach { (month, posts) ->
|
||||
stickyHeader(contentType = "month-header") { MonthHeader(label = month) }
|
||||
items(items = posts, key = { it.shortId }, contentType = { "LobstersItem" }) { item ->
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
package dev.msfjarvis.claw.android.ui.lists
|
||||
|
||||
import androidx.activity.compose.ReportDrawnWhen
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
|
@ -47,6 +48,7 @@ fun NetworkPosts(
|
|||
lazyPagingItems: LazyPagingItems<UIPost>,
|
||||
listState: LazyListState,
|
||||
postActions: PostActions,
|
||||
contentPadding: PaddingValues,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
ReportDrawnWhen { lazyPagingItems.itemCount > 0 }
|
||||
|
@ -72,7 +74,7 @@ fun NetworkPosts(
|
|||
modifier = Modifier.align(Alignment.Center),
|
||||
)
|
||||
} else {
|
||||
LazyColumn(state = listState) {
|
||||
LazyColumn(contentPadding = contentPadding, state = listState) {
|
||||
items(
|
||||
count = lazyPagingItems.itemCount,
|
||||
key = lazyPagingItems.itemKey { it.shortId },
|
||||
|
@ -109,6 +111,7 @@ private fun ListPreview() {
|
|||
lazyPagingItems = flow.collectAsLazyPagingItems(),
|
||||
listState = rememberLazyListState(),
|
||||
postActions = TEST_POST_ACTIONS,
|
||||
contentPadding = PaddingValues(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
package dev.msfjarvis.claw.android.ui.lists
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
|
@ -32,6 +33,7 @@ fun SearchList(
|
|||
searchQuery: String,
|
||||
setSearchQuery: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
contentPadding: PaddingValues = PaddingValues(),
|
||||
) {
|
||||
val lazyPagingItems = items.collectAsLazyPagingItems()
|
||||
val triggerSearch = { query: String ->
|
||||
|
@ -49,6 +51,7 @@ fun SearchList(
|
|||
lazyPagingItems = lazyPagingItems,
|
||||
listState = listState,
|
||||
postActions = postActions,
|
||||
contentPadding = contentPadding,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,6 @@ import androidx.compose.animation.fadeIn
|
|||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.icons.Icons
|
||||
|
@ -26,7 +25,6 @@ import androidx.compose.material.icons.filled.Whatshot
|
|||
import androidx.compose.material.icons.outlined.FavoriteBorder
|
||||
import androidx.compose.material.icons.outlined.NewReleases
|
||||
import androidx.compose.material.icons.outlined.Whatshot
|
||||
import androidx.compose.material3.BottomAppBarDefaults
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
|
@ -45,7 +43,6 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
@ -61,6 +58,8 @@ import androidx.navigation.toRoute
|
|||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import com.deliveryhero.whetstone.compose.injectedViewModel
|
||||
import com.mikepenz.aboutlibraries.ui.compose.m3.LibrariesContainer
|
||||
import dev.chrisbanes.haze.HazeState
|
||||
import dev.chrisbanes.haze.haze
|
||||
import dev.msfjarvis.claw.android.MainActivity
|
||||
import dev.msfjarvis.claw.android.R
|
||||
import dev.msfjarvis.claw.android.SearchActivity
|
||||
|
@ -111,8 +110,7 @@ fun LobstersPostsScreen(
|
|||
val coroutineScope = rememberCoroutineScope()
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
val postActions = rememberPostActions(context, urlLauncher, navController, viewModel)
|
||||
|
||||
val scrollBehavior = BottomAppBarDefaults.exitAlwaysScrollBehavior()
|
||||
val hazeState = remember { HazeState() }
|
||||
|
||||
val hottestPosts = viewModel.hottestPosts.collectAsLazyPagingItems()
|
||||
val newestPosts = viewModel.newestPosts.collectAsLazyPagingItems()
|
||||
|
@ -209,17 +207,14 @@ fun LobstersPostsScreen(
|
|||
navController = navController,
|
||||
items = navItems,
|
||||
isVisible = currentDestination.any(navDestinations),
|
||||
scrollBehavior = scrollBehavior,
|
||||
hazeState = hazeState,
|
||||
)
|
||||
}
|
||||
},
|
||||
snackbarHost = { SnackbarHost(snackbarHostState) },
|
||||
modifier =
|
||||
modifier.nestedScroll(scrollBehavior.nestedScrollConnection).semantics {
|
||||
testTagsAsResourceId = true
|
||||
},
|
||||
) { paddingValues ->
|
||||
Row(modifier = Modifier.padding(paddingValues)) {
|
||||
modifier = modifier.semantics { testTagsAsResourceId = true },
|
||||
) { contentPadding ->
|
||||
Row {
|
||||
AnimatedVisibility(visible = navigationType == ClawNavigationType.NAVIGATION_RAIL) {
|
||||
ClawNavigationRail(
|
||||
navController = navController,
|
||||
|
@ -234,6 +229,7 @@ fun LobstersPostsScreen(
|
|||
// Make animations 2x faster than default specs
|
||||
enterTransition = { fadeIn(animationSpec = tween(350)) },
|
||||
exitTransition = { fadeOut(animationSpec = tween(350)) },
|
||||
modifier = Modifier.haze(hazeState),
|
||||
) {
|
||||
composable<Hottest> {
|
||||
setWebUri("https://lobste.rs/")
|
||||
|
@ -241,6 +237,7 @@ fun LobstersPostsScreen(
|
|||
lazyPagingItems = hottestPosts,
|
||||
listState = hottestListState,
|
||||
postActions = postActions,
|
||||
contentPadding = contentPadding,
|
||||
)
|
||||
}
|
||||
composable<Newest> {
|
||||
|
@ -249,11 +246,17 @@ fun LobstersPostsScreen(
|
|||
lazyPagingItems = newestPosts,
|
||||
listState = newestListState,
|
||||
postActions = postActions,
|
||||
contentPadding = contentPadding,
|
||||
)
|
||||
}
|
||||
composable<Saved> {
|
||||
setWebUri(null)
|
||||
DatabasePosts(items = savedPosts, listState = savedListState, postActions = postActions)
|
||||
DatabasePosts(
|
||||
items = savedPosts,
|
||||
listState = savedListState,
|
||||
postActions = postActions,
|
||||
contentPadding = contentPadding,
|
||||
)
|
||||
}
|
||||
composable<Comments> { backStackEntry ->
|
||||
val postId = backStackEntry.toRoute<Comments>().postId
|
||||
|
@ -264,6 +267,7 @@ fun LobstersPostsScreen(
|
|||
htmlConverter = htmlConverter,
|
||||
getSeenComments = viewModel::getSeenComments,
|
||||
markSeenComments = viewModel::markSeenComments,
|
||||
contentPadding = contentPadding,
|
||||
openUserProfile = { navController.navigate(User(it)) },
|
||||
)
|
||||
}
|
||||
|
@ -273,6 +277,7 @@ fun LobstersPostsScreen(
|
|||
UserProfile(
|
||||
username = username,
|
||||
getProfile = viewModel::getUserProfile,
|
||||
contentPadding = contentPadding,
|
||||
openUserProfile = { navController.navigate(User(it)) },
|
||||
)
|
||||
}
|
||||
|
@ -284,9 +289,12 @@ fun LobstersPostsScreen(
|
|||
exportPostsAsJson = viewModel::exportPostsAsJson,
|
||||
exportPostsAsHtml = viewModel::exportPostsAsHtml,
|
||||
snackbarHostState = snackbarHostState,
|
||||
contentPadding = contentPadding,
|
||||
)
|
||||
}
|
||||
composable<AboutLibraries> { LibrariesContainer(modifier = Modifier.fillMaxSize()) }
|
||||
composable<AboutLibraries> {
|
||||
LibrariesContainer(contentPadding = contentPadding, modifier = Modifier.fillMaxSize())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
*/
|
||||
package dev.msfjarvis.claw.android.ui.screens
|
||||
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.runtime.Composable
|
||||
|
@ -39,12 +38,8 @@ fun SearchScreen(
|
|||
val navController = rememberNavController()
|
||||
val postActions = rememberPostActions(LocalContext.current, urlLauncher, navController, viewModel)
|
||||
val listState = rememberLazyListState()
|
||||
Scaffold(modifier = modifier) { paddingValues ->
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = Search,
|
||||
modifier = Modifier.padding(paddingValues),
|
||||
) {
|
||||
Scaffold(modifier = modifier) { contentPadding ->
|
||||
NavHost(navController = navController, startDestination = Search) {
|
||||
composable<Search> {
|
||||
setWebUri("https://lobste.rs/search")
|
||||
SearchList(
|
||||
|
@ -52,6 +47,7 @@ fun SearchScreen(
|
|||
listState = listState,
|
||||
postActions = postActions,
|
||||
searchQuery = viewModel.searchQuery,
|
||||
contentPadding = contentPadding,
|
||||
setSearchQuery = { query -> viewModel.searchQuery = query },
|
||||
)
|
||||
}
|
||||
|
@ -65,6 +61,7 @@ fun SearchScreen(
|
|||
getSeenComments = viewModel::getSeenComments,
|
||||
markSeenComments = viewModel::markSeenComments,
|
||||
openUserProfile = { navController.navigate(User(it)) },
|
||||
contentPadding = contentPadding,
|
||||
)
|
||||
}
|
||||
composable<User> { backStackEntry ->
|
||||
|
@ -74,6 +71,7 @@ fun SearchScreen(
|
|||
username = username,
|
||||
getProfile = viewModel::getUserProfile,
|
||||
openUserProfile = { navController.navigate(User(it)) },
|
||||
contentPadding = contentPadding,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,11 +13,13 @@ import androidx.compose.foundation.clickable
|
|||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.LibraryBooks
|
||||
import androidx.compose.material.icons.filled.Bookmarks
|
||||
|
@ -55,10 +57,11 @@ fun SettingsScreen(
|
|||
importPosts: suspend (InputStream) -> Unit,
|
||||
exportPostsAsJson: suspend (OutputStream) -> Unit,
|
||||
exportPostsAsHtml: suspend (OutputStream) -> Unit,
|
||||
contentPadding: PaddingValues,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
Box(modifier = modifier.fillMaxSize()) {
|
||||
Box(modifier = modifier.padding(contentPadding).fillMaxSize()) {
|
||||
Column {
|
||||
ListItem(
|
||||
headlineContent = { Text("Data transfer") },
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
package dev.msfjarvis.claw.common.comments
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
|
@ -34,6 +35,7 @@ fun CommentsPage(
|
|||
htmlConverter: HTMLConverter,
|
||||
getSeenComments: suspend (String) -> PostComments,
|
||||
markSeenComments: (String, List<Comment>) -> Unit,
|
||||
contentPadding: PaddingValues,
|
||||
modifier: Modifier = Modifier,
|
||||
openUserProfile: (String) -> Unit,
|
||||
) {
|
||||
|
@ -59,6 +61,7 @@ fun CommentsPage(
|
|||
commentState = commentState,
|
||||
markSeenComments = markSeenComments,
|
||||
openUserProfile = openUserProfile,
|
||||
contentPadding = contentPadding,
|
||||
modifier = modifier.fillMaxSize(),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -52,6 +52,7 @@ internal fun CommentsPageInternal(
|
|||
commentState: PostComments,
|
||||
markSeenComments: (String, List<Comment>) -> Unit,
|
||||
openUserProfile: (String) -> Unit,
|
||||
contentPadding: PaddingValues,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
@ -68,7 +69,7 @@ internal fun CommentsPageInternal(
|
|||
}
|
||||
|
||||
Surface(color = MaterialTheme.colorScheme.surfaceVariant) {
|
||||
LazyColumn(modifier = modifier, contentPadding = PaddingValues(bottom = 24.dp)) {
|
||||
LazyColumn(modifier = modifier, contentPadding = contentPadding) {
|
||||
item {
|
||||
CommentsHeader(
|
||||
post = details,
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Copyright © 2024 Harsh Shandilya.
|
||||
* Use of this source code is governed by an MIT-style
|
||||
* license that can be found in the LICENSE file or at
|
||||
* https://opensource.org/licenses/MIT.
|
||||
*/
|
||||
package dev.msfjarvis.claw.common.ui
|
||||
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.selection.selectableGroup
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.BarChart
|
||||
import androidx.compose.material.icons.filled.BrokenImage
|
||||
import androidx.compose.material.icons.filled.HeartBroken
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.NavigationBarDefaults
|
||||
import androidx.compose.material3.NavigationBarItem
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.contentColorFor
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
// Taken from Chris Banes' amazing app Tivi
|
||||
// https://github.com/chrisbanes/tivi/blob/836d596d74959f4235ca2395b5bbfdd6fd9c9a9e/ui/root/src/commonMain/kotlin/app/tivi/home/Home.kt#L173
|
||||
@Composable
|
||||
fun FloatingNavigationBar(
|
||||
modifier: Modifier = Modifier,
|
||||
shape: Shape = MaterialTheme.shapes.extraLarge,
|
||||
containerColor: Color = NavigationBarDefaults.containerColor,
|
||||
contentColor: Color = MaterialTheme.colorScheme.contentColorFor(containerColor),
|
||||
tonalElevation: Dp = NavigationBarDefaults.Elevation,
|
||||
content: @Composable RowScope.() -> Unit,
|
||||
) {
|
||||
Surface(
|
||||
color = containerColor,
|
||||
contentColor = contentColor,
|
||||
tonalElevation = tonalElevation,
|
||||
shape = shape,
|
||||
border =
|
||||
BorderStroke(
|
||||
width = 0.5.dp,
|
||||
brush =
|
||||
Brush.verticalGradient(
|
||||
colors =
|
||||
listOf(
|
||||
MaterialTheme.colorScheme.surfaceVariant,
|
||||
MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.3f),
|
||||
)
|
||||
),
|
||||
),
|
||||
modifier = modifier,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(horizontal = 8.dp).fillMaxWidth().height(80.dp).selectableGroup(),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun FloatingNavigationBarPreview() {
|
||||
FloatingNavigationBar {
|
||||
NavigationBarItem(
|
||||
selected = true,
|
||||
onClick = {},
|
||||
icon = { Icon(imageVector = Icons.Filled.HeartBroken, contentDescription = "Home") },
|
||||
)
|
||||
NavigationBarItem(
|
||||
selected = true,
|
||||
onClick = {},
|
||||
icon = { Icon(imageVector = Icons.Filled.BarChart, contentDescription = "Home") },
|
||||
)
|
||||
NavigationBarItem(
|
||||
selected = true,
|
||||
onClick = {},
|
||||
icon = { Icon(imageVector = Icons.Filled.BrokenImage, contentDescription = "Home") },
|
||||
)
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ package dev.msfjarvis.claw.common.user
|
|||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.requiredSize
|
||||
|
@ -45,6 +46,7 @@ fun UserProfile(
|
|||
username: String,
|
||||
getProfile: suspend (username: String) -> User,
|
||||
openUserProfile: (String) -> Unit,
|
||||
contentPadding: PaddingValues,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val user by
|
||||
|
@ -67,7 +69,7 @@ fun UserProfile(
|
|||
}
|
||||
is Error -> {
|
||||
val error = user as Error
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
Box(modifier = Modifier.padding(contentPadding).fillMaxSize()) {
|
||||
NetworkError(
|
||||
label = error.description,
|
||||
error = error.error,
|
||||
|
|
|
@ -73,6 +73,8 @@ copydown = "io.github.furstenheim:copy_down:1.1"
|
|||
dagger = { module = "com.google.dagger:dagger", version.ref = "dagger" }
|
||||
dagger-compiler = { module = "com.google.dagger:dagger-compiler", version.ref = "dagger" }
|
||||
eithernet = "com.slack.eithernet:eithernet:1.9.0"
|
||||
haze = "dev.chrisbanes.haze:haze:0.9.0-alpha08"
|
||||
haze-materials = "dev.chrisbanes.haze:haze-materials:0.9.0-alpha08"
|
||||
javax-inject = "javax.inject:javax.inject:1"
|
||||
jsoup = "org.jsoup:jsoup:1.18.1"
|
||||
junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" }
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue