@apexnative/navigation 1.0.0-alpha.2 → 1.0.0-alpha.3
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/CHANGELOG.md +22 -0
- package/android/CLAUDE.md +21 -2
- package/android/CORE.md +3 -1
- package/android/src/main/java/dev/apexnative/navigation/NavigationActivity.kt +29 -1
- package/android/src/main/java/dev/apexnative/navigation/options/StatusBarOptions.kt +7 -3
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/arrangement/SecondaryScreenPresenter.kt +13 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsController.kt +76 -13
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ComponentPresenter.kt +28 -7
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ScreenFrame.kt +1 -1
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalAnimations.kt +22 -24
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalCoordinator.kt +74 -8
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalRecord.kt +21 -6
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalSheet.kt +29 -12
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalViews.kt +22 -18
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/BackCallbackState.kt +4 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/Navigator.kt +41 -3
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/RootCoordinator.kt +29 -6
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/overlay/OverlayCoordinator.kt +2 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/ContainerBackground.kt +3 -3
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/ParentController.kt +3 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuController.kt +4 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/splitview/SplitViewController.kt +14 -1
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/BackPreview.kt +287 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackController.kt +148 -6
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackDisplay.kt +24 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackQueue.kt +51 -11
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackTransitions.kt +26 -8
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackViews.kt +105 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/TopBarPresenter.kt +121 -7
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/viewcontroller/ViewController.kt +19 -0
- package/android/src/main/java/dev/apexnative/navigation/views/animations/Curves.kt +29 -11
- package/android/src/main/java/dev/apexnative/navigation/views/stack/ScreenEdge.kt +15 -0
- package/android/src/main/java/dev/apexnative/navigation/views/stack/StackLayout.kt +137 -1
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TitleBar.kt +8 -1
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TopBar.kt +26 -4
- package/docs-md/architecture/transitions.md +6 -0
- package/docs-md/containers/modals.md +4 -2
- package/docs-md/containers/overlays.md +3 -1
- package/docs-md/containers/side-menu.md +4 -2
- package/docs-md/containers/tabs.md +2 -0
- package/docs-md/flows/forms-in-a-modal.md +1 -1
- package/docs-md/help/glossary.md +1 -1
- package/docs-md/index.json +8 -0
- package/docs-md/screen-ui/back-and-gestures.md +6 -0
- package/docs-md/screen-ui/system-ui.md +1 -1
- package/lib/module/api/hooks/useNavigation.js +8 -3
- package/lib/module/api/hooks/useNavigation.js.map +1 -1
- package/lib/typescript/api/hooks/useNavigation.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/animations.d.ts +10 -5
- package/lib/typescript/interfaces/options/animations.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/modals.d.ts +18 -13
- package/lib/typescript/interfaces/options/modals.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/system.d.ts +6 -3
- package/lib/typescript/interfaces/options/system.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/api/hooks/useNavigation.ts +8 -3
- package/src/interfaces/options/animations.ts +10 -5
- package/src/interfaces/options/modals.ts +18 -13
- package/src/interfaces/options/system.ts +6 -3
package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalCoordinator.kt
CHANGED
|
@@ -261,6 +261,54 @@ internal class ModalCoordinator(
|
|
|
261
261
|
}
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
+
/**
|
|
265
|
+
* A modal of a `setRoot`'s layout made with the root, before its turn: it joins the modals layer and renders
|
|
266
|
+
* unseen while the root waits and comes in, held — it doesn't show, and commands and back don't count it — until
|
|
267
|
+
* [release]; [animated] as in [showInChain]. A failure is logged, and nothing is made (null).
|
|
268
|
+
*/
|
|
269
|
+
fun hold(id: String, make: () -> ViewController<*>, animated: Boolean): ModalRecord? = try {
|
|
270
|
+
start(make, null, ModalRecord.Chain(animated) {}, held = true)
|
|
271
|
+
} catch (e: Throwable) {
|
|
272
|
+
val reason = ErrorTexts.reasonOf(e)
|
|
273
|
+
host.log("[ApexNavigation] setRoot: ${ErrorTexts.couldNot("show the screen '$id'", reason)}")
|
|
274
|
+
null
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* The turn of a [hold]'s modal in its `setRoot`'s chain: it shows as soon as it rendered, as [showInChain]'s
|
|
279
|
+
* would, with [done] once it showed or went; at once when it went meanwhile. The function returned stops it while
|
|
280
|
+
* it waits.
|
|
281
|
+
*/
|
|
282
|
+
fun release(record: ModalRecord, done: () -> Unit): () -> Unit {
|
|
283
|
+
if (!record.held || record.state != State.Waiting) {
|
|
284
|
+
done()
|
|
285
|
+
return {}
|
|
286
|
+
}
|
|
287
|
+
record.chain = ModalRecord.Chain(record.chain?.animated ?: true, done)
|
|
288
|
+
record.held = false
|
|
289
|
+
guarded(record) {
|
|
290
|
+
host.backChanged()
|
|
291
|
+
// Rendered while held: the targets of its elements are in the layer under it only now.
|
|
292
|
+
if (record.isReady && !record.targetsAwaited && awaitsTargets(record)) {
|
|
293
|
+
record.isReady = false
|
|
294
|
+
awaitTargets(record)
|
|
295
|
+
} else {
|
|
296
|
+
presentReady()
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return {
|
|
300
|
+
record.chain = null
|
|
301
|
+
if (record.state == State.Waiting) cancel(record, null, reports = false)
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** A [hold]'s modal whose `setRoot` ended before its turn: it goes without a word. */
|
|
306
|
+
fun drop(record: ModalRecord) {
|
|
307
|
+
if (!record.held) return
|
|
308
|
+
record.chain = null
|
|
309
|
+
if (record.state == State.Waiting) cancel(record, null, reports = false)
|
|
310
|
+
}
|
|
311
|
+
|
|
264
312
|
/**
|
|
265
313
|
* `dismissModal` after its shared steps: the record of [componentId], Android's rule of the last modal without a
|
|
266
314
|
* root, [options] merged into the modal's visible screen, the animation of the command's screen with its parents
|
|
@@ -279,6 +327,7 @@ internal class ModalCoordinator(
|
|
|
279
327
|
State.ClosedBelow -> return answer.resolve(record.rootId)
|
|
280
328
|
else -> Unit
|
|
281
329
|
}
|
|
330
|
+
if (record.held) return answer.reject(ErrorTexts.screenNotShown(componentId))
|
|
282
331
|
if (isLastWithoutRoot(record)) return answer.reject(ErrorTexts.modalWithoutRoot(componentId))
|
|
283
332
|
options?.let { mergeInto(record, it) }
|
|
284
333
|
val from = node?.takeUnless { it.isDestroyed } ?: record.root
|
|
@@ -293,7 +342,7 @@ internal class ModalCoordinator(
|
|
|
293
342
|
* before anything changes, with the id of the top modal's visible screen.
|
|
294
343
|
*/
|
|
295
344
|
fun dismissAll(options: Options?, unreadablePath: String?, answer: CommandAnswer) {
|
|
296
|
-
val members = records.filter { it.batch == null && it.state in BATCHED }
|
|
345
|
+
val members = records.filter { it.batch == null && !it.held && it.state in BATCHED }
|
|
297
346
|
if (members.isEmpty()) {
|
|
298
347
|
// Nothing else to take: the second one answers with the running batch (N6), else at once.
|
|
299
348
|
val running = batches.lastOrNull()
|
|
@@ -347,6 +396,12 @@ internal class ModalCoordinator(
|
|
|
347
396
|
return true
|
|
348
397
|
}
|
|
349
398
|
|
|
399
|
+
/**
|
|
400
|
+
* The root of the modal that back goes to first; none without one. A back gesture previews only a pop in it: its
|
|
401
|
+
* dismissal has a preview of its own nowhere yet.
|
|
402
|
+
*/
|
|
403
|
+
fun backTargetRoot(): ViewController<*>? = backTarget()?.root
|
|
404
|
+
|
|
350
405
|
/** The mirror of [handleBack], without acting; null without a modal to ask. */
|
|
351
406
|
fun canHandleBack(): Boolean? {
|
|
352
407
|
val record = backTarget() ?: return null
|
|
@@ -422,6 +477,8 @@ internal class ModalCoordinator(
|
|
|
422
477
|
}
|
|
423
478
|
batches.clear()
|
|
424
479
|
for (record in records.toList()) {
|
|
480
|
+
// A modal of the new root's layout, made with it, stays: it shows over the new root in its turn.
|
|
481
|
+
if (record.held) continue
|
|
425
482
|
if (record.state == State.Waiting) {
|
|
426
483
|
cancel(record, null, reports = false)
|
|
427
484
|
continue
|
|
@@ -432,7 +489,7 @@ internal class ModalCoordinator(
|
|
|
432
489
|
views.carry(record, old)
|
|
433
490
|
carried += record
|
|
434
491
|
}
|
|
435
|
-
records.
|
|
492
|
+
records.retainAll { it.held }
|
|
436
493
|
host.backChanged()
|
|
437
494
|
}
|
|
438
495
|
|
|
@@ -482,7 +539,12 @@ internal class ModalCoordinator(
|
|
|
482
539
|
|
|
483
540
|
// The show (3, 4).
|
|
484
541
|
|
|
485
|
-
private fun start(
|
|
542
|
+
private fun start(
|
|
543
|
+
make: () -> ViewController<*>,
|
|
544
|
+
answer: CommandAnswer?,
|
|
545
|
+
chain: ModalRecord.Chain?,
|
|
546
|
+
held: Boolean = false,
|
|
547
|
+
): ModalRecord {
|
|
486
548
|
val root = make()
|
|
487
549
|
val record: ModalRecord
|
|
488
550
|
val options: Options
|
|
@@ -492,6 +554,7 @@ internal class ModalCoordinator(
|
|
|
492
554
|
val sheet = ModalRecord.isSheet(options)
|
|
493
555
|
root.shownAsSheet = sheet
|
|
494
556
|
record = ModalRecord(root, sheet)
|
|
557
|
+
record.held = held
|
|
495
558
|
} catch (e: Throwable) {
|
|
496
559
|
root.destroy()
|
|
497
560
|
throw e
|
|
@@ -555,7 +618,7 @@ internal class ModalCoordinator(
|
|
|
555
618
|
private fun ready(record: ModalRecord) {
|
|
556
619
|
if (record.state != State.Waiting) return
|
|
557
620
|
record.wait = null
|
|
558
|
-
if (!record.targetsAwaited && awaitsTargets(record)) return awaitTargets(record)
|
|
621
|
+
if (!record.targetsAwaited && !record.held && awaitsTargets(record)) return awaitTargets(record)
|
|
559
622
|
record.cancelLimit()
|
|
560
623
|
record.isReady = true
|
|
561
624
|
presentReady()
|
|
@@ -607,7 +670,7 @@ internal class ModalCoordinator(
|
|
|
607
670
|
if (taking) return
|
|
608
671
|
for (record in records.toList()) {
|
|
609
672
|
if (record.state != State.Waiting) continue
|
|
610
|
-
if (!record.isReady) return
|
|
673
|
+
if (record.held || !record.isReady) return
|
|
611
674
|
present(record)
|
|
612
675
|
}
|
|
613
676
|
}
|
|
@@ -1156,13 +1219,16 @@ internal class ModalCoordinator(
|
|
|
1156
1219
|
|
|
1157
1220
|
// A record is the top when no record above it waits, comes in or shows; one that leaves doesn't count.
|
|
1158
1221
|
private fun isTop(record: ModalRecord): Boolean =
|
|
1159
|
-
records.drop(records.indexOf(record) + 1).none
|
|
1222
|
+
records.drop(records.indexOf(record) + 1).none(::countsAsTop)
|
|
1160
1223
|
|
|
1161
1224
|
// The top record, as the commands see it; none.
|
|
1162
|
-
private fun topRecord(): ModalRecord? = records.lastOrNull
|
|
1225
|
+
private fun topRecord(): ModalRecord? = records.lastOrNull(::countsAsTop)
|
|
1163
1226
|
|
|
1164
1227
|
// Back goes to the top modal that waits, comes in or shows.
|
|
1165
|
-
private fun backTarget(): ModalRecord? = records.lastOrNull
|
|
1228
|
+
private fun backTarget(): ModalRecord? = records.lastOrNull(::countsAsTop)
|
|
1229
|
+
|
|
1230
|
+
// A record that waits, comes in or shows; a held one doesn't count before its turn.
|
|
1231
|
+
private fun countsAsTop(record: ModalRecord): Boolean = !record.held && record.effective in TOP_STATES
|
|
1166
1232
|
|
|
1167
1233
|
// `hardwareBackButton.dismissModalOnPress: false` of the modal's options now.
|
|
1168
1234
|
private fun holdsBack(record: ModalRecord): Boolean =
|
package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalRecord.kt
CHANGED
|
@@ -17,7 +17,10 @@ import dev.apexnative.navigation.viewcontrollers.viewcontroller.ViewController
|
|
|
17
17
|
internal class ModalRecord(
|
|
18
18
|
/** The top node of the modal's layout. */
|
|
19
19
|
val root: ViewController<*>,
|
|
20
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* The modal shows as a Material sheet (`pageSheet`, `formSheet`, `popover`, or `modal.sheet` without a style),
|
|
22
|
+
* decided before its root's view is made.
|
|
23
|
+
*/
|
|
21
24
|
val isSheet: Boolean,
|
|
22
25
|
) : WaitingLayer {
|
|
23
26
|
/** Where the modal is. */
|
|
@@ -78,6 +81,12 @@ internal class ModalRecord(
|
|
|
78
81
|
/** Its wait ended: it shows once every older waiting modal showed or went (N1). */
|
|
79
82
|
var isReady = false
|
|
80
83
|
|
|
84
|
+
/**
|
|
85
|
+
* A modal of a `setRoot`'s layout made with the root, before its turn: it renders unseen while the root waits and
|
|
86
|
+
* comes in, and neither shows nor counts for commands or back until its step of the chain lets it go.
|
|
87
|
+
*/
|
|
88
|
+
var held = false
|
|
89
|
+
|
|
81
90
|
/** The wait for its screens while it waits. */
|
|
82
91
|
var wait: RenderWait? = null
|
|
83
92
|
|
|
@@ -160,14 +169,20 @@ internal class ModalRecord(
|
|
|
160
169
|
/** `modalPresentationStyle` values that keep the layer under in place. */
|
|
161
170
|
private val OVER_STYLES = setOf("overFullScreen", "overCurrentContext")
|
|
162
171
|
|
|
163
|
-
/**
|
|
164
|
-
|
|
172
|
+
/**
|
|
173
|
+
* The styles that always show as a sheet: the sheets of iOS, and the popover, which an iPhone shows as a sheet
|
|
174
|
+
* too.
|
|
175
|
+
*/
|
|
176
|
+
private val SHEET_STYLES = setOf("pageSheet", "formSheet", "popover")
|
|
165
177
|
|
|
166
178
|
/**
|
|
167
|
-
* Whether a modal with [options] (resolved, with the defaults) shows as a sheet:
|
|
168
|
-
*
|
|
179
|
+
* Whether a modal with [options] (resolved, with the defaults) shows as a sheet: its style is `pageSheet`,
|
|
180
|
+
* `formSheet` or `popover`, or it has none and any `modal.sheet` field is set.
|
|
169
181
|
*/
|
|
170
|
-
fun isSheet(options: Options): Boolean
|
|
182
|
+
fun isSheet(options: Options): Boolean {
|
|
183
|
+
val style = styleOf(options)
|
|
184
|
+
return style in SHEET_STYLES || (style == null && options.modal.sheet.hasValue)
|
|
185
|
+
}
|
|
171
186
|
|
|
172
187
|
/**
|
|
173
188
|
* Whether a modal covers the layer under it at the end of its show: full screen, unless it is a sheet or
|
|
@@ -29,11 +29,12 @@ import dev.apexnative.navigation.utils.dp
|
|
|
29
29
|
import kotlin.math.roundToInt
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
* A modal shown as a Material bottom sheet (`modal.sheet`
|
|
33
|
-
* [BottomSheetBehavior], in the modals' CoordinatorLayout, over a scrim. It stops at
|
|
34
|
-
* `medium` at half the height, `large` at the full height under the status bar, a number
|
|
35
|
-
* user drags it between them.
|
|
36
|
-
*
|
|
32
|
+
* A modal shown as a Material bottom sheet (`pageSheet`, `formSheet`, `popover`, or `modal.sheet` without a style): the
|
|
33
|
+
* modal's view in a container with a [BottomSheetBehavior], in the modals' CoordinatorLayout, over a scrim. It stops at
|
|
34
|
+
* the detents of its options — `medium` at half the height, `large` at the full height under the status bar, a number
|
|
35
|
+
* at that height in dp — and the user drags it between them. In a window wider than [MAX_WIDTH_DP] it stands in the
|
|
36
|
+
* middle at that width. A drag down past the smallest one closes it, unless `swipeToDismiss` is false: then the sheet
|
|
37
|
+
* stays, and the try is told. The screen under it stays in place.
|
|
37
38
|
*/
|
|
38
39
|
internal class ModalSheet(
|
|
39
40
|
private val layout: CoordinatorLayout,
|
|
@@ -96,11 +97,19 @@ internal class ModalSheet(
|
|
|
96
97
|
outline.setRoundRect(0, 0, view.width, view.height + radius.roundToInt(), radius)
|
|
97
98
|
}
|
|
98
99
|
}
|
|
99
|
-
// The sheet is under the status bar: its screens leave no room for it at their top.
|
|
100
|
+
// The sheet is under the status bar: its screens leave no room for it at their top. In the middle of a wide
|
|
101
|
+
// window it reaches no side, and keeps room only for the part of a side bar that is over it.
|
|
100
102
|
ViewCompat.setOnApplyWindowInsetsListener(container) { _, insets ->
|
|
101
103
|
val types = WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout()
|
|
102
104
|
val bars = insets.getInsets(types)
|
|
103
|
-
|
|
105
|
+
val laidOut = container.width > 0
|
|
106
|
+
val left = if (laidOut) (bars.left - container.left).coerceAtLeast(0) else bars.left
|
|
107
|
+
val right = if (laidOut) (bars.right - (layout.width - container.right)).coerceAtLeast(0) else bars.right
|
|
108
|
+
WindowInsetsCompat.Builder(insets).setInsets(types, Insets.of(left, 0, right, bars.bottom)).build()
|
|
109
|
+
}
|
|
110
|
+
// Its sides move with the window's width (a rotation, a resize): the room for the side bars follows.
|
|
111
|
+
container.addOnLayoutChangeListener { _, left, _, right, _, oldLeft, _, oldRight, _ ->
|
|
112
|
+
if (left != oldLeft || right != oldRight) container.post { ViewCompat.requestApplyInsets(container) }
|
|
104
113
|
}
|
|
105
114
|
container.addView(content, FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT))
|
|
106
115
|
container.addView(grabber, FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT, Gravity.TOP or Gravity.CENTER_HORIZONTAL))
|
|
@@ -120,7 +129,12 @@ internal class ModalSheet(
|
|
|
120
129
|
layout.addView(scrim, CoordinatorLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT))
|
|
121
130
|
// As tall as its largest detent (maxHeight), not moved down by a margin: the behavior counts its states from
|
|
122
131
|
// where the sheet is laid out.
|
|
123
|
-
|
|
132
|
+
// In a wide window it stands in the middle, as wide as a Material modal sheet at most.
|
|
133
|
+
behavior.maxWidth = MAX_WIDTH_DP.dp
|
|
134
|
+
val params = CoordinatorLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
|
|
135
|
+
params.gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
|
|
136
|
+
params.behavior = behavior
|
|
137
|
+
layout.addView(container, params)
|
|
124
138
|
shown = onShown
|
|
125
139
|
update(sheetOptions, swipeToDismiss, merge = null)
|
|
126
140
|
selected = sheetOptions.selectedDetent?.takeIf { it in detents }
|
|
@@ -334,10 +348,13 @@ internal class ModalSheet(
|
|
|
334
348
|
}
|
|
335
349
|
}
|
|
336
350
|
|
|
337
|
-
|
|
351
|
+
companion object {
|
|
352
|
+
/** The widest a sheet is, in dp: the Material modal bottom sheet's `maxWidth`. */
|
|
353
|
+
const val MAX_WIDTH_DP = 640
|
|
354
|
+
|
|
338
355
|
/** The dim of a Material modal bottom sheet's scrim. */
|
|
339
|
-
const val SCRIM_ALPHA = 0.32f
|
|
340
|
-
const val GRAB_AREA_DP = 48
|
|
341
|
-
const val ATTEMPT_DISTANCE_DP = 48
|
|
356
|
+
private const val SCRIM_ALPHA = 0.32f
|
|
357
|
+
private const val GRAB_AREA_DP = 48
|
|
358
|
+
private const val ATTEMPT_DISTANCE_DP = 48
|
|
342
359
|
}
|
|
343
360
|
}
|
|
@@ -4,9 +4,9 @@ import android.animation.Animator
|
|
|
4
4
|
import android.animation.AnimatorListenerAdapter
|
|
5
5
|
import android.animation.AnimatorSet
|
|
6
6
|
import android.animation.ObjectAnimator
|
|
7
|
-
import android.animation.PropertyValuesHolder
|
|
8
7
|
import android.animation.TimeInterpolator
|
|
9
8
|
import android.content.Context
|
|
9
|
+
import android.view.Gravity
|
|
10
10
|
import android.view.View
|
|
11
11
|
import android.view.ViewGroup
|
|
12
12
|
import android.view.animation.LinearInterpolator
|
|
@@ -17,6 +17,7 @@ import androidx.core.view.WindowInsetsCompat
|
|
|
17
17
|
import dev.apexnative.navigation.options.ModalAnimationOptions
|
|
18
18
|
import dev.apexnative.navigation.options.Options
|
|
19
19
|
import dev.apexnative.navigation.utils.TestIds
|
|
20
|
+
import dev.apexnative.navigation.utils.dp
|
|
20
21
|
import dev.apexnative.navigation.viewcontrollers.viewcontroller.ViewController
|
|
21
22
|
import dev.apexnative.navigation.views.animations.AnimationRules
|
|
22
23
|
import dev.apexnative.navigation.views.animations.SideAnimator
|
|
@@ -62,9 +63,15 @@ internal class ModalViews(
|
|
|
62
63
|
try {
|
|
63
64
|
val view = record.root.view
|
|
64
65
|
(view.parent as? ViewGroup)?.removeView(view)
|
|
65
|
-
// A sheet's content waits at the sheet's full height, under the status bar.
|
|
66
|
+
// A sheet's content waits at the sheet's full height, under the status bar, and at its width.
|
|
66
67
|
val params = CoordinatorLayout.LayoutParams(MATCH, MATCH)
|
|
67
|
-
if (record.isSheet)
|
|
68
|
+
if (record.isSheet) {
|
|
69
|
+
params.topMargin = statusBarTop()
|
|
70
|
+
params.gravity = Gravity.CENTER_HORIZONTAL
|
|
71
|
+
// The modals layer is hidden while it has no modal: the window's content gives the width.
|
|
72
|
+
val maxWidth = ModalSheet.MAX_WIDTH_DP.dp
|
|
73
|
+
if (((modalsLayer.parent as? View)?.width ?: 0) > maxWidth) params.width = maxWidth
|
|
74
|
+
}
|
|
68
75
|
container.addView(view, params)
|
|
69
76
|
modalsLayer.addView(container, FrameLayout.LayoutParams(MATCH, MATCH))
|
|
70
77
|
} catch (e: Throwable) {
|
|
@@ -100,14 +107,13 @@ internal class ModalViews(
|
|
|
100
107
|
container.translationY = 0f
|
|
101
108
|
return playOwn(record, parts, motion, showing = true, onEnd)
|
|
102
109
|
}
|
|
110
|
+
// The system's slide over the window below: the modal comes up whole, without a fade.
|
|
103
111
|
val distance = distanceOf(container)
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
)
|
|
109
|
-
animator.interpolator = TimeInterpolator { ModalAnimations.decelerate(it) }
|
|
110
|
-
return play(parts, animator, onEnd)
|
|
112
|
+
container.alpha = 1f
|
|
113
|
+
container.translationY = distance
|
|
114
|
+
val animator = ObjectAnimator.ofFloat(container, View.TRANSLATION_Y, distance, 0f)
|
|
115
|
+
animator.interpolator = TimeInterpolator { ModalAnimations.linearOutSlowIn(it) }
|
|
116
|
+
return play(parts, animator, ModalAnimations.SHOW_DURATION, onEnd)
|
|
111
117
|
}
|
|
112
118
|
|
|
113
119
|
// The root's view leaves the container and goes into the sheet's, in one turn; the sheet slides to its first
|
|
@@ -163,13 +169,11 @@ internal class ModalViews(
|
|
|
163
169
|
if (!animated || !seen) return none(onEnd)
|
|
164
170
|
if (motion != null) return playOwn(record, parts, motion, showing = false, onEnd)
|
|
165
171
|
val container = parts.container
|
|
166
|
-
val animator = ObjectAnimator.
|
|
167
|
-
container,
|
|
168
|
-
PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, container.translationY, distanceOf(container)),
|
|
169
|
-
PropertyValuesHolder.ofFloat(View.ALPHA, container.alpha, 0f),
|
|
172
|
+
val animator = ObjectAnimator.ofFloat(
|
|
173
|
+
container, View.TRANSLATION_Y, container.translationY, distanceOf(container),
|
|
170
174
|
)
|
|
171
|
-
animator.interpolator = TimeInterpolator { ModalAnimations.
|
|
172
|
-
return play(parts, animator, onEnd)
|
|
175
|
+
animator.interpolator = TimeInterpolator { ModalAnimations.accelerate(it) }
|
|
176
|
+
return play(parts, animator, ModalAnimations.DISMISS_DURATION, onEnd)
|
|
173
177
|
}
|
|
174
178
|
|
|
175
179
|
override fun awaitTargets(
|
|
@@ -355,8 +359,8 @@ internal class ModalViews(
|
|
|
355
359
|
private fun viewOf(node: ViewController<*>): View? =
|
|
356
360
|
parts.entries.firstOrNull { it.key.root === node }?.value?.container ?: node.peekView()
|
|
357
361
|
|
|
358
|
-
private fun play(parts: Parts, animator: Animator, onEnd: () -> Unit): () -> Unit {
|
|
359
|
-
animator.duration =
|
|
362
|
+
private fun play(parts: Parts, animator: Animator, duration: Long, onEnd: () -> Unit): () -> Unit {
|
|
363
|
+
animator.duration = duration
|
|
360
364
|
animator.addListener(object : AnimatorListenerAdapter() {
|
|
361
365
|
override fun onAnimationEnd(animation: Animator) {
|
|
362
366
|
if (parts.animator === animation) parts.animator = null
|
|
@@ -41,6 +41,10 @@ internal object BackCallbackState {
|
|
|
41
41
|
entries.removeAll { it.navigator.get().let { attached -> attached == null || attached === navigator } }
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
/** JavaScript listens to back (`BackHandler`): it hears a press before the navigation does. */
|
|
45
|
+
val hasJsListeners: Boolean
|
|
46
|
+
get() = jsListeners
|
|
47
|
+
|
|
44
48
|
/** `setBackListeners` of JavaScript; false again when its JavaScript goes. */
|
|
45
49
|
fun setJsListeners(active: Boolean) {
|
|
46
50
|
jsListeners = active
|
package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/Navigator.kt
CHANGED
|
@@ -31,6 +31,7 @@ import dev.apexnative.navigation.viewcontrollers.component.ContentReadiness
|
|
|
31
31
|
import dev.apexnative.navigation.viewcontrollers.component.ScreenReadiness
|
|
32
32
|
import dev.apexnative.navigation.viewcontrollers.externalcomponent.ExternalComponentViewController
|
|
33
33
|
import dev.apexnative.navigation.viewcontrollers.modal.ModalCoordinator
|
|
34
|
+
import dev.apexnative.navigation.viewcontrollers.modal.ModalRecord
|
|
34
35
|
import dev.apexnative.navigation.viewcontrollers.modal.ModalViews
|
|
35
36
|
import dev.apexnative.navigation.viewcontrollers.overlay.OverlayCoordinator
|
|
36
37
|
import dev.apexnative.navigation.viewcontrollers.overlay.OverlayRecord
|
|
@@ -38,6 +39,7 @@ import dev.apexnative.navigation.viewcontrollers.overlay.OverlayViews
|
|
|
38
39
|
import dev.apexnative.navigation.viewcontrollers.parent.ParentController
|
|
39
40
|
import dev.apexnative.navigation.viewcontrollers.stack.PreparedPushes
|
|
40
41
|
import dev.apexnative.navigation.viewcontrollers.stack.StackCommands
|
|
42
|
+
import dev.apexnative.navigation.viewcontrollers.stack.StackController
|
|
41
43
|
import dev.apexnative.navigation.viewcontrollers.stack.topbar.BarComponents
|
|
42
44
|
import dev.apexnative.navigation.viewcontrollers.viewcontroller.ViewController
|
|
43
45
|
import dev.apexnative.navigation.views.animations.FadeThrough
|
|
@@ -153,6 +155,15 @@ class Navigator(
|
|
|
153
155
|
fun handleBack(listener: CommandListener): Boolean =
|
|
154
156
|
modals.handleBack(listener) ?: (root?.handleBack(listener) == true)
|
|
155
157
|
|
|
158
|
+
/**
|
|
159
|
+
* The stack whose top [handleBack] would take away with the default pop: in the top modal, else in the root; none
|
|
160
|
+
* when back would do anything else.
|
|
161
|
+
*/
|
|
162
|
+
internal fun stackPoppedByBack(): StackController? {
|
|
163
|
+
val modalRoot = modals.backTargetRoot()
|
|
164
|
+
return if (modalRoot != null) modalRoot.stackPoppedByBack() else root?.stackPoppedByBack()
|
|
165
|
+
}
|
|
166
|
+
|
|
156
167
|
/**
|
|
157
168
|
* [layer] may wait to show from now on ([WaitingLayer]): the nodes in it ask it through [isInWaitingLayer] and
|
|
158
169
|
* [shownScreensChanged]. The root's layer is there from the start.
|
|
@@ -264,9 +275,7 @@ class Navigator(
|
|
|
264
275
|
if (!made) return answer.reject(ErrorTexts.notDoneYet("setRoot"))
|
|
265
276
|
val steps = layout.modals.mapIndexed { index, node ->
|
|
266
277
|
val last = index == layout.modals.lastIndex
|
|
267
|
-
|
|
268
|
-
modals.showInChain(node.id, { makeLayer(node, isOverlay = false) }, last, done)
|
|
269
|
-
}
|
|
278
|
+
ModalLayerStep(node, last)
|
|
270
279
|
} + layout.overlays.map { node ->
|
|
271
280
|
RootCoordinator.LayerStep { done ->
|
|
272
281
|
overlays.showInChain(node.id, { makeLayer(node, isOverlay = true) }, done)
|
|
@@ -275,6 +284,35 @@ class Navigator(
|
|
|
275
284
|
roots.setRoot({ factory.create(layout.root) }, answer, steps)
|
|
276
285
|
}
|
|
277
286
|
|
|
287
|
+
// A modal of a `setRoot`'s layout: made with the root and held until its turn, so it renders while the root waits
|
|
288
|
+
// and comes in; made in its turn instead while an id of it still shows (a modal of the old root with that id goes
|
|
289
|
+
// with the old root). A modal that couldn't be made is skipped in its turn.
|
|
290
|
+
private inner class ModalLayerStep(private val node: LayoutNode, private val animated: Boolean) :
|
|
291
|
+
RootCoordinator.LayerStep {
|
|
292
|
+
private var prepared = false
|
|
293
|
+
private var held: ModalRecord? = null
|
|
294
|
+
|
|
295
|
+
override fun prepare() {
|
|
296
|
+
if (LayoutChecks.firstShownId(listOf(node), ::isShownNow) { false } != null) return
|
|
297
|
+
prepared = true
|
|
298
|
+
held = modals.hold(node.id, { factory.create(node) }, animated)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
override fun discard() {
|
|
302
|
+
held?.let(modals::drop)
|
|
303
|
+
held = null
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
override fun show(done: () -> Unit): () -> Unit {
|
|
307
|
+
val record = held
|
|
308
|
+
held = null
|
|
309
|
+
if (record != null) return modals.release(record, done)
|
|
310
|
+
if (!prepared) return modals.showInChain(node.id, { makeLayer(node, isOverlay = false) }, animated, done)
|
|
311
|
+
done()
|
|
312
|
+
return {}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
278
316
|
// A modal or an overlay of a `setRoot`'s layout, made as its turn comes: an id that shows by then fails it.
|
|
279
317
|
private fun makeLayer(node: LayoutNode, isOverlay: Boolean): ViewController<*> {
|
|
280
318
|
val shown = LayoutChecks.firstShownId(listOf(node), ::isShownNow) { isOverlay && isReplacedOverlay(node, it) }
|
package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/RootCoordinator.kt
CHANGED
|
@@ -36,7 +36,8 @@ import dev.apexnative.navigation.views.animations.AnimationRules
|
|
|
36
36
|
* The modals over the old root ([ModalsOverRoot]): taken as the `setRoot` is accepted (commands don't find them), given
|
|
37
37
|
* back when it ends without replacing the root, carried with the old root into its transition and destroyed with it,
|
|
38
38
|
* without `ANN.ModalDismissed`. The modals and the overlays of the layout ([LayerStep]) show after the root, one by
|
|
39
|
-
* one, before the answer: a newer `setRoot` stops them.
|
|
39
|
+
* one, before the answer: a newer `setRoot` stops them. Its modals are made with the root ([LayerStep.prepare]) and
|
|
40
|
+
* render unseen while it waits and comes in, so each shows in its turn without a wait of its own.
|
|
40
41
|
*
|
|
41
42
|
* The steps that come later (the wait's timers and readiness signals, the end of the transition, the end of a layer,
|
|
42
43
|
* a stack of the waiting root that changed its top) run under no runner: each goes through one guard, and a failure
|
|
@@ -128,11 +129,20 @@ internal class RootCoordinator(
|
|
|
128
129
|
}
|
|
129
130
|
|
|
130
131
|
/**
|
|
131
|
-
* A modal or an overlay of the layout of a `setRoot`, shown after its root: [show]
|
|
132
|
-
* its argument once it showed or went (a failure is logged and skipped); what it
|
|
133
|
-
* a newer `setRoot`: it goes without a word.
|
|
132
|
+
* A modal or an overlay of the layout of a `setRoot`, shown after its root: [show] shows it (made then, unless
|
|
133
|
+
* [prepare] made it), and calls its argument once it showed or went (a failure is logged and skipped); what it
|
|
134
|
+
* returns stops it while it waits — a newer `setRoot`: it goes without a word.
|
|
134
135
|
*/
|
|
135
136
|
fun interface LayerStep {
|
|
137
|
+
/**
|
|
138
|
+
* Right after the root is placed, before its wait: a step may make its layer now, so that it renders unseen
|
|
139
|
+
* while the root waits and comes in, and shows without a wait of its own in its turn.
|
|
140
|
+
*/
|
|
141
|
+
fun prepare() = Unit
|
|
142
|
+
|
|
143
|
+
/** The `setRoot` ended before this step's turn: what [prepare] made goes without a word. */
|
|
144
|
+
fun discard() = Unit
|
|
145
|
+
|
|
136
146
|
fun show(done: () -> Unit): () -> Unit
|
|
137
147
|
}
|
|
138
148
|
|
|
@@ -221,6 +231,7 @@ internal class RootCoordinator(
|
|
|
221
231
|
modals.rootAccepted()
|
|
222
232
|
val waitForRender = animationsOf(root).enter.waitForRender
|
|
223
233
|
this.pending = pending
|
|
234
|
+
layers.forEach { it.prepare() }
|
|
224
235
|
firstRoot.rootSet(waitForRender)
|
|
225
236
|
await(pending, waitForRender)
|
|
226
237
|
}
|
|
@@ -367,6 +378,14 @@ internal class RootCoordinator(
|
|
|
367
378
|
if (!done) pending.stopLayer = stop
|
|
368
379
|
}
|
|
369
380
|
|
|
381
|
+
// The layers of [pending] not shown yet go: what they made ahead goes unseen, the others aren't made. Each on its
|
|
382
|
+
// own: one that fails doesn't keep the others.
|
|
383
|
+
private fun discardLayers(pending: Pending) {
|
|
384
|
+
val rest = pending.layers.drop(pending.nextLayer)
|
|
385
|
+
pending.nextLayer = pending.layers.size
|
|
386
|
+
rest.forEach { attempt { it.discard() } }
|
|
387
|
+
}
|
|
388
|
+
|
|
370
389
|
// A new `setRoot` came while this one was on its way.
|
|
371
390
|
private fun supersede() {
|
|
372
391
|
val pending = pending ?: return
|
|
@@ -389,14 +408,15 @@ internal class RootCoordinator(
|
|
|
389
408
|
pending.replaced = null
|
|
390
409
|
modals.replacedRootGone()
|
|
391
410
|
}
|
|
392
|
-
// Its root is the current root: the modal or the overlay that waits now goes without a word, the others
|
|
393
|
-
// aren't made; those shown stay over its root, and go with it when the new root replaces it.
|
|
411
|
+
// Its root is the current root: the modal or the overlay that waits now goes without a word, the others go
|
|
412
|
+
// unseen or aren't made; those shown stay over its root, and go with it when the new root replaces it.
|
|
394
413
|
Phase.Layers -> {
|
|
395
414
|
pending.stopLayer?.invoke()
|
|
396
415
|
pending.stopLayer = null
|
|
397
416
|
releaseState(pending)
|
|
398
417
|
}
|
|
399
418
|
}
|
|
419
|
+
discardLayers(pending)
|
|
400
420
|
pending.answer.reject(ErrorTexts.setRootReplaced(pending.root.id))
|
|
401
421
|
}
|
|
402
422
|
|
|
@@ -426,6 +446,7 @@ internal class RootCoordinator(
|
|
|
426
446
|
pending.wait = null
|
|
427
447
|
when (pending.phase) {
|
|
428
448
|
Phase.Waiting -> {
|
|
449
|
+
discardLayers(pending)
|
|
429
450
|
attempt { takeAway(pending.root) }
|
|
430
451
|
attempt { modals.rootKept() }
|
|
431
452
|
attempt { firstRoot.rootDestroyed() }
|
|
@@ -451,6 +472,7 @@ internal class RootCoordinator(
|
|
|
451
472
|
pending.stopLayer = null
|
|
452
473
|
}
|
|
453
474
|
}
|
|
475
|
+
discardLayers(pending)
|
|
454
476
|
// A root that is up takes the splash away, as at its show.
|
|
455
477
|
if (pending.phase != Phase.Waiting) attempt { onRootShown() }
|
|
456
478
|
attempt { releaseState(pending) }
|
|
@@ -482,6 +504,7 @@ internal class RootCoordinator(
|
|
|
482
504
|
// The root was destroyed while it waited, by something other than a newer `setRoot` or the activity going.
|
|
483
505
|
private fun rootGone(pending: Pending) {
|
|
484
506
|
this.pending = null
|
|
507
|
+
discardLayers(pending)
|
|
485
508
|
releaseState(pending)
|
|
486
509
|
layer.remove(pending.root)
|
|
487
510
|
modals.rootKept()
|
|
@@ -185,6 +185,8 @@ internal class OverlayCoordinator(
|
|
|
185
185
|
|
|
186
186
|
private fun start(make: () -> ViewController<*>, answer: CommandAnswer?, chainDone: (() -> Unit)?): OverlayRecord {
|
|
187
187
|
val root = make()
|
|
188
|
+
// Before the root's view is made: its screens are drawn under the status bar from their first layout.
|
|
189
|
+
root.shownAsOverlay = true
|
|
188
190
|
val record = OverlayRecord(root)
|
|
189
191
|
// A waiting layer before its views join the window: what is in it isn't on screen from its first layout.
|
|
190
192
|
record.screensChanged = { screensChanged(record) }
|
|
@@ -31,9 +31,9 @@ internal object ContainerBackground {
|
|
|
31
31
|
if (!colour.hasValue()) return null
|
|
32
32
|
val color = colour.get(node.activity, Color.TRANSPARENT) ?: Color.TRANSPARENT
|
|
33
33
|
// The screens' band rule (`ScreenSafeArea.backgroundBand`) for a layout, which keeps no room at its top: the
|
|
34
|
-
// status bar's height, but none when it is hidden or the layout is drawn under it
|
|
35
|
-
// a pair.
|
|
36
|
-
val full = options.statusBar.isHiddenOrDrawBehind || node.isInSideMenu || node.isInSheet ||
|
|
34
|
+
// status bar's height, but none when it is hidden or the layout is drawn under it (in an overlay by default),
|
|
35
|
+
// in a side menu, a sheet or a pair.
|
|
36
|
+
val full = options.statusBar.isHiddenOrDrawBehind(node.isInOverlay) || node.isInSideMenu || node.isInSheet ||
|
|
37
37
|
node.parentController is KeepsStatusBarRoom
|
|
38
38
|
val band = if (full) 0 else SystemUiUtils.getStatusBarHeight(node.activity)
|
|
39
39
|
if (band <= 0) return ColorDrawable(color)
|
package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/ParentController.kt
CHANGED
|
@@ -8,6 +8,7 @@ import dev.apexnative.navigation.react.CommandListener
|
|
|
8
8
|
import dev.apexnative.navigation.viewcontrollers.bottomtabs.TabBarMotion
|
|
9
9
|
import dev.apexnative.navigation.viewcontrollers.child.ChildController
|
|
10
10
|
import dev.apexnative.navigation.viewcontrollers.child.ChildControllersRegistry
|
|
11
|
+
import dev.apexnative.navigation.viewcontrollers.stack.StackController
|
|
11
12
|
import dev.apexnative.navigation.viewcontrollers.stack.fab.FabPlacement
|
|
12
13
|
import dev.apexnative.navigation.viewcontrollers.viewcontroller.Presenter
|
|
13
14
|
import dev.apexnative.navigation.viewcontrollers.viewcontroller.ViewController
|
|
@@ -210,6 +211,8 @@ abstract class ParentController<V : ViewGroup>(
|
|
|
210
211
|
|
|
211
212
|
override fun canHandleBack(): Boolean = getCurrentChild()?.canHandleBack() == true
|
|
212
213
|
|
|
214
|
+
override fun stackPoppedByBack(): StackController? = getCurrentChild()?.stackPoppedByBack()
|
|
215
|
+
|
|
213
216
|
override fun destroy() {
|
|
214
217
|
if (isDestroyed) return
|
|
215
218
|
getChildControllers().forEach { it.destroy() }
|
|
@@ -16,6 +16,7 @@ import dev.apexnative.navigation.viewcontrollers.navigator.Navigator
|
|
|
16
16
|
import dev.apexnative.navigation.viewcontrollers.parent.ChildOffsetFromRest
|
|
17
17
|
import dev.apexnative.navigation.viewcontrollers.parent.ParentController
|
|
18
18
|
import dev.apexnative.navigation.viewcontrollers.sidemenu.SideMenuMotion.Side
|
|
19
|
+
import dev.apexnative.navigation.viewcontrollers.stack.StackController
|
|
19
20
|
import dev.apexnative.navigation.viewcontrollers.viewcontroller.Presenter
|
|
20
21
|
import dev.apexnative.navigation.viewcontrollers.viewcontroller.ViewController
|
|
21
22
|
import dev.apexnative.navigation.views.sidemenu.SideMenuView
|
|
@@ -183,6 +184,9 @@ internal class SideMenuController(
|
|
|
183
184
|
|
|
184
185
|
override fun canHandleBack(): Boolean = motion.canGoBack || center.canHandleBack()
|
|
185
186
|
|
|
187
|
+
// An open menu closes first: no preview of a pop then.
|
|
188
|
+
override fun stackPoppedByBack(): StackController? = if (motion.canGoBack) null else center.stackPoppedByBack()
|
|
189
|
+
|
|
186
190
|
// A button's press addressed to the side menu goes to the center.
|
|
187
191
|
override fun sendOnNavigationButtonPressed(buttonId: String) = center.sendOnNavigationButtonPressed(buttonId)
|
|
188
192
|
|
|
@@ -26,8 +26,8 @@ import dev.apexnative.navigation.viewcontrollers.navigator.Navigator
|
|
|
26
26
|
import dev.apexnative.navigation.viewcontrollers.parent.ChildOffsetFromRest
|
|
27
27
|
import dev.apexnative.navigation.viewcontrollers.parent.ParentController
|
|
28
28
|
import dev.apexnative.navigation.viewcontrollers.parent.ReturnsScreenFromColumn
|
|
29
|
-
import dev.apexnative.navigation.viewcontrollers.parent.ShowsColumns
|
|
30
29
|
import dev.apexnative.navigation.viewcontrollers.parent.ShownChildren
|
|
30
|
+
import dev.apexnative.navigation.viewcontrollers.parent.ShowsColumns
|
|
31
31
|
import dev.apexnative.navigation.viewcontrollers.stack.StackController
|
|
32
32
|
import dev.apexnative.navigation.viewcontrollers.viewcontroller.Presenter
|
|
33
33
|
import dev.apexnative.navigation.viewcontrollers.viewcontroller.ViewController
|
|
@@ -366,6 +366,19 @@ class SplitViewController(
|
|
|
366
366
|
return can || super.canHandleBack()
|
|
367
367
|
}
|
|
368
368
|
|
|
369
|
+
// As handleBack: the pane whose back goes first decides; an open sliding pane that would close previews nothing.
|
|
370
|
+
override fun stackPoppedByBack(): StackController? {
|
|
371
|
+
if (!isViewCreated) return super.stackPoppedByBack()
|
|
372
|
+
val layout = getView()
|
|
373
|
+
return when {
|
|
374
|
+
layout.isCompact -> compact?.stackPoppedByBack()
|
|
375
|
+
isSliding && !layout.panes.isOpen -> primary.stackPoppedByBack()
|
|
376
|
+
isSliding -> if (secondary.canHandleBack()) secondary.stackPoppedByBack() else null
|
|
377
|
+
secondary.canHandleBack() -> secondary.stackPoppedByBack()
|
|
378
|
+
else -> primary.stackPoppedByBack()
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
369
382
|
/**
|
|
370
383
|
* How far the pane of [child] is from where it rests: the secondary pane sliding over the primary one rests over
|
|
371
384
|
* it, closed or on its way. Zero for the other children.
|