@momo-kits/native-kits 0.89.19 → 0.89.21

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 (43) hide show
  1. package/compose/build.gradle.kts +11 -15
  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 -27
  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/Button.kt +0 -7
  10. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +0 -2
  11. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +6 -8
  12. package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +0 -2
  13. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +37 -17
  14. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +54 -21
  15. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +155 -0
  16. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +2 -20
  17. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +185 -0
  18. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +0 -19
  19. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +2 -18
  20. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +58 -0
  21. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +37 -0
  22. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +65 -0
  23. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +43 -0
  24. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +0 -3
  25. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +0 -2
  26. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +14 -1
  27. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +2 -2
  28. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +28 -0
  29. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +1 -2
  30. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +18 -0
  31. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +0 -23
  32. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +33 -0
  33. package/gradle.properties +1 -1
  34. package/package.json +1 -1
  35. package/settings.gradle.kts +1 -0
  36. package/compose/src/androidMain/kotlin/vn/momo/kits/utils/Platform.kt +0 -3
  37. package/compose/src/androidMain/kotlin/vn/momo/kits/utils/SetMaskFilter.kt +0 -8
  38. package/compose/src/androidMain/kotlin/vn/momo/kits/utils/Utils.android.kt +0 -14
  39. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Platform.kt +0 -3
  40. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/SetMaskFilter.kt +0 -5
  41. package/compose/src/iosMain/kotlin/vn/momo/kits/utils/Platform.kt +0 -3
  42. package/compose/src/iosMain/kotlin/vn/momo/kits/utils/SetMaskFilter.kt +0 -9
  43. 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)
@@ -224,8 +224,6 @@ fun getTypeStyle(
224
224
  * @param enabled controls the enabled state of this button. When `false`,
225
225
  * this component will not respond to user input, and it will appear
226
226
  * visually disabled and disabled to accessibility services.
227
- * @param isRipple [Boolean] controls the ripple animation of button. When
228
- * `false`, the button won't have ripple animation.
229
227
  * @param useTintColor [Boolean] controls color of the icon.
230
228
  */
231
229
  @Composable
@@ -238,7 +236,6 @@ fun Button(
238
236
  title: String = "Button",
239
237
  loading: Boolean = false,
240
238
  enabled: Boolean = true,
241
- isRipple: Boolean = true,
242
239
  useTintColor: Boolean = true,
243
240
  isFull: Boolean = true,
244
241
  modifier: Modifier = Modifier,
@@ -247,8 +244,6 @@ fun Button(
247
244
  modifier
248
245
  .fillMaxWidth()
249
246
  .clip(RoundedCornerShape(size.value.radius)).clickable(
250
- interactionSource = remember { MutableInteractionSource() },
251
- indication = if (isRipple) rememberRipple() else null,
252
247
  enabled = enabled,
253
248
  onClick = onClick
254
249
  )
@@ -256,8 +251,6 @@ fun Button(
256
251
  } else {
257
252
  modifier
258
253
  .clip(RoundedCornerShape(size.value.radius)).clickable(
259
- interactionSource = remember { MutableInteractionSource() },
260
- indication = if (isRipple) rememberRipple() else null,
261
254
  enabled = enabled,
262
255
  onClick = onClick
263
256
  )
@@ -56,8 +56,6 @@ fun CheckBox(
56
56
  .clickable(
57
57
  onClick = onCheckedChange,
58
58
  enabled = !disabled,
59
- indication = rememberRipple(),
60
- interactionSource = remember { MutableInteractionSource() }
61
59
  ),
62
60
  ) {
63
61
  if (checked) {
@@ -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
  }
@@ -110,8 +110,6 @@ fun IconButton(
110
110
  .then(getIconStyle(type))
111
111
  .clip(RoundedCornerShape(size.value.radius))
112
112
  .clickable(
113
- interactionSource = remember { MutableInteractionSource() },
114
- indication = rememberRipple(),
115
113
  enabled = type == ButtonType.DISABLED,
116
114
  onClick = onClick
117
115
  ),
@@ -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,8 @@ 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 androidx.compose.ui.unit.dp
19
+ import coil3.compose.AsyncImage
16
20
  import vn.momo.kits.const.AppTheme
17
21
 
18
22
  data class Options(
@@ -22,38 +26,54 @@ data class Options(
22
26
  val alpha: Float = DefaultAlpha,
23
27
  )
24
28
 
25
- @OptIn(ExperimentalResourceApi::class)
29
+ enum class ImageState {
30
+ Loading, Success, Error
31
+ }
32
+
26
33
  @Composable
27
34
  fun Image(
28
- source: String,
35
+ source: Any,
29
36
  modifier: Modifier = Modifier,
30
37
  options: Options? = null,
31
38
  loading: Boolean = true,
32
39
  ) {
33
40
  modifier.semantics {contentDescription = "img|$source"; testTag = "img|$source"}
34
- var imageOptions = ImageOptions()
41
+ var imageOptions = Options()
42
+ var imageState by remember { mutableStateOf(ImageState.Loading) }
43
+
35
44
  options?.let {
36
- imageOptions = ImageOptions(
45
+ imageOptions = Options(
37
46
  alignment = options.alignment,
38
47
  contentScale = options.contentScale,
39
48
  colorFilter = options.colorFilter,
40
49
  alpha = DefaultAlpha,
41
50
  )
42
51
  }
43
- CoilImage(
44
- modifier = modifier,
45
- imageOptions = imageOptions,
46
- imageModel = { source },
47
- loading = {
48
- if (loading) Skeleton()
49
- },
50
- failure = {
51
- Icon(
52
+
53
+ Box(modifier = modifier) {
54
+ AsyncImage(
55
+ modifier = modifier,
56
+ model = source,
57
+ contentDescription = null,
58
+ contentScale = imageOptions.contentScale,
59
+ alignment = imageOptions.alignment,
60
+ colorFilter = imageOptions.colorFilter,
61
+ alpha = imageOptions.alpha,
62
+ onLoading = { imageState = ImageState.Loading },
63
+ onSuccess = { imageState = ImageState.Success },
64
+ onError = { imageState = ImageState.Error },
65
+ )
66
+ when (imageState) {
67
+ ImageState.Loading -> if (loading) Skeleton()
68
+ ImageState.Error -> Icon(
52
69
  source = "media_fail",
53
70
  color = AppTheme.current.colors.text.disable,
71
+ modifier = modifier
54
72
  )
73
+ ImageState.Success -> {}
55
74
  }
56
- )
75
+ }
76
+
57
77
  }
58
78
 
59
79
  @Composable