Associate unsignedNostrEvent to broadcast request/receipt

This commit is contained in:
Kgothatso Ngako
2026-03-26 23:32:47 +02:00
parent 72631cadf1
commit 5d2e376adf
18 changed files with 411 additions and 79 deletions

View File

@@ -2,11 +2,11 @@
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "3d48904218bb18d9efb6dba14449f27c",
"identityHash": "5b27659e597a301316dcc8ed9eaf8a3e",
"entities": [
{
"tableName": "BroadcastNostrEventReceipt",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nostrEventId` TEXT NOT NULL, `isAccepted` INTEGER NOT NULL, `relayURL` TEXT NOT NULL, `messages` TEXT, FOREIGN KEY(`nostrEventId`) REFERENCES `NostrEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nostrEventId` TEXT NOT NULL, `unsignedNostrEventId` INTEGER, `isAccepted` INTEGER NOT NULL, `relayURL` TEXT NOT NULL, `messages` TEXT, 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",
@@ -20,6 +20,11 @@
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "unsignedNostrEventId",
"columnName": "unsignedNostrEventId",
"affinity": "INTEGER"
},
{
"fieldPath": "isAccepted",
"columnName": "isAccepted",
@@ -75,12 +80,23 @@
"referencedColumns": [
"id"
]
},
{
"table": "UnsignedNostrEvent",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"unsignedNostrEventId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "BroadcastNostrEventRequest",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nostrEventId` TEXT NOT NULL, `status` TEXT NOT NULL, `relayURL` TEXT NOT NULL, FOREIGN KEY(`nostrEventId`) REFERENCES `NostrEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nostrEventId` TEXT NOT NULL, `unsignedNostrEventId` INTEGER, `status` TEXT NOT NULL, `relayURL` TEXT NOT NULL, 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",
@@ -94,6 +110,11 @@
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "unsignedNostrEventId",
"columnName": "unsignedNostrEventId",
"affinity": "INTEGER"
},
{
"fieldPath": "status",
"columnName": "status",
@@ -144,12 +165,23 @@
"referencedColumns": [
"id"
]
},
{
"table": "UnsignedNostrEvent",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"unsignedNostrEventId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "NostrEvent",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `pubKey` TEXT NOT NULL, `kind` INTEGER NOT NULL, `tags` TEXT NOT NULL, `content` TEXT NOT NULL, `sig` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `deletedAt` INTEGER, `broadcastedAt` INTEGER, PRIMARY KEY(`id`))",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `pubKey` TEXT NOT NULL, `kind` INTEGER NOT NULL, `tags` TEXT NOT NULL, `content` TEXT NOT NULL, `sig` TEXT NOT NULL, `unsignedNostrEventId` INTEGER, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `deletedAt` INTEGER, `broadcastedAt` INTEGER, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
@@ -187,6 +219,11 @@
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "unsignedNostrEventId",
"columnName": "unsignedNostrEventId",
"affinity": "INTEGER"
},
{
"fieldPath": "createdAt",
"columnName": "createdAt",
@@ -836,7 +873,7 @@
},
{
"tableName": "UnsignedNostrEvent",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `pubKey` TEXT NOT NULL, `kind` INTEGER NOT NULL, `tags` TEXT NOT NULL, `content` TEXT NOT NULL, `signedNostrEventId` TEXT, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `deletedAt` INTEGER, `broadcastedAt` INTEGER, FOREIGN KEY(`signedNostrEventId`) REFERENCES `NostrEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `pubKey` TEXT NOT NULL, `kind` INTEGER NOT NULL, `tags` TEXT NOT NULL, `content` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `deletedAt` INTEGER, `broadcastedAt` INTEGER)",
"fields": [
{
"fieldPath": "id",
@@ -868,11 +905,6 @@
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "signedNostrEventId",
"columnName": "signedNostrEventId",
"affinity": "TEXT"
},
{
"fieldPath": "createdAt",
"columnName": "createdAt",
@@ -910,26 +942,22 @@
},
"indices": [
{
"name": "index_UnsignedNostrEvent_signedNostrEventId",
"name": "index_UnsignedNostrEvent_pubKey",
"unique": false,
"columnNames": [
"signedNostrEventId"
"pubKey"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_UnsignedNostrEvent_signedNostrEventId` ON `${TABLE_NAME}` (`signedNostrEventId`)"
}
],
"foreignKeys": [
"createSql": "CREATE INDEX IF NOT EXISTS `index_UnsignedNostrEvent_pubKey` ON `${TABLE_NAME}` (`pubKey`)"
},
{
"table": "NostrEvent",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"signedNostrEventId"
"name": "index_UnsignedNostrEvent_kind",
"unique": false,
"columnNames": [
"kind"
],
"referencedColumns": [
"id"
]
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_UnsignedNostrEvent_kind` ON `${TABLE_NAME}` (`kind`)"
}
]
},
@@ -1101,7 +1129,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, '3d48904218bb18d9efb6dba14449f27c')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5b27659e597a301316dcc8ed9eaf8a3e')"
]
}
}

View File

@@ -112,6 +112,7 @@ abstract class NostrDao(
database.broadcastNostrEventRequestDao().upsert(
BroadcastNostrEventRequest(
nostrEventId = nostrEvent.id,
unsignedNostrEventId = nostrEvent.unsignedNostrEventId,
relayURL = relayURL
)
)

View File

@@ -14,7 +14,7 @@ interface UnsignedNostrEventDao {
@Query("SELECT * FROM UnsignedNostrEvent")
fun getAUnsignedNostrEvents(): List<UnsignedNostrEvent>
@Query("SELECT * FROM UnsignedNostrEvent WHERE signedNostrEventId IS NULL")
@Query("SELECT * FROM UnsignedNostrEvent") // TODO: where nostrEvent.unsignedNostrEventId is 0
fun getAllUnprocessedUnsignedNostrEvents(): List<UnsignedNostrEvent>
@Query("SELECT * FROM UnsignedNostrEvent WHERE id = :id")
@@ -26,6 +26,6 @@ interface UnsignedNostrEventDao {
@Query("SELECT * FROM UnsignedNostrEvent WHERE kind = 0 AND pubKey = :publicKey")
fun observeProfile(publicKey: String): Flow<LocalProfile?>
@Query("SELECT * FROM UnsignedNostrEvent WHERE pubKey = :publicKey AND signedNostrEventId IS NULL")
fun observeUnsignedNostrEvents(publicKey: String): Flow<UnsignedNostrEvent>
@Query("SELECT * FROM UnsignedNostrEvent WHERE pubKey = :publicKey") // TODO: where nostrEvent.unsignedNostrEventId is 0
fun observeUnsignedNostrEvents(publicKey: String): Flow<UnsignedNostrEvent?>
}

View File

@@ -1,6 +1,7 @@
package ac.aux.compose.database.model
import ac.aux.compose.database.model.traits.NostrEventEntity
import ac.aux.compose.database.model.traits.UnsignedNostrEventEntity
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Index
@@ -15,6 +16,12 @@ import com.vitorpamplona.quartz.nip01Core.core.HexKey
childColumns = ["nostrEventId"],
onDelete = ForeignKey.CASCADE,
),
ForeignKey(
entity = UnsignedNostrEvent::class,
parentColumns = ["id"],
childColumns = ["unsignedNostrEventId"],
onDelete = ForeignKey.CASCADE,
),
],
indices = [
Index("nostrEventId"),
@@ -25,7 +32,8 @@ data class BroadcastNostrEventReceipt(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
override val nostrEventId: HexKey,
override val unsignedNostrEventId: Long? = null,
val isAccepted: Boolean,
val relayURL: String,
val messages: String? = null
): NostrEventEntity
): NostrEventEntity, UnsignedNostrEventEntity

View File

@@ -1,6 +1,7 @@
package ac.aux.compose.database.model
import ac.aux.compose.database.model.traits.NostrEventEntity
import ac.aux.compose.database.model.traits.UnsignedNostrEventEntity
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Index
@@ -15,6 +16,12 @@ import com.vitorpamplona.quartz.nip01Core.core.HexKey
childColumns = ["nostrEventId"],
onDelete = ForeignKey.CASCADE,
),
ForeignKey(
entity = UnsignedNostrEvent::class,
parentColumns = ["id"],
childColumns = ["unsignedNostrEventId"],
onDelete = ForeignKey.CASCADE,
),
],
indices = [
Index("nostrEventId"),
@@ -25,6 +32,7 @@ data class BroadcastNostrEventRequest(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
override val nostrEventId: HexKey,
override val unsignedNostrEventId: Long? = null,
val status: String = "pending",
val relayURL: String,
): NostrEventEntity
): NostrEventEntity, UnsignedNostrEventEntity

View File

@@ -4,7 +4,9 @@ import ac.aux.compose.database.model.traits.BroadcastableEntity
import ac.aux.compose.database.model.traits.LocalStoreEntity
import ac.aux.compose.database.model.traits.SoftDeletableEntity
import ac.aux.compose.database.model.traits.TimestampedEntity
import ac.aux.compose.database.model.traits.UnsignedNostrEventEntity
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Ignore
import androidx.room.Index
import androidx.room.PrimaryKey
@@ -37,12 +39,14 @@ data class NostrEvent(
val content: String,
val sig: HexKey,
override val unsignedNostrEventId: Long? = null,
override val createdAt: Instant = Clock.System.now(),
override val updatedAt: Instant = createdAt,
override val savedAt: Instant = Clock.System.now(),
override val deletedAt: Instant? = null,
override val broadcastedAt: Instant? = null,
): TimestampedEntity, LocalStoreEntity, BroadcastableEntity, SoftDeletableEntity {
): UnsignedNostrEventEntity, TimestampedEntity, LocalStoreEntity, BroadcastableEntity, SoftDeletableEntity {
@Ignore
private val logger = Logger.withTag("NostrEvent")

View File

@@ -23,16 +23,9 @@ import kotlin.time.Clock
import kotlin.time.Instant
@Entity(
foreignKeys = [
ForeignKey(
entity = NostrEvent::class,
parentColumns = ["id"],
childColumns = ["signedNostrEventId"],
onDelete = ForeignKey.CASCADE,
)
],
indices = [
Index("signedNostrEventId"),
Index("pubKey"),
Index("kind"),
],
)
data class UnsignedNostrEvent(
@@ -44,8 +37,6 @@ data class UnsignedNostrEvent(
val tags: TagArray,
val content: String,
val signedNostrEventId: HexKey? = null,
override val createdAt: Instant = Clock.System.now(),
override val updatedAt: Instant = Clock.System.now(),
override val savedAt: Instant = Clock.System.now(),

View File

@@ -9,11 +9,11 @@ import androidx.room.Embedded
import androidx.room.Relation
data class LocalProfile(
@Embedded val unsignedNostrEvent: UnsignedNostrEvent,
@Embedded val unsignedNostrEvent: UnsignedNostrEvent?,
@Relation(
parentColumn = "signedNostrEventId",
entityColumn = "id"
parentColumn = "id",
entityColumn = "unsignedNostrEventId"
)
val nostrEvent: NostrEvent?,
@@ -24,14 +24,14 @@ data class LocalProfile(
val profile: Profile?,
@Relation(
parentColumn = "signedNostrEventId",
entityColumn = "nostrEventId"
parentColumn = "id",
entityColumn = "unsignedNostrEventId"
)
val broadcastNostrEventRequest: BroadcastNostrEventRequest?,
@Relation(
parentColumn = "signedNostrEventId",
entityColumn = "nostrEventId"
parentColumn = "id",
entityColumn = "unsignedNostrEventId"
)
val broadcastNostrEventReceipt: BroadcastNostrEventReceipt?
)

View File

@@ -0,0 +1,5 @@
package ac.aux.compose.database.model.traits
interface UnsignedNostrEventEntity {
val unsignedNostrEventId: Long?
}

View File

@@ -26,7 +26,7 @@ class DatabaseNostrRepository(
return database.unsignedNostrEventDao().observeProfile(publicKey)
}
override suspend fun observeUnsignedNostrEvents(publicKey: HexKey): Flow<UnsignedNostrEvent> {
override suspend fun observeUnsignedNostrEvents(publicKey: HexKey): Flow<UnsignedNostrEvent?> {
return database.unsignedNostrEventDao().observeUnsignedNostrEvents(publicKey)
}

View File

@@ -10,7 +10,7 @@ import kotlinx.coroutines.flow.Flow
interface NostrRepository {
suspend fun observeProfile(publicKey: HexKey): Flow<LocalProfile?>
suspend fun observeUnsignedNostrEvents(publicKey: HexKey): Flow<UnsignedNostrEvent>
suspend fun observeUnsignedNostrEvents(publicKey: HexKey): Flow<UnsignedNostrEvent?>
suspend fun createNewProfile(
publicKey: HexKey,
@@ -37,7 +37,7 @@ interface NostrRepository {
TODO("Not yet implemented")
}
override suspend fun observeUnsignedNostrEvents(publicKey: HexKey): Flow<UnsignedNostrEvent> {
override suspend fun observeUnsignedNostrEvents(publicKey: HexKey): Flow<UnsignedNostrEvent?> {
TODO("Not yet implemented")
}

View File

@@ -0,0 +1,103 @@
package ac.aux.compose.ui.composable
import ac.aux.compose.database.model.NostrEvent
import ac.aux.compose.database.model.Profile
import ac.aux.compose.database.model.UnsignedNostrEvent
import ac.aux.compose.repository.NostrRepository
import ac.aux.compose.ui.composable.widgets.LoadingDataIndicator
import ac.aux.compose.ui.theme.AuxTheme
import ac.aux.compose.ui.theme.BluePill
import ac.aux.compose.ui.theme.RedPill
import ac.aux.compose.ui.view.model.CreateProfileViewModel
import ac.aux.compose.ui.view.state.CreateProfileUIState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Badge
import androidx.compose.material.icons.filled.Description
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.LoadingIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun UnindexedProfileScreen() {
Scaffold { innerPadding ->
Column(
modifier = Modifier.padding(innerPadding)
) {
Column(
modifier = Modifier.fillMaxWidth().padding(
10.dp
),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(20.dp)
) {
Spacer(
modifier = Modifier.weight(1f)
)
Text(
text = "Everything is cryptographical sound. Just indexing your profile on the device.",
style = MaterialTheme.typography.bodyLarge,
textAlign = TextAlign.Center
)
Text(
text = "Events are indexed so that we can deliver a premium local first experience.",
style = MaterialTheme.typography.bodySmall,
textAlign = TextAlign.Center
)
Spacer(
modifier = Modifier.weight(1f)
)
LoadingDataIndicator(
fillScreen = false
)
Spacer(
modifier = Modifier.weight(8f)
)
// TODO: Give user ability to start over...
}
}
}
}
@Preview
@Composable
private fun UnannouncedProfileScreenPreview() {
AuxTheme {
Surface(
modifier = Modifier.fillMaxSize()
) {
UnindexedProfileScreen()
}
}
}

View File

@@ -0,0 +1,103 @@
package ac.aux.compose.ui.composable
import ac.aux.compose.database.model.NostrEvent
import ac.aux.compose.database.model.Profile
import ac.aux.compose.database.model.UnsignedNostrEvent
import ac.aux.compose.repository.NostrRepository
import ac.aux.compose.ui.composable.widgets.LoadingDataIndicator
import ac.aux.compose.ui.theme.AuxTheme
import ac.aux.compose.ui.theme.BluePill
import ac.aux.compose.ui.theme.RedPill
import ac.aux.compose.ui.view.model.CreateProfileViewModel
import ac.aux.compose.ui.view.state.CreateProfileUIState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Badge
import androidx.compose.material.icons.filled.Description
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.LoadingIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun UnqueuedProfileScreen() {
Scaffold { innerPadding ->
Column(
modifier = Modifier.padding(innerPadding)
) {
Column(
modifier = Modifier.fillMaxWidth().padding(
10.dp
),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(20.dp)
) {
Spacer(
modifier = Modifier.weight(1f)
)
Text(
text = "Everything is cryptographical sound. Just need to queue your profile and announce it to the world.",
style = MaterialTheme.typography.bodyLarge,
textAlign = TextAlign.Center
)
Text(
text = "All broadcasts are queued so that we can manage data usage on metered connections.",
style = MaterialTheme.typography.bodySmall,
textAlign = TextAlign.Center
)
Spacer(
modifier = Modifier.weight(1f)
)
LoadingDataIndicator(
fillScreen = false
)
Spacer(
modifier = Modifier.weight(8f)
)
// TODO: Give user ability to start over...
}
}
}
}
@Preview
@Composable
private fun UnannouncedProfileScreenPreview() {
AuxTheme {
Surface(
modifier = Modifier.fillMaxSize()
) {
UnqueuedProfileScreen()
}
}
}

View File

@@ -10,6 +10,8 @@ import ac.aux.compose.ui.composable.LandingScreen
import ac.aux.compose.ui.composable.LoadingScreen
import ac.aux.compose.ui.composable.SocialPreconditionScreen
import ac.aux.compose.ui.composable.UnannouncedProfileScreen
import ac.aux.compose.ui.composable.UnindexedProfileScreen
import ac.aux.compose.ui.composable.UnqueuedProfileScreen
import ac.aux.compose.ui.composable.UnsignedProfileScreen
import ac.aux.compose.ui.composable.navigation.routes.BlankRoute
import ac.aux.compose.ui.composable.navigation.routes.CreateProfileRoute
@@ -24,6 +26,8 @@ import androidx.navigation.compose.composable
import androidx.navigation.toRoute
import ac.aux.compose.ui.composable.navigation.routes.SocialPreconditionRoute
import ac.aux.compose.ui.composable.navigation.routes.UnannouncedProfileRoute
import ac.aux.compose.ui.composable.navigation.routes.UnindexedProfileRoute
import ac.aux.compose.ui.composable.navigation.routes.UnqueuedProfileRoute
import ac.aux.compose.ui.composable.navigation.routes.UnsignedProfileRoute
import ac.aux.compose.ui.composable.widgets.feed.DUMMY_EVENTS
import ac.aux.compose.ui.view.model.NavigationViewModel
@@ -91,10 +95,26 @@ fun AuxNavHost(
popUpTo(0)
}
}
is NavigationUIState.UnindexedProfile -> {
navController.navigate(
route = UnindexedProfileRoute(
state.nostrEvent.id
)
) {
popUpTo(0)
}
}
is NavigationUIState.UnqueuedProfile -> {
navController.navigate(
route = UnqueuedProfileRoute(
state.profile.nostrEventId
)
)
}
is NavigationUIState.UnannouncedProfile -> {
navController.navigate(
route = UnannouncedProfileRoute(
state.nostrEvent.id
state.broadcastNostrEventRequest.nostrEventId
)
) {
popUpTo(0)
@@ -170,6 +190,12 @@ fun AuxNavHost(
unsignedNostrEvent = route.toPlaceholderUnsignedNostrEvent()
)
}
composable<UnindexedProfileRoute> {
UnindexedProfileScreen()
}
composable<UnqueuedProfileRoute> {
UnqueuedProfileScreen()
}
composable<UnannouncedProfileRoute> { backStackEntry ->
val route = backStackEntry.toRoute<UnannouncedProfileRoute>()

View File

@@ -0,0 +1,13 @@
package ac.aux.compose.ui.composable.navigation.routes
import ac.aux.compose.database.model.NostrEvent
import ac.aux.compose.database.model.Profile
import kotlinx.serialization.Serializable
import kotlin.time.Clock
@Serializable
data class UnindexedProfileRoute(
val nostrEventId: String,
): Route() {
}

View File

@@ -0,0 +1,13 @@
package ac.aux.compose.ui.composable.navigation.routes
import ac.aux.compose.database.model.NostrEvent
import ac.aux.compose.database.model.Profile
import kotlinx.serialization.Serializable
import kotlin.time.Clock
@Serializable
data class UnqueuedProfileRoute(
val nostrEventId: String,
): Route() {
}

View File

@@ -64,27 +64,29 @@ class NavigationViewModel(
viewModelScope.launch {
nostrRepository.observeUnsignedNostrEvents(
publicKey = SeedManager.activePublicKey().toHexKey()
).collect { unsignedNostrEvent ->
).collect { unsignedNostrEventOrNull ->
unsignedNostrEventOrNull?.let { unsignedNostrEvent ->
val event = tempSigner.signNormal<Event>(
createdAt = unsignedNostrEvent.createdAt.toEpochMilliseconds(),
kind = unsignedNostrEvent.kind,
tags = unsignedNostrEvent.tags,
content = unsignedNostrEvent.content
)
val event = tempSigner.signNormal<Event>(
createdAt = unsignedNostrEvent.createdAt.toEpochMilliseconds(),
kind = unsignedNostrEvent.kind,
tags = unsignedNostrEvent.tags,
content = unsignedNostrEvent.content
)
nostrRepository.publishNostrEvent(
NostrEvent(
id = event.id,
pubKey = event.pubKey,
kind = event.kind,
tags = event.tags,
content = event.content,
createdAt = Instant.fromEpochMilliseconds(event.createdAt),
sig = event.sig
),
relayURLs = Relays.eventFinderRelaySet.map { normalizedRelayUrl -> normalizedRelayUrl.url }
)
nostrRepository.publishNostrEvent(
NostrEvent(
id = event.id,
pubKey = event.pubKey,
kind = event.kind,
tags = event.tags,
content = event.content,
createdAt = Instant.fromEpochMilliseconds(event.createdAt),
sig = event.sig,
unsignedNostrEventId = unsignedNostrEvent.id
),
relayURLs = Relays.eventFinderRelaySet.map { normalizedRelayUrl -> normalizedRelayUrl.url }
)
}
}
}
@@ -105,14 +107,27 @@ class NavigationViewModel(
if (localProfile == null) {
logger.i("We need to be on the landing screen so user creates account")
NavigationUIState.Landing
} else if (localProfile.profile != null) {
logger.i("We have a profile: ${localProfile.profile}")
} else if (localProfile.unsignedNostrEvent == null) {
logger.i("We need to be on the landing screen so user creates account")
NavigationUIState.Landing
} else if (localProfile.broadcastNostrEventReceipt != null) {
logger.i("We have a broadcast receipt: ${localProfile.profile}")
NavigationUIState.ProfileLoaded(
broadcastNostrEventReceipt = localProfile.broadcastNostrEventReceipt
)
} else if (localProfile.broadcastNostrEventRequest != null) {
logger.i("We have a profile: ${localProfile.profile}")
NavigationUIState.UnannouncedProfile(
broadcastNostrEventRequest = localProfile.broadcastNostrEventRequest
)
} else if (localProfile.profile != null) {
logger.i("We have a profile that needs to queued for broadcast: ${localProfile.profile}")
NavigationUIState.UnqueuedProfile(
profile = localProfile.profile
)
} else if (localProfile.nostrEvent != null) {
logger.i("Nostr event with the profile needs to be announced")
NavigationUIState.UnannouncedProfile(
logger.i("Nostr event with the profile needs to be indexed: ${localProfile.nostrEvent}")
NavigationUIState.UnindexedProfile(
nostrEvent = localProfile.nostrEvent
)
} else {

View File

@@ -1,8 +1,11 @@
package ac.aux.compose.ui.view.state
import ac.aux.compose.database.model.BroadcastNostrEventReceipt
import ac.aux.compose.database.model.BroadcastNostrEventRequest
import ac.aux.compose.database.model.NostrEvent
import ac.aux.compose.database.model.Profile
import ac.aux.compose.database.model.UnsignedNostrEvent
import ac.aux.compose.database.model.intermdiate.LocalProfile
abstract class NavigationUIState {
data object Error: NavigationUIState()
@@ -15,11 +18,22 @@ abstract class NavigationUIState {
val unsignedNostrEvent: UnsignedNostrEvent,
) : NavigationUIState()
data class UnannouncedProfile(
data class UnindexedProfile(
val nostrEvent: NostrEvent,
) : NavigationUIState()
data class UnqueuedProfile(
val profile: Profile,
) : NavigationUIState()
data class UnannouncedProfile(
val broadcastNostrEventRequest: BroadcastNostrEventRequest,
) : NavigationUIState()
data class ProfileLoaded(
val profile: Profile
val broadcastNostrEventReceipt: BroadcastNostrEventReceipt
) : NavigationUIState()
}