feat: add MatchupRow and Schedule Badge to MatchDetailsScreen.kt

This commit is contained in:
Leonardo Murça 2025-07-19 11:02:50 -03:00
parent 877433eff6
commit 75cf97a3a4
4 changed files with 146 additions and 14 deletions

View file

@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.Row
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.requiredWidthIn
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
@ -25,6 +26,7 @@ import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import coil3.compose.AsyncImage import coil3.compose.AsyncImage
@ -59,6 +61,7 @@ fun MatchCard(
rightOpponentId = rightOpponent.id, rightOpponentId = rightOpponent.id,
leagueAndSerieName = leagueAndSerieName, leagueAndSerieName = leagueAndSerieName,
scheduleString = scheduleConfig.first, scheduleString = scheduleConfig.first,
status = match.status
) )
) )
}, colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface) }, colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface)
@ -92,7 +95,10 @@ private fun MatchupRow(leftOpponent: Opponent, rightOpponent: Opponent) {
Text( Text(
leftOpponent.name, leftOpponent.name,
style = MaterialTheme.typography.labelMedium, style = MaterialTheme.typography.labelMedium,
modifier = Modifier.padding(top = 10.dp) textAlign = TextAlign.Center,
modifier = Modifier
.padding(top = 10.dp)
.requiredWidthIn(max = 60.dp)
) )
} }
@ -115,7 +121,10 @@ private fun MatchupRow(leftOpponent: Opponent, rightOpponent: Opponent) {
Text( Text(
rightOpponent.name, rightOpponent.name,
style = MaterialTheme.typography.labelMedium, style = MaterialTheme.typography.labelMedium,
modifier = Modifier.padding(top = 10.dp) textAlign = TextAlign.Center,
modifier = Modifier
.padding(top = 10.dp)
.requiredWidthIn(max = 60.dp)
) )
} }
} }
@ -179,7 +188,7 @@ private fun Modifier.topBorder(color: Color, thickness: Dp): Modifier = this.the
}) })
private fun getOrDefaultOpponents(opponents: List<Opponent>): Pair<Opponent, Opponent> { private fun getOrDefaultOpponents(opponents: List<Opponent>): Pair<Opponent, Opponent> {
val default = Opponent(id = 0, name = "A ser definido", imageUrl = "") val default = Opponent(id = -1, name = "A ser definido", imageUrl = "")
return when { return when {
opponents.size >= 2 -> opponents[0] to opponents[1] opponents.size >= 2 -> opponents[0] to opponents[1]
opponents.size == 1 -> opponents[0] to default opponents.size == 1 -> opponents[0] to default

View file

@ -4,6 +4,7 @@ import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.toRoute import androidx.navigation.toRoute
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import xyz.leomurca.csgomatches.domain.model.MatchStatus
import xyz.leomurca.csgomatches.ui.screens.matchdetails.MatchDetailsScreen import xyz.leomurca.csgomatches.ui.screens.matchdetails.MatchDetailsScreen
@Serializable @Serializable
@ -11,7 +12,8 @@ data class MatchDetailsRoute(
val leftOpponentId: Long, val leftOpponentId: Long,
val rightOpponentId: Long, val rightOpponentId: Long,
val leagueAndSerieName: String, val leagueAndSerieName: String,
val scheduleString: String val scheduleString: String,
val status: MatchStatus
) )
fun NavGraphBuilder.matchDetailsScreen(onBackClick: () -> Unit) { fun NavGraphBuilder.matchDetailsScreen(onBackClick: () -> Unit) {

View file

@ -1,13 +1,15 @@
package xyz.leomurca.csgomatches.ui.screens.matchdetails package xyz.leomurca.csgomatches.ui.screens.matchdetails
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.BoxScope
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.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredWidthIn
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
@ -19,12 +21,20 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import coil3.compose.AsyncImage
import xyz.leomurca.csgomatches.R
import xyz.leomurca.csgomatches.domain.model.MatchStatus
import xyz.leomurca.csgomatches.domain.model.Team
import xyz.leomurca.csgomatches.ui.components.LoadingIndicator import xyz.leomurca.csgomatches.ui.components.LoadingIndicator
import xyz.leomurca.csgomatches.ui.navigation.MatchDetailsRoute import xyz.leomurca.csgomatches.ui.navigation.MatchDetailsRoute
import xyz.leomurca.csgomatches.ui.theme.LiveRed
import xyz.leomurca.csgomatches.ui.theme.White import xyz.leomurca.csgomatches.ui.theme.White
import xyz.leomurca.csgomatches.ui.theme.White_50
@Composable @Composable
fun MatchDetailsScreen( fun MatchDetailsScreen(
@ -35,15 +45,22 @@ fun MatchDetailsScreen(
val uiState = viewModel.uiState.collectAsState() val uiState = viewModel.uiState.collectAsState()
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
if (matchDetails.leftOpponentId != -1L) {
viewModel.loadTeam(matchDetails.leftOpponentId.toString(), isLeft = true) viewModel.loadTeam(matchDetails.leftOpponentId.toString(), isLeft = true)
} else {
viewModel.updateTeamToDefault(isLeft = true)
}
if (matchDetails.rightOpponentId != -1L) {
viewModel.loadTeam(matchDetails.rightOpponentId.toString(), isLeft = false) viewModel.loadTeam(matchDetails.rightOpponentId.toString(), isLeft = false)
} else {
viewModel.updateTeamToDefault(isLeft = false)
}
} }
Box( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.background(MaterialTheme.colorScheme.background), .background(MaterialTheme.colorScheme.background),
contentAlignment = Alignment.Center
) { ) {
TopBar(matchDetails.leagueAndSerieName, onBackClick) TopBar(matchDetails.leagueAndSerieName, onBackClick)
@ -52,20 +69,97 @@ fun MatchDetailsScreen(
value.errorMessage != null -> Text(value.errorMessage) value.errorMessage != null -> Text(value.errorMessage)
value.isLoading -> LoadingIndicator() value.isLoading -> LoadingIndicator()
value.leftTeam != null && value.rightTeam != null -> Column { value.leftTeam != null && value.rightTeam != null -> Column {
Text(value.leftTeam.name.toString()) MatchupRow(leftTeam = value.leftTeam, rightTeam = value.rightTeam)
Text(value.rightTeam.name.toString()) ScheduleRow(matchDetails.scheduleString, matchDetails.status)
} }
} }
} }
} }
@Composable @Composable
private fun BoxScope.TopBar(leagueAndSerieName: String, onBackClick: () -> Unit) { fun ScheduleRow(scheduleString: String, matchStatus: MatchStatus) {
val modifier = if (matchStatus == MatchStatus.LIVE)
Modifier
.background(LiveRed, RoundedCornerShape(16.dp))
.padding(horizontal = 12.dp, vertical = 8.dp)
else Modifier
Row(
Modifier
.fillMaxWidth()
.padding(top = 20.dp), horizontalArrangement = Arrangement.Center
) {
Text(
scheduleString,
style = MaterialTheme.typography.headlineMedium,
color = White,
modifier = modifier
)
}
}
@Composable
private fun MatchupRow(leftTeam: Team, rightTeam: Team) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(top = 24.dp, bottom = 20.dp),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
AsyncImage(
model = leftTeam.imageUrl,
contentDescription = "${leftTeam.name} logo",
error = painterResource(R.drawable.fallback_image_round),
placeholder = painterResource(R.drawable.fallback_image_round),
modifier = Modifier.size(60.dp),
contentScale = ContentScale.Fit
)
Text(
leftTeam.name ?: "A ser definido",
style = MaterialTheme.typography.labelMedium,
color = White,
textAlign = TextAlign.Center,
modifier = Modifier
.padding(top = 10.dp)
.requiredWidthIn(max = 60.dp)
)
}
Text(
"vs",
style = MaterialTheme.typography.bodyMedium,
color = White_50,
modifier = Modifier.padding(horizontal = 20.dp)
)
Column(horizontalAlignment = Alignment.CenterHorizontally) {
AsyncImage(
model = rightTeam.imageUrl,
contentDescription = "${rightTeam.name} logo",
error = painterResource(R.drawable.fallback_image_round),
placeholder = painterResource(R.drawable.fallback_image_round),
modifier = Modifier.size(60.dp),
contentScale = ContentScale.Fit
)
Text(
rightTeam.name ?: "A ser definido",
style = MaterialTheme.typography.labelMedium,
color = White,
textAlign = TextAlign.Center,
modifier = Modifier
.padding(top = 10.dp)
.requiredWidthIn(max = 60.dp)
)
}
}
}
@Composable
private fun TopBar(leagueAndSerieName: String, onBackClick: () -> Unit) {
Row( Row(
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
.background(MaterialTheme.colorScheme.background) .background(MaterialTheme.colorScheme.background)
.align(Alignment.TopCenter)
.padding(top = 52.dp, start = 24.dp, end = 24.dp), .padding(top = 52.dp, start = 24.dp, end = 24.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {

View file

@ -43,6 +43,33 @@ class MatchDetailsViewModel @Inject constructor(
} }
} }
fun updateTeamToDefault(isLeft: Boolean) {
_uiState.update { it.copy(isLoading = true, errorMessage = null) }
_uiState.update {
if (isLeft) {
it.copy(
leftTeam = Team(
id = -1,
name = null,
imageUrl = null,
players = emptyList()
),
isLoading = false
)
} else {
it.copy(
rightTeam = Team(
id = -1,
name = null,
imageUrl = null,
players = emptyList()
),
isLoading = false
)
}
}
}
data class TeamUiState( data class TeamUiState(
val leftTeam: Team? = null, val leftTeam: Team? = null,
val rightTeam: Team? = null, val rightTeam: Team? = null,