chore: Extract strings into resources for upcoming ability to localise through Weblate

This commit is contained in:
Nishant Mishra
2025-09-28 10:22:46 +05:30
parent 039a540ad9
commit ef502892cc
17 changed files with 197 additions and 101 deletions

View File

@@ -74,19 +74,19 @@ class MainActivity : ComponentActivity() {
Screen.Timer, Screen.Timer,
R.drawable.timer_outlined, R.drawable.timer_outlined,
R.drawable.timer_filled, R.drawable.timer_filled,
"Timer" R.string.timer
), ),
NavItem( NavItem(
Screen.Stats, Screen.Stats,
R.drawable.monitoring, R.drawable.monitoring,
R.drawable.monitoring_filled, R.drawable.monitoring_filled,
"Stats" R.string.stats
), ),
NavItem( NavItem(
Screen.Settings, Screen.Settings,
R.drawable.settings, R.drawable.settings,
R.drawable.settings_filled, R.drawable.settings_filled,
"Settings" R.string.settings
) )
) )
} }

View File

@@ -14,7 +14,7 @@ class TomatoApplication : Application() {
val notificationChannel = NotificationChannel( val notificationChannel = NotificationChannel(
"timer", "timer",
"Timer progress", getString(R.string.timer_progress),
NotificationManager.IMPORTANCE_HIGH NotificationManager.IMPORTANCE_HIGH
) )

View File

@@ -57,7 +57,7 @@ class DefaultAppContainer(context: Context) : AppContainer {
PendingIntent.FLAG_IMMUTABLE PendingIntent.FLAG_IMMUTABLE
) )
) )
.addTimerActions(context, R.drawable.play, "Start") .addTimerActions(context, R.drawable.play, context.getString(R.string.start))
.setShowWhen(true) .setShowWhen(true)
.setSilent(true) .setSilent(true)
.setOngoing(true) .setOngoing(true)

View File

