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..1f7c8c3 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 @@ -35,6 +35,7 @@ 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 @@ -56,10 +57,8 @@ 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.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 @@ -72,20 +71,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 +99,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 +112,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) @@ -218,7 +241,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 +317,11 @@ fun StatsScreen( .padding(horizontal = 16.dp) ) { Text( - millisecondsToHoursMinutes(lastMonthAverageFocusTimes?.total() ?: 0), + millisecondsToHoursMinutes( + remember(lastMonthAverageFocusTimes) { + lastMonthAverageFocusTimes.sum().toLong() + } + ), style = typography.displaySmall, fontFamily = openRundeClock ) @@ -343,8 +374,51 @@ 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)) } } } } @@ -356,23 +430,25 @@ fun StatsScreen( @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) } + extras { it[keys] = listOf("M", "T", "W", "T", "F", "S", "S") } } } StatsScreen( PaddingValues(), - Pair(modelProducer, ExtraStore.Key()), - modelProducer, - Pair(modelProducer, ExtraStore.Key()), - modelProducer, + Pair(modelProducer, keys), + Pair(modelProducer, keys), + Pair(modelProducer, keys), null, - null, - 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/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