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 )