Improve create profile flow

This commit is contained in:
Kgothatso Ngako committed 2026-05-19 00:33:33 +02:00
1 parent 9264e95af0
commit e4e7d6570b
8 files changed
+89 -111

No files matched your search

@@ -58,28 +58,6 @@ abstract class NostrDao(
signedAt = Clock.System.now() signedAt = Clock.System.now()
) )
) )
// Find or create profile with the pubKey... if not found TODO: submit a sync request...
// val profile = database.profileDao().getProfileByPublicKey(nostrEvent.pubKey)
//
// if (profile == null) {
// val placeHolderProfile = Profile(
// publicKey = nostrEvent.pubKey,
// nostrEventId = nostrEvent.pubKey // This is illegal... because the nostrEventId should be the one with all the profile information
// )
// val placeHolderProfileNostrEvent = NostrEvent(
// id = placeHolderProfile.publicKey,
// kind = DraftWrapEvent.KIND,
// pubKey = placeHolderProfile.publicKey,
// content = placeHolderProfile.publicKey,
// createdAt = GENESIS_AT,
// sig = placeHolderProfile.publicKey,
// tags = emptyArray(),
// )
// database.nostrEventDao().upsert(placeHolderProfileNostrEvent)
// database.profileDao().upsert(placeHolderProfile)
//
// // TODO: Sync to find and update profile
// }
database.nostrEventDao().upsert(nostrEvent) database.nostrEventDao().upsert(nostrEvent)
@@ -3,6 +3,7 @@ package ac.cord.auxiliary.compose.database.dao
import ac.cord.auxiliary.compose.database.model.UnsignedNostrEvent import ac.cord.auxiliary.compose.database.model.UnsignedNostrEvent
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalAccount import ac.cord.auxiliary.compose.database.model.intermdiate.LocalAccount
import androidx.room3.Dao import androidx.room3.Dao
import androidx.room3.Insert
import androidx.room3.Query import androidx.room3.Query
import androidx.room3.Transaction import androidx.room3.Transaction
import androidx.room3.Upsert import androidx.room3.Upsert
@@ -22,10 +23,13 @@ interface UnsignedNostrEventDao {
@Upsert @Upsert
suspend fun upsert(unsignedNostrEvent: UnsignedNostrEvent): Long suspend fun upsert(unsignedNostrEvent: UnsignedNostrEvent): Long
@Insert
suspend fun insert(unsignedNostrEvents: List<UnsignedNostrEvent>)
@Transaction @Transaction
@Query("SELECT * FROM UnsignedNostrEvent WHERE kind = 0 AND pubKey = :publicKey") @Query("SELECT * FROM UnsignedNostrEvent WHERE kind = 0 AND pubKey = :publicKey")
fun observeProfile(publicKey: String): Flow<LocalAccount?> fun observeProfile(publicKey: String): Flow<LocalAccount?>
@Query("SELECT * FROM UnsignedNostrEvent WHERE pubKey = :publicKey AND signedAt IS NULL") // TODO: where nostrEvent.unsignedNostrEventId is 0 @Query("SELECT * FROM UnsignedNostrEvent WHERE pubKey = :publicKey AND signedAt IS NULL")
fun observeUnsignedNostrEvents(publicKey: String): Flow<UnsignedNostrEvent?> fun observeUnsignedNostrEvents(publicKey: String): Flow<UnsignedNostrEvent?>
} }
@@ -63,7 +63,6 @@ import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import kotlin.collections.emptyMap import kotlin.collections.emptyMap
import kotlin.collections.plus import kotlin.collections.plus
import kotlin.math.sin
import kotlin.time.Clock import kotlin.time.Clock
import kotlin.time.Instant import kotlin.time.Instant
@@ -116,48 +115,29 @@ class DatabaseNostrRepository(
override suspend fun createNewProfile( override suspend fun createNewProfile(
publicKey: HexKey, publicKey: HexKey,
name: String?, name: String?,
biography: String?, biography: String?
onError: () -> Unit,
onProfileReady: (Profile) -> Unit,
synchronizationRelayURLs: List<String>
) { ) {
val profileEventTemplate = MetadataEvent.createNew( val profileEventTemplate = MetadataEvent.createNew(
name = name, name = name,
about = biography, about = biography,
) )
val signedAt = if (name == null && biography == null) { val unsignedNostrEvents = mutableListOf(
// We are most likely try to sign in to an already existing block...
GENESIS_AT
} else {
null
}
val unsignedNostrEvent = saveUnsignedNostrEvent(
UnsignedNostrEvent( UnsignedNostrEvent(
pubKey = publicKey, pubKey = publicKey,
kind = profileEventTemplate.kind, kind = profileEventTemplate.kind,
createdAt = Instant.fromEpochSeconds(profileEventTemplate.createdAt), createdAt = Instant.fromEpochSeconds(profileEventTemplate.createdAt),
tags = profileEventTemplate.tags, tags = profileEventTemplate.tags,
content = profileEventTemplate.content, content = profileEventTemplate.content,
signedAt = signedAt // This will keep us from trying to sign this event... but we should automatically sync the npub profile signedAt = null
) )
) )
if (unsignedNostrEvent == null) {
onError.invoke()
} else if (unsignedNostrEvent.kind == 0) {
// Look busy for the onboarding screen
delay(3_500)
if (unsignedNostrEvent.signedAt == null) {
// Save ContactListNostrEvent
val followUsers = listOf(ContactTag(publicKey, null, null)) val followUsers = listOf(ContactTag(publicKey, null, null))
val contactListTagList = listOf(AltTag.assemble(ContactListEvent.ALT)) + val contactListTagList = listOf(AltTag.assemble(ContactListEvent.ALT)) +
followUsers.map { it.toTagArray() } followUsers.map { it.toTagArray() }
saveUnsignedNostrEvent( unsignedNostrEvents.add(
UnsignedNostrEvent( UnsignedNostrEvent(
pubKey = publicKey, pubKey = publicKey,
kind = ContactListEvent.KIND, kind = ContactListEvent.KIND,
@@ -168,8 +148,18 @@ class DatabaseNostrRepository(
) )
) )
// Save AdvertisedRelayListEvent unsignedNostrEvents.add(
saveUnsignedNostrEvent( UnsignedNostrEvent(
pubKey = publicKey,
kind = ChatMessageRelayListEvent.KIND,
tags = ChatMessageRelayListEvent.createTagArray(
Relays.DefaultDMRelayList
),
content = ""
)
)
unsignedNostrEvents.add(
UnsignedNostrEvent( UnsignedNostrEvent(
pubKey = publicKey, pubKey = publicKey,
kind = AdvertisedRelayListEvent.KIND, kind = AdvertisedRelayListEvent.KIND,
@@ -177,20 +167,10 @@ class DatabaseNostrRepository(
content = "" content = ""
) )
) )
// Save ChatMessageRelayListEvent
saveUnsignedNostrEvent(
UnsignedNostrEvent(
pubKey = publicKey, unsignedNostrEvents.add(
kind = ChatMessageRelayListEvent.KIND,
tags = Relays.DefaultDMRelayList.map{ RelayTag.assemble(it) }
.plusElement(
AltTag.assemble("Relay list to receive private messages")
).toTypedArray(),
content = ""
)
)
// Save SearchRelayListEvent
saveUnsignedNostrEvent(
UnsignedNostrEvent( UnsignedNostrEvent(
pubKey = publicKey, pubKey = publicKey,
kind = SearchRelayListEvent.KIND, kind = SearchRelayListEvent.KIND,
@@ -202,8 +182,8 @@ class DatabaseNostrRepository(
content = "" content = ""
) )
) )
// Save IndexerRelayListEvent
saveUnsignedNostrEvent( unsignedNostrEvents.add(
UnsignedNostrEvent( UnsignedNostrEvent(
pubKey = publicKey, pubKey = publicKey,
kind = IndexerRelayListEvent.KIND, kind = IndexerRelayListEvent.KIND,
@@ -212,8 +192,8 @@ class DatabaseNostrRepository(
content = "" content = ""
) )
) )
// Save ChannelListEvent
saveUnsignedNostrEvent( unsignedNostrEvents.add(
UnsignedNostrEvent( UnsignedNostrEvent(
pubKey = publicKey, pubKey = publicKey,
kind = ChannelListEvent.KIND, kind = ChannelListEvent.KIND,
@@ -226,8 +206,8 @@ class DatabaseNostrRepository(
content = "" content = ""
) )
) )
// Save RelayFeedsListEvent
saveUnsignedNostrEvent( unsignedNostrEvents.add(
UnsignedNostrEvent( UnsignedNostrEvent(
pubKey = publicKey, pubKey = publicKey,
kind = RelayFeedsListEvent.KIND, kind = RelayFeedsListEvent.KIND,
@@ -238,10 +218,26 @@ class DatabaseNostrRepository(
content = "" content = ""
) )
) )
}
} saveUnsignedNostrEvents(unsignedNostrEvents)
} }
override suspend fun signInToProfile(
publicKey: HexKey
) {
val profileEventTemplate = MetadataEvent.createNew()
saveUnsignedNostrEvent(
UnsignedNostrEvent(
pubKey = publicKey,
kind = profileEventTemplate.kind,
createdAt = Instant.fromEpochSeconds(profileEventTemplate.createdAt),
tags = profileEventTemplate.tags,
content = profileEventTemplate.content,
signedAt = GENESIS_AT // This will keep us from trying to sign this event... but we should automatically sync the npub profile
)
)
}
override suspend fun createNewTextNote( override suspend fun createNewTextNote(
publicKey: HexKey, publicKey: HexKey,
mentionedPublicKeys: List<HexKey>, mentionedPublicKeys: List<HexKey>,
@@ -362,8 +358,12 @@ class DatabaseNostrRepository(
} }
override suspend fun saveUnsignedNostrEvent(unsignedNostrEvent: UnsignedNostrEvent): UnsignedNostrEvent? = try { override suspend fun saveUnsignedNostrEvent(unsignedNostrEvent: UnsignedNostrEvent): UnsignedNostrEvent? = try {
logger.d("Save unsigned: ${unsignedNostrEvent.kind}")
// TODO: We can actually check the unsignedNostrEvent.pubKey for support... if it's writeable // TODO: We can actually check the unsignedNostrEvent.pubKey for support... if it's writeable
if (unsignedNostrEvent.kind == MetadataEvent.KIND){
delay(2_100) // Just to make it look like we are doing serious work... delay(2_100) // Just to make it look like we are doing serious work...
}
unsignedNostrEvent.copy( unsignedNostrEvent.copy(
id = database.unsignedNostrEventDao().upsert(unsignedNostrEvent) id = database.unsignedNostrEventDao().upsert(unsignedNostrEvent)
) )
@@ -372,6 +372,15 @@ class DatabaseNostrRepository(
null null
} }
override suspend fun saveUnsignedNostrEvents(unsignedNostrEvents: List<UnsignedNostrEvent>) {
logger.d("saveUnsignedNostrEvents: $unsignedNostrEvents")
delay(2_100)
database.unsignedNostrEventDao().insert(
unsignedNostrEvents
)
}
override suspend fun wipeDatabase() { override suspend fun wipeDatabase() {
database.wipeData() database.wipeData()
} }
@@ -51,7 +51,7 @@ object Relays {
val DefaultGlobalRelays = listOf(wine, news) val DefaultGlobalRelays = listOf(wine, news)
val DefaultDMRelayList = listOf(auth, oxchat, nos) val DefaultDMRelayList = listOf(primal, oxtr, nos)
val DefaultSearchRelayList = setOf(wine, where, nostoday, antiprimal, ditto) val DefaultSearchRelayList = setOf(wine, where, nostoday, antiprimal, ditto)
@@ -40,12 +40,11 @@ interface NostrRepository {
suspend fun createNewProfile( suspend fun createNewProfile(
publicKey: HexKey, publicKey: HexKey,
name: String?, name: String?,
biography: String?, biography: String?
onError: () -> Unit, )
// onUnsignProfile: (UnsignedNostrEvent) -> Unit,
// onUnbroadcastedProfile: (NostrEvent) -> Unit, suspend fun signInToProfile(
onProfileReady: (Profile) -> Unit, publicKey: HexKey
synchronizationRelayURLs: List<String>
) )
suspend fun createNewTextNote( suspend fun createNewTextNote(
@@ -60,6 +59,8 @@ interface NostrRepository {
suspend fun saveUnsignedNostrEvent(unsignedNostrEvent: UnsignedNostrEvent): UnsignedNostrEvent? suspend fun saveUnsignedNostrEvent(unsignedNostrEvent: UnsignedNostrEvent): UnsignedNostrEvent?
suspend fun saveUnsignedNostrEvents(unsignedNostrEvents: List<UnsignedNostrEvent>)
suspend fun wipeDatabase() suspend fun wipeDatabase()
suspend fun publishNostrEvent( suspend fun publishNostrEvent(
@@ -169,13 +170,14 @@ interface NostrRepository {
override suspend fun createNewProfile( override suspend fun createNewProfile(
publicKey: HexKey, publicKey: HexKey,
name: String?, name: String?,
biography: String?, biography: String?
onError: () -> Unit,
onProfileReady: (Profile) -> Unit,
synchronizationRelayURLs: List<String>
) { ) {
} }
override suspend fun signInToProfile(publicKey: HexKey) {
TODO("Not yet implemented")
}
override suspend fun createNewTextNote( override suspend fun createNewTextNote(
publicKey: HexKey, publicKey: HexKey,
mentionedPublicKeys: List<HexKey>, mentionedPublicKeys: List<HexKey>,
@@ -192,6 +194,10 @@ interface NostrRepository {
return null return null
} }
override suspend fun saveUnsignedNostrEvents(unsignedNostrEvents: List<UnsignedNostrEvent>) {
TODO("Not yet implemented")
}
override suspend fun wipeDatabase() { override suspend fun wipeDatabase() {
} }
@@ -1,7 +1,6 @@
package ac.cord.auxiliary.compose.ui.view.model package ac.cord.auxiliary.compose.ui.view.model
import ac.cord.auxiliary.compose.managers.SeedManager import ac.cord.auxiliary.compose.managers.SeedManager
import ac.cord.auxiliary.compose.nostr.Relays
import ac.cord.auxiliary.compose.repository.NostrRepository import ac.cord.auxiliary.compose.repository.NostrRepository
import ac.cord.auxiliary.compose.ui.view.state.CreateProfileUIState import ac.cord.auxiliary.compose.ui.view.state.CreateProfileUIState
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
@@ -78,16 +77,7 @@ class CreateProfileViewModel(
nostrRepository.createNewProfile( nostrRepository.createNewProfile(
publicKey, publicKey,
name = createProfileFormState.nameField.textFieldState.text.toString(), name = createProfileFormState.nameField.textFieldState.text.toString(),
biography = createProfileFormState.biographyField.textFieldState.text.toString(), biography = createProfileFormState.biographyField.textFieldState.text.toString()
onError = {
createProfileUIState.value = CreateProfileUIState.Error
},
{ profile ->
createProfileUIState.value = CreateProfileUIState.ProfileReady(
profile = profile
)
},
synchronizationRelayURLs = Relays.eventPublishRelaySet.map { normalizedRelayUrl -> normalizedRelayUrl.url }
) )
} }
} }
@@ -104,19 +104,21 @@ class NavigationViewModel(
val tempSigner = NostrSignerSync( val tempSigner = NostrSignerSync(
SeedManager.activeKeyPair() SeedManager.activeKeyPair()
) )
logger.i { "observeUnsignedNostrEvents" }
scope.launch(Dispatchers.IO) { scope.launch(Dispatchers.IO) {
logger.i { "observeUnsignedNostrEvents" }
nostrRepository.observeUnsignedNostrEvents( nostrRepository.observeUnsignedNostrEvents(
publicKey = SeedManager.activePublicKey().toHexKey() publicKey = SeedManager.activePublicKey().toHexKey()
).distinctUntilChanged().collect { unsignedNostrEventOrNull -> ).distinctUntilChanged().collect { unsignedNostrEventOrNull ->
scope.launch(Dispatchers.IO) {
unsignedNostrEventOrNull?.let { unsignedNostrEvent -> unsignedNostrEventOrNull?.let { unsignedNostrEvent ->
logger.d("Unsigned") logger.d("Unsigned: ${unsignedNostrEvent.kind}")
val event = tempSigner.signNormal<Event>( val event = tempSigner.signNormal<Event>(
createdAt = unsignedNostrEvent.createdAt.epochSeconds, createdAt = unsignedNostrEvent.createdAt.epochSeconds,
kind = unsignedNostrEvent.kind, kind = unsignedNostrEvent.kind,
tags = unsignedNostrEvent.tags, tags = unsignedNostrEvent.tags,
content = unsignedNostrEvent.privateTags?.let { PrivateTagsInContent.encryptNip44(it, tempSigner) } ?: unsignedNostrEvent.content content = unsignedNostrEvent.privateTags?.let { PrivateTagsInContent.encryptNip44(it, tempSigner) } ?: unsignedNostrEvent.content
) )
logger.d("Signed: ${event.toJson()}")
nostrRepository.publishNostrEvent( nostrRepository.publishNostrEvent(
unsignedNostrEvent, unsignedNostrEvent,
@@ -133,7 +135,7 @@ class NavigationViewModel(
relayURLs = Relays.eventPublishRelaySet.map { normalizedRelayUrl -> normalizedRelayUrl.url } relayURLs = Relays.eventPublishRelaySet.map { normalizedRelayUrl -> normalizedRelayUrl.url }
) )
} }
}
} }
} }
} }
@@ -1,6 +1,5 @@
package ac.cord.auxiliary.compose.ui.view.model package ac.cord.auxiliary.compose.ui.view.model
import ac.cord.auxiliary.compose.nostr.Relays
import ac.cord.auxiliary.compose.repository.NostrRepository import ac.cord.auxiliary.compose.repository.NostrRepository
import ac.cord.auxiliary.compose.ui.view.state.SignInToProfileUIState import ac.cord.auxiliary.compose.ui.view.state.SignInToProfileUIState
import ac.cord.auxiliary.compose.ui.view.state.form.SignInToProfileFormState import ac.cord.auxiliary.compose.ui.view.state.form.SignInToProfileFormState
@@ -72,23 +71,13 @@ class SignInToProfileViewModel(
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
when (credentialEntity) { when (credentialEntity) {
is NPub -> { is NPub -> {
nostrRepository.createNewProfile( nostrRepository.signInToProfile(
publicKey = credentialEntity.hex, publicKey = credentialEntity.hex
name = null,
biography = null,
onError = onError,
onProfileReady = {},
synchronizationRelayURLs = Relays.eventPublishRelaySet.map { normalizedRelayUrl -> normalizedRelayUrl.url }
) )
} }
is NSec -> { is NSec -> {
nostrRepository.createNewProfile( nostrRepository.signInToProfile(
publicKey = credentialEntity.toPubKeyHex(), publicKey = credentialEntity.toPubKeyHex()
name = null,
biography = null,
onError = onError,
onProfileReady = {},
synchronizationRelayURLs = Relays.eventPublishRelaySet.map { normalizedRelayUrl -> normalizedRelayUrl.url }
) )
} }
else -> { else -> {