Merge branch 'dev'
This commit is contained in:
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ class TomatoApplication : Application() {
|
||||
|
||||
val notificationChannel = NotificationChannel(
|
||||
"timer",
|
||||
"Timer progress",
|
||||
getString(R.string.timer_progress),
|
||||
NotificationManager.IMPORTANCE_HIGH
|
||||
)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<String, String> = 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
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -1,3 +1,59 @@
|
||||
<resources>
|
||||
<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>
|
||||
10
fastlane/metadata/android/en-US/changelogs/10.txt
Normal file
10
fastlane/metadata/android/en-US/changelogs/10.txt
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user