Skip to content
165 changes: 68 additions & 97 deletions timepicker/src/main/java/com/dongchyeon/timepicker/TimePicker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
Expand All @@ -23,9 +22,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.dongchyeon.timepicker.model.PickerState
import com.dongchyeon.timepicker.model.rememberPickerState
Expand All @@ -39,35 +36,33 @@ import kotlinx.datetime.toLocalDateTime
@Composable
fun TimePicker(
modifier: Modifier = Modifier,
itemSpacing: Dp = 2.dp,
visibleItemsCount: Int = TimePickerDefaults.visibleItemsCount,
itemLabel: ItemLabel = TimePickerDefaults.itemLabel(),
initialTime: LocalTime = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).time,
visibleItemsCount: Int = TimePickerDefaults.visibleItemsCount,
timeFormat: TimeFormat = TimePickerDefaults.timeFormat,
style: PickerStyle = TimePickerDefaults.pickerStyle(),
selector: PickerSelector = TimePickerDefaults.pickerSelector(),
curveEffect: CurveEffect = TimePickerDefaults.curveEffect(),
onValueChange: (LocalTime) -> Unit
) {
if (timeFormat.is24Hour) {
TimePicker24Hour(
modifier = modifier,
itemSpacing = itemSpacing,
visibleItemsCount = visibleItemsCount,
textStyle = itemLabel.style,
textColor = itemLabel.color,
initialTime = initialTime,
style = style,
selector = selector,
curveEffect = curveEffect,
onValueChange = onValueChange
)
} else {
TimePicker12Hour(
modifier = modifier,
itemSpacing = itemSpacing,
visibleItemsCount = visibleItemsCount,
textStyle = itemLabel.style,
textColor = itemLabel.color,
initialTime = initialTime,
localeTimeFormat = timeFormat.localeTimeFormat,
style = style,
selector = selector,
curveEffect = curveEffect,
onValueChange = onValueChange
)
}
Expand All @@ -76,15 +71,39 @@ fun TimePicker(
@Composable
private fun TimePicker12Hour(
modifier: Modifier = Modifier,
itemSpacing: Dp = 2.dp,
visibleItemsCount: Int = 5,
textStyle: TextStyle = MaterialTheme.typography.bodyLarge,
textColor: Color = Color.White,
initialTime: LocalTime = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).time,
initialTime: LocalTime,
visibleItemsCount: Int,
localeTimeFormat: LocaleTimeFormat,
style: PickerStyle,
selector: PickerSelector,
curveEffect: CurveEffect,
onValueChange: (LocalTime) -> Unit
) {
val amPmItems = remember {
listOf(
TimePeriod.AM.getLabel(localeTimeFormat),
TimePeriod.PM.getLabel(localeTimeFormat)
)
}
val hourItems = remember { (1..12).toList() }
val minuteItems = remember { (0..59).toList() }

val amPmPickerState = rememberPickerState(
initialIndex = if (initialTime.hour < 12) 0 else 1,
items = amPmItems
)
val hourPickerState = rememberPickerState(
initialIndex = hourItems.indexOf(if (initialTime.hour % 12 == 0) 12 else initialTime.hour % 12),
items = hourItems
)
val minutePickerState = rememberPickerState(
initialIndex = minuteItems.indexOf(initialTime.minute),
items = minuteItems
)

var previousHour by remember { mutableIntStateOf(initialTime.hour) }
val scope = rememberCoroutineScope()

Box(modifier = modifier.fillMaxWidth()) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
Expand All @@ -97,32 +116,6 @@ private fun TimePicker12Hour(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Bottom
) {
val amPmItems = remember {
listOf(
TimePeriod.AM.getLabel(localeTimeFormat),
TimePeriod.PM.getLabel(localeTimeFormat)
)
}
val hourItems = remember { (1..12).toList() }
val minuteItems = remember { (0..59).toList() }

val amPmPickerState = rememberPickerState(
initialIndex = if (initialTime.hour < 12) 0 else 1,
items = amPmItems
)
val hourPickerState = rememberPickerState(
initialIndex = hourItems.indexOf(if (initialTime.hour % 12 == 0) 12 else initialTime.hour % 12),
items = hourItems
)
val minutePickerState = rememberPickerState(
initialIndex = minuteItems.indexOf(initialTime.minute),
items = minuteItems
)

var previousHour by remember { mutableIntStateOf(initialTime.hour) }

val scope = rememberCoroutineScope()

Box(
modifier = Modifier.fillMaxWidth()
) {
Expand All @@ -131,7 +124,7 @@ private fun TimePicker12Hour(
.fillMaxWidth()
.align(Alignment.Center)
.padding(horizontal = 20.dp)
.height(with(LocalDensity.current) { textStyle.lineHeight.toDp() } + 20.dp)
.height(with(LocalDensity.current) { style.textStyle.lineHeight.toDp() } + 20.dp)
.background(
color = selector.color,
shape = selector.shape
Expand All @@ -155,15 +148,14 @@ private fun TimePicker12Hour(
verticalAlignment = Alignment.CenterVertically
) {
PickerItem(
state = amPmPickerState,
items = amPmItems,
state = amPmPickerState,
visibleItemsCount = 3,
itemSpacing = itemSpacing,
textStyle = textStyle,
textColor = textColor,
style = style,
modifier = Modifier.weight(1f),
textModifier = Modifier.padding(8.dp),
infiniteScroll = false,
curveEffect = curveEffect,
onValueChange = {
onPickerValueChange(
amPmPickerState,
Expand All @@ -176,15 +168,14 @@ private fun TimePicker12Hour(
)

PickerItem(
state = hourPickerState,
items = hourItems,
state = hourPickerState,
visibleItemsCount = visibleItemsCount,
itemSpacing = itemSpacing,
textStyle = textStyle,
textColor = textColor,
style = style,
modifier = Modifier.weight(1f),
textModifier = Modifier.padding(8.dp),
infiniteScroll = true,
curveEffect = curveEffect,
onValueChange = {
onPickerValueChange(
amPmPickerState,
Expand All @@ -193,38 +184,31 @@ private fun TimePicker12Hour(
localeTimeFormat,
onValueChange
)

scope.launch {
val currentHour = hourPickerState.selectedItem
val currentIndex = amPmPickerState.lazyListState.firstVisibleItemIndex % amPmItems.size
val nextIndex = (currentIndex + 1) % amPmItems.size

if (currentHour == 12 && previousHour == 11) {
val currentIndex = amPmPickerState.lazyListState.firstVisibleItemIndex % amPmItems.size
val nextIndex = (currentIndex + 1) % amPmItems.size
amPmPickerState.lazyListState.animateScrollToItem(nextIndex)
} else if (currentHour == 11 && previousHour == 12) {
val currentIndex = amPmPickerState.lazyListState.firstVisibleItemIndex % amPmItems.size
val nextIndex = (currentIndex + 1) % amPmItems.size
if ((currentHour == 12 && previousHour == 11) ||
(currentHour == 11 && previousHour == 12)
) {
amPmPickerState.lazyListState.animateScrollToItem(nextIndex)
}

previousHour = currentHour
}
}
)

PickerItem(
state = minutePickerState,
items = minuteItems,
state = minutePickerState,
visibleItemsCount = visibleItemsCount,
itemSpacing = itemSpacing,
textStyle = textStyle,
textColor = textColor,
style = style,
modifier = Modifier.weight(1f),
textModifier = Modifier.padding(8.dp),
infiniteScroll = true,
itemFormatter = { item ->
item.toString().padStart(2, '0')
},
itemFormatter = { it.toString().padStart(2, '0') },
curveEffect = curveEffect,
onValueChange = {
onPickerValueChange(
amPmPickerState,
Expand All @@ -246,12 +230,11 @@ private fun TimePicker12Hour(
@Composable
private fun TimePicker24Hour(
modifier: Modifier = Modifier,
itemSpacing: Dp = 2.dp,
visibleItemsCount: Int = 5,
textStyle: TextStyle = MaterialTheme.typography.bodyLarge,
textColor: Color = Color.White,
initialTime: LocalTime = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).time,
initialTime: LocalTime,
visibleItemsCount: Int,
style: PickerStyle,
selector: PickerSelector,
curveEffect: CurveEffect,
onValueChange: (LocalTime) -> Unit
) {
val hourItems = remember { (0..23).toList() }
Expand Down Expand Up @@ -286,7 +269,7 @@ private fun TimePicker24Hour(
.fillMaxWidth()
.align(Alignment.Center)
.padding(horizontal = 20.dp)
.height(with(LocalDensity.current) { textStyle.lineHeight.toDp() } + 20.dp)
.height(with(LocalDensity.current) { style.textStyle.lineHeight.toDp() } + 20.dp)
.background(
color = selector.color,
shape = selector.shape
Expand All @@ -310,49 +293,37 @@ private fun TimePicker24Hour(
verticalAlignment = Alignment.CenterVertically
) {
PickerItem(
state = hourPickerState,
items = hourItems,
state = hourPickerState,
visibleItemsCount = visibleItemsCount,
itemSpacing = itemSpacing,
textStyle = textStyle,
textColor = textColor,
style = style,
modifier = Modifier.weight(1f),
textModifier = Modifier.padding(8.dp),
infiniteScroll = true,
curveEffect = curveEffect,
onValueChange = {
onPickerValueChange(
hourPickerState,
minutePickerState,
onValueChange
)
onPickerValueChange(hourPickerState, minutePickerState, onValueChange)
}
)

Text(
text = ":",
style = textStyle,
color = textColor
style = style.textStyle,
color = style.textColor
)

PickerItem(
state = minutePickerState,
items = minuteItems,
state = minutePickerState,
visibleItemsCount = visibleItemsCount,
itemSpacing = itemSpacing,
textStyle = textStyle,
textColor = textColor,
style = style,
modifier = Modifier.weight(1f),
textModifier = Modifier.padding(8.dp),
infiniteScroll = true,
itemFormatter = { item ->
item.toString().padStart(2, '0')
},
itemFormatter = { it.toString().padStart(2, '0') },
curveEffect = curveEffect,
onValueChange = {
onPickerValueChange(
hourPickerState,
minutePickerState,
onValueChange
)
onPickerValueChange(hourPickerState, minutePickerState, onValueChange)
}
)
}
Expand Down
Loading