feature clients
new frontend
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
plugins {
|
||||
alias(libs.plugins.kotlinMultiplatform)
|
||||
alias(libs.plugins.composeMultiplatform)
|
||||
alias(libs.plugins.composeCompiler)
|
||||
alias(libs.plugins.kotlinSerialization)
|
||||
}
|
||||
|
||||
group = "at.mocode.clients"
|
||||
version = "1.0.0"
|
||||
|
||||
kotlin {
|
||||
jvm()
|
||||
js {
|
||||
browser()
|
||||
}
|
||||
|
||||
jvmToolchain(21)
|
||||
|
||||
sourceSets {
|
||||
val commonMain by getting {
|
||||
dependencies {
|
||||
// Contract from backend
|
||||
implementation(projects.services.ping.pingApi)
|
||||
|
||||
// UI Kit
|
||||
implementation(project(":clients:shared:common-ui"))
|
||||
|
||||
// Compose dependencies
|
||||
implementation(compose.runtime)
|
||||
implementation(compose.foundation)
|
||||
implementation(compose.material3)
|
||||
implementation(compose.ui)
|
||||
|
||||
// Ktor client for HTTP calls
|
||||
implementation(libs.ktor.client.core)
|
||||
implementation(libs.ktor.client.contentNegotiation)
|
||||
implementation(libs.ktor.client.serialization.kotlinx.json)
|
||||
|
||||
// Coroutines and serialization
|
||||
implementation(libs.kotlinx.coroutines.core)
|
||||
implementation(libs.kotlinx.serialization.json)
|
||||
|
||||
// ViewModel lifecycle
|
||||
implementation(libs.androidx.lifecycle.viewmodelCompose)
|
||||
}
|
||||
}
|
||||
val commonTest by getting {
|
||||
dependencies {
|
||||
implementation(libs.kotlin.test)
|
||||
}
|
||||
}
|
||||
val jvmMain by getting {
|
||||
dependencies {
|
||||
implementation(libs.ktor.client.cio)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package at.mocode.clients.pingfeature
|
||||
|
||||
import at.mocode.ping.api.PingApi
|
||||
import at.mocode.ping.api.PingResponse
|
||||
import at.mocode.ping.api.EnhancedPingResponse
|
||||
import at.mocode.ping.api.HealthResponse
|
||||
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.serialization.json.Json
|
||||
|
||||
class PingApiClient(
|
||||
private val baseUrl: String = "http://localhost:8080"
|
||||
) : PingApi {
|
||||
|
||||
private val client = HttpClient {
|
||||
install(ContentNegotiation) {
|
||||
json(Json {
|
||||
prettyPrint = true
|
||||
isLenient = true
|
||||
ignoreUnknownKeys = true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun simplePing(): PingResponse {
|
||||
return client.get("$baseUrl/api/ping/simple").body()
|
||||
}
|
||||
|
||||
override suspend fun enhancedPing(simulate: Boolean): EnhancedPingResponse {
|
||||
return client.get("$baseUrl/api/ping/enhanced") {
|
||||
parameter("simulate", simulate)
|
||||
}.body()
|
||||
}
|
||||
|
||||
override suspend fun healthCheck(): HealthResponse {
|
||||
return client.get("$baseUrl/api/ping/health").body()
|
||||
}
|
||||
}
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
package at.mocode.clients.pingfeature
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun PingScreen(viewModel: PingViewModel) {
|
||||
val uiState = viewModel.uiState
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp)
|
||||
.verticalScroll(rememberScrollState()),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "Ping Service",
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
|
||||
// Action Buttons
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Button(
|
||||
onClick = { viewModel.performSimplePing() },
|
||||
enabled = !uiState.isLoading,
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
Text("Simple Ping")
|
||||
}
|
||||
|
||||
Button(
|
||||
onClick = { viewModel.performEnhancedPing() },
|
||||
enabled = !uiState.isLoading,
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
Text("Enhanced Ping")
|
||||
}
|
||||
|
||||
Button(
|
||||
onClick = { viewModel.performHealthCheck() },
|
||||
enabled = !uiState.isLoading,
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
Text("Health Check")
|
||||
}
|
||||
}
|
||||
|
||||
// Loading indicator
|
||||
if (uiState.isLoading) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
}
|
||||
|
||||
// Error message
|
||||
uiState.errorMessage?.let { error ->
|
||||
Card(
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.errorContainer
|
||||
)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "Error",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.onErrorContainer,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = error,
|
||||
color = MaterialTheme.colorScheme.onErrorContainer
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Button(
|
||||
onClick = { viewModel.clearError() }
|
||||
) {
|
||||
Text("Dismiss")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Simple Ping Response
|
||||
uiState.simplePingResponse?.let { response ->
|
||||
ResponseCard(
|
||||
title = "Simple Ping Response",
|
||||
status = response.status,
|
||||
timestamp = response.timestamp,
|
||||
service = response.service
|
||||
)
|
||||
}
|
||||
|
||||
// Enhanced Ping Response
|
||||
uiState.enhancedPingResponse?.let { response ->
|
||||
ResponseCard(
|
||||
title = "Enhanced Ping Response",
|
||||
status = response.status,
|
||||
timestamp = response.timestamp,
|
||||
service = response.service,
|
||||
additionalInfo = mapOf(
|
||||
"Circuit Breaker State" to response.circuitBreakerState,
|
||||
"Response Time" to "${response.responseTime}ms"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// Health Response
|
||||
uiState.healthResponse?.let { response ->
|
||||
ResponseCard(
|
||||
title = "Health Check Response",
|
||||
status = response.status,
|
||||
timestamp = response.timestamp,
|
||||
service = response.service,
|
||||
additionalInfo = mapOf(
|
||||
"Healthy" to response.healthy.toString()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ResponseCard(
|
||||
title: String,
|
||||
status: String,
|
||||
timestamp: String,
|
||||
service: String,
|
||||
additionalInfo: Map<String, String> = emptyMap()
|
||||
) {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.surfaceVariant
|
||||
)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
|
||||
InfoRow("Status", status)
|
||||
InfoRow("Timestamp", timestamp)
|
||||
InfoRow("Service", service)
|
||||
|
||||
additionalInfo.forEach { (key, value) ->
|
||||
InfoRow(key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InfoRow(label: String, value: String) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Text(
|
||||
text = "$label:",
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
Text(text = value)
|
||||
}
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
package at.mocode.clients.pingfeature
|
||||
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import at.mocode.ping.api.PingResponse
|
||||
import at.mocode.ping.api.EnhancedPingResponse
|
||||
import at.mocode.ping.api.HealthResponse
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
data class PingUiState(
|
||||
val isLoading: Boolean = false,
|
||||
val simplePingResponse: PingResponse? = null,
|
||||
val enhancedPingResponse: EnhancedPingResponse? = null,
|
||||
val healthResponse: HealthResponse? = null,
|
||||
val errorMessage: String? = null
|
||||
)
|
||||
|
||||
class PingViewModel : ViewModel() {
|
||||
private val apiClient = PingApiClient()
|
||||
|
||||
var uiState by mutableStateOf(PingUiState())
|
||||
private set
|
||||
|
||||
fun performSimplePing() {
|
||||
viewModelScope.launch {
|
||||
uiState = uiState.copy(isLoading = true, errorMessage = null)
|
||||
try {
|
||||
val response = apiClient.simplePing()
|
||||
uiState = uiState.copy(
|
||||
isLoading = false,
|
||||
simplePingResponse = response
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
uiState = uiState.copy(
|
||||
isLoading = false,
|
||||
errorMessage = "Simple ping failed: ${e.message}"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun performEnhancedPing(simulate: Boolean = false) {
|
||||
viewModelScope.launch {
|
||||
uiState = uiState.copy(isLoading = true, errorMessage = null)
|
||||
try {
|
||||
val response = apiClient.enhancedPing(simulate)
|
||||
uiState = uiState.copy(
|
||||
isLoading = false,
|
||||
enhancedPingResponse = response
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
uiState = uiState.copy(
|
||||
isLoading = false,
|
||||
errorMessage = "Enhanced ping failed: ${e.message}"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun performHealthCheck() {
|
||||
viewModelScope.launch {
|
||||
uiState = uiState.copy(isLoading = true, errorMessage = null)
|
||||
try {
|
||||
val response = apiClient.healthCheck()
|
||||
uiState = uiState.copy(
|
||||
isLoading = false,
|
||||
healthResponse = response
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
uiState = uiState.copy(
|
||||
isLoading = false,
|
||||
errorMessage = "Health check failed: ${e.message}"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun clearError() {
|
||||
uiState = uiState.copy(errorMessage = null)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user