Prettify a bit the report UI.

This commit is contained in:
Andres Gomez, Thomas (ITDV RL) 2025-09-24 11:02:10 +02:00
parent 3645b92a82
commit 1bbd523e88
4 changed files with 217 additions and 60 deletions

View file

@ -1,12 +1,18 @@
package com.pixelized.headache.ui.page.summary.monthly 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.Arrangement
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.NavigationBarDefaults import androidx.compose.material3.NavigationBarDefaults
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
@ -22,6 +28,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.keepScreenOn import androidx.compose.ui.keepScreenOn
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview 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.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel 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.page.summary.monthly.item.MonthSummaryTitleUio
import com.pixelized.headache.ui.theme.HeadacheTheme import com.pixelized.headache.ui.theme.HeadacheTheme
import com.pixelized.headache.ui.theme.color.HeadacheColorPalette import com.pixelized.headache.ui.theme.color.HeadacheColorPalette
import java.util.Date
@Stable @Stable
data object MonthSummaryPageDefault { data object MonthSummaryPageDefault {
@ -72,6 +79,11 @@ fun MonthSummaryPage(
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
private fun MonthSummaryContent( private fun MonthSummaryContent(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
state: LazyListState = rememberLazyListState(),
flingBehavior: FlingBehavior = rememberSnapFlingBehavior(
lazyListState = state,
snapPosition = SnapPosition.Start,
),
spacing: Dp = MonthSummaryPageDefault.spacing, spacing: Dp = MonthSummaryPageDefault.spacing,
listPadding: PaddingValues = MonthSummaryPageDefault.listPadding, listPadding: PaddingValues = MonthSummaryPageDefault.listPadding,
events: State<Map<MonthSummaryTitleUio, List<MonthSummaryItemUio>>>, events: State<Map<MonthSummaryTitleUio, List<MonthSummaryItemUio>>>,
@ -95,6 +107,8 @@ private fun MonthSummaryContent(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.padding(paddingValues = paddingValues), .padding(paddingValues = paddingValues),
state = state,
flingBehavior = flingBehavior,
contentPadding = listPadding, contentPadding = listPadding,
verticalArrangement = Arrangement.spacedBy(space = spacing), verticalArrangement = Arrangement.spacedBy(space = spacing),
reverseLayout = false, reverseLayout = false,
@ -123,37 +137,109 @@ private fun MonthSummaryContent(
@Composable @Composable
@Preview @Preview
private fun MonthSummaryPreview() { private fun MonthSummaryPreview(
@PreviewParameter(MonthSummaryPreviewProvider::class) preview: Map<MonthSummaryTitleUio, List<MonthSummaryItemUio>>,
) {
HeadacheTheme { HeadacheTheme {
val events = remember { val events = remember { mutableStateOf(preview) }
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,
),
),
),
),
)
)
}
MonthSummaryContent( MonthSummaryContent(
events = events, events = events,
onItem = { }, onItem = { },
) )
} }
}
private class MonthSummaryPreviewProvider :
PreviewParameterProvider<Map<MonthSummaryTitleUio, List<MonthSummaryItemUio>>> {
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<MonthSummaryTitleUio, List<MonthSummaryItemUio>> {
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<Map<MonthSummaryTitleUio, List<MonthSummaryItemUio>>>
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(),
)
}
)
)
} }

View file