@@ -34,7 +34,7 @@ fun NotificationCompat.Builder.addTimerActions(
) )
.addAction( .addAction(
R.drawable.restart, R.drawable.restart,
"Exit", context.getString(R.string.exit),
PendingIntent.getService( PendingIntent.getService(
context, context,
0, 0,
@@ -46,7 +46,7 @@ fun NotificationCompat.Builder.addTimerActions(
) )
.addAction( .addAction(
R.drawable.skip_next, R.drawable.skip_next,
"Skip", context.getString(R.string.skip),
PendingIntent.getService( PendingIntent.getService(
context, context,
0, 0,
@@ -62,7 +62,7 @@ fun NotificationCompat.Builder.addStopAlarmAction(
): NotificationCompat.Builder = this ): NotificationCompat.Builder = this
.addAction( .addAction(
R.drawable.alarm, R.drawable.alarm,
"Stop alarm", context.getString(R.string.stop_alarm),
PendingIntent.getService( PendingIntent.getService(
context, context,
0, 0,

View File

@@ -119,7 +119,7 @@ class TimerService : Service() {
if (timerState.value.timerRunning) { if (timerState.value.timerRunning) {
notificationBuilder.clearActions().addTimerActions( notificationBuilder.clearActions().addTimerActions(
this, R.drawable.play, "Start" this, R.drawable.play, getString(R.string.start)
) )
showTimerNotification(time.toInt(), paused = true) showTimerNotification(time.toInt(), paused = true)
_timerState.update { currentState -> _timerState.update { currentState ->
@@ -128,7 +128,7 @@ class TimerService : Service() {
pauseTime = SystemClock.elapsedRealtime() pauseTime = SystemClock.elapsedRealtime()
} else { } else {
notificationBuilder.clearActions().addTimerActions( notificationBuilder.clearActions().addTimerActions(
this, R.drawable.pause, "Stop" this, R.drawable.pause, getString(R.string.stop)
) )
_timerState.update { it.copy(timerRunning = true) } _timerState.update { it.copy(timerRunning = true) }
if (pauseTime != 0L) pauseDuration += SystemClock.elapsedRealtime() - pauseTime if (pauseTime != 0L) pauseDuration += SystemClock.elapsedRealtime() - pauseTime
@@ -186,15 +186,15 @@ class TimerService : Service() {
} }
val currentTimer = when (timerState.value.timerMode) { val currentTimer = when (timerState.value.timerMode) {
TimerMode.FOCUS -> "Focus" TimerMode.FOCUS -> getString(R.string.focus)
TimerMode.SHORT_BREAK -> "Short break" TimerMode.SHORT_BREAK -> getString(R.string.short_break)
else -> "Long break" else -> getString(R.string.long_break)
} }
val nextTimer = when (timerState.value.nextTimerMode) { val nextTimer = when (timerState.value.nextTimerMode) {
TimerMode.FOCUS -> "Focus" TimerMode.FOCUS -> getString(R.string.focus)
TimerMode.SHORT_BREAK -> "Short break" TimerMode.SHORT_BREAK -> getString(R.string.short_break)
else -> "Long break" else -> getString(R.string.long_break)
} }
val remainingTimeString = if ((remainingTime.toFloat() / 60000f) < 1.0f) "< 1" val remainingTimeString = if ((remainingTime.toFloat() / 60000f) < 1.0f) "< 1"
@@ -205,10 +205,18 @@ class TimerService : Service() {
notificationBuilder notificationBuilder
.setContentTitle( .setContentTitle(
if (!complete) { if (!complete) {
"$currentTimer $middleDot $remainingTimeString min remaining" + if (paused) " $middleDot Paused" else "" "$currentTimer $middleDot ${
} else "$currentTimer $middleDot Completed" getString(R.string.min_remaining_notification, remainingTimeString)
}" + if (paused) " $middleDot ${getString(R.string.paused)}" else ""
} else "$currentTimer $middleDot ${getString(R.string.completed)}"
)
.setContentText(
getString(
R.string.up_next_notification,
nextTimer,
timerState.value.nextTimeStr
)
) )
.setContentText("Up next: $nextTimer (${timerState.value.nextTimeStr})")
.setStyle( .setStyle(
notificationStyle notificationStyle
.setProgress( // Set the current progress by filling the previous intervals and part of the current interval .setProgress( // Set the current progress by filling the previous intervals and part of the current interval
@@ -364,7 +372,10 @@ class TimerService : Service() {
_timerState.update { currentState -> _timerState.update { currentState ->
currentState.copy(alarmRinging = false) currentState.copy(alarmRinging = false)
} }
notificationBuilder.clearActions().addTimerActions(this, R.drawable.play, "Start next") notificationBuilder.clearActions().addTimerActions(
this, R.drawable.play,
getString(R.string.start_next)
)
showTimerNotification( showTimerNotification(
when (timerState.value.timerMode) { when (timerState.value.timerMode) {
TimerMode.FOCUS -> timerRepository.focusTime.toInt() TimerMode.FOCUS -> timerRepository.focusTime.toInt()

View File

@@ -35,6 +35,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation3.runtime.entryProvider import androidx.navigation3.runtime.entryProvider
@@ -110,7 +111,7 @@ fun AppScreen(
iconPosition = iconPosition =
if (wide) NavigationItemIconPosition.Start if (wide) NavigationItemIconPosition.Start
else NavigationItemIconPosition.Top, else NavigationItemIconPosition.Top,
label = { Text(it.label) } label = { Text(stringResource(it.label)) }
) )
} }
} }

View File

@@ -1,23 +1,24 @@
package org.nsh07.pomodoro.ui package org.nsh07.pomodoro.ui
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.navigation3.runtime.NavKey import androidx.navigation3.runtime.NavKey
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
sealed class Screen: NavKey { sealed class Screen : NavKey {
@Serializable @Serializable
object Timer : Screen() object Timer : Screen()
@Serializable @Serializable
object Settings : Screen() object Settings : Screen()
@Serializable @Serializable
object Stats : Screen() object Stats : Screen()
} }
data class NavItem( data class NavItem(
val route: Screen, val route: Screen,
@param:DrawableRes @param:DrawableRes val unselectedIcon: Int,
val unselectedIcon: Int, @param:DrawableRes val selectedIcon: Int,
@param:DrawableRes @param:StringRes val label: Int
val selectedIcon: Int,
val label: String
) )

View File

@@ -32,6 +32,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach import androidx.compose.ui.util.fastForEach
@@ -100,7 +101,7 @@ fun ColorSchemePickerDialog(
) { ) {
Column(modifier = Modifier.padding(24.dp)) { Column(modifier = Modifier.padding(24.dp)) {
Text( Text(
text = "Choose color scheme", text = stringResource(R.string.choose_color_scheme),
style = MaterialTheme.typography.headlineSmall style = MaterialTheme.typography.headlineSmall
) )
@@ -136,7 +137,7 @@ fun ColorSchemePickerDialog(
onClick = { setShowDialog(false) }, onClick = { setShowDialog(false) },
modifier = Modifier.align(Alignment.End) modifier = Modifier.align(Alignment.End)
) { ) {
Text("Ok") Text(stringResource(R.string.ok))
} }
} }
} }

View File

@@ -19,6 +19,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import org.nsh07.pomodoro.R import org.nsh07.pomodoro.R
import org.nsh07.pomodoro.ui.ClickableListItem import org.nsh07.pomodoro.ui.ClickableListItem
import org.nsh07.pomodoro.ui.theme.CustomColors.listItemColors import org.nsh07.pomodoro.ui.theme.CustomColors.listItemColors
@@ -49,11 +50,11 @@ fun ColorSchemePickerListItem(
tint = colorScheme.primary tint = colorScheme.primary
) )
}, },
headlineContent = { Text("Color scheme") }, headlineContent = { Text(stringResource(R.string.color_scheme)) },
supportingContent = { supportingContent = {
Text( Text(
if (color == Color.White) "Dynamic" if (color == Color.White) stringResource(R.string.dynamic)
else "Color" else stringResource(R.string.color)
) )
}, },
colors = listItemColors, colors = listItemColors,

View File

@@ -68,6 +68,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext 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.input.ImeAction import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.tooling.preview.Devices import androidx.compose.ui.tooling.preview.Devices
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
@@ -174,6 +175,7 @@ private fun SettingsScreen(
onColorSchemeChange: (Color) -> Unit, onColorSchemeChange: (Color) -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val context = LocalContext.current
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
val switchColors = SwitchDefaults.colors( val switchColors = SwitchDefaults.colors(
checkedIconColor = colorScheme.primary, checkedIconColor = colorScheme.primary,
@@ -183,21 +185,20 @@ private fun SettingsScreen(
mapOf( mapOf(
"auto" to Pair( "auto" to Pair(
R.drawable.brightness_auto, R.drawable.brightness_auto,
"System default" context.getString(R.string.system_default)
), ),
"light" to Pair(R.drawable.light_mode, "Light"), "light" to Pair(R.drawable.light_mode, context.getString(R.string.light)),
"dark" to Pair(R.drawable.dark_mode, "Dark") "dark" to Pair(R.drawable.dark_mode, context.getString(R.string.dark))
) )
} }
val reverseThemeMap: Map<String, String> = remember { val reverseThemeMap: Map<String, String> = remember {
mapOf( mapOf(
"System default" to "auto", context.getString(R.string.system_default) to "auto",
"Light" to "light", context.getString(R.string.light) to "light",
"Dark" to "dark" context.getString(R.string.dark) to "dark"
) )
} }
val context = LocalContext.current
var alarmName by remember { mutableStateOf("") } var alarmName by remember { mutableStateOf("") }
LaunchedEffect(alarmSound) { LaunchedEffect(alarmSound) {
@@ -233,7 +234,7 @@ private fun SettingsScreen(
val intent = Intent(RingtoneManager.ACTION_RINGTONE_PICKER).apply { val intent = Intent(RingtoneManager.ACTION_RINGTONE_PICKER).apply {
putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_ALARM) putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_ALARM)
putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE, "Alarm sound") putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE, stringResource(R.string.alarm_sound))
putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, alarmSound.toUri()) putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, alarmSound.toUri())
} }
@@ -242,22 +243,22 @@ private fun SettingsScreen(
SettingsSwitchItem( SettingsSwitchItem(
checked = preferencesState.blackTheme, checked = preferencesState.blackTheme,
icon = R.drawable.contrast, icon = R.drawable.contrast,
label = "Black theme", label = context.getString(R.string.black_theme),
description = "Use a pure black dark theme", description = context.getString(R.string.black_theme_desc),
onClick = onBlackThemeChange onClick = onBlackThemeChange
), ),
SettingsSwitchItem( SettingsSwitchItem(
checked = alarmEnabled, checked = alarmEnabled,
icon = R.drawable.alarm_on, icon = R.drawable.alarm_on,
label = "Alarm", label = context.getString(R.string.alarm),
description = "Ring alarm when a timer completes", description = context.getString(R.string.alarm_desc),
onClick = onAlarmEnabledChange onClick = onAlarmEnabledChange
), ),
SettingsSwitchItem( SettingsSwitchItem(
checked = vibrateEnabled, checked = vibrateEnabled,
icon = R.drawable.mobile_vibrate, icon = R.drawable.mobile_vibrate,
label = "Vibrate", label = context.getString(R.string.vibrate),
description = "Vibrate when a timer completes", description = context.getString(R.string.vibrate_desc),
onClick = onVibrateEnabledChange onClick = onVibrateEnabledChange
) )
) )
@@ -267,7 +268,7 @@ private fun SettingsScreen(
TopAppBar( TopAppBar(
title = { title = {
Text( Text(
"Settings", stringResource(R.string.settings),
style = LocalTextStyle.current.copy( style = LocalTextStyle.current.copy(
fontFamily = robotoFlexTopBar, fontFamily = robotoFlexTopBar,
fontSize = 32.sp, fontSize = 32.sp,
@@ -303,7 +304,7 @@ private fun SettingsScreen(
verticalArrangement = Arrangement.spacedBy(2.dp) verticalArrangement = Arrangement.spacedBy(2.dp)
) { ) {
Text( Text(
"Focus", stringResource(R.string.focus),
style = typography.titleSmallEmphasized style = typography.titleSmallEmphasized
) )
MinuteInputField( MinuteInputField(
@@ -323,7 +324,7 @@ private fun SettingsScreen(
verticalArrangement = Arrangement.spacedBy(2.dp) verticalArrangement = Arrangement.spacedBy(2.dp)
) { ) {
Text( Text(
"Short break", stringResource(R.string.short_break),
style = typography.titleSmallEmphasized style = typography.titleSmallEmphasized
) )
MinuteInputField( MinuteInputField(
@@ -338,7 +339,7 @@ private fun SettingsScreen(
verticalArrangement = Arrangement.spacedBy(2.dp) verticalArrangement = Arrangement.spacedBy(2.dp)
) { ) {
Text( Text(
"Long break", stringResource(R.string.long_break),
style = typography.titleSmallEmphasized style = typography.titleSmallEmphasized
) )
MinuteInputField( MinuteInputField(
@@ -366,11 +367,16 @@ private fun SettingsScreen(
) )
}, },
headlineContent = { headlineContent = {
Text("Session length") Text(stringResource(R.string.session_length))
}, },
supportingContent = { supportingContent = {
Column { Column {
Text("Focus intervals in one session: ${sessionsSliderState.value.toInt()}") Text(
stringResource(
R.string.session_length_desc,
sessionsSliderState.value.toInt()
)
)
Slider( Slider(
state = sessionsSliderState, state = sessionsSliderState,
modifier = Modifier.padding(vertical = 4.dp) modifier = Modifier.padding(vertical = 4.dp)
@@ -446,7 +452,7 @@ private fun SettingsScreen(
leadingContent = { leadingContent = {
Icon(painterResource(R.drawable.alarm), null) Icon(painterResource(R.drawable.alarm), null)
}, },
headlineContent = { Text("Alarm sound") }, headlineContent = { Text(stringResource(R.string.alarm_sound)) },
supportingContent = { Text(alarmName) }, supportingContent = { Text(alarmName) },
colors = listItemColors, colors = listItemColors,
modifier = Modifier modifier = Modifier
@@ -514,9 +520,7 @@ private fun SettingsScreen(
} }
AnimatedVisibility(expanded) { AnimatedVisibility(expanded) {
Text( Text(
"A \"session\" is a sequence of pomodoro intervals that contain focus" + stringResource(R.string.pomodoro_info),
" intervals, short break intervals, and a long break interval. The " +
"last break of a session is always a long break.",
style = typography.bodyMedium, style = typography.bodyMedium,
color = colorScheme.onSurfaceVariant, color = colorScheme.onSurfaceVariant,
modifier = Modifier.padding(8.dp) modifier = Modifier.padding(8.dp)
@@ -558,7 +562,7 @@ fun SettingsScreenPreview() {
data class SettingsSwitchItem( data class SettingsSwitchItem(
val checked: Boolean, val checked: Boolean,
@DrawableRes val icon: Int, @param:DrawableRes val icon: Int,
val label: String, val label: String,
val description: String, val description: String,
val onClick: (Boolean) -> Unit val onClick: (Boolean) -> Unit

View File

@@ -37,6 +37,7 @@ 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.draw.clip
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.Role
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import org.nsh07.pomodoro.R import org.nsh07.pomodoro.R
@@ -71,7 +72,7 @@ fun ThemeDialog(
) { ) {
Column(modifier = Modifier.padding(24.dp)) { Column(modifier = Modifier.padding(24.dp)) {
Text( Text(
text = "Choose theme", text = stringResource(R.string.choose_theme),
style = MaterialTheme.typography.headlineSmall style = MaterialTheme.typography.headlineSmall
) )
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
@@ -122,7 +123,7 @@ fun ThemeDialog(
onClick = { setShowThemeDialog(false) }, onClick = { setShowThemeDialog(false) },
modifier = Modifier.align(Alignment.End) modifier = Modifier.align(Alignment.End)
) { ) {
Text("Ok") Text(stringResource(R.string.ok))
} }
} }
} }

View File

@@ -17,6 +17,8 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import org.nsh07.pomodoro.R
import org.nsh07.pomodoro.ui.ClickableListItem import org.nsh07.pomodoro.ui.ClickableListItem
import org.nsh07.pomodoro.ui.theme.CustomColors.listItemColors import org.nsh07.pomodoro.ui.theme.CustomColors.listItemColors
@@ -49,7 +51,7 @@ fun ThemePickerListItem(
contentDescription = null contentDescription = null
) )
}, },
headlineContent = { Text("Theme") }, headlineContent = { Text(stringResource(R.string.theme)) },
supportingContent = { supportingContent = {
Text(themeMap[theme]!!.second) Text(themeMap[theme]!!.second)
}, },

View File

@@ -16,9 +16,11 @@ import androidx.compose.material3.MaterialTheme.typography
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModelProducer import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModelProducer
import com.patrykandpatrick.vico.core.cartesian.data.CartesianValueFormatter import com.patrykandpatrick.vico.core.cartesian.data.CartesianValueFormatter
import org.nsh07.pomodoro.R
import org.nsh07.pomodoro.utils.millisecondsToHoursMinutes import org.nsh07.pomodoro.utils.millisecondsToHoursMinutes
@Composable @Composable
@@ -26,12 +28,15 @@ fun ColumnScope.ProductivityGraph(
expanded: Boolean, expanded: Boolean,
modelProducer: CartesianChartModelProducer, modelProducer: CartesianChartModelProducer,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
label: String = "Productivity analysis" label: String = stringResource(R.string.productivity_analysis)
) { ) {
AnimatedVisibility(expanded) { AnimatedVisibility(expanded) {
Column(modifier = modifier) { Column(modifier = modifier) {
Text(label, style = typography.titleMedium) Text(label, style = typography.titleMedium)
Text("Focus durations at different times of the day", style = typography.bodySmall) Text(
stringResource(R.string.productivity_analysis_desc),
style = typography.bodySmall
)
Spacer(Modifier.height(8.dp)) Spacer(Modifier.height(8.dp))
TimeColumnChart( TimeColumnChart(
modelProducer, modelProducer,

View File

@@ -45,6 +45,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate import androidx.compose.ui.draw.rotate
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Devices import androidx.compose.ui.tooling.preview.Devices
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@@ -114,7 +115,7 @@ fun StatsScreen(
TopAppBar( TopAppBar(
title = { title = {
Text( Text(
"Stats", stringResource(R.string.stats),
style = LocalTextStyle.current.copy( style = LocalTextStyle.current.copy(
fontFamily = robotoFlexTopBar, fontFamily = robotoFlexTopBar,
fontSize = 32.sp, fontSize = 32.sp,
@@ -138,7 +139,7 @@ fun StatsScreen(
item { Spacer(Modifier) } item { Spacer(Modifier) }
item { item {
Text( Text(
"Today", stringResource(R.string.today),
style = typography.headlineSmall, style = typography.headlineSmall,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -157,7 +158,7 @@ fun StatsScreen(
) { ) {
Column(Modifier.padding(16.dp)) { Column(Modifier.padding(16.dp)) {
Text( Text(
"Focus", stringResource(R.string.focus),
style = typography.titleMedium, style = typography.titleMedium,
color = colorScheme.onPrimaryContainer color = colorScheme.onPrimaryContainer
) )
@@ -182,7 +183,7 @@ fun StatsScreen(
) { ) {
Column(Modifier.padding(16.dp)) { Column(Modifier.padding(16.dp)) {
Text( Text(
"Break", stringResource(R.string.break_),
style = typography.titleMedium, style = typography.titleMedium,
color = colorScheme.onTertiaryContainer color = colorScheme.onTertiaryContainer
) )
@@ -201,7 +202,7 @@ fun StatsScreen(
item { Spacer(Modifier) } item { Spacer(Modifier) }
item { item {
Text( Text(
"Last week", stringResource(R.string.last_week),
style = typography.headlineSmall, style = typography.headlineSmall,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -222,7 +223,7 @@ fun StatsScreen(
fontFamily = openRundeClock fontFamily = openRundeClock
) )
Text( Text(
"focus per day (avg)", stringResource(R.string.focus_per_day_avg),
style = typography.titleSmall, style = typography.titleSmall,
modifier = Modifier.padding(bottom = 6.3.dp) modifier = Modifier.padding(bottom = 6.3.dp)
) )
@@ -258,14 +259,14 @@ fun StatsScreen(
) { ) {
Icon( Icon(
painterResource(R.drawable.arrow_down), painterResource(R.drawable.arrow_down),
"More info", stringResource(R.string.more_info),
modifier = Modifier.rotate(iconRotation) modifier = Modifier.rotate(iconRotation)
) )
} }
ProductivityGraph( ProductivityGraph(
lastWeekStatExpanded, lastWeekStatExpanded,
lastWeekSummaryAnalysisModelProducer, lastWeekSummaryAnalysisModelProducer,
label = "Weekly productivity analysis", label = stringResource(R.string.weekly_productivity_analysis),
modifier = Modifier.padding(horizontal = 32.dp) modifier = Modifier.padding(horizontal = 32.dp)
) )
} }
@@ -273,7 +274,7 @@ fun StatsScreen(
item { Spacer(Modifier) } item { Spacer(Modifier) }
item { item {
Text( Text(
"Last month", stringResource(R.string.last_month),
style = typography.headlineSmall, style = typography.headlineSmall,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -294,7 +295,7 @@ fun StatsScreen(
fontFamily = openRundeClock fontFamily = openRundeClock
) )
Text( Text(
"focus per day (avg)", text = stringResource(R.string.focus_per_day_avg),
style = typography.titleSmall, style = typography.titleSmall,
modifier = Modifier.padding(bottom = 6.3.dp) modifier = Modifier.padding(bottom = 6.3.dp)
) )
@@ -331,14 +332,14 @@ fun StatsScreen(
) { ) {
Icon( Icon(
painterResource(R.drawable.arrow_down), painterResource(R.drawable.arrow_down),
"More info", stringResource(R.string.more_info),
modifier = Modifier.rotate(iconRotation) modifier = Modifier.rotate(iconRotation)
) )
} }
ProductivityGraph( ProductivityGraph(
lastMonthStatExpanded, lastMonthStatExpanded,
lastMonthSummaryAnalysisModelProducer, lastMonthSummaryAnalysisModelProducer,
label = "Monthly productivity analysis", label = stringResource(R.string.monthly_productivity_analysis),
modifier = Modifier.padding(horizontal = 32.dp) modifier = Modifier.padding(horizontal = 32.dp)
) )
} }

View File

@@ -16,17 +16,18 @@ import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.material3.AlertDialogDefaults import androidx.compose.material3.AlertDialogDefaults
import androidx.compose.material3.BasicAlertDialog import androidx.compose.material3.BasicAlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MaterialTheme.typography import androidx.compose.material3.MaterialTheme.typography
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
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.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import org.nsh07.pomodoro.R import org.nsh07.pomodoro.R
@@ -51,25 +52,25 @@ fun AlarmDialog(
Column(modifier = Modifier.padding(24.dp)) { Column(modifier = Modifier.padding(24.dp)) {
Icon( Icon(
painter = painterResource(R.drawable.alarm), painter = painterResource(R.drawable.alarm),
contentDescription = "Alarm", contentDescription = stringResource(R.string.alarm),
modifier = Modifier.align(Alignment.CenterHorizontally) modifier = Modifier.align(Alignment.CenterHorizontally)
) )
Spacer(Modifier.height(16.dp)) Spacer(Modifier.height(16.dp))
Text( Text(
text = "Stop Alarm?", text = stringResource(R.string.stop_alarm_question),
style = typography.headlineSmall, style = typography.headlineSmall,
modifier = Modifier.align(Alignment.CenterHorizontally) modifier = Modifier.align(Alignment.CenterHorizontally)
) )
Spacer(Modifier.height(16.dp)) Spacer(Modifier.height(16.dp))
Text( Text(
text = "Current timer session is complete. Tap anywhere to stop the alarm." text = stringResource(R.string.stop_alarm_dialog_text)
) )
Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.height(24.dp))
Button( TextButton(
onClick = stopAlarm, onClick = stopAlarm,
modifier = Modifier.align(Alignment.End), modifier = Modifier.align(Alignment.End),
) { ) {
Text("Dismiss") Text(stringResource(R.string.stop_alarm))
} }
} }
} }

View File

@@ -67,6 +67,7 @@ import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
@@ -134,7 +135,7 @@ fun TimerScreen(
when (it) { when (it) {
TimerMode.BRAND -> TimerMode.BRAND ->
Text( Text(
"Tomato", stringResource(R.string.app_name),
style = TextStyle( style = TextStyle(
fontFamily = robotoFlexTopBar, fontFamily = robotoFlexTopBar,
fontSize = 32.sp, fontSize = 32.sp,
@@ -147,7 +148,7 @@ fun TimerScreen(
TimerMode.FOCUS -> TimerMode.FOCUS ->
Text( Text(
"Focus", stringResource(R.string.focus),
style = TextStyle( style = TextStyle(
fontFamily = robotoFlexTopBar, fontFamily = robotoFlexTopBar,
fontSize = 32.sp, fontSize = 32.sp,
@@ -159,7 +160,7 @@ fun TimerScreen(
) )
TimerMode.SHORT_BREAK -> Text( TimerMode.SHORT_BREAK -> Text(
"Short break", stringResource(R.string.short_break),
style = TextStyle( style = TextStyle(
fontFamily = robotoFlexTopBar, fontFamily = robotoFlexTopBar,
fontSize = 32.sp, fontSize = 32.sp,
@@ -171,7 +172,7 @@ fun TimerScreen(
) )
TimerMode.LONG_BREAK -> Text( TimerMode.LONG_BREAK -> Text(
"Long Break", stringResource(R.string.long_break),
style = TextStyle( style = TextStyle(
fontFamily = robotoFlexTopBar, fontFamily = robotoFlexTopBar,
fontSize = 32.sp, fontSize = 32.sp,
@@ -261,7 +262,11 @@ fun TimerScreen(
shrinkVertically(motionScheme.defaultSpatialSpec()) shrinkVertically(motionScheme.defaultSpatialSpec())
) { ) {
Text( Text(
"${timerState.currentFocusCount} of ${timerState.totalFocusCount}", stringResource(
R.string.timer_session_count,
timerState.currentFocusCount,
timerState.totalFocusCount
),
fontFamily = openRundeClock, fontFamily = openRundeClock,
style = typography.titleLarge, style = typography.titleLarge,
color = colorScheme.outline color = colorScheme.outline
@@ -289,7 +294,7 @@ fun TimerScreen(
) { ) {
Icon( Icon(
painterResource(R.drawable.more_vert_large), painterResource(R.drawable.more_vert_large),
contentDescription = "More", contentDescription = stringResource(R.string.more),
modifier = Modifier.size(32.dp) modifier = Modifier.size(32.dp)
) )
} }
@@ -323,13 +328,13 @@ fun TimerScreen(
if (timerState.timerRunning) { if (timerState.timerRunning) {
Icon( Icon(
painterResource(R.drawable.pause_large), painterResource(R.drawable.pause_large),
contentDescription = "Pause", contentDescription = stringResource(R.string.pause),
modifier = Modifier.size(32.dp) modifier = Modifier.size(32.dp)
) )
} else { } else {
Icon( Icon(
painterResource(R.drawable.play_large), painterResource(R.drawable.play_large),
contentDescription = "Play", contentDescription = stringResource(R.string.play),
modifier = Modifier.size(32.dp) modifier = Modifier.size(32.dp)
) )
} }
@@ -341,16 +346,22 @@ fun TimerScreen(
if (timerState.timerRunning) { if (timerState.timerRunning) {
Icon( Icon(
painterResource(R.drawable.pause), painterResource(R.drawable.pause),
contentDescription = "Pause" contentDescription = stringResource(R.string.pause)
) )
} else { } else {
Icon( Icon(
painterResource(R.drawable.play), painterResource(R.drawable.play),
contentDescription = "Play" contentDescription = stringResource(R.string.play)
) )
} }
}, },
text = { Text(if (timerState.timerRunning) "Pause" else "Play") }, text = {
Text(
if (timerState.timerRunning) stringResource(R.string.pause) else stringResource(
R.string.play
)
)
},
onClick = { onClick = {
onAction(TimerAction.ToggleTimer) onAction(TimerAction.ToggleTimer)
state.dismiss() state.dismiss()
@@ -377,7 +388,7 @@ fun TimerScreen(
) { ) {
Icon( Icon(
painterResource(R.drawable.restart_large), painterResource(R.drawable.restart_large),
contentDescription = "Restart", contentDescription = stringResource(R.string.restart),
modifier = Modifier.size(32.dp) modifier = Modifier.size(32.dp)
) )
} }
@@ -387,10 +398,10 @@ fun TimerScreen(
leadingIcon = { leadingIcon = {
Icon( Icon(
painterResource(R.drawable.restart), painterResource(R.drawable.restart),
"Restart" stringResource(R.string.restart)
) )
}, },
text = { Text("Restart") }, text = { Text(stringResource(R.string.restart)) },
onClick = { onClick = {
onAction(TimerAction.ResetTimer) onAction(TimerAction.ResetTimer)
state.dismiss() state.dismiss()
@@ -417,7 +428,7 @@ fun TimerScreen(
) { ) {
Icon( Icon(
painterResource(R.drawable.skip_next_large), painterResource(R.drawable.skip_next_large),
contentDescription = "Skip to next", contentDescription = stringResource(R.string.skip_to_next),
modifier = Modifier.size(32.dp) modifier = Modifier.size(32.dp)
) )
} }
@@ -427,10 +438,10 @@ fun TimerScreen(
leadingIcon = { leadingIcon = {
Icon( Icon(
painterResource(R.drawable.skip_next), painterResource(R.drawable.skip_next),
"Skip to next" stringResource(R.string.skip_to_next)
) )
}, },
text = { Text("Skip to next") }, text = { Text(stringResource(R.string.skip_to_next)) },
onClick = { onClick = {
onAction(TimerAction.SkipTimer(fromButton = true)) onAction(TimerAction.SkipTimer(fromButton = true))
state.dismiss() state.dismiss()
@@ -444,7 +455,7 @@ fun TimerScreen(
Spacer(Modifier.height(32.dp)) Spacer(Modifier.height(32.dp))
Column(horizontalAlignment = CenterHorizontally) { Column(horizontalAlignment = CenterHorizontally) {
Text("Up next", style = typography.titleSmall) Text(stringResource(R.string.up_next), style = typography.titleSmall)
Text( Text(
timerState.nextTimeStr, timerState.nextTimeStr,
style = TextStyle( style = TextStyle(
@@ -457,9 +468,9 @@ fun TimerScreen(
) )
Text( Text(
when (timerState.nextTimerMode) { when (timerState.nextTimerMode) {
TimerMode.FOCUS -> "Focus" TimerMode.FOCUS -> stringResource(R.string.focus)
TimerMode.SHORT_BREAK -> "Short break" TimerMode.SHORT_BREAK -> stringResource(R.string.short_break)
else -> "Long Break" else -> stringResource(R.string.long_break)
}, },
style = typography.titleMediumEmphasized style = typography.titleMediumEmphasized
) )

View File

@@ -1,3 +1,59 @@
<resources> <resources>
<string name="app_name">Tomato</string> <string name="app_name">Tomato</string>
<string name="start">Start</string>
<string name="stop">Stop</string>
<string name="focus">Focus</string>
<string name="short_break">Short break</string>
<string name="long_break">Long break</string>
<string name="exit">Exit</string>
<string name="skip">Skip</string>
<string name="stop_alarm">Stop alarm</string>
<string name="min_remaining_notification">%1$s min remaining</string>
<string name="paused">Paused</string>
<string name="completed">Completed</string>
<string name="up_next_notification">Up next: %1$s (%2$s)</string>
<string name="start_next">Start next</string>
<string name="choose_color_scheme">Choose color scheme</string>
<string name="ok">OK</string>
<string name="color_scheme">Color scheme</string>
<string name="dynamic">Dynamic</string>
<string name="color">Color</string>
<string name="system_default">System default</string>
<string name="alarm">Alarm</string>
<string name="light">Light</string>
<string name="dark">Dark</string>
<string name="choose_theme">Choose theme</string>
<string name="productivity_analysis">Productivity analysis</string>
<string name="productivity_analysis_desc">Focus durations at different times of the day</string>
<string name="alarm_sound">Alarm sound</string>
<string name="black_theme">Black theme</string>
<string name="black_theme_desc">Use a pure black dark theme</string>
<string name="alarm_desc">Ring alarm when a timer completes</string>
<string name="vibrate">Vibrate</string>
<string name="vibrate_desc">Vibrate when a timer completes</string>
<string name="theme">Theme</string>
<string name="settings">Settings</string>
<string name="session_length">Session length</string>
<string name="session_length_desc">Focus intervals in one session: %1$d</string>
<string name="pomodoro_info">A \"session\" is a sequence of pomodoro intervals that contain focus intervals, short break intervals, and a long break interval. The last break of a session is always a long break.</string>
<string name="stats">Stats</string>
<string name="today">Today</string>
<string name="break_">Break</string>
<string name="last_week">Last week</string>
<string name="focus_per_day_avg">focus per day (avg)</string>
<string name="more_info">More info</string>
<string name="weekly_productivity_analysis">Weekly productivity analysis</string>
<string name="last_month">Last month</string>
<string name="monthly_productivity_analysis">Monthly productivity analysis</string>
<string name="stop_alarm_question">Stop Alarm?</string>
<string name="stop_alarm_dialog_text">Current timer session is complete. Tap anywhere to stop the alarm.</string>
<string name="timer_session_count">%1$d of %2$d</string>
<string name="more">More</string>
<string name="pause">Pause</string>
<string name="play">Play</string>
<string name="restart">Restart</string>
<string name="skip_to_next">Skip to next</string>
<string name="up_next">Up next</string>
<string name="timer">Timer</string>
<string name="timer_progress">Timer progress</string>
</resources> </resources>