- Adds Support for Kind 16

- Migrates citations to `e` when they were supposed to be `a`
- Removes original tags from Reposts
-
This commit is contained in:
Vitor Pamplona
2023-06-17 21:08:33 -04:00
parent 611305a406
commit 05fb408842
18 changed files with 220 additions and 38 deletions
@@ -331,9 +331,15 @@ class Account(
} }
note.event?.let { note.event?.let {
val event = RepostEvent.create(it, loggedIn.privKey!!) if (it.kind() == 1) {
Client.send(event) val event = RepostEvent.create(it, loggedIn.privKey!!)
LocalCache.consume(event) Client.send(event)
LocalCache.consume(event)
} else {
val event = GenericRepostEvent.create(it, loggedIn.privKey!!)
Client.send(event)
LocalCache.consume(event)
}
} }
} }
@@ -1111,7 +1117,7 @@ class Account(
return note.author?.let { isAcceptable(it) } ?: true && // if user hasn't hided this author return note.author?.let { isAcceptable(it) } ?: true && // if user hasn't hided this author
isAcceptableDirect(note) && isAcceptableDirect(note) &&
( (
note.event !is RepostEvent || (note.event !is RepostEvent && note.event !is GenericRepostEvent) ||
(note.replyTo?.firstOrNull { isAcceptableDirect(it) } != null) (note.replyTo?.firstOrNull { isAcceptableDirect(it) } != null)
) // is not a reaction about a blocked post ) // is not a reaction about a blocked post
} }
@@ -1119,7 +1125,7 @@ class Account(
fun getRelevantReports(note: Note): Set<Note> { fun getRelevantReports(note: Note): Set<Note> {
val followsPlusMe = userProfile().latestContactList?.verifiedFollowKeySetAndMe ?: emptySet() val followsPlusMe = userProfile().latestContactList?.verifiedFollowKeySetAndMe ?: emptySet()
val innerReports = if (note.event is RepostEvent) { val innerReports = if (note.event is RepostEvent || note.event is GenericRepostEvent) {
note.replyTo?.map { getRelevantReports(it) }?.flatten() ?: emptyList() note.replyTo?.map { getRelevantReports(it) }?.flatten() ?: emptyList()
} else { } else {
emptyList() emptyList()
@@ -70,7 +70,14 @@ object LocalCache {
return checkGetOrCreateAddressableNote(key) return checkGetOrCreateAddressableNote(key)
} }
if (isValidHexNpub(key)) { if (isValidHexNpub(key)) {
return getOrCreateNote(key) val note = getOrCreateNote(key)
val noteEvent = note.event
if (noteEvent is AddressableEvent) {
// upgrade to the latest
return checkGetOrCreateAddressableNote(noteEvent.address().toTag())
} else {
return note
}
} }
return null return null
} }
@@ -185,6 +192,7 @@ object LocalCache {
if (version.event == null) { if (version.event == null) {
version.loadEvent(event, author, emptyList()) version.loadEvent(event, author, emptyList())
version.moveAllReferencesTo(note)
} }
// Already processed this event. // Already processed this event.
@@ -245,6 +253,7 @@ object LocalCache {
if (version.event == null) { if (version.event == null) {
version.loadEvent(event, author, emptyList()) version.loadEvent(event, author, emptyList())
version.moveAllReferencesTo(note)
} }
if (relay != null) { if (relay != null) {
@@ -316,6 +325,7 @@ object LocalCache {
if (version.event == null) { if (version.event == null) {
version.loadEvent(event, author, emptyList()) version.loadEvent(event, author, emptyList())
version.moveAllReferencesTo(note)
} }
if (note.event?.id() == event.id()) return if (note.event?.id() == event.id()) return
@@ -334,6 +344,7 @@ object LocalCache {
if (version.event == null) { if (version.event == null) {
version.loadEvent(event, author, emptyList()) version.loadEvent(event, author, emptyList())
version.moveAllReferencesTo(note)
} }
if (note.event?.id() == event.id()) return if (note.event?.id() == event.id()) return
@@ -352,6 +363,7 @@ object LocalCache {
if (version.event == null) { if (version.event == null) {
version.loadEvent(event, author, emptyList()) version.loadEvent(event, author, emptyList())
version.moveAllReferencesTo(note)
} }
if (note.event?.id() == event.id()) return if (note.event?.id() == event.id()) return
@@ -370,6 +382,7 @@ object LocalCache {
if (version.event == null) { if (version.event == null) {
version.loadEvent(event, author, emptyList()) version.loadEvent(event, author, emptyList())
version.moveAllReferencesTo(note)
} }
// Already processed this event. // Already processed this event.
@@ -389,6 +402,7 @@ object LocalCache {
if (version.event == null) { if (version.event == null) {
version.loadEvent(event, author, emptyList()) version.loadEvent(event, author, emptyList())
version.moveAllReferencesTo(note)
} }
// Already processed this event. // Already processed this event.
@@ -408,6 +422,7 @@ object LocalCache {
if (version.event == null) { if (version.event == null) {
version.loadEvent(event, author, emptyList()) version.loadEvent(event, author, emptyList())
version.moveAllReferencesTo(note)
} }
// Already processed this event. // Already processed this event.
@@ -451,6 +466,7 @@ object LocalCache {
if (version.event == null) { if (version.event == null) {
version.loadEvent(event, author, emptyList()) version.loadEvent(event, author, emptyList())
version.moveAllReferencesTo(note)
} }
// Already processed this event. // Already processed this event.
@@ -470,6 +486,7 @@ object LocalCache {
if (version.event == null) { if (version.event == null) {
version.loadEvent(event, author, emptyList()) version.loadEvent(event, author, emptyList())
version.moveAllReferencesTo(note)
} }
// Already processed this event. // Already processed this event.
@@ -605,6 +622,31 @@ object LocalCache {
refreshObservers(note) refreshObservers(note)
} }
fun consume(event: GenericRepostEvent) {
val note = getOrCreateNote(event.id)
// Already processed this event.
if (note.event != null) return
// Log.d("TN", "New Boost (${notes.size},${users.size}) ${note.author?.toBestDisplayName()} ${formattedDateTime(event.createdAt)}")
val author = getOrCreateUser(event.pubKey)
val repliesTo = event.boostedPost().mapNotNull { checkGetOrCreateNote(it) } +
event.taggedAddresses().mapNotNull { getOrCreateAddressableNote(it) }
note.loadEvent(event, author, repliesTo)
// Prepares user's profile view.
author.addNote(note)
// Counts the replies
repliesTo.forEach {
it.addBoost(note)
}
refreshObservers(note)
}
fun consume(event: ReactionEvent) { fun consume(event: ReactionEvent) {
val note = getOrCreateNote(event.id) val note = getOrCreateNote(event.id)
@@ -1154,6 +1196,12 @@ object LocalCache {
} }
consume(event) consume(event)
} }
is GenericRepostEvent -> {
event.containedPost()?.let {
verifyAndConsume(it, relay)
}
consume(event)
}
is TextNoteEvent -> consume(event, relay) is TextNoteEvent -> consume(event, relay)
is PollNoteEvent -> consume(event, relay) is PollNoteEvent -> consume(event, relay)
else -> { else -> {
@@ -10,6 +10,7 @@ import com.vitorpamplona.amethyst.service.model.*
import com.vitorpamplona.amethyst.service.nip19.Nip19 import com.vitorpamplona.amethyst.service.nip19.Nip19
import com.vitorpamplona.amethyst.service.relays.EOSETime import com.vitorpamplona.amethyst.service.relays.EOSETime
import com.vitorpamplona.amethyst.service.relays.Relay import com.vitorpamplona.amethyst.service.relays.Relay
import com.vitorpamplona.amethyst.ui.actions.updated
import com.vitorpamplona.amethyst.ui.components.BundledUpdate import com.vitorpamplona.amethyst.ui.components.BundledUpdate
import com.vitorpamplona.amethyst.ui.note.toShortenHex import com.vitorpamplona.amethyst.ui.note.toShortenHex
import fr.acinq.secp256k1.Hex import fr.acinq.secp256k1.Hex
@@ -96,7 +97,7 @@ open class Note(val idHex: String) {
*/ */
fun replyLevelSignature(cachedSignatures: MutableMap<Note, String> = mutableMapOf()): String { fun replyLevelSignature(cachedSignatures: MutableMap<Note, String> = mutableMapOf()): String {
val replyTo = replyTo val replyTo = replyTo
if (replyTo == null || replyTo.isEmpty()) { if (event is RepostEvent || event is GenericRepostEvent || replyTo == null || replyTo.isEmpty()) {
return "/" + formattedDateTime(createdAt() ?: 0) + ";" return "/" + formattedDateTime(createdAt() ?: 0) + ";"
} }
@@ -109,7 +110,7 @@ open class Note(val idHex: String) {
fun replyLevel(cachedLevels: MutableMap<Note, Int> = mutableMapOf()): Int { fun replyLevel(cachedLevels: MutableMap<Note, Int> = mutableMapOf()): Int {
val replyTo = replyTo val replyTo = replyTo
if (replyTo == null || replyTo.isEmpty()) { if (event is RepostEvent || event is GenericRepostEvent || replyTo == null || replyTo.isEmpty()) {
return 0 return 0
} }
@@ -372,7 +373,7 @@ open class Note(val idHex: String) {
} }
fun isNewThread(): Boolean { fun isNewThread(): Boolean {
return event is RepostEvent || replyTo == null || replyTo?.size == 0 return event is RepostEvent || event is GenericRepostEvent || replyTo == null || replyTo?.size == 0
} }
fun hasZapped(loggedIn: User): Boolean { fun hasZapped(loggedIn: User): Boolean {
@@ -400,6 +401,42 @@ open class Note(val idHex: String) {
return boosts.filter { it.author == loggedIn } return boosts.filter { it.author == loggedIn }
} }
fun moveAllReferencesTo(note: AddressableNote) {
// migrates these comments to a new version
replies.forEach {
note.addReply(it)
it.replyTo = it.replyTo?.updated(this, note)
}
reactions.forEach {
it.value.forEach {
note.addReaction(it)
it.replyTo = it.replyTo?.updated(this, note)
}
}
boosts.forEach {
note.addBoost(it)
it.replyTo = it.replyTo?.updated(this, note)
}
reports.forEach {
it.value.forEach {
note.addReport(it)
it.replyTo = it.replyTo?.updated(this, note)
}
}
zaps.forEach {
note.addZap(it.key, it.value)
it.key.replyTo = it.key.replyTo?.updated(this, note)
it.value?.replyTo = it.value?.replyTo?.updated(this, note)
}
replyTo = null
replies = emptySet()
reactions = emptyMap()
boosts = emptySet()
reports = emptyMap()
zaps = emptyMap()
}
var liveSet: NoteLiveSet? = null var liveSet: NoteLiveSet? = null
fun live(): NoteLiveSet { fun live(): NoteLiveSet {
@@ -2,6 +2,8 @@ package com.vitorpamplona.amethyst.model
import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.amethyst.service.checkNotInMainThread
import com.vitorpamplona.amethyst.service.model.ATag import com.vitorpamplona.amethyst.service.model.ATag
import com.vitorpamplona.amethyst.service.model.GenericRepostEvent
import com.vitorpamplona.amethyst.service.model.RepostEvent
import kotlin.time.ExperimentalTime import kotlin.time.ExperimentalTime
import kotlin.time.measureTimedValue import kotlin.time.measureTimedValue
@@ -10,6 +12,8 @@ class ThreadAssembler {
private fun searchRoot(note: Note, testedNotes: MutableSet<Note> = mutableSetOf()): Note? { private fun searchRoot(note: Note, testedNotes: MutableSet<Note> = mutableSetOf()): Note? {
if (note.replyTo == null || note.replyTo?.isEmpty() == true) return note if (note.replyTo == null || note.replyTo?.isEmpty() == true) return note
if (note.event is RepostEvent || note.event is GenericRepostEvent) return note
testedNotes.add(note) testedNotes.add(note)
val markedAsRoot = note.event?.tags()?.firstOrNull { it[0] == "e" && it.size > 3 && it[3] == "root" }?.getOrNull(1) val markedAsRoot = note.event?.tags()?.firstOrNull { it[0] == "e" && it.size > 3 && it[3] == "root" }?.getOrNull(1)
@@ -88,6 +88,7 @@ object NostrAccountDataSource : NostrDataSource("AccountData") {
PollNoteEvent.kind, PollNoteEvent.kind,
ReactionEvent.kind, ReactionEvent.kind,
RepostEvent.kind, RepostEvent.kind,
GenericRepostEvent.kind,
ReportEvent.kind, ReportEvent.kind,
LnZapEvent.kind, LnZapEvent.kind,
LnZapPaymentResponseEvent.kind, LnZapPaymentResponseEvent.kind,
@@ -3,6 +3,7 @@ package com.vitorpamplona.amethyst.service
import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.UserState import com.vitorpamplona.amethyst.model.UserState
import com.vitorpamplona.amethyst.service.model.AudioTrackEvent import com.vitorpamplona.amethyst.service.model.AudioTrackEvent
import com.vitorpamplona.amethyst.service.model.GenericRepostEvent
import com.vitorpamplona.amethyst.service.model.HighlightEvent import com.vitorpamplona.amethyst.service.model.HighlightEvent
import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent
import com.vitorpamplona.amethyst.service.model.PinListEvent import com.vitorpamplona.amethyst.service.model.PinListEvent
@@ -59,7 +60,16 @@ object NostrHomeDataSource : NostrDataSource("HomeFeed") {
return TypedFilter( return TypedFilter(
types = setOf(FeedType.FOLLOWS), types = setOf(FeedType.FOLLOWS),
filter = JsonFilter( filter = JsonFilter(
kinds = listOf(TextNoteEvent.kind, RepostEvent.kind, LongTextNoteEvent.kind, PollNoteEvent.kind, HighlightEvent.kind, AudioTrackEvent.kind, PinListEvent.kind), kinds = listOf(
TextNoteEvent.kind,
RepostEvent.kind,
GenericRepostEvent.kind,
LongTextNoteEvent.kind,
PollNoteEvent.kind,
HighlightEvent.kind,
AudioTrackEvent.kind,
PinListEvent.kind
),
authors = followSet, authors = followSet,
limit = 400, limit = 400,
since = latestEOSEs.users[account.userProfile()]?.followList?.get(account.defaultHomeFollowList)?.relayList since = latestEOSEs.users[account.userProfile()]?.followList?.get(account.defaultHomeFollowList)?.relayList
@@ -26,7 +26,7 @@ object NostrSingleEventDataSource : NostrDataSource("SingleEventFeed") {
filter = JsonFilter( filter = JsonFilter(
kinds = listOf( kinds = listOf(
TextNoteEvent.kind, LongTextNoteEvent.kind, TextNoteEvent.kind, LongTextNoteEvent.kind,
ReactionEvent.kind, RepostEvent.kind, ReportEvent.kind, ReactionEvent.kind, RepostEvent.kind, GenericRepostEvent.kind, ReportEvent.kind,
LnZapEvent.kind, LnZapRequestEvent.kind, LnZapEvent.kind, LnZapRequestEvent.kind,
BadgeAwardEvent.kind, BadgeDefinitionEvent.kind, BadgeProfilesEvent.kind, BadgeAwardEvent.kind, BadgeDefinitionEvent.kind, BadgeProfilesEvent.kind,
PollNoteEvent.kind, AudioTrackEvent.kind, PinListEvent.kind, PollNoteEvent.kind, AudioTrackEvent.kind, PinListEvent.kind,
@@ -77,6 +77,7 @@ object NostrSingleEventDataSource : NostrDataSource("SingleEventFeed") {
LongTextNoteEvent.kind, LongTextNoteEvent.kind,
ReactionEvent.kind, ReactionEvent.kind,
RepostEvent.kind, RepostEvent.kind,
GenericRepostEvent.kind,
ReportEvent.kind, ReportEvent.kind,
LnZapEvent.kind, LnZapEvent.kind,
LnZapRequestEvent.kind, LnZapRequestEvent.kind,
@@ -28,7 +28,16 @@ object NostrUserProfileDataSource : NostrDataSource("UserProfileFeed") {
TypedFilter( TypedFilter(
types = COMMON_FEED_TYPES, types = COMMON_FEED_TYPES,
filter = JsonFilter( filter = JsonFilter(
kinds = listOf(TextNoteEvent.kind, RepostEvent.kind, LongTextNoteEvent.kind, AudioTrackEvent.kind, PinListEvent.kind, PollNoteEvent.kind, HighlightEvent.kind), kinds = listOf(
TextNoteEvent.kind,
GenericRepostEvent.kind,
RepostEvent.kind,
LongTextNoteEvent.kind,
AudioTrackEvent.kind,
PinListEvent.kind,
PollNoteEvent.kind,
HighlightEvent.kind
),
authors = listOf(it.pubkeyHex), authors = listOf(it.pubkeyHex),
limit = 200 limit = 200
) )
@@ -245,6 +245,7 @@ open class Event(
FileHeaderEvent.kind -> FileHeaderEvent(id, pubKey, createdAt, tags, content, sig) FileHeaderEvent.kind -> FileHeaderEvent(id, pubKey, createdAt, tags, content, sig)
FileStorageEvent.kind -> FileStorageEvent(id, pubKey, createdAt, tags, content, sig) FileStorageEvent.kind -> FileStorageEvent(id, pubKey, createdAt, tags, content, sig)
FileStorageHeaderEvent.kind -> FileStorageHeaderEvent(id, pubKey, createdAt, tags, content, sig) FileStorageHeaderEvent.kind -> FileStorageHeaderEvent(id, pubKey, createdAt, tags, content, sig)
GenericRepostEvent.kind -> GenericRepostEvent(id, pubKey, createdAt, tags, content, sig)
HighlightEvent.kind -> HighlightEvent(id, pubKey, createdAt, tags, content, sig) HighlightEvent.kind -> HighlightEvent(id, pubKey, createdAt, tags, content, sig)
LiveActivitiesEvent.kind -> LiveActivitiesEvent(id, pubKey, createdAt, tags, content, sig) LiveActivitiesEvent.kind -> LiveActivitiesEvent(id, pubKey, createdAt, tags, content, sig)
LnZapEvent.kind -> LnZapEvent(id, pubKey, createdAt, tags, content, sig) LnZapEvent.kind -> LnZapEvent(id, pubKey, createdAt, tags, content, sig)
@@ -0,0 +1,52 @@
package com.vitorpamplona.amethyst.service.model
import androidx.compose.runtime.Immutable
import com.vitorpamplona.amethyst.model.HexKey
import com.vitorpamplona.amethyst.model.toHexKey
import com.vitorpamplona.amethyst.service.relays.Client
import nostr.postr.Utils
import java.util.Date
@Immutable
class GenericRepostEvent(
id: HexKey,
pubKey: HexKey,
createdAt: Long,
tags: List<List<String>>,
content: String,
sig: HexKey
) : Event(id, pubKey, createdAt, kind, tags, content, sig) {
fun boostedPost() = tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) }
fun originalAuthor() = tags.filter { it.firstOrNull() == "p" }.mapNotNull { it.getOrNull(1) }
fun containedPost() = try {
fromJson(content, Client.lenient)
} catch (e: Exception) {
null
}
companion object {
const val kind = 16
fun create(boostedPost: EventInterface, privateKey: ByteArray, createdAt: Long = Date().time / 1000): GenericRepostEvent {
val content = boostedPost.toJson()
val replyToPost = listOf("e", boostedPost.id())
val replyToAuthor = listOf("p", boostedPost.pubKey())
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
var tags: List<List<String>> = listOf(replyToPost, replyToAuthor)
if (boostedPost is AddressableEvent) {
tags = tags + listOf(listOf("a", boostedPost.address().toTag()))
}
tags = tags + listOf(listOf("k", "${boostedPost.kind()}"))
val id = generateId(pubKey, createdAt, kind, tags, content)
val sig = Utils.sign(id, privateKey)
return GenericRepostEvent(id.toHexKey(), pubKey, createdAt, tags, content, sig.toHexKey())
}
}
}
@@ -36,7 +36,7 @@ class RepostEvent(
val replyToAuthor = listOf("p", boostedPost.pubKey()) val replyToAuthor = listOf("p", boostedPost.pubKey())
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey() val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
var tags: List<List<String>> = boostedPost.tags().plus(listOf(replyToPost, replyToAuthor)) var tags: List<List<String>> = listOf(replyToPost, replyToAuthor)
if (boostedPost is AddressableEvent) { if (boostedPost is AddressableEvent) {
tags = tags + listOf(listOf("a", boostedPost.address().toTag())) tags = tags + listOf(listOf("a", boostedPost.address().toTag()))
@@ -4,6 +4,7 @@ import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.service.model.AudioTrackEvent import com.vitorpamplona.amethyst.service.model.AudioTrackEvent
import com.vitorpamplona.amethyst.service.model.GenericRepostEvent
import com.vitorpamplona.amethyst.service.model.HighlightEvent import com.vitorpamplona.amethyst.service.model.HighlightEvent
import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent
import com.vitorpamplona.amethyst.service.model.PollNoteEvent import com.vitorpamplona.amethyst.service.model.PollNoteEvent
@@ -26,19 +27,24 @@ class HomeNewThreadFeedFilter(val account: Account) : AdditiveFeedFilter<Note>()
val followingKeySet = account.selectedUsersFollowList(account.defaultHomeFollowList) ?: emptySet() val followingKeySet = account.selectedUsersFollowList(account.defaultHomeFollowList) ?: emptySet()
val followingTagSet = account.selectedTagsFollowList(account.defaultHomeFollowList) ?: emptySet() val followingTagSet = account.selectedTagsFollowList(account.defaultHomeFollowList) ?: emptySet()
val oneHr = 60 * 60
return collection return collection
.asSequence() .asSequence()
.filter { it -> .filter { it ->
val noteEvent = it.event val noteEvent = it.event
(noteEvent is TextNoteEvent || noteEvent is RepostEvent || noteEvent is LongTextNoteEvent || noteEvent is PollNoteEvent || noteEvent is HighlightEvent || noteEvent is AudioTrackEvent) && (noteEvent is TextNoteEvent || noteEvent is RepostEvent || noteEvent is GenericRepostEvent || noteEvent is LongTextNoteEvent || noteEvent is PollNoteEvent || noteEvent is HighlightEvent || noteEvent is AudioTrackEvent) &&
(it.author?.pubkeyHex in followingKeySet || (noteEvent.isTaggedHashes(followingTagSet))) && (it.author?.pubkeyHex in followingKeySet || (noteEvent.isTaggedHashes(followingTagSet))) &&
// && 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.pubkeyHex) } ?: true && it.author?.let { !account.isHidden(it.pubkeyHex) } ?: true &&
it.isNewThread() && it.isNewThread() &&
( (
noteEvent !is RepostEvent || // not a repost (noteEvent !is RepostEvent && noteEvent !is GenericRepostEvent) || // not a repost
(it.replyTo?.lastOrNull()?.author?.pubkeyHex !in followingKeySet) // or a repost of by a non-follower's post (likely not seen yet) (
) // || (noteEvent.createdAt > (it.replyTo?.lastOrNull()?.createdAt() ?: 0) + 60*60*1000 )) // it.replyTo?.lastOrNull()?.author?.pubkeyHex !in followingKeySet ||
(noteEvent.createdAt() > (it.replyTo?.lastOrNull()?.createdAt() ?: 0) + oneHr)
) // or a repost of by a non-follower's post (likely not seen yet)
)
} }
.toSet() .toSet()
} }
@@ -61,7 +61,7 @@ class NotificationFeedFilter(val account: Account) : AdditiveFeedFilter<Note>()
return note.replyTo?.lastOrNull()?.author?.pubkeyHex == authorHex return note.replyTo?.lastOrNull()?.author?.pubkeyHex == authorHex
} }
if (event is RepostEvent) { if (event is RepostEvent || event is GenericRepostEvent) {
return note.replyTo?.lastOrNull()?.author?.pubkeyHex == authorHex return note.replyTo?.lastOrNull()?.author?.pubkeyHex == authorHex
} }
@@ -98,6 +98,7 @@ import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
import com.vitorpamplona.amethyst.service.model.EventInterface import com.vitorpamplona.amethyst.service.model.EventInterface
import com.vitorpamplona.amethyst.service.model.FileHeaderEvent import com.vitorpamplona.amethyst.service.model.FileHeaderEvent
import com.vitorpamplona.amethyst.service.model.FileStorageHeaderEvent import com.vitorpamplona.amethyst.service.model.FileStorageHeaderEvent
import com.vitorpamplona.amethyst.service.model.GenericRepostEvent
import com.vitorpamplona.amethyst.service.model.HighlightEvent import com.vitorpamplona.amethyst.service.model.HighlightEvent
import com.vitorpamplona.amethyst.service.model.LiveActivitiesEvent import com.vitorpamplona.amethyst.service.model.LiveActivitiesEvent
import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent
@@ -520,7 +521,7 @@ private fun RenderNoteWithReactions(
val showSecondRow by remember { val showSecondRow by remember {
derivedStateOf { derivedStateOf {
baseNote.event !is RepostEvent && !isBoostedNote && !isQuotedNote baseNote.event !is RepostEvent && baseNote.event !is GenericRepostEvent && !isBoostedNote && !isQuotedNote
} }
} }
@@ -554,7 +555,7 @@ private fun RenderNoteWithReactions(
) )
} }
if (!makeItShort && baseNote.event !is RepostEvent) { if (!makeItShort && baseNote.event !is RepostEvent && baseNote.event !is GenericRepostEvent) {
ReactionsRow( ReactionsRow(
baseNote = baseNote, baseNote = baseNote,
showReactionDetail = notBoostedNorQuote, showReactionDetail = notBoostedNorQuote,
@@ -562,7 +563,7 @@ private fun RenderNoteWithReactions(
nav = nav nav = nav
) )
} else { } else {
if (!isBoostedNote && baseNote.event !is RepostEvent) { if (!isBoostedNote && baseNote.event !is RepostEvent && baseNote.event !is GenericRepostEvent) {
Spacer(modifier = Modifier.height(10.dp)) Spacer(modifier = Modifier.height(10.dp))
} }
} }
@@ -648,6 +649,10 @@ private fun RenderNoteRow(
RenderRepost(baseNote, backgroundColor, accountViewModel, nav) RenderRepost(baseNote, backgroundColor, accountViewModel, nav)
} }
is GenericRepostEvent -> {
RenderRepost(baseNote, backgroundColor, accountViewModel, nav)
}
is ReportEvent -> { is ReportEvent -> {
RenderReport(baseNote, backgroundColor, accountViewModel, nav) RenderReport(baseNote, backgroundColor, accountViewModel, nav)
} }
@@ -1421,7 +1426,7 @@ private fun RenderReaction(
} }
@Composable @Composable
private fun RenderRepost( fun RenderRepost(
note: Note, note: Note,
backgroundColor: MutableState<Color>, backgroundColor: MutableState<Color>,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
@@ -1754,7 +1759,7 @@ private fun FirstUserInfoRow(
NoteUsernameDisplay(baseNote, remember { Modifier.weight(1f) }) NoteUsernameDisplay(baseNote, remember { Modifier.weight(1f) })
} }
if (eventNote is RepostEvent) { if (eventNote is RepostEvent || eventNote is GenericRepostEvent) {
Text( Text(
" ${stringResource(id = R.string.boosted)}", " ${stringResource(id = R.string.boosted)}",
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
@@ -1828,7 +1833,7 @@ private fun DrawAuthorImages(baseNote: Note, accountViewModel: AccountViewModel,
Box(modifier = modifier, contentAlignment = Alignment.BottomEnd) { Box(modifier = modifier, contentAlignment = Alignment.BottomEnd) {
NoteAuthorPicture(baseNote, nav, accountViewModel, 55.dp) NoteAuthorPicture(baseNote, nav, accountViewModel, 55.dp)
if (baseNote.event is RepostEvent) { if (baseNote.event is RepostEvent || baseNote.event is GenericRepostEvent) {
RepostNoteAuthorPicture(baseNote, accountViewModel, nav) RepostNoteAuthorPicture(baseNote, accountViewModel, nav)
} }
@@ -1839,7 +1844,7 @@ private fun DrawAuthorImages(baseNote: Note, accountViewModel: AccountViewModel,
} }
} }
if (baseNote.event is RepostEvent) { if (baseNote.event is RepostEvent || baseNote.event is GenericRepostEvent) {
val baseReply = remember { val baseReply = remember {
baseNote.replyTo?.lastOrNull() baseNote.replyTo?.lastOrNull()
} }
@@ -40,6 +40,7 @@ import com.vitorpamplona.amethyst.model.LocalCache
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.service.checkNotInMainThread import com.vitorpamplona.amethyst.service.checkNotInMainThread
import com.vitorpamplona.amethyst.service.model.GenericRepostEvent
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
import com.vitorpamplona.amethyst.service.model.RepostEvent import com.vitorpamplona.amethyst.service.model.RepostEvent
@@ -178,9 +179,9 @@ class UserReactionsViewModel(val account: Account) : ViewModel() {
reactions[netDate] = (reactions[netDate] ?: 0) + 1 reactions[netDate] = (reactions[netDate] ?: 0) + 1
takenIntoAccount.add(noteEvent.id()) takenIntoAccount.add(noteEvent.id())
} }
} else if (noteEvent is RepostEvent) { } else if (noteEvent is RepostEvent || noteEvent is GenericRepostEvent) {
if (noteEvent.isTaggedUser(currentUser) && noteEvent.pubKey != currentUser) { if (noteEvent.isTaggedUser(currentUser) && noteEvent.pubKey() != currentUser) {
val netDate = formatDate(noteEvent.createdAt) val netDate = formatDate(noteEvent.createdAt())
boosts[netDate] = (boosts[netDate] ?: 0) + 1 boosts[netDate] = (boosts[netDate] ?: 0) + 1
takenIntoAccount.add(noteEvent.id()) takenIntoAccount.add(noteEvent.id())
} }
@@ -229,9 +230,9 @@ class UserReactionsViewModel(val account: Account) : ViewModel() {
takenIntoAccount.add(noteEvent.id()) takenIntoAccount.add(noteEvent.id())
hasNewElements = true hasNewElements = true
} }
} else if (noteEvent is RepostEvent) { } else if (noteEvent is RepostEvent || noteEvent is GenericRepostEvent) {
if (noteEvent.isTaggedUser(currentUser) && noteEvent.pubKey != currentUser) { if (noteEvent.isTaggedUser(currentUser) && noteEvent.pubKey() != currentUser) {
val netDate = formatDate(noteEvent.createdAt) val netDate = formatDate(noteEvent.createdAt())
boosts[netDate] = (boosts[netDate] ?: 0) + 1 boosts[netDate] = (boosts[netDate] ?: 0) + 1
takenIntoAccount.add(noteEvent.id()) takenIntoAccount.add(noteEvent.id())
hasNewElements = true hasNewElements = true
@@ -15,6 +15,7 @@ import com.vitorpamplona.amethyst.service.checkNotInMainThread
import com.vitorpamplona.amethyst.service.model.BadgeAwardEvent import com.vitorpamplona.amethyst.service.model.BadgeAwardEvent
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.GenericRepostEvent
import com.vitorpamplona.amethyst.service.model.LnZapEvent import com.vitorpamplona.amethyst.service.model.LnZapEvent
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
import com.vitorpamplona.amethyst.service.model.ReactionEvent import com.vitorpamplona.amethyst.service.model.ReactionEvent
@@ -162,7 +163,7 @@ open class CardFeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel() {
val boostsPerEvent = mutableMapOf<Note, MutableList<Note>>() val boostsPerEvent = mutableMapOf<Note, MutableList<Note>>()
notes notes
.filter { it.event is RepostEvent } .filter { it.event is RepostEvent || it.event is GenericRepostEvent }
.forEach { .forEach {
val boostedPost = it.replyTo?.lastOrNull() { it.event !is ChannelMetadataEvent && it.event !is ChannelCreateEvent } val boostedPost = it.replyTo?.lastOrNull() { it.event !is ChannelMetadataEvent && it.event !is ChannelCreateEvent }
if (boostedPost != null) { if (boostedPost != null) {
@@ -197,7 +198,7 @@ open class CardFeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel() {
) )
} }
val textNoteCards = notes.filter { it.event !is ReactionEvent && it.event !is RepostEvent && it.event !is LnZapEvent }.map { val textNoteCards = notes.filter { it.event !is ReactionEvent && it.event !is RepostEvent && it.event !is GenericRepostEvent && it.event !is LnZapEvent }.map {
if (it.event is PrivateDmEvent) { if (it.event is PrivateDmEvent) {
MessageSetCard(it) MessageSetCard(it)
} else if (it.event is BadgeAwardEvent) { } else if (it.event is BadgeAwardEvent) {
@@ -57,12 +57,14 @@ import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.service.model.AppDefinitionEvent import com.vitorpamplona.amethyst.service.model.AppDefinitionEvent
import com.vitorpamplona.amethyst.service.model.AudioTrackEvent import com.vitorpamplona.amethyst.service.model.AudioTrackEvent
import com.vitorpamplona.amethyst.service.model.BadgeDefinitionEvent import com.vitorpamplona.amethyst.service.model.BadgeDefinitionEvent
import com.vitorpamplona.amethyst.service.model.GenericRepostEvent
import com.vitorpamplona.amethyst.service.model.HighlightEvent import com.vitorpamplona.amethyst.service.model.HighlightEvent
import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent
import com.vitorpamplona.amethyst.service.model.PeopleListEvent import com.vitorpamplona.amethyst.service.model.PeopleListEvent
import com.vitorpamplona.amethyst.service.model.PinListEvent import com.vitorpamplona.amethyst.service.model.PinListEvent
import com.vitorpamplona.amethyst.service.model.PollNoteEvent import com.vitorpamplona.amethyst.service.model.PollNoteEvent
import com.vitorpamplona.amethyst.service.model.RelaySetEvent import com.vitorpamplona.amethyst.service.model.RelaySetEvent
import com.vitorpamplona.amethyst.service.model.RepostEvent
import com.vitorpamplona.amethyst.ui.components.ObserveDisplayNip05Status import com.vitorpamplona.amethyst.ui.components.ObserveDisplayNip05Status
import com.vitorpamplona.amethyst.ui.note.* import com.vitorpamplona.amethyst.ui.note.*
import com.vitorpamplona.amethyst.ui.note.BadgeDisplay import com.vitorpamplona.amethyst.ui.note.BadgeDisplay
@@ -401,6 +403,8 @@ fun NoteMaster(
accountViewModel, accountViewModel,
nav nav
) )
} else if (noteEvent is RepostEvent || noteEvent is GenericRepostEvent) {
RenderRepost(baseNote, backgroundColor, accountViewModel, nav)
} else if (noteEvent is PollNoteEvent) { } else if (noteEvent is PollNoteEvent) {
val canPreview = note.author == account.userProfile() || val canPreview = note.author == account.userProfile() ||
(note.author?.let { account.userProfile().isFollowingCached(it) } ?: true) || (note.author?.let { account.userProfile().isFollowingCached(it) } ?: true) ||
@@ -2,14 +2,12 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.unit.dp
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
@@ -55,9 +53,7 @@ fun ThreadScreen(noteId: String?, accountViewModel: AccountViewModel, nav: (Stri
} }
Column(Modifier.fillMaxHeight()) { Column(Modifier.fillMaxHeight()) {
Column( Column() {
modifier = Modifier.padding(vertical = 0.dp)
) {
ThreadFeedView(noteId, feedViewModel, accountViewModel, nav) ThreadFeedView(noteId, feedViewModel, accountViewModel, nav)
} }
} }