chore: improve performance #41

Merged
reya merged 9 commits from fix-perf into master 2026-07-11 12:45:54 +00:00
4 changed files with 24 additions and 19 deletions
Showing only changes of commit 467bc20013 - Show all commits

View File

@@ -1,6 +1,8 @@
package su.reya.coop.screens package su.reya.coop.screens
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import su.reya.coop.LocalAccountViewModel import su.reya.coop.LocalAccountViewModel
import su.reya.coop.LocalNavigator import su.reya.coop.LocalNavigator
import su.reya.coop.Screen import su.reya.coop.Screen
@@ -10,10 +12,12 @@ import su.reya.coop.shared.ProfileEditor
fun NewIdentityScreen() { fun NewIdentityScreen() {
val accountViewModel = LocalAccountViewModel.current val accountViewModel = LocalAccountViewModel.current
val navigator = LocalNavigator.current val navigator = LocalNavigator.current
val accountState by accountViewModel.state.collectAsStateWithLifecycle()
ProfileEditor( ProfileEditor(
title = "Create a new identity", title = "Create a new identity",
buttonLabel = "Continue", buttonLabel = "Continue",
isBusy = accountState.isImporting,
onBack = { navigator.goBack() }, onBack = { navigator.goBack() },
onConfirm = { name, bio, bytes, type -> onConfirm = { name, bio, bytes, type ->
accountViewModel.createIdentity(name, bio, bytes, type) accountViewModel.createIdentity(name, bio, bytes, type)

View File

@@ -14,6 +14,7 @@ fun UpdateProfileScreen() {
val currentUser by accountViewModel.currentUserProfile.collectAsStateWithLifecycle() val currentUser by accountViewModel.currentUserProfile.collectAsStateWithLifecycle()
val profile = currentUser?.metadata?.asRecord() val profile = currentUser?.metadata?.asRecord()
val isUpdatingProfile by accountViewModel.isUpdatingProfile.collectAsStateWithLifecycle()
ProfileEditor( ProfileEditor(
title = "Update profile", title = "Update profile",
@@ -21,6 +22,7 @@ fun UpdateProfileScreen() {
initialName = profile?.displayName ?: profile?.name ?: "", initialName = profile?.displayName ?: profile?.name ?: "",
initialBio = profile?.about ?: "", initialBio = profile?.about ?: "",
initialPicture = profile?.picture, initialPicture = profile?.picture,
isBusy = isUpdatingProfile,
onBack = { navigator.goBack() }, onBack = { navigator.goBack() },
onConfirm = { name, bio, bytes, type -> onConfirm = { name, bio, bytes, type ->
accountViewModel.updateProfile(name, bio, bytes, type) accountViewModel.updateProfile(name, bio, bytes, type)

View File

@@ -69,6 +69,7 @@ fun ProfileEditor(
initialName: String = "", initialName: String = "",
initialBio: String = "", initialBio: String = "",
initialPicture: Any? = null, // Accepts Uri (picked) or String (current URL) initialPicture: Any? = null, // Accepts Uri (picked) or String (current URL)
isBusy: Boolean = false,
onBack: () -> Unit, onBack: () -> Unit,
onConfirm: (name: String, bio: String, pictureBytes: ByteArray?, contentType: String?) -> Unit, onConfirm: (name: String, bio: String, pictureBytes: ByteArray?, contentType: String?) -> Unit,
ioDispatcher: CoroutineDispatcher = Dispatchers.IO, ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
@@ -81,7 +82,6 @@ fun ProfileEditor(
var name by remember(initialName) { mutableStateOf(initialName) } var name by remember(initialName) { mutableStateOf(initialName) }
var bio by remember(initialBio) { mutableStateOf(initialBio) } var bio by remember(initialBio) { mutableStateOf(initialBio) }
var picture by remember(initialPicture) { mutableStateOf(initialPicture) } var picture by remember(initialPicture) { mutableStateOf(initialPicture) }
var isBusy by remember { mutableStateOf(false) }
val hasPicture = remember(picture) { val hasPicture = remember(picture) {
when (picture) { when (picture) {
@@ -269,7 +269,6 @@ fun ProfileEditor(
.size(ButtonDefaults.MediumContainerHeight), .size(ButtonDefaults.MediumContainerHeight),
onClick = { onClick = {
scope.launch { scope.launch {
isBusy = true
try { try {
val bytes = withContext(ioDispatcher) { val bytes = withContext(ioDispatcher) {
(picture as? Uri)?.let { (picture as? Uri)?.let {
@@ -282,7 +281,6 @@ fun ProfileEditor(
} catch (e: Exception) { } catch (e: Exception) {
snackbarHostState.showSnackbar(e.message ?: "Error") snackbarHostState.showSnackbar(e.message ?: "Error")
} }
isBusy = false
} }
}, },
enabled = name.isNotBlank() && !isBusy enabled = name.isNotBlank() && !isBusy

View File

@@ -145,9 +145,7 @@ class AccountAuthDelegate(
} }
secret.startsWith("nip55://") -> { secret.startsWith("nip55://") -> {
val handler = externalSignerHandler val handler = externalSignerHandler ?: throw IllegalStateException("Not available")
?: throw IllegalStateException("External signer not available on this platform")
val parts = secret.removePrefix("nip55://").split("/", limit = 2) val parts = secret.removePrefix("nip55://").split("/", limit = 2)
val packageName = parts[0] val packageName = parts[0]
val pubkey = PublicKey.parse(parts[1]) 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) { fun importIdentity(secret: String, password: String? = null) {
scope.launch { scope.launch {
_state.update { it.copy(isImporting = true, importError = null) } _state.update { it.copy(isImporting = true, importError = null) }
try { try {
val (signer, decryptedSecret) = createSigner(secret, password) val (signer, decryptedSecret) = createSigner(secret, password)
nostr.setSigner(signer) nostr.setSigner(signer)
onSignerReady()
storage.setSecret(KEY_USER_SIGNER, decryptedSecret ?: secret) storage.setSecret(KEY_USER_SIGNER, decryptedSecret ?: secret)
_state.update { it.copy(signerRequired = false, isImporting = false) } _state.update { it.copy(signerRequired = false, isImporting = false) }
onSignerReady()
} catch (e: Exception) { } catch (e: Exception) {
onError("Import failed: ${e.message}") onError("Import failed: ${e.message}")
_state.update { it.copy(isImporting = false, importError = e.message) } _state.update { it.copy(isImporting = false, importError = e.message) }
@@ -180,8 +184,8 @@ class AccountAuthDelegate(
scope.launch { scope.launch {
_state.update { it.copy(isImporting = true, importError = null) } _state.update { it.copy(isImporting = true, importError = null) }
try { try {
val handler = externalSignerHandler val handler =
?: throw IllegalStateException("Signer not available") externalSignerHandler ?: throw IllegalStateException("Signer not available")
val permissions = SignerPermissions.toJson( val permissions = SignerPermissions.toJson(
listOf( listOf(
@@ -200,14 +204,13 @@ class AccountAuthDelegate(
val result = handler.getPublicKey(permissions) ?: throw Exception("Rejected") val result = handler.getPublicKey(permissions) ?: throw Exception("Rejected")
val signer = ExternalSignerProxy(handler, result.pubkey) val signer = ExternalSignerProxy(handler, result.pubkey)
val uri = "nip55://${result.packageName}/${result.pubkey.toHex()}"
nostr.setSigner(signer) nostr.setSigner(signer)
storage.setSecret(
KEY_USER_SIGNER,
"nip55://${result.packageName}/${result.pubkey.toHex()}"
)
_state.update { it.copy(signerRequired = false, isImporting = false) }
onSignerReady() onSignerReady()
storage.setSecret(KEY_USER_SIGNER, uri)
_state.update { it.copy(signerRequired = false, isImporting = false) }
} catch (e: Exception) { } catch (e: Exception) {
onError("External signer connection failed: ${e.message}") onError("External signer connection failed: ${e.message}")
_state.update { it.copy(isImporting = false, importError = 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( fun createIdentity(
name: String, name: String,
bio: String?, bio: String?,
@@ -233,15 +232,17 @@ class AccountAuthDelegate(
val avatarUrl = picture?.let { val avatarUrl = picture?.let {
mediaRepository.blossomUpload(keys, it, contentType ?: "image/jpeg") mediaRepository.blossomUpload(keys, it, contentType ?: "image/jpeg")
} }
nostr.profiles.createIdentity( nostr.profiles.createIdentity(
keys = keys, keys = keys,
name = name, name = name,
bio = bio, bio = bio,
picture = avatarUrl picture = avatarUrl
) )
onSignerReady()
storage.setSecret(KEY_USER_SIGNER, secret) storage.setSecret(KEY_USER_SIGNER, secret)
_state.update { it.copy(signerRequired = false, isImporting = false) } _state.update { it.copy(signerRequired = false, isImporting = false) }
onSignerReady()
} catch (e: Exception) { } catch (e: Exception) {
onError("Identity creation failed: ${e.message}") onError("Identity creation failed: ${e.message}")
_state.update { it.copy(isImporting = false, importError = e.message) } _state.update { it.copy(isImporting = false, importError = e.message) }