Sync taggedEvent + taggedAuthor...

This commit is contained in:
Kgothatso Ngako
2026-03-31 21:01:11 +02:00
parent c3c92d96ad
commit b01088a74f
8 changed files with 150 additions and 194 deletions

View File

@@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "a9975543a4ca1505dcf4ee74321b3e69",
"identityHash": "31032766d6bdd6ae4664e854a928dd32",
"entities": [
{
"tableName": "BroadcastNostrEventReceipt",
@@ -929,7 +929,7 @@
},
{
"tableName": "SynchronizeNostrEventRequest",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `status` TEXT NOT NULL, `relayURL` TEXT NOT NULL, `synchronizationFilters` TEXT NOT NULL, `nostrEventId` TEXT, `unsignedNostrEventId` INTEGER, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`nostrEventId`) REFERENCES `NostrEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`unsignedNostrEventId`) REFERENCES `UnsignedNostrEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `status` TEXT NOT NULL, `relayURL` TEXT NOT NULL, `isRecommendedRelay` INTEGER NOT NULL, `synchronizationFilters` TEXT NOT NULL, `nostrEventId` TEXT, `unsignedNostrEventId` INTEGER, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`nostrEventId`) REFERENCES `NostrEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`unsignedNostrEventId`) REFERENCES `UnsignedNostrEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
@@ -949,6 +949,12 @@
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isRecommendedRelay",
"columnName": "isRecommendedRelay",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "synchronizationFilters",
"columnName": "synchronizationFilters",
@@ -1362,7 +1368,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, 'a9975543a4ca1505dcf4ee74321b3e69')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '31032766d6bdd6ae4664e854a928dd32')"
]
}
}

View File

