* refactor(nav): remove bottom NavigationBar from Scaffold (#665) * refactor(theme): remove BottomNavDisplayMode enum (#666) * refactor(settings): remove nav bar customization page (#667) * chore: clean up unused imports after bottom nav removal (#668) * chore: remove dead bottom nav infrastructure after removal - Remove SettingsNavBar NavKey (NavigationKeys.kt) - Remove bottomNavItems field from ServerStoreState + migration code - Remove _bottomNavItemsFlow, getBottomNavItems, setBottomNavItems from AuthManager - Remove bottomNavItems handling from selfHealed() (ServerCollectionOps) - Remove redundant bottomBar={} from Scaffold (Navigation.kt) - Remove unused nav bar strings from values/strings.xml + values-ko/strings.xml - Remove content_desc_move_up/down/remove_item strings - Update SettingsViewModelTest.kt (drop stale getBottomNavItems mock) - Update ServerStoreTest.kt (remove bottomNavItems assertions) --------- Co-authored-by: M57 <Hy4ri@users.noreply.github.com>
This commit is contained in:
1 parent
c43904b2b8
commit
0edb67338d
17 files changed
-580
No files matched your search
@@ -11,7 +11,6 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowForward
|
||||
import androidx.compose.material.icons.automirrored.filled.ListAlt
|
||||
import androidx.compose.material.icons.filled.ChatBubbleOutline
|
||||
import androidx.compose.material.icons.filled.Dashboard
|
||||
import androidx.compose.material.icons.filled.Info
|
||||
import androidx.compose.material.icons.filled.Palette
|
||||
import androidx.compose.material.icons.filled.Tune
|
||||
@@ -40,7 +39,6 @@ import com.m57.hermescontrol.SettingsAppearance
|
||||
import com.m57.hermescontrol.SettingsBehavior
|
||||
import com.m57.hermescontrol.SettingsChat
|
||||
import com.m57.hermescontrol.SettingsConnection
|
||||
import com.m57.hermescontrol.SettingsNavBar
|
||||
import com.m57.hermescontrol.theme.ThemePreference
|
||||
import com.m57.hermescontrol.ui.common.HermesScaffold
|
||||
import com.m57.hermescontrol.ui.common.NavIcon
|
||||
@@ -100,11 +98,6 @@ fun SettingsScreen(
|
||||
},
|
||||
onClick = { NavigationController.navigateTo(SettingsChat) },
|
||||
),
|
||||
SettingsRow(
|
||||
icon = Icons.Filled.Dashboard,
|
||||
label = stringResource(R.string.settings_sec_nav_bar),
|
||||
onClick = { NavigationController.navigateTo(SettingsNavBar) },
|
||||
),
|
||||
SettingsRow(
|
||||
icon = Icons.Filled.Tune,
|
||||
label = stringResource(R.string.settings_sec_behavior),
|
||||
|
||||
@@ -31,7 +31,6 @@ import com.m57.hermescontrol.ui.settings.components.AppearanceSection
|
||||
import com.m57.hermescontrol.ui.settings.components.BehaviorSection
|
||||
import com.m57.hermescontrol.ui.settings.components.ChatSection
|
||||
import com.m57.hermescontrol.ui.settings.components.ConnectionSection
|
||||
import com.m57.hermescontrol.ui.settings.components.NavBarSection
|
||||
import com.m57.hermescontrol.ui.settings.components.TestConnectionButton
|
||||
import com.m57.hermescontrol.ui.settings.components.TestResultCard
|
||||
|
||||
@@ -165,39 +164,6 @@ internal fun SettingsChatPage(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun SettingsNavBarPage(
|
||||
onBack: () -> Unit,
|
||||
viewModel: SettingsViewModel = viewModel { SettingsViewModel() },
|
||||
) {
|
||||
val state by viewModel.uiState.collectAsStateWithLifecycle()
|
||||
|
||||
HermesScaffold(
|
||||
title = { Text(stringResource(R.string.settings_sec_nav_bar)) },
|
||||
navigationIcon = NavIcon.Back(onBack),
|
||||
drawerGesturesEnabled = false,
|
||||
) {
|
||||
Column(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
) {
|
||||
NavBarSection(
|
||||
bottomNavDisplayMode = state.bottomNavDisplayMode,
|
||||
onBottomNavDisplayModeChange = viewModel::onBottomNavDisplayModeChange,
|
||||
selectedNavItems = state.selectedNavItems,
|
||||
availableNavItems = viewModel.availableNavItems,
|
||||
onReorderNavItems = viewModel::reorderNavItems,
|
||||
onRemoveNavItem = viewModel::removeNavItem,
|
||||
onAddNavItem = viewModel::addNavItem,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun SettingsBehaviorPage(
|
||||
onBack: () -> Unit,
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.m57.hermescontrol.ui.settings
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.m57.hermescontrol.ScreenRegistry
|
||||
import com.m57.hermescontrol.data.config.ConnectionProfile
|
||||
import com.m57.hermescontrol.data.config.resolveBaseUrl
|
||||
import com.m57.hermescontrol.data.local.AuthManager
|
||||
@@ -12,7 +11,6 @@ import com.m57.hermescontrol.data.remote.NetworkResult
|
||||
import com.m57.hermescontrol.data.remote.ServerEndpoint
|
||||
import com.m57.hermescontrol.data.remote.safeApiCall
|
||||
import com.m57.hermescontrol.data.ws.HermesWsClient
|
||||
import com.m57.hermescontrol.theme.BottomNavDisplayMode
|
||||
import com.m57.hermescontrol.theme.ThemePreference
|
||||
import com.m57.hermescontrol.theme.ThemePreset
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
@@ -35,13 +33,11 @@ data class SettingsUiState(
|
||||
val isTesting: Boolean = false,
|
||||
val testResult: String? = null,
|
||||
val isSaved: Boolean = false,
|
||||
val selectedNavItems: List<String> = emptyList(),
|
||||
val typingEffectEnabled: Boolean = false,
|
||||
val typingEffectDelayMs: Int = 30,
|
||||
val profiles: List<ConnectionProfile> = emptyList(),
|
||||
val selectedProfileId: String? = null,
|
||||
val renameProfileName: String = "",
|
||||
val bottomNavDisplayMode: BottomNavDisplayMode = BottomNavDisplayMode.ICON_AND_TEXT,
|
||||
val appLanguage: String = "system",
|
||||
// Profile add/edit dialog
|
||||
val showProfileDialog: Boolean = false,
|
||||
@@ -77,11 +73,9 @@ class SettingsViewModel(
|
||||
val themePreference = AuthManager.getThemePreference()
|
||||
val useDynamicColors = AuthManager.isUseDynamicColors()
|
||||
val themePreset = AuthManager.getThemePreset()
|
||||
val selectedNavItems = AuthManager.getBottomNavItems()
|
||||
val typingEffectEnabled = AuthManager.isTypingEffectEnabled()
|
||||
val typingEffectDelayMs = AuthManager.getTypingEffectDelayMs()
|
||||
val profiles = AuthManager.getConnectionProfiles()
|
||||
val bottomNavDisplayMode = AuthManager.getBottomNavDisplayMode()
|
||||
val appLanguage = AuthManager.getAppLanguage()
|
||||
val renameProfileName =
|
||||
profiles.firstOrNull { p -> p.id == selectedId }?.name ?: ""
|
||||
@@ -98,13 +92,11 @@ class SettingsViewModel(
|
||||
themePreference = themePreference,
|
||||
useDynamicColors = useDynamicColors,
|
||||
themePreset = themePreset,
|
||||
selectedNavItems = selectedNavItems,
|
||||
typingEffectEnabled = typingEffectEnabled,
|
||||
typingEffectDelayMs = typingEffectDelayMs,
|
||||
profiles = profiles,
|
||||
selectedProfileId = selectedId,
|
||||
renameProfileName = renameProfileName,
|
||||
bottomNavDisplayMode = bottomNavDisplayMode,
|
||||
appLanguage = appLanguage,
|
||||
)
|
||||
}
|
||||
@@ -312,11 +304,6 @@ class SettingsViewModel(
|
||||
AuthManager.setAppLanguage(code)
|
||||
}
|
||||
|
||||
fun onBottomNavDisplayModeChange(mode: BottomNavDisplayMode) {
|
||||
_uiState.update { it.copy(bottomNavDisplayMode = mode, isSaved = false) }
|
||||
AuthManager.setBottomNavDisplayMode(mode)
|
||||
}
|
||||
|
||||
fun onTypingEffectEnabledChange(enabled: Boolean) {
|
||||
_uiState.update { it.copy(typingEffectEnabled = enabled, isSaved = false) }
|
||||
AuthManager.setTypingEffectEnabled(enabled)
|
||||
@@ -336,34 +323,6 @@ class SettingsViewModel(
|
||||
// Don't rebuild ApiClient here — let the navigation complete first
|
||||
}
|
||||
|
||||
/** All screens available for bottom-nav selection (name → display label). */
|
||||
val availableNavItems: List<Pair<String, Int>> =
|
||||
ScreenRegistry.ALL_SCREENS.map { screen ->
|
||||
screen.key::class.simpleName!! to screen.labelRes
|
||||
}
|
||||
|
||||
/** Add a nav item to the bottom bar (max 5). Auto-saves. */
|
||||
fun addNavItem(name: String) {
|
||||
val current = _uiState.value.selectedNavItems
|
||||
if (name in current || current.size >= 5) return
|
||||
val updated = current + name
|
||||
_uiState.update { it.copy(selectedNavItems = updated) }
|
||||
AuthManager.setBottomNavItems(updated)
|
||||
}
|
||||
|
||||
/** Remove a nav item from the bottom bar. Auto-saves. */
|
||||
fun removeNavItem(name: String) {
|
||||
val updated = _uiState.value.selectedNavItems - name
|
||||
_uiState.update { it.copy(selectedNavItems = updated) }
|
||||
AuthManager.setBottomNavItems(updated)
|
||||
}
|
||||
|
||||
/** Reorder the selected nav items. Auto-saves. */
|
||||
fun reorderNavItems(items: List<String>) {
|
||||
_uiState.update { it.copy(selectedNavItems = items) }
|
||||
AuthManager.setBottomNavItems(items)
|
||||
}
|
||||
|
||||
fun save() {
|
||||
val state = _uiState.value
|
||||
val normalized =
|
||||
@@ -379,7 +338,6 @@ class SettingsViewModel(
|
||||
AuthManager.setThemePreference(state.themePreference)
|
||||
AuthManager.setUseDynamicColors(state.useDynamicColors)
|
||||
AuthManager.setThemePreset(state.themePreset)
|
||||
AuthManager.setBottomNavDisplayMode(state.bottomNavDisplayMode)
|
||||
AuthManager.setTypingEffectEnabled(state.typingEffectEnabled)
|
||||
AuthManager.setTypingEffectDelayMs(state.typingEffectDelayMs)
|
||||
ApiClient.rebuild()
|
||||
|
||||
@@ -11,16 +11,8 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowUp
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.SegmentedButton
|
||||
@@ -40,7 +32,6 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.m57.hermescontrol.R
|
||||
import com.m57.hermescontrol.theme.BottomNavDisplayMode
|
||||
import com.m57.hermescontrol.theme.ThemePreference
|
||||
import com.m57.hermescontrol.theme.ThemePreset
|
||||
import com.m57.hermescontrol.ui.settings.SectionCard
|
||||
@@ -315,157 +306,3 @@ internal fun ChatSection(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun NavBarSection(
|
||||
bottomNavDisplayMode: BottomNavDisplayMode,
|
||||
onBottomNavDisplayModeChange: (BottomNavDisplayMode) -> Unit,
|
||||
selectedNavItems: List<String>,
|
||||
availableNavItems: List<Pair<String, Int>>,
|
||||
onReorderNavItems: (List<String>) -> Unit,
|
||||
onRemoveNavItem: (String) -> Unit,
|
||||
onAddNavItem: (String) -> Unit,
|
||||
) {
|
||||
SectionCard {
|
||||
Text(
|
||||
text = stringResource(R.string.settings_item_bottom_nav_display_mode),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
SingleChoiceSegmentedButtonRow(modifier = Modifier.fillMaxWidth()) {
|
||||
BottomNavDisplayMode.entries.forEachIndexed { index, mode ->
|
||||
SegmentedButton(
|
||||
selected = bottomNavDisplayMode == mode,
|
||||
onClick = { onBottomNavDisplayModeChange(mode) },
|
||||
shape =
|
||||
SegmentedButtonDefaults.itemShape(
|
||||
index = index,
|
||||
count = BottomNavDisplayMode.entries.size,
|
||||
),
|
||||
) {
|
||||
Text(
|
||||
when (mode) {
|
||||
BottomNavDisplayMode.ICON_AND_TEXT -> {
|
||||
stringResource(
|
||||
R.string.settings_display_mode_icon_and_text,
|
||||
)
|
||||
}
|
||||
|
||||
BottomNavDisplayMode.ICON_ONLY -> {
|
||||
stringResource(
|
||||
R.string.settings_display_mode_icon_only,
|
||||
)
|
||||
}
|
||||
|
||||
BottomNavDisplayMode.TEXT_ONLY -> {
|
||||
stringResource(
|
||||
R.string.settings_display_mode_text_only,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.settings_item_nav_items, selectedNavItems.size),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
selectedNavItems.forEachIndexed { index, name ->
|
||||
val labelRes = availableNavItems.firstOrNull { it.first == name }?.second
|
||||
val label = if (labelRes != null) stringResource(labelRes) else name
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 2.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = label,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
val list = selectedNavItems.toMutableList()
|
||||
val temp = list[index]
|
||||
list[index] = list[index - 1]
|
||||
list[index - 1] = temp
|
||||
onReorderNavItems(list)
|
||||
},
|
||||
enabled = index > 0,
|
||||
) {
|
||||
Icon(
|
||||
Icons.Filled.KeyboardArrowUp,
|
||||
contentDescription = stringResource(R.string.content_desc_move_up),
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
onClick = {
|
||||
val list = selectedNavItems.toMutableList()
|
||||
val temp = list[index]
|
||||
list[index] = list[index + 1]
|
||||
list[index + 1] = temp
|
||||
onReorderNavItems(list)
|
||||
},
|
||||
enabled = index < selectedNavItems.lastIndex,
|
||||
) {
|
||||
Icon(
|
||||
Icons.Filled.KeyboardArrowDown,
|
||||
contentDescription = stringResource(R.string.content_desc_move_down),
|
||||
)
|
||||
}
|
||||
IconButton(onClick = { onRemoveNavItem(name) }) {
|
||||
Icon(
|
||||
Icons.Filled.Close,
|
||||
contentDescription =
|
||||
stringResource(
|
||||
R.string.content_desc_remove_item,
|
||||
label,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedNavItems.size < 5) {
|
||||
val available =
|
||||
availableNavItems.filter { it.first !in selectedNavItems }
|
||||
if (available.isNotEmpty()) {
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
Box {
|
||||
OutlinedButton(onClick = { expanded = true }) {
|
||||
Icon(Icons.Filled.Add, contentDescription = null)
|
||||
Spacer(Modifier.size(4.dp))
|
||||
Text(stringResource(R.string.settings_action_add_item))
|
||||
}
|
||||
DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
|
||||
available.forEach { (name, labelRes) ->
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(labelRes)) },
|
||||
onClick = {
|
||||
onAddNavItem(name)
|
||||
expanded = false
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Text(
|
||||
text = stringResource(R.string.settings_nav_max_reached),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user