mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-09-20 13:51:25 +02:00
Removes several Indexes from User and Notes to see if we can reduce App's memory usage.
This commit is contained in:
@@ -16,7 +16,6 @@ import com.vitorpamplona.amethyst.service.relays.Constants
|
|||||||
import com.vitorpamplona.amethyst.service.relays.FeedType
|
import com.vitorpamplona.amethyst.service.relays.FeedType
|
||||||
import com.vitorpamplona.amethyst.service.relays.Relay
|
import com.vitorpamplona.amethyst.service.relays.Relay
|
||||||
import com.vitorpamplona.amethyst.service.relays.RelayPool
|
import com.vitorpamplona.amethyst.service.relays.RelayPool
|
||||||
import java.util.Date
|
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -150,7 +149,7 @@ class Account(
|
|||||||
if (!isWriteable()) return null
|
if (!isWriteable()) return null
|
||||||
|
|
||||||
note.event?.let {
|
note.event?.let {
|
||||||
return LnZapRequestEvent.create(it, userProfile().relays?.keys?.ifEmpty { null } ?: localRelays.map { it.url }.toSet(), loggedIn.privKey!!)
|
return LnZapRequestEvent.create(it, userProfile().latestContactList?.relays()?.keys?.ifEmpty { null } ?: localRelays.map { it.url }.toSet(), loggedIn.privKey!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
@@ -163,7 +162,7 @@ class Account(
|
|||||||
fun createZapRequestFor(userPubKeyHex: String): LnZapRequestEvent? {
|
fun createZapRequestFor(userPubKeyHex: String): LnZapRequestEvent? {
|
||||||
if (!isWriteable()) return null
|
if (!isWriteable()) return null
|
||||||
|
|
||||||
return LnZapRequestEvent.create(userPubKeyHex, userProfile().relays?.keys?.ifEmpty { null } ?: localRelays.map { it.url }.toSet(), loggedIn.privKey!!)
|
return LnZapRequestEvent.create(userPubKeyHex, userProfile().latestContactList?.relays()?.keys?.ifEmpty { null } ?: localRelays.map { it.url }.toSet(), loggedIn.privKey!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun report(note: Note, type: ReportEvent.ReportType) {
|
fun report(note: Note, type: ReportEvent.ReportType) {
|
||||||
@@ -246,7 +245,7 @@ class Account(
|
|||||||
val event = if (contactList != null && follows.isNotEmpty()) {
|
val event = if (contactList != null && follows.isNotEmpty()) {
|
||||||
ContactListEvent.create(
|
ContactListEvent.create(
|
||||||
follows.plus(Contact(user.pubkeyHex, null)),
|
follows.plus(Contact(user.pubkeyHex, null)),
|
||||||
userProfile().relays,
|
contactList.relays(),
|
||||||
loggedIn.privKey!!)
|
loggedIn.privKey!!)
|
||||||
} else {
|
} else {
|
||||||
val relays = Constants.defaultRelays.associate { it.url to ContactListEvent.ReadWrite(it.read, it.write) }
|
val relays = Constants.defaultRelays.associate { it.url to ContactListEvent.ReadWrite(it.read, it.write) }
|
||||||
@@ -270,7 +269,7 @@ class Account(
|
|||||||
if (contactList != null && follows.isNotEmpty()) {
|
if (contactList != null && follows.isNotEmpty()) {
|
||||||
val event = ContactListEvent.create(
|
val event = ContactListEvent.create(
|
||||||
follows.filter { it.pubKeyHex != user.pubkeyHex },
|
follows.filter { it.pubKeyHex != user.pubkeyHex },
|
||||||
userProfile().relays,
|
contactList.relays(),
|
||||||
loggedIn.privKey!!)
|
loggedIn.privKey!!)
|
||||||
|
|
||||||
Client.send(event)
|
Client.send(event)
|
||||||
@@ -443,7 +442,7 @@ class Account(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun updateContactListTo(newContactList: ContactListEvent?) {
|
private fun updateContactListTo(newContactList: ContactListEvent?) {
|
||||||
if (newContactList?.follows().isNullOrEmpty()) return
|
if (newContactList?.unverifiedFollowKeySet().isNullOrEmpty()) return
|
||||||
|
|
||||||
// Events might be different objects, we have to compare their ids.
|
// Events might be different objects, we have to compare their ids.
|
||||||
if (backupContactList?.id != newContactList?.id) {
|
if (backupContactList?.id != newContactList?.id) {
|
||||||
@@ -454,7 +453,7 @@ class Account(
|
|||||||
|
|
||||||
// Takes a User's relay list and adds the types of feeds they are active for.
|
// Takes a User's relay list and adds the types of feeds they are active for.
|
||||||
fun activeRelays(): Array<Relay>? {
|
fun activeRelays(): Array<Relay>? {
|
||||||
var usersRelayList = userProfile().relays?.map {
|
var usersRelayList = userProfile().latestContactList?.relays()?.map {
|
||||||
val localFeedTypes = localRelays.firstOrNull() { localRelay -> localRelay.url == it.key }?.feedTypes ?: FeedType.values().toSet()
|
val localFeedTypes = localRelays.firstOrNull() { localRelay -> localRelay.url == it.key }?.feedTypes ?: FeedType.values().toSet()
|
||||||
Relay(it.key, it.value.read, it.value.write, localFeedTypes)
|
Relay(it.key, it.value.read, it.value.write, localFeedTypes)
|
||||||
} ?: return null
|
} ?: return null
|
||||||
@@ -490,15 +489,23 @@ class Account(
|
|||||||
|
|
||||||
fun isHidden(user: User) = user.pubkeyHex in hiddenUsers || user.pubkeyHex in transientHiddenUsers
|
fun isHidden(user: User) = user.pubkeyHex in hiddenUsers || user.pubkeyHex in transientHiddenUsers
|
||||||
|
|
||||||
|
fun followingKeySet(): Set<HexKey> {
|
||||||
|
return userProfile().latestContactList?.verifiedFollowKeySet ?: emptySet()
|
||||||
|
}
|
||||||
|
|
||||||
fun isAcceptable(user: User): Boolean {
|
fun isAcceptable(user: User): Boolean {
|
||||||
return !isHidden(user) // if user hasn't hided this author
|
return !isHidden(user) // if user hasn't hided this author
|
||||||
&& user.reportsBy( userProfile() ).isEmpty() // if user has not reported this post
|
&& user.reportsBy( userProfile() ).isEmpty() // if user has not reported this post
|
||||||
&& user.countReportAuthorsBy( userProfile().follows ) < 5
|
&& user.countReportAuthorsBy( followingKeySet() ) < 5
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isAcceptableDirect(note: Note): Boolean {
|
fun isAcceptableDirect(note: Note): Boolean {
|
||||||
return note.reportsBy( userProfile() ).isEmpty() // if user has not reported this post
|
return note.reportsBy( userProfile() ).isEmpty() // if user has not reported this post
|
||||||
&& note.countReportAuthorsBy( userProfile().follows ) < 5 // if it has 5 reports by reliable users
|
&& note.countReportAuthorsBy( followingKeySet() ) < 5 // if it has 5 reports by reliable users
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isFollowing(user: User): Boolean {
|
||||||
|
return user.pubkeyHex in followingKeySet()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isAcceptable(note: Note): Boolean {
|
fun isAcceptable(note: Note): Boolean {
|
||||||
@@ -510,7 +517,7 @@ class Account(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getRelevantReports(note: Note): Set<Note> {
|
fun getRelevantReports(note: Note): Set<Note> {
|
||||||
val followsPlusMe = userProfile().follows + userProfile()
|
val followsPlusMe = userProfile().latestContactList?.verifiedFollowKeySetAndMe ?: emptySet()
|
||||||
|
|
||||||
val innerReports = if (note.event is RepostEvent) {
|
val innerReports = if (note.event is RepostEvent) {
|
||||||
note.replyTo?.map { getRelevantReports(it) }?.flatten() ?: emptyList()
|
note.replyTo?.map { getRelevantReports(it) }?.flatten() ?: emptyList()
|
||||||
@@ -518,9 +525,10 @@ class Account(
|
|||||||
emptyList()
|
emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
return (note.reportsBy(followsPlusMe) +
|
return (
|
||||||
(note.author?.reportsBy(followsPlusMe) ?: emptyList()) +
|
note.reportsBy(followsPlusMe) +
|
||||||
innerReports).toSet()
|
(note.author?.reportsBy(followsPlusMe) ?: emptyList()
|
||||||
|
) + innerReports).toSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveRelayList(value: List<RelaySetupInfo>) {
|
fun saveRelayList(value: List<RelaySetupInfo>) {
|
||||||
@@ -556,7 +564,7 @@ class Account(
|
|||||||
it.cache.spamMessages.snapshot().values.forEach {
|
it.cache.spamMessages.snapshot().values.forEach {
|
||||||
if (it.pubkeyHex !in transientHiddenUsers && it.duplicatedMessages.size >= 5) {
|
if (it.pubkeyHex !in transientHiddenUsers && it.duplicatedMessages.size >= 5) {
|
||||||
val userToBlock = LocalCache.getOrCreateUser(it.pubkeyHex)
|
val userToBlock = LocalCache.getOrCreateUser(it.pubkeyHex)
|
||||||
if (userToBlock != userProfile() && userToBlock !in userProfile().follows) {
|
if (userToBlock != userProfile() && userToBlock.pubkeyHex !in followingKeySet()) {
|
||||||
transientHiddenUsers = transientHiddenUsers + it.pubkeyHex
|
transientHiddenUsers = transientHiddenUsers + it.pubkeyHex
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,7 +4,6 @@ import android.util.Log
|
|||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||||
import com.google.gson.reflect.TypeToken
|
|
||||||
import com.vitorpamplona.amethyst.service.model.ATag
|
import com.vitorpamplona.amethyst.service.model.ATag
|
||||||
import com.vitorpamplona.amethyst.service.model.BadgeAwardEvent
|
import com.vitorpamplona.amethyst.service.model.BadgeAwardEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.BadgeDefinitionEvent
|
import com.vitorpamplona.amethyst.service.model.BadgeDefinitionEvent
|
||||||
@@ -190,25 +189,16 @@ object LocalCache {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val mentions = event.mentions().mapNotNull { checkGetOrCreateUser(it) }
|
val replyTo = event.replyToWithoutCitations().mapNotNull { checkGetOrCreateNote(it) } +
|
||||||
val replyTo = replyToWithoutCitations(event).mapNotNull { checkGetOrCreateNote(it) } +
|
|
||||||
event.taggedAddresses().mapNotNull { getOrCreateAddressableNote(it) }
|
event.taggedAddresses().mapNotNull { getOrCreateAddressableNote(it) }
|
||||||
|
|
||||||
note.loadEvent(event, author, mentions, replyTo)
|
note.loadEvent(event, author, replyTo)
|
||||||
|
|
||||||
//Log.d("TN", "New Note (${notes.size},${users.size}) ${note.author?.toBestDisplayName()} ${note.event?.content()?.take(100)} ${formattedDateTime(event.createdAt)}")
|
//Log.d("TN", "New Note (${notes.size},${users.size}) ${note.author?.toBestDisplayName()} ${note.event?.content()?.take(100)} ${formattedDateTime(event.createdAt)}")
|
||||||
|
|
||||||
// Prepares user's profile view.
|
// Prepares user's profile view.
|
||||||
author.addNote(note)
|
author.addNote(note)
|
||||||
|
|
||||||
// Adds notifications to users.
|
|
||||||
mentions.forEach {
|
|
||||||
it.addTaggedPost(note)
|
|
||||||
}
|
|
||||||
replyTo.forEach {
|
|
||||||
it.author?.addTaggedPost(note)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Counts the replies
|
// Counts the replies
|
||||||
replyTo.forEach {
|
replyTo.forEach {
|
||||||
it.addReply(note)
|
it.addReply(note)
|
||||||
@@ -236,22 +226,13 @@ object LocalCache {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val mentions = event.mentions().mapNotNull { checkGetOrCreateUser(it) }
|
val replyTo = event.replyToWithoutCitations().mapNotNull { checkGetOrCreateNote(it) }
|
||||||
val replyTo = replyToWithoutCitations(event).mapNotNull { checkGetOrCreateNote(it) }
|
|
||||||
|
|
||||||
if (event.createdAt > (note.createdAt() ?: 0)) {
|
if (event.createdAt > (note.createdAt() ?: 0)) {
|
||||||
note.loadEvent(event, author, mentions, replyTo)
|
note.loadEvent(event, author, replyTo)
|
||||||
|
|
||||||
author.addNote(note)
|
author.addNote(note)
|
||||||
|
|
||||||
// Adds notifications to users.
|
|
||||||
mentions.forEach {
|
|
||||||
it.addTaggedPost(note)
|
|
||||||
}
|
|
||||||
replyTo.forEach {
|
|
||||||
it.author?.addTaggedPost(note)
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshObservers()
|
refreshObservers()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -264,7 +245,7 @@ object LocalCache {
|
|||||||
if (note.event?.id() == event.id()) return
|
if (note.event?.id() == event.id()) return
|
||||||
|
|
||||||
if (event.createdAt > (note.createdAt() ?: 0)) {
|
if (event.createdAt > (note.createdAt() ?: 0)) {
|
||||||
note.loadEvent(event, author, emptyList<User>(), emptyList<Note>())
|
note.loadEvent(event, author, emptyList<Note>())
|
||||||
|
|
||||||
refreshObservers()
|
refreshObservers()
|
||||||
}
|
}
|
||||||
@@ -281,7 +262,7 @@ object LocalCache {
|
|||||||
event.badgeAwardDefinitions().mapNotNull { getOrCreateAddressableNote(it) }
|
event.badgeAwardDefinitions().mapNotNull { getOrCreateAddressableNote(it) }
|
||||||
|
|
||||||
if (event.createdAt > (note.createdAt() ?: 0)) {
|
if (event.createdAt > (note.createdAt() ?: 0)) {
|
||||||
note.loadEvent(event, author, emptyList(), replyTo)
|
note.loadEvent(event, author, replyTo)
|
||||||
|
|
||||||
author.updateAcceptedBadges(note)
|
author.updateAcceptedBadges(note)
|
||||||
|
|
||||||
@@ -301,12 +282,7 @@ object LocalCache {
|
|||||||
val awardees = event.awardees().mapNotNull { checkGetOrCreateUser(it) }
|
val awardees = event.awardees().mapNotNull { checkGetOrCreateUser(it) }
|
||||||
val awardDefinition = event.awardDefinition().map { getOrCreateAddressableNote(it) }
|
val awardDefinition = event.awardDefinition().map { getOrCreateAddressableNote(it) }
|
||||||
|
|
||||||
note.loadEvent(event, author, awardees, awardDefinition)
|
note.loadEvent(event, author, awardDefinition)
|
||||||
|
|
||||||
// Adds notifications to users.
|
|
||||||
awardees.forEach {
|
|
||||||
it.addTaggedPost(note)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Counts the replies
|
// Counts the replies
|
||||||
awardees.forEach {
|
awardees.forEach {
|
||||||
@@ -321,90 +297,17 @@ object LocalCache {
|
|||||||
refreshObservers()
|
refreshObservers()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findCitations(event: Event): Set<String> {
|
|
||||||
var citations = mutableSetOf<String>()
|
|
||||||
// Removes citations from replies:
|
|
||||||
val matcher = tagSearch.matcher(event.content)
|
|
||||||
while (matcher.find()) {
|
|
||||||
try {
|
|
||||||
val tag = matcher.group(1)?.let { event.tags[it.toInt()] }
|
|
||||||
if (tag != null && tag[0] == "e") {
|
|
||||||
citations.add(tag[1])
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return citations
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun replyToWithoutCitations(event: TextNoteEvent): List<String> {
|
|
||||||
val repliesTo = event.replyTos()
|
|
||||||
if (repliesTo.isEmpty()) return repliesTo
|
|
||||||
|
|
||||||
val citations = findCitations(event)
|
|
||||||
|
|
||||||
return if (citations.isEmpty()) {
|
|
||||||
repliesTo
|
|
||||||
} else {
|
|
||||||
repliesTo.filter { it !in citations }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun replyToWithoutCitations(event: LongTextNoteEvent): List<String> {
|
|
||||||
val repliesTo = event.replyTos()
|
|
||||||
if (repliesTo.isEmpty()) return repliesTo
|
|
||||||
|
|
||||||
val citations = findCitations(event)
|
|
||||||
|
|
||||||
return if (citations.isEmpty()) {
|
|
||||||
repliesTo
|
|
||||||
} else {
|
|
||||||
repliesTo.filter { it !in citations }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun consume(event: RecommendRelayEvent) {
|
fun consume(event: RecommendRelayEvent) {
|
||||||
//Log.d("RR", event.toJson())
|
//Log.d("RR", event.toJson())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun consume(event: ContactListEvent) {
|
fun consume(event: ContactListEvent) {
|
||||||
val user = getOrCreateUser(event.pubKey)
|
val user = getOrCreateUser(event.pubKey)
|
||||||
val follows = event.follows()
|
val follows = event.unverifiedFollowKeySet()
|
||||||
|
|
||||||
if (event.createdAt > user.updatedFollowsAt && !follows.isNullOrEmpty()) {
|
if (event.createdAt > (user.latestContactList?.createdAt ?: 0) && !follows.isNullOrEmpty()) {
|
||||||
// Saves relay list only if it's a user that is currently been seen
|
// Saves relay list only if it's a user that is currently been seen
|
||||||
user.latestContactList = event
|
user.updateContactList(event)
|
||||||
|
|
||||||
user.updateFollows(
|
|
||||||
follows.map {
|
|
||||||
try {
|
|
||||||
val pubKey = decodePublicKey(it.pubKeyHex)
|
|
||||||
getOrCreateUser(pubKey.toHexKey())
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w("ContactList Parser", "Ignoring: Could not parse Hex key: ${it.pubKeyHex} in ${event.toJson()}")
|
|
||||||
//e.printStackTrace()
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}.filterNotNull().toSet(),
|
|
||||||
event.createdAt
|
|
||||||
)
|
|
||||||
|
|
||||||
// Saves relay list only if it's a user that is currently been seen
|
|
||||||
try {
|
|
||||||
if (event.content.isNotEmpty()) {
|
|
||||||
val relays: Map<String, ContactListEvent.ReadWrite> =
|
|
||||||
Event.gson.fromJson(
|
|
||||||
event.content,
|
|
||||||
object : TypeToken<Map<String, ContactListEvent.ReadWrite>>() {}.type
|
|
||||||
)
|
|
||||||
|
|
||||||
user.updateRelays(relays)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w("Relay List Parser","Relay import issue ${e.message}", e)
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.d("CL", "AAA ${user.toBestDisplayName()} ${follows.size}")
|
Log.d("CL", "AAA ${user.toBestDisplayName()} ${follows.size}")
|
||||||
}
|
}
|
||||||
@@ -427,9 +330,8 @@ 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) }.mapNotNull { checkGetOrCreateNote(it) }
|
val repliesTo = event.tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) }.mapNotNull { checkGetOrCreateNote(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, repliesTo)
|
||||||
|
|
||||||
if (recipient != null) {
|
if (recipient != null) {
|
||||||
author.addMessage(recipient, note)
|
author.addMessage(recipient, note)
|
||||||
@@ -448,13 +350,10 @@ object LocalCache {
|
|||||||
deleteNote.author?.removeNote(deleteNote)
|
deleteNote.author?.removeNote(deleteNote)
|
||||||
|
|
||||||
// reverts the add
|
// reverts the add
|
||||||
deleteNote.mentions?.forEach { user ->
|
val mentions = deleteNote.event?.tags()?.filter { it.firstOrNull() == "p" }?.mapNotNull { it.getOrNull(1) }?.mapNotNull { checkGetOrCreateUser(it) }
|
||||||
user.removeTaggedPost(deleteNote)
|
|
||||||
user.removeReport(deleteNote)
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteNote.replyTo?.forEach { replyingNote ->
|
mentions?.forEach { user ->
|
||||||
replyingNote.author?.removeTaggedPost(deleteNote)
|
user.removeReport(deleteNote)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Counts the replies
|
// Counts the replies
|
||||||
@@ -486,23 +385,14 @@ 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)
|
val author = getOrCreateUser(event.pubKey)
|
||||||
val mentions = event.originalAuthor().mapNotNull { checkGetOrCreateUser(it) }
|
|
||||||
val repliesTo = event.boostedPost().mapNotNull { checkGetOrCreateNote(it) } +
|
val repliesTo = event.boostedPost().mapNotNull { checkGetOrCreateNote(it) } +
|
||||||
event.taggedAddresses().mapNotNull { getOrCreateAddressableNote(it) }
|
event.taggedAddresses().mapNotNull { getOrCreateAddressableNote(it) }
|
||||||
|
|
||||||
note.loadEvent(event, author, mentions, repliesTo)
|
note.loadEvent(event, author, repliesTo)
|
||||||
|
|
||||||
// Prepares user's profile view.
|
// Prepares user's profile view.
|
||||||
author.addNote(note)
|
author.addNote(note)
|
||||||
|
|
||||||
// Adds notifications to users.
|
|
||||||
mentions.forEach {
|
|
||||||
it.addTaggedPost(note)
|
|
||||||
}
|
|
||||||
repliesTo.forEach {
|
|
||||||
it.author?.addTaggedPost(note)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Counts the replies
|
// Counts the replies
|
||||||
repliesTo.forEach {
|
repliesTo.forEach {
|
||||||
it.addBoost(note)
|
it.addBoost(note)
|
||||||
@@ -522,18 +412,10 @@ object LocalCache {
|
|||||||
val repliesTo = event.originalPost().mapNotNull { checkGetOrCreateNote(it) } +
|
val repliesTo = event.originalPost().mapNotNull { checkGetOrCreateNote(it) } +
|
||||||
event.taggedAddresses().mapNotNull { getOrCreateAddressableNote(it) }
|
event.taggedAddresses().mapNotNull { getOrCreateAddressableNote(it) }
|
||||||
|
|
||||||
note.loadEvent(event, author, mentions, repliesTo)
|
note.loadEvent(event, author, repliesTo)
|
||||||
|
|
||||||
//Log.d("RE", "New Reaction ${event.content} (${notes.size},${users.size}) ${note.author?.toBestDisplayName()} ${formattedDateTime(event.createdAt)}")
|
//Log.d("RE", "New Reaction ${event.content} (${notes.size},${users.size}) ${note.author?.toBestDisplayName()} ${formattedDateTime(event.createdAt)}")
|
||||||
|
|
||||||
// Adds notifications to users.
|
|
||||||
mentions.forEach {
|
|
||||||
it.addTaggedPost(note)
|
|
||||||
}
|
|
||||||
repliesTo.forEach {
|
|
||||||
it.author?.addTaggedPost(note)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
event.content == "" ||
|
event.content == "" ||
|
||||||
event.content == "+" ||
|
event.content == "+" ||
|
||||||
@@ -573,7 +455,7 @@ object LocalCache {
|
|||||||
val repliesTo = event.reportedPost().mapNotNull { checkGetOrCreateNote(it.key) } +
|
val repliesTo = event.reportedPost().mapNotNull { checkGetOrCreateNote(it.key) } +
|
||||||
event.taggedAddresses().mapNotNull { getOrCreateAddressableNote(it) }
|
event.taggedAddresses().mapNotNull { getOrCreateAddressableNote(it) }
|
||||||
|
|
||||||
note.loadEvent(event, author, mentions, repliesTo)
|
note.loadEvent(event, author, repliesTo)
|
||||||
|
|
||||||
//Log.d("RP", "New Report ${event.content} by ${note.author?.toBestDisplayName()} ${formattedDateTime(event.createdAt)}")
|
//Log.d("RP", "New Report ${event.content} by ${note.author?.toBestDisplayName()} ${formattedDateTime(event.createdAt)}")
|
||||||
// Adds notifications to users.
|
// Adds notifications to users.
|
||||||
@@ -598,7 +480,7 @@ object LocalCache {
|
|||||||
|
|
||||||
val note = getOrCreateNote(event.id)
|
val note = getOrCreateNote(event.id)
|
||||||
oldChannel.addNote(note)
|
oldChannel.addNote(note)
|
||||||
note.loadEvent(event, author, emptyList(), emptyList())
|
note.loadEvent(event, author, emptyList())
|
||||||
|
|
||||||
refreshObservers()
|
refreshObservers()
|
||||||
}
|
}
|
||||||
@@ -621,7 +503,7 @@ object LocalCache {
|
|||||||
|
|
||||||
val note = getOrCreateNote(event.id)
|
val note = getOrCreateNote(event.id)
|
||||||
oldChannel.addNote(note)
|
oldChannel.addNote(note)
|
||||||
note.loadEvent(event, author, emptyList(), emptyList())
|
note.loadEvent(event, author, emptyList())
|
||||||
|
|
||||||
refreshObservers()
|
refreshObservers()
|
||||||
}
|
}
|
||||||
@@ -662,18 +544,10 @@ object LocalCache {
|
|||||||
.mapNotNull { checkGetOrCreateNote(it) }
|
.mapNotNull { checkGetOrCreateNote(it) }
|
||||||
.filter { it.event !is ChannelCreateEvent }
|
.filter { it.event !is ChannelCreateEvent }
|
||||||
|
|
||||||
note.loadEvent(event, author, mentions, replyTo)
|
note.loadEvent(event, author, replyTo)
|
||||||
|
|
||||||
//Log.d("CM", "New Note (${notes.size},${users.size}) ${note.author?.toBestDisplayName()} ${note.event?.content()} ${formattedDateTime(event.createdAt)}")
|
//Log.d("CM", "New Note (${notes.size},${users.size}) ${note.author?.toBestDisplayName()} ${note.event?.content()} ${formattedDateTime(event.createdAt)}")
|
||||||
|
|
||||||
// Adds notifications to users.
|
|
||||||
mentions.forEach {
|
|
||||||
it.addTaggedPost(note)
|
|
||||||
}
|
|
||||||
replyTo.forEach {
|
|
||||||
it.author?.addTaggedPost(note)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Counts the replies
|
// Counts the replies
|
||||||
replyTo.forEach {
|
replyTo.forEach {
|
||||||
it.addReply(note)
|
it.addReply(note)
|
||||||
@@ -704,7 +578,7 @@ object LocalCache {
|
|||||||
event.taggedAddresses().map { getOrCreateAddressableNote(it) } +
|
event.taggedAddresses().map { getOrCreateAddressableNote(it) } +
|
||||||
((zapRequest?.event as? LnZapRequestEvent)?.taggedAddresses()?.map { getOrCreateAddressableNote(it) } ?: emptySet<Note>())
|
((zapRequest?.event as? LnZapRequestEvent)?.taggedAddresses()?.map { getOrCreateAddressableNote(it) } ?: emptySet<Note>())
|
||||||
|
|
||||||
note.loadEvent(event, author, mentions, repliesTo)
|
note.loadEvent(event, author, repliesTo)
|
||||||
|
|
||||||
if (zapRequest == null) {
|
if (zapRequest == null) {
|
||||||
Log.e("ZP","Zap Request not found. Unable to process Zap {${event.toJson()}}")
|
Log.e("ZP","Zap Request not found. Unable to process Zap {${event.toJson()}}")
|
||||||
@@ -713,14 +587,6 @@ object LocalCache {
|
|||||||
|
|
||||||
//Log.d("ZP", "New ZapEvent ${event.content} (${notes.size},${users.size}) ${note.author?.toBestDisplayName()} ${formattedDateTime(event.createdAt)}")
|
//Log.d("ZP", "New ZapEvent ${event.content} (${notes.size},${users.size}) ${note.author?.toBestDisplayName()} ${formattedDateTime(event.createdAt)}")
|
||||||
|
|
||||||
// Adds notifications to users.
|
|
||||||
mentions.forEach {
|
|
||||||
it.addTaggedPost(note)
|
|
||||||
}
|
|
||||||
repliesTo.forEach {
|
|
||||||
it.author?.addTaggedPost(note)
|
|
||||||
}
|
|
||||||
|
|
||||||
repliesTo.forEach {
|
repliesTo.forEach {
|
||||||
it.addZap(zapRequest, note)
|
it.addZap(zapRequest, note)
|
||||||
}
|
}
|
||||||
@@ -740,18 +606,10 @@ object LocalCache {
|
|||||||
val repliesTo = event.zappedPost().mapNotNull { checkGetOrCreateNote(it) } +
|
val repliesTo = event.zappedPost().mapNotNull { checkGetOrCreateNote(it) } +
|
||||||
event.taggedAddresses().map { getOrCreateAddressableNote(it) }
|
event.taggedAddresses().map { getOrCreateAddressableNote(it) }
|
||||||
|
|
||||||
note.loadEvent(event, author, mentions, repliesTo)
|
note.loadEvent(event, author, repliesTo)
|
||||||
|
|
||||||
//Log.d("ZP", "New Zap Request ${event.content} (${notes.size},${users.size}) ${note.author?.toBestDisplayName()} ${formattedDateTime(event.createdAt)}")
|
//Log.d("ZP", "New Zap Request ${event.content} (${notes.size},${users.size}) ${note.author?.toBestDisplayName()} ${formattedDateTime(event.createdAt)}")
|
||||||
|
|
||||||
// Adds notifications to users.
|
|
||||||
mentions.forEach {
|
|
||||||
it.addTaggedPost(note)
|
|
||||||
}
|
|
||||||
repliesTo.forEach {
|
|
||||||
it.author?.addTaggedPost(note)
|
|
||||||
}
|
|
||||||
|
|
||||||
repliesTo.forEach {
|
repliesTo.forEach {
|
||||||
it.addZap(note, null)
|
it.addZap(note, null)
|
||||||
}
|
}
|
||||||
@@ -809,12 +667,7 @@ object LocalCache {
|
|||||||
// Doesn't need to clean up the replies and mentions.. Too small to matter.
|
// Doesn't need to clean up the replies and mentions.. Too small to matter.
|
||||||
|
|
||||||
// reverts the add
|
// reverts the add
|
||||||
it.mentions?.forEach { user ->
|
val mentions = it.event?.tags()?.filter { it.firstOrNull() == "p" }?.mapNotNull { it.getOrNull(1) }?.mapNotNull { checkGetOrCreateUser(it) }
|
||||||
user.removeTaggedPost(it)
|
|
||||||
}
|
|
||||||
it.replyTo?.forEach { replyingNote ->
|
|
||||||
replyingNote.author?.removeTaggedPost(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Counts the replies
|
// Counts the replies
|
||||||
it.replyTo?.forEach { replyingNote ->
|
it.replyTo?.forEach { replyingNote ->
|
||||||
@@ -826,40 +679,6 @@ object LocalCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun pruneNonFollows(account: Account) {
|
|
||||||
val follows = account.userProfile().follows
|
|
||||||
val knownPMs = account.userProfile().privateChatrooms.filter {
|
|
||||||
account.userProfile().hasSentMessagesTo(it.key) && account.isAcceptable(it.key)
|
|
||||||
}
|
|
||||||
|
|
||||||
val followsFollow = follows.map {
|
|
||||||
it.follows
|
|
||||||
}.flatten()
|
|
||||||
|
|
||||||
val followSet = follows.plus(knownPMs).plus(account.userProfile()).plus(followsFollow)
|
|
||||||
|
|
||||||
val toBeRemoved = notes
|
|
||||||
.filter {
|
|
||||||
(it.value.author == null || it.value.author!! !in followSet) && it.value.event?.kind() == TextNoteEvent.kind && it.value.liveSet?.isInUse() != true
|
|
||||||
}
|
|
||||||
|
|
||||||
toBeRemoved.forEach {
|
|
||||||
notes.remove(it.key)
|
|
||||||
}
|
|
||||||
|
|
||||||
val toBeRemovedUsers = users
|
|
||||||
.filter {
|
|
||||||
(it.value !in followSet) && it.value.liveSet?.isInUse() != true
|
|
||||||
}
|
|
||||||
|
|
||||||
toBeRemovedUsers.forEach {
|
|
||||||
users.remove(it.key)
|
|
||||||
}
|
|
||||||
|
|
||||||
println("PRUNE: ${toBeRemoved.size} messages removed because they came from NonFollows")
|
|
||||||
println("PRUNE: ${toBeRemovedUsers.size} users removed because are NonFollows")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun pruneHiddenMessages(account: Account) {
|
fun pruneHiddenMessages(account: Account) {
|
||||||
val toBeRemoved = account.hiddenUsers.map {
|
val toBeRemoved = account.hiddenUsers.map {
|
||||||
(users[it]?.notes ?: emptySet())
|
(users[it]?.notes ?: emptySet())
|
||||||
@@ -872,14 +691,6 @@ object LocalCache {
|
|||||||
toBeRemoved.forEach {
|
toBeRemoved.forEach {
|
||||||
it.author?.removeNote(it)
|
it.author?.removeNote(it)
|
||||||
|
|
||||||
// reverts the add
|
|
||||||
it.mentions?.forEach { user ->
|
|
||||||
user.removeTaggedPost(it)
|
|
||||||
}
|
|
||||||
it.replyTo?.forEach { replyingNote ->
|
|
||||||
replyingNote.author?.removeTaggedPost(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Counts the replies
|
// Counts the replies
|
||||||
it.replyTo?.forEach { masterNote ->
|
it.replyTo?.forEach { masterNote ->
|
||||||
masterNote.removeReply(it)
|
masterNote.removeReply(it)
|
||||||
|
@@ -36,7 +36,6 @@ open class Note(val idHex: String) {
|
|||||||
// They are immutable after that.
|
// They are immutable after that.
|
||||||
var event: EventInterface? = null
|
var event: EventInterface? = null
|
||||||
var author: User? = null
|
var author: User? = null
|
||||||
var mentions: List<User>? = null
|
|
||||||
var replyTo: List<Note>? = null
|
var replyTo: List<Note>? = null
|
||||||
|
|
||||||
// These fields are updated every time an event related to this note is received.
|
// These fields are updated every time an event related to this note is received.
|
||||||
@@ -73,10 +72,9 @@ open class Note(val idHex: String) {
|
|||||||
|
|
||||||
open fun createdAt() = event?.createdAt()
|
open fun createdAt() = event?.createdAt()
|
||||||
|
|
||||||
fun loadEvent(event: Event, author: User, mentions: List<User>, replyTo: List<Note>) {
|
fun loadEvent(event: Event, author: User, replyTo: List<Note>) {
|
||||||
this.event = event
|
this.event = event
|
||||||
this.author = author
|
this.author = author
|
||||||
this.mentions = mentions
|
|
||||||
this.replyTo = replyTo
|
this.replyTo = replyTo
|
||||||
|
|
||||||
liveSet?.metadata?.invalidateData()
|
liveSet?.metadata?.invalidateData()
|
||||||
@@ -217,15 +215,15 @@ open class Note(val idHex: String) {
|
|||||||
return reports[user] ?: emptySet()
|
return reports[user] ?: emptySet()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun reportAuthorsBy(users: Set<User>): List<User> {
|
fun reportAuthorsBy(users: Set<HexKey>): List<User> {
|
||||||
return reports.keys.filter { it in users }
|
return reports.keys.filter { it.pubkeyHex in users }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun countReportAuthorsBy(users: Set<User>): Int {
|
fun countReportAuthorsBy(users: Set<HexKey>): Int {
|
||||||
return reports.keys.count { it in users }
|
return reports.keys.count { it.pubkeyHex in users }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun reportsBy(users: Set<User>): List<Note> {
|
fun reportsBy(users: Set<HexKey>): List<Note> {
|
||||||
return reportAuthorsBy(users).mapNotNull {
|
return reportAuthorsBy(users).mapNotNull {
|
||||||
reports[it]
|
reports[it]
|
||||||
}.flatten()
|
}.flatten()
|
||||||
|
@@ -24,25 +24,14 @@ import nostr.postr.toNpub
|
|||||||
|
|
||||||
val lnurlpPattern = Pattern.compile("(?i:http|https):\\/\\/((.+)\\/)*\\.well-known\\/lnurlp\\/(.*)")
|
val lnurlpPattern = Pattern.compile("(?i:http|https):\\/\\/((.+)\\/)*\\.well-known\\/lnurlp\\/(.*)")
|
||||||
|
|
||||||
class Badges(val definition: Note, val awardees: Set<Note>)
|
|
||||||
|
|
||||||
class User(val pubkeyHex: String) {
|
class User(val pubkeyHex: String) {
|
||||||
var info: UserMetadata? = null
|
var info: UserMetadata? = null
|
||||||
|
|
||||||
var updatedFollowsAt: Long = 0;
|
|
||||||
var latestContactList: ContactListEvent? = null
|
var latestContactList: ContactListEvent? = null
|
||||||
|
|
||||||
var follows = setOf<User>()
|
|
||||||
private set
|
|
||||||
var followers = setOf<User>()
|
|
||||||
private set
|
|
||||||
|
|
||||||
var notes = setOf<Note>()
|
var notes = setOf<Note>()
|
||||||
private set
|
private set
|
||||||
|
|
||||||
var taggedPosts = setOf<Note>()
|
|
||||||
private set
|
|
||||||
|
|
||||||
var reports = mapOf<User, Set<Note>>()
|
var reports = mapOf<User, Set<Note>>()
|
||||||
private set
|
private set
|
||||||
|
|
||||||
@@ -51,9 +40,6 @@ class User(val pubkeyHex: String) {
|
|||||||
var zaps = mapOf<Note, Note?>()
|
var zaps = mapOf<Note, Note?>()
|
||||||
private set
|
private set
|
||||||
|
|
||||||
var relays: Map<String, ContactListEvent.ReadWrite>? = null
|
|
||||||
private set
|
|
||||||
|
|
||||||
var relaysBeingUsed = mapOf<String, RelayInfo>()
|
var relaysBeingUsed = mapOf<String, RelayInfo>()
|
||||||
private set
|
private set
|
||||||
|
|
||||||
@@ -92,54 +78,25 @@ class User(val pubkeyHex: String) {
|
|||||||
return info?.picture
|
return info?.picture
|
||||||
}
|
}
|
||||||
|
|
||||||
fun follow(user: User, followedAt: Long) {
|
fun updateContactList(event: ContactListEvent) {
|
||||||
follows = follows + user
|
if (event.id == latestContactList?.id) return
|
||||||
user.followers = user.followers + this
|
|
||||||
|
|
||||||
|
val oldContactListEvent = latestContactList
|
||||||
|
latestContactList = event
|
||||||
|
|
||||||
|
// Update following of the current user
|
||||||
liveSet?.follows?.invalidateData()
|
liveSet?.follows?.invalidateData()
|
||||||
user.liveSet?.follows?.invalidateData()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun unfollow(user: User) {
|
// Update Followers of the past user list
|
||||||
follows = follows - user
|
// Update Followers of the new contact list
|
||||||
user.followers = user.followers - this
|
(oldContactListEvent)?.unverifiedFollowKeySet()?.forEach {
|
||||||
|
LocalCache.users[it]?.liveSet?.follows?.invalidateData()
|
||||||
liveSet?.follows?.invalidateData()
|
}
|
||||||
user.liveSet?.follows?.invalidateData()
|
(latestContactList)?.unverifiedFollowKeySet()?.forEach {
|
||||||
}
|
LocalCache.users[it]?.liveSet?.follows?.invalidateData()
|
||||||
|
|
||||||
fun follow(users: Set<User>, followedAt: Long) {
|
|
||||||
follows = follows + users
|
|
||||||
users.forEach {
|
|
||||||
if (this !in it.followers && it.liveSet?.isInUse() == true) {
|
|
||||||
it.followers = it.followers + this
|
|
||||||
it.liveSet?.follows?.invalidateData()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
liveSet?.follows?.invalidateData()
|
liveSet?.relays?.invalidateData()
|
||||||
}
|
|
||||||
|
|
||||||
fun unfollow(users: Set<User>) {
|
|
||||||
follows = follows - users
|
|
||||||
users.forEach {
|
|
||||||
if (this in it.followers && it.liveSet?.isInUse() == true) {
|
|
||||||
it.followers = it.followers - this
|
|
||||||
it.liveSet?.follows?.invalidateData()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
liveSet?.follows?.invalidateData()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addTaggedPost(note: Note) {
|
|
||||||
if (note !in taggedPosts) {
|
|
||||||
taggedPosts = taggedPosts + note
|
|
||||||
// No need for Listener yet
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun removeTaggedPost(note: Note) {
|
|
||||||
taggedPosts = taggedPosts - note
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addNote(note: Note) {
|
fun addNote(note: Note) {
|
||||||
@@ -224,15 +181,15 @@ class User(val pubkeyHex: String) {
|
|||||||
return reports[user] ?: emptySet()
|
return reports[user] ?: emptySet()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun reportAuthorsBy(users: Set<User>): List<User> {
|
fun reportAuthorsBy(users: Set<HexKey>): List<User> {
|
||||||
return reports.keys.filter { it in users }
|
return reports.keys.filter { it.pubkeyHex in users }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun countReportAuthorsBy(users: Set<User>): Int {
|
fun countReportAuthorsBy(users: Set<HexKey>): Int {
|
||||||
return reports.keys.count { it in users }
|
return reports.keys.count { it.pubkeyHex in users }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun reportsBy(users: Set<User>): List<Note> {
|
fun reportsBy(users: Set<HexKey>): List<Note> {
|
||||||
return reportAuthorsBy(users).mapNotNull {
|
return reportAuthorsBy(users).mapNotNull {
|
||||||
reports[it]
|
reports[it]
|
||||||
}.flatten()
|
}.flatten()
|
||||||
@@ -268,22 +225,6 @@ class User(val pubkeyHex: String) {
|
|||||||
|
|
||||||
liveSet?.relayInfo?.invalidateData()
|
liveSet?.relayInfo?.invalidateData()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateFollows(newFollows: Set<User>, updateAt: Long) {
|
|
||||||
val toBeAdded = newFollows - follows
|
|
||||||
val toBeRemoved = follows - newFollows
|
|
||||||
|
|
||||||
follow(toBeAdded, updateAt)
|
|
||||||
unfollow(toBeRemoved)
|
|
||||||
|
|
||||||
updatedFollowsAt = updateAt
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateRelays(relayUse: Map<String, ContactListEvent.ReadWrite>) {
|
|
||||||
// no need to test if relays are different. The Account will check for us.
|
|
||||||
relays = relayUse
|
|
||||||
liveSet?.relays?.invalidateData()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateUserInfo(newUserInfo: UserMetadata, latestMetadata: MetadataEvent) {
|
fun updateUserInfo(newUserInfo: UserMetadata, latestMetadata: MetadataEvent) {
|
||||||
info = newUserInfo
|
info = newUserInfo
|
||||||
@@ -310,7 +251,29 @@ class User(val pubkeyHex: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun isFollowing(user: User): Boolean {
|
fun isFollowing(user: User): Boolean {
|
||||||
return follows.contains(user)
|
return (latestContactList)?.unverifiedFollowKeySet()?.toSet()?.let {
|
||||||
|
return user.pubkeyHex in it
|
||||||
|
} ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun transientFollowCount(): Int? {
|
||||||
|
return latestContactList?.unverifiedFollowKeySet()?.size
|
||||||
|
}
|
||||||
|
|
||||||
|
fun transientFollowerCount(): Int {
|
||||||
|
return LocalCache.users.values.count { it.latestContactList?.let { pubkeyHex in it.unverifiedFollowKeySet() } ?: false }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cachedFollowingKeySet(): Set<HexKey> {
|
||||||
|
return latestContactList?.verifiedFollowKeySet ?: emptySet()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cachedFollowCount(): Int? {
|
||||||
|
return latestContactList?.verifiedFollowKeySet?.size
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cachedFollowerCount(): Int {
|
||||||
|
return LocalCache.users.values.count { it.latestContactList?.let { pubkeyHex in it.unverifiedFollowKeySet() } ?: false }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hasSentMessagesTo(user: User?): Boolean {
|
fun hasSentMessagesTo(user: User?): Boolean {
|
||||||
|
@@ -37,10 +37,10 @@ object NostrHomeDataSource: NostrDataSource("HomeFeed") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun createFollowAccountsFilter(): TypedFilter {
|
fun createFollowAccountsFilter(): TypedFilter {
|
||||||
val follows = account.userProfile().follows
|
val follows = account.followingKeySet()
|
||||||
|
|
||||||
val followKeys = follows.map {
|
val followKeys = follows.map {
|
||||||
it.pubkeyHex.substring(0, 6)
|
it.substring(0, 6)
|
||||||
}
|
}
|
||||||
|
|
||||||
val followSet = followKeys.plus(account.userProfile().pubkeyHex.substring(0, 6))
|
val followSet = followKeys.plus(account.userProfile().pubkeyHex.substring(0, 6))
|
||||||
|
@@ -0,0 +1,48 @@
|
|||||||
|
package com.vitorpamplona.amethyst.service.model
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.tagSearch
|
||||||
|
|
||||||
|
open class BaseTextNoteEvent(
|
||||||
|
id: HexKey,
|
||||||
|
pubKey: HexKey,
|
||||||
|
createdAt: Long,
|
||||||
|
kind: Int,
|
||||||
|
tags: List<List<String>>,
|
||||||
|
content: String,
|
||||||
|
sig: HexKey
|
||||||
|
): Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||||
|
fun mentions() = taggedUsers()
|
||||||
|
fun replyTos() = tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) }
|
||||||
|
|
||||||
|
fun findCitations(): Set<String> {
|
||||||
|
var citations = mutableSetOf<String>()
|
||||||
|
// Removes citations from replies:
|
||||||
|
val matcher = tagSearch.matcher(content)
|
||||||
|
while (matcher.find()) {
|
||||||
|
try {
|
||||||
|
val tag = matcher.group(1)?.let { tags[it.toInt()] }
|
||||||
|
if (tag != null && tag[0] == "e") {
|
||||||
|
citations.add(tag[1])
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return citations
|
||||||
|
}
|
||||||
|
|
||||||
|
fun replyToWithoutCitations(): List<String> {
|
||||||
|
val repliesTo = replyTos()
|
||||||
|
if (repliesTo.isEmpty()) return repliesTo
|
||||||
|
|
||||||
|
val citations = findCitations()
|
||||||
|
|
||||||
|
return if (citations.isEmpty()) {
|
||||||
|
repliesTo
|
||||||
|
} else {
|
||||||
|
repliesTo.filter { it !in citations }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -14,7 +14,7 @@ class ChannelCreateEvent (
|
|||||||
content: String,
|
content: String,
|
||||||
sig: HexKey
|
sig: HexKey
|
||||||
): Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
): Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||||
fun channelInfo() = try {
|
fun channelInfo(): ChannelData = try {
|
||||||
MetadataEvent.gson.fromJson(content, ChannelData::class.java)
|
MetadataEvent.gson.fromJson(content, ChannelData::class.java)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("ChannelMetadataEvent", "Can't parse channel info $content", e)
|
Log.e("ChannelMetadataEvent", "Can't parse channel info $content", e)
|
||||||
|
@@ -3,6 +3,7 @@ package com.vitorpamplona.amethyst.service.model
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
import com.vitorpamplona.amethyst.model.HexKey
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.decodePublicKey
|
||||||
import com.vitorpamplona.amethyst.model.toHexKey
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
@@ -17,20 +18,38 @@ class ContactListEvent(
|
|||||||
content: String,
|
content: String,
|
||||||
sig: HexKey
|
sig: HexKey
|
||||||
): Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
): Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||||
fun follows() = try {
|
// This function is only used by the user logged in
|
||||||
tags.filter { it[0] == "p" }.map { Contact(it[1], it.getOrNull(2)) }
|
// But it is used all the time.
|
||||||
} catch (e: Exception) {
|
val verifiedFollowKeySet: Set<HexKey> by lazy {
|
||||||
Log.e("ContactListEvent", "can't parse tags as follows: $tags", e)
|
tags.filter { it[0] == "p" }.mapNotNull {
|
||||||
null
|
it.getOrNull(1)?.let { unverifiedHex: String ->
|
||||||
|
decodePublicKey(unverifiedHex).toHexKey()
|
||||||
|
}
|
||||||
|
}.toSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun relayUse() = try {
|
val verifiedFollowKeySetAndMe: Set<HexKey> by lazy {
|
||||||
|
verifiedFollowKeySet + pubKey
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unverifiedFollowKeySet() = tags.filter { it[0] == "p" }.mapNotNull { it.getOrNull(1) }
|
||||||
|
|
||||||
|
fun follows() = tags.filter { it[0] == "p" }.mapNotNull {
|
||||||
|
try {
|
||||||
|
Contact(decodePublicKey(it[1]).toHexKey(), it.getOrNull(2))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w("ContactListEvent", "Can't parse tags as a follows: ${it[1]}", e)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun relays(): Map<String, ReadWrite>? = try {
|
||||||
if (content.isNotEmpty())
|
if (content.isNotEmpty())
|
||||||
gson.fromJson(content, object: TypeToken<Map<String, ReadWrite>>() {}.type)
|
gson.fromJson(content, object: TypeToken<Map<String, ReadWrite>>() {}.type) as Map<String, ReadWrite>
|
||||||
else
|
else
|
||||||
null
|
null
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("ContactListEvent", "can't parse content as relay lists: $tags", e)
|
Log.w("ContactListEvent", "Can't parse content as relay lists: $content", e)
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -37,6 +37,11 @@ open class Event(
|
|||||||
|
|
||||||
override fun toJson(): String = gson.toJson(this)
|
override fun toJson(): String = gson.toJson(this)
|
||||||
|
|
||||||
|
fun taggedUsers() = tags.filter { it.firstOrNull() == "p" }.mapNotNull { it.getOrNull(1) }
|
||||||
|
|
||||||
|
override fun isTaggedUser(idHex: String) = tags.any { it.getOrNull(0) == "p" && it.getOrNull(1) == idHex }
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the ID is correct and then if the pubKey's secret key signed the event.
|
* Checks if the ID is correct and then if the pubKey's secret key signed the event.
|
||||||
*/
|
*/
|
||||||
|
@@ -22,4 +22,6 @@ interface EventInterface {
|
|||||||
fun checkSignature()
|
fun checkSignature()
|
||||||
|
|
||||||
fun hasValidSignature(): Boolean
|
fun hasValidSignature(): Boolean
|
||||||
|
|
||||||
|
fun isTaggedUser(loggedInUser: String): Boolean
|
||||||
}
|
}
|
||||||
|
@@ -12,10 +12,7 @@ class LongTextNoteEvent(
|
|||||||
tags: List<List<String>>,
|
tags: List<List<String>>,
|
||||||
content: String,
|
content: String,
|
||||||
sig: HexKey
|
sig: HexKey
|
||||||
): Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
): BaseTextNoteEvent(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||||
fun replyTos() = tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) }
|
|
||||||
fun mentions() = tags.filter { it.firstOrNull() == "p" }.mapNotNull { it.getOrNull(1) }
|
|
||||||
|
|
||||||
fun dTag() = tags.filter { it.firstOrNull() == "d" }.mapNotNull { it.getOrNull(1) }.firstOrNull() ?: ""
|
fun dTag() = tags.filter { it.firstOrNull() == "d" }.mapNotNull { it.getOrNull(1) }.firstOrNull() ?: ""
|
||||||
fun address() = ATag(kind, pubKey, dTag(), null)
|
fun address() = ATag(kind, pubKey, dTag(), null)
|
||||||
|
|
||||||
|
@@ -12,8 +12,8 @@ class TextNoteEvent(
|
|||||||
tags: List<List<String>>,
|
tags: List<List<String>>,
|
||||||
content: String,
|
content: String,
|
||||||
sig: HexKey
|
sig: HexKey
|
||||||
): Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
): BaseTextNoteEvent(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||||
fun mentions() = tags.filter { it.firstOrNull() == "p" }.mapNotNull { it.getOrNull(1) }
|
|
||||||
fun taggedAddresses() = tags.filter { it.firstOrNull() == "a" }.mapNotNull {
|
fun taggedAddresses() = tags.filter { it.firstOrNull() == "a" }.mapNotNull {
|
||||||
val aTagValue = it.getOrNull(1)
|
val aTagValue = it.getOrNull(1)
|
||||||
val relay = it.getOrNull(2)
|
val relay = it.getOrNull(2)
|
||||||
@@ -21,8 +21,6 @@ class TextNoteEvent(
|
|||||||
if (aTagValue != null) ATag.parse(aTagValue, relay) else null
|
if (aTagValue != null) ATag.parse(aTagValue, relay) else null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun replyTos() = tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) }
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val kind = 1
|
const val kind = 1
|
||||||
|
|
||||||
|
@@ -10,6 +10,7 @@ import androidx.compose.ui.text.input.TextFieldValue
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.vitorpamplona.amethyst.model.*
|
import com.vitorpamplona.amethyst.model.*
|
||||||
|
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
||||||
import com.vitorpamplona.amethyst.ui.components.isValidURL
|
import com.vitorpamplona.amethyst.ui.components.isValidURL
|
||||||
import com.vitorpamplona.amethyst.ui.components.noProtocolUrlValidator
|
import com.vitorpamplona.amethyst.ui.components.noProtocolUrlValidator
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
@@ -35,7 +36,10 @@ class NewPostViewModel: ViewModel() {
|
|||||||
replyingTo?.let { replyNote ->
|
replyingTo?.let { replyNote ->
|
||||||
this.replyTos = (replyNote.replyTo ?: emptyList()).plus(replyNote)
|
this.replyTos = (replyNote.replyTo ?: emptyList()).plus(replyNote)
|
||||||
replyNote.author?.let { replyUser ->
|
replyNote.author?.let { replyUser ->
|
||||||
val currentMentions = replyNote.mentions ?: emptyList()
|
val currentMentions = (replyNote.event as? TextNoteEvent)
|
||||||
|
?.mentions()
|
||||||
|
?.map { LocalCache.getOrCreateUser(it) } ?: emptyList()
|
||||||
|
|
||||||
if (currentMentions.contains(replyUser)) {
|
if (currentMentions.contains(replyUser)) {
|
||||||
this.mentions = currentMentions
|
this.mentions = currentMentions
|
||||||
} else {
|
} else {
|
||||||
|
@@ -34,7 +34,7 @@ class NewRelayListViewModel: ViewModel() {
|
|||||||
|
|
||||||
fun clear(ctx: Context) {
|
fun clear(ctx: Context) {
|
||||||
_relays.update {
|
_relays.update {
|
||||||
var relayFile = account.userProfile().relays
|
var relayFile = account.userProfile().latestContactList?.relays()
|
||||||
|
|
||||||
// Ugly, but forces nostr.band as the only search-supporting relay today.
|
// Ugly, but forces nostr.band as the only search-supporting relay today.
|
||||||
// TODO: Remove when search becomes more available.
|
// TODO: Remove when search becomes more available.
|
||||||
|
@@ -19,7 +19,7 @@ object GlobalFeedFilter: FeedFilter<Note>() {
|
|||||||
// does not show events already in the public chat list
|
// does not show events already in the public chat list
|
||||||
(it.channel() == null || it.channel() !in account.followingChannels())
|
(it.channel() == null || it.channel() !in account.followingChannels())
|
||||||
// does not show people the user already follows
|
// does not show people the user already follows
|
||||||
&& (it.author !in account.userProfile().follows)
|
&& (it.author?.pubkeyHex !in account.followingKeySet())
|
||||||
}
|
}
|
||||||
.filter { account.isAcceptable(it) }
|
.filter { account.isAcceptable(it) }
|
||||||
.sortedBy { it.createdAt() }
|
.sortedBy { it.createdAt() }
|
||||||
|
@@ -15,7 +15,7 @@ object HomeConversationsFeedFilter: FeedFilter<Note>() {
|
|||||||
return LocalCache.notes.values
|
return LocalCache.notes.values
|
||||||
.filter {
|
.filter {
|
||||||
(it.event is TextNoteEvent || it.event is RepostEvent)
|
(it.event is TextNoteEvent || it.event is RepostEvent)
|
||||||
&& it.author in user.follows
|
&& it.author?.pubkeyHex in user.cachedFollowingKeySet()
|
||||||
// && account.isAcceptable(it) // This filter follows only. No need to check if acceptable
|
// && account.isAcceptable(it) // This filter follows only. No need to check if acceptable
|
||||||
&& it.author?.let { !HomeNewThreadFeedFilter.account.isHidden(it) } ?: true
|
&& it.author?.let { !HomeNewThreadFeedFilter.account.isHidden(it) } ?: true
|
||||||
&& !it.isNewThread()
|
&& !it.isNewThread()
|
||||||
|
@@ -16,7 +16,7 @@ object HomeNewThreadFeedFilter: FeedFilter<Note>() {
|
|||||||
val notes = LocalCache.notes.values
|
val notes = LocalCache.notes.values
|
||||||
.filter { it ->
|
.filter { it ->
|
||||||
(it.event is TextNoteEvent || it.event is RepostEvent || it.event is LongTextNoteEvent)
|
(it.event is TextNoteEvent || it.event is RepostEvent || it.event is LongTextNoteEvent)
|
||||||
&& it.author in user.follows
|
&& it.author?.pubkeyHex in user.cachedFollowingKeySet()
|
||||||
// && account.isAcceptable(it) // This filter follows only. No need to check if acceptable
|
// && account.isAcceptable(it) // This filter follows only. No need to check if acceptable
|
||||||
&& it.author?.let { !account.isHidden(it) } ?: true
|
&& it.author?.let { !account.isHidden(it) } ?: true
|
||||||
&& it.isNewThread()
|
&& it.isNewThread()
|
||||||
@@ -25,7 +25,7 @@ object HomeNewThreadFeedFilter: FeedFilter<Note>() {
|
|||||||
val longFormNotes = LocalCache.addressables.values
|
val longFormNotes = LocalCache.addressables.values
|
||||||
.filter { it ->
|
.filter { it ->
|
||||||
(it.event is TextNoteEvent || it.event is RepostEvent || it.event is LongTextNoteEvent)
|
(it.event is TextNoteEvent || it.event is RepostEvent || it.event is LongTextNoteEvent)
|
||||||
&& it.author in user.follows
|
&& it.author?.pubkeyHex in user.cachedFollowingKeySet()
|
||||||
// && account.isAcceptable(it) // This filter follows only. No need to check if acceptable
|
// && account.isAcceptable(it) // This filter follows only. No need to check if acceptable
|
||||||
&& it.author?.let { !account.isHidden(it) } ?: true
|
&& it.author?.let { !account.isHidden(it) } ?: true
|
||||||
&& it.isNewThread()
|
&& it.isNewThread()
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.dal
|
package com.vitorpamplona.amethyst.ui.dal
|
||||||
|
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.service.model.*
|
import com.vitorpamplona.amethyst.service.model.*
|
||||||
|
|
||||||
@@ -8,12 +9,11 @@ object NotificationFeedFilter: FeedFilter<Note>() {
|
|||||||
lateinit var account: Account
|
lateinit var account: Account
|
||||||
|
|
||||||
override fun feed(): List<Note> {
|
override fun feed(): List<Note> {
|
||||||
return account.userProfile()
|
val loggedInUser = account.userProfile().pubkeyHex
|
||||||
.taggedPosts
|
return LocalCache.notes.values
|
||||||
.asSequence()
|
.filter { it.event?.isTaggedUser(loggedInUser) ?: false }
|
||||||
.filter {
|
.filter {
|
||||||
it.author == null
|
it.author == null || (!account.isHidden(it.author!!) && it.author != account.userProfile())
|
||||||
|| (!account.isHidden(it.author!!) && it.author != account.userProfile())
|
|
||||||
}
|
}
|
||||||
.filter {
|
.filter {
|
||||||
it.event !is ChannelCreateEvent
|
it.event !is ChannelCreateEvent
|
||||||
|
@@ -14,7 +14,8 @@ object UserProfileFollowersFeedFilter: FeedFilter<User>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun feed(): List<User> {
|
override fun feed(): List<User> {
|
||||||
return user?.followers
|
return user?.let { myUser ->
|
||||||
?.filter { account.isAcceptable(it) } ?: emptyList()
|
LocalCache.users.values.filter { it.isFollowing(myUser) && account.isAcceptable(it) }
|
||||||
|
}?: emptyList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -14,7 +14,9 @@ object UserProfileFollowsFeedFilter: FeedFilter<User>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun feed(): List<User> {
|
override fun feed(): List<User> {
|
||||||
return user?.follows
|
return user?.latestContactList?.unverifiedFollowKeySet()?.map {
|
||||||
|
LocalCache.getOrCreateUser(it)
|
||||||
|
}
|
||||||
?.filter { account.isAcceptable(it) }
|
?.filter { account.isAcceptable(it) }
|
||||||
?.reversed() ?: emptyList()
|
?.reversed() ?: emptyList()
|
||||||
}
|
}
|
||||||
|
@@ -196,11 +196,11 @@ fun ProfileContent(baseAccountUser: User, modifier: Modifier = Modifier, scaffol
|
|||||||
}
|
}
|
||||||
})) {
|
})) {
|
||||||
Row() {
|
Row() {
|
||||||
Text("${accountUserFollows.follows.size}", fontWeight = FontWeight.Bold)
|
Text("${accountUserFollows.cachedFollowCount() ?: "--"}", fontWeight = FontWeight.Bold)
|
||||||
Text(stringResource(R.string.following))
|
Text(stringResource(R.string.following))
|
||||||
}
|
}
|
||||||
Row(modifier = Modifier.padding(start = 10.dp)) {
|
Row(modifier = Modifier.padding(start = 10.dp)) {
|
||||||
Text("${accountUserFollows.followers.size}", fontWeight = FontWeight.Bold)
|
Text("${accountUserFollows.cachedFollowerCount() ?: "--"}", fontWeight = FontWeight.Bold)
|
||||||
Text(stringResource(R.string.followers))
|
Text(stringResource(R.string.followers))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -41,9 +41,11 @@ import androidx.navigation.NavController
|
|||||||
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.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
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.ChannelMetadataEvent
|
import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
||||||
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.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
@@ -110,7 +112,10 @@ fun ChatroomCompose(baseNote: Note, accountViewModel: AccountViewModel, navContr
|
|||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
val replyAuthorBase = note.mentions?.first()
|
val replyAuthorBase =
|
||||||
|
(note.event as? PrivateDmEvent)
|
||||||
|
?.recipientPubKey()
|
||||||
|
?.let { LocalCache.getOrCreateUser(it) }
|
||||||
|
|
||||||
var userToComposeOn = note.author!!
|
var userToComposeOn = note.author!!
|
||||||
|
|
||||||
|
@@ -280,7 +280,12 @@ fun NoteCompose(
|
|||||||
|
|
||||||
Spacer(modifier = Modifier.height(3.dp))
|
Spacer(modifier = Modifier.height(3.dp))
|
||||||
|
|
||||||
if (noteEvent is TextNoteEvent && (note.replyTo != null || note.mentions != null)) {
|
if (noteEvent is TextNoteEvent && (note.replyTo != null || noteEvent.mentions().isNotEmpty())) {
|
||||||
|
val sortedMentions = noteEvent.mentions()
|
||||||
|
.map { LocalCache.getOrCreateUser(it) }
|
||||||
|
.toSet()
|
||||||
|
.sortedBy { account.userProfile().isFollowing(it) }
|
||||||
|
|
||||||
val replyingDirectlyTo = note.replyTo?.lastOrNull()
|
val replyingDirectlyTo = note.replyTo?.lastOrNull()
|
||||||
if (replyingDirectlyTo != null && unPackReply) {
|
if (replyingDirectlyTo != null && unPackReply) {
|
||||||
NoteCompose(
|
NoteCompose(
|
||||||
@@ -302,10 +307,13 @@ fun NoteCompose(
|
|||||||
navController = navController
|
navController = navController
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
ReplyInformation(note.replyTo, note.mentions, account, navController)
|
ReplyInformation(note.replyTo, sortedMentions, account, navController)
|
||||||
}
|
}
|
||||||
} else if (noteEvent is ChannelMessageEvent && (note.replyTo != null || note.mentions != null)) {
|
} else if (noteEvent is ChannelMessageEvent && (note.replyTo != null || noteEvent.mentions() != null)) {
|
||||||
val sortedMentions = note.mentions?.toSet()?.sortedBy { account.userProfile().isFollowing(it) }
|
val sortedMentions = noteEvent.mentions()
|
||||||
|
.map { LocalCache.getOrCreateUser(it) }
|
||||||
|
.toSet()
|
||||||
|
.sortedBy { account.userProfile().isFollowing(it) }
|
||||||
|
|
||||||
note.channel()?.let {
|
note.channel()?.let {
|
||||||
ReplyInformationChannel(note.replyTo, sortedMentions, it, navController)
|
ReplyInformationChannel(note.replyTo, sortedMentions, it, navController)
|
||||||
@@ -368,7 +376,9 @@ fun NoteCompose(
|
|||||||
Text(text = stringResource(R.string.award_granted_to))
|
Text(text = stringResource(R.string.award_granted_to))
|
||||||
|
|
||||||
FlowRow(modifier = Modifier.padding(top = 5.dp)) {
|
FlowRow(modifier = Modifier.padding(top = 5.dp)) {
|
||||||
note.mentions?.forEach {
|
noteEvent.awardees()
|
||||||
|
.map { LocalCache.getOrCreateUser(it) }
|
||||||
|
.forEach {
|
||||||
UserPicture(
|
UserPicture(
|
||||||
user = it,
|
user = it,
|
||||||
navController = navController,
|
navController = navController,
|
||||||
|
@@ -23,6 +23,8 @@ 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.NotificationCache
|
import com.vitorpamplona.amethyst.NotificationCache
|
||||||
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
|
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
||||||
import com.vitorpamplona.amethyst.ui.note.ChatroomCompose
|
import com.vitorpamplona.amethyst.ui.note.ChatroomCompose
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|
||||||
@@ -109,7 +111,11 @@ private fun FeedLoaded(
|
|||||||
if (channel != null) {
|
if (channel != null) {
|
||||||
route = "Channel/${channel.idHex}"
|
route = "Channel/${channel.idHex}"
|
||||||
} else {
|
} else {
|
||||||
val replyAuthorBase = note.mentions?.first()
|
val replyAuthorBase =
|
||||||
|
(note.event as? PrivateDmEvent)
|
||||||
|
?.recipientPubKey()
|
||||||
|
?.let { LocalCache.getOrCreateUser(it) }
|
||||||
|
|
||||||
var userToComposeOn = note.author!!
|
var userToComposeOn = note.author!!
|
||||||
if (replyAuthorBase != null) {
|
if (replyAuthorBase != null) {
|
||||||
if (note.author == account.userProfile()) {
|
if (note.author == account.userProfile()) {
|
||||||
|
@@ -50,7 +50,7 @@ class RelayFeedViewModel: ViewModel() {
|
|||||||
val beingUsed = currentUser?.relaysBeingUsed?.values ?: emptyList()
|
val beingUsed = currentUser?.relaysBeingUsed?.values ?: emptyList()
|
||||||
val beingUsedSet = currentUser?.relaysBeingUsed?.keys ?: emptySet()
|
val beingUsedSet = currentUser?.relaysBeingUsed?.keys ?: emptySet()
|
||||||
|
|
||||||
val newRelaysFromRecord = currentUser?.relays?.entries?.mapNotNull {
|
val newRelaysFromRecord = currentUser?.latestContactList?.relays()?.entries?.mapNotNull {
|
||||||
if (it.key !in beingUsedSet) {
|
if (it.key !in beingUsedSet) {
|
||||||
RelayInfo(it.key, 0, 0)
|
RelayInfo(it.key, 0, 0)
|
||||||
} else {
|
} else {
|
||||||
|
@@ -196,13 +196,13 @@ fun ProfileScreen(userId: String?, accountViewModel: AccountViewModel, navContro
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
val userState by baseUser.live().follows.observeAsState()
|
val userState by baseUser.live().follows.observeAsState()
|
||||||
val userFollows = userState?.user?.follows?.size ?: "--"
|
val userFollows = userState?.user?.transientFollowCount() ?: "--"
|
||||||
|
|
||||||
Text(text = "$userFollows ${stringResource(R.string.follows)}")
|
Text(text = "$userFollows ${stringResource(R.string.follows)}")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
val userState by baseUser.live().follows.observeAsState()
|
val userState by baseUser.live().follows.observeAsState()
|
||||||
val userFollowers = userState?.user?.followers?.size ?: "--"
|
val userFollowers = userState?.user?.transientFollowerCount() ?: "--"
|
||||||
|
|
||||||
Text(text = "$userFollowers ${stringResource(id = R.string.followers)}")
|
Text(text = "$userFollowers ${stringResource(id = R.string.followers)}")
|
||||||
},
|
},
|
||||||
@@ -223,7 +223,7 @@ fun ProfileScreen(userId: String?, accountViewModel: AccountViewModel, navContro
|
|||||||
val userRelaysBeingUsed = userState?.user?.relaysBeingUsed?.size ?: "--"
|
val userRelaysBeingUsed = userState?.user?.relaysBeingUsed?.size ?: "--"
|
||||||
|
|
||||||
val userStateRelayInfo by baseUser.live().relayInfo.observeAsState()
|
val userStateRelayInfo by baseUser.live().relayInfo.observeAsState()
|
||||||
val userRelays = userStateRelayInfo?.user?.relays?.size ?: "--"
|
val userRelays = userStateRelayInfo?.user?.latestContactList?.relays()?.size ?: "--"
|
||||||
|
|
||||||
Text(text = "$userRelaysBeingUsed / $userRelays ${stringResource(R.string.relays)}")
|
Text(text = "$userRelaysBeingUsed / $userRelays ${stringResource(R.string.relays)}")
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user