From ba120779124476fd71fb90a7ff5b48d8b2799a3c Mon Sep 17 00:00:00 2001 From: Kgothatso Ngako Date: Tue, 31 Mar 2026 19:16:30 +0200 Subject: [PATCH] Sync reposted notes from mentions --- .../ac/aux/compose/database/dao/NostrDao.kt | 80 ++++++++++----- .../aux/compose/database/model/NostrEvent.kt | 5 +- .../model/intermdiate/LocalNostrEvent.kt | 9 +- .../composable/widgets/feed/EventListView.kt | 99 +++++++++++++++++-- .../ui/view/model/NavigationViewModel.kt | 44 +++++---- 5 files changed, 180 insertions(+), 57 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/database/dao/NostrDao.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/dao/NostrDao.kt index 54d43277..0beed1a0 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/database/dao/NostrDao.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/dao/NostrDao.kt @@ -154,34 +154,60 @@ abstract class NostrDao( } } - if (post.repostId != null) { - val repostPost = database.postDao().getPostById(post.repostId) +// 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 +// ) +// } +// ) +// } +// } - 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 - ) - } - ) - } - } database.postDao().upsert(post) } diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/database/model/NostrEvent.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/model/NostrEvent.kt index 65974f8e..f091dc73 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/database/model/NostrEvent.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/model/NostrEvent.kt @@ -103,8 +103,9 @@ data class NostrEvent( createdAt = Instant.fromEpochSeconds(textNote.createdAt), updatedAt = Instant.fromEpochSeconds(textNote.createdAt), ) + } else { + return null } - return null } catch (e: Throwable) { logger.e("Failed to return Post: ", e) return null @@ -203,7 +204,7 @@ data class NostrEvent( repostedPostTags = containedPost.tags, repostedPostContent = containedPost.content, repostedPostCreatedAt = Instant.fromEpochSeconds(containedPost.createdAt), - content = repostEvent.content, + content = containedPost.content, nostrEventId = id, profilePublicKey = repostEvent.pubKey, diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/database/model/intermdiate/LocalNostrEvent.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/model/intermdiate/LocalNostrEvent.kt index 5b4085d6..daf0b5e9 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/database/model/intermdiate/LocalNostrEvent.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/model/intermdiate/LocalNostrEvent.kt @@ -3,6 +3,7 @@ package ac.aux.compose.database.model.intermdiate import ac.aux.compose.database.model.NostrEvent import ac.aux.compose.database.model.Post import ac.aux.compose.database.model.Profile +import ac.aux.compose.database.model.Repost import androidx.room.Embedded import androidx.room.Relation @@ -13,5 +14,11 @@ data class LocalNostrEvent( parentColumn = "pubKey", entityColumn = "publicKey" ) - val profile: Profile + val profile: Profile, + + @Relation( + parentColumn = "id", + entityColumn = "nostrEventId" + ) + val repost: Repost? = null, ) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/widgets/feed/EventListView.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/widgets/feed/EventListView.kt index 751d0660..79cc3b32 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/widgets/feed/EventListView.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/widgets/feed/EventListView.kt @@ -2,6 +2,7 @@ package ac.aux.compose.ui.composable.widgets.feed import ac.aux.compose.database.model.NostrEvent import ac.aux.compose.database.model.Profile +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 @@ -36,7 +37,6 @@ fun LocalNostrEvent.ListView( ) { when (nostrEvent.kind) { RepostEvent.KIND -> { - val repost = nostrEvent.toRepost() // TODO: Make use of the indexed Repost field to render... Column( @@ -57,10 +57,15 @@ fun LocalNostrEvent.ListView( style = MaterialTheme.typography.labelSmall) } - TextNoteFeedItem( - this@ListView, - onNavigateToEvent = onNavigateToEvent - ) + if (repost != null) { + RepostFeedItem( + repost, + this@ListView, + onNavigateToEvent = onNavigateToEvent + ) + } else { + Text("Missing note") + } } } // TODO: Support other kinds... @@ -152,6 +157,88 @@ 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() { @@ -172,7 +259,7 @@ private fun EventListViewPreview() { publicKey = "", nostrEventId = "referenced", displayName = "Frank Talk" - ) + ), ).ListView( onNavigateToEvent = {} ) diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/view/model/NavigationViewModel.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/view/model/NavigationViewModel.kt index f26a95a4..1e02688b 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/view/model/NavigationViewModel.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/view/model/NavigationViewModel.kt @@ -143,26 +143,26 @@ class NavigationViewModel( scope.launch(Dispatchers.IO) { nostrRepository.observePendingSynchronizeNostrEventRequests().collect { synchronizeNostrEventRequestOrNull -> synchronizeNostrEventRequestOrNull?.let { synchronizeNostrEventRequest -> - try { - logger.i("synchronizeNostrEventRequest: $synchronizeNostrEventRequest") - val reqCommand = ReqCmd( - subId = synchronizeNostrEventRequest.id, - filters = synchronizeNostrEventRequest.synchronizationFilters.map { synchronizationFilter -> - Filter( - ids = synchronizationFilter.ids?.toList(), - authors = synchronizationFilter.authors?.toList(), - kinds = synchronizationFilter.kinds?.toList(), - tags = synchronizationFilter.tags, - tagsAll = synchronizationFilter.tagsAll, - since = synchronizationFilter.since?.epochSeconds, - until = synchronizationFilter.until?.epochSeconds, - limit = synchronizationFilter.limit, - search = synchronizationFilter.search - ) - } - ) + logger.i("synchronizeNostrEventRequest: $synchronizeNostrEventRequest") + val reqCommand = ReqCmd( + subId = synchronizeNostrEventRequest.id, + filters = synchronizeNostrEventRequest.synchronizationFilters.map { synchronizationFilter -> + Filter( + ids = synchronizationFilter.ids?.toList(), + authors = synchronizationFilter.authors?.toList(), + kinds = synchronizationFilter.kinds?.toList(), + tags = synchronizationFilter.tags, + tagsAll = synchronizationFilter.tagsAll, + since = synchronizationFilter.since?.epochSeconds, + until = synchronizationFilter.until?.epochSeconds, + limit = synchronizationFilter.limit, + search = synchronizationFilter.search + ) + } + ) - scope.launch { + scope.launch { + try { val webSocketSession = httpClient.webSocketSession( urlString = synchronizeNostrEventRequest.relayURL ) @@ -260,9 +260,11 @@ class NavigationViewModel( } syncingJobs[synchronizeNostrEventRequest.relayURL]?.join() + } catch (e: Throwable) { + logger.e("Relay Error (${synchronizeNostrEventRequest.relayURL})", e) + // TODO: Have it failed instead of processed... + nostrRepository.synchronizeNostrEventRequestProcessed(synchronizeNostrEventRequest) } - } catch (e: Throwable) { - logger.e("Relay Error", e) } } }