@momo-kits/native-kits 0.300.3 → 0.300.4-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 (272) hide show
  1. package/CLAUDE.md +51 -7
  2. package/README.md +2 -2
  3. package/build.gradle.kts +13 -0
  4. package/compose/build.gradle.kts +197 -0
  5. package/compose/build.gradle.kts.backup +197 -0
  6. package/compose/compose.podspec +47 -0
  7. package/compose/src/androidMain/kotlin/vn/momo/kits/const/HostFont.android.kt +20 -0
  8. package/compose/src/androidMain/kotlin/vn/momo/kits/navigation/ScrollToTop.android.kt +6 -0
  9. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/BackGesture.android.kt +12 -0
  10. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/OsFontScale.android.kt +7 -0
  11. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +153 -0
  12. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/ReservedRegions.android.kt +6 -0
  13. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +110 -0
  14. package/compose/src/commonMain/kotlin/vn/momo/kits/application/DesignSystemKits.kt +62 -0
  15. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +1071 -0
  16. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Localize.kt +269 -0
  17. package/compose/src/commonMain/kotlin/vn/momo/kits/application/WidgetContainer.kt +65 -0
  18. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Avatar.kt +157 -0
  19. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +81 -0
  20. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeDot.kt +32 -0
  21. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +338 -0
  22. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BaselineView.kt +175 -0
  23. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +357 -0
  24. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +115 -0
  25. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +89 -0
  26. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +128 -0
  27. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Collapse.kt +209 -0
  28. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +542 -0
  29. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +49 -0
  30. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +86 -0
  31. package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +125 -0
  32. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +231 -0
  33. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +189 -0
  34. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +478 -0
  35. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +114 -0
  36. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +265 -0
  37. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +235 -0
  38. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +223 -0
  39. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +265 -0
  40. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +185 -0
  41. package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +343 -0
  42. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Loader.kt +108 -0
  43. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +56 -0
  44. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +41 -0
  45. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +84 -0
  46. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +40 -0
  47. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +361 -0
  48. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +108 -0
  49. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +308 -0
  50. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +59 -0
  51. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Rating.kt +87 -0
  52. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ScaleSizeScope.kt +21 -0
  53. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +90 -0
  54. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +330 -0
  55. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Stepper.kt +220 -0
  56. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Steps.kt +473 -0
  57. package/compose/src/commonMain/kotlin/vn/momo/kits/components/SuggestAction.kt +122 -0
  58. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +207 -0
  59. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +92 -0
  60. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +560 -0
  61. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +86 -0
  62. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +125 -0
  63. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +207 -0
  64. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +501 -0
  65. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +176 -0
  66. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Uploader.kt +182 -0
  67. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +193 -0
  68. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +29 -0
  69. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +236 -0
  70. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +180 -0
  71. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +325 -0
  72. package/compose/src/commonMain/kotlin/vn/momo/kits/const/HostFont.kt +17 -0
  73. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Radius.kt +12 -0
  74. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Spacing.kt +16 -0
  75. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +194 -0
  76. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +287 -0
  77. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +54 -0
  78. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +50 -0
  79. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/ScreenLayout.kt +306 -0
  80. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +41 -0
  81. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +52 -0
  82. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +66 -0
  83. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Conditional.kt +11 -0
  84. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/DeprecatedModifier.kt +104 -0
  85. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +48 -0
  86. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Size.kt +51 -0
  87. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +338 -0
  88. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/HostBackGestureBridge.kt +58 -0
  89. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +137 -0
  90. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +116 -0
  91. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +215 -0
  92. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +404 -0
  93. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ScrollToTop.kt +8 -0
  94. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +732 -0
  95. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackTransition.kt +148 -0
  96. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/SwipeBackHaptic.kt +40 -0
  97. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +233 -0
  98. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +217 -0
  99. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +86 -0
  100. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoBarCompression.kt +97 -0
  101. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoTabBar.kt +377 -0
  102. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +169 -0
  103. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +455 -0
  104. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +103 -0
  105. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderJourney.kt +106 -0
  106. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderLocation.kt +74 -0
  107. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +413 -0
  108. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +39 -0
  109. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +363 -0
  110. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HelpCenterRequest.kt +37 -0
  111. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SideBar.kt +192 -0
  112. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +133 -0
  113. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/ToolkitRequests.kt +41 -0
  114. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/tracking/ScreenTracker.kt +135 -0
  115. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/BackGesture.kt +12 -0
  116. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ComposeLottieAnimation.kt +91 -0
  117. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/OsFontScale.kt +9 -0
  118. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +74 -0
  119. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ReservedRegions.kt +20 -0
  120. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Icons.kt +1329 -0
  121. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +55 -0
  122. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Tracking.kt +15 -0
  123. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +118 -0
  124. package/compose/src/commonTest/kotlin/vn/momo/kits/navigation/component/HelpCenterRequestTest.kt +44 -0
  125. package/compose/src/commonTest/kotlin/vn/momo/kits/navigation/component/ToolkitRequestsTest.kt +62 -0
  126. package/compose/src/iosMain/kotlin/vn/momo/kits/const/HostFont.ios.kt +42 -0
  127. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/HostBackGesture.ios.kt +45 -0
  128. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/ScrollToTop.ios.kt +36 -0
  129. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/BackGesture.ios.kt +38 -0
  130. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/OsFontScale.ios.kt +66 -0
  131. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +217 -0
  132. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/ReservedRegions.ios.kt +138 -0
  133. package/gradle/libs.versions.toml +68 -0
  134. package/gradle/wrapper/gradle-wrapper.jar +0 -0
  135. package/gradle/wrapper/gradle-wrapper.properties +8 -0
  136. package/gradle.properties +26 -0
  137. package/gradlew +252 -0
  138. package/gradlew.bat +94 -0
  139. package/ios/Application/ApplicationEnvironment.swift +24 -6
  140. package/ios/Application/DesignSystemKits.swift +100 -0
  141. package/ios/Application/DesignSystemKitsPayload.swift +68 -0
  142. package/ios/Application/MaxApi.swift +30 -3
  143. package/ios/Avatar/Avatar.swift +4 -3
  144. package/ios/Badge/Badge.swift +6 -4
  145. package/ios/Badge/BadgeDot.swift +1 -1
  146. package/ios/Badge/BadgeRibbon.swift +8 -2
  147. package/ios/Button/Button.swift +15 -12
  148. package/ios/CalculatorKeyboard/CalculatorKeyboard.swift +64 -18
  149. package/ios/Carousel/Carousel.swift +29 -12
  150. package/ios/Checkbox/Checkbox.swift +7 -3
  151. package/ios/Chip/Chip.swift +5 -4
  152. package/ios/Collapse/Collapse.swift +70 -19
  153. package/ios/Colors+Radius+Spacing/Colors.swift +10 -1
  154. package/ios/Colors+Radius+Spacing/Radius.swift +6 -0
  155. package/ios/DateTimePicker/DateTimePicker.swift +54 -20
  156. package/ios/DateTimePicker/WheelPicker.swift +103 -105
  157. package/ios/Divider/Divider.swift +37 -4
  158. package/ios/Extensions/Color++.swift +38 -9
  159. package/ios/Extensions/OptionalEnvironmentObject.swift +12 -0
  160. package/ios/Extensions/SkeletonUICompat.swift +11 -0
  161. package/ios/Icon/Icon.swift +21 -23
  162. package/ios/Icon/Icons.swift +1329 -0
  163. package/ios/IconButton/IconButton.swift +15 -7
  164. package/ios/Image/Image.swift +7 -23
  165. package/ios/Information/Information.swift +27 -9
  166. package/ios/Input/ErrorView.swift +2 -2
  167. package/ios/Input/Input.swift +171 -148
  168. package/ios/Input/InputPhoneNumber.swift +10 -9
  169. package/ios/Input/InputSearch.swift +107 -78
  170. package/ios/Input/InputTextArea.swift +133 -148
  171. package/ios/InputDropDown/InputDropDown.swift +10 -7
  172. package/ios/InputMoney/InputMoney.swift +32 -11
  173. package/ios/InputOTP/InputOTP.swift +22 -12
  174. package/ios/Layout/Card.swift +50 -0
  175. package/ios/Layout/GridContext.swift +187 -3
  176. package/ios/Layout/Item.swift +5 -4
  177. package/ios/Layout/ScreenLayout.swift +415 -0
  178. package/ios/Layout/Section.swift +4 -100
  179. package/ios/Loader/Loader.swift +5 -9
  180. package/ios/Lottie/LottieView.swift +14 -2
  181. package/ios/{Application/Navigation/Overplay → Navigation}/BottomSheet.swift +169 -29
  182. package/ios/Navigation/BottomTab/BottomTab.swift +332 -0
  183. package/ios/{Application/Navigation → Navigation}/BottomTab/BottomTabBar.swift +18 -7
  184. package/ios/Navigation/BottomTab/DuoBarCompression.swift +104 -0
  185. package/ios/Navigation/BottomTab/DuoTabBar.swift +329 -0
  186. package/ios/{Application → Navigation/Component}/FloatingButton.swift +43 -54
  187. package/ios/Navigation/Component/Header.swift +545 -0
  188. package/ios/Navigation/Component/HeaderBackground.swift +154 -0
  189. package/ios/Navigation/Component/HeaderJourney.swift +73 -0
  190. package/ios/Navigation/Component/HeaderLocation.swift +48 -0
  191. package/ios/Navigation/Component/HeaderRight.swift +425 -0
  192. package/ios/Navigation/Component/HeaderTitle.swift +36 -0
  193. package/ios/{Application/Navigation → Navigation/Component}/HeaderUser.swift +21 -24
  194. package/ios/Navigation/Component/SideBar.swift +183 -0
  195. package/ios/{Application/Navigation/Overplay → Navigation/Component}/SnackBar.swift +8 -8
  196. package/ios/Navigation/HostBackGestureBridge.swift +78 -0
  197. package/ios/{Application/Navigation/Overplay → Navigation}/ModalScreen.swift +9 -2
  198. package/ios/Navigation/Navigation.swift +204 -0
  199. package/ios/Navigation/NavigationContainer.swift +195 -0
  200. package/ios/{Application/Navigation → Navigation}/Navigator.swift +207 -107
  201. package/ios/Navigation/StackScreen.swift +930 -0
  202. package/ios/Navigation/StackTransition.swift +400 -0
  203. package/ios/Navigation/SwipeBackHaptic.swift +33 -0
  204. package/ios/{Application/Navigation → Navigation}/Tracking/ScreenTracker.swift +12 -5
  205. package/ios/OTPKeyboard/OTPKeyboard.swift +8 -11
  206. package/ios/Pagination/PaginationScroll.swift +2 -2
  207. package/ios/Popup/PopupDisplay.swift +72 -135
  208. package/ios/Popup/PopupInput.swift +10 -13
  209. package/ios/Popup/PopupLayout.swift +138 -0
  210. package/ios/Popup/PopupNotify.swift +59 -69
  211. package/ios/Popup/PopupPromotion.swift +31 -47
  212. package/ios/PopupView/FullscreenPopup.swift +13 -19
  213. package/ios/PopupView/PopupView.swift +1 -1
  214. package/ios/PopupView/Utils++.swift +0 -8
  215. package/ios/ProgressInfo/ProgressInfo.swift +4 -2
  216. package/ios/Radio/Radio.swift +2 -1
  217. package/ios/Rating/Rating.swift +9 -1
  218. package/ios/Resources/dot_loading.json +558 -0
  219. package/ios/Resources/lottie_circle_loader.json +1 -0
  220. package/ios/ScrollIndicator/ScrollIndicator.swift +11 -10
  221. package/ios/Skeleton/Skeleton.swift +22 -27
  222. package/ios/Slider/Slider.swift +16 -18
  223. package/ios/Stepper/Stepper.swift +7 -3
  224. package/ios/Steps/Steps.swift +8 -6
  225. package/ios/SuggestAction/SuggestAction.swift +5 -0
  226. package/ios/Swipeable/SwipeCell.swift +10 -6
  227. package/ios/Switch/Switch.swift +22 -20
  228. package/ios/TabView/TabView.swift +102 -21
  229. package/ios/Tag/Tag.swift +15 -4
  230. package/ios/Template/Logo/Logo.swift +2 -1
  231. package/ios/Template/TrustBanner/TrustBanner.swift +109 -55
  232. package/ios/Title/Title.swift +6 -17
  233. package/ios/Tooltip/Tooltip.swift +29 -14
  234. package/ios/Typography/FontScale.swift +17 -14
  235. package/ios/Typography/Text.swift +156 -20
  236. package/ios/Typography/Typography.swift +2 -2
  237. package/ios/Uploader/Uploader.swift +3 -1
  238. package/ios/native-kits.podspec +2 -3
  239. package/ios-demo/MoMoUIKitsDemo.podspec +1 -3
  240. package/ios-demo/README.md +120 -0
  241. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ContentView.swift +1 -1
  242. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DuoTabDemo.swift +80 -0
  243. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +2 -0
  244. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IPhoneDuoDemo.swift +173 -0
  245. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/NavigationDemo.swift +77 -1
  246. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupDisplayDemo.swift +1 -0
  247. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupNotifyDemo.swift +2 -2
  248. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupPromotionDemo.swift +2 -0
  249. package/package.json +1 -1
  250. package/publish.sh +19 -2
  251. package/scripts/gen-ios-icons.mjs +36 -0
  252. package/settings.gradle.kts +64 -0
  253. package/ios/Application/Components.swift +0 -270
  254. package/ios/Application/ComposeApi.swift +0 -22
  255. package/ios/Application/HeaderRight.swift +0 -306
  256. package/ios/Application/Navigation/BottomTab/BottomTab.swift +0 -104
  257. package/ios/Application/Navigation/HeaderBackProps.swift +0 -66
  258. package/ios/Application/Navigation/HeaderTitle.swift +0 -57
  259. package/ios/Application/Navigation/NavigationContainer.swift +0 -213
  260. package/ios/Application/Navigation/NavigationOptions.swift +0 -109
  261. package/ios/Application/Navigation/NavigatorManager.swift +0 -22
  262. package/ios/Application/Navigation/component/Header.swift +0 -178
  263. package/ios/Application/Navigation/component/HeaderBackground.swift +0 -83
  264. package/ios/Application/Navigation/component/HeaderColor.swift +0 -65
  265. package/ios/Application/Navigation/component/HeaderRightWidthKey.swift +0 -11
  266. package/ios/Application/Navigation/component/NavigationFooter.swift +0 -90
  267. package/ios/Application/Navigation/component/NavigationHeaderRight.swift +0 -196
  268. package/ios/Application/Screen.swift +0 -250
  269. package/ios/Application/StackScreen.swift +0 -492
  270. package/ios/Typography/FontScaleStore.swift +0 -58
  271. package/ios/Typography/KitDesignSystem.swift +0 -17
  272. /package/ios/{Application/Navigation → Navigation}/BottomTab/CurvedContainer.swift +0 -0
