Bringing TextNoteEvent from NostrPostr to Amethyst's codebase and Restructuring Addressable Notes.
This commit is contained in:
@@ -34,7 +34,7 @@ import nostr.postr.events.DeletionEvent
|
|||||||
import nostr.postr.events.Event
|
import nostr.postr.events.Event
|
||||||
import nostr.postr.events.MetadataEvent
|
import nostr.postr.events.MetadataEvent
|
||||||
import nostr.postr.events.PrivateDmEvent
|
import nostr.postr.events.PrivateDmEvent
|
||||||
import nostr.postr.events.TextNoteEvent
|
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
||||||
import nostr.postr.toHex
|
import nostr.postr.toHex
|
||||||
|
|
||||||
val DefaultChannels = setOf(
|
val DefaultChannels = setOf(
|
||||||
@@ -290,18 +290,20 @@ class Account(
|
|||||||
|
|
||||||
val repliesToHex = replyTo?.map { it.idHex }
|
val repliesToHex = replyTo?.map { it.idHex }
|
||||||
val mentionsHex = mentions?.map { it.pubkeyHex }
|
val mentionsHex = mentions?.map { it.pubkeyHex }
|
||||||
|
val addressesHex = replyTo?.mapNotNull { it.address() }
|
||||||
|
|
||||||
val signedEvent = TextNoteEvent.create(
|
val signedEvent = TextNoteEvent.create(
|
||||||
msg = message,
|
msg = message,
|
||||||
replyTos = repliesToHex,
|
replyTos = repliesToHex,
|
||||||
mentions = mentionsHex,
|
mentions = mentionsHex,
|
||||||
|
addresses = addressesHex,
|
||||||
privateKey = loggedIn.privKey!!
|
privateKey = loggedIn.privKey!!
|
||||||
)
|
)
|
||||||
Client.send(signedEvent)
|
Client.send(signedEvent)
|
||||||
LocalCache.consume(signedEvent)
|
LocalCache.consume(signedEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendChannelMeesage(message: String, toChannel: String, replyingTo: Note? = null, mentions: List<User>?) {
|
fun sendChannelMessage(message: String, toChannel: String, replyingTo: Note? = null, mentions: List<User>?) {
|
||||||
if (!isWriteable()) return
|
if (!isWriteable()) return
|
||||||
|
|
||||||
val repliesToHex = listOfNotNull(replyingTo?.idHex).ifEmpty { null }
|
val repliesToHex = listOfNotNull(replyingTo?.idHex).ifEmpty { null }
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ fun parseDirtyWordForKey(mightBeAKey: String): DirtyKeyInfo? {
|
|||||||
return DirtyKeyInfo("note", keyB32.bechToBytes().toHexKey(), restOfWord)
|
return DirtyKeyInfo("note", keyB32.bechToBytes().toHexKey(), restOfWord)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ import nostr.postr.events.Event
|
|||||||
import nostr.postr.events.MetadataEvent
|
import nostr.postr.events.MetadataEvent
|
||||||
import nostr.postr.events.PrivateDmEvent
|
import nostr.postr.events.PrivateDmEvent
|
||||||
import nostr.postr.events.RecommendRelayEvent
|
import nostr.postr.events.RecommendRelayEvent
|
||||||
import nostr.postr.events.TextNoteEvent
|
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
||||||
import nostr.postr.toHex
|
import nostr.postr.toHex
|
||||||
import nostr.postr.toNpub
|
import nostr.postr.toNpub
|
||||||
|
|
||||||
@@ -212,7 +212,7 @@ object LocalCache {
|
|||||||
//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.addLongFormNote(note)
|
||||||
|
|
||||||
// Adds notifications to users.
|
// Adds notifications to users.
|
||||||
mentions.forEach {
|
mentions.forEach {
|
||||||
@@ -222,11 +222,6 @@ object LocalCache {
|
|||||||
it.author?.addTaggedPost(note)
|
it.author?.addTaggedPost(note)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Counts the replies
|
|
||||||
replyTo.forEach {
|
|
||||||
it.addReply(note)
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshObservers()
|
refreshObservers()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -492,7 +487,6 @@ object LocalCache {
|
|||||||
|
|
||||||
val note = getOrCreateNote(event.id.toHex())
|
val note = getOrCreateNote(event.id.toHex())
|
||||||
oldChannel.addNote(note)
|
oldChannel.addNote(note)
|
||||||
note.channel = oldChannel
|
|
||||||
note.loadEvent(event, author, emptyList(), emptyList())
|
note.loadEvent(event, author, emptyList(), emptyList())
|
||||||
|
|
||||||
refreshObservers()
|
refreshObservers()
|
||||||
@@ -514,7 +508,6 @@ object LocalCache {
|
|||||||
|
|
||||||
val note = getOrCreateNote(event.id.toHex())
|
val note = getOrCreateNote(event.id.toHex())
|
||||||
oldChannel.addNote(note)
|
oldChannel.addNote(note)
|
||||||
note.channel = oldChannel
|
|
||||||
note.loadEvent(event, author, emptyList(), emptyList())
|
note.loadEvent(event, author, emptyList(), emptyList())
|
||||||
|
|
||||||
refreshObservers()
|
refreshObservers()
|
||||||
@@ -553,7 +546,6 @@ object LocalCache {
|
|||||||
.mapNotNull { checkGetOrCreateNote(it) }
|
.mapNotNull { checkGetOrCreateNote(it) }
|
||||||
.filter { it.event !is ChannelCreateEvent }
|
.filter { it.event !is ChannelCreateEvent }
|
||||||
|
|
||||||
note.channel = channel
|
|
||||||
note.loadEvent(event, author, mentions, replyTo)
|
note.loadEvent(event, author, mentions, 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)}")
|
||||||
@@ -658,8 +650,9 @@ object LocalCache {
|
|||||||
|
|
||||||
fun findNotesStartingWith(text: String): List<Note> {
|
fun findNotesStartingWith(text: String): List<Note> {
|
||||||
return notes.values.filter {
|
return notes.values.filter {
|
||||||
(it.event is TextNoteEvent && it.event?.content?.contains(text, true) ?: false)
|
(it.event is TextNoteEvent && it.event?.content?.contains(text, true) ?: false)
|
||||||
|| (it.event is ChannelMessageEvent && it.event?.content?.contains(text, true) ?: false)
|
|| (it.event is ChannelMessageEvent && it.event?.content?.contains(text, true) ?: false)
|
||||||
|
|| (it.event is LongTextNoteEvent && it.event?.content?.contains(text, true) ?: false)
|
||||||
|| it.idHex.startsWith(text, true)
|
|| it.idHex.startsWith(text, true)
|
||||||
|| it.idNote().startsWith(text, true)
|
|| it.idNote().startsWith(text, true)
|
||||||
}
|
}
|
||||||
@@ -745,7 +738,7 @@ object LocalCache {
|
|||||||
|
|
||||||
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()) + (users[it]?.longFormNotes?.values?.flatten() ?: emptySet())
|
||||||
}.flatten()
|
}.flatten()
|
||||||
|
|
||||||
account.hiddenUsers.forEach {
|
account.hiddenUsers.forEach {
|
||||||
@@ -754,6 +747,7 @@ object LocalCache {
|
|||||||
|
|
||||||
toBeRemoved.forEach {
|
toBeRemoved.forEach {
|
||||||
it.author?.removeNote(it)
|
it.author?.removeNote(it)
|
||||||
|
it.author?.removeLongFormNote(it)
|
||||||
|
|
||||||
// reverts the add
|
// reverts the add
|
||||||
it.mentions?.forEach { user ->
|
it.mentions?.forEach { user ->
|
||||||
|
|||||||
@@ -2,7 +2,11 @@ package com.vitorpamplona.amethyst.model
|
|||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import com.vitorpamplona.amethyst.service.NostrSingleEventDataSource
|
import com.vitorpamplona.amethyst.service.NostrSingleEventDataSource
|
||||||
|
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.LnZapEvent
|
import com.vitorpamplona.amethyst.service.model.LnZapEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent
|
||||||
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
|
||||||
import com.vitorpamplona.amethyst.service.relays.Relay
|
import com.vitorpamplona.amethyst.service.relays.Relay
|
||||||
@@ -23,6 +27,7 @@ import kotlinx.coroutines.delay
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import nostr.postr.events.Event
|
import nostr.postr.events.Event
|
||||||
|
import nostr.postr.toHex
|
||||||
|
|
||||||
val tagSearch = Pattern.compile("(?:\\s|\\A)\\#\\[([0-9]+)\\]")
|
val tagSearch = Pattern.compile("(?:\\s|\\A)\\#\\[([0-9]+)\\]")
|
||||||
|
|
||||||
@@ -49,14 +54,22 @@ class Note(val idHex: String) {
|
|||||||
var relays = setOf<String>()
|
var relays = setOf<String>()
|
||||||
private set
|
private set
|
||||||
|
|
||||||
var channel: Channel? = null
|
|
||||||
|
|
||||||
var lastReactionsDownloadTime: Long? = null
|
var lastReactionsDownloadTime: Long? = null
|
||||||
|
|
||||||
fun id() = Hex.decode(idHex)
|
fun id() = Hex.decode(idHex)
|
||||||
fun idNote() = id().toNote()
|
fun idNote() = id().toNote()
|
||||||
fun idDisplayNote() = idNote().toShortenHex()
|
fun idDisplayNote() = idNote().toShortenHex()
|
||||||
|
|
||||||
|
fun channel(): Channel? {
|
||||||
|
val channelHex = (event as? ChannelMessageEvent)?.channel ?:
|
||||||
|
(event as? ChannelMetadataEvent)?.channel ?:
|
||||||
|
(event as? ChannelCreateEvent)?.let { idHex }
|
||||||
|
|
||||||
|
return channelHex?.let { LocalCache.checkGetOrCreateChannel(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun address() = (event as? LongTextNoteEvent)?.address
|
||||||
|
|
||||||
fun loadEvent(event: Event, author: User, mentions: List<User>, replyTo: List<Note>) {
|
fun loadEvent(event: Event, author: User, mentions: List<User>, replyTo: List<Note>) {
|
||||||
this.event = event
|
this.event = event
|
||||||
this.author = author
|
this.author = author
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.vitorpamplona.amethyst.model
|
|||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import com.vitorpamplona.amethyst.service.NostrSingleUserDataSource
|
import com.vitorpamplona.amethyst.service.NostrSingleUserDataSource
|
||||||
import com.vitorpamplona.amethyst.service.model.LnZapEvent
|
import com.vitorpamplona.amethyst.service.model.LnZapEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.ReportEvent
|
import com.vitorpamplona.amethyst.service.model.ReportEvent
|
||||||
import com.vitorpamplona.amethyst.service.relays.Relay
|
import com.vitorpamplona.amethyst.service.relays.Relay
|
||||||
import com.vitorpamplona.amethyst.ui.note.toShortenHex
|
import com.vitorpamplona.amethyst.ui.note.toShortenHex
|
||||||
@@ -37,6 +38,8 @@ class User(val pubkeyHex: String) {
|
|||||||
|
|
||||||
var notes = setOf<Note>()
|
var notes = setOf<Note>()
|
||||||
private set
|
private set
|
||||||
|
var longFormNotes = mapOf<String, Set<Note>>()
|
||||||
|
private set
|
||||||
var taggedPosts = setOf<Note>()
|
var taggedPosts = setOf<Note>()
|
||||||
private set
|
private set
|
||||||
|
|
||||||
@@ -142,8 +145,27 @@ class User(val pubkeyHex: String) {
|
|||||||
notes = notes - note
|
notes = notes - note
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun addLongFormNote(note: Note) {
|
||||||
|
val address = (note.event as LongTextNoteEvent).address
|
||||||
|
|
||||||
|
if (address in longFormNotes.keys) {
|
||||||
|
if (longFormNotes[address]?.contains(note) == false)
|
||||||
|
longFormNotes = longFormNotes + Pair(address, (longFormNotes[address] ?: emptySet()) + note)
|
||||||
|
} else {
|
||||||
|
longFormNotes = longFormNotes + Pair(address, setOf(note))
|
||||||
|
// No need for Listener yet
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeLongFormNote(note: Note) {
|
||||||
|
val address = (note.event as LongTextNoteEvent).address ?: return
|
||||||
|
|
||||||
|
longFormNotes = longFormNotes - address
|
||||||
|
}
|
||||||
|
|
||||||
fun clearNotes() {
|
fun clearNotes() {
|
||||||
notes = setOf<Note>()
|
notes = setOf<Note>()
|
||||||
|
longFormNotes = mapOf<String, Set<Note>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addReport(note: Note) {
|
fun addReport(note: Note) {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import com.vitorpamplona.amethyst.service.relays.TypedFilter
|
|||||||
import nostr.postr.JsonFilter
|
import nostr.postr.JsonFilter
|
||||||
import nostr.postr.events.ContactListEvent
|
import nostr.postr.events.ContactListEvent
|
||||||
import nostr.postr.events.MetadataEvent
|
import nostr.postr.events.MetadataEvent
|
||||||
import nostr.postr.events.TextNoteEvent
|
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
||||||
|
|
||||||
object NostrAccountDataSource: NostrDataSource("AccountData") {
|
object NostrAccountDataSource: NostrDataSource("AccountData") {
|
||||||
lateinit var account: Account
|
lateinit var account: Account
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import nostr.postr.events.Event
|
|||||||
import nostr.postr.events.MetadataEvent
|
import nostr.postr.events.MetadataEvent
|
||||||
import nostr.postr.events.PrivateDmEvent
|
import nostr.postr.events.PrivateDmEvent
|
||||||
import nostr.postr.events.RecommendRelayEvent
|
import nostr.postr.events.RecommendRelayEvent
|
||||||
import nostr.postr.events.TextNoteEvent
|
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
||||||
|
|
||||||
abstract class NostrDataSource(val debugName: String) {
|
abstract class NostrDataSource(val debugName: String) {
|
||||||
private var subscriptions = mapOf<String, Subscription>()
|
private var subscriptions = mapOf<String, Subscription>()
|
||||||
@@ -63,12 +63,14 @@ abstract class NostrDataSource(val debugName: String) {
|
|||||||
try {
|
try {
|
||||||
when (event) {
|
when (event) {
|
||||||
is MetadataEvent -> LocalCache.consume(event)
|
is MetadataEvent -> LocalCache.consume(event)
|
||||||
is TextNoteEvent -> LocalCache.consume(event, relay)
|
//is TextNoteEvent -> LocalCache.consume(event, relay) overrides default TextNote
|
||||||
is RecommendRelayEvent -> LocalCache.consume(event)
|
is RecommendRelayEvent -> LocalCache.consume(event)
|
||||||
is ContactListEvent -> LocalCache.consume(event)
|
is ContactListEvent -> LocalCache.consume(event)
|
||||||
is PrivateDmEvent -> LocalCache.consume(event, relay)
|
is PrivateDmEvent -> LocalCache.consume(event, relay)
|
||||||
is DeletionEvent -> LocalCache.consume(event)
|
is DeletionEvent -> LocalCache.consume(event)
|
||||||
else -> when (event.kind) {
|
else -> when (event.kind) {
|
||||||
|
TextNoteEvent.kind -> LocalCache.consume(TextNoteEvent(event.id, event.pubKey, event.createdAt, event.tags, event.content, event.sig), relay)
|
||||||
|
|
||||||
RepostEvent.kind -> {
|
RepostEvent.kind -> {
|
||||||
val repostEvent = RepostEvent(event.id, event.pubKey, event.createdAt, event.tags, event.content, event.sig)
|
val repostEvent = RepostEvent(event.id, event.pubKey, event.createdAt, event.tags, event.content, event.sig)
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent
|
|||||||
import com.vitorpamplona.amethyst.service.relays.FeedType
|
import com.vitorpamplona.amethyst.service.relays.FeedType
|
||||||
import com.vitorpamplona.amethyst.service.relays.TypedFilter
|
import com.vitorpamplona.amethyst.service.relays.TypedFilter
|
||||||
import nostr.postr.JsonFilter
|
import nostr.postr.JsonFilter
|
||||||
import nostr.postr.events.TextNoteEvent
|
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
||||||
|
|
||||||
object NostrGlobalDataSource: NostrDataSource("GlobalFeed") {
|
object NostrGlobalDataSource: NostrDataSource("GlobalFeed") {
|
||||||
fun createGlobalFilter() = TypedFilter(
|
fun createGlobalFilter() = TypedFilter(
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import nostr.postr.JsonFilter
|
import nostr.postr.JsonFilter
|
||||||
import nostr.postr.events.TextNoteEvent
|
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
||||||
|
|
||||||
object NostrHomeDataSource: NostrDataSource("HomeFeed") {
|
object NostrHomeDataSource: NostrDataSource("HomeFeed") {
|
||||||
lateinit var account: Account
|
lateinit var account: Account
|
||||||
|
|||||||
@@ -14,11 +14,37 @@ import com.vitorpamplona.amethyst.service.relays.FeedType
|
|||||||
import com.vitorpamplona.amethyst.service.relays.TypedFilter
|
import com.vitorpamplona.amethyst.service.relays.TypedFilter
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import nostr.postr.JsonFilter
|
import nostr.postr.JsonFilter
|
||||||
import nostr.postr.events.TextNoteEvent
|
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
||||||
|
|
||||||
object NostrSingleEventDataSource: NostrDataSource("SingleEventFeed") {
|
object NostrSingleEventDataSource: NostrDataSource("SingleEventFeed") {
|
||||||
private var eventsToWatch = setOf<String>()
|
private var eventsToWatch = setOf<String>()
|
||||||
|
|
||||||
|
private fun createAddressFilter(): List<TypedFilter>? {
|
||||||
|
val addressesToWatch = eventsToWatch.map { LocalCache.getOrCreateNote(it) }.filter { it.address() != null }
|
||||||
|
|
||||||
|
if (addressesToWatch.isEmpty()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val now = Date().time / 1000
|
||||||
|
|
||||||
|
return addressesToWatch.filter {
|
||||||
|
val lastTime = it.lastReactionsDownloadTime
|
||||||
|
lastTime == null || lastTime < (now - 10)
|
||||||
|
}.map {
|
||||||
|
TypedFilter(
|
||||||
|
types = FeedType.values().toSet(),
|
||||||
|
filter = JsonFilter(
|
||||||
|
kinds = listOf(
|
||||||
|
TextNoteEvent.kind, LongTextNoteEvent.kind, ReactionEvent.kind, RepostEvent.kind, ReportEvent.kind, LnZapEvent.kind, LnZapRequestEvent.kind
|
||||||
|
),
|
||||||
|
tags = mapOf("a" to listOf(it.address()!!)),
|
||||||
|
since = it.lastReactionsDownloadTime
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun createRepliesAndReactionsFilter(): List<TypedFilter>? {
|
private fun createRepliesAndReactionsFilter(): List<TypedFilter>? {
|
||||||
val reactionsToWatch = eventsToWatch.map { LocalCache.getOrCreateNote(it) }
|
val reactionsToWatch = eventsToWatch.map { LocalCache.getOrCreateNote(it) }
|
||||||
|
|
||||||
@@ -91,8 +117,9 @@ object NostrSingleEventDataSource: NostrDataSource("SingleEventFeed") {
|
|||||||
override fun updateChannelFilters() {
|
override fun updateChannelFilters() {
|
||||||
val reactions = createRepliesAndReactionsFilter()
|
val reactions = createRepliesAndReactionsFilter()
|
||||||
val missing = createLoadEventsIfNotLoadedFilter()
|
val missing = createLoadEventsIfNotLoadedFilter()
|
||||||
|
val addresses = createAddressFilter()
|
||||||
|
|
||||||
singleEventChannel.typedFilters = listOfNotNull(reactions, missing).flatten().ifEmpty { null }
|
singleEventChannel.typedFilters = listOfNotNull(reactions, missing, addresses).flatten().ifEmpty { null }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun add(eventId: String) {
|
fun add(eventId: String) {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import com.vitorpamplona.amethyst.service.relays.TypedFilter
|
|||||||
import nostr.postr.JsonFilter
|
import nostr.postr.JsonFilter
|
||||||
import nostr.postr.events.ContactListEvent
|
import nostr.postr.events.ContactListEvent
|
||||||
import nostr.postr.events.MetadataEvent
|
import nostr.postr.events.MetadataEvent
|
||||||
import nostr.postr.events.TextNoteEvent
|
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
||||||
|
|
||||||
object NostrUserProfileDataSource: NostrDataSource("UserProfileFeed") {
|
object NostrUserProfileDataSource: NostrDataSource("UserProfileFeed") {
|
||||||
var user: User? = null
|
var user: User? = null
|
||||||
|
|||||||
@@ -28,11 +28,15 @@ class LnZapRequestEvent (
|
|||||||
fun create(originalNote: Event, relays: Set<String>, privateKey: ByteArray, createdAt: Long = Date().time / 1000): LnZapRequestEvent {
|
fun create(originalNote: Event, relays: Set<String>, privateKey: ByteArray, createdAt: Long = Date().time / 1000): LnZapRequestEvent {
|
||||||
val content = ""
|
val content = ""
|
||||||
val pubKey = Utils.pubkeyCreate(privateKey)
|
val pubKey = Utils.pubkeyCreate(privateKey)
|
||||||
val tags = listOf(
|
var tags = listOf(
|
||||||
listOf("e", originalNote.id.toHex()),
|
listOf("e", originalNote.id.toHex()),
|
||||||
listOf("p", originalNote.pubKey.toHex()),
|
listOf("p", originalNote.pubKey.toHex()),
|
||||||
listOf("relays") + relays
|
listOf("relays") + relays
|
||||||
)
|
)
|
||||||
|
if (originalNote is LongTextNoteEvent) {
|
||||||
|
tags = tags + listOf( listOf("a", originalNote.address) )
|
||||||
|
}
|
||||||
|
|
||||||
val id = generateId(pubKey, createdAt, kind, tags, content)
|
val id = generateId(pubKey, createdAt, kind, tags, content)
|
||||||
val sig = Utils.sign(id, privateKey)
|
val sig = Utils.sign(id, privateKey)
|
||||||
return LnZapRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
return LnZapRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.vitorpamplona.amethyst.service.model
|
package com.vitorpamplona.amethyst.service.model
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import nostr.postr.events.Event
|
import nostr.postr.events.Event
|
||||||
@@ -20,11 +21,16 @@ class LongTextNoteEvent(
|
|||||||
@Transient val summary: String?
|
@Transient val summary: String?
|
||||||
@Transient val publishedAt: Long?
|
@Transient val publishedAt: Long?
|
||||||
@Transient val topics: List<String>
|
@Transient val topics: List<String>
|
||||||
|
@Transient val address: String
|
||||||
|
@Transient val dTag: String?
|
||||||
|
|
||||||
init {
|
init {
|
||||||
replyTos = tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) }
|
replyTos = tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) }
|
||||||
mentions = tags.filter { it.firstOrNull() == "p" }.mapNotNull { it.getOrNull(1) }
|
mentions = tags.filter { it.firstOrNull() == "p" }.mapNotNull { it.getOrNull(1) }
|
||||||
|
|
||||||
|
dTag = tags.filter { it.firstOrNull() == "d" }.mapNotNull { it.getOrNull(1) }.firstOrNull()
|
||||||
|
|
||||||
|
address = tags.filter { it.firstOrNull() == "a" }.mapNotNull { it.getOrNull(1) }.firstOrNull() ?: "$kind:${pubKey.toHexKey()}:$dTag"
|
||||||
topics = tags.filter { it.firstOrNull() == "t" }.mapNotNull { it.getOrNull(1) }
|
topics = tags.filter { it.firstOrNull() == "t" }.mapNotNull { it.getOrNull(1) }
|
||||||
title = tags.filter { it.firstOrNull() == "title" }.mapNotNull { it.getOrNull(1) }.firstOrNull()
|
title = tags.filter { it.firstOrNull() == "title" }.mapNotNull { it.getOrNull(1) }.firstOrNull()
|
||||||
image = tags.filter { it.firstOrNull() == "image" }.mapNotNull { it.getOrNull(1) }.firstOrNull()
|
image = tags.filter { it.firstOrNull() == "image" }.mapNotNull { it.getOrNull(1) }.firstOrNull()
|
||||||
|
|||||||
@@ -35,7 +35,12 @@ class ReactionEvent (
|
|||||||
|
|
||||||
fun create(content: String, originalNote: Event, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ReactionEvent {
|
fun create(content: String, originalNote: Event, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ReactionEvent {
|
||||||
val pubKey = Utils.pubkeyCreate(privateKey)
|
val pubKey = Utils.pubkeyCreate(privateKey)
|
||||||
val tags = listOf( listOf("e", originalNote.id.toHex()), listOf("p", originalNote.pubKey.toHex()))
|
|
||||||
|
var tags = listOf( listOf("e", originalNote.id.toHex()), listOf("p", originalNote.pubKey.toHex()))
|
||||||
|
if (originalNote is LongTextNoteEvent) {
|
||||||
|
tags = tags + listOf( listOf("a", originalNote.address) )
|
||||||
|
}
|
||||||
|
|
||||||
val id = generateId(pubKey, createdAt, kind, tags, content)
|
val id = generateId(pubKey, createdAt, kind, tags, content)
|
||||||
val sig = Utils.sign(id, privateKey)
|
val sig = Utils.sign(id, privateKey)
|
||||||
return ReactionEvent(id, pubKey, createdAt, tags, content, sig)
|
return ReactionEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|||||||
@@ -60,7 +60,12 @@ class ReportEvent (
|
|||||||
val reportAuthorTag = listOf("p", reportedPost.pubKey.toHex(), type.name.toLowerCase())
|
val reportAuthorTag = listOf("p", reportedPost.pubKey.toHex(), type.name.toLowerCase())
|
||||||
|
|
||||||
val pubKey = Utils.pubkeyCreate(privateKey)
|
val pubKey = Utils.pubkeyCreate(privateKey)
|
||||||
val tags:List<List<String>> = listOf(reportPostTag, reportAuthorTag)
|
var tags:List<List<String>> = listOf(reportPostTag, reportAuthorTag)
|
||||||
|
|
||||||
|
if (reportedPost is LongTextNoteEvent) {
|
||||||
|
tags = tags + listOf( listOf("a", reportedPost.address) )
|
||||||
|
}
|
||||||
|
|
||||||
val id = generateId(pubKey, createdAt, kind, tags, content)
|
val id = generateId(pubKey, createdAt, kind, tags, content)
|
||||||
val sig = Utils.sign(id, privateKey)
|
val sig = Utils.sign(id, privateKey)
|
||||||
return ReportEvent(id, pubKey, createdAt, tags, content, sig)
|
return ReportEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|||||||
@@ -40,7 +40,12 @@ class RepostEvent (
|
|||||||
val replyToAuthor = listOf("p", boostedPost.pubKey.toHex())
|
val replyToAuthor = listOf("p", boostedPost.pubKey.toHex())
|
||||||
|
|
||||||
val pubKey = Utils.pubkeyCreate(privateKey)
|
val pubKey = Utils.pubkeyCreate(privateKey)
|
||||||
val tags:List<List<String>> = boostedPost.tags.plus(listOf(replyToPost, replyToAuthor))
|
var tags:List<List<String>> = boostedPost.tags.plus(listOf(replyToPost, replyToAuthor))
|
||||||
|
|
||||||
|
if (boostedPost is LongTextNoteEvent) {
|
||||||
|
tags = tags + listOf( listOf("a", boostedPost.address) )
|
||||||
|
}
|
||||||
|
|
||||||
val id = generateId(pubKey, createdAt, kind, tags, content)
|
val id = generateId(pubKey, createdAt, kind, tags, content)
|
||||||
val sig = Utils.sign(id, privateKey)
|
val sig = Utils.sign(id, privateKey)
|
||||||
return RepostEvent(id, pubKey, createdAt, tags, content, sig)
|
return RepostEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package com.vitorpamplona.amethyst.service.model
|
||||||
|
|
||||||
|
import java.util.Date
|
||||||
|
import nostr.postr.Utils
|
||||||
|
import nostr.postr.events.Event
|
||||||
|
|
||||||
|
class TextNoteEvent(
|
||||||
|
id: ByteArray,
|
||||||
|
pubKey: ByteArray,
|
||||||
|
createdAt: Long,
|
||||||
|
tags: List<List<String>>,
|
||||||
|
content: String,
|
||||||
|
sig: ByteArray
|
||||||
|
): Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||||
|
@Transient val replyTos: List<String>
|
||||||
|
@Transient val mentions: List<String>
|
||||||
|
@Transient val longFormAddress: List<String>
|
||||||
|
|
||||||
|
init {
|
||||||
|
longFormAddress = tags.filter { it.firstOrNull() == "a" }.mapNotNull { it.getOrNull(1) }
|
||||||
|
replyTos = tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) }
|
||||||
|
mentions = tags.filter { it.firstOrNull() == "p" }.mapNotNull { it.getOrNull(1) }
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val kind = 1
|
||||||
|
|
||||||
|
fun create(msg: String, replyTos: List<String>?, mentions: List<String>?, addresses: List<String>?, privateKey: ByteArray, createdAt: Long = Date().time / 1000): TextNoteEvent {
|
||||||
|
val pubKey = Utils.pubkeyCreate(privateKey)
|
||||||
|
val tags = mutableListOf<List<String>>()
|
||||||
|
replyTos?.forEach {
|
||||||
|
tags.add(listOf("e", it))
|
||||||
|
}
|
||||||
|
mentions?.forEach {
|
||||||
|
tags.add(listOf("p", it))
|
||||||
|
}
|
||||||
|
addresses?.forEach {
|
||||||
|
tags.add(listOf("a", it))
|
||||||
|
}
|
||||||
|
val id = generateId(pubKey, createdAt, kind, tags, msg)
|
||||||
|
val sig = Utils.sign(id, privateKey)
|
||||||
|
return TextNoteEvent(id, pubKey, createdAt, tags, msg, sig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,7 +41,7 @@ import com.vitorpamplona.amethyst.ui.navigation.UploadFromGallery
|
|||||||
import com.vitorpamplona.amethyst.ui.note.ReplyInformation
|
import com.vitorpamplona.amethyst.ui.note.ReplyInformation
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.UserLine
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.UserLine
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import nostr.postr.events.TextNoteEvent
|
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
||||||
|
|
||||||
|
|
||||||
@OptIn(ExperimentalComposeUiApi::class)
|
@OptIn(ExperimentalComposeUiApi::class)
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ class NewPostViewModel: ViewModel() {
|
|||||||
} else {
|
} else {
|
||||||
this.mentions = currentMentions.plus(replyUser)
|
this.mentions = currentMentions.plus(replyUser)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,12 +67,12 @@ class NewPostViewModel: ViewModel() {
|
|||||||
|
|
||||||
fun tagIndex(user: User): Int {
|
fun tagIndex(user: User): Int {
|
||||||
// Postr Events assembles replies before mentions in the tag order
|
// Postr Events assembles replies before mentions in the tag order
|
||||||
return (if (originalNote?.channel != null) 1 else 0) + (replyTos?.size ?: 0) + (mentions?.indexOf(user) ?: 0)
|
return (if (originalNote?.channel() != null) 1 else 0) + (replyTos?.size ?: 0) + (mentions?.indexOf(user) ?: 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun tagIndex(note: Note): Int {
|
fun tagIndex(note: Note): Int {
|
||||||
// Postr Events assembles replies before mentions in the tag order
|
// Postr Events assembles replies before mentions in the tag order
|
||||||
return (if (originalNote?.channel != null) 1 else 0) + (replyTos?.indexOf(note) ?: 0)
|
return (if (originalNote?.channel() != null) 1 else 0) + (replyTos?.indexOf(note) ?: 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendPost() {
|
fun sendPost() {
|
||||||
@@ -108,8 +107,8 @@ class NewPostViewModel: ViewModel() {
|
|||||||
}.joinToString(" ")
|
}.joinToString(" ")
|
||||||
}.joinToString("\n")
|
}.joinToString("\n")
|
||||||
|
|
||||||
if (originalNote?.channel != null) {
|
if (originalNote?.channel() != null) {
|
||||||
account?.sendChannelMeesage(newMessage, originalNote!!.channel!!.idHex, originalNote!!, mentions)
|
account?.sendChannelMessage(newMessage, originalNote!!.channel()!!.idHex, originalNote!!, mentions)
|
||||||
} else {
|
} else {
|
||||||
account?.sendPost(newMessage, replyTos, mentions)
|
account?.sendPost(newMessage, replyTos, mentions)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,10 +42,10 @@ fun ClickableRoute(
|
|||||||
onClick = { navController.navigate("Channel/${nip19.hex}") },
|
onClick = { navController.navigate("Channel/${nip19.hex}") },
|
||||||
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary)
|
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary)
|
||||||
)
|
)
|
||||||
} else if (note.channel != null) {
|
} else if (note.channel() != null) {
|
||||||
ClickableText(
|
ClickableText(
|
||||||
text = AnnotatedString("@${note.channel?.toBestDisplayName()} "),
|
text = AnnotatedString("@${note.channel()?.toBestDisplayName()} "),
|
||||||
onClick = { navController.navigate("Channel/${note.channel?.idHex}") },
|
onClick = { navController.navigate("Channel/${note.channel()?.idHex}") },
|
||||||
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary)
|
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -26,20 +26,20 @@ import androidx.compose.ui.graphics.Color
|
|||||||
import androidx.compose.ui.graphics.compositeOver
|
import androidx.compose.ui.graphics.compositeOver
|
||||||
import androidx.compose.ui.text.Paragraph
|
import androidx.compose.ui.text.Paragraph
|
||||||
import androidx.compose.ui.text.SpanStyle
|
import androidx.compose.ui.text.SpanStyle
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextDecoration
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
import androidx.compose.ui.text.style.TextDirection
|
import androidx.compose.ui.text.style.TextDirection
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import com.google.accompanist.flowlayout.FlowRow
|
import com.google.accompanist.flowlayout.FlowRow
|
||||||
import com.halilibo.richtext.markdown.Markdown
|
import com.halilibo.richtext.markdown.Markdown
|
||||||
import com.halilibo.richtext.markdown.MarkdownParseOptions
|
import com.halilibo.richtext.markdown.MarkdownParseOptions
|
||||||
import com.halilibo.richtext.ui.RichText
|
|
||||||
import com.halilibo.richtext.ui.RichTextStyle
|
import com.halilibo.richtext.ui.RichTextStyle
|
||||||
import com.halilibo.richtext.ui.currentRichTextStyle
|
|
||||||
import com.halilibo.richtext.ui.material.MaterialRichText
|
import com.halilibo.richtext.ui.material.MaterialRichText
|
||||||
import com.halilibo.richtext.ui.resolveDefaults
|
import com.halilibo.richtext.ui.resolveDefaults
|
||||||
import com.halilibo.richtext.ui.string.RichTextString
|
|
||||||
import com.halilibo.richtext.ui.string.RichTextStringStyle
|
|
||||||
import com.vitorpamplona.amethyst.service.lnurl.LnInvoiceUtil
|
import com.vitorpamplona.amethyst.service.lnurl.LnInvoiceUtil
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.service.Nip19
|
import com.vitorpamplona.amethyst.service.Nip19
|
||||||
@@ -81,20 +81,47 @@ fun RichTextViewer(
|
|||||||
navController: NavController,
|
navController: NavController,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
val myMarkDownStyle = RichTextStyle().resolveDefaults().copy(
|
||||||
|
codeBlockStyle = RichTextStyle().resolveDefaults().codeBlockStyle?.copy(
|
||||||
|
textStyle = TextStyle(
|
||||||
|
fontFamily = FontFamily.Monospace,
|
||||||
|
fontSize = 14.sp
|
||||||
|
),
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(0.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(shape = RoundedCornerShape(15.dp))
|
||||||
|
.border(
|
||||||
|
1.dp,
|
||||||
|
MaterialTheme.colors.onSurface.copy(alpha = 0.12f),
|
||||||
|
RoundedCornerShape(15.dp)
|
||||||
|
)
|
||||||
|
.background(MaterialTheme.colors.onSurface.copy(alpha = 0.05f).compositeOver(backgroundColor))
|
||||||
|
),
|
||||||
|
stringStyle = RichTextStyle().resolveDefaults().stringStyle?.copy(
|
||||||
|
linkStyle = SpanStyle(
|
||||||
|
textDecoration = TextDecoration.Underline,
|
||||||
|
color = MaterialTheme.colors.primary
|
||||||
|
),
|
||||||
|
codeStyle = SpanStyle(
|
||||||
|
fontFamily = FontFamily.Monospace,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
background = MaterialTheme.colors.onSurface.copy(alpha = 0.22f).compositeOver(backgroundColor)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
Column(modifier = modifier.animateContentSize()) {
|
Column(modifier = modifier.animateContentSize()) {
|
||||||
|
|
||||||
if (content.startsWith("# ") || content.contains("##") || content.contains("```")) {
|
if ( content.startsWith("# ")
|
||||||
var richTextStyle by remember { mutableStateOf(RichTextStyle().resolveDefaults()) }
|
|| content.contains("##")
|
||||||
|
|| content.contains("**")
|
||||||
|
|| content.contains("__")
|
||||||
|
|| content.contains("```")
|
||||||
|
) {
|
||||||
|
|
||||||
MaterialRichText(
|
MaterialRichText(
|
||||||
style = RichTextStyle().resolveDefaults().copy(
|
style = myMarkDownStyle,
|
||||||
stringStyle = richTextStyle.stringStyle?.copy(
|
|
||||||
linkStyle = SpanStyle(
|
|
||||||
textDecoration = TextDecoration.Underline,
|
|
||||||
color = MaterialTheme.colors.primary
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
) {
|
) {
|
||||||
Markdown(
|
Markdown(
|
||||||
content = content,
|
content = content,
|
||||||
|
|||||||
@@ -4,7 +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.ChannelMessageEvent
|
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
||||||
import nostr.postr.events.TextNoteEvent
|
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
||||||
|
|
||||||
object GlobalFeedFilter: FeedFilter<Note>() {
|
object GlobalFeedFilter: FeedFilter<Note>() {
|
||||||
lateinit var account: Account
|
lateinit var account: Account
|
||||||
|
|||||||
@@ -4,7 +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.RepostEvent
|
import com.vitorpamplona.amethyst.service.model.RepostEvent
|
||||||
import nostr.postr.events.TextNoteEvent
|
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
||||||
|
|
||||||
object HomeConversationsFeedFilter: FeedFilter<Note>() {
|
object HomeConversationsFeedFilter: FeedFilter<Note>() {
|
||||||
lateinit var account: Account
|
lateinit var account: Account
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import com.vitorpamplona.amethyst.model.LocalCache
|
|||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent
|
import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.RepostEvent
|
import com.vitorpamplona.amethyst.service.model.RepostEvent
|
||||||
import nostr.postr.events.TextNoteEvent
|
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
||||||
|
|
||||||
object HomeNewThreadFeedFilter: FeedFilter<Note>() {
|
object HomeNewThreadFeedFilter: FeedFilter<Note>() {
|
||||||
lateinit var account: Account
|
lateinit var account: Account
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
|
|||||||
import com.vitorpamplona.amethyst.service.model.LnZapRequestEvent
|
import com.vitorpamplona.amethyst.service.model.LnZapRequestEvent
|
||||||
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
|
||||||
import nostr.postr.events.TextNoteEvent
|
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
||||||
|
|
||||||
object NotificationFeedFilter: FeedFilter<Note>() {
|
object NotificationFeedFilter: FeedFilter<Note>() {
|
||||||
lateinit var account: Account
|
lateinit var account: Account
|
||||||
|
|||||||
+1
-1
@@ -15,7 +15,7 @@ object UserProfileNewThreadFeedFilter: FeedFilter<Note>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun feed(): List<Note> {
|
override fun feed(): List<Note> {
|
||||||
return user?.notes
|
return user?.notes?.plus(user?.longFormNotes?.values?.flatten() ?: emptySet())
|
||||||
?.filter { account?.isAcceptable(it) == true && it.isNewThread() }
|
?.filter { account?.isAcceptable(it) == true && it.isNewThread() }
|
||||||
?.sortedBy { it.event?.createdAt }
|
?.sortedBy { it.event?.createdAt }
|
||||||
?.reversed()
|
?.reversed()
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ private fun messagesHasNewItems(account: Account, cache: NotificationCache, cont
|
|||||||
ChatroomListKnownFeedFilter.account = account
|
ChatroomListKnownFeedFilter.account = account
|
||||||
|
|
||||||
val note = ChatroomListKnownFeedFilter.feed().firstOrNull {
|
val note = ChatroomListKnownFeedFilter.feed().firstOrNull {
|
||||||
it.event?.createdAt != null && it.channel == null && it.author != account.userProfile()
|
it.event?.createdAt != null && it.channel() == null && it.author != account.userProfile()
|
||||||
} ?: return false
|
} ?: return false
|
||||||
|
|
||||||
val lastTime = cache.load("Room/${note.author?.pubkeyHex}", context)
|
val lastTime = cache.load("Room/${note.author?.pubkeyHex}", context)
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ fun BoostSetCompose(boostSetCard: BoostSetCard, isInnerNote: Boolean = false, ro
|
|||||||
launchSingleTop = true
|
launchSingleTop = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
note.channel?.let {
|
note.channel()?.let {
|
||||||
navController.navigate("Channel/${it.idHex}")
|
navController.navigate("Channel/${it.idHex}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,11 +63,11 @@ fun ChatroomCompose(baseNote: Note, accountViewModel: AccountViewModel, navContr
|
|||||||
|
|
||||||
if (note?.event == null) {
|
if (note?.event == null) {
|
||||||
BlankNote(Modifier)
|
BlankNote(Modifier)
|
||||||
} else if (note.channel != null) {
|
} else if (note.channel() != null) {
|
||||||
val authorState by note.author!!.live().metadata.observeAsState()
|
val authorState by note.author!!.live().metadata.observeAsState()
|
||||||
val author = authorState?.user
|
val author = authorState?.user
|
||||||
|
|
||||||
val channelState by note.channel!!.live.observeAsState()
|
val channelState by note.channel()!!.live.observeAsState()
|
||||||
val channel = channelState?.channel
|
val channel = channelState?.channel
|
||||||
|
|
||||||
val noteEvent = note.event
|
val noteEvent = note.event
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ fun LikeSetCompose(likeSetCard: LikeSetCard, modifier: Modifier = Modifier, isIn
|
|||||||
launchSingleTop = true
|
launchSingleTop = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
note.channel?.let {
|
note.channel()?.let {
|
||||||
navController.navigate("Channel/${it.idHex}")
|
navController.navigate("Channel/${it.idHex}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, modifier: Modifier = Modifier, r
|
|||||||
launchSingleTop = true
|
launchSingleTop = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
note.channel?.let {
|
note.channel()?.let {
|
||||||
navController.navigate("Channel/${it.idHex}")
|
navController.navigate("Channel/${it.idHex}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import com.google.accompanist.flowlayout.FlowRow
|
|||||||
import com.vitorpamplona.amethyst.NotificationCache
|
import com.vitorpamplona.amethyst.NotificationCache
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.RoboHashCache
|
import com.vitorpamplona.amethyst.RoboHashCache
|
||||||
|
import com.vitorpamplona.amethyst.model.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.model.ChannelMessageEvent
|
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
||||||
@@ -51,7 +52,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
|||||||
import com.vitorpamplona.amethyst.ui.theme.Following
|
import com.vitorpamplona.amethyst.ui.theme.Following
|
||||||
import kotlin.time.ExperimentalTime
|
import kotlin.time.ExperimentalTime
|
||||||
import nostr.postr.events.PrivateDmEvent
|
import nostr.postr.events.PrivateDmEvent
|
||||||
import nostr.postr.events.TextNoteEvent
|
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -133,7 +134,7 @@ fun NoteCompose(
|
|||||||
launchSingleTop = true
|
launchSingleTop = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
note.channel?.let {
|
note.channel()?.let {
|
||||||
navController.navigate("Channel/${it.idHex}")
|
navController.navigate("Channel/${it.idHex}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -175,7 +176,7 @@ fun NoteCompose(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// boosted picture
|
// boosted picture
|
||||||
val baseChannel = note.channel
|
val baseChannel = note.channel()
|
||||||
if (noteEvent is ChannelMessageEvent && baseChannel != null) {
|
if (noteEvent is ChannelMessageEvent && baseChannel != null) {
|
||||||
val channelState by baseChannel.live.observeAsState()
|
val channelState by baseChannel.live.observeAsState()
|
||||||
val channel = channelState?.channel
|
val channel = channelState?.channel
|
||||||
@@ -293,7 +294,7 @@ fun NoteCompose(
|
|||||||
} else if (noteEvent is ChannelMessageEvent && (note.replyTo != null || note.mentions != null)) {
|
} else if (noteEvent is ChannelMessageEvent && (note.replyTo != null || note.mentions != null)) {
|
||||||
val sortedMentions = note.mentions?.toSet()?.sortedBy { account.userProfile().isFollowing(it) }
|
val sortedMentions = note.mentions?.toSet()?.sortedBy { account.userProfile().isFollowing(it) }
|
||||||
|
|
||||||
note.channel?.let {
|
note.channel()?.let {
|
||||||
ReplyInformationChannel(note.replyTo, sortedMentions, it, navController)
|
ReplyInformationChannel(note.replyTo, sortedMentions, it, navController)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ fun ZapSetCompose(zapSetCard: ZapSetCard, modifier: Modifier = Modifier, isInner
|
|||||||
launchSingleTop = true
|
launchSingleTop = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
note.channel?.let {
|
note.channel()?.let {
|
||||||
navController.navigate("Channel/${it.idHex}")
|
navController.navigate("Channel/${it.idHex}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -268,7 +268,7 @@ fun NoteMaster(baseNote: Note,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (noteEvent is LongTextNoteEvent) {
|
if (noteEvent is LongTextNoteEvent) {
|
||||||
Row(modifier = Modifier.padding(horizontal = 12.dp)) {
|
Row(modifier = Modifier.padding(start = 12.dp, end = 12.dp, top = 10.dp)) {
|
||||||
Column {
|
Column {
|
||||||
noteEvent.image?.let {
|
noteEvent.image?.let {
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
@@ -295,7 +295,10 @@ fun NoteMaster(baseNote: Note,
|
|||||||
|
|
||||||
noteEvent.summary?.let {
|
noteEvent.summary?.let {
|
||||||
Text(
|
Text(
|
||||||
text = it
|
text = it,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(top = 10.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ fun ChannelScreen(channelId: String?, accountViewModel: AccountViewModel, accoun
|
|||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
PostButton(
|
PostButton(
|
||||||
onPost = {
|
onPost = {
|
||||||
account.sendChannelMeesage(newPost.value.text, channel.idHex, replyTo.value, null)
|
account.sendChannelMessage(newPost.value.text, channel.idHex, replyTo.value, null)
|
||||||
newPost.value = TextFieldValue("")
|
newPost.value = TextFieldValue("")
|
||||||
replyTo.value = null
|
replyTo.value = null
|
||||||
feedViewModel.refresh() // Don't wait a full second before updating
|
feedViewModel.refresh() // Don't wait a full second before updating
|
||||||
|
|||||||
Reference in New Issue
Block a user