From dcf825c43ec7749a3f83c571eed53e7cf546de97 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Fri, 7 Jul 2023 17:13:20 -0400 Subject: [PATCH] Slightly faster reactions and zap icons --- .../com/vitorpamplona/amethyst/model/Note.kt | 12 +- .../amethyst/ui/note/ReactionsRow.kt | 144 +++++++++++------- .../ui/screen/loggedIn/ChannelScreen.kt | 2 +- 3 files changed, 99 insertions(+), 59 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt index 57e050070..cbc421b71 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt @@ -328,10 +328,14 @@ open class Note(val idHex: String) { }.toSet() } - fun isReactedBy(user: User): String? { - return reactions.filter { - it.value.any { it.author?.pubkeyHex == user.pubkeyHex } - }.keys.firstOrNull() + fun getReactionBy(user: User): String? { + return reactions.firstNotNullOfOrNull { + if (it.value.any { it.author?.pubkeyHex == user.pubkeyHex }) { + it.key + } else { + null + } + } } fun isBoostedBy(user: User): Boolean { 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 94bde9f2b..0aa5ec64c 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 @@ -308,7 +308,9 @@ fun RenderZapRaiser(baseNote: Note, zapraiserAmount: Long, details: Boolean, acc } LinearProgressIndicator( - modifier = Modifier.fillMaxWidth().height(if (details) 24.dp else 4.dp), + modifier = Modifier + .fillMaxWidth() + .height(if (details) 24.dp else 4.dp), color = color, progress = zapraiserProgress ) @@ -558,12 +560,17 @@ fun ReplyCounter(baseNote: Note, textColor: Color) { it.note.replies.size }.observeAsState(baseNote.replies.size) - SlidingAnimation(repliesState, textColor) + SlidingAnimationCount(repliesState, textColor) +} + +@Composable +private fun SlidingAnimationCount(baseCount: MutableState, textColor: Color) { + SlidingAnimationCount(baseCount.value, textColor) } @OptIn(ExperimentalAnimationApi::class) @Composable -private fun SlidingAnimation(baseCount: Int, textColor: Color) { +private fun SlidingAnimationCount(baseCount: Int, textColor: Color) { AnimatedContent( targetState = baseCount, transitionSpec = AnimatedContentScope::transitionSpec @@ -597,9 +604,9 @@ private fun TextCount(count: Int, textColor: Color) { @Composable @OptIn(ExperimentalAnimationApi::class) -private fun SlidingAnimation(amount: String, textColor: Color) { +private fun SlidingAnimationAmount(amount: MutableState, textColor: Color) { AnimatedContent( - targetState = amount, + targetState = amount.value, transitionSpec = AnimatedContentScope::transitionSpec ) { count -> Text( @@ -694,7 +701,7 @@ fun BoostText(baseNote: Note, grayTint: Color) { it.note.boosts.size }.distinctUntilChanged().observeAsState(baseNote.boosts.size) - SlidingAnimation(boostState, grayTint) + SlidingAnimationCount(boostState, grayTint) } @OptIn(ExperimentalFoundationApi::class) @@ -771,32 +778,41 @@ fun LikeIcon( grayTint: Color, accountViewModel: AccountViewModel ) { - val reactionsState by baseNote.live().reactions.observeAsState() - - var reactionType by remember(baseNote) { + val reactionType = remember(baseNote) { mutableStateOf(null) } - LaunchedEffect(key1 = reactionsState) { - launch(Dispatchers.Default) { - val newReactionType = reactionsState?.note?.isReactedBy(accountViewModel.userProfile())?.firstFullChar() - if (reactionType != newReactionType) { - launch(Dispatchers.Main) { - reactionType = newReactionType - } + val scope = rememberCoroutineScope() + + WatchReactionTypeForNote(baseNote, accountViewModel) { newReactionType -> + if (reactionType.value != newReactionType) { + scope.launch(Dispatchers.Main) { + reactionType.value = newReactionType } } } Crossfade(targetState = reactionType) { - if (it != null) { - RenderReactionType(it, iconSize, iconFontSize) + val value = it.value + if (value != null) { + RenderReactionType(value, iconSize, iconFontSize) } else { RenderLikeIcon(iconSize, grayTint) } } } +@Composable +private fun WatchReactionTypeForNote(baseNote: Note, accountViewModel: AccountViewModel, onNewReactionType: (String?) -> Unit) { + val reactionsState by baseNote.live().reactions.observeAsState() + + LaunchedEffect(key1 = reactionsState) { + launch(Dispatchers.Default) { + onNewReactionType(reactionsState?.note?.getReactionBy(accountViewModel.userProfile())?.firstFullChar()) + } + } +} + @Composable private fun RenderLikeIcon( iconSize: Dp = Size20dp, @@ -841,24 +857,32 @@ private fun RenderReactionType( @Composable fun LikeText(baseNote: Note, grayTint: Color) { - val reactionsState by baseNote.live().reactions.observeAsState() - - var reactionsCount by remember(baseNote) { + val reactionsCount = remember(baseNote) { mutableStateOf(baseNote.reactions.size) } - LaunchedEffect(key1 = reactionsState) { - launch(Dispatchers.Default) { - val newReactionsCount = reactionsState?.note?.countReactions() ?: 0 - if (reactionsCount != newReactionsCount) { - launch(Dispatchers.Main) { - reactionsCount = newReactionsCount - } + val scope = rememberCoroutineScope() + + WatchReactionCountForNote(baseNote) { newReactionsCount -> + if (reactionsCount.value != newReactionsCount) { + scope.launch(Dispatchers.Main) { + reactionsCount.value = newReactionsCount } } } - SlidingAnimation(reactionsCount, grayTint) + SlidingAnimationCount(reactionsCount, grayTint) +} + +@Composable +private fun WatchReactionCountForNote(baseNote: Note, onNewReactionCount: (Int) -> Unit) { + val reactionsState by baseNote.live().reactions.observeAsState() + + LaunchedEffect(key1 = reactionsState) { + launch(Dispatchers.Default) { + onNewReactionCount(reactionsState?.note?.countReactions() ?: 0) + } + } } private fun likeClick( @@ -1072,27 +1096,22 @@ private fun ZapIcon( grayTint: Color, accountViewModel: AccountViewModel ) { - var wasZappedByLoggedInUser by remember { mutableStateOf(false) } - val zapsState by baseNote.live().zaps.observeAsState() + val wasZappedByLoggedInUser = remember { mutableStateOf(false) } - LaunchedEffect(key1 = zapsState) { - launch(Dispatchers.Default) { - zapsState?.note?.let { - if (!wasZappedByLoggedInUser) { - val newWasZapped = accountViewModel.calculateIfNoteWasZappedByAccount(it) + val scope = rememberCoroutineScope() - if (wasZappedByLoggedInUser != newWasZapped) { - launch(Dispatchers.Main) { - wasZappedByLoggedInUser = newWasZapped - } - } + if (!wasZappedByLoggedInUser.value) { + WatchZapsForNote(baseNote, accountViewModel) { newWasZapped -> + if (wasZappedByLoggedInUser.value != newWasZapped) { + scope.launch(Dispatchers.Main) { + wasZappedByLoggedInUser.value = newWasZapped } } } } Crossfade(targetState = wasZappedByLoggedInUser) { - if (it) { + if (it.value) { Icon( imageVector = Icons.Default.Bolt, contentDescription = stringResource(R.string.zaps), @@ -1110,30 +1129,47 @@ private fun ZapIcon( } } +@Composable +private fun WatchZapsForNote(baseNote: Note, accountViewModel: AccountViewModel, onWasZapped: (Boolean) -> Unit) { + val zapsState by baseNote.live().zaps.observeAsState() + + LaunchedEffect(key1 = zapsState) { + launch(Dispatchers.Default) { + onWasZapped(accountViewModel.calculateIfNoteWasZappedByAccount(baseNote)) + } + } +} + @Composable private fun ZapAmountText( baseNote: Note, grayTint: Color, accountViewModel: AccountViewModel ) { - val zapsState by baseNote.live().zaps.observeAsState() + val zapAmountTxt = remember(baseNote) { mutableStateOf("") } - var zapAmountTxt by remember { mutableStateOf("") } + val scope = rememberCoroutineScope() - LaunchedEffect(key1 = zapsState) { - launch(Dispatchers.Default) { - zapsState?.note?.let { - val newZapAmount = showAmount(accountViewModel.calculateZapAmount(it)) - if (newZapAmount != zapAmountTxt) { - launch(Dispatchers.Main) { - zapAmountTxt = newZapAmount - } - } + WatchZapAmountsForNote(baseNote, accountViewModel) { newZapAmount -> + if (zapAmountTxt.value != newZapAmount) { + scope.launch(Dispatchers.Main) { + zapAmountTxt.value = newZapAmount } } } - SlidingAnimation(zapAmountTxt, grayTint) + SlidingAnimationAmount(zapAmountTxt, grayTint) +} + +@Composable +fun WatchZapAmountsForNote(baseNote: Note, accountViewModel: AccountViewModel, onZapAmount: (String) -> Unit) { + val zapsState by baseNote.live().zaps.observeAsState() + + LaunchedEffect(key1 = zapsState) { + launch(Dispatchers.Default) { + onZapAmount(showAmount(accountViewModel.calculateZapAmount(baseNote))) + } + } } @Composable diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt index 77f7fded4..ac251bd75 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt @@ -584,7 +584,7 @@ fun ChannelHeader( ShowVideoStreaming(baseChannel, accountViewModel) } - var expanded = remember { mutableStateOf(false) } + val expanded = remember { mutableStateOf(false) } Column( modifier = modifier.clickable {