mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-09 08:04:45 +00:00
refactor(buzz): nest DMs + Agent Console per-community, drop drawer entries
Buzz DMs and the Agent Console are community-scoped (a DM is a t=dm relay group on one community's relay; agents/telemetry live on community relays), so they don't belong as global drawer entries. Remove the AGENT_CONSOLE + BUZZ_DMS nav items and reach both from within a community instead: the Buzz relay's group screen now shows "Direct Messages" and "Agent Console" cards scoped to that one relay. - Route.BuzzDmList / BuzzNewDm / AgentConsole now carry a relayUrl. - BuzzDmListViewModel / BuzzNewDmViewModel / AgentConsoleViewModel take a single-relay scope (and mark it joined + pre-approve NIP-42 so the #p=me discovery authenticates). - Removed the settings-catalog AgentConsole search entry (no longer global). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J8KBSw6smQRyXLiWHeDsZ8
This commit is contained in:
@@ -461,12 +461,12 @@ fun BuildNavigation(
|
||||
composableFromEnd<Route.Shorts> { ShortsScreen(accountViewModel, nav) }
|
||||
composableFromEnd<Route.PublicChats> { PublicChatsScreen(accountViewModel, nav) }
|
||||
composableFromEnd<Route.RelayGroups> { RelayGroupDiscoveryScreen(accountViewModel, nav) }
|
||||
composableFromEnd<Route.BuzzDmList> { BuzzDmListScreen(accountViewModel, nav) }
|
||||
composableFromEnd<Route.BuzzNewDm> { BuzzNewDmScreen(accountViewModel, nav) }
|
||||
composableFromEndArgs<Route.BuzzDmList> { BuzzDmListScreen(it.relayUrl, accountViewModel, nav) }
|
||||
composableFromEndArgs<Route.BuzzNewDm> { BuzzNewDmScreen(it.relayUrl, accountViewModel, nav) }
|
||||
composableFromEnd<Route.FollowPacks> { FollowPacksScreen(accountViewModel, nav) }
|
||||
composableFromEnd<Route.LiveStreams> { LiveStreamsScreen(accountViewModel, nav) }
|
||||
composableFromEnd<Route.Nests> { NestsScreen(accountViewModel, nav) }
|
||||
composableFromEnd<Route.AgentConsole> { AgentConsoleScreen(accountViewModel, nav) }
|
||||
composableFromEndArgs<Route.AgentConsole> { AgentConsoleScreen(it.relayUrl, accountViewModel, nav) }
|
||||
composableFromEnd<Route.AgentAttestation> { AgentAttestationScreen(accountViewModel, nav) }
|
||||
composableFromEndArgs<Route.AgentPersonaEdit> { AgentPersonaEditScreen(it.slug, accountViewModel, nav) }
|
||||
composableFromEndArgs<Route.NestLobby> { NestLobbyScreen(it.addressValue, accountViewModel, nav) }
|
||||
|
||||
-20
@@ -69,8 +69,6 @@ enum class NavBarItem {
|
||||
PODCASTS,
|
||||
PUBLIC_CHATS,
|
||||
RELAY_GROUPS,
|
||||
AGENT_CONSOLE,
|
||||
BUZZ_DMS,
|
||||
CONCORD,
|
||||
GEOHASH_CHATS,
|
||||
FOLLOW_PACKS,
|
||||
@@ -346,20 +344,6 @@ val NavBarCatalog: Map<NavBarItem, NavBarItemDef> =
|
||||
icon = MaterialSymbols.Forum,
|
||||
resolveRoute = { Route.RelayGroups },
|
||||
),
|
||||
NavBarItem.AGENT_CONSOLE to
|
||||
NavBarItemDef(
|
||||
id = NavBarItem.AGENT_CONSOLE,
|
||||
labelRes = R.string.buzz_console_card_title,
|
||||
icon = MaterialSymbols.AutoAwesome,
|
||||
resolveRoute = { Route.AgentConsole },
|
||||
),
|
||||
NavBarItem.BUZZ_DMS to
|
||||
NavBarItemDef(
|
||||
id = NavBarItem.BUZZ_DMS,
|
||||
labelRes = R.string.buzz_dm_nav_label,
|
||||
icon = MaterialSymbols.AutoMirrored.Send,
|
||||
resolveRoute = { Route.BuzzDmList },
|
||||
),
|
||||
NavBarItem.CONCORD to
|
||||
NavBarItemDef(
|
||||
id = NavBarItem.CONCORD,
|
||||
@@ -512,8 +496,6 @@ val BottomBarCategories: List<NavBarCategory> =
|
||||
listOf(
|
||||
NavBarItem.PUBLIC_CHATS,
|
||||
NavBarItem.RELAY_GROUPS,
|
||||
NavBarItem.AGENT_CONSOLE,
|
||||
NavBarItem.BUZZ_DMS,
|
||||
NavBarItem.CONCORD,
|
||||
NavBarItem.GEOHASH_CHATS,
|
||||
),
|
||||
@@ -597,8 +579,6 @@ val DrawerFeedsItems: List<NavBarItem> =
|
||||
NavBarItem.COMMUNITIES,
|
||||
NavBarItem.PUBLIC_CHATS,
|
||||
NavBarItem.RELAY_GROUPS,
|
||||
NavBarItem.AGENT_CONSOLE,
|
||||
NavBarItem.BUZZ_DMS,
|
||||
NavBarItem.CONCORD,
|
||||
NavBarItem.GEOHASH_CHATS,
|
||||
NavBarItem.CALENDARS,
|
||||
|
||||
@@ -469,7 +469,10 @@ sealed class Route {
|
||||
|
||||
@Serializable object EditNestsServers : Route()
|
||||
|
||||
@Serializable object AgentConsole : Route()
|
||||
@Serializable
|
||||
data class AgentConsole(
|
||||
val relayUrl: String,
|
||||
) : Route()
|
||||
|
||||
@Serializable object AgentAttestation : Route()
|
||||
|
||||
@@ -719,9 +722,15 @@ sealed class Route {
|
||||
|
||||
@Serializable object RelayGroups : Route()
|
||||
|
||||
@Serializable object BuzzDmList : Route()
|
||||
@Serializable
|
||||
data class BuzzDmList(
|
||||
val relayUrl: String,
|
||||
) : Route()
|
||||
|
||||
@Serializable object BuzzNewDm : Route()
|
||||
@Serializable
|
||||
data class BuzzNewDm(
|
||||
val relayUrl: String,
|
||||
) : Route()
|
||||
|
||||
@Serializable object RelayGroupBrowse : Route()
|
||||
|
||||
|
||||
-4
@@ -137,10 +137,6 @@ private fun PreloadFor(
|
||||
// this tab needs no extra preload.
|
||||
NavBarItem.RELAY_GROUPS -> Unit
|
||||
|
||||
// The Agent Console + Buzz DM inbox fetch on-screen; nothing extra to preload here.
|
||||
NavBarItem.AGENT_CONSOLE -> Unit
|
||||
NavBarItem.BUZZ_DMS -> Unit
|
||||
|
||||
NavBarItem.CONCORD -> ConcordChannelSubscription(accountViewModel.dataSources().concordChannels, accountViewModel)
|
||||
|
||||
NavBarItem.FOLLOW_PACKS -> FollowPacksFilterAssemblerSubscription(accountViewModel)
|
||||
|
||||
+3
-2
@@ -81,13 +81,14 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
*/
|
||||
@Composable
|
||||
fun AgentConsoleScreen(
|
||||
relayUrl: String,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val pubkey = accountViewModel.account.userProfile().pubkeyHex
|
||||
val viewModel: AgentConsoleViewModel = viewModel(key = "AgentConsole-$pubkey")
|
||||
val viewModel: AgentConsoleViewModel = viewModel(key = "AgentConsole-$relayUrl-$pubkey")
|
||||
|
||||
viewModel.bindAccountIfMissing(accountViewModel.account)
|
||||
viewModel.bind(accountViewModel.account, relayUrl)
|
||||
|
||||
val metrics by viewModel.metrics.collectAsStateWithLifecycle()
|
||||
val personas by viewModel.personas.collectAsStateWithLifecycle()
|
||||
|
||||
+20
-3
@@ -26,6 +26,8 @@ import androidx.lifecycle.viewModelScope
|
||||
import com.vitorpamplona.amethyst.commons.model.buzz.AgentFleetAggregator
|
||||
import com.vitorpamplona.amethyst.commons.model.buzz.AgentFleetMetrics
|
||||
import com.vitorpamplona.amethyst.commons.model.buzz.BuzzRelayDialect
|
||||
import com.vitorpamplona.amethyst.commons.model.buzz.BuzzWorkspaces
|
||||
import com.vitorpamplona.amethyst.commons.relayauth.RelayAuthDecision
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.filter
|
||||
@@ -38,6 +40,8 @@ import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.accessories.fetchAllPagesFromPool
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.subscribeAsFlow
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
@@ -68,6 +72,9 @@ import java.util.Collections
|
||||
class AgentConsoleViewModel : ViewModel() {
|
||||
@Volatile private var account: Account? = null
|
||||
|
||||
/** The community (relay) this console is scoped to — agents/telemetry live on community relays. */
|
||||
private var scopeRelay: NormalizedRelayUrl? = null
|
||||
|
||||
/** event id -> decrypted payload (null = decryption failed; don't retry). */
|
||||
private val decryptCache = HashMap<HexKey, AgentTurnMetricPayload?>()
|
||||
private val refreshMutex = Mutex()
|
||||
@@ -89,9 +96,19 @@ class AgentConsoleViewModel : ViewModel() {
|
||||
/** Dedups ephemeral frames across relays and re-emissions (accessed off multiple readers). */
|
||||
private val observerSeen = Collections.synchronizedSet(HashSet<HexKey>())
|
||||
|
||||
fun bindAccountIfMissing(account: Account) {
|
||||
/** Binds to [account] scoped to the community [relayUrl] — the console shows that community's fleet. */
|
||||
fun bind(
|
||||
account: Account,
|
||||
relayUrl: String,
|
||||
) {
|
||||
if (this.account != null) return
|
||||
val relay = RelayUrlNormalizer.normalizeOrNull(relayUrl)
|
||||
this.scopeRelay = relay
|
||||
this.account = account
|
||||
relay?.let {
|
||||
BuzzWorkspaces.join(it)
|
||||
viewModelScope.launch { account.relayAuthLedger.setDecision(it.url, RelayAuthDecision.ALLOW) }
|
||||
}
|
||||
refresh()
|
||||
}
|
||||
|
||||
@@ -118,7 +135,7 @@ class AgentConsoleViewModel : ViewModel() {
|
||||
*/
|
||||
private suspend fun fetchFromRelays(account: Account) {
|
||||
val myPubkey = account.userProfile().pubkeyHex
|
||||
val relays = BuzzRelayDialect.flow.value + account.outboxRelays.flow.value
|
||||
val relays = scopeRelay?.let { setOf(it) } ?: (BuzzRelayDialect.flow.value + account.outboxRelays.flow.value)
|
||||
if (relays.isEmpty()) return
|
||||
|
||||
val filters =
|
||||
@@ -183,7 +200,7 @@ class AgentConsoleViewModel : ViewModel() {
|
||||
val account = account ?: return
|
||||
if (observerJob != null) return
|
||||
|
||||
val relays = BuzzRelayDialect.flow.value
|
||||
val relays = scopeRelay?.let { setOf(it) } ?: BuzzRelayDialect.flow.value
|
||||
if (relays.isEmpty()) return
|
||||
|
||||
val signer = account.signer
|
||||
|
||||
+4
-3
@@ -90,12 +90,13 @@ import kotlinx.coroutines.launch
|
||||
*/
|
||||
@Composable
|
||||
fun BuzzDmListScreen(
|
||||
relayUrl: String,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val pubkey = accountViewModel.account.userProfile().pubkeyHex
|
||||
val viewModel: BuzzDmListViewModel = viewModel(key = "BuzzDmList-$pubkey")
|
||||
viewModel.bindAccountIfMissing(accountViewModel.account)
|
||||
val viewModel: BuzzDmListViewModel = viewModel(key = "BuzzDmList-$relayUrl-$pubkey")
|
||||
viewModel.bind(accountViewModel.account, relayUrl)
|
||||
|
||||
val rows by viewModel.rows.collectAsStateWithLifecycle()
|
||||
|
||||
@@ -105,7 +106,7 @@ fun BuzzDmListScreen(
|
||||
ExtendedFloatingActionButton(
|
||||
text = { Text(stringRes(R.string.buzz_dm_new)) },
|
||||
icon = { Icon(symbol = MaterialSymbols.Add, contentDescription = null) },
|
||||
onClick = { nav.nav(Route.BuzzNewDm) },
|
||||
onClick = { nav.nav(Route.BuzzNewDm(relayUrl)) },
|
||||
)
|
||||
},
|
||||
) { padding ->
|
||||
|
||||
+20
-2
@@ -26,6 +26,7 @@ import androidx.lifecycle.viewModelScope
|
||||
import com.vitorpamplona.amethyst.commons.model.buzz.BuzzDmRegistry
|
||||
import com.vitorpamplona.amethyst.commons.model.buzz.BuzzRelayDialect
|
||||
import com.vitorpamplona.amethyst.commons.model.buzz.BuzzWorkspaces
|
||||
import com.vitorpamplona.amethyst.commons.relayauth.RelayAuthDecision
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.filter
|
||||
@@ -40,6 +41,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.client.accessories.fetchAllPages
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.subscribeAsFlow
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
|
||||
import com.vitorpamplona.quartz.nip29RelayGroups.GroupId
|
||||
import com.vitorpamplona.quartz.nipC7Chats.ChatEvent
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -73,6 +75,9 @@ import java.util.concurrent.ConcurrentHashMap
|
||||
*/
|
||||
class BuzzDmListViewModel : ViewModel() {
|
||||
@Volatile private var account: Account? = null
|
||||
|
||||
/** The community (relay) this inbox is scoped to — DMs are per-community. */
|
||||
private var scopeRelay: NormalizedRelayUrl? = null
|
||||
private val refreshMutex = Mutex()
|
||||
private var liveJob: Job? = null
|
||||
|
||||
@@ -98,11 +103,24 @@ class BuzzDmListViewModel : ViewModel() {
|
||||
val lastActivity: Long,
|
||||
)
|
||||
|
||||
private fun relays(): Set<NormalizedRelayUrl> = BuzzWorkspaces.flow.value + BuzzRelayDialect.flow.value
|
||||
private fun relays(): Set<NormalizedRelayUrl> = scopeRelay?.let { setOf(it) } ?: (BuzzWorkspaces.flow.value + BuzzRelayDialect.flow.value)
|
||||
|
||||
fun bindAccountIfMissing(account: Account) {
|
||||
/**
|
||||
* Binds to [account] scoped to the community [relayUrl]. Marks that relay a joined workspace and
|
||||
* pre-approves NIP-42 so the read-only `#p=me` DM discovery authenticates against it.
|
||||
*/
|
||||
fun bind(
|
||||
account: Account,
|
||||
relayUrl: String,
|
||||
) {
|
||||
if (this.account != null) return
|
||||
val relay = RelayUrlNormalizer.normalizeOrNull(relayUrl)
|
||||
this.scopeRelay = relay
|
||||
this.account = account
|
||||
relay?.let {
|
||||
BuzzWorkspaces.join(it)
|
||||
viewModelScope.launch { account.relayAuthLedger.setDecision(it.url, RelayAuthDecision.ALLOW) }
|
||||
}
|
||||
refresh()
|
||||
startLive()
|
||||
}
|
||||
|
||||
+4
-3
@@ -75,11 +75,12 @@ import com.vitorpamplona.quartz.nip01Core.relay.normalizer.displayUrl
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun BuzzNewDmScreen(
|
||||
relayUrl: String,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val viewModel: BuzzNewDmViewModel = viewModel()
|
||||
viewModel.bindAccountIfMissing(accountViewModel.account)
|
||||
val viewModel: BuzzNewDmViewModel = viewModel(key = "BuzzNewDm-$relayUrl")
|
||||
viewModel.bind(accountViewModel.account, relayUrl)
|
||||
|
||||
val relays by viewModel.relays.collectAsStateWithLifecycle()
|
||||
val selectedRelay by viewModel.relay.collectAsStateWithLifecycle()
|
||||
@@ -162,7 +163,7 @@ fun BuzzNewDmScreen(
|
||||
if (groupId != null) {
|
||||
nav.newStack(Route.RelayGroup(groupId.id, groupId.relayUrl.url))
|
||||
} else {
|
||||
nav.newStack(Route.BuzzDmList)
|
||||
nav.newStack(Route.BuzzDmList(relayUrl))
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
+9
-9
@@ -22,13 +22,12 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.buzz
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.vitorpamplona.amethyst.commons.model.buzz.BuzzRelayDialect
|
||||
import com.vitorpamplona.amethyst.commons.model.buzz.BuzzWorkspaces
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.quartz.buzz.dm.DmOpenEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.isValid
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
|
||||
import com.vitorpamplona.quartz.nip19Bech32.decodePublicKeyAsHexOrNull
|
||||
import com.vitorpamplona.quartz.nip29RelayGroups.GroupId
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -70,15 +69,16 @@ class BuzzNewDmViewModel : ViewModel() {
|
||||
) : Status
|
||||
}
|
||||
|
||||
fun bindAccountIfMissing(account: Account) {
|
||||
/** Binds scoped to a single community [relayUrl] — a new DM is created on that community. */
|
||||
fun bind(
|
||||
account: Account,
|
||||
relayUrl: String,
|
||||
) {
|
||||
if (this.account != null) return
|
||||
this.account = account
|
||||
val buzz =
|
||||
(BuzzWorkspaces.flow.value + BuzzRelayDialect.flow.value)
|
||||
.toList()
|
||||
.sortedBy { it.url }
|
||||
_relays.value = buzz
|
||||
_relay.value = buzz.firstOrNull()
|
||||
val relay = RelayUrlNormalizer.normalizeOrNull(relayUrl)
|
||||
_relays.value = listOfNotNull(relay)
|
||||
_relay.value = relay
|
||||
}
|
||||
|
||||
fun selectRelay(relay: NormalizedRelayUrl) {
|
||||
|
||||
+51
-1
@@ -62,6 +62,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbol
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.commons.model.buzz.BuzzRelayDialect
|
||||
import com.vitorpamplona.amethyst.commons.model.nip29RelayGroups.RelayGroupChannel
|
||||
@@ -218,7 +219,9 @@ fun RelayGroupChannelListScreen(
|
||||
) { padding ->
|
||||
val myPubkey = accountViewModel.userProfile().pubkeyHex
|
||||
val showBuzz = isBuzz && buzzChannels.isNotEmpty()
|
||||
if (channels.isEmpty() && !showBuzz) {
|
||||
// A Buzz relay is a community, so always render its list (with the per-community DM + Console
|
||||
// entries) even before channels load, rather than the generic "empty" text.
|
||||
if (channels.isEmpty() && !showBuzz && !isBuzz) {
|
||||
// An empty directory on a relay whose NIP-11 says it doesn't run NIP-29 is almost
|
||||
// certainly the wrong relay, not a young one — say so instead of the generic empty text.
|
||||
// For a Buzz relay still discovering, say so; if it settled empty, guide the user.
|
||||
@@ -249,6 +252,13 @@ fun RelayGroupChannelListScreen(
|
||||
}
|
||||
} else {
|
||||
LazyColumn(modifier = Modifier.padding(padding)) {
|
||||
// A Buzz relay is a community: its Direct Messages and (owner) Agent Console are
|
||||
// scoped to it, reached from here rather than as global drawer entries.
|
||||
if (isBuzz) {
|
||||
item(key = "buzz-community-actions") {
|
||||
BuzzCommunityActions(relayUrl = relay.url, nav = nav)
|
||||
}
|
||||
}
|
||||
// Buzz membership section: the channels you already belong to on this workspace,
|
||||
// each addable to your kind-10009 list.
|
||||
if (showBuzz) {
|
||||
@@ -325,6 +335,46 @@ private fun TorClearnetBanner(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Per-community entry points on a Buzz relay's screen: its Direct Messages inbox and (owner) Agent
|
||||
* Console, both scoped to this one relay — replacing the old global drawer entries.
|
||||
*/
|
||||
@Composable
|
||||
private fun BuzzCommunityActions(
|
||||
relayUrl: String,
|
||||
nav: INav,
|
||||
) {
|
||||
Column(Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 8.dp)) {
|
||||
BuzzActionCard(
|
||||
icon = MaterialSymbols.AutoMirrored.Send,
|
||||
label = stringRes(R.string.buzz_dm_title),
|
||||
onClick = { nav.nav(Route.BuzzDmList(relayUrl)) },
|
||||
)
|
||||
Spacer(Modifier.size(8.dp))
|
||||
BuzzActionCard(
|
||||
icon = MaterialSymbols.AutoAwesome,
|
||||
label = stringRes(R.string.buzz_console_card_title),
|
||||
onClick = { nav.nav(Route.AgentConsole(relayUrl)) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BuzzActionCard(
|
||||
icon: MaterialSymbol,
|
||||
label: String,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
Card(onClick = onClick, modifier = Modifier.fillMaxWidth()) {
|
||||
Row(Modifier.padding(14.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(symbol = icon, contentDescription = null, tint = MaterialTheme.colorScheme.primary, modifier = Modifier.size(22.dp))
|
||||
Spacer(Modifier.size(12.dp))
|
||||
Text(label, style = MaterialTheme.typography.titleSmall, fontWeight = FontWeight.Bold, modifier = Modifier.weight(1f))
|
||||
Icon(symbol = MaterialSymbols.ChevronRight, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(20.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RelayGroupChannelRow(
|
||||
channel: RelayGroupChannel,
|
||||
|
||||
-1
@@ -104,7 +104,6 @@ fun buildSettingsCatalog(
|
||||
symEntry(R.string.ots_explorer_settings, MaterialSymbols.Search, R.string.ots_explorer_search_keywords, Route.OtsSettings),
|
||||
symEntry(R.string.namecoin_settings, MaterialSymbols.Security, R.string.namecoin_search_keywords, Route.NamecoinSettings),
|
||||
symEntry(R.string.resource_usage_title, MaterialSymbols.Bolt, R.string.resource_usage_search_keywords, Route.ResourceUsage),
|
||||
symEntry(R.string.agent_console_title, MaterialSymbols.AutoAwesome, R.string.agent_console_search_keywords, Route.AgentConsole),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user