@momo-kits/native-kits 0.162.49 → 0.162.50-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 (148) hide show
  1. package/CLAUDE.md +4 -2
  2. package/build.gradle.kts +11 -0
  3. package/compose/build.gradle.kts +198 -0
  4. package/compose/build.gradle.kts.backup +198 -0
  5. package/compose/compose.podspec +47 -0
  6. package/compose/src/androidMain/kotlin/vn/momo/kits/const/HostFont.android.kt +20 -0
  7. package/compose/src/androidMain/kotlin/vn/momo/kits/navigation/ScrollToTop.android.kt +6 -0
  8. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/BackGesture.android.kt +12 -0
  9. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/OsFontScale.android.kt +7 -0
  10. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +153 -0
  11. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/ReservedRegions.android.kt +6 -0
  12. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +110 -0
  13. package/compose/src/commonMain/kotlin/vn/momo/kits/application/DesignSystemKits.kt +62 -0
  14. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +1071 -0
  15. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Localize.kt +269 -0
  16. package/compose/src/commonMain/kotlin/vn/momo/kits/application/WidgetContainer.kt +56 -0
  17. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Avatar.kt +157 -0
  18. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +81 -0
  19. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeDot.kt +32 -0
  20. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +338 -0
  21. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BaselineView.kt +175 -0
  22. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +357 -0
  23. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +115 -0
  24. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +89 -0
  25. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +128 -0
  26. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Collapse.kt +209 -0
  27. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +542 -0
  28. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +49 -0
  29. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +86 -0
  30. package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +125 -0
  31. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +231 -0
  32. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +189 -0
  33. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +478 -0
  34. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +114 -0
  35. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +265 -0
  36. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +235 -0
  37. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +223 -0
  38. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +265 -0
  39. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +185 -0
  40. package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +343 -0
  41. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Loader.kt +108 -0
  42. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +56 -0
  43. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +41 -0
  44. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +84 -0
  45. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +40 -0
  46. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +370 -0
  47. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +108 -0
  48. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +308 -0
  49. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +59 -0
  50. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Rating.kt +87 -0
  51. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ScaleSizeScope.kt +21 -0
  52. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +90 -0
  53. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +330 -0
  54. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Stepper.kt +220 -0
  55. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Steps.kt +473 -0
  56. package/compose/src/commonMain/kotlin/vn/momo/kits/components/SuggestAction.kt +122 -0
  57. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +207 -0
  58. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +92 -0
  59. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +560 -0
  60. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +86 -0
  61. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +125 -0
  62. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +207 -0
  63. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +501 -0
  64. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +176 -0
  65. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Uploader.kt +182 -0
  66. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +193 -0
  67. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +29 -0
  68. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +236 -0
  69. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +180 -0
  70. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +325 -0
  71. package/compose/src/commonMain/kotlin/vn/momo/kits/const/HostFont.kt +17 -0
  72. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Radius.kt +12 -0
  73. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Spacing.kt +16 -0
  74. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +194 -0
  75. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +287 -0
  76. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +54 -0
  77. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +50 -0
  78. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/ScreenLayout.kt +321 -0
  79. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +41 -0
  80. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +52 -0
  81. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +66 -0
  82. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Conditional.kt +11 -0
  83. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/DeprecatedModifier.kt +104 -0
  84. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +48 -0
  85. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Size.kt +51 -0
  86. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +502 -0
  87. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/HostBackGestureBridge.kt +58 -0
  88. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +148 -0
  89. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +116 -0
  90. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +205 -0
  91. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +422 -0
  92. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ScrollToTop.kt +8 -0
  93. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +732 -0
  94. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackTransition.kt +148 -0
  95. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/SwipeBackHaptic.kt +35 -0
  96. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +228 -0
  97. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +217 -0
  98. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +86 -0
  99. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoBarCompression.kt +97 -0
  100. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoTabBar.kt +377 -0
  101. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +169 -0
  102. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +455 -0
  103. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +105 -0
  104. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderJourney.kt +106 -0
  105. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderLocation.kt +74 -0
  106. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +399 -0
  107. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +39 -0
  108. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +363 -0
  109. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SideBar.kt +192 -0
  110. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +133 -0
  111. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/tracking/ScreenTracker.kt +127 -0
  112. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/BackGesture.kt +12 -0
  113. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ComposeLottieAnimation.kt +91 -0
  114. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/OsFontScale.kt +9 -0
  115. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +74 -0
  116. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ReservedRegions.kt +20 -0
  117. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Icons.kt +1329 -0
  118. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +55 -0
  119. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Tracking.kt +15 -0
  120. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +118 -0
  121. package/compose/src/iosMain/kotlin/vn/momo/kits/const/HostFont.ios.kt +42 -0
  122. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/HostBackGesture.ios.kt +45 -0
  123. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/ScrollToTop.ios.kt +32 -0
  124. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/BackGesture.ios.kt +38 -0
  125. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/OsFontScale.ios.kt +66 -0
  126. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +218 -0
  127. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/ReservedRegions.ios.kt +138 -0
  128. package/gradle/libs.versions.toml +69 -0
  129. package/gradle/wrapper/gradle-wrapper.jar +0 -0
  130. package/gradle/wrapper/gradle-wrapper.properties +8 -0
  131. package/gradle.properties +26 -0
  132. package/gradlew +252 -0
  133. package/gradlew.bat +94 -0
  134. package/ios/Application/DesignSystemKits.swift +64 -1
  135. package/ios/Layout/ScreenLayout.swift +23 -3
  136. package/ios/Navigation/BottomSheet.swift +150 -22
  137. package/ios/Navigation/Component/SnackBar.swift +4 -0
  138. package/ios/Navigation/ModalScreen.swift +7 -3
  139. package/ios/Popup/PopupDisplay.swift +2 -2
  140. package/ios/Popup/PopupInput.swift +2 -2
  141. package/ios/Popup/PopupLayout.swift +15 -8
  142. package/ios/Popup/PopupNotify.swift +2 -2
  143. package/ios/Typography/FontScale.swift +15 -4
  144. package/ios/native-kits.podspec +1 -1
  145. package/ios-demo/MoMoUIKitsDemo.podspec +1 -1
  146. package/ios-demo/README.md +120 -0
  147. package/package.json +1 -1
  148. package/settings.gradle.kts +64 -0
