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:
+2
-1
@@ -90,7 +90,7 @@ subprojects {
|
|||||||
jvmArgs("--add-opens=java.base/java.nio=ALL-UNNAMED")
|
jvmArgs("--add-opens=java.base/java.nio=ALL-UNNAMED")
|
||||||
// Suppress ByteBuddy/Mockito dynamic agent loading warnings (Java 21+)
|
// Suppress ByteBuddy/Mockito dynamic agent loading warnings (Java 21+)
|
||||||
jvmArgs("-XX:+EnableDynamicAgentLoading")
|
jvmArgs("-XX:+EnableDynamicAgentLoading")
|
||||||
// Increase test JVM memory with a stable configuration
|
jvmArgs("--enable-native-access=ALL-UNNAMED")
|
||||||
minHeapSize = "512m"
|
minHeapSize = "512m"
|
||||||
maxHeapSize = "2g"
|
maxHeapSize = "2g"
|
||||||
// Parallel test execution for better performance
|
// Parallel test execution for better performance
|
||||||
@@ -166,6 +166,7 @@ subprojects {
|
|||||||
jvmArgs("-Xshare:auto", "-Djdk.instrument.traceUsage=false")
|
jvmArgs("-Xshare:auto", "-Djdk.instrument.traceUsage=false")
|
||||||
jvmArgs("--add-opens=java.base/java.nio=ALL-UNNAMED")
|
jvmArgs("--add-opens=java.base/java.nio=ALL-UNNAMED")
|
||||||
jvmArgs("-XX:+EnableDynamicAgentLoading")
|
jvmArgs("-XX:+EnableDynamicAgentLoading")
|
||||||
|
jvmArgs("--enable-native-access=ALL-UNNAMED")
|
||||||
maxHeapSize = "2g"
|
maxHeapSize = "2g"
|
||||||
dependsOn("testClasses")
|
dependsOn("testClasses")
|
||||||
}
|
}
|
||||||
|
|||||||
-9
@@ -2,12 +2,6 @@ package at.mocode.frontend.core.localdb
|
|||||||
|
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
/**
|
|
||||||
* Thin wrapper around SQLDelight `AppDatabase` creation.
|
|
||||||
*
|
|
||||||
* The platform-specific part is the `DatabaseDriverFactory` (expect/actual),
|
|
||||||
* which provides the appropriate SQLDelight driver (JVM sqlite driver, JS WebWorkerDriver, ...).
|
|
||||||
*/
|
|
||||||
class DatabaseProvider(
|
class DatabaseProvider(
|
||||||
private val driverFactory: DatabaseDriverFactory
|
private val driverFactory: DatabaseDriverFactory
|
||||||
) {
|
) {
|
||||||
@@ -17,9 +11,6 @@ class DatabaseProvider(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Koin module to provide the SQLDelight database for all frontend targets.
|
|
||||||
*/
|
|
||||||
val localDbModule = module {
|
val localDbModule = module {
|
||||||
single<DatabaseDriverFactory> { DatabaseDriverFactory() }
|
single<DatabaseDriverFactory> { DatabaseDriverFactory() }
|
||||||
single<DatabaseProvider> { DatabaseProvider(get()) }
|
single<DatabaseProvider> { DatabaseProvider(get()) }
|
||||||
|
|||||||
+1
-1
@@ -45,6 +45,7 @@ class JvmP2pSyncService : P2pSyncService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Prozessweiter, portbasierter Guard
|
// Prozessweiter, portbasierter Guard
|
||||||
|
println("[P2P Server] Versuche Port $port zu reservieren...")
|
||||||
if (!startedPorts.add(port)) {
|
if (!startedPorts.add(port)) {
|
||||||
println("[P2P Server] Bereits gestartet (Prozess) auf Port $port – idempotent, kein neuer Bind")
|
println("[P2P Server] Bereits gestartet (Prozess) auf Port $port – idempotent, kein neuer Bind")
|
||||||
return
|
return
|
||||||
@@ -79,7 +80,6 @@ class JvmP2pSyncService : P2pSyncService {
|
|||||||
}
|
}
|
||||||
}.start(wait = false)
|
}.start(wait = false)
|
||||||
currentPort = port
|
currentPort = port
|
||||||
println("[P2P Server] Gestartet auf Port $port")
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// Start fehlgeschlagen -> Port-Lock wieder freigeben
|
// Start fehlgeschlagen -> Port-Lock wieder freigeben
|
||||||
startedPorts.remove(port)
|
startedPorts.remove(port)
|
||||||
|
|||||||
+3
-1
@@ -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.DeviceInitializationValidator
|
||||||
import at.mocode.frontend.features.device.initialization.domain.model.AppThemeSetting
|
import at.mocode.frontend.features.device.initialization.domain.model.AppThemeSetting
|
||||||
import at.mocode.frontend.features.device.initialization.domain.model.NetworkRole
|
import at.mocode.frontend.features.device.initialization.domain.model.NetworkRole
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun DiscoveryRadar(
|
private fun DiscoveryRadar(
|
||||||
@@ -94,7 +96,7 @@ fun DeviceInitializationScreen(
|
|||||||
// Automatische Discovery starten
|
// Automatische Discovery starten
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
viewModel.startDiscovery()
|
viewModel.startDiscovery()
|
||||||
roleSelectorFocus.requestFocus()
|
delay(100.milliseconds); withFrameMillis { roleSelectorFocus.requestFocus() }
|
||||||
}
|
}
|
||||||
|
|
||||||
Surface(
|
Surface(
|
||||||
|
|||||||
+62
-27
@@ -48,12 +48,6 @@ actual fun DeviceInitializationConfig(
|
|||||||
val focusManager = LocalFocusManager.current
|
val focusManager = LocalFocusManager.current
|
||||||
val (_, sharedKeyFocus, backupPathFocus, clientNameFocus, clientRoleFocus) = remember { FocusRequester.createRefs() }
|
val (_, sharedKeyFocus, backupPathFocus, clientNameFocus, clientRoleFocus) = remember { FocusRequester.createRefs() }
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
if (settings.deviceName.isEmpty()) {
|
|
||||||
deviceNameFocus.requestFocus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
shape = MaterialTheme.shapes.large,
|
shape = MaterialTheme.shapes.large,
|
||||||
@@ -66,7 +60,7 @@ actual fun DeviceInitializationConfig(
|
|||||||
value = settings.deviceName,
|
value = settings.deviceName,
|
||||||
onValueChange = { viewModel.updateSettings { s -> s.copy(deviceName = it) } },
|
onValueChange = { viewModel.updateSettings { s -> s.copy(deviceName = it) } },
|
||||||
label = "Gerätename",
|
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",
|
placeholder = "z.B. Meldestelle-PC-1",
|
||||||
isError = settings.deviceName.isNotEmpty() && !DeviceInitializationValidator.isNameValid(settings.deviceName),
|
isError = settings.deviceName.isNotEmpty() && !DeviceInitializationValidator.isNameValid(settings.deviceName),
|
||||||
errorMessage = "Mindestens ${DeviceInitializationValidator.MIN_NAME_LENGTH} Zeichen erforderlich.",
|
errorMessage = "Mindestens ${DeviceInitializationValidator.MIN_NAME_LENGTH} Zeichen erforderlich.",
|
||||||
@@ -77,24 +71,43 @@ actual fun DeviceInitializationConfig(
|
|||||||
compact = true
|
compact = true
|
||||||
)
|
)
|
||||||
|
|
||||||
// NETZWERK-INTERFACES (EXPERTEN-MODUS)
|
|
||||||
val interfaces = remember {
|
val interfaces = remember {
|
||||||
NetworkInterface.getNetworkInterfaces().toList()
|
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 ->
|
.map { ni ->
|
||||||
val friendlyName = when {
|
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("wlan", ignoreCase = true) || ni.displayName.contains(
|
||||||
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"
|
"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
|
else -> "💻 " + ni.displayName
|
||||||
}
|
}
|
||||||
val address = ni.inetAddresses.asSequence().firstOrNull { !it.isLinkLocalAddress && it.hostAddress.indexOf(':') == -1 }?.hostAddress
|
val address = ni.inetAddresses.asSequence()
|
||||||
|
.firstOrNull { !it.isLinkLocalAddress && it.hostAddress.indexOf(":") == -1 }?.hostAddress
|
||||||
?: ni.inetAddresses.nextElement().hostAddress
|
?: ni.inetAddresses.nextElement().hostAddress
|
||||||
|
|
||||||
val isConnected = !ni.isLoopback && ni.isUp && ni.interfaceAddresses.any {
|
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(
|
Surface(
|
||||||
onClick = { if (!uiState.isLocked) viewModel.updateSettings { s -> s.copy(networkInterface = info.id) } },
|
onClick = { if (!uiState.isLocked) viewModel.updateSettings { s -> s.copy(networkInterface = info.id) } },
|
||||||
shape = MaterialTheme.shapes.medium,
|
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,
|
border = if (isSelected) BorderStroke(2.dp, MaterialTheme.colorScheme.primary) else null,
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
Row(Modifier.padding(12.dp), verticalAlignment = Alignment.CenterVertically) {
|
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))
|
Spacer(Modifier.width(12.dp))
|
||||||
Column(Modifier.weight(1f)) {
|
Column(Modifier.weight(1f)) {
|
||||||
Text(info.name, style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Bold)
|
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) }
|
var passwordVisible by remember { mutableStateOf(false) }
|
||||||
MsTextField(
|
MsTextField(
|
||||||
value = settings.sharedKey,
|
value = settings.sharedKey,
|
||||||
onValueChange = { viewModel.updateSettings { s -> s.copy(sharedKey = it) } },
|
onValueChange = { viewModel.updateSettings { s -> s.copy(sharedKey = it) } },
|
||||||
label = "Sicherheitsschlüssel (Sync-Key)",
|
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",
|
placeholder = "Mindestens 8 Zeichen",
|
||||||
isError = settings.sharedKey.isNotEmpty() && !DeviceInitializationValidator.isKeyValid(settings.sharedKey),
|
isError = settings.sharedKey.isNotEmpty() && !DeviceInitializationValidator.isKeyValid(settings.sharedKey),
|
||||||
errorMessage = "Mindestens ${DeviceInitializationValidator.MIN_KEY_LENGTH} Zeichen erforderlich.",
|
errorMessage = "Mindestens ${DeviceInitializationValidator.MIN_KEY_LENGTH} Zeichen erforderlich.",
|
||||||
@@ -157,7 +174,6 @@ actual fun DeviceInitializationConfig(
|
|||||||
compact = true
|
compact = true
|
||||||
)
|
)
|
||||||
|
|
||||||
// CLIENT-VERBINDUNG-FEEDBACK
|
|
||||||
if (settings.networkRole == NetworkRole.CLIENT && !uiState.isLocked) {
|
if (settings.networkRole == NetworkRole.CLIENT && !uiState.isLocked) {
|
||||||
val masterSelected = uiState.selectedMaster != null
|
val masterSelected = uiState.selectedMaster != null
|
||||||
val canConnect = masterSelected && settings.sharedKey.isNotBlank()
|
val canConnect = masterSelected && settings.sharedKey.isNotBlank()
|
||||||
@@ -170,13 +186,19 @@ actual fun DeviceInitializationConfig(
|
|||||||
else -> MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.1f)
|
else -> MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.1f)
|
||||||
},
|
},
|
||||||
shape = MaterialTheme.shapes.medium,
|
shape = MaterialTheme.shapes.medium,
|
||||||
border = BorderStroke(1.dp, when (uiState.connectionStatus) {
|
border = BorderStroke(
|
||||||
|
1.dp, when (uiState.connectionStatus) {
|
||||||
ConnectionStatus.CONNECTED -> Color(0xFF4CAF50)
|
ConnectionStatus.CONNECTED -> Color(0xFF4CAF50)
|
||||||
ConnectionStatus.FAILED -> Color(0xFFF44336)
|
ConnectionStatus.FAILED -> Color(0xFFF44336)
|
||||||
else -> MaterialTheme.colorScheme.outlineVariant
|
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)) {
|
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||||
when (uiState.connectionStatus) {
|
when (uiState.connectionStatus) {
|
||||||
ConnectionStatus.CONNECTING -> CircularProgressIndicator(Modifier.size(20.dp), strokeWidth = 2.dp)
|
ConnectionStatus.CONNECTING -> CircularProgressIndicator(Modifier.size(20.dp), strokeWidth = 2.dp)
|
||||||
@@ -210,7 +232,6 @@ actual fun DeviceInitializationConfig(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// BACKUP & DRUCKER
|
|
||||||
MsFilePicker(
|
MsFilePicker(
|
||||||
label = "Backup-Verzeichnis (Plan-USB)",
|
label = "Backup-Verzeichnis (Plan-USB)",
|
||||||
selectedPath = settings.backupPath,
|
selectedPath = settings.backupPath,
|
||||||
@@ -246,10 +267,13 @@ actual fun DeviceInitializationConfig(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MASTER: ERWARTETE CLIENTS
|
|
||||||
if (settings.networkRole == NetworkRole.MASTER && !uiState.isLocked) {
|
if (settings.networkRole == NetworkRole.MASTER && !uiState.isLocked) {
|
||||||
HorizontalDivider(Modifier.padding(vertical = 8.dp))
|
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)
|
Text("👥 Erwartete Clients", style = MaterialTheme.typography.titleSmall)
|
||||||
TextButton(onClick = { viewModel.addExpectedClient() }) {
|
TextButton(onClick = { viewModel.addExpectedClient() }) {
|
||||||
Icon(Icons.Default.Add, null, Modifier.size(18.dp))
|
Icon(Icons.Default.Add, null, Modifier.size(18.dp))
|
||||||
@@ -295,7 +319,12 @@ actual fun DeviceInitializationConfig(
|
|||||||
},
|
},
|
||||||
trailingContent = {
|
trailingContent = {
|
||||||
IconButton(onClick = { viewModel.removeExpectedClient(index) }) {
|
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)),
|
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
|
||||||
|
)
|
||||||
|
|||||||
@@ -2,9 +2,6 @@
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
||||||
|
|
||||||
/**
|
|
||||||
* Dieses Modul kapselt die gesamte UI und Logik für das Ping-Feature.
|
|
||||||
*/
|
|
||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.kotlinMultiplatform)
|
alias(libs.plugins.kotlinMultiplatform)
|
||||||
alias(libs.plugins.composeMultiplatform)
|
alias(libs.plugins.composeMultiplatform)
|
||||||
@@ -17,14 +14,9 @@ version = "1.0.0"
|
|||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
jvm()
|
jvm()
|
||||||
|
|
||||||
wasmJs {
|
wasmJs {
|
||||||
binaries.library()
|
binaries.library()
|
||||||
browser {
|
browser { testTask { enabled = false } }
|
||||||
testTask {
|
|
||||||
enabled = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
@@ -33,12 +25,13 @@ kotlin {
|
|||||||
implementation(projects.frontend.core.designSystem)
|
implementation(projects.frontend.core.designSystem)
|
||||||
implementation(projects.frontend.core.sync)
|
implementation(projects.frontend.core.sync)
|
||||||
implementation(projects.frontend.core.localDb)
|
implementation(projects.frontend.core.localDb)
|
||||||
implementation(projects.frontend.core.auth) // Added auth module for AuthTokenManager
|
implementation(projects.frontend.core.auth)
|
||||||
implementation(libs.sqldelight.coroutines)
|
implementation(libs.sqldelight.coroutines)
|
||||||
implementation(projects.frontend.core.domain)
|
implementation(projects.frontend.core.domain)
|
||||||
|
|
||||||
implementation(compose.foundation)
|
// Explizite Compose-Abhängigkeiten zur Vermeidung von Gradle 10 Warnungen
|
||||||
implementation(compose.runtime)
|
implementation(compose.runtime)
|
||||||
|
implementation(compose.foundation)
|
||||||
implementation(compose.material3)
|
implementation(compose.material3)
|
||||||
implementation(compose.ui)
|
implementation(compose.ui)
|
||||||
implementation(compose.components.resources)
|
implementation(compose.components.resources)
|
||||||
@@ -49,7 +42,7 @@ kotlin {
|
|||||||
implementation(libs.bundles.compose.common)
|
implementation(libs.bundles.compose.common)
|
||||||
|
|
||||||
implementation(libs.koin.core)
|
implementation(libs.koin.core)
|
||||||
implementation(libs.koin.compose) // Added koin.compose for koinInject
|
implementation(libs.koin.compose)
|
||||||
}
|
}
|
||||||
|
|
||||||
commonTest.dependencies {
|
commonTest.dependencies {
|
||||||
@@ -72,6 +65,5 @@ kotlin {
|
|||||||
wasmJsMain.dependencies {
|
wasmJsMain.dependencies {
|
||||||
implementation(libs.kotlin.stdlib.wasm.js)
|
implementation(libs.kotlin.stdlib.wasm.js)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ kotlin {
|
|||||||
// Bundles
|
// Bundles
|
||||||
implementation(libs.bundles.kmp.common)
|
implementation(libs.bundles.kmp.common)
|
||||||
implementation(libs.bundles.compose.common)
|
implementation(libs.bundles.compose.common)
|
||||||
|
implementation(libs.logback.classic)
|
||||||
}
|
}
|
||||||
|
|
||||||
jvmTest.dependencies {
|
jvmTest.dependencies {
|
||||||
@@ -178,6 +179,7 @@ compose.desktop {
|
|||||||
|
|
||||||
// JVM-Argumente für die gepackte Anwendung
|
// JVM-Argumente für die gepackte Anwendung
|
||||||
jvmArgs(
|
jvmArgs(
|
||||||
|
"--enable-native-access=ALL-UNNAMED",
|
||||||
"-Xms128m",
|
"-Xms128m",
|
||||||
"-Xmx512m",
|
"-Xmx512m",
|
||||||
"-Dfile.encoding=UTF-8",
|
"-Dfile.encoding=UTF-8",
|
||||||
|
|||||||
+13
-24
@@ -8,7 +8,6 @@ import at.mocode.frontend.core.auth.di.authModule
|
|||||||
import at.mocode.frontend.core.localdb.AppDatabase
|
import at.mocode.frontend.core.localdb.AppDatabase
|
||||||
import at.mocode.frontend.core.localdb.DatabaseProvider
|
import at.mocode.frontend.core.localdb.DatabaseProvider
|
||||||
import at.mocode.frontend.core.localdb.localDbModule
|
import at.mocode.frontend.core.localdb.localDbModule
|
||||||
import at.mocode.frontend.core.network.NetworkConfig
|
|
||||||
import at.mocode.frontend.core.network.chat.KtorWebSocketServerService
|
import at.mocode.frontend.core.network.chat.KtorWebSocketServerService
|
||||||
import at.mocode.frontend.core.network.discovery.NetworkDiscoveryService
|
import at.mocode.frontend.core.network.discovery.NetworkDiscoveryService
|
||||||
import at.mocode.frontend.core.network.networkModule
|
import at.mocode.frontend.core.network.networkModule
|
||||||
@@ -62,42 +61,32 @@ fun main() = application {
|
|||||||
desktopModule,
|
desktopModule,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
println("[DesktopApp] KOIN initialisiert")
|
|
||||||
// Base URL Log für schnelle Fehlerdiagnose
|
|
||||||
println("[Network] baseUrl=${NetworkConfig.baseUrl}")
|
|
||||||
|
|
||||||
// Starte Netzwerk-Dienste für den POC
|
// Datenbank EAGER initialisieren (JVM-safe via runBlocking)
|
||||||
val koin = GlobalContext.get()
|
val koin = GlobalContext.get()
|
||||||
|
val dbProvider = koin.get<DatabaseProvider>()
|
||||||
|
val database = runBlocking { dbProvider.createDatabase() }
|
||||||
|
|
||||||
|
loadKoinModules(module {
|
||||||
|
single<AppDatabase> { database }
|
||||||
|
})
|
||||||
|
|
||||||
|
println("[DesktopApp] KOIN & DB initialisiert")
|
||||||
|
|
||||||
|
// Start POC Netzwerk-Dienste
|
||||||
try {
|
try {
|
||||||
val wsServer = koin.get<KtorWebSocketServerService>()
|
val wsServer = koin.get<KtorWebSocketServerService>()
|
||||||
wsServer.start()
|
wsServer.start()
|
||||||
val discovery = koin.get<NetworkDiscoveryService>()
|
val discovery = koin.get<NetworkDiscoveryService>()
|
||||||
discovery.startDiscovery()
|
discovery.startDiscovery()
|
||||||
// Im Host-Modus würden wir hier registerService aufrufen.
|
|
||||||
// Für den POC registrieren wir den lokalen Host-Dienst immer mit dem WS-Port
|
|
||||||
try {
|
|
||||||
discovery.registerService(wsServer.getPort())
|
discovery.registerService(wsServer.getPort())
|
||||||
println("[DesktopApp] Discovery-Registrierung durchgeführt (Port ${wsServer.getPort()})")
|
|
||||||
} catch(e: Exception) {
|
} catch(e: Exception) {
|
||||||
println("[DesktopApp] Discovery-Registrierung fehlgeschlagen: ${e.message}")
|
println("[DesktopApp] Netzwerk-Dienste Fehler: %s".format(e.message))
|
||||||
}
|
|
||||||
} catch(e: Exception) {
|
|
||||||
println("[DesktopApp] POC-Dienste konnten nicht gestartet werden: ${e.message}")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Testdaten für Prototyp laden
|
|
||||||
at.mocode.frontend.shell.desktop.data.Store.seed()
|
at.mocode.frontend.shell.desktop.data.Store.seed()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
println("[DesktopApp] Koin-Warnung: ${e.message}")
|
println("[DesktopApp] Startup-Fehler: %s".format(e.message))
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
val provider = GlobalContext.get().get<DatabaseProvider>()
|
|
||||||
val db = runBlocking { provider.createDatabase() }
|
|
||||||
loadKoinModules(module { single<AppDatabase> { db } })
|
|
||||||
println("[DesktopApp] Lokale DB bereit")
|
|
||||||
} catch (e: Exception) {
|
|
||||||
println("[DesktopApp] DB-Warnung: ${e.message}")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Window(
|
Window(
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<configuration>
|
||||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<root level="INFO">
|
||||||
|
<appender-ref ref="STDOUT" />
|
||||||
|
</root>
|
||||||
|
|
||||||
|
<!-- JmDNS ist extrem gesprächig auf DEBUG/TRACE -->
|
||||||
|
<logger name="javax.jmdns" level="INFO"/>
|
||||||
|
<logger name="io.netty" level="INFO"/>
|
||||||
|
</configuration>
|
||||||
+14
-26
@@ -4,31 +4,29 @@ android.nonTransitiveRClass=true
|
|||||||
|
|
||||||
# Kotlin Configuration
|
# Kotlin Configuration
|
||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
# Increased Kotlin Daemon Heap for JS Compilation
|
# Increased Kotlin Daemon Heap for JS Compilation + JDK 25 Warning Suppression
|
||||||
kotlin.daemon.jvmargs=-Xmx8g -XX:+UseParallelGC -XX:MaxMetaspaceSize=2g
|
kotlin.daemon.jvmargs=-Xmx8g -XX:+UseParallelGC -XX:MaxMetaspaceSize=2g --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --enable-native-access=ALL-UNNAMED
|
||||||
kotlin.js.compiler.sourcemaps=false
|
kotlin.js.compiler.sourcemaps=false
|
||||||
|
|
||||||
# Kotlin Compiler Optimizations (Phase 5)
|
# Kotlin Compiler Optimizations
|
||||||
kotlin.incremental=true
|
kotlin.incremental=true
|
||||||
kotlin.incremental.multiplatform=true
|
kotlin.incremental.multiplatform=true
|
||||||
kotlin.incremental.js=true
|
kotlin.incremental.js=true
|
||||||
|
|
||||||
kotlin.caching.enabled=true
|
kotlin.caching.enabled=true
|
||||||
kotlin.compiler.execution.strategy=in-process
|
kotlin.compiler.execution.strategy=in-process
|
||||||
# kotlin.compiler.preciseCompilationResultsBackup=true
|
|
||||||
kotlin.stdlib.default.dependency=true
|
kotlin.stdlib.default.dependency=true
|
||||||
|
|
||||||
# Gradle Configuration
|
# Gradle Configuration
|
||||||
# Increased Gradle Daemon Heap
|
# Optimized for JDK 25: Added --add-opens and --enable-native-access for compiler tools
|
||||||
org.gradle.jvmargs=-Xmx8g -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx6g" -XX:+UseParallelGC -XX:MaxMetaspaceSize=2g -XX:+HeapDumpOnOutOfMemoryError -Xshare:off -Djava.awt.headless=true
|
org.gradle.jvmargs=-Xmx8g -Dfile.encoding=UTF-8 -XX:+UseParallelGC -XX:MaxMetaspaceSize=2g -XX:+HeapDumpOnOutOfMemoryError -Xshare:off -Djava.awt.headless=true --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.nio=ALL-UNNAMED --enable-native-access=ALL-UNNAMED -Djdk.instrument.traceUsage=false
|
||||||
org.gradle.workers.max=8
|
org.gradle.workers.max=8
|
||||||
org.gradle.vfs.watch=true
|
org.gradle.vfs.watch=true
|
||||||
|
|
||||||
# Configuration Cache optimieren - TEMPORÄR DEAKTIVIERT wegen JS-Test Serialisierungsproblemen
|
# Configuration Cache (JS-Test workaround)
|
||||||
org.gradle.configuration-cache=false
|
org.gradle.configuration-cache=false
|
||||||
org.gradle.configuration-cache.problems=warn
|
org.gradle.configuration-cache.problems=warn
|
||||||
|
|
||||||
# Build Performance verbessern
|
# Build Performance
|
||||||
org.gradle.parallel=true
|
org.gradle.parallel=true
|
||||||
org.gradle.caching=true
|
org.gradle.caching=true
|
||||||
|
|
||||||
@@ -46,7 +44,7 @@ org.jetbrains.kotlin.wasm.check.wasm.binary.format=false
|
|||||||
kotlin.native.ignoreDisabledTargets=true
|
kotlin.native.ignoreDisabledTargets=true
|
||||||
idea.project.settings.delegate.build.run.actions.to.gradle=true
|
idea.project.settings.delegate.build.run.actions.to.gradle=true
|
||||||
|
|
||||||
# Enable NPM/Yarn lifecycle scripts for Kotlin/JS (required for sql.js & worker setup)
|
# NPM/Yarn lifecycle
|
||||||
kotlin.js.yarn.ignoreScripts=false
|
kotlin.js.yarn.ignoreScripts=false
|
||||||
org.jetbrains.kotlin.js.yarn.ignoreScripts=false
|
org.jetbrains.kotlin.js.yarn.ignoreScripts=false
|
||||||
kotlin.js.npm.ignoreScripts=false
|
kotlin.js.npm.ignoreScripts=false
|
||||||
@@ -56,31 +54,21 @@ org.jetbrains.kotlin.js.npm.ignoreScripts=false
|
|||||||
org.gradle.logging.level=lifecycle
|
org.gradle.logging.level=lifecycle
|
||||||
kotlin.build.report.single_file=false
|
kotlin.build.report.single_file=false
|
||||||
|
|
||||||
# Compose Experimental Features
|
# Compose Experimental
|
||||||
org.jetbrains.compose.experimental.jscanvas.enabled=true
|
org.jetbrains.compose.experimental.jscanvas.enabled=true
|
||||||
org.jetbrains.compose.experimental.wasm.enabled=true
|
org.jetbrains.compose.experimental.wasm.enabled=true
|
||||||
|
|
||||||
# Java Toolchain: ensure Gradle auto-downloads a full JDK when needed
|
# Java Toolchain
|
||||||
org.gradle.java.installations.auto-download=true
|
org.gradle.java.installations.auto-download=true
|
||||||
org.gradle.java.installations.auto-detect=true
|
org.gradle.java.installations.auto-detect=true
|
||||||
|
|
||||||
# Development Environment Support
|
# Feature Toggles
|
||||||
dev.port.offset=0
|
|
||||||
# Set dev.port.offset=100 for second developer
|
|
||||||
# Set dev.port.offset=200 for the third developer
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
# Wasm/JS Feature Toggle
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
# Setze enableWasm=true, um die Web-App zu bauen oder Web-spezifische
|
|
||||||
# Module zu testen. Default=false spart massiv Zeit beim Desktop-Build.
|
|
||||||
enableWasm=true
|
enableWasm=true
|
||||||
enableDesktop=true
|
enableDesktop=true
|
||||||
|
dev.port.offset=0
|
||||||
|
|
||||||
# Dokka Gradle plugin V2 mode (with helpers for V1 compatibility)
|
# Dokka V2
|
||||||
# See https://kotl.in/dokka-gradle-migration
|
|
||||||
# org.jetbrains.dokka.experimental.gradle.pluginMode=V2EnabledWithHelpers
|
|
||||||
org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
|
org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
|
||||||
|
|
||||||
# Workaround for Gradle 9 / KMP "Plugin loaded multiple times" error in Docker/CI
|
# Gradle 9 Workaround
|
||||||
# This allows subprojects to re-declare plugins even if they are already on the classpath
|
|
||||||
kotlin.mpp.allowMultiplePluginDeclarations=true
|
kotlin.mpp.allowMultiplePluginDeclarations=true
|
||||||
|
|||||||
Reference in New Issue
Block a user