Making poll votes work with Private Zaps.
This commit is contained in:
@@ -0,0 +1,139 @@
|
|||||||
|
package com.vitorpamplona.amethyst
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
|
import com.vitorpamplona.amethyst.service.model.Event
|
||||||
|
import com.vitorpamplona.amethyst.service.model.LnZapEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.LnZapRequestEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.LnZapRequestEvent.Companion.createEncryptionPrivateKey
|
||||||
|
import fr.acinq.secp256k1.Hex
|
||||||
|
import junit.framework.TestCase.assertNotNull
|
||||||
|
import junit.framework.TestCase.fail
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class PrivateZapTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testPollZap() {
|
||||||
|
val poll = Event.Companion.fromJson(
|
||||||
|
"""{
|
||||||
|
"content": "New poll \n\n #zappoll",
|
||||||
|
"created_at": 1682440713,
|
||||||
|
"id": "16291ba452bb0786a4bf5c278d38de73c96b58c056ed75c5ea466b0795197288",
|
||||||
|
"kind": 6969,
|
||||||
|
"pubkey": "f8ff11c7a7d3478355d3b4d174e5a473797a906ea4aa61aa9b6bc0652c1ea17a",
|
||||||
|
"sig": "ac05fa4004c3f7c42913c87b11bf9714bb61a3f0940863a6b9ff0f8105b399add72dbc09bf944c79b9a72ef009ec6905adedbd2c4c8fb3d2f57007bad8fcb279",
|
||||||
|
"tags": [
|
||||||
|
[
|
||||||
|
"poll_option",
|
||||||
|
"0",
|
||||||
|
"Test 1"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"poll_option",
|
||||||
|
"1",
|
||||||
|
"Test 2"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"value_maximum",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"value_minimum",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"consensus_threshold",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"closed_at",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"seenOn": [
|
||||||
|
"wss://relay.damus.io/"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
""",
|
||||||
|
true
|
||||||
|
)
|
||||||
|
|
||||||
|
val loggedIn = Hex.decode("e8e7197ccc53c9ed4cf9b1c8dce085475fa1ffdd71f2c14e44fe23d0bdf77598")
|
||||||
|
|
||||||
|
val privateZapRequest = LnZapRequestEvent.create(
|
||||||
|
poll,
|
||||||
|
setOf("wss://relay.damus.io/"),
|
||||||
|
loggedIn,
|
||||||
|
0,
|
||||||
|
"",
|
||||||
|
LnZapEvent.ZapType.PRIVATE
|
||||||
|
)
|
||||||
|
|
||||||
|
val recepientPK = privateZapRequest.zappedAuthor().firstOrNull()
|
||||||
|
val recepientPost = privateZapRequest.zappedPost().firstOrNull()
|
||||||
|
|
||||||
|
if (recepientPK != null && recepientPost != null) {
|
||||||
|
val privateKey = createEncryptionPrivateKey(loggedIn.toHexKey(), recepientPost, privateZapRequest.createdAt)
|
||||||
|
val decodedPrivateZap =
|
||||||
|
LnZapRequestEvent.checkForPrivateZap(privateZapRequest, privateKey, recepientPK)
|
||||||
|
|
||||||
|
println(decodedPrivateZap?.toJson())
|
||||||
|
assertNotNull(decodedPrivateZap)
|
||||||
|
} else {
|
||||||
|
fail("Should not be null")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testKind1PrivateZap() {
|
||||||
|
val textNote = Event.Companion.fromJson(
|
||||||
|
"""{
|
||||||
|
"content": "Testing copied author. \n\nnostr:npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z",
|
||||||
|
"created_at": 1682369982,
|
||||||
|
"id": "c757e1371d715c711ec9ef9740a3df6475d64b3d0af45ffcbfca08d273baf1c1",
|
||||||
|
"kind": 1,
|
||||||
|
"pubkey": "f8ff11c7a7d3478355d3b4d174e5a473797a906ea4aa61aa9b6bc0652c1ea17a",
|
||||||
|
"sig": "1fb5b6fd980f4c2ef058d5f4f7b166c0e5fb21eff26fe9cacd87a9aa4feb344485841ebcc26a233bf8d6ea0a66acf0db2bfdb11ad1cb04bcea4cfa3e78c3eaf1",
|
||||||
|
"tags": [
|
||||||
|
[
|
||||||
|
"p",
|
||||||
|
"460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"seenOn": [
|
||||||
|
"wss://relay.damus.io/"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
""",
|
||||||
|
true
|
||||||
|
)
|
||||||
|
|
||||||
|
val loggedIn = Hex.decode("e8e7197ccc53c9ed4cf9b1c8dce085475fa1ffdd71f2c14e44fe23d0bdf77598")
|
||||||
|
|
||||||
|
val privateZapRequest = LnZapRequestEvent.create(
|
||||||
|
textNote,
|
||||||
|
setOf("wss://relay.damus.io/", "wss://relay.damus2.io/", "wss://relay.damus3.io/"),
|
||||||
|
loggedIn,
|
||||||
|
null,
|
||||||
|
"test",
|
||||||
|
LnZapEvent.ZapType.PRIVATE
|
||||||
|
)
|
||||||
|
|
||||||
|
val recepientPK = privateZapRequest.zappedAuthor().firstOrNull()
|
||||||
|
val recepientPost = privateZapRequest.zappedPost().firstOrNull()
|
||||||
|
|
||||||
|
if (recepientPK != null && recepientPost != null) {
|
||||||
|
val privateKey = createEncryptionPrivateKey(loggedIn.toHexKey(), recepientPost, privateZapRequest.createdAt)
|
||||||
|
val decodedPrivateZap =
|
||||||
|
LnZapRequestEvent.checkForPrivateZap(privateZapRequest, privateKey, recepientPK)
|
||||||
|
|
||||||
|
println(decodedPrivateZap?.toJson())
|
||||||
|
assertNotNull(decodedPrivateZap)
|
||||||
|
} else {
|
||||||
|
fail("Should not be null")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -692,7 +692,7 @@ class Account(
|
|||||||
|
|
||||||
event.plainContent(loggedIn.privKey!!, pubkeyToUse.toByteArray())
|
event.plainContent(loggedIn.privKey!!, pubkeyToUse.toByteArray())
|
||||||
} else if (event is LnZapRequestEvent && loggedIn.privKey != null) {
|
} else if (event is LnZapRequestEvent && loggedIn.privKey != null) {
|
||||||
LnZapRequestEvent.checkForPrivateZap(event, loggedIn.privKey!!)?.content()
|
decryptZapContentAuthor(note)?.content()
|
||||||
} else {
|
} else {
|
||||||
event?.content()
|
event?.content()
|
||||||
}
|
}
|
||||||
@@ -700,8 +700,43 @@ class Account(
|
|||||||
|
|
||||||
fun decryptZapContentAuthor(note: Note): Event? {
|
fun decryptZapContentAuthor(note: Note): Event? {
|
||||||
val event = note.event
|
val event = note.event
|
||||||
return if (event is LnZapRequestEvent && loggedIn.privKey != null) {
|
val loggedInPrivateKey = loggedIn.privKey
|
||||||
LnZapRequestEvent.checkForPrivateZap(event, loggedIn.privKey!!)
|
|
||||||
|
return if (event is LnZapRequestEvent && loggedInPrivateKey != null && event.isPrivateZap()) {
|
||||||
|
val recipientPK = event.zappedAuthor().firstOrNull()
|
||||||
|
val recipientPost = event.zappedPost().firstOrNull()
|
||||||
|
|
||||||
|
if (recipientPK == userProfile().pubkeyHex) {
|
||||||
|
// if the receiver is logged in, these are the params.
|
||||||
|
val privateKeyToUse = loggedInPrivateKey
|
||||||
|
val pubkeyToUse = event.pubKey
|
||||||
|
|
||||||
|
LnZapRequestEvent.checkForPrivateZap(event, privateKeyToUse, pubkeyToUse)
|
||||||
|
} else {
|
||||||
|
// if the sender is logged in, these are the params
|
||||||
|
val altPubkeyToUse = recipientPK
|
||||||
|
val altPrivateKeyToUse = if (recipientPost != null) {
|
||||||
|
LnZapRequestEvent.createEncryptionPrivateKey(
|
||||||
|
loggedInPrivateKey.toHexKey(),
|
||||||
|
recipientPost,
|
||||||
|
event.createdAt
|
||||||
|
)
|
||||||
|
} else if (recipientPK != null) {
|
||||||
|
LnZapRequestEvent.createEncryptionPrivateKey(
|
||||||
|
loggedInPrivateKey.toHexKey(),
|
||||||
|
recipientPK,
|
||||||
|
event.createdAt
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (altPrivateKeyToUse != null && altPubkeyToUse != null) {
|
||||||
|
LnZapRequestEvent.checkForPrivateZap(event, altPrivateKeyToUse, altPubkeyToUse)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -200,9 +200,11 @@ open class Note(val idHex: String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isZappedBy(user: User): Boolean {
|
fun isZappedBy(user: User, account: Account): Boolean {
|
||||||
// Zaps who the requester was the user
|
// Zaps who the requester was the user
|
||||||
return zaps.any { it.key.author === user }
|
return zaps.any {
|
||||||
|
it.key.author === user || account.decryptZapContentAuthor(it.key)?.pubKey == user.pubkeyHex
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isReactedBy(user: User): Boolean {
|
fun isReactedBy(user: User): Boolean {
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ class LnZapRequestEvent(
|
|||||||
|
|
||||||
fun zappedAuthor() = tags.filter { it.size > 1 && it[0] == "p" }.map { it[1] }
|
fun zappedAuthor() = tags.filter { it.size > 1 && it[0] == "p" }.map { it[1] }
|
||||||
|
|
||||||
|
fun isPrivateZap() = tags.any { t -> t.size >= 2 && t[0] == "anon" && t[1].isNotBlank() }
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val kind = 9734
|
const val kind = 9734
|
||||||
|
|
||||||
@@ -135,26 +137,27 @@ class LnZapRequestEvent(
|
|||||||
if (parts.size != 2) {
|
if (parts.size != 2) {
|
||||||
throw IllegalArgumentException("Invalid message format")
|
throw IllegalArgumentException("Invalid message format")
|
||||||
}
|
}
|
||||||
val iv = parts[1].run { Bech32.decode(this) }
|
val iv = parts[1].run { Bech32.decode(this).second }
|
||||||
val encryptedMsg = parts.first().run { Bech32.decode(this) }
|
val encryptedMsg = parts.first().run { Bech32.decode(this).second }
|
||||||
|
val encryptedBytes = Bech32.five2eight(encryptedMsg, 0)
|
||||||
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
|
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
|
||||||
cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(sharedSecret, "AES"), IvParameterSpec(Bech32.five2eight(iv.second, 0)))
|
cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(sharedSecret, "AES"), IvParameterSpec(Bech32.five2eight(iv, 0)))
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val decryptedMsgBytes = cipher.doFinal(Bech32.five2eight(encryptedMsg.second, 0))
|
val decryptedMsgBytes = cipher.doFinal(encryptedBytes)
|
||||||
return String(decryptedMsgBytes)
|
return String(decryptedMsgBytes)
|
||||||
} catch (ex: BadPaddingException) {
|
} catch (ex: BadPaddingException) {
|
||||||
throw IllegalArgumentException("Bad padding")
|
throw IllegalArgumentException("Bad padding: ${ex.message}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun checkForPrivateZap(zaprequest: Event, loggedInUserPrivKey: ByteArray): Event? {
|
fun checkForPrivateZap(zapRequest: LnZapRequestEvent, loggedInUserPrivKey: ByteArray, pubKey: HexKey): Event? {
|
||||||
val anonTag = zaprequest.tags.firstOrNull { t -> t.size >= 2 && t[0] == "anon" }
|
val anonTag = zapRequest.tags.firstOrNull { t -> t.size >= 2 && t[0] == "anon" }
|
||||||
if (anonTag != null) {
|
if (anonTag != null) {
|
||||||
val encnote = anonTag[1]
|
val encnote = anonTag[1]
|
||||||
if (encnote != null && encnote != "") {
|
if (encnote.isNotBlank()) {
|
||||||
try {
|
try {
|
||||||
val note = decryptPrivateZapMessage(encnote, loggedInUserPrivKey, zaprequest.pubKey.toByteArray())
|
val note = decryptPrivateZapMessage(encnote, loggedInUserPrivKey, pubKey.toByteArray())
|
||||||
val decryptedEvent = fromJson(note)
|
val decryptedEvent = fromJson(note)
|
||||||
if (decryptedEvent.kind == 9733) {
|
if (decryptedEvent.kind == 9733) {
|
||||||
return decryptedEvent
|
return decryptedEvent
|
||||||
|
|||||||
@@ -31,12 +31,12 @@ import androidx.compose.ui.window.Popup
|
|||||||
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
|
||||||
import com.vitorpamplona.amethyst.service.model.LnZapEvent
|
|
||||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||||
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.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
@@ -51,7 +51,11 @@ fun PollNote(
|
|||||||
val zapsState by baseNote.live().zaps.observeAsState()
|
val zapsState by baseNote.live().zaps.observeAsState()
|
||||||
val zappedNote = zapsState?.note ?: return
|
val zappedNote = zapsState?.note ?: return
|
||||||
|
|
||||||
|
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||||
|
val account = accountState?.account ?: return
|
||||||
|
|
||||||
val pollViewModel = PollNoteViewModel()
|
val pollViewModel = PollNoteViewModel()
|
||||||
|
pollViewModel.account = account
|
||||||
pollViewModel.load(zappedNote)
|
pollViewModel.load(zappedNote)
|
||||||
|
|
||||||
pollViewModel.pollEvent?.pollOptions()?.forEach { poll_op ->
|
pollViewModel.pollEvent?.pollOptions()?.forEach { poll_op ->
|
||||||
@@ -69,7 +73,7 @@ fun PollNote(
|
|||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = Modifier.padding(vertical = 3.dp)
|
modifier = Modifier.padding(vertical = 3.dp)
|
||||||
) {
|
) {
|
||||||
if (accountViewModel.isLoggedUser(zappedNote.author) || zappedNote.isZappedBy(accountViewModel.userProfile())) {
|
if (pollViewModel.canZap()) {
|
||||||
ZapVote(
|
ZapVote(
|
||||||
baseNote,
|
baseNote,
|
||||||
accountViewModel,
|
accountViewModel,
|
||||||
@@ -288,7 +292,17 @@ fun ZapVote(
|
|||||||
|
|
||||||
clickablePrepend()
|
clickablePrepend()
|
||||||
|
|
||||||
if (pollViewModel.isPollOptionZappedBy(pollOption, accountViewModel.userProfile())) {
|
var optionWasZappedByLoggedInUser by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
LaunchedEffect(key1 = zappedNote) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
if (!optionWasZappedByLoggedInUser) {
|
||||||
|
optionWasZappedByLoggedInUser = pollViewModel.isPollOptionZappedBy(pollOption, accountViewModel.userProfile())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (optionWasZappedByLoggedInUser) {
|
||||||
zappingProgress = 1f
|
zappingProgress = 1f
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Bolt,
|
imageVector = Icons.Default.Bolt,
|
||||||
@@ -315,8 +329,18 @@ fun ZapVote(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var wasZappedByLoggedInUser by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
LaunchedEffect(key1 = zappedNote) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
if (!wasZappedByLoggedInUser) {
|
||||||
|
wasZappedByLoggedInUser = zappedNote?.isZappedBy(accountViewModel.userProfile(), account) == true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// only show tallies after a user has zapped note
|
// only show tallies after a user has zapped note
|
||||||
if (baseNote.author == accountViewModel.userProfile() || zappedNote?.isZappedBy(accountViewModel.userProfile()) == true) {
|
if (baseNote.author == accountViewModel.userProfile() || wasZappedByLoggedInUser) {
|
||||||
Text(
|
Text(
|
||||||
showAmount(pollViewModel.zappedPollOptionAmount(pollOption)),
|
showAmount(pollViewModel.zappedPollOptionAmount(pollOption)),
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
@@ -378,7 +402,7 @@ fun FilteredZapAmountChoicePopup(
|
|||||||
context,
|
context,
|
||||||
onError,
|
onError,
|
||||||
onProgress,
|
onProgress,
|
||||||
LnZapEvent.ZapType.PUBLIC
|
account.defaultZapType
|
||||||
)
|
)
|
||||||
onDismiss()
|
onDismiss()
|
||||||
}
|
}
|
||||||
@@ -404,7 +428,7 @@ fun FilteredZapAmountChoicePopup(
|
|||||||
context,
|
context,
|
||||||
onError,
|
onError,
|
||||||
onProgress,
|
onProgress,
|
||||||
LnZapEvent.ZapType.PUBLIC
|
account.defaultZapType
|
||||||
)
|
)
|
||||||
onDismiss()
|
onDismiss()
|
||||||
}
|
}
|
||||||
@@ -496,8 +520,7 @@ fun ZapVoteAmountChoicePopup(
|
|||||||
"",
|
"",
|
||||||
context,
|
context,
|
||||||
onError,
|
onError,
|
||||||
onProgress,
|
onProgress
|
||||||
LnZapEvent.ZapType.PUBLIC
|
|
||||||
)
|
)
|
||||||
onDismiss()
|
onDismiss()
|
||||||
}
|
}
|
||||||
@@ -522,8 +545,7 @@ fun ZapVoteAmountChoicePopup(
|
|||||||
"",
|
"",
|
||||||
context,
|
context,
|
||||||
onError,
|
onError,
|
||||||
onProgress,
|
onProgress
|
||||||
LnZapEvent.ZapType.PUBLIC
|
|
||||||
)
|
)
|
||||||
onDismiss()
|
onDismiss()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,13 @@ class PollNoteViewModel {
|
|||||||
totalZapped = totalZapped()
|
totalZapped = totalZapped()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun canZap(): Boolean {
|
||||||
|
val account = account ?: return false
|
||||||
|
val user = account.userProfile() ?: return false
|
||||||
|
val note = pollNote ?: return false
|
||||||
|
return user != note.author && !note.isZappedBy(user, account)
|
||||||
|
}
|
||||||
|
|
||||||
fun isVoteAmountAtomic() = valueMaximum != null && valueMinimum != null && valueMinimum == valueMaximum
|
fun isVoteAmountAtomic() = valueMaximum != null && valueMinimum != null && valueMinimum == valueMaximum
|
||||||
|
|
||||||
fun isPollClosed(): Boolean = closedAt?.let { // allow 2 minute leeway for zap to propagate
|
fun isPollClosed(): Boolean = closedAt?.let { // allow 2 minute leeway for zap to propagate
|
||||||
@@ -87,14 +94,12 @@ class PollNoteViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun isPollOptionZappedBy(option: Int, user: User): Boolean {
|
fun isPollOptionZappedBy(option: Int, user: User): Boolean {
|
||||||
if (pollNote?.zaps?.any { it.key.author === user } == true) {
|
return pollNote!!.zaps
|
||||||
pollNote!!.zaps
|
.any {
|
||||||
.any {
|
val zapEvent = it.value?.event as? LnZapEvent
|
||||||
val event = it.value?.event as? LnZapEvent
|
val privateZapAuthor = account?.decryptZapContentAuthor(it.key)
|
||||||
event?.zappedPollOption() == option && event.zappedRequestAuthor() == user.pubkeyHex
|
zapEvent?.zappedPollOption() == option && (it.key.author?.pubkeyHex == user.pubkeyHex || privateZapAuthor?.pubKey == user.pubkeyHex)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun zappedPollOptionAmount(option: Int): BigDecimal {
|
fun zappedPollOptionAmount(option: Int): BigDecimal {
|
||||||
|
|||||||
@@ -314,6 +314,16 @@ fun ZapReaction(
|
|||||||
|
|
||||||
var zappingProgress by remember { mutableStateOf(0f) }
|
var zappingProgress by remember { mutableStateOf(0f) }
|
||||||
|
|
||||||
|
var wasZappedByLoggedInUser by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
LaunchedEffect(key1 = zappedNote) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
if (!wasZappedByLoggedInUser) {
|
||||||
|
wasZappedByLoggedInUser = zappedNote?.isZappedBy(account.userProfile(), account) == true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = CenterVertically,
|
verticalAlignment = CenterVertically,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -412,7 +422,7 @@ fun ZapReaction(
|
|||||||
ZapCustomDialog({ wantsToSetCustomZap = false }, account = account, accountViewModel, baseNote)
|
ZapCustomDialog({ wantsToSetCustomZap = false }, account = account, accountViewModel, baseNote)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (zappedNote?.isZappedBy(account.userProfile()) == true) {
|
if (wasZappedByLoggedInUser) {
|
||||||
zappingProgress = 1f
|
zappingProgress = 1f
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Bolt,
|
imageVector = Icons.Default.Bolt,
|
||||||
|
|||||||
@@ -55,6 +55,10 @@ class AccountViewModel(private val account: Account) : ViewModel() {
|
|||||||
account.delete(account.boostsTo(note))
|
account.delete(account.boostsTo(note))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun zap(note: Note, amount: Long, pollOption: Int?, message: String, context: Context, onError: (String) -> Unit, onProgress: (percent: Float) -> Unit) {
|
||||||
|
zap(note, amount, pollOption, message, context, onError, onProgress, account.defaultZapType)
|
||||||
|
}
|
||||||
|
|
||||||
fun zap(note: Note, amount: Long, pollOption: Int?, message: String, context: Context, onError: (String) -> Unit, onProgress: (percent: Float) -> Unit, zapType: LnZapEvent.ZapType) {
|
fun zap(note: Note, amount: Long, pollOption: Int?, message: String, context: Context, onError: (String) -> Unit, onProgress: (percent: Float) -> Unit, zapType: LnZapEvent.ZapType) {
|
||||||
val lud16 = note.author?.info?.lud16?.trim() ?: note.author?.info?.lud06?.trim()
|
val lud16 = note.author?.info?.lud16?.trim() ?: note.author?.info?.lud06?.trim()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user