Skip to content

Commit 1b61295

Browse files
authored
Merge pull request #344 from Spendesk/task/datepicker_dialog
feat(compose) Rework GrapesDatePicker + Add GrapesDatePickerDialog
2 parents 6009678 + 84361d8 commit 1b61295

File tree

3 files changed

+219
-77
lines changed

3 files changed

+219
-77
lines changed

library-compose/src/main/java/com/spendesk/grapes/compose/calendar/GrapesDatePicker.kt

+88-77
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
11
package com.spendesk.grapes.compose.calendar
22

3+
import androidx.compose.foundation.layout.padding
34
import androidx.compose.material3.DatePicker
4-
import androidx.compose.material3.DatePickerDefaults
5+
import androidx.compose.material3.DatePickerColors
56
import androidx.compose.material3.DisplayMode
67
import androidx.compose.material3.ExperimentalMaterial3Api
78
import androidx.compose.material3.SelectableDates
9+
import androidx.compose.material3.Text
810
import androidx.compose.material3.rememberDatePickerState
911
import androidx.compose.runtime.Composable
1012
import androidx.compose.runtime.LaunchedEffect
1113
import androidx.compose.ui.Modifier
1214
import androidx.compose.ui.tooling.preview.Preview
15+
import androidx.compose.ui.tooling.preview.PreviewParameter
16+
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
1317
import com.spendesk.grapes.compose.extensions.resetDateToMidnight
14-
import com.spendesk.grapes.compose.extensions.resetDateToTomorrowMidnight
1518
import com.spendesk.grapes.compose.theme.GrapesTheme
1619
import java.util.Calendar
1720
import java.util.Date
18-
import java.util.TimeZone
1921

2022
/**
2123
* @author : dany
@@ -26,116 +28,125 @@ import java.util.TimeZone
2628
* Grapes date picker which lets the user select a date via a calendar UI.
2729
*
2830
* @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
3237
* @param onDateSelected Callback when a date is selected in the picker
3338
*/
3439
@OptIn(ExperimentalMaterial3Api::class)
3540
@Composable
3641
fun GrapesDatePicker(
3742
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,
4250
) {
4351
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,
4755
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,
5857
)
5958

6059
DatePicker(
6160
state = selectedDate,
6261
modifier = modifier,
6362
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,
8266
)
8367

8468
LaunchedEffect(selectedDate.selectedDateMillis) {
8569
selectedDate.selectedDateMillis?.let {
86-
if (Date(it).resetDateToMidnight() != date?.resetDateToMidnight()) {
70+
if (Date(it).resetDateToMidnight() != initialDisplayedDate?.resetDateToMidnight()) {
8771
onDateSelected?.invoke(Date(it))
8872
}
8973
}
9074
}
9175
}
9276

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+
)
9886

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> {
10789

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()
11291

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+
),
12097

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+
),
125105

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+
),
130118
)
131-
}
119+
)
132120
}
133121

134-
@Preview(showBackground = true)
122+
@OptIn(ExperimentalMaterial3Api::class)
135123
@Composable
136-
private fun GrapesDatePickerPreview() {
124+
@Preview(backgroundColor = 0xFFFFFFFF, showBackground = true)
125+
private fun GrapesDatePickerPreview(
126+
@PreviewParameter(GrapesDatePickerProvider::class) data: GrapesDatePickerData,
127+
) {
137128
GrapesTheme {
138129
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(),
139150
onDateSelected = { date -> println("Selected date: $date") }
140151
)
141152
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package com.spendesk.grapes.compose.calendar
2+
3+
import androidx.compose.material3.DatePickerColors
4+
import androidx.compose.material3.DatePickerDefaults
5+
import androidx.compose.material3.ExperimentalMaterial3Api
6+
import androidx.compose.material3.SelectableDates
7+
import androidx.compose.runtime.Composable
8+
import androidx.compose.runtime.Immutable
9+
import androidx.compose.ui.graphics.Color
10+
import com.spendesk.grapes.compose.extensions.resetDateToMidnight
11+
import com.spendesk.grapes.compose.extensions.resetDateToTomorrowMidnight
12+
import com.spendesk.grapes.compose.theme.GrapesTheme
13+
import java.util.Calendar
14+
import java.util.Date
15+
import java.util.TimeZone
16+
17+
@Immutable
18+
object GrapesDatePickerDefaults {
19+
20+
val YearRange: IntRange = IntRange(1900, 2100)
21+
22+
@ExperimentalMaterial3Api
23+
@Composable
24+
fun colors(
25+
containerColor: Color = GrapesTheme.colors.structureBackground,
26+
titleContentColor: Color = GrapesTheme.colors.neutralDarker,
27+
headlineContentColor: Color = GrapesTheme.colors.neutralDarker,
28+
weekdayContentColor: Color = GrapesTheme.colors.neutralDarker,
29+
subheadContentColor: Color = GrapesTheme.colors.neutralDarker,
30+
yearContentColor: Color = GrapesTheme.colors.neutralDarker,
31+
currentYearContentColor: Color = GrapesTheme.colors.neutralDarker,
32+
selectedYearContentColor: Color = Color.White,
33+
selectedYearContainerColor: Color = GrapesTheme.colors.primaryNormal,
34+
dayContentColor: Color = GrapesTheme.colors.neutralDarker,
35+
selectedDayContentColor: Color = Color.White,
36+
selectedDayContainerColor: Color = GrapesTheme.colors.primaryNormal,
37+
todayContentColor: Color = GrapesTheme.colors.primaryNormal,
38+
todayDateBorderColor: Color = GrapesTheme.colors.primaryNormal,
39+
): DatePickerColors = DatePickerDefaults.colors(
40+
containerColor = containerColor,
41+
titleContentColor = titleContentColor,
42+
headlineContentColor = headlineContentColor,
43+
weekdayContentColor = weekdayContentColor,
44+
subheadContentColor = subheadContentColor,
45+
yearContentColor = yearContentColor,
46+
currentYearContentColor = currentYearContentColor,
47+
selectedYearContentColor = selectedYearContentColor,
48+
selectedYearContainerColor = selectedYearContainerColor,
49+
dayContentColor = dayContentColor,
50+
selectedDayContentColor = selectedDayContentColor,
51+
selectedDayContainerColor = selectedDayContainerColor,
52+
todayContentColor = todayContentColor,
53+
todayDateBorderColor = todayDateBorderColor,
54+
)
55+
56+
@OptIn(ExperimentalMaterial3Api::class)
57+
fun selectableDatesEdges(minDate: Date? = null, maxDate: Date? = null): SelectableDates {
58+
return if (minDate != null || maxDate != null) {
59+
object : SelectableDates {
60+
override fun isSelectableDate(utcTimeMillis: Long): Boolean {
61+
val calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")).apply { timeInMillis = utcTimeMillis }
62+
63+
val isAfterMinDate = minDate?.let { calendar.time.after(it.resetDateToMidnight()) } ?: true
64+
val isBeforeMaxDate = maxDate?.let { calendar.time.before(it.resetDateToTomorrowMidnight()) } ?: true
65+
66+
return isAfterMinDate && isBeforeMaxDate
67+
}
68+
}
69+
} else {
70+
object : SelectableDates {}
71+
}
72+
}
73+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package com.spendesk.grapes.compose.calendar
2+
3+
import androidx.compose.material3.DatePickerColors
4+
import androidx.compose.material3.DatePickerDialog
5+
import androidx.compose.material3.ExperimentalMaterial3Api
6+
import androidx.compose.runtime.Composable
7+
import androidx.compose.ui.Modifier
8+
import androidx.compose.ui.graphics.Shape
9+
import androidx.compose.ui.tooling.preview.Preview
10+
import androidx.compose.ui.window.DialogProperties
11+
import com.spendesk.grapes.compose.theme.GrapesTheme
12+
import java.util.Date
13+
14+
@OptIn(ExperimentalMaterial3Api::class)
15+
@Composable
16+
fun GrapesDatePickerDialog(
17+
initialDisplayedDate: Date,
18+
onDateSelected: (selectedDate: Date) -> Unit,
19+
modifier: Modifier = Modifier,
20+
colors: DatePickerColors = GrapesDatePickerDefaults.colors(),
21+
shape: Shape = GrapesTheme.shapes.shape2,
22+
dismissOnBack: Boolean = true,
23+
dismissOnClickOutside: Boolean = true,
24+
confirmButton: @Composable () -> Unit = { },
25+
dismissButton: (@Composable () -> Unit)? = null,
26+
onDismissRequest: (() -> Unit)? = null,
27+
) {
28+
DatePickerDialog(
29+
modifier = modifier,
30+
onDismissRequest = { onDismissRequest?.invoke() },
31+
confirmButton = confirmButton,
32+
dismissButton = dismissButton,
33+
colors = colors,
34+
shape = shape,
35+
properties = DialogProperties(
36+
dismissOnBackPress = dismissOnBack,
37+
dismissOnClickOutside = dismissOnClickOutside,
38+
),
39+
content = {
40+
GrapesDatePicker(
41+
initialDisplayedDate = initialDisplayedDate,
42+
onDateSelected = { date -> onDateSelected(date) }
43+
)
44+
}
45+
)
46+
}
47+
48+
@OptIn(ExperimentalMaterial3Api::class)
49+
@Preview(showBackground = false)
50+
@Composable
51+
fun PreviewGrapesDatePickerDialog() {
52+
GrapesTheme {
53+
GrapesDatePickerDialog(
54+
initialDisplayedDate = Date(),
55+
onDateSelected = { }
56+
)
57+
}
58+
}

0 commit comments

Comments
 (0)