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 6034c9c00..9438cf907 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 @@ -150,7 +150,7 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun } @Composable -private fun RenderLikeGallery( +fun RenderLikeGallery( likeEvents: ImmutableList, backgroundColor: Color, nav: (String) -> Unit, @@ -182,7 +182,7 @@ private fun RenderLikeGallery( @Composable fun RenderZapGallery( - zapEvents: ImmutableMap, + zapEvents: ImmutableMap, backgroundColor: Color, nav: (String) -> Unit, accountViewModel: AccountViewModel @@ -212,7 +212,7 @@ fun RenderZapGallery( } @Composable -private fun RenderBoostGallery( +fun RenderBoostGallery( boostEvents: ImmutableList, backgroundColor: Color, nav: (String) -> Unit, @@ -249,7 +249,7 @@ private fun RenderBoostGallery( @OptIn(ExperimentalLayoutApi::class) @Composable fun AuthorGalleryZaps( - authorNotes: ImmutableMap, + authorNotes: ImmutableMap, backgroundColor: Color, nav: (String) -> Unit, accountViewModel: AccountViewModel diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt index 808e048af..a30e92832 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt @@ -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>(persistentListOf()) } + var shortRelayList by remember { mutableStateOf>(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 } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt index 5b0bb6f71..4ea95b966 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt @@ -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(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) { + 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(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(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 } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapUserSetCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapUserSetCompose.kt index 52f313b2d..b41b14ee8 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapUserSetCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapUserSetCompose.kt @@ -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(false) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt index 198b1c7ff..9d06cb3e3 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt @@ -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)