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> {
|
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 {
|
return when {
|
||||||
opponents.size >= 2 -> opponents[0] to opponents[1]
|
opponents.size >= 2 -> opponents[0] to opponents[1]
|
||||||
opponents.size == 1 -> opponents[0] to default
|
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.background
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
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.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.requiredWidthIn
|
import androidx.compose.foundation.layout.requiredWidthIn
|
||||||
import androidx.compose.foundation.layout.size
|
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.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
@ -19,16 +26,26 @@ import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
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.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
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.layout.ContentScale
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
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.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import coil3.compose.AsyncImage
|
import coil3.compose.AsyncImage
|
||||||
|
import coil3.compose.AsyncImagePainter
|
||||||
import xyz.leomurca.csgomatches.R
|
import xyz.leomurca.csgomatches.R
|
||||||
import xyz.leomurca.csgomatches.domain.model.MatchStatus
|
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.domain.model.Team
|
||||||
import xyz.leomurca.csgomatches.ui.components.LoadingIndicator
|
import xyz.leomurca.csgomatches.ui.components.LoadingIndicator
|
||||||
import xyz.leomurca.csgomatches.ui.navigation.MatchDetailsRoute
|
import xyz.leomurca.csgomatches.ui.navigation.MatchDetailsRoute
|
||||||
|
@ -48,12 +65,12 @@ fun MatchDetailsScreen(
|
||||||
if (matchDetails.leftOpponentId != -1L) {
|
if (matchDetails.leftOpponentId != -1L) {
|
||||||
viewModel.loadTeam(matchDetails.leftOpponentId.toString(), isLeft = true)
|
viewModel.loadTeam(matchDetails.leftOpponentId.toString(), isLeft = true)
|
||||||
} else {
|
} else {
|
||||||
viewModel.updateTeamToDefault(isLeft = true)
|
viewModel.applyPlaceholderTeamToSide(isLeft = true)
|
||||||
}
|
}
|
||||||
if (matchDetails.rightOpponentId != -1L) {
|
if (matchDetails.rightOpponentId != -1L) {
|
||||||
viewModel.loadTeam(matchDetails.rightOpponentId.toString(), isLeft = false)
|
viewModel.loadTeam(matchDetails.rightOpponentId.toString(), isLeft = false)
|
||||||
} else {
|
} else {
|
||||||
viewModel.updateTeamToDefault(isLeft = false)
|
viewModel.applyPlaceholderTeamToSide(isLeft = false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,13 +88,130 @@ fun MatchDetailsScreen(
|
||||||
value.leftTeam != null && value.rightTeam != null -> Column {
|
value.leftTeam != null && value.rightTeam != null -> Column {
|
||||||
MatchupRow(leftTeam = value.leftTeam, rightTeam = value.rightTeam)
|
MatchupRow(leftTeam = value.leftTeam, rightTeam = value.rightTeam)
|
||||||
ScheduleRow(matchDetails.scheduleString, matchDetails.matchStatus)
|
ScheduleRow(matchDetails.scheduleString, matchDetails.matchStatus)
|
||||||
|
DualPlayerLists(value.leftTeam.players, value.rightTeam.players)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@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(
|
Row(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
@ -115,7 +249,7 @@ private fun MatchupRow(leftTeam: Team, rightTeam: Team) {
|
||||||
contentScale = ContentScale.Fit
|
contentScale = ContentScale.Fit
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
leftTeam.name ?: "A ser definido",
|
leftTeam.name ?: "A definir",
|
||||||
style = MaterialTheme.typography.labelMedium,
|
style = MaterialTheme.typography.labelMedium,
|
||||||
color = White,
|
color = White,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
|
@ -141,7 +275,7 @@ private fun MatchupRow(leftTeam: Team, rightTeam: Team) {
|
||||||
contentScale = ContentScale.Fit
|
contentScale = ContentScale.Fit
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
rightTeam.name ?: "A ser definido",
|
rightTeam.name ?: "A definir",
|
||||||
style = MaterialTheme.typography.labelMedium,
|
style = MaterialTheme.typography.labelMedium,
|
||||||
color = White,
|
color = White,
|
||||||
textAlign = TextAlign.Center,
|
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.asStateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import xyz.leomurca.csgomatches.domain.model.Player
|
||||||
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.model.Team
|
||||||
import xyz.leomurca.csgomatches.domain.repository.MatchRepository
|
import xyz.leomurca.csgomatches.domain.repository.MatchRepository
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import kotlin.String
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class MatchDetailsViewModel @Inject constructor(
|
class MatchDetailsViewModel @Inject constructor(
|
||||||
private val matchRepository: MatchRepository,
|
private val matchRepository: MatchRepository
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val _uiState = MutableStateFlow(TeamUiState())
|
private val _uiState = MutableStateFlow(TeamUiState())
|
||||||
|
@ -23,15 +25,11 @@ class MatchDetailsViewModel @Inject constructor(
|
||||||
|
|
||||||
fun loadTeam(teamId: String, isLeft: Boolean) {
|
fun loadTeam(teamId: String, isLeft: Boolean) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_uiState.update { it.copy(isLoading = true, errorMessage = null) }
|
setLoading()
|
||||||
|
|
||||||
when (val result = matchRepository.teamDetails(teamId)) {
|
when (val result = matchRepository.teamDetails(teamId)) {
|
||||||
is Resource.Success -> {
|
is Resource.Success -> {
|
||||||
val team = result.data.firstOrNull()
|
val team = teamOrWithDefaultPlayers(result.data.firstOrNull())
|
||||||
_uiState.update {
|
updateTeam(team, isLeft)
|
||||||
if (isLeft) it.copy(leftTeam = team, isLoading = false)
|
|
||||||
else it.copy(rightTeam = team, isLoading = false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
is Resource.Error -> {
|
is Resource.Error -> {
|
||||||
|
@ -43,32 +41,35 @@ class MatchDetailsViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateTeamToDefault(isLeft: Boolean) {
|
fun applyPlaceholderTeamToSide(isLeft: Boolean) {
|
||||||
_uiState.update { it.copy(isLoading = true, errorMessage = null) }
|
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 {
|
_uiState.update {
|
||||||
if (isLeft) {
|
if (isLeft) it.copy(leftTeam = team, isLoading = false)
|
||||||
it.copy(
|
else it.copy(rightTeam = team, isLoading = false)
|
||||||
leftTeam = Team(
|
}
|
||||||
id = -1,
|
}
|
||||||
name = null,
|
|
||||||
imageUrl = null,
|
private fun setLoading() {
|
||||||
players = emptyList()
|
_uiState.update { it.copy(isLoading = true, errorMessage = null) }
|
||||||
),
|
}
|
||||||
isLoading = false
|
|
||||||
|
private fun playerPlaceholder() = Player(
|
||||||
|
id = -1, nickName = "A definir", firstName = "", lastName = "", imageUrl = null
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
it.copy(
|
private fun teamPlaceholder() = Team(
|
||||||
rightTeam = Team(
|
id = -1, name = null, imageUrl = null, players = List(5) { playerPlaceholder() }
|
||||||
id = -1,
|
|
||||||
name = null,
|
|
||||||
imageUrl = null,
|
|
||||||
players = emptyList()
|
|
||||||
),
|
|
||||||
isLoading = false
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class TeamUiState(
|
data class TeamUiState(
|
||||||
val leftTeam: Team? = null,
|
val leftTeam: Team? = null,
|
||||||
|
|
Loading…
Add table
Reference in a new issue