mirror of
https://github.com/msfjarvis/compose-lobsters
synced 2025-08-14 19:57:04 +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
|
- Change submitter text to 'authored' when applicable
|
||||||
- Unread comments now have a brighter background rather than a text badge
|
- 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
|
## [1.48.0] - 2024-06-05
|
||||||
|
|
||||||
|
|
|
@ -95,6 +95,8 @@ dependencies {
|
||||||
implementation(libs.copydown)
|
implementation(libs.copydown)
|
||||||
implementation(libs.dagger)
|
implementation(libs.dagger)
|
||||||
implementation(libs.eithernet)
|
implementation(libs.eithernet)
|
||||||
|
implementation(libs.haze)
|
||||||
|
implementation(libs.haze.materials)
|
||||||
implementation(libs.javax.inject)
|
implementation(libs.javax.inject)
|
||||||
implementation(libs.kotlinx.collections.immutable)
|
implementation(libs.kotlinx.collections.immutable)
|
||||||
implementation(libs.kotlinx.coroutines.core)
|
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.core.tween
|
||||||
import androidx.compose.animation.slideInVertically
|
import androidx.compose.animation.slideInVertically
|
||||||
import androidx.compose.animation.slideOutVertically
|
import androidx.compose.animation.slideOutVertically
|
||||||
import androidx.compose.material3.BottomAppBar
|
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||||
import androidx.compose.material3.BottomAppBarScrollBehavior
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.NavigationBarItem
|
import androidx.compose.material3.NavigationBarItem
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
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.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.platform.testTag
|
import androidx.compose.ui.platform.testTag
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
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.Destination
|
||||||
import dev.msfjarvis.claw.android.ui.navigation.matches
|
import dev.msfjarvis.claw.android.ui.navigation.matches
|
||||||
|
import dev.msfjarvis.claw.common.ui.FloatingNavigationBar
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
const val AnimationDuration = 100
|
const val AnimationDuration = 100
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ClawNavigationBar(
|
fun ClawNavigationBar(
|
||||||
navController: NavController,
|
navController: NavController,
|
||||||
items: ImmutableList<NavigationItem>,
|
items: ImmutableList<NavigationItem>,
|
||||||
isVisible: Boolean,
|
isVisible: Boolean,
|
||||||
scrollBehavior: BottomAppBarScrollBehavior,
|
hazeState: HazeState,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
|
@ -54,40 +60,60 @@ fun ClawNavigationBar(
|
||||||
targetOffsetY = { fullHeight -> fullHeight },
|
targetOffsetY = { fullHeight -> fullHeight },
|
||||||
animationSpec = tween(durationMillis = AnimationDuration, easing = FastOutLinearInEasing),
|
animationSpec = tween(durationMillis = AnimationDuration, easing = FastOutLinearInEasing),
|
||||||
),
|
),
|
||||||
modifier = Modifier,
|
label = "",
|
||||||
) {
|
content = {
|
||||||
BottomAppBar(modifier = modifier, scrollBehavior = scrollBehavior) {
|
FloatingNavigationBar(
|
||||||
val navBackStackEntry = navController.currentBackStackEntryAsState().value
|
tonalElevation = 16.dp,
|
||||||
val currentDestination = navBackStackEntry?.destination
|
shape = MaterialTheme.shapes.extraLarge,
|
||||||
items.forEach { navItem ->
|
modifier =
|
||||||
val isSelected = currentDestination.matches(navItem.destination)
|
modifier
|
||||||
NavigationBarItem(
|
.padding(horizontal = 16.dp)
|
||||||
icon = {
|
.navigationBarsPadding()
|
||||||
Crossfade(isSelected, label = "nav-label") {
|
.clip(MaterialTheme.shapes.extraLarge)
|
||||||
Icon(
|
.hazeChild(
|
||||||
imageVector = if (it) navItem.selectedIcon else navItem.icon,
|
hazeState,
|
||||||
contentDescription = navItem.label.replaceFirstChar(Char::uppercase),
|
style =
|
||||||
)
|
HazeStyle(
|
||||||
}
|
backgroundColor = MaterialTheme.colorScheme.surface,
|
||||||
},
|
tints = emptyList(),
|
||||||
label = { Text(text = navItem.label) },
|
blurRadius = 24.dp,
|
||||||
selected = isSelected,
|
noiseFactor = 0f,
|
||||||
onClick = {
|
),
|
||||||
if (isSelected) {
|
),
|
||||||
navItem.listStateResetCallback()
|
containerColor = Color.Transparent,
|
||||||
} else {
|
) {
|
||||||
navController.navigate(navItem.destination) {
|
val navBackStackEntry = navController.currentBackStackEntryAsState().value
|
||||||
popUpTo(navController.graph.startDestinationId) { saveState = true }
|
val currentDestination = navBackStackEntry?.destination
|
||||||
launchSingleTop = true
|
items.forEach { navItem ->
|
||||||
restoreState = true
|
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) },
|
||||||
modifier = Modifier.testTag(navItem.label.uppercase()),
|
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(
|
class NavigationItem(
|
||||||
|
|
|
@ -10,6 +10,7 @@ import androidx.activity.compose.ReportDrawn
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
@ -36,6 +37,7 @@ fun DatabasePosts(
|
||||||
items: ImmutableMap<String, List<UIPost>>,
|
items: ImmutableMap<String, List<UIPost>>,
|
||||||
listState: LazyListState,
|
listState: LazyListState,
|
||||||
postActions: PostActions,
|
postActions: PostActions,
|
||||||
|
contentPadding: PaddingValues,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
ReportDrawn()
|
ReportDrawn()
|
||||||
|
@ -50,7 +52,7 @@ fun DatabasePosts(
|
||||||
Text(text = "No saved posts", style = MaterialTheme.typography.headlineSmall)
|
Text(text = "No saved posts", style = MaterialTheme.typography.headlineSmall)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LazyColumn(state = listState) {
|
LazyColumn(state = listState, contentPadding = contentPadding) {
|
||||||
items.forEach { (month, posts) ->
|
items.forEach { (month, posts) ->
|
||||||
stickyHeader(contentType = "month-header") { MonthHeader(label = month) }
|
stickyHeader(contentType = "month-header") { MonthHeader(label = month) }
|
||||||
items(items = posts, key = { it.shortId }, contentType = { "LobstersItem" }) { item ->
|
items(items = posts, key = { it.shortId }, contentType = { "LobstersItem" }) { item ->
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
package dev.msfjarvis.claw.android.ui.lists
|
package dev.msfjarvis.claw.android.ui.lists
|
||||||
|
|
||||||
import androidx.activity.compose.ReportDrawnWhen
|
import androidx.activity.compose.ReportDrawnWhen
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
@ -47,6 +48,7 @@ fun NetworkPosts(
|
||||||
lazyPagingItems: LazyPagingItems<UIPost>,
|
lazyPagingItems: LazyPagingItems<UIPost>,
|
||||||
listState: LazyListState,
|
listState: LazyListState,
|
||||||
postActions: PostActions,
|
postActions: PostActions,
|
||||||
|
contentPadding: PaddingValues,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
ReportDrawnWhen { lazyPagingItems.itemCount > 0 }
|
ReportDrawnWhen { lazyPagingItems.itemCount > 0 }
|
||||||
|
@ -72,7 +74,7 @@ fun NetworkPosts(
|
||||||
modifier = Modifier.align(Alignment.Center),
|
modifier = Modifier.align(Alignment.Center),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
LazyColumn(state = listState) {
|
LazyColumn(contentPadding = contentPadding, state = listState) {
|
||||||
items(
|
items(
|
||||||
count = lazyPagingItems.itemCount,
|
count = lazyPagingItems.itemCount,
|
||||||
key = lazyPagingItems.itemKey { it.shortId },
|
key = lazyPagingItems.itemKey { it.shortId },
|
||||||
|
@ -109,6 +111,7 @@ private fun ListPreview() {
|
||||||
lazyPagingItems = flow.collectAsLazyPagingItems(),
|
lazyPagingItems = flow.collectAsLazyPagingItems(),
|
||||||
listState = rememberLazyListState(),
|
listState = rememberLazyListState(),
|
||||||
postActions = TEST_POST_ACTIONS,
|
postActions = TEST_POST_ACTIONS,
|
||||||
|
contentPadding = PaddingValues(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
package dev.msfjarvis.claw.android.ui.lists
|
package dev.msfjarvis.claw.android.ui.lists
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
|
@ -32,6 +33,7 @@ fun SearchList(
|
||||||
searchQuery: String,
|
searchQuery: String,
|
||||||
setSearchQuery: (String) -> Unit,
|
setSearchQuery: (String) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
contentPadding: PaddingValues = PaddingValues(),
|
||||||
) {
|
) {
|
||||||
val lazyPagingItems = items.collectAsLazyPagingItems()
|
val lazyPagingItems = items.collectAsLazyPagingItems()
|
||||||
val triggerSearch = { query: String ->
|
val triggerSearch = { query: String ->
|
||||||
|
@ -49,6 +51,7 @@ fun SearchList(
|
||||||
lazyPagingItems = lazyPagingItems,
|
lazyPagingItems = lazyPagingItems,
|
||||||
listState = listState,
|
listState = listState,
|
||||||
postActions = postActions,
|
postActions = postActions,
|
||||||
|
contentPadding = contentPadding,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,6 @@ import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material.icons.Icons
|
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.FavoriteBorder
|
||||||
import androidx.compose.material.icons.outlined.NewReleases
|
import androidx.compose.material.icons.outlined.NewReleases
|
||||||
import androidx.compose.material.icons.outlined.Whatshot
|
import androidx.compose.material.icons.outlined.Whatshot
|
||||||
import androidx.compose.material3.BottomAppBarDefaults
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
|
@ -45,7 +43,6 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.shadow
|
import androidx.compose.ui.draw.shadow
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
@ -61,6 +58,8 @@ import androidx.navigation.toRoute
|
||||||
import androidx.paging.compose.collectAsLazyPagingItems
|
import androidx.paging.compose.collectAsLazyPagingItems
|
||||||
import com.deliveryhero.whetstone.compose.injectedViewModel
|
import com.deliveryhero.whetstone.compose.injectedViewModel
|
||||||
import com.mikepenz.aboutlibraries.ui.compose.m3.LibrariesContainer
|
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.MainActivity
|
||||||
import dev.msfjarvis.claw.android.R
|
import dev.msfjarvis.claw.android.R
|
||||||
import dev.msfjarvis.claw.android.SearchActivity
|
import dev.msfjarvis.claw.android.SearchActivity
|
||||||
|
@ -111,8 +110,7 @@ fun LobstersPostsScreen(
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
val postActions = rememberPostActions(context, urlLauncher, navController, viewModel)
|
val postActions = rememberPostActions(context, urlLauncher, navController, viewModel)
|
||||||
|
val hazeState = remember { HazeState() }
|
||||||
val scrollBehavior = BottomAppBarDefaults.exitAlwaysScrollBehavior()
|
|
||||||
|
|
||||||
val hottestPosts = viewModel.hottestPosts.collectAsLazyPagingItems()
|
val hottestPosts = viewModel.hottestPosts.collectAsLazyPagingItems()
|
||||||
val newestPosts = viewModel.newestPosts.collectAsLazyPagingItems()
|
val newestPosts = viewModel.newestPosts.collectAsLazyPagingItems()
|
||||||
|
@ -209,17 +207,14 @@ fun LobstersPostsScreen(
|
||||||
navController = navController,
|
navController = navController,
|
||||||
items = navItems,
|
items = navItems,
|
||||||
isVisible = currentDestination.any(navDestinations),
|
isVisible = currentDestination.any(navDestinations),
|
||||||
scrollBehavior = scrollBehavior,
|
hazeState = hazeState,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
snackbarHost = { SnackbarHost(snackbarHostState) },
|
snackbarHost = { SnackbarHost(snackbarHostState) },
|
||||||
modifier =
|
modifier = modifier.semantics { testTagsAsResourceId = true },
|
||||||
modifier.nestedScroll(scrollBehavior.nestedScrollConnection).semantics {
|
) { contentPadding ->
|
||||||
testTagsAsResourceId = true
|
Row {
|
||||||
},
|
|
||||||
) { paddingValues ->
|
|
||||||
Row(modifier = Modifier.padding(paddingValues)) {
|
|
||||||
AnimatedVisibility(visible = navigationType == ClawNavigationType.NAVIGATION_RAIL) {
|
AnimatedVisibility(visible = navigationType == ClawNavigationType.NAVIGATION_RAIL) {
|
||||||
ClawNavigationRail(
|
ClawNavigationRail(
|
||||||
navController = navController,
|
navController = navController,
|
||||||
|
@ -234,6 +229,7 @@ fun LobstersPostsScreen(
|
||||||
// Make animations 2x faster than default specs
|
// Make animations 2x faster than default specs
|
||||||
enterTransition = { fadeIn(animationSpec = tween(350)) },
|
enterTransition = { fadeIn(animationSpec = tween(350)) },
|
||||||
exitTransition = { fadeOut(animationSpec = tween(350)) },
|
exitTransition = { fadeOut(animationSpec = tween(350)) },
|
||||||
|
modifier = Modifier.haze(hazeState),
|
||||||
) {
|
) {
|
||||||
composable<Hottest> {
|
composable<Hottest> {
|
||||||
setWebUri("https://lobste.rs/")
|
setWebUri("https://lobste.rs/")
|
||||||
|
@ -241,6 +237,7 @@ fun LobstersPostsScreen(
|
||||||
lazyPagingItems = hottestPosts,
|
lazyPagingItems = hottestPosts,
|
||||||
listState = hottestListState,
|
listState = hottestListState,
|
||||||
postActions = postActions,
|
postActions = postActions,
|
||||||
|
contentPadding = contentPadding,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
composable<Newest> {
|
composable<Newest> {
|
||||||
|
@ -249,11 +246,17 @@ fun LobstersPostsScreen(
|
||||||
lazyPagingItems = newestPosts,
|
lazyPagingItems = newestPosts,
|
||||||
listState = newestListState,
|
listState = newestListState,
|
||||||
postActions = postActions,
|
postActions = postActions,
|
||||||
|
contentPadding = contentPadding,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
composable<Saved> {
|
composable<Saved> {
|
||||||
setWebUri(null)
|
setWebUri(null)
|
||||||
DatabasePosts(items = savedPosts, listState = savedListState, postActions = postActions)
|
DatabasePosts(
|
||||||
|
items = savedPosts,
|
||||||
|
listState = savedListState,
|
||||||
|
postActions = postActions,
|
||||||
|
contentPadding = contentPadding,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
composable<Comments> { backStackEntry ->
|
composable<Comments> { backStackEntry ->
|
||||||
val postId = backStackEntry.toRoute<Comments>().postId
|
val postId = backStackEntry.toRoute<Comments>().postId
|
||||||
|
@ -264,6 +267,7 @@ fun LobstersPostsScreen(
|
||||||
htmlConverter = htmlConverter,
|
htmlConverter = htmlConverter,
|
||||||
getSeenComments = viewModel::getSeenComments,
|
getSeenComments = viewModel::getSeenComments,
|
||||||
markSeenComments = viewModel::markSeenComments,
|
markSeenComments = viewModel::markSeenComments,
|
||||||
|
contentPadding = contentPadding,
|
||||||
openUserProfile = { navController.navigate(User(it)) },
|
openUserProfile = { navController.navigate(User(it)) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -273,6 +277,7 @@ fun LobstersPostsScreen(
|
||||||
UserProfile(
|
UserProfile(
|
||||||
username = username,
|
username = username,
|
||||||
getProfile = viewModel::getUserProfile,
|
getProfile = viewModel::getUserProfile,
|
||||||
|
contentPadding = contentPadding,
|
||||||
openUserProfile = { navController.navigate(User(it)) },
|
openUserProfile = { navController.navigate(User(it)) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -284,9 +289,12 @@ fun LobstersPostsScreen(
|
||||||
exportPostsAsJson = viewModel::exportPostsAsJson,
|
exportPostsAsJson = viewModel::exportPostsAsJson,
|
||||||
exportPostsAsHtml = viewModel::exportPostsAsHtml,
|
exportPostsAsHtml = viewModel::exportPostsAsHtml,
|
||||||
snackbarHostState = snackbarHostState,
|
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
|
package dev.msfjarvis.claw.android.ui.screens
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
@ -39,12 +38,8 @@ fun SearchScreen(
|
||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
val postActions = rememberPostActions(LocalContext.current, urlLauncher, navController, viewModel)
|
val postActions = rememberPostActions(LocalContext.current, urlLauncher, navController, viewModel)
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
Scaffold(modifier = modifier) { paddingValues ->
|
Scaffold(modifier = modifier) { contentPadding ->
|
||||||
NavHost(
|
NavHost(navController = navController, startDestination = Search) {
|
||||||
navController = navController,
|
|
||||||
startDestination = Search,
|
|
||||||
modifier = Modifier.padding(paddingValues),
|
|
||||||
) {
|
|
||||||
composable<Search> {
|
composable<Search> {
|
||||||
setWebUri("https://lobste.rs/search")
|
setWebUri("https://lobste.rs/search")
|
||||||
SearchList(
|
SearchList(
|
||||||
|
@ -52,6 +47,7 @@ fun SearchScreen(
|
||||||
listState = listState,
|
listState = listState,
|
||||||
postActions = postActions,
|
postActions = postActions,
|
||||||
searchQuery = viewModel.searchQuery,
|
searchQuery = viewModel.searchQuery,
|
||||||
|
contentPadding = contentPadding,
|
||||||
setSearchQuery = { query -> viewModel.searchQuery = query },
|
setSearchQuery = { query -> viewModel.searchQuery = query },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -65,6 +61,7 @@ fun SearchScreen(
|
||||||
getSeenComments = viewModel::getSeenComments,
|
getSeenComments = viewModel::getSeenComments,
|
||||||
markSeenComments = viewModel::markSeenComments,
|
markSeenComments = viewModel::markSeenComments,
|
||||||
openUserProfile = { navController.navigate(User(it)) },
|
openUserProfile = { navController.navigate(User(it)) },
|
||||||
|
contentPadding = contentPadding,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
composable<User> { backStackEntry ->
|
composable<User> { backStackEntry ->
|
||||||
|
@ -74,6 +71,7 @@ fun SearchScreen(
|
||||||
username = username,
|
username = username,
|
||||||
getProfile = viewModel::getUserProfile,
|
getProfile = viewModel::getUserProfile,
|
||||||
openUserProfile = { navController.navigate(User(it)) },
|
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.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.RowScope
|
import androidx.compose.foundation.layout.RowScope
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.LibraryBooks
|
import androidx.compose.material.icons.automirrored.filled.LibraryBooks
|
||||||
import androidx.compose.material.icons.filled.Bookmarks
|
import androidx.compose.material.icons.filled.Bookmarks
|
||||||
|
@ -55,10 +57,11 @@ fun SettingsScreen(
|
||||||
importPosts: suspend (InputStream) -> Unit,
|
importPosts: suspend (InputStream) -> Unit,
|
||||||
exportPostsAsJson: suspend (OutputStream) -> Unit,
|
exportPostsAsJson: suspend (OutputStream) -> Unit,
|
||||||
exportPostsAsHtml: suspend (OutputStream) -> Unit,
|
exportPostsAsHtml: suspend (OutputStream) -> Unit,
|
||||||
|
contentPadding: PaddingValues,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
Box(modifier = modifier.fillMaxSize()) {
|
Box(modifier = modifier.padding(contentPadding).fillMaxSize()) {
|
||||||
Column {
|
Column {
|
||||||
ListItem(
|
ListItem(
|
||||||
headlineContent = { Text("Data transfer") },
|
headlineContent = { Text("Data transfer") },
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
package dev.msfjarvis.claw.common.comments
|
package dev.msfjarvis.claw.common.comments
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
@ -34,6 +35,7 @@ fun CommentsPage(
|
||||||
htmlConverter: HTMLConverter,
|
htmlConverter: HTMLConverter,
|
||||||
getSeenComments: suspend (String) -> PostComments,
|
getSeenComments: suspend (String) -> PostComments,
|
||||||
markSeenComments: (String, List<Comment>) -> Unit,
|
markSeenComments: (String, List<Comment>) -> Unit,
|
||||||
|
contentPadding: PaddingValues,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
openUserProfile: (String) -> Unit,
|
openUserProfile: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
@ -59,6 +61,7 @@ fun CommentsPage(
|
||||||
commentState = commentState,
|
commentState = commentState,
|
||||||
markSeenComments = markSeenComments,
|
markSeenComments = markSeenComments,
|
||||||
openUserProfile = openUserProfile,
|
openUserProfile = openUserProfile,
|
||||||
|
contentPadding = contentPadding,
|
||||||
modifier = modifier.fillMaxSize(),
|
modifier = modifier.fillMaxSize(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,6 +52,7 @@ internal fun CommentsPageInternal(
|
||||||
commentState: PostComments,
|
commentState: PostComments,
|
||||||
markSeenComments: (String, List<Comment>) -> Unit,
|
markSeenComments: (String, List<Comment>) -> Unit,
|
||||||
openUserProfile: (String) -> Unit,
|
openUserProfile: (String) -> Unit,
|
||||||
|
contentPadding: PaddingValues,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
@ -68,7 +69,7 @@ internal fun CommentsPageInternal(
|
||||||
}
|
}
|
||||||
|
|
||||||
Surface(color = MaterialTheme.colorScheme.surfaceVariant) {
|
Surface(color = MaterialTheme.colorScheme.surfaceVariant) {
|
||||||
LazyColumn(modifier = modifier, contentPadding = PaddingValues(bottom = 24.dp)) {
|
LazyColumn(modifier = modifier, contentPadding = contentPadding) {
|
||||||
item {
|
item {
|
||||||
CommentsHeader(
|
CommentsHeader(
|
||||||
post = details,
|
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.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.requiredSize
|
import androidx.compose.foundation.layout.requiredSize
|
||||||
|
@ -45,6 +46,7 @@ fun UserProfile(
|
||||||
username: String,
|
username: String,
|
||||||
getProfile: suspend (username: String) -> User,
|
getProfile: suspend (username: String) -> User,
|
||||||
openUserProfile: (String) -> Unit,
|
openUserProfile: (String) -> Unit,
|
||||||
|
contentPadding: PaddingValues,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val user by
|
val user by
|
||||||
|
@ -67,7 +69,7 @@ fun UserProfile(
|
||||||
}
|
}
|
||||||
is Error -> {
|
is Error -> {
|
||||||
val error = user as Error
|
val error = user as Error
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
Box(modifier = Modifier.padding(contentPadding).fillMaxSize()) {
|
||||||
NetworkError(
|
NetworkError(
|
||||||
label = error.description,
|
label = error.description,
|
||||||
error = error.error,
|
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 = { module = "com.google.dagger:dagger", version.ref = "dagger" }
|
||||||
dagger-compiler = { module = "com.google.dagger:dagger-compiler", version.ref = "dagger" }
|
dagger-compiler = { module = "com.google.dagger:dagger-compiler", version.ref = "dagger" }
|
||||||
eithernet = "com.slack.eithernet:eithernet:1.9.0"
|
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"
|
javax-inject = "javax.inject:javax.inject:1"
|
||||||
jsoup = "org.jsoup:jsoup:1.18.1"
|
jsoup = "org.jsoup:jsoup:1.18.1"
|
||||||
junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" }
|
junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" }
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue