mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-09-20 18:21:12 +02:00
Performance Improvements for Zaps in Polls.
This commit is contained in:
@@ -595,7 +595,7 @@ object LocalCache {
|
|||||||
// Already processed this event.
|
// Already processed this event.
|
||||||
if (note.event != null) return
|
if (note.event != null) return
|
||||||
|
|
||||||
val zapRequest = event.containedPost()?.id?.let { getOrCreateNote(it) }
|
val zapRequest = event.zapRequest?.id?.let { getOrCreateNote(it) }
|
||||||
|
|
||||||
val author = getOrCreateUser(event.pubKey)
|
val author = getOrCreateUser(event.pubKey)
|
||||||
val mentions = event.zappedAuthor().mapNotNull { checkGetOrCreateUser(it) }
|
val mentions = event.zappedAuthor().mapNotNull { checkGetOrCreateUser(it) }
|
||||||
|
@@ -76,7 +76,7 @@ abstract class NostrDataSource(val debugName: String) {
|
|||||||
is DeletionEvent -> LocalCache.consume(event)
|
is DeletionEvent -> LocalCache.consume(event)
|
||||||
|
|
||||||
is LnZapEvent -> {
|
is LnZapEvent -> {
|
||||||
event.containedPost()?.let { onEvent(it, subscriptionId, relay) }
|
event.zapRequest?.let { onEvent(it, subscriptionId, relay) }
|
||||||
LocalCache.consume(event)
|
LocalCache.consume(event)
|
||||||
}
|
}
|
||||||
is LnZapRequestEvent -> LocalCache.consume(event)
|
is LnZapRequestEvent -> LocalCache.consume(event)
|
||||||
|
@@ -4,7 +4,6 @@ import android.util.Log
|
|||||||
import com.vitorpamplona.amethyst.model.HexKey
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
import com.vitorpamplona.amethyst.service.lnurl.LnInvoiceUtil
|
import com.vitorpamplona.amethyst.service.lnurl.LnInvoiceUtil
|
||||||
import com.vitorpamplona.amethyst.service.relays.Client
|
import com.vitorpamplona.amethyst.service.relays.Client
|
||||||
import java.math.BigDecimal
|
|
||||||
|
|
||||||
class LnZapEvent(
|
class LnZapEvent(
|
||||||
id: HexKey,
|
id: HexKey,
|
||||||
@@ -14,25 +13,37 @@ class LnZapEvent(
|
|||||||
content: String,
|
content: String,
|
||||||
sig: HexKey
|
sig: HexKey
|
||||||
) : LnZapEventInterface, Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
) : LnZapEventInterface, Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||||
|
// This event is also kept in LocalCache (same object)
|
||||||
|
@Transient val zapRequest: LnZapRequestEvent?
|
||||||
|
|
||||||
override fun zappedPost() = tags
|
private fun containedPost(): LnZapRequestEvent? = try {
|
||||||
.filter { it.firstOrNull() == "e" }
|
description()?.ifBlank { null }?.let {
|
||||||
.mapNotNull { it.getOrNull(1) }
|
fromJson(it, Client.lenient)
|
||||||
|
} as? LnZapRequestEvent
|
||||||
override fun zappedPollOption(): Int? = containedPost()?.tags
|
} catch (e: Exception) {
|
||||||
?.filter { it.firstOrNull() == POLL_OPTION }
|
Log.e("LnZapEvent", "Failed to Parse Contained Post ${description()}", e)
|
||||||
?.getOrNull(0)?.getOrNull(1)?.toInt()
|
null
|
||||||
|
|
||||||
override fun zappedAuthor() = tags
|
|
||||||
.filter { it.firstOrNull() == "p" }
|
|
||||||
.mapNotNull { it.getOrNull(1) }
|
|
||||||
|
|
||||||
override fun zappedRequestAuthor(): String? = containedPost()?.pubKey()
|
|
||||||
|
|
||||||
override fun amount(): BigDecimal? {
|
|
||||||
return amount
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
zapRequest = containedPost()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun zappedPost() = tags.filter { it.size > 1 && it[0] == "e" }.map { it[1] }
|
||||||
|
|
||||||
|
override fun zappedAuthor() = tags.filter { it.size > 1 && it[0] == "p" }.map { it[1] }
|
||||||
|
|
||||||
|
override fun zappedPollOption(): Int? = try {
|
||||||
|
zapRequest?.tags?.firstOrNull { it.size > 1 && it[0] == POLL_OPTION }?.get(1)?.toInt()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("LnZapEvent", "ZappedPollOption failed to parse", e)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun zappedRequestAuthor(): String? = zapRequest?.pubKey()
|
||||||
|
|
||||||
|
override fun amount() = amount
|
||||||
|
|
||||||
// Keeps this as a field because it's a heavier function used everywhere.
|
// Keeps this as a field because it's a heavier function used everywhere.
|
||||||
val amount by lazy {
|
val amount by lazy {
|
||||||
try {
|
try {
|
||||||
@@ -42,29 +53,14 @@ class LnZapEvent(
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun message(): String {
|
override fun message(): String {
|
||||||
return message
|
return content
|
||||||
}
|
|
||||||
val message = content
|
|
||||||
|
|
||||||
override fun containedPost(): Event? = try {
|
|
||||||
description()?.ifBlank { null }?.let {
|
|
||||||
fromJson(it, Client.lenient)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e("LnZapEvent", "Failed to Parse Contained Post ${description()}", e)
|
|
||||||
null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun lnInvoice(): String? = tags
|
private fun lnInvoice() = tags.firstOrNull { it.size > 1 && it[0] == "bolt11" }?.get(1)
|
||||||
.filter { it.firstOrNull() == "bolt11" }
|
|
||||||
.mapNotNull { it.getOrNull(1) }
|
|
||||||
.firstOrNull()
|
|
||||||
|
|
||||||
private fun description(): String? = tags
|
private fun description() = tags.firstOrNull { it.size > 1 && it[0] == "description" }?.get(1)
|
||||||
.filter { it.firstOrNull() == "description" }
|
|
||||||
.mapNotNull { it.getOrNull(1) }
|
|
||||||
.firstOrNull()
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val kind = 9735
|
const val kind = 9735
|
||||||
|
@@ -16,7 +16,5 @@ interface LnZapEventInterface : EventInterface {
|
|||||||
|
|
||||||
fun amount(): BigDecimal?
|
fun amount(): BigDecimal?
|
||||||
|
|
||||||
fun containedPost(): Event?
|
|
||||||
|
|
||||||
fun message(): String
|
fun message(): String
|
||||||
}
|
}
|
||||||
|
@@ -13,8 +13,10 @@ class LnZapRequestEvent(
|
|||||||
content: String,
|
content: String,
|
||||||
sig: HexKey
|
sig: HexKey
|
||||||
) : Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
) : Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||||
fun zappedPost() = tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) }
|
|
||||||
fun zappedAuthor() = tags.filter { it.firstOrNull() == "p" }.mapNotNull { it.getOrNull(1) }
|
fun zappedPost() = tags.filter { it.size > 1 && it[0] == "e" }.map { it[1] }
|
||||||
|
|
||||||
|
fun zappedAuthor() = tags.filter { it.size > 1 && it[0] == "p" }.map { it[1] }
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val kind = 9734
|
const val kind = 9734
|
||||||
|
@@ -20,24 +20,17 @@ class PollNoteEvent(
|
|||||||
content: String,
|
content: String,
|
||||||
sig: HexKey
|
sig: HexKey
|
||||||
) : BaseTextNoteEvent(id, pubKey, createdAt, kind, tags, content, sig) {
|
) : BaseTextNoteEvent(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||||
fun pollOptions(): Map<Int, String> {
|
fun pollOptions() =
|
||||||
val map = mutableMapOf<Int, String>()
|
tags.filter { it.size > 2 && it[0] == POLL_OPTION }
|
||||||
tags.filter { it.first() == POLL_OPTION }
|
.associate { it[1].toInt() to it[2] }
|
||||||
.forEach { map[it[1].toInt()] = it[2] }
|
|
||||||
return map
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getTagInt(property: String): Int? {
|
fun getTagInt(property: String): Int? {
|
||||||
val tagList = tags.filter {
|
val number = tags.firstOrNull() { it.size > 1 && it[0] == property }?.get(1)
|
||||||
it.firstOrNull() == property
|
|
||||||
}
|
|
||||||
val tag = tagList.getOrNull(0)
|
|
||||||
val s = tag?.getOrNull(1)
|
|
||||||
|
|
||||||
return if (s.isNullOrBlank() || s == "null") {
|
return if (number.isNullOrBlank() || number == "null") {
|
||||||
null
|
null
|
||||||
} else {
|
} else {
|
||||||
s.toInt()
|
number.toInt()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -48,9 +48,6 @@ fun PollNote(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
navController: NavController
|
navController: NavController
|
||||||
) {
|
) {
|
||||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
|
||||||
val account = accountState?.account ?: return
|
|
||||||
|
|
||||||
val zapsState by baseNote.live().zaps.observeAsState()
|
val zapsState by baseNote.live().zaps.observeAsState()
|
||||||
val zappedNote = zapsState?.note ?: return
|
val zappedNote = zapsState?.note ?: return
|
||||||
|
|
||||||
@@ -72,7 +69,7 @@ fun PollNote(
|
|||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = Modifier.padding(vertical = 3.dp)
|
modifier = Modifier.padding(vertical = 3.dp)
|
||||||
) {
|
) {
|
||||||
if (zappedNote.author == account.userProfile() || zappedNote.isZappedBy(account.userProfile())) {
|
if (accountViewModel.isLoggedUser(zappedNote.author) || zappedNote.isZappedBy(accountViewModel.userProfile())) {
|
||||||
ZapVote(
|
ZapVote(
|
||||||
baseNote,
|
baseNote,
|
||||||
accountViewModel,
|
accountViewModel,
|
||||||
@@ -90,7 +87,7 @@ fun PollNote(
|
|||||||
LinearProgressIndicator(
|
LinearProgressIndicator(
|
||||||
modifier = Modifier.matchParentSize(),
|
modifier = Modifier.matchParentSize(),
|
||||||
color = color,
|
color = color,
|
||||||
progress = optionTally
|
progress = optionTally.toFloat()
|
||||||
)
|
)
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
@@ -101,7 +98,7 @@ fun PollNote(
|
|||||||
modifier = Modifier.padding(horizontal = 10.dp).width(40.dp)
|
modifier = Modifier.padding(horizontal = 10.dp).width(40.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "${(optionTally * 100).roundToInt()}%",
|
text = "${(optionTally.toFloat() * 100).roundToInt()}%",
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -210,7 +207,7 @@ fun ZapVote(
|
|||||||
)
|
)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
} else if (zappedNote?.author == account.userProfile()) {
|
} else if (accountViewModel.isLoggedUser(zappedNote?.author)) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
Toast
|
Toast
|
||||||
.makeText(
|
.makeText(
|
||||||
|
@@ -5,6 +5,7 @@ import com.vitorpamplona.amethyst.model.Note
|
|||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.model.*
|
import com.vitorpamplona.amethyst.service.model.*
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
import java.math.RoundingMode
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class PollNoteViewModel {
|
class PollNoteViewModel {
|
||||||
@@ -16,7 +17,9 @@ class PollNoteViewModel {
|
|||||||
var valueMaximum: Int? = null
|
var valueMaximum: Int? = null
|
||||||
var valueMinimum: Int? = null
|
var valueMinimum: Int? = null
|
||||||
private var closedAt: Int? = null
|
private var closedAt: Int? = null
|
||||||
var consensusThreshold: Float? = null
|
var consensusThreshold: BigDecimal? = null
|
||||||
|
|
||||||
|
var totalZapped: BigDecimal = BigDecimal.ZERO
|
||||||
|
|
||||||
fun load(note: Note?) {
|
fun load(note: Note?) {
|
||||||
pollNote = note
|
pollNote = note
|
||||||
@@ -24,8 +27,10 @@ class PollNoteViewModel {
|
|||||||
pollOptions = pollEvent?.pollOptions()
|
pollOptions = pollEvent?.pollOptions()
|
||||||
valueMaximum = pollEvent?.getTagInt(VALUE_MAXIMUM)
|
valueMaximum = pollEvent?.getTagInt(VALUE_MAXIMUM)
|
||||||
valueMinimum = pollEvent?.getTagInt(VALUE_MINIMUM)
|
valueMinimum = pollEvent?.getTagInt(VALUE_MINIMUM)
|
||||||
consensusThreshold = pollEvent?.getTagInt(CONSENSUS_THRESHOLD)?.toFloat()?.div(100)
|
consensusThreshold = pollEvent?.getTagInt(CONSENSUS_THRESHOLD)?.toFloat()?.div(100)?.toBigDecimal()
|
||||||
closedAt = pollEvent?.getTagInt(CLOSED_AT)
|
closedAt = pollEvent?.getTagInt(CLOSED_AT)
|
||||||
|
|
||||||
|
totalZapped = totalZapped()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isVoteAmountAtomic() = valueMaximum != null && valueMinimum != null && valueMinimum == valueMaximum
|
fun isVoteAmountAtomic() = valueMaximum != null && valueMinimum != null && valueMinimum == valueMaximum
|
||||||
@@ -73,47 +78,39 @@ class PollNoteViewModel {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun optionVoteTally(op: Int): Float {
|
fun optionVoteTally(op: Int): BigDecimal {
|
||||||
val tally = zappedPollOptionAmount(op).toFloat().div(zappedVoteTotal())
|
return if (totalZapped.compareTo(BigDecimal.ZERO) > 0) {
|
||||||
return if (tally.isNaN()) { // catch div by 0
|
zappedPollOptionAmount(op).divide(totalZapped, 2, RoundingMode.HALF_UP)
|
||||||
0f
|
} else {
|
||||||
} else { tally }
|
BigDecimal.ZERO
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun zappedVoteTotal(): Float {
|
|
||||||
var total = 0f
|
|
||||||
pollOptions?.keys?.forEach {
|
|
||||||
total += zappedPollOptionAmount(it).toFloat()
|
|
||||||
}
|
|
||||||
return total
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isPollOptionZappedBy(option: Int, user: User): Boolean {
|
fun isPollOptionZappedBy(option: Int, user: User): Boolean {
|
||||||
if (pollNote?.zaps?.any { it.key.author == user } == true) {
|
if (pollNote?.zaps?.any { it.key.author === user } == true) {
|
||||||
pollNote!!.zaps.mapNotNull { it.value?.event }
|
pollNote!!.zaps
|
||||||
.filterIsInstance<LnZapEvent>()
|
.any {
|
||||||
.map {
|
val event = it.value?.event as? LnZapEvent
|
||||||
val zappedOption = it.zappedPollOption()
|
event?.zappedPollOption() == option && event.zappedRequestAuthor() == user.pubkeyHex
|
||||||
if (zappedOption == option && it.zappedRequestAuthor() == user.pubkeyHex) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun zappedPollOptionAmount(option: Int): BigDecimal {
|
fun zappedPollOptionAmount(option: Int): BigDecimal {
|
||||||
return if (pollNote != null) {
|
return pollNote?.zaps?.values?.sumOf {
|
||||||
pollNote!!.zaps.mapNotNull { it.value?.event }
|
val event = it?.event as? LnZapEvent
|
||||||
.filterIsInstance<LnZapEvent>()
|
if (event?.zappedPollOption() == option) {
|
||||||
.mapNotNull {
|
event.amount ?: BigDecimal(0)
|
||||||
val zappedOption = it.zappedPollOption()
|
|
||||||
if (zappedOption == option) {
|
|
||||||
it.amount
|
|
||||||
} else { null }
|
|
||||||
}.sumOf { it }
|
|
||||||
} else {
|
} else {
|
||||||
BigDecimal(0)
|
BigDecimal(0)
|
||||||
}
|
}
|
||||||
|
} ?: BigDecimal(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun totalZapped(): BigDecimal {
|
||||||
|
return pollNote?.zaps?.values?.sumOf {
|
||||||
|
(it?.event as? LnZapEvent)?.amount ?: BigDecimal(0)
|
||||||
|
} ?: BigDecimal(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user