Add chatMessage relations

This commit is contained in:
Kgothatso Ngako
2026-06-03 02:57:36 +02:00
parent 22800bdabb
commit ff7fe8bc4b
11 changed files with 434 additions and 11 deletions

View File

@@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "24fc70446262631361657ac068483160",
"identityHash": "a7a5014361138681b4a2123531f2a0e3",
"entities": [
{
"tableName": "BroadcastNostrEventReceipt",
@@ -384,6 +384,222 @@
}
]
},
{
"tableName": "ChatMessageBroadcastNostrEventRequestRelation",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `chatMessageId` INTEGER NOT NULL, `broadcastNostrEventRequestId` INTEGER NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, FOREIGN KEY(`chatMessageId`) REFERENCES `ChatMessage`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`broadcastNostrEventRequestId`) REFERENCES `BroadcastNostrEventRequest`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "chatMessageId",
"columnName": "chatMessageId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "broadcastNostrEventRequestId",
"columnName": "broadcastNostrEventRequestId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "createdAt",
"columnName": "createdAt",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "updatedAt",
"columnName": "updatedAt",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "savedAt",
"columnName": "savedAt",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"foreignKeys": [
{
"table": "ChatMessage",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"chatMessageId"
],
"referencedColumns": [
"id"
]
},
{
"table": "BroadcastNostrEventRequest",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"broadcastNostrEventRequestId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "ChatMessageBroadcastNostrEventReceiptRelation",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `chatMessageId` INTEGER NOT NULL, `broadcastNostrEventReceiptId` INTEGER NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, FOREIGN KEY(`chatMessageId`) REFERENCES `ChatMessage`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`broadcastNostrEventReceiptId`) REFERENCES `BroadcastNostrEventReceipt`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "chatMessageId",
"columnName": "chatMessageId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "broadcastNostrEventReceiptId",
"columnName": "broadcastNostrEventReceiptId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "createdAt",
"columnName": "createdAt",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "updatedAt",
"columnName": "updatedAt",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "savedAt",
"columnName": "savedAt",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"foreignKeys": [
{
"table": "ChatMessage",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"chatMessageId"
],
"referencedColumns": [
"id"
]
},
{
"table": "BroadcastNostrEventReceipt",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"broadcastNostrEventReceiptId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "ChatMessageNostrEventRelation",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `chatMessageId` INTEGER NOT NULL, `nostrEventId` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, FOREIGN KEY(`chatMessageId`) REFERENCES `ChatMessage`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`nostrEventId`) REFERENCES `NostrEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "chatMessageId",
"columnName": "chatMessageId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "nostrEventId",
"columnName": "nostrEventId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "createdAt",
"columnName": "createdAt",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "updatedAt",
"columnName": "updatedAt",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "savedAt",
"columnName": "savedAt",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"foreignKeys": [
{
"table": "ChatMessage",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"chatMessageId"
],
"referencedColumns": [
"id"
]
},
{
"table": "NostrEvent",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"nostrEventId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "ChatRoom",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `userPublicKey` TEXT NOT NULL, `subject` TEXT, `initialGiftWrapPayloadId` INTEGER, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `viewedAt` INTEGER, `deletedAt` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`userPublicKey`) REFERENCES `Profile`(`publicKey`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`initialGiftWrapPayloadId`) REFERENCES `GiftWrapPayload`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
@@ -2631,7 +2847,7 @@
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '24fc70446262631361657ac068483160')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'a7a5014361138681b4a2123531f2a0e3')"
]
}
}

View File

@@ -4,6 +4,9 @@ import ac.cord.auxiliary.compose.database.converters.AuxConverters
import ac.cord.auxiliary.compose.database.dao.BroadcastNostrEventReceiptDao
import ac.cord.auxiliary.compose.database.dao.BroadcastNostrEventRequestDao
import ac.cord.auxiliary.compose.database.dao.ChatMessageDao
import ac.cord.auxiliary.compose.database.dao.ChatMessageBroadcastNostrEventRequestRelationDao
import ac.cord.auxiliary.compose.database.dao.ChatMessageBroadcastNostrEventReceiptRelationDao
import ac.cord.auxiliary.compose.database.dao.ChatMessageNostrEventRelationDao
import ac.cord.auxiliary.compose.database.dao.ChatRoomDao
import ac.cord.auxiliary.compose.database.dao.ConnectionDao
import ac.cord.auxiliary.compose.database.dao.GiftWrapMessageDao
@@ -32,6 +35,9 @@ import ac.cord.auxiliary.compose.database.dao.ZapDao
import ac.cord.auxiliary.compose.database.model.BroadcastNostrEventReceipt
import ac.cord.auxiliary.compose.database.model.BroadcastNostrEventRequest
import ac.cord.auxiliary.compose.database.model.ChatMessage
import ac.cord.auxiliary.compose.database.model.ChatMessageBroadcastNostrEventReceiptRelation
import ac.cord.auxiliary.compose.database.model.ChatMessageBroadcastNostrEventRequestRelation
import ac.cord.auxiliary.compose.database.model.ChatMessageNostrEventRelation
import ac.cord.auxiliary.compose.database.model.ChatRoom
import ac.cord.auxiliary.compose.database.model.Connection
import ac.cord.auxiliary.compose.database.model.GiftWrapMessage
@@ -70,6 +76,9 @@ val GENESIS_AT = Instant.fromEpochMilliseconds(1231006505000L)
BroadcastNostrEventRequest::class,
Connection::class,
ChatMessage::class,
ChatMessageBroadcastNostrEventRequestRelation::class,
ChatMessageBroadcastNostrEventReceiptRelation::class,
ChatMessageNostrEventRelation::class,
ChatRoom::class,
InReplyToRelation::class,
GiftWrapMessage::class,
@@ -102,6 +111,12 @@ abstract class AuxDatabase: RoomDatabase() {
abstract fun chatMessageDao(): ChatMessageDao
abstract fun chatMessageBroadcastNostrEventRequestRelationDao(): ChatMessageBroadcastNostrEventRequestRelationDao
abstract fun chatMessageBroadcastNostrEventReceiptRelationDao(): ChatMessageBroadcastNostrEventReceiptRelationDao
abstract fun chatMessageNostrEventRelationDao(): ChatMessageNostrEventRelationDao
abstract fun chatRoomDao(): ChatRoomDao
abstract fun connectionDao(): ConnectionDao

View File

@@ -0,0 +1,11 @@
package ac.cord.auxiliary.compose.database.dao
import ac.cord.auxiliary.compose.database.model.ChatMessageBroadcastNostrEventReceiptRelation
import androidx.room3.Dao
import androidx.room3.Upsert
@Dao
interface ChatMessageBroadcastNostrEventReceiptRelationDao {
@Upsert
suspend fun upsert(chatMessageBroadcastNostrEventReceiptRelation: ChatMessageBroadcastNostrEventReceiptRelation)
}

View File

@@ -0,0 +1,12 @@
package ac.cord.auxiliary.compose.database.dao
import ac.cord.auxiliary.compose.database.model.ChatMessageBroadcastNostrEventRequestRelation
import androidx.room3.Dao
import androidx.room3.Upsert
@Dao
interface ChatMessageBroadcastNostrEventRequestRelationDao {
@Upsert
suspend fun upsert(chatMessageBroadcastNostrEventRequestRelation: ChatMessageBroadcastNostrEventRequestRelation)
}

View File

@@ -0,0 +1,12 @@
package ac.cord.auxiliary.compose.database.dao
import ac.cord.auxiliary.compose.database.model.ChatMessageNostrEventRelation
import androidx.room3.Dao
import androidx.room3.Upsert
@Dao
interface ChatMessageNostrEventRelationDao {
@Upsert
suspend fun upsert(chatMessageNostrEventRelation: ChatMessageNostrEventRelation)
}

View File

@@ -0,0 +1,37 @@
package ac.cord.auxiliary.compose.database.model
import ac.cord.auxiliary.compose.database.model.traits.LocalStoreEntity
import ac.cord.auxiliary.compose.database.model.traits.TimestampedEntity
import androidx.room3.Entity
import androidx.room3.ForeignKey
import androidx.room3.PrimaryKey
import kotlin.time.Clock
import kotlin.time.Instant
@Entity(
foreignKeys = [
ForeignKey(
entity = ChatMessage::class,
parentColumns = ["id"],
childColumns = ["chatMessageId"],
onDelete = ForeignKey.CASCADE,
),
ForeignKey(
entity = BroadcastNostrEventReceipt::class,
parentColumns = ["id"],
childColumns = ["broadcastNostrEventReceiptId"],
onDelete = ForeignKey.CASCADE,
),
]
)
data class ChatMessageBroadcastNostrEventReceiptRelation(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
val chatMessageId: Long,
val broadcastNostrEventReceiptId: Long,
override val createdAt: Instant = Clock.System.now(),
override val updatedAt: Instant = Clock.System.now(),
override val savedAt: Instant = Clock.System.now()
): TimestampedEntity, LocalStoreEntity

View File

@@ -0,0 +1,37 @@
package ac.cord.auxiliary.compose.database.model
import ac.cord.auxiliary.compose.database.model.traits.LocalStoreEntity
import ac.cord.auxiliary.compose.database.model.traits.TimestampedEntity
import androidx.room3.Entity
import androidx.room3.ForeignKey
import androidx.room3.PrimaryKey
import kotlin.time.Clock
import kotlin.time.Instant
@Entity(
foreignKeys = [
ForeignKey(
entity = ChatMessage::class,
parentColumns = ["id"],
childColumns = ["chatMessageId"],
onDelete = ForeignKey.CASCADE,
),
ForeignKey(
entity = BroadcastNostrEventRequest::class,
parentColumns = ["id"],
childColumns = ["broadcastNostrEventRequestId"],
onDelete = ForeignKey.CASCADE,
),
]
)
data class ChatMessageBroadcastNostrEventRequestRelation(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
val chatMessageId: Long,
val broadcastNostrEventRequestId: Long,
override val createdAt: Instant = Clock.System.now(),
override val updatedAt: Instant = Clock.System.now(),
override val savedAt: Instant = Clock.System.now()
): TimestampedEntity, LocalStoreEntity

View File

@@ -0,0 +1,41 @@
package ac.cord.auxiliary.compose.database.model
import ac.cord.auxiliary.compose.database.model.traits.BroadcastableEntity
import ac.cord.auxiliary.compose.database.model.traits.LocalStoreEntity
import ac.cord.auxiliary.compose.database.model.traits.NostrEventEntity
import ac.cord.auxiliary.compose.database.model.traits.SoftDeletableEntity
import ac.cord.auxiliary.compose.database.model.traits.TimestampedEntity
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(
foreignKeys = [
ForeignKey(
entity = ChatMessage::class,
parentColumns = ["id"],
childColumns = ["chatMessageId"],
onDelete = ForeignKey.CASCADE,
),
ForeignKey(
entity = NostrEvent::class,
parentColumns = ["id"],
childColumns = ["nostrEventId"],
onDelete = ForeignKey.CASCADE,
),
]
)
data class ChatMessageNostrEventRelation(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
val chatMessageId: Long,
val nostrEventId: HexKey,
override val createdAt: Instant = Clock.System.now(),
override val updatedAt: Instant = Clock.System.now(),
override val savedAt: Instant = Clock.System.now(),
): TimestampedEntity, LocalStoreEntity

View File

@@ -26,7 +26,7 @@ import kotlin.time.Instant
parentColumns = ["id"],
childColumns = ["giftWrapSealId"],
onDelete = ForeignKey.CASCADE,
),
)
],
)
data class GiftWrapPayload( // TODO: Rename this to GiftWrapPayload...

View File

@@ -1,10 +1,10 @@
package ac.cord.auxiliary.compose.database.model.intermdiate
import ac.cord.auxiliary.compose.database.model.ChatMessage
import ac.cord.auxiliary.compose.database.model.ChatRoom
import ac.cord.auxiliary.compose.database.model.Mention
import ac.cord.auxiliary.compose.database.model.Participant
import ac.cord.auxiliary.compose.database.model.Post
import ac.cord.auxiliary.compose.database.model.ChatMessageBroadcastNostrEventReceiptRelation
import ac.cord.auxiliary.compose.database.model.ChatMessageBroadcastNostrEventRequestRelation
import ac.cord.auxiliary.compose.database.model.ChatMessageNostrEventRelation
import ac.cord.auxiliary.compose.database.model.GiftWrapPayload
import ac.cord.auxiliary.compose.database.model.Profile
import androidx.room3.Embedded
import androidx.room3.Relation
@@ -17,4 +17,24 @@ data class LocalChatMessage(
entityColumns = ["publicKey"]
)
val profile: Profile?,
@Relation(
parentColumns = ["id"],
entityColumns = ["chatMessageId"]
)
val chatMessageBroadcastNostrEventReceiptRelation: ChatMessageBroadcastNostrEventReceiptRelation? = null,
@Relation(
parentColumns = ["id"],
entityColumns = ["chatMessageId"]
)
val chatMessageBroadcastNostrEventRequestRelation: ChatMessageBroadcastNostrEventRequestRelation? = null,
@Relation(
parentColumns = ["id"],
entityColumns = ["chatMessageId"]
)
val chatMessageNostrEventRelation: ChatMessageNostrEventRelation? = null,
)

View File

@@ -21,7 +21,10 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.foundation.text.input.clearText
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AccessTime
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.KeyOff
import androidx.compose.material.icons.filled.LockClock
import androidx.compose.material3.Card
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
@@ -213,10 +216,29 @@ class ChatRoomDetailMessageListViewModel(
style = MaterialTheme.typography.labelSmall
)
if (localChatMessage.chatMessage.isUserMessage) {
Icon(
Icons.Default.KeyOff,
contentDescription = "Message status"
)
// TODO: Add delivered and read receipt..
if (localChatMessage.chatMessageBroadcastNostrEventReceiptRelation != null) {
Icon(
Icons.Default.Check,
contentDescription = "Message sent"
)
} else if (localChatMessage.chatMessageBroadcastNostrEventRequestRelation != null) {
Icon(
Icons.Default.AccessTime,
contentDescription = "Message sent"
)
} else if (localChatMessage.chatMessageNostrEventRelation != null) {
Icon(
Icons.Default.LockClock,
contentDescription = "Message signed and sealed status"
)
} else {
Icon(
Icons.Default.KeyOff,
contentDescription = "Unsealed message status"
)
}
}
}