@momo-kits/native-kits 0.167.1-duo.18-debug → 0.167.1-duo.19

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