Add a yeary stats screen

This commit is contained in:
Andres Gomez, Thomas (ITDV RL) 2025-09-23 14:16:31 +02:00
parent 6bb58e06c0
commit 3645b92a82
24 changed files with 744 additions and 464 deletions

View file

@ -8,15 +8,15 @@
<SelectionState runConfigName="EventFactoryText">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
<SelectionState runConfigName="text2Pills_3()">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
<SelectionState runConfigName="EventEditPreview">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
<SelectionState runConfigName="YearPreview">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
<SelectionState runConfigName="ReportBoxPreview">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
</selectionStates>
</component>
</project>

View file

@ -47,9 +47,9 @@ class EventRepository @Inject constructor(
)
// Collectable data. Provide event data in a nested map struct of year, month, day.
private val eventMapFlow: StateFlow<Map<Int, Map<Int, Map<Int, Event>>>> = eventListFlow
private val eventMapFlow: StateFlow<Map<Int, Map<Int, Map<Int, List<Event>>>>> = eventListFlow
.map { events ->
events.fold(initial = hashMapOf<Int, HashMap<Int, HashMap<Int, Event>>>()) { acc, event ->
events.fold(initial = hashMapOf<Int, HashMap<Int, HashMap<Int, MutableList<Event>>>>()) { acc, event ->
acc.also {
val years = it.getOrPut(key = event.date.year) {
hashMapOf(
@ -68,7 +68,8 @@ class EventRepository @Inject constructor(
)
}
val months = years.getOrPut(key = event.date.month) { hashMapOf() }
months[event.date.day] = event
val events = months.getOrPut(key = event.date.day) { mutableListOf() }
events.add(event)
}
}
}
@ -78,6 +79,20 @@ class EventRepository @Inject constructor(
initialValue = emptyMap(),
)
private val maxPillAmountPerMonth: StateFlow<Int> = eventMapFlow
.map { events ->
events.values.maxOf { yearEntry: Map<Int, Map<Int, List<Event>>> ->
yearEntry.values.maxOf { monthEntry ->
monthEntry.values.sumOf { events -> events.sumOf { it.pills.size } }
}
}
}
.stateIn(
scope = scope,
started = SharingStarted.Lazily,
initialValue = 0,
)
init {
calendarIdRepository.calendarId
.onEach { id ->
@ -92,7 +107,9 @@ class EventRepository @Inject constructor(
fun eventsListFlow(): StateFlow<Collection<Event>> = eventListFlow
fun eventsMapFlow(): StateFlow<Map<Int, Map<Int, Map<Int, Event>>>> = eventMapFlow
fun eventsMapFlow(): StateFlow<Map<Int, Map<Int, Map<Int, List<Event>>>>> = eventMapFlow
fun maxPillAmountPerMonthFlow(): StateFlow<Int> = maxPillAmountPerMonth
fun event(id: Long): Event? = eventFlow.value.get(key = id)

View file

@ -0,0 +1,19 @@
package com.pixelized.headache.ui.navigation.destination
import androidx.navigation3.runtime.EntryProviderBuilder
import androidx.navigation3.runtime.entry
import com.pixelized.headache.ui.navigation.home.HomeDestination
import com.pixelized.headache.ui.navigation.home.HomeNavigator
import com.pixelized.headache.ui.page.summary.report.ReportPage
data object ReportDestination : HomeDestination
fun EntryProviderBuilder<*>.reportDestinationEntry() {
entry<ReportDestination> {
ReportPage()
}
}
fun HomeNavigator.navigateToReport() {
goTo(ReportDestination)
}

View file

@ -13,6 +13,7 @@ import com.pixelized.headache.ui.navigation.destination.calendarChooserDestinati
import com.pixelized.headache.ui.navigation.destination.eventDestinationEntry
import com.pixelized.headache.ui.navigation.destination.homeDestinationEntry
import com.pixelized.headache.ui.navigation.destination.monthSummaryDestinationEntry
import com.pixelized.headache.ui.navigation.destination.reportDestinationEntry
import com.pixelized.headache.ui.navigation.destination.yearSummaryDestinationEntry
val LocalHomeNavigator = staticCompositionLocalOf<HomeNavigator> {
@ -41,6 +42,7 @@ fun HomeNavDisplay(
entryProvider = entryProvider {
monthSummaryDestinationEntry()
yearSummaryDestinationEntry()
reportDestinationEntry()
}
)
}

View file

@ -67,13 +67,13 @@ class EventEditFactory @Inject constructor() {
EventPillEditUio(
id = Event.Pill.Id.IBUPROFENE_400.value,
color = HeadacheColorPalette.Pill.Ibuprofene400,
label = "Paracétamol 1000",
label = "Ibuprofène 400",
amount = ibuprofeneAmount,
),
EventPillEditUio(
id = Event.Pill.Id.PARACETAMOL_1000.value,
color = HeadacheColorPalette.Pill.Paracetamol1000,
label = "Ibuprofène 400",
label = "Paracétamol 1000",
amount = paracetamolAmount,
),
EventPillEditUio(

View file

@ -21,26 +21,26 @@ import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarDefaults
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.IntState
import androidx.compose.runtime.Stable
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import com.pixelized.headache.R
import com.pixelized.headache.ui.navigation.destination.MonthSummaryDestination
import com.pixelized.headache.ui.navigation.destination.ReportDestination
import com.pixelized.headache.ui.navigation.destination.YearSummaryDestination
import com.pixelized.headache.ui.navigation.destination.navigateToMonthSummary
import com.pixelized.headache.ui.navigation.destination.navigateToReport
import com.pixelized.headache.ui.navigation.destination.navigateToYearSummary
import com.pixelized.headache.ui.navigation.home.HomeNavDisplay
import com.pixelized.headache.ui.navigation.home.HomeNavigator
@ -61,18 +61,25 @@ fun HomePage(
navigator: HomeNavigator,
editViewModel: EventEditBottomSheetViewModel = hiltViewModel(),
) {
val selectedItem = remember { mutableIntStateOf(0) }
val selectedItem = remember {
derivedStateOf {
when (navigator.backStack.last()) {
is YearSummaryDestination -> 0
is ReportDestination -> 1
is MonthSummaryDestination -> 2
else -> -1
}
}
}
val items = rememberBottomBarItems(
onYearlyFollowUp = {
selectedItem.intValue = 0
navigator.navigateToYearSummary()
},
onMonthlyStatFollowUp = {
selectedItem.intValue = 1
navigator.navigateToMonthSummary()
navigator.navigateToReport()
},
onMonthlyListFollowUp = {
selectedItem.intValue = 2
navigator.navigateToMonthSummary()
},
)
@ -123,7 +130,7 @@ private fun HomePageContent(
modifier: Modifier = Modifier,
navigator: HomeNavigator,
items: List<BottomBarItemUio>,
selectedItem: IntState,
selectedItem: State<Int>,
onFabClick: () -> Unit,
) {
Scaffold(

View file

@ -1,106 +1,78 @@
package com.pixelized.headache.ui.page.summary.monthly
import android.icu.text.Collator
import android.icu.util.Calendar
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.intl.Locale
import com.pixelized.headache.repository.event.Event
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
class MonthSummaryFactory @Inject constructor() {
private val calendar = Calendar.getInstance()
private val locale = Locale.current.let {
java.util.Locale.forLanguageTag(it.toLanguageTag())
}
fun convertToItemUio(
events: Collection<Event>,
): Map<MonthSummaryCell, List<MonthSummaryCell>> {
events: Map<Int, Map<Int, Map<Int, List<Event>>>>,
): Map<MonthSummaryTitleUio, List<MonthSummaryItemUio>> {
return events
.fold(hashMapOf<Event.Date, MutableList<Event>>()) { acc, event ->
acc.also { it.getOrPut(event.date.copy(day = 1)) { mutableListOf() }.add(event) }
}
.map { entry ->
val pills = entry.value
.fold(hashMapOf<Event.Pill.Id, EventFolder>()) { acc, event ->
event.pills.forEach { pill ->
val value = acc.getOrElse(
key = pill.id,
defaultValue = {
EventFolder(
label = pill.label,
amount = 0,
color = pill.color,
.flatMap { yearEntry ->
yearEntry.value.map { monthEntry ->
val pills = monthEntry.value.values
.fold(
initial = hashMapOf<Event.Pill.Id, PillFolder>(),
) { acc, events ->
events.map { event ->
event.pills.forEach { pill ->
val value = acc.getOrElse(
key = pill.id,
defaultValue = {
PillFolder(label = pill.label, color = pill.color)
},
)
},
)
value.amount += 1
acc[pill.id] = value
value.amount += pill.amount
acc[pill.id] = value
}
}
acc
}
acc
}
.map { entry ->
MonthSummaryPillItemUio(
label = entry.value.label,
color = entry.value.color,
amount = entry.value.amount,
)
}
.toList()
.map { entry ->
MonthSummaryPillItemUio(
label = entry.value.label,
color = entry.value.color,
amount = entry.value.amount,
)
}
.toList()
.sortedWith(Comparator { s1, s2 ->
Collator.getInstance(locale).compare(s1.label, s2.label)
})
MonthSummaryItemUio(
date = calendar.apply { event = entry.key }.time,
days = entry.value.size,
pills = pills,
)
}
.sortedByDescending { it.date }
.groupByMonth()
}
fun convertToBoxUio(
events: Collection<Event>,
): Map<MonthSummaryCell, List<MonthSummaryCell>> {
var maxPillAmount = 0
return events
.fold(hashMapOf<Event.Date, MutableList<Event>>()) { acc, event ->
acc.also { it.getOrPut(event.date.copy(day = 1)) { mutableListOf() }.add(event) }
}
.mapKeys { entry ->
maxPillAmount = max(
entry.value.sumOf { events -> events.pills.sumOf { pill -> pill.amount } },
maxPillAmount,
)
entry.key
}
.map { entry ->
val pillAmount = entry.value.sumOf { events ->
events.pills.sumOf { pill -> pill.amount }
MonthSummaryItemUio(
date = calendar.apply {
set(Calendar.YEAR, yearEntry.key)
set(Calendar.MONTH, monthEntry.key)
}.time,
days = monthEntry.value.values.sumOf { it.size },
pills = pills,
)
}
val monthMaxDay = calendar.apply {
event = entry.key
add(Calendar.MONTH, 1)
add(Calendar.DAY_OF_YEAR, -1)
}.get(Calendar.DAY_OF_MONTH)
MonthSummaryBoxUio(
date = calendar.apply { event = entry.key }.time,
headacheRatio = entry.value.size.toFloat() / monthMaxDay,
headacheAmount = entry.value.size,
headacheColor = Color.Companion.Red,
pillRatio = pillAmount.takeIf { it > 0 }
?.let { it.toFloat() / maxPillAmount.toFloat() },
pillAmount = pillAmount,
pillColor = Color.Companion.Blue,
)
}
.sortedByDescending { it.date }
.groupByMonth()
.toSortedMap{s1, s2 ->
when{
s1.date < s2.date -> 1
s1.date > s2.date -> -1
else -> 0
}
}
}
fun List<MonthSummaryCell>.groupByMonth(): Map<MonthSummaryCell, List<MonthSummaryCell>> {
fun List<MonthSummaryItemUio>.groupByMonth(): Map<MonthSummaryTitleUio, List<MonthSummaryItemUio>> {
return this.groupBy {
MonthSummaryTitleUio(
date = calendar.apply {
@ -112,9 +84,9 @@ class MonthSummaryFactory @Inject constructor() {
}
}
private class EventFolder(
private class PillFolder(
val label: String,
val color: Color,
var amount: Int,
var amount: Int = 0,
)
}

View file

@ -1,23 +1,13 @@
package com.pixelized.headache.ui.page.summary.monthly
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith
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.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.Article
import androidx.compose.material.icons.filled.BarChart
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.NavigationBarDefaults
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
@ -29,7 +19,6 @@ import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.keepScreenOn
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
@ -40,9 +29,6 @@ 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.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
@ -69,7 +55,6 @@ fun MonthSummaryPage(
viewModel: MonthSummaryViewModel = hiltViewModel(),
) {
val navigation = LocalMainNavigator.current
val boxMode = viewModel.boxMode.collectAsStateWithLifecycle()
val events = viewModel.events.collectAsStateWithLifecycle()
MonthSummaryContent(
@ -77,10 +62,6 @@ fun MonthSummaryPage(
.keepScreenOn()
.fillMaxSize(),
events = events,
boxMode = boxMode,
onDisplay = {
viewModel.toggleDisplay()
},
onItem = {
navigation.navigateToEventPage(date = it.date)
},
@ -93,10 +74,8 @@ private fun MonthSummaryContent(
modifier: Modifier = Modifier,
spacing: Dp = MonthSummaryPageDefault.spacing,
listPadding: PaddingValues = MonthSummaryPageDefault.listPadding,
boxMode: State<Boolean>,
events: State<Map<MonthSummaryCell, List<MonthSummaryCell>>>,
onDisplay: () -> Unit,
onItem: (MonthSummaryCell) -> Unit,
events: State<Map<MonthSummaryTitleUio, List<MonthSummaryItemUio>>>,
onItem: (MonthSummaryItemUio) -> Unit,
) {
Scaffold(
modifier = modifier,
@ -109,24 +88,6 @@ private fun MonthSummaryContent(
title = {
Text(text = stringResource(R.string.month_summary_title))
},
actions = {
AnimatedContent(
targetState = boxMode.value,
transitionSpec = { fadeIn() togetherWith fadeOut() },
) {
IconButton(
onClick = onDisplay,
) {
Icon(
imageVector = when (it) {
true -> Icons.AutoMirrored.Filled.Article
else -> Icons.Default.BarChart
},
contentDescription = null,
)
}
}
}
)
},
content = { paddingValues ->
@ -140,24 +101,16 @@ private fun MonthSummaryContent(
) {
events.value.forEach { entry ->
item {
MonthSummaryCell(
MonthSummaryTitle(
modifier = Modifier.padding(top = 16.dp),
item = entry.key,
onItem = onItem,
)
}
items(
items = entry.value,
key = { item -> item.date },
contentType = { item ->
when (item) {
is MonthSummaryBoxUio -> "MonthSummaryBoxUio"
is MonthSummaryItemUio -> "MonthSummaryItemUio"
is MonthSummaryTitleUio -> "MonthSummaryTitleUio"
}
},
) { item ->
MonthSummaryCell(
MonthSummaryItem(
item = item,
onItem = onItem,
)
@ -168,79 +121,38 @@ private fun MonthSummaryContent(
)
}
@Composable
private fun MonthSummaryCell(
modifier: Modifier = Modifier,
item: MonthSummaryCell,
onItem: (MonthSummaryCell) -> Unit,
) {
AnimatedContent(
modifier = Modifier
.fillMaxWidth()
.then(other = modifier),
targetState = item,
transitionSpec = { fadeIn() togetherWith fadeOut() },
) { item ->
when (item) {
is MonthSummaryTitleUio -> MonthSummaryTitle(
item = item,
)
is MonthSummaryBoxUio -> MonthSummaryBox(
item = item,
onItem = onItem,
)
is MonthSummaryItemUio -> MonthSummaryItem(
item = item,
onItem = onItem,
)
}
}
}
@Composable
@Preview
private fun MonthSummaryPreview() {
HeadacheTheme {
MonthSummaryContent(
boxMode = remember { mutableStateOf(false) },
events = remember {
mutableStateOf(
mapOf(
MonthSummaryTitleUio(
val events = remember {
mutableStateOf(
mapOf(
MonthSummaryTitleUio(
date = Date(),
) to listOf(
MonthSummaryItemUio(
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,
),
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,
),
),
MonthSummaryBoxUio(
date = Date(),
headacheRatio = 8f / 30f,
headacheAmount = 8,
headacheColor = Color.Red,
pillRatio = 6f / 20f,
pillAmount = 6,
pillColor = Color.Blue,
),
),
)
),
)
},
onDisplay = { },
)
}
MonthSummaryContent(
events = events,
onItem = { },
)
}

View file

@ -2,15 +2,11 @@ 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.monthly.item.MonthSummaryCell
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import javax.inject.Inject
@ -20,26 +16,13 @@ class MonthSummaryViewModel @Inject constructor(
eventItemFactory: MonthSummaryFactory,
) : ViewModel() {
private val displayTypeFlow = MutableStateFlow(false)
val boxMode: StateFlow<Boolean> = displayTypeFlow
@OptIn(ExperimentalCoroutinesApi::class)
val events: StateFlow<Map<MonthSummaryCell, List<MonthSummaryCell>>> = combine(
eventRepository.eventsListFlow(),
displayTypeFlow,
transform = { events: Collection<Event>, display ->
when (display) {
true -> eventItemFactory.convertToBoxUio(events = events)
else -> eventItemFactory.convertToItemUio(events = events)
}
}
).stateIn(
scope = viewModelScope,
started = SharingStarted.Lazily,
initialValue = emptyMap(),
)
fun toggleDisplay() {
displayTypeFlow.value = displayTypeFlow.value.not()
}
val events = eventRepository.eventsMapFlow()
.map { events ->
eventItemFactory.convertToItemUio(events = events)
}.stateIn(
scope = viewModelScope,
started = SharingStarted.Lazily,
initialValue = emptyMap(),
)
}

View file

@ -1,174 +0,0 @@
package com.pixelized.headache.ui.page.summary.monthly.item
import android.annotation.SuppressLint
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
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.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.text.style.TextAlign
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.DpSize
import androidx.compose.ui.unit.dp
import com.pixelized.headache.ui.theme.HeadacheTheme
import com.pixelized.headache.utils.extention.capitalize
import java.util.Date
import java.util.Locale
@Stable
data object MonthSummaryBoxDefault {
@Stable
val padding: PaddingValues = PaddingValues(horizontal = 16.dp, vertical = 8.dp)
@Stable
val spacing: DpSize = DpSize(width = 8.dp, height = 4.dp)
@Stable
val labelWidth: Dp = 32.dp + 16.dp
@Stable
val boxHeight: Dp = 16.dp
@Stable
val boxPaddingValues: PaddingValues = PaddingValues(horizontal = 4.dp)
@SuppressLint("ConstantLocale")
@Stable
val formatter = SimpleDateFormat("MMM", Locale.getDefault())
}
@Stable
data class MonthSummaryBoxUio(
override val date: Date,
val headacheRatio: Float,
val headacheAmount: Int,
val headacheColor: Color,
val pillRatio: Float?,
val pillAmount: Int,
val pillColor: Color,
) : MonthSummaryCell
@Composable
fun MonthSummaryBox(
modifier: Modifier = Modifier,
padding: PaddingValues = MonthSummaryBoxDefault.padding,
spacing: DpSize = MonthSummaryBoxDefault.spacing,
boxPaddingValues: PaddingValues = MonthSummaryBoxDefault.boxPaddingValues,
labelWidth: Dp = MonthSummaryBoxDefault.labelWidth,
boxHeight: Dp = MonthSummaryBoxDefault.boxHeight,
formatter: SimpleDateFormat = MonthSummaryBoxDefault.formatter,
item: MonthSummaryBoxUio,
onItem: (MonthSummaryBoxUio) -> Unit,
) {
Row(
modifier = Modifier
.clickable { onItem(item) }
.padding(paddingValues = padding)
.then(other = modifier),
horizontalArrangement = Arrangement.spacedBy(space = spacing.width),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
modifier = Modifier.width(width = labelWidth),
style = MaterialTheme.typography.bodyMedium,
textAlign = TextAlign.End,
text = formatter.format(item.date).capitalize(),
)
Column(
verticalArrangement = Arrangement.spacedBy(space = spacing.height),
) {
Box(
modifier = Modifier
.background(color = item.headacheColor)
.height(height = boxHeight)
.fillMaxWidth(fraction = item.headacheRatio)
.padding(paddingValues = boxPaddingValues),
contentAlignment = Alignment.CenterStart,
) {
Text(
style = MaterialTheme.typography.labelSmall,
color = Color.White,
text = "${item.headacheAmount}"
)
}
item.pillRatio?.let {
Box(
modifier = Modifier
.background(color = item.pillColor)
.height(height = boxHeight)
.fillMaxWidth(fraction = it)
.padding(paddingValues = boxPaddingValues),
) {
Text(
style = MaterialTheme.typography.labelSmall,
color = Color.White,
text = "${item.pillAmount}"
)
}
}
}
}
}
@Composable
@Preview
private fun MonthSummaryBoxPreview(
@PreviewParameter(BoxPreviewProvider::class) preview: MonthSummaryBoxUio,
) {
HeadacheTheme {
Surface {
MonthSummaryBox(
modifier = Modifier.fillMaxWidth(),
item = preview,
onItem = { },
)
}
}
}
private class BoxPreviewProvider : PreviewParameterProvider<MonthSummaryBoxUio> {
val calendar = Calendar.getInstance().apply {
time = Date()
set(Calendar.DAY_OF_MONTH, 1)
}
override val values: Sequence<MonthSummaryBoxUio>
get() = sequenceOf(
MonthSummaryBoxUio(
date = calendar.apply { set(Calendar.MONTH, Calendar.DECEMBER) }.time,
headacheRatio = 0.2f,
headacheAmount = 1,
headacheColor = Color.Red,
pillRatio = 0.3f,
pillAmount = 1,
pillColor = Color.Blue,
),
MonthSummaryBoxUio(
date = calendar.apply { set(Calendar.MONTH, Calendar.SEPTEMBER) }.time,
headacheRatio = 1f,
headacheAmount = 1,
headacheColor = Color.Red,
pillRatio = 0.3f,
pillAmount = 1,
pillColor = Color.Blue,
),
)
}

View file

@ -1,9 +0,0 @@
package com.pixelized.headache.ui.page.summary.monthly.item
import androidx.compose.runtime.Stable
import java.util.Date
@Stable
sealed interface MonthSummaryCell {
val date: Date
}

View file

@ -29,10 +29,10 @@ import java.util.Locale
@Stable
data class MonthSummaryItemUio(
override val date: Date,
val date: Date,
val days: Int,
val pills: List<MonthSummaryPillItemUio>,
) : MonthSummaryCell
)
@Stable
object MonthSummaryItemDefault {

View file

@ -15,8 +15,8 @@ import java.util.Locale
@Stable
data class MonthSummaryTitleUio(
override val date: Date,
) : MonthSummaryCell
val date: Date,
)
@Stable
object MonthSummaryTitleDefault {

View file

@ -0,0 +1,229 @@
package com.pixelized.headache.ui.page.summary.report
import android.annotation.SuppressLint
import android.icu.text.DateFormat
import android.icu.text.SimpleDateFormat
import android.icu.util.Calendar
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
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.graphics.Shape
import androidx.compose.ui.text.style.TextOverflow
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.DpSize
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
class ReportBoxUio(
val year: Int,
val months: List<Month>,
) {
@Stable
data class Month(
val date: Date,
val stats: List<Bar>,
)
@Stable
data class Bar(
val color: Color,
val label: String,
val ratio: Float,
)
}
@Stable
object ReportBoxDefault {
@Stable
val barSpace: Dp = 4.dp
@Stable
val titleSpace: Dp = 8.dp
@Stable
val barSize: DpSize = DpSize(
width = 28.dp,
height = 320.dp,
)
@Stable
val barShape: Shape = RoundedCornerShape(
topStart = barSize.width / 4,
topEnd = barSize.width / 4
)
@SuppressLint("ConstantLocale")
@Stable
val formatter = SimpleDateFormat("MMM", Locale.getDefault())
}
@Composable
fun ReportBox(
modifier: Modifier = Modifier,
formatter: DateFormat = ReportBoxDefault.formatter,
barSize: DpSize = ReportBoxDefault.barSize,
barShape: Shape = ReportBoxDefault.barShape,
barSpace: Dp = ReportBoxDefault.barSpace,
titleSpace: Dp = ReportBoxDefault.titleSpace,
item: ReportBoxUio,
) {
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(space = titleSpace),
) {
Text(
modifier = Modifier.padding(top = 16.dp),
style = MaterialTheme.typography.displaySmall,
text = "${item.year}",
)
Row(
horizontalArrangement = Arrangement.spacedBy(space = barSpace),
) {
item.months.forEach {
Month(
formatter = formatter,
barSize = barSize,
barShape = barShape,
item = it,
)
}
}
}
}
@Composable
private fun Month(
modifier: Modifier = Modifier,
formatter: DateFormat = ReportBoxDefault.formatter,
barSize: DpSize = ReportBoxDefault.barSize,
barShape: Shape = ReportBoxDefault.barShape,
item: ReportBoxUio.Month,
) {
Column(
modifier = Modifier
.width(width = barSize.width)
.then(other = modifier),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Box(
modifier = Modifier.height(height = barSize.height),
contentAlignment = Alignment.BottomStart,
) {
item.stats.forEachIndexed { index, stat ->
Box(
modifier = Modifier
.size(
width = barSize.width,
height = barSize.height * stat.ratio
)
.background(
color = stat.color,
shape = barShape,
),
contentAlignment = Alignment.TopCenter,
) {
Text(
style = MaterialTheme.typography.labelSmall,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
color = Color.White,
text = stat.label,
)
}
}
}
Text(
style = MaterialTheme.typography.labelSmall,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = formatter.format(item.date).capitalize(),
)
}
}
@Composable
@Preview
private fun ReportBoxPreview(
@PreviewParameter(ReportBoxPreviewProvider::class) preview: ReportBoxUio,
) {
HeadacheTheme {
Surface {
ReportBox(
item = preview,
)
}
}
}
object ReportBoxPreviewHelper {
fun month(
month: Int,
headache: Int = 0,
pills: Int = 0,
) = ReportBoxUio.Month(
date = Calendar.getInstance().apply {
set(Calendar.MONTH, month)
}.time,
stats = listOf(
ReportBoxUio.Bar(
color = HeadacheColorPalette.Calendar.Headache,
label = "$headache",
ratio = (headache.toFloat() / 30).coerceIn(0f, 1f),
),
ReportBoxUio.Bar(
color = HeadacheColorPalette.Calendar.Pill,
label = "$pills",
ratio = (pills.toFloat() / 24).coerceIn(0f, 1f),
),
).sortedByDescending { it.ratio },
)
}
private class ReportBoxPreviewProvider : PreviewParameterProvider<ReportBoxUio> {
override val values: Sequence<ReportBoxUio>
get() = with(ReportBoxPreviewHelper) {
sequenceOf(
ReportBoxUio(
year = 2025,
months = listOf(
month(month = Calendar.JANUARY, headache = 6, pills = 10),
month(month = Calendar.FEBRUARY, headache = 15, pills = 24),
month(month = Calendar.MARCH, headache = 14, pills = 16),
month(month = Calendar.APRIL, headache = 16, pills = 18),
month(month = Calendar.MARCH, headache = 14, pills = 20),
month(month = Calendar.JUNE, headache = 12, pills = 13),
month(month = Calendar.JULY, headache = 7, pills = 3),
month(month = Calendar.AUGUST, headache = 8, pills = 5),
month(month = Calendar.SEPTEMBER, headache = 8, pills = 5),
month(month = Calendar.OCTOBER),
month(month = Calendar.NOVEMBER),
month(month = Calendar.DECEMBER),
)
)
)
}
}

View file

@ -0,0 +1,59 @@
package com.pixelized.headache.ui.page.summary.report
import android.icu.util.Calendar
import com.pixelized.headache.repository.event.Event
import com.pixelized.headache.ui.theme.color.HeadacheColorPalette
import com.pixelized.headache.utils.extention.event
import javax.inject.Inject
class ReportFactory @Inject constructor() {
fun convertToUio(
maxPillAmountPerMonth: Int,
events: Map<Int, Map<Int, Map<Int, List<Event>>>>,
): List<ReportBoxUio> {
return events
.map { yearEntry ->
ReportBoxUio(
year = yearEntry.key,
months = yearEntry.value.map { monthEntry ->
var headache = 0
var pills = 0
monthEntry.value.values.forEach { events ->
headache += events.size
pills += events.sumOf { it.pills.size }
}
val dayInMonth = Calendar.getInstance().let {
it.set(Calendar.YEAR, yearEntry.key)
it.set(Calendar.MONTH, monthEntry.key + 1)
it.set(Calendar.DAY_OF_MONTH, 1)
it.add(Calendar.DAY_OF_YEAR, -1)
it.get(Calendar.DAY_OF_MONTH)
}
ReportBoxUio.Month(
date = Calendar.getInstance().apply {
event = Event.Date(
day = 1,
month = monthEntry.key,
year = yearEntry.key
)
}.time,
stats = listOf(
ReportBoxUio.Bar(
color = HeadacheColorPalette.Calendar.Headache,
label = "$headache",
ratio = headache.toFloat() / dayInMonth.toFloat(),
),
ReportBoxUio.Bar(
color = HeadacheColorPalette.Calendar.Pill,
label = "$pills",
ratio = pills.toFloat() / maxPillAmountPerMonth.toFloat(),
),
).sortedByDescending { it.ratio },
)
}
)
}
.sortedByDescending { it.year }
}
}

View file

@ -0,0 +1,237 @@
package com.pixelized.headache.ui.page.summary.report
import android.icu.util.Calendar
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
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.items
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBarDefaults
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
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.Alignment
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.DpSize
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.theme.HeadacheTheme
import com.pixelized.headache.ui.theme.color.HeadacheColorPalette
import com.pixelized.headache.utils.extention.calculate
@Stable
data object ReportPageDefault {
@Stable
val paddingValues = PaddingValues(
start = 16.dp,
end = 16.dp,
bottom = 16.dp + 16.dp + 56.dp,
)
@Stable
val contentSpace: Dp = 8.dp
@Stable
val barSpace: Dp = ReportBoxDefault.barSpace
}
@Composable
fun ReportPage(
viewModel: ReportViewModel = hiltViewModel(),
) {
val events = viewModel.events.collectAsStateWithLifecycle()
ReportContent(
modifier = Modifier
.keepScreenOn()
.fillMaxSize(),
events = events,
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun ReportContent(
modifier: Modifier = Modifier,
paddingValues: PaddingValues = ReportPageDefault.paddingValues,
contentSpace: Dp = ReportPageDefault.contentSpace,
barSpace: Dp = ReportPageDefault.barSpace,
barSize: DpSize = rememberBarSize(column = 12, paddingValues = paddingValues, space = barSpace),
events: State<List<ReportBoxUio>>,
) {
Scaffold(
modifier = modifier,
contentWindowInsets = remember { WindowInsets(0, 0, 0, 0) },
topBar = {
TopAppBar(
colors = TopAppBarDefaults.topAppBarColors(
containerColor = NavigationBarDefaults.containerColor,
),
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(
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",
)
}
}
}
)
},
content = { it ->
LazyColumn(
modifier = Modifier.padding(paddingValues = it),
contentPadding = paddingValues,
verticalArrangement = Arrangement.spacedBy(space = contentSpace),
) {
items(
items = events.value
) { item ->
ReportBox(
barSize = barSize,
barSpace = barSpace,
item = item,
)
}
}
}
)
}
@Composable
private fun rememberBarSize(
column: Int,
paddingValues: PaddingValues,
space: Dp,
): DpSize {
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 {
DpSize(
width = (screenWidth - space * (column - 1) - start - end) / column,
height = ReportBoxDefault.barSize.height,
)
}
}
@Composable
@Preview
private fun ReportPreview(
@PreviewParameter(ReportPreviewProvider::class) preview: List<ReportBoxUio>,
) {
HeadacheTheme {
Surface {
ReportContent(
events = remember { mutableStateOf(preview) },
)
}
}
}
private class ReportPreviewProvider : PreviewParameterProvider<List<ReportBoxUio>> {
override val values: Sequence<List<ReportBoxUio>>
get() = with(ReportBoxPreviewHelper) {
sequenceOf(
listOf(
ReportBoxUio(
year = 2025,
months = listOf(
month(month = Calendar.JANUARY, headache = 6, pills = 10),
month(month = Calendar.FEBRUARY, headache = 15, pills = 24),
month(month = Calendar.MARCH, headache = 14, pills = 16),
month(month = Calendar.APRIL, headache = 16, pills = 18),
month(month = Calendar.MARCH, headache = 14, pills = 20),
month(month = Calendar.JUNE, headache = 12, pills = 13),
month(month = Calendar.JULY, headache = 7, pills = 3),
month(month = Calendar.AUGUST, headache = 8, pills = 5),
month(month = Calendar.SEPTEMBER, headache = 8, pills = 5),
month(month = Calendar.OCTOBER, headache = 0, pills = 0),
month(month = Calendar.NOVEMBER, headache = 0, pills = 0),
month(month = Calendar.DECEMBER, headache = 0, pills = 0),
),
),
ReportBoxUio(
year = 2024,
months = listOf(
month(month = Calendar.JANUARY, headache = 14),
month(month = Calendar.FEBRUARY, headache = 15),
month(month = Calendar.MARCH, headache = 15),
month(month = Calendar.APRIL, headache = 10),
month(month = Calendar.MARCH, headache = 7),
month(month = Calendar.JUNE, headache = 15),
month(month = Calendar.JULY, headache = 7),
month(month = Calendar.AUGUST, headache = 11, pills = 12),
month(month = Calendar.SEPTEMBER, headache = 12, pills = 15),
month(month = Calendar.OCTOBER, headache = 5, pills = 8),
month(month = Calendar.NOVEMBER, headache = 17, pills = 22),
month(month = Calendar.DECEMBER, headache = 12, pills = 17),
),
)
)
)
}
}

View file

@ -0,0 +1,28 @@
package com.pixelized.headache.ui.page.summary.report
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.pixelized.headache.repository.event.EventRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
import javax.inject.Inject
@HiltViewModel
class ReportViewModel @Inject constructor(
eventRepository: EventRepository,
reportFactory: ReportFactory,
) : ViewModel() {
val events: StateFlow<List<ReportBoxUio>> = combine(
eventRepository.maxPillAmountPerMonthFlow(),
eventRepository.eventsMapFlow(),
reportFactory::convertToUio
).stateIn(
scope = viewModelScope,
started = SharingStarted.Lazily,
initialValue = emptyList(),
)
}

View file

@ -8,7 +8,7 @@ import javax.inject.Inject
class YearSummaryFactory @Inject constructor() {
fun convertToUio(
events: Map<Int, Map<Int, Map<Int, Event>>>,
events: Map<Int, Map<Int, Map<Int, List<Event>>>>,
): List<YearUio> {
val monthFirstDayCalendar = Calendar.getInstance().apply {
firstDayOfWeek = Calendar.MONDAY
@ -49,7 +49,7 @@ class YearSummaryFactory @Inject constructor() {
}
val weeks = (1..monthLastDayCalendar.get(Calendar.DAY_OF_MONTH))
.fold(initial = initial) { accumulator, dayNumber ->
val event: Event? = monthEntry.value.get(dayNumber)
val event: List<Event> = monthEntry.value[dayNumber] ?: emptyList()
val weekIndex = currentDayCalendar
.apply {
set(Calendar.YEAR, yearEntry.key)
@ -60,8 +60,8 @@ class YearSummaryFactory @Inject constructor() {
.get(Calendar.WEEK_OF_MONTH) - 1
val day = DayUio(
number = dayNumber,
headache = event != null,
pills = event?.pills?.map { it.color } ?: emptyList(),
headache = event.isNotEmpty(),
pills = event.flatMap { it.pills.map { pill -> pill.color } },
)
accumulator.also { acc ->
acc[weekIndex] = acc.get(index = weekIndex).also { week ->

View file

@ -142,7 +142,7 @@ fun YearSummaryMonth(
width = localCellSize.width - 0f,
height = localCellSize.height - (pillSize.height + space) * 2,
),
color = HeadacheColorPalette.Pill.Unknown,
color = HeadacheColorPalette.Calendar.Headache,
)
}

View file

@ -83,23 +83,6 @@ fun YearSummaryPage(
)
}
@Composable
private fun rememberDaySize(
column: Int,
paddingValues: PaddingValues = YearSummaryPageDefault.paddingValues,
space: Dp = YearSummaryPageDefault.space,
): 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(
@ -169,6 +152,23 @@ fun YearSummaryContent(
)
}
@Composable
private fun rememberDaySize(
column: Int,
paddingValues: PaddingValues = YearSummaryPageDefault.paddingValues,
space: Dp = YearSummaryPageDefault.space,
): 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)
}
}
@Composable
@Preview()
private fun YearSummaryPreview(

View file

@ -3,14 +3,6 @@ package com.pixelized.headache.ui.theme.color
import androidx.compose.ui.graphics.Color
import javax.annotation.concurrent.Immutable
val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFFEFB8C8)
val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)
@Immutable
object HeadacheColorPalette {
@ -23,6 +15,12 @@ object HeadacheColorPalette {
val Eletriptan40 = Additional.VeryLightPink
}
@Immutable
object Calendar {
val Headache = Additional.LightRed
val Pill = Additional.DarkRed
}
@Immutable
object Additional {
val VeryDarkBlue: Color = Color(0xFF09179D)

View file

@ -40,7 +40,7 @@ data class HeadacheColors(
fun headacheDarkColorScheme(
base: ColorScheme = darkColorScheme(),
calendar: HeadacheColors.Calendar = HeadacheColors.Calendar(
headache = HeadacheColorPalette.Additional.Red,
headache = HeadacheColorPalette.Calendar.Headache,
onHeadache = Color.White,
),
pill: HeadacheColors.Pill = HeadacheColors.Pill(
@ -61,7 +61,7 @@ fun headacheDarkColorScheme(
fun headacheLightColorScheme(
base: ColorScheme = lightColorScheme(),
calendar: HeadacheColors.Calendar = HeadacheColors.Calendar(
headache = HeadacheColorPalette.Additional.Red,
headache = HeadacheColorPalette.Calendar.Headache,
onHeadache = Color.White,
),
pill: HeadacheColors.Pill = HeadacheColors.Pill(
@ -94,7 +94,7 @@ fun calculateElevatedColor(
@ReadOnlyComposable
@Composable
private fun calculateForegroundColor(color: Color, elevation: Dp): Color {
fun calculateForegroundColor(color: Color, elevation: Dp): Color {
val alpha = ((4.5f * ln(elevation.value + 1)) + 2f) / 100f
return color.copy(alpha = alpha)
}