Improve create profile flow

This commit is contained in:
Kgothatso Ngako
2026-05-19 00:33:33 +02:00
parent 9264e95af0
commit e4e7d6570b
8 changed files with 168 additions and 190 deletions

View File

@@ -58,28 +58,6 @@ abstract class NostrDao(
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)

View File

@@ -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.intermdiate.LocalAccount
import androidx.room3.Dao
import androidx.room3.Insert
import androidx.room3.Query
import androidx.room3.Transaction
import androidx.room3.Upsert
@@ -22,10 +23,13 @@ interface UnsignedNostrEventDao {
@Upsert
suspend fun upsert(unsignedNostrEvent: UnsignedNostrEvent): Long
@Insert
suspend fun insert(unsignedNostrEvents: List<UnsignedNostrEvent>)
@Transaction
@Query("SELECT * FROM UnsignedNostrEvent WHERE kind = 0 AND pubKey = :publicKey")
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?>
}

View File

@@ -63,7 +63,6 @@ import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlin.collections.emptyMap
import kotlin.collections.plus
import kotlin.math.sin
import kotlin.time.Clock
import kotlin.time.Instant
@@ -116,132 +115,129 @@ class DatabaseNostrRepository(
override suspend fun createNewProfile(
publicKey: HexKey,
name: String?,
biography: String?,
onError: () -> Unit,
onProfileReady: (Profile) -> Unit,
synchronizationRelayURLs: List<String>
biography: String?
) {
val profileEventTemplate = MetadataEvent.createNew(
name = name,
about = biography,
)
val signedAt = if (name == null && biography == null) {
// We are most likely try to sign in to an already existing block...
GENESIS_AT
} else {
null
}
val unsignedNostrEvent = saveUnsignedNostrEvent(
val unsignedNostrEvents = mutableListOf(
UnsignedNostrEvent(
pubKey = publicKey,
kind = profileEventTemplate.kind,
createdAt = Instant.fromEpochSeconds(profileEventTemplate.createdAt),
tags = profileEventTemplate.tags,
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)
val followUsers = listOf(ContactTag(publicKey, null, null))
val contactListTagList = listOf(AltTag.assemble(ContactListEvent.ALT)) +
followUsers.map { it.toTagArray() }
unsignedNostrEvents.add(
UnsignedNostrEvent(
pubKey = publicKey,
kind = ContactListEvent.KIND,
tags = contactListTagList.toTypedArray(),
content = RelaySet.assemble(
emptyMap()
)
)
)
unsignedNostrEvents.add(
UnsignedNostrEvent(
pubKey = publicKey,
kind = ChatMessageRelayListEvent.KIND,
tags = ChatMessageRelayListEvent.createTagArray(
Relays.DefaultDMRelayList
),
content = ""
)
)
unsignedNostrEvents.add(
UnsignedNostrEvent(
pubKey = publicKey,
kind = AdvertisedRelayListEvent.KIND,
tags = Relays.DefaultNIP65List.map{ it.toTagArray() }.toTypedArray(),
content = ""
)
)
if (unsignedNostrEvent.signedAt == null) {
// Save ContactListNostrEvent
val followUsers = listOf(ContactTag(publicKey, null, null))
val contactListTagList = listOf(AltTag.assemble(ContactListEvent.ALT)) +
followUsers.map { it.toTagArray() }
saveUnsignedNostrEvent(
UnsignedNostrEvent(
pubKey = publicKey,
kind = ContactListEvent.KIND,
tags = contactListTagList.toTypedArray(),
content = RelaySet.assemble(
emptyMap()
)
)
)
unsignedNostrEvents.add(
UnsignedNostrEvent(
pubKey = publicKey,
kind = SearchRelayListEvent.KIND,
tags = Relays.DefaultSearchRelayList.map{ RelayTag.assemble(it) }
.plus(
SearchRelayListEvent.ALT_TAG
).toTypedArray(),
privateTags = emptyArray(),
content = ""
)
)
// Save AdvertisedRelayListEvent
saveUnsignedNostrEvent(
UnsignedNostrEvent(
pubKey = publicKey,
kind = AdvertisedRelayListEvent.KIND,
tags = Relays.DefaultNIP65List.map{ it.toTagArray() }.toTypedArray(),
content = ""
unsignedNostrEvents.add(
UnsignedNostrEvent(
pubKey = publicKey,
kind = IndexerRelayListEvent.KIND,
tags = arrayOf(AltTag.assemble(IndexerRelayListEvent.ALT)),
privateTags = Relays.DefaultIndexerRelayList.map { RelayTag.assemble(it) }.toTypedArray() ,
content = ""
)
)
unsignedNostrEvents.add(
UnsignedNostrEvent(
pubKey = publicKey,
kind = ChannelListEvent.KIND,
tags = arrayOf(
AltTag.assemble(
ChannelListEvent.ALT
)
)
// Save ChatMessageRelayListEvent
saveUnsignedNostrEvent(
UnsignedNostrEvent(
pubKey = publicKey,
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(
pubKey = publicKey,
kind = SearchRelayListEvent.KIND,
tags = Relays.DefaultSearchRelayList.map{ RelayTag.assemble(it) }
.plus(
SearchRelayListEvent.ALT_TAG
).toTypedArray(),
privateTags = emptyArray(),
content = ""
)
)
// Save IndexerRelayListEvent
saveUnsignedNostrEvent(
UnsignedNostrEvent(
pubKey = publicKey,
kind = IndexerRelayListEvent.KIND,
tags = arrayOf(AltTag.assemble(IndexerRelayListEvent.ALT)),
privateTags = Relays.DefaultIndexerRelayList.map { RelayTag.assemble(it) }.toTypedArray() ,
content = ""
)
)
// Save ChannelListEvent
saveUnsignedNostrEvent(
UnsignedNostrEvent(
pubKey = publicKey,
kind = ChannelListEvent.KIND,
tags = arrayOf(
AltTag.assemble(
ChannelListEvent.ALT
)
),
privateTags = Relays.DefaultChannels.map { it.toTagArray() }.toTypedArray(),
content = ""
)
)
// Save RelayFeedsListEvent
saveUnsignedNostrEvent(
UnsignedNostrEvent(
pubKey = publicKey,
kind = RelayFeedsListEvent.KIND,
tags = arrayOf(
AltTag.assemble(RelayFeedsListEvent.ALT)
),
privateTags = Relays.DefaultGlobalRelays.map { RelayTag.assemble(it)}.toTypedArray(),
content = ""
)
)
}
}
),
privateTags = Relays.DefaultChannels.map { it.toTagArray() }.toTypedArray(),
content = ""
)
)
unsignedNostrEvents.add(
UnsignedNostrEvent(
pubKey = publicKey,
kind = RelayFeedsListEvent.KIND,
tags = arrayOf(
AltTag.assemble(RelayFeedsListEvent.ALT)
),
privateTags = Relays.DefaultGlobalRelays.map { RelayTag.assemble(it)}.toTypedArray(),
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(
publicKey: HexKey,
mentionedPublicKeys: List<HexKey>,
@@ -362,8 +358,12 @@ class DatabaseNostrRepository(
}
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
delay(2_100) // Just to make it look like we are doing serious work...
if (unsignedNostrEvent.kind == MetadataEvent.KIND){
delay(2_100) // Just to make it look like we are doing serious work...
}
unsignedNostrEvent.copy(
id = database.unsignedNostrEventDao().upsert(unsignedNostrEvent)
)
@@ -372,6 +372,15 @@ class DatabaseNostrRepository(
null
}
override suspend fun saveUnsignedNostrEvents(unsignedNostrEvents: List<UnsignedNostrEvent>) {
logger.d("saveUnsignedNostrEvents: $unsignedNostrEvents")
delay(2_100)
database.unsignedNostrEventDao().insert(
unsignedNostrEvents
)
}
override suspend fun wipeDatabase() {
database.wipeData()
}

View File

@@ -51,7 +51,7 @@ object Relays {
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)

View File

@@ -40,12 +40,11 @@ interface NostrRepository {
suspend fun createNewProfile(
publicKey: HexKey,
name: String?,
biography: String?,
onError: () -> Unit,
// onUnsignProfile: (UnsignedNostrEvent) -> Unit,
// onUnbroadcastedProfile: (NostrEvent) -> Unit,
onProfileReady: (Profile) -> Unit,
synchronizationRelayURLs: List<String>
biography: String?
)
suspend fun signInToProfile(
publicKey: HexKey
)
suspend fun createNewTextNote(
@@ -60,6 +59,8 @@ interface NostrRepository {
suspend fun saveUnsignedNostrEvent(unsignedNostrEvent: UnsignedNostrEvent): UnsignedNostrEvent?
suspend fun saveUnsignedNostrEvents(unsignedNostrEvents: List<UnsignedNostrEvent>)
suspend fun wipeDatabase()
suspend fun publishNostrEvent(
@@ -169,13 +170,14 @@ interface NostrRepository {
override suspend fun createNewProfile(
publicKey: HexKey,
name: String?,
biography: String?,
onError: () -> Unit,
onProfileReady: (Profile) -> Unit,
synchronizationRelayURLs: List<String>
biography: String?
) {
}
override suspend fun signInToProfile(publicKey: HexKey) {
TODO("Not yet implemented")
}
override suspend fun createNewTextNote(
publicKey: HexKey,
mentionedPublicKeys: List<HexKey>,
@@ -192,6 +194,10 @@ interface NostrRepository {
return null
}
override suspend fun saveUnsignedNostrEvents(unsignedNostrEvents: List<UnsignedNostrEvent>) {
TODO("Not yet implemented")
}
override suspend fun wipeDatabase() {
}

View File

@@ -1,7 +1,6 @@
package ac.cord.auxiliary.compose.ui.view.model
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.ui.view.state.CreateProfileUIState
import androidx.compose.runtime.MutableState
@@ -78,16 +77,7 @@ class CreateProfileViewModel(
nostrRepository.createNewProfile(
publicKey,
name = createProfileFormState.nameField.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 }
biography = createProfileFormState.biographyField.textFieldState.text.toString()
)
}
}

View File

@@ -104,36 +104,38 @@ class NavigationViewModel(
val tempSigner = NostrSignerSync(
SeedManager.activeKeyPair()
)
logger.i { "observeUnsignedNostrEvents" }
scope.launch(Dispatchers.IO) {
logger.i { "observeUnsignedNostrEvents" }
nostrRepository.observeUnsignedNostrEvents(
publicKey = SeedManager.activePublicKey().toHexKey()
).distinctUntilChanged().collect { unsignedNostrEventOrNull ->
unsignedNostrEventOrNull?.let { unsignedNostrEvent ->
logger.d("Unsigned")
val event = tempSigner.signNormal<Event>(
createdAt = unsignedNostrEvent.createdAt.epochSeconds,
kind = unsignedNostrEvent.kind,
tags = unsignedNostrEvent.tags,
content = unsignedNostrEvent.privateTags?.let { PrivateTagsInContent.encryptNip44(it, tempSigner) } ?: unsignedNostrEvent.content
)
scope.launch(Dispatchers.IO) {
unsignedNostrEventOrNull?.let { unsignedNostrEvent ->
logger.d("Unsigned: ${unsignedNostrEvent.kind}")
val event = tempSigner.signNormal<Event>(
createdAt = unsignedNostrEvent.createdAt.epochSeconds,
kind = unsignedNostrEvent.kind,
tags = unsignedNostrEvent.tags,
content = unsignedNostrEvent.privateTags?.let { PrivateTagsInContent.encryptNip44(it, tempSigner) } ?: unsignedNostrEvent.content
)
logger.d("Signed: ${event.toJson()}")
nostrRepository.publishNostrEvent(
unsignedNostrEvent,
NostrEvent(
id = event.id,
pubKey = event.pubKey,
kind = event.kind,
tags = event.tags,
content = event.content,
createdAt = Instant.fromEpochSeconds(event.createdAt),
sig = event.sig,
unsignedNostrEventId = unsignedNostrEvent.id
),
relayURLs = Relays.eventPublishRelaySet.map { normalizedRelayUrl -> normalizedRelayUrl.url }
)
nostrRepository.publishNostrEvent(
unsignedNostrEvent,
NostrEvent(
id = event.id,
pubKey = event.pubKey,
kind = event.kind,
tags = event.tags,
content = event.content,
createdAt = Instant.fromEpochSeconds(event.createdAt),
sig = event.sig,
unsignedNostrEventId = unsignedNostrEvent.id
),
relayURLs = Relays.eventPublishRelaySet.map { normalizedRelayUrl -> normalizedRelayUrl.url }
)
}
}
}
}
}

View File

@@ -1,6 +1,5 @@
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.ui.view.state.SignInToProfileUIState
import ac.cord.auxiliary.compose.ui.view.state.form.SignInToProfileFormState
@@ -72,23 +71,13 @@ class SignInToProfileViewModel(
viewModelScope.launch(Dispatchers.IO) {
when (credentialEntity) {
is NPub -> {
nostrRepository.createNewProfile(
publicKey = credentialEntity.hex,
name = null,
biography = null,
onError = onError,
onProfileReady = {},
synchronizationRelayURLs = Relays.eventPublishRelaySet.map { normalizedRelayUrl -> normalizedRelayUrl.url }
nostrRepository.signInToProfile(
publicKey = credentialEntity.hex
)
}
is NSec -> {
nostrRepository.createNewProfile(
publicKey = credentialEntity.toPubKeyHex(),
name = null,
biography = null,
onError = onError,
onProfileReady = {},
synchronizationRelayURLs = Relays.eventPublishRelaySet.map { normalizedRelayUrl -> normalizedRelayUrl.url }
nostrRepository.signInToProfile(
publicKey = credentialEntity.toPubKeyHex()
)
}
else -> {