1 | 'use strict';
|
2 | import { runOnUIImmediately } from '../../threads';
|
3 | import type {
|
4 | ProgressAnimation,
|
5 | SharedTransitionAnimationsValues,
|
6 | } from '../animationBuilder/commonTypes';
|
7 | import { registerEventHandler, unregisterEventHandler } from '../../core';
|
8 | import { Platform } from 'react-native';
|
9 | import { isJest, shouldBeUseWeb } from '../../PlatformChecker';
|
10 |
|
11 | type TransitionProgressEvent = {
|
12 | closing: number;
|
13 | goingForward: number;
|
14 | eventName: string;
|
15 | progress: number;
|
16 | target: number;
|
17 | };
|
18 |
|
19 | const IS_ANDROID = Platform.OS === 'android';
|
20 |
|
21 | export class ProgressTransitionManager {
|
22 | private _sharedElementCount = 0;
|
23 | private _eventHandler = {
|
24 | isRegistered: false,
|
25 | onTransitionProgress: -1,
|
26 | onAppear: -1,
|
27 | onDisappear: -1,
|
28 | onSwipeDismiss: -1,
|
29 | };
|
30 |
|
31 | public addProgressAnimation(
|
32 | viewTag: number,
|
33 | progressAnimation: ProgressAnimation
|
34 | ) {
|
35 | runOnUIImmediately(() => {
|
36 | 'worklet';
|
37 | global.ProgressTransitionRegister.addProgressAnimation(
|
38 | viewTag,
|
39 | progressAnimation
|
40 | );
|
41 | })();
|
42 |
|
43 | this.registerEventHandlers();
|
44 | }
|
45 |
|
46 | public removeProgressAnimation(viewTag: number, isUnmounting = true) {
|
47 | this.unregisterEventHandlers();
|
48 | runOnUIImmediately(() => {
|
49 | 'worklet';
|
50 | global.ProgressTransitionRegister.removeProgressAnimation(
|
51 | viewTag,
|
52 | isUnmounting
|
53 | );
|
54 | })();
|
55 | }
|
56 |
|
57 | private registerEventHandlers() {
|
58 | this._sharedElementCount++;
|
59 | const eventHandler = this._eventHandler;
|
60 | if (!eventHandler.isRegistered) {
|
61 | eventHandler.isRegistered = true;
|
62 | const eventPrefix = IS_ANDROID ? 'on' : 'top';
|
63 | let lastProgressValue = -1;
|
64 | eventHandler.onTransitionProgress = registerEventHandler(
|
65 | (event: TransitionProgressEvent) => {
|
66 | 'worklet';
|
67 | const progress = event.progress;
|
68 | if (progress === lastProgressValue) {
|
69 |
|
70 |
|
71 |
|
72 | return;
|
73 | }
|
74 | lastProgressValue = progress;
|
75 | global.ProgressTransitionRegister.frame(progress);
|
76 | },
|
77 | eventPrefix + 'TransitionProgress'
|
78 | );
|
79 | eventHandler.onAppear = registerEventHandler(() => {
|
80 | 'worklet';
|
81 | global.ProgressTransitionRegister.onTransitionEnd();
|
82 | }, eventPrefix + 'Appear');
|
83 |
|
84 | if (IS_ANDROID) {
|
85 |
|
86 |
|
87 | eventHandler.onDisappear = registerEventHandler(() => {
|
88 | 'worklet';
|
89 | global.ProgressTransitionRegister.onAndroidFinishTransitioning();
|
90 | }, 'onFinishTransitioning');
|
91 | } else if (Platform.OS === 'ios') {
|
92 |
|
93 | eventHandler.onDisappear = registerEventHandler(() => {
|
94 | 'worklet';
|
95 | global.ProgressTransitionRegister.onTransitionEnd(true);
|
96 | }, 'topDisappear');
|
97 | eventHandler.onSwipeDismiss = registerEventHandler(() => {
|
98 | 'worklet';
|
99 | global.ProgressTransitionRegister.onTransitionEnd();
|
100 | }, 'topGestureCancel');
|
101 | }
|
102 | }
|
103 | }
|
104 |
|
105 | private unregisterEventHandlers(): void {
|
106 | this._sharedElementCount--;
|
107 | if (this._sharedElementCount === 0) {
|
108 | const eventHandler = this._eventHandler;
|
109 | eventHandler.isRegistered = false;
|
110 | if (eventHandler.onTransitionProgress !== -1) {
|
111 | unregisterEventHandler(eventHandler.onTransitionProgress);
|
112 | eventHandler.onTransitionProgress = -1;
|
113 | }
|
114 | if (eventHandler.onAppear !== -1) {
|
115 | unregisterEventHandler(eventHandler.onAppear);
|
116 | eventHandler.onAppear = -1;
|
117 | }
|
118 | if (eventHandler.onDisappear !== -1) {
|
119 | unregisterEventHandler(eventHandler.onDisappear);
|
120 | eventHandler.onDisappear = -1;
|
121 | }
|
122 | if (eventHandler.onSwipeDismiss !== -1) {
|
123 | unregisterEventHandler(eventHandler.onSwipeDismiss);
|
124 | eventHandler.onSwipeDismiss = -1;
|
125 | }
|
126 | }
|
127 | }
|
128 | }
|
129 |
|
130 | function createProgressTransitionRegister() {
|
131 | 'worklet';
|
132 | const progressAnimations = new Map<number, ProgressAnimation>();
|
133 | const snapshots = new Map<
|
134 | number,
|
135 | Partial<SharedTransitionAnimationsValues>
|
136 | >();
|
137 | const currentTransitions = new Set<number>();
|
138 | const toRemove = new Set<number>();
|
139 |
|
140 | let skipCleaning = false;
|
141 | let isTransitionRestart = false;
|
142 |
|
143 | const progressTransitionManager = {
|
144 | addProgressAnimation: (
|
145 | viewTag: number,
|
146 | progressAnimation: ProgressAnimation
|
147 | ) => {
|
148 | if (currentTransitions.size > 0 && !progressAnimations.has(viewTag)) {
|
149 |
|
150 | isTransitionRestart = !IS_ANDROID;
|
151 | }
|
152 | progressAnimations.set(viewTag, progressAnimation);
|
153 | },
|
154 | removeProgressAnimation: (viewTag: number, isUnmounting: boolean) => {
|
155 | if (currentTransitions.size > 0) {
|
156 |
|
157 | isTransitionRestart = !IS_ANDROID;
|
158 | }
|
159 | if (isUnmounting) {
|
160 |
|
161 | toRemove.add(viewTag);
|
162 | } else {
|
163 |
|
164 | progressAnimations.delete(viewTag);
|
165 | }
|
166 | },
|
167 | onTransitionStart: (
|
168 | viewTag: number,
|
169 | snapshot: Partial<SharedTransitionAnimationsValues>
|
170 | ) => {
|
171 | skipCleaning = isTransitionRestart;
|
172 | snapshots.set(viewTag, snapshot);
|
173 | currentTransitions.add(viewTag);
|
174 |
|
175 | progressTransitionManager.frame(0);
|
176 | },
|
177 | frame: (progress: number) => {
|
178 | for (const viewTag of currentTransitions) {
|
179 | const progressAnimation = progressAnimations.get(viewTag);
|
180 | if (!progressAnimation) {
|
181 | continue;
|
182 | }
|
183 | const snapshot = snapshots.get(
|
184 | viewTag
|
185 | )! as SharedTransitionAnimationsValues;
|
186 | progressAnimation(viewTag, snapshot, progress);
|
187 | }
|
188 | },
|
189 | onAndroidFinishTransitioning: () => {
|
190 | if (toRemove.size > 0) {
|
191 |
|
192 | progressTransitionManager.onTransitionEnd();
|
193 | }
|
194 | },
|
195 | onTransitionEnd: (removeViews = false) => {
|
196 | if (currentTransitions.size === 0) {
|
197 | toRemove.clear();
|
198 | return;
|
199 | }
|
200 | if (skipCleaning) {
|
201 | skipCleaning = false;
|
202 | isTransitionRestart = false;
|
203 | return;
|
204 | }
|
205 | for (const viewTag of currentTransitions) {
|
206 | global._notifyAboutEnd(viewTag, removeViews);
|
207 | }
|
208 | currentTransitions.clear();
|
209 | if (isTransitionRestart) {
|
210 |
|
211 |
|
212 | return;
|
213 | }
|
214 | snapshots.clear();
|
215 | if (toRemove.size > 0) {
|
216 | for (const viewTag of toRemove) {
|
217 | progressAnimations.delete(viewTag);
|
218 | global._notifyAboutEnd(viewTag, removeViews);
|
219 | }
|
220 | toRemove.clear();
|
221 | }
|
222 | },
|
223 | };
|
224 | return progressTransitionManager;
|
225 | }
|
226 |
|
227 | if (shouldBeUseWeb()) {
|
228 | const maybeThrowError = () => {
|
229 |
|
230 |
|
231 | if (!isJest()) {
|
232 | throw new Error(
|
233 | '[Reanimated] `ProgressTransitionRegister` is not available on non-native platform.'
|
234 | );
|
235 | }
|
236 | };
|
237 | global.ProgressTransitionRegister = new Proxy(
|
238 | {} as ProgressTransitionRegister,
|
239 | {
|
240 | get: maybeThrowError,
|
241 | set: () => {
|
242 | maybeThrowError();
|
243 | return false;
|
244 | },
|
245 | }
|
246 | );
|
247 | } else {
|
248 | runOnUIImmediately(() => {
|
249 | 'worklet';
|
250 | global.ProgressTransitionRegister = createProgressTransitionRegister();
|
251 | })();
|
252 | }
|
253 |
|
254 | export type ProgressTransitionRegister = ReturnType<
|
255 | typeof createProgressTransitionRegister
|
256 | >;
|