@momo-kits/native-kits 0.300.5-debug → 0.300.5

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/package.json +1 -1
  2. package/build.gradle.kts +0 -13
  3. package/compose/build.gradle.kts +0 -197
  4. package/compose/build.gradle.kts.backup +0 -197
  5. package/compose/compose.podspec +0 -47
  6. package/compose/src/androidMain/kotlin/vn/momo/kits/const/HostFont.android.kt +0 -20
  7. package/compose/src/androidMain/kotlin/vn/momo/kits/navigation/ScrollToTop.android.kt +0 -6
  8. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/BackGesture.android.kt +0 -12
  9. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/OsFontScale.android.kt +0 -7
  10. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +0 -153
  11. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/ReservedRegions.android.kt +0 -6
  12. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +0 -110
  13. package/compose/src/commonMain/kotlin/vn/momo/kits/application/DesignSystemKits.kt +0 -62
  14. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +0 -1071
  15. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Localize.kt +0 -269
  16. package/compose/src/commonMain/kotlin/vn/momo/kits/application/WidgetContainer.kt +0 -65
  17. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Avatar.kt +0 -157
  18. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +0 -81
  19. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeDot.kt +0 -32
  20. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +0 -338
  21. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BaselineView.kt +0 -175
  22. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +0 -357
  23. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +0 -115
  24. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +0 -89
  25. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +0 -128
  26. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Collapse.kt +0 -209
  27. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +0 -542
  28. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +0 -49
  29. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +0 -86
  30. package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +0 -125
  31. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +0 -231
  32. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +0 -189
  33. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +0 -478
  34. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +0 -114
  35. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +0 -265
  36. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +0 -235
  37. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +0 -223
  38. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +0 -265
  39. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +0 -185
  40. package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +0 -343
  41. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Loader.kt +0 -108
  42. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +0 -56
  43. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +0 -41
  44. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +0 -84
  45. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +0 -40
  46. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +0 -361
  47. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +0 -108
  48. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +0 -308
  49. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +0 -59
  50. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Rating.kt +0 -87
  51. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ScaleSizeScope.kt +0 -21
  52. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +0 -90
  53. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +0 -330
  54. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Stepper.kt +0 -220
  55. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Steps.kt +0 -473
  56. package/compose/src/commonMain/kotlin/vn/momo/kits/components/SuggestAction.kt +0 -122
  57. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +0 -207
  58. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +0 -92
  59. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +0 -560
  60. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +0 -86
  61. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +0 -125
  62. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +0 -207
  63. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +0 -501
  64. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +0 -176
  65. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Uploader.kt +0 -182
  66. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +0 -193
  67. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +0 -29
  68. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +0 -236
  69. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +0 -180
  70. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +0 -325
  71. package/compose/src/commonMain/kotlin/vn/momo/kits/const/HostFont.kt +0 -17
  72. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Radius.kt +0 -12
  73. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Spacing.kt +0 -16
  74. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +0 -194
  75. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +0 -287
  76. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +0 -54
  77. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +0 -50
  78. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/ScreenLayout.kt +0 -306
  79. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +0 -41
  80. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +0 -52
  81. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +0 -66
  82. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Conditional.kt +0 -11
  83. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/DeprecatedModifier.kt +0 -104
  84. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +0 -48
  85. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Size.kt +0 -51
  86. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +0 -338
  87. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/HostBackGestureBridge.kt +0 -58
  88. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +0 -137
  89. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +0 -116
  90. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +0 -215
  91. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +0 -404
  92. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ScrollToTop.kt +0 -8
  93. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +0 -732
  94. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackTransition.kt +0 -148
  95. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/SwipeBackHaptic.kt +0 -40
  96. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +0 -233
  97. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +0 -217
  98. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +0 -86
  99. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoBarCompression.kt +0 -97
  100. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoTabBar.kt +0 -377
  101. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +0 -169
  102. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +0 -455
  103. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +0 -103
  104. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderJourney.kt +0 -106
  105. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderLocation.kt +0 -74
  106. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +0 -413
  107. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +0 -39
  108. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +0 -363
  109. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HelpCenterRequest.kt +0 -37
  110. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SideBar.kt +0 -192
  111. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +0 -133
  112. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/ToolkitRequests.kt +0 -41
  113. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/tracking/ScreenTracker.kt +0 -135
  114. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/BackGesture.kt +0 -12
  115. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ComposeLottieAnimation.kt +0 -91
  116. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/OsFontScale.kt +0 -9
  117. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +0 -74
  118. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ReservedRegions.kt +0 -20
  119. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Icons.kt +0 -1329
  120. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +0 -55
  121. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Tracking.kt +0 -15
  122. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +0 -118
  123. package/compose/src/commonTest/kotlin/vn/momo/kits/navigation/component/HelpCenterRequestTest.kt +0 -44
  124. package/compose/src/commonTest/kotlin/vn/momo/kits/navigation/component/ToolkitRequestsTest.kt +0 -62
  125. package/compose/src/iosMain/kotlin/vn/momo/kits/const/HostFont.ios.kt +0 -42
  126. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/HostBackGesture.ios.kt +0 -45
  127. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/ScrollToTop.ios.kt +0 -34
  128. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/BackGesture.ios.kt +0 -38
  129. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/OsFontScale.ios.kt +0 -66
  130. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +0 -217
  131. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/ReservedRegions.ios.kt +0 -138
  132. package/gradle/libs.versions.toml +0 -68
  133. package/gradle/wrapper/gradle-wrapper.jar +0 -0
  134. package/gradle/wrapper/gradle-wrapper.properties +0 -8
  135. package/gradle.properties +0 -26
  136. package/gradlew +0 -252
  137. package/gradlew.bat +0 -94
  138. package/ios-demo/README.md +0 -120
  139. package/settings.gradle.kts +0 -64
