feat: add details screen with navigation and initial structure

This commit is contained in:
Leonardo Murça 2025-07-18 22:33:27 -03:00
parent c5d1bbcbed
commit 07d20aba86
19 changed files with 312 additions and 82 deletions

View file

@ -5,6 +5,7 @@ import xyz.leomurca.csgomatches.data.model.MatchDto
import xyz.leomurca.csgomatches.data.model.OpponentDto import xyz.leomurca.csgomatches.data.model.OpponentDto
import xyz.leomurca.csgomatches.data.model.OpponentRecord import xyz.leomurca.csgomatches.data.model.OpponentRecord
import xyz.leomurca.csgomatches.data.model.SerieDto 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.data.source.MatchDataSource
import xyz.leomurca.csgomatches.domain.model.Resource import xyz.leomurca.csgomatches.domain.model.Resource
@ -151,4 +152,8 @@ class MatchLocalDataSource : MatchDataSource {
) )
) )
} }
override suspend fun teamDetails(teamId: String): Resource<List<TeamDetailsDto>> {
TODO("Not yet implemented")
}
} }

View file

@ -1,11 +1,14 @@
package xyz.leomurca.csgomatches.data.mapper package xyz.leomurca.csgomatches.data.mapper
import xyz.leomurca.csgomatches.data.model.MatchDto 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.League
import xyz.leomurca.csgomatches.domain.model.Match import xyz.leomurca.csgomatches.domain.model.Match
import xyz.leomurca.csgomatches.domain.model.MatchStatus import xyz.leomurca.csgomatches.domain.model.MatchStatus
import xyz.leomurca.csgomatches.domain.model.Opponent 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.Serie
import xyz.leomurca.csgomatches.domain.model.Team
fun MatchDto.toDomain(): Match { fun MatchDto.toDomain(): Match {
return Match( return Match(
@ -31,4 +34,21 @@ fun MatchDto.toDomain(): Match {
} }
private fun String?.toMatchStatus() = private fun String?.toMatchStatus() =
if (this == "running") MatchStatus.LIVE else MatchStatus.SCHEDULED 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
)
}
)
}

View file

@ -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?
)

View file

@ -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<PlayerDto>
)

View file

@ -3,6 +3,7 @@ package xyz.leomurca.csgomatches.data.remote
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import xyz.leomurca.csgomatches.data.model.ErrorDto import xyz.leomurca.csgomatches.data.model.ErrorDto
import xyz.leomurca.csgomatches.data.model.MatchDto 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.data.source.MatchDataSource
import xyz.leomurca.csgomatches.domain.model.Resource import xyz.leomurca.csgomatches.domain.model.Resource
@ -25,4 +26,20 @@ class MatchRemoteDataSourceImpl(
Resource.Error(e.message.toString()) Resource.Error(e.message.toString())
} }
} }
override suspend fun teamDetails(teamId: String): Resource<List<TeamDetailsDto>> {
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<ErrorDto>(errorBody)
Resource.Error(networkError.message)
}
} catch (e: Exception) {
Resource.Error(e.message.toString())
}
}
} }

View file

@ -4,6 +4,7 @@ import retrofit2.Response
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.Query import retrofit2.http.Query
import xyz.leomurca.csgomatches.data.model.MatchDto import xyz.leomurca.csgomatches.data.model.MatchDto
import xyz.leomurca.csgomatches.data.model.TeamDetailsDto
interface MatchesApiService { interface MatchesApiService {
@ -13,4 +14,9 @@ interface MatchesApiService {
@Query("finished") finished: Boolean = false, @Query("finished") finished: Boolean = false,
@Query("sort") sort: String = "begin_at", @Query("sort") sort: String = "begin_at",
): Response<List<MatchDto>> ): Response<List<MatchDto>>
@GET("teams")
suspend fun teamDetails(
@Query("filter[id]") id: String
): Response<List<TeamDetailsDto>>
} }

View file

