@momo-kits/native-kits 0.300.3-debug → 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 (218) hide show
  1. package/CLAUDE.md +51 -7
  2. package/README.md +2 -2
  3. package/compose/build.gradle.kts +10 -2
  4. package/compose/build.gradle.kts.backup +8 -0
  5. package/compose/compose.podspec +1 -3
  6. package/compose/src/androidMain/kotlin/vn/momo/kits/const/HostFont.android.kt +4 -3
  7. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +2 -0
  8. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/ReservedRegions.android.kt +6 -0
  9. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +0 -14
  10. package/compose/src/commonMain/kotlin/vn/momo/kits/application/DesignSystemKits.kt +62 -0
  11. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +351 -111
  12. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +20 -21
  13. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +2 -1
  14. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +4 -2
  15. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +1 -1
  16. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +1 -1
  17. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +1 -1
  18. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +9 -6
  19. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +11 -12
  20. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +4 -9
  21. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +2 -2
  22. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +10 -9
  23. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +6 -6
  24. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +55 -22
  25. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +12 -1
  26. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +7 -9
  27. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +1 -1
  28. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +13 -6
  29. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +13 -5
  30. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +3 -2
  31. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +14 -6
  32. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +19 -21
  33. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +2 -2
  34. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +5 -2
  35. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +3 -1
  36. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +7 -15
  37. package/compose/src/commonMain/kotlin/vn/momo/kits/const/HostFont.kt +2 -1
  38. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +20 -15
  39. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +1 -2
  40. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/ScreenLayout.kt +306 -0
  41. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +43 -4
  42. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +8 -0
  43. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +10 -0
  44. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +6 -2
  45. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +6 -0
  46. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +197 -60
  47. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +132 -83
  48. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +54 -62
  49. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoBarCompression.kt +97 -0
  50. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoTabBar.kt +377 -0
  51. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +122 -24
  52. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +34 -14
  53. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderJourney.kt +3 -3
  54. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderLocation.kt +1 -2
  55. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +113 -26
  56. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +2 -2
  57. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +1 -2
  58. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HelpCenterRequest.kt +37 -0
  59. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SideBar.kt +192 -0
  60. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +2 -1
  61. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/ToolkitRequests.kt +8 -0
  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 +20 -0
  64. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +13 -0
  65. package/compose/src/commonTest/kotlin/vn/momo/kits/navigation/component/HelpCenterRequestTest.kt +44 -0
  66. package/compose/src/commonTest/kotlin/vn/momo/kits/navigation/component/ToolkitRequestsTest.kt +15 -0
  67. package/compose/src/iosMain/kotlin/vn/momo/kits/const/HostFont.ios.kt +3 -1
  68. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +4 -0
  69. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/ReservedRegions.ios.kt +138 -0
  70. package/gradle/libs.versions.toml +3 -14
  71. package/gradle/wrapper/gradle-wrapper.properties +0 -2
  72. package/gradle.properties +3 -5
  73. package/ios/Application/ApplicationEnvironment.swift +24 -6
  74. package/ios/Application/DesignSystemKits.swift +100 -0
  75. package/ios/Application/DesignSystemKitsPayload.swift +68 -0
  76. package/ios/Application/MaxApi.swift +30 -3
  77. package/ios/Avatar/Avatar.swift +4 -3
  78. package/ios/Badge/Badge.swift +6 -4
  79. package/ios/Badge/BadgeDot.swift +1 -1
  80. package/ios/Badge/BadgeRibbon.swift +8 -2
  81. package/ios/Button/Button.swift +15 -12
  82. package/ios/CalculatorKeyboard/CalculatorKeyboard.swift +64 -18
  83. package/ios/Carousel/Carousel.swift +29 -12
  84. package/ios/Checkbox/Checkbox.swift +7 -3
  85. package/ios/Chip/Chip.swift +5 -4
  86. package/ios/Collapse/Collapse.swift +70 -19
  87. package/ios/Colors+Radius+Spacing/Colors.swift +10 -1
  88. package/ios/Colors+Radius+Spacing/Radius.swift +6 -0
  89. package/ios/DateTimePicker/DateTimePicker.swift +54 -20
  90. package/ios/DateTimePicker/WheelPicker.swift +103 -105
  91. package/ios/Divider/Divider.swift +37 -4
  92. package/ios/Extensions/Color++.swift +38 -9
  93. package/ios/Extensions/OptionalEnvironmentObject.swift +12 -0
  94. package/ios/Extensions/SkeletonUICompat.swift +11 -0
  95. package/ios/Icon/Icon.swift +21 -23
  96. package/ios/Icon/Icons.swift +1329 -0
  97. package/ios/IconButton/IconButton.swift +15 -7
  98. package/ios/Image/Image.swift +7 -23
  99. package/ios/Information/Information.swift +27 -9
  100. package/ios/Input/ErrorView.swift +2 -2
  101. package/ios/Input/Input.swift +171 -148
  102. package/ios/Input/InputPhoneNumber.swift +10 -9
  103. package/ios/Input/InputSearch.swift +107 -78
  104. package/ios/Input/InputTextArea.swift +133 -148
  105. package/ios/InputDropDown/InputDropDown.swift +10 -7
  106. package/ios/InputMoney/InputMoney.swift +32 -11
  107. package/ios/InputOTP/InputOTP.swift +22 -12
  108. package/ios/Layout/Card.swift +50 -0
  109. package/ios/Layout/GridContext.swift +187 -3
  110. package/ios/Layout/Item.swift +5 -4
  111. package/ios/Layout/ScreenLayout.swift +415 -0
  112. package/ios/Layout/Section.swift +4 -100
  113. package/ios/Loader/Loader.swift +5 -9
  114. package/ios/Lottie/LottieView.swift +14 -2
  115. package/ios/{Application/Navigation/Overplay → Navigation}/BottomSheet.swift +169 -29
  116. package/ios/Navigation/BottomTab/BottomTab.swift +332 -0
  117. package/ios/{Application/Navigation → Navigation}/BottomTab/BottomTabBar.swift +18 -7
  118. package/ios/Navigation/BottomTab/DuoBarCompression.swift +104 -0
  119. package/ios/Navigation/BottomTab/DuoTabBar.swift +329 -0
  120. package/ios/{Application → Navigation/Component}/FloatingButton.swift +43 -54
  121. package/ios/Navigation/Component/Header.swift +545 -0
  122. package/ios/Navigation/Component/HeaderBackground.swift +154 -0
  123. package/ios/Navigation/Component/HeaderJourney.swift +73 -0
  124. package/ios/Navigation/Component/HeaderLocation.swift +48 -0
  125. package/ios/Navigation/Component/HeaderRight.swift +425 -0
  126. package/ios/Navigation/Component/HeaderTitle.swift +36 -0
  127. package/ios/{Application/Navigation → Navigation/Component}/HeaderUser.swift +21 -24
  128. package/ios/Navigation/Component/SideBar.swift +183 -0
  129. package/ios/{Application/Navigation/Overplay → Navigation/Component}/SnackBar.swift +8 -8
  130. package/ios/Navigation/HostBackGestureBridge.swift +78 -0
  131. package/ios/{Application/Navigation/Overplay → Navigation}/ModalScreen.swift +9 -2
  132. package/ios/Navigation/Navigation.swift +204 -0
  133. package/ios/Navigation/NavigationContainer.swift +195 -0
  134. package/ios/{Application/Navigation → Navigation}/Navigator.swift +207 -107
  135. package/ios/Navigation/StackScreen.swift +930 -0
  136. package/ios/Navigation/StackTransition.swift +400 -0
  137. package/ios/Navigation/SwipeBackHaptic.swift +33 -0
  138. package/ios/{Application/Navigation → Navigation}/Tracking/ScreenTracker.swift +12 -5
  139. package/ios/OTPKeyboard/OTPKeyboard.swift +8 -11
  140. package/ios/Pagination/PaginationScroll.swift +2 -2
  141. package/ios/Popup/PopupDisplay.swift +72 -135
  142. package/ios/Popup/PopupInput.swift +10 -13
  143. package/ios/Popup/PopupLayout.swift +138 -0
  144. package/ios/Popup/PopupNotify.swift +59 -69
  145. package/ios/Popup/PopupPromotion.swift +31 -47
  146. package/ios/PopupView/FullscreenPopup.swift +13 -19
  147. package/ios/PopupView/PopupView.swift +1 -1
  148. package/ios/PopupView/Utils++.swift +0 -8
  149. package/ios/ProgressInfo/ProgressInfo.swift +4 -2
  150. package/ios/Radio/Radio.swift +2 -1
  151. package/ios/Rating/Rating.swift +9 -1
  152. package/ios/Resources/dot_loading.json +558 -0
  153. package/ios/Resources/lottie_circle_loader.json +1 -0
  154. package/ios/ScrollIndicator/ScrollIndicator.swift +11 -10
  155. package/ios/Skeleton/Skeleton.swift +22 -27
  156. package/ios/Slider/Slider.swift +16 -18
  157. package/ios/Stepper/Stepper.swift +7 -3
  158. package/ios/Steps/Steps.swift +8 -6
  159. package/ios/SuggestAction/SuggestAction.swift +5 -0
  160. package/ios/Swipeable/SwipeCell.swift +10 -6
  161. package/ios/Switch/Switch.swift +22 -20
  162. package/ios/TabView/TabView.swift +102 -21
  163. package/ios/Tag/Tag.swift +15 -4
  164. package/ios/Template/Logo/Logo.swift +2 -1
  165. package/ios/Template/TrustBanner/TrustBanner.swift +109 -55
  166. package/ios/Title/Title.swift +6 -17
  167. package/ios/Tooltip/Tooltip.swift +29 -14
  168. package/ios/Typography/FontScale.swift +17 -14
  169. package/ios/Typography/Text.swift +156 -20
  170. package/ios/Typography/Typography.swift +2 -2
  171. package/ios/Uploader/Uploader.swift +3 -1
  172. package/ios/native-kits.podspec +2 -3
  173. package/ios-demo/MoMoUIKitsDemo.podspec +1 -3
  174. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ContentView.swift +1 -1
  175. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DuoTabDemo.swift +80 -0
  176. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +2 -0
  177. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IPhoneDuoDemo.swift +173 -0
  178. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/NavigationDemo.swift +77 -1
  179. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupDisplayDemo.swift +1 -0
  180. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupNotifyDemo.swift +2 -2
  181. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupPromotionDemo.swift +2 -0
  182. package/package.json +1 -1
  183. package/publish.sh +19 -2
  184. package/scripts/gen-ios-icons.mjs +36 -0
  185. package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +0 -52
  186. package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +0 -183
  187. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +0 -207
  188. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +0 -39
  189. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +0 -86
  190. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +0 -76
  191. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +0 -76
  192. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +0 -293
  193. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +0 -33
  194. package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +0 -110
  195. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +0 -378
  196. package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +0 -69
  197. package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitDesignSystem.kt +0 -18
  198. package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitFontScale.kt +0 -22
  199. package/ios/Application/Components.swift +0 -270
  200. package/ios/Application/ComposeApi.swift +0 -22
  201. package/ios/Application/HeaderRight.swift +0 -306
  202. package/ios/Application/Navigation/BottomTab/BottomTab.swift +0 -104
  203. package/ios/Application/Navigation/HeaderBackProps.swift +0 -66
  204. package/ios/Application/Navigation/HeaderTitle.swift +0 -57
  205. package/ios/Application/Navigation/NavigationContainer.swift +0 -213
  206. package/ios/Application/Navigation/NavigationOptions.swift +0 -109
  207. package/ios/Application/Navigation/NavigatorManager.swift +0 -22
  208. package/ios/Application/Navigation/component/Header.swift +0 -178
  209. package/ios/Application/Navigation/component/HeaderBackground.swift +0 -83
  210. package/ios/Application/Navigation/component/HeaderColor.swift +0 -65
  211. package/ios/Application/Navigation/component/HeaderRightWidthKey.swift +0 -11
  212. package/ios/Application/Navigation/component/NavigationFooter.swift +0 -90
  213. package/ios/Application/Navigation/component/NavigationHeaderRight.swift +0 -196
  214. package/ios/Application/Screen.swift +0 -250
  215. package/ios/Application/StackScreen.swift +0 -492
  216. package/ios/Typography/FontScaleStore.swift +0 -58
  217. package/ios/Typography/KitDesignSystem.swift +0 -17
  218. /package/ios/{Application/Navigation → Navigation}/BottomTab/CurvedContainer.swift +0 -0
