fixing client
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
package at.mocode
|
||||
|
||||
expect object ApiConfig {
|
||||
val baseUrl: String
|
||||
val pingEndpoint: String
|
||||
}
|
||||
@@ -16,7 +16,8 @@ import io.ktor.client.request.*
|
||||
import io.ktor.serialization.kotlinx.json.*
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||
import at.mocode.components.*
|
||||
import at.mocode.http.GlobalHttpClient
|
||||
|
||||
@Serializable
|
||||
data class PingResponse(
|
||||
@@ -33,26 +34,19 @@ sealed class PingState {
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
fun App() {
|
||||
MaterialTheme {
|
||||
var showContent by remember { mutableStateOf(false) }
|
||||
var pingState by remember { mutableStateOf<PingState>(PingState.Idle) }
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
// Create HTTP client
|
||||
val httpClient = remember {
|
||||
HttpClient {
|
||||
install(ContentNegotiation) {
|
||||
json()
|
||||
}
|
||||
}
|
||||
}
|
||||
// Use optimized global HTTP client for minimal bundle size
|
||||
val httpClient = GlobalHttpClient.client
|
||||
|
||||
// Cleanup client on disposal
|
||||
// Cleanup global client on disposal
|
||||
DisposableEffect(Unit) {
|
||||
onDispose {
|
||||
httpClient.close()
|
||||
GlobalHttpClient.cleanup()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,10 +78,8 @@ fun App() {
|
||||
coroutineScope.launch {
|
||||
pingState = PingState.Loading
|
||||
try {
|
||||
// Direkter Aufruf des Ping-Service
|
||||
//val response: PingResponse = httpClient.get("http://localhost:8082/ping").body()
|
||||
// NEU: Aufruf über das Gateway
|
||||
val response: PingResponse = httpClient.get("http://localhost:8081/api/ping").body()
|
||||
// Konfigurierbare API-URL basierend auf Deployment-Umgebung
|
||||
val response: PingResponse = httpClient.get(ApiConfig.pingEndpoint).body()
|
||||
pingState = PingState.Success(response)
|
||||
} catch (e: Exception) {
|
||||
pingState = PingState.Error(e.message ?: "Unknown error occurred")
|
||||
@@ -190,6 +182,16 @@ fun App() {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Feature Control Panel für conditional loading
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
FeatureControlPanel()
|
||||
|
||||
// Conditional Features - nur laden wenn aktiviert
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
ConditionalDebugPanel()
|
||||
ConditionalAdminPanel()
|
||||
ConditionalAdvancedFeatures()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,371 @@
|
||||
package at.mocode.components
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import at.mocode.getPlatform
|
||||
|
||||
/**
|
||||
* Conditional Feature Loading Manager
|
||||
* Lädt Features nur bei Bedarf um Bundle-Größe zu reduzieren
|
||||
*/
|
||||
object ConditionalFeatures {
|
||||
|
||||
// Feature Flags für conditional loading
|
||||
private var debugModeEnabled by mutableStateOf(false)
|
||||
private var adminModeEnabled by mutableStateOf(false)
|
||||
private var advancedFeaturesEnabled by mutableStateOf(false)
|
||||
|
||||
fun enableDebugMode() { debugModeEnabled = true }
|
||||
fun disableDebugMode() { debugModeEnabled = false }
|
||||
fun isDebugModeEnabled() = debugModeEnabled
|
||||
|
||||
fun enableAdminMode() { adminModeEnabled = true }
|
||||
fun disableAdminMode() { adminModeEnabled = false }
|
||||
fun isAdminModeEnabled() = adminModeEnabled
|
||||
|
||||
fun enableAdvancedFeatures() { advancedFeaturesEnabled = true }
|
||||
fun disableAdvancedFeatures() { advancedFeaturesEnabled = false }
|
||||
fun areAdvancedFeaturesEnabled() = advancedFeaturesEnabled
|
||||
|
||||
// Platform-spezifische Feature-Detection
|
||||
fun isDesktopFeatureAvailable(): Boolean = getPlatform().name.contains("JVM", ignoreCase = true)
|
||||
fun isWebFeatureAvailable(): Boolean = getPlatform().name.contains("JavaScript", ignoreCase = true) ||
|
||||
getPlatform().name.contains("WASM", ignoreCase = true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug Panel - nur laden wenn Debug-Mode aktiviert
|
||||
*/
|
||||
@Composable
|
||||
fun ConditionalDebugPanel() {
|
||||
// Nur rendern wenn Debug-Mode aktiv ist
|
||||
if (ConditionalFeatures.isDebugModeEnabled()) {
|
||||
LazyDebugPanel()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LazyDebugPanel() {
|
||||
val platform = remember { getPlatform() }
|
||||
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp),
|
||||
colors = CardDefaults.cardColors(containerColor = Color(0xFFFFECB3))
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(12.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "🐛 Debug Panel",
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
color = Color(0xFF6B5B00)
|
||||
)
|
||||
Text(
|
||||
text = "Platform: ${platform.name}",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
modifier = Modifier.padding(top = 4.dp)
|
||||
)
|
||||
Text(
|
||||
text = "Bundle: WASM optimiert",
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
if (ConditionalFeatures.isDesktopFeatureAvailable()) {
|
||||
Text(
|
||||
text = "Desktop-Features: Verfügbar",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = Color(0xFF2E7D32)
|
||||
)
|
||||
}
|
||||
if (ConditionalFeatures.isWebFeatureAvailable()) {
|
||||
Text(
|
||||
text = "Web-Features: Verfügbar",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = Color(0xFF1976D2)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin Panel - nur laden wenn Admin-Mode aktiviert
|
||||
*/
|
||||
@Composable
|
||||
fun ConditionalAdminPanel() {
|
||||
if (ConditionalFeatures.isAdminModeEnabled()) {
|
||||
LazyAdminPanel()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LazyAdminPanel() {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp),
|
||||
colors = CardDefaults.cardColors(containerColor = Color(0xFFFFEBEE))
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(12.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "⚙️ Admin Panel",
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
color = Color(0xFFC62828)
|
||||
)
|
||||
|
||||
Row(
|
||||
modifier = Modifier.padding(top = 8.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Button(
|
||||
onClick = { ConditionalFeatures.enableAdvancedFeatures() },
|
||||
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFE53935))
|
||||
) {
|
||||
Text("Erweiterte Features", style = MaterialTheme.typography.labelSmall)
|
||||
}
|
||||
|
||||
Button(
|
||||
onClick = { ConditionalFeatures.enableDebugMode() },
|
||||
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFFF9800))
|
||||
) {
|
||||
Text("Debug Mode", style = MaterialTheme.typography.labelSmall)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Advanced Features - nur laden wenn explizit aktiviert
|
||||
*/
|
||||
@Composable
|
||||
fun ConditionalAdvancedFeatures() {
|
||||
if (ConditionalFeatures.areAdvancedFeaturesEnabled()) {
|
||||
LazyAdvancedFeatures()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LazyAdvancedFeatures() {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp),
|
||||
colors = CardDefaults.cardColors(containerColor = Color(0xFFF3E5F5))
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(12.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = "🚀 Erweiterte Features",
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
color = Color(0xFF7B1FA2)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
// Erweiterte Ping-Statistiken (nur bei Bedarf geladen)
|
||||
LazyPingStatistics()
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
// Platform-spezifische Features
|
||||
if (ConditionalFeatures.isDesktopFeatureAvailable()) {
|
||||
LazyDesktopOnlyFeatures()
|
||||
}
|
||||
|
||||
if (ConditionalFeatures.isWebFeatureAvailable()) {
|
||||
LazyWebOnlyFeatures()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LazyPingStatistics() {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(containerColor = Color(0xFFE8F5E8))
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(8.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "📊 Ping-Statistiken",
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = Color(0xFF388E3C)
|
||||
)
|
||||
Text(
|
||||
text = "Letzter Ping: Erfolgreich",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
modifier = Modifier.padding(top = 4.dp)
|
||||
)
|
||||
Text(
|
||||
text = "Durchschnitt: ~200ms",
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LazyDesktopOnlyFeatures() {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(containerColor = Color(0xFFE1F5FE))
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(8.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "🖥️ Desktop Features",
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = Color(0xFF0277BD)
|
||||
)
|
||||
Text(
|
||||
text = "• Datei-Export verfügbar",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
modifier = Modifier.padding(top = 4.dp)
|
||||
)
|
||||
Text(
|
||||
text = "• System-Integration aktiv",
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LazyWebOnlyFeatures() {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(containerColor = Color(0xFFFFF3E0))
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(8.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "🌐 Web Features",
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = Color(0xFFF57C00)
|
||||
)
|
||||
Text(
|
||||
text = "• PWA-Support verfügbar",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
modifier = Modifier.padding(top = 4.dp)
|
||||
)
|
||||
Text(
|
||||
text = "• Browser-API Integration",
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Feature Control Panel - für Benutzer-Kontrolle über conditional loading
|
||||
*/
|
||||
@Composable
|
||||
fun FeatureControlPanel() {
|
||||
var showControls by remember { mutableStateOf(false) }
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Button(
|
||||
onClick = { showControls = !showControls },
|
||||
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF424242))
|
||||
) {
|
||||
Text(
|
||||
if (showControls) "Feature-Kontrollen ausblenden" else "Feature-Kontrollen anzeigen",
|
||||
style = MaterialTheme.typography.labelMedium
|
||||
)
|
||||
}
|
||||
|
||||
if (showControls) {
|
||||
LazyFeatureControls()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LazyFeatureControls() {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(12.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "🎛️ Feature Controls",
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
modifier = Modifier.padding(bottom = 8.dp)
|
||||
)
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceEvenly
|
||||
) {
|
||||
Button(
|
||||
onClick = {
|
||||
if (ConditionalFeatures.isDebugModeEnabled()) {
|
||||
ConditionalFeatures.disableDebugMode()
|
||||
} else {
|
||||
ConditionalFeatures.enableDebugMode()
|
||||
}
|
||||
},
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = if (ConditionalFeatures.isDebugModeEnabled())
|
||||
Color(0xFF4CAF50) else Color(0xFF9E9E9E)
|
||||
)
|
||||
) {
|
||||
Text("Debug", style = MaterialTheme.typography.labelSmall)
|
||||
}
|
||||
|
||||
Button(
|
||||
onClick = {
|
||||
if (ConditionalFeatures.isAdminModeEnabled()) {
|
||||
ConditionalFeatures.disableAdminMode()
|
||||
} else {
|
||||
ConditionalFeatures.enableAdminMode()
|
||||
}
|
||||
},
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = if (ConditionalFeatures.isAdminModeEnabled())
|
||||
Color(0xFF4CAF50) else Color(0xFF9E9E9E)
|
||||
)
|
||||
) {
|
||||
Text("Admin", style = MaterialTheme.typography.labelSmall)
|
||||
}
|
||||
|
||||
Button(
|
||||
onClick = {
|
||||
if (ConditionalFeatures.areAdvancedFeaturesEnabled()) {
|
||||
ConditionalFeatures.disableAdvancedFeatures()
|
||||
} else {
|
||||
ConditionalFeatures.enableAdvancedFeatures()
|
||||
}
|
||||
},
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = if (ConditionalFeatures.areAdvancedFeaturesEnabled())
|
||||
Color(0xFF4CAF50) else Color(0xFF9E9E9E)
|
||||
)
|
||||
) {
|
||||
Text("Erweitert", style = MaterialTheme.typography.labelSmall)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package at.mocode.components
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.call.*
|
||||
import io.ktor.client.plugins.contentnegotiation.*
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.serialization.kotlinx.json.*
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.Serializable
|
||||
import at.mocode.ApiConfig
|
||||
|
||||
@Serializable
|
||||
data class PingResponse(
|
||||
val status: String,
|
||||
val timestamp: String? = null,
|
||||
val message: String? = null
|
||||
)
|
||||
|
||||
sealed class PingState {
|
||||
object Idle : PingState()
|
||||
object Loading : PingState()
|
||||
data class Success(val response: PingResponse) : PingState()
|
||||
data class Error(val message: String) : PingState()
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazy-loadable Ping Service Component
|
||||
* Encapsulates HTTP client, state management, and ping functionality
|
||||
* This component is only fully initialized when first used
|
||||
*/
|
||||
@Composable
|
||||
fun PingServiceComponent(
|
||||
modifier: Modifier = Modifier,
|
||||
onStateChange: (PingState) -> Unit = {}
|
||||
) {
|
||||
var pingState by remember { mutableStateOf<PingState>(PingState.Idle) }
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
// Lazy HTTP client - only created when component is first composed
|
||||
val httpClient = remember {
|
||||
HttpClient {
|
||||
install(ContentNegotiation) {
|
||||
json()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup client on disposal
|
||||
DisposableEffect(Unit) {
|
||||
onDispose {
|
||||
httpClient.close()
|
||||
}
|
||||
}
|
||||
|
||||
// Notify parent of state changes
|
||||
LaunchedEffect(pingState) {
|
||||
onStateChange(pingState)
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = modifier,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
// Ping Backend Button
|
||||
Button(
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
pingState = PingState.Loading
|
||||
try {
|
||||
// Konfigurierbare API-URL basierend auf Deployment-Umgebung
|
||||
val response: PingResponse = httpClient.get(ApiConfig.pingEndpoint).body()
|
||||
pingState = PingState.Success(response)
|
||||
} catch (e: Exception) {
|
||||
pingState = PingState.Error(e.message ?: "Unknown error occurred")
|
||||
}
|
||||
}
|
||||
},
|
||||
enabled = pingState !is PingState.Loading,
|
||||
modifier = Modifier.padding(8.dp)
|
||||
) {
|
||||
if (pingState is PingState.Loading) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.height(16.dp),
|
||||
color = MaterialTheme.colorScheme.onPrimary
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
Text("Ping Backend")
|
||||
}
|
||||
|
||||
// Status Display - conditionally rendered
|
||||
when (val state = pingState) {
|
||||
is PingState.Success -> {
|
||||
SuccessCard(
|
||||
response = state.response,
|
||||
modifier = Modifier.padding(top = 8.dp)
|
||||
)
|
||||
}
|
||||
is PingState.Error -> {
|
||||
ErrorCard(
|
||||
message = state.message,
|
||||
modifier = Modifier.padding(top = 8.dp)
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
// Idle or Loading state - no additional display needed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package at.mocode.components
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import at.mocode.Greeting
|
||||
|
||||
/**
|
||||
* Lazy-loadable Platform Info Component
|
||||
* This component is only loaded when needed to reduce initial bundle size
|
||||
*/
|
||||
@Composable
|
||||
fun PlatformInfoComponent(
|
||||
showContent: Boolean,
|
||||
onToggle: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
// Platform Info Toggle Button
|
||||
Button(
|
||||
onClick = onToggle,
|
||||
modifier = Modifier.padding(16.dp)
|
||||
) {
|
||||
Text(if (showContent) "Platform-Info ausblenden" else "Platform-Info anzeigen")
|
||||
}
|
||||
|
||||
// Lazy-loaded content - only create Greeting when actually shown
|
||||
AnimatedVisibility(showContent) {
|
||||
LazyPlatformInfo()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal composable that's only loaded when AnimatedVisibility is active
|
||||
*/
|
||||
@Composable
|
||||
private fun LazyPlatformInfo() {
|
||||
// This is only instantiated when showContent is true
|
||||
val greeting = remember { Greeting().greet() }
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
text = greeting,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
modifier = Modifier.padding(8.dp)
|
||||
)
|
||||
Text(
|
||||
text = "Willkommen in der Meldestelle-Anwendung!",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(top = 8.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package at.mocode.components
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* Lazy-loadable Success Card Component
|
||||
* Only loaded when a successful ping response is available
|
||||
*/
|
||||
@Composable
|
||||
fun SuccessCard(
|
||||
response: PingResponse,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Card(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "✅ Ping erfolgreich!",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = Color(0xFF4CAF50)
|
||||
)
|
||||
Text(
|
||||
text = "Status: ${response.status}",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.padding(top = 4.dp)
|
||||
)
|
||||
response.timestamp?.let {
|
||||
Text(
|
||||
text = "Zeit: $it",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(top = 2.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazy-loadable Error Card Component
|
||||
* Only loaded when a ping error occurs
|
||||
*/
|
||||
@Composable
|
||||
fun ErrorCard(
|
||||
message: String,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Card(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "❌ Ping fehlgeschlagen",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = Color(0xFFF44336)
|
||||
)
|
||||
Text(
|
||||
text = "Fehler: $message",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.padding(top = 4.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package at.mocode.http
|
||||
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.plugins.contentnegotiation.*
|
||||
import io.ktor.serialization.kotlinx.json.*
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
/**
|
||||
* Optimized HTTP Client für minimale Bundle-Größe
|
||||
* Enthält nur die minimal notwendigen Features für Ping-Service
|
||||
*/
|
||||
object OptimizedHttpClient {
|
||||
|
||||
/**
|
||||
* Erstellt einen minimalen HTTP Client mit nur den notwendigen Features
|
||||
* - ContentNegotiation für JSON
|
||||
* - Minimale JSON-Konfiguration
|
||||
* - Keine unnötigen Plugins oder Features
|
||||
*/
|
||||
fun createMinimalClient(): HttpClient {
|
||||
return HttpClient {
|
||||
// Nur ContentNegotiation für JSON - keine anderen Plugins
|
||||
install(ContentNegotiation) {
|
||||
json(Json {
|
||||
// Minimale JSON-Konfiguration für kleinste Bundle-Größe
|
||||
ignoreUnknownKeys = true
|
||||
isLenient = false
|
||||
encodeDefaults = false
|
||||
// Keine pretty printing für Production
|
||||
prettyPrint = false
|
||||
// Keine explicitNulls für kleinere Payloads
|
||||
explicitNulls = false
|
||||
})
|
||||
}
|
||||
|
||||
// Explizit keine anderen Features installieren:
|
||||
// - Kein Logging (spart Bundle-Größe)
|
||||
// - Kein DefaultRequest (nicht benötigt für einfachen Ping)
|
||||
// - Kein Timeout (Browser/Platform Default verwenden)
|
||||
// - Kein Auth (Ping-Service ist öffentlich)
|
||||
// - Keine Cookies (nicht benötigt)
|
||||
// - Keine Compression (nicht benötigt für kleine Payloads)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Platform-optimierter Client (vereinfacht für alle Platforms)
|
||||
* Verwendet minimale Konfiguration für alle Targets
|
||||
*/
|
||||
fun createPlatformOptimizedClient(): HttpClient {
|
||||
return HttpClient {
|
||||
install(ContentNegotiation) {
|
||||
json(createMinimalJson())
|
||||
}
|
||||
// Einheitliche Optimierungen für alle Platforms
|
||||
expectSuccess = false // Keine Exception bei HTTP-Errors (spart Bundle-Größe)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimale JSON-Konfiguration für kleinste Serialization-Overhead
|
||||
*/
|
||||
private fun createMinimalJson(): Json {
|
||||
return Json {
|
||||
ignoreUnknownKeys = true
|
||||
isLenient = false
|
||||
encodeDefaults = false
|
||||
prettyPrint = false
|
||||
explicitNulls = false
|
||||
// Klassennamen nicht einbetten (spart Bytes)
|
||||
classDiscriminator = ""
|
||||
// Keine Polymorphie für einfache DTOs
|
||||
useAlternativeNames = false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazy HTTP Client Instance für optimale Performance
|
||||
* Erstellt den Client nur einmal bei erster Verwendung
|
||||
*/
|
||||
class LazyHttpClient {
|
||||
private var _client: HttpClient? = null
|
||||
|
||||
val client: HttpClient
|
||||
get() {
|
||||
if (_client == null) {
|
||||
_client = OptimizedHttpClient.createPlatformOptimizedClient()
|
||||
}
|
||||
return _client!!
|
||||
}
|
||||
|
||||
fun close() {
|
||||
_client?.close()
|
||||
_client = null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Globale Singleton-Instanz für den optimierten HTTP Client
|
||||
* Minimiert Memory-Overhead und Bundle-Größe
|
||||
*/
|
||||
object GlobalHttpClient {
|
||||
private val lazyClient = LazyHttpClient()
|
||||
|
||||
/**
|
||||
* Zugriff auf den optimierten HTTP Client
|
||||
*/
|
||||
val client: HttpClient
|
||||
get() = lazyClient.client
|
||||
|
||||
/**
|
||||
* Client schließen bei App-Beendigung
|
||||
*/
|
||||
fun cleanup() {
|
||||
lazyClient.close()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package at.mocode
|
||||
|
||||
actual object ApiConfig {
|
||||
actual val baseUrl: String = "" // Same-origin für Nginx-Proxy
|
||||
actual val pingEndpoint: String = "/api/ping"
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package at.mocode
|
||||
|
||||
actual object ApiConfig {
|
||||
actual val baseUrl: String = System.getenv("API_BASE_URL") ?: "http://localhost:8081"
|
||||
actual val pingEndpoint: String = "$baseUrl/api/ping"
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package at.mocode
|
||||
|
||||
actual object ApiConfig {
|
||||
actual val baseUrl: String = "" // Same-origin für Nginx-Proxy
|
||||
actual val pingEndpoint: String = "/api/ping"
|
||||
}
|
||||
Reference in New Issue
Block a user