Merging all notification reactions in one card.

This commit is contained in:
Vitor Pamplona
2023-02-27 21:04:29 -05:00
parent 256de32b7c
commit a7e17c810d
4 changed files with 249 additions and 4 deletions

View File

@@ -0,0 +1,216 @@
package com.vitorpamplona.amethyst.ui.note
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
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.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.compositeOver
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.google.accompanist.flowlayout.FlowRow
import com.vitorpamplona.amethyst.NotificationCache
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
import com.vitorpamplona.amethyst.ui.screen.MultiSetCard
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun MultiSetCompose(multiSetCard: MultiSetCard, modifier: Modifier = Modifier, routeForLastRead: String, accountViewModel: AccountViewModel, navController: NavController) {
val noteState by multiSetCard.note.live().metadata.observeAsState()
val note = noteState?.note
val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account ?: return
val context = LocalContext.current.applicationContext
val noteEvent = note?.event
var popupExpanded by remember { mutableStateOf(false) }
if (note == null) {
BlankNote(Modifier, false)
} else {
var isNew by remember { mutableStateOf<Boolean>(false) }
LaunchedEffect(key1 = multiSetCard) {
isNew = multiSetCard.createdAt > NotificationCache.load(routeForLastRead, context)
NotificationCache.markAsRead(routeForLastRead, multiSetCard.createdAt, context)
}
var backgroundColor = if (isNew) {
MaterialTheme.colors.primary.copy(0.12f).compositeOver(MaterialTheme.colors.background)
} else {
MaterialTheme.colors.background
}
Column(
modifier = Modifier
.background(backgroundColor)
.combinedClickable(
onClick = {
if (noteEvent !is ChannelMessageEvent) {
navController.navigate("Note/${note.idHex}") {
launchSingleTop = true
}
} else {
note.channel?.let {
navController.navigate("Channel/${it.idHex}")
}
}
},
onLongClick = { popupExpanded = true }
)
) {
Row(modifier = Modifier
.padding(
start = 12.dp,
end = 12.dp,
top = 10.dp)
) {
Column(Modifier.fillMaxWidth()) {
if (multiSetCard.zapEvents.isNotEmpty()) {
Row(Modifier.fillMaxWidth()) {
// Draws the like picture outside the boosted card.
Box(
modifier = Modifier
.width(55.dp)
.padding(0.dp)
) {
Icon(
imageVector = Icons.Default.Bolt,
contentDescription = "Zaps",
tint = BitcoinOrange,
modifier = Modifier
.size(25.dp)
.align(Alignment.TopEnd)
)
}
Column(modifier = Modifier.padding(start = 10.dp)) {
FlowRow() {
multiSetCard.zapEvents.forEach {
NoteAuthorPicture(
note = it.key,
navController = navController,
userAccount = account.userProfile(),
size = 35.dp
)
}
}
}
}
}
if (multiSetCard.boostEvents.isNotEmpty()) {
Row(Modifier.fillMaxWidth()) {
Box(
modifier = Modifier
.width(55.dp)
.padding(end = 4.dp)
) {
Icon(
painter = painterResource(R.drawable.ic_retweeted),
null,
modifier = Modifier
.size(18.dp)
.align(Alignment.TopEnd),
tint = Color.Unspecified
)
}
Column(modifier = Modifier.padding(start = 10.dp)) {
FlowRow() {
multiSetCard.boostEvents.forEach {
NoteAuthorPicture(
note = it,
navController = navController,
userAccount = account.userProfile(),
size = 35.dp
)
}
}
}
}
}
if (multiSetCard.likeEvents.isNotEmpty()) {
Row(Modifier.fillMaxWidth()) {
Box(
modifier = Modifier
.width(55.dp)
.padding(end = 5.dp)
) {
Icon(
painter = painterResource(R.drawable.ic_liked),
null,
modifier = Modifier
.size(16.dp)
.align(Alignment.TopEnd),
tint = Color.Unspecified
)
}
Column(modifier = Modifier.padding(start = 10.dp)) {
FlowRow() {
multiSetCard.likeEvents.forEach {
NoteAuthorPicture(
note = it,
navController = navController,
userAccount = account.userProfile(),
size = 35.dp
)
}
}
}
}
}
Row(Modifier.fillMaxWidth()) {
Box(modifier = Modifier
.width(55.dp)
.padding(0.dp)) {
}
NoteCompose(
baseNote = note,
routeForLastRead = null,
modifier = Modifier.padding(top = 5.dp),
isBoostedNote = true,
parentBackgroundColor = backgroundColor,
accountViewModel = accountViewModel,
navController = navController
)
NoteDropDownMenu(note, popupExpanded, { popupExpanded = false }, accountViewModel)
}
}
}
}
}
}

