@momo-kits/native-kits 0.152.4-beta.7 → 0.152.4-maxapi
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/compose/MoMoComposeKits.podspec +1 -1
- package/compose/build.gradle.kts +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +78 -234
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +36 -106
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +10 -35
- package/package.json +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +0 -123
package/compose/build.gradle.kts
CHANGED
|
@@ -76,7 +76,7 @@ kotlin {
|
|
|
76
76
|
implementation(libs.coil.multiplatform.network.ktor)
|
|
77
77
|
implementation(libs.jetbrains.serialization.json)
|
|
78
78
|
implementation(libs.kotlinx.datetime)
|
|
79
|
-
api(
|
|
79
|
+
api(libs.native.max.api)
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
val androidMain by getting {
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
package vn.momo.kits.application
|
|
2
2
|
|
|
3
|
-
import androidx.compose.animation.core.Animatable
|
|
4
3
|
import androidx.compose.animation.core.animateFloatAsState
|
|
5
|
-
import androidx.compose.animation.core.tween
|
|
6
4
|
import androidx.compose.foundation.ScrollState
|
|
7
5
|
import androidx.compose.foundation.background
|
|
8
6
|
import androidx.compose.foundation.gestures.detectTapGestures
|
|
@@ -15,23 +13,14 @@ import androidx.compose.foundation.layout.asPaddingValues
|
|
|
15
13
|
import androidx.compose.foundation.layout.aspectRatio
|
|
16
14
|
import androidx.compose.foundation.layout.fillMaxSize
|
|
17
15
|
import androidx.compose.foundation.layout.fillMaxWidth
|
|
18
|
-
import androidx.compose.foundation.layout.height
|
|
19
16
|
import androidx.compose.foundation.layout.ime
|
|
20
17
|
import androidx.compose.foundation.layout.imePadding
|
|
21
|
-
import androidx.compose.foundation.layout.offset
|
|
22
18
|
import androidx.compose.foundation.layout.padding
|
|
23
19
|
import androidx.compose.foundation.layout.systemBars
|
|
24
20
|
import androidx.compose.foundation.rememberScrollState
|
|
25
21
|
import androidx.compose.foundation.verticalScroll
|
|
26
22
|
import androidx.compose.runtime.Composable
|
|
27
|
-
import androidx.compose.runtime.CompositionLocalProvider
|
|
28
|
-
import androidx.compose.runtime.DisposableEffect
|
|
29
|
-
import androidx.compose.runtime.LaunchedEffect
|
|
30
23
|
import androidx.compose.runtime.getValue
|
|
31
|
-
import androidx.compose.runtime.mutableStateOf
|
|
32
|
-
import androidx.compose.runtime.remember
|
|
33
|
-
import androidx.compose.runtime.setValue
|
|
34
|
-
import androidx.compose.runtime.staticCompositionLocalOf
|
|
35
24
|
import androidx.compose.ui.Alignment
|
|
36
25
|
import androidx.compose.ui.Modifier
|
|
37
26
|
import androidx.compose.ui.graphics.Color
|
|
@@ -41,24 +30,15 @@ import androidx.compose.ui.layout.onGloballyPositioned
|
|
|
41
30
|
import androidx.compose.ui.platform.LocalDensity
|
|
42
31
|
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
|
43
32
|
import androidx.compose.ui.unit.Dp
|
|
44
|
-
import androidx.compose.ui.unit.IntOffset
|
|
45
33
|
import androidx.compose.ui.unit.dp
|
|
46
34
|
import androidx.compose.ui.unit.min
|
|
47
35
|
import androidx.compose.ui.zIndex
|
|
48
|
-
import kotlinx.coroutines.CoroutineScope
|
|
49
|
-
import kotlinx.coroutines.Dispatchers
|
|
50
|
-
import kotlinx.coroutines.SupervisorJob
|
|
51
|
-
import kotlinx.coroutines.cancel
|
|
52
|
-
import kotlinx.coroutines.delay
|
|
53
|
-
import kotlinx.coroutines.launch
|
|
54
36
|
import vn.momo.kits.components.InputSearchProps
|
|
55
|
-
import vn.momo.kits.const.AppNavigationBar
|
|
56
37
|
import vn.momo.kits.const.AppTheme
|
|
57
38
|
import vn.momo.kits.const.Colors
|
|
58
39
|
import vn.momo.kits.const.Spacing
|
|
59
40
|
import vn.momo.kits.modifier.conditional
|
|
60
41
|
import vn.momo.kits.modifier.shadow
|
|
61
|
-
import vn.momo.kits.navigation.component.SnackBar
|
|
62
42
|
import vn.momo.kits.platform.getAndroidBuildVersion
|
|
63
43
|
import vn.momo.kits.utils.getAppStatusBarHeight
|
|
64
44
|
|
|
@@ -122,134 +102,97 @@ fun Screen(
|
|
|
122
102
|
animatedHeader?.composable?.invoke(scrollState.value)
|
|
123
103
|
}
|
|
124
104
|
}
|
|
125
|
-
val helper = remember { ScreenHelper() }
|
|
126
105
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
106
|
+
Box(
|
|
107
|
+
Modifier.fillMaxSize()
|
|
108
|
+
.background(backgroundColor ?: AppTheme.current.colors.background.default)
|
|
109
|
+
.conditional(useAvoidKeyboard && getAndroidBuildVersion() > 29) {
|
|
110
|
+
imePadding()
|
|
111
|
+
}
|
|
133
112
|
) {
|
|
134
|
-
Box(
|
|
135
|
-
Modifier.fillMaxSize()
|
|
136
|
-
.background(backgroundColor ?: AppTheme.current.colors.background.default)
|
|
137
|
-
.conditional(useAvoidKeyboard && getAndroidBuildVersion() > 29) {
|
|
138
|
-
imePadding()
|
|
139
|
-
}
|
|
140
|
-
) {
|
|
141
|
-
val footerHeightPx = remember { mutableStateOf(0) }
|
|
142
113
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
}
|
|
114
|
+
if (animatedHeader === null) {
|
|
115
|
+
HeaderBackground(
|
|
116
|
+
headerType = headerType,
|
|
117
|
+
scrollState = scrollState.value,
|
|
118
|
+
headerTransparent = headerTransparent
|
|
119
|
+
)
|
|
120
|
+
}
|
|
152
121
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
122
|
+
Header(
|
|
123
|
+
headerType = headerType,
|
|
124
|
+
title = title,
|
|
125
|
+
titlePosition = titlePosition,
|
|
126
|
+
headerRight = headerRight,
|
|
127
|
+
headerRightWidth = headerRightWidth,
|
|
128
|
+
goBack = goBack,
|
|
129
|
+
opacity = opacity,
|
|
130
|
+
animatedHeader = animatedHeader,
|
|
131
|
+
inputSearchProps = inputSearchProps,
|
|
132
|
+
scrollState = scrollState.value,
|
|
133
|
+
useAnimationSearch = useAnimationSearch,
|
|
134
|
+
tintColor = tintColor
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
Column(
|
|
138
|
+
modifier = Modifier.fillMaxSize()
|
|
139
|
+
.padding( top = when {
|
|
140
|
+
animatedHeader != null -> 0.dp
|
|
141
|
+
headerType == HeaderType.NONE -> 0.dp
|
|
142
|
+
fullScreenContent -> 0.dp
|
|
143
|
+
else -> statusBarHeight + HEADER_HEIGHT.dp
|
|
144
|
+
})
|
|
145
|
+
.pointerInput(Unit) {
|
|
146
|
+
detectTapGestures(onTap = {
|
|
147
|
+
keyboardController?.hide()
|
|
148
|
+
})
|
|
149
|
+
}
|
|
150
|
+
.zIndex(1f),
|
|
151
|
+
) {
|
|
169
152
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
153
|
+
Column(
|
|
154
|
+
modifier = Modifier
|
|
155
|
+
.conditional(scrollable) { weight(1f) }
|
|
156
|
+
.conditional(onContentLayout != null) {
|
|
157
|
+
onGloballyPositioned(onContentLayout ?: {})
|
|
158
|
+
}
|
|
159
|
+
.conditional(scrollable) { verticalScroll(scrollState) },
|
|
160
|
+
verticalArrangement = verticalArrangement,
|
|
161
|
+
horizontalAlignment = horizontalAlignment
|
|
162
|
+
) {
|
|
163
|
+
Box {
|
|
164
|
+
if (animatedHeader !== null) headerAnimated()
|
|
165
|
+
Column {
|
|
166
|
+
if (animatedHeader !== null) {
|
|
167
|
+
Spacer(modifier = Modifier.padding(top = statusBarHeight + HEADER_HEIGHT.dp + layoutOffset))
|
|
183
168
|
}
|
|
184
|
-
.zIndex(1f),
|
|
185
|
-
) {
|
|
186
169
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
.conditional(scrollable) { weight(1f) }
|
|
190
|
-
.conditional(onContentLayout != null) {
|
|
191
|
-
onGloballyPositioned(onContentLayout ?: {})
|
|
192
|
-
}
|
|
193
|
-
.conditional(scrollable) { verticalScroll(scrollState) },
|
|
194
|
-
verticalArrangement = verticalArrangement,
|
|
195
|
-
horizontalAlignment = horizontalAlignment
|
|
196
|
-
) {
|
|
197
|
-
Box {
|
|
198
|
-
if (animatedHeader !== null) headerAnimated()
|
|
199
|
-
Column {
|
|
200
|
-
if (animatedHeader !== null) {
|
|
201
|
-
Spacer(modifier = Modifier.padding(top = statusBarHeight + HEADER_HEIGHT.dp + layoutOffset))
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
if (useAnimationSearch && inputSearchProps != null && headerType == HeaderType.EXTENDED) {
|
|
205
|
-
Spacer(modifier = Modifier.padding(top = (HEADER_HEIGHT.dp + Spacing.S)))
|
|
206
|
-
}
|
|
207
|
-
content()
|
|
208
|
-
}
|
|
170
|
+
if (useAnimationSearch && inputSearchProps != null && headerType == HeaderType.EXTENDED) {
|
|
171
|
+
Spacer(modifier = Modifier.padding(top = (HEADER_HEIGHT.dp + Spacing.S)))
|
|
209
172
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
footer?.let {
|
|
213
|
-
val footerHeight = with(LocalDensity.current) { footerHeightPx.value.toDp() }
|
|
214
|
-
Spacer(Modifier.height(footerHeight))
|
|
173
|
+
content()
|
|
215
174
|
}
|
|
216
175
|
}
|
|
217
176
|
}
|
|
218
177
|
|
|
219
|
-
|
|
220
|
-
footer
|
|
221
|
-
Footer(
|
|
222
|
-
footer = footer,
|
|
223
|
-
bottom = bottomPadding,
|
|
224
|
-
onFooterMeasured = { footerHeightPx.value = it }
|
|
225
|
-
)
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
Column (Modifier.zIndex(6f)) {
|
|
230
|
-
if (fabProps != null) {
|
|
231
|
-
FloatingButton(
|
|
232
|
-
scrollPosition = fabProps.scrollState?.value ?: scrollState.value,
|
|
233
|
-
onClick = fabProps.onClick,
|
|
234
|
-
containerColor = AppTheme.current.colors.primary,
|
|
235
|
-
bottom = fabProps.bottom
|
|
236
|
-
?: if (footer != null) bottomPadding + 76.dp else bottomPadding + 12.dp,
|
|
237
|
-
icon = fabProps.icon,
|
|
238
|
-
iconColor = fabProps.iconColor,
|
|
239
|
-
text = fabProps.label,
|
|
240
|
-
size = fabProps.size,
|
|
241
|
-
position = fabProps.position ?: FABPosition.END,
|
|
242
|
-
)
|
|
243
|
-
}
|
|
178
|
+
footer?.let {
|
|
179
|
+
Footer(footer, bottomPadding)
|
|
244
180
|
}
|
|
181
|
+
}
|
|
245
182
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
183
|
+
if (fabProps != null) {
|
|
184
|
+
FloatingButton(
|
|
185
|
+
scrollPosition = fabProps.scrollState?.value ?: scrollState.value,
|
|
186
|
+
onClick = fabProps.onClick,
|
|
187
|
+
containerColor = AppTheme.current.colors.primary,
|
|
188
|
+
bottom = fabProps.bottom
|
|
189
|
+
?: if (footer != null) bottomPadding + 76.dp else bottomPadding + 12.dp,
|
|
190
|
+
icon = fabProps.icon,
|
|
191
|
+
iconColor = fabProps.iconColor,
|
|
192
|
+
text = fabProps.label,
|
|
193
|
+
size = fabProps.size,
|
|
194
|
+
position = fabProps.position ?: FABPosition.END,
|
|
195
|
+
)
|
|
253
196
|
}
|
|
254
197
|
}
|
|
255
198
|
}
|
|
@@ -263,11 +206,7 @@ internal fun isKeyboardVisible(): Boolean {
|
|
|
263
206
|
}
|
|
264
207
|
|
|
265
208
|
@Composable
|
|
266
|
-
fun Footer(
|
|
267
|
-
footer: @Composable (() -> Unit)? = null,
|
|
268
|
-
bottom: Dp = 0.dp,
|
|
269
|
-
onFooterMeasured: ((Int) -> Unit)? = null
|
|
270
|
-
) {
|
|
209
|
+
fun Footer(footer: @Composable (() -> Unit)? = null, bottom: Dp = 0.dp) {
|
|
271
210
|
Box(
|
|
272
211
|
Modifier
|
|
273
212
|
.shadow(
|
|
@@ -277,9 +216,6 @@ fun Footer(
|
|
|
277
216
|
offsetY = (-4).dp
|
|
278
217
|
)
|
|
279
218
|
.background(AppTheme.current.colors.background.surface)
|
|
280
|
-
.onGloballyPositioned {
|
|
281
|
-
onFooterMeasured?.invoke(it.size.height)
|
|
282
|
-
}
|
|
283
219
|
) {
|
|
284
220
|
Box(
|
|
285
221
|
Modifier.fillMaxWidth()
|
|
@@ -292,98 +228,6 @@ fun Footer(
|
|
|
292
228
|
}
|
|
293
229
|
|
|
294
230
|
|
|
295
|
-
class ScreenHelper {
|
|
296
|
-
|
|
297
|
-
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
|
298
|
-
|
|
299
|
-
internal var snackBarState by mutableStateOf<SnackBarData?>(null)
|
|
300
|
-
|
|
301
|
-
internal var requestHide by mutableStateOf(false)
|
|
302
|
-
|
|
303
|
-
fun showSnackBar(snackBar: SnackBar, onDismiss: (() -> Unit)? = null) {
|
|
304
|
-
scope.launch {
|
|
305
|
-
requestHide = false
|
|
306
|
-
snackBarState = SnackBarData(snackBar, onDismiss)
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
fun hideSnackBar() {
|
|
311
|
-
scope.launch {
|
|
312
|
-
requestHide = true
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
internal fun removeSnackBarNow() {
|
|
317
|
-
snackBarState = null
|
|
318
|
-
requestHide = false
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
fun dispose() {
|
|
322
|
-
scope.cancel()
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
data class SnackBarData(
|
|
326
|
-
val type: SnackBar,
|
|
327
|
-
val onDismiss: (() -> Unit)?
|
|
328
|
-
)
|
|
329
|
-
|
|
330
|
-
val LocalScreenHelper = staticCompositionLocalOf<ScreenHelper> {
|
|
331
|
-
error("No Screen helper provided")
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
@Composable
|
|
335
|
-
fun ScreenSnackBarHost(footerHeightPx: Int) {
|
|
336
|
-
val helper = LocalScreenHelper.current
|
|
337
|
-
val snackBarData = helper.snackBarState ?: return
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
val snackBarHeight = when (snackBarData.type) {
|
|
341
|
-
is SnackBar.Custom -> snackBarData.type.heightContent
|
|
342
|
-
is SnackBar.Toast -> 300f
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
val startPosition = if (footerHeightPx > 0) 0f else snackBarHeight
|
|
346
|
-
val targetPosition = if (footerHeightPx > 0) -footerHeightPx.toFloat() else -with(LocalDensity.current) { AppNavigationBar.current.toPx() }
|
|
347
|
-
|
|
348
|
-
val offsetY = remember { Animatable(startPosition) }
|
|
349
|
-
|
|
350
|
-
LaunchedEffect(snackBarData, footerHeightPx) {
|
|
351
|
-
offsetY.snapTo(startPosition)
|
|
352
|
-
offsetY.animateTo(targetPosition, tween(350))
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
LaunchedEffect(helper.requestHide) {
|
|
356
|
-
if (helper.requestHide) {
|
|
357
|
-
offsetY.animateTo(startPosition, tween(200))
|
|
358
|
-
helper.removeSnackBarNow()
|
|
359
|
-
snackBarData.onDismiss?.invoke()
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
LaunchedEffect(snackBarData.type.duration) {
|
|
364
|
-
val duration = snackBarData.type.duration
|
|
365
|
-
if (duration != null) {
|
|
366
|
-
delay(duration)
|
|
367
|
-
helper.hideSnackBar()
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
DisposableEffect(Unit) {
|
|
372
|
-
onDispose { snackBarData.onDismiss?.invoke() }
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
Box(
|
|
376
|
-
modifier = Modifier
|
|
377
|
-
.offset { IntOffset(0, offsetY.value.toInt()) }
|
|
378
|
-
.fillMaxWidth()
|
|
379
|
-
) {
|
|
380
|
-
when (val type = snackBarData.type) {
|
|
381
|
-
is SnackBar.Custom -> type.content()
|
|
382
|
-
is SnackBar.Toast -> {}
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
231
|
|
|
388
232
|
|
|
389
233
|
|
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
package vn.momo.kits.navigation
|
|
2
2
|
|
|
3
|
-
import androidx.compose.foundation.layout.Box
|
|
4
|
-
import androidx.compose.foundation.layout.fillMaxSize
|
|
5
3
|
import androidx.compose.runtime.Composable
|
|
6
4
|
import androidx.compose.runtime.MutableState
|
|
7
5
|
import androidx.compose.runtime.mutableStateOf
|
|
8
6
|
import androidx.compose.runtime.staticCompositionLocalOf
|
|
9
|
-
import androidx.compose.ui.Alignment
|
|
10
|
-
import androidx.compose.ui.Modifier
|
|
11
7
|
import androidx.navigation.NavController
|
|
12
8
|
import kotlinx.coroutines.CoroutineScope
|
|
13
9
|
import kotlinx.coroutines.Dispatchers
|
|
@@ -16,8 +12,6 @@ import kotlinx.coroutines.cancel
|
|
|
16
12
|
import kotlinx.coroutines.delay
|
|
17
13
|
import kotlinx.coroutines.launch
|
|
18
14
|
import kotlinx.serialization.Serializable
|
|
19
|
-
import vn.momo.kits.navigation.BottomHeader.*
|
|
20
|
-
import vn.momo.kits.navigation.component.SnackBar
|
|
21
15
|
import vn.momo.maxapi.IMaxApi
|
|
22
16
|
|
|
23
17
|
class Navigator(
|
|
@@ -43,36 +37,25 @@ class Navigator(
|
|
|
43
37
|
fun pop(count: Int = 1, callBack: (() -> Unit)? = null) {
|
|
44
38
|
scope.launch {
|
|
45
39
|
repeat(count) {
|
|
46
|
-
if (OverplayComponentRegistry.
|
|
47
|
-
|
|
40
|
+
if (OverplayComponentRegistry.isOverplayShowing()){
|
|
41
|
+
OverplayComponentRegistry.clear()
|
|
42
|
+
delay(300L)
|
|
48
43
|
OverplayComponentRegistry.hardClearAfterDismiss()
|
|
49
|
-
}
|
|
50
|
-
else if (OverplayComponentRegistry.isOverplayShowing()){
|
|
51
|
-
dismissOverplay()
|
|
52
44
|
} else {
|
|
53
|
-
|
|
45
|
+
if (navController.previousBackStackEntry != null){
|
|
46
|
+
navController.popBackStack()
|
|
47
|
+
delay(300L)
|
|
48
|
+
DynamicScreenRegistry.getLatestScreen()?.let { it1 ->
|
|
49
|
+
DynamicScreenRegistry.unregisterScreen(it1.id)
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
maxApi?.dismiss { }
|
|
53
|
+
}
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
callBack?.invoke()
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
|
-
private suspend fun CoroutineScope.dismissOverplay(isDelay: Boolean = true) {
|
|
60
|
-
OverplayComponentRegistry.clear()
|
|
61
|
-
if (isDelay) delay(300L)
|
|
62
|
-
OverplayComponentRegistry.hardClearAfterDismiss()
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
private suspend fun CoroutineScope.dismissScreen() {
|
|
66
|
-
if (navController.previousBackStackEntry != null){
|
|
67
|
-
navController.popBackStack()
|
|
68
|
-
delay(300L)
|
|
69
|
-
DynamicScreenRegistry.getLatestScreen()?.let { it1 ->
|
|
70
|
-
DynamicScreenRegistry.unregisterScreen(it1.id)
|
|
71
|
-
}
|
|
72
|
-
} else {
|
|
73
|
-
maxApi?.dismiss { }
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
59
|
|
|
77
60
|
fun present(content: @Composable () -> Unit, options: NavigationOptions? = null) {
|
|
78
61
|
val route = DynamicScreenRegistry.register(content, options)
|
|
@@ -93,8 +76,7 @@ class Navigator(
|
|
|
93
76
|
barrierDismissible: Boolean = true,
|
|
94
77
|
onDismiss: (() -> Unit)? = null
|
|
95
78
|
){
|
|
96
|
-
|
|
97
|
-
OverplayComponentRegistry.registerOverplay(id, content, OverplayComponentType.MODAL, false, barrierDismissible, onDismiss)
|
|
79
|
+
OverplayComponentRegistry.registerModal(content, false, barrierDismissible, onDismiss, false)
|
|
98
80
|
}
|
|
99
81
|
|
|
100
82
|
fun showBottomSheet(
|
|
@@ -104,30 +86,7 @@ class Navigator(
|
|
|
104
86
|
onDismiss: (() -> Unit)? = null,
|
|
105
87
|
bottomSheetHeader: BottomHeader? = null
|
|
106
88
|
){
|
|
107
|
-
|
|
108
|
-
OverplayComponentRegistry.registerOverplay(id, content, OverplayComponentType.BOTTOM_SHEET, isSurface, barrierDismissible, onDismiss, bottomSheetHeader)
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
fun showSnackBar(snackBar: SnackBar, onDismiss: (() -> Unit)? = null) {
|
|
112
|
-
val id = DynamicScreenRegistry.getLatestScreen()?.id ?: -1
|
|
113
|
-
scope.launch {
|
|
114
|
-
OverplayComponentRegistry.registerOverplay(
|
|
115
|
-
id = id,
|
|
116
|
-
content = {
|
|
117
|
-
SnackBar(snackBar, onDismiss)
|
|
118
|
-
},
|
|
119
|
-
type = OverplayComponentType.SNACK_BAR,
|
|
120
|
-
onDismiss = onDismiss
|
|
121
|
-
)
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
fun hideSnackBar() {
|
|
126
|
-
scope.launch {
|
|
127
|
-
OverplayComponentRegistry.clear()
|
|
128
|
-
delay(250L)
|
|
129
|
-
OverplayComponentRegistry.hardClearAfterDismiss()
|
|
130
|
-
}
|
|
89
|
+
OverplayComponentRegistry.registerModal(content, isSurface, barrierDismissible, onDismiss, true, bottomSheetHeader)
|
|
131
90
|
}
|
|
132
91
|
|
|
133
92
|
fun dispose(){
|
|
@@ -200,21 +159,13 @@ sealed class OverplayComponentParams {
|
|
|
200
159
|
val barrierDismissible: Boolean = true,
|
|
201
160
|
val bottomSheetHeader: BottomHeader? = null,
|
|
202
161
|
) : OverplayComponentParams()
|
|
203
|
-
|
|
204
|
-
class SnackBar() : OverplayComponentParams()
|
|
205
162
|
}
|
|
206
163
|
|
|
207
164
|
data class OverplayComponent(
|
|
208
|
-
val id: Int,
|
|
209
|
-
val type: OverplayComponentType? = null,
|
|
210
165
|
val content: @Composable () -> Unit,
|
|
211
166
|
val params: OverplayComponentParams? = null
|
|
212
167
|
)
|
|
213
168
|
|
|
214
|
-
enum class OverplayComponentType {
|
|
215
|
-
MODAL, BOTTOM_SHEET, SNACK_BAR
|
|
216
|
-
}
|
|
217
|
-
|
|
218
169
|
object OverplayComponentRegistry {
|
|
219
170
|
private var currentOverlayComponent : MutableState<OverplayComponent?> = mutableStateOf(null)
|
|
220
171
|
private var requestClose: (() -> Unit)? = null
|
|
@@ -222,24 +173,20 @@ object OverplayComponentRegistry {
|
|
|
222
173
|
requestClose = handler
|
|
223
174
|
}
|
|
224
175
|
|
|
225
|
-
fun
|
|
226
|
-
id: Int,
|
|
176
|
+
fun registerModal(
|
|
227
177
|
content: @Composable () -> Unit,
|
|
228
|
-
type: OverplayComponentType,
|
|
229
178
|
isSurface: Boolean = false,
|
|
230
179
|
barrierDismissible: Boolean = true,
|
|
231
180
|
onDismiss: (() -> Unit)?,
|
|
181
|
+
isBottomSheet: Boolean = false,
|
|
232
182
|
bottomSheetHeader: BottomHeader? = null,
|
|
233
183
|
){
|
|
234
|
-
val params =
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
184
|
+
val params = if (isBottomSheet) {
|
|
185
|
+
OverplayComponentParams.BottomSheet(isSurface, onDismiss, barrierDismissible, bottomSheetHeader)
|
|
186
|
+
} else {
|
|
187
|
+
OverplayComponentParams.Modal(onDismiss, barrierDismissible)
|
|
238
188
|
}
|
|
239
|
-
|
|
240
189
|
currentOverlayComponent.value = OverplayComponent(
|
|
241
|
-
id = id,
|
|
242
|
-
type = type,
|
|
243
190
|
content = content,
|
|
244
191
|
params = params
|
|
245
192
|
)
|
|
@@ -247,10 +194,6 @@ object OverplayComponentRegistry {
|
|
|
247
194
|
|
|
248
195
|
fun isOverplayShowing(): Boolean = currentOverlayComponent.value != null
|
|
249
196
|
|
|
250
|
-
fun getOverplayType(): OverplayComponentType? = currentOverlayComponent.value?.type
|
|
251
|
-
|
|
252
|
-
fun currentRootId(): Int? = currentOverlayComponent.value?.id
|
|
253
|
-
|
|
254
197
|
fun clear(){
|
|
255
198
|
if (requestClose != null) {
|
|
256
199
|
requestClose?.invoke()
|
|
@@ -268,35 +211,22 @@ object OverplayComponentRegistry {
|
|
|
268
211
|
fun OverlayComponent(){
|
|
269
212
|
val overplay = currentOverlayComponent.value ?: return
|
|
270
213
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
)
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
is OverplayComponentParams.SnackBar -> {
|
|
291
|
-
Box(
|
|
292
|
-
modifier = Modifier.fillMaxSize(),
|
|
293
|
-
contentAlignment = Alignment.BottomCenter
|
|
294
|
-
) {
|
|
295
|
-
overplay.content()
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
null -> {}
|
|
214
|
+
if (overplay.params is OverplayComponentParams.BottomSheet) {
|
|
215
|
+
val param = overplay.params
|
|
216
|
+
BottomSheet(
|
|
217
|
+
content = overplay.content,
|
|
218
|
+
header = param.bottomSheetHeader ?: BottomHeader.Title(),
|
|
219
|
+
isSurface = param.isSurface,
|
|
220
|
+
barrierDismissible = param.barrierDismissible,
|
|
221
|
+
onDismiss = param.onDismiss
|
|
222
|
+
)
|
|
223
|
+
} else if (overplay.params is OverplayComponentParams.Modal) {
|
|
224
|
+
val param = overplay.params
|
|
225
|
+
ModalScreen(
|
|
226
|
+
content = overplay.content,
|
|
227
|
+
barrierDismissible = param.barrierDismissible,
|
|
228
|
+
onDismiss = param.onDismiss
|
|
229
|
+
)
|
|
300
230
|
}
|
|
301
231
|
}
|
|
302
232
|
}
|
|
@@ -6,7 +6,6 @@ import androidx.compose.foundation.ScrollState
|
|
|
6
6
|
import androidx.compose.foundation.background
|
|
7
7
|
import androidx.compose.foundation.gestures.ScrollableState
|
|
8
8
|
import androidx.compose.foundation.layout.Box
|
|
9
|
-
import androidx.compose.foundation.layout.BoxScope
|
|
10
9
|
import androidx.compose.foundation.layout.Column
|
|
11
10
|
import androidx.compose.foundation.layout.ColumnScope
|
|
12
11
|
import androidx.compose.foundation.layout.Spacer
|
|
@@ -116,11 +115,11 @@ internal fun StackScreen(
|
|
|
116
115
|
HeaderBackground()
|
|
117
116
|
}
|
|
118
117
|
|
|
119
|
-
Box(Modifier.zIndex(
|
|
118
|
+
Box(Modifier.zIndex(4f)) {
|
|
120
119
|
Header()
|
|
121
120
|
}
|
|
122
121
|
|
|
123
|
-
Column (Modifier.zIndex(
|
|
122
|
+
Column (Modifier.zIndex(5f)) {
|
|
124
123
|
SearchAnimated(isScrollInProgress = scrollInProcess)
|
|
125
124
|
}
|
|
126
125
|
|
|
@@ -128,15 +127,14 @@ internal fun StackScreen(
|
|
|
128
127
|
MainContent(content = content)
|
|
129
128
|
}
|
|
130
129
|
|
|
131
|
-
Box(Modifier.zIndex(
|
|
132
|
-
FooterContent()
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
Box(Modifier.zIndex(7f)){
|
|
130
|
+
Box(Modifier.zIndex(5f)){
|
|
136
131
|
FloatingContent()
|
|
137
132
|
}
|
|
138
133
|
|
|
139
|
-
|
|
134
|
+
Box(Modifier.zIndex(6f).fillMaxSize()){
|
|
135
|
+
if (bottomTabIndex != -1) return@Box
|
|
136
|
+
OverplayComponentRegistry.OverlayComponent()
|
|
137
|
+
}
|
|
140
138
|
}
|
|
141
139
|
}
|
|
142
140
|
}
|
|
@@ -193,9 +191,9 @@ fun ColumnScope.MainContent(content: @Composable ()-> Unit){
|
|
|
193
191
|
}
|
|
194
192
|
|
|
195
193
|
if (options.footerComponent != null){
|
|
196
|
-
val
|
|
197
|
-
val
|
|
198
|
-
|
|
194
|
+
val isKeyboardVisible = isKeyboardVisible()
|
|
195
|
+
val bottomPadding = min(AppNavigationBar.current, if (isKeyboardVisible) 0.dp else 21.dp)
|
|
196
|
+
Footer(footerComponent = options.footerComponent, bottomPadding = bottomPadding)
|
|
199
197
|
}
|
|
200
198
|
}
|
|
201
199
|
|
|
@@ -219,29 +217,6 @@ fun ScreenContent(content: @Composable () -> Unit){
|
|
|
219
217
|
}
|
|
220
218
|
}
|
|
221
219
|
|
|
222
|
-
@Composable
|
|
223
|
-
fun FooterContent(){
|
|
224
|
-
val options = LocalOptions.current
|
|
225
|
-
if (options.footerComponent != null){
|
|
226
|
-
val isKeyboardVisible = isKeyboardVisible()
|
|
227
|
-
val bottomPadding = min(AppNavigationBar.current, if (isKeyboardVisible) 0.dp else 21.dp)
|
|
228
|
-
Footer(footerComponent = options.footerComponent, bottomPadding = bottomPadding)
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
@Composable
|
|
233
|
-
fun OverplayView(bottomTabIndex: Int, id: Int){
|
|
234
|
-
val overplayType = OverplayComponentRegistry.getOverplayType()
|
|
235
|
-
|
|
236
|
-
if (overplayType != null) {
|
|
237
|
-
Box(Modifier.zIndex(if (overplayType == OverplayComponentType.SNACK_BAR) 3f else 8f).fillMaxSize()){
|
|
238
|
-
if (bottomTabIndex != -1) return@Box
|
|
239
|
-
if (OverplayComponentRegistry.currentRootId() != id) return@Box
|
|
240
|
-
OverplayComponentRegistry.OverlayComponent()
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
220
|
@Composable
|
|
246
221
|
fun Footer(footerComponent: @Composable (() -> Unit)?, bottomPadding: Dp) {
|
|
247
222
|
if (footerComponent == null) return
|
package/package.json
CHANGED
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
package vn.momo.kits.navigation.component
|
|
2
|
-
|
|
3
|
-
import androidx.compose.animation.core.Animatable
|
|
4
|
-
import androidx.compose.animation.core.tween
|
|
5
|
-
import androidx.compose.foundation.layout.Box
|
|
6
|
-
import androidx.compose.foundation.layout.fillMaxWidth
|
|
7
|
-
import androidx.compose.foundation.layout.offset
|
|
8
|
-
import androidx.compose.foundation.layout.padding
|
|
9
|
-
import androidx.compose.runtime.Composable
|
|
10
|
-
import androidx.compose.runtime.DisposableEffect
|
|
11
|
-
import androidx.compose.runtime.LaunchedEffect
|
|
12
|
-
import androidx.compose.runtime.getValue
|
|
13
|
-
import androidx.compose.runtime.mutableStateOf
|
|
14
|
-
import androidx.compose.runtime.remember
|
|
15
|
-
import androidx.compose.runtime.rememberCoroutineScope
|
|
16
|
-
import androidx.compose.ui.Modifier
|
|
17
|
-
import androidx.compose.ui.platform.LocalDensity
|
|
18
|
-
import androidx.compose.ui.unit.IntOffset
|
|
19
|
-
import androidx.compose.ui.unit.dp
|
|
20
|
-
import androidx.compose.ui.unit.min
|
|
21
|
-
import kotlinx.coroutines.delay
|
|
22
|
-
import kotlinx.coroutines.launch
|
|
23
|
-
import vn.momo.kits.const.AppNavigationBar
|
|
24
|
-
import vn.momo.kits.const.Spacing
|
|
25
|
-
import vn.momo.kits.navigation.LocalFooterHeightPx
|
|
26
|
-
import vn.momo.kits.navigation.LocalNavigator
|
|
27
|
-
import vn.momo.kits.navigation.LocalOptions
|
|
28
|
-
import vn.momo.kits.navigation.OverplayComponentRegistry
|
|
29
|
-
|
|
30
|
-
sealed class SnackBar(open val duration: Long? = null) {
|
|
31
|
-
data class Custom(
|
|
32
|
-
val content: @Composable () -> Unit,
|
|
33
|
-
val heightContent: Float = 300f,
|
|
34
|
-
override val duration: Long? = 3000L
|
|
35
|
-
) : SnackBar(duration)
|
|
36
|
-
|
|
37
|
-
data class Toast(
|
|
38
|
-
override val duration: Long? = 3000L
|
|
39
|
-
) : SnackBar(duration)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
@Composable
|
|
43
|
-
fun SnackBar(
|
|
44
|
-
data: SnackBar,
|
|
45
|
-
onDismiss: (() -> Unit)?
|
|
46
|
-
) {
|
|
47
|
-
val navigator = LocalNavigator.current
|
|
48
|
-
val options = LocalOptions.current
|
|
49
|
-
val footerHeightPxState = LocalFooterHeightPx.current
|
|
50
|
-
|
|
51
|
-
val snackBarHeight = when (data) {
|
|
52
|
-
is SnackBar.Custom -> -data.heightContent
|
|
53
|
-
is SnackBar.Toast -> -300f
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
val startPosition = if (options.footerComponent != null) 0f else -snackBarHeight
|
|
57
|
-
val targetPosition = if (options.footerComponent != null) {
|
|
58
|
-
footerHeightPxState.intValue.toFloat()
|
|
59
|
-
} else {
|
|
60
|
-
with(LocalDensity.current) { min(AppNavigationBar.current, 21.dp).toPx() }
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
val offsetY by remember { mutableStateOf(Animatable(startPosition)) }
|
|
64
|
-
val coroutineScope = rememberCoroutineScope()
|
|
65
|
-
|
|
66
|
-
suspend fun openEvent() {
|
|
67
|
-
offsetY.snapTo(startPosition)
|
|
68
|
-
offsetY.animateTo(
|
|
69
|
-
targetValue = -targetPosition,
|
|
70
|
-
animationSpec = tween(
|
|
71
|
-
durationMillis = 350,
|
|
72
|
-
)
|
|
73
|
-
)
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
fun closeEvent() {
|
|
77
|
-
coroutineScope.launch {
|
|
78
|
-
offsetY.animateTo(
|
|
79
|
-
targetValue = startPosition,
|
|
80
|
-
animationSpec = tween(
|
|
81
|
-
durationMillis = 200,
|
|
82
|
-
)
|
|
83
|
-
)
|
|
84
|
-
navigator.hideSnackBar()
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
LaunchedEffect(targetPosition) {
|
|
89
|
-
openEvent()
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
LaunchedEffect(data.duration) {
|
|
93
|
-
val duration = data.duration
|
|
94
|
-
if (duration != null) {
|
|
95
|
-
delay(duration)
|
|
96
|
-
navigator.hideSnackBar()
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
DisposableEffect(Unit) {
|
|
101
|
-
OverplayComponentRegistry.bindClose {
|
|
102
|
-
closeEvent()
|
|
103
|
-
}
|
|
104
|
-
onDispose {
|
|
105
|
-
onDismiss?.invoke()
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
Box(
|
|
110
|
-
modifier = Modifier
|
|
111
|
-
.offset { IntOffset(0, offsetY.value.toInt()) }
|
|
112
|
-
.padding(bottom = Spacing.S)
|
|
113
|
-
.fillMaxWidth()
|
|
114
|
-
) {
|
|
115
|
-
when (data) {
|
|
116
|
-
is SnackBar.Custom -> data.content()
|
|
117
|
-
is SnackBar.Toast -> {}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|