diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 06934fb..9e46fb8 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -33,8 +33,8 @@ android { applicationId = "org.nsh07.pomodoro" minSdk = 26 targetSdk = 36 - versionCode = 9 - versionName = "1.4.1" + versionCode = 10 + versionName = "1.4.2" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } diff --git a/app/src/main/java/org/nsh07/pomodoro/MainActivity.kt b/app/src/main/java/org/nsh07/pomodoro/MainActivity.kt index dfdf04c..baf7924 100644 --- a/app/src/main/java/org/nsh07/pomodoro/MainActivity.kt +++ b/app/src/main/java/org/nsh07/pomodoro/MainActivity.kt @@ -74,19 +74,19 @@ class MainActivity : ComponentActivity() { Screen.Timer, R.drawable.timer_outlined, R.drawable.timer_filled, - "Timer" + R.string.timer ), NavItem( Screen.Stats, R.drawable.monitoring, R.drawable.monitoring_filled, - "Stats" + R.string.stats ), NavItem( Screen.Settings, R.drawable.settings, R.drawable.settings_filled, - "Settings" + R.string.settings ) ) } diff --git a/app/src/main/java/org/nsh07/pomodoro/TomatoApplication.kt b/app/src/main/java/org/nsh07/pomodoro/TomatoApplication.kt index e1902bc..7b0da79 100644 --- a/app/src/main/java/org/nsh07/pomodoro/TomatoApplication.kt +++ b/app/src/main/java/org/nsh07/pomodoro/TomatoApplication.kt @@ -14,7 +14,7 @@ class TomatoApplication : Application() { val notificationChannel = NotificationChannel( "timer", - "Timer progress", + getString(R.string.timer_progress), NotificationManager.IMPORTANCE_HIGH ) diff --git a/app/src/main/java/org/nsh07/pomodoro/data/AppContainer.kt b/app/src/main/java/org/nsh07/pomodoro/data/AppContainer.kt index 2bde857..a681968 100644 --- a/app/src/main/java/org/nsh07/pomodoro/data/AppContainer.kt +++ b/app/src/main/java/org/nsh07/pomodoro/data/AppContainer.kt @@ -57,7 +57,7 @@ class DefaultAppContainer(context: Context) : AppContainer { PendingIntent.FLAG_IMMUTABLE ) ) - .addTimerActions(context, R.drawable.play, "Start") + .addTimerActions(context, R.drawable.play, context.getString(R.string.start)) .setShowWhen(true) .setSilent(true) .setOngoing(true) diff --git a/app/src/main/java/org/nsh07/pomodoro/service/AddActions.kt b/app/src/main/java/org/nsh07/pomodoro/service/AddActions.kt index b410a75..9ff5f36 100644 --- a/app/src/main/java/org/nsh07/pomodoro/service/AddActions.kt +++ b/app/src/main/java/org/nsh07/pomodoro/service/AddActions.kt @@ -34,7 +34,7 @@ fun NotificationCompat.Builder.addTimerActions( ) .addAction( R.drawable.restart, - "Exit", + context.getString(R.string.exit), PendingIntent.getService( context, 0, @@ -46,7 +46,7 @@ fun NotificationCompat.Builder.addTimerActions( ) .addAction( R.drawable.skip_next, - "Skip", + context.getString(R.string.skip), PendingIntent.getService( context, 0, @@ -62,7 +62,7 @@ fun NotificationCompat.Builder.addStopAlarmAction( ): NotificationCompat.Builder = this .addAction( R.drawable.alarm, - "Stop alarm", + context.getString(R.string.stop_alarm), PendingIntent.getService( context, 0, diff --git a/app/src/main/java/org/nsh07/pomodoro/service/TimerService.kt b/app/src/main/java/org/nsh07/pomodoro/service/TimerService.kt index 2828a34..d6a69b0 100644 --- a/app/src/main/java/org/nsh07/pomodoro/service/TimerService.kt +++ b/app/src/main/java/org/nsh07/pomodoro/service/TimerService.kt @@ -119,7 +119,7 @@ class TimerService : Service() { if (timerState.value.timerRunning) { notificationBuilder.clearActions().addTimerActions( - this, R.drawable.play, "Start" + this, R.drawable.play, getString(R.string.start) ) showTimerNotification(time.toInt(), paused = true) _timerState.update { currentState -> @@ -128,7 +128,7 @@ class TimerService : Service() { pauseTime = SystemClock.elapsedRealtime() } else { notificationBuilder.clearActions().addTimerActions( - this, R.drawable.pause, "Stop" + this, R.drawable.pause, getString(R.string.stop) ) _timerState.update { it.copy(timerRunning = true) } if (pauseTime != 0L) pauseDuration += SystemClock.elapsedRealtime() - pauseTime @@ -186,15 +186,15 @@ class TimerService : Service() { } val currentTimer = when (timerState.value.timerMode) { - TimerMode.FOCUS -> "Focus" - TimerMode.SHORT_BREAK -> "Short break" - else -> "Long break" + TimerMode.FOCUS -> getString(R.string.focus) + TimerMode.SHORT_BREAK -> getString(R.string.short_break) + else -> getString(R.string.long_break) } val nextTimer = when (timerState.value.nextTimerMode) { - TimerMode.FOCUS -> "Focus" - TimerMode.SHORT_BREAK -> "Short break" - else -> "Long break" + TimerMode.FOCUS -> getString(R.string.focus) + TimerMode.SHORT_BREAK -> getString(R.string.short_break) + else -> getString(R.string.long_break) } val remainingTimeString = if ((remainingTime.toFloat() / 60000f) < 1.0f) "< 1" @@ -205,10 +205,18 @@ class TimerService : Service() { notificationBuilder .setContentTitle( if (!complete) { - "$currentTimer $middleDot $remainingTimeString min remaining" + if (paused) " $middleDot Paused" else "" - } else "$currentTimer $middleDot Completed" + "$currentTimer $middleDot ${ + 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( notificationStyle .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 -> 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( when (timerState.value.timerMode) { TimerMode.FOCUS -> timerRepository.focusTime.toInt() diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/AppScreen.kt b/app/src/main/java/org/nsh07/pomodoro/ui/AppScreen.kt index d3125e5..8e84a98 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/AppScreen.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/AppScreen.kt @@ -35,6 +35,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation3.runtime.entryProvider @@ -110,7 +111,7 @@ fun AppScreen( iconPosition = if (wide) NavigationItemIconPosition.Start else NavigationItemIconPosition.Top, - label = { Text(it.label) } + label = { Text(stringResource(it.label)) } ) } } diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/Screen.kt b/app/src/main/java/org/nsh07/pomodoro/ui/Screen.kt index 5f359c3..85482cf 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/Screen.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/Screen.kt @@ -1,23 +1,24 @@ package org.nsh07.pomodoro.ui import androidx.annotation.DrawableRes +import androidx.annotation.StringRes import androidx.navigation3.runtime.NavKey import kotlinx.serialization.Serializable -sealed class Screen: NavKey { +sealed class Screen : NavKey { @Serializable object Timer : Screen() + @Serializable object Settings : Screen() + @Serializable object Stats : Screen() } data class NavItem( val route: Screen, - @param:DrawableRes - val unselectedIcon: Int, - @param:DrawableRes - val selectedIcon: Int, - val label: String + @param:DrawableRes val unselectedIcon: Int, + @param:DrawableRes val selectedIcon: Int, + @param:StringRes val label: Int ) \ No newline at end of file diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/ColorSchemePickerDialog.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/ColorSchemePickerDialog.kt index d963ee5..519f8c7 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/ColorSchemePickerDialog.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/ColorSchemePickerDialog.kt @@ -32,6 +32,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastForEach @@ -100,7 +101,7 @@ fun ColorSchemePickerDialog( ) { Column(modifier = Modifier.padding(24.dp)) { Text( - text = "Choose color scheme", + text = stringResource(R.string.choose_color_scheme), style = MaterialTheme.typography.headlineSmall ) @@ -136,7 +137,7 @@ fun ColorSchemePickerDialog( onClick = { setShowDialog(false) }, modifier = Modifier.align(Alignment.End) ) { - Text("Ok") + Text(stringResource(R.string.ok)) } } } diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/ColorSchemePickerListItem.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/ColorSchemePickerListItem.kt index bc09f9e..088d5be 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/ColorSchemePickerListItem.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/ColorSchemePickerListItem.kt @@ -19,6 +19,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color 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.theme.CustomColors.listItemColors @@ -49,11 +50,11 @@ fun ColorSchemePickerListItem( tint = colorScheme.primary ) }, - headlineContent = { Text("Color scheme") }, + headlineContent = { Text(stringResource(R.string.color_scheme)) }, supportingContent = { Text( - if (color == Color.White) "Dynamic" - else "Color" + if (color == Color.White) stringResource(R.string.dynamic) + else stringResource(R.string.color) ) }, colors = listItemColors, diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt index 3e9b0cc..67b00a0 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt @@ -12,7 +12,6 @@ import android.content.Intent import android.media.RingtoneManager import android.net.Uri import android.os.Build -import android.provider.MediaStore import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.DrawableRes @@ -68,15 +67,17 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.tooling.preview.Devices import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.compose.ui.util.fastCoerceAtLeast import androidx.core.net.toUri import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import org.nsh07.pomodoro.R import org.nsh07.pomodoro.service.TimerService import org.nsh07.pomodoro.ui.settingsScreen.viewModel.PreferencesState @@ -174,6 +175,7 @@ private fun SettingsScreen( onColorSchemeChange: (Color) -> Unit, modifier: Modifier = Modifier ) { + val context = LocalContext.current val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() val switchColors = SwitchDefaults.colors( checkedIconColor = colorScheme.primary, @@ -183,34 +185,27 @@ private fun SettingsScreen( mapOf( "auto" to Pair( R.drawable.brightness_auto, - "System default" + context.getString(R.string.system_default) ), - "light" to Pair(R.drawable.light_mode, "Light"), - "dark" to Pair(R.drawable.dark_mode, "Dark") + "light" to Pair(R.drawable.light_mode, context.getString(R.string.light)), + "dark" to Pair(R.drawable.dark_mode, context.getString(R.string.dark)) ) } val reverseThemeMap: Map = remember { mapOf( - "System default" to "auto", - "Light" to "light", - "Dark" to "dark" + context.getString(R.string.system_default) to "auto", + context.getString(R.string.light) to "light", + context.getString(R.string.dark) to "dark" ) } - val context = LocalContext.current - var alarmName by remember { mutableStateOf("") } + var alarmName by remember { mutableStateOf("...") } LaunchedEffect(alarmSound) { - val returnCursor = context.contentResolver.query(alarmSound.toUri(), null, null, null, null) - returnCursor?.moveToFirst() - alarmName = - returnCursor - ?.getString( - returnCursor - .getColumnIndex(MediaStore.MediaColumns.TITLE) - .fastCoerceAtLeast(0) - ) ?: "" - returnCursor?.close() + withContext(Dispatchers.IO) { + alarmName = + RingtoneManager.getRingtone(context, alarmSound.toUri())?.getTitle(context) ?: "" + } } val ringtonePickerLauncher = rememberLauncherForActivityResult( @@ -233,7 +228,7 @@ private fun SettingsScreen( val intent = Intent(RingtoneManager.ACTION_RINGTONE_PICKER).apply { 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()) } @@ -242,22 +237,22 @@ private fun SettingsScreen( SettingsSwitchItem( checked = preferencesState.blackTheme, icon = R.drawable.contrast, - label = "Black theme", - description = "Use a pure black dark theme", + label = context.getString(R.string.black_theme), + description = context.getString(R.string.black_theme_desc), onClick = onBlackThemeChange ), SettingsSwitchItem( checked = alarmEnabled, icon = R.drawable.alarm_on, - label = "Alarm", - description = "Ring alarm when a timer completes", + label = context.getString(R.string.alarm), + description = context.getString(R.string.alarm_desc), onClick = onAlarmEnabledChange ), SettingsSwitchItem( checked = vibrateEnabled, icon = R.drawable.mobile_vibrate, - label = "Vibrate", - description = "Vibrate when a timer completes", + label = context.getString(R.string.vibrate), + description = context.getString(R.string.vibrate_desc), onClick = onVibrateEnabledChange ) ) @@ -267,7 +262,7 @@ private fun SettingsScreen( TopAppBar( title = { Text( - "Settings", + stringResource(R.string.settings), style = LocalTextStyle.current.copy( fontFamily = robotoFlexTopBar, fontSize = 32.sp, @@ -303,7 +298,7 @@ private fun SettingsScreen( verticalArrangement = Arrangement.spacedBy(2.dp) ) { Text( - "Focus", + stringResource(R.string.focus), style = typography.titleSmallEmphasized ) MinuteInputField( @@ -323,7 +318,7 @@ private fun SettingsScreen( verticalArrangement = Arrangement.spacedBy(2.dp) ) { Text( - "Short break", + stringResource(R.string.short_break), style = typography.titleSmallEmphasized ) MinuteInputField( @@ -338,7 +333,7 @@ private fun SettingsScreen( verticalArrangement = Arrangement.spacedBy(2.dp) ) { Text( - "Long break", + stringResource(R.string.long_break), style = typography.titleSmallEmphasized ) MinuteInputField( @@ -366,11 +361,16 @@ private fun SettingsScreen( ) }, headlineContent = { - Text("Session length") + Text(stringResource(R.string.session_length)) }, supportingContent = { Column { - Text("Focus intervals in one session: ${sessionsSliderState.value.toInt()}") + Text( + stringResource( + R.string.session_length_desc, + sessionsSliderState.value.toInt() + ) + ) Slider( state = sessionsSliderState, modifier = Modifier.padding(vertical = 4.dp) @@ -446,7 +446,7 @@ private fun SettingsScreen( leadingContent = { Icon(painterResource(R.drawable.alarm), null) }, - headlineContent = { Text("Alarm sound") }, + headlineContent = { Text(stringResource(R.string.alarm_sound)) }, supportingContent = { Text(alarmName) }, colors = listItemColors, modifier = Modifier @@ -514,9 +514,7 @@ private fun SettingsScreen( } AnimatedVisibility(expanded) { Text( - "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.", + stringResource(R.string.pomodoro_info), style = typography.bodyMedium, color = colorScheme.onSurfaceVariant, modifier = Modifier.padding(8.dp) @@ -558,7 +556,7 @@ fun SettingsScreenPreview() { data class SettingsSwitchItem( val checked: Boolean, - @DrawableRes val icon: Int, + @param:DrawableRes val icon: Int, val label: String, val description: String, val onClick: (Boolean) -> Unit diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/ThemeDialog.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/ThemeDialog.kt index 32a131e..67f4d7a 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/ThemeDialog.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/ThemeDialog.kt @@ -37,6 +37,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.unit.dp import org.nsh07.pomodoro.R @@ -71,7 +72,7 @@ fun ThemeDialog( ) { Column(modifier = Modifier.padding(24.dp)) { Text( - text = "Choose theme", + text = stringResource(R.string.choose_theme), style = MaterialTheme.typography.headlineSmall ) Spacer(modifier = Modifier.height(16.dp)) @@ -122,7 +123,7 @@ fun ThemeDialog( onClick = { setShowThemeDialog(false) }, modifier = Modifier.align(Alignment.End) ) { - Text("Ok") + Text(stringResource(R.string.ok)) } } } diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/ThemePickerListItem.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/ThemePickerListItem.kt index 9fda303..2bee9c5 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/ThemePickerListItem.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/ThemePickerListItem.kt @@ -17,6 +17,8 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier 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.theme.CustomColors.listItemColors @@ -49,7 +51,7 @@ fun ThemePickerListItem( contentDescription = null ) }, - headlineContent = { Text("Theme") }, + headlineContent = { Text(stringResource(R.string.theme)) }, supportingContent = { Text(themeMap[theme]!!.second) }, diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/ProductivityGraph.kt b/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/ProductivityGraph.kt index f10402f..37cecba 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/ProductivityGraph.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/ProductivityGraph.kt @@ -16,9 +16,11 @@ import androidx.compose.material3.MaterialTheme.typography import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModelProducer import com.patrykandpatrick.vico.core.cartesian.data.CartesianValueFormatter +import org.nsh07.pomodoro.R import org.nsh07.pomodoro.utils.millisecondsToHoursMinutes @Composable @@ -26,12 +28,15 @@ fun ColumnScope.ProductivityGraph( expanded: Boolean, modelProducer: CartesianChartModelProducer, modifier: Modifier = Modifier, - label: String = "Productivity analysis" + label: String = stringResource(R.string.productivity_analysis) ) { AnimatedVisibility(expanded) { Column(modifier = modifier) { 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)) TimeColumnChart( modelProducer, diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/StatsScreen.kt b/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/StatsScreen.kt index 44eec0c..e40a0b2 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/StatsScreen.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/StatsScreen.kt @@ -45,6 +45,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.rotate import androidx.compose.ui.input.nestedscroll.nestedScroll 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.Preview import androidx.compose.ui.unit.dp @@ -114,7 +115,7 @@ fun StatsScreen( TopAppBar( title = { Text( - "Stats", + stringResource(R.string.stats), style = LocalTextStyle.current.copy( fontFamily = robotoFlexTopBar, fontSize = 32.sp, @@ -138,7 +139,7 @@ fun StatsScreen( item { Spacer(Modifier) } item { Text( - "Today", + stringResource(R.string.today), style = typography.headlineSmall, modifier = Modifier .fillMaxWidth() @@ -157,7 +158,7 @@ fun StatsScreen( ) { Column(Modifier.padding(16.dp)) { Text( - "Focus", + stringResource(R.string.focus), style = typography.titleMedium, color = colorScheme.onPrimaryContainer ) @@ -182,7 +183,7 @@ fun StatsScreen( ) { Column(Modifier.padding(16.dp)) { Text( - "Break", + stringResource(R.string.break_), style = typography.titleMedium, color = colorScheme.onTertiaryContainer ) @@ -201,7 +202,7 @@ fun StatsScreen( item { Spacer(Modifier) } item { Text( - "Last week", + stringResource(R.string.last_week), style = typography.headlineSmall, modifier = Modifier .fillMaxWidth() @@ -222,7 +223,7 @@ fun StatsScreen( fontFamily = openRundeClock ) Text( - "focus per day (avg)", + stringResource(R.string.focus_per_day_avg), style = typography.titleSmall, modifier = Modifier.padding(bottom = 6.3.dp) ) @@ -258,14 +259,14 @@ fun StatsScreen( ) { Icon( painterResource(R.drawable.arrow_down), - "More info", + stringResource(R.string.more_info), modifier = Modifier.rotate(iconRotation) ) } ProductivityGraph( lastWeekStatExpanded, lastWeekSummaryAnalysisModelProducer, - label = "Weekly productivity analysis", + label = stringResource(R.string.weekly_productivity_analysis), modifier = Modifier.padding(horizontal = 32.dp) ) } @@ -273,7 +274,7 @@ fun StatsScreen( item { Spacer(Modifier) } item { Text( - "Last month", + stringResource(R.string.last_month), style = typography.headlineSmall, modifier = Modifier .fillMaxWidth() @@ -294,7 +295,7 @@ fun StatsScreen( fontFamily = openRundeClock ) Text( - "focus per day (avg)", + text = stringResource(R.string.focus_per_day_avg), style = typography.titleSmall, modifier = Modifier.padding(bottom = 6.3.dp) ) @@ -331,14 +332,14 @@ fun StatsScreen( ) { Icon( painterResource(R.drawable.arrow_down), - "More info", + stringResource(R.string.more_info), modifier = Modifier.rotate(iconRotation) ) } ProductivityGraph( lastMonthStatExpanded, lastMonthSummaryAnalysisModelProducer, - label = "Monthly productivity analysis", + label = stringResource(R.string.monthly_productivity_analysis), modifier = Modifier.padding(horizontal = 32.dp) ) } diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/AlarmDialog.kt b/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/AlarmDialog.kt index 6d67506..9b27779 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/AlarmDialog.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/AlarmDialog.kt @@ -16,17 +16,18 @@ import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.material3.AlertDialogDefaults import androidx.compose.material3.BasicAlertDialog -import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme.typography import androidx.compose.material3.Surface import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import org.nsh07.pomodoro.R @@ -51,25 +52,25 @@ fun AlarmDialog( Column(modifier = Modifier.padding(24.dp)) { Icon( painter = painterResource(R.drawable.alarm), - contentDescription = "Alarm", + contentDescription = stringResource(R.string.alarm), modifier = Modifier.align(Alignment.CenterHorizontally) ) Spacer(Modifier.height(16.dp)) Text( - text = "Stop Alarm?", + text = stringResource(R.string.stop_alarm_question), style = typography.headlineSmall, modifier = Modifier.align(Alignment.CenterHorizontally) ) Spacer(Modifier.height(16.dp)) 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)) - Button( + TextButton( onClick = stopAlarm, modifier = Modifier.align(Alignment.End), ) { - Text("Dismiss") + Text(stringResource(R.string.stop_alarm)) } } } diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/TimerScreen.kt b/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/TimerScreen.kt index e373346..d6d448c 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/TimerScreen.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/TimerScreen.kt @@ -67,6 +67,7 @@ import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign @@ -134,7 +135,7 @@ fun TimerScreen( when (it) { TimerMode.BRAND -> Text( - "Tomato", + stringResource(R.string.app_name), style = TextStyle( fontFamily = robotoFlexTopBar, fontSize = 32.sp, @@ -147,7 +148,7 @@ fun TimerScreen( TimerMode.FOCUS -> Text( - "Focus", + stringResource(R.string.focus), style = TextStyle( fontFamily = robotoFlexTopBar, fontSize = 32.sp, @@ -159,7 +160,7 @@ fun TimerScreen( ) TimerMode.SHORT_BREAK -> Text( - "Short break", + stringResource(R.string.short_break), style = TextStyle( fontFamily = robotoFlexTopBar, fontSize = 32.sp, @@ -171,7 +172,7 @@ fun TimerScreen( ) TimerMode.LONG_BREAK -> Text( - "Long Break", + stringResource(R.string.long_break), style = TextStyle( fontFamily = robotoFlexTopBar, fontSize = 32.sp, @@ -261,7 +262,11 @@ fun TimerScreen( shrinkVertically(motionScheme.defaultSpatialSpec()) ) { Text( - "${timerState.currentFocusCount} of ${timerState.totalFocusCount}", + stringResource( + R.string.timer_session_count, + timerState.currentFocusCount, + timerState.totalFocusCount + ), fontFamily = openRundeClock, style = typography.titleLarge, color = colorScheme.outline @@ -289,7 +294,7 @@ fun TimerScreen( ) { Icon( painterResource(R.drawable.more_vert_large), - contentDescription = "More", + contentDescription = stringResource(R.string.more), modifier = Modifier.size(32.dp) ) } @@ -323,13 +328,13 @@ fun TimerScreen( if (timerState.timerRunning) { Icon( painterResource(R.drawable.pause_large), - contentDescription = "Pause", + contentDescription = stringResource(R.string.pause), modifier = Modifier.size(32.dp) ) } else { Icon( painterResource(R.drawable.play_large), - contentDescription = "Play", + contentDescription = stringResource(R.string.play), modifier = Modifier.size(32.dp) ) } @@ -341,16 +346,22 @@ fun TimerScreen( if (timerState.timerRunning) { Icon( painterResource(R.drawable.pause), - contentDescription = "Pause" + contentDescription = stringResource(R.string.pause) ) } else { Icon( 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 = { onAction(TimerAction.ToggleTimer) state.dismiss() @@ -377,7 +388,7 @@ fun TimerScreen( ) { Icon( painterResource(R.drawable.restart_large), - contentDescription = "Restart", + contentDescription = stringResource(R.string.restart), modifier = Modifier.size(32.dp) ) } @@ -387,10 +398,10 @@ fun TimerScreen( leadingIcon = { Icon( painterResource(R.drawable.restart), - "Restart" + stringResource(R.string.restart) ) }, - text = { Text("Restart") }, + text = { Text(stringResource(R.string.restart)) }, onClick = { onAction(TimerAction.ResetTimer) state.dismiss() @@ -417,7 +428,7 @@ fun TimerScreen( ) { Icon( painterResource(R.drawable.skip_next_large), - contentDescription = "Skip to next", + contentDescription = stringResource(R.string.skip_to_next), modifier = Modifier.size(32.dp) ) } @@ -427,10 +438,10 @@ fun TimerScreen( leadingIcon = { Icon( 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 = { onAction(TimerAction.SkipTimer(fromButton = true)) state.dismiss() @@ -444,7 +455,7 @@ fun TimerScreen( Spacer(Modifier.height(32.dp)) Column(horizontalAlignment = CenterHorizontally) { - Text("Up next", style = typography.titleSmall) + Text(stringResource(R.string.up_next), style = typography.titleSmall) Text( timerState.nextTimeStr, style = TextStyle( @@ -457,9 +468,9 @@ fun TimerScreen( ) Text( when (timerState.nextTimerMode) { - TimerMode.FOCUS -> "Focus" - TimerMode.SHORT_BREAK -> "Short break" - else -> "Long Break" + TimerMode.FOCUS -> stringResource(R.string.focus) + TimerMode.SHORT_BREAK -> stringResource(R.string.short_break) + else -> stringResource(R.string.long_break) }, style = typography.titleMediumEmphasized ) diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/viewModel/TimerViewModel.kt b/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/viewModel/TimerViewModel.kt index e01a1d5..2202d42 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/viewModel/TimerViewModel.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/viewModel/TimerViewModel.kt @@ -117,11 +117,14 @@ class TimerViewModel( val today = LocalDate.now() // Fills dates between today and lastDate with 0s to ensure continuous history - if (lastDate != null) + if (lastDate != null) { while (ChronoUnit.DAYS.between(lastDate, today) > 0) { lastDate = lastDate?.plusDays(1) statRepository.insertStat(Stat(lastDate!!, 0, 0, 0, 0, 0)) } + } else { + statRepository.insertStat(Stat(today, 0, 0, 0, 0, 0)) + } delay(1500) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2bb470b..0d55c21 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,3 +1,59 @@ Tomato + Start + Stop + Focus + Short break + Long break + Exit + Skip + Stop alarm + %1$s min remaining + Paused + Completed + Up next: %1$s (%2$s) + Start next + Choose color scheme + OK + Color scheme + Dynamic + Color + System default + Alarm + Light + Dark + Choose theme + Productivity analysis + Focus durations at different times of the day + Alarm sound + Black theme + Use a pure black dark theme + Ring alarm when a timer completes + Vibrate + Vibrate when a timer completes + Theme + Settings + Session length + Focus intervals in one session: %1$d + 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. + Stats + Today + Break + Last week + focus per day (avg) + More info + Weekly productivity analysis + Last month + Monthly productivity analysis + Stop Alarm? + Current timer session is complete. Tap anywhere to stop the alarm. + %1$d of %2$d + More + Pause + Play + Restart + Skip to next + Up next + Timer + Timer progress \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/changelogs/10.txt b/fastlane/metadata/android/en-US/changelogs/10.txt new file mode 100644 index 0000000..850bf66 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/10.txt @@ -0,0 +1,10 @@ +This release contains bug fixes on top of the existing new features of 1.4.0: + +New features: +- You can now choose a custom theme and color scheme for the app's UI +- New pure black dark theme mode + +Fixes: +- Average focus durations now do not include days with no activity +- Fix a critical bug that caused the app's timer state to reset to Focus whenever the app was closed from recents and then opened +- Replace the word "Reset" with "Exit" in the notification to make its purpose less ambiguous \ No newline at end of file