@momo-kits/native-kits 0.167.1-duo.2-debug → 0.167.1-duo.20-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 (194) hide show
  1. package/CLAUDE.md +36 -8
  2. package/README.md +2 -2
  3. package/compose/build.gradle.kts +15 -1
  4. package/compose/build.gradle.kts.backup +14 -0
  5. package/compose/compose.podspec +1 -1
  6. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +2 -0
  7. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/ReservedRegions.android.kt +6 -0
  8. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +168 -52
  9. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +20 -21
  10. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +2 -1
  11. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +4 -2
  12. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +1 -1
  13. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +24 -14
  14. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +1 -1
  15. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +1 -1
  16. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +9 -6
  17. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +11 -12
  18. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +4 -9
  19. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +2 -2
  20. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +10 -9
  21. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +6 -6
  22. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +53 -26
  23. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +12 -2
  24. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +7 -9
  25. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +1 -1
  26. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +13 -6
  27. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +13 -5
  28. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +3 -2
  29. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +14 -6
  30. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +19 -21
  31. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +2 -2
  32. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +5 -2
  33. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +3 -1
  34. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +7 -15
  35. package/compose/src/commonMain/kotlin/vn/momo/kits/const/HostFont.kt +4 -0
  36. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +10 -4
  37. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +3 -17
  38. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/ScreenLayout.kt +173 -21
  39. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +43 -4
  40. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +8 -0
  41. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +10 -0
  42. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +153 -48
  43. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +114 -71
  44. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +8 -9
  45. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoBarCompression.kt +98 -0
  46. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoTabBar.kt +312 -0
  47. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +84 -18
  48. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +33 -13
  49. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderJourney.kt +2 -1
  50. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +51 -19
  51. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +4 -2
  52. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +1 -1
  53. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SideBar.kt +46 -23
  54. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +2 -1
  55. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +3 -0
  56. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ReservedRegions.kt +13 -0
  57. package/compose/src/iosMain/kotlin/vn/momo/kits/const/HostFont.ios.kt +38 -0
  58. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +4 -0
  59. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/ReservedRegions.ios.kt +94 -0
  60. package/gradle.properties +1 -1
  61. package/ios/Application/ApplicationEnvironment.swift +17 -0
  62. package/ios/Application/MaxApi.swift +12 -3
  63. package/ios/Avatar/Avatar.swift +4 -3
  64. package/ios/Badge/Badge.swift +6 -4
  65. package/ios/Badge/BadgeDot.swift +1 -1
  66. package/ios/Badge/BadgeRibbon.swift +0 -1
  67. package/ios/Button/Button.swift +15 -12
  68. package/ios/CalculatorKeyboard/CalculatorKeyboard.swift +64 -18
  69. package/ios/Carousel/Carousel.swift +29 -12
  70. package/ios/Checkbox/Checkbox.swift +7 -3
  71. package/ios/Chip/Chip.swift +5 -4
  72. package/ios/Collapse/Collapse.swift +70 -19
  73. package/ios/Colors+Radius+Spacing/Colors.swift +10 -1
  74. package/ios/Colors+Radius+Spacing/Radius.swift +6 -0
  75. package/ios/DateTimePicker/DateTimePicker.swift +54 -20
  76. package/ios/DateTimePicker/WheelPicker.swift +103 -105
  77. package/ios/Divider/Divider.swift +37 -4
  78. package/ios/Extensions/Color++.swift +38 -9
  79. package/ios/Extensions/OptionalEnvironmentObject.swift +12 -0
  80. package/ios/Extensions/SkeletonUICompat.swift +11 -0
  81. package/ios/Icon/Icon.swift +21 -23
  82. package/ios/Icon/Icons.swift +1329 -0
  83. package/ios/IconButton/IconButton.swift +15 -7
  84. package/ios/Image/Image.swift +7 -23
  85. package/ios/Information/Information.swift +27 -9
  86. package/ios/Input/ErrorView.swift +2 -2
  87. package/ios/Input/Input.swift +160 -147
  88. package/ios/Input/InputPhoneNumber.swift +12 -9
  89. package/ios/Input/InputSearch.swift +102 -75
  90. package/ios/Input/InputTextArea.swift +128 -145
  91. package/ios/InputDropDown/InputDropDown.swift +10 -7
  92. package/ios/InputMoney/InputMoney.swift +33 -11
  93. package/ios/InputOTP/InputOTP.swift +22 -12
  94. package/ios/Layout/Card.swift +50 -0
  95. package/ios/Layout/GridContext.swift +187 -3
  96. package/ios/Layout/Item.swift +5 -4
  97. package/ios/Layout/ScreenLayout.swift +380 -0
  98. package/ios/Layout/Section.swift +4 -100
  99. package/ios/Loader/Loader.swift +5 -9
  100. package/ios/Lottie/LottieView.swift +14 -2
  101. package/ios/{Application/Navigation/Overplay → Navigation}/BottomSheet.swift +128 -9
  102. package/ios/Navigation/BottomTab/BottomTab.swift +185 -0
  103. package/ios/{Application/Navigation → Navigation}/BottomTab/BottomTabBar.swift +15 -7
  104. package/ios/Navigation/BottomTab/DuoBarCompression.swift +105 -0
  105. package/ios/Navigation/BottomTab/DuoTabBar.swift +304 -0
  106. package/ios/{Application → Navigation/Component}/FloatingButton.swift +14 -3
  107. package/ios/Navigation/Component/Header.swift +531 -0
  108. package/ios/Navigation/Component/HeaderBackground.swift +133 -0
  109. package/ios/Navigation/Component/HeaderJourney.swift +73 -0
  110. package/ios/Navigation/Component/HeaderLocation.swift +48 -0
  111. package/ios/Navigation/Component/HeaderRight.swift +377 -0
  112. package/ios/Navigation/Component/HeaderTitle.swift +36 -0
  113. package/ios/{Application/Navigation → Navigation/Component}/HeaderUser.swift +21 -24
  114. package/ios/Navigation/Component/SideBar.swift +183 -0
  115. package/ios/{Application/Navigation/Overplay → Navigation/Component}/SnackBar.swift +5 -6
  116. package/ios/Navigation/HostBackGestureBridge.swift +78 -0
  117. package/ios/{Application/Navigation/Overplay → Navigation}/ModalScreen.swift +5 -2
  118. package/ios/Navigation/Navigation.swift +204 -0
  119. package/ios/Navigation/NavigationContainer.swift +194 -0
  120. package/ios/{Application/Navigation → Navigation}/Navigator.swift +177 -91
  121. package/ios/Navigation/StackScreen.swift +820 -0
  122. package/ios/Navigation/StackTransition.swift +400 -0
  123. package/ios/Navigation/SwipeBackHaptic.swift +33 -0
  124. package/ios/{Application/Navigation → Navigation}/Tracking/ScreenTracker.swift +12 -5
  125. package/ios/OTPKeyboard/OTPKeyboard.swift +8 -11
  126. package/ios/Pagination/PaginationScroll.swift +2 -2
  127. package/ios/Popup/PopupDisplay.swift +72 -136
  128. package/ios/Popup/PopupInput.swift +10 -13
  129. package/ios/Popup/PopupLayout.swift +140 -0
  130. package/ios/Popup/PopupNotify.swift +56 -73
  131. package/ios/Popup/PopupPromotion.swift +31 -47
  132. package/ios/PopupView/FullscreenPopup.swift +13 -19
  133. package/ios/PopupView/PopupView.swift +1 -1
  134. package/ios/PopupView/Utils++.swift +0 -8
  135. package/ios/ProgressInfo/ProgressInfo.swift +4 -2
  136. package/ios/Radio/Radio.swift +2 -1
  137. package/ios/Rating/Rating.swift +9 -1
  138. package/ios/Resources/dot_loading.json +558 -0
  139. package/ios/Resources/lottie_circle_loader.json +1 -0
  140. package/ios/ScrollIndicator/ScrollIndicator.swift +11 -10
  141. package/ios/Skeleton/Skeleton.swift +22 -27
  142. package/ios/Slider/Slider.swift +16 -18
  143. package/ios/Stepper/Stepper.swift +6 -1
  144. package/ios/Steps/Steps.swift +8 -6
  145. package/ios/SuggestAction/SuggestAction.swift +5 -0
  146. package/ios/Swipeable/SwipeCell.swift +10 -6
  147. package/ios/Switch/Switch.swift +22 -20
  148. package/ios/TabView/TabView.swift +102 -21
  149. package/ios/Tag/Tag.swift +15 -4
  150. package/ios/Template/Logo/Logo.swift +2 -1
  151. package/ios/Template/TrustBanner/TrustBanner.swift +106 -54
  152. package/ios/Title/Title.swift +6 -17
  153. package/ios/Tooltip/Tooltip.swift +29 -14
  154. package/ios/Typography/Text.swift +61 -19
  155. package/ios/Uploader/Uploader.swift +3 -1
  156. package/ios/native-kits.podspec +2 -1
  157. package/ios-demo/MoMoUIKitsDemo.podspec +1 -1
  158. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ContentView.swift +1 -1
  159. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DuoTabDemo.swift +80 -0
  160. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +1 -0
  161. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IPhoneDuoDemo.swift +173 -0
  162. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/NavigationDemo.swift +77 -1
  163. package/package.json +1 -1
  164. package/scripts/gen-ios-icons.mjs +36 -0
  165. package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +0 -52
  166. package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +0 -183
  167. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +0 -207
  168. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +0 -39
  169. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +0 -86
  170. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +0 -76
  171. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +0 -76
  172. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +0 -293
  173. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +0 -33
  174. package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +0 -110
  175. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +0 -378
  176. package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +0 -69
  177. package/ios/Application/Components.swift +0 -270
  178. package/ios/Application/ComposeApi.swift +0 -22
  179. package/ios/Application/HeaderRight.swift +0 -306
  180. package/ios/Application/Navigation/BottomTab/BottomTab.swift +0 -104
  181. package/ios/Application/Navigation/HeaderBackProps.swift +0 -66
  182. package/ios/Application/Navigation/HeaderTitle.swift +0 -57
  183. package/ios/Application/Navigation/NavigationContainer.swift +0 -213
  184. package/ios/Application/Navigation/NavigationOptions.swift +0 -109
  185. package/ios/Application/Navigation/NavigatorManager.swift +0 -22
  186. package/ios/Application/Navigation/component/Header.swift +0 -178
  187. package/ios/Application/Navigation/component/HeaderBackground.swift +0 -83
  188. package/ios/Application/Navigation/component/HeaderColor.swift +0 -65
  189. package/ios/Application/Navigation/component/HeaderRightWidthKey.swift +0 -11
  190. package/ios/Application/Navigation/component/NavigationFooter.swift +0 -90
  191. package/ios/Application/Navigation/component/NavigationHeaderRight.swift +0 -196
  192. package/ios/Application/Screen.swift +0 -250
  193. package/ios/Application/StackScreen.swift +0 -492
  194. /package/ios/{Application/Navigation → Navigation}/BottomTab/CurvedContainer.swift +0 -0
