Fix sport squadra su partite basket e ruoli organico per board.

Le partite ereditano lo sport_key della squadra alla creazione invece del default pallavolo; l'organico mostra ruoli per board (basket, volley, timed) e il wizard Android usa lo sport del team per overlay e icona.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-09 22:32:36 +02:00
parent 01b7a3c96b
commit 34c97f4447
9 changed files with 136 additions and 49 deletions

View File

@@ -50,6 +50,16 @@ private const val DEFAULT_PERIOD_DURATION_SECS = 600
private const val DEFAULT_OVERTIME_DURATION_SECS = 300
private const val DEFAULT_OPPONENT_COLOR = "#1E3A8A"
private fun overlayLabel(key: String): String =
OverlayKind.fromApi(key).name.lowercase().replaceFirstChar { it.uppercase() }
/** True se la partita ha un overlay diverso dal default dello sport. */
private fun matchHasCustomOverlay(match: Match, sportDefaultOverlay: String?): Boolean {
val default = sportDefaultOverlay ?: return false
val current = match.overlayKind ?: return false
return current != default
}
/** True se la partita ha regole non standard rispetto ai default del board. */
private fun matchHasCustomScoring(match: Match, boardType: String): Boolean {
val rules = match.scoringRules ?: return false
@@ -136,22 +146,41 @@ fun StepMatchScreen(
var saving by remember { mutableStateOf(false) }
var sports by remember { mutableStateOf<List<SportOption>>(emptyList()) }
var selectedSportKey by remember { mutableStateOf(match.sportKey) }
var selectedOverlay by remember { mutableStateOf(match.overlayKind ?: match.effectiveOverlayKind) }
var customOverlay by remember { mutableStateOf(false) }
var selectedOverlay by remember { mutableStateOf(match.effectiveOverlayKind) }
LaunchedEffect(Unit) {
runCatching { container.matchRepository.fetchSports() }
.onSuccess { sports = it }
}
val currentSport = sports.find { it.key == selectedSportKey }
?: sports.find { it.key == homeTeam?.sportKey }
val teamSportKey = homeTeam?.sportKey?.takeIf { it.isNotBlank() }
val effectiveSportKey = teamSportKey ?: selectedSportKey
val currentSport = sports.find { it.key == effectiveSportKey }
val allowedOverlays = currentSport?.allowedOverlays.orEmpty()
val defaultOverlay = currentSport?.overlay ?: match.effectiveOverlayKind
val boardType = currentSport?.board ?: homeTeam?.boardType ?: match.boardType
LaunchedEffect(boardType, match.id) {
customRules = matchHasCustomScoring(match, boardType)
}
LaunchedEffect(currentSport?.overlay, match.id) {
customOverlay = matchHasCustomOverlay(match, currentSport?.overlay)
selectedOverlay = when {
customOverlay -> match.overlayKind ?: match.effectiveOverlayKind
else -> defaultOverlay
}
}
LaunchedEffect(customOverlay, defaultOverlay) {
if (!customOverlay) {
selectedOverlay = defaultOverlay
} else if (selectedOverlay !in allowedOverlays) {
selectedOverlay = defaultOverlay
}
}
Column(
Modifier
.verticalScroll(rememberScrollState())
@@ -214,31 +243,34 @@ fun StepMatchScreen(
)
}
Spacer(Modifier.height(16.dp))
WizardReadOnlyField(
label = "Sport",
value = currentSport?.label ?: match.sportLabel ?: selectedSportKey,
)
if (allowedOverlays.isNotEmpty()) {
Spacer(Modifier.height(12.dp))
Text("Overlay video", style = MaterialTheme.typography.bodyMedium)
Spacer(Modifier.height(8.dp))
allowedOverlays.forEach { overlayKey ->
val selected = selectedOverlay == overlayKey
val label = OverlayKind.fromApi(overlayKey).name.lowercase()
.replaceFirstChar { it.uppercase() }
if (selected) {
MatchPrimaryButton(
label = label,
onClick = { selectedOverlay = overlayKey },
modifier = Modifier.fillMaxWidth().padding(bottom = 6.dp),
)
} else {
MatchSecondaryButton(
label = label,
onClick = { selectedOverlay = overlayKey },
modifier = Modifier.fillMaxWidth().padding(bottom = 6.dp),
)
Spacer(Modifier.height(20.dp))
Row(
Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Text("Overlay video personalizzato", modifier = Modifier.weight(1f))
Switch(checked = customOverlay, onCheckedChange = { customOverlay = it })
}
if (customOverlay) {
Spacer(Modifier.height(12.dp))
allowedOverlays.forEach { overlayKey ->
val selected = selectedOverlay == overlayKey
val label = overlayLabel(overlayKey)
if (selected) {
MatchPrimaryButton(
label = label,
onClick = { selectedOverlay = overlayKey },
modifier = Modifier.fillMaxWidth().padding(bottom = 6.dp),
)
} else {
MatchSecondaryButton(
label = label,
onClick = { selectedOverlay = overlayKey },
modifier = Modifier.fillMaxWidth().padding(bottom = 6.dp),
)
}
}
}
}
@@ -440,9 +472,11 @@ fun StepMatchScreen(
category = campionato.trim(),
opponentPrimaryColor = opponentPrimaryColor,
scoringRules = resolvedRules,
sportKey = selectedSportKey,
overlayKind = selectedOverlay.takeIf {
it != currentSport?.overlay
sportKey = effectiveSportKey,
overlayKind = if (customOverlay && selectedOverlay != defaultOverlay) {
selectedOverlay
} else {
""
},
opponentLogoUri = opponentLogoUri,
)

View File

@@ -3,6 +3,7 @@ package com.matchlivetv.match_live_tv.ui.wizard
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material3.CircularProgressIndicator
@@ -25,9 +26,11 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.matchlivetv.match_live_tv.data.AppContainer
import com.matchlivetv.match_live_tv.domain.Match
import com.matchlivetv.match_live_tv.domain.StreamSession
import com.matchlivetv.match_live_tv.domain.Team
import com.matchlivetv.match_live_tv.ui.components.MatchScreenScaffold
import com.matchlivetv.match_live_tv.ui.theme.MatchColors
import kotlinx.coroutines.launch
@@ -46,6 +49,7 @@ fun WizardShellScreen(
val snackbarHostState = remember { SnackbarHostState() }
var loading by remember { mutableStateOf(true) }
var match by remember { mutableStateOf<Match?>(null) }
var homeTeam by remember { mutableStateOf<Team?>(null) }
val currentStep = step.coerceIn(1, 3)
fun showError(message: String) {
@@ -55,9 +59,11 @@ fun WizardShellScreen(
LaunchedEffect(matchId) {
loading = true
runCatching { container.matchRepository.fetchMatch(matchId) }
.onSuccess {
match = it
container.wizardSession.match = it
.onSuccess { loaded ->
match = loaded
container.wizardSession.match = loaded
runCatching { container.matchRepository.fetchTeam(loaded.teamId) }
.onSuccess { homeTeam = it }
}
loading = false
}
@@ -71,6 +77,19 @@ fun WizardShellScreen(
Icon(Icons.Default.Close, contentDescription = "Chiudi", tint = Color.White)
}
},
actions = {
if (currentStep == 1) {
val badgeSportKey = homeTeam?.sportKey ?: match?.sportKey.orEmpty()
if (badgeSportKey.isNotBlank()) {
SportWizardBadge(
sportKey = badgeSportKey,
boardType = homeTeam?.boardType ?: match?.boardType ?: "volley",
sportLabel = match?.sportLabel,
modifier = Modifier.padding(end = 12.dp),
)
}
}
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MatchColors.Background,
titleContentColor = Color.White,