diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml index c925b19..c9a3bbe 100644 --- a/.idea/deploymentTargetSelector.xml +++ b/.idea/deploymentTargetSelector.xml @@ -8,15 +8,15 @@ - - + + \ No newline at end of file diff --git a/app/release/baselineProfiles/0/app-release.dm b/app/release/baselineProfiles/0/app-release.dm index 6b396b6..12141fa 100644 Binary files a/app/release/baselineProfiles/0/app-release.dm and b/app/release/baselineProfiles/0/app-release.dm differ diff --git a/app/release/baselineProfiles/1/app-release.dm b/app/release/baselineProfiles/1/app-release.dm index 2b85bed..9c9005b 100644 Binary files a/app/release/baselineProfiles/1/app-release.dm and b/app/release/baselineProfiles/1/app-release.dm differ diff --git a/app/src/main/java/com/pixelized/headache/repository/event/EventRepository.kt b/app/src/main/java/com/pixelized/headache/repository/event/EventRepository.kt index 062f632..ed43d79 100644 --- a/app/src/main/java/com/pixelized/headache/repository/event/EventRepository.kt +++ b/app/src/main/java/com/pixelized/headache/repository/event/EventRepository.kt @@ -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>>> = eventListFlow + private val eventMapFlow: StateFlow>>>> = eventListFlow .map { events -> - events.fold(initial = hashMapOf>>()) { acc, event -> + events.fold(initial = hashMapOf>>>()) { 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 = eventMapFlow + .map { events -> + events.values.maxOf { yearEntry: Map>> -> + 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> = eventListFlow - fun eventsMapFlow(): StateFlow>>> = eventMapFlow + fun eventsMapFlow(): StateFlow>>>> = eventMapFlow + + fun maxPillAmountPerMonthFlow(): StateFlow = maxPillAmountPerMonth fun event(id: Long): Event? = eventFlow.value.get(key = id) diff --git a/app/src/main/java/com/pixelized/headache/ui/navigation/destination/ReportDestination.kt b/app/src/main/java/com/pixelized/headache/ui/navigation/destination/ReportDestination.kt new file mode 100644 index 0000000..00a7503 --- /dev/null +++ b/app/src/main/java/com/pixelized/headache/ui/navigation/destination/ReportDestination.kt @@ -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 { + ReportPage() + } +} + +fun HomeNavigator.navigateToReport() { + goTo(ReportDestination) +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/headache/ui/navigation/home/HomeNavDisplay.kt b/app/src/main/java/com/pixelized/headache/ui/navigation/home/HomeNavDisplay.kt index ec6204b..cd8d92b 100644 --- a/app/src/main/java/com/pixelized/headache/ui/navigation/home/HomeNavDisplay.kt +++ b/app/src/main/java/com/pixelized/headache/ui/navigation/home/HomeNavDisplay.kt @@ -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 { @@ -41,6 +42,7 @@ fun HomeNavDisplay( entryProvider = entryProvider { monthSummaryDestinationEntry() yearSummaryDestinationEntry() + reportDestinationEntry() } ) } diff --git a/app/src/main/java/com/pixelized/headache/ui/page/event/edit/EventEditFactory.kt b/app/src/main/java/com/pixelized/headache/ui/page/event/edit/EventEditFactory.kt index 6e90490..802e8a3 100644 --- a/app/src/main/java/com/pixelized/headache/ui/page/event/edit/EventEditFactory.kt +++ b/app/src/main/java/com/pixelized/headache/ui/page/event/edit/EventEditFactory.kt @@ -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( diff --git a/app/src/main/java/com/pixelized/headache/ui/page/home/HomePage.kt b/app/src/main/java/com/pixelized/headache/ui/page/home/HomePage.kt index dbf6aa7..69e2379 100644 --- a/app/src/main/java/com/pixelized/headache/ui/page/home/HomePage.kt +++ b/app/src/main/java/com/pixelized/headache/ui/page/home/HomePage.kt @@ -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, - selectedItem: IntState, + selectedItem: State, onFabClick: () -> Unit, ) { Scaffold( diff --git a/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/MonthSummaryFactory.kt b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/MonthSummaryFactory.kt index 2fd478a..b25e9ac 100644 --- a/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/MonthSummaryFactory.kt +++ b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/MonthSummaryFactory.kt @@ -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, - ): Map> { + events: Map>>>, + ): Map> { return events - .fold(hashMapOf>()) { acc, event -> - acc.also { it.getOrPut(event.date.copy(day = 1)) { mutableListOf() }.add(event) } - } - .map { entry -> - val pills = entry.value - .fold(hashMapOf()) { 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(), + ) { 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, - ): Map> { - var maxPillAmount = 0 - return events - .fold(hashMapOf>()) { 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.groupByMonth(): Map> { + fun List.groupByMonth(): Map> { 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, ) } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/MonthSummaryPage.kt b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/MonthSummaryPage.kt index c2b4f9b..efa3bfb 100644 --- a/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/MonthSummaryPage.kt +++ b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/MonthSummaryPage.kt @@ -1,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, - events: State>>, - onDisplay: () -> Unit, - onItem: (MonthSummaryCell) -> Unit, + events: State>>, + 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 = { }, ) } diff --git a/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/MonthSummaryViewModel.kt b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/MonthSummaryViewModel.kt index 06c3d71..6645d12 100644 --- a/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/MonthSummaryViewModel.kt +++ b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/MonthSummaryViewModel.kt @@ -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 = displayTypeFlow - @OptIn(ExperimentalCoroutinesApi::class) - val events: StateFlow>> = combine( - eventRepository.eventsListFlow(), - displayTypeFlow, - transform = { events: Collection, 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(), + ) } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/item/MonthSummaryBox.kt b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/item/MonthSummaryBox.kt deleted file mode 100644 index 99f51ba..0000000 --- a/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/item/MonthSummaryBox.kt +++ /dev/null @@ -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 { - val calendar = Calendar.getInstance().apply { - time = Date() - set(Calendar.DAY_OF_MONTH, 1) - } - override val values: Sequence - 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, - ), - ) -} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/item/MonthSummaryCell.kt b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/item/MonthSummaryCell.kt deleted file mode 100644 index 9d06e78..0000000 --- a/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/item/MonthSummaryCell.kt +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/item/MonthSummaryItem.kt b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/item/MonthSummaryItem.kt index 4101862..7ee80f1 100644 --- a/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/item/MonthSummaryItem.kt +++ b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/item/MonthSummaryItem.kt @@ -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, -) : MonthSummaryCell +) @Stable object MonthSummaryItemDefault { diff --git a/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/item/MonthSummaryTitle.kt b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/item/MonthSummaryTitle.kt index f898bd3..6c869e9 100644 --- a/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/item/MonthSummaryTitle.kt +++ b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/item/MonthSummaryTitle.kt @@ -15,8 +15,8 @@ import java.util.Locale @Stable data class MonthSummaryTitleUio( - override val date: Date, -) : MonthSummaryCell + val date: Date, +) @Stable object MonthSummaryTitleDefault { diff --git a/app/src/main/java/com/pixelized/headache/ui/page/summary/report/ReportBox.kt b/app/src/main/java/com/pixelized/headache/ui/page/summary/report/ReportBox.kt new file mode 100644 index 0000000..936bac6 --- /dev/null +++ b/app/src/main/java/com/pixelized/headache/ui/page/summary/report/ReportBox.kt @@ -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, +) { + @Stable + data class Month( + val date: Date, + val stats: List, + ) + + @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 { + override val values: Sequence + 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), + ) + ) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/headache/ui/page/summary/report/ReportFactory.kt b/app/src/main/java/com/pixelized/headache/ui/page/summary/report/ReportFactory.kt new file mode 100644 index 0000000..b1d05cf --- /dev/null +++ b/app/src/main/java/com/pixelized/headache/ui/page/summary/report/ReportFactory.kt @@ -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>>>, + ): List { + 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 } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/headache/ui/page/summary/report/ReportPage.kt b/app/src/main/java/com/pixelized/headache/ui/page/summary/report/ReportPage.kt new file mode 100644 index 0000000..d90bbf7 --- /dev/null +++ b/app/src/main/java/com/pixelized/headache/ui/page/summary/report/ReportPage.kt @@ -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>, +) { + 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, +) { + HeadacheTheme { + Surface { + ReportContent( + events = remember { mutableStateOf(preview) }, + ) + } + } +} + +private class ReportPreviewProvider : PreviewParameterProvider> { + + override val values: Sequence> + 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), + ), + ) + ) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/headache/ui/page/summary/report/ReportViewModel.kt b/app/src/main/java/com/pixelized/headache/ui/page/summary/report/ReportViewModel.kt new file mode 100644 index 0000000..1ac2581 --- /dev/null +++ b/app/src/main/java/com/pixelized/headache/ui/page/summary/report/ReportViewModel.kt @@ -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> = combine( + eventRepository.maxPillAmountPerMonthFlow(), + eventRepository.eventsMapFlow(), + reportFactory::convertToUio + ).stateIn( + scope = viewModelScope, + started = SharingStarted.Lazily, + initialValue = emptyList(), + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/headache/ui/page/summary/yearly/YearSummaryFactory.kt b/app/src/main/java/com/pixelized/headache/ui/page/summary/yearly/YearSummaryFactory.kt index 8b49002..27a35cf 100644 --- a/app/src/main/java/com/pixelized/headache/ui/page/summary/yearly/YearSummaryFactory.kt +++ b/app/src/main/java/com/pixelized/headache/ui/page/summary/yearly/YearSummaryFactory.kt @@ -8,7 +8,7 @@ import javax.inject.Inject class YearSummaryFactory @Inject constructor() { fun convertToUio( - events: Map>>, + events: Map>>>, ): List { 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 = 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 -> diff --git a/app/src/main/java/com/pixelized/headache/ui/page/summary/yearly/YearSummaryMonth.kt b/app/src/main/java/com/pixelized/headache/ui/page/summary/yearly/YearSummaryMonth.kt index 7961ea3..edb5d37 100644 --- a/app/src/main/java/com/pixelized/headache/ui/page/summary/yearly/YearSummaryMonth.kt +++ b/app/src/main/java/com/pixelized/headache/ui/page/summary/yearly/YearSummaryMonth.kt @@ -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, ) } diff --git a/app/src/main/java/com/pixelized/headache/ui/page/summary/yearly/YearSummaryPage.kt b/app/src/main/java/com/pixelized/headache/ui/page/summary/yearly/YearSummaryPage.kt index 790dd46..216522e 100644 --- a/app/src/main/java/com/pixelized/headache/ui/page/summary/yearly/YearSummaryPage.kt +++ b/app/src/main/java/com/pixelized/headache/ui/page/summary/yearly/YearSummaryPage.kt @@ -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( diff --git a/app/src/main/java/com/pixelized/headache/ui/theme/color/HeadacheColorPalette.kt b/app/src/main/java/com/pixelized/headache/ui/theme/color/HeadacheColorPalette.kt index 1dbb3b9..468c5f1 100644 --- a/app/src/main/java/com/pixelized/headache/ui/theme/color/HeadacheColorPalette.kt +++ b/app/src/main/java/com/pixelized/headache/ui/theme/color/HeadacheColorPalette.kt @@ -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) diff --git a/app/src/main/java/com/pixelized/headache/ui/theme/color/HeadacheColors.kt b/app/src/main/java/com/pixelized/headache/ui/theme/color/HeadacheColors.kt index 8ce3d84..602d222 100644 --- a/app/src/main/java/com/pixelized/headache/ui/theme/color/HeadacheColors.kt +++ b/app/src/main/java/com/pixelized/headache/ui/theme/color/HeadacheColors.kt @@ -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) } \ No newline at end of file