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 6f91988e8..32b3b853f 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 @@ -1432,7 +1432,7 @@ fun RenderPoll( nav: (String) -> Unit, ) { val noteEvent = note.event as? PollNoteEvent ?: return - val eventContent = remember(note) { noteEvent.content() } + val eventContent = noteEvent.content() if (makeItShort && accountViewModel.isLoggedUser(note.author)) { Text( diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/PollNote.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/PollNote.kt index e40d7ac40..e17404ba2 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/PollNote.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/PollNote.kt @@ -68,7 +68,6 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Popup -import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Note @@ -101,7 +100,7 @@ fun PollNote( accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { - val pollViewModel: PollNoteViewModel = viewModel(key = "PollNoteViewModel") + val pollViewModel: PollNoteViewModel = viewModel(key = "PollNoteViewModel${baseNote.idHex}") pollViewModel.load(accountViewModel.account, baseNote) @@ -126,11 +125,9 @@ fun PollNote( ) { WatchZapsAndUpdateTallies(baseNote, pollViewModel) - val tallies by pollViewModel.tallies.collectAsStateWithLifecycle() - - tallies.forEach { poll_op -> + pollViewModel.tallies.forEach { option -> OptionNote( - poll_op, + option, pollViewModel, baseNote, accountViewModel, @@ -167,9 +164,9 @@ private fun OptionNote( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(vertical = 3.dp), ) { - if (!pollViewModel.canZap()) { + if (!pollViewModel.canZap.value) { val color = - if (poolOption.consensusThreadhold) { + if (poolOption.consensusThreadhold.value) { Color.Green.copy(alpha = 0.32f) } else { MaterialTheme.colorScheme.mediumImportanceLink @@ -181,8 +178,7 @@ private fun OptionNote( pollViewModel = pollViewModel, nonClickablePrepend = { RenderOptionAfterVote( - poolOption.descriptor, - poolOption.tally.toFloat(), + poolOption, color, canPreview, tags, @@ -220,8 +216,7 @@ private fun OptionNote( @Composable private fun RenderOptionAfterVote( - description: String, - totalRatio: Float, + poolOption: PollOption, color: Color, canPreview: Boolean, tags: ImmutableListOfLists, @@ -229,10 +224,9 @@ private fun RenderOptionAfterVote( accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { - val totalPercentage = remember(totalRatio) { "${(totalRatio * 100).roundToInt()}%" } - Box( - Modifier.fillMaxWidth(0.75f) + Modifier + .fillMaxWidth(0.75f) .clip(shape = QuoteBorder) .border( 2.dp, @@ -243,7 +237,9 @@ private fun RenderOptionAfterVote( LinearProgressIndicator( modifier = Modifier.matchParentSize(), color = color, - progress = totalRatio, + progress = { + poolOption.tally.value.toFloat() + }, ) Row( @@ -251,21 +247,31 @@ private fun RenderOptionAfterVote( ) { Column( horizontalAlignment = Alignment.End, - modifier = remember { Modifier.padding(horizontal = 10.dp).width(40.dp) }, + modifier = + remember { + Modifier + .padding(horizontal = 10.dp) + .width(45.dp) + }, ) { Text( - text = totalPercentage, + text = "${(poolOption.tally.value.toFloat() * 100).roundToInt()}%", fontWeight = FontWeight.Bold, ) } Column( - modifier = remember { Modifier.fillMaxWidth().padding(15.dp) }, + modifier = + remember { + Modifier + .fillMaxWidth() + .padding(vertical = 15.dp, horizontal = 10.dp) + }, ) { TranslatableRichTextViewer( - description, + poolOption.descriptor, canPreview, - remember { Modifier }, + Modifier, tags, backgroundColor, accountViewModel, @@ -286,7 +292,8 @@ private fun RenderOptionBeforeVote( nav: (String) -> Unit, ) { Box( - Modifier.fillMaxWidth(0.75f) + Modifier + .fillMaxWidth(0.75f) .clip(shape = QuoteBorder) .border( 2.dp, @@ -358,7 +365,7 @@ fun ZapVote( R.string.poll_unable_to_vote, R.string.poll_author_no_vote, ) - } else if (pollViewModel.isVoteAmountAtomic() && poolOption.zappedByLoggedIn) { + } else if (pollViewModel.isVoteAmountAtomic() && poolOption.zappedByLoggedIn.value) { // only allow one vote per option when min==max, i.e. atomic vote amount specified accountViewModel.toast( R.string.poll_unable_to_vote, @@ -443,7 +450,7 @@ fun ZapVote( clickablePrepend() - if (poolOption.zappedByLoggedIn) { + if (poolOption.zappedByLoggedIn.value) { zappingProgress = 1f Icon( imageVector = Icons.Default.Bolt, @@ -471,8 +478,8 @@ fun ZapVote( } // only show tallies after a user has zapped note - if (!pollViewModel.canZap()) { - val amountStr = remember(poolOption.zappedValue) { showAmount(poolOption.zappedValue) } + if (!pollViewModel.canZap.value) { + val amountStr = remember(poolOption.zappedValue.value) { showAmount(poolOption.zappedValue.value) } Text( text = amountStr, fontSize = Font14SP, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/PollNoteViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/PollNoteViewModel.kt index 0fb6008fa..bfbfa71c8 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/PollNoteViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/PollNoteViewModel.kt @@ -20,8 +20,9 @@ */ package com.vitorpamplona.amethyst.ui.note -import androidx.compose.runtime.Immutable +import androidx.compose.runtime.MutableState import androidx.compose.runtime.Stable +import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.vitorpamplona.amethyst.model.Account @@ -36,20 +37,18 @@ import com.vitorpamplona.quartz.events.VALUE_MAXIMUM import com.vitorpamplona.quartz.events.VALUE_MINIMUM import com.vitorpamplona.quartz.utils.TimeUtils import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import java.math.BigDecimal import java.math.RoundingMode -@Immutable +@Stable data class PollOption( val option: Int, val descriptor: String, - val zappedValue: BigDecimal, - val tally: BigDecimal, - val consensusThreadhold: Boolean, - val zappedByLoggedIn: Boolean, + var zappedValue: MutableState = mutableStateOf(BigDecimal.ZERO), + var tally: MutableState = mutableStateOf(BigDecimal.ZERO), + var consensusThreadhold: MutableState = mutableStateOf(false), + var zappedByLoggedIn: MutableState = mutableStateOf(false), ) @Stable @@ -70,8 +69,8 @@ class PollNoteViewModel : ViewModel() { private var totalZapped: BigDecimal = BigDecimal.ZERO private var wasZappedByLoggedInAccount: Boolean = false - private val _tallies = MutableStateFlow>(emptyList()) - val tallies = _tallies.asStateFlow() + var canZap = mutableStateOf(false) + var tallies: List = emptyList() fun load( acc: Account, @@ -88,47 +87,44 @@ class PollNoteViewModel : ViewModel() { consensusThreshold = pollEvent?.getTagLong(CONSENSUS_THRESHOLD)?.toFloat()?.div(100)?.toBigDecimal() closedAt = pollEvent?.getTagLong(CLOSED_AT) + + totalZapped = BigDecimal.ZERO + wasZappedByLoggedInAccount = false + + canZap.value = checkIfCanZap() + + tallies = pollOptions?.keys?.map { option -> + PollOption( + option, + pollOptions?.get(option) ?: "", + ) + } ?: emptyList() } fun refreshTallies() { viewModelScope.launch(Dispatchers.Default) { totalZapped = totalZapped() wasZappedByLoggedInAccount = false - account?.calculateIfNoteWasZappedByAccount(pollNote) { wasZappedByLoggedInAccount = true } + account?.calculateIfNoteWasZappedByAccount(pollNote) { + wasZappedByLoggedInAccount = true + canZap.value = checkIfCanZap() + } - val newOptions = - pollOptions?.keys?.map { option -> - val zappedInOption = zappedPollOptionAmount(option) - - val myTally = - if (totalZapped.compareTo(BigDecimal.ZERO) > 0) { - zappedInOption.divide(totalZapped, 2, RoundingMode.HALF_UP) - } else { - BigDecimal.ZERO - } - - val cachedZappedByLoggedIn = - account?.userProfile()?.let { it1 -> cachedIsPollOptionZappedBy(option, it1) } ?: false - - val consensus = consensusThreshold != null && myTally >= consensusThreshold!! - - PollOption( - option, - pollOptions?.get(option) ?: "", - zappedInOption, - myTally, - consensus, - cachedZappedByLoggedIn, - ) - } - - _tallies.emit( - newOptions ?: emptyList(), - ) + tallies.forEach { + it.zappedValue.value = zappedPollOptionAmount(it.option) + it.tally.value = + if (totalZapped.compareTo(BigDecimal.ZERO) > 0) { + it.zappedValue.value.divide(totalZapped, 2, RoundingMode.HALF_UP) + } else { + BigDecimal.ZERO + } + it.consensusThreadhold.value = consensusThreshold != null && it.tally.value >= consensusThreshold!! + it.zappedByLoggedIn.value = account?.userProfile()?.let { it1 -> cachedIsPollOptionZappedBy(it.option, it1) } ?: false + } } } - fun canZap(): Boolean { + fun checkIfCanZap(): Boolean { val account = account ?: return false val note = pollNote ?: return false return account.userProfile() != note.author && !wasZappedByLoggedInAccount