From 1de4916c9cf0550b03d79279b56a5fb6ccd06f6a Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Wed, 28 Aug 2024 15:16:42 +0530 Subject: [PATCH] feat: redesign bottom navigation bar --- CHANGELOG.md | 2 +- android/build.gradle.kts | 2 + .../ui/decorations/ClawNavigationBar.kt | 100 +++++++++++------- .../claw/android/ui/lists/DatabasePosts.kt | 4 +- .../claw/android/ui/lists/NetworkPosts.kt | 5 +- .../claw/android/ui/lists/SearchList.kt | 3 + .../android/ui/screens/LobstersPostsScreen.kt | 36 ++++--- .../claw/android/ui/screens/SearchScreen.kt | 12 +-- .../claw/android/ui/screens/SettingsScreen.kt | 5 +- .../claw/common/comments/CommentsPage.kt | 3 + .../claw/common/comments/CommentsPageImpl.kt | 3 +- .../claw/common/ui/FloatingNavigationBar.kt | 94 ++++++++++++++++ .../msfjarvis/claw/common/user/UserProfile.kt | 4 +- gradle/libs.versions.toml | 2 + 14 files changed, 211 insertions(+), 64 deletions(-) create mode 100644 common/src/main/kotlin/dev/msfjarvis/claw/common/ui/FloatingNavigationBar.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 6960911b..0cab9577 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/android/build.gradle.kts b/android/build.gradle.kts index b218f453..26453419 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -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) diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/decorations/ClawNavigationBar.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/decorations/ClawNavigationBar.kt index 1f1c04b1..d9ff9b1b 100644 --- a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/decorations/ClawNavigationBar.kt +++ b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/decorations/ClawNavigationBar.kt @@ -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, 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( diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/lists/DatabasePosts.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/lists/DatabasePosts.kt index 48e920fa..b34b1478 100644 --- a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/lists/DatabasePosts.kt +++ b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/lists/DatabasePosts.kt @@ -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>, 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 -> diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/lists/NetworkPosts.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/lists/NetworkPosts.kt index e996e37e..be5c9763 100644 --- a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/lists/NetworkPosts.kt +++ b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/lists/NetworkPosts.kt @@ -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, 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(), ) } } diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/lists/SearchList.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/lists/SearchList.kt index ef06f0aa..c86e0f06 100644 --- a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/lists/SearchList.kt +++ b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/lists/SearchList.kt @@ -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, ) } } diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/LobstersPostsScreen.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/LobstersPostsScreen.kt index f28bfc01..1d3edb33 100644 --- a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/LobstersPostsScreen.kt +++ b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/LobstersPostsScreen.kt @@ -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 { setWebUri("https://lobste.rs/") @@ -241,6 +237,7 @@ fun LobstersPostsScreen( lazyPagingItems = hottestPosts, listState = hottestListState, postActions = postActions, + contentPadding = contentPadding, ) } composable { @@ -249,11 +246,17 @@ fun LobstersPostsScreen( lazyPagingItems = newestPosts, listState = newestListState, postActions = postActions, + contentPadding = contentPadding, ) } composable { setWebUri(null) - DatabasePosts(items = savedPosts, listState = savedListState, postActions = postActions) + DatabasePosts( + items = savedPosts, + listState = savedListState, + postActions = postActions, + contentPadding = contentPadding, + ) } composable { backStackEntry -> val postId = backStackEntry.toRoute().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 { LibrariesContainer(modifier = Modifier.fillMaxSize()) } + composable { + LibrariesContainer(contentPadding = contentPadding, modifier = Modifier.fillMaxSize()) + } } } } diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/SearchScreen.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/SearchScreen.kt index c6fa72f7..4d72a009 100644 --- a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/SearchScreen.kt +++ b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/SearchScreen.kt @@ -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 { 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 { backStackEntry -> @@ -74,6 +71,7 @@ fun SearchScreen( username = username, getProfile = viewModel::getUserProfile, openUserProfile = { navController.navigate(User(it)) }, + contentPadding = contentPadding, ) } } diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/SettingsScreen.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/SettingsScreen.kt index eb8bdc65..4b33598f 100644 --- a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/SettingsScreen.kt +++ b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/SettingsScreen.kt @@ -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") }, diff --git a/common/src/main/kotlin/dev/msfjarvis/claw/common/comments/CommentsPage.kt b/common/src/main/kotlin/dev/msfjarvis/claw/common/comments/CommentsPage.kt index d0ff3ae6..14c452e3 100644 --- a/common/src/main/kotlin/dev/msfjarvis/claw/common/comments/CommentsPage.kt +++ b/common/src/main/kotlin/dev/msfjarvis/claw/common/comments/CommentsPage.kt @@ -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) -> 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(), ) } diff --git a/common/src/main/kotlin/dev/msfjarvis/claw/common/comments/CommentsPageImpl.kt b/common/src/main/kotlin/dev/msfjarvis/claw/common/comments/CommentsPageImpl.kt index 797288fb..638abb32 100644 --- a/common/src/main/kotlin/dev/msfjarvis/claw/common/comments/CommentsPageImpl.kt +++ b/common/src/main/kotlin/dev/msfjarvis/claw/common/comments/CommentsPageImpl.kt @@ -52,6 +52,7 @@ internal fun CommentsPageInternal( commentState: PostComments, markSeenComments: (String, List) -> 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, diff --git a/common/src/main/kotlin/dev/msfjarvis/claw/common/ui/FloatingNavigationBar.kt b/common/src/main/kotlin/dev/msfjarvis/claw/common/ui/FloatingNavigationBar.kt new file mode 100644 index 00000000..56b8df94 --- /dev/null +++ b/common/src/main/kotlin/dev/msfjarvis/claw/common/ui/FloatingNavigationBar.kt @@ -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") }, + ) + } +} diff --git a/common/src/main/kotlin/dev/msfjarvis/claw/common/user/UserProfile.kt b/common/src/main/kotlin/dev/msfjarvis/claw/common/user/UserProfile.kt index 4639c1fe..bfc21226 100644 --- a/common/src/main/kotlin/dev/msfjarvis/claw/common/user/UserProfile.kt +++ b/common/src/main/kotlin/dev/msfjarvis/claw/common/user/UserProfile.kt @@ -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, diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index dfe9f2e5..e8acbeff 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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" }