@ -8,6 +8,7 @@ import xyz.leomurca.csgomatches.di.AppDispatchers
import xyz.leomurca.csgomatches.di.Dispatcher import xyz.leomurca.csgomatches.di.Dispatcher
import xyz.leomurca.csgomatches.domain.model.Match import xyz.leomurca.csgomatches.domain.model.Match
import xyz.leomurca.csgomatches.domain.model.Resource import xyz.leomurca.csgomatches.domain.model.Resource
import xyz.leomurca.csgomatches.domain.model.Team
import xyz.leomurca.csgomatches.domain.repository.MatchRepository import xyz.leomurca.csgomatches.domain.repository.MatchRepository
class MatchRepositoryImpl( class MatchRepositoryImpl(
@ -22,4 +23,13 @@ class MatchRepositoryImpl(
} }
} }
} }
override suspend fun teamDetails(teamId: String): Resource<List<Team>> {
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)
}
}
}
} }

View file

@ -1,8 +1,10 @@
package xyz.leomurca.csgomatches.data.source package xyz.leomurca.csgomatches.data.source
import xyz.leomurca.csgomatches.data.model.MatchDto import xyz.leomurca.csgomatches.data.model.MatchDto
import xyz.leomurca.csgomatches.data.model.TeamDetailsDto
import xyz.leomurca.csgomatches.domain.model.Resource import xyz.leomurca.csgomatches.domain.model.Resource
interface MatchDataSource { interface MatchDataSource {
suspend fun upcomingMatches(): Resource<List<MatchDto>> suspend fun upcomingMatches(): Resource<List<MatchDto>>
suspend fun teamDetails(teamId: String): Resource<List<TeamDetailsDto>>
} }

View file

@ -2,7 +2,6 @@ package xyz.leomurca.csgomatches.domain.model
import java.time.ZonedDateTime import java.time.ZonedDateTime
data class Match( data class Match(
val beginAt: ZonedDateTime?, val beginAt: ZonedDateTime?,
val opponents: List<Opponent>, val opponents: List<Opponent>,

View file

@ -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?
)

View file

@ -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<Player>
)

View file

@ -2,8 +2,11 @@ package xyz.leomurca.csgomatches.domain.repository
import xyz.leomurca.csgomatches.domain.model.Match import xyz.leomurca.csgomatches.domain.model.Match
import xyz.leomurca.csgomatches.domain.model.Resource import xyz.leomurca.csgomatches.domain.model.Resource
import xyz.leomurca.csgomatches.domain.model.Team
interface MatchRepository { interface MatchRepository {
suspend fun upcomingMatches(): Resource<List<Match>> suspend fun upcomingMatches(): Resource<List<Match>>
suspend fun teamDetails(teamId: String): Resource<List<Team>>
} }

View file

