@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.
- package/package.json +1 -1
- package/build.gradle.kts +0 -13
- package/compose/build.gradle.kts +0 -197
- package/compose/build.gradle.kts.backup +0 -197
- package/compose/compose.podspec +0 -47
- package/compose/src/androidMain/kotlin/vn/momo/kits/const/HostFont.android.kt +0 -20
- package/compose/src/androidMain/kotlin/vn/momo/kits/navigation/ScrollToTop.android.kt +0 -6
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/BackGesture.android.kt +0 -12
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/OsFontScale.android.kt +0 -7
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +0 -153
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/ReservedRegions.android.kt +0 -6
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +0 -110
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/DesignSystemKits.kt +0 -62
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +0 -1071
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Localize.kt +0 -269
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/WidgetContainer.kt +0 -65
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Avatar.kt +0 -157
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +0 -81
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeDot.kt +0 -32
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +0 -338
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/BaselineView.kt +0 -175
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +0 -357
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +0 -115
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +0 -89
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +0 -128
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Collapse.kt +0 -209
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +0 -542
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +0 -49
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +0 -86
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +0 -125
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +0 -231
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +0 -189
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +0 -478
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +0 -114
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +0 -265
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +0 -235
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +0 -223
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +0 -265
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +0 -185
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +0 -343
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Loader.kt +0 -108
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +0 -56
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +0 -41
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +0 -84
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +0 -40
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +0 -361
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +0 -108
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +0 -308
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +0 -59
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Rating.kt +0 -87
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/ScaleSizeScope.kt +0 -21
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +0 -90
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +0 -330
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Stepper.kt +0 -220
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Steps.kt +0 -473
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/SuggestAction.kt +0 -122
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +0 -207
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +0 -92
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +0 -560
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +0 -86
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +0 -125
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +0 -207
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +0 -501
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +0 -176
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Uploader.kt +0 -182
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +0 -193
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +0 -29
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +0 -236
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +0 -180
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +0 -325
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/HostFont.kt +0 -17
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Radius.kt +0 -12
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Spacing.kt +0 -16
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +0 -194
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +0 -287
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +0 -54
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +0 -50
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/ScreenLayout.kt +0 -306
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +0 -41
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +0 -52
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +0 -66
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Conditional.kt +0 -11
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/DeprecatedModifier.kt +0 -104
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +0 -48
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Size.kt +0 -51
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +0 -338
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/HostBackGestureBridge.kt +0 -58
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +0 -137
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +0 -116
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +0 -215
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +0 -404
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ScrollToTop.kt +0 -8
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +0 -732
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackTransition.kt +0 -148
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/SwipeBackHaptic.kt +0 -40
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +0 -233
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +0 -217
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +0 -86
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoBarCompression.kt +0 -97
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoTabBar.kt +0 -377
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +0 -169
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +0 -455
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +0 -103
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderJourney.kt +0 -106
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderLocation.kt +0 -74
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +0 -413
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +0 -39
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +0 -363
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HelpCenterRequest.kt +0 -37
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SideBar.kt +0 -192
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +0 -133
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/ToolkitRequests.kt +0 -41
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/tracking/ScreenTracker.kt +0 -135
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/BackGesture.kt +0 -12
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ComposeLottieAnimation.kt +0 -91
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/OsFontScale.kt +0 -9
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +0 -74
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ReservedRegions.kt +0 -20
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Icons.kt +0 -1329
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +0 -55
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Tracking.kt +0 -15
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +0 -118
- package/compose/src/commonTest/kotlin/vn/momo/kits/navigation/component/HelpCenterRequestTest.kt +0 -44
- package/compose/src/commonTest/kotlin/vn/momo/kits/navigation/component/ToolkitRequestsTest.kt +0 -62
- package/compose/src/iosMain/kotlin/vn/momo/kits/const/HostFont.ios.kt +0 -42
- package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/HostBackGesture.ios.kt +0 -45
- package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/ScrollToTop.ios.kt +0 -34
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/BackGesture.ios.kt +0 -38
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/OsFontScale.ios.kt +0 -66
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +0 -217
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/ReservedRegions.ios.kt +0 -138
- package/gradle/libs.versions.toml +0 -68
- package/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/gradle/wrapper/gradle-wrapper.properties +0 -8
- package/gradle.properties +0 -26
- package/gradlew +0 -252
- package/gradlew.bat +0 -94
- package/ios-demo/README.md +0 -120
- 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
|
-
|