Skip to content

add a date grouped wheel #34

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,44 @@ WheelDateTimePicker(

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

</td>
</tr>
</table>
<table>
<tr>
<td>

```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) }
```
</td>
<td>

<img src="https://github.com/KDVL/WheelPickerCompose/assets/26932740/67c63d6d-6247-4c64-ba1f-1b2aa17ec6fe" width="256" height="256">

</td>
</tr>
</table>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ 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
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?) {
Expand Down Expand Up @@ -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) }
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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) }
)
}
Original file line number Diff line number Diff line change
@@ -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)
}
Original file line number Diff line number Diff line change
@@ -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()))
Original file line number Diff line number Diff line change
@@ -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
}
}
}
)
}
}
}