diff --git a/app/src/main/java/xyz/leomurca/csgomatches/ui/components/ErrorMessage.kt b/app/src/main/java/xyz/leomurca/csgomatches/ui/components/ErrorMessage.kt new file mode 100644 index 0000000..c06d1ea --- /dev/null +++ b/app/src/main/java/xyz/leomurca/csgomatches/ui/components/ErrorMessage.kt @@ -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) + } + } + } +} diff --git a/app/src/main/java/xyz/leomurca/csgomatches/ui/screens/matchdetails/MatchDetailsScreen.kt b/app/src/main/java/xyz/leomurca/csgomatches/ui/screens/matchdetails/MatchDetailsScreen.kt index efb4c0a..6e63250 100644 --- a/app/src/main/java/xyz/leomurca/csgomatches/ui/screens/matchdetails/MatchDetailsScreen.kt +++ b/app/src/main/java/xyz/leomurca/csgomatches/ui/screens/matchdetails/MatchDetailsScreen.kt @@ -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) + } +} diff --git a/app/src/main/java/xyz/leomurca/csgomatches/ui/screens/matches/MatchesScreen.kt b/app/src/main/java/xyz/leomurca/csgomatches/ui/screens/matches/MatchesScreen.kt index 0c6b23b..0e65319 100644 --- a/app/src/main/java/xyz/leomurca/csgomatches/ui/screens/matches/MatchesScreen.kt +++ b/app/src/main/java/xyz/leomurca/csgomatches/ui/screens/matches/MatchesScreen.kt @@ -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 } } }