mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-10-10 15:53:10 +02:00
Merge pull request #245 from maxmoney21m/feature/keep-feed-position
Keep feed position while navigating different screens
This commit is contained in:
@@ -80,9 +80,6 @@ dependencies {
|
|||||||
// Biometrics
|
// Biometrics
|
||||||
implementation "androidx.biometric:biometric-ktx:1.2.0-alpha05"
|
implementation "androidx.biometric:biometric-ktx:1.2.0-alpha05"
|
||||||
|
|
||||||
// Swipe Refresh
|
|
||||||
implementation 'com.google.accompanist:accompanist-swiperefresh:0.29.1-alpha'
|
|
||||||
|
|
||||||
// Bitcoin secp256k1 bindings to Android
|
// Bitcoin secp256k1 bindings to Android
|
||||||
implementation 'fr.acinq.secp256k1:secp256k1-kmp-jni-android:0.7.1'
|
implementation 'fr.acinq.secp256k1:secp256k1-kmp-jni-android:0.7.1'
|
||||||
|
|
||||||
|
@@ -84,8 +84,8 @@ fun keyboardAsState(): State<Keyboard> {
|
|||||||
@Composable
|
@Composable
|
||||||
fun AppBottomBar(navController: NavHostController, accountViewModel: AccountViewModel) {
|
fun AppBottomBar(navController: NavHostController, accountViewModel: AccountViewModel) {
|
||||||
val currentRoute = currentRoute(navController)
|
val currentRoute = currentRoute(navController)
|
||||||
|
val currentRouteBase = currentRoute?.substringBefore("?")
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
val isKeyboardOpen by keyboardAsState()
|
val isKeyboardOpen by keyboardAsState()
|
||||||
|
|
||||||
if (isKeyboardOpen == Keyboard.Closed) {
|
if (isKeyboardOpen == Keyboard.Closed) {
|
||||||
@@ -99,13 +99,15 @@ fun AppBottomBar(navController: NavHostController, accountViewModel: AccountView
|
|||||||
backgroundColor = MaterialTheme.colors.background
|
backgroundColor = MaterialTheme.colors.background
|
||||||
) {
|
) {
|
||||||
bottomNavigationItems.forEach { item ->
|
bottomNavigationItems.forEach { item ->
|
||||||
|
val selected = currentRouteBase == item.base
|
||||||
|
|
||||||
BottomNavigationItem(
|
BottomNavigationItem(
|
||||||
icon = { NotifiableIcon(item, currentRoute, accountViewModel) },
|
icon = { NotifiableIcon(item, selected, accountViewModel) },
|
||||||
selected = currentRoute == item.route,
|
selected = selected,
|
||||||
onClick = {
|
onClick = {
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
if (currentRoute != item.route) {
|
if (currentRouteBase != item.base) {
|
||||||
navController.navigate(item.route) {
|
navController.navigate(item.base) {
|
||||||
navController.graph.startDestinationRoute?.let { start ->
|
navController.graph.startDestinationRoute?.let { start ->
|
||||||
popUpTo(start)
|
popUpTo(start)
|
||||||
restoreState = true
|
restoreState = true
|
||||||
@@ -114,8 +116,8 @@ fun AppBottomBar(navController: NavHostController, accountViewModel: AccountView
|
|||||||
restoreState = true
|
restoreState = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// TODO: Make it scrool to the top
|
val route = currentRoute.replace("{scrollToTop}", "true")
|
||||||
navController.navigate(item.route) {
|
navController.navigate(route) {
|
||||||
navController.graph.startDestinationRoute?.let { start ->
|
navController.graph.startDestinationRoute?.let { start ->
|
||||||
popUpTo(start) { inclusive = item.route == Route.Home.route }
|
popUpTo(start) { inclusive = item.route == Route.Home.route }
|
||||||
restoreState = true
|
restoreState = true
|
||||||
@@ -135,13 +137,13 @@ fun AppBottomBar(navController: NavHostController, accountViewModel: AccountView
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun NotifiableIcon(item: Route, currentRoute: String?, accountViewModel: AccountViewModel) {
|
private fun NotifiableIcon(route: Route, selected: Boolean, accountViewModel: AccountViewModel) {
|
||||||
Box(Modifier.size(if ("Home" == item.route) 25.dp else 23.dp)) {
|
Box(Modifier.size(if ("Home" == route.base) 25.dp else 23.dp)) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(id = item.icon),
|
painter = painterResource(id = route.icon),
|
||||||
null,
|
null,
|
||||||
modifier = Modifier.size(if ("Home" == item.route) 24.dp else 20.dp),
|
modifier = Modifier.size(if ("Home" == route.base) 24.dp else 20.dp),
|
||||||
tint = if (currentRoute == item.route) MaterialTheme.colors.primary else Color.Unspecified
|
tint = if (selected) MaterialTheme.colors.primary else Color.Unspecified
|
||||||
)
|
)
|
||||||
|
|
||||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||||
@@ -160,13 +162,13 @@ private fun NotifiableIcon(item: Route, currentRoute: String?, accountViewModel:
|
|||||||
|
|
||||||
LaunchedEffect(key1 = notif) {
|
LaunchedEffect(key1 = notif) {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
hasNewItems = item.hasNewItems(account, notif.cache, context)
|
hasNewItems = route.hasNewItems(account, notif.cache, context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(key1 = db) {
|
LaunchedEffect(key1 = db) {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
hasNewItems = item.hasNewItems(account, notif.cache, context)
|
hasNewItems = route.hasNewItems(account, notif.cache, context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,12 +1,33 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.navigation
|
package com.vitorpamplona.amethyst.ui.navigation
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
|
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||||
|
import com.google.accompanist.pager.rememberPagerState
|
||||||
|
import com.vitorpamplona.amethyst.ui.dal.GlobalFeedFilter
|
||||||
|
import com.vitorpamplona.amethyst.ui.dal.HomeConversationsFeedFilter
|
||||||
|
import com.vitorpamplona.amethyst.ui.dal.HomeNewThreadFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
|
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.NostrGlobalFeedViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.NostrHomeFeedViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.NostrHomeRepliesFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChannelScreen
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChatroomListScreen
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChatroomScreen
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.FiltersScreen
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.HomeScreen
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.NotificationScreen
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ProfileScreen
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SearchScreen
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ThreadScreen
|
||||||
|
|
||||||
|
@OptIn(ExperimentalPagerApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun AppNavigation(
|
fun AppNavigation(
|
||||||
navController: NavHostController,
|
navController: NavHostController,
|
||||||
@@ -14,9 +35,86 @@ fun AppNavigation(
|
|||||||
accountStateViewModel: AccountStateViewModel,
|
accountStateViewModel: AccountStateViewModel,
|
||||||
nextPage: String? = null
|
nextPage: String? = null
|
||||||
) {
|
) {
|
||||||
|
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||||
|
val account = accountState?.account ?: return
|
||||||
|
|
||||||
|
GlobalFeedFilter.account = account
|
||||||
|
HomeNewThreadFeedFilter.account = account
|
||||||
|
HomeConversationsFeedFilter.account = account
|
||||||
|
|
||||||
|
val globalFeedViewModel: NostrGlobalFeedViewModel = viewModel()
|
||||||
|
val homeFeedViewModel: NostrHomeFeedViewModel = viewModel()
|
||||||
|
val homeRepliesFeedViewModel: NostrHomeRepliesFeedViewModel = viewModel()
|
||||||
|
val homePagerState = rememberPagerState()
|
||||||
|
|
||||||
NavHost(navController, startDestination = Route.Home.route) {
|
NavHost(navController, startDestination = Route.Home.route) {
|
||||||
Routes.forEach {
|
Route.Search.let { route ->
|
||||||
composable(it.route, it.arguments, content = it.buildScreen(accountViewModel, accountStateViewModel, navController))
|
composable(route.route, route.arguments, content = {
|
||||||
|
SearchScreen(
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
feedViewModel = globalFeedViewModel,
|
||||||
|
navController = navController,
|
||||||
|
scrollToTop = it.arguments?.getBoolean("scrollToTop") ?: false
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Route.Home.let { route ->
|
||||||
|
composable(route.route, route.arguments, content = {
|
||||||
|
HomeScreen(
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
navController = navController,
|
||||||
|
homeFeedViewModel = homeFeedViewModel,
|
||||||
|
repliesFeedViewModel = homeRepliesFeedViewModel,
|
||||||
|
pagerState = homePagerState,
|
||||||
|
scrollToTop = it.arguments?.getBoolean("scrollToTop") ?: false
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
composable(Route.Message.route, content = { ChatroomListScreen(accountViewModel, navController) })
|
||||||
|
composable(Route.Notification.route, content = { NotificationScreen(accountViewModel, navController) })
|
||||||
|
composable(Route.Filters.route, content = { FiltersScreen(accountViewModel, navController) })
|
||||||
|
|
||||||
|
Route.Profile.let { route ->
|
||||||
|
composable(route.route, route.arguments, content = {
|
||||||
|
ProfileScreen(
|
||||||
|
userId = it.arguments?.getString("id"),
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
navController = navController
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Route.Note.let { route ->
|
||||||
|
composable(route.route, route.arguments, content = {
|
||||||
|
ThreadScreen(
|
||||||
|
noteId = it.arguments?.getString("id"),
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
navController = navController
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Route.Room.let { route ->
|
||||||
|
composable(route.route, route.arguments, content = {
|
||||||
|
ChatroomScreen(
|
||||||
|
userId = it.arguments?.getString("id"),
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
navController = navController
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Route.Channel.let { route ->
|
||||||
|
composable(route.route, route.arguments, content = {
|
||||||
|
ChannelScreen(
|
||||||
|
channelId = it.arguments?.getString("id"),
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
accountStateViewModel = accountStateViewModel,
|
||||||
|
navController = navController
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -4,8 +4,6 @@ import android.content.Context
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.navigation.NamedNavArgument
|
import androidx.navigation.NamedNavArgument
|
||||||
import androidx.navigation.NavBackStackEntry
|
|
||||||
import androidx.navigation.NavController
|
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import androidx.navigation.NavType
|
import androidx.navigation.NavType
|
||||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||||
@@ -16,42 +14,27 @@ import com.vitorpamplona.amethyst.model.Account
|
|||||||
import com.vitorpamplona.amethyst.ui.dal.ChatroomListKnownFeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.ChatroomListKnownFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.dal.HomeNewThreadFeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.HomeNewThreadFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.dal.NotificationFeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.NotificationFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChannelScreen
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChatroomListScreen
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChatroomScreen
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.FiltersScreen
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.HomeScreen
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.NotificationScreen
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ProfileScreen
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SearchScreen
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ThreadScreen
|
|
||||||
|
|
||||||
sealed class Route(
|
sealed class Route(
|
||||||
val route: String,
|
val route: String,
|
||||||
val icon: Int,
|
val icon: Int,
|
||||||
val hasNewItems: (Account, NotificationCache, Context) -> Boolean = { _, _, _ -> false },
|
val hasNewItems: (Account, NotificationCache, Context) -> Boolean = { _, _, _ -> false },
|
||||||
val arguments: List<NamedNavArgument> = emptyList(),
|
val arguments: List<NamedNavArgument> = emptyList()
|
||||||
val buildScreen: (AccountViewModel, AccountStateViewModel, NavController) -> @Composable (NavBackStackEntry) -> Unit
|
|
||||||
) {
|
) {
|
||||||
|
val base: String
|
||||||
|
get() = route.substringBefore("?")
|
||||||
|
|
||||||
object Home : Route(
|
object Home : Route(
|
||||||
route = "Home",
|
route = "Home?scrollToTop={scrollToTop}",
|
||||||
icon = R.drawable.ic_home,
|
icon = R.drawable.ic_home,
|
||||||
hasNewItems = { accountViewModel, cache, context ->
|
arguments = listOf(navArgument("scrollToTop") { type = NavType.BoolType; defaultValue = false }),
|
||||||
homeHasNewItems(accountViewModel, cache, context)
|
hasNewItems = { accountViewModel, cache, context -> homeHasNewItems(accountViewModel, cache, context) }
|
||||||
},
|
|
||||||
buildScreen = { accountViewModel, _, navController ->
|
|
||||||
{ HomeScreen(accountViewModel, navController) }
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
object Search : Route(
|
object Search : Route(
|
||||||
route = "Search",
|
route = "Search?scrollToTop={scrollToTop}",
|
||||||
icon = R.drawable.ic_globe,
|
icon = R.drawable.ic_globe,
|
||||||
buildScreen = { accountViewModel, _, navController ->
|
arguments = listOf(navArgument("scrollToTop") { type = NavType.BoolType; defaultValue = false })
|
||||||
{ SearchScreen(accountViewModel, navController) }
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
object Notification : Route(
|
object Notification : Route(
|
||||||
@@ -59,9 +42,6 @@ sealed class Route(
|
|||||||
icon = R.drawable.ic_notifications,
|
icon = R.drawable.ic_notifications,
|
||||||
hasNewItems = { accountViewModel, cache, context ->
|
hasNewItems = { accountViewModel, cache, context ->
|
||||||
notificationHasNewItems(accountViewModel, cache, context)
|
notificationHasNewItems(accountViewModel, cache, context)
|
||||||
},
|
|
||||||
buildScreen = { accountViewModel, _, navController ->
|
|
||||||
{ NotificationScreen(accountViewModel, navController) }
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -70,84 +50,44 @@ sealed class Route(
|
|||||||
icon = R.drawable.ic_dm,
|
icon = R.drawable.ic_dm,
|
||||||
hasNewItems = { accountViewModel, cache, context ->
|
hasNewItems = { accountViewModel, cache, context ->
|
||||||
messagesHasNewItems(accountViewModel, cache, context)
|
messagesHasNewItems(accountViewModel, cache, context)
|
||||||
},
|
|
||||||
buildScreen = { accountViewModel, _, navController ->
|
|
||||||
{ ChatroomListScreen(accountViewModel, navController) }
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
object Filters : Route(
|
object Filters : Route(
|
||||||
route = "Filters",
|
route = "Filters",
|
||||||
icon = R.drawable.ic_security,
|
icon = R.drawable.ic_security
|
||||||
buildScreen = { accountViewModel, _, navController ->
|
|
||||||
{ FiltersScreen(accountViewModel, navController) }
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
object Profile : Route(
|
object Profile : Route(
|
||||||
route = "User/{id}",
|
route = "User/{id}",
|
||||||
icon = R.drawable.ic_profile,
|
icon = R.drawable.ic_profile,
|
||||||
arguments = listOf(navArgument("id") { type = NavType.StringType }),
|
arguments = listOf(navArgument("id") { type = NavType.StringType })
|
||||||
buildScreen = { accountViewModel, _, navController ->
|
|
||||||
{ ProfileScreen(it.arguments?.getString("id"), accountViewModel, navController) }
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
object Note : Route(
|
object Note : Route(
|
||||||
route = "Note/{id}",
|
route = "Note/{id}",
|
||||||
icon = R.drawable.ic_moments,
|
icon = R.drawable.ic_moments,
|
||||||
arguments = listOf(navArgument("id") { type = NavType.StringType }),
|
arguments = listOf(navArgument("id") { type = NavType.StringType })
|
||||||
buildScreen = { accountViewModel, _, navController ->
|
|
||||||
{ ThreadScreen(it.arguments?.getString("id"), accountViewModel, navController) }
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
object Room : Route(
|
object Room : Route(
|
||||||
route = "Room/{id}",
|
route = "Room/{id}",
|
||||||
icon = R.drawable.ic_moments,
|
icon = R.drawable.ic_moments,
|
||||||
arguments = listOf(navArgument("id") { type = NavType.StringType }),
|
arguments = listOf(navArgument("id") { type = NavType.StringType })
|
||||||
buildScreen = { accountViewModel, _, navController ->
|
|
||||||
{ ChatroomScreen(it.arguments?.getString("id"), accountViewModel, navController) }
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
object Channel : Route(
|
object Channel : Route(
|
||||||
route = "Channel/{id}",
|
route = "Channel/{id}",
|
||||||
icon = R.drawable.ic_moments,
|
icon = R.drawable.ic_moments,
|
||||||
arguments = listOf(navArgument("id") { type = NavType.StringType }),
|
arguments = listOf(navArgument("id") { type = NavType.StringType })
|
||||||
buildScreen = { accountViewModel, accountStateViewModel, navController ->
|
|
||||||
{
|
|
||||||
ChannelScreen(
|
|
||||||
it.arguments?.getString("id"),
|
|
||||||
accountViewModel,
|
|
||||||
accountStateViewModel,
|
|
||||||
navController
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val Routes = listOf(
|
|
||||||
// bottom
|
|
||||||
Route.Home,
|
|
||||||
Route.Message,
|
|
||||||
Route.Search,
|
|
||||||
Route.Notification,
|
|
||||||
|
|
||||||
// drawer
|
|
||||||
Route.Profile,
|
|
||||||
Route.Note,
|
|
||||||
Route.Room,
|
|
||||||
Route.Channel,
|
|
||||||
Route.Filters
|
|
||||||
)
|
|
||||||
|
|
||||||
// **
|
// **
|
||||||
// * Functions below only exist because we have not broken the datasource classes into backend and frontend.
|
// * Functions below only exist because we have not broken the datasource classes into backend and frontend.
|
||||||
// **
|
// **
|
||||||
@Composable
|
@Composable
|
||||||
public fun currentRoute(navController: NavHostController): String? {
|
fun currentRoute(navController: NavHostController): String? {
|
||||||
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
||||||
return navBackStackEntry?.destination?.route
|
return navBackStackEntry?.destination?.route
|
||||||
}
|
}
|
||||||
|
@@ -2,22 +2,26 @@ package com.vitorpamplona.amethyst.ui.screen
|
|||||||
|
|
||||||
import androidx.compose.animation.Crossfade
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.material.ExperimentalMaterialApi
|
||||||
|
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.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import com.google.accompanist.swiperefresh.SwipeRefresh
|
|
||||||
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
|
||||||
import com.vitorpamplona.amethyst.ui.note.BadgeCompose
|
import com.vitorpamplona.amethyst.ui.note.BadgeCompose
|
||||||
import com.vitorpamplona.amethyst.ui.note.BoostSetCompose
|
import com.vitorpamplona.amethyst.ui.note.BoostSetCompose
|
||||||
import com.vitorpamplona.amethyst.ui.note.LikeSetCompose
|
import com.vitorpamplona.amethyst.ui.note.LikeSetCompose
|
||||||
@@ -27,40 +31,31 @@ import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
|||||||
import com.vitorpamplona.amethyst.ui.note.ZapSetCompose
|
import com.vitorpamplona.amethyst.ui.note.ZapSetCompose
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun CardFeedView(viewModel: CardFeedViewModel, accountViewModel: AccountViewModel, navController: NavController, routeForLastRead: String) {
|
fun CardFeedView(viewModel: CardFeedViewModel, accountViewModel: AccountViewModel, navController: NavController, routeForLastRead: String) {
|
||||||
val feedState by viewModel.feedContent.collectAsState()
|
val feedState by viewModel.feedContent.collectAsState()
|
||||||
|
|
||||||
var isRefreshing by remember { mutableStateOf(false) }
|
var refreshing by remember { mutableStateOf(false) }
|
||||||
val swipeRefreshState = rememberSwipeRefreshState(isRefreshing)
|
val refresh = { refreshing = true; viewModel.refresh(); refreshing = false }
|
||||||
|
val pullRefreshState = rememberPullRefreshState(refreshing, onRefresh = refresh)
|
||||||
|
|
||||||
LaunchedEffect(isRefreshing) {
|
Box(Modifier.pullRefresh(pullRefreshState)) {
|
||||||
if (isRefreshing) {
|
|
||||||
viewModel.refresh()
|
|
||||||
isRefreshing = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SwipeRefresh(
|
|
||||||
state = swipeRefreshState,
|
|
||||||
onRefresh = {
|
|
||||||
isRefreshing = true
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Column() {
|
Column() {
|
||||||
Crossfade(targetState = feedState, animationSpec = tween(durationMillis = 100)) { state ->
|
Crossfade(targetState = feedState, animationSpec = tween(durationMillis = 100)) { state ->
|
||||||
when (state) {
|
when (state) {
|
||||||
is CardFeedState.Empty -> {
|
is CardFeedState.Empty -> {
|
||||||
FeedEmpty {
|
FeedEmpty {
|
||||||
isRefreshing = true
|
refreshing = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is CardFeedState.FeedError -> {
|
is CardFeedState.FeedError -> {
|
||||||
FeedError(state.errorMessage) {
|
FeedError(state.errorMessage) {
|
||||||
isRefreshing = true
|
refreshing = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is CardFeedState.Loaded -> {
|
is CardFeedState.Loaded -> {
|
||||||
|
refreshing = false
|
||||||
FeedLoaded(
|
FeedLoaded(
|
||||||
state,
|
state,
|
||||||
accountViewModel,
|
accountViewModel,
|
||||||
@@ -74,6 +69,8 @@ fun CardFeedView(viewModel: CardFeedViewModel, accountViewModel: AccountViewMode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PullRefreshIndicator(refreshing, pullRefreshState, Modifier.align(Alignment.TopCenter))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.Column
|
|||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
@@ -21,12 +20,12 @@ import com.vitorpamplona.amethyst.ui.note.ChatroomMessageCompose
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChatroomFeedView(viewModel: FeedViewModel, accountViewModel: AccountViewModel, navController: NavController, routeForLastRead: String?, onWantsToReply: (Note) -> Unit) {
|
fun ChatroomFeedView(viewModel: FeedViewModel, accountViewModel: AccountViewModel, navController: NavController, routeForLastRead: String, onWantsToReply: (Note) -> Unit) {
|
||||||
val feedState by viewModel.feedContent.collectAsState()
|
val feedState by viewModel.feedContent.collectAsState()
|
||||||
|
|
||||||
var isRefreshing by remember { mutableStateOf(false) }
|
var isRefreshing by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberForeverLazyListState(routeForLastRead)
|
||||||
|
|
||||||
LaunchedEffect(isRefreshing) {
|
LaunchedEffect(isRefreshing) {
|
||||||
if (isRefreshing) {
|
if (isRefreshing) {
|
||||||
@@ -63,8 +62,7 @@ fun ChatroomFeedView(viewModel: FeedViewModel, accountViewModel: AccountViewMode
|
|||||||
reverseLayout = true,
|
reverseLayout = true,
|
||||||
state = listState
|
state = listState
|
||||||
) {
|
) {
|
||||||
var previousDate: String = ""
|
itemsIndexed(state.feed.value, key = { _, item -> item.idHex }) { _, item ->
|
||||||
itemsIndexed(state.feed.value, key = { index, item -> item.idHex }) { index, item ->
|
|
||||||
ChatroomMessageCompose(item, routeForLastRead, accountViewModel = accountViewModel, navController = navController, onWantsToReply = onWantsToReply)
|
ChatroomMessageCompose(item, routeForLastRead, accountViewModel = accountViewModel, navController = navController, onWantsToReply = onWantsToReply)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,11 +2,16 @@ package com.vitorpamplona.amethyst.ui.screen
|
|||||||
|
|
||||||
import androidx.compose.animation.Crossfade
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.material.ExperimentalMaterialApi
|
||||||
|
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.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
@@ -14,18 +19,18 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import com.google.accompanist.swiperefresh.SwipeRefresh
|
|
||||||
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
|
||||||
import com.vitorpamplona.amethyst.NotificationCache
|
import com.vitorpamplona.amethyst.NotificationCache
|
||||||
import com.vitorpamplona.amethyst.ui.note.ChatroomCompose
|
import com.vitorpamplona.amethyst.ui.note.ChatroomCompose
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ChatroomListFeedView(
|
fun ChatroomListFeedView(
|
||||||
viewModel: FeedViewModel,
|
viewModel: FeedViewModel,
|
||||||
@@ -35,24 +40,11 @@ fun ChatroomListFeedView(
|
|||||||
) {
|
) {
|
||||||
val feedState by viewModel.feedContent.collectAsStateWithLifecycle()
|
val feedState by viewModel.feedContent.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
var isRefreshing by remember { mutableStateOf(false) }
|
var refreshing by remember { mutableStateOf(false) }
|
||||||
val swipeRefreshState = rememberSwipeRefreshState(isRefreshing)
|
val refresh = { refreshing = true; viewModel.refresh(); refreshing = false }
|
||||||
|
val pullRefreshState = rememberPullRefreshState(refreshing, onRefresh = refresh)
|
||||||
|
|
||||||
val coroutineScope = rememberCoroutineScope()
|
Box(Modifier.pullRefresh(pullRefreshState)) {
|
||||||
|
|
||||||
LaunchedEffect(isRefreshing) {
|
|
||||||
if (isRefreshing) {
|
|
||||||
viewModel.refresh()
|
|
||||||
isRefreshing = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SwipeRefresh(
|
|
||||||
state = swipeRefreshState,
|
|
||||||
onRefresh = {
|
|
||||||
isRefreshing = true
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Column() {
|
Column() {
|
||||||
Crossfade(
|
Crossfade(
|
||||||
targetState = feedState,
|
targetState = feedState,
|
||||||
@@ -61,17 +53,18 @@ fun ChatroomListFeedView(
|
|||||||
when (state) {
|
when (state) {
|
||||||
is FeedState.Empty -> {
|
is FeedState.Empty -> {
|
||||||
FeedEmpty {
|
FeedEmpty {
|
||||||
isRefreshing = true
|
refreshing = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is FeedState.FeedError -> {
|
is FeedState.FeedError -> {
|
||||||
FeedError(state.errorMessage) {
|
FeedError(state.errorMessage) {
|
||||||
isRefreshing = true
|
refreshing = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is FeedState.Loaded -> {
|
is FeedState.Loaded -> {
|
||||||
|
refreshing = false
|
||||||
FeedLoaded(state, accountViewModel, navController, markAsRead)
|
FeedLoaded(state, accountViewModel, navController, markAsRead)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,6 +74,8 @@ fun ChatroomListFeedView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PullRefreshIndicator(refreshing, pullRefreshState, Modifier.align(Alignment.TopCenter))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,11 +98,9 @@ private fun FeedLoaded(
|
|||||||
if (markAsRead.value) {
|
if (markAsRead.value) {
|
||||||
for (note in state.feed.value) {
|
for (note in state.feed.value) {
|
||||||
note.event?.let {
|
note.event?.let {
|
||||||
var route = ""
|
|
||||||
val channel = note.channel()
|
val channel = note.channel()
|
||||||
|
val route = if (channel != null) {
|
||||||
if (channel != null) {
|
"Channel/${channel.idHex}"
|
||||||
route = "Channel/${channel.idHex}"
|
|
||||||
} else {
|
} else {
|
||||||
val replyAuthorBase = note.mentions?.first()
|
val replyAuthorBase = note.mentions?.first()
|
||||||
var userToComposeOn = note.author!!
|
var userToComposeOn = note.author!!
|
||||||
@@ -116,7 +109,7 @@ private fun FeedLoaded(
|
|||||||
userToComposeOn = replyAuthorBase
|
userToComposeOn = replyAuthorBase
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
route = "Room/${userToComposeOn.pubkeyHex}"
|
"Room/${userToComposeOn.pubkeyHex}"
|
||||||
}
|
}
|
||||||
|
|
||||||
notificationCache.cache.markAsRead(route, it.createdAt(), context)
|
notificationCache.cache.markAsRead(route, it.createdAt(), context)
|
||||||
@@ -136,7 +129,7 @@ private fun FeedLoaded(
|
|||||||
itemsIndexed(
|
itemsIndexed(
|
||||||
state.feed.value,
|
state.feed.value,
|
||||||
key = { index, item -> if (index == 0) index else item.idHex }
|
key = { index, item -> if (index == 0) index else item.idHex }
|
||||||
) { index, item ->
|
) { _, item ->
|
||||||
ChatroomCompose(
|
ChatroomCompose(
|
||||||
item,
|
item,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
|
@@ -3,6 +3,7 @@ package com.vitorpamplona.amethyst.ui.screen
|
|||||||
import androidx.compose.animation.Crossfade
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
@@ -11,8 +12,12 @@ import androidx.compose.foundation.lazy.LazyColumn
|
|||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material.Button
|
import androidx.compose.material.Button
|
||||||
|
import androidx.compose.material.ExperimentalMaterialApi
|
||||||
import androidx.compose.material.OutlinedButton
|
import androidx.compose.material.OutlinedButton
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
|
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.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
@@ -25,64 +30,65 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import com.google.accompanist.swiperefresh.SwipeRefresh
|
|
||||||
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun FeedView(
|
fun FeedView(
|
||||||
viewModel: FeedViewModel,
|
viewModel: FeedViewModel,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
navController: NavController,
|
navController: NavController,
|
||||||
routeForLastRead: String?
|
routeForLastRead: String?,
|
||||||
|
scrollStateKey: String? = null,
|
||||||
|
scrollToTop: Boolean = false
|
||||||
) {
|
) {
|
||||||
val feedState by viewModel.feedContent.collectAsState()
|
val feedState by viewModel.feedContent.collectAsState()
|
||||||
|
|
||||||
var isRefreshing by remember { mutableStateOf(false) }
|
var refreshing by remember { mutableStateOf(false) }
|
||||||
val swipeRefreshState = rememberSwipeRefreshState(isRefreshing)
|
val refresh = { refreshing = true; viewModel.refresh(); refreshing = false }
|
||||||
|
val pullRefreshState = rememberPullRefreshState(refreshing, onRefresh = refresh)
|
||||||
|
|
||||||
LaunchedEffect(isRefreshing) {
|
Box(Modifier.pullRefresh(pullRefreshState)) {
|
||||||
if (isRefreshing) {
|
Column {
|
||||||
viewModel.refresh()
|
Crossfade(
|
||||||
isRefreshing = false
|
targetState = feedState,
|
||||||
}
|
animationSpec = tween(durationMillis = 100)
|
||||||
}
|
) { state ->
|
||||||
|
|
||||||
SwipeRefresh(
|
|
||||||
state = swipeRefreshState,
|
|
||||||
onRefresh = {
|
|
||||||
isRefreshing = true
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Column() {
|
|
||||||
Crossfade(targetState = feedState, animationSpec = tween(durationMillis = 100)) { state ->
|
|
||||||
when (state) {
|
when (state) {
|
||||||
is FeedState.Empty -> {
|
is FeedState.Empty -> {
|
||||||
FeedEmpty {
|
FeedEmpty {
|
||||||
isRefreshing = true
|
refreshing = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is FeedState.FeedError -> {
|
is FeedState.FeedError -> {
|
||||||
FeedError(state.errorMessage) {
|
FeedError(state.errorMessage) {
|
||||||
isRefreshing = true
|
refreshing = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is FeedState.Loaded -> {
|
is FeedState.Loaded -> {
|
||||||
|
refreshing = false
|
||||||
FeedLoaded(
|
FeedLoaded(
|
||||||
state,
|
state,
|
||||||
routeForLastRead,
|
routeForLastRead,
|
||||||
accountViewModel,
|
accountViewModel,
|
||||||
navController
|
navController,
|
||||||
|
scrollStateKey,
|
||||||
|
scrollToTop
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
is FeedState.Loading -> {
|
is FeedState.Loading -> {
|
||||||
LoadingFeed()
|
LoadingFeed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PullRefreshIndicator(refreshing, pullRefreshState, Modifier.align(Alignment.TopCenter))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,9 +97,21 @@ private fun FeedLoaded(
|
|||||||
state: FeedState.Loaded,
|
state: FeedState.Loaded,
|
||||||
routeForLastRead: String?,
|
routeForLastRead: String?,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
navController: NavController
|
navController: NavController,
|
||||||
|
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(
|
LazyColumn(
|
||||||
contentPadding = PaddingValues(
|
contentPadding = PaddingValues(
|
||||||
@@ -102,7 +120,7 @@ private fun FeedLoaded(
|
|||||||
),
|
),
|
||||||
state = listState
|
state = listState
|
||||||
) {
|
) {
|
||||||
itemsIndexed(state.feed.value, key = { _, item -> item.idHex }) { index, item ->
|
itemsIndexed(state.feed.value, key = { _, item -> item.idHex }) { _, item ->
|
||||||
NoteCompose(
|
NoteCompose(
|
||||||
item,
|
item,
|
||||||
isBoostedNote = false,
|
isBoostedNote = false,
|
||||||
|
@@ -0,0 +1,41 @@
|
|||||||
|
package com.vitorpamplona.amethyst.ui.screen
|
||||||
|
|
||||||
|
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,
|
||||||
|
initialFirstVisibleItemIndex: Int = 0,
|
||||||
|
initialFirstVisibleItemScrollOffset: Int = 0
|
||||||
|
): LazyListState {
|
||||||
|
val scrollState = rememberSaveable(saver = LazyListState.Saver) {
|
||||||
|
val savedValue = savedScrollStates[key]
|
||||||
|
val savedIndex = savedValue?.index ?: initialFirstVisibleItemIndex
|
||||||
|
val savedOffset = savedValue?.scrollOffset ?: initialFirstVisibleItemScrollOffset
|
||||||
|
LazyListState(
|
||||||
|
savedIndex,
|
||||||
|
savedOffset
|
||||||
|
)
|
||||||
|
}
|
||||||
|
DisposableEffect(Unit) {
|
||||||
|
onDispose {
|
||||||
|
val lastIndex = scrollState.firstVisibleItemIndex
|
||||||
|
val lastOffset = scrollState.firstVisibleItemScrollOffset
|
||||||
|
savedScrollStates[key] = ScrollState(lastIndex, lastOffset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return scrollState
|
||||||
|
}
|
@@ -2,59 +2,54 @@ package com.vitorpamplona.amethyst.ui.screen
|
|||||||
|
|
||||||
import androidx.compose.animation.Crossfade
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.material.ExperimentalMaterialApi
|
||||||
|
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.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import com.google.accompanist.swiperefresh.SwipeRefresh
|
|
||||||
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
|
||||||
import com.vitorpamplona.amethyst.ui.note.ZapNoteCompose
|
import com.vitorpamplona.amethyst.ui.note.ZapNoteCompose
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun LnZapFeedView(viewModel: LnZapFeedViewModel, accountViewModel: AccountViewModel, navController: NavController) {
|
fun LnZapFeedView(viewModel: LnZapFeedViewModel, accountViewModel: AccountViewModel, navController: NavController) {
|
||||||
val feedState by viewModel.feedContent.collectAsState()
|
val feedState by viewModel.feedContent.collectAsState()
|
||||||
|
|
||||||
var isRefreshing by remember { mutableStateOf(false) }
|
var refreshing by remember { mutableStateOf(false) }
|
||||||
val swipeRefreshState = rememberSwipeRefreshState(isRefreshing)
|
val refresh = { refreshing = true; viewModel.refresh(); refreshing = false }
|
||||||
|
val pullRefreshState = rememberPullRefreshState(refreshing, onRefresh = refresh)
|
||||||
|
|
||||||
LaunchedEffect(isRefreshing) {
|
Box(Modifier.pullRefresh(pullRefreshState)) {
|
||||||
if (isRefreshing) {
|
|
||||||
viewModel.refresh()
|
|
||||||
isRefreshing = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SwipeRefresh(
|
|
||||||
state = swipeRefreshState,
|
|
||||||
onRefresh = {
|
|
||||||
isRefreshing = true
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Column() {
|
Column() {
|
||||||
Crossfade(targetState = feedState, animationSpec = tween(durationMillis = 100)) { state ->
|
Crossfade(targetState = feedState, animationSpec = tween(durationMillis = 100)) { state ->
|
||||||
when (state) {
|
when (state) {
|
||||||
is LnZapFeedState.Empty -> {
|
is LnZapFeedState.Empty -> {
|
||||||
FeedEmpty {
|
FeedEmpty {
|
||||||
isRefreshing = true
|
refreshing = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is LnZapFeedState.FeedError -> {
|
is LnZapFeedState.FeedError -> {
|
||||||
FeedError(state.errorMessage) {
|
FeedError(state.errorMessage) {
|
||||||
isRefreshing = true
|
refreshing = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is LnZapFeedState.Loaded -> {
|
is LnZapFeedState.Loaded -> {
|
||||||
|
refreshing = false
|
||||||
LnZapFeedLoaded(state, accountViewModel, navController)
|
LnZapFeedLoaded(state, accountViewModel, navController)
|
||||||
}
|
}
|
||||||
is LnZapFeedState.Loading -> {
|
is LnZapFeedState.Loading -> {
|
||||||
@@ -63,6 +58,8 @@ fun LnZapFeedView(viewModel: LnZapFeedViewModel, accountViewModel: AccountViewMo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PullRefreshIndicator(refreshing, pullRefreshState, Modifier.align(Alignment.TopCenter))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,24 +1,28 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.screen
|
package com.vitorpamplona.amethyst.ui.screen
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.material.ExperimentalMaterialApi
|
||||||
|
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.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import com.google.accompanist.swiperefresh.SwipeRefresh
|
|
||||||
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
|
||||||
import com.vitorpamplona.amethyst.model.RelayInfo
|
import com.vitorpamplona.amethyst.model.RelayInfo
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.model.UserState
|
import com.vitorpamplona.amethyst.model.UserState
|
||||||
@@ -101,6 +105,7 @@ class RelayFeedViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun RelayFeedView(viewModel: RelayFeedViewModel, accountViewModel: AccountViewModel, navController: NavController) {
|
fun RelayFeedView(viewModel: RelayFeedViewModel, accountViewModel: AccountViewModel, navController: NavController) {
|
||||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||||
@@ -108,9 +113,6 @@ fun RelayFeedView(viewModel: RelayFeedViewModel, accountViewModel: AccountViewMo
|
|||||||
|
|
||||||
val feedState by viewModel.feedContent.collectAsState()
|
val feedState by viewModel.feedContent.collectAsState()
|
||||||
|
|
||||||
var isRefreshing by remember { mutableStateOf(false) }
|
|
||||||
val swipeRefreshState = rememberSwipeRefreshState(isRefreshing)
|
|
||||||
|
|
||||||
var wantsToAddRelay by remember {
|
var wantsToAddRelay by remember {
|
||||||
mutableStateOf("")
|
mutableStateOf("")
|
||||||
}
|
}
|
||||||
@@ -119,19 +121,11 @@ fun RelayFeedView(viewModel: RelayFeedViewModel, accountViewModel: AccountViewMo
|
|||||||
NewRelayListView({ wantsToAddRelay = "" }, account, wantsToAddRelay)
|
NewRelayListView({ wantsToAddRelay = "" }, account, wantsToAddRelay)
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(isRefreshing) {
|
var refreshing by remember { mutableStateOf(false) }
|
||||||
if (isRefreshing) {
|
val refresh = { refreshing = true; viewModel.refresh(); refreshing = false }
|
||||||
viewModel.refresh()
|
val pullRefreshState = rememberPullRefreshState(refreshing, onRefresh = refresh)
|
||||||
isRefreshing = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SwipeRefresh(
|
Box(Modifier.pullRefresh(pullRefreshState)) {
|
||||||
state = swipeRefreshState,
|
|
||||||
onRefresh = {
|
|
||||||
isRefreshing = true
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Column() {
|
Column() {
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
|
|
||||||
@@ -153,5 +147,7 @@ fun RelayFeedView(viewModel: RelayFeedViewModel, accountViewModel: AccountViewMo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PullRefreshIndicator(refreshing, pullRefreshState, Modifier.align(Alignment.TopCenter))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,6 +5,7 @@ import androidx.compose.animation.core.tween
|
|||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@@ -16,12 +17,16 @@ import androidx.compose.foundation.lazy.LazyColumn
|
|||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material.Divider
|
import androidx.compose.material.Divider
|
||||||
|
import androidx.compose.material.ExperimentalMaterialApi
|
||||||
import androidx.compose.material.Icon
|
import androidx.compose.material.Icon
|
||||||
import androidx.compose.material.IconButton
|
import androidx.compose.material.IconButton
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.MoreVert
|
import androidx.compose.material.icons.filled.MoreVert
|
||||||
|
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.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
@@ -44,8 +49,6 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import com.google.accompanist.swiperefresh.SwipeRefresh
|
|
||||||
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.service.model.BadgeDefinitionEvent
|
import com.vitorpamplona.amethyst.service.model.BadgeDefinitionEvent
|
||||||
@@ -65,42 +68,33 @@ import com.vitorpamplona.amethyst.ui.note.timeAgo
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ThreadFeedView(noteId: String, viewModel: FeedViewModel, accountViewModel: AccountViewModel, navController: NavController) {
|
fun ThreadFeedView(noteId: String, viewModel: FeedViewModel, accountViewModel: AccountViewModel, navController: NavController) {
|
||||||
val feedState by viewModel.feedContent.collectAsState()
|
val feedState by viewModel.feedContent.collectAsState()
|
||||||
|
|
||||||
var isRefreshing by remember { mutableStateOf(false) }
|
|
||||||
val swipeRefreshState = rememberSwipeRefreshState(isRefreshing)
|
|
||||||
|
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
|
|
||||||
LaunchedEffect(isRefreshing) {
|
var refreshing by remember { mutableStateOf(false) }
|
||||||
if (isRefreshing) {
|
val refresh = { refreshing = true; viewModel.refresh(); refreshing = false }
|
||||||
viewModel.refresh()
|
val pullRefreshState = rememberPullRefreshState(refreshing, onRefresh = refresh)
|
||||||
isRefreshing = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SwipeRefresh(
|
Box(Modifier.pullRefresh(pullRefreshState)) {
|
||||||
state = swipeRefreshState,
|
|
||||||
onRefresh = {
|
|
||||||
isRefreshing = true
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Column() {
|
Column() {
|
||||||
Crossfade(targetState = feedState, animationSpec = tween(durationMillis = 100)) { state ->
|
Crossfade(targetState = feedState, animationSpec = tween(durationMillis = 100)) { state ->
|
||||||
when (state) {
|
when (state) {
|
||||||
is FeedState.Empty -> {
|
is FeedState.Empty -> {
|
||||||
FeedEmpty {
|
FeedEmpty {
|
||||||
isRefreshing = true
|
refreshing = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is FeedState.FeedError -> {
|
is FeedState.FeedError -> {
|
||||||
FeedError(state.errorMessage) {
|
FeedError(state.errorMessage) {
|
||||||
isRefreshing = true
|
refreshing = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is FeedState.Loaded -> {
|
is FeedState.Loaded -> {
|
||||||
|
refreshing = false
|
||||||
LaunchedEffect(noteId) {
|
LaunchedEffect(noteId) {
|
||||||
// waits to load the thread to scroll to item.
|
// waits to load the thread to scroll to item.
|
||||||
delay(100)
|
delay(100)
|
||||||
@@ -163,6 +157,8 @@ fun ThreadFeedView(noteId: String, viewModel: FeedViewModel, accountViewModel: A
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PullRefreshIndicator(refreshing, pullRefreshState, Modifier.align(Alignment.TopCenter))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,59 +2,54 @@ package com.vitorpamplona.amethyst.ui.screen
|
|||||||
|
|
||||||
import androidx.compose.animation.Crossfade
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.material.ExperimentalMaterialApi
|
||||||
|
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.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import com.google.accompanist.swiperefresh.SwipeRefresh
|
|
||||||
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
|
||||||
import com.vitorpamplona.amethyst.ui.note.UserCompose
|
import com.vitorpamplona.amethyst.ui.note.UserCompose
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun UserFeedView(viewModel: UserFeedViewModel, accountViewModel: AccountViewModel, navController: NavController) {
|
fun UserFeedView(viewModel: UserFeedViewModel, accountViewModel: AccountViewModel, navController: NavController) {
|
||||||
val feedState by viewModel.feedContent.collectAsState()
|
val feedState by viewModel.feedContent.collectAsState()
|
||||||
|
|
||||||
var isRefreshing by remember { mutableStateOf(false) }
|
var refreshing by remember { mutableStateOf(false) }
|
||||||
val swipeRefreshState = rememberSwipeRefreshState(isRefreshing)
|
val refresh = { refreshing = true; viewModel.refresh(); refreshing = false }
|
||||||
|
val pullRefreshState = rememberPullRefreshState(refreshing, onRefresh = refresh)
|
||||||
|
|
||||||
LaunchedEffect(isRefreshing) {
|
Box(Modifier.pullRefresh(pullRefreshState)) {
|
||||||
if (isRefreshing) {
|
|
||||||
viewModel.refresh()
|
|
||||||
isRefreshing = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SwipeRefresh(
|
|
||||||
state = swipeRefreshState,
|
|
||||||
onRefresh = {
|
|
||||||
isRefreshing = true
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Column() {
|
Column() {
|
||||||
Crossfade(targetState = feedState, animationSpec = tween(durationMillis = 100)) { state ->
|
Crossfade(targetState = feedState, animationSpec = tween(durationMillis = 100)) { state ->
|
||||||
when (state) {
|
when (state) {
|
||||||
is UserFeedState.Empty -> {
|
is UserFeedState.Empty -> {
|
||||||
FeedEmpty {
|
FeedEmpty {
|
||||||
isRefreshing = true
|
refreshing = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is UserFeedState.FeedError -> {
|
is UserFeedState.FeedError -> {
|
||||||
FeedError(state.errorMessage) {
|
FeedError(state.errorMessage) {
|
||||||
isRefreshing = true
|
refreshing = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is UserFeedState.Loaded -> {
|
is UserFeedState.Loaded -> {
|
||||||
|
refreshing = false
|
||||||
FeedLoaded(state, accountViewModel, navController)
|
FeedLoaded(state, accountViewModel, navController)
|
||||||
}
|
}
|
||||||
is UserFeedState.Loading -> {
|
is UserFeedState.Loading -> {
|
||||||
@@ -63,6 +58,8 @@ fun UserFeedView(viewModel: UserFeedViewModel, accountViewModel: AccountViewMode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PullRefreshIndicator(refreshing, pullRefreshState, Modifier.align(Alignment.TopCenter))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -11,8 +11,6 @@ import androidx.compose.material.Text
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||||
@@ -20,51 +18,46 @@ import androidx.compose.ui.res.stringResource
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.LifecycleEventObserver
|
import androidx.lifecycle.LifecycleEventObserver
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import com.google.accompanist.pager.ExperimentalPagerApi
|
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||||
import com.google.accompanist.pager.HorizontalPager
|
import com.google.accompanist.pager.HorizontalPager
|
||||||
|
import com.google.accompanist.pager.PagerState
|
||||||
import com.google.accompanist.pager.pagerTabIndicatorOffset
|
import com.google.accompanist.pager.pagerTabIndicatorOffset
|
||||||
import com.google.accompanist.pager.rememberPagerState
|
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.service.NostrHomeDataSource
|
import com.vitorpamplona.amethyst.service.NostrHomeDataSource
|
||||||
import com.vitorpamplona.amethyst.ui.dal.HomeConversationsFeedFilter
|
|
||||||
import com.vitorpamplona.amethyst.ui.dal.HomeNewThreadFeedFilter
|
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.Route
|
import com.vitorpamplona.amethyst.ui.navigation.Route
|
||||||
import com.vitorpamplona.amethyst.ui.screen.FeedView
|
import com.vitorpamplona.amethyst.ui.screen.FeedView
|
||||||
import com.vitorpamplona.amethyst.ui.screen.NostrHomeFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrHomeFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.NostrHomeRepliesFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrHomeRepliesFeedViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.ScrollStateKeys
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@OptIn(ExperimentalPagerApi::class)
|
@OptIn(ExperimentalPagerApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun HomeScreen(accountViewModel: AccountViewModel, navController: NavController) {
|
fun HomeScreen(
|
||||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
accountViewModel: AccountViewModel,
|
||||||
val account = accountState?.account ?: return
|
navController: NavController,
|
||||||
|
homeFeedViewModel: NostrHomeFeedViewModel,
|
||||||
HomeNewThreadFeedFilter.account = account
|
repliesFeedViewModel: NostrHomeRepliesFeedViewModel,
|
||||||
HomeConversationsFeedFilter.account = account
|
pagerState: PagerState,
|
||||||
|
scrollToTop: Boolean = false
|
||||||
val feedViewModel: NostrHomeFeedViewModel = viewModel()
|
) {
|
||||||
val feedViewModelReplies: NostrHomeRepliesFeedViewModel = viewModel()
|
|
||||||
|
|
||||||
val pagerState = rememberPagerState()
|
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
LaunchedEffect(accountViewModel) {
|
LaunchedEffect(accountViewModel) {
|
||||||
NostrHomeDataSource.resetFilters()
|
NostrHomeDataSource.resetFilters()
|
||||||
|
|
||||||
feedViewModel.refresh()
|
homeFeedViewModel.refresh()
|
||||||
feedViewModelReplies.refresh()
|
repliesFeedViewModel.refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
val lifeCycleOwner = LocalLifecycleOwner.current
|
val lifeCycleOwner = LocalLifecycleOwner.current
|
||||||
DisposableEffect(accountViewModel) {
|
DisposableEffect(accountViewModel) {
|
||||||
val observer = LifecycleEventObserver { source, event ->
|
val observer = LifecycleEventObserver { _, event ->
|
||||||
if (event == Lifecycle.Event.ON_RESUME) {
|
if (event == Lifecycle.Event.ON_RESUME) {
|
||||||
NostrHomeDataSource.resetFilters()
|
NostrHomeDataSource.resetFilters()
|
||||||
feedViewModel.refresh()
|
homeFeedViewModel.refresh()
|
||||||
feedViewModelReplies.refresh()
|
repliesFeedViewModel.refresh()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,8 +99,8 @@ fun HomeScreen(accountViewModel: AccountViewModel, navController: NavController)
|
|||||||
}
|
}
|
||||||
HorizontalPager(count = 2, state = pagerState) {
|
HorizontalPager(count = 2, state = pagerState) {
|
||||||
when (pagerState.currentPage) {
|
when (pagerState.currentPage) {
|
||||||
0 -> FeedView(feedViewModel, accountViewModel, navController, Route.Home.route + "Follows")
|
0 -> FeedView(homeFeedViewModel, accountViewModel, navController, Route.Home.base + "Follows", ScrollStateKeys.HOME_FOLLOWS, scrollToTop)
|
||||||
1 -> FeedView(feedViewModelReplies, accountViewModel, navController, Route.Home.route + "FollowsReplies")
|
1 -> FeedView(repliesFeedViewModel, accountViewModel, navController, Route.Home.base + "FollowsReplies", ScrollStateKeys.HOME_REPLIES, scrollToTop)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -79,7 +79,7 @@ fun MainScreen(accountViewModel: AccountViewModel, accountStateViewModel: Accoun
|
|||||||
fun FloatingButton(navController: NavHostController, accountViewModel: AccountStateViewModel) {
|
fun FloatingButton(navController: NavHostController, accountViewModel: AccountStateViewModel) {
|
||||||
val accountState by accountViewModel.accountContent.collectAsState()
|
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 ->
|
Crossfade(targetState = accountState, animationSpec = tween(durationMillis = 100)) { state ->
|
||||||
when (state) {
|
when (state) {
|
||||||
is AccountState.LoggedInViewOnly -> {
|
is AccountState.LoggedInViewOnly -> {
|
||||||
@@ -95,7 +95,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 ->
|
Crossfade(targetState = accountState, animationSpec = tween(durationMillis = 100)) { state ->
|
||||||
when (state) {
|
when (state) {
|
||||||
is AccountState.LoggedInViewOnly -> {
|
is AccountState.LoggedInViewOnly -> {
|
||||||
|
@@ -47,7 +47,6 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.LifecycleEventObserver
|
import androidx.lifecycle.LifecycleEventObserver
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.RoboHashCache
|
import com.vitorpamplona.amethyst.RoboHashCache
|
||||||
@@ -58,14 +57,14 @@ import com.vitorpamplona.amethyst.model.Note
|
|||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.NostrGlobalDataSource
|
import com.vitorpamplona.amethyst.service.NostrGlobalDataSource
|
||||||
import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
|
import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
|
||||||
import com.vitorpamplona.amethyst.ui.dal.GlobalFeedFilter
|
|
||||||
import com.vitorpamplona.amethyst.ui.note.ChannelName
|
import com.vitorpamplona.amethyst.ui.note.ChannelName
|
||||||
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
||||||
import com.vitorpamplona.amethyst.ui.note.UserCompose
|
import com.vitorpamplona.amethyst.ui.note.UserCompose
|
||||||
import com.vitorpamplona.amethyst.ui.note.UserPicture
|
import com.vitorpamplona.amethyst.ui.note.UserPicture
|
||||||
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
|
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
|
||||||
import com.vitorpamplona.amethyst.ui.screen.FeedView
|
import com.vitorpamplona.amethyst.ui.screen.FeedView
|
||||||
import com.vitorpamplona.amethyst.ui.screen.NostrGlobalFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.FeedViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.ScrollStateKeys
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.FlowPreview
|
import kotlinx.coroutines.FlowPreview
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
@@ -78,12 +77,12 @@ import kotlinx.coroutines.withContext
|
|||||||
import kotlinx.coroutines.channels.Channel as CoroutineChannel
|
import kotlinx.coroutines.channels.Channel as CoroutineChannel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SearchScreen(accountViewModel: AccountViewModel, navController: NavController) {
|
fun SearchScreen(
|
||||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
accountViewModel: AccountViewModel,
|
||||||
val account = accountState?.account ?: return
|
feedViewModel: FeedViewModel,
|
||||||
|
navController: NavController,
|
||||||
GlobalFeedFilter.account = account
|
scrollToTop: Boolean = false
|
||||||
val feedViewModel: NostrGlobalFeedViewModel = viewModel()
|
) {
|
||||||
val lifeCycleOwner = LocalLifecycleOwner.current
|
val lifeCycleOwner = LocalLifecycleOwner.current
|
||||||
|
|
||||||
LaunchedEffect(accountViewModel) {
|
LaunchedEffect(accountViewModel) {
|
||||||
@@ -114,7 +113,7 @@ fun SearchScreen(accountViewModel: AccountViewModel, navController: NavControlle
|
|||||||
modifier = Modifier.padding(vertical = 0.dp)
|
modifier = Modifier.padding(vertical = 0.dp)
|
||||||
) {
|
) {
|
||||||
SearchBar(accountViewModel, navController)
|
SearchBar(accountViewModel, navController)
|
||||||
FeedView(feedViewModel, accountViewModel, navController, null)
|
FeedView(feedViewModel, accountViewModel, navController, null, ScrollStateKeys.GLOBAL_SCREEN, scrollToTop)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user