Improve create profile flow
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
@@ -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?>
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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() {
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 -> {
|
||||
|
||||
Reference in New Issue
Block a user