Merge branch 'main' into quote_problem
This commit is contained in:
@@ -54,8 +54,14 @@ class Account(
|
||||
val liveLanguages: AccountLiveData = AccountLiveData(this)
|
||||
val saveable: AccountLiveData = AccountLiveData(this)
|
||||
|
||||
var userProfileCache: User? = null
|
||||
|
||||
fun userProfile(): User {
|
||||
return LocalCache.getOrCreateUser(loggedIn.pubKey.toHexKey())
|
||||
return userProfileCache ?: run {
|
||||
val myUser: User = LocalCache.getOrCreateUser(loggedIn.pubKey.toHexKey())
|
||||
userProfileCache = myUser
|
||||
myUser
|
||||
}
|
||||
}
|
||||
|
||||
fun followingChannels(): List<Channel> {
|
||||
|
||||
@@ -595,7 +595,7 @@ object LocalCache {
|
||||
// Already processed this event.
|
||||
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 mentions = event.zappedAuthor().mapNotNull { checkGetOrCreateUser(it) }
|
||||
|
||||
@@ -53,13 +53,14 @@ open class Note(val idHex: String) {
|
||||
open fun idNote() = id().toNote()
|
||||
open fun idDisplayNote() = idNote().toShortenHex()
|
||||
|
||||
fun channel(): Channel? {
|
||||
val channelHex =
|
||||
(event as? ChannelMessageEvent)?.channel()
|
||||
?: (event as? ChannelMetadataEvent)?.channel()
|
||||
?: (event as? ChannelCreateEvent)?.id
|
||||
fun channelHex(): HexKey? {
|
||||
return (event as? ChannelMessageEvent)?.channel()
|
||||
?: (event as? ChannelMetadataEvent)?.channel()
|
||||
?: (event as? ChannelCreateEvent)?.id
|
||||
}
|
||||
|
||||
return channelHex?.let { LocalCache.checkGetOrCreateChannel(it) }
|
||||
fun channel(): Channel? {
|
||||
return channelHex()?.let { LocalCache.checkGetOrCreateChannel(it) }
|
||||
}
|
||||
|
||||
open fun address(): ATag? = null
|
||||
|
||||
@@ -76,7 +76,7 @@ abstract class NostrDataSource(val debugName: String) {
|
||||
is DeletionEvent -> LocalCache.consume(event)
|
||||
|
||||
is LnZapEvent -> {
|
||||
event.containedPost()?.let { onEvent(it, subscriptionId, relay) }
|
||||
event.zapRequest?.let { onEvent(it, subscriptionId, relay) }
|
||||
LocalCache.consume(event)
|
||||
}
|
||||
is LnZapRequestEvent -> LocalCache.consume(event)
|
||||
@@ -154,7 +154,7 @@ abstract class NostrDataSource(val debugName: String) {
|
||||
|
||||
// Refreshes observers in batches.
|
||||
private val bundler = BundledUpdate(250, Dispatchers.IO) {
|
||||
println("DataSource: ${this.javaClass.simpleName} InvalidateFilters")
|
||||
// println("DataSource: ${this.javaClass.simpleName} InvalidateFilters")
|
||||
|
||||
// adds the time to perform the refresh into this delay
|
||||
// holding off new updates in case of heavy refresh routines.
|
||||
|
||||
@@ -4,7 +4,6 @@ import android.util.Log
|
||||
import com.vitorpamplona.amethyst.model.HexKey
|
||||
import com.vitorpamplona.amethyst.service.lnurl.LnInvoiceUtil
|
||||
import com.vitorpamplona.amethyst.service.relays.Client
|
||||
import java.math.BigDecimal
|
||||
|
||||
class LnZapEvent(
|
||||
id: HexKey,
|
||||
@@ -14,25 +13,37 @@ class LnZapEvent(
|
||||
content: String,
|
||||
sig: HexKey
|
||||
) : 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
|
||||
.filter { it.firstOrNull() == "e" }
|
||||
.mapNotNull { it.getOrNull(1) }
|
||||
|
||||
override fun zappedPollOption(): Int? = containedPost()?.tags
|
||||
?.filter { it.firstOrNull() == POLL_OPTION }
|
||||
?.getOrNull(0)?.getOrNull(1)?.toInt()
|
||||
|
||||
override fun zappedAuthor() = tags
|
||||
.filter { it.firstOrNull() == "p" }
|
||||
.mapNotNull { it.getOrNull(1) }
|
||||
|
||||
override fun zappedRequestAuthor(): String? = containedPost()?.pubKey()
|
||||
|
||||
override fun amount(): BigDecimal? {
|
||||
return amount
|
||||
private fun containedPost(): LnZapRequestEvent? = try {
|
||||
description()?.ifBlank { null }?.let {
|
||||
fromJson(it, Client.lenient)
|
||||
} as? LnZapRequestEvent
|
||||
} catch (e: Exception) {
|
||||
Log.e("LnZapEvent", "Failed to Parse Contained Post ${description()}", e)
|
||||
null
|
||||
}
|
||||
|
||||
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.
|
||||
val amount by lazy {
|
||||
try {
|
||||
@@ -42,29 +53,14 @@ class LnZapEvent(
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override fun message(): String {
|
||||
return message
|
||||
}
|
||||
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
|
||||
return content
|
||||
}
|
||||
|
||||
private fun lnInvoice(): String? = tags
|
||||
.filter { it.firstOrNull() == "bolt11" }
|
||||
.mapNotNull { it.getOrNull(1) }
|
||||
.firstOrNull()
|
||||
private fun lnInvoice() = tags.firstOrNull { it.size > 1 && it[0] == "bolt11" }?.get(1)
|
||||
|
||||
private fun description(): String? = tags
|
||||
.filter { it.firstOrNull() == "description" }
|
||||
.mapNotNull { it.getOrNull(1) }
|
||||
.firstOrNull()
|
||||
private fun description() = tags.firstOrNull { it.size > 1 && it[0] == "description" }?.get(1)
|
||||
|
||||
companion object {
|
||||
const val kind = 9735
|
||||
|
||||
@@ -16,7 +16,5 @@ interface LnZapEventInterface : EventInterface {
|
||||
|
||||
fun amount(): BigDecimal?
|
||||
|
||||
fun containedPost(): Event?
|
||||
|
||||
fun message(): String
|
||||
}
|
||||
|
||||
@@ -13,8 +13,10 @@ class LnZapRequestEvent(
|
||||
content: String,
|
||||
sig: HexKey
|
||||
) : 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 {
|
||||
const val kind = 9734
|
||||
|
||||
@@ -20,24 +20,17 @@ class PollNoteEvent(
|
||||
content: String,
|
||||
sig: HexKey
|
||||
) : BaseTextNoteEvent(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||
fun pollOptions(): Map<Int, String> {
|
||||
val map = mutableMapOf<Int, String>()
|
||||
tags.filter { it.first() == POLL_OPTION }
|
||||
.forEach { map[it[1].toInt()] = it[2] }
|
||||
return map
|
||||
}
|
||||
fun pollOptions() =
|
||||
tags.filter { it.size > 2 && it[0] == POLL_OPTION }
|
||||
.associate { it[1].toInt() to it[2] }
|
||||
|
||||
fun getTagInt(property: String): Int? {
|
||||
val tagList = tags.filter {
|
||||
it.firstOrNull() == property
|
||||
}
|
||||
val tag = tagList.getOrNull(0)
|
||||
val s = tag?.getOrNull(1)
|
||||
val number = tags.firstOrNull() { it.size > 1 && it[0] == property }?.get(1)
|
||||
|
||||
return if (s.isNullOrBlank() || s == "null") {
|
||||
return if (number.isNullOrBlank() || number == "null") {
|
||||
null
|
||||
} else {
|
||||
s.toInt()
|
||||
number.toInt()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ class PrivateDmEvent(
|
||||
* nip-04 EncryptedDmEvent but may omit the recipient, too. This value can be queried and used
|
||||
* for initial messages.
|
||||
*/
|
||||
fun recipientPubKey() = tags.firstOrNull { it.firstOrNull() == "p" }?.run { Hex.decode(this[1]).toHexKey() } // makes sure its a valid one
|
||||
fun recipientPubKey() = tags.firstOrNull { it.size > 1 && it[0] == "p" }?.run { Hex.decode(this[1]).toHexKey() } // makes sure its a valid one
|
||||
|
||||
/**
|
||||
* To be fully compatible with nip-04, we read e-tags that are in violation to nip-18.
|
||||
@@ -29,7 +29,7 @@ class PrivateDmEvent(
|
||||
* Nip-18 messages should refer to other events by inline references in the content like
|
||||
* `[](e/c06f795e1234a9a1aecc731d768d4f3ca73e80031734767067c82d67ce82e506).
|
||||
*/
|
||||
fun replyTo() = tags.firstOrNull { it.firstOrNull() == "e" }?.getOrNull(1)
|
||||
fun replyTo() = tags.firstOrNull { it.size > 1 && it[0] == "e" }?.get(1)
|
||||
|
||||
fun plainContent(privKey: ByteArray, pubKey: ByteArray): String? {
|
||||
return try {
|
||||
|
||||
@@ -14,8 +14,8 @@ class ReactionEvent(
|
||||
sig: HexKey
|
||||
) : Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||
|
||||
fun originalPost() = tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) }
|
||||
fun originalAuthor() = tags.filter { it.firstOrNull() == "p" }.mapNotNull { it.getOrNull(1) }
|
||||
fun originalPost() = tags.filter { it.size > 1 && it[0] == "e" }.map { it[1] }
|
||||
fun originalAuthor() = tags.filter { it.size > 1 && it[0] == "p" }.map { it[1] }
|
||||
|
||||
companion object {
|
||||
const val kind = 7
|
||||
|
||||
@@ -30,7 +30,7 @@ class ReportEvent(
|
||||
}
|
||||
|
||||
fun reportedPost() = tags
|
||||
.filter { it.firstOrNull() == "e" && it.getOrNull(1) != null }
|
||||
.filter { it.size > 1 && it[0] == "e" }
|
||||
.map {
|
||||
ReportedKey(
|
||||
it[1],
|
||||
@@ -39,7 +39,7 @@ class ReportEvent(
|
||||
}
|
||||
|
||||
fun reportedAuthor() = tags
|
||||
.filter { it.firstOrNull() == "p" && it.getOrNull(1) != null }
|
||||
.filter { it.size > 1 && it[0] == "p" }
|
||||
.map {
|
||||
ReportedKey(
|
||||
it[1],
|
||||
|
||||
@@ -9,7 +9,7 @@ object GlobalFeedFilter : FeedFilter<Note>() {
|
||||
lateinit var account: Account
|
||||
|
||||
override fun feed(): List<Note> {
|
||||
val followChannels = account.followingChannels()
|
||||
val followChannels = account.followingChannels
|
||||
val followUsers = account.followingKeySet()
|
||||
val now = System.currentTimeMillis() / 1000
|
||||
|
||||
@@ -19,7 +19,7 @@ object GlobalFeedFilter : FeedFilter<Note>() {
|
||||
it.event is BaseTextNoteEvent && it.replyTo.isNullOrEmpty()
|
||||
}
|
||||
.filter {
|
||||
val channel = it.channel()
|
||||
val channel = it.channelHex()
|
||||
// does not show events already in the public chat list
|
||||
(channel == null || channel !in followChannels) &&
|
||||
// does not show people the user already follows
|
||||
@@ -38,7 +38,7 @@ object GlobalFeedFilter : FeedFilter<Note>() {
|
||||
it.event is LongTextNoteEvent && it.replyTo.isNullOrEmpty()
|
||||
}
|
||||
.filter {
|
||||
val channel = it.channel()
|
||||
val channel = it.channelHex()
|
||||
// does not show events already in the public chat list
|
||||
(channel == null || channel !in followChannels) &&
|
||||
// does not show people the user already follows
|
||||
|
||||
@@ -246,30 +246,32 @@ fun ChatroomMessageCompose(
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
val event = note.event
|
||||
if (event is ChannelCreateEvent) {
|
||||
val channelInfo = event.channelInfo()
|
||||
Text(
|
||||
text = note.author?.toBestDisplayName()
|
||||
.toString() + " ${stringResource(R.string.created)} " + (
|
||||
event.channelInfo().name
|
||||
channelInfo.name
|
||||
?: ""
|
||||
) + " ${stringResource(R.string.with_description_of)} '" + (
|
||||
event.channelInfo().about
|
||||
channelInfo.about
|
||||
?: ""
|
||||
) + "', ${stringResource(R.string.and_picture)} '" + (
|
||||
event.channelInfo().picture
|
||||
channelInfo.picture
|
||||
?: ""
|
||||
) + "'"
|
||||
)
|
||||
} else if (event is ChannelMetadataEvent) {
|
||||
val channelInfo = event.channelInfo()
|
||||
Text(
|
||||
text = note.author?.toBestDisplayName()
|
||||
.toString() + " ${stringResource(R.string.changed_chat_name_to)} '" + (
|
||||
event.channelInfo().name
|
||||
channelInfo.name
|
||||
?: ""
|
||||
) + "$', {stringResource(R.string.description_to)} '" + (
|
||||
event.channelInfo().about
|
||||
channelInfo.about
|
||||
?: ""
|
||||
) + "', ${stringResource(R.string.and_picture_to)} '" + (
|
||||
event.channelInfo().picture
|
||||
channelInfo.picture
|
||||
?: ""
|
||||
) + "'"
|
||||
)
|
||||
|
||||
@@ -26,7 +26,9 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavController
|
||||
import com.vitorpamplona.amethyst.NotificationCache
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
||||
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
||||
import com.vitorpamplona.amethyst.ui.screen.MessageSetCard
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -46,7 +48,7 @@ fun MessageSetCompose(messageSetCard: MessageSetCard, isInnerNote: Boolean = fal
|
||||
} else {
|
||||
var isNew by remember { mutableStateOf<Boolean>(false) }
|
||||
|
||||
LaunchedEffect(key1 = messageSetCard) {
|
||||
LaunchedEffect(key1 = messageSetCard.createdAt()) {
|
||||
withContext(Dispatchers.IO) {
|
||||
isNew =
|
||||
messageSetCard.createdAt() > NotificationCache.load(routeForLastRead)
|
||||
@@ -64,14 +66,29 @@ fun MessageSetCompose(messageSetCard: MessageSetCard, isInnerNote: Boolean = fal
|
||||
Column(
|
||||
modifier = Modifier.background(backgroundColor).combinedClickable(
|
||||
onClick = {
|
||||
if (noteEvent !is ChannelMessageEvent) {
|
||||
navController.navigate("Note/${note.idHex}") {
|
||||
launchSingleTop = true
|
||||
}
|
||||
} else {
|
||||
if (noteEvent is ChannelMessageEvent) {
|
||||
note.channel()?.let {
|
||||
navController.navigate("Channel/${it.idHex}")
|
||||
}
|
||||
} else if (noteEvent is PrivateDmEvent) {
|
||||
val replyAuthorBase =
|
||||
(note.event as? PrivateDmEvent)
|
||||
?.recipientPubKey()
|
||||
?.let { LocalCache.getOrCreateUser(it) }
|
||||
|
||||
var userToComposeOn = note.author!!
|
||||
|
||||
if (replyAuthorBase != null) {
|
||||
if (note.author == accountViewModel.userProfile()) {
|
||||
userToComposeOn = replyAuthorBase
|
||||
}
|
||||
}
|
||||
|
||||
navController.navigate("Room/${userToComposeOn.pubkeyHex}")
|
||||
} else {
|
||||
navController.navigate("Note/${note.idHex}") {
|
||||
launchSingleTop = true
|
||||
}
|
||||
}
|
||||
},
|
||||
onLongClick = { popupExpanded = true }
|
||||
|
||||
@@ -61,7 +61,7 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun
|
||||
} else {
|
||||
var isNew by remember { mutableStateOf<Boolean>(false) }
|
||||
|
||||
LaunchedEffect(key1 = multiSetCard) {
|
||||
LaunchedEffect(key1 = multiSetCard.createdAt()) {
|
||||
withContext(Dispatchers.IO) {
|
||||
isNew = multiSetCard.createdAt > NotificationCache.load(routeForLastRead)
|
||||
|
||||
@@ -244,7 +244,7 @@ fun FastNoteAuthorPicture(
|
||||
val userState by author.live().metadata.observeAsState()
|
||||
val user = userState?.user ?: return
|
||||
|
||||
val showFollowingMark = userAccount.isFollowingCached(user) || user == userAccount
|
||||
val showFollowingMark = userAccount.isFollowingCached(user) || user === userAccount
|
||||
|
||||
UserPicture(
|
||||
userHex = user.pubkeyHex,
|
||||
|
||||
@@ -96,7 +96,6 @@ fun ObserveDisplayNip05Status(baseUser: User, columnModifier: Modifier = Modifie
|
||||
|
||||
user.nip05()?.let { nip05 ->
|
||||
if (nip05.split("@").size == 2) {
|
||||
val nip05Verified by nip05VerificationAsAState(user.info!!, user.pubkeyHex)
|
||||
Column(modifier = columnModifier) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
if (nip05.split("@")[0] != "_") {
|
||||
@@ -108,6 +107,7 @@ fun ObserveDisplayNip05Status(baseUser: User, columnModifier: Modifier = Modifie
|
||||
)
|
||||
}
|
||||
|
||||
val nip05Verified by nip05VerificationAsAState(user.info!!, user.pubkeyHex)
|
||||
if (nip05Verified == null) {
|
||||
Icon(
|
||||
tint = Color.Yellow,
|
||||
|
||||
@@ -96,7 +96,7 @@ fun NoteCompose(
|
||||
)
|
||||
}
|
||||
|
||||
Log.d("Time", "Note Compose in $elapsed for ${baseNote.event?.kind()} ${baseNote.event?.content()?.split("\n")?.get(0)?.take(100)}")
|
||||
Log.d("Time", "Note Compose in $elapsed for ${baseNote.idHex} ${baseNote.event?.kind()} ${baseNote.event?.content()?.split("\n")?.get(0)?.take(100)}")
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@@ -582,7 +582,9 @@ fun DisplayFollowingHashtagsInPost(
|
||||
var firstTag by remember { mutableStateOf<String?>(null) }
|
||||
|
||||
LaunchedEffect(key1 = noteEvent) {
|
||||
firstTag = noteEvent.firstIsTaggedHashes(account.followingTagSet())
|
||||
withContext(Dispatchers.IO) {
|
||||
firstTag = noteEvent.firstIsTaggedHashes(account.followingTagSet())
|
||||
}
|
||||
}
|
||||
|
||||
Column() {
|
||||
@@ -1108,12 +1110,14 @@ fun NoteDropDownMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Unit,
|
||||
}
|
||||
|
||||
LaunchedEffect(key1 = note) {
|
||||
state = DropDownParams(
|
||||
accountViewModel.isFollowing(note.author),
|
||||
accountViewModel.isInPrivateBookmarks(note),
|
||||
accountViewModel.isInPublicBookmarks(note),
|
||||
accountViewModel.isLoggedUser(note.author)
|
||||
)
|
||||
withContext(Dispatchers.IO) {
|
||||
state = DropDownParams(
|
||||
accountViewModel.isFollowing(note.author),
|
||||
accountViewModel.isInPrivateBookmarks(note),
|
||||
accountViewModel.isInPublicBookmarks(note),
|
||||
accountViewModel.isLoggedUser(note.author)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
DropdownMenu(
|
||||
|
||||
@@ -48,9 +48,6 @@ fun PollNote(
|
||||
accountViewModel: AccountViewModel,
|
||||
navController: NavController
|
||||
) {
|
||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||
val account = accountState?.account ?: return
|
||||
|
||||
val zapsState by baseNote.live().zaps.observeAsState()
|
||||
val zappedNote = zapsState?.note ?: return
|
||||
|
||||
@@ -72,7 +69,7 @@ fun PollNote(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
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(
|
||||
baseNote,
|
||||
accountViewModel,
|
||||
@@ -90,7 +87,7 @@ fun PollNote(
|
||||
LinearProgressIndicator(
|
||||
modifier = Modifier.matchParentSize(),
|
||||
color = color,
|
||||
progress = optionTally
|
||||
progress = optionTally.toFloat()
|
||||
)
|
||||
|
||||
Row(
|
||||
@@ -101,7 +98,7 @@ fun PollNote(
|
||||
modifier = Modifier.padding(horizontal = 10.dp).width(40.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "${(optionTally * 100).roundToInt()}%",
|
||||
text = "${(optionTally.toFloat() * 100).roundToInt()}%",
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
}
|
||||
@@ -210,7 +207,7 @@ fun ZapVote(
|
||||
)
|
||||
.show()
|
||||
}
|
||||
} else if (zappedNote?.author == account.userProfile()) {
|
||||
} else if (accountViewModel.isLoggedUser(zappedNote?.author)) {
|
||||
scope.launch {
|
||||
Toast
|
||||
.makeText(
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.service.model.*
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
import java.util.*
|
||||
|
||||
class PollNoteViewModel {
|
||||
@@ -16,7 +17,9 @@ class PollNoteViewModel {
|
||||
var valueMaximum: Int? = null
|
||||
var valueMinimum: Int? = null
|
||||
private var closedAt: Int? = null
|
||||
var consensusThreshold: Float? = null
|
||||
var consensusThreshold: BigDecimal? = null
|
||||
|
||||
var totalZapped: BigDecimal = BigDecimal.ZERO
|
||||
|
||||
fun load(note: Note?) {
|
||||
pollNote = note
|
||||
@@ -24,8 +27,10 @@ class PollNoteViewModel {
|
||||
pollOptions = pollEvent?.pollOptions()
|
||||
valueMaximum = pollEvent?.getTagInt(VALUE_MAXIMUM)
|
||||
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)
|
||||
|
||||
totalZapped = totalZapped()
|
||||
}
|
||||
|
||||
fun isVoteAmountAtomic() = valueMaximum != null && valueMinimum != null && valueMinimum == valueMaximum
|
||||
@@ -73,47 +78,39 @@ class PollNoteViewModel {
|
||||
return false
|
||||
}
|
||||
|
||||
fun optionVoteTally(op: Int): Float {
|
||||
val tally = zappedPollOptionAmount(op).toFloat().div(zappedVoteTotal())
|
||||
return if (tally.isNaN()) { // catch div by 0
|
||||
0f
|
||||
} else { tally }
|
||||
}
|
||||
|
||||
private fun zappedVoteTotal(): Float {
|
||||
var total = 0f
|
||||
pollOptions?.keys?.forEach {
|
||||
total += zappedPollOptionAmount(it).toFloat()
|
||||
fun optionVoteTally(op: Int): BigDecimal {
|
||||
return if (totalZapped.compareTo(BigDecimal.ZERO) > 0) {
|
||||
zappedPollOptionAmount(op).divide(totalZapped, 2, RoundingMode.HALF_UP)
|
||||
} else {
|
||||
BigDecimal.ZERO
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
fun isPollOptionZappedBy(option: Int, user: User): Boolean {
|
||||
if (pollNote?.zaps?.any { it.key.author == user } == true) {
|
||||
pollNote!!.zaps.mapNotNull { it.value?.event }
|
||||
.filterIsInstance<LnZapEvent>()
|
||||
.map {
|
||||
val zappedOption = it.zappedPollOption()
|
||||
if (zappedOption == option && it.zappedRequestAuthor() == user.pubkeyHex) {
|
||||
return true
|
||||
}
|
||||
if (pollNote?.zaps?.any { it.key.author === user } == true) {
|
||||
pollNote!!.zaps
|
||||
.any {
|
||||
val event = it.value?.event as? LnZapEvent
|
||||
event?.zappedPollOption() == option && event.zappedRequestAuthor() == user.pubkeyHex
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun zappedPollOptionAmount(option: Int): BigDecimal {
|
||||
return if (pollNote != null) {
|
||||
pollNote!!.zaps.mapNotNull { it.value?.event }
|
||||
.filterIsInstance<LnZapEvent>()
|
||||
.mapNotNull {
|
||||
val zappedOption = it.zappedPollOption()
|
||||
if (zappedOption == option) {
|
||||
it.amount
|
||||
} else { null }
|
||||
}.sumOf { it }
|
||||
} else {
|
||||
BigDecimal(0)
|
||||
}
|
||||
return pollNote?.zaps?.values?.sumOf {
|
||||
val event = it?.event as? LnZapEvent
|
||||
if (event?.zappedPollOption() == option) {
|
||||
event.amount ?: BigDecimal(0)
|
||||
} else {
|
||||
BigDecimal(0)
|
||||
}
|
||||
} ?: BigDecimal(0)
|
||||
}
|
||||
|
||||
fun totalZapped(): BigDecimal {
|
||||
return pollNote?.zaps?.values?.sumOf {
|
||||
(it?.event as? LnZapEvent)?.amount ?: BigDecimal(0)
|
||||
} ?: BigDecimal(0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ fun ZapUserSetCompose(zapSetCard: ZapUserSetCard, isInnerNote: Boolean = false,
|
||||
|
||||
var isNew by remember { mutableStateOf<Boolean>(false) }
|
||||
|
||||
LaunchedEffect(key1 = zapSetCard) {
|
||||
LaunchedEffect(key1 = zapSetCard.createdAt()) {
|
||||
withContext(Dispatchers.IO) {
|
||||
isNew = zapSetCard.createdAt > NotificationCache.load(routeForLastRead)
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.background
|
||||
@@ -19,6 +20,7 @@ import androidx.compose.material.rememberScaffoldState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
@@ -34,10 +36,12 @@ import com.vitorpamplona.amethyst.ui.navigation.Route
|
||||
import com.vitorpamplona.amethyst.ui.navigation.currentRoute
|
||||
import com.vitorpamplona.amethyst.ui.screen.AccountState
|
||||
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun MainScreen(accountViewModel: AccountViewModel, accountStateViewModel: AccountStateViewModel, startingPage: String? = null) {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val navController = rememberNavController()
|
||||
val scaffoldState = rememberScaffoldState(rememberDrawerState(DrawerValue.Closed))
|
||||
val sheetState = rememberModalBottomSheetState(
|
||||
@@ -64,6 +68,9 @@ fun MainScreen(accountViewModel: AccountViewModel, accountStateViewModel: Accoun
|
||||
},
|
||||
drawerContent = {
|
||||
DrawerContent(navController, scaffoldState, sheetState, accountViewModel)
|
||||
BackHandler(enabled = scaffoldState.drawerState.isOpen) {
|
||||
coroutineScope.launch { scaffoldState.drawerState.close() }
|
||||
}
|
||||
},
|
||||
floatingActionButton = {
|
||||
FloatingButtons(navController, accountStateViewModel)
|
||||
|
||||
@@ -263,9 +263,30 @@
|
||||
<string name="wallet_connect_service_secret_placeholder">nsec / hex privát kulcs</string>
|
||||
|
||||
<string name="pledge_amount_in_sats">Hozzájárulás összege sats-ban</string>
|
||||
<string name="post_poll">Szavazás Létrehozása</string>
|
||||
<string name="poll_heading_required">Szükséges mezők:</string>
|
||||
<string name="poll_zap_recipients">Zap-et kapják</string>
|
||||
<string name="poll_primary_description">Szavazás elsődleges leírása…</string>
|
||||
<string name="poll_option_index">Szavazás %s megoszlása</string>
|
||||
<string name="poll_option_description">Szavazás opcióinak leírása</string>
|
||||
<string name="poll_heading_optional">Kiegészítő mezők:</string>
|
||||
<string name="poll_zap_value_min">Zap minimum</string>
|
||||
<string name="poll_zap_value_max">Zap maximum</string>
|
||||
<string name="poll_consensus_threshold">Konszenzus</string>
|
||||
<string name="poll_consensus_threshold_percent">(0–100)%</string>
|
||||
<string name="poll_closing_time">Szavazás lezárása</string>
|
||||
<string name="poll_closing_time_days">napok</string>
|
||||
<string name="poll_is_closed">A szavazásra nem lehet már új szavazatot leadni</string>
|
||||
<string name="poll_zap_amount">Zap összege</string>
|
||||
<string name="one_vote_per_user_on_atomic_votes">Az ilyen típusú szavazásokon felhasználónként csak egy szavazat engedélyezett</string>
|
||||
|
||||
|
||||
<string name="looking_for_event">"%1$s esemény keresése"</string>
|
||||
|
||||
<string name="custom_zaps_add_a_message">Nyilvános üzenet hozzáadása</string>
|
||||
<string name="custom_zaps_add_a_message_example">Köszönöm a kemény munkát!</string>
|
||||
|
||||
<string name="lightning_create_and_add_invoice">Létrehoz és Hozzáad</string>
|
||||
<string name="poll_author_no_vote">A szavazás létrehozója sajátjára nem szavazhat.</string>
|
||||
<string name="poll_hashtag" translatable="false">#zappoll</string>
|
||||
</resources>
|
||||
|
||||
+2
-2
@@ -10,8 +10,8 @@ buildscript {
|
||||
}
|
||||
}// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
id 'com.android.application' version '7.4.2' apply false
|
||||
id 'com.android.library' version '7.4.2' apply false
|
||||
id 'com.android.application' version '8.0.0' apply false
|
||||
id 'com.android.library' version '8.0.0' apply false
|
||||
id 'org.jetbrains.kotlin.android' version '1.8.10' apply false
|
||||
id 'org.jetbrains.kotlin.jvm' version '1.8.10' apply false
|
||||
}
|
||||
|
||||
+3
-1
@@ -21,4 +21,6 @@ kotlin.code.style=official
|
||||
# resources declared in the library itself and none from the library's dependencies,
|
||||
# thereby reducing the size of the R class for that library
|
||||
android.nonTransitiveRClass=true
|
||||
android.enableR8.fullMode=true
|
||||
android.enableR8.fullMode=true
|
||||
android.defaults.buildfeatures.buildconfig=true
|
||||
android.nonFinalResIds=false
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#Wed Jan 04 09:23:50 EST 2023
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
Reference in New Issue
Block a user