@@ -0,0 +1,86 @@
1
+ package vn.momo.kits.navigation.bottomtab
2
+
3
+ import androidx.compose.foundation.Canvas
4
+ import androidx.compose.foundation.layout.Box
5
+ import androidx.compose.foundation.layout.height
6
+ import androidx.compose.foundation.layout.offset
7
+ import androidx.compose.foundation.layout.width
8
+ import androidx.compose.runtime.Composable
9
+ import androidx.compose.ui.Alignment
10
+ import androidx.compose.ui.Modifier
11
+ import androidx.compose.ui.graphics.Path
12
+ import androidx.compose.ui.platform.LocalDensity
13
+ import androidx.compose.ui.unit.Dp
14
+ import androidx.compose.ui.unit.IntOffset
15
+ import androidx.compose.ui.unit.dp
16
+ import androidx.compose.ui.zIndex
17
+ import vn.momo.kits.const.AppTheme
18
+
19
+ @Composable
20
+ internal fun CurvedContainer(
21
+ circleSize: Dp = 48.dp,
22
+ tabBarItemIconSize: Dp = 28.dp,
23
+ circleOverSize: Dp = 2.dp,
24
+ width: Dp = 71.dp,
25
+ height: Dp = 16.dp
26
+ ) {
27
+ val theme = AppTheme.current
28
+ val density = LocalDensity.current
29
+ val widthPx = with(density) { width.toPx()}
30
+
31
+ val circleSizePx = with(density) { circleSize.toPx() }
32
+ val tabBarItemIconSizePx = with(density) { tabBarItemIconSize.toPx() }
33
+ val circleOverSizePx = with(density) { circleOverSize.toPx() }
34
+
35
+ val circlePositionTop = circleSizePx - tabBarItemIconSizePx - 10f
36
+ val circleOverTop = circlePositionTop + circleOverSizePx
37
+ val circleR = circleSizePx / 2
38
+ val circleOverR = circleR + circleOverSizePx
39
+
40
+ val centerX = widthPx / 2
41
+
42
+ val curvedPath = getPath(
43
+ circleOverTop = circleOverTop,
44
+ centerX = centerX,
45
+ circleOverR = circleOverR,
46
+ widthPx = widthPx
47
+ )
48
+
49
+ Box(
50
+ contentAlignment = Alignment.BottomCenter
51
+ ){
52
+ Canvas(
53
+ modifier = Modifier
54
+ .width(width)
55
+ .height(height)
56
+ .offset{ IntOffset(x = 0, y = 24) }
57
+ .zIndex(-1f)
58
+ ) {
59
+ drawPath(path = curvedPath, color = theme.colors.background.surface)
60
+ }
61
+ }
62
+ }
63
+
64
+ private fun getPath(circleOverTop: Float, centerX: Float, circleOverR: Float, widthPx: Float): Path{
65
+ return Path().apply {
66
+ moveTo(0f, circleOverTop)
67
+ lineTo(centerX - circleOverR - 40f, circleOverTop)
68
+
69
+ cubicTo(
70
+ centerX - circleOverR, circleOverTop,
71
+ centerX - circleOverR, 0f,
72
+ centerX, 0f
73
+ )
74
+
75
+ cubicTo(
76
+ centerX + circleOverR, 0f,
77
+ centerX + circleOverR, circleOverTop,
78
+ centerX + circleOverR + 40f, circleOverTop
79
+ )
80
+
81
+ lineTo(widthPx, circleOverTop)
82
+ lineTo(widthPx, 60f)
83
+ lineTo(0f, 60f)
84
+ close()
85
+ }
86
+ }
@@ -0,0 +1,97 @@
1
+ package vn.momo.kits.navigation.bottomtab
2
+
3
+ import androidx.compose.runtime.Composable
4
+ import androidx.compose.runtime.Stable
5
+ import androidx.compose.runtime.compositionLocalOf
6
+ import androidx.compose.runtime.getValue
7
+ import androidx.compose.runtime.mutableStateOf
8
+ import androidx.compose.runtime.remember
9
+ import androidx.compose.runtime.setValue
10
+ import androidx.compose.ui.unit.Dp
11
+ import vn.momo.kits.const.Spacing
12
+ import vn.momo.kits.layout.DuoMetrics
13
+ import vn.momo.kits.layout.ScreenLayout
14
+
15
+ // iPhone Duo: which side bar bar starts compressed when the strip cannot hold both whole.
16
+ enum class DuoBarCompressionBehavior { TOOLBAR, TAB_BAR }
17
+
18
+ private enum class DuoBar { TOOLBAR, TAB_BAR }
19
+
20
+ @Stable
21
+ internal class DuoBarCompression(
22
+ val toolbarCompressed: Boolean = false,
23
+ val tabBarCompressed: Boolean = false,
24
+ // the open rail needs the toolbar button slot too: only Back stays
25
+ val toolbarHidden: Boolean = false,
26
+ val expandToolbar: () -> Unit = {},
27
+ val expandTabBar: () -> Unit = {},
28
+ // a rail opened from the compressed state closes once a tab is picked
29
+ val onTabSelected: () -> Unit = {},
30
+ // side bar columns report their full height so the tab bar knows what is left
31
+ val setToolbarHeight: (Dp) -> Unit = {},
32
+ )
33
+
34
+ internal val LocalDuoBarCompression = compositionLocalOf { DuoBarCompression() }
35
+
36
+ // space kept between the top and the bottom groups (HIG Vertical controls)
37
+ internal val DUO_GROUP_GAP = Spacing.M
38
+ // Back, then the right toolkit: help + home capsule
39
+ private val DEFAULT_TOOLBAR_HEIGHT = DuoMetrics.controlSize + Spacing.M + DuoMetrics.itemPitch * 2
40
+ // Back, then the single button the toolbar folds into
41
+ private val COMPRESSED_TOOLBAR_HEIGHT = DuoMetrics.controlSize * 2 + Spacing.M
42
+
43
+ // Port of RN DuoBarCompression (HIG, Designing for iPhone Duo: Vertical controls):
44
+ // - both fit: both stay whole
45
+ // - one fits whole beside the other compressed: one is compressed, tapping it swaps them
46
+ // - otherwise both rest compressed; tapping one opens it for a moment (the rail closes
47
+ // again once a tab is picked)
48
+ @Composable
49
+ internal fun rememberDuoBarCompression(
50
+ behavior: DuoBarCompressionBehavior,
51
+ tabCount: Int,
52
+ hasQr: Boolean,
53
+ layout: ScreenLayout,
54
+ ): DuoBarCompression {
55
+ var toolbarHeight by remember { mutableStateOf(DEFAULT_TOOLBAR_HEIGHT) }
56
+ var expanded by remember(behavior) {
57
+ mutableStateOf(if (behavior == DuoBarCompressionBehavior.TOOLBAR) DuoBar.TAB_BAR else DuoBar.TOOLBAR)
58
+ }
59
+ var opened by remember { mutableStateOf<DuoBar?>(null) }
60
+ val setToolbarHeight = remember { { height: Dp -> toolbarHeight = height } }
61
+ val sideBar = layout.sideBar
62
+
63
+ return remember(sideBar, layout.height, tabCount, hasQr, toolbarHeight, expanded, opened) {
64
+ if (sideBar == null) return@remember DuoBarCompression()
65
+ val room = layout.height - sideBar.bottom - sideBar.top - DUO_GROUP_GAP
66
+ val tabBar = duoRailHeight(tabCount, hasQr)
67
+ val compressedTabBar = duoRailHeight(1, hasQr, compressed = true)
68
+ fun fits(toolbar: Dp, tabs: Dp) = toolbar + tabs <= room
69
+
70
+ when {
71
+ fits(toolbarHeight, tabBar) -> DuoBarCompression(setToolbarHeight = setToolbarHeight)
72
+ fits(COMPRESSED_TOOLBAR_HEIGHT, tabBar) && fits(toolbarHeight, compressedTabBar) -> DuoBarCompression(
73
+ toolbarCompressed = expanded == DuoBar.TAB_BAR,
74
+ tabBarCompressed = expanded == DuoBar.TOOLBAR,
75
+ expandToolbar = { expanded = DuoBar.TOOLBAR },
76
+ expandTabBar = { expanded = DuoBar.TAB_BAR },
77
+ setToolbarHeight = setToolbarHeight,
78
+ )
79
+ // an opened rail always shows, even when only the group gap is short: it closes once a tab is picked
80
+ opened == DuoBar.TAB_BAR -> DuoBarCompression(
81
+ toolbarCompressed = true,
82
+ toolbarHidden = true,
83
+ expandToolbar = { opened = DuoBar.TOOLBAR },
84
+ expandTabBar = { opened = DuoBar.TAB_BAR },
85
+ onTabSelected = { opened = null },
86
+ setToolbarHeight = setToolbarHeight,
87
+ )
88
+ else -> DuoBarCompression(
89
+ toolbarCompressed = opened != DuoBar.TOOLBAR,
90
+ tabBarCompressed = true,
91
+ expandToolbar = { opened = DuoBar.TOOLBAR },
92
+ expandTabBar = { opened = DuoBar.TAB_BAR },
93
+ setToolbarHeight = setToolbarHeight,
94
+ )
95
+ }
96
+ }
97
+ }
@@ -0,0 +1,377 @@
1
+ package vn.momo.kits.navigation.bottomtab
2
+
3
+ import androidx.compose.animation.core.Spring
4
+ import androidx.compose.animation.core.animateDpAsState
5
+ import androidx.compose.animation.core.animateFloatAsState
6
+ import androidx.compose.animation.core.spring
7
+ import androidx.compose.foundation.background
8
+ import androidx.compose.foundation.clickable
9
+ import androidx.compose.foundation.interaction.MutableInteractionSource
10
+ import androidx.compose.foundation.interaction.collectIsPressedAsState
11
+ import androidx.compose.foundation.border
12
+ import androidx.compose.foundation.layout.Arrangement
13
+ import androidx.compose.foundation.layout.Box
14
+ import androidx.compose.foundation.layout.BoxScope
15
+ import androidx.compose.foundation.layout.BoxWithConstraints
16
+ import androidx.compose.foundation.layout.Column
17
+ import androidx.compose.foundation.layout.Row
18
+ import androidx.compose.foundation.layout.RowScope
19
+ import androidx.compose.foundation.layout.absolutePadding
20
+ import androidx.compose.foundation.layout.fillMaxHeight
21
+ import androidx.compose.foundation.layout.fillMaxSize
22
+ import androidx.compose.foundation.layout.fillMaxWidth
23
+ import androidx.compose.foundation.layout.height
24
+ import androidx.compose.foundation.layout.offset
25
+ import androidx.compose.foundation.layout.padding
26
+ import androidx.compose.foundation.layout.size
27
+ import androidx.compose.foundation.layout.width
28
+ import androidx.compose.foundation.shape.CircleShape
29
+ import androidx.compose.foundation.shape.RoundedCornerShape
30
+ import androidx.compose.runtime.Composable
31
+ import androidx.compose.runtime.CompositionLocalProvider
32
+ import androidx.compose.runtime.LaunchedEffect
33
+ import androidx.compose.runtime.MutableState
34
+ import androidx.compose.runtime.compositionLocalOf
35
+ import androidx.compose.runtime.getValue
36
+ import androidx.compose.runtime.mutableStateOf
37
+ import androidx.compose.runtime.remember
38
+ import androidx.compose.ui.AbsoluteAlignment
39
+ import androidx.compose.ui.Alignment
40
+ import androidx.compose.ui.Modifier
41
+ import androidx.compose.ui.draw.clip
42
+ import androidx.compose.ui.draw.shadow
43
+ import androidx.compose.ui.graphics.graphicsLayer
44
+ import androidx.compose.ui.graphics.Color
45
+ import androidx.compose.ui.graphics.Shape
46
+ import androidx.compose.ui.semantics.Role
47
+ import androidx.compose.ui.text.style.TextOverflow
48
+ import androidx.compose.ui.unit.Dp
49
+ import androidx.compose.ui.unit.dp
50
+ import vn.momo.kits.application.LocalContext
51
+ import vn.momo.kits.application.MiniAppContext
52
+ import vn.momo.kits.components.Badge
53
+ import vn.momo.kits.components.BadgeDot
54
+ import vn.momo.kits.components.DotSize
55
+ import vn.momo.kits.components.Icon
56
+ import vn.momo.kits.components.Text
57
+ import vn.momo.kits.const.AppTheme
58
+ import vn.momo.kits.const.Spacing
59
+ import vn.momo.kits.const.Typography
60
+ import vn.momo.kits.layout.DuoMetrics
61
+ import vn.momo.kits.layout.ScreenLayout
62
+ import vn.momo.kits.layout.SideBarEdge
63
+ import vn.momo.kits.modifier.noFeedbackClickable
64
+ import vn.momo.kits.modifier.setAutomationId
65
+
66
+ // Figma Fold-Design-System MT1/MT2, mirrored from RN DuoTabBar.tsx.
67
+ private val ITEM_SIZE = DuoMetrics.controlSize
68
+ private val ICON_SIZE = 28.dp
69
+ private val QR_ICON_SIZE = 24.dp
70
+ private val RAIL_PADDING = 6.dp
71
+ private val RAIL_GAP = Spacing.XXS
72
+ // selection bubble overflows the 48 rail cell by 5 above and below
73
+ private val RAIL_BUBBLE_WIDTH = 44.dp
74
+ private val RAIL_BUBBLE_HEIGHT = 58.dp
75
+ private val FLOATING_HEIGHT = DuoMetrics.floatingTabBarHeight
76
+ private val FLOATING_PADDING = Spacing.XS
77
+ // the system bar sizes its pill to the tabs instead of stretching it across the display
78
+ private val FLOATING_ITEM_WIDTH = 80.dp
79
+ // while pressed the selection bubble grows into a lens past the pill's edges
80
+ private const val LENS_SCALE_X = 1.2f
81
+ private const val LENS_SCALE_Y = 1.3f
82
+ // Figma Home-2026 tab bar "Selection": brand pink at 8%
83
+ private const val SELECTION_TINT_ALPHA = 0x14 / 255f
84
+ // RN reanimated spring { damping: 18, stiffness: 220, mass: 0.8 }
85
+ private val BUBBLE_SPRING = spring<Dp>(dampingRatio = 0.68f, stiffness = 275f, visibilityThreshold = Dp(0.5f))
86
+ private val LENS_SPRING = spring<Float>(dampingRatio = 0.68f, stiffness = 275f)
87
+
88
+ // Space from the window bottom the rail stack and its gap take, so the header side bar stays above it.
89
+ internal val LocalDuoRailReserve = compositionLocalOf { 0.dp }
90
+
91
+ // Rail plus the QR button under it; compressed, the rail is a single button.
92
+ internal fun duoRailHeight(itemCount: Int, hasQr: Boolean, compressed: Boolean = false): Dp {
93
+ val rail = if (compressed) ITEM_SIZE
94
+ else RAIL_PADDING * 2 + ITEM_SIZE * itemCount + RAIL_GAP * (itemCount - 1).coerceAtLeast(0)
95
+ return rail + if (hasQr) Spacing.M + ITEM_SIZE else 0.dp
96
+ }
97
+
98
+ // Overlaps the home indicator inset like the system tab bar (21pt above the bottom with a 34pt inset).
99
+ private fun floatingTabBarBottom(insetBottom: Dp): Dp =
100
+ maxOf(insetBottom - DuoMetrics.floatingTabBarInsetOverlap, Spacing.S)
101
+
102
+ // Space tab content leaves under the floating bar; 0 when the rail sits in the side strip or off Duo.
103
+ internal fun duoFloatingTabBarInset(layout: ScreenLayout): Dp =
104
+ if (layout.isIPhoneDuo && layout.sideBar == null) FLOATING_HEIGHT + floatingTabBarBottom(layout.insetBottom) else 0.dp
105
+
106
+ // iPhone Duo tab bar: rail in the side bar, or a floating bar when unfolded portrait.
107
+ @Composable
108
+ internal fun BoxScope.DuoTabBar(
109
+ layout: ScreenLayout,
110
+ items: List<BottomTabItem>,
111
+ floatingButton: BottomTabFloatingButton?,
112
+ selectedIndex: Int,
113
+ onTabSelected: (Int) -> Unit,
114
+ compression: DuoBarCompression = DuoBarCompression(),
115
+ ) {
116
+ if (layout.sideBar != null) {
117
+ DuoRailTabBar(layout, items, floatingButton, selectedIndex, onTabSelected, compression)
118
+ } else {
119
+ DuoFloatingTabBar(layout, items, floatingButton, selectedIndex, onTabSelected)
120
+ }
121
+ }
122
+
123
+ // Vertical bar anchored to the bottom of the side bar, QR button below it (Figma MT1-C/E). When the
124
+ // side bar is short it can shrink to one button showing the current tab (HIG "Tab bar compressed");
125
+ // tapping it expands the rail and compresses the toolbar.
126
+ @Composable
127
+ private fun BoxScope.DuoRailTabBar(
128
+ layout: ScreenLayout,
129
+ items: List<BottomTabItem>,
130
+ floatingButton: BottomTabFloatingButton?,
131
+ selectedIndex: Int,
132
+ onTabSelected: (Int) -> Unit,
133
+ compression: DuoBarCompression,
134
+ ) {
135
+ val sideBar = layout.sideBar ?: return
136
+ val isRight = sideBar.edge == SideBarEdge.RIGHT
137
+
138
+ Column(
139
+ modifier = Modifier
140
+ .align(if (isRight) AbsoluteAlignment.BottomRight else AbsoluteAlignment.BottomLeft)
141
+ .width(sideBar.width)
142
+ .padding(bottom = sideBar.bottom)
143
+ .absolutePadding(left = if (isRight) Spacing.M else 0.dp, right = if (isRight) 0.dp else Spacing.M),
144
+ horizontalAlignment = if (isRight) AbsoluteAlignment.Left else AbsoluteAlignment.Right,
145
+ verticalArrangement = Arrangement.spacedBy(Spacing.M),
146
+ ) {
147
+ val current = items.getOrNull(selectedIndex)
148
+ if (compression.tabBarCompressed && current != null) {
149
+ Box(
150
+ modifier = Modifier
151
+ .size(ITEM_SIZE)
152
+ .tabBarMaterial(CircleShape)
153
+ .noFeedbackClickable(onClick = compression.expandTabBar)
154
+ .setAutomationId("bottom_tab_bar"),
155
+ contentAlignment = Alignment.Center,
156
+ ) {
157
+ DuoTabIcon(current, selected = true)
158
+ }
159
+ } else {
160
+ Rail(items, selectedIndex) {
161
+ onTabSelected(it)
162
+ compression.onTabSelected()
163
+ }
164
+ }
165
+ if (floatingButton != null) {
166
+ QrButton(floatingButton, size = ITEM_SIZE)
167
+ }
168
+ }
169
+ }
170
+
171
+ // Column of icon-only tab items with the sliding selection bubble.
172
+ @Composable
173
+ private fun Rail(items: List<BottomTabItem>, selectedIndex: Int, onItemClick: (Int) -> Unit) {
174
+ val pressedIndex = remember { mutableStateOf<Int?>(null) }
175
+ val bubbleOffset by animateDpAsState(
176
+ targetValue = (ITEM_SIZE + RAIL_GAP) * (pressedIndex.value ?: selectedIndex).coerceAtLeast(0),
177
+ animationSpec = BUBBLE_SPRING,
178
+ label = "DuoRailBubble",
179
+ )
180
+ Box(
181
+ Modifier
182
+ .width(ITEM_SIZE)
183
+ .tabBarMaterial(CircleShape)
184
+ .setAutomationId("bottom_tab_bar")
185
+ ) {
186
+ Box(
187
+ Modifier
188
+ .offset(
189
+ x = (ITEM_SIZE - RAIL_BUBBLE_WIDTH) / 2,
190
+ y = RAIL_PADDING - (RAIL_BUBBLE_HEIGHT - ITEM_SIZE) / 2 + bubbleOffset,
191
+ )
192
+ .size(RAIL_BUBBLE_WIDTH, RAIL_BUBBLE_HEIGHT)
193
+ .selectionLens(pressedIndex.value != null)
194
+ )
195
+ Column(
196
+ modifier = Modifier.padding(vertical = RAIL_PADDING),
197
+ verticalArrangement = Arrangement.spacedBy(RAIL_GAP),
198
+ ) {
199
+ items.forEachIndexed { index, item ->
200
+ Box(
201
+ modifier = Modifier
202
+ .size(ITEM_SIZE)
203
+ .tabClickable(index, pressedIndex) { onItemClick(index) },
204
+ contentAlignment = Alignment.Center,
205
+ ) {
206
+ DuoTabIcon(item, selected = index == selectedIndex)
207
+ }
208
+ }
209
+ }
210
+ }
211
+ }
212
+
213
+ // Floating labelled bar for the unfolded portrait display, QR pinned in its own platter beside it.
214
+ @Composable
215
+ private fun BoxScope.DuoFloatingTabBar(
216
+ layout: ScreenLayout,
217
+ items: List<BottomTabItem>,
218
+ floatingButton: BottomTabFloatingButton?,
219
+ selectedIndex: Int,
220
+ onTabSelected: (Int) -> Unit,
221
+ ) {
222
+ val pressedIndex = remember { mutableStateOf<Int?>(null) }
223
+
224
+ Row(
225
+ modifier = Modifier
226
+ .align(Alignment.BottomCenter)
227
+ .fillMaxWidth()
228
+ .absolutePadding(
229
+ left = layout.insetLeft + DuoMetrics.floatingTabBarMargin,
230
+ right = layout.insetRight + DuoMetrics.floatingTabBarMargin,
231
+ )
232
+ .padding(bottom = floatingTabBarBottom(layout.insetBottom)),
233
+ horizontalArrangement = Arrangement.spacedBy(Spacing.S, Alignment.CenterHorizontally),
234
+ ) {
235
+ Box(
236
+ modifier = Modifier
237
+ .weight(1f, fill = false)
238
+ .width(FLOATING_ITEM_WIDTH * items.size + FLOATING_PADDING * 2)
239
+ .height(FLOATING_HEIGHT)
240
+ .tabBarMaterial(RoundedCornerShape(FLOATING_HEIGHT / 2))
241
+ .padding(FLOATING_PADDING)
242
+ .setAutomationId("bottom_tab_bar")
243
+ ) {
244
+ BoxWithConstraints(Modifier.fillMaxSize()) {
245
+ val cellWidth = maxWidth / items.size.coerceAtLeast(1)
246
+ val bubbleOffset by animateDpAsState(
247
+ targetValue = cellWidth * (pressedIndex.value ?: selectedIndex).coerceAtLeast(0),
248
+ animationSpec = BUBBLE_SPRING,
249
+ label = "DuoFloatingBubble",
250
+ )
251
+ Box(
252
+ Modifier
253
+ .offset(x = bubbleOffset)
254
+ .width(cellWidth)
255
+ .fillMaxHeight()
256
+ .selectionLens(pressedIndex.value != null)
257
+ )
258
+ Row(Modifier.fillMaxSize()) {
259
+ items.forEachIndexed { index, item ->
260
+ FloatingCell(item, selected = index == selectedIndex, index, pressedIndex) {
261
+ onTabSelected(index)
262
+ }
263
+ }
264
+ }
265
+ }
266
+ }
267
+ if (floatingButton != null) {
268
+ QrButton(floatingButton, size = FLOATING_HEIGHT, Modifier.shadow(3.dp, CircleShape))
269
+ }
270
+ }
271
+ }
272
+
273
+ @Composable
274
+ private fun RowScope.FloatingCell(
275
+ item: BottomTabItem,
276
+ selected: Boolean,
277
+ index: Int,
278
+ pressedIndex: MutableState<Int?>,
279
+ onClick: () -> Unit,
280
+ ) {
281
+ val colors = AppTheme.current.colors
282
+ Column(
283
+ modifier = Modifier
284
+ .weight(1f)
285
+ .fillMaxHeight()
286
+ .tabClickable(index, pressedIndex, onClick),
287
+ horizontalAlignment = Alignment.CenterHorizontally,
288
+ verticalArrangement = Arrangement.spacedBy(Spacing.XXS, Alignment.CenterVertically),
289
+ ) {
290
+ DuoTabIcon(item, selected)
291
+ // like the system tab bar the label keeps its design size; Text grows it up to 5pt on the wide Duo displays
292
+ val context = LocalContext.current
293
+ val fixedScale = remember(context) {
294
+ (context ?: MiniAppContext()).copy(userScaleRate = 1f, useOSScaleRate = false)
295
+ }
296
+ CompositionLocalProvider(LocalContext provides fixedScale) {
297
+ Text(
298
+ text = item.label,
299
+ style = Typography.labelXsMedium,
300
+ color = if (selected) colors.primary else colors.text.hint,
301
+ maxLines = 1,
302
+ overflow = TextOverflow.Ellipsis,
303
+ )
304
+ }
305
+ }
306
+ }
307
+
308
+ // Tracks the finger so the selection lens jumps to the pressed tab before it is selected.
309
+ @Composable
310
+ private fun Modifier.tabClickable(index: Int, pressedIndex: MutableState<Int?>, onClick: () -> Unit): Modifier {
311
+ val interactionSource = remember { MutableInteractionSource() }
312
+ val pressed by interactionSource.collectIsPressedAsState()
313
+ LaunchedEffect(pressed) {
314
+ if (pressed) pressedIndex.value = index
315
+ else if (pressedIndex.value == index) pressedIndex.value = null
316
+ }
317
+ return clickable(interactionSource = interactionSource, indication = null, role = Role.Tab, onClick = onClick)
318
+ }
319
+
320
+ // Selection bubble; while pressed it grows into a lens (80x54 to about 96x70).
321
+ @Composable
322
+ private fun Modifier.selectionLens(pressed: Boolean): Modifier {
323
+ val lens by animateFloatAsState(if (pressed) 1f else 0f, LENS_SPRING, label = "DuoSelectionLens")
324
+ val tint = AppTheme.current.colors.primary.copy(alpha = SELECTION_TINT_ALPHA)
325
+ return graphicsLayer {
326
+ scaleX = 1 + lens * (LENS_SCALE_X - 1)
327
+ scaleY = 1 + lens * (LENS_SCALE_Y - 1)
328
+ }.background(tint, CircleShape)
329
+ }
330
+
331
+ @Composable
332
+ private fun DuoTabIcon(item: BottomTabItem, selected: Boolean) {
333
+ val colors = AppTheme.current.colors
334
+ val badgeLabel = item.badgeLabel?.takeUnless { it.toIntOrNull() == 0 }
335
+ Box {
336
+ Icon(
337
+ source = item.icon,
338
+ size = ICON_SIZE,
339
+ color = if (selected) colors.primary else colors.text.hint,
340
+ )
341
+ when {
342
+ badgeLabel == null && !item.showDot -> Unit
343
+ badgeLabel.isNullOrEmpty() -> BadgeDot(
344
+ size = DotSize.Small,
345
+ modifier = Modifier.align(Alignment.TopEnd),
346
+ )
347
+ else -> Badge(
348
+ badgeLabel,
349
+ modifier = Modifier.align(Alignment.TopStart).offset(x = ICON_SIZE / 2, y = -Spacing.XS),
350
+ )
351
+ }
352
+ }
353
+ }
354
+
355
+ @Composable
356
+ private fun QrButton(data: BottomTabFloatingButton, size: Dp, modifier: Modifier = Modifier) {
357
+ Box(
358
+ modifier = modifier
359
+ .size(size)
360
+ .clip(CircleShape)
361
+ .background(AppTheme.current.colors.primary)
362
+ .noFeedbackClickable(onClick = data.onPress)
363
+ .setAutomationId(data.label),
364
+ contentAlignment = Alignment.Center,
365
+ ) {
366
+ Icon(source = data.icon, color = Color.White, size = QR_ICON_SIZE)
367
+ }
368
+ }
369
+
370
+ // RN BarMaterial: surface with a 1dp border.
371
+ @Composable
372
+ private fun Modifier.tabBarMaterial(shape: Shape): Modifier {
373
+ val colors = AppTheme.current.colors
374
+ return shadow(3.dp, shape)
375
+ .background(colors.background.surface)
376
+ .border(1.dp, colors.border.default, shape)
377
+ }