Skip to content

Commit 12a890a

Browse files
committed
Feat: Implement Date Range Picker and Popup Functionality
This commit introduces a new `PersianDateRangePicker` composable and related state management, along with a reusable `PersianDatePickerPopup` component. It also includes refactoring of existing date picker components into a `date_picker` subpackage and `share` subpackage for common elements. **Key Changes:** - **`PersianDateRangePicker` Implementation:** - Added `PersianDateRangePicker.kt` with the main composable for selecting a date range. - Introduced `PersianDateRangePickerState.kt` to manage the state of the date range picker, including selected start and end dates, displayed month, year range, and display mode. - Added `PersianDateRangePickerDefaults.kt` providing default composables for the title and headline of the date range picker. - New string resources for date range picker headlines (`dateRangeStartHeadline`, `dateRangeEndHeadline`) added to `strings.xml`. - **`PersianDatePickerPopup` Implementation:** - Created `PersianDatePickerPopup.kt` which provides a generic popup mechanism that can anchor to a composable and display content like a date picker. - It allows customization of shape, colors, offset, and alignment. - The popup position is calculated to stay within window bounds. - **Code Refactoring and Organization:** - Existing single date picker components (`PersianDatePicker.kt`, `PersianDatePickerState.kt`, `PersianDatePickerDefaults.kt`, and internal calendar views) have been moved into a new `io.github.faridsolgi.date_picker.view` package. - Shared components like `PersianDatePickerDialog.kt`, `DisplayModeToggleButton.kt`, and `ProvideContentColorTextStyle.kt` have been moved into a new `io.github.faridsolgi.share` package. - Imports have been updated across the codebase to reflect these changes. - **`PersianDatePickerState` Enhancements:** - `PersianDatePickerStateImpl` now correctly updates `initDisplayedDate` to the `selectedDate` when `displayMode` changes. - Updated `Saver` logic in `PersianDatePickerStateImpl` to use `toEpochMilliseconds()` and `PersianDateTime.parse()` for saving and restoring dates. - **UI and Token Updates:** - Added `ContainerHeightMax` and `ContainerWidthMax` to `PersianDatePickerTokens`. - Added `PopupOffsetY` to `PersianDatePickerDefaults`. - The `PersianDatePicker` composable no longer has a default background color; this is now handled by the container (e.g., `PersianDatePickerDialog` or `PersianDatePickerPopup`). - Added a preview example in `PersianDatePicker.kt` demonstrating how to use `PersianDatePicker` within a `Popup` triggered by an `OutlinedTextField`. - **Dependency Updates:** - Updated `persianDateTime` dependency from `0.1.0` to `0.2.2` in `gradle/libs.versions.toml`. - **Version Update:** - Library version updated from `0.0.12-beta2` to `0.0.13-beta1` in `library/build.gradle.kts`. These changes provide new date range selection capabilities, improve code organization, and offer more flexibility for displaying date pickers within different UI contexts.
1 parent 2d5971b commit 12a890a

17 files changed

+803
-75
lines changed

gradle/libs.versions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ android-compileSdk = "36"
66
vanniktechMavenPublish = "0.34.0"
77
compose-multiplatform = "1.9.0"
88
kotlinxDatetime = "0.7.1-0.6.x-compat"
9-
persianDateTime = "0.1.0"
9+
persianDateTime = "0.2.2"
1010
uiTooling = "1.9.2"
1111

1212
[libraries]

library/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ plugins {
1616
}
1717

1818
group = "io.github.faridsolgi"
19-
version = "0.0.12-beta2"
19+
version = "0.0.13-beta1"
2020

