Introduce giftwrap work flow
This commit is contained in:
@@ -55,7 +55,6 @@ import ac.cord.auxiliary.compose.database.model.SynchronizeNostrEventRequest
|
||||
import ac.cord.auxiliary.compose.database.model.SynchronizeNostrEventResult
|
||||
import ac.cord.auxiliary.compose.database.model.UnsignedNostrEvent
|
||||
import ac.cord.auxiliary.compose.database.model.Zap
|
||||
import androidx.room3.AutoMigration
|
||||
import androidx.room3.Database
|
||||
import androidx.room3.RoomDatabase
|
||||
import androidx.room3.TypeConverters
|
||||
@@ -111,7 +110,7 @@ abstract class AuxDatabase: RoomDatabase() {
|
||||
|
||||
abstract fun giftWrapSealDao(): GiftWrapSealDao
|
||||
|
||||
abstract fun giftWrapUnsignedDao(): GiftWrapPayloadDao
|
||||
abstract fun giftWrapPayloadDao(): GiftWrapPayloadDao
|
||||
|
||||
abstract fun inReplyToRelationDao(): InReplyToRelationDao
|
||||
|
||||
|
||||
@@ -4,13 +4,16 @@ import ac.cord.auxiliary.compose.database.model.GiftWrapPayload
|
||||
import androidx.room3.Dao
|
||||
import androidx.room3.Query
|
||||
import androidx.room3.Upsert
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface GiftWrapPayloadDao {
|
||||
@Query("SELECT * FROM GiftWrapPayload")
|
||||
fun getAllGiftWrapUnsigned(): List<GiftWrapPayload>
|
||||
|
||||
@Upsert
|
||||
suspend fun upsert(giftWrapPayload: GiftWrapPayload)
|
||||
@Query("SELECT * FROM GiftWrapPayload WHERE publicKey = :publicKey AND giftWrapSealId IS NULL")
|
||||
fun observeUnsealedGiftWrapPayloads(publicKey: String): Flow<GiftWrapPayload?>
|
||||
|
||||
@Upsert
|
||||
suspend fun upsert(giftWrapPayload: GiftWrapPayload): Long
|
||||
}
|
||||
@@ -347,11 +347,19 @@ abstract class NostrDao(
|
||||
|
||||
giftWrapSeal.decryptGiftWrapPayload(
|
||||
activeUserPrivateKey
|
||||
)?.let { giftWrapPayload ->
|
||||
database.giftWrapUnsignedDao().upsert(
|
||||
giftWrapPayload
|
||||
)?.let { decryptedGiftWrapPayload ->
|
||||
val result = database.giftWrapPayloadDao().upsert(
|
||||
decryptedGiftWrapPayload
|
||||
)
|
||||
|
||||
val giftWrapPayload = if (result != -1L) {
|
||||
decryptedGiftWrapPayload.copy(
|
||||
id = result
|
||||
)
|
||||
} else {
|
||||
decryptedGiftWrapPayload
|
||||
}
|
||||
|
||||
val chatRoomId = giftWrapPayload.aggregatedParticipantsPublicKey()
|
||||
logger.d("ChatRoomId: $chatRoomId")
|
||||
|
||||
@@ -368,7 +376,7 @@ abstract class NostrDao(
|
||||
userPublicKey = SeedManager.activeKeyPair().pubKey.toHex(),
|
||||
subject = null, // TODO: Get subject from tags...
|
||||
createdAt = giftWrapPayload.createdAt,
|
||||
initialGiftWrapUnsignedId = giftWrapPayload.id
|
||||
initialGiftWrapPayloadId = giftWrapPayload.id
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
package ac.cord.auxiliary.compose.database.dao
|
||||
|
||||
import ac.cord.auxiliary.compose.database.AuxDatabase
|
||||
import ac.cord.auxiliary.compose.database.model.BroadcastNostrEventRequest
|
||||
import ac.cord.auxiliary.compose.database.model.ChatRoom
|
||||
import ac.cord.auxiliary.compose.database.model.GiftWrapMessage
|
||||
import ac.cord.auxiliary.compose.database.model.GiftWrapSeal
|
||||
import ac.cord.auxiliary.compose.database.model.NostrEvent
|
||||
import ac.cord.auxiliary.compose.database.model.Participant
|
||||
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalChatRoom
|
||||
import ac.cord.auxiliary.compose.managers.SeedManager
|
||||
@@ -9,6 +13,11 @@ import androidx.room3.Dao
|
||||
import androidx.room3.Transaction
|
||||
import co.touchlab.kermit.Logger
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
|
||||
import com.vitorpamplona.quartz.nip17Dm.settings.ChatMessageRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.seals.SealedRumorEvent
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
||||
import kotlin.time.Instant
|
||||
|
||||
|
||||
@Dao
|
||||
@@ -64,6 +73,66 @@ abstract class NostrNip17Dao(
|
||||
return null
|
||||
}
|
||||
|
||||
@Transaction
|
||||
open suspend fun persistAndBroadcastGiftWrap(
|
||||
sealedRumorEvent: SealedRumorEvent,
|
||||
giftWrapEvent: GiftWrapEvent,
|
||||
receiverPublicKey: PTag
|
||||
) {
|
||||
|
||||
val nostrEvent = NostrEvent(
|
||||
id = giftWrapEvent.id,
|
||||
content = giftWrapEvent.content,
|
||||
createdAt = Instant.fromEpochSeconds(giftWrapEvent.createdAt),
|
||||
sig = giftWrapEvent.sig,
|
||||
pubKey = giftWrapEvent.pubKey,
|
||||
tags = giftWrapEvent.tags,
|
||||
kind = giftWrapEvent.kind
|
||||
)
|
||||
|
||||
|
||||
database.nostrEventDao().insert(nostrEvent)
|
||||
|
||||
// TODO: Get ChatMessageRelayListEvent.KIND for receiverPublicKey.pubkey
|
||||
// receiverPublicKey.relayHint?.url?.let { url ->
|
||||
// database.broadcastNostrEventRequestDao().insert(
|
||||
// listOf(
|
||||
// BroadcastNostrEventRequest(
|
||||
// nostrEventId = nostrEvent.id,
|
||||
// relayURL = url
|
||||
// )
|
||||
// )
|
||||
// )
|
||||
// }
|
||||
|
||||
|
||||
database.giftWrapMessageDao().upsert(
|
||||
GiftWrapMessage(
|
||||
id = giftWrapEvent.id,
|
||||
nostrEventId = nostrEvent.id,
|
||||
content = giftWrapEvent.content,
|
||||
createdAt = Instant.fromEpochSeconds(giftWrapEvent.createdAt),
|
||||
signature = giftWrapEvent.sig,
|
||||
publicKey = giftWrapEvent.pubKey,
|
||||
kind = giftWrapEvent.kind,
|
||||
receiverPublicKey = receiverPublicKey.pubKey,
|
||||
receiverRelayHit = receiverPublicKey.relayHint?.url
|
||||
)
|
||||
)
|
||||
|
||||
val giftWrapSeal = GiftWrapSeal(
|
||||
id = sealedRumorEvent.id,
|
||||
publicKey = sealedRumorEvent.pubKey,
|
||||
content = sealedRumorEvent.content,
|
||||
kind = sealedRumorEvent.kind,
|
||||
tags = sealedRumorEvent.tags,
|
||||
signature = sealedRumorEvent.sig,
|
||||
giftWrapMessageId = giftWrapEvent.id
|
||||
)
|
||||
|
||||
database.giftWrapSealDao().upsert(giftWrapSeal)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "NostrNip17Dao"
|
||||
}
|
||||
|
||||
@@ -34,8 +34,8 @@ data class ChatMessage(
|
||||
val senderPublicKey: String,
|
||||
val isUserMessage: Boolean,
|
||||
|
||||
val giftWrapPayloadId: Long,
|
||||
val chatRoomId: String,
|
||||
val giftWrapPayloadId: String? = null,
|
||||
|
||||
val replyToMessageId: Long? = null,
|
||||
val quotedMessageId: Long? = null,
|
||||
|
||||
@@ -28,7 +28,7 @@ import kotlin.time.Instant
|
||||
ForeignKey(
|
||||
entity = GiftWrapPayload::class,
|
||||
parentColumns = ["id"],
|
||||
childColumns = ["initialGiftWrapUnsignedId"],
|
||||
childColumns = ["initialGiftWrapPayloadId"],
|
||||
onDelete = ForeignKey.CASCADE,
|
||||
),
|
||||
],
|
||||
@@ -69,7 +69,7 @@ data class ChatRoom(
|
||||
// TODO: Add a LastChatMessageRelation
|
||||
// val lastChatMessageId: String? = null,
|
||||
// Might want to focus on the other last messages...
|
||||
val initialGiftWrapUnsignedId: String? = null,
|
||||
val initialGiftWrapPayloadId: Long? = null,
|
||||
|
||||
override val createdAt: Instant = Clock.System.now(),
|
||||
override val updatedAt: Instant = createdAt,
|
||||
@@ -77,7 +77,7 @@ data class ChatRoom(
|
||||
override val viewedAt: Instant? = null,
|
||||
override val deletedAt: Instant? = null,
|
||||
|
||||
): TimestampedEntity, LocalStoreEntity, UserViewableEntity, SoftDeletableEntity {
|
||||
): TimestampedEntity, LocalStoreEntity, UserViewableEntity, SoftDeletableEntity {
|
||||
|
||||
companion object {
|
||||
const val TAG = "ChatRoom"
|
||||
|
||||
@@ -30,8 +30,8 @@ import kotlin.time.Instant
|
||||
],
|
||||
)
|
||||
data class GiftWrapPayload( // TODO: Rename this to GiftWrapPayload...
|
||||
@PrimaryKey
|
||||
val id: HexKey,
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
val id: Long = 0,
|
||||
|
||||
/**
|
||||
* Sender Public Key
|
||||
|
||||
@@ -96,7 +96,7 @@ data class GiftWrapSeal(
|
||||
logger.d("giftWrapPayload: ${giftWrapPayload.toJson()}")
|
||||
|
||||
return GiftWrapPayload(
|
||||
id = giftWrapPayload.id,
|
||||
giftWrapSealId = id,
|
||||
publicKey = giftWrapPayload.pubKey,
|
||||
kind = giftWrapPayload.kind,
|
||||
tags = giftWrapPayload.tags,
|
||||
|
||||
@@ -8,9 +8,15 @@ import ac.cord.auxiliary.compose.database.model.intermdiate.LocalChatMessage
|
||||
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalChatRoom
|
||||
import ac.cord.auxiliary.compose.repository.ChatRepository
|
||||
import co.touchlab.kermit.Logger
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
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.signers.NostrSignerSync
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.taggedUsers
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.seals.SealedRumorEvent
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
||||
import fr.acinq.lightning.utils.UUID
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@@ -49,15 +55,32 @@ class DatabaseChatRepository(
|
||||
|
||||
override suspend fun sendChatMessage(
|
||||
text: String,
|
||||
chatRoom: ChatRoom
|
||||
localChatRoom: LocalChatRoom,
|
||||
messageType: Kind
|
||||
) {
|
||||
logger.i("sendChatMessage($text): $chatRoom")
|
||||
logger.i("sendChatMessage($text): $localChatRoom")
|
||||
|
||||
val receiverTags = localChatRoom.participants.filter { participant ->
|
||||
participant.participant.participantPublicKey != localChatRoom.chatRoom.userPublicKey
|
||||
}.map { participant ->
|
||||
PTag.assemble(participant.participant.participantPublicKey, null) // TODO: Get the relays associated with the participant...
|
||||
}
|
||||
|
||||
val giftWrapPayloadId = database.giftWrapPayloadDao().upsert(
|
||||
GiftWrapPayload(
|
||||
kind = messageType,
|
||||
tags = receiverTags.toTypedArray(), // TODO: Get participants...
|
||||
content = text,
|
||||
publicKey = localChatRoom.chatRoom.userPublicKey
|
||||
)
|
||||
)
|
||||
database.chatMessageDao().upsert(
|
||||
ChatMessage(
|
||||
content = text,
|
||||
chatRoomId = chatRoom.id,
|
||||
senderPublicKey = chatRoom.userPublicKey,
|
||||
isUserMessage = true
|
||||
chatRoomId = localChatRoom.chatRoom.id,
|
||||
senderPublicKey = localChatRoom.chatRoom.userPublicKey,
|
||||
isUserMessage = true,
|
||||
giftWrapPayloadId = giftWrapPayloadId
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -66,9 +89,67 @@ class DatabaseChatRepository(
|
||||
publicKey: String,
|
||||
subject: String
|
||||
): ChatRoom? {
|
||||
// TODO: Update chatRoom with subject...
|
||||
return null
|
||||
}
|
||||
|
||||
override suspend fun observeUnsealedGiftWrapPayloads(publicKey: HexKey): Flow<GiftWrapPayload?> {
|
||||
return database.giftWrapPayloadDao().observeUnsealedGiftWrapPayloads(publicKey)
|
||||
}
|
||||
|
||||
override suspend fun sealGiftWrapPayload(
|
||||
giftWrapPayload: GiftWrapPayload,
|
||||
nostrSignerSync: NostrSignerSync
|
||||
) {
|
||||
val giftWrapPayloadEvent = nostrSignerSync.signNormal<Event>(
|
||||
createdAt = giftWrapPayload.createdAt.epochSeconds,
|
||||
kind = giftWrapPayload.kind,
|
||||
tags = giftWrapPayload.tags,
|
||||
content = giftWrapPayload.content
|
||||
)
|
||||
|
||||
giftWrapPayload.tags.taggedUsers().forEachIndexed { index, receiverPublicKey ->
|
||||
val payload = giftWrapPayloadEvent.toJson()
|
||||
|
||||
val sealedRumorEvent = nostrSignerSync.signNormal<SealedRumorEvent>(
|
||||
createdAt = TimeUtils.randomWithTwoDays(),
|
||||
kind = SealedRumorEvent.KIND,
|
||||
tags = emptyArray(),
|
||||
content = nostrSignerSync.nip44Encrypt(
|
||||
plaintext = payload,
|
||||
toPublicKey = receiverPublicKey.pubKey
|
||||
)
|
||||
)
|
||||
|
||||
val giftWrapEvent = nostrSignerSync.signNormal<GiftWrapEvent>(
|
||||
createdAt = TimeUtils.randomWithTwoDays(),
|
||||
kind = GiftWrapEvent.KIND,
|
||||
tags = arrayOf(
|
||||
PTag.assemble(receiverPublicKey.pubKey, receiverPublicKey.relayHint)
|
||||
),
|
||||
content = nostrSignerSync.nip44Encrypt(
|
||||
plaintext = sealedRumorEvent.toJson(),
|
||||
toPublicKey = receiverPublicKey.pubKey
|
||||
)
|
||||
)
|
||||
|
||||
database.nostrNip17Dao().persistAndBroadcastGiftWrap(
|
||||
sealedRumorEvent = sealedRumorEvent,
|
||||
giftWrapEvent = giftWrapEvent,
|
||||
receiverPublicKey = receiverPublicKey
|
||||
)
|
||||
|
||||
if (index == 0 && giftWrapPayload.giftWrapSealId == null) {
|
||||
// Only update the giftWrapPayload.giftWrapSealId on the first seal...
|
||||
database.giftWrapPayloadDao().upsert(
|
||||
giftWrapPayload.copy(
|
||||
giftWrapSealId = sealedRumorEvent.id
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "DatabaseChatRepository"
|
||||
}
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
package ac.cord.auxiliary.compose.repository
|
||||
|
||||
import ac.cord.auxiliary.compose.database.model.ChatMessage
|
||||
import ac.cord.auxiliary.compose.database.model.ChatRoom
|
||||
import ac.cord.auxiliary.compose.database.model.GiftWrapPayload
|
||||
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalChatMessage
|
||||
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalChatRoom
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Kind
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerSync
|
||||
import com.vitorpamplona.quartz.nip17Dm.messages.ChatMessageEvent
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
|
||||
@@ -17,10 +23,18 @@ interface ChatRepository {
|
||||
|
||||
suspend fun getOrCreateChatRoom(publicKey: String, defaultSubject: String? = null): LocalChatRoom?
|
||||
|
||||
suspend fun sendChatMessage(text: String, chatRoom: ChatRoom)
|
||||
suspend fun sendChatMessage(
|
||||
text: String,
|
||||
localChatRoom: LocalChatRoom,
|
||||
messageType: Kind = ChatMessageEvent.KIND
|
||||
)
|
||||
|
||||
fun updateChatRoomSubject(publicKey: String, subject: String): ChatRoom?
|
||||
|
||||
suspend fun observeUnsealedGiftWrapPayloads(publicKey: HexKey): Flow<GiftWrapPayload?>
|
||||
|
||||
suspend fun sealGiftWrapPayload(giftWrapPayload: GiftWrapPayload, nostrSignerSync: NostrSignerSync)
|
||||
|
||||
companion object {
|
||||
val NO_OP_CHAT_REPOSITORY = object: ChatRepository {
|
||||
override suspend fun getChatRoomListByPublicKey(publicKey: String): List<LocalChatRoom> {
|
||||
@@ -46,7 +60,8 @@ interface ChatRepository {
|
||||
|
||||
override suspend fun sendChatMessage(
|
||||
text: String,
|
||||
chatRoom: ChatRoom
|
||||
localChatRoom: LocalChatRoom,
|
||||
messageType: Kind
|
||||
) {
|
||||
// TODO("Not yet implemented")
|
||||
}
|
||||
@@ -54,6 +69,17 @@ interface ChatRepository {
|
||||
override fun updateChatRoomSubject(publicKey: String, subject: String): ChatRoom? {
|
||||
return null
|
||||
}
|
||||
|
||||
override suspend fun observeUnsealedGiftWrapPayloads(publicKey: HexKey): Flow<GiftWrapPayload?> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun sealGiftWrapPayload(
|
||||
giftWrapPayload: GiftWrapPayload,
|
||||
nostrSignerSync: NostrSignerSync
|
||||
) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +1,31 @@
|
||||
package ac.cord.auxiliary.compose.ui.composable
|
||||
|
||||
import ac.cord.auxiliary.compose.database.model.ChatRoom
|
||||
import ac.cord.auxiliary.compose.database.model.Participant
|
||||
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalChatRoom
|
||||
import ac.cord.auxiliary.compose.repository.ChatRepository
|
||||
import ac.cord.auxiliary.compose.repository.NostrRepository
|
||||
import ac.cord.auxiliary.compose.ui.composable.widgets.LoadingDataIndicator
|
||||
import ac.cord.auxiliary.compose.ui.composable.widgets.profile.ProfileColor
|
||||
import ac.cord.auxiliary.compose.ui.theme.AuxTheme
|
||||
import ac.cord.auxiliary.compose.ui.view.model.ChatRoomDetailMessageListViewModel
|
||||
import ac.cord.auxiliary.compose.ui.view.model.ChatRoomDetailViewModel
|
||||
import ac.cord.auxiliary.compose.ui.view.state.ChatRoomDetailMessageListUIState
|
||||
import ac.cord.auxiliary.compose.ui.view.state.ChatRoomDetailUIState
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.foundation.text.input.rememberTextFieldState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.AlternateEmail
|
||||
import androidx.compose.material.icons.filled.BrowseGallery
|
||||
import androidx.compose.material.icons.filled.Collections
|
||||
import androidx.compose.material.icons.filled.Email
|
||||
import androidx.compose.material.icons.filled.EmojiFlags
|
||||
import androidx.compose.material.icons.filled.InsertEmoticon
|
||||
import androidx.compose.material.icons.filled.LocationOn
|
||||
import androidx.compose.material.icons.filled.Mic
|
||||
import androidx.compose.material.icons.filled.Pin
|
||||
import androidx.compose.material.icons.filled.PinDrop
|
||||
import androidx.compose.material.icons.filled.RecordVoiceOver
|
||||
import androidx.compose.material.icons.filled.Send
|
||||
import androidx.compose.material3.BottomAppBar
|
||||
import androidx.compose.material3.BottomAppBarDefaults
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.FlexibleBottomAppBar
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@@ -52,25 +34,17 @@ import androidx.compose.material3.OutlinedTextFieldDefaults
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import kotlin.time.Clock
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -300,7 +274,7 @@ private fun ChatRoomDetailScreenPreview() {
|
||||
id = "",
|
||||
userPublicKey = "",
|
||||
subject = "Message title",
|
||||
initialGiftWrapUnsignedId = ""
|
||||
initialGiftWrapPayloadId = 0
|
||||
),
|
||||
)
|
||||
),
|
||||
|
||||
@@ -105,6 +105,7 @@ fun AuxNavHost(
|
||||
factory = NavigationViewModel.factory(
|
||||
initialNavigationUIState = NavigationUIState.Loading,
|
||||
nostrRepository = databaseNostrRepository,
|
||||
chatRepository = databaseChatRepository,
|
||||
relayRepository = databaseNostrRepository,
|
||||
scope = applicationIOScope
|
||||
)
|
||||
|
||||
@@ -260,7 +260,7 @@ class ChatRoomDetailMessageListViewModel(
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
chatRepository.sendChatMessage(
|
||||
text = text,
|
||||
chatRoom = localChatRoom.chatRoom
|
||||
localChatRoom = localChatRoom
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package ac.cord.auxiliary.compose.ui.view.model
|
||||
|
||||
import ac.cord.auxiliary.compose.database.model.BroadcastNostrEventRequest
|
||||
import ac.cord.auxiliary.compose.database.model.GiftWrapSeal
|
||||
import ac.cord.auxiliary.compose.database.model.NostrEvent
|
||||
import ac.cord.auxiliary.compose.database.model.SynchronizeNostrEventRequest
|
||||
import ac.cord.auxiliary.compose.database.model.types.SynchronizationFilter
|
||||
import ac.cord.auxiliary.compose.managers.SeedManager
|
||||
import ac.cord.auxiliary.compose.network.dto.RelayDTO
|
||||
import ac.cord.auxiliary.compose.network.dto.toRelayDTO
|
||||
import ac.cord.auxiliary.compose.network.relays.RelayPool.Companion.PUBLISH_TIMEOUT
|
||||
import ac.cord.auxiliary.compose.network.relays.RelaysSocketManager
|
||||
@@ -13,6 +13,7 @@ import ac.cord.auxiliary.compose.network.sockets.NostrIncomingMessage
|
||||
import ac.cord.auxiliary.compose.network.sockets.NostrSocketClientFactory
|
||||
import ac.cord.auxiliary.compose.nostr.Relays
|
||||
import ac.cord.auxiliary.compose.repository.CachingImportRepository
|
||||
import ac.cord.auxiliary.compose.repository.ChatRepository
|
||||
import ac.cord.auxiliary.compose.repository.NostrRepository
|
||||
import ac.cord.auxiliary.compose.repository.RelayRepository
|
||||
import ac.cord.auxiliary.compose.ui.view.state.NavigationUIState
|
||||
@@ -29,9 +30,15 @@ import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.CloseCmd
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.ReqCmd
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerSync
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.taggedUsers
|
||||
import com.vitorpamplona.quartz.nip17Dm.NIP17Factory
|
||||
import com.vitorpamplona.quartz.nip17Dm.messages.ChatMessageEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.encryption.PrivateTagsInContent
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.rumors.Rumor
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.seals.SealedRumorEvent
|
||||
import com.vitorpamplona.quartz.nip77Negentropy.NegCloseCmd
|
||||
import com.vitorpamplona.quartz.nip77Negentropy.NegOpenCmd
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
@@ -52,6 +59,7 @@ import kotlin.time.Instant
|
||||
class NavigationViewModel(
|
||||
initialNavigationUIState: NavigationUIState,
|
||||
val nostrRepository: NostrRepository,
|
||||
val chatRepository: ChatRepository,
|
||||
val relayRepository: RelayRepository,
|
||||
val scope: CoroutineScope,
|
||||
): ViewModel() {
|
||||
@@ -70,6 +78,7 @@ class NavigationViewModel(
|
||||
fun factory(
|
||||
initialNavigationUIState: NavigationUIState,
|
||||
nostrRepository: NostrRepository,
|
||||
chatRepository: ChatRepository,
|
||||
relayRepository: RelayRepository,
|
||||
scope: CoroutineScope,
|
||||
): ViewModelProvider.Factory = viewModelFactory {
|
||||
@@ -77,6 +86,7 @@ class NavigationViewModel(
|
||||
NavigationViewModel(
|
||||
initialNavigationUIState = initialNavigationUIState,
|
||||
nostrRepository = nostrRepository,
|
||||
chatRepository = chatRepository,
|
||||
relayRepository = relayRepository,
|
||||
scope = scope
|
||||
)
|
||||
@@ -93,7 +103,7 @@ class NavigationViewModel(
|
||||
|
||||
init {
|
||||
observeUnsignedNostrEvents()
|
||||
// TODO: observePendingUnsignedChatMessages
|
||||
observeUnsealedGiftWrapPayloads()
|
||||
observePendingBroadcastNostrEventRequests()
|
||||
observeProfile()
|
||||
observePendingSyncNostrEventRequests()
|
||||
@@ -141,6 +151,29 @@ class NavigationViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeUnsealedGiftWrapPayloads() {
|
||||
val tempSigner = NostrSignerSync(
|
||||
SeedManager.activeKeyPair()
|
||||
)
|
||||
scope.launch(Dispatchers.IO) {
|
||||
logger.i { "observeUnsealedGiftWrapPayloads" }
|
||||
chatRepository.observeUnsealedGiftWrapPayloads(
|
||||
publicKey = SeedManager.activePublicKey().toHexKey()
|
||||
).distinctUntilChanged().collect { giftWrapPayloadOrNull ->
|
||||
scope.launch(Dispatchers.IO) {
|
||||
giftWrapPayloadOrNull?.let { giftWrapPayload ->
|
||||
logger.d("seal and deliver giftWrapPayload: $giftWrapPayload")
|
||||
|
||||
chatRepository.sealGiftWrapPayload(
|
||||
giftWrapPayload,
|
||||
nostrSignerSync = tempSigner
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun observePendingSyncNostrEventRequests() {
|
||||
logger.i { "observePendingSyncNostrEventRequests" }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user