@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.
Files changed (38) hide show
  1. package/compose/build.gradle.kts +18 -14
  2. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +22 -0
  3. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Components.kt +71 -25
  4. package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +200 -0
  5. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +47 -0
  6. package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +7 -139
  7. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +89 -22
  8. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +17 -1
  9. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +6 -8
  10. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +36 -17
  11. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +54 -21
  12. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +155 -0
  13. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +2 -20
  14. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +185 -0
  15. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +0 -19
  16. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +2 -18
  17. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +58 -0
  18. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +37 -0
  19. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +65 -0
  20. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +43 -0
  21. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +0 -3
  22. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +14 -1
  23. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +2 -2
  24. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +28 -0
  25. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +1 -2
  26. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +18 -0
  27. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +0 -23
  28. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +33 -0
  29. package/package.json +1 -1
  30. package/settings.gradle.kts +1 -0
  31. package/compose/src/androidMain/kotlin/vn/momo/kits/utils/Platform.kt +0 -3
  32. package/compose/src/androidMain/kotlin/vn/momo/kits/utils/SetMaskFilter.kt +0 -8
  33. package/compose/src/androidMain/kotlin/vn/momo/kits/utils/Utils.android.kt +0 -14
  34. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Platform.kt +0 -3
  35. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/SetMaskFilter.kt +0 -5
  36. package/compose/src/iosMain/kotlin/vn/momo/kits/utils/Platform.kt +0 -3
  37. package/compose/src/iosMain/kotlin/vn/momo/kits/utils/SetMaskFilter.kt +0 -9
  38. 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
- AppStatusBar provides statusBarHeight,
59
+ AppNavigator provides Navigator(),
60
+ AppStatusBar provides WindowInsets.systemBars.asPaddingValues().calculateTopPadding(),
101
61
  ) {
102
- BottomSheetNavigator(sheetBackgroundColor = Color.Transparent, sheetElevation = 0.dp) {
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
- class SheetScreen(private val content: @Composable () -> Unit) : Screen {
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().background(backgroundColor ?: AppTheme.current.colors.background.default)
86
+ Modifier.fillMaxSize()
87
+ .background(backgroundColor ?: AppTheme.current.colors.background.default)
67
88
  ) {
68
- HeaderImage(headerType, headerBackground ?: AppTheme.current.assets.headerBackground)
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().pointerInput(Unit) {
71
- detectTapGestures(onTap = {
72
- keyboardController?.hide()
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(rememberScrollState()) else
131
+ .verticalScroll(scrollState) else
88
132
  Modifier.padding(bottom = keyboardSize),
89
133
  verticalArrangement = verticalArrangement,
90
134
  horizontalAlignment = horizontalAlignment
91
135
  ) {
92
- content()
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 = - keyboardSize)
108
- .padding(bottom = min(indicator, 21.dp))
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
- import vn.momo.kits.utils.primaryColors
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 com.skydoves.landscapist.ImageOptions
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
- CoilImage(
31
+ AsyncImage(
33
32
  modifier = Modifier.fillMaxSize(),
34
- imageOptions = ImageOptions(
35
- contentScale = ContentScale.Fit,
36
- colorFilter = color?.let { ColorFilter.tint(it) }
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 com.skydoves.landscapist.ImageOptions
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
- @OptIn(ExperimentalResourceApi::class)
28
+ enum class ImageState {
29
+ Loading, Success, Error
30
+ }
31
+
26
32
  @Composable
27
33
  fun Image(
28
- source: String,
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 = ImageOptions()
40
+ var imageOptions = Options()
41
+ var imageState by remember { mutableStateOf(ImageState.Loading) }
42
+
35
43
  options?.let {
36
- imageOptions = 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
- CoilImage(
44
- modifier = modifier,
45
- imageOptions = imageOptions,
46
- imageModel = { source },
47
- loading = {
48
- if (loading) Skeleton()
49
- },
50
- failure = {
51
- Icon(
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
- keyboardOptions = KeyboardOptions.Default.copy(keyboardType = keyboardType),
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.XS),
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
- RenderRightIcon(loading, icon, iconTintColor, onRightIconPressed)
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
- if (error.isNotEmpty()) {
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
  }