diff --git a/composeApp/schemas/ac.aux.compose.database.AuxDatabase/1.json b/composeApp/schemas/ac.aux.compose.database.AuxDatabase/1.json index 81124740..6431844b 100644 --- a/composeApp/schemas/ac.aux.compose.database.AuxDatabase/1.json +++ b/composeApp/schemas/ac.aux.compose.database.AuxDatabase/1.json @@ -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')" ] } } \ No newline at end of file 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 0beed1a0..f415c516 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 @@ -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... ) ) diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/database/dao/NostrEventDao.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/dao/NostrEventDao.kt index f67daaa6..9b1dfec8 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/database/dao/NostrEventDao.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/dao/NostrEventDao.kt @@ -19,7 +19,10 @@ interface NostrEventDao { fun getAllNostrEvents(kinds: Array): List @Query("SELECT * FROM NostrEvent WHERE id = :id") - fun getNostrEventById(id: String): Flow + fun observeNostrEventById(id: String): Flow + + @Query("SELECT * FROM NostrEvent WHERE id = :id") + fun getNostrEventById(id: String): NostrEvent? @Upsert suspend fun upsert(nostrEvent: NostrEvent) diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/database/model/SynchronizeNostrEventRequest.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/model/SynchronizeNostrEventRequest.kt index 2739bae8..6ee293fa 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/database/model/SynchronizeNostrEventRequest.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/model/SynchronizeNostrEventRequest.kt @@ -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, diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/FeedScreen.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/FeedScreen.kt index feda0569..634b869b 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/FeedScreen.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/FeedScreen.kt @@ -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, ) { diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/navigation/AuxNavHost.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/navigation/AuxNavHost.kt index 43464efd..0d8284ae 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/navigation/AuxNavHost.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/navigation/AuxNavHost.kt @@ -295,10 +295,10 @@ fun AuxNavHost( composable { FeedScreen( initialFeedListUIState = FeedListUIState.Loading, - onNavigateToEvent = { event -> + onNavigateToEvent = { nostrEventId -> navController.navigate( route = ImplementationPendingRoute( - event.content + nostrEventId ) ) }, 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 44c2ec7d..eaffc2ad 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 @@ -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() { diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/widgets/feed/FeedListItem.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/widgets/feed/FeedListItem.kt index 416d0fbf..e77fb120 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/widgets/feed/FeedListItem.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/widgets/feed/FeedListItem.kt @@ -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