feat: show average focus hours within sections in stats screen

This commit is contained in:
Nishant Mishra
2025-07-13 10:20:29 +05:30
parent 7541bd84dd
commit e6e21ea7d1
4 changed files with 84 additions and 20 deletions

View File

@@ -25,7 +25,9 @@ data class Stat(
val focusTimeQ3: Long,
val focusTimeQ4: Long,
val breakTime: Long
)
) {
fun totalFocusTime() = focusTimeQ1 + focusTimeQ2 + focusTimeQ3 + focusTimeQ4
}
data class StatSummary(
val date: LocalDate,
@@ -38,4 +40,6 @@ data class StatFocusTime(
val focusTimeQ2: Long,
val focusTimeQ3: Long,
val focusTimeQ4: Long
)
) {
fun total() = focusTimeQ1 + focusTimeQ2 + focusTimeQ3 + focusTimeQ4
}

View File

@@ -33,7 +33,6 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -48,6 +47,7 @@ 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
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModelProducer
import com.patrykandpatrick.vico.core.cartesian.data.CartesianValueFormatter
@@ -56,6 +56,7 @@ 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
@@ -66,13 +67,20 @@ fun StatsScreenRoot(
modifier: Modifier = Modifier,
viewModel: StatsViewModel = viewModel(factory = StatsViewModel.Factory)
) {
val todayStat by viewModel.todayStat.collectAsState(null)
val todayStat by viewModel.todayStat.collectAsStateWithLifecycle(null)
val lastWeekAverageFocusTimes by viewModel
.lastWeekAverageFocusTimes.collectAsStateWithLifecycle(null)
val lastMonthAverageFocusTimes by viewModel
.lastMonthAverageFocusTimes.collectAsStateWithLifecycle(null)
StatsScreen(
lastWeekSummaryChartData = remember { viewModel.lastWeekSummaryChartData },
lastWeekSummaryAnalysisModelProducer = remember { viewModel.lastWeekSummaryAnalysisModelProducer },
lastMonthSummaryChartData = remember { viewModel.lastMonthSummaryChartData },
lastMonthSummaryAnalysisModelProducer = remember { viewModel.lastMonthSummaryAnalysisModelProducer },
todayStat = todayStat,
lastWeekAverageFocusTimes = lastWeekAverageFocusTimes,
lastMonthAverageFocusTimes = lastMonthAverageFocusTimes,
modifier = modifier
)
}
@@ -85,6 +93,8 @@ fun StatsScreen(
lastMonthSummaryChartData: Pair<CartesianChartModelProducer, ExtraStore.Key<List<String>>>,
lastMonthSummaryAnalysisModelProducer: CartesianChartModelProducer,
todayStat: Stat?,
lastWeekAverageFocusTimes: StatFocusTime?,
lastMonthAverageFocusTimes: StatFocusTime?,
modifier: Modifier = Modifier
) {
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
@@ -143,14 +153,9 @@ fun StatsScreen(
color = colorScheme.onPrimaryContainer
)
Text(
if (todayStat != null) remember(todayStat) {
millisecondsToHoursMinutes(
todayStat.focusTimeQ1 +
todayStat.focusTimeQ2 +
todayStat.focusTimeQ3 +
todayStat.focusTimeQ4
)
} else "0h 0m",
remember(todayStat) {
millisecondsToHoursMinutes(todayStat?.totalFocusTime() ?: 0)
},
style = typography.displaySmall,
fontFamily = openRundeClock,
color = colorScheme.onPrimaryContainer
@@ -173,9 +178,9 @@ fun StatsScreen(
color = colorScheme.onTertiaryContainer
)
Text(
if (todayStat != null) remember(todayStat) {
millisecondsToHoursMinutes(todayStat.breakTime)
} else "0h 0m",
remember(todayStat) {
millisecondsToHoursMinutes(todayStat?.breakTime ?: 0)
},
style = typography.displaySmall,
fontFamily = openRundeClock,
color = colorScheme.onTertiaryContainer
@@ -194,6 +199,26 @@ fun StatsScreen(
.padding(horizontal = 16.dp)
)
}
item {
Row(
verticalAlignment = Alignment.Bottom,
horizontalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
) {
Text(
millisecondsToHoursMinutes(lastWeekAverageFocusTimes?.total() ?: 0),
style = typography.displaySmall,
fontFamily = openRundeClock
)
Text(
"focus per day (avg)",
style = typography.titleSmall,
modifier = Modifier.padding(bottom = 6.3.dp)
)
}
}
item {
TimeColumnChart(
lastWeekSummaryChartData.first,
@@ -246,6 +271,26 @@ fun StatsScreen(
.padding(horizontal = 16.dp)
)
}
item {
Row(
verticalAlignment = Alignment.Bottom,
horizontalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
) {
Text(
millisecondsToHoursMinutes(lastMonthAverageFocusTimes?.total() ?: 0),
style = typography.displaySmall,
fontFamily = openRundeClock
)
Text(
"focus per day (avg)",
style = typography.titleSmall,
modifier = Modifier.padding(bottom = 6.3.dp)
)
}
}
item {
TimeColumnChart(
lastMonthSummaryChartData.first,
@@ -315,6 +360,8 @@ fun StatsScreenPreview() {
modelProducer,
Pair(modelProducer, ExtraStore.Key()),
modelProducer,
null,
null,
null
)
}

View File

@@ -21,18 +21,20 @@ import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
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
class StatsViewModel(
statRepository: StatRepository
) : ViewModel() {
private val dayFormatter = DateTimeFormatter.ofPattern("E")
val todayStat = statRepository.getTodayStat().distinctUntilChanged()
private val lastWeekStatsSummary = statRepository.getLastNDaysStatsSummary(7)
private val lastWeekAverageFocusTimes = statRepository.getLastNDaysAverageFocusTimes(7)
val lastWeekAverageFocusTimes =
statRepository.getLastNDaysAverageFocusTimes(7).distinctUntilChanged()
private val lastMonthStatsSummary = statRepository.getLastNDaysStatsSummary(30)
private val lastMonthAverageFocusTimes = statRepository.getLastNDaysAverageFocusTimes(30)
val lastMonthAverageFocusTimes =
statRepository.getLastNDaysAverageFocusTimes(30).distinctUntilChanged()
val lastWeekSummaryChartData =
Pair(CartesianChartModelProducer(), ExtraStore.Key<List<String>>())
@@ -48,7 +50,12 @@ class StatsViewModel(
.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.format(dayFormatter) }
val keys = reversed.map {
it.date.dayOfWeek.getDisplayName(
TextStyle.NARROW,
Locale.getDefault()
)
}
val values = reversed.map { it.focusTime }
lastWeekSummaryChartData.first.runTransaction {
columnSeries { series(values) }

View File

@@ -39,6 +39,12 @@ val Typography = Typography(
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.15.sp,
),
titleSmall = TextStyle(
fontFamily = robotoFlexTitle,
fontSize = 14.sp,
lineHeight = 20.sp,
letterSpacing = 0.1.sp
)
)