diff --git a/app/src/main/java/org/nsh07/pomodoro/data/Stat.kt b/app/src/main/java/org/nsh07/pomodoro/data/Stat.kt index b97a564..04fee02 100644 --- a/app/src/main/java/org/nsh07/pomodoro/data/Stat.kt +++ b/app/src/main/java/org/nsh07/pomodoro/data/Stat.kt @@ -17,6 +17,7 @@ package org.nsh07.pomodoro.data +import androidx.compose.runtime.Immutable import androidx.room.Entity import androidx.room.PrimaryKey import java.time.LocalDate @@ -26,6 +27,7 @@ import java.time.LocalDate * durations for the 4 quarters of a day (00:00 - 12:00, 12:00 - 16:00, 16:00 - 20:00, 20:00 - 00:00) * separately for later analysis (e.g. for showing which parts of the day are most productive). */ +@Immutable @Entity(tableName = "stat") data class Stat( @PrimaryKey diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/components/visualizations.kt b/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/components/visualizations.kt index 74d8426..70ec661 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/components/visualizations.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/components/visualizations.kt @@ -59,9 +59,11 @@ import androidx.compose.ui.util.fastForEachIndexed import androidx.compose.ui.util.fastMaxBy import androidx.compose.ui.window.Popup import org.nsh07.pomodoro.R +import org.nsh07.pomodoro.data.Stat import org.nsh07.pomodoro.ui.theme.TomatoTheme import org.nsh07.pomodoro.utils.millisecondsToHoursMinutes import org.nsh07.pomodoro.utils.millisecondsToMinutes +import java.time.DayOfWeek import java.time.LocalDate import java.time.format.TextStyle import java.util.Locale @@ -271,45 +273,36 @@ val HEATMAP_CELL_SIZE = 28.dp val HEATMAP_CELL_GAP = 2.dp /** - * A horizontally scrollable heatmap with week labels in the first column + * A horizontally scrollable heatmap with persistent week labels in the first column * - * @param data Data to be represented in the heatmap in the form of [Pair]s of [LocalDate]s and - * their corresponding focus durations as a list. A null value passed in the list can be used to - * insert gaps in the heatmap, and can be used to, for example, delimit months by inserting an - * empty week + * @param data Data to be represented in the heatmap as a [List] of [Stat] objects. A null value + * passed in the list can be used to insert gaps in the heatmap, and can be used to, for example, + * delimit months by inserting a null week. Note that it is assumed that the dates are continuous + * (without gaps) and start with a Monday. * @param modifier Modifier to be applied to the heatmap - * @param maxValue Maximum total value of the items present in [data]. This value must correspond to - * the sum of the list present in one of the elements on [data] for accurate representation. - * - * Note that it is assumed that the dates are continuous (without gaps) and start with a Monday + * @param maxValue Maximum total focus duration of the items present in [data]. This value must + * correspond to the total focus duration one of the elements in [data] for accurate representation. */ @Composable fun HeatmapWithWeekLabels( - data: List?>, + data: List, modifier: Modifier = Modifier, size: Dp = HEATMAP_CELL_SIZE, gap: Dp = HEATMAP_CELL_GAP, contentPadding: PaddingValues = PaddingValues(), - maxValue: Long = remember { data.fastMaxBy { it?.sum() ?: 0 }?.sum() ?: 0 }, + maxValue: Long = remember { + data.fastMaxBy { it?.totalFocusTime() ?: 0 }?.totalFocusTime() ?: 0 + }, ) { val locale = Locale.getDefault() val shapes = shapes - val first7 = remember(locale) { - val monday = LocalDate.of(2024, 1, 1) // Monday - - buildList { - repeat(7) { - add( - monday - .plusDays(it.toLong()) - .dayOfWeek - .getDisplayName( - TextStyle.NARROW, - locale - ) - ) - } + val daysOfWeek = remember(locale) { + DayOfWeek.entries.map { + it.getDisplayName( + TextStyle.NARROW, + locale + ) } } // Names of the 7 days of the week in the current locale @@ -317,7 +310,7 @@ fun HeatmapWithWeekLabels( Column( verticalArrangement = Arrangement.spacedBy(gap), ) { - first7.fastForEach { + daysOfWeek.fastForEach { Box( contentAlignment = Alignment.Center, modifier = Modifier.size(size) @@ -338,11 +331,13 @@ fun HeatmapWithWeekLabels( verticalArrangement = Arrangement.spacedBy(gap), horizontalArrangement = Arrangement.spacedBy(gap) ) { - itemsIndexed(data) { index, it -> + itemsIndexed( + data, + key = { index, it -> it?.date?.toEpochDay() ?: index.toString() }) { index, it -> if (it == null) { Spacer(Modifier.size(size)) } else { - val sum = remember { it.sum().toFloat() } + val sum = remember { it.totalFocusTime().toFloat() } val shape = remember { val top = data.getOrNull(index - 1) != null && index % 7 != 0 @@ -411,12 +406,12 @@ fun HeatmapWithWeekLabelsPreview() { buildList { (0..93).forEach { index -> val date = startDate.plusDays(index.toLong()) - val focusDurations = listOf(index % 10L / 2) // Varying focus durations + val focusStat = Stat(date, index % 10L / 2, 0, 0, 0, 0) // Varying focus durations if (date.month != date.minusDays(1).month && index > 0) repeat(7) { add(null) } - add(focusDurations) + add(focusStat) } } } diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/screens/LastYearScreen.kt b/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/screens/LastYearScreen.kt index 422eee7..0573306 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/screens/LastYearScreen.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/screens/LastYearScreen.kt @@ -69,6 +69,7 @@ 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 org.nsh07.pomodoro.R +import org.nsh07.pomodoro.data.Stat import org.nsh07.pomodoro.ui.mergePaddingValues import org.nsh07.pomodoro.ui.statsScreen.components.FocusBreakRatioVisualization import org.nsh07.pomodoro.ui.statsScreen.components.FocusBreakdownChart @@ -88,7 +89,7 @@ import org.nsh07.pomodoro.utils.millisecondsToMinutes fun SharedTransitionScope.LastYearScreen( contentPadding: PaddingValues, focusBreakdownValues: Pair, Long>, - focusHeatmapData: List?>, + focusHeatmapData: List, heatmapMaxValue: Long, mainChartData: Pair>>, onBack: () -> Unit, 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 0baa281..7dee21b 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 @@ -206,7 +206,7 @@ class StatsViewModel( initialValue = lastYearSummary ) - val lastYearFocusHeatmapData: StateFlow?>> = + val lastYearFocusHeatmapData: StateFlow> = lastYearStatsFlow .map { list -> val list = list.reversed() @@ -224,7 +224,7 @@ class StatsViewModel( repeat(7) { add(null) } // Add a week gap if a new month starts } with(list[it]) { - add(listOf(focusTimeQ1, focusTimeQ2, focusTimeQ3, focusTimeQ4)) + add(list[it]) } } }