Support to per-post display reactions

This commit is contained in:
Vitor Pamplona
2023-06-07 18:50:29 -04:00
parent 705309cb8a
commit 6906993cc3
5 changed files with 208 additions and 138 deletions
@@ -150,7 +150,7 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun
} }
@Composable @Composable
private fun RenderLikeGallery( fun RenderLikeGallery(
likeEvents: ImmutableList<Note>, likeEvents: ImmutableList<Note>,
backgroundColor: Color, backgroundColor: Color,
nav: (String) -> Unit, nav: (String) -> Unit,
@@ -182,7 +182,7 @@ private fun RenderLikeGallery(
@Composable @Composable
fun RenderZapGallery( fun RenderZapGallery(
zapEvents: ImmutableMap<Note, Note>, zapEvents: ImmutableMap<Note, Note?>,
backgroundColor: Color, backgroundColor: Color,
nav: (String) -> Unit, nav: (String) -> Unit,
accountViewModel: AccountViewModel accountViewModel: AccountViewModel
@@ -212,7 +212,7 @@ fun RenderZapGallery(
} }
@Composable @Composable
private fun RenderBoostGallery( fun RenderBoostGallery(
boostEvents: ImmutableList<Note>, boostEvents: ImmutableList<Note>,
backgroundColor: Color, backgroundColor: Color,
nav: (String) -> Unit, nav: (String) -> Unit,
@@ -249,7 +249,7 @@ private fun RenderBoostGallery(
@OptIn(ExperimentalLayoutApi::class) @OptIn(ExperimentalLayoutApi::class)
@Composable @Composable
fun AuthorGalleryZaps( fun AuthorGalleryZaps(
authorNotes: ImmutableMap<Note, Note>, authorNotes: ImmutableMap<Note, Note?>,
backgroundColor: Color, backgroundColor: Color,
nav: (String) -> Unit, nav: (String) -> Unit,
accountViewModel: AccountViewModel accountViewModel: AccountViewModel
@@ -138,6 +138,7 @@ import com.vitorpamplona.amethyst.ui.components.ZoomableUrlImage
import com.vitorpamplona.amethyst.ui.components.ZoomableUrlVideo import com.vitorpamplona.amethyst.ui.components.ZoomableUrlVideo
import com.vitorpamplona.amethyst.ui.components.figureOutMimeType import com.vitorpamplona.amethyst.ui.components.figureOutMimeType
import com.vitorpamplona.amethyst.ui.components.imageExtensions import com.vitorpamplona.amethyst.ui.components.imageExtensions
import com.vitorpamplona.amethyst.ui.screen.equalImmutableLists
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChannelHeader import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChannelHeader
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ReportNoteDialog import com.vitorpamplona.amethyst.ui.screen.loggedIn.ReportNoteDialog
@@ -560,6 +561,15 @@ fun NormalNote(
NoteQuickActionMenu(baseNote, popupExpanded, { popupExpanded = false }, accountViewModel) NoteQuickActionMenu(baseNote, popupExpanded, { popupExpanded = false }, accountViewModel)
} }
} }
if (!makeItShort) {
ReactionsRow(baseNote, !isBoostedNote && !isQuotedNote, accountViewModel, nav)
}
Divider(
modifier = Modifier.padding(top = 10.dp),
thickness = 0.25.dp
)
} }
} }
} }
@@ -626,15 +636,6 @@ private fun RenderTextEvent(
DisplayUncitedHashtags(hashtags, eventContent, nav) DisplayUncitedHashtags(hashtags, eventContent, nav)
} }
} }
if (!makeItShort) {
ReactionsRow(note, accountViewModel, nav)
}
Divider(
modifier = Modifier.padding(top = 10.dp),
thickness = 0.25.dp
)
} }
@Composable @Composable
@@ -687,15 +688,6 @@ private fun RenderPoll(
var hashtags = remember { noteEvent.hashtags().toImmutableList() } var hashtags = remember { noteEvent.hashtags().toImmutableList() }
DisplayUncitedHashtags(hashtags, eventContent, nav) DisplayUncitedHashtags(hashtags, eventContent, nav)
} }
if (!makeItShort) {
ReactionsRow(note, accountViewModel, nav)
}
Divider(
modifier = Modifier.padding(top = 10.dp),
thickness = 0.25.dp
)
} }
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@@ -877,15 +869,6 @@ private fun RenderHighlight(
accountViewModel, accountViewModel,
nav nav
) )
if (!makeItShort) {
ReactionsRow(note, accountViewModel, nav)
}
Divider(
modifier = Modifier.padding(top = 10.dp),
thickness = 0.25.dp
)
} }
@Composable @Composable
@@ -955,15 +938,6 @@ private fun RenderPrivateMessage(
nav nav
) )
} }
if (!makeItShort) {
ReactionsRow(note, accountViewModel, nav)
}
Divider(
modifier = Modifier.padding(top = 10.dp),
thickness = 0.25.dp
)
} }
@Composable @Composable
@@ -974,13 +948,6 @@ fun RelaySetList(
nav: (String) -> Unit nav: (String) -> Unit
) { ) {
DisplayRelaySet(baseNote, backgroundColor, accountViewModel, nav) DisplayRelaySet(baseNote, backgroundColor, accountViewModel, nav)
ReactionsRow(baseNote, accountViewModel, nav)
Divider(
modifier = Modifier.padding(top = 10.dp),
thickness = 0.25.dp
)
} }
@Composable @Composable
@@ -991,13 +958,6 @@ fun RenderPeopleList(
nav: (String) -> Unit nav: (String) -> Unit
) { ) {
DisplayPeopleList(baseNote, backgroundColor, accountViewModel, nav) DisplayPeopleList(baseNote, backgroundColor, accountViewModel, nav)
ReactionsRow(baseNote, accountViewModel, nav)
Divider(
modifier = Modifier.padding(top = 10.dp),
thickness = 0.25.dp
)
} }
@Composable @Composable
@@ -1282,13 +1242,6 @@ private fun RenderBadgeAward(
nav = nav nav = nav
) )
} }
ReactionsRow(note, accountViewModel, nav)
Divider(
modifier = Modifier.padding(top = 10.dp),
thickness = 0.25.dp
)
} }
@Composable @Composable
@@ -1350,16 +1303,7 @@ private fun RenderPinListEvent(
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
nav: (String) -> Unit nav: (String) -> Unit
) { ) {
val noteEvent = baseNote.event as? PinListEvent ?: return
PinListHeader(baseNote, backgroundColor, accountViewModel, nav) PinListHeader(baseNote, backgroundColor, accountViewModel, nav)
ReactionsRow(baseNote, accountViewModel, nav)
Divider(
modifier = Modifier.padding(top = 10.dp),
thickness = 0.25.dp
)
} }
@OptIn(ExperimentalLayoutApi::class) @OptIn(ExperimentalLayoutApi::class)
@@ -1462,13 +1406,6 @@ private fun RenderAudioTrack(
val noteEvent = note.event as? AudioTrackEvent ?: return val noteEvent = note.event as? AudioTrackEvent ?: return
AudioTrackHeader(noteEvent, accountViewModel, nav) AudioTrackHeader(noteEvent, accountViewModel, nav)
ReactionsRow(note, accountViewModel, nav)
Divider(
modifier = Modifier.padding(top = 10.dp),
thickness = 0.25.dp
)
} }
@Composable @Composable
@@ -1480,13 +1417,6 @@ private fun RenderLongFormContent(
val noteEvent = note.event as? LongTextNoteEvent ?: return val noteEvent = note.event as? LongTextNoteEvent ?: return
LongFormHeader(noteEvent, note, accountViewModel) LongFormHeader(noteEvent, note, accountViewModel)
ReactionsRow(note, accountViewModel, nav)
Divider(
modifier = Modifier.padding(top = 10.dp),
thickness = 0.25.dp
)
} }
@Composable @Composable
@@ -1547,13 +1477,6 @@ private fun RenderReport(
nav = nav nav = nav
) )
} }
ReactionsRow(note, accountViewModel, nav)
Divider(
modifier = Modifier.padding(top = 10.dp),
thickness = 0.25.dp
)
} }
@Composable @Composable
@@ -2458,17 +2381,17 @@ private fun CreateImageHeader(
private fun RelayBadges(baseNote: Note) { private fun RelayBadges(baseNote: Note) {
var expanded by remember { mutableStateOf(false) } var expanded by remember { mutableStateOf(false) }
var showShowMore by remember { mutableStateOf(false) } var showShowMore by remember { mutableStateOf(false) }
var lazyRelayList by remember { mutableStateOf<ImmutableList<String>>(persistentListOf()) } var lazyRelayList by remember { mutableStateOf<ImmutableList<String>>(persistentListOf()) }
var shortRelayList by remember { mutableStateOf<ImmutableList<String>>(persistentListOf()) }
WatchRelayLists(baseNote) { relayList -> WatchRelayLists(baseNote) { relayList ->
val relaysToDisplay = if (expanded) relayList else relayList.take(3) if (!equalImmutableLists(relayList, lazyRelayList)) {
val shouldListChange = lazyRelayList.size < 3 || lazyRelayList.size != relayList.size lazyRelayList = relayList.toImmutableList()
shortRelayList = relayList.take(3).toImmutableList()
if (shouldListChange) {
lazyRelayList = relaysToDisplay.toImmutableList()
} }
val nextShowMore = relayList.size > 3 && !expanded val nextShowMore = relayList.size > 3
if (nextShowMore != showShowMore) { if (nextShowMore != showShowMore) {
// only triggers recomposition when actually different // only triggers recomposition when actually different
showShowMore = nextShowMore showShowMore = nextShowMore
@@ -2477,9 +2400,13 @@ private fun RelayBadges(baseNote: Note) {
Spacer(Modifier.height(10.dp)) Spacer(Modifier.height(10.dp))
VerticalRelayPanelWithFlow(lazyRelayList) if (expanded) {
VerticalRelayPanelWithFlow(lazyRelayList)
} else {
VerticalRelayPanelWithFlow(shortRelayList)
}
if (showShowMore) { if (showShowMore && !expanded) {
ShowMoreRelaysButton { ShowMoreRelaysButton {
expanded = true expanded = true
} }
@@ -7,6 +7,7 @@ import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
@@ -26,11 +27,14 @@ import androidx.compose.material.ProgressIndicatorDefaults
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.Bolt import androidx.compose.material.icons.filled.Bolt
import androidx.compose.material.icons.filled.ExpandLess
import androidx.compose.material.icons.filled.ExpandMore
import androidx.compose.material.icons.outlined.BarChart import androidx.compose.material.icons.outlined.BarChart
import androidx.compose.material.icons.outlined.Bolt import androidx.compose.material.icons.outlined.Bolt
import androidx.compose.material.ripple.rememberRipple import androidx.compose.material.ripple.rememberRipple
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.derivedStateOf import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.livedata.observeAsState
@@ -63,23 +67,188 @@ import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.ui.actions.NewPostView import com.vitorpamplona.amethyst.ui.actions.NewPostView
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toImmutableMap
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.math.BigDecimal import java.math.BigDecimal
import java.math.RoundingMode import java.math.RoundingMode
import kotlin.math.abs
import kotlin.math.roundToInt import kotlin.math.roundToInt
@Composable @Composable
fun ReactionsRow(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) { fun ReactionsRow(baseNote: Note, showReactions: Boolean, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
val grayTint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) val grayTint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
var wantsToReplyTo by remember { var wantsToSeeReactions = remember {
mutableStateOf<Boolean>(false)
}
Spacer(modifier = Modifier.height(8.dp))
Row(verticalAlignment = CenterVertically, modifier = Modifier.padding(start = 10.dp)) {
if (showReactions) {
Row(
verticalAlignment = CenterVertically,
modifier = Modifier
.width(65.dp)
.padding(start = 31.dp),
horizontalArrangement = Arrangement.Start
) {
ExpandButton(baseNote, wantsToSeeReactions)
}
}
Row(verticalAlignment = CenterVertically, modifier = remember { Modifier.weight(1f) }) {
ReplyReactionWithDialog(accountViewModel, nav, baseNote, grayTint)
}
Row(verticalAlignment = CenterVertically, modifier = remember { Modifier.weight(1f) }) {
BoostWithDialog(accountViewModel, nav, baseNote, grayTint)
}
Row(verticalAlignment = CenterVertically, modifier = remember { Modifier.weight(1f) }) {
LikeReaction(baseNote, grayTint, accountViewModel)
}
Row(verticalAlignment = CenterVertically, modifier = remember { Modifier.weight(1f) }) {
ZapReaction(baseNote, grayTint, accountViewModel)
}
Row(verticalAlignment = CenterVertically, modifier = remember { Modifier.weight(1f) }) {
ViewCountReaction(baseNote.idHex, grayTint)
}
}
if (showReactions && wantsToSeeReactions.value) {
ReactionDetailGallery(baseNote, nav, accountViewModel)
}
}
@Composable
private fun ExpandButton(baseNote: Note, wantsToSeeReactions: MutableState<Boolean>) {
val zapsState by baseNote.live().zaps.observeAsState()
val boostsState by baseNote.live().boosts.observeAsState()
val reactionsState by baseNote.live().reactions.observeAsState()
val hasReactions by remember(zapsState, boostsState, reactionsState) {
derivedStateOf {
baseNote.zaps.isNotEmpty() ||
baseNote.boosts.isNotEmpty() ||
baseNote.reactions.isNotEmpty()
}
}
if (hasReactions) {
IconButton(
onClick = {
wantsToSeeReactions.value = !wantsToSeeReactions.value
},
modifier = Modifier.size(20.dp)
) {
if (wantsToSeeReactions.value) {
Icon(
imageVector = Icons.Default.ExpandLess,
null,
modifier = Modifier.size(22.dp),
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.22f)
)
} else {
Icon(
imageVector = Icons.Default.ExpandMore,
null,
modifier = Modifier.size(22.dp),
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.22f)
)
}
}
}
}
@Composable
private fun ReactionDetailGallery(
baseNote: Note,
nav: (String) -> Unit,
accountViewModel: AccountViewModel
) {
val zapsState by baseNote.live().zaps.observeAsState()
val boostsState by baseNote.live().boosts.observeAsState()
val reactionsState by baseNote.live().reactions.observeAsState()
val hasReactions by remember(zapsState, boostsState, reactionsState) {
derivedStateOf {
baseNote.zaps.isNotEmpty() ||
baseNote.boosts.isNotEmpty() ||
baseNote.reactions.isNotEmpty()
}
}
if (hasReactions) {
Row(verticalAlignment = CenterVertically, modifier = Modifier.padding(start = 10.dp, top = 5.dp)) {
Column() {
val zapEvents by remember(zapsState) { derivedStateOf { baseNote.zaps.toImmutableMap() } }
val boostEvents by remember(boostsState) { derivedStateOf { baseNote.boosts.toImmutableList() } }
val likeEvents by remember(reactionsState) { derivedStateOf { baseNote.reactions.toImmutableList() } }
val hasZapEvents by remember(zapsState) { derivedStateOf { baseNote.zaps.isNotEmpty() } }
val hasBoostEvents by remember(boostsState) { derivedStateOf { baseNote.boosts.isNotEmpty() } }
val hasLikeEvents by remember(reactionsState) { derivedStateOf { baseNote.reactions.isNotEmpty() } }
if (hasZapEvents) {
RenderZapGallery(
zapEvents,
MaterialTheme.colors.background,
nav,
accountViewModel
)
}
if (hasBoostEvents) {
RenderBoostGallery(
boostEvents,
MaterialTheme.colors.background,
nav,
accountViewModel
)
}
if (hasLikeEvents) {
RenderLikeGallery(
likeEvents,
MaterialTheme.colors.background,
nav,
accountViewModel
)
}
}
}
}
}
@Composable
private fun BoostWithDialog(
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
baseNote: Note,
grayTint: Color
) {
var wantsToQuote by remember {
mutableStateOf<Note?>(null) mutableStateOf<Note?>(null)
} }
var wantsToQuote by remember { if (wantsToQuote != null) {
NewPostView({ wantsToQuote = null }, null, wantsToQuote, accountViewModel, nav)
}
BoostReaction(baseNote, grayTint, accountViewModel) {
wantsToQuote = baseNote
}
}
@Composable
private fun ReplyReactionWithDialog(
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
baseNote: Note,
grayTint: Color
) {
var wantsToReplyTo by remember {
mutableStateOf<Note?>(null) mutableStateOf<Note?>(null)
} }
@@ -87,32 +256,8 @@ fun ReactionsRow(baseNote: Note, accountViewModel: AccountViewModel, nav: (Strin
NewPostView({ wantsToReplyTo = null }, wantsToReplyTo, null, accountViewModel, nav) NewPostView({ wantsToReplyTo = null }, wantsToReplyTo, null, accountViewModel, nav)
} }
if (wantsToQuote != null) { ReplyReaction(baseNote, grayTint, accountViewModel) {
NewPostView({ wantsToQuote = null }, null, wantsToQuote, accountViewModel, nav) wantsToReplyTo = baseNote
}
Spacer(modifier = Modifier.height(8.dp))
Row(verticalAlignment = CenterVertically) {
Row(verticalAlignment = CenterVertically, modifier = remember { Modifier.weight(1f) }) {
ReplyReaction(baseNote, grayTint, accountViewModel) {
wantsToReplyTo = baseNote
}
}
Row(verticalAlignment = CenterVertically, modifier = Modifier.weight(1f)) {
BoostReaction(baseNote, grayTint, accountViewModel) {
wantsToQuote = baseNote
}
}
Row(verticalAlignment = CenterVertically, modifier = Modifier.weight(1f)) {
LikeReaction(baseNote, grayTint, accountViewModel)
}
Row(verticalAlignment = CenterVertically, modifier = Modifier.weight(1f)) {
ZapReaction(baseNote, grayTint, accountViewModel)
}
Row(verticalAlignment = CenterVertically, modifier = Modifier.weight(1f)) {
ViewCountReaction(baseNote.idHex, grayTint)
}
} }
} }
@@ -4,7 +4,6 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
@@ -34,7 +33,6 @@ import com.vitorpamplona.amethyst.ui.theme.newItemBackgroundColor
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@OptIn(ExperimentalLayoutApi::class)
@Composable @Composable
fun ZapUserSetCompose(zapSetCard: ZapUserSetCard, isInnerNote: Boolean = false, routeForLastRead: String, accountViewModel: AccountViewModel, nav: (String) -> Unit) { fun ZapUserSetCompose(zapSetCard: ZapUserSetCard, isInnerNote: Boolean = false, routeForLastRead: String, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
var isNew by remember { mutableStateOf<Boolean>(false) } var isNew by remember { mutableStateOf<Boolean>(false) }
@@ -437,15 +437,15 @@ fun NoteMaster(
} }
} }
} }
ReactionsRow(note, accountViewModel, nav)
Divider(
modifier = Modifier.padding(top = 10.dp),
thickness = 0.25.dp
)
} }
} }
ReactionsRow(note, true, accountViewModel, nav)
Divider(
modifier = Modifier.padding(top = 10.dp),
thickness = 0.25.dp
)
} }
NoteQuickActionMenu(note, popupExpanded, { popupExpanded = false }, accountViewModel) NoteQuickActionMenu(note, popupExpanded, { popupExpanded = false }, accountViewModel)