@momo-kits/native-kits 0.114.2-beta.1 → 0.114.2-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/compose/build.gradle.kts +1 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +28 -30
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +29 -9
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +3 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/dateTimePicker/DateTimePicker.kt +178 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/dateTimePicker/DateTimePickerExample.kt +90 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/dateTimePicker/DateTimePickerTypes.kt +15 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/dateTimePicker/DateTimePickerUtils.kt +226 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/dateTimePicker/WheelPicker.kt +201 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +83 -123
- package/ios/Application/ApplicationEnvironment.swift +17 -9
- package/ios/Application/HeaderRight.swift +29 -30
- package/ios/Template/TrustBanner/TrustBanner.swift +75 -144
- package/local.properties +2 -2
- package/package.json +1 -1
package/compose/build.gradle.kts
CHANGED
|
@@ -88,8 +88,32 @@ fun HeaderRight(
|
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
private fun getNavigationButtonConfig(
|
|
92
|
+
headerRight: HeaderRightData? = null,
|
|
93
|
+
isFavorite: Boolean = false,
|
|
94
|
+
onPressShortcut: () -> Unit,
|
|
95
|
+
onPressMore: () -> Unit
|
|
96
|
+
): NavigationButtonConfig {
|
|
97
|
+
val totalTools = headerRight?.tools?.sumOf { it.items.size } ?: 0
|
|
98
|
+
var icon = if (isFavorite) "pin_star_checked" else "pin_star"
|
|
99
|
+
var onClickHandler = onPressShortcut
|
|
100
|
+
if (totalTools > 1 || headerRight?.useMore == true) {
|
|
101
|
+
icon = "navigation_more_icon"
|
|
102
|
+
onClickHandler = onPressMore
|
|
103
|
+
} else if (totalTools == 1 && !headerRight?.tools.isNullOrEmpty()) {
|
|
104
|
+
val singleTool = headerRight?.tools?.first()?.items?.firstOrNull()
|
|
105
|
+
if (singleTool != null) {
|
|
106
|
+
icon = singleTool.icon
|
|
107
|
+
onClickHandler = {
|
|
108
|
+
headerRight.toolCallback?.invoke(singleTool.key)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return NavigationButtonConfig(icon, onClickHandler)
|
|
113
|
+
}
|
|
114
|
+
|
|
91
115
|
@Composable
|
|
92
|
-
fun ToolkitHeaderRight(headerRight: HeaderRightData? = null) {
|
|
116
|
+
private fun ToolkitHeaderRight(headerRight: HeaderRightData? = null) {
|
|
93
117
|
var isFavorite by remember { mutableStateOf(false) }
|
|
94
118
|
val isLoading by remember { mutableStateOf(false) }
|
|
95
119
|
val api = PlatformApi.current as? ComposeApi
|
|
@@ -148,34 +172,8 @@ fun ToolkitHeaderRight(headerRight: HeaderRightData? = null) {
|
|
|
148
172
|
}
|
|
149
173
|
}
|
|
150
174
|
|
|
151
|
-
fun getNavigationButtonConfig(
|
|
152
|
-
tools: List<ToolGroup>? = emptyList(),
|
|
153
|
-
useMore: Boolean? = false,
|
|
154
|
-
isFavorite: Boolean = false,
|
|
155
|
-
onPressShortcut: () -> Unit,
|
|
156
|
-
onPressMore: () -> Unit
|
|
157
|
-
): NavigationButtonConfig {
|
|
158
|
-
val totalTools = tools?.sumOf { it.items.size } ?: 0
|
|
159
|
-
var icon = if (isFavorite) "pin_star_checked" else "pin_star"
|
|
160
|
-
var onClickHandler = onPressShortcut
|
|
161
|
-
if (totalTools > 1 || useMore == true) {
|
|
162
|
-
icon = "navigation_more_icon"
|
|
163
|
-
onClickHandler = onPressMore
|
|
164
|
-
} else if (totalTools == 1 && !tools.isNullOrEmpty()) {
|
|
165
|
-
val singleTool = tools.first().items.firstOrNull()
|
|
166
|
-
if (singleTool != null) {
|
|
167
|
-
icon = singleTool.icon
|
|
168
|
-
onClickHandler = {
|
|
169
|
-
headerRight?.toolCallback?.invoke(singleTool.key)
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
return NavigationButtonConfig(icon, onClickHandler)
|
|
174
|
-
}
|
|
175
|
-
|
|
176
175
|
val navButtonConfig = getNavigationButtonConfig(
|
|
177
|
-
headerRight
|
|
178
|
-
headerRight?.useMore,
|
|
176
|
+
headerRight,
|
|
179
177
|
isFavorite,
|
|
180
178
|
::onPressShortcut,
|
|
181
179
|
::onPressMore
|
|
@@ -188,7 +186,7 @@ fun ToolkitHeaderRight(headerRight: HeaderRightData? = null) {
|
|
|
188
186
|
val toolkitWidth = if (context !== null) 65.dp else 28.dp
|
|
189
187
|
|
|
190
188
|
var isShowShortcut = headerRight?.useShortcut == true
|
|
191
|
-
|
|
189
|
+
|
|
192
190
|
if (headerRight?.useMore == true && context == null) {
|
|
193
191
|
isShowShortcut = false
|
|
194
192
|
}
|
|
@@ -264,7 +262,7 @@ fun NavigationButton(
|
|
|
264
262
|
) {
|
|
265
263
|
Icon(
|
|
266
264
|
source = icon,
|
|
267
|
-
size =
|
|
265
|
+
size = 20.dp
|
|
268
266
|
)
|
|
269
267
|
}
|
|
270
268
|
|
|
@@ -9,7 +9,6 @@ import androidx.compose.runtime.remember
|
|
|
9
9
|
import androidx.compose.runtime.setValue
|
|
10
10
|
import androidx.compose.runtime.staticCompositionLocalOf
|
|
11
11
|
import androidx.compose.ui.unit.Dp
|
|
12
|
-
import kotlinx.serialization.Serializable
|
|
13
12
|
import vn.momo.kits.components.TrustBannerData
|
|
14
13
|
import vn.momo.kits.const.AppStatusBar
|
|
15
14
|
import vn.momo.kits.const.AppTheme
|
|
@@ -27,20 +26,35 @@ interface ComposeApi {
|
|
|
27
26
|
}
|
|
28
27
|
|
|
29
28
|
class Navigator {
|
|
30
|
-
fun push(content: @Composable () -> Unit) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
fun
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
fun push(content: @Composable () -> Unit) {
|
|
30
|
+
//implement
|
|
31
|
+
}
|
|
32
|
+
fun present(content: @Composable () -> Unit) {
|
|
33
|
+
//implement
|
|
34
|
+
}
|
|
35
|
+
fun reset(content: @Composable () -> Unit) {
|
|
36
|
+
//implement
|
|
37
|
+
}
|
|
38
|
+
fun pop() {
|
|
39
|
+
//implement
|
|
40
|
+
}
|
|
41
|
+
fun replace(content: @Composable () -> Unit) {
|
|
42
|
+
//implement
|
|
43
|
+
}
|
|
44
|
+
fun popToTop() {
|
|
45
|
+
//implement
|
|
46
|
+
}
|
|
36
47
|
fun showModal(
|
|
37
48
|
onClose: () -> Unit = {},
|
|
38
49
|
canBackgroundClose: Boolean = true,
|
|
39
50
|
content: @Composable () -> Unit,
|
|
40
51
|
) {
|
|
52
|
+
//implement
|
|
41
53
|
}
|
|
42
54
|
|
|
43
|
-
fun showBottomSheet(content: @Composable () -> Unit) {
|
|
55
|
+
fun showBottomSheet(content: @Composable () -> Unit) {
|
|
56
|
+
//implement
|
|
57
|
+
}
|
|
44
58
|
}
|
|
45
59
|
|
|
46
60
|
data class MiniAppContext(
|
|
@@ -69,6 +83,10 @@ val AppConfig = staticCompositionLocalOf<KitConfig?> {
|
|
|
69
83
|
null
|
|
70
84
|
}
|
|
71
85
|
|
|
86
|
+
val AppLanguage = staticCompositionLocalOf<String?> {
|
|
87
|
+
null
|
|
88
|
+
}
|
|
89
|
+
|
|
72
90
|
data class KitConfig(
|
|
73
91
|
val trustBanner: TrustBannerData? = null,
|
|
74
92
|
val headerBar: String? = null,
|
|
@@ -82,6 +100,7 @@ fun ApplicationContainer(
|
|
|
82
100
|
statusBarHeight: Dp? = AppStatusBar.current,
|
|
83
101
|
applicationContext: MiniAppContext? = null,
|
|
84
102
|
config: KitConfig? = null,
|
|
103
|
+
language: String? = null,
|
|
85
104
|
content: @Composable () -> Unit,
|
|
86
105
|
) {
|
|
87
106
|
var appTheme by remember { mutableStateOf(theme) }
|
|
@@ -110,7 +129,8 @@ fun ApplicationContainer(
|
|
|
110
129
|
AppNavigator provides Navigator(),
|
|
111
130
|
AppStatusBar provides appStatusBarHeight,
|
|
112
131
|
ApplicationContext provides applicationContext,
|
|
113
|
-
AppConfig provides config
|
|
132
|
+
AppConfig provides config,
|
|
133
|
+
AppLanguage provides language
|
|
114
134
|
) {
|
|
115
135
|
content()
|
|
116
136
|
}
|
|
@@ -20,6 +20,7 @@ import androidx.compose.ui.layout.ContentScale
|
|
|
20
20
|
import androidx.compose.ui.unit.dp
|
|
21
21
|
import androidx.compose.ui.unit.sp
|
|
22
22
|
import vn.momo.kits.application.AppConfig
|
|
23
|
+
import vn.momo.kits.application.AppLanguage
|
|
23
24
|
import vn.momo.kits.modifier.noFeedbackClickable
|
|
24
25
|
|
|
25
26
|
val defaultBanner = TrustBannerData(
|
|
@@ -62,10 +63,10 @@ fun TrustBanner(
|
|
|
62
63
|
serviceName: String = "",
|
|
63
64
|
screenName: String = "",
|
|
64
65
|
onPress: ((Map<String, String>) -> Unit)? = null,
|
|
65
|
-
trackEvent: ((String, Map<String, String>) -> Unit)? = null
|
|
66
|
-
language: String = "vi"
|
|
66
|
+
trackEvent: ((String, Map<String, String>) -> Unit)? = null
|
|
67
67
|
) {
|
|
68
68
|
val appConfig = AppConfig.current
|
|
69
|
+
val language = AppLanguage.current ?: "vi"
|
|
69
70
|
val trustBanner = appConfig?.trustBanner ?: defaultBanner
|
|
70
71
|
val trackParams = mapOf(
|
|
71
72
|
"service_name" to serviceName,
|
package/compose/src/commonMain/kotlin/vn/momo/kits/components/dateTimePicker/DateTimePicker.kt
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
package vn.momo.kits.components.dateTimePicker
|
|
2
|
+
|
|
3
|
+
import androidx.compose.foundation.background
|
|
4
|
+
import androidx.compose.foundation.layout.Column
|
|
5
|
+
import androidx.compose.foundation.layout.Row
|
|
6
|
+
import androidx.compose.foundation.layout.Spacer
|
|
7
|
+
import androidx.compose.foundation.layout.fillMaxWidth
|
|
8
|
+
import androidx.compose.foundation.layout.height
|
|
9
|
+
import androidx.compose.foundation.layout.padding
|
|
10
|
+
import androidx.compose.foundation.layout.width
|
|
11
|
+
import androidx.compose.runtime.Composable
|
|
12
|
+
import androidx.compose.runtime.getValue
|
|
13
|
+
import androidx.compose.runtime.mutableStateOf
|
|
14
|
+
import androidx.compose.runtime.remember
|
|
15
|
+
import androidx.compose.ui.Alignment
|
|
16
|
+
import androidx.compose.ui.Modifier
|
|
17
|
+
import androidx.compose.ui.unit.dp
|
|
18
|
+
import kotlinx.datetime.LocalDateTime
|
|
19
|
+
import kotlinx.datetime.number
|
|
20
|
+
import vn.momo.kits.components.Text
|
|
21
|
+
import vn.momo.kits.const.AppTheme
|
|
22
|
+
import vn.momo.kits.const.Spacing
|
|
23
|
+
import vn.momo.kits.const.Typography
|
|
24
|
+
|
|
25
|
+
private val datePickerHeight = 210.dp
|
|
26
|
+
private val datePickerWithLabelsHeight = 238.dp
|
|
27
|
+
|
|
28
|
+
private fun onPickerChange(
|
|
29
|
+
name: String,
|
|
30
|
+
value: String,
|
|
31
|
+
currentDate: PickerData,
|
|
32
|
+
effectiveMinDate: LocalDateTime,
|
|
33
|
+
effectiveMaxDate: LocalDateTime,
|
|
34
|
+
onChange: (LocalDateTime) -> Unit
|
|
35
|
+
){
|
|
36
|
+
when (name) {
|
|
37
|
+
"day" -> currentDate.day = value.toInt()
|
|
38
|
+
"month" -> currentDate.month = value.toInt()
|
|
39
|
+
"year" -> currentDate.year = value.toInt()
|
|
40
|
+
"hour" -> currentDate.hour = value.toInt()
|
|
41
|
+
"minute" -> currentDate.minute = value.toInt()
|
|
42
|
+
"timeMode" -> currentDate.timeMode = value
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (name == "month" || name == "year") {
|
|
46
|
+
val maxDayOfMonth = when (currentDate.month) {
|
|
47
|
+
1, 3, 5, 7, 8, 10, 12 -> 31
|
|
48
|
+
4, 6, 9, 11 -> 30
|
|
49
|
+
2 -> if (currentDate.year % 4 == 0 && (currentDate.year % 100 != 0 || currentDate.year % 400 == 0)) 29 else 28
|
|
50
|
+
else -> 31
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (currentDate.day > maxDayOfMonth) {
|
|
54
|
+
currentDate.day = maxDayOfMonth
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
val changedDate = LocalDateTime(
|
|
59
|
+
year = currentDate.year,
|
|
60
|
+
monthNumber = currentDate.month,
|
|
61
|
+
dayOfMonth = currentDate.day,
|
|
62
|
+
hour = currentDate.hour,
|
|
63
|
+
minute = currentDate.minute,
|
|
64
|
+
second = 0,
|
|
65
|
+
nanosecond = 0
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
if (changedDate in effectiveMinDate..effectiveMaxDate) {
|
|
69
|
+
onChange(changedDate)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@Composable
|
|
74
|
+
fun DateTimePicker(
|
|
75
|
+
modifier: Modifier = Modifier,
|
|
76
|
+
format: String = "DD-MM-YYYY",
|
|
77
|
+
minuteInterval: Int = 1,
|
|
78
|
+
onChange: (LocalDateTime) -> Unit,
|
|
79
|
+
selectedValue: LocalDateTime? = null,
|
|
80
|
+
minDate: LocalDateTime? = null,
|
|
81
|
+
maxDate: LocalDateTime? = null,
|
|
82
|
+
arrayLabelTime: List<String> = emptyList()
|
|
83
|
+
) {
|
|
84
|
+
val now = remember { getCurrentDateTime() }
|
|
85
|
+
|
|
86
|
+
val minDateDefault = remember { createRelativeDate(-10) }
|
|
87
|
+
|
|
88
|
+
val maxDateDefault = remember { createRelativeDate(10) }
|
|
89
|
+
|
|
90
|
+
val effectiveSelectedValue = selectedValue ?: now
|
|
91
|
+
val effectiveMinDate = minDate ?: minDateDefault
|
|
92
|
+
val effectiveMaxDate = maxDate ?: maxDateDefault
|
|
93
|
+
|
|
94
|
+
val initialValue = remember {
|
|
95
|
+
when {
|
|
96
|
+
effectiveSelectedValue < effectiveMinDate -> effectiveMinDate
|
|
97
|
+
effectiveSelectedValue > effectiveMaxDate -> effectiveMaxDate
|
|
98
|
+
else -> effectiveSelectedValue
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
val currentDate by remember {
|
|
103
|
+
mutableStateOf(
|
|
104
|
+
PickerData(
|
|
105
|
+
day = initialValue.dayOfMonth,
|
|
106
|
+
month = initialValue.month.number,
|
|
107
|
+
year = initialValue.year,
|
|
108
|
+
hour = initialValue.hour,
|
|
109
|
+
minute = initialValue.minute
|
|
110
|
+
)
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
val pickerHeight = if (arrayLabelTime.isNotEmpty())
|
|
115
|
+
datePickerWithLabelsHeight else
|
|
116
|
+
datePickerHeight
|
|
117
|
+
|
|
118
|
+
Row(
|
|
119
|
+
modifier = modifier
|
|
120
|
+
.fillMaxWidth()
|
|
121
|
+
.height(pickerHeight)
|
|
122
|
+
.background(AppTheme.current.colors.background.surface)
|
|
123
|
+
.padding(horizontal = Spacing.M),
|
|
124
|
+
verticalAlignment = Alignment.CenterVertically
|
|
125
|
+
) {
|
|
126
|
+
val dateComponents = getDateComponents(
|
|
127
|
+
format,
|
|
128
|
+
currentDate,
|
|
129
|
+
effectiveMinDate,
|
|
130
|
+
effectiveMaxDate,
|
|
131
|
+
minuteInterval,
|
|
132
|
+
)
|
|
133
|
+
dateComponents.forEachIndexed { index, component ->
|
|
134
|
+
val hasLabel = index < arrayLabelTime.size && arrayLabelTime[index].isNotEmpty()
|
|
135
|
+
|
|
136
|
+
Column(
|
|
137
|
+
horizontalAlignment = Alignment.CenterHorizontally,
|
|
138
|
+
modifier = Modifier.weight(1f)
|
|
139
|
+
) {
|
|
140
|
+
if (hasLabel) {
|
|
141
|
+
Text(
|
|
142
|
+
text = arrayLabelTime[index],
|
|
143
|
+
style = Typography.actionS,
|
|
144
|
+
modifier = Modifier.padding(bottom = Spacing.S)
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
WheelPicker(
|
|
149
|
+
name = component.name,
|
|
150
|
+
data = component.data,
|
|
151
|
+
selectedData = when (component.name) {
|
|
152
|
+
"day" -> paddingNum(currentDate.day)
|
|
153
|
+
"month" -> paddingNum(currentDate.month)
|
|
154
|
+
"year" -> currentDate.year.toString()
|
|
155
|
+
"hour" -> paddingNum(currentDate.hour)
|
|
156
|
+
"minute" -> paddingNum(currentDate.minute)
|
|
157
|
+
"timeMode" -> currentDate.timeMode
|
|
158
|
+
else -> ""
|
|
159
|
+
},
|
|
160
|
+
onChange = { name, value ->
|
|
161
|
+
onPickerChange(
|
|
162
|
+
name,
|
|
163
|
+
value,
|
|
164
|
+
currentDate,
|
|
165
|
+
effectiveMinDate,
|
|
166
|
+
effectiveMaxDate,
|
|
167
|
+
onChange
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (index < dateComponents.size - 1) {
|
|
174
|
+
Spacer(modifier = Modifier.width(Spacing.M))
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
package vn.momo.kits.components.dateTimePicker
|
|
2
|
+
|
|
3
|
+
import androidx.compose.foundation.layout.Column
|
|
4
|
+
import androidx.compose.foundation.layout.Spacer
|
|
5
|
+
import androidx.compose.foundation.layout.fillMaxWidth
|
|
6
|
+
import androidx.compose.foundation.layout.height
|
|
7
|
+
import androidx.compose.foundation.layout.padding
|
|
8
|
+
import androidx.compose.runtime.Composable
|
|
9
|
+
import androidx.compose.runtime.getValue
|
|
10
|
+
import androidx.compose.runtime.mutableStateOf
|
|
11
|
+
import androidx.compose.runtime.remember
|
|
12
|
+
import androidx.compose.runtime.setValue
|
|
13
|
+
import androidx.compose.ui.Modifier
|
|
14
|
+
import vn.momo.kits.components.Text
|
|
15
|
+
import vn.momo.kits.const.Spacing
|
|
16
|
+
import vn.momo.kits.const.Typography
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Example usage of DateTimePicker component
|
|
20
|
+
*/
|
|
21
|
+
@Composable
|
|
22
|
+
fun DateTimePickerExample() {
|
|
23
|
+
// The current date and time
|
|
24
|
+
val now = remember { getCurrentDateTime() }
|
|
25
|
+
|
|
26
|
+
// State to hold the selected date
|
|
27
|
+
var selectedDate by remember { mutableStateOf(now) }
|
|
28
|
+
|
|
29
|
+
Column(
|
|
30
|
+
modifier = Modifier
|
|
31
|
+
.fillMaxWidth()
|
|
32
|
+
.padding(Spacing.M)
|
|
33
|
+
) {
|
|
34
|
+
// Display the current selected date
|
|
35
|
+
Text(
|
|
36
|
+
text = "Selected Date: ${formatLocalDateTime(selectedDate)}",
|
|
37
|
+
style = Typography.paragraphDefault
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
Spacer(modifier = Modifier.height(Spacing.L))
|
|
41
|
+
|
|
42
|
+
// Date Picker (DD-MM-YYYY)
|
|
43
|
+
Text(
|
|
44
|
+
text = "Date Picker",
|
|
45
|
+
style = Typography.descriptionDefault
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
Spacer(modifier = Modifier.height(Spacing.S))
|
|
49
|
+
|
|
50
|
+
DateTimePicker(
|
|
51
|
+
selectedValue = selectedDate,
|
|
52
|
+
onChange = { selectedDate = it },
|
|
53
|
+
format = "DD-MM-YYYY"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
Spacer(modifier = Modifier.height(Spacing.L))
|
|
57
|
+
|
|
58
|
+
// Date and Time Picker (DD-MM-YYYY HH:mm)
|
|
59
|
+
Text(
|
|
60
|
+
text = "Date and Time Picker",
|
|
61
|
+
style = Typography.descriptionDefault
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
Spacer(modifier = Modifier.height(Spacing.S))
|
|
65
|
+
|
|
66
|
+
DateTimePicker(
|
|
67
|
+
selectedValue = selectedDate,
|
|
68
|
+
onChange = { selectedDate = it },
|
|
69
|
+
format = "DD-MM-YYYY-HH-mm",
|
|
70
|
+
arrayLabelTime = listOf("Day", "Month", "Year", "Hour", "Minute")
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
Spacer(modifier = Modifier.height(Spacing.L))
|
|
74
|
+
|
|
75
|
+
// Time Picker (HH:mm)
|
|
76
|
+
Text(
|
|
77
|
+
text = "Time Picker",
|
|
78
|
+
style = Typography.descriptionDefault
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
Spacer(modifier = Modifier.height(Spacing.S))
|
|
82
|
+
|
|
83
|
+
DateTimePicker(
|
|
84
|
+
selectedValue = selectedDate,
|
|
85
|
+
onChange = { selectedDate = it },
|
|
86
|
+
format = "HH-mm",
|
|
87
|
+
arrayLabelTime = listOf("Hour", "Minute")
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
}
|
package/compose/src/commonMain/kotlin/vn/momo/kits/components/dateTimePicker/DateTimePickerTypes.kt
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
package vn.momo.kits.components.dateTimePicker
|
|
2
|
+
|
|
3
|
+
data class PickerData(
|
|
4
|
+
var day: Int,
|
|
5
|
+
var month: Int,
|
|
6
|
+
var year: Int,
|
|
7
|
+
var hour: Int = 0,
|
|
8
|
+
var minute: Int = 0,
|
|
9
|
+
var timeMode: String = ""
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
data class DateComponent(
|
|
13
|
+
val name: String,
|
|
14
|
+
val data: List<String>
|
|
15
|
+
)
|
package/compose/src/commonMain/kotlin/vn/momo/kits/components/dateTimePicker/DateTimePickerUtils.kt
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
package vn.momo.kits.components.dateTimePicker
|
|
2
|
+
|
|
3
|
+
import androidx.compose.runtime.Composable
|
|
4
|
+
import androidx.compose.runtime.remember
|
|
5
|
+
import kotlinx.datetime.Clock
|
|
6
|
+
import kotlinx.datetime.DatePeriod
|
|
7
|
+
import kotlinx.datetime.LocalDateTime
|
|
8
|
+
import kotlinx.datetime.TimeZone
|
|
9
|
+
import kotlinx.datetime.minus
|
|
10
|
+
import kotlinx.datetime.number
|
|
11
|
+
import kotlinx.datetime.plus
|
|
12
|
+
import kotlinx.datetime.toLocalDateTime
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Format a LocalDateTime object into a string
|
|
16
|
+
*/
|
|
17
|
+
fun formatLocalDateTime(dateTime: LocalDateTime, format: String = "yyyy-MM-dd HH:mm"): String {
|
|
18
|
+
return when (format) {
|
|
19
|
+
"yyyy-MM-dd HH:mm" -> "${dateTime.year}-${paddingNum(dateTime.month.number)}-${paddingNum(dateTime.dayOfMonth)} ${paddingNum(dateTime.hour)}:${paddingNum(dateTime.minute)}"
|
|
20
|
+
"dd-MM-yyyy" -> "${paddingNum(dateTime.dayOfMonth)}-${paddingNum(dateTime.month.number)}-${dateTime.year}"
|
|
21
|
+
"HH:mm" -> "${paddingNum(dateTime.hour)}:${paddingNum(dateTime.minute)}"
|
|
22
|
+
else -> "${dateTime.year}-${paddingNum(dateTime.month.number)}-${paddingNum(dateTime.dayOfMonth)} ${paddingNum(dateTime.hour)}:${paddingNum(dateTime.minute)}"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Pad a number with a leading zero if it's less than 10
|
|
28
|
+
*/
|
|
29
|
+
fun paddingNum(num: Int): String {
|
|
30
|
+
return if (num > 9) num.toString() else "0$num"
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Get a list of days in a month
|
|
35
|
+
*/
|
|
36
|
+
fun getDaysInMonth(
|
|
37
|
+
year: Int,
|
|
38
|
+
month: Int,
|
|
39
|
+
minDate: LocalDateTime,
|
|
40
|
+
maxDate: LocalDateTime
|
|
41
|
+
): List<String> {
|
|
42
|
+
val daysInMonth = when (month) {
|
|
43
|
+
1, 3, 5, 7, 8, 10, 12 -> 31
|
|
44
|
+
4, 6, 9, 11 -> 30
|
|
45
|
+
2 -> if (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) 29 else 28
|
|
46
|
+
else -> throw IllegalArgumentException("Invalid month: $month")
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
var startDay = 1
|
|
50
|
+
var endDay = daysInMonth
|
|
51
|
+
|
|
52
|
+
if (year == minDate.year && month == minDate.month.number) {
|
|
53
|
+
startDay = minDate.dayOfMonth
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (year == maxDate.year && month == maxDate.month.number) {
|
|
57
|
+
endDay = maxDate.dayOfMonth
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
val days = mutableListOf<String>()
|
|
61
|
+
for (day in startDay..endDay) {
|
|
62
|
+
days.add(paddingNum(day))
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return days
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get a list of months
|
|
70
|
+
*/
|
|
71
|
+
fun getMonths(
|
|
72
|
+
minDate: LocalDateTime,
|
|
73
|
+
maxDate: LocalDateTime,
|
|
74
|
+
currentYear: Int
|
|
75
|
+
): List<String> {
|
|
76
|
+
var startMonth = 1
|
|
77
|
+
var endMonth = 12
|
|
78
|
+
|
|
79
|
+
if (currentYear == minDate.year) {
|
|
80
|
+
startMonth = minDate.month.number
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (currentYear == maxDate.year) {
|
|
84
|
+
endMonth = maxDate.month.number
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
val months = mutableListOf<String>()
|
|
88
|
+
for (month in startMonth..endMonth) {
|
|
89
|
+
months.add(paddingNum(month))
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return months
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get a list of years
|
|
97
|
+
*/
|
|
98
|
+
fun getYears(minDate: LocalDateTime, maxDate: LocalDateTime): List<String> {
|
|
99
|
+
val startYear = minDate.year
|
|
100
|
+
val endYear = maxDate.year
|
|
101
|
+
val years = mutableListOf<String>()
|
|
102
|
+
|
|
103
|
+
for (year in startYear..endYear) {
|
|
104
|
+
years.add(year.toString())
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return years
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Get a list of hours
|
|
112
|
+
*/
|
|
113
|
+
fun getHours(hourMode: Int): List<String> {
|
|
114
|
+
val hours = mutableListOf<String>()
|
|
115
|
+
for (i in 0 until hourMode) {
|
|
116
|
+
val hour = if (hourMode == 12) i + 1 else i
|
|
117
|
+
hours.add(paddingNum(hour))
|
|
118
|
+
}
|
|
119
|
+
return hours
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Get a list of minutes
|
|
124
|
+
*/
|
|
125
|
+
fun getMinutes(interval: Int): List<String> {
|
|
126
|
+
val minutes = mutableListOf<String>()
|
|
127
|
+
var min = 0
|
|
128
|
+
while (min < 60) {
|
|
129
|
+
minutes.add(paddingNum(min))
|
|
130
|
+
min += interval
|
|
131
|
+
}
|
|
132
|
+
return minutes
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Time mode for 12-hour clock
|
|
137
|
+
*/
|
|
138
|
+
val timeMode = listOf("AM", "PM")
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Get today's date
|
|
142
|
+
*/
|
|
143
|
+
fun getCurrentDateTime(): LocalDateTime {
|
|
144
|
+
return Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault())
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Create a date that's a certain number of years before or after the current date
|
|
149
|
+
*/
|
|
150
|
+
fun createRelativeDate(years: Int): LocalDateTime {
|
|
151
|
+
val currentDateTime = getCurrentDateTime()
|
|
152
|
+
val period = DatePeriod(years = if (years < 0) -years else years)
|
|
153
|
+
|
|
154
|
+
val adjustedDate = if (years < 0) {
|
|
155
|
+
currentDateTime.date.minus(period)
|
|
156
|
+
} else {
|
|
157
|
+
currentDateTime.date.plus(period)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return LocalDateTime(
|
|
161
|
+
adjustedDate.year,
|
|
162
|
+
adjustedDate.monthNumber,
|
|
163
|
+
adjustedDate.dayOfMonth,
|
|
164
|
+
currentDateTime.hour,
|
|
165
|
+
currentDateTime.minute
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
@Composable
|
|
170
|
+
internal fun getDateComponents(
|
|
171
|
+
format: String,
|
|
172
|
+
currentDate: PickerData,
|
|
173
|
+
effectiveMinDate: LocalDateTime,
|
|
174
|
+
effectiveMaxDate: LocalDateTime,
|
|
175
|
+
minuteInterval: Int,
|
|
176
|
+
): List<DateComponent> {
|
|
177
|
+
val formatParts = remember(format) {
|
|
178
|
+
format.split(Regex("[^A-Za-z]+"))
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
val isOnlyHour = remember(formatParts) {
|
|
182
|
+
formatParts.size == 1 && formatParts[0] == "HH"
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
val dayData = remember(currentDate.year, currentDate.month, effectiveMinDate, effectiveMaxDate) {
|
|
186
|
+
getDaysInMonth(currentDate.year, currentDate.month, effectiveMinDate, effectiveMaxDate)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
val monthData = remember(currentDate.year, effectiveMinDate, effectiveMaxDate) {
|
|
190
|
+
getMonths(effectiveMinDate, effectiveMaxDate, currentDate.year)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
val yearData = remember(effectiveMinDate, effectiveMaxDate) {
|
|
194
|
+
getYears(effectiveMinDate, effectiveMaxDate)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
val hourData = remember(isOnlyHour) {
|
|
198
|
+
getHours(if (isOnlyHour) 12 else 24)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
val minData = remember(minuteInterval) {
|
|
202
|
+
getMinutes(minuteInterval)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
val dateComponents = remember(formatParts, dayData, monthData, yearData, hourData, minData, isOnlyHour) {
|
|
206
|
+
val components = mutableListOf<DateComponent>()
|
|
207
|
+
|
|
208
|
+
formatParts.forEach { part ->
|
|
209
|
+
when (part) {
|
|
210
|
+
"DD" -> components.add(DateComponent("day", dayData))
|
|
211
|
+
"MM" -> components.add(DateComponent("month", monthData))
|
|
212
|
+
"YYYY" -> components.add(DateComponent("year", yearData))
|
|
213
|
+
"HH" -> components.add(DateComponent("hour", hourData))
|
|
214
|
+
"mm" -> components.add(DateComponent("minute", minData))
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (isOnlyHour) {
|
|
219
|
+
components.add(DateComponent("timeMode", timeMode))
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
components
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return dateComponents
|
|
226
|
+
}
|