Slightly faster reactions and zap icons

This commit is contained in:
Vitor Pamplona
2023-07-07 17:13:20 -04:00
parent 8ae05d9964
commit dcf825c43e
3 changed files with 99 additions and 59 deletions
@@ -328,10 +328,14 @@ open class Note(val idHex: String) {
}.toSet() }.toSet()
} }
fun isReactedBy(user: User): String? { fun getReactionBy(user: User): String? {
return reactions.filter { return reactions.firstNotNullOfOrNull {
it.value.any { it.author?.pubkeyHex == user.pubkeyHex } if (it.value.any { it.author?.pubkeyHex == user.pubkeyHex }) {
}.keys.firstOrNull() it.key
} else {
null
}
}
} }
fun isBoostedBy(user: User): Boolean { fun isBoostedBy(user: User): Boolean {
@@ -308,7 +308,9 @@ fun RenderZapRaiser(baseNote: Note, zapraiserAmount: Long, details: Boolean, acc
} }
LinearProgressIndicator( 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, color = color,
progress = zapraiserProgress progress = zapraiserProgress
) )
@@ -558,12 +560,17 @@ fun ReplyCounter(baseNote: Note, textColor: Color) {
it.note.replies.size it.note.replies.size
}.observeAsState(baseNote.replies.size) }.observeAsState(baseNote.replies.size)
SlidingAnimation(repliesState, textColor) SlidingAnimationCount(repliesState, textColor)
}
@Composable
private fun SlidingAnimationCount(baseCount: MutableState<Int>, textColor: Color) {
SlidingAnimationCount(baseCount.value, textColor)
} }
@OptIn(ExperimentalAnimationApi::class) @OptIn(ExperimentalAnimationApi::class)
@Composable @Composable
private fun SlidingAnimation(baseCount: Int, textColor: Color) { private fun SlidingAnimationCount(baseCount: Int, textColor: Color) {
AnimatedContent<Int>( AnimatedContent<Int>(
targetState = baseCount, targetState = baseCount,
transitionSpec = AnimatedContentScope<Int>::transitionSpec transitionSpec = AnimatedContentScope<Int>::transitionSpec
@@ -597,9 +604,9 @@ private fun TextCount(count: Int, textColor: Color) {
@Composable @Composable
@OptIn(ExperimentalAnimationApi::class) @OptIn(ExperimentalAnimationApi::class)
private fun SlidingAnimation(amount: String, textColor: Color) { private fun SlidingAnimationAmount(amount: MutableState<String>, textColor: Color) {
AnimatedContent( AnimatedContent(
targetState = amount, targetState = amount.value,
transitionSpec = AnimatedContentScope<String>::transitionSpec transitionSpec = AnimatedContentScope<String>::transitionSpec
) { count -> ) { count ->
Text( Text(
@@ -694,7 +701,7 @@ fun BoostText(baseNote: Note, grayTint: Color) {
it.note.boosts.size it.note.boosts.size
}.distinctUntilChanged().observeAsState(baseNote.boosts.size) }.distinctUntilChanged().observeAsState(baseNote.boosts.size)
SlidingAnimation(boostState, grayTint) SlidingAnimationCount(boostState, grayTint)
} }
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@@ -771,32 +778,41 @@ fun LikeIcon(
grayTint: Color, grayTint: Color,
accountViewModel: AccountViewModel accountViewModel: AccountViewModel
) { ) {
val reactionsState by baseNote.live().reactions.observeAsState() val reactionType = remember(baseNote) {
var reactionType by remember(baseNote) {
mutableStateOf<String?>(null) mutableStateOf<String?>(null)
} }
LaunchedEffect(key1 = reactionsState) { val scope = rememberCoroutineScope()
launch(Dispatchers.Default) {
val newReactionType = reactionsState?.note?.isReactedBy(accountViewModel.userProfile())?.firstFullChar() WatchReactionTypeForNote(baseNote, accountViewModel) { newReactionType ->
if (reactionType != newReactionType) { if (reactionType.value != newReactionType) {
launch(Dispatchers.Main) { scope.launch(Dispatchers.Main) {
reactionType = newReactionType reactionType.value = newReactionType
}
} }
} }
} }
Crossfade(targetState = reactionType) { Crossfade(targetState = reactionType) {
if (it != null) { val value = it.value
RenderReactionType(it, iconSize, iconFontSize) if (value != null) {
RenderReactionType(value, iconSize, iconFontSize)
} else { } else {
RenderLikeIcon(iconSize, grayTint) 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 @Composable
private fun RenderLikeIcon( private fun RenderLikeIcon(
iconSize: Dp = Size20dp, iconSize: Dp = Size20dp,
@@ -841,24 +857,32 @@ private fun RenderReactionType(
@Composable @Composable
fun LikeText(baseNote: Note, grayTint: Color) { fun LikeText(baseNote: Note, grayTint: Color) {
val reactionsState by baseNote.live().reactions.observeAsState() val reactionsCount = remember(baseNote) {
var reactionsCount by remember(baseNote) {
mutableStateOf(baseNote.reactions.size) mutableStateOf(baseNote.reactions.size)
} }
LaunchedEffect(key1 = reactionsState) { val scope = rememberCoroutineScope()
launch(Dispatchers.Default) {
val newReactionsCount = reactionsState?.note?.countReactions() ?: 0 WatchReactionCountForNote(baseNote) { newReactionsCount ->
if (reactionsCount != newReactionsCount) { if (reactionsCount.value != newReactionsCount) {
launch(Dispatchers.Main) { scope.launch(Dispatchers.Main) {
reactionsCount = newReactionsCount 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( private fun likeClick(
@@ -1072,27 +1096,22 @@ private fun ZapIcon(
grayTint: Color, grayTint: Color,
accountViewModel: AccountViewModel accountViewModel: AccountViewModel
) { ) {
var wasZappedByLoggedInUser by remember { mutableStateOf(false) } val wasZappedByLoggedInUser = remember { mutableStateOf(false) }
val zapsState by baseNote.live().zaps.observeAsState()
LaunchedEffect(key1 = zapsState) { val scope = rememberCoroutineScope()
launch(Dispatchers.Default) {
zapsState?.note?.let {
if (!wasZappedByLoggedInUser) {
val newWasZapped = accountViewModel.calculateIfNoteWasZappedByAccount(it)
if (wasZappedByLoggedInUser != newWasZapped) { if (!wasZappedByLoggedInUser.value) {
launch(Dispatchers.Main) { WatchZapsForNote(baseNote, accountViewModel) { newWasZapped ->
wasZappedByLoggedInUser = newWasZapped if (wasZappedByLoggedInUser.value != newWasZapped) {
} scope.launch(Dispatchers.Main) {
} wasZappedByLoggedInUser.value = newWasZapped
} }
} }
} }
} }
Crossfade(targetState = wasZappedByLoggedInUser) { Crossfade(targetState = wasZappedByLoggedInUser) {
if (it) { if (it.value) {
Icon( Icon(
imageVector = Icons.Default.Bolt, imageVector = Icons.Default.Bolt,
contentDescription = stringResource(R.string.zaps), 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 @Composable
private fun ZapAmountText( private fun ZapAmountText(
baseNote: Note, baseNote: Note,
grayTint: Color, grayTint: Color,
accountViewModel: AccountViewModel 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) { WatchZapAmountsForNote(baseNote, accountViewModel) { newZapAmount ->
launch(Dispatchers.Default) { if (zapAmountTxt.value != newZapAmount) {
zapsState?.note?.let { scope.launch(Dispatchers.Main) {
val newZapAmount = showAmount(accountViewModel.calculateZapAmount(it)) zapAmountTxt.value = newZapAmount
if (newZapAmount != zapAmountTxt) {
launch(Dispatchers.Main) {
zapAmountTxt = 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 @Composable
@@ -584,7 +584,7 @@ fun ChannelHeader(
ShowVideoStreaming(baseChannel, accountViewModel) ShowVideoStreaming(baseChannel, accountViewModel)
} }
var expanded = remember { mutableStateOf(false) } val expanded = remember { mutableStateOf(false) }
Column( Column(
modifier = modifier.clickable { modifier = modifier.clickable {