refactor: extract strings to string resources

This commit is contained in:
Leonardo Murça 2025-07-19 19:58:08 -03:00
parent 4631c7a42b
commit a3fdd86e01
7 changed files with 65 additions and 26 deletions

View file

@ -19,8 +19,10 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import xyz.leomurca.csgomatches.R
@Composable @Composable
fun ErrorMessage( fun ErrorMessage(
@ -55,11 +57,15 @@ fun ErrorMessage(
if (onRetry != null) { if (onRetry != null) {
Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.height(24.dp))
Button(onClick = onRetry, Button(
onClick = onRetry,
colors = ButtonDefaults.buttonColors() colors = ButtonDefaults.buttonColors()
.copy(containerColor = MaterialTheme.colorScheme.secondary) .copy(containerColor = MaterialTheme.colorScheme.secondary)
) { ) {
Text("Tentar novamente", style = MaterialTheme.typography.bodyMedium) Text(
stringResource(R.string.try_again),
style = MaterialTheme.typography.bodyMedium
)
} }
} }
} }

View file

@ -25,7 +25,9 @@ import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@ -86,7 +88,7 @@ private fun MatchupRow(leftOpponent: Opponent, rightOpponent: Opponent) {
Column(horizontalAlignment = Alignment.CenterHorizontally) { Column(horizontalAlignment = Alignment.CenterHorizontally) {
AsyncImage( AsyncImage(
model = leftOpponent.imageUrl, model = leftOpponent.imageUrl,
contentDescription = "${leftOpponent.name} logo", contentDescription = stringResource(R.string.logo_description, leftOpponent.name),
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),
modifier = Modifier.size(60.dp), modifier = Modifier.size(60.dp),
@ -103,7 +105,7 @@ private fun MatchupRow(leftOpponent: Opponent, rightOpponent: Opponent) {
} }
Text( Text(
"vs", stringResource(R.string.versus),
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
color = White_50, color = White_50,
modifier = Modifier.padding(horizontal = 20.dp) modifier = Modifier.padding(horizontal = 20.dp)
@ -112,7 +114,7 @@ private fun MatchupRow(leftOpponent: Opponent, rightOpponent: Opponent) {
Column(horizontalAlignment = Alignment.CenterHorizontally) { Column(horizontalAlignment = Alignment.CenterHorizontally) {
AsyncImage( AsyncImage(
model = rightOpponent.imageUrl, model = rightOpponent.imageUrl,
contentDescription = "${rightOpponent.name} logo", contentDescription = stringResource(R.string.logo_description, rightOpponent.name),
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),
modifier = Modifier.size(60.dp), modifier = Modifier.size(60.dp),
@ -141,10 +143,9 @@ private fun LeagueInfoRow(league: League, leagueAndSerieName: String) {
) { ) {
AsyncImage( AsyncImage(
model = league.imageUrl, model = league.imageUrl,
contentDescription = "${league.name} logo", contentDescription = stringResource(R.string.logo_description, league.name),
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 = {},
modifier = Modifier modifier = Modifier
.height(16.dp) .height(16.dp)
.wrapContentWidth(), .wrapContentWidth(),
@ -187,8 +188,9 @@ private fun Modifier.topBorder(color: Color, thickness: Dp): Modifier = this.the
) )
}) })
@Composable
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 definir", imageUrl = "") val default = Opponent(id = -1, name = stringResource(R.string.to_be_defined), 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
@ -196,8 +198,9 @@ private fun getOrDefaultOpponents(opponents: List<Opponent>): Pair<Opponent, Opp
} }
} }
@Composable
private fun MatchStatus.scheduleConfigFor(beginAt: ZonedDateTime?) = when (this) { private fun MatchStatus.scheduleConfigFor(beginAt: ZonedDateTime?) = when (this) {
MatchStatus.LIVE -> "Agora" to LiveRed MatchStatus.LIVE -> stringResource(R.string.live) to LiveRed
MatchStatus.SCHEDULED -> beginAt.toFormattedMatchTime() to White_20 MatchStatus.SCHEDULED -> beginAt.toFormattedMatchTime(LocalContext.current) to White_20
MatchStatus.UNKNOWN -> "A definir" to White_20 MatchStatus.UNKNOWN -> stringResource(R.string.to_be_defined) to White_20
} }

View file

@ -36,6 +36,7 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color 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.res.stringResource
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.text.style.TextOverflow
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
@ -167,7 +168,7 @@ private fun PlayerAvatar(size: Dp, imageUrl: String?, nickName: String?) {
) { ) {
AsyncImage( AsyncImage(
model = imageUrl, model = imageUrl,
contentDescription = "$nickName logo", contentDescription = stringResource(R.string.logo_description, nickName ?: ""),
contentScale = ContentScale.Crop, contentScale = ContentScale.Crop,
onState = { isLoading = it is AsyncImagePainter.State.Loading }, onState = { isLoading = it is AsyncImagePainter.State.Loading },
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
@ -191,7 +192,7 @@ private fun PlayerInfo(player: Player, modifier: Modifier = Modifier, alignEnd:
horizontalAlignment = if (alignEnd) Alignment.End else Alignment.Start horizontalAlignment = if (alignEnd) Alignment.End else Alignment.Start
) { ) {
Text( Text(
text = player.nickName ?: "A definir", text = player.nickName ?: stringResource(R.string.to_be_defined),
color = Color.White, color = Color.White,
style = MaterialTheme.typography.headlineLarge, style = MaterialTheme.typography.headlineLarge,
maxLines = 1, maxLines = 1,
@ -239,14 +240,14 @@ private fun MatchupRow(leftTeam: Team, rightTeam: Team) {
Column(horizontalAlignment = Alignment.CenterHorizontally) { Column(horizontalAlignment = Alignment.CenterHorizontally) {
AsyncImage( AsyncImage(
model = leftTeam.imageUrl, model = leftTeam.imageUrl,
contentDescription = "${leftTeam.name} logo", contentDescription = stringResource(R.string.logo_description, leftTeam.name ?: ""),
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),
modifier = Modifier.size(60.dp), modifier = Modifier.size(60.dp),
contentScale = ContentScale.Fit contentScale = ContentScale.Fit
) )
Text( Text(
leftTeam.name ?: "A definir", leftTeam.name ?: stringResource(R.string.to_be_defined),
style = MaterialTheme.typography.labelMedium, style = MaterialTheme.typography.labelMedium,
color = White, color = White,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
@ -256,7 +257,7 @@ private fun MatchupRow(leftTeam: Team, rightTeam: Team) {
) )
} }
Text( Text(
"vs", stringResource(R.string.versus),
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
color = White_50, color = White_50,
modifier = Modifier.padding(horizontal = 20.dp) modifier = Modifier.padding(horizontal = 20.dp)
@ -265,14 +266,17 @@ private fun MatchupRow(leftTeam: Team, rightTeam: Team) {
Column(horizontalAlignment = Alignment.CenterHorizontally) { Column(horizontalAlignment = Alignment.CenterHorizontally) {
AsyncImage( AsyncImage(
model = rightTeam.imageUrl, model = rightTeam.imageUrl,
contentDescription = "${rightTeam.name} logo", contentDescription = stringResource(
R.string.logo_description,
rightTeam.name ?: ""
),
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),
modifier = Modifier.size(60.dp), modifier = Modifier.size(60.dp),
contentScale = ContentScale.Fit contentScale = ContentScale.Fit
) )
Text( Text(
rightTeam.name ?: "A definir", rightTeam.name ?: stringResource(R.string.to_be_defined),
style = MaterialTheme.typography.labelMedium, style = MaterialTheme.typography.labelMedium,
color = White, color = White,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
@ -300,7 +304,7 @@ private fun TopBar(leagueAndSerieName: String, onBackClick: () -> Unit) {
) { ) {
Icon( Icon(
imageVector = Icons.AutoMirrored.Default.ArrowBack, imageVector = Icons.AutoMirrored.Default.ArrowBack,
contentDescription = "Back", contentDescription = stringResource(R.string.back),
tint = White tint = White
) )
} }

View file

@ -48,7 +48,10 @@ class MatchDetailsViewModel @Inject constructor(
private fun teamOrWithDefaultPlayers(team: Team?): Team? { private fun teamOrWithDefaultPlayers(team: Team?): Team? {
return team?.let { return team?.let {
val filledPlayers = it.players + List((5 - it.players.size).coerceAtLeast(0)) { playerPlaceholder() } val filledPlayers =
it.players + List((DEFAULT_TEAM_SIZE - it.players.size).coerceAtLeast(0)) {
playerPlaceholder()
}
it.copy(players = filledPlayers) it.copy(players = filledPlayers)
} }
} }
@ -65,7 +68,7 @@ class MatchDetailsViewModel @Inject constructor(
} }
private fun playerPlaceholder() = Player( private fun playerPlaceholder() = Player(
id = -1, nickName = "A definir", firstName = "", lastName = "", imageUrl = null id = -1, nickName = null, firstName = null, lastName = null, imageUrl = null
) )
private fun teamPlaceholder() = Team( private fun teamPlaceholder() = Team(
@ -78,4 +81,8 @@ class MatchDetailsViewModel @Inject constructor(
val isLoading: Boolean = true, val isLoading: Boolean = true,
val errorMessage: String? = null val errorMessage: String? = null
) )
companion object {
private const val DEFAULT_TEAM_SIZE = 5
}
} }

View file

@ -26,8 +26,10 @@ import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource
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 xyz.leomurca.csgomatches.R
import xyz.leomurca.csgomatches.domain.model.Match import xyz.leomurca.csgomatches.domain.model.Match
import xyz.leomurca.csgomatches.ui.components.ErrorMessage import xyz.leomurca.csgomatches.ui.components.ErrorMessage
import xyz.leomurca.csgomatches.ui.components.MatchCard import xyz.leomurca.csgomatches.ui.components.MatchCard
@ -63,7 +65,7 @@ fun MatchesScreen(
TopAppBar( TopAppBar(
title = { title = {
Text( Text(
"Partidas", stringResource(R.string.matches),
style = MaterialTheme.typography.titleLarge, style = MaterialTheme.typography.titleLarge,
color = White, color = White,
modifier = Modifier.padding(top = 24.dp, start = 6.dp) modifier = Modifier.padding(top = 24.dp, start = 6.dp)

View file

@ -1,5 +1,7 @@
package xyz.leomurca.csgomatches.utils package xyz.leomurca.csgomatches.utils
import android.content.Context
import xyz.leomurca.csgomatches.R
import java.time.DayOfWeek import java.time.DayOfWeek
import java.time.LocalDate import java.time.LocalDate
import java.time.ZonedDateTime import java.time.ZonedDateTime
@ -19,20 +21,25 @@ import java.util.Locale
* @receiver the [ZonedDateTime] instance to format. If null, an empty string is returned. * @receiver the [ZonedDateTime] instance to format. If null, an empty string is returned.
* @return a formatted string representing the match time, or `"A definir"` if the input is null. * @return a formatted string representing the match time, or `"A definir"` if the input is null.
*/ */
fun ZonedDateTime?.toFormattedMatchTime(): String { fun ZonedDateTime?.toFormattedMatchTime(context: Context): String {
if (this == null) return "A definir" if (this == null) return context.getString(R.string.match_time_tbd)
val targetDate = toLocalDate() val targetDate = toLocalDate()
val timeFormatter = DateTimeFormatter.ofPattern("HH:mm") val timeFormatter = DateTimeFormatter.ofPattern("HH:mm")
return when { return when {
targetDate.isToday() -> "Hoje, ${format(timeFormatter)}" targetDate.isToday() -> {
val time = format(timeFormatter)
context.getString(R.string.match_time_today, time)
}
targetDate.isInCurrentWeek() -> { targetDate.isInCurrentWeek() -> {
val dayOfWeekFormatter = DateTimeFormatter.ofPattern("EEE", Locale("pt", "BR")) val dayOfWeekFormatter = DateTimeFormatter.ofPattern("EEE", Locale("pt", "BR"))
val day = format(dayOfWeekFormatter).replaceFirstChar { val day = format(dayOfWeekFormatter).replaceFirstChar {
it.titlecase(Locale("pt", "BR")) it.titlecase(Locale("pt", "BR"))
}.dropLast(1) }.dropLast(1)
"$day, ${format(timeFormatter)}" val time = format(timeFormatter)
context.getString(R.string.match_time_weekday, day, time)
} }
else -> { else -> {

View file

@ -1,3 +1,13 @@
<resources> <resources>
<string name="app_name">CSGO Matches</string> <string name="app_name">CSGO Matches</string>
<string name="matches">Partidas</string>
<string name="versus">vs</string>
<string name="to_be_defined">A definir</string>
<string name="logo_description">%1$s logo</string>
<string name="live">Agora</string>
<string name="match_time_tbd">A definir</string>
<string name="match_time_today">Hoje, %1$s</string>
<string name="match_time_weekday">%1$s, %2$s</string>
<string name="try_again">Tentar novamente</string>
<string name="back">Voltar</string>
</resources> </resources>