refactor(#78): extract UI strings for chat, settings, connect, kanban, and skills screens
This commit is contained in:
1 parent
acd6cbf751
commit
cdad31b555
9 files changed
+348
-149
No files matched your search
@@ -54,6 +54,7 @@ import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
@@ -64,6 +65,7 @@ import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.m57.hermescontrol.R
|
||||
import com.m57.hermescontrol.theme.AssistantBubble
|
||||
import com.m57.hermescontrol.theme.AssistantBubbleLight
|
||||
import com.m57.hermescontrol.theme.StatusGreen
|
||||
@@ -232,7 +234,7 @@ private fun UserBubble(
|
||||
) {
|
||||
Icon(
|
||||
Icons.Filled.ContentCopy,
|
||||
contentDescription = "Copy",
|
||||
contentDescription = stringResource(R.string.content_desc_copy),
|
||||
modifier = Modifier.size(16.dp),
|
||||
tint = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
@@ -347,7 +349,7 @@ private fun AssistantBubble(
|
||||
) {
|
||||
Icon(
|
||||
Icons.Filled.ContentCopy,
|
||||
contentDescription = "Copy",
|
||||
contentDescription = stringResource(R.string.content_desc_copy),
|
||||
modifier = Modifier.size(16.dp),
|
||||
tint = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
@@ -489,7 +491,7 @@ private fun ParsedToolContent(
|
||||
}
|
||||
parsed.stderr?.let {
|
||||
Text(
|
||||
text = "Error Output (stderr):\n$it",
|
||||
text = stringResource(R.string.chat_tool_stderr, it),
|
||||
style =
|
||||
MaterialTheme.typography.bodySmall.copy(
|
||||
color = StatusRed.copy(alpha = 0.9f),
|
||||
@@ -500,7 +502,7 @@ private fun ParsedToolContent(
|
||||
}
|
||||
parsed.error?.let {
|
||||
Text(
|
||||
text = "Execution Error: $it",
|
||||
text = stringResource(R.string.chat_tool_execution_error, it),
|
||||
style =
|
||||
MaterialTheme.typography.bodySmall.copy(
|
||||
color = StatusRed,
|
||||
@@ -513,7 +515,7 @@ private fun ParsedToolContent(
|
||||
parsed.exitCode?.let { code ->
|
||||
if (code != 0) {
|
||||
Text(
|
||||
text = "Exit Code: $code",
|
||||
text = stringResource(R.string.chat_tool_exit_code, code),
|
||||
style =
|
||||
MaterialTheme.typography.labelSmall.copy(
|
||||
color = StatusRed,
|
||||
@@ -619,7 +621,7 @@ private fun ToolBubble(
|
||||
}
|
||||
|
||||
Text(
|
||||
text = message.toolName ?: "Tool",
|
||||
text = message.toolName ?: stringResource(R.string.chat_tool_fallback),
|
||||
style =
|
||||
MaterialTheme.typography.labelMedium.copy(
|
||||
color = contentColor,
|
||||
@@ -635,7 +637,7 @@ private fun ToolBubble(
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = "Show Raw JSON",
|
||||
text = stringResource(R.string.chat_tool_show_raw),
|
||||
style =
|
||||
MaterialTheme.typography.labelSmall.copy(
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
@@ -660,7 +662,7 @@ private fun ToolBubble(
|
||||
if (parsedOutput != null) {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = "Show Parsed Output",
|
||||
text = stringResource(R.string.chat_tool_show_parsed),
|
||||
style =
|
||||
MaterialTheme.typography.labelSmall.copy(
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
|
||||
@@ -293,14 +293,20 @@ fun ChatScreen(
|
||||
imageVector =
|
||||
if (state.isSearchActive) Icons.Filled.Close else Icons.Filled.Search,
|
||||
contentDescription =
|
||||
if (state.isSearchActive) "Close search" else "Search",
|
||||
if (state.isSearchActive) {
|
||||
stringResource(
|
||||
R.string.chat_action_close_search,
|
||||
)
|
||||
} else {
|
||||
stringResource(R.string.chat_action_search)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
IconButton(onClick = { viewModel.createNewSession() }) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Add,
|
||||
contentDescription = "New Chat",
|
||||
contentDescription = stringResource(R.string.content_desc_new_chat),
|
||||
)
|
||||
}
|
||||
},
|
||||
@@ -335,9 +341,9 @@ fun ChatScreen(
|
||||
Text(
|
||||
text =
|
||||
if (state.connectionStatus == ConnectionStatus.RECONNECTING) {
|
||||
"Reconnecting to Hermes…"
|
||||
stringResource(R.string.chat_status_reconnecting)
|
||||
} else {
|
||||
"Disconnected from Hermes"
|
||||
stringResource(R.string.chat_status_disconnected)
|
||||
},
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
@@ -349,7 +355,7 @@ fun ChatScreen(
|
||||
contentColor = MaterialTheme.colorScheme.onErrorContainer,
|
||||
),
|
||||
) {
|
||||
Text("Reconnect")
|
||||
Text(stringResource(R.string.chat_action_reconnect))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -393,8 +399,8 @@ fun ChatScreen(
|
||||
) {
|
||||
if (state.messages.isEmpty() && !state.isLoading) {
|
||||
EmptyState(
|
||||
title = "Ready to chat",
|
||||
subtitle = "Send a message to start a conversation",
|
||||
title = stringResource(R.string.chat_empty_title),
|
||||
subtitle = stringResource(R.string.chat_empty_subtitle),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -482,7 +488,7 @@ fun ChatScreen(
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Text(
|
||||
text = "Connecting to Hermes…",
|
||||
text = stringResource(R.string.chat_status_connecting),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
@@ -512,7 +518,7 @@ fun ChatScreen(
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.KeyboardArrowDown,
|
||||
contentDescription = "Scroll to bottom",
|
||||
contentDescription = stringResource(R.string.content_desc_scroll_to_bottom),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -584,9 +590,9 @@ private fun ThinkingIndicator(thinkingText: String) {
|
||||
text =
|
||||
if (thinkingText.isNotBlank()) {
|
||||
val display = thinkingText.takeLast(100)
|
||||
"Thinking: $display…"
|
||||
stringResource(R.string.chat_thinking_param, display)
|
||||
} else {
|
||||
"Thinking…"
|
||||
stringResource(R.string.chat_thinking)
|
||||
},
|
||||
style =
|
||||
MaterialTheme.typography.bodySmall.copy(
|
||||
@@ -671,14 +677,14 @@ private fun ChatInputBar(
|
||||
items(filteredCommands) { cmd ->
|
||||
val description =
|
||||
when (cmd) {
|
||||
"/help" -> "Show help menu"
|
||||
"/status" -> "Check gateway and platform status"
|
||||
"/sessions" -> "List all chat sessions"
|
||||
"/stats" -> "Check system resource usage (stats)"
|
||||
"/system" -> "Check system resource usage (system)"
|
||||
"/new" -> "Create a new chat session"
|
||||
"/stop" -> "Interrupt the active run"
|
||||
"/interrupt" -> "Interrupt the active run"
|
||||
"/help" -> stringResource(R.string.command_help_desc)
|
||||
"/status" -> stringResource(R.string.command_status_desc)
|
||||
"/sessions" -> stringResource(R.string.command_sessions_desc)
|
||||
"/stats" -> stringResource(R.string.command_stats_desc)
|
||||
"/system" -> stringResource(R.string.command_system_desc)
|
||||
"/new" -> stringResource(R.string.command_new_desc)
|
||||
"/stop" -> stringResource(R.string.command_stop_desc)
|
||||
"/interrupt" -> stringResource(R.string.command_stop_desc)
|
||||
else -> ""
|
||||
}
|
||||
DropdownMenuItem(
|
||||
@@ -725,11 +731,11 @@ private fun ChatInputBar(
|
||||
placeholder = {
|
||||
Text(
|
||||
if (!isConnected) {
|
||||
"Not connected…"
|
||||
stringResource(R.string.chat_input_placeholder_not_connected)
|
||||
} else if (isAgentTyping) {
|
||||
"Waiting for response…"
|
||||
stringResource(R.string.chat_input_placeholder_waiting)
|
||||
} else {
|
||||
"Type a message…"
|
||||
stringResource(R.string.chat_input_placeholder_type_message)
|
||||
},
|
||||
)
|
||||
},
|
||||
@@ -759,7 +765,7 @@ private fun ChatInputBar(
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Stop,
|
||||
contentDescription = "Interrupt",
|
||||
contentDescription = stringResource(R.string.content_desc_interrupt),
|
||||
tint = MaterialTheme.colorScheme.error,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -250,7 +250,15 @@ fun FilterChipRow(
|
||||
FilterChip(
|
||||
selected = chip == selectedChip,
|
||||
onClick = { onChipSelected(chip) },
|
||||
label = { Text(chip) },
|
||||
label = {
|
||||
Text(
|
||||
if (chip == "All") {
|
||||
stringResource(R.string.skills_category_all)
|
||||
} else {
|
||||
chip
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
@@ -53,6 +54,7 @@ import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.m57.hermescontrol.R
|
||||
|
||||
@Composable
|
||||
fun ConnectScreen(
|
||||
@@ -106,7 +108,7 @@ fun ConnectScreen(
|
||||
}
|
||||
|
||||
Text(
|
||||
text = "Hermes",
|
||||
text = stringResource(R.string.notif_title),
|
||||
style =
|
||||
MaterialTheme.typography.displayMedium.copy(
|
||||
fontWeight = FontWeight.Bold,
|
||||
@@ -114,7 +116,7 @@ fun ConnectScreen(
|
||||
),
|
||||
)
|
||||
Text(
|
||||
text = "AI Agent Control",
|
||||
text = stringResource(R.string.nav_drawer_subtitle),
|
||||
style =
|
||||
MaterialTheme.typography.bodyLarge.copy(
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
@@ -134,9 +136,9 @@ fun ConnectScreen(
|
||||
Text(
|
||||
text =
|
||||
if (state.selectedProfile != null) {
|
||||
"Profile: ${state.selectedProfile!!.name}"
|
||||
stringResource(R.string.connect_selected_profile, state.selectedProfile!!.name)
|
||||
} else {
|
||||
"Select Saved Profile"
|
||||
stringResource(R.string.connect_action_select_profile)
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -146,7 +148,7 @@ fun ConnectScreen(
|
||||
modifier = Modifier.fillMaxWidth(0.85f),
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
text = { Text("None (Standalone)") },
|
||||
text = { Text(stringResource(R.string.connect_profile_none)) },
|
||||
onClick = {
|
||||
viewModel.selectProfile(null)
|
||||
profilesExpanded = false
|
||||
@@ -171,7 +173,7 @@ fun ConnectScreen(
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
Text(
|
||||
text = "Pairing string",
|
||||
text = stringResource(R.string.connect_pairing_string),
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
)
|
||||
@@ -182,7 +184,7 @@ fun ConnectScreen(
|
||||
pairingString = it
|
||||
if (it.isNotBlank()) viewModel.onPairingString(it)
|
||||
},
|
||||
placeholder = { Text("Paste hermes://… or Base64 config") },
|
||||
placeholder = { Text(stringResource(R.string.connect_placeholder_pairing)) },
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
@@ -215,7 +217,7 @@ fun ConnectScreen(
|
||||
onCheckedChange = viewModel::onSaveProfileChange,
|
||||
)
|
||||
Text(
|
||||
text = "Save connection profile",
|
||||
text = stringResource(R.string.connect_save_profile),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
)
|
||||
@@ -225,8 +227,8 @@ fun ConnectScreen(
|
||||
OutlinedTextField(
|
||||
value = state.profileName,
|
||||
onValueChange = viewModel::onProfileNameChange,
|
||||
label = { Text("Profile Name") },
|
||||
placeholder = { Text("e.g. Local Hermit, Remote Server") },
|
||||
label = { Text(stringResource(R.string.connect_profile_name)) },
|
||||
placeholder = { Text(stringResource(R.string.connect_placeholder_profile_name)) },
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
@@ -247,7 +249,7 @@ fun ConnectScreen(
|
||||
strokeWidth = 2.dp,
|
||||
)
|
||||
} else {
|
||||
Text("Connect", style = MaterialTheme.typography.labelLarge)
|
||||
Text(stringResource(R.string.action_connect), style = MaterialTheme.typography.labelLarge)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,7 +258,12 @@ fun ConnectScreen(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Text(
|
||||
text = if (showManualFields) "Hide manual entry" else "Enter manually instead",
|
||||
text =
|
||||
if (showManualFields) {
|
||||
stringResource(R.string.connect_action_hide_manual)
|
||||
} else {
|
||||
stringResource(R.string.connect_action_show_manual)
|
||||
},
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
@@ -271,8 +278,8 @@ fun ConnectScreen(
|
||||
OutlinedTextField(
|
||||
value = state.token,
|
||||
onValueChange = viewModel::onTokenChange,
|
||||
label = { Text("Auth Token") },
|
||||
placeholder = { Text("API token") },
|
||||
label = { Text(stringResource(R.string.settings_field_token)) },
|
||||
placeholder = { Text(stringResource(R.string.connect_placeholder_token)) },
|
||||
singleLine = true,
|
||||
visualTransformation =
|
||||
if (passwordVisible) {
|
||||
@@ -290,7 +297,11 @@ fun ConnectScreen(
|
||||
Icons.Filled.VisibilityOff
|
||||
},
|
||||
contentDescription =
|
||||
if (passwordVisible) "Hide token" else "Show token",
|
||||
if (passwordVisible) {
|
||||
stringResource(R.string.content_desc_hide_token)
|
||||
} else {
|
||||
stringResource(R.string.content_desc_show_token)
|
||||
},
|
||||
)
|
||||
}
|
||||
},
|
||||
@@ -299,16 +310,16 @@ fun ConnectScreen(
|
||||
OutlinedTextField(
|
||||
value = state.host,
|
||||
onValueChange = viewModel::onHostChange,
|
||||
label = { Text("Host") },
|
||||
placeholder = { Text("127.0.0.1") },
|
||||
label = { Text(stringResource(R.string.settings_field_host)) },
|
||||
placeholder = { Text(stringResource(R.string.connect_placeholder_host)) },
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
OutlinedTextField(
|
||||
value = state.port,
|
||||
onValueChange = viewModel::onPortChange,
|
||||
label = { Text("Port") },
|
||||
placeholder = { Text("9119") },
|
||||
label = { Text(stringResource(R.string.settings_field_port)) },
|
||||
placeholder = { Text(stringResource(R.string.connect_placeholder_port)) },
|
||||
singleLine = true,
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
|
||||
@@ -39,10 +39,12 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.m57.hermescontrol.R
|
||||
import com.m57.hermescontrol.data.model.KanbanTask
|
||||
import com.m57.hermescontrol.ui.common.ErrorState
|
||||
import com.m57.hermescontrol.ui.common.HermesScaffold
|
||||
@@ -73,7 +75,7 @@ fun KanbanScreen(
|
||||
}
|
||||
|
||||
HermesScaffold(
|
||||
title = { Text("Kanban Board") },
|
||||
title = { Text(stringResource(R.string.kanban_board_title)) },
|
||||
onOpenDrawer = onOpenDrawer,
|
||||
isRefreshing = state.isLoading,
|
||||
onRefresh = { viewModel.loadBoards() },
|
||||
@@ -99,10 +101,12 @@ fun KanbanScreen(
|
||||
Text(
|
||||
text = state.errorMessage ?: "",
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
modifier = Modifier.padding(16.dp),
|
||||
modifier = Modifier.padding(paddingValues),
|
||||
)
|
||||
} else {
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize().padding(paddingValues),
|
||||
) {
|
||||
// Board selector tab row
|
||||
if (state.boards.isNotEmpty()) {
|
||||
PrimaryScrollableTabRow(
|
||||
@@ -121,11 +125,11 @@ fun KanbanScreen(
|
||||
|
||||
if (state.selectedBoard == null) {
|
||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
Text("No boards found.")
|
||||
Text(stringResource(R.string.kanban_no_boards))
|
||||
}
|
||||
} else if (state.columns.isEmpty()) {
|
||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
Text("No columns found for this board.")
|
||||
Text(stringResource(R.string.kanban_no_columns))
|
||||
}
|
||||
} else {
|
||||
LazyRow(
|
||||
@@ -219,7 +223,10 @@ fun TaskCard(
|
||||
) {
|
||||
if (onMoveLeft != null) {
|
||||
IconButton(onClick = onMoveLeft) {
|
||||
Icon(imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Move Left")
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||
contentDescription = stringResource(R.string.content_desc_move_left),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Spacer(modifier = Modifier.size(48.dp))
|
||||
@@ -227,7 +234,10 @@ fun TaskCard(
|
||||
|
||||
if (onMoveRight != null) {
|
||||
IconButton(onClick = onMoveRight) {
|
||||
Icon(imageVector = Icons.Filled.ArrowForward, contentDescription = "Move Right")
|
||||
Icon(
|
||||
imageVector = Icons.Filled.ArrowForward,
|
||||
contentDescription = stringResource(R.string.content_desc_move_right),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Spacer(modifier = Modifier.size(48.dp))
|
||||
@@ -248,20 +258,20 @@ fun AddTaskDialog(
|
||||
|
||||
androidx.compose.material3.AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text("Add New Task") },
|
||||
title = { Text(stringResource(R.string.kanban_add_new_task)) },
|
||||
text = {
|
||||
Column {
|
||||
OutlinedTextField(
|
||||
value = title,
|
||||
onValueChange = { title = it },
|
||||
label = { Text("Task Title") },
|
||||
label = { Text(stringResource(R.string.kanban_task_title)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
OutlinedTextField(
|
||||
value = desc,
|
||||
onValueChange = { desc = it },
|
||||
label = { Text("Task Description") },
|
||||
label = { Text(stringResource(R.string.kanban_task_desc)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
@@ -271,12 +281,12 @@ fun AddTaskDialog(
|
||||
onClick = { if (title.isNotBlank()) onConfirm(title, desc.ifBlank { null }) },
|
||||
enabled = title.isNotBlank(),
|
||||
) {
|
||||
Text("Add")
|
||||
Text(stringResource(R.string.action_add))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
Button(onClick = onDismiss) {
|
||||
Text("Cancel")
|
||||
Text(stringResource(R.string.action_cancel))
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -53,6 +53,7 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
@@ -61,6 +62,7 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.m57.hermescontrol.BuildConfig
|
||||
import com.m57.hermescontrol.R
|
||||
import com.m57.hermescontrol.theme.ThemePreference
|
||||
import com.m57.hermescontrol.ui.common.HermesScaffold
|
||||
|
||||
@@ -77,7 +79,7 @@ fun SettingsScreen(
|
||||
BackHandler(onBack = onBack)
|
||||
|
||||
HermesScaffold(
|
||||
title = { Text("Settings") },
|
||||
title = { Text(stringResource(R.string.screen_settings)) },
|
||||
showBack = true,
|
||||
onBack = onBack,
|
||||
) {
|
||||
@@ -90,12 +92,12 @@ fun SettingsScreen(
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
) {
|
||||
// Connection section
|
||||
SectionCard(title = "Connection") {
|
||||
SectionCard(title = stringResource(R.string.settings_sec_connection)) {
|
||||
// Connection Profiles management in Settings
|
||||
if (state.profiles.isNotEmpty()) {
|
||||
var profilesExpanded by remember { mutableStateOf(false) }
|
||||
Text(
|
||||
text = "Connection Profile",
|
||||
text = stringResource(R.string.settings_item_profile),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
@@ -105,7 +107,7 @@ fun SettingsScreen(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
val activeProfile = state.profiles.firstOrNull { it.id == state.selectedProfileId }
|
||||
Text(text = activeProfile?.name ?: "Standalone / Default")
|
||||
Text(text = activeProfile?.name ?: stringResource(R.string.settings_profile_default))
|
||||
}
|
||||
DropdownMenu(
|
||||
expanded = profilesExpanded,
|
||||
@@ -113,7 +115,7 @@ fun SettingsScreen(
|
||||
modifier = Modifier.fillMaxWidth(0.85f),
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
text = { Text("None (Standalone / Default)") },
|
||||
text = { Text(stringResource(R.string.settings_profile_none)) },
|
||||
onClick = {
|
||||
viewModel.selectProfile(null)
|
||||
profilesExpanded = false
|
||||
@@ -136,7 +138,10 @@ fun SettingsScreen(
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Close,
|
||||
contentDescription = "Delete Profile",
|
||||
contentDescription =
|
||||
stringResource(
|
||||
R.string.content_desc_delete_profile,
|
||||
),
|
||||
tint = MaterialTheme.colorScheme.error,
|
||||
)
|
||||
}
|
||||
@@ -155,7 +160,7 @@ fun SettingsScreen(
|
||||
OutlinedTextField(
|
||||
value = state.renameProfileName,
|
||||
onValueChange = viewModel::onRenameProfileNameChange,
|
||||
label = { Text("Rename Profile") },
|
||||
label = { Text(stringResource(R.string.settings_action_rename_profile)) },
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
trailingIcon = {
|
||||
@@ -163,7 +168,7 @@ fun SettingsScreen(
|
||||
onClick = viewModel::renameProfile,
|
||||
modifier = Modifier.padding(end = 4.dp),
|
||||
) {
|
||||
Text("Rename")
|
||||
Text(stringResource(R.string.action_rename))
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -174,7 +179,7 @@ fun SettingsScreen(
|
||||
OutlinedTextField(
|
||||
value = state.host,
|
||||
onValueChange = viewModel::onHostChange,
|
||||
label = { Text("Host") },
|
||||
label = { Text(stringResource(R.string.settings_field_host)) },
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors =
|
||||
@@ -189,7 +194,7 @@ fun SettingsScreen(
|
||||
OutlinedTextField(
|
||||
value = state.port,
|
||||
onValueChange = viewModel::onPortChange,
|
||||
label = { Text("Port") },
|
||||
label = { Text(stringResource(R.string.settings_field_port)) },
|
||||
singleLine = true,
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
@@ -205,7 +210,7 @@ fun SettingsScreen(
|
||||
OutlinedTextField(
|
||||
value = state.token,
|
||||
onValueChange = viewModel::onTokenChange,
|
||||
label = { Text("Auth Token") },
|
||||
label = { Text(stringResource(R.string.settings_field_token)) },
|
||||
singleLine = true,
|
||||
visualTransformation =
|
||||
if (passwordVisible) {
|
||||
@@ -224,9 +229,9 @@ fun SettingsScreen(
|
||||
},
|
||||
contentDescription =
|
||||
if (passwordVisible) {
|
||||
"Hide token"
|
||||
stringResource(R.string.content_desc_hide_token)
|
||||
} else {
|
||||
"Show token"
|
||||
stringResource(R.string.content_desc_show_token)
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -253,12 +258,12 @@ fun SettingsScreen(
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Text("Clear Token")
|
||||
Text(stringResource(R.string.settings_action_clear_token))
|
||||
}
|
||||
}
|
||||
|
||||
// Behavior section
|
||||
SectionCard(title = "Behavior") {
|
||||
SectionCard(title = stringResource(R.string.settings_sec_behavior)) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
@@ -266,11 +271,11 @@ fun SettingsScreen(
|
||||
) {
|
||||
Column {
|
||||
Text(
|
||||
text = "Auto-Reconnect",
|
||||
text = stringResource(R.string.settings_item_auto_reconnect),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
)
|
||||
Text(
|
||||
text = "Automatically reconnect on disconnect",
|
||||
text = stringResource(R.string.settings_desc_auto_reconnect),
|
||||
style =
|
||||
MaterialTheme.typography.bodySmall.copy(
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
@@ -290,9 +295,9 @@ fun SettingsScreen(
|
||||
}
|
||||
|
||||
// Appearance section
|
||||
SectionCard(title = "Appearance") {
|
||||
SectionCard(title = stringResource(R.string.settings_sec_appearance)) {
|
||||
Text(
|
||||
text = "Theme",
|
||||
text = stringResource(R.string.settings_item_theme),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
@@ -308,9 +313,11 @@ fun SettingsScreen(
|
||||
),
|
||||
) {
|
||||
Text(
|
||||
pref.name
|
||||
.lowercase()
|
||||
.replaceFirstChar { it.uppercase() },
|
||||
when (pref) {
|
||||
ThemePreference.SYSTEM -> stringResource(R.string.theme_system)
|
||||
ThemePreference.LIGHT -> stringResource(R.string.theme_light)
|
||||
ThemePreference.DARK -> stringResource(R.string.theme_dark)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -318,7 +325,7 @@ fun SettingsScreen(
|
||||
}
|
||||
|
||||
// Chat section
|
||||
SectionCard(title = "Chat") {
|
||||
SectionCard(title = stringResource(R.string.settings_sec_chat)) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
@@ -326,11 +333,11 @@ fun SettingsScreen(
|
||||
) {
|
||||
Column {
|
||||
Text(
|
||||
text = "Typing Effect",
|
||||
text = stringResource(R.string.settings_item_typing_effect),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
)
|
||||
Text(
|
||||
text = "Animate new messages word by word",
|
||||
text = stringResource(R.string.settings_desc_typing_effect),
|
||||
style =
|
||||
MaterialTheme.typography.bodySmall.copy(
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
@@ -352,7 +359,7 @@ fun SettingsScreen(
|
||||
AnimatedVisibility(visible = state.typingEffectEnabled) {
|
||||
Column(modifier = Modifier.padding(top = 12.dp)) {
|
||||
Text(
|
||||
text = "Delay: ${state.typingEffectDelayMs} ms",
|
||||
text = stringResource(R.string.settings_item_typing_delay, state.typingEffectDelayMs),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
@@ -368,14 +375,14 @@ fun SettingsScreen(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Text(
|
||||
text = "10 ms",
|
||||
text = stringResource(R.string.settings_delay_10ms),
|
||||
style =
|
||||
MaterialTheme.typography.labelSmall.copy(
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
)
|
||||
Text(
|
||||
text = "100 ms",
|
||||
text = stringResource(R.string.settings_delay_100ms),
|
||||
style =
|
||||
MaterialTheme.typography.labelSmall.copy(
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
@@ -387,15 +394,16 @@ fun SettingsScreen(
|
||||
}
|
||||
|
||||
// Navigation Bar section
|
||||
SectionCard(title = "Navigation Bar") {
|
||||
SectionCard(title = stringResource(R.string.settings_sec_nav_bar)) {
|
||||
Text(
|
||||
text = "Bottom bar items (${state.selectedNavItems.size}/5)",
|
||||
text = stringResource(R.string.settings_item_nav_items, state.selectedNavItems.size),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
state.selectedNavItems.forEachIndexed { index, name ->
|
||||
val label = viewModel.availableNavItems.firstOrNull { it.first == name }?.second ?: name
|
||||
val labelRes = viewModel.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,
|
||||
@@ -417,7 +425,10 @@ fun SettingsScreen(
|
||||
},
|
||||
enabled = index > 0,
|
||||
) {
|
||||
Icon(Icons.Filled.KeyboardArrowUp, contentDescription = "Move up")
|
||||
Icon(
|
||||
Icons.Filled.KeyboardArrowUp,
|
||||
contentDescription = stringResource(R.string.content_desc_move_up),
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
onClick = {
|
||||
@@ -429,10 +440,16 @@ fun SettingsScreen(
|
||||
},
|
||||
enabled = index < state.selectedNavItems.lastIndex,
|
||||
) {
|
||||
Icon(Icons.Filled.KeyboardArrowDown, contentDescription = "Move down")
|
||||
Icon(
|
||||
Icons.Filled.KeyboardArrowDown,
|
||||
contentDescription = stringResource(R.string.content_desc_move_down),
|
||||
)
|
||||
}
|
||||
IconButton(onClick = { viewModel.removeNavItem(name) }) {
|
||||
Icon(Icons.Filled.Close, contentDescription = "Remove $label")
|
||||
Icon(
|
||||
Icons.Filled.Close,
|
||||
contentDescription = stringResource(R.string.content_desc_remove_item, label),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -446,12 +463,12 @@ fun SettingsScreen(
|
||||
OutlinedButton(onClick = { expanded = true }) {
|
||||
Icon(Icons.Filled.Add, contentDescription = null)
|
||||
Spacer(Modifier.size(4.dp))
|
||||
Text("Add Item")
|
||||
Text(stringResource(R.string.settings_action_add_item))
|
||||
}
|
||||
DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
|
||||
available.forEach { (name, label) ->
|
||||
available.forEach { (name, labelRes) ->
|
||||
DropdownMenuItem(
|
||||
text = { Text(label) },
|
||||
text = { Text(stringResource(labelRes)) },
|
||||
onClick = {
|
||||
viewModel.addNavItem(name)
|
||||
expanded = false
|
||||
@@ -463,7 +480,7 @@ fun SettingsScreen(
|
||||
}
|
||||
} else {
|
||||
Text(
|
||||
text = "Maximum of 5 items",
|
||||
text = stringResource(R.string.settings_nav_max_reached),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
@@ -505,7 +522,7 @@ fun SettingsScreen(
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
Text(
|
||||
text = "✅ Settings saved",
|
||||
text = stringResource(R.string.settings_save_success),
|
||||
style =
|
||||
MaterialTheme.typography.bodyMedium.copy(
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
@@ -529,7 +546,7 @@ fun SettingsScreen(
|
||||
strokeWidth = 2.dp,
|
||||
)
|
||||
} else {
|
||||
Text("Test Connection")
|
||||
Text(stringResource(R.string.settings_action_test_connection))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -541,17 +558,17 @@ fun SettingsScreen(
|
||||
containerColor = MaterialTheme.colorScheme.primary,
|
||||
),
|
||||
) {
|
||||
Text("Save")
|
||||
Text(stringResource(R.string.action_save))
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
// ── About section ─────────────────────────────────────────
|
||||
SectionCard(title = "About") {
|
||||
SectionCard(title = stringResource(R.string.settings_sec_about)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
text = "⚡ Hermes Control",
|
||||
text = stringResource(R.string.settings_about_app_name),
|
||||
style =
|
||||
MaterialTheme.typography.titleMedium.copy(
|
||||
fontWeight = FontWeight.Bold,
|
||||
@@ -563,13 +580,21 @@ fun SettingsScreen(
|
||||
HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
InfoRow(label = "Version", value = "v${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})")
|
||||
InfoRow(
|
||||
label = "Build",
|
||||
value = if (BuildConfig.DEBUG) "Debug" else "Release",
|
||||
label = stringResource(R.string.settings_about_version),
|
||||
value = "v${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})",
|
||||
)
|
||||
InfoRow(
|
||||
label = stringResource(R.string.settings_about_build),
|
||||
value =
|
||||
if (BuildConfig.DEBUG) {
|
||||
stringResource(R.string.settings_about_debug)
|
||||
} else {
|
||||
stringResource(R.string.settings_about_release)
|
||||
},
|
||||
)
|
||||
if (BuildConfig.GIT_SHA.isNotBlank()) {
|
||||
InfoRow(label = "Commit", value = BuildConfig.GIT_SHA)
|
||||
InfoRow(label = stringResource(R.string.settings_about_commit), value = BuildConfig.GIT_SHA)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.m57.hermescontrol.ui.settings
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.m57.hermescontrol.R
|
||||
import com.m57.hermescontrol.data.local.AuthManager
|
||||
import com.m57.hermescontrol.data.remote.ApiClient
|
||||
import com.m57.hermescontrol.data.remote.NetworkResult
|
||||
@@ -129,28 +130,28 @@ class SettingsViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
/** All screens available for bottom-nav selection (name → display label). */
|
||||
val availableNavItems: List<Pair<String, String>> =
|
||||
val availableNavItems: List<Pair<String, Int>> =
|
||||
listOf(
|
||||
"ChatScreen" to "Chat",
|
||||
"SkillsScreen" to "Skills",
|
||||
"CronJobsScreen" to "Cron Jobs",
|
||||
"SystemScreen" to "System",
|
||||
"SettingsScreen" to "Settings",
|
||||
"ProfilesScreen" to "Profiles",
|
||||
"WebhooksScreen" to "Webhooks",
|
||||
"GatewayScreen" to "Gateway",
|
||||
"HistoryScreen" to "History",
|
||||
"ToolsetsScreen" to "Toolsets",
|
||||
"PluginsScreen" to "Plugins",
|
||||
"ConfigScreen" to "Config",
|
||||
"McpServersScreen" to "MCP Servers",
|
||||
"ModelScreen" to "Models",
|
||||
"PairingScreen" to "Pairing",
|
||||
"KeysScreen" to "Keys",
|
||||
"ChannelsScreen" to "Channels",
|
||||
"LogsScreen" to "Logs",
|
||||
"KanbanScreen" to "Kanban",
|
||||
"AchievementsScreen" to "Achievements",
|
||||
"ChatScreen" to R.string.screen_chat,
|
||||
"SkillsScreen" to R.string.screen_skills,
|
||||
"CronJobsScreen" to R.string.screen_cron,
|
||||
"SystemScreen" to R.string.screen_system,
|
||||
"SettingsScreen" to R.string.screen_settings,
|
||||
"ProfilesScreen" to R.string.screen_profiles,
|
||||
"WebhooksScreen" to R.string.screen_webhooks,
|
||||
"GatewayScreen" to R.string.screen_gateway,
|
||||
"HistoryScreen" to R.string.screen_history,
|
||||
"ToolsetsScreen" to R.string.screen_toolsets,
|
||||
"PluginsScreen" to R.string.screen_plugins,
|
||||
"ConfigScreen" to R.string.screen_config,
|
||||
"McpServersScreen" to R.string.screen_mcp_servers,
|
||||
"ModelScreen" to R.string.screen_models,
|
||||
"PairingScreen" to R.string.screen_pairing,
|
||||
"KeysScreen" to R.string.screen_keys,
|
||||
"ChannelsScreen" to R.string.screen_channels,
|
||||
"LogsScreen" to R.string.screen_logs,
|
||||
"KanbanScreen" to R.string.screen_kanban,
|
||||
"AchievementsScreen" to R.string.screen_achievements,
|
||||
)
|
||||
|
||||
/** Add a nav item to the bottom bar (max 5). Auto-saves. */
|
||||
|
||||
@@ -35,6 +35,7 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
@@ -43,6 +44,7 @@ import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.m57.hermescontrol.R
|
||||
import com.m57.hermescontrol.theme.LocalSpacing
|
||||
import com.m57.hermescontrol.ui.common.EmptyState
|
||||
import com.m57.hermescontrol.ui.common.ErrorState
|
||||
@@ -117,7 +119,7 @@ fun SkillsScreen(
|
||||
}
|
||||
|
||||
HermesScaffold(
|
||||
title = { Text("Skills") },
|
||||
title = { Text(stringResource(R.string.screen_skills)) },
|
||||
onOpenDrawer = onOpenDrawer,
|
||||
isRefreshing = state.isLoading,
|
||||
onRefresh = { viewModel.loadSkills() },
|
||||
@@ -129,15 +131,15 @@ fun SkillsScreen(
|
||||
|
||||
state.errorMessage != null -> {
|
||||
ErrorState(
|
||||
message = state.errorMessage ?: "Unknown error",
|
||||
message = state.errorMessage ?: stringResource(R.string.error_unknown),
|
||||
onRetry = { viewModel.loadSkills() },
|
||||
)
|
||||
}
|
||||
|
||||
state.skills.isEmpty() -> {
|
||||
EmptyState(
|
||||
title = "No skills found",
|
||||
subtitle = "Skills loaded from Hermes will appear here.",
|
||||
title = stringResource(R.string.skills_no_skills),
|
||||
subtitle = stringResource(R.string.skills_no_skills_desc),
|
||||
icon = Icons.Filled.Extension,
|
||||
)
|
||||
}
|
||||
@@ -152,7 +154,7 @@ fun SkillsScreen(
|
||||
SearchBar(
|
||||
query = query,
|
||||
onQueryChange = { query = it },
|
||||
placeholder = "Search skills…",
|
||||
placeholder = stringResource(R.string.skills_search_placeholder),
|
||||
)
|
||||
}
|
||||
item {
|
||||
@@ -164,7 +166,7 @@ fun SkillsScreen(
|
||||
verticalArrangement = Arrangement.spacedBy(spacing.xs),
|
||||
) {
|
||||
Text(
|
||||
text = "Status",
|
||||
text = stringResource(R.string.skills_filter_status),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
@@ -175,7 +177,16 @@ fun SkillsScreen(
|
||||
FilterChip(
|
||||
selected = selectedStatus == status,
|
||||
onClick = { selectedStatus = status },
|
||||
label = { Text(status) },
|
||||
label = {
|
||||
Text(
|
||||
when (status) {
|
||||
"All Statuses" -> stringResource(R.string.skills_status_all)
|
||||
"Enabled" -> stringResource(R.string.skills_status_enabled)
|
||||
"Disabled" -> stringResource(R.string.skills_status_disabled)
|
||||
else -> status
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -191,7 +202,7 @@ fun SkillsScreen(
|
||||
verticalArrangement = Arrangement.spacedBy(spacing.xs),
|
||||
) {
|
||||
Text(
|
||||
text = "Category",
|
||||
text = stringResource(R.string.skills_filter_category),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(horizontal = spacing.md),
|
||||
@@ -218,14 +229,14 @@ fun SkillsScreen(
|
||||
verticalArrangement = Arrangement.spacedBy(spacing.xs),
|
||||
) {
|
||||
Text(
|
||||
text = "No matching skills",
|
||||
text = stringResource(R.string.skills_no_matching),
|
||||
style =
|
||||
MaterialTheme.typography.titleMedium.copy(
|
||||
fontWeight = FontWeight.Bold,
|
||||
),
|
||||
)
|
||||
Text(
|
||||
text = "Try adjusting your search or filters.",
|
||||
text = stringResource(R.string.skills_no_matching_desc),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
@@ -293,7 +304,7 @@ fun SkillsScreen(
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Edit,
|
||||
contentDescription = "Edit Skill File",
|
||||
contentDescription = stringResource(R.string.content_desc_edit_skill),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
@@ -373,7 +384,7 @@ fun SkillEditorDialog(
|
||||
),
|
||||
) {
|
||||
HermesScaffold(
|
||||
title = { Text("Edit: $skillName") },
|
||||
title = { Text(stringResource(R.string.skills_edit_title, skillName)) },
|
||||
onBack = {
|
||||
if (hasChanges) {
|
||||
showDiscardConfirm = true
|
||||
@@ -397,7 +408,7 @@ fun SkillEditorDialog(
|
||||
} else {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Save,
|
||||
contentDescription = "Save Changes",
|
||||
contentDescription = stringResource(R.string.content_desc_save_changes),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -429,7 +440,7 @@ fun SkillEditorDialog(
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
),
|
||||
placeholder = {
|
||||
Text("Enter skill content...")
|
||||
Text(stringResource(R.string.skills_content_placeholder))
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -442,8 +453,8 @@ fun SkillEditorDialog(
|
||||
if (showDiscardConfirm) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { showDiscardConfirm = false },
|
||||
title = { Text("Discard changes?") },
|
||||
text = { Text("You have unsaved changes. Are you sure you want to discard them?") },
|
||||
title = { Text(stringResource(R.string.skills_discard_title)) },
|
||||
text = { Text(stringResource(R.string.skills_discard_message)) },
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
@@ -451,12 +462,12 @@ fun SkillEditorDialog(
|
||||
onDismiss()
|
||||
},
|
||||
) {
|
||||
Text("Discard")
|
||||
Text(stringResource(R.string.action_discard))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { showDiscardConfirm = false }) {
|
||||
Text("Cancel")
|
||||
Text(stringResource(R.string.action_cancel))
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user