@momo-kits/native-kits 0.162.2-debug → 0.162.2-lottie.1-debug

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 (117) hide show
  1. package/.claude/settings.local.json +14 -0
  2. package/CLAUDE.md +78 -0
  3. package/compose/build.gradle.kts +12 -8
  4. package/compose/build.gradle.kts.backup +11 -7
  5. package/compose/compose.podspec +1 -1
  6. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +17 -8
  7. package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +2 -7
  8. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +3 -11
  9. package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +6 -24
  10. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +4 -19
  11. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +1 -10
  12. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +2 -2
  13. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +1 -1
  14. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +1 -1
  15. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +6 -17
  16. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +1 -1
  17. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +6 -101
  18. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Localize.kt +269 -0
  19. package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +5 -19
  20. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +8 -33
  21. package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +1 -1
  22. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +22 -25
  23. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +27 -29
  24. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BaselineView.kt +3 -26
  25. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +30 -31
  26. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +3 -13
  27. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +0 -5
  28. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +3 -11
  29. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Collapse.kt +3 -18
  30. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +11 -11
  31. package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +3 -11
  32. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +69 -26
  33. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +1 -5
  34. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +84 -59
  35. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +13 -71
  36. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +10 -51
  37. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +6 -1
  38. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +4 -21
  39. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +1 -1
  40. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +16 -75
  41. package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +5 -26
  42. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +1 -1
  43. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +3 -11
  44. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +16 -40
  45. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +3 -9
  46. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +0 -28
  47. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +2 -13
  48. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +4 -10
  49. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +3 -28
  50. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Stepper.kt +8 -47
  51. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Steps.kt +4 -25
  52. package/compose/src/commonMain/kotlin/vn/momo/kits/components/SuggestAction.kt +2 -11
  53. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +1 -17
  54. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +1 -6
  55. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +50 -111
  56. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +1 -5
  57. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +1 -5
  58. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +1 -12
  59. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +4 -96
  60. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +5 -7
  61. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Uploader.kt +2 -12
  62. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +9 -21
  63. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +2 -2
  64. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +9 -14
  65. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +8 -11
  66. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +1 -1
  67. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +3 -18
  68. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +1 -1
  69. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +0 -5
  70. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +0 -2
  71. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/DeprecatedModifier.kt +94 -4
  72. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +0 -2
  73. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +2 -2
  74. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +3 -7
  75. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +12 -16
  76. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +33 -24
  77. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +48 -33
  78. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +88 -64
  79. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +9 -14
  80. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +13 -31
  81. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +1 -1
  82. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +6 -24
  83. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +11 -6
  84. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +2 -1
  85. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +9 -6
  86. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +1 -1
  87. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +3 -13
  88. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +3 -2
  89. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/tracking/ScreenTracker.kt +1 -40
  90. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ComposeLottieAnimation.kt +15 -7
  91. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +6 -7
  92. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +2 -17
  93. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Tracking.kt +1 -1
  94. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +3 -3
  95. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/ScrollToTop.ios.kt +2 -3
  96. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +26 -5
  97. package/gradle/libs.versions.toml +2 -0
  98. package/gradle.properties +1 -1
  99. package/ios/Application/Components.swift +1 -0
  100. package/ios/Application/FloatingButton.swift +1 -0
  101. package/ios/Badge/Badge.swift +1 -0
  102. package/ios/Badge/BadgeRibbon.swift +2 -0
  103. package/ios/Button/Button.swift +1 -1
  104. package/ios/Checkbox/Checkbox.swift +1 -0
  105. package/ios/Information/Information.swift +151 -0
  106. package/ios/Input/Input.swift +1 -0
  107. package/ios/Input/InputPhoneNumber.swift +1 -0
  108. package/ios/Input/InputSearch.swift +3 -0
  109. package/ios/Input/InputTextArea.swift +1 -0
  110. package/ios/OTPKeyboard/KeyboardButton.swift +1 -1
  111. package/ios/Popup/PopupDisplay.swift +6 -0
  112. package/ios/Popup/PopupInput.swift +2 -0
  113. package/ios/Template/TrustBanner/TrustBanner.swift +2 -0
  114. package/ios/Typography/Text.swift +1 -0
  115. package/ios/Typography/Typography.swift +14 -5
  116. package/local.properties +8 -0
  117. package/package.json +1 -1