2121
kotlin {
2222
jvm()

library/src/commonMain/composeResources/values/strings.xml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
<resources>
33
<string name="datePickerTitle">انتخاب تاریخ</string>
44
<string name="dateInputTitle">انتخاب تاریخ</string>
5-
<string name="DatePickerHeadline">انتخاب تاریخ</string>
6-
<string name="DateInputHeadline">وارد کردن تاریخ</string>
5+
<string name="datePickerHeadline">انتخاب تاریخ</string>
6+
<string name="dateInputHeadline">وارد کردن تاریخ</string>
7+
<string name="dateRangeStartHeadline">تاریخ شروع</string>
8+
<string name="dateRangeEndHeadline">تاریخ پایان</string>
79
<string name="date">تاریخ</string>
810
<string name="dateHint">سال/ماه/روز</string>
911
<string name="datePickerSwitchToInputMode">Switch to input mode</string>

library/src/commonMain/kotlin/io/github/faridsolgi/view/PersianDatePicker.kt renamed to library/src/commonMain/kotlin/io/github/faridsolgi/date_picker/view/PersianDatePicker.kt

Lines changed: 75 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package io.github.faridsolgi.view
1+
package io.github.faridsolgi.date_picker.view
22

33

44
import androidx.compose.animation.AnimatedContent
@@ -10,24 +10,26 @@ import androidx.compose.animation.slideOutHorizontally
1010
import androidx.compose.animation.togetherWith
1111
import androidx.compose.foundation.background
1212
import androidx.compose.foundation.layout.Arrangement
13+
import androidx.compose.foundation.layout.Box
1314
import androidx.compose.foundation.layout.Column
1415
import androidx.compose.foundation.layout.PaddingValues
1516
import androidx.compose.foundation.layout.Row
1617
import androidx.compose.foundation.layout.Spacer
1718
import androidx.compose.foundation.layout.fillMaxWidth
19+
import androidx.compose.foundation.layout.height
20+
import androidx.compose.foundation.layout.offset
1821
import androidx.compose.foundation.layout.padding
1922
import androidx.compose.foundation.layout.sizeIn
20-
import androidx.compose.material3.DatePicker
21-
import androidx.compose.material3.DatePickerDefaults
22-
import androidx.compose.material3.DatePickerFormatter
23+
import androidx.compose.material.icons.Icons
24+
import androidx.compose.material.icons.filled.DateRange
2325
import androidx.compose.material3.ExperimentalMaterial3Api
2426
import androidx.compose.material3.HorizontalDivider
27+
import androidx.compose.material3.Icon
28+
import androidx.compose.material3.IconButton
2529
import androidx.compose.material3.LocalTextStyle
2630
import androidx.compose.material3.MaterialTheme
2731
import androidx.compose.material3.OutlinedTextField
2832
import androidx.compose.material3.Text
29-
import androidx.compose.material3.TextButton
30-
import androidx.compose.material3.rememberDatePickerState
3133
import androidx.compose.runtime.Composable
3234
import androidx.compose.runtime.CompositionLocalProvider
3335
import androidx.compose.runtime.LaunchedEffect
@@ -37,14 +39,12 @@ import androidx.compose.runtime.remember
3739
import androidx.compose.runtime.setValue
3840
import androidx.compose.ui.Alignment
3941
import androidx.compose.ui.Modifier
42+
import androidx.compose.ui.draw.shadow
4043
import androidx.compose.ui.platform.LocalLayoutDirection
41-
import androidx.compose.ui.text.AnnotatedString
42-
import androidx.compose.ui.text.input.OffsetMapping
43-
import androidx.compose.ui.text.input.TransformedText
44-
import androidx.compose.ui.text.input.VisualTransformation
4544
import androidx.compose.ui.text.style.TextAlign
4645
import androidx.compose.ui.unit.LayoutDirection
4746
import androidx.compose.ui.unit.dp
47+
import androidx.compose.ui.window.Popup
4848
import io.github.faridsolgi.domain.model.DisplayMode
4949
import io.github.faridsolgi.domain.model.PersianDatePickerColors
5050
import io.github.faridsolgi.domain.model.PersianDatePickerTokens
@@ -53,14 +53,13 @@ import io.github.faridsolgi.library.generated.resources.date
5353
import io.github.faridsolgi.library.generated.resources.dateHint
5454
import io.github.faridsolgi.library.generated.resources.error_pattern_not_valid
5555
import io.github.faridsolgi.library.generated.resources.error_year_not_valid_range
56-
import io.github.faridsolgi.persiandatetime.converter.format
57-
import io.github.faridsolgi.persiandatetime.converter.toDateString
5856
import io.github.faridsolgi.persiandatetime.domain.PersianDateTime
57+
import io.github.faridsolgi.persiandatetime.extensions.format
58+
import io.github.faridsolgi.persiandatetime.extensions.toDateString
5959
import io.github.faridsolgi.util.DateVisualTransformation
60-
import io.github.faridsolgi.view.internal.DisplayModeToggleButton
61-
import io.github.faridsolgi.view.internal.PersianDatePickerCalendar
62-
import io.github.faridsolgi.view.internal.ProvideContentColorTextStyle
63-
import kotlinx.datetime.toLocalDateTime
60+
import io.github.faridsolgi.share.internal.DisplayModeToggleButton
61+
import io.github.faridsolgi.date_picker.view.internal.PersianDatePickerCalendar
62+
import io.github.faridsolgi.share.internal.ProvideContentColorTextStyle
6463
import org.jetbrains.compose.resources.stringResource
6564
import org.jetbrains.compose.ui.tooling.preview.Preview
6665

@@ -86,6 +85,7 @@ fun PersianDatePicker(
8685
showModeToggle: Boolean = true,
8786
colors: PersianDatePickerColors = PersianDatePickerDefaults.colors(),
8887
) {
88+
8989
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
9090
Column(modifier) {
9191
PersianDatePickerHeadLine(
@@ -200,29 +200,71 @@ internal fun PersianDateEnterSection(
200200

201201
@OptIn(ExperimentalMaterial3Api::class)
202202
@Composable
203-
@Preview
203+
@Preview(heightDp = 480, widthDp = 720)
204204
private fun PersianDatePickerPreview() {
205205
MaterialTheme {
206-
val state = rememberPersianDatePickerState(yearRange = 1400..1500)
206+
/* val state = rememberPersianDatePickerState(yearRange = 1400..1500)
207207
208-
PersianDatePickerDialog(
209-
dismissButton = {
210-
TextButton({}) {
211-
Text("لغو")
212-
}
213-
},
214-
confirmButton = {
215-
TextButton({}) {
216-
Text("تایید")
217-
}
218-
},
219-
onDismissRequest = {
208+
PersianDatePickerDialog(
209+
dismissButton = {
210+
TextButton({}) {
211+
Text("لغو")
212+
}
213+
},
214+
confirmButton = {
215+
TextButton({}) {
216+
Text("تایید")
217+
}
218+
},
219+
onDismissRequest = {
220220
221-
},
221+
},
222+
) {
223+
PersianDatePicker(
224+
state = state
225+
)
226+
}*/
227+
228+
var showDatePicker by remember { mutableStateOf(false) }
229+
val datePickerState = rememberPersianDatePickerState()
230+
val selectedDate = datePickerState.selectedDate?.let {
231+
it.toDateString()
232+
} ?: ""
233+
234+
Box(
235+
modifier = Modifier.fillMaxWidth()
222236
) {
223-
PersianDatePicker(
224-
state = state
237+
OutlinedTextField(
238+
value = selectedDate,
239+
onValueChange = { },
240+
label = { Text("DOB") },
241+
readOnly = true,
242+
trailingIcon = {
243+
IconButton(onClick = { showDatePicker = !showDatePicker }) {
244+
Icon(
245+
imageVector = Icons.Default.DateRange,
246+
contentDescription = "Select date"
247+
)
248+
}
249+
},
250+
modifier = Modifier.fillMaxWidth().height(64.dp)
225251
)
252+
253+
if (showDatePicker) {
254+
Popup(
255+
onDismissRequest = { showDatePicker = false }, alignment = Alignment.TopStart
256+
) {
257+
Box(
258+
modifier = Modifier.fillMaxWidth().offset(y = 64.dp)
259+
.shadow(elevation = 4.dp).background(MaterialTheme.colorScheme.surface)
260+
.padding(16.dp)
261+
) {
262+
PersianDatePicker(
263+
state = datePickerState, showModeToggle = false
264+
)
265+
}
266+
}
267+
}
226268
}
227269
}
228270
}
@@ -239,7 +281,6 @@ internal fun PersianDatePickerHeadLine(
239281
Column(
240282
modifier = Modifier
241283
.sizeIn(minWidth = PersianDatePickerTokens.ContainerWidth)
242-
.background(colors.containerColor)
243284
) {
244285
ProvideContentColorTextStyle(
245286
colors.titleColor,

library/src/commonMain/kotlin/io/github/faridsolgi/view/PersianDatePickerDefaults.kt renamed to library/src/commonMain/kotlin/io/github/faridsolgi/date_picker/view/PersianDatePickerDefaults.kt

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package io.github.faridsolgi.view
1+
package io.github.faridsolgi.date_picker.view
22

33
import androidx.compose.material3.ColorScheme
44
import androidx.compose.material3.MaterialTheme
@@ -13,13 +13,13 @@ import androidx.compose.ui.unit.dp
1313
import io.github.faridsolgi.domain.SelectableDates
1414
import io.github.faridsolgi.domain.model.DisplayMode
1515
import io.github.faridsolgi.domain.model.PersianDatePickerColors
16-
import io.github.faridsolgi.library.generated.resources.DateInputHeadline
17-
import io.github.faridsolgi.library.generated.resources.DatePickerHeadline
16+
import io.github.faridsolgi.library.generated.resources.dateInputHeadline
17+
import io.github.faridsolgi.library.generated.resources.datePickerHeadline
1818
import io.github.faridsolgi.library.generated.resources.Res
1919
import io.github.faridsolgi.library.generated.resources.dateInputTitle
2020
import io.github.faridsolgi.library.generated.resources.datePickerTitle
21-
import io.github.faridsolgi.persiandatetime.converter.format
2221
import io.github.faridsolgi.persiandatetime.domain.PersianDateTime
22+
import io.github.faridsolgi.persiandatetime.extensions.format
2323
import org.jetbrains.compose.resources.stringResource
2424
import kotlin.time.ExperimentalTime
2525

@@ -107,8 +107,8 @@ object PersianDatePickerDefaults {
107107
year()
108108
}
109109
?: when (displayMode) {
110-
DisplayMode.Companion.Picker -> stringResource(Res.string.DatePickerHeadline)
111-
DisplayMode.Companion.Input -> stringResource(Res.string.DateInputHeadline)
110+
DisplayMode.Companion.Picker -> stringResource(Res.string.datePickerHeadline)
111+
DisplayMode.Companion.Input -> stringResource(Res.string.dateInputHeadline)
112112
else -> ""
113113
}
114114

@@ -125,4 +125,6 @@ object PersianDatePickerDefaults {
125125
get() = MaterialTheme.shapes.extraLarge
126126
val TonalElevation
127127
get() = 0.dp
128+
129+
val PopupOffsetY = 0.dp
128130
}

library/src/commonMain/kotlin/io/github/faridsolgi/view/PersianDatePickerState.kt renamed to library/src/commonMain/kotlin/io/github/faridsolgi/date_picker/view/PersianDatePickerState.kt

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package io.github.faridsolgi.view
1+
package io.github.faridsolgi.date_picker.view
22

33

44
import androidx.compose.material3.ExperimentalMaterial3Api
@@ -12,13 +12,13 @@ import androidx.compose.runtime.saveable.rememberSaveable
1212
import androidx.compose.runtime.setValue
1313
import io.github.faridsolgi.domain.SelectableDates
1414
import io.github.faridsolgi.domain.model.DisplayMode
15-
import io.github.faridsolgi.view.PersianDatePickerStateImpl.Companion.Saver
16-
import io.github.faridsolgi.persiandatetime.converter.nowPersianDate
17-
import io.github.faridsolgi.persiandatetime.converter.toLocalDate
18-
import io.github.faridsolgi.persiandatetime.converter.toPersianDateTime
15+
import io.github.faridsolgi.date_picker.view.PersianDatePickerStateImpl.Companion.Saver
16+
1917
import io.github.faridsolgi.persiandatetime.domain.PersianDateTime
18+
import io.github.faridsolgi.persiandatetime.extensions.nowPersianDate
19+
import io.github.faridsolgi.persiandatetime.extensions.toEpochMilliseconds
20+
import io.github.faridsolgi.persiandatetime.extensions.toPersianDateTime
2021
import kotlinx.datetime.TimeZone
21-
import kotlinx.datetime.atStartOfDayIn
2222
import kotlin.time.Clock
2323
import kotlin.time.ExperimentalTime
2424
import kotlin.time.Instant
@@ -95,7 +95,7 @@ private class PersianDatePickerStateImpl(
9595
set(value) {
9696
_displayMode.value = value
9797
selectedDate?.let {
98-
initDisplayedDate = it
98+
this@PersianDatePickerStateImpl.initDisplayedDate = it
9999
}
100100
}
101101

@@ -113,11 +113,9 @@ private class PersianDatePickerStateImpl(
113113
listSaver(
114114
save = {
115115
listOf(
116-
it.selectedDate?.toLocalDate()
117-
?.atStartOfDayIn(TimeZone.currentSystemDefault())
116+
it.selectedDate
118117
?.toEpochMilliseconds(),
119-
it.initDisplayedDate.toLocalDate()
120-
.atStartOfDayIn(TimeZone.currentSystemDefault()).toEpochMilliseconds(),
118+
it.initDisplayedDate.toEpochMilliseconds(),
121119
it.yearRange.first,
122120
it.yearRange.last,
123121
it.displayMode.value
@@ -126,14 +124,10 @@ private class PersianDatePickerStateImpl(
126124
restore = { value ->
127125
PersianDatePickerStateImpl(
128126
initialSelectedDate = (value[0] as Long?)?.let {
129-
Instant.fromEpochMilliseconds(it)
130-
.toPersianDateTime(
131-
TimeZone.currentSystemDefault()
132-
)
127+
PersianDateTime.parse(it)
133128
},
134129
initDisplayedDate = (value[1] as Long?)?.let {
135-
Instant.fromEpochMilliseconds(it)
136-
.toPersianDateTime(TimeZone.currentSystemDefault())
130+
PersianDateTime.parse(it)
137131
},
138132
yearRange = IntRange(value[2] as Int, value[3] as Int),
139133
initialDisplayMode = DisplayMode(value[4] as Int),

library/src/commonMain/kotlin/io/github/faridsolgi/view/internal/PersianCalender.kt renamed to library/src/commonMain/kotlin/io/github/faridsolgi/date_picker/view/internal/PersianCalender.kt

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package io.github.faridsolgi.view.internal
1+
package io.github.faridsolgi.date_picker.view.internal
22

33
import androidx.compose.animation.AnimatedContent
44
import androidx.compose.animation.AnimatedVisibility
@@ -56,17 +56,18 @@ import androidx.compose.ui.text.style.TextAlign
5656
import androidx.compose.ui.unit.dp
5757
import io.github.faridsolgi.domain.model.PersianDatePickerColors
5858
import io.github.faridsolgi.domain.model.PersianDatePickerTokens
59-
import io.github.faridsolgi.persiandatetime.converter.format
60-
import io.github.faridsolgi.persiandatetime.converter.monthLength
61-
import io.github.faridsolgi.persiandatetime.converter.nowPersianDate
62-
import io.github.faridsolgi.persiandatetime.converter.persianDayOfWeek
6359
import io.github.faridsolgi.persiandatetime.domain.PersianDateTime
6460
import io.github.faridsolgi.persiandatetime.domain.PersianWeekday
61+
import io.github.faridsolgi.persiandatetime.extensions.format
62+
import io.github.faridsolgi.persiandatetime.extensions.monthLength
63+
import io.github.faridsolgi.persiandatetime.extensions.nowPersianDate
64+
import io.github.faridsolgi.persiandatetime.extensions.persianDayOfWeek
65+
import io.github.faridsolgi.share.internal.ProvideContentColorTextStyle
6566
import io.github.faridsolgi.util.canNavigateToNextMonth
6667
import io.github.faridsolgi.util.canNavigateToPreviousMonth
6768
import io.github.faridsolgi.util.navigateToNextMonth
6869
import io.github.faridsolgi.util.navigateToPreviousMonth
69-
import io.github.faridsolgi.view.PersianDatePickerState
70+
import io.github.faridsolgi.date_picker.view.PersianDatePickerState
7071
import kotlinx.datetime.TimeZone
7172
import kotlin.time.Clock
7273
import kotlin.time.ExperimentalTime
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package io.github.faridsolgi.date_range_picker
2+
3+
import androidx.compose.foundation.layout.PaddingValues
4+
import androidx.compose.foundation.layout.padding
5+
import androidx.compose.material3.DatePickerColors
6+
import androidx.compose.runtime.Composable
7+
import androidx.compose.runtime.Stable
8+
import androidx.compose.runtime.remember
9+
import androidx.compose.ui.Modifier
10+
import androidx.compose.ui.focus.FocusRequester
11+
import androidx.compose.ui.unit.dp
12+
import io.github.faridsolgi.date_picker.view.PersianDatePickerDefaults
13+
import io.github.faridsolgi.domain.model.PersianDatePickerColors
14+
15+
@Composable
16+
internal fun DateRangePicker(
17+
state: PersianDateRangePickerState,
18+
modifier: Modifier = Modifier,
19+
colors: PersianDatePickerColors = PersianDatePickerDefaults.colors(),
20+
title: (@Composable () -> Unit)? = {
21+
PersianDateRangePickerDefaults.DateRangePickerTitle(
22+
displayMode = state.displayMode,
23+
modifier = Modifier.padding(DatePickerTitlePadding)
24+
)
25+
},
26+
headline: (@Composable () -> Unit)? = {
27+
PersianDateRangePickerDefaults.DateRangePickerHeadline(
28+
selectedStartDate = state.selectedStartDate,
29+
selectedEndDate = state.selectedEndDate,
30+
displayMode = state.displayMode,
31+
modifier = Modifier.padding(DatePickerHeadlinePadding)
32+
)
33+
},
34+
showModeToggle: Boolean = true,
35+
focusRequester: FocusRequester? = remember { FocusRequester() },
36+
) {
37+
38+
}
39+
private val DatePickerTitlePadding = PaddingValues(start = 24.dp, end = 12.dp, top = 16.dp)
40+
private val DatePickerHeadlinePadding = PaddingValues(start = 24.dp, end = 12.dp, bottom = 12.dp)
41+

0 commit comments

Comments
 (0)