@momo-kits/native-kits 0.167.1-sizescale.2-debug → 0.168.1-beta.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 (223) hide show
  1. package/CLAUDE.md +45 -3
  2. package/README.md +2 -2
  3. package/compose/build.gradle.kts +9 -1
  4. package/compose/build.gradle.kts.backup +8 -0
  5. package/compose/compose.podspec +1 -1
  6. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +3 -4
  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 +353 -113
  9. package/compose/src/commonMain/kotlin/vn/momo/kits/application/WidgetContainer.kt +2 -2
  10. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +20 -21
  11. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +2 -1
  12. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +4 -2
  13. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +1 -1
  14. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +24 -14
  15. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +1 -1
  16. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +1 -1
  17. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +9 -6
  18. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +11 -12
  19. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +4 -9
  20. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +2 -2
  21. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +10 -9
  22. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +6 -6
  23. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +55 -22
  24. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +12 -1
  25. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +7 -9
  26. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +1 -1
  27. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +16 -9
  28. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +13 -5
  29. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +3 -2
  30. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +14 -6
  31. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +19 -21
  32. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +2 -2
  33. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +6 -3
  34. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +3 -1
  35. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +7 -15
  36. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Spacing.kt +11 -15
  37. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +10 -4
  38. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +7 -23
  39. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +13 -11
  40. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +21 -9
  41. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/ScreenLayout.kt +309 -0
  42. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +11 -9
  43. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +43 -4
  44. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +8 -0
  45. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +10 -0
  46. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +8 -7
  47. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +6 -0
  48. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +197 -60
  49. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +134 -81
  50. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +54 -62
  51. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoBarCompression.kt +98 -0
  52. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoTabBar.kt +312 -0
  53. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +126 -29
  54. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +34 -14
  55. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderJourney.kt +3 -3
  56. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderLocation.kt +1 -2
  57. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +116 -26
  58. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +4 -2
  59. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +2 -3
  60. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SideBar.kt +192 -0
  61. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +2 -1
  62. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +3 -0
  63. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ReservedRegions.kt +13 -0
  64. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +13 -0
  65. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/OsFontScale.ios.kt +1 -3
  66. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +10 -11
  67. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/ReservedRegions.ios.kt +94 -0
  68. package/gradle/libs.versions.toml +1 -1
  69. package/gradle.properties +1 -1
  70. package/ios/Application/ApplicationEnvironment.swift +17 -0
  71. package/ios/Application/MaxApi.swift +30 -3
  72. package/ios/Avatar/Avatar.swift +4 -3
  73. package/ios/Badge/Badge.swift +6 -4
  74. package/ios/Badge/BadgeDot.swift +1 -1
  75. package/ios/Badge/BadgeRibbon.swift +0 -1
  76. package/ios/Button/Button.swift +15 -12
  77. package/ios/CalculatorKeyboard/CalculatorKeyboard.swift +64 -18
  78. package/ios/Carousel/Carousel.swift +29 -12
  79. package/ios/Checkbox/Checkbox.swift +7 -3
  80. package/ios/Chip/Chip.swift +5 -4
  81. package/ios/Collapse/Collapse.swift +70 -19
  82. package/ios/Colors+Radius+Spacing/Colors.swift +10 -1
  83. package/ios/Colors+Radius+Spacing/Radius.swift +6 -0
  84. package/ios/DateTimePicker/DateTimePicker.swift +54 -20
  85. package/ios/DateTimePicker/WheelPicker.swift +103 -105
  86. package/ios/Divider/Divider.swift +37 -4
  87. package/ios/Extensions/Color++.swift +38 -9
  88. package/ios/Extensions/OptionalEnvironmentObject.swift +12 -0
  89. package/ios/Extensions/SkeletonUICompat.swift +11 -0
  90. package/ios/Icon/Icon.swift +21 -23
  91. package/ios/Icon/Icons.swift +1329 -0
  92. package/ios/IconButton/IconButton.swift +15 -7
  93. package/ios/Image/Image.swift +7 -23
  94. package/ios/Information/Information.swift +27 -9
  95. package/ios/Input/ErrorView.swift +2 -2
  96. package/ios/Input/Input.swift +160 -147
  97. package/ios/Input/InputPhoneNumber.swift +12 -9
  98. package/ios/Input/InputSearch.swift +107 -77
  99. package/ios/Input/InputTextArea.swift +128 -145
  100. package/ios/InputDropDown/InputDropDown.swift +10 -7
  101. package/ios/InputMoney/InputMoney.swift +33 -11
  102. package/ios/InputOTP/InputOTP.swift +22 -12
  103. package/ios/Layout/Card.swift +50 -0
  104. package/ios/Layout/GridContext.swift +187 -3
  105. package/ios/Layout/Item.swift +5 -4
  106. package/ios/Layout/ScreenLayout.swift +380 -0
  107. package/ios/Layout/Section.swift +4 -100
  108. package/ios/Loader/Loader.swift +5 -9
  109. package/ios/Lottie/LottieView.swift +14 -2
  110. package/ios/{Application/Navigation/Overplay → Navigation}/BottomSheet.swift +169 -29
  111. package/ios/Navigation/BottomTab/BottomTab.swift +332 -0
  112. package/ios/{Application/Navigation → Navigation}/BottomTab/BottomTabBar.swift +18 -7
  113. package/ios/Navigation/BottomTab/DuoBarCompression.swift +105 -0
  114. package/ios/Navigation/BottomTab/DuoTabBar.swift +304 -0
  115. package/ios/{Application → Navigation/Component}/FloatingButton.swift +43 -54
  116. package/ios/Navigation/Component/Header.swift +545 -0
  117. package/ios/Navigation/Component/HeaderBackground.swift +133 -0
  118. package/ios/Navigation/Component/HeaderJourney.swift +73 -0
  119. package/ios/Navigation/Component/HeaderLocation.swift +48 -0
  120. package/ios/Navigation/Component/HeaderRight.swift +425 -0
  121. package/ios/Navigation/Component/HeaderTitle.swift +36 -0
  122. package/ios/{Application/Navigation → Navigation/Component}/HeaderUser.swift +21 -24
  123. package/ios/Navigation/Component/SideBar.swift +183 -0
  124. package/ios/{Application/Navigation/Overplay → Navigation/Component}/SnackBar.swift +8 -8
  125. package/ios/Navigation/HostBackGestureBridge.swift +78 -0
  126. package/ios/{Application/Navigation/Overplay → Navigation}/ModalScreen.swift +9 -2
  127. package/ios/Navigation/Navigation.swift +204 -0
  128. package/ios/Navigation/NavigationContainer.swift +195 -0
  129. package/ios/{Application/Navigation → Navigation}/Navigator.swift +207 -107
  130. package/ios/Navigation/StackScreen.swift +934 -0
  131. package/ios/Navigation/StackTransition.swift +400 -0
  132. package/ios/Navigation/SwipeBackHaptic.swift +33 -0
  133. package/ios/{Application/Navigation → Navigation}/Tracking/ScreenTracker.swift +12 -5
  134. package/ios/OTPKeyboard/OTPKeyboard.swift +8 -11
  135. package/ios/Pagination/PaginationScroll.swift +2 -2
  136. package/ios/Popup/PopupDisplay.swift +72 -135
  137. package/ios/Popup/PopupInput.swift +10 -13
  138. package/ios/Popup/PopupLayout.swift +140 -0
  139. package/ios/Popup/PopupNotify.swift +59 -69
  140. package/ios/Popup/PopupPromotion.swift +31 -47
  141. package/ios/PopupView/FullscreenPopup.swift +13 -19
  142. package/ios/PopupView/PopupView.swift +1 -1
  143. package/ios/PopupView/Utils++.swift +0 -8
  144. package/ios/ProgressInfo/ProgressInfo.swift +4 -2
  145. package/ios/Radio/Radio.swift +2 -1
  146. package/ios/Rating/Rating.swift +9 -1
  147. package/ios/Resources/dot_loading.json +558 -0
  148. package/ios/Resources/lottie_circle_loader.json +1 -0
  149. package/ios/ScrollIndicator/ScrollIndicator.swift +11 -10
  150. package/ios/Skeleton/Skeleton.swift +22 -27
  151. package/ios/Slider/Slider.swift +16 -18
  152. package/ios/Stepper/Stepper.swift +6 -1
  153. package/ios/Steps/Steps.swift +8 -6
  154. package/ios/SuggestAction/SuggestAction.swift +5 -0
  155. package/ios/Swipeable/SwipeCell.swift +10 -6
  156. package/ios/Switch/Switch.swift +22 -20
  157. package/ios/TabView/TabView.swift +102 -21
  158. package/ios/Tag/Tag.swift +15 -4
  159. package/ios/Template/Logo/Logo.swift +2 -1
  160. package/ios/Template/TrustBanner/TrustBanner.swift +106 -54
  161. package/ios/Title/Title.swift +6 -17
  162. package/ios/Tooltip/Tooltip.swift +29 -14
  163. package/ios/Typography/FontScale.swift +3 -3
  164. package/ios/Typography/Text.swift +81 -19
  165. package/ios/Uploader/Uploader.swift +3 -1
  166. package/ios/native-kits.podspec +2 -1
  167. package/ios-demo/MoMoUIKitsDemo.podspec +1 -1
  168. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ContentView.swift +1 -1
  169. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DuoTabDemo.swift +80 -0
  170. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +2 -0
  171. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IPhoneDuoDemo.swift +173 -0
  172. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/NavigationDemo.swift +77 -1
  173. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupDisplayDemo.swift +1 -0
  174. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupNotifyDemo.swift +2 -2
  175. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupPromotionDemo.swift +2 -0
  176. package/local.properties +2 -2
  177. package/package.json +1 -1
  178. package/scripts/gen-ios-icons.mjs +36 -0
  179. package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +0 -52
  180. package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +0 -183
  181. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +0 -207
  182. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +0 -39
  183. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +0 -86
  184. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +0 -76
  185. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +0 -76
  186. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +0 -293
  187. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +0 -33
  188. package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +0 -110
  189. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +0 -378
  190. package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +0 -69
  191. package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitSizeScale.kt +0 -56
  192. package/example/ios/Example.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/Example.xcscheme +0 -32
  193. package/example/ios/Example.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
  194. package/example/ios/Example.xcworkspace/xcuserdata/sonnguyen.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  195. package/example/ios/Example.xcworkspace/xcuserdata/sonnguyen.xcuserdatad/WorkspaceSettings.xcsettings +0 -16
  196. package/example/ios/Example.xcworkspace/xcuserdata/sonnguyen.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +0 -6
  197. package/example/ios/Example.xcworkspace/xcuserdata/sonnguyen.xcuserdatad/xcschemes/xcschememanagement.plist +0 -5
  198. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/MoMoUIKits.xcscheme +0 -58
  199. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/Pods-Example.xcscheme +0 -58
  200. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/SDWebImage.xcscheme +0 -58
  201. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/SDWebImageSwiftUI.xcscheme +0 -58
  202. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/SkeletonUI.xcscheme +0 -58
  203. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/lottie-ios-LottiePrivacyInfo.xcscheme +0 -58
  204. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/lottie-ios.xcscheme +0 -58
  205. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/xcschememanagement.plist +0 -60
  206. package/ios/Application/Components.swift +0 -270
  207. package/ios/Application/ComposeApi.swift +0 -22
  208. package/ios/Application/HeaderRight.swift +0 -306
  209. package/ios/Application/Navigation/BottomTab/BottomTab.swift +0 -104
  210. package/ios/Application/Navigation/HeaderBackProps.swift +0 -66
  211. package/ios/Application/Navigation/HeaderTitle.swift +0 -57
  212. package/ios/Application/Navigation/NavigationContainer.swift +0 -213
  213. package/ios/Application/Navigation/NavigationOptions.swift +0 -109
  214. package/ios/Application/Navigation/NavigatorManager.swift +0 -22
  215. package/ios/Application/Navigation/component/Header.swift +0 -178
  216. package/ios/Application/Navigation/component/HeaderBackground.swift +0 -83
  217. package/ios/Application/Navigation/component/HeaderColor.swift +0 -65
  218. package/ios/Application/Navigation/component/HeaderRightWidthKey.swift +0 -11
  219. package/ios/Application/Navigation/component/NavigationFooter.swift +0 -90
  220. package/ios/Application/Navigation/component/NavigationHeaderRight.swift +0 -196
  221. package/ios/Application/Screen.swift +0 -250
  222. package/ios/Application/StackScreen.swift +0 -492
  223. /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) {
@@ -26,9 +26,9 @@ import kotlin.math.roundToInt
26
26
  private val THUMB_SIZE = 20.dp
27
27
  private val THUMB_BORDER = 3.dp
28
28
  private val TRACK_HEIGHT = 4.dp
29
- private val LABEL_PAD_H get() = Spacing.XS
30
- private val LABEL_PAD_V get() = Spacing.XXS
31
- private val LABEL_GAP get() = Spacing.XS // space between label and thumb top
29
+ private val LABEL_PAD_H = Spacing.XS
30
+ private val LABEL_PAD_V = Spacing.XXS
31
+ private val LABEL_GAP = Spacing.XS // space between label and thumb top
32
32
 
33
33
 
34
34
  private fun clamp(value: Float, min: Float, max: Float): Float =
@@ -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
@@ -67,7 +68,7 @@ class TooltipState {
67
68
  fun rememberTooltipState(): TooltipState {
68
69
  return remember { TooltipState() }
69
70
  }
70
- private val TOOLTIP_OFFSET get() = Spacing.S
71
+ private val TOOLTIP_OFFSET = Spacing.S
71
72
  private val ARROW_SIZE = 6.dp
72
73
  private class TooltipPositionProvider(
73
74
  private val placement: TooltipPlacement,
@@ -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
  },
@@ -1,20 +1,16 @@
1
1
  package vn.momo.kits.const
2
2
 
3
- import androidx.compose.ui.unit.Dp
4
3
  import androidx.compose.ui.unit.dp
5
4
 
6
- // Divided by KitSizeScale.rate: containers scale LocalDensity for sizes, spacing must stay unscaled.
7
- private fun spacing(value: Int): Dp = (value / KitSizeScale.rate).dp
8
-
9
5
  object Spacing {
10
- val XXS: Dp get() = spacing(2)
11
- val XS: Dp get() = spacing(4)
12
- val S: Dp get() = spacing(8)
13
- val M: Dp get() = spacing(12)
14
- val L: Dp get() = spacing(16)
15
- val XL: Dp get() = spacing(24)
16
- val XXL: Dp get() = spacing(32)
17
- val Size3XL: Dp get() = spacing(48)
18
- val Size4XL: Dp get() = spacing(56)
19
- val Size5XL: Dp get() = spacing(64)
20
- }
6
+ val XXS = 2.dp
7
+ val XS = 4.dp
8
+ val S = 8.dp
9
+ val M = 12.dp
10
+ val L = 16.dp
11
+ val XL = 24.dp
12
+ val XXL = 32.dp
13
+ val Size3XL = 48.dp
14
+ val Size4XL = 56.dp
15
+ val Size5XL = 64.dp
16
+ }
@@ -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 }
@@ -26,13 +26,12 @@ fun scaleSize(size: Float): Float {
26
26
  return size * (context?.userScaleRate ?: KitFontScale.userScaleRate)
27
27
  }
28
28
  val scaleSizeMaxRate: Float = MAX_FONT_SCALE
29
- // Real screen width / OS font scale: undo the container size scale.
30
- val sizeScale = LocalSizeScale.current
31
- val deviceWidth = getScreenDimensions().width * sizeScale
29
+ // Short side, not width: landscape must not inflate every font.
30
+ val deviceWidth = getScreenDimensions().let { minOf(it.width, it.height) }
32
31
  val deviceScale = deviceWidth / DEFAULT_SCREEN_SIZE
33
32
 
34
33
  val density = LocalDensity.current
35
- val fontScale = density.fontScale * sizeScale
34
+ val fontScale = density.fontScale
36
35
 
37
36
  var fontSizeScaleDevice = size
38
37
  var fontSizeScaleOS = size
@@ -59,8 +58,7 @@ fun scaleSize(size: TextUnit): TextUnit {
59
58
  val density = LocalDensity.current
60
59
 
61
60
  val scaled = scaleSize(size.value)
62
- // Real OS font scale (density.fontScale is divided by the size scale inside containers).
63
- val spValue = scaled / (density.fontScale * LocalSizeScale.current)
61
+ val spValue = scaled / density.fontScale
64
62
 
65
63
  return TextUnit(value = spValue, type = TextUnitType.Sp)
66
64
  }