@@ -1,732 +0,0 @@
1
- package vn.momo.kits.navigation
2
-
3
- import androidx.compose.animation.animateColorAsState
4
- import androidx.compose.animation.core.Animatable
5
- import androidx.compose.animation.core.AnimationVector1D
6
- import androidx.compose.animation.core.tween
7
- import androidx.compose.foundation.ScrollState
8
- import androidx.compose.foundation.background
9
- import androidx.compose.foundation.border
10
- import androidx.compose.foundation.gestures.ScrollableState
11
- import androidx.compose.foundation.gestures.animateScrollBy
12
- import androidx.compose.foundation.layout.Box
13
- import androidx.compose.foundation.layout.Column
14
- import androidx.compose.foundation.layout.ColumnScope
15
- import androidx.compose.foundation.layout.Spacer
16
- import androidx.compose.foundation.layout.WindowInsets
17
- import androidx.compose.foundation.layout.asPaddingValues
18
- import androidx.compose.foundation.layout.aspectRatio
19
- import androidx.compose.foundation.layout.fillMaxSize
20
- import androidx.compose.foundation.layout.fillMaxWidth
21
- import androidx.compose.foundation.layout.height
22
- import androidx.compose.foundation.layout.ime
23
- import androidx.compose.foundation.layout.imePadding
24
- import androidx.compose.foundation.layout.offset
25
- import androidx.compose.foundation.layout.padding
26
- import androidx.compose.foundation.lazy.LazyListState
27
- import androidx.compose.foundation.rememberScrollState
28
- import androidx.compose.foundation.verticalScroll
29
- import androidx.compose.material.ExperimentalMaterialApi
30
- import androidx.compose.runtime.Composable
31
- import androidx.compose.runtime.CompositionLocalProvider
32
- import androidx.compose.runtime.LaunchedEffect
33
- import androidx.compose.runtime.State
34
- import androidx.compose.runtime.getValue
35
- import androidx.compose.runtime.mutableIntStateOf
36
- import androidx.compose.runtime.mutableStateOf
37
- import androidx.compose.runtime.remember
38
- import androidx.compose.runtime.rememberUpdatedState
39
- import androidx.compose.runtime.snapshotFlow
40
- import androidx.compose.runtime.staticCompositionLocalOf
41
- import androidx.compose.ui.Alignment
42
- import androidx.compose.ui.Modifier
43
- import androidx.compose.ui.graphics.Brush
44
- import androidx.compose.ui.graphics.Color
45
- import androidx.compose.ui.graphics.graphicsLayer
46
- import androidx.compose.ui.graphics.lerp
47
- import androidx.compose.ui.layout.onGloballyPositioned
48
- import androidx.compose.ui.platform.LocalDensity
49
- import androidx.compose.ui.unit.Dp
50
- import androidx.compose.ui.unit.dp
51
- import androidx.compose.ui.unit.lerp
52
- import androidx.compose.ui.zIndex
53
- import kotlinx.coroutines.flow.Flow
54
- import kotlinx.coroutines.flow.filter
55
- import kotlinx.coroutines.launch
56
- import vn.momo.kits.components.InputSearch
57
- import vn.momo.kits.const.AppNavigationBar
58
- import vn.momo.kits.const.AppTheme
59
- import vn.momo.kits.application.IsShowBaseLineDebug
60
- import vn.momo.kits.const.Colors
61
- import vn.momo.kits.const.Spacing
62
- import vn.momo.kits.modifier.conditional
63
- import vn.momo.kits.modifier.hideKeyboardOnTap
64
- import vn.momo.kits.navigation.bottomtab.BOTTOM_TAB_BAR_HEIGHT
65
- import vn.momo.kits.navigation.component.FABPosition
66
- import vn.momo.kits.navigation.component.FloatingButton
67
- import vn.momo.kits.navigation.component.HEADER_HEIGHT
68
- import vn.momo.kits.navigation.component.Header
69
- import vn.momo.kits.navigation.component.HeaderBackground
70
- import vn.momo.kits.navigation.component.HeaderRight
71
- import vn.momo.kits.navigation.component.HeaderSideBar
72
- import vn.momo.kits.navigation.bottomtab.LocalDuoRailReserve
73
- import vn.momo.kits.navigation.bottomtab.duoFloatingTabBarInset
74
- import vn.momo.kits.navigation.component.HeaderType
75
- import vn.momo.kits.navigation.component.InputSearchType
76
- import vn.momo.kits.navigation.bottomtab.LocalBottomTabFocused
77
- import vn.momo.kits.platform.BackHandler
78
- import vn.momo.kits.platform.supportsImePadding
79
- import vn.momo.kits.navigation.tracking.ScreenTracker
80
- import vn.momo.kits.navigation.tracking.ScreenTrackingState
81
- import kotlinx.coroutines.delay
82
- import vn.momo.kits.application.LocalContext
83
- import vn.momo.kits.utils.horizontalSafeAreaPadding
84
- import vn.momo.kits.layout.LocalScreenLayout
85
- import vn.momo.kits.layout.ScreenLayout
86
- import vn.momo.kits.layout.consumeHorizontalInsets
87
- import vn.momo.kits.layout.DuoMetrics
88
- import vn.momo.kits.layout.getScreenLayout
89
- import kotlin.time.Clock
90
- import kotlin.time.Duration.Companion.milliseconds
91
- import kotlin.time.ExperimentalTime
92
-
93
- internal val LocalFooterHeightPx = staticCompositionLocalOf { mutableIntStateOf(0) }
94
- internal val LocalHeaderRightWidthPx = staticCompositionLocalOf { mutableIntStateOf(0) }
95
-
96
- // Space a tab's content leaves for the tab bar and home indicator. On iPhone Duo the floating bar
97
- // overlays the content and the rail lives in the side strip, so only their own inset applies.
98
- @Composable
99
- private fun bottomTabChildInset(): Dp {
100
- val keyboard = keyboardSizeState().value
101
- val navigationBar = AppNavigationBar.current
102
- val layout = getScreenLayout()
103
- return when {
104
- layout.isIPhoneDuo && layout.sideBar == null -> (duoFloatingTabBarInset(layout) - keyboard).coerceAtLeast(0.dp)
105
- layout.isIPhoneDuo -> (navigationBar - keyboard).coerceAtLeast(0.dp)
106
- else -> (BOTTOM_TAB_BAR_HEIGHT.dp + Spacing.S - keyboard).coerceAtLeast(0.dp) +
107
- (navigationBar - keyboard).coerceAtLeast(0.dp)
108
- }
109
- }
110
-
111
- @OptIn(ExperimentalMaterialApi::class, ExperimentalTime::class)
112
- @Composable
113
- internal fun StackScreen(
114
- content: @Composable () -> Unit,
115
- navigationOptions: NavigationOptions? = null,
116
- id: Int = -1,
117
- name: String = "",
118
- bottomTabIndex: Int = -1,
119
- onBackHandler: (() -> Unit)? = null,
120
- ) {
121
- val navigator = LocalNavigator.current
122
- val maxApi = LocalMaxApi.current
123
- val context = LocalContext.current
124
- val screenLayout = getScreenLayout()
125
- val density = LocalDensity.current
126
- val screenRegistry = LocalDynamicScreenRegistry.current
127
- val navigation = remember { Navigation(id = id, bottomTabIndex = bottomTabIndex, initOptions = navigationOptions, registry = screenRegistry) }
128
-
129
- val options by navigation.currentOptions
130
- val isBottomTabChild = bottomTabIndex != -1
131
- val isBottomTabRoot = navigation.isBottomTabRoot
132
-
133
- // A mounted-but-hidden bottom tab tracks again each time it is focused (RN useFocusEffect).
134
- val tabFocused = LocalBottomTabFocused.current
135
-
136
- // Auto tracking state
137
- val trackingState = remember(tabFocused) {
138
- ScreenTrackingState().apply {
139
- time = Clock.System.now().toEpochMilliseconds()
140
- }
141
- }
142
-
143
- // Determine action: push or back
144
- val action = remember(tabFocused) {
145
- if (ScreenTracker.getLastScreenName() != null) "push" else "back"
146
- }
147
-
148
- // Auto tracking effects
149
- LaunchedEffect(name, tabFocused) {
150
- if (!tabFocused) return@LaunchedEffect
151
- // Track screen navigated immediately
152
- ScreenTracker.trackScreenNavigated(
153
- maxApi = maxApi,
154
- context = context,
155
- screenName = name,
156
- action = action,
157
- state = trackingState
158
- )
159
-
160
- // Track screen displayed after 2 seconds (debounce)
161
- delay(2000.milliseconds)
162
- val loadTime = Clock.System.now().toEpochMilliseconds() - trackingState.time
163
- ScreenTracker.trackScreenDisplayed(
164
- maxApi = maxApi,
165
- context = context,
166
- screenName = name,
167
- duration = loadTime,
168
- state = trackingState
169
- )
170
-
171
- // Track screen interacted after displayed
172
- val interactionTime = Clock.System.now().toEpochMilliseconds() - trackingState.time
173
- ScreenTracker.trackScreenInteracted(
174
- maxApi = maxApi,
175
- context = context,
176
- screenName = name,
177
- duration = interactionTime - loadTime,
178
- totalDuration = interactionTime,
179
- state = trackingState
180
- )
181
- }
182
-
183
- val limit = with(density) {
184
- screenLayout.headerHeight.toPx()
185
- }.toInt()
186
-
187
- val lazyListState = options.scrollData.scrollState as? LazyListState
188
- val (scrollState, _) = if (lazyListState != null)
189
- lazyListState.proxyScrollState(limit, 1)
190
- else
191
- (options.scrollData.scrollState as? ScrollState ?: rememberScrollState()).proxyLimitedScrollState(limit, 1)
192
- val snapState: ScrollableState = lazyListState ?: scrollState
193
- val headerHide = rememberQuickReturnHide(
194
- enabled = options.usesDuoQuickReturnHeader(screenLayout),
195
- scrollState = scrollState,
196
- heightPx = with(density) { screenLayout.headerHeight.toPx() },
197
- )
198
-
199
- val footerHeightPx = remember { mutableIntStateOf(0) }
200
- val headerRightWidthPx = remember { mutableIntStateOf(0) }
201
-
202
- if (tabFocused && navigation.options.scrollData.scrollToTopCallback != null) {
203
- RegisterScrollToTop(navigation.options.scrollData.scrollToTopCallback)
204
- }
205
-
206
- val canPop = navigator.canPop()
207
-
208
- val hasCustomBackHandling = !SwipeBackConfig.enabled ||
209
- OverplayComponentRegistry.isOverplayShowing() ||
210
- !canPop
211
-
212
- BackHandler(hasCustomBackHandling) { navigator.onBackSafe() }
213
-
214
- CompositionLocalProvider(
215
- StackScreenScrollableState provides options.scrollData.scrollState,
216
- LocalNavigation provides navigation,
217
- LocalScrollState provides scrollState,
218
- LocalOptions provides options,
219
- LocalFooterHeightPx provides footerHeightPx,
220
- LocalHeaderRightWidthPx provides headerRightWidthPx
221
- ) {
222
- // AI-GENERATED START: custom screen background, full window and outside the keyboard padding
223
- Box(Modifier.fillMaxSize()) {
224
- options.backgroundComponent?.let { background ->
225
- Box(Modifier.fillMaxSize()) { background() }
226
- }
227
- // AI-GENERATED END: custom screen background, full window and outside the keyboard padding
228
- Box(
229
- Modifier
230
- .fillMaxSize()
231
- // AI-GENERATED START: custom background replaces the colour
232
- .background(
233
- if (options.backgroundComponent != null) Color.Transparent
234
- else options.backgroundColor ?: AppTheme.current.colors.background.default
235
- )
236
- // AI-GENERATED END: custom background replaces the colour
237
- .conditional(options.keyboardOptions.keyboardShouldPersistTaps) {
238
- hideKeyboardOnTap()
239
- }
240
- .conditional(options.keyboardOptions.useAvoidKeyboard && supportsImePadding()) {
241
- imePadding()
242
- }
243
- ) {
244
- Box(Modifier.zIndex(1f)) {
245
- HeaderBackground()
246
- }
247
-
248
- Box(Modifier.zIndex(4f).graphicsLayer { translationY = -headerHide.value }) {
249
- Header(onBackHandler)
250
- }
251
-
252
- HeaderSideBar(
253
- bottomInset = maxOf(
254
- bottomBarsHeight(isBottomTabChild) + Spacing.XL,
255
- if (isBottomTabChild) LocalDuoRailReserve.current else 0.dp,
256
- ),
257
- modifier = Modifier.zIndex(4f),
258
- )
259
-
260
- Column(Modifier.zIndex(5f).horizontalSafeAreaPadding()) {
261
- SearchAnimated(snapState = snapState)
262
- }
263
-
264
- Column(Modifier.zIndex(2f).fillMaxSize()) {
265
- MainContent(content = content, isBottomTabRoot = isBottomTabRoot, isBottomTabChild = isBottomTabChild)
266
- FooterContent()
267
- }
268
-
269
- Box(Modifier.zIndex(6f).horizontalSafeAreaPadding()) {
270
- FloatingContent(isBottomTabChild = isBottomTabChild)
271
- }
272
-
273
- OverplayView(isBottomTabChild = isBottomTabChild, id = id)
274
- }
275
- // AI-GENERATED START: custom screen background, full window and outside the keyboard padding
276
- }
277
- // AI-GENERATED END: custom screen background, full window and outside the keyboard padding
278
- }
279
- }
280
-
281
- // Height the footer, tab bar or home indicator takes from the bottom of the screen.
282
- @Composable
283
- private fun bottomBarsHeight(isBottomTabChild: Boolean): Dp {
284
- val options = LocalOptions.current
285
- val navigationBar = AppNavigationBar.current
286
- return when {
287
- options.hasFooter() -> with(LocalDensity.current) { LocalFooterHeightPx.current.intValue.toDp() }
288
- isBottomTabChild -> bottomTabChildInset()
289
- else -> navigationBar
290
- }
291
- }
292
-
293
- @Suppress("FrequentlyChangingValue")
294
- @Composable
295
- internal fun FloatingContent(isBottomTabChild: Boolean) {
296
- val options = LocalOptions.current
297
- val scrollState = LocalScrollState.current
298
-
299
- val fabProps = options.floatingButtonProps ?: return
300
- val bottomPadding = fabProps.bottom ?: (Spacing.M + bottomBarsHeight(isBottomTabChild))
301
-
302
- FloatingButton(
303
- scrollPosition = fabProps.scrollState?.value ?: scrollState.value,
304
- onClick = fabProps.onClick,
305
- containerColor = AppTheme.current.colors.primary,
306
- bottom = bottomPadding,
307
- icon = fabProps.icon,
308
- iconColor = fabProps.iconColor,
309
- text = fabProps.label,
310
- size = fabProps.size,
311
- position = fabProps.position ?: FABPosition.END,
312
- )
313
- }
314
-
315
- @Composable
316
- internal fun ColumnScope.MainContent(content: @Composable () -> Unit, isBottomTabRoot: Boolean, isBottomTabChild: Boolean) {
317
- val options = LocalOptions.current
318
- val inputSearchType = getInputSearchType(options)
319
- val density = LocalDensity.current
320
- val scrollState = LocalScrollState.current
321
- val navigationBar = AppNavigationBar.current
322
- val screenLayout = getScreenLayout()
323
- val useDuoGlass = options.usesDuoGlassHeader(screenLayout)
324
-
325
- Spacer(
326
- Modifier.height(
327
- if (!useDuoGlass && (options.headerType is HeaderType.DefaultOrExtended || (options.headerType is HeaderType.Transparent && !options.headerType.isFullScreenContent)))
328
- screenLayout.headerHeight else 0.dp
329
- )
330
- )
331
- if (inputSearchType == InputSearchType.Animated) {
332
- val scrollDp = with(density) { scrollState.value.toDp() }
333
-
334
- Spacer(Modifier.height((HEADER_HEIGHT.dp - scrollDp).coerceIn(0.dp, HEADER_HEIGHT.dp)))
335
- }
336
- // a bottom tab root stays full width: the tab bar and each tab's own StackScreen handle the insets
337
- val insetContent = !isBottomTabRoot
338
- Column(
339
- Modifier
340
- .fillMaxWidth()
341
- .weight(1f)
342
- .conditional(insetContent && options.headerType !is HeaderType.Animated) {
343
- horizontalSafeAreaPadding()
344
- }
345
- .conditional(options.scrollData.scrollable && options.scrollData.scrollState is ScrollState) {
346
- verticalScroll(scrollState)
347
- }
348
- .conditional(useDuoGlass) {
349
- padding(top = screenLayout.headerHeight)
350
- }
351
- .padding(
352
- bottom = when {
353
- options.hasFooter() || isBottomTabRoot -> 0.dp
354
- isBottomTabChild -> bottomTabChildInset()
355
- !options.scrollData.scrollable && options.scrollData.scrollState == null -> 0.dp
356
- else -> navigationBar
357
- }
358
- )
359
- ) {
360
- ScreenContent(content = content, insetContent = insetContent)
361
- }
362
-
363
- }
364
-
365
- @Composable
366
- internal fun ScreenContent(content: @Composable () -> Unit, insetContent: Boolean) {
367
- val scrollState = LocalScrollState.current
368
- val options = LocalOptions.current
369
- val screenLayout = getScreenLayout()
370
- val insetBody: @Composable () -> Unit = {
371
- CompositionLocalProvider(
372
- LocalScreenLayout provides if (insetContent) screenLayout.consumeHorizontalInsets() else screenLayout,
373
- content = content,
374
- )
375
- }
376
-
377
- if (options.headerType is HeaderType.Animated) {
378
- val animatedHeader = options.headerType
379
- Box {
380
- val bannerModifier = animatedHeader.height
381
- ?.let { Modifier.fillMaxWidth().height(it) }
382
- ?: Modifier.fillMaxWidth().aspectRatio(animatedHeader.aspectRatio.value)
383
- Box(bannerModifier) {
384
- animatedHeader.composable.invoke(scrollState.value)
385
- }
386
- Box(
387
- Modifier
388
- .padding(top = screenLayout.headerHeight + animatedHeader.layoutOffSet)
389
- .conditional(insetContent) { horizontalSafeAreaPadding() }
390
- ) {
391
- insetBody()
392
- }
393
- }
394
- } else {
395
- insetBody()
396
- }
397
- }
398
-
399
- @Composable
400
- internal fun FooterContent() {
401
- val options = LocalOptions.current
402
- if (options.hasFooter()) {
403
- val keyboardSize = keyboardSizeState()
404
- // the Duo floating tab bar overlays tab content, so a tab's footer sits above it
405
- val duoTabInset = if (LocalNavigation.current.bottomTabIndex != -1) duoFloatingTabBarInset(getScreenLayout()) else 0.dp
406
- val bottomPadding = (maxOf(AppNavigationBar.current, duoTabInset) - keyboardSize.value).coerceAtLeast(0.dp)
407
- Footer(
408
- footerComponent = options.footerComponent,
409
- bottomPadding = bottomPadding,
410
- transparent = options.footerTransparent,
411
- useMargin = options.footerUseMargin
412
- )
413
- }
414
- }
415
-
416
- @Composable
417
- internal fun keyboardSizeState(): State<Dp> {
418
- val bottom = WindowInsets.ime.asPaddingValues()
419
- return rememberUpdatedState(bottom.calculateBottomPadding())
420
- }
421
-
422
- @Composable
423
- internal fun OverplayView(isBottomTabChild: Boolean, id: Int) {
424
- val overplayType = OverplayComponentRegistry.getOverplayType()
425
-
426
- if (overplayType != null) {
427
- Box(Modifier.zIndex(if (overplayType == OverplayComponentType.SNACK_BAR) 3f else 7f).fillMaxSize()) {
428
- if (isBottomTabChild) return@Box
429
- if (OverplayComponentRegistry.currentRootId() != id) return@Box
430
- OverplayComponentRegistry.OverlayComponent()
431
- }
432
- }
433
- }
434
-
435
- @Composable
436
- fun Footer(
437
- footerComponent: @Composable (() -> Unit)?,
438
- bottomPadding: Dp,
439
- transparent: Boolean = false,
440
- useMargin: Boolean = true,
441
- ) {
442
- if (footerComponent == null) return
443
-
444
- val footerHeightPx = LocalFooterHeightPx.current
445
-
446
- val shadowBrush = remember {
447
- Brush.verticalGradient(
448
- colors = listOf(Color.Transparent, Color.Black.copy(alpha = 0.05f))
449
- )
450
- }
451
-
452
- val backgroundColor = if (transparent) Color.Transparent else AppTheme.current.colors.background.surface
453
-
454
- // inset as a whole, not padded inside, so the surface and its shadow stay off the iPhone Duo side bar
455
- Box(Modifier.onGloballyPositioned {
456
- if (footerHeightPx.intValue != it.size.height) footerHeightPx.intValue = it.size.height
457
- }.horizontalSafeAreaPadding()) {
458
- Box(
459
- Modifier
460
- .fillMaxWidth()
461
- .background(backgroundColor)
462
- .conditional(IsShowBaseLineDebug) {
463
- border(1.dp, Colors.blue_03)
464
- }
465
- .padding(
466
- top = if (useMargin) Spacing.S else 0.dp,
467
- start = if (useMargin) Spacing.M else 0.dp,
468
- end = if (useMargin) Spacing.M else 0.dp,
469
- bottom = bottomPadding + if (useMargin) Spacing.S else 0.dp,
470
- )
471
- ) {
472
- footerComponent.invoke()
473
- }
474
-
475
- if (!transparent) {
476
- Box(
477
- modifier = Modifier
478
- .fillMaxWidth()
479
- .height(6.dp)
480
- .offset(x = 0.dp, y = (-6).dp)
481
- .background(shadowBrush)
482
- )
483
- }
484
- }
485
- }
486
-
487
- data class InputSearchLayoutParams(
488
- val topPadding: Dp,
489
- val startPadding: Dp,
490
- val endPadding: Dp
491
- )
492
-
493
- @Suppress("FrequentlyChangingValue")
494
- @Composable
495
- internal fun SearchAnimated(
496
- snapState: ScrollableState
497
- ) {
498
- val scrollState = LocalScrollState.current
499
- val options = LocalOptions.current
500
- val navigator = LocalNavigator.current
501
- val density = LocalDensity.current
502
-
503
- val scrollDp = with(density) { scrollState.value.toDp() }
504
-
505
- val inputSearchType = getInputSearchType(options)
506
- val headerRightWidthPx = LocalHeaderRightWidthPx.current
507
- val headerRightWidthDp = with(density) { headerRightWidthPx.intValue.toDp() }
508
-
509
- if (inputSearchType == InputSearchType.None) return
510
- val inputSearchProps = (options.headerType as? HeaderType.DefaultOrExtended)?.inputSearchProps ?: return
511
-
512
- val screenLayout = getScreenLayout()
513
- val useSideBar = screenLayout.sideBar != null
514
- // collapsed, the search is centered in the bar row
515
- val minTopPadding = screenLayout.headerStatusBarHeight + (screenLayout.headerBarHeight - HEADER_HEIGHT.dp) / 2
516
- val maxTopPadding = screenLayout.headerHeight
517
- // collapsed on the iPhone Duo status bar row, the search stays between its clusters (48pt back there)
518
- val clearance = screenLayout.statusBarClearance
519
- val backWidth = if (clearance != null && !useSideBar) DuoMetrics.controlSize else 28.dp
520
- val minStartPadding = Spacing.M
521
- val maxStartPadding = (clearance?.left ?: 0.dp) +
522
- if (options.hiddenBack || useSideBar) Spacing.M else backWidth + Spacing.M + Spacing.S
523
- val minEndPadding = Spacing.M
524
- val maxEndPadding = (clearance?.right ?: 0.dp) + when {
525
- options.headerRight is HeaderRight.None -> Spacing.M
526
- useSideBar && options.headerRight is HeaderRight.Toolkit -> Spacing.M
527
- else -> headerRightWidthDp + Spacing.M * 2
528
- }
529
-
530
- val targetColor = lerp(
531
- AppTheme.current.colors.background.surface,
532
- AppTheme.current.colors.background.default,
533
- (scrollDp / minTopPadding.coerceAtLeast(1.dp)).coerceIn(0f, 1f)
534
- )
535
- val animatedColor by animateColorAsState(targetValue = targetColor, label = "BackgroundColor")
536
-
537
- val layoutParams = when (inputSearchType) {
538
- InputSearchType.Header -> {
539
- InputSearchLayoutParams(
540
- topPadding = minTopPadding,
541
- startPadding = maxStartPadding,
542
- endPadding = maxEndPadding
543
- )
544
- }
545
-
546
- InputSearchType.Animated -> {
547
- val collapsePx = with(density) { HEADER_HEIGHT.dp.roundToPx() }
548
- val progress = (scrollState.value / collapsePx.toFloat()).coerceIn(0f, 1f)
549
-
550
- LaunchedEffect(snapState, collapsePx) {
551
- snapshotFlow { snapState.isScrollInProgress }
552
- .filter { !it }
553
- .collect {
554
- val offset = snapState.headerOffsetPx() ?: return@collect
555
- if (offset <= 0 || offset >= collapsePx) return@collect
556
- val target = if (offset < collapsePx / 2) 0 else collapsePx
557
- launch { snapState.animateScrollBy((target - offset).toFloat()) }
558
- }
559
- }
560
-
561
- InputSearchLayoutParams(
562
- topPadding = lerp(maxTopPadding, minTopPadding, progress),
563
- startPadding = lerp(minStartPadding, maxStartPadding, progress),
564
- endPadding = lerp(minEndPadding, maxEndPadding, progress)
565
- )
566
- }
567
-
568
- InputSearchType.None -> return
569
- }
570
-
571
- Spacer(Modifier.height(layoutParams.topPadding))
572
- Box(
573
- modifier = Modifier
574
- .height(HEADER_HEIGHT.dp)
575
- .fillMaxWidth()
576
- .padding(
577
- start = layoutParams.startPadding,
578
- end = layoutParams.endPadding
579
- ),
580
- contentAlignment = Alignment.Center
581
- ) {
582
- InputSearch(
583
- inputSearchProps = inputSearchProps.copy(
584
- backgroundColor = animatedColor,
585
- onPressButtonText = {
586
- navigator.pop()
587
- }
588
- )
589
- )
590
- }
591
- }
592
-
593
- private fun ScrollableState.headerOffsetPx(): Int? = when (this) {
594
- is LazyListState -> if (firstVisibleItemIndex == 0) firstVisibleItemScrollOffset else null
595
- is ScrollState -> value
596
- else -> null
597
- }
598
-
599
- private fun quantize(value: Int, stepPx: Int): Int {
600
- if (stepPx <= 1) return value
601
- return (value / stepPx) * stepPx
602
- }
603
-
604
- @Composable
605
- internal fun LazyListState.proxyScrollState(
606
- thresholdPx: Int = 200,
607
- stepPx: Int = 4
608
- ): Pair<ScrollState, Boolean> = proxyScroll(
609
- source = this,
610
- thresholdPx = thresholdPx,
611
- stepPx = stepPx,
612
- isScrollInProgress = { isScrollInProgress },
613
- rawTargetFlow = {
614
- snapshotFlow {
615
- if (firstVisibleItemIndex == 0) firstVisibleItemScrollOffset
616
- else SCROLLED_PAST_FIRST_ITEM_THRESHOLD
617
- }
618
- },
619
- )
620
-
621
- @Composable
622
- internal fun ScrollState.proxyLimitedScrollState(
623
- thresholdPx: Int = 200,
624
- stepPx: Int = 4
625
- ): Pair<ScrollState, Boolean> = proxyScroll(
626
- source = this,
627
- thresholdPx = thresholdPx,
628
- stepPx = stepPx,
629
- isScrollInProgress = { isScrollInProgress },
630
- rawTargetFlow = { snapshotFlow { value } },
631
- )
632
-
633
- @Composable
634
- private fun proxyScroll(
635
- source: Any,
636
- thresholdPx: Int,
637
- stepPx: Int,
638
- isScrollInProgress: () -> Boolean,
639
- rawTargetFlow: () -> Flow<Int>,
640
- ): Pair<ScrollState, Boolean> {
641
- val proxy = rememberScrollState()
642
- val locked = remember { mutableStateOf(false) }
643
-
644
- LaunchedEffect(source, proxy, thresholdPx, stepPx) {
645
- rawTargetFlow().collect { rawTarget ->
646
- val shouldLock = rawTarget > thresholdPx
647
- if (locked.value != shouldLock) locked.value = shouldLock
648
-
649
- val desired = if (shouldLock) thresholdPx else quantize(rawTarget, stepPx)
650
-
651
- if (proxy.value != desired) {
652
- proxy.scrollTo(desired.coerceAtLeast(0))
653
- }
654
- }
655
- }
656
-
657
- val inProgress = remember { mutableStateOf(false) }
658
- LaunchedEffect(source, thresholdPx) {
659
- snapshotFlow { isScrollInProgress() }
660
- .collect { progressing ->
661
- inProgress.value = if (locked.value) false else progressing
662
- }
663
- }
664
-
665
- return proxy to inProgress.value
666
- }
667
-
668
- private const val SCROLLED_PAST_FIRST_ITEM_THRESHOLD = 10_000
669
-
670
- internal val LocalScrollState = staticCompositionLocalOf<ScrollState> { error("No Scroll State provided") }
671
- internal val LocalOptions = staticCompositionLocalOf<NavigationOptions> { error("No NavigationOptions provided") }
672
-
673
- val StackScreenScrollableState = staticCompositionLocalOf<ScrollableState?> { null }
674
-
675
- // iPhone Duo side bar: the header leaves with the content and slides back in on a reverse scroll
676
- // (RN DuoQuickReturnHeader). The side bar is drawn apart from the header, so it stays put.
677
- @Composable
678
- private fun rememberQuickReturnHide(
679
- enabled: Boolean,
680
- scrollState: ScrollState,
681
- heightPx: Float,
682
- ): Animatable<Float, AnimationVector1D> {
683
- val hide = remember { Animatable(0f) }
684
- LaunchedEffect(enabled, scrollState, heightPx) {
685
- if (!enabled) {
686
- hide.snapTo(0f)
687
- return@LaunchedEffect
688
- }
689
- var last = scrollState.value
690
- snapshotFlow { scrollState.value }.collect { y ->
691
- val delta = y - last
692
- last = y
693
- hide.snapTo(if (y <= 0) 0f else (hide.value + delta).coerceIn(0f, heightPx))
694
- }
695
- }
696
- // a half-shown header settles fully in or out once the scroll stops
697
- LaunchedEffect(enabled, scrollState, heightPx) {
698
- if (!enabled) return@LaunchedEffect
699
- snapshotFlow { scrollState.isScrollInProgress }
700
- .filter { !it }
701
- .collect {
702
- val value = hide.value
703
- // within the first header height the header still sits on the content
704
- if (scrollState.value < heightPx || value <= 0f || value >= heightPx) return@collect
705
- launch { hide.animateTo(if (value > heightPx / 2) heightPx else 0f, tween(200)) }
706
- }
707
- }
708
- return hide
709
- }
710
-
711
- internal fun NavigationOptions.usesDuoQuickReturnHeader(layout: ScreenLayout): Boolean =
712
- usesDuoGlassHeader(layout) || (layout.sideBar != null && headerType is HeaderType.Animated &&
713
- scrollData.scrollable && scrollData.scrollState is ScrollState)
714
-
715
- // iPhone Duo side bar: content scrolls under a glass header instead of starting below it (RN Screen).
716
- internal fun NavigationOptions.usesDuoGlassHeader(layout: ScreenLayout): Boolean =
717
- layout.sideBar != null && headerType is HeaderType.DefaultOrExtended &&
718
- getInputSearchType(this) == InputSearchType.None &&
719
- scrollData.scrollable && scrollData.scrollState is ScrollState
720
-
721
- internal fun getInputSearchType(options: NavigationOptions): InputSearchType {
722
- return when (val headerType = options.headerType) {
723
- is HeaderType.DefaultOrExtended -> when {
724
- headerType.inputSearchProps == null -> InputSearchType.None
725
- headerType.useAnimated -> InputSearchType.Animated
726
- else -> InputSearchType.Header
727
- }
728
-
729
- else -> InputSearchType.None
730
- }
731
- }
732
-