diff --git a/android/build.gradle.kts b/android/build.gradle.kts
index 414e13fa..3634798c 100644
--- a/android/build.gradle.kts
+++ b/android/build.gradle.kts
@@ -107,6 +107,8 @@ dependencies {
implementation(libs.androidx.core.splashscreen)
implementation(libs.androidx.lifecycle.compose)
implementation(libs.androidx.navigation.compose)
+ implementation(libs.androidx.navigation3.runtime)
+ implementation(libs.androidx.navigation3.ui)
implementation(libs.androidx.paging.compose)
implementation(libs.androidx.profileinstaller)
implementation(libs.androidx.work.runtime)
diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml
index c71c7019..dd3c0dff 100644
--- a/android/src/main/AndroidManifest.xml
+++ b/android/src/main/AndroidManifest.xml
@@ -33,19 +33,6 @@
-
-
-
-
-
-
{
- LobstersPostsScreen(
- urlLauncher = urlLauncher,
- windowSizeClass = windowSizeClass,
- setWebUri = { url -> webUri = url },
- )
- }
-
- else -> {
- TabletScreen(urlLauncher = urlLauncher, modifier = Modifier.fillMaxSize())
- }
- }
+ Nav3Screen(
+ urlLauncher = urlLauncher,
+ windowSizeClass = windowSizeClass,
+ setWebUri = { url -> webUri = url },
+ )
}
override fun preLaunch() {
super.preLaunch()
+ enableEdgeToEdge()
installSplashScreen()
}
diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/SearchActivity.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/SearchActivity.kt
deleted file mode 100644
index bd9ad7d7..00000000
--- a/android/src/main/kotlin/dev/msfjarvis/claw/android/SearchActivity.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright © 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.android
-
-import androidx.compose.runtime.Composable
-import com.deliveryhero.whetstone.activity.ContributesActivityInjector
-import dev.msfjarvis.claw.android.ui.screens.SearchScreen
-
-@ContributesActivityInjector
-class SearchActivity : BaseActivity() {
- @Composable
- override fun Content() {
- SearchScreen(urlLauncher = urlLauncher, setWebUri = { webUri = it }, viewModel = viewModel)
- }
-}
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 b3ef444c..f869f073 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
@@ -20,21 +20,19 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.snapshots.SnapshotStateList
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.HazeDefaults
import dev.chrisbanes.haze.HazeState
import dev.chrisbanes.haze.HazeStyle
import dev.chrisbanes.haze.hazeEffect
import dev.msfjarvis.claw.android.ui.navigation.AppDestinations
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
@@ -42,7 +40,7 @@ const val AnimationDuration = 100
@Composable
fun ClawNavigationBar(
- navController: NavController,
+ backStack: SnapshotStateList,
items: ImmutableList,
isVisible: Boolean,
hazeState: HazeState,
@@ -85,10 +83,9 @@ fun ClawNavigationBar(
containerColor =
if (HazeDefaults.blurEnabled()) Color.Transparent else MaterialTheme.colorScheme.surface,
) {
- val navBackStackEntry = navController.currentBackStackEntryAsState().value
- val currentDestination = navBackStackEntry?.destination
+ val currentDestination = backStack.firstOrNull()
items.forEach { navItem ->
- val isSelected = currentDestination.matches(navItem.destination)
+ val isSelected = currentDestination == navItem.destination
NavigationBarItem(
icon = {
Crossfade(isSelected, label = "nav-label") {
@@ -104,11 +101,7 @@ fun ClawNavigationBar(
if (isSelected) {
navItem.listStateResetCallback()
} else {
- navController.navigate(navItem.destination) {
- popUpTo(navController.graph.startDestinationId) { saveState = true }
- launchSingleTop = true
- restoreState = true
- }
+ backStack.add(navItem.destination)
}
},
modifier = Modifier.testTag(navItem.label.uppercase()),
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/Nav3Screen.kt
similarity index 57%
rename from android/src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/LobstersPostsScreen.kt
rename to android/src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/Nav3Screen.kt
index 11c80178..34a46c2b 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/Nav3Screen.kt
@@ -6,13 +6,8 @@
*/
package dev.msfjarvis.claw.android.ui.screens
-import android.content.Intent
import androidx.activity.compose.LocalActivity
import androidx.compose.animation.AnimatedVisibility
-import androidx.compose.animation.core.tween
-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.size
import androidx.compose.foundation.lazy.rememberLazyListState
@@ -32,6 +27,7 @@ import androidx.compose.material3.windowsizeclass.WindowSizeClass
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
@@ -44,22 +40,17 @@ import androidx.compose.ui.semantics.testTagsAsResourceId
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import androidx.navigation.compose.NavHost
-import androidx.navigation.compose.composable
-import androidx.navigation.compose.currentBackStackEntryAsState
-import androidx.navigation.compose.rememberNavController
-import androidx.navigation.toRoute
+import androidx.navigation3.runtime.entry
+import androidx.navigation3.runtime.entryProvider
+import androidx.navigation3.ui.NavDisplay
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.hazeSource
import dev.msfjarvis.claw.android.MainActivity
import dev.msfjarvis.claw.android.R
-import dev.msfjarvis.claw.android.SearchActivity
import dev.msfjarvis.claw.android.ui.PostActions
import dev.msfjarvis.claw.android.ui.decorations.ClawNavigationBar
-import dev.msfjarvis.claw.android.ui.decorations.ClawNavigationRail
import dev.msfjarvis.claw.android.ui.decorations.NavigationItem
import dev.msfjarvis.claw.android.ui.lists.DatabasePosts
import dev.msfjarvis.claw.android.ui.lists.NetworkPosts
@@ -67,9 +58,11 @@ import dev.msfjarvis.claw.android.ui.navigation.AboutLibraries
import dev.msfjarvis.claw.android.ui.navigation.AppDestinations
import dev.msfjarvis.claw.android.ui.navigation.ClawNavigationType
import dev.msfjarvis.claw.android.ui.navigation.Comments
+import dev.msfjarvis.claw.android.ui.navigation.Destination
import dev.msfjarvis.claw.android.ui.navigation.Hottest
import dev.msfjarvis.claw.android.ui.navigation.Newest
import dev.msfjarvis.claw.android.ui.navigation.Saved
+import dev.msfjarvis.claw.android.ui.navigation.Search
import dev.msfjarvis.claw.android.ui.navigation.Settings
import dev.msfjarvis.claw.android.ui.navigation.User
import dev.msfjarvis.claw.android.ui.navigation.any
@@ -85,26 +78,23 @@ import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)
@Composable
-fun LobstersPostsScreen(
+fun Nav3Screen(
urlLauncher: UrlLauncher,
windowSizeClass: WindowSizeClass,
setWebUri: (String?) -> Unit,
modifier: Modifier = Modifier,
viewModel: ClawViewModel = injectedViewModel(),
) {
+ val backStack = remember { mutableStateListOf(Hottest) }
+
+ // region Pain
val context = LocalContext.current
val activity = LocalActivity.current
val hottestListState = rememberLazyListState()
val newestListState = rememberLazyListState()
val savedListState = rememberLazyListState()
- val navController = rememberNavController()
- val navBackStackEntry = navController.currentBackStackEntryAsState().value
- val currentDestination = navBackStackEntry?.destination
val coroutineScope = rememberCoroutineScope()
val snackbarHostState = remember { SnackbarHostState() }
- val postActions = remember {
- PostActions(context, urlLauncher, viewModel) { navController.navigate(Comments(it)) }
- }
val hazeState = remember { HazeState() }
val hottestPosts = viewModel.hottestPosts.collectAsLazyPagingItems()
@@ -116,7 +106,7 @@ fun LobstersPostsScreen(
val postIdOverride = activity?.intent?.extras?.getString(MainActivity.NAVIGATION_KEY)
LaunchedEffect(Unit) {
if (postIdOverride != null) {
- navController.navigate(Comments(postIdOverride))
+ backStack.add(Comments(postIdOverride))
}
}
@@ -137,16 +127,19 @@ fun LobstersPostsScreen(
},
)
val navDestinations = navItems.map(NavigationItem::destination).toPersistentList()
+ // endregion
+
+ val postActions = remember {
+ PostActions(context, urlLauncher, viewModel) { backStack.add(Comments(it)) }
+ }
Scaffold(
topBar = {
TopAppBar(
modifier = Modifier.shadow(8.dp),
navigationIcon = {
- if (
- navController.previousBackStackEntry != null && currentDestination.none(navDestinations)
- ) {
- IconButton(onClick = { if (!navController.popBackStack()) activity?.finish() }) {
+ if (backStack.none { it in navDestinations }) {
+ IconButton(onClick = { if (backStack.removeLastOrNull() == null) activity?.finish() }) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "Go back to previous screen",
@@ -161,18 +154,16 @@ fun LobstersPostsScreen(
}
},
title = {
- if (currentDestination.any(navDestinations)) {
+ if (backStack.any { it in navDestinations }) {
Text(text = stringResource(R.string.app_name), fontWeight = FontWeight.Bold)
}
},
actions = {
- if (currentDestination.any(navDestinations)) {
- IconButton(
- onClick = { context.startActivity(Intent(context, SearchActivity::class.java)) }
- ) {
+ if (backStack.any { it in navDestinations }) {
+ IconButton(onClick = { backStack.add(Search) }) {
Icon(imageVector = Icons.Filled.Search, contentDescription = "Search posts")
}
- IconButton(onClick = { navController.navigate(Settings) }) {
+ IconButton(onClick = { backStack.add(Settings) }) {
Icon(imageVector = Icons.Filled.Tune, contentDescription = "Settings")
}
}
@@ -182,99 +173,87 @@ fun LobstersPostsScreen(
bottomBar = {
AnimatedVisibility(visible = navigationType == ClawNavigationType.BOTTOM_NAVIGATION) {
ClawNavigationBar(
- navController = navController,
+ backStack,
items = navItems,
- isVisible = currentDestination.any(navDestinations),
+ isVisible = backStack.any { it in navDestinations },
hazeState = hazeState,
)
}
},
snackbarHost = { SnackbarHost(snackbarHostState) },
- modifier = modifier.semantics { testTagsAsResourceId = true },
+ modifier = Modifier.semantics { testTagsAsResourceId = true },
) { contentPadding ->
- Row {
- AnimatedVisibility(visible = navigationType == ClawNavigationType.NAVIGATION_RAIL) {
- ClawNavigationRail(
- navController = navController,
- items = navItems,
- isVisible = currentDestination.any(navDestinations),
- )
- }
-
- NavHost(
- navController = navController,
- startDestination = Hottest,
- // Make animations 2x faster than default specs
- enterTransition = { fadeIn(animationSpec = tween(350)) },
- exitTransition = { fadeOut(animationSpec = tween(350)) },
- modifier = Modifier.hazeSource(hazeState),
- ) {
- composable {
- setWebUri("https://lobste.rs/")
- NetworkPosts(
- lazyPagingItems = hottestPosts,
- listState = hottestListState,
- postActions = postActions,
- contentPadding = contentPadding,
- )
- }
- composable {
- setWebUri("https://lobste.rs/")
- NetworkPosts(
- lazyPagingItems = newestPosts,
- listState = newestListState,
- postActions = postActions,
- contentPadding = contentPadding,
- )
- }
- composable {
- setWebUri(null)
- DatabasePosts(
- items = savedPosts,
- listState = savedListState,
- postActions = postActions,
- contentPadding = contentPadding,
- )
- }
- composable { backStackEntry ->
- val postId = backStackEntry.toRoute().postId
- setWebUri("https://lobste.rs/s/$postId")
- CommentsPage(
- postId = postId,
- postActions = postActions,
- getSeenComments = viewModel::getSeenComments,
- markSeenComments = viewModel::markSeenComments,
- contentPadding = contentPadding,
- openUserProfile = { navController.navigate(User(it)) },
- )
- }
- composable { backStackEntry ->
- val username = backStackEntry.toRoute().username
- setWebUri("https://lobste.rs/u/$username")
- UserProfile(
- username = username,
- getProfile = viewModel::getUserProfile,
- contentPadding = contentPadding,
- openUserProfile = { navController.navigate(User(it)) },
- )
- }
- composable {
- SettingsScreen(
- openInputStream = context.contentResolver::openInputStream,
- openOutputStream = context.contentResolver::openOutputStream,
- openLibrariesScreen = { navController.navigate(AboutLibraries) },
- importPosts = viewModel::importPosts,
- exportPostsAsJson = viewModel::exportPostsAsJson,
- exportPostsAsHtml = viewModel::exportPostsAsHtml,
- snackbarHostState = snackbarHostState,
- contentPadding = contentPadding,
- modifier = Modifier.fillMaxSize(),
- )
- }
- composable {
- LibrariesContainer(contentPadding = contentPadding, modifier = Modifier.fillMaxSize())
- }
- }
- }
+ NavDisplay(
+ backStack = backStack,
+ modifier = modifier,
+ onBack = { backStack.removeLastOrNull() },
+ entryProvider =
+ entryProvider {
+ entry {
+ setWebUri("https://lobste.rs/")
+ NetworkPosts(
+ lazyPagingItems = hottestPosts,
+ listState = hottestListState,
+ postActions = postActions,
+ contentPadding = contentPadding,
+ )
+ }
+ entry {
+ setWebUri("https://lobste.rs/")
+ NetworkPosts(
+ lazyPagingItems = newestPosts,
+ listState = newestListState,
+ postActions = postActions,
+ contentPadding = contentPadding,
+ )
+ }
+ entry {
+ setWebUri(null)
+ DatabasePosts(
+ items = savedPosts,
+ listState = savedListState,
+ postActions = postActions,
+ contentPadding = contentPadding,
+ )
+ }
+ entry {
+ SettingsScreen(
+ openInputStream = context.contentResolver::openInputStream,
+ openOutputStream = context.contentResolver::openOutputStream,
+ openLibrariesScreen = { backStack.add(AboutLibraries) },
+ importPosts = viewModel::importPosts,
+ exportPostsAsJson = viewModel::exportPostsAsJson,
+ exportPostsAsHtml = viewModel::exportPostsAsHtml,
+ snackbarHostState = snackbarHostState,
+ contentPadding = contentPadding,
+ modifier = Modifier.fillMaxSize(),
+ )
+ }
+ entry { dest ->
+ CommentsPage(
+ postId = dest.postId,
+ postActions = postActions,
+ getSeenComments = viewModel::getSeenComments,
+ markSeenComments = viewModel::markSeenComments,
+ contentPadding = contentPadding,
+ openUserProfile = { backStack.add(User(it)) },
+ )
+ }
+ entry { dest ->
+ UserProfile(
+ username = dest.username,
+ getProfile = viewModel::getUserProfile,
+ contentPadding = contentPadding,
+ openUserProfile = { backStack.add(User(it)) },
+ )
+ }
+ entry {
+ SearchScreen(urlLauncher = urlLauncher, setWebUri = setWebUri, viewModel = viewModel)
+ }
+ entry {
+ LibrariesContainer(contentPadding = contentPadding, modifier = Modifier.fillMaxSize())
+ }
+ },
+ )
}
}
diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/TabletScreen.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/TabletScreen.kt
deleted file mode 100644
index 9714f37a..00000000
--- a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/TabletScreen.kt
+++ /dev/null
@@ -1,234 +0,0 @@
-/*
- * Copyright © 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.
- */
-@file:OptIn(ExperimentalMaterial3AdaptiveApi::class)
-
-package dev.msfjarvis.claw.android.ui.screens
-
-import androidx.activity.compose.BackHandler
-import androidx.compose.animation.core.tween
-import androidx.compose.animation.fadeIn
-import androidx.compose.animation.fadeOut
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.PaddingValues
-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
-import androidx.compose.material.icons.automirrored.filled.ArrowBack
-import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButton
-import androidx.compose.material3.Scaffold
-import androidx.compose.material3.Text
-import androidx.compose.material3.TopAppBar
-import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
-import androidx.compose.material3.adaptive.layout.AnimatedPane
-import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold
-import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole
-import androidx.compose.material3.adaptive.layout.PaneAdaptedValue
-import androidx.compose.material3.adaptive.navigation.BackNavigationBehavior
-import androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator
-import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.unit.dp
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import androidx.navigation.compose.NavHost
-import androidx.navigation.compose.composable
-import androidx.navigation.compose.rememberNavController
-import androidx.paging.compose.collectAsLazyPagingItems
-import com.deliveryhero.whetstone.compose.injectedViewModel
-import dev.msfjarvis.claw.android.R
-import dev.msfjarvis.claw.android.ui.PostActions
-import dev.msfjarvis.claw.android.ui.decorations.ClawNavigationRail
-import dev.msfjarvis.claw.android.ui.decorations.NavigationItem
-import dev.msfjarvis.claw.android.ui.lists.DatabasePosts
-import dev.msfjarvis.claw.android.ui.lists.NetworkPosts
-import dev.msfjarvis.claw.android.ui.navigation.AppDestinations
-import dev.msfjarvis.claw.android.ui.navigation.Comments
-import dev.msfjarvis.claw.android.ui.navigation.Hottest
-import dev.msfjarvis.claw.android.ui.navigation.Newest
-import dev.msfjarvis.claw.android.ui.navigation.Saved
-import dev.msfjarvis.claw.android.ui.navigation.User
-import dev.msfjarvis.claw.android.viewmodel.ClawViewModel
-import dev.msfjarvis.claw.common.comments.CommentsPage
-import dev.msfjarvis.claw.common.urllauncher.UrlLauncher
-import kotlinx.collections.immutable.persistentListOf
-import kotlinx.collections.immutable.persistentMapOf
-import kotlinx.coroutines.launch
-
-private fun ThreePaneScaffoldNavigator<*>.isListExpanded() =
- scaffoldValue[ListDetailPaneScaffoldRole.List] == PaneAdaptedValue.Expanded
-
-private fun ThreePaneScaffoldNavigator<*>.isDetailExpanded() =
- scaffoldValue[ListDetailPaneScaffoldRole.Detail] == PaneAdaptedValue.Expanded
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-fun TabletScreen(
- urlLauncher: UrlLauncher,
- modifier: Modifier = Modifier,
- viewModel: ClawViewModel = injectedViewModel(),
-) {
- val context = LocalContext.current
- val hottestListState = rememberLazyListState()
- val newestListState = rememberLazyListState()
- val savedListState = rememberLazyListState()
- val navController = rememberNavController()
- val coroutineScope = rememberCoroutineScope()
- val hottestPosts = viewModel.hottestPosts.collectAsLazyPagingItems()
- val newestPosts = viewModel.newestPosts.collectAsLazyPagingItems()
- val savedPosts by viewModel.savedPostsByMonth.collectAsStateWithLifecycle(persistentMapOf())
- val navigator = rememberListDetailPaneScaffoldNavigator()
- val backBehavior =
- if (navigator.isListExpanded() && navigator.isDetailExpanded()) {
- BackNavigationBehavior.PopUntilContentChange
- } else {
- BackNavigationBehavior.PopUntilScaffoldValueChange
- }
-
- val postActions = remember {
- PostActions(context, urlLauncher, viewModel) {
- coroutineScope.launch {
- navigator.navigateTo(pane = ListDetailPaneScaffoldRole.Detail, contentKey = Comments(it))
- }
- }
- }
-
- val navItems =
- persistentListOf(
- NavigationItem(AppDestinations.HOTTEST) {
- coroutineScope.launch {
- if (hottestPosts.itemCount > 0) hottestListState.animateScrollToItem(index = 0)
- }
- },
- NavigationItem(AppDestinations.NEWEST) {
- coroutineScope.launch {
- if (newestPosts.itemCount > 0) newestListState.animateScrollToItem(index = 0)
- }
- },
- NavigationItem(AppDestinations.SAVED) {
- coroutineScope.launch {
- if (savedPosts.isNotEmpty()) savedListState.animateScrollToItem(index = 0)
- }
- },
- )
-
- BackHandler(navigator.canNavigateBack(backBehavior)) {
- coroutineScope.launch { navigator.navigateBack(backBehavior) }
- }
-
- Scaffold(
- topBar = {
- TopAppBar(
- navigationIcon = {
- if (navigator.canNavigateBack(backBehavior)) {
- IconButton(
- onClick = { coroutineScope.launch { navigator.navigateBack(backBehavior) } }
- ) {
- Icon(
- imageVector = Icons.AutoMirrored.Filled.ArrowBack,
- contentDescription = "Go back to previous screen",
- )
- }
- } else {
- Icon(
- painter = painterResource(id = R.drawable.ic_launcher_foreground),
- contentDescription = "The app icon for Claw",
- modifier = Modifier.size(48.dp),
- )
- }
- },
- title = {
- if (!navigator.canNavigateBack(backBehavior)) {
- Text(text = stringResource(R.string.app_name), fontWeight = FontWeight.Bold)
- }
- },
- )
- },
- content = { paddingValues ->
- Row {
- ClawNavigationRail(navController = navController, items = navItems, isVisible = true)
- ListDetailPaneScaffold(
- modifier = modifier.padding(paddingValues),
- directive = navigator.scaffoldDirective,
- value = navigator.scaffoldValue,
- listPane = {
- AnimatedPane {
- NavHost(
- navController = navController,
- startDestination = Hottest,
- enterTransition = { fadeIn(tween(350)) },
- exitTransition = { fadeOut(tween(350)) },
- ) {
- composable {
- NetworkPosts(
- lazyPagingItems = hottestPosts,
- listState = hottestListState,
- postActions = postActions,
- contentPadding = PaddingValues(),
- )
- }
- composable {
- NetworkPosts(
- lazyPagingItems = newestPosts,
- listState = newestListState,
- postActions = postActions,
- contentPadding = PaddingValues(),
- )
- }
- composable {
- DatabasePosts(
- items = savedPosts,
- listState = savedListState,
- postActions = postActions,
- contentPadding = PaddingValues(),
- )
- }
- }
- }
- },
- detailPane = {
- AnimatedPane {
- when (val contentKey = navigator.currentDestination?.contentKey) {
- null -> {
- Box(Modifier.fillMaxSize()) {
- Text(
- text = "Select a post to view comments",
- modifier = Modifier.align(Alignment.Center),
- )
- }
- }
- else -> {
- CommentsPage(
- postId = contentKey.postId,
- postActions = postActions,
- getSeenComments = viewModel::getSeenComments,
- markSeenComments = viewModel::markSeenComments,
- contentPadding = PaddingValues(),
- modifier = Modifier.fillMaxSize(),
- openUserProfile = { navController.navigate(User(it)) },
- )
- }
- }
- }
- },
- )
- }
- },
- )
-}
diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/TabletScreen2.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/TabletScreen2.kt
deleted file mode 100644
index 90c9e90f..00000000
--- a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/TabletScreen2.kt
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Copyright © 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.android.ui.screens
-
-import androidx.activity.compose.BackHandler
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.lazy.rememberLazyListState
-import androidx.compose.material3.Icon
-import androidx.compose.material3.SnackbarHostState
-import androidx.compose.material3.Text
-import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffold
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateListOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.saveable.rememberSaveable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalContext
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import androidx.paging.compose.collectAsLazyPagingItems
-import com.deliveryhero.whetstone.compose.injectedViewModel
-import com.mikepenz.aboutlibraries.ui.compose.m3.LibrariesContainer
-import dev.msfjarvis.claw.android.ui.PostActions
-import dev.msfjarvis.claw.android.ui.lists.DatabasePosts
-import dev.msfjarvis.claw.android.ui.lists.NetworkPosts
-import dev.msfjarvis.claw.android.ui.navigation.AboutLibraries
-import dev.msfjarvis.claw.android.ui.navigation.AppDestinations
-import dev.msfjarvis.claw.android.ui.navigation.Comments
-import dev.msfjarvis.claw.android.ui.navigation.Hottest
-import dev.msfjarvis.claw.android.ui.navigation.Newest
-import dev.msfjarvis.claw.android.ui.navigation.Saved
-import dev.msfjarvis.claw.android.ui.navigation.Settings
-import dev.msfjarvis.claw.android.ui.navigation.User
-import dev.msfjarvis.claw.android.viewmodel.ClawViewModel
-import dev.msfjarvis.claw.common.comments.CommentsPage
-import dev.msfjarvis.claw.common.urllauncher.UrlLauncher
-import dev.msfjarvis.claw.common.user.UserProfile
-import kotlinx.collections.immutable.persistentMapOf
-
-@Composable
-fun TabletScreen2(
- urlLauncher: UrlLauncher,
- setWebUri: (String?) -> Unit,
- modifier: Modifier = Modifier,
- viewModel: ClawViewModel = injectedViewModel(),
-) {
- // TODO: Needs a custom Saver implementation, should probably be an ArrayDeque
- val navigationBackStack = rememberSaveable {
- mutableStateListOf(AppDestinations.HOTTEST.destination)
- }
-
- val context = LocalContext.current
- val hottestListState = rememberLazyListState()
- val newestListState = rememberLazyListState()
- val savedListState = rememberLazyListState()
- val snackbarHostState = remember { SnackbarHostState() }
- val postActions = remember {
- PostActions(context, urlLauncher, viewModel) { navigationBackStack.add(Comments(it)) }
- }
-
- val hottestPosts = viewModel.hottestPosts.collectAsLazyPagingItems()
- val newestPosts = viewModel.newestPosts.collectAsLazyPagingItems()
- val savedPosts by viewModel.savedPostsByMonth.collectAsStateWithLifecycle(persistentMapOf())
-
- BackHandler(navigationBackStack.size > 1) {
- navigationBackStack.removeAt(navigationBackStack.size - 1)
- }
-
- val contentPadding = PaddingValues()
-
- NavigationSuiteScaffold(
- navigationSuiteItems = {
- AppDestinations.entries.forEach {
- item(
- icon = { Icon(imageVector = it.icon, contentDescription = it.label) },
- selected = it.destination == navigationBackStack.first(),
- onClick = { navigationBackStack.add(it.destination) },
- )
- }
- },
- modifier = modifier,
- ) {
- when (navigationBackStack.first()) {
- AboutLibraries -> {
- LibrariesContainer(contentPadding = contentPadding, modifier = Modifier.fillMaxSize())
- }
- Hottest -> {
- setWebUri("https://lobste.rs/")
- NetworkPosts(
- lazyPagingItems = hottestPosts,
- listState = hottestListState,
- postActions = postActions,
- contentPadding = contentPadding,
- )
- }
- Newest -> {
- setWebUri("https://lobste.rs/")
- NetworkPosts(
- lazyPagingItems = newestPosts,
- listState = newestListState,
- postActions = postActions,
- contentPadding = contentPadding,
- )
- }
- Saved -> {
- setWebUri(null)
- DatabasePosts(
- items = savedPosts,
- listState = savedListState,
- postActions = postActions,
- contentPadding = contentPadding,
- )
- }
- Settings -> {
- setWebUri(null)
- SettingsScreen(
- openInputStream = context.contentResolver::openInputStream,
- openOutputStream = context.contentResolver::openOutputStream,
- openLibrariesScreen = { navigationBackStack.add(AboutLibraries) },
- importPosts = viewModel::importPosts,
- exportPostsAsJson = viewModel::exportPostsAsJson,
- exportPostsAsHtml = viewModel::exportPostsAsHtml,
- snackbarHostState = snackbarHostState,
- contentPadding = contentPadding,
- modifier = Modifier.fillMaxSize(),
- )
- }
- is Comments -> {
- val postId = (navigationBackStack.first() as Comments).postId
- setWebUri("https://lobste.rs/s/$postId")
- CommentsPage(
- postId = postId,
- postActions = postActions,
- getSeenComments = viewModel::getSeenComments,
- markSeenComments = viewModel::markSeenComments,
- contentPadding = contentPadding,
- openUserProfile = { navigationBackStack.add(User(it)) },
- )
- }
- is User -> {
- val username = (navigationBackStack.first() as User).username
- setWebUri("https://lobste.rs/u/$username")
- UserProfile(
- username = username,
- getProfile = viewModel::getUserProfile,
- contentPadding = contentPadding,
- openUserProfile = { navigationBackStack.add(User(it)) },
- )
- }
- else -> {
- Box(Modifier.fillMaxSize()) {
- Text(
- text = "Unexpected destination: ${navigationBackStack.first()}, please report this bug",
- modifier = Modifier.align(Alignment.Center),
- )
- }
- }
- }
- }
-}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 30b9d151..f62f94d3 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -17,6 +17,7 @@ kotlinResult = "2.0.1"
leakcanary = "3.0-alpha-8"
lifecycle = "2.9.0"
navigation = "2.9.0"
+navigation3 = "1.0.0-alpha02"
retrofit = "3.0.0"
richtext = "1.0.0-alpha02"
sentry-sdk = "8.12.0"
@@ -55,6 +56,8 @@ androidx-core-splashscreen = "androidx.core:core-splashscreen:1.2.0-beta02"
androidx-lifecycle-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycle" }
androidx-lint-gradle = "androidx.lint:lint-gradle:1.0.0-alpha05"
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigation" }
+androidx-navigation3-runtime = { module = "androidx.navigation3:navigation3-runtime", version.ref = "navigation3" }
+androidx-navigation3-ui = { module = "androidx.navigation3:navigation3-ui", version.ref = "navigation3" }
androidx-paging-compose = "androidx.paging:paging-compose:3.3.6"
androidx-profileinstaller = "androidx.profileinstaller:profileinstaller:1.4.1"
androidx-test-core = { module = "androidx.test:core", version.ref = "androidx-test" }