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

View File

@ -150,7 +150,7 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun
}
@Composable
private fun RenderLikeGallery(
fun RenderLikeGallery(
likeEvents: ImmutableList<Note>,
backgroundColor: Color,
nav: (String) -> Unit,
@ -182,7 +182,7 @@ private fun RenderLikeGallery(
@Composable
fun RenderZapGallery(
zapEvents: ImmutableMap<Note, Note>,
zapEvents: ImmutableMap<Note, Note?>,
backgroundColor: Color,
nav: (String) -> Unit,
accountViewModel: AccountViewModel
@ -212,7 +212,7 @@ fun RenderZapGallery(
}
@Composable
private fun RenderBoostGallery(
fun RenderBoostGallery(
boostEvents: ImmutableList<Note>,
backgroundColor: Color,
nav: (String) -> Unit,
@ -249,7 +249,7 @@ private fun RenderBoostGallery(
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun AuthorGalleryZaps(
authorNotes: ImmutableMap<Note, Note>,
authorNotes: ImmutableMap<Note, Note?>,
backgroundColor: Color,
nav: (String) -> Unit,
accountViewModel: AccountViewModel

View File

@ -138,6 +138,7 @@ import com.vitorpamplona.amethyst.ui.components.ZoomableUrlImage
import com.vitorpamplona.amethyst.ui.components.ZoomableUrlVideo
import com.vitorpamplona.amethyst.ui.components.figureOutMimeType
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.ChannelHeader
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ReportNoteDialog
@ -560,6 +561,15 @@ fun NormalNote(
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)
}
}
if (!makeItShort) {
ReactionsRow(note, accountViewModel, nav)
}
Divider(
modifier = Modifier.padding(top = 10.dp),
thickness = 0.25.dp
)
}
@Composable
@ -687,15 +688,6 @@ private fun RenderPoll(
var hashtags = remember { noteEvent.hashtags().toImmutableList() }
DisplayUncitedHashtags(hashtags, eventContent, nav)
}
if (!makeItShort) {
ReactionsRow(note, accountViewModel, nav)
}
Divider(
modifier = Modifier.padding(top = 10.dp),
thickness = 0.25.dp
)
}
@OptIn(ExperimentalFoundationApi::class)
@ -877,15 +869,6 @@ private fun RenderHighlight(
accountViewModel,
nav
)
if (!makeItShort) {
ReactionsRow(note, accountViewModel, nav)
}
Divider(
modifier = Modifier.padding(top = 10.dp),
thickness = 0.25.dp
)
}
@Composable
@ -955,15 +938,6 @@ private fun RenderPrivateMessage(
nav
)
}
if (!makeItShort) {
ReactionsRow(note, accountViewModel, nav)
}
Divider(
modifier = Modifier.padding(top = 10.dp),
thickness = 0.25.dp
)
}
@Composable
@ -974,13 +948,6 @@ fun RelaySetList(
nav: (String) -> Unit
) {
DisplayRelaySet(baseNote, backgroundColor, accountViewModel, nav)
ReactionsRow(baseNote, accountViewModel, nav)
Divider(
modifier = Modifier.padding(top = 10.dp),
thickness = 0.25.dp
)
}
@Composable
@ -991,13 +958,6 @@ fun RenderPeopleList(
nav: (String) -> Unit
) {
DisplayPeopleList(baseNote, backgroundColor, accountViewModel, nav)
ReactionsRow(baseNote, accountViewModel, nav)
Divider(
modifier = Modifier.padding(top = 10.dp),
thickness = 0.25.dp
)
}
@Composable
@ -1282,13 +1242,6 @@ private fun RenderBadgeAward(
nav = nav
)
}
ReactionsRow(note, accountViewModel, nav)
Divider(
modifier = Modifier.padding(top = 10.dp),
thickness = 0.25.dp
)
}
@Composable
@ -1350,16 +1303,7 @@ private fun RenderPinListEvent(
accountViewModel: AccountViewModel,
nav: (String) -> Unit
) {
val noteEvent = baseNote.event as? PinListEvent ?: return
PinListHeader(baseNote, backgroundColor, accountViewModel, nav)
ReactionsRow(baseNote, accountViewModel, nav)
Divider(
modifier = Modifier.padding(top = 10.dp),
thickness = 0.25.dp
)
}
@OptIn(ExperimentalLayoutApi::class)
@ -1462,13 +1406,6 @@ private fun RenderAudioTrack(
val noteEvent = note.event as? AudioTrackEvent ?: return
AudioTrackHeader(noteEvent, accountViewModel, nav)
ReactionsRow(note, accountViewModel, nav)
Divider(
modifier = Modifier.padding(top = 10.dp),
thickness = 0.25.dp
)
}
@Composable
@ -1480,13 +1417,6 @@ private fun RenderLongFormContent(
val noteEvent = note.event as? LongTextNoteEvent ?: return
LongFormHeader(noteEvent, note, accountViewModel)
ReactionsRow(note, accountViewModel, nav)
Divider(
modifier = Modifier.padding(top = 10.dp),
thickness = 0.25.dp
)
}
@Composable
@ -1547,13 +1477,6 @@ private fun RenderReport(
nav = nav
)
}
ReactionsRow(note, accountViewModel, nav)
Divider(
modifier = Modifier.padding(top = 10.dp),
thickness = 0.25.dp
)
}
@Composable
@ -2458,17 +2381,17 @@ private fun CreateImageHeader(
private fun RelayBadges(baseNote: Note) {
var expanded by remember { mutableStateOf(false) }
var showShowMore by remember { mutableStateOf(false) }
var lazyRelayList by remember { mutableStateOf<ImmutableList<String>>(persistentListOf()) }
var shortRelayList by remember { mutableStateOf<ImmutableList<String>>(persistentListOf()) }
WatchRelayLists(baseNote) { relayList ->
val relaysToDisplay = if (expanded) relayList else relayList.take(3)
val shouldListChange = lazyRelayList.size < 3 || lazyRelayList.size != relayList.size
if (shouldListChange) {
lazyRelayList = relaysToDisplay.toImmutableList()
if (!equalImmutableLists(relayList, lazyRelayList)) {
lazyRelayList = relayList.toImmutableList()
shortRelayList = relayList.take(3).toImmutableList()
}
val nextShowMore = relayList.size > 3 && !expanded
val nextShowMore = relayList.size > 3
if (nextShowMore != showShowMore) {
// only triggers recomposition when actually different
showShowMore = nextShowMore
@ -2477,9 +2400,13 @@ private fun RelayBadges(baseNote: Note) {
Spacer(Modifier.height(10.dp))
VerticalRelayPanelWithFlow(lazyRelayList)
if (expanded) {
VerticalRelayPanelWithFlow(lazyRelayList)
} else {
VerticalRelayPanelWithFlow(shortRelayList)
}
if (showShowMore) {
if (showShowMore && !expanded) {
ShowMoreRelaysButton {
expanded = true
}

View File

@ -7,6 +7,7 @@ import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row
@ -26,11 +27,14 @@ import androidx.compose.material.ProgressIndicatorDefaults
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
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.Bolt
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
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.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toImmutableMap
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.math.BigDecimal
import java.math.RoundingMode
import kotlin.math.abs
import kotlin.math.roundToInt
@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)
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)
}
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)
}
@ -87,32 +256,8 @@ fun ReactionsRow(baseNote: Note, accountViewModel: AccountViewModel, nav: (Strin
NewPostView({ wantsToReplyTo = null }, wantsToReplyTo, null, accountViewModel, nav)
}
if (wantsToQuote != null) {
NewPostView({ wantsToQuote = null }, null, wantsToQuote, accountViewModel, nav)
}
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)
}
ReplyReaction(baseNote, grayTint, accountViewModel) {
wantsToReplyTo = baseNote
}
}

View File

@ -4,7 +4,6 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
@ -34,7 +33,6 @@ import com.vitorpamplona.amethyst.ui.theme.newItemBackgroundColor
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun ZapUserSetCompose(zapSetCard: ZapUserSetCard, isInnerNote: Boolean = false, routeForLastRead: String, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
var isNew by remember { mutableStateOf<Boolean>(false) }

View File

@ -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)