@@ -0,0 +1,20 @@
1
+ package vn.momo.kits.platform
2
+
3
+ import androidx.compose.runtime.Composable
4
+ import androidx.compose.runtime.Immutable
5
+ import androidx.compose.ui.unit.Dp
6
+
7
+ @Immutable
8
+ internal data class ReservedRegion(val x: Dp, val y: Dp, val width: Dp, val height: Dp)
9
+
10
+ // occlusion: regions the system occludes (iOS 27.1+), in the coordinates of the Compose root view:
11
+ // the iPhone Duo camera, and the status bar cluster as it grows for a call or a Live Activity.
12
+ // isFoldable: the device reports a `.division` (hinge) region, Apple's foldable check.
13
+ @Immutable
14
+ internal data class ReservedRegions(
15
+ val occlusion: List<ReservedRegion> = emptyList(),
16
+ val isFoldable: Boolean = false,
17
+ )
18
+
19
+ @Composable
20
+ internal expect fun rememberReservedRegions(): ReservedRegions
@@ -1,6 +1,11 @@
1
1
  package vn.momo.kits.utils
2
2
 
3
3
  import androidx.compose.foundation.layout.WindowInsets
4
+ import androidx.compose.foundation.layout.WindowInsetsSides
5
+ import androidx.compose.foundation.layout.displayCutout
6
+ import androidx.compose.foundation.layout.only
7
+ import androidx.compose.foundation.layout.union
8
+ import androidx.compose.foundation.layout.windowInsetsPadding
4
9
  import androidx.compose.foundation.layout.asPaddingValues
