feat(multi-account): display names, middle-truncated npub, npub-only account fix
Account switcher dropdown improvements: - Two-row display: Display Name on top, npub (middle-truncated) below e.g. 'Alice' / 'npub1abc...wxyz · Bunker' - Middle-truncation for npub: shows first 10 + last 6 chars - Resolves display names from DesktopLocalCache user metadata - Confirmation dialog also shows display name - npub-only (view-only) accounts now persist to encrypted storage (ensureCurrentAccountInStorage called in onLoginSuccess) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -729,8 +729,9 @@ fun App(
|
||||
if (current?.signerType is com.vitorpamplona.amethyst.commons.model.account.SignerType.Remote) {
|
||||
accountManager.startHeartbeat(scope)
|
||||
}
|
||||
// Refresh account list for switcher
|
||||
// Ensure account is in multi-account storage + refresh list
|
||||
scope.launch(Dispatchers.IO) {
|
||||
accountManager.ensureCurrentAccountInStorage()
|
||||
accountManager.refreshAccountList()
|
||||
}
|
||||
},
|
||||
@@ -1050,6 +1051,7 @@ fun MainContent(
|
||||
DeckSidebar(
|
||||
activeNpub = accountManager.currentAccount()?.npub,
|
||||
allAccounts = allAccountsState,
|
||||
localCache = localCache,
|
||||
onSwitchAccount = { npub ->
|
||||
scope.launch(Dispatchers.IO) {
|
||||
accountManager.switchAccount(npub)
|
||||
|
||||
+53
-13
@@ -55,12 +55,23 @@ import androidx.compose.ui.unit.DpOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.commons.model.account.AccountInfo
|
||||
import com.vitorpamplona.amethyst.commons.model.account.SignerType
|
||||
import com.vitorpamplona.amethyst.desktop.cache.DesktopLocalCache
|
||||
import com.vitorpamplona.quartz.nip19Bech32.decodePublicKeyAsHexOrNull
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
/**
|
||||
* Middle-truncates an npub: "npub1abcdef...wxyz"
|
||||
*/
|
||||
private fun npubShortMiddle(npub: String): String {
|
||||
if (npub.length <= 20) return npub
|
||||
return "${npub.take(10)}...${npub.takeLast(6)}"
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AccountSwitcherDropdown(
|
||||
activeNpub: String?,
|
||||
allAccounts: ImmutableList<AccountInfo>,
|
||||
localCache: DesktopLocalCache?,
|
||||
onSwitchAccount: (String) -> Unit,
|
||||
onAddAccount: () -> Unit,
|
||||
onRemoveAccount: (String) -> Unit,
|
||||
@@ -90,29 +101,45 @@ fun AccountSwitcherDropdown(
|
||||
) {
|
||||
allAccounts.forEach { account ->
|
||||
val isActive = account.npub == activeNpub
|
||||
|
||||
// Resolve display name from cache
|
||||
val displayName =
|
||||
remember(account.npub, localCache) {
|
||||
val pubkeyHex = decodePublicKeyAsHexOrNull(account.npub)
|
||||
if (pubkeyHex != null && localCache != null) {
|
||||
val user = localCache.getUserIfExists(pubkeyHex)
|
||||
user?.toBestDisplayName()?.takeIf { it != user.pubkeyDisplayHex() }
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
DropdownMenuItem(
|
||||
text = {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
// Row 1: Display name (or npub if no name)
|
||||
Text(
|
||||
account.npub.take(16) + "...",
|
||||
displayName ?: npubShortMiddle(account.npub),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
fontWeight = if (isActive) FontWeight.SemiBold else FontWeight.Normal,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
val label =
|
||||
// Row 2: npub (middle-truncated) + signer type badge
|
||||
val signerLabel =
|
||||
when (account.signerType) {
|
||||
is SignerType.Remote -> "Bunker"
|
||||
is SignerType.ViewOnly -> "View only"
|
||||
is SignerType.Internal -> null
|
||||
is SignerType.Remote -> " · Bunker"
|
||||
is SignerType.ViewOnly -> " · View only"
|
||||
is SignerType.Internal -> ""
|
||||
}
|
||||
if (label != null) {
|
||||
Text(
|
||||
label,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
}
|
||||
Text(
|
||||
npubShortMiddle(account.npub) + signerLabel,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
if (isActive) {
|
||||
Icon(
|
||||
@@ -160,11 +187,24 @@ fun AccountSwitcherDropdown(
|
||||
// Logout confirmation dialog
|
||||
val logoutNpub = confirmLogoutNpub
|
||||
if (logoutNpub != null) {
|
||||
val logoutDisplayName =
|
||||
remember(logoutNpub, localCache) {
|
||||
val hex = decodePublicKeyAsHexOrNull(logoutNpub)
|
||||
if (hex != null && localCache != null) {
|
||||
localCache.getUserIfExists(hex)?.toBestDisplayName()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = { confirmLogoutNpub = null },
|
||||
title = { Text("Remove Account") },
|
||||
text = {
|
||||
Text("Remove ${logoutNpub.take(16)}...? This will delete the account from this device.")
|
||||
Text(
|
||||
"Remove ${logoutDisplayName ?: npubShortMiddle(logoutNpub)}? " +
|
||||
"This will delete the account from this device.",
|
||||
)
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
|
||||
+3
@@ -42,6 +42,7 @@ import com.vitorpamplona.amethyst.commons.domain.nip46.SignerConnectionState
|
||||
import com.vitorpamplona.amethyst.commons.model.account.AccountInfo
|
||||
import com.vitorpamplona.amethyst.commons.tor.TorServiceStatus
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.BunkerHeartbeatIndicator
|
||||
import com.vitorpamplona.amethyst.desktop.cache.DesktopLocalCache
|
||||
import com.vitorpamplona.amethyst.desktop.ui.account.AccountSwitcherDropdown
|
||||
import com.vitorpamplona.amethyst.desktop.ui.tor.TorStatusIndicator
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
@@ -50,6 +51,7 @@ import kotlinx.collections.immutable.ImmutableList
|
||||
fun DeckSidebar(
|
||||
activeNpub: String?,
|
||||
allAccounts: ImmutableList<AccountInfo>,
|
||||
localCache: DesktopLocalCache?,
|
||||
onSwitchAccount: (String) -> Unit,
|
||||
onAddAccount: () -> Unit,
|
||||
onRemoveAccount: (String) -> Unit,
|
||||
@@ -73,6 +75,7 @@ fun DeckSidebar(
|
||||
AccountSwitcherDropdown(
|
||||
activeNpub = activeNpub,
|
||||
allAccounts = allAccounts,
|
||||
localCache = localCache,
|
||||
onSwitchAccount = onSwitchAccount,
|
||||
onAddAccount = onAddAccount,
|
||||
onRemoveAccount = onRemoveAccount,
|
||||
|
||||
+1
@@ -183,6 +183,7 @@ fun SinglePaneLayout(
|
||||
AccountSwitcherDropdown(
|
||||
activeNpub = accountManager.currentAccount()?.npub,
|
||||
allAccounts = allAccountsState,
|
||||
localCache = localCache,
|
||||
onSwitchAccount = { npub ->
|
||||
singlePaneScope.launch(Dispatchers.IO) {
|
||||
accountManager.switchAccount(npub)
|
||||
|
||||
Reference in New Issue
Block a user