fix: center top bar match details text and add error message component

This commit is contained in:
Leonardo Murça 2025-07-19 15:28:30 -03:00
parent c10a667b8d
commit fc496e9eed
3 changed files with 123 additions and 29 deletions

View file

@ -0,0 +1,66 @@
package xyz.leomurca.csgomatches.ui.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Warning
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
@Composable
fun ErrorMessage(
message: String,
modifier: Modifier = Modifier,
onRetry: (() -> Unit)? = null
) {
Column(
modifier = modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(24.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Icon(
imageVector = Icons.Filled.Warning,
contentDescription = null,
tint = MaterialTheme.colorScheme.error,
modifier = Modifier.size(64.dp)
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = message,
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.error,
textAlign = TextAlign.Center
)
if (onRetry != null) {
Spacer(modifier = Modifier.height(24.dp))
Button(onClick = onRetry,
colors = ButtonDefaults.buttonColors()
.copy(containerColor = MaterialTheme.colorScheme.secondary)
) {
Text("Tentar novamente", style = MaterialTheme.typography.bodyMedium)
}
}
}
}

View file

@ -47,7 +47,7 @@ import xyz.leomurca.csgomatches.R
import xyz.leomurca.csgomatches.domain.model.MatchStatus
import xyz.leomurca.csgomatches.domain.model.Player
import xyz.leomurca.csgomatches.domain.model.Team
import xyz.leomurca.csgomatches.ui.components.LoadingIndicator
import xyz.leomurca.csgomatches.ui.components.ErrorMessage
import xyz.leomurca.csgomatches.ui.navigation.MatchDetailsRoute
import xyz.leomurca.csgomatches.ui.theme.LiveRed
import xyz.leomurca.csgomatches.ui.theme.White
@ -62,16 +62,7 @@ fun MatchDetailsScreen(
val uiState = viewModel.uiState.collectAsState()
LaunchedEffect(Unit) {
if (matchDetails.leftOpponentId != -1L) {
viewModel.loadTeam(matchDetails.leftOpponentId.toString(), isLeft = true)
} else {
viewModel.applyPlaceholderTeamToSide(isLeft = true)
}
if (matchDetails.rightOpponentId != -1L) {
viewModel.loadTeam(matchDetails.rightOpponentId.toString(), isLeft = false)
} else {
viewModel.applyPlaceholderTeamToSide(isLeft = false)
}
loadTeams(matchDetails, viewModel)
}
Column(
@ -79,13 +70,15 @@ fun MatchDetailsScreen(
.fillMaxSize()
.background(MaterialTheme.colorScheme.background),
) {
TopBar(matchDetails.leagueAndSerieName, onBackClick)
val value = uiState.value
when {
value.errorMessage != null -> Text(value.errorMessage)
value.isLoading -> LoadingIndicator()
value.errorMessage != null -> ErrorMessage(
message = value.errorMessage,
onRetry = { loadTeams(matchDetails, viewModel) }
)
value.leftTeam != null && value.rightTeam != null -> Column {
TopBar(matchDetails.leagueAndSerieName, onBackClick)
MatchupRow(leftTeam = value.leftTeam, rightTeam = value.rightTeam)
ScheduleRow(matchDetails.scheduleString, matchDetails.matchStatus)
DualPlayerLists(value.leftTeam.players, value.rightTeam.players)
@ -179,7 +172,9 @@ private fun PlayerAvatar(size: Dp, imageUrl: String?, nickName: String?) {
)
if (isLoading) {
CircularProgressIndicator(
modifier = Modifier.size(16.dp).align(Alignment.Center),
modifier = Modifier
.size(16.dp)
.align(Alignment.Center),
strokeWidth = 2.dp,
color = MaterialTheme.colorScheme.background
)
@ -289,19 +284,30 @@ private fun MatchupRow(leftTeam: Team, rightTeam: Team) {
@Composable
private fun TopBar(leagueAndSerieName: String, onBackClick: () -> Unit) {
Row(
Modifier
Box(
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.background)
.padding(top = 52.dp, start = 24.dp, end = 24.dp),
verticalAlignment = Alignment.CenterVertically,
.padding(top = 52.dp)
) {
IconButton(onBackClick, modifier = Modifier) {
Icon(Icons.AutoMirrored.Default.ArrowBack, contentDescription = "", tint = White)
IconButton(
onClick = onBackClick,
modifier = Modifier
.align(Alignment.CenterStart)
.padding(start = 18.dp)
) {
Icon(
imageVector = Icons.AutoMirrored.Default.ArrowBack,
contentDescription = "Back",
tint = White
)
}
Text(
leagueAndSerieName,
text = leagueAndSerieName,
modifier = Modifier
.align(Alignment.Center)
.requiredWidthIn(max = 250.dp),
textAlign = TextAlign.Center,
style = MaterialTheme.typography.titleMedium,
color = White,
@ -316,3 +322,20 @@ private fun playerFullNameOrEmpty(firstName: String?, lastName: String?): String
return if (fullName.isBlank()) "" else fullName
}
private fun loadTeams(matchDetails: MatchDetailsRoute, viewModel: MatchDetailsViewModel) {
loadTeamOrPlaceholder(matchDetails.leftOpponentId, isLeft = true, viewModel)
loadTeamOrPlaceholder(matchDetails.rightOpponentId, isLeft = false, viewModel)
}
private fun loadTeamOrPlaceholder(
opponentId: Long,
isLeft: Boolean,
viewModel: MatchDetailsViewModel
) {
if (opponentId != -1L) {
viewModel.loadTeam(opponentId.toString(), isLeft)
} else {
viewModel.applyPlaceholderTeamToSide(isLeft)
}
}

View file

@ -16,12 +16,12 @@ import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import xyz.leomurca.csgomatches.domain.model.Match
import xyz.leomurca.csgomatches.ui.components.ErrorMessage
import xyz.leomurca.csgomatches.ui.components.MatchCard
import xyz.leomurca.csgomatches.ui.navigation.MatchDetailsRoute
import xyz.leomurca.csgomatches.ui.screens.matches.MatchesViewModel.MatchesUiState
@ -37,7 +37,8 @@ fun MatchesScreen(
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = {
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
TopAppBar(
title = {
Text(
@ -53,7 +54,8 @@ fun MatchesScreen(
scrolledContainerColor = MaterialTheme.colorScheme.background
)
)
}) { innerPadding ->
}
) { innerPadding ->
PullToRefreshBox(
isRefreshing = uiState.value is MatchesUiState.Loading,
onRefresh = { viewModel.loadUpcomingMatches() },
@ -61,12 +63,15 @@ fun MatchesScreen(
.padding(innerPadding)
.fillMaxSize()
.background(MaterialTheme.colorScheme.background),
contentAlignment = Alignment.Center
) {
when (val value = uiState.value) {
is MatchesUiState.Success -> MatchesList(value.matches, onTapCard)
is MatchesUiState.Error -> Text(value.message)
else -> Unit // Do nothing
is MatchesUiState.Error -> ErrorMessage(
message = value.message,
onRetry = { viewModel.loadUpcomingMatches() }
)
else -> Unit
}
}
}