Add year summary feature
This commit is contained in:
parent
62c3639a9e
commit
51bf0c7496
26 changed files with 852 additions and 41 deletions
6
.idea/deploymentTargetSelector.xml
generated
6
.idea/deploymentTargetSelector.xml
generated
|
|
@ -5,9 +5,6 @@
|
||||||
<SelectionState runConfigName="app">
|
<SelectionState runConfigName="app">
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
</SelectionState>
|
</SelectionState>
|
||||||
<SelectionState runConfigName="testPill()">
|
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
|
||||||
</SelectionState>
|
|
||||||
<SelectionState runConfigName="EventFactoryText">
|
<SelectionState runConfigName="EventFactoryText">
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
</SelectionState>
|
</SelectionState>
|
||||||
|
|
@ -17,6 +14,9 @@
|
||||||
<SelectionState runConfigName="EventEditPreview">
|
<SelectionState runConfigName="EventEditPreview">
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
</SelectionState>
|
</SelectionState>
|
||||||
|
<SelectionState runConfigName="YearPreview">
|
||||||
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
|
</SelectionState>
|
||||||
</selectionStates>
|
</selectionStates>
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -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.eventDestinationEntry
|
||||||
import com.pixelized.headache.ui.navigation.destination.homeDestinationEntry
|
import com.pixelized.headache.ui.navigation.destination.homeDestinationEntry
|
||||||
import com.pixelized.headache.ui.navigation.destination.monthSummaryDestinationEntry
|
import com.pixelized.headache.ui.navigation.destination.monthSummaryDestinationEntry
|
||||||
|
import com.pixelized.headache.ui.navigation.destination.yearSummaryDestinationEntry
|
||||||
|
|
||||||
val LocalNavigator = staticCompositionLocalOf<Navigator> {
|
val LocalNavigator = staticCompositionLocalOf<Navigator> {
|
||||||
error("Local Navigation no yet ready")
|
error("Local Navigation no yet ready")
|
||||||
|
|
@ -42,6 +43,7 @@ fun MainNavDisplay(
|
||||||
calendarChooserDestinationEntry()
|
calendarChooserDestinationEntry()
|
||||||
eventDestinationEntry()
|
eventDestinationEntry()
|
||||||
monthSummaryDestinationEntry()
|
monthSummaryDestinationEntry()
|
||||||
|
yearSummaryDestinationEntry()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ package com.pixelized.headache.ui.navigation.destination
|
||||||
import androidx.navigation3.runtime.EntryProviderBuilder
|
import androidx.navigation3.runtime.EntryProviderBuilder
|
||||||
import androidx.navigation3.runtime.entry
|
import androidx.navigation3.runtime.entry
|
||||||
import com.pixelized.headache.ui.navigation.Navigator
|
import com.pixelized.headache.ui.navigation.Navigator
|
||||||
import com.pixelized.headache.ui.page.summary.MonthSummaryPage
|
import com.pixelized.headache.ui.page.summary.month.MonthSummaryPage
|
||||||
|
|
||||||
data object MonthSummaryDestination
|
data object MonthSummaryDestination
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.Navigator
|
||||||
|
import com.pixelized.headache.ui.page.summary.year.YearSummaryPage
|
||||||
|
|
||||||
|
|
||||||
|
data object YearSummaryDestination
|
||||||
|
|
||||||
|
fun EntryProviderBuilder<*>.yearSummaryDestinationEntry() {
|
||||||
|
entry<YearSummaryDestination> {
|
||||||
|
YearSummaryPage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Navigator.navigateToYearSummary() {
|
||||||
|
goTo(YearSummaryDestination)
|
||||||
|
}
|
||||||
|
|
@ -41,6 +41,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import com.pixelized.headache.R
|
import com.pixelized.headache.R
|
||||||
import com.pixelized.headache.ui.common.error.HandleErrorMessage
|
import com.pixelized.headache.ui.common.error.HandleErrorMessage
|
||||||
import com.pixelized.headache.ui.theme.HeadacheTheme
|
import com.pixelized.headache.ui.theme.HeadacheTheme
|
||||||
|
import com.pixelized.headache.ui.theme.color.HeadacheColorPalette
|
||||||
import com.pixelized.headache.utils.extention.capitalize
|
import com.pixelized.headache.utils.extention.capitalize
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
@ -223,20 +224,60 @@ private class EventEditPreviewPreviewProvider : PreviewParameterProvider<EventEd
|
||||||
id = null,
|
id = null,
|
||||||
date = Date(),
|
date = Date(),
|
||||||
pills = listOf(
|
pills = listOf(
|
||||||
EventPillEditUio(id = 0, label = "Paracétamol 1000", 0),
|
EventPillEditUio(
|
||||||
EventPillEditUio(id = 1, label = "Ibuprofène 400", 0),
|
id = 0,
|
||||||
EventPillEditUio(id = 2, label = "Spifen 400", 0),
|
color = HeadacheColorPalette.Pill.Paracetamol1000,
|
||||||
EventPillEditUio(id = 3, label = "Élétriptan 40", 0),
|
label = "Paracétamol 1000",
|
||||||
|
amount = 0,
|
||||||
|
),
|
||||||
|
EventPillEditUio(
|
||||||
|
id = 1,
|
||||||
|
color = HeadacheColorPalette.Pill.Ibuprofene400,
|
||||||
|
label = "Ibuprofène 400",
|
||||||
|
amount = 0,
|
||||||
|
),
|
||||||
|
EventPillEditUio(
|
||||||
|
id = 2,
|
||||||
|
color = HeadacheColorPalette.Pill.Spifen400,
|
||||||
|
label = "Spifen 400",
|
||||||
|
amount = 0,
|
||||||
|
),
|
||||||
|
EventPillEditUio(
|
||||||
|
id = 3,
|
||||||
|
color = HeadacheColorPalette.Pill.Eletriptan40,
|
||||||
|
label = "Élétriptan 40",
|
||||||
|
amount = 0,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
EventEditBottomSheetUio(
|
EventEditBottomSheetUio(
|
||||||
id = 1,
|
id = 1,
|
||||||
date = Date(),
|
date = Date(),
|
||||||
pills = listOf(
|
pills = listOf(
|
||||||
EventPillEditUio(id = 0, label = "Paracétamol 1000", 0),
|
EventPillEditUio(
|
||||||
EventPillEditUio(id = 1, label = "Ibuprofène 400", 0),
|
id = 0,
|
||||||
EventPillEditUio(id = 2, label = "Spifen 400", 2),
|
color = HeadacheColorPalette.Pill.Paracetamol1000,
|
||||||
EventPillEditUio(id = 3, label = "Élétriptan 40", 1),
|
label = "Paracétamol 1000",
|
||||||
|
amount = 0,
|
||||||
|
),
|
||||||
|
EventPillEditUio(
|
||||||
|
id = 1,
|
||||||
|
color = HeadacheColorPalette.Pill.Ibuprofene400,
|
||||||
|
label = "Ibuprofène 400",
|
||||||
|
amount = 0,
|
||||||
|
),
|
||||||
|
EventPillEditUio(
|
||||||
|
id = 2,
|
||||||
|
color = HeadacheColorPalette.Pill.Spifen400,
|
||||||
|
label = "Spifen 400",
|
||||||
|
amount = 2,
|
||||||
|
),
|
||||||
|
EventPillEditUio(
|
||||||
|
id = 3,
|
||||||
|
color = HeadacheColorPalette.Pill.Eletriptan40,
|
||||||
|
label = "Élétriptan 40",
|
||||||
|
amount = 1,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package com.pixelized.headache.ui.page.event.edit
|
||||||
|
|
||||||
import android.icu.util.Calendar
|
import android.icu.util.Calendar
|
||||||
import com.pixelized.headache.repository.event.Event
|
import com.pixelized.headache.repository.event.Event
|
||||||
|
import com.pixelized.headache.ui.theme.color.HeadacheColorPalette
|
||||||
import com.pixelized.headache.utils.extention.event
|
import com.pixelized.headache.utils.extention.event
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
@ -18,21 +19,25 @@ class EventEditFactory @Inject constructor() {
|
||||||
pills = listOf(
|
pills = listOf(
|
||||||
EventPillEditUio(
|
EventPillEditUio(
|
||||||
id = Event.Pill.Id.IBUPROFENE_400.value,
|
id = Event.Pill.Id.IBUPROFENE_400.value,
|
||||||
|
color = HeadacheColorPalette.Pill.Ibuprofene400,
|
||||||
label = "Paracétamol 1000",
|
label = "Paracétamol 1000",
|
||||||
amount = 0,
|
amount = 0,
|
||||||
),
|
),
|
||||||
EventPillEditUio(
|
EventPillEditUio(
|
||||||
id = Event.Pill.Id.PARACETAMOL_1000.value,
|
id = Event.Pill.Id.PARACETAMOL_1000.value,
|
||||||
|
color = HeadacheColorPalette.Pill.Paracetamol1000,
|
||||||
label = "Ibuprofène 400",
|
label = "Ibuprofène 400",
|
||||||
amount = 0,
|
amount = 0,
|
||||||
),
|
),
|
||||||
EventPillEditUio(
|
EventPillEditUio(
|
||||||
id = Event.Pill.Id.SPIFEN_400.value,
|
id = Event.Pill.Id.SPIFEN_400.value,
|
||||||
|
color = HeadacheColorPalette.Pill.Spifen400,
|
||||||
label = "Spifen 400",
|
label = "Spifen 400",
|
||||||
amount = 0,
|
amount = 0,
|
||||||
),
|
),
|
||||||
EventPillEditUio(
|
EventPillEditUio(
|
||||||
id = Event.Pill.Id.ELETRIPTAN_40.value,
|
id = Event.Pill.Id.ELETRIPTAN_40.value,
|
||||||
|
color = HeadacheColorPalette.Pill.Eletriptan40,
|
||||||
label = "Élétriptan 40",
|
label = "Élétriptan 40",
|
||||||
amount = 0,
|
amount = 0,
|
||||||
),
|
),
|
||||||
|
|
@ -61,21 +66,25 @@ class EventEditFactory @Inject constructor() {
|
||||||
pills = listOf(
|
pills = listOf(
|
||||||
EventPillEditUio(
|
EventPillEditUio(
|
||||||
id = Event.Pill.Id.IBUPROFENE_400.value,
|
id = Event.Pill.Id.IBUPROFENE_400.value,
|
||||||
|
color = HeadacheColorPalette.Pill.Ibuprofene400,
|
||||||
label = "Paracétamol 1000",
|
label = "Paracétamol 1000",
|
||||||
amount = ibuprofeneAmount,
|
amount = ibuprofeneAmount,
|
||||||
),
|
),
|
||||||
EventPillEditUio(
|
EventPillEditUio(
|
||||||
id = Event.Pill.Id.PARACETAMOL_1000.value,
|
id = Event.Pill.Id.PARACETAMOL_1000.value,
|
||||||
|
color = HeadacheColorPalette.Pill.Paracetamol1000,
|
||||||
label = "Ibuprofène 400",
|
label = "Ibuprofène 400",
|
||||||
amount = paracetamolAmount,
|
amount = paracetamolAmount,
|
||||||
),
|
),
|
||||||
EventPillEditUio(
|
EventPillEditUio(
|
||||||
id = Event.Pill.Id.SPIFEN_400.value,
|
id = Event.Pill.Id.SPIFEN_400.value,
|
||||||
|
color = HeadacheColorPalette.Pill.Spifen400,
|
||||||
label = "Spifen 400",
|
label = "Spifen 400",
|
||||||
amount = spifenAmount,
|
amount = spifenAmount,
|
||||||
),
|
),
|
||||||
EventPillEditUio(
|
EventPillEditUio(
|
||||||
id = Event.Pill.Id.ELETRIPTAN_40.value,
|
id = Event.Pill.Id.ELETRIPTAN_40.value,
|
||||||
|
color = HeadacheColorPalette.Pill.Eletriptan40,
|
||||||
label = "Élétriptan 40",
|
label = "Élétriptan 40",
|
||||||
amount = eletriptanAmount,
|
amount = eletriptanAmount,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,13 @@ import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.animation.slideInVertically
|
import androidx.compose.animation.slideInVertically
|
||||||
import androidx.compose.animation.slideOutVertically
|
import androidx.compose.animation.slideOutVertically
|
||||||
import androidx.compose.animation.togetherWith
|
import androidx.compose.animation.togetherWith
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.widthIn
|
import androidx.compose.foundation.layout.widthIn
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
|
@ -26,16 +29,19 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.Shape
|
import androidx.compose.ui.graphics.Shape
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.pixelized.headache.ui.theme.HeadacheTheme
|
import com.pixelized.headache.ui.theme.HeadacheTheme
|
||||||
|
import com.pixelized.headache.ui.theme.color.HeadacheColorPalette
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
data class EventPillEditUio(
|
data class EventPillEditUio(
|
||||||
val id: Long,
|
val id: Long,
|
||||||
|
val color: Color,
|
||||||
val label: String,
|
val label: String,
|
||||||
val amount: Int,
|
val amount: Int,
|
||||||
)
|
)
|
||||||
|
|
@ -87,6 +93,18 @@ fun EventPillEdit(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(space = spacing),
|
horizontalArrangement = Arrangement.spacedBy(space = spacing),
|
||||||
) {
|
) {
|
||||||
|
Text(
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
text = "-",
|
||||||
|
)
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(alignment = Alignment.CenterVertically)
|
||||||
|
.size(12.dp)
|
||||||
|
.background(color = item.color)
|
||||||
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.weight(weight = 1f),
|
modifier = Modifier.weight(weight = 1f),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
|
@ -148,6 +166,7 @@ private fun EventPillEditPreview() {
|
||||||
EventPillEdit(
|
EventPillEdit(
|
||||||
item = EventPillEditUio(
|
item = EventPillEditUio(
|
||||||
id = 0,
|
id = 0,
|
||||||
|
color = HeadacheColorPalette.Pill.Eletriptan40,
|
||||||
label = "Élétriptan 40",
|
label = "Élétriptan 40",
|
||||||
amount = 2,
|
amount = 2,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import com.pixelized.headache.ui.navigation.LocalNavigator
|
||||||
import com.pixelized.headache.ui.navigation.destination.navigateToCalendarChooserPage
|
import com.pixelized.headache.ui.navigation.destination.navigateToCalendarChooserPage
|
||||||
import com.pixelized.headache.ui.navigation.destination.navigateToEventPage
|
import com.pixelized.headache.ui.navigation.destination.navigateToEventPage
|
||||||
import com.pixelized.headache.ui.navigation.destination.navigateToMonthSummary
|
import com.pixelized.headache.ui.navigation.destination.navigateToMonthSummary
|
||||||
|
import com.pixelized.headache.ui.navigation.destination.navigateToYearSummary
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -45,6 +46,9 @@ fun HomePage() {
|
||||||
onMonthSummary = {
|
onMonthSummary = {
|
||||||
navigation.navigateToMonthSummary()
|
navigation.navigateToMonthSummary()
|
||||||
},
|
},
|
||||||
|
onYearSummary = {
|
||||||
|
navigation.navigateToYearSummary()
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -55,6 +59,7 @@ private fun HomePageContent(
|
||||||
onCalendarChooser: () -> Unit,
|
onCalendarChooser: () -> Unit,
|
||||||
onEvent: () -> Unit,
|
onEvent: () -> Unit,
|
||||||
onMonthSummary: () -> Unit,
|
onMonthSummary: () -> Unit,
|
||||||
|
onYearSummary: () -> Unit,
|
||||||
) {
|
) {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
|
@ -87,6 +92,12 @@ private fun HomePageContent(
|
||||||
onClick = onMonthSummary,
|
onClick = onMonthSummary,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
item {
|
||||||
|
NavigationItem(
|
||||||
|
label = stringResource(R.string.year_summary_title),
|
||||||
|
onClick = onYearSummary,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,16 @@
|
||||||
package com.pixelized.headache.ui.page.summary.item
|
package com.pixelized.headache.ui.page.summary.month
|
||||||
|
|
||||||
import android.icu.util.Calendar
|
import android.icu.util.Calendar
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import com.pixelized.headache.repository.event.Event
|
import com.pixelized.headache.repository.event.Event
|
||||||
import com.pixelized.headache.ui.page.summary.MonthSummaryPillItemUio
|
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.utils.extention.event
|
import com.pixelized.headache.utils.extention.event
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.reflect.KClass
|
|
||||||
|
|
||||||
class MonthSummaryFactory @Inject constructor() {
|
class MonthSummaryFactory @Inject constructor() {
|
||||||
private val calendar = Calendar.getInstance()
|
private val calendar = Calendar.getInstance()
|
||||||
|
|
@ -86,11 +89,11 @@ class MonthSummaryFactory @Inject constructor() {
|
||||||
date = calendar.apply { event = entry.key }.time,
|
date = calendar.apply { event = entry.key }.time,
|
||||||
headacheRatio = entry.value.size.toFloat() / monthMaxDay,
|
headacheRatio = entry.value.size.toFloat() / monthMaxDay,
|
||||||
headacheAmount = entry.value.size,
|
headacheAmount = entry.value.size,
|
||||||
headacheColor = Color.Red,
|
headacheColor = Color.Companion.Red,
|
||||||
pillRatio = pillAmount.takeIf { it > 0 }
|
pillRatio = pillAmount.takeIf { it > 0 }
|
||||||
?.let { it.toFloat() / maxPillAmount.toFloat() },
|
?.let { it.toFloat() / maxPillAmount.toFloat() },
|
||||||
pillAmount = pillAmount,
|
pillAmount = pillAmount,
|
||||||
pillColor = Color.Blue,
|
pillColor = Color.Companion.Blue,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.sortedByDescending { it.date }
|
.sortedByDescending { it.date }
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.pixelized.headache.ui.page.summary
|
package com.pixelized.headache.ui.page.summary.month
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedContent
|
import androidx.compose.animation.AnimatedContent
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
|
|
@ -44,13 +44,14 @@ import com.pixelized.headache.ui.navigation.LocalNavigator
|
||||||
import com.pixelized.headache.ui.navigation.destination.navigateToEventPage
|
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.EventEditBottomSheet
|
||||||
import com.pixelized.headache.ui.page.event.edit.EventEditBottomSheetViewModel
|
import com.pixelized.headache.ui.page.event.edit.EventEditBottomSheetViewModel
|
||||||
import com.pixelized.headache.ui.page.summary.item.MonthSummaryBox
|
import com.pixelized.headache.ui.page.summary.month.item.MonthSummaryBox
|
||||||
import com.pixelized.headache.ui.page.summary.item.MonthSummaryBoxUio
|
import com.pixelized.headache.ui.page.summary.month.item.MonthSummaryBoxUio
|
||||||
import com.pixelized.headache.ui.page.summary.item.MonthSummaryCell
|
import com.pixelized.headache.ui.page.summary.month.item.MonthSummaryCell
|
||||||
import com.pixelized.headache.ui.page.summary.item.MonthSummaryItem
|
import com.pixelized.headache.ui.page.summary.month.item.MonthSummaryItem
|
||||||
import com.pixelized.headache.ui.page.summary.item.MonthSummaryItemUio
|
import com.pixelized.headache.ui.page.summary.month.item.MonthSummaryItemUio
|
||||||
import com.pixelized.headache.ui.page.summary.item.MonthSummaryTitle
|
import com.pixelized.headache.ui.page.summary.month.item.MonthSummaryPillItemUio
|
||||||
import com.pixelized.headache.ui.page.summary.item.MonthSummaryTitleUio
|
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.theme.HeadacheTheme
|
import com.pixelized.headache.ui.theme.HeadacheTheme
|
||||||
import com.pixelized.headache.ui.theme.color.HeadacheColorPalette
|
import com.pixelized.headache.ui.theme.color.HeadacheColorPalette
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
package com.pixelized.headache.ui.page.summary
|
package com.pixelized.headache.ui.page.summary.month
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.pixelized.headache.repository.event.Event
|
import com.pixelized.headache.repository.event.Event
|
||||||
import com.pixelized.headache.repository.event.EventRepository
|
import com.pixelized.headache.repository.event.EventRepository
|
||||||
import com.pixelized.headache.ui.page.summary.item.MonthSummaryCell
|
import com.pixelized.headache.ui.page.summary.month.item.MonthSummaryCell
|
||||||
import com.pixelized.headache.ui.page.summary.item.MonthSummaryFactory
|
|
||||||
import com.pixelized.headache.ui.page.summary.item.MonthSummaryTitleUio
|
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.pixelized.headache.ui.page.summary.item
|
package com.pixelized.headache.ui.page.summary.month.item
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.icu.text.SimpleDateFormat
|
import android.icu.text.SimpleDateFormat
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.pixelized.headache.ui.page.summary.item
|
package com.pixelized.headache.ui.page.summary.month.item
|
||||||
|
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.pixelized.headache.ui.page.summary.item
|
package com.pixelized.headache.ui.page.summary.month.item
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.icu.text.SimpleDateFormat
|
import android.icu.text.SimpleDateFormat
|
||||||
|
|
@ -14,14 +14,13 @@ import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import androidx.compose.ui.unit.DpSize
|
import androidx.compose.ui.unit.DpSize
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.pixelized.headache.ui.page.summary.MonthSummaryPillItem
|
|
||||||
import com.pixelized.headache.ui.page.summary.MonthSummaryPillItemUio
|
|
||||||
import com.pixelized.headache.ui.theme.HeadacheTheme
|
import com.pixelized.headache.ui.theme.HeadacheTheme
|
||||||
import com.pixelized.headache.ui.theme.color.HeadacheColorPalette
|
import com.pixelized.headache.ui.theme.color.HeadacheColorPalette
|
||||||
import com.pixelized.headache.utils.extention.capitalize
|
import com.pixelized.headache.utils.extention.capitalize
|
||||||
|
|
@ -68,11 +67,10 @@ fun MonthSummaryItem(
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.spacedBy(space = spacing.width)
|
horizontalArrangement = Arrangement.spacedBy(space = spacing.width)
|
||||||
) {
|
) {
|
||||||
formatter.format(item.date)
|
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.alignByBaseline(),
|
modifier = Modifier.alignByBaseline(),
|
||||||
style = MaterialTheme.typography.titleLarge,
|
style = MaterialTheme.typography.titleLarge,
|
||||||
text = formatter.format(item.date).capitalize(),
|
text = remember(formatter, item.date) { formatter.format(item.date) }.capitalize(),
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.alignByBaseline(),
|
modifier = Modifier.alignByBaseline(),
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.pixelized.headache.ui.page.summary
|
package com.pixelized.headache.ui.page.summary.month.item
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.pixelized.headache.ui.page.summary.item
|
package com.pixelized.headache.ui.page.summary.month.item
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.icu.text.SimpleDateFormat
|
import android.icu.text.SimpleDateFormat
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
package com.pixelized.headache.ui.page.summary.year
|
||||||
|
|
||||||
|
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 javax.inject.Inject
|
||||||
|
|
||||||
|
|
||||||
|
class YearSummaryFactory @Inject constructor() {
|
||||||
|
|
||||||
|
fun convertToUio(
|
||||||
|
events: Collection<Event>,
|
||||||
|
): List<YearUio> {
|
||||||
|
|
||||||
|
val monthFirstDayCalendar = Calendar.getInstance().apply {
|
||||||
|
firstDayOfWeek = Calendar.MONDAY
|
||||||
|
setMinimalDaysInFirstWeek(1)
|
||||||
|
}
|
||||||
|
val monthLastDayCalendar = Calendar.getInstance().apply {
|
||||||
|
firstDayOfWeek = Calendar.MONDAY
|
||||||
|
setMinimalDaysInFirstWeek(1)
|
||||||
|
}
|
||||||
|
val currentDayCalendar = Calendar.getInstance().apply {
|
||||||
|
firstDayOfWeek = Calendar.MONDAY
|
||||||
|
setMinimalDaysInFirstWeek(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return events
|
||||||
|
.fold(initial = hashMapOf<Int, HashMap<Int, HashMap<Int, Event>>>()) { acc, event ->
|
||||||
|
acc.also {
|
||||||
|
val years = it.getOrPut(key = event.date.year) { hashMapOf() }
|
||||||
|
val months = years.getOrPut(key = event.date.month) { hashMapOf() }
|
||||||
|
months[event.date.day] = event
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.map { yearEntry ->
|
||||||
|
YearUio(
|
||||||
|
year = yearEntry.key,
|
||||||
|
months = yearEntry.value.map { monthEntry ->
|
||||||
|
monthFirstDayCalendar.apply {
|
||||||
|
set(Calendar.YEAR, yearEntry.key)
|
||||||
|
set(Calendar.MONTH, monthEntry.key)
|
||||||
|
set(Calendar.DAY_OF_MONTH, 1)
|
||||||
|
set(Calendar.MILLISECONDS_IN_DAY, 1)
|
||||||
|
}
|
||||||
|
monthLastDayCalendar.apply {
|
||||||
|
time = monthFirstDayCalendar.time
|
||||||
|
add(Calendar.MONTH, 1)
|
||||||
|
add(Calendar.DAY_OF_MONTH, -1)
|
||||||
|
}
|
||||||
|
currentDayCalendar.apply {
|
||||||
|
time = monthFirstDayCalendar.time
|
||||||
|
}
|
||||||
|
val initial = MutableList(
|
||||||
|
size = monthLastDayCalendar.get(Calendar.WEEK_OF_MONTH),
|
||||||
|
) {
|
||||||
|
mutableListOf<YearSummaryDayUio>()
|
||||||
|
}
|
||||||
|
val weeks = (1..monthLastDayCalendar.get(Calendar.DAY_OF_MONTH))
|
||||||
|
.fold(initial = initial) { accumulator, dayNumber ->
|
||||||
|
val event: Event? = monthEntry.value.get(dayNumber)
|
||||||
|
val weekIndex = currentDayCalendar
|
||||||
|
.apply {
|
||||||
|
set(Calendar.YEAR, yearEntry.key)
|
||||||
|
set(Calendar.MONTH, monthEntry.key)
|
||||||
|
set(Calendar.DAY_OF_MONTH, dayNumber)
|
||||||
|
set(Calendar.MILLISECONDS_IN_DAY, 1)
|
||||||
|
}
|
||||||
|
.get(Calendar.WEEK_OF_MONTH) - 1
|
||||||
|
val day = YearSummaryDayUio(
|
||||||
|
number = dayNumber,
|
||||||
|
headache = event != null,
|
||||||
|
pills = event?.pills?.map { it.color } ?: emptyList(),
|
||||||
|
)
|
||||||
|
accumulator.also { acc ->
|
||||||
|
acc[weekIndex] = acc.get(index = weekIndex).also { week ->
|
||||||
|
week.add(day)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
YearSummaryMonthUio(
|
||||||
|
date = monthFirstDayCalendar.time,
|
||||||
|
weeks = weeks,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.sortedByDescending { it.year }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,277 @@
|
||||||
|
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.graphics.Color
|
||||||
|
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.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.YearSummaryDayDefault
|
||||||
|
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 java.util.Calendar
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
|
@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()
|
||||||
|
.fillMaxSize(),
|
||||||
|
uio = uio,
|
||||||
|
onBack = {
|
||||||
|
navigation.popBackstack()
|
||||||
|
},
|
||||||
|
onMonth = {
|
||||||
|
navigation.navigateToEventPage(date = it.date)
|
||||||
|
},
|
||||||
|
onAddEvent = {
|
||||||
|
editViewModel.show()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
EventEditBottomSheet(
|
||||||
|
viewModel = editViewModel,
|
||||||
|
onDismissRequest = { editViewModel.dismiss() },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun YearSummaryContent(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
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 = { paddingValues ->
|
||||||
|
LazyVerticalGrid(
|
||||||
|
modifier = Modifier.padding(paddingValues = paddingValues),
|
||||||
|
columns = GridCells.Adaptive(
|
||||||
|
minSize = YearSummaryDayDefault.size * 7,
|
||||||
|
),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(space = 4.dp),
|
||||||
|
contentPadding = PaddingValues(
|
||||||
|
start = 16.dp,
|
||||||
|
top = 16.dp,
|
||||||
|
end = 16.dp,
|
||||||
|
bottom = 16.dp + 16.dp + 56.dp,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
uio.value.forEachIndexed { index, (year, months) ->
|
||||||
|
item(
|
||||||
|
span = { GridItemSpan(maxCurrentLineSpan) },
|
||||||
|
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,
|
||||||
|
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(),
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun month(
|
||||||
|
month: Date,
|
||||||
|
vararg weeks: List<YearSummaryDayUio>,
|
||||||
|
) = YearSummaryMonthUio(
|
||||||
|
date = month,
|
||||||
|
weeks = weeks.toList(),
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun week(
|
||||||
|
vararg days: YearSummaryDayUio,
|
||||||
|
) = days.toList()
|
||||||
|
|
||||||
|
private fun day(
|
||||||
|
number: Int,
|
||||||
|
headache: Boolean = false,
|
||||||
|
pills: List<Color> = emptyList(),
|
||||||
|
) = YearSummaryDayUio(
|
||||||
|
number = number,
|
||||||
|
headache = headache,
|
||||||
|
pills = pills,
|
||||||
|
)
|
||||||
|
|
||||||
|
override val values: Sequence<List<YearUio>> = sequenceOf(
|
||||||
|
listOf(
|
||||||
|
year(
|
||||||
|
year = 2025,
|
||||||
|
month(
|
||||||
|
month = Calendar.getInstance().apply {
|
||||||
|
set(Calendar.YEAR, 2025)
|
||||||
|
set(Calendar.MONTH, Calendar.JANUARY)
|
||||||
|
}.time,
|
||||||
|
week(day(1), day(2), day(3), day(4), day(5)),
|
||||||
|
week(day(6), day(7), day(8), day(9), day(10), day(11), day(12)),
|
||||||
|
week(day(13), day(14), day(15), day(16), day(17), day(18), day(19)),
|
||||||
|
week(day(20), day(21), day(22), day(23), day(24), day(25), day(26)),
|
||||||
|
week(day(27), day(28), day(29), day(30), day(31)),
|
||||||
|
),
|
||||||
|
month(
|
||||||
|
month = Calendar.getInstance().apply {
|
||||||
|
set(Calendar.YEAR, 2025)
|
||||||
|
set(Calendar.MONTH, Calendar.FEBRUARY)
|
||||||
|
}.time,
|
||||||
|
week(day(1), day(2)),
|
||||||
|
week(day(3), day(4), day(5), day(6), day(7), day(8), day(9)),
|
||||||
|
week(day(10), day(11), day(12), day(13), day(14), day(15), day(16)),
|
||||||
|
week(day(17), day(18), day(19), day(20), day(21), day(22), day(23)),
|
||||||
|
week(day(24), day(25), day(26), day(27), day(28)),
|
||||||
|
),
|
||||||
|
month(
|
||||||
|
month = Calendar.getInstance().apply {
|
||||||
|
set(Calendar.YEAR, 2025)
|
||||||
|
set(Calendar.MONTH, Calendar.MARCH)
|
||||||
|
}.time,
|
||||||
|
week(day(1), day(2)),
|
||||||
|
week(day(3), day(4), day(5), day(6), day(7), day(8), day(9)),
|
||||||
|
week(day(10), day(11), day(12), day(13), day(14), day(15), day(16)),
|
||||||
|
week(day(17), day(18), day(19), day(20), day(21), day(22), day(23)),
|
||||||
|
week(day(24), day(25), day(26), day(27), day(28), day(29), day(30)),
|
||||||
|
week(day(31)),
|
||||||
|
),
|
||||||
|
month(
|
||||||
|
month = Calendar.getInstance().apply {
|
||||||
|
set(Calendar.YEAR, 2025)
|
||||||
|
set(Calendar.MONTH, Calendar.APRIL)
|
||||||
|
}.time,
|
||||||
|
week(day(1), day(2), day(3), day(4), day(5), day(6)),
|
||||||
|
week(day(7), day(8), day(9), day(10), day(11), day(12), day(13)),
|
||||||
|
week(day(14), day(15), day(16), day(17), day(18), day(19), day(20)),
|
||||||
|
week(day(21), day(22), day(23), day(24), day(25), day(26), day(27)),
|
||||||
|
week(day(28), day(29), day(30), day(31)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
package com.pixelized.headache.ui.page.summary.year
|
||||||
|
|
||||||
|
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.map
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class YearSummaryViewModel @Inject constructor(
|
||||||
|
eventRepository: EventRepository,
|
||||||
|
summaryFactory: YearSummaryFactory,
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
val events: StateFlow<List<YearUio>> = eventRepository
|
||||||
|
.eventsListFlow()
|
||||||
|
.map(summaryFactory::convertToUio)
|
||||||
|
.stateIn(
|
||||||
|
scope = viewModelScope,
|
||||||
|
started = SharingStarted.Lazily,
|
||||||
|
initialValue = emptyList(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,127 @@
|
||||||
|
package com.pixelized.headache.ui.page.summary.year.item
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
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
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(vertical = pill + 1.dp)
|
||||||
|
.background(color = HeadacheColorPalette.Pill.Unknown)
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.align(alignment = Alignment.Center),
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
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 {
|
||||||
|
YearSummaryDay(
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,185 @@
|
||||||
|
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.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
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.graphics.Color
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
|
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,
|
||||||
|
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(),
|
||||||
|
)
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.height(height = remember { YearSummaryDayDefault.size * 6 }),
|
||||||
|
) {
|
||||||
|
uio.weeks.forEachIndexed { index, week ->
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.width(width = remember { YearSummaryDayDefault.size * 7 }),
|
||||||
|
horizontalArrangement = when (index) {
|
||||||
|
0 -> Arrangement.End
|
||||||
|
else -> Arrangement.Start
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
week.forEach { day ->
|
||||||
|
YearSummaryDay(day = day)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@Preview
|
||||||
|
private fun YearSummaryMonthPreview(
|
||||||
|
@PreviewParameter(MonthPreviewProvider::class) preview: YearSummaryMonthUio,
|
||||||
|
) {
|
||||||
|
HeadacheTheme {
|
||||||
|
Surface {
|
||||||
|
YearSummaryMonth(
|
||||||
|
uio = preview,
|
||||||
|
onMonth = { },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MonthPreviewProvider() : PreviewParameterProvider<YearSummaryMonthUio> {
|
||||||
|
|
||||||
|
fun day(
|
||||||
|
number: Int,
|
||||||
|
headache: Boolean = false,
|
||||||
|
pills: List<Color> = emptyList(),
|
||||||
|
): YearSummaryDayUio = YearSummaryDayUio(
|
||||||
|
number = number,
|
||||||
|
headache = headache,
|
||||||
|
pills = pills,
|
||||||
|
)
|
||||||
|
|
||||||
|
override val values: Sequence<YearSummaryMonthUio>
|
||||||
|
get() = sequenceOf(
|
||||||
|
YearSummaryMonthUio(
|
||||||
|
date = Calendar.getInstance().apply {
|
||||||
|
set(Calendar.YEAR, 2025)
|
||||||
|
set(Calendar.MONTH, Calendar.JULY)
|
||||||
|
}.time,
|
||||||
|
weeks = listOf(
|
||||||
|
listOf(
|
||||||
|
day(number = 1),
|
||||||
|
day(number = 2),
|
||||||
|
day(number = 3),
|
||||||
|
day(number = 4),
|
||||||
|
),
|
||||||
|
listOf(
|
||||||
|
day(number = 5),
|
||||||
|
day(number = 6, headache = true),
|
||||||
|
day(number = 7),
|
||||||
|
day(number = 8, headache = true),
|
||||||
|
day(number = 9),
|
||||||
|
day(number = 10),
|
||||||
|
day(number = 11),
|
||||||
|
),
|
||||||
|
listOf(
|
||||||
|
day(number = 12),
|
||||||
|
day(number = 13),
|
||||||
|
day(number = 14),
|
||||||
|
day(
|
||||||
|
number = 15,
|
||||||
|
headache = true,
|
||||||
|
pills = listOf(HeadacheColorPalette.Pill.Spifen400),
|
||||||
|
),
|
||||||
|
day(number = 16),
|
||||||
|
day(number = 17),
|
||||||
|
day(number = 18),
|
||||||
|
),
|
||||||
|
listOf(
|
||||||
|
day(
|
||||||
|
number = 19,
|
||||||
|
headache = true,
|
||||||
|
pills = listOf(HeadacheColorPalette.Pill.Spifen400),
|
||||||
|
),
|
||||||
|
day(number = 20),
|
||||||
|
day(number = 21),
|
||||||
|
day(number = 22, headache = true),
|
||||||
|
day(number = 23),
|
||||||
|
day(number = 24),
|
||||||
|
day(number = 25),
|
||||||
|
),
|
||||||
|
listOf(
|
||||||
|
day(
|
||||||
|
number = 26,
|
||||||
|
headache = true,
|
||||||
|
pills = listOf(
|
||||||
|
HeadacheColorPalette.Pill.Spifen400,
|
||||||
|
HeadacheColorPalette.Pill.Eletriptan40,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
day(number = 27, headache = true),
|
||||||
|
day(number = 28),
|
||||||
|
day(number = 29),
|
||||||
|
day(
|
||||||
|
number = 30,
|
||||||
|
headache = true,
|
||||||
|
pills = listOf(HeadacheColorPalette.Pill.Spifen400),
|
||||||
|
),
|
||||||
|
day(number = 31),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -20,7 +20,7 @@ object HeadacheColorPalette {
|
||||||
val Ibuprofene400 = Additional.Blue
|
val Ibuprofene400 = Additional.Blue
|
||||||
val Paracetamol1000 = Additional.Green
|
val Paracetamol1000 = Additional.Green
|
||||||
val Spifen400 = Additional.Yellow
|
val Spifen400 = Additional.Yellow
|
||||||
val Eletriptan40 = Additional.Pink
|
val Eletriptan40 = Additional.VeryLightPink
|
||||||
}
|
}
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
|
|
|
||||||
|
|
@ -8,5 +8,6 @@
|
||||||
|
|
||||||
<string name="calendar_chooser_title">Choix du calendrier</string>
|
<string name="calendar_chooser_title">Choix du calendrier</string>
|
||||||
<string name="event_title">Évennement migraineux</string>
|
<string name="event_title">Évennement migraineux</string>
|
||||||
<string name="month_summary_title">Suivi des migraines</string>
|
<string name="month_summary_title">Suivi mensuel</string>
|
||||||
|
<string name="year_summary_title">Suivi annuel</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -8,5 +8,6 @@
|
||||||
|
|
||||||
<string name="calendar_chooser_title">Choose your calendar</string>
|
<string name="calendar_chooser_title">Choose your calendar</string>
|
||||||
<string name="event_title">Headache event</string>
|
<string name="event_title">Headache event</string>
|
||||||
<string name="month_summary_title">Headache summary</string>
|
<string name="month_summary_title">Monthly follow-up</string>
|
||||||
|
<string name="year_summary_title">Annual follow-up</string>
|
||||||
</resources>
|
</resources>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue