refactor(internal): Add TypeConvertor for LocalDate to directly handle LocalDate in database
This commit is contained in:
@@ -11,11 +11,13 @@ import android.content.Context
|
||||
import androidx.room.Database
|
||||
import androidx.room.Room
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.room.TypeConverters
|
||||
|
||||
@Database(
|
||||
entities = [IntPreference::class, Stat::class],
|
||||
version = 1
|
||||
)
|
||||
@TypeConverters(Converters::class)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
|
||||
abstract fun preferenceDao(): PreferenceDao
|
||||
|
||||
23
app/src/main/java/org/nsh07/pomodoro/data/Converters.kt
Normal file
23
app/src/main/java/org/nsh07/pomodoro/data/Converters.kt
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package org.nsh07.pomodoro.data
|
||||
|
||||
import androidx.room.TypeConverter
|
||||
import java.time.LocalDate
|
||||
|
||||
class Converters {
|
||||
@TypeConverter
|
||||
fun localDateToString(localDate: LocalDate?): String? {
|
||||
return localDate?.toString()
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun stringToLocalDate(date: String?): LocalDate? {
|
||||
return if (date != null) LocalDate.parse(date) else null
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ package org.nsh07.pomodoro.data
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import java.time.LocalDate
|
||||
|
||||
/**
|
||||
* Data class for storing the user's statistics in the app's database. This class stores the focus
|
||||
@@ -18,7 +19,7 @@ import androidx.room.PrimaryKey
|
||||
@Entity(tableName = "stat")
|
||||
data class Stat(
|
||||
@PrimaryKey
|
||||
val date: String,
|
||||
val date: LocalDate,
|
||||
val focusTimeQ1: Long,
|
||||
val focusTimeQ2: Long,
|
||||
val focusTimeQ3: Long,
|
||||
@@ -27,7 +28,7 @@ data class Stat(
|
||||
)
|
||||
|
||||
data class StatSummary(
|
||||
val date: String,
|
||||
val date: LocalDate,
|
||||
val focusTime: Long,
|
||||
val breakTime: Long
|
||||
)
|
||||
|
||||
@@ -12,6 +12,7 @@ import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy.Companion.REPLACE
|
||||
import androidx.room.Query
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import java.time.LocalDate
|
||||
|
||||
@Dao
|
||||
interface StatDao {
|
||||
@@ -19,22 +20,22 @@ interface StatDao {
|
||||
suspend fun insertStat(stat: Stat)
|
||||
|
||||
@Query("UPDATE stat SET focusTimeQ1 = focusTimeQ1 + :focusTime WHERE date = :date")
|
||||
suspend fun addFocusTimeQ1(date: String, focusTime: Long)
|
||||
suspend fun addFocusTimeQ1(date: LocalDate, focusTime: Long)
|
||||
|
||||
@Query("UPDATE stat SET focusTimeQ2 = focusTimeQ2 + :focusTime WHERE date = :date")
|
||||
suspend fun addFocusTimeQ2(date: String, focusTime: Long)
|
||||
suspend fun addFocusTimeQ2(date: LocalDate, focusTime: Long)
|
||||
|
||||
@Query("UPDATE stat SET focusTimeQ3 = focusTimeQ3 + :focusTime WHERE date = :date")
|
||||
suspend fun addFocusTimeQ3(date: String, focusTime: Long)
|
||||
suspend fun addFocusTimeQ3(date: LocalDate, focusTime: Long)
|
||||
|
||||
@Query("UPDATE stat SET focusTimeQ4 = focusTimeQ4 + :focusTime WHERE date = :date")
|
||||
suspend fun addFocusTimeQ4(date: String, focusTime: Long)
|
||||
suspend fun addFocusTimeQ4(date: LocalDate, focusTime: Long)
|
||||
|
||||
@Query("UPDATE stat SET breakTime = breakTime + :breakTime WHERE date = :date")
|
||||
suspend fun addBreakTime(date: String, breakTime: Long)
|
||||
suspend fun addBreakTime(date: LocalDate, breakTime: Long)
|
||||
|
||||
@Query("SELECT * FROM stat WHERE date = :date")
|
||||
fun getStat(date: String): Flow<Stat?>
|
||||
fun getStat(date: LocalDate): Flow<Stat?>
|
||||
|
||||
@Query("SELECT date, focusTimeQ1 + focusTimeQ2 + focusTimeQ3 + focusTimeQ4 as focusTime, breakTime FROM stat ORDER BY date DESC LIMIT 7")
|
||||
fun getLastWeekStatsSummary(): Flow<List<StatSummary>>
|
||||
@@ -43,8 +44,8 @@ interface StatDao {
|
||||
fun getAvgFocusTimes(): Flow<StatFocusTime?>
|
||||
|
||||
@Query("SELECT EXISTS (SELECT * FROM stat WHERE date = :date)")
|
||||
suspend fun statExists(date: String): Boolean
|
||||
suspend fun statExists(date: LocalDate): Boolean
|
||||
|
||||
@Query("SELECT date FROM stat ORDER BY date DESC LIMIT 1")
|
||||
suspend fun getLastDate(): String?
|
||||
suspend fun getLastDate(): LocalDate?
|
||||
}
|
||||
@@ -32,7 +32,7 @@ interface StatRepository {
|
||||
|
||||
fun getAverageFocusTimes(): Flow<StatFocusTime?>
|
||||
|
||||
suspend fun getLastDate(): String?
|
||||
suspend fun getLastDate(): LocalDate?
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -45,7 +45,7 @@ class AppStatRepository(
|
||||
override suspend fun insertStat(stat: Stat) = statDao.insertStat(stat)
|
||||
|
||||
override suspend fun addFocusTime(focusTime: Long) = withContext(ioDispatcher) {
|
||||
val currentDate = LocalDate.now().toString()
|
||||
val currentDate = LocalDate.now()
|
||||
val currentTime = LocalTime.now().toSecondOfDay()
|
||||
val secondsInDay = 24 * 60 * 60
|
||||
|
||||
@@ -88,7 +88,7 @@ class AppStatRepository(
|
||||
}
|
||||
|
||||
override suspend fun addBreakTime(breakTime: Long) = withContext(ioDispatcher) {
|
||||
val currentDate = LocalDate.now().toString()
|
||||
val currentDate = LocalDate.now()
|
||||
if (statDao.statExists(currentDate)) {
|
||||
statDao.addBreakTime(currentDate, breakTime)
|
||||
} else {
|
||||
@@ -97,7 +97,7 @@ class AppStatRepository(
|
||||
}
|
||||
|
||||
override fun getTodayStat(): Flow<Stat?> {
|
||||
val currentDate = LocalDate.now().toString()
|
||||
val currentDate = LocalDate.now()
|
||||
return statDao.getStat(currentDate)
|
||||
}
|
||||
|
||||
@@ -106,5 +106,5 @@ class AppStatRepository(
|
||||
|
||||
override fun getAverageFocusTimes(): Flow<StatFocusTime?> = statDao.getAvgFocusTimes()
|
||||
|
||||
override suspend fun getLastDate(): String? = statDao.getLastDate()
|
||||
override suspend fun getLastDate(): LocalDate? = statDao.getLastDate()
|
||||
}
|
||||
@@ -157,7 +157,13 @@ fun AppScreen(
|
||||
}
|
||||
|
||||
entry<Screen.Stats> {
|
||||
StatsScreenRoot()
|
||||
StatsScreenRoot(
|
||||
modifier = modifier.padding(
|
||||
start = contentPadding.calculateStartPadding(layoutDirection),
|
||||
end = contentPadding.calculateEndPadding(layoutDirection),
|
||||
bottom = contentPadding.calculateBottomPadding()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package org.nsh07.pomodoro.ui.statsScreen
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.material3.MaterialTheme.typography
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModelProducer
|
||||
import com.patrykandpatrick.vico.core.cartesian.data.CartesianValueFormatter
|
||||
import org.nsh07.pomodoro.utils.millisecondsToHoursMinutes
|
||||
|
||||
@Composable
|
||||
fun ColumnScope.ProductivityGraph(
|
||||
expanded: Boolean,
|
||||
modelProducer: CartesianChartModelProducer,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
AnimatedVisibility(expanded) {
|
||||
Column(modifier = modifier) {
|
||||
Text("Productivity analysis", style = typography.titleMedium)
|
||||
Text("Time of day versus focus hours", style = typography.bodySmall)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
TimeColumnChart(
|
||||
modelProducer,
|
||||
yValueFormatter = CartesianValueFormatter { _, value, _ ->
|
||||
millisecondsToHoursMinutes(value.toLong())
|
||||
},
|
||||
xValueFormatter = CartesianValueFormatter { _, value, _ ->
|
||||
when (value) {
|
||||
0.0 -> "0 - 6"
|
||||
1.0 -> "6 - 12"
|
||||
2.0 -> "12 - 18"
|
||||
3.0 -> "18 - 24"
|
||||
else -> ""
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
package org.nsh07.pomodoro.ui.statsScreen
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@@ -31,6 +30,7 @@ import androidx.compose.material3.MaterialTheme.motionScheme
|
||||
import androidx.compose.material3.MaterialTheme.typography
|
||||
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
|
||||
@@ -41,6 +41,7 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Devices
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
@@ -64,7 +65,7 @@ fun StatsScreenRoot(
|
||||
) {
|
||||
val todayStat by viewModel.todayStat.collectAsState(null)
|
||||
StatsScreen(
|
||||
allStatsSummaryModelProducer = viewModel.allStatsSummaryModelProducer,
|
||||
lastWeekSummaryModelProducer = viewModel.lastWeekSummaryChartModelProducer,
|
||||
todayStatModelProducer = viewModel.todayStatModelProducer,
|
||||
todayStat = todayStat,
|
||||
modifier = modifier
|
||||
@@ -74,150 +75,150 @@ fun StatsScreenRoot(
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun StatsScreen(
|
||||
allStatsSummaryModelProducer: CartesianChartModelProducer,
|
||||
lastWeekSummaryModelProducer: CartesianChartModelProducer,
|
||||
todayStatModelProducer: CartesianChartModelProducer,
|
||||
todayStat: Stat?,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
||||
|
||||
var todayStatExpanded by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
LazyColumn(
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = modifier
|
||||
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||
) {
|
||||
item {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Text(
|
||||
"Stats",
|
||||
style = LocalTextStyle.current.copy(
|
||||
fontFamily = robotoFlexTopBar,
|
||||
fontSize = 32.sp,
|
||||
lineHeight = 32.sp
|
||||
)
|
||||
TopAppBar(
|
||||
title = {
|
||||
Text(
|
||||
"Stats",
|
||||
style = LocalTextStyle.current.copy(
|
||||
fontFamily = robotoFlexTopBar,
|
||||
fontSize = 32.sp,
|
||||
lineHeight = 32.sp
|
||||
)
|
||||
},
|
||||
subtitle = {},
|
||||
titleHorizontalAlignment = Alignment.CenterHorizontally
|
||||
)
|
||||
}
|
||||
item {
|
||||
Text(
|
||||
"Today",
|
||||
style = typography.headlineSmall,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp)
|
||||
)
|
||||
Spacer(Modifier.height(16.dp))
|
||||
}
|
||||
item {
|
||||
Row(modifier = Modifier.padding(horizontal = 16.dp)) {
|
||||
Box(
|
||||
)
|
||||
},
|
||||
subtitle = {},
|
||||
titleHorizontalAlignment = Alignment.CenterHorizontally,
|
||||
scrollBehavior = scrollBehavior
|
||||
)
|
||||
|
||||
LazyColumn(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
item {
|
||||
Text(
|
||||
"Today",
|
||||
style = typography.headlineSmall,
|
||||
modifier = Modifier
|
||||
.background(
|
||||
colorScheme.primaryContainer,
|
||||
MaterialTheme.shapes.largeIncreased
|
||||
)
|
||||
.weight(1f)
|
||||
) {
|
||||
Column(Modifier.padding(16.dp)) {
|
||||
Text(
|
||||
"Focus",
|
||||
style = typography.titleMedium,
|
||||
color = colorScheme.onPrimaryContainer
|
||||
)
|
||||
Text(
|
||||
if (todayStat != null) remember(todayStat) {
|
||||
millisecondsToHoursMinutes(
|
||||
todayStat.focusTimeQ1 +
|
||||
todayStat.focusTimeQ2 +
|
||||
todayStat.focusTimeQ3 +
|
||||
todayStat.focusTimeQ4
|
||||
)
|
||||
} else "0h 0m",
|
||||
style = typography.displaySmall,
|
||||
fontFamily = openRundeClock,
|
||||
color = colorScheme.onPrimaryContainer
|
||||
)
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
)
|
||||
}
|
||||
item {
|
||||
Row(modifier = Modifier.padding(horizontal = 16.dp)) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(
|
||||
colorScheme.primaryContainer,
|
||||
MaterialTheme.shapes.largeIncreased
|
||||
)
|
||||
.weight(1f)
|
||||
) {
|
||||
Column(Modifier.padding(16.dp)) {
|
||||
Text(
|
||||
"Focus",
|
||||
style = typography.titleMedium,
|
||||
color = colorScheme.onPrimaryContainer
|
||||
)
|
||||
Text(
|
||||
if (todayStat != null) remember(todayStat) {
|
||||
millisecondsToHoursMinutes(
|
||||
todayStat.focusTimeQ1 +
|
||||
todayStat.focusTimeQ2 +
|
||||
todayStat.focusTimeQ3 +
|
||||
todayStat.focusTimeQ4
|
||||
)
|
||||
} else "0h 0m",
|
||||
style = typography.displaySmall,
|
||||
fontFamily = openRundeClock,
|
||||
color = colorScheme.onPrimaryContainer
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(
|
||||
colorScheme.tertiaryContainer,
|
||||
MaterialTheme.shapes.largeIncreased
|
||||
)
|
||||
.weight(1f)
|
||||
) {
|
||||
Column(Modifier.padding(16.dp)) {
|
||||
Text(
|
||||
"Break",
|
||||
style = typography.titleMedium,
|
||||
color = colorScheme.onTertiaryContainer
|
||||
)
|
||||
Text(
|
||||
if (todayStat != null) remember(todayStat) {
|
||||
millisecondsToHoursMinutes(todayStat.breakTime)
|
||||
} else "0h 0m",
|
||||
style = typography.displaySmall,
|
||||
fontFamily = openRundeClock,
|
||||
color = colorScheme.onTertiaryContainer
|
||||
)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(
|
||||
colorScheme.tertiaryContainer,
|
||||
MaterialTheme.shapes.largeIncreased
|
||||
)
|
||||
.weight(1f)
|
||||
) {
|
||||
Column(Modifier.padding(16.dp)) {
|
||||
Text(
|
||||
"Break",
|
||||
style = typography.titleMedium,
|
||||
color = colorScheme.onTertiaryContainer
|
||||
)
|
||||
Text(
|
||||
if (todayStat != null) remember(todayStat) {
|
||||
millisecondsToHoursMinutes(todayStat.breakTime)
|
||||
} else "0h 0m",
|
||||
style = typography.displaySmall,
|
||||
fontFamily = openRundeClock,
|
||||
color = colorScheme.onTertiaryContainer
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
item {
|
||||
val iconRotation by animateFloatAsState(
|
||||
if (todayStatExpanded) 180f else 0f,
|
||||
animationSpec = motionScheme.defaultSpatialSpec()
|
||||
)
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
FilledTonalIconToggleButton(
|
||||
checked = todayStatExpanded,
|
||||
onCheckedChange = { todayStatExpanded = it },
|
||||
shapes = IconButtonDefaults.toggleableShapes(),
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.width(52.dp)
|
||||
.align(Alignment.End)
|
||||
item {
|
||||
val iconRotation by animateFloatAsState(
|
||||
if (todayStatExpanded) 180f else 0f,
|
||||
animationSpec = motionScheme.defaultSpatialSpec()
|
||||
)
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Icon(
|
||||
painterResource(R.drawable.arrow_down),
|
||||
"More info",
|
||||
modifier = Modifier.rotate(iconRotation)
|
||||
)
|
||||
}
|
||||
AnimatedVisibility(todayStatExpanded) {
|
||||
TimeColumnChart(
|
||||
FilledTonalIconToggleButton(
|
||||
checked = todayStatExpanded,
|
||||
onCheckedChange = { todayStatExpanded = it },
|
||||
shapes = IconButtonDefaults.toggleableShapes(),
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.width(52.dp)
|
||||
.align(Alignment.End)
|
||||
) {
|
||||
Icon(
|
||||
painterResource(R.drawable.arrow_down),
|
||||
"More info",
|
||||
modifier = Modifier.rotate(iconRotation)
|
||||
)
|
||||
}
|
||||
ProductivityGraph(
|
||||
todayStatExpanded,
|
||||
todayStatModelProducer,
|
||||
timeConverter = ::millisecondsToHoursMinutes,
|
||||
modifier = Modifier.padding(start = 16.dp)
|
||||
Modifier.padding(horizontal = 32.dp)
|
||||
)
|
||||
}
|
||||
Spacer(Modifier.height(16.dp))
|
||||
}
|
||||
item {
|
||||
Text(
|
||||
"This week",
|
||||
style = typography.headlineSmall,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
)
|
||||
}
|
||||
item {
|
||||
TimeColumnChart(
|
||||
lastWeekSummaryModelProducer,
|
||||
modifier = Modifier.padding(start = 16.dp)
|
||||
)
|
||||
}
|
||||
Spacer(Modifier.height(16.dp))
|
||||
}
|
||||
item {
|
||||
Text(
|
||||
"This week",
|
||||
style = typography.headlineSmall,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp)
|
||||
)
|
||||
Spacer(Modifier.height(16.dp))
|
||||
}
|
||||
item {
|
||||
TimeColumnChart(
|
||||
allStatsSummaryModelProducer,
|
||||
modifier = Modifier.padding(start = 16.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,11 @@ internal fun TimeColumnChart(
|
||||
modelProducer: CartesianChartModelProducer,
|
||||
modifier: Modifier = Modifier,
|
||||
thickness: Dp = 40.dp,
|
||||
timeConverter: (Long) -> String = ::millisecondsToHours
|
||||
columnCollectionSpacing: Dp = 4.dp,
|
||||
xValueFormatter: CartesianValueFormatter = CartesianValueFormatter.Default,
|
||||
yValueFormatter: CartesianValueFormatter = CartesianValueFormatter { measuringContext, value, _ ->
|
||||
millisecondsToHours(value.toLong())
|
||||
}
|
||||
) {
|
||||
val radius = with(LocalDensity.current) {
|
||||
(thickness / 2).toPx()
|
||||
@@ -82,20 +86,19 @@ internal fun TimeColumnChart(
|
||||
)
|
||||
}
|
||||
),
|
||||
columnCollectionSpacing = 4.dp
|
||||
columnCollectionSpacing = columnCollectionSpacing
|
||||
),
|
||||
startAxis = VerticalAxis.rememberStart(
|
||||
line = rememberLineComponent(Fill.Transparent),
|
||||
tick = rememberLineComponent(Fill.Transparent),
|
||||
guideline = rememberLineComponent(Fill.Transparent),
|
||||
valueFormatter = CartesianValueFormatter { measuringContext, value, _ ->
|
||||
timeConverter(value.toLong())
|
||||
}
|
||||
valueFormatter = yValueFormatter
|
||||
),
|
||||
bottomAxis = HorizontalAxis.rememberBottom(
|
||||
rememberLineComponent(Fill.Transparent),
|
||||
tick = rememberLineComponent(Fill.Transparent),
|
||||
guideline = rememberLineComponent(Fill.Transparent)
|
||||
guideline = rememberLineComponent(Fill.Transparent),
|
||||
valueFormatter = xValueFormatter
|
||||
)
|
||||
),
|
||||
modelProducer = modelProducer,
|
||||
|
||||
@@ -28,15 +28,18 @@ class StatsViewModel(
|
||||
private val allStatsSummary = statRepository.getLastWeekStatsSummary()
|
||||
private val averageFocusTimes = statRepository.getAverageFocusTimes()
|
||||
|
||||
val allStatsSummaryModelProducer = CartesianChartModelProducer()
|
||||
val lastWeekSummaryChartModelProducer = CartesianChartModelProducer()
|
||||
val todayStatModelProducer = CartesianChartModelProducer()
|
||||
|
||||
init {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
allStatsSummary
|
||||
.collect { list ->
|
||||
allStatsSummaryModelProducer.runTransaction {
|
||||
columnSeries { series(list.reversed().map { it.focusTime }) }
|
||||
lastWeekSummaryChartModelProducer.runTransaction {
|
||||
columnSeries {
|
||||
// reversing is required because we need ascending order while the DB returns descending order
|
||||
series(list.reversed().map { it.focusTime })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,13 +85,15 @@ class TimerViewModel(
|
||||
|
||||
resetTimer()
|
||||
|
||||
var lastDate = LocalDate.parse(statRepository.getLastDate())
|
||||
var lastDate = statRepository.getLastDate()
|
||||
val today = LocalDate.now()
|
||||
|
||||
// Fills dates between today and lastDate with 0s to ensure continuous history
|
||||
while (lastDate.until(today).days > 0) {
|
||||
lastDate = lastDate.plusDays(1)
|
||||
statRepository.insertStat(Stat(lastDate.toString(), 0, 0, 0, 0, 0))
|
||||
lastDate?.until(today)?.days?.let {
|
||||
while (it > 0) {
|
||||
lastDate = lastDate?.plusDays(1)
|
||||
statRepository.insertStat(Stat(lastDate!!, 0, 0, 0, 0, 0))
|
||||
}
|
||||
}
|
||||
|
||||
delay(1500)
|
||||
|
||||
Reference in New Issue
Block a user