feat(multi-account): add account switcher to single-pane layout, scrollable dropdown

- Account switcher now appears at bottom of NavigationRail in single-pane mode
  (after tor indicator), matching placement in deck sidebar
- AddAccountDialog wired in single-pane mode too
- Dropdown scrollable with 400dp max height for many accounts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
nrobi144
2026-04-28 13:12:55 +03:00
parent 82ec892296
commit b1098368a1
2 changed files with 48 additions and 2 deletions
@@ -24,8 +24,10 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Check
@@ -83,6 +85,8 @@ fun AccountSwitcherDropdown(
expanded = expanded,
onDismissRequest = { expanded = false },
offset = DpOffset(x = 48.dp, y = 0.dp),
modifier = Modifier.heightIn(max = 400.dp),
scrollState = rememberScrollState(),
) {
allAccounts.forEach { account ->
val isActive = account.npub == activeNpub
@@ -41,7 +41,10 @@ import androidx.compose.material3.VerticalDivider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
@@ -55,12 +58,16 @@ import com.vitorpamplona.amethyst.desktop.network.DesktopRelayConnectionManager
import com.vitorpamplona.amethyst.desktop.service.highlights.DesktopHighlightStore
import com.vitorpamplona.amethyst.desktop.subscriptions.DesktopRelaySubscriptionsCoordinator
import com.vitorpamplona.amethyst.desktop.ui.ZapFeedback
import com.vitorpamplona.amethyst.desktop.ui.account.AccountSwitcherDropdown
import com.vitorpamplona.amethyst.desktop.ui.account.AddAccountDialog
import com.vitorpamplona.amethyst.desktop.ui.components.RelayHealthIndicator
import com.vitorpamplona.amethyst.desktop.ui.media.LocalIsImmersiveFullscreen
import com.vitorpamplona.amethyst.desktop.ui.tor.LocalTorState
import com.vitorpamplona.amethyst.desktop.ui.tor.TorStatusIndicator
import com.vitorpamplona.quartz.nip47WalletConnect.Nip47WalletConnect.Nip47URINorm
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@Composable
fun SinglePaneLayout(
@@ -157,7 +164,7 @@ fun SinglePaneLayout(
modifier = Modifier.padding(bottom = 4.dp),
)
// Tor status — always last so it's never pushed off screen
// Tor status
val torState = LocalTorState.current
TorStatusIndicator(
status = torState.status,
@@ -165,8 +172,43 @@ fun SinglePaneLayout(
singlePaneState.navigate(DeckColumnType.Settings)
navState.clear()
},
modifier = Modifier.padding(bottom = 12.dp),
modifier = Modifier.padding(bottom = 4.dp),
)
// Account switcher — at very bottom of rail
val allAccountsState by accountManager.allAccounts.collectAsState()
val singlePaneScope = rememberCoroutineScope()
var showAddAccountDialog by remember { mutableStateOf(false) }
AccountSwitcherDropdown(
activeNpub = accountManager.currentAccount()?.npub,
allAccounts = allAccountsState,
onSwitchAccount = { npub ->
singlePaneScope.launch(Dispatchers.IO) {
accountManager.switchAccount(npub)
}
},
onAddAccount = { showAddAccountDialog = true },
onRemoveAccount = { npub ->
singlePaneScope.launch(Dispatchers.IO) {
accountManager.removeAccountFromStorage(npub)
}
},
modifier = Modifier.padding(bottom = 8.dp),
)
if (showAddAccountDialog) {
AddAccountDialog(
accountManager = accountManager,
onDismiss = { showAddAccountDialog = false },
onAccountAdded = {
showAddAccountDialog = false
singlePaneScope.launch(Dispatchers.IO) {
accountManager.refreshAccountList()
}
},
)
}
}
}