@@ -13,7 +13,7 @@ import vn.momo.kits.const.Typography
13
13
  import vn.momo.kits.modifier.setAutomationId
14
14
 
15
15
  @Composable
16
- fun HeaderTitle(
16
+ internal fun HeaderTitle(
17
17
  title: String = "",
18
18
  color: Color? = null,
19
19
  modifier: Modifier = Modifier.fillMaxWidth(),
@@ -2,17 +2,7 @@ package vn.momo.kits.navigation.component
2
2
 
3
3
  import androidx.compose.foundation.background
4
4
  import androidx.compose.foundation.border
5
- import androidx.compose.foundation.layout.Arrangement
6
- import androidx.compose.foundation.layout.Box
7
- import androidx.compose.foundation.layout.Column
8
- import androidx.compose.foundation.layout.Row
9
- import androidx.compose.foundation.layout.Spacer
10
- import androidx.compose.foundation.layout.fillMaxWidth
11
- import androidx.compose.foundation.layout.height
12
- import androidx.compose.foundation.layout.padding
13
- import androidx.compose.foundation.layout.size
14
- import androidx.compose.foundation.layout.width
15
- import androidx.compose.foundation.layout.widthIn
5
+ import androidx.compose.foundation.layout.*
16
6
  import androidx.compose.foundation.shape.CircleShape
17
7
  import androidx.compose.foundation.shape.RoundedCornerShape
18
8
  import androidx.compose.foundation.text.BasicText
@@ -43,7 +33,7 @@ import vn.momo.kits.navigation.component.HeaderTitle.User
43
33
  import vn.momo.kits.platform.getScreenDimensions
44
34
 
45
35
  @Composable
46
- fun HeaderUser(
36
+ internal fun HeaderUser(
47
37
  data: HeaderTitle
48
38
  ) {
49
39
  if (data !is User) return
@@ -367,4 +357,4 @@ private fun RemoteIcon16(url: String) {
367
357
  }
368
358
 
369
359
  @Immutable
370
- data class AvatarUrls(val items: List<String>)
360
+ private data class AvatarUrls(val items: List<String>)
@@ -29,6 +29,7 @@ import vn.momo.kits.navigation.LocalFooterHeightPx
29
29
  import vn.momo.kits.navigation.LocalNavigator
30
30
  import vn.momo.kits.navigation.LocalOptions
31
31
  import vn.momo.kits.navigation.OverplayComponentRegistry
32
+ import kotlin.time.Duration.Companion.milliseconds
32
33
 
33
34
  sealed class SnackBar(open val duration: Long? = null) {
34
35
  data class Custom(
@@ -42,7 +43,7 @@ sealed class SnackBar(open val duration: Long? = null) {
42
43
  }
43
44
 
44
45
  @Composable
45
- fun SnackBar(
46
+ internal fun SnackBar(
46
47
  data: SnackBar,
47
48
  onDismiss: (() -> Unit)?
48
49
  ) {
@@ -95,7 +96,7 @@ fun SnackBar(
95
96
  LaunchedEffect(data.duration) {
96
97
  val duration = data.duration
97
98
  if (duration != null) {
98
- delay(duration)
99
+ delay(duration.milliseconds)
99
100
  navigator.hideSnackBar()
100
101
  }
101
102
  }
@@ -5,13 +5,9 @@ import vn.momo.maxapi.IMaxApi
5
5
  import kotlin.time.Clock
6
6
  import kotlin.time.ExperimentalTime
7
7
 
8
- /**
9
- * Screen tracking state holder
10
- */
11
8
  class ScreenTrackingState {
12
9
  @OptIn(ExperimentalTime::class)
13
- var startTime: Long = Clock.System.now().toEpochMilliseconds()
14
- var endTime: Long = startTime
10
+ var time: Long = Clock.System.now().toEpochMilliseconds()
15
11
  var releaseNavigated: Boolean = false
16
12
  var releaseLoad: Boolean = false
17
13
  var releaseInteraction: Boolean = false
@@ -19,22 +15,10 @@ class ScreenTrackingState {
19
15
  var action: String = "push"
20
16
  }
21
17
 
22
- /**
23
- * Screen tracker for auto tracking events
24
- *
25
- * Tracks 3 main events:
26
- * - auto_screen_navigated: When screen is focused
27
- * - auto_screen_displayed: When screen finished loading
28
- * - auto_screen_interacted: When screen is ready for interaction
29
- */
30
18
  object ScreenTracker {
31
19
 
32
20
  private var lastScreenName: String? = null
33
21
 
34
- /**
35
- * Track screen navigation event
36
- * Called when screen is focused
37
- */
38
22
  fun trackScreenNavigated(
39
23
  maxApi: IMaxApi?,
40
24
  context: MiniAppContext?,
@@ -61,10 +45,6 @@ object ScreenTracker {
61
45
  state.previousScreenName = previousScreen
62
46
  }
63
47
 
64
- /**
65
- * Track screen displayed event
66
- * Called when screen finished loading all elements
67
- */
68
48
  fun trackScreenDisplayed(
69
49
  maxApi: IMaxApi?,
70
50
  context: MiniAppContext?,
@@ -85,10 +65,6 @@ object ScreenTracker {
85
65
  state.releaseLoad = true
86
66
  }
87
67
 
88
- /**
89
- * Track screen interacted event
90
- * Called when screen is ready for user interaction
91
- */
92
68
  fun trackScreenInteracted(
93
69
  maxApi: IMaxApi?,
94
70
  context: MiniAppContext?,
@@ -111,9 +87,6 @@ object ScreenTracker {
111
87
  state.releaseInteraction = true
112
88
  }
113
89
 
114
- /**
115
- * Build base tracking params from MiniAppContext
116
- */
117
90
  private fun buildBaseParams(
118
91
  context: MiniAppContext?,
119
92
  screenName: String,
@@ -128,12 +101,6 @@ object ScreenTracker {
128
101
  )
129
102
  }
130
103
 
131
- /**
132
- * Track popup/modal displayed event
133
- *
134
- * @param popupName name of the popup/modal
135
- * @param parentScreenName the screen that shows the popup
136
- */
137
104
  fun trackPopupDisplayed(
138
105
  maxApi: IMaxApi?,
139
106
  context: MiniAppContext?,
@@ -152,14 +119,8 @@ object ScreenTracker {
152
119
  )
153
120
  }
154
121
 
155
- /**
156
- * Get previous screen name for back navigation detection
157
- */
158
122
  fun getLastScreenName(): String? = lastScreenName
159
123
 
160
- /**
161
- * Reset tracker (for testing or cleanup)
162
- */
163
124
  @Suppress("UNUSED")
164
125
  fun reset() {
165
126
  lastScreenName = null
@@ -6,9 +6,9 @@ import androidx.compose.foundation.layout.Box
6
6
  import androidx.compose.foundation.layout.fillMaxSize
7
7
  import androidx.compose.runtime.Composable
8
8
  import androidx.compose.runtime.getValue
9
+ import androidx.compose.runtime.key
9
10
  import androidx.compose.ui.Modifier
10
11
  import androidx.compose.ui.graphics.Color
11
- import io.github.alexzhirkevich.compottie.Compottie
12
12
  import io.github.alexzhirkevich.compottie.ExperimentalCompottieApi
13
13
  import io.github.alexzhirkevich.compottie.LottieCompositionSpec
14
14
  import io.github.alexzhirkevich.compottie.animateLottieCompositionAsState
@@ -23,6 +23,9 @@ internal fun ComposeLottieAnimation(
23
23
  path: String,
24
24
  tintColor: Color?,
25
25
  bgColor: Color?,
26
+ iterations: Int,
27
+ isPlaying: Boolean,
28
+ restartKey: Any?,
26
29
  modifier: Modifier,
27
30
  ) {
28
31
  val json = readJson(path)
@@ -36,8 +39,6 @@ internal fun ComposeLottieAnimation(
36
39
  LottieCompositionSpec.JsonString(json)
37
40
  }
38
41
 
39
- // Recolor only the fill/stroke color slots (matching the native lottie-ios per-keypath tint)
40
- // instead of a global ColorFilter.tint, which would flatten a multicolor animation to a silhouette.
41
42
  val dynamicProperties = if (tintColor != null) {
42
43
  rememberLottieDynamicProperties(tintColor) {
43
44
  shapeLayer("**") {
@@ -49,10 +50,17 @@ internal fun ComposeLottieAnimation(
49
50
  null
50
51
  }
51
52
 
52
- val lottieState = animateLottieCompositionAsState(
53
- composition = composition,
54
- iterations = Compottie.IterateForever,
55
- )
53
+ // Our sentinel LottieIterateForever == Compottie.IterateForever (both Int.MAX_VALUE).
54
+ // key(restartKey) recreates the animation state so a new key replays from frame 0;
55
+ // restartOnPlay = false makes isPlaying resume from the current frame instead.
56
+ val lottieState = key(restartKey) {
57
+ animateLottieCompositionAsState(
58
+ composition = composition,
59
+ iterations = iterations,
60
+ isPlaying = isPlaying,
61
+ restartOnPlay = false,
62
+ )
63
+ }
56
64
 
57
65
  Box(modifier.background(bgColor ?: Color.Transparent)) {
58
66
  Image(
@@ -43,20 +43,19 @@ fun supportsImePadding(): Boolean = when (val v = getOSVersion()) {
43
43
  is OSVersion.Android -> v.sdk > 29
44
44
  is OSVersion.IOS -> true
45
45
  }
46
+ /** Sentinel for [LottieAnimation.iterations] meaning "loop forever" (kept as the default). */
47
+ const val LottieIterateForever: Int = Int.MAX_VALUE
46
48
 
47
- /**
48
- * @param useCompose when `true`, renders via the pure-Compose Compottie engine instead of the
49
- * platform-native engine (airbnb-lottie on Android, lottie-ios on iOS). Defaults to `false` for
50
- * backward compatibility. Note: when `useCompose = true`, `tintColor` recolors only the fill/stroke
51
- * color slots (matching the native lottie-ios per-keypath tint), so gradient and text-layer colors
52
- * are left untinted; `placedAsOverlay` is a no-op.
53
- */
54
49
  @Composable
55
50
  expect fun LottieAnimation(
56
51
  path: String,
57
52
  tintColor: Color? = null,
58
53
  bgColor: Color? = null,
59
54
  placedAsOverlay: Boolean = false,
55
+ iterations: Int = LottieIterateForever,
56
+ // isPlaying pauses/resumes from the current frame; assign a new restartKey to replay from frame 0.
57
+ isPlaying: Boolean = true,
58
+ restartKey: Any? = null,
60
59
  modifier: Modifier = Modifier,
61
60
  useCompose: Boolean = false
62
61
  )
@@ -1,28 +1,13 @@
1
1
  package vn.momo.kits.utils
2
2
 
3
- import androidx.compose.runtime.Composable
4
- import androidx.compose.runtime.LaunchedEffect
5
- import androidx.compose.runtime.MutableState
6
- import androidx.compose.runtime.getValue
7
- import androidx.compose.runtime.mutableStateOf
8
- import androidx.compose.runtime.remember
3
+ import androidx.compose.runtime.*
9
4
  import org.jetbrains.compose.resources.DrawableResource
10
5
  import org.jetbrains.compose.resources.InternalResourceApi
11
6
  import org.jetbrains.compose.resources.ResourceItem
12
7
  import org.jetbrains.compose.resources.readResourceBytes
13
-
14
- @OptIn(InternalResourceApi::class)
15
- @Composable
16
- fun getResource(name: String): DrawableResource {
17
- return DrawableResource(
18
- "drawable:$name",
19
- setOf(ResourceItem(setOf(), "drawable/$name", -1, -1))
20
- )
21
- }
22
-
23
8
  @OptIn(InternalResourceApi::class)
24
9
  @Composable
25
- fun readJson(name: String): String {
10
+ internal fun readJson(name: String): String {
26
11
  val path = name.plus(".json")
27
12
  val candidatePaths = listOf(
28
13
  path,
@@ -4,7 +4,7 @@ import androidx.compose.runtime.Composable
4
4
  import vn.momo.kits.application.LocalComponentInformation
5
5
 
6
6
  @Composable
7
- fun getComponentId(
7
+ internal fun getComponentId(
8
8
  componentName: String? = null,
9
9
  accessibilityLabel: String? = null,
10
10
  ): String {
@@ -41,8 +41,8 @@ internal fun Modifier.bottomBorder(strokeWidth: Dp, color: Color) = composed(
41
41
  }
42
42
  )
43
43
 
44
- inline fun <T> Boolean.ifTrue(trueCase: T, falseCase: T): T = if (this) trueCase else falseCase
45
- inline fun <T> String.ifNotNull(trueCase: T, falseCase: T): T =
44
+ fun <T> Boolean.ifTrue(trueCase: T, falseCase: T): T = if (this) trueCase else falseCase
45
+ fun <T> String.ifNotNull(trueCase: T, falseCase: T): T =
46
46
  if (this.isBlank()) falseCase else trueCase
47
47
 
48
48
  fun formatNumberToMoney(number: Long, currency: String = "đ"): String {
@@ -91,7 +91,7 @@ fun getAppStatusBarHeight(): Dp {
91
91
  }
92
92
 
93
93
  @Composable
94
- fun getNavigationBarHeight(): Dp {
94
+ internal fun getNavigationBarHeight(): Dp {
95
95
  return if (AppNavigationBar.current == 0.dp) {
96
96
  val systemBottomPadding = WindowInsets.systemBars.asPaddingValues().calculateBottomPadding()
97
97
  if (getPlatformName() == "iOS") {
@@ -13,14 +13,13 @@ private const val STATUS_BAR_TAPPED_NOTIFICATION = "statusBarSelected"
13
13
  internal actual fun RegisterScrollToTop(callback: (() -> Unit)?) {
14
14
  val maxApi = LocalMaxApi.current
15
15
  DisposableEffect(callback) {
16
- val cb = callback
17
- val token: NSObjectProtocol? = if (cb != null) {
16
+ val token: NSObjectProtocol? = if (callback != null) {
18
17
  NSNotificationCenter.defaultCenter.addObserverForName(
19
18
  name = STATUS_BAR_TAPPED_NOTIFICATION,
20
19
  `object` = null,
21
20
  queue = NSOperationQueue.mainQueue,
22
21
  ) { _: NSNotification? ->
23
- runCatching { cb() }.onFailure {
22
+ runCatching { callback() }.onFailure {
24
23
  maxApi?.logFile("[ScrollToTop] callback threw: ${it.message}") {}
25
24
 
26
25
  }
@@ -19,8 +19,6 @@ import androidx.compose.ui.backhandler.LocalCompatNavigationEventDispatcherOwner
19
19
  import androidx.compose.ui.graphics.Color
20
20
  import androidx.compose.ui.graphics.NativePaint
21
21
  import androidx.compose.ui.graphics.toArgb
22
- import androidx.compose.ui.semantics.SemanticsPropertyKey
23
- import androidx.compose.ui.semantics.semantics
24
22
  import androidx.compose.ui.unit.Dp
25
23
  import androidx.compose.ui.unit.dp
26
24
  import androidx.compose.ui.viewinterop.UIKitInteropProperties
@@ -98,20 +96,41 @@ actual fun LottieAnimation(
98
96
  tintColor: Color?,
99
97
  bgColor: Color?,
100
98
  placedAsOverlay: Boolean,
99
+ iterations: Int,
100
+ isPlaying: Boolean,
101
+ restartKey: Any?,
101
102
  modifier: Modifier,
102
103
  useCompose: Boolean
103
104
  ) {
104
105
  if (useCompose) {
105
- ComposeLottieAnimation(path, tintColor, bgColor, modifier)
106
+ ComposeLottieAnimation(path, tintColor, bgColor, iterations, isPlaying, restartKey, modifier)
106
107
  return
107
108
  }
108
109
 
110
+ // lottie-ios uses -1 for infinite looping; map our IterateForever sentinel onto it.
111
+ val loopCount = if (iterations == LottieIterateForever) -1.0 else iterations.toDouble()
112
+
109
113
  var animation by remember { mutableStateOf<CompatibleAnimation?>(null) }
114
+ // Held so the play/pause/restart effects below can drive the imperative lottie-ios view.
115
+ val animationView = remember { mutableStateOf<CompatibleAnimationView?>(null) }
110
116
 
111
117
  LaunchedEffect(Unit) {
112
118
  animation = CompatibleAnimation(name = "compose-resources/composeResources/vn.momo.compose.resources/".plus(path), subdirectory = null, NSBundle.mainBundle)
113
119
  }
114
120
 
121
+ // Pause / resume from the current frame.
122
+ LaunchedEffect(animationView.value, isPlaying) {
123
+ val view = animationView.value ?: return@LaunchedEffect
124
+ if (isPlaying) view.play() else view.pause()
125
+ }
126
+
127
+ // Replay from frame 0 whenever restartKey changes (and on first attach).
128
+ LaunchedEffect(animationView.value, restartKey) {
129
+ val view = animationView.value ?: return@LaunchedEffect
130
+ view.stop()
131
+ if (isPlaying) view.play()
132
+ }
133
+
115
134
  when (val value = animation) {
116
135
  null -> {
117
136
  Box(modifier)
@@ -128,7 +147,7 @@ actual fun LottieAnimation(
128
147
 
129
148
  setBackgroundColor(bgColor?.toUIColor() ?: UIColor.clearColor)
130
149
 
131
- setLoopAnimationCount(-1.0)
150
+ setLoopAnimationCount(loopCount)
132
151
  setAnimationSpeed(1.0)
133
152
 
134
153
  if (tintColor != null) {
@@ -142,12 +161,14 @@ actual fun LottieAnimation(
142
161
  setColorValue(uiColor, CompatibleAnimationKeypath("**.Stroke.Color"))
143
162
  }
144
163
 
145
- play()
164
+ // Playback is driven by the LaunchedEffects above via animationView.
165
+ animationView.value = this
146
166
  }
147
167
  },
148
168
 
149
169
  update = { view ->
150
170
  view.setBackgroundColor(bgColor?.toUIColor() ?: UIColor.clearColor)
171
+ view.setLoopAnimationCount(loopCount)
151
172
 
152
173
  if (tintColor != null) {
153
174
  val uiColor = tintColor.toUIColor()
@@ -21,6 +21,7 @@ maxapi = "0.1.1"
21
21
  vanniktechMavenPublish = "0.34.0"
22
22
  kits = "0.159.1-beta.7"
23
23
  nativemaxapi = "0.0.6"
24
+ gif-image = "0.1.2"
24
25
 
25
26
  [libraries]
26
27
  ktor-client-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor-version" }
@@ -53,6 +54,7 @@ androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "a
53
54
  androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" }
54
55
  material = { module = "com.google.android.material:material", version.ref = "material" }
55
56
 
57
+ gif-image = { module = "vn.momo.kits:gif-image", version.ref = "gif-image" }
56
58
  [plugins]
57
59
  jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
58
60
  jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
package/gradle.properties CHANGED
@@ -18,7 +18,7 @@ kotlin.apple.xcodeCompatibility.nowarn=true
18
18
  name="ComposeKits"
19
19
  group=vn.momo.kits
20
20
  artifact.id=kits
21
- version=0.162.2
21
+ version=0.162.2-lottie.1
22
22
 
23
23
  repo=GitLab
24
24
  url=https://gitlab.mservice.com.vn/api/v4/projects/5400/packages/maven
@@ -245,6 +245,7 @@ public struct Header: View {
245
245
  } else {
246
246
  Text(title)
247
247
  .font(.system(size: 17, weight: .bold))
248
+ .ignoreBoldTextSetting()
248
249
  .foregroundColor(tintColor ?? Colors.black17)
249
250
  .frame(maxWidth: titlePosition == .center ? UIScreen.main.bounds.width - 252 : nil)
250
251
  .frame(maxWidth: titlePosition == .center ? .infinity : UIScreen.main.bounds.width - 172, alignment: titlePosition == .center ? .center : .leading)
@@ -155,6 +155,7 @@ public struct FloatingButton: View {
155
155
  Text(label)
156
156
  .foregroundColor(.white)
157
157
  .font(.system(size: scaleSize(16), weight: .bold))
158
+ .ignoreBoldTextSetting()
158
159
  .lineLimit(1)
159
160
  .background(
160
161
  GeometryReader { geo in
@@ -54,6 +54,7 @@ public struct Badge: View {
54
54
  public var body: some View {
55
55
  Text(formatTitle(label))
56
56
  .font(.system(size: scaleSize(10), weight: .bold))
57
+ .ignoreBoldTextSetting()
57
58
  .foregroundColor(Colors.black01)
58
59
  .padding(.horizontal, Spacing.XS)
59
60
  .frame(minWidth: scaleSize(16), minHeight: scaleSize(16))
@@ -138,6 +138,7 @@ public struct BadgeRibbon: View {
138
138
  HStack(spacing: 0) {
139
139
  Text(label)
140
140
  .font(.system(size: scaleSize(12), weight: .medium))
141
+ .ignoreBoldTextSetting()
141
142
  .foregroundColor(Colors.black01)
142
143
  .lineLimit(1)
143
144
  .rotationEffect(rotation)
@@ -159,6 +160,7 @@ public struct BadgeRibbon: View {
159
160
  HStack(spacing: 0) {
160
161
  Text(label)
161
162
  .font(.system(size: scaleSize(12), weight: .medium))
163
+ .ignoreBoldTextSetting()
162
164
  .foregroundColor(Colors.black01)
163
165
  .lineLimit(1)
164
166
  .rotationEffect(rotation)
@@ -200,7 +200,7 @@ public struct Button: View {
200
200
  .background(bg)
201
201
  .overlay(
202
202
  RoundedRectangle(cornerRadius: size.radius)
203
- .stroke(border ?? .clear, lineWidth: border != nil ? borderWidth : 0)
203
+ .strokeBorder(border ?? .clear, lineWidth: border != nil ? borderWidth : 0)
204
204
  )
205
205
  .clipShape(RoundedRectangle(cornerRadius: size.radius))
206
206
  .opacity(loading ? 0.75 : 1)
@@ -55,6 +55,7 @@ public struct Checkbox: View{
55
55
  if !title.isEmpty {
56
56
  Text(title)
57
57
  .font(.description1)
58
+ .ignoreBoldTextSetting()
58
59
  }
59
60
  }
60
61
  }
@@ -0,0 +1,151 @@
1
+ import SwiftUI
2
+
3
+ public enum InformationState {
4
+ case `default`
5
+ case warning
6
+ case error
7
+ }
8
+
9
+ public struct Information: View {
10
+ private let title: String?
11
+ private let description: String
12
+ private let state: InformationState
13
+ private let image: String?
14
+ private let onPressAction: (() -> Void)?
15
+ private let showIcon: Bool
16
+ private let action: String?
17
+ private let showIconClose: Bool
18
+ private let onPressClose: (() -> Void)?
19
+
20
+ public init(
21
+ title: String? = nil,
22
+ description: String = "Description",
23
+ state: InformationState = .default,
24
+ image: String? = nil,
25
+ onPressAction: (() -> Void)? = nil,
26
+ showIcon: Bool = true,
27
+ action: String? = nil,
28
+ showIconClose: Bool = true,
29
+ onPressClose: (() -> Void)? = nil
30
+ ) {
31
+ self.title = title
32
+ self.description = description
33
+ self.state = state
34
+ self.image = image
35
+ self.onPressAction = onPressAction
36
+ self.showIcon = showIcon
37
+ self.action = action
38
+ self.showIconClose = showIconClose
39
+ self.onPressClose = onPressClose
40
+ }
41
+
42
+ private var borderColor: Color {
43
+ switch state {
44
+ case .default: return Colors.blue07
45
+ case .warning: return Colors.yellow06
46
+ case .error: return Colors.red07
47
+ }
48
+ }
49
+
50
+ private var backgroundColor: Color {
51
+ switch state {
52
+ case .default: return Colors.blue10
53
+ case .warning: return Colors.yellow09
54
+ case .error: return Colors.red10
55
+ }
56
+ }
57
+
58
+ private var accentColor: Color {
59
+ switch state {
60
+ case .default: return Colors.blue03
61
+ case .warning: return Colors.orange03
62
+ case .error: return Colors.red03
63
+ }
64
+ }
65
+
66
+ private var iconSource: String {
67
+ switch state {
68
+ case .default: return "notifications_info"
69
+ case .warning: return "24_notifications_alert_triangle"
70
+ case .error: return "24_notifications_alert_octagon"
71
+ }
72
+ }
73
+
74
+ public var body: some View {
75
+ VStack(alignment: .leading, spacing: 0) {
76
+ HStack(alignment: .top, spacing: 0) {
77
+ if let image {
78
+ ImageView(image)
79
+ .frame(width: 40, height: 40)
80
+ .clipShape(RoundedRectangle(cornerRadius: Radius.S))
81
+ .padding(.trailing, Spacing.S)
82
+ } else if showIcon {
83
+ Icon(source: iconSource, size: 16, color: accentColor)
84
+ .padding(.trailing, Spacing.S)
85
+ }
86
+
87
+ VStack(alignment: .leading, spacing: 0) {
88
+ if let title {
89
+ MomoText(title, typography: .labelDefaultMedium)
90
+ }
91
+ MomoText(description, typography: .descriptionDefaultRegular)
92
+ }
93
+ .frame(maxWidth: .infinity, alignment: .leading)
94
+
95
+ if let onPressClose, showIconClose {
96
+ Icon(source: "navigation_close", size: 16, color: Colors.black17)
97
+ .contentShape(Rectangle())
98
+ .onTapGesture(perform: onPressClose)
99
+ }
100
+ }
101
+
102
+ if let action, let onPressAction {
103
+ MomoText(action, typography: .actionXsBold, color: accentColor)
104
+ .frame(maxWidth: .infinity, alignment: .trailing)
105
+ .contentShape(Rectangle())
106
+ .onTapGesture(perform: onPressAction)
107
+ .padding(.top, Spacing.S)
108
+ }
109
+ }
110
+ .padding(Spacing.M)
111
+ .background(
112
+ RoundedRectangle(cornerRadius: Radius.S)
113
+ .fill(backgroundColor)
114
+ )
115
+ .overlay(
116
+ RoundedRectangle(cornerRadius: Radius.S)
117
+ .stroke(borderColor, lineWidth: 0.5)
118
+ )
119
+ }
120
+ }
121
+
122
+ // MARK: - Preview
123
+ #if DEBUG
124
+ struct Information_Previews: PreviewProvider {
125
+ static var previews: some View {
126
+ VStack(spacing: 16) {
127
+ Information(
128
+ title: "Information",
129
+ description: "This is a default information message."
130
+ )
131
+ Information(
132
+ title: "Warning",
133
+ description: "This is a warning message.",
134
+ state: .warning
135
+ )
136
+ Information(
137
+ title: "Error",
138
+ description: "This is an error message.",
139
+ state: .error,
140
+ onPressAction: {}, action: "Retry"
141
+ )
142
+ Information(
143
+ description: "Dismissible information.",
144
+ onPressClose: {}
145
+ )
146
+ }
147
+ .padding()
148
+ .previewLayout(.sizeThatFits)
149
+ }
150
+ }
151
+ #endif
@@ -157,6 +157,7 @@ public struct Input: View {
157
157
  })
158
158
  .keyboardType(keyboardType)
159
159
  .font(fontWeight == .bold ? .action_s_bold : .body_default_regular)
160
+ .ignoreBoldTextSetting()
160
161
  .foregroundColor(getTextColor())
161
162
  .disabled(disabled || readOnly)
162
163
  .applyPrimaryCursorColor()
@@ -94,6 +94,7 @@ public struct InputPhoneNumber: View {
94
94
  })
95
95
  .keyboardType(.numberPad)
96
96
  .font(size == .small ? .header_s_semibold : .header_m_bold)
97
+ .ignoreBoldTextSetting()
97
98
  .foregroundColor(Colors.black17)
98
99
  .applyPrimaryCursorColor()
99
100
  .lineLimit(1)