From 1bbd523e882da301002195ddd6270e9b0d0d203f Mon Sep 17 00:00:00 2001 From: "Andres Gomez, Thomas (ITDV RL)" Date: Wed, 24 Sep 2025 11:02:10 +0200 Subject: [PATCH] Prettify a bit the report UI. --- .../page/summary/monthly/MonthSummaryPage.kt | 142 ++++++++++++++---- .../ui/page/summary/report/ReportBox.kt | 8 +- .../ui/page/summary/report/ReportPage.kt | 113 ++++++++++---- .../ui/page/summary/yearly/YearSummaryPage.kt | 14 +- 4 files changed, 217 insertions(+), 60 deletions(-) diff --git a/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/MonthSummaryPage.kt b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/MonthSummaryPage.kt index efa3bfb..3c66f27 100644 --- a/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/MonthSummaryPage.kt +++ b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/MonthSummaryPage.kt @@ -1,12 +1,18 @@ package com.pixelized.headache.ui.page.summary.monthly +import android.icu.util.Calendar +import androidx.compose.foundation.gestures.FlingBehavior +import androidx.compose.foundation.gestures.snapping.SnapPosition +import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.NavigationBarDefaults import androidx.compose.material3.Scaffold @@ -22,6 +28,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.keepScreenOn 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 @@ -36,7 +44,6 @@ 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 @Stable data object MonthSummaryPageDefault { @@ -72,6 +79,11 @@ fun MonthSummaryPage( @OptIn(ExperimentalMaterial3Api::class) private fun MonthSummaryContent( modifier: Modifier = Modifier, + state: LazyListState = rememberLazyListState(), + flingBehavior: FlingBehavior = rememberSnapFlingBehavior( + lazyListState = state, + snapPosition = SnapPosition.Start, + ), spacing: Dp = MonthSummaryPageDefault.spacing, listPadding: PaddingValues = MonthSummaryPageDefault.listPadding, events: State>>, @@ -95,6 +107,8 @@ private fun MonthSummaryContent( modifier = Modifier .fillMaxSize() .padding(paddingValues = paddingValues), + state = state, + flingBehavior = flingBehavior, contentPadding = listPadding, verticalArrangement = Arrangement.spacedBy(space = spacing), reverseLayout = false, @@ -123,37 +137,109 @@ private fun MonthSummaryContent( @Composable @Preview -private fun MonthSummaryPreview() { +private fun MonthSummaryPreview( + @PreviewParameter(MonthSummaryPreviewProvider::class) preview: Map>, +) { HeadacheTheme { - val events = remember { - mutableStateOf( - mapOf( - MonthSummaryTitleUio( - date = Date(), - ) to listOf( - MonthSummaryItemUio( - date = Date(), - days = 8, - pills = listOf( - MonthSummaryPillItemUio( - label = "Spifen 400", - amount = 4, - color = HeadacheColorPalette.Pill.Spifen400, - ), - MonthSummaryPillItemUio( - label = "Élétriptan 40", - amount = 2, - color = HeadacheColorPalette.Pill.Eletriptan40, - ), - ), - ), - ), - ) - ) - } + val events = remember { mutableStateOf(preview) } MonthSummaryContent( events = events, onItem = { }, ) } +} + +private class MonthSummaryPreviewProvider : + PreviewParameterProvider>> { + + private fun date( + year: Int, + ) = Calendar.getInstance().also { + it.set(Calendar.YEAR, year) + it.set(Calendar.MONTH, Calendar.JANUARY) + it.set(Calendar.DAY_OF_MONTH, 1) + it.set(Calendar.MILLISECONDS_IN_DAY, 1) + } + + private fun Calendar.year( + vararg month: MonthSummaryItemUio, + ): Pair> { + this.add(Calendar.YEAR, -1) + return MonthSummaryTitleUio( + date = this.time + ) to month.toList() + } + + private fun Calendar.month( + headache: Int = 0, + eletriptan: Int = 0, + ibuprofen: Int = 0, + paracetamol: Int = 0, + spifen: Int = 0, + ): MonthSummaryItemUio { + val date = this.time + this.add(Calendar.MONTH, 1) + return MonthSummaryItemUio( + date = date, + days = headache, + pills = listOfNotNull( + eletriptan.takeIf { it > 0 }?.let { + MonthSummaryPillItemUio( + label = "Élétriptan 40", + amount = it, + color = HeadacheColorPalette.Pill.Eletriptan40, + ) + }, + ibuprofen.takeIf { it > 0 }?.let { + MonthSummaryPillItemUio( + label = "Ibuprofène 400", + amount = it, + color = HeadacheColorPalette.Pill.Ibuprofene400, + ) + }, + paracetamol.takeIf { it > 0 }?.let { + MonthSummaryPillItemUio( + label = "Paracetamol 1000", + amount = it, + color = HeadacheColorPalette.Pill.Paracetamol1000, + ) + }, + spifen.takeIf { it > 0 }?.let { + MonthSummaryPillItemUio( + label = "Spifen 400", + amount = it, + color = HeadacheColorPalette.Pill.Spifen400, + ) + }, + ), + ) + } + + override val values: Sequence>> + get() = sequenceOf( + mapOf( + with(date(year = 2025)) { + year( + month(headache = 6, eletriptan = 4, spifen = 6), + month(headache = 15, eletriptan = 9, paracetamol = 2, spifen = 14), + month(headache = 14, eletriptan = 6, spifen = 10), + month(headache = 16, eletriptan = 7, spifen = 11), + month(headache = 14, eletriptan = 7, ibuprofen = 1, spifen = 12), + month(headache = 12, eletriptan = 3, spifen = 10), + month(headache = 7, spifen = 3), + month(headache = 8, eletriptan = 1, spifen = 4), + month(headache = 8, eletriptan = 1, spifen = 4), + month(), + month(), + month(), + ) + }, + with(date(year = 2024)) { + year( + month(), + month(), + ) + } + ) + ) } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/headache/ui/page/summary/report/ReportBox.kt b/app/src/main/java/com/pixelized/headache/ui/page/summary/report/ReportBox.kt index 936bac6..a5bfa42 100644 --- a/app/src/main/java/com/pixelized/headache/ui/page/summary/report/ReportBox.kt +++ b/app/src/main/java/com/pixelized/headache/ui/page/summary/report/ReportBox.kt @@ -5,6 +5,7 @@ import android.icu.text.DateFormat import android.icu.text.SimpleDateFormat import android.icu.util.Calendar import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -67,7 +68,7 @@ object ReportBoxDefault { @Stable val barSize: DpSize = DpSize( width = 28.dp, - height = 320.dp, + height = 240.dp, ) @Stable @@ -90,6 +91,7 @@ fun ReportBox( barSpace: Dp = ReportBoxDefault.barSpace, titleSpace: Dp = ReportBoxDefault.titleSpace, item: ReportBoxUio, + onMonth: (ReportBoxUio.Month) -> Unit, ) { Column( modifier = modifier, @@ -109,6 +111,7 @@ fun ReportBox( barSize = barSize, barShape = barShape, item = it, + onItem = onMonth, ) } } @@ -122,10 +125,12 @@ private fun Month( barSize: DpSize = ReportBoxDefault.barSize, barShape: Shape = ReportBoxDefault.barShape, item: ReportBoxUio.Month, + onItem: (ReportBoxUio.Month) -> Unit, ) { Column( modifier = Modifier .width(width = barSize.width) + .clickable { onItem(item) } .then(other = modifier), horizontalAlignment = Alignment.CenterHorizontally, ) { @@ -174,6 +179,7 @@ private fun ReportBoxPreview( Surface { ReportBox( item = preview, + onMonth = { }, ) } } diff --git a/app/src/main/java/com/pixelized/headache/ui/page/summary/report/ReportPage.kt b/app/src/main/java/com/pixelized/headache/ui/page/summary/report/ReportPage.kt index d90bbf7..996d51b 100644 --- a/app/src/main/java/com/pixelized/headache/ui/page/summary/report/ReportPage.kt +++ b/app/src/main/java/com/pixelized/headache/ui/page/summary/report/ReportPage.kt @@ -3,6 +3,9 @@ package com.pixelized.headache.ui.page.summary.report import android.icu.util.Calendar import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.FlingBehavior +import androidx.compose.foundation.gestures.snapping.SnapPosition +import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -13,8 +16,15 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Info +import androidx.compose.material3.DropdownMenu import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.NavigationBarDefaults import androidx.compose.material3.Scaffold @@ -42,6 +52,8 @@ 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.destination.navigateToEventPage +import com.pixelized.headache.ui.navigation.main.LocalMainNavigator import com.pixelized.headache.ui.theme.HeadacheTheme import com.pixelized.headache.ui.theme.color.HeadacheColorPalette import com.pixelized.headache.utils.extention.calculate @@ -66,6 +78,7 @@ data object ReportPageDefault { fun ReportPage( viewModel: ReportViewModel = hiltViewModel(), ) { + val navigator = LocalMainNavigator.current val events = viewModel.events.collectAsStateWithLifecycle() ReportContent( @@ -73,6 +86,9 @@ fun ReportPage( .keepScreenOn() .fillMaxSize(), events = events, + onMonth = { + navigator.navigateToEventPage(date = it.date) + }, ) } @@ -80,11 +96,17 @@ fun ReportPage( @Composable private fun ReportContent( modifier: Modifier = Modifier, + state: LazyListState = rememberLazyListState(), + flingBehavior: FlingBehavior = rememberSnapFlingBehavior( + lazyListState = state, + snapPosition = SnapPosition.Start, + ), paddingValues: PaddingValues = ReportPageDefault.paddingValues, contentSpace: Dp = ReportPageDefault.contentSpace, barSpace: Dp = ReportPageDefault.barSpace, barSize: DpSize = rememberBarSize(column = 12, paddingValues = paddingValues, space = barSpace), events: State>, + onMonth: (ReportBoxUio.Month) -> Unit, ) { Scaffold( modifier = modifier, @@ -95,42 +117,27 @@ private fun ReportContent( containerColor = NavigationBarDefaults.containerColor, ), title = { - Text(text = stringResource(R.string.year_summary_title)) + Text( + text = stringResource(R.string.year_summary_title), + ) }, actions = { - Column( - modifier = Modifier.padding(end = 16.dp) - ) { - Row( - horizontalArrangement = Arrangement.spacedBy(space = 4.dp), - verticalAlignment = Alignment.CenterVertically, + Box { + val expanded = remember { mutableStateOf(false) } + IconButton( + onClick = { expanded.value = true }, ) { - Box( - modifier = Modifier - .size(8.dp) - .background( - color = HeadacheColorPalette.Calendar.Headache, - ) - ) - Text( - style = MaterialTheme.typography.labelSmall, - text = "Jours de migraine", + Icon( + imageVector = Icons.Default.Info, + contentDescription = null, ) } - Row( - horizontalArrangement = Arrangement.spacedBy(space = 4.dp), - verticalAlignment = Alignment.CenterVertically, + DropdownMenu( + expanded = expanded.value, + onDismissRequest = { expanded.value = false } ) { - Box( - modifier = Modifier - .size(8.dp) - .background( - color = HeadacheColorPalette.Calendar.Pill, - ) - ) - Text( - style = MaterialTheme.typography.labelSmall, - text = "Prise de cachet", + Legend( + modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp) ) } } @@ -140,6 +147,8 @@ private fun ReportContent( content = { it -> LazyColumn( modifier = Modifier.padding(paddingValues = it), + state = state, + flingBehavior = flingBehavior, contentPadding = paddingValues, verticalArrangement = Arrangement.spacedBy(space = contentSpace), ) { @@ -150,6 +159,7 @@ private fun ReportContent( barSize = barSize, barSpace = barSpace, item = item, + onMonth = onMonth, ) } } @@ -157,6 +167,48 @@ private fun ReportContent( ) } +@Composable +private fun Legend( + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier, + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(space = 4.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + modifier = Modifier + .size(8.dp) + .background( + color = HeadacheColorPalette.Calendar.Headache, + ) + ) + Text( + style = MaterialTheme.typography.labelSmall, + text = "Jours de migraine", + ) + } + Row( + horizontalArrangement = Arrangement.spacedBy(space = 4.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + modifier = Modifier + .size(8.dp) + .background( + color = HeadacheColorPalette.Calendar.Pill, + ) + ) + Text( + style = MaterialTheme.typography.labelSmall, + text = "Prise de cachet", + ) + } + } +} + @Composable private fun rememberBarSize( column: Int, @@ -186,6 +238,7 @@ private fun ReportPreview( Surface { ReportContent( events = remember { mutableStateOf(preview) }, + onMonth = { }, ) } } 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 index 216522e..121f1ec 100644 --- 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 @@ -1,5 +1,8 @@ package com.pixelized.headache.ui.page.summary.yearly +import androidx.compose.foundation.gestures.FlingBehavior +import androidx.compose.foundation.gestures.snapping.SnapPosition +import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.WindowInsets @@ -7,8 +10,10 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridItemSpan +import androidx.compose.foundation.lazy.grid.LazyGridState import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items +import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.NavigationBarDefaults @@ -87,6 +92,11 @@ fun YearSummaryPage( @Composable fun YearSummaryContent( modifier: Modifier = Modifier, + state: LazyGridState = rememberLazyGridState(), + flingBehavior: FlingBehavior = rememberSnapFlingBehavior( + lazyGridState = state, + snapPosition = SnapPosition.Start, + ), paddingValues: PaddingValues = YearSummaryPageDefault.paddingValues, space: Dp = YearSummaryPageDefault.space, daySize: Dp = rememberDaySize(column = YearSummaryPageDefault.column), @@ -117,6 +127,8 @@ fun YearSummaryContent( } LazyVerticalGrid( modifier = Modifier.padding(paddingValues = it), + state = state, + flingBehavior = flingBehavior, columns = GridCells.Adaptive(minSize = daySize * 7), horizontalArrangement = Arrangement.spacedBy(space = space), verticalArrangement = Arrangement.spacedBy(space = space), @@ -128,7 +140,7 @@ fun YearSummaryContent( contentType = { "Title" }, ) { Text( - modifier = Modifier.padding(top = 16.dp), + modifier = Modifier.padding(top = 16.dp), style = MaterialTheme.typography.displaySmall, text = "$year", )