@momo-kits/native-kits 0.167.1-duo.2-debug → 0.167.1-duo.20-debug

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. package/CLAUDE.md +36 -8
  2. package/README.md +2 -2
  3. package/compose/build.gradle.kts +15 -1
  4. package/compose/build.gradle.kts.backup +14 -0
  5. package/compose/compose.podspec +1 -1
  6. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +2 -0
  7. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/ReservedRegions.android.kt +6 -0
  8. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +168 -52
  9. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +20 -21
  10. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +2 -1
  11. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +4 -2
  12. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +1 -1
  13. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +24 -14
  14. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +1 -1
  15. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +1 -1
  16. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +9 -6
  17. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +11 -12
  18. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +4 -9
  19. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +2 -2
  20. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +10 -9
  21. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +6 -6
  22. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +53 -26
  23. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +12 -2
  24. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +7 -9
  25. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +1 -1
  26. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +13 -6
  27. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +13 -5
  28. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +3 -2
  29. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +14 -6
  30. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +19 -21
  31. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +2 -2
  32. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +5 -2
  33. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +3 -1
  34. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +7 -15
  35. package/compose/src/commonMain/kotlin/vn/momo/kits/const/HostFont.kt +4 -0
  36. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +10 -4
  37. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +3 -17
  38. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/ScreenLayout.kt +173 -21
  39. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +43 -4
  40. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +8 -0
  41. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +10 -0
  42. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +153 -48
  43. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +114 -71
  44. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +8 -9
  45. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoBarCompression.kt +98 -0
  46. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoTabBar.kt +312 -0
  47. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +84 -18
  48. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +33 -13
  49. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderJourney.kt +2 -1
  50. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +51 -19
  51. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +4 -2
  52. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +1 -1
  53. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SideBar.kt +46 -23
  54. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +2 -1
  55. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +3 -0
  56. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ReservedRegions.kt +13 -0
  57. package/compose/src/iosMain/kotlin/vn/momo/kits/const/HostFont.ios.kt +38 -0
  58. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +4 -0
  59. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/ReservedRegions.ios.kt +94 -0
  60. package/gradle.properties +1 -1
  61. package/ios/Application/ApplicationEnvironment.swift +17 -0
  62. package/ios/Application/MaxApi.swift +12 -3
  63. package/ios/Avatar/Avatar.swift +4 -3
  64. package/ios/Badge/Badge.swift +6 -4
  65. package/ios/Badge/BadgeDot.swift +1 -1
  66. package/ios/Badge/BadgeRibbon.swift +0 -1
  67. package/ios/Button/Button.swift +15 -12
  68. package/ios/CalculatorKeyboard/CalculatorKeyboard.swift +64 -18
  69. package/ios/Carousel/Carousel.swift +29 -12
  70. package/ios/Checkbox/Checkbox.swift +7 -3
  71. package/ios/Chip/Chip.swift +5 -4
  72. package/ios/Collapse/Collapse.swift +70 -19
  73. package/ios/Colors+Radius+Spacing/Colors.swift +10 -1
  74. package/ios/Colors+Radius+Spacing/Radius.swift +6 -0
  75. package/ios/DateTimePicker/DateTimePicker.swift +54 -20
  76. package/ios/DateTimePicker/WheelPicker.swift +103 -105
  77. package/ios/Divider/Divider.swift +37 -4
  78. package/ios/Extensions/Color++.swift +38 -9
  79. package/ios/Extensions/OptionalEnvironmentObject.swift +12 -0
  80. package/ios/Extensions/SkeletonUICompat.swift +11 -0
  81. package/ios/Icon/Icon.swift +21 -23
  82. package/ios/Icon/Icons.swift +1329 -0
  83. package/ios/IconButton/IconButton.swift +15 -7
  84. package/ios/Image/Image.swift +7 -23
  85. package/ios/Information/Information.swift +27 -9
  86. package/ios/Input/ErrorView.swift +2 -2
  87. package/ios/Input/Input.swift +160 -147
  88. package/ios/Input/InputPhoneNumber.swift +12 -9
  89. package/ios/Input/InputSearch.swift +102 -75
  90. package/ios/Input/InputTextArea.swift +128 -145
  91. package/ios/InputDropDown/InputDropDown.swift +10 -7
  92. package/ios/InputMoney/InputMoney.swift +33 -11
  93. package/ios/InputOTP/InputOTP.swift +22 -12
  94. package/ios/Layout/Card.swift +50 -0
  95. package/ios/Layout/GridContext.swift +187 -3
  96. package/ios/Layout/Item.swift +5 -4
  97. package/ios/Layout/ScreenLayout.swift +380 -0
  98. package/ios/Layout/Section.swift +4 -100
  99. package/ios/Loader/Loader.swift +5 -9
  100. package/ios/Lottie/LottieView.swift +14 -2
  101. package/ios/{Application/Navigation/Overplay → Navigation}/BottomSheet.swift +128 -9
  102. package/ios/Navigation/BottomTab/BottomTab.swift +185 -0
  103. package/ios/{Application/Navigation → Navigation}/BottomTab/BottomTabBar.swift +15 -7
  104. package/ios/Navigation/BottomTab/DuoBarCompression.swift +105 -0
  105. package/ios/Navigation/BottomTab/DuoTabBar.swift +304 -0
  106. package/ios/{Application → Navigation/Component}/FloatingButton.swift +14 -3
  107. package/ios/Navigation/Component/Header.swift +531 -0
  108. package/ios/Navigation/Component/HeaderBackground.swift +133 -0
  109. package/ios/Navigation/Component/HeaderJourney.swift +73 -0
  110. package/ios/Navigation/Component/HeaderLocation.swift +48 -0
  111. package/ios/Navigation/Component/HeaderRight.swift +377 -0
  112. package/ios/Navigation/Component/HeaderTitle.swift +36 -0
  113. package/ios/{Application/Navigation → Navigation/Component}/HeaderUser.swift +21 -24
  114. package/ios/Navigation/Component/SideBar.swift +183 -0
  115. package/ios/{Application/Navigation/Overplay → Navigation/Component}/SnackBar.swift +5 -6
  116. package/ios/Navigation/HostBackGestureBridge.swift +78 -0
  117. package/ios/{Application/Navigation/Overplay → Navigation}/ModalScreen.swift +5 -2
  118. package/ios/Navigation/Navigation.swift +204 -0
  119. package/ios/Navigation/NavigationContainer.swift +194 -0
  120. package/ios/{Application/Navigation → Navigation}/Navigator.swift +177 -91
  121. package/ios/Navigation/StackScreen.swift +820 -0
  122. package/ios/Navigation/StackTransition.swift +400 -0
  123. package/ios/Navigation/SwipeBackHaptic.swift +33 -0
  124. package/ios/{Application/Navigation → Navigation}/Tracking/ScreenTracker.swift +12 -5
  125. package/ios/OTPKeyboard/OTPKeyboard.swift +8 -11
  126. package/ios/Pagination/PaginationScroll.swift +2 -2
  127. package/ios/Popup/PopupDisplay.swift +72 -136
  128. package/ios/Popup/PopupInput.swift +10 -13
  129. package/ios/Popup/PopupLayout.swift +140 -0
  130. package/ios/Popup/PopupNotify.swift +56 -73
  131. package/ios/Popup/PopupPromotion.swift +31 -47
  132. package/ios/PopupView/FullscreenPopup.swift +13 -19
  133. package/ios/PopupView/PopupView.swift +1 -1
  134. package/ios/PopupView/Utils++.swift +0 -8
  135. package/ios/ProgressInfo/ProgressInfo.swift +4 -2
  136. package/ios/Radio/Radio.swift +2 -1
  137. package/ios/Rating/Rating.swift +9 -1
  138. package/ios/Resources/dot_loading.json +558 -0
  139. package/ios/Resources/lottie_circle_loader.json +1 -0
  140. package/ios/ScrollIndicator/ScrollIndicator.swift +11 -10
  141. package/ios/Skeleton/Skeleton.swift +22 -27
  142. package/ios/Slider/Slider.swift +16 -18
  143. package/ios/Stepper/Stepper.swift +6 -1
  144. package/ios/Steps/Steps.swift +8 -6
  145. package/ios/SuggestAction/SuggestAction.swift +5 -0
  146. package/ios/Swipeable/SwipeCell.swift +10 -6
  147. package/ios/Switch/Switch.swift +22 -20
  148. package/ios/TabView/TabView.swift +102 -21
  149. package/ios/Tag/Tag.swift +15 -4
  150. package/ios/Template/Logo/Logo.swift +2 -1
  151. package/ios/Template/TrustBanner/TrustBanner.swift +106 -54
  152. package/ios/Title/Title.swift +6 -17
  153. package/ios/Tooltip/Tooltip.swift +29 -14
  154. package/ios/Typography/Text.swift +61 -19
  155. package/ios/Uploader/Uploader.swift +3 -1
  156. package/ios/native-kits.podspec +2 -1
  157. package/ios-demo/MoMoUIKitsDemo.podspec +1 -1
  158. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ContentView.swift +1 -1
  159. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DuoTabDemo.swift +80 -0
  160. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +1 -0
  161. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IPhoneDuoDemo.swift +173 -0
  162. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/NavigationDemo.swift +77 -1
  163. package/package.json +1 -1
  164. package/scripts/gen-ios-icons.mjs +36 -0
  165. package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +0 -52
  166. package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +0 -183
  167. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +0 -207
  168. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +0 -39
  169. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +0 -86
  170. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +0 -76
  171. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +0 -76
  172. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +0 -293
  173. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +0 -33
  174. package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +0 -110
  175. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +0 -378
  176. package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +0 -69
  177. package/ios/Application/Components.swift +0 -270
  178. package/ios/Application/ComposeApi.swift +0 -22
  179. package/ios/Application/HeaderRight.swift +0 -306
  180. package/ios/Application/Navigation/BottomTab/BottomTab.swift +0 -104
  181. package/ios/Application/Navigation/HeaderBackProps.swift +0 -66
  182. package/ios/Application/Navigation/HeaderTitle.swift +0 -57
  183. package/ios/Application/Navigation/NavigationContainer.swift +0 -213
  184. package/ios/Application/Navigation/NavigationOptions.swift +0 -109
  185. package/ios/Application/Navigation/NavigatorManager.swift +0 -22
  186. package/ios/Application/Navigation/component/Header.swift +0 -178
  187. package/ios/Application/Navigation/component/HeaderBackground.swift +0 -83
  188. package/ios/Application/Navigation/component/HeaderColor.swift +0 -65
  189. package/ios/Application/Navigation/component/HeaderRightWidthKey.swift +0 -11
  190. package/ios/Application/Navigation/component/NavigationFooter.swift +0 -90
  191. package/ios/Application/Navigation/component/NavigationHeaderRight.swift +0 -196
  192. package/ios/Application/Screen.swift +0 -250
  193. package/ios/Application/StackScreen.swift +0 -492
  194. /package/ios/{Application/Navigation → Navigation}/BottomTab/CurvedContainer.swift +0 -0
