diff --git a/CHANGELOG.md b/CHANGELOG.md index bce5ced8..88351ef9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Tablet users can now navigate between hottest/newest/saved posts again + ## [1.52.0] - 2024-11-26 ### Added 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 index 05dbf639..1b2e6bbf 100644 --- 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 @@ -9,14 +9,27 @@ 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.material.icons.filled.Favorite +import androidx.compose.material.icons.filled.NewReleases +import androidx.compose.material.icons.filled.Whatshot +import androidx.compose.material.icons.outlined.FavoriteBorder +import androidx.compose.material.icons.outlined.NewReleases +import androidx.compose.material.icons.outlined.Whatshot import androidx.compose.material3.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 @@ -29,6 +42,7 @@ 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 @@ -38,18 +52,29 @@ 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.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.comments.HTMLConverter 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() = @@ -68,9 +93,13 @@ fun TabletScreen( ) { 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()) { @@ -87,6 +116,32 @@ fun TabletScreen( } } + val navItems = + persistentListOf( + NavigationItem( + label = "Hottest", + destination = Hottest, + icon = Icons.Outlined.Whatshot, + selectedIcon = Icons.Filled.Whatshot, + ) { + coroutineScope.launch { + if (hottestPosts.itemCount > 0) hottestListState.animateScrollToItem(index = 0) + } + }, + NavigationItem( + label = "Newest", + destination = Newest, + icon = Icons.Outlined.NewReleases, + selectedIcon = Icons.Filled.NewReleases, + ) {}, + NavigationItem( + label = "Saved", + destination = Saved, + icon = Icons.Outlined.FavoriteBorder, + selectedIcon = Icons.Filled.Favorite, + ) {}, + ) + BackHandler(navigator.canNavigateBack(backBehavior)) { coroutineScope.launch { navigator.navigateBack(backBehavior) } } @@ -95,58 +150,100 @@ fun TabletScreen( topBar = { TopAppBar( navigationIcon = { - Icon( - painter = painterResource(id = R.drawable.ic_launcher_foreground), - contentDescription = "The app icon for Claw", - modifier = Modifier.size(48.dp), - ) - }, - title = { Text(text = stringResource(R.string.app_name), fontWeight = FontWeight.Bold) }, - ) - }, - content = { paddingValues -> - ListDetailPaneScaffold( - modifier = modifier.padding(paddingValues), - directive = navigator.scaffoldDirective, - value = navigator.scaffoldValue, - listPane = { - AnimatedPane { - NetworkPosts( - lazyPagingItems = hottestPosts, - listState = hottestListState, - postActions = postActions, - contentPadding = PaddingValues(), - modifier = Modifier.fillMaxSize(), + 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), ) } }, - 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, - htmlConverter = htmlConverter, - getSeenComments = viewModel::getSeenComments, - markSeenComments = viewModel::markSeenComments, - openUserProfile = { navController.navigate(User(it)) }, - contentPadding = PaddingValues(), - modifier = Modifier.fillMaxSize(), - ) - } - } + 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, + htmlConverter = htmlConverter, + getSeenComments = viewModel::getSeenComments, + markSeenComments = viewModel::markSeenComments, + openUserProfile = { navController.navigate(User(it)) }, + contentPadding = PaddingValues(), + modifier = Modifier.fillMaxSize(), + ) + } + } + } + }, + ) + } + }, ) }