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
This commit is contained in:
2026-08-06 17:17:37 +02:00
parent 3edf991cde
commit d03ac82a8a
@@ -17,7 +17,9 @@
package ch.parano.myicek.ui.screens package ch.parano.myicek.ui.screens
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer 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.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding 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.LazyColumn
import androidx.compose.foundation.lazy.items 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.Card
import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ExperimentalMaterial3Api 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.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@@ -42,11 +60,13 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier 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.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import ch.parano.myicek.ui.PlatformBackHandler 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.components.LoadingIndicator
import ch.parano.myicek.ui.theme.Dimens
import ch.parano.myicek.util.formatEventDate import ch.parano.myicek.util.formatEventDate
import ch.parano.myicek.util.formatEventTimeRange import ch.parano.myicek.util.formatEventTimeRange
import ch.parano.myicek.viewmodel.EventDetailViewModel import ch.parano.myicek.viewmodel.EventDetailViewModel
@@ -76,8 +96,11 @@ fun EventDetailScreen(
TopAppBar( TopAppBar(
title = { Text(eventTitle) }, title = { Text(eventTitle) },
navigationIcon = { navigationIcon = {
androidx.compose.material3.TextButton(onClick = onBack) { IconButton(onClick = onBack) {
Text("Retour") Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "Retour",
)
} }
}, },
) )
@@ -89,41 +112,39 @@ fun EventDetailScreen(
LoadingIndicator(modifier = Modifier.padding(padding)) LoadingIndicator(modifier = Modifier.padding(padding))
} }
state.error != null -> { state.error != null -> {
Column( ErrorState(
modifier = Modifier.fillMaxSize().padding(padding), icon = Icons.Filled.ErrorOutline,
horizontalAlignment = Alignment.CenterHorizontally, title = "Erreur",
verticalArrangement = Arrangement.Center, message = state.error!!,
) { onRetry = { viewModel.loadEventDetail(gameId, account) },
Text( modifier = Modifier.padding(padding),
text = state.error!!, )
color = MaterialTheme.colorScheme.error,
)
Spacer(modifier = Modifier.height(8.dp))
TextButton(onClick = { viewModel.loadEventDetail(gameId, account) }) {
Text("Reessayer")
}
}
} }
state.eventDetail != null -> { state.eventDetail != null -> {
val detail = state.eventDetail!! val detail = state.eventDetail!!
val hasPlayers = detail.convocation.available.isNotEmpty() val players = detail.convocation.available.sortedBy { it.number?.toIntOrNull() ?: Int.MAX_VALUE }
val hasStaff = detail.convocation.staff.isNotEmpty() val staff = detail.convocation.staff
val hasPlayers = players.isNotEmpty()
val hasStaff = staff.isNotEmpty()
LazyColumn( LazyColumn(
modifier = Modifier.fillMaxSize().padding(padding), modifier = Modifier.fillMaxSize().padding(padding),
contentPadding = androidx.compose.foundation.layout.PaddingValues(16.dp), contentPadding = androidx.compose.foundation.layout.PaddingValues(Dimens.lg),
verticalArrangement = Arrangement.spacedBy(16.dp), verticalArrangement = Arrangement.spacedBy(Dimens.lg),
) { ) {
item { item {
Card(modifier = Modifier.fillMaxWidth()) { Card(
modifier = Modifier.fillMaxWidth(),
elevation = CardDefaults.cardElevation(defaultElevation = 1.dp),
) {
Column( Column(
modifier = Modifier.padding(16.dp), modifier = Modifier.padding(Dimens.lg),
verticalArrangement = Arrangement.spacedBy(4.dp), verticalArrangement = Arrangement.spacedBy(Dimens.md),
) { ) {
Text("Type: ${detail.type}") InfoRow(Icons.Filled.Event, "Type", detail.type)
Text("Lieu: ${detail.place}") InfoRow(Icons.Filled.Place, "Lieu", detail.place)
Text("Date: ${formatEventDate(eventStart)}") InfoRow(Icons.Filled.CalendarMonth, "Date", formatEventDate(eventStart))
Text("Heure: ${formatEventTimeRange(eventStart, eventEnd)}") InfoRow(Icons.Filled.Schedule, "Heure", formatEventTimeRange(eventStart, eventEnd))
} }
} }
} }
@@ -133,49 +154,74 @@ fun EventDetailScreen(
Card( Card(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors( colors = CardDefaults.cardColors(
containerColor = Color(0xFFFFC107), containerColor = MaterialTheme.colorScheme.errorContainer,
), ),
) { ) {
Text( Row(
text = "Aucun joueur ni personnel convoque", modifier = Modifier.padding(Dimens.lg),
fontWeight = FontWeight.Bold, verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(16.dp), 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) { if (hasPlayers) {
item { item {
Text( Row(
text = "Joueurs (${detail.convocation.available.size})", verticalAlignment = Alignment.CenterVertically,
style = MaterialTheme.typography.titleLarge, horizontalArrangement = Arrangement.spacedBy(Dimens.sm),
) ) {
Text(
text = "Joueurs",
style = MaterialTheme.typography.titleMedium,
)
BadgedBox(badge = { Badge { Text("${players.size}") } }) {}
}
} }
item { item {
Card(modifier = Modifier.fillMaxWidth()) { Card(
Column(modifier = Modifier.padding(16.dp)) { modifier = Modifier.fillMaxWidth(),
detail.convocation.available.forEach { player -> elevation = CardDefaults.cardElevation(defaultElevation = 1.dp),
val position = player.position ?: "" ) {
val number = player.number ?: "" Column {
val muted = MaterialTheme.colorScheme.onSurfaceVariant players.forEach { player ->
Row( ListItem(
verticalAlignment = Alignment.CenterVertically, leadingContent = {
horizontalArrangement = Arrangement.spacedBy(6.dp), Box(
modifier = Modifier.fillMaxWidth(), modifier = Modifier
) { .size(40.dp)
Text( .clip(CircleShape)
if (number.isNotEmpty()) "#$number" else "", .background(MaterialTheme.colorScheme.primaryContainer),
color = muted, contentAlignment = Alignment.Center,
modifier = Modifier.width(40.dp), ) {
) Text(
Text("${player.fname} ${player.lname}") text = player.number?.let { "#$it" } ?: "",
Spacer(Modifier.weight(1f)) style = MaterialTheme.typography.labelMedium,
if (position.isNotEmpty()) { color = MaterialTheme.colorScheme.onPrimaryContainer,
Text(position, color = muted) )
} }
Text("(${player.dob.take(4)})", color = muted) },
} headlineContent = {
Text("${player.fname} ${player.lname}")
},
supportingContent = {
val parts = mutableListOf<String>()
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 { item {
Text( Text(
text = "Personnel", text = "Personnel",
style = MaterialTheme.typography.titleLarge, style = MaterialTheme.typography.titleMedium,
) )
} }
item { item {
Card(modifier = Modifier.fillMaxWidth()) { Card(
Column(modifier = Modifier.padding(16.dp)) { modifier = Modifier.fillMaxWidth(),
detail.convocation.staff.forEach { staff -> elevation = CardDefaults.cardElevation(defaultElevation = 1.dp),
val muted = MaterialTheme.colorScheme.onSurfaceVariant ) {
Row( Column {
verticalAlignment = Alignment.CenterVertically, staff.forEach { member ->
horizontalArrangement = Arrangement.spacedBy(6.dp), ListItem(
modifier = Modifier.fillMaxWidth(), leadingContent = {
) { Icon(
Text("${staff.fname} ${staff.lname}") imageVector = Icons.Filled.Person,
Spacer(Modifier.weight(1f)) contentDescription = null,
Text(staff.role, color = muted) 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, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center, 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,
)
}
}
}