refactor(gradle, desktop): Build-Konfiguration bereinigt, Ports optimiert und UI-Logik konsolidiert

Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
This commit is contained in:
2026-05-09 14:27:17 +02:00
parent 280db663c7
commit 8d176ce955
10 changed files with 123 additions and 107 deletions
@@ -33,6 +33,8 @@ import androidx.compose.ui.unit.sp
import at.mocode.frontend.features.device.initialization.domain.DeviceInitializationValidator
import at.mocode.frontend.features.device.initialization.domain.model.AppThemeSetting
import at.mocode.frontend.features.device.initialization.domain.model.NetworkRole
import kotlinx.coroutines.delay
import kotlin.time.Duration.Companion.milliseconds
@Composable
private fun DiscoveryRadar(
@@ -94,7 +96,7 @@ fun DeviceInitializationScreen(
// Automatische Discovery starten
LaunchedEffect(Unit) {
viewModel.startDiscovery()
roleSelectorFocus.requestFocus()
delay(100.milliseconds); withFrameMillis { roleSelectorFocus.requestFocus() }
}
Surface(
@@ -48,12 +48,6 @@ actual fun DeviceInitializationConfig(
val focusManager = LocalFocusManager.current
val (_, sharedKeyFocus, backupPathFocus, clientNameFocus, clientRoleFocus) = remember { FocusRequester.createRefs() }
LaunchedEffect(Unit) {
if (settings.deviceName.isEmpty()) {
deviceNameFocus.requestFocus()
}
}
Card(
modifier = Modifier.fillMaxWidth(),
shape = MaterialTheme.shapes.large,
@@ -66,7 +60,7 @@ actual fun DeviceInitializationConfig(
value = settings.deviceName,
onValueChange = { viewModel.updateSettings { s -> s.copy(deviceName = it) } },
label = "Gerätename",
helpDescription = "Ein eindeutiger Name für diesen PC (z.B. 'Richter-Springplatz').",
helpDescription = "Ein eindeutiger Name für diesen PC (z.B. Richter-Springplatz).",
placeholder = "z.B. Meldestelle-PC-1",
isError = settings.deviceName.isNotEmpty() && !DeviceInitializationValidator.isNameValid(settings.deviceName),
errorMessage = "Mindestens ${DeviceInitializationValidator.MIN_NAME_LENGTH} Zeichen erforderlich.",
@@ -77,24 +71,43 @@ actual fun DeviceInitializationConfig(
compact = true
)
// NETZWERK-INTERFACES (EXPERTEN-MODUS)
val interfaces = remember {
NetworkInterface.getNetworkInterfaces().toList()
.filter { it.isUp && !it.isLoopback && it.inetAddresses.hasMoreElements() }
.filter { it.isUp && !it.isLoopback && it.inetAddresses.hasMoreElements() && !it.name.startsWith("br-") && !it.name.startsWith("docker") && !it.name.startsWith("veth") }
.map { ni ->
val friendlyName = when {
ni.displayName.contains("wlan", ignoreCase = true) || ni.displayName.contains("wi-fi", ignoreCase = true) || ni.name.contains("wlan", ignoreCase = true) -> "🌐 WLAN"
ni.displayName.contains("eth", ignoreCase = true) || ni.displayName.contains("ethernet", ignoreCase = true) || ni.name.contains("eth", ignoreCase = true) || ni.name.contains("en", ignoreCase = true) -> "🔌 Ethernet"
ni.displayName.contains("wlan", ignoreCase = true) || ni.displayName.contains(
"wi-fi",
ignoreCase = true
) || ni.name.contains("wlan", ignoreCase = true) -> "🌐 WLAN"
ni.displayName.contains("eth", ignoreCase = true) || ni.displayName.contains(
"ethernet",
ignoreCase = true
) || ni.name.contains("eth", ignoreCase = true) || ni.name.contains(
"en",
ignoreCase = true
) -> "🔌 Ethernet"
else -> "💻 " + ni.displayName
}
val address = ni.inetAddresses.asSequence().firstOrNull { !it.isLinkLocalAddress && it.hostAddress.indexOf(':') == -1 }?.hostAddress
?: ni.inetAddresses.nextElement().hostAddress
val address = ni.inetAddresses.asSequence()
.firstOrNull { !it.isLinkLocalAddress && it.hostAddress.indexOf(":") == -1 }?.hostAddress
?: ni.inetAddresses.nextElement().hostAddress
val isConnected = !ni.isLoopback && ni.isUp && ni.interfaceAddresses.any {
it.address.isSiteLocalAddress || it.address.hostAddress.startsWith("192.168") || it.address.hostAddress.startsWith("10.")
it.address.isSiteLocalAddress || it.address.hostAddress.startsWith("192.168") || it.address.hostAddress.startsWith(
"10."
)
}
InterfaceInfo(id = "$friendlyName ($address)", name = friendlyName, address = address, hardwareName = ni.name, isConnected = isConnected)
InterfaceInfo(
id = "$friendlyName ($address)",
name = friendlyName,
address = address,
hardwareName = ni.name,
isConnected = isConnected
)
}
}
@@ -120,12 +133,17 @@ actual fun DeviceInitializationConfig(
Surface(
onClick = { if (!uiState.isLocked) viewModel.updateSettings { s -> s.copy(networkInterface = info.id) } },
shape = MaterialTheme.shapes.medium,
color = if (isSelected) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.3f),
color = if (isSelected) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.surfaceVariant.copy(
alpha = 0.3f
),
border = if (isSelected) BorderStroke(2.dp, MaterialTheme.colorScheme.primary) else null,
modifier = Modifier.fillMaxWidth()
) {
Row(Modifier.padding(12.dp), verticalAlignment = Alignment.CenterVertically) {
Box(Modifier.size(10.dp).background(if (info.isConnected) Color(0xFF4CAF50) else Color(0xFFF44336), CircleShape))
Box(
Modifier.size(10.dp)
.background(if (info.isConnected) Color(0xFF4CAF50) else Color(0xFFF44336), CircleShape)
)
Spacer(Modifier.width(12.dp))
Column(Modifier.weight(1f)) {
Text(info.name, style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Bold)
@@ -137,13 +155,12 @@ actual fun DeviceInitializationConfig(
}
}
// SICHERHEITSSCHLÜSSEL
var passwordVisible by remember { mutableStateOf(false) }
MsTextField(
value = settings.sharedKey,
onValueChange = { viewModel.updateSettings { s -> s.copy(sharedKey = it) } },
label = "Sicherheitsschlüssel (Sync-Key)",
helpDescription = "Das 'Turnier-Passwort'. Muss auf allen Geräten gleich sein.",
helpDescription = "Das Turnier-Passwort. Muss auf allen Geräten gleich sein.",
placeholder = "Mindestens 8 Zeichen",
isError = settings.sharedKey.isNotEmpty() && !DeviceInitializationValidator.isKeyValid(settings.sharedKey),
errorMessage = "Mindestens ${DeviceInitializationValidator.MIN_KEY_LENGTH} Zeichen erforderlich.",
@@ -157,7 +174,6 @@ actual fun DeviceInitializationConfig(
compact = true
)
// CLIENT-VERBINDUNG-FEEDBACK
if (settings.networkRole == NetworkRole.CLIENT && !uiState.isLocked) {
val masterSelected = uiState.selectedMaster != null
val canConnect = masterSelected && settings.sharedKey.isNotBlank()
@@ -170,13 +186,19 @@ actual fun DeviceInitializationConfig(
else -> MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.1f)
},
shape = MaterialTheme.shapes.medium,
border = BorderStroke(1.dp, when (uiState.connectionStatus) {
ConnectionStatus.CONNECTED -> Color(0xFF4CAF50)
ConnectionStatus.FAILED -> Color(0xFFF44336)
else -> MaterialTheme.colorScheme.outlineVariant
})
border = BorderStroke(
1.dp, when (uiState.connectionStatus) {
ConnectionStatus.CONNECTED -> Color(0xFF4CAF50)
ConnectionStatus.FAILED -> Color(0xFFF44336)
else -> MaterialTheme.colorScheme.outlineVariant
}
)
) {
Column(Modifier.padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(12.dp)) {
Column(
Modifier.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp)) {
when (uiState.connectionStatus) {
ConnectionStatus.CONNECTING -> CircularProgressIndicator(Modifier.size(20.dp), strokeWidth = 2.dp)
@@ -210,7 +232,6 @@ actual fun DeviceInitializationConfig(
}
}
// BACKUP & DRUCKER
MsFilePicker(
label = "Backup-Verzeichnis (Plan-USB)",
selectedPath = settings.backupPath,
@@ -246,10 +267,13 @@ actual fun DeviceInitializationConfig(
)
}
// MASTER: ERWARTETE CLIENTS
if (settings.networkRole == NetworkRole.MASTER && !uiState.isLocked) {
HorizontalDivider(Modifier.padding(vertical = 8.dp))
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) {
Row(
Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text("👥 Erwartete Clients", style = MaterialTheme.typography.titleSmall)
TextButton(onClick = { viewModel.addExpectedClient() }) {
Icon(Icons.Default.Add, null, Modifier.size(18.dp))
@@ -295,7 +319,12 @@ actual fun DeviceInitializationConfig(
},
trailingContent = {
IconButton(onClick = { viewModel.removeExpectedClient(index) }) {
Icon(Icons.Default.Delete, null, tint = MaterialTheme.colorScheme.error, modifier = Modifier.size(20.dp))
Icon(
Icons.Default.Delete,
null,
tint = MaterialTheme.colorScheme.error,
modifier = Modifier.size(20.dp)
)
}
},
colors = ListItemDefaults.colors(containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f)),
@@ -307,4 +336,10 @@ actual fun DeviceInitializationConfig(
}
}
private data class InterfaceInfo(val id: String, val name: String, val address: String, val hardwareName: String, val isConnected: Boolean)
private data class InterfaceInfo(
val id: String,
val name: String,
val address: String,
val hardwareName: String,
val isConnected: Boolean
)