@@ -0,0 +1,51 @@
1
+ package vn.momo.kits.modifier
2
+
3
+ import androidx.compose.runtime.Composable
4
+ import androidx.compose.runtime.State
5
+ import androidx.compose.ui.Modifier
6
+ import androidx.compose.ui.layout.Measurable
7
+ import androidx.compose.ui.layout.MeasureResult
8
+ import androidx.compose.ui.layout.MeasureScope
9
+ import androidx.compose.ui.node.LayoutModifierNode
10
+ import androidx.compose.ui.node.ModifierNodeElement
11
+ import androidx.compose.ui.unit.Constraints
12
+ import androidx.compose.ui.unit.IntOffset
13
+
14
+ @Composable
15
+ fun Modifier.dynamicSize(
16
+ widthState: State<Int>? = null,
17
+ heightState: State<Int>? = null,
18
+ ) = this then DynamicSizeElement(widthState, heightState)
19
+
20
+ private data class DynamicSizeElement(
21
+ val widthState: State<Int>?,
22
+ val heightState: State<Int>?,
23
+ ) : ModifierNodeElement<DynamicSizeNode>() {
24
+ override fun create(): DynamicSizeNode {
25
+ return DynamicSizeNode(widthState, heightState)
26
+ }
27
+
28
+ override fun update(node: DynamicSizeNode) {
29
+ node.widthState = widthState
30
+ node.heightState = heightState
31
+ }
32
+
33
+ }
34
+
35
+ private class DynamicSizeNode(
36
+ var widthState: State<Int>?,
37
+ var heightState: State<Int>?,
38
+ ) : Modifier.Node(), LayoutModifierNode {
39
+ override fun MeasureScope.measure(
40
+ measurable: Measurable,
41
+ constraints: Constraints
42
+ ): MeasureResult {
43
+ val p = measurable.measure(constraints)
44
+ return layout(
45
+ width = widthState?.value ?: p.width,
46
+ height = heightState?.value ?: p.height,
47
+ ) {
48
+ p.place(IntOffset.Zero)
49
+ }
50
+ }
51
+ }
@@ -0,0 +1,502 @@
1
+ package vn.momo.kits.navigation
2
+
3
+ import androidx.compose.animation.core.Animatable
4
+ import androidx.compose.animation.core.CubicBezierEasing
5
+ import androidx.compose.animation.core.tween
6
+ import androidx.compose.foundation.background
7
+ import androidx.compose.foundation.border
8
+ import androidx.compose.foundation.gestures.detectDragGestures
9
+ import androidx.compose.foundation.layout.Arrangement
10
+ import androidx.compose.foundation.layout.Box
11
+ import androidx.compose.foundation.layout.BoxScope
12
+ import androidx.compose.foundation.layout.BoxWithConstraints
13
+ import androidx.compose.foundation.layout.Column
14
+ import androidx.compose.foundation.layout.Row
15
+ import androidx.compose.foundation.layout.Spacer
16
+ import androidx.compose.foundation.layout.fillMaxHeight
17
+ import androidx.compose.foundation.layout.fillMaxSize
18
+ import androidx.compose.foundation.layout.fillMaxWidth
19
+ import androidx.compose.foundation.layout.height
20
+ import androidx.compose.foundation.layout.heightIn
21
+ import androidx.compose.foundation.layout.offset
22
+ import androidx.compose.foundation.layout.PaddingValues
23
+ import androidx.compose.foundation.layout.padding
24
+ import androidx.compose.foundation.layout.size
25
+ import androidx.compose.foundation.layout.width
26
+ import androidx.compose.foundation.layout.absolutePadding
27
+ import androidx.compose.foundation.shape.RoundedCornerShape
28
+ import androidx.compose.runtime.Composable
29
+ import androidx.compose.runtime.DisposableEffect
30
+ import androidx.compose.runtime.LaunchedEffect
31
+ import androidx.compose.runtime.getValue
32
+ import androidx.compose.runtime.mutableStateOf
33
+ import androidx.compose.runtime.remember
34
+ import androidx.compose.runtime.setValue
35
+ import androidx.compose.runtime.rememberCoroutineScope
36
+ import androidx.compose.ui.AbsoluteAlignment
37
+ import androidx.compose.ui.Alignment
38
+ import androidx.compose.ui.Modifier
39
+ import androidx.compose.ui.graphics.Brush
40
+ import androidx.compose.ui.graphics.Color
41
+ import androidx.compose.ui.input.pointer.pointerInput
42
+ import androidx.compose.ui.layout.layout
43
+ import androidx.compose.ui.platform.LocalDensity
44
+ import androidx.compose.ui.unit.Dp
45
+ import androidx.compose.ui.unit.IntOffset
46
+ import androidx.compose.ui.unit.constrainHeight
47
+ import androidx.compose.ui.unit.dp
48
+ import kotlinx.coroutines.launch
49
+ import vn.momo.kits.components.Divider
50
+ import vn.momo.kits.components.Icon
51
+ import vn.momo.kits.components.Text
52
+ import vn.momo.kits.const.AppTheme
53
+ import vn.momo.kits.const.Colors
54
+ import vn.momo.kits.const.Radius
55
+ import vn.momo.kits.const.Spacing
56
+ import vn.momo.kits.application.LocalContext
57
+ import vn.momo.kits.application.IsShowBaseLineDebug
58
+ import vn.momo.kits.const.AppNavigationBar
59
+ import vn.momo.kits.const.Typography
60
+ import vn.momo.kits.navigation.tracking.ScreenTracker
61
+ import vn.momo.kits.modifier.conditional
62
+ import vn.momo.kits.modifier.noFeedbackClickable
63
+ import vn.momo.kits.platform.BackHandler
64
+ import vn.momo.kits.platform.getScreenHeight
65
+ import vn.momo.kits.layout.DuoMetrics
66
+ import vn.momo.kits.layout.SideBarEdge
67
+ import vn.momo.kits.layout.SideBarLayout
68
+ import vn.momo.kits.layout.getScreenLayout
69
+ import vn.momo.kits.modifier.setAutomationId
70
+ import vn.momo.kits.navigation.component.HeaderType
71
+ import vn.momo.kits.navigation.component.SideBarButton
72
+ import vn.momo.kits.navigation.component.getHeaderColor
73
+
74
+ // iPhone Duo inner display: center the sheet instead of spanning the width (RN BottomSheet).
75
+ private val DUO_REGULAR_MIN_WIDTH = 600.dp
76
+
77
+ private val INDICATOR_HEIGHT = 16.dp
78
+ private val HEADER_ROW_HEIGHT = 56.dp
79
+
80
+ // Side column controls line up with the header row while the sheet is short.
81
+ private val SIDE_COLUMN_OFFSET = INDICATOR_HEIGHT + (HEADER_ROW_HEIGHT - DuoMetrics.controlSize) / 2
82
+
83
+ @Composable
84
+ internal fun BottomSheet(
85
+ content: @Composable () -> Unit,
86
+ header: BottomHeader,
87
+ isSurface: Boolean = false,
88
+ barrierDismissible: Boolean = true,
89
+ draggable: Boolean = true,
90
+ useBottomInset: Boolean = true,
91
+ footerComponent: (@Composable () -> Unit)? = null,
92
+ onDismiss: (() -> Unit)?
93
+ ) {
94
+ val layout = getScreenLayout()
95
+ val isDuo = layout.isIPhoneDuoDevice
96
+ val isDuoCentered = isDuo && layout.width >= DUO_REGULAR_MIN_WIDTH
97
+ // outer display: the sheet floats Spacing.M off the side edges like system sheets, its content
98
+ // stays in the safe area and its buttons move to the side bar column (HIG)
99
+ val isDuoFloating = isDuo && !isDuoCentered
100
+ val title = header as? BottomHeader.Title
101
+ val sideColumn = layout.sideBar?.takeIf {
102
+ isDuoFloating && title != null && it.width - Spacing.M >= DuoMetrics.controlSize + Spacing.M
103
+ }
104
+ // the floating sheet spans the side safe area, its content stays clear of it
105
+ val sidePadding = if (isDuoFloating) {
106
+ PaddingValues.Absolute(
107
+ left = (layout.insetLeft - Spacing.M).coerceAtLeast(0.dp),
108
+ right = (layout.insetRight - Spacing.M).coerceAtLeast(0.dp),
109
+ )
110
+ } else {
111
+ PaddingValues()
112
+ }
113
+ // the screen is the outer display on iPhone Duo, even when unfolded
114
+ val screenHeightDp = if (isDuo) layout.height else getScreenHeight()
115
+ val screenHeightPx = with(LocalDensity.current) { screenHeightDp.toPx() }
116
+ val sheetOffset = remember { Animatable(screenHeightPx) }
117
+
118
+ val backgroundAlpha = remember { Animatable(0f) }
119
+ val dynamicAlpha = ((1f - (sheetOffset.value / screenHeightPx)).coerceIn(0f, 1f)) * 0.3f
120
+ val alpha = backgroundAlpha.value + dynamicAlpha
121
+
122
+ val sheetCloseOffset = with(LocalDensity.current) { 100.dp.toPx() }
123
+
124
+ val coroutineScope = rememberCoroutineScope()
125
+
126
+ val maxApi = LocalMaxApi.current
127
+ val context = LocalContext.current
128
+
129
+ DisposableEffect(Unit) {
130
+ ScreenTracker.trackPopupDisplayed(
131
+ maxApi = maxApi,
132
+ context = context,
133
+ parentScreenName = ScreenTracker.getLastScreenName()
134
+ )
135
+ onDispose { }
136
+ }
137
+
138
+ // a closing sheet keeps going: a second close, the open slide or a drag would cancel its animation
139
+ var isClosing by remember { mutableStateOf(false) }
140
+ // runs once the sheet has closed itself, after its onDismiss
141
+ var afterDismiss by remember { mutableStateOf<(() -> Unit)?>(null) }
142
+
143
+ suspend fun openEvent(){
144
+ backgroundAlpha.animateTo(
145
+ targetValue = 0.3f,
146
+ animationSpec = tween(
147
+ durationMillis = 100,
148
+ easing = CubicBezierEasing(0.05f, 0.7f, 0.1f, 1f)
149
+ )
150
+ )
151
+ if (isClosing) return
152
+ sheetOffset.animateTo(
153
+ targetValue = 0f,
154
+ animationSpec = tween(
155
+ durationMillis = 350,
156
+ easing = CubicBezierEasing(0.05f, 0.7f, 0.1f, 1f)
157
+ )
158
+ )
159
+ }
160
+
161
+ fun closeEvent(then: (() -> Unit)? = null){
162
+ if (isClosing) return
163
+ isClosing = true
164
+ coroutineScope.launch {
165
+ sheetOffset.animateTo(
166
+ targetValue = screenHeightPx,
167
+ animationSpec = tween(
168
+ durationMillis = 200,
169
+ easing = CubicBezierEasing(0.3f, 0.0f, 0.8f, 0.15f)
170
+ )
171
+ )
172
+ backgroundAlpha.animateTo(
173
+ targetValue = 0f,
174
+ animationSpec = tween(
175
+ durationMillis = 100,
176
+ easing = CubicBezierEasing(0.3f, 0.0f, 0.8f, 0.15f)
177
+ )
178
+ )
179
+ // not reached when a newer overlay replaced this one, so its back is dropped
180
+ afterDismiss = then
181
+ OverplayComponentRegistry.hardClearAfterDismiss()
182
+ }
183
+ }
184
+
185
+ // RN `leftOptions.onPressIconLeft`: the sheet closes first, then the back runs
186
+ val onBack = title?.onBack?.let { back -> { closeEvent(then = back) } }
187
+
188
+ LaunchedEffect(Unit) {
189
+ openEvent()
190
+ }
191
+
192
+ DisposableEffect(Unit) {
193
+ OverplayComponentRegistry.bindClose { closeEvent() }
194
+ onDispose {
195
+ onDismiss?.invoke()
196
+ afterDismiss?.invoke()
197
+ }
198
+ }
199
+
200
+ BackHandler(true){
201
+ closeEvent()
202
+ }
203
+
204
+ BoxWithConstraints(
205
+ modifier = Modifier
206
+ .fillMaxSize()
207
+ .background(Color.Black.copy(alpha = alpha))
208
+ .noFeedbackClickable {
209
+ if(barrierDismissible) closeEvent()
210
+ }
211
+ ) {
212
+ val parentHeight = with(LocalDensity.current) { constraints.maxHeight.toDp() }
213
+ val maxHeight = if (isDuo) {
214
+ // with its buttons in the side column the sheet can pass the side status bar
215
+ parentHeight - maxOf(if (sideColumn != null) layout.insetTop else layout.duoTopInset, Spacing.XL)
216
+ } else {
217
+ parentHeight - 90.dp
218
+ }
219
+ val radius = if (isDuo) Radius.XL else Radius.M
220
+
221
+ Box(
222
+ modifier = Modifier
223
+ .align(Alignment.BottomCenter)
224
+ .offset { IntOffset(x = 0, y = sheetOffset.value.toInt()) }
225
+ .then(
226
+ when {
227
+ // keeps the portrait width in landscape, like system sheets
228
+ isDuoCentered -> Modifier.width(
229
+ minOf(
230
+ minOf(layout.width, layout.height) - Spacing.L * 2,
231
+ layout.width - maxOf(layout.insetLeft, layout.insetRight, Spacing.L) * 2,
232
+ )
233
+ )
234
+ isDuoFloating -> Modifier.fillMaxWidth().padding(horizontal = Spacing.M)
235
+ else -> Modifier.fillMaxWidth()
236
+ }
237
+ )
238
+ .heightIn(max = maxHeight)
239
+ .background(
240
+ color = if (isSurface) AppTheme.current.colors.background.surface else AppTheme.current.colors.background.default,
241
+ shape = RoundedCornerShape(topStart = radius, topEnd = radius)
242
+ )
243
+ .conditional(IsShowBaseLineDebug) {
244
+ border(1.dp, Colors.blue_03)
245
+ }
246
+ .noFeedbackClickable { },
247
+ contentAlignment = Alignment.BottomCenter
248
+ ) {
249
+ val bottomInset = if (useBottomInset) {
250
+ (AppNavigationBar.current - keyboardSizeState().value).coerceAtLeast(0.dp)
251
+ } else 0.dp
252
+ Column(Modifier.padding(bottom = bottomInset)) {
253
+ Box(
254
+ modifier = Modifier
255
+ .height(INDICATOR_HEIGHT + HEADER_ROW_HEIGHT)
256
+ .fillMaxWidth()
257
+ .conditional(draggable) {
258
+ pointerInput(Unit) {
259
+ detectDragGestures(
260
+ onDrag = { change, dragAmount ->
261
+ change.consume()
262
+ if (isClosing) return@detectDragGestures
263
+ coroutineScope.launch {
264
+ val newOffset = (sheetOffset.value + dragAmount.y).coerceAtLeast(0f)
265
+ sheetOffset.snapTo(newOffset)
266
+ }
267
+ },
268
+ onDragEnd = {
269
+ if (isClosing) return@detectDragGestures
270
+ coroutineScope.launch {
271
+ if (sheetOffset.value > sheetCloseOffset) {
272
+ closeEvent()
273
+ } else {
274
+ sheetOffset.animateTo(0f)
275
+ }
276
+ }
277
+ }
278
+ )
279
+ }
280
+ }
281
+ ) {
282
+ Column(
283
+ modifier = Modifier
284
+ .fillMaxSize()
285
+ .padding(sidePadding)
286
+ .padding(horizontal = Spacing.M),
287
+ horizontalAlignment = Alignment.CenterHorizontally
288
+ ) {
289
+ Spacer(Modifier.height(8.dp))
290
+ Box(
291
+ modifier = Modifier
292
+ .height(4.dp)
293
+ .width(40.dp)
294
+ .background(
295
+ color = Colors.black_06,
296
+ shape = RoundedCornerShape(Radius.S)
297
+ )
298
+ )
299
+ Spacer(Modifier.height(4.dp))
300
+ Box(Modifier.fillMaxWidth().height(HEADER_ROW_HEIGHT), contentAlignment = Alignment.Center){
301
+ when (header) {
302
+ is BottomHeader.Title -> BottomSheetTitle(
303
+ text = header.data,
304
+ backIcon = header.backIcon,
305
+ // the side column holds back and close, the row keeps only the title
306
+ showButtons = sideColumn == null,
307
+ onBack = onBack,
308
+ onClose = { closeEvent() },
309
+ )
310
+ is BottomHeader.Custom -> {
311
+ header.content()
312
+ }
313
+ }
314
+ }
315
+ }
316
+ }
317
+ Box(Modifier.padding(sidePadding)) {
318
+ Divider()
319
+ }
320
+ if (footerComponent != null) {
321
+ Box(Modifier.weight(1f, fill = false).padding(sidePadding)) {
322
+ content()
323
+ }
324
+ BottomSheetFooter(footerComponent, sidePadding)
325
+ } else if (isDuo) {
326
+ // content shrinks so a tall sheet keeps its header
327
+ Box(Modifier.weight(1f, fill = false).padding(sidePadding)) {
328
+ content()
329
+ }
330
+ } else {
331
+ content()
332
+ }
333
+ }
334
+ if (sideColumn != null && title != null) {
335
+ BottomSheetSideColumn(
336
+ sideBar = sideColumn,
337
+ parentHeight = parentHeight,
338
+ backIcon = title.backIcon,
339
+ onBack = onBack,
340
+ onClose = { closeEvent() },
341
+ )
342
+ }
343
+ }
344
+ }
345
+ }
346
+
347
+ @Composable
348
+ private fun BottomSheetTitle(
349
+ text: String,
350
+ backIcon: String,
351
+ showButtons: Boolean,
352
+ onBack: (() -> Unit)?,
353
+ onClose: () -> Unit,
354
+ ) {
355
+ Row(modifier = Modifier.fillMaxSize(), verticalAlignment = Alignment.CenterVertically) {
356
+ if (showButtons) {
357
+ if (onBack != null) {
358
+ BottomSheetHeaderButton(icon = backIcon, automationId = "btn_bottom_sheet_back", onClick = onBack)
359
+ } else {
360
+ Box(Modifier.size(24.dp))
361
+ }
362
+ }
363
+ Box(
364
+ Modifier
365
+ .fillMaxHeight()
366
+ .weight(1f)
367
+ .conditional(!showButtons) { padding(horizontal = Spacing.M) },
368
+ contentAlignment = Alignment.Center
369
+ ) {
370
+ Text(
371
+ text = text,
372
+ color = Colors.black_17,
373
+ style = Typography.headerDefaultBold
374
+ )
375
+ }
376
+ if (showButtons) {
377
+ BottomSheetHeaderButton(icon = "navigation_close", automationId = "btn_bottom_sheet_close", onClick = onClose)
378
+ }
379
+ }
380
+ }
381
+
382
+ @Composable
383
+ private fun BottomSheetHeaderButton(icon: String, automationId: String, onClick: () -> Unit) {
384
+ Box(
385
+ Modifier
386
+ .size(24.dp)
387
+ .noFeedbackClickable(onClick = onClick)
388
+ .setAutomationId(automationId),
389
+ contentAlignment = Alignment.Center
390
+ ) {
391
+ Icon(source = icon)
392
+ }
393
+ }
394
+
395
+ // iPhone Duo outer display: back and close sit on the part of the sheet under the side safe area,
396
+ // 12pt from the strip's inner edge like the screen side bar. They follow the header row while the
397
+ // sheet is short and stay below the status bar cluster while it is tall.
398
+ @Composable
399
+ private fun BoxScope.BottomSheetSideColumn(
400
+ sideBar: SideBarLayout,
401
+ parentHeight: Dp,
402
+ backIcon: String,
403
+ onBack: (() -> Unit)?,
404
+ onClose: () -> Unit,
405
+ ) {
406
+ val isRight = sideBar.edge == SideBarEdge.RIGHT
407
+ val headerColor = getHeaderColor(HeaderType.None, 1f, null, AppTheme.current.colors.text.default)
408
+
409
+ Box(Modifier.matchParentSize()) {
410
+ Column(
411
+ modifier = Modifier
412
+ .align(if (isRight) AbsoluteAlignment.TopRight else AbsoluteAlignment.TopLeft)
413
+ .width(sideBar.width - Spacing.M)
414
+ .layout { measurable, constraints ->
415
+ val placeable = measurable.measure(constraints.copy(minHeight = 0))
416
+ val sheetHeight = constraints.maxHeight
417
+ // the sheet rests on the bottom of the overlay, which starts at the window top
418
+ val sheetTop = parentHeight.roundToPx() - sheetHeight
419
+ // clear of a camera at the bottom end of the strip when it can
420
+ val lowest = sheetHeight - sideBar.bottom.roundToPx() - placeable.height
421
+ val y = maxOf(sideBar.top.roundToPx() - sheetTop, SIDE_COLUMN_OFFSET.roundToPx())
422
+ .coerceAtMost(lowest)
423
+ .coerceAtLeast(0)
424
+ layout(placeable.width, constraints.constrainHeight(y + placeable.height)) {
425
+ placeable.place(0, y)
426
+ }
427
+ }
428
+ .absolutePadding(left = if (isRight) Spacing.M else 0.dp, right = if (isRight) 0.dp else Spacing.M),
429
+ horizontalAlignment = if (isRight) AbsoluteAlignment.Left else AbsoluteAlignment.Right,
430
+ verticalArrangement = Arrangement.spacedBy(Spacing.M),
431
+ ) {
432
+ if (onBack != null) {
433
+ SideBarButton(
434
+ icon = backIcon,
435
+ headerColor = headerColor,
436
+ automationId = "btn_bottom_sheet_back",
437
+ onClick = onBack,
438
+ )
439
+ }
440
+ SideBarButton(
441
+ icon = "navigation_close",
442
+ headerColor = headerColor,
443
+ automationId = "btn_bottom_sheet_close",
444
+ onClick = onClose,
445
+ )
446
+ }
447
+ }
448
+ }
449
+
450
+ @Composable
451
+ private fun BottomSheetFooter(footerComponent: @Composable () -> Unit, sidePadding: PaddingValues) {
452
+ val shadowBrush = remember {
453
+ Brush.verticalGradient(
454
+ colors = listOf(Color.Transparent, Color.Black.copy(alpha = 0.05f))
455
+ )
456
+ }
457
+
458
+ Box {
459
+ Box(
460
+ Modifier
461
+ .fillMaxWidth()
462
+ .background(AppTheme.current.colors.background.surface)
463
+ .conditional(IsShowBaseLineDebug) {
464
+ border(1.dp, Colors.blue_03)
465
+ }
466
+ .padding(sidePadding)
467
+ .padding(vertical = Spacing.S)
468
+ ) {
469
+ footerComponent()
470
+ }
471
+
472
+ Box(
473
+ modifier = Modifier
474
+ .fillMaxWidth()
475
+ .height(6.dp)
476
+ .offset(x = 0.dp, y = (-6).dp)
477
+ .background(shadowBrush)
478
+ )
479
+ }
480
+ }
481
+
482
+ sealed class BottomHeader {
483
+ data class Title(
484
+ val data: String = "Bottom Sheet Title",
485
+ ) : BottomHeader() {
486
+ // Outside the primary constructor so code built against older kits keeps Title() and copy().
487
+ // onBack shows a back button: the sheet closes (onDismiss fires), then it runs.
488
+ var onBack: (() -> Unit)? = null
489
+ private set
490
+ var backIcon: String = "arrow-back"
491
+ private set
492
+
493
+ constructor(data: String, onBack: (() -> Unit)?, backIcon: String = "arrow-back") : this(data) {
494
+ this.onBack = onBack
495
+ this.backIcon = backIcon
496
+ }
497
+ }
498
+
499
+ data class Custom(
500
+ val content: @Composable () -> Unit
501
+ ) : BottomHeader()
502
+ }
@@ -0,0 +1,58 @@
1
+ package vn.momo.kits.navigation
2
+
3
+ interface HostBackTarget {
4
+ fun canPop(): Boolean
5
+
6
+ fun onBack()
7
+
8
+ fun backStarted()
9
+ fun backProgressed(progress: Float)
10
+ fun backCompleted()
11
+ fun backCancelled()
12
+ }
13
+
14
+ object HostBackGestureBridge {
15
+ private val targets = mutableListOf<HostBackTarget>()
16
+ private var gestureTarget: HostBackTarget? = null
17
+
18
+ var shouldUseFloatingArrowBack: Boolean = false
19
+
20
+ fun checkCanPop(): Boolean = targets.isNotEmpty()
21
+
22
+ fun canPopInner(): Boolean = top()?.canPop() == true
23
+
24
+ fun pop() {
25
+ top()?.onBack()
26
+ }
27
+
28
+ fun started() {
29
+ val target = top() ?: return
30
+ gestureTarget = target
31
+ target.backStarted()
32
+ }
33
+
34
+ fun progressed(progress: Float) {
35
+ gestureTarget?.backProgressed(progress.coerceIn(0f, 1f))
36
+ }
37
+
38
+ fun completed() {
39
+ (gestureTarget ?: top())?.backCompleted()
40
+ gestureTarget = null
41
+ }
42
+
43
+ fun cancelled() {
44
+ gestureTarget?.backCancelled()
45
+ gestureTarget = null
46
+ }
47
+
48
+ fun attach(target: HostBackTarget) {
49
+ targets += target
50
+ }
51
+
52
+ fun detach(target: HostBackTarget) {
53
+ targets -= target
54
+ if (gestureTarget === target) gestureTarget = null
55
+ }
56
+
57
+ private fun top(): HostBackTarget? = targets.lastOrNull()
58
+ }