From 467bc20013c092a885b0b48bb885029e924d111b Mon Sep 17 00:00:00 2001 From: Ren Amamiya Date: Sat, 11 Jul 2026 17:44:18 +0700 Subject: [PATCH] update --- .../su/reya/coop/screens/NewIdentityScreen.kt | 4 +++ .../reya/coop/screens/UpdateProfileScreen.kt | 2 ++ .../su/reya/coop/shared/ProfileEditor.kt | 4 +-- .../viewmodel/account/AccountAuthDelegate.kt | 33 ++++++++++--------- 4 files changed, 24 insertions(+), 19 deletions(-) diff --git a/composeApp/src/androidMain/kotlin/su/reya/coop/screens/NewIdentityScreen.kt b/composeApp/src/androidMain/kotlin/su/reya/coop/screens/NewIdentityScreen.kt index 276f9f9..54d7d36 100644 --- a/composeApp/src/androidMain/kotlin/su/reya/coop/screens/NewIdentityScreen.kt +++ b/composeApp/src/androidMain/kotlin/su/reya/coop/screens/NewIdentityScreen.kt @@ -1,6 +1,8 @@ package su.reya.coop.screens import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.lifecycle.compose.collectAsStateWithLifecycle import su.reya.coop.LocalAccountViewModel import su.reya.coop.LocalNavigator import su.reya.coop.Screen @@ -10,10 +12,12 @@ import su.reya.coop.shared.ProfileEditor fun NewIdentityScreen() { val accountViewModel = LocalAccountViewModel.current val navigator = LocalNavigator.current + val accountState by accountViewModel.state.collectAsStateWithLifecycle() ProfileEditor( title = "Create a new identity", buttonLabel = "Continue", + isBusy = accountState.isImporting, onBack = { navigator.goBack() }, onConfirm = { name, bio, bytes, type -> accountViewModel.createIdentity(name, bio, bytes, type) diff --git a/composeApp/src/androidMain/kotlin/su/reya/coop/screens/UpdateProfileScreen.kt b/composeApp/src/androidMain/kotlin/su/reya/coop/screens/UpdateProfileScreen.kt index 1599437..acdd26a 100644 --- a/composeApp/src/androidMain/kotlin/su/reya/coop/screens/UpdateProfileScreen.kt +++ b/composeApp/src/androidMain/kotlin/su/reya/coop/screens/UpdateProfileScreen.kt @@ -14,6 +14,7 @@ fun UpdateProfileScreen() { val currentUser by accountViewModel.currentUserProfile.collectAsStateWithLifecycle() val profile = currentUser?.metadata?.asRecord() + val isUpdatingProfile by accountViewModel.isUpdatingProfile.collectAsStateWithLifecycle() ProfileEditor( title = "Update profile", @@ -21,6 +22,7 @@ fun UpdateProfileScreen() { initialName = profile?.displayName ?: profile?.name ?: "", initialBio = profile?.about ?: "", initialPicture = profile?.picture, + isBusy = isUpdatingProfile, onBack = { navigator.goBack() }, onConfirm = { name, bio, bytes, type -> accountViewModel.updateProfile(name, bio, bytes, type) diff --git a/composeApp/src/androidMain/kotlin/su/reya/coop/shared/ProfileEditor.kt b/composeApp/src/androidMain/kotlin/su/reya/coop/shared/ProfileEditor.kt index f40e07a..92c8644 100644 --- a/composeApp/src/androidMain/kotlin/su/reya/coop/shared/ProfileEditor.kt +++ b/composeApp/src/androidMain/kotlin/su/reya/coop/shared/ProfileEditor.kt @@ -69,6 +69,7 @@ fun ProfileEditor( initialName: String = "", initialBio: String = "", initialPicture: Any? = null, // Accepts Uri (picked) or String (current URL) + isBusy: Boolean = false, onBack: () -> Unit, onConfirm: (name: String, bio: String, pictureBytes: ByteArray?, contentType: String?) -> Unit, ioDispatcher: CoroutineDispatcher = Dispatchers.IO, @@ -81,7 +82,6 @@ fun ProfileEditor( var name by remember(initialName) { mutableStateOf(initialName) } var bio by remember(initialBio) { mutableStateOf(initialBio) } var picture by remember(initialPicture) { mutableStateOf(initialPicture) } - var isBusy by remember { mutableStateOf(false) } val hasPicture = remember(picture) { when (picture) { @@ -269,7 +269,6 @@ fun ProfileEditor( .size(ButtonDefaults.MediumContainerHeight), onClick = { scope.launch { - isBusy = true try { val bytes = withContext(ioDispatcher) { (picture as? Uri)?.let { @@ -282,7 +281,6 @@ fun ProfileEditor( } catch (e: Exception) { snackbarHostState.showSnackbar(e.message ?: "Error") } - isBusy = false } }, enabled = name.isNotBlank() && !isBusy diff --git a/shared/src/commonMain/kotlin/su/reya/coop/viewmodel/account/AccountAuthDelegate.kt b/shared/src/commonMain/kotlin/su/reya/coop/viewmodel/account/AccountAuthDelegate.kt index a339fa1..f6dc1d5 100644 --- a/shared/src/commonMain/kotlin/su/reya/coop/viewmodel/account/AccountAuthDelegate.kt +++ b/shared/src/commonMain/kotlin/su/reya/coop/viewmodel/account/AccountAuthDelegate.kt @@ -145,9 +145,7 @@ class AccountAuthDelegate( } secret.startsWith("nip55://") -> { - val handler = externalSignerHandler - ?: throw IllegalStateException("External signer not available on this platform") - + val handler = externalSignerHandler ?: throw IllegalStateException("Not available") val parts = secret.removePrefix("nip55://").split("/", limit = 2) val packageName = parts[0] val pubkey = PublicKey.parse(parts[1]) @@ -160,15 +158,21 @@ class AccountAuthDelegate( } } + fun isExternalSignerAvailable(): Boolean { + return externalSignerHandler?.isAvailable() == true + } + fun importIdentity(secret: String, password: String? = null) { scope.launch { _state.update { it.copy(isImporting = true, importError = null) } try { val (signer, decryptedSecret) = createSigner(secret, password) + nostr.setSigner(signer) + onSignerReady() + storage.setSecret(KEY_USER_SIGNER, decryptedSecret ?: secret) _state.update { it.copy(signerRequired = false, isImporting = false) } - onSignerReady() } catch (e: Exception) { onError("Import failed: ${e.message}") _state.update { it.copy(isImporting = false, importError = e.message) } @@ -180,8 +184,8 @@ class AccountAuthDelegate( scope.launch { _state.update { it.copy(isImporting = true, importError = null) } try { - val handler = externalSignerHandler - ?: throw IllegalStateException("Signer not available") + val handler = + externalSignerHandler ?: throw IllegalStateException("Signer not available") val permissions = SignerPermissions.toJson( listOf( @@ -200,14 +204,13 @@ class AccountAuthDelegate( val result = handler.getPublicKey(permissions) ?: throw Exception("Rejected") val signer = ExternalSignerProxy(handler, result.pubkey) + val uri = "nip55://${result.packageName}/${result.pubkey.toHex()}" nostr.setSigner(signer) - storage.setSecret( - KEY_USER_SIGNER, - "nip55://${result.packageName}/${result.pubkey.toHex()}" - ) - _state.update { it.copy(signerRequired = false, isImporting = false) } onSignerReady() + + storage.setSecret(KEY_USER_SIGNER, uri) + _state.update { it.copy(signerRequired = false, isImporting = false) } } catch (e: Exception) { onError("External signer connection failed: ${e.message}") _state.update { it.copy(isImporting = false, importError = e.message) } @@ -215,10 +218,6 @@ class AccountAuthDelegate( } } - fun isExternalSignerAvailable(): Boolean { - return externalSignerHandler?.isAvailable() == true - } - fun createIdentity( name: String, bio: String?, @@ -233,15 +232,17 @@ class AccountAuthDelegate( val avatarUrl = picture?.let { mediaRepository.blossomUpload(keys, it, contentType ?: "image/jpeg") } + nostr.profiles.createIdentity( keys = keys, name = name, bio = bio, picture = avatarUrl ) + onSignerReady() + storage.setSecret(KEY_USER_SIGNER, secret) _state.update { it.copy(signerRequired = false, isImporting = false) } - onSignerReady() } catch (e: Exception) { onError("Identity creation failed: ${e.message}") _state.update { it.copy(isImporting = false, importError = e.message) }