Add buttons to mark DMs and chatrooms as read #172

This commit is contained in:
maxmoney21m
2023-03-03 02:24:13 +08:00
parent 0da542fede
commit 8d7897601b
4 changed files with 174 additions and 50 deletions
@@ -9,21 +9,30 @@ import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState 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.MutableState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
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.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
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.SwipeRefresh
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
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
@Composable @Composable
fun ChatroomListFeedView(viewModel: FeedViewModel, accountViewModel: AccountViewModel, navController: NavController) { fun ChatroomListFeedView(
viewModel: FeedViewModel,
accountViewModel: AccountViewModel,
navController: NavController,
markAsRead: MutableState<Boolean>
) {
val feedState by viewModel.feedContent.collectAsStateWithLifecycle() val feedState by viewModel.feedContent.collectAsStateWithLifecycle()
var isRefreshing by remember { mutableStateOf(false) } var isRefreshing by remember { mutableStateOf(false) }
@@ -45,21 +54,27 @@ fun ChatroomListFeedView(viewModel: FeedViewModel, accountViewModel: AccountView
}, },
) { ) {
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 isRefreshing = true
} }
} }
is FeedState.FeedError -> { is FeedState.FeedError -> {
FeedError(state.errorMessage) { FeedError(state.errorMessage) {
isRefreshing = true isRefreshing = true
} }
} }
is FeedState.Loaded -> { is FeedState.Loaded -> {
FeedLoaded(state, accountViewModel, navController) FeedLoaded(state, accountViewModel, navController, markAsRead)
} }
FeedState.Loading -> { FeedState.Loading -> {
LoadingFeed() LoadingFeed()
} }
@@ -73,10 +88,43 @@ fun ChatroomListFeedView(viewModel: FeedViewModel, accountViewModel: AccountView
private fun FeedLoaded( private fun FeedLoaded(
state: FeedState.Loaded, state: FeedState.Loaded,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController navController: NavController,
markAsRead: MutableState<Boolean>,
) { ) {
val listState = rememberLazyListState() val listState = rememberLazyListState()
val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account ?: return
val notificationCacheState = NotificationCache.live.observeAsState()
val notificationCache = notificationCacheState.value ?: return
val context = LocalContext.current.applicationContext
if (markAsRead.value) {
LaunchedEffect(key1 = notificationCache) {
for (note in state.feed.value) {
note.event?.let {
var route = ""
if (note.channel != null) {
route = "Channel/${note.channel!!.idHex}"
} else {
val replyAuthorBase = note.mentions?.first()
var userToComposeOn = note.author!!
if (replyAuthorBase != null) {
if (note.author == account.userProfile()) {
userToComposeOn = replyAuthorBase
}
}
route = "Room/${userToComposeOn.pubkeyHex}"
}
notificationCache.cache.markAsRead(route, it.createdAt, context)
}
}
markAsRead.value = false
}
}
LazyColumn( LazyColumn(
contentPadding = PaddingValues( contentPadding = PaddingValues(
top = 10.dp, top = 10.dp,
@@ -84,7 +132,9 @@ private fun FeedLoaded(
), ),
state = listState state = listState
) { ) {
itemsIndexed(state.feed.value, key = { index, item -> if (index == 0) index else item.idHex }) { index, item -> itemsIndexed(
state.feed.value,
key = { index, item -> if (index == 0) index else item.idHex }) { index, item ->
ChatroomCompose( ChatroomCompose(
item, item,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
@@ -113,7 +113,4 @@ class AccountViewModel(private val account: Account): ViewModel() {
account.prefer(source, target, preference) account.prefer(source, target, preference)
} }
} }
@@ -1,31 +1,42 @@
package com.vitorpamplona.amethyst.ui.screen.loggedIn package com.vitorpamplona.amethyst.ui.screen.loggedIn
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.DropdownMenu
import androidx.compose.material.DropdownMenuItem
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
import androidx.compose.material.Tab import androidx.compose.material.Tab
import androidx.compose.material.TabRow import androidx.compose.material.TabRow
import androidx.compose.material.TabRowDefaults import androidx.compose.material.TabRowDefaults
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
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.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLifecycleOwner
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.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.viewmodel.compose.viewModel 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.pagerTabIndicatorOffset import com.google.accompanist.pager.pagerTabIndicatorOffset
import com.google.accompanist.pager.rememberPagerState import com.google.accompanist.pager.rememberPagerState
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.service.NostrChatroomListDataSource import com.vitorpamplona.amethyst.service.NostrChatroomListDataSource
import com.vitorpamplona.amethyst.ui.dal.ChatroomListKnownFeedFilter import com.vitorpamplona.amethyst.ui.dal.ChatroomListKnownFeedFilter
import com.vitorpamplona.amethyst.ui.dal.ChatroomListNewFeedFilter import com.vitorpamplona.amethyst.ui.dal.ChatroomListNewFeedFilter
@@ -33,7 +44,6 @@ import com.vitorpamplona.amethyst.ui.screen.ChatroomListFeedView
import com.vitorpamplona.amethyst.ui.screen.NostrChatroomListKnownFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrChatroomListKnownFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.NostrChatroomListNewFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrChatroomListNewFeedViewModel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import com.vitorpamplona.amethyst.R
@OptIn(ExperimentalPagerApi::class) @OptIn(ExperimentalPagerApi::class)
@Composable @Composable
@@ -41,48 +51,78 @@ fun ChatroomListScreen(accountViewModel: AccountViewModel, navController: NavCon
val pagerState = rememberPagerState() val pagerState = rememberPagerState()
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
Column(Modifier.fillMaxHeight()) { var moreActionsExpanded by remember { mutableStateOf(false) }
Column( val markKnownAsRead = remember { mutableStateOf(false) }
modifier = Modifier.padding(vertical = 0.dp) val markNewAsRead = remember { mutableStateOf(false) }
) {
TabRow(
backgroundColor = MaterialTheme.colors.background,
selectedTabIndex = pagerState.currentPage,
indicator = { tabPositions ->
TabRowDefaults.Indicator(
Modifier.pagerTabIndicatorOffset(pagerState, tabPositions),
color = MaterialTheme.colors.primary
)
},
) {
Tab(
selected = pagerState.currentPage == 0,
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(0) } },
text = {
Text(text = stringResource(R.string.known))
}
)
Tab( Box(Modifier.fillMaxSize()) {
selected = pagerState.currentPage == 1, Column(Modifier.fillMaxHeight()) {
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(1) } }, Column(
text = { modifier = Modifier.padding(vertical = 0.dp)
Text(text = stringResource(R.string.new_requests)) ) {
TabRow(
backgroundColor = MaterialTheme.colors.background,
selectedTabIndex = pagerState.currentPage,
indicator = { tabPositions ->
TabRowDefaults.Indicator(
Modifier.pagerTabIndicatorOffset(pagerState, tabPositions),
color = MaterialTheme.colors.primary
)
},
) {
Tab(
selected = pagerState.currentPage == 0,
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(0) } },
text = {
Text(text = stringResource(R.string.known))
}
)
Tab(
selected = pagerState.currentPage == 1,
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(1) } },
text = {
Text(text = stringResource(R.string.new_requests))
}
)
}
HorizontalPager(count = 2, state = pagerState) {
when (pagerState.currentPage) {
0 -> TabKnown(accountViewModel, navController, markKnownAsRead,)
1 -> TabNew(accountViewModel, navController, markNewAsRead)
} }
)
}
HorizontalPager(count = 2, state = pagerState) {
when (pagerState.currentPage) {
0 -> TabKnown(accountViewModel, navController)
1 -> TabNew(accountViewModel, navController)
} }
} }
} }
IconButton(
modifier = Modifier
.padding(0.dp)
.size(30.dp)
.align(Alignment.TopEnd),
onClick = { moreActionsExpanded = true }
) {
Icon(
imageVector = Icons.Default.MoreVert,
contentDescription = null,
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
)
ChatroomTabMenu(
moreActionsExpanded,
{ moreActionsExpanded = false },
{ markKnownAsRead.value = true },
{ markNewAsRead.value = true },
)
}
} }
} }
@Composable @Composable
fun TabKnown(accountViewModel: AccountViewModel, navController: NavController) { fun TabKnown(
accountViewModel: AccountViewModel,
navController: NavController,
markAsRead: MutableState<Boolean>,
) {
val accountState by accountViewModel.accountLiveData.observeAsState() val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account ?: return val account = accountState?.account ?: return
@@ -98,13 +138,17 @@ fun TabKnown(accountViewModel: AccountViewModel, navController: NavController) {
Column( Column(
modifier = Modifier.padding(vertical = 0.dp) modifier = Modifier.padding(vertical = 0.dp)
) { ) {
ChatroomListFeedView(feedViewModel, accountViewModel, navController) ChatroomListFeedView(feedViewModel, accountViewModel, navController, markAsRead)
} }
} }
} }
@Composable @Composable
fun TabNew(accountViewModel: AccountViewModel, navController: NavController) { fun TabNew(
accountViewModel: AccountViewModel,
navController: NavController,
markAsRead: MutableState<Boolean>
) {
val accountState by accountViewModel.accountLiveData.observeAsState() val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account ?: return val account = accountState?.account ?: return
@@ -120,7 +164,37 @@ fun TabNew(accountViewModel: AccountViewModel, navController: NavController) {
Column( Column(
modifier = Modifier.padding(vertical = 0.dp) modifier = Modifier.padding(vertical = 0.dp)
) { ) {
ChatroomListFeedView(feedViewModel, accountViewModel, navController) ChatroomListFeedView(feedViewModel, accountViewModel, navController, markAsRead)
}
}
}
@Composable
fun ChatroomTabMenu(
expanded: Boolean,
onDismiss: () -> Unit,
onMarkKnownAsRead: () -> Unit,
onMarkNewAsRead: () -> Unit,
) {
DropdownMenu(expanded = expanded, onDismissRequest = onDismiss) {
DropdownMenuItem(onClick = {
onMarkKnownAsRead()
onDismiss()
}) {
Text(stringResource(R.string.mark_all_known_as_read))
}
DropdownMenuItem(onClick = {
onMarkNewAsRead()
onDismiss()
}) {
Text(stringResource(R.string.mark_all_new_as_read))
}
DropdownMenuItem(onClick = {
onMarkKnownAsRead()
onMarkNewAsRead()
onDismiss()
}) {
Text(stringResource(R.string.mark_all_as_read))
} }
} }
} }
+3
View File
@@ -177,4 +177,7 @@
<string name="report_hateful_speech">Report Hateful speech</string> <string name="report_hateful_speech">Report Hateful speech</string>
<string name="report_nudity_porn">Report Nudity / Porn</string> <string name="report_nudity_porn">Report Nudity / Porn</string>
<string name="others">others</string> <string name="others">others</string>
<string name="mark_all_known_as_read">Mark all Known as read</string>
<string name="mark_all_new_as_read">Mark all New as read</string>
<string name="mark_all_as_read">Mark all as read</string>
</resources> </resources>