@ -33,6 +33,7 @@ import xyz.leomurca.csgomatches.domain.model.League
import xyz.leomurca.csgomatches.domain.model.Match import xyz.leomurca.csgomatches.domain.model.Match
import xyz.leomurca.csgomatches.domain.model.MatchStatus import xyz.leomurca.csgomatches.domain.model.MatchStatus
import xyz.leomurca.csgomatches.domain.model.Opponent 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.LiveRed
import xyz.leomurca.csgomatches.ui.theme.White_20 import xyz.leomurca.csgomatches.ui.theme.White_20
import xyz.leomurca.csgomatches.ui.theme.White_50 import xyz.leomurca.csgomatches.ui.theme.White_50
@ -42,23 +43,35 @@ import java.time.ZonedDateTime
@Composable @Composable
fun MatchCard( fun MatchCard(
match: Match, match: Match,
onTapCard: (DetailsRoute) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
Box(modifier = modifier.fillMaxWidth()) { 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( Card(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(16.dp), onClick = {
shape = RoundedCornerShape(16.dp), onTapCard(
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface) DetailsRoute(
leftOpponentId = leftOpponent.id,
rightOpponentId = rightOpponent.id,
leagueAndSerieName = leagueAndSerieName,
scheduleString = scheduleConfig.first,
)
)
}, colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface)
) { ) {
MatchupRow(match.opponents) MatchupRow(leftOpponent, rightOpponent)
LeagueInfoRow(match.league, match.serie.name) LeagueInfoRow(match.league, leagueAndSerieName)
} }
ScheduleBadge(match.status, match.beginAt) ScheduleBadge(scheduleConfig)
} }
} }
@Composable @Composable
private fun MatchupRow(opponents: List<Opponent>) { private fun MatchupRow(leftOpponent: Opponent, rightOpponent: Opponent) {
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@ -66,7 +79,6 @@ private fun MatchupRow(opponents: List<Opponent>) {
horizontalArrangement = Arrangement.Center, horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
val (leftOpponent, rightOpponent) = getOrDefaultOpponents(opponents)
Column(horizontalAlignment = Alignment.CenterHorizontally) { Column(horizontalAlignment = Alignment.CenterHorizontally) {
AsyncImage( AsyncImage(
@ -110,7 +122,7 @@ private fun MatchupRow(opponents: List<Opponent>) {
} }
@Composable @Composable
private fun LeagueInfoRow(league: League, serieName: String) { private fun LeagueInfoRow(league: League, leagueAndSerieName: String) {
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
modifier = Modifier modifier = Modifier
@ -123,15 +135,14 @@ private fun LeagueInfoRow(league: League, serieName: String) {
contentDescription = "${league.name} logo", contentDescription = "${league.name} logo",
error = painterResource(R.drawable.fallback_image_round), error = painterResource(R.drawable.fallback_image_round),
placeholder = painterResource(R.drawable.fallback_image_round), placeholder = painterResource(R.drawable.fallback_image_round),
onLoading = { onLoading = {},
},
modifier = Modifier modifier = Modifier
.height(16.dp) .height(16.dp)
.wrapContentWidth(), .wrapContentWidth(),
contentScale = ContentScale.Fit contentScale = ContentScale.Fit
) )
Text( Text(
"${league.name} + $serieName", leagueAndSerieName,
style = MaterialTheme.typography.labelSmall, style = MaterialTheme.typography.labelSmall,
modifier = Modifier.padding(start = 8.dp) modifier = Modifier.padding(start = 8.dp)
) )
@ -139,52 +150,20 @@ private fun LeagueInfoRow(league: League, serieName: String) {
} }
@Composable @Composable
private fun BoxScope.ScheduleBadge(status: MatchStatus, beginAt: ZonedDateTime?) { private fun BoxScope.ScheduleBadge(scheduleConfig: Pair<String, Color>) {
when (status) { val (scheduleString, scheduleBackgroundColor) = scheduleConfig
MatchStatus.LIVE -> Box(
Box( modifier = Modifier
modifier = Modifier .align(Alignment.TopEnd)
.align(Alignment.TopEnd) .clip(RoundedCornerShape(topEnd = 16.dp, bottomStart = 16.dp))
.clip(RoundedCornerShape(topEnd = 16.dp, bottomStart = 16.dp)) .background(scheduleBackgroundColor)
.background(LiveRed) .padding(8.dp)
.padding(8.dp) ) {
) { Text(
Text( text = scheduleString,
text = "AGORA", style = MaterialTheme.typography.displayMedium,
style = MaterialTheme.typography.displayMedium, color = Color.White
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
)
}
} }
} }
@ -197,8 +176,7 @@ private fun Modifier.topBorder(color: Color, thickness: Dp): Modifier = this.the
end = Offset(size.width, 0f), end = Offset(size.width, 0f),
strokeWidth = strokeWidth strokeWidth = strokeWidth
) )
} })
)
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 = 0, name = "A ser definido", imageUrl = "")
@ -209,3 +187,8 @@ private fun getOrDefaultOpponents(opponents: List<Opponent>): Pair<Opponent, Opp
} }
} }
private fun MatchStatus.scheduleConfigFor(beginAt: ZonedDateTime?) = when (this) {
MatchStatus.LIVE -> "Agora" to LiveRed
MatchStatus.SCHEDULED -> beginAt.toFormattedMatchTime() to White_20
MatchStatus.UNKNOWN -> "A definir" to White_20
}

View file

@ -2,12 +2,21 @@ package xyz.leomurca.csgomatches.ui.navigation
import androidx.navigation.NavGraphBuilder import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.toRoute
import kotlinx.serialization.Serializable
import xyz.leomurca.csgomatches.ui.screens.details.DetailsScreen 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() { fun NavGraphBuilder.detailsScreen(onBackClick: () -> Unit) {
composable(DETAILS_ROUTE) { composable<DetailsRoute> {
DetailsScreen() val details: DetailsRoute = it.toRoute()
DetailsScreen(details = details, onBackClick = onBackClick)
} }
} }

