Create EventInterface

pull/205/head
Chemaclass 2023-03-05 22:42:19 +01:00
rodzic 12570d3e26
commit b8937594bc
25 zmienionych plików z 199 dodań i 113 usunięć

Wyświetl plik

@ -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">

Wyświetl plik

@ -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"
}
}

Wyświetl plik

@ -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)

Wyświetl plik

@ -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) {
}
}

Wyświetl plik

@ -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 {

Wyświetl plik

@ -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 {
}
}
}
}
}

Wyświetl plik

@ -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()) {

Wyświetl plik

@ -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()
}

Wyświetl plik

@ -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)
}
}
}
}

Wyświetl plik

@ -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
}

Wyświetl plik

@ -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 (
]
]
}
*/
*/

Wyświetl plik

@ -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())
}
}
}
}

Wyświetl plik

@ -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)

Wyświetl plik

@ -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())
}
}
}
}

Wyświetl plik

@ -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>>
}
}

Wyświetl plik

@ -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
}
}
}

Wyświetl plik

@ -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++

Wyświetl plik

@ -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)

Wyświetl plik

@ -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)
}
}

Wyświetl plik

@ -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)
)
}
}
}

Wyświetl plik

@ -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

Wyświetl plik

@ -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,
}
}
}
}
}

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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
)
}
}