Prep to show loading on pending sync

This commit is contained in:
Kgothatso Ngako
2026-04-01 01:47:24 +02:00
parent 9b6ae7159a
commit b3b93008e5
10 changed files with 60 additions and 4 deletions

View File

@@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "31032766d6bdd6ae4664e854a928dd32",
"identityHash": "770e86deaca12c1e4d82a71f6613da37",
"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, `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 )",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `purpose` 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",
@@ -937,6 +937,12 @@
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "purpose",
"columnName": "purpose",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "status",
"columnName": "status",
@@ -1368,7 +1374,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, '31032766d6bdd6ae4664e854a928dd32')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '770e86deaca12c1e4d82a71f6613da37')"
]
}
}

View File

@@ -122,6 +122,7 @@ abstract class NostrDao(
database.synchronizeNostrEventRequestDao().insert(
synchronizationRelayURLs.map { synchronizationRelayURL ->
SynchronizeNostrEventRequest(
purpose = "synchronization",
synchronizationFilters = arrayOf(
SynchronizationFilter(
authors = arrayOf(nostrEvent.pubKey),
@@ -144,6 +145,7 @@ abstract class NostrDao(
val synchronizeNostrEventRequests = if (recommendRelayUrl != null) {
listOf(
SynchronizeNostrEventRequest(
purpose = "synchronization",
synchronizationFilters = arrayOf(
SynchronizationFilter(
ids = arrayOf(taggedEvent.eventId)
@@ -156,6 +158,7 @@ abstract class NostrDao(
} else {
synchronizationRelayURLs.map { synchronizationRelayURL ->
SynchronizeNostrEventRequest(
purpose = "synchronization",
synchronizationFilters = arrayOf(
SynchronizationFilter(
ids = arrayOf(taggedEvent.eventId)
@@ -178,6 +181,7 @@ abstract class NostrDao(
val synchronizeNostrEventRequests = if (recommendedRelay != null) {
listOf(
SynchronizeNostrEventRequest(
purpose = "synchronization",
synchronizationFilters = arrayOf(
SynchronizationFilter(
authors = arrayOf(taggedUser.pubKey),
@@ -191,6 +195,7 @@ abstract class NostrDao(
} else {
synchronizationRelayURLs.map { synchronizationRelayURL ->
SynchronizeNostrEventRequest(
purpose = "synchronization",
synchronizationFilters = arrayOf(
SynchronizationFilter(
authors = arrayOf(taggedUser.pubKey),
@@ -266,6 +271,7 @@ abstract class NostrDao(
database.synchronizeNostrEventRequestDao().insert(
synchronizationRelayURLs.map { synchronizationRelayURL ->
SynchronizeNostrEventRequest(
purpose = "synchronization",
synchronizationFilters = arrayOf(
SynchronizationFilter(
authors = arrayOf(nostrEvent.pubKey),
@@ -298,6 +304,7 @@ abstract class NostrDao(
database.synchronizeNostrEventRequestDao().insert(
synchronizationRelayURLs.map { synchronizationRelayURL ->
SynchronizeNostrEventRequest(
purpose = "synchronization",
synchronizationFilters = arrayOf(
SynchronizationFilter(
ids = arrayOf(zap.postId)

View File

@@ -12,6 +12,9 @@ interface SynchronizeNostrEventRequestDao {
@Query("SELECT * FROM SynchronizeNostrEventRequest WHERE status = :status")
fun observeSynchronizeNostrEventRequestsByStatus(status: String): Flow<SynchronizeNostrEventRequest?>
@Query("SELECT COUNT(*) FROM SynchronizeNostrEventRequest WHERE purpose = :purpose AND status IN (:status)")
fun observeSynchronizeNostrEventRequestByPurposeAndStatusCount(purpose: String, status: List<String>): Flow<Int>
@Upsert
fun upsert(synchronizeNostrEventRequest: SynchronizeNostrEventRequest)

View File

@@ -38,6 +38,7 @@ import kotlin.uuid.Uuid
data class SynchronizeNostrEventRequest(
@PrimaryKey
val id: String = Uuid.generateV4().toHexDashString(),
val purpose: String,
val status: String = "pending",
val relayURL: String,
val isRecommendedRelay: Boolean = false,

View File

@@ -297,6 +297,13 @@ class DatabaseNostrRepository(
)
}
override suspend fun observeSynchronizeNostrEventRequestByPurposeAndStatusCount(purpose: String): Flow<Int> {
return database.synchronizeNostrEventRequestDao().observeSynchronizeNostrEventRequestByPurposeAndStatusCount(
"feed",
listOf("pending", "processing")
)
}
override suspend fun observeNostrFeed(): Flow<List<LocalNostrEvent>> {
return database.nostrEventDao().observeNostrEvents(
kinds = arrayOf(

View File

@@ -65,6 +65,8 @@ interface NostrRepository {
synchronizeNostrEventRequests: List<SynchronizeNostrEventRequest>,
)
suspend fun observeSynchronizeNostrEventRequestByPurposeAndStatusCount(purpose: String): Flow<Int>
suspend fun observeNostrFeed(): Flow<List<LocalNostrEvent>>
suspend fun observeLocalNostrEventById(nostrEventId: String): Flow<LocalNostrEvent?>
@@ -145,6 +147,10 @@ interface NostrRepository {
TODO("Not yet implemented")
}
override suspend fun observeSynchronizeNostrEventRequestByPurposeAndStatusCount(purpose: String): Flow<Int> {
TODO("Not yet implemented")
}
override suspend fun observeNostrFeed(): Flow<List<LocalNostrEvent>> {
TODO("Not yet implemented")
}

View File

@@ -11,8 +11,10 @@ import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
@@ -23,10 +25,12 @@ import androidx.compose.material.icons.filled.Create
import androidx.compose.material.icons.filled.Person
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.FilterChip
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LoadingIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MediumTopAppBar
import androidx.compose.material3.Scaffold
@@ -45,7 +49,7 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.quartz.nip01Core.core.HexKey
@OptIn(ExperimentalMaterial3Api::class)
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun FeedScreen(
initialFeedListUIState: FeedListUIState,
@@ -194,6 +198,14 @@ fun FeedScreen(
}
}
if (feedListViewModel.isSynchronizationPending.value) {
item {
Spacer(
modifier = Modifier.height(50.dp)
)
LoadingIndicator()
}
}
items(
items = feedListUIState.localNostrEvents,
key = { localNostrEvent -> localNostrEvent.nostrEvent.id }

View File

@@ -6,6 +6,7 @@ import ac.aux.compose.managers.SeedManager
import ac.aux.compose.nostr.Relays
import ac.aux.compose.repository.NostrRepository
import ac.aux.compose.ui.view.state.FeedListUIState
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
@@ -14,6 +15,7 @@ import androidx.lifecycle.ViewModelProvider
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.nip10Notes.TextNoteEvent
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
@@ -28,9 +30,12 @@ class FeedListViewModel(
initialFeedListUIState: FeedListUIState,
val nostrRepository: NostrRepository
): ViewModel() {
val logger = Logger.withTag(TAG)
var feedListUIState: FeedListUIState by mutableStateOf(initialFeedListUIState)
private set
val isSynchronizationPending: MutableState<Boolean> = mutableStateOf(false)
fun retryLoad() {
feedListUIState = FeedListUIState.Loading
loadNostrFeed()
@@ -42,6 +47,7 @@ class FeedListViewModel(
nostrRepository.queueSynchronizeNostrEvent(
Relays.eventPublishRelaySet.map { normalizedRelay ->
SynchronizeNostrEventRequest(
purpose = "feed",
synchronizationFilters = arrayOf(
SynchronizationFilter(
kinds = arrayOf(
@@ -61,6 +67,7 @@ class FeedListViewModel(
}
)
// Get contact list and sync feed...
val localProfile = nostrRepository.observeProfile(SeedManager.activePublicKey().toHexKey()).firstOrNull()
@@ -73,6 +80,11 @@ class FeedListViewModel(
localNostrEvents = localNostrEvents
)
}
nostrRepository.observeSynchronizeNostrEventRequestByPurposeAndStatusCount("feed").collect { count ->
logger.d("Pending/Processing Synchronization Request: $count")
isSynchronizationPending.value = count > 0
}
}
}
}

View File

@@ -43,6 +43,7 @@ class NostrEventDetailViewModel(
nostrRepository.queueSynchronizeNostrEvent(
Relays.eventPublishRelaySet.map { normalizedRelay ->
SynchronizeNostrEventRequest(
purpose = "detail",
synchronizationFilters = arrayOf(
SynchronizationFilter(
kinds = arrayOf(

View File

@@ -55,6 +55,7 @@ class UnqueuedProfileSynchronizationViewModel(
nostrRepository.queueSynchronizeNostrEvent(
Relays.eventPublishRelaySet.map { normalizedRelayUrl ->
SynchronizeNostrEventRequest(
purpose = "sign-in",
synchronizationFilters = arrayOf(
SynchronizationFilter(
authors = arrayOf(profilePublicKey),