feat(stats): fully implement last week screen

This commit is contained in:
Nishant Mishra
2025-12-12 12:48:46 +05:30
parent 4ec2ba1321
commit c39089de21
10 changed files with 267 additions and 72 deletions

View File

@@ -1,8 +1,18 @@
/*
* Copyright (c) 2025 Nishant Mishra
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
* This file is part of Tomato - a minimalist pomodoro timer for Android.
*
* Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
* General Public License as published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Tomato.
* If not, see <https://www.gnu.org/licenses/>.
*/
package org.nsh07.pomodoro.data
@@ -29,17 +39,10 @@ data class Stat(
fun totalFocusTime() = focusTimeQ1 + focusTimeQ2 + focusTimeQ3 + focusTimeQ4
}
data class StatSummary(
val date: LocalDate,
val focusTime: Long,
val breakTime: Long
)
data class StatFocusTime(
data class StatTime(
val focusTimeQ1: Long,
val focusTimeQ2: Long,
val focusTimeQ3: Long,
val focusTimeQ4: Long
) {
fun total() = focusTimeQ1 + focusTimeQ2 + focusTimeQ3 + focusTimeQ4
}
val focusTimeQ4: Long,
val breakTime: Long,
)

View File

@@ -1,8 +1,18 @@
/*
* Copyright (c) 2025 Nishant Mishra
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
* This file is part of Tomato - a minimalist pomodoro timer for Android.
*
* Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
* General Public License as published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Tomato.
* If not, see <https://www.gnu.org/licenses/>.
*/
package org.nsh07.pomodoro.data
@@ -37,23 +47,24 @@ interface StatDao {
@Query("SELECT * FROM stat WHERE date = :date")
fun getStat(date: LocalDate): Flow<Stat?>
@Query("SELECT date, focusTimeQ1 + focusTimeQ2 + focusTimeQ3 + focusTimeQ4 as focusTime, breakTime FROM stat ORDER BY date DESC LIMIT :n")
fun getLastNDaysStatsSummary(n: Int): Flow<List<StatSummary>>
@Query("SELECT date, focusTimeQ1, focusTimeQ2, focusTimeQ3, focusTimeQ4, breakTime FROM stat ORDER BY date DESC LIMIT :n")
fun getLastNDaysStats(n: Int): Flow<List<Stat>>
@Query(
"SELECT " +
"AVG(focusTimeQ1) AS focusTimeQ1, " +
"AVG(focusTimeQ2) AS focusTimeQ2, " +
"AVG(focusTimeQ3) AS focusTimeQ3, " +
"AVG(focusTimeQ4) AS focusTimeQ4 " +
"AVG(focusTimeQ4) AS focusTimeQ4, " +
"AVG(breakTime) AS breakTime " +
"FROM (" +
"SELECT * FROM (" +
"SELECT focusTimeQ1, focusTimeQ2, focusTimeQ3, focusTimeQ4 FROM stat ORDER BY date DESC LIMIT :n" +
"SELECT focusTimeQ1, focusTimeQ2, focusTimeQ3, focusTimeQ4, breakTime FROM stat ORDER BY date DESC LIMIT :n" +
") " +
"WHERE focusTimeQ1 != 0 OR focusTimeQ2 != 0 OR focusTimeQ3 != 0 OR focusTimeQ4 != 0 " +
")"
)
fun getLastNDaysAvgFocusTimes(n: Int): Flow<StatFocusTime?>
fun getLastNDaysAvgStats(n: Int): Flow<StatTime?>
@Query("SELECT EXISTS (SELECT * FROM stat WHERE date = :date)")
suspend fun statExists(date: LocalDate): Boolean

View File

@@ -1,8 +1,18 @@
/*
* Copyright (c) 2025 Nishant Mishra
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
* This file is part of Tomato - a minimalist pomodoro timer for Android.
*
* Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
* General Public License as published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Tomato.
* If not, see <https://www.gnu.org/licenses/>.
*/
package org.nsh07.pomodoro.data
@@ -28,9 +38,9 @@ interface StatRepository {
fun getTodayStat(): Flow<Stat?>
fun getLastNDaysStatsSummary(n: Int): Flow<List<StatSummary>>
fun getLastNDaysStats(n: Int): Flow<List<Stat>>
fun getLastNDaysAverageFocusTimes(n: Int): Flow<StatFocusTime?>
fun getLastNDaysAverageFocusTimes(n: Int): Flow<StatTime?>
suspend fun getLastDate(): LocalDate?
}
@@ -101,11 +111,11 @@ class AppStatRepository(
return statDao.getStat(currentDate)
}
override fun getLastNDaysStatsSummary(n: Int): Flow<List<StatSummary>> =
statDao.getLastNDaysStatsSummary(n)
override fun getLastNDaysStats(n: Int): Flow<List<Stat>> =
statDao.getLastNDaysStats(n)
override fun getLastNDaysAverageFocusTimes(n: Int): Flow<StatFocusTime?> =
statDao.getLastNDaysAvgFocusTimes(n)
override fun getLastNDaysAverageFocusTimes(n: Int): Flow<StatTime?> =
statDao.getLastNDaysAvgStats(n)
override suspend fun getLastDate(): LocalDate? = statDao.getLastDate()
}

