Fix gated close codes, run ticket refresh on Dispatchers.IO, prevent scope race on startup, and auto-format files.
This commit is contained in:
12 files changed
+118
-24
No files matched your search
@@ -301,6 +301,7 @@ class ChannelsViewModel :
|
||||
onboardingExpiresIn = formatExpiry(setup?.expiresAt),
|
||||
)
|
||||
}
|
||||
|
||||
is com.m57.hermescontrol.data.remote.NetworkResult.Failure -> {
|
||||
it.copy(
|
||||
onboardingPhase = OnboardingPhase.IDLE,
|
||||
@@ -350,6 +351,7 @@ class ChannelsViewModel :
|
||||
}
|
||||
_uiState.update { it.copy(onboardingError = null) }
|
||||
}
|
||||
|
||||
is com.m57.hermescontrol.data.remote.NetworkResult.Failure -> {
|
||||
val expired = isOnboardingExpired(_uiState.value.onboardingSetup)
|
||||
if (expired) {
|
||||
@@ -470,6 +472,7 @@ class ChannelsViewModel :
|
||||
loadPlatforms()
|
||||
}
|
||||
}
|
||||
|
||||
is com.m57.hermescontrol.data.remote.NetworkResult.Failure -> {
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
@@ -520,7 +523,9 @@ private fun formatExpiry(expiresAt: String?): String {
|
||||
val ms =
|
||||
try {
|
||||
isoTimestampParser
|
||||
.parse(expiresAt)?.time?.minus(System.currentTimeMillis())
|
||||
.parse(expiresAt)
|
||||
?.time
|
||||
?.minus(System.currentTimeMillis())
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
@@ -537,7 +542,8 @@ private fun isOnboardingExpired(setup: com.m57.hermescontrol.data.model.Telegram
|
||||
val ms =
|
||||
try {
|
||||
isoTimestampParser
|
||||
.parse(expiresAt)?.time
|
||||
.parse(expiresAt)
|
||||
?.time
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
|
||||
+3
@@ -186,6 +186,7 @@ internal fun TelegramOnboardingDialog(
|
||||
Text("Save and restart")
|
||||
}
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
},
|
||||
@@ -194,9 +195,11 @@ internal fun TelegramOnboardingDialog(
|
||||
OnboardingPhase.READY -> {
|
||||
TextButton(onClick = onDismiss) { Text("Cancel") }
|
||||
}
|
||||
|
||||
OnboardingPhase.WAITING -> {
|
||||
TextButton(onClick = onDismiss) { Text("Cancel") }
|
||||
}
|
||||
|
||||
OnboardingPhase.IDLE, OnboardingPhase.STARTING, OnboardingPhase.APPLYING -> {}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -54,7 +54,9 @@ object ChatWsEventReducer {
|
||||
is WsEvent.MessageToken -> onMessageToken(state, streamingState, event)
|
||||
|
||||
is WsEvent.ThinkingDelta -> onThinkingDelta(state, streamingState, event)
|
||||
|
||||
is WsEvent.ReasoningDelta -> onReasoningDelta(state, streamingState, event)
|
||||
|
||||
is WsEvent.ReasoningAvailable -> onReasoningAvailable(state, streamingState, event)
|
||||
|
||||
is WsEvent.MessageComplete -> onMessageComplete(state, streamingState, event)
|
||||
@@ -86,6 +88,7 @@ object ChatWsEventReducer {
|
||||
|
||||
// SudoRequest / SecretRequest are handled by the ViewModel (issue #524)
|
||||
is WsEvent.SudoRequest -> ReducerResult(state = state, streamingState = streamingState)
|
||||
|
||||
is WsEvent.SecretRequest -> ReducerResult(state = state, streamingState = streamingState)
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,10 @@ class KeysViewModel :
|
||||
|
||||
private fun buildCategoryList(
|
||||
envVars: Map<String, EnvVarConfig>,
|
||||
expandedCategories: Set<String> = _uiState.value.categories.map { it.name }.toSet(),
|
||||
expandedCategories: Set<String> =
|
||||
_uiState.value.categories
|
||||
.map { it.name }
|
||||
.toSet(),
|
||||
): List<CategorySection> {
|
||||
// Group by category, uncategorized go to "Other"
|
||||
val grouped = mutableMapOf<String, MutableMap<String, EnvVarConfig>>()
|
||||
|
||||
@@ -119,6 +119,7 @@ fun McpServersScreen(
|
||||
state.isLoading && state.servers.isEmpty() -> {
|
||||
LoadingState(modifier = Modifier.padding(paddingValues))
|
||||
}
|
||||
|
||||
state.errorMessage != null -> {
|
||||
ErrorState(
|
||||
message = state.errorMessage ?: "",
|
||||
@@ -126,6 +127,7 @@ fun McpServersScreen(
|
||||
modifier = Modifier.padding(paddingValues),
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
@@ -557,12 +559,14 @@ private fun CatalogSection(
|
||||
}
|
||||
Spacer(modifier = Modifier.height(spacing.sm))
|
||||
}
|
||||
|
||||
state.catalogError != null -> {
|
||||
ErrorState(
|
||||
message = state.catalogError ?: "",
|
||||
onRetry = { viewModel.loadCatalog() },
|
||||
)
|
||||
}
|
||||
|
||||
filteredCatalog.isEmpty() -> {
|
||||
Text(
|
||||
text = stringResource(R.string.mcp_servers_catalog_empty),
|
||||
@@ -571,6 +575,7 @@ private fun CatalogSection(
|
||||
modifier = Modifier.padding(vertical = spacing.md),
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
filteredCatalog.forEach { entry ->
|
||||
CatalogEntryCard(
|
||||
|
||||
@@ -112,6 +112,7 @@ class McpServersViewModel :
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is NetworkResult.Failure -> {
|
||||
revertToggle(server.name, originalEnabled, "Failed to toggle server: ${result.error.message}")
|
||||
}
|
||||
@@ -130,6 +131,7 @@ class McpServersViewModel :
|
||||
is NetworkResult.Success -> {
|
||||
_uiState.update { it.copy(toastMessage = "Server '$name' tested — OK") }
|
||||
}
|
||||
|
||||
is NetworkResult.Failure -> {
|
||||
_uiState.update { it.copy(toastMessage = "Server '$name' test failed: ${result.error.message}") }
|
||||
}
|
||||
@@ -148,6 +150,7 @@ class McpServersViewModel :
|
||||
_uiState.update { it.copy(toastMessage = "Server '$name' deleted") }
|
||||
loadServers()
|
||||
}
|
||||
|
||||
is NetworkResult.Failure -> {
|
||||
_uiState.update { it.copy(toastMessage = "Failed to delete server: ${result.error.message}") }
|
||||
}
|
||||
@@ -167,6 +170,7 @@ class McpServersViewModel :
|
||||
_uiState.update { it.copy(toastMessage = "Server '$name' restarted") }
|
||||
loadServers()
|
||||
}
|
||||
|
||||
is NetworkResult.Failure -> {
|
||||
_uiState.update { it.copy(toastMessage = "Failed to restart server: ${result.error.message}") }
|
||||
}
|
||||
@@ -220,7 +224,10 @@ class McpServersViewModel :
|
||||
},
|
||||
args =
|
||||
if (state.addMode == AddServerMode.Stdio && state.addServerArgs.isNotBlank()) {
|
||||
state.addServerArgs.trim().split("\\s+".toRegex()).filter { it.isNotEmpty() }
|
||||
state.addServerArgs
|
||||
.trim()
|
||||
.split("\\s+".toRegex())
|
||||
.filter { it.isNotEmpty() }
|
||||
} else {
|
||||
null
|
||||
},
|
||||
@@ -244,6 +251,7 @@ class McpServersViewModel :
|
||||
}
|
||||
loadServers()
|
||||
}
|
||||
|
||||
is NetworkResult.Failure -> {
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
@@ -300,6 +308,7 @@ class McpServersViewModel :
|
||||
}
|
||||
loadServers()
|
||||
}
|
||||
|
||||
is NetworkResult.Failure -> {
|
||||
_uiState.update { it.copy(toastMessage = "Failed to add env var: ${result.error.message}") }
|
||||
}
|
||||
@@ -324,6 +333,7 @@ class McpServersViewModel :
|
||||
_uiState.update { it.copy(toastMessage = "Env var '$key' removed") }
|
||||
loadServers()
|
||||
}
|
||||
|
||||
is NetworkResult.Failure -> {
|
||||
_uiState.update { it.copy(toastMessage = "Failed to remove env var: ${result.error.message}") }
|
||||
}
|
||||
@@ -349,6 +359,7 @@ class McpServersViewModel :
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is NetworkResult.Failure -> {
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
@@ -389,6 +400,7 @@ class McpServersViewModel :
|
||||
}
|
||||
loadServers()
|
||||
}
|
||||
|
||||
is NetworkResult.Failure -> {
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
|
||||
@@ -29,7 +29,9 @@ enum class SkillsViewMode {
|
||||
HUB,
|
||||
}
|
||||
|
||||
enum class SkillFilter(val labelRes: Int) {
|
||||
enum class SkillFilter(
|
||||
val labelRes: Int,
|
||||
) {
|
||||
ALL_STATUSES(R.string.skills_status_all),
|
||||
ENABLED(R.string.skills_status_enabled),
|
||||
DISABLED(R.string.skills_status_disabled),
|
||||
@@ -74,8 +76,9 @@ data class SkillsUiState(
|
||||
val sourceFilter: String? = null,
|
||||
)
|
||||
|
||||
class SkillsViewModel(application: Application) :
|
||||
AndroidViewModel(application),
|
||||
class SkillsViewModel(
|
||||
application: Application,
|
||||
) : AndroidViewModel(application),
|
||||
ToastHost {
|
||||
private val _uiState = MutableStateFlow(SkillsUiState())
|
||||
val uiState: StateFlow<SkillsUiState> = _uiState.asStateFlow()
|
||||
@@ -189,6 +192,7 @@ class SkillsViewModel(application: Application) :
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is NetworkResult.Failure -> {
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
@@ -238,6 +242,7 @@ class SkillsViewModel(application: Application) :
|
||||
// Refresh installed skills
|
||||
loadSkills()
|
||||
}
|
||||
|
||||
is NetworkResult.Failure -> {
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
@@ -274,6 +279,7 @@ class SkillsViewModel(application: Application) :
|
||||
}
|
||||
loadSkills()
|
||||
}
|
||||
|
||||
is NetworkResult.Failure -> {
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
@@ -311,6 +317,7 @@ class SkillsViewModel(application: Application) :
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is NetworkResult.Failure -> {
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
@@ -469,6 +476,7 @@ class SkillsViewModel(application: Application) :
|
||||
_uiState.update { it.copy(toastMessage = "Skills updated") }
|
||||
loadSkills()
|
||||
}
|
||||
|
||||
is NetworkResult.Failure -> {
|
||||
_uiState.update { it.copy(toastMessage = "Update failed: ${result.error.message}") }
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user