diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt
index 60d29b5..496b3ef 100644
--- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt
+++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt
@@ -1,3 +1,10 @@
+/*
+ * Copyright (c) 2025 Nishant Mishra
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
package org.nsh07.pomodoro.ui.settingsScreen
import androidx.compose.animation.AnimatedVisibility
@@ -52,7 +59,7 @@ import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import org.nsh07.pomodoro.R
import org.nsh07.pomodoro.ui.settingsScreen.viewModel.SettingsViewModel
-import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTitle
+import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTopBar
import org.nsh07.pomodoro.ui.theme.TomatoTheme
@OptIn(ExperimentalMaterial3Api::class)
@@ -106,7 +113,7 @@ private fun SettingsScreen(
Text(
"Settings",
style = LocalTextStyle.current.copy(
- fontFamily = robotoFlexTitle,
+ fontFamily = robotoFlexTopBar,
fontSize = 32.sp,
lineHeight = 32.sp
)
diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/StatsScreen.kt b/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/StatsScreen.kt
index 7317e54..7cd8f25 100644
--- a/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/StatsScreen.kt
+++ b/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/StatsScreen.kt
@@ -7,18 +7,41 @@
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
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.FilledTonalIconToggleButton
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.LocalTextStyle
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.MaterialTheme.colorScheme
+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.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+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.res.painterResource
import androidx.compose.ui.tooling.preview.Devices
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@@ -27,17 +50,23 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModelProducer
import com.patrykandpatrick.vico.core.cartesian.data.columnSeries
import kotlinx.coroutines.runBlocking
+import org.nsh07.pomodoro.R
+import org.nsh07.pomodoro.data.Stat
import org.nsh07.pomodoro.ui.statsScreen.viewModel.StatsViewModel
-import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTitle
+import org.nsh07.pomodoro.ui.theme.AppFonts.openRundeClock
+import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTopBar
+import org.nsh07.pomodoro.utils.millisecondsToHoursMinutes
@Composable
fun StatsScreenRoot(
modifier: Modifier = Modifier,
viewModel: StatsViewModel = viewModel(factory = StatsViewModel.Factory)
) {
- val allStatsSummaryModelProducer = viewModel.allStatsSummaryModelProducer
+ val todayStat by viewModel.todayStat.collectAsState(null)
StatsScreen(
- allStatsSummaryModelProducer = allStatsSummaryModelProducer,
+ allStatsSummaryModelProducer = viewModel.allStatsSummaryModelProducer,
+ todayStatModelProducer = viewModel.todayStatModelProducer,
+ todayStat = todayStat,
modifier = modifier
)
}
@@ -46,25 +75,150 @@ fun StatsScreenRoot(
@Composable
fun StatsScreen(
allStatsSummaryModelProducer: CartesianChartModelProducer,
+ todayStatModelProducer: CartesianChartModelProducer,
+ todayStat: Stat?,
modifier: Modifier = Modifier
) {
- Column(modifier) {
- TopAppBar(
- title = {
- Text(
- "Stats",
- style = LocalTextStyle.current.copy(
- fontFamily = robotoFlexTitle,
- fontSize = 32.sp,
- lineHeight = 32.sp
+ var todayStatExpanded by rememberSaveable { mutableStateOf(false) }
+
+ LazyColumn(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = modifier
+ ) {
+ item {
+ TopAppBar(
+ title = {
+ Text(
+ "Stats",
+ style = LocalTextStyle.current.copy(
+ fontFamily = robotoFlexTopBar,
+ fontSize = 32.sp,
+ lineHeight = 32.sp
+ )
)
- )
- },
- subtitle = {},
- titleHorizontalAlignment = Alignment.CenterHorizontally
- )
- Text("This week", style = typography.headlineSmall, modifier = Modifier.padding(16.dp))
- TimeColumnChart(allStatsSummaryModelProducer, modifier = Modifier.padding(start = 16.dp))
+ },
+ 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(
+ 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
+ )
+ }
+ }
+ }
+ }
+ 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)
+ ) {
+ Icon(
+ painterResource(R.drawable.arrow_down),
+ "More info",
+ modifier = Modifier.rotate(iconRotation)
+ )
+ }
+ AnimatedVisibility(todayStatExpanded) {
+ TimeColumnChart(
+ todayStatModelProducer,
+ timeConverter = ::millisecondsToHoursMinutes,
+ 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)
+ )
+ }
}
}
@@ -78,11 +232,15 @@ fun StatsScreenPreview() {
runBlocking {
modelProducer.runTransaction {
- columnSeries { series(5, 6) }
+ columnSeries {
+ series(5, 6, 5, 2, 11, 8, 5, 2, 15, 11, 8, 13, 12, 10, 2, 7)
+ }
}
}
StatsScreen(
- allStatsSummaryModelProducer = modelProducer
+ modelProducer,
+ modelProducer,
+ null
)
}
diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/TimeColumnChart.kt b/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/TimeColumnChart.kt
index c49be99..1925d4f 100644
--- a/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/TimeColumnChart.kt
+++ b/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/TimeColumnChart.kt
@@ -10,10 +10,10 @@ package org.nsh07.pomodoro.ui.statsScreen
import android.graphics.Path
import android.graphics.RectF
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
-import androidx.compose.material3.MaterialTheme.motionScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.patrykandpatrick.vico.compose.cartesian.CartesianChartHost
import com.patrykandpatrick.vico.compose.cartesian.axis.rememberBottom
@@ -40,9 +40,11 @@ import org.nsh07.pomodoro.utils.millisecondsToHours
internal fun TimeColumnChart(
modelProducer: CartesianChartModelProducer,
modifier: Modifier = Modifier,
+ thickness: Dp = 40.dp,
+ timeConverter: (Long) -> String = ::millisecondsToHours
) {
val radius = with(LocalDensity.current) {
- (48.dp / 2).toPx()
+ (thickness / 2).toPx()
}
ProvideVicoTheme(rememberM3VicoTheme()) {
CartesianChartHost(
@@ -53,7 +55,7 @@ internal fun TimeColumnChart(
vicoTheme.columnCartesianLayerColors.map { color ->
rememberLineComponent(
fill = fill(color),
- thickness = 48.dp,
+ thickness = thickness,
shape = { _, path, left, top, right, bottom ->
if (top + radius <= bottom - radius) {
path.arcTo(
@@ -87,21 +89,21 @@ internal fun TimeColumnChart(
tick = rememberLineComponent(Fill.Transparent),
guideline = rememberLineComponent(Fill.Transparent),
valueFormatter = CartesianValueFormatter { measuringContext, value, _ ->
- millisecondsToHours(value.toLong())
+ timeConverter(value.toLong())
}
),
bottomAxis = HorizontalAxis.rememberBottom(
rememberLineComponent(Fill.Transparent),
tick = rememberLineComponent(Fill.Transparent),
guideline = rememberLineComponent(Fill.Transparent)
- ),
+ )
),
modelProducer = modelProducer,
zoomState = rememberVicoZoomState(
+ zoomEnabled = false,
initialZoom = Zoom.fixed(),
- minZoom = Zoom.min(Zoom.fixed(), Zoom.Content)
+ minZoom = Zoom.min(Zoom.Content, Zoom.fixed())
),
- animationSpec = motionScheme.defaultSpatialSpec(),
modifier = modifier,
)
}
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 99257cf..9a887e5 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
@@ -16,6 +16,7 @@ import androidx.lifecycle.viewmodel.viewModelFactory
import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModelProducer
import com.patrykandpatrick.vico.core.cartesian.data.columnSeries
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
import org.nsh07.pomodoro.TomatoApplication
import org.nsh07.pomodoro.data.StatRepository
@@ -23,11 +24,12 @@ import org.nsh07.pomodoro.data.StatRepository
class StatsViewModel(
statRepository: StatRepository
) : ViewModel() {
- private val todayStat = statRepository.getTodayStat()
+ val todayStat = statRepository.getTodayStat().distinctUntilChanged()
private val allStatsSummary = statRepository.getLastWeekStatsSummary()
private val averageFocusTimes = statRepository.getAverageFocusTimes()
val allStatsSummaryModelProducer = CartesianChartModelProducer()
+ val todayStatModelProducer = CartesianChartModelProducer()
init {
viewModelScope.launch(Dispatchers.IO) {
@@ -38,6 +40,21 @@ class StatsViewModel(
}
}
}
+ viewModelScope.launch(Dispatchers.IO) {
+ todayStat
+ .collect {
+ todayStatModelProducer.runTransaction {
+ columnSeries {
+ series(
+ it?.focusTimeQ1 ?: 0,
+ it?.focusTimeQ2 ?: 0,
+ it?.focusTimeQ3 ?: 0,
+ it?.focusTimeQ4 ?: 0
+ )
+ }
+ }
+ }
+ }
}
companion object {
diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/theme/Type.kt b/app/src/main/java/org/nsh07/pomodoro/ui/theme/Type.kt
index a2966f0..6fd8593 100644
--- a/app/src/main/java/org/nsh07/pomodoro/ui/theme/Type.kt
+++ b/app/src/main/java/org/nsh07/pomodoro/ui/theme/Type.kt
@@ -1,3 +1,10 @@
+/*
+ * Copyright (c) 2025 Nishant Mishra
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
package org.nsh07.pomodoro.ui.theme
import androidx.compose.material3.Typography
@@ -9,6 +16,8 @@ import androidx.compose.ui.text.font.FontVariation
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
import org.nsh07.pomodoro.R
+import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexHeadline
+import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTitle
// Set of Material typography styles to start with
val Typography = Typography(
@@ -18,6 +27,18 @@ val Typography = Typography(
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
+ ),
+ headlineSmall = TextStyle(
+ fontFamily = robotoFlexHeadline,
+ fontSize = 24.sp,
+ lineHeight = 32.sp,
+ letterSpacing = 0.sp,
+ ),
+ titleMedium = TextStyle(
+ fontFamily = robotoFlexTitle,
+ fontSize = 16.sp,
+ lineHeight = 24.sp,
+ letterSpacing = 0.15.sp,
)
)
@@ -27,7 +48,7 @@ object AppFonts {
)
@OptIn(ExperimentalTextApi::class)
- val robotoFlexTitle = FontFamily(
+ val robotoFlexTopBar = FontFamily(
Font(
R.font.roboto_flex_variable,
variationSettings = FontVariation.Settings(
@@ -45,4 +66,28 @@ object AppFonts {
)
)
)
+
+ @OptIn(ExperimentalTextApi::class)
+ val robotoFlexHeadline = FontFamily(
+ Font(
+ R.font.roboto_flex_variable,
+ variationSettings = FontVariation.Settings(
+ FontVariation.width(130f),
+ FontVariation.weight(600),
+ FontVariation.grade(0)
+ )
+ )
+ )
+
+ @OptIn(ExperimentalTextApi::class)
+ val robotoFlexTitle = FontFamily(
+ Font(
+ R.font.roboto_flex_variable,
+ variationSettings = FontVariation.Settings(
+ FontVariation.width(130f),
+ FontVariation.weight(700),
+ FontVariation.grade(0)
+ )
+ )
+ )
}
\ No newline at end of file
diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/TimerScreen.kt b/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/TimerScreen.kt
index 855217a..d731065 100644
--- a/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/TimerScreen.kt
+++ b/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/TimerScreen.kt
@@ -58,7 +58,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import org.nsh07.pomodoro.R
import org.nsh07.pomodoro.ui.theme.AppFonts.openRundeClock
-import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTitle
+import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTopBar
import org.nsh07.pomodoro.ui.theme.TomatoTheme
import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerAction
import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerMode
@@ -112,7 +112,7 @@ fun TimerScreen(
Text(
"Tomato",
style = TextStyle(
- fontFamily = robotoFlexTitle,
+ fontFamily = robotoFlexTopBar,
fontSize = 32.sp,
lineHeight = 32.sp,
color = colorScheme.onErrorContainer
@@ -125,7 +125,7 @@ fun TimerScreen(
Text(
"Focus",
style = TextStyle(
- fontFamily = robotoFlexTitle,
+ fontFamily = robotoFlexTopBar,
fontSize = 32.sp,
lineHeight = 32.sp,
color = colorScheme.onPrimaryContainer
@@ -137,7 +137,7 @@ fun TimerScreen(
TimerMode.SHORT_BREAK -> Text(
"Short Break",
style = TextStyle(
- fontFamily = robotoFlexTitle,
+ fontFamily = robotoFlexTopBar,
fontSize = 32.sp,
lineHeight = 32.sp,
color = colorScheme.onTertiaryContainer
@@ -149,7 +149,7 @@ fun TimerScreen(
TimerMode.LONG_BREAK -> Text(
"Long Break",
style = TextStyle(
- fontFamily = robotoFlexTitle,
+ fontFamily = robotoFlexTopBar,
fontSize = 32.sp,
lineHeight = 32.sp,
color = colorScheme.onTertiaryContainer
diff --git a/app/src/main/java/org/nsh07/pomodoro/utils/Utils.kt b/app/src/main/java/org/nsh07/pomodoro/utils/Utils.kt
index 9b6dfd7..47378e2 100644
--- a/app/src/main/java/org/nsh07/pomodoro/utils/Utils.kt
+++ b/app/src/main/java/org/nsh07/pomodoro/utils/Utils.kt
@@ -23,4 +23,11 @@ fun millisecondsToHours(t: Long): String =
Locale.getDefault(),
"%dh",
TimeUnit.MILLISECONDS.toHours(t)
+ )
+
+fun millisecondsToHoursMinutes(t: Long): String =
+ String.format(
+ Locale.getDefault(),
+ "%dh %dm", TimeUnit.MILLISECONDS.toHours(t),
+ TimeUnit.MILLISECONDS.toMinutes(t) % TimeUnit.HOURS.toMinutes(1)
)
\ No newline at end of file
diff --git a/app/src/main/res/drawable/arrow_down.xml b/app/src/main/res/drawable/arrow_down.xml
new file mode 100644
index 0000000..d565e75
--- /dev/null
+++ b/app/src/main/res/drawable/arrow_down.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/font/open_runde_bold_clock_only.otf b/app/src/main/res/font/open_runde_bold_clock_only.otf
index 9c1b2b4..1c18ecf 100644
Binary files a/app/src/main/res/font/open_runde_bold_clock_only.otf and b/app/src/main/res/font/open_runde_bold_clock_only.otf differ