View file

@ -2,12 +2,16 @@ package xyz.leomurca.csgomatches.ui.navigation
import androidx.navigation.NavGraphBuilder import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable 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 import xyz.leomurca.csgomatches.ui.screens.matches.MatchesScreen
const val MATCHES_ROUTE = "matches" @Serializable
object MatchesRoute
fun NavGraphBuilder.matchesScreen() { fun NavGraphBuilder.matchesScreen(onTapCard: (DetailsRoute) -> Unit) {
composable(MATCHES_ROUTE) { composable<MatchesRoute> {
MatchesScreen() MatchesScreen(onTapCard = onTapCard)
} }
} }

View file

@ -9,9 +9,13 @@ fun RootNavHost() {
val navController = rememberNavController() val navController = rememberNavController()
NavHost( NavHost(
navController = navController, navController = navController,
startDestination = MATCHES_ROUTE startDestination = MatchesRoute
) { ) {
matchesScreen() matchesScreen {
detailsScreen() navController.navigate(it)
}
detailsScreen {
navController.popBackStack()
}
} }
} }

View file

@ -2,20 +2,84 @@ package xyz.leomurca.csgomatches.ui.screens.details
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box 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.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.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
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.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 @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( Box(
modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.background), modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.background),
contentAlignment = Alignment.Center 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,
)
} }
} }

View file

@ -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<TeamUiState> = _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
)
}

View file

@ -24,12 +24,16 @@ import androidx.hilt.navigation.compose.hiltViewModel
import xyz.leomurca.csgomatches.domain.model.Match import xyz.leomurca.csgomatches.domain.model.Match
import xyz.leomurca.csgomatches.ui.components.LoadingIndicator import xyz.leomurca.csgomatches.ui.components.LoadingIndicator
import xyz.leomurca.csgomatches.ui.components.MatchCard 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.screens.matches.MatchesViewModel.MatchesUiState
import xyz.leomurca.csgomatches.ui.theme.White import xyz.leomurca.csgomatches.ui.theme.White
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun MatchesScreen(viewModel: MatchesViewModel = hiltViewModel()) { fun MatchesScreen(
viewModel: MatchesViewModel = hiltViewModel(),
onTapCard: (DetailsRoute) -> Unit
) {
val uiState = viewModel.uiState.collectAsState() val uiState = viewModel.uiState.collectAsState()
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
@ -41,7 +45,7 @@ fun MatchesScreen(viewModel: MatchesViewModel = hiltViewModel()) {
"Partidas", "Partidas",
style = MaterialTheme.typography.titleLarge, style = MaterialTheme.typography.titleLarge,
color = White, color = White,
modifier = Modifier.padding(top = 24.dp, start = 24.dp) modifier = Modifier.padding(top = 24.dp, start = 6.dp)
) )
}, },
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
@ -60,7 +64,7 @@ fun MatchesScreen(viewModel: MatchesViewModel = hiltViewModel()) {
) { ) {
when (val value = uiState.value) { when (val value = uiState.value) {
is MatchesUiState.Loading -> LoadingIndicator() is MatchesUiState.Loading -> LoadingIndicator()
is MatchesUiState.Success -> MatchesList(value.matches) is MatchesUiState.Success -> MatchesList(value.matches, onTapCard)
is MatchesUiState.Error -> Text(value.message) is MatchesUiState.Error -> Text(value.message)
} }
} }
@ -68,7 +72,7 @@ fun MatchesScreen(viewModel: MatchesViewModel = hiltViewModel()) {
} }
@Composable @Composable
private fun MatchesList(matches: List<Match>) { private fun MatchesList(matches: List<Match>, onTapCard: (DetailsRoute) -> Unit) {
LazyColumn( LazyColumn(
Modifier Modifier
.padding(horizontal = 24.dp), .padding(horizontal = 24.dp),
@ -76,7 +80,7 @@ private fun MatchesList(matches: List<Match>) {
contentPadding = PaddingValues(vertical = 24.dp) contentPadding = PaddingValues(vertical = 24.dp)
) { ) {
items(matches) { items(matches) {
MatchCard(it) MatchCard(it, onTapCard)
} }
} }
} }