diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomListFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomListFeedView.kt index ef4a9c386..6a54db451 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomListFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomListFeedView.kt @@ -9,21 +9,30 @@ import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue +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.setValue +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle 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.ui.note.ChatroomCompose import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel @Composable -fun ChatroomListFeedView(viewModel: FeedViewModel, accountViewModel: AccountViewModel, navController: NavController) { +fun ChatroomListFeedView( + viewModel: FeedViewModel, + accountViewModel: AccountViewModel, + navController: NavController, + markAsRead: MutableState +) { val feedState by viewModel.feedContent.collectAsStateWithLifecycle() var isRefreshing by remember { mutableStateOf(false) } @@ -45,21 +54,27 @@ fun ChatroomListFeedView(viewModel: FeedViewModel, accountViewModel: AccountView }, ) { Column() { - Crossfade(targetState = feedState, animationSpec = tween(durationMillis = 100)) { state -> + Crossfade( + targetState = feedState, + animationSpec = tween(durationMillis = 100) + ) { state -> when (state) { is FeedState.Empty -> { FeedEmpty { isRefreshing = true } } + is FeedState.FeedError -> { FeedError(state.errorMessage) { isRefreshing = true } } + is FeedState.Loaded -> { - FeedLoaded(state, accountViewModel, navController) + FeedLoaded(state, accountViewModel, navController, markAsRead) } + FeedState.Loading -> { LoadingFeed() } @@ -73,10 +88,43 @@ fun ChatroomListFeedView(viewModel: FeedViewModel, accountViewModel: AccountView private fun FeedLoaded( state: FeedState.Loaded, accountViewModel: AccountViewModel, - navController: NavController + navController: NavController, + markAsRead: MutableState, ) { 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( contentPadding = PaddingValues( top = 10.dp, @@ -84,7 +132,9 @@ private fun FeedLoaded( ), 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( item, accountViewModel = accountViewModel, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt index 66e702358..42687c2d4 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt @@ -113,7 +113,4 @@ class AccountViewModel(private val account: Account): ViewModel() { account.prefer(source, target, preference) } - - - } \ No newline at end of file diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomListScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomListScreen.kt index 4572dabd9..387df78ce 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomListScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomListScreen.kt @@ -1,31 +1,42 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize 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.Tab import androidx.compose.material.TabRow import androidx.compose.material.TabRowDefaults 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.DisposableEffect import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue 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.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.HorizontalPager import com.google.accompanist.pager.pagerTabIndicatorOffset import com.google.accompanist.pager.rememberPagerState +import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.service.NostrChatroomListDataSource import com.vitorpamplona.amethyst.ui.dal.ChatroomListKnownFeedFilter 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.NostrChatroomListNewFeedViewModel import kotlinx.coroutines.launch -import com.vitorpamplona.amethyst.R @OptIn(ExperimentalPagerApi::class) @Composable @@ -41,48 +51,78 @@ fun ChatroomListScreen(accountViewModel: AccountViewModel, navController: NavCon val pagerState = rememberPagerState() val coroutineScope = rememberCoroutineScope() - Column(Modifier.fillMaxHeight()) { - Column( - modifier = Modifier.padding(vertical = 0.dp) - ) { - 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)) - } - ) + var moreActionsExpanded by remember { mutableStateOf(false) } + val markKnownAsRead = remember { mutableStateOf(false) } + val markNewAsRead = remember { mutableStateOf(false) } - Tab( - selected = pagerState.currentPage == 1, - onClick = { coroutineScope.launch { pagerState.animateScrollToPage(1) } }, - text = { - Text(text = stringResource(R.string.new_requests)) + Box(Modifier.fillMaxSize()) { + Column(Modifier.fillMaxHeight()) { + Column( + modifier = Modifier.padding(vertical = 0.dp) + ) { + 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 -fun TabKnown(accountViewModel: AccountViewModel, navController: NavController) { +fun TabKnown( + accountViewModel: AccountViewModel, + navController: NavController, + markAsRead: MutableState, +) { val accountState by accountViewModel.accountLiveData.observeAsState() val account = accountState?.account ?: return @@ -98,13 +138,17 @@ fun TabKnown(accountViewModel: AccountViewModel, navController: NavController) { Column( modifier = Modifier.padding(vertical = 0.dp) ) { - ChatroomListFeedView(feedViewModel, accountViewModel, navController) + ChatroomListFeedView(feedViewModel, accountViewModel, navController, markAsRead) } } } @Composable -fun TabNew(accountViewModel: AccountViewModel, navController: NavController) { +fun TabNew( + accountViewModel: AccountViewModel, + navController: NavController, + markAsRead: MutableState +) { val accountState by accountViewModel.accountLiveData.observeAsState() val account = accountState?.account ?: return @@ -120,7 +164,37 @@ fun TabNew(accountViewModel: AccountViewModel, navController: NavController) { Column( modifier = Modifier.padding(vertical = 0.dp) ) { - ChatroomListFeedView(feedViewModel, accountViewModel, navController) + ChatroomListFeedView(feedViewModel, accountViewModel, navController, markAsRead) } } -} \ No newline at end of file +} + +@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)) + } + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 952cd426c..84cf1d090 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -177,4 +177,7 @@ Report Hateful speech Report Nudity / Porn others + Mark all Known as read + Mark all New as read + Mark all as read \ No newline at end of file