Optimize the month cell display in the year summary.

This commit is contained in:
Andres Gomez, Thomas (ITDV RL) 2025-09-22 18:28:37 +02:00
parent 7d2508a4de
commit 470c9ce706
19 changed files with 914 additions and 907 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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<YearSummaryMonthUio>,
)
@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<List<YearUio>>,
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<YearUio>,
) {
HeadacheTheme {
Surface {
YearSummaryContent(
uio = remember { mutableStateOf(preview) },
onBack = { },
onMonth = { },
onAddEvent = { },
)
}
}
}
private class YearPreviewProvider() : PreviewParameterProvider<List<YearUio>> {
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<YearSummaryDayUio> = 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<YearSummaryDayUio>,
): 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<List<YearUio>> = 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),
),
)
),
)
}

View file

@ -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<Color>,
)
@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<YearSummaryDayUio> {
override val values: Sequence<YearSummaryDayUio>
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,
),
),
)
}

View file

@ -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<List<YearSummaryDayUio>>,
)
@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<YearSummaryMonthUio> {
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<YearSummaryDayUio> = listOfNotNull(
mon?.invoke(),
tue?.invoke(),
wen?.invoke(),
thu?.invoke(),
fry?.invoke(),
sat?.invoke(),
sun?.invoke(),
)
private fun month(
month: Int,
vararg weeks: List<YearSummaryDayUio>,
): 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<YearSummaryMonthUio>
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),
),
)
}

View file

@ -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<YearSummaryDayUio>()
mutableListOf<DayUio>()
}
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(),

View file

@ -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<List<DayUio>>,
) {
@Stable
data class DayUio(
val number: Int,
val headache: Boolean,
val pills: List<Color>,
)
}
@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<YearSummaryMonthUio.DayUio> = 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.DayUio>,
): 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<YearSummaryMonthUio> {
override val values: Sequence<YearSummaryMonthUio>
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),
),
)
}
}

View file

@ -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<YearSummaryMonthUio>,
)
@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<List<YearUio>>,
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<YearUio>,
) {
HeadacheTheme {
Surface {
YearSummaryContent(
uio = remember { mutableStateOf(preview) },
onBack = { },
onMonth = { },
onAddEvent = { },
)
}
}
}
private class YearPreviewProvider() : PreviewParameterProvider<List<YearUio>> {
private fun year(
year: Int,
vararg months: YearSummaryMonthUio,
) = YearUio(
year = year,
months = months.toList(),
)
override val values: Sequence<List<YearUio>>
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
),
),
)
),
)
}
}

View file

@ -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

View file

@ -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<HeadacheTheme> {
error("Local Snack Controller is not yet ready")
val LocalHeadacheTheme = compositionLocalOf<HeadacheTheme> {
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
)
}
}

View file

@ -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(