refactor(stats): refactor heatmap for upcoming tooltip feature
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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<List<Long>?>,
|
||||
data: List<Stat?>,
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<List<Long>, Long>,
|
||||
focusHeatmapData: List<List<Long>?>,
|
||||
focusHeatmapData: List<Stat?>,
|
||||
heatmapMaxValue: Long,
|
||||
mainChartData: Pair<CartesianChartModelProducer, ExtraStore.Key<List<String>>>,
|
||||
onBack: () -> Unit,
|
||||
|
||||
@@ -206,7 +206,7 @@ class StatsViewModel(
|
||||
initialValue = lastYearSummary
|
||||
)
|
||||
|
||||
val lastYearFocusHeatmapData: StateFlow<List<List<Long>?>> =
|
||||
val lastYearFocusHeatmapData: StateFlow<List<Stat?>> =
|
||||
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])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user