diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 77e1df8..1475dce 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 = 11 - versionName = "1.4.3" + versionCode = 12 + versionName = "1.5.0-alpha01" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } diff --git a/app/src/main/java/org/nsh07/pomodoro/TomatoApplication.kt b/app/src/main/java/org/nsh07/pomodoro/TomatoApplication.kt index 7b0da79..87be588 100644 --- a/app/src/main/java/org/nsh07/pomodoro/TomatoApplication.kt +++ b/app/src/main/java/org/nsh07/pomodoro/TomatoApplication.kt @@ -15,7 +15,7 @@ class TomatoApplication : Application() { val notificationChannel = NotificationChannel( "timer", getString(R.string.timer_progress), - NotificationManager.IMPORTANCE_HIGH + NotificationManager.IMPORTANCE_DEFAULT ) container.notificationManager.createNotificationChannel(notificationChannel) 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 8e84a98..59390fd 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/AppScreen.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/AppScreen.kt @@ -68,7 +68,7 @@ fun AppScreen( val motionScheme = motionScheme val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass - val backStack = rememberNavBackStack(Screen.Timer) + val backStack = rememberNavBackStack(Screen.Timer) if (uiState.alarmRinging) AlarmDialog { 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 e40a0b2..4a261e4 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 @@ -21,6 +21,7 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.text.TextAutoSize import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.FilledTonalIconToggleButton @@ -31,10 +32,12 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.motionScheme import androidx.compose.material3.MaterialTheme.typography +import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -46,7 +49,6 @@ 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 import androidx.compose.ui.unit.sp @@ -55,14 +57,14 @@ import androidx.lifecycle.viewmodel.compose.viewModel import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModelProducer import com.patrykandpatrick.vico.core.cartesian.data.CartesianValueFormatter import com.patrykandpatrick.vico.core.cartesian.data.columnSeries +import com.patrykandpatrick.vico.core.cartesian.data.lineSeries import com.patrykandpatrick.vico.core.common.data.ExtraStore -import kotlinx.coroutines.runBlocking import org.nsh07.pomodoro.R import org.nsh07.pomodoro.data.Stat -import org.nsh07.pomodoro.data.StatFocusTime import org.nsh07.pomodoro.ui.statsScreen.viewModel.StatsViewModel import org.nsh07.pomodoro.ui.theme.AppFonts.openRundeClock import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTopBar +import org.nsh07.pomodoro.ui.theme.TomatoTheme import org.nsh07.pomodoro.utils.millisecondsToHoursMinutes @Composable @@ -72,20 +74,25 @@ fun StatsScreenRoot( viewModel: StatsViewModel = viewModel(factory = StatsViewModel.Factory) ) { val todayStat by viewModel.todayStat.collectAsStateWithLifecycle(null) - val lastWeekAverageFocusTimes by viewModel - .lastWeekAverageFocusTimes.collectAsStateWithLifecycle(null) - val lastMonthAverageFocusTimes by viewModel - .lastMonthAverageFocusTimes.collectAsStateWithLifecycle(null) + + val lastWeekSummaryChartData by viewModel.lastWeekSummaryChartData.collectAsStateWithLifecycle() + val lastWeekAnalysisValues by viewModel.lastWeekAverageFocusTimes.collectAsStateWithLifecycle() + + val lastMonthSummaryChartData by viewModel.lastMonthSummaryChartData.collectAsStateWithLifecycle() + val lastMonthAnalysisValues by viewModel.lastMonthAverageFocusTimes.collectAsStateWithLifecycle() + + val lastYearSummaryChartData by viewModel.lastYearSummaryChartData.collectAsStateWithLifecycle() + val lastYearAnalysisValues by viewModel.lastYearAverageFocusTimes.collectAsStateWithLifecycle() StatsScreen( contentPadding = contentPadding, - lastWeekSummaryChartData = remember { viewModel.lastWeekSummaryChartData }, - lastWeekSummaryAnalysisModelProducer = remember { viewModel.lastWeekSummaryAnalysisModelProducer }, - lastMonthSummaryChartData = remember { viewModel.lastMonthSummaryChartData }, - lastMonthSummaryAnalysisModelProducer = remember { viewModel.lastMonthSummaryAnalysisModelProducer }, + lastWeekSummaryChartData = lastWeekSummaryChartData, + lastMonthSummaryChartData = lastMonthSummaryChartData, + lastYearSummaryChartData = lastYearSummaryChartData, todayStat = todayStat, - lastWeekAverageFocusTimes = lastWeekAverageFocusTimes, - lastMonthAverageFocusTimes = lastMonthAverageFocusTimes, + lastWeekAverageFocusTimes = lastWeekAnalysisValues, + lastMonthAverageFocusTimes = lastMonthAnalysisValues, + lastYearAverageFocusTimes = lastYearAnalysisValues, modifier = modifier ) } @@ -95,12 +102,12 @@ fun StatsScreenRoot( fun StatsScreen( contentPadding: PaddingValues, lastWeekSummaryChartData: Pair>>, - lastWeekSummaryAnalysisModelProducer: CartesianChartModelProducer, lastMonthSummaryChartData: Pair>>, - lastMonthSummaryAnalysisModelProducer: CartesianChartModelProducer, + lastYearSummaryChartData: Pair>>, todayStat: Stat?, - lastWeekAverageFocusTimes: StatFocusTime?, - lastMonthAverageFocusTimes: StatFocusTime?, + lastWeekAverageFocusTimes: List, + lastMonthAverageFocusTimes: List, + lastYearAverageFocusTimes: List, modifier: Modifier = Modifier ) { val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() @@ -108,6 +115,25 @@ fun StatsScreen( var lastWeekStatExpanded by rememberSaveable { mutableStateOf(false) } var lastMonthStatExpanded by rememberSaveable { mutableStateOf(false) } + val lastWeekSummaryAnalysisModelProducer = remember { CartesianChartModelProducer() } + val lastMonthSummaryAnalysisModelProducer = remember { CartesianChartModelProducer() } + + LaunchedEffect(lastWeekAverageFocusTimes) { + lastWeekSummaryAnalysisModelProducer.runTransaction { + columnSeries { + series(lastWeekAverageFocusTimes) + } + } + } + + LaunchedEffect(lastMonthAverageFocusTimes) { + lastMonthSummaryAnalysisModelProducer.runTransaction { + columnSeries { + series(lastMonthAverageFocusTimes) + } + } + } + Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection) @@ -168,7 +194,9 @@ fun StatsScreen( }, style = typography.displaySmall, fontFamily = openRundeClock, - color = colorScheme.onPrimaryContainer + color = colorScheme.onPrimaryContainer, + maxLines = 1, + autoSize = TextAutoSize.StepBased(maxFontSize = typography.displaySmall.fontSize) ) } } @@ -193,7 +221,9 @@ fun StatsScreen( }, style = typography.displaySmall, fontFamily = openRundeClock, - color = colorScheme.onTertiaryContainer + color = colorScheme.onTertiaryContainer, + maxLines = 1, + autoSize = TextAutoSize.StepBased(maxFontSize = typography.displaySmall.fontSize) ) } } @@ -218,7 +248,11 @@ fun StatsScreen( .padding(horizontal = 16.dp) ) { Text( - millisecondsToHoursMinutes(lastWeekAverageFocusTimes?.total() ?: 0), + millisecondsToHoursMinutes( + remember(lastWeekAverageFocusTimes) { + lastWeekAverageFocusTimes.sum().toLong() + } + ), style = typography.displaySmall, fontFamily = openRundeClock ) @@ -290,7 +324,11 @@ fun StatsScreen( .padding(horizontal = 16.dp) ) { Text( - millisecondsToHoursMinutes(lastMonthAverageFocusTimes?.total() ?: 0), + millisecondsToHoursMinutes( + remember(lastMonthAverageFocusTimes) { + lastMonthAverageFocusTimes.sum().toLong() + } + ), style = typography.displaySmall, fontFamily = openRundeClock ) @@ -343,36 +381,85 @@ fun StatsScreen( modifier = Modifier.padding(horizontal = 32.dp) ) } - Spacer(Modifier.height(16.dp)) } + item { Spacer(Modifier) } + item { + Text( + stringResource(R.string.last_year), + style = typography.headlineSmall, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + ) + } + item { + Row( + verticalAlignment = Alignment.Bottom, + horizontalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + ) { + Text( + millisecondsToHoursMinutes( + remember(lastYearAverageFocusTimes) { + lastYearAverageFocusTimes.sum().toLong() + } + ), + style = typography.displaySmall, + fontFamily = openRundeClock + ) + Text( + text = stringResource(R.string.focus_per_day_avg), + style = typography.titleSmall, + modifier = Modifier.padding(bottom = 6.3.dp) + ) + } + } + item { + TimeLineChart( + lastYearSummaryChartData.first, + modifier = Modifier.padding(start = 16.dp), + xValueFormatter = CartesianValueFormatter { context, x, _ -> + context.model.extraStore[lastYearSummaryChartData.second][x.toInt()] + } + ) + } + item { Spacer(Modifier.height(16.dp)) } } } } @Preview( - showSystemUi = true, - device = Devices.PIXEL_9_PRO + widthDp = 400 ) @Composable fun StatsScreenPreview() { val modelProducer = remember { CartesianChartModelProducer() } + val keys = remember { ExtraStore.Key>() } - runBlocking { + LaunchedEffect(Unit) { modelProducer.runTransaction { columnSeries { - series(5, 6, 5, 2, 11, 8, 5, 2, 15, 11, 8, 13, 12, 10, 2, 7) + series(5, 6, 5, 2, 11, 8, 5) } + lineSeries {} + extras { it[keys] = listOf("M", "T", "W", "T", "F", "S", "S") } } } - StatsScreen( - PaddingValues(), - Pair(modelProducer, ExtraStore.Key()), - modelProducer, - Pair(modelProducer, ExtraStore.Key()), - modelProducer, - null, - null, - null - ) -} + TomatoTheme { + Surface { + StatsScreen( + PaddingValues(), + Pair(modelProducer, keys), + Pair(modelProducer, keys), + Pair(modelProducer, keys), + null, + listOf(0, 0, 0, 0), + listOf(0, 0, 0, 0), + listOf(0, 0, 0, 0) + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/TimeColumnChart.kt b/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/TimeColumnChart.kt index 497e553..ef5c563 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/TimeColumnChart.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/TimeColumnChart.kt @@ -10,8 +10,12 @@ package org.nsh07.pomodoro.ui.statsScreen import androidx.compose.animation.core.AnimationSpec import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.MaterialTheme.motionScheme +import androidx.compose.material3.Surface import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.patrykandpatrick.vico.compose.cartesian.CartesianChartHost @@ -25,20 +29,23 @@ import com.patrykandpatrick.vico.compose.common.component.rememberLineComponent import com.patrykandpatrick.vico.compose.common.fill import com.patrykandpatrick.vico.compose.common.vicoTheme import com.patrykandpatrick.vico.compose.m3.common.rememberM3VicoTheme +import com.patrykandpatrick.vico.core.cartesian.FadingEdges import com.patrykandpatrick.vico.core.cartesian.Zoom import com.patrykandpatrick.vico.core.cartesian.axis.HorizontalAxis import com.patrykandpatrick.vico.core.cartesian.axis.VerticalAxis import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModelProducer import com.patrykandpatrick.vico.core.cartesian.data.CartesianValueFormatter +import com.patrykandpatrick.vico.core.cartesian.data.columnSeries import com.patrykandpatrick.vico.core.cartesian.layer.ColumnCartesianLayer import com.patrykandpatrick.vico.core.common.Fill import com.patrykandpatrick.vico.core.common.shape.CorneredShape +import org.nsh07.pomodoro.ui.theme.TomatoTheme import org.nsh07.pomodoro.utils.millisecondsToHours import org.nsh07.pomodoro.utils.millisecondsToMinutes @OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable -internal fun TimeColumnChart( +fun TimeColumnChart( modelProducer: CartesianChartModelProducer, modifier: Modifier = Modifier, thickness: Dp = 40.dp, @@ -80,7 +87,8 @@ internal fun TimeColumnChart( tick = rememberLineComponent(Fill.Transparent), guideline = rememberLineComponent(Fill.Transparent), valueFormatter = xValueFormatter - ) + ), + fadingEdges = FadingEdges() ), modelProducer = modelProducer, zoomState = rememberVicoZoomState( @@ -92,4 +100,26 @@ internal fun TimeColumnChart( modifier = modifier, ) } -} \ No newline at end of file +} + +@Preview +@Composable +private fun TimeColumnChartPreview() { + val modelProducer = remember { CartesianChartModelProducer() } + val values = mutableListOf() + LaunchedEffect(Unit) { + repeat(30) { + values.add((0..120).random() * 60 * 1000) + } + modelProducer.runTransaction { + columnSeries { + series(values) + } + } + } + TomatoTheme { + Surface { + TimeColumnChart(thickness = 8.dp, modelProducer = modelProducer) + } + } +} diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/TimeLineChart.kt b/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/TimeLineChart.kt new file mode 100644 index 0000000..73485f9 --- /dev/null +++ b/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/TimeLineChart.kt @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2025 Nishant Mishra + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.nsh07.pomodoro.ui.statsScreen + +import androidx.compose.animation.core.AnimationSpec +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.MaterialTheme.motionScheme +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.patrykandpatrick.vico.compose.cartesian.CartesianChartHost +import com.patrykandpatrick.vico.compose.cartesian.axis.rememberBottom +import com.patrykandpatrick.vico.compose.cartesian.axis.rememberStart +import com.patrykandpatrick.vico.compose.cartesian.layer.rememberLine +import com.patrykandpatrick.vico.compose.cartesian.layer.rememberLineCartesianLayer +import com.patrykandpatrick.vico.compose.cartesian.rememberCartesianChart +import com.patrykandpatrick.vico.compose.cartesian.rememberVicoZoomState +import com.patrykandpatrick.vico.compose.common.ProvideVicoTheme +import com.patrykandpatrick.vico.compose.common.component.rememberLineComponent +import com.patrykandpatrick.vico.compose.common.fill +import com.patrykandpatrick.vico.compose.common.vicoTheme +import com.patrykandpatrick.vico.compose.m3.common.rememberM3VicoTheme +import com.patrykandpatrick.vico.core.cartesian.FadingEdges +import com.patrykandpatrick.vico.core.cartesian.Zoom +import com.patrykandpatrick.vico.core.cartesian.axis.HorizontalAxis +import com.patrykandpatrick.vico.core.cartesian.axis.VerticalAxis +import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModelProducer +import com.patrykandpatrick.vico.core.cartesian.data.CartesianValueFormatter +import com.patrykandpatrick.vico.core.cartesian.data.lineSeries +import com.patrykandpatrick.vico.core.cartesian.layer.LineCartesianLayer +import com.patrykandpatrick.vico.core.cartesian.layer.LineCartesianLayer.LineFill.Companion.single +import com.patrykandpatrick.vico.core.common.Fill +import com.patrykandpatrick.vico.core.common.shader.ShaderProvider +import org.nsh07.pomodoro.ui.theme.TomatoTheme +import org.nsh07.pomodoro.utils.millisecondsToHours +import org.nsh07.pomodoro.utils.millisecondsToMinutes + +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun TimeLineChart( + modelProducer: CartesianChartModelProducer, + modifier: Modifier = Modifier, + thickness: Float = 2f, + pointSpacing: Dp = 12.dp, + xValueFormatter: CartesianValueFormatter = CartesianValueFormatter.Default, + yValueFormatter: CartesianValueFormatter = CartesianValueFormatter { _, value, _ -> + if (value >= 60 * 60 * 1000) { + millisecondsToHours(value.toLong()) + } else { + millisecondsToMinutes(value.toLong()) + } + }, + animationSpec: AnimationSpec? = motionScheme.slowEffectsSpec() +) { + ProvideVicoTheme(rememberM3VicoTheme()) { + CartesianChartHost( + chart = + rememberCartesianChart( + rememberLineCartesianLayer( + LineCartesianLayer.LineProvider.series( + vicoTheme.lineCartesianLayerColors.map { color -> + LineCartesianLayer.rememberLine( + fill = single(fill(color)), + stroke = LineCartesianLayer.LineStroke.Continuous( + thicknessDp = thickness, + ), + areaFill = LineCartesianLayer.AreaFill.single( + fill( + ShaderProvider.verticalGradient( + color.toArgb(), + Color.Transparent.toArgb() + ) + ) + ), + pointConnector = LineCartesianLayer.PointConnector.cubic(0.5f) + ) + } + ), + pointSpacing = pointSpacing + ), + startAxis = VerticalAxis.rememberStart( + line = rememberLineComponent(Fill.Transparent), + tick = rememberLineComponent(Fill.Transparent), + guideline = rememberLineComponent(Fill.Transparent), + valueFormatter = yValueFormatter + ), + bottomAxis = HorizontalAxis.rememberBottom( + rememberLineComponent(Fill.Transparent), + tick = rememberLineComponent(Fill.Transparent), + guideline = rememberLineComponent(Fill.Transparent), + valueFormatter = xValueFormatter + ), + fadingEdges = FadingEdges() + ), + modelProducer = modelProducer, + zoomState = rememberVicoZoomState( + zoomEnabled = true, + initialZoom = Zoom.fixed(), + minZoom = Zoom.min(Zoom.Content, Zoom.fixed()) + ), + animationSpec = animationSpec, + modifier = modifier, + ) + } +} + +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Preview +@Composable +private fun TimeLineChartPreview() { + val modelProducer = remember { CartesianChartModelProducer() } + val values = mutableListOf() + LaunchedEffect(Unit) { + repeat(365) { + values.add((0..120).random() * 60 * 1000) + } + modelProducer.runTransaction { + lineSeries { + series(values) + } + } + } + TomatoTheme { + Surface { + TimeLineChart(modelProducer = modelProducer) + } + } +} diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/viewModel/StatsViewModel.kt b/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/viewModel/StatsViewModel.kt index 73f4f8e..2b907d5 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/viewModel/StatsViewModel.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/viewModel/StatsViewModel.kt @@ -15,12 +15,16 @@ import androidx.lifecycle.viewmodel.initializer import androidx.lifecycle.viewmodel.viewModelFactory import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModelProducer import com.patrykandpatrick.vico.core.cartesian.data.columnSeries +import com.patrykandpatrick.vico.core.cartesian.data.lineSeries import com.patrykandpatrick.vico.core.common.data.ExtraStore -import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.launch +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn import org.nsh07.pomodoro.TomatoApplication import org.nsh07.pomodoro.data.StatRepository +import java.time.format.DateTimeFormatter import java.time.format.TextStyle import java.util.Locale @@ -29,83 +33,123 @@ class StatsViewModel( ) : ViewModel() { val todayStat = statRepository.getTodayStat().distinctUntilChanged() - private val lastWeekStatsSummary = statRepository.getLastNDaysStatsSummary(7) - val lastWeekAverageFocusTimes = - statRepository.getLastNDaysAverageFocusTimes(7).distinctUntilChanged() - private val lastMonthStatsSummary = statRepository.getLastNDaysStatsSummary(30) - val lastMonthAverageFocusTimes = - statRepository.getLastNDaysAverageFocusTimes(30).distinctUntilChanged() - val lastWeekSummaryChartData = + private val lastWeekSummary = Pair(CartesianChartModelProducer(), ExtraStore.Key>()) - val lastWeekSummaryAnalysisModelProducer = CartesianChartModelProducer() - - val lastMonthSummaryChartData = + private val lastMonthSummary = + Pair(CartesianChartModelProducer(), ExtraStore.Key>()) + private val lastYearSummary = Pair(CartesianChartModelProducer(), ExtraStore.Key>()) - val lastMonthSummaryAnalysisModelProducer = CartesianChartModelProducer() - init { - viewModelScope.launch(Dispatchers.IO) { - lastWeekStatsSummary - .collect { list -> - // reversing is required because we need ascending order while the DB returns descending order - val reversed = list.reversed() - val keys = reversed.map { - it.date.dayOfWeek.getDisplayName( - TextStyle.NARROW, - Locale.getDefault() - ) - } - val values = reversed.map { it.focusTime } - lastWeekSummaryChartData.first.runTransaction { - columnSeries { series(values) } - extras { it[lastWeekSummaryChartData.second] = keys } - } + private val yearDayFormatter = DateTimeFormatter.ofPattern("d MMM") + + val lastWeekSummaryChartData: StateFlow>>> = + statRepository.getLastNDaysStatsSummary(7) + .map { list -> + // reversing is required because we need ascending order while the DB returns descending order + val reversed = list.reversed() + val keys = reversed.map { + it.date.dayOfWeek.getDisplayName( + TextStyle.NARROW, + Locale.getDefault() + ) } - } - viewModelScope.launch(Dispatchers.IO) { - lastWeekAverageFocusTimes - .collect { - lastWeekSummaryAnalysisModelProducer.runTransaction { - columnSeries { - series( - it?.focusTimeQ1 ?: 0, - it?.focusTimeQ2 ?: 0, - it?.focusTimeQ3 ?: 0, - it?.focusTimeQ4 ?: 0 - ) - } - } + val values = reversed.map { it.focusTime } + lastWeekSummary.first.runTransaction { + columnSeries { series(values) } + extras { it[lastWeekSummary.second] = keys } } - } - viewModelScope.launch(Dispatchers.IO) { - lastMonthStatsSummary - .collect { list -> - val reversed = list.reversed() - val keys = reversed.map { it.date.dayOfMonth.toString() } - val values = reversed.map { it.focusTime } - lastMonthSummaryChartData.first.runTransaction { - columnSeries { series(values) } - extras { it[lastMonthSummaryChartData.second] = keys } - } + lastWeekSummary + } + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5000), + initialValue = lastWeekSummary + ) + + val lastWeekAverageFocusTimes: StateFlow> = + statRepository.getLastNDaysAverageFocusTimes(7) + .map { + listOf( + it?.focusTimeQ1?.toInt() ?: 0, + it?.focusTimeQ2?.toInt() ?: 0, + it?.focusTimeQ3?.toInt() ?: 0, + it?.focusTimeQ4?.toInt() ?: 0 + ) + } + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5000), + initialValue = listOf(0, 0, 0, 0) + ) + + val lastMonthSummaryChartData: StateFlow>>> = + statRepository.getLastNDaysStatsSummary(30) + .map { list -> + val reversed = list.reversed() + val keys = reversed.map { it.date.dayOfMonth.toString() } + val values = reversed.map { it.focusTime } + lastMonthSummary.first.runTransaction { + columnSeries { series(values) } + extras { it[lastMonthSummary.second] = keys } } - } - viewModelScope.launch(Dispatchers.IO) { - lastMonthAverageFocusTimes - .collect { - lastMonthSummaryAnalysisModelProducer.runTransaction { - columnSeries { - series( - it?.focusTimeQ1 ?: 0, - it?.focusTimeQ2 ?: 0, - it?.focusTimeQ3 ?: 0, - it?.focusTimeQ4 ?: 0 - ) - } - } + lastMonthSummary + } + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5000), + initialValue = lastMonthSummary + ) + + val lastMonthAverageFocusTimes: StateFlow> = + statRepository.getLastNDaysAverageFocusTimes(30) + .map { + listOf( + it?.focusTimeQ1?.toInt() ?: 0, + it?.focusTimeQ2?.toInt() ?: 0, + it?.focusTimeQ3?.toInt() ?: 0, + it?.focusTimeQ4?.toInt() ?: 0 + ) + } + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5000), + initialValue = listOf(0, 0, 0, 0) + ) + + val lastYearSummaryChartData: StateFlow>>> = + statRepository.getLastNDaysStatsSummary(365) + .map { list -> + val reversed = list.reversed() + val keys = reversed.map { it.date.format(yearDayFormatter) } + val values = reversed.map { it.focusTime } + lastYearSummary.first.runTransaction { + lineSeries { series(values) } + extras { it[lastYearSummary.second] = keys } } - } - } + lastYearSummary + } + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5000), + initialValue = lastYearSummary + ) + + val lastYearAverageFocusTimes: StateFlow> = + statRepository.getLastNDaysAverageFocusTimes(365) + .map { + listOf( + it?.focusTimeQ1?.toInt() ?: 0, + it?.focusTimeQ2?.toInt() ?: 0, + it?.focusTimeQ3?.toInt() ?: 0, + it?.focusTimeQ4?.toInt() ?: 0 + ) + } + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5000), + initialValue = listOf(0, 0, 0, 0) + ) companion object { val Factory: ViewModelProvider.Factory = viewModelFactory { 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 e608cdc..6fb988b 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 @@ -35,7 +35,10 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.ButtonGroup +import androidx.compose.material3.ButtonGroupDefaults import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularWavyProgressIndicator import androidx.compose.material3.DropdownMenuItem @@ -49,8 +52,10 @@ import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.motionScheme import androidx.compose.material3.MaterialTheme.shapes import androidx.compose.material3.MaterialTheme.typography +import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -64,6 +69,7 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.hapticfeedback.HapticFeedbackType +import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.painterResource @@ -115,7 +121,9 @@ fun TimerScreen( onResult = {} ) - Column(modifier = modifier) { + val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() + + Column(modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection)) { TopAppBar( title = { AnimatedContent( @@ -140,7 +148,7 @@ fun TimerScreen( fontFamily = robotoFlexTopBar, fontSize = 32.sp, lineHeight = 32.sp, - color = colorScheme.onErrorContainer + color = colorScheme.error ), textAlign = TextAlign.Center, modifier = Modifier.width(210.dp) @@ -153,7 +161,7 @@ fun TimerScreen( fontFamily = robotoFlexTopBar, fontSize = 32.sp, lineHeight = 32.sp, - color = colorScheme.onPrimaryContainer + color = colorScheme.primary ), textAlign = TextAlign.Center, modifier = Modifier.width(210.dp) @@ -165,7 +173,7 @@ fun TimerScreen( fontFamily = robotoFlexTopBar, fontSize = 32.sp, lineHeight = 32.sp, - color = colorScheme.onTertiaryContainer + color = colorScheme.tertiary ), textAlign = TextAlign.Center, modifier = Modifier.width(210.dp) @@ -177,7 +185,7 @@ fun TimerScreen( fontFamily = robotoFlexTopBar, fontSize = 32.sp, lineHeight = 32.sp, - color = colorScheme.onTertiaryContainer + color = colorScheme.tertiary ), textAlign = TextAlign.Center, modifier = Modifier.width(210.dp) @@ -186,13 +194,16 @@ fun TimerScreen( } }, subtitle = {}, - titleHorizontalAlignment = CenterHorizontally + titleHorizontalAlignment = CenterHorizontally, + scrollBehavior = scrollBehavior ) Column( verticalArrangement = Arrangement.Center, horizontalAlignment = CenterHorizontally, - modifier = Modifier.fillMaxSize() + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) ) { Column(horizontalAlignment = CenterHorizontally) { Box(contentAlignment = Alignment.Center) { @@ -277,27 +288,11 @@ fun TimerScreen( val interactionSources = remember { List(3) { MutableInteractionSource() } } ButtonGroup( overflowIndicator = { state -> - FilledTonalIconButton( - onClick = { - if (state.isExpanded) { - state.dismiss() - } else { - state.show() - } - }, - colors = IconButtonDefaults.filledTonalIconButtonColors( - containerColor = colorContainer - ), - shapes = IconButtonDefaults.shapes(), - modifier = Modifier - .size(64.dp, 96.dp) - ) { - Icon( - painterResource(R.drawable.more_vert_large), - contentDescription = stringResource(R.string.more), - modifier = Modifier.size(32.dp) - ) - } + ButtonGroupDefaults.OverflowIndicator( + state, + colors = IconButtonDefaults.filledTonalIconButtonColors(), + modifier = Modifier.size(64.dp, 96.dp) + ) }, modifier = Modifier.padding(16.dp) ) { @@ -509,6 +504,8 @@ fun TimerScreen( ) } } + + Spacer(Modifier.height(16.dp)) } } } @@ -523,10 +520,12 @@ fun TimerScreenPreview() { timeStr = "03:34", nextTimeStr = "5:00", timerMode = TimerMode.FOCUS, timerRunning = true ) TomatoTheme { - TimerScreen( - timerState, - { 0.3f }, - {} - ) + Surface { + TimerScreen( + timerState, + { 0.3f }, + {} + ) + } } } diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml new file mode 100644 index 0000000..55344e5 --- /dev/null +++ b/app/src/main/res/values-ar/strings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/values-b+nan+Hant/strings.xml b/app/src/main/res/values-b+nan+Hant/strings.xml new file mode 100644 index 0000000..5815af7 --- /dev/null +++ b/app/src/main/res/values-b+nan+Hant/strings.xml @@ -0,0 +1,38 @@ + + + 開始 + 停止 + 專注 + 小歇 + 系統預設 + 亂鐘 + 淺色 + 深色 + 選擇主題 + 工課效率分析 + 無仝時段的專注連紲時間 + 亂鐘聲音 + 烏色的主題 + 使用專烏色的主題 + 計時結束時喨喨仔 + 震動 + 計時結束時震動 + 主題 + 設定 + 小節長度 + 一个小節的專注時段:%1$d + 長歇 + 離開 + 跳過 + 止喨 + 閣賰 %1$s 分鐘 + 已暫停 + 已完成 + 紲落來:%1$s (%2$s) + 開始後一個 + 選擇配色方案 + 確認 + 配色方案 + 動態 + 色彩 + diff --git a/app/src/main/res/values-ckb/strings.xml b/app/src/main/res/values-ckb/strings.xml new file mode 100644 index 0000000..b5a25e8 --- /dev/null +++ b/app/src/main/res/values-ckb/strings.xml @@ -0,0 +1,39 @@ + + + دەستپێکردن + وەستاندن + سەرنجدان + پشوویەکی کورت + پشوویەکی درێژ + دەرچوون + پەڕاندن + وەستاندنی زەنگی ئاگادار کردنەوە + %1$s خولەک ماوە + باشە + ڕەنگی رووکار + داینامیکی + ڕەنگ + ڕەنگی ڕووکار هەڵبژێرە + وەک سیستەم + زەنگی ئاگادارکردنەوە + تاریک + ڕووکارێک هەلبژێرە + شرۆڤەی بە بەرهەمی + ماوەی سەرنجدان لە کاتە جیاوازەکانی ڕۆژ + زەنگی ئاگادارکردنەوە + ڕووکاری ڕەش + بەکارهێنانی ڕووکاری تەواو ڕەش + زەنگی ئاگادارکردنەوە لێبدە لە کاتی تەواوبوونی کاتگرەوە + لەرینەوە + لەرینەوە لە کاتی تەواوبوونی کاتگرەوە + ڕووناک + ڕووکار + ڕێکخستنەکان + ئەمڕۆ + پشوو + هەفتەی ڕابردوو + زیاتر + ئامار + زانیاری زیاتر + وەستاندن + diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml new file mode 100644 index 0000000..9050dd5 --- /dev/null +++ b/app/src/main/res/values-cs/strings.xml @@ -0,0 +1,5 @@ + + + Zapnout + Zastavit + diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml new file mode 100644 index 0000000..3c08f8f --- /dev/null +++ b/app/src/main/res/values-de/strings.xml @@ -0,0 +1,59 @@ + + + Start + Stop + Fokus + Kurze Pause + Lange Pause + Abbrechen + Überspringen + Alarm beenden + %1$s min verbleibend + Pausiert + Abgeschlossen + Als nächstes: %1$s (%2$s) + Weiter + Farbschema auswählen + OK + Farbschema + Dynamisch + Farbe + Systemstandard + Alarm + Hell + Dunkel + Erscheinungsbild wählen + Produktivitätsanalyse + Fokusdauer zu verschiedenen Tageszeiten + Alarmton + Schwarzes Design + Echten Schwarzmodus verwenden + Alarmton bei Ablauf des Timers + Vibration + Vibrieren, wenn ein Timer abgelaufen ist + Design + Einstellungen + Fokusphasen + Fokusintervalle pro Sitzung:%1$d + Eine „Sitzung“ ist eine Abfolge von Pomodoro-Intervallen, die Fokusintervalle, kurze Pausenintervalle und ein langes Pausenintervall enthalten. Die letzte Pause einer Sitzung ist immer eine lange Pause. + Statistik + Heute + Pause + Letzte Woche + Fokus pro Tag (⌀) + Weitere Infos + Wöchentliche Produktivität + Letzter Monat + Monatliche Produktivität + Alarm stoppen? + Die aktuelle Timer-Sitzung ist beendet. Tippen Sie auf eine beliebige Stelle, um den Alarm zu stoppen. + %1$d von %2$d + Mehr + Pausieren + Fortsetzen + Neustarten + Zum nächsten springen + Als nächstes + Timer + Timer-Fortschritt + diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml new file mode 100644 index 0000000..5a729c0 --- /dev/null +++ b/app/src/main/res/values-es/strings.xml @@ -0,0 +1,59 @@ + + + Iniciar + Detener + Concentración + Pequeño descanso + Descanso extenso + Salir + Omitir + Parar alarma + En pausa + Predeterminado del sistema + Alarma + Elige tema + Análisis de productividad + Duración de la concentración en diferentes momentos del día + Sonido de la alarma + Tema negro + Utiliza un tema oscuro negro puro + Sonar alarma cuando el temporizador finalice + Vibrar + Vibrar cuando el temporizador finalice + Tema + Configuración + Duración de la sesión + Intervalos de concentración en una sesión: %1$d + Una \"sesión\" es una secuencia de intervalos Pomodoro que contiene intervalos de concentración, intervalos de descanso cortos y un intervalo de descanso largo. El último descanso de una sesión es siempre un descanso largo. + Estadísticas + Hoy + Descanso + Semana pasada + concentración por día (avg) + Más informes + Análisis de productividad semanal + Mes pasado + Análisis de productividad mensual + Parar alarma? + La sesión actual del temporizador ha finalizado. Toque en cualquier lugar para detener la alarma. + %1$d de %2$d + Más + Pausa + Empezar + Reiniciar + Pasar al siguiente + A continuación + Temporizador + Pregreso del temporizador + %1$s minutos restantes + Completado + A continuación: %1$s (%2$s) + Empezar siguiente + Elige la combinación de colores + Ok + Esquema de colores + Dinámica + Color + Luz + Oscuro + diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml new file mode 100644 index 0000000..8955466 --- /dev/null +++ b/app/src/main/res/values-hu/strings.xml @@ -0,0 +1,59 @@ + + + Indít + Leállít + Fókuszálás + Rövid szünet + Hosszú szünet + Kilépés + Átugrás + Jelzés leállítása + %1$s perc van hátra + Megállítva + Befejezve + Következő: %1$s (%2$s) + Következő indítása + Válassz színsémát + OK + Színséma + Dinamikus + Szín + Rendszer alapértelmezés + Jelzés + Világos + Sötét + Válassz témát + Produktivitás elemzése + Fókuszálás időtartama a nap különböző szakaszaiban + Jelzés hangja + Fekete téma + Használjon teljesen fekete sötét témát + Csengőhang, amikor az időzítő lejár + Rezgés + Rezgés, amikor az időzítő lejár + Téma + Beállítások + Munkamenet hossza + Fókusz-intervallumok száma: %1$d + Egy „munkamenet” egy sor pomodoro-intervallumból áll, amely fókusz-intervallumokat, rövid szüneteket és egy hosszú szünetet tartalmaz. A munkamenet utolsó szünete mindig hosszú szünet. + Statisztikák + Ma + Szünet + Múlt hét + naponta átlagosan fókuszált idő + További információk + Heti produktivitás elemzés + Múlt hónap + Havi produktivitás elemzés + Jelzés leállítása? + A jelenlegi időzítő munkamenet befejeződött. Érintse meg a képernyőt bárhol, hogy leállítsa a jelzést. + %1$d a %2$d-ből + Több + Megállít + Indítás + Újraindítás + Ugrás a következőre + Következik + Időzítő + Időzítő állása + diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml new file mode 100644 index 0000000..1f13090 --- /dev/null +++ b/app/src/main/res/values-it/strings.xml @@ -0,0 +1,59 @@ + + + Inizia + Ferma + Concentrati + Pausa breve + Pausa lunga + Esci + Salta + Ferma sveglia + %1$s min rimanenti + In pausa + Completato + Prossimo: %1$s (%2$s) + Inizia il prossimo + Scegli schema colori + OK + Schema colori + Dinamico + Colore + Segui sistema + Sveglia + Chiaro + Scuro + Scegli tema + Analisi produttività + Durata della concentrazione in diverse ore del giorno + Suono sveglia + Tema nero + Tema scuro completamente nero + Suona la sveglia quando il timer scade + Vibrazione + Vibra il telefono quando il timer scade + Tema + Impostazioni + Lunghezza sessione + Intervalli di concentrazione per sessione: %1$d + Una \"sessione\" è una sequenza di intervalli \"pomodoro\" che include intervalli di concentrazione, di pausa breve, e di pausa lunga. L\'ultima pausa di una sessione è sempre una pausa lunga. + Statistiche + Oggi + Pausa + Ultima settimana + concentrazione al giorno (media) + Più informazioni + Analisi produttività settimanale + Ultimo mese + Analisi produttività mensile + Ferma allarme? + Il timer per la sessione corrente è scaduto. Tocca in qualsiasi punto per fermare la sveglia. + %1$d di %2$d + Altro + Pausa + Inizia + Reinizia + Salta al prossimo + Prossimo + Timer + Progresso timer + diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml new file mode 100644 index 0000000..93ee360 --- /dev/null +++ b/app/src/main/res/values-ja/strings.xml @@ -0,0 +1,59 @@ + + + 開始 + 停止 + 集中 + 短い休憩 + 長い休憩 + 終了 + スキップ + アラームを停止 + 残り%1$s分 + 中断 + 完了 + 次の予定: %1$s (%2$s) + 開始 + カラースキームを選んでください + OK + カラースキーム + ダイナミック + + 初期設定 + アラーム + ライト + ダーク + テーマ選択 + 生産性の分析 + 時間帯別の集中した時間 + アラームサウンド + ブラックテーマ + 漆黒テーマを使用 + 終了時にアラームを鳴らす + 振動 + 終了時に振動します + テーマ + 設定 + セッションの長さ + %1$dに一度長い休憩を行う + 「セッション」とは、集中時間・短い休憩・長い休憩を含む一連のポモドーロ間隔のことです。セッションの最後の休憩は必ず長い休憩になります。 + 統計 + 今日 + 休憩 + 先週 + 平均時間 + 詳細 + 一週間の生産性の分析 + 先月 + 先月の生産性の分析 + アラームを停止しますか? + 現在のタイマーセッションは終了しました。アラームを停止するには、任意の場所をタップしてください。 + %1$dの%2$d + 追加 + 一時停止 + 再開 + やり直し + スキップ + 次は + タイマー + タイマーの進行状況 + diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml new file mode 100644 index 0000000..e857b72 --- /dev/null +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -0,0 +1,59 @@ + + + Começar + Parar + Focar + Pausa curta + Pausa longa + Sair + Pular + Parar alarme + %1$s minutos restantes + Pausado + Concluído + A seguir: %1$s (%2$s) + Começar o próximo + Escolha o esquema de cores + OK + Esquema de cores + Dinâmico + Cor + Padrão do sistema + Alarme + Claro + Escuro + Escolher tema + Análise de produtividade + Duração do foco em diferentes horas do dia + Som de alarme + Tema escuro + Use um tema preto escuro puro + Tocar o alarme quando um timer for concluído + Vibrar + Vibrar quanto um timer for concluído + Tema + Configurações + Duração da sessão + Intervalos de foco em uma sessão: %1$d + Uma \"sessão\" é uma sequência de intervalos Pomodoro que contém intervalos de foco, intervalos de pausa curta e um intervalo de pausa longa. A última pausa de uma sessão é sempre uma pausa longa. + Estatísticas + Hoje + Pausa + Última semana + foco por dia (média) + Mais informações + Análise de produtividade semanal + Último mês + Análise de produtividade mensal + Parar Alarme? + A sessão atual do timer foi concluída. Toque em qualquer lugar para interromper o alarme. + %1$d de %2$d + Mais + Pausar + Começar + Reiniciar + Pular para o próximo + Próximo + Timer + Progresso do timer + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0d55c21..067cff5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -56,4 +56,5 @@ Up next Timer Timer progress + Last year \ No newline at end of file diff --git a/fastlane/metadata/android/ckb/short_description.txt b/fastlane/metadata/android/ckb/short_description.txt new file mode 100644 index 0000000..4edb81f --- /dev/null +++ b/fastlane/metadata/android/ckb/short_description.txt @@ -0,0 +1 @@ +کاتگرەوەیەکی چکۆلانەی پۆمۆدۆرە diff --git a/fastlane/metadata/android/de-DE/full_description.txt b/fastlane/metadata/android/de-DE/full_description.txt new file mode 100644 index 0000000..1c1fcad --- /dev/null +++ b/fastlane/metadata/android/de-DE/full_description.txt @@ -0,0 +1 @@ +

Tomato ist ein minimalistischer Pomodoro-Timer für Android, der auf Material 3 Expressive basiert.


Funktionen:

  • Einfache, minimalistische Benutzeroberfläche, die auf den neuesten Material 3 Expressive-Richtlinien basiert
  • Detaillierte Statistiken zu Arbeits-/Lernzeiten in leicht verständlicher Form
    • Statistiken für den aktuellen Tag auf einen Blick sichtbar
    • Statistiken für die letzte Woche und den letzten Monat in einer übersichtlichen und sauberen Grafik dargestellt
    • Zusätzliche Statistiken für die letzte Woche und den letzten Monat zeigen, zu welcher Tageszeit Sie am produktivsten sind
  • Anpassbare Timer Voreinstellungen
diff --git a/fastlane/metadata/android/de-DE/short_description.txt b/fastlane/metadata/android/de-DE/short_description.txt new file mode 100644 index 0000000..fbf59f3 --- /dev/null +++ b/fastlane/metadata/android/de-DE/short_description.txt @@ -0,0 +1 @@ +Minimalistischer Pomodoro-Timer diff --git a/fastlane/metadata/android/en-US/changelogs/12.txt b/fastlane/metadata/android/en-US/changelogs/12.txt new file mode 100644 index 0000000..01a6a5e --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/12.txt @@ -0,0 +1,12 @@ +New features: +- The Stats screen now contains the data for the past year +- Charts now have fading edges to indicate scrollable content + +Fixes: +- Improved Stats screen performance +- Notification now does not pop up on your screen +- Fixed low contrast on the Timer screen heading +- Improve app usability on smaller screens + +Translators on Weblate added support for: +Arabic, Czech, German, Hungarian, Italian, Japanese, Kurdish (Central), Minnan (Traditional), Portuguese (Brazil), Spanish \ No newline at end of file diff --git a/fastlane/metadata/android/es-ES/full_description.txt b/fastlane/metadata/android/es-ES/full_description.txt new file mode 100644 index 0000000..4feacf8 --- /dev/null +++ b/fastlane/metadata/android/es-ES/full_description.txt @@ -0,0 +1 @@ +

Tomato es un temporizador Pomodoro minimalista para Android basado en "Material 3 Expressive".


Características:

  • Interfaz de usuario sencilla y minimalista basada en las últimas directrices de "Material 3 Expressive".
  • Estadísticas detalladas e intuitivas en los tiempos de trabajo/estudio.
    • Estadísticas simples y accesibles del día en curso.
    • Estadísticas de la última semana y del último mes mostradas en un gráfico sencillo.
    • Estadísticas adicionales de la última semana y del último mes que muestran a qué hora del día eres más productivo.
  • Parámetros de temporizador personalizables.
diff --git a/fastlane/metadata/android/es-ES/short_description.txt b/fastlane/metadata/android/es-ES/short_description.txt new file mode 100644 index 0000000..ba57514 --- /dev/null +++ b/fastlane/metadata/android/es-ES/short_description.txt @@ -0,0 +1 @@ +Temporizador Pomodoro minimalista diff --git a/fastlane/metadata/android/hu-HU/full_description.txt b/fastlane/metadata/android/hu-HU/full_description.txt new file mode 100644 index 0000000..247aaa2 --- /dev/null +++ b/fastlane/metadata/android/hu-HU/full_description.txt @@ -0,0 +1 @@ +

A Tomato egy minimalista Pomodoro időzítő Androidra, ami Material 3 Expressive alapú.


Funkciók:

  • Egyszerű, minimalista felhasználói felület, a legújabb Material 3 Expressive irányelvek alapján.
  • Részletes statisztikák a munka/tanulási időkről, könnyen érthető módon
    • A mai nap statisztikái egy pillanat alatt áttekinthetők
    • Az elmúlt hét és hónap statisztikái könnyen olvasható, letisztult grafikonon
    • A múlt hét és hónap további statisztikái, amelyekből kiderül, hogy a nap melyik szakaszában vagy a legproduktívabb
  • Testreszabható időzítő paraméterek
diff --git a/fastlane/metadata/android/hu-HU/short_description.txt b/fastlane/metadata/android/hu-HU/short_description.txt new file mode 100644 index 0000000..6e775e7 --- /dev/null +++ b/fastlane/metadata/android/hu-HU/short_description.txt @@ -0,0 +1 @@ +Minimalista Pomodoro időzítő diff --git a/fastlane/metadata/android/it-IT/full_description.txt b/fastlane/metadata/android/it-IT/full_description.txt new file mode 100644 index 0000000..c15cbd1 --- /dev/null +++ b/fastlane/metadata/android/it-IT/full_description.txt @@ -0,0 +1 @@ +

Tomato è un timer Pomodoro minimalista per Android con design Material 3 Expressive.


Features:

  • UI semplice e minimalista basata sulle ultime linee guida Material 3 Expressive
  • Statistiche dettagliate dei tempi di studio/lavoro in modo semplice da comprendere
    • Statistiche per il giorno attuale visibili a colpo d'occhio
    • Statistiche per l'ultima settimana e ultimo mese visibili in un chiaro grafico di semplice lettura
    • Statistiche aggiuntive per l'ultima settimana e ultimo mese che mostrano a quale ora del giorno sei più produttivo
  • Parametri del timer personalizzabili
diff --git a/fastlane/metadata/android/it-IT/short_description.txt b/fastlane/metadata/android/it-IT/short_description.txt new file mode 100644 index 0000000..1704e13 --- /dev/null +++ b/fastlane/metadata/android/it-IT/short_description.txt @@ -0,0 +1 @@ +Timer Pomodoro Minimalista diff --git a/fastlane/metadata/android/ja-JP/full_description.txt b/fastlane/metadata/android/ja-JP/full_description.txt new file mode 100644 index 0000000..b959733 --- /dev/null +++ b/fastlane/metadata/android/ja-JP/full_description.txt @@ -0,0 +1 @@ +

Tomato は、Material 3 Expressive をベースにしたミニマルな Android 向けポモドーロ・タイマーです。


主な機能:

  • 最新の Material 3 Expressive ガイドラインに基づいたシンプルでミニマルなUI
  • 作業・学習時間をわかりやすく可視化した詳細な統計
    • 当日の統計をひと目で確認可能
    • 過去1週間・1か月分の統計を見やすく整理されたグラフで表示
    • さらに、先週・先月の統計から一日のうち最も集中できる時間帯を確認可能
  • タイマー設定を自由にカスタマイズ可能
diff --git a/fastlane/metadata/android/ja-JP/short_description.txt b/fastlane/metadata/android/ja-JP/short_description.txt new file mode 100644 index 0000000..d761598 --- /dev/null +++ b/fastlane/metadata/android/ja-JP/short_description.txt @@ -0,0 +1 @@ +シンプルなポモドーロタイマー diff --git a/fastlane/metadata/android/nan-Hant/full_description.txt b/fastlane/metadata/android/nan-Hant/full_description.txt new file mode 100644 index 0000000..36ae7dc --- /dev/null +++ b/fastlane/metadata/android/nan-Hant/full_description.txt @@ -0,0 +1,9 @@ +

Tomato 是一个簡單的柑仔蜜鐘計時器,設計的風格是根據 Material 3 Expressive。


功能:

    +
  • 簡單、極簡的使用介面,遵照最新的 Material 3 Expressive 指引
  • +
  • 提供詳細的工作/讀書時間統計,容易看懂
      +
    • 當日的統計一目了然
    • +
    • 上禮拜佮上個月的統計,以簡潔 ê 圖表呈現
    • +
    • 閣有額外 ê 統計,顯示你上生產力的時段(上禮拜佮上個月)
    • +
  • +
  • 計時參數會當自由調整
  • +
diff --git a/fastlane/metadata/android/nan-Hant/short_description.txt b/fastlane/metadata/android/nan-Hant/short_description.txt new file mode 100644 index 0000000..935f8ab --- /dev/null +++ b/fastlane/metadata/android/nan-Hant/short_description.txt @@ -0,0 +1 @@ +簡潔柑仔蜜計時器 diff --git a/fastlane/metadata/android/pt-BR/full_description.txt b/fastlane/metadata/android/pt-BR/full_description.txt new file mode 100644 index 0000000..b9e7b5b --- /dev/null +++ b/fastlane/metadata/android/pt-BR/full_description.txt @@ -0,0 +1 @@ +

Tomato é um timer Pomodoro minimalista para Android baseado em Material 3 Expressive.


Funções:

  • Simples, interface minimalista baseada nas últimas diretrizes do Material 3 Expressive
  • Estatísticas detalhadas de tempo de trabalho/estudo de um jeito fácil de entender
    • Estatísticas do dia atual visíveis num relance
    • Estatísticas da última semana e do último mês mostrados de um jeito fácil de ler em um gráfico limpo
    • Estatísticas adicionais da última semana e mês mostrando que hora do dia você é mais produtivo
  • Parâmetros do timer customizáveis
diff --git a/fastlane/metadata/android/pt-BR/short_description.txt b/fastlane/metadata/android/pt-BR/short_description.txt new file mode 100644 index 0000000..8846b7b --- /dev/null +++ b/fastlane/metadata/android/pt-BR/short_description.txt @@ -0,0 +1 @@ +Timer minimalista Pomodoro diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ed4870e..0e776d2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,18 +2,18 @@ activityCompose = "1.11.0" adaptive = "1.1.0" agp = "8.11.2" -composeBom = "2025.09.01" +composeBom = "2025.10.00" coreKtx = "1.17.0" espressoCore = "3.7.0" junit = "4.13.2" junitVersion = "1.3.0" kotlin = "2.2.20" -ksp = "2.2.20-2.0.3" +ksp = "2.2.20-2.0.4" lifecycleRuntimeKtx = "2.9.4" materialKolor = "3.0.1" -navigation3 = "1.0.0-alpha10" -room = "2.8.1" -vico = "2.2.0" +navigation3 = "1.0.0-alpha11" +room = "2.8.2" +vico = "2.2.1" [libraries] androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2a84e18..2e11132 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index ef07e01..adff685 100755 --- a/gradlew +++ b/gradlew @@ -114,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -172,7 +171,6 @@ fi # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) @@ -212,7 +210,6 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" diff --git a/gradlew.bat b/gradlew.bat index 5eed7ee..e509b2d 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -70,11 +70,10 @@ goto fail :execute @rem Setup the command line -set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell