diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 1051ae6e..382927c0 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -127,9 +127,6 @@ dependencies {
implementation(libs.decompose.core)
implementation(libs.decompose.compose)
- implementation(libs.vico.multiplatform)
- implementation(libs.vico.multiplatform.m3)
-
// Wearable
implementation(libs.playServices.wearable)
// for connectedNodes.await()
diff --git a/cmp-common/build.gradle.kts b/cmp-common/build.gradle.kts
index 52baf999..2b01ed56 100644
--- a/cmp-common/build.gradle.kts
+++ b/cmp-common/build.gradle.kts
@@ -93,6 +93,9 @@ kotlin {
implementation(libs.koin.compose)
implementation(libs.coil.compose)
+
+ implementation(libs.vico.multiplatform)
+ implementation(libs.vico.multiplatform.m3)
}
val notWasm by getting {
dependencies {
diff --git a/cmp-common/src/commonMain/composeResources/values/strings.xml b/cmp-common/src/commonMain/composeResources/values/strings.xml
index 669f6e62..884a37df 100644
--- a/cmp-common/src/commonMain/composeResources/values/strings.xml
+++ b/cmp-common/src/commonMain/composeResources/values/strings.xml
@@ -27,4 +27,19 @@
Specialty
Add drink
+ Weekly
+ All Time
+ Coffee Consumption This Week
+ Coffee Consumption Over Time
+ Coffee Type Distribution
+ No data available
+
+ Mon
+ Tue
+ Wed
+ Thu
+ Fri
+ Sat
+ Sun
+
diff --git a/cmp-common/src/commonMain/kotlin/ru/beryukhov/coffeegram/components/RootComponent.kt b/cmp-common/src/commonMain/kotlin/ru/beryukhov/coffeegram/components/RootComponent.kt
index 48921e30..173f1ce1 100644
--- a/cmp-common/src/commonMain/kotlin/ru/beryukhov/coffeegram/components/RootComponent.kt
+++ b/cmp-common/src/commonMain/kotlin/ru/beryukhov/coffeegram/components/RootComponent.kt
@@ -24,6 +24,10 @@ interface RootComponent {
val component: CoffeeEditComponent,
) : Child
+ class Stats(
+ val component: StatsComponent,
+ ) : Child
+
class Settings(
val component: SettingsComponent,
) : Child
@@ -41,7 +45,12 @@ class DefaultRootComponent(
childPages(
source = navigation,
serializer = Config.serializer(),
- initialPages = { Pages(items = listOf(Config.CoffeeEdit, Config.Settings), selectedIndex = 0) },
+ initialPages = {
+ Pages(
+ items = listOf(Config.CoffeeEdit, Config.Stats, Config.Settings),
+ selectedIndex = 0
+ )
+ },
childFactory = ::child,
)
@@ -63,6 +72,13 @@ class DefaultRootComponent(
)
)
+ Config.Stats -> RootComponent.Child.Stats(
+ DefaultStatsComponent(
+ context = context,
+ daysCoffeesStore = daysCoffeesStore,
+ )
+ )
+
Config.Settings -> RootComponent.Child.Settings(
DefaultSettingsComponent(
context = context,
@@ -76,6 +92,9 @@ class DefaultRootComponent(
@Serializable
data object CoffeeEdit : Config
+ @Serializable
+ data object Stats : Config
+
@Serializable
data object Settings : Config
}
diff --git a/app/src/main/java/ru/beryukhov/coffeegram/components/StatsComponent.kt b/cmp-common/src/commonMain/kotlin/ru/beryukhov/coffeegram/components/StatsComponent.kt
similarity index 100%
rename from app/src/main/java/ru/beryukhov/coffeegram/components/StatsComponent.kt
rename to cmp-common/src/commonMain/kotlin/ru/beryukhov/coffeegram/components/StatsComponent.kt
diff --git a/cmp-common/src/commonMain/kotlin/ru/beryukhov/coffeegram/model/NavBarItem.kt b/cmp-common/src/commonMain/kotlin/ru/beryukhov/coffeegram/model/NavBarItem.kt
index 21ebd339..4a5b9b76 100644
--- a/cmp-common/src/commonMain/kotlin/ru/beryukhov/coffeegram/model/NavBarItem.kt
+++ b/cmp-common/src/commonMain/kotlin/ru/beryukhov/coffeegram/model/NavBarItem.kt
@@ -41,7 +41,7 @@ val settings = NavBarItem(
Icons.Default.LocationOn
)
-internal fun getNavBarItems() = persistentListOf(calendar, settings)
+internal fun getNavBarItems() = persistentListOf(calendar, stats, settings)
@Composable
@Suppress("ModifierMissing")
diff --git a/app/src/main/java/ru/beryukhov/coffeegram/pages/CoffeeCharts.kt b/cmp-common/src/commonMain/kotlin/ru/beryukhov/coffeegram/pages/CoffeeCharts.kt
similarity index 67%
rename from app/src/main/java/ru/beryukhov/coffeegram/pages/CoffeeCharts.kt
rename to cmp-common/src/commonMain/kotlin/ru/beryukhov/coffeegram/pages/CoffeeCharts.kt
index b138109a..d014935f 100644
--- a/app/src/main/java/ru/beryukhov/coffeegram/pages/CoffeeCharts.kt
+++ b/cmp-common/src/commonMain/kotlin/ru/beryukhov/coffeegram/pages/CoffeeCharts.kt
@@ -4,11 +4,17 @@
package ru.beryukhov.coffeegram.pages
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
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.rememberScrollState
+import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SecondaryTabRow
import androidx.compose.material3.Tab
@@ -22,6 +28,20 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
+import coffeegram.cmp_common.generated.resources.Res
+import coffeegram.cmp_common.generated.resources.chart_title_distribution
+import coffeegram.cmp_common.generated.resources.chart_title_over_time
+import coffeegram.cmp_common.generated.resources.chart_title_weekly
+import coffeegram.cmp_common.generated.resources.day_fri
+import coffeegram.cmp_common.generated.resources.day_mon
+import coffeegram.cmp_common.generated.resources.day_sat
+import coffeegram.cmp_common.generated.resources.day_sun
+import coffeegram.cmp_common.generated.resources.day_thu
+import coffeegram.cmp_common.generated.resources.day_tue
+import coffeegram.cmp_common.generated.resources.day_wed
+import coffeegram.cmp_common.generated.resources.no_data_available
+import coffeegram.cmp_common.generated.resources.tab_all_time
+import coffeegram.cmp_common.generated.resources.tab_weekly
import com.patrykandpatrick.vico.multiplatform.cartesian.CartesianChartHost
import com.patrykandpatrick.vico.multiplatform.cartesian.axis.HorizontalAxis
import com.patrykandpatrick.vico.multiplatform.cartesian.axis.VerticalAxis
@@ -36,12 +56,12 @@ import com.patrykandpatrick.vico.multiplatform.m3.common.rememberM3VicoTheme
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.datetime.DatePeriod
-import kotlinx.datetime.DayOfWeek
import kotlinx.datetime.LocalDate
import kotlinx.datetime.TimeZone
import kotlinx.datetime.minus
import kotlinx.datetime.plus
import kotlinx.datetime.toLocalDateTime
+import org.jetbrains.compose.resources.stringResource
import ru.beryukhov.coffeegram.data.CoffeeType
import ru.beryukhov.coffeegram.data.CoffeeTypes
import ru.beryukhov.coffeegram.data.DayCoffee
@@ -54,7 +74,7 @@ import kotlin.time.ExperimentalTime
@Composable
fun CoffeeCharts(coffeeState: DaysCoffeesState, modifier: Modifier = Modifier) {
var selectedTabIndex by remember { mutableIntStateOf(0) }
- val tabs = listOf("Weekly", "All Time")
+ val tabs = listOf(stringResource(Res.string.tab_weekly), stringResource(Res.string.tab_all_time))
Column(modifier = modifier.fillMaxWidth()) {
SecondaryTabRow(selectedTabIndex = selectedTabIndex) {
@@ -99,7 +119,7 @@ fun WeeklyCoffeeChart(coffeeState: DaysCoffeesState) {
}
}
Text(
- text = "Coffee Consumption This Week",
+ text = stringResource(Res.string.chart_title_weekly),
style = MaterialTheme.typography.headlineSmall,
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center
@@ -107,13 +127,23 @@ fun WeeklyCoffeeChart(coffeeState: DaysCoffeesState) {
Spacer(modifier = Modifier.height(16.dp))
+ val dayNames = listOf(
+ stringResource(Res.string.day_mon),
+ stringResource(Res.string.day_tue),
+ stringResource(Res.string.day_wed),
+ stringResource(Res.string.day_thu),
+ stringResource(Res.string.day_fri),
+ stringResource(Res.string.day_sat),
+ stringResource(Res.string.day_sun),
+ )
+
CartesianChartHost(
chart = rememberCartesianChart(
rememberColumnCartesianLayer(),
startAxis = VerticalAxis.rememberStart(),
bottomAxis = HorizontalAxis.rememberBottom(
valueFormatter = { _, value, _ ->
- weekData.getOrNull(value.toInt())?.dayName ?: ""
+ dayNames.getOrNull(weekData.getOrNull(value.toInt())?.dayIndex ?: -1) ?: ""
}
),
),
@@ -133,15 +163,7 @@ internal fun weeklyChartData(
WeeklyChartData(
date = date,
- dayName = when (date.dayOfWeek) {
- DayOfWeek.MONDAY -> "Mon"
- DayOfWeek.TUESDAY -> "Tue"
- DayOfWeek.WEDNESDAY -> "Wed"
- DayOfWeek.THURSDAY -> "Thu"
- DayOfWeek.FRIDAY -> "Fri"
- DayOfWeek.SATURDAY -> "Sat"
- DayOfWeek.SUNDAY -> "Sun"
- },
+ dayIndex = date.dayOfWeek.ordinal,
totalCoffees = totalForDay,
)
}
@@ -160,7 +182,7 @@ fun AllTimeCoffeeChart(coffeeState: DaysCoffeesState) {
.fillMaxWidth()
.padding(16.dp)
) {
- Text("No data available")
+ Text(stringResource(Res.string.no_data_available))
}
return
}
@@ -184,36 +206,85 @@ fun AllTimeCoffeeChart(coffeeState: DaysCoffeesState) {
dailyAggregation(coffeeState)
}
- Column(
+ BoxWithConstraints(
modifier = Modifier
- .fillMaxWidth()
+ .fillMaxSize()
.padding(16.dp)
) {
- Text(
- text = "Coffee Consumption Over Time",
- style = MaterialTheme.typography.headlineSmall,
- modifier = Modifier.fillMaxWidth(),
- textAlign = TextAlign.Center
- )
+ val isLandscape = maxWidth > maxHeight
+
+ if (isLandscape) {
+ // Horizontal layout for landscape
+ Row(
+ modifier = Modifier.fillMaxSize()
+ ) {
+ // Left chart - Coffee Consumption Over Time
+ Column(
+ modifier = Modifier
+ .weight(1f)
+ .verticalScroll(rememberScrollState())
+ ) {
+ Text(
+ text = stringResource(Res.string.chart_title_over_time),
+ style = MaterialTheme.typography.headlineSmall,
+ modifier = Modifier.fillMaxWidth(),
+ textAlign = TextAlign.Center
+ )
+ Spacer(modifier = Modifier.height(16.dp))
+ LineChart(aggregatedData.toImmutableList())
+ }
+
+ Spacer(modifier = Modifier.width(16.dp))
+
+ // Right chart - Coffee Type Distribution
+ Column(
+ modifier = Modifier
+ .weight(1f)
+ .verticalScroll(rememberScrollState())
+ ) {
+ Text(
+ text = stringResource(Res.string.chart_title_distribution),
+ style = MaterialTheme.typography.headlineSmall,
+ modifier = Modifier.fillMaxWidth(),
+ textAlign = TextAlign.Center
+ )
+ Spacer(modifier = Modifier.height(16.dp))
+ ColumnChart(coffeeState)
+ }
+ }
+ } else {
+ // Vertical layout for portrait
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .verticalScroll(rememberScrollState())
+ ) {
+ Text(
+ text = stringResource(Res.string.chart_title_over_time),
+ style = MaterialTheme.typography.headlineSmall,
+ modifier = Modifier.fillMaxWidth(),
+ textAlign = TextAlign.Center
+ )
- Spacer(modifier = Modifier.height(16.dp))
- LineChart(aggregatedData.toImmutableList())
+ Spacer(modifier = Modifier.height(16.dp))
+ LineChart(aggregatedData.toImmutableList())
- Spacer(modifier = Modifier.height(24.dp))
+ Spacer(modifier = Modifier.height(24.dp))
- // Coffee type distribution
- Text(
- text = "Coffee Type Distribution",
- style = MaterialTheme.typography.headlineSmall,
- modifier = Modifier.fillMaxWidth(),
- textAlign = TextAlign.Center
- )
+ Text(
+ text = stringResource(Res.string.chart_title_distribution),
+ style = MaterialTheme.typography.headlineSmall,
+ modifier = Modifier.fillMaxWidth(),
+ textAlign = TextAlign.Center
+ )
- Spacer(modifier = Modifier.height(16.dp))
+ Spacer(modifier = Modifier.height(16.dp))
- ColumnChart(coffeeState)
+ ColumnChart(coffeeState)
- Spacer(modifier = Modifier.height(16.dp))
+ Spacer(modifier = Modifier.height(16.dp))
+ }
+ }
}
}
@@ -317,7 +388,7 @@ internal fun monthlyAggregation(coffeeState: DaysCoffeesState): List
when (val c = page) {
is RootComponent.Child.CoffeeEdit -> CoffeeEditAppBar(c.component)
+ is RootComponent.Child.Stats -> StatsAppBar()
is RootComponent.Child.Settings -> SettingsAppBar(c.component)
}
}
@@ -66,6 +67,7 @@ private fun CurrentScreen(
) { index, page ->
when (val c = page) {
is RootComponent.Child.CoffeeEdit -> CoffeeEditScreen(c.component)
+ is RootComponent.Child.Stats -> StatsScreen(c.component)
is RootComponent.Child.Settings -> SettingsScreen(c.component)
}
}
diff --git a/app/src/main/java/ru/beryukhov/coffeegram/screens/StatsScreen.kt b/cmp-common/src/commonMain/kotlin/ru/beryukhov/coffeegram/screens/StatsScreen.kt
similarity index 81%
rename from app/src/main/java/ru/beryukhov/coffeegram/screens/StatsScreen.kt
rename to cmp-common/src/commonMain/kotlin/ru/beryukhov/coffeegram/screens/StatsScreen.kt
index 7012c6b2..78be0380 100644
--- a/app/src/main/java/ru/beryukhov/coffeegram/screens/StatsScreen.kt
+++ b/cmp-common/src/commonMain/kotlin/ru/beryukhov/coffeegram/screens/StatsScreen.kt
@@ -11,8 +11,9 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
-import ru.beryukhov.coffeegram.R
+import coffeegram.cmp_common.generated.resources.Res
+import coffeegram.cmp_common.generated.resources.stats
+import org.jetbrains.compose.resources.stringResource
import ru.beryukhov.coffeegram.components.StatsComponent
import ru.beryukhov.coffeegram.pages.CoffeeCharts
@@ -31,7 +32,7 @@ fun StatsScreen(
@Composable
fun StatsAppBar(modifier: Modifier = Modifier) {
TopAppBar(
- title = { Text(stringResource(R.string.stats)) },
+ title = { Text(stringResource(Res.string.stats)) },
modifier = modifier
)
}
diff --git a/app/src/test/kotlin/ru/beryukhov/coffeegram/pages/CoffeeChartsDataTest.kt b/cmp-common/src/commonTest/kotlin/ru/beryukhov/coffeegram/pages/CoffeeChartsDataTest.kt
similarity index 92%
rename from app/src/test/kotlin/ru/beryukhov/coffeegram/pages/CoffeeChartsDataTest.kt
rename to cmp-common/src/commonTest/kotlin/ru/beryukhov/coffeegram/pages/CoffeeChartsDataTest.kt
index a43525c1..e8e7aa47 100644
--- a/app/src/test/kotlin/ru/beryukhov/coffeegram/pages/CoffeeChartsDataTest.kt
+++ b/cmp-common/src/commonTest/kotlin/ru/beryukhov/coffeegram/pages/CoffeeChartsDataTest.kt
@@ -22,15 +22,14 @@ class CoffeeChartsDataTest {
)
assertEquals(
expected = listOf(
- WeeklyChartData(date = LocalDate(2023, 1, 2), dayName = "Mon", totalCoffees = 3),
- WeeklyChartData(date = LocalDate(2023, 1, 3), dayName = "Tue", totalCoffees = 4),
- WeeklyChartData(date = LocalDate(2023, 1, 4), dayName = "Wed", totalCoffees = 0),
- WeeklyChartData(date = LocalDate(2023, 1, 5), dayName = "Thu", totalCoffees = 0),
- WeeklyChartData(date = LocalDate(2023, 1, 6), dayName = "Fri", totalCoffees = 0),
- WeeklyChartData(date = LocalDate(2023, 1, 7), dayName = "Sat", totalCoffees = 0),
- WeeklyChartData(date = LocalDate(2023, 1, 8), dayName = "Sun", totalCoffees = 0),
-
- ),
+ WeeklyChartData(date = LocalDate(2023, 1, 2), dayIndex = 0, totalCoffees = 3),
+ WeeklyChartData(date = LocalDate(2023, 1, 3), dayIndex = 1, totalCoffees = 4),
+ WeeklyChartData(date = LocalDate(2023, 1, 4), dayIndex = 2, totalCoffees = 0),
+ WeeklyChartData(date = LocalDate(2023, 1, 5), dayIndex = 3, totalCoffees = 0),
+ WeeklyChartData(date = LocalDate(2023, 1, 6), dayIndex = 4, totalCoffees = 0),
+ WeeklyChartData(date = LocalDate(2023, 1, 7), dayIndex = 5, totalCoffees = 0),
+ WeeklyChartData(date = LocalDate(2023, 1, 8), dayIndex = 6, totalCoffees = 0),
+ ),
actual = actualData
)