@momo-kits/native-kits 0.89.19 → 0.89.20
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 +18 -14
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +22 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Components.kt +71 -25
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +200 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +47 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +7 -139
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +89 -22
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +17 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +6 -8
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +36 -17
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +54 -21
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +155 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +2 -20
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +185 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +0 -19
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +2 -18
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +58 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +37 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +65 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +43 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +0 -3
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +14 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +2 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +28 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +1 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +18 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +0 -23
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +33 -0
- package/package.json +1 -1
- package/settings.gradle.kts +1 -0
- package/compose/src/androidMain/kotlin/vn/momo/kits/utils/Platform.kt +0 -3
- package/compose/src/androidMain/kotlin/vn/momo/kits/utils/SetMaskFilter.kt +0 -8
- package/compose/src/androidMain/kotlin/vn/momo/kits/utils/Utils.android.kt +0 -14
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Platform.kt +0 -3
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/SetMaskFilter.kt +0 -5
- package/compose/src/iosMain/kotlin/vn/momo/kits/utils/Platform.kt +0 -3
- package/compose/src/iosMain/kotlin/vn/momo/kits/utils/SetMaskFilter.kt +0 -9
- package/compose/src/iosMain/kotlin/vn/momo/kits/utils/Utils.ios.kt +0 -18
|
@@ -1,48 +1,23 @@
|
|
|
1
1
|
package vn.momo.kits.application
|
|
2
2
|
|
|
3
|
-
import androidx.compose.foundation.background
|
|
4
|
-
import androidx.compose.foundation.clickable
|
|
5
|
-
import androidx.compose.foundation.interaction.MutableInteractionSource
|
|
6
|
-
import androidx.compose.foundation.layout.Box
|
|
7
3
|
import androidx.compose.foundation.layout.WindowInsets
|
|
8
4
|
import androidx.compose.foundation.layout.asPaddingValues
|
|
9
|
-
import androidx.compose.foundation.layout.fillMaxSize
|
|
10
|
-
import androidx.compose.foundation.layout.padding
|
|
11
5
|
import androidx.compose.foundation.layout.systemBars
|
|
12
|
-
import androidx.compose.material.ExperimentalMaterialApi
|
|
13
6
|
import androidx.compose.runtime.Composable
|
|
14
7
|
import androidx.compose.runtime.CompositionLocalProvider
|
|
15
|
-
import androidx.compose.runtime.remember
|
|
16
8
|
import androidx.compose.runtime.staticCompositionLocalOf
|
|
17
|
-
import androidx.compose.ui.Alignment
|
|
18
|
-
import androidx.compose.ui.Modifier
|
|
19
|
-
import androidx.compose.ui.graphics.Color
|
|
20
|
-
import androidx.compose.ui.unit.Dp
|
|
21
|
-
import androidx.compose.ui.unit.dp
|
|
22
|
-
import cafe.adriel.voyager.core.screen.Screen
|
|
23
|
-
import cafe.adriel.voyager.navigator.LocalNavigator
|
|
24
|
-
import cafe.adriel.voyager.navigator.bottomSheet.BottomSheetNavigator
|
|
25
|
-
import cafe.adriel.voyager.navigator.bottomSheet.LocalBottomSheetNavigator
|
|
26
|
-
import cafe.adriel.voyager.navigator.currentOrThrow
|
|
27
|
-
import cafe.adriel.voyager.transitions.SlideTransition
|
|
28
9
|
import vn.momo.kits.const.AppStatusBar
|
|
29
10
|
import vn.momo.kits.const.AppTheme
|
|
30
|
-
import vn.momo.kits.const.Spacing
|
|
31
11
|
import vn.momo.kits.const.Theme
|
|
32
12
|
import vn.momo.kits.const.defaultTheme
|
|
13
|
+
import vn.momo.kits.platform.disableOverscroll
|
|
33
14
|
|
|
34
15
|
val AppNavigator = staticCompositionLocalOf<Navigator> {
|
|
35
16
|
error("no navigator provided!")
|
|
36
17
|
}
|
|
37
18
|
|
|
38
|
-
class Navigator
|
|
39
|
-
private val stack: cafe.adriel.voyager.navigator.Navigator,
|
|
40
|
-
private val sheet: BottomSheetNavigator,
|
|
41
|
-
) {
|
|
19
|
+
class Navigator {
|
|
42
20
|
fun push(content: @Composable () -> Unit) {
|
|
43
|
-
stack.push(StackScreen {
|
|
44
|
-
content()
|
|
45
|
-
})
|
|
46
21
|
}
|
|
47
22
|
|
|
48
23
|
fun present(content: @Composable () -> Unit) {
|
|
@@ -54,21 +29,12 @@ class Navigator(
|
|
|
54
29
|
}
|
|
55
30
|
|
|
56
31
|
fun pop() {
|
|
57
|
-
if (sheet.isVisible) {
|
|
58
|
-
sheet.hide()
|
|
59
|
-
} else {
|
|
60
|
-
stack.pop()
|
|
61
|
-
}
|
|
62
32
|
}
|
|
63
33
|
|
|
64
34
|
fun replace(content: @Composable () -> Unit) {
|
|
65
|
-
stack.replace(StackScreen {
|
|
66
|
-
content()
|
|
67
|
-
})
|
|
68
35
|
}
|
|
69
36
|
|
|
70
37
|
fun popToTop() {
|
|
71
|
-
stack.popUntilRoot()
|
|
72
38
|
}
|
|
73
39
|
|
|
74
40
|
fun showModal(
|
|
@@ -76,122 +42,24 @@ class Navigator(
|
|
|
76
42
|
canBackgroundClose: Boolean = true,
|
|
77
43
|
content: @Composable () -> Unit,
|
|
78
44
|
) {
|
|
79
|
-
sheet.show(ModalScreen(onClose = onClose, barrierDismissible = canBackgroundClose) {
|
|
80
|
-
content()
|
|
81
|
-
})
|
|
82
45
|
}
|
|
83
46
|
|
|
84
47
|
fun showBottomSheet(content: @Composable () -> Unit) {
|
|
85
|
-
sheet.show(SheetScreen {
|
|
86
|
-
content()
|
|
87
|
-
})
|
|
88
48
|
}
|
|
89
49
|
}
|
|
90
50
|
|
|
91
|
-
@OptIn(ExperimentalMaterialApi::class)
|
|
92
51
|
@Composable
|
|
93
52
|
fun ApplicationContainer(
|
|
94
53
|
theme: Theme = defaultTheme,
|
|
95
|
-
statusBarHeight: Dp = WindowInsets.systemBars.asPaddingValues().calculateTopPadding(),
|
|
96
54
|
content: @Composable () -> Unit,
|
|
97
55
|
) {
|
|
56
|
+
disableOverscroll()
|
|
98
57
|
CompositionLocalProvider(
|
|
99
58
|
AppTheme provides theme,
|
|
100
|
-
|
|
59
|
+
AppNavigator provides Navigator(),
|
|
60
|
+
AppStatusBar provides WindowInsets.systemBars.asPaddingValues().calculateTopPadding(),
|
|
101
61
|
) {
|
|
102
|
-
|
|
103
|
-
cafe.adriel.voyager.navigator.Navigator(MainScreen {
|
|
104
|
-
content()
|
|
105
|
-
}) {
|
|
106
|
-
SlideTransition(it)
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
class MainScreen(private val content: @Composable () -> Unit) : Screen {
|
|
114
|
-
@Composable
|
|
115
|
-
override fun Content() {
|
|
116
|
-
CompositionLocalProvider(
|
|
117
|
-
AppNavigator provides Navigator(
|
|
118
|
-
LocalNavigator.currentOrThrow,
|
|
119
|
-
LocalBottomSheetNavigator.current
|
|
120
|
-
),
|
|
121
|
-
) {
|
|
122
|
-
content()
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
class StackScreen(private val content: @Composable () -> Unit) : Screen {
|
|
128
|
-
@Composable
|
|
129
|
-
override fun Content() {
|
|
130
|
-
CompositionLocalProvider(
|
|
131
|
-
AppNavigator provides Navigator(
|
|
132
|
-
LocalNavigator.currentOrThrow,
|
|
133
|
-
LocalBottomSheetNavigator.current
|
|
134
|
-
),
|
|
135
|
-
) {
|
|
136
|
-
content()
|
|
137
|
-
}
|
|
62
|
+
content()
|
|
138
63
|
}
|
|
139
|
-
}
|
|
140
64
|
|
|
141
|
-
|
|
142
|
-
@Composable
|
|
143
|
-
override fun Content() {
|
|
144
|
-
CompositionLocalProvider(
|
|
145
|
-
AppNavigator provides Navigator(
|
|
146
|
-
LocalNavigator.currentOrThrow,
|
|
147
|
-
LocalBottomSheetNavigator.current
|
|
148
|
-
),
|
|
149
|
-
) {
|
|
150
|
-
content()
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
class ModalScreen(
|
|
156
|
-
private val onClose: () -> Unit,
|
|
157
|
-
private val barrierDismissible: Boolean = true,
|
|
158
|
-
private val content: @Composable () -> Unit,
|
|
159
|
-
) :
|
|
160
|
-
Screen {
|
|
161
|
-
@Composable
|
|
162
|
-
override fun Content() {
|
|
163
|
-
val navigator = Navigator(
|
|
164
|
-
LocalNavigator.currentOrThrow,
|
|
165
|
-
LocalBottomSheetNavigator.current
|
|
166
|
-
)
|
|
167
|
-
CompositionLocalProvider(
|
|
168
|
-
AppNavigator provides navigator,
|
|
169
|
-
) {
|
|
170
|
-
Box(
|
|
171
|
-
modifier = Modifier.fillMaxSize().padding(horizontal = Spacing.XL)
|
|
172
|
-
.background(Color.Transparent)
|
|
173
|
-
.clickable(
|
|
174
|
-
interactionSource = remember { MutableInteractionSource() },
|
|
175
|
-
indication = null,
|
|
176
|
-
onClick = {
|
|
177
|
-
if (barrierDismissible) {
|
|
178
|
-
navigator.pop()
|
|
179
|
-
onClose()
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
),
|
|
183
|
-
contentAlignment = Alignment.Center
|
|
184
|
-
) {
|
|
185
|
-
Box(
|
|
186
|
-
modifier = Modifier.clickable(
|
|
187
|
-
interactionSource = remember { MutableInteractionSource() },
|
|
188
|
-
indication = null
|
|
189
|
-
) {},
|
|
190
|
-
contentAlignment = Alignment.Center
|
|
191
|
-
) {
|
|
192
|
-
content()
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
}
|
|
65
|
+
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
package vn.momo.kits.application
|
|
2
2
|
|
|
3
|
+
import androidx.compose.animation.core.animateFloatAsState
|
|
3
4
|
import androidx.compose.foundation.background
|
|
4
5
|
import androidx.compose.foundation.gestures.detectTapGestures
|
|
5
6
|
import androidx.compose.foundation.layout.Arrangement
|
|
6
7
|
import androidx.compose.foundation.layout.Box
|
|
7
8
|
import androidx.compose.foundation.layout.Column
|
|
9
|
+
import androidx.compose.foundation.layout.Spacer
|
|
8
10
|
import androidx.compose.foundation.layout.WindowInsets
|
|
9
11
|
import androidx.compose.foundation.layout.asPaddingValues
|
|
10
12
|
import androidx.compose.foundation.layout.fillMaxSize
|
|
@@ -16,14 +18,18 @@ import androidx.compose.foundation.layout.systemBars
|
|
|
16
18
|
import androidx.compose.foundation.rememberScrollState
|
|
17
19
|
import androidx.compose.foundation.verticalScroll
|
|
18
20
|
import androidx.compose.runtime.Composable
|
|
21
|
+
import androidx.compose.runtime.getValue
|
|
19
22
|
import androidx.compose.ui.Alignment
|
|
20
23
|
import androidx.compose.ui.Modifier
|
|
21
24
|
import androidx.compose.ui.graphics.Color
|
|
25
|
+
import androidx.compose.ui.graphics.graphicsLayer
|
|
22
26
|
import androidx.compose.ui.input.pointer.pointerInput
|
|
23
27
|
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
|
24
28
|
import androidx.compose.ui.unit.Dp
|
|
25
29
|
import androidx.compose.ui.unit.dp
|
|
26
30
|
import androidx.compose.ui.unit.min
|
|
31
|
+
import androidx.compose.ui.zIndex
|
|
32
|
+
import vn.momo.kits.const.AppStatusBar
|
|
27
33
|
import vn.momo.kits.const.AppTheme
|
|
28
34
|
import vn.momo.kits.const.Colors
|
|
29
35
|
import vn.momo.kits.const.Spacing
|
|
@@ -51,6 +57,9 @@ fun Screen(
|
|
|
51
57
|
useAvoidKeyboard: Boolean = true,
|
|
52
58
|
footer: @Composable (() -> Unit)? = null,
|
|
53
59
|
headerRight: @Composable (() -> Unit)? = null,
|
|
60
|
+
fabProps: FabProps? = null,
|
|
61
|
+
animatedHeader: AnimatedHeader? = null,
|
|
62
|
+
layoutOffset: Dp = 56.dp,
|
|
54
63
|
content: @Composable () -> Unit,
|
|
55
64
|
) {
|
|
56
65
|
val keyboardController = LocalSoftwareKeyboardController.current
|
|
@@ -62,50 +71,108 @@ fun Screen(
|
|
|
62
71
|
else -> 0.dp
|
|
63
72
|
}
|
|
64
73
|
|
|
74
|
+
val indicator = WindowInsets.systemBars.asPaddingValues().calculateBottomPadding()
|
|
75
|
+
val bottomPadding = min(indicator, 21.dp)
|
|
76
|
+
val scrollState = rememberScrollState()
|
|
77
|
+
|
|
78
|
+
val opacity by animateFloatAsState(
|
|
79
|
+
targetValue = ((scrollState.value / 114f)).coerceIn(0f, 1f)
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
val headerAnimated =
|
|
83
|
+
@Composable { animatedHeader?.composable?.invoke(scrollState.value) }
|
|
84
|
+
|
|
65
85
|
Box(
|
|
66
|
-
Modifier.fillMaxSize()
|
|
86
|
+
Modifier.fillMaxSize()
|
|
87
|
+
.background(backgroundColor ?: AppTheme.current.colors.background.default)
|
|
67
88
|
) {
|
|
68
|
-
|
|
89
|
+
|
|
90
|
+
if (animatedHeader === null) {
|
|
91
|
+
HeaderImage(
|
|
92
|
+
headerType,
|
|
93
|
+
headerBackground ?: AppTheme.current.assets.headerBackground
|
|
94
|
+
)
|
|
95
|
+
} else {
|
|
96
|
+
Box(
|
|
97
|
+
Modifier.zIndex(5f).background(Color.White.copy(alpha = opacity))
|
|
98
|
+
.graphicsLayer { alpha = opacity }) {
|
|
99
|
+
HeaderImage(
|
|
100
|
+
headerType,
|
|
101
|
+
headerBackground ?: AppTheme.current.assets.headerBackground
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
Header(
|
|
107
|
+
headerType = headerType,
|
|
108
|
+
title = title,
|
|
109
|
+
titlePosition = titlePosition,
|
|
110
|
+
headerRight = headerRight,
|
|
111
|
+
isBack = isBack,
|
|
112
|
+
goBack = goBack,
|
|
113
|
+
opacity = opacity,
|
|
114
|
+
animatedHeader = animatedHeader,
|
|
115
|
+
scrollState = scrollState.value
|
|
116
|
+
)
|
|
117
|
+
|
|
69
118
|
Column(
|
|
70
|
-
modifier = Modifier.fillMaxSize()
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
119
|
+
modifier = Modifier.fillMaxSize()
|
|
120
|
+
.padding(top = if (animatedHeader === null && headerType !== HeaderType.NONE) AppStatusBar.current + 52.dp else 0.dp)
|
|
121
|
+
.pointerInput(Unit) {
|
|
122
|
+
detectTapGestures(onTap = {
|
|
123
|
+
keyboardController?.hide()
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
.zIndex(1f),
|
|
75
127
|
) {
|
|
76
|
-
Header(
|
|
77
|
-
headerType = headerType,
|
|
78
|
-
title = title,
|
|
79
|
-
titlePosition = titlePosition,
|
|
80
|
-
headerRight = headerRight,
|
|
81
|
-
isBack = isBack,
|
|
82
|
-
goBack = goBack,
|
|
83
|
-
)
|
|
84
128
|
|
|
85
129
|
Column(
|
|
86
130
|
modifier = if (scrollable) Modifier.weight(1f).padding(bottom = keyboardSize)
|
|
87
|
-
.verticalScroll(
|
|
131
|
+
.verticalScroll(scrollState) else
|
|
88
132
|
Modifier.padding(bottom = keyboardSize),
|
|
89
133
|
verticalArrangement = verticalArrangement,
|
|
90
134
|
horizontalAlignment = horizontalAlignment
|
|
91
135
|
) {
|
|
92
|
-
|
|
136
|
+
Box {
|
|
137
|
+
headerAnimated()
|
|
138
|
+
Column {
|
|
139
|
+
if (animatedHeader !== null) {
|
|
140
|
+
Spacer(modifier = Modifier.padding(top = AppStatusBar.current + 52.dp + layoutOffset))
|
|
141
|
+
}
|
|
142
|
+
content()
|
|
143
|
+
}
|
|
144
|
+
}
|
|
93
145
|
}
|
|
94
146
|
|
|
95
147
|
footer?.let {
|
|
96
|
-
Footer(footer, keyboardSize)
|
|
148
|
+
Footer(footer, keyboardSize, bottomPadding)
|
|
97
149
|
}
|
|
98
150
|
}
|
|
151
|
+
|
|
152
|
+
if (fabProps != null) {
|
|
153
|
+
FloatingButton(
|
|
154
|
+
scrollPosition = fabProps.scrollState?.value ?: scrollState.value,
|
|
155
|
+
onClick = fabProps.onClick,
|
|
156
|
+
containerColor = AppTheme.current.colors.primary,
|
|
157
|
+
bottom = fabProps.bottom
|
|
158
|
+
?: if (footer != null) bottomPadding + 76.dp else bottomPadding + 12.dp,
|
|
159
|
+
icon = fabProps.icon,
|
|
160
|
+
iconColor = fabProps.iconColor,
|
|
161
|
+
text = fabProps.label,
|
|
162
|
+
size = fabProps.size,
|
|
163
|
+
position = fabProps.position ?: FABPosition.END,
|
|
164
|
+
keyboardSize = keyboardSize
|
|
165
|
+
)
|
|
166
|
+
}
|
|
99
167
|
}
|
|
100
168
|
}
|
|
101
169
|
|
|
102
170
|
@Composable
|
|
103
|
-
fun Footer(footer: @Composable (() -> Unit)? = null, keyboardSize: Dp = 0.dp) {
|
|
104
|
-
val indicator = WindowInsets.systemBars.asPaddingValues().calculateBottomPadding()
|
|
171
|
+
fun Footer(footer: @Composable (() -> Unit)? = null, keyboardSize: Dp = 0.dp, bottom: Dp = 0.dp) {
|
|
105
172
|
Box(
|
|
106
173
|
Modifier
|
|
107
|
-
.offset(y = -
|
|
108
|
-
.padding(bottom =
|
|
174
|
+
.offset(y = -keyboardSize)
|
|
175
|
+
.padding(bottom = bottom)
|
|
109
176
|
.shadow(
|
|
110
177
|
Colors.black_20.copy(alpha = 0.1f),
|
|
111
178
|
borderRadius = 0.dp,
|
|
@@ -18,10 +18,26 @@ import vn.momo.kits.const.Radius
|
|
|
18
18
|
import vn.momo.kits.const.Spacing
|
|
19
19
|
import vn.momo.kits.const.Typography
|
|
20
20
|
import vn.momo.kits.const.scaleSize
|
|
21
|
-
|
|
21
|
+
|
|
22
|
+
|
|
22
23
|
|
|
23
24
|
@Composable
|
|
24
25
|
fun Badge(label: String = "Label", backgroundColor: Color? = null) {
|
|
26
|
+
val primaryColors = listOf(
|
|
27
|
+
Color(0xFFF0F0F0),
|
|
28
|
+
Color(0xFFEB2F96),
|
|
29
|
+
Color(0xFF962AF0),
|
|
30
|
+
Color(0xFF4E4BFF),
|
|
31
|
+
Color(0xFF007AFF),
|
|
32
|
+
Color(0xFF13C2C2),
|
|
33
|
+
Color(0xFF34C759),
|
|
34
|
+
Color(0xFFA0D911),
|
|
35
|
+
Color(0xFFFFCC00),
|
|
36
|
+
Color(0xFFFA8C16),
|
|
37
|
+
Color(0xFFFA541C),
|
|
38
|
+
Color(0xFFF5222D)
|
|
39
|
+
)
|
|
40
|
+
|
|
25
41
|
fun isNumber(label: String): Boolean {
|
|
26
42
|
val numberRegex = "^\\d+$".toRegex()
|
|
27
43
|
return numberRegex.matches(label)
|
|
@@ -11,8 +11,7 @@ import androidx.compose.ui.graphics.ColorFilter
|
|
|
11
11
|
import androidx.compose.ui.layout.ContentScale
|
|
12
12
|
import androidx.compose.ui.unit.Dp
|
|
13
13
|
import androidx.compose.ui.unit.dp
|
|
14
|
-
import
|
|
15
|
-
import com.skydoves.landscapist.coil3.CoilImage
|
|
14
|
+
import coil3.compose.AsyncImage
|
|
16
15
|
import vn.momo.kits.const.AppTheme
|
|
17
16
|
import vn.momo.kits.utils.Icons
|
|
18
17
|
|
|
@@ -29,13 +28,12 @@ fun Icon(
|
|
|
29
28
|
Icons[source]
|
|
30
29
|
}
|
|
31
30
|
Box(modifier.size(size), contentAlignment = Alignment.Center) {
|
|
32
|
-
|
|
31
|
+
AsyncImage(
|
|
33
32
|
modifier = Modifier.fillMaxSize(),
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
)
|
|
38
|
-
imageModel = { icon },
|
|
33
|
+
model = icon,
|
|
34
|
+
contentDescription = null,
|
|
35
|
+
contentScale = ContentScale.Fit,
|
|
36
|
+
colorFilter = color?.let { ColorFilter.tint(it) }
|
|
39
37
|
)
|
|
40
38
|
}
|
|
41
39
|
}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
package vn.momo.kits.components
|
|
2
2
|
|
|
3
|
+
import androidx.compose.foundation.layout.Box
|
|
3
4
|
import androidx.compose.runtime.Composable
|
|
5
|
+
import androidx.compose.runtime.getValue
|
|
6
|
+
import androidx.compose.runtime.mutableStateOf
|
|
7
|
+
import androidx.compose.runtime.remember
|
|
8
|
+
import androidx.compose.runtime.setValue
|
|
4
9
|
import androidx.compose.ui.Alignment
|
|
5
10
|
import androidx.compose.ui.Modifier
|
|
6
11
|
import androidx.compose.ui.graphics.ColorFilter
|
|
@@ -10,9 +15,7 @@ import androidx.compose.ui.layout.ContentScale
|
|
|
10
15
|
import androidx.compose.ui.semantics.contentDescription
|
|
11
16
|
import androidx.compose.ui.semantics.semantics
|
|
12
17
|
import androidx.compose.ui.semantics.testTag
|
|
13
|
-
import
|
|
14
|
-
import com.skydoves.landscapist.coil3.CoilImage
|
|
15
|
-
import org.jetbrains.compose.resources.ExperimentalResourceApi
|
|
18
|
+
import coil3.compose.AsyncImage
|
|
16
19
|
import vn.momo.kits.const.AppTheme
|
|
17
20
|
|
|
18
21
|
data class Options(
|
|
@@ -22,38 +25,54 @@ data class Options(
|
|
|
22
25
|
val alpha: Float = DefaultAlpha,
|
|
23
26
|
)
|
|
24
27
|
|
|
25
|
-
|
|
28
|
+
enum class ImageState {
|
|
29
|
+
Loading, Success, Error
|
|
30
|
+
}
|
|
31
|
+
|
|
26
32
|
@Composable
|
|
27
33
|
fun Image(
|
|
28
|
-
source:
|
|
34
|
+
source: Any,
|
|
29
35
|
modifier: Modifier = Modifier,
|
|
30
36
|
options: Options? = null,
|
|
31
37
|
loading: Boolean = true,
|
|
32
38
|
) {
|
|
33
39
|
modifier.semantics {contentDescription = "img|$source"; testTag = "img|$source"}
|
|
34
|
-
var imageOptions =
|
|
40
|
+
var imageOptions = Options()
|
|
41
|
+
var imageState by remember { mutableStateOf(ImageState.Loading) }
|
|
42
|
+
|
|
35
43
|
options?.let {
|
|
36
|
-
imageOptions =
|
|
44
|
+
imageOptions = Options(
|
|
37
45
|
alignment = options.alignment,
|
|
38
46
|
contentScale = options.contentScale,
|
|
39
47
|
colorFilter = options.colorFilter,
|
|
40
48
|
alpha = DefaultAlpha,
|
|
41
49
|
)
|
|
42
50
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
|
|
52
|
+
Box(modifier = modifier) {
|
|
53
|
+
AsyncImage(
|
|
54
|
+
modifier = modifier,
|
|
55
|
+
model = source,
|
|
56
|
+
contentDescription = null,
|
|
57
|
+
contentScale = imageOptions.contentScale,
|
|
58
|
+
alignment = imageOptions.alignment,
|
|
59
|
+
colorFilter = imageOptions.colorFilter,
|
|
60
|
+
alpha = imageOptions.alpha,
|
|
61
|
+
onLoading = { imageState = ImageState.Loading },
|
|
62
|
+
onSuccess = { imageState = ImageState.Success },
|
|
63
|
+
onError = { imageState = ImageState.Error },
|
|
64
|
+
)
|
|
65
|
+
when (imageState) {
|
|
66
|
+
ImageState.Loading -> Skeleton()
|
|
67
|
+
ImageState.Error -> Icon(
|
|
52
68
|
source = "media_fail",
|
|
53
69
|
color = AppTheme.current.colors.text.disable,
|
|
70
|
+
modifier = modifier
|
|
54
71
|
)
|
|
72
|
+
ImageState.Success -> {}
|
|
55
73
|
}
|
|
56
|
-
|
|
74
|
+
}
|
|
75
|
+
|
|
57
76
|
}
|
|
58
77
|
|
|
59
78
|
@Composable
|
|
@@ -32,6 +32,8 @@ import androidx.compose.ui.graphics.Color
|
|
|
32
32
|
import androidx.compose.ui.text.TextStyle
|
|
33
33
|
import androidx.compose.ui.text.font.FontWeight
|
|
34
34
|
import androidx.compose.ui.text.input.KeyboardType
|
|
35
|
+
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
|
36
|
+
import androidx.compose.ui.text.input.VisualTransformation
|
|
35
37
|
import androidx.compose.ui.unit.Dp
|
|
36
38
|
import androidx.compose.ui.unit.dp
|
|
37
39
|
import androidx.compose.ui.unit.sp
|
|
@@ -116,6 +118,36 @@ fun RenderRightIcon(loading: Boolean, icon: String, color: Color, onClick: () ->
|
|
|
116
118
|
}
|
|
117
119
|
}
|
|
118
120
|
|
|
121
|
+
@Composable
|
|
122
|
+
fun ErrorView(errorMessage: String, errorSpacing: Boolean, hintText: String) {
|
|
123
|
+
val errorColor = AppTheme.current.colors.error.primary
|
|
124
|
+
val hintColor = AppTheme.current.colors.text.hint
|
|
125
|
+
val hintTextDefault = hintText.ifEmpty { "Không thể chỉnh sửa" }
|
|
126
|
+
|
|
127
|
+
if (errorMessage.isNotEmpty() || hintText.isNotEmpty()) {
|
|
128
|
+
val color: Color =
|
|
129
|
+
if (errorMessage.isNotEmpty()) errorColor else hintColor
|
|
130
|
+
Row(
|
|
131
|
+
Modifier.padding(top = Spacing.XS),
|
|
132
|
+
verticalAlignment = Alignment.CenterVertically
|
|
133
|
+
) {
|
|
134
|
+
Icon(
|
|
135
|
+
size = 16.dp,
|
|
136
|
+
color = color,
|
|
137
|
+
source = "ic_error"
|
|
138
|
+
)
|
|
139
|
+
Text(
|
|
140
|
+
errorMessage.ifEmpty { hintTextDefault },
|
|
141
|
+
style = Typography.descriptionS,
|
|
142
|
+
modifier = Modifier.padding(start = Spacing.XS),
|
|
143
|
+
color = color,
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
} else if (errorSpacing) {
|
|
147
|
+
Spacer(Modifier.padding(top = Spacing.XS).height(18.dp))
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
119
151
|
@Composable
|
|
120
152
|
fun Input(
|
|
121
153
|
text: MutableState<String> = remember { mutableStateOf("") },
|
|
@@ -126,8 +158,12 @@ fun Input(
|
|
|
126
158
|
placeholder: String = "",
|
|
127
159
|
size: InputSize = InputSize.SMALL,
|
|
128
160
|
onChangeText: (String) -> Unit = {},
|
|
161
|
+
hintText: String = "",
|
|
129
162
|
error: String = "",
|
|
163
|
+
errorSpacing: Boolean = false,
|
|
130
164
|
disabled: Boolean = false,
|
|
165
|
+
readOnly: Boolean = false,
|
|
166
|
+
secureTextEntry: Boolean = false,
|
|
131
167
|
icon: String = "",
|
|
132
168
|
iconColor: Color = AppTheme.current.colors.text.default,
|
|
133
169
|
onRightIconPressed: () -> Unit = {},
|
|
@@ -141,6 +177,7 @@ fun Input(
|
|
|
141
177
|
keyboardType: KeyboardType = KeyboardType.Text,
|
|
142
178
|
) {
|
|
143
179
|
var isFocused by remember { mutableStateOf(false) }
|
|
180
|
+
var passHidden by remember { mutableStateOf(false) }
|
|
144
181
|
var isBlurred = false
|
|
145
182
|
val disabledColor = AppTheme.current.colors.text.disable
|
|
146
183
|
var textColor = AppTheme.current.colors.text.default
|
|
@@ -164,6 +201,7 @@ fun Input(
|
|
|
164
201
|
Column {
|
|
165
202
|
BasicTextField(
|
|
166
203
|
enabled = !disabled,
|
|
204
|
+
readOnly = readOnly,
|
|
167
205
|
singleLine = true,
|
|
168
206
|
value = text.value,
|
|
169
207
|
textStyle = TextStyle(
|
|
@@ -172,7 +210,8 @@ fun Input(
|
|
|
172
210
|
lineHeight = 24.sp,
|
|
173
211
|
fontWeight = fontWeight.value
|
|
174
212
|
),
|
|
175
|
-
|
|
213
|
+
visualTransformation = if (secureTextEntry && !passHidden) PasswordVisualTransformation() else VisualTransformation.None,
|
|
214
|
+
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = if (secureTextEntry) KeyboardType.Ascii else keyboardType),
|
|
176
215
|
modifier = Modifier.height(size.values.height).onFocusChanged {
|
|
177
216
|
isFocused = it.isFocused
|
|
178
217
|
if (it.isFocused) {
|
|
@@ -237,7 +276,7 @@ fun Input(
|
|
|
237
276
|
if (leadingIcon.isNotEmpty()) {
|
|
238
277
|
Icon(
|
|
239
278
|
source = leadingIcon,
|
|
240
|
-
modifier = Modifier.padding(end = Spacing.
|
|
279
|
+
modifier = Modifier.padding(end = Spacing.M),
|
|
241
280
|
size = if (size.name == InputSize.SMALL.name) 24.dp else 32.dp,
|
|
242
281
|
color = leadingIconColor
|
|
243
282
|
)
|
|
@@ -271,29 +310,23 @@ fun Input(
|
|
|
271
310
|
)
|
|
272
311
|
}
|
|
273
312
|
}
|
|
274
|
-
|
|
313
|
+
if (secureTextEntry && passHidden) {
|
|
314
|
+
val hidePassword = { onRightIconPressed(); passHidden = !passHidden}
|
|
315
|
+
RenderRightIcon(loading, "24_security_eye_open", iconTintColor,
|
|
316
|
+
hidePassword)
|
|
317
|
+
} else if (secureTextEntry && !passHidden) {
|
|
318
|
+
val showPassword = { onRightIconPressed(); passHidden = !passHidden}
|
|
319
|
+
RenderRightIcon(loading, "24_security_eye_off", iconTintColor,
|
|
320
|
+
showPassword)
|
|
321
|
+
} else {
|
|
322
|
+
RenderRightIcon(loading, icon, iconTintColor,
|
|
323
|
+
onRightIconPressed)
|
|
324
|
+
}
|
|
275
325
|
}
|
|
276
326
|
}
|
|
277
327
|
},
|
|
278
328
|
)
|
|
279
329
|
// show error
|
|
280
|
-
|
|
281
|
-
Row(
|
|
282
|
-
Modifier.padding(top = Spacing.XS),
|
|
283
|
-
verticalAlignment = Alignment.CenterVertically
|
|
284
|
-
) {
|
|
285
|
-
Icon(
|
|
286
|
-
size = 16.dp,
|
|
287
|
-
color = AppTheme.current.colors.error.primary,
|
|
288
|
-
source = "ic_error"
|
|
289
|
-
)
|
|
290
|
-
Text(
|
|
291
|
-
error,
|
|
292
|
-
style = Typography.descriptionS,
|
|
293
|
-
modifier = Modifier.padding(start = Spacing.XS),
|
|
294
|
-
color = AppTheme.current.colors.error.primary,
|
|
295
|
-
)
|
|
296
|
-
}
|
|
297
|
-
}
|
|
330
|
+
ErrorView(error, errorSpacing, hintText)
|
|
298
331
|
}
|
|
299
332
|
}
|