From f7c3bd2dcfe943359ed2cf8cac1a2e1a4bf7a1f0 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Tue, 26 May 2026 19:59:20 -0400 Subject: [PATCH] =?UTF-8?q?feat(android):=20Settings=20=E2=80=94=20My=20Re?= =?UTF-8?q?quests=20+=20Admin=20tiles?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Audit v2 reachability gap: Requests screen was orphaned (no entry point) and admins had no Settings-side affordance for admin tools. Both now surface as ListTile-style cards in the Settings stack with Lucide chevron + leading icon, matching Flutter's layout. Admin tile gated on SettingsState.isAdmin (sourced from AuthController. currentUser, plumbed through the combine() chain). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../minstrel/settings/ui/SettingsScreen.kt | 66 +++++++++++++++++++ .../minstrel/settings/ui/SettingsViewModel.kt | 2 + 2 files changed, 68 insertions(+) diff --git a/android/app/src/main/java/com/fabledsword/minstrel/settings/ui/SettingsScreen.kt b/android/app/src/main/java/com/fabledsword/minstrel/settings/ui/SettingsScreen.kt index e5439bc7..17ffa046 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/settings/ui/SettingsScreen.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/settings/ui/SettingsScreen.kt @@ -1,9 +1,11 @@ package com.fabledsword.minstrel.settings.ui +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -26,15 +28,22 @@ import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavHostController +import com.composables.icons.lucide.ChevronRight +import com.composables.icons.lucide.ListMusic import com.composables.icons.lucide.LogOut import com.composables.icons.lucide.Lucide +import com.composables.icons.lucide.Shield import com.fabledsword.minstrel.BuildConfig +import com.fabledsword.minstrel.nav.Admin +import com.fabledsword.minstrel.nav.Requests import com.fabledsword.minstrel.nav.Settings as SettingsRoute import com.fabledsword.minstrel.nav.ServerUrl import com.fabledsword.minstrel.shared.widgets.MainAppBarActions @@ -82,6 +91,20 @@ fun SettingsScreen( verticalArrangement = Arrangement.spacedBy(16.dp), ) { AccountCard(username = state.username, serverUrl = state.serverUrl) + NavTile( + icon = Lucide.ListMusic, + title = "My requests", + subtitle = "Track what you've asked Minstrel to add", + onClick = { navController.navigate(Requests) }, + ) + if (state.isAdmin) { + NavTile( + icon = Lucide.Shield, + title = "Admin", + subtitle = "Manage requests, quarantine, and users", + onClick = { navController.navigate(Admin) }, + ) + } AppearanceCard(themeMode = themeMode, onPick = themeVm::setThemeMode) StorageCard() AboutCard() @@ -90,6 +113,49 @@ fun SettingsScreen( } } +@Composable +private fun NavTile( + icon: ImageVector, + title: String, + subtitle: String, + onClick: () -> Unit, +) { + ElevatedCard( + modifier = Modifier + .fillMaxWidth() + .clickable(onClick = onClick), + ) { + Row( + modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + imageVector = icon, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurface, + ) + Spacer(Modifier.size(16.dp)) + Column(modifier = Modifier.weight(1f)) { + Text( + text = title, + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.onSurface, + ) + Text( + text = subtitle, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + Icon( + imageVector = Lucide.ChevronRight, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + } +} + @Composable private fun AccountCard(username: String, serverUrl: String) { ElevatedCard(modifier = Modifier.fillMaxWidth()) { diff --git a/android/app/src/main/java/com/fabledsword/minstrel/settings/ui/SettingsViewModel.kt b/android/app/src/main/java/com/fabledsword/minstrel/settings/ui/SettingsViewModel.kt index 441fac10..ebe2f563 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/settings/ui/SettingsViewModel.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/settings/ui/SettingsViewModel.kt @@ -16,6 +16,7 @@ import javax.inject.Inject data class SettingsState( val serverUrl: String = "", val username: String = "", + val isAdmin: Boolean = false, val isSigningOut: Boolean = false, val signedOut: Boolean = false, ) @@ -45,6 +46,7 @@ class SettingsViewModel @Inject constructor( SettingsState( serverUrl = url, username = user?.username.orEmpty(), + isAdmin = user?.isAdmin == true, isSigningOut = t.isSigningOut, signedOut = t.signedOut, )