feat: add 2 columns players lists
This commit is contained in:
parent
fc2c939e0b
commit
ab727dd061
3 changed files with 181 additions and 38 deletions
|
@ -188,7 +188,7 @@ private fun Modifier.topBorder(color: Color, thickness: Dp): Modifier = this.the
|
|||
})
|
||||
|
||||
private fun getOrDefaultOpponents(opponents: List<Opponent>): Pair<Opponent, Opponent> {
|
||||
val default = Opponent(id = -1, name = "A ser definido", imageUrl = "")
|
||||
val default = Opponent(id = -1, name = "A definir", imageUrl = "")
|
||||
return when {
|
||||
opponents.size >= 2 -> opponents[0] to opponents[1]
|
||||
opponents.size == 1 -> opponents[0] to default
|
||||
|
|
|
@ -2,16 +2,23 @@ package xyz.leomurca.csgomatches.ui.screens.matchdetails
|
|||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.absoluteOffset
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.requiredWidthIn
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
|
@ -19,16 +26,26 @@ import androidx.compose.material3.Text
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import coil3.compose.AsyncImage
|
||||
import coil3.compose.AsyncImagePainter
|
||||
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.navigation.MatchDetailsRoute
|
||||
|
@ -48,12 +65,12 @@ fun MatchDetailsScreen(
|
|||
if (matchDetails.leftOpponentId != -1L) {
|
||||
viewModel.loadTeam(matchDetails.leftOpponentId.toString(), isLeft = true)
|
||||
} else {
|
||||
viewModel.updateTeamToDefault(isLeft = true)
|
||||
viewModel.applyPlaceholderTeamToSide(isLeft = true)
|
||||
}
|
||||
if (matchDetails.rightOpponentId != -1L) {
|
||||
viewModel.loadTeam(matchDetails.rightOpponentId.toString(), isLeft = false)
|
||||
} else {
|
||||
viewModel.updateTeamToDefault(isLeft = false)
|
||||
viewModel.applyPlaceholderTeamToSide(isLeft = false)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,13 +88,130 @@ fun MatchDetailsScreen(
|
|||
value.leftTeam != null && value.rightTeam != null -> Column {
|
||||
MatchupRow(leftTeam = value.leftTeam, rightTeam = value.rightTeam)
|
||||
ScheduleRow(matchDetails.scheduleString, matchDetails.matchStatus)
|
||||
DualPlayerLists(value.leftTeam.players, value.rightTeam.players)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ScheduleRow(scheduleString: String, matchStatus: MatchStatus) {
|
||||
fun DualPlayerLists(
|
||||
leftPlayers: List<Player>,
|
||||
rightPlayers: List<Player>,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 0.dp, top = 16.dp, end = 0.dp, bottom = 60.dp)
|
||||
) {
|
||||
// Left team
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(end = 4.dp)
|
||||
) {
|
||||
items(leftPlayers) { player ->
|
||||
PlayerCard(player = player, isLeft = true)
|
||||
}
|
||||
}
|
||||
|
||||
// Right team
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(start = 4.dp)
|
||||
) {
|
||||
items(rightPlayers) { player ->
|
||||
PlayerCard(player = player, isLeft = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PlayerCard(player: Player, isLeft: Boolean) {
|
||||
val roundedCornerShape = if (isLeft) RoundedCornerShape(topEnd = 12.dp, bottomEnd = 12.dp)
|
||||
else RoundedCornerShape(topStart = 12.dp, bottomStart = 12.dp)
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 6.dp)
|
||||
.background(MaterialTheme.colorScheme.surface, roundedCornerShape)
|
||||
.padding(
|
||||
top = 8.dp,
|
||||
bottom = 8.dp,
|
||||
start = if (isLeft) 4.dp else 12.dp,
|
||||
end = if (isLeft) 12.dp else 4.dp
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = if (isLeft) Arrangement.End else Arrangement.Start
|
||||
) {
|
||||
if (isLeft) {
|
||||
PlayerInfo(player, Modifier.width(100.dp), alignEnd = true)
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
PlayerAvatar(size = 48.dp, player.imageUrl, player.nickName)
|
||||
} else {
|
||||
PlayerAvatar(size = 48.dp, player.imageUrl, player.nickName)
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
PlayerInfo(player, Modifier.width(100.dp), alignEnd = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PlayerAvatar(size: Dp, imageUrl: String?, nickName: String?) {
|
||||
var isLoading by remember { mutableStateOf(true) }
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.absoluteOffset(y = (-12).dp)
|
||||
.size(size)
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(Color.LightGray)
|
||||
) {
|
||||
AsyncImage(
|
||||
model = imageUrl,
|
||||
contentDescription = "$nickName logo",
|
||||
contentScale = ContentScale.Crop,
|
||||
onState = { isLoading = it is AsyncImagePainter.State.Loading },
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
if (isLoading) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(16.dp).align(Alignment.Center),
|
||||
strokeWidth = 2.dp,
|
||||
color = MaterialTheme.colorScheme.background
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PlayerInfo(player: Player, modifier: Modifier = Modifier, alignEnd: Boolean) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
horizontalAlignment = if (alignEnd) Alignment.End else Alignment.Start
|
||||
) {
|
||||
Text(
|
||||
text = player.nickName ?: "A definir",
|
||||
color = Color.White,
|
||||
style = MaterialTheme.typography.headlineLarge,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Text(
|
||||
text = playerFullNameOrEmpty(player.firstName, player.lastName),
|
||||
color = Color.Gray,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ScheduleRow(scheduleString: String, matchStatus: MatchStatus) {
|
||||
Row(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
|
@ -115,7 +249,7 @@ private fun MatchupRow(leftTeam: Team, rightTeam: Team) {
|
|||
contentScale = ContentScale.Fit
|
||||
)
|
||||
Text(
|
||||
leftTeam.name ?: "A ser definido",
|
||||
leftTeam.name ?: "A definir",
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = White,
|
||||
textAlign = TextAlign.Center,
|
||||
|
@ -141,7 +275,7 @@ private fun MatchupRow(leftTeam: Team, rightTeam: Team) {
|
|||
contentScale = ContentScale.Fit
|
||||
)
|
||||
Text(
|
||||
rightTeam.name ?: "A ser definido",
|
||||
rightTeam.name ?: "A definir",
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = White,
|
||||
textAlign = TextAlign.Center,
|
||||
|
@ -174,3 +308,11 @@ private fun TopBar(leagueAndSerieName: String, onBackClick: () -> Unit) {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun playerFullNameOrEmpty(firstName: String?, lastName: String?): String {
|
||||
val fullName = listOfNotNull(firstName?.trim(), lastName?.trim())
|
||||
.filter { it.isNotBlank() }
|
||||
.joinToString(" ")
|
||||
|
||||
return if (fullName.isBlank()) "" else fullName
|
||||
}
|
||||
|
|
|
@ -8,14 +8,16 @@ 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.Player
|
||||
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
|
||||
import kotlin.String
|
||||
|
||||
@HiltViewModel
|
||||
class MatchDetailsViewModel @Inject constructor(
|
||||
private val matchRepository: MatchRepository,
|
||||
private val matchRepository: MatchRepository
|
||||
) : ViewModel() {
|
||||
|
||||
private val _uiState = MutableStateFlow(TeamUiState())
|
||||
|
@ -23,15 +25,11 @@ class MatchDetailsViewModel @Inject constructor(
|
|||
|
||||
fun loadTeam(teamId: String, isLeft: Boolean) {
|
||||
viewModelScope.launch {
|
||||
_uiState.update { it.copy(isLoading = true, errorMessage = null) }
|
||||
|
||||
setLoading()
|
||||
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)
|
||||
}
|
||||
val team = teamOrWithDefaultPlayers(result.data.firstOrNull())
|
||||
updateTeam(team, isLeft)
|
||||
}
|
||||
|
||||
is Resource.Error -> {
|
||||
|
@ -43,32 +41,35 @@ class MatchDetailsViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
fun updateTeamToDefault(isLeft: Boolean) {
|
||||
_uiState.update { it.copy(isLoading = true, errorMessage = null) }
|
||||
fun applyPlaceholderTeamToSide(isLeft: Boolean) {
|
||||
setLoading()
|
||||
updateTeam(teamPlaceholder(), isLeft)
|
||||
}
|
||||
|
||||
private fun teamOrWithDefaultPlayers(team: Team?): Team? {
|
||||
return team?.let {
|
||||
if (it.players.isEmpty()) it.copy(players = List(5) { playerPlaceholder() }) else it
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateTeam(team: Team?, isLeft: Boolean) {
|
||||
_uiState.update {
|
||||
if (isLeft) {
|
||||
it.copy(
|
||||
leftTeam = Team(
|
||||
id = -1,
|
||||
name = null,
|
||||
imageUrl = null,
|
||||
players = emptyList()
|
||||
),
|
||||
isLoading = false
|
||||
if (isLeft) it.copy(leftTeam = team, isLoading = false)
|
||||
else it.copy(rightTeam = team, isLoading = false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setLoading() {
|
||||
_uiState.update { it.copy(isLoading = true, errorMessage = null) }
|
||||
}
|
||||
|
||||
private fun playerPlaceholder() = Player(
|
||||
id = -1, nickName = "A definir", firstName = "", lastName = "", imageUrl = null
|
||||
)
|
||||
} else {
|
||||
it.copy(
|
||||
rightTeam = Team(
|
||||
id = -1,
|
||||
name = null,
|
||||
imageUrl = null,
|
||||
players = emptyList()
|
||||
),
|
||||
isLoading = false
|
||||
|
||||
private fun teamPlaceholder() = Team(
|
||||
id = -1, name = null, imageUrl = null, players = List(5) { playerPlaceholder() }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class TeamUiState(
|
||||
val leftTeam: Team? = null,
|
||||
|
|
Loading…
Add table
Reference in a new issue