@apexnative/navigation 1.0.0-alpha.1 → 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 +38 -0
- package/README.md +147 -51
- package/android/CLAUDE.md +31 -6
- package/android/CORE.md +7 -2
- package/android/RECIPES.md +4 -3
- 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/react/ArrangementStates.kt +5 -4
- package/android/src/main/java/dev/apexnative/navigation/react/ErrorTexts.kt +75 -60
- package/android/src/main/java/dev/apexnative/navigation/react/Hinge.kt +3 -2
- package/android/src/main/java/dev/apexnative/navigation/react/OptionsCommands.kt +1 -1
- package/android/src/main/java/dev/apexnative/navigation/react/ScreenInsets.kt +5 -4
- package/android/src/main/java/dev/apexnative/navigation/react/ScreenRegions.kt +7 -6
- package/android/src/main/java/dev/apexnative/navigation/react/events/AppearanceDispatcher.kt +3 -35
- package/android/src/main/java/dev/apexnative/navigation/react/events/EventContract.kt +226 -0
- package/android/src/main/java/dev/apexnative/navigation/react/events/EventEmitter.kt +101 -87
- package/android/src/main/java/dev/apexnative/navigation/react/events/ScreenAppearance.kt +0 -6
- package/android/src/main/java/dev/apexnative/navigation/utils/UiUtils.kt +3 -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/bottomtabs/TabItems.kt +3 -3
- 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 +86 -16
- 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 +41 -23
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalViews.kt +24 -20
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/SheetStops.kt +25 -0
- 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 +42 -4
- 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 +5 -3
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuMotion.kt +4 -7
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuPresenter.kt +16 -7
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuRules.kt +8 -1
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/splitview/SplitViewController.kt +16 -2
- 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 +123 -8
- 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/bottomtabs/BottomTabs.kt +5 -5
- package/android/src/main/java/dev/apexnative/navigation/views/element/ElementStep.kt +31 -0
- package/android/src/main/java/dev/apexnative/navigation/views/element/ElementTransitions.kt +85 -27
- package/android/src/main/java/dev/apexnative/navigation/views/element/TargetsWait.kt +5 -4
- package/android/src/main/java/dev/apexnative/navigation/views/element/TransitionTargets.kt +9 -6
- package/android/src/main/java/dev/apexnative/navigation/views/element/TransitionViews.kt +100 -0
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ElementPictures.kt +45 -1
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ImageMatrixAnimator.kt +3 -5
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PlainViewAnimator.kt +3 -5
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ReactImageAnimator.kt +3 -5
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/SharedAnimatorFactory.kt +2 -0
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/TextChangeAnimator.kt +4 -6
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ViewChangeAnimator.kt +4 -6
- 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/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonBar.kt +4 -3
- package/cli/app.js +1 -1
- package/cli/project.js +6 -2
- package/cli/setup/index.js +13 -0
- package/cli/setup/steps/appDelegate.js +66 -11
- package/cli/setup/steps/sceneDelegate.js +108 -0
- package/docs-md/architecture/transitions.md +7 -1
- 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/guides/deep-links.md +15 -33
- 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/docs-md/start/installation.md +1 -0
- package/ios/Animations/ANNTransitionAnimator.mm +13 -11
- package/ios/Animations/ANNTransitionWait.mm +6 -7
- package/ios/Animations/Elements/ANNElementFinder.h +57 -0
- package/ios/Animations/Elements/ANNElementFinder.mm +174 -2
- package/ios/Animations/Elements/ANNElementTransition.h +7 -5
- package/ios/Animations/Elements/ANNElementTransition.mm +17 -19
- package/ios/Animations/Elements/ANNSharedElementFlight.h +13 -8
- package/ios/Animations/Elements/ANNSharedElementFlight.mm +59 -24
- package/ios/CLAUDE.md +10 -2
- package/ios/CORE.md +3 -2
- package/ios/Contract/ANNCommandAnswer.h +4 -3
- package/ios/Contract/ANNCommandAnswer.mm +12 -2
- package/ios/Contract/ANNCommandRunner.h +2 -2
- package/ios/Contract/ANNCommandRunner.mm +1 -1
- package/ios/Contract/ANNErrorTexts.h +6 -2
- package/ios/Contract/ANNErrorTexts.mm +18 -10
- package/ios/Core/ANNDeepLinkQueue.h +20 -1
- package/ios/Core/ANNDeepLinkQueue.mm +107 -12
- package/ios/Core/ANNErrorHandler.h +8 -3
- package/ios/Core/ANNErrorHandler.mm +17 -1
- package/ios/Core/ANNEventContract.h +246 -0
- package/ios/Core/ANNEventContract.mm +225 -0
- package/ios/Core/ANNEventEmitter.h +5 -1
- package/ios/Core/ANNEventEmitter.mm +117 -80
- package/ios/Core/ANNHinge.mm +6 -4
- package/ios/Core/ANNSceneDelegate.mm +25 -15
- package/ios/Core/ANNSystemUI.h +3 -2
- package/ios/Core/ANNSystemUI.mm +7 -0
- package/ios/Layouts/Arrangement/ANNArrangementViewController.mm +6 -4
- package/ios/Layouts/Modal/ANNModalCoordinator.mm +1 -1
- package/ios/Layouts/Modal/ANNModalPresentation.h +8 -5
- package/ios/Layouts/Modal/ANNModalPresentation.mm +12 -12
- package/ios/Layouts/Modal/ANNSheetPresenter.mm +83 -1
- package/ios/Layouts/Root/ANNRootCoordinator.mm +12 -3
- package/ios/Layouts/Screen/ANNComponentPresenter.h +11 -3
- package/ios/Layouts/Screen/ANNComponentPresenter.mm +34 -6
- package/ios/Layouts/Screen/ANNComponentViewController.mm +28 -7
- package/ios/Layouts/Screen/ANNNativeScreenContext.mm +7 -5
- package/ios/Layouts/Screen/ANNScreenInsets.mm +6 -4
- package/ios/Layouts/Screen/ANNScreenRegions.mm +9 -6
- package/ios/Layouts/SplitView/ANNSplitViewController.mm +4 -7
- package/ios/Options/ANNSheetOptions.h +4 -1
- package/ios/Public/ApexNavigation/ANNSceneDelegate.h +3 -0
- package/ios/RECIPES.md +2 -1
- package/ios/TurboModules/ANNTurboEventEmitter.mm +4 -35
- package/ios/TurboModules/ANNTurboModule.mm +5 -2
- package/lib/module/ARCHITECTURE.md +7 -4
- package/lib/module/api/app/createNavigation.js +2 -0
- package/lib/module/api/app/createNavigation.js.map +1 -1
- package/lib/module/api/hooks/useNavigation.js +8 -3
- package/lib/module/api/hooks/useNavigation.js.map +1 -1
- package/lib/module/api/linking/dispatchedLinks.js +23 -0
- package/lib/module/api/linking/dispatchedLinks.js.map +1 -0
- package/lib/module/interfaces/contract/errors.json +275 -0
- package/lib/typescript/api/app/createNavigation.d.ts.map +1 -1
- package/lib/typescript/api/hooks/useNavigation.d.ts.map +1 -1
- package/lib/typescript/api/linking/dispatchedLinks.d.ts +7 -0
- package/lib/typescript/api/linking/dispatchedLinks.d.ts.map +1 -0
- package/lib/typescript/interfaces/Events.d.ts +9 -0
- package/lib/typescript/interfaces/Events.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/animations.d.ts +27 -18
- package/lib/typescript/interfaces/options/animations.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/modals.d.ts +33 -24
- package/lib/typescript/interfaces/options/modals.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/sideMenu.d.ts +7 -6
- package/lib/typescript/interfaces/options/sideMenu.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/system.d.ts +10 -6
- package/lib/typescript/interfaces/options/system.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/ARCHITECTURE.md +7 -4
- package/src/api/app/createNavigation.ts +2 -0
- package/src/api/hooks/useNavigation.ts +8 -3
- package/src/api/linking/dispatchedLinks.ts +18 -0
- package/src/interfaces/Events.ts +10 -0
- package/src/interfaces/contract/errors.json +275 -0
- package/src/interfaces/options/animations.ts +27 -18
- package/src/interfaces/options/modals.ts +33 -24
- package/src/interfaces/options/sideMenu.ts +7 -6
- package/src/interfaces/options/system.ts +10 -6
|
@@ -94,6 +94,19 @@ abstract class ViewController<V : ViewGroup>(
|
|
|
94
94
|
open val isInSheet: Boolean
|
|
95
95
|
get() = shownAsSheet || parent?.isInSheet == true
|
|
96
96
|
|
|
97
|
+
/**
|
|
98
|
+
* The node is the root of an overlay: the overlays set it before the node's view is made, so its screens are drawn
|
|
99
|
+
* under the status bar from their first layout (`StatusBarOptions.isHiddenOrDrawBehind`).
|
|
100
|
+
*/
|
|
101
|
+
internal var shownAsOverlay = false
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The node is in an overlay: it covers the whole window, and is drawn under the status bar by default. It holds from
|
|
105
|
+
* the start of the show, while `OverlayCoordinator.isInOverlay` answers only for the overlays it lists.
|
|
106
|
+
*/
|
|
107
|
+
open val isInOverlay: Boolean
|
|
108
|
+
get() = shownAsOverlay || parent?.isInOverlay == true
|
|
109
|
+
|
|
97
110
|
/**
|
|
98
111
|
* The node is in a side of a side menu (its left or right menu, not its center): the side goes the whole height of
|
|
99
112
|
* the window, and the node keeps no room for the status bar. The layout around answers for it
|
|
@@ -265,6 +278,12 @@ abstract class ViewController<V : ViewGroup>(
|
|
|
265
278
|
/** Whether [handleBack] would go back. */
|
|
266
279
|
open fun canHandleBack(): Boolean = false
|
|
267
280
|
|
|
281
|
+
/**
|
|
282
|
+
* The stack whose top [handleBack] would take away with the default pop, which a back gesture previews; none when
|
|
283
|
+
* back would do anything else (hold, close a menu or a pane, a pop of its own) or nothing.
|
|
284
|
+
*/
|
|
285
|
+
internal open fun stackPoppedByBack(): StackController? = null
|
|
286
|
+
|
|
268
287
|
/** Starts the node's content (a native screen's view, a React screen's surface). */
|
|
269
288
|
open fun start() = Unit
|
|
270
289
|
|
|
@@ -96,6 +96,9 @@ internal object Curves {
|
|
|
96
96
|
* linear between them — the values of AndroidX's `FastOutSlowInInterpolator`, computed here.
|
|
97
97
|
*/
|
|
98
98
|
object FastOutSlowIn : Curve() {
|
|
99
|
+
// Before the table, which is made from it.
|
|
100
|
+
private val curve = CubicBezier(0.4, 0.0, 0.2, 1.0)
|
|
101
|
+
|
|
99
102
|
/** The 201 values. */
|
|
100
103
|
val table: DoubleArray = DoubleArray(TABLE_STEPS + 1) {
|
|
101
104
|
round(bezierAt(it.toDouble() / TABLE_STEPS) * DECIMALS) / DECIMALS
|
|
@@ -109,7 +112,25 @@ internal object Curves {
|
|
|
109
112
|
}
|
|
110
113
|
|
|
111
114
|
/** y of the Bézier at its x = [x], exactly (to 1e-12). */
|
|
112
|
-
fun bezierAt(x: Double): Double
|
|
115
|
+
fun bezierAt(x: Double): Double = curve.yAt(x)
|
|
116
|
+
|
|
117
|
+
private const val DECIMALS = 1e4
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* A cubic Bézier from (0, 0) to (1, 1) with the control points ([x1], [y1]) and ([x2], [y2]), as the framework's
|
|
122
|
+
* `PathInterpolator` draws it, computed exactly: the system's curves without a framework class.
|
|
123
|
+
*/
|
|
124
|
+
class CubicBezier(
|
|
125
|
+
private val x1: Double,
|
|
126
|
+
private val y1: Double,
|
|
127
|
+
private val x2: Double,
|
|
128
|
+
private val y2: Double,
|
|
129
|
+
) : Curve() {
|
|
130
|
+
override fun at(p: Double): Double = yAt(p)
|
|
131
|
+
|
|
132
|
+
/** y of the curve at its x = [x], exactly (to 1e-12). */
|
|
133
|
+
fun yAt(x: Double): Double {
|
|
113
134
|
if (x <= 0.0) return 0.0
|
|
114
135
|
if (x >= 1.0) return 1.0
|
|
115
136
|
// x(t) only grows with t: a bisection finds t, then y(t).
|
|
@@ -118,23 +139,20 @@ internal object Curves {
|
|
|
118
139
|
var t = x
|
|
119
140
|
repeat(BISECTION_STEPS) {
|
|
120
141
|
t = (low + high) / 2
|
|
121
|
-
if (
|
|
142
|
+
if (coordinate(t, x1, x2) < x) low = t else high = t
|
|
122
143
|
}
|
|
123
|
-
return
|
|
144
|
+
return coordinate(t, y1, y2)
|
|
124
145
|
}
|
|
125
146
|
|
|
126
|
-
// One coordinate of the curve
|
|
127
|
-
private fun
|
|
147
|
+
// One coordinate of the curve with the control points' coordinates [c1], [c2].
|
|
148
|
+
private fun coordinate(t: Double, c1: Double, c2: Double): Double {
|
|
128
149
|
val u = 1 - t
|
|
129
150
|
return 3 * u * u * t * c1 + 3 * u * t * t * c2 + t * t * t
|
|
130
151
|
}
|
|
131
152
|
|
|
132
|
-
private
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
private const val Y2 = 1.0
|
|
136
|
-
private const val BISECTION_STEPS = 60
|
|
137
|
-
private const val DECIMALS = 1e4
|
|
153
|
+
private companion object {
|
|
154
|
+
const val BISECTION_STEPS = 60
|
|
155
|
+
}
|
|
138
156
|
}
|
|
139
157
|
|
|
140
158
|
/**
|
|
@@ -98,8 +98,8 @@ class BottomTabs(context: Context) : BottomNavigationView(MaterialTheme.context(
|
|
|
98
98
|
var iconInactiveColor: Int? = null
|
|
99
99
|
var titleActiveColor: Int? = null
|
|
100
100
|
var titleInactiveColor: Int? = null
|
|
101
|
-
var iconWidth:
|
|
102
|
-
var iconHeight:
|
|
101
|
+
var iconWidth: Double? = null
|
|
102
|
+
var iconHeight: Double? = null
|
|
103
103
|
var typeface: Typeface? = null
|
|
104
104
|
var activeTextSize: Float? = null
|
|
105
105
|
var inactiveTextSize: Float? = null
|
|
@@ -246,9 +246,9 @@ class BottomTabs(context: Context) : BottomNavigationView(MaterialTheme.context(
|
|
|
246
246
|
|
|
247
247
|
fun setTabTestId(index: Int, testId: String?) = updateStyle(index) { it.testId = testId }
|
|
248
248
|
|
|
249
|
-
fun setIconWidth(index: Int, width:
|
|
249
|
+
fun setIconWidth(index: Int, width: Double?) = updateStyle(index) { it.iconWidth = width }
|
|
250
250
|
|
|
251
|
-
fun setIconHeight(index: Int, height:
|
|
251
|
+
fun setIconHeight(index: Int, height: Double?) = updateStyle(index) { it.iconHeight = height }
|
|
252
252
|
|
|
253
253
|
fun setTitleTypeface(index: Int, typeface: Typeface?) = updateStyle(index) { it.typeface = typeface }
|
|
254
254
|
|
|
@@ -475,7 +475,7 @@ class BottomTabs(context: Context) : BottomNavigationView(MaterialTheme.context(
|
|
|
475
475
|
view.setIconTintList(tint(style.iconActiveColor, style.iconInactiveColor, itemIconTintList))
|
|
476
476
|
view.setTextColor(tint(style.titleActiveColor, style.titleInactiveColor, itemTextColor))
|
|
477
477
|
val iconSize = style.iconWidth ?: style.iconHeight
|
|
478
|
-
view.setIconSize(if (iconSize != null) UiUtils.
|
|
478
|
+
view.setIconSize(if (iconSize != null) UiUtils.dpToPixels(context, iconSize) else itemIconSize)
|
|
479
479
|
view.findViewById<TextView>(MaterialR.id.navigation_bar_item_small_label_view)?.let {
|
|
480
480
|
style.typeface?.let { typeface -> it.typeface = typeface }
|
|
481
481
|
style.inactiveTextSize?.let { size -> it.setTextSize(TypedValue.COMPLEX_UNIT_SP, size) }
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
package dev.apexnative.navigation.views.element
|
|
2
|
+
|
|
3
|
+
import dev.apexnative.navigation.utils.DevelopmentLog
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The isolation of every element of the transitions: [work], a step of the element named [element] (its `nativeID` in
|
|
7
|
+
* quotes, or a pair's two), runs; an exception in it skips that element only — one line in the log
|
|
8
|
+
* ([elementSkipped]), and false — while the rest of the transition plays and its command completes.
|
|
9
|
+
*/
|
|
10
|
+
internal inline fun elementStep(
|
|
11
|
+
element: String,
|
|
12
|
+
noinline log: (String) -> Unit = DevelopmentLog::warn,
|
|
13
|
+
work: () -> Unit,
|
|
14
|
+
): Boolean = try {
|
|
15
|
+
work()
|
|
16
|
+
true
|
|
17
|
+
} catch (e: Exception) {
|
|
18
|
+
elementSkipped(element, e.toString(), log)
|
|
19
|
+
false
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** The line in the log for the element named [element], skipped for [reason]: it shows and hides with its screen. */
|
|
23
|
+
internal fun elementSkipped(element: String, reason: String, log: (String) -> Unit = DevelopmentLog::warn) =
|
|
24
|
+
log("[ApexNavigation] the transition's element $element is skipped, it shows and hides with its screen while the " +
|
|
25
|
+
"rest plays: $reason")
|
|
26
|
+
|
|
27
|
+
/** The name of an element in the log. */
|
|
28
|
+
internal fun elementName(nativeId: String) = "'$nativeId'"
|
|
29
|
+
|
|
30
|
+
/** The name of a shared pair in the log. */
|
|
31
|
+
internal fun pairName(fromId: String, toId: String) = "'$fromId' -> '$toId'"
|
|
@@ -7,7 +7,6 @@ import android.os.Build
|
|
|
7
7
|
import android.util.Log
|
|
8
8
|
import android.view.View
|
|
9
9
|
import android.view.ViewGroup
|
|
10
|
-
import com.facebook.react.uimanager.util.ReactFindViewUtil
|
|
11
10
|
import com.facebook.react.views.view.ReactViewGroup
|
|
12
11
|
import dev.apexnative.navigation.options.ElementTransitionOptions
|
|
13
12
|
import dev.apexnative.navigation.options.LayoutAnimation
|
|
@@ -23,12 +22,13 @@ import dev.apexnative.navigation.views.element.animators.SharedAnimatorFactory
|
|
|
23
22
|
* The elements of one transition: the element transitions and the targets of the shared elements found by their
|
|
24
23
|
* `nativeID` (depth first, the first match) in the whole view of the screen each side shows — an element in the screen
|
|
25
24
|
* that goes first, then in the one that comes; a pair's `fromId` in the screen that goes, its `toId` in the one that
|
|
26
|
-
* comes
|
|
27
|
-
*
|
|
28
|
-
* it
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
25
|
+
* comes; each screen is walked once for all of them ([TransitionViews]). What isn't found, what is in another
|
|
26
|
+
* transition's layer and an image that isn't loaded is skipped; so is an element whose start fails ([elementStep]):
|
|
27
|
+
* it goes back at once and the rest plays. Each view found moves into a [TransitionLayer] at its place on the screen,
|
|
28
|
+
* in the order its hierarchy draws it, and an [ElementStub] takes its index in its parent. A pair's `from` stays where
|
|
29
|
+
* it is, hidden while its copy flies. [finish] puts every view back at its index with its layout, its pivot and the
|
|
30
|
+
* values of its properties read before — unless React removed its stub meanwhile: then it is gone for good —, shows
|
|
31
|
+
* the `from`s again and takes the layer away. Main thread only.
|
|
32
32
|
*/
|
|
33
33
|
internal class ElementTransition private constructor(private val layer: TransitionLayer) {
|
|
34
34
|
// A view in the layer: where it came from and what it was.
|
|
@@ -89,18 +89,41 @@ internal class ElementTransition private constructor(private val layer: Transiti
|
|
|
89
89
|
view.rotationX, view.rotationY, view.pivotX, view.pivotY,
|
|
90
90
|
)
|
|
91
91
|
val stub = ElementStub(view.context, view)
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
92
|
+
try {
|
|
93
|
+
parent.removeViewAt(index)
|
|
94
|
+
parent.addView(stub, index, params)
|
|
95
|
+
stub.layout(frame.left, frame.top, frame.right, frame.bottom)
|
|
96
|
+
val left = originX + frame.left
|
|
97
|
+
val top = originY + frame.top
|
|
98
|
+
layer.put(view, left, top, frame.width(), frame.height())
|
|
99
|
+
} catch (e: RuntimeException) {
|
|
100
|
+
putBack(view, stub, parent, index, params, frame)
|
|
101
|
+
throw e
|
|
102
|
+
}
|
|
98
103
|
// Scale and rotations turn about the center.
|
|
99
104
|
view.pivotX = frame.width() / 2f
|
|
100
105
|
view.pivotY = frame.height() / 2f
|
|
101
106
|
return Moved(view, stub, params, frame, values, pivotSet, originX, originY).also { moved += it }
|
|
102
107
|
}
|
|
103
108
|
|
|
109
|
+
// A move that failed half way undone: the view back at its index in its parent, the stub gone.
|
|
110
|
+
private fun putBack(
|
|
111
|
+
view: View,
|
|
112
|
+
stub: ElementStub,
|
|
113
|
+
parent: ViewGroup,
|
|
114
|
+
index: Int,
|
|
115
|
+
params: ViewGroup.LayoutParams,
|
|
116
|
+
frame: Rect,
|
|
117
|
+
) {
|
|
118
|
+
if (view.parent === layer) layer.removeView(view)
|
|
119
|
+
val stubIndex = parent.indexOfChild(stub)
|
|
120
|
+
if (stubIndex >= 0) parent.removeViewAt(stubIndex)
|
|
121
|
+
if (view.parent == null) {
|
|
122
|
+
parent.addView(view, index.coerceAtMost(parent.childCount), params)
|
|
123
|
+
view.layout(frame.left, frame.top, frame.right, frame.bottom)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
104
127
|
private fun giveBack(item: Moved) {
|
|
105
128
|
val view = item.view
|
|
106
129
|
// A commit of Fabric meanwhile laid it out in its parent's coordinates: the newest such frame holds.
|
|
@@ -156,13 +179,26 @@ internal class ElementTransition private constructor(private val layer: Transiti
|
|
|
156
179
|
from, to, toParent, layer, options, fallbackDuration, DevelopmentLog::warn,
|
|
157
180
|
) ?: return false
|
|
158
181
|
val alpha = item.values[ALPHA]
|
|
182
|
+
val fromAlpha = from.alpha
|
|
159
183
|
to.alpha = 0f
|
|
160
|
-
hidden += from to
|
|
184
|
+
hidden += from to fromAlpha
|
|
161
185
|
flight.animator.addListener(object : AnimatorListenerAdapter() {
|
|
186
|
+
private var stopped = false
|
|
187
|
+
|
|
162
188
|
override fun onAnimationStart(animation: Animator) {
|
|
189
|
+
if (stopped) return
|
|
163
190
|
from.alpha = 0f
|
|
164
191
|
if (!flight.drawsPictures) to.alpha = alpha
|
|
165
192
|
}
|
|
193
|
+
|
|
194
|
+
// A flight whose start or frame failed stops (`MoveAnimator`), maybe inside its own start: `from` shows
|
|
195
|
+
// again in its screen, and a `to` that moves itself in its place, as at any stop (a flight of pictures
|
|
196
|
+
// shows its `to` at its end).
|
|
197
|
+
override fun onAnimationCancel(animation: Animator) {
|
|
198
|
+
stopped = true
|
|
199
|
+
from.alpha = fromAlpha
|
|
200
|
+
if (!flight.drawsPictures) to.alpha = alpha
|
|
201
|
+
}
|
|
166
202
|
})
|
|
167
203
|
animators += flight.animator
|
|
168
204
|
return true
|
|
@@ -195,20 +231,23 @@ internal class ElementTransition private constructor(private val layer: Transiti
|
|
|
195
231
|
): ElementTransition? {
|
|
196
232
|
val fromView = from?.let { AppearanceDispatcher.visibleNode(it).peekView() }
|
|
197
233
|
val toView = AppearanceDispatcher.visibleNode(to).peekView()
|
|
234
|
+
val transitions = animation.elementTransitions.transitions
|
|
235
|
+
val shared = animation.sharedElements.get()
|
|
236
|
+
val views = TransitionViews(shared, transitions, fromView, toView)
|
|
198
237
|
val taken = HashSet<View>()
|
|
199
238
|
val elements = ArrayList<Pair<ElementTransitionOptions, View>>()
|
|
200
|
-
for (element in
|
|
239
|
+
for (element in transitions) {
|
|
201
240
|
if (element.id.isEmpty()) continue
|
|
202
|
-
val view =
|
|
241
|
+
val view = found(views.leaving(element.id)) ?: found(views.arriving(element.id)) ?: continue
|
|
203
242
|
if (TransitionLayer.holds(view) || !taken.add(view)) continue
|
|
204
243
|
elements += element to view
|
|
205
244
|
}
|
|
206
245
|
val pairs = ArrayList<Triple<SharedElementTransitionOptions, View, View>>()
|
|
207
|
-
for (pair in
|
|
246
|
+
for (pair in shared) {
|
|
208
247
|
val fromId = pair.fromId.get(null) ?: continue
|
|
209
248
|
val toId = pair.toId.get(null) ?: continue
|
|
210
|
-
val source =
|
|
211
|
-
val target =
|
|
249
|
+
val source = found(views.leaving(fromId)) ?: continue
|
|
250
|
+
val target = found(views.arriving(toId)) ?: continue
|
|
212
251
|
if (source === target || TransitionLayer.holds(source) || TransitionLayer.holds(target)) continue
|
|
213
252
|
if (target in taken || source in taken) continue
|
|
214
253
|
if (!loaded(source, fromId) || !loaded(target, toId)) continue
|
|
@@ -246,8 +285,7 @@ internal class ElementTransition private constructor(private val layer: Transiti
|
|
|
246
285
|
null
|
|
247
286
|
}
|
|
248
287
|
|
|
249
|
-
private fun
|
|
250
|
-
root?.let { ReactFindViewUtil.findView(it, nativeId) }?.takeUnless { it is ElementStub }
|
|
288
|
+
private fun found(view: View?): View? = view?.takeUnless { it is ElementStub }
|
|
251
289
|
|
|
252
290
|
// An image not loaded drops out of the transition; its wait logged it already, else it is logged now.
|
|
253
291
|
private fun loaded(view: View, id: String): Boolean {
|
|
@@ -258,24 +296,44 @@ internal class ElementTransition private constructor(private val layer: Transiti
|
|
|
258
296
|
}
|
|
259
297
|
|
|
260
298
|
// The views into the layer in the order they are drawn, then their animators; a pair that doesn't fly goes back at
|
|
261
|
-
// once.
|
|
299
|
+
// once. Each element's steps run on their own ([elementStep]): one that fails goes back, the others play.
|
|
262
300
|
private fun fill(
|
|
263
301
|
host: ViewGroup,
|
|
264
302
|
elements: List<Pair<ElementTransitionOptions, View>>,
|
|
265
303
|
pairs: List<Triple<SharedElementTransitionOptions, View, View>>,
|
|
266
304
|
fallbackDuration: Long,
|
|
267
305
|
) {
|
|
306
|
+
val names = HashMap<View, String>()
|
|
307
|
+
for ((options, view) in elements) names[view] = elementName(options.id)
|
|
308
|
+
for ((options, _, target) in pairs) names[target] = pairName(options.fromId.get(""), options.toId.get(""))
|
|
268
309
|
val views = elements.map { it.second } + pairs.map { it.third }
|
|
269
310
|
val parents = views.associateWith { it.parent as? View }
|
|
270
311
|
val placed = HashMap<View, Moved>()
|
|
271
|
-
for (view in DrawOrder.sorted(views, host) { it })
|
|
272
|
-
|
|
312
|
+
for (view in DrawOrder.sorted(views, host) { it }) {
|
|
313
|
+
var item: Moved? = null
|
|
314
|
+
if (elementStep(names.getValue(view)) { item = moveIntoLayer(view) }) item?.let { placed[view] = it }
|
|
315
|
+
}
|
|
316
|
+
for ((options, view) in elements) {
|
|
317
|
+
val item = placed[view] ?: continue
|
|
318
|
+
if (!elementStep(names.getValue(view)) { addElement(options, item) }) goBack(item)
|
|
319
|
+
}
|
|
273
320
|
for ((options, source, target) in pairs) {
|
|
274
321
|
val item = placed[target] ?: continue
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
322
|
+
var flies = false
|
|
323
|
+
elementStep(names.getValue(target)) {
|
|
324
|
+
flies = addPair(options, source, item, parents[target], fallbackDuration)
|
|
278
325
|
}
|
|
326
|
+
if (!flies) goBack(item)
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// An element that doesn't play leaves the layer now; one that can't go back is logged, the others go on.
|
|
331
|
+
private fun goBack(item: Moved) {
|
|
332
|
+
moved -= item
|
|
333
|
+
try {
|
|
334
|
+
giveBack(item)
|
|
335
|
+
} catch (e: RuntimeException) {
|
|
336
|
+
Log.w(NavigationLog.TAG, "[ApexNavigation] An element of a transition couldn't go back", e)
|
|
279
337
|
}
|
|
280
338
|
}
|
|
281
339
|
}
|
|
@@ -3,7 +3,6 @@ package dev.apexnative.navigation.views.element
|
|
|
3
3
|
import android.util.Log
|
|
4
4
|
import android.view.Choreographer
|
|
5
5
|
import android.view.View
|
|
6
|
-
import com.facebook.react.uimanager.util.ReactFindViewUtil
|
|
7
6
|
import dev.apexnative.navigation.options.LayoutAnimation
|
|
8
7
|
import dev.apexnative.navigation.react.events.AppearanceDispatcher
|
|
9
8
|
import dev.apexnative.navigation.utils.DevelopmentLog
|
|
@@ -72,12 +71,14 @@ internal object TargetsWait {
|
|
|
72
71
|
): () -> Unit {
|
|
73
72
|
val fromView = from?.let { AppearanceDispatcher.visibleNode(it).peekView() }
|
|
74
73
|
val toView = AppearanceDispatcher.visibleNode(to).peekView()
|
|
74
|
+
val pairs = animation.sharedElements.get()
|
|
75
|
+
val views = TransitionViews(pairs, emptyList(), fromView, toView)
|
|
75
76
|
val images = ArrayList<ImageWait.Image?>()
|
|
76
|
-
for (pair in
|
|
77
|
+
for (pair in pairs) {
|
|
77
78
|
val fromId = pair.fromId.get(null) ?: continue
|
|
78
79
|
val toId = pair.toId.get(null) ?: continue
|
|
79
|
-
val source =
|
|
80
|
-
val target =
|
|
80
|
+
val source = views.leaving(fromId) ?: continue
|
|
81
|
+
val target = views.arriving(toId) ?: continue
|
|
81
82
|
images += imageOf(source, fromId)
|
|
82
83
|
images += imageOf(target, toId)
|
|
83
84
|
}
|
|
@@ -2,7 +2,6 @@ package dev.apexnative.navigation.views.element
|
|
|
2
2
|
|
|
3
3
|
import android.os.SystemClock
|
|
4
4
|
import android.view.ViewGroup
|
|
5
|
-
import com.facebook.react.uimanager.util.ReactFindViewUtil
|
|
6
5
|
import dev.apexnative.navigation.options.LayoutAnimation
|
|
7
6
|
import dev.apexnative.navigation.utils.DRAW_WAIT_LIMIT
|
|
8
7
|
import dev.apexnative.navigation.viewcontrollers.component.ContentReadiness
|
|
@@ -38,13 +37,17 @@ private fun areReady(animation: LayoutAnimation, from: ViewController<*>, to: Vi
|
|
|
38
37
|
val screen = to.shownScreen()
|
|
39
38
|
val content = screen.view as? ViewGroup
|
|
40
39
|
if (content != null && !ContentReadiness.isReady(screen)) return false
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
// Each screen walked once for all the ids ([TransitionViews.findViews]); an element is looked for in the screen
|
|
41
|
+
// that goes only when the one that comes doesn't have it.
|
|
42
|
+
val targets = animation.sharedElements.get().map { it.toId.get("") }.filter { it.isNotEmpty() }
|
|
43
|
+
val elements = animation.elementTransitions.transitions.map { it.id }.filter { it.isNotEmpty() }
|
|
44
|
+
if (targets.isEmpty() && elements.isEmpty()) return true
|
|
45
|
+
val inTo = TransitionViews.findViews(to.view, (targets + elements).toSet())
|
|
46
|
+
if (!targets.all { it in inTo }) return false
|
|
47
|
+
val missing = elements.filterNot { it in inTo }.toSet()
|
|
48
|
+
return missing.isEmpty() || TransitionViews.findViews(from.view, missing).size == missing.size
|
|
43
49
|
}
|
|
44
50
|
|
|
45
|
-
private fun isIn(screen: ViewController<*>, nativeId: String) =
|
|
46
|
-
nativeId.isEmpty() || ReactFindViewUtil.findView(screen.view, nativeId) != null
|
|
47
|
-
|
|
48
51
|
// The screen a layout shows: a modal of a stack shows the screen on its top.
|
|
49
52
|
private fun ViewController<*>.shownScreen(): ViewController<*> {
|
|
50
53
|
var controller: ViewController<*> = this
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
package dev.apexnative.navigation.views.element
|
|
2
|
+
|
|
3
|
+
import android.view.View
|
|
4
|
+
import android.view.ViewGroup
|
|
5
|
+
import com.facebook.react.R as ReactR
|
|
6
|
+
import dev.apexnative.navigation.options.ElementTransitionOptions
|
|
7
|
+
import dev.apexnative.navigation.options.SharedElementTransitionOptions
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The views of a transition's elements in its two screens: each screen is walked once, at its first lookup, for the ids
|
|
11
|
+
* looked for there — the screen that goes ([fromRoot]) for the pairs' `fromId`s and the elements' ids, the one that
|
|
12
|
+
* comes ([toRoot]) for the pairs' `toId`s and the elements' ids the other doesn't have. A lookup finds what
|
|
13
|
+
* `ReactFindViewUtil.findView` finds in that screen as it was at its walk (depth first, the first match). Main thread
|
|
14
|
+
* only.
|
|
15
|
+
*/
|
|
16
|
+
internal class TransitionViews(
|
|
17
|
+
pairs: List<SharedElementTransitionOptions>,
|
|
18
|
+
elements: List<ElementTransitionOptions>,
|
|
19
|
+
private val fromRoot: View?,
|
|
20
|
+
private val toRoot: View?,
|
|
21
|
+
) {
|
|
22
|
+
// The ids each screen is walked for: the screen that goes for the sources and the elements, the one that comes for
|
|
23
|
+
// the targets (and the elements the other screen doesn't have, added at its walk).
|
|
24
|
+
private val leavingIds = HashSet<String>()
|
|
25
|
+
private val arrivingIds = HashSet<String>()
|
|
26
|
+
private val elementIds = ArrayList<String>()
|
|
27
|
+
private var leavingViews: Map<String, View>? = null
|
|
28
|
+
private var arrivingViews: Map<String, View>? = null
|
|
29
|
+
|
|
30
|
+
init {
|
|
31
|
+
for (pair in pairs) {
|
|
32
|
+
pair.fromId.get(null)?.takeIf { it.isNotEmpty() }?.let(leavingIds::add)
|
|
33
|
+
pair.toId.get(null)?.takeIf { it.isNotEmpty() }?.let(arrivingIds::add)
|
|
34
|
+
}
|
|
35
|
+
for (element in elements) {
|
|
36
|
+
if (element.id.isEmpty()) continue
|
|
37
|
+
leavingIds += element.id
|
|
38
|
+
elementIds += element.id
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** The view with [nativeId] in the screen that goes; none when none has it or it isn't one of the transition's. */
|
|
43
|
+
fun leaving(nativeId: String): View? {
|
|
44
|
+
val views = leavingViews ?: findViews(fromRoot, leavingIds).also { leavingViews = it }
|
|
45
|
+
return views[nativeId]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** The view with [nativeId] in the screen that comes; none when none has it or it isn't one of the transition's. */
|
|
49
|
+
fun arriving(nativeId: String): View? {
|
|
50
|
+
val views = arrivingViews ?: run {
|
|
51
|
+
// An element is looked for here only when the screen that goes doesn't have it: the walk stops sooner.
|
|
52
|
+
for (id in elementIds) if (leaving(id) == null) arrivingIds += id
|
|
53
|
+
findViews(toRoot, arrivingIds).also { arrivingViews = it }
|
|
54
|
+
}
|
|
55
|
+
return views[nativeId]
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
companion object {
|
|
59
|
+
/** The views with the `nativeID`s of [nativeIds] under [root], in one walk ([firstByKey]). */
|
|
60
|
+
fun findViews(root: View?, nativeIds: Set<String>): Map<String, View> {
|
|
61
|
+
if (root == null || nativeIds.isEmpty()) return emptyMap()
|
|
62
|
+
return firstByKey(
|
|
63
|
+
root,
|
|
64
|
+
nativeIds,
|
|
65
|
+
keyOf = { it.getTag(ReactR.id.view_tag_native_id) as? String },
|
|
66
|
+
childCount = { (it as? ViewGroup)?.childCount ?: 0 },
|
|
67
|
+
childAt = { view, index -> (view as ViewGroup).getChildAt(index) },
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* For each key of [keys], the first node with it in a depth-first walk from [root] (the node, then its children in
|
|
75
|
+
* order): what a search for that one key finds, for all of them in one walk, which stops once every key is found. A key
|
|
76
|
+
* no node has is left out. Inline: the walk calls no function objects.
|
|
77
|
+
*/
|
|
78
|
+
internal inline fun <N : Any> firstByKey(
|
|
79
|
+
root: N,
|
|
80
|
+
keys: Set<String>,
|
|
81
|
+
keyOf: (N) -> String?,
|
|
82
|
+
childCount: (N) -> Int,
|
|
83
|
+
childAt: (N, Int) -> N,
|
|
84
|
+
): Map<String, N> {
|
|
85
|
+
val found = HashMap<String, N>()
|
|
86
|
+
if (keys.isEmpty()) return found
|
|
87
|
+
// A stack of the nodes still to visit, the next one on top: the order of the recursive search without its depth.
|
|
88
|
+
val stack = ArrayList<N>()
|
|
89
|
+
stack.add(root)
|
|
90
|
+
while (stack.isNotEmpty()) {
|
|
91
|
+
val node = stack.removeAt(stack.size - 1)
|
|
92
|
+
val key = keyOf(node)
|
|
93
|
+
if (key != null && key in keys && key !in found) {
|
|
94
|
+
found[key] = node
|
|
95
|
+
if (found.size == keys.size) break
|
|
96
|
+
}
|
|
97
|
+
for (index in childCount(node) - 1 downTo 0) stack.add(childAt(node, index))
|
|
98
|
+
}
|
|
99
|
+
return found
|
|
100
|
+
}
|
package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ElementPictures.kt
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
package dev.apexnative.navigation.views.element.animators
|
|
2
2
|
|
|
3
|
+
import android.animation.Animator
|
|
4
|
+
import android.animation.AnimatorListenerAdapter
|
|
3
5
|
import android.animation.TimeInterpolator
|
|
4
6
|
import android.animation.ValueAnimator
|
|
5
7
|
import android.graphics.Picture
|
|
@@ -7,6 +9,7 @@ import android.util.Log
|
|
|
7
9
|
import android.view.View
|
|
8
10
|
import android.view.animation.LinearInterpolator
|
|
9
11
|
import dev.apexnative.navigation.utils.NavigationLog
|
|
12
|
+
import dev.apexnative.navigation.views.element.elementStep
|
|
10
13
|
|
|
11
14
|
/**
|
|
12
15
|
* The view's own drawing with its children, as drawing commands: sharp at any scale. Its opacity and place belong to
|
|
@@ -28,14 +31,55 @@ internal fun recordPicture(view: View): Picture? = try {
|
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
// Runs in linear time and keeps the curve it is given for the move: an element moves along the transition's curve,
|
|
31
|
-
// while what has to follow the clock (the pictures of a text changing over) can.
|
|
34
|
+
// while what has to follow the clock (the pictures of a text changing over) can. Its start and its frames each run in
|
|
35
|
+
// [elementStep]: one that fails stops the element — it is cancelled, so its ends put back what it changed and the
|
|
36
|
+
// transition plays on without it.
|
|
32
37
|
internal class MoveAnimator : ValueAnimator() {
|
|
33
38
|
var curve: TimeInterpolator = LinearInterpolator()
|
|
34
39
|
private set
|
|
35
40
|
|
|
41
|
+
/** The element's name in the log ([pairName]). */
|
|
42
|
+
var element = "''"
|
|
43
|
+
|
|
44
|
+
private val starts = ArrayList<() -> Unit>()
|
|
45
|
+
private val frames = ArrayList<(Float) -> Unit>()
|
|
46
|
+
private val ends = ArrayList<() -> Unit>()
|
|
47
|
+
private var failed = false
|
|
48
|
+
|
|
36
49
|
init {
|
|
37
50
|
setFloatValues(0f, 1f)
|
|
38
51
|
super.setInterpolator(LinearInterpolator())
|
|
52
|
+
addListener(object : AnimatorListenerAdapter() {
|
|
53
|
+
override fun onAnimationStart(animation: Animator) = guarded { for (start in starts) start() }
|
|
54
|
+
|
|
55
|
+
override fun onAnimationEnd(animation: Animator) {
|
|
56
|
+
for (end in ends) elementStep(element) { end() }
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
addUpdateListener { animation ->
|
|
60
|
+
if (!failed) guarded { for (frame in frames) frame(animation.animatedFraction) }
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** At the start of the flight, after its delay. */
|
|
65
|
+
fun onStart(action: () -> Unit) {
|
|
66
|
+
starts += action
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Each frame, with the fraction of the time, linear. */
|
|
70
|
+
fun onFrame(action: (fraction: Float) -> Unit) {
|
|
71
|
+
frames += action
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** At its end, also when it is cancelled or its start or a frame failed. */
|
|
75
|
+
fun onEnd(action: () -> Unit) {
|
|
76
|
+
ends += action
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private fun guarded(work: () -> Unit) {
|
|
80
|
+
if (failed || elementStep(element, work = work)) return
|
|
81
|
+
failed = true
|
|
82
|
+
cancel()
|
|
39
83
|
}
|
|
40
84
|
|
|
41
85
|
override fun setInterpolator(value: TimeInterpolator?) {
|
|
@@ -5,8 +5,6 @@ import android.graphics.Matrix
|
|
|
5
5
|
import android.view.View
|
|
6
6
|
import android.view.ViewOutlineProvider
|
|
7
7
|
import android.widget.ImageView
|
|
8
|
-
import androidx.core.animation.doOnEnd
|
|
9
|
-
import androidx.core.animation.doOnStart
|
|
10
8
|
import com.facebook.react.views.view.ReactViewGroup
|
|
11
9
|
import dev.apexnative.navigation.utils.borderRadius
|
|
12
10
|
import dev.apexnative.navigation.views.animations.ValueInterpolation
|
|
@@ -78,7 +76,7 @@ internal class ImageMatrixAnimator(
|
|
|
78
76
|
to.imageMatrix = matrix
|
|
79
77
|
}
|
|
80
78
|
}
|
|
81
|
-
|
|
79
|
+
onStart {
|
|
82
80
|
savedProvider = to.outlineProvider
|
|
83
81
|
savedClip = to.clipToOutline
|
|
84
82
|
to.outlineProvider = outline
|
|
@@ -86,8 +84,8 @@ internal class ImageMatrixAnimator(
|
|
|
86
84
|
if (fits) to.scaleType = ImageView.ScaleType.MATRIX
|
|
87
85
|
frame(0f)
|
|
88
86
|
}
|
|
89
|
-
|
|
90
|
-
|
|
87
|
+
onFrame { frame(it) }
|
|
88
|
+
onEnd {
|
|
91
89
|
if (fits) {
|
|
92
90
|
to.scaleType = toFit
|
|
93
91
|
to.imageMatrix = savedMatrix
|
package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PlainViewAnimator.kt
CHANGED
|
@@ -3,8 +3,6 @@ package dev.apexnative.navigation.views.element.animators
|
|
|
3
3
|
import android.animation.Animator
|
|
4
4
|
import android.view.View
|
|
5
5
|
import android.view.ViewOutlineProvider
|
|
6
|
-
import androidx.core.animation.doOnEnd
|
|
7
|
-
import androidx.core.animation.doOnStart
|
|
8
6
|
import com.facebook.react.uimanager.BackgroundStyleApplicator
|
|
9
7
|
import com.facebook.react.uimanager.style.BorderRadiusProp
|
|
10
8
|
import dev.apexnative.navigation.utils.borderRadius
|
|
@@ -57,7 +55,7 @@ internal class PlainViewAnimator(
|
|
|
57
55
|
}
|
|
58
56
|
to.rotation = ValueInterpolation.number(fromRotation, toRotation, f, time)
|
|
59
57
|
}
|
|
60
|
-
|
|
58
|
+
onStart {
|
|
61
59
|
savedProvider = to.outlineProvider
|
|
62
60
|
savedClip = to.clipToOutline
|
|
63
61
|
if (ownRadius != null) BackgroundStyleApplicator.setBorderRadius(to, RADIUS, null)
|
|
@@ -66,9 +64,9 @@ internal class PlainViewAnimator(
|
|
|
66
64
|
layer.place(to, toRect.left.toInt(), toRect.top.toInt(), width, height)
|
|
67
65
|
frame(0f)
|
|
68
66
|
}
|
|
69
|
-
|
|
67
|
+
onFrame { frame(it) }
|
|
70
68
|
// A cancelled flight ends too: the view goes back whole.
|
|
71
|
-
|
|
69
|
+
onEnd {
|
|
72
70
|
to.translationX = 0f
|
|
73
71
|
to.translationY = 0f
|
|
74
72
|
to.rotation = toRotation
|