@apexnative/navigation 1.0.0-alpha.6 → 1.0.0-alpha.7
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/ApexNavigation.podspec +2 -1
- package/CHANGELOG.md +7 -0
- package/android/CLAUDE.md +19 -10
- package/android/CORE.md +3 -2
- package/android/consumer-rules.pro +6 -0
- package/android/src/main/java/dev/apexnative/navigation/options/BottomTabOptions.kt +1 -1
- package/android/src/main/java/dev/apexnative/navigation/options/ButtonOptions.kt +1 -1
- package/android/src/main/java/dev/apexnative/navigation/options/LargeTitleOptions.kt +1 -1
- package/android/src/main/java/dev/apexnative/navigation/options/LayoutAnimation.kt +1 -1
- package/android/src/main/java/dev/apexnative/navigation/options/OptionKey.kt +18 -0
- package/android/src/main/java/dev/apexnative/navigation/options/OptionPaths.kt +91 -0
- package/android/src/main/java/dev/apexnative/navigation/options/SheetOptions.kt +7 -4
- package/android/src/main/java/dev/apexnative/navigation/options/TitleOptions.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/options/TopBarAppearanceOptions.kt +1 -1
- package/android/src/main/java/dev/apexnative/navigation/options/TopBarOptions.kt +16 -7
- package/android/src/main/java/dev/apexnative/navigation/react/ContentHeights.kt +95 -0
- package/android/src/main/java/dev/apexnative/navigation/react/ContentMounts.kt +66 -0
- package/android/src/main/java/dev/apexnative/navigation/react/NavigationTurboModule.kt +4 -0
- package/android/src/main/java/dev/apexnative/navigation/react/OptionsCommands.kt +41 -3
- package/android/src/main/java/dev/apexnative/navigation/react/ReactView.kt +16 -1
- package/android/src/main/java/dev/apexnative/navigation/slots/SlotScreenView.kt +27 -7
- package/android/src/main/java/dev/apexnative/navigation/slots/SlotView.kt +12 -1
- package/android/src/main/java/dev/apexnative/navigation/utils/MaterialTheme.kt +5 -6
- package/android/src/main/java/dev/apexnative/navigation/utils/SystemAnimations.kt +36 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalSheet.kt +329 -55
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalViews.kt +26 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/SheetStops.kt +60 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuMotion.kt +8 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/BackPreview.kt +32 -4
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackController.kt +46 -7
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackQueue.kt +15 -3
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackViews.kt +7 -4
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/fab/FabMotion.kt +142 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/fab/FabPresenter.kt +215 -43
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/search/StackSearchPresenter.kt +25 -26
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/BarRowTransition.kt +3 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/ButtonPlaces.kt +11 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/TopBarPresenter.kt +36 -38
- package/android/src/main/java/dev/apexnative/navigation/views/element/ElementTransitions.kt +124 -181
- package/android/src/main/java/dev/apexnative/navigation/views/element/TargetsWait.kt +8 -1
- package/android/src/main/java/dev/apexnative/navigation/views/element/TransitionLayer.kt +37 -112
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ElementPictures.kt +56 -3
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/FlightGeometry.kt +170 -153
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ImageMorph.kt +199 -0
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/SharedAnimatorFactory.kt +55 -45
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/{TextChangeAnimator.kt → TextMorph.kt} +79 -129
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ViewMorph.kt +108 -0
- package/android/src/main/java/dev/apexnative/navigation/views/stack/fab/FabCopy.kt +85 -0
- package/android/src/main/java/dev/apexnative/navigation/views/stack/fab/FabMenu.kt +13 -0
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TitleBar.kt +5 -24
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TopBar.kt +51 -34
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/BarButtonComponentCell.kt +6 -6
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/BarButtonView.kt +113 -0
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/BarMenu.kt +219 -0
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonBar.kt +121 -175
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonMenus.kt +34 -64
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonRow.kt +216 -0
- package/android/src/main/jni/ANNContentHeights.cpp +82 -0
- package/android/src/main/res/drawable/apex_navigation_ic_arrow_right.xml +14 -0
- package/android/src/main/res/layout/apex_navigation_menu_header.xml +19 -0
- package/android/src/main/res/layout/apex_navigation_menu_item.xml +68 -0
- package/android/src/main/res/values/styles.xml +1 -1
- package/android/src/main/res/values-night/styles.xml +1 -1
- package/cli/doctor/checks/app.js +16 -1
- package/cli/project.js +4 -0
- package/cli/setup/index.js +5 -0
- package/cli/setup/steps/metro.js +46 -0
- package/docs-md/architecture/native-screens.md +4 -0
- package/docs-md/architecture/theme-and-context.md +131 -12
- package/docs-md/architecture/transitions.md +43 -4
- package/docs-md/flows/bottom-sheets.md +4 -2
- package/docs-md/fundamentals/screens-and-props.md +9 -1
- package/docs-md/guides/cli.md +3 -1
- package/docs-md/help/troubleshooting.md +1 -1
- package/docs-md/index.json +29 -1
- package/docs-md/screen-ui/back-and-gestures.md +3 -1
- package/docs-md/screen-ui/bar-buttons.md +1 -1
- package/docs-md/screen-ui/toolbar-and-fab.md +2 -0
- package/docs-md/start/installation.md +16 -1
- package/ios/Animations/ANNFrameDriver.h +55 -15
- package/ios/Animations/ANNFrameDriver.mm +214 -60
- package/ios/Animations/ANNTransitionAnimator.h +26 -5
- package/ios/Animations/ANNTransitionAnimator.mm +49 -3
- package/ios/Animations/ANNTransitionPlan.h +2 -2
- package/ios/Animations/ANNTransitionPlan.mm +10 -0
- package/ios/Animations/Elements/ANNElementFinder.h +2 -2
- package/ios/Animations/Elements/ANNElementFinder.mm +2 -2
- package/ios/Animations/Elements/ANNElementTransition.h +3 -3
- package/ios/Animations/Elements/ANNElementTransition.mm +2 -2
- package/ios/Animations/Elements/ANNSharedElementFlight.h +3 -3
- package/ios/Animations/Elements/ANNSharedElementFlight.mm +181 -43
- package/ios/Animations/Interpolators/ANNViewTracks.h +6 -5
- package/ios/Animations/Interpolators/ANNViewTracks.mm +51 -21
- package/ios/CLAUDE.md +15 -7
- package/ios/CORE.md +1 -1
- package/ios/Core/ANNAppDelegate.mm +0 -2
- package/ios/Core/ANNDeepLinkQueue.h +6 -5
- package/ios/Core/ANNDeepLinkQueue.mm +22 -10
- package/ios/Core/ANNSceneDelegate.mm +2 -2
- package/ios/Core/ApexNavigation.mm +6 -2
- package/ios/Layouts/Root/ANNRootCoordinator.mm +45 -15
- package/ios/Layouts/Screen/ANNComponentViewController.mm +4 -1
- package/ios/Layouts/Stack/ANNStackAnimatingDelegate.h +11 -2
- package/ios/Layouts/Stack/ANNStackAnimatingDelegate.mm +14 -7
- package/ios/Layouts/Stack/ANNStackController.h +8 -0
- package/ios/Layouts/Stack/ANNStackController.mm +153 -11
- package/ios/Layouts/Stack/ANNSwipePopInteraction.h +26 -0
- package/ios/Layouts/Stack/ANNSwipePopInteraction.mm +185 -0
- package/ios/Layouts/Stack/InteractivePopGestureDelegate.mm +28 -4
- package/ios/Options/ANNAnimationOptions.h +2 -1
- package/ios/Options/ANNAnimationOptions.mm +1 -1
- package/ios/Options/ANNOptions.h +7 -0
- package/ios/Options/ANNOptions.mm +45 -0
- package/ios/Options/ANNOptionsReader.mm +2 -4
- package/ios/Options/ANNStackPopAnimationOptions.h +13 -0
- package/ios/Options/ANNStackPopAnimationOptions.mm +11 -0
- package/ios/Options/Params/ColorParser.h +4 -3
- package/ios/Options/Params/ColorParser.mm +22 -4
- package/ios/React/ANNSurfaceHostingView.h +8 -0
- package/ios/React/ANNSurfaceHostingView.mm +28 -1
- package/ios/React/Slots/ANNSlotScreenView.mm +8 -2
- package/ios/TurboModules/ANNTurboModule.mm +46 -0
- package/ios/cpp/react/renderer/components/ApexNavigationSpec/ANNSlotState.cpp +20 -0
- package/ios/cpp/react/renderer/components/ApexNavigationSpec/ANNSlotState.h +5 -14
- package/lib/module/ARCHITECTURE.md +9 -3
- package/lib/module/adapters/NativeANNTurboModule.js.map +1 -1
- package/lib/module/api/app/AppRegistry.js +14 -2
- package/lib/module/api/app/AppRegistry.js.map +1 -1
- package/lib/module/api/app/createNavigation.js +188 -66
- package/lib/module/api/app/createNavigation.js.map +1 -1
- package/lib/module/api/events/navigationEvents.js +6 -2
- package/lib/module/api/events/navigationEvents.js.map +1 -1
- package/lib/module/api/linking/linking.js +11 -0
- package/lib/module/api/linking/linking.js.map +1 -1
- package/lib/module/api/screens/defineScreen.js +45 -3
- package/lib/module/api/screens/defineScreen.js.map +1 -1
- package/lib/module/api/screens/nativeScreens.js +31 -6
- package/lib/module/api/screens/nativeScreens.js.map +1 -1
- package/lib/module/api/screens/registerScreens.js +172 -35
- package/lib/module/api/screens/registerScreens.js.map +1 -1
- package/lib/module/api/theme/theme.js +5 -0
- package/lib/module/api/theme/theme.js.map +1 -1
- package/lib/module/engine/commands.js +21 -1
- package/lib/module/engine/commands.js.map +1 -1
- package/lib/module/engine/layout.js +7 -2
- package/lib/module/engine/layout.js.map +1 -1
- package/lib/module/engine/nativeScreens.js +9 -0
- package/lib/module/engine/nativeScreens.js.map +1 -1
- package/lib/module/engine/options.js +5 -4
- package/lib/module/engine/options.js.map +1 -1
- package/lib/module/engine/optionsChange.js +102 -0
- package/lib/module/engine/optionsChange.js.map +1 -0
- package/lib/module/engine/refresh.js +85 -0
- package/lib/module/engine/refresh.js.map +1 -0
- package/lib/module/engine/rememberedOptions.js +60 -0
- package/lib/module/engine/rememberedOptions.js.map +1 -1
- package/lib/module/engine/screens.js +26 -0
- package/lib/module/engine/screens.js.map +1 -1
- package/lib/typescript/adapters/NativeANNTurboModule.d.ts +7 -0
- package/lib/typescript/adapters/NativeANNTurboModule.d.ts.map +1 -1
- package/lib/typescript/api/app/AppRegistry.d.ts.map +1 -1
- package/lib/typescript/api/app/createNavigation.d.ts +5 -0
- package/lib/typescript/api/app/createNavigation.d.ts.map +1 -1
- package/lib/typescript/api/events/navigationEvents.d.ts +2 -2
- package/lib/typescript/api/events/navigationEvents.d.ts.map +1 -1
- package/lib/typescript/api/index.d.ts +1 -1
- package/lib/typescript/api/index.d.ts.map +1 -1
- package/lib/typescript/api/linking/linking.d.ts +8 -0
- package/lib/typescript/api/linking/linking.d.ts.map +1 -1
- package/lib/typescript/api/screens/defineScreen.d.ts +23 -4
- package/lib/typescript/api/screens/defineScreen.d.ts.map +1 -1
- package/lib/typescript/api/screens/nativeScreens.d.ts +8 -1
- package/lib/typescript/api/screens/nativeScreens.d.ts.map +1 -1
- package/lib/typescript/api/screens/registerScreens.d.ts.map +1 -1
- package/lib/typescript/api/theme/theme.d.ts +2 -0
- package/lib/typescript/api/theme/theme.d.ts.map +1 -1
- package/lib/typescript/engine/commands.d.ts +5 -0
- package/lib/typescript/engine/commands.d.ts.map +1 -1
- package/lib/typescript/engine/layout.d.ts.map +1 -1
- package/lib/typescript/engine/nativeScreens.d.ts +5 -0
- package/lib/typescript/engine/nativeScreens.d.ts.map +1 -1
- package/lib/typescript/engine/optionsChange.d.ts +28 -0
- package/lib/typescript/engine/optionsChange.d.ts.map +1 -0
- package/lib/typescript/engine/refresh.d.ts +35 -0
- package/lib/typescript/engine/refresh.d.ts.map +1 -0
- package/lib/typescript/engine/rememberedOptions.d.ts +13 -0
- package/lib/typescript/engine/rememberedOptions.d.ts.map +1 -1
- package/lib/typescript/engine/screens.d.ts +7 -0
- package/lib/typescript/engine/screens.d.ts.map +1 -1
- package/lib/typescript/interfaces/ComponentEvents.d.ts +2 -2
- package/lib/typescript/interfaces/options/animations.d.ts +40 -15
- package/lib/typescript/interfaces/options/animations.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/bottomTabs.d.ts +2 -2
- package/lib/typescript/interfaces/options/buttons.d.ts +2 -2
- package/lib/typescript/interfaces/options/modals.d.ts +6 -7
- package/lib/typescript/interfaces/options/modals.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/stack.d.ts +8 -3
- package/lib/typescript/interfaces/options/stack.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/topBar.d.ts +14 -0
- package/lib/typescript/interfaces/options/topBar.d.ts.map +1 -1
- package/metro/acceptPlugin.js +57 -0
- package/metro/babelTransformer.js +45 -0
- package/metro/index.js +36 -0
- package/package.json +3 -1
- package/src/ARCHITECTURE.md +9 -3
- package/src/adapters/NativeANNTurboModule.ts +7 -0
- package/src/api/app/AppRegistry.ts +16 -2
- package/src/api/app/createNavigation.ts +198 -66
- package/src/api/events/navigationEvents.ts +9 -3
- package/src/api/index.ts +1 -0
- package/src/api/linking/linking.ts +22 -1
- package/src/api/screens/defineScreen.ts +49 -5
- package/src/api/screens/nativeScreens.ts +40 -4
- package/src/api/screens/registerScreens.tsx +170 -42
- package/src/api/theme/theme.ts +5 -0
- package/src/engine/commands.ts +21 -0
- package/src/engine/layout.ts +11 -2
- package/src/engine/nativeScreens.ts +10 -1
- package/src/engine/options.ts +6 -8
- package/src/engine/optionsChange.ts +107 -0
- package/src/engine/refresh.ts +97 -0
- package/src/engine/rememberedOptions.ts +70 -0
- package/src/engine/screens.tsx +24 -0
- package/src/interfaces/ComponentEvents.ts +2 -2
- package/src/interfaces/options/animations.ts +40 -15
- package/src/interfaces/options/bottomTabs.ts +2 -2
- package/src/interfaces/options/buttons.ts +2 -2
- package/src/interfaces/options/modals.ts +6 -7
- package/src/interfaces/options/stack.ts +8 -3
- package/src/interfaces/options/topBar.ts +14 -0
- package/android/src/main/java/dev/apexnative/navigation/views/element/ElementStub.kt +0 -31
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ImageMatrixAnimator.kt +0 -134
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PlainViewAnimator.kt +0 -78
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PropertyAnimatorCreator.kt +0 -33
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ReactImageAnimator.kt +0 -57
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ViewChangeAnimator.kt +0 -146
|
@@ -29,9 +29,77 @@ export function rememberMergedOptions(id: string, options: Options): void {
|
|
|
29
29
|
remembered.set(id, before ? mergeOptions(before, kept) : kept);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
/** Takes the options at the paths, such as `['topBar', 'subtitle']`, out of those remembered for the node with the id. */
|
|
33
|
+
export function forgetMergedPaths(id: string, paths: readonly string[][]): void {
|
|
34
|
+
const own = ownOptions.get(id);
|
|
35
|
+
if (own) own.options = withoutPaths(own.options, paths);
|
|
36
|
+
const options = remembered.get(id);
|
|
37
|
+
if (options) remembered.set(id, withoutPaths(options, paths));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// A copy of the options without the values at the paths.
|
|
41
|
+
function withoutPaths(options: Options, paths: readonly string[][]): Options {
|
|
42
|
+
const kept = structuredCopy(options);
|
|
43
|
+
for (const path of paths) {
|
|
44
|
+
const parent = path
|
|
45
|
+
.slice(0, -1)
|
|
46
|
+
.reduce<Record<string, unknown> | undefined>(
|
|
47
|
+
(group, key) => group?.[key] as Record<string, unknown> | undefined,
|
|
48
|
+
kept
|
|
49
|
+
);
|
|
50
|
+
if (parent) delete parent[path[path.length - 1]!];
|
|
51
|
+
}
|
|
52
|
+
return kept as Options;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// A copy of the plain objects of options, the other values shared.
|
|
56
|
+
function structuredCopy(options: Options): Record<string, unknown> {
|
|
57
|
+
return Object.fromEntries(
|
|
58
|
+
Object.entries(options).map(([key, value]) => [
|
|
59
|
+
key,
|
|
60
|
+
isGroup(value) ? structuredCopy(value as Options) : value,
|
|
61
|
+
])
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function isGroup(value: unknown): boolean {
|
|
66
|
+
return (
|
|
67
|
+
typeof value === 'object' && value !== null && Object.getPrototypeOf(value) === Object.prototype
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
32
71
|
/** Forgets the merged options of a node that went; a node of the last root keeps them until another root goes out. */
|
|
33
72
|
export function forgetMergedOptions(id: string): void {
|
|
34
73
|
if (!lastRoot?.ids.has(id)) remembered.delete(id);
|
|
74
|
+
ownOptions.delete(id);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// The own options of each screen as JavaScript sent them, in development: those it showed with, and every merge and
|
|
78
|
+
// clear since. An edit of a screen's options (Fast Refresh) changes only what still has the old definition's value.
|
|
79
|
+
const ownOptions = new Map<string, { name: string; options: Options }>();
|
|
80
|
+
|
|
81
|
+
/** Notes the own options a screen of the name shows with (development). */
|
|
82
|
+
export function noteOwnOptions(id: string, name: string, options: Options): void {
|
|
83
|
+
if (__DEV__) ownOptions.set(id, { name, options });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Notes a merge into a screen's own options (development). */
|
|
87
|
+
export function noteMergedOwnOptions(id: string, options: Options): void {
|
|
88
|
+
const noted = __DEV__ ? ownOptions.get(id) : undefined;
|
|
89
|
+
if (noted) noted.options = mergeOptions(noted.options, options);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** The own options of a screen as JavaScript sent them (development); undefined for a screen it didn't note. */
|
|
93
|
+
export function ownOptionsOf(id: string): Options | undefined {
|
|
94
|
+
return ownOptions.get(id)?.options;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* The ids of the screens of the name the native side has, mounted in React or not yet (a tab not selected yet), as
|
|
99
|
+
* development notes them.
|
|
100
|
+
*/
|
|
101
|
+
export function notedScreens(name: string): string[] {
|
|
102
|
+
return [...ownOptions].filter(([, noted]) => noted.name === name).map(([id]) => id);
|
|
35
103
|
}
|
|
36
104
|
|
|
37
105
|
/** The options merged into the node with the id, as remembered; undefined without any. */
|
|
@@ -55,6 +123,8 @@ export function rootGoesOut(nodes: LayoutNode[], again: SentRoot | undefined): v
|
|
|
55
123
|
const before = lastRoot;
|
|
56
124
|
lastRoot = { nodes, ids: new Set(nodes.flatMap(idsOf)) };
|
|
57
125
|
if (!before) return;
|
|
126
|
+
// The screens of the root before go with it, the tabs React never mounted too.
|
|
127
|
+
for (const id of before.ids) if (!lastRoot.ids.has(id)) ownOptions.delete(id);
|
|
58
128
|
const moved = new Map<string, Options>();
|
|
59
129
|
if (again === before) {
|
|
60
130
|
pairNodes(before.nodes, nodes, (old, node) => {
|
package/src/engine/screens.tsx
CHANGED
|
@@ -68,6 +68,29 @@ let unmounted: ((screenId: string) => void) | undefined;
|
|
|
68
68
|
// bar of every screen that shows it. The id lets its props go only with the last of them. A slot of the app's one tree
|
|
69
69
|
// has no tag: while the one tree shows a screen with the id, no root of that id lets it go.
|
|
70
70
|
const owners = new Map<string, Set<number>>();
|
|
71
|
+
// The screens that show by their ids: the name, and how many roots or slots of the id are mounted (a component of the
|
|
72
|
+
// options has a root in the bar of every screen that shows it).
|
|
73
|
+
const shown = new Map<string, { name: string; mounts: number }>();
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Notes a screen as shown, a React root or slot of it or the controller of a native screen, until the function it
|
|
77
|
+
* gives is called.
|
|
78
|
+
*/
|
|
79
|
+
export function noteShown(componentId: string, name: string): () => void {
|
|
80
|
+
const entry = shown.get(componentId);
|
|
81
|
+
if (entry?.name === name) entry.mounts++;
|
|
82
|
+
else shown.set(componentId, { name, mounts: 1 });
|
|
83
|
+
return () => {
|
|
84
|
+
const current = shown.get(componentId);
|
|
85
|
+
if (current?.name !== name) return;
|
|
86
|
+
if (--current.mounts === 0) shown.delete(componentId);
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** The ids of the screens with the name that show now. */
|
|
91
|
+
export function shownScreens(name: string): string[] {
|
|
92
|
+
return [...shown].filter(([, entry]) => entry.name === name).map(([id]) => id);
|
|
93
|
+
}
|
|
71
94
|
|
|
72
95
|
/** Whether a React root of the screen with the id is mounted now (a slot of the app's one tree doesn't count). */
|
|
73
96
|
export function hasRoot(componentId: string): boolean {
|
|
@@ -235,6 +258,7 @@ export function screenRoot(name: string): ComponentType<NativeRootProps> | undef
|
|
|
235
258
|
[componentId]
|
|
236
259
|
);
|
|
237
260
|
const props = useSyncExternalStore(watch, () => propsOf(componentId));
|
|
261
|
+
useEffect(() => noteShown(componentId, name), [componentId]);
|
|
238
262
|
useEffect(() => {
|
|
239
263
|
if (rootTag !== undefined) {
|
|
240
264
|
const tags = owners.get(componentId) ?? new Set<number>();
|
|
@@ -95,8 +95,8 @@ export interface SheetDetentChangedEvent {
|
|
|
95
95
|
/** The id of the visible screen of the modal. */
|
|
96
96
|
componentId: string;
|
|
97
97
|
/**
|
|
98
|
-
* The detent the sheet stopped at: `'medium'`, `'large'`,
|
|
99
|
-
*
|
|
98
|
+
* The detent the sheet stopped at: `'medium'`, `'large'`, `'content'` (the detent that fits the content), or a
|
|
99
|
+
* height (points on iOS, dp on Android).
|
|
100
100
|
*/
|
|
101
101
|
detent: 'medium' | 'large' | 'content' | number;
|
|
102
102
|
}
|
|
@@ -262,8 +262,8 @@ export type ElementAnimation = AppearingElementAnimation | DisappearingElementAn
|
|
|
262
262
|
* element's value, and a missing end is that value (a turn read from −180° to 180°, as React Native draws it). `x`
|
|
263
263
|
* and `y` are the position in the parent of the element on both platforms. As on a side of a screen, don't set `x`
|
|
264
264
|
* with `translationX` or `y` with `translationY` on one element: the translation plays and the position doesn't.
|
|
265
|
-
*
|
|
266
|
-
*
|
|
265
|
+
* The element plays where it is, in its screen: it moves with its screen, its parents clip it, and the views drawn
|
|
266
|
+
* after it in its screen stay over it.
|
|
267
267
|
*/
|
|
268
268
|
export interface ElementTransition {
|
|
269
269
|
/** The `nativeID` of the view to animate. */
|
|
@@ -294,15 +294,20 @@ export interface ElementTransition {
|
|
|
294
294
|
* A view that flies from the screen that goes to the screen that comes, changing its place, size, corners and look on
|
|
295
295
|
* the way. A pair whose view is not found on either screen is skipped while the rest plays, and the transition waits
|
|
296
296
|
* until the views are found. A pair that fails on the way (an error in its start or its flight) is skipped too, with a
|
|
297
|
-
* warning in the log: its views show and hide with their screens, and the transition and its command go on.
|
|
298
|
-
*
|
|
299
|
-
* own, the screen that comes fades in (or the one that goes fades out) in 300 ms around the pair.
|
|
300
|
-
*
|
|
301
|
-
*
|
|
302
|
-
* the
|
|
303
|
-
*
|
|
304
|
-
*
|
|
305
|
-
*
|
|
297
|
+
* warning in the log: its views show and hide with their screens, and the transition and its command go on. So is a
|
|
298
|
+
* pair with a view without a size, or one whose picture can't be taken. Without an animation of the screens of its
|
|
299
|
+
* own, the screen that comes fades in (or the one that goes fades out) in 300 ms around the pair. What flies is a copy:
|
|
300
|
+
* pictures of the two views taken as the transition starts, while the views stay hidden in their places; what changes
|
|
301
|
+
* in them meanwhile shows when they are back. A view its parents cut (`overflow: hidden`, a scroll view, the edge of
|
|
302
|
+
* its screen) shows only the part they leave visible, and that part goes to the one of the other view on the way.
|
|
303
|
+
* Text keeps its proportions, flies whole and changes its picture within a quarter of the flight. A view turned or
|
|
304
|
+
* scaled by its own `transform` or by a parent's flies from where it shows, and its angle goes to the other view's
|
|
305
|
+
* along the shorter arc. The angle is the one React Native draws, from −180° to 180°:
|
|
306
|
+
* `rotate: '270deg'` turns back a quarter, and no pair spins a full turn. (Android) An image without a background or a
|
|
307
|
+
* border flies as the image of the view that comes, from the part and place the fit (`resizeMode`) shows in one view
|
|
308
|
+
* to those in the other, and an animated image plays on; images of other proportions fly as pictures. An image that
|
|
309
|
+
* doesn't load within a second drops out of the transition. A skew and a tilt (`skewX`, `rotateX`, …) don't fly: the
|
|
310
|
+
* view flies without them and has them again at the end.
|
|
306
311
|
*/
|
|
307
312
|
export interface SharedElementTransition {
|
|
308
313
|
/** The `nativeID` of the view on the screen that goes. */
|
|
@@ -411,14 +416,27 @@ export interface StackPushAnimationOptions extends StackAnimationOptions {
|
|
|
411
416
|
}
|
|
412
417
|
|
|
413
418
|
/**
|
|
414
|
-
* The animation of a pop, read from the screen that goes. As {@link StackAnimationOptions}, with the
|
|
415
|
-
* back gesture on Android.
|
|
419
|
+
* The animation of a pop, read from the screen that goes. As {@link StackAnimationOptions}, with the swipe back of iOS
|
|
420
|
+
* and the preview of the back gesture on Android.
|
|
416
421
|
*/
|
|
417
422
|
export interface StackPopAnimationOptions extends StackAnimationOptions {
|
|
423
|
+
/**
|
|
424
|
+
* (iOS) Whether the swipe back plays this pop under the finger. Without it, the swipe back plays UIKit's slide, and
|
|
425
|
+
* the pop's own animation plays only for the back button and `pop`. With `true`, the swipe from the edge plays the
|
|
426
|
+
* pop's own animation as far as the finger has gone across the screen: the user can stop halfway, and letting go
|
|
427
|
+
* finishes the pop or puts the screen back, the rest playing faster after a quick flick. It applies only when the pop
|
|
428
|
+
* has an animation of its own (`content`, `elementTransitions`, `sharedElementTransitions`). The top bar shows the
|
|
429
|
+
* screen under from the start of the swipe, as UIKit does in a transition of its own, and comes back when the swipe
|
|
430
|
+
* is given up. On iOS 26 the swipe from the content stays off for a screen with an own pop.
|
|
431
|
+
* Android ignores it: a pop with an animation of its own plays when the back gesture ends.
|
|
432
|
+
* @default false
|
|
433
|
+
*/
|
|
434
|
+
interactive?: boolean;
|
|
418
435
|
/**
|
|
419
436
|
* (Android 14+) Whether the back gesture on a pushed screen previews its pop as the user swipes: the screen shrinks
|
|
420
437
|
* and follows the finger, the screen under it shows behind. `false` pops the screen when the gesture is released,
|
|
421
|
-
* as on Android 13 and older; back still pops it. iOS ignores it: its swipe back is UIKit's
|
|
438
|
+
* as on Android 13 and older; back still pops it. iOS ignores it: its swipe back is UIKit's, or the pop's own
|
|
439
|
+
* animation with `interactive`.
|
|
422
440
|
* @default true
|
|
423
441
|
*/
|
|
424
442
|
preview?: boolean;
|
|
@@ -474,6 +492,12 @@ export interface ModalAnimationOptions extends EnterExitAnimationOptions {
|
|
|
474
492
|
* options of the command. A change by `mergeOptions` is for the next command and animates nothing itself. A theme
|
|
475
493
|
* switch or a rotation does not change an animation. Without a key, the transition is the platform's own.
|
|
476
494
|
*
|
|
495
|
+
* The system's setting to reduce motion is respected, with no option to turn that off. (iOS) With Reduce Motion on, a
|
|
496
|
+
* transition of these keys is a fade of the same length: the screen that comes fades in (a push, `setStackRoot`, a
|
|
497
|
+
* modal's show, `setRoot`), the screen that goes fades out (a pop, a dismissal); no element moves. (Android) With
|
|
498
|
+
* Remove animations on (or the animator duration scale at 0), the transitions end at once, no element moves and none
|
|
499
|
+
* is waited for, a side menu opens and closes without its slide, and a sheet shows and closes without it.
|
|
500
|
+
*
|
|
477
501
|
* ```ts
|
|
478
502
|
* const options: Options = {
|
|
479
503
|
* animations: {
|
|
@@ -489,7 +513,8 @@ export interface AnimationOptions {
|
|
|
489
513
|
push?: StackPushAnimationOptions;
|
|
490
514
|
/**
|
|
491
515
|
* The pop of a screen, also `popTo` and `popToRoot`, read from the screen that goes. The swipe back of iOS is
|
|
492
|
-
* UIKit's; the back gesture of Android previews the pop
|
|
516
|
+
* UIKit's, or the pop's own animation under the finger (`interactive`); the back gesture of Android previews the pop
|
|
517
|
+
* (`preview`).
|
|
493
518
|
*/
|
|
494
519
|
pop?: StackPopAnimationOptions;
|
|
495
520
|
/** The replacing of the screens of a stack, read from the new top screen. */
|
|
@@ -199,8 +199,8 @@ export interface OptionsBottomTabs {
|
|
|
199
199
|
*/
|
|
200
200
|
drawBehind?: boolean;
|
|
201
201
|
/**
|
|
202
|
-
* The background color of the bar; `null` gives the default one. On Android the default is the surface color
|
|
203
|
-
* Material 3 theme. Before iOS 26, and in the compatibility mode (`UIDesignRequiresCompatibility` in Info.plist: iOS 26, and iOS 27
|
|
202
|
+
* The background color of the bar; `null` gives the default one. On Android the default is the surface container color
|
|
203
|
+
* (`colorSurfaceContainer`) of the Material 3 theme. Before iOS 26, and in the compatibility mode (`UIDesignRequiresCompatibility` in Info.plist: iOS 26, and iOS 27
|
|
204
204
|
* only for an app built with an SDK before iOS 27), the bar is opaque with the color, or system opaque without it,
|
|
205
205
|
* and a transparent color makes it transparent. On iOS 26 and later with glass the color is ignored
|
|
206
206
|
* with one warning, since the system has no color for glass, while a transparent color gives a transparent bar. The
|
|
@@ -159,7 +159,7 @@ export interface OptionsTopBarButtonMenu {
|
|
|
159
159
|
* A button of the top bar (`leftButtons`, `rightButtons`, `centerButtons`) or of the toolbar (`toolbar.buttons`). The
|
|
160
160
|
* kinds are picked by the fields that are set. On iOS, the first that fits wins: a `component`, then an icon with text
|
|
161
161
|
* and `showText`, then an icon with `iconBackground` or `iconInsets`, then an SF Symbol, an image `icon`, a `text`,
|
|
162
|
-
* a `systemItem`; a button with none of them is not shown. On Android a button is a
|
|
162
|
+
* a `systemItem`; a button with none of them is not shown. On Android a button is a button of the bar's row: text, icon
|
|
163
163
|
* or a component. Lists of buttons are replaced as a whole and take nothing from the lists of lower layers.
|
|
164
164
|
*
|
|
165
165
|
* Text, icon, color and similar fields are the same in all four places; where a place or a side differs, the field
|
|
@@ -204,7 +204,7 @@ export interface OptionsTopBarButton {
|
|
|
204
204
|
systemItem?: SystemItemIcon;
|
|
205
205
|
/**
|
|
206
206
|
* The text of the button. A button with an icon keeps it as a label for the overflow menu, the customization panel
|
|
207
|
-
* and VoiceOver (iOS) or
|
|
207
|
+
* and VoiceOver (iOS), or for TalkBack, the tooltip of a long press and the overflow menu (Android).
|
|
208
208
|
*/
|
|
209
209
|
text?: string;
|
|
210
210
|
/** The text a screen reader says for the button. */
|
|
@@ -51,8 +51,7 @@ export type OptionsModalTransitionStyle =
|
|
|
51
51
|
*
|
|
52
52
|
* On iOS `'medium'` and `'large'` are the system stops; a number and `'content'` need iOS 16 and are left out below it
|
|
53
53
|
* with a warning in the log. On Android `'medium'` is half of the window, `'large'` the full height under the status
|
|
54
|
-
* bar, a number is capped at `'large'`, and `'content'`
|
|
55
|
-
* height by content.
|
|
54
|
+
* bar, a number is capped at `'large'`, and `'content'` is the height of the content, measured as on iOS.
|
|
56
55
|
*/
|
|
57
56
|
export type OptionsModalSheetDetent = 'medium' | 'large' | 'content' | number;
|
|
58
57
|
|
|
@@ -67,11 +66,11 @@ export type OptionsModalSheetDetent = 'medium' | 'large' | 'content' | number;
|
|
|
67
66
|
export interface OptionsModalSheet {
|
|
68
67
|
/**
|
|
69
68
|
* The stops the sheet can rest on, and the user drags it between, in any order: on both platforms they are sorted by
|
|
70
|
-
* height and repeats dropped. On iOS `'medium'` counts as half of `'large'` there, and `'content'` takes its
|
|
71
|
-
* the height of the content, again whenever that changes; a list left empty by unsupported values
|
|
72
|
-
* `['large']`. On Android the Material sheet has three resting places at most: the lowest stop of the list, the
|
|
73
|
-
* highest, and
|
|
74
|
-
* dropped.
|
|
69
|
+
* height and repeats dropped. On iOS `'medium'` counts as half of `'large'` there, and on both `'content'` takes its
|
|
70
|
+
* place by the height of the content, again whenever that changes; on iOS a list left empty by unsupported values
|
|
71
|
+
* gives `['large']`. On Android the Material sheet has three resting places at most: the lowest stop of the list, the
|
|
72
|
+
* highest, and one between them, `selectedDetent` when it stands there, else the lowest of the others; the rest are
|
|
73
|
+
* dropped. Stops of the same height there are one.
|
|
75
74
|
* @default ['large']
|
|
76
75
|
*/
|
|
77
76
|
detents?: OptionsModalSheetDetent[];
|
|
@@ -39,13 +39,18 @@ export interface HardwareBackButtonOptions {
|
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
41
|
* (Android) A floating action button over the screen, or an entry of its menu. The button shows while its screen is
|
|
42
|
-
* on top of the stack; without the option there is none.
|
|
43
|
-
* the
|
|
42
|
+
* on top of the stack; without the option there is none. It changes as a push or a pop starts and moves with its
|
|
43
|
+
* screen, as the tab bar does: the new screen's button comes in with it, the old one goes out with the screen it
|
|
44
|
+
* belongs to, and an open menu closes. With an animation of the screens' own the old button shrinks away and the new
|
|
45
|
+
* one grows as it starts; during the back gesture the button of the screen under the top shows with that screen.
|
|
46
|
+
* `mergeOptions` and the default options of the app apply, and the colors of the theme are read again when the theme
|
|
47
|
+
* changes.
|
|
44
48
|
*/
|
|
45
49
|
export interface OptionsFab {
|
|
46
50
|
/**
|
|
47
51
|
* The identifier of the button. A press reports it, and the app finds the `onPress` of the button by it. A button
|
|
48
|
-
* without an `id` doesn't show. The same `id` on a later show or merge updates the button
|
|
52
|
+
* without an `id` doesn't show. The same `id` on a later show or merge updates the button, which stays in place
|
|
53
|
+
* while the screens move; another `id` replaces it.
|
|
49
54
|
* For an entry of `actions` it is the id of that entry's own press.
|
|
50
55
|
*/
|
|
51
56
|
id: string;
|
|
@@ -654,6 +654,20 @@ export interface OptionsTopBar {
|
|
|
654
654
|
centerButtonColor?: Color;
|
|
655
655
|
/** (iOS 16+) The color of the disabled center buttons that have no `disabledColor` of their own. */
|
|
656
656
|
centerButtonDisabledColor?: Color;
|
|
657
|
+
/**
|
|
658
|
+
* (Android) The background of the bar's menus: "⋮" and the menus of the buttons, their submenus too. Without it the
|
|
659
|
+
* bar's color (`background.color`, else the theme's surface), unless the bar lets something through; then the
|
|
660
|
+
* theme's surface container color. It follows light and dark mode; an open menu closes as the mode switches. iOS
|
|
661
|
+
* draws its system menus in the system's colors.
|
|
662
|
+
*/
|
|
663
|
+
menuBackgroundColor?: Color;
|
|
664
|
+
/**
|
|
665
|
+
* (Android) The color of the text and the icons of the items of the bar's menus, and of the titles of their
|
|
666
|
+
* submenus. Without it the title's color (`title.color`) when the menu takes the bar's color, else the theme's
|
|
667
|
+
* colors. A destructive item stays red, and a button moved into "⋮" keeps its own color or the side's
|
|
668
|
+
* (`rightButtonColor`). A disabled item is dimmed. An open menu closes when either menu color is merged.
|
|
669
|
+
*/
|
|
670
|
+
menuTextColor?: Color;
|
|
657
671
|
/**
|
|
658
672
|
* Whether the content of the screen goes under the bar, starting at the top of the screen. The screen goes under the
|
|
659
673
|
* bar by itself in some cases (a large title that may show, `hideOnScroll`, a hidden bar, an explicit translucent
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
package dev.apexnative.navigation.views.element
|
|
2
|
-
|
|
3
|
-
import android.annotation.SuppressLint
|
|
4
|
-
import android.content.Context
|
|
5
|
-
import android.graphics.Canvas
|
|
6
|
-
import android.view.MotionEvent
|
|
7
|
-
import android.view.View
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* The empty view that stands in an element's place in its parent while the element is in the transition layer: the same
|
|
11
|
-
* id at the same index with the same layout, so Fabric finds the parent's children as it left them; React removing it
|
|
12
|
-
* removes the element for good. It has no `nativeID`, so no search finds it as the element. It draws nothing and takes
|
|
13
|
-
* no touches. Main thread only.
|
|
14
|
-
*/
|
|
15
|
-
@SuppressLint("ViewConstructor")
|
|
16
|
-
internal class ElementStub(context: Context, element: View) : View(context) {
|
|
17
|
-
init {
|
|
18
|
-
id = element.id
|
|
19
|
-
importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_NO
|
|
20
|
-
isClickable = false
|
|
21
|
-
isFocusable = false
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) =
|
|
25
|
-
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec))
|
|
26
|
-
|
|
27
|
-
override fun draw(canvas: Canvas) = Unit
|
|
28
|
-
|
|
29
|
-
@SuppressLint("ClickableViewAccessibility")
|
|
30
|
-
override fun onTouchEvent(event: MotionEvent?): Boolean = false
|
|
31
|
-
}
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
package dev.apexnative.navigation.views.element.animators
|
|
2
|
-
|
|
3
|
-
import android.animation.Animator
|
|
4
|
-
import android.graphics.Matrix
|
|
5
|
-
import android.view.View
|
|
6
|
-
import android.view.ViewOutlineProvider
|
|
7
|
-
import android.widget.ImageView
|
|
8
|
-
import com.facebook.react.views.view.ReactViewGroup
|
|
9
|
-
import dev.apexnative.navigation.utils.borderRadius
|
|
10
|
-
import dev.apexnative.navigation.views.animations.ValueInterpolation
|
|
11
|
-
import dev.apexnative.navigation.views.element.TransitionLayer
|
|
12
|
-
import kotlin.math.max
|
|
13
|
-
import kotlin.math.min
|
|
14
|
-
import kotlin.math.roundToInt
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* A shared image of another kind (an `ImageView` that isn't React Native's): from where `from` is drawn to where it is
|
|
18
|
-
* drawn in its screen ([FlightGeometry]), unscaled and turned about its middle; its image matrix from `from`'s fit
|
|
19
|
-
* (`centerCrop`, `fitXY`, `fitCenter`, `centerInside`) to its own, the part seen from `from`'s size to its own, the
|
|
20
|
-
* corner radius of the parent that clips it (`borderRadius` with `overflow: hidden`) by its outline. Another fit: the
|
|
21
|
-
* pair moves without the change of its matrix, and a line goes to the log. Everything goes back as it was at the end.
|
|
22
|
-
* Main thread only.
|
|
23
|
-
*/
|
|
24
|
-
internal class ImageMatrixAnimator(
|
|
25
|
-
private val from: ImageView,
|
|
26
|
-
private val to: ImageView,
|
|
27
|
-
/** Where [to] was in its screen: what it flies to. */
|
|
28
|
-
private val toStandIn: View?,
|
|
29
|
-
private val layer: TransitionLayer,
|
|
30
|
-
/** The parents the views are clipped by in their screens: [to]'s is the one it had before the layer. */
|
|
31
|
-
private val fromParent: View?,
|
|
32
|
-
private val toParent: View?,
|
|
33
|
-
private val id: String,
|
|
34
|
-
private val log: (String) -> Unit,
|
|
35
|
-
) {
|
|
36
|
-
/** The animator; none when [to] has no place in the layer. */
|
|
37
|
-
fun create(): Animator? {
|
|
38
|
-
val geometry = FlightGeometry.of(from, to, toStandIn, layer) ?: return null
|
|
39
|
-
val fromFit = from.scaleType
|
|
40
|
-
val toFit = to.scaleType
|
|
41
|
-
// The image whose matrix changes; none when a fit isn't one of the four or there is no image to fit.
|
|
42
|
-
val image = to.drawable?.takeIf {
|
|
43
|
-
fromFit in FITS && toFit in FITS && it.intrinsicWidth > 0 && it.intrinsicHeight > 0
|
|
44
|
-
}
|
|
45
|
-
val fits = image != null
|
|
46
|
-
if (!fits) log("The image '$id' fits by ${fromFit.name} and ${toFit.name}: it moves without a change of fit")
|
|
47
|
-
val fromRadius = clipRadius(fromParent) * geometry.from.scale
|
|
48
|
-
val toRadius = clipRadius(toParent) * geometry.to.scale
|
|
49
|
-
val outline = FlightOutline()
|
|
50
|
-
val moved = MovedProperties(to)
|
|
51
|
-
var savedProvider: ViewOutlineProvider? = null
|
|
52
|
-
var savedClip = false
|
|
53
|
-
val savedMatrix = Matrix(to.imageMatrix)
|
|
54
|
-
val start = FloatArray(MATRIX)
|
|
55
|
-
val end = FloatArray(MATRIX)
|
|
56
|
-
val values = FloatArray(MATRIX)
|
|
57
|
-
val matrix = Matrix()
|
|
58
|
-
return MoveAnimator().apply {
|
|
59
|
-
fun frame(time: Float) {
|
|
60
|
-
val f = curve.getInterpolation(time)
|
|
61
|
-
val at = geometry.at(f, time)
|
|
62
|
-
val width = at.width.roundToInt()
|
|
63
|
-
val height = at.height.roundToInt()
|
|
64
|
-
layer.place(to, geometry.toLeft, geometry.toTop, width, height)
|
|
65
|
-
moved.show(geometry, at, width, height)
|
|
66
|
-
outline.width = width
|
|
67
|
-
outline.height = height
|
|
68
|
-
outline.radius = ValueInterpolation.number(fromRadius, toRadius, f, time)
|
|
69
|
-
to.invalidateOutline()
|
|
70
|
-
if (image != null) {
|
|
71
|
-
val imageWidth = image.intrinsicWidth
|
|
72
|
-
val imageHeight = image.intrinsicHeight
|
|
73
|
-
fitMatrix(fromFit, imageWidth, imageHeight, width, height).getValues(start)
|
|
74
|
-
fitMatrix(toFit, imageWidth, imageHeight, width, height).getValues(end)
|
|
75
|
-
for (i in 0 until MATRIX) values[i] = ValueInterpolation.number(start[i], end[i], f, time)
|
|
76
|
-
matrix.setValues(values)
|
|
77
|
-
to.imageMatrix = matrix
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
onStart {
|
|
81
|
-
savedProvider = to.outlineProvider
|
|
82
|
-
savedClip = to.clipToOutline
|
|
83
|
-
to.outlineProvider = outline
|
|
84
|
-
to.clipToOutline = true
|
|
85
|
-
if (fits) to.scaleType = ImageView.ScaleType.MATRIX
|
|
86
|
-
frame(0f)
|
|
87
|
-
}
|
|
88
|
-
onFrame { frame(it) }
|
|
89
|
-
onEnd {
|
|
90
|
-
if (fits) {
|
|
91
|
-
to.scaleType = toFit
|
|
92
|
-
to.imageMatrix = savedMatrix
|
|
93
|
-
}
|
|
94
|
-
to.outlineProvider = savedProvider
|
|
95
|
-
to.clipToOutline = savedClip
|
|
96
|
-
moved.end(geometry, landed) { w, h -> layer.place(to, geometry.toLeft, geometry.toTop, w, h) }
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// The radius of a React parent that clips what is in it; 0 for another.
|
|
102
|
-
private fun clipRadius(parent: View?): Float =
|
|
103
|
-
(parent as? ReactViewGroup)?.takeIf { it.overflow == HIDDEN }?.borderRadius ?: 0f
|
|
104
|
-
|
|
105
|
-
private companion object {
|
|
106
|
-
const val MATRIX = 9
|
|
107
|
-
const val HIDDEN = "hidden"
|
|
108
|
-
val FITS = setOf(
|
|
109
|
-
ImageView.ScaleType.CENTER_CROP,
|
|
110
|
-
ImageView.ScaleType.FIT_XY,
|
|
111
|
-
ImageView.ScaleType.FIT_CENTER,
|
|
112
|
-
ImageView.ScaleType.CENTER_INSIDE,
|
|
113
|
-
)
|
|
114
|
-
|
|
115
|
-
// The matrix of a fit of an image of [imageWidth] × [imageHeight] into [width] × [height].
|
|
116
|
-
fun fitMatrix(fit: ImageView.ScaleType, imageWidth: Int, imageHeight: Int, width: Int, height: Int): Matrix {
|
|
117
|
-
val sx = width.toFloat() / imageWidth
|
|
118
|
-
val sy = height.toFloat() / imageHeight
|
|
119
|
-
val matrix = Matrix()
|
|
120
|
-
if (fit == ImageView.ScaleType.FIT_XY) {
|
|
121
|
-
matrix.setScale(sx, sy)
|
|
122
|
-
return matrix
|
|
123
|
-
}
|
|
124
|
-
val scale = when (fit) {
|
|
125
|
-
ImageView.ScaleType.CENTER_CROP -> max(sx, sy)
|
|
126
|
-
ImageView.ScaleType.CENTER_INSIDE -> min(1f, min(sx, sy))
|
|
127
|
-
else -> min(sx, sy)
|
|
128
|
-
}
|
|
129
|
-
matrix.setScale(scale, scale)
|
|
130
|
-
matrix.postTranslate((width - imageWidth * scale) / 2f, (height - imageHeight * scale) / 2f)
|
|
131
|
-
return matrix
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PlainViewAnimator.kt
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
package dev.apexnative.navigation.views.element.animators
|
|
2
|
-
|
|
3
|
-
import android.animation.Animator
|
|
4
|
-
import android.view.View
|
|
5
|
-
import android.view.ViewOutlineProvider
|
|
6
|
-
import com.facebook.react.uimanager.BackgroundStyleApplicator
|
|
7
|
-
import com.facebook.react.uimanager.style.BorderRadiusProp
|
|
8
|
-
import dev.apexnative.navigation.utils.borderRadius
|
|
9
|
-
import dev.apexnative.navigation.views.animations.ValueInterpolation
|
|
10
|
-
import dev.apexnative.navigation.views.element.TransitionLayer
|
|
11
|
-
import kotlin.math.roundToInt
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* A shared React view without children moves itself: from where `from` is drawn to where it is drawn in its screen
|
|
15
|
-
* ([FlightGeometry]); its size and corner radius by an outline that grows from `from`'s (at the scale it is drawn at)
|
|
16
|
-
* to its own while it is laid out at the larger of the two sizes, unscaled and turned about the outline's middle; its
|
|
17
|
-
* own radius taken off meanwhile and put back as it was; its background color from `from`'s to its own in Lab, ending
|
|
18
|
-
* exactly on its own (`BackgroundStyleApplicator`). Main thread only.
|
|
19
|
-
*/
|
|
20
|
-
internal class PlainViewAnimator(
|
|
21
|
-
private val from: View,
|
|
22
|
-
private val to: View,
|
|
23
|
-
/** Where [to] was in its screen: what it flies to. */
|
|
24
|
-
private val toStandIn: View?,
|
|
25
|
-
private val layer: TransitionLayer,
|
|
26
|
-
) {
|
|
27
|
-
/** The animator; none when [to] has no place in the layer. */
|
|
28
|
-
fun create(): Animator? {
|
|
29
|
-
val geometry = FlightGeometry.of(from, to, toStandIn, layer) ?: return null
|
|
30
|
-
val fromRadius = from.borderRadius * geometry.from.scale
|
|
31
|
-
val toRadius = to.borderRadius * geometry.to.scale
|
|
32
|
-
val fromColor = BackgroundStyleApplicator.getBackgroundColor(from)
|
|
33
|
-
val toColor = BackgroundStyleApplicator.getBackgroundColor(to)
|
|
34
|
-
val ownRadius = BackgroundStyleApplicator.getBorderRadius(to, RADIUS)
|
|
35
|
-
val outline = FlightOutline()
|
|
36
|
-
val moved = MovedProperties(to)
|
|
37
|
-
var savedProvider: ViewOutlineProvider? = null
|
|
38
|
-
var savedClip = false
|
|
39
|
-
val (width, height) = geometry.largest
|
|
40
|
-
return MoveAnimator().apply {
|
|
41
|
-
fun frame(time: Float) {
|
|
42
|
-
val f = curve.getInterpolation(time)
|
|
43
|
-
val at = geometry.at(f, time)
|
|
44
|
-
outline.width = at.width.roundToInt()
|
|
45
|
-
outline.height = at.height.roundToInt()
|
|
46
|
-
outline.radius = ValueInterpolation.number(fromRadius, toRadius, f, time)
|
|
47
|
-
moved.show(geometry, at, outline.width, outline.height)
|
|
48
|
-
to.invalidateOutline()
|
|
49
|
-
if (fromColor != toColor) {
|
|
50
|
-
val color = ValueInterpolation.color(fromColor, toColor, f, time)
|
|
51
|
-
BackgroundStyleApplicator.setBackgroundColor(to, color)
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
onStart {
|
|
55
|
-
savedProvider = to.outlineProvider
|
|
56
|
-
savedClip = to.clipToOutline
|
|
57
|
-
if (ownRadius != null) BackgroundStyleApplicator.setBorderRadius(to, RADIUS, null)
|
|
58
|
-
to.outlineProvider = outline
|
|
59
|
-
to.clipToOutline = true
|
|
60
|
-
layer.place(to, geometry.toLeft, geometry.toTop, width, height)
|
|
61
|
-
frame(0f)
|
|
62
|
-
}
|
|
63
|
-
onFrame { frame(it) }
|
|
64
|
-
// A cancelled flight ends too: the view goes back whole.
|
|
65
|
-
onEnd {
|
|
66
|
-
if (fromColor != toColor) BackgroundStyleApplicator.setBackgroundColor(to, toColor)
|
|
67
|
-
if (ownRadius != null) BackgroundStyleApplicator.setBorderRadius(to, RADIUS, ownRadius)
|
|
68
|
-
to.outlineProvider = savedProvider
|
|
69
|
-
to.clipToOutline = savedClip
|
|
70
|
-
moved.end(geometry, landed) { w, h -> layer.place(to, geometry.toLeft, geometry.toTop, w, h) }
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
private companion object {
|
|
76
|
-
val RADIUS = BorderRadiusProp.BORDER_RADIUS
|
|
77
|
-
}
|
|
78
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
package dev.apexnative.navigation.views.element.animators
|
|
2
|
-
|
|
3
|
-
import android.animation.Animator
|
|
4
|
-
import android.view.View
|
|
5
|
-
import dev.apexnative.navigation.options.SharedElementTransitionOptions
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* One animator of a shared element: how [from], on the screen that goes, becomes [to], on the screen that comes, when
|
|
9
|
-
* both are of [type]. The core picks one by the views' classes (`SharedAnimatorFactory`) and runs it in the
|
|
10
|
-
* transition's layer.
|
|
11
|
-
*/
|
|
12
|
-
abstract class PropertyAnimatorCreator<T : View>(
|
|
13
|
-
protected val from: View,
|
|
14
|
-
protected val to: View,
|
|
15
|
-
private val type: Class<T>,
|
|
16
|
-
) {
|
|
17
|
-
/** Whether this animator takes part: both views are of its type and differ in what it animates. */
|
|
18
|
-
fun shouldAnimate(): Boolean {
|
|
19
|
-
if (!type.isInstance(from) || !type.isInstance(to)) return false
|
|
20
|
-
val fromChild = type.cast(from) ?: return false
|
|
21
|
-
val toChild = type.cast(to) ?: return false
|
|
22
|
-
return shouldAnimateProperty(fromChild, toChild)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** Whether [fromChild] and [toChild] differ in what this animator animates. */
|
|
26
|
-
abstract fun shouldAnimateProperty(fromChild: T, toChild: T): Boolean
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* The animator of the pair for [options]; the core gives it the pair's duration, delay and curve (each animator
|
|
30
|
-
* its own, never through a set around it).
|
|
31
|
-
*/
|
|
32
|
-
abstract fun create(options: SharedElementTransitionOptions): Animator
|
|
33
|
-
}
|