@@ -3,13 +3,15 @@ package vn.momo.kits.navigation.component
3
3
  import androidx.compose.animation.core.animateFloatAsState
4
4
  import androidx.compose.foundation.background
5
5
  import androidx.compose.foundation.layout.Box
6
+ import androidx.compose.foundation.layout.fillMaxHeight
6
7
  import androidx.compose.foundation.layout.fillMaxSize
7
8
  import androidx.compose.foundation.layout.fillMaxWidth
8
9
  import androidx.compose.foundation.layout.height
9
10
  import androidx.compose.runtime.Composable
10
11
  import androidx.compose.runtime.getValue
11
12
  import androidx.compose.ui.Modifier
12
- import androidx.compose.ui.geometry.Offset
13
+ import androidx.compose.ui.draw.clipToBounds
14
+ import androidx.compose.ui.graphics.graphicsLayer
13
15
  import androidx.compose.ui.graphics.Brush
14
16
  import androidx.compose.ui.graphics.Color
15
17
  import androidx.compose.ui.platform.LocalDensity
@@ -20,6 +22,8 @@ import vn.momo.kits.layout.getScreenLayout
20
22
  import vn.momo.kits.modifier.conditional
21
23
  import vn.momo.kits.navigation.LocalOptions
22
24
  import vn.momo.kits.navigation.LocalScrollState
