From bfc540812d2717da0f41e5b6a7ca597d1772cdab Mon Sep 17 00:00:00 2001 From: Kgothatso Ngako Date: Sat, 2 May 2026 01:01:15 +0200 Subject: [PATCH] Trying to fix the crash --- .../compose/database/dao/NostrDao.kt | 7 ++--- .../compose/database/dao/NostrEventDao.kt | 12 ++++----- .../model/intermdiate/LocalMention.kt | 2 +- .../model/intermdiate/LocalNostrEvent.kt | 2 +- .../repository/DatabaseNostrRepository.kt | 13 +++++----- .../compose/repository/NostrRepository.kt | 8 +++--- .../composable/widgets/content/RichContent.kt | 4 +-- .../widgets/feed/TextNoteFeedItem.kt | 2 +- .../ui/view/model/FeedListViewModel.kt | 14 +--------- .../ui/view/model/NavigationViewModel.kt | 26 ++++++++++--------- .../ui/view/model/SearchResultViewModel.kt | 2 +- 11 files changed, 41 insertions(+), 51 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/dao/NostrDao.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/dao/NostrDao.kt index ec971605..ac0e3be7 100644 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/dao/NostrDao.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/dao/NostrDao.kt @@ -103,15 +103,17 @@ abstract class NostrDao( synchronizationRelayURLs: List, level: Int ) { - logger.i("Store Nostr Event: $nostrEvent") + val storedNostrEvent = database.nostrEventDao().getNostrEventById(nostrEvent.id) if (storedNostrEvent == null) { // Add new nostr event + logger.i("Store Nostr Event: $nostrEvent") database.nostrEventDao().insert(nostrEvent) } else if (nostrEvent.createdAt > storedNostrEvent.createdAt) { // Update nostr event` + logger.i("Update Nostr Event: $nostrEvent") database.nostrEventDao().upsert(nostrEvent) } else { // Nothing else needs to be done @@ -120,12 +122,12 @@ abstract class NostrDao( logger.i("This is a placeholder profile might need to get synced...: ${profile.publicKey}") // Setup the sync here... } else { - logger.i("We have the latest version for: ${nostrEvent.id}") return } } // Index nostrEvent + logger.i("Index Nostr Event: ${nostrEvent.id}") indexNostrEvent( nostrEvent = nostrEvent, synchronizationRelayURLs = synchronizationRelayURLs, @@ -144,7 +146,6 @@ abstract class NostrDao( val eventIdsToSync = mutableSetOf() if (nostrEvent.unsignedNostrEventId == null) { - logger.i("Store Nostr Event: $nostrEvent") // Find or create profile with the pubKey... if not found submit a sync request... val profile = database.profileDao().getProfileByPublicKey(nostrEvent.pubKey) diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/dao/NostrEventDao.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/dao/NostrEventDao.kt index cd7e1ef2..2c8459fb 100644 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/dao/NostrEventDao.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/dao/NostrEventDao.kt @@ -17,36 +17,36 @@ import kotlinx.coroutines.flow.Flow @Dao interface NostrEventDao { @Query("SELECT * FROM NostrEvent WHERE kind in (:kinds) ORDER BY createdAt DESC") - fun observeNostrEvents(kinds: Array): Flow> + fun observeNostrEvents(kinds: Array): Flow?> @Query("SELECT * FROM NostrEvent WHERE content LIKE '%' || :search || '%' AND kind in (:kinds) ORDER BY createdAt DESC") fun observeFilteredNostrEvents( kinds: Array, search: String - ): Flow> + ): Flow?> @Query("SELECT * FROM NostrEvent WHERE kind in (:kinds) AND id in (:ids) ORDER BY createdAt DESC") fun observeFilteredNostrEvents( kinds: Array, ids: Array, - ): Flow> + ): Flow?> @Query("SELECT * FROM NostrEvent WHERE kind in (:kinds) AND pubKey in (:authors) ORDER BY createdAt DESC LIMIT 50") fun observeAuthoredNostrEvents( kinds: Array, authors: Array, - ): Flow> + ): Flow?> @Query("SELECT * FROM NostrEvent WHERE tags LIKE '%' || :publicKey || '%' AND kind in (:kinds) ORDER BY createdAt DESC") fun observePublicKeyMentionedNostrEvents( kinds: Array, publicKey: HexKey - ): Flow> + ): Flow?> @RawQuery fun observePublicKeyFollowingNostrEvents( query: RoomRawQuery - ): Flow> + ): Flow?> @Query("SELECT * FROM NostrEvent WHERE kind in (:kinds) ORDER BY savedAt DESC") fun getAllNostrEvents(kinds: Array): List diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/model/intermdiate/LocalMention.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/model/intermdiate/LocalMention.kt index acdca3d2..1247d7c0 100644 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/model/intermdiate/LocalMention.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/model/intermdiate/LocalMention.kt @@ -6,7 +6,7 @@ import androidx.room3.Embedded import androidx.room3.Relation data class LocalMention( - @Embedded val mention: Mention, + @Embedded val mention: Mention?, @Relation( parentColumn = "mentionedPublicKey", diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/model/intermdiate/LocalNostrEvent.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/model/intermdiate/LocalNostrEvent.kt index 6dbf36a4..f793527b 100644 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/model/intermdiate/LocalNostrEvent.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/model/intermdiate/LocalNostrEvent.kt @@ -61,7 +61,7 @@ data class LocalNostrEvent( parentColumn = "id", entityColumn = "nostrEventId", ) - val mentionedProfiles: List = emptyList(), + val mentionedProfiles: List? = emptyList(), ) { @Composable fun RenderNotePreview() { diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/repository/DatabaseNostrRepository.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/repository/DatabaseNostrRepository.kt index a8314257..f064c365 100644 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/repository/DatabaseNostrRepository.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/repository/DatabaseNostrRepository.kt @@ -57,14 +57,11 @@ import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlin.collections.emptyMap import kotlin.collections.plus import kotlin.time.Clock -import kotlin.time.Duration.Companion.milliseconds -import kotlin.time.Duration.Companion.seconds import kotlin.time.Instant class DatabaseNostrRepository( @@ -73,12 +70,12 @@ class DatabaseNostrRepository( ): NostrRepository, RelayRepository { companion object { const val TAG = "DatabaseNostrRepository" + + private val storeNostrEventMutex = Mutex() } val logger = Logger.withTag(TAG) - private val storeNostrEventMutex = Mutex() - override suspend fun observeProfile(publicKey: HexKey): Flow { return database.unsignedNostrEventDao().observeProfile(publicKey) } @@ -404,6 +401,7 @@ class DatabaseNostrRepository( synchronizationRelayURLs: List ) { storeNostrEventMutex.withLock { + logger.d("saveNostrEvent: $nostrEvent") database.nostrDao().storeNostrEvent( nostrEvent, synchronizationRelayURLs = synchronizationRelayURLs, @@ -417,6 +415,7 @@ class DatabaseNostrRepository( ) ) } + logger.d("done: ${nostrEvent.id}") } override suspend fun queueSynchronizeNostrEvent( @@ -442,7 +441,7 @@ class DatabaseNostrRepository( ) } - override suspend fun observeNostrFeed(): Flow> { + override suspend fun observeNostrFeed(): Flow?> { return database.nostrEventDao().observeNostrEvents( kinds = arrayOf( TextNoteEvent.KIND, @@ -453,7 +452,7 @@ class DatabaseNostrRepository( ) } - override suspend fun observeNostrFeed(synchronizationFilter: SynchronizationFilter): Flow> { + override suspend fun observeNostrFeed(synchronizationFilter: SynchronizationFilter): Flow?> { return when { synchronizationFilter.kinds != null && synchronizationFilter.search != null -> { diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/repository/NostrRepository.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/repository/NostrRepository.kt index fd8311de..49d7ab07 100644 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/repository/NostrRepository.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/repository/NostrRepository.kt @@ -84,9 +84,9 @@ interface NostrRepository { suspend fun getNostrEvent(publicKey: HexKey, kind: Kind): NostrEvent? - suspend fun observeNostrFeed(): Flow> + suspend fun observeNostrFeed(): Flow?> - suspend fun observeNostrFeed(synchronizationFilter: SynchronizationFilter): Flow> + suspend fun observeNostrFeed(synchronizationFilter: SynchronizationFilter): Flow?> suspend fun observeLocalNostrEventById(nostrEventId: String): Flow @@ -207,11 +207,11 @@ interface NostrRepository { TODO("Not yet implemented") } - override suspend fun observeNostrFeed(): Flow> { + override suspend fun observeNostrFeed(): Flow?> { TODO("Not yet implemented") } - override suspend fun observeNostrFeed(synchronizationFilter: SynchronizationFilter): Flow> { + override suspend fun observeNostrFeed(synchronizationFilter: SynchronizationFilter): Flow?> { TODO("Not yet implemented") } diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/widgets/content/RichContent.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/widgets/content/RichContent.kt index 34284c5d..fb2a7616 100644 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/widgets/content/RichContent.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/widgets/content/RichContent.kt @@ -35,7 +35,7 @@ import coil3.compose.AsyncImage fun RichContent( nostrEvent: NostrEvent, localQuotedNostrEvent: LocalQuotedNostrEvent?, - mentionedProfiles: List, + mentionedProfiles: List?, style: TextStyle = MaterialTheme.typography.bodyLarge, color: Color = MaterialTheme.colorScheme.onSurface, linkColor: Color = Color.Unspecified, @@ -110,7 +110,7 @@ fun RichContent( val profileNames = remember(profilePubkeys) { val names = mutableMapOf() - if (mentionedProfiles.isNotEmpty()) { + if (mentionedProfiles?.isNotEmpty() == true) { Logger.withTag("RichContent").d("mentionedProfiles: ${mentionedProfiles}") for (pubkey in profilePubkeys) { val mentionedProfile = mentionedProfiles.find { mentionedProfile -> mentionedProfile?.profile?.publicKey == pubkey } diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/widgets/feed/TextNoteFeedItem.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/widgets/feed/TextNoteFeedItem.kt index 4e2243c9..b1e1bc28 100644 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/widgets/feed/TextNoteFeedItem.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/widgets/feed/TextNoteFeedItem.kt @@ -36,7 +36,7 @@ internal fun TextNoteFeedItem( profile: Profile?, localInReplyToNostrEvent: LocalInReplyToNostrEvent?, localQuotedNostrEvent: LocalQuotedNostrEvent?, - mentionedProfiles: List, + mentionedProfiles: List?, onNavigateToEvent: (HexKey) -> Unit ) { if (localInReplyToNostrEvent != null) { diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/view/model/FeedListViewModel.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/view/model/FeedListViewModel.kt index cdd06201..13e30cb1 100755 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/view/model/FeedListViewModel.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/view/model/FeedListViewModel.kt @@ -2,7 +2,6 @@ package ac.cord.auxiliary.compose.ui.view.model import ac.cord.auxiliary.compose.database.model.SynchronizeNostrEventRequest import ac.cord.auxiliary.compose.database.model.types.SynchronizationFilter -import ac.cord.auxiliary.compose.managers.SeedManager import ac.cord.auxiliary.compose.nostr.Relays import ac.cord.auxiliary.compose.repository.NostrRepository import ac.cord.auxiliary.compose.ui.composable.navigation.routes.NostrEventDetailRoute @@ -34,19 +33,8 @@ import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.initializer import androidx.lifecycle.viewmodel.viewModelFactory import co.touchlab.kermit.Logger -import com.vitorpamplona.quartz.nip01Core.core.toHexKey -import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent -import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent -import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent -import com.vitorpamplona.quartz.nip18Reposts.RepostEvent -import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent -import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent -import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent -import com.vitorpamplona.quartz.nip96FileStorage.config.FileServersEvent -import com.vitorpamplona.quartz.nipB7Blossom.BlossomServersEvent import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.IO -import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch class FeedListViewModel( @@ -74,7 +62,7 @@ class FeedListViewModel( synchronizationFilter ).collect { localNostrEvents -> feedListUIState = FeedListUIState.Loaded( - localNostrEvents = localNostrEvents + localNostrEvents = localNostrEvents?.filterNotNull() ?: emptyList() ) } } diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/view/model/NavigationViewModel.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/view/model/NavigationViewModel.kt index e3929b71..d0d2401d 100644 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/view/model/NavigationViewModel.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/view/model/NavigationViewModel.kt @@ -32,6 +32,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.getAndUpdate import kotlinx.coroutines.flow.timeout import kotlinx.coroutines.launch @@ -94,7 +95,7 @@ class NavigationViewModel( scope.launch(Dispatchers.IO) { nostrRepository.observeUnsignedNostrEvents( publicKey = SeedManager.activePublicKey().toHexKey() - ).collect { unsignedNostrEventOrNull -> + ).distinctUntilChanged().collect { unsignedNostrEventOrNull -> unsignedNostrEventOrNull?.let { unsignedNostrEvent -> logger.d("Unsigned") val event = tempSigner.signNormal( @@ -128,7 +129,7 @@ class NavigationViewModel( logger.i { "observePendingSyncNostrEventRequests" } scope.launch(Dispatchers.IO) { - nostrRepository.observePendingSynchronizeNostrEventRequests().collect { synchronizeNostrEventRequestOrNull -> + nostrRepository.observePendingSynchronizeNostrEventRequests().distinctUntilChanged().collect { synchronizeNostrEventRequestOrNull -> synchronizeNostrEventRequestOrNull?.let { synchronizeNostrEventRequest -> logger.i("synchronizeNostrEventRequest: $synchronizeNostrEventRequest") val reqCommand = ReqCmd( @@ -158,15 +159,16 @@ class NavigationViewModel( ).collect { nostrIncomingMessage -> when (nostrIncomingMessage) { is NostrIncomingMessage.EventMessage -> { - logger.d("Import message: $nostrIncomingMessage") - nostrIncomingMessage.nostrEvent?.let { - nostrRepository.saveNostrEvent( - nostrEvent = it, - synchronizeNostrEventRequest, - synchronizationRelayURLs = listOf(synchronizeNostrEventRequest.relayURL) // TODO: + Relays.eventPublishRelaySet.map { normalizedRelayUrl -> normalizedRelayUrl.url } - ) + scope.launch(Dispatchers.IO) { + logger.d("Import message: $nostrIncomingMessage") + nostrIncomingMessage.nostrEvent?.let { + nostrRepository.saveNostrEvent( + nostrEvent = it, + synchronizeNostrEventRequest, + synchronizationRelayURLs = listOf(synchronizeNostrEventRequest.relayURL) // TODO: + Relays.eventPublishRelaySet.map { normalizedRelayUrl -> normalizedRelayUrl.url } + ) + } } - } is NostrIncomingMessage.EventsMessage -> { logger.d("Import messages: $nostrIncomingMessage") @@ -212,7 +214,7 @@ class NavigationViewModel( logger.i { "observePendingBroadcastNostrEventRequests" } scope.launch(Dispatchers.IO) { - nostrRepository.observePendingBroadcastNostrEventRequests().collect { localBroadcastNostrEventRequests -> + nostrRepository.observePendingBroadcastNostrEventRequests().distinctUntilChanged().collect { localBroadcastNostrEventRequests -> localBroadcastNostrEventRequests.forEach { localBroadcastNostrEventRequest -> nostrRepository.broadcastProcessed( @@ -289,7 +291,7 @@ class NavigationViewModel( logger.i("Observing: $publicKey") nostrRepository.observeProfile( publicKey = publicKey - ).collect { localProfile -> + ).distinctUntilChanged().collect { localProfile -> logger.i("Local Profile: $localProfile") _navigationUIState.getAndUpdate { if (localProfile == null) { diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/view/model/SearchResultViewModel.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/view/model/SearchResultViewModel.kt index d36c5e92..93e039a2 100755 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/view/model/SearchResultViewModel.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/view/model/SearchResultViewModel.kt @@ -161,7 +161,7 @@ class SearchResultViewModel( // This is something not taking into consideration the results we are actually looking for... nostrRepository.observeNostrFeed(synchronizationFilter).collect { localNostrEvents -> searchResultListUIState = SearchResultListUIState.Loaded( - localNostrEvents = localNostrEvents + localNostrEvents = localNostrEvents?.filterNotNull() ?: emptyList() ) } }