diff --git a/app/src/main/java/com/commandiron/wheelpickercompose/MainActivity.kt b/app/src/main/java/com/commandiron/wheelpickercompose/MainActivity.kt index f310a7e..5136924 100644 --- a/app/src/main/java/com/commandiron/wheelpickercompose/MainActivity.kt +++ b/app/src/main/java/com/commandiron/wheelpickercompose/MainActivity.kt @@ -20,8 +20,10 @@ import androidx.compose.ui.unit.dp 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.sql.Time import java.time.LocalDateTime class MainActivity : ComponentActivity() { @@ -51,9 +53,9 @@ class MainActivity : ComponentActivity() { startDateTime = LocalDateTime.of( 2025, 10, 30, 5, 0 ), - yearsRange = IntRange(1950, 2050), + yearsRange = null, size = DpSize(200.dp, 100.dp), - backwardsDisabled = false, + backwardsDisabled = true, textStyle = MaterialTheme.typography.titleSmall, textColor = Color(0xFFffc300), selectorProperties = WheelPickerDefaults.selectorProperties( diff --git a/wheel-picker-compose/build.gradle b/wheel-picker-compose/build.gradle index 8d84018..a5be642 100644 --- a/wheel-picker-compose/build.gradle +++ b/wheel-picker-compose/build.gradle @@ -55,7 +55,7 @@ afterEvaluate { groupId = 'com.github.commandiron' artifactId = 'wheel-picker-compose' - version = '1.1.3' + version = '1.1.4' } } } diff --git a/wheel-picker-compose/src/main/java/com/commandiron/wheel_picker_compose/WheelTimePicker.kt b/wheel-picker-compose/src/main/java/com/commandiron/wheel_picker_compose/WheelTimePicker.kt index 5da9cc5..4e8f212 100644 --- a/wheel-picker-compose/src/main/java/com/commandiron/wheel_picker_compose/WheelTimePicker.kt +++ b/wheel-picker-compose/src/main/java/com/commandiron/wheel_picker_compose/WheelTimePicker.kt @@ -12,6 +12,7 @@ import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import com.commandiron.wheel_picker_compose.core.DefaultWheelTimePicker 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.LocalTime @@ -20,6 +21,7 @@ import java.time.LocalTime fun WheelTimePicker( modifier: Modifier = Modifier, startTime: LocalTime = LocalTime.now(), + timeFormat: TimeFormat = TimeFormat.HOUR_24, backwardsDisabled: Boolean = false, size: DpSize = DpSize(128.dp, 128.dp), textStyle: TextStyle = MaterialTheme.typography.titleMedium, @@ -30,6 +32,7 @@ fun WheelTimePicker( DefaultWheelTimePicker( modifier, startTime, + timeFormat, backwardsDisabled, size, textStyle, diff --git a/wheel-picker-compose/src/main/java/com/commandiron/wheel_picker_compose/core/CalculateMonthDayTexts.kt b/wheel-picker-compose/src/main/java/com/commandiron/wheel_picker_compose/core/CalculateMonthDayTexts.kt deleted file mode 100644 index 75b54f6..0000000 --- a/wheel-picker-compose/src/main/java/com/commandiron/wheel_picker_compose/core/CalculateMonthDayTexts.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.commandiron.wheel_picker_compose.core - -import android.os.Build -import androidx.annotation.RequiresApi -import java.time.LocalDate - -@RequiresApi(Build.VERSION_CODES.O) -internal fun calculateMonthDayTexts(month: Int, year: Int): List { - - val isLeapYear = LocalDate.of(year, month, 1).isLeapYear - - val month31day = (1..31).toList().map { it.toString() } - val month30day = (1..30).toList().map { it.toString() } - val month29day = (1..29).toList().map { it.toString() } - val month28day = (1..28).toList().map { it.toString() } - - return when(month){ - 1 -> { month31day } - 2 -> { if(isLeapYear) month29day else month28day } - 3 -> { month31day } - 4 -> { month30day } - 5 -> { month31day } - 6 -> { month30day } - 7 -> { month31day } - 8 -> { month31day } - 9 -> { month30day } - 10 -> { month31day } - 11 -> { month30day } - 12 -> { month31day } - else -> { emptyList() } - } -} \ No newline at end of file diff --git a/wheel-picker-compose/src/main/java/com/commandiron/wheel_picker_compose/core/DefaultWheelDatePicker.kt b/wheel-picker-compose/src/main/java/com/commandiron/wheel_picker_compose/core/DefaultWheelDatePicker.kt index 50b3633..ae53b19 100644 --- a/wheel-picker-compose/src/main/java/com/commandiron/wheel_picker_compose/core/DefaultWheelDatePicker.kt +++ b/wheel-picker-compose/src/main/java/com/commandiron/wheel_picker_compose/core/DefaultWheelDatePicker.kt @@ -2,6 +2,7 @@ package com.commandiron.wheel_picker_compose.core import android.os.Build import androidx.annotation.RequiresApi +import androidx.compose.foundation.border import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.size @@ -31,16 +32,27 @@ internal fun DefaultWheelDatePicker( selectorProperties: SelectorProperties = WheelPickerDefaults.selectorProperties(), onSnappedDate : (snappedDate: SnappedDate) -> Int? = { _ -> null } ) { - var dayTexts by remember { mutableStateOf((1..31).toList().map { it.toString() }) } - val monthTexts: List = if(size.width / 3 < 55.dp){ - DateFormatSymbols().shortMonths.toList() - }else{ - DateFormatSymbols().months.toList() - } + var snappedDate by remember { mutableStateOf(startDate) } - val yearTexts = yearsRange?.map { it.toString() } ?: listOf() + var dayOfMonths = calculateDayOfMonths(snappedDate.month.value, snappedDate.year) - var snappedDate by remember { mutableStateOf(startDate) } + val months = (1..12).map { + Month( + text = if(size.width / 3 < 55.dp){ + DateFormatSymbols().shortMonths[it - 1] + } else DateFormatSymbols().months[it - 1], + value = it, + index = it - 1 + ) + } + + val years = yearsRange?.map { + Year( + text = it.toString(), + value = it, + index = yearsRange.indexOf(it) + ) + } Box(modifier = modifier, contentAlignment = Alignment.Center){ if(selectorProperties.enabled().value){ @@ -55,100 +67,146 @@ internal fun DefaultWheelDatePicker( Row { //Day of Month WheelTextPicker( - size = DpSize(size.width / 3 / if(yearsRange == null) 2 else 1, size.height), - texts = dayTexts, + size = DpSize( + width = if(yearsRange == null) size.width / 2 else size.width / 3, + height = size.height + ), + texts = dayOfMonths.map { it.text }, style = textStyle, color = textColor, selectorProperties = WheelPickerDefaults.selectorProperties( enabled = false ), - startIndex = startDate.dayOfMonth - 1, + startIndex = dayOfMonths.find { it.value== startDate.dayOfMonth }?.index ?: 0, onScrollFinished = { snappedIndex -> - val newDate = snappedDate.withDayOfMonth(if(snappedIndex + 1 > dayTexts.size) snappedIndex else snappedIndex + 1) - val isDateBefore = isDateBefore(newDate, startDate) + val newDayOfMonth = dayOfMonths.find { it.index == snappedIndex }?.value + + newDayOfMonth?.let { + val newDate = snappedDate.withDayOfMonth(newDayOfMonth) - if(backwardsDisabled) { - if(!isDateBefore) { + val isDateBefore = isDateBefore(newDate, startDate) + + if(backwardsDisabled) { + if(!isDateBefore) { + snappedDate = newDate + } + } else { snappedDate = newDate } - } else { - snappedDate = newDate - } - onSnappedDate( - SnappedDate.DayOfMonth(snappedDate, snappedDate.dayOfMonth - 1) - )?.let { return@WheelTextPicker it } + val newIndex = dayOfMonths.find { it.value == snappedDate.dayOfMonth }?.index - return@WheelTextPicker snappedDate.dayOfMonth - 1 + newIndex?.let { + onSnappedDate( + SnappedDate.DayOfMonth( + localDate = snappedDate, + index = newIndex + ) + )?.let { return@WheelTextPicker it } + } + } + + return@WheelTextPicker dayOfMonths.find { it.value == snappedDate.dayOfMonth }?.index } ) //Month WheelTextPicker( - size = DpSize(size.width / 3, size.height), - texts = monthTexts, + size = DpSize( + width = if(yearsRange == null) size.width / 2 else size.width / 3, + height = size.height + ), + texts = months.map { it.text }, style = textStyle, color = textColor, selectorProperties = WheelPickerDefaults.selectorProperties( enabled = false ), - startIndex = startDate.month.value - 1, + startIndex = months.find { it.value== startDate.monthValue }?.index ?: 0, onScrollFinished = { snappedIndex -> - val newDate = snappedDate.withMonth(if(snappedIndex + 1 > 12) 12 else snappedIndex + 1) - val isDateBefore = isDateBefore(newDate, startDate) + val newMonth = months.find { it.index == snappedIndex }?.value + + newMonth?.let { + + val newDate = snappedDate.withMonth(newMonth) + + val isDateBefore = isDateBefore(newDate, startDate) - if(backwardsDisabled) { - if(!isDateBefore) { + if(backwardsDisabled) { + if(!isDateBefore) { + snappedDate = newDate + } + } else { snappedDate = newDate } - } else { - snappedDate = newDate - } - dayTexts = calculateMonthDayTexts(snappedDate.month.value, snappedDate.year) + dayOfMonths = calculateDayOfMonths(snappedDate.month.value, snappedDate.year) + + val newIndex = months.find { it.value == snappedDate.monthValue }?.index - onSnappedDate( - SnappedDate.Month(snappedDate, snappedDate.month.value - 1) - )?.let { return@WheelTextPicker it } + newIndex?.let { + onSnappedDate( + SnappedDate.Month( + localDate = snappedDate, + index = newIndex + ) + )?.let { return@WheelTextPicker it } + } + } - return@WheelTextPicker snappedDate.month.value - 1 + + return@WheelTextPicker months.find { it.value == snappedDate.monthValue }?.index } ) //Year - yearsRange?.let { yearsRange -> + years?.let { years -> WheelTextPicker( - size = DpSize(size.width / 3, size.height), - texts = yearTexts, + size = DpSize( + width = size.width / 3, + height = size.height + ), + texts = years.map { it.text }, style = textStyle, color = textColor, selectorProperties = WheelPickerDefaults.selectorProperties( enabled = false ), - startIndex = if(yearsRange.indexOf(yearsRange.find { it == startDate.year }) == -1) { - throw IllegalArgumentException( - "startDate.year should greater than minYear and smaller than maxYear" - ) - } else yearsRange.indexOf(yearsRange.find { it == startDate.year }), + startIndex = years.find { it.value == startDate.year }?.index ?:0, onScrollFinished = { snappedIndex -> - val selectedYearText = yearTexts.getOrNull(snappedIndex) ?: yearTexts.last() - val newDate = snappedDate.withYear(selectedYearText.toInt()) - val isDateBefore = isDateBefore(newDate, startDate) + val newYear = years.find { it.index == snappedIndex }?.value - if(backwardsDisabled) { - if(!isDateBefore) { + newYear?.let { + + val newDate = snappedDate.withYear(newYear) + + val isDateBefore = isDateBefore(newDate, startDate) + + if(backwardsDisabled) { + if(!isDateBefore) { + snappedDate = newDate + } + } else { snappedDate = newDate } - } else { - snappedDate = newDate - } - dayTexts = calculateMonthDayTexts(snappedDate.month.value, snappedDate.year) + dayOfMonths = calculateDayOfMonths(snappedDate.month.value, snappedDate.year) + + val newIndex = years.find { it.value == snappedDate.year }?.index - onSnappedDate(SnappedDate.Year(snappedDate, yearTexts.indexOf(snappedDate.year.toString())))?.let { return@WheelTextPicker it } + newIndex?.let { + onSnappedDate( + SnappedDate.Year( + localDate = snappedDate, + index = newIndex + ) + )?.let { return@WheelTextPicker it } - return@WheelTextPicker yearTexts.indexOf(snappedDate.year.toString()) + } + } + + return@WheelTextPicker years.find { it.value == snappedDate.year }?.index } ) } @@ -159,4 +217,73 @@ internal fun DefaultWheelDatePicker( @RequiresApi(Build.VERSION_CODES.O) private fun isDateBefore(date: LocalDate, currentDate: LocalDate): Boolean{ return date.isBefore(currentDate) +} + +internal data class DayOfMonth( + val text: String, + val value: Int, + val index: Int +) + +private data class Month( + val text: String, + val value: Int, + val index: Int +) + +private data class Year( + val text: String, + val value: Int, + val index: Int +) + +@RequiresApi(Build.VERSION_CODES.O) +internal fun calculateDayOfMonths(month: Int, year: Int): List { + + val isLeapYear = LocalDate.of(year, month, 1).isLeapYear + + val month31day = (1..31).map { + DayOfMonth( + text = it.toString(), + value = it, + index = it - 1 + ) + } + val month30day = (1..30).map { + DayOfMonth( + text = it.toString(), + value = it, + index = it - 1 + ) + } + val month29day = (1..29).map { + DayOfMonth( + text = it.toString(), + value = it, + index = it - 1 + ) + } + val month28day = (1..28).map { + DayOfMonth( + text = it.toString(), + value = it, + index = it - 1 + ) + } + + return when(month){ + 1 -> { month31day } + 2 -> { if(isLeapYear) month29day else month28day } + 3 -> { month31day } + 4 -> { month30day } + 5 -> { month31day } + 6 -> { month30day } + 7 -> { month31day } + 8 -> { month31day } + 9 -> { month30day } + 10 -> { month31day } + 11 -> { month30day } + 12 -> { month31day } + else -> { emptyList() } + } } \ No newline at end of file diff --git a/wheel-picker-compose/src/main/java/com/commandiron/wheel_picker_compose/core/DefaultWheelDateTimePicker.kt b/wheel-picker-compose/src/main/java/com/commandiron/wheel_picker_compose/core/DefaultWheelDateTimePicker.kt index d947fbd..298dbe2 100644 --- a/wheel-picker-compose/src/main/java/com/commandiron/wheel_picker_compose/core/DefaultWheelDateTimePicker.kt +++ b/wheel-picker-compose/src/main/java/com/commandiron/wheel_picker_compose/core/DefaultWheelDateTimePicker.kt @@ -2,6 +2,7 @@ package com.commandiron.wheel_picker_compose.core import android.os.Build import androidx.annotation.RequiresApi +import androidx.compose.foundation.border import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.size @@ -16,6 +17,7 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import java.time.LocalDateTime +import java.time.temporal.ChronoUnit @RequiresApi(Build.VERSION_CODES.O) @Composable @@ -31,7 +33,7 @@ internal fun DefaultWheelDateTimePicker( onSnappedDateTime : (snappedDateTime: SnappedDateTime) -> Int? = { _ -> null } ) { - var snappedDateTime by remember { mutableStateOf(startDateTime) } + var snappedDateTime by remember { mutableStateOf(startDateTime.truncatedTo(ChronoUnit.MINUTES)) } val yearTexts = yearsRange?.map { it.toString() } ?: listOf() @@ -51,7 +53,10 @@ internal fun DefaultWheelDateTimePicker( startDate = startDateTime.toLocalDate(), yearsRange = yearsRange, backwardsDisabled = false, - size = DpSize(size.width / 5 * 3, size.height), + size = DpSize( + width = if(yearsRange == null ) size.width * 3 / 6 else size.width * 3 / 5 , + height = size.height + ), textStyle = textStyle, textColor = textColor, selectorProperties = WheelPickerDefaults.selectorProperties( @@ -101,7 +106,10 @@ internal fun DefaultWheelDateTimePicker( DefaultWheelTimePicker( startTime = startDateTime.toLocalTime(), backwardsDisabled = false, - size = DpSize(size.width / 5 * 2, size.height), + size = DpSize( + width = if(yearsRange == null ) size.width * 3 / 6 else size.width * 2 / 5 , + height = size.height + ), textStyle = textStyle, textColor = textColor, selectorProperties = WheelPickerDefaults.selectorProperties( diff --git a/wheel-picker-compose/src/main/java/com/commandiron/wheel_picker_compose/core/DefaultWheelTimePicker.kt b/wheel-picker-compose/src/main/java/com/commandiron/wheel_picker_compose/core/DefaultWheelTimePicker.kt index 7ac3dd1..672f6e0 100644 --- a/wheel-picker-compose/src/main/java/com/commandiron/wheel_picker_compose/core/DefaultWheelTimePicker.kt +++ b/wheel-picker-compose/src/main/java/com/commandiron/wheel_picker_compose/core/DefaultWheelTimePicker.kt @@ -17,12 +17,14 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import java.time.LocalTime +import java.time.temporal.ChronoUnit @RequiresApi(Build.VERSION_CODES.O) @Composable internal fun DefaultWheelTimePicker( modifier: Modifier = Modifier, startTime: LocalTime = LocalTime.now(), + timeFormat: TimeFormat = TimeFormat.HOUR_24, backwardsDisabled: Boolean = false, size: DpSize = DpSize(128.dp, 128.dp), textStyle: TextStyle = MaterialTheme.typography.titleMedium, @@ -30,16 +32,55 @@ internal fun DefaultWheelTimePicker( selectorProperties: SelectorProperties = WheelPickerDefaults.selectorProperties(), onSnappedTime : (snappedTime: SnappedTime) -> Int? = { _ -> null }, ) { - val hourTexts: List = (0..23).map { it.toString().padStart(2, '0') } - val minuteTexts: List = (0..59).map { it.toString().padStart(2, '0') } - var snappedTime by remember { mutableStateOf(startTime) } + var snappedTime by remember { mutableStateOf(startTime.truncatedTo(ChronoUnit.MINUTES)) } + + val hours = (0..23).map { + Hour( + text = it.toString().padStart(2, '0'), + value = it, + index = it + ) + } + val amPmHours = (1..12).map { + AmPmHour( + text = it.toString(), + value = it, + index = it - 1 + ) + } + + val minutes = (0..59).map { + Minute( + text = it.toString().padStart(2, '0'), + value = it, + index = it + ) + } + + val amPms = listOf( + AmPm( + text = "AM", + value = AmPmValue.AM, + index = 0 + ), + AmPm( + text = "PM", + value = AmPmValue.PM, + index = 1 + ) + ) + + var snappedAmPm by remember { + mutableStateOf( + amPms.find { it.value == amPmValueFromTime(startTime) } ?: amPms[0] + ) + } Box(modifier = modifier, contentAlignment = Alignment.Center){ if(selectorProperties.enabled().value){ Surface( - modifier = Modifier - .size(size.width, size.height / 3), + modifier = Modifier.size(size.width,size.height / 3), shape = selectorProperties.shape().value, color = selectorProperties.color().value, border = selectorProperties.border().value @@ -48,77 +89,195 @@ internal fun DefaultWheelTimePicker( Row { //Hour WheelTextPicker( - size = DpSize(size.width / 2, size.height), - texts = hourTexts, + size = DpSize( + width = size.width / if(timeFormat == TimeFormat.HOUR_24) 2 else 3, + height = size.height + ), + texts = if(timeFormat == TimeFormat.HOUR_24) hours.map { it.text } else amPmHours.map { it.text }, style = textStyle, color = textColor, - startIndex = startTime.hour, + startIndex = if(timeFormat == TimeFormat.HOUR_24) { + hours.find { it.value == startTime.hour }?.index ?: 0 + } else amPmHours.find { it.value == hourToAmPmHour(startTime.hour) }?.index ?: 0, selectorProperties = WheelPickerDefaults.selectorProperties( enabled = false ), onScrollFinished = { snappedIndex -> - try { - val newTime = snappedTime.withHour(snappedIndex) + val newHour = if(timeFormat == TimeFormat.HOUR_24) { + hours.find { it.index == snappedIndex }?.value + } else { + amPmHourToHour( + amPmHours.find { it.index == snappedIndex }?.value ?: 0, + snappedAmPm.value + ) + } + + newHour?.let { + + val newTime = snappedTime.withHour(newHour) + val isTimeBefore = isTimeBefore(newTime, startTime) - if(backwardsDisabled){ - if(!isTimeBefore){ + if (backwardsDisabled) { + if (!isTimeBefore) { snappedTime = newTime } - }else{ + } else { snappedTime = newTime } - onSnappedTime( - SnappedTime.Hour(snappedTime, snappedTime.hour) - )?.let { return@WheelTextPicker it } + val newIndex = if(timeFormat == TimeFormat.HOUR_24) { + hours.find { it.value == snappedTime.hour }?.index + } else { + amPmHours.find { it.value == hourToAmPmHour(snappedTime.hour)}?.index + } - }catch (e: Exception){ - e.printStackTrace() + newIndex?.let { + onSnappedTime( + SnappedTime.Hour( + localTime = snappedTime, + index = newIndex + ) + )?.let { return@WheelTextPicker it } + } } - return@WheelTextPicker snappedTime.hour + return@WheelTextPicker if(timeFormat == TimeFormat.HOUR_24) { + hours.find { it.value == snappedTime.hour }?.index + } else { + amPmHours.find { it.value == hourToAmPmHour(snappedTime.hour)}?.index + } } ) //Minute WheelTextPicker( - size = DpSize(size.width / 2, size.height), - texts = minuteTexts, + size = DpSize( + width = size.width / if(timeFormat == TimeFormat.HOUR_24) 2 else 3, + height = size.height + ), + texts = minutes.map { it.text }, style = textStyle, color = textColor, - startIndex = startTime.minute, + startIndex = minutes.find { it.value == startTime.minute }?.index ?: 0, selectorProperties = WheelPickerDefaults.selectorProperties( enabled = false ), onScrollFinished = { snappedIndex -> - try { - val newTime = snappedTime.withMinute(snappedIndex) - val isTimeBefore = isTimeBefore(newTime, startTime) + val newMinute = minutes.find { it.index == snappedIndex }?.value + + val newHour = if(timeFormat == TimeFormat.HOUR_24) { + hours.find { it.value == snappedTime.hour }?.value + } else { + amPmHourToHour( + amPmHours.find { it.value == hourToAmPmHour(snappedTime.hour) }?.value ?: 0, + snappedAmPm.value + ) + } - if(backwardsDisabled){ - if(!isTimeBefore){ + newMinute?.let { + newHour?.let { + val newTime = snappedTime.withMinute(newMinute).withHour(newHour) + + val isTimeBefore = isTimeBefore(newTime, startTime) + + if(backwardsDisabled){ + if(!isTimeBefore){ + snappedTime = newTime + } + }else{ snappedTime = newTime } - }else{ - snappedTime = newTime - } - onSnappedTime( - SnappedTime.Minute(snappedTime, snappedTime.minute) - )?.let { return@WheelTextPicker it } + val newIndex = minutes.find { it.value == snappedTime.minute }?.index - }catch (e: Exception){ - e.printStackTrace() + newIndex?.let { + onSnappedTime( + SnappedTime.Minute( + localTime = snappedTime, + index = newIndex + ) + )?.let { return@WheelTextPicker it } + } + } } - return@WheelTextPicker snappedTime.minute + return@WheelTextPicker minutes.find { it.value == snappedTime.minute }?.index } ) + //AM_PM + if(timeFormat == TimeFormat.AM_PM) { + WheelTextPicker( + size = DpSize( + width = size.width / 3, + height = size.height + ), + texts = amPms.map { it.text }, + style = textStyle, + color = textColor, + startIndex = amPms.find { it.value == amPmValueFromTime(startTime) }?.index ?:0, + selectorProperties = WheelPickerDefaults.selectorProperties( + enabled = false + ), + onScrollFinished = { snappedIndex -> + + val newAmPm = amPms.find { it.index == snappedIndex } + + newAmPm?.let { + snappedAmPm = newAmPm + } + + val newMinute = minutes.find { it.value == snappedTime.minute }?.value + + val newHour = amPmHourToHour( + amPmHours.find { it.value == hourToAmPmHour(snappedTime.hour) }?.value ?: 0, + snappedAmPm.value + ) + + newMinute?.let { + val newTime = snappedTime.withMinute(newMinute).withHour(newHour) + + val isTimeBefore = isTimeBefore(newTime, startTime) + + if(backwardsDisabled){ + if(!isTimeBefore){ + snappedTime = newTime + } + }else{ + snappedTime = newTime + } + + val newIndex = minutes.find { it.value == snappedTime.hour }?.index + + newIndex?.let { + onSnappedTime( + SnappedTime.Hour( + localTime = snappedTime, + index = newIndex + ) + ) + } + } + + return@WheelTextPicker snappedIndex + } + ) + } } Box( - modifier = Modifier.size(size), + modifier = Modifier + .size( + width = if (timeFormat == TimeFormat.HOUR_24) { + size.width + } else size.width * 2 / 3, + height = size.height / 3 + ) + .align( + alignment = if (timeFormat == TimeFormat.HOUR_24) { + Alignment.Center + } else Alignment.CenterStart + ), contentAlignment = Alignment.Center ) { Text( @@ -135,6 +294,67 @@ private fun isTimeBefore(time: LocalTime, currentTime: LocalTime): Boolean{ return time.isBefore(currentTime) } +enum class TimeFormat { + HOUR_24, AM_PM +} + +private data class Hour( + val text: String, + val value: Int, + val index: Int +) +private data class AmPmHour( + val text: String, + val value: Int, + val index: Int +) + +private fun hourToAmPmHour(hour: Int): Int { + if(hour == 0) { + return 12 + } + if(hour <= 12) { + return hour + } + return hour - 12 +} + +private fun amPmHourToHour(amPmHour: Int, amPmValue: AmPmValue): Int { + return when(amPmValue) { + AmPmValue.AM -> { + amPmHour + } + AmPmValue.PM -> { + if(amPmHour == 12) { + 0 + } else { + amPmHour + 12 + } + } + } +} + +private data class Minute( + val text: String, + val value: Int, + val index: Int +) + +private data class AmPm( + val text: String, + val value: AmPmValue, + val index: Int? +) + +internal enum class AmPmValue { + AM, PM +} + +@RequiresApi(Build.VERSION_CODES.O) +private fun amPmValueFromTime(time: LocalTime): AmPmValue { + return if(time.hour > 11) AmPmValue.PM else AmPmValue.AM +} +