Skip to content

Commit 9451553

Browse files
committed
add a date grouped wheel
1 parent b9e3d72 commit 9451553

File tree

6 files changed

+364
-0
lines changed

6 files changed

+364
-0
lines changed

README.md

+38
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,44 @@ WheelDateTimePicker(
4545

4646
<img src="https://user-images.githubusercontent.com/50905347/201922097-86422287-cbd7-40ab-bf3c-5e0475828976.gif" width="256" height="256">
4747

48+
</td>
49+
</tr>
50+
</table>
51+
<table>
52+
<tr>
53+
<td>
54+
55+
```kotlin
56+
GroupedWheelDateTimePicker(
57+
startDateTime = LocalDateTime.of(
58+
2023, 6, 22, 5, 30
59+
),
60+
minDateTime = LocalDateTime.of(
61+
2023, 4, 20, 5, 30
62+
),
63+
maxDateTime = LocalDateTime.of(
64+
2023, 10, 20, 5, 30
65+
),
66+
todayLabel = "Today",
67+
dateFormat = DateTimeFormatter.ofPattern("EEE d MMM yy"),
68+
timeFormat = TimeFormat.HOUR_24,
69+
size = DpSize(300.dp, 200.dp),
70+
rowCount = 5,
71+
textStyle = MaterialTheme.typography.bodyLarge,
72+
textColor = Color(0xFFFFFFFF),
73+
selectorProperties = WheelPickerDefaults.selectorProperties(
74+
enabled = true,
75+
shape = RoundedCornerShape(5.dp),
76+
color = Color(0xFF036AB3).copy(alpha = 0.2f),
77+
border = BorderStroke(1.dp, Color(0xFF036AB3))
78+
)
79+
) { println(it) }
80+
```
81+
</td>
82+
<td>
83+
84+
<img src="https://github.com/KDVL/WheelPickerCompose/assets/26932740/67c63d6d-6247-4c64-ba1f-1b2aa17ec6fe" width="256" height="256">
85+
4886
</td>
4987
</tr>
5088
</table>

app/src/main/java/com/commandiron/wheelpickercompose/MainActivity.kt

+27
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@ import androidx.compose.ui.Modifier
1515
import androidx.compose.ui.graphics.Color
1616
import androidx.compose.ui.unit.DpSize
1717
import androidx.compose.ui.unit.dp
18+
import com.commandiron.wheel_picker_compose.GroupedWheelDateTimePicker
1819
import com.commandiron.wheel_picker_compose.WheelDatePicker
1920
import com.commandiron.wheel_picker_compose.WheelDateTimePicker
2021
import com.commandiron.wheel_picker_compose.WheelTimePicker
2122
import com.commandiron.wheel_picker_compose.core.TimeFormat
2223
import com.commandiron.wheel_picker_compose.core.WheelPickerDefaults
2324
import com.commandiron.wheelpickercompose.ui.theme.WheelPickerComposeTheme
2425
import java.time.LocalDateTime
26+
import java.time.format.DateTimeFormatter
2527

2628
class MainActivity : ComponentActivity() {
2729
override fun onCreate(savedInstanceState: Bundle?) {
@@ -67,6 +69,31 @@ class MainActivity : ComponentActivity() {
6769
){ snappedDateTime ->
6870
println(snappedDateTime)
6971
}
72+
73+
GroupedWheelDateTimePicker(
74+
startDateTime = LocalDateTime.of(
75+
2023, 6, 22, 5, 30
76+
),
77+
minDateTime = LocalDateTime.of(
78+
2023, 4, 20, 5, 30
79+
),
80+
maxDateTime = LocalDateTime.of(
81+
2023, 10, 20, 5, 30
82+
),
83+
todayLabel = "Today",
84+
dateFormat = DateTimeFormatter.ofPattern("EEE d MMM yy"),
85+
timeFormat = TimeFormat.HOUR_24,
86+
size = DpSize(300.dp, 200.dp),
87+
rowCount = 5,
88+
textStyle = MaterialTheme.typography.bodyLarge,
89+
textColor = Color(0xFFFFFFFF),
90+
selectorProperties = WheelPickerDefaults.selectorProperties(
91+
enabled = true,
92+
shape = RoundedCornerShape(5.dp),
93+
color = Color(0xFF036AB3).copy(alpha = 0.2f),
94+
border = BorderStroke(1.dp, Color(0xFF036AB3))
95+
)
96+
) { println(it) }
7097
}
7198
}
7299
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package com.commandiron.wheel_picker_compose
2+
3+
import androidx.compose.material3.LocalContentColor
4+
import androidx.compose.material3.MaterialTheme
5+
import androidx.compose.runtime.Composable
6+
import androidx.compose.ui.Modifier
7+
import androidx.compose.ui.graphics.Color
8+
import androidx.compose.ui.text.TextStyle
9+
import androidx.compose.ui.unit.DpSize
10+
import androidx.compose.ui.unit.dp
11+
import com.commandiron.wheel_picker_compose.core.GroupedWheelDateTimePicker
12+
import com.commandiron.wheel_picker_compose.core.SelectorProperties
13+
import com.commandiron.wheel_picker_compose.core.TimeFormat
14+
import com.commandiron.wheel_picker_compose.core.WheelPickerDefaults
15+
import java.time.LocalDateTime
16+
import java.time.format.DateTimeFormatter
17+
18+
@Composable
19+
fun GroupedWheelDateTimePicker(
20+
modifier: Modifier = Modifier,
21+
startDateTime: LocalDateTime = LocalDateTime.now(),
22+
minDateTime: LocalDateTime = LocalDateTime.MIN,
23+
maxDateTime: LocalDateTime = LocalDateTime.of(2122, 1, 1, 0, 0),
24+
todayLabel: String? = null,
25+
dateFormat: DateTimeFormatter = DateTimeFormatter.ofPattern("dd/MM/yyyy"),
26+
timeFormat: TimeFormat = TimeFormat.HOUR_24,
27+
size: DpSize = DpSize(256.dp, 128.dp),
28+
rowCount: Int = 3,
29+
textStyle: TextStyle = MaterialTheme.typography.titleMedium,
30+
textColor: Color = LocalContentColor.current,
31+
selectorProperties: SelectorProperties = WheelPickerDefaults.selectorProperties(),
32+
onDateTimeChanged: (dateTime: LocalDateTime) -> Unit = {}
33+
) {
34+
GroupedWheelDateTimePicker(
35+
modifier,
36+
startDateTime,
37+
minDateTime,
38+
maxDateTime,
39+
todayLabel,
40+
dateFormat,
41+
timeFormat,
42+
size,
43+
rowCount,
44+
textStyle,
45+
textColor,
46+
selectorProperties,
47+
onDateTime = { onDateTimeChanged(it.groupedLocalDateTime) }
48+
)
49+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.commandiron.wheel_picker_compose.core
2+
3+
import java.time.LocalDate
4+
import java.time.LocalDateTime
5+
import java.time.LocalTime
6+
7+
8+
internal sealed class GroupedDateTime(val groupedLocalDateTime: LocalDateTime) {
9+
data class Date(val localDateTime: LocalDateTime): GroupedDateTime(localDateTime)
10+
data class Hour(val localDateTime: LocalDateTime): GroupedDateTime(localDateTime)
11+
data class Minute(val localDateTime: LocalDateTime): GroupedDateTime(localDateTime)
12+
}
13+
14+
internal sealed class GroupedDate(val groupedLocalDate: LocalDate) {
15+
data class Date(val localDate: LocalDate): GroupedDate(localDate)
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package com.commandiron.wheel_picker_compose.core
2+
3+
import androidx.compose.foundation.layout.Box
4+
import androidx.compose.foundation.layout.Row
5+
import androidx.compose.foundation.layout.size
6+
import androidx.compose.material3.LocalContentColor
7+
import androidx.compose.material3.MaterialTheme
8+
import androidx.compose.material3.Surface
9+
import androidx.compose.runtime.*
10+
import androidx.compose.ui.Alignment
11+
import androidx.compose.ui.Modifier
12+
import androidx.compose.ui.graphics.Color
13+
import androidx.compose.ui.text.TextStyle
14+
import androidx.compose.ui.unit.DpSize
15+
import androidx.compose.ui.unit.dp
16+
import java.time.LocalDate
17+
import java.time.ZoneId
18+
import java.time.format.DateTimeFormatter
19+
import java.time.temporal.ChronoUnit
20+
import java.util.stream.Collectors
21+
import java.util.stream.IntStream
22+
23+
24+
@Composable
25+
internal fun GroupedWheelDatePicker(
26+
modifier: Modifier = Modifier,
27+
startDate: LocalDate = LocalDate.now(),
28+
minDate: LocalDate = LocalDate.MIN,
29+
maxDate: LocalDate = LocalDate.MAX,
30+
todayLabel: String? = null,
31+
format: DateTimeFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy"),
32+
size: DpSize = DpSize(256.dp, 128.dp),
33+
rowCount: Int = 3,
34+
textStyle: TextStyle = MaterialTheme.typography.titleMedium,
35+
textColor: Color = LocalContentColor.current,
36+
selectorProperties: SelectorProperties = WheelPickerDefaults.selectorProperties(),
37+
onSnappedDate : (snappedDate: GroupedDate) -> Unit = {},
38+
) {
39+
var snappedDate by remember { mutableStateOf(startDate) }
40+
41+
val dates = getDatesRange(minDate, maxDate).mapIndexed { index, date ->
42+
Date(
43+
todayLabel?.let { if (date.isToday) todayLabel else null } ?: date.format(format),
44+
date,
45+
index
46+
)
47+
}
48+
49+
Box(modifier = modifier, contentAlignment = Alignment.Center) {
50+
if (selectorProperties.enabled().value) {
51+
Surface(
52+
modifier = Modifier.size(size.width, size.height / rowCount),
53+
shape = selectorProperties.shape().value,
54+
color = selectorProperties.color().value,
55+
border = selectorProperties.border().value
56+
) {}
57+
}
58+
Row {
59+
WheelTextPicker(
60+
size = DpSize(
61+
width = size.width,
62+
height = size.height
63+
),
64+
texts = dates.map { it.text },
65+
rowCount = rowCount,
66+
style = textStyle,
67+
color = textColor,
68+
selectorProperties = WheelPickerDefaults.selectorProperties(enabled = false),
69+
startIndex = dates.find { it.value == startDate }?.index ?: 0,
70+
onScrollFinished = { snappedIndex ->
71+
dates
72+
.find { it.index == snappedIndex }
73+
?.also {
74+
75+
it.value.apply {
76+
if (!isBefore(minDate) && !isAfter(maxDate)) {
77+
snappedDate = this
78+
}
79+
}
80+
81+
dates.find { it.value == snappedDate }?.index?.also {
82+
onSnappedDate(
83+
GroupedDate.Date(localDate = snappedDate)
84+
)
85+
}
86+
}
87+
return@WheelTextPicker dates.find { it.value == snappedDate }?.index
88+
}
89+
)
90+
}
91+
}
92+
}
93+
94+
private data class Date(
95+
val text: String,
96+
val value: LocalDate,
97+
val index: Int
98+
)
99+
100+
private fun getDatesRange(startDate: LocalDate, endDate: LocalDate)
101+
= IntStream.iterate(0) { i -> i + 1 }
102+
.limit( ChronoUnit.DAYS.between(startDate, endDate.plusDays(1)))
103+
.mapToObj { i -> startDate.plusDays(i.toLong()) }
104+
.collect(Collectors.toList())
105+
106+
private val LocalDate.isToday
107+
get() = isEqual(LocalDate.now(ZoneId.systemDefault()))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
package com.commandiron.wheel_picker_compose.core
2+
3+
import androidx.compose.foundation.layout.Box
4+
import androidx.compose.foundation.layout.Row
5+
import androidx.compose.foundation.layout.size
6+
import androidx.compose.material3.LocalContentColor
7+
import androidx.compose.material3.MaterialTheme
8+
import androidx.compose.material3.Surface
9+
import androidx.compose.runtime.*
10+
import androidx.compose.ui.Alignment
11+
import androidx.compose.ui.Modifier
12+
import androidx.compose.ui.graphics.Color
13+
import androidx.compose.ui.text.TextStyle
14+
import androidx.compose.ui.unit.DpSize
15+
import androidx.compose.ui.unit.dp
16+
import java.security.acl.Group
17+
import java.time.LocalDateTime
18+
import java.time.format.DateTimeFormatter
19+
import java.time.temporal.ChronoUnit
20+
21+
@Composable
22+
internal fun GroupedWheelDateTimePicker(
23+
modifier: Modifier = Modifier,
24+
startDateTime: LocalDateTime = LocalDateTime.now(),
25+
minDateTime: LocalDateTime = LocalDateTime.MIN,
26+
maxDateTime: LocalDateTime = LocalDateTime.MAX,
27+
todayLabel: String? = null,
28+
dateFormat: DateTimeFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy"),
29+
timeFormat: TimeFormat = TimeFormat.HOUR_24,
30+
size: DpSize = DpSize(256.dp, 128.dp),
31+
rowCount: Int = 3,
32+
textStyle: TextStyle = MaterialTheme.typography.titleMedium,
33+
textColor: Color = LocalContentColor.current,
34+
selectorProperties: SelectorProperties = WheelPickerDefaults.selectorProperties(),
35+
onDateTime : (snappedDateTime: GroupedDateTime) -> Unit = {}
36+
) {
37+
var snappedDateTime by remember { mutableStateOf(startDateTime.truncatedTo(ChronoUnit.MINUTES)) }
38+
39+
Box(modifier = modifier, contentAlignment = Alignment.Center) {
40+
if (selectorProperties.enabled().value) {
41+
Surface(
42+
modifier = Modifier
43+
.size(size.width, size.height / rowCount),
44+
shape = selectorProperties.shape().value,
45+
color = selectorProperties.color().value,
46+
border = selectorProperties.border().value
47+
) {}
48+
}
49+
Row {
50+
//Date
51+
GroupedWheelDatePicker(
52+
startDate = startDateTime.toLocalDate(),
53+
minDate = minDateTime.toLocalDate(),
54+
maxDate = maxDateTime.toLocalDate(),
55+
todayLabel = todayLabel,
56+
format = dateFormat,
57+
size = DpSize(
58+
width = size.width / 2,
59+
height = size.height
60+
),
61+
rowCount = rowCount,
62+
textStyle = textStyle,
63+
textColor = textColor,
64+
selectorProperties = WheelPickerDefaults.selectorProperties(
65+
enabled = false
66+
),
67+
onSnappedDate = { snappedDate ->
68+
val newDateTime = when(snappedDate) {
69+
is GroupedDate.Date -> {
70+
snappedDateTime
71+
.withYear(snappedDate.groupedLocalDate.year)
72+
.withMonth(snappedDate.groupedLocalDate.monthValue)
73+
.withDayOfMonth(snappedDate.groupedLocalDate.dayOfMonth)
74+
}
75+
}
76+
77+
if (!newDateTime.isBefore(minDateTime) && !newDateTime.isAfter(maxDateTime)) {
78+
snappedDateTime = newDateTime
79+
}
80+
81+
onDateTime(GroupedDateTime.Date(snappedDateTime))
82+
}
83+
)
84+
//Time
85+
DefaultWheelTimePicker(
86+
startTime = startDateTime.toLocalTime(),
87+
timeFormat = timeFormat,
88+
size = DpSize(
89+
width = size.width / 2,
90+
height = size.height
91+
),
92+
rowCount = rowCount,
93+
textStyle = textStyle,
94+
textColor = textColor,
95+
selectorProperties = WheelPickerDefaults.selectorProperties(
96+
enabled = false
97+
),
98+
onSnappedTime = { snappedTime, timeFormat ->
99+
val newDateTime = when(snappedTime) {
100+
is SnappedTime.Hour -> {
101+
snappedDateTime.withHour(snappedTime.snappedLocalTime.hour)
102+
}
103+
is SnappedTime.Minute -> {
104+
snappedDateTime.withMinute(snappedTime.snappedLocalTime.minute)
105+
}
106+
}
107+
108+
if (!newDateTime.isBefore(minDateTime) && !newDateTime.isAfter(maxDateTime)) {
109+
snappedDateTime = newDateTime
110+
}
111+
112+
return@DefaultWheelTimePicker when(snappedTime) {
113+
is SnappedTime.Hour -> {
114+
onDateTime(GroupedDateTime.Hour(snappedDateTime))
115+
if (timeFormat == TimeFormat.HOUR_24) snappedDateTime.hour else
116+
localTimeToAmPmHour(snappedDateTime.toLocalTime()) - 1
117+
}
118+
is SnappedTime.Minute -> {
119+
onDateTime(GroupedDateTime.Minute(snappedDateTime))
120+
snappedDateTime.minute
121+
}
122+
}
123+
}
124+
)
125+
}
126+
}
127+
}

0 commit comments

Comments
 (0)