@@ -123,31 +121,17 @@ private val hostFontFiles: Map<String, String> = mapOf(
123
121
  )
124
122
  }
125
123
 
126
- /**
127
- * Font mặc định mới, chỉ áp cho user nằm trong whitelist design system ([KitDesignSystem]).
128
- * Ngoài whitelist thì quay về [LEGACY_DEFAULT_FONT_FAMILY] như trước.
129
- */
130
124
  private const val NEW_DEFAULT_FONT_FAMILY = "MomoTrustSans"
131
125
  private const val LEGACY_DEFAULT_FONT_FAMILY = "SFProText"
132
126
 
133
- /**
134
- * Font thuoc bo design system moi. MoMoTrustDisplay chi duoc header dung tu DS-791
135
- * (commit header title 5bfde6251); truoc do header lay font theo theme, nen ngoai
136
- * whitelist ca hai deu ha ve [LEGACY_DEFAULT_FONT_FAMILY].
137
- */
138
- 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
139
129
 
140
130
  @Composable
141
131
  fun getFontFamily(fontFamily: String, fontWeight: FontWeight? = FontWeight.Normal): FontFamily {
142
132
  val isWhiteList = KitDesignSystem.isWhiteList
143
- val resolvedFamily =
144
- if (fontFamily in newFontFamilies && !isWhiteList) {
145
- LEGACY_DEFAULT_FONT_FAMILY
146
- } else {
147
- fontFamily
148
- }
149
133
  val defaultFile = if (isWhiteList) NEW_DEFAULT_FONT_FILE else LEGACY_DEFAULT_FONT_FILE
150
- val key = "$resolvedFamily-${fontWeight?.weight}"
134
+ val key = "$fontFamily-${fontWeight?.weight}"
151
135
  val fileName = hostFontFiles[key] ?: defaultFile
152
136
  return hostFontFamily(fileName)
153
137
  ?: hostFontFamily(defaultFile)
@@ -4,6 +4,7 @@ import androidx.compose.foundation.background
4
4
  import androidx.compose.foundation.layout.Arrangement
5
5
  import androidx.compose.foundation.layout.Box
6
6
  import androidx.compose.foundation.layout.BoxWithConstraints
7
+ import androidx.compose.foundation.layout.ExperimentalLayoutApi
7
8
  import androidx.compose.foundation.layout.FlowRow
8
9
  import androidx.compose.foundation.layout.PaddingValues
9
10
  import androidx.compose.foundation.layout.fillMaxWidth
@@ -14,6 +15,7 @@ import androidx.compose.runtime.CompositionLocalProvider
14
15
  import androidx.compose.ui.Modifier
15
16
  import androidx.compose.ui.draw.clip
16
17
  import androidx.compose.ui.draw.shadow
18
+ import androidx.compose.ui.graphics.Color
17
19
  import androidx.compose.ui.unit.dp
18
20
  import vn.momo.kits.components.Image
19
21
  import vn.momo.kits.const.AppTheme
@@ -21,30 +23,30 @@ import vn.momo.kits.const.Radius
21
23
  import vn.momo.kits.const.Spacing
22
24
 
23
25
  @Composable
24
- internal fun Card(
26
+ @OptIn(ExperimentalLayoutApi::class)
27
+ fun Card(
25
28
  useShadow: Boolean = false,
26
29
  backgroundImage: String? = null,
30
+ backgroundColor: Color? = null,
27
31
  contentPadding: PaddingValues = PaddingValues(Spacing.M),
28
32
  modifier: Modifier = Modifier,
29
33
  content: @Composable () -> Unit,
30
34
  ) {
31
- val columns = 12
32
- val gutter = 8.dp
35
+ val columns = gridColumns()
36
+ val gap = Spacing.S
33
37
  val shape = RoundedCornerShape(Radius.M)
34
- var surface = modifier.fillMaxWidth().padding(horizontal = 12.dp)
35
- if (useShadow) surface = surface.shadow(4.dp, shape)
36
- surface = surface.clip(shape)
37
- if (backgroundImage == null) surface = surface.background(AppTheme.current.colors.background.surface)
38
+ var surface = modifier.fillMaxWidth().padding(horizontal = Spacing.M)
39
+ if (useShadow) surface = surface.shadow(6.dp, shape)
40
+ surface = surface.clip(shape).background(backgroundColor ?: AppTheme.current.colors.background.surface)
38
41
  Box(modifier = surface) {
39
42
  if (backgroundImage != null) {
40
43
  Image(source = backgroundImage, modifier = Modifier.matchParentSize())
41
44
  }
42
45
  BoxWithConstraints(modifier = Modifier.fillMaxWidth().padding(contentPadding)) {
43
- val sizePerSpan = (maxWidth - gutter * (columns - 1)) / columns - 1.dp / columns
44
- CompositionLocalProvider(LocalGridContext provides GridContext(columns, gutter, sizePerSpan)) {
46
+ CompositionLocalProvider(LocalGridContext provides gridContext(constraints.maxWidth, columns, gap)) {
45
47
  FlowRow(
46
- horizontalArrangement = Arrangement.spacedBy(gutter),
47
- verticalArrangement = Arrangement.spacedBy(gutter),
48
+ horizontalArrangement = Arrangement.spacedBy(gap),
49
+ verticalArrangement = Arrangement.spacedBy(gap),
48
50
  ) { content() }
49
51
  }
50
52
  }
@@ -6,33 +6,45 @@ import androidx.compose.foundation.layout.width
6
6
  import androidx.compose.runtime.Composable
7
7
  import androidx.compose.runtime.compositionLocalOf
8
8
  import androidx.compose.ui.Modifier
9
+ import androidx.compose.ui.platform.LocalDensity
9
10
  import androidx.compose.ui.unit.Dp
10
11
  import androidx.compose.ui.unit.dp
12
+ import kotlin.math.floor
11
13
 
12
14
  internal data class GridContext(
13
15
  val numberOfColumns: Int = 12,
14
- val gutter: Dp = 12.dp,
15
- val sizePerSpan: Dp = 0.dp,
16
+ val gutterPx: Int = 0,
17
+ val columnPx: Float = 0f,
18
+ val density: Float = 1f,
16
19
  ) {
17
- fun sizeForSpan(span: Int): Dp = sizePerSpan * span + gutter * (span - 1)
20
+ fun sizeForSpan(span: Int): Dp = (floor(columnPx * span + gutterPx * (span - 1)) / density).dp
18
21
  }
19
22
 
20
23
  internal val LocalGridContext = compositionLocalOf<GridContext?> { null }
21
24
 
22
25
  @Composable
23
- internal fun Item(
24
- widthSpan: Int = 12,
26
+ internal fun gridColumns(): Int = if (getScreenLayout().contentWidth > 600.dp) 24 else 12
27
+
28
+ @Composable
29
+ internal fun gridContext(maxWidthPx: Int, columns: Int, gap: Dp): GridContext {
30
+ val density = LocalDensity.current
31
+ val gutterPx = with(density) { gap.roundToPx() }
32
+ return GridContext(columns, gutterPx, (maxWidthPx - gutterPx * (columns - 1)).toFloat() / columns, density.density)
33
+ }
34
+
35
+ @Composable
36
+ fun Item(
37
+ widthSpan: Int? = null,
25
38
  heightSpan: Int = 0,
26
39
  modifier: Modifier = Modifier,
40
+ contentModifier: Modifier = Modifier,
27
41
  content: @Composable () -> Unit,
28
42
  ) {
29
43
  val grid = LocalGridContext.current
30
44
  var m = modifier
31
45
  if (grid != null) {
32
- m = m.width(grid.sizeForSpan(widthSpan))
46
+ m = m.width(grid.sizeForSpan(widthSpan ?: grid.numberOfColumns))
33
47
  if (heightSpan > 0) m = m.height(grid.sizeForSpan(heightSpan))
34
48
  }
35
- // A Column (not a Box) so multiple children flow VERTICALLY instead of stacking
36
- // on top of each other — matches the React Item's flex-column content.
37
- Column(modifier = m) { content() }
49
+ Column(modifier = m.then(contentModifier)) { content() }
38
50
  }