feat(ui): reintroduce navigation rail to tablet UI

This commit is contained in:
Harsh Shandilya 2024-11-30 13:53:11 +05:30
parent d2202204d2
commit 57bc93d5ea
2 changed files with 147 additions and 46 deletions

View file

@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Fixed
- Tablet users can now navigate between hottest/newest/saved posts again
## [1.52.0] - 2024-11-26 ## [1.52.0] - 2024-11-26
### Added ### Added

View file

@ -9,14 +9,27 @@
package dev.msfjarvis.claw.android.ui.screens package dev.msfjarvis.claw.android.ui.screens
import androidx.activity.compose.BackHandler 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.Box
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
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.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.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.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar 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.ThreePaneScaffoldNavigator
import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment 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.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp 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.navigation.compose.rememberNavController
import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems
import com.deliveryhero.whetstone.compose.injectedViewModel import com.deliveryhero.whetstone.compose.injectedViewModel
import dev.msfjarvis.claw.android.R import dev.msfjarvis.claw.android.R
import dev.msfjarvis.claw.android.ui.PostActions 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.lists.NetworkPosts
import dev.msfjarvis.claw.android.ui.navigation.Comments 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.ui.navigation.User
import dev.msfjarvis.claw.android.viewmodel.ClawViewModel import dev.msfjarvis.claw.android.viewmodel.ClawViewModel
import dev.msfjarvis.claw.common.comments.CommentsPage import dev.msfjarvis.claw.common.comments.CommentsPage
import dev.msfjarvis.claw.common.comments.HTMLConverter import dev.msfjarvis.claw.common.comments.HTMLConverter
import dev.msfjarvis.claw.common.urllauncher.UrlLauncher import dev.msfjarvis.claw.common.urllauncher.UrlLauncher
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentMapOf
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
private fun ThreePaneScaffoldNavigator<*>.isListExpanded() = private fun ThreePaneScaffoldNavigator<*>.isListExpanded() =
@ -68,9 +93,13 @@ fun TabletScreen(
) { ) {
val context = LocalContext.current val context = LocalContext.current
val hottestListState = rememberLazyListState() val hottestListState = rememberLazyListState()
val newestListState = rememberLazyListState()
val savedListState = rememberLazyListState()
val navController = rememberNavController() val navController = rememberNavController()
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
val hottestPosts = viewModel.hottestPosts.collectAsLazyPagingItems() val hottestPosts = viewModel.hottestPosts.collectAsLazyPagingItems()
val newestPosts = viewModel.newestPosts.collectAsLazyPagingItems()
val savedPosts by viewModel.savedPostsByMonth.collectAsStateWithLifecycle(persistentMapOf())
val navigator = rememberListDetailPaneScaffoldNavigator<Comments>() val navigator = rememberListDetailPaneScaffoldNavigator<Comments>()
val backBehavior = val backBehavior =
if (navigator.isListExpanded() && navigator.isDetailExpanded()) { 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)) { BackHandler(navigator.canNavigateBack(backBehavior)) {
coroutineScope.launch { navigator.navigateBack(backBehavior) } coroutineScope.launch { navigator.navigateBack(backBehavior) }
} }
@ -95,58 +150,100 @@ fun TabletScreen(
topBar = { topBar = {
TopAppBar( TopAppBar(
navigationIcon = { navigationIcon = {
Icon( if (navigator.canNavigateBack(backBehavior)) {
painter = painterResource(id = R.drawable.ic_launcher_foreground), IconButton(
contentDescription = "The app icon for Claw", onClick = { coroutineScope.launch { navigator.navigateBack(backBehavior) } }
modifier = Modifier.size(48.dp), ) {
) Icon(
}, imageVector = Icons.AutoMirrored.Filled.ArrowBack,
title = { Text(text = stringResource(R.string.app_name), fontWeight = FontWeight.Bold) }, contentDescription = "Go back to previous screen",
) )
}, }
content = { paddingValues -> } else {
ListDetailPaneScaffold( Icon(
modifier = modifier.padding(paddingValues), painter = painterResource(id = R.drawable.ic_launcher_foreground),
directive = navigator.scaffoldDirective, contentDescription = "The app icon for Claw",
value = navigator.scaffoldValue, modifier = Modifier.size(48.dp),
listPane = {
AnimatedPane {
NetworkPosts(
lazyPagingItems = hottestPosts,
listState = hottestListState,
postActions = postActions,
contentPadding = PaddingValues(),
modifier = Modifier.fillMaxSize(),
) )
} }
}, },
detailPane = { title = {
AnimatedPane { if (!navigator.canNavigateBack(backBehavior)) {
when (val contentKey = navigator.currentDestination?.contentKey) { Text(text = stringResource(R.string.app_name), fontWeight = FontWeight.Bold)
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(),
)
}
}
} }
}, },
) )
}, },
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<Hottest> {
NetworkPosts(
lazyPagingItems = hottestPosts,
listState = hottestListState,
postActions = postActions,
contentPadding = PaddingValues(),
)
}
composable<Newest> {
NetworkPosts(
lazyPagingItems = newestPosts,
listState = newestListState,
postActions = postActions,
contentPadding = PaddingValues(),
)
}
composable<Saved> {
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(),
)
}
}
}
},
)
}
},
) )
} }