@momo-kits/native-kits 0.162.51 → 0.162.52-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 (139) hide show
  1. package/CLAUDE.md +1 -1
  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/Navigation/BottomSheet.swift +3 -2
  135. package/ios/native-kits.podspec +1 -1
  136. package/ios-demo/MoMoUIKitsDemo.podspec +1 -1
  137. package/ios-demo/README.md +120 -0
  138. package/package.json +1 -1
  139. 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 && minOf(layout.width, layout.height) >= DUO_REGULAR_MIN_WIDTH
97
+ val isDuoFloating = isDuo && !isDuoCentered
98
+ val title = header as? BottomHeader.Title
99
+ val sideColumn = layout.sideBar?.takeIf {
100
+ isDuoFloating && title != null && it.width - Spacing.M >= DuoMetrics.controlSize + Spacing.M
101
+ }
102
+ // the floating sheet spans the side safe area, its content stays clear of it
103
+ val sidePadding = if (isDuoFloating) {
104
+ PaddingValues.Absolute(
105
+ left = (layout.insetLeft - Spacing.M).coerceAtLeast(0.dp),
106
+ right = (layout.insetRight - Spacing.M).coerceAtLeast(0.dp),
107
+ )
108
+ } else {
109
+ PaddingValues()
110
+ }
111
+ // the screen is the outer display on iPhone Duo, even when unfolded
112
+ val screenHeightDp = if (isDuo) layout.height else getScreenHeight()
113
+ val screenHeightPx = with(LocalDensity.current) { screenHeightDp.toPx() }
114
+ val sheetOffset = remember { Animatable(screenHeightPx) }
115
+
116
+ val backgroundAlpha = remember { Animatable(0f) }
117
+ val dynamicAlpha = ((1f - (sheetOffset.value / screenHeightPx)).coerceIn(0f, 1f)) * 0.3f
118
+ val alpha = backgroundAlpha.value + dynamicAlpha
119
+
120
+ val sheetCloseOffset = with(LocalDensity.current) { 100.dp.toPx() }
121
+
122
+ val coroutineScope = rememberCoroutineScope()
123
+
124
+ val maxApi = LocalMaxApi.current
125
+ val context = LocalContext.current
126
+
127
+ DisposableEffect(Unit) {
128
+ ScreenTracker.trackPopupDisplayed(
129
+ maxApi = maxApi,
130
+ context = context,
131
+ parentScreenName = ScreenTracker.getLastScreenName()
132
+ )
133
+ onDispose { }
134
+ }
135
+
136
+ // a closing sheet keeps going: a second close, the open slide or a drag would cancel its animation
137
+ var isClosing by remember { mutableStateOf(false) }
138
+ // runs once the sheet has closed itself, after its onDismiss
139
+ var afterDismiss by remember { mutableStateOf<(() -> Unit)?>(null) }
140
+
141
+ suspend fun openEvent(){
142
+ backgroundAlpha.animateTo(
143
+ targetValue = 0.3f,
144
+ animationSpec = tween(
145
+ durationMillis = 100,
146
+ easing = CubicBezierEasing(0.05f, 0.7f, 0.1f, 1f)
147
+ )
148
+ )
149
+ if (isClosing) return
150
+ sheetOffset.animateTo(
151
+ targetValue = 0f,
152
+ animationSpec = tween(
153
+ durationMillis = 350,
154
+ easing = CubicBezierEasing(0.05f, 0.7f, 0.1f, 1f)
155
+ )
156
+ )
157
+ }
158
+
159
+ fun closeEvent(then: (() -> Unit)? = null){
160
+ if (isClosing) return
161
+ isClosing = true
162
+ coroutineScope.launch {
163
+ sheetOffset.animateTo(
164
+ targetValue = screenHeightPx,
165
+ animationSpec = tween(
166
+ durationMillis = 200,
167
+ easing = CubicBezierEasing(0.3f, 0.0f, 0.8f, 0.15f)
168
+ )
169
+ )
170
+ backgroundAlpha.animateTo(
171
+ targetValue = 0f,
172
+ animationSpec = tween(
173
+ durationMillis = 100,
174
+ easing = CubicBezierEasing(0.3f, 0.0f, 0.8f, 0.15f)
175
+ )
176
+ )
177
+ // not reached when a newer overlay replaced this one, so its back is dropped
178
+ afterDismiss = then
179
+ OverplayComponentRegistry.hardClearAfterDismiss()
180
+ }
181
+ }
182
+
183
+ // RN `leftOptions.onPressIconLeft`: the sheet closes first, then the back runs
184
+ val onBack = title?.onBack?.let { back -> { closeEvent(then = back) } }
185
+
186
+ LaunchedEffect(Unit) {
187
+ openEvent()
188
+ }
189
+
190
+ DisposableEffect(Unit) {
191
+ OverplayComponentRegistry.bindClose { closeEvent() }
192
+ onDispose {
193
+ onDismiss?.invoke()
194
+ afterDismiss?.invoke()
195
+ }
196
+ }
197
+
198
+ BackHandler(true){
199
+ closeEvent()
200
+ }
201
+
202
+ BoxWithConstraints(
203
+ modifier = Modifier
204
+ .fillMaxSize()
205
+ .background(Color.Black.copy(alpha = alpha))
206
+ .noFeedbackClickable {
207
+ if(barrierDismissible) closeEvent()
208
+ }
209
+ ) {
210
+ val parentHeight = with(LocalDensity.current) { constraints.maxHeight.toDp() }
211
+ val maxHeight = if (isDuo) {
212
+ parentHeight - maxOf(layout.duoTopInset, Spacing.XL)
213
+ } else {
214
+ parentHeight - 90.dp
215
+ }
216
+ val radius = if (isDuo) Radius.XL else Radius.M
217
+
218
+ Box(
219
+ modifier = Modifier
220
+ .align(Alignment.BottomCenter)
221
+ .offset { IntOffset(x = 0, y = sheetOffset.value.toInt()) }
222
+ .then(
223
+ when {
224
+ // keeps the portrait width in landscape, like system sheets
225
+ isDuoCentered -> Modifier.width(
226
+ minOf(
227
+ minOf(layout.width, layout.height) - Spacing.L * 2,
228
+ layout.width - maxOf(layout.insetLeft, layout.insetRight, Spacing.L) * 2,
229
+ )
230
+ )
231
+ isDuoFloating -> Modifier.fillMaxWidth().padding(horizontal = Spacing.M)
232
+ else -> Modifier.fillMaxWidth()
233
+ }
234
+ )
235
+ .heightIn(max = maxHeight)
236
+ .background(
237
+ color = if (isSurface) AppTheme.current.colors.background.surface else AppTheme.current.colors.background.default,
238
+ shape = RoundedCornerShape(topStart = radius, topEnd = radius)
239
+ )
240
+ .conditional(IsShowBaseLineDebug) {
241
+ border(1.dp, Colors.blue_03)
242
+ }
243
+ .noFeedbackClickable { },
244
+ contentAlignment = Alignment.BottomCenter
245
+ ) {
246
+ val bottomInset = if (useBottomInset) {
247
+ (AppNavigationBar.current - keyboardSizeState().value).coerceAtLeast(0.dp)
248
+ } else 0.dp
249
+ Column(Modifier.padding(bottom = bottomInset)) {
250
+ Box(
251
+ modifier = Modifier
252
+ .height(INDICATOR_HEIGHT + HEADER_ROW_HEIGHT)
253
+ .fillMaxWidth()
254
+ .conditional(draggable) {
255
+ pointerInput(Unit) {
256
+ detectDragGestures(
257
+ onDrag = { change, dragAmount ->
258
+ change.consume()
259
+ if (isClosing) return@detectDragGestures
260
+ coroutineScope.launch {
261
+ val newOffset = (sheetOffset.value + dragAmount.y).coerceAtLeast(0f)
262
+ sheetOffset.snapTo(newOffset)
263
+ }
264
+ },
265
+ onDragEnd = {
266
+ if (isClosing) return@detectDragGestures
267
+ coroutineScope.launch {
268
+ if (sheetOffset.value > sheetCloseOffset) {
269
+ closeEvent()
270
+ } else {
271
+ sheetOffset.animateTo(0f)
272
+ }
273
+ }
274
+ }
275
+ )
276
+ }
277
+ }
278
+ ) {
279
+ // the indicator centers on the whole sheet, only the header row keeps clear of the strip
280
+ Column(
281
+ modifier = Modifier
282
+ .fillMaxSize()
283
+ .padding(horizontal = Spacing.M),
284
+ horizontalAlignment = Alignment.CenterHorizontally
285
+ ) {
286
+ Spacer(Modifier.height(8.dp))
287
+ Box(
288
+ modifier = Modifier
289
+ .height(4.dp)
290
+ .width(40.dp)
291
+ .background(
292
+ color = Colors.black_06,
293
+ shape = RoundedCornerShape(Radius.S)
294
+ )
295
+ )
296
+ Spacer(Modifier.height(4.dp))
297
+ Box(
298
+ Modifier.padding(sidePadding).fillMaxWidth().height(HEADER_ROW_HEIGHT),
299
+ contentAlignment = Alignment.Center
300
+ ) {
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
+ }