@@ -12,6 +12,8 @@ import androidx.room.Dao
import androidx.room.Transaction
import co.touchlab.kermit.Logger
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
import com.vitorpamplona.quartz.nip01Core.tags.events.taggedEvents
import com.vitorpamplona.quartz.nip01Core.tags.people.taggedUsers
import com.vitorpamplona.quartz.nip37Drafts.DraftWrapEvent
import kotlin.time.Clock
@@ -122,6 +124,77 @@ abstract class NostrDao(
}
}
// Sync tagged events and authors...
nostrEvent.tags.taggedEvents().forEach { taggedEvent ->
val taggedNostrEvent = database.nostrEventDao().getNostrEventById(taggedEvent.eventId)
if (taggedNostrEvent == null) {
val recommendRelayUrl = taggedEvent.relay?.url
val synchronizeNostrEventRequests = if (recommendRelayUrl != null) {
listOf(
SynchronizeNostrEventRequest(
synchronizationFilters = arrayOf(
SynchronizationFilter(
ids = arrayOf(taggedEvent.eventId)
)
),
relayURL = recommendRelayUrl,
isRecommendedRelay = true,
)
)
} else {
synchronizationRelayURLs.map { synchronizationRelayURL ->
SynchronizeNostrEventRequest(
synchronizationFilters = arrayOf(
SynchronizationFilter(
ids = arrayOf(taggedEvent.eventId)
)
),
relayURL = synchronizationRelayURL
)
}
}
database.synchronizeNostrEventRequestDao().insert(
synchronizeNostrEventRequests
)
}
}
nostrEvent.tags.taggedUsers().forEach { taggedUser ->
val recommendedRelay = taggedUser.relayHint?.url
val synchronizeNostrEventRequests = if (recommendedRelay != null) {
listOf(
SynchronizeNostrEventRequest(
synchronizationFilters = arrayOf(
SynchronizationFilter(
authors = arrayOf(taggedUser.pubKey),
kinds = arrayOf(MetadataEvent.KIND)
)
),
relayURL = recommendedRelay,
isRecommendedRelay = true,
)
)
} else {
synchronizationRelayURLs.map { synchronizationRelayURL ->
SynchronizeNostrEventRequest(
synchronizationFilters = arrayOf(
SynchronizationFilter(
authors = arrayOf(taggedUser.pubKey),
kinds = arrayOf(MetadataEvent.KIND)
)
),
relayURL = synchronizationRelayURL
)
}
}
database.synchronizeNostrEventRequestDao().insert(
synchronizeNostrEventRequests
)
}
nostrEvent.toPost()?.let { post ->
if (post.replyToId != null) {
// Find or create placeHolder note that is replyTo
@@ -132,82 +205,14 @@ abstract class NostrDao(
database.postDao().insert(
Post(
id = post.replyToId,
nostrEventId = nostrEvent.id, // Will get overwriting by sync,
nostrEventId = nostrEvent.id, // Will get overwriting by sync handled by tagged events...
content = post.replyToId,
profilePublicKey = nostrEvent.pubKey // Will get overwriting by sync,
profilePublicKey = nostrEvent.pubKey // Will get overwriting by sync handled by tagged events...
)
)
// Request a sync for the post being replied to
database.synchronizeNostrEventRequestDao().insert(
synchronizationRelayURLs.map { synchronizationRelayURL ->
SynchronizeNostrEventRequest(
synchronizationFilters = arrayOf(
SynchronizationFilter(
ids = arrayOf(post.replyToId)
)
),
relayURL = synchronizationRelayURL
)
}
)
}
}
// if (post.repostId != null) {
// val repostPost = database.postDao().getPostById(post.repostId)
//
// if (repostPost == null) {
// // Placeholder Post...
// database.postDao().insert(
// Post(
// id = post.repostId,
// nostrEventId = nostrEvent.id, // Will get overwriting by sync,
// content = post.repostId,
// profilePublicKey = nostrEvent.pubKey // Will get overwriting by sync,
// )
// )
// // Request a sync for the post being reposted
// database.synchronizeNostrEventRequestDao().insert(
// synchronizationRelayURLs.map { synchronizationRelayURL ->
// SynchronizeNostrEventRequest(
// synchronizationFilters = arrayOf(
// SynchronizationFilter(
// ids = arrayOf(post.repostId)
// )
// ),
// relayURL = synchronizationRelayURL
// )
// }
// )
// }
//
// // repostProfile??
// val profile = database.profileDao().getProfileByPublicKey(post.profilePublicKey)
//
// if (profile == null) {
// val placeHolderProfile = Profile(
// publicKey = nostrEvent.pubKey,
// nostrEventId = nostrEvent.id, // Will get overwriting by sync,
// )
// database.profileDao().insertPlaceholderProfile(placeHolderProfile)
//
// database.synchronizeNostrEventRequestDao().insert(
// synchronizationRelayURLs.map { synchronizationRelayURL ->
// SynchronizeNostrEventRequest(
// synchronizationFilters = arrayOf(
// SynchronizationFilter(
// authors = arrayOf(nostrEvent.pubKey),
// kinds = arrayOf(MetadataEvent.KIND)
// )
// ),
// relayURL = synchronizationRelayURL
// )
// }
// )
// }
// }
database.postDao().upsert(post)
}
@@ -228,25 +233,11 @@ abstract class NostrDao(
database.postDao().insert(
Post(
id = repost.repostedPostId,
nostrEventId = nostrEvent.id, // Will get overwriting by sync,
nostrEventId = nostrEvent.id, // Will get overwriting by sync handled by tagged events...
content = repost.repostedPostId,
profilePublicKey = nostrEvent.pubKey // Will get overwriting by sync,
profilePublicKey = nostrEvent.pubKey // Will get overwriting by sync handled by tagged events...
)
)
// Request a sync for the post being replied to
database.synchronizeNostrEventRequestDao().insert(
synchronizationRelayURLs.map { synchronizationRelayURL ->
SynchronizeNostrEventRequest(
synchronizationFilters = arrayOf(
SynchronizationFilter(
ids = arrayOf(repost.repostedPostId)
)
),
relayURL = synchronizationRelayURL
)
}
)
}
val repostedPostProfile = database.profileDao().getProfileByPublicKey(repost.repostedPostAuthorPublicKey)
@@ -256,7 +247,7 @@ abstract class NostrDao(
database.profileDao().insertPlaceholderProfile(
Profile(
publicKey = nostrEvent.pubKey,
nostrEventId = nostrEvent.id, // Will get overwriting by sync,
nostrEventId = nostrEvent.id, // Will get overwriting by sync handled by tagged events...
)
)

View File

@@ -19,7 +19,10 @@ interface NostrEventDao {
fun getAllNostrEvents(kinds: Array<Kind>): List<NostrEvent>
@Query("SELECT * FROM NostrEvent WHERE id = :id")
fun getNostrEventById(id: String): Flow<NostrEvent?>
fun observeNostrEventById(id: String): Flow<NostrEvent?>
@Query("SELECT * FROM NostrEvent WHERE id = :id")
fun getNostrEventById(id: String): NostrEvent?
@Upsert
suspend fun upsert(nostrEvent: NostrEvent)

View File

@@ -40,6 +40,7 @@ data class SynchronizeNostrEventRequest(
val id: String = Uuid.generateV4().toHexDashString(),
val status: String = "pending",
val relayURL: String,
val isRecommendedRelay: Boolean = false,
val synchronizationFilters: SynchronizationFilterArray,

View File

@@ -41,12 +41,13 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.quartz.nip01Core.core.HexKey
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun FeedScreen(
initialFeedListUIState: FeedListUIState,
onNavigateToEvent: (NostrEvent) -> Unit,
onNavigateToEvent: (HexKey) -> Unit,
onNavigateToWriteNewNote: () -> Unit,
nostrRepository: NostrRepository,
) {

View File

@@ -295,10 +295,10 @@ fun AuxNavHost(
composable<FeedRoute> {
FeedScreen(
initialFeedListUIState = FeedListUIState.Loading,
onNavigateToEvent = { event ->
onNavigateToEvent = { nostrEventId ->
navController.navigate(
route = ImplementationPendingRoute(
event.content
nostrEventId
)
)
},

View File

@@ -6,6 +6,7 @@ import ac.aux.compose.database.model.Repost
import ac.aux.compose.database.model.intermdiate.LocalNostrEvent
import ac.aux.compose.extensions.toFormattedTimeAndDateString
import ac.aux.compose.ui.theme.AuxTheme
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -29,24 +30,28 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
import kotlin.time.Instant
@Composable
fun LocalNostrEvent.ListView(
onNavigateToEvent: (NostrEvent) -> Unit
onNavigateToEvent: (HexKey) -> Unit
) {
when (nostrEvent.kind) {
RepostEvent.KIND -> {
// TODO: Make use of the indexed Repost field to render...
Column(
modifier = Modifier.fillMaxWidth().padding(10.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
Row(
modifier = Modifier.fillMaxWidth()
modifier = Modifier.fillMaxWidth().clickable(
enabled = true,
onClick = {
onNavigateToEvent.invoke(profile.nostrEventId)
}
)
) {
Text(
modifier = Modifier.weight(1f),
@@ -105,14 +110,32 @@ fun LocalNostrEvent.ListView(
private fun TextNoteFeedItem(
nostrEvent: NostrEvent,
profile: Profile?,
onNavigateToEvent: (NostrEvent) -> Unit
taggedNostrEvent: NostrEvent? = null,
taggedAuthor: Profile? = null,
onNavigateToEvent: (HexKey) -> Unit
) {
if (nostrEvent.kind == 1 && taggedNostrEvent != null) {
Row {
Text(
"In reply to"
)
Text(
modifier = Modifier.clickable(
onClick = {
onNavigateToEvent.invoke(taggedNostrEvent.id)
}
),
text = taggedAuthor?.displayName ?: taggedAuthor?.userName ?: "loading information..."
)
}
}
ElevatedCard(
modifier = Modifier.padding(
horizontal = 10.dp
),
onClick = {
onNavigateToEvent.invoke(nostrEvent)
onNavigateToEvent.invoke(nostrEvent.id)
}
) {
Column(
@@ -120,21 +143,32 @@ private fun TextNoteFeedItem(
verticalArrangement = Arrangement.spacedBy(10.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(10.dp, Alignment.Start),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
Icons.Default.Person,
contentDescription = "Profile picture",
)
if (profile != null) {
Row(
modifier = Modifier.fillMaxWidth().clickable(
onClick = {
onNavigateToEvent.invoke(profile?.nostrEventId ?: nostrEvent.id)
}
),
horizontalArrangement = Arrangement.spacedBy(10.dp, Alignment.Start),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
Icons.Default.Person,
contentDescription = "Profile picture",
)
Text(
modifier = Modifier.weight(1f),
text = profile?.displayName ?: profile?.userName ?: nostrEvent.pubKey,
style = MaterialTheme.typography.bodyMedium
)
}
} else {
Text(
modifier = Modifier.weight(1f),
text = profile?.displayName ?: profile?.userName ?: nostrEvent.pubKey,
style = MaterialTheme.typography.bodyMedium
text = "Loading author information"
)
}
Text(
text = nostrEvent.content,
)
@@ -182,87 +216,6 @@ private fun TextNoteFeedItem(
}
}
@Composable
private fun RepostFeedItem(
repost: Repost,
localNostrEvent: LocalNostrEvent,
onNavigateToEvent: (NostrEvent) -> Unit
) {
ElevatedCard(
modifier = Modifier.padding(
horizontal = 10.dp
),
onClick = {
onNavigateToEvent.invoke(localNostrEvent.nostrEvent)
}
) {
Column(
modifier = Modifier.padding(10.dp).fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(10.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(10.dp, Alignment.Start),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
Icons.Default.Person,
contentDescription = "Profile picture",
)
Text(
modifier = Modifier.weight(1f),
text = repost.repostedPostAuthorPublicKey,
style = MaterialTheme.typography.bodyMedium
)
}
Text(
text = repost.content,
)
LazyRow(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(
10.dp,
alignment = Alignment.Start
)
) {
item {
AssistChip(
label = {
Text(
text = localNostrEvent.nostrEvent.kind.toString() // TODO: Kind by name...
)
},
onClick = {
}
)
}
items(
items = localNostrEvent.nostrEvent.tags,
) { tag ->
SuggestionChip(
label = {
Text(
text = tag.joinToString(":")
)
},
onClick = {
}
)
}
}
Text(
text = localNostrEvent.nostrEvent.createdAt.toFormattedTimeAndDateString()
)
}
}
}
@Preview
@Composable
private fun EventListViewPreview() {

View File

@@ -7,11 +7,12 @@ import androidx.compose.material3.ElevatedCard
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.vitorpamplona.quartz.nip01Core.core.HexKey
@Composable
fun FeedListItem(
localNostrEvent: LocalNostrEvent,
onNavigateToEvent: (NostrEvent) -> Unit
onNavigateToEvent: (HexKey) -> Unit
) {
localNostrEvent.ListView(
onNavigateToEvent = onNavigateToEvent