Created
March 22, 2023 18:16
-
-
Save bagus2x/80febed92af716a7614ccd4495daaddc 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 bagus2x.sosmed.presentation.common.components | |
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.material.MaterialTheme | |
import androidx.compose.material.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.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.body1, | |
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 | |
) | |
val monthsPagerState = rememberPagerState( | |
initialPage = state.initialMonthIndex | |
) | |
val yearsPagerState = rememberPagerState( | |
initialPage = state.initialYearIndex | |
) | |
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.colors.onSurface, | |
pageHeight = pageSize.height | |
) | |
Row( | |
modifier = modifier | |
.size(pagerSize) | |
.fadeTopAndBottom() | |
) { | |
VerticalPager( | |
pageCount = state.dates.size, | |
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( | |
pageCount = state.months.size, | |
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( | |
pageCount = state.years.size, | |
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) | |
) | |
} | |
} | |
} | |
@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