mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-04-02 08:58:23 +02:00
Create EventInterface
This commit is contained in:
parent
12570d3e26
commit
b8937594bc
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="Android Studio default JDK" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="Android Studio default JDK" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
|
@ -159,9 +159,10 @@ dependencies {
|
||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10'
|
||||
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
testImplementation "io.mockk:mockk:1.13.4"
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
||||
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_ui_version"
|
||||
debugImplementation "androidx.compose.ui:ui-tooling:$compose_ui_version"
|
||||
debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_ui_version"
|
||||
}
|
||||
}
|
||||
|
@ -415,7 +415,7 @@ class Account(
|
||||
|
||||
event.plainContent(loggedIn.privKey!!, pubkeyToUse.toByteArray())
|
||||
} else {
|
||||
event?.content
|
||||
event?.content()
|
||||
}
|
||||
}
|
||||
|
||||
@ -590,4 +590,4 @@ class AccountLiveData(private val account: Account): LiveData<AccountState>(Acco
|
||||
}
|
||||
}
|
||||
|
||||
class AccountState(val account: Account)
|
||||
class AccountState(val account: Account)
|
||||
|
@ -192,7 +192,7 @@ object LocalCache {
|
||||
|
||||
note.loadEvent(event, author, mentions, replyTo)
|
||||
|
||||
//Log.d("TN", "New Note (${notes.size},${users.size}) ${note.author?.toBestDisplayName()} ${note.event?.content?.take(100)} ${formattedDateTime(event.createdAt)}")
|
||||
//Log.d("TN", "New Note (${notes.size},${users.size}) ${note.author?.toBestDisplayName()} ${note.event?.content()?.take(100)} ${formattedDateTime(event.createdAt)}")
|
||||
|
||||
// Prepares user's profile view.
|
||||
author.addNote(note)
|
||||
@ -223,7 +223,7 @@ object LocalCache {
|
||||
}
|
||||
|
||||
// Already processed this event.
|
||||
if (note.event?.id == event.id) return
|
||||
if (note.event?.id() == event.id) return
|
||||
|
||||
if (antiSpam.isSpam(event)) {
|
||||
relay?.let {
|
||||
@ -594,7 +594,7 @@ object LocalCache {
|
||||
|
||||
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)}")
|
||||
|
||||
// Adds notifications to users.
|
||||
mentions.forEach {
|
||||
@ -700,8 +700,8 @@ object LocalCache {
|
||||
|
||||
fun findNotesStartingWith(text: String): List<Note> {
|
||||
return notes.values.filter {
|
||||
(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 TextNoteEvent && it.event?.content()?.contains(text, true) ?: false)
|
||||
|| (it.event is ChannelMessageEvent && it.event?.content()?.contains(text, true) ?: false)
|
||||
|| it.idHex.startsWith(text, true)
|
||||
|| it.idNote().startsWith(text, true)
|
||||
} + addressables.values.filter {
|
||||
@ -770,7 +770,7 @@ object LocalCache {
|
||||
|
||||
val toBeRemoved = notes
|
||||
.filter {
|
||||
(it.value.author == null || it.value.author!! !in followSet) && it.value.event?.kind == TextNoteEvent.kind && it.value.liveSet?.isInUse() != true
|
||||
(it.value.author == null || it.value.author!! !in followSet) && it.value.event?.kind() == TextNoteEvent.kind && it.value.liveSet?.isInUse() != true
|
||||
}
|
||||
|
||||
toBeRemoved.forEach {
|
||||
@ -862,4 +862,4 @@ class LocalCacheLiveData(val cache: LocalCache): LiveData<LocalCacheState>(Local
|
||||
|
||||
class LocalCacheState(val cache: LocalCache) {
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -2,14 +2,7 @@ package com.vitorpamplona.amethyst.model
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import com.vitorpamplona.amethyst.service.NostrSingleEventDataSource
|
||||
import com.vitorpamplona.amethyst.service.model.ATag
|
||||
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.LongTextNoteEvent
|
||||
import com.vitorpamplona.amethyst.service.model.ReactionEvent
|
||||
import com.vitorpamplona.amethyst.service.model.RepostEvent
|
||||
import com.vitorpamplona.amethyst.service.model.*
|
||||
import com.vitorpamplona.amethyst.service.relays.Relay
|
||||
import com.vitorpamplona.amethyst.ui.note.toShortenHex
|
||||
import fr.acinq.secp256k1.Hex
|
||||
@ -27,7 +20,6 @@ import kotlinx.coroutines.NonCancellable
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import com.vitorpamplona.amethyst.service.model.Event
|
||||
|
||||
val tagSearch = Pattern.compile("(?:\\s|\\A)\\#\\[([0-9]+)\\]")
|
||||
|
||||
@ -36,13 +28,13 @@ class AddressableNote(val address: ATag): Note(address.toNAddr()) {
|
||||
override fun idNote() = address.toNAddr()
|
||||
override fun idDisplayNote() = idNote().toShortenHex()
|
||||
override fun address() = address
|
||||
override fun createdAt() = (event as? LongTextNoteEvent)?.publishedAt() ?: event?.createdAt
|
||||
override fun createdAt() = (event as? LongTextNoteEvent)?.publishedAt() ?: event?.createdAt()
|
||||
}
|
||||
|
||||
open class Note(val idHex: String) {
|
||||
// These fields are only available after the Text Note event is received.
|
||||
// They are immutable after that.
|
||||
var event: Event? = null
|
||||
var event: EventInterface? = null
|
||||
var author: User? = null
|
||||
var mentions: List<User>? = null
|
||||
var replyTo: List<Note>? = null
|
||||
@ -79,7 +71,7 @@ open class Note(val idHex: String) {
|
||||
|
||||
open fun address() = (event as? LongTextNoteEvent)?.address()
|
||||
|
||||
open fun createdAt() = event?.createdAt
|
||||
open fun createdAt() = event?.createdAt()
|
||||
|
||||
fun loadEvent(event: Event, author: User, mentions: List<User>, replyTo: List<Note>) {
|
||||
this.event = event
|
||||
@ -256,11 +248,11 @@ open class Note(val idHex: String) {
|
||||
}
|
||||
|
||||
fun directlyCiteUsersHex(): Set<HexKey> {
|
||||
val matcher = tagSearch.matcher(event?.content ?: "")
|
||||
val matcher = tagSearch.matcher(event?.content() ?: "")
|
||||
val returningList = mutableSetOf<String>()
|
||||
while (matcher.find()) {
|
||||
try {
|
||||
val tag = matcher.group(1)?.let { event?.tags?.get(it.toInt()) }
|
||||
val tag = matcher.group(1)?.let { event?.tags()?.get(it.toInt()) }
|
||||
if (tag != null && tag[0] == "p") {
|
||||
returningList.add(tag[1])
|
||||
}
|
||||
@ -272,11 +264,11 @@ open class Note(val idHex: String) {
|
||||
}
|
||||
|
||||
fun directlyCiteUsers(): Set<User> {
|
||||
val matcher = tagSearch.matcher(event?.content ?: "")
|
||||
val matcher = tagSearch.matcher(event?.content() ?: "")
|
||||
val returningList = mutableSetOf<User>()
|
||||
while (matcher.find()) {
|
||||
try {
|
||||
val tag = matcher.group(1)?.let { event?.tags?.get(it.toInt()) }
|
||||
val tag = matcher.group(1)?.let { event?.tags()?.get(it.toInt()) }
|
||||
if (tag != null && tag[0] == "p") {
|
||||
LocalCache.checkGetOrCreateUser(tag[1])?.let {
|
||||
returningList.add(it)
|
||||
@ -309,7 +301,7 @@ open class Note(val idHex: String) {
|
||||
}
|
||||
|
||||
fun reactedBy(loggedIn: User, content: String): List<Note> {
|
||||
return reactions.filter { it.author == loggedIn && it.event?.content == content }
|
||||
return reactions.filter { it.author == loggedIn && it.event?.content() == content }
|
||||
}
|
||||
|
||||
fun hasBoostedInTheLast5Minutes(loggedIn: User): Boolean {
|
||||
|
@ -11,7 +11,7 @@ class ThreadAssembler {
|
||||
|
||||
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)
|
||||
if (markedAsRoot != null) return LocalCache.checkGetOrCreateNote(markedAsRoot)
|
||||
|
||||
val hasNoReplyTo = note.replyTo?.firstOrNull { it.replyTo?.isEmpty() == true }
|
||||
@ -73,4 +73,4 @@ class ThreadAssembler {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ object UrlCachedPreviewer {
|
||||
}
|
||||
|
||||
fun preloadPreviewsFor(note: Note) {
|
||||
note.event?.content?.let {
|
||||
note.event?.content()?.let {
|
||||
findUrlsInMessage(it).forEach {
|
||||
val removedParamsFromUrl = it.split("?")[0].lowercase()
|
||||
if (imageExtension.matcher(removedParamsFromUrl).matches()) {
|
||||
|
@ -61,6 +61,8 @@ class User(val pubkeyHex: String) {
|
||||
fun pubkeyNpub() = pubkey().toNpub()
|
||||
fun pubkeyDisplayHex() = pubkeyNpub().toShortenHex()
|
||||
|
||||
override fun toString(): String = pubkeyHex
|
||||
|
||||
fun toBestDisplayName(): String {
|
||||
return bestDisplayName() ?: bestUsername() ?: pubkeyDisplayHex()
|
||||
}
|
||||
|
@ -1,25 +1,16 @@
|
||||
package com.vitorpamplona.amethyst.service.model
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonDeserializationContext
|
||||
import com.google.gson.JsonDeserializer
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonPrimitive
|
||||
import com.google.gson.JsonSerializationContext
|
||||
import com.google.gson.JsonSerializer
|
||||
import com.google.gson.*
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import com.vitorpamplona.amethyst.model.HexKey
|
||||
import com.vitorpamplona.amethyst.model.toHexKey
|
||||
import fr.acinq.secp256k1.Hex
|
||||
import fr.acinq.secp256k1.Secp256k1
|
||||
import java.lang.reflect.Type
|
||||
import java.security.MessageDigest
|
||||
import java.util.Date
|
||||
import nostr.postr.Utils
|
||||
import nostr.postr.toHex
|
||||
import java.lang.reflect.Type
|
||||
import java.security.MessageDigest
|
||||
import java.util.*
|
||||
|
||||
open class Event(
|
||||
val id: HexKey,
|
||||
@ -29,34 +20,27 @@ open class Event(
|
||||
val tags: List<List<String>>,
|
||||
val content: String,
|
||||
val sig: HexKey
|
||||
) {
|
||||
fun toJson(): String = gson.toJson(this)
|
||||
): EventInterface {
|
||||
override fun id(): HexKey = id
|
||||
|
||||
fun generateId(): String {
|
||||
val rawEvent = listOf(
|
||||
0,
|
||||
pubKey,
|
||||
createdAt,
|
||||
kind,
|
||||
tags,
|
||||
content
|
||||
)
|
||||
override fun pubKey(): HexKey = pubKey
|
||||
|
||||
// GSON decided to hardcode these replacements.
|
||||
// They break Nostr's hash check.
|
||||
// These lines revert their code.
|
||||
// https://github.com/google/gson/issues/2295
|
||||
val rawEventJson = gson.toJson(rawEvent)
|
||||
.replace("\\u2028", "\u2028")
|
||||
.replace("\\u2029", "\u2029")
|
||||
override fun createdAt(): Long = createdAt
|
||||
|
||||
return sha256.digest(rawEventJson.toByteArray()).toHexKey()
|
||||
}
|
||||
override fun kind(): Int = kind
|
||||
|
||||
override fun tags(): List<List<String>> = tags
|
||||
|
||||
override fun content(): String = content
|
||||
|
||||
override fun sig(): HexKey = sig
|
||||
|
||||
override fun toJson(): String = gson.toJson(this)
|
||||
|
||||
/**
|
||||
* Checks if the ID is correct and then if the pubKey's secret key signed the event.
|
||||
*/
|
||||
fun checkSignature() {
|
||||
override fun checkSignature() {
|
||||
if (!id.contentEquals(generateId())) {
|
||||
throw Exception(
|
||||
"""|Unexpected ID.
|
||||
@ -70,18 +54,29 @@ open class Event(
|
||||
}
|
||||
}
|
||||
|
||||
fun hasValidSignature(): Boolean {
|
||||
override fun hasValidSignature(): Boolean {
|
||||
if (!id.contentEquals(generateId())) {
|
||||
return false
|
||||
}
|
||||
if (!Secp256k1.get().verifySchnorr(Hex.decode(sig), Hex.decode(id), Hex.decode(pubKey))) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
return secp256k1.verifySchnorr(Hex.decode(sig), Hex.decode(id), Hex.decode(pubKey))
|
||||
}
|
||||
|
||||
class EventDeserializer : JsonDeserializer<Event> {
|
||||
private fun generateId(): String {
|
||||
val rawEvent = listOf(0, pubKey, createdAt, kind, tags, content)
|
||||
|
||||
// GSON decided to hardcode these replacements.
|
||||
// They break Nostr's hash check.
|
||||
// These lines revert their code.
|
||||
// https://github.com/google/gson/issues/2295
|
||||
val rawEventJson = gson.toJson(rawEvent)
|
||||
.replace("\\u2028", "\u2028")
|
||||
.replace("\\u2029", "\u2029")
|
||||
|
||||
return sha256.digest(rawEventJson.toByteArray()).toHexKey()
|
||||
}
|
||||
|
||||
private class EventDeserializer : JsonDeserializer<Event> {
|
||||
override fun deserialize(
|
||||
json: JsonElement,
|
||||
typeOfT: Type?,
|
||||
@ -102,7 +97,7 @@ open class Event(
|
||||
}
|
||||
}
|
||||
|
||||
class EventSerializer : JsonSerializer<Event> {
|
||||
private class EventSerializer : JsonSerializer<Event> {
|
||||
override fun serialize(
|
||||
src: Event,
|
||||
typeOfSrc: Type?,
|
||||
@ -128,7 +123,7 @@ open class Event(
|
||||
}
|
||||
}
|
||||
|
||||
class ByteArrayDeserializer : JsonDeserializer<ByteArray> {
|
||||
private class ByteArrayDeserializer : JsonDeserializer<ByteArray> {
|
||||
override fun deserialize(
|
||||
json: JsonElement,
|
||||
typeOfT: Type?,
|
||||
@ -136,7 +131,7 @@ open class Event(
|
||||
): ByteArray = Hex.decode(json.asString)
|
||||
}
|
||||
|
||||
class ByteArraySerializer : JsonSerializer<ByteArray> {
|
||||
private class ByteArraySerializer : JsonSerializer<ByteArray> {
|
||||
override fun serialize(
|
||||
src: ByteArray,
|
||||
typeOfSrc: Type?,
|
||||
@ -202,4 +197,4 @@ open class Event(
|
||||
return Event(id.toHexKey(), pubKey, createdAt, kind, tags, content, sig)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,25 @@
|
||||
package com.vitorpamplona.amethyst.service.model
|
||||
|
||||
import com.vitorpamplona.amethyst.model.HexKey
|
||||
|
||||
interface EventInterface {
|
||||
fun id(): HexKey
|
||||
|
||||
fun pubKey(): HexKey
|
||||
|
||||
fun createdAt(): Long
|
||||
|
||||
fun kind(): Int
|
||||
|
||||
fun tags(): List<List<String>>
|
||||
|
||||
fun content(): String
|
||||
|
||||
fun sig(): HexKey
|
||||
|
||||
fun toJson(): String
|
||||
|
||||
fun checkSignature()
|
||||
|
||||
fun hasValidSignature(): Boolean
|
||||
}
|
@ -21,12 +21,12 @@ class LnZapRequestEvent (
|
||||
companion object {
|
||||
const val kind = 9734
|
||||
|
||||
fun create(originalNote: Event, relays: Set<String>, privateKey: ByteArray, createdAt: Long = Date().time / 1000): LnZapRequestEvent {
|
||||
fun create(originalNote: EventInterface, relays: Set<String>, privateKey: ByteArray, createdAt: Long = Date().time / 1000): LnZapRequestEvent {
|
||||
val content = ""
|
||||
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
||||
var tags = listOf(
|
||||
listOf("e", originalNote.id),
|
||||
listOf("p", originalNote.pubKey),
|
||||
listOf("e", originalNote.id()),
|
||||
listOf("p", originalNote.pubKey()),
|
||||
listOf("relays") + relays
|
||||
)
|
||||
if (originalNote is LongTextNoteEvent) {
|
||||
@ -84,4 +84,4 @@ class LnZapRequestEvent (
|
||||
]
|
||||
]
|
||||
}
|
||||
*/
|
||||
*/
|
||||
|
@ -22,18 +22,18 @@ class ReactionEvent (
|
||||
companion object {
|
||||
const val kind = 7
|
||||
|
||||
fun createWarning(originalNote: Event, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ReactionEvent {
|
||||
fun createWarning(originalNote: EventInterface, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ReactionEvent {
|
||||
return create("\u26A0\uFE0F", originalNote, privateKey, createdAt)
|
||||
}
|
||||
|
||||
fun createLike(originalNote: Event, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ReactionEvent {
|
||||
fun createLike(originalNote: EventInterface, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ReactionEvent {
|
||||
return create("+", originalNote, privateKey, createdAt)
|
||||
}
|
||||
|
||||
fun create(content: String, originalNote: Event, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ReactionEvent {
|
||||
fun create(content: String, originalNote: EventInterface, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ReactionEvent {
|
||||
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
||||
|
||||
var tags = listOf( listOf("e", originalNote.id), listOf("p", originalNote.pubKey))
|
||||
var tags = listOf( listOf("e", originalNote.id()), listOf("p", originalNote.pubKey()))
|
||||
if (originalNote is LongTextNoteEvent) {
|
||||
tags = tags + listOf( listOf("a", originalNote.address().toTag()) )
|
||||
}
|
||||
@ -43,4 +43,4 @@ class ReactionEvent (
|
||||
return ReactionEvent(id.toHexKey(), pubKey, createdAt, tags, content, sig.toHexKey())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -53,11 +53,11 @@ class ReportEvent (
|
||||
companion object {
|
||||
const val kind = 1984
|
||||
|
||||
fun create(reportedPost: Event, type: ReportType, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ReportEvent {
|
||||
fun create(reportedPost: EventInterface, type: ReportType, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ReportEvent {
|
||||
val content = ""
|
||||
|
||||
val reportPostTag = listOf("e", reportedPost.id, type.name.lowercase())
|
||||
val reportAuthorTag = listOf("p", reportedPost.pubKey, type.name.lowercase())
|
||||
val reportPostTag = listOf("e", reportedPost.id(), type.name.lowercase())
|
||||
val reportAuthorTag = listOf("p", reportedPost.pubKey(), type.name.lowercase())
|
||||
|
||||
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
||||
var tags:List<List<String>> = listOf(reportPostTag, reportAuthorTag)
|
||||
|
@ -29,14 +29,14 @@ class RepostEvent (
|
||||
companion object {
|
||||
const val kind = 6
|
||||
|
||||
fun create(boostedPost: Event, privateKey: ByteArray, createdAt: Long = Date().time / 1000): RepostEvent {
|
||||
fun create(boostedPost: EventInterface, privateKey: ByteArray, createdAt: Long = Date().time / 1000): RepostEvent {
|
||||
val content = boostedPost.toJson()
|
||||
|
||||
val replyToPost = listOf("e", boostedPost.id)
|
||||
val replyToAuthor = listOf("p", boostedPost.pubKey)
|
||||
val replyToPost = listOf("e", boostedPost.id())
|
||||
val replyToAuthor = listOf("p", boostedPost.pubKey())
|
||||
|
||||
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
||||
var 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().toTag()) )
|
||||
@ -47,4 +47,4 @@ class RepostEvent (
|
||||
return RepostEvent(id.toHexKey(), pubKey, createdAt, tags, content, sig.toHexKey())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,16 @@
|
||||
package com.vitorpamplona.amethyst.service.model.zaps
|
||||
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.service.model.LnZapEvent
|
||||
|
||||
object UserZaps {
|
||||
fun groupByUser(zaps: Map<Note, Note?>?): List<Pair<Note, Note>> {
|
||||
if (zaps == null) return emptyList()
|
||||
|
||||
return (zaps
|
||||
.filter { it.value != null }
|
||||
.toList()
|
||||
.sortedBy { (it.second?.event as? LnZapEvent)?.amount }
|
||||
.reversed()) as List<Pair<Note, Note>>
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import com.vitorpamplona.amethyst.service.model.Event
|
||||
import com.vitorpamplona.amethyst.service.model.EventInterface
|
||||
|
||||
/**
|
||||
* The Nostr Client manages multiple personae the user may switch between. Events are received and
|
||||
@ -62,7 +63,7 @@ object Client: RelayPool.Listener {
|
||||
RelayPool.sendFilterOnlyIfDisconnected()
|
||||
}
|
||||
|
||||
fun send(signedEvent: Event) {
|
||||
fun send(signedEvent: EventInterface) {
|
||||
RelayPool.send(signedEvent)
|
||||
}
|
||||
|
||||
@ -146,4 +147,4 @@ object Client: RelayPool.Listener {
|
||||
*/
|
||||
open fun onSendResponse(eventId: String, success: Boolean, message: String, relay: Relay) = Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import android.util.Log
|
||||
import com.google.gson.JsonElement
|
||||
import java.util.Date
|
||||
import com.vitorpamplona.amethyst.service.model.Event
|
||||
import com.vitorpamplona.amethyst.service.model.EventInterface
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
@ -193,7 +194,7 @@ class Relay(
|
||||
}
|
||||
}
|
||||
|
||||
fun send(signedEvent: Event) {
|
||||
fun send(signedEvent: EventInterface) {
|
||||
if (write) {
|
||||
socket?.send("""["EVENT",${signedEvent.toJson()}]""")
|
||||
eventUploadCounter++
|
||||
|
@ -6,6 +6,7 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import com.vitorpamplona.amethyst.service.model.Event
|
||||
import com.vitorpamplona.amethyst.service.model.EventInterface
|
||||
|
||||
/**
|
||||
* RelayPool manages the connection to multiple Relays and lets consumers deal with simple events.
|
||||
@ -54,7 +55,7 @@ object RelayPool: Relay.Listener {
|
||||
relays.forEach { it.sendFilterOnlyIfDisconnected() }
|
||||
}
|
||||
|
||||
fun send(signedEvent: Event) {
|
||||
fun send(signedEvent: EventInterface) {
|
||||
relays.forEach { it.send(signedEvent) }
|
||||
}
|
||||
|
||||
@ -128,4 +129,4 @@ class RelayPoolLiveData(val relays: RelayPool): LiveData<RelayPoolState>(RelayPo
|
||||
}
|
||||
}
|
||||
|
||||
class RelayPoolState(val relays: RelayPool)
|
||||
class RelayPoolState(val relays: RelayPool)
|
||||
|
@ -3,7 +3,7 @@ package com.vitorpamplona.amethyst.ui.dal
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.service.model.LnZapEvent
|
||||
import com.vitorpamplona.amethyst.service.model.zaps.UserZaps
|
||||
|
||||
object UserProfileZapsFeedFilter: FeedFilter<Pair<Note, Note>>() {
|
||||
var user: User? = null
|
||||
@ -13,10 +13,6 @@ object UserProfileZapsFeedFilter: FeedFilter<Pair<Note, Note>>() {
|
||||
}
|
||||
|
||||
override fun feed(): List<Pair<Note, Note>> {
|
||||
return (user?.zaps
|
||||
?.filter { it.value != null }
|
||||
?.toList()
|
||||
?.sortedBy { (it.second?.event as? LnZapEvent)?.amount }
|
||||
?.reversed() ?: emptyList()) as List<Pair<Note, Note>>
|
||||
return UserZaps.groupByUser(user?.zaps)
|
||||
}
|
||||
}
|
||||
|
@ -77,7 +77,7 @@ fun ChatroomCompose(baseNote: Note, accountViewModel: AccountViewModel, navContr
|
||||
} else if (noteEvent is ChannelMetadataEvent) {
|
||||
"${stringResource(R.string.channel_information_changed_to)} "
|
||||
} else {
|
||||
noteEvent?.content
|
||||
noteEvent?.content()
|
||||
}
|
||||
channel?.let { channel ->
|
||||
var hasNewMessages by remember { mutableStateOf<Boolean>(false) }
|
||||
@ -127,7 +127,7 @@ fun ChatroomCompose(baseNote: Note, accountViewModel: AccountViewModel, navContr
|
||||
|
||||
LaunchedEffect(key1 = notificationCache, key2 = note) {
|
||||
noteEvent?.let {
|
||||
hasNewMessages = it.createdAt > notificationCache.cache.load("Room/${userToComposeOn.pubkeyHex}", context)
|
||||
hasNewMessages = it.createdAt() > notificationCache.cache.load("Room/${userToComposeOn.pubkeyHex}", context)
|
||||
}
|
||||
}
|
||||
|
||||
@ -263,4 +263,4 @@ fun NewItemsBubble() {
|
||||
.align(Alignment.Center)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -265,7 +265,7 @@ fun ChatroomMessageCompose(
|
||||
eventContent,
|
||||
canPreview,
|
||||
Modifier,
|
||||
note.event?.tags,
|
||||
note.event?.tags(),
|
||||
backgroundBubbleColor,
|
||||
accountViewModel,
|
||||
navController
|
||||
@ -275,7 +275,7 @@ fun ChatroomMessageCompose(
|
||||
stringResource(R.string.could_not_decrypt_the_message),
|
||||
true,
|
||||
Modifier,
|
||||
note.event?.tags,
|
||||
note.event?.tags(),
|
||||
backgroundBubbleColor,
|
||||
accountViewModel,
|
||||
navController
|
||||
|
@ -370,7 +370,7 @@ fun NoteCompose(
|
||||
eventContent,
|
||||
canPreview = canPreview && !makeItShort,
|
||||
Modifier.fillMaxWidth(),
|
||||
noteEvent.tags,
|
||||
noteEvent.tags(),
|
||||
backgroundColor,
|
||||
accountViewModel,
|
||||
navController
|
||||
@ -724,4 +724,4 @@ fun NoteDropDownMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Unit,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -119,7 +119,7 @@ private fun FeedLoaded(
|
||||
route = "Room/${userToComposeOn.pubkeyHex}"
|
||||
}
|
||||
|
||||
notificationCache.cache.markAsRead(route, it.createdAt, context)
|
||||
notificationCache.cache.markAsRead(route, it.createdAt(), context)
|
||||
}
|
||||
}
|
||||
markAsRead.value = false
|
||||
|
@ -307,7 +307,7 @@ fun NoteMaster(baseNote: Note,
|
||||
|
||||
Row(modifier = Modifier.padding(horizontal = 12.dp)) {
|
||||
Column() {
|
||||
val eventContent = note.event?.content
|
||||
val eventContent = note.event?.content()
|
||||
|
||||
val canPreview = note.author == account.userProfile()
|
||||
|| (note.author?.let { account.userProfile().isFollowing(it) } ?: true )
|
||||
@ -318,7 +318,7 @@ fun NoteMaster(baseNote: Note,
|
||||
eventContent,
|
||||
canPreview,
|
||||
Modifier.fillMaxWidth(),
|
||||
note.event?.tags,
|
||||
note.event?.tags(),
|
||||
MaterialTheme.colors.background,
|
||||
accountViewModel,
|
||||
navController
|
||||
|
@ -0,0 +1,56 @@
|
||||
package com.vitorpamplona.amethyst.service.zaps
|
||||
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.service.model.EventInterface
|
||||
import com.vitorpamplona.amethyst.service.model.zaps.UserZaps
|
||||
import io.mockk.*
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
|
||||
class UserZapsTest {
|
||||
@Test
|
||||
fun nothing() {
|
||||
Assert.assertEquals(1, 1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun user_without_zaps() {
|
||||
val actual = UserZaps.groupByUser(zaps = null)
|
||||
|
||||
Assert.assertEquals(emptyList<Pair<Note, Note>>(), actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun group_by_user_with_just_one_user() {
|
||||
val u1 = mockk<Note>()
|
||||
val z1 = mockk<Note>()
|
||||
val z2 = mockk<Note>()
|
||||
val zaps: Map<Note, Note?> = mapOf(u1 to z1, u1 to z2)
|
||||
val actual = UserZaps.groupByUser(zaps)
|
||||
|
||||
Assert.assertEquals(listOf(Pair(u1, z2)), actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun group_by_user() {
|
||||
// FIXME: not working yet...
|
||||
// IDEA:
|
||||
// [ (u1 -> z1) (u1 -> z2) (u2 -> z3) ]
|
||||
// [ (u1 -> z1 + z2) (u2 -> z3)]
|
||||
val u1 = mockk<Note>()
|
||||
val u2 = mockk<Note>()
|
||||
|
||||
val z1 = mockk<Note>()
|
||||
val z2 = mockk<Note>()
|
||||
val z3 = mockk<Note>()
|
||||
every { z3.event } returns mockk<EventInterface>()
|
||||
|
||||
val zaps: Map<Note, Note?> = mapOf(u1 to z1, u1 to z2, u2 to z3)
|
||||
val actual = UserZaps.groupByUser(zaps)
|
||||
|
||||
Assert.assertEquals(
|
||||
listOf(Pair(u1, z1), Pair(u1, z2), Pair(u2, z3)),
|
||||
actual
|
||||
)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user