diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt index 339afcf93..7b847cdc7 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt @@ -19,7 +19,6 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Bolt import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.State import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState @@ -39,11 +38,9 @@ import androidx.compose.ui.unit.sp import com.google.accompanist.flowlayout.FlowRow import com.vitorpamplona.amethyst.NotificationCache import com.vitorpamplona.amethyst.R -import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User -import com.vitorpamplona.amethyst.model.UserState import com.vitorpamplona.amethyst.service.model.LnZapEvent import com.vitorpamplona.amethyst.service.model.LnZapRequestEvent import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer @@ -55,7 +52,6 @@ import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableMap import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import java.math.BigDecimal @OptIn(ExperimentalFoundationApi::class) @Composable @@ -66,7 +62,7 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun val note = remember(noteState) { noteState?.note } ?: return val accountState by accountViewModel.accountLiveData.observeAsState() - val account = remember(accountState) { accountState?.account } ?: return + val loggedIn = remember(accountState) { accountState?.account?.userProfile() } ?: return var popupExpanded by remember { mutableStateOf(false) } @@ -92,55 +88,48 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun val primaryColor = MaterialTheme.colors.primary.copy(0.12f) val defaultBackgroundColor = MaterialTheme.colors.background - val backgroundColor by remember(isNew) { - derivedStateOf { - if (isNew) { - primaryColor.compositeOver(defaultBackgroundColor) - } else { - defaultBackgroundColor - } - } + val backgroundColor = if (isNew) { + primaryColor.compositeOver(defaultBackgroundColor) + } else { + defaultBackgroundColor } - val columnModifier by remember(isNew, backgroundColor) { - derivedStateOf { - Modifier - .background(backgroundColor) - .padding( - start = 12.dp, - end = 12.dp, - top = 10.dp - ) - .combinedClickable( - onClick = { - scope.launch { - routeFor( - baseNote, - account.userProfile() - )?.let { nav(it) } - } - }, - onLongClick = { popupExpanded = true } - ) - .fillMaxWidth() - } - } + val columnModifier = Modifier + .background(backgroundColor) + .padding( + start = 12.dp, + end = 12.dp, + top = 10.dp + ) + .combinedClickable( + onClick = { + scope.launch { + routeFor(baseNote, loggedIn)?.let { nav(it) } + } + }, + onLongClick = { popupExpanded = true } + ) + .fillMaxWidth() - val zapEvents = remember { multiSetCard.zapEvents } - val boostEvents = remember { multiSetCard.boostEvents } - val likeEvents = remember { multiSetCard.likeEvents } + val zapEvents by remember { derivedStateOf { multiSetCard.zapEvents } } + val boostEvents by remember { derivedStateOf { multiSetCard.boostEvents } } + val likeEvents by remember { derivedStateOf { multiSetCard.likeEvents } } + + val hasZapEvents by remember { derivedStateOf { multiSetCard.zapEvents.isNotEmpty() } } + val hasBoostEvents by remember { derivedStateOf { multiSetCard.boostEvents.isNotEmpty() } } + val hasLikeEvents by remember { derivedStateOf { multiSetCard.likeEvents.isNotEmpty() } } Column(modifier = columnModifier) { - if (zapEvents.isNotEmpty()) { - RenderZapGallery(zapEvents, backgroundColor, nav, account, accountViewModel) + if (hasZapEvents) { + RenderZapGallery(zapEvents, backgroundColor, nav, accountViewModel) } - if (boostEvents.isNotEmpty()) { - RenderBoostGallery(boostEvents, backgroundColor, nav, account, accountViewModel) + if (hasBoostEvents) { + RenderBoostGallery(boostEvents, backgroundColor, nav, accountViewModel) } - if (likeEvents.isNotEmpty()) { - RenderLikeGallery(likeEvents, backgroundColor, nav, account, accountViewModel) + if (hasLikeEvents) { + RenderLikeGallery(likeEvents, backgroundColor, nav, accountViewModel) } Row(Modifier.fillMaxWidth()) { @@ -167,7 +156,6 @@ private fun RenderLikeGallery( likeEvents: ImmutableList, backgroundColor: Color, nav: (String) -> Unit, - account: Account, accountViewModel: AccountViewModel ) { Row(Modifier.fillMaxWidth()) { @@ -190,7 +178,7 @@ private fun RenderLikeGallery( ) } - AuthorGallery(likeEvents, backgroundColor, nav, account, accountViewModel) + AuthorGallery(likeEvents, backgroundColor, nav, accountViewModel) } } @@ -199,7 +187,6 @@ private fun RenderZapGallery( zapEvents: ImmutableMap, backgroundColor: Color, nav: (String) -> Unit, - account: Account, accountViewModel: AccountViewModel ) { Row(Modifier.fillMaxWidth()) { @@ -222,7 +209,7 @@ private fun RenderZapGallery( ) } - AuthorGalleryZaps(zapEvents, backgroundColor, nav, account, accountViewModel) + AuthorGalleryZaps(zapEvents, backgroundColor, nav, accountViewModel) } } @@ -231,7 +218,6 @@ private fun RenderBoostGallery( boostEvents: ImmutableList, backgroundColor: Color, nav: (String) -> Unit, - account: Account, accountViewModel: AccountViewModel ) { Row( @@ -258,7 +244,7 @@ private fun RenderBoostGallery( ) } - AuthorGallery(boostEvents, backgroundColor, nav, account, accountViewModel) + AuthorGallery(boostEvents, backgroundColor, nav, accountViewModel) } } @@ -267,15 +253,14 @@ fun AuthorGalleryZaps( authorNotes: ImmutableMap, backgroundColor: Color, nav: (String) -> Unit, - account: Account, accountViewModel: AccountViewModel ) { - val accountState = account.userProfile().live().follows.observeAsState() - Column(modifier = Modifier.padding(start = 10.dp)) { FlowRow() { authorNotes.forEach { - AuthorPictureAndComment(it.key, it.value, backgroundColor, nav, accountState, accountViewModel) + Box() { + AuthorPictureAndComment(it.key, it.value, backgroundColor, nav, accountViewModel) + } } } } @@ -287,55 +272,45 @@ private fun AuthorPictureAndComment( zapEvent: Note?, backgroundColor: Color, nav: (String) -> Unit, - accountUser: State, accountViewModel: AccountViewModel ) { - val author = zapRequest.author ?: return - - var content by remember { mutableStateOf>(Triple(author, null, null)) } + var content by remember { mutableStateOf>(Triple(zapRequest.author, null, null)) } val scope = rememberCoroutineScope() LaunchedEffect(key1 = zapRequest.idHex, key2 = zapEvent?.idHex) { - scope.launch(Dispatchers.IO) { + scope.launch(Dispatchers.Default) { (zapRequest.event as? LnZapRequestEvent)?.let { val decryptedContent = accountViewModel.decryptZap(zapRequest) val amount = (zapEvent?.event as? LnZapEvent)?.amount if (decryptedContent != null) { val newAuthor = LocalCache.getOrCreateUser(decryptedContent.pubKey) - content = Triple(newAuthor, decryptedContent.content.ifBlank { null }, amount) + content = Triple(newAuthor, decryptedContent.content.ifBlank { null }, showAmountAxis(amount)) } else { if (!zapRequest.event?.content().isNullOrBlank() || amount != null) { - content = Triple(author, zapRequest.event?.content()?.ifBlank { null }, amount) + content = Triple(zapRequest.author, zapRequest.event?.content()?.ifBlank { null }, showAmountAxis(amount)) } } } } } - val modifier = remember(content.second) { - if (!content.second.isNullOrBlank()) { - Modifier - .fillMaxWidth() - .clickable { - nav("User/${author.pubkeyHex}") - } - } else { - Modifier.clickable { - nav("User/${author.pubkeyHex}") + content.first?.let { + val route by remember { + derivedStateOf { + "User/${it.pubkeyHex}" } } - } - AuthorPictureAndComment( - author = content.first, - comment = content.second, - amount = showAmountAxis(content.third), - backgroundColor = backgroundColor, - nav = nav, - accountUser = accountUser, - accountViewModel = accountViewModel, - modifier = modifier - ) + AuthorPictureAndComment( + author = it, + comment = content.second, + amount = content.third, + route = route, + backgroundColor = backgroundColor, + nav = nav, + accountViewModel = accountViewModel + ) + } } @Composable @@ -343,12 +318,27 @@ private fun AuthorPictureAndComment( author: User, comment: String?, amount: String?, + route: String, backgroundColor: Color, nav: (String) -> Unit, - accountUser: State, - accountViewModel: AccountViewModel, - modifier: Modifier + accountViewModel: AccountViewModel ) { + val authorPictureModifier = remember { Modifier } + + val modifier = remember(comment) { + if (comment != null) { + Modifier + .fillMaxWidth() + .clickable { + nav(route) + } + } else { + Modifier.clickable { + nav(route) + } + } + } + Row( modifier = modifier, verticalAlignment = Alignment.CenterVertically @@ -356,8 +346,9 @@ private fun AuthorPictureAndComment( Box(modifier = remember { Modifier.size(35.dp) }, contentAlignment = Alignment.BottomCenter) { FastNoteAuthorPicture( author = author, - userAccount = accountUser, - size = 35.dp + size = 35.dp, + accountViewModel = accountViewModel, + pictureModifier = authorPictureModifier ) amount?.let { @@ -397,34 +388,47 @@ fun AuthorGallery( authorNotes: ImmutableList, backgroundColor: Color, nav: (String) -> Unit, - account: Account, accountViewModel: AccountViewModel ) { - val accountState = account.userProfile().live().follows.observeAsState() - Column(modifier = Modifier.padding(start = 10.dp)) { FlowRow() { authorNotes.forEach { note -> - note.author?.let { user -> - val modifier = remember { - Modifier.clickable { - nav("User/${user.pubkeyHex}") - } - } - - AuthorPictureAndComment(user, null, null, backgroundColor, nav, accountState, accountViewModel, modifier) + Box(Modifier.size(35.dp)) { + NotePictureAndComment(note, backgroundColor, nav, accountViewModel) } } } } } +@Composable +private fun NotePictureAndComment( + baseNote: Note, + backgroundColor: Color, + nav: (String) -> Unit, + accountViewModel: AccountViewModel +) { + val author by remember(baseNote) { + derivedStateOf { + baseNote.author + } + } + + val route by remember(baseNote) { + derivedStateOf { + "User/${baseNote.author?.pubkeyHex}" + } + } + + author?.let { AuthorPictureAndComment(it, null, null, route, backgroundColor, nav, accountViewModel) } +} + @Composable fun FastNoteAuthorPicture( author: User, - userAccount: State, size: Dp, - pictureModifier: Modifier = Modifier + pictureModifier: Modifier = Modifier, + accountViewModel: AccountViewModel ) { val userState by author.live().metadata.observeAsState() val profilePicture = remember(userState) { @@ -435,8 +439,15 @@ fun FastNoteAuthorPicture( author.pubkeyHex } - val showFollowingMark = remember(userAccount.value) { - userAccount.value?.user?.isFollowingCached(author) == true || (author === userAccount.value?.user) + val accountState by accountViewModel.accountLiveData.observeAsState() + val loggedInLiveFollows = remember(accountState) { accountState?.account?.userProfile()?.live()?.follows } ?: return + + val accountFollowsState by loggedInLiveFollows.observeAsState() + + val showFollowingMark by remember(accountFollowsState) { + derivedStateOf { + accountFollowsState?.user?.isFollowingCached(author) == true || (author === accountFollowsState?.user) + } } UserPicture( diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt index fe85aaa2d..7bd6b749f 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt @@ -5,7 +5,12 @@ import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.ExperimentalMaterialApi @@ -48,9 +53,14 @@ fun CardFeedView( val refresh = { refreshing = true; viewModel.invalidateData(); refreshing = false } val pullRefreshState = rememberPullRefreshState(refreshing, onRefresh = refresh) - Box(Modifier.pullRefresh(pullRefreshState)) { - Column() { - Crossfade(targetState = feedState, animationSpec = tween(durationMillis = 100)) { state -> + Box(Modifier.fillMaxSize().pullRefresh(pullRefreshState)) { + Column(modifier = Modifier.fillMaxSize()) { + Crossfade( + modifier = Modifier.fillMaxSize(), + targetState = feedState, + animationSpec = tween(durationMillis = 100) + ) { state -> + when (state) { is CardFeedState.Empty -> { FeedEmpty { @@ -110,68 +120,91 @@ private fun FeedLoaded( } } + LazyNotificationList(listState, state, routeForLastRead, accountViewModel, nav) +} + +@Composable +private fun LazyNotificationList( + listState: LazyListState, + state: CardFeedState.Loaded, + routeForLastRead: String, + accountViewModel: AccountViewModel, + nav: (String) -> Unit +) { LazyColumn( - contentPadding = PaddingValues( - top = 10.dp, - bottom = 10.dp - ), + modifier = remember { Modifier.fillMaxSize() }, + contentPadding = remember { + PaddingValues( + top = 10.dp, + bottom = 10.dp + ) + }, state = listState ) { itemsIndexed(state.feed.value, key = { _, item -> item.id() }) { _, item -> - when (item) { - is NoteCard -> NoteCompose( - item.note, - isBoostedNote = false, - accountViewModel = accountViewModel, - nav = nav, - routeForLastRead = routeForLastRead - ) - is ZapSetCard -> ZapSetCompose( - item, - isInnerNote = false, - accountViewModel = accountViewModel, - nav = nav, - routeForLastRead = routeForLastRead - ) - is ZapUserSetCard -> ZapUserSetCompose( - item, - isInnerNote = false, - accountViewModel = accountViewModel, - nav = nav, - routeForLastRead = routeForLastRead - ) - is LikeSetCard -> LikeSetCompose( - item, - isInnerNote = false, - accountViewModel = accountViewModel, - nav = nav, - routeForLastRead = routeForLastRead - ) - is BoostSetCard -> BoostSetCompose( - item, - isInnerNote = false, - accountViewModel = accountViewModel, - nav = nav, - routeForLastRead = routeForLastRead - ) - is MultiSetCard -> MultiSetCompose( - item, - accountViewModel = accountViewModel, - nav = nav, - routeForLastRead = routeForLastRead - ) - is BadgeCard -> BadgeCompose( - item, - accountViewModel = accountViewModel, - nav = nav, - routeForLastRead = routeForLastRead - ) - is MessageSetCard -> MessageSetCompose( - messageSetCard = item, - routeForLastRead = routeForLastRead, - accountViewModel = accountViewModel, - nav = nav - ) + Row(Modifier.fillMaxWidth().defaultMinSize(minHeight = 100.dp)) { + when (item) { + is NoteCard -> NoteCompose( + item.note, + isBoostedNote = false, + accountViewModel = accountViewModel, + nav = nav, + routeForLastRead = routeForLastRead + ) + + is ZapSetCard -> ZapSetCompose( + item, + isInnerNote = false, + accountViewModel = accountViewModel, + nav = nav, + routeForLastRead = routeForLastRead + ) + + is ZapUserSetCard -> ZapUserSetCompose( + item, + isInnerNote = false, + accountViewModel = accountViewModel, + nav = nav, + routeForLastRead = routeForLastRead + ) + + is LikeSetCard -> LikeSetCompose( + item, + isInnerNote = false, + accountViewModel = accountViewModel, + nav = nav, + routeForLastRead = routeForLastRead + ) + + is BoostSetCard -> BoostSetCompose( + item, + isInnerNote = false, + accountViewModel = accountViewModel, + nav = nav, + routeForLastRead = routeForLastRead + ) + + is MultiSetCard -> MultiSetCompose( + item, + accountViewModel = accountViewModel, + nav = nav, + routeForLastRead = routeForLastRead + ) + + is BadgeCard -> BadgeCompose( + item, + accountViewModel = accountViewModel, + nav = nav, + routeForLastRead = routeForLastRead + ) + + is MessageSetCard -> MessageSetCompose( + messageSetCard = item, + routeForLastRead = routeForLastRead, + accountViewModel = accountViewModel, + nav = nav + ) + } } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt index e37ba81c9..90089e97c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt @@ -13,6 +13,7 @@ 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.Modifier import androidx.compose.ui.graphics.Brush @@ -51,6 +52,8 @@ import com.vitorpamplona.amethyst.ui.screen.CardFeedView import com.vitorpamplona.amethyst.ui.screen.NotificationViewModel import com.vitorpamplona.amethyst.ui.screen.ScrollStateKeys import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import java.math.BigDecimal import java.math.RoundingMode import kotlin.math.roundToInt @@ -64,10 +67,16 @@ fun NotificationScreen( scrollToTop: Boolean = false ) { val accountState by accountViewModel.accountLiveData.observeAsState() - val account = accountState?.account ?: return + val account = remember(accountState) { accountState?.account } ?: return if (scrollToTop) { - notifFeedViewModel.clear() + val scope = rememberCoroutineScope() + LaunchedEffect(key1 = Unit) { + scope.launch(Dispatchers.IO) { + notifFeedViewModel.clear() + notifFeedViewModel.invalidateData(true) + } + } } LaunchedEffect(accountViewModel, account.defaultNotificationFollowList) { @@ -94,7 +103,9 @@ fun NotificationScreen( Column( modifier = Modifier.padding(vertical = 0.dp) ) { - SummaryBar(userReactionsStatsModel, accountViewModel, nav) + SummaryBar( + model = userReactionsStatsModel + ) CardFeedView( viewModel = notifFeedViewModel, accountViewModel = accountViewModel, @@ -108,12 +119,12 @@ fun NotificationScreen( } @Composable -fun SummaryBar(model: UserReactionsViewModel, accountViewModel: AccountViewModel, nav: (String) -> Unit) { +fun SummaryBar(model: UserReactionsViewModel) { var showChart by remember { mutableStateOf(false) } - UserReactionsRow(model, accountViewModel, nav) { + UserReactionsRow(model) { showChart = !showChart } @@ -153,8 +164,8 @@ fun SummaryBar(model: UserReactionsViewModel, accountViewModel: AccountViewModel targetVerticalAxisPosition = AxisPosition.Vertical.End ) - model.chartModel?.let { - if (showChart) { + if (showChart) { + model.chartModel?.let { Row( modifier = Modifier .padding(vertical = 10.dp, horizontal = 20.dp)