Merge pull request #2482 from vitorpamplona/claude/reorganize-app-navigation-wkjQ6

Organize drawer navigation into collapsible sections
This commit is contained in:
Vitor Pamplona
2026-04-21 16:59:55 -04:00
committed by GitHub
3 changed files with 278 additions and 166 deletions
@@ -20,6 +20,7 @@
*/
package com.vitorpamplona.amethyst.ui.navigation.drawer
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.border
@@ -58,6 +59,8 @@ import androidx.compose.material.icons.outlined.AccountBalanceWallet
import androidx.compose.material.icons.outlined.CollectionsBookmark
import androidx.compose.material.icons.outlined.Drafts
import androidx.compose.material.icons.outlined.EmojiEmotions
import androidx.compose.material.icons.outlined.ExpandLess
import androidx.compose.material.icons.outlined.ExpandMore
import androidx.compose.material.icons.outlined.GroupAdd
import androidx.compose.material.icons.outlined.Groups
import androidx.compose.material.icons.outlined.Language
@@ -100,6 +103,7 @@ import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.text.withLink
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -116,14 +120,18 @@ import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUse
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserStatuses
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
import com.vitorpamplona.amethyst.ui.navigation.navs.EmptyNav
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor
import com.vitorpamplona.amethyst.ui.painterRes
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.DrawerSectionHeaderModifier
import com.vitorpamplona.amethyst.ui.theme.Font14SP
import com.vitorpamplona.amethyst.ui.theme.Font18SP
import com.vitorpamplona.amethyst.ui.theme.IconRowTextModifier
import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
@@ -133,6 +141,7 @@ import com.vitorpamplona.amethyst.ui.theme.Size24Modifier
import com.vitorpamplona.amethyst.ui.theme.Size26Modifier
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.TextStyleBottomNavBar
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
import com.vitorpamplona.amethyst.ui.theme.Width16Space
import com.vitorpamplona.amethyst.ui.theme.bannerModifier
import com.vitorpamplona.amethyst.ui.theme.drawerSpacing
@@ -538,179 +547,187 @@ fun ListContent(
nav: INav,
) {
Column(modifier) {
NavigationRow(
title = R.string.profile,
icon = Icons.Default.AccountCircle,
tint = MaterialTheme.colorScheme.primary,
nav = nav,
computeRoute = {
Route.Profile(accountViewModel.userProfile().pubkeyHex)
},
)
NavigationRow(
title = R.string.my_lists,
icon = Icons.AutoMirrored.Filled.FormatListBulleted,
tint = MaterialTheme.colorScheme.onBackground,
nav = nav,
route = Route.Lists,
)
NavigationRow(
title = R.string.bookmarks,
icon = Icons.Outlined.CollectionsBookmark,
tint = MaterialTheme.colorScheme.onBackground,
nav = nav,
route = Route.BookmarkGroups,
)
NavigationRow(
title = R.string.manage_emoji_packs,
icon = Icons.Outlined.EmojiEmotions,
tint = MaterialTheme.colorScheme.onBackground,
nav = nav,
route = Route.EmojiPacks,
)
NavigationRow(
title = R.string.interest_sets_title,
icon = Icons.Outlined.Tag,
tint = MaterialTheme.colorScheme.onBackground,
nav = nav,
route = Route.InterestSets,
)
NavigationRow(
title = R.string.web_bookmarks,
icon = Icons.Outlined.Language,
tint = MaterialTheme.colorScheme.onBackground,
nav = nav,
route = Route.WebBookmarks,
)
NavigationRow(
title = R.string.drafts,
icon = Icons.Outlined.Drafts,
tint = MaterialTheme.colorScheme.onBackground,
nav = nav,
route = Route.Drafts,
)
NavigationRow(
title = R.string.badges,
icon = Icons.Outlined.MilitaryTech,
tint = MaterialTheme.colorScheme.onBackground,
nav = nav,
route = Route.Badges,
)
NavigationRow(
title = R.string.communities,
icon = Icons.Outlined.Groups,
tint = MaterialTheme.colorScheme.onBackground,
nav = nav,
route = Route.Communities,
)
NavigationRow(
title = R.string.discover_marketplace,
icon = Icons.Outlined.Storefront,
tint = MaterialTheme.colorScheme.onBackground,
nav = nav,
route = Route.Products,
)
NavigationRow(
title = R.string.pictures,
icon = Icons.Outlined.Photo,
tint = MaterialTheme.colorScheme.onBackground,
nav = nav,
route = Route.Pictures,
)
NavigationRow(
title = R.string.polls,
icon = R.drawable.ic_poll,
iconReference = 1,
tint = MaterialTheme.colorScheme.onBackground,
nav = nav,
route = Route.Polls,
)
NavigationRow(
title = R.string.discover_reads,
icon = Icons.AutoMirrored.Outlined.Article,
tint = MaterialTheme.colorScheme.onBackground,
nav = nav,
route = Route.Articles,
)
NavigationRow(
title = R.string.shorts,
icon = Icons.Outlined.PlayCircle,
tint = MaterialTheme.colorScheme.onBackground,
nav = nav,
route = Route.Shorts,
)
NavigationRow(
title = R.string.longs,
icon = Icons.Outlined.SmartDisplay,
tint = MaterialTheme.colorScheme.onBackground,
nav = nav,
route = Route.Longs,
)
NavigationRow(
title = R.string.wallet,
icon = Icons.Outlined.AccountBalanceWallet,
tint = MaterialTheme.colorScheme.onBackground,
nav = nav,
route = Route.Wallet,
)
NavigationRow(
title = R.string.emoji_sets,
icon = Icons.Outlined.EmojiEmotions,
tint = MaterialTheme.colorScheme.onBackground,
nav = nav,
route = Route.BrowseEmojiSets,
)
NavigationRow(
title = R.string.share_hls_video,
icon = Icons.Outlined.SettingsInputAntenna,
tint = MaterialTheme.colorScheme.onBackground,
nav = nav,
route = Route.NewHlsVideo,
)
if (isDebug) {
CollapsibleSection(title = R.string.drawer_section_you) {
NavigationRow(
title = R.string.route_chess,
icon = R.drawable.ic_chess,
iconReference = 1,
title = R.string.profile,
icon = Icons.Default.AccountCircle,
tint = MaterialTheme.colorScheme.primary,
nav = nav,
computeRoute = {
Route.Profile(accountViewModel.userProfile().pubkeyHex)
},
)
NavigationRow(
title = R.string.my_lists,
icon = Icons.AutoMirrored.Filled.FormatListBulleted,
tint = MaterialTheme.colorScheme.onBackground,
nav = nav,
route = Route.Chess,
route = Route.Lists,
)
NavigationRow(
title = R.string.bookmarks,
icon = Icons.Outlined.CollectionsBookmark,
tint = MaterialTheme.colorScheme.onBackground,
nav = nav,
route = Route.BookmarkGroups,
)
NavigationRow(
title = R.string.web_bookmarks,
icon = Icons.Outlined.Language,
tint = MaterialTheme.colorScheme.onBackground,
nav = nav,
route = Route.WebBookmarks,
)
NavigationRow(
title = R.string.drafts,
icon = Icons.Outlined.Drafts,
tint = MaterialTheme.colorScheme.onBackground,
nav = nav,
route = Route.Drafts,
)
NavigationRow(
title = R.string.interest_sets_title,
icon = Icons.Outlined.Tag,
tint = MaterialTheme.colorScheme.onBackground,
nav = nav,
route = Route.InterestSets,
)
NavigationRow(
title = R.string.manage_emoji_packs,
icon = Icons.Outlined.EmojiEmotions,
tint = MaterialTheme.colorScheme.onBackground,
nav = nav,
route = Route.EmojiPacks,
)
NavigationRow(
title = R.string.wallet,
icon = Icons.Outlined.AccountBalanceWallet,
tint = MaterialTheme.colorScheme.onBackground,
nav = nav,
route = Route.Wallet,
)
}
IconRowRelays(
accountViewModel = accountViewModel,
onClick = {
nav.closeDrawer()
nav.nav(Route.EditRelays)
},
)
CollapsibleSection(title = R.string.drawer_section_feeds) {
NavigationRow(
title = R.string.communities,
icon = Icons.Outlined.Groups,
tint = MaterialTheme.colorScheme.onBackground,
nav = nav,
route = Route.Communities,
)
NavigationRow(
title = R.string.settings,
icon = Icons.Outlined.Settings,
tint = MaterialTheme.colorScheme.onBackground,
nav = nav,
route = Route.AllSettings,
)
NavigationRow(
title = R.string.discover_reads,
icon = Icons.AutoMirrored.Outlined.Article,
tint = MaterialTheme.colorScheme.onBackground,
nav = nav,
route = Route.Articles,
)
NavigationRow(
title = R.string.pictures,
icon = Icons.Outlined.Photo,
tint = MaterialTheme.colorScheme.onBackground,
nav = nav,
route = Route.Pictures,
)
NavigationRow(
title = R.string.shorts,
icon = Icons.Outlined.PlayCircle,
tint = MaterialTheme.colorScheme.onBackground,
nav = nav,
route = Route.Shorts,
)
NavigationRow(
title = R.string.longs,
icon = Icons.Outlined.SmartDisplay,
tint = MaterialTheme.colorScheme.onBackground,
nav = nav,
route = Route.Longs,
)
NavigationRow(
title = R.string.polls,
icon = R.drawable.ic_poll,
iconReference = 1,
tint = MaterialTheme.colorScheme.onBackground,
nav = nav,
route = Route.Polls,
)
NavigationRow(
title = R.string.badges,
icon = Icons.Outlined.MilitaryTech,
tint = MaterialTheme.colorScheme.onBackground,
nav = nav,
route = Route.Badges,
)
NavigationRow(
title = R.string.discover_marketplace,
icon = Icons.Outlined.Storefront,
tint = MaterialTheme.colorScheme.onBackground,
nav = nav,
route = Route.Products,
)
NavigationRow(
title = R.string.emoji_sets,
icon = Icons.Outlined.EmojiEmotions,
tint = MaterialTheme.colorScheme.onBackground,
nav = nav,
route = Route.BrowseEmojiSets,
)
}
CollapsibleSection(title = R.string.drawer_section_create) {
NavigationRow(
title = R.string.share_hls_video,
icon = Icons.Outlined.SettingsInputAntenna,
tint = MaterialTheme.colorScheme.onBackground,
nav = nav,
route = Route.NewHlsVideo,
)
if (isDebug) {
NavigationRow(
title = R.string.route_chess,
icon = R.drawable.ic_chess,
iconReference = 1,
tint = MaterialTheme.colorScheme.onBackground,
nav = nav,
route = Route.Chess,
)
}
}
CollapsibleSection(title = R.string.drawer_section_system) {
IconRowRelays(
accountViewModel = accountViewModel,
onClick = {
nav.closeDrawer()
nav.nav(Route.EditRelays)
},
)
NavigationRow(
title = R.string.settings,
icon = Icons.Outlined.Settings,
tint = MaterialTheme.colorScheme.onBackground,
nav = nav,
route = Route.AllSettings,
)
}
Spacer(modifier = Modifier.weight(1f))
@@ -723,6 +740,40 @@ fun ListContent(
}
}
@Composable
fun CollapsibleSection(
title: Int,
content: @Composable () -> Unit,
) {
var expanded by remember { mutableStateOf(true) }
val sectionTitle = stringRes(title)
Column(modifier = Modifier.animateContentSize()) {
Row(
modifier = DrawerSectionHeaderModifier.clickable { expanded = !expanded },
verticalAlignment = Alignment.CenterVertically,
) {
Text(
modifier = Modifier.weight(1f),
text = sectionTitle,
fontSize = Font14SP,
fontWeight = FontWeight.SemiBold,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
Icon(
imageVector = if (expanded) Icons.Outlined.ExpandLess else Icons.Outlined.ExpandMore,
contentDescription = sectionTitle,
modifier = Size22Modifier,
tint = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
if (expanded) {
content()
}
}
}
@Composable
fun NavigationRow(
title: Int,
@@ -970,3 +1021,59 @@ fun BottomContent(
}
}
}
@Preview
@Composable
private fun CollapsibleSectionPreview() {
ThemeComparisonColumn {
Column {
CollapsibleSection(title = R.string.drawer_section_you) {
IconRow(
title = R.string.profile,
icon = Icons.Default.AccountCircle,
tint = MaterialTheme.colorScheme.primary,
onClick = {},
)
IconRow(
title = R.string.bookmarks,
icon = Icons.Outlined.CollectionsBookmark,
tint = MaterialTheme.colorScheme.onBackground,
onClick = {},
)
IconRow(
title = R.string.drafts,
icon = Icons.Outlined.Drafts,
tint = MaterialTheme.colorScheme.onBackground,
onClick = {},
)
}
CollapsibleSection(title = R.string.drawer_section_feeds) {
IconRow(
title = R.string.pictures,
icon = Icons.Outlined.Photo,
tint = MaterialTheme.colorScheme.onBackground,
onClick = {},
)
IconRow(
title = R.string.longs,
icon = Icons.Outlined.SmartDisplay,
tint = MaterialTheme.colorScheme.onBackground,
onClick = {},
)
}
}
}
}
@Preview(widthDp = 320, heightDp = 1400)
@Composable
private fun ListContentPreview() {
ThemeComparisonColumn {
ListContent(
modifier = Modifier.fillMaxWidth(),
openSheet = {},
accountViewModel = mockAccountViewModel(),
nav = EmptyNav(),
)
}
}
@@ -263,6 +263,7 @@ val drawerSpacing = Modifier.padding(top = Size10dp, start = Size25dp, end = Siz
val IconRowTextModifier = Modifier.padding(start = 16.dp)
val IconRowModifier = Modifier.fillMaxWidth().padding(vertical = 15.dp, horizontal = 25.dp)
val DrawerSectionHeaderModifier = Modifier.fillMaxWidth().padding(top = 12.dp, bottom = 4.dp, start = 25.dp, end = 15.dp)
val Width16Space = Modifier.width(Size16dp)
+4
View File
@@ -369,6 +369,10 @@
<string name="private_conversation_notification">"&lt;Unable to decrypt private message&gt;\n\nYou were cited in a private/encrypted conversation between %1$s and %2$s."</string>
<string name="account_switch_add_account_dialog_title">Add New Account</string>
<string name="drawer_accounts">Accounts</string>
<string name="drawer_section_you">You</string>
<string name="drawer_section_feeds">Feeds</string>
<string name="drawer_section_create">Create</string>
<string name="drawer_section_system">System</string>
<string name="account_switch_select_account">Select Account</string>
<string name="account_switch_add_account_btn">Add New Account</string>
<string name="account_switch_active_account">Active account</string>