feat(#1121): add multiplexed gateway awareness (#1127)

This commit is contained in:
M57 authored and GitHub committed 2026-09-15 15:56:32 +03:00
1 parent fe465ad93c
commit 528ec4f28f
20 files changed
+1065 -8

No files matched your search

@@ -125,7 +125,8 @@ class ChannelsViewModel :
}
},
onStart = {},
onSuccess = {
onSuccess = { response ->
val hotServed = response.hotServed
_uiState.update { state ->
state.copy(
platforms =
@@ -133,16 +134,28 @@ class ChannelsViewModel :
if (p.id == platformId) {
p.copy(
enabled = enabled,
state = if (enabled) "pending_restart" else "disabled",
state =
when {
!enabled -> "disabled"
hotServed -> "connected"
else -> "pending_restart"
},
)
} else {
p
}
},
restartNeeded = true,
restartNeeded = !hotServed,
togglingId = null,
toastMessage =
if (hotServed) {
"$platformId updated live"
} else {
null
},
)
}
loadPlatforms()
},
onError = { error ->
_uiState.update {
@@ -201,12 +214,18 @@ class ChannelsViewModel :
it.copy(isLoading = true, errorMessage = null)
}
},
onSuccess = {
val message = "$platformId configured successfully — restart the gateway for changes to take effect"
onSuccess = { response ->
val hotServed = response.hotServed
val message =
if (hotServed) {
"$platformId configured and reloaded live"
} else {
"$platformId configured successfully — restart the gateway for changes to take effect"
}
_uiState.update {
it.copy(
isLoading = false,
restartNeeded = true,
restartNeeded = !hotServed,
toastMessage = message,
)
}
@@ -1,5 +1,6 @@
package com.m57.hermescontrol.ui.channels.components
import android.content.ClipData
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -12,6 +13,7 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material.icons.filled.ContentCopy
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.QrCode
import androidx.compose.material.icons.filled.RadioButtonChecked
@@ -34,18 +36,23 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.ClipEntry
import androidx.compose.ui.platform.LocalClipboard
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.unit.dp
import com.m57.hermescontrol.R
import com.m57.hermescontrol.data.model.MessagingPlatform
import com.m57.hermescontrol.data.model.MessagingPlatformUpdate
import com.m57.hermescontrol.theme.LocalHermesStatusColors
import com.m57.hermescontrol.ui.channels.ConfigureForm
import kotlinx.coroutines.launch
private enum class StatusRole { SUCCESS, WARNING, ERROR, NEUTRAL }
@@ -110,6 +117,8 @@ internal fun PlatformCard(
) {
var showConfigureForm by remember { mutableStateOf(false) }
var showRemoveConfirm by remember { mutableStateOf(false) }
val clipboard = LocalClipboard.current
val scope = rememberCoroutineScope()
val style = platformStateStyle(platform.state)
Card(modifier = Modifier.fillMaxWidth()) {
@@ -166,6 +175,40 @@ internal fun PlatformCard(
)
}
platform.ingressUrl?.takeIf { it.isNotBlank() }?.let { ingressUrl ->
Spacer(modifier = Modifier.height(8.dp))
Text(
text = stringResource(R.string.channels_ingress_url),
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = ingressUrl,
style = MaterialTheme.typography.bodySmall.copy(fontFamily = FontFamily.Monospace),
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.weight(1f),
)
IconButton(
onClick = {
scope.launch {
clipboard.setClipEntry(ClipEntry(ClipData.newPlainText(null, ingressUrl)))
}
},
modifier = Modifier.size(32.dp),
) {
Icon(
imageVector = Icons.Filled.ContentCopy,
contentDescription = stringResource(R.string.channels_copy_ingress_url),
modifier = Modifier.size(18.dp),
)
}
}
}
// ── Error message ──
if (!platform.errorMessage.isNullOrBlank()) {
Spacer(modifier = Modifier.height(4.dp))