From d03ac82a8a3b3c5b4f396f3b564cc8d445db36cd Mon Sep 17 00:00:00 2001 From: Rene Luria Date: Thu, 6 Aug 2026 17:17:37 +0200 Subject: [PATCH] feat: redesign EventDetailScreen with icon back, styled info, ListItem players - ArrowBack icon replaces TextButton('Retour') - Info card with label/value rows and leading icons - Players as ListItem with number avatar in primaryContainer circle - Staff as ListItem with role AssistChip - ErrorState shared component - errorContainer for empty convocation warning - French accents corrected --- .../myicek/ui/screens/EventDetailScreen.kt | 248 ++++++++++++------ 1 file changed, 170 insertions(+), 78 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/ch/parano/myicek/ui/screens/EventDetailScreen.kt b/composeApp/src/commonMain/kotlin/ch/parano/myicek/ui/screens/EventDetailScreen.kt index a32f6fc..1379ca4 100644 --- a/composeApp/src/commonMain/kotlin/ch/parano/myicek/ui/screens/EventDetailScreen.kt +++ b/composeApp/src/commonMain/kotlin/ch/parano/myicek/ui/screens/EventDetailScreen.kt @@ -17,7 +17,9 @@ package ch.parano.myicek.ui.screens +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -25,16 +27,32 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.CalendarMonth +import androidx.compose.material.icons.filled.ErrorOutline +import androidx.compose.material.icons.filled.Event +import androidx.compose.material.icons.filled.Person +import androidx.compose.material.icons.filled.Place +import androidx.compose.material.icons.filled.Schedule +import androidx.compose.material.icons.filled.Warning +import androidx.compose.material3.AssistChip +import androidx.compose.material3.AssistChipDefaults +import androidx.compose.material3.Badge +import androidx.compose.material3.BadgedBox import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.ListItem import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -42,11 +60,13 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color +import androidx.compose.ui.draw.clip import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import ch.parano.myicek.ui.PlatformBackHandler +import ch.parano.myicek.ui.components.ErrorState import ch.parano.myicek.ui.components.LoadingIndicator +import ch.parano.myicek.ui.theme.Dimens import ch.parano.myicek.util.formatEventDate import ch.parano.myicek.util.formatEventTimeRange import ch.parano.myicek.viewmodel.EventDetailViewModel @@ -76,8 +96,11 @@ fun EventDetailScreen( TopAppBar( title = { Text(eventTitle) }, navigationIcon = { - androidx.compose.material3.TextButton(onClick = onBack) { - Text("Retour") + IconButton(onClick = onBack) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = "Retour", + ) } }, ) @@ -89,41 +112,39 @@ fun EventDetailScreen( LoadingIndicator(modifier = Modifier.padding(padding)) } state.error != null -> { - Column( - modifier = Modifier.fillMaxSize().padding(padding), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - ) { - Text( - text = state.error!!, - color = MaterialTheme.colorScheme.error, - ) - Spacer(modifier = Modifier.height(8.dp)) - TextButton(onClick = { viewModel.loadEventDetail(gameId, account) }) { - Text("Reessayer") - } - } + ErrorState( + icon = Icons.Filled.ErrorOutline, + title = "Erreur", + message = state.error!!, + onRetry = { viewModel.loadEventDetail(gameId, account) }, + modifier = Modifier.padding(padding), + ) } state.eventDetail != null -> { val detail = state.eventDetail!! - val hasPlayers = detail.convocation.available.isNotEmpty() - val hasStaff = detail.convocation.staff.isNotEmpty() + val players = detail.convocation.available.sortedBy { it.number?.toIntOrNull() ?: Int.MAX_VALUE } + val staff = detail.convocation.staff + val hasPlayers = players.isNotEmpty() + val hasStaff = staff.isNotEmpty() LazyColumn( modifier = Modifier.fillMaxSize().padding(padding), - contentPadding = androidx.compose.foundation.layout.PaddingValues(16.dp), - verticalArrangement = Arrangement.spacedBy(16.dp), + contentPadding = androidx.compose.foundation.layout.PaddingValues(Dimens.lg), + verticalArrangement = Arrangement.spacedBy(Dimens.lg), ) { item { - Card(modifier = Modifier.fillMaxWidth()) { + Card( + modifier = Modifier.fillMaxWidth(), + elevation = CardDefaults.cardElevation(defaultElevation = 1.dp), + ) { Column( - modifier = Modifier.padding(16.dp), - verticalArrangement = Arrangement.spacedBy(4.dp), + modifier = Modifier.padding(Dimens.lg), + verticalArrangement = Arrangement.spacedBy(Dimens.md), ) { - Text("Type: ${detail.type}") - Text("Lieu: ${detail.place}") - Text("Date: ${formatEventDate(eventStart)}") - Text("Heure: ${formatEventTimeRange(eventStart, eventEnd)}") + InfoRow(Icons.Filled.Event, "Type", detail.type) + InfoRow(Icons.Filled.Place, "Lieu", detail.place) + InfoRow(Icons.Filled.CalendarMonth, "Date", formatEventDate(eventStart)) + InfoRow(Icons.Filled.Schedule, "Heure", formatEventTimeRange(eventStart, eventEnd)) } } } @@ -133,49 +154,74 @@ fun EventDetailScreen( Card( modifier = Modifier.fillMaxWidth(), colors = CardDefaults.cardColors( - containerColor = Color(0xFFFFC107), + containerColor = MaterialTheme.colorScheme.errorContainer, ), ) { - Text( - text = "Aucun joueur ni personnel convoque", - fontWeight = FontWeight.Bold, - modifier = Modifier.padding(16.dp), - ) + Row( + modifier = Modifier.padding(Dimens.lg), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(Dimens.sm), + ) { + Icon( + imageVector = Icons.Filled.Warning, + contentDescription = null, + tint = MaterialTheme.colorScheme.onErrorContainer, + ) + Text( + text = "Aucun joueur ni personnel convoqué", + color = MaterialTheme.colorScheme.onErrorContainer, + ) + } } } } if (hasPlayers) { item { - Text( - text = "Joueurs (${detail.convocation.available.size})", - style = MaterialTheme.typography.titleLarge, - ) + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(Dimens.sm), + ) { + Text( + text = "Joueurs", + style = MaterialTheme.typography.titleMedium, + ) + BadgedBox(badge = { Badge { Text("${players.size}") } }) {} + } } item { - Card(modifier = Modifier.fillMaxWidth()) { - Column(modifier = Modifier.padding(16.dp)) { - detail.convocation.available.forEach { player -> - val position = player.position ?: "" - val number = player.number ?: "" - val muted = MaterialTheme.colorScheme.onSurfaceVariant - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(6.dp), - modifier = Modifier.fillMaxWidth(), - ) { - Text( - if (number.isNotEmpty()) "#$number" else "", - color = muted, - modifier = Modifier.width(40.dp), - ) - Text("${player.fname} ${player.lname}") - Spacer(Modifier.weight(1f)) - if (position.isNotEmpty()) { - Text(position, color = muted) - } - Text("(${player.dob.take(4)})", color = muted) - } + Card( + modifier = Modifier.fillMaxWidth(), + elevation = CardDefaults.cardElevation(defaultElevation = 1.dp), + ) { + Column { + players.forEach { player -> + ListItem( + leadingContent = { + Box( + modifier = Modifier + .size(40.dp) + .clip(CircleShape) + .background(MaterialTheme.colorScheme.primaryContainer), + contentAlignment = Alignment.Center, + ) { + Text( + text = player.number?.let { "#$it" } ?: "", + style = MaterialTheme.typography.labelMedium, + color = MaterialTheme.colorScheme.onPrimaryContainer, + ) + } + }, + headlineContent = { + Text("${player.fname} ${player.lname}") + }, + supportingContent = { + val parts = mutableListOf() + if (!player.position.isNullOrBlank()) parts.add(player.position) + if (player.dob.isNotBlank()) parts.add("(née ${player.dob.take(4)})") + if (parts.isNotEmpty()) Text(parts.joinToString(" ")) + }, + ) } } } @@ -186,23 +232,38 @@ fun EventDetailScreen( item { Text( text = "Personnel", - style = MaterialTheme.typography.titleLarge, + style = MaterialTheme.typography.titleMedium, ) } item { - Card(modifier = Modifier.fillMaxWidth()) { - Column(modifier = Modifier.padding(16.dp)) { - detail.convocation.staff.forEach { staff -> - val muted = MaterialTheme.colorScheme.onSurfaceVariant - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(6.dp), - modifier = Modifier.fillMaxWidth(), - ) { - Text("${staff.fname} ${staff.lname}") - Spacer(Modifier.weight(1f)) - Text(staff.role, color = muted) - } + Card( + modifier = Modifier.fillMaxWidth(), + elevation = CardDefaults.cardElevation(defaultElevation = 1.dp), + ) { + Column { + staff.forEach { member -> + ListItem( + leadingContent = { + Icon( + imageVector = Icons.Filled.Person, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurfaceVariant, + ) + }, + headlineContent = { + Text("${member.fname} ${member.lname}") + }, + trailingContent = { + AssistChip( + onClick = {}, + label = { Text(member.role) }, + colors = AssistChipDefaults.assistChipColors( + containerColor = MaterialTheme.colorScheme.secondaryContainer, + labelColor = MaterialTheme.colorScheme.onSecondaryContainer, + ), + ) + }, + ) } } } @@ -216,9 +277,40 @@ fun EventDetailScreen( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, ) { - Text("Aucune donnee disponible") + Text("Aucune donnée disponible") } } } } } + +@Composable +private fun InfoRow( + icon: androidx.compose.ui.graphics.vector.ImageVector, + label: String, + value: String, +) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(Dimens.sm), + ) { + Icon( + imageVector = icon, + contentDescription = null, + modifier = Modifier.size(18.dp), + tint = MaterialTheme.colorScheme.onSurfaceVariant, + ) + Column { + Text( + text = label.uppercase(), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + Text( + text = value, + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurface, + ) + } + } +}