@ -5,6 +5,7 @@ import android.icu.text.DateFormat
import android.icu.text.SimpleDateFormat import android.icu.text.SimpleDateFormat
import android.icu.util.Calendar import android.icu.util.Calendar
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@ -67,7 +68,7 @@ object ReportBoxDefault {
@Stable @Stable
val barSize: DpSize = DpSize( val barSize: DpSize = DpSize(
width = 28.dp, width = 28.dp,
height = 320.dp, height = 240.dp,
) )
@Stable @Stable
@ -90,6 +91,7 @@ fun ReportBox(
barSpace: Dp = ReportBoxDefault.barSpace, barSpace: Dp = ReportBoxDefault.barSpace,
titleSpace: Dp = ReportBoxDefault.titleSpace, titleSpace: Dp = ReportBoxDefault.titleSpace,
item: ReportBoxUio, item: ReportBoxUio,
onMonth: (ReportBoxUio.Month) -> Unit,
) { ) {
Column( Column(
modifier = modifier, modifier = modifier,
@ -109,6 +111,7 @@ fun ReportBox(
barSize = barSize, barSize = barSize,
barShape = barShape, barShape = barShape,
item = it, item = it,
onItem = onMonth,
) )
} }
} }
@ -122,10 +125,12 @@ private fun Month(
barSize: DpSize = ReportBoxDefault.barSize, barSize: DpSize = ReportBoxDefault.barSize,
barShape: Shape = ReportBoxDefault.barShape, barShape: Shape = ReportBoxDefault.barShape,
item: ReportBoxUio.Month, item: ReportBoxUio.Month,
onItem: (ReportBoxUio.Month) -> Unit,
) { ) {
Column( Column(
modifier = Modifier modifier = Modifier
.width(width = barSize.width) .width(width = barSize.width)
.clickable { onItem(item) }
.then(other = modifier), .then(other = modifier),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
@ -174,6 +179,7 @@ private fun ReportBoxPreview(
Surface { Surface {
ReportBox( ReportBox(
item = preview, item = preview,
onMonth = { },
) )
} }
} }

View file

@ -3,6 +3,9 @@ package com.pixelized.headache.ui.page.summary.report
import android.icu.util.Calendar import android.icu.util.Calendar
import androidx.compose.foundation.background 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.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column 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.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.items 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.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBarDefaults import androidx.compose.material3.NavigationBarDefaults
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
@ -42,6 +52,8 @@ import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.pixelized.headache.R 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.HeadacheTheme
import com.pixelized.headache.ui.theme.color.HeadacheColorPalette import com.pixelized.headache.ui.theme.color.HeadacheColorPalette
import com.pixelized.headache.utils.extention.calculate import com.pixelized.headache.utils.extention.calculate
@ -66,6 +78,7 @@ data object ReportPageDefault {
fun ReportPage( fun ReportPage(
viewModel: ReportViewModel = hiltViewModel(), viewModel: ReportViewModel = hiltViewModel(),
) { ) {
val navigator = LocalMainNavigator.current
val events = viewModel.events.collectAsStateWithLifecycle() val events = viewModel.events.collectAsStateWithLifecycle()
ReportContent( ReportContent(
@ -73,6 +86,9 @@ fun ReportPage(
.keepScreenOn() .keepScreenOn()
.fillMaxSize(), .fillMaxSize(),
events = events, events = events,
onMonth = {
navigator.navigateToEventPage(date = it.date)
},
) )
} }
@ -80,11 +96,17 @@ fun ReportPage(
@Composable @Composable
private fun ReportContent( private fun ReportContent(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
state: LazyListState = rememberLazyListState(),
flingBehavior: FlingBehavior = rememberSnapFlingBehavior(
lazyListState = state,
snapPosition = SnapPosition.Start,
),
paddingValues: PaddingValues = ReportPageDefault.paddingValues, paddingValues: PaddingValues = ReportPageDefault.paddingValues,
contentSpace: Dp = ReportPageDefault.contentSpace, contentSpace: Dp = ReportPageDefault.contentSpace,
barSpace: Dp = ReportPageDefault.barSpace, barSpace: Dp = ReportPageDefault.barSpace,
barSize: DpSize = rememberBarSize(column = 12, paddingValues = paddingValues, space = barSpace), barSize: DpSize = rememberBarSize(column = 12, paddingValues = paddingValues, space = barSpace),
events: State<List<ReportBoxUio>>, events: State<List<ReportBoxUio>>,
onMonth: (ReportBoxUio.Month) -> Unit,
) { ) {
Scaffold( Scaffold(
modifier = modifier, modifier = modifier,
@ -95,42 +117,27 @@ private fun ReportContent(
containerColor = NavigationBarDefaults.containerColor, containerColor = NavigationBarDefaults.containerColor,
), ),
title = { title = {
Text(text = stringResource(R.string.year_summary_title)) Text(
text = stringResource(R.string.year_summary_title),
)
}, },
actions = { actions = {
Column( Box {
modifier = Modifier.padding(end = 16.dp) val expanded = remember { mutableStateOf(false) }
) { IconButton(
Row( onClick = { expanded.value = true },
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
verticalAlignment = Alignment.CenterVertically,
) { ) {
Box( Icon(
modifier = Modifier imageVector = Icons.Default.Info,
.size(8.dp) contentDescription = null,
.background(
color = HeadacheColorPalette.Calendar.Headache,
)
)
Text(
style = MaterialTheme.typography.labelSmall,
text = "Jours de migraine",
) )
} }
Row( DropdownMenu(
horizontalArrangement = Arrangement.spacedBy(space = 4.dp), expanded = expanded.value,
verticalAlignment = Alignment.CenterVertically, onDismissRequest = { expanded.value = false }
) { ) {
Box( Legend(
modifier = Modifier modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp)
.size(8.dp)
.background(
color = HeadacheColorPalette.Calendar.Pill,
)
)
Text(
style = MaterialTheme.typography.labelSmall,
text = "Prise de cachet",
) )
} }
} }
@ -140,6 +147,8 @@ private fun ReportContent(
content = { it -> content = { it ->
LazyColumn( LazyColumn(
modifier = Modifier.padding(paddingValues = it), modifier = Modifier.padding(paddingValues = it),
state = state,
flingBehavior = flingBehavior,
contentPadding = paddingValues, contentPadding = paddingValues,
verticalArrangement = Arrangement.spacedBy(space = contentSpace), verticalArrangement = Arrangement.spacedBy(space = contentSpace),
) { ) {
@ -150,6 +159,7 @@ private fun ReportContent(
barSize = barSize, barSize = barSize,
barSpace = barSpace, barSpace = barSpace,
item = item, 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 @Composable
private fun rememberBarSize( private fun rememberBarSize(
column: Int, column: Int,
@ -186,6 +238,7 @@ private fun ReportPreview(
Surface { Surface {
ReportContent( ReportContent(
events = remember { mutableStateOf(preview) }, events = remember { mutableStateOf(preview) },
onMonth = { },
) )
} }
} }

View file

@ -1,5 +1,8 @@
package com.pixelized.headache.ui.page.summary.yearly 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.Arrangement
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.WindowInsets 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.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan 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.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBarDefaults import androidx.compose.material3.NavigationBarDefaults
@ -87,6 +92,11 @@ fun YearSummaryPage(
@Composable @Composable
fun YearSummaryContent( fun YearSummaryContent(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
state: LazyGridState = rememberLazyGridState(),
flingBehavior: FlingBehavior = rememberSnapFlingBehavior(
lazyGridState = state,
snapPosition = SnapPosition.Start,
),
paddingValues: PaddingValues = YearSummaryPageDefault.paddingValues, paddingValues: PaddingValues = YearSummaryPageDefault.paddingValues,
space: Dp = YearSummaryPageDefault.space, space: Dp = YearSummaryPageDefault.space,
daySize: Dp = rememberDaySize(column = YearSummaryPageDefault.column), daySize: Dp = rememberDaySize(column = YearSummaryPageDefault.column),
@ -117,6 +127,8 @@ fun YearSummaryContent(
} }
LazyVerticalGrid( LazyVerticalGrid(
modifier = Modifier.padding(paddingValues = it), modifier = Modifier.padding(paddingValues = it),
state = state,
flingBehavior = flingBehavior,
columns = GridCells.Adaptive(minSize = daySize * 7), columns = GridCells.Adaptive(minSize = daySize * 7),
horizontalArrangement = Arrangement.spacedBy(space = space), horizontalArrangement = Arrangement.spacedBy(space = space),
verticalArrangement = Arrangement.spacedBy(space = space), verticalArrangement = Arrangement.spacedBy(space = space),
@ -128,7 +140,7 @@ fun YearSummaryContent(
contentType = { "Title" }, contentType = { "Title" },
) { ) {
Text( Text(
modifier = Modifier.padding(top = 16.dp), modifier = Modifier.padding(top = 16.dp),
style = MaterialTheme.typography.displaySmall, style = MaterialTheme.typography.displaySmall,
text = "$year", text = "$year",
) )