feat(keys): overhaul Keys screen UI and UX (#707)

* feat(keys): overhaul Keys screen UI and UX

Fix card padding alignment, add delete confirmation dialog, add FAB with AddKeyDialog, support copying revealed values, add status badges, and pin restart banner.

* fix(keys): fix scaffold actions parameter and missing string resource

Resolve compilation errors in KeysScreen by removing floatingActionButton from HermesScaffold and adding missing keys_action_open_url resource.

* feat(keys): replace dropdown headers with horizontal category filter chips

Add CategoryFilterBar featuring horizontal FilterChips for quick category filtering (All, LLM Providers, Tool API Keys, Messaging Platforms, Agent Settings) with total key counts.

* style(keys): enhance key card padding, spacing, borders, and surface contrast

Use surfaceContainerLow with subtle outlineVariant border and 16dp rounded corners for high contrast cards, and set 12dp list spacing between cards.

* fix(keys): wrap key title and status badge in FlowRow to prevent layout overflow on long key names

Prevent status badge from squishing or overflowing when displaying long API key names by using a FlowRow that wraps gracefully.

* fix(keys): add file-level OptIn for ExperimentalLayoutApi

Fix compilation error by adding @file:OptIn(ExperimentalLayoutApi::class, ExperimentalMaterial3Api::class) to KeysScreen.kt.

* fix(keys): fully qualify class names in file-level OptIn annotation

Fix unresolved reference build error by fully qualifying ExperimentalMaterial3Api and ExperimentalLayoutApi in @file:OptIn before package statement.

* fix(keys): remove invalid verticalAlignment parameter from FlowRow

* fix(keys): optimize long API key name readability and wrapping

Insert zero-width spaces after underscores to ensure clean word-boundary wrapping, add adaptive typography (titleSmall with 20sp line height for long names), and stack StatusBadge cleanly below the title.

* feat(keys): make API key names and values single-line and horizontally scrollable

Enforce singleLine=true / maxLines=1 with horizontalScroll(rememberScrollState()) for both key names and revealed key values so cards remain compact with uniform heights.

* feat(keys): keep single-line scrollable key values and clean multi-line key names

Keep secret key values single-line and horizontally scrollable, while restoring clean multi-line word-boundary breaking (at underscores) for key names.

* style(keys): format KeysScreen.kt with ktlint
This commit is contained in:
M57 authored and GitHub committed 2026-07-24 02:16:22 +03:00
1 parent 8d3d4b83f3
commit 9089b4f8ac
3 files changed
+626 -278

No files matched your search

File diff suppressed because it is too large. Load diff
@@ -32,6 +32,8 @@ data class KeysUiState(
val newKeyName: String = "",
val newKeyValue: String = "",
val isAddingKey: Boolean = false,
val showAddDialog: Boolean = false,
val deleteTargetKey: String? = null,
val deletingKeys: Set<String> = emptySet(),
val keysChanged: Boolean = false,
val isRestartingGateway: Boolean = false,
@@ -128,6 +130,20 @@ class KeysViewModel :
}
}
fun openAddDialog() {
_uiState.update { it.copy(showAddDialog = true) }
}
fun dismissAddDialog() {
_uiState.update {
it.copy(
showAddDialog = false,
newKeyName = "",
newKeyValue = "",
)
}
}
fun setNewKeyName(name: String) {
_uiState.update { it.copy(newKeyName = name) }
}
@@ -155,6 +171,7 @@ class KeysViewModel :
_uiState.update {
it.copy(
isAddingKey = false,
showAddDialog = false,
newKeyName = "",
newKeyValue = "",
keysChanged = true,
@@ -176,7 +193,21 @@ class KeysViewModel :
}
}
fun deleteKey(key: String) {
fun requestDeleteKey(key: String) {
_uiState.update { it.copy(deleteTargetKey = key) }
}
fun dismissDeleteDialog() {
_uiState.update { it.copy(deleteTargetKey = null) }
}
fun confirmDeleteKey() {
val key = _uiState.value.deleteTargetKey ?: return
_uiState.update { it.copy(deleteTargetKey = null) }
deleteKey(key)
}
private fun deleteKey(key: String) {
_uiState.update { it.copy(deletingKeys = it.deletingKeys + key) }
viewModelScope.launch {
val result =
@@ -260,6 +291,10 @@ class KeysViewModel :
}
}
fun showToast(message: String) {
_uiState.update { it.copy(toastMessage = message) }
}
fun restartGateway() {
_uiState.update { it.copy(isRestartingGateway = true) }
viewModelScope.launch {