diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt index a08f68c2e..bba99be7a 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt @@ -53,13 +53,9 @@ fun AppNavigation( GlobalFeedFilter.account = account val searchFeedViewModel: NostrGlobalFeedViewModel = viewModel() - val restartNotificationList = NotificationFeedFilter.isDifferentAccount(account) - NotificationFeedFilter.account = account val notifFeedViewModel: NotificationViewModel = viewModel() - if (restartNotificationList) notifFeedViewModel.clear() - NavHost(navController, startDestination = Route.Home.route) { Route.Search.let { route -> composable(route.route, route.arguments, content = { @@ -99,8 +95,25 @@ fun AppNavigation( }) } + Route.Notification.let { route -> + composable(route.route, route.arguments, content = { + val scrollToTop = it.arguments?.getBoolean("scrollToTop") ?: false + + NotificationScreen( + notifFeedViewModel = notifFeedViewModel, + accountViewModel = accountViewModel, + navController = navController, + scrollToTop = scrollToTop + ) + + // Avoids running scroll to top when back button is pressed + if (scrollToTop) { + it.arguments?.remove("scrollToTop") + } + }) + } + composable(Route.Message.route, content = { ChatroomListScreen(accountViewModel, navController) }) - composable(Route.Notification.route, content = { NotificationScreen(notifFeedViewModel, accountViewModel, navController) }) composable(Route.BlockedUsers.route, content = { HiddenUsersScreen(accountViewModel, navController) }) composable(Route.Bookmarks.route, content = { BookmarkListScreen(accountViewModel, navController) }) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt index 99ea241af..66fd2fa05 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt @@ -37,8 +37,9 @@ sealed class Route( ) object Notification : Route( - route = "Notification", + route = "Notification?scrollToTop={scrollToTop}", icon = R.drawable.ic_notifications, + arguments = listOf(navArgument("scrollToTop") { type = NavType.BoolType; defaultValue = false }), hasNewItems = { accountViewModel, cache -> notificationHasNewItems(accountViewModel, cache) } ) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt index 0744d4b1d..ed5e46c62 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt @@ -13,6 +13,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 @@ -33,7 +34,14 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel @OptIn(ExperimentalMaterialApi::class) @Composable -fun CardFeedView(viewModel: CardFeedViewModel, accountViewModel: AccountViewModel, navController: NavController, routeForLastRead: String) { +fun CardFeedView( + viewModel: CardFeedViewModel, + accountViewModel: AccountViewModel, + navController: NavController, + routeForLastRead: String, + scrollStateKey: String? = null, + scrollToTop: Boolean = false +) { val feedState by viewModel.feedContent.collectAsState() var refreshing by remember { mutableStateOf(false) } @@ -57,10 +65,12 @@ fun CardFeedView(viewModel: CardFeedViewModel, accountViewModel: AccountViewMode is CardFeedState.Loaded -> { refreshing = false FeedLoaded( - state, - accountViewModel, - navController, - routeForLastRead + state = state, + accountViewModel = accountViewModel, + navController = navController, + routeForLastRead = routeForLastRead, + scrollStateKey = scrollStateKey, + scrollToTop = scrollToTop ) } CardFeedState.Loading -> { @@ -79,9 +89,21 @@ private fun FeedLoaded( state: CardFeedState.Loaded, accountViewModel: AccountViewModel, navController: NavController, - routeForLastRead: String + routeForLastRead: String, + scrollStateKey: String?, + scrollToTop: Boolean = false ) { - val listState = rememberLazyListState() + val listState = if (scrollStateKey != null) { + rememberForeverLazyListState(scrollStateKey) + } else { + rememberLazyListState() + } + + if (scrollToTop) { + LaunchedEffect(Unit) { + listState.scrollToItem(index = 0) + } + } LazyColumn( contentPadding = PaddingValues( diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt index 1b546d246..9095dc023 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt @@ -3,6 +3,7 @@ package com.vitorpamplona.amethyst.ui.screen import android.util.Log import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel +import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.LocalCacheState import com.vitorpamplona.amethyst.model.Note @@ -32,6 +33,7 @@ open class CardFeedViewModel(val dataSource: FeedFilter) : ViewModel() { private val _feedContent = MutableStateFlow(CardFeedState.Loading) val feedContent = _feedContent.asStateFlow() + private var lastAccount: Account? = null private var lastNotes: List? = null private fun refresh() { @@ -45,18 +47,21 @@ open class CardFeedViewModel(val dataSource: FeedFilter) : ViewModel() { private fun refreshSuspended() { val notes = dataSource.loadTop() - val lastNotesCopy = lastNotes + val thisAccount = (dataSource as? NotificationFeedFilter)?.account + val lastNotesCopy = if (thisAccount == lastAccount) lastNotes else null val oldNotesState = feedContent.value if (lastNotesCopy != null && oldNotesState is CardFeedState.Loaded) { val newCards = convertToCard(notes.minus(lastNotesCopy)) if (newCards.isNotEmpty()) { lastNotes = notes + lastAccount = (dataSource as? NotificationFeedFilter)?.account updateFeed((oldNotesState.feed.value + newCards).distinctBy { it.id() }.sortedBy { it.createdAt() }.reversed()) } } else { val cards = convertToCard(notes) lastNotes = notes + lastAccount = (dataSource as? NotificationFeedFilter)?.account updateFeed(cards) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LazyListState.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LazyListState.kt index 0a4a2d5cf..ebf122ee5 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LazyListState.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LazyListState.kt @@ -11,6 +11,7 @@ private data class ScrollState(val index: Int, val scrollOffset: Int) object ScrollStateKeys { const val GLOBAL_SCREEN = "Global" + const val NOTIFICATION_SCREEN = "Notifications" val HOME_FOLLOWS = Route.Home.base + "Follows" val HOME_REPLIES = Route.Home.base + "FollowsReplies" } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt index 5411488c9..dfba51058 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt @@ -18,9 +18,15 @@ import com.vitorpamplona.amethyst.ui.dal.NotificationFeedFilter import com.vitorpamplona.amethyst.ui.navigation.Route import com.vitorpamplona.amethyst.ui.screen.CardFeedView import com.vitorpamplona.amethyst.ui.screen.NotificationViewModel +import com.vitorpamplona.amethyst.ui.screen.ScrollStateKeys @Composable -fun NotificationScreen(notifFeedViewModel: NotificationViewModel, accountViewModel: AccountViewModel, navController: NavController) { +fun NotificationScreen( + notifFeedViewModel: NotificationViewModel, + accountViewModel: AccountViewModel, + navController: NavController, + scrollToTop: Boolean = false +) { val accountState by accountViewModel.accountLiveData.observeAsState() val account = accountState?.account ?: return @@ -48,7 +54,14 @@ fun NotificationScreen(notifFeedViewModel: NotificationViewModel, accountViewMod Column( modifier = Modifier.padding(vertical = 0.dp) ) { - CardFeedView(notifFeedViewModel, accountViewModel = accountViewModel, navController, Route.Notification.route) + CardFeedView( + viewModel = notifFeedViewModel, + accountViewModel = accountViewModel, + navController = navController, + routeForLastRead = Route.Notification.base, + scrollStateKey = ScrollStateKeys.NOTIFICATION_SCREEN, + scrollToTop = scrollToTop + ) } } } diff --git a/app/src/main/res/drawable/alby.xml b/app/src/main/res/drawable/alby.xml new file mode 100644 index 000000000..1188a62fc --- /dev/null +++ b/app/src/main/res/drawable/alby.xml @@ -0,0 +1,39 @@ + + + + + + + + + + +