View File

@@ -32,6 +32,19 @@ class ZapSetCard(val note: Note, val zapEvents: Map<Note, Note>): Card() {
override fun id() = note.idHex + "Z" + createdAt
}
class MultiSetCard(val note: Note, val boostEvents: List<Note>, val likeEvents: List<Note>, val zapEvents: Map<Note, Note>): Card() {
val createdAt = maxOf(
zapEvents.maxOfOrNull { it.value.event?.createdAt ?: 0 } ?: 0 ,
likeEvents.maxOfOrNull { it.event?.createdAt ?: 0 } ?: 0 ,
boostEvents.maxOfOrNull { it.event?.createdAt ?: 0 } ?: 0
)
override fun createdAt(): Long {
return createdAt
}
override fun id() = note.idHex + "X" + createdAt
}
class BoostSetCard(val note: Note, val boostEvents: List<Note>): Card() {
val createdAt = boostEvents.maxOf { it.event?.createdAt ?: 0 }

View File

@@ -20,6 +20,7 @@ import com.google.accompanist.swiperefresh.SwipeRefresh
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
import com.vitorpamplona.amethyst.ui.note.BoostSetCompose
import com.vitorpamplona.amethyst.ui.note.LikeSetCompose
import com.vitorpamplona.amethyst.ui.note.MultiSetCompose
import com.vitorpamplona.amethyst.ui.note.NoteCompose
import com.vitorpamplona.amethyst.ui.note.ZapSetCompose
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
@@ -120,6 +121,12 @@ private fun FeedLoaded(
navController = navController,
routeForLastRead = routeForLastRead
)
is MultiSetCard -> MultiSetCompose(
item,
accountViewModel = accountViewModel,
navController = navController,
routeForLastRead = routeForLastRead
)
}
}
}

View File

@@ -69,7 +69,7 @@ open class CardFeedViewModel(val dataSource: FeedFilter<Note>): ViewModel() {
reactionsPerEvent.getOrPut(reactedPost, { mutableListOf() }).add(it)
}
val reactionCards = reactionsPerEvent.map { LikeSetCard(it.key, it.value) }
//val reactionCards = reactionsPerEvent.map { LikeSetCard(it.key, it.value) }
val zapsPerEvent = mutableMapOf<Note, MutableMap<Note, Note>>()
notes
@@ -84,7 +84,7 @@ open class CardFeedViewModel(val dataSource: FeedFilter<Note>): ViewModel() {
}
}
val zapCards = zapsPerEvent.map { ZapSetCard(it.key, it.value) }
//val zapCards = zapsPerEvent.map { ZapSetCard(it.key, it.value) }
val boostsPerEvent = mutableMapOf<Note, MutableList<Note>>()
notes
@@ -95,11 +95,20 @@ open class CardFeedViewModel(val dataSource: FeedFilter<Note>): ViewModel() {
boostsPerEvent.getOrPut(boostedPost, { mutableListOf() }).add(it)
}
val boostCards = boostsPerEvent.map { BoostSetCard(it.key, it.value) }
//val boostCards = boostsPerEvent.map { BoostSetCard(it.key, it.value) }
val allBaseNotes = zapsPerEvent.keys + boostsPerEvent.keys + reactionsPerEvent.keys
val multiCards = allBaseNotes.map {
MultiSetCard(it,
boostsPerEvent.get(it) ?: emptyList(),
reactionsPerEvent.get(it) ?: emptyList(),
zapsPerEvent.get(it) ?: emptyMap()
)
}
val textNoteCards = notes.filter { it.event !is ReactionEvent && it.event !is RepostEvent && it.event !is LnZapEvent }.map { NoteCard(it) }
return (reactionCards + boostCards + zapCards + textNoteCards).sortedBy { it.createdAt() }.reversed()
return (multiCards + textNoteCards).sortedBy { it.createdAt() }.reversed()
}
private fun updateFeed(notes: List<Card>) {