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" }