From 07d20aba86475204f0106ddaf13264244ef96272 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leonardo=20Mur=C3=A7a?= Date: Fri, 18 Jul 2025 22:33:27 -0300 Subject: [PATCH] feat: add details screen with navigation and initial structure --- .../data/local/MatchLocalDataSource.kt | 5 + .../csgomatches/data/mapper/MatchMapper.kt | 22 +++- .../csgomatches/data/model/PlayerDto.kt | 16 +++ .../csgomatches/data/model/TeamDetailsDto.kt | 13 +++ .../data/remote/MatchRemoteDataSourceImpl.kt | 17 +++ .../data/remote/MatchesApiService.kt | 6 + .../data/repository/MatchRepositoryImpl.kt | 10 ++ .../data/source/MatchDataSource.kt | 2 + .../csgomatches/domain/model/Match.kt | 1 - .../csgomatches/domain/model/Player.kt | 10 ++ .../leomurca/csgomatches/domain/model/Team.kt | 8 ++ .../domain/repository/MatchRepository.kt | 3 + .../csgomatches/ui/components/MatchCard.kt | 103 ++++++++---------- .../csgomatches/ui/navigation/DetailsRoute.kt | 17 ++- .../csgomatches/ui/navigation/MatchesRoute.kt | 12 +- .../csgomatches/ui/navigation/RootNavHost.kt | 10 +- .../ui/screens/details/DetailsScreen.kt | 72 +++++++++++- .../ui/screens/details/DetailsViewModel.kt | 53 +++++++++ .../ui/screens/matches/MatchesScreen.kt | 14 ++- 19 files changed, 312 insertions(+), 82 deletions(-) create mode 100644 app/src/main/java/xyz/leomurca/csgomatches/data/model/PlayerDto.kt create mode 100644 app/src/main/java/xyz/leomurca/csgomatches/data/model/TeamDetailsDto.kt create mode 100644 app/src/main/java/xyz/leomurca/csgomatches/domain/model/Player.kt create mode 100644 app/src/main/java/xyz/leomurca/csgomatches/domain/model/Team.kt create mode 100644 app/src/main/java/xyz/leomurca/csgomatches/ui/screens/details/DetailsViewModel.kt diff --git a/app/src/main/java/xyz/leomurca/csgomatches/data/local/MatchLocalDataSource.kt b/app/src/main/java/xyz/leomurca/csgomatches/data/local/MatchLocalDataSource.kt index 71b8545..c7cac3f 100644 --- a/app/src/main/java/xyz/leomurca/csgomatches/data/local/MatchLocalDataSource.kt +++ b/app/src/main/java/xyz/leomurca/csgomatches/data/local/MatchLocalDataSource.kt @@ -5,6 +5,7 @@ import xyz.leomurca.csgomatches.data.model.MatchDto import xyz.leomurca.csgomatches.data.model.OpponentDto import xyz.leomurca.csgomatches.data.model.OpponentRecord import xyz.leomurca.csgomatches.data.model.SerieDto +import xyz.leomurca.csgomatches.data.model.TeamDetailsDto import xyz.leomurca.csgomatches.data.source.MatchDataSource import xyz.leomurca.csgomatches.domain.model.Resource @@ -151,4 +152,8 @@ class MatchLocalDataSource : MatchDataSource { ) ) } + + override suspend fun teamDetails(teamId: String): Resource> { + TODO("Not yet implemented") + } } \ No newline at end of file diff --git a/app/src/main/java/xyz/leomurca/csgomatches/data/mapper/MatchMapper.kt b/app/src/main/java/xyz/leomurca/csgomatches/data/mapper/MatchMapper.kt index 7f57069..200851e 100644 --- a/app/src/main/java/xyz/leomurca/csgomatches/data/mapper/MatchMapper.kt +++ b/app/src/main/java/xyz/leomurca/csgomatches/data/mapper/MatchMapper.kt @@ -1,11 +1,14 @@ package xyz.leomurca.csgomatches.data.mapper import xyz.leomurca.csgomatches.data.model.MatchDto +import xyz.leomurca.csgomatches.data.model.TeamDetailsDto import xyz.leomurca.csgomatches.domain.model.League import xyz.leomurca.csgomatches.domain.model.Match import xyz.leomurca.csgomatches.domain.model.MatchStatus import xyz.leomurca.csgomatches.domain.model.Opponent +import xyz.leomurca.csgomatches.domain.model.Player import xyz.leomurca.csgomatches.domain.model.Serie +import xyz.leomurca.csgomatches.domain.model.Team fun MatchDto.toDomain(): Match { return Match( @@ -31,4 +34,21 @@ fun MatchDto.toDomain(): Match { } private fun String?.toMatchStatus() = - if (this == "running") MatchStatus.LIVE else MatchStatus.SCHEDULED \ No newline at end of file + if (this == "running") MatchStatus.LIVE else MatchStatus.SCHEDULED + +fun TeamDetailsDto.toDomain(): Team { + return Team( + id = id, + name = name, + imageUrl = imageUrl, + players = players.map { + Player( + id = it.id, + nickName = it.name, + firstName = it.firstName, + lastName = it.lastName, + imageUrl = it.imageUrl + ) + } + ) +} \ No newline at end of file diff --git a/app/src/main/java/xyz/leomurca/csgomatches/data/model/PlayerDto.kt b/app/src/main/java/xyz/leomurca/csgomatches/data/model/PlayerDto.kt new file mode 100644 index 0000000..efbb6ae --- /dev/null +++ b/app/src/main/java/xyz/leomurca/csgomatches/data/model/PlayerDto.kt @@ -0,0 +1,16 @@ +package xyz.leomurca.csgomatches.data.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class PlayerDto( + val id: Long, + val name: String?, + @SerialName("first_name") + val firstName: String?, + @SerialName("last_name") + val lastName: String?, + @SerialName("image_url") + val imageUrl: String? +) diff --git a/app/src/main/java/xyz/leomurca/csgomatches/data/model/TeamDetailsDto.kt b/app/src/main/java/xyz/leomurca/csgomatches/data/model/TeamDetailsDto.kt new file mode 100644 index 0000000..4b1781b --- /dev/null +++ b/app/src/main/java/xyz/leomurca/csgomatches/data/model/TeamDetailsDto.kt @@ -0,0 +1,13 @@ +package xyz.leomurca.csgomatches.data.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class TeamDetailsDto( + val id: Long, + val name: String?, + @SerialName("image_url") + val imageUrl: String?, + val players: List +) \ No newline at end of file diff --git a/app/src/main/java/xyz/leomurca/csgomatches/data/remote/MatchRemoteDataSourceImpl.kt b/app/src/main/java/xyz/leomurca/csgomatches/data/remote/MatchRemoteDataSourceImpl.kt index f7c456f..36e074f 100644 --- a/app/src/main/java/xyz/leomurca/csgomatches/data/remote/MatchRemoteDataSourceImpl.kt +++ b/app/src/main/java/xyz/leomurca/csgomatches/data/remote/MatchRemoteDataSourceImpl.kt @@ -3,6 +3,7 @@ package xyz.leomurca.csgomatches.data.remote import kotlinx.serialization.json.Json import xyz.leomurca.csgomatches.data.model.ErrorDto import xyz.leomurca.csgomatches.data.model.MatchDto +import xyz.leomurca.csgomatches.data.model.TeamDetailsDto import xyz.leomurca.csgomatches.data.source.MatchDataSource import xyz.leomurca.csgomatches.domain.model.Resource @@ -25,4 +26,20 @@ class MatchRemoteDataSourceImpl( Resource.Error(e.message.toString()) } } + + override suspend fun teamDetails(teamId: String): Resource> { + return try { + val response = matchesApiService.teamDetails(teamId) + + if (response.isSuccessful) { + Resource.Success(response.body() ?: throw Exception("Empty response body")) + } else { + val errorBody = response.errorBody()?.string() ?: "" + val networkError = json.decodeFromString(errorBody) + Resource.Error(networkError.message) + } + } catch (e: Exception) { + Resource.Error(e.message.toString()) + } + } } \ No newline at end of file diff --git a/app/src/main/java/xyz/leomurca/csgomatches/data/remote/MatchesApiService.kt b/app/src/main/java/xyz/leomurca/csgomatches/data/remote/MatchesApiService.kt index 7b40939..1e5f220 100644 --- a/app/src/main/java/xyz/leomurca/csgomatches/data/remote/MatchesApiService.kt +++ b/app/src/main/java/xyz/leomurca/csgomatches/data/remote/MatchesApiService.kt @@ -4,6 +4,7 @@ import retrofit2.Response import retrofit2.http.GET import retrofit2.http.Query import xyz.leomurca.csgomatches.data.model.MatchDto +import xyz.leomurca.csgomatches.data.model.TeamDetailsDto interface MatchesApiService { @@ -13,4 +14,9 @@ interface MatchesApiService { @Query("finished") finished: Boolean = false, @Query("sort") sort: String = "begin_at", ): Response> + + @GET("teams") + suspend fun teamDetails( + @Query("filter[id]") id: String + ): Response> } \ No newline at end of file diff --git a/app/src/main/java/xyz/leomurca/csgomatches/data/repository/MatchRepositoryImpl.kt b/app/src/main/java/xyz/leomurca/csgomatches/data/repository/MatchRepositoryImpl.kt index 64b6dd5..044a023 100644 --- a/app/src/main/java/xyz/leomurca/csgomatches/data/repository/MatchRepositoryImpl.kt +++ b/app/src/main/java/xyz/leomurca/csgomatches/data/repository/MatchRepositoryImpl.kt @@ -8,6 +8,7 @@ import xyz.leomurca.csgomatches.di.AppDispatchers import xyz.leomurca.csgomatches.di.Dispatcher import xyz.leomurca.csgomatches.domain.model.Match import xyz.leomurca.csgomatches.domain.model.Resource +import xyz.leomurca.csgomatches.domain.model.Team import xyz.leomurca.csgomatches.domain.repository.MatchRepository class MatchRepositoryImpl( @@ -22,4 +23,13 @@ class MatchRepositoryImpl( } } } + + override suspend fun teamDetails(teamId: String): Resource> { + return withContext(ioDispatcher) { + when (val result = matchRemoteDataSource.teamDetails(teamId)) { + is Resource.Success -> Resource.Success(result.data.map { it.toDomain() }) + is Resource.Error -> Resource.Error(result.message) + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/xyz/leomurca/csgomatches/data/source/MatchDataSource.kt b/app/src/main/java/xyz/leomurca/csgomatches/data/source/MatchDataSource.kt index 676cc5d..13cf872 100644 --- a/app/src/main/java/xyz/leomurca/csgomatches/data/source/MatchDataSource.kt +++ b/app/src/main/java/xyz/leomurca/csgomatches/data/source/MatchDataSource.kt @@ -1,8 +1,10 @@ package xyz.leomurca.csgomatches.data.source import xyz.leomurca.csgomatches.data.model.MatchDto +import xyz.leomurca.csgomatches.data.model.TeamDetailsDto import xyz.leomurca.csgomatches.domain.model.Resource interface MatchDataSource { suspend fun upcomingMatches(): Resource> + suspend fun teamDetails(teamId: String): Resource> } \ No newline at end of file diff --git a/app/src/main/java/xyz/leomurca/csgomatches/domain/model/Match.kt b/app/src/main/java/xyz/leomurca/csgomatches/domain/model/Match.kt index cf62608..f9140a7 100644 --- a/app/src/main/java/xyz/leomurca/csgomatches/domain/model/Match.kt +++ b/app/src/main/java/xyz/leomurca/csgomatches/domain/model/Match.kt @@ -2,7 +2,6 @@ package xyz.leomurca.csgomatches.domain.model import java.time.ZonedDateTime - data class Match( val beginAt: ZonedDateTime?, val opponents: List, diff --git a/app/src/main/java/xyz/leomurca/csgomatches/domain/model/Player.kt b/app/src/main/java/xyz/leomurca/csgomatches/domain/model/Player.kt new file mode 100644 index 0000000..013968b --- /dev/null +++ b/app/src/main/java/xyz/leomurca/csgomatches/domain/model/Player.kt @@ -0,0 +1,10 @@ +package xyz.leomurca.csgomatches.domain.model + + +data class Player( + val id: Long, + val nickName: String?, + val firstName: String?, + val lastName: String?, + val imageUrl: String? +) diff --git a/app/src/main/java/xyz/leomurca/csgomatches/domain/model/Team.kt b/app/src/main/java/xyz/leomurca/csgomatches/domain/model/Team.kt new file mode 100644 index 0000000..923ddfe --- /dev/null +++ b/app/src/main/java/xyz/leomurca/csgomatches/domain/model/Team.kt @@ -0,0 +1,8 @@ +package xyz.leomurca.csgomatches.domain.model + +data class Team( + val id: Long, + val name: String?, + val imageUrl: String?, + val players: List +) diff --git a/app/src/main/java/xyz/leomurca/csgomatches/domain/repository/MatchRepository.kt b/app/src/main/java/xyz/leomurca/csgomatches/domain/repository/MatchRepository.kt index c148c22..7a7aa9a 100644 --- a/app/src/main/java/xyz/leomurca/csgomatches/domain/repository/MatchRepository.kt +++ b/app/src/main/java/xyz/leomurca/csgomatches/domain/repository/MatchRepository.kt @@ -2,8 +2,11 @@ package xyz.leomurca.csgomatches.domain.repository import xyz.leomurca.csgomatches.domain.model.Match import xyz.leomurca.csgomatches.domain.model.Resource +import xyz.leomurca.csgomatches.domain.model.Team interface MatchRepository { suspend fun upcomingMatches(): Resource> + + suspend fun teamDetails(teamId: String): Resource> } \ No newline at end of file diff --git a/app/src/main/java/xyz/leomurca/csgomatches/ui/components/MatchCard.kt b/app/src/main/java/xyz/leomurca/csgomatches/ui/components/MatchCard.kt index 0470596..ab65a6e 100644 --- a/app/src/main/java/xyz/leomurca/csgomatches/ui/components/MatchCard.kt +++ b/app/src/main/java/xyz/leomurca/csgomatches/ui/components/MatchCard.kt @@ -33,6 +33,7 @@ import xyz.leomurca.csgomatches.domain.model.League import xyz.leomurca.csgomatches.domain.model.Match import xyz.leomurca.csgomatches.domain.model.MatchStatus import xyz.leomurca.csgomatches.domain.model.Opponent +import xyz.leomurca.csgomatches.ui.navigation.DetailsRoute import xyz.leomurca.csgomatches.ui.theme.LiveRed import xyz.leomurca.csgomatches.ui.theme.White_20 import xyz.leomurca.csgomatches.ui.theme.White_50 @@ -42,23 +43,35 @@ import java.time.ZonedDateTime @Composable fun MatchCard( match: Match, + onTapCard: (DetailsRoute) -> Unit, modifier: Modifier = Modifier, ) { Box(modifier = modifier.fillMaxWidth()) { + val (leftOpponent, rightOpponent) = getOrDefaultOpponents(match.opponents) + val leagueAndSerieName = "${match.league.name} + ${match.serie.name}" + val scheduleConfig = match.status.scheduleConfigFor(match.beginAt) + Card( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(16.dp), - colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface) + modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(16.dp), onClick = { + onTapCard( + DetailsRoute( + leftOpponentId = leftOpponent.id, + rightOpponentId = rightOpponent.id, + leagueAndSerieName = leagueAndSerieName, + scheduleString = scheduleConfig.first, + ) + ) + }, colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface) ) { - MatchupRow(match.opponents) - LeagueInfoRow(match.league, match.serie.name) + MatchupRow(leftOpponent, rightOpponent) + LeagueInfoRow(match.league, leagueAndSerieName) } - ScheduleBadge(match.status, match.beginAt) + ScheduleBadge(scheduleConfig) } } @Composable -private fun MatchupRow(opponents: List) { +private fun MatchupRow(leftOpponent: Opponent, rightOpponent: Opponent) { Row( modifier = Modifier .fillMaxWidth() @@ -66,7 +79,6 @@ private fun MatchupRow(opponents: List) { horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically ) { - val (leftOpponent, rightOpponent) = getOrDefaultOpponents(opponents) Column(horizontalAlignment = Alignment.CenterHorizontally) { AsyncImage( @@ -110,7 +122,7 @@ private fun MatchupRow(opponents: List) { } @Composable -private fun LeagueInfoRow(league: League, serieName: String) { +private fun LeagueInfoRow(league: League, leagueAndSerieName: String) { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier @@ -123,15 +135,14 @@ private fun LeagueInfoRow(league: League, serieName: String) { contentDescription = "${league.name} logo", error = painterResource(R.drawable.fallback_image_round), placeholder = painterResource(R.drawable.fallback_image_round), - onLoading = { - }, + onLoading = {}, modifier = Modifier .height(16.dp) .wrapContentWidth(), contentScale = ContentScale.Fit ) Text( - "${league.name} + $serieName", + leagueAndSerieName, style = MaterialTheme.typography.labelSmall, modifier = Modifier.padding(start = 8.dp) ) @@ -139,52 +150,20 @@ private fun LeagueInfoRow(league: League, serieName: String) { } @Composable -private fun BoxScope.ScheduleBadge(status: MatchStatus, beginAt: ZonedDateTime?) { - when (status) { - MatchStatus.LIVE -> - Box( - modifier = Modifier - .align(Alignment.TopEnd) - .clip(RoundedCornerShape(topEnd = 16.dp, bottomStart = 16.dp)) - .background(LiveRed) - .padding(8.dp) - ) { - Text( - text = "AGORA", - style = MaterialTheme.typography.displayMedium, - color = Color.White - ) - } - - MatchStatus.SCHEDULED -> - Box( - modifier = Modifier - .align(Alignment.TopEnd) - .clip(RoundedCornerShape(topEnd = 16.dp, bottomStart = 16.dp)) - .background(White_20) - .padding(8.dp) - ) { - Text( - text = beginAt.toFormattedMatchTime(), - style = MaterialTheme.typography.displayMedium, - color = Color.White - ) - } - - MatchStatus.UNKNOWN -> - Box( - modifier = Modifier - .align(Alignment.TopEnd) - .clip(RoundedCornerShape(topEnd = 16.dp, bottomStart = 16.dp)) - .background(White_20) - .padding(8.dp) - ) { - Text( - text = "A ser definido", - style = MaterialTheme.typography.displayMedium, - color = Color.White - ) - } +private fun BoxScope.ScheduleBadge(scheduleConfig: Pair) { + val (scheduleString, scheduleBackgroundColor) = scheduleConfig + Box( + modifier = Modifier + .align(Alignment.TopEnd) + .clip(RoundedCornerShape(topEnd = 16.dp, bottomStart = 16.dp)) + .background(scheduleBackgroundColor) + .padding(8.dp) + ) { + Text( + text = scheduleString, + style = MaterialTheme.typography.displayMedium, + color = Color.White + ) } } @@ -197,8 +176,7 @@ private fun Modifier.topBorder(color: Color, thickness: Dp): Modifier = this.the end = Offset(size.width, 0f), strokeWidth = strokeWidth ) - } -) + }) private fun getOrDefaultOpponents(opponents: List): Pair { val default = Opponent(id = 0, name = "A ser definido", imageUrl = "") @@ -209,3 +187,8 @@ private fun getOrDefaultOpponents(opponents: List): Pair "Agora" to LiveRed + MatchStatus.SCHEDULED -> beginAt.toFormattedMatchTime() to White_20 + MatchStatus.UNKNOWN -> "A definir" to White_20 +} diff --git a/app/src/main/java/xyz/leomurca/csgomatches/ui/navigation/DetailsRoute.kt b/app/src/main/java/xyz/leomurca/csgomatches/ui/navigation/DetailsRoute.kt index 27b319e..6797805 100644 --- a/app/src/main/java/xyz/leomurca/csgomatches/ui/navigation/DetailsRoute.kt +++ b/app/src/main/java/xyz/leomurca/csgomatches/ui/navigation/DetailsRoute.kt @@ -2,12 +2,21 @@ package xyz.leomurca.csgomatches.ui.navigation import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable +import androidx.navigation.toRoute +import kotlinx.serialization.Serializable import xyz.leomurca.csgomatches.ui.screens.details.DetailsScreen -const val DETAILS_ROUTE = "details" +@Serializable +data class DetailsRoute( + val leftOpponentId: Long, + val rightOpponentId: Long, + val leagueAndSerieName: String, + val scheduleString: String +) -fun NavGraphBuilder.detailsScreen() { - composable(DETAILS_ROUTE) { - DetailsScreen() +fun NavGraphBuilder.detailsScreen(onBackClick: () -> Unit) { + composable { + val details: DetailsRoute = it.toRoute() + DetailsScreen(details = details, onBackClick = onBackClick) } } diff --git a/app/src/main/java/xyz/leomurca/csgomatches/ui/navigation/MatchesRoute.kt b/app/src/main/java/xyz/leomurca/csgomatches/ui/navigation/MatchesRoute.kt index 7094c74..ebfe038 100644 --- a/app/src/main/java/xyz/leomurca/csgomatches/ui/navigation/MatchesRoute.kt +++ b/app/src/main/java/xyz/leomurca/csgomatches/ui/navigation/MatchesRoute.kt @@ -2,12 +2,16 @@ package xyz.leomurca.csgomatches.ui.navigation import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable +import androidx.navigation.toRoute +import kotlinx.serialization.Serializable +import xyz.leomurca.csgomatches.domain.model.Match import xyz.leomurca.csgomatches.ui.screens.matches.MatchesScreen -const val MATCHES_ROUTE = "matches" +@Serializable +object MatchesRoute -fun NavGraphBuilder.matchesScreen() { - composable(MATCHES_ROUTE) { - MatchesScreen() +fun NavGraphBuilder.matchesScreen(onTapCard: (DetailsRoute) -> Unit) { + composable { + MatchesScreen(onTapCard = onTapCard) } } \ No newline at end of file diff --git a/app/src/main/java/xyz/leomurca/csgomatches/ui/navigation/RootNavHost.kt b/app/src/main/java/xyz/leomurca/csgomatches/ui/navigation/RootNavHost.kt index 974e14c..8b49ce3 100644 --- a/app/src/main/java/xyz/leomurca/csgomatches/ui/navigation/RootNavHost.kt +++ b/app/src/main/java/xyz/leomurca/csgomatches/ui/navigation/RootNavHost.kt @@ -9,9 +9,13 @@ fun RootNavHost() { val navController = rememberNavController() NavHost( navController = navController, - startDestination = MATCHES_ROUTE + startDestination = MatchesRoute ) { - matchesScreen() - detailsScreen() + matchesScreen { + navController.navigate(it) + } + detailsScreen { + navController.popBackStack() + } } } \ No newline at end of file diff --git a/app/src/main/java/xyz/leomurca/csgomatches/ui/screens/details/DetailsScreen.kt b/app/src/main/java/xyz/leomurca/csgomatches/ui/screens/details/DetailsScreen.kt index 83e23ba..a865e71 100644 --- a/app/src/main/java/xyz/leomurca/csgomatches/ui/screens/details/DetailsScreen.kt +++ b/app/src/main/java/xyz/leomurca/csgomatches/ui/screens/details/DetailsScreen.kt @@ -2,20 +2,84 @@ package xyz.leomurca.csgomatches.ui.screens.details import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +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.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.sp +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import xyz.leomurca.csgomatches.ui.components.LoadingIndicator +import xyz.leomurca.csgomatches.ui.navigation.DetailsRoute +import xyz.leomurca.csgomatches.ui.theme.White +@OptIn(ExperimentalMaterial3Api::class) @Composable -fun DetailsScreen() { +fun DetailsScreen( + viewModel: DetailsViewModel = hiltViewModel(), + details: DetailsRoute, + onBackClick: () -> Unit +) { + val uiState = viewModel.uiState.collectAsState() + + LaunchedEffect(Unit) { + viewModel.loadTeam(details.leftOpponentId.toString(), isLeft = true) + viewModel.loadTeam(details.rightOpponentId.toString(), isLeft = false) + } + Box( - modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.background), + modifier = Modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.background), contentAlignment = Alignment.Center ) { - Text("Details Screen", fontSize = 24.sp) + TopBar(details.leagueAndSerieName, onBackClick) + + val value = uiState.value + when { + value.errorMessage != null -> Text(value.errorMessage) + value.isLoading -> LoadingIndicator() + value.leftTeam != null && value.rightTeam != null -> Column { + Text(value.leftTeam.name.toString()) + Text(value.rightTeam.name.toString()) + } + } + } +} + +@Composable +private fun BoxScope.TopBar(leagueAndSerieName: String, onBackClick: () -> Unit) { + Row( + Modifier + .fillMaxWidth() + .background(MaterialTheme.colorScheme.background) + .align(Alignment.TopCenter) + .padding(top = 52.dp, start = 24.dp, end = 24.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + IconButton(onBackClick, modifier = Modifier) { + Icon(Icons.AutoMirrored.Default.ArrowBack, contentDescription = "", tint = White) + } + + Text( + leagueAndSerieName, + textAlign = TextAlign.Center, + style = MaterialTheme.typography.titleMedium, + color = White, + ) } } diff --git a/app/src/main/java/xyz/leomurca/csgomatches/ui/screens/details/DetailsViewModel.kt b/app/src/main/java/xyz/leomurca/csgomatches/ui/screens/details/DetailsViewModel.kt new file mode 100644 index 0000000..7b64904 --- /dev/null +++ b/app/src/main/java/xyz/leomurca/csgomatches/ui/screens/details/DetailsViewModel.kt @@ -0,0 +1,53 @@ +package xyz.leomurca.csgomatches.ui.screens.details + +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import xyz.leomurca.csgomatches.domain.model.Resource +import xyz.leomurca.csgomatches.domain.model.Team +import xyz.leomurca.csgomatches.domain.repository.MatchRepository +import javax.inject.Inject + +@HiltViewModel +class DetailsViewModel @Inject constructor( + private val matchRepository: MatchRepository, +) : ViewModel() { + + private val _uiState = MutableStateFlow(TeamUiState()) + val uiState: StateFlow = _uiState.asStateFlow() + + fun loadTeam(teamId: String, isLeft: Boolean) { + viewModelScope.launch { + _uiState.update { it.copy(isLoading = true, errorMessage = null) } + + when (val result = matchRepository.teamDetails(teamId)) { + is Resource.Success -> { + val team = result.data.firstOrNull() + _uiState.update { + if (isLeft) it.copy(leftTeam = team, isLoading = false) + else it.copy(rightTeam = team, isLoading = false) + } + } + + is Resource.Error -> { + _uiState.update { + it.copy(errorMessage = result.message, isLoading = false) + } + } + } + } + } + + data class TeamUiState( + val leftTeam: Team? = null, + val rightTeam: Team? = null, + val isLoading: Boolean = true, + val errorMessage: String? = null + ) +} \ No newline at end of file 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 7b0482d..7c57c25 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 @@ -24,12 +24,16 @@ import androidx.hilt.navigation.compose.hiltViewModel import xyz.leomurca.csgomatches.domain.model.Match import xyz.leomurca.csgomatches.ui.components.LoadingIndicator import xyz.leomurca.csgomatches.ui.components.MatchCard +import xyz.leomurca.csgomatches.ui.navigation.DetailsRoute import xyz.leomurca.csgomatches.ui.screens.matches.MatchesViewModel.MatchesUiState import xyz.leomurca.csgomatches.ui.theme.White @OptIn(ExperimentalMaterial3Api::class) @Composable -fun MatchesScreen(viewModel: MatchesViewModel = hiltViewModel()) { +fun MatchesScreen( + viewModel: MatchesViewModel = hiltViewModel(), + onTapCard: (DetailsRoute) -> Unit +) { val uiState = viewModel.uiState.collectAsState() val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() @@ -41,7 +45,7 @@ fun MatchesScreen(viewModel: MatchesViewModel = hiltViewModel()) { "Partidas", style = MaterialTheme.typography.titleLarge, color = White, - modifier = Modifier.padding(top = 24.dp, start = 24.dp) + modifier = Modifier.padding(top = 24.dp, start = 6.dp) ) }, scrollBehavior = scrollBehavior, @@ -60,7 +64,7 @@ fun MatchesScreen(viewModel: MatchesViewModel = hiltViewModel()) { ) { when (val value = uiState.value) { is MatchesUiState.Loading -> LoadingIndicator() - is MatchesUiState.Success -> MatchesList(value.matches) + is MatchesUiState.Success -> MatchesList(value.matches, onTapCard) is MatchesUiState.Error -> Text(value.message) } } @@ -68,7 +72,7 @@ fun MatchesScreen(viewModel: MatchesViewModel = hiltViewModel()) { } @Composable -private fun MatchesList(matches: List) { +private fun MatchesList(matches: List, onTapCard: (DetailsRoute) -> Unit) { LazyColumn( Modifier .padding(horizontal = 24.dp), @@ -76,7 +80,7 @@ private fun MatchesList(matches: List) { contentPadding = PaddingValues(vertical = 24.dp) ) { items(matches) { - MatchCard(it) + MatchCard(it, onTapCard) } } } \ No newline at end of file