@@ -5,13 +5,10 @@ import androidx.compose.foundation.border
5
5
  import androidx.compose.foundation.layout.*
6
6
  import androidx.compose.foundation.shape.CircleShape
7
7
  import androidx.compose.runtime.Composable
8
- import androidx.compose.runtime.mutableFloatStateOf
9
8
  import androidx.compose.runtime.remember
10
9
  import androidx.compose.ui.Alignment
11
10
  import androidx.compose.ui.Modifier
12
11
  import androidx.compose.ui.draw.clip
13
- import androidx.compose.ui.layout.onGloballyPositioned
14
- import androidx.compose.ui.platform.LocalDensity
15
12
  import androidx.compose.ui.text.style.TextAlign
16
13
  import androidx.compose.ui.unit.dp
17
14
  import vn.momo.kits.application.IsShowBaseLineDebug
@@ -180,7 +177,6 @@ private fun ProgressInfoVertical(
180
177
  ) {
181
178
  val theme = AppTheme.current
182
179
  val lineColor = remember(theme) { theme.colors.primary.copy(alpha = 0.2f) }
183
- val density = LocalDensity.current
184
180
 
185
181
  Column(
186
182
  modifier = modifier
@@ -189,16 +185,18 @@ private fun ProgressInfoVertical(
189
185
  ) {
190
186
  steps.forEachIndexed { index, item ->
191
187
  val isLast = index == steps.lastIndex
192
- val rowHeightPx = remember { mutableFloatStateOf(0f) }
193
- val rowHeightDp = with(density) { rowHeightPx.value.toDp() }
194
188
 
189
+ // Intrinsic height lets the connector fill the row in one pass; measuring the row and
190
+ // feeding it back lagged a frame and never let the line shrink with its text.
195
191
  Row(
196
192
  modifier = Modifier
197
193
  .fillMaxWidth()
198
- .onGloballyPositioned { coords -> rowHeightPx.value = coords.size.height.toFloat() },
194
+ .height(IntrinsicSize.Min),
199
195
  ) {
200
196
  Column(
201
- modifier = Modifier.heightIn(min = if (isLast) 40.dp else 48.dp),
197
+ modifier = Modifier
198
+ .fillMaxHeight()
199
+ .heightIn(min = if (isLast) 40.dp else 48.dp),
202
200
  horizontalAlignment = Alignment.CenterHorizontally,
203
201
  ) {
204
202
  ProgressInfoStepIcon(
@@ -211,7 +209,7 @@ private fun ProgressInfoVertical(
211
209
  Spacer(
212
210
  modifier = Modifier
213
211
  .width(2.dp)
214
- .height(rowHeightDp-if(size == ProgressInfoSize.Large) scaleSize(24f).dp else scaleSize(16f).dp)
212
+ .weight(1f)
215
213
  .background(lineColor),
216
214
  )
217
215
  }
@@ -40,7 +40,7 @@ fun Radio(
40
40
  verticalAlignment = Alignment.CenterVertically,
41
41
  ) {
42
42
  Box(
43
- modifier = modifier
43
+ modifier = Modifier
44
44
  .padding(end = if (label.isEmpty()) 0.dp else Spacing.XS)
45
45
  .border(borderWidth, borderColor, RoundedCornerShape(Radius.M))
46
46
  .conditional(IsShowBaseLineDebug) {
@@ -43,9 +43,11 @@ private fun getValueForPosition(
43
43
  step: Float,
44
44
  ): Float {
45
45
  val availableSpace = containerWidth - thumbWidth
46
- if (availableSpace <= 0f) return min
47
- val relStepUnit = step / (max - min)
46
+ if (availableSpace <= 0f || max <= min) return min
48
47
  var relPosition = (positionInView - thumbWidth / 2f) / availableSpace
48
+ // A zero step would make the snap math NaN, and NaN.roundToInt() throws.
49
+ if (step <= 0f) return clamp(min + relPosition * (max - min), min, max)
50
+ val relStepUnit = step / (max - min)
49
51
  val relOffset = relPosition % relStepUnit
50
52
  relPosition -= relOffset
51
53
  if (relOffset / relStepUnit >= 0.5f) relPosition += relStepUnit
@@ -131,6 +133,11 @@ fun Slider(
131
133
  var draggingLow by remember { mutableStateOf(true) }
132
134
  var isDragging by remember { mutableStateOf(false) }
133
135
 
136
+ // pointerInput is not keyed on the callbacks, so read their latest values.
137
+ val currentOnValueChanged by rememberUpdatedState(onValueChanged)
138
+ val currentOnTouchStart by rememberUpdatedState(onSliderTouchStart)
139
+ val currentOnTouchEnd by rememberUpdatedState(onSliderTouchEnd)
140
+
134
141
  val thumbColor = if (disabled) theme.colors.text.disable else theme.colors.primary
135
142
  val trackActiveColor = if (disabled) theme.colors.text.disable else theme.colors.primary
136
143
  val trackInactiveColor = theme.colors.background.default
@@ -167,7 +174,7 @@ fun Slider(
167
174
 
168
175
  draggingLow = disableRange || isLowCloser(touchX, lowCenterX, highCenterX)
169
176
  isDragging = true
170
- onSliderTouchStart(lowValue, highValue)
177
+ currentOnTouchStart(lowValue, highValue)
171
178
 
172
179
  // Apply initial press
173
180
  applyDrag(touchX, trackWidthPx, thumbSizePx,
@@ -175,7 +182,7 @@ fun Slider(
175
182
  lowValue, highValue) { nl, nh ->
176
183
  lowValue = nl
177
184
  highValue = nh
178
- onValueChanged(nl, nh, true)
185
+ currentOnValueChanged(nl, nh, true)
179
186
  }
180
187
 
181
188
  // Track move / release
@@ -186,7 +193,7 @@ fun Slider(
186
193
 
187
194
  if (!moveChange.pressed) {
188
195
  isDragging = false
189
- onSliderTouchEnd(lowValue, highValue)
196
+ currentOnTouchEnd(lowValue, highValue)
190
197
  break
191
198
  }
192
199
 
@@ -195,7 +202,7 @@ fun Slider(
195
202
  lowValue, highValue) { nl, nh ->
196
203
  lowValue = nl
197
204
  highValue = nh
198
- onValueChanged(nl, nh, true)
205
+ currentOnValueChanged(nl, nh, true)
199
206
  }
200
207
  }
201
208
  }
@@ -8,8 +8,10 @@ import androidx.compose.foundation.gestures.detectHorizontalDragGestures
8
8
  import androidx.compose.foundation.layout.*
9
9
  import androidx.compose.foundation.shape.RoundedCornerShape
10
10
  import androidx.compose.runtime.Composable
11
+ import androidx.compose.runtime.getValue
11
12
  import androidx.compose.runtime.remember
12
13
  import androidx.compose.runtime.rememberCoroutineScope
14
+ import androidx.compose.runtime.rememberUpdatedState
13
15
  import androidx.compose.ui.Alignment
14
16
  import androidx.compose.ui.Modifier
15
17
  import androidx.compose.ui.draw.clip
@@ -62,6 +64,12 @@ fun Swipe(
62
64
 
63
65
  val offsetX = remember { Animatable(0f) }
64
66
 
67
+ // pointerInput is keyed on the action counts only, so read the latest callbacks.
68
+ val currentOnOpen by rememberUpdatedState(onSwipeableOpen)
69
+ val currentOnClose by rememberUpdatedState(onSwipeableClose)
70
+ val currentOnLeftOpen by rememberUpdatedState(onSwipeableLeftOpen)
71
+ val currentOnRightOpen by rememberUpdatedState(onSwipeableRightOpen)
72
+
65
73
  val shape = remember(itemRadius) {
66
74
  if (itemRadius > 0.dp) RoundedCornerShape(itemRadius) else RoundedCornerShape(0.dp)
67
75
  }
@@ -71,17 +79,17 @@ fun Swipe(
71
79
  val current = offsetX.value
72
80
  val target = when {
73
81
  current > leftWidthPx * 0.5f && left.isNotEmpty() -> {
74
- onSwipeableLeftOpen()
75
- onSwipeableOpen()
82
+ currentOnLeftOpen()
83
+ currentOnOpen()
76
84
  leftWidthPx
77
85
  }
78
86
  current < -rightWidthPx * 0.5f && right.isNotEmpty() -> {
79
- onSwipeableRightOpen()
80
- onSwipeableOpen()
87
+ currentOnRightOpen()
88
+ currentOnOpen()
81
89
  -rightWidthPx
82
90
  }
83
91
  else -> {
84
- onSwipeableClose()
92
+ currentOnClose()
85
93
  0f
86
94
  }
87
95
  }
@@ -43,9 +43,10 @@ fun Switch(
43
43
 
44
44
  val (circleBackgroundColor, backgroundColor) = colors
45
45
 
46
- // Memoize automation ID to avoid string concatenation on every render
46
+ // Both params default to "", so blank must fall back too or the id is always empty.
47
47
  val automationIdValue = remember(accessibilityId, title, value) {
48
- accessibilityId ?: "toggle_${title ?: "unknown"}_$value"
48
+ accessibilityId?.takeIf { it.isNotEmpty() }
49
+ ?: "toggle_${title?.takeIf { it.isNotEmpty() } ?: "unknown"}_$value"
49
50
  }
50
51
 
51
52
  // Memoize alignment calculation
@@ -10,9 +10,10 @@ import androidx.compose.foundation.shape.RoundedCornerShape
10
10
  import androidx.compose.runtime.*
11
11
  import androidx.compose.ui.Alignment
12
12
  import androidx.compose.ui.Modifier
13
+ import androidx.compose.ui.draw.clipToBounds
14
+ import androidx.compose.ui.geometry.Offset
13
15
  import androidx.compose.ui.graphics.Color
14
16
  import androidx.compose.ui.graphics.drawscope.withTransform
15
- import androidx.compose.ui.graphics.drawscope.DrawScope
16
17
  import androidx.compose.ui.graphics.Path
17
18
  import androidx.compose.ui.layout.onGloballyPositioned
18
19
  import androidx.compose.ui.layout.positionInParent
@@ -73,6 +74,8 @@ fun TabView(
73
74
  } else null
74
75
 
75
76
  val coroutineScope = rememberCoroutineScope()
77
+ // The collector below outlives recompositions; read the latest callback, not the first one.
78
+ val currentOnPageSelected by rememberUpdatedState(onPageSelected)
76
79
 
77
80
  // Keep selectedIndex in sync when user swipes the pager
78
81
  if (pagerState != null) {
@@ -80,7 +83,7 @@ fun TabView(
80
83
  snapshotFlow { pagerState.currentPage }.collect { page ->
81
84
  if (!lazyRendered.contains(page)) lazyRendered.add(page)
82
85
  selectedIndex = page
83
- onPageSelected(page)
86
+ currentOnPageSelected(page)
84
87
  }
85
88
  }
86
89
  }
@@ -362,7 +365,7 @@ private fun CardTabBar(
362
365
  }
363
366
  }
364
367
 
365
- val active = tabs[selectedIndex]
368
+ val active = tabs.getOrNull(selectedIndex) ?: return@BoxWithConstraints
366
369
  Row(
367
370
  modifier = Modifier
368
371
  // Pinned to the strip's BOTTOM edge so the two always end on the same
@@ -415,7 +418,8 @@ private val CARD_TAB_HEIGHT = 40.dp
415
418
  private val CARD_TAB_ACTIVE_HEIGHT = 44.dp
416
419
  @Composable
417
420
  private fun CardTabEar(width: Dp, mirrored: Boolean, color: Color, nudge: Dp) {
418
- Canvas(modifier = Modifier.offset(x = nudge).width(width).height(CARD_TAB_ACTIVE_HEIGHT)) {
421
+ // Clipped like react-kits' Svg, whose 28-wide path is cut at a 22pt `pathWidth`.
422
+ Canvas(modifier = Modifier.offset(x = nudge).width(width).height(CARD_TAB_ACTIVE_HEIGHT).clipToBounds()) {
419
423
  val path = Path().apply {
420
424
  moveTo(0f, 0f)
421
425
  lineTo(0.60765f, 0f)
@@ -425,8 +429,12 @@ private fun CardTabEar(width: Dp, mirrored: Boolean, color: Color, nudge: Dp) {
425
429
  lineTo(0f, 44f)
426
430
  close()
427
431
  }
428
- val draw: DrawScope.() -> Unit = { drawPath(path = path, color = color) }
429
- if (mirrored) withTransform({ scale(-1f, 1f) }) { draw() } else draw()
432
+ // Path coordinates are dp (react-kits viewBox), but DrawScope draws in px.
433
+ val pxPerDp = density
434
+ withTransform({
435
+ if (mirrored) scale(-1f, 1f)
436
+ scale(pxPerDp, pxPerDp, pivot = Offset.Zero)
437
+ }) { drawPath(path = path, color = color) }
430
438
  }
431
439
  }
432
440
 
@@ -30,6 +30,22 @@ enum class TagColor(val backgroundColor: Color, val textColor: Color) {
30
30
  Grey(Colors.black_04, Colors.black_12),
31
31
  }
32
32
 
33
+ // RN only accepts the `_03` palette as a custom tag color.
34
+ private val tagCustomColors = listOf(
35
+ Colors.black_03,
36
+ Colors.pink_03,
37
+ Colors.violet_03,
38
+ Colors.indigo_03,
39
+ Colors.blue_03,
40
+ Colors.mint_03,
41
+ Colors.green_03,
42
+ Colors.lime_03,
43
+ Colors.yellow_03,
44
+ Colors.gold_03,
45
+ Colors.orange_03,
46
+ Colors.red_03,
47
+ )
48
+
33
49
  @Composable
34
50
  fun Tag(
35
51
  label: String = "Label",
@@ -38,27 +54,9 @@ fun Tag(
38
54
  size: TagSize = TagSize.Large,
39
55
  customColor: Color? = null,
40
56
  ) {
41
- val primaryColors = listOf(
42
- Color(0xFFF0F0F0),
43
- Color(0xFFEB2F96),
44
- Color(0xFF962AF0),
45
- Color(0xFF4E4BFF),
46
- Color(0xFF007AFF),
47
- Color(0xFF13C2C2),
48
- Color(0xFF34C759),
49
- Color(0xFFA0D911),
50
- Color(0xFFFFCC00),
51
- Color(0xFFFA8C16),
52
- Color(0xFFFA541C),
53
- Color(0xFFF5222D)
54
- )
55
- var tagColor = color.backgroundColor
56
- var labelColor = color.textColor
57
-
58
- if (primaryColors.contains(customColor)) {
59
- tagColor = color.backgroundColor
60
- labelColor = color.textColor
61
- }
57
+ val custom = customColor?.takeIf { it in tagCustomColors }
58
+ val tagColor = custom ?: color.backgroundColor
59
+ val labelColor = if (custom != null) Colors.black_01 else color.textColor
62
60
 
63
61
  Box(
64
62
  modifier = Modifier
@@ -87,8 +87,8 @@ fun Text(
87
87
  color ?: theme.colors.text.default
88
88
  }
89
89
 
90
- // Memoize style copy operation
91
- val optimizedStyle = remember(style) {
90
+ // lineHeight is a key: it is scaled, so it changes with the font scale even when style doesn't.
91
+ val optimizedStyle = remember(style, lineHeight) {
92
92
  style.copy(
93
93
  fontWeight = null,
94
94
  lineHeight = if (lineHeight.value.isFinite() && lineHeight.value > 0) lineHeight else style.lineHeight
@@ -21,6 +21,7 @@ import androidx.compose.ui.unit.*
21
21
  import androidx.compose.ui.window.Popup
22
22
  import androidx.compose.ui.window.PopupPositionProvider
23
23
  import androidx.compose.ui.window.PopupProperties
24
+ import kotlinx.coroutines.flow.drop
24
25
  import vn.momo.kits.application.IsShowBaseLineDebug
25
26
  import vn.momo.kits.const.*
26
27
  import vn.momo.kits.modifier.activeOpacityClickable
@@ -147,8 +148,10 @@ fun Tooltip(
147
148
  val density = LocalDensity.current
148
149
  val offsetPx = with(density) { (TOOLTIP_OFFSET + ARROW_SIZE).roundToPx() }
149
150
 
150
- LaunchedEffect(state.isVisible) {
151
- onVisibleChange?.invoke(state.isVisible)
151
+ val currentOnVisibleChange by rememberUpdatedState(onVisibleChange)
152
+ LaunchedEffect(state) {
153
+ // changes only, like RN: the initial value is not reported
154
+ snapshotFlow { state.isVisible }.drop(1).collect { currentOnVisibleChange?.invoke(it) }
152
155
  }
153
156
 
154
157
  Box(modifier = modifier) {
@@ -128,11 +128,13 @@ fun getHours(hourMode: Int): List<String> {
128
128
  * Get a list of minutes
129
129
  */
130
130
  fun getMinutes(interval: Int): List<String> {
131
+ // A non-positive interval would never reach 60.
132
+ val step = interval.coerceAtLeast(1)
131
133
  val minutes = mutableListOf<String>()
132
134
  var min = 0
133
135
  while (min < 60) {
134
136
  minutes.add(paddingNum(min))
135
- min += interval
137
+ min += step
136
138
  }
137
139
  return minutes
138
140
  }
@@ -154,22 +154,14 @@ internal fun WheelPickerItem(
154
154
  .fillMaxWidth()
155
155
  .height(itemHeight)
156
156
  .graphicsLayer {
157
- val firstVisibleIndex = listState.firstVisibleItemIndex
158
- val visibleItemOffset = listState.firstVisibleItemScrollOffset
159
-
160
- val viewportHeight = 5 * itemHeight.value
161
- val itemHeightValue = itemHeight.value
162
-
163
- val absoluteOffset =
164
- (index - firstVisibleIndex) * itemHeightValue - (visibleItemOffset / itemHeightValue)
165
- val relativePosition = absoluteOffset / viewportHeight
166
-
167
- val distance = abs(2 - relativePosition)
168
-
157
+ // Rows away from the centre slot (two below the first visible row); RN fades 1 → 0.8 → 0.4.
158
+ val distance = abs(
159
+ index - listState.firstVisibleItemIndex -
160
+ listState.firstVisibleItemScrollOffset / itemHeight.toPx() - 2
161
+ )
169
162
  alpha = when {
170
- distance <= 1 -> 1f
171
- distance <= 2 -> 0.8f
172
- distance <= 3 -> 0.4f
163
+ distance <= 1f -> 1f - 0.2f * distance
164
+ distance <= 2f -> 0.8f - 0.4f * (distance - 1f)
173
165
  else -> 0.4f
174
166
  }
175
167
  },
@@ -10,6 +10,10 @@ import androidx.compose.ui.text.font.FontFamily
10
10
  * - Android: `assets/fonts/<fileName>` (gradle task `syncMomoFonts`)
11
11
  * - iOS: file rời ở bundle root (pbxproj fileRef được `post_install.rb` trỏ về font-source)
12
12
  *
13
+ * AI-GENERATED START: note SF Pro -> iOS system font (ITMS-91182)
14
+ * Ngoại lệ: SF Pro trên iOS dùng system font (chính là SF Pro) — Apple cấm app nhúng font của Apple.
15
+ * AI-GENERATED END: note SF Pro -> iOS system font (ITMS-91182)
16
+ *
13
17
  * Trả về `null` nếu host không có font đó — caller tự fallback [FontFamily.Default].
14
18
  */
15
19
  @Composable
@@ -67,7 +67,7 @@ data class Theme(
67
67
  return !assets.headerBackground.isNullOrEmpty()
68
68
  }
69
69
  }
70
- val defaultTheme = Theme(
70
+ private val lightBaseTheme = Theme(
71
71
  dark = false,
72
72
  colors = ColorScheme(
73
73
  primary = Colors.pink_03,
@@ -117,14 +117,14 @@ val defaultTheme = Theme(
117
117
  container = Colors.blue_08
118
118
  )
119
119
  ),
120
- font = "MomoTrustSans",
120
+ font = "SFProText",
121
121
  assets = ThemeAssets(
122
122
  headerBackground = null
123
123
  )
124
124
  )
125
125
 
126
126
 
127
- val darkTheme = Theme(
127
+ private val darkBaseTheme = Theme(
128
128
  dark = true,
129
129
  colors = ColorScheme(
130
130
  primary = Colors.pink_04,
@@ -174,13 +174,19 @@ val darkTheme = Theme(
174
174
  container = Colors.blue_08
175
175
  )
176
176
  ),
177
- font = "MomoTrustSans",
177
+ font = "SFProText",
178
178
  assets = ThemeAssets(
179
179
  headerBackground = null
180
180
  )
181
181
  )
182
182
 
183
183
 
184
+ val defaultTheme: Theme
185
+ get() = lightBaseTheme.copy(font = defaultFontFamily)
186
+
187
+ val darkTheme: Theme
188
+ get() = darkBaseTheme.copy(font = defaultFontFamily)
189
+
184
190
  val AppTheme = staticCompositionLocalOf { defaultTheme }
185
191
 
186
192
  val AppStatusBar = staticCompositionLocalOf { 0.dp }
@@ -121,31 +121,17 @@ private val hostFontFiles: Map<String, String> = mapOf(
121
121
  )
122
122
  }
123
123
 
124
- /**
125
- * Font mặc định mới, chỉ áp cho user nằm trong whitelist design system ([KitDesignSystem]).
126
- * Ngoài whitelist thì quay về [LEGACY_DEFAULT_FONT_FAMILY] như trước.
127
- */
128
124
  private const val NEW_DEFAULT_FONT_FAMILY = "MomoTrustSans"
129
125
  private const val LEGACY_DEFAULT_FONT_FAMILY = "SFProText"
130
126
 
131
- /**
132
- * Font thuoc bo design system moi. MoMoTrustDisplay chi duoc header dung tu DS-791
133
- * (commit header title 5bfde6251); truoc do header lay font theo theme, nen ngoai
134
- * whitelist ca hai deu ha ve [LEGACY_DEFAULT_FONT_FAMILY].
135
- */
136
- private val newFontFamilies = setOf(NEW_DEFAULT_FONT_FAMILY, "MoMoTrustDisplay")
127
+ internal val defaultFontFamily: String
128
+ get() = if (KitDesignSystem.isWhiteList) NEW_DEFAULT_FONT_FAMILY else LEGACY_DEFAULT_FONT_FAMILY
137
129
 
138
130
  @Composable
139
131
  fun getFontFamily(fontFamily: String, fontWeight: FontWeight? = FontWeight.Normal): FontFamily {
140
132
  val isWhiteList = KitDesignSystem.isWhiteList
141
- val resolvedFamily =
142
- if (fontFamily in newFontFamilies && !isWhiteList) {
143
- LEGACY_DEFAULT_FONT_FAMILY
144
- } else {
145
- fontFamily
146
- }
147
133
  val defaultFile = if (isWhiteList) NEW_DEFAULT_FONT_FILE else LEGACY_DEFAULT_FONT_FILE
148
- val key = "$resolvedFamily-${fontWeight?.weight}"
134
+ val key = "$fontFamily-${fontWeight?.weight}"
149
135
  val fileName = hostFontFiles[key] ?: defaultFile
150
136
  return hostFontFamily(fileName)
151
137
  ?: hostFontFamily(defaultFile)