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.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import xyz.leomurca.csgomatches.R
@Composable
fun ErrorMessage(
@ -55,11 +57,15 @@ fun ErrorMessage(
if (onRetry != null) {
Spacer(modifier = Modifier.height(24.dp))
Button(onClick = onRetry,
Button(
onClick = onRetry,
colors = ButtonDefaults.buttonColors()
.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.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
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) {
AsyncImage(
model = leftOpponent.imageUrl,
contentDescription = "${leftOpponent.name} logo",
contentDescription = stringResource(R.string.logo_description, leftOpponent.name),
error = painterResource(R.drawable.fallback_image_round),
placeholder = painterResource(R.drawable.fallback_image_round),
modifier = Modifier.size(60.dp),
@ -103,7 +105,7 @@ private fun MatchupRow(leftOpponent: Opponent, rightOpponent: Opponent) {
}
Text(
"vs",
stringResource(R.string.versus),
style = MaterialTheme.typography.bodyMedium,
color = White_50,
modifier = Modifier.padding(horizontal = 20.dp)
@ -112,7 +114,7 @@ private fun MatchupRow(leftOpponent: Opponent, rightOpponent: Opponent) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
AsyncImage(
model = rightOpponent.imageUrl,
contentDescription = "${rightOpponent.name} logo",
contentDescription = stringResource(R.string.logo_description, rightOpponent.name),
error = painterResource(R.drawable.fallback_image_round),
placeholder = painterResource(R.drawable.fallback_image_round),
modifier = Modifier.size(60.dp),
@ -141,10 +143,9 @@ private fun LeagueInfoRow(league: League, leagueAndSerieName: String) {
) {
AsyncImage(
model = league.imageUrl,
contentDescription = "${league.name} logo",
contentDescription = stringResource(R.string.logo_description, league.name),
error = painterResource(R.drawable.fallback_image_round),
placeholder = painterResource(R.drawable.fallback_image_round),
onLoading = {},
modifier = Modifier
.height(16.dp)
.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> {
val default = Opponent(id = -1, name = "A definir", imageUrl = "")
val default = Opponent(id = -1, name = stringResource(R.string.to_be_defined), imageUrl = "")
return when {
opponents.size >= 2 -> opponents[0] to opponents[1]
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) {
MatchStatus.LIVE -> "Agora" to LiveRed
MatchStatus.SCHEDULED -> beginAt.toFormattedMatchTime() to White_20
MatchStatus.UNKNOWN -> "A definir" to White_20
MatchStatus.LIVE -> stringResource(R.string.live) to LiveRed
MatchStatus.SCHEDULED -> beginAt.toFormattedMatchTime(LocalContext.current) 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.layout.ContentScale
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.TextOverflow
import androidx.compose.ui.unit.Dp
@ -167,7 +168,7 @@ private fun PlayerAvatar(size: Dp, imageUrl: String?, nickName: String?) {
) {
AsyncImage(
model = imageUrl,
contentDescription = "$nickName logo",
contentDescription = stringResource(R.string.logo_description, nickName ?: ""),
contentScale = ContentScale.Crop,
onState = { isLoading = it is AsyncImagePainter.State.Loading },
modifier = Modifier.fillMaxSize()
@ -191,7 +192,7 @@ private fun PlayerInfo(player: Player, modifier: Modifier = Modifier, alignEnd:
horizontalAlignment = if (alignEnd) Alignment.End else Alignment.Start
) {
Text(
text = player.nickName ?: "A definir",
text = player.nickName ?: stringResource(R.string.to_be_defined),
color = Color.White,
style = MaterialTheme.typography.headlineLarge,
maxLines = 1,
@ -239,14 +240,14 @@ private fun MatchupRow(leftTeam: Team, rightTeam: Team) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
AsyncImage(
model = leftTeam.imageUrl,
contentDescription = "${leftTeam.name} logo",
contentDescription = stringResource(R.string.logo_description, leftTeam.name ?: ""),
error = painterResource(R.drawable.fallback_image_round),
placeholder = painterResource(R.drawable.fallback_image_round),
modifier = Modifier.size(60.dp),
contentScale = ContentScale.Fit
)
Text(
leftTeam.name ?: "A definir",
leftTeam.name ?: stringResource(R.string.to_be_defined),
style = MaterialTheme.typography.labelMedium,
color = White,
textAlign = TextAlign.Center,
@ -256,7 +257,7 @@ private fun MatchupRow(leftTeam: Team, rightTeam: Team) {
)
}
Text(
"vs",
stringResource(R.string.versus),
style = MaterialTheme.typography.bodyMedium,
color = White_50,
modifier = Modifier.padding(horizontal = 20.dp)
@ -265,14 +266,17 @@ private fun MatchupRow(leftTeam: Team, rightTeam: Team) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
AsyncImage(
model = rightTeam.imageUrl,
contentDescription = "${rightTeam.name} logo",
contentDescription = stringResource(
R.string.logo_description,
rightTeam.name ?: ""
),
error = painterResource(R.drawable.fallback_image_round),
placeholder = painterResource(R.drawable.fallback_image_round),
modifier = Modifier.size(60.dp),
contentScale = ContentScale.Fit
)
Text(
rightTeam.name ?: "A definir",
rightTeam.name ?: stringResource(R.string.to_be_defined),
style = MaterialTheme.typography.labelMedium,
color = White,
textAlign = TextAlign.Center,
@ -300,7 +304,7 @@ private fun TopBar(leagueAndSerieName: String, onBackClick: () -> Unit) {
) {
Icon(
imageVector = Icons.AutoMirrored.Default.ArrowBack,
contentDescription = "Back",
contentDescription = stringResource(R.string.back),
tint = White
)
}

View file

@ -48,7 +48,10 @@ class MatchDetailsViewModel @Inject constructor(
private fun teamOrWithDefaultPlayers(team: Team?): Team? {
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)
}
}
@ -65,7 +68,7 @@ class MatchDetailsViewModel @Inject constructor(
}
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(
@ -78,4 +81,8 @@ class MatchDetailsViewModel @Inject constructor(
val isLoading: Boolean = true,
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.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import xyz.leomurca.csgomatches.R
import xyz.leomurca.csgomatches.domain.model.Match
import xyz.leomurca.csgomatches.ui.components.ErrorMessage
import xyz.leomurca.csgomatches.ui.components.MatchCard
@ -63,7 +65,7 @@ fun MatchesScreen(
TopAppBar(
title = {
Text(
"Partidas",
stringResource(R.string.matches),
style = MaterialTheme.typography.titleLarge,
color = White,
modifier = Modifier.padding(top = 24.dp, start = 6.dp)

View file

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

View file

@ -1,3 +1,13 @@
<resources>
<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>