1
1
package com.spendesk.grapes.compose.calendar
2
2
3
+ import androidx.compose.foundation.layout.padding
3
4
import androidx.compose.material3.DatePicker
4
- import androidx.compose.material3.DatePickerDefaults
5
+ import androidx.compose.material3.DatePickerColors
5
6
import androidx.compose.material3.DisplayMode
6
7
import androidx.compose.material3.ExperimentalMaterial3Api
7
8
import androidx.compose.material3.SelectableDates
9
+ import androidx.compose.material3.Text
8
10
import androidx.compose.material3.rememberDatePickerState
9
11
import androidx.compose.runtime.Composable
10
12
import androidx.compose.runtime.LaunchedEffect
11
13
import androidx.compose.ui.Modifier
12
14
import androidx.compose.ui.tooling.preview.Preview
15
+ import androidx.compose.ui.tooling.preview.PreviewParameter
16
+ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
13
17
import com.spendesk.grapes.compose.extensions.resetDateToMidnight
14
- import com.spendesk.grapes.compose.extensions.resetDateToTomorrowMidnight
15
18
import com.spendesk.grapes.compose.theme.GrapesTheme
16
19
import java.util.Calendar
17
20
import java.util.Date
18
- import java.util.TimeZone
19
21
20
22
/* *
21
23
* @author : dany
@@ -26,116 +28,125 @@ import java.util.TimeZone
26
28
* Grapes date picker which lets the user select a date via a calendar UI.
27
29
*
28
30
* @param modifier The [Modifier] to be applied to this date picker
29
- * @param date The pre-selected date in the calendar. Defaults to now if not provided
30
- * @param minDate The minimum selectable date in the calendar (previous dates will be disabled) if provided. Defaults to infinite in the past
31
- * @param maxDate The maximum selectable date in the calendar (further dates will be disabled) if provided. Defaults to infinite in the future
31
+ * @param initialDisplayedDate The pre-selected date in the calendar. Defaults to now if not provided
32
+ * @param yearRange The range of years to be displayed in the year picker. Defaults to 1900-2100
33
+ * @param dateEdges The minimum and maximum selectable dates in the calendar. Defaults to infinite in the past and future
34
+ * @param colors The colors to be used for the date picker
35
+ * @param title The title to be displayed at the top of the date picker
36
+ * @param headline The headline to be displayed below the title of the date picker
32
37
* @param onDateSelected Callback when a date is selected in the picker
33
38
*/
34
39
@OptIn(ExperimentalMaterial3Api ::class )
35
40
@Composable
36
41
fun GrapesDatePicker (
37
42
modifier : Modifier = Modifier ,
38
- date : Date ? = null,
39
- minDate : Date ? = null,
40
- maxDate : Date ? = null,
41
- onDateSelected : ((Date ) -> Unit )? = null
43
+ initialDisplayedDate : Date ? = null,
44
+ yearRange : IntRange = GrapesDatePickerDefaults .YearRange ,
45
+ dateEdges : SelectableDates = GrapesDatePickerDefaults .selectableDatesEdges(),
46
+ colors : DatePickerColors = GrapesDatePickerDefaults .colors(),
47
+ title : (@Composable () -> Unit )? = null,
48
+ headline : (@Composable () -> Unit )? = null,
49
+ onDateSelected : ((Date ) -> Unit )? = null,
42
50
) {
43
51
val selectedDate = rememberDatePickerState(
44
- initialSelectedDateMillis = date ?.time,
45
- initialDisplayedMonthMillis = date ?.time,
46
- yearRange = DatePickerDefaults . YearRange ,
52
+ initialSelectedDateMillis = initialDisplayedDate ?.time,
53
+ initialDisplayedMonthMillis = initialDisplayedDate ?.time,
54
+ yearRange = yearRange ,
47
55
initialDisplayMode = DisplayMode .Picker ,
48
- selectableDates = object : SelectableDates {
49
- override fun isSelectableDate (utcTimeMillis : Long ): Boolean {
50
- val calendar = Calendar .getInstance(TimeZone .getTimeZone(" UTC" )).apply { timeInMillis = utcTimeMillis }
51
-
52
- val isAfterMinDate = minDate?.let { calendar.time.after(it.resetDateToMidnight()) } ? : true
53
- val isBeforeMaxDate = maxDate?.let { calendar.time.before(it.resetDateToTomorrowMidnight()) } ? : true
54
-
55
- return isAfterMinDate && isBeforeMaxDate
56
- }
57
- }
56
+ selectableDates = dateEdges,
58
57
)
59
58
60
59
DatePicker (
61
60
state = selectedDate,
62
61
modifier = modifier,
63
62
showModeToggle = false ,
64
- title = null ,
65
- headline = null ,
66
- colors = DatePickerDefaults .colors(
67
- containerColor = GrapesTheme .colors.mainBackground,
68
- titleContentColor = GrapesTheme .colors.mainNeutralDarkest,
69
- headlineContentColor = GrapesTheme .colors.mainNeutralDarkest,
70
- weekdayContentColor = GrapesTheme .colors.mainNeutralDarkest,
71
- subheadContentColor = GrapesTheme .colors.mainNeutralDarkest,
72
- yearContentColor = GrapesTheme .colors.mainNeutralDarkest,
73
- currentYearContentColor = GrapesTheme .colors.mainNeutralDarkest,
74
- selectedYearContentColor = GrapesTheme .colors.mainWhite,
75
- selectedYearContainerColor = GrapesTheme .colors.mainPrimaryNormal,
76
- dayContentColor = GrapesTheme .colors.mainNeutralDarkest,
77
- selectedDayContentColor = GrapesTheme .colors.mainWhite,
78
- selectedDayContainerColor = GrapesTheme .colors.mainPrimaryNormal,
79
- todayContentColor = GrapesTheme .colors.mainPrimaryNormal,
80
- todayDateBorderColor = GrapesTheme .colors.mainPrimaryNormal
81
- ),
63
+ title = title,
64
+ headline = headline,
65
+ colors = colors,
82
66
)
83
67
84
68
LaunchedEffect (selectedDate.selectedDateMillis) {
85
69
selectedDate.selectedDateMillis?.let {
86
- if (Date (it).resetDateToMidnight() != date ?.resetDateToMidnight()) {
70
+ if (Date (it).resetDateToMidnight() != initialDisplayedDate ?.resetDateToMidnight()) {
87
71
onDateSelected?.invoke(Date (it))
88
72
}
89
73
}
90
74
}
91
75
}
92
76
93
- @Preview(showBackground = true )
94
- @Composable
95
- private fun GrapesDatePickerWithMinDateAndMaxDatePreview () {
96
- val minDate = Calendar .getInstance().apply { add(Calendar .DAY_OF_WEEK , - 1 ) }.time
97
- val maxDate = Calendar .getInstance().apply { add(Calendar .DAY_OF_WEEK , 2 ) }.time
77
+ @OptIn(ExperimentalMaterial3Api ::class )
78
+ private data class GrapesDatePickerData (
79
+ val title : String? = null ,
80
+ val headline : String? = null ,
81
+ val initialDisplayedDate : Date ? = null ,
82
+ val yearRange : IntRange ? = null ,
83
+ val dateEdges : SelectableDates ? = null ,
84
+ val onDateSelected : ((Date ) -> Unit )? = null ,
85
+ )
98
86
99
- GrapesTheme {
100
- GrapesDatePicker (
101
- minDate = minDate,
102
- maxDate = maxDate,
103
- onDateSelected = { date -> println (" Selected date: $date " ) }
104
- )
105
- }
106
- }
87
+ @OptIn(ExperimentalMaterial3Api ::class )
88
+ private class GrapesDatePickerProvider : PreviewParameterProvider <GrapesDatePickerData > {
107
89
108
- @Preview(showBackground = true )
109
- @Composable
110
- private fun GrapesDatePickerWithMinDatePreview () {
111
- val minDate = Calendar .getInstance().apply { add(Calendar .DAY_OF_WEEK , - 1 ) }.time
90
+ private val calendar = Calendar .getInstance()
112
91
113
- GrapesTheme {
114
- GrapesDatePicker (
115
- minDate = minDate,
116
- onDateSelected = { date -> println (" Selected date: $date " ) }
117
- )
118
- }
119
- }
92
+ override val values: Sequence <GrapesDatePickerData > = sequenceOf(
93
+ GrapesDatePickerData (
94
+ title = " Select a date" ,
95
+ headline = " Choose a date to proceed" ,
96
+ ),
120
97
121
- @Preview(showBackground = true )
122
- @Composable
123
- private fun GrapesDatePickerWithMaxDatePreview () {
124
- val maxDate = Calendar .getInstance().apply { add(Calendar .DAY_OF_WEEK , 2 ) }.time
98
+ GrapesDatePickerData (
99
+ initialDisplayedDate = calendar.time, // Today
100
+ dateEdges = GrapesDatePickerDefaults .selectableDatesEdges(
101
+ minDate = calendar.apply { add(Calendar .DAY_OF_WEEK , - 1 ) }.time,
102
+ maxDate = calendar.apply { add(Calendar .DAY_OF_WEEK , 2 ) }.time
103
+ ),
104
+ ),
125
105
126
- GrapesTheme {
127
- GrapesDatePicker (
128
- maxDate = maxDate,
129
- onDateSelected = { date -> println (" Selected date: $date " ) }
106
+ GrapesDatePickerData (
107
+ dateEdges = GrapesDatePickerDefaults .selectableDatesEdges(
108
+ minDate = calendar.apply { add(Calendar .DAY_OF_WEEK , - 1 ) }.time,
109
+ maxDate = null
110
+ ),
111
+ ),
112
+
113
+ GrapesDatePickerData (
114
+ dateEdges = GrapesDatePickerDefaults .selectableDatesEdges(
115
+ minDate = null ,
116
+ maxDate = calendar.apply { add(Calendar .DAY_OF_WEEK , 1 ) }.time
117
+ ),
130
118
)
131
- }
119
+ )
132
120
}
133
121
134
- @Preview(showBackground = true )
122
+ @OptIn( ExperimentalMaterial3Api :: class )
135
123
@Composable
136
- private fun GrapesDatePickerPreview () {
124
+ @Preview(backgroundColor = 0xFFFFFFFF , showBackground = true )
125
+ private fun GrapesDatePickerPreview (
126
+ @PreviewParameter(GrapesDatePickerProvider ::class ) data : GrapesDatePickerData ,
127
+ ) {
137
128
GrapesTheme {
138
129
GrapesDatePicker (
130
+ title = data.title?.let {
131
+ @Composable {
132
+ Text (
133
+ modifier = Modifier .padding(GrapesTheme .dimensions.spacing3),
134
+ text = it,
135
+ style = GrapesTheme .typography.titleXl
136
+ )
137
+ }
138
+ },
139
+ headline = data.headline?.let {
140
+ @Composable {
141
+ Text (
142
+ modifier = Modifier .padding(GrapesTheme .dimensions.spacing3),
143
+ text = it,
144
+ style = GrapesTheme .typography.titleM
145
+ )
146
+ }
147
+ },
148
+ initialDisplayedDate = data.initialDisplayedDate,
149
+ dateEdges = data.dateEdges ? : GrapesDatePickerDefaults .selectableDatesEdges(),
139
150
onDateSelected = { date -> println (" Selected date: $date " ) }
140
151
)
141
152
}
0 commit comments