-
-
Save SatyaSnehith/ee599bb3bc91d0e717c442f72c59f882 to your computer and use it in GitHub Desktop.
Jetpack compose twitter date picker (dob). Jetpack Compose scrollable date picker.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.satyasnehith.wpd | |
import androidx.compose.foundation.ExperimentalFoundationApi | |
import androidx.compose.foundation.layout.PaddingValues | |
import androidx.compose.foundation.layout.Row | |
import androidx.compose.foundation.layout.size | |
import androidx.compose.foundation.layout.wrapContentSize | |
import androidx.compose.foundation.pager.VerticalPager | |
import androidx.compose.foundation.pager.rememberPagerState | |
import androidx.compose.material3.MaterialTheme | |
import androidx.compose.material3.Text | |
import androidx.compose.runtime.Composable | |
import androidx.compose.runtime.LaunchedEffect | |
import androidx.compose.runtime.Stable | |
import androidx.compose.runtime.snapshotFlow | |
import androidx.compose.ui.Alignment | |
import androidx.compose.ui.Modifier | |
import androidx.compose.ui.draw.drawBehind | |
import androidx.compose.ui.draw.drawWithContent | |
import androidx.compose.ui.geometry.Offset | |
import androidx.compose.ui.geometry.Size | |
import androidx.compose.ui.graphics.* | |
import androidx.compose.ui.graphics.drawscope.rotate | |
import androidx.compose.ui.platform.LocalDensity | |
import androidx.compose.ui.text.ExperimentalTextApi | |
import androidx.compose.ui.text.TextStyle | |
import androidx.compose.ui.text.rememberTextMeasurer | |
import androidx.compose.ui.tooling.preview.Preview | |
import androidx.compose.ui.unit.Dp | |
import androidx.compose.ui.unit.DpSize | |
import androidx.compose.ui.unit.dp | |
import kotlinx.coroutines.flow.collectLatest | |
import java.time.LocalDate | |
@OptIn(ExperimentalTextApi::class, ExperimentalFoundationApi::class) | |
@Composable | |
fun DatePicker( | |
modifier: Modifier = Modifier, | |
state: DatePickerState = rememberDatePickerState(), | |
style: TextStyle = MaterialTheme.typography.bodyMedium, | |
onChange: (LocalDate) -> Unit, | |
) { | |
val textMeasurer = rememberTextMeasurer() | |
val textLayoutResult = textMeasurer.measure(text = "99999", style = style) | |
val density = LocalDensity.current | |
val pageSize = with(density) { | |
DpSize( | |
width = textLayoutResult.size.width.toDp() + 32.dp, | |
height = textLayoutResult.size.height.toDp() + 32.dp | |
) | |
} | |
val pagerSize = pageSize * 3 | |
val datesPagerState = rememberPagerState( | |
initialPage = state.initialDateIndex, | |
pageCount = { | |
state.dates.size | |
} | |
) | |
val monthsPagerState = rememberPagerState( | |
initialPage = state.initialMonthIndex, | |
pageCount = { | |
state.months.size | |
} | |
) | |
val yearsPagerState = rememberPagerState( | |
initialPage = state.initialYearIndex, | |
pageCount = { | |
state.years.size | |
} | |
) | |
LaunchedEffect(Unit) { | |
snapshotFlow { yearsPagerState.currentPage }.collectLatest { index -> | |
val year = state.years[index] | |
val date = state.dates[datesPagerState.currentPage].withYear(year) | |
val dateIndex = state.dates.indexOf(date) | |
val monthsIndex = state.months.indexOf(date.month to date.year) | |
datesPagerState.scrollToPage(dateIndex) | |
monthsPagerState.scrollToPage(monthsIndex) | |
} | |
} | |
LaunchedEffect(Unit) { | |
snapshotFlow { monthsPagerState.currentPage }.collectLatest { index -> | |
val (month, year) = state.months[index] | |
val yearIndex = state.years.indexOf(year) | |
val date = state.dates[datesPagerState.currentPage] | |
.withMonth(month.value) | |
.withYear(year) | |
val dateIndex = state.dates.indexOf(date) | |
yearsPagerState.scrollToPage(yearIndex) | |
datesPagerState.scrollToPage(dateIndex) | |
} | |
} | |
LaunchedEffect(Unit) { | |
snapshotFlow { datesPagerState.currentPage }.collectLatest { index -> | |
val date = state.dates[index].apply(onChange) | |
val yearIndex = state.years.indexOf(date.year) | |
val monthIndex = state.months.indexOf(date.month to date.year) | |
yearsPagerState.scrollToPage(yearIndex) | |
monthsPagerState.scrollToPage(monthIndex) | |
} | |
} | |
val indicator = Modifier.indicator( | |
color = MaterialTheme.colorScheme.onSurface, | |
pageHeight = pageSize.height | |
) | |
Row( | |
modifier = modifier | |
.size(pagerSize) | |
.fadeTopAndBottom() | |
) { | |
VerticalPager( | |
contentPadding = PaddingValues(vertical = pageSize.height), | |
state = datesPagerState, | |
modifier = indicator | |
) { index -> | |
val date = state.dates[index] | |
Text( | |
text = "${date.dayOfMonth}", | |
modifier = Modifier | |
.size(pageSize) | |
.wrapContentSize(align = Alignment.Center) | |
) | |
} | |
VerticalPager( | |
contentPadding = PaddingValues(vertical = pageSize.height), | |
state = monthsPagerState, | |
modifier = indicator | |
) { index -> | |
val (month, _) = state.months[index] | |
Text( | |
text = "${month.value}", | |
modifier = Modifier | |
.size(pageSize) | |
.wrapContentSize(align = Alignment.Center) | |
) | |
} | |
VerticalPager( | |
contentPadding = PaddingValues(vertical = pageSize.height), | |
state = yearsPagerState, | |
modifier = indicator | |
) { index -> | |
val year = state.years[index] | |
Text( | |
text = "$year", | |
modifier = Modifier | |
.size(pageSize) | |
.wrapContentSize(align = Alignment.Center) | |
) | |
} | |
} | |
} | |
@Preview(showSystemUi = false, showBackground = true) | |
@Composable | |
private fun DatePickerPreview() { | |
DatePicker( | |
onChange = { | |
} | |
) | |
} | |
@Stable | |
class DatePickerState( | |
currentDate: LocalDate, | |
startDate: LocalDate, | |
endDate: LocalDate | |
) { | |
val years = (startDate.year..endDate.year).toList() | |
val dates = (startDate.toEpochDay()..endDate.toEpochDay()).map(LocalDate::ofEpochDay) | |
val months = dates | |
.groupBy { it.month to it.year } | |
.toList() | |
.map { it.first } | |
.sortedBy { it.second } | |
val initialYearIndex = years.indexOf(currentDate.year) | |
val initialMonthIndex = months.indexOf(currentDate.month to currentDate.year) | |
val initialDateIndex = dates.indexOf(currentDate) | |
} | |
@Composable | |
fun rememberDatePickerState( | |
currentDate: LocalDate = LocalDate.now(), | |
startDate: LocalDate = LocalDate.now().minusYears(100), | |
endDate: LocalDate = LocalDate.now().plusYears(100) | |
): DatePickerState { | |
return DatePickerState(currentDate, startDate, endDate) | |
} | |
private fun Modifier.indicator( | |
color: Color, | |
strokeWidth: Dp = 2.dp, | |
pageHeight: Dp | |
): Modifier { | |
return this.drawBehind { | |
drawLine( | |
color = color, | |
strokeWidth = strokeWidth.toPx(), | |
start = Offset( | |
x = 16.dp.toPx(), | |
y = pageHeight.toPx() | |
), | |
end = Offset( | |
x = size.width - 16.dp.toPx(), | |
y = pageHeight.toPx() | |
), | |
cap = StrokeCap.Round | |
) | |
drawLine( | |
color = color, | |
strokeWidth = strokeWidth.toPx(), | |
start = Offset( | |
x = 16.dp.toPx(), | |
y = pageHeight.toPx() * 2 - strokeWidth.toPx() | |
), | |
end = Offset( | |
x = size.width - 16.dp.toPx(), | |
y = pageHeight.toPx() * 2 - strokeWidth.toPx() | |
), | |
cap = StrokeCap.Round | |
) | |
} | |
} | |
private fun Modifier.fadeTopAndBottom(): Modifier { | |
return this | |
.graphicsLayer { alpha = 0.99f } | |
.drawWithContent { | |
drawContent() | |
drawRect( | |
brush = Brush.verticalGradient( | |
colors = listOf(Color.Transparent, Color.Black, Color.Black), | |
), | |
blendMode = BlendMode.DstIn, | |
topLeft = Offset.Zero, | |
size = Size( | |
width = size.width, | |
height = size.height / 3 | |
) | |
) | |
this.rotate(180f) { | |
drawRect( | |
brush = Brush.verticalGradient( | |
colors = listOf(Color.Transparent, Color.Black, Color.Black), | |
), | |
blendMode = BlendMode.DstIn, | |
topLeft = Offset.Zero, | |
size = Size( | |
width = size.width, | |
height = size.height / 3 | |
) | |
) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment