From 470c9ce7063afa78373ae0b704a5ac407b6cf95f Mon Sep 17 00:00:00 2001 From: "Andres Gomez, Thomas (ITDV RL)" Date: Mon, 22 Sep 2025 18:28:37 +0200 Subject: [PATCH] Optimize the month cell display in the year summary. --- .../destination/MonthSummaryDestination.kt | 2 +- .../destination/YearSummaryDestination.kt | 2 +- .../{month => monthly}/MonthSummaryFactory.kt | 12 +- .../{month => monthly}/MonthSummaryPage.kt | 18 +- .../MonthSummaryViewModel.kt | 4 +- .../item/MonthSummaryBox.kt | 2 +- .../item/MonthSummaryCell.kt | 2 +- .../item/MonthSummaryItem.kt | 2 +- .../item/MonthSummaryPillItem.kt | 2 +- .../item/MonthSummaryTitle.kt | 2 +- .../ui/page/summary/year/YearSummaryPage.kt | 483 ------------------ .../page/summary/year/item/YearSummaryDay.kt | 136 ----- .../summary/year/item/YearSummaryMonth.kt | 243 --------- .../{year => yearly}/YearSummaryFactory.kt | 10 +- .../page/summary/yearly/YearSummaryMonth.kt | 346 +++++++++++++ .../ui/page/summary/yearly/YearSummaryPage.kt | 477 +++++++++++++++++ .../{year => yearly}/YearSummaryViewModel.kt | 2 +- .../com/pixelized/headache/ui/theme/Theme.kt | 30 +- .../headache/ui/theme/color/HeadacheColors.kt | 46 +- 19 files changed, 914 insertions(+), 907 deletions(-) rename app/src/main/java/com/pixelized/headache/ui/page/summary/{month => monthly}/MonthSummaryFactory.kt (90%) rename app/src/main/java/com/pixelized/headache/ui/page/summary/{month => monthly}/MonthSummaryPage.kt (93%) rename app/src/main/java/com/pixelized/headache/ui/page/summary/{month => monthly}/MonthSummaryViewModel.kt (91%) rename app/src/main/java/com/pixelized/headache/ui/page/summary/{month => monthly}/item/MonthSummaryBox.kt (99%) rename app/src/main/java/com/pixelized/headache/ui/page/summary/{month => monthly}/item/MonthSummaryCell.kt (67%) rename app/src/main/java/com/pixelized/headache/ui/page/summary/{month => monthly}/item/MonthSummaryItem.kt (98%) rename app/src/main/java/com/pixelized/headache/ui/page/summary/{month => monthly}/item/MonthSummaryPillItem.kt (97%) rename app/src/main/java/com/pixelized/headache/ui/page/summary/{month => monthly}/item/MonthSummaryTitle.kt (97%) delete mode 100644 app/src/main/java/com/pixelized/headache/ui/page/summary/year/YearSummaryPage.kt delete mode 100644 app/src/main/java/com/pixelized/headache/ui/page/summary/year/item/YearSummaryDay.kt delete mode 100644 app/src/main/java/com/pixelized/headache/ui/page/summary/year/item/YearSummaryMonth.kt rename app/src/main/java/com/pixelized/headache/ui/page/summary/{year => yearly}/YearSummaryFactory.kt (93%) create mode 100644 app/src/main/java/com/pixelized/headache/ui/page/summary/yearly/YearSummaryMonth.kt create mode 100644 app/src/main/java/com/pixelized/headache/ui/page/summary/yearly/YearSummaryPage.kt rename app/src/main/java/com/pixelized/headache/ui/page/summary/{year => yearly}/YearSummaryViewModel.kt (93%) diff --git a/app/src/main/java/com/pixelized/headache/ui/navigation/destination/MonthSummaryDestination.kt b/app/src/main/java/com/pixelized/headache/ui/navigation/destination/MonthSummaryDestination.kt index 2f4541a..0369ebe 100644 --- a/app/src/main/java/com/pixelized/headache/ui/navigation/destination/MonthSummaryDestination.kt +++ b/app/src/main/java/com/pixelized/headache/ui/navigation/destination/MonthSummaryDestination.kt @@ -3,7 +3,7 @@ package com.pixelized.headache.ui.navigation.destination import androidx.navigation3.runtime.EntryProviderBuilder import androidx.navigation3.runtime.entry import com.pixelized.headache.ui.navigation.Navigator -import com.pixelized.headache.ui.page.summary.month.MonthSummaryPage +import com.pixelized.headache.ui.page.summary.monthly.MonthSummaryPage data object MonthSummaryDestination diff --git a/app/src/main/java/com/pixelized/headache/ui/navigation/destination/YearSummaryDestination.kt b/app/src/main/java/com/pixelized/headache/ui/navigation/destination/YearSummaryDestination.kt index 6fd30fb..022a2d2 100644 --- a/app/src/main/java/com/pixelized/headache/ui/navigation/destination/YearSummaryDestination.kt +++ b/app/src/main/java/com/pixelized/headache/ui/navigation/destination/YearSummaryDestination.kt @@ -3,7 +3,7 @@ package com.pixelized.headache.ui.navigation.destination import androidx.navigation3.runtime.EntryProviderBuilder import androidx.navigation3.runtime.entry import com.pixelized.headache.ui.navigation.Navigator -import com.pixelized.headache.ui.page.summary.year.YearSummaryPage +import com.pixelized.headache.ui.page.summary.yearly.YearSummaryPage data object YearSummaryDestination diff --git a/app/src/main/java/com/pixelized/headache/ui/page/summary/month/MonthSummaryFactory.kt b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/MonthSummaryFactory.kt similarity index 90% rename from app/src/main/java/com/pixelized/headache/ui/page/summary/month/MonthSummaryFactory.kt rename to app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/MonthSummaryFactory.kt index eb624aa..2fd478a 100644 --- a/app/src/main/java/com/pixelized/headache/ui/page/summary/month/MonthSummaryFactory.kt +++ b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/MonthSummaryFactory.kt @@ -1,13 +1,13 @@ -package com.pixelized.headache.ui.page.summary.month +package com.pixelized.headache.ui.page.summary.monthly import android.icu.util.Calendar import androidx.compose.ui.graphics.Color import com.pixelized.headache.repository.event.Event -import com.pixelized.headache.ui.page.summary.month.item.MonthSummaryBoxUio -import com.pixelized.headache.ui.page.summary.month.item.MonthSummaryCell -import com.pixelized.headache.ui.page.summary.month.item.MonthSummaryItemUio -import com.pixelized.headache.ui.page.summary.month.item.MonthSummaryPillItemUio -import com.pixelized.headache.ui.page.summary.month.item.MonthSummaryTitleUio +import com.pixelized.headache.ui.page.summary.monthly.item.MonthSummaryBoxUio +import com.pixelized.headache.ui.page.summary.monthly.item.MonthSummaryCell +import com.pixelized.headache.ui.page.summary.monthly.item.MonthSummaryItemUio +import com.pixelized.headache.ui.page.summary.monthly.item.MonthSummaryPillItemUio +import com.pixelized.headache.ui.page.summary.monthly.item.MonthSummaryTitleUio import com.pixelized.headache.utils.extention.event import javax.inject.Inject import kotlin.math.max diff --git a/app/src/main/java/com/pixelized/headache/ui/page/summary/month/MonthSummaryPage.kt b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/MonthSummaryPage.kt similarity index 93% rename from app/src/main/java/com/pixelized/headache/ui/page/summary/month/MonthSummaryPage.kt rename to app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/MonthSummaryPage.kt index 40ab528..1d7d2c6 100644 --- a/app/src/main/java/com/pixelized/headache/ui/page/summary/month/MonthSummaryPage.kt +++ b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/MonthSummaryPage.kt @@ -1,4 +1,4 @@ -package com.pixelized.headache.ui.page.summary.month +package com.pixelized.headache.ui.page.summary.monthly import androidx.compose.animation.AnimatedContent import androidx.compose.animation.fadeIn @@ -45,14 +45,14 @@ import com.pixelized.headache.ui.navigation.LocalNavigator import com.pixelized.headache.ui.navigation.destination.navigateToEventPage import com.pixelized.headache.ui.page.event.edit.EventEditBottomSheet import com.pixelized.headache.ui.page.event.edit.EventEditBottomSheetViewModel -import com.pixelized.headache.ui.page.summary.month.item.MonthSummaryBox -import com.pixelized.headache.ui.page.summary.month.item.MonthSummaryBoxUio -import com.pixelized.headache.ui.page.summary.month.item.MonthSummaryCell -import com.pixelized.headache.ui.page.summary.month.item.MonthSummaryItem -import com.pixelized.headache.ui.page.summary.month.item.MonthSummaryItemUio -import com.pixelized.headache.ui.page.summary.month.item.MonthSummaryPillItemUio -import com.pixelized.headache.ui.page.summary.month.item.MonthSummaryTitle -import com.pixelized.headache.ui.page.summary.month.item.MonthSummaryTitleUio +import com.pixelized.headache.ui.page.summary.monthly.item.MonthSummaryBox +import com.pixelized.headache.ui.page.summary.monthly.item.MonthSummaryBoxUio +import com.pixelized.headache.ui.page.summary.monthly.item.MonthSummaryCell +import com.pixelized.headache.ui.page.summary.monthly.item.MonthSummaryItem +import com.pixelized.headache.ui.page.summary.monthly.item.MonthSummaryItemUio +import com.pixelized.headache.ui.page.summary.monthly.item.MonthSummaryPillItemUio +import com.pixelized.headache.ui.page.summary.monthly.item.MonthSummaryTitle +import com.pixelized.headache.ui.page.summary.monthly.item.MonthSummaryTitleUio import com.pixelized.headache.ui.theme.HeadacheTheme import com.pixelized.headache.ui.theme.color.HeadacheColorPalette import java.util.Date diff --git a/app/src/main/java/com/pixelized/headache/ui/page/summary/month/MonthSummaryViewModel.kt b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/MonthSummaryViewModel.kt similarity index 91% rename from app/src/main/java/com/pixelized/headache/ui/page/summary/month/MonthSummaryViewModel.kt rename to app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/MonthSummaryViewModel.kt index 86f038a..06c3d71 100644 --- a/app/src/main/java/com/pixelized/headache/ui/page/summary/month/MonthSummaryViewModel.kt +++ b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/MonthSummaryViewModel.kt @@ -1,10 +1,10 @@ -package com.pixelized.headache.ui.page.summary.month +package com.pixelized.headache.ui.page.summary.monthly import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.pixelized.headache.repository.event.Event import com.pixelized.headache.repository.event.EventRepository -import com.pixelized.headache.ui.page.summary.month.item.MonthSummaryCell +import com.pixelized.headache.ui.page.summary.monthly.item.MonthSummaryCell import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow diff --git a/app/src/main/java/com/pixelized/headache/ui/page/summary/month/item/MonthSummaryBox.kt b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/item/MonthSummaryBox.kt similarity index 99% rename from app/src/main/java/com/pixelized/headache/ui/page/summary/month/item/MonthSummaryBox.kt rename to app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/item/MonthSummaryBox.kt index dc4d060..99f51ba 100644 --- a/app/src/main/java/com/pixelized/headache/ui/page/summary/month/item/MonthSummaryBox.kt +++ b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/item/MonthSummaryBox.kt @@ -1,4 +1,4 @@ -package com.pixelized.headache.ui.page.summary.month.item +package com.pixelized.headache.ui.page.summary.monthly.item import android.annotation.SuppressLint import android.icu.text.SimpleDateFormat diff --git a/app/src/main/java/com/pixelized/headache/ui/page/summary/month/item/MonthSummaryCell.kt b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/item/MonthSummaryCell.kt similarity index 67% rename from app/src/main/java/com/pixelized/headache/ui/page/summary/month/item/MonthSummaryCell.kt rename to app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/item/MonthSummaryCell.kt index 863d793..9d06e78 100644 --- a/app/src/main/java/com/pixelized/headache/ui/page/summary/month/item/MonthSummaryCell.kt +++ b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/item/MonthSummaryCell.kt @@ -1,4 +1,4 @@ -package com.pixelized.headache.ui.page.summary.month.item +package com.pixelized.headache.ui.page.summary.monthly.item import androidx.compose.runtime.Stable import java.util.Date diff --git a/app/src/main/java/com/pixelized/headache/ui/page/summary/month/item/MonthSummaryItem.kt b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/item/MonthSummaryItem.kt similarity index 98% rename from app/src/main/java/com/pixelized/headache/ui/page/summary/month/item/MonthSummaryItem.kt rename to app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/item/MonthSummaryItem.kt index 9adf1f0..4101862 100644 --- a/app/src/main/java/com/pixelized/headache/ui/page/summary/month/item/MonthSummaryItem.kt +++ b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/item/MonthSummaryItem.kt @@ -1,4 +1,4 @@ -package com.pixelized.headache.ui.page.summary.month.item +package com.pixelized.headache.ui.page.summary.monthly.item import android.annotation.SuppressLint import android.icu.text.SimpleDateFormat diff --git a/app/src/main/java/com/pixelized/headache/ui/page/summary/month/item/MonthSummaryPillItem.kt b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/item/MonthSummaryPillItem.kt similarity index 97% rename from app/src/main/java/com/pixelized/headache/ui/page/summary/month/item/MonthSummaryPillItem.kt rename to app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/item/MonthSummaryPillItem.kt index f0c799e..1d55727 100644 --- a/app/src/main/java/com/pixelized/headache/ui/page/summary/month/item/MonthSummaryPillItem.kt +++ b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/item/MonthSummaryPillItem.kt @@ -1,4 +1,4 @@ -package com.pixelized.headache.ui.page.summary.month.item +package com.pixelized.headache.ui.page.summary.monthly.item import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement diff --git a/app/src/main/java/com/pixelized/headache/ui/page/summary/month/item/MonthSummaryTitle.kt b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/item/MonthSummaryTitle.kt similarity index 97% rename from app/src/main/java/com/pixelized/headache/ui/page/summary/month/item/MonthSummaryTitle.kt rename to app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/item/MonthSummaryTitle.kt index 65cc3ec..ff1a023 100644 --- a/app/src/main/java/com/pixelized/headache/ui/page/summary/month/item/MonthSummaryTitle.kt +++ b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/item/MonthSummaryTitle.kt @@ -1,4 +1,4 @@ -package com.pixelized.headache.ui.page.summary.month.item +package com.pixelized.headache.ui.page.summary.monthly.item import android.annotation.SuppressLint import android.icu.text.SimpleDateFormat diff --git a/app/src/main/java/com/pixelized/headache/ui/page/summary/year/YearSummaryPage.kt b/app/src/main/java/com/pixelized/headache/ui/page/summary/year/YearSummaryPage.kt deleted file mode 100644 index 026bcfe..0000000 --- a/app/src/main/java/com/pixelized/headache/ui/page/summary/year/YearSummaryPage.kt +++ /dev/null @@ -1,483 +0,0 @@ -package com.pixelized.headache.ui.page.summary.year - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.systemBarsPadding -import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.GridItemSpan -import androidx.compose.foundation.lazy.grid.LazyVerticalGrid -import androidx.compose.foundation.lazy.grid.items -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material.icons.filled.Add -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.FloatingActionButton -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Stable -import androidx.compose.runtime.State -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.keepScreenOn -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.platform.LocalWindowInfo -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.pixelized.headache.R -import com.pixelized.headache.ui.navigation.LocalNavigator -import com.pixelized.headache.ui.navigation.destination.navigateToEventPage -import com.pixelized.headache.ui.page.event.edit.EventEditBottomSheet -import com.pixelized.headache.ui.page.event.edit.EventEditBottomSheetViewModel -import com.pixelized.headache.ui.page.summary.year.item.YearSummaryDayUio -import com.pixelized.headache.ui.page.summary.year.item.YearSummaryMonth -import com.pixelized.headache.ui.page.summary.year.item.YearSummaryMonthUio -import com.pixelized.headache.ui.theme.HeadacheTheme -import com.pixelized.headache.ui.theme.color.HeadacheColorPalette -import com.pixelized.headache.utils.extention.calculate -import java.util.Calendar - -@Stable -data class YearUio( - val year: Int, - val months: List, -) - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun YearSummaryPage( - viewModel: YearSummaryViewModel = hiltViewModel(), - editViewModel: EventEditBottomSheetViewModel = hiltViewModel(), -) { - val navigation = LocalNavigator.current - val uio = viewModel.events.collectAsStateWithLifecycle() - - YearSummaryContent( - modifier = Modifier - .systemBarsPadding() - .keepScreenOn() - .fillMaxSize(), - uio = uio, - onBack = { - navigation.popBackstack() - }, - onMonth = { - navigation.navigateToEventPage(date = it.date) - }, - onAddEvent = { - editViewModel.show() - }, - ) - - EventEditBottomSheet( - viewModel = editViewModel, - onDismissRequest = { editViewModel.dismiss() }, - ) -} - -@Composable -private fun rememberDaySize( - column: Int = 3, - space: Dp, - paddingValues: PaddingValues, -): Dp { - val density = LocalDensity.current - val windowInfo = LocalWindowInfo.current - val screenWidth = remember(density, windowInfo) { - with(density) { - windowInfo.containerSize.width.toDp() - } - } - val (start, _, end, _) = paddingValues.calculate() - return (screenWidth - space * (column - 1) - start - end) / (7 * column) -} - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun YearSummaryContent( - modifier: Modifier = Modifier, - paddingValues: PaddingValues = PaddingValues( - start = 16.dp, - top = 16.dp, - end = 16.dp, - bottom = 16.dp + 16.dp + 56.dp, - ), - space: Dp = 8.dp, - daySize: Dp = rememberDaySize( - column = 3, - paddingValues = paddingValues, - space = space, - ), - uio: State>, - onBack: () -> Unit, - onMonth: (YearSummaryMonthUio) -> Unit, - onAddEvent: () -> Unit, -) { - Scaffold( - modifier = modifier, - topBar = { - TopAppBar( - navigationIcon = { - IconButton( - onClick = onBack, - ) { - Icon( - imageVector = Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = null, - ) - } - }, - title = { - Text(text = stringResource(R.string.year_summary_title)) - }, - ) - }, - floatingActionButton = { - FloatingActionButton( - containerColor = MaterialTheme.colorScheme.primary, - shape = CircleShape, - onClick = onAddEvent, - ) { - Icon( - imageVector = Icons.Default.Add, - contentDescription = null, - ) - } - }, - content = { it -> - LazyVerticalGrid( - modifier = Modifier.padding(paddingValues = it), - columns = GridCells.Adaptive(minSize = daySize * 7), - horizontalArrangement = Arrangement.spacedBy(space = space), - contentPadding = paddingValues, - ) { - uio.value.forEachIndexed { index, (year, months) -> - item( - span = { GridItemSpan(maxLineSpan) }, - contentType = { "Title" }, - ) { - Text( - modifier = Modifier.padding( - top = when (index) { - 0 -> 0.dp - else -> 16.dp - } - ), - style = MaterialTheme.typography.displaySmall, - text = "$year", - ) - } - items( - items = months, - key = { it.date.time }, - contentType = { "Month" }, - ) { - YearSummaryMonth( - uio = it, - daySize = daySize, - onMonth = onMonth, - ) - } - } - } - } - ) -} - -@Composable -@Preview() -private fun YearSummaryPreview( - @PreviewParameter(YearPreviewProvider::class) preview: List, -) { - HeadacheTheme { - Surface { - YearSummaryContent( - uio = remember { mutableStateOf(preview) }, - onBack = { }, - onMonth = { }, - onAddEvent = { }, - ) - } - } -} - -private class YearPreviewProvider() : PreviewParameterProvider> { - - private var day = 1 - - private fun day(): () -> YearSummaryDayUio = { - YearSummaryDayUio( - number = day++, - headache = false, - pills = emptyList(), - ) - } - - private fun headache( - spifen: Boolean = false, - eletriptan: Boolean = false, - ): () -> YearSummaryDayUio = { - YearSummaryDayUio( - number = day++, - headache = true, - pills = listOfNotNull( - if (spifen) HeadacheColorPalette.Pill.Spifen400 else null, - if (eletriptan) HeadacheColorPalette.Pill.Eletriptan40 else null, - ), - ) - } - - // inline to avoid un sequential call to "day()" between default and overridden function parameters. - @Suppress("NOTHING_TO_INLINE") - private fun week( - mon: (() -> YearSummaryDayUio)? = day(), - tue: (() -> YearSummaryDayUio)? = day(), - wen: (() -> YearSummaryDayUio)? = day(), - thu: (() -> YearSummaryDayUio)? = day(), - fry: (() -> YearSummaryDayUio)? = day(), - sat: (() -> YearSummaryDayUio)? = day(), - sun: (() -> YearSummaryDayUio)? = day(), - ): List = listOfNotNull( - mon?.invoke(), - tue?.invoke(), - wen?.invoke(), - thu?.invoke(), - fry?.invoke(), - sat?.invoke(), - sun?.invoke(), - ) - - private fun month( - month: Int, - year: Int, - vararg weeks: List, - ): YearSummaryMonthUio { - day = 1 - return YearSummaryMonthUio( - date = Calendar.getInstance().apply { - set(Calendar.YEAR, year) - set(Calendar.MONTH, month) - }.time, - weeks = weeks.toList(), - ) - } - - private fun year( - year: Int, - vararg months: YearSummaryMonthUio, - ) = YearUio( - year = year, - months = months.toList(), - ) - - override val values: Sequence> = sequenceOf( - listOf( - year( - year = 2025, - month( - month = Calendar.JANUARY, - year = 2025, - week(mon = null, tue = null), - week(), - week(), - week(), - week(sat = null, sun = null), - ), - month( - month = Calendar.FEBRUARY, - year = 2025, - week(mon = null, tue = null, wen = null, thu = null, fry = null), - week(), - week(), - week(), - week(sat = null, sun = null), - ), - month( - month = Calendar.MARCH, - year = 2025, - week(mon = null, tue = null, wen = null, thu = null, fry = null), - week(), - week(), - week(), - week(), - week(tue = null, wen = null, thu = null, fry = null, sat = null, sun = null), - ), - month( - month = Calendar.APRIL, - year = 2025, - week(mon = null), - week(), - week(), - week(), - week(thu = null, fry = null, sat = null, sun = null), - ), - month( - month = Calendar.MAY, - year = 2025, - week(mon = null, tue = null, wen = null), - week(), - week(), - week(), - week(sun = null), - ), - month( - month = Calendar.JUNE, - year = 2025, - week(mon = null, tue = null, wen = null, thu = null, fry = null, sat = null), - week(), - week(), - week(), - week(), - week(tue = null, wen = null, thu = null, fry = null, sat = null, sun = null), - ), - month( - month = Calendar.JULY, - year = 2025, - week(mon = null, sun = headache(spifen = true)), - week(wen = headache(spifen = true)), - week(sat = headache()), - week( - wen = headache(spifen = true), - thu = headache(), - sat = headache(spifen = true), - ), - week(wen = headache(), fry = null, sat = null, sun = null), - ), - month( - month = Calendar.AUGUST, - year = 2025, - week(mon = null, tue = null, wen = null, thu = null), - week(wen = headache(), fry = headache()), - week(fry = headache(spifen = true)), - week(tue = headache(spifen = true), fry = headache()), - week( - tue = headache(spifen = true, eletriptan = true), - wen = headache(), - sat = headache(spifen = true), - ), - ), - month( - month = Calendar.SEPTEMBER, - year = 2025, - week( - tue = headache(spifen = true, eletriptan = true), - wen = headache(spifen = true), - fry = headache(), - sat = headache(), - sun = headache(), - ), - week( - sun = headache(spifen = true), - ), - week( - wen = headache(spifen = true), - tue = headache(), - ), - week(), - week(wen = null, thu = null, fry = null, sat = null, sun = null), - ), - month( - month = Calendar.OCTOBER, - year = 2025, - week(mon = null, tue = null), - week(), - week(), - week(), - week(sat = null, sun = null), - ), - month( - month = Calendar.NOVEMBER, - year = 2025, - week(mon = null, tue = null, wen = null, thu = null, fry = null), - week(), - week(), - week(), - week(), - ), - month( - month = Calendar.DECEMBER, - year = 2025, - week(), - week(), - week(), - week(), - week(thu = null, fry = null, sat = null, sun = null), - ), - ), - year( - year = 2024, - month( - month = Calendar.JANUARY, - year = 2024, - week(mon = null, tue = null), - week(), - week(), - week(), - week(sat = null, sun = null), - ), - month( - month = Calendar.FEBRUARY, - year = 2024, - week(mon = null, tue = null, wen = null, thu = null, fry = null), - week(), - week(), - week(), - week(sat = null, sun = null), - ), - month( - month = Calendar.MARCH, - year = 2024, - week(mon = null, tue = null, wen = null, thu = null, fry = null), - week(), - week(), - week(), - week(), - week(tue = null, wen = null, thu = null, fry = null, sat = null, sun = null), - ), - month( - month = Calendar.APRIL, - year = 2024, - week(mon = null), - week(), - week(), - week(), - week(thu = null, fry = null, sat = null, sun = null), - ), - month( - month = Calendar.MAY, - year = 2024, - week(mon = null, tue = null, wen = null), - week(), - week(), - week(), - week(sun = null), - ), - month( - month = Calendar.JUNE, - year = 2024, - week(mon = null, tue = null, wen = null, thu = null, fry = null, sat = null), - week(), - week(), - week(), - week(), - week(tue = null, wen = null, thu = null, fry = null, sat = null, sun = null), - ), - ) - ), - ) -} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/headache/ui/page/summary/year/item/YearSummaryDay.kt b/app/src/main/java/com/pixelized/headache/ui/page/summary/year/item/YearSummaryDay.kt deleted file mode 100644 index c3040b0..0000000 --- a/app/src/main/java/com/pixelized/headache/ui/page/summary/year/item/YearSummaryDay.kt +++ /dev/null @@ -1,136 +0,0 @@ -package com.pixelized.headache.ui.page.summary.year.item - -import androidx.compose.foundation.background -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Stable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import com.pixelized.headache.ui.theme.HeadacheTheme -import com.pixelized.headache.ui.theme.color.HeadacheColorPalette - -@Stable -object YearSummaryDayDefault { - @Stable - val size: Dp = 16.dp - - @Stable - val pill: Dp = 2.dp -} - -@Stable -data class YearSummaryDayUio( - val number: Int, - val headache: Boolean, - val pills: List, -) - -@Composable -fun YearSummaryDay( - modifier: Modifier = Modifier, - size: Dp = YearSummaryDayDefault.size, - pill: Dp = YearSummaryDayDefault.pill, - day: YearSummaryDayUio, -) { - Box( - modifier = Modifier - .size(size = size) - .then(other = modifier), - ) { - if (day.headache) { - Box( - modifier = Modifier - .matchParentSize() - .padding(vertical = pill + 1.dp) - .background(color = HeadacheColorPalette.Pill.Unknown) - ) - } - - Text( - modifier = Modifier.align(alignment = Alignment.Center), - style = MaterialTheme.typography.labelSmall, - color = when { - !isSystemInDarkTheme() && day.headache -> Color.White - else -> MaterialTheme.colorScheme.onSurface - }, - text = "${day.number}", - ) - - if (day.headache) { - Row( - modifier = Modifier.align(alignment = Alignment.BottomCenter), - horizontalArrangement = Arrangement.spacedBy(space = 1.dp), - ) { - day.pills.forEach { - Box( - modifier = Modifier - .background(color = it) - .size(size = pill) - ) - } - } - } - } -} - -@Composable -@Preview -private fun YearSummaryDayPreview( - @PreviewParameter(DayPreviewProvider::class) preview: YearSummaryDayUio, -) { - HeadacheTheme { - Surface { - YearSummaryDay( - modifier = Modifier.size(size = 16.dp), - day = preview, - ) - } - } -} - -private class DayPreviewProvider() : PreviewParameterProvider { - override val values: Sequence - get() = sequenceOf( - YearSummaryDayUio( - number = 1, - headache = false, - pills = emptyList(), - ), - YearSummaryDayUio( - number = 2, - headache = true, - pills = emptyList(), - ), - YearSummaryDayUio( - number = 3, - headache = true, - pills = listOf( - HeadacheColorPalette.Pill.Eletriptan40, - ), - ), - YearSummaryDayUio( - number = 3, - headache = true, - pills = listOf( - HeadacheColorPalette.Pill.Eletriptan40, - HeadacheColorPalette.Pill.Ibuprofene400, - HeadacheColorPalette.Pill.Spifen400, - HeadacheColorPalette.Pill.Paracetamol1000, - ), - ), - ) -} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/headache/ui/page/summary/year/item/YearSummaryMonth.kt b/app/src/main/java/com/pixelized/headache/ui/page/summary/year/item/YearSummaryMonth.kt deleted file mode 100644 index 1946042..0000000 --- a/app/src/main/java/com/pixelized/headache/ui/page/summary/year/item/YearSummaryMonth.kt +++ /dev/null @@ -1,243 +0,0 @@ -package com.pixelized.headache.ui.page.summary.year.item - -import android.annotation.SuppressLint -import android.icu.text.DateFormat -import android.icu.text.SimpleDateFormat -import android.icu.util.Calendar -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.offset -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.material3.ripple -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Stable -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import com.pixelized.headache.ui.theme.HeadacheTheme -import com.pixelized.headache.ui.theme.color.HeadacheColorPalette -import com.pixelized.headache.utils.extention.capitalize -import java.util.Date -import java.util.Locale - -@Stable -data object YearSummaryMonthDefault { - @SuppressLint("ConstantLocale") - @Stable - val formatter = SimpleDateFormat("MMMM", Locale.getDefault()) -} - -@Stable -data class YearSummaryMonthUio( - val date: Date, - val weeks: List>, -) - -@Composable -fun YearSummaryMonth( - modifier: Modifier = Modifier, - formatter: DateFormat = YearSummaryMonthDefault.formatter, - daySize: Dp, - uio: YearSummaryMonthUio, - onMonth: (YearSummaryMonthUio) -> Unit, -) { - Column( - modifier = Modifier - .clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = ripple(bounded = false), - onClick = { onMonth(uio) } - ) - .then(other = modifier), - ) { - Text( - modifier = Modifier.align(alignment = Alignment.Start), - style = MaterialTheme.typography.titleMedium, - text = remember(uio.date) { formatter.format(uio.date) }.capitalize(), - ) - Box( - modifier = Modifier.size(width = daySize * 7, height = daySize * 6), - ) { - uio.weeks.forEachIndexed { row, week -> - week.forEachIndexed { column, day -> - YearSummaryDay( - Modifier.offset( - x = when (row) { - 0 -> daySize * (column + 7 - week.size) - else -> daySize * column - }, - y = daySize * row, - ), - size = daySize, - day = day, - ) - } - } - } - } -} - -@Composable -@Preview -private fun YearSummaryMonthPreview( - @PreviewParameter(MonthPreviewProvider::class) preview: YearSummaryMonthUio, -) { - HeadacheTheme { - Surface { - YearSummaryMonth( - modifier = Modifier.padding(all = 8.dp), - daySize = YearSummaryDayDefault.size, - uio = preview, - onMonth = { }, - ) - } - } -} - -private class MonthPreviewProvider() : PreviewParameterProvider { - - private var day: Int = 1 - - private fun day(): () -> YearSummaryDayUio = { - YearSummaryDayUio( - number = day++, - headache = false, - pills = emptyList(), - ) - } - - private fun headache( - spifen: Boolean = false, - eletriptan: Boolean = false, - ): () -> YearSummaryDayUio = { - YearSummaryDayUio( - number = day++, - headache = true, - pills = listOfNotNull( - if (spifen) HeadacheColorPalette.Pill.Spifen400 else null, - if (eletriptan) HeadacheColorPalette.Pill.Eletriptan40 else null, - ), - ) - } - - // inline to avoid un sequential call to "day()" between default and overridden function parameters. - @Suppress("NOTHING_TO_INLINE") - private fun week( - mon: (() -> YearSummaryDayUio)? = day(), - tue: (() -> YearSummaryDayUio)? = day(), - wen: (() -> YearSummaryDayUio)? = day(), - thu: (() -> YearSummaryDayUio)? = day(), - fry: (() -> YearSummaryDayUio)? = day(), - sat: (() -> YearSummaryDayUio)? = day(), - sun: (() -> YearSummaryDayUio)? = day(), - ): List = listOfNotNull( - mon?.invoke(), - tue?.invoke(), - wen?.invoke(), - thu?.invoke(), - fry?.invoke(), - sat?.invoke(), - sun?.invoke(), - ) - - private fun month( - month: Int, - vararg weeks: List, - ): YearSummaryMonthUio { - day = 1 - return YearSummaryMonthUio( - date = java.util.Calendar.getInstance().apply { - set(java.util.Calendar.YEAR, 2025) - set(java.util.Calendar.MONTH, month) - }.time, - weeks = weeks.toList(), - ) - } - - override val values: Sequence - get() = sequenceOf( - month( - month = Calendar.MARCH, - week( - mon = null, - tue = null, - wen = null, - thu = null, - fry = null, - sat = headache(spifen = true) - ), - week(sat = headache(spifen = true)), - week( - wen = headache(spifen = true, eletriptan = true), - thu = headache(spifen = true, eletriptan = true), - fry = headache(spifen = true, eletriptan = true), - sat = headache(), - sun = headache(spifen = true, eletriptan = true) - ), - week( - mon = headache(), - tue = headache(), - sat = headache(spifen = true, eletriptan = true), - ), - week(thu = headache(), fry = headache(), sat = headache()), - week( - mon = headache(spifen = true, eletriptan = true), - tue = null, wen = null, thu = null, fry = null, sat = null, sun = null - ), - ), - month( - month = java.util.Calendar.JULY, - week(mon = null, sun = headache(spifen = true)), - week(wen = headache(spifen = true)), - week(sat = headache()), - week( - wen = headache(spifen = true), - thu = headache(), - sat = headache(spifen = true), - ), - week(wen = headache(), fry = null, sat = null, sun = null), - ), - month( - month = java.util.Calendar.AUGUST, - week(mon = null, tue = null, wen = null, thu = null), - week(wen = headache(), fry = headache()), - week(fry = headache(spifen = true)), - week(tue = headache(spifen = true), fry = headache()), - week( - tue = headache(spifen = true, eletriptan = true), - wen = headache(), - sat = headache(spifen = true), - ), - ), - month( - month = java.util.Calendar.SEPTEMBER, - week( - tue = headache(spifen = true, eletriptan = true), - wen = headache(spifen = true), - fry = headache(), - sat = headache(), - sun = headache(), - ), - week( - sun = headache(spifen = true), - ), - week( - wen = headache(spifen = true), - tue = headache(), - ), - week(), - week(wen = null, thu = null, fry = null, sat = null, sun = null), - ), - ) -} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/headache/ui/page/summary/year/YearSummaryFactory.kt b/app/src/main/java/com/pixelized/headache/ui/page/summary/yearly/YearSummaryFactory.kt similarity index 93% rename from app/src/main/java/com/pixelized/headache/ui/page/summary/year/YearSummaryFactory.kt rename to app/src/main/java/com/pixelized/headache/ui/page/summary/yearly/YearSummaryFactory.kt index 6b11433..6c3793d 100644 --- a/app/src/main/java/com/pixelized/headache/ui/page/summary/year/YearSummaryFactory.kt +++ b/app/src/main/java/com/pixelized/headache/ui/page/summary/yearly/YearSummaryFactory.kt @@ -1,12 +1,10 @@ -package com.pixelized.headache.ui.page.summary.year +package com.pixelized.headache.ui.page.summary.yearly import android.icu.util.Calendar import com.pixelized.headache.repository.event.Event -import com.pixelized.headache.ui.page.summary.year.item.YearSummaryDayUio -import com.pixelized.headache.ui.page.summary.year.item.YearSummaryMonthUio +import com.pixelized.headache.ui.page.summary.yearly.YearSummaryMonthUio.DayUio import javax.inject.Inject - class YearSummaryFactory @Inject constructor() { fun convertToUio( @@ -70,7 +68,7 @@ class YearSummaryFactory @Inject constructor() { val initial = MutableList( size = monthLastDayCalendar.get(Calendar.WEEK_OF_MONTH), ) { - mutableListOf() + mutableListOf() } val weeks = (1..monthLastDayCalendar.get(Calendar.DAY_OF_MONTH)) .fold(initial = initial) { accumulator, dayNumber -> @@ -83,7 +81,7 @@ class YearSummaryFactory @Inject constructor() { set(Calendar.MILLISECONDS_IN_DAY, 1) } .get(Calendar.WEEK_OF_MONTH) - 1 - val day = YearSummaryDayUio( + val day = DayUio( number = dayNumber, headache = event != null, pills = event?.pills?.map { it.color } ?: emptyList(), diff --git a/app/src/main/java/com/pixelized/headache/ui/page/summary/yearly/YearSummaryMonth.kt b/app/src/main/java/com/pixelized/headache/ui/page/summary/yearly/YearSummaryMonth.kt new file mode 100644 index 0000000..7961ea3 --- /dev/null +++ b/app/src/main/java/com/pixelized/headache/ui/page/summary/yearly/YearSummaryMonth.kt @@ -0,0 +1,346 @@ +package com.pixelized.headache.ui.page.summary.yearly + +import android.annotation.SuppressLint +import android.icu.text.DateFormat +import android.icu.text.SimpleDateFormat +import android.icu.util.Calendar +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.ripple +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithCache +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.TextMeasurer +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.drawText +import androidx.compose.ui.text.rememberTextMeasurer +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.pixelized.headache.ui.theme.HeadacheTheme +import com.pixelized.headache.ui.theme.color.HeadacheColorPalette +import com.pixelized.headache.utils.extention.capitalize +import java.util.Date +import java.util.Locale + +@Stable +data object YearSummaryMonthDefault { + + @Stable + val daySize: Dp = 16.dp + + @Stable + val pillSize: Dp = 2.dp + + @Stable + val pillSpace: Dp = 1.dp + + @SuppressLint("ConstantLocale") + @Stable + val formatter = SimpleDateFormat("MMMM", Locale.getDefault()) +} + +@Stable +data class YearSummaryMonthUio( + val date: Date, + val weeks: List>, +) { + @Stable + data class DayUio( + val number: Int, + val headache: Boolean, + val pills: List, + ) +} + +@Composable +fun YearSummaryMonth( + modifier: Modifier = Modifier, + formatter: DateFormat = YearSummaryMonthDefault.formatter, + defaultTextStyle: TextStyle, + headacheTextStyle: TextStyle, + daySize: Dp = YearSummaryMonthDefault.daySize, + pillSize: Dp = YearSummaryMonthDefault.pillSize, + pillSpace: Dp = YearSummaryMonthDefault.pillSpace, + uio: YearSummaryMonthUio, + onMonth: (YearSummaryMonthUio) -> Unit, +) { + val textMeasurer: TextMeasurer = rememberTextMeasurer() + val pill: Float = with(LocalDensity.current) { pillSize.toPx() } + val space: Float = with(LocalDensity.current) { pillSpace.toPx() } + + Column( + modifier = Modifier + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = ripple(bounded = false), + onClick = { onMonth(uio) } + ) + .then(other = modifier), + ) { + Text( + modifier = Modifier.align(alignment = Alignment.Start), + style = MaterialTheme.typography.titleMedium, + text = remember(uio.date) { formatter.format(uio.date) }.capitalize(), + ) + Box( + modifier = Modifier + .size(width = daySize * 7, height = daySize * 6) + .drawWithCache { + val cellSize = Size( + width = size.width / 7f, + height = size.height / 6f, + ) + val pillSize = Size( + width = pill, + height = pill, + ) + + onDrawBehind { + var localCellOffset: Offset = Offset.Zero + var localCellSize: Size = Size.Zero + + uio.weeks.forEachIndexed { row, week -> + week.forEachIndexed { column, day -> + // Compute current cell offset and size + localCellOffset = Offset( + x = when (row) { + 0 -> cellSize.width * (column + 7 - week.size) + else -> cellSize.width * column + }, + y = cellSize.height * row, + ) + localCellSize = Size( + width = cellSize.width, + height = cellSize.height, + ) + + // draw background + if (day.headache) { + drawRect( + topLeft = Offset( + x = localCellOffset.x - 1f, + y = localCellOffset.y + pillSize.height + space, + ), + size = Size( + width = localCellSize.width - 0f, + height = localCellSize.height - (pillSize.height + space) * 2, + ), + color = HeadacheColorPalette.Pill.Unknown, + ) + } + + // draw day number + val label = "${day.number}" + val measure = textMeasurer.measure(label, defaultTextStyle) + + drawText( + textMeasurer = textMeasurer, + text = label, + style = when { + day.headache -> headacheTextStyle + else -> defaultTextStyle + }, + topLeft = Offset( + x = localCellOffset.x + (localCellSize.width - measure.size.width) / 2, + y = localCellOffset.y + (localCellSize.height - measure.size.height) / 2, + ), + ) + + // draw pills + day.pills.forEachIndexed { index, pill -> + drawRect( + topLeft = Offset( + x = localCellOffset.x + (localCellSize.width - (pillSize.width + space) * day.pills.size) / 2f + index * (pillSize.width + space), + y = localCellOffset.y + localCellSize.height - pillSize.height, + ), + size = pillSize, + color = pill + ) + } + } + } + } + }, + ) + } +} + +@Composable +@Preview +private fun YearSummaryMonthPreview( + @PreviewParameter(MonthPreviewProvider::class) preview: YearSummaryMonthUio, +) { + HeadacheTheme { + Surface { + YearSummaryMonth( + modifier = Modifier.padding(all = 8.dp), + defaultTextStyle = MaterialTheme.typography.labelSmall, + headacheTextStyle = MaterialTheme.typography.labelSmall.copy(color = Color.White), + uio = preview, + onMonth = { }, + ) + } + } +} + +object MonthPreviewProviderHelper { + private var day: Int = 1 + + fun day(): () -> YearSummaryMonthUio.DayUio = { + YearSummaryMonthUio.DayUio( + number = day++, + headache = false, + pills = emptyList(), + ) + } + + fun headache( + spifen: Boolean = false, + eletriptan: Boolean = false, + ): () -> YearSummaryMonthUio.DayUio = { + YearSummaryMonthUio.DayUio( + number = day++, + headache = true, + pills = listOfNotNull( + if (spifen) HeadacheColorPalette.Pill.Spifen400 else null, + if (eletriptan) HeadacheColorPalette.Pill.Eletriptan40 else null, + ), + ) + } + + // inline to avoid un sequential call to "day()" between default and overridden function parameters. + @Suppress("NOTHING_TO_INLINE") + fun week( + mon: (() -> YearSummaryMonthUio.DayUio)? = day(), + tue: (() -> YearSummaryMonthUio.DayUio)? = day(), + wen: (() -> YearSummaryMonthUio.DayUio)? = day(), + thu: (() -> YearSummaryMonthUio.DayUio)? = day(), + fry: (() -> YearSummaryMonthUio.DayUio)? = day(), + sat: (() -> YearSummaryMonthUio.DayUio)? = day(), + sun: (() -> YearSummaryMonthUio.DayUio)? = day(), + ): List = listOfNotNull( + mon?.invoke(), + tue?.invoke(), + wen?.invoke(), + thu?.invoke(), + fry?.invoke(), + sat?.invoke(), + sun?.invoke(), + ) + + fun month( + month: Int, + year: Int, + vararg weeks: List, + ): YearSummaryMonthUio { + day = 1 + return YearSummaryMonthUio( + date = java.util.Calendar.getInstance().apply { + set(java.util.Calendar.YEAR, year) + set(java.util.Calendar.MONTH, month) + }.time, + weeks = weeks.toList(), + ) + } +} + +private class MonthPreviewProvider() : PreviewParameterProvider { + override val values: Sequence + get() = with(MonthPreviewProviderHelper) { + sequenceOf( + month( + month = Calendar.MARCH, + year = 2025, + week( + mon = null, + tue = null, + wen = null, + thu = null, + fry = null, + sat = headache(spifen = true) + ), + week(sat = headache(spifen = true)), + week( + wen = headache(spifen = true, eletriptan = true), + thu = headache(spifen = true, eletriptan = true), + fry = headache(spifen = true, eletriptan = true), + sat = headache(), + sun = headache(spifen = true, eletriptan = true) + ), + week( + mon = headache(), + tue = headache(), + sat = headache(spifen = true, eletriptan = true), + ), + week(thu = headache(), fry = headache(), sat = headache()), + week( + mon = headache(spifen = true, eletriptan = true), + tue = null, wen = null, thu = null, fry = null, sat = null, sun = null + ), + ), + month( + month = java.util.Calendar.JULY, + year = 2025, + week(mon = null, sun = headache(spifen = true)), + week(wen = headache(spifen = true)), + week(sat = headache()), + week( + wen = headache(spifen = true), + thu = headache(), + sat = headache(spifen = true), + ), + week(wen = headache(), fry = null, sat = null, sun = null), + ), + month( + month = java.util.Calendar.AUGUST, + year = 2025, + week(mon = null, tue = null, wen = null, thu = null), + week(wen = headache(), fry = headache()), + week(fry = headache(spifen = true)), + week(tue = headache(spifen = true), fry = headache()), + week( + tue = headache(spifen = true, eletriptan = true), + wen = headache(), + sat = headache(spifen = true), + ), + ), + month( + month = java.util.Calendar.SEPTEMBER, + year = 2025, + week( + tue = headache(spifen = true, eletriptan = true), + wen = headache(spifen = true), + fry = headache(), + sat = headache(), + sun = headache(), + ), + week( + sun = headache(spifen = true), + ), + week( + wen = headache(spifen = true), + tue = headache(), + ), + week(), + week(wen = null, thu = null, fry = null, sat = null, sun = null), + ), + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/headache/ui/page/summary/yearly/YearSummaryPage.kt b/app/src/main/java/com/pixelized/headache/ui/page/summary/yearly/YearSummaryPage.kt new file mode 100644 index 0000000..165a4c9 --- /dev/null +++ b/app/src/main/java/com/pixelized/headache/ui/page/summary/yearly/YearSummaryPage.kt @@ -0,0 +1,477 @@ +package com.pixelized.headache.ui.page.summary.yearly + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.GridItemSpan +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.Add +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.FloatingActionButton +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.keepScreenOn +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalWindowInfo +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.pixelized.headache.R +import com.pixelized.headache.ui.navigation.LocalNavigator +import com.pixelized.headache.ui.navigation.destination.navigateToEventPage +import com.pixelized.headache.ui.page.event.edit.EventEditBottomSheet +import com.pixelized.headache.ui.page.event.edit.EventEditBottomSheetViewModel +import com.pixelized.headache.ui.theme.HeadacheTheme +import com.pixelized.headache.ui.theme.headache +import com.pixelized.headache.utils.extention.calculate +import java.util.Calendar + +@Stable +data class YearUio( + val year: Int, + val months: List, +) + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun YearSummaryPage( + viewModel: YearSummaryViewModel = hiltViewModel(), + editViewModel: EventEditBottomSheetViewModel = hiltViewModel(), +) { + val navigation = LocalNavigator.current + val uio = viewModel.events.collectAsStateWithLifecycle() + + YearSummaryContent( + modifier = Modifier + .systemBarsPadding() + .keepScreenOn() + .fillMaxSize(), + uio = uio, + onBack = { + navigation.popBackstack() + }, + onMonth = { + navigation.navigateToEventPage(date = it.date) + }, + onAddEvent = { + editViewModel.show() + }, + ) + + EventEditBottomSheet( + viewModel = editViewModel, + onDismissRequest = { editViewModel.dismiss() }, + ) +} + +@Composable +private fun rememberDaySize( + column: Int, + space: Dp, + paddingValues: PaddingValues, +): Dp { + val density = LocalDensity.current + val windowInfo = LocalWindowInfo.current + val screenWidth = remember(density, windowInfo) { + with(density) { windowInfo.containerSize.width.toDp() } + } + val (start, _, end, _) = paddingValues.calculate() + return remember { + (screenWidth - space * (column - 1) - start - end) / (7 * column) + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun YearSummaryContent( + modifier: Modifier = Modifier, + paddingValues: PaddingValues = PaddingValues( + start = 16.dp, + top = 16.dp, + end = 16.dp, + bottom = 16.dp + 16.dp + 56.dp, + ), + space: Dp = 8.dp, + daySize: Dp = rememberDaySize( + column = 3, + paddingValues = paddingValues, + space = space, + ), + uio: State>, + onBack: () -> Unit, + onMonth: (YearSummaryMonthUio) -> Unit, + onAddEvent: () -> Unit, +) { + Scaffold( + modifier = modifier, + topBar = { + TopAppBar( + navigationIcon = { + IconButton( + onClick = onBack, + ) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = null, + ) + } + }, + title = { + Text(text = stringResource(R.string.year_summary_title)) + }, + ) + }, + floatingActionButton = { + FloatingActionButton( + containerColor = MaterialTheme.colorScheme.primary, + shape = CircleShape, + onClick = onAddEvent, + ) { + Icon( + imageVector = Icons.Default.Add, + contentDescription = null, + ) + } + }, + content = { it -> + val typography = MaterialTheme.typography + val colorScheme = MaterialTheme.headache.colorScheme + val defaultTextStyle = remember(typography, colorScheme) { + typography.labelSmall.copy(color = colorScheme.base.onSurface) + } + val headacheTextStyle = remember(typography, colorScheme) { + defaultTextStyle.copy(color = colorScheme.calendar.onHeadache) + } + LazyVerticalGrid( + modifier = Modifier.padding(paddingValues = it), + columns = GridCells.Adaptive(minSize = daySize * 7), + horizontalArrangement = Arrangement.spacedBy(space = space), + verticalArrangement = Arrangement.spacedBy(space = space), + contentPadding = paddingValues, + ) { + uio.value.forEachIndexed { index, (year, months) -> + item( + span = { GridItemSpan(maxLineSpan) }, + contentType = { "Title" }, + ) { + Text( + modifier = Modifier.padding( + top = when (index) { + 0 -> 0.dp + else -> 16.dp + } + ), + style = MaterialTheme.typography.displaySmall, + text = "$year", + ) + } + items( + items = months, + key = { it.date.time }, + contentType = { "Month" }, + ) { + YearSummaryMonth( + uio = it, + defaultTextStyle = defaultTextStyle, + headacheTextStyle = headacheTextStyle, + daySize = daySize, + onMonth = onMonth, + ) + } + } + } + } + ) +} + +@Composable +@Preview() +private fun YearSummaryPreview( + @PreviewParameter(YearPreviewProvider::class) preview: List, +) { + HeadacheTheme { + Surface { + YearSummaryContent( + uio = remember { mutableStateOf(preview) }, + onBack = { }, + onMonth = { }, + onAddEvent = { }, + ) + } + } +} + +private class YearPreviewProvider() : PreviewParameterProvider> { + + private fun year( + year: Int, + vararg months: YearSummaryMonthUio, + ) = YearUio( + year = year, + months = months.toList(), + ) + + override val values: Sequence> + get() = with(MonthPreviewProviderHelper) { + sequenceOf( + listOf( + year( + year = 2025, + month( + month = Calendar.JANUARY, + year = 2025, + week(mon = null, tue = null), + week(), + week(), + week(), + week(sat = null, sun = null), + ), + month( + month = Calendar.FEBRUARY, + year = 2025, + week(mon = null, tue = null, wen = null, thu = null, fry = null), + week(), + week(), + week(), + week(sat = null, sun = null), + ), + month( + month = Calendar.MARCH, + year = 2025, + week(mon = null, tue = null, wen = null, thu = null, fry = null), + week(), + week(), + week(), + week(), + week( + tue = null, + wen = null, + thu = null, + fry = null, + sat = null, + sun = null + ), + ), + month( + month = Calendar.APRIL, + year = 2025, + week(mon = null), + week(), + week(), + week(), + week(thu = null, fry = null, sat = null, sun = null), + ), + month( + month = Calendar.MAY, + year = 2025, + week(mon = null, tue = null, wen = null), + week(), + week(), + week(), + week(sun = null), + ), + month( + month = Calendar.JUNE, + year = 2025, + week( + mon = null, + tue = null, + wen = null, + thu = null, + fry = null, + sat = null + ), + week(), + week(), + week(), + week(), + week( + tue = null, + wen = null, + thu = null, + fry = null, + sat = null, + sun = null + ), + ), + month( + month = Calendar.JULY, + year = 2025, + week(mon = null, sun = headache(spifen = true)), + week(wen = headache(spifen = true)), + week(sat = headache()), + week( + wen = headache(spifen = true), + thu = headache(), + sat = headache(spifen = true), + ), + week(wen = headache(), fry = null, sat = null, sun = null), + ), + month( + month = Calendar.AUGUST, + year = 2025, + week(mon = null, tue = null, wen = null, thu = null), + week(wen = headache(), fry = headache()), + week(fry = headache(spifen = true)), + week(tue = headache(spifen = true), fry = headache()), + week( + tue = headache(spifen = true, eletriptan = true), + wen = headache(), + sat = headache(spifen = true), + ), + ), + month( + month = Calendar.SEPTEMBER, + year = 2025, + week( + tue = headache(spifen = true, eletriptan = true), + wen = headache(spifen = true), + fry = headache(), + sat = headache(), + sun = headache(), + ), + week( + sun = headache(spifen = true), + ), + week( + wen = headache(spifen = true), + tue = headache(), + ), + week(), + week(wen = null, thu = null, fry = null, sat = null, sun = null), + ), + month( + month = Calendar.OCTOBER, + year = 2025, + week(mon = null, tue = null), + week(), + week(), + week(), + week(sat = null, sun = null), + ), + month( + month = Calendar.NOVEMBER, + year = 2025, + week(mon = null, tue = null, wen = null, thu = null, fry = null), + week(), + week(), + week(), + week(), + ), + month( + month = Calendar.DECEMBER, + year = 2025, + week(), + week(), + week(), + week(), + week(thu = null, fry = null, sat = null, sun = null), + ), + ), + year( + year = 2024, + month( + month = Calendar.JANUARY, + year = 2024, + week(mon = null, tue = null), + week(), + week(), + week(), + week(sat = null, sun = null), + ), + month( + month = Calendar.FEBRUARY, + year = 2024, + week(mon = null, tue = null, wen = null, thu = null, fry = null), + week(), + week(), + week(), + week(sat = null, sun = null), + ), + month( + month = Calendar.MARCH, + year = 2024, + week(mon = null, tue = null, wen = null, thu = null, fry = null), + week(), + week(), + week(), + week(), + week( + tue = null, + wen = null, + thu = null, + fry = null, + sat = null, + sun = null + ), + ), + month( + month = Calendar.APRIL, + year = 2024, + week(mon = null), + week(), + week(), + week(), + week(thu = null, fry = null, sat = null, sun = null), + ), + month( + month = Calendar.MAY, + year = 2024, + week(mon = null, tue = null, wen = null), + week(), + week(), + week(), + week(sun = null), + ), + month( + month = Calendar.JUNE, + year = 2024, + week( + mon = null, + tue = null, + wen = null, + thu = null, + fry = null, + sat = null + ), + week(), + week(), + week(), + week(), + week( + tue = null, + wen = null, + thu = null, + fry = null, + sat = null, + sun = null + ), + ), + ) + ), + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/headache/ui/page/summary/year/YearSummaryViewModel.kt b/app/src/main/java/com/pixelized/headache/ui/page/summary/yearly/YearSummaryViewModel.kt similarity index 93% rename from app/src/main/java/com/pixelized/headache/ui/page/summary/year/YearSummaryViewModel.kt rename to app/src/main/java/com/pixelized/headache/ui/page/summary/yearly/YearSummaryViewModel.kt index 56ab4a5..886d8c8 100644 --- a/app/src/main/java/com/pixelized/headache/ui/page/summary/year/YearSummaryViewModel.kt +++ b/app/src/main/java/com/pixelized/headache/ui/page/summary/yearly/YearSummaryViewModel.kt @@ -1,4 +1,4 @@ -package com.pixelized.headache.ui.page.summary.year +package com.pixelized.headache.ui.page.summary.yearly import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope diff --git a/app/src/main/java/com/pixelized/headache/ui/theme/Theme.kt b/app/src/main/java/com/pixelized/headache/ui/theme/Theme.kt index e22dca7..b0955df 100644 --- a/app/src/main/java/com/pixelized/headache/ui/theme/Theme.kt +++ b/app/src/main/java/com/pixelized/headache/ui/theme/Theme.kt @@ -3,6 +3,7 @@ package com.pixelized.headache.ui.theme import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.Stable import androidx.compose.runtime.compositionLocalOf @@ -10,15 +11,15 @@ import com.pixelized.headache.ui.theme.color.HeadacheColors import com.pixelized.headache.ui.theme.color.headacheDarkColorScheme import com.pixelized.headache.ui.theme.color.headacheLightColorScheme -val LocalLwaTheme = compositionLocalOf { - error("Local Snack Controller is not yet ready") +val LocalHeadacheTheme = compositionLocalOf { + error("Local Theme Controller is not yet ready") } val MaterialTheme.headache: HeadacheTheme @Stable @Composable @ReadOnlyComposable - get() = LocalLwaTheme.current + get() = LocalHeadacheTheme.current @Stable data class HeadacheTheme( @@ -30,14 +31,19 @@ fun HeadacheTheme( darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit, ) { - val colorScheme = when { - darkTheme -> headacheDarkColorScheme() - else -> headacheLightColorScheme() - } - - MaterialTheme( - colorScheme = colorScheme.base, - typography = Typography, - content = content + val theme = HeadacheTheme( + colorScheme = when { + darkTheme -> headacheDarkColorScheme() + else -> headacheLightColorScheme() + } ) + CompositionLocalProvider( + LocalHeadacheTheme provides theme, + ) { + MaterialTheme( + colorScheme = theme.colorScheme.base, + typography = Typography, + content = content + ) + } } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/headache/ui/theme/color/HeadacheColors.kt b/app/src/main/java/com/pixelized/headache/ui/theme/color/HeadacheColors.kt index 0e9efdd..8ce3d84 100644 --- a/app/src/main/java/com/pixelized/headache/ui/theme/color/HeadacheColors.kt +++ b/app/src/main/java/com/pixelized/headache/ui/theme/color/HeadacheColors.kt @@ -16,25 +16,67 @@ import kotlin.math.ln @Stable data class HeadacheColors( val base: ColorScheme, -) + val calendar: Calendar, + val pill: Pill, +) { + @Stable + data class Calendar( + val headache: Color, + val onHeadache: Color, + ) + + @Stable + data class Pill( + val unknown: Color, + val ibuprofene400: Color, + val paracetamol1000: Color, + val spifen400: Color, + val eletriptan40: Color, + ) +} @Composable @Stable fun headacheDarkColorScheme( base: ColorScheme = darkColorScheme(), + calendar: HeadacheColors.Calendar = HeadacheColors.Calendar( + headache = HeadacheColorPalette.Additional.Red, + onHeadache = Color.White, + ), + pill: HeadacheColors.Pill = HeadacheColors.Pill( + unknown = HeadacheColorPalette.Pill.Unknown, + ibuprofene400 = HeadacheColorPalette.Pill.Ibuprofene400, + paracetamol1000 = HeadacheColorPalette.Pill.Paracetamol1000, + spifen400 = HeadacheColorPalette.Pill.Spifen400, + eletriptan40 = HeadacheColorPalette.Pill.Eletriptan40, + ), ) = HeadacheColors( base = base, + calendar = calendar, + pill = pill, ) @Composable @Stable fun headacheLightColorScheme( base: ColorScheme = lightColorScheme(), + calendar: HeadacheColors.Calendar = HeadacheColors.Calendar( + headache = HeadacheColorPalette.Additional.Red, + onHeadache = Color.White, + ), + pill: HeadacheColors.Pill = HeadacheColors.Pill( + unknown = HeadacheColorPalette.Pill.Unknown, + ibuprofene400 = HeadacheColorPalette.Pill.Ibuprofene400, + paracetamol1000 = HeadacheColorPalette.Pill.Paracetamol1000, + spifen400 = HeadacheColorPalette.Pill.Spifen400, + eletriptan40 = HeadacheColorPalette.Pill.Eletriptan40, + ), ) = HeadacheColors( base = base, + calendar = calendar, + pill = pill, ) - @ReadOnlyComposable @Composable fun calculateElevatedColor(