25
+ import vn.momo.kits.navigation.getInputSearchType
26
+ import vn.momo.kits.navigation.usesDuoGlassHeader
23
27
 
24
28
  enum class AnimatedHeaderRatio(val value: Float){
25
29
  RATIO_16_9(16f / 9f),
@@ -29,6 +33,9 @@ enum class AnimatedHeaderRatio(val value: Float){
29
33
 
30
34
  internal val HEADER_BACKGROUND_HEIGHT = 154.dp
31
35
 
36
+ // RN DuoScrollGradient: extended gradient runs this far below the header
37
+ private val DUO_GRADIENT_EXTRA = 52.dp
38
+
32
39
  @Suppress("FrequentlyChangingValue")
33
40
  @Composable
34
41
  internal fun HeaderBackground() {
@@ -38,7 +45,8 @@ internal fun HeaderBackground() {
38
45
  val scrollState = LocalScrollState.current
39
46
  val backgroundColor = options.backgroundColor ?: AppTheme.current.colors.background.default
40
47
 
41
- val minHeight = getScreenLayout().headerHeight
48
+ val layout = getScreenLayout()
49
+ val minHeight = layout.headerHeight
42
50
  val maxHeight = HEADER_BACKGROUND_HEIGHT
43
51
  val opacity by animateFloatAsState(
44
52
  targetValue = (1 - (scrollState.value * 1f / HEADER_HEIGHT * 1f)).coerceIn(0f, 1f),
@@ -57,20 +65,32 @@ internal fun HeaderBackground() {
57
65
  else -> 0.dp
58
66
  }
59
67
 
60
- Box(Modifier.fillMaxWidth().height(height).background(backgroundColor)) {
68
+ // iPhone Duo glass header: the gradient leaves with the content, as the quick return header does
69
+ val useDuoGlass = options.usesDuoGlassHeader(layout)
70
+ // RN HeaderExtendHeader with search on the side bar layout: the gradient fills the header box,
71
+ // which for extended shrinks from 52pt below the header over the first 100pt of scroll
72
+ val duoSearch = layout.sideBar != null && getInputSearchType(options) != InputSearchType.None
73
+ val fillsBox = useDuoGlass || duoSearch
74
+ val duoModifier = when {
75
+ useDuoGlass -> {
76
+ val extra = if (options.headerType is HeaderType.Extended) DUO_GRADIENT_EXTRA else 0.dp
77
+ Modifier.height(minHeight + extra).graphicsLayer { translationY = -scrollState.value.toFloat() }
78
+ }
79
+ duoSearch && options.headerType is HeaderType.Extended -> {
80
+ val progress = (with(density) { scrollState.value.toDp() } / 100.dp).coerceIn(0f, 1f)
81
+ Modifier.height(minHeight + DUO_GRADIENT_EXTRA * (1 - progress))
82
+ }
83
+ duoSearch -> Modifier.height(minHeight)
84
+ else -> Modifier.height(height)
85
+ }
86
+
87
+ Box(Modifier.fillMaxWidth().then(duoModifier).clipToBounds().background(backgroundColor)) {
88
+ // on Duo the fade spans the gradient box; a banner image keeps its 375:154 shape, clipped
61
89
  Box(Modifier
62
- .height(maxHeight)
63
90
  .fillMaxWidth()
91
+ .then(if (fillsBox && !theme.isHeaderImage()) Modifier.fillMaxHeight() else Modifier.height(maxHeight))
64
92
  .conditional(!theme.isHeaderImage()) {
65
- background(Brush.linearGradient(
66
- colors = listOf(
67
- color,
68
- backgroundColor
69
- ),
70
- start = Offset(0f, 0f),
71
- end = Offset(0f, with(density) { maxHeight.toPx() })
72
- )
73
- )
93
+ background(Brush.verticalGradient(colors = listOf(color, backgroundColor)))
74
94
  }
75
95
  ){
76
96
  if (!theme.isHeaderImage()) return@Box
@@ -97,7 +97,8 @@ internal fun parseHeaderTint(value: String?): Color? {
97
97
  val hex = when (raw.length) {
98
98
  3 -> "FF" + raw.map { "$it$it" }.joinToString("")
99
99
  6 -> "FF$raw"
100
- 8 -> raw
100
+ // CSS #RRGGBBAA, as RN passes it; Compose wants AARRGGBB
101
+ 8 -> raw.substring(6) + raw.substring(0, 6)
101
102
  else -> return null
102
103
  }
103
104
  val v = hex.toLongOrNull(16) ?: return null
@@ -26,7 +26,9 @@ import androidx.compose.ui.Alignment
26
26
  import androidx.compose.ui.Modifier
27
27
  import androidx.compose.ui.draw.clip
28
28
  import androidx.compose.ui.graphics.Color
29
+ import androidx.compose.ui.unit.Dp
29
30
  import androidx.compose.ui.unit.dp
31
+ import vn.momo.kits.layout.DuoMetrics
30
32
  import vn.momo.kits.application.LocalContext
31
33
  import vn.momo.kits.application.MiniAppContext
32
34
  import vn.momo.kits.components.BadgeDot
@@ -60,12 +62,32 @@ fun HeaderRight(
60
62
  }
61
63
  }
62
64
 
65
+ private const val HELP_CENTER_APP_ID = "vn.momo.helpcenter"
66
+
67
+ private fun usesHelpCenter(context: MiniAppContext?): Boolean =
68
+ context != null && context.appId != HELP_CENTER_APP_ID
69
+
70
+ private fun showsShortcut(headerRight: HeaderRight.Toolkit, context: MiniAppContext?): Boolean =
71
+ headerRight.useShortcut && !(headerRight.useMore && context == null)
72
+
73
+ // Height of the vertical toolkit: the optional shortcut, then the help/home capsule.
74
+ @Composable
75
+ internal fun toolkitColumnHeight(headerRight: HeaderRight.Toolkit): Dp {
76
+ val context = LocalContext.current
77
+ val shortcut = if (showsShortcut(headerRight, context)) DuoMetrics.controlSize + Spacing.M else 0.dp
78
+ val groupCount = if (usesHelpCenter(context)) 2 else 1
79
+ return shortcut + if (groupCount > 1) DuoMetrics.itemPitch * groupCount else DuoMetrics.controlSize
80
+ }
81
+
82
+ // SideBarColumn: the iPhone Duo side bar; SideBarRow: the Duo header row on the status bar, same 48pt controls.
83
+ internal enum class ToolkitLayout { Bar, SideBarColumn, SideBarRow }
84
+
63
85
  @Composable
64
86
  internal fun Toolkit(
65
87
  headerRight: HeaderRight.Toolkit,
66
88
  tintColor: Color? = null,
67
89
  headerColor: HeaderColor,
68
- vertical: Boolean = false,
90
+ layout: ToolkitLayout = ToolkitLayout.Bar,
69
91
  ) {
70
92
  val api = LocalMaxApi.current
71
93
  val context = LocalContext.current
@@ -118,10 +140,13 @@ internal fun Toolkit(
118
140
  ) {}
119
141
  }
120
142
 
121
- fun onPressHome() {
122
- api?.dismissAll { }
143
+ fun onPressClose() {
144
+ if (headerRight.useCloseIcon) api?.dismiss { } else api?.dismissAll { }
123
145
  }
124
146
 
147
+ val useHelpCenter = usesHelpCenter(context)
148
+ val closeIcon = if (headerRight.useCloseIcon) "16_navigation_close_circle" else "16_basic_home"
149
+
125
150
  val navButtonConfig = getNavigationButtonConfig(
126
151
  headerRight,
127
152
  ::onPressShortcut,
@@ -132,12 +157,7 @@ internal fun Toolkit(
132
157
  group.items.any { it.showBadge }
133
158
  }
134
159
 
135
- val isShowShortcut by remember(headerRight.useShortcut, headerRight.useMore, context) {
136
- derivedStateOf {
137
- (headerRight.useShortcut == true) &&
138
- !(headerRight.useMore == true && context == null)
139
- }
140
- }
160
+ val isShowShortcut = showsShortcut(headerRight, context)
141
161
 
142
162
  val icon by remember(navButtonConfig.icon, isFavorite) {
143
163
  derivedStateOf {
@@ -146,8 +166,9 @@ internal fun Toolkit(
146
166
  }
147
167
  }
148
168
 
149
- if (vertical) {
150
- Column(verticalArrangement = Arrangement.spacedBy(Spacing.M)) {
169
+ if (layout != ToolkitLayout.Bar) {
170
+ val horizontal = layout == ToolkitLayout.SideBarRow
171
+ val controls = @Composable {
151
172
  if (isShowShortcut) {
152
173
  SideBarButton(
153
174
  icon = icon,
@@ -157,23 +178,32 @@ internal fun Toolkit(
157
178
  onClick = navButtonConfig.onPress,
158
179
  )
159
180
  }
160
- SideBarGroup(headerColor = headerColor, itemCount = if (context != null) 2 else 1) {
161
- if (context != null) {
181
+ SideBarGroup(headerColor = headerColor, itemCount = if (useHelpCenter) 2 else 1, horizontal = horizontal) { item ->
182
+ if (useHelpCenter) {
162
183
  SideBarGroupItem(
163
184
  icon = "help_center",
164
185
  headerColor = headerColor,
165
186
  automationId = "btn_navigation_help_center",
166
187
  onClick = ::onPressHelpCenter,
188
+ modifier = item,
167
189
  )
168
190
  }
169
191
  SideBarGroupItem(
170
- icon = "16_basic_home",
192
+ icon = closeIcon,
171
193
  headerColor = headerColor,
172
194
  automationId = "btn_navigation_close",
173
- onClick = ::onPressHome,
195
+ onClick = ::onPressClose,
196
+ modifier = item,
174
197
  )
175
198
  }
176
199
  }
200
+ if (horizontal) {
201
+ Row(horizontalArrangement = Arrangement.spacedBy(Spacing.M), verticalAlignment = Alignment.CenterVertically) {
202
+ controls()
203
+ }
204
+ } else {
205
+ Column(verticalArrangement = Arrangement.spacedBy(Spacing.M)) { controls() }
206
+ }
177
207
  return
178
208
  }
179
209
 
@@ -200,7 +230,7 @@ internal fun Toolkit(
200
230
  .clip(shape = RoundedCornerShape(14.dp))
201
231
  .background(headerColor.backgroundButton)
202
232
  ) {
203
- if (context != null) {
233
+ if (useHelpCenter) {
204
234
  Icon(
205
235
  source = "help_center",
206
236
  size = 20.dp,
@@ -215,10 +245,10 @@ internal fun Toolkit(
215
245
  )
216
246
  }
217
247
  Icon(
218
- source = "16_basic_home",
248
+ source = closeIcon,
219
249
  size = 20.dp,
220
250
  color = headerColor.tintIconColor,
221
- modifier = Modifier.padding(4.dp).activeOpacityClickable(onClick = ::onPressHome)
251
+ modifier = Modifier.padding(4.dp).activeOpacityClickable(onClick = ::onPressClose)
222
252
  )
223
253
  }
224
254
  }
@@ -308,7 +338,9 @@ sealed interface HeaderRight {
308
338
  val useMore: Boolean = false,
309
339
  val useSystemTools: Boolean = true,
310
340
  val tools: List<ToolGroup> = emptyList(),
311
- val toolCallback: ((String) -> Unit)? = { _ -> }
341
+ val toolCallback: ((String) -> Unit)? = { _ -> },
342
+ // Close dismisses only this mini app instead of returning home.
343
+ val useCloseIcon: Boolean = false,
312
344
  ) : HeaderRight
313
345
  }
314
346
 
@@ -9,10 +9,12 @@ import androidx.compose.ui.text.style.TextOverflow
9
9
  import androidx.compose.ui.unit.sp
10
10
  import androidx.compose.ui.zIndex
11
11
  import vn.momo.kits.components.Text
12
+ import vn.momo.kits.const.KitDesignSystem
12
13
  import vn.momo.kits.const.Typography
13
14
  import vn.momo.kits.modifier.setAutomationId
14
15
 
15
- internal const val HEADER_FONT_FAMILY = "MoMoTrustDisplay"
16
+ internal val headerFontFamily: String?
17
+ get() = if (KitDesignSystem.isWhiteList) "MoMoTrustDisplay" else null
16
18
 
17
19
  @Composable
18
20
  internal fun HeaderTitle(
@@ -29,7 +31,7 @@ internal fun HeaderTitle(
29
31
  fontSize = 15.sp,
30
32
  lineHeight = 22.sp,
31
33
  ),
32
- fontFamily = HEADER_FONT_FAMILY,
34
+ fontFamily = headerFontFamily,
33
35
  color = color,
34
36
  maxLines = 1,
35
37
  overflow = TextOverflow.Ellipsis
@@ -78,7 +78,7 @@ internal fun HeaderUser(
78
78
  text = data.title,
79
79
  color = data.tintColor,
80
80
  style = Typography.actionXsBold,
81
- fontFamily = HEADER_FONT_FAMILY,
81
+ fontFamily = headerFontFamily,
82
82
  maxLines = 1,
83
83
  overflow = TextOverflow.Ellipsis
84
84
  )
@@ -6,17 +6,16 @@ import androidx.compose.foundation.layout.Arrangement
6
6
  import androidx.compose.foundation.layout.Box
7
7
  import androidx.compose.foundation.layout.BoxScope
8
8
  import androidx.compose.foundation.layout.Column
9
- import androidx.compose.foundation.layout.ColumnScope
9
+ import androidx.compose.foundation.layout.Row
10
10
  import androidx.compose.foundation.layout.Spacer
11
11
  import androidx.compose.foundation.layout.absolutePadding
12
- import androidx.compose.foundation.layout.fillMaxWidth
13
- import androidx.compose.foundation.layout.height
14
12
  import androidx.compose.foundation.layout.offset
15
13
  import androidx.compose.foundation.layout.size
16
14
  import androidx.compose.foundation.layout.width
17
15
  import androidx.compose.foundation.shape.CircleShape
18
16
  import androidx.compose.foundation.shape.RoundedCornerShape
19
17
  import androidx.compose.runtime.Composable
18
+ import androidx.compose.runtime.LaunchedEffect
20
19
  import androidx.compose.ui.AbsoluteAlignment
21
20
  import androidx.compose.ui.Alignment
22
21
  import androidx.compose.ui.Modifier
@@ -41,18 +40,24 @@ import vn.momo.kits.modifier.conditional
41
40
  import vn.momo.kits.modifier.setAutomationId
42
41
  import vn.momo.kits.navigation.LocalNavigator
43
42
  import vn.momo.kits.navigation.LocalOptions
43
+ import vn.momo.kits.navigation.bottomtab.LocalDuoBarCompression
44
44
 
45
45
  private val SIDE_BAR_ICON_SIZE = 24.dp
46
46
 
47
47
  // iPhone Duo: back and toolkit leave the bar for a column in the side safe area, as the system bars do.
48
- // bottomInset is what footer / tab bar / home indicator take from the bottom; the column stays above it.
48
+ // bottomInset is what footer / tab bar / home indicator take from the bottom, gap included; the column stays above it.
49
49
  @Composable
50
50
  internal fun BoxScope.HeaderSideBar(bottomInset: Dp, modifier: Modifier = Modifier) {
51
51
  val options = LocalOptions.current
52
52
  val navigator = LocalNavigator.current
53
- val sideBar = getScreenLayout().sideBar ?: return
53
+ val screenLayout = getScreenLayout()
54
+ val sideBar = screenLayout.sideBar ?: return
54
55
  val toolkit = options.headerRight as? HeaderRight.Toolkit
55
- if (options.headerType == HeaderType.None || (options.hiddenBack && toolkit == null)) return
56
+ val empty = options.headerType == HeaderType.None || (options.hiddenBack && toolkit == null)
57
+ val compression = LocalDuoBarCompression.current
58
+ val fullHeight = if (empty) 0.dp else DuoMetrics.controlSize + (toolkit?.let { Spacing.M + toolkitColumnHeight(it) } ?: 0.dp)
59
+ LaunchedEffect(fullHeight, compression.setToolbarHeight) { compression.setToolbarHeight(fullHeight) }
60
+ if (empty) return
56
61
 
57
62
  val headerColor = getHeaderColor(options.headerType, headerOpacity(), options.tintColor, Colors.black_17)
58
63
  val isRight = sideBar.edge == SideBarEdge.RIGHT
@@ -63,7 +68,7 @@ internal fun BoxScope.HeaderSideBar(bottomInset: Dp, modifier: Modifier = Modifi
63
68
  .width(sideBar.width)
64
69
  .layout { measurable, constraints ->
65
70
  val placeable = measurable.measure(constraints.copy(minHeight = 0))
66
- val maxY = constraints.maxHeight - (bottomInset + Spacing.XL).roundToPx() - placeable.height
71
+ val maxY = screenLayout.height.roundToPx() - bottomInset.roundToPx() - placeable.height
67
72
  val y = minOf(sideBar.top.roundToPx(), maxY).coerceAtLeast(0)
68
73
  layout(placeable.width, constraints.constrainHeight(y + placeable.height)) {
69
74
  placeable.place(0, y)
@@ -84,8 +89,22 @@ internal fun BoxScope.HeaderSideBar(bottomInset: Dp, modifier: Modifier = Modifi
84
89
  onClick = { navigator.onBackSafe { } },
85
90
  )
86
91
  }
87
- if (toolkit != null) {
88
- Toolkit(headerRight = toolkit, tintColor = options.tintColor, headerColor = headerColor, vertical = true)
92
+ when {
93
+ toolkit == null || compression.toolbarHidden -> Unit
94
+ // HIG "Toolbar compressed": tools fold into one button, distinct from the useMore icon
95
+ compression.toolbarCompressed -> SideBarButton(
96
+ icon = "navigation_menu",
97
+ headerColor = headerColor,
98
+ showBadge = toolkit.tools.any { group -> group.items.any { it.showBadge } },
99
+ automationId = "btn_navigation_toolbar",
100
+ onClick = compression.expandToolbar,
101
+ )
102
+ else -> Toolkit(
103
+ headerRight = toolkit,
104
+ tintColor = options.tintColor,
105
+ headerColor = headerColor,
106
+ layout = ToolkitLayout.SideBarColumn,
107
+ )
89
108
  }
90
109
  }
91
110
  }
@@ -119,34 +138,38 @@ internal fun SideBarButton(
119
138
  }
120
139
  }
121
140
 
122
- // Capsule grouping related controls, one item per 50pt.
141
+ // Capsule grouping related controls, one item per 50pt: vertical in the side bar, horizontal on a header row.
123
142
  @Composable
124
143
  internal fun SideBarGroup(
125
144
  headerColor: HeaderColor,
126
145
  itemCount: Int,
127
- content: @Composable ColumnScope.() -> Unit,
146
+ horizontal: Boolean = false,
147
+ content: @Composable (itemModifier: Modifier) -> Unit,
128
148
  ) {
129
- val height = if (itemCount > 1) DuoMetrics.itemPitch * itemCount else DuoMetrics.controlSize
130
- Column(
131
- modifier = Modifier
132
- .width(DuoMetrics.controlSize)
133
- .height(height)
134
- .sideBarGlass(headerColor, RoundedCornerShape(Radius.XL)),
135
- content = content,
136
- )
149
+ val length = if (itemCount > 1) DuoMetrics.itemPitch * itemCount else DuoMetrics.controlSize
150
+ val item = length / itemCount.coerceAtLeast(1)
151
+ val glass = Modifier.sideBarGlass(headerColor, RoundedCornerShape(Radius.XL))
152
+ if (horizontal) {
153
+ Row(Modifier.size(width = length, height = DuoMetrics.controlSize).then(glass)) {
154
+ content(Modifier.size(width = item, height = DuoMetrics.controlSize))
155
+ }
156
+ } else {
157
+ Column(Modifier.size(width = DuoMetrics.controlSize, height = length).then(glass)) {
158
+ content(Modifier.size(width = DuoMetrics.controlSize, height = item))
159
+ }
160
+ }
137
161
  }
138
162
 
139
163
  @Composable
140
- internal fun ColumnScope.SideBarGroupItem(
164
+ internal fun SideBarGroupItem(
141
165
  icon: String,
142
166
  headerColor: HeaderColor,
143
167
  automationId: String,
144
168
  onClick: () -> Unit,
169
+ modifier: Modifier = Modifier,
145
170
  ) {
146
171
  Box(
147
- modifier = Modifier
148
- .weight(1f)
149
- .fillMaxWidth()
172
+ modifier = modifier
150
173
  .activeOpacityClickable(onClick = onClick)
151
174
  .setAutomationId(automationId),
152
175
  contentAlignment = Alignment.Center,
@@ -83,7 +83,8 @@ internal fun SnackBar(
83
83
  durationMillis = 200,
84
84
  )
85
85
  )
86
- navigator.hideSnackBar()
86
+ // hideSnackBar() would call clear() -> this close again while still bound
87
+ OverplayComponentRegistry.hardClearAfterDismiss()
87
88
  }
88
89
  }
89
90
 
@@ -14,6 +14,9 @@ data class ScreenDimension(
14
14
 
15
15
  expect fun getPlatformName(): String
16
16
 
17
+ // iPhone idiom only; iPad windows and Android never get the iPhone / iPhone Duo layout.
18
+ expect fun isPhoneIdiom(): Boolean
19
+
17
20
  @Composable
18
21
  expect fun getScreenDimensions(): ScreenDimension
19
22
 
@@ -0,0 +1,13 @@
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
+ // Regions the system occludes (iOS 27.1+), in the coordinates of the Compose root view: the
11
+ // iPhone Duo camera, and the status bar cluster as it grows for a call or a Live Activity.
12
+ @Composable
13
+ internal expect fun rememberReservedRegions(): List<ReservedRegion>
@@ -4,6 +4,12 @@ package vn.momo.kits.const
4
4
 
5
5
  import androidx.compose.runtime.Composable
6
6
  import androidx.compose.runtime.remember
7
+ // AI-GENERATED START: imports for the SF Pro -> iOS system font mapping (ITMS-91182)
8
+ import androidx.compose.ui.text.ExperimentalTextApi
9
+ import androidx.compose.ui.text.font.FontWeight
10
+ import androidx.compose.ui.text.platform.SystemFont
11
+ import platform.UIKit.UIFont
12
+ // AI-GENERATED END: imports for the SF Pro -> iOS system font mapping (ITMS-91182)
7
13
  import androidx.compose.ui.text.font.FontFamily
8
14
  import androidx.compose.ui.text.platform.Font
9
15
  import kotlinx.cinterop.ExperimentalForeignApi
@@ -14,8 +20,40 @@ import platform.Foundation.NSData
14
20
  import platform.Foundation.dataWithContentsOfFile
15
21
  import platform.posix.memcpy
16
22
 
23
+ // AI-GENERATED START: SF Pro -> iOS system font mapping (ITMS-91182)
24
+ /**
25
+ * Apple cấm app nhúng font của Apple (ITMS-91182) và font hệ thống iOS chính là SF Pro, nên file SF Pro
26
+ * được thay bằng system font cùng weight thay vì đọc từ bundle — host không cần (và không được) ship nó.
27
+ *
28
+ * Weight đi theo tên face của Apple (Ultralight nhẹ hơn Thin), khớp thang weight CoreText mà Skia dùng
29
+ * (100 = UltraLight, 200 = Thin), để ra đúng face mà tên file chỉ định.
30
+ * Phải mang weight trong Font vì `Text` bỏ `fontWeight` khỏi style (weight nằm sẵn trong font).
31
+ */
32
+ private val systemFontWeights: Map<String, FontWeight> = mapOf(
33
+ "SFProText-Ultralight.ttf" to FontWeight.Thin,
34
+ "SFProText-Thin.ttf" to FontWeight.ExtraLight,
35
+ "SFProText-Light.ttf" to FontWeight.Light,
36
+ "SFProText-Regular.ttf" to FontWeight.Normal,
37
+ "SFProText-Medium.ttf" to FontWeight.Medium,
38
+ "SFProText-Semibold.ttf" to FontWeight.SemiBold,
39
+ "SFProText-Bold.ttf" to FontWeight.Bold,
40
+ "SFProText-Heavy.ttf" to FontWeight.ExtraBold,
41
+ "SFProText-Black.ttf" to FontWeight.Black,
42
+ )
43
+
44
+ /** Family của system font (`.AppleSystemUIFont`), lấy qua API chính thức thay vì hard-code tên ẩn. */
45
+ private val systemFontFamilyName: String by lazy { UIFont.systemFontOfSize(14.0).familyName }
46
+
47
+ @OptIn(ExperimentalTextApi::class)
48
+ private fun systemFontFamily(weight: FontWeight): FontFamily =
49
+ FontFamily(SystemFont(identity = systemFontFamilyName, weight = weight))
50
+ // AI-GENERATED END: SF Pro -> iOS system font mapping (ITMS-91182)
51
+
17
52
  @Composable
18
53
  internal actual fun hostFontFamily(fileName: String): FontFamily? = remember(fileName) {
54
+ // AI-GENERATED START: SF Pro -> iOS system font mapping (ITMS-91182)
55
+ systemFontWeights[fileName]?.let { weight -> return@remember systemFontFamily(weight) }
56
+ // AI-GENERATED END: SF Pro -> iOS system font mapping (ITMS-91182)
19
57
  val name = fileName.substringBeforeLast('.')
20
58
  val ext = fileName.substringAfterLast('.', "")
21
59
  // Font từ font-source nằm rời ở bundle root, không nằm trong compose-resources.
@@ -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,94 @@
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.ExperimentalForeignApi
13
+ import kotlinx.cinterop.interpretObjCPointer
14
+ import kotlinx.cinterop.objcPtr
15
+ import kotlinx.cinterop.readValue
16
+ import kotlinx.cinterop.useContents
17
+ import platform.CoreGraphics.CGRectZero
18
+ import platform.Foundation.NSArray
19
+ import platform.Foundation.NSSelectorFromString
20
+ import platform.Foundation.NSValue
21
+ import platform.Foundation.valueForKey
22
+ import platform.UIKit.CGRectValue
23
+ import platform.UIKit.UIView
24
+ import platform.UIKit.UIViewAutoresizingFlexibleHeight
25
+ import platform.UIKit.UIViewAutoresizingFlexibleWidth
26
+ import platform.darwin.NSObject
27
+ import platform.Foundation.NSClassFromString
28
+
29
+ // The API is newer than the SDK Kotlin/Native binds, so it is reached through the runtime.
30
+ @OptIn(ExperimentalForeignApi::class)
31
+ private val regionsOfKind = NSSelectorFromString("reservedRegionsOfKind:")
32
+
33
+ @OptIn(ExperimentalForeignApi::class, BetaInteropApi::class)
34
+ private fun UIView.occlusionRegions(target: UIView): List<ReservedRegion> {
35
+ if (window == null || !respondsToSelector(regionsOfKind)) return emptyList()
36
+ // a class object fails `as? NSObjectProtocol` (K/N checks it as an instance), so it is reinterpreted
37
+ val kindClass = NSClassFromString("UIViewReservedRegionKind")
38
+ ?.let { interpretObjCPointer<NSObject>(it.objcPtr()) } ?: return emptyList()
39
+ val kind = kindClass.performSelector(NSSelectorFromString("occlusionRegionKind")) ?: return emptyList()
40
+ val regions = when (val result = performSelector(regionsOfKind, withObject = kind)) {
41
+ is List<*> -> result
42
+ is NSArray -> List(result.count.toInt()) { result.objectAtIndex(it.toULong()) }
43
+ else -> return emptyList()
44
+ }
45
+ return regions.mapNotNull { region ->
46
+ val frame = ((region as? NSObject)?.valueForKey("frame") as? NSValue)?.CGRectValue ?: return@mapNotNull null
47
+ convertRect(frame, toView = target).useContents {
48
+ ReservedRegion(origin.x.dp, origin.y.dp, size.width.dp, size.height.dp)
49
+ }
50
+ }
51
+ }
52
+
53
+ // Invisible full-size probe: reading the regions in layoutSubviews makes UIKit run it again
54
+ // when they change, so nothing polls.
55
+ @OptIn(ExperimentalForeignApi::class)
56
+ private class ReservedRegionsProbe(
57
+ private val onChange: (List<ReservedRegion>) -> Unit,
58
+ ) : UIView(frame = CGRectZero.readValue()) {
59
+ private var last: List<ReservedRegion>? = null
60
+
61
+ init {
62
+ userInteractionEnabled = false
63
+ autoresizingMask = UIViewAutoresizingFlexibleWidth or UIViewAutoresizingFlexibleHeight
64
+ }
65
+
66
+ override fun layoutSubviews() {
67
+ super.layoutSubviews()
68
+ val regions = superview?.let { occlusionRegions(it) } ?: emptyList()
69
+ if (regions != last) {
70
+ last = regions
71
+ onChange(regions)
72
+ }
73
+ }
74
+
75
+ override fun didMoveToWindow() {
76
+ super.didMoveToWindow()
77
+ setNeedsLayout()
78
+ }
79
+ }
80
+
81
+ @OptIn(ExperimentalForeignApi::class)
82
+ @Composable
83
+ internal actual fun rememberReservedRegions(): List<ReservedRegion> {
84
+ if (!isPhoneIdiom()) return emptyList()
85
+ val host = LocalUIViewController.current.view
86
+ var regions by remember { mutableStateOf(emptyList<ReservedRegion>()) }
87
+ DisposableEffect(host) {
88
+ val probe = ReservedRegionsProbe { regions = it }
89
+ probe.setFrame(host.bounds)
90
+ host.insertSubview(probe, atIndex = 0)
91
+ onDispose { probe.removeFromSuperview() }
92
+ }
93
+ return regions
94
+ }
package/gradle.properties CHANGED
@@ -18,7 +18,7 @@ kotlin.apple.xcodeCompatibility.nowarn=true
18
18
  name="ComposeKits"
19
19
  group=vn.momo.kits
20
20
  artifact.id=kits
21
- version=0.167.1-beta.4
21
+ version=0.167.1-duo.15
22
22
 
23
23
  repo=GitLab
24
24
  url=https://gitlab.mservice.com.vn/api/v4/projects/5400/packages/maven
@@ -41,6 +41,12 @@ public class KitConfig {
41
41
  public var trustBanner: TrustBannerData? = nil
42
42
  public var headerBar: String? = nil
43
43
  public var headerGradient: String? = nil
44
+
45
+ public init(trustBanner: TrustBannerData? = nil, headerBar: String? = nil, headerGradient: String? = nil) {
46
+ self.trustBanner = trustBanner
47
+ self.headerBar = headerBar
48
+ self.headerGradient = headerGradient
49
+ }
44
50
  }
45
51
 
46
52
  public class ApplicationEnvironment: ObservableObject {
@@ -59,6 +65,17 @@ public class ApplicationEnvironment: ObservableObject {
59
65
  }
60
66
  }
61
67
 
68
+ extension ApplicationEnvironment {
69
+ /// Closes the mini app: through `composeApi` as before, else `maxApi` (Compose `maxApi.dismiss`).
70
+ func requestDismiss() {
71
+ if let composeApi {
72
+ composeApi.requestCallback(funcName: "dismiss", params: nil, onResponse: nil)
73
+ } else {
74
+ maxApi?.dismiss(completion: nil)
75
+ }
76
+ }
77
+ }
78
+
62
79
  private struct ApplicationEnvironmentKey: EnvironmentKey {
63
80
  static let defaultValue = ApplicationEnvironment()
64
81
  }