View File

@@ -62,6 +62,7 @@ fun StatsScreenRoot(
val todayStat by viewModel.todayStat.collectAsStateWithLifecycle(null)
val lastWeekSummaryChartData by viewModel.lastWeekSummaryChartData.collectAsStateWithLifecycle()
val lastWeekSummaryValues by viewModel.lastWeekStats.collectAsStateWithLifecycle()
val lastWeekAnalysisValues by viewModel.lastWeekAverageFocusTimes.collectAsStateWithLifecycle()
val lastMonthSummaryChartData by viewModel.lastMonthSummaryChartData.collectAsStateWithLifecycle()
@@ -74,10 +75,11 @@ fun StatsScreenRoot(
contentPadding = contentPadding,
backStack = backStack,
lastWeekSummaryChartData = lastWeekSummaryChartData,
lastWeekSummaryValues = lastWeekSummaryValues,
lastMonthSummaryChartData = lastMonthSummaryChartData,
lastYearSummaryChartData = lastYearSummaryChartData,
todayStat = todayStat,
lastWeekAverageFocusTimes = lastWeekAnalysisValues,
lastWeekAnalysisValues = lastWeekAnalysisValues,
lastMonthAverageFocusTimes = lastMonthAnalysisValues,
lastYearAverageFocusTimes = lastYearAnalysisValues,
generateSampleData = viewModel::generateSampleData,
@@ -94,10 +96,11 @@ fun StatsScreen(
contentPadding: PaddingValues,
backStack: SnapshotStateList<Screen.Stats>,
lastWeekSummaryChartData: Pair<CartesianChartModelProducer, ExtraStore.Key<List<String>>>,
lastWeekSummaryValues: List<Pair<String, List<Long>>>,
lastMonthSummaryChartData: Pair<CartesianChartModelProducer, ExtraStore.Key<List<String>>>,
lastYearSummaryChartData: Pair<CartesianChartModelProducer, ExtraStore.Key<List<String>>>,
todayStat: Stat?,
lastWeekAverageFocusTimes: List<Int>,
lastWeekAnalysisValues: Pair<List<Long>, Long>,
lastMonthAverageFocusTimes: List<Int>,
lastYearAverageFocusTimes: List<Int>,
generateSampleData: () -> Unit,
@@ -134,7 +137,7 @@ fun StatsScreen(
lastMonthSummaryChartData = lastMonthSummaryChartData,
lastYearSummaryChartData = lastYearSummaryChartData,
todayStat = todayStat,
lastWeekAverageFocusTimes = lastWeekAverageFocusTimes,
lastWeekAverageFocusTimes = lastWeekAnalysisValues.first,
lastMonthAverageFocusTimes = lastMonthAverageFocusTimes,
lastYearAverageFocusTimes = lastYearAverageFocusTimes,
generateSampleData = generateSampleData,
@@ -154,10 +157,11 @@ fun StatsScreen(
entry<Screen.Stats.LastWeek> {
LastWeekScreen(
contentPadding = contentPadding,
lastWeekAverageFocusTimes = lastWeekAverageFocusTimes,
lastWeekAnalysisValues = lastWeekAnalysisValues,
lastWeekSummaryValues = lastWeekSummaryValues,
lastWeekSummaryChartData = lastWeekSummaryChartData,
onBack = backStack::removeLastOrNull,
hoursMinutesFormat = hoursMinutesFormat,
lastWeekSummaryChartData = lastWeekSummaryChartData,
hoursFormat = hoursFormat,
minutesFormat = minutesFormat,
axisTypeface = axisTypeface,

View File

@@ -23,6 +23,8 @@ import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
import androidx.compose.animation.SharedTransitionDefaults
import androidx.compose.animation.SharedTransitionScope
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
@@ -37,8 +39,8 @@ fun Modifier.sharedBoundsReveal(
sharedTransitionScope: SharedTransitionScope,
animatedVisibilityScope: AnimatedVisibilityScope = LocalNavAnimatedContentScope.current,
boundsTransform: BoundsTransform = SharedTransitionDefaults.BoundsTransform,
enter: EnterTransition = EnterTransition.None,
exit: ExitTransition = ExitTransition.None,
enter: EnterTransition = fadeIn(),
exit: ExitTransition = fadeOut(),
resizeMode: SharedTransitionScope.ResizeMode = SharedTransitionScope.ResizeMode.RemeasureToBounds,
clipShape: Shape = MaterialTheme.shapes.largeIncreased,
renderInOverlayDuringTransition: Boolean = true,
@@ -55,7 +57,7 @@ fun Modifier.sharedBoundsReveal(
clipInOverlayDuringTransition = OverlayClip(clipShape),
renderInOverlayDuringTransition = renderInOverlayDuringTransition,
)
.skipToLookaheadSize()
.skipToLookaheadPosition()
// .skipToLookaheadSize()
// .skipToLookaheadPosition()
}
}

View File

@@ -25,9 +25,12 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme.colorScheme
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.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.tooling.preview.Preview
@@ -35,6 +38,7 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEachIndexed
import org.nsh07.pomodoro.ui.theme.TomatoTheme
import kotlin.math.roundToInt
/**
* A custom implementation of the 1-Dimensional heatmap plot that varies the width of the cells
@@ -98,6 +102,60 @@ fun VariableWidth1DHeatmap(
}
}
@Composable
fun FocusBreakRatioVisualization(
focusDuration: Long,
breakDuration: Long,
modifier: Modifier = Modifier,
height: Dp = 40.dp,
gap: Dp = 2.dp
) {
val focusPercentage = ((focusDuration / (focusDuration.toFloat() + breakDuration)) * 100)
val breakPercentage = ((breakDuration / (focusDuration.toFloat() + breakDuration)) * 100)
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(gap),
modifier = modifier
) {
Text(
text = focusPercentage.roundToInt().toString() + '%',
style = typography.bodyLarge,
color = colorScheme.primary,
modifier = Modifier.padding(end = 6.dp)
)
Spacer(
Modifier
.weight(focusPercentage)
.height(height)
.background(
colorScheme.primary,
shapes.large.copy(
topEnd = shapes.extraSmall.topEnd,
bottomEnd = shapes.extraSmall.bottomEnd
)
)
)
Spacer(
Modifier
.weight(breakPercentage)
.height(height)
.background(
colorScheme.tertiary,
shapes.large.copy(
topStart = shapes.extraSmall.topStart,
bottomStart = shapes.extraSmall.bottomStart
)
)
)
Text(
text = breakPercentage.roundToInt().toString() + '%',
style = typography.bodyLarge,
color = colorScheme.tertiary,
modifier = Modifier.padding(start = 6.dp)
)
}
}
@Preview
@Composable
fun VariableWidth1DHeatmapPreview() {

View File

@@ -19,14 +19,15 @@ package org.nsh07.pomodoro.ui.statsScreen.screens
import android.graphics.Typeface
import androidx.compose.animation.SharedTransitionScope
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
@@ -46,28 +47,34 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach
import androidx.navigation3.ui.LocalNavAnimatedContentScope
import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModelProducer
import com.patrykandpatrick.vico.core.cartesian.data.CartesianValueFormatter
import com.patrykandpatrick.vico.core.common.data.ExtraStore
import org.nsh07.pomodoro.R
import org.nsh07.pomodoro.ui.mergePaddingValues
import org.nsh07.pomodoro.ui.statsScreen.components.FocusBreakRatioVisualization
import org.nsh07.pomodoro.ui.statsScreen.components.TimeColumnChart
import org.nsh07.pomodoro.ui.statsScreen.components.VariableWidth1DHeatmap
import org.nsh07.pomodoro.ui.statsScreen.components.sharedBoundsReveal
import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTopBar
import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.topListItemShape
import org.nsh07.pomodoro.utils.millisecondsToHoursMinutes
import org.nsh07.pomodoro.utils.millisecondsToMinutes
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun SharedTransitionScope.LastWeekScreen(
contentPadding: PaddingValues,
lastWeekAverageFocusTimes: List<Int>,
lastWeekAnalysisValues: Pair<List<Long>, Long>,
lastWeekSummaryValues: List<Pair<String, List<Long>>>,
lastWeekSummaryChartData: Pair<CartesianChartModelProducer, ExtraStore.Key<List<String>>>,
onBack: () -> Unit,
modifier: Modifier = Modifier,
hoursMinutesFormat: String,
lastWeekSummaryChartData: Pair<CartesianChartModelProducer, ExtraStore.Key<List<String>>>,
hoursFormat: String,
minutesFormat: String,
axisTypeface: Typeface,
@@ -75,10 +82,10 @@ fun SharedTransitionScope.LastWeekScreen(
) {
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
val rankList = remember(lastWeekAverageFocusTimes) {
val rankList = remember(lastWeekAnalysisValues) {
val sortedIndices =
lastWeekAverageFocusTimes.indices.sortedByDescending { lastWeekAverageFocusTimes[it] }
val ranks = MutableList(lastWeekAverageFocusTimes.size) { 0 }
lastWeekAnalysisValues.first.indices.sortedByDescending { lastWeekAnalysisValues.first[it] }
val ranks = MutableList(lastWeekAnalysisValues.first.size) { 0 }
sortedIndices.forEachIndexed { rank, originalIndex ->
ranks[originalIndex] = rank
@@ -87,6 +94,10 @@ fun SharedTransitionScope.LastWeekScreen(
ranks
}
val focusDuration = remember(lastWeekAnalysisValues) {
lastWeekAnalysisValues.first.sum()
}
Scaffold(
topBar = {
TopAppBar(
@@ -115,11 +126,7 @@ fun SharedTransitionScope.LastWeekScreen(
)
}
},
scrollBehavior = scrollBehavior,
colors = TopAppBarDefaults.topAppBarColors(
containerColor = colorScheme.surfaceBright,
scrolledContainerColor = colorScheme.surfaceBright
)
scrollBehavior = scrollBehavior
)
},
modifier = modifier
@@ -135,11 +142,10 @@ fun SharedTransitionScope.LastWeekScreen(
) { innerPadding ->
val insets = mergePaddingValues(innerPadding, contentPadding)
LazyColumn(
verticalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
contentPadding = insets,
modifier = Modifier
.fillMaxSize()
.background(colorScheme.surfaceBright)
.padding(horizontal = 16.dp)
) {
item {
@@ -150,9 +156,7 @@ fun SharedTransitionScope.LastWeekScreen(
) {
Text(
millisecondsToHoursMinutes(
remember(lastWeekAverageFocusTimes) {
lastWeekAverageFocusTimes.sum().toLong()
},
focusDuration,
hoursMinutesFormat
),
style = typography.displaySmall,
@@ -175,7 +179,8 @@ fun SharedTransitionScope.LastWeekScreen(
)
)
}
Spacer(Modifier.height(16.dp))
}
item {
TimeColumnChart(
modelProducer = lastWeekSummaryChartData.first,
hoursFormat = hoursFormat,
@@ -194,6 +199,79 @@ fun SharedTransitionScope.LastWeekScreen(
)
)
}
item { Spacer(Modifier.height(8.dp)) }
item {
Text(
"Focus overview",
style = typography.headlineSmall
)
Text(
"Average focus durations at different times of the day",
style = typography.bodySmall,
color = colorScheme.onSurfaceVariant
)
}
item { VariableWidth1DHeatmap(lastWeekAnalysisValues.first, rankList = rankList) }
item {
Row {
lastWeekAnalysisValues.first.fastForEach {
Text(
if (it <= 60 * 60 * 1000)
millisecondsToMinutes(it, minutesFormat)
else millisecondsToHoursMinutes(it, hoursMinutesFormat),
style = typography.bodyLarge,
textAlign = TextAlign.Center,
color = colorScheme.onSurfaceVariant,
modifier = Modifier.weight(1f)
)
}
}
}
item { Spacer(Modifier.height(8.dp)) }
item {
Text(
stringResource(R.string.focus_break_ratio),
style = typography.headlineSmall
)
}
item {
FocusBreakRatioVisualization(
focusDuration = focusDuration,
breakDuration = lastWeekAnalysisValues.second
)
}
item { Spacer(Modifier.height(8.dp)) }
item {
Text(
"Focus insights",
style = typography.headlineSmall
)
Text(
"Focus overview of each day of the past week",
style = typography.bodySmall,
color = colorScheme.onSurfaceVariant
)
}
item {
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
lastWeekSummaryValues.fastForEach {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
it.first,
style = typography.labelSmall,
modifier = Modifier.size(18.dp)
)
VariableWidth1DHeatmap(it.second, rankList = rankList)
}
}
}
}
}
}
}

View File

@@ -82,7 +82,7 @@ fun SharedTransitionScope.StatsMainScreen(
lastMonthSummaryChartData: Pair<CartesianChartModelProducer, ExtraStore.Key<List<String>>>,
lastYearSummaryChartData: Pair<CartesianChartModelProducer, ExtraStore.Key<List<String>>>,
todayStat: Stat?,
lastWeekAverageFocusTimes: List<Int>,
lastWeekAverageFocusTimes: List<Long>,
lastMonthAverageFocusTimes: List<Int>,
lastYearAverageFocusTimes: List<Int>,
generateSampleData: () -> Unit,
@@ -254,7 +254,7 @@ fun SharedTransitionScope.StatsMainScreen(
Text(
millisecondsToHoursMinutes(
remember(lastWeekAverageFocusTimes) {
lastWeekAverageFocusTimes.sum().toLong()
lastWeekAverageFocusTimes.sum()
},
hoursMinutesFormat
),

View File

@@ -67,8 +67,10 @@ class StatsViewModel(
private val yearDayFormatter = DateTimeFormatter.ofPattern("d MMM")
private val lastWeekStatsFlow = statRepository.getLastNDaysStats(7)
val lastWeekSummaryChartData: StateFlow<Pair<CartesianChartModelProducer, ExtraStore.Key<List<String>>>> =
statRepository.getLastNDaysStatsSummary(7)
lastWeekStatsFlow
.map { list ->
// reversing is required because we need ascending order while the DB returns descending order
val reversed = list.reversed()
@@ -78,7 +80,7 @@ class StatsViewModel(
Locale.getDefault()
)
}
val values = reversed.map { it.focusTime }
val values = reversed.map { it.totalFocusTime() }
lastWeekSummary.first.runTransaction {
columnSeries { series(values) }
extras { it[lastWeekSummary.second] = keys }
@@ -92,29 +94,57 @@ class StatsViewModel(
initialValue = lastWeekSummary
)
val lastWeekAverageFocusTimes: StateFlow<List<Int>> =
val lastWeekStats: StateFlow<List<Pair<String, List<Long>>>> =
lastWeekStatsFlow
.map { value ->
value.reversed().map {
Pair(
it.date.dayOfWeek.getDisplayName(
TextStyle.NARROW,
Locale.getDefault()
),
listOf(
it.focusTimeQ1,
it.focusTimeQ2,
it.focusTimeQ3,
it.focusTimeQ4
)
)
}
}
.flowOn(Dispatchers.IO)
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = emptyList()
)
val lastWeekAverageFocusTimes: StateFlow<Pair<List<Long>, Long>> =
statRepository.getLastNDaysAverageFocusTimes(7)
.map {
listOf(
it?.focusTimeQ1?.toInt() ?: 0,
it?.focusTimeQ2?.toInt() ?: 0,
it?.focusTimeQ3?.toInt() ?: 0,
it?.focusTimeQ4?.toInt() ?: 0
Pair(
listOf(
it?.focusTimeQ1 ?: 0L,
it?.focusTimeQ2 ?: 0L,
it?.focusTimeQ3 ?: 0L,
it?.focusTimeQ4 ?: 0L
),
it?.breakTime ?: 0L
)
}
.flowOn(Dispatchers.IO)
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = listOf(0, 0, 0, 0)
initialValue = Pair(listOf(0L, 0L, 0L, 0L), 0L)
)
val lastMonthSummaryChartData: StateFlow<Pair<CartesianChartModelProducer, ExtraStore.Key<List<String>>>> =
statRepository.getLastNDaysStatsSummary(30)
statRepository.getLastNDaysStats(30)
.map { list ->
val reversed = list.reversed()
val keys = reversed.map { it.date.dayOfMonth.toString() }
val values = reversed.map { it.focusTime }
val values = reversed.map { it.totalFocusTime() }
lastMonthSummary.first.runTransaction {
columnSeries { series(values) }
extras { it[lastMonthSummary.second] = keys }
@@ -146,11 +176,11 @@ class StatsViewModel(
)
val lastYearSummaryChartData: StateFlow<Pair<CartesianChartModelProducer, ExtraStore.Key<List<String>>>> =
statRepository.getLastNDaysStatsSummary(365)
statRepository.getLastNDaysStats(365)
.map { list ->
val reversed = list.reversed()
val keys = reversed.map { it.date.format(yearDayFormatter) }
val values = reversed.map { it.focusTime }
val values = reversed.map { it.totalFocusTime() }
lastYearSummary.first.runTransaction {
lineSeries { series(values) }
extras { it[lastYearSummary.second] = keys }
@@ -195,13 +225,11 @@ class StatsViewModel(
(1 * 60 * 60 * 1000L..3 * 60 * 60 * 1000L).random(),
(0..3 * 60 * 60 * 1000L).random(),
(0..1 * 60 * 60 * 1000L).random(),
0
(0..100 * 60 * 1000L).random()
)
)
it = it.plusDays(1)
}
statRepository.addBreakTime((0..30 * 60 * 1000L).random())
}
}
}

View File

@@ -113,4 +113,5 @@
<string name="secure_aod_desc">Automatically lock your device after a timeout, while keeping the AOD visible</string>
<string name="timer_reset_message">Timer reset</string>
<string name="undo">Undo</string>
<string name="focus_break_ratio">Focus-break ratio</string>
</resources>