Add chatroom functionality
This commit is contained in:
@@ -8,7 +8,7 @@ import ac.cord.auxiliary.compose.database.dao.ChatRoomDao
|
||||
import ac.cord.auxiliary.compose.database.dao.ConnectionDao
|
||||
import ac.cord.auxiliary.compose.database.dao.GiftWrapMessageDao
|
||||
import ac.cord.auxiliary.compose.database.dao.GiftWrapSealDao
|
||||
import ac.cord.auxiliary.compose.database.dao.GiftWrapUnsignedDao
|
||||
import ac.cord.auxiliary.compose.database.dao.GiftWrapPayloadDao
|
||||
import ac.cord.auxiliary.compose.database.dao.InReplyToRelationDao
|
||||
import ac.cord.auxiliary.compose.database.dao.MentionDao
|
||||
import ac.cord.auxiliary.compose.database.dao.NegentropySynchronizeRequestDao
|
||||
@@ -34,7 +34,7 @@ import ac.cord.auxiliary.compose.database.model.ChatRoom
|
||||
import ac.cord.auxiliary.compose.database.model.Connection
|
||||
import ac.cord.auxiliary.compose.database.model.GiftWrapMessage
|
||||
import ac.cord.auxiliary.compose.database.model.GiftWrapSeal
|
||||
import ac.cord.auxiliary.compose.database.model.GiftWrapUnsigned
|
||||
import ac.cord.auxiliary.compose.database.model.GiftWrapPayload
|
||||
import ac.cord.auxiliary.compose.database.model.InReplyToRelation
|
||||
import ac.cord.auxiliary.compose.database.model.Mention
|
||||
import ac.cord.auxiliary.compose.database.model.NegentropySynchronizeRequest
|
||||
@@ -72,7 +72,7 @@ val GENESIS_AT = Instant.fromEpochMilliseconds(1231006505000L)
|
||||
InReplyToRelation::class,
|
||||
GiftWrapMessage::class,
|
||||
GiftWrapSeal::class,
|
||||
GiftWrapUnsigned::class,
|
||||
GiftWrapPayload::class,
|
||||
Mention::class,
|
||||
NegentropySynchronizeRequest::class,
|
||||
NegentropySynchronizeResult::class,
|
||||
@@ -90,13 +90,7 @@ val GENESIS_AT = Instant.fromEpochMilliseconds(1231006505000L)
|
||||
UnsignedNostrEvent::class,
|
||||
Zap::class
|
||||
],
|
||||
version = 2,
|
||||
autoMigrations = [
|
||||
AutoMigration(
|
||||
from = 1,
|
||||
to = 2
|
||||
)
|
||||
]
|
||||
version = 1
|
||||
)
|
||||
@TypeConverters(AuxConverters::class)
|
||||
abstract class AuxDatabase: RoomDatabase() {
|
||||
@@ -113,7 +107,7 @@ abstract class AuxDatabase: RoomDatabase() {
|
||||
|
||||
abstract fun giftWrapSealDao(): GiftWrapSealDao
|
||||
|
||||
abstract fun giftWrapUnsignedDao(): GiftWrapUnsignedDao
|
||||
abstract fun giftWrapUnsignedDao(): GiftWrapPayloadDao
|
||||
|
||||
abstract fun inReplyToRelationDao(): InReplyToRelationDao
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package ac.cord.auxiliary.compose.database.dao
|
||||
|
||||
import ac.cord.auxiliary.compose.database.model.ChatRoom
|
||||
import ac.cord.auxiliary.compose.database.model.Relay
|
||||
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalChatRoom
|
||||
import androidx.room3.Dao
|
||||
import androidx.room3.Query
|
||||
import androidx.room3.Upsert
|
||||
@@ -10,9 +11,11 @@ import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface ChatRoomDao {
|
||||
@Query("SELECT * FROM ChatRoom WHERE id = :id")
|
||||
fun findChatRoomById(id: String): ChatRoom?
|
||||
|
||||
@Query("SELECT * FROM ChatRoom WHERE userPublicKey = :userPublicKey")
|
||||
fun observeChatRoomByUserPublicKey(userPublicKey: String): Flow<List<ChatRoom>>
|
||||
fun observeChatRoomByUserPublicKey(userPublicKey: String): Flow<List<LocalChatRoom>>
|
||||
|
||||
@Upsert
|
||||
suspend fun upsert(chatRoom: ChatRoom)
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package ac.cord.auxiliary.compose.database.dao
|
||||
|
||||
import ac.cord.auxiliary.compose.database.model.GiftWrapPayload
|
||||
import androidx.room3.Dao
|
||||
import androidx.room3.Query
|
||||
import androidx.room3.Upsert
|
||||
|
||||
@Dao
|
||||
interface GiftWrapPayloadDao {
|
||||
@Query("SELECT * FROM GiftWrapPayload")
|
||||
fun getAllGiftWrapUnsigned(): List<GiftWrapPayload>
|
||||
|
||||
@Upsert
|
||||
suspend fun upsert(giftWrapPayload: GiftWrapPayload)
|
||||
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package ac.cord.auxiliary.compose.database.dao
|
||||
|
||||
import ac.cord.auxiliary.compose.database.model.GiftWrapUnsigned
|
||||
import ac.cord.auxiliary.compose.database.model.Relay
|
||||
import androidx.room3.Dao
|
||||
import androidx.room3.Query
|
||||
import androidx.room3.Upsert
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface GiftWrapUnsignedDao {
|
||||
@Query("SELECT * FROM GiftWrapUnsigned")
|
||||
fun getAllGiftWrapUnsigned(): List<GiftWrapUnsigned>
|
||||
|
||||
|
||||
@Upsert
|
||||
suspend fun upsert(giftWrapUnsigned: GiftWrapUnsigned)
|
||||
|
||||
}
|
||||
@@ -3,6 +3,8 @@ package ac.cord.auxiliary.compose.database.dao
|
||||
import ac.cord.auxiliary.compose.database.AuxDatabase
|
||||
import ac.cord.auxiliary.compose.database.GENESIS_AT
|
||||
import ac.cord.auxiliary.compose.database.model.BroadcastNostrEventRequest
|
||||
import ac.cord.auxiliary.compose.database.model.ChatMessage
|
||||
import ac.cord.auxiliary.compose.database.model.ChatRoom
|
||||
import ac.cord.auxiliary.compose.database.model.Connection
|
||||
import ac.cord.auxiliary.compose.database.model.Mention
|
||||
import ac.cord.auxiliary.compose.database.model.NegentropySynchronizeRequest
|
||||
@@ -10,24 +12,21 @@ import ac.cord.auxiliary.compose.database.model.NostrEvent
|
||||
import ac.cord.auxiliary.compose.database.model.NostrEventRelay
|
||||
import ac.cord.auxiliary.compose.database.model.Post
|
||||
import ac.cord.auxiliary.compose.database.model.Profile
|
||||
import ac.cord.auxiliary.compose.database.model.SynchronizeNostrEventRequest
|
||||
import ac.cord.auxiliary.compose.database.model.UnsignedNostrEvent
|
||||
import ac.cord.auxiliary.compose.database.model.types.SynchronizationFilter
|
||||
import ac.cord.auxiliary.compose.network.serialization.encodeToJsonString
|
||||
import ac.cord.auxiliary.compose.nostr.Relays
|
||||
import ac.cord.auxiliary.compose.managers.SeedManager
|
||||
import ac.cord.auxiliary.compose.managers.toHex
|
||||
import androidx.room3.Dao
|
||||
import androidx.room3.Transaction
|
||||
import androidx.room3.useWriterConnection
|
||||
import co.touchlab.kermit.Logger
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.events.taggedEvents
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.taggedUsers
|
||||
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.quotes.QAddressableTag
|
||||
import com.vitorpamplona.quartz.nip18Reposts.quotes.QEventTag
|
||||
import com.vitorpamplona.quartz.nip18Reposts.quotes.firstTaggedQuote
|
||||
import kotlin.math.ceil
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
||||
import fr.acinq.bitcoin.crypto.musig2.Musig2
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Instant
|
||||
|
||||
@Dao
|
||||
abstract class NostrDao(
|
||||
@@ -124,7 +123,7 @@ abstract class NostrDao(
|
||||
} else {
|
||||
// Nothing else needs to be done
|
||||
val profile = database.profileDao().getProfileByPublicKey(nostrEvent.pubKey)
|
||||
if (profile?.createdAt == GENESIS_AT) {
|
||||
if (profile?.createdAt == GENESIS_AT && nostrEvent.kind == MetadataEvent.KIND) {
|
||||
logger.i("This is a placeholder profile might need to get synced...: ${profile.publicKey}")
|
||||
// Setup the sync here...
|
||||
} else {
|
||||
@@ -158,13 +157,13 @@ abstract class NostrDao(
|
||||
val profilePublicKeysToSync = mutableMapOf<String, MutableSet<String>>()
|
||||
val eventIdsToSync = mutableMapOf<String, MutableSet<String>>()
|
||||
|
||||
if (nostrEvent.unsignedNostrEventId == null) {
|
||||
if (nostrEvent.unsignedNostrEventId == null && nostrEvent.kind != GiftWrapEvent.KIND) {
|
||||
// Find or create profile with the pubKey... if not found submit a sync request...
|
||||
val profile = database.profileDao().getProfileByPublicKey(nostrEvent.pubKey)
|
||||
|
||||
if (profile == null) {
|
||||
val placeHolderProfile = Profile(
|
||||
displayName = "LOADING PROFILE...",
|
||||
displayName = "LOADING...",
|
||||
publicKey = nostrEvent.pubKey,
|
||||
createdAt = GENESIS_AT,
|
||||
nostrEventId = nostrEvent.id, // Will get overwriting by sync,
|
||||
@@ -185,6 +184,67 @@ abstract class NostrDao(
|
||||
logger.w("We somehow have an unsignedNostrEvent: $nostrEvent")
|
||||
}
|
||||
|
||||
// Index reposts first because they have the contained posts...
|
||||
nostrEvent.toRepostedRelationWithContainedPost()?.let { (repostedRelation, containedPost) ->
|
||||
// Find or create placeHolder note that is being reposted
|
||||
val repostedNostrEvent = database.nostrEventDao().getNostrEventById(repostedRelation.repostedNostrEventId)
|
||||
|
||||
val repostedProfilePublicKey = if (repostedNostrEvent == null) {
|
||||
// Sync event...
|
||||
if (eventIdsToSync[relayURL] == null) {
|
||||
eventIdsToSync[relayURL] = mutableSetOf()
|
||||
}
|
||||
eventIdsToSync[relayURL]?.add(repostedRelation.repostedNostrEventId)
|
||||
|
||||
database.nostrEventDao().insert(
|
||||
if (containedPost != null) {
|
||||
NostrEvent(
|
||||
id = containedPost.id,
|
||||
content = containedPost.content,
|
||||
createdAt = Instant.fromEpochSeconds(containedPost.createdAt),
|
||||
pubKey = containedPost.pubKey,
|
||||
sig = containedPost.sig,
|
||||
kind = containedPost.kind,
|
||||
tags = containedPost.tags
|
||||
)
|
||||
} else {
|
||||
NostrEvent(
|
||||
id = repostedRelation.repostedNostrEventId,
|
||||
content = "Pending Sync",
|
||||
createdAt = GENESIS_AT,
|
||||
pubKey = nostrEvent.pubKey,
|
||||
sig = "",
|
||||
kind = nostrEvent.kind,
|
||||
tags = emptyArray()
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
repostedRelation.repostedProfilePublicKey
|
||||
} else {
|
||||
repostedRelation.repostedProfilePublicKey ?: repostedNostrEvent.pubKey
|
||||
}
|
||||
|
||||
val repostedProfile = repostedProfilePublicKey?.let { database.profileDao().getProfileByPublicKey(it) }
|
||||
|
||||
if (repostedProfilePublicKey != null && repostedProfile == null) {
|
||||
database.profileDao().insertPlaceholderProfile(
|
||||
Profile(
|
||||
displayName = "LOADING...",
|
||||
publicKey = repostedProfilePublicKey,
|
||||
createdAt = GENESIS_AT,
|
||||
nostrEventId = nostrEvent.id, // Will get overwriting by sync,
|
||||
)
|
||||
)
|
||||
}
|
||||
database.repostedRelationDao().upsert(
|
||||
repostedRelation.copy(
|
||||
repostedProfilePublicKey = repostedProfilePublicKey
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// Sync tagged events and authors...
|
||||
nostrEvent.tags.taggedEvents().take(4).forEach { taggedEvent -> // We only look at the first 3 tagged events...
|
||||
val taggedNostrEvent = database.nostrEventDao().getNostrEventById(taggedEvent.eventId)
|
||||
@@ -227,7 +287,7 @@ abstract class NostrDao(
|
||||
// Save a placeholder
|
||||
database.profileDao().insertPlaceholderProfile(
|
||||
Profile(
|
||||
displayName = "LOADING PROFILE...",
|
||||
displayName = "LOADING...",
|
||||
publicKey = taggedUser.pubKey,
|
||||
createdAt = GENESIS_AT,
|
||||
nostrEventId = nostrEvent.id, // Will get overwriting by sync,
|
||||
@@ -243,11 +303,10 @@ abstract class NostrDao(
|
||||
)
|
||||
)
|
||||
if (recommendedRelay != null) {
|
||||
if (profilePublicKeysToSync[relayURL] == null) {
|
||||
profilePublicKeysToSync[relayURL] = mutableSetOf()
|
||||
if (profilePublicKeysToSync[recommendedRelay] == null) {
|
||||
profilePublicKeysToSync[recommendedRelay] = mutableSetOf()
|
||||
}
|
||||
profilePublicKeysToSync[relayURL]?.add(taggedUser.pubKey)
|
||||
|
||||
profilePublicKeysToSync[recommendedRelay]?.add(taggedUser.pubKey)
|
||||
} else {
|
||||
if (profilePublicKeysToSync[relayURL] == null) {
|
||||
profilePublicKeysToSync[relayURL] = mutableSetOf()
|
||||
@@ -262,6 +321,100 @@ abstract class NostrDao(
|
||||
database.profileDao().upsert(profile)
|
||||
}
|
||||
|
||||
nostrEvent.toGiftWrapMessageWithReceiverPTag()?.let { giftWrapMessage ->
|
||||
val giftWrapReceiverProfile = database.profileDao().getProfileByPublicKey(giftWrapMessage.receiverPublicKey)
|
||||
|
||||
if (giftWrapReceiverProfile == null) {
|
||||
// Save a placeholder
|
||||
database.profileDao().insertPlaceholderProfile(
|
||||
Profile(
|
||||
displayName = "LOADING...",
|
||||
publicKey = giftWrapMessage.receiverPublicKey,
|
||||
createdAt = GENESIS_AT,
|
||||
nostrEventId = nostrEvent.id, // Will get overwriting by sync,
|
||||
)
|
||||
)
|
||||
|
||||
val recommendRelayUrl = giftWrapMessage.receiverRelayHit
|
||||
|
||||
if (recommendRelayUrl != null) {
|
||||
if (profilePublicKeysToSync[recommendRelayUrl] == null) {
|
||||
profilePublicKeysToSync[recommendRelayUrl] = mutableSetOf()
|
||||
}
|
||||
profilePublicKeysToSync[recommendRelayUrl]?.add(giftWrapMessage.receiverPublicKey)
|
||||
} else {
|
||||
if (profilePublicKeysToSync[relayURL] == null) {
|
||||
profilePublicKeysToSync[relayURL] = mutableSetOf()
|
||||
}
|
||||
profilePublicKeysToSync[relayURL]?.add(
|
||||
giftWrapMessage.receiverPublicKey
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
database.giftWrapMessageDao().upsert(
|
||||
giftWrapMessage
|
||||
)
|
||||
|
||||
// TODO: Might want to process this somewhere else where privateKey is available...
|
||||
SeedManager.activeKeyPair().privKey?.let { activeUserPrivateKey ->
|
||||
giftWrapMessage.decryptGiftWrapSeal(
|
||||
activeUserPrivateKey
|
||||
)?.let { giftWrapSeal ->
|
||||
database.giftWrapSealDao().upsert(
|
||||
giftWrapSeal
|
||||
)
|
||||
|
||||
giftWrapSeal.decryptGiftWrapPayload(
|
||||
activeUserPrivateKey
|
||||
)?.let { giftWrapPayload ->
|
||||
|
||||
database.giftWrapUnsignedDao().upsert(
|
||||
giftWrapPayload
|
||||
)
|
||||
|
||||
val chatRoomId = Musig2.aggregateKeys(
|
||||
giftWrapPayload.receiverPublicKeys()
|
||||
).value.toHex()
|
||||
// Process chatMessage and chatRoom stuff...
|
||||
// Find or create chatRoom
|
||||
val chatRoom = database.chatRoomDao().findChatRoomById(
|
||||
chatRoomId
|
||||
)
|
||||
|
||||
if (chatRoom == null) {
|
||||
database.chatRoomDao().upsert(
|
||||
ChatRoom(
|
||||
id = chatRoomId,
|
||||
userPublicKey = SeedManager.activeKeyPair().pubKey.toHex(),
|
||||
subject = null, // TODO: Get subject from tags...
|
||||
createdAt = giftWrapPayload.createdAt,
|
||||
initialGiftWrapUnsignedId = giftWrapPayload.id
|
||||
)
|
||||
)
|
||||
} else {
|
||||
// TODO: Update subject from tags...
|
||||
}
|
||||
|
||||
// Save chatMessage
|
||||
database.chatMessageDao().upsert(
|
||||
ChatMessage(
|
||||
id = giftWrapPayload.id,
|
||||
senderPublicKey = giftWrapPayload.publicKey,
|
||||
isUserMessage = SeedManager.activePublicKey().toHex() == giftWrapPayload.publicKey,
|
||||
chatRoomId = chatRoomId,
|
||||
createdAt = giftWrapPayload.createdAt,
|
||||
content = giftWrapPayload.content,
|
||||
)
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
nostrEvent.toPost()?.let { post ->
|
||||
if (post.replyToId != null) {
|
||||
// Find or create placeHolder note that is replyTo
|
||||
@@ -334,7 +487,7 @@ abstract class NostrDao(
|
||||
if (quotedProfilePublicKey != null && quotedProfile == null) {
|
||||
database.profileDao().insertPlaceholderProfile(
|
||||
Profile(
|
||||
displayName = "LOADING PROFILE...",
|
||||
displayName = "LOADING...",
|
||||
publicKey = quotedProfilePublicKey,
|
||||
createdAt = GENESIS_AT,
|
||||
nostrEventId = nostrEvent.id, // Will get overwriting by sync,
|
||||
@@ -349,52 +502,6 @@ abstract class NostrDao(
|
||||
)
|
||||
)
|
||||
}
|
||||
nostrEvent.toRepostedRelation()?.let { repostedRelation ->
|
||||
// Find or create placeHolder note that is being reposted
|
||||
val repostedNostrEvent = database.nostrEventDao().getNostrEventById(repostedRelation.repostedNostrEventId)
|
||||
|
||||
val repostedProfilePublicKey = if (repostedNostrEvent == null) {
|
||||
// Sync event...
|
||||
if (eventIdsToSync[relayURL] == null) {
|
||||
eventIdsToSync[relayURL] = mutableSetOf()
|
||||
}
|
||||
eventIdsToSync[relayURL]?.add(repostedRelation.repostedNostrEventId)
|
||||
|
||||
database.nostrEventDao().insert(
|
||||
NostrEvent(
|
||||
id = repostedRelation.repostedNostrEventId,
|
||||
content = "Pending Sync",
|
||||
createdAt = GENESIS_AT,
|
||||
pubKey = nostrEvent.pubKey,
|
||||
sig = "",
|
||||
kind = nostrEvent.kind,
|
||||
tags = emptyArray()
|
||||
)
|
||||
)
|
||||
|
||||
repostedRelation.repostedProfilePublicKey
|
||||
} else {
|
||||
repostedRelation.repostedProfilePublicKey ?: repostedNostrEvent.pubKey
|
||||
}
|
||||
|
||||
val repostedProfile = repostedProfilePublicKey?.let { database.profileDao().getProfileByPublicKey(it) }
|
||||
|
||||
if (repostedProfilePublicKey != null && repostedProfile == null) {
|
||||
database.profileDao().insertPlaceholderProfile(
|
||||
Profile(
|
||||
displayName = "LOADING PROFILE...",
|
||||
publicKey = repostedProfilePublicKey,
|
||||
createdAt = GENESIS_AT,
|
||||
nostrEventId = nostrEvent.id, // Will get overwriting by sync,
|
||||
)
|
||||
)
|
||||
}
|
||||
database.repostedRelationDao().upsert(
|
||||
repostedRelation.copy(
|
||||
repostedProfilePublicKey = repostedProfilePublicKey
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
nostrEvent.toInReplyToRelation()?.let { inReplyToRelation ->
|
||||
|
||||
@@ -442,7 +549,7 @@ abstract class NostrDao(
|
||||
if (inReplyToRootProfilePublicKey != null && inReplyToProfile == null) {
|
||||
database.profileDao().insertPlaceholderProfile(
|
||||
Profile(
|
||||
displayName = "LOADING PROFILE...",
|
||||
displayName = "LOADING...",
|
||||
publicKey = inReplyToRootProfilePublicKey,
|
||||
createdAt = GENESIS_AT,
|
||||
nostrEventId = nostrEvent.id, // Will get overwriting by sync,
|
||||
@@ -502,7 +609,7 @@ abstract class NostrDao(
|
||||
|
||||
if (profile == null) {
|
||||
val placeHolderProfile = Profile(
|
||||
displayName = "LOADING PROFILE...",
|
||||
displayName = "LOADING...",
|
||||
publicKey = followedHexKEys,
|
||||
createdAt = GENESIS_AT,
|
||||
nostrEventId = nostrEvent.id, // Will get overwriting by sync,
|
||||
|
||||
@@ -8,6 +8,7 @@ import androidx.room3.Entity
|
||||
import androidx.room3.ForeignKey
|
||||
import androidx.room3.PrimaryKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Instant
|
||||
|
||||
@Entity(
|
||||
@@ -19,9 +20,9 @@ import kotlin.time.Instant
|
||||
onDelete = ForeignKey.CASCADE,
|
||||
),
|
||||
ForeignKey(
|
||||
entity = GiftWrapUnsigned::class,
|
||||
entity = GiftWrapPayload::class,
|
||||
parentColumns = ["id"],
|
||||
childColumns = ["giftWrapUnsignedId"],
|
||||
childColumns = ["id"],
|
||||
onDelete = ForeignKey.CASCADE,
|
||||
),
|
||||
],
|
||||
@@ -35,17 +36,15 @@ data class ChatMessage(
|
||||
|
||||
val chatRoomId: String,
|
||||
|
||||
val giftWrapUnsignedId: String,
|
||||
|
||||
val replyToEventId: String?,
|
||||
val quotedEventId: String?,
|
||||
val replyToEventId: String? = null,
|
||||
val quotedEventId: String? = null,
|
||||
|
||||
val content: String,
|
||||
|
||||
override val createdAt: Instant,
|
||||
override val updatedAt: Instant,
|
||||
override val savedAt: Instant,
|
||||
override val viewedAt: Instant?,
|
||||
override val deletedAt: Instant?, // Who this message should be seen by (used to derive conversationId)
|
||||
override val createdAt: Instant = Clock.System.now(),
|
||||
override val updatedAt: Instant = createdAt,
|
||||
override val savedAt: Instant = Clock.System.now(),
|
||||
override val viewedAt: Instant? = null,
|
||||
override val deletedAt: Instant? = null, // Who this message should be seen by (used to derive conversationId)
|
||||
): TimestampedEntity, LocalStoreEntity, UserViewableEntity, SoftDeletableEntity {
|
||||
}
|
||||
@@ -20,7 +20,7 @@ import kotlin.time.Instant
|
||||
onDelete = ForeignKey.CASCADE,
|
||||
),
|
||||
ForeignKey(
|
||||
entity = GiftWrapUnsigned::class,
|
||||
entity = GiftWrapPayload::class,
|
||||
parentColumns = ["id"],
|
||||
childColumns = ["initialGiftWrapUnsignedId"],
|
||||
onDelete = ForeignKey.CASCADE,
|
||||
@@ -50,11 +50,6 @@ data class ChatRoom(
|
||||
*/
|
||||
val subject: String?,
|
||||
|
||||
/**
|
||||
* Should probably be the human-readable name of whichever participant is not the user...
|
||||
*/
|
||||
val name: String?,
|
||||
|
||||
/**
|
||||
* Derived from kind14/15 tags
|
||||
* [
|
||||
|
||||
@@ -11,6 +11,15 @@ import androidx.room3.PrimaryKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Kind
|
||||
import com.vitorpamplona.quartz.nip01Core.core.TagArray
|
||||
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.seals.SealedRumorEvent
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
||||
import fr.acinq.bitcoin.PrivateKey
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Instant
|
||||
|
||||
@Entity(
|
||||
@@ -33,12 +42,19 @@ data class GiftWrapMessage(
|
||||
val publicKey: String,
|
||||
|
||||
val kind: Kind = 1059,
|
||||
|
||||
/**
|
||||
* Receiver Public Key Derived from tags
|
||||
*
|
||||
* [
|
||||
* ["p", receiverPublicKey, "<relay-url>"] // receiver
|
||||
* ]
|
||||
*
|
||||
*/
|
||||
val tags: Array<Array<String>>,
|
||||
val receiverPublicKey: String,
|
||||
|
||||
val receiverRelayHit: String?,
|
||||
|
||||
/**
|
||||
*
|
||||
* encrypt(GiftWrapSeal, randomPrivateKey, receiverPublicKey)
|
||||
@@ -57,47 +73,50 @@ data class GiftWrapMessage(
|
||||
/**
|
||||
* randomTimeUpTo2DaysInThePast
|
||||
*/
|
||||
override val createdAt: Instant,
|
||||
override val updatedAt: Instant,
|
||||
override val savedAt: Instant,
|
||||
override val viewedAt: Instant?,
|
||||
override val deletedAt: Instant?, // Who this message should be seen by (used to derive conversationId)
|
||||
override val createdAt: Instant = Clock.System.now(),
|
||||
override val updatedAt: Instant = createdAt,
|
||||
override val savedAt: Instant = Clock.System.now(),
|
||||
override val viewedAt: Instant? = null,
|
||||
override val deletedAt: Instant? = null, // Who this message should be seen by (used to derive conversationId)
|
||||
): NostrEventEntity, TimestampedEntity, LocalStoreEntity, UserViewableEntity, SoftDeletableEntity {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other == null || this::class != other::class) return false
|
||||
|
||||
other as GiftWrapMessage
|
||||
suspend fun decryptGiftWrapSeal(
|
||||
privateKey: ByteArray
|
||||
): GiftWrapSeal? {
|
||||
if (kind == GiftWrapEvent.KIND) {
|
||||
val giftWrapEvent = GiftWrapEvent(
|
||||
id = id,
|
||||
pubKey = publicKey,
|
||||
tags = arrayOf(
|
||||
PTag(
|
||||
pubKey = receiverPublicKey,
|
||||
relayHint = receiverRelayHit?.let { NormalizedRelayUrl(it) }
|
||||
).toTagArray()
|
||||
),
|
||||
createdAt = createdAt.epochSeconds,
|
||||
content = content,
|
||||
sig = signature
|
||||
)
|
||||
|
||||
if (kind != other.kind) return false
|
||||
if (id != other.id) return false
|
||||
if (publicKey != other.publicKey) return false
|
||||
if (!tags.contentDeepEquals(other.tags)) return false
|
||||
if (content != other.content) return false
|
||||
if (signature != other.signature) return false
|
||||
if (nostrEventId != other.nostrEventId) return false
|
||||
if (createdAt != other.createdAt) return false
|
||||
if (updatedAt != other.updatedAt) return false
|
||||
if (savedAt != other.savedAt) return false
|
||||
if (viewedAt != other.viewedAt) return false
|
||||
if (deletedAt != other.deletedAt) return false
|
||||
val nostrSigner = NostrSignerInternal(
|
||||
keyPair = KeyPair(privKey = privateKey)
|
||||
)
|
||||
|
||||
return true
|
||||
}
|
||||
giftWrapEvent.unwrapOrNull(
|
||||
nostrSigner
|
||||
)?.let { giftWrapSeal ->
|
||||
return GiftWrapSeal(
|
||||
id = giftWrapSeal.id,
|
||||
publicKey = giftWrapSeal.pubKey,
|
||||
content = giftWrapSeal.content,
|
||||
tags = giftWrapSeal.tags,
|
||||
createdAt = Instant.fromEpochSeconds(giftWrapSeal.createdAt),
|
||||
signature = giftWrapSeal.sig
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = kind
|
||||
result = 31 * result + id.hashCode()
|
||||
result = 31 * result + publicKey.hashCode()
|
||||
result = 31 * result + tags.contentDeepHashCode()
|
||||
result = 31 * result + content.hashCode()
|
||||
result = 31 * result + signature.hashCode()
|
||||
result = 31 * result + nostrEventId.hashCode()
|
||||
result = 31 * result + createdAt.hashCode()
|
||||
result = 31 * result + updatedAt.hashCode()
|
||||
result = 31 * result + savedAt.hashCode()
|
||||
result = 31 * result + (viewedAt?.hashCode() ?: 0)
|
||||
result = 31 * result + (deletedAt?.hashCode() ?: 0)
|
||||
return result
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,9 @@ import androidx.room3.ForeignKey
|
||||
import androidx.room3.PrimaryKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Kind
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.taggedUsers
|
||||
import fr.acinq.bitcoin.PublicKey
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Instant
|
||||
|
||||
@Entity(
|
||||
@@ -21,7 +24,7 @@ import kotlin.time.Instant
|
||||
),
|
||||
],
|
||||
)
|
||||
data class GiftWrapUnsigned(
|
||||
data class GiftWrapPayload( // TODO: Rename this to GiftWrapPayload...
|
||||
@PrimaryKey
|
||||
val id: HexKey,
|
||||
|
||||
@@ -31,9 +34,9 @@ data class GiftWrapUnsigned(
|
||||
val publicKey: String,
|
||||
|
||||
/**
|
||||
* Chat Message Kind
|
||||
* Payload Event Kind
|
||||
*/
|
||||
val kind: Kind = 14,
|
||||
val kind: Kind,
|
||||
|
||||
/**
|
||||
* Tags for the unsigned giftwrap
|
||||
@@ -54,7 +57,7 @@ data class GiftWrapUnsigned(
|
||||
* content MUST be plain text. Fields id and created_at are required.
|
||||
*/
|
||||
val content: String,
|
||||
val quotedEventId: String?,
|
||||
val quotedEventId: String? = null,
|
||||
|
||||
/**
|
||||
* Associated GiftWrapSeal
|
||||
@@ -64,17 +67,28 @@ data class GiftWrapUnsigned(
|
||||
/**
|
||||
* Current time
|
||||
*/
|
||||
override val createdAt: Instant,
|
||||
override val updatedAt: Instant,
|
||||
override val savedAt: Instant,
|
||||
override val viewedAt: Instant?,
|
||||
override val deletedAt: Instant?, // Who this message should be seen by (used to derive conversationId)
|
||||
override val createdAt: Instant = Clock.System.now(),
|
||||
override val updatedAt: Instant = createdAt,
|
||||
override val savedAt: Instant = Clock.System.now(),
|
||||
override val viewedAt: Instant? = null,
|
||||
override val deletedAt: Instant? = null, // Who this message should be seen by (used to derive conversationId)
|
||||
): TimestampedEntity, LocalStoreEntity, UserViewableEntity, SoftDeletableEntity {
|
||||
|
||||
fun receiverPublicKeys(): List<PublicKey> = try {
|
||||
tags.taggedUsers().map {
|
||||
PublicKey(
|
||||
it.pubKey.hexToByteArray()
|
||||
)
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other == null || this::class != other::class) return false
|
||||
|
||||
other as GiftWrapUnsigned
|
||||
other as GiftWrapPayload
|
||||
|
||||
if (kind != other.kind) return false
|
||||
if (id != other.id) return false
|
||||
@@ -9,7 +9,10 @@ import androidx.room3.ForeignKey
|
||||
import androidx.room3.PrimaryKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Kind
|
||||
import com.vitorpamplona.quartz.nip01Core.core.TagArray
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.seals.SealedRumorEvent
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Instant
|
||||
|
||||
@Entity(
|
||||
@@ -56,12 +59,48 @@ data class GiftWrapSeal(
|
||||
/**
|
||||
* randomTimeUpTo2DaysInThePast()
|
||||
*/
|
||||
override val createdAt: Instant,
|
||||
override val updatedAt: Instant,
|
||||
override val savedAt: Instant,
|
||||
override val viewedAt: Instant?,
|
||||
override val deletedAt: Instant?, // Who this message should be seen by (used to derive conversationId)
|
||||
override val createdAt: Instant = Clock.System.now(),
|
||||
override val updatedAt: Instant = createdAt,
|
||||
override val savedAt: Instant = Clock.System.now(),
|
||||
override val viewedAt: Instant? = null,
|
||||
override val deletedAt: Instant? = null, // Who this message should be seen by (used to derive conversationId)
|
||||
): TimestampedEntity, LocalStoreEntity, UserViewableEntity, SoftDeletableEntity {
|
||||
|
||||
suspend fun decryptGiftWrapPayload(
|
||||
privateKey: ByteArray
|
||||
): GiftWrapPayload? {
|
||||
if (kind == SealedRumorEvent.KIND) {
|
||||
val sealedRumorEvent = SealedRumorEvent(
|
||||
id = id,
|
||||
pubKey = publicKey,
|
||||
tags = tags,
|
||||
createdAt = createdAt.epochSeconds,
|
||||
content = content,
|
||||
sig = signature
|
||||
)
|
||||
|
||||
val nostrSigner = NostrSignerInternal(
|
||||
keyPair = KeyPair(privKey = privateKey)
|
||||
)
|
||||
|
||||
sealedRumorEvent.unsealOrNull(
|
||||
nostrSigner
|
||||
)?.let { giftWrapPayload ->
|
||||
return GiftWrapPayload(
|
||||
id = giftWrapPayload.id,
|
||||
publicKey = giftWrapPayload.pubKey,
|
||||
kind = giftWrapPayload.kind,
|
||||
tags = giftWrapPayload.tags,
|
||||
content = giftWrapPayload.content,
|
||||
createdAt = Instant.fromEpochSeconds(giftWrapPayload.createdAt),
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other == null || this::class != other::class) return false
|
||||
|
||||
@@ -5,7 +5,6 @@ import ac.cord.auxiliary.compose.database.model.traits.LocalStoreEntity
|
||||
import ac.cord.auxiliary.compose.database.model.traits.SoftDeletableEntity
|
||||
import ac.cord.auxiliary.compose.database.model.traits.TimestampedEntity
|
||||
import ac.cord.auxiliary.compose.database.model.traits.UnsignedNostrEventEntity
|
||||
import ac.cord.auxiliary.compose.database.model.types.SynchronizationFilter
|
||||
import ac.cord.auxiliary.compose.ui.composable.widgets.content.ContentParser
|
||||
import ac.cord.auxiliary.compose.ui.composable.widgets.content.ContentSegment
|
||||
import androidx.room3.Entity
|
||||
@@ -16,10 +15,10 @@ import co.touchlab.kermit.Logger
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Kind
|
||||
import com.vitorpamplona.quartz.nip01Core.core.TagArray
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.events.firstTaggedEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.events.taggedEvents
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.taggedUsers
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.quotes.QAddressableTag
|
||||
@@ -27,6 +26,7 @@ import com.vitorpamplona.quartz.nip18Reposts.quotes.QEventTag
|
||||
import com.vitorpamplona.quartz.nip18Reposts.quotes.firstTaggedQuote
|
||||
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
|
||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
||||
import com.vitorpamplona.quartz.utils.EventFactory
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
@@ -180,6 +180,40 @@ data class NostrEvent(
|
||||
return null
|
||||
}
|
||||
|
||||
fun toGiftWrapMessageWithReceiverPTag(): GiftWrapMessage? = try {
|
||||
if (kind == GiftWrapEvent.KIND) {
|
||||
val giftWrapEvent = GiftWrapEvent(
|
||||
id = id,
|
||||
pubKey = pubKey,
|
||||
createdAt = createdAt.epochSeconds,
|
||||
kind = kind,
|
||||
tags = tags,
|
||||
content = content,
|
||||
sig = sig
|
||||
)
|
||||
|
||||
|
||||
|
||||
return giftWrapEvent.taggedUsers().firstOrNull()?.let { receiverPTag ->
|
||||
GiftWrapMessage(
|
||||
id = giftWrapEvent.id,
|
||||
kind = giftWrapEvent.kind,
|
||||
publicKey = giftWrapEvent.pubKey,
|
||||
content = giftWrapEvent.content,
|
||||
createdAt = Instant.fromEpochSeconds(giftWrapEvent.createdAt),
|
||||
receiverPublicKey = receiverPTag.pubKey,
|
||||
receiverRelayHit = receiverPTag.relayHint?.url,
|
||||
signature = giftWrapEvent.sig,
|
||||
nostrEventId = id,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
return null
|
||||
} catch (e: Throwable) {
|
||||
return null
|
||||
}
|
||||
|
||||
fun toReaction(): Reaction? = try {
|
||||
if (kind == ReactionEvent.KIND) {
|
||||
val reactionEvent = EventFactory.create<ReactionEvent>(
|
||||
@@ -212,7 +246,7 @@ data class NostrEvent(
|
||||
return null
|
||||
}
|
||||
|
||||
fun toRepostedRelation(): RepostedRelation? = try {
|
||||
fun toRepostedRelationWithContainedPost(): Pair<RepostedRelation, Event?>? = try {
|
||||
if (kind == RepostEvent.KIND) {
|
||||
val repostEvent = EventFactory.create<RepostEvent>(
|
||||
id = id,
|
||||
@@ -224,16 +258,37 @@ data class NostrEvent(
|
||||
sig = sig
|
||||
)
|
||||
|
||||
return repostEvent.containedPost()?.let { containedPost ->
|
||||
RepostedRelation(
|
||||
repostedNostrEventId = containedPost.id,
|
||||
repostedProfilePublicKey = containedPost.pubKey,
|
||||
return repostEvent.containedPost().let { containedPost ->
|
||||
if (containedPost != null) {
|
||||
Pair(
|
||||
RepostedRelation(
|
||||
repostedNostrEventId = containedPost.id,
|
||||
repostedProfilePublicKey = containedPost.pubKey,
|
||||
|
||||
repostingNostrEventId = repostEvent.id,
|
||||
repostingNostrEventId = repostEvent.id,
|
||||
|
||||
createdAt = Instant.fromEpochSeconds(repostEvent.createdAt),
|
||||
updatedAt = Instant.fromEpochSeconds(repostEvent.createdAt),
|
||||
)
|
||||
createdAt = Instant.fromEpochSeconds(repostEvent.createdAt),
|
||||
updatedAt = Instant.fromEpochSeconds(repostEvent.createdAt),
|
||||
),
|
||||
containedPost
|
||||
)
|
||||
} else {
|
||||
repostEvent.boostedEvent()?.let { eTag ->
|
||||
Pair(
|
||||
RepostedRelation(
|
||||
repostedNostrEventId = eTag.eventId,
|
||||
repostedProfilePublicKey = eTag.author,
|
||||
|
||||
repostingNostrEventId = repostEvent.id,
|
||||
|
||||
createdAt = Instant.fromEpochSeconds(repostEvent.createdAt),
|
||||
updatedAt = Instant.fromEpochSeconds(repostEvent.createdAt),
|
||||
),
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package ac.cord.auxiliary.compose.database.model.intermdiate
|
||||
|
||||
import ac.cord.auxiliary.compose.database.model.ChatRoom
|
||||
import ac.cord.auxiliary.compose.database.model.Post
|
||||
import ac.cord.auxiliary.compose.database.model.Profile
|
||||
import androidx.room3.Embedded
|
||||
import androidx.room3.Relation
|
||||
|
||||
data class LocalChatRoom(
|
||||
@Embedded val chatRoom: ChatRoom,
|
||||
)
|
||||
@@ -15,6 +15,7 @@ import ac.cord.auxiliary.compose.database.model.UnsignedNostrEvent
|
||||
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalBroadcastNostrEventRequest
|
||||
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalNostrEvent
|
||||
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalAccount
|
||||
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalChatRoom
|
||||
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalProfile
|
||||
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalProfileWithFollowers
|
||||
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalProfileWithFollowing
|
||||
@@ -682,6 +683,10 @@ class DatabaseNostrRepository(
|
||||
return database.profileDao().observeProfileWithFollowingByPublicKey(publicKey)
|
||||
}
|
||||
|
||||
override suspend fun observeChatRoomsByPublicKey(publicKey: String): Flow<List<LocalChatRoom>> {
|
||||
return database.chatRoomDao().observeChatRoomByUserPublicKey(publicKey)
|
||||
}
|
||||
|
||||
override suspend fun getRecentSearches(): List<RecentSearch> {
|
||||
return database.recentSearchDao().getAllRecentSearches()
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import ac.cord.auxiliary.compose.database.model.UnsignedNostrEvent
|
||||
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalBroadcastNostrEventRequest
|
||||
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalNostrEvent
|
||||
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalAccount
|
||||
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalChatRoom
|
||||
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalProfile
|
||||
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalProfileWithFollowers
|
||||
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalProfileWithFollowing
|
||||
@@ -122,6 +123,8 @@ interface NostrRepository {
|
||||
|
||||
suspend fun observeProfileWithFollowing(publicKey: String): Flow<LocalProfileWithFollowing?>
|
||||
|
||||
suspend fun observeChatRoomsByPublicKey(publicKey: String): Flow<List<LocalChatRoom>>
|
||||
|
||||
suspend fun getRecentSearches(): List<RecentSearch>
|
||||
|
||||
suspend fun removeRecentSearch(recentSearch: RecentSearch)
|
||||
@@ -289,6 +292,10 @@ interface NostrRepository {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun observeChatRoomsByPublicKey(publicKey: String): Flow<List<LocalChatRoom>> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun getRecentSearches(): List<RecentSearch> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import ac.cord.auxiliary.compose.repository.NostrRepository
|
||||
import ac.cord.auxiliary.compose.ui.view.state.ChatRoomListUIState
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
@@ -49,51 +48,20 @@ class ChatRoomListViewModel(
|
||||
var chatRoomListUIState: ChatRoomListUIState by mutableStateOf(initialChatRoomListUIState)
|
||||
private set
|
||||
|
||||
val isSynchronizationPending: MutableState<Boolean> = mutableStateOf(false)
|
||||
|
||||
fun initiate() {
|
||||
logger.d("init")
|
||||
// scheduleSynchronization()
|
||||
observeLocalNostrFeed()
|
||||
// observeSynchronizeNostrEventRequestByPurposeAndStatusCount()
|
||||
scheduleSynchronization()
|
||||
observeChatRoomFeed()
|
||||
}
|
||||
fun observeLocalNostrFeed() {
|
||||
logger.d("observeLocalNostrFeed")
|
||||
|
||||
fun observeChatRoomFeed() {
|
||||
logger.d("observeChatRoomFeed")
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
nostrRepository.observeProfileWithFollowing(
|
||||
nostrRepository.observeChatRoomsByPublicKey(
|
||||
publicKey
|
||||
).distinctUntilChanged().collect {
|
||||
chatRoomListUIState = ChatRoomListUIState.Loaded(
|
||||
chatRoomList = listOf(
|
||||
ChatRoom(
|
||||
id ="steveBikoId",
|
||||
userPublicKey = "steveBikoId",
|
||||
name = "Steve Biko",
|
||||
subject = "BC: Systems",
|
||||
initialGiftWrapUnsignedId = "",
|
||||
),
|
||||
ChatRoom(
|
||||
id = "chrisHaniId",
|
||||
userPublicKey = "chrisHaniId",
|
||||
name = "Chris Hani",
|
||||
subject = "MK: Gym",
|
||||
initialGiftWrapUnsignedId = "",
|
||||
),
|
||||
ChatRoom(
|
||||
id = "desmondTutuId",
|
||||
userPublicKey = "desmondTutuId",
|
||||
name = "Desmond Tutu",
|
||||
subject = "TRC: Masses",
|
||||
initialGiftWrapUnsignedId = "",
|
||||
),
|
||||
ChatRoom(
|
||||
id = "hipHopPantsulaId",
|
||||
userPublicKey = "hipHopPantsulaId",
|
||||
name = "Hip Hop Pantsula",
|
||||
subject = "Motswako: Jabba",
|
||||
initialGiftWrapUnsignedId = "",
|
||||
)
|
||||
)
|
||||
chatRoomList = it
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -110,7 +78,7 @@ class ChatRoomListViewModel(
|
||||
relayURL = normalizedRelayUrl.url,
|
||||
synchronizationFilter = synchronizationFilter
|
||||
),
|
||||
purpose = "following",
|
||||
purpose = "chat",
|
||||
synchronizationFilter = synchronizationFilter,
|
||||
relayURL = normalizedRelayUrl.url,
|
||||
level = 0
|
||||
@@ -120,16 +88,6 @@ class ChatRoomListViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
fun observeSynchronizeNostrEventRequestByPurposeAndStatusCount() {
|
||||
logger.d("observeSynchronizeNostrEventRequestByPurposeAndStatusCount")
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
nostrRepository.observeSynchronizeNostrEventRequestByPurposeAndStatusCount("feed").distinctUntilChanged().collect { count ->
|
||||
logger.d("Pending/Processing Synchronization Request: $count")
|
||||
isSynchronizationPending.value = count > 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun RenderFeed(
|
||||
@@ -177,8 +135,8 @@ class ChatRoomListViewModel(
|
||||
|
||||
items(
|
||||
items = chatRoomListUIState.chatRoomList,
|
||||
key = { it.id }
|
||||
) { chatRoom ->
|
||||
key = { it.chatRoom.id }
|
||||
) { localChatRoom ->
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onClick = {
|
||||
@@ -190,16 +148,13 @@ class ChatRoomListViewModel(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
text = chatRoom.name ?: chatRoom.subject ?: "Unnamed chat"
|
||||
text = localChatRoom.chatRoom.subject ?: "Unnamed chat"
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
ChatRoomListUIState.Loading -> {
|
||||
@@ -226,7 +181,7 @@ class ChatRoomListViewModel(
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "MessageListViewModel"
|
||||
const val TAG = "ChatRoomListViewModel"
|
||||
|
||||
fun factory(
|
||||
initialChatRoomListUIState: ChatRoomListUIState = ChatRoomListUIState.Loading,
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
||||
import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
|
||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
||||
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip96FileStorage.config.FileServersEvent
|
||||
import com.vitorpamplona.quartz.nipB7Blossom.BlossomServersEvent
|
||||
@@ -116,11 +117,11 @@ class HomeViewModel(
|
||||
}
|
||||
HomeScreenType.Messages -> {
|
||||
SynchronizationFilter(
|
||||
authors = arrayOf(
|
||||
publicKey
|
||||
),
|
||||
kinds = arrayOf(
|
||||
ContactListEvent.KIND,
|
||||
GiftWrapEvent.KIND,
|
||||
),
|
||||
tags = mapOf(
|
||||
Pair("p", listOf(publicKey))
|
||||
),
|
||||
limit = 50
|
||||
)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package ac.cord.auxiliary.compose.ui.view.state
|
||||
|
||||
import ac.cord.auxiliary.compose.database.model.ChatRoom
|
||||
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalChatRoom
|
||||
|
||||
sealed interface ChatRoomListUIState {
|
||||
data class Loaded(
|
||||
val chatRoomList: List<ChatRoom>
|
||||
val chatRoomList: List<LocalChatRoom>
|
||||
): ChatRoomListUIState
|
||||
|
||||
data object Error: ChatRoomListUIState
|
||||
|
||||
Reference in New Issue
Block a user