5
10
  import androidx.compose.foundation.layout.systemBars
6
11
  import androidx.compose.runtime.Composable
@@ -90,6 +95,14 @@ fun getAppStatusBarHeight(): Dp {
90
95
  }
91
96
  }
92
97
 
98
+ internal val horizontalSafeAreaInsets: WindowInsets
99
+ @Composable get() = WindowInsets.systemBars.union(WindowInsets.displayCutout).only(WindowInsetsSides.Horizontal)
100
+
101
+ // Left/right safe area like SwiftUI (landscape camera/notch); consumed, so nested StackScreens don't pad twice.
102
+ internal fun Modifier.horizontalSafeAreaPadding(): Modifier = composed {
103
+ windowInsetsPadding(horizontalSafeAreaInsets)
104
+ }
105
+
93
106
  @Composable
94
107
  internal fun getNavigationBarHeight(): Dp {
95
108
  return if (AppNavigationBar.current == 0.dp) {
@@ -0,0 +1,44 @@
1
+ // File: HelpCenterRequestTest.kt
2
+ // Created At: 2026-10-19 +07:00
3
+ // Created By: AI | AI Agent: Codex | Model: GPT-6
4
+ // AI-GENERATED START: verify Help Center identity and localized payload preservation
5
+ package vn.momo.kits.navigation.component
6
+
7
+ import kotlinx.serialization.json.JsonObject
8
+ import kotlinx.serialization.json.JsonPrimitive
9
+ import vn.momo.kits.application.MiniAppContext
10
+ import kotlin.test.Test
11
+ import kotlin.test.assertEquals
12
+ import kotlin.test.assertNull
13
+
14
+ class HelpCenterRequestTest {
15
+ @Test
16
+ fun preservesApplicationIdentityAndLocalizedObjects() {
17
+ val context = MiniAppContext(
18
+ appId = "app-id",
19
+ appCode = "app-code",
20
+ appIcon = "app-icon",
21
+ appName = mapOf("vi" to "Tên ứng dụng", "en" to "App name"),
22
+ description = mapOf("vi" to "Mô tả", "en" to "Description"),
23
+ )
24
+ val request = context.helpCenterRequest().context
25
+ assertEquals(context.appId, request.appId)
26
+ assertEquals(context.appCode, request.code)
27
+ assertEquals(context.appIcon, request.icon)
28
+ assertEquals(JsonObject(mapOf("vi" to JsonPrimitive("Tên ứng dụng"),
29
+ "en" to JsonPrimitive("App name"))), request.name)
30
+ assertEquals(JsonObject(mapOf("vi" to JsonPrimitive("Mô tả"),
31
+ "en" to JsonPrimitive("Description"))), request.description)
32
+ assertNull(request.support)
33
+ assertNull(request.toolkitConfig)
34
+ assertNull(request.permissions)
35
+ }
36
+
37
+ @Test
38
+ fun preservesPlainStringsAndAbsentValues() {
39
+ val request = MiniAppContext(appName = "App name").helpCenterRequest().context
40
+ assertEquals(JsonPrimitive("App name"), request.name)
41
+ assertNull(request.description)
42
+ }
43
+ }
44
+ // AI-GENERATED END: verify Help Center identity and localized payload preservation
@@ -38,6 +38,21 @@ class ToolkitRequestsTest {
38
38
  assertEquals(1, results)
39
39
  }
40
40
 
41
+ @Test
42
+ fun sheetEventsKeepTheirSubscriptionUntilTheOwnerCloses() {
43
+ val owner = ToolkitRequests()
44
+ val handle = Handle()
45
+ var callback: ApiCallback<Boolean> = {}
46
+ var results = 0
47
+ owner.observe<Boolean>({ callback = it; handle }) { results++ }
48
+ callback(ApiResult.Success(true))
49
+ callback(ApiResult.Success(false))
50
+ owner.close()
51
+ callback(ApiResult.Success(true))
52
+ assertEquals(2, results)
53
+ assertEquals(1, handle.cancellations)
54
+ }
55
+
41
56
  private class Handle : ApiRequestHandle {
42
57
  override val requestId = "toolkit-test"
43
58
  var cancellations = 0
@@ -5,6 +5,7 @@ package vn.momo.kits.const
5
5
  import androidx.compose.runtime.Composable
6
6
  import androidx.compose.runtime.remember
7
7
  import androidx.compose.ui.text.font.FontFamily
8
+ import androidx.compose.ui.text.font.FontWeight
8
9
  import androidx.compose.ui.text.platform.Font
9
10
  import kotlinx.cinterop.ExperimentalForeignApi
10
11
  import kotlinx.cinterop.addressOf
@@ -15,7 +16,7 @@ import platform.Foundation.dataWithContentsOfFile
15
16
  import platform.posix.memcpy
16
17
 
17
18
  @Composable
18
- internal actual fun hostFontFamily(fileName: String): FontFamily? = remember(fileName) {
19
+ internal actual fun hostFontFamily(fileName: String, weight: FontWeight): FontFamily? = remember(fileName, weight) {
19
20
  val name = fileName.substringBeforeLast('.')
20
21
  val ext = fileName.substringAfterLast('.', "")
21
22
  // Font từ font-source nằm rời ở bundle root, không nằm trong compose-resources.
@@ -26,6 +27,7 @@ internal actual fun hostFontFamily(fileName: String): FontFamily? = remember(fil
26
27
  getData = {
27
28
  NSData.dataWithContentsOfFile(path)?.toByteArray() ?: ByteArray(0)
28
29
  },
30
+ weight = weight,
29
31
  ),
30
32
  )
31
33
  }
@@ -39,11 +39,15 @@ import platform.Foundation.NSBundle
39
39
  import platform.UIKit.UIColor
40
40
  import platform.UIKit.UIDevice
41
41
  import platform.UIKit.UIScreen
42
+ import platform.UIKit.UIUserInterfaceIdiomPhone
42
43
  import vn.momo.kits.navigation.AttachHostBackGesture
43
44
  import vn.momo.kits.navigation.Navigator
44
45
 
45
46
  actual fun getPlatformName(): String = "iOS"
46
47
 
48
+ actual fun isPhoneIdiom(): Boolean =
49
+ UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPhone
50
+
47
51
  @OptIn(ExperimentalForeignApi::class)
48
52
  @Composable
49
53
  actual fun getScreenDimensions(): ScreenDimension = memScoped {
@@ -0,0 +1,138 @@
1
+ package vn.momo.kits.platform
2
+
3
+ import androidx.compose.runtime.Composable
4
+ import androidx.compose.runtime.DisposableEffect
5
+ import androidx.compose.runtime.getValue
6
+ import androidx.compose.runtime.mutableStateOf
7
+ import androidx.compose.runtime.remember
8
+ import androidx.compose.runtime.setValue
9
+ import androidx.compose.ui.uikit.LocalUIViewController
10
+ import androidx.compose.ui.unit.dp
11
+ import kotlinx.cinterop.BetaInteropApi
12
+ import kotlinx.cinterop.CFunction
13
+ import kotlinx.cinterop.COpaquePointer
14
+ import kotlinx.cinterop.ExperimentalForeignApi
15
+ import kotlinx.cinterop.interpretCPointer
16
+ import kotlinx.cinterop.interpretObjCPointer
17
+ import kotlinx.cinterop.interpretObjCPointerOrNull
18
+ import kotlinx.cinterop.invoke
19
+ import kotlinx.cinterop.objcPtr
20
+ import kotlinx.cinterop.reinterpret
21
+ import kotlinx.cinterop.readValue
22
+ import kotlinx.cinterop.useContents
23
+ import platform.CoreGraphics.CGRectZero
24
+ import platform.Foundation.NSArray
25
+ import platform.Foundation.NSSelectorFromString
26
+ import platform.Foundation.NSValue
27
+ import platform.Foundation.valueForKey
28
+ import platform.UIKit.CGRectValue
29
+ import platform.UIKit.UIView
30
+ import platform.UIKit.UIViewAutoresizingFlexibleHeight
31
+ import platform.UIKit.UIViewAutoresizingFlexibleWidth
32
+ import platform.darwin.NSObject
33
+ import platform.Foundation.NSClassFromString
34
+ import platform.posix.RTLD_DEFAULT
35
+ import platform.posix.dlsym
36
+
37
+ // The API is newer than the SDK Kotlin/Native binds, so it is reached through the runtime.
38
+ @OptIn(ExperimentalForeignApi::class)
39
+ private val regionsOfKind = NSSelectorFromString("reservedRegionsOfKind:")
40
+
41
+ @OptIn(ExperimentalForeignApi::class)
42
+ private val regionsOfKindOptions = NSSelectorFromString("reservedRegionsOfKind:options:")
43
+
44
+ // a hinge never goes away, so a positive answer is kept for every later container
45
+ private var isKnownFoldable = false
46
+
47
+ @OptIn(ExperimentalForeignApi::class, BetaInteropApi::class)
48
+ private fun regionKind(name: String): Any? {
49
+ // a class object fails `as? NSObjectProtocol` (K/N checks it as an instance), so it is reinterpreted
50
+ val kindClass = NSClassFromString("UIViewReservedRegionKind")
51
+ ?.let { interpretObjCPointer<NSObject>(it.objcPtr()) } ?: return null
52
+ return kindClass.performSelector(NSSelectorFromString(name))
53
+ }
54
+
55
+ // performSelector cannot pass the NSUInteger options, so objc_msgSend is called with the method's own signature.
56
+ @OptIn(ExperimentalForeignApi::class)
57
+ private val msgSendRegionsOfKindOptions = dlsym(RTLD_DEFAULT, "objc_msgSend")
58
+ ?.reinterpret<CFunction<(COpaquePointer?, COpaquePointer?, COpaquePointer?, ULong) -> COpaquePointer?>>()
59
+
60
+ // Apple's foldable check: only a device with a hinge reports a `.division` region, inactive while
61
+ // flat (UIViewReservedRegionQueryOptionsIncludeInactive) but still there. Screen size cannot tell it
62
+ // apart from a large iPhone in landscape or a narrowed window.
63
+ @OptIn(ExperimentalForeignApi::class)
64
+ private fun UIView.hasDivisionRegion(): Boolean {
65
+ if (window == null || !respondsToSelector(regionsOfKindOptions)) return false
66
+ val msgSend = msgSendRegionsOfKindOptions ?: return false
67
+ val kind = regionKind("divisionRegionKind") as? NSObject ?: return false
68
+ val result = msgSend(
69
+ interpretCPointer(objcPtr()),
70
+ regionsOfKindOptions,
71
+ interpretCPointer(kind.objcPtr()),
72
+ 1uL,
73
+ )
74
+ val regions = interpretObjCPointerOrNull<NSArray>(result?.rawValue ?: return false)
75
+ return (regions?.count ?: 0uL) > 0uL
76
+ }
77
+
78
+ @OptIn(ExperimentalForeignApi::class)
79
+ private fun UIView.occlusionRegions(target: UIView): List<ReservedRegion> {
80
+ if (window == null || !respondsToSelector(regionsOfKind)) return emptyList()
81
+ val kind = regionKind("occlusionRegionKind") ?: return emptyList()
82
+ val regions = when (val result = performSelector(regionsOfKind, withObject = kind)) {
83
+ is List<*> -> result
84
+ is NSArray -> List(result.count.toInt()) { result.objectAtIndex(it.toULong()) }
85
+ else -> return emptyList()
86
+ }
87
+ return regions.mapNotNull { region ->
88
+ val frame = ((region as? NSObject)?.valueForKey("frame") as? NSValue)?.CGRectValue ?: return@mapNotNull null
89
+ convertRect(frame, toView = target).useContents {
90
+ ReservedRegion(origin.x.dp, origin.y.dp, size.width.dp, size.height.dp)
91
+ }
92
+ }
93
+ }
94
+
95
+ // Invisible full-size probe: reading the regions in layoutSubviews makes UIKit run it again
96
+ // when they change, so nothing polls.
97
+ @OptIn(ExperimentalForeignApi::class)
98
+ private class ReservedRegionsProbe(
99
+ private val onChange: (ReservedRegions) -> Unit,
100
+ ) : UIView(frame = CGRectZero.readValue()) {
101
+ private var last: ReservedRegions? = null
102
+
103
+ init {
104
+ userInteractionEnabled = false
105
+ autoresizingMask = UIViewAutoresizingFlexibleWidth or UIViewAutoresizingFlexibleHeight
106
+ }
107
+
108
+ override fun layoutSubviews() {
109
+ super.layoutSubviews()
110
+ // re-read until found: the window may not report the hinge on the first pass
111
+ if (!isKnownFoldable) isKnownFoldable = hasDivisionRegion()
112
+ val regions = ReservedRegions(superview?.let { occlusionRegions(it) } ?: emptyList(), isKnownFoldable)
113
+ if (regions != last) {
114
+ last = regions
115
+ onChange(regions)
116
+ }
117
+ }
118
+
119
+ override fun didMoveToWindow() {
120
+ super.didMoveToWindow()
121
+ setNeedsLayout()
122
+ }
123
+ }
124
+
125
+ @OptIn(ExperimentalForeignApi::class)
126
+ @Composable
127
+ internal actual fun rememberReservedRegions(): ReservedRegions {
128
+ if (!isPhoneIdiom()) return ReservedRegions()
129
+ val host = LocalUIViewController.current.view
130
+ var regions by remember { mutableStateOf(ReservedRegions(isFoldable = isKnownFoldable)) }
131
+ DisposableEffect(host) {
132
+ val probe = ReservedRegionsProbe { regions = it }
133
+ probe.setFrame(host.bounds)
134
+ host.insertSubview(probe, atIndex = 0)
135
+ onDispose { probe.removeFromSuperview() }
136
+ }
137
+ return regions
138
+ }
@@ -1,7 +1,5 @@
1
1
  [versions]
2
- # AI-GENERATED START: align Kits and its samples with the approved SDK 36 toolchain
3
- android-compileSdk = "36"
4
- # AI-GENERATED END: align Kits and its samples with the approved SDK 36 toolchain
2
+ android-compileSdk = "37"
5
3
  android-minSdk = "24"
6
4
  android-targetSdk = "36"
7
5
  ktor-version = "3.0.0-rc-1"
@@ -11,9 +9,7 @@ kSerialize = "1.7.1"
11
9
  compose = "1.10.3"
12
10
  navigation-multiplatform = "2.9.2"
13
11
  coil3-multiplatform = "3.0.0-alpha10"
14
- # AI-GENERATED START: align the Android toolchain with the integrating app
15
12
  androidGradlePlugin = "9.2.1"
16
- # AI-GENERATED END: align the Android toolchain with the integrating app
17
13
  r8 = "8.9.35"
18
14
  kotlinx-datetime = "0.7.1"
19
15
  airbnb-lottie = "5.2.0"
@@ -21,12 +17,9 @@ compottie = "2.1.0"
21
17
  androidx-activity = "1.9.1"
22
18
  androidx-appcompat = "1.7.0"
23
19
  material = "1.10.0"
24
- maxapi = "0.1.1"
25
20
  vanniktechMavenPublish = "0.34.0"
26
- # AI-GENERATED START: identify the SDK 36 compatible Kits release
27
- kits = "0.300.3"
28
- # AI-GENERATED END: identify the SDK 36 compatible Kits release
29
- nativemaxapi = "0.3.1"
21
+ kits = "0.167.1-duo.1"
22
+ nativemaxapi = "0.3.9"
30
23
  gif-image = "0.1.2"
31
24
 
32
25
  [libraries]
@@ -46,14 +39,12 @@ coil-multiplatform-compose = { module = "io.coil-kt.coil3:coil-compose", version
46
39
  coil-multiplatform-network-ktor = { module = "io.coil-kt.coil3:coil-network-ktor3", version.ref = "coil3-multiplatform" }
47
40
  r8 = { module = "com.android.tools:r8", version.ref = "r8" }
48
41
  airbnb-lottie = { group = "com.airbnb.android", name = "lottie-compose", version.ref = "airbnb-lottie" }
49
- # AI-GENERATED START
50
42
  # Depend on compottie-core (not the `compottie` umbrella). The umbrella drags in the
51
43
  # separate `keight` umbrella artifact, which collides with `keight-core` on the
52
44
  # `findRoot__at__ScriptRuntime` symbol (duplicate symbol at the iOS `ld` link of
53
45
  # shared.framework). compottie-core pulls only `keight-core` (which declares
54
46
  # keight.js.JsAny, so the earlier unlinked-symbol error stays fixed too).
55
47
  compottie = { module = "io.github.alexzhirkevich:compottie-core", version.ref = "compottie" }
56
- # AI-GENERATED END
57
48
  native-max-api = { group = "vn.momo.maxapi", name = "NativeMaxApi", version.ref = "nativemaxapi" }
58
49
  kits = { module = "vn.momo.kits:kits", version.ref = "kits" }
59
50
  androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" }
@@ -74,6 +65,4 @@ android-library = { id = "com.android.library", version.ref = "androidGradlePlug
74
65
  android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }
75
66
  vanniktech-mavenPublish = { id = "com.vanniktech.maven.publish", version.ref = "vanniktechMavenPublish" }
76
67
 
77
- # AI-GENERATED START: use the AGP Android target for Kotlin Multiplatform libraries
78
68
  android-kotlin-multiplatform-library = { id = "com.android.kotlin.multiplatform.library", version.ref = "androidGradlePlugin" }
79
- # AI-GENERATED END: use the AGP Android target for Kotlin Multiplatform libraries
@@ -1,9 +1,7 @@
1
1
  #Thu Jul 31 17:05:40 ICT 2025
2
2
  distributionBase=GRADLE_USER_HOME
3
3
  distributionPath=wrapper/dists
4
- # AI-GENERATED START: align the Gradle wrapper with the integrating app
5
4
  distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip
6
- # AI-GENERATED END: align the Gradle wrapper with the integrating app
7
5
  networkTimeout=10000
8
6
  validateDistributionUrl=true
9
7
  zipStoreBase=GRADLE_USER_HOME
package/gradle.properties CHANGED
@@ -18,11 +18,9 @@ kotlin.apple.xcodeCompatibility.nowarn=true
18
18
  name="ComposeKits"
19
19
  group=vn.momo.kits
20
20
  artifact.id=kits
21
- # AI-GENERATED START: publish the SDK 36 compatible Kits under a new version
22
- version=0.300.3
23
- # AI-GENERATED END: publish the SDK 36 compatible Kits under a new version
21
+ version=0.300.4
24
22
 
25
23
  repo=GitLab
26
24
  url=https://gitlab.mservice.com.vn/api/v4/projects/5400/packages/maven
27
- gitlab.user=upload_packages
28
- gitlab.password=gldt-CtTSGmzz57sUL9cnjdaQ
25
+ gitlab.user=
26
+ gitlab.password=
@@ -17,8 +17,8 @@ public class MiniAppContext {
17
17
  public var toolkitConfig: [String: Any] = [:]
18
18
  public var providerId: String = "momo"
19
19
  public var permissions: [[String: Any]] = []
20
- // Per-mini-app font-scale override; nil falls back to FontScaleStore. Mirrors Compose's
21
- // MiniAppContext.userScaleRate / useOSScaleRate, which win per field over KitFontScale.
20
+ // Per-mini-app font-scale override; nil falls back to DesignSystemKits. Mirrors Compose's
21
+ // MiniAppContext.userScaleRate / useOSScaleRate, which win per field over DesignSystemKits.
22
22
  public var userScaleRate: CGFloat? = nil
23
23
  public var useOSScaleRate: Bool? = nil
24
24
 
@@ -37,10 +37,17 @@ public class MiniAppContext {
37
37
  }
38
38
  }
39
39
 
40
- public class KitConfig {
41
- public var trustBanner: TrustBannerData? = nil
42
- public var headerBar: String? = nil
43
- public var headerGradient: String? = nil
40
+ // Value type so DesignSystemKitsConfigs can dedupe host pushes; mirrors Compose's DesignSystemConfig.
41
+ public struct KitConfig: Equatable {
42
+ public var trustBanner: TrustBannerData?
43
+ public var headerBar: String?
44
+ public var headerGradient: String?
45
+
46
+ public init(trustBanner: TrustBannerData? = nil, headerBar: String? = nil, headerGradient: String? = nil) {
47
+ self.trustBanner = trustBanner
48
+ self.headerBar = headerBar
49
+ self.headerGradient = headerGradient
50
+ }
44
51
  }
45
52
 
46
53
  public class ApplicationEnvironment: ObservableObject {
@@ -59,6 +66,17 @@ public class ApplicationEnvironment: ObservableObject {
59
66
  }
60
67
  }
61
68
 
69
+ extension ApplicationEnvironment {
70
+ /// Closes the mini app: through `composeApi` as before, else `maxApi` (Compose `maxApi.dismiss`).
71
+ func requestDismiss() {
72
+ if let composeApi {
73
+ composeApi.requestCallback(funcName: "dismiss", params: nil, onResponse: nil)
74
+ } else {
75
+ maxApi?.dismiss(completion: nil)
76
+ }
77
+ }
78
+ }
79
+
62
80
  private struct ApplicationEnvironmentKey: EnvironmentKey {
63
81
  static let defaultValue = ApplicationEnvironment()
64
82
  }
@@ -0,0 +1,100 @@
1
+ // File: ios/Application/DesignSystemKits.swift
2
+ // Created At: 2026-10-02 14:17:29 +07:00
3
+ // Created By: AI
4
+ // AI Agent: Claude Code
5
+ // Model: claude-opus-5-5
6
+
7
+ // AI-GENERATED START: app-wide design-system holder mirroring Compose's DesignSystemKits
8
+ import SwiftUI
9
+
10
+ /// Host-owned design-system inputs; mirrors Compose's `DesignSystemKitsConfigs` field for field.
11
+ public struct DesignSystemKitsConfigs: Equatable {
12
+ public var userScaleRate: CGFloat
13
+ public var useOSScaleRate: Bool
14
+ public var isWhiteList: Bool
15
+ public var designSystemConfig: KitConfig?
16
+
17
+ public init(
18
+ userScaleRate: CGFloat = 1,
19
+ useOSScaleRate: Bool = true,
20
+ isWhiteList: Bool = false,
21
+ designSystemConfig: KitConfig? = nil
22
+ ) {
23
+ self.userScaleRate = userScaleRate
24
+ self.useOSScaleRate = useOSScaleRate
25
+ self.isWhiteList = isWhiteList
26
+ self.designSystemConfig = designSystemConfig
27
+ }
28
+ }
29
+
30
+ /// App-wide holder for the SwiftUI kit, mirroring Compose's `DesignSystemKits`. The kit is sandboxed from the
31
+ /// host, so the host pushes `setConfigs(_:)` at startup and on every change; the OS Dynamic Type factor is
32
+ /// tracked here. Views re-render on change as long as the subtree got `.momoFontScale()` (`NavigationContainer`
33
+ /// applies it). Swift reserves `init`, so there is no `init(source:)` twin: the host observes and pushes.
34
+ public final class DesignSystemKits: ObservableObject {
35
+ public static let shared = DesignSystemKits()
36
+
37
+ @Published private var configs = DesignSystemKitsConfigs()
38
+ @Published public private(set) var osFontScale: CGFloat = DesignSystemKits.currentOsFontScale()
39
+
40
+ private init() {
41
+ NotificationCenter.default.addObserver(
42
+ self,
43
+ selector: #selector(contentSizeCategoryDidChange),
44
+ name: UIContentSizeCategory.didChangeNotification,
45
+ object: nil
46
+ )
47
+ }
48
+
49
+ public func setConfigs(_ configs: DesignSystemKitsConfigs) {
50
+ onMain {
51
+ // Re-assigning an unchanged value would still publish and invalidate every kit subtree.
52
+ guard self.configs != configs else { return }
53
+ self.configs = configs
54
+ }
55
+ }
56
+
57
+ public func getConfigs() -> DesignSystemKitsConfigs {
58
+ configs
59
+ }
60
+
61
+ public var fontScale: FontScale {
62
+ FontScale(
63
+ userScaleRate: configs.userScaleRate,
64
+ useOSScaleRate: configs.useOSScaleRate,
65
+ osFontScale: osFontScale
66
+ )
67
+ }
68
+
69
+ @objc private func contentSizeCategoryDidChange() {
70
+ let next = Self.currentOsFontScale()
71
+ guard next != osFontScale else { return }
72
+ osFontScale = next
73
+ }
74
+
75
+ /// Same factor Compose's iOS `ProvideOsFontScale` derives from
76
+ /// `UIFont.preferredFont(forTextStyle: .body).pointSize / 17`, so both iOS surfaces scale alike.
77
+ static func currentOsFontScale() -> CGFloat {
78
+ let base = UIFont.systemFont(ofSize: UIFont.labelFontSize)
79
+ return UIFontMetrics.default.scaledFont(for: base).pointSize / base.pointSize
80
+ }
81
+
82
+ /// Synchronous when already on main — an async hop would defer the change by a frame.
83
+ private func onMain(_ work: @escaping () -> Void) {
84
+ if Thread.isMainThread { work() } else { DispatchQueue.main.async(execute: work) }
85
+ }
86
+ }
87
+
88
+ private struct DesignSystemKitsConfigsKey: EnvironmentKey {
89
+ /// Computed, never stored: an un-injected subtree still reads the live holder, just not reactively.
90
+ static var defaultValue: DesignSystemKitsConfigs { DesignSystemKits.shared.getConfigs() }
91
+ }
92
+
93
+ public extension EnvironmentValues {
94
+ /// Injected by `.momoFontScale()`; never set it directly (same trap as `\.momoFontScale`).
95
+ var designSystemKitsConfigs: DesignSystemKitsConfigs {
96
+ get { self[DesignSystemKitsConfigsKey.self] }
97
+ set { self[DesignSystemKitsConfigsKey.self] = newValue }
98
+ }
99
+ }
100
+ // AI-GENERATED END: app-wide design-system holder mirroring Compose's DesignSystemKits
@@ -0,0 +1,68 @@
1
+ // File: ios/Application/DesignSystemKitsPayload.swift
2
+ // Created At: 2026-10-02 15:40:00 +07:00
3
+ // Created By: AI
4
+ // AI Agent: Claude Code
5
+ // Model: claude-opus-5-5
6
+
7
+ // AI-GENERATED START: JSON entry point for hosts that can't share Swift types with the kit
8
+ import Foundation
9
+
10
+ public extension DesignSystemKits {
11
+ /// Applies configs pushed as JSON by a host that can't share Swift types with the kit (e.g. the KMP bridge).
12
+ /// Invalid JSON is ignored and the current configs are kept.
13
+ func setConfigs(json: String) {
14
+ guard let data = json.data(using: .utf8),
15
+ let payload = try? JSONDecoder().decode(DesignSystemKitsPayload.self, from: data) else { return }
16
+ setConfigs(payload.configs)
17
+ }
18
+ }
19
+
20
+ // Wire format; field names match the host's serializer (momo-app HostDesignSystemKits / DesignSystemData.kt).
21
+ private struct DesignSystemKitsPayload: Decodable {
22
+ let userScaleRate: CGFloat
23
+ let useOSScaleRate: Bool
24
+ let isWhiteList: Bool
25
+ let designSystemConfig: KitConfigPayload?
26
+
27
+ var configs: DesignSystemKitsConfigs {
28
+ DesignSystemKitsConfigs(
29
+ userScaleRate: userScaleRate,
30
+ useOSScaleRate: useOSScaleRate,
31
+ isWhiteList: isWhiteList,
32
+ designSystemConfig: designSystemConfig?.kitConfig
33
+ )
34
+ }
35
+ }
36
+
37
+ private struct KitConfigPayload: Decodable {
38
+ let trustBanner: TrustBannerPayload?
39
+ let headerBar: String?
40
+ let headerGradient: String?
41
+
42
+ var kitConfig: KitConfig {
43
+ KitConfig(trustBanner: trustBanner?.trustBannerData, headerBar: headerBar, headerGradient: headerGradient)
44
+ }
45
+ }
46
+
47
+ private struct TrustBannerPayload: Decodable {
48
+ let content: [String: String]
49
+ let subContent: [String: String]
50
+ let pciImage: String?
51
+ let sslImage: String?
52
+ let urlConfig: String?
53
+ let icons: [String]?
54
+ let momoImage: String?
55
+
56
+ var trustBannerData: TrustBannerData {
57
+ TrustBannerData(
58
+ content: content,
59
+ subContent: subContent,
60
+ pciImage: pciImage,
61
+ momoImage: momoImage,
62
+ sslImage: sslImage,
63
+ icons: icons,
64
+ urlConfig: urlConfig ?? ""
65
+ )
66
+ }
67
+ }
68
+ // AI-GENERATED END: JSON entry point for hosts that can't share Swift types with the kit
@@ -5,14 +5,41 @@
5
5
  // Swift mirror of the host-app bridge exposed on Kotlin as `vn.momo.maxapi.IMaxApi`
6
6
  // (`LocalMaxApi`). `IMaxApi` itself is an external compiled dependency with no
7
7
  // source in this repo, so this protocol intentionally exposes only the methods
8
- // this kit's Kotlin source actually calls (`track`, `dismiss`, `getLanguage`) —
9
- // not a full guess at IMaxApi's real surface.
8
+ // this kit's Kotlin source actually calls — not a full guess at IMaxApi's real surface.
9
+ // Callbacks carry IMaxApi's `["response": …]` payload.
10
10
  //
11
11
 
12
- import Foundation
12
+ import UIKit
13
13
 
14
14
  public protocol MaxApi {
15
15
  func track(eventName: String, params: [String: Any?])
16
16
  func dismiss(completion: (() -> Void)?)
17
17
  func getLanguage(callback: @escaping ([String: Any?]?) -> Void)
18
+ /// `type` is the IMaxApi vibration name (`"light"`, …).
19
+ func triggerEventVibration(_ type: String)
20
+ // Toolkit (Compose `HeaderRight.Toolkit`).
21
+ func isFavoriteApp(_ params: [String: Any?], callback: @escaping ([String: Any?]?) -> Void)
22
+ func onToolAction(_ params: [String: Any?], callback: @escaping ([String: Any?]?) -> Void)
23
+ func showTools(_ params: [String: Any?], callback: @escaping ([String: Any?]?) -> Void)
24
+ func showHelpCenter(_ params: [String: Any?], callback: @escaping ([String: Any?]?) -> Void)
25
+ /// Back to the host home, closing every mini app.
26
+ func dismissAll(completion: (() -> Void)?)
27
+ }
28
+
29
+ public extension MaxApi {
30
+ /// Hosts that don't bridge haptics still get the system light tick.
31
+ func triggerEventVibration(_ type: String) {
32
+ UIImpactFeedbackGenerator(style: .light).impactOccurred()
33
+ }
34
+
35
+ // Hosts that don't bridge the toolkit keep it inert, as before these joined the protocol.
36
+ func isFavoriteApp(_ params: [String: Any?], callback: @escaping ([String: Any?]?) -> Void) {}
37
+ func onToolAction(_ params: [String: Any?], callback: @escaping ([String: Any?]?) -> Void) {}
38
+ func showTools(_ params: [String: Any?], callback: @escaping ([String: Any?]?) -> Void) {}
39
+ func showHelpCenter(_ params: [String: Any?], callback: @escaping ([String: Any?]?) -> Void) {}
40
+
41
+ /// Without a home to return to, closing this mini app is the closest thing.
42
+ func dismissAll(completion: (() -> Void)?) {
43
+ dismiss(completion: completion)
44
+ }
18
45
  }