Improves Poll Rendering speed.

This commit is contained in:
Vitor Pamplona
2023-05-31 20:08:07 -04:00
parent a57ca2a79d
commit 3eb832c4e0
6 changed files with 261 additions and 309 deletions
@@ -45,7 +45,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.uriToRoute
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.net.MalformedURLException
import java.net.URISyntaxException
import java.net.URL
@@ -128,10 +127,8 @@ private fun RenderRegular(
mutableStateOf(RichTextViewerState(content, emptySet(), emptyMap(), emptyList(), emptyMap()))
}
val scope = rememberCoroutineScope()
LaunchedEffect(key1 = content) {
scope.launch(Dispatchers.IO) {
launch(Dispatchers.IO) {
val urls = UrlDetector(content, UrlDetectorOptions.Default).detect()
val urlSet = urls.mapTo(LinkedHashSet(urls.size)) { it.originalUrl }
val imagesForPager = urlSet.mapNotNull { fullUrl ->
@@ -363,14 +360,14 @@ private fun RenderContentAsMarkdown(content: String, backgroundColor: Color, tag
var refresh by remember(content) { mutableStateOf(0) }
LaunchedEffect(key1 = content) {
withContext(Dispatchers.IO) {
launch(Dispatchers.IO) {
nip19References = returnNIP19References(content, tags)
markdownWithSpecialContent = returnMarkdownWithSpecialContent(content, tags)
}
}
LaunchedEffect(key1 = refresh) {
withContext(Dispatchers.IO) {
launch(Dispatchers.IO) {
val newMarkdownWithSpecialContent = returnMarkdownWithSpecialContent(content, tags)
if (markdownWithSpecialContent != newMarkdownWithSpecialContent) {
markdownWithSpecialContent = newMarkdownWithSpecialContent
@@ -425,7 +422,7 @@ private fun ObserveNIP19Event(
var baseNote by remember(it) { mutableStateOf<Note?>(null) }
LaunchedEffect(key1 = it.hex) {
withContext(Dispatchers.IO) {
launch(Dispatchers.IO) {
if (it.type == Nip19.Type.NOTE || it.type == Nip19.Type.EVENT || it.type == Nip19.Type.ADDRESS) {
LocalCache.checkGetOrCreateNote(it.hex)?.let { note ->
baseNote = note
@@ -436,9 +433,12 @@ private fun ObserveNIP19Event(
baseNote?.let { note ->
val noteState by note.live().metadata.observeAsState()
if (noteState?.note?.event != null) {
LaunchedEffect(key1 = noteState) {
onRefresh()
LaunchedEffect(key1 = noteState) {
if (noteState?.note?.event != null) {
launch(Dispatchers.IO) {
onRefresh()
}
}
}
}
@@ -452,7 +452,7 @@ private fun ObserveNIP19User(
var baseUser by remember(it) { mutableStateOf<User?>(null) }
LaunchedEffect(key1 = it.hex) {
withContext(Dispatchers.IO) {
launch(Dispatchers.IO) {
if (it.type == Nip19.Type.USER) {
LocalCache.checkGetOrCreateUser(it.hex)?.let { user ->
baseUser = user
@@ -463,9 +463,12 @@ private fun ObserveNIP19User(
baseUser?.let { user ->
val userState by user.live().metadata.observeAsState()
if (userState?.user?.info != null) {
LaunchedEffect(key1 = userState) {
onRefresh()
LaunchedEffect(key1 = userState) {
if (userState?.user?.info != null) {
launch(Dispatchers.IO) {
onRefresh()
}
}
}
}
@@ -622,10 +625,8 @@ fun BechLink(word: String, canPreview: Boolean, backgroundColor: Color, accountV
var nip19Route by remember { mutableStateOf<Nip19.Return?>(null) }
var baseNotePair by remember { mutableStateOf<Pair<Note, String?>?>(null) }
val scope = rememberCoroutineScope()
LaunchedEffect(key1 = word) {
scope.launch(Dispatchers.IO) {
launch(Dispatchers.IO) {
Nip19.uriToRoute(word)?.let {
if (it.type == Nip19.Type.NOTE || it.type == Nip19.Type.EVENT || it.type == Nip19.Type.ADDRESS) {
LocalCache.checkGetOrCreateNote(it.hex)?.let { note ->
@@ -675,10 +676,8 @@ fun BechLink(word: String, canPreview: Boolean, backgroundColor: Color, accountV
fun HashTag(word: String, nav: (String) -> Unit) {
var tagSuffixPair by remember { mutableStateOf<Pair<String, String?>?>(null) }
val scope = rememberCoroutineScope()
LaunchedEffect(key1 = word) {
scope.launch(Dispatchers.IO) {
launch(Dispatchers.IO) {
val hashtagMatcher = hashTagsPattern.matcher(word)
val (myTag, mySuffix) = try {
@@ -755,10 +754,8 @@ fun TagLink(word: String, tags: List<List<String>>, canPreview: Boolean, backgro
var baseUserPair by remember { mutableStateOf<Pair<User, String?>?>(null) }
var baseNotePair by remember { mutableStateOf<Pair<Note, String?>?>(null) }
val scope = rememberCoroutineScope()
LaunchedEffect(key1 = word) {
scope.launch(Dispatchers.IO) {
launch(Dispatchers.IO) {
val matcher = tagIndex.matcher(word)
val (index, suffix) = try {
matcher.find()
@@ -262,9 +262,7 @@ fun AuthorGalleryZaps(
Column(modifier = Modifier.padding(start = 10.dp)) {
FlowRow() {
authorNotes.forEach {
Box() {
AuthorPictureAndComment(it.key, it.value, backgroundColor, nav, accountViewModel)
}
AuthorPictureAndComment(it.key, it.value, backgroundColor, nav, accountViewModel)
}
}
}
@@ -329,17 +327,9 @@ private fun AuthorPictureAndComment(
) {
val authorPictureModifier = remember { Modifier }
val modifier = remember(comment) {
if (comment != null) {
Modifier
.fillMaxWidth()
.clickable {
nav(route)
}
} else {
Modifier.clickable {
nav(route)
}
val modifier = remember {
Modifier.clickable {
nav(route)
}
}
@@ -381,7 +371,7 @@ private fun AuthorPictureAndComment(
content = it,
canPreview = true,
tags = null,
modifier = Modifier.weight(1f),
modifier = remember { Modifier.fillMaxWidth() },
backgroundColor = backgroundColor,
accountViewModel = accountViewModel,
nav = nav
@@ -604,7 +604,7 @@ private fun RenderPoll(
nav: (String) -> Unit
) {
val noteEvent = note.event as? PollNoteEvent ?: return
val eventContent = noteEvent.content()
val eventContent = remember { noteEvent.content() }
if (makeItShort && accountViewModel.isLoggedUser(note.author)) {
Text(
@@ -2249,35 +2249,42 @@ fun UserPicture(
.align(Alignment.TopEnd)
}
val myIconBackgroundModifier = remember {
Modifier
.clip(CircleShape)
.fillMaxSize(0.6f)
.align(Alignment.Center)
}
val myIconModifier = remember {
Modifier
.width(size.div(3.5f))
.height(size.div(3.5f))
}
Box(myIconBoxModifier, contentAlignment = Alignment.Center) {
Box(
myIconBackgroundModifier.background(MaterialTheme.colors.background)
)
Icon(
painter = painterResource(R.drawable.ic_verified),
stringResource(id = R.string.following),
modifier = myIconModifier,
tint = Following
)
}
FollowingIcon(myIconBoxModifier, size)
}
}
}
@Composable
private fun FollowingIcon(
myIconBoxModifier: Modifier,
size: Dp
) {
Box(myIconBoxModifier, contentAlignment = Alignment.Center) {
val myIconBackgroundModifier = remember {
Modifier
.clip(CircleShape)
.fillMaxSize(0.6f)
}
Box(
myIconBackgroundModifier.background(MaterialTheme.colors.background)
)
val myIconModifier = remember {
Modifier
.width(size.div(3.5f))
.height(size.div(3.5f))
}
Icon(
painter = painterResource(R.drawable.ic_verified),
stringResource(id = R.string.following),
modifier = myIconModifier,
tint = Following
)
}
}
data class DropDownParams(
val isFollowingAuthor: Boolean,
val isPrivateBookmarkNote: Boolean,
@@ -4,14 +4,12 @@ import android.widget.Toast
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Bolt
import androidx.compose.material.icons.outlined.Bolt
import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@@ -20,23 +18,20 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.compose.ui.window.Popup
import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.service.model.LnZapEvent
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.*
import kotlin.math.roundToInt
@@ -48,16 +43,11 @@ fun PollNote(
accountViewModel: AccountViewModel,
nav: (String) -> Unit
) {
val accountState by accountViewModel.accountLiveData.observeAsState()
val account = remember(accountState) { accountState?.account } ?: return
val pollViewModel: PollNoteViewModel = viewModel(
key = baseNote.idHex
)
LaunchedEffect(key1 = baseNote) {
pollViewModel.load(account, baseNote)
}
pollViewModel.load(accountViewModel.account, baseNote)
PollNote(
baseNote = baseNote,
@@ -78,15 +68,11 @@ fun PollNote(
accountViewModel: AccountViewModel,
nav: (String) -> Unit
) {
val zapsState by baseNote.live().zaps.observeAsState()
WatchZapsAndUpdateTallies(baseNote, pollViewModel)
LaunchedEffect(key1 = zapsState) {
withContext(Dispatchers.IO) {
pollViewModel.refreshTallies()
}
}
val tallies by pollViewModel.tallies.collectAsState()
pollViewModel.tallies.forEach { poll_op ->
tallies.forEach { poll_op ->
OptionNote(
poll_op,
pollViewModel,
@@ -99,6 +85,21 @@ fun PollNote(
}
}
@Composable
private fun WatchZapsAndUpdateTallies(
baseNote: Note,
pollViewModel: PollNoteViewModel
) {
val zapsState by baseNote.live().zaps.observeAsState()
val scope = rememberCoroutineScope()
LaunchedEffect(key1 = zapsState) {
scope.launch(Dispatchers.IO) {
pollViewModel.refreshTallies()
}
}
}
@Composable
private fun OptionNote(
poolOption: PollOption,
@@ -109,6 +110,8 @@ private fun OptionNote(
backgroundColor: Color,
nav: (String) -> Unit
) {
val tags = remember { baseNote.event?.tags() }
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(vertical = 3.dp)
@@ -126,54 +129,16 @@ private fun OptionNote(
accountViewModel,
pollViewModel,
nonClickablePrepend = {
Box(
Modifier
.fillMaxWidth(0.75f)
.clip(shape = RoundedCornerShape(15.dp))
.border(
2.dp,
color,
RoundedCornerShape(15.dp)
)
) {
LinearProgressIndicator(
modifier = Modifier.matchParentSize(),
color = color,
progress = poolOption.tally.toFloat()
)
Row(
verticalAlignment = Alignment.CenterVertically
) {
Column(
horizontalAlignment = Alignment.End,
modifier = Modifier
.padding(horizontal = 10.dp)
.width(40.dp)
) {
Text(
text = "${(poolOption.tally.toFloat() * 100).roundToInt()}%",
fontWeight = FontWeight.Bold
)
}
Column(
modifier = Modifier
.fillMaxWidth()
.padding(15.dp)
) {
TranslatableRichTextViewer(
poolOption.descriptor,
canPreview,
Modifier,
pollViewModel.pollEvent?.tags(),
backgroundColor,
accountViewModel,
nav
)
}
}
}
RenderOptionAfterVote(
poolOption.descriptor,
poolOption.tally.toFloat(),
color,
canPreview,
tags,
backgroundColor,
accountViewModel,
nav
)
},
clickablePrepend = {
}
@@ -186,32 +151,113 @@ private fun OptionNote(
pollViewModel,
nonClickablePrepend = {},
clickablePrepend = {
Box(
Modifier
.fillMaxWidth(0.75f)
.clip(shape = RoundedCornerShape(15.dp))
.border(
2.dp,
MaterialTheme.colors.primary,
RoundedCornerShape(15.dp)
)
) {
TranslatableRichTextViewer(
poolOption.descriptor,
canPreview,
Modifier.padding(15.dp),
pollViewModel.pollEvent?.tags(),
backgroundColor,
accountViewModel,
nav
)
}
RenderOptionBeforeVote(poolOption.descriptor, canPreview, tags, backgroundColor, accountViewModel, nav)
}
)
}
}
}
@Composable
private fun RenderOptionAfterVote(
description: String,
totalRatio: Float,
color: Color,
canPreview: Boolean,
tags: List<List<String>>?,
backgroundColor: Color,
accountViewModel: AccountViewModel,
nav: (String) -> Unit
) {
val totalPercentage = remember(totalRatio) {
"${(totalRatio * 100).roundToInt()}%"
}
Box(
Modifier
.fillMaxWidth(0.75f)
.clip(shape = RoundedCornerShape(15.dp))
.border(
2.dp,
color,
RoundedCornerShape(15.dp)
)
) {
LinearProgressIndicator(
modifier = Modifier.matchParentSize(),
color = color,
progress = totalRatio
)
Row(
verticalAlignment = Alignment.CenterVertically
) {
Column(
horizontalAlignment = Alignment.End,
modifier = remember {
Modifier
.padding(horizontal = 10.dp)
.width(40.dp)
}
) {
Text(
text = totalPercentage,
fontWeight = FontWeight.Bold
)
}
Column(
modifier = remember {
Modifier
.fillMaxWidth()
.padding(15.dp)
}
) {
TranslatableRichTextViewer(
description,
canPreview,
remember { Modifier },
tags,
backgroundColor,
accountViewModel,
nav
)
}
}
}
}
@Composable
private fun RenderOptionBeforeVote(
description: String,
canPreview: Boolean,
tags: List<List<String>>?,
backgroundColor: Color,
accountViewModel: AccountViewModel,
nav: (String) -> Unit
) {
Box(
Modifier
.fillMaxWidth(0.75f)
.clip(shape = RoundedCornerShape(15.dp))
.border(
2.dp,
MaterialTheme.colors.primary,
RoundedCornerShape(15.dp)
)
) {
TranslatableRichTextViewer(
description,
canPreview,
remember { Modifier.padding(15.dp) },
tags,
backgroundColor,
accountViewModel,
nav
)
}
}
@Composable
@OptIn(ExperimentalFoundationApi::class)
fun ZapVote(
@@ -374,7 +420,7 @@ fun ZapVote(
}
// only show tallies after a user has zapped note
if (baseNote.author == accountViewModel.userProfile() || pollViewModel.wasZappedByLoggedInAccount) {
if (!pollViewModel.canZap()) {
Text(
showAmount(poolOption.zappedValue),
fontSize = 14.sp,
@@ -399,23 +445,18 @@ fun FilteredZapAmountChoicePopup(
val context = LocalContext.current
val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account ?: return
val defaultZapType by remember(accountState) {
derivedStateOf {
accountState?.account?.defaultZapType ?: LnZapEvent.ZapType.PRIVATE
}
}
val zapMessage = ""
val scope = rememberCoroutineScope()
val options = account.zapAmountChoices.filter { pollViewModel.isValidInputVoteAmount(it) }.toMutableList()
if (options.isEmpty()) {
pollViewModel.valueMinimum?.let { minimum ->
pollViewModel.valueMaximum?.let { maximum ->
if (minimum != maximum) {
options.add(((minimum + maximum) / 2).toLong())
}
}
}
val sortedOptions = remember(accountState) {
pollViewModel.createZapOptionsThatMatchThePollingParameters()
}
pollViewModel.valueMinimum?.let { options.add(it.toLong()) }
pollViewModel.valueMaximum?.let { options.add(it.toLong()) }
val sortedOptions = options.toSet().sorted()
Popup(
alignment = Alignment.BottomCenter,
@@ -424,6 +465,10 @@ fun FilteredZapAmountChoicePopup(
) {
FlowRow(horizontalArrangement = Arrangement.Center) {
sortedOptions.forEach { amountInSats ->
val zapAmount = remember {
"${showAmount(amountInSats.toBigDecimal().setScale(1))}"
}
Button(
modifier = Modifier.padding(horizontal = 3.dp),
onClick = {
@@ -436,7 +481,7 @@ fun FilteredZapAmountChoicePopup(
context,
onError,
onProgress,
account.defaultZapType
defaultZapType
)
onDismiss()
}
@@ -448,7 +493,7 @@ fun FilteredZapAmountChoicePopup(
)
) {
Text(
"${showAmount(amountInSats.toBigDecimal().setScale(1))}",
text = zapAmount,
color = Color.White,
textAlign = TextAlign.Center,
modifier = Modifier.combinedClickable(
@@ -462,7 +507,7 @@ fun FilteredZapAmountChoicePopup(
context,
onError,
onProgress,
account.defaultZapType
defaultZapType
)
onDismiss()
}
@@ -477,118 +522,3 @@ fun FilteredZapAmountChoicePopup(
}
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun ZapVoteAmountChoicePopup(
baseNote: Note,
accountViewModel: AccountViewModel,
pollViewModel: PollNoteViewModel,
pollOption: Int,
onDismiss: () -> Unit,
onError: (text: String) -> Unit,
onProgress: (percent: Float) -> Unit
) {
val context = LocalContext.current
var inputAmountText by rememberSaveable { mutableStateOf("") }
val colorInValid = TextFieldDefaults.outlinedTextFieldColors(
focusedBorderColor = MaterialTheme.colors.error,
unfocusedBorderColor = Color.Red
)
val colorValid = TextFieldDefaults.outlinedTextFieldColors(
focusedBorderColor = MaterialTheme.colors.primary,
unfocusedBorderColor = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
)
Dialog(
onDismissRequest = { onDismiss() },
properties = DialogProperties(
dismissOnClickOutside = true,
usePlatformDefaultWidth = false
)
) {
Surface {
Row(
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.padding(10.dp)
) {
var amount = pollViewModel.inputVoteAmountLong(inputAmountText)
// only prompt for input amount if vote is not atomic
if (!pollViewModel.isVoteAmountAtomic()) {
OutlinedTextField(
value = inputAmountText,
onValueChange = { inputAmountText = it },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
modifier = Modifier.width(150.dp),
colors = if (pollViewModel.isValidInputVoteAmount(amount)) colorValid else colorInValid,
label = {
Text(
text = stringResource(R.string.poll_zap_amount),
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
)
},
placeholder = {
Text(
text = pollViewModel.voteAmountPlaceHolderText(context.getString(R.string.sats)),
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
)
}
)
} else { amount = pollViewModel.valueMaximum?.toLong() }
val isValidInputAmount = pollViewModel.isValidInputVoteAmount(amount)
Button(
modifier = Modifier.padding(horizontal = 3.dp),
enabled = isValidInputAmount,
onClick = {
if (amount != null && isValidInputAmount) {
accountViewModel.zap(
baseNote,
amount * 1000,
pollOption,
"",
context,
onError,
onProgress
)
onDismiss()
}
},
shape = RoundedCornerShape(20.dp),
colors = ButtonDefaults
.buttonColors(
backgroundColor = MaterialTheme.colors.primary
)
) {
Text(
"${showAmount(amount?.toBigDecimal()?.setScale(1))}",
color = Color.White,
textAlign = TextAlign.Center,
modifier = Modifier.combinedClickable(
onClick = {
if (amount != null && isValidInputAmount) {
accountViewModel.zap(
baseNote,
amount * 1000,
pollOption,
"",
context,
onError,
onProgress
)
onDismiss()
}
},
onLongClick = {}
)
)
}
}
}
}
}
@@ -1,17 +1,23 @@
package com.vitorpamplona.amethyst.ui.note
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.RelaySetupInfo
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.model.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import java.math.BigDecimal
import java.math.RoundingMode
import java.util.*
@Immutable
data class PollOption(
val option: Int,
val descriptor: String,
@@ -21,21 +27,23 @@ data class PollOption(
val zappedByLoggedIn: Boolean
)
@Stable
class PollNoteViewModel : ViewModel() {
var account: Account? = null
private var account: Account? = null
private var pollNote: Note? = null
var pollEvent: PollNoteEvent? = null
var pollOptions: Map<Int, String>? = null
var valueMaximum: Int? = null
var valueMinimum: Int? = null
private var pollEvent: PollNoteEvent? = null
private var pollOptions: Map<Int, String>? = null
private var valueMaximum: Int? = null
private var valueMinimum: Int? = null
private var closedAt: Int? = null
var consensusThreshold: BigDecimal? = null
private var consensusThreshold: BigDecimal? = null
var totalZapped: BigDecimal = BigDecimal.ZERO
var wasZappedByLoggedInAccount: Boolean = false
private var totalZapped: BigDecimal = BigDecimal.ZERO
private var wasZappedByLoggedInAccount: Boolean = false
var tallies by mutableStateOf<List<PollOption>>(emptyList())
private val _tallies = MutableStateFlow<List<PollOption>>(emptyList())
val tallies = _tallies.asStateFlow()
fun load(acc: Account, note: Note?) {
account = acc
@@ -46,36 +54,35 @@ class PollNoteViewModel : ViewModel() {
valueMinimum = pollEvent?.getTagInt(VALUE_MINIMUM)
consensusThreshold = pollEvent?.getTagInt(CONSENSUS_THRESHOLD)?.toFloat()?.div(100)?.toBigDecimal()
closedAt = pollEvent?.getTagInt(CLOSED_AT)
refreshTallies()
}
fun refreshTallies() {
suspend fun refreshTallies() {
totalZapped = totalZapped()
wasZappedByLoggedInAccount = pollNote?.let { account?.calculateIfNoteWasZappedByAccount(it) } ?: false
tallies = pollOptions?.keys?.map {
val zappedInOption = zappedPollOptionAmount(it)
_tallies.emit(
pollOptions?.keys?.map {
val zappedInOption = zappedPollOptionAmount(it)
val myTally = if (totalZapped.compareTo(BigDecimal.ZERO) > 0) {
zappedInOption.divide(totalZapped, 2, RoundingMode.HALF_UP)
} else {
BigDecimal.ZERO
}
val myTally = if (totalZapped.compareTo(BigDecimal.ZERO) > 0) {
zappedInOption.divide(totalZapped, 2, RoundingMode.HALF_UP)
} else {
BigDecimal.ZERO
}
val zappedByLoggedIn = account?.userProfile()?.let { it1 -> isPollOptionZappedBy(it, it1) } ?: false
val zappedByLoggedIn = account?.userProfile()?.let { it1 -> isPollOptionZappedBy(it, it1) } ?: false
val consensus = consensusThreshold != null && myTally >= consensusThreshold!!
val consensus = consensusThreshold != null && myTally >= consensusThreshold!!
PollOption(it, pollOptions?.get(it) ?: "", zappedInOption, myTally, consensus, zappedByLoggedIn)
} ?: emptyList()
PollOption(it, pollOptions?.get(it) ?: "", zappedInOption, myTally, consensus, zappedByLoggedIn)
} ?: emptyList()
)
}
fun canZap(): Boolean {
val account = account ?: return false
val user = account.userProfile() ?: return false
val note = pollNote ?: return false
return user != note.author && !wasZappedByLoggedInAccount
return account.userProfile() != note.author && !wasZappedByLoggedInAccount
}
fun isVoteAmountAtomic() = valueMaximum != null && valueMinimum != null && valueMinimum == valueMaximum
@@ -154,4 +161,21 @@ class PollNoteViewModel : ViewModel() {
}
} ?: BigDecimal(0)
}
fun createZapOptionsThatMatchThePollingParameters(): List<Long> {
val options = account?.zapAmountChoices?.filter { isValidInputVoteAmount(it) }?.toMutableList() ?: mutableListOf()
if (options.isEmpty()) {
valueMinimum?.let { minimum ->
valueMaximum?.let { maximum ->
if (minimum != maximum) {
options.add(((minimum + maximum) / 2).toLong())
}
}
}
}
valueMinimum?.let { options.add(it.toLong()) }
valueMaximum?.let { options.add(it.toLong()) }
return options.toSet().sorted()
}
}
@@ -153,6 +153,10 @@ private fun FeedLoaded(
nav: (String) -> Unit,
routeForLastRead: String
) {
val defaultModifier = remember {
Modifier.fillMaxWidth().defaultMinSize(minHeight = 100.dp)
}
LazyColumn(
modifier = remember { Modifier.fillMaxSize() },
contentPadding = remember {
@@ -164,7 +168,7 @@ private fun FeedLoaded(
state = listState
) {
itemsIndexed(state.feed.value, key = { _, item -> item.id() }) { _, item ->
Row(Modifier.fillMaxWidth().defaultMinSize(minHeight = 100.dp)) {
Row(defaultModifier) {
when (item) {
is NoteCard -> NoteCompose(
item.note,