feat: show average focus hours within sections in stats screen
This commit is contained in:
@@ -25,7 +25,9 @@ data class Stat(
|
|||||||
val focusTimeQ3: Long,
|
val focusTimeQ3: Long,
|
||||||
val focusTimeQ4: Long,
|
val focusTimeQ4: Long,
|
||||||
val breakTime: Long
|
val breakTime: Long
|
||||||
)
|
) {
|
||||||
|
fun totalFocusTime() = focusTimeQ1 + focusTimeQ2 + focusTimeQ3 + focusTimeQ4
|
||||||
|
}
|
||||||
|
|
||||||
data class StatSummary(
|
data class StatSummary(
|
||||||
val date: LocalDate,
|
val date: LocalDate,
|
||||||
@@ -38,4 +40,6 @@ data class StatFocusTime(
|
|||||||
val focusTimeQ2: Long,
|
val focusTimeQ2: Long,
|
||||||
val focusTimeQ3: Long,
|
val focusTimeQ3: Long,
|
||||||
val focusTimeQ4: Long
|
val focusTimeQ4: Long
|
||||||
)
|
) {
|
||||||
|
fun total() = focusTimeQ1 + focusTimeQ2 + focusTimeQ3 + focusTimeQ4
|
||||||
|
}
|
||||||
@@ -33,7 +33,6 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
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.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModelProducer
|
import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModelProducer
|
||||||
import com.patrykandpatrick.vico.core.cartesian.data.CartesianValueFormatter
|
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 kotlinx.coroutines.runBlocking
|
||||||
import org.nsh07.pomodoro.R
|
import org.nsh07.pomodoro.R
|
||||||
import org.nsh07.pomodoro.data.Stat
|
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.statsScreen.viewModel.StatsViewModel
|
||||||
import org.nsh07.pomodoro.ui.theme.AppFonts.openRundeClock
|
import org.nsh07.pomodoro.ui.theme.AppFonts.openRundeClock
|
||||||
import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTopBar
|
import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTopBar
|
||||||
@@ -66,13 +67,20 @@ fun StatsScreenRoot(
|
|||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
viewModel: StatsViewModel = viewModel(factory = StatsViewModel.Factory)
|
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(
|
StatsScreen(
|
||||||
lastWeekSummaryChartData = remember { viewModel.lastWeekSummaryChartData },
|
lastWeekSummaryChartData = remember { viewModel.lastWeekSummaryChartData },
|
||||||
lastWeekSummaryAnalysisModelProducer = remember { viewModel.lastWeekSummaryAnalysisModelProducer },
|
lastWeekSummaryAnalysisModelProducer = remember { viewModel.lastWeekSummaryAnalysisModelProducer },
|
||||||
lastMonthSummaryChartData = remember { viewModel.lastMonthSummaryChartData },
|
lastMonthSummaryChartData = remember { viewModel.lastMonthSummaryChartData },
|
||||||
lastMonthSummaryAnalysisModelProducer = remember { viewModel.lastMonthSummaryAnalysisModelProducer },
|
lastMonthSummaryAnalysisModelProducer = remember { viewModel.lastMonthSummaryAnalysisModelProducer },
|
||||||
todayStat = todayStat,
|
todayStat = todayStat,
|
||||||
|
lastWeekAverageFocusTimes = lastWeekAverageFocusTimes,
|
||||||
|
lastMonthAverageFocusTimes = lastMonthAverageFocusTimes,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -85,6 +93,8 @@ fun StatsScreen(
|
|||||||
lastMonthSummaryChartData: Pair<CartesianChartModelProducer, ExtraStore.Key<List<String>>>,
|
lastMonthSummaryChartData: Pair<CartesianChartModelProducer, ExtraStore.Key<List<String>>>,
|
||||||
lastMonthSummaryAnalysisModelProducer: CartesianChartModelProducer,
|
lastMonthSummaryAnalysisModelProducer: CartesianChartModelProducer,
|
||||||
todayStat: Stat?,
|
todayStat: Stat?,
|
||||||
|
lastWeekAverageFocusTimes: StatFocusTime?,
|
||||||
|
lastMonthAverageFocusTimes: StatFocusTime?,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
|
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
|
||||||
@@ -143,14 +153,9 @@ fun StatsScreen(
|
|||||||
color = colorScheme.onPrimaryContainer
|
color = colorScheme.onPrimaryContainer
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
if (todayStat != null) remember(todayStat) {
|
remember(todayStat) {
|
||||||
millisecondsToHoursMinutes(
|
millisecondsToHoursMinutes(todayStat?.totalFocusTime() ?: 0)
|
||||||
todayStat.focusTimeQ1 +
|
},
|
||||||
todayStat.focusTimeQ2 +
|
|
||||||
todayStat.focusTimeQ3 +
|
|
||||||
todayStat.focusTimeQ4
|
|
||||||
)
|
|
||||||
} else "0h 0m",
|
|
||||||
style = typography.displaySmall,
|
style = typography.displaySmall,
|
||||||
fontFamily = openRundeClock,
|
fontFamily = openRundeClock,
|
||||||
color = colorScheme.onPrimaryContainer
|
color = colorScheme.onPrimaryContainer
|
||||||
@@ -173,9 +178,9 @@ fun StatsScreen(
|
|||||||
color = colorScheme.onTertiaryContainer
|
color = colorScheme.onTertiaryContainer
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
if (todayStat != null) remember(todayStat) {
|
remember(todayStat) {
|
||||||
millisecondsToHoursMinutes(todayStat.breakTime)
|
millisecondsToHoursMinutes(todayStat?.breakTime ?: 0)
|
||||||
} else "0h 0m",
|
},
|
||||||
style = typography.displaySmall,
|
style = typography.displaySmall,
|
||||||
fontFamily = openRundeClock,
|
fontFamily = openRundeClock,
|
||||||
color = colorScheme.onTertiaryContainer
|
color = colorScheme.onTertiaryContainer
|
||||||
@@ -194,6 +199,26 @@ fun StatsScreen(
|
|||||||
.padding(horizontal = 16.dp)
|
.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 {
|
item {
|
||||||
TimeColumnChart(
|
TimeColumnChart(
|
||||||
lastWeekSummaryChartData.first,
|
lastWeekSummaryChartData.first,
|
||||||
@@ -246,6 +271,26 @@ fun StatsScreen(
|
|||||||
.padding(horizontal = 16.dp)
|
.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 {
|
item {
|
||||||
TimeColumnChart(
|
TimeColumnChart(
|
||||||
lastMonthSummaryChartData.first,
|
lastMonthSummaryChartData.first,
|
||||||
@@ -315,6 +360,8 @@ fun StatsScreenPreview() {
|
|||||||
modelProducer,
|
modelProducer,
|
||||||
Pair(modelProducer, ExtraStore.Key()),
|
Pair(modelProducer, ExtraStore.Key()),
|
||||||
modelProducer,
|
modelProducer,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,18 +21,20 @@ import kotlinx.coroutines.flow.distinctUntilChanged
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.nsh07.pomodoro.TomatoApplication
|
import org.nsh07.pomodoro.TomatoApplication
|
||||||
import org.nsh07.pomodoro.data.StatRepository
|
import org.nsh07.pomodoro.data.StatRepository
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.TextStyle
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
class StatsViewModel(
|
class StatsViewModel(
|
||||||
statRepository: StatRepository
|
statRepository: StatRepository
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
private val dayFormatter = DateTimeFormatter.ofPattern("E")
|
|
||||||
|
|
||||||
val todayStat = statRepository.getTodayStat().distinctUntilChanged()
|
val todayStat = statRepository.getTodayStat().distinctUntilChanged()
|
||||||
private val lastWeekStatsSummary = statRepository.getLastNDaysStatsSummary(7)
|
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 lastMonthStatsSummary = statRepository.getLastNDaysStatsSummary(30)
|
||||||
private val lastMonthAverageFocusTimes = statRepository.getLastNDaysAverageFocusTimes(30)
|
val lastMonthAverageFocusTimes =
|
||||||
|
statRepository.getLastNDaysAverageFocusTimes(30).distinctUntilChanged()
|
||||||
|
|
||||||
val lastWeekSummaryChartData =
|
val lastWeekSummaryChartData =
|
||||||
Pair(CartesianChartModelProducer(), ExtraStore.Key<List<String>>())
|
Pair(CartesianChartModelProducer(), ExtraStore.Key<List<String>>())
|
||||||
@@ -48,7 +50,12 @@ class StatsViewModel(
|
|||||||
.collect { list ->
|
.collect { list ->
|
||||||
// reversing is required because we need ascending order while the DB returns descending order
|
// reversing is required because we need ascending order while the DB returns descending order
|
||||||
val reversed = list.reversed()
|
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 }
|
val values = reversed.map { it.focusTime }
|
||||||
lastWeekSummaryChartData.first.runTransaction {
|
lastWeekSummaryChartData.first.runTransaction {
|
||||||
columnSeries { series(values) }
|
columnSeries { series(values) }
|
||||||
|
|||||||
@@ -39,6 +39,12 @@ val Typography = Typography(
|
|||||||
fontSize = 16.sp,
|
fontSize = 16.sp,
|
||||||
lineHeight = 24.sp,
|
lineHeight = 24.sp,
|
||||||
letterSpacing = 0.15.sp,
|
letterSpacing = 0.15.sp,
|
||||||
|
),
|
||||||
|
titleSmall = TextStyle(
|
||||||
|
fontFamily = robotoFlexTitle,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
lineHeight = 20.sp,
|
||||||
|
letterSpacing = 0.1.sp
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user