diff --git a/composeApp/schemas/ac.aux.compose.database.AuxDatabase/1.json b/composeApp/schemas/ac.aux.compose.database.AuxDatabase/1.json index 97ad3579..4ea050c1 100644 --- a/composeApp/schemas/ac.aux.compose.database.AuxDatabase/1.json +++ b/composeApp/schemas/ac.aux.compose.database.AuxDatabase/1.json @@ -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')" ] } } \ 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 66a34de7..74ad4e6f 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 @@ -112,6 +112,7 @@ abstract class NostrDao( database.broadcastNostrEventRequestDao().upsert( BroadcastNostrEventRequest( nostrEventId = nostrEvent.id, + unsignedNostrEventId = nostrEvent.unsignedNostrEventId, relayURL = relayURL ) ) diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/database/dao/UnsignedNostrEventDao.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/dao/UnsignedNostrEventDao.kt index d83b4bdd..95947dd1 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/database/dao/UnsignedNostrEventDao.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/dao/UnsignedNostrEventDao.kt @@ -14,7 +14,7 @@ interface UnsignedNostrEventDao { @Query("SELECT * FROM UnsignedNostrEvent") fun getAUnsignedNostrEvents(): List - @Query("SELECT * FROM UnsignedNostrEvent WHERE signedNostrEventId IS NULL") + @Query("SELECT * FROM UnsignedNostrEvent") // TODO: where nostrEvent.unsignedNostrEventId is 0 fun getAllUnprocessedUnsignedNostrEvents(): List @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 - @Query("SELECT * FROM UnsignedNostrEvent WHERE pubKey = :publicKey AND signedNostrEventId IS NULL") - fun observeUnsignedNostrEvents(publicKey: String): Flow + @Query("SELECT * FROM UnsignedNostrEvent WHERE pubKey = :publicKey") // TODO: where nostrEvent.unsignedNostrEventId is 0 + fun observeUnsignedNostrEvents(publicKey: String): Flow } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/database/model/BroadcastNostrEventReceipt.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/model/BroadcastNostrEventReceipt.kt index 6f9bc79c..566f318b 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/database/model/BroadcastNostrEventReceipt.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/model/BroadcastNostrEventReceipt.kt @@ -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 \ No newline at end of file +): NostrEventEntity, UnsignedNostrEventEntity \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/database/model/BroadcastNostrEventRequest.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/model/BroadcastNostrEventRequest.kt index 62d89845..3643037b 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/database/model/BroadcastNostrEventRequest.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/model/BroadcastNostrEventRequest.kt @@ -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 \ No newline at end of file +): NostrEventEntity, UnsignedNostrEventEntity \ No newline at end of file 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 29c7d195..aec78c2a 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 @@ -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") diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/database/model/UnsignedNostrEvent.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/model/UnsignedNostrEvent.kt index c6b4db71..9da55fca 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/database/model/UnsignedNostrEvent.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/model/UnsignedNostrEvent.kt @@ -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(), diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/database/model/intermdiate/LocalProfile.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/model/intermdiate/LocalProfile.kt index 1cb323db..a316a987 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/database/model/intermdiate/LocalProfile.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/model/intermdiate/LocalProfile.kt @@ -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? ) diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/database/model/traits/UnsignedNostrEventEntity.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/model/traits/UnsignedNostrEventEntity.kt new file mode 100644 index 00000000..b2836505 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/model/traits/UnsignedNostrEventEntity.kt @@ -0,0 +1,5 @@ +package ac.aux.compose.database.model.traits + +interface UnsignedNostrEventEntity { + val unsignedNostrEventId: Long? +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/database/repository/DatabaseNostrRepository.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/repository/DatabaseNostrRepository.kt index fc329ed5..5174e0be 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/database/repository/DatabaseNostrRepository.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/repository/DatabaseNostrRepository.kt @@ -26,7 +26,7 @@ class DatabaseNostrRepository( return database.unsignedNostrEventDao().observeProfile(publicKey) } - override suspend fun observeUnsignedNostrEvents(publicKey: HexKey): Flow { + override suspend fun observeUnsignedNostrEvents(publicKey: HexKey): Flow { return database.unsignedNostrEventDao().observeUnsignedNostrEvents(publicKey) } diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/repository/NostrRepository.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/repository/NostrRepository.kt index ca491c2e..6d12bab9 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/repository/NostrRepository.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/repository/NostrRepository.kt @@ -10,7 +10,7 @@ import kotlinx.coroutines.flow.Flow interface NostrRepository { suspend fun observeProfile(publicKey: HexKey): Flow - suspend fun observeUnsignedNostrEvents(publicKey: HexKey): Flow + suspend fun observeUnsignedNostrEvents(publicKey: HexKey): Flow suspend fun createNewProfile( publicKey: HexKey, @@ -37,7 +37,7 @@ interface NostrRepository { TODO("Not yet implemented") } - override suspend fun observeUnsignedNostrEvents(publicKey: HexKey): Flow { + override suspend fun observeUnsignedNostrEvents(publicKey: HexKey): Flow { TODO("Not yet implemented") } diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/UnindexedProfileScreen.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/UnindexedProfileScreen.kt new file mode 100644 index 00000000..9493e4db --- /dev/null +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/UnindexedProfileScreen.kt @@ -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() + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/UnqueuedProfileScreen.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/UnqueuedProfileScreen.kt new file mode 100644 index 00000000..467927e5 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/UnqueuedProfileScreen.kt @@ -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() + } + } +} \ No newline at end of file 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 bd123936..0f3f38f5 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 @@ -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 { + UnindexedProfileScreen() + } + composable { + UnqueuedProfileScreen() + } composable { backStackEntry -> val route = backStackEntry.toRoute() diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/navigation/routes/UnindexedProfileRoute.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/navigation/routes/UnindexedProfileRoute.kt new file mode 100755 index 00000000..161569a0 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/navigation/routes/UnindexedProfileRoute.kt @@ -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() { + +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/navigation/routes/UnqueuedProfileRoute.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/navigation/routes/UnqueuedProfileRoute.kt new file mode 100755 index 00000000..78fe58d1 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/navigation/routes/UnqueuedProfileRoute.kt @@ -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() { + +} \ No newline at end of file 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 4ff6cfb9..7f6397b7 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 @@ -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( + createdAt = unsignedNostrEvent.createdAt.toEpochMilliseconds(), + kind = unsignedNostrEvent.kind, + tags = unsignedNostrEvent.tags, + content = unsignedNostrEvent.content + ) - val event = tempSigner.signNormal( - 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 { diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/view/state/NavigationUIState.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/view/state/NavigationUIState.kt index 2f6ca964..a1434819 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/view/state/NavigationUIState.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/view/state/NavigationUIState.kt @@ -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() } \ No newline at end of file