@momo-kits/native-kits 0.156.6-beta.4 → 0.156.6-dialog.1-debug
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build.gradle.kts +11 -0
- package/compose/MoMoComposeKits.podspec +54 -0
- package/compose/build.gradle.kts +183 -0
- package/compose/build.gradle.kts.backup +183 -0
- package/compose/compose.podspec +48 -0
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +270 -0
- package/compose/src/commonMain/composeResources/font/momosignature.otf +0 -0
- package/compose/src/commonMain/composeResources/font/momotrustdisplay.otf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_black.otf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_black.ttf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_bold.ttf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_heavy.ttf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_light.ttf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_medium.ttf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_regular.ttf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_semibold.ttf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_thin.otf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_thin.ttf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_ultralight.otf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_ultralight.ttf +0 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +57 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +95 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +201 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +222 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +48 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +86 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +76 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +76 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +305 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +33 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +720 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +121 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +405 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +69 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +83 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeDot.kt +32 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +340 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +348 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +94 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +136 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +543 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +23 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +63 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +148 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +185 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +116 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +447 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +172 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +255 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +231 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +234 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +254 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +241 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +364 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +56 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +41 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +92 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +40 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +352 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +103 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +70 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/ScaleSizeScope.kt +17 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +96 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +96 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +92 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +130 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +214 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +177 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +205 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +29 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +239 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +191 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +306 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Radius.kt +12 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Spacing.kt +13 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +189 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +285 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +2 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +35 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +2 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +59 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +68 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Conditional.kt +11 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/DeprecatedModifier.kt +14 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +50 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Size.kt +51 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +238 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +118 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +98 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +209 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +227 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +484 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +162 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +226 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +86 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +187 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +277 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +80 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +306 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +31 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +370 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +129 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +63 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Icons.kt +1329 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +62 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +88 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +325 -0
- package/gradle/libs.versions.toml +57 -0
- package/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/gradle/wrapper/gradle-wrapper.properties +8 -0
- package/gradle.properties +26 -0
- package/gradlew +252 -0
- package/gradlew.bat +94 -0
- package/ios/Image/Image.swift +1 -5
- package/local.properties +8 -0
- package/package.json +1 -1
- package/settings.gradle.kts +52 -0
- package/example/ios/Example.xcodeproj/xcuserdata/sophia.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
- package/example/ios/Example.xcworkspace/xcuserdata/sophia.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/example/ios/Example.xcworkspace/xcuserdata/sophia.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +0 -6
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sophia.xcuserdatad/xcschemes/MoMoUIKits.xcscheme +0 -58
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sophia.xcuserdatad/xcschemes/Pods-Example.xcscheme +0 -58
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sophia.xcuserdatad/xcschemes/SDWebImage.xcscheme +0 -58
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sophia.xcuserdatad/xcschemes/SDWebImageSwiftUI.xcscheme +0 -58
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sophia.xcuserdatad/xcschemes/SkeletonUI.xcscheme +0 -58
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sophia.xcuserdatad/xcschemes/lottie-ios-LottiePrivacyInfo.xcscheme +0 -58
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sophia.xcuserdatad/xcschemes/lottie-ios.xcscheme +0 -58
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sophia.xcuserdatad/xcschemes/xcschememanagement.plist +0 -46
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
package vn.momo.kits.navigation
|
|
2
|
+
|
|
3
|
+
import androidx.compose.foundation.layout.Box
|
|
4
|
+
import androidx.compose.foundation.layout.fillMaxSize
|
|
5
|
+
import androidx.compose.runtime.Composable
|
|
6
|
+
import androidx.compose.runtime.staticCompositionLocalOf
|
|
7
|
+
import androidx.compose.ui.Alignment
|
|
8
|
+
import androidx.compose.ui.Modifier
|
|
9
|
+
import androidx.navigation.NavController
|
|
10
|
+
import kotlinx.coroutines.CoroutineScope
|
|
11
|
+
import kotlinx.coroutines.Dispatchers
|
|
12
|
+
import kotlinx.coroutines.SupervisorJob
|
|
13
|
+
import kotlinx.coroutines.cancel
|
|
14
|
+
import kotlinx.coroutines.delay
|
|
15
|
+
import kotlinx.coroutines.launch
|
|
16
|
+
import kotlinx.serialization.Serializable
|
|
17
|
+
import vn.momo.kits.components.PopupAction
|
|
18
|
+
import vn.momo.kits.components.PopupNotify
|
|
19
|
+
import vn.momo.kits.navigation.BottomHeader.*
|
|
20
|
+
import vn.momo.kits.navigation.component.SnackBar
|
|
21
|
+
import vn.momo.maxapi.IMaxApi
|
|
22
|
+
|
|
23
|
+
class Navigator(
|
|
24
|
+
private val navController: NavController,
|
|
25
|
+
private val maxApi: IMaxApi?
|
|
26
|
+
) {
|
|
27
|
+
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
|
28
|
+
|
|
29
|
+
fun push(content: @Composable () -> Unit, options: NavigationOptions? = null) {
|
|
30
|
+
val route = DynamicScreenRegistry.register(content, options)
|
|
31
|
+
navController.navigate(DynamicScreenRoute(route.id))
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
fun replace(content: @Composable () -> Unit, options: NavigationOptions? = null) {
|
|
35
|
+
if (navController.previousBackStackEntry != null){
|
|
36
|
+
val latestScreen = DynamicScreenRegistry.getLatestScreen()
|
|
37
|
+
latestScreen?.let { DynamicScreenRegistry.unregisterScreen(it.id) }
|
|
38
|
+
navController.popBackStack()
|
|
39
|
+
push(content, options)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
fun onBackSafe(callBack: (() -> Unit)? = null) {
|
|
44
|
+
val latestScreen = DynamicScreenRegistry.getLatestScreen()
|
|
45
|
+
val options = latestScreen?.options
|
|
46
|
+
if (options?.onBackHandler != null) {
|
|
47
|
+
options.onBackHandler.invoke()
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
val preventBack = options?.headerBackProps?.preventBack
|
|
51
|
+
if (preventBack != null) {
|
|
52
|
+
showModal(
|
|
53
|
+
content = {
|
|
54
|
+
PopupNotify(preventBack.copy(
|
|
55
|
+
primary = PopupAction(
|
|
56
|
+
title = preventBack.primary?.title ?: "",
|
|
57
|
+
onPress = {
|
|
58
|
+
preventBack.primary?.onPress?.invoke()
|
|
59
|
+
pop(2, callBack) // close popup notify, then close the screen
|
|
60
|
+
}
|
|
61
|
+
)
|
|
62
|
+
))
|
|
63
|
+
}
|
|
64
|
+
)
|
|
65
|
+
} else {
|
|
66
|
+
pop(1, callBack)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
fun pop(count: Int = 1, callBack: (() -> Unit)? = null) {
|
|
71
|
+
scope.launch {
|
|
72
|
+
repeat(count) {
|
|
73
|
+
dismissEntry()
|
|
74
|
+
}
|
|
75
|
+
callBack?.invoke()
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private suspend fun dismissEntry() {
|
|
80
|
+
if (navController.previousBackStackEntry != null) {
|
|
81
|
+
navController.popBackStack()
|
|
82
|
+
delay(300L)
|
|
83
|
+
DynamicScreenRegistry.getLatestScreen()?.let {
|
|
84
|
+
DynamicScreenRegistry.unregisterScreen(it.id)
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
maxApi?.dismiss { }
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
fun present(content: @Composable () -> Unit, options: NavigationOptions? = null) {
|
|
92
|
+
val route = DynamicScreenRegistry.register(content, options)
|
|
93
|
+
navController.navigate(DynamicDialogRoute(route.id))
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
fun reset(content: @Composable () -> Unit, options: NavigationOptions? = null) {
|
|
97
|
+
DynamicScreenRegistry.unregisterAll()
|
|
98
|
+
|
|
99
|
+
val route = DynamicScreenRegistry.register(content, options)
|
|
100
|
+
navController.navigate(DynamicScreenRoute(route.id)) {
|
|
101
|
+
popUpTo(0) { inclusive = true }
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
fun showModal(
|
|
106
|
+
content: @Composable () -> Unit,
|
|
107
|
+
barrierDismissible: Boolean = true,
|
|
108
|
+
onDismiss: (() -> Unit)? = null
|
|
109
|
+
){
|
|
110
|
+
val route = DynamicScreenRegistry.register({
|
|
111
|
+
ModalScreen(
|
|
112
|
+
content = content,
|
|
113
|
+
barrierDismissible = barrierDismissible,
|
|
114
|
+
onDismiss = onDismiss
|
|
115
|
+
)
|
|
116
|
+
}, null)
|
|
117
|
+
navController.navigate(DynamicModalRoute(route.id))
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
fun showBottomSheet(
|
|
121
|
+
content: @Composable () -> Unit,
|
|
122
|
+
isSurface: Boolean = false,
|
|
123
|
+
barrierDismissible: Boolean = true,
|
|
124
|
+
onDismiss: (() -> Unit)? = null,
|
|
125
|
+
bottomSheetHeader: BottomHeader? = null
|
|
126
|
+
){
|
|
127
|
+
val route = DynamicScreenRegistry.register({
|
|
128
|
+
BottomSheet(
|
|
129
|
+
content = content,
|
|
130
|
+
header = bottomSheetHeader ?: Title(),
|
|
131
|
+
isSurface = isSurface,
|
|
132
|
+
barrierDismissible = barrierDismissible,
|
|
133
|
+
onDismiss = onDismiss
|
|
134
|
+
)
|
|
135
|
+
}, null)
|
|
136
|
+
navController.navigate(DynamicBottomSheetRoute(route.id))
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
fun showSnackBar(snackBar: SnackBar, onDismiss: (() -> Unit)? = null) {
|
|
140
|
+
val capturedSnackBar = snackBar
|
|
141
|
+
val capturedOnDismiss = onDismiss
|
|
142
|
+
val route = DynamicScreenRegistry.register({
|
|
143
|
+
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.BottomCenter) {
|
|
144
|
+
SnackBar(capturedSnackBar, capturedOnDismiss)
|
|
145
|
+
}
|
|
146
|
+
}, null)
|
|
147
|
+
navController.navigate(DynamicSnackBarRoute(route.id))
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
fun hideSnackBar() {
|
|
151
|
+
scope.launch {
|
|
152
|
+
if (navController.previousBackStackEntry != null) {
|
|
153
|
+
navController.popBackStack()
|
|
154
|
+
delay(250L)
|
|
155
|
+
DynamicScreenRegistry.getLatestScreen()?.let {
|
|
156
|
+
DynamicScreenRegistry.unregisterScreen(it.id)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
fun dispose(){
|
|
164
|
+
scope.cancel()
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
val LocalNavigator = staticCompositionLocalOf<Navigator> {
|
|
169
|
+
error("No NavigationStack provided")
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
@Serializable
|
|
173
|
+
data class DynamicScreenRoute(val id: Int)
|
|
174
|
+
|
|
175
|
+
@Serializable
|
|
176
|
+
data class DynamicDialogRoute(val id: Int)
|
|
177
|
+
|
|
178
|
+
@Serializable
|
|
179
|
+
data class DynamicBottomSheetRoute(val id: Int)
|
|
180
|
+
|
|
181
|
+
@Serializable
|
|
182
|
+
data class DynamicModalRoute(val id: Int)
|
|
183
|
+
|
|
184
|
+
@Serializable
|
|
185
|
+
data class DynamicSnackBarRoute(val id: Int)
|
|
186
|
+
|
|
187
|
+
data class DynamicScreen(
|
|
188
|
+
val id: Int,
|
|
189
|
+
val content: @Composable () -> Unit,
|
|
190
|
+
var options: NavigationOptions? = null
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
object DynamicScreenRegistry {
|
|
194
|
+
private val screens = mutableMapOf<Int, DynamicScreen>()
|
|
195
|
+
private var idCounter = 1
|
|
196
|
+
|
|
197
|
+
fun register(content: @Composable () -> Unit, options: NavigationOptions?): DynamicScreenRoute {
|
|
198
|
+
val id = idCounter++
|
|
199
|
+
screens[id] = DynamicScreen(
|
|
200
|
+
id = id,
|
|
201
|
+
content = content,
|
|
202
|
+
options = options
|
|
203
|
+
)
|
|
204
|
+
return DynamicScreenRoute(id)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
fun unregisterScreen(id: Int) {
|
|
208
|
+
screens.remove(id)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
fun getLatestScreen(): DynamicScreen?{
|
|
212
|
+
return screens.entries.lastOrNull()?.value
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
fun unregisterAll(){
|
|
216
|
+
val ids = screens.keys.toList()
|
|
217
|
+
for (id in ids) {
|
|
218
|
+
unregisterScreen(id)
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
fun getScreen(id: Int): DynamicScreen? = screens[id]
|
|
223
|
+
|
|
224
|
+
fun setOptions(id: Int, options: NavigationOptions){
|
|
225
|
+
screens[id]?.options = options
|
|
226
|
+
}
|
|
227
|
+
}
|
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
package vn.momo.kits.navigation
|
|
2
|
+
|
|
3
|
+
import androidx.compose.animation.animateColorAsState
|
|
4
|
+
import androidx.compose.animation.core.animateDpAsState
|
|
5
|
+
import androidx.compose.foundation.ScrollState
|
|
6
|
+
import androidx.compose.foundation.background
|
|
7
|
+
import androidx.compose.foundation.border
|
|
8
|
+
import androidx.compose.foundation.gestures.ScrollableState
|
|
9
|
+
import androidx.compose.foundation.layout.Box
|
|
10
|
+
import androidx.compose.foundation.layout.Column
|
|
11
|
+
import androidx.compose.foundation.layout.ColumnScope
|
|
12
|
+
import androidx.compose.foundation.layout.Spacer
|
|
13
|
+
import androidx.compose.foundation.layout.WindowInsets
|
|
14
|
+
import androidx.compose.foundation.layout.aspectRatio
|
|
15
|
+
import androidx.compose.foundation.layout.fillMaxSize
|
|
16
|
+
import androidx.compose.foundation.layout.fillMaxWidth
|
|
17
|
+
import androidx.compose.foundation.layout.height
|
|
18
|
+
import androidx.compose.foundation.layout.ime
|
|
19
|
+
import androidx.compose.foundation.layout.imePadding
|
|
20
|
+
import androidx.compose.foundation.layout.offset
|
|
21
|
+
import androidx.compose.foundation.layout.padding
|
|
22
|
+
import androidx.compose.foundation.lazy.LazyListState
|
|
23
|
+
import androidx.compose.foundation.lazy.rememberLazyListState
|
|
24
|
+
import androidx.compose.foundation.rememberScrollState
|
|
25
|
+
import androidx.compose.foundation.verticalScroll
|
|
26
|
+
import androidx.compose.material.ExperimentalMaterialApi
|
|
27
|
+
import androidx.compose.runtime.Composable
|
|
28
|
+
import androidx.compose.runtime.CompositionLocalProvider
|
|
29
|
+
import androidx.compose.runtime.LaunchedEffect
|
|
30
|
+
import androidx.compose.runtime.getValue
|
|
31
|
+
import androidx.compose.runtime.mutableIntStateOf
|
|
32
|
+
import androidx.compose.runtime.mutableStateOf
|
|
33
|
+
import androidx.compose.runtime.remember
|
|
34
|
+
import androidx.compose.runtime.snapshotFlow
|
|
35
|
+
import androidx.compose.runtime.staticCompositionLocalOf
|
|
36
|
+
import androidx.compose.ui.Alignment
|
|
37
|
+
import androidx.compose.ui.Modifier
|
|
38
|
+
import androidx.compose.ui.graphics.Brush
|
|
39
|
+
import androidx.compose.ui.graphics.Color
|
|
40
|
+
import androidx.compose.ui.graphics.lerp
|
|
41
|
+
import androidx.compose.ui.layout.onGloballyPositioned
|
|
42
|
+
import androidx.compose.ui.platform.LocalDensity
|
|
43
|
+
import androidx.compose.ui.unit.Dp
|
|
44
|
+
import androidx.compose.ui.unit.dp
|
|
45
|
+
import androidx.compose.ui.unit.min
|
|
46
|
+
import androidx.compose.ui.zIndex
|
|
47
|
+
import vn.momo.kits.components.InputSearch
|
|
48
|
+
import vn.momo.kits.const.AppNavigationBar
|
|
49
|
+
import vn.momo.kits.const.AppStatusBar
|
|
50
|
+
import vn.momo.kits.const.AppTheme
|
|
51
|
+
import vn.momo.kits.application.IsShowBaseLineDebug
|
|
52
|
+
import vn.momo.kits.const.Colors
|
|
53
|
+
import vn.momo.kits.const.Spacing
|
|
54
|
+
import vn.momo.kits.modifier.conditional
|
|
55
|
+
import vn.momo.kits.modifier.hideKeyboardOnTap
|
|
56
|
+
import vn.momo.kits.navigation.component.FABPosition
|
|
57
|
+
import vn.momo.kits.navigation.component.FloatingButton
|
|
58
|
+
import vn.momo.kits.navigation.component.HEADER_HEIGHT
|
|
59
|
+
import vn.momo.kits.navigation.component.Header
|
|
60
|
+
import vn.momo.kits.navigation.component.HeaderBackground
|
|
61
|
+
import vn.momo.kits.navigation.component.HeaderRight
|
|
62
|
+
import vn.momo.kits.navigation.component.HeaderType
|
|
63
|
+
import vn.momo.kits.navigation.component.InputSearchType
|
|
64
|
+
import vn.momo.kits.platform.BackHandler
|
|
65
|
+
import vn.momo.kits.platform.getAndroidBuildVersion
|
|
66
|
+
|
|
67
|
+
internal val LocalFooterHeightPx = staticCompositionLocalOf { mutableIntStateOf(0) }
|
|
68
|
+
internal val LocalHeaderRightWidthPx = staticCompositionLocalOf { mutableIntStateOf(0) }
|
|
69
|
+
|
|
70
|
+
@OptIn(ExperimentalMaterialApi::class)
|
|
71
|
+
@Composable
|
|
72
|
+
internal fun StackScreen(
|
|
73
|
+
content: @Composable () -> Unit,
|
|
74
|
+
navigationOptions: NavigationOptions? = null,
|
|
75
|
+
id: Int = -1,
|
|
76
|
+
bottomTabIndex: Int = -1,
|
|
77
|
+
onBackHandler: (() -> Unit)? = null,
|
|
78
|
+
) {
|
|
79
|
+
val navigator = LocalNavigator.current
|
|
80
|
+
val statusBar = AppStatusBar.current
|
|
81
|
+
val density = LocalDensity.current
|
|
82
|
+
val navigation = remember { Navigation(id = id, bottomTabIndex = bottomTabIndex, initOptions = navigationOptions) }
|
|
83
|
+
|
|
84
|
+
val options by navigation.currentOptions
|
|
85
|
+
|
|
86
|
+
val limit = with(density) {
|
|
87
|
+
(statusBar).toPx() + HEADER_HEIGHT
|
|
88
|
+
}.toInt()
|
|
89
|
+
|
|
90
|
+
val (scrollState, scrollInProcess) = if (options.scrollData.scrollState is LazyListState)
|
|
91
|
+
(options.scrollData.scrollState as? LazyListState ?: rememberLazyListState()).proxyScrollState(limit, 15)
|
|
92
|
+
else
|
|
93
|
+
(options.scrollData.scrollState as? ScrollState ?: rememberScrollState()).proxyLimitedScrollState(limit, 15)
|
|
94
|
+
|
|
95
|
+
val footerHeightPx = remember { mutableIntStateOf(0) }
|
|
96
|
+
val headerRightWidthPx = remember { mutableIntStateOf(0) }
|
|
97
|
+
|
|
98
|
+
BackHandler(true) { navigator.onBackSafe() }
|
|
99
|
+
|
|
100
|
+
CompositionLocalProvider(
|
|
101
|
+
StackScreenScrollableState provides options.scrollData.scrollState,
|
|
102
|
+
LocalNavigation provides navigation,
|
|
103
|
+
LocalScrollState provides scrollState,
|
|
104
|
+
LocalOptions provides options,
|
|
105
|
+
LocalFooterHeightPx provides footerHeightPx,
|
|
106
|
+
LocalHeaderRightWidthPx provides headerRightWidthPx
|
|
107
|
+
) {
|
|
108
|
+
Box(Modifier
|
|
109
|
+
.fillMaxSize()
|
|
110
|
+
.background(options.backgroundColor ?: AppTheme.current.colors.background.default)
|
|
111
|
+
.conditional(options.keyboardOptions.keyboardShouldPersistTaps) {
|
|
112
|
+
hideKeyboardOnTap()
|
|
113
|
+
}
|
|
114
|
+
.conditional(options.keyboardOptions.useAvoidKeyboard && getAndroidBuildVersion() > 29) {
|
|
115
|
+
imePadding()
|
|
116
|
+
}
|
|
117
|
+
) {
|
|
118
|
+
Box(Modifier.zIndex(1f)) {
|
|
119
|
+
HeaderBackground()
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
Box(Modifier.zIndex(5f)) {
|
|
123
|
+
Header(onBackHandler)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
Column (Modifier.zIndex(6f)) {
|
|
127
|
+
SearchAnimated(isScrollInProgress = scrollInProcess)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
Column(Modifier.zIndex(2f).fillMaxSize()) {
|
|
131
|
+
MainContent(content = content)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
Box(Modifier.zIndex(4f).align(Alignment.BottomCenter)) {
|
|
135
|
+
FooterContent()
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
Box(Modifier.zIndex(7f)){
|
|
139
|
+
FloatingContent()
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
@Composable
|
|
146
|
+
fun FloatingContent(){
|
|
147
|
+
val options = LocalOptions.current
|
|
148
|
+
val density = LocalDensity.current
|
|
149
|
+
val footerHeightPx = LocalFooterHeightPx.current
|
|
150
|
+
val scrollState = LocalScrollState.current
|
|
151
|
+
|
|
152
|
+
val fabProps = options.floatingButtonProps ?: return
|
|
153
|
+
val bottomPadding = fabProps.bottom ?:
|
|
154
|
+
(Spacing.M + if (options.footerComponent != null) with(density){ footerHeightPx.intValue.toDp() } else 0.dp)
|
|
155
|
+
|
|
156
|
+
FloatingButton(
|
|
157
|
+
scrollPosition = fabProps.scrollState?.value ?: scrollState.value,
|
|
158
|
+
onClick = fabProps.onClick,
|
|
159
|
+
containerColor = AppTheme.current.colors.primary,
|
|
160
|
+
bottom = bottomPadding,
|
|
161
|
+
icon = fabProps.icon,
|
|
162
|
+
iconColor = fabProps.iconColor,
|
|
163
|
+
text = fabProps.label,
|
|
164
|
+
size = fabProps.size,
|
|
165
|
+
position = fabProps.position ?: FABPosition.END,
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
@Composable
|
|
170
|
+
fun ColumnScope.MainContent(content: @Composable ()-> Unit){
|
|
171
|
+
val options = LocalOptions.current
|
|
172
|
+
val inputSearchType = getInputSearchType(options)
|
|
173
|
+
val density = LocalDensity.current
|
|
174
|
+
val scrollState = LocalScrollState.current
|
|
175
|
+
|
|
176
|
+
Spacer(Modifier.height(
|
|
177
|
+
if (options.headerType is HeaderType.DefaultOrExtended || (options.headerType is HeaderType.Transparent && !options.headerType.isFullScreenContent))
|
|
178
|
+
AppStatusBar.current + HEADER_HEIGHT.dp else 0.dp)
|
|
179
|
+
)
|
|
180
|
+
if (inputSearchType == InputSearchType.Animated){
|
|
181
|
+
val scrollDp = with(density) { scrollState.value.toDp() }
|
|
182
|
+
|
|
183
|
+
val animatedTopPadding by animateDpAsState(
|
|
184
|
+
targetValue = (HEADER_HEIGHT.dp - scrollDp).coerceIn(0.dp, HEADER_HEIGHT.dp),
|
|
185
|
+
label = "AnimatedTopPadding"
|
|
186
|
+
)
|
|
187
|
+
Spacer(Modifier.height(animatedTopPadding))
|
|
188
|
+
}
|
|
189
|
+
Column (Modifier
|
|
190
|
+
.fillMaxWidth()
|
|
191
|
+
.weight(1f)
|
|
192
|
+
.conditional(options.scrollData.scrollable && options.scrollData.scrollState is ScrollState) {
|
|
193
|
+
verticalScroll(scrollState)
|
|
194
|
+
}
|
|
195
|
+
) {
|
|
196
|
+
ScreenContent(content = content)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (options.footerComponent != null){
|
|
200
|
+
val footerHeight = LocalFooterHeightPx.current
|
|
201
|
+
val footerHeightDp = with(density) { footerHeight.value.toDp() }
|
|
202
|
+
Spacer(Modifier.height(footerHeightDp))
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
@Composable
|
|
207
|
+
fun ScreenContent(content: @Composable () -> Unit){
|
|
208
|
+
val scrollState = LocalScrollState.current
|
|
209
|
+
val options = LocalOptions.current
|
|
210
|
+
|
|
211
|
+
if (options.headerType is HeaderType.Animated){
|
|
212
|
+
val animatedHeader = options.headerType
|
|
213
|
+
Box {
|
|
214
|
+
Box(Modifier.fillMaxWidth().aspectRatio(animatedHeader.aspectRatio.value)){
|
|
215
|
+
animatedHeader.composable.invoke(scrollState.value)
|
|
216
|
+
}
|
|
217
|
+
Box(Modifier.offset(x = 0.dp, y = AppStatusBar.current + HEADER_HEIGHT.dp + animatedHeader.layoutOffSet)){
|
|
218
|
+
content()
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
} else {
|
|
222
|
+
content()
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
@Composable
|
|
227
|
+
fun FooterContent(){
|
|
228
|
+
val options = LocalOptions.current
|
|
229
|
+
if (options.footerComponent != null){
|
|
230
|
+
val ime = WindowInsets.ime
|
|
231
|
+
val density = LocalDensity.current
|
|
232
|
+
val imeBottom = ime.getBottom(density)
|
|
233
|
+
val thresholdPx = with(density) { 50.dp.toPx() }
|
|
234
|
+
val isKeyboardVisible = imeBottom > thresholdPx
|
|
235
|
+
val bottomPadding = min(AppNavigationBar.current, if (isKeyboardVisible) 0.dp else 21.dp)
|
|
236
|
+
Footer(footerComponent = options.footerComponent, bottomPadding = bottomPadding)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
@Composable
|
|
243
|
+
fun Footer(footerComponent: @Composable (() -> Unit)?, bottomPadding: Dp) {
|
|
244
|
+
if (footerComponent == null) return
|
|
245
|
+
|
|
246
|
+
val footerHeightPx = LocalFooterHeightPx.current
|
|
247
|
+
|
|
248
|
+
val shadowBrush = remember {
|
|
249
|
+
Brush.verticalGradient(
|
|
250
|
+
colors = listOf(Color.Transparent, Color.Black.copy(alpha = 0.05f))
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
Box(Modifier.onGloballyPositioned {
|
|
255
|
+
if (footerHeightPx.intValue != it.size.height) footerHeightPx.intValue = it.size.height
|
|
256
|
+
}) {
|
|
257
|
+
Box(Modifier
|
|
258
|
+
.fillMaxWidth()
|
|
259
|
+
.background(AppTheme.current.colors.background.surface)
|
|
260
|
+
.conditional(IsShowBaseLineDebug) {
|
|
261
|
+
border(1.dp, Colors.blue_03)
|
|
262
|
+
}
|
|
263
|
+
.padding(top = Spacing.S, start = Spacing.M, end = Spacing.M, bottom = bottomPadding + Spacing.S)
|
|
264
|
+
){
|
|
265
|
+
footerComponent.invoke()
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
Box(modifier = Modifier
|
|
269
|
+
.fillMaxWidth()
|
|
270
|
+
.height(6.dp)
|
|
271
|
+
.offset(x = 0.dp, y = (-6).dp)
|
|
272
|
+
.background(shadowBrush)
|
|
273
|
+
)
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
data class InputSearchLayoutParams(
|
|
278
|
+
val topPadding: Dp,
|
|
279
|
+
val startPadding: Dp,
|
|
280
|
+
val endPadding: Dp
|
|
281
|
+
)
|
|
282
|
+
@Composable
|
|
283
|
+
fun SearchAnimated(
|
|
284
|
+
isScrollInProgress: Boolean
|
|
285
|
+
) {
|
|
286
|
+
val scrollState = LocalScrollState.current
|
|
287
|
+
val options = LocalOptions.current
|
|
288
|
+
val navigator = LocalNavigator.current
|
|
289
|
+
val density = LocalDensity.current
|
|
290
|
+
|
|
291
|
+
val scrollDp = with(density) { scrollState.value.toDp() }
|
|
292
|
+
|
|
293
|
+
val inputSearchType = getInputSearchType(options)
|
|
294
|
+
val headerRightWidthPx = LocalHeaderRightWidthPx.current
|
|
295
|
+
val headerRightWidthDp = with(density) { headerRightWidthPx.intValue.toDp() }
|
|
296
|
+
|
|
297
|
+
if (inputSearchType == InputSearchType.None) return
|
|
298
|
+
val inputSearchProps = (options.headerType as? HeaderType.DefaultOrExtended)?.inputSearchProps ?: return
|
|
299
|
+
|
|
300
|
+
val minTopPadding = AppStatusBar.current
|
|
301
|
+
val maxTopPadding = AppStatusBar.current + HEADER_HEIGHT.dp
|
|
302
|
+
val minStartPadding = Spacing.M
|
|
303
|
+
val maxStartPadding = if (options.hiddenBack) Spacing.M else 52.dp
|
|
304
|
+
val minEndPadding = Spacing.M
|
|
305
|
+
val maxEndPadding = headerRightWidthDp + if (options.headerRight is HeaderRight.None) Spacing.M else Spacing.M * 2
|
|
306
|
+
|
|
307
|
+
val targetColor = lerp(
|
|
308
|
+
AppTheme.current.colors.background.surface,
|
|
309
|
+
AppTheme.current.colors.background.default,
|
|
310
|
+
(scrollDp / minTopPadding).coerceIn(0f, 1f)
|
|
311
|
+
)
|
|
312
|
+
val animatedColor by animateColorAsState(targetValue = targetColor, label = "BackgroundColor")
|
|
313
|
+
|
|
314
|
+
val layoutParams = when (inputSearchType) {
|
|
315
|
+
InputSearchType.Header -> {
|
|
316
|
+
InputSearchLayoutParams(
|
|
317
|
+
topPadding = minTopPadding,
|
|
318
|
+
startPadding = maxStartPadding,
|
|
319
|
+
endPadding = maxEndPadding
|
|
320
|
+
)
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
InputSearchType.Animated -> {
|
|
324
|
+
val animatedTopPadding by animateDpAsState(
|
|
325
|
+
targetValue = (maxTopPadding - scrollDp).coerceIn(minTopPadding, maxTopPadding),
|
|
326
|
+
label = "AnimatedTopPadding"
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
val animatedStartPadding by animateDpAsState(
|
|
330
|
+
targetValue = (minStartPadding + scrollDp).coerceIn(minStartPadding, maxStartPadding),
|
|
331
|
+
label = "AnimatedStartPadding"
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
val animatedEndPadding by animateDpAsState(
|
|
335
|
+
targetValue = (minEndPadding + scrollDp).coerceIn(minEndPadding, maxEndPadding),
|
|
336
|
+
label = "AnimatedEndPadding"
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
val maxPadding = remember(maxTopPadding, maxStartPadding, maxEndPadding) {
|
|
340
|
+
maxOf(maxEndPadding, maxStartPadding, maxTopPadding)
|
|
341
|
+
}
|
|
342
|
+
val snapScrollValue = with(density) { maxPadding.toPx().toInt() }
|
|
343
|
+
|
|
344
|
+
LaunchedEffect(isScrollInProgress && scrollState.value != 0 && scrollState.value != snapScrollValue) {
|
|
345
|
+
if (scrollDp < maxPadding) {
|
|
346
|
+
if (!isScrollInProgress) {
|
|
347
|
+
val midpoint = (maxTopPadding - minTopPadding) / 2
|
|
348
|
+
|
|
349
|
+
if (scrollDp < midpoint) {
|
|
350
|
+
scrollState.animateScrollTo(0)
|
|
351
|
+
} else {
|
|
352
|
+
scrollState.animateScrollTo(snapScrollValue)
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
InputSearchLayoutParams(
|
|
359
|
+
topPadding = animatedTopPadding,
|
|
360
|
+
startPadding = animatedStartPadding,
|
|
361
|
+
endPadding = animatedEndPadding
|
|
362
|
+
)
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
InputSearchType.None -> return
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
Spacer(Modifier.height(layoutParams.topPadding))
|
|
369
|
+
Box(
|
|
370
|
+
modifier = Modifier
|
|
371
|
+
.height(HEADER_HEIGHT.dp)
|
|
372
|
+
.fillMaxWidth()
|
|
373
|
+
.padding(
|
|
374
|
+
start = layoutParams.startPadding,
|
|
375
|
+
end = layoutParams.endPadding
|
|
376
|
+
),
|
|
377
|
+
contentAlignment = Alignment.Center
|
|
378
|
+
) {
|
|
379
|
+
InputSearch(
|
|
380
|
+
inputSearchProps = inputSearchProps.copy(
|
|
381
|
+
backgroundColor = animatedColor,
|
|
382
|
+
onPressButtonText = {
|
|
383
|
+
navigator.pop()
|
|
384
|
+
}
|
|
385
|
+
)
|
|
386
|
+
)
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
@Composable
|
|
391
|
+
internal fun isKeyboardVisible(): Boolean {
|
|
392
|
+
val ime = WindowInsets.ime
|
|
393
|
+
val density = LocalDensity.current
|
|
394
|
+
return ime.getBottom(density) > 0
|
|
395
|
+
}
|
|
396
|
+
private fun quantize(value: Int, stepPx: Int): Int {
|
|
397
|
+
if (stepPx <= 1) return value
|
|
398
|
+
return (value / stepPx) * stepPx
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
@Composable
|
|
402
|
+
fun LazyListState.proxyScrollState(
|
|
403
|
+
thresholdPx: Int = 200,
|
|
404
|
+
stepPx: Int = 4
|
|
405
|
+
): Pair<ScrollState, Boolean> {
|
|
406
|
+
val scrollState = rememberScrollState()
|
|
407
|
+
val locked = remember { mutableStateOf(false) }
|
|
408
|
+
|
|
409
|
+
LaunchedEffect(this, scrollState, thresholdPx, stepPx) {
|
|
410
|
+
snapshotFlow { firstVisibleItemIndex to firstVisibleItemScrollOffset }
|
|
411
|
+
.collect { (index, offset) ->
|
|
412
|
+
val rawTarget = if (index == 0) offset else SCROLLED_PAST_FIRST_ITEM_THRESHOLD
|
|
413
|
+
val shouldLock = rawTarget > thresholdPx
|
|
414
|
+
if (locked.value != shouldLock) locked.value = shouldLock
|
|
415
|
+
|
|
416
|
+
val desired = if (shouldLock) thresholdPx else quantize(rawTarget, stepPx)
|
|
417
|
+
|
|
418
|
+
if (scrollState.value != desired) {
|
|
419
|
+
scrollState.scrollTo(desired.coerceAtLeast(0))
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
val inProgress = remember { mutableStateOf(false) }
|
|
425
|
+
LaunchedEffect(this, thresholdPx) {
|
|
426
|
+
snapshotFlow { isScrollInProgress }
|
|
427
|
+
.collect { progressing ->
|
|
428
|
+
inProgress.value = if (locked.value) false else progressing
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
return scrollState to inProgress.value
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
@Composable
|
|
436
|
+
fun ScrollState.proxyLimitedScrollState(
|
|
437
|
+
thresholdPx: Int = 200,
|
|
438
|
+
stepPx: Int = 4
|
|
439
|
+
): Pair<ScrollState, Boolean> {
|
|
440
|
+
val proxy = rememberScrollState()
|
|
441
|
+
val locked = remember { mutableStateOf(false) }
|
|
442
|
+
|
|
443
|
+
LaunchedEffect(this, proxy, thresholdPx, stepPx) {
|
|
444
|
+
snapshotFlow { value }
|
|
445
|
+
.collect { rawTarget ->
|
|
446
|
+
val shouldLock = rawTarget > thresholdPx
|
|
447
|
+
if (locked.value != shouldLock) locked.value = shouldLock
|
|
448
|
+
|
|
449
|
+
val desired = if (shouldLock) thresholdPx else quantize(rawTarget, stepPx)
|
|
450
|
+
|
|
451
|
+
if (proxy.value != desired) {
|
|
452
|
+
proxy.scrollTo(desired.coerceAtLeast(0))
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
val inProgress = remember { mutableStateOf(false) }
|
|
458
|
+
LaunchedEffect(this, thresholdPx) {
|
|
459
|
+
snapshotFlow { isScrollInProgress }
|
|
460
|
+
.collect { progressing ->
|
|
461
|
+
inProgress.value = if (locked.value) false else progressing
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
return proxy to inProgress.value
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
private const val SCROLLED_PAST_FIRST_ITEM_THRESHOLD = 10_000
|
|
469
|
+
|
|
470
|
+
internal val LocalScrollState = staticCompositionLocalOf<ScrollState> { error("No Scroll State provided") }
|
|
471
|
+
internal val LocalOptions = staticCompositionLocalOf<NavigationOptions> { error("No NavigationOptions provided") }
|
|
472
|
+
|
|
473
|
+
val StackScreenScrollableState = staticCompositionLocalOf<ScrollableState?> { null }
|
|
474
|
+
|
|
475
|
+
internal fun getInputSearchType(options: NavigationOptions): InputSearchType{
|
|
476
|
+
return when (val headerType = options.headerType) {
|
|
477
|
+
is HeaderType.DefaultOrExtended -> when {
|
|
478
|
+
headerType.inputSearchProps == null -> InputSearchType.None
|
|
479
|
+
headerType.useAnimated -> InputSearchType.Animated
|
|
480
|
+
else -> InputSearchType.Header
|
|
481
|
+
}
|
|
482
|
+
else -> InputSearchType.None
|
|
483
|
+
}
|
|
484
|
+
}
|