From 6bb58e06c0bd671edd244003ed38c6f46545502c Mon Sep 17 00:00:00 2001 From: "Andres Gomez, Thomas (ITDV RL)" Date: Mon, 22 Sep 2025 18:28:37 +0200 Subject: [PATCH] Optimize the month cell display in the year summary. --- app/release/baselineProfiles/0/app-release.dm | Bin 4581 -> 4550 bytes app/release/baselineProfiles/1/app-release.dm | Bin 4543 -> 4502 bytes .../com/pixelized/headache/MainActivity.kt | 11 +- .../pixelized/headache/module/AppModule.kt | 14 +- .../repository/event/EventRepository.kt | 39 +- .../destination/CalendarChooserDestination.kt | 7 +- .../destination/EventDestination.kt | 7 +- .../navigation/destination/HomeDestination.kt | 16 +- .../destination/MonthSummaryDestination.kt | 9 +- .../destination/YearSummaryDestination.kt | 9 +- .../ui/navigation/home/HomeNavDisplay.kt | 47 ++ .../{Navigator.kt => home/HomeNavigator.kt} | 10 +- .../navigation/{ => main}/MainNavDisplay.kt | 18 +- .../ui/navigation/main/MainNavigator.kt | 20 + .../pixelized/headache/ui/page/MainPage.kt | 11 +- .../ui/page/calendar/CalendarChooserPage.kt | 4 +- .../headache/ui/page/event/list/EventPage.kt | 4 +- .../ui/page/event/list/EventViewModel.kt | 1 - .../headache/ui/page/home/HomePage.kt | 181 +++++-- .../{month => monthly}/MonthSummaryFactory.kt | 12 +- .../{month => monthly}/MonthSummaryPage.kt | 98 ++-- .../MonthSummaryViewModel.kt | 4 +- .../item/MonthSummaryBox.kt | 2 +- .../item/MonthSummaryCell.kt | 2 +- .../item/MonthSummaryItem.kt | 2 +- .../item/MonthSummaryPillItem.kt | 2 +- .../item/MonthSummaryTitle.kt | 39 +- .../ui/page/summary/year/YearSummaryPage.kt | 483 ------------------ .../page/summary/year/item/YearSummaryDay.kt | 136 ----- .../summary/year/item/YearSummaryMonth.kt | 243 --------- .../{year => yearly}/YearSummaryFactory.kt | 35 +- .../page/summary/yearly/YearSummaryMonth.kt | 346 +++++++++++++ .../ui/page/summary/yearly/YearSummaryPage.kt | 437 ++++++++++++++++ .../{year => yearly}/YearSummaryViewModel.kt | 4 +- .../com/pixelized/headache/ui/theme/Theme.kt | 30 +- .../headache/ui/theme/color/HeadacheColors.kt | 46 +- .../main/res/drawable/ic_analytics_24dp.xml | 9 + .../res/drawable/ic_analytics_filled_24dp.xml | 9 + .../res/drawable/ic_calendar_month_24dp.xml | 9 + .../ic_calendar_month_filled_24dp.xml | 9 + .../main/res/drawable/ic_list_alt_24dp.xml | 10 + .../res/drawable/ic_list_alt_filled_24dp.xml | 10 + 42 files changed, 1266 insertions(+), 1119 deletions(-) create mode 100644 app/src/main/java/com/pixelized/headache/ui/navigation/home/HomeNavDisplay.kt rename app/src/main/java/com/pixelized/headache/ui/navigation/{Navigator.kt => home/HomeNavigator.kt} (67%) rename app/src/main/java/com/pixelized/headache/ui/navigation/{ => main}/MainNavDisplay.kt (75%) create mode 100644 app/src/main/java/com/pixelized/headache/ui/navigation/main/MainNavigator.kt rename app/src/main/java/com/pixelized/headache/ui/page/summary/{month => monthly}/MonthSummaryFactory.kt (90%) rename app/src/main/java/com/pixelized/headache/ui/page/summary/{month => monthly}/MonthSummaryPage.kt (72%) rename app/src/main/java/com/pixelized/headache/ui/page/summary/{month => monthly}/MonthSummaryViewModel.kt (91%) rename app/src/main/java/com/pixelized/headache/ui/page/summary/{month => monthly}/item/MonthSummaryBox.kt (99%) rename app/src/main/java/com/pixelized/headache/ui/page/summary/{month => monthly}/item/MonthSummaryCell.kt (67%) rename app/src/main/java/com/pixelized/headache/ui/page/summary/{month => monthly}/item/MonthSummaryItem.kt (98%) rename app/src/main/java/com/pixelized/headache/ui/page/summary/{month => monthly}/item/MonthSummaryPillItem.kt (97%) rename app/src/main/java/com/pixelized/headache/ui/page/summary/{month => monthly}/item/MonthSummaryTitle.kt (53%) delete mode 100644 app/src/main/java/com/pixelized/headache/ui/page/summary/year/YearSummaryPage.kt delete mode 100644 app/src/main/java/com/pixelized/headache/ui/page/summary/year/item/YearSummaryDay.kt delete mode 100644 app/src/main/java/com/pixelized/headache/ui/page/summary/year/item/YearSummaryMonth.kt rename app/src/main/java/com/pixelized/headache/ui/page/summary/{year => yearly}/YearSummaryFactory.kt (68%) create mode 100644 app/src/main/java/com/pixelized/headache/ui/page/summary/yearly/YearSummaryMonth.kt create mode 100644 app/src/main/java/com/pixelized/headache/ui/page/summary/yearly/YearSummaryPage.kt rename app/src/main/java/com/pixelized/headache/ui/page/summary/{year => yearly}/YearSummaryViewModel.kt (90%) create mode 100644 app/src/main/res/drawable/ic_analytics_24dp.xml create mode 100644 app/src/main/res/drawable/ic_analytics_filled_24dp.xml create mode 100644 app/src/main/res/drawable/ic_calendar_month_24dp.xml create mode 100644 app/src/main/res/drawable/ic_calendar_month_filled_24dp.xml create mode 100644 app/src/main/res/drawable/ic_list_alt_24dp.xml create mode 100644 app/src/main/res/drawable/ic_list_alt_filled_24dp.xml diff --git a/app/release/baselineProfiles/0/app-release.dm b/app/release/baselineProfiles/0/app-release.dm index f3c8975c83144b16da34d0776931eddae74ef7c6..6b396b61f9f22fa32abe0f308b7194810d7e8c8c 100644 GIT binary patch delta 4435 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 TmjM}k=bL;$P@c_N5F`iyAYI*j literal 4581 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 literal 4543 zcmWIWW@Zs#;Nak3Fl#c4W_P zV3@(jz)->XHYPhq=Bntv*IW1YJ-*SV^2G7PEENaUMMnY~*+e(Fc}8FP=Falhku{fx z_3OeAUas7=s|s4+EmwrPh5Kq8<*|nxfhvw!R4j4xO?=erSspFoS11T z`m5*TF$qESV_wfSj_q-*UPW!;ah zS+`p1UNz?(TOc6+#pR~&FO9zs68EobOqc(e(Y;KYUClmYEC1m=iM21(^|O1G_sba< zt}xtQBA@U;!Ta^0o1)p}68=)B_IPDzOy99`#o;oC=bH|Qy=wnELtERa;@jeDMH|u* z|GloW{bwLod*G$5-{mVzGI8Bo&ps@D)-xgBQTVMgEF+;)zna+a-GTu8**P5~ftWr{)WP_AQY-VfoN- zp~p_ex?XilcX+*76>{C@fwpMz-u3=hD=gWUIqM`czkT@QYZPZQQx!8P~)Df~ISb>hZnzZ(nIZ9Kb~{hYx8LH~UpBoxHA+}OPF-o(1HhIHwgDSMp~ zwyfvPy5w-}XVF|$al2}TI-BQ=pDy^VJ2*l1pH1_5`3Dz&i{IMu!ur%D)2{(`zg{Hs zX3KndEZKc6T+im_r_);(3(WmyoOzD>&sno?8o$in*k8Kx`sMa(Q>2B@Nv;um@RI9Q z(a-5@U()*~>uY^Kb#>*-3A4|5KhwD}<`;)Zwaz8S@9HBIWdZ~Dmx)fvwJ@Me45 zmrT1e_MgHxKimB>>r~Gq)6l9#CFfn{oI5|$V&6(B;pZlQPJUf9;a%xL8J4%~Z|na` zmMB)Q?$m!eJL|=x-#eW)ElN64%VpE@Q>OLCBO6Yegm1|@JAUv9>^Z}H;DYhQ@|(}u zjktQ7e~3D{@3^$9ahF`P_3RN}*L#_J z`MWM=te@ij_;tb8zlE`^cF$zyovHXxT^Z#bqWAh=RAc+mfE%YQJtA()n|^O#?w|Mg z!NWgCcAEaVSy*Aq|G8=Ydua`BlODsDk(b?GrRr_!}|VMM%0t!(&TTh%wg=N=Y4)3amNY}Z?}_};nx z$66m3fAie=+3EK@gKS;?4@ZmcDv5vZDyY}p{_#)1>2K#*ZqF?LwZ>8A*z$)n4ere} zW0T#!Vq@s8ybslqB!rLjEj`n_4@c}J{zKeHwOG-Q2U+_7xsHLFbu7nYu1 zy=C#8T|vg-+C6kKXUxvCh%?eCYb7IpJ~E(-@mEO z=5r9=?~8o>|79JE_SjFUJG6A4^AW@FUEiF}e{zsNc79`keb&DCmb((d1=-C}m#;pI zx$|n@iruEm0@O_uYTX-s(wzg|?n^yBWhL`;Fm2zBRB{ zyx8f#&cJu0**BSY>
4tgh8Q65`gZe>?5_PKQL`gf% z3`M+_esR|pJbW%VpD!bxBkS4qqRiJZSNAO8yuRe3)yJKdPZ!L;FX+{gYWOo-vM0Ycg)*7Y4J<#=9LRgwr`6JJ})#|Vd+Wf@R&8( zQ{v zWD{55QJVvI&$r6xKa&1?U8l0mF!VWp<=q#NfnIWH^?IufA0^3Dkg zeIhpXJ)&!q4SIZ>6O7XZl_pMjf9uC)?HvC)7mdT)pLaw~-&((BU(M|2mEWFxejePD zt2=$`GVK$6Zux1yBbmL{Ne8~Z6Qkc>Q@L~N%*tt(5AU3K*oWm*s;j1Eht;OsN`q}O zw%4w`u&?0zc~9}?_628RdZ&pTI~Ho!{#)&yTii>D7S*JfLgNR$I(Nd)mO1Sc6hHSi z>(qLmY5V^_FWb3c`L&MtFZXZ!WacUN515y1Cspucd5^JL?v*XATek%t4m1w=?$0{M zT6D^$vZ6I7ug|(a%l_G!N%!ODxz?#Y%_z2fy8iWxlP9n4ebRa8XYHHFY1`w@ns%#h zpX9jh=W6XR(>1F$E!(o|em%4O@!!++_awfbv-zLc!Bn|i}R|B zlbPG9i`8a&rsn7*zdifz-SM}#uc_U=nRecAwpD8U??u*sd@XzUZyjXyp7(9$=JJF3 zcPuh~eLG$iYq{}$YjE|i_s(Hjn+4*<&rX~7gJE`75~0@ZC`2L4i#HOc-{hJT&SZ1H*|HW)a=a+v!&wX3# zo~(4RRphx!2mi4%n;Xw8@H(=a)js1Xr*>{n%cXn&zcw?!zRvT`V;84!3p?YN!}dSi zV?7x^?wX-{|H>&&p~uaS)@)fY>5{e9iX9%?zKNWOkGy;F-kUswxzfARbt4rjYOZV7 zN|m2?FyH+0r0w%PTNdAzt#YU+y4bW~md4(BQ+YD~NnA2hzhAm1rb2Jpy-UeayQ1XVgpPl}q@y)zv zCiCCt&sK2oE_>HE+yBIyBQDxupVV^t3KZD6T3FXau^L-P@6pto)W6qW;6!=V!qtnH zzFqQTap0XIhqH4U($>`9nS1}!H9HQ!^rii*3-&x;+xYy9G82mo_w$*o|2t=DpDq7x zm!Cer_ImcRn>{tj6D3~?2*-U5J(eQ8W#wx>&ieQDxmiafHx{xSv0(1-^78u-%@_S# z_V=CSXDnrlw$#>qy;Yr=AK+cG`}gII3#}hzS}M&j+rw1+-nz;4T1o!I=xsY*&+9qr z`%8IQwTAAt%2bV)62%u&&;L`)t=p6OcYWPnr!9ZZ%vaxlxodN;A|@=fFQ*DpW%d9QiR-H*S0 z8J~piE?7{Z+x_wFOX6mR&|4nY2E58pj&*Yi*Wx4Eyhtm%)Ve#CWvvKF$Bjx9eyWia? zb3NbmT)$Q=?VZ`$`QlxQ{dt%2?k&{w-=%G=CY*h&WWve{clY!0fAhkBr|7(q&*drm z@0c(4Gv<{Z=a(ryQZ z>atL)@yg+Rt10{WcTJkhy>b866VEnApWFX%l0NgJXZv2Rt!3DInX#dx=|58mzvcd( zyBd@pYS>9XW<#E@Yv$@r31jUH4JmkVtX1FX0{5e9-184Te00+ zwy-C^w6;_^S1@;`^P~?dd>cJ0pNYj4-;54Sx$#BgXQ<3t{maW@b#~_b$lE`AhhA|Gz-sz`bwQ@zg+*lK{zRW)Ods1r4?!=9%TUI4*zIH4NiD$Jt+%(sC_tR)0F>n*Y_! zTLo?!`=9JuSLyVQ)BLwMTcLm%C@ryn@hK2wVqoB50;MHJCJ_e2s0ng91C5#>4Wgi> otpIOSoyeww(jh|U3qI7*m)rnvRyL3{69W@NDFXw8i6Dpv04^)#H~;_u diff --git a/app/src/main/java/com/pixelized/headache/MainActivity.kt b/app/src/main/java/com/pixelized/headache/MainActivity.kt index 2df193a..3e1b6b4 100644 --- a/app/src/main/java/com/pixelized/headache/MainActivity.kt +++ b/app/src/main/java/com/pixelized/headache/MainActivity.kt @@ -10,7 +10,8 @@ import androidx.activity.enableEdgeToEdge import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.material3.Surface import androidx.core.content.ContextCompat -import com.pixelized.headache.ui.navigation.Navigator +import com.pixelized.headache.ui.navigation.home.HomeNavigator +import com.pixelized.headache.ui.navigation.main.MainNavigator import com.pixelized.headache.ui.page.MainPage import com.pixelized.headache.ui.theme.HeadacheTheme import dagger.hilt.android.AndroidEntryPoint @@ -20,7 +21,10 @@ import javax.inject.Inject class MainActivity : ComponentActivity() { @Inject - lateinit var navigator: Navigator + lateinit var mainNavigator: MainNavigator + + @Inject + lateinit var homeNavigator: HomeNavigator private val requestPermissionLauncher = registerForActivityResult( ActivityResultContracts.RequestPermission() @@ -41,7 +45,8 @@ class MainActivity : ComponentActivity() { HeadacheTheme { Surface { MainPage( - navigator = navigator, + mainNavigator = mainNavigator, + homeNavigator = homeNavigator, ) } } diff --git a/app/src/main/java/com/pixelized/headache/module/AppModule.kt b/app/src/main/java/com/pixelized/headache/module/AppModule.kt index ccb74ad..583424f 100644 --- a/app/src/main/java/com/pixelized/headache/module/AppModule.kt +++ b/app/src/main/java/com/pixelized/headache/module/AppModule.kt @@ -1,7 +1,9 @@ package com.pixelized.headache.module -import com.pixelized.headache.ui.navigation.Navigator import com.pixelized.headache.ui.navigation.destination.HomePageDestination +import com.pixelized.headache.ui.navigation.destination.YearSummaryDestination +import com.pixelized.headache.ui.navigation.home.HomeNavigator +import com.pixelized.headache.ui.navigation.main.MainNavigator import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -14,5 +16,13 @@ object AppModule { @Provides @ActivityRetainedScoped - fun provideNavigator(): Navigator = Navigator(startDestination = HomePageDestination) + fun provideMainNavigator(): MainNavigator { + return MainNavigator(startDestination = HomePageDestination) + } + + @Provides + @ActivityRetainedScoped + fun provideHomeNavigator(): HomeNavigator { + return HomeNavigator(startDestination = YearSummaryDestination) + } } 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 c7c74e2..062f632 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 @@ -33,8 +33,11 @@ class EventRepository @Inject constructor( ) { private val timeZone = TimeZone.getTimeZone("UTC") private val scope = CoroutineScope(Dispatchers.IO + Job()) + + // Main flow of event. Store event in a map by Id private val eventFlow = MutableStateFlow(mapOf()) + // Collectable data. Provide events in a sorted list by dates. private val eventListFlow: StateFlow> = eventFlow .map { events -> events.values.sortedBy { it.date } } .stateIn( @@ -43,6 +46,38 @@ class EventRepository @Inject constructor( initialValue = emptyList(), ) + // Collectable data. Provide event data in a nested map struct of year, month, day. + private val eventMapFlow: StateFlow>>> = eventListFlow + .map { events -> + events.fold(initial = hashMapOf>>()) { acc, event -> + acc.also { + val years = it.getOrPut(key = event.date.year) { + hashMapOf( + Calendar.JANUARY to hashMapOf(), + Calendar.FEBRUARY to hashMapOf(), + Calendar.MARCH to hashMapOf(), + Calendar.APRIL to hashMapOf(), + Calendar.MAY to hashMapOf(), + Calendar.JUNE to hashMapOf(), + Calendar.JULY to hashMapOf(), + Calendar.AUGUST to hashMapOf(), + Calendar.SEPTEMBER to hashMapOf(), + Calendar.OCTOBER to hashMapOf(), + Calendar.NOVEMBER to hashMapOf(), + Calendar.DECEMBER to hashMapOf(), + ) + } + val months = years.getOrPut(key = event.date.month) { hashMapOf() } + months[event.date.day] = event + } + } + } + .stateIn( + scope = scope, + started = SharingStarted.Lazily, + initialValue = emptyMap(), + ) + init { calendarIdRepository.calendarId .onEach { id -> @@ -55,10 +90,10 @@ class EventRepository @Inject constructor( .launchIn(scope = scope) } - fun eventsFlow(): StateFlow> = eventFlow - fun eventsListFlow(): StateFlow> = eventListFlow + fun eventsMapFlow(): StateFlow>>> = eventMapFlow + fun event(id: Long): Event? = eventFlow.value.get(key = id) fun createEvent( diff --git a/app/src/main/java/com/pixelized/headache/ui/navigation/destination/CalendarChooserDestination.kt b/app/src/main/java/com/pixelized/headache/ui/navigation/destination/CalendarChooserDestination.kt index de619b0..4248285 100644 --- a/app/src/main/java/com/pixelized/headache/ui/navigation/destination/CalendarChooserDestination.kt +++ b/app/src/main/java/com/pixelized/headache/ui/navigation/destination/CalendarChooserDestination.kt @@ -2,10 +2,11 @@ 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.navigation.main.MainDestination +import com.pixelized.headache.ui.navigation.main.MainNavigator import com.pixelized.headache.ui.page.calendar.CalendarChooserPage -data object CalendarChooserDestination +data object CalendarChooserDestination : MainDestination fun EntryProviderBuilder<*>.calendarChooserDestinationEntry() { entry { @@ -13,6 +14,6 @@ fun EntryProviderBuilder<*>.calendarChooserDestinationEntry() { } } -fun Navigator.navigateToCalendarChooserPage() { +fun MainNavigator.navigateToCalendarChooserPage() { goTo(CalendarChooserDestination) } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/headache/ui/navigation/destination/EventDestination.kt b/app/src/main/java/com/pixelized/headache/ui/navigation/destination/EventDestination.kt index 4eff6e4..ad901f5 100644 --- a/app/src/main/java/com/pixelized/headache/ui/navigation/destination/EventDestination.kt +++ b/app/src/main/java/com/pixelized/headache/ui/navigation/destination/EventDestination.kt @@ -5,7 +5,8 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation3.runtime.EntryProviderBuilder import androidx.navigation3.runtime.entry import com.pixelized.headache.repository.event.Event -import com.pixelized.headache.ui.navigation.Navigator +import com.pixelized.headache.ui.navigation.main.MainDestination +import com.pixelized.headache.ui.navigation.main.MainNavigator import com.pixelized.headache.ui.page.event.list.EventPage import com.pixelized.headache.ui.page.event.list.EventViewModel import com.pixelized.headache.utils.extention.event @@ -15,7 +16,7 @@ data class EventDestination( val date: Event.Date?, val misspelledFilter: Boolean, val invalidFilter: Boolean, -) +) : MainDestination fun EntryProviderBuilder<*>.eventDestinationEntry() { entry { key -> @@ -30,7 +31,7 @@ fun EntryProviderBuilder<*>.eventDestinationEntry() { } } -fun Navigator.navigateToEventPage( +fun MainNavigator.navigateToEventPage( misspelledFilter: Boolean = false, invalidFilter: Boolean = false, date: Date? = null, diff --git a/app/src/main/java/com/pixelized/headache/ui/navigation/destination/HomeDestination.kt b/app/src/main/java/com/pixelized/headache/ui/navigation/destination/HomeDestination.kt index a53fde6..d506f26 100644 --- a/app/src/main/java/com/pixelized/headache/ui/navigation/destination/HomeDestination.kt +++ b/app/src/main/java/com/pixelized/headache/ui/navigation/destination/HomeDestination.kt @@ -2,17 +2,23 @@ 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.navigation.home.HomeNavigator +import com.pixelized.headache.ui.navigation.main.MainDestination +import com.pixelized.headache.ui.navigation.main.MainNavigator import com.pixelized.headache.ui.page.home.HomePage -data object HomePageDestination +data object HomePageDestination : MainDestination -fun EntryProviderBuilder<*>.homeDestinationEntry() { +fun EntryProviderBuilder<*>.homeDestinationEntry( + navigator: HomeNavigator, +) { entry { - HomePage() + HomePage( + navigator = navigator, + ) } } -fun Navigator.navigateToHomePage() { +fun MainNavigator.navigateToHomePage() { goTo(HomePageDestination) } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/headache/ui/navigation/destination/MonthSummaryDestination.kt b/app/src/main/java/com/pixelized/headache/ui/navigation/destination/MonthSummaryDestination.kt index 2f4541a..6166876 100644 --- a/app/src/main/java/com/pixelized/headache/ui/navigation/destination/MonthSummaryDestination.kt +++ b/app/src/main/java/com/pixelized/headache/ui/navigation/destination/MonthSummaryDestination.kt @@ -2,10 +2,11 @@ package com.pixelized.headache.ui.navigation.destination import androidx.navigation3.runtime.EntryProviderBuilder import androidx.navigation3.runtime.entry -import com.pixelized.headache.ui.navigation.Navigator -import com.pixelized.headache.ui.page.summary.month.MonthSummaryPage +import com.pixelized.headache.ui.navigation.home.HomeDestination +import com.pixelized.headache.ui.navigation.home.HomeNavigator +import com.pixelized.headache.ui.page.summary.monthly.MonthSummaryPage -data object MonthSummaryDestination +data object MonthSummaryDestination : HomeDestination fun EntryProviderBuilder<*>.monthSummaryDestinationEntry() { entry { @@ -13,6 +14,6 @@ fun EntryProviderBuilder<*>.monthSummaryDestinationEntry() { } } -fun Navigator.navigateToMonthSummary() { +fun HomeNavigator.navigateToMonthSummary() { goTo(MonthSummaryDestination) } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/headache/ui/navigation/destination/YearSummaryDestination.kt b/app/src/main/java/com/pixelized/headache/ui/navigation/destination/YearSummaryDestination.kt index 6fd30fb..1218a57 100644 --- a/app/src/main/java/com/pixelized/headache/ui/navigation/destination/YearSummaryDestination.kt +++ b/app/src/main/java/com/pixelized/headache/ui/navigation/destination/YearSummaryDestination.kt @@ -2,11 +2,12 @@ package com.pixelized.headache.ui.navigation.destination import androidx.navigation3.runtime.EntryProviderBuilder import androidx.navigation3.runtime.entry -import com.pixelized.headache.ui.navigation.Navigator -import com.pixelized.headache.ui.page.summary.year.YearSummaryPage +import com.pixelized.headache.ui.navigation.home.HomeDestination +import com.pixelized.headache.ui.navigation.home.HomeNavigator +import com.pixelized.headache.ui.page.summary.yearly.YearSummaryPage -data object YearSummaryDestination +data object YearSummaryDestination : HomeDestination fun EntryProviderBuilder<*>.yearSummaryDestinationEntry() { entry { @@ -14,6 +15,6 @@ fun EntryProviderBuilder<*>.yearSummaryDestinationEntry() { } } -fun Navigator.navigateToYearSummary() { +fun HomeNavigator.navigateToYearSummary() { goTo(YearSummaryDestination) } \ 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 new file mode 100644 index 0000000..ec6204b --- /dev/null +++ b/app/src/main/java/com/pixelized/headache/ui/navigation/home/HomeNavDisplay.kt @@ -0,0 +1,47 @@ +package com.pixelized.headache.ui.navigation.home + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.staticCompositionLocalOf +import androidx.compose.ui.Modifier +import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator +import androidx.navigation3.runtime.entryProvider +import androidx.navigation3.runtime.rememberSavedStateNavEntryDecorator +import androidx.navigation3.ui.NavDisplay +import androidx.navigation3.ui.rememberSceneSetupNavEntryDecorator +import com.pixelized.headache.ui.navigation.destination.calendarChooserDestinationEntry +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.yearSummaryDestinationEntry + +val LocalHomeNavigator = staticCompositionLocalOf { + error("Local Navigation no yet ready") +} + +@Composable +fun HomeNavDisplay( + modifier: Modifier = Modifier, + navigator: HomeNavigator, +) { + CompositionLocalProvider( + LocalHomeNavigator provides navigator, + ) { + NavDisplay( + modifier = modifier, + backStack = navigator.backStack, + entryDecorators = listOf( + rememberSceneSetupNavEntryDecorator(), + rememberSavedStateNavEntryDecorator(), + rememberViewModelStoreNavEntryDecorator(), + ), + onBack = { + navigator.popBackstack() + }, + entryProvider = entryProvider { + monthSummaryDestinationEntry() + yearSummaryDestinationEntry() + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/headache/ui/navigation/Navigator.kt b/app/src/main/java/com/pixelized/headache/ui/navigation/home/HomeNavigator.kt similarity index 67% rename from app/src/main/java/com/pixelized/headache/ui/navigation/Navigator.kt rename to app/src/main/java/com/pixelized/headache/ui/navigation/home/HomeNavigator.kt index 262d9e6..ff85fba 100644 --- a/app/src/main/java/com/pixelized/headache/ui/navigation/Navigator.kt +++ b/app/src/main/java/com/pixelized/headache/ui/navigation/home/HomeNavigator.kt @@ -1,18 +1,20 @@ -package com.pixelized.headache.ui.navigation +package com.pixelized.headache.ui.navigation.home import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.snapshots.SnapshotStateList import dagger.hilt.android.scopes.ActivityRetainedScoped +interface HomeDestination + @ActivityRetainedScoped -class Navigator( - startDestination: Any, +class HomeNavigator( + startDestination: HomeDestination, ) { val backStack: SnapshotStateList = mutableStateListOf(startDestination) fun popBackstack() = backStack.removeLastOrNull() - fun goTo(destination: Any) { + fun goTo(destination: HomeDestination) { backStack.add(destination) } } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/headache/ui/navigation/MainNavDisplay.kt b/app/src/main/java/com/pixelized/headache/ui/navigation/main/MainNavDisplay.kt similarity index 75% rename from app/src/main/java/com/pixelized/headache/ui/navigation/MainNavDisplay.kt rename to app/src/main/java/com/pixelized/headache/ui/navigation/main/MainNavDisplay.kt index 0b88856..a1b1e3f 100644 --- a/app/src/main/java/com/pixelized/headache/ui/navigation/MainNavDisplay.kt +++ b/app/src/main/java/com/pixelized/headache/ui/navigation/main/MainNavDisplay.kt @@ -1,4 +1,4 @@ -package com.pixelized.headache.ui.navigation +package com.pixelized.headache.ui.navigation.main import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider @@ -14,32 +14,34 @@ 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.yearSummaryDestinationEntry +import com.pixelized.headache.ui.navigation.home.HomeNavigator -val LocalNavigator = staticCompositionLocalOf { +val LocalMainNavigator = staticCompositionLocalOf { error("Local Navigation no yet ready") } @Composable fun MainNavDisplay( modifier: Modifier = Modifier, - navigator: Navigator, + mainNavigator: MainNavigator, + homeNavigator: HomeNavigator, ) { CompositionLocalProvider( - LocalNavigator provides navigator, + LocalMainNavigator provides mainNavigator, ) { NavDisplay( modifier = modifier, - backStack = navigator.backStack, + backStack = mainNavigator.backStack, entryDecorators = listOf( rememberSceneSetupNavEntryDecorator(), rememberSavedStateNavEntryDecorator(), - rememberViewModelStoreNavEntryDecorator() + rememberViewModelStoreNavEntryDecorator(), ), onBack = { - navigator.popBackstack() + mainNavigator.popBackstack() }, entryProvider = entryProvider { - homeDestinationEntry() + homeDestinationEntry(navigator = homeNavigator) calendarChooserDestinationEntry() eventDestinationEntry() monthSummaryDestinationEntry() diff --git a/app/src/main/java/com/pixelized/headache/ui/navigation/main/MainNavigator.kt b/app/src/main/java/com/pixelized/headache/ui/navigation/main/MainNavigator.kt new file mode 100644 index 0000000..34b83f1 --- /dev/null +++ b/app/src/main/java/com/pixelized/headache/ui/navigation/main/MainNavigator.kt @@ -0,0 +1,20 @@ +package com.pixelized.headache.ui.navigation.main + +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.snapshots.SnapshotStateList +import dagger.hilt.android.scopes.ActivityRetainedScoped + +interface MainDestination + +@ActivityRetainedScoped +class MainNavigator( + startDestination: MainDestination, +) { + val backStack: SnapshotStateList = mutableStateListOf(startDestination) + + fun popBackstack() = backStack.removeLastOrNull() + + fun goTo(destination: MainDestination) { + backStack.add(destination) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/headache/ui/page/MainPage.kt b/app/src/main/java/com/pixelized/headache/ui/page/MainPage.kt index 56ea573..b6cddef 100644 --- a/app/src/main/java/com/pixelized/headache/ui/page/MainPage.kt +++ b/app/src/main/java/com/pixelized/headache/ui/page/MainPage.kt @@ -12,8 +12,9 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.remember import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.Modifier -import com.pixelized.headache.ui.navigation.MainNavDisplay -import com.pixelized.headache.ui.navigation.Navigator +import com.pixelized.headache.ui.navigation.home.HomeNavigator +import com.pixelized.headache.ui.navigation.main.MainNavDisplay +import com.pixelized.headache.ui.navigation.main.MainNavigator val LocalSnack = staticCompositionLocalOf { error("Local SnackHost no yet ready") @@ -25,7 +26,8 @@ val LocalErrorSnack = staticCompositionLocalOf { @Composable fun MainPage( - navigator: Navigator, + mainNavigator: MainNavigator, + homeNavigator: HomeNavigator, snack: SnackbarHostState = remember { SnackbarHostState() }, errorSnack: SnackbarHostState = remember { SnackbarHostState() }, ) { @@ -54,7 +56,8 @@ fun MainPage( content = { padding -> MainNavDisplay( modifier = Modifier.padding(paddingValues = padding), - navigator = navigator, + mainNavigator = mainNavigator, + homeNavigator = homeNavigator, ) } ) diff --git a/app/src/main/java/com/pixelized/headache/ui/page/calendar/CalendarChooserPage.kt b/app/src/main/java/com/pixelized/headache/ui/page/calendar/CalendarChooserPage.kt index 5540219..9d158f0 100644 --- a/app/src/main/java/com/pixelized/headache/ui/page/calendar/CalendarChooserPage.kt +++ b/app/src/main/java/com/pixelized/headache/ui/page/calendar/CalendarChooserPage.kt @@ -22,7 +22,7 @@ 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.main.LocalMainNavigator import kotlinx.coroutines.launch @@ -30,7 +30,7 @@ import kotlinx.coroutines.launch fun CalendarChooserPage( viewModel: CalendarViewModel = hiltViewModel(), ) { - val navigation = LocalNavigator.current + val navigation = LocalMainNavigator.current val scope = rememberCoroutineScope() val calendars = viewModel.calendars.collectAsStateWithLifecycle() diff --git a/app/src/main/java/com/pixelized/headache/ui/page/event/list/EventPage.kt b/app/src/main/java/com/pixelized/headache/ui/page/event/list/EventPage.kt index c7406f7..30d4f90 100644 --- a/app/src/main/java/com/pixelized/headache/ui/page/event/list/EventPage.kt +++ b/app/src/main/java/com/pixelized/headache/ui/page/event/list/EventPage.kt @@ -41,7 +41,7 @@ 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.main.LocalMainNavigator import com.pixelized.headache.ui.page.event.edit.EventEditBottomSheet import com.pixelized.headache.ui.page.event.edit.EventEditBottomSheetViewModel import com.pixelized.headache.ui.theme.HeadacheTheme @@ -63,7 +63,7 @@ fun EventPage( viewModel: EventViewModel = hiltViewModel(), editViewModel: EventEditBottomSheetViewModel = hiltViewModel(), ) { - val navigation = LocalNavigator.current + val navigation = LocalMainNavigator.current val events = viewModel.events.collectAsStateWithLifecycle() val misspelledFilter = viewModel.misspelledFilter.collectAsStateWithLifecycle() val invalidFilter = viewModel.invalidFilter.collectAsStateWithLifecycle() diff --git a/app/src/main/java/com/pixelized/headache/ui/page/event/list/EventViewModel.kt b/app/src/main/java/com/pixelized/headache/ui/page/event/list/EventViewModel.kt index 9e62d33..d4db750 100644 --- a/app/src/main/java/com/pixelized/headache/ui/page/event/list/EventViewModel.kt +++ b/app/src/main/java/com/pixelized/headache/ui/page/event/list/EventViewModel.kt @@ -2,7 +2,6 @@ package com.pixelized.headache.ui.page.event.list import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.pixelized.headache.repository.calendar.GoogleCalendarIdRepository import com.pixelized.headache.repository.event.Event import com.pixelized.headache.repository.event.EventRepository import com.pixelized.headache.ui.navigation.destination.EventDestination 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 c4908d0..dbf6aa7 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 @@ -1,54 +1,119 @@ package com.pixelized.headache.ui.page.home +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.togetherWith import androidx.compose.foundation.clickable 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.fillMaxWidth import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight +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.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.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.LocalNavigator -import com.pixelized.headache.ui.navigation.destination.navigateToCalendarChooserPage -import com.pixelized.headache.ui.navigation.destination.navigateToEventPage import com.pixelized.headache.ui.navigation.destination.navigateToMonthSummary import com.pixelized.headache.ui.navigation.destination.navigateToYearSummary +import com.pixelized.headache.ui.navigation.home.HomeNavDisplay +import com.pixelized.headache.ui.navigation.home.HomeNavigator +import com.pixelized.headache.ui.page.event.edit.EventEditBottomSheet +import com.pixelized.headache.ui.page.event.edit.EventEditBottomSheetViewModel + + +@Stable +data class BottomBarItemUio( + val selectedIcon: Int, + val unselectedIcon: Int, + val onClick: () -> Unit, +) @OptIn(ExperimentalMaterial3Api::class) @Composable -fun HomePage() { - val navigation = LocalNavigator.current +fun HomePage( + navigator: HomeNavigator, + editViewModel: EventEditBottomSheetViewModel = hiltViewModel(), +) { + val selectedItem = remember { mutableIntStateOf(0) } + val items = rememberBottomBarItems( + onYearlyFollowUp = { + selectedItem.intValue = 0 + navigator.navigateToYearSummary() + }, + onMonthlyStatFollowUp = { + selectedItem.intValue = 1 + navigator.navigateToMonthSummary() + }, + onMonthlyListFollowUp = { + selectedItem.intValue = 2 + }, + ) HomePageContent( modifier = Modifier.fillMaxSize(), - onCalendarChooser = { - navigation.navigateToCalendarChooserPage() + navigator = navigator, + items = items, + selectedItem = selectedItem, + onFabClick = { + editViewModel.show() }, - onEvent = { - navigation.navigateToEventPage() - }, - onMonthSummary = { - navigation.navigateToMonthSummary() - }, - onYearSummary = { - navigation.navigateToYearSummary() - } + ) + + EventEditBottomSheet( + viewModel = editViewModel, + onDismissRequest = { editViewModel.dismiss() }, + ) +} + +@Composable +private fun rememberBottomBarItems( + onYearlyFollowUp: () -> Unit, + onMonthlyStatFollowUp: () -> Unit, + onMonthlyListFollowUp: () -> Unit, +): List = remember { + listOf( + BottomBarItemUio( + selectedIcon = R.drawable.ic_calendar_month_filled_24dp, + unselectedIcon = R.drawable.ic_calendar_month_24dp, + onClick = onYearlyFollowUp, + ), + BottomBarItemUio( + selectedIcon = R.drawable.ic_analytics_filled_24dp, + unselectedIcon = R.drawable.ic_analytics_24dp, + onClick = onMonthlyStatFollowUp, + ), + BottomBarItemUio( + selectedIcon = R.drawable.ic_list_alt_filled_24dp, + unselectedIcon = R.drawable.ic_list_alt_24dp, + onClick = onMonthlyListFollowUp, + ) ) } @@ -56,49 +121,57 @@ fun HomePage() { @Composable private fun HomePageContent( modifier: Modifier = Modifier, - onCalendarChooser: () -> Unit, - onEvent: () -> Unit, - onMonthSummary: () -> Unit, - onYearSummary: () -> Unit, + navigator: HomeNavigator, + items: List, + selectedItem: IntState, + onFabClick: () -> Unit, ) { Scaffold( modifier = modifier, - topBar = { - TopAppBar( - title = { - Text(text = stringResource(R.string.app_name)) - }, - ) - }, - content = { paddingValues -> - LazyColumn( - modifier = Modifier.padding(paddingValues = paddingValues), - ) { - item { - NavigationItem( - label = stringResource(R.string.calendar_chooser_title), - onClick = onCalendarChooser, - ) - } - item { - NavigationItem( - label = stringResource(R.string.event_title), - onClick = onEvent, - ) - } - item { - NavigationItem( - label = stringResource(R.string.month_summary_title), - onClick = onMonthSummary, - ) - } - item { - NavigationItem( - label = stringResource(R.string.year_summary_title), - onClick = onYearSummary, + contentWindowInsets = remember { WindowInsets(0, 0, 0, 0) }, + bottomBar = { + NavigationBar { + items.forEachIndexed { index, item -> + NavigationBarItem( + icon = { + AnimatedContent( + targetState = selectedItem.value, + transitionSpec = { fadeIn() togetherWith fadeOut() }, + ) { + Icon( + painter = painterResource( + when (it) { + index -> item.selectedIcon + else -> item.unselectedIcon + } + ), + contentDescription = null, + ) + } + }, + selected = selectedItem.value == index, + onClick = item.onClick, ) } } + }, + floatingActionButton = { + FloatingActionButton( + containerColor = MaterialTheme.colorScheme.primary, + shape = CircleShape, + onClick = onFabClick, + ) { + Icon( + imageVector = Icons.Default.Add, + contentDescription = null, + ) + } + }, + content = { paddingValues -> + HomeNavDisplay( + modifier = Modifier.padding(paddingValues = paddingValues), + navigator = navigator, + ) } ) } diff --git a/app/src/main/java/com/pixelized/headache/ui/page/summary/month/MonthSummaryFactory.kt b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/MonthSummaryFactory.kt similarity index 90% rename from app/src/main/java/com/pixelized/headache/ui/page/summary/month/MonthSummaryFactory.kt rename to app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/MonthSummaryFactory.kt index eb624aa..2fd478a 100644 --- a/app/src/main/java/com/pixelized/headache/ui/page/summary/month/MonthSummaryFactory.kt +++ b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/MonthSummaryFactory.kt @@ -1,13 +1,13 @@ -package com.pixelized.headache.ui.page.summary.month +package com.pixelized.headache.ui.page.summary.monthly import android.icu.util.Calendar import androidx.compose.ui.graphics.Color import com.pixelized.headache.repository.event.Event -import com.pixelized.headache.ui.page.summary.month.item.MonthSummaryBoxUio -import com.pixelized.headache.ui.page.summary.month.item.MonthSummaryCell -import com.pixelized.headache.ui.page.summary.month.item.MonthSummaryItemUio -import com.pixelized.headache.ui.page.summary.month.item.MonthSummaryPillItemUio -import com.pixelized.headache.ui.page.summary.month.item.MonthSummaryTitleUio +import com.pixelized.headache.ui.page.summary.monthly.item.MonthSummaryBoxUio +import com.pixelized.headache.ui.page.summary.monthly.item.MonthSummaryCell +import com.pixelized.headache.ui.page.summary.monthly.item.MonthSummaryItemUio +import com.pixelized.headache.ui.page.summary.monthly.item.MonthSummaryPillItemUio +import com.pixelized.headache.ui.page.summary.monthly.item.MonthSummaryTitleUio import com.pixelized.headache.utils.extention.event import javax.inject.Inject import kotlin.math.max diff --git a/app/src/main/java/com/pixelized/headache/ui/page/summary/month/MonthSummaryPage.kt b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/MonthSummaryPage.kt similarity index 72% rename from app/src/main/java/com/pixelized/headache/ui/page/summary/month/MonthSummaryPage.kt rename to app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/MonthSummaryPage.kt index 40ab528..c2b4f9b 100644 --- a/app/src/main/java/com/pixelized/headache/ui/page/summary/month/MonthSummaryPage.kt +++ b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/MonthSummaryPage.kt @@ -1,4 +1,4 @@ -package com.pixelized.headache.ui.page.summary.month +package com.pixelized.headache.ui.page.summary.monthly import androidx.compose.animation.AnimatedContent import androidx.compose.animation.fadeIn @@ -6,26 +6,23 @@ 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.layout.systemBarsPadding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.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.automirrored.filled.Article -import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.BarChart 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.NavigationBarDefaults 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.Stable import androidx.compose.runtime.State @@ -41,18 +38,16 @@ 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.month.item.MonthSummaryBox -import com.pixelized.headache.ui.page.summary.month.item.MonthSummaryBoxUio -import com.pixelized.headache.ui.page.summary.month.item.MonthSummaryCell -import com.pixelized.headache.ui.page.summary.month.item.MonthSummaryItem -import com.pixelized.headache.ui.page.summary.month.item.MonthSummaryItemUio -import com.pixelized.headache.ui.page.summary.month.item.MonthSummaryPillItemUio -import com.pixelized.headache.ui.page.summary.month.item.MonthSummaryTitle -import com.pixelized.headache.ui.page.summary.month.item.MonthSummaryTitleUio +import com.pixelized.headache.ui.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 +import com.pixelized.headache.ui.page.summary.monthly.item.MonthSummaryTitle +import com.pixelized.headache.ui.page.summary.monthly.item.MonthSummaryTitleUio import com.pixelized.headache.ui.theme.HeadacheTheme import com.pixelized.headache.ui.theme.color.HeadacheColorPalette import java.util.Date @@ -60,7 +55,9 @@ import java.util.Date @Stable data object MonthSummaryPageDefault { @Stable - val listPadding: PaddingValues = PaddingValues(bottom = 56.dp + 16.dp * 2) + val listPadding: PaddingValues = PaddingValues( + bottom = 16.dp + 16.dp + 56.dp, + ) @Stable val spacing: Dp = 0.dp @@ -70,36 +67,23 @@ data object MonthSummaryPageDefault { @Composable fun MonthSummaryPage( viewModel: MonthSummaryViewModel = hiltViewModel(), - editViewModel: EventEditBottomSheetViewModel = hiltViewModel(), ) { - val navigation = LocalNavigator.current + val navigation = LocalMainNavigator.current val boxMode = viewModel.boxMode.collectAsStateWithLifecycle() val events = viewModel.events.collectAsStateWithLifecycle() MonthSummaryContent( modifier = Modifier .keepScreenOn() - .systemBarsPadding() .fillMaxSize(), events = events, boxMode = boxMode, - onBack = { - navigation.popBackstack() - }, onDisplay = { viewModel.toggleDisplay() }, onItem = { navigation.navigateToEventPage(date = it.date) }, - onAddEvent = { - editViewModel.show() - }, - ) - - EventEditBottomSheet( - viewModel = editViewModel, - onDismissRequest = { editViewModel.dismiss() }, ) } @@ -111,25 +95,17 @@ private fun MonthSummaryContent( listPadding: PaddingValues = MonthSummaryPageDefault.listPadding, boxMode: State, events: State>>, - onBack: () -> Unit, onDisplay: () -> Unit, onItem: (MonthSummaryCell) -> Unit, - onAddEvent: () -> Unit, ) { Scaffold( modifier = modifier, + contentWindowInsets = remember { WindowInsets(0, 0, 0, 0) }, topBar = { TopAppBar( - navigationIcon = { - IconButton( - onClick = onBack, - ) { - Icon( - imageVector = Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = null, - ) - } - }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = NavigationBarDefaults.containerColor, + ), title = { Text(text = stringResource(R.string.month_summary_title)) }, @@ -153,39 +129,28 @@ private fun MonthSummaryContent( } ) }, - floatingActionButton = { - FloatingActionButton( - containerColor = MaterialTheme.colorScheme.primary, - shape = CircleShape, - onClick = onAddEvent, - ) { - Icon( - imageVector = Icons.Default.Add, - contentDescription = null, - ) - } - }, content = { paddingValues -> LazyColumn( modifier = Modifier - .padding(paddingValues = paddingValues) - .fillMaxSize(), + .fillMaxSize() + .padding(paddingValues = paddingValues), contentPadding = listPadding, verticalArrangement = Arrangement.spacedBy(space = spacing), reverseLayout = false, ) { events.value.forEach { entry -> - stickyHeader { + item { MonthSummaryCell( + modifier = Modifier.padding(top = 16.dp), item = entry.key, onItem = onItem, ) } items( items = entry.value, - key = { it.date }, - contentType = { - when (it) { + key = { item -> item.date }, + contentType = { item -> + when (item) { is MonthSummaryBoxUio -> "MonthSummaryBoxUio" is MonthSummaryItemUio -> "MonthSummaryItemUio" is MonthSummaryTitleUio -> "MonthSummaryTitleUio" @@ -205,11 +170,14 @@ private fun MonthSummaryContent( @Composable private fun MonthSummaryCell( + modifier: Modifier = Modifier, item: MonthSummaryCell, onItem: (MonthSummaryCell) -> Unit, ) { AnimatedContent( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .fillMaxWidth() + .then(other = modifier), targetState = item, transitionSpec = { fadeIn() togetherWith fadeOut() }, ) { item -> @@ -272,10 +240,8 @@ private fun MonthSummaryPreview() { ) ) }, - onBack = { }, onDisplay = { }, onItem = { }, - onAddEvent = { }, ) } } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/headache/ui/page/summary/month/MonthSummaryViewModel.kt b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/MonthSummaryViewModel.kt similarity index 91% rename from app/src/main/java/com/pixelized/headache/ui/page/summary/month/MonthSummaryViewModel.kt rename to app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/MonthSummaryViewModel.kt index 86f038a..06c3d71 100644 --- a/app/src/main/java/com/pixelized/headache/ui/page/summary/month/MonthSummaryViewModel.kt +++ b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/MonthSummaryViewModel.kt @@ -1,10 +1,10 @@ -package com.pixelized.headache.ui.page.summary.month +package com.pixelized.headache.ui.page.summary.monthly import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.pixelized.headache.repository.event.Event import com.pixelized.headache.repository.event.EventRepository -import com.pixelized.headache.ui.page.summary.month.item.MonthSummaryCell +import com.pixelized.headache.ui.page.summary.monthly.item.MonthSummaryCell import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow diff --git a/app/src/main/java/com/pixelized/headache/ui/page/summary/month/item/MonthSummaryBox.kt b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/item/MonthSummaryBox.kt similarity index 99% rename from app/src/main/java/com/pixelized/headache/ui/page/summary/month/item/MonthSummaryBox.kt rename to app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/item/MonthSummaryBox.kt index dc4d060..99f51ba 100644 --- a/app/src/main/java/com/pixelized/headache/ui/page/summary/month/item/MonthSummaryBox.kt +++ b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/item/MonthSummaryBox.kt @@ -1,4 +1,4 @@ -package com.pixelized.headache.ui.page.summary.month.item +package com.pixelized.headache.ui.page.summary.monthly.item import android.annotation.SuppressLint import android.icu.text.SimpleDateFormat diff --git a/app/src/main/java/com/pixelized/headache/ui/page/summary/month/item/MonthSummaryCell.kt b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/item/MonthSummaryCell.kt similarity index 67% rename from app/src/main/java/com/pixelized/headache/ui/page/summary/month/item/MonthSummaryCell.kt rename to app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/item/MonthSummaryCell.kt index 863d793..9d06e78 100644 --- a/app/src/main/java/com/pixelized/headache/ui/page/summary/month/item/MonthSummaryCell.kt +++ b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/item/MonthSummaryCell.kt @@ -1,4 +1,4 @@ -package com.pixelized.headache.ui.page.summary.month.item +package com.pixelized.headache.ui.page.summary.monthly.item import androidx.compose.runtime.Stable import java.util.Date diff --git a/app/src/main/java/com/pixelized/headache/ui/page/summary/month/item/MonthSummaryItem.kt b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/item/MonthSummaryItem.kt similarity index 98% rename from app/src/main/java/com/pixelized/headache/ui/page/summary/month/item/MonthSummaryItem.kt rename to app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/item/MonthSummaryItem.kt index 9adf1f0..4101862 100644 --- a/app/src/main/java/com/pixelized/headache/ui/page/summary/month/item/MonthSummaryItem.kt +++ b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/item/MonthSummaryItem.kt @@ -1,4 +1,4 @@ -package com.pixelized.headache.ui.page.summary.month.item +package com.pixelized.headache.ui.page.summary.monthly.item import android.annotation.SuppressLint import android.icu.text.SimpleDateFormat diff --git a/app/src/main/java/com/pixelized/headache/ui/page/summary/month/item/MonthSummaryPillItem.kt b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/item/MonthSummaryPillItem.kt similarity index 97% rename from app/src/main/java/com/pixelized/headache/ui/page/summary/month/item/MonthSummaryPillItem.kt rename to app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/item/MonthSummaryPillItem.kt index f0c799e..1d55727 100644 --- a/app/src/main/java/com/pixelized/headache/ui/page/summary/month/item/MonthSummaryPillItem.kt +++ b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/item/MonthSummaryPillItem.kt @@ -1,4 +1,4 @@ -package com.pixelized.headache.ui.page.summary.month.item +package com.pixelized.headache.ui.page.summary.monthly.item import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement diff --git a/app/src/main/java/com/pixelized/headache/ui/page/summary/month/item/MonthSummaryTitle.kt b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/item/MonthSummaryTitle.kt similarity index 53% rename from app/src/main/java/com/pixelized/headache/ui/page/summary/month/item/MonthSummaryTitle.kt rename to app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/item/MonthSummaryTitle.kt index 65cc3ec..f898bd3 100644 --- a/app/src/main/java/com/pixelized/headache/ui/page/summary/month/item/MonthSummaryTitle.kt +++ b/app/src/main/java/com/pixelized/headache/ui/page/summary/monthly/item/MonthSummaryTitle.kt @@ -1,18 +1,14 @@ -package com.pixelized.headache.ui.page.summary.month.item +package com.pixelized.headache.ui.page.summary.monthly.item import android.annotation.SuppressLint import android.icu.text.SimpleDateFormat -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable -import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Brush import androidx.compose.ui.unit.dp import java.util.Date import java.util.Locale @@ -26,30 +22,13 @@ data class MonthSummaryTitleUio( object MonthSummaryTitleDefault { @Stable - val padding: PaddingValues = PaddingValues(horizontal = 16.dp, vertical = 8.dp) + val padding: PaddingValues = PaddingValues(horizontal = 16.dp) @SuppressLint("ConstantLocale") @Stable val formatter = SimpleDateFormat("yyyy", Locale.getDefault()) } -val backgroundBrush: Brush - @Composable - @Stable - get() { - val colorScheme = MaterialTheme.colorScheme - return remember(colorScheme) { - Brush.verticalGradient( - listOf( - colorScheme.surface, - colorScheme.surface, - colorScheme.surface, - colorScheme.surface.copy(alpha = 0f), - ) - ) - } - } - @Composable fun MonthSummaryTitle( modifier: Modifier = Modifier, @@ -57,15 +36,11 @@ fun MonthSummaryTitle( formatter: SimpleDateFormat = MonthSummaryTitleDefault.formatter, item: MonthSummaryTitleUio, ) { - Box( + Text( modifier = Modifier - .background(brush = backgroundBrush) + .padding(paddingValues = padding) .then(other = modifier), - ) { - Text( - modifier = Modifier.padding(paddingValues = padding), - style = MaterialTheme.typography.titleLarge, - text = formatter.format(item.date) - ) - } + style = MaterialTheme.typography.displaySmall, + text = formatter.format(item.date) + ) } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/headache/ui/page/summary/year/YearSummaryPage.kt b/app/src/main/java/com/pixelized/headache/ui/page/summary/year/YearSummaryPage.kt deleted file mode 100644 index 026bcfe..0000000 --- a/app/src/main/java/com/pixelized/headache/ui/page/summary/year/YearSummaryPage.kt +++ /dev/null @@ -1,483 +0,0 @@ -package com.pixelized.headache.ui.page.summary.year - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.systemBarsPadding -import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.GridItemSpan -import androidx.compose.foundation.lazy.grid.LazyVerticalGrid -import androidx.compose.foundation.lazy.grid.items -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material.icons.filled.Add -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.FloatingActionButton -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Stable -import androidx.compose.runtime.State -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.keepScreenOn -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.platform.LocalWindowInfo -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.pixelized.headache.R -import com.pixelized.headache.ui.navigation.LocalNavigator -import com.pixelized.headache.ui.navigation.destination.navigateToEventPage -import com.pixelized.headache.ui.page.event.edit.EventEditBottomSheet -import com.pixelized.headache.ui.page.event.edit.EventEditBottomSheetViewModel -import com.pixelized.headache.ui.page.summary.year.item.YearSummaryDayUio -import com.pixelized.headache.ui.page.summary.year.item.YearSummaryMonth -import com.pixelized.headache.ui.page.summary.year.item.YearSummaryMonthUio -import com.pixelized.headache.ui.theme.HeadacheTheme -import com.pixelized.headache.ui.theme.color.HeadacheColorPalette -import com.pixelized.headache.utils.extention.calculate -import java.util.Calendar - -@Stable -data class YearUio( - val year: Int, - val months: List, -) - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun YearSummaryPage( - viewModel: YearSummaryViewModel = hiltViewModel(), - editViewModel: EventEditBottomSheetViewModel = hiltViewModel(), -) { - val navigation = LocalNavigator.current - val uio = viewModel.events.collectAsStateWithLifecycle() - - YearSummaryContent( - modifier = Modifier - .systemBarsPadding() - .keepScreenOn() - .fillMaxSize(), - uio = uio, - onBack = { - navigation.popBackstack() - }, - onMonth = { - navigation.navigateToEventPage(date = it.date) - }, - onAddEvent = { - editViewModel.show() - }, - ) - - EventEditBottomSheet( - viewModel = editViewModel, - onDismissRequest = { editViewModel.dismiss() }, - ) -} - -@Composable -private fun rememberDaySize( - column: Int = 3, - space: Dp, - paddingValues: PaddingValues, -): Dp { - val density = LocalDensity.current - val windowInfo = LocalWindowInfo.current - val screenWidth = remember(density, windowInfo) { - with(density) { - windowInfo.containerSize.width.toDp() - } - } - val (start, _, end, _) = paddingValues.calculate() - return (screenWidth - space * (column - 1) - start - end) / (7 * column) -} - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun YearSummaryContent( - modifier: Modifier = Modifier, - paddingValues: PaddingValues = PaddingValues( - start = 16.dp, - top = 16.dp, - end = 16.dp, - bottom = 16.dp + 16.dp + 56.dp, - ), - space: Dp = 8.dp, - daySize: Dp = rememberDaySize( - column = 3, - paddingValues = paddingValues, - space = space, - ), - uio: State>, - onBack: () -> Unit, - onMonth: (YearSummaryMonthUio) -> Unit, - onAddEvent: () -> Unit, -) { - Scaffold( - modifier = modifier, - topBar = { - TopAppBar( - navigationIcon = { - IconButton( - onClick = onBack, - ) { - Icon( - imageVector = Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = null, - ) - } - }, - title = { - Text(text = stringResource(R.string.year_summary_title)) - }, - ) - }, - floatingActionButton = { - FloatingActionButton( - containerColor = MaterialTheme.colorScheme.primary, - shape = CircleShape, - onClick = onAddEvent, - ) { - Icon( - imageVector = Icons.Default.Add, - contentDescription = null, - ) - } - }, - content = { it -> - LazyVerticalGrid( - modifier = Modifier.padding(paddingValues = it), - columns = GridCells.Adaptive(minSize = daySize * 7), - horizontalArrangement = Arrangement.spacedBy(space = space), - contentPadding = paddingValues, - ) { - uio.value.forEachIndexed { index, (year, months) -> - item( - span = { GridItemSpan(maxLineSpan) }, - contentType = { "Title" }, - ) { - Text( - modifier = Modifier.padding( - top = when (index) { - 0 -> 0.dp - else -> 16.dp - } - ), - style = MaterialTheme.typography.displaySmall, - text = "$year", - ) - } - items( - items = months, - key = { it.date.time }, - contentType = { "Month" }, - ) { - YearSummaryMonth( - uio = it, - daySize = daySize, - onMonth = onMonth, - ) - } - } - } - } - ) -} - -@Composable -@Preview() -private fun YearSummaryPreview( - @PreviewParameter(YearPreviewProvider::class) preview: List, -) { - HeadacheTheme { - Surface { - YearSummaryContent( - uio = remember { mutableStateOf(preview) }, - onBack = { }, - onMonth = { }, - onAddEvent = { }, - ) - } - } -} - -private class YearPreviewProvider() : PreviewParameterProvider> { - - private var day = 1 - - private fun day(): () -> YearSummaryDayUio = { - YearSummaryDayUio( - number = day++, - headache = false, - pills = emptyList(), - ) - } - - private fun headache( - spifen: Boolean = false, - eletriptan: Boolean = false, - ): () -> YearSummaryDayUio = { - YearSummaryDayUio( - number = day++, - headache = true, - pills = listOfNotNull( - if (spifen) HeadacheColorPalette.Pill.Spifen400 else null, - if (eletriptan) HeadacheColorPalette.Pill.Eletriptan40 else null, - ), - ) - } - - // inline to avoid un sequential call to "day()" between default and overridden function parameters. - @Suppress("NOTHING_TO_INLINE") - private fun week( - mon: (() -> YearSummaryDayUio)? = day(), - tue: (() -> YearSummaryDayUio)? = day(), - wen: (() -> YearSummaryDayUio)? = day(), - thu: (() -> YearSummaryDayUio)? = day(), - fry: (() -> YearSummaryDayUio)? = day(), - sat: (() -> YearSummaryDayUio)? = day(), - sun: (() -> YearSummaryDayUio)? = day(), - ): List = listOfNotNull( - mon?.invoke(), - tue?.invoke(), - wen?.invoke(), - thu?.invoke(), - fry?.invoke(), - sat?.invoke(), - sun?.invoke(), - ) - - private fun month( - month: Int, - year: Int, - vararg weeks: List, - ): YearSummaryMonthUio { - day = 1 - return YearSummaryMonthUio( - date = Calendar.getInstance().apply { - set(Calendar.YEAR, year) - set(Calendar.MONTH, month) - }.time, - weeks = weeks.toList(), - ) - } - - private fun year( - year: Int, - vararg months: YearSummaryMonthUio, - ) = YearUio( - year = year, - months = months.toList(), - ) - - override val values: Sequence> = sequenceOf( - listOf( - year( - year = 2025, - month( - month = Calendar.JANUARY, - year = 2025, - week(mon = null, tue = null), - week(), - week(), - week(), - week(sat = null, sun = null), - ), - month( - month = Calendar.FEBRUARY, - year = 2025, - week(mon = null, tue = null, wen = null, thu = null, fry = null), - week(), - week(), - week(), - week(sat = null, sun = null), - ), - month( - month = Calendar.MARCH, - year = 2025, - week(mon = null, tue = null, wen = null, thu = null, fry = null), - week(), - week(), - week(), - week(), - week(tue = null, wen = null, thu = null, fry = null, sat = null, sun = null), - ), - month( - month = Calendar.APRIL, - year = 2025, - week(mon = null), - week(), - week(), - week(), - week(thu = null, fry = null, sat = null, sun = null), - ), - month( - month = Calendar.MAY, - year = 2025, - week(mon = null, tue = null, wen = null), - week(), - week(), - week(), - week(sun = null), - ), - month( - month = Calendar.JUNE, - year = 2025, - week(mon = null, tue = null, wen = null, thu = null, fry = null, sat = null), - week(), - week(), - week(), - week(), - week(tue = null, wen = null, thu = null, fry = null, sat = null, sun = null), - ), - month( - month = Calendar.JULY, - year = 2025, - week(mon = null, sun = headache(spifen = true)), - week(wen = headache(spifen = true)), - week(sat = headache()), - week( - wen = headache(spifen = true), - thu = headache(), - sat = headache(spifen = true), - ), - week(wen = headache(), fry = null, sat = null, sun = null), - ), - month( - month = Calendar.AUGUST, - year = 2025, - week(mon = null, tue = null, wen = null, thu = null), - week(wen = headache(), fry = headache()), - week(fry = headache(spifen = true)), - week(tue = headache(spifen = true), fry = headache()), - week( - tue = headache(spifen = true, eletriptan = true), - wen = headache(), - sat = headache(spifen = true), - ), - ), - month( - month = Calendar.SEPTEMBER, - year = 2025, - week( - tue = headache(spifen = true, eletriptan = true), - wen = headache(spifen = true), - fry = headache(), - sat = headache(), - sun = headache(), - ), - week( - sun = headache(spifen = true), - ), - week( - wen = headache(spifen = true), - tue = headache(), - ), - week(), - week(wen = null, thu = null, fry = null, sat = null, sun = null), - ), - month( - month = Calendar.OCTOBER, - year = 2025, - week(mon = null, tue = null), - week(), - week(), - week(), - week(sat = null, sun = null), - ), - month( - month = Calendar.NOVEMBER, - year = 2025, - week(mon = null, tue = null, wen = null, thu = null, fry = null), - week(), - week(), - week(), - week(), - ), - month( - month = Calendar.DECEMBER, - year = 2025, - week(), - week(), - week(), - week(), - week(thu = null, fry = null, sat = null, sun = null), - ), - ), - year( - year = 2024, - month( - month = Calendar.JANUARY, - year = 2024, - week(mon = null, tue = null), - week(), - week(), - week(), - week(sat = null, sun = null), - ), - month( - month = Calendar.FEBRUARY, - year = 2024, - week(mon = null, tue = null, wen = null, thu = null, fry = null), - week(), - week(), - week(), - week(sat = null, sun = null), - ), - month( - month = Calendar.MARCH, - year = 2024, - week(mon = null, tue = null, wen = null, thu = null, fry = null), - week(), - week(), - week(), - week(), - week(tue = null, wen = null, thu = null, fry = null, sat = null, sun = null), - ), - month( - month = Calendar.APRIL, - year = 2024, - week(mon = null), - week(), - week(), - week(), - week(thu = null, fry = null, sat = null, sun = null), - ), - month( - month = Calendar.MAY, - year = 2024, - week(mon = null, tue = null, wen = null), - week(), - week(), - week(), - week(sun = null), - ), - month( - month = Calendar.JUNE, - year = 2024, - week(mon = null, tue = null, wen = null, thu = null, fry = null, sat = null), - week(), - week(), - week(), - week(), - week(tue = null, wen = null, thu = null, fry = null, sat = null, sun = null), - ), - ) - ), - ) -} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/headache/ui/page/summary/year/item/YearSummaryDay.kt b/app/src/main/java/com/pixelized/headache/ui/page/summary/year/item/YearSummaryDay.kt deleted file mode 100644 index c3040b0..0000000 --- a/app/src/main/java/com/pixelized/headache/ui/page/summary/year/item/YearSummaryDay.kt +++ /dev/null @@ -1,136 +0,0 @@ -package com.pixelized.headache.ui.page.summary.year.item - -import androidx.compose.foundation.background -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Stable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import com.pixelized.headache.ui.theme.HeadacheTheme -import com.pixelized.headache.ui.theme.color.HeadacheColorPalette - -@Stable -object YearSummaryDayDefault { - @Stable - val size: Dp = 16.dp - - @Stable - val pill: Dp = 2.dp -} - -@Stable -data class YearSummaryDayUio( - val number: Int, - val headache: Boolean, - val pills: List, -) - -@Composable -fun YearSummaryDay( - modifier: Modifier = Modifier, - size: Dp = YearSummaryDayDefault.size, - pill: Dp = YearSummaryDayDefault.pill, - day: YearSummaryDayUio, -) { - Box( - modifier = Modifier - .size(size = size) - .then(other = modifier), - ) { - if (day.headache) { - Box( - modifier = Modifier - .matchParentSize() - .padding(vertical = pill + 1.dp) - .background(color = HeadacheColorPalette.Pill.Unknown) - ) - } - - Text( - modifier = Modifier.align(alignment = Alignment.Center), - style = MaterialTheme.typography.labelSmall, - color = when { - !isSystemInDarkTheme() && day.headache -> Color.White - else -> MaterialTheme.colorScheme.onSurface - }, - text = "${day.number}", - ) - - if (day.headache) { - Row( - modifier = Modifier.align(alignment = Alignment.BottomCenter), - horizontalArrangement = Arrangement.spacedBy(space = 1.dp), - ) { - day.pills.forEach { - Box( - modifier = Modifier - .background(color = it) - .size(size = pill) - ) - } - } - } - } -} - -@Composable -@Preview -private fun YearSummaryDayPreview( - @PreviewParameter(DayPreviewProvider::class) preview: YearSummaryDayUio, -) { - HeadacheTheme { - Surface { - YearSummaryDay( - modifier = Modifier.size(size = 16.dp), - day = preview, - ) - } - } -} - -private class DayPreviewProvider() : PreviewParameterProvider { - override val values: Sequence - 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, - ), - ), - ) -} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/headache/ui/page/summary/year/item/YearSummaryMonth.kt b/app/src/main/java/com/pixelized/headache/ui/page/summary/year/item/YearSummaryMonth.kt deleted file mode 100644 index 1946042..0000000 --- a/app/src/main/java/com/pixelized/headache/ui/page/summary/year/item/YearSummaryMonth.kt +++ /dev/null @@ -1,243 +0,0 @@ -package com.pixelized.headache.ui.page.summary.year.item - -import android.annotation.SuppressLint -import android.icu.text.DateFormat -import android.icu.text.SimpleDateFormat -import android.icu.util.Calendar -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.offset -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.material3.ripple -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Stable -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import com.pixelized.headache.ui.theme.HeadacheTheme -import com.pixelized.headache.ui.theme.color.HeadacheColorPalette -import com.pixelized.headache.utils.extention.capitalize -import java.util.Date -import java.util.Locale - -@Stable -data object YearSummaryMonthDefault { - @SuppressLint("ConstantLocale") - @Stable - val formatter = SimpleDateFormat("MMMM", Locale.getDefault()) -} - -@Stable -data class YearSummaryMonthUio( - val date: Date, - val weeks: List>, -) - -@Composable -fun YearSummaryMonth( - modifier: Modifier = Modifier, - formatter: DateFormat = YearSummaryMonthDefault.formatter, - daySize: Dp, - uio: YearSummaryMonthUio, - onMonth: (YearSummaryMonthUio) -> Unit, -) { - Column( - modifier = Modifier - .clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = ripple(bounded = false), - onClick = { onMonth(uio) } - ) - .then(other = modifier), - ) { - Text( - modifier = Modifier.align(alignment = Alignment.Start), - style = MaterialTheme.typography.titleMedium, - text = remember(uio.date) { formatter.format(uio.date) }.capitalize(), - ) - Box( - modifier = Modifier.size(width = daySize * 7, height = daySize * 6), - ) { - uio.weeks.forEachIndexed { row, week -> - week.forEachIndexed { column, day -> - YearSummaryDay( - Modifier.offset( - x = when (row) { - 0 -> daySize * (column + 7 - week.size) - else -> daySize * column - }, - y = daySize * row, - ), - size = daySize, - day = day, - ) - } - } - } - } -} - -@Composable -@Preview -private fun YearSummaryMonthPreview( - @PreviewParameter(MonthPreviewProvider::class) preview: YearSummaryMonthUio, -) { - HeadacheTheme { - Surface { - YearSummaryMonth( - modifier = Modifier.padding(all = 8.dp), - daySize = YearSummaryDayDefault.size, - uio = preview, - onMonth = { }, - ) - } - } -} - -private class MonthPreviewProvider() : PreviewParameterProvider { - - private var day: Int = 1 - - private fun day(): () -> YearSummaryDayUio = { - YearSummaryDayUio( - number = day++, - headache = false, - pills = emptyList(), - ) - } - - private fun headache( - spifen: Boolean = false, - eletriptan: Boolean = false, - ): () -> YearSummaryDayUio = { - YearSummaryDayUio( - number = day++, - headache = true, - pills = listOfNotNull( - if (spifen) HeadacheColorPalette.Pill.Spifen400 else null, - if (eletriptan) HeadacheColorPalette.Pill.Eletriptan40 else null, - ), - ) - } - - // inline to avoid un sequential call to "day()" between default and overridden function parameters. - @Suppress("NOTHING_TO_INLINE") - private fun week( - mon: (() -> YearSummaryDayUio)? = day(), - tue: (() -> YearSummaryDayUio)? = day(), - wen: (() -> YearSummaryDayUio)? = day(), - thu: (() -> YearSummaryDayUio)? = day(), - fry: (() -> YearSummaryDayUio)? = day(), - sat: (() -> YearSummaryDayUio)? = day(), - sun: (() -> YearSummaryDayUio)? = day(), - ): List = listOfNotNull( - mon?.invoke(), - tue?.invoke(), - wen?.invoke(), - thu?.invoke(), - fry?.invoke(), - sat?.invoke(), - sun?.invoke(), - ) - - private fun month( - month: Int, - vararg weeks: List, - ): YearSummaryMonthUio { - day = 1 - return YearSummaryMonthUio( - date = java.util.Calendar.getInstance().apply { - set(java.util.Calendar.YEAR, 2025) - set(java.util.Calendar.MONTH, month) - }.time, - weeks = weeks.toList(), - ) - } - - override val values: Sequence - get() = sequenceOf( - month( - month = Calendar.MARCH, - week( - mon = null, - tue = null, - wen = null, - thu = null, - fry = null, - sat = headache(spifen = true) - ), - week(sat = headache(spifen = true)), - week( - wen = headache(spifen = true, eletriptan = true), - thu = headache(spifen = true, eletriptan = true), - fry = headache(spifen = true, eletriptan = true), - sat = headache(), - sun = headache(spifen = true, eletriptan = true) - ), - week( - mon = headache(), - tue = headache(), - sat = headache(spifen = true, eletriptan = true), - ), - week(thu = headache(), fry = headache(), sat = headache()), - week( - mon = headache(spifen = true, eletriptan = true), - tue = null, wen = null, thu = null, fry = null, sat = null, sun = null - ), - ), - month( - month = java.util.Calendar.JULY, - week(mon = null, sun = headache(spifen = true)), - week(wen = headache(spifen = true)), - week(sat = headache()), - week( - wen = headache(spifen = true), - thu = headache(), - sat = headache(spifen = true), - ), - week(wen = headache(), fry = null, sat = null, sun = null), - ), - month( - month = java.util.Calendar.AUGUST, - week(mon = null, tue = null, wen = null, thu = null), - week(wen = headache(), fry = headache()), - week(fry = headache(spifen = true)), - week(tue = headache(spifen = true), fry = headache()), - week( - tue = headache(spifen = true, eletriptan = true), - wen = headache(), - sat = headache(spifen = true), - ), - ), - month( - month = java.util.Calendar.SEPTEMBER, - week( - tue = headache(spifen = true, eletriptan = true), - wen = headache(spifen = true), - fry = headache(), - sat = headache(), - sun = headache(), - ), - week( - sun = headache(spifen = true), - ), - week( - wen = headache(spifen = true), - tue = headache(), - ), - week(), - week(wen = null, thu = null, fry = null, sat = null, sun = null), - ), - ) -} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/headache/ui/page/summary/year/YearSummaryFactory.kt b/app/src/main/java/com/pixelized/headache/ui/page/summary/yearly/YearSummaryFactory.kt similarity index 68% rename from app/src/main/java/com/pixelized/headache/ui/page/summary/year/YearSummaryFactory.kt rename to app/src/main/java/com/pixelized/headache/ui/page/summary/yearly/YearSummaryFactory.kt index 6b11433..8b49002 100644 --- a/app/src/main/java/com/pixelized/headache/ui/page/summary/year/YearSummaryFactory.kt +++ b/app/src/main/java/com/pixelized/headache/ui/page/summary/yearly/YearSummaryFactory.kt @@ -1,18 +1,15 @@ -package com.pixelized.headache.ui.page.summary.year +package com.pixelized.headache.ui.page.summary.yearly import android.icu.util.Calendar import com.pixelized.headache.repository.event.Event -import com.pixelized.headache.ui.page.summary.year.item.YearSummaryDayUio -import com.pixelized.headache.ui.page.summary.year.item.YearSummaryMonthUio +import com.pixelized.headache.ui.page.summary.yearly.YearSummaryMonthUio.DayUio import javax.inject.Inject - class YearSummaryFactory @Inject constructor() { fun convertToUio( - events: Collection, + events: Map>>, ): List { - val monthFirstDayCalendar = Calendar.getInstance().apply { firstDayOfWeek = Calendar.MONDAY setMinimalDaysInFirstWeek(1) @@ -27,28 +24,6 @@ class YearSummaryFactory @Inject constructor() { } return events - .fold(initial = hashMapOf>>()) { acc, event -> - acc.also { - val years = it.getOrPut(key = event.date.year) { - hashMapOf( - Calendar.JANUARY to hashMapOf(), - Calendar.FEBRUARY to hashMapOf(), - Calendar.MARCH to hashMapOf(), - Calendar.APRIL to hashMapOf(), - Calendar.MAY to hashMapOf(), - Calendar.JUNE to hashMapOf(), - Calendar.JULY to hashMapOf(), - Calendar.AUGUST to hashMapOf(), - Calendar.SEPTEMBER to hashMapOf(), - Calendar.OCTOBER to hashMapOf(), - Calendar.NOVEMBER to hashMapOf(), - Calendar.DECEMBER to hashMapOf(), - ) - } - val months = years.getOrPut(key = event.date.month) { hashMapOf() } - months[event.date.day] = event - } - } .map { yearEntry -> YearUio( year = yearEntry.key, @@ -70,7 +45,7 @@ class YearSummaryFactory @Inject constructor() { val initial = MutableList( size = monthLastDayCalendar.get(Calendar.WEEK_OF_MONTH), ) { - mutableListOf() + mutableListOf() } val weeks = (1..monthLastDayCalendar.get(Calendar.DAY_OF_MONTH)) .fold(initial = initial) { accumulator, dayNumber -> @@ -83,7 +58,7 @@ class YearSummaryFactory @Inject constructor() { set(Calendar.MILLISECONDS_IN_DAY, 1) } .get(Calendar.WEEK_OF_MONTH) - 1 - val day = YearSummaryDayUio( + val day = DayUio( number = dayNumber, headache = event != null, pills = event?.pills?.map { it.color } ?: emptyList(), 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 new file mode 100644 index 0000000..7961ea3 --- /dev/null +++ b/app/src/main/java/com/pixelized/headache/ui/page/summary/yearly/YearSummaryMonth.kt @@ -0,0 +1,346 @@ +package com.pixelized.headache.ui.page.summary.yearly + +import android.annotation.SuppressLint +import android.icu.text.DateFormat +import android.icu.text.SimpleDateFormat +import android.icu.util.Calendar +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.ripple +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithCache +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.TextMeasurer +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.drawText +import androidx.compose.ui.text.rememberTextMeasurer +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.pixelized.headache.ui.theme.HeadacheTheme +import com.pixelized.headache.ui.theme.color.HeadacheColorPalette +import com.pixelized.headache.utils.extention.capitalize +import java.util.Date +import java.util.Locale + +@Stable +data object YearSummaryMonthDefault { + + @Stable + val daySize: Dp = 16.dp + + @Stable + val pillSize: Dp = 2.dp + + @Stable + val pillSpace: Dp = 1.dp + + @SuppressLint("ConstantLocale") + @Stable + val formatter = SimpleDateFormat("MMMM", Locale.getDefault()) +} + +@Stable +data class YearSummaryMonthUio( + val date: Date, + val weeks: List>, +) { + @Stable + data class DayUio( + val number: Int, + val headache: Boolean, + val pills: List, + ) +} + +@Composable +fun YearSummaryMonth( + modifier: Modifier = Modifier, + formatter: DateFormat = YearSummaryMonthDefault.formatter, + defaultTextStyle: TextStyle, + headacheTextStyle: TextStyle, + daySize: Dp = YearSummaryMonthDefault.daySize, + pillSize: Dp = YearSummaryMonthDefault.pillSize, + pillSpace: Dp = YearSummaryMonthDefault.pillSpace, + uio: YearSummaryMonthUio, + onMonth: (YearSummaryMonthUio) -> Unit, +) { + val textMeasurer: TextMeasurer = rememberTextMeasurer() + val pill: Float = with(LocalDensity.current) { pillSize.toPx() } + val space: Float = with(LocalDensity.current) { pillSpace.toPx() } + + Column( + modifier = Modifier + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = ripple(bounded = false), + onClick = { onMonth(uio) } + ) + .then(other = modifier), + ) { + Text( + modifier = Modifier.align(alignment = Alignment.Start), + style = MaterialTheme.typography.titleMedium, + text = remember(uio.date) { formatter.format(uio.date) }.capitalize(), + ) + Box( + modifier = Modifier + .size(width = daySize * 7, height = daySize * 6) + .drawWithCache { + val cellSize = Size( + width = size.width / 7f, + height = size.height / 6f, + ) + val pillSize = Size( + width = pill, + height = pill, + ) + + onDrawBehind { + var localCellOffset: Offset = Offset.Zero + var localCellSize: Size = Size.Zero + + uio.weeks.forEachIndexed { row, week -> + week.forEachIndexed { column, day -> + // Compute current cell offset and size + localCellOffset = Offset( + x = when (row) { + 0 -> cellSize.width * (column + 7 - week.size) + else -> cellSize.width * column + }, + y = cellSize.height * row, + ) + localCellSize = Size( + width = cellSize.width, + height = cellSize.height, + ) + + // draw background + if (day.headache) { + drawRect( + topLeft = Offset( + x = localCellOffset.x - 1f, + y = localCellOffset.y + pillSize.height + space, + ), + size = Size( + width = localCellSize.width - 0f, + height = localCellSize.height - (pillSize.height + space) * 2, + ), + color = HeadacheColorPalette.Pill.Unknown, + ) + } + + // draw day number + val label = "${day.number}" + val measure = textMeasurer.measure(label, defaultTextStyle) + + drawText( + textMeasurer = textMeasurer, + text = label, + style = when { + day.headache -> headacheTextStyle + else -> defaultTextStyle + }, + topLeft = Offset( + x = localCellOffset.x + (localCellSize.width - measure.size.width) / 2, + y = localCellOffset.y + (localCellSize.height - measure.size.height) / 2, + ), + ) + + // draw pills + day.pills.forEachIndexed { index, pill -> + drawRect( + topLeft = Offset( + x = localCellOffset.x + (localCellSize.width - (pillSize.width + space) * day.pills.size) / 2f + index * (pillSize.width + space), + y = localCellOffset.y + localCellSize.height - pillSize.height, + ), + size = pillSize, + color = pill + ) + } + } + } + } + }, + ) + } +} + +@Composable +@Preview +private fun YearSummaryMonthPreview( + @PreviewParameter(MonthPreviewProvider::class) preview: YearSummaryMonthUio, +) { + HeadacheTheme { + Surface { + YearSummaryMonth( + modifier = Modifier.padding(all = 8.dp), + defaultTextStyle = MaterialTheme.typography.labelSmall, + headacheTextStyle = MaterialTheme.typography.labelSmall.copy(color = Color.White), + uio = preview, + onMonth = { }, + ) + } + } +} + +object MonthPreviewProviderHelper { + private var day: Int = 1 + + fun day(): () -> YearSummaryMonthUio.DayUio = { + YearSummaryMonthUio.DayUio( + number = day++, + headache = false, + pills = emptyList(), + ) + } + + fun headache( + spifen: Boolean = false, + eletriptan: Boolean = false, + ): () -> YearSummaryMonthUio.DayUio = { + YearSummaryMonthUio.DayUio( + number = day++, + headache = true, + pills = listOfNotNull( + if (spifen) HeadacheColorPalette.Pill.Spifen400 else null, + if (eletriptan) HeadacheColorPalette.Pill.Eletriptan40 else null, + ), + ) + } + + // inline to avoid un sequential call to "day()" between default and overridden function parameters. + @Suppress("NOTHING_TO_INLINE") + fun week( + mon: (() -> YearSummaryMonthUio.DayUio)? = day(), + tue: (() -> YearSummaryMonthUio.DayUio)? = day(), + wen: (() -> YearSummaryMonthUio.DayUio)? = day(), + thu: (() -> YearSummaryMonthUio.DayUio)? = day(), + fry: (() -> YearSummaryMonthUio.DayUio)? = day(), + sat: (() -> YearSummaryMonthUio.DayUio)? = day(), + sun: (() -> YearSummaryMonthUio.DayUio)? = day(), + ): List = listOfNotNull( + mon?.invoke(), + tue?.invoke(), + wen?.invoke(), + thu?.invoke(), + fry?.invoke(), + sat?.invoke(), + sun?.invoke(), + ) + + fun month( + month: Int, + year: Int, + vararg weeks: List, + ): YearSummaryMonthUio { + day = 1 + return YearSummaryMonthUio( + date = java.util.Calendar.getInstance().apply { + set(java.util.Calendar.YEAR, year) + set(java.util.Calendar.MONTH, month) + }.time, + weeks = weeks.toList(), + ) + } +} + +private class MonthPreviewProvider() : PreviewParameterProvider { + override val values: Sequence + get() = with(MonthPreviewProviderHelper) { + sequenceOf( + month( + month = Calendar.MARCH, + year = 2025, + week( + mon = null, + tue = null, + wen = null, + thu = null, + fry = null, + sat = headache(spifen = true) + ), + week(sat = headache(spifen = true)), + week( + wen = headache(spifen = true, eletriptan = true), + thu = headache(spifen = true, eletriptan = true), + fry = headache(spifen = true, eletriptan = true), + sat = headache(), + sun = headache(spifen = true, eletriptan = true) + ), + week( + mon = headache(), + tue = headache(), + sat = headache(spifen = true, eletriptan = true), + ), + week(thu = headache(), fry = headache(), sat = headache()), + week( + mon = headache(spifen = true, eletriptan = true), + tue = null, wen = null, thu = null, fry = null, sat = null, sun = null + ), + ), + month( + month = java.util.Calendar.JULY, + year = 2025, + week(mon = null, sun = headache(spifen = true)), + week(wen = headache(spifen = true)), + week(sat = headache()), + week( + wen = headache(spifen = true), + thu = headache(), + sat = headache(spifen = true), + ), + week(wen = headache(), fry = null, sat = null, sun = null), + ), + month( + month = java.util.Calendar.AUGUST, + year = 2025, + week(mon = null, tue = null, wen = null, thu = null), + week(wen = headache(), fry = headache()), + week(fry = headache(spifen = true)), + week(tue = headache(spifen = true), fry = headache()), + week( + tue = headache(spifen = true, eletriptan = true), + wen = headache(), + sat = headache(spifen = true), + ), + ), + month( + month = java.util.Calendar.SEPTEMBER, + year = 2025, + week( + tue = headache(spifen = true, eletriptan = true), + wen = headache(spifen = true), + fry = headache(), + sat = headache(), + sun = headache(), + ), + week( + sun = headache(spifen = true), + ), + week( + wen = headache(spifen = true), + tue = headache(), + ), + week(), + week(wen = null, thu = null, fry = null, sat = null, sun = null), + ), + ) + } +} \ No newline at end of file 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 new file mode 100644 index 0000000..790dd46 --- /dev/null +++ b/app/src/main/java/com/pixelized/headache/ui/page/summary/yearly/YearSummaryPage.kt @@ -0,0 +1,437 @@ +package com.pixelized.headache.ui.page.summary.yearly + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +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.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.Modifier +import androidx.compose.ui.keepScreenOn +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalWindowInfo +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.pixelized.headache.R +import com.pixelized.headache.ui.navigation.destination.navigateToEventPage +import com.pixelized.headache.ui.navigation.main.LocalMainNavigator +import com.pixelized.headache.ui.theme.HeadacheTheme +import com.pixelized.headache.ui.theme.headache +import com.pixelized.headache.utils.extention.calculate +import java.util.Calendar + +@Stable +data object YearSummaryPageDefault { + @Stable + val paddingValues = PaddingValues( + start = 16.dp, + end = 16.dp, + bottom = 16.dp + 16.dp + 56.dp, + ) + + @Stable + val space = 8.dp + + @Stable + val column = 3 +} + +@Stable +data class YearUio( + val year: Int, + val months: List, +) + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun YearSummaryPage( + viewModel: YearSummaryViewModel = hiltViewModel(), +) { + val navigation = LocalMainNavigator.current + val uio = viewModel.events.collectAsStateWithLifecycle() + + YearSummaryContent( + modifier = Modifier + .keepScreenOn() + .fillMaxSize(), + uio = uio, + onMonth = { + navigation.navigateToEventPage(date = it.date) + }, + ) +} + +@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( + modifier: Modifier = Modifier, + paddingValues: PaddingValues = YearSummaryPageDefault.paddingValues, + space: Dp = YearSummaryPageDefault.space, + daySize: Dp = rememberDaySize(column = YearSummaryPageDefault.column), + uio: State>, + onMonth: (YearSummaryMonthUio) -> Unit, +) { + 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)) + }, + ) + }, + content = { it -> + val typography = MaterialTheme.typography + val colorScheme = MaterialTheme.headache.colorScheme + val defaultTextStyle = remember(typography, colorScheme) { + typography.labelSmall.copy(color = colorScheme.base.onSurface) + } + val headacheTextStyle = remember(typography, colorScheme) { + defaultTextStyle.copy(color = colorScheme.calendar.onHeadache) + } + LazyVerticalGrid( + modifier = Modifier.padding(paddingValues = it), + columns = GridCells.Adaptive(minSize = daySize * 7), + horizontalArrangement = Arrangement.spacedBy(space = space), + verticalArrangement = Arrangement.spacedBy(space = space), + contentPadding = paddingValues, + ) { + uio.value.forEachIndexed { index, (year, months) -> + item( + span = { GridItemSpan(maxLineSpan) }, + contentType = { "Title" }, + ) { + Text( + modifier = Modifier.padding(top = 16.dp), + style = MaterialTheme.typography.displaySmall, + text = "$year", + ) + } + items( + items = months, + key = { it.date.time }, + contentType = { "Month" }, + ) { + YearSummaryMonth( + uio = it, + defaultTextStyle = defaultTextStyle, + headacheTextStyle = headacheTextStyle, + daySize = daySize, + onMonth = onMonth, + ) + } + } + } + } + ) +} + +@Composable +@Preview() +private fun YearSummaryPreview( + @PreviewParameter(YearPreviewProvider::class) preview: List, +) { + HeadacheTheme { + Surface { + YearSummaryContent( + uio = remember { mutableStateOf(preview) }, + onMonth = { }, + ) + } + } +} + +private class YearPreviewProvider() : PreviewParameterProvider> { + + private fun year( + year: Int, + vararg months: YearSummaryMonthUio, + ) = YearUio( + year = year, + months = months.toList(), + ) + + override val values: Sequence> + get() = with(MonthPreviewProviderHelper) { + sequenceOf( + listOf( + year( + year = 2025, + month( + month = Calendar.JANUARY, + year = 2025, + week(mon = null, tue = null), + week(), + week(), + week(), + week(sat = null, sun = null), + ), + month( + month = Calendar.FEBRUARY, + year = 2025, + week(mon = null, tue = null, wen = null, thu = null, fry = null), + week(), + week(), + week(), + week(sat = null, sun = null), + ), + month( + month = Calendar.MARCH, + year = 2025, + week(mon = null, tue = null, wen = null, thu = null, fry = null), + week(), + week(), + week(), + week(), + week( + tue = null, + wen = null, + thu = null, + fry = null, + sat = null, + sun = null + ), + ), + month( + month = Calendar.APRIL, + year = 2025, + week(mon = null), + week(), + week(), + week(), + week(thu = null, fry = null, sat = null, sun = null), + ), + month( + month = Calendar.MAY, + year = 2025, + week(mon = null, tue = null, wen = null), + week(), + week(), + week(), + week(sun = null), + ), + month( + month = Calendar.JUNE, + year = 2025, + week( + mon = null, + tue = null, + wen = null, + thu = null, + fry = null, + sat = null + ), + week(), + week(), + week(), + week(), + week( + tue = null, + wen = null, + thu = null, + fry = null, + sat = null, + sun = null + ), + ), + month( + month = Calendar.JULY, + year = 2025, + week(mon = null, sun = headache(spifen = true)), + week(wen = headache(spifen = true)), + week(sat = headache()), + week( + wen = headache(spifen = true), + thu = headache(), + sat = headache(spifen = true), + ), + week(wen = headache(), fry = null, sat = null, sun = null), + ), + month( + month = Calendar.AUGUST, + year = 2025, + week(mon = null, tue = null, wen = null, thu = null), + week(wen = headache(), fry = headache()), + week(fry = headache(spifen = true)), + week(tue = headache(spifen = true), fry = headache()), + week( + tue = headache(spifen = true, eletriptan = true), + wen = headache(), + sat = headache(spifen = true), + ), + ), + month( + month = Calendar.SEPTEMBER, + year = 2025, + week( + tue = headache(spifen = true, eletriptan = true), + wen = headache(spifen = true), + fry = headache(), + sat = headache(), + sun = headache(), + ), + week( + sun = headache(spifen = true), + ), + week( + wen = headache(spifen = true), + tue = headache(), + ), + week(), + week(wen = null, thu = null, fry = null, sat = null, sun = null), + ), + month( + month = Calendar.OCTOBER, + year = 2025, + week(mon = null, tue = null), + week(), + week(), + week(), + week(sat = null, sun = null), + ), + month( + month = Calendar.NOVEMBER, + year = 2025, + week(mon = null, tue = null, wen = null, thu = null, fry = null), + week(), + week(), + week(), + week(), + ), + month( + month = Calendar.DECEMBER, + year = 2025, + week(), + week(), + week(), + week(), + week(thu = null, fry = null, sat = null, sun = null), + ), + ), + year( + year = 2024, + month( + month = Calendar.JANUARY, + year = 2024, + week(mon = null, tue = null), + week(), + week(), + week(), + week(sat = null, sun = null), + ), + month( + month = Calendar.FEBRUARY, + year = 2024, + week(mon = null, tue = null, wen = null, thu = null, fry = null), + week(), + week(), + week(), + week(sat = null, sun = null), + ), + month( + month = Calendar.MARCH, + year = 2024, + week(mon = null, tue = null, wen = null, thu = null, fry = null), + week(), + week(), + week(), + week(), + week( + tue = null, + wen = null, + thu = null, + fry = null, + sat = null, + sun = null + ), + ), + month( + month = Calendar.APRIL, + year = 2024, + week(mon = null), + week(), + week(), + week(), + week(thu = null, fry = null, sat = null, sun = null), + ), + month( + month = Calendar.MAY, + year = 2024, + week(mon = null, tue = null, wen = null), + week(), + week(), + week(), + week(sun = null), + ), + month( + month = Calendar.JUNE, + year = 2024, + week( + mon = null, + tue = null, + wen = null, + thu = null, + fry = null, + sat = null + ), + week(), + week(), + week(), + week(), + week( + tue = null, + wen = null, + thu = null, + fry = null, + sat = null, + sun = null + ), + ), + ) + ), + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/headache/ui/page/summary/year/YearSummaryViewModel.kt b/app/src/main/java/com/pixelized/headache/ui/page/summary/yearly/YearSummaryViewModel.kt similarity index 90% rename from app/src/main/java/com/pixelized/headache/ui/page/summary/year/YearSummaryViewModel.kt rename to app/src/main/java/com/pixelized/headache/ui/page/summary/yearly/YearSummaryViewModel.kt index 56ab4a5..19bdfd4 100644 --- a/app/src/main/java/com/pixelized/headache/ui/page/summary/year/YearSummaryViewModel.kt +++ b/app/src/main/java/com/pixelized/headache/ui/page/summary/yearly/YearSummaryViewModel.kt @@ -1,4 +1,4 @@ -package com.pixelized.headache.ui.page.summary.year +package com.pixelized.headache.ui.page.summary.yearly import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope @@ -18,7 +18,7 @@ class YearSummaryViewModel @Inject constructor( ) : ViewModel() { val events: StateFlow> = eventRepository - .eventsListFlow() + .eventsMapFlow() .map(summaryFactory::convertToUio) .stateIn( scope = viewModelScope, diff --git a/app/src/main/java/com/pixelized/headache/ui/theme/Theme.kt b/app/src/main/java/com/pixelized/headache/ui/theme/Theme.kt index e22dca7..b0955df 100644 --- a/app/src/main/java/com/pixelized/headache/ui/theme/Theme.kt +++ b/app/src/main/java/com/pixelized/headache/ui/theme/Theme.kt @@ -3,6 +3,7 @@ package com.pixelized.headache.ui.theme import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.Stable import androidx.compose.runtime.compositionLocalOf @@ -10,15 +11,15 @@ import com.pixelized.headache.ui.theme.color.HeadacheColors import com.pixelized.headache.ui.theme.color.headacheDarkColorScheme import com.pixelized.headache.ui.theme.color.headacheLightColorScheme -val LocalLwaTheme = compositionLocalOf { - error("Local Snack Controller is not yet ready") +val LocalHeadacheTheme = compositionLocalOf { + error("Local Theme Controller is not yet ready") } val MaterialTheme.headache: HeadacheTheme @Stable @Composable @ReadOnlyComposable - get() = LocalLwaTheme.current + get() = LocalHeadacheTheme.current @Stable data class HeadacheTheme( @@ -30,14 +31,19 @@ fun HeadacheTheme( darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit, ) { - val colorScheme = when { - darkTheme -> headacheDarkColorScheme() - else -> headacheLightColorScheme() - } - - MaterialTheme( - colorScheme = colorScheme.base, - typography = Typography, - content = content + val theme = HeadacheTheme( + colorScheme = when { + darkTheme -> headacheDarkColorScheme() + else -> headacheLightColorScheme() + } ) + CompositionLocalProvider( + LocalHeadacheTheme provides theme, + ) { + MaterialTheme( + colorScheme = theme.colorScheme.base, + typography = Typography, + content = content + ) + } } \ No newline at end of file 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 0e9efdd..8ce3d84 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 @@ -16,25 +16,67 @@ import kotlin.math.ln @Stable data class HeadacheColors( val base: ColorScheme, -) + val calendar: Calendar, + val pill: Pill, +) { + @Stable + data class Calendar( + val headache: Color, + val onHeadache: Color, + ) + + @Stable + data class Pill( + val unknown: Color, + val ibuprofene400: Color, + val paracetamol1000: Color, + val spifen400: Color, + val eletriptan40: Color, + ) +} @Composable @Stable fun headacheDarkColorScheme( base: ColorScheme = darkColorScheme(), + calendar: HeadacheColors.Calendar = HeadacheColors.Calendar( + headache = HeadacheColorPalette.Additional.Red, + onHeadache = Color.White, + ), + pill: HeadacheColors.Pill = HeadacheColors.Pill( + unknown = HeadacheColorPalette.Pill.Unknown, + ibuprofene400 = HeadacheColorPalette.Pill.Ibuprofene400, + paracetamol1000 = HeadacheColorPalette.Pill.Paracetamol1000, + spifen400 = HeadacheColorPalette.Pill.Spifen400, + eletriptan40 = HeadacheColorPalette.Pill.Eletriptan40, + ), ) = HeadacheColors( base = base, + calendar = calendar, + pill = pill, ) @Composable @Stable fun headacheLightColorScheme( base: ColorScheme = lightColorScheme(), + calendar: HeadacheColors.Calendar = HeadacheColors.Calendar( + headache = HeadacheColorPalette.Additional.Red, + onHeadache = Color.White, + ), + pill: HeadacheColors.Pill = HeadacheColors.Pill( + unknown = HeadacheColorPalette.Pill.Unknown, + ibuprofene400 = HeadacheColorPalette.Pill.Ibuprofene400, + paracetamol1000 = HeadacheColorPalette.Pill.Paracetamol1000, + spifen400 = HeadacheColorPalette.Pill.Spifen400, + eletriptan40 = HeadacheColorPalette.Pill.Eletriptan40, + ), ) = HeadacheColors( base = base, + calendar = calendar, + pill = pill, ) - @ReadOnlyComposable @Composable fun calculateElevatedColor( diff --git a/app/src/main/res/drawable/ic_analytics_24dp.xml b/app/src/main/res/drawable/ic_analytics_24dp.xml new file mode 100644 index 0000000..fd21c24 --- /dev/null +++ b/app/src/main/res/drawable/ic_analytics_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_analytics_filled_24dp.xml b/app/src/main/res/drawable/ic_analytics_filled_24dp.xml new file mode 100644 index 0000000..2c36e68 --- /dev/null +++ b/app/src/main/res/drawable/ic_analytics_filled_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_calendar_month_24dp.xml b/app/src/main/res/drawable/ic_calendar_month_24dp.xml new file mode 100644 index 0000000..eec5fdc --- /dev/null +++ b/app/src/main/res/drawable/ic_calendar_month_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_calendar_month_filled_24dp.xml b/app/src/main/res/drawable/ic_calendar_month_filled_24dp.xml new file mode 100644 index 0000000..b305544 --- /dev/null +++ b/app/src/main/res/drawable/ic_calendar_month_filled_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_list_alt_24dp.xml b/app/src/main/res/drawable/ic_list_alt_24dp.xml new file mode 100644 index 0000000..20cb491 --- /dev/null +++ b/app/src/main/res/drawable/ic_list_alt_24dp.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_list_alt_filled_24dp.xml b/app/src/main/res/drawable/ic_list_alt_filled_24dp.xml new file mode 100644 index 0000000..c93cae2 --- /dev/null +++ b/app/src/main/res/drawable/ic_list_alt_filled_24dp.xml @@ -0,0 +1,10 @@ + + +