Allow tapping nav icon to refresh & scroll to top

This commit is contained in:
maxmoney21m 2023-03-11 00:59:18 +08:00
parent 4fa584ca1a
commit 90147ce557
7 changed files with 56 additions and 23 deletions

View File

@ -83,7 +83,7 @@ fun keyboardAsState(): State<Keyboard> {
@Composable
fun AppBottomBar(navController: NavHostController, accountViewModel: AccountViewModel) {
val currentRoute = currentRoute(navController)
val currentRoute = currentRoute(navController)?.substringBefore("?")
val coroutineScope = rememberCoroutineScope()
val isKeyboardOpen by keyboardAsState()
@ -101,10 +101,10 @@ fun AppBottomBar(navController: NavHostController, accountViewModel: AccountView
bottomNavigationItems.forEach { item ->
BottomNavigationItem(
icon = { NotifiableIcon(item, currentRoute, accountViewModel) },
selected = currentRoute == item.route,
selected = currentRoute == item.base,
onClick = {
coroutineScope.launch {
if (currentRoute != item.route) {
if (currentRoute != item.base) {
navController.navigate(item.route) {
navController.graph.startDestinationRoute?.let { start ->
popUpTo(start)
@ -114,8 +114,7 @@ fun AppBottomBar(navController: NavHostController, accountViewModel: AccountView
restoreState = true
}
} else {
// TODO: Make it scrool to the top
navController.navigate(item.route) {
navController.navigate("${item.base}?forceRefresh=${true}") {
navController.graph.startDestinationRoute?.let { start ->
popUpTo(start) { inclusive = item.route == Route.Home.route }
restoreState = true
@ -136,12 +135,12 @@ fun AppBottomBar(navController: NavHostController, accountViewModel: AccountView
@Composable
private fun NotifiableIcon(item: Route, currentRoute: String?, accountViewModel: AccountViewModel) {
Box(Modifier.size(if ("Home" == item.route) 25.dp else 23.dp)) {
Box(Modifier.size(if ("Home" == item.base) 25.dp else 23.dp)) {
Icon(
painter = painterResource(id = item.icon),
null,
modifier = Modifier.size(if ("Home" == item.route) 24.dp else 20.dp),
tint = if (currentRoute == item.route) MaterialTheme.colors.primary else Color.Unspecified
modifier = Modifier.size(if ("Home" == item.base) 24.dp else 20.dp),
tint = if (currentRoute == item.base) MaterialTheme.colors.primary else Color.Unspecified
)
val accountState by accountViewModel.accountLiveData.observeAsState()

View File

@ -35,17 +35,32 @@ sealed class Route(
val arguments: List<NamedNavArgument> = emptyList(),
val buildScreen: (AccountViewModel, AccountStateViewModel, NavController) -> @Composable (NavBackStackEntry) -> Unit
) {
val base: String
get() = route.substringBefore("?")
object Home : Route(
"Home",
"Home?forceRefresh={forceRefresh}",
R.drawable.ic_home,
arguments = listOf(navArgument("forceRefresh") { type = NavType.BoolType; defaultValue = false }),
hasNewItems = { acc, cache, ctx -> homeHasNewItems(acc, cache, ctx) },
buildScreen = { acc, accSt, nav -> { _ -> HomeScreen(acc, nav) } }
buildScreen = { acc, accSt, nav ->
{ backStackEntry ->
HomeScreen(acc, nav, backStackEntry.arguments?.getBoolean("forceRefresh", false))
}
}
)
object Search : Route(
"Search",
"Search?forceRefresh={forceRefresh}",
R.drawable.ic_globe,
buildScreen = { acc, accSt, nav -> { _ -> SearchScreen(acc, nav) } }
arguments = listOf(navArgument("forceRefresh") { type = NavType.BoolType; defaultValue = false }),
buildScreen = { acc, accSt, nav ->
{ backStackEntry ->
SearchScreen(acc, nav, backStackEntry.arguments?.getBoolean("forceRefresh", false))
}
}
)
object Notification : Route(
"Notification",
R.drawable.ic_notifications,

View File

@ -19,6 +19,7 @@ import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@ -40,11 +41,12 @@ fun FeedView(
accountViewModel: AccountViewModel,
navController: NavController,
routeForLastRead: String?,
scrollStateKey: String? = null
scrollStateKey: String? = null,
forceRefresh: Boolean? = false
) {
val feedState by viewModel.feedContent.collectAsState()
var refreshing by remember { mutableStateOf(false) }
var refreshing by remember { mutableStateOf(forceRefresh!!) }
val refresh = { refreshing = true; viewModel.refresh(); refreshing = false }
val pullRefreshState = rememberPullRefreshState(refreshing, onRefresh = refresh)
@ -74,7 +76,8 @@ fun FeedView(
routeForLastRead,
accountViewModel,
navController,
scrollStateKey
scrollStateKey,
forceRefresh!!
)
}
@ -95,7 +98,8 @@ private fun FeedLoaded(
routeForLastRead: String?,
accountViewModel: AccountViewModel,
navController: NavController,
scrollStateKey: String?
scrollStateKey: String?,
forceRefresh: Boolean = false
) {
val listState = if (scrollStateKey != null) {
rememberForeverLazyListState(scrollStateKey)
@ -103,6 +107,12 @@ private fun FeedLoaded(
rememberLazyListState()
}
if (forceRefresh) {
LaunchedEffect(Unit) {
listState.animateScrollToItem(0)
}
}
LazyColumn(
contentPadding = PaddingValues(
top = 10.dp,

View File

@ -4,10 +4,17 @@ import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.saveable.rememberSaveable
import com.vitorpamplona.amethyst.ui.navigation.Route
private val savedScrollStates = mutableMapOf<String, ScrollState>()
private data class ScrollState(val index: Int, val scrollOffset: Int)
object ScrollStateKeys {
const val GLOBAL_SCREEN = "Global"
val HOME_FOLLOWS = Route.Home.base + "Follows"
val HOME_REPLIES = Route.Home.base + "FollowsReplies"
}
@Composable
fun rememberForeverLazyListState(
key: String,

View File

@ -34,11 +34,12 @@ import com.vitorpamplona.amethyst.ui.navigation.Route
import com.vitorpamplona.amethyst.ui.screen.FeedView
import com.vitorpamplona.amethyst.ui.screen.NostrHomeFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.NostrHomeRepliesFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.ScrollStateKeys
import kotlinx.coroutines.launch
@OptIn(ExperimentalPagerApi::class)
@Composable
fun HomeScreen(accountViewModel: AccountViewModel, navController: NavController) {
fun HomeScreen(accountViewModel: AccountViewModel, navController: NavController, forceRefresh: Boolean? = false) {
val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account ?: return
@ -106,8 +107,8 @@ fun HomeScreen(accountViewModel: AccountViewModel, navController: NavController)
}
HorizontalPager(count = 2, state = pagerState) {
when (pagerState.currentPage) {
0 -> FeedView(feedViewModel, accountViewModel, navController, Route.Home.route + "Follows", Route.Home.route + "Follows")
1 -> FeedView(feedViewModelReplies, accountViewModel, navController, Route.Home.route + "FollowsReplies", Route.Home.route + "FollowsReplies")
0 -> FeedView(feedViewModel, accountViewModel, navController, Route.Home.base + "Follows", ScrollStateKeys.HOME_FOLLOWS, forceRefresh)
1 -> FeedView(feedViewModelReplies, accountViewModel, navController, Route.Home.base + "FollowsReplies", ScrollStateKeys.HOME_REPLIES)
}
}
}

View File

@ -61,7 +61,7 @@ fun MainScreen(accountViewModel: AccountViewModel, accountStateViewModel: Accoun
fun FloatingButton(navController: NavHostController, accountViewModel: AccountStateViewModel) {
val accountState by accountViewModel.accountContent.collectAsState()
if (currentRoute(navController) == Route.Home.route) {
if (currentRoute(navController)?.substringBefore("?") == Route.Home.base) {
Crossfade(targetState = accountState, animationSpec = tween(durationMillis = 100)) { state ->
when (state) {
is AccountState.LoggedInViewOnly -> {
@ -77,7 +77,7 @@ fun FloatingButton(navController: NavHostController, accountViewModel: AccountSt
}
}
if (currentRoute(navController) == Route.Message.route) {
if (currentRoute(navController) == Route.Message.base) {
Crossfade(targetState = accountState, animationSpec = tween(durationMillis = 100)) { state ->
when (state) {
is AccountState.LoggedInViewOnly -> {

View File

@ -66,6 +66,7 @@ import com.vitorpamplona.amethyst.ui.note.UserPicture
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
import com.vitorpamplona.amethyst.ui.screen.FeedView
import com.vitorpamplona.amethyst.ui.screen.NostrGlobalFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.ScrollStateKeys
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.collectLatest
@ -78,7 +79,7 @@ import kotlinx.coroutines.withContext
import kotlinx.coroutines.channels.Channel as CoroutineChannel
@Composable
fun SearchScreen(accountViewModel: AccountViewModel, navController: NavController) {
fun SearchScreen(accountViewModel: AccountViewModel, navController: NavController, forceRefresh: Boolean? = false) {
val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account ?: return
@ -114,7 +115,7 @@ fun SearchScreen(accountViewModel: AccountViewModel, navController: NavControlle
modifier = Modifier.padding(vertical = 0.dp)
) {
SearchBar(accountViewModel, navController)
FeedView(feedViewModel, accountViewModel, navController, null, "Global")
FeedView(feedViewModel, accountViewModel, navController, null, ScrollStateKeys.GLOBAL_SCREEN, forceRefresh)
}
}
}