feat: add details screen with navigation and initial structure
This commit is contained in:
parent
c5d1bbcbed
commit
07d20aba86
19 changed files with 312 additions and 82 deletions
|
@ -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<List<TeamDetailsDto>> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
|
@ -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(
|
||||
|
@ -32,3 +35,20 @@ fun MatchDto.toDomain(): Match {
|
|||
|
||||
private fun String?.toMatchStatus() =
|
||||
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
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
|
@ -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?
|
||||
)
|
|
@ -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>
|
||||
)
|
|
@ -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<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())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<List<MatchDto>>
|
||||
|
||||
@GET("teams")
|
||||
suspend fun teamDetails(
|
||||
@Query("filter[id]") id: String
|
||||
): Response<List<TeamDetailsDto>>
|
||||
}
|
|
@ -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<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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<List<MatchDto>>
|
||||
suspend fun teamDetails(teamId: String): Resource<List<TeamDetailsDto>>
|
||||
}
|
|
@ -2,7 +2,6 @@ package xyz.leomurca.csgomatches.domain.model
|
|||
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
|
||||
data class Match(
|
||||
val beginAt: ZonedDateTime?,
|
||||
val opponents: List<Opponent>,
|
||||
|
|
|
@ -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?
|
||||
)
|
|
@ -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>
|
||||
)
|
|
@ -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<List<Match>>
|
||||
|
||||
suspend fun teamDetails(teamId: String): Resource<List<Team>>
|
||||
}
|
|
@ -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<Opponent>) {
|
||||
private fun MatchupRow(leftOpponent: Opponent, rightOpponent: Opponent) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
|
@ -66,7 +79,6 @@ private fun MatchupRow(opponents: List<Opponent>) {
|
|||
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<Opponent>) {
|
|||
}
|
||||
|
||||
@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,53 +150,21 @@ private fun LeagueInfoRow(league: League, serieName: String) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun BoxScope.ScheduleBadge(status: MatchStatus, beginAt: ZonedDateTime?) {
|
||||
when (status) {
|
||||
MatchStatus.LIVE ->
|
||||
private fun BoxScope.ScheduleBadge(scheduleConfig: Pair<String, Color>) {
|
||||
val (scheduleString, scheduleBackgroundColor) = scheduleConfig
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopEnd)
|
||||
.clip(RoundedCornerShape(topEnd = 16.dp, bottomStart = 16.dp))
|
||||
.background(LiveRed)
|
||||
.background(scheduleBackgroundColor)
|
||||
.padding(8.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "AGORA",
|
||||
text = scheduleString,
|
||||
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 Modifier.topBorder(color: Color, thickness: Dp): Modifier = this.then(
|
||||
|
@ -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<Opponent>): Pair<Opponent, Opponent> {
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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<DetailsRoute> {
|
||||
val details: DetailsRoute = it.toRoute()
|
||||
DetailsScreen(details = details, onBackClick = onBackClick)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<MatchesRoute> {
|
||||
MatchesScreen(onTapCard = onTapCard)
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
|
@ -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<Match>) {
|
||||
private fun MatchesList(matches: List<Match>, onTapCard: (DetailsRoute) -> Unit) {
|
||||
LazyColumn(
|
||||
Modifier
|
||||
.padding(horizontal = 24.dp),
|
||||
|
@ -76,7 +80,7 @@ private fun MatchesList(matches: List<Match>) {
|
|||
contentPadding = PaddingValues(vertical = 24.dp)
|
||||
) {
|
||||
items(matches) {
|
||||
MatchCard(it)
|
||||
MatchCard(it, onTapCard)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue