mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-09-27 13:57:19 +02:00
add zap vote amount dialog,
show vote option tallies, format zapped vote button icons
This commit is contained in:
@@ -237,6 +237,21 @@ open class Note(val idHex: String) {
|
|||||||
}.sumOf { it }
|
}.sumOf { it }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun zappedPollOptionAmount(option: Int): BigDecimal {
|
||||||
|
return zaps.mapNotNull { it.value?.event }
|
||||||
|
.filterIsInstance<LnZapEvent>()
|
||||||
|
.mapNotNull {
|
||||||
|
val zappedOption = it.zappedPollOption()
|
||||||
|
if (zappedOption == option) {
|
||||||
|
it.amount
|
||||||
|
} else { null }
|
||||||
|
}.sumOf { it }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isPollOptionZapped(option: Int): Boolean {
|
||||||
|
return zappedPollOptionAmount(option).toInt() > 0
|
||||||
|
}
|
||||||
|
|
||||||
fun hasAnyReports(): Boolean {
|
fun hasAnyReports(): Boolean {
|
||||||
val dayAgo = Date().time / 1000 - 24 * 60 * 60
|
val dayAgo = Date().time / 1000 - 24 * 60 * 60
|
||||||
return reports.isNotEmpty() ||
|
return reports.isNotEmpty() ||
|
||||||
|
@@ -19,9 +19,16 @@ class LnZapEvent(
|
|||||||
.filter { it.firstOrNull() == "e" }
|
.filter { it.firstOrNull() == "e" }
|
||||||
.mapNotNull { it.getOrNull(1) }
|
.mapNotNull { it.getOrNull(1) }
|
||||||
|
|
||||||
|
/* // TODO add poll_option tag to LnZapEvent
|
||||||
override fun zappedPollOption(): Int? = tags
|
override fun zappedPollOption(): Int? = tags
|
||||||
.filter { it.firstOrNull() == "poll_option" }
|
.filter { it.firstOrNull() == "poll_option" }
|
||||||
.getOrNull(1)?.getOrNull(1)?.toInt()
|
.getOrNull(1)?.getOrNull(1)?.toInt()
|
||||||
|
*/
|
||||||
|
// TODO replace this hacky way to get poll option with above function
|
||||||
|
override fun zappedPollOption(): Int? = description()
|
||||||
|
?.substringAfter("poll_option\",\"")
|
||||||
|
?.substringBefore("\"")
|
||||||
|
?.toInt()
|
||||||
|
|
||||||
override fun zappedAuthor() = tags
|
override fun zappedAuthor() = tags
|
||||||
.filter { it.firstOrNull() == "p" }
|
.filter { it.firstOrNull() == "p" }
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.components
|
package com.vitorpamplona.amethyst.ui.actions
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@@ -21,7 +21,6 @@ import androidx.compose.ui.text.input.KeyboardType
|
|||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.ui.actions.NewPollViewModel
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun NewPollVoteValueRange(pollViewModel: NewPollViewModel) {
|
fun NewPollVoteValueRange(pollViewModel: NewPollViewModel) {
|
||||||
@@ -93,7 +92,7 @@ fun NewPollVoteValueRange(pollViewModel: NewPollViewModel) {
|
|||||||
},
|
},
|
||||||
placeholder = {
|
placeholder = {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.poll_zap_amount),
|
text = stringResource(R.string.sats),
|
||||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -112,7 +111,7 @@ fun NewPollVoteValueRange(pollViewModel: NewPollViewModel) {
|
|||||||
},
|
},
|
||||||
placeholder = {
|
placeholder = {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.poll_zap_amount),
|
text = stringResource(R.string.sats),
|
||||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@@ -1,13 +1,11 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.note
|
package com.vitorpamplona.amethyst.ui.note
|
||||||
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.compose.foundation.BorderStroke
|
import androidx.compose.foundation.*
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
|
||||||
import androidx.compose.foundation.border
|
|
||||||
import androidx.compose.foundation.combinedClickable
|
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.*
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Bolt
|
import androidx.compose.material.icons.filled.Bolt
|
||||||
@@ -15,17 +13,18 @@ import androidx.compose.material.icons.outlined.Bolt
|
|||||||
import androidx.compose.material.ripple.rememberRipple
|
import androidx.compose.material.ripple.rememberRipple
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.semantics.Role
|
import androidx.compose.ui.semantics.Role
|
||||||
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.IntOffset
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.compose.ui.window.Popup
|
import androidx.compose.ui.window.Dialog
|
||||||
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
@@ -34,6 +33,7 @@ import com.vitorpamplona.amethyst.ui.components.TranslateableRichTextViewer
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PollNote(
|
fun PollNote(
|
||||||
@@ -44,6 +44,7 @@ fun PollNote(
|
|||||||
navController: NavController
|
navController: NavController
|
||||||
) {
|
) {
|
||||||
val pollEvent = note.event as PollNoteEvent
|
val pollEvent = note.event as PollNoteEvent
|
||||||
|
val consensusThreshold = pollEvent.consensusThreshold()
|
||||||
|
|
||||||
pollEvent.pollOptions().forEach { poll_op ->
|
pollEvent.pollOptions().forEach { poll_op ->
|
||||||
Row(Modifier.fillMaxWidth()) {
|
Row(Modifier.fillMaxWidth()) {
|
||||||
@@ -86,6 +87,15 @@ fun ZapVote(
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
val pollEvent = baseNote.event as PollNoteEvent
|
||||||
|
val valueMaximum = pollEvent.valueMaximum()
|
||||||
|
val valueMinimum = pollEvent.valueMinimum()
|
||||||
|
|
||||||
|
val isPollClosed: Boolean = pollEvent.closedAt()?.let { // allow 2 minute leeway for zap to propagate
|
||||||
|
baseNote.createdAt()?.plus(it * (86400 + 120))!! > Date().time / 1000
|
||||||
|
} == true
|
||||||
|
val isVoteAmountAtomic = valueMaximum != null && valueMinimum != null && valueMinimum == valueMaximum
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.then(Modifier.size(20.dp))
|
.then(Modifier.size(20.dp))
|
||||||
@@ -104,10 +114,20 @@ fun ZapVote(
|
|||||||
)
|
)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
} else if (account.zapAmountChoices.size == 1) {
|
} else if (isPollClosed) {
|
||||||
|
scope.launch {
|
||||||
|
Toast
|
||||||
|
.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.poll_is_closed),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
} else if (isVoteAmountAtomic) {
|
||||||
accountViewModel.zap(
|
accountViewModel.zap(
|
||||||
baseNote,
|
baseNote,
|
||||||
account.zapAmountChoices.first() * 1000,
|
valueMaximum!!.toLong() * 1000,
|
||||||
pollOption,
|
pollOption,
|
||||||
"",
|
"",
|
||||||
context
|
context
|
||||||
@@ -118,7 +138,7 @@ fun ZapVote(
|
|||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (account.zapAmountChoices.size > 1) {
|
} else {
|
||||||
wantsToZap = true
|
wantsToZap = true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -130,6 +150,8 @@ fun ZapVote(
|
|||||||
baseNote,
|
baseNote,
|
||||||
accountViewModel,
|
accountViewModel,
|
||||||
pollOption,
|
pollOption,
|
||||||
|
valueMinimum,
|
||||||
|
valueMaximum,
|
||||||
onDismiss = {
|
onDismiss = {
|
||||||
wantsToZap = false
|
wantsToZap = false
|
||||||
},
|
},
|
||||||
@@ -141,7 +163,7 @@ fun ZapVote(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (zappedNote?.isZappedBy(account.userProfile()) == true) {
|
if (zappedNote?.isPollOptionZapped(pollOption) == true) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Bolt,
|
imageVector = Icons.Default.Bolt,
|
||||||
contentDescription = stringResource(R.string.zaps),
|
contentDescription = stringResource(R.string.zaps),
|
||||||
@@ -159,7 +181,7 @@ fun ZapVote(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
showAmount(zappedNote?.zappedAmount()),
|
showAmount(zappedNote?.zappedPollOptionAmount(pollOption)),
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
@@ -172,25 +194,107 @@ fun ZapVoteAmountChoicePopup(
|
|||||||
baseNote: Note,
|
baseNote: Note,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
pollOption: Int,
|
pollOption: Int,
|
||||||
|
valueMinimum: Int?,
|
||||||
|
valueMaximum: Int?,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
onError: (text: String) -> Unit
|
onError: (text: String) -> Unit
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
var textAmount by rememberSaveable { mutableStateOf("") }
|
||||||
val account = accountState?.account ?: return
|
|
||||||
|
|
||||||
Popup(
|
val placeHolderText = if (valueMinimum == null && valueMaximum == null) {
|
||||||
alignment = Alignment.BottomCenter,
|
stringResource(R.string.sats)
|
||||||
offset = IntOffset(0, -50),
|
} else if (valueMinimum == null) {
|
||||||
onDismissRequest = { onDismiss() }
|
"1—$valueMaximum " + stringResource(R.string.sats)
|
||||||
|
} else if (valueMaximum == null) {
|
||||||
|
">$valueMinimum " + stringResource(R.string.sats)
|
||||||
|
} else {
|
||||||
|
"$valueMinimum—$valueMaximum " + stringResource(R.string.sats)
|
||||||
|
}
|
||||||
|
|
||||||
|
val amount = if (textAmount.isEmpty()) { null } else {
|
||||||
|
try {
|
||||||
|
textAmount.toLong()
|
||||||
|
} catch (e: Exception) { null }
|
||||||
|
}
|
||||||
|
|
||||||
|
var isValidAmount = false
|
||||||
|
if (amount == null) {
|
||||||
|
isValidAmount = false
|
||||||
|
} else if (valueMinimum == null && valueMaximum == null) {
|
||||||
|
if (amount > 0) {
|
||||||
|
isValidAmount = true
|
||||||
|
}
|
||||||
|
} else if (valueMinimum == null) {
|
||||||
|
if (amount > 0 && amount <= valueMaximum!!) {
|
||||||
|
isValidAmount = true
|
||||||
|
}
|
||||||
|
} else if (valueMaximum == null) {
|
||||||
|
if (amount >= valueMinimum) {
|
||||||
|
isValidAmount = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ((valueMinimum <= amount) && (amount <= valueMaximum)) {
|
||||||
|
isValidAmount = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
FlowRow(horizontalArrangement = Arrangement.Center) {
|
Surface {
|
||||||
account.zapAmountChoices.forEach { amountInSats ->
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.Center,
|
||||||
|
modifier = Modifier
|
||||||
|
.background(MaterialTheme.colors.primary)
|
||||||
|
.padding(10.dp)
|
||||||
|
) {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = textAmount,
|
||||||
|
onValueChange = { textAmount = it },
|
||||||
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||||
|
modifier = Modifier.width(150.dp),
|
||||||
|
colors = if (isValidAmount) colorValid else colorInValid,
|
||||||
|
label = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.poll_zap_amount),
|
||||||
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
placeholder = {
|
||||||
|
Text(
|
||||||
|
text = placeHolderText,
|
||||||
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (amount != null && isValidAmount) {
|
||||||
Button(
|
Button(
|
||||||
modifier = Modifier.padding(horizontal = 3.dp),
|
modifier = Modifier.padding(horizontal = 3.dp),
|
||||||
onClick = {
|
onClick = {
|
||||||
accountViewModel.zap(baseNote, amountInSats * 1000, pollOption, "", context, onError)
|
accountViewModel.zap(
|
||||||
|
baseNote,
|
||||||
|
amount * 1000,
|
||||||
|
pollOption,
|
||||||
|
"",
|
||||||
|
context,
|
||||||
|
onError
|
||||||
|
)
|
||||||
onDismiss()
|
onDismiss()
|
||||||
},
|
},
|
||||||
shape = RoundedCornerShape(20.dp),
|
shape = RoundedCornerShape(20.dp),
|
||||||
@@ -200,12 +304,19 @@ fun ZapVoteAmountChoicePopup(
|
|||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
"⚡ ${showAmount(amountInSats.toBigDecimal().setScale(1))}",
|
"⚡ ${showAmount(amount.toBigDecimal().setScale(1))}",
|
||||||
color = Color.White,
|
color = Color.White,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
modifier = Modifier.combinedClickable(
|
modifier = Modifier.combinedClickable(
|
||||||
onClick = {
|
onClick = {
|
||||||
accountViewModel.zap(baseNote, amountInSats * 1000, pollOption, "", context, onError)
|
accountViewModel.zap(
|
||||||
|
baseNote,
|
||||||
|
amount * 1000,
|
||||||
|
pollOption,
|
||||||
|
"",
|
||||||
|
context,
|
||||||
|
onError
|
||||||
|
)
|
||||||
onDismiss()
|
onDismiss()
|
||||||
},
|
},
|
||||||
onLongClick = {}
|
onLongClick = {}
|
||||||
@@ -215,4 +326,5 @@ fun ZapVoteAmountChoicePopup(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -231,8 +231,9 @@
|
|||||||
<string name="poll_zap_value_max">Zap maximum</string>
|
<string name="poll_zap_value_max">Zap maximum</string>
|
||||||
<string name="poll_consensus_threshold">Consensus</string>
|
<string name="poll_consensus_threshold">Consensus</string>
|
||||||
<string name="poll_consensus_threshold_percent">(0–100)%</string>
|
<string name="poll_consensus_threshold_percent">(0–100)%</string>
|
||||||
<string name="poll_zap_amount">sats</string>
|
|
||||||
<string name="poll_closing_time">Close after</string>
|
<string name="poll_closing_time">Close after</string>
|
||||||
<string name="poll_closing_time_days">days</string>
|
<string name="poll_closing_time_days">days</string>
|
||||||
|
<string name="poll_is_closed">Poll is closed to new votes</string>
|
||||||
|
<string name="poll_zap_amount">Zap amount</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
Reference in New Issue
Block a user