Add chatroom functionality
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
"formatVersion": 1,
|
"formatVersion": 1,
|
||||||
"database": {
|
"database": {
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"identityHash": "49c580a762a1c128bf5b4fc6f93eeea4",
|
"identityHash": "6eec904dd8f91ab435301e23425eb536",
|
||||||
"entities": [
|
"entities": [
|
||||||
{
|
{
|
||||||
"tableName": "BroadcastNostrEventReceipt",
|
"tableName": "BroadcastNostrEventReceipt",
|
||||||
@@ -274,6 +274,197 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"tableName": "ChatMessage",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `senderPublicKey` TEXT NOT NULL, `isUserMessage` INTEGER NOT NULL, `chatRoomId` TEXT NOT NULL, `replyToEventId` TEXT, `quotedEventId` TEXT, `content` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `viewedAt` INTEGER, `deletedAt` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`chatRoomId`) REFERENCES `ChatRoom`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`id`) REFERENCES `GiftWrapPayload`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "senderPublicKey",
|
||||||
|
"columnName": "senderPublicKey",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isUserMessage",
|
||||||
|
"columnName": "isUserMessage",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "chatRoomId",
|
||||||
|
"columnName": "chatRoomId",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "replyToEventId",
|
||||||
|
"columnName": "replyToEventId",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "quotedEventId",
|
||||||
|
"columnName": "quotedEventId",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "content",
|
||||||
|
"columnName": "content",
|
||||||
|
"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
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "viewedAt",
|
||||||
|
"columnName": "viewedAt",
|
||||||
|
"affinity": "INTEGER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "deletedAt",
|
||||||
|
"columnName": "deletedAt",
|
||||||
|
"affinity": "INTEGER"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "ChatRoom",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"chatRoomId"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"table": "GiftWrapPayload",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "ChatRoom",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `userPublicKey` TEXT NOT NULL, `subject` TEXT, `initialGiftWrapUnsignedId` TEXT NOT NULL, `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(`initialGiftWrapUnsignedId`) REFERENCES `GiftWrapPayload`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "userPublicKey",
|
||||||
|
"columnName": "userPublicKey",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "subject",
|
||||||
|
"columnName": "subject",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "initialGiftWrapUnsignedId",
|
||||||
|
"columnName": "initialGiftWrapUnsignedId",
|
||||||
|
"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
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "viewedAt",
|
||||||
|
"columnName": "viewedAt",
|
||||||
|
"affinity": "INTEGER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "deletedAt",
|
||||||
|
"columnName": "deletedAt",
|
||||||
|
"affinity": "INTEGER"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "Profile",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"userPublicKey"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"publicKey"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"table": "GiftWrapPayload",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"initialGiftWrapUnsignedId"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"tableName": "InReplyToRelation",
|
"tableName": "InReplyToRelation",
|
||||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`replyingNostrEventId` TEXT NOT NULL, `inReplyToNostrEventId` TEXT NOT NULL, `inReplyToProfilePublicKey` TEXT, `inReplyToRootNostrEventId` TEXT, `inReplyToRootProfilePublicKey` TEXT, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `viewedAt` INTEGER, `deletedAt` INTEGER, PRIMARY KEY(`replyingNostrEventId`, `inReplyToNostrEventId`), FOREIGN KEY(`inReplyToProfilePublicKey`) REFERENCES `Profile`(`publicKey`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`inReplyToRootProfilePublicKey`) REFERENCES `Profile`(`publicKey`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`replyingNostrEventId`) REFERENCES `NostrEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`inReplyToNostrEventId`) REFERENCES `NostrEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`inReplyToRootNostrEventId`) REFERENCES `NostrEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`replyingNostrEventId` TEXT NOT NULL, `inReplyToNostrEventId` TEXT NOT NULL, `inReplyToProfilePublicKey` TEXT, `inReplyToRootNostrEventId` TEXT, `inReplyToRootProfilePublicKey` TEXT, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `viewedAt` INTEGER, `deletedAt` INTEGER, PRIMARY KEY(`replyingNostrEventId`, `inReplyToNostrEventId`), FOREIGN KEY(`inReplyToProfilePublicKey`) REFERENCES `Profile`(`publicKey`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`inReplyToRootProfilePublicKey`) REFERENCES `Profile`(`publicKey`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`replyingNostrEventId`) REFERENCES `NostrEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`inReplyToNostrEventId`) REFERENCES `NostrEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`inReplyToRootNostrEventId`) REFERENCES `NostrEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
@@ -399,6 +590,293 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"tableName": "GiftWrapMessage",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `publicKey` TEXT NOT NULL, `kind` INTEGER NOT NULL, `receiverPublicKey` TEXT NOT NULL, `receiverRelayHit` TEXT, `content` TEXT NOT NULL, `signature` TEXT NOT NULL, `nostrEventId` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `viewedAt` INTEGER, `deletedAt` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`nostrEventId`) REFERENCES `NostrEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "publicKey",
|
||||||
|
"columnName": "publicKey",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "kind",
|
||||||
|
"columnName": "kind",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "receiverPublicKey",
|
||||||
|
"columnName": "receiverPublicKey",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "receiverRelayHit",
|
||||||
|
"columnName": "receiverRelayHit",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "content",
|
||||||
|
"columnName": "content",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "signature",
|
||||||
|
"columnName": "signature",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"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
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "viewedAt",
|
||||||
|
"columnName": "viewedAt",
|
||||||
|
"affinity": "INTEGER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "deletedAt",
|
||||||
|
"columnName": "deletedAt",
|
||||||
|
"affinity": "INTEGER"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "NostrEvent",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"nostrEventId"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "GiftWrapSeal",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `publicKey` TEXT NOT NULL, `kind` INTEGER NOT NULL, `tags` TEXT NOT NULL, `content` TEXT NOT NULL, `signature` TEXT NOT NULL, `giftWrapMessageId` TEXT, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `viewedAt` INTEGER, `deletedAt` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`giftWrapMessageId`) REFERENCES `GiftWrapMessage`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "publicKey",
|
||||||
|
"columnName": "publicKey",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "kind",
|
||||||
|
"columnName": "kind",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "tags",
|
||||||
|
"columnName": "tags",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "content",
|
||||||
|
"columnName": "content",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "signature",
|
||||||
|
"columnName": "signature",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "giftWrapMessageId",
|
||||||
|
"columnName": "giftWrapMessageId",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "createdAt",
|
||||||
|
"columnName": "createdAt",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "updatedAt",
|
||||||
|
"columnName": "updatedAt",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "savedAt",
|
||||||
|
"columnName": "savedAt",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "viewedAt",
|
||||||
|
"columnName": "viewedAt",
|
||||||
|
"affinity": "INTEGER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "deletedAt",
|
||||||
|
"columnName": "deletedAt",
|
||||||
|
"affinity": "INTEGER"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "GiftWrapMessage",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"giftWrapMessageId"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "GiftWrapPayload",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `publicKey` TEXT NOT NULL, `kind` INTEGER NOT NULL, `tags` TEXT NOT NULL, `content` TEXT NOT NULL, `quotedEventId` TEXT, `giftWrapSealId` TEXT, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `viewedAt` INTEGER, `deletedAt` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`giftWrapSealId`) REFERENCES `GiftWrapSeal`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "publicKey",
|
||||||
|
"columnName": "publicKey",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "kind",
|
||||||
|
"columnName": "kind",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "tags",
|
||||||
|
"columnName": "tags",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "content",
|
||||||
|
"columnName": "content",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "quotedEventId",
|
||||||
|
"columnName": "quotedEventId",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "giftWrapSealId",
|
||||||
|
"columnName": "giftWrapSealId",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "createdAt",
|
||||||
|
"columnName": "createdAt",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "updatedAt",
|
||||||
|
"columnName": "updatedAt",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "savedAt",
|
||||||
|
"columnName": "savedAt",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "viewedAt",
|
||||||
|
"columnName": "viewedAt",
|
||||||
|
"affinity": "INTEGER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "deletedAt",
|
||||||
|
"columnName": "deletedAt",
|
||||||
|
"affinity": "INTEGER"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "GiftWrapSeal",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"giftWrapSealId"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"tableName": "Mention",
|
"tableName": "Mention",
|
||||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `mentionedPublicKey` TEXT NOT NULL, `mentioningNostrEventId` TEXT, `relayUrl` TEXT, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `deletedAt` INTEGER, `broadcastedAt` INTEGER, FOREIGN KEY(`mentionedPublicKey`) REFERENCES `Profile`(`publicKey`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`mentioningNostrEventId`) REFERENCES `NostrEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `mentionedPublicKey` TEXT NOT NULL, `mentioningNostrEventId` TEXT, `relayUrl` TEXT, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `deletedAt` INTEGER, `broadcastedAt` INTEGER, FOREIGN KEY(`mentionedPublicKey`) REFERENCES `Profile`(`publicKey`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`mentioningNostrEventId`) REFERENCES `NostrEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
@@ -2066,7 +2544,7 @@
|
|||||||
],
|
],
|
||||||
"setupQueries": [
|
"setupQueries": [
|
||||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
"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, '49c580a762a1c128bf5b4fc6f93eeea4')"
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6eec904dd8f91ab435301e23425eb536')"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -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.ConnectionDao
|
||||||
import ac.cord.auxiliary.compose.database.dao.GiftWrapMessageDao
|
import ac.cord.auxiliary.compose.database.dao.GiftWrapMessageDao
|
||||||
import ac.cord.auxiliary.compose.database.dao.GiftWrapSealDao
|
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.InReplyToRelationDao
|
||||||
import ac.cord.auxiliary.compose.database.dao.MentionDao
|
import ac.cord.auxiliary.compose.database.dao.MentionDao
|
||||||
import ac.cord.auxiliary.compose.database.dao.NegentropySynchronizeRequestDao
|
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.Connection
|
||||||
import ac.cord.auxiliary.compose.database.model.GiftWrapMessage
|
import ac.cord.auxiliary.compose.database.model.GiftWrapMessage
|
||||||
import ac.cord.auxiliary.compose.database.model.GiftWrapSeal
|
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.InReplyToRelation
|
||||||
import ac.cord.auxiliary.compose.database.model.Mention
|
import ac.cord.auxiliary.compose.database.model.Mention
|
||||||
import ac.cord.auxiliary.compose.database.model.NegentropySynchronizeRequest
|
import ac.cord.auxiliary.compose.database.model.NegentropySynchronizeRequest
|
||||||
@@ -72,7 +72,7 @@ val GENESIS_AT = Instant.fromEpochMilliseconds(1231006505000L)
|
|||||||
InReplyToRelation::class,
|
InReplyToRelation::class,
|
||||||
GiftWrapMessage::class,
|
GiftWrapMessage::class,
|
||||||
GiftWrapSeal::class,
|
GiftWrapSeal::class,
|
||||||
GiftWrapUnsigned::class,
|
GiftWrapPayload::class,
|
||||||
Mention::class,
|
Mention::class,
|
||||||
NegentropySynchronizeRequest::class,
|
NegentropySynchronizeRequest::class,
|
||||||
NegentropySynchronizeResult::class,
|
NegentropySynchronizeResult::class,
|
||||||
@@ -90,13 +90,7 @@ val GENESIS_AT = Instant.fromEpochMilliseconds(1231006505000L)
|
|||||||
UnsignedNostrEvent::class,
|
UnsignedNostrEvent::class,
|
||||||
Zap::class
|
Zap::class
|
||||||
],
|
],
|
||||||
version = 2,
|
version = 1
|
||||||
autoMigrations = [
|
|
||||||
AutoMigration(
|
|
||||||
from = 1,
|
|
||||||
to = 2
|
|
||||||
)
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
@TypeConverters(AuxConverters::class)
|
@TypeConverters(AuxConverters::class)
|
||||||
abstract class AuxDatabase: RoomDatabase() {
|
abstract class AuxDatabase: RoomDatabase() {
|
||||||
@@ -113,7 +107,7 @@ abstract class AuxDatabase: RoomDatabase() {
|
|||||||
|
|
||||||
abstract fun giftWrapSealDao(): GiftWrapSealDao
|
abstract fun giftWrapSealDao(): GiftWrapSealDao
|
||||||
|
|
||||||
abstract fun giftWrapUnsignedDao(): GiftWrapUnsignedDao
|
abstract fun giftWrapUnsignedDao(): GiftWrapPayloadDao
|
||||||
|
|
||||||
abstract fun inReplyToRelationDao(): InReplyToRelationDao
|
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.ChatRoom
|
||||||
import ac.cord.auxiliary.compose.database.model.Relay
|
import ac.cord.auxiliary.compose.database.model.Relay
|
||||||
|
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalChatRoom
|
||||||
import androidx.room3.Dao
|
import androidx.room3.Dao
|
||||||
import androidx.room3.Query
|
import androidx.room3.Query
|
||||||
import androidx.room3.Upsert
|
import androidx.room3.Upsert
|
||||||
@@ -10,9 +11,11 @@ import kotlinx.coroutines.flow.Flow
|
|||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
interface ChatRoomDao {
|
interface ChatRoomDao {
|
||||||
|
@Query("SELECT * FROM ChatRoom WHERE id = :id")
|
||||||
|
fun findChatRoomById(id: String): ChatRoom?
|
||||||
|
|
||||||
@Query("SELECT * FROM ChatRoom WHERE userPublicKey = :userPublicKey")
|
@Query("SELECT * FROM ChatRoom WHERE userPublicKey = :userPublicKey")
|
||||||
fun observeChatRoomByUserPublicKey(userPublicKey: String): Flow<List<ChatRoom>>
|
fun observeChatRoomByUserPublicKey(userPublicKey: String): Flow<List<LocalChatRoom>>
|
||||||
|
|
||||||
@Upsert
|
@Upsert
|
||||||
suspend fun upsert(chatRoom: ChatRoom)
|
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.AuxDatabase
|
||||||
import ac.cord.auxiliary.compose.database.GENESIS_AT
|
import ac.cord.auxiliary.compose.database.GENESIS_AT
|
||||||
import ac.cord.auxiliary.compose.database.model.BroadcastNostrEventRequest
|
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.Connection
|
||||||
import ac.cord.auxiliary.compose.database.model.Mention
|
import ac.cord.auxiliary.compose.database.model.Mention
|
||||||
import ac.cord.auxiliary.compose.database.model.NegentropySynchronizeRequest
|
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.NostrEventRelay
|
||||||
import ac.cord.auxiliary.compose.database.model.Post
|
import ac.cord.auxiliary.compose.database.model.Post
|
||||||
import ac.cord.auxiliary.compose.database.model.Profile
|
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.UnsignedNostrEvent
|
||||||
import ac.cord.auxiliary.compose.database.model.types.SynchronizationFilter
|
import ac.cord.auxiliary.compose.database.model.types.SynchronizationFilter
|
||||||
import ac.cord.auxiliary.compose.network.serialization.encodeToJsonString
|
import ac.cord.auxiliary.compose.managers.SeedManager
|
||||||
import ac.cord.auxiliary.compose.nostr.Relays
|
import ac.cord.auxiliary.compose.managers.toHex
|
||||||
import androidx.room3.Dao
|
import androidx.room3.Dao
|
||||||
import androidx.room3.Transaction
|
import androidx.room3.Transaction
|
||||||
import androidx.room3.useWriterConnection
|
|
||||||
import co.touchlab.kermit.Logger
|
import co.touchlab.kermit.Logger
|
||||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||||
import com.vitorpamplona.quartz.nip01Core.tags.events.taggedEvents
|
import com.vitorpamplona.quartz.nip01Core.tags.events.taggedEvents
|
||||||
import com.vitorpamplona.quartz.nip01Core.tags.people.taggedUsers
|
import com.vitorpamplona.quartz.nip01Core.tags.people.taggedUsers
|
||||||
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
|
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
|
||||||
import com.vitorpamplona.quartz.nip18Reposts.quotes.QAddressableTag
|
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
||||||
import com.vitorpamplona.quartz.nip18Reposts.quotes.QEventTag
|
import fr.acinq.bitcoin.crypto.musig2.Musig2
|
||||||
import com.vitorpamplona.quartz.nip18Reposts.quotes.firstTaggedQuote
|
|
||||||
import kotlin.math.ceil
|
|
||||||
import kotlin.time.Clock
|
import kotlin.time.Clock
|
||||||
|
import kotlin.time.Instant
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
abstract class NostrDao(
|
abstract class NostrDao(
|
||||||
@@ -124,7 +123,7 @@ abstract class NostrDao(
|
|||||||
} else {
|
} else {
|
||||||
// Nothing else needs to be done
|
// Nothing else needs to be done
|
||||||
val profile = database.profileDao().getProfileByPublicKey(nostrEvent.pubKey)
|
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}")
|
logger.i("This is a placeholder profile might need to get synced...: ${profile.publicKey}")
|
||||||
// Setup the sync here...
|
// Setup the sync here...
|
||||||
} else {
|
} else {
|
||||||
@@ -158,13 +157,13 @@ abstract class NostrDao(
|
|||||||
val profilePublicKeysToSync = mutableMapOf<String, MutableSet<String>>()
|
val profilePublicKeysToSync = mutableMapOf<String, MutableSet<String>>()
|
||||||
val eventIdsToSync = 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...
|
// Find or create profile with the pubKey... if not found submit a sync request...
|
||||||
val profile = database.profileDao().getProfileByPublicKey(nostrEvent.pubKey)
|
val profile = database.profileDao().getProfileByPublicKey(nostrEvent.pubKey)
|
||||||
|
|
||||||
if (profile == null) {
|
if (profile == null) {
|
||||||
val placeHolderProfile = Profile(
|
val placeHolderProfile = Profile(
|
||||||
displayName = "LOADING PROFILE...",
|
displayName = "LOADING...",
|
||||||
publicKey = nostrEvent.pubKey,
|
publicKey = nostrEvent.pubKey,
|
||||||
createdAt = GENESIS_AT,
|
createdAt = GENESIS_AT,
|
||||||
nostrEventId = nostrEvent.id, // Will get overwriting by sync,
|
nostrEventId = nostrEvent.id, // Will get overwriting by sync,
|
||||||
@@ -185,6 +184,67 @@ abstract class NostrDao(
|
|||||||
logger.w("We somehow have an unsignedNostrEvent: $nostrEvent")
|
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...
|
// Sync tagged events and authors...
|
||||||
nostrEvent.tags.taggedEvents().take(4).forEach { taggedEvent -> // We only look at the first 3 tagged events...
|
nostrEvent.tags.taggedEvents().take(4).forEach { taggedEvent -> // We only look at the first 3 tagged events...
|
||||||
val taggedNostrEvent = database.nostrEventDao().getNostrEventById(taggedEvent.eventId)
|
val taggedNostrEvent = database.nostrEventDao().getNostrEventById(taggedEvent.eventId)
|
||||||
@@ -227,7 +287,7 @@ abstract class NostrDao(
|
|||||||
// Save a placeholder
|
// Save a placeholder
|
||||||
database.profileDao().insertPlaceholderProfile(
|
database.profileDao().insertPlaceholderProfile(
|
||||||
Profile(
|
Profile(
|
||||||
displayName = "LOADING PROFILE...",
|
displayName = "LOADING...",
|
||||||
publicKey = taggedUser.pubKey,
|
publicKey = taggedUser.pubKey,
|
||||||
createdAt = GENESIS_AT,
|
createdAt = GENESIS_AT,
|
||||||
nostrEventId = nostrEvent.id, // Will get overwriting by sync,
|
nostrEventId = nostrEvent.id, // Will get overwriting by sync,
|
||||||
@@ -243,11 +303,10 @@ abstract class NostrDao(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
if (recommendedRelay != null) {
|
if (recommendedRelay != null) {
|
||||||
if (profilePublicKeysToSync[relayURL] == null) {
|
if (profilePublicKeysToSync[recommendedRelay] == null) {
|
||||||
profilePublicKeysToSync[relayURL] = mutableSetOf()
|
profilePublicKeysToSync[recommendedRelay] = mutableSetOf()
|
||||||
}
|
}
|
||||||
profilePublicKeysToSync[relayURL]?.add(taggedUser.pubKey)
|
profilePublicKeysToSync[recommendedRelay]?.add(taggedUser.pubKey)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (profilePublicKeysToSync[relayURL] == null) {
|
if (profilePublicKeysToSync[relayURL] == null) {
|
||||||
profilePublicKeysToSync[relayURL] = mutableSetOf()
|
profilePublicKeysToSync[relayURL] = mutableSetOf()
|
||||||
@@ -262,6 +321,100 @@ abstract class NostrDao(
|
|||||||
database.profileDao().upsert(profile)
|
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 ->
|
nostrEvent.toPost()?.let { post ->
|
||||||
if (post.replyToId != null) {
|
if (post.replyToId != null) {
|
||||||
// Find or create placeHolder note that is replyTo
|
// Find or create placeHolder note that is replyTo
|
||||||
@@ -334,7 +487,7 @@ abstract class NostrDao(
|
|||||||
if (quotedProfilePublicKey != null && quotedProfile == null) {
|
if (quotedProfilePublicKey != null && quotedProfile == null) {
|
||||||
database.profileDao().insertPlaceholderProfile(
|
database.profileDao().insertPlaceholderProfile(
|
||||||
Profile(
|
Profile(
|
||||||
displayName = "LOADING PROFILE...",
|
displayName = "LOADING...",
|
||||||
publicKey = quotedProfilePublicKey,
|
publicKey = quotedProfilePublicKey,
|
||||||
createdAt = GENESIS_AT,
|
createdAt = GENESIS_AT,
|
||||||
nostrEventId = nostrEvent.id, // Will get overwriting by sync,
|
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 ->
|
nostrEvent.toInReplyToRelation()?.let { inReplyToRelation ->
|
||||||
|
|
||||||
@@ -442,7 +549,7 @@ abstract class NostrDao(
|
|||||||
if (inReplyToRootProfilePublicKey != null && inReplyToProfile == null) {
|
if (inReplyToRootProfilePublicKey != null && inReplyToProfile == null) {
|
||||||
database.profileDao().insertPlaceholderProfile(
|
database.profileDao().insertPlaceholderProfile(
|
||||||
Profile(
|
Profile(
|
||||||
displayName = "LOADING PROFILE...",
|
displayName = "LOADING...",
|
||||||
publicKey = inReplyToRootProfilePublicKey,
|
publicKey = inReplyToRootProfilePublicKey,
|
||||||
createdAt = GENESIS_AT,
|
createdAt = GENESIS_AT,
|
||||||
nostrEventId = nostrEvent.id, // Will get overwriting by sync,
|
nostrEventId = nostrEvent.id, // Will get overwriting by sync,
|
||||||
@@ -502,7 +609,7 @@ abstract class NostrDao(
|
|||||||
|
|
||||||
if (profile == null) {
|
if (profile == null) {
|
||||||
val placeHolderProfile = Profile(
|
val placeHolderProfile = Profile(
|
||||||
displayName = "LOADING PROFILE...",
|
displayName = "LOADING...",
|
||||||
publicKey = followedHexKEys,
|
publicKey = followedHexKEys,
|
||||||
createdAt = GENESIS_AT,
|
createdAt = GENESIS_AT,
|
||||||
nostrEventId = nostrEvent.id, // Will get overwriting by sync,
|
nostrEventId = nostrEvent.id, // Will get overwriting by sync,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import androidx.room3.Entity
|
|||||||
import androidx.room3.ForeignKey
|
import androidx.room3.ForeignKey
|
||||||
import androidx.room3.PrimaryKey
|
import androidx.room3.PrimaryKey
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
|
import kotlin.time.Clock
|
||||||
import kotlin.time.Instant
|
import kotlin.time.Instant
|
||||||
|
|
||||||
@Entity(
|
@Entity(
|
||||||
@@ -19,9 +20,9 @@ import kotlin.time.Instant
|
|||||||
onDelete = ForeignKey.CASCADE,
|
onDelete = ForeignKey.CASCADE,
|
||||||
),
|
),
|
||||||
ForeignKey(
|
ForeignKey(
|
||||||
entity = GiftWrapUnsigned::class,
|
entity = GiftWrapPayload::class,
|
||||||
parentColumns = ["id"],
|
parentColumns = ["id"],
|
||||||
childColumns = ["giftWrapUnsignedId"],
|
childColumns = ["id"],
|
||||||
onDelete = ForeignKey.CASCADE,
|
onDelete = ForeignKey.CASCADE,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -35,17 +36,15 @@ data class ChatMessage(
|
|||||||
|
|
||||||
val chatRoomId: String,
|
val chatRoomId: String,
|
||||||
|
|
||||||
val giftWrapUnsignedId: String,
|
val replyToEventId: String? = null,
|
||||||
|
val quotedEventId: String? = null,
|
||||||
val replyToEventId: String?,
|
|
||||||
val quotedEventId: String?,
|
|
||||||
|
|
||||||
val content: String,
|
val content: String,
|
||||||
|
|
||||||
override val createdAt: Instant,
|
override val createdAt: Instant = Clock.System.now(),
|
||||||
override val updatedAt: Instant,
|
override val updatedAt: Instant = createdAt,
|
||||||
override val savedAt: Instant,
|
override val savedAt: Instant = Clock.System.now(),
|
||||||
override val viewedAt: Instant?,
|
override val viewedAt: Instant? = null,
|
||||||
override val deletedAt: Instant?, // Who this message should be seen by (used to derive conversationId)
|
override val deletedAt: Instant? = null, // Who this message should be seen by (used to derive conversationId)
|
||||||
): TimestampedEntity, LocalStoreEntity, UserViewableEntity, SoftDeletableEntity {
|
): TimestampedEntity, LocalStoreEntity, UserViewableEntity, SoftDeletableEntity {
|
||||||
}
|
}
|
||||||
@@ -20,7 +20,7 @@ import kotlin.time.Instant
|
|||||||
onDelete = ForeignKey.CASCADE,
|
onDelete = ForeignKey.CASCADE,
|
||||||
),
|
),
|
||||||
ForeignKey(
|
ForeignKey(
|
||||||
entity = GiftWrapUnsigned::class,
|
entity = GiftWrapPayload::class,
|
||||||
parentColumns = ["id"],
|
parentColumns = ["id"],
|
||||||
childColumns = ["initialGiftWrapUnsignedId"],
|
childColumns = ["initialGiftWrapUnsignedId"],
|
||||||
onDelete = ForeignKey.CASCADE,
|
onDelete = ForeignKey.CASCADE,
|
||||||
@@ -50,11 +50,6 @@ data class ChatRoom(
|
|||||||
*/
|
*/
|
||||||
val subject: String?,
|
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
|
* 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.HexKey
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.Kind
|
import com.vitorpamplona.quartz.nip01Core.core.Kind
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.TagArray
|
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
|
import kotlin.time.Instant
|
||||||
|
|
||||||
@Entity(
|
@Entity(
|
||||||
@@ -33,12 +42,19 @@ data class GiftWrapMessage(
|
|||||||
val publicKey: String,
|
val publicKey: String,
|
||||||
|
|
||||||
val kind: Kind = 1059,
|
val kind: Kind = 1059,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Receiver Public Key Derived from tags
|
||||||
|
*
|
||||||
* [
|
* [
|
||||||
* ["p", receiverPublicKey, "<relay-url>"] // receiver
|
* ["p", receiverPublicKey, "<relay-url>"] // receiver
|
||||||
* ]
|
* ]
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
val tags: Array<Array<String>>,
|
val receiverPublicKey: String,
|
||||||
|
|
||||||
|
val receiverRelayHit: String?,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* encrypt(GiftWrapSeal, randomPrivateKey, receiverPublicKey)
|
* encrypt(GiftWrapSeal, randomPrivateKey, receiverPublicKey)
|
||||||
@@ -57,47 +73,50 @@ data class GiftWrapMessage(
|
|||||||
/**
|
/**
|
||||||
* randomTimeUpTo2DaysInThePast
|
* randomTimeUpTo2DaysInThePast
|
||||||
*/
|
*/
|
||||||
override val createdAt: Instant,
|
override val createdAt: Instant = Clock.System.now(),
|
||||||
override val updatedAt: Instant,
|
override val updatedAt: Instant = createdAt,
|
||||||
override val savedAt: Instant,
|
override val savedAt: Instant = Clock.System.now(),
|
||||||
override val viewedAt: Instant?,
|
override val viewedAt: Instant? = null,
|
||||||
override val deletedAt: Instant?, // Who this message should be seen by (used to derive conversationId)
|
override val deletedAt: Instant? = null, // Who this message should be seen by (used to derive conversationId)
|
||||||
): NostrEventEntity, TimestampedEntity, LocalStoreEntity, UserViewableEntity, SoftDeletableEntity {
|
): 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
|
val nostrSigner = NostrSignerInternal(
|
||||||
if (id != other.id) return false
|
keyPair = KeyPair(privKey = privateKey)
|
||||||
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
|
|
||||||
|
|
||||||
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
|
return null
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,6 +9,9 @@ import androidx.room3.ForeignKey
|
|||||||
import androidx.room3.PrimaryKey
|
import androidx.room3.PrimaryKey
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.Kind
|
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
|
import kotlin.time.Instant
|
||||||
|
|
||||||
@Entity(
|
@Entity(
|
||||||
@@ -21,7 +24,7 @@ import kotlin.time.Instant
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
data class GiftWrapUnsigned(
|
data class GiftWrapPayload( // TODO: Rename this to GiftWrapPayload...
|
||||||
@PrimaryKey
|
@PrimaryKey
|
||||||
val id: HexKey,
|
val id: HexKey,
|
||||||
|
|
||||||
@@ -31,9 +34,9 @@ data class GiftWrapUnsigned(
|
|||||||
val publicKey: String,
|
val publicKey: String,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Chat Message Kind
|
* Payload Event Kind
|
||||||
*/
|
*/
|
||||||
val kind: Kind = 14,
|
val kind: Kind,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tags for the unsigned giftwrap
|
* Tags for the unsigned giftwrap
|
||||||
@@ -54,7 +57,7 @@ data class GiftWrapUnsigned(
|
|||||||
* content MUST be plain text. Fields id and created_at are required.
|
* content MUST be plain text. Fields id and created_at are required.
|
||||||
*/
|
*/
|
||||||
val content: String,
|
val content: String,
|
||||||
val quotedEventId: String?,
|
val quotedEventId: String? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Associated GiftWrapSeal
|
* Associated GiftWrapSeal
|
||||||
@@ -64,17 +67,28 @@ data class GiftWrapUnsigned(
|
|||||||
/**
|
/**
|
||||||
* Current time
|
* Current time
|
||||||
*/
|
*/
|
||||||
override val createdAt: Instant,
|
override val createdAt: Instant = Clock.System.now(),
|
||||||
override val updatedAt: Instant,
|
override val updatedAt: Instant = createdAt,
|
||||||
override val savedAt: Instant,
|
override val savedAt: Instant = Clock.System.now(),
|
||||||
override val viewedAt: Instant?,
|
override val viewedAt: Instant? = null,
|
||||||
override val deletedAt: Instant?, // Who this message should be seen by (used to derive conversationId)
|
override val deletedAt: Instant? = null, // Who this message should be seen by (used to derive conversationId)
|
||||||
): TimestampedEntity, LocalStoreEntity, UserViewableEntity, SoftDeletableEntity {
|
): 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 {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (other == null || this::class != other::class) return false
|
if (other == null || this::class != other::class) return false
|
||||||
|
|
||||||
other as GiftWrapUnsigned
|
other as GiftWrapPayload
|
||||||
|
|
||||||
if (kind != other.kind) return false
|
if (kind != other.kind) return false
|
||||||
if (id != other.id) return false
|
if (id != other.id) return false
|
||||||
@@ -9,7 +9,10 @@ import androidx.room3.ForeignKey
|
|||||||
import androidx.room3.PrimaryKey
|
import androidx.room3.PrimaryKey
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.Kind
|
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
|
import kotlin.time.Instant
|
||||||
|
|
||||||
@Entity(
|
@Entity(
|
||||||
@@ -56,12 +59,48 @@ data class GiftWrapSeal(
|
|||||||
/**
|
/**
|
||||||
* randomTimeUpTo2DaysInThePast()
|
* randomTimeUpTo2DaysInThePast()
|
||||||
*/
|
*/
|
||||||
override val createdAt: Instant,
|
override val createdAt: Instant = Clock.System.now(),
|
||||||
override val updatedAt: Instant,
|
override val updatedAt: Instant = createdAt,
|
||||||
override val savedAt: Instant,
|
override val savedAt: Instant = Clock.System.now(),
|
||||||
override val viewedAt: Instant?,
|
override val viewedAt: Instant? = null,
|
||||||
override val deletedAt: Instant?, // Who this message should be seen by (used to derive conversationId)
|
override val deletedAt: Instant? = null, // Who this message should be seen by (used to derive conversationId)
|
||||||
): TimestampedEntity, LocalStoreEntity, UserViewableEntity, SoftDeletableEntity {
|
): 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 {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (other == null || this::class != other::class) return false
|
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.SoftDeletableEntity
|
||||||
import ac.cord.auxiliary.compose.database.model.traits.TimestampedEntity
|
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.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.ContentParser
|
||||||
import ac.cord.auxiliary.compose.ui.composable.widgets.content.ContentSegment
|
import ac.cord.auxiliary.compose.ui.composable.widgets.content.ContentSegment
|
||||||
import androidx.room3.Entity
|
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.Event
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.Kind
|
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.metadata.MetadataEvent
|
||||||
import com.vitorpamplona.quartz.nip01Core.tags.events.firstTaggedEvent
|
import com.vitorpamplona.quartz.nip01Core.tags.events.firstTaggedEvent
|
||||||
import com.vitorpamplona.quartz.nip01Core.tags.events.taggedEvents
|
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.nip10Notes.TextNoteEvent
|
||||||
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
||||||
import com.vitorpamplona.quartz.nip18Reposts.quotes.QAddressableTag
|
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.nip18Reposts.quotes.firstTaggedQuote
|
||||||
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
|
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
|
||||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
|
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
|
||||||
|
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
||||||
import com.vitorpamplona.quartz.utils.EventFactory
|
import com.vitorpamplona.quartz.utils.EventFactory
|
||||||
import kotlinx.serialization.json.JsonArray
|
import kotlinx.serialization.json.JsonArray
|
||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
@@ -180,6 +180,40 @@ data class NostrEvent(
|
|||||||
return null
|
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 {
|
fun toReaction(): Reaction? = try {
|
||||||
if (kind == ReactionEvent.KIND) {
|
if (kind == ReactionEvent.KIND) {
|
||||||
val reactionEvent = EventFactory.create<ReactionEvent>(
|
val reactionEvent = EventFactory.create<ReactionEvent>(
|
||||||
@@ -212,7 +246,7 @@ data class NostrEvent(
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toRepostedRelation(): RepostedRelation? = try {
|
fun toRepostedRelationWithContainedPost(): Pair<RepostedRelation, Event?>? = try {
|
||||||
if (kind == RepostEvent.KIND) {
|
if (kind == RepostEvent.KIND) {
|
||||||
val repostEvent = EventFactory.create<RepostEvent>(
|
val repostEvent = EventFactory.create<RepostEvent>(
|
||||||
id = id,
|
id = id,
|
||||||
@@ -224,16 +258,37 @@ data class NostrEvent(
|
|||||||
sig = sig
|
sig = sig
|
||||||
)
|
)
|
||||||
|
|
||||||
return repostEvent.containedPost()?.let { containedPost ->
|
return repostEvent.containedPost().let { containedPost ->
|
||||||
RepostedRelation(
|
if (containedPost != null) {
|
||||||
repostedNostrEventId = containedPost.id,
|
Pair(
|
||||||
repostedProfilePublicKey = containedPost.pubKey,
|
RepostedRelation(
|
||||||
|
repostedNostrEventId = containedPost.id,
|
||||||
|
repostedProfilePublicKey = containedPost.pubKey,
|
||||||
|
|
||||||
repostingNostrEventId = repostEvent.id,
|
repostingNostrEventId = repostEvent.id,
|
||||||
|
|
||||||
createdAt = Instant.fromEpochSeconds(repostEvent.createdAt),
|
createdAt = Instant.fromEpochSeconds(repostEvent.createdAt),
|
||||||
updatedAt = 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.LocalBroadcastNostrEventRequest
|
||||||
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalNostrEvent
|
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.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.LocalProfile
|
||||||
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalProfileWithFollowers
|
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalProfileWithFollowers
|
||||||
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalProfileWithFollowing
|
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalProfileWithFollowing
|
||||||
@@ -682,6 +683,10 @@ class DatabaseNostrRepository(
|
|||||||
return database.profileDao().observeProfileWithFollowingByPublicKey(publicKey)
|
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> {
|
override suspend fun getRecentSearches(): List<RecentSearch> {
|
||||||
return database.recentSearchDao().getAllRecentSearches()
|
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.LocalBroadcastNostrEventRequest
|
||||||
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalNostrEvent
|
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.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.LocalProfile
|
||||||
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalProfileWithFollowers
|
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalProfileWithFollowers
|
||||||
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalProfileWithFollowing
|
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalProfileWithFollowing
|
||||||
@@ -122,6 +123,8 @@ interface NostrRepository {
|
|||||||
|
|
||||||
suspend fun observeProfileWithFollowing(publicKey: String): Flow<LocalProfileWithFollowing?>
|
suspend fun observeProfileWithFollowing(publicKey: String): Flow<LocalProfileWithFollowing?>
|
||||||
|
|
||||||
|
suspend fun observeChatRoomsByPublicKey(publicKey: String): Flow<List<LocalChatRoom>>
|
||||||
|
|
||||||
suspend fun getRecentSearches(): List<RecentSearch>
|
suspend fun getRecentSearches(): List<RecentSearch>
|
||||||
|
|
||||||
suspend fun removeRecentSearch(recentSearch: RecentSearch)
|
suspend fun removeRecentSearch(recentSearch: RecentSearch)
|
||||||
@@ -289,6 +292,10 @@ interface NostrRepository {
|
|||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun observeChatRoomsByPublicKey(publicKey: String): Flow<List<LocalChatRoom>> {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun getRecentSearches(): List<RecentSearch> {
|
override suspend fun getRecentSearches(): List<RecentSearch> {
|
||||||
TODO("Not yet implemented")
|
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 ac.cord.auxiliary.compose.ui.view.state.ChatRoomListUIState
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
@@ -49,51 +48,20 @@ class ChatRoomListViewModel(
|
|||||||
var chatRoomListUIState: ChatRoomListUIState by mutableStateOf(initialChatRoomListUIState)
|
var chatRoomListUIState: ChatRoomListUIState by mutableStateOf(initialChatRoomListUIState)
|
||||||
private set
|
private set
|
||||||
|
|
||||||
val isSynchronizationPending: MutableState<Boolean> = mutableStateOf(false)
|
|
||||||
|
|
||||||
fun initiate() {
|
fun initiate() {
|
||||||
logger.d("init")
|
logger.d("init")
|
||||||
// scheduleSynchronization()
|
scheduleSynchronization()
|
||||||
observeLocalNostrFeed()
|
observeChatRoomFeed()
|
||||||
// observeSynchronizeNostrEventRequestByPurposeAndStatusCount()
|
|
||||||
}
|
}
|
||||||
fun observeLocalNostrFeed() {
|
|
||||||
logger.d("observeLocalNostrFeed")
|
fun observeChatRoomFeed() {
|
||||||
|
logger.d("observeChatRoomFeed")
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
nostrRepository.observeProfileWithFollowing(
|
nostrRepository.observeChatRoomsByPublicKey(
|
||||||
publicKey
|
publicKey
|
||||||
).distinctUntilChanged().collect {
|
).distinctUntilChanged().collect {
|
||||||
chatRoomListUIState = ChatRoomListUIState.Loaded(
|
chatRoomListUIState = ChatRoomListUIState.Loaded(
|
||||||
chatRoomList = listOf(
|
chatRoomList = it
|
||||||
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 = "",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -110,7 +78,7 @@ class ChatRoomListViewModel(
|
|||||||
relayURL = normalizedRelayUrl.url,
|
relayURL = normalizedRelayUrl.url,
|
||||||
synchronizationFilter = synchronizationFilter
|
synchronizationFilter = synchronizationFilter
|
||||||
),
|
),
|
||||||
purpose = "following",
|
purpose = "chat",
|
||||||
synchronizationFilter = synchronizationFilter,
|
synchronizationFilter = synchronizationFilter,
|
||||||
relayURL = normalizedRelayUrl.url,
|
relayURL = normalizedRelayUrl.url,
|
||||||
level = 0
|
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)
|
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun RenderFeed(
|
fun RenderFeed(
|
||||||
@@ -177,8 +135,8 @@ class ChatRoomListViewModel(
|
|||||||
|
|
||||||
items(
|
items(
|
||||||
items = chatRoomListUIState.chatRoomList,
|
items = chatRoomListUIState.chatRoomList,
|
||||||
key = { it.id }
|
key = { it.chatRoom.id }
|
||||||
) { chatRoom ->
|
) { localChatRoom ->
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
onClick = {
|
onClick = {
|
||||||
@@ -190,16 +148,13 @@ class ChatRoomListViewModel(
|
|||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = chatRoom.name ?: chatRoom.subject ?: "Unnamed chat"
|
text = localChatRoom.chatRoom.subject ?: "Unnamed chat"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ChatRoomListUIState.Loading -> {
|
ChatRoomListUIState.Loading -> {
|
||||||
@@ -226,7 +181,7 @@ class ChatRoomListViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG = "MessageListViewModel"
|
const val TAG = "ChatRoomListViewModel"
|
||||||
|
|
||||||
fun factory(
|
fun factory(
|
||||||
initialChatRoomListUIState: ChatRoomListUIState = ChatRoomListUIState.Loading,
|
initialChatRoomListUIState: ChatRoomListUIState = ChatRoomListUIState.Loading,
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
|||||||
import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
|
import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
|
||||||
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
|
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
|
||||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
|
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
|
||||||
|
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
||||||
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
|
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
|
||||||
import com.vitorpamplona.quartz.nip96FileStorage.config.FileServersEvent
|
import com.vitorpamplona.quartz.nip96FileStorage.config.FileServersEvent
|
||||||
import com.vitorpamplona.quartz.nipB7Blossom.BlossomServersEvent
|
import com.vitorpamplona.quartz.nipB7Blossom.BlossomServersEvent
|
||||||
@@ -116,11 +117,11 @@ class HomeViewModel(
|
|||||||
}
|
}
|
||||||
HomeScreenType.Messages -> {
|
HomeScreenType.Messages -> {
|
||||||
SynchronizationFilter(
|
SynchronizationFilter(
|
||||||
authors = arrayOf(
|
|
||||||
publicKey
|
|
||||||
),
|
|
||||||
kinds = arrayOf(
|
kinds = arrayOf(
|
||||||
ContactListEvent.KIND,
|
GiftWrapEvent.KIND,
|
||||||
|
),
|
||||||
|
tags = mapOf(
|
||||||
|
Pair("p", listOf(publicKey))
|
||||||
),
|
),
|
||||||
limit = 50
|
limit = 50
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package ac.cord.auxiliary.compose.ui.view.state
|
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 {
|
sealed interface ChatRoomListUIState {
|
||||||
data class Loaded(
|
data class Loaded(
|
||||||
val chatRoomList: List<ChatRoom>
|
val chatRoomList: List<LocalChatRoom>
|
||||||
): ChatRoomListUIState
|
): ChatRoomListUIState
|
||||||
|
|
||||||
data object Error: ChatRoomListUIState
|
data object Error: ChatRoomListUIState
|
||||||
|
|||||||
Reference in New Issue
Block a user