diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index 5a96fd013..a7ffbe0c8 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -331,9 +331,15 @@ class Account( } note.event?.let { - val event = RepostEvent.create(it, loggedIn.privKey!!) - Client.send(event) - LocalCache.consume(event) + if (it.kind() == 1) { + val event = RepostEvent.create(it, loggedIn.privKey!!) + 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 isAcceptableDirect(note) && ( - note.event !is RepostEvent || + (note.event !is RepostEvent && note.event !is GenericRepostEvent) || (note.replyTo?.firstOrNull { isAcceptableDirect(it) } != null) ) // is not a reaction about a blocked post } @@ -1119,7 +1125,7 @@ class Account( fun getRelevantReports(note: Note): Set { 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() } else { emptyList() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt index 5f8b95f1c..921e79f0b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt @@ -70,7 +70,14 @@ object LocalCache { return checkGetOrCreateAddressableNote(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 } @@ -185,6 +192,7 @@ object LocalCache { if (version.event == null) { version.loadEvent(event, author, emptyList()) + version.moveAllReferencesTo(note) } // Already processed this event. @@ -245,6 +253,7 @@ object LocalCache { if (version.event == null) { version.loadEvent(event, author, emptyList()) + version.moveAllReferencesTo(note) } if (relay != null) { @@ -316,6 +325,7 @@ object LocalCache { if (version.event == null) { version.loadEvent(event, author, emptyList()) + version.moveAllReferencesTo(note) } if (note.event?.id() == event.id()) return @@ -334,6 +344,7 @@ object LocalCache { if (version.event == null) { version.loadEvent(event, author, emptyList()) + version.moveAllReferencesTo(note) } if (note.event?.id() == event.id()) return @@ -352,6 +363,7 @@ object LocalCache { if (version.event == null) { version.loadEvent(event, author, emptyList()) + version.moveAllReferencesTo(note) } if (note.event?.id() == event.id()) return @@ -370,6 +382,7 @@ object LocalCache { if (version.event == null) { version.loadEvent(event, author, emptyList()) + version.moveAllReferencesTo(note) } // Already processed this event. @@ -389,6 +402,7 @@ object LocalCache { if (version.event == null) { version.loadEvent(event, author, emptyList()) + version.moveAllReferencesTo(note) } // Already processed this event. @@ -408,6 +422,7 @@ object LocalCache { if (version.event == null) { version.loadEvent(event, author, emptyList()) + version.moveAllReferencesTo(note) } // Already processed this event. @@ -451,6 +466,7 @@ object LocalCache { if (version.event == null) { version.loadEvent(event, author, emptyList()) + version.moveAllReferencesTo(note) } // Already processed this event. @@ -470,6 +486,7 @@ object LocalCache { if (version.event == null) { version.loadEvent(event, author, emptyList()) + version.moveAllReferencesTo(note) } // Already processed this event. @@ -605,6 +622,31 @@ object LocalCache { 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) { val note = getOrCreateNote(event.id) @@ -1154,6 +1196,12 @@ object LocalCache { } consume(event) } + is GenericRepostEvent -> { + event.containedPost()?.let { + verifyAndConsume(it, relay) + } + consume(event) + } is TextNoteEvent -> consume(event, relay) is PollNoteEvent -> consume(event, relay) else -> { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt index 99d1526f7..d7fb74ee9 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt @@ -10,6 +10,7 @@ import com.vitorpamplona.amethyst.service.model.* import com.vitorpamplona.amethyst.service.nip19.Nip19 import com.vitorpamplona.amethyst.service.relays.EOSETime 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.note.toShortenHex import fr.acinq.secp256k1.Hex @@ -96,7 +97,7 @@ open class Note(val idHex: String) { */ fun replyLevelSignature(cachedSignatures: MutableMap = mutableMapOf()): String { val replyTo = replyTo - if (replyTo == null || replyTo.isEmpty()) { + if (event is RepostEvent || event is GenericRepostEvent || replyTo == null || replyTo.isEmpty()) { return "/" + formattedDateTime(createdAt() ?: 0) + ";" } @@ -109,7 +110,7 @@ open class Note(val idHex: String) { fun replyLevel(cachedLevels: MutableMap = mutableMapOf()): Int { val replyTo = replyTo - if (replyTo == null || replyTo.isEmpty()) { + if (event is RepostEvent || event is GenericRepostEvent || replyTo == null || replyTo.isEmpty()) { return 0 } @@ -372,7 +373,7 @@ open class Note(val idHex: String) { } 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 { @@ -400,6 +401,42 @@ open class Note(val idHex: String) { 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 fun live(): NoteLiveSet { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/ThreadAssembler.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/ThreadAssembler.kt index 8b6c2b4ef..0a9af62d0 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/ThreadAssembler.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/ThreadAssembler.kt @@ -2,6 +2,8 @@ package com.vitorpamplona.amethyst.model import com.vitorpamplona.amethyst.service.checkNotInMainThread 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.measureTimedValue @@ -10,6 +12,8 @@ class ThreadAssembler { private fun searchRoot(note: Note, testedNotes: MutableSet = mutableSetOf()): 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) val markedAsRoot = note.event?.tags()?.firstOrNull { it[0] == "e" && it.size > 3 && it[3] == "root" }?.getOrNull(1) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt index 8a2ceecee..cffc86f28 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt @@ -88,6 +88,7 @@ object NostrAccountDataSource : NostrDataSource("AccountData") { PollNoteEvent.kind, ReactionEvent.kind, RepostEvent.kind, + GenericRepostEvent.kind, ReportEvent.kind, LnZapEvent.kind, LnZapPaymentResponseEvent.kind, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrHomeDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrHomeDataSource.kt index 8ec3716f4..c87cf68ba 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrHomeDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrHomeDataSource.kt @@ -3,6 +3,7 @@ package com.vitorpamplona.amethyst.service import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.UserState 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.LongTextNoteEvent import com.vitorpamplona.amethyst.service.model.PinListEvent @@ -59,7 +60,16 @@ object NostrHomeDataSource : NostrDataSource("HomeFeed") { return TypedFilter( types = setOf(FeedType.FOLLOWS), 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, limit = 400, since = latestEOSEs.users[account.userProfile()]?.followList?.get(account.defaultHomeFollowList)?.relayList diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleEventDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleEventDataSource.kt index 3a73ad66e..11dcf4584 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleEventDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleEventDataSource.kt @@ -26,7 +26,7 @@ object NostrSingleEventDataSource : NostrDataSource("SingleEventFeed") { filter = JsonFilter( kinds = listOf( TextNoteEvent.kind, LongTextNoteEvent.kind, - ReactionEvent.kind, RepostEvent.kind, ReportEvent.kind, + ReactionEvent.kind, RepostEvent.kind, GenericRepostEvent.kind, ReportEvent.kind, LnZapEvent.kind, LnZapRequestEvent.kind, BadgeAwardEvent.kind, BadgeDefinitionEvent.kind, BadgeProfilesEvent.kind, PollNoteEvent.kind, AudioTrackEvent.kind, PinListEvent.kind, @@ -77,6 +77,7 @@ object NostrSingleEventDataSource : NostrDataSource("SingleEventFeed") { LongTextNoteEvent.kind, ReactionEvent.kind, RepostEvent.kind, + GenericRepostEvent.kind, ReportEvent.kind, LnZapEvent.kind, LnZapRequestEvent.kind, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrUserProfileDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrUserProfileDataSource.kt index 30ee695fb..6ccc6f1ee 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrUserProfileDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrUserProfileDataSource.kt @@ -28,7 +28,16 @@ object NostrUserProfileDataSource : NostrDataSource("UserProfileFeed") { TypedFilter( types = COMMON_FEED_TYPES, 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), limit = 200 ) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/Event.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/Event.kt index 96091d565..22aa0c440 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/Event.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/Event.kt @@ -245,6 +245,7 @@ open class Event( FileHeaderEvent.kind -> FileHeaderEvent(id, pubKey, createdAt, tags, content, sig) FileStorageEvent.kind -> FileStorageEvent(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) LiveActivitiesEvent.kind -> LiveActivitiesEvent(id, pubKey, createdAt, tags, content, sig) LnZapEvent.kind -> LnZapEvent(id, pubKey, createdAt, tags, content, sig) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/GenericRepostEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/GenericRepostEvent.kt new file mode 100644 index 000000000..a1994c825 --- /dev/null +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/GenericRepostEvent.kt @@ -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>, + 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> = 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()) + } + } +} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/RepostEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/RepostEvent.kt index 6b2053afd..e289423c7 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/RepostEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/RepostEvent.kt @@ -36,7 +36,7 @@ class RepostEvent( val replyToAuthor = listOf("p", boostedPost.pubKey()) val pubKey = Utils.pubkeyCreate(privateKey).toHexKey() - var tags: List> = boostedPost.tags().plus(listOf(replyToPost, replyToAuthor)) + var tags: List> = listOf(replyToPost, replyToAuthor) if (boostedPost is AddressableEvent) { tags = tags + listOf(listOf("a", boostedPost.address().toTag())) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/HomeNewThreadFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/HomeNewThreadFeedFilter.kt index 055be1534..e8b5861d1 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/HomeNewThreadFeedFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/HomeNewThreadFeedFilter.kt @@ -4,6 +4,7 @@ import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note 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.LongTextNoteEvent import com.vitorpamplona.amethyst.service.model.PollNoteEvent @@ -26,19 +27,24 @@ class HomeNewThreadFeedFilter(val account: Account) : AdditiveFeedFilter() val followingKeySet = account.selectedUsersFollowList(account.defaultHomeFollowList) ?: emptySet() val followingTagSet = account.selectedTagsFollowList(account.defaultHomeFollowList) ?: emptySet() + val oneHr = 60 * 60 + return collection .asSequence() .filter { it -> 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))) && // && account.isAcceptable(it) // This filter follows only. No need to check if acceptable it.author?.let { !account.isHidden(it.pubkeyHex) } ?: true && it.isNewThread() && ( - noteEvent !is RepostEvent || // 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 )) // + (noteEvent !is RepostEvent && noteEvent !is GenericRepostEvent) || // not a repost + ( + 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() } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/NotificationFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/NotificationFeedFilter.kt index d6a976fb8..8ab920e5c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/NotificationFeedFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/NotificationFeedFilter.kt @@ -61,7 +61,7 @@ class NotificationFeedFilter(val account: Account) : AdditiveFeedFilter() 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 } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt index 0ae62b49d..cf3bdf6b0 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt @@ -98,6 +98,7 @@ import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent import com.vitorpamplona.amethyst.service.model.EventInterface import com.vitorpamplona.amethyst.service.model.FileHeaderEvent 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.LiveActivitiesEvent import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent @@ -520,7 +521,7 @@ private fun RenderNoteWithReactions( val showSecondRow by remember { 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( baseNote = baseNote, showReactionDetail = notBoostedNorQuote, @@ -562,7 +563,7 @@ private fun RenderNoteWithReactions( nav = nav ) } else { - if (!isBoostedNote && baseNote.event !is RepostEvent) { + if (!isBoostedNote && baseNote.event !is RepostEvent && baseNote.event !is GenericRepostEvent) { Spacer(modifier = Modifier.height(10.dp)) } } @@ -648,6 +649,10 @@ private fun RenderNoteRow( RenderRepost(baseNote, backgroundColor, accountViewModel, nav) } + is GenericRepostEvent -> { + RenderRepost(baseNote, backgroundColor, accountViewModel, nav) + } + is ReportEvent -> { RenderReport(baseNote, backgroundColor, accountViewModel, nav) } @@ -1421,7 +1426,7 @@ private fun RenderReaction( } @Composable -private fun RenderRepost( +fun RenderRepost( note: Note, backgroundColor: MutableState, accountViewModel: AccountViewModel, @@ -1754,7 +1759,7 @@ private fun FirstUserInfoRow( NoteUsernameDisplay(baseNote, remember { Modifier.weight(1f) }) } - if (eventNote is RepostEvent) { + if (eventNote is RepostEvent || eventNote is GenericRepostEvent) { Text( " ${stringResource(id = R.string.boosted)}", fontWeight = FontWeight.Bold, @@ -1828,7 +1833,7 @@ private fun DrawAuthorImages(baseNote: Note, accountViewModel: AccountViewModel, Box(modifier = modifier, contentAlignment = Alignment.BottomEnd) { NoteAuthorPicture(baseNote, nav, accountViewModel, 55.dp) - if (baseNote.event is RepostEvent) { + if (baseNote.event is RepostEvent || baseNote.event is GenericRepostEvent) { 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 { baseNote.replyTo?.lastOrNull() } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserReactionsRow.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserReactionsRow.kt index 422e3676a..096b479fb 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserReactionsRow.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserReactionsRow.kt @@ -40,6 +40,7 @@ import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User 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.ReactionEvent import com.vitorpamplona.amethyst.service.model.RepostEvent @@ -178,9 +179,9 @@ class UserReactionsViewModel(val account: Account) : ViewModel() { reactions[netDate] = (reactions[netDate] ?: 0) + 1 takenIntoAccount.add(noteEvent.id()) } - } else if (noteEvent is RepostEvent) { - if (noteEvent.isTaggedUser(currentUser) && noteEvent.pubKey != currentUser) { - val netDate = formatDate(noteEvent.createdAt) + } else if (noteEvent is RepostEvent || noteEvent is GenericRepostEvent) { + if (noteEvent.isTaggedUser(currentUser) && noteEvent.pubKey() != currentUser) { + val netDate = formatDate(noteEvent.createdAt()) boosts[netDate] = (boosts[netDate] ?: 0) + 1 takenIntoAccount.add(noteEvent.id()) } @@ -229,9 +230,9 @@ class UserReactionsViewModel(val account: Account) : ViewModel() { takenIntoAccount.add(noteEvent.id()) hasNewElements = true } - } else if (noteEvent is RepostEvent) { - if (noteEvent.isTaggedUser(currentUser) && noteEvent.pubKey != currentUser) { - val netDate = formatDate(noteEvent.createdAt) + } else if (noteEvent is RepostEvent || noteEvent is GenericRepostEvent) { + if (noteEvent.isTaggedUser(currentUser) && noteEvent.pubKey() != currentUser) { + val netDate = formatDate(noteEvent.createdAt()) boosts[netDate] = (boosts[netDate] ?: 0) + 1 takenIntoAccount.add(noteEvent.id()) hasNewElements = true diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt index 2ef773e10..aec5b2a38 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt @@ -15,6 +15,7 @@ import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.amethyst.service.model.BadgeAwardEvent import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent 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.PrivateDmEvent import com.vitorpamplona.amethyst.service.model.ReactionEvent @@ -162,7 +163,7 @@ open class CardFeedViewModel(val localFilter: FeedFilter) : ViewModel() { val boostsPerEvent = mutableMapOf>() notes - .filter { it.event is RepostEvent } + .filter { it.event is RepostEvent || it.event is GenericRepostEvent } .forEach { val boostedPost = it.replyTo?.lastOrNull() { it.event !is ChannelMetadataEvent && it.event !is ChannelCreateEvent } if (boostedPost != null) { @@ -197,7 +198,7 @@ open class CardFeedViewModel(val localFilter: FeedFilter) : 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) { MessageSetCard(it) } else if (it.event is BadgeAwardEvent) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt index 2f7e6d657..199d3a347 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt @@ -57,12 +57,14 @@ import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.service.model.AppDefinitionEvent import com.vitorpamplona.amethyst.service.model.AudioTrackEvent 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.LongTextNoteEvent import com.vitorpamplona.amethyst.service.model.PeopleListEvent import com.vitorpamplona.amethyst.service.model.PinListEvent import com.vitorpamplona.amethyst.service.model.PollNoteEvent 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.note.* import com.vitorpamplona.amethyst.ui.note.BadgeDisplay @@ -401,6 +403,8 @@ fun NoteMaster( accountViewModel, nav ) + } else if (noteEvent is RepostEvent || noteEvent is GenericRepostEvent) { + RenderRepost(baseNote, backgroundColor, accountViewModel, nav) } else if (noteEvent is PollNoteEvent) { val canPreview = note.author == account.userProfile() || (note.author?.let { account.userProfile().isFollowingCached(it) } ?: true) || diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ThreadScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ThreadScreen.kt index d368a5908..06ea14cec 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ThreadScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ThreadScreen.kt @@ -2,14 +2,12 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalLifecycleOwner -import androidx.compose.ui.unit.dp import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.viewmodel.compose.viewModel @@ -55,9 +53,7 @@ fun ThreadScreen(noteId: String?, accountViewModel: AccountViewModel, nav: (Stri } Column(Modifier.fillMaxHeight()) { - Column( - modifier = Modifier.padding(vertical = 0.dp) - ) { + Column() { ThreadFeedView(noteId, feedViewModel, accountViewModel, nav) } }