Uses 1/3 of the memory per not-fully loaded user/note
BugFix for invalid Hexes in mentions, contact lists, etc.
This commit is contained in:
@@ -5,6 +5,7 @@ import androidx.compose.ui.text.AnnotatedString
|
|||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
|
import com.vitorpamplona.amethyst.model.UserMetadata
|
||||||
import com.vitorpamplona.amethyst.model.decodePublicKey
|
import com.vitorpamplona.amethyst.model.decodePublicKey
|
||||||
import com.vitorpamplona.amethyst.model.toHexKey
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import com.vitorpamplona.amethyst.ui.actions.buildAnnotatedStringWithUrlHighlighting
|
import com.vitorpamplona.amethyst.ui.actions.buildAnnotatedStringWithUrlHighlighting
|
||||||
@@ -29,7 +30,8 @@ class EUrlUserTagTransformationTest {
|
|||||||
@Test
|
@Test
|
||||||
fun transformationText() {
|
fun transformationText() {
|
||||||
val user = LocalCache.getOrCreateUser(decodePublicKey("npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z").toHexKey())
|
val user = LocalCache.getOrCreateUser(decodePublicKey("npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z").toHexKey())
|
||||||
user.info.displayName = "Vitor Pamplona"
|
user.info = UserMetadata()
|
||||||
|
user.info?.displayName = "Vitor Pamplona"
|
||||||
|
|
||||||
var transformedText = buildAnnotatedStringWithUrlHighlighting(
|
var transformedText = buildAnnotatedStringWithUrlHighlighting(
|
||||||
AnnotatedString("New Hey @npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z"),
|
AnnotatedString("New Hey @npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z"),
|
||||||
@@ -63,7 +65,8 @@ class EUrlUserTagTransformationTest {
|
|||||||
@Test
|
@Test
|
||||||
fun transformationTextTwoKeys() {
|
fun transformationTextTwoKeys() {
|
||||||
val user = LocalCache.getOrCreateUser(decodePublicKey("npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z").toHexKey())
|
val user = LocalCache.getOrCreateUser(decodePublicKey("npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z").toHexKey())
|
||||||
user.info.displayName = "Vitor Pamplona"
|
user.info = UserMetadata()
|
||||||
|
user.info?.displayName = "Vitor Pamplona"
|
||||||
|
|
||||||
var transformedText = buildAnnotatedStringWithUrlHighlighting(
|
var transformedText = buildAnnotatedStringWithUrlHighlighting(
|
||||||
AnnotatedString("New Hey @npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z and @npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z"),
|
AnnotatedString("New Hey @npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z and @npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z"),
|
||||||
|
|||||||
@@ -276,8 +276,8 @@ class Account(
|
|||||||
val user = LocalCache.users[toUser] ?: return
|
val user = LocalCache.users[toUser] ?: return
|
||||||
|
|
||||||
val signedEvent = PrivateDmEvent.create(
|
val signedEvent = PrivateDmEvent.create(
|
||||||
recipientPubKey = user.pubkey,
|
recipientPubKey = user.pubkey(),
|
||||||
publishedRecipientPubKey = user.pubkey,
|
publishedRecipientPubKey = user.pubkey(),
|
||||||
msg = message,
|
msg = message,
|
||||||
privateKey = loggedIn.privKey!!,
|
privateKey = loggedIn.privKey!!,
|
||||||
advertiseNip18 = false
|
advertiseNip18 = false
|
||||||
@@ -406,7 +406,7 @@ class Account(
|
|||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
userProfile().liveRelays.observeForever {
|
userProfile().live().relays.observeForever {
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
reconnectIfRelaysHaveChanged()
|
reconnectIfRelaysHaveChanged()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,12 +5,10 @@ import com.vitorpamplona.amethyst.service.NostrSingleChannelDataSource
|
|||||||
import com.vitorpamplona.amethyst.service.NostrSingleEventDataSource
|
import com.vitorpamplona.amethyst.service.NostrSingleEventDataSource
|
||||||
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
|
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
|
||||||
import com.vitorpamplona.amethyst.ui.note.toShortenHex
|
import com.vitorpamplona.amethyst.ui.note.toShortenHex
|
||||||
|
import fr.acinq.secp256k1.Hex
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
class Channel(val id: ByteArray) {
|
class Channel(val idHex: String) {
|
||||||
val idHex = id.toHexKey()
|
|
||||||
val idDisplayHex = id.toShortenHex()
|
|
||||||
|
|
||||||
var creator: User? = null
|
var creator: User? = null
|
||||||
var info = ChannelCreateEvent.ChannelData(null, null, null)
|
var info = ChannelCreateEvent.ChannelData(null, null, null)
|
||||||
|
|
||||||
@@ -18,8 +16,12 @@ class Channel(val id: ByteArray) {
|
|||||||
|
|
||||||
val notes = ConcurrentHashMap<HexKey, Note>()
|
val notes = ConcurrentHashMap<HexKey, Note>()
|
||||||
|
|
||||||
|
fun id() = Hex.decode(idHex)
|
||||||
|
fun idNote() = id().toNote()
|
||||||
|
fun idDisplayNote() = idNote().toShortenHex()
|
||||||
|
|
||||||
fun toBestDisplayName(): String {
|
fun toBestDisplayName(): String {
|
||||||
return info.name ?: idDisplayHex
|
return info.name ?: idDisplayNote()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import java.time.format.DateTimeFormatter
|
|||||||
import java.util.Collections
|
import java.util.Collections
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import com.vitorpamplona.amethyst.service.relays.Relay
|
import com.vitorpamplona.amethyst.service.relays.Relay
|
||||||
|
import fr.acinq.secp256k1.Hex
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import kotlin.time.ExperimentalTime
|
import kotlin.time.ExperimentalTime
|
||||||
import kotlin.time.measureTimedValue
|
import kotlin.time.measureTimedValue
|
||||||
@@ -51,6 +52,17 @@ object LocalCache {
|
|||||||
val users = ConcurrentHashMap<HexKey, User>()
|
val users = ConcurrentHashMap<HexKey, User>()
|
||||||
val notes = ConcurrentHashMap<HexKey, Note>()
|
val notes = ConcurrentHashMap<HexKey, Note>()
|
||||||
val channels = ConcurrentHashMap<HexKey, Channel>()
|
val channels = ConcurrentHashMap<HexKey, Channel>()
|
||||||
|
|
||||||
|
fun checkGetOrCreateUser(key: HexKey): User? {
|
||||||
|
return try {
|
||||||
|
val checkHex = Hex.decode(key) // Checks if this is a valid Hex
|
||||||
|
getOrCreateUser(key)
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
Log.e("LocalCache", "Invalid Key to create user: $key", e)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun getOrCreateUser(key: HexKey): User {
|
fun getOrCreateUser(key: HexKey): User {
|
||||||
return users[key] ?: run {
|
return users[key] ?: run {
|
||||||
@@ -72,7 +84,7 @@ object LocalCache {
|
|||||||
@Synchronized
|
@Synchronized
|
||||||
fun getOrCreateChannel(key: String): Channel {
|
fun getOrCreateChannel(key: String): Channel {
|
||||||
return channels[key] ?: run {
|
return channels[key] ?: run {
|
||||||
val answer = Channel(key.toByteArray())
|
val answer = Channel(key)
|
||||||
channels.put(key, answer)
|
channels.put(key, answer)
|
||||||
answer
|
answer
|
||||||
}
|
}
|
||||||
@@ -82,18 +94,19 @@ object LocalCache {
|
|||||||
fun consume(event: MetadataEvent) {
|
fun consume(event: MetadataEvent) {
|
||||||
// new event
|
// new event
|
||||||
val oldUser = getOrCreateUser(event.pubKey.toHexKey())
|
val oldUser = getOrCreateUser(event.pubKey.toHexKey())
|
||||||
if (event.createdAt > oldUser.updatedMetadataAt) {
|
if (oldUser.info == null || event.createdAt > oldUser.info!!.updatedMetadataAt) {
|
||||||
val newUser = try {
|
val newUser = try {
|
||||||
metadataParser.readValue<UserMetadata>(ByteArrayInputStream(event.content.toByteArray(Charsets.UTF_8)), UserMetadata::class.java)
|
metadataParser.readValue(
|
||||||
|
ByteArrayInputStream(event.content.toByteArray(Charsets.UTF_8)),
|
||||||
|
UserMetadata::class.java
|
||||||
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
Log.w("MT", "Content Parse Error ${e.localizedMessage} ${event.content}")
|
Log.w("MT", "Content Parse Error ${e.localizedMessage} ${event.content}")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
oldUser.updateUserInfo(newUser, event.createdAt)
|
oldUser.updateUserInfo(newUser, event)
|
||||||
oldUser.latestMetadata = event
|
|
||||||
|
|
||||||
//Log.d("MT", "New User Metadata ${oldUser.pubkeyDisplayHex} ${oldUser.toBestDisplayName()}")
|
//Log.d("MT", "New User Metadata ${oldUser.pubkeyDisplayHex} ${oldUser.toBestDisplayName()}")
|
||||||
} else {
|
} else {
|
||||||
//Log.d("MT","Relay sent a previous Metadata Event ${oldUser.toBestDisplayName()} ${formattedDateTime(event.createdAt)} > ${formattedDateTime(oldUser.updatedAt)}")
|
//Log.d("MT","Relay sent a previous Metadata Event ${oldUser.toBestDisplayName()} ${formattedDateTime(event.createdAt)} > ${formattedDateTime(oldUser.updatedAt)}")
|
||||||
@@ -118,7 +131,7 @@ object LocalCache {
|
|||||||
// Already processed this event.
|
// Already processed this event.
|
||||||
if (note.event != null) return
|
if (note.event != null) return
|
||||||
|
|
||||||
val mentions = event.mentions.map { getOrCreateUser(it) }
|
val mentions = event.mentions.mapNotNull { checkGetOrCreateUser(it) }
|
||||||
val replyTo = replyToWithoutCitations(event).map { getOrCreateNote(it) }
|
val replyTo = replyToWithoutCitations(event).map { getOrCreateNote(it) }
|
||||||
|
|
||||||
note.loadEvent(event, author, mentions, replyTo)
|
note.loadEvent(event, author, mentions, replyTo)
|
||||||
@@ -227,7 +240,7 @@ object LocalCache {
|
|||||||
//Log.d("PM", "${author.toBestDisplayName()} to ${recipient?.toBestDisplayName()}")
|
//Log.d("PM", "${author.toBestDisplayName()} to ${recipient?.toBestDisplayName()}")
|
||||||
|
|
||||||
val repliesTo = event.tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) }.map { getOrCreateNote(it) }
|
val repliesTo = event.tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) }.map { getOrCreateNote(it) }
|
||||||
val mentions = event.tags.filter { it.firstOrNull() == "p" }.mapNotNull { it.getOrNull(1) }.map { getOrCreateUser(it) }
|
val mentions = event.tags.filter { it.firstOrNull() == "p" }.mapNotNull { it.getOrNull(1) }.mapNotNull { checkGetOrCreateUser(it) }
|
||||||
|
|
||||||
note.loadEvent(event, author, mentions, repliesTo)
|
note.loadEvent(event, author, mentions, repliesTo)
|
||||||
|
|
||||||
@@ -252,7 +265,7 @@ object LocalCache {
|
|||||||
//Log.d("TN", "New Boost (${notes.size},${users.size}) ${note.author?.toBestDisplayName()} ${formattedDateTime(event.createdAt)}")
|
//Log.d("TN", "New Boost (${notes.size},${users.size}) ${note.author?.toBestDisplayName()} ${formattedDateTime(event.createdAt)}")
|
||||||
|
|
||||||
val author = getOrCreateUser(event.pubKey.toHexKey())
|
val author = getOrCreateUser(event.pubKey.toHexKey())
|
||||||
val mentions = event.originalAuthor.map { getOrCreateUser(it) }
|
val mentions = event.originalAuthor.mapNotNull { checkGetOrCreateUser(it) }
|
||||||
val repliesTo = event.boostedPost.map { getOrCreateNote(it) }
|
val repliesTo = event.boostedPost.map { getOrCreateNote(it) }
|
||||||
|
|
||||||
note.loadEvent(event, author, mentions, repliesTo)
|
note.loadEvent(event, author, mentions, repliesTo)
|
||||||
@@ -283,7 +296,7 @@ object LocalCache {
|
|||||||
if (note.event != null) return
|
if (note.event != null) return
|
||||||
|
|
||||||
val author = getOrCreateUser(event.pubKey.toHexKey())
|
val author = getOrCreateUser(event.pubKey.toHexKey())
|
||||||
val mentions = event.originalAuthor.map { getOrCreateUser(it) }
|
val mentions = event.originalAuthor.mapNotNull { checkGetOrCreateUser(it) }
|
||||||
val repliesTo = event.originalPost.map { getOrCreateNote(it) }
|
val repliesTo = event.originalPost.map { getOrCreateNote(it) }
|
||||||
|
|
||||||
note.loadEvent(event, author, mentions, repliesTo)
|
note.loadEvent(event, author, mentions, repliesTo)
|
||||||
@@ -328,7 +341,7 @@ object LocalCache {
|
|||||||
if (note.event != null) return
|
if (note.event != null) return
|
||||||
|
|
||||||
val author = getOrCreateUser(event.pubKey.toHexKey())
|
val author = getOrCreateUser(event.pubKey.toHexKey())
|
||||||
val mentions = event.reportedAuthor.map { getOrCreateUser(it) }
|
val mentions = event.reportedAuthor.mapNotNull { checkGetOrCreateUser(it) }
|
||||||
val repliesTo = event.reportedPost.map { getOrCreateNote(it) }
|
val repliesTo = event.reportedPost.map { getOrCreateNote(it) }
|
||||||
|
|
||||||
note.loadEvent(event, author, mentions, repliesTo)
|
note.loadEvent(event, author, mentions, repliesTo)
|
||||||
@@ -406,7 +419,7 @@ object LocalCache {
|
|||||||
// Already processed this event.
|
// Already processed this event.
|
||||||
if (note.event != null) return
|
if (note.event != null) return
|
||||||
|
|
||||||
val mentions = event.mentions.map { getOrCreateUser(it) }
|
val mentions = event.mentions.mapNotNull { checkGetOrCreateUser(it) }
|
||||||
val replyTo = event.replyTos
|
val replyTo = event.replyTos
|
||||||
.map { getOrCreateNote(it) }
|
.map { getOrCreateNote(it) }
|
||||||
.filter { it.event !is ChannelCreateEvent }
|
.filter { it.event !is ChannelCreateEvent }
|
||||||
@@ -447,7 +460,7 @@ object LocalCache {
|
|||||||
if (note.event != null) return
|
if (note.event != null) return
|
||||||
|
|
||||||
val author = getOrCreateUser(event.pubKey.toHexKey())
|
val author = getOrCreateUser(event.pubKey.toHexKey())
|
||||||
val mentions = event.zappedAuthor.map { getOrCreateUser(it) }
|
val mentions = event.zappedAuthor.mapNotNull { checkGetOrCreateUser(it) }
|
||||||
val repliesTo = event.zappedPost.map { getOrCreateNote(it) }
|
val repliesTo = event.zappedPost.map { getOrCreateNote(it) }
|
||||||
|
|
||||||
note.loadEvent(event, author, mentions, repliesTo)
|
note.loadEvent(event, author, mentions, repliesTo)
|
||||||
@@ -483,7 +496,7 @@ object LocalCache {
|
|||||||
if (note.event != null) return
|
if (note.event != null) return
|
||||||
|
|
||||||
val author = getOrCreateUser(event.pubKey.toHexKey())
|
val author = getOrCreateUser(event.pubKey.toHexKey())
|
||||||
val mentions = event.zappedAuthor.map { getOrCreateUser(it) }
|
val mentions = event.zappedAuthor.mapNotNull { checkGetOrCreateUser(it) }
|
||||||
val repliesTo = event.zappedPost.map { getOrCreateNote(it) }
|
val repliesTo = event.zappedPost.map { getOrCreateNote(it) }
|
||||||
|
|
||||||
note.loadEvent(event, author, mentions, repliesTo)
|
note.loadEvent(event, author, mentions, repliesTo)
|
||||||
@@ -508,9 +521,9 @@ object LocalCache {
|
|||||||
|
|
||||||
fun findUsersStartingWith(username: String): List<User> {
|
fun findUsersStartingWith(username: String): List<User> {
|
||||||
return users.values.filter {
|
return users.values.filter {
|
||||||
it.info.anyNameStartsWith(username)
|
(it.anyNameStartsWith(username))
|
||||||
|| it.pubkeyHex.startsWith(username, true)
|
|| it.pubkeyHex.startsWith(username, true)
|
||||||
|| it.pubkey.toNpub().startsWith(username, true)
|
|| it.pubkeyNpub().startsWith(username, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -519,15 +532,15 @@ object LocalCache {
|
|||||||
(it.event is TextNoteEvent && it.event?.content?.contains(text, true) ?: false)
|
(it.event is TextNoteEvent && it.event?.content?.contains(text, true) ?: false)
|
||||||
|| (it.event is ChannelMessageEvent && it.event?.content?.contains(text, true) ?: false)
|
|| (it.event is ChannelMessageEvent && it.event?.content?.contains(text, true) ?: false)
|
||||||
|| it.idHex.startsWith(text, true)
|
|| it.idHex.startsWith(text, true)
|
||||||
|| it.id.toNote().startsWith(text, true)
|
|| it.idNote().startsWith(text, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun findChannelsStartingWith(text: String): List<Channel> {
|
fun findChannelsStartingWith(text: String): List<Channel> {
|
||||||
return channels.values.filter {
|
return channels.values.filter {
|
||||||
it.anyNameStartsWith(text)
|
it.anyNameStartsWith(text)
|
||||||
|| it.idHex.startsWith(text, true)
|
|| it.idHex.startsWith(text, true)
|
||||||
|| it.id.toNote().startsWith(text, true)
|
|| it.idNote().startsWith(text, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.vitorpamplona.amethyst.model
|
package com.vitorpamplona.amethyst.model
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import com.vitorpamplona.amethyst.lnurl.LnInvoiceUtil
|
|
||||||
import com.vitorpamplona.amethyst.service.NostrSingleEventDataSource
|
import com.vitorpamplona.amethyst.service.NostrSingleEventDataSource
|
||||||
import com.vitorpamplona.amethyst.service.model.LnZapEvent
|
import com.vitorpamplona.amethyst.service.model.LnZapEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.ReactionEvent
|
import com.vitorpamplona.amethyst.service.model.ReactionEvent
|
||||||
@@ -11,7 +10,6 @@ import fr.acinq.secp256k1.Hex
|
|||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.time.ZoneId
|
import java.time.ZoneId
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
import java.util.Collections
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
@@ -21,18 +19,13 @@ import com.vitorpamplona.amethyst.service.relays.Relay
|
|||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import java.util.regex.Matcher
|
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
import nostr.postr.events.Event
|
import nostr.postr.events.Event
|
||||||
|
import nostr.postr.toNpub
|
||||||
|
|
||||||
val tagSearch = Pattern.compile("(?:\\s|\\A)\\#\\[([0-9]+)\\]")
|
val tagSearch = Pattern.compile("(?:\\s|\\A)\\#\\[([0-9]+)\\]")
|
||||||
|
|
||||||
class Note(val idHex: String) {
|
class Note(val idHex: String) {
|
||||||
// These fields are always available.
|
|
||||||
// They are immutable
|
|
||||||
val id = Hex.decode(idHex)
|
|
||||||
val idDisplayNote = id.toNote().toShortenHex()
|
|
||||||
|
|
||||||
// These fields are only available after the Text Note event is received.
|
// These fields are only available after the Text Note event is received.
|
||||||
// They are immutable after that.
|
// They are immutable after that.
|
||||||
var event: Event? = null
|
var event: Event? = null
|
||||||
@@ -59,13 +52,17 @@ class Note(val idHex: String) {
|
|||||||
|
|
||||||
var lastReactionsDownloadTime: Long? = null
|
var lastReactionsDownloadTime: Long? = null
|
||||||
|
|
||||||
|
fun id() = Hex.decode(idHex)
|
||||||
|
fun idNote() = id().toNote()
|
||||||
|
fun idDisplayNote() = idNote().toShortenHex()
|
||||||
|
|
||||||
fun loadEvent(event: Event, author: User, mentions: List<User>, replyTo: List<Note>) {
|
fun loadEvent(event: Event, author: User, mentions: List<User>, replyTo: List<Note>) {
|
||||||
this.event = event
|
this.event = event
|
||||||
this.author = author
|
this.author = author
|
||||||
this.mentions = mentions
|
this.mentions = mentions
|
||||||
this.replyTo = replyTo
|
this.replyTo = replyTo
|
||||||
|
|
||||||
live.invalidateData()
|
liveSet?.metadata?.invalidateData()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun formattedDateTime(timestamp: Long): String {
|
fun formattedDateTime(timestamp: Long): String {
|
||||||
@@ -103,31 +100,31 @@ class Note(val idHex: String) {
|
|||||||
fun addReply(note: Note) {
|
fun addReply(note: Note) {
|
||||||
if (note !in replies) {
|
if (note !in replies) {
|
||||||
replies = replies + note
|
replies = replies + note
|
||||||
liveReplies.invalidateData()
|
liveSet?.replies?.invalidateData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addBoost(note: Note) {
|
fun addBoost(note: Note) {
|
||||||
if (note !in boosts) {
|
if (note !in boosts) {
|
||||||
boosts = boosts + note
|
boosts = boosts + note
|
||||||
liveBoosts.invalidateData()
|
liveSet?.boosts?.invalidateData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addZap(zapRequest: Note, zap: Note?) {
|
fun addZap(zapRequest: Note, zap: Note?) {
|
||||||
if (zapRequest !in zaps.keys) {
|
if (zapRequest !in zaps.keys) {
|
||||||
zaps = zaps + Pair(zapRequest, zap)
|
zaps = zaps + Pair(zapRequest, zap)
|
||||||
liveZaps.invalidateData()
|
liveSet?.zaps?.invalidateData()
|
||||||
} else if (zapRequest in zaps.keys && zaps[zapRequest] == null) {
|
} else if (zapRequest in zaps.keys && zaps[zapRequest] == null) {
|
||||||
zaps = zaps + Pair(zapRequest, zap)
|
zaps = zaps + Pair(zapRequest, zap)
|
||||||
liveZaps.invalidateData()
|
liveSet?.zaps?.invalidateData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addReaction(note: Note) {
|
fun addReaction(note: Note) {
|
||||||
if (note !in reactions) {
|
if (note !in reactions) {
|
||||||
reactions = reactions + note
|
reactions = reactions + note
|
||||||
liveReactions.invalidateData()
|
liveSet?.reactions?.invalidateData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,17 +133,17 @@ class Note(val idHex: String) {
|
|||||||
|
|
||||||
if (author !in reports.keys) {
|
if (author !in reports.keys) {
|
||||||
reports = reports + Pair(author, setOf(note))
|
reports = reports + Pair(author, setOf(note))
|
||||||
liveReports.invalidateData()
|
liveSet?.reports?.invalidateData()
|
||||||
} else if (reports[author]?.contains(note) == false) {
|
} else if (reports[author]?.contains(note) == false) {
|
||||||
reports = reports + Pair(author, (reports[author] ?: emptySet()) + note)
|
reports = reports + Pair(author, (reports[author] ?: emptySet()) + note)
|
||||||
liveReports.invalidateData()
|
liveSet?.reports?.invalidateData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addRelay(relay: Relay) {
|
fun addRelay(relay: Relay) {
|
||||||
if (relay.url !in relays) {
|
if (relay.url !in relays) {
|
||||||
relays = relays + relay.url
|
relays = relays + relay.url
|
||||||
liveRelays.invalidateData()
|
liveSet?.relays?.invalidateData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,7 +186,7 @@ class Note(val idHex: String) {
|
|||||||
val dayAgo = Date().time / 1000 - 24*60*60
|
val dayAgo = Date().time / 1000 - 24*60*60
|
||||||
return reports.isNotEmpty() ||
|
return reports.isNotEmpty() ||
|
||||||
(author?.reports?.values?.filter {
|
(author?.reports?.values?.filter {
|
||||||
it.firstOrNull { it.event?.createdAt ?: 0 > dayAgo } != null
|
it.firstOrNull { ( it.event?.createdAt ?: 0 ) > dayAgo } != null
|
||||||
}?.isNotEmpty() ?: false)
|
}?.isNotEmpty() ?: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,7 +197,9 @@ class Note(val idHex: String) {
|
|||||||
try {
|
try {
|
||||||
val tag = matcher.group(1)?.let { event?.tags?.get(it.toInt()) }
|
val tag = matcher.group(1)?.let { event?.tags?.get(it.toInt()) }
|
||||||
if (tag != null && tag[0] == "p") {
|
if (tag != null && tag[0] == "p") {
|
||||||
returningList.add(LocalCache.getOrCreateUser(tag[1]))
|
LocalCache.checkGetOrCreateUser(tag[1])?.let {
|
||||||
|
returningList.add(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|
||||||
@@ -233,15 +232,42 @@ class Note(val idHex: String) {
|
|||||||
return boosts.firstOrNull { it.author == loggedIn && (it.event?.createdAt ?: 0) > currentTime - (60 * 5)} != null // 5 minute protection
|
return boosts.firstOrNull { it.author == loggedIn && (it.event?.createdAt ?: 0) > currentTime - (60 * 5)} != null // 5 minute protection
|
||||||
}
|
}
|
||||||
|
|
||||||
// Observers line up here.
|
var liveSet: NoteLiveSet? = null
|
||||||
val live: NoteLiveData = NoteLiveData(this)
|
|
||||||
|
|
||||||
val liveReactions: NoteLiveData = NoteLiveData(this)
|
fun live(): NoteLiveSet {
|
||||||
val liveBoosts: NoteLiveData = NoteLiveData(this)
|
if (liveSet == null) {
|
||||||
val liveReplies: NoteLiveData = NoteLiveData(this)
|
liveSet = NoteLiveSet(this)
|
||||||
val liveReports: NoteLiveData = NoteLiveData(this)
|
}
|
||||||
val liveRelays: NoteLiveData = NoteLiveData(this)
|
return liveSet!!
|
||||||
val liveZaps: NoteLiveData = NoteLiveData(this)
|
}
|
||||||
|
|
||||||
|
fun clearLive() {
|
||||||
|
if (liveSet != null && liveSet?.isInUse() == true) {
|
||||||
|
liveSet = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class NoteLiveSet(u: Note) {
|
||||||
|
// Observers line up here.
|
||||||
|
val metadata: NoteLiveData = NoteLiveData(u)
|
||||||
|
|
||||||
|
val reactions: NoteLiveData = NoteLiveData(u)
|
||||||
|
val boosts: NoteLiveData = NoteLiveData(u)
|
||||||
|
val replies: NoteLiveData = NoteLiveData(u)
|
||||||
|
val reports: NoteLiveData = NoteLiveData(u)
|
||||||
|
val relays: NoteLiveData = NoteLiveData(u)
|
||||||
|
val zaps: NoteLiveData = NoteLiveData(u)
|
||||||
|
|
||||||
|
fun isInUse(): Boolean {
|
||||||
|
return reactions.hasObservers()
|
||||||
|
|| boosts.hasObservers()
|
||||||
|
|| replies.hasObservers()
|
||||||
|
|| reports.hasObservers()
|
||||||
|
|| relays.hasObservers()
|
||||||
|
|| zaps.hasObservers()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class NoteLiveData(val note: Note): LiveData<NoteState>(NoteState(note)) {
|
class NoteLiveData(val note: Note): LiveData<NoteState>(NoteState(note)) {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.vitorpamplona.amethyst.model
|
package com.vitorpamplona.amethyst.model
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import com.vitorpamplona.amethyst.service.NostrSingleUserDataSource
|
import com.vitorpamplona.amethyst.service.NostrSingleUserDataSource
|
||||||
import com.vitorpamplona.amethyst.service.model.LnZapEvent
|
import com.vitorpamplona.amethyst.service.model.LnZapEvent
|
||||||
@@ -22,16 +21,10 @@ import nostr.postr.events.MetadataEvent
|
|||||||
import nostr.postr.toNpub
|
import nostr.postr.toNpub
|
||||||
|
|
||||||
class User(val pubkeyHex: String) {
|
class User(val pubkeyHex: String) {
|
||||||
val pubkey = Hex.decode(pubkeyHex)
|
var info: UserMetadata? = null
|
||||||
val pubkeyDisplayHex = pubkey.toNpub().toShortenHex()
|
|
||||||
|
|
||||||
var info = UserMetadata()
|
|
||||||
|
|
||||||
var updatedMetadataAt: Long = 0;
|
|
||||||
var updatedFollowsAt: Long = 0;
|
var updatedFollowsAt: Long = 0;
|
||||||
|
|
||||||
var latestContactList: ContactListEvent? = null
|
var latestContactList: ContactListEvent? = null
|
||||||
var latestMetadata: MetadataEvent? = null
|
|
||||||
|
|
||||||
var follows = setOf<User>()
|
var follows = setOf<User>()
|
||||||
private set
|
private set
|
||||||
@@ -56,44 +49,44 @@ class User(val pubkeyHex: String) {
|
|||||||
var relaysBeingUsed = mapOf<String, RelayInfo>()
|
var relaysBeingUsed = mapOf<String, RelayInfo>()
|
||||||
private set
|
private set
|
||||||
|
|
||||||
data class Chatroom(var roomMessages: Set<Note>)
|
|
||||||
|
|
||||||
var privateChatrooms = mapOf<User, Chatroom>()
|
var privateChatrooms = mapOf<User, Chatroom>()
|
||||||
private set
|
private set
|
||||||
|
|
||||||
var latestReportRequestEOSE: Long? = null
|
fun pubkey() = Hex.decode(pubkeyHex)
|
||||||
|
fun pubkeyNpub() = pubkey().toNpub()
|
||||||
|
fun pubkeyDisplayHex() = pubkeyNpub().toShortenHex()
|
||||||
|
|
||||||
fun toBestDisplayName(): String {
|
fun toBestDisplayName(): String {
|
||||||
return bestDisplayName() ?: bestUsername() ?: pubkeyDisplayHex
|
return bestDisplayName() ?: bestUsername() ?: pubkeyDisplayHex()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun bestUsername(): String? {
|
fun bestUsername(): String? {
|
||||||
return info.name?.ifBlank { null } ?: info.username?.ifBlank { null }
|
return info?.name?.ifBlank { null } ?: info?.username?.ifBlank { null }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun bestDisplayName(): String? {
|
fun bestDisplayName(): String? {
|
||||||
return info.displayName?.ifBlank { null } ?: info.display_name?.ifBlank { null }
|
return info?.displayName?.ifBlank { null } ?: info?.display_name?.ifBlank { null }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun profilePicture(): String? {
|
fun profilePicture(): String? {
|
||||||
if (info.picture.isNullOrBlank()) info.picture = null
|
if (info?.picture.isNullOrBlank()) info?.picture = null
|
||||||
return info.picture
|
return info?.picture
|
||||||
}
|
}
|
||||||
|
|
||||||
fun follow(user: User, followedAt: Long) {
|
fun follow(user: User, followedAt: Long) {
|
||||||
follows = follows + user
|
follows = follows + user
|
||||||
user.followers = user.followers + this
|
user.followers = user.followers + this
|
||||||
|
|
||||||
liveFollows.invalidateData()
|
liveSet?.follows?.invalidateData()
|
||||||
user.liveFollows.invalidateData()
|
user.liveSet?.follows?.invalidateData()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unfollow(user: User) {
|
fun unfollow(user: User) {
|
||||||
follows = follows - user
|
follows = follows - user
|
||||||
user.followers = user.followers - this
|
user.followers = user.followers - this
|
||||||
|
|
||||||
liveFollows.invalidateData()
|
liveSet?.follows?.invalidateData()
|
||||||
user.liveFollows.invalidateData()
|
user.liveSet?.follows?.invalidateData()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun follow(users: Set<User>, followedAt: Long) {
|
fun follow(users: Set<User>, followedAt: Long) {
|
||||||
@@ -101,11 +94,11 @@ class User(val pubkeyHex: String) {
|
|||||||
users.forEach {
|
users.forEach {
|
||||||
if (this !in it.followers) {
|
if (this !in it.followers) {
|
||||||
it.followers = it.followers + this
|
it.followers = it.followers + this
|
||||||
it.liveFollows.invalidateData()
|
it.liveSet?.follows?.invalidateData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
liveFollows.invalidateData()
|
liveSet?.follows?.invalidateData()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unfollow(users: Set<User>) {
|
fun unfollow(users: Set<User>) {
|
||||||
@@ -113,10 +106,10 @@ class User(val pubkeyHex: String) {
|
|||||||
users.forEach {
|
users.forEach {
|
||||||
if (this in it.followers) {
|
if (this in it.followers) {
|
||||||
it.followers = it.followers - this
|
it.followers = it.followers - this
|
||||||
it.liveFollows.invalidateData()
|
it.liveSet?.follows?.invalidateData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
liveFollows.invalidateData()
|
liveSet?.follows?.invalidateData()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addTaggedPost(note: Note) {
|
fun addTaggedPost(note: Note) {
|
||||||
@@ -138,10 +131,10 @@ class User(val pubkeyHex: String) {
|
|||||||
|
|
||||||
if (author !in reports.keys) {
|
if (author !in reports.keys) {
|
||||||
reports = reports + Pair(author, setOf(note))
|
reports = reports + Pair(author, setOf(note))
|
||||||
liveReports.invalidateData()
|
liveSet?.reports?.invalidateData()
|
||||||
} else if (reports[author]?.contains(note) == false) {
|
} else if (reports[author]?.contains(note) == false) {
|
||||||
reports = reports + Pair(author, (reports[author] ?: emptySet()) + note)
|
reports = reports + Pair(author, (reports[author] ?: emptySet()) + note)
|
||||||
liveReports.invalidateData()
|
liveSet?.reports?.invalidateData()
|
||||||
}
|
}
|
||||||
|
|
||||||
val reportTime = note.event?.createdAt ?: 0
|
val reportTime = note.event?.createdAt ?: 0
|
||||||
@@ -153,10 +146,10 @@ class User(val pubkeyHex: String) {
|
|||||||
fun addZap(zapRequest: Note, zap: Note?) {
|
fun addZap(zapRequest: Note, zap: Note?) {
|
||||||
if (zapRequest !in zaps.keys) {
|
if (zapRequest !in zaps.keys) {
|
||||||
zaps = zaps + Pair(zapRequest, zap)
|
zaps = zaps + Pair(zapRequest, zap)
|
||||||
liveZaps.invalidateData()
|
liveSet?.zaps?.invalidateData()
|
||||||
} else if (zapRequest in zaps.keys && zaps[zapRequest] == null) {
|
} else if (zapRequest in zaps.keys && zaps[zapRequest] == null) {
|
||||||
zaps = zaps + Pair(zapRequest, zap)
|
zaps = zaps + Pair(zapRequest, zap)
|
||||||
liveZaps.invalidateData()
|
liveSet?.zaps?.invalidateData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,16 +188,10 @@ class User(val pubkeyHex: String) {
|
|||||||
val privateChatroom = getOrCreatePrivateChatroom(user)
|
val privateChatroom = getOrCreatePrivateChatroom(user)
|
||||||
if (msg !in privateChatroom.roomMessages) {
|
if (msg !in privateChatroom.roomMessages) {
|
||||||
privateChatroom.roomMessages = privateChatroom.roomMessages + msg
|
privateChatroom.roomMessages = privateChatroom.roomMessages + msg
|
||||||
liveMessages.invalidateData()
|
liveSet?.messages?.invalidateData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class RelayInfo (
|
|
||||||
val url: String,
|
|
||||||
var lastEvent: Long,
|
|
||||||
var counter: Long
|
|
||||||
)
|
|
||||||
|
|
||||||
fun addRelay(relay: Relay, eventTime: Long) {
|
fun addRelay(relay: Relay, eventTime: Long) {
|
||||||
val here = relaysBeingUsed[relay.url]
|
val here = relaysBeingUsed[relay.url]
|
||||||
if (here == null) {
|
if (here == null) {
|
||||||
@@ -216,7 +203,7 @@ class User(val pubkeyHex: String) {
|
|||||||
here.counter++
|
here.counter++
|
||||||
}
|
}
|
||||||
|
|
||||||
liveRelayInfo.invalidateData()
|
liveSet?.relayInfo?.invalidateData()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateFollows(newFollows: Set<User>, updateAt: Long) {
|
fun updateFollows(newFollows: Set<User>, updateAt: Long) {
|
||||||
@@ -232,14 +219,15 @@ class User(val pubkeyHex: String) {
|
|||||||
fun updateRelays(relayUse: Map<String, ContactListEvent.ReadWrite>) {
|
fun updateRelays(relayUse: Map<String, ContactListEvent.ReadWrite>) {
|
||||||
// no need to test if relays are different. The Account will check for us.
|
// no need to test if relays are different. The Account will check for us.
|
||||||
relays = relayUse
|
relays = relayUse
|
||||||
liveRelays.invalidateData()
|
liveSet?.relays?.invalidateData()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateUserInfo(newUserInfo: UserMetadata, updateAt: Long) {
|
fun updateUserInfo(newUserInfo: UserMetadata, latestMetadata: MetadataEvent) {
|
||||||
info = newUserInfo
|
info = newUserInfo
|
||||||
updatedMetadataAt = updateAt
|
info?.latestMetadata = latestMetadata
|
||||||
|
info?.updatedMetadataAt = latestMetadata.createdAt
|
||||||
|
|
||||||
liveMetadata.invalidateData()
|
liveSet?.metadata?.invalidateData()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isFollowing(user: User): Boolean {
|
fun isFollowing(user: User): Boolean {
|
||||||
@@ -258,16 +246,55 @@ class User(val pubkeyHex: String) {
|
|||||||
} != null
|
} != null
|
||||||
}
|
}
|
||||||
|
|
||||||
// UI Observers line up here.
|
fun anyNameStartsWith(username: String): Boolean {
|
||||||
val liveFollows: UserLiveData = UserLiveData(this)
|
return info?.anyNameStartsWith(username) ?: false
|
||||||
val liveReports: UserLiveData = UserLiveData(this)
|
}
|
||||||
val liveMessages: UserLiveData = UserLiveData(this)
|
|
||||||
val liveRelays: UserLiveData = UserLiveData(this)
|
var liveSet: UserLiveSet? = null
|
||||||
val liveRelayInfo: UserLiveData = UserLiveData(this)
|
|
||||||
val liveMetadata: UserLiveData = UserLiveData(this)
|
fun live(): UserLiveSet {
|
||||||
val liveZaps: UserLiveData = UserLiveData(this)
|
if (liveSet == null) {
|
||||||
|
liveSet = UserLiveSet(this)
|
||||||
|
}
|
||||||
|
return liveSet!!
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearLive() {
|
||||||
|
if (liveSet != null && liveSet?.isInUse() == true) {
|
||||||
|
liveSet = null
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class UserLiveSet(u: User) {
|
||||||
|
// UI Observers line up here.
|
||||||
|
val follows: UserLiveData = UserLiveData(u)
|
||||||
|
val reports: UserLiveData = UserLiveData(u)
|
||||||
|
val messages: UserLiveData = UserLiveData(u)
|
||||||
|
val relays: UserLiveData = UserLiveData(u)
|
||||||
|
val relayInfo: UserLiveData = UserLiveData(u)
|
||||||
|
val metadata: UserLiveData = UserLiveData(u)
|
||||||
|
val zaps: UserLiveData = UserLiveData(u)
|
||||||
|
|
||||||
|
fun isInUse(): Boolean {
|
||||||
|
return follows.hasObservers()
|
||||||
|
|| reports.hasObservers()
|
||||||
|
|| messages.hasObservers()
|
||||||
|
|| relays.hasObservers()
|
||||||
|
|| relayInfo.hasObservers()
|
||||||
|
|| metadata.hasObservers()
|
||||||
|
|| zaps.hasObservers()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class RelayInfo (
|
||||||
|
val url: String,
|
||||||
|
var lastEvent: Long,
|
||||||
|
var counter: Long
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Chatroom(var roomMessages: Set<Note>)
|
||||||
|
|
||||||
class UserMetadata {
|
class UserMetadata {
|
||||||
var name: String? = null
|
var name: String? = null
|
||||||
var username: String? = null
|
var username: String? = null
|
||||||
@@ -287,6 +314,9 @@ class UserMetadata {
|
|||||||
var main_relay: String? = null
|
var main_relay: String? = null
|
||||||
var twitter: String? = null
|
var twitter: String? = null
|
||||||
|
|
||||||
|
var updatedMetadataAt: Long = 0;
|
||||||
|
var latestMetadata: MetadataEvent? = null
|
||||||
|
|
||||||
fun anyNameStartsWith(prefix: String): Boolean {
|
fun anyNameStartsWith(prefix: String): Boolean {
|
||||||
return listOfNotNull(name, username, display_name, displayName, nip05, lud06, lud16)
|
return listOfNotNull(name, username, display_name, displayName, nip05, lud06, lud16)
|
||||||
.filter { it.startsWith(prefix, true) }.isNotEmpty()
|
.filter { it.startsWith(prefix, true) }.isNotEmpty()
|
||||||
@@ -323,12 +353,12 @@ class UserLiveData(val user: User): LiveData<UserState>(UserState(user)) {
|
|||||||
|
|
||||||
override fun onActive() {
|
override fun onActive() {
|
||||||
super.onActive()
|
super.onActive()
|
||||||
NostrSingleUserDataSource.add(user.pubkeyHex)
|
NostrSingleUserDataSource.add(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onInactive() {
|
override fun onInactive() {
|
||||||
super.onInactive()
|
super.onInactive()
|
||||||
NostrSingleUserDataSource.remove(user.pubkeyHex)
|
NostrSingleUserDataSource.remove(user)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ object NostrHomeDataSource: NostrDataSource("HomeFeed") {
|
|||||||
override fun start() {
|
override fun start() {
|
||||||
if (this::account.isInitialized) {
|
if (this::account.isInitialized) {
|
||||||
GlobalScope.launch(Dispatchers.Main) {
|
GlobalScope.launch(Dispatchers.Main) {
|
||||||
account.userProfile().liveFollows.observeForever(cacheListener)
|
account.userProfile().live().follows.observeForever(cacheListener)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
super.start()
|
super.start()
|
||||||
@@ -36,7 +36,7 @@ object NostrHomeDataSource: NostrDataSource("HomeFeed") {
|
|||||||
super.stop()
|
super.stop()
|
||||||
if (this::account.isInitialized) {
|
if (this::account.isInitialized) {
|
||||||
GlobalScope.launch(Dispatchers.Main) {
|
GlobalScope.launch(Dispatchers.Main) {
|
||||||
account.userProfile().liveFollows.removeObserver(cacheListener)
|
account.userProfile().live().follows.removeObserver(cacheListener)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -45,7 +45,7 @@ object NostrHomeDataSource: NostrDataSource("HomeFeed") {
|
|||||||
val follows = account.userProfile().follows
|
val follows = account.userProfile().follows
|
||||||
|
|
||||||
val followKeys = follows.map {
|
val followKeys = follows.map {
|
||||||
it.pubkey.toHex().substring(0, 6)
|
it.pubkeyHex.substring(0, 6)
|
||||||
}
|
}
|
||||||
|
|
||||||
val followSet = followKeys.plus(account.userProfile().pubkeyHex.substring(0, 6))
|
val followSet = followKeys.plus(account.userProfile().pubkeyHex.substring(0, 6))
|
||||||
|
|||||||
@@ -9,17 +9,17 @@ import nostr.postr.JsonFilter
|
|||||||
import nostr.postr.events.MetadataEvent
|
import nostr.postr.events.MetadataEvent
|
||||||
|
|
||||||
object NostrSingleUserDataSource: NostrDataSource("SingleUserFeed") {
|
object NostrSingleUserDataSource: NostrDataSource("SingleUserFeed") {
|
||||||
var usersToWatch = setOf<String>()
|
var usersToWatch = setOf<User>()
|
||||||
|
|
||||||
fun createUserFilter(): List<TypedFilter>? {
|
fun createUserFilter(): List<TypedFilter>? {
|
||||||
if (usersToWatch.isEmpty()) return null
|
if (usersToWatch.isEmpty()) return null
|
||||||
|
|
||||||
return usersToWatch.filter { LocalCache.getOrCreateUser(it).latestMetadata == null }.map {
|
return usersToWatch.filter { it.info?.latestMetadata == null }.map {
|
||||||
TypedFilter(
|
TypedFilter(
|
||||||
types = FeedType.values().toSet(),
|
types = FeedType.values().toSet(),
|
||||||
filter = JsonFilter(
|
filter = JsonFilter(
|
||||||
kinds = listOf(MetadataEvent.kind),
|
kinds = listOf(MetadataEvent.kind),
|
||||||
authors = listOf(it),
|
authors = listOf(it.pubkeyHex),
|
||||||
limit = 1
|
limit = 1
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -34,8 +34,8 @@ object NostrSingleUserDataSource: NostrDataSource("SingleUserFeed") {
|
|||||||
types = FeedType.values().toSet(),
|
types = FeedType.values().toSet(),
|
||||||
filter = JsonFilter(
|
filter = JsonFilter(
|
||||||
kinds = listOf(ReportEvent.kind),
|
kinds = listOf(ReportEvent.kind),
|
||||||
tags = mapOf("p" to listOf(it)),
|
tags = mapOf("p" to listOf(it.pubkeyHex)),
|
||||||
since = LocalCache.users[it]?.latestReportTime
|
since = it.latestReportTime
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -54,13 +54,13 @@ object NostrSingleUserDataSource: NostrDataSource("SingleUserFeed") {
|
|||||||
userChannelOnce.typedFilters = listOfNotNull(createUserReportFilter()).flatten().ifEmpty { null }
|
userChannelOnce.typedFilters = listOfNotNull(createUserReportFilter()).flatten().ifEmpty { null }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun add(userId: String) {
|
fun add(user: User) {
|
||||||
usersToWatch = usersToWatch.plus(userId)
|
usersToWatch = usersToWatch.plus(user)
|
||||||
invalidateFilters()
|
invalidateFilters()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun remove(userId: String) {
|
fun remove(user: User) {
|
||||||
usersToWatch = usersToWatch.minus(userId)
|
usersToWatch = usersToWatch.minus(user)
|
||||||
invalidateFilters()
|
invalidateFilters()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -161,7 +161,7 @@ class NewPostViewModel: ViewModel() {
|
|||||||
userSuggestionAnchor?.let {
|
userSuggestionAnchor?.let {
|
||||||
val lastWord = message.text.substring(0, it.end).substringAfterLast("\n").substringAfterLast(" ")
|
val lastWord = message.text.substring(0, it.end).substringAfterLast("\n").substringAfterLast(" ")
|
||||||
val lastWordStart = it.end - lastWord.length
|
val lastWordStart = it.end - lastWord.length
|
||||||
val wordToInsert = "@${item.pubkey.toNpub()} "
|
val wordToInsert = "@${item.pubkeyNpub()} "
|
||||||
|
|
||||||
message = TextFieldValue(
|
message = TextFieldValue(
|
||||||
message.text.replaceRange(lastWordStart, it.end, wordToInsert),
|
message.text.replaceRange(lastWordStart, it.end, wordToInsert),
|
||||||
|
|||||||
@@ -29,19 +29,19 @@ class NewUserMetadataViewModel: ViewModel() {
|
|||||||
account.userProfile().let {
|
account.userProfile().let {
|
||||||
userName.value = it.bestUsername() ?: ""
|
userName.value = it.bestUsername() ?: ""
|
||||||
displayName.value = it.bestDisplayName() ?: ""
|
displayName.value = it.bestDisplayName() ?: ""
|
||||||
about.value = it.info.about ?: ""
|
about.value = it.info?.about ?: ""
|
||||||
picture.value = it.info.picture ?: ""
|
picture.value = it.info?.picture ?: ""
|
||||||
banner.value = it.info.banner ?: ""
|
banner.value = it.info?.banner ?: ""
|
||||||
website.value = it.info.website ?: ""
|
website.value = it.info?.website ?: ""
|
||||||
nip05.value = it.info.nip05 ?: ""
|
nip05.value = it.info?.nip05 ?: ""
|
||||||
lnAddress.value = it.info.lud16 ?: ""
|
lnAddress.value = it.info?.lud16 ?: ""
|
||||||
lnURL.value = it.info.lud06 ?: ""
|
lnURL.value = it.info?.lud06 ?: ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun create() {
|
fun create() {
|
||||||
// Tries to not delete any existing attribute that we do not work with.
|
// Tries to not delete any existing attribute that we do not work with.
|
||||||
val latest = account.userProfile().latestMetadata
|
val latest = account.userProfile().info?.latestMetadata
|
||||||
val currentJson = if (latest != null) {
|
val currentJson = if (latest != null) {
|
||||||
ObjectMapper().readTree(
|
ObjectMapper().readTree(
|
||||||
ByteArrayInputStream(latest.content.toByteArray(Charsets.UTF_8))
|
ByteArrayInputStream(latest.content.toByteArray(Charsets.UTF_8))
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ fun ClickableNoteTag(
|
|||||||
navController: NavController
|
navController: NavController
|
||||||
) {
|
) {
|
||||||
ClickableText(
|
ClickableText(
|
||||||
text = AnnotatedString("@${baesNote.id.toNote().toShortenHex()} "),
|
text = AnnotatedString("@${baesNote.idNote().toShortenHex()} "),
|
||||||
onClick = { navController.navigate("Note/${baesNote.idHex}") },
|
onClick = { navController.navigate("Note/${baesNote.idHex}") },
|
||||||
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary)
|
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -9,13 +9,8 @@ import androidx.compose.runtime.livedata.observeAsState
|
|||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.User
|
|
||||||
import com.vitorpamplona.amethyst.model.toByteArray
|
|
||||||
import com.vitorpamplona.amethyst.model.toNote
|
|
||||||
import com.vitorpamplona.amethyst.service.Nip19
|
import com.vitorpamplona.amethyst.service.Nip19
|
||||||
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
|
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
|
||||||
import com.vitorpamplona.amethyst.ui.note.toShortenHex
|
|
||||||
import nostr.postr.toNpub
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ClickableRoute(
|
fun ClickableRoute(
|
||||||
@@ -25,7 +20,7 @@ fun ClickableRoute(
|
|||||||
if (nip19.type == Nip19.Type.USER) {
|
if (nip19.type == Nip19.Type.USER) {
|
||||||
val userBase = LocalCache.getOrCreateUser(nip19.hex)
|
val userBase = LocalCache.getOrCreateUser(nip19.hex)
|
||||||
|
|
||||||
val userState by userBase.liveMetadata.observeAsState()
|
val userState by userBase.live().metadata.observeAsState()
|
||||||
val user = userState?.user ?: return
|
val user = userState?.user ?: return
|
||||||
|
|
||||||
val route = "User/${nip19.hex}"
|
val route = "User/${nip19.hex}"
|
||||||
@@ -38,12 +33,12 @@ fun ClickableRoute(
|
|||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
val noteBase = LocalCache.getOrCreateNote(nip19.hex)
|
val noteBase = LocalCache.getOrCreateNote(nip19.hex)
|
||||||
val noteState by noteBase.live.observeAsState()
|
val noteState by noteBase.live().metadata.observeAsState()
|
||||||
val note = noteState?.note ?: return
|
val note = noteState?.note ?: return
|
||||||
|
|
||||||
if (note.event is ChannelCreateEvent) {
|
if (note.event is ChannelCreateEvent) {
|
||||||
ClickableText(
|
ClickableText(
|
||||||
text = AnnotatedString("@${note.idDisplayNote} "),
|
text = AnnotatedString("@${note.idDisplayNote()} "),
|
||||||
onClick = { navController.navigate("Channel/${nip19.hex}") },
|
onClick = { navController.navigate("Channel/${nip19.hex}") },
|
||||||
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary)
|
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary)
|
||||||
)
|
)
|
||||||
@@ -55,7 +50,7 @@ fun ClickableRoute(
|
|||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
ClickableText(
|
ClickableText(
|
||||||
text = AnnotatedString("@${note.idDisplayNote} "),
|
text = AnnotatedString("@${note.idDisplayNote()} "),
|
||||||
onClick = { navController.navigate("Note/${nip19.hex}") },
|
onClick = { navController.navigate("Note/${nip19.hex}") },
|
||||||
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary)
|
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ fun ClickableUserTag(
|
|||||||
user: User,
|
user: User,
|
||||||
navController: NavController
|
navController: NavController
|
||||||
) {
|
) {
|
||||||
val innerUserState by user.liveMetadata.observeAsState()
|
val innerUserState by user.live().metadata.observeAsState()
|
||||||
ClickableText(
|
ClickableText(
|
||||||
text = AnnotatedString("@${innerUserState?.user?.toBestDisplayName()} "),
|
text = AnnotatedString("@${innerUserState?.user?.toBestDisplayName()} "),
|
||||||
onClick = { navController.navigate("User/${innerUserState?.user?.pubkeyHex}") },
|
onClick = { navController.navigate("User/${innerUserState?.user?.pubkeyHex}") },
|
||||||
|
|||||||
@@ -6,18 +6,23 @@ import com.vitorpamplona.amethyst.model.Note
|
|||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
|
|
||||||
object ChatroomFeedFilter: FeedFilter<Note>() {
|
object ChatroomFeedFilter: FeedFilter<Note>() {
|
||||||
lateinit var account: Account
|
var account: Account? = null
|
||||||
lateinit var withUser: User
|
var withUser: User? = null
|
||||||
|
|
||||||
fun loadMessagesBetween(accountIn: Account, userId: String) {
|
fun loadMessagesBetween(accountIn: Account, userId: String) {
|
||||||
account = accountIn
|
account = accountIn
|
||||||
withUser = LocalCache.getOrCreateUser(userId)
|
withUser = LocalCache.checkGetOrCreateUser(userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns the last Note of each user.
|
// returns the last Note of each user.
|
||||||
override fun feed(): List<Note> {
|
override fun feed(): List<Note> {
|
||||||
val messages = account.userProfile().privateChatrooms[withUser] ?: return emptyList()
|
val myAccount = account
|
||||||
|
val myUser = withUser
|
||||||
|
|
||||||
return messages.roomMessages.filter { account.isAcceptable(it) }.sortedBy { it.event?.createdAt }.reversed()
|
if (myAccount == null || myUser == null) return emptyList()
|
||||||
|
|
||||||
|
val messages = myAccount.userProfile().privateChatrooms[myUser] ?: return emptyList()
|
||||||
|
|
||||||
|
return messages.roomMessages.filter { myAccount.isAcceptable(it) }.sortedBy { it.event?.createdAt }.reversed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,17 +6,17 @@ import com.vitorpamplona.amethyst.model.Note
|
|||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
|
|
||||||
object UserProfileNoteFeedFilter: FeedFilter<Note>() {
|
object UserProfileNoteFeedFilter: FeedFilter<Note>() {
|
||||||
lateinit var account: Account
|
var account: Account? = null
|
||||||
var user: User? = null
|
var user: User? = null
|
||||||
|
|
||||||
fun loadUserProfile(accountLoggedIn: Account, userId: String) {
|
fun loadUserProfile(accountLoggedIn: Account, userId: String) {
|
||||||
account = accountLoggedIn
|
account = accountLoggedIn
|
||||||
user = LocalCache.getOrCreateUser(userId)
|
user = LocalCache.checkGetOrCreateUser(userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun feed(): List<Note> {
|
override fun feed(): List<Note> {
|
||||||
return user?.notes
|
return user?.notes
|
||||||
?.filter { account.isAcceptable(it) }
|
?.filter { account?.isAcceptable(it) == true }
|
||||||
?.sortedBy { it.event?.createdAt }
|
?.sortedBy { it.event?.createdAt }
|
||||||
?.reversed()
|
?.reversed()
|
||||||
?: emptyList()
|
?: emptyList()
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ object UserProfileReportsFeedFilter: FeedFilter<Note>() {
|
|||||||
var user: User? = null
|
var user: User? = null
|
||||||
|
|
||||||
fun loadUserProfile(userId: String) {
|
fun loadUserProfile(userId: String) {
|
||||||
user = LocalCache.getOrCreateUser(userId)
|
user = LocalCache.checkGetOrCreateUser(userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun feed(): List<Note> {
|
override fun feed(): List<Note> {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ object UserProfileZapsFeedFilter: FeedFilter<Pair<Note,Note>>() {
|
|||||||
var user: User? = null
|
var user: User? = null
|
||||||
|
|
||||||
fun loadUserProfile(userId: String) {
|
fun loadUserProfile(userId: String) {
|
||||||
user = LocalCache.getOrCreateUser(userId)
|
user = LocalCache.checkGetOrCreateUser(userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun feed(): List<Pair<Note,Note>> {
|
override fun feed(): List<Pair<Note,Note>> {
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ fun MainTopBar(scaffoldState: ScaffoldState, accountViewModel: AccountViewModel)
|
|||||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||||
val account = accountState?.account ?: return
|
val account = accountState?.account ?: return
|
||||||
|
|
||||||
val accountUserState by account.userProfile().liveMetadata.observeAsState()
|
val accountUserState by account.userProfile().live().metadata.observeAsState()
|
||||||
val accountUser = accountUserState?.user ?: return
|
val accountUser = accountUserState?.user ?: return
|
||||||
|
|
||||||
val relayViewModel: RelayPoolViewModel = viewModel { RelayPoolViewModel() }
|
val relayViewModel: RelayPoolViewModel = viewModel { RelayPoolViewModel() }
|
||||||
@@ -149,7 +149,7 @@ fun MainTopBar(scaffoldState: ScaffoldState, accountViewModel: AccountViewModel)
|
|||||||
println("Image Memory Cache ${(imageLoader.memoryCache?.size ?: 0)/(1024*1024)}/${(imageLoader.memoryCache?.maxSize ?: 0)/(1024*1024)} MB")
|
println("Image Memory Cache ${(imageLoader.memoryCache?.size ?: 0)/(1024*1024)}/${(imageLoader.memoryCache?.maxSize ?: 0)/(1024*1024)} MB")
|
||||||
|
|
||||||
println("Notes: " + LocalCache.notes.filter { it.value.event != null }.size +"/"+ LocalCache.notes.size)
|
println("Notes: " + LocalCache.notes.filter { it.value.event != null }.size +"/"+ LocalCache.notes.size)
|
||||||
println("Users: " + LocalCache.users.filter { it.value.latestMetadata != null }.size +"/"+ LocalCache.users.size)
|
println("Users: " + LocalCache.users.filter { it.value.info?.latestMetadata != null }.size +"/"+ LocalCache.users.size)
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
|
|||||||
@@ -123,16 +123,16 @@ fun DrawerContent(navController: NavHostController,
|
|||||||
fun ProfileContent(baseAccountUser: User, modifier: Modifier = Modifier, scaffoldState: ScaffoldState, navController: NavController) {
|
fun ProfileContent(baseAccountUser: User, modifier: Modifier = Modifier, scaffoldState: ScaffoldState, navController: NavController) {
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
val accountUserState by baseAccountUser.liveMetadata.observeAsState()
|
val accountUserState by baseAccountUser.live().metadata.observeAsState()
|
||||||
val accountUser = accountUserState?.user ?: return
|
val accountUser = accountUserState?.user ?: return
|
||||||
|
|
||||||
val accountUserFollowsState by baseAccountUser.liveFollows.observeAsState()
|
val accountUserFollowsState by baseAccountUser.live().follows.observeAsState()
|
||||||
val accountUserFollows = accountUserFollowsState?.user ?: return
|
val accountUserFollows = accountUserFollowsState?.user ?: return
|
||||||
|
|
||||||
val ctx = LocalContext.current.applicationContext
|
val ctx = LocalContext.current.applicationContext
|
||||||
|
|
||||||
Box {
|
Box {
|
||||||
val banner = accountUser.info.banner
|
val banner = accountUser.info?.banner
|
||||||
if (banner != null && banner.isNotBlank()) {
|
if (banner != null && banner.isNotBlank()) {
|
||||||
AsyncImageProxy(
|
AsyncImageProxy(
|
||||||
model = ResizeImage(banner, 150.dp),
|
model = ResizeImage(banner, 150.dp),
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BoostSetCompose(boostSetCard: BoostSetCard, isInnerNote: Boolean = false, routeForLastRead: String, accountViewModel: AccountViewModel, navController: NavController) {
|
fun BoostSetCompose(boostSetCard: BoostSetCard, isInnerNote: Boolean = false, routeForLastRead: String, accountViewModel: AccountViewModel, navController: NavController) {
|
||||||
val noteState by boostSetCard.note.live.observeAsState()
|
val noteState by boostSetCard.note.live().metadata.observeAsState()
|
||||||
val note = noteState?.note
|
val note = noteState?.note
|
||||||
|
|
||||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.note
|
package com.vitorpamplona.amethyst.ui.note
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.border
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Arrangement.Center
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
@@ -33,7 +30,6 @@ import androidx.compose.ui.graphics.Color
|
|||||||
import androidx.compose.ui.graphics.painter.BitmapPainter
|
import androidx.compose.ui.graphics.painter.BitmapPainter
|
||||||
import androidx.compose.ui.graphics.painter.Painter
|
import androidx.compose.ui.graphics.painter.Painter
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.text.TextStyle
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextDirection
|
import androidx.compose.ui.text.style.TextDirection
|
||||||
@@ -41,9 +37,6 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||||||
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.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import coil.compose.AsyncImage
|
|
||||||
import coil.compose.rememberAsyncImagePainter
|
|
||||||
import com.vitorpamplona.amethyst.LocalPreferences
|
|
||||||
import com.vitorpamplona.amethyst.NotificationCache
|
import com.vitorpamplona.amethyst.NotificationCache
|
||||||
import com.vitorpamplona.amethyst.RoboHashCache
|
import com.vitorpamplona.amethyst.RoboHashCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
@@ -55,7 +48,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChatroomCompose(baseNote: Note, accountViewModel: AccountViewModel, navController: NavController) {
|
fun ChatroomCompose(baseNote: Note, accountViewModel: AccountViewModel, navController: NavController) {
|
||||||
val noteState by baseNote.live.observeAsState()
|
val noteState by baseNote.live().metadata.observeAsState()
|
||||||
val note = noteState?.note
|
val note = noteState?.note
|
||||||
|
|
||||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||||
@@ -69,7 +62,7 @@ fun ChatroomCompose(baseNote: Note, accountViewModel: AccountViewModel, navContr
|
|||||||
if (note?.event == null) {
|
if (note?.event == null) {
|
||||||
BlankNote(Modifier)
|
BlankNote(Modifier)
|
||||||
} else if (note.channel != null) {
|
} else if (note.channel != null) {
|
||||||
val authorState by note.author!!.liveMetadata.observeAsState()
|
val authorState by note.author!!.live().metadata.observeAsState()
|
||||||
val author = authorState?.user
|
val author = authorState?.user
|
||||||
|
|
||||||
val channelState by note.channel!!.live.observeAsState()
|
val channelState by note.channel!!.live.observeAsState()
|
||||||
|
|||||||
@@ -2,33 +2,27 @@ package com.vitorpamplona.amethyst.ui.note
|
|||||||
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.border
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.layout.widthIn
|
|
||||||
import androidx.compose.foundation.layout.wrapContentWidth
|
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.Icon
|
import androidx.compose.material.Icon
|
||||||
import androidx.compose.material.IconButton
|
import androidx.compose.material.IconButton
|
||||||
import androidx.compose.material.LocalTextStyle
|
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.Surface
|
import androidx.compose.material.Surface
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.ChevronRight
|
import androidx.compose.material.icons.filled.ChevronRight
|
||||||
import androidx.compose.material.icons.filled.ExpandMore
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -47,12 +41,10 @@ import androidx.compose.ui.graphics.painter.BitmapPainter
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextDirection
|
|
||||||
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.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import coil.compose.rememberAsyncImagePainter
|
|
||||||
import com.google.accompanist.flowlayout.FlowRow
|
import com.google.accompanist.flowlayout.FlowRow
|
||||||
import com.vitorpamplona.amethyst.NotificationCache
|
import com.vitorpamplona.amethyst.NotificationCache
|
||||||
import com.vitorpamplona.amethyst.RoboHashCache
|
import com.vitorpamplona.amethyst.RoboHashCache
|
||||||
@@ -60,11 +52,8 @@ import com.vitorpamplona.amethyst.model.Note
|
|||||||
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
|
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
|
import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.ReactionEvent
|
|
||||||
import com.vitorpamplona.amethyst.service.model.RepostEvent
|
|
||||||
import com.vitorpamplona.amethyst.ui.components.AsyncImageProxy
|
import com.vitorpamplona.amethyst.ui.components.AsyncImageProxy
|
||||||
import com.vitorpamplona.amethyst.ui.components.ResizeImage
|
import com.vitorpamplona.amethyst.ui.components.ResizeImage
|
||||||
import com.vitorpamplona.amethyst.ui.components.RichTextViewer
|
|
||||||
import com.vitorpamplona.amethyst.ui.components.TranslateableRichTextViewer
|
import com.vitorpamplona.amethyst.ui.components.TranslateableRichTextViewer
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|
||||||
@@ -74,13 +63,13 @@ val ChatBubbleShapeThem = RoundedCornerShape(3.dp, 15.dp, 15.dp, 15.dp)
|
|||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ChatroomMessageCompose(baseNote: Note, routeForLastRead: String?, innerQuote: Boolean = false, accountViewModel: AccountViewModel, navController: NavController) {
|
fun ChatroomMessageCompose(baseNote: Note, routeForLastRead: String?, innerQuote: Boolean = false, accountViewModel: AccountViewModel, navController: NavController) {
|
||||||
val noteState by baseNote.live.observeAsState()
|
val noteState by baseNote.live().metadata.observeAsState()
|
||||||
val note = noteState?.note
|
val note = noteState?.note
|
||||||
|
|
||||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||||
val account = accountState?.account ?: return
|
val account = accountState?.account ?: return
|
||||||
|
|
||||||
val noteReportsState by baseNote.liveReports.observeAsState()
|
val noteReportsState by baseNote.live().reports.observeAsState()
|
||||||
val noteForReports = noteReportsState?.note ?: return
|
val noteForReports = noteReportsState?.note ?: return
|
||||||
|
|
||||||
val accountUser = account.userProfile()
|
val accountUser = account.userProfile()
|
||||||
@@ -158,7 +147,7 @@ fun ChatroomMessageCompose(baseNote: Note, routeForLastRead: String?, innerQuote
|
|||||||
modifier = Modifier.padding(start = 10.dp, end = 10.dp, bottom = 5.dp),
|
modifier = Modifier.padding(start = 10.dp, end = 10.dp, bottom = 5.dp),
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val authorState by note.author!!.liveMetadata.observeAsState()
|
val authorState by note.author!!.live().metadata.observeAsState()
|
||||||
val author = authorState?.user!!
|
val author = authorState?.user!!
|
||||||
|
|
||||||
if (innerQuote || author != accountUser && note.event is ChannelMessageEvent) {
|
if (innerQuote || author != accountUser && note.event is ChannelMessageEvent) {
|
||||||
@@ -279,7 +268,7 @@ fun ChatroomMessageCompose(baseNote: Note, routeForLastRead: String?, innerQuote
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RelayBadges(baseNote: Note) {
|
private fun RelayBadges(baseNote: Note) {
|
||||||
val noteRelaysState by baseNote.liveRelays.observeAsState()
|
val noteRelaysState by baseNote.live().relays.observeAsState()
|
||||||
val noteRelays = noteRelaysState?.note?.relays ?: emptySet()
|
val noteRelays = noteRelaysState?.note?.relays ?: emptySet()
|
||||||
|
|
||||||
var expanded by remember { mutableStateOf(false) }
|
var expanded by remember { mutableStateOf(false) }
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.note
|
package com.vitorpamplona.amethyst.ui.note
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
|
||||||
import androidx.compose.material.Icon
|
import androidx.compose.material.Icon
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@@ -21,14 +18,11 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
|
||||||
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.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import coil.compose.AsyncImage
|
|
||||||
import coil.compose.rememberAsyncImagePainter
|
|
||||||
import com.google.accompanist.flowlayout.FlowRow
|
import com.google.accompanist.flowlayout.FlowRow
|
||||||
import com.vitorpamplona.amethyst.NotificationCache
|
import com.vitorpamplona.amethyst.NotificationCache
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
@@ -37,7 +31,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LikeSetCompose(likeSetCard: LikeSetCard, modifier: Modifier = Modifier, isInnerNote: Boolean = false, routeForLastRead: String, accountViewModel: AccountViewModel, navController: NavController) {
|
fun LikeSetCompose(likeSetCard: LikeSetCard, modifier: Modifier = Modifier, isInnerNote: Boolean = false, routeForLastRead: String, accountViewModel: AccountViewModel, navController: NavController) {
|
||||||
val noteState by likeSetCard.note.live.observeAsState()
|
val noteState by likeSetCard.note.live().metadata.observeAsState()
|
||||||
val note = noteState?.note
|
val note = noteState?.note
|
||||||
|
|
||||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.Arrangement
|
|||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
@@ -23,8 +22,6 @@ import androidx.compose.material.DropdownMenu
|
|||||||
import androidx.compose.material.DropdownMenuItem
|
import androidx.compose.material.DropdownMenuItem
|
||||||
import androidx.compose.material.Icon
|
import androidx.compose.material.Icon
|
||||||
import androidx.compose.material.IconButton
|
import androidx.compose.material.IconButton
|
||||||
import androidx.compose.material.LocalContentColor
|
|
||||||
import androidx.compose.material.LocalTextStyle
|
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
@@ -40,10 +37,8 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.graphics.ColorFilter
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
import androidx.compose.ui.graphics.ColorMatrix
|
import androidx.compose.ui.graphics.ColorMatrix
|
||||||
import androidx.compose.ui.graphics.Shape
|
|
||||||
import androidx.compose.ui.graphics.painter.BitmapPainter
|
import androidx.compose.ui.graphics.painter.BitmapPainter
|
||||||
import androidx.compose.ui.platform.LocalClipboardManager
|
import androidx.compose.ui.platform.LocalClipboardManager
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
@@ -51,33 +46,27 @@ import androidx.compose.ui.platform.LocalUriHandler
|
|||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextDirection
|
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import coil.compose.rememberAsyncImagePainter
|
|
||||||
import com.google.accompanist.flowlayout.FlowRow
|
import com.google.accompanist.flowlayout.FlowRow
|
||||||
import com.vitorpamplona.amethyst.NotificationCache
|
import com.vitorpamplona.amethyst.NotificationCache
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.RoboHashCache
|
import com.vitorpamplona.amethyst.RoboHashCache
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.model.toNote
|
import com.vitorpamplona.amethyst.model.toNote
|
||||||
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.LnZapEvent
|
|
||||||
import com.vitorpamplona.amethyst.service.model.ReactionEvent
|
import com.vitorpamplona.amethyst.service.model.ReactionEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.ReportEvent
|
import com.vitorpamplona.amethyst.service.model.ReportEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.RepostEvent
|
import com.vitorpamplona.amethyst.service.model.RepostEvent
|
||||||
import com.vitorpamplona.amethyst.ui.components.AsyncImageProxy
|
import com.vitorpamplona.amethyst.ui.components.AsyncImageProxy
|
||||||
import com.vitorpamplona.amethyst.ui.components.ResizeImage
|
import com.vitorpamplona.amethyst.ui.components.ResizeImage
|
||||||
import com.vitorpamplona.amethyst.ui.components.RichTextViewer
|
|
||||||
import com.vitorpamplona.amethyst.ui.components.TranslateableRichTextViewer
|
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.Following
|
import com.vitorpamplona.amethyst.ui.theme.Following
|
||||||
import nostr.postr.events.TextNoteEvent
|
import nostr.postr.events.TextNoteEvent
|
||||||
import nostr.postr.toNpub
|
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -92,10 +81,10 @@ fun NoteCompose(
|
|||||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||||
val account = accountState?.account ?: return
|
val account = accountState?.account ?: return
|
||||||
|
|
||||||
val noteState by baseNote.live.observeAsState()
|
val noteState by baseNote.live().metadata.observeAsState()
|
||||||
val note = noteState?.note
|
val note = noteState?.note
|
||||||
|
|
||||||
val noteReportsState by baseNote.liveReports.observeAsState()
|
val noteReportsState by baseNote.live().reports.observeAsState()
|
||||||
val noteForReports = noteReportsState?.note ?: return
|
val noteForReports = noteReportsState?.note ?: return
|
||||||
|
|
||||||
var popupExpanded by remember { mutableStateOf(false) }
|
var popupExpanded by remember { mutableStateOf(false) }
|
||||||
@@ -346,7 +335,7 @@ fun NoteCompose(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RelayBadges(baseNote: Note) {
|
private fun RelayBadges(baseNote: Note) {
|
||||||
val noteRelaysState by baseNote.liveRelays.observeAsState()
|
val noteRelaysState by baseNote.live().relays.observeAsState()
|
||||||
val noteRelays = noteRelaysState?.note?.relays ?: emptySet()
|
val noteRelays = noteRelaysState?.note?.relays ?: emptySet()
|
||||||
|
|
||||||
var expanded by remember { mutableStateOf(false) }
|
var expanded by remember { mutableStateOf(false) }
|
||||||
@@ -423,7 +412,7 @@ fun NoteAuthorPicture(
|
|||||||
pictureModifier: Modifier = Modifier,
|
pictureModifier: Modifier = Modifier,
|
||||||
onClick: ((User) -> Unit)? = null
|
onClick: ((User) -> Unit)? = null
|
||||||
) {
|
) {
|
||||||
val noteState by baseNote.live.observeAsState()
|
val noteState by baseNote.live().metadata.observeAsState()
|
||||||
val note = noteState?.note ?: return
|
val note = noteState?.note ?: return
|
||||||
|
|
||||||
val author = note.author
|
val author = note.author
|
||||||
@@ -470,7 +459,7 @@ fun UserPicture(
|
|||||||
pictureModifier: Modifier = Modifier,
|
pictureModifier: Modifier = Modifier,
|
||||||
onClick: ((User) -> Unit)? = null
|
onClick: ((User) -> Unit)? = null
|
||||||
) {
|
) {
|
||||||
val userState by baseUser.liveMetadata.observeAsState()
|
val userState by baseUser.live().metadata.observeAsState()
|
||||||
val user = userState?.user ?: return
|
val user = userState?.user ?: return
|
||||||
|
|
||||||
val ctx = LocalContext.current.applicationContext
|
val ctx = LocalContext.current.applicationContext
|
||||||
@@ -499,7 +488,7 @@ fun UserPicture(
|
|||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val accountState by baseUserAccount.liveFollows.observeAsState()
|
val accountState by baseUserAccount.live().follows.observeAsState()
|
||||||
val accountUser = accountState?.user ?: return
|
val accountUser = accountState?.user ?: return
|
||||||
|
|
||||||
if (accountUser.isFollowing(user) || user == accountUser) {
|
if (accountUser.isFollowing(user) || user == accountUser) {
|
||||||
@@ -545,10 +534,10 @@ fun NoteDropDownMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Unit,
|
|||||||
DropdownMenuItem(onClick = { clipboardManager.setText(AnnotatedString(accountViewModel.decrypt(note) ?: "")); onDismiss() }) {
|
DropdownMenuItem(onClick = { clipboardManager.setText(AnnotatedString(accountViewModel.decrypt(note) ?: "")); onDismiss() }) {
|
||||||
Text("Copy Text")
|
Text("Copy Text")
|
||||||
}
|
}
|
||||||
DropdownMenuItem(onClick = { clipboardManager.setText(AnnotatedString(note.author?.pubkey?.toNpub() ?: "")); onDismiss() }) {
|
DropdownMenuItem(onClick = { clipboardManager.setText(AnnotatedString(note.author?.pubkeyNpub() ?: "")); onDismiss() }) {
|
||||||
Text("Copy User PubKey")
|
Text("Copy User PubKey")
|
||||||
}
|
}
|
||||||
DropdownMenuItem(onClick = { clipboardManager.setText(AnnotatedString(note.id.toNote())); onDismiss() }) {
|
DropdownMenuItem(onClick = { clipboardManager.setText(AnnotatedString(note.idNote())); onDismiss() }) {
|
||||||
Text("Copy Note ID")
|
Text("Copy Note ID")
|
||||||
}
|
}
|
||||||
Divider()
|
Divider()
|
||||||
|
|||||||
@@ -53,16 +53,16 @@ fun ReactionsRow(baseNote: Note, accountViewModel: AccountViewModel) {
|
|||||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||||
val account = accountState?.account ?: return
|
val account = accountState?.account ?: return
|
||||||
|
|
||||||
val reactionsState by baseNote.liveReactions.observeAsState()
|
val reactionsState by baseNote.live().reactions.observeAsState()
|
||||||
val reactedNote = reactionsState?.note
|
val reactedNote = reactionsState?.note
|
||||||
|
|
||||||
val boostsState by baseNote.liveBoosts.observeAsState()
|
val boostsState by baseNote.live().boosts.observeAsState()
|
||||||
val boostedNote = boostsState?.note
|
val boostedNote = boostsState?.note
|
||||||
|
|
||||||
val zapsState by baseNote.liveZaps.observeAsState()
|
val zapsState by baseNote.live().zaps.observeAsState()
|
||||||
val zappedNote = zapsState?.note
|
val zappedNote = zapsState?.note
|
||||||
|
|
||||||
val repliesState by baseNote.liveReplies.observeAsState()
|
val repliesState by baseNote.live().replies.observeAsState()
|
||||||
val replies = repliesState?.note?.replies ?: emptySet()
|
val replies = repliesState?.note?.replies ?: emptySet()
|
||||||
|
|
||||||
val grayTint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
val grayTint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import androidx.compose.ui.text.font.FontWeight
|
|||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
|
import com.vitorpamplona.amethyst.model.RelayInfo
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
@@ -30,7 +31,7 @@ import java.time.format.DateTimeFormatter
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RelayCompose(
|
fun RelayCompose(
|
||||||
relay: User.RelayInfo,
|
relay: RelayInfo,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
navController: NavController,
|
navController: NavController,
|
||||||
onAddRelay: () -> Unit,
|
onAddRelay: () -> Unit,
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ fun ReplyInformation(replyTo: List<Note>?, mentions: List<User>?, prefix: String
|
|||||||
val mentionSet = mentions.toSet()
|
val mentionSet = mentions.toSet()
|
||||||
|
|
||||||
mentionSet.toSet().forEachIndexed { idx, user ->
|
mentionSet.toSet().forEachIndexed { idx, user ->
|
||||||
val innerUserState by user.liveMetadata.observeAsState()
|
val innerUserState by user.live().metadata.observeAsState()
|
||||||
val innerUser = innerUserState?.user
|
val innerUser = innerUserState?.user
|
||||||
|
|
||||||
innerUser?.let { myUser ->
|
innerUser?.let { myUser ->
|
||||||
@@ -123,7 +123,7 @@ fun ReplyInformationChannel(replyTo: List<Note>?,
|
|||||||
val mentionSet = mentions.toSet()
|
val mentionSet = mentions.toSet()
|
||||||
|
|
||||||
mentionSet.forEachIndexed { idx, user ->
|
mentionSet.forEachIndexed { idx, user ->
|
||||||
val innerUserState by user.liveMetadata.observeAsState()
|
val innerUserState by user.live().metadata.observeAsState()
|
||||||
val innerUser = innerUserState?.user
|
val innerUser = innerUserState?.user
|
||||||
|
|
||||||
innerUser?.let { myUser ->
|
innerUser?.let { myUser ->
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ fun UserCompose(baseUser: User, accountViewModel: AccountViewModel, navControlle
|
|||||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||||
val account = accountState?.account ?: return
|
val account = accountState?.account ?: return
|
||||||
|
|
||||||
val userState by account.userProfile().liveFollows.observeAsState()
|
val userState by account.userProfile().live().follows.observeAsState()
|
||||||
val userFollows = userState?.user ?: return
|
val userFollows = userState?.user ?: return
|
||||||
|
|
||||||
val ctx = LocalContext.current.applicationContext
|
val ctx = LocalContext.current.applicationContext
|
||||||
@@ -60,11 +60,11 @@ fun UserCompose(baseUser: User, accountViewModel: AccountViewModel, navControlle
|
|||||||
UsernameDisplay(baseUser)
|
UsernameDisplay(baseUser)
|
||||||
}
|
}
|
||||||
|
|
||||||
val userState by baseUser.liveMetadata.observeAsState()
|
val userState by baseUser.live().metadata.observeAsState()
|
||||||
val user = userState?.user ?: return
|
val user = userState?.user ?: return
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
user.info.about ?: "",
|
user.info?.about ?: "",
|
||||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import com.vitorpamplona.amethyst.model.User
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun NoteUsernameDisplay(baseNote: Note, weight: Modifier = Modifier) {
|
fun NoteUsernameDisplay(baseNote: Note, weight: Modifier = Modifier) {
|
||||||
val noteState by baseNote.live.observeAsState()
|
val noteState by baseNote.live().metadata.observeAsState()
|
||||||
val note = noteState?.note ?: return
|
val note = noteState?.note ?: return
|
||||||
|
|
||||||
val author = note.author
|
val author = note.author
|
||||||
@@ -26,7 +26,7 @@ fun NoteUsernameDisplay(baseNote: Note, weight: Modifier = Modifier) {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun UsernameDisplay(baseUser: User, weight: Modifier = Modifier) {
|
fun UsernameDisplay(baseUser: User, weight: Modifier = Modifier) {
|
||||||
val userState by baseUser.liveMetadata.observeAsState()
|
val userState by baseUser.live().metadata.observeAsState()
|
||||||
val user = userState?.user ?: return
|
val user = userState?.user ?: return
|
||||||
|
|
||||||
if (user.bestUsername() != null || user.bestDisplayName() != null) {
|
if (user.bestUsername() != null || user.bestDisplayName() != null) {
|
||||||
@@ -53,7 +53,7 @@ fun UsernameDisplay(baseUser: User, weight: Modifier = Modifier) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Text(
|
Text(
|
||||||
user.pubkeyDisplayHex,
|
user.pubkeyDisplayHex(),
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import androidx.compose.foundation.clickable
|
|||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.Divider
|
import androidx.compose.material.Divider
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
@@ -21,7 +20,6 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import com.vitorpamplona.amethyst.LocalPreferences
|
import com.vitorpamplona.amethyst.LocalPreferences
|
||||||
import com.vitorpamplona.amethyst.lnurl.LnInvoiceUtil
|
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.service.model.LnZapEvent
|
import com.vitorpamplona.amethyst.service.model.LnZapEvent
|
||||||
import com.vitorpamplona.amethyst.ui.screen.FollowButton
|
import com.vitorpamplona.amethyst.ui.screen.FollowButton
|
||||||
@@ -35,13 +33,13 @@ fun ZapNoteCompose(baseNote: Pair<Note, Note>, accountViewModel: AccountViewMode
|
|||||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||||
val account = accountState?.account ?: return
|
val account = accountState?.account ?: return
|
||||||
|
|
||||||
val userState by account.userProfile().liveFollows.observeAsState()
|
val userState by account.userProfile().live().follows.observeAsState()
|
||||||
val userFollows = userState?.user ?: return
|
val userFollows = userState?.user ?: return
|
||||||
|
|
||||||
val noteState by baseNote.second.live.observeAsState()
|
val noteState by baseNote.second.live().metadata.observeAsState()
|
||||||
val noteZap = noteState?.note ?: return
|
val noteZap = noteState?.note ?: return
|
||||||
|
|
||||||
val baseNoteRequest by baseNote.first.live.observeAsState()
|
val baseNoteRequest by baseNote.first.live().metadata.observeAsState()
|
||||||
val noteZapRequest = baseNoteRequest?.note ?: return
|
val noteZapRequest = baseNoteRequest?.note ?: return
|
||||||
|
|
||||||
val baseAuthor = noteZapRequest.author
|
val baseAuthor = noteZapRequest.author
|
||||||
@@ -74,11 +72,11 @@ fun ZapNoteCompose(baseNote: Pair<Note, Note>, accountViewModel: AccountViewMode
|
|||||||
UsernameDisplay(baseAuthor)
|
UsernameDisplay(baseAuthor)
|
||||||
}
|
}
|
||||||
|
|
||||||
val userState by baseAuthor.liveMetadata.observeAsState()
|
val userState by baseAuthor.live().metadata.observeAsState()
|
||||||
val user = userState?.user ?: return
|
val user = userState?.user ?: return
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
user.info.about ?: "",
|
user.info?.about ?: "",
|
||||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
|
|||||||
@@ -20,22 +20,18 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import com.google.accompanist.flowlayout.FlowRow
|
import com.google.accompanist.flowlayout.FlowRow
|
||||||
import com.vitorpamplona.amethyst.NotificationCache
|
import com.vitorpamplona.amethyst.NotificationCache
|
||||||
import com.vitorpamplona.amethyst.R
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.LikeSetCard
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.ZapSetCard
|
import com.vitorpamplona.amethyst.ui.screen.ZapSetCard
|
||||||
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
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ZapSetCompose(zapSetCard: ZapSetCard, modifier: Modifier = Modifier, isInnerNote: Boolean = false, routeForLastRead: String, accountViewModel: AccountViewModel, navController: NavController) {
|
fun ZapSetCompose(zapSetCard: ZapSetCard, modifier: Modifier = Modifier, isInnerNote: Boolean = false, routeForLastRead: String, accountViewModel: AccountViewModel, navController: NavController) {
|
||||||
val noteState by zapSetCard.note.live.observeAsState()
|
val noteState by zapSetCard.note.live().metadata.observeAsState()
|
||||||
val note = noteState?.note
|
val note = noteState?.note
|
||||||
|
|
||||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ fun ShowQRDialog(user: User, onScan: (String) -> Unit, onClose: () -> Unit) {
|
|||||||
horizontalArrangement = Arrangement.Center,
|
horizontalArrangement = Arrangement.Center,
|
||||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 35.dp, vertical = 10.dp)
|
modifier = Modifier.fillMaxWidth().padding(horizontal = 35.dp, vertical = 10.dp)
|
||||||
) {
|
) {
|
||||||
QrCodeDrawer("nostr:${user.pubkey.toNpub()}")
|
QrCodeDrawer("nostr:${user.pubkeyNpub()}")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import androidx.lifecycle.viewModelScope
|
|||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import com.google.accompanist.swiperefresh.SwipeRefresh
|
import com.google.accompanist.swiperefresh.SwipeRefresh
|
||||||
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
||||||
|
import com.vitorpamplona.amethyst.model.RelayInfo
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.model.UserState
|
import com.vitorpamplona.amethyst.model.UserState
|
||||||
import com.vitorpamplona.amethyst.service.NostrHomeDataSource
|
import com.vitorpamplona.amethyst.service.NostrHomeDataSource
|
||||||
@@ -35,9 +36,9 @@ import kotlinx.coroutines.flow.update
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class RelayFeedViewModel: ViewModel() {
|
class RelayFeedViewModel: ViewModel() {
|
||||||
val order = compareByDescending<User.RelayInfo> { it.lastEvent }.thenByDescending { it.counter }.thenBy { it.url }
|
val order = compareByDescending<RelayInfo> { it.lastEvent }.thenByDescending { it.counter }.thenBy { it.url }
|
||||||
|
|
||||||
private val _feedContent = MutableStateFlow<List<User.RelayInfo>>(emptyList())
|
private val _feedContent = MutableStateFlow<List<RelayInfo>>(emptyList())
|
||||||
val feedContent = _feedContent.asStateFlow()
|
val feedContent = _feedContent.asStateFlow()
|
||||||
|
|
||||||
var currentUser: User? = null
|
var currentUser: User? = null
|
||||||
@@ -49,7 +50,7 @@ class RelayFeedViewModel: ViewModel() {
|
|||||||
|
|
||||||
val newRelaysFromRecord = currentUser?.relays?.entries?.mapNotNull {
|
val newRelaysFromRecord = currentUser?.relays?.entries?.mapNotNull {
|
||||||
if (it.key !in beingUsedSet) {
|
if (it.key !in beingUsedSet) {
|
||||||
User.RelayInfo(it.key, 0, 0)
|
RelayInfo(it.key, 0, 0)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
@@ -67,14 +68,14 @@ class RelayFeedViewModel: ViewModel() {
|
|||||||
|
|
||||||
fun subscribeTo(user: User) {
|
fun subscribeTo(user: User) {
|
||||||
currentUser = user
|
currentUser = user
|
||||||
user.liveRelays.observeForever(listener)
|
user.live().relays.observeForever(listener)
|
||||||
user.liveRelayInfo.observeForever(listener)
|
user.live().relayInfo.observeForever(listener)
|
||||||
invalidateData()
|
invalidateData()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unsubscribeTo(user: User) {
|
fun unsubscribeTo(user: User) {
|
||||||
user.liveRelays.removeObserver(listener)
|
user.live().relays.removeObserver(listener)
|
||||||
user.liveRelayInfo.removeObserver(listener)
|
user.live().relayInfo.removeObserver(listener)
|
||||||
currentUser = null
|
currentUser = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,20 +3,15 @@ package com.vitorpamplona.amethyst.ui.screen
|
|||||||
import androidx.compose.animation.Crossfade
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.combinedClickable
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
|
||||||
import androidx.compose.material.Divider
|
import androidx.compose.material.Divider
|
||||||
import androidx.compose.material.LocalTextStyle
|
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@@ -28,20 +23,15 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
|
||||||
import androidx.compose.ui.draw.drawBehind
|
import androidx.compose.ui.draw.drawBehind
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.ui.text.style.TextDirection
|
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import coil.compose.AsyncImage
|
|
||||||
import coil.compose.rememberAsyncImagePainter
|
|
||||||
import com.google.accompanist.swiperefresh.SwipeRefresh
|
import com.google.accompanist.swiperefresh.SwipeRefresh
|
||||||
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.ui.components.RichTextViewer
|
|
||||||
import com.vitorpamplona.amethyst.ui.components.TranslateableRichTextViewer
|
import com.vitorpamplona.amethyst.ui.components.TranslateableRichTextViewer
|
||||||
import com.vitorpamplona.amethyst.ui.note.BlankNote
|
import com.vitorpamplona.amethyst.ui.note.BlankNote
|
||||||
import com.vitorpamplona.amethyst.ui.note.HiddenNote
|
import com.vitorpamplona.amethyst.ui.note.HiddenNote
|
||||||
@@ -49,8 +39,6 @@ import com.vitorpamplona.amethyst.ui.note.NoteAuthorPicture
|
|||||||
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
||||||
import com.vitorpamplona.amethyst.ui.note.NoteUsernameDisplay
|
import com.vitorpamplona.amethyst.ui.note.NoteUsernameDisplay
|
||||||
import com.vitorpamplona.amethyst.ui.note.ReactionsRow
|
import com.vitorpamplona.amethyst.ui.note.ReactionsRow
|
||||||
import com.vitorpamplona.amethyst.ui.note.UserPicture
|
|
||||||
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
|
|
||||||
import com.vitorpamplona.amethyst.ui.note.timeAgoLong
|
import com.vitorpamplona.amethyst.ui.note.timeAgoLong
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|
||||||
@@ -162,10 +150,10 @@ fun Modifier.drawReplyLevel(level: Int, color: Color): Modifier = this
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun NoteMaster(baseNote: Note, accountViewModel: AccountViewModel, navController: NavController) {
|
fun NoteMaster(baseNote: Note, accountViewModel: AccountViewModel, navController: NavController) {
|
||||||
val noteState by baseNote.live.observeAsState()
|
val noteState by baseNote.live().metadata.observeAsState()
|
||||||
val note = noteState?.note
|
val note = noteState?.note
|
||||||
|
|
||||||
val noteReportsState by baseNote.liveReports.observeAsState()
|
val noteReportsState by baseNote.live().reports.observeAsState()
|
||||||
val noteForReports = noteReportsState?.note ?: return
|
val noteForReports = noteReportsState?.note ?: return
|
||||||
|
|
||||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||||
|
|||||||
@@ -271,7 +271,7 @@ private fun NoteCopyButton(
|
|||||||
expanded = popupExpanded,
|
expanded = popupExpanded,
|
||||||
onDismissRequest = { popupExpanded = false }
|
onDismissRequest = { popupExpanded = false }
|
||||||
) {
|
) {
|
||||||
DropdownMenuItem(onClick = { clipboardManager.setText(AnnotatedString(note.id.toNote())); popupExpanded = false }) {
|
DropdownMenuItem(onClick = { clipboardManager.setText(AnnotatedString(note.idNote())); popupExpanded = false }) {
|
||||||
Text("Copy Channel ID (Note) to the Clipboard")
|
Text("Copy Channel ID (Note) to the Clipboard")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ fun ChatroomHeader(baseUser: User, accountViewModel: AccountViewModel, navContro
|
|||||||
Column(modifier = Modifier.padding(12.dp)) {
|
Column(modifier = Modifier.padding(12.dp)) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
|
||||||
val authorState by baseUser.liveMetadata.observeAsState()
|
val authorState by baseUser.live().metadata.observeAsState()
|
||||||
val author = authorState?.user!!
|
val author = authorState?.user!!
|
||||||
|
|
||||||
AsyncImageProxy(
|
AsyncImageProxy(
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ fun ProfileScreen(userId: String?, accountViewModel: AccountViewModel, navContro
|
|||||||
selected = pagerState.currentPage == 1,
|
selected = pagerState.currentPage == 1,
|
||||||
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(1) } },
|
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(1) } },
|
||||||
text = {
|
text = {
|
||||||
val userState by baseUser.liveFollows.observeAsState()
|
val userState by baseUser.live().follows.observeAsState()
|
||||||
val userFollows = userState?.user?.follows?.size ?: "--"
|
val userFollows = userState?.user?.follows?.size ?: "--"
|
||||||
|
|
||||||
Text(text = "$userFollows Follows")
|
Text(text = "$userFollows Follows")
|
||||||
@@ -187,7 +187,7 @@ fun ProfileScreen(userId: String?, accountViewModel: AccountViewModel, navContro
|
|||||||
selected = pagerState.currentPage == 2,
|
selected = pagerState.currentPage == 2,
|
||||||
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(2) } },
|
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(2) } },
|
||||||
text = {
|
text = {
|
||||||
val userState by baseUser.liveFollows.observeAsState()
|
val userState by baseUser.live().follows.observeAsState()
|
||||||
val userFollows = userState?.user?.followers?.size ?: "--"
|
val userFollows = userState?.user?.followers?.size ?: "--"
|
||||||
|
|
||||||
Text(text = "$userFollows Followers")
|
Text(text = "$userFollows Followers")
|
||||||
@@ -198,7 +198,7 @@ fun ProfileScreen(userId: String?, accountViewModel: AccountViewModel, navContro
|
|||||||
selected = pagerState.currentPage == 3,
|
selected = pagerState.currentPage == 3,
|
||||||
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(3) } },
|
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(3) } },
|
||||||
text = {
|
text = {
|
||||||
val userState by baseUser.liveZaps.observeAsState()
|
val userState by baseUser.live().zaps.observeAsState()
|
||||||
val userZaps = userState?.user?.zappedAmount()
|
val userZaps = userState?.user?.zappedAmount()
|
||||||
|
|
||||||
Text(text = "${showAmount(userZaps)} Zaps")
|
Text(text = "${showAmount(userZaps)} Zaps")
|
||||||
@@ -209,7 +209,7 @@ fun ProfileScreen(userId: String?, accountViewModel: AccountViewModel, navContro
|
|||||||
selected = pagerState.currentPage == 4,
|
selected = pagerState.currentPage == 4,
|
||||||
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(4) } },
|
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(4) } },
|
||||||
text = {
|
text = {
|
||||||
val userState by baseUser.liveReports.observeAsState()
|
val userState by baseUser.live().reports.observeAsState()
|
||||||
val userReports = userState?.user?.reports?.values?.flatten()?.count()
|
val userReports = userState?.user?.reports?.values?.flatten()?.count()
|
||||||
|
|
||||||
Text(text = "${userReports} Reports")
|
Text(text = "${userReports} Reports")
|
||||||
@@ -220,11 +220,11 @@ fun ProfileScreen(userId: String?, accountViewModel: AccountViewModel, navContro
|
|||||||
selected = pagerState.currentPage == 5,
|
selected = pagerState.currentPage == 5,
|
||||||
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(5) } },
|
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(5) } },
|
||||||
text = {
|
text = {
|
||||||
val userState by baseUser.liveRelays.observeAsState()
|
val userState by baseUser.live().relays.observeAsState()
|
||||||
val userRelaysBeingUsed =
|
val userRelaysBeingUsed =
|
||||||
userState?.user?.relaysBeingUsed?.size ?: "--"
|
userState?.user?.relaysBeingUsed?.size ?: "--"
|
||||||
|
|
||||||
val userStateRelayInfo by baseUser.liveRelayInfo.observeAsState()
|
val userStateRelayInfo by baseUser.live().relayInfo.observeAsState()
|
||||||
val userRelays = userStateRelayInfo?.user?.relays?.size ?: "--"
|
val userRelays = userStateRelayInfo?.user?.relays?.size ?: "--"
|
||||||
|
|
||||||
Text(text = "$userRelaysBeingUsed / $userRelays Relays")
|
Text(text = "$userRelaysBeingUsed / $userRelays Relays")
|
||||||
@@ -263,7 +263,7 @@ private fun ProfileHeader(
|
|||||||
val ctx = LocalContext.current.applicationContext
|
val ctx = LocalContext.current.applicationContext
|
||||||
var popupExpanded by remember { mutableStateOf(false) }
|
var popupExpanded by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val accountUserState by account.userProfile().liveFollows.observeAsState()
|
val accountUserState by account.userProfile().live().follows.observeAsState()
|
||||||
val accountUser = accountUserState?.user ?: return
|
val accountUser = accountUserState?.user ?: return
|
||||||
|
|
||||||
Box {
|
Box {
|
||||||
@@ -357,24 +357,28 @@ private fun ProfileHeader(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun DrawAdditionalInfo(baseUser: User, account: Account) {
|
private fun DrawAdditionalInfo(baseUser: User, account: Account) {
|
||||||
val userState by baseUser.liveMetadata.observeAsState()
|
val userState by baseUser.live().metadata.observeAsState()
|
||||||
val user = userState?.user ?: return
|
val user = userState?.user ?: return
|
||||||
|
|
||||||
val uri = LocalUriHandler.current
|
val uri = LocalUriHandler.current
|
||||||
|
|
||||||
Text(
|
user.bestDisplayName()?.let {
|
||||||
user.bestDisplayName() ?: "",
|
Text( "$it",
|
||||||
modifier = Modifier.padding(top = 7.dp),
|
modifier = Modifier.padding(top = 7.dp),
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
fontSize = 25.sp
|
fontSize = 25.sp
|
||||||
)
|
)
|
||||||
Text(
|
}
|
||||||
"@${user.bestUsername()}",
|
|
||||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
|
||||||
modifier = Modifier.padding(top = 1.dp, bottom = 1.dp, start = 5.dp)
|
|
||||||
)
|
|
||||||
|
|
||||||
val website = user.info.website
|
user.bestUsername()?.let {
|
||||||
|
Text(
|
||||||
|
"@$it",
|
||||||
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||||
|
modifier = Modifier.padding(top = 1.dp, bottom = 1.dp, start = 5.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val website = user.info?.website
|
||||||
if (!website.isNullOrEmpty()) {
|
if (!website.isNullOrEmpty()) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -386,7 +390,7 @@ private fun DrawAdditionalInfo(baseUser: User, account: Account) {
|
|||||||
|
|
||||||
ClickableText(
|
ClickableText(
|
||||||
text = AnnotatedString(website.removePrefix("https://")),
|
text = AnnotatedString(website.removePrefix("https://")),
|
||||||
onClick = { user.info.website?.let { runCatching { uri.openUri(it) } } },
|
onClick = { website.let { runCatching { uri.openUri(it) } } },
|
||||||
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary),
|
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary),
|
||||||
modifier = Modifier.padding(top = 1.dp, bottom = 1.dp, start = 5.dp)
|
modifier = Modifier.padding(top = 1.dp, bottom = 1.dp, start = 5.dp)
|
||||||
)
|
)
|
||||||
@@ -395,7 +399,7 @@ private fun DrawAdditionalInfo(baseUser: User, account: Account) {
|
|||||||
|
|
||||||
var ZapExpanded by remember { mutableStateOf(false) }
|
var ZapExpanded by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val lud16 = user.info.lud16?.trim()
|
val lud16 = user.info?.lud16?.trim()
|
||||||
|
|
||||||
if (!lud16.isNullOrEmpty()) {
|
if (!lud16.isNullOrEmpty()) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
@@ -423,19 +427,21 @@ private fun DrawAdditionalInfo(baseUser: User, account: Account) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Text(
|
user.info?.about?.let {
|
||||||
"${user.info.about}",
|
Text(
|
||||||
color = MaterialTheme.colors.onSurface,
|
"$it",
|
||||||
modifier = Modifier.padding(top = 5.dp, bottom = 5.dp)
|
color = MaterialTheme.colors.onSurface,
|
||||||
)
|
modifier = Modifier.padding(top = 5.dp, bottom = 5.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun DrawBanner(baseUser: User) {
|
private fun DrawBanner(baseUser: User) {
|
||||||
val userState by baseUser.liveMetadata.observeAsState()
|
val userState by baseUser.live().metadata.observeAsState()
|
||||||
val user = userState?.user ?: return
|
val user = userState?.user ?: return
|
||||||
|
|
||||||
val banner = user.info.banner
|
val banner = user.info?.banner
|
||||||
|
|
||||||
if (banner != null && banner.isNotBlank()) {
|
if (banner != null && banner.isNotBlank()) {
|
||||||
AsyncImageProxy(
|
AsyncImageProxy(
|
||||||
@@ -632,7 +638,7 @@ private fun NPubCopyButton(
|
|||||||
expanded = popupExpanded,
|
expanded = popupExpanded,
|
||||||
onDismissRequest = { popupExpanded = false }
|
onDismissRequest = { popupExpanded = false }
|
||||||
) {
|
) {
|
||||||
DropdownMenuItem(onClick = { clipboardManager.setText(AnnotatedString(user.pubkey.toNpub())); popupExpanded = false }) {
|
DropdownMenuItem(onClick = { clipboardManager.setText(AnnotatedString(user.pubkeyNpub())); popupExpanded = false }) {
|
||||||
Text("Copy Public Key (NPub) to the Clipboard")
|
Text("Copy Public Key (NPub) to the Clipboard")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -750,7 +756,7 @@ fun UserProfileDropDownMenu(user: User, popupExpanded: Boolean, onDismiss: () ->
|
|||||||
expanded = popupExpanded,
|
expanded = popupExpanded,
|
||||||
onDismissRequest = onDismiss
|
onDismissRequest = onDismiss
|
||||||
) {
|
) {
|
||||||
DropdownMenuItem(onClick = { clipboardManager.setText(AnnotatedString(user.pubkey.toNpub())); onDismiss() }) {
|
DropdownMenuItem(onClick = { clipboardManager.setText(AnnotatedString(user.pubkeyNpub())); onDismiss() }) {
|
||||||
Text("Copy User ID")
|
Text("Copy User ID")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -296,11 +296,11 @@ fun UserLine(
|
|||||||
UsernameDisplay(baseUser)
|
UsernameDisplay(baseUser)
|
||||||
}
|
}
|
||||||
|
|
||||||
val userState by baseUser.liveMetadata.observeAsState()
|
val userState by baseUser.live().metadata.observeAsState()
|
||||||
val user = userState?.user ?: return
|
val user = userState?.user ?: return
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
user.info.about?.take(100) ?: "",
|
user.info?.about?.take(100) ?: "",
|
||||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
|
|||||||
Reference in New Issue
Block a user