From 9451553affae60e5d72d647d97808e78c6f4ef17 Mon Sep 17 00:00:00 2001 From: Kevin Do Vale Date: Thu, 22 Jun 2023 14:27:02 +0200 Subject: [PATCH] add a date grouped wheel --- README.md | 38 ++++++ .../wheelpickercompose/MainActivity.kt | 27 ++++ .../GroupedWheelDateTimePicker.kt | 49 +++++++ .../core/GroupedDateTime.kt | 16 +++ .../core/GroupedWheelDatePicker.kt | 107 +++++++++++++++ .../core/GroupedWheelDateTimePicker.kt | 127 ++++++++++++++++++ 6 files changed, 364 insertions(+) create mode 100644 wheel-picker-compose/src/main/java/com/commandiron/wheel_picker_compose/GroupedWheelDateTimePicker.kt create mode 100644 wheel-picker-compose/src/main/java/com/commandiron/wheel_picker_compose/core/GroupedDateTime.kt create mode 100644 wheel-picker-compose/src/main/java/com/commandiron/wheel_picker_compose/core/GroupedWheelDatePicker.kt create mode 100644 wheel-picker-compose/src/main/java/com/commandiron/wheel_picker_compose/core/GroupedWheelDateTimePicker.kt diff --git a/README.md b/README.md index cfd9b67..7c6d151 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,44 @@ WheelDateTimePicker( + + + + + + +
+ +```kotlin +GroupedWheelDateTimePicker( + startDateTime = LocalDateTime.of( + 2023, 6, 22, 5, 30 + ), + minDateTime = LocalDateTime.of( + 2023, 4, 20, 5, 30 + ), + maxDateTime = LocalDateTime.of( + 2023, 10, 20, 5, 30 + ), + todayLabel = "Today", + dateFormat = DateTimeFormatter.ofPattern("EEE d MMM yy"), + timeFormat = TimeFormat.HOUR_24, + size = DpSize(300.dp, 200.dp), + rowCount = 5, + textStyle = MaterialTheme.typography.bodyLarge, + textColor = Color(0xFFFFFFFF), + selectorProperties = WheelPickerDefaults.selectorProperties( + enabled = true, + shape = RoundedCornerShape(5.dp), + color = Color(0xFF036AB3).copy(alpha = 0.2f), + border = BorderStroke(1.dp, Color(0xFF036AB3)) + ) +) { println(it) } +``` + + + +
diff --git a/app/src/main/java/com/commandiron/wheelpickercompose/MainActivity.kt b/app/src/main/java/com/commandiron/wheelpickercompose/MainActivity.kt index dda0f31..2f1a39f 100644 --- a/app/src/main/java/com/commandiron/wheelpickercompose/MainActivity.kt +++ b/app/src/main/java/com/commandiron/wheelpickercompose/MainActivity.kt @@ -15,6 +15,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp +import com.commandiron.wheel_picker_compose.GroupedWheelDateTimePicker import com.commandiron.wheel_picker_compose.WheelDatePicker import com.commandiron.wheel_picker_compose.WheelDateTimePicker import com.commandiron.wheel_picker_compose.WheelTimePicker @@ -22,6 +23,7 @@ import com.commandiron.wheel_picker_compose.core.TimeFormat import com.commandiron.wheel_picker_compose.core.WheelPickerDefaults import com.commandiron.wheelpickercompose.ui.theme.WheelPickerComposeTheme import java.time.LocalDateTime +import java.time.format.DateTimeFormatter class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -67,6 +69,31 @@ class MainActivity : ComponentActivity() { ){ snappedDateTime -> println(snappedDateTime) } + + GroupedWheelDateTimePicker( + startDateTime = LocalDateTime.of( + 2023, 6, 22, 5, 30 + ), + minDateTime = LocalDateTime.of( + 2023, 4, 20, 5, 30 + ), + maxDateTime = LocalDateTime.of( + 2023, 10, 20, 5, 30 + ), + todayLabel = "Today", + dateFormat = DateTimeFormatter.ofPattern("EEE d MMM yy"), + timeFormat = TimeFormat.HOUR_24, + size = DpSize(300.dp, 200.dp), + rowCount = 5, + textStyle = MaterialTheme.typography.bodyLarge, + textColor = Color(0xFFFFFFFF), + selectorProperties = WheelPickerDefaults.selectorProperties( + enabled = true, + shape = RoundedCornerShape(5.dp), + color = Color(0xFF036AB3).copy(alpha = 0.2f), + border = BorderStroke(1.dp, Color(0xFF036AB3)) + ) + ) { println(it) } } } } diff --git a/wheel-picker-compose/src/main/java/com/commandiron/wheel_picker_compose/GroupedWheelDateTimePicker.kt b/wheel-picker-compose/src/main/java/com/commandiron/wheel_picker_compose/GroupedWheelDateTimePicker.kt new file mode 100644 index 0000000..6ea1235 --- /dev/null +++ b/wheel-picker-compose/src/main/java/com/commandiron/wheel_picker_compose/GroupedWheelDateTimePicker.kt @@ -0,0 +1,49 @@ +package com.commandiron.wheel_picker_compose + +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.dp +import com.commandiron.wheel_picker_compose.core.GroupedWheelDateTimePicker +import com.commandiron.wheel_picker_compose.core.SelectorProperties +import com.commandiron.wheel_picker_compose.core.TimeFormat +import com.commandiron.wheel_picker_compose.core.WheelPickerDefaults +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter + +@Composable +fun GroupedWheelDateTimePicker( + modifier: Modifier = Modifier, + startDateTime: LocalDateTime = LocalDateTime.now(), + minDateTime: LocalDateTime = LocalDateTime.MIN, + maxDateTime: LocalDateTime = LocalDateTime.of(2122, 1, 1, 0, 0), + todayLabel: String? = null, + dateFormat: DateTimeFormatter = DateTimeFormatter.ofPattern("dd/MM/yyyy"), + timeFormat: TimeFormat = TimeFormat.HOUR_24, + size: DpSize = DpSize(256.dp, 128.dp), + rowCount: Int = 3, + textStyle: TextStyle = MaterialTheme.typography.titleMedium, + textColor: Color = LocalContentColor.current, + selectorProperties: SelectorProperties = WheelPickerDefaults.selectorProperties(), + onDateTimeChanged: (dateTime: LocalDateTime) -> Unit = {} +) { + GroupedWheelDateTimePicker( + modifier, + startDateTime, + minDateTime, + maxDateTime, + todayLabel, + dateFormat, + timeFormat, + size, + rowCount, + textStyle, + textColor, + selectorProperties, + onDateTime = { onDateTimeChanged(it.groupedLocalDateTime) } + ) +} \ No newline at end of file diff --git a/wheel-picker-compose/src/main/java/com/commandiron/wheel_picker_compose/core/GroupedDateTime.kt b/wheel-picker-compose/src/main/java/com/commandiron/wheel_picker_compose/core/GroupedDateTime.kt new file mode 100644 index 0000000..d207bcf --- /dev/null +++ b/wheel-picker-compose/src/main/java/com/commandiron/wheel_picker_compose/core/GroupedDateTime.kt @@ -0,0 +1,16 @@ +package com.commandiron.wheel_picker_compose.core + +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.LocalTime + + +internal sealed class GroupedDateTime(val groupedLocalDateTime: LocalDateTime) { + data class Date(val localDateTime: LocalDateTime): GroupedDateTime(localDateTime) + data class Hour(val localDateTime: LocalDateTime): GroupedDateTime(localDateTime) + data class Minute(val localDateTime: LocalDateTime): GroupedDateTime(localDateTime) +} + +internal sealed class GroupedDate(val groupedLocalDate: LocalDate) { + data class Date(val localDate: LocalDate): GroupedDate(localDate) +} \ No newline at end of file diff --git a/wheel-picker-compose/src/main/java/com/commandiron/wheel_picker_compose/core/GroupedWheelDatePicker.kt b/wheel-picker-compose/src/main/java/com/commandiron/wheel_picker_compose/core/GroupedWheelDatePicker.kt new file mode 100644 index 0000000..22e4cb8 --- /dev/null +++ b/wheel-picker-compose/src/main/java/com/commandiron/wheel_picker_compose/core/GroupedWheelDatePicker.kt @@ -0,0 +1,107 @@ +package com.commandiron.wheel_picker_compose.core + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.size +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.dp +import java.time.LocalDate +import java.time.ZoneId +import java.time.format.DateTimeFormatter +import java.time.temporal.ChronoUnit +import java.util.stream.Collectors +import java.util.stream.IntStream + + +@Composable +internal fun GroupedWheelDatePicker( + modifier: Modifier = Modifier, + startDate: LocalDate = LocalDate.now(), + minDate: LocalDate = LocalDate.MIN, + maxDate: LocalDate = LocalDate.MAX, + todayLabel: String? = null, + format: DateTimeFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy"), + size: DpSize = DpSize(256.dp, 128.dp), + rowCount: Int = 3, + textStyle: TextStyle = MaterialTheme.typography.titleMedium, + textColor: Color = LocalContentColor.current, + selectorProperties: SelectorProperties = WheelPickerDefaults.selectorProperties(), + onSnappedDate : (snappedDate: GroupedDate) -> Unit = {}, +) { + var snappedDate by remember { mutableStateOf(startDate) } + + val dates = getDatesRange(minDate, maxDate).mapIndexed { index, date -> + Date( + todayLabel?.let { if (date.isToday) todayLabel else null } ?: date.format(format), + date, + index + ) + } + + Box(modifier = modifier, contentAlignment = Alignment.Center) { + if (selectorProperties.enabled().value) { + Surface( + modifier = Modifier.size(size.width, size.height / rowCount), + shape = selectorProperties.shape().value, + color = selectorProperties.color().value, + border = selectorProperties.border().value + ) {} + } + Row { + WheelTextPicker( + size = DpSize( + width = size.width, + height = size.height + ), + texts = dates.map { it.text }, + rowCount = rowCount, + style = textStyle, + color = textColor, + selectorProperties = WheelPickerDefaults.selectorProperties(enabled = false), + startIndex = dates.find { it.value == startDate }?.index ?: 0, + onScrollFinished = { snappedIndex -> + dates + .find { it.index == snappedIndex } + ?.also { + + it.value.apply { + if (!isBefore(minDate) && !isAfter(maxDate)) { + snappedDate = this + } + } + + dates.find { it.value == snappedDate }?.index?.also { + onSnappedDate( + GroupedDate.Date(localDate = snappedDate) + ) + } + } + return@WheelTextPicker dates.find { it.value == snappedDate }?.index + } + ) + } + } +} + +private data class Date( + val text: String, + val value: LocalDate, + val index: Int +) + +private fun getDatesRange(startDate: LocalDate, endDate: LocalDate) + = IntStream.iterate(0) { i -> i + 1 } + .limit( ChronoUnit.DAYS.between(startDate, endDate.plusDays(1))) + .mapToObj { i -> startDate.plusDays(i.toLong()) } + .collect(Collectors.toList()) + +private val LocalDate.isToday + get() = isEqual(LocalDate.now(ZoneId.systemDefault())) \ No newline at end of file diff --git a/wheel-picker-compose/src/main/java/com/commandiron/wheel_picker_compose/core/GroupedWheelDateTimePicker.kt b/wheel-picker-compose/src/main/java/com/commandiron/wheel_picker_compose/core/GroupedWheelDateTimePicker.kt new file mode 100644 index 0000000..62753b1 --- /dev/null +++ b/wheel-picker-compose/src/main/java/com/commandiron/wheel_picker_compose/core/GroupedWheelDateTimePicker.kt @@ -0,0 +1,127 @@ +package com.commandiron.wheel_picker_compose.core + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.size +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.dp +import java.security.acl.Group +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter +import java.time.temporal.ChronoUnit + +@Composable +internal fun GroupedWheelDateTimePicker( + modifier: Modifier = Modifier, + startDateTime: LocalDateTime = LocalDateTime.now(), + minDateTime: LocalDateTime = LocalDateTime.MIN, + maxDateTime: LocalDateTime = LocalDateTime.MAX, + todayLabel: String? = null, + dateFormat: DateTimeFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy"), + timeFormat: TimeFormat = TimeFormat.HOUR_24, + size: DpSize = DpSize(256.dp, 128.dp), + rowCount: Int = 3, + textStyle: TextStyle = MaterialTheme.typography.titleMedium, + textColor: Color = LocalContentColor.current, + selectorProperties: SelectorProperties = WheelPickerDefaults.selectorProperties(), + onDateTime : (snappedDateTime: GroupedDateTime) -> Unit = {} +) { + var snappedDateTime by remember { mutableStateOf(startDateTime.truncatedTo(ChronoUnit.MINUTES)) } + + Box(modifier = modifier, contentAlignment = Alignment.Center) { + if (selectorProperties.enabled().value) { + Surface( + modifier = Modifier + .size(size.width, size.height / rowCount), + shape = selectorProperties.shape().value, + color = selectorProperties.color().value, + border = selectorProperties.border().value + ) {} + } + Row { + //Date + GroupedWheelDatePicker( + startDate = startDateTime.toLocalDate(), + minDate = minDateTime.toLocalDate(), + maxDate = maxDateTime.toLocalDate(), + todayLabel = todayLabel, + format = dateFormat, + size = DpSize( + width = size.width / 2, + height = size.height + ), + rowCount = rowCount, + textStyle = textStyle, + textColor = textColor, + selectorProperties = WheelPickerDefaults.selectorProperties( + enabled = false + ), + onSnappedDate = { snappedDate -> + val newDateTime = when(snappedDate) { + is GroupedDate.Date -> { + snappedDateTime + .withYear(snappedDate.groupedLocalDate.year) + .withMonth(snappedDate.groupedLocalDate.monthValue) + .withDayOfMonth(snappedDate.groupedLocalDate.dayOfMonth) + } + } + + if (!newDateTime.isBefore(minDateTime) && !newDateTime.isAfter(maxDateTime)) { + snappedDateTime = newDateTime + } + + onDateTime(GroupedDateTime.Date(snappedDateTime)) + } + ) + //Time + DefaultWheelTimePicker( + startTime = startDateTime.toLocalTime(), + timeFormat = timeFormat, + size = DpSize( + width = size.width / 2, + height = size.height + ), + rowCount = rowCount, + textStyle = textStyle, + textColor = textColor, + selectorProperties = WheelPickerDefaults.selectorProperties( + enabled = false + ), + onSnappedTime = { snappedTime, timeFormat -> + val newDateTime = when(snappedTime) { + is SnappedTime.Hour -> { + snappedDateTime.withHour(snappedTime.snappedLocalTime.hour) + } + is SnappedTime.Minute -> { + snappedDateTime.withMinute(snappedTime.snappedLocalTime.minute) + } + } + + if (!newDateTime.isBefore(minDateTime) && !newDateTime.isAfter(maxDateTime)) { + snappedDateTime = newDateTime + } + + return@DefaultWheelTimePicker when(snappedTime) { + is SnappedTime.Hour -> { + onDateTime(GroupedDateTime.Hour(snappedDateTime)) + if (timeFormat == TimeFormat.HOUR_24) snappedDateTime.hour else + localTimeToAmPmHour(snappedDateTime.toLocalTime()) - 1 + } + is SnappedTime.Minute -> { + onDateTime(GroupedDateTime.Minute(snappedDateTime)) + snappedDateTime.minute + } + } + } + ) + } + } +} \ No newline at end of file