Rimuovi replay da app, fix live web e sync punteggi broadcast.
La pagina live mostra solo l'overlay nel video (niente tabellone HTML); l'app nativa non espone più l'archivio replay, il toggle regole punteggio parte spento e i controlli in diretta seguono il punteggio dalla regia. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -12,7 +12,7 @@ android {
|
||||
applicationId = "com.matchlivetv.match_live_tv"
|
||||
minSdk = 24
|
||||
targetSdk = 35
|
||||
versionCode = 15
|
||||
versionCode = 16
|
||||
versionName = "2.0.0-native"
|
||||
|
||||
val apiBaseUrl = project.findProperty("API_BASE_URL") as String?
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.matchlivetv.match_live_tv.data.api
|
||||
|
||||
import com.matchlivetv.match_live_tv.domain.AuthTokens
|
||||
import com.matchlivetv.match_live_tv.domain.Match
|
||||
import com.matchlivetv.match_live_tv.domain.Recording
|
||||
import com.matchlivetv.match_live_tv.domain.ScoringRules
|
||||
import com.matchlivetv.match_live_tv.domain.StreamSession
|
||||
import com.matchlivetv.match_live_tv.domain.Team
|
||||
@@ -230,6 +229,12 @@ fun ScoringRules.toBody() = ScoringRulesBody(
|
||||
minPointLead = minPointLead,
|
||||
)
|
||||
|
||||
fun ScoringRulesBody.toMap(): Map<String, Int> = mapOf(
|
||||
"points_per_set" to pointsPerSet,
|
||||
"points_deciding_set" to pointsDecidingSet,
|
||||
"min_point_lead" to minPointLead,
|
||||
)
|
||||
|
||||
data class UpdateMatchBodyFull(
|
||||
@Json(name = "opponent_name") val opponentName: String,
|
||||
val location: String? = null,
|
||||
@@ -237,7 +242,7 @@ data class UpdateMatchBodyFull(
|
||||
@Json(name = "sets_to_win") val setsToWin: Int,
|
||||
val category: String? = null,
|
||||
@Json(name = "opponent_primary_color") val opponentPrimaryColor: String? = null,
|
||||
@Json(name = "scoring_rules") val scoringRules: ScoringRulesBody? = null,
|
||||
@Json(name = "scoring_rules") val scoringRules: Map<String, Int>? = null,
|
||||
)
|
||||
|
||||
data class UpdateTeamRequest(val team: UpdateTeamBody)
|
||||
@@ -257,43 +262,6 @@ data class NetworkTestResponse(
|
||||
@Json(name = "target_fps") val targetFps: Int? = null,
|
||||
)
|
||||
|
||||
data class RecordingDto(
|
||||
val id: String,
|
||||
val title: String? = null,
|
||||
@Json(name = "opponent_name") val opponentName: String? = null,
|
||||
@Json(name = "team_name") val teamName: String? = null,
|
||||
val status: String? = null,
|
||||
@Json(name = "status_label") val statusLabel: String? = null,
|
||||
@Json(name = "recorded_at") val recordedAt: String? = null,
|
||||
@Json(name = "ended_at") val endedAt: String? = null,
|
||||
@Json(name = "duration_label") val durationLabel: String? = null,
|
||||
@Json(name = "views_label") val viewsLabel: String? = null,
|
||||
@Json(name = "replay_url") val replayUrl: String? = null,
|
||||
@Json(name = "thumbnail_url") val thumbnailUrl: String? = null,
|
||||
@Json(name = "download_enabled") val downloadEnabled: Boolean? = null,
|
||||
@Json(name = "youtube_watch_url") val youtubeWatchUrl: String? = null,
|
||||
@Json(name = "expires_at") val expiresAt: String? = null,
|
||||
@Json(name = "title_or_default") val titleOrDefault: String? = null,
|
||||
) {
|
||||
fun toDomain() = Recording(
|
||||
id = id,
|
||||
title = title ?: titleOrDefault ?: "Replay",
|
||||
opponentName = opponentName.orEmpty(),
|
||||
teamName = teamName.orEmpty(),
|
||||
status = status ?: "processing",
|
||||
statusLabel = statusLabel ?: status.orEmpty(),
|
||||
recordedAt = recordedAt,
|
||||
endedAt = endedAt,
|
||||
durationLabel = durationLabel,
|
||||
viewsLabel = viewsLabel,
|
||||
replayUrl = replayUrl,
|
||||
thumbnailUrl = thumbnailUrl,
|
||||
downloadEnabled = downloadEnabled ?: false,
|
||||
youtubeWatchUrl = youtubeWatchUrl,
|
||||
expiresAt = expiresAt,
|
||||
)
|
||||
}
|
||||
|
||||
data class RegiaLinkResponse(
|
||||
@Json(name = "regia_url") val regiaUrl: String,
|
||||
)
|
||||
|
||||
@@ -44,9 +44,6 @@ interface MatchLiveApi {
|
||||
@Part logoFile: MultipartBody.Part?,
|
||||
): TeamDto
|
||||
|
||||
@GET("teams/{teamId}/recordings")
|
||||
suspend fun recordings(@Path("teamId") teamId: String): List<RecordingDto>
|
||||
|
||||
@GET("teams/{teamId}/matches")
|
||||
suspend fun matches(@Path("teamId") teamId: String): List<MatchDto>
|
||||
|
||||
@@ -126,6 +123,4 @@ interface MatchLiveApi {
|
||||
@Body body: TelemetryRequest,
|
||||
)
|
||||
|
||||
@GET("recordings/{recordingId}/download")
|
||||
suspend fun recordingDownload(@Path("recordingId") recordingId: String): Map<String, String>
|
||||
}
|
||||
|
||||
@@ -127,13 +127,13 @@ class SessionCableService(
|
||||
}
|
||||
|
||||
private fun parseScore(data: Map<String, Any?>): ScoreState {
|
||||
val partialsRaw = data["set_partials"] as? List<*> ?: emptyList<Any>()
|
||||
val partialsRaw = data["set_partials"] as? List<*> ?: data["setPartials"] as? List<*> ?: emptyList<Any>()
|
||||
return ScoreState(
|
||||
homeSets = (data["home_sets"] as? Number)?.toInt() ?: 0,
|
||||
awaySets = (data["away_sets"] as? Number)?.toInt() ?: 0,
|
||||
homePoints = (data["home_points"] as? Number)?.toInt() ?: 0,
|
||||
awayPoints = (data["away_points"] as? Number)?.toInt() ?: 0,
|
||||
currentSet = (data["current_set"] as? Number)?.toInt() ?: 1,
|
||||
homeSets = intField(data, "home_sets", "homeSets"),
|
||||
awaySets = intField(data, "away_sets", "awaySets"),
|
||||
homePoints = intField(data, "home_points", "homePoints"),
|
||||
awayPoints = intField(data, "away_points", "awayPoints"),
|
||||
currentSet = intField(data, "current_set", "currentSet").coerceAtLeast(1),
|
||||
setPartials = partialsRaw.mapNotNull { item ->
|
||||
val row = item as? Map<*, *> ?: return@mapNotNull null
|
||||
com.matchlivetv.match_live_tv.domain.SetPartial(
|
||||
@@ -147,6 +147,15 @@ class SessionCableService(
|
||||
)
|
||||
}
|
||||
|
||||
private fun intField(data: Map<String, Any?>, snake: String, camel: String): Int {
|
||||
val raw = data[snake] ?: data[camel]
|
||||
return when (raw) {
|
||||
is Number -> raw.toInt()
|
||||
is String -> raw.toIntOrNull() ?: 0
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "SessionCableService"
|
||||
}
|
||||
|
||||
@@ -9,12 +9,12 @@ import com.matchlivetv.match_live_tv.data.api.CreateMatchRequest
|
||||
import com.matchlivetv.match_live_tv.data.api.MatchLiveApi
|
||||
import com.matchlivetv.match_live_tv.data.api.MultipartBodies
|
||||
import com.matchlivetv.match_live_tv.data.api.ScoringRulesBody
|
||||
import com.matchlivetv.match_live_tv.data.api.toMap
|
||||
import com.matchlivetv.match_live_tv.data.api.UpdateMatchBodyFull
|
||||
import com.matchlivetv.match_live_tv.data.api.UpdateMatchRequestFull
|
||||
import com.matchlivetv.match_live_tv.data.api.UpdateTeamBody
|
||||
import com.matchlivetv.match_live_tv.data.api.UpdateTeamRequest
|
||||
import com.matchlivetv.match_live_tv.domain.Match
|
||||
import com.matchlivetv.match_live_tv.domain.Recording
|
||||
import com.matchlivetv.match_live_tv.domain.Team
|
||||
import com.matchlivetv.match_live_tv.domain.coachHubVisible
|
||||
import kotlinx.coroutines.flow.first
|
||||
@@ -55,9 +55,6 @@ class MatchRepository(
|
||||
return fetchMatchesForTeam(team.id)
|
||||
}
|
||||
|
||||
suspend fun fetchRecordings(teamId: String): List<Recording> =
|
||||
api.recordings(teamId).map { it.toDomain() }
|
||||
|
||||
suspend fun selectTeam(teamId: String) {
|
||||
tokenStore.saveActiveTeamId(teamId)
|
||||
}
|
||||
@@ -127,7 +124,7 @@ class MatchRepository(
|
||||
setsToWin = setsToWin,
|
||||
category = category?.takeIf { it.isNotBlank() },
|
||||
opponentPrimaryColor = opponentPrimaryColor,
|
||||
scoringRules = scoringRules,
|
||||
scoringRules = scoringRules?.toMap() ?: emptyMap(),
|
||||
)
|
||||
if (opponentLogoUri != null) {
|
||||
return api.updateMatchMultipart(
|
||||
@@ -154,11 +151,6 @@ class MatchRepository(
|
||||
api.deleteMatch(matchId)
|
||||
}
|
||||
|
||||
suspend fun recordingDownloadUrl(recordingId: String): String {
|
||||
val body = api.recordingDownload(recordingId)
|
||||
return body["download_url"] ?: error("URL download mancante")
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val matchComparator = compareBy<Match, Instant?>(nullsLast()) { match ->
|
||||
parseApiInstant(match.scheduledAt)
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.matchlivetv.match_live_tv.domain.ScoreState
|
||||
import com.matchlivetv.match_live_tv.domain.SetPartial
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
@@ -35,6 +36,12 @@ class ScoreController(
|
||||
}
|
||||
|
||||
fun applyRemote(remote: ScoreState) {
|
||||
scope.launch {
|
||||
applyRemoteOnMain(remote)
|
||||
}
|
||||
}
|
||||
|
||||
private fun applyRemoteOnMain(remote: ScoreState) {
|
||||
if (remote == _score.value) return
|
||||
val now = System.currentTimeMillis()
|
||||
if (now < suppressRemoteUntilMs && remote.progressKey() <= _score.value.progressKey()) {
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
package com.matchlivetv.match_live_tv.domain
|
||||
|
||||
data class Recording(
|
||||
val id: String,
|
||||
val title: String,
|
||||
val opponentName: String,
|
||||
val teamName: String,
|
||||
val status: String,
|
||||
val statusLabel: String,
|
||||
val recordedAt: String? = null,
|
||||
val endedAt: String? = null,
|
||||
val durationLabel: String? = null,
|
||||
val viewsLabel: String? = null,
|
||||
val replayUrl: String? = null,
|
||||
val thumbnailUrl: String? = null,
|
||||
val downloadEnabled: Boolean = false,
|
||||
val youtubeWatchUrl: String? = null,
|
||||
val expiresAt: String? = null,
|
||||
) {
|
||||
val isReady: Boolean get() = status == "ready"
|
||||
val isProcessing: Boolean get() = status == "processing"
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
package com.matchlivetv.match_live_tv.ui.archive
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
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.platform.LocalContext
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.matchlivetv.match_live_tv.data.AppContainer
|
||||
import com.matchlivetv.match_live_tv.domain.Recording
|
||||
import com.matchlivetv.match_live_tv.domain.Team
|
||||
import com.matchlivetv.match_live_tv.ui.components.MatchPrimaryButton
|
||||
import com.matchlivetv.match_live_tv.ui.components.MatchScreenScaffold
|
||||
import com.matchlivetv.match_live_tv.ui.theme.MatchColors
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ArchiveScreen(
|
||||
container: AppContainer,
|
||||
onBack: () -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
var loading by remember { mutableStateOf(true) }
|
||||
var team by remember { mutableStateOf<Team?>(null) }
|
||||
var recordings by remember { mutableStateOf<List<Recording>>(emptyList()) }
|
||||
var error by remember { mutableStateOf<String?>(null) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
loading = true
|
||||
runCatching {
|
||||
val teams = container.matchRepository.fetchTeams()
|
||||
val active = container.matchRepository.resolveActiveTeam(teams)
|
||||
team = active
|
||||
recordings = active?.let { container.matchRepository.fetchRecordings(it.id) }.orEmpty()
|
||||
}.onFailure { error = it.message }
|
||||
loading = false
|
||||
}
|
||||
|
||||
MatchScreenScaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text("Replay") },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onBack) {
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, null, tint = Color.White)
|
||||
}
|
||||
},
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = MatchColors.Background,
|
||||
titleContentColor = Color.White,
|
||||
),
|
||||
)
|
||||
},
|
||||
) {
|
||||
when {
|
||||
loading -> Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
CircularProgressIndicator(color = MatchColors.PrimaryRed)
|
||||
}
|
||||
error != null -> Column(
|
||||
Modifier.fillMaxSize().padding(24.dp),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(error!!, color = MatchColors.PrimaryRed, textAlign = TextAlign.Center)
|
||||
}
|
||||
team != null && !team!!.recordingsEnabled -> Column(
|
||||
Modifier.fillMaxSize().padding(24.dp),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
"Replay disponibili con Premium Light o Full",
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
Text(
|
||||
"Light: 30 giorni · Full: 90 giorni",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.padding(top = 8.dp),
|
||||
)
|
||||
team?.billingUrl?.let { url ->
|
||||
MatchPrimaryButton(
|
||||
label = "SCOPRI PREMIUM",
|
||||
onClick = {
|
||||
context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
|
||||
},
|
||||
modifier = Modifier.padding(top = 16.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
recordings.isEmpty() -> Box(Modifier.fillMaxSize().padding(24.dp), contentAlignment = Alignment.Center) {
|
||||
Text(
|
||||
"Nessun replay ancora.\nAl termine delle dirette Premium le registrazioni compaiono qui.",
|
||||
textAlign = TextAlign.Center,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
else -> LazyColumn(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
items(recordings, key = { it.id }) { recording ->
|
||||
Card(
|
||||
colors = CardDefaults.cardColors(containerColor = MatchColors.Surface),
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onClick = {
|
||||
if (recording.isReady) {
|
||||
recording.replayUrl?.let { url ->
|
||||
context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
|
||||
}
|
||||
}
|
||||
},
|
||||
) {
|
||||
Column(Modifier.padding(16.dp)) {
|
||||
Text(recording.title, style = MaterialTheme.typography.titleMedium)
|
||||
Text(
|
||||
listOfNotNull(
|
||||
recording.durationLabel,
|
||||
recording.viewsLabel,
|
||||
recording.statusLabel,
|
||||
).joinToString(" · "),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
if (recording.downloadEnabled && team?.phoneDownloadEnabled == true) {
|
||||
MatchPrimaryButton(
|
||||
label = "SCARICA MP4",
|
||||
onClick = {
|
||||
scope.launch {
|
||||
runCatching {
|
||||
container.matchRepository.recordingDownloadUrl(recording.id)
|
||||
}.onSuccess { url ->
|
||||
context.startActivity(
|
||||
Intent(Intent.ACTION_VIEW, Uri.parse(url)),
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = Modifier.padding(top = 8.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -233,8 +233,8 @@ fun BroadcastControlsOverlay(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
"${score.homeSets} - ${score.awaySets}",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
"${score.homePoints} - ${score.awayPoints}",
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = Color.White,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
@@ -243,6 +243,13 @@ fun BroadcastControlsOverlay(
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MatchColors.TextSecondary,
|
||||
)
|
||||
if (score.homeSets > 0 || score.awaySets > 0) {
|
||||
Text(
|
||||
"Set vinti ${score.homeSets}-${score.awaySets}",
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MatchColors.TextSecondary,
|
||||
)
|
||||
}
|
||||
}
|
||||
TeamScoreColumn(
|
||||
teamLabel = "OSPITE",
|
||||
|
||||
@@ -17,6 +17,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -201,16 +202,23 @@ fun BroadcastScreen(
|
||||
loading = false
|
||||
}
|
||||
|
||||
LaunchedEffect(score, match, metrics.phase) {
|
||||
LaunchedEffect(match?.id) {
|
||||
val currentMatch = match ?: return@LaunchedEffect
|
||||
val authToken = container.authRepository.currentSession()?.accessToken
|
||||
val homeLogoUrl = resolveMediaUrl(currentMatch.homeLogoUrl)
|
||||
val awayLogoUrl = resolveMediaUrl(currentMatch.opponentLogoUrl)
|
||||
OverlayLogoCache.preloadAll(
|
||||
context,
|
||||
listOf(homeLogoUrl, awayLogoUrl),
|
||||
listOf(
|
||||
resolveMediaUrl(currentMatch.homeLogoUrl),
|
||||
resolveMediaUrl(currentMatch.opponentLogoUrl),
|
||||
),
|
||||
bearerToken = authToken,
|
||||
)
|
||||
}
|
||||
|
||||
LaunchedEffect(score, match, metrics.phase) {
|
||||
val currentMatch = match ?: return@LaunchedEffect
|
||||
val homeLogoUrl = resolveMediaUrl(currentMatch.homeLogoUrl)
|
||||
val awayLogoUrl = resolveMediaUrl(currentMatch.opponentLogoUrl)
|
||||
container.broadcastCoordinator.updateOverlay(
|
||||
OverlayState(
|
||||
scoreboard = score.toScoreboardState(
|
||||
@@ -366,6 +374,7 @@ fun BroadcastScreen(
|
||||
}
|
||||
|
||||
if (!loading && permissions.granted && currentMatch != null && scoreActions != null) {
|
||||
key(score.progressKey()) {
|
||||
BroadcastControlsOverlay(
|
||||
controlsVisible = controlsVisible,
|
||||
onToggleControls = { controlsVisible = !controlsVisible },
|
||||
@@ -436,6 +445,7 @@ fun BroadcastScreen(
|
||||
networkType = DeviceTelemetry.networkType(context),
|
||||
deviceHealth = deviceHealth,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
SnackbarHost(
|
||||
|
||||
@@ -62,7 +62,6 @@ fun MatchesScreen(
|
||||
container: AppContainer,
|
||||
onOpenSetup: (matchId: String) -> Unit,
|
||||
onOpenBroadcast: (sessionId: String) -> Unit,
|
||||
onOpenArchive: () -> Unit,
|
||||
onLogout: () -> Unit,
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
@@ -157,9 +156,6 @@ fun MatchesScreen(
|
||||
titleContentColor = Color.White,
|
||||
),
|
||||
actions = {
|
||||
IconButton(onClick = onOpenArchive) {
|
||||
Text("Archivio", color = MatchColors.TextSecondary)
|
||||
}
|
||||
IconButton(onClick = {
|
||||
scope.launch {
|
||||
container.authRepository.logout()
|
||||
|
||||
@@ -7,7 +7,6 @@ import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import androidx.navigation.navArgument
|
||||
import com.matchlivetv.match_live_tv.data.AppContainer
|
||||
import com.matchlivetv.match_live_tv.ui.archive.ArchiveScreen
|
||||
import com.matchlivetv.match_live_tv.ui.broadcast.BroadcastScreen
|
||||
import com.matchlivetv.match_live_tv.ui.login.LoginScreen
|
||||
import com.matchlivetv.match_live_tv.ui.matches.MatchesScreen
|
||||
@@ -50,9 +49,6 @@ fun AppNavHost(container: AppContainer) {
|
||||
onOpenBroadcast = { sessionId ->
|
||||
navController.navigate(Routes.broadcast(sessionId))
|
||||
},
|
||||
onOpenArchive = {
|
||||
navController.navigate(Routes.Archive)
|
||||
},
|
||||
onLogout = {
|
||||
navController.navigate(Routes.Login) {
|
||||
popUpTo(Routes.Matches) { inclusive = true }
|
||||
@@ -60,17 +56,6 @@ fun AppNavHost(container: AppContainer) {
|
||||
},
|
||||
)
|
||||
}
|
||||
composable(Routes.Archive) {
|
||||
ArchiveScreen(
|
||||
container = container,
|
||||
onBack = {
|
||||
navController.navigate(Routes.Matches) {
|
||||
popUpTo(Routes.Matches) { inclusive = false }
|
||||
launchSingleTop = true
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
composable(
|
||||
route = Routes.Setup,
|
||||
arguments = listOf(
|
||||
|
||||
@@ -4,7 +4,6 @@ object Routes {
|
||||
const val Splash = "splash"
|
||||
const val Login = "login"
|
||||
const val Matches = "matches"
|
||||
const val Archive = "archive"
|
||||
const val Setup = "setup/{matchId}/{step}"
|
||||
const val Broadcast = "broadcast/{sessionId}"
|
||||
|
||||
|
||||
@@ -45,6 +45,15 @@ private const val DEFAULT_POINTS_DECIDING_SET = 15
|
||||
private const val DEFAULT_MIN_POINT_LEAD = 2
|
||||
private const val DEFAULT_OPPONENT_COLOR = "#1E3A8A"
|
||||
|
||||
/** True solo se la partita ha regole non standard (non i default FIPAV). */
|
||||
private fun matchHasCustomScoring(match: Match): Boolean {
|
||||
if (match.setsToWin != DEFAULT_SETS_TO_WIN) return true
|
||||
val rules = match.scoringRules ?: return false
|
||||
return rules.pointsPerSet != DEFAULT_POINTS_PER_SET ||
|
||||
rules.pointsDecidingSet != DEFAULT_POINTS_DECIDING_SET ||
|
||||
rules.minPointLead != DEFAULT_MIN_POINT_LEAD
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun StepMatchScreen(
|
||||
container: AppContainer,
|
||||
@@ -91,9 +100,7 @@ fun StepMatchScreen(
|
||||
) { uri -> opponentLogoUri = uri }
|
||||
|
||||
val existingRules = match.scoringRules
|
||||
var customRules by remember {
|
||||
mutableStateOf(existingRules != null || match.setsToWin != DEFAULT_SETS_TO_WIN)
|
||||
}
|
||||
var customRules by remember { mutableStateOf(matchHasCustomScoring(match)) }
|
||||
var setsToWin by remember { mutableIntStateOf(match.setsToWin.coerceIn(2, 3)) }
|
||||
var pointsPerSet by remember {
|
||||
mutableIntStateOf(existingRules?.pointsPerSet ?: DEFAULT_POINTS_PER_SET)
|
||||
@@ -262,11 +269,7 @@ fun StepMatchScreen(
|
||||
minPointLead = existingRules?.minPointLead ?: DEFAULT_MIN_POINT_LEAD,
|
||||
)
|
||||
} else {
|
||||
ScoringRulesBody(
|
||||
pointsPerSet = DEFAULT_POINTS_PER_SET,
|
||||
pointsDecidingSet = DEFAULT_POINTS_DECIDING_SET,
|
||||
minPointLead = DEFAULT_MIN_POINT_LEAD,
|
||||
)
|
||||
null
|
||||
}
|
||||
|
||||
val initialHomeColor = normalizeHexColor(
|
||||
|
||||
Reference in New Issue
Block a user