fix(multi-account): sequential account save in AddAccountDialog, debounced metadata
Root cause of accounts not persisting: ensureCurrentAccountInStorage() was fire-and-forget (scope.launch), so loginWithKey() ran before the old account was saved. The old account was lost. Now all steps run sequentially in one coroutine. UI hanging fix: metadataVersion LaunchedEffect fired on every metadata event, doing encrypted file I/O each time. Now debounced with 2s delay so it only writes once after a batch of metadata settles. Also: - loadInternalAccount falls back to read-only (test updated) - bunker/nostrconnect paths also await ensureCurrentAccountInStorage Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -673,14 +673,16 @@ fun App(
|
||||
}
|
||||
}
|
||||
|
||||
// Persist display name when metadata arrives from relays
|
||||
val metadataVersion by localCache.metadataVersion.collectAsState()
|
||||
LaunchedEffect(metadataVersion) {
|
||||
val current = accountManager.currentAccount() ?: return@LaunchedEffect
|
||||
val user = localCache.getUserIfExists(current.pubKeyHex) ?: return@LaunchedEffect
|
||||
val name = user.toBestDisplayName()
|
||||
if (name != user.pubkeyDisplayHex()) {
|
||||
accountManager.updateDisplayName(current.npub, name)
|
||||
// Persist display name when metadata arrives from relays (debounced)
|
||||
LaunchedEffect(Unit) {
|
||||
localCache.metadataVersion.collect {
|
||||
kotlinx.coroutines.delay(2000) // debounce — wait for batch of metadata to settle
|
||||
val current = accountManager.currentAccount() ?: return@collect
|
||||
val user = localCache.getUserIfExists(current.pubKeyHex) ?: return@collect
|
||||
val name = user.toBestDisplayName()
|
||||
if (name != user.pubkeyDisplayHex()) {
|
||||
accountManager.updateDisplayName(current.npub, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+20
-18
@@ -88,18 +88,21 @@ fun AddAccountDialog(
|
||||
) {
|
||||
LoginCard(
|
||||
onLogin = { keyInput ->
|
||||
// Save current account before switching
|
||||
// All steps must be sequential — no fire-and-forget
|
||||
scope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
accountManager.ensureCurrentAccountInStorage()
|
||||
}
|
||||
}
|
||||
accountManager.loginWithKey(keyInput).map {
|
||||
scope.launch {
|
||||
withContext(Dispatchers.IO) { accountManager.saveCurrentAccount() }
|
||||
val result = accountManager.loginWithKey(keyInput)
|
||||
if (result.isSuccess) {
|
||||
withContext(Dispatchers.IO) {
|
||||
accountManager.saveCurrentAccount()
|
||||
}
|
||||
onAccountAdded()
|
||||
}
|
||||
}
|
||||
// Return success to dismiss any error in LoginCard
|
||||
Result.success(Unit)
|
||||
},
|
||||
onGenerateNew = {
|
||||
scope.launch {
|
||||
@@ -107,29 +110,28 @@ fun AddAccountDialog(
|
||||
accountManager.ensureCurrentAccountInStorage()
|
||||
}
|
||||
accountManager.generateNewAccount()
|
||||
withContext(Dispatchers.IO) { accountManager.saveCurrentAccount() }
|
||||
withContext(Dispatchers.IO) {
|
||||
accountManager.saveCurrentAccount()
|
||||
}
|
||||
onAccountAdded()
|
||||
}
|
||||
},
|
||||
onLoginBunker = { bunkerUri ->
|
||||
scope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
accountManager.ensureCurrentAccountInStorage()
|
||||
}
|
||||
}
|
||||
accountManager.loginWithBunker(bunkerUri).map {
|
||||
// ensureCurrentAccountInStorage first, then bunker login
|
||||
accountManager.ensureCurrentAccountInStorage()
|
||||
val result = accountManager.loginWithBunker(bunkerUri)
|
||||
if (result.isSuccess) {
|
||||
onAccountAdded()
|
||||
}
|
||||
result.map { }
|
||||
},
|
||||
onLoginNostrConnect = { onUriGenerated ->
|
||||
scope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
accountManager.ensureCurrentAccountInStorage()
|
||||
}
|
||||
}
|
||||
accountManager.loginWithNostrConnect(onUriGenerated).map {
|
||||
accountManager.ensureCurrentAccountInStorage()
|
||||
val result = accountManager.loginWithNostrConnect(onUriGenerated)
|
||||
if (result.isSuccess) {
|
||||
onAccountAdded()
|
||||
}
|
||||
result.map { }
|
||||
},
|
||||
loginProgress = loginProgress,
|
||||
cardWidth = 420.dp,
|
||||
|
||||
+4
-2
@@ -85,7 +85,7 @@ class AccountManagerLoadAccountTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun loadSavedAccountInternalNoPrivkeyReturnsFailure() =
|
||||
fun loadSavedAccountInternalNoPrivkeyFallsBackToReadOnly() =
|
||||
runTest {
|
||||
val keyPair = KeyPair()
|
||||
val npub = keyPair.pubKey.toNpub()
|
||||
@@ -94,7 +94,9 @@ class AccountManagerLoadAccountTest {
|
||||
coEvery { storage.getPrivateKey(npub) } returns null
|
||||
|
||||
val result = manager.loadSavedAccount()
|
||||
assertTrue(result.isFailure)
|
||||
assertTrue(result.isSuccess)
|
||||
val state = result.getOrThrow()
|
||||
assertTrue(state.isReadOnly)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user