Account creation workflow... still need logic.

This commit is contained in:
Kgothatso Ngako
2026-03-25 04:19:06 +02:00
parent 60719d1d41
commit f8e7a20b98
13 changed files with 688 additions and 84 deletions

View File

@@ -2,8 +2,151 @@
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "7219ad348874b83380e4072dec332bee",
"identityHash": "61eb893d34ad04f95f8b007f8448ad7f",
"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 )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "nostrEventId",
"columnName": "nostrEventId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isAccepted",
"columnName": "isAccepted",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "relayURL",
"columnName": "relayURL",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "messages",
"columnName": "messages",
"affinity": "TEXT"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_BroadcastNostrEventReceipt_nostrEventId",
"unique": false,
"columnNames": [
"nostrEventId"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_BroadcastNostrEventReceipt_nostrEventId` ON `${TABLE_NAME}` (`nostrEventId`)"
},
{
"name": "index_BroadcastNostrEventReceipt_isAccepted",
"unique": false,
"columnNames": [
"isAccepted"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_BroadcastNostrEventReceipt_isAccepted` ON `${TABLE_NAME}` (`isAccepted`)"
}
],
"foreignKeys": [
{
"table": "NostrEvent",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"nostrEventId"
],
"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 )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "nostrEventId",
"columnName": "nostrEventId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "status",
"columnName": "status",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "relayURL",
"columnName": "relayURL",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_BroadcastNostrEventRequest_nostrEventId",
"unique": false,
"columnNames": [
"nostrEventId"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_BroadcastNostrEventRequest_nostrEventId` ON `${TABLE_NAME}` (`nostrEventId`)"
},
{
"name": "index_BroadcastNostrEventRequest_status",
"unique": false,
"columnNames": [
"status"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_BroadcastNostrEventRequest_status` ON `${TABLE_NAME}` (`status`)"
}
],
"foreignKeys": [
{
"table": "NostrEvent",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"nostrEventId"
],
"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`))",
@@ -82,7 +225,7 @@
},
{
"tableName": "Post",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `nostrEventId` TEXT NOT NULL, `replyToId` TEXT, `repostId` TEXT, `quote` TEXT, `profilePublicKey` TEXT NOT NULL, `content` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `viewedAt` INTEGER, `deletedAt` INTEGER, `broadcastedAt` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`profilePublicKey`) REFERENCES `Profile`(`publicKey`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`nostrEventId`) REFERENCES `NostrEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`replyToId`) REFERENCES `Post`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`repostId`) REFERENCES `Post`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `repostId` TEXT, `quote` TEXT, `content` TEXT NOT NULL, `nostrEventId` TEXT NOT NULL, `replyToId` TEXT, `profilePublicKey` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `viewedAt` INTEGER, `deletedAt` INTEGER, `broadcastedAt` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`profilePublicKey`) REFERENCES `Profile`(`publicKey`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`nostrEventId`) REFERENCES `NostrEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`replyToId`) REFERENCES `Post`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`repostId`) REFERENCES `Post`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
@@ -90,6 +233,22 @@
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "repostId",
"columnName": "repostId",
"affinity": "TEXT"
},
{
"fieldPath": "quote",
"columnName": "quote",
"affinity": "TEXT"
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "nostrEventId",
"columnName": "nostrEventId",
@@ -101,28 +260,12 @@
"columnName": "replyToId",
"affinity": "TEXT"
},
{
"fieldPath": "repostId",
"columnName": "repostId",
"affinity": "TEXT"
},
{
"fieldPath": "quote",
"columnName": "quote",
"affinity": "TEXT"
},
{
"fieldPath": "profilePublicKey",
"columnName": "profilePublicKey",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "createdAt",
"columnName": "createdAt",
@@ -250,7 +393,7 @@
},
{
"tableName": "Profile",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`publicKey` TEXT NOT NULL, `nostrEventId` TEXT NOT NULL, `userName` TEXT, `displayName` TEXT, `picture` TEXT, `banner` TEXT, `website` TEXT, `about` TEXT, `bot` INTEGER, `pronouns` TEXT, `nip05` TEXT, `domain` TEXT, `lud06` TEXT, `lud16` TEXT, `twitter` TEXT, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `viewedAt` INTEGER, `deletedAt` INTEGER, `broadcastedAt` INTEGER, PRIMARY KEY(`publicKey`), FOREIGN KEY(`nostrEventId`) REFERENCES `NostrEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`publicKey` TEXT NOT NULL, `userName` TEXT, `displayName` TEXT, `picture` TEXT, `banner` TEXT, `website` TEXT, `about` TEXT, `bot` INTEGER, `pronouns` TEXT, `nip05` TEXT, `domain` TEXT, `lud06` TEXT, `lud16` TEXT, `twitter` TEXT, `nostrEventId` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `viewedAt` INTEGER, `deletedAt` INTEGER, `broadcastedAt` INTEGER, PRIMARY KEY(`publicKey`), FOREIGN KEY(`nostrEventId`) REFERENCES `NostrEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "publicKey",
@@ -258,12 +401,6 @@
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "nostrEventId",
"columnName": "nostrEventId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "userName",
"columnName": "userName",
@@ -329,6 +466,12 @@
"columnName": "twitter",
"affinity": "TEXT"
},
{
"fieldPath": "nostrEventId",
"columnName": "nostrEventId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "createdAt",
"columnName": "createdAt",
@@ -396,7 +539,7 @@
},
{
"tableName": "Reaction",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `nostrEventId` TEXT NOT NULL, `replyToId` TEXT NOT NULL, `profilePublicKey` TEXT NOT NULL, `content` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `viewedAt` INTEGER, `deletedAt` INTEGER, `broadcastedAt` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`profilePublicKey`) REFERENCES `Profile`(`publicKey`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`nostrEventId`) REFERENCES `NostrEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`replyToId`) REFERENCES `Post`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `content` TEXT NOT NULL, `nostrEventId` TEXT NOT NULL, `replyToId` TEXT NOT NULL, `profilePublicKey` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `viewedAt` INTEGER, `deletedAt` INTEGER, `broadcastedAt` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`profilePublicKey`) REFERENCES `Profile`(`publicKey`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`nostrEventId`) REFERENCES `NostrEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`replyToId`) REFERENCES `Post`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
@@ -404,6 +547,12 @@
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "nostrEventId",
"columnName": "nostrEventId",
@@ -422,12 +571,6 @@
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "createdAt",
"columnName": "createdAt",
@@ -535,7 +678,7 @@
},
{
"tableName": "Repost",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `nostrEventId` TEXT NOT NULL, `postId` TEXT, `profilePublicKey` TEXT NOT NULL, `content` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `viewedAt` INTEGER, `deletedAt` INTEGER, `broadcastedAt` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`profilePublicKey`) REFERENCES `Profile`(`publicKey`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`nostrEventId`) REFERENCES `NostrEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`postId`) REFERENCES `Post`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `repostedPostId` TEXT, `content` TEXT NOT NULL, `nostrEventId` TEXT NOT NULL, `profilePublicKey` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `viewedAt` INTEGER, `deletedAt` INTEGER, `broadcastedAt` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`profilePublicKey`) REFERENCES `Profile`(`publicKey`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`nostrEventId`) REFERENCES `NostrEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`repostedPostId`) REFERENCES `Post`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
@@ -543,29 +686,29 @@
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "repostedPostId",
"columnName": "repostedPostId",
"affinity": "TEXT"
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "nostrEventId",
"columnName": "nostrEventId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "postId",
"columnName": "postId",
"affinity": "TEXT"
},
{
"fieldPath": "profilePublicKey",
"columnName": "profilePublicKey",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "createdAt",
"columnName": "createdAt",
@@ -626,13 +769,13 @@
"createSql": "CREATE INDEX IF NOT EXISTS `index_Repost_nostrEventId` ON `${TABLE_NAME}` (`nostrEventId`)"
},
{
"name": "index_Repost_postId",
"name": "index_Repost_repostedPostId",
"unique": false,
"columnNames": [
"postId"
"repostedPostId"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_Repost_postId` ON `${TABLE_NAME}` (`postId`)"
"createSql": "CREATE INDEX IF NOT EXISTS `index_Repost_repostedPostId` ON `${TABLE_NAME}` (`repostedPostId`)"
}
],
"foreignKeys": [
@@ -663,7 +806,100 @@
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"postId"
"repostedPostId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "UnsignedNostrEvent",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT 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 )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "kind",
"columnName": "kind",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "tags",
"columnName": "tags",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "signedNostrEventId",
"columnName": "signedNostrEventId",
"affinity": "TEXT"
},
{
"fieldPath": "createdAt",
"columnName": "createdAt",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "updatedAt",
"columnName": "updatedAt",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "savedAt",
"columnName": "savedAt",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "deletedAt",
"columnName": "deletedAt",
"affinity": "INTEGER"
},
{
"fieldPath": "broadcastedAt",
"columnName": "broadcastedAt",
"affinity": "INTEGER"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_UnsignedNostrEvent_signedNostrEventId",
"unique": false,
"columnNames": [
"signedNostrEventId"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_UnsignedNostrEvent_signedNostrEventId` ON `${TABLE_NAME}` (`signedNostrEventId`)"
}
],
"foreignKeys": [
{
"table": "NostrEvent",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"signedNostrEventId"
],
"referencedColumns": [
"id"
@@ -673,7 +909,7 @@
},
{
"tableName": "Zap",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `nostrEventId` TEXT NOT NULL, `postId` TEXT NOT NULL, `senderPublicKey` TEXT NOT NULL, `receiverPublicKey` TEXT NOT NULL, `message` TEXT, `invoice` TEXT, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `viewedAt` INTEGER, `deletedAt` INTEGER, `broadcastedAt` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`senderPublicKey`) REFERENCES `Profile`(`publicKey`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`receiverPublicKey`) REFERENCES `Profile`(`publicKey`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`nostrEventId`) REFERENCES `NostrEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`postId`) REFERENCES `Post`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `postId` TEXT NOT NULL, `senderPublicKey` TEXT NOT NULL, `receiverPublicKey` TEXT NOT NULL, `message` TEXT, `invoice` TEXT, `amountInMillisatoshis` INTEGER, `nostrEventId` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `viewedAt` INTEGER, `deletedAt` INTEGER, `broadcastedAt` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`senderPublicKey`) REFERENCES `Profile`(`publicKey`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`receiverPublicKey`) REFERENCES `Profile`(`publicKey`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`nostrEventId`) REFERENCES `NostrEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`postId`) REFERENCES `Post`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
@@ -681,12 +917,6 @@
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "nostrEventId",
"columnName": "nostrEventId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "postId",
"columnName": "postId",
@@ -715,6 +945,17 @@
"columnName": "invoice",
"affinity": "TEXT"
},
{
"fieldPath": "amountInMillisatoshis",
"columnName": "amountInMillisatoshis",
"affinity": "INTEGER"
},
{
"fieldPath": "nostrEventId",
"columnName": "nostrEventId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "createdAt",
"columnName": "createdAt",
@@ -834,7 +1075,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, '7219ad348874b83380e4072dec332bee')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '61eb893d34ad04f95f8b007f8448ad7f')"
]
}
}

View File

@@ -9,12 +9,16 @@ import ac.aux.compose.database.dao.PostDao
import ac.aux.compose.database.dao.ProfileDao
import ac.aux.compose.database.dao.ReactionDao
import ac.aux.compose.database.dao.RepostDao
import ac.aux.compose.database.dao.UnsignedNostrEventDao
import ac.aux.compose.database.dao.ZapDao
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.Post
import ac.aux.compose.database.model.Profile
import ac.aux.compose.database.model.Reaction
import ac.aux.compose.database.model.Repost
import ac.aux.compose.database.model.UnsignedNostrEvent
import ac.aux.compose.database.model.Zap
import androidx.room.Database
import androidx.room.RoomDatabase
@@ -22,11 +26,14 @@ import androidx.room.TypeConverters
@Database(
entities = [
BroadcastNostrEventReceipt::class,
BroadcastNostrEventRequest::class,
NostrEvent::class,
Post::class,
Profile::class,
Reaction::class,
Repost::class,
UnsignedNostrEvent::class,
Zap::class
],
version = 1
@@ -42,5 +49,7 @@ abstract class AuxDatabase: RoomDatabase() {
abstract fun profileDao(): ProfileDao
abstract fun reactionDao(): ReactionDao
abstract fun repostDao(): RepostDao
abstract fun unsignedNostrEventDao(): UnsignedNostrEventDao
abstract fun zapDao(): ZapDao
}

View File

@@ -20,5 +20,5 @@ interface UnsignedNostrEventDao {
fun getUnsignedNostrEventById(id: Long): Flow<UnsignedNostrEvent?>
@Upsert
suspend fun upsert(unsignedNostrEvent: UnsignedNostrEvent)
suspend fun upsert(unsignedNostrEvent: UnsignedNostrEvent): Long
}

View File

@@ -5,6 +5,7 @@ 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 androidx.room.Entity
import androidx.room.Ignore
import androidx.room.PrimaryKey
import co.touchlab.kermit.Logger
import com.vitorpamplona.quartz.nip01Core.core.HexKey
@@ -36,7 +37,8 @@ data class NostrEvent(
override val deletedAt: Instant? = null,
override val broadcastedAt: Instant? = null,
): TimestampedEntity, LocalStoreEntity, BroadcastableEntity, SoftDeletableEntity {
val logger = Logger.withTag("NostrEvent")
@Ignore
private val logger = Logger.withTag("NostrEvent")
override fun equals(other: Any?): Boolean {
if (this === other) return true

View File

@@ -0,0 +1,21 @@
package ac.aux.compose.database.repository
import ac.aux.compose.database.AuxDatabase
import ac.aux.compose.database.model.UnsignedNostrEvent
import ac.aux.compose.repository.NostrRepository
import kotlinx.coroutines.delay
class DatabaseNostrRepository(
private val database: AuxDatabase
): NostrRepository {
override suspend fun saveUnsignedNostrEvent(unsignedNostrEvent: UnsignedNostrEvent): UnsignedNostrEvent? = try {
delay(2_100) // Just to make it look like we are doing serious work...
unsignedNostrEvent.copy(
id = database.unsignedNostrEventDao().upsert(unsignedNostrEvent)
)
} catch (e: Throwable) {
null
}
}

View File

@@ -0,0 +1,7 @@
package ac.aux.compose.repository
import ac.aux.compose.database.model.UnsignedNostrEvent
interface NostrRepository {
suspend fun saveUnsignedNostrEvent(unsignedNostrEvent: UnsignedNostrEvent): UnsignedNostrEvent?
}

View File

@@ -1,6 +1,12 @@
package ac.aux.compose.ui.composable
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
@@ -14,7 +20,11 @@ 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
@@ -23,20 +33,25 @@ 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 CreateProfileScreen(
initialCreateProfileUIState: CreateProfileUIState = CreateProfileUIState.Declaration,
onNavigateToChatListRoute: () -> Unit
onNavigateToSocialPreconditionRoute: () -> Unit,
onEndThis: () -> Unit,
nostrRepository: NostrRepository
) {
val createProfileViewModel: CreateProfileViewModel = viewModel (
factory = CreateProfileViewModel.factory(
initialCreateProfileUIState
initialCreateProfileUIState,
nostrRepository
)
)
Scaffold { innerPadding ->
@@ -191,11 +206,17 @@ fun CreateProfileScreen(
Button(
onClick = {
createProfileViewModel.createProfileUIState.value =
CreateProfileUIState.ConfirmInput(
name = createProfileViewModel.createProfileFormState.nameField.text.value,
bio = createProfileViewModel.createProfileFormState.biographyField.text.value
)
createProfileViewModel.validateInput().let { isInputValid ->
if (isInputValid) {
createProfileViewModel.createProfileUIState.value =
CreateProfileUIState.ConfirmInput(
name = createProfileViewModel.createProfileFormState.nameField.text.value,
bio = createProfileViewModel.createProfileFormState.biographyField.text.value
)
}
}
}
) {
Text(
@@ -245,13 +266,135 @@ fun CreateProfileScreen(
textAlign = TextAlign.Center
)
if (createProfileViewModel.isActionPending.value) {
LoadingIndicator(
modifier = Modifier
)
} else {
Button(
onClick = {
createProfileViewModel.createAccount()
}
) {
Text(
text = "Create Profile"
)
}
}
}
is CreateProfileUIState.Error -> {
Spacer(
modifier = Modifier.weight(1f)
)
Text(
text = "Something went wrong and we were unable to sign you up. Please try again later.",
style = MaterialTheme.typography.bodyLarge,
textAlign = TextAlign.Center
)
Spacer(
modifier = Modifier.weight(1f)
)
}
is CreateProfileUIState.UnsignedProfile -> {
Spacer(
modifier = Modifier.weight(1f)
)
Text(
text = "Your profile is almost ready... just getting it's first cryptographic signature together.",
style = MaterialTheme.typography.bodyLarge,
textAlign = TextAlign.Center
)
Text(
text = "As long as you control your keys there can be no dispute about who ${createAccountUIState.name} actually is.",
style = MaterialTheme.typography.bodySmall,
textAlign = TextAlign.Center
)
LoadingDataIndicator(
fillScreen = false
)
Spacer(
modifier = Modifier.weight(2f)
)
}
is CreateProfileUIState.UnbroadcastedProfile -> {
Spacer(
modifier = Modifier.weight(1f)
)
Text(
text = "Everything is cryptographical sound. Just announcing your profile to the world.",
style = MaterialTheme.typography.bodyLarge,
textAlign = TextAlign.Center
)
Text(
text = "Aux will be broadcast to what you publish to relays so that it's distributed and decentralized.",
style = MaterialTheme.typography.bodySmall,
textAlign = TextAlign.Center
)
LoadingDataIndicator(
fillScreen = false
)
Spacer(
modifier = Modifier.weight(2f)
)
}
is CreateProfileUIState.ProfileReady -> {
Spacer(
modifier = Modifier.weight(1f)
)
Text(
text = "Profile is ready",
style = MaterialTheme.typography.headlineLarge,
textAlign = TextAlign.Center
)
Text(
text = "After this there is no turning back."
)
Button(
onClick = onNavigateToChatListRoute
colors = ButtonDefaults.buttonColors(
containerColor = RedPill,
contentColor = Color.White
),
onClick = {
onNavigateToSocialPreconditionRoute.invoke()
}
) {
Text(
text = "Create Profile"
text = "See how deep the rabbit hole goes"
)
}
HorizontalDivider(
modifier = Modifier.padding(20.dp)
)
Button(
colors = ButtonDefaults.buttonColors(
containerColor = BluePill,
contentColor = Color.DarkGray
),
onClick = {
// TODO: Delete everything and close the app
onEndThis.invoke()
}
) {
Text(
text = "end this"
)
}
Spacer(
modifier = Modifier.weight(2f)
)
}
}
@@ -270,13 +413,48 @@ fun CreateAccountScreenPreview() {
) {
CreateProfileScreen(
initialCreateProfileUIState =
// CreateProfileUIState.InputPrompt
CreateProfileUIState.ConfirmInput(
name = "Alan Turing",
bio = "Polyglot: Math... is my middle name, computer scientist. cryptographer, and gold hider."
// CreateProfileUIState.InputPrompt,
// CreateProfileUIState.Error,
// CreateProfileUIState.ConfirmInput(
// name = "Alan Turing",
// bio = "Polyglot: Math... is my middle name, computer scientist. cryptographer, and gold hider."
// ),
// CreateProfileUIState.UnsignedProfile(
// unsignedNostrEvent = UnsignedNostrEvent(
// kind = 0,
// content = "Something here",
// tags = emptyArray(),
// ),
// name = "Alan Turing",
// bio = "Polyglot: Math... is my middle name, computer scientist. cryptographer, and gold hider."
// ),
// CreateProfileUIState.UnbroadcastedProfile(
// nostrEvent = NostrEvent(
// id = "",
// pubKey = "",
// kind = 0,
// content = "Something here",
// tags = emptyArray(),
// sig = ""
// ),
// name = "Alan Turing",
// bio = "Polyglot: Math... is my middle name, computer scientist. cryptographer, and gold hider."
// ),
CreateProfileUIState.ProfileReady(
profile = Profile(
publicKey = "",
nostrEventId = "",
displayName = "Alan Turing",
about = "Polyglot: Math... is my middle name, computer scientist. cryptographer, and gold hider."
)
),
onNavigateToChatListRoute = {}
)
onNavigateToSocialPreconditionRoute = {},
onEndThis = {},
nostrRepository = object : NostrRepository {
override suspend fun saveUnsignedNostrEvent(unsignedNostrEvent: UnsignedNostrEvent): UnsignedNostrEvent? {
TODO("Not yet implemented")
}
})
}
}
}

View File

@@ -77,7 +77,7 @@ fun SocialPreconditionScreen(
)
Text(
"Tell friends to join you on Aux so your feed stays lively and fresh.",
"Tell friends to join you so your feed stays lively and fresh.",
textAlign = TextAlign.Center
)

View File

@@ -1,10 +1,13 @@
package ac.aux.compose.ui.composable.navigation
import ac.aux.compose.database.model.UnsignedNostrEvent
import ac.aux.compose.repository.NostrRepository
import ac.aux.compose.ui.composable.CreateProfileScreen
import ac.aux.compose.ui.composable.FeedScreen
import ac.aux.compose.ui.composable.ImplementationPendingScreen
import ac.aux.compose.ui.composable.LandingScreen
import ac.aux.compose.ui.composable.SocialPreconditionScreen
import ac.aux.compose.ui.composable.navigation.routes.BlankRoute
import ac.aux.compose.ui.composable.navigation.routes.CreateProfileRoute
import ac.aux.compose.ui.composable.navigation.routes.FeedRoute
import ac.aux.compose.ui.composable.navigation.routes.ImplementationPendingRoute
@@ -18,6 +21,12 @@ import ac.aux.compose.ui.composable.navigation.routes.Route
import ac.aux.compose.ui.composable.navigation.routes.SocialPreconditionRoute
import ac.aux.compose.ui.composable.widgets.feed.DUMMY_EVENTS
import ac.aux.compose.ui.view.state.FeedListUIState
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Surface
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import kotlinx.coroutines.delay
@Composable
fun AuxNavHost(
@@ -50,12 +59,25 @@ fun AuxNavHost(
}
composable<CreateProfileRoute> {
CreateProfileScreen(
onNavigateToChatListRoute = {
onNavigateToSocialPreconditionRoute = {
navController.navigate(
route = SocialPreconditionRoute
)
}
)
},
onEndThis = {
// TODO: Delete everything and close the app
navController.navigate(
route = BlankRoute
) {
popUpTo(0)
}
},
nostrRepository = object : NostrRepository {
override suspend fun saveUnsignedNostrEvent(unsignedNostrEvent: UnsignedNostrEvent): UnsignedNostrEvent? {
delay(2_100)
return unsignedNostrEvent
}
})
}
composable<SocialPreconditionRoute> {
SocialPreconditionScreen(
@@ -90,6 +112,15 @@ fun AuxNavHost(
}
)
}
composable<BlankRoute> {
Surface(
modifier = Modifier.fillMaxSize(),
color = Color.Black
) {
}
}
composable<ImplementationPendingRoute> { backStackEntry ->
val route: ImplementationPendingRoute = backStackEntry.toRoute()
ImplementationPendingScreen(

View File

@@ -0,0 +1,6 @@
package ac.aux.compose.ui.composable.navigation.routes
import kotlinx.serialization.Serializable
@Serializable
object BlankRoute: Route()

View File

@@ -2,6 +2,9 @@ package ac.aux.compose.ui.theme
import androidx.compose.ui.graphics.Color
val BluePill = Color(0xFF5D8DD6)
val RedPill = Color(230, 32, 32, 191)
val primaryLight = Color(0xFF000000)
val onPrimaryLight = Color(0xFFFFFFFF)
val primaryContainerLight = Color(0xFF1B1B1B)

View File

@@ -1,5 +1,9 @@
package ac.aux.compose.ui.view.model
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.view.state.CreateProfileUIState
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
@@ -8,21 +12,31 @@ import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import ac.aux.compose.ui.view.state.form.CreateProfileFormState
import androidx.lifecycle.viewModelScope
import co.touchlab.kermit.Logger
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlin.time.Instant
class CreateProfileViewModel(
val initialCreateProfileUIState: CreateProfileUIState,
val createProfileFormState: CreateProfileFormState = CreateProfileFormState(),
val nostrRepository: NostrRepository
): ViewModel() {
companion object {
private const val TAG = "CreateAccountViewModel"
fun factory(
initialCreateProfileUIState: CreateProfileUIState
initialCreateProfileUIState: CreateProfileUIState,
nostrRepository: NostrRepository
): ViewModelProvider.Factory = viewModelFactory {
initializer {
CreateProfileViewModel(
initialCreateProfileUIState
initialCreateProfileUIState,
nostrRepository = nostrRepository
)
}
}
@@ -36,11 +50,82 @@ class CreateProfileViewModel(
initialCreateProfileUIState
)
public fun createAccount(
onSuccess: () -> Unit,
onFailure: () -> Unit
) {
fun validateInput(): Boolean {
if (createProfileFormState.nameField.text.value.isBlank()) {
createProfileFormState.nameField.errorMessage.value = "Please input a display name"
return false
} else {
createProfileFormState.nameField.errorMessage.value = null
}
if (createProfileFormState.biographyField.text.value.isBlank()) {
createProfileFormState.biographyField.errorMessage.value = "Please input a biography"
return false
} else {
createProfileFormState.biographyField.errorMessage.value = null
}
return true
}
public fun createAccount() {
logger.d { "createAccount" }
onFailure.invoke()
isActionPending.value = true
viewModelScope.launch(Dispatchers.IO) {
val profileEventTemplate = MetadataEvent.createNew(
name = createProfileFormState.nameField.text.value,
about = createProfileFormState.biographyField.text.value
)
val unsignedNostrEvent = nostrRepository.saveUnsignedNostrEvent(
UnsignedNostrEvent(
kind = profileEventTemplate.kind,
createdAt = Instant.fromEpochMilliseconds(profileEventTemplate.createdAt),
tags = profileEventTemplate.tags,
content = profileEventTemplate.content
)
)
isActionPending.value = false
if (unsignedNostrEvent != null) {
createProfileUIState.value = CreateProfileUIState.UnsignedProfile(
unsignedNostrEvent = unsignedNostrEvent,
name = createProfileFormState.nameField.text.value,
bio = createProfileFormState.biographyField.text.value
)
delay(7_500) // Seeming busy is very important...
createProfileUIState.value = CreateProfileUIState.UnbroadcastedProfile(
nostrEvent = NostrEvent(
id = "",
pubKey = "",
kind = 0,
content = "Something here",
tags = emptyArray(),
sig = ""
),
name = createProfileFormState.nameField.text.value,
bio = createProfileFormState.biographyField.text.value
)
delay(4_500) // Seeming busy is very important...
createProfileUIState.value = CreateProfileUIState.ProfileReady(
profile = Profile(
publicKey = "",
nostrEventId = "",
displayName = createProfileFormState.nameField.text.value,
about = createProfileFormState.biographyField.text.value,
)
)
} else {
createProfileUIState.value = CreateProfileUIState.Error
}
}
}
}

View File

@@ -1,6 +1,11 @@
package ac.aux.compose.ui.view.state
import ac.aux.compose.database.model.NostrEvent
import ac.aux.compose.database.model.Profile
import ac.aux.compose.database.model.UnsignedNostrEvent
abstract class CreateProfileUIState {
data object Error: CreateProfileUIState()
data object Declaration: CreateProfileUIState()
@@ -10,4 +15,20 @@ abstract class CreateProfileUIState {
val name: String,
val bio: String
) : CreateProfileUIState()
data class UnsignedProfile(
val unsignedNostrEvent: UnsignedNostrEvent,
val name: String,
val bio: String,
) : CreateProfileUIState()
data class UnbroadcastedProfile(
val nostrEvent: NostrEvent,
val name: String,
val bio: String
) : CreateProfileUIState()
data class ProfileReady(
val profile: Profile
) : CreateProfileUIState()
}