Skip to content

Commit

Permalink
fix: fixed no or stale transition when switching route (goBack) too fast
Browse files Browse the repository at this point in the history
  • Loading branch information
IjzerenHein committed Sep 3, 2019
1 parent cd652dc commit f154a93
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 82 deletions.
184 changes: 129 additions & 55 deletions src/SharedElementRendererData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,21 @@ import {
SharedElementsStrictConfig,
SharedElementAnimatedValue,
SharedElementTransitionProps,
Route,
} from './types';
import { normalizeSharedElementsConfig } from './utils';

export type SharedElementRendererUpdateHandler = () => any;

export interface ISharedElementRendererData {
startTransition(animValue: SharedElementAnimatedValue): void;
endTransition(): void;
willActivateScene(sceneData: SharedElementSceneData): void;
didActivateScene(sceneData: SharedElementSceneData): void;
startTransition(
animValue: SharedElementAnimatedValue,
route: Route,
prevRoute: Route
): void;
endTransition(route: Route, prevRoute: Route): void;
willActivateScene(sceneData: SharedElementSceneData, route: Route): void;
didActivateScene(sceneData: SharedElementSceneData, route: Route): void;
}

function getSharedElements(
Expand All @@ -28,67 +33,138 @@ function getSharedElements(
);
}

const NO_SHARED_ELEMENTS: any[] = [];

type SceneRoute = {
scene: SharedElementSceneData;
route: Route;
subscription: SharedElementEventSubscription | null;
};

export default class SharedElementRendererData
implements ISharedElementRendererData {
private sceneData: SharedElementSceneData | null = null;
private prevSceneData: SharedElementSceneData | null = null;
private scenes: SceneRoute[] = [];
private updateSubscribers = new Set<SharedElementRendererUpdateHandler>();
private sceneSubscription: SharedElementEventSubscription | null = null;
private sharedElements: SharedElementsStrictConfig = [];
private sharedElements: SharedElementsStrictConfig | null = null;
private isShowing: boolean = true;
private animValue: SharedElementAnimatedValue;
private route: Route | null = null;
private prevRoute: Route | null = null;
private scene: SharedElementSceneData | null = null;
private prevScene: SharedElementSceneData | null = null;

startTransition(animValue: SharedElementAnimatedValue) {
startTransition(
animValue: SharedElementAnimatedValue,
route: Route,
// @ts-ignore
prevRoute: Route //eslint-disable-line @typescript-eslint/no-unused-vars
) {
//console.log('startTransition, route: ', route.key);
this.animValue = animValue;
this.prevRoute = this.route;
this.route = route;
this.updateSceneListeners();
this.updateSharedElements();
}

endTransition(
// @ts-ignore
route: Route, //eslint-disable-line @typescript-eslint/no-unused-vars
// @ts-ignore
prevRoute: Route //eslint-disable-line @typescript-eslint/no-unused-vars
) {
//console.log('endTransition, route: ', route.key);
if (this.prevRoute != null) {
this.prevRoute = null;
this.animValue = null;
this.updateSceneListeners();
this.updateSharedElements();
}
}

willActivateScene(sceneData: SharedElementSceneData, route: Route): void {
//console.log('willActivateScene, route: ', route.key);
this.registerScene(sceneData, route);
}

didActivateScene(sceneData: SharedElementSceneData, route: Route): void {
//console.log('didActivateScene, route: ', route.key);
this.prevRoute = null;
this.registerScene(sceneData, route);
}

endTransition() {
// Nothing to do
private registerScene(sceneData: SharedElementSceneData, route: Route) {
this.scenes.push({
scene: sceneData,
route,
subscription: null,
});
if (this.scenes.length > 5) {
const { subscription } = this.scenes[0];
this.scenes.splice(0, 1);
if (subscription) subscription.remove();
}
this.updateSceneListeners();
this.updateSharedElements();
}

willActivateScene(sceneData: SharedElementSceneData): void {
/*console.log(
'SharedElementRendererData.willActivateScene: ',
sceneData.name,
', previous: ',
this.prevSceneData ? this.prevSceneData.name : ''
);*/
if (!this.prevSceneData) return;
private updateSceneListeners() {
this.scenes.forEach(sceneRoute => {
const { scene, route, subscription } = sceneRoute;
const isActive =
(this.route && this.route.key === route.key) ||
(this.prevRoute && this.prevRoute.key === route.key);
if (isActive && !subscription) {
sceneRoute.subscription = scene.addUpdateListener(() => {
// TODO optimize
this.emitUpdateEvent();
});
} else if (!isActive && subscription) {
sceneRoute.subscription = null;
subscription.remove();
}
});
}

private updateSharedElements() {
const { route, prevRoute, animValue } = this;
const sceneRoute = route
? this.scenes.find(sc => sc.route.key === route.key)
: undefined;
const prevSceneRoute = prevRoute
? this.scenes.find(sc => sc.route.key === prevRoute.key)
: undefined;
const scene = sceneRoute ? sceneRoute.scene : null;
const prevScene = prevSceneRoute ? prevSceneRoute.scene : null;

// Update current scene & previous scene
if (scene === this.scene && prevScene === this.prevScene) return;
this.scene = scene;
this.prevScene = prevScene;

// Update shared elements
let sharedElements: SharedElementsStrictConfig | null = null;
let isShowing = true;
let sharedElements = getSharedElements(sceneData, this.prevSceneData, true);
if (!sharedElements) {
isShowing = false;
sharedElements = getSharedElements(this.prevSceneData, sceneData, false);
if (animValue && scene && prevScene) {
sharedElements = getSharedElements(scene, prevScene, true);
if (!sharedElements) {
isShowing = false;
sharedElements = getSharedElements(prevScene, scene, false);
}
}
if (sharedElements && sharedElements.length) {
// console.log('sharedElements: ', sharedElements, sceneData);
this.sceneData = sceneData;
if (this.sharedElements !== sharedElements) {
this.sharedElements = sharedElements;
this.isShowing = isShowing;
this.sceneSubscription = this.sceneData.addUpdateListener(() => {
// TODO optimize
this.emitUpdateEvent();
});
/*console.log(
'updateSharedElements: ',
sharedElements,
' ,isShowing: ',
isShowing
);*/
this.emitUpdateEvent();
}
}

didActivateScene(sceneData: SharedElementSceneData): void {
//console.log('SharedElementRendererData.didActivateScene: ', sceneData.name);
if (this.sceneSubscription) {
this.sceneSubscription.remove();
this.sceneSubscription = null;
}
this.prevSceneData = sceneData;
if (this.sceneData) {
this.sceneData = null;
if (this.sharedElements.length) {
this.sharedElements = [];
this.emitUpdateEvent();
}
}
}

addUpdateListener(
handler: SharedElementRendererUpdateHandler
): SharedElementEventSubscription {
Expand All @@ -103,23 +179,21 @@ export default class SharedElementRendererData
}

getTransitions(): SharedElementTransitionProps[] {
const { sharedElements, prevSceneData, sceneData, isShowing } = this;
const { sharedElements, prevScene, scene, isShowing, animValue } = this;
// console.log('getTransitions: ', sharedElements);
if (!sharedElements || !scene || !prevScene) return NO_SHARED_ELEMENTS;
return sharedElements.map(({ id, otherId, ...other }) => {
const startId = isShowing ? otherId || id : id;
const endId = isShowing ? id : otherId || id;
return {
position: this.animValue,
position: animValue,
start: {
ancestor:
(prevSceneData ? prevSceneData.getAncestor() : undefined) || null,
node:
(prevSceneData ? prevSceneData.getNode(startId) : undefined) ||
null,
ancestor: (prevScene ? prevScene.getAncestor() : undefined) || null,
node: (prevScene ? prevScene.getNode(startId) : undefined) || null,
},
end: {
ancestor: (sceneData ? sceneData.getAncestor() : undefined) || null,
node: (sceneData ? sceneData.getNode(endId) : undefined) || null,
ancestor: (scene ? scene.getAncestor() : undefined) || null,
node: (scene ? scene.getNode(endId) : undefined) || null,
},
...other,
};
Expand Down
22 changes: 13 additions & 9 deletions src/SharedElementRendererProxy.tsx
Original file line number Diff line number Diff line change
@@ -1,50 +1,54 @@
import SharedElementRendererData, {
ISharedElementRendererData,
} from './SharedElementRendererData';
import { SharedElementAnimatedValue } from './types';
import { SharedElementAnimatedValue, Route } from './types';
import SharedElementSceneData from './SharedElementSceneData';

export class SharedElementRendererProxy implements ISharedElementRendererData {
private data: SharedElementRendererData | null = null;

startTransition(animValue: SharedElementAnimatedValue) {
startTransition(
animValue: SharedElementAnimatedValue,
route: Route,
prevRoute: Route
) {
if (!this.data) {
console.warn(
'SharedElementRendererProxy.startTransition called before Proxy was initialized'
);
return;
}
return this.data.startTransition(animValue);
return this.data.startTransition(animValue, route, prevRoute);
}

endTransition() {
endTransition(route: Route, prevRoute: Route) {
if (!this.data) {
console.warn(
'SharedElementRendererProxy.endTransition called before Proxy was initialized'
);
return;
}
return this.data.endTransition();
return this.data.endTransition(route, prevRoute);
}

willActivateScene(sceneData: SharedElementSceneData) {
willActivateScene(sceneData: SharedElementSceneData, route: Route) {
if (!this.data) {
console.warn(
'SharedElementRendererProxy.willActivateScene called before Proxy was initialized'
);
return;
}
return this.data.willActivateScene(sceneData);
return this.data.willActivateScene(sceneData, route);
}

didActivateScene(sceneData: SharedElementSceneData) {
didActivateScene(sceneData: SharedElementSceneData, route: Route) {
if (!this.data) {
console.warn(
'SharedElementRendererProxy.didActivateScene called before Proxy was initialized'
);
return;
}
return this.data.didActivateScene(sceneData);
return this.data.didActivateScene(sceneData, route);
}

get source(): SharedElementRendererData | null {
Expand Down
22 changes: 8 additions & 14 deletions src/createSharedElementScene.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
SharedElementSceneComponent,
} from './types';
import { ISharedElementRendererData } from './SharedElementRendererData';
import { getActiveRouteState } from './utils';

const styles = StyleSheet.create({
container: {
Expand All @@ -21,18 +22,6 @@ type PropsType = {
navigation: NavigationProp;
};

function getActiveRouteState(route: any): any {
if (
!route.routes ||
route.routes.length === 0 ||
route.index >= route.routes.length
) {
return route;
} else {
return getActiveRouteState(route.routes[route.index]);
}
}

function createSharedElementScene(
Component: SharedElementSceneComponent,
rendererData: ISharedElementRendererData
Expand Down Expand Up @@ -84,15 +73,20 @@ function createSharedElementScene(
};

private onWillFocus = () => {
rendererData.willActivateScene(this.sceneData);
const { navigation } = this.props;
const activeRoute = getActiveRouteState(navigation.state);
if (navigation.state.routeName === activeRoute.routeName) {
// console.log('onWillFocus: ', navigation.state, activeRoute);
rendererData.willActivateScene(this.sceneData, navigation.state);
}
};

private onDidFocus = () => {
const { navigation } = this.props;
const activeRoute = getActiveRouteState(navigation.state);
if (navigation.state.routeName === activeRoute.routeName) {
// console.log('onDidFocus: ', this.sceneData.name, navigation);
rendererData.didActivateScene(this.sceneData);
rendererData.didActivateScene(this.sceneData, navigation.state);
}
};
}
Expand Down
16 changes: 12 additions & 4 deletions src/createSharedElementStackNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import SharedElementRendererData, {
import createSharedElementScene from './createSharedElementScene';
import SharedElementRendererContext from './SharedElementRendererContext';
import { SharedElementRendererProxy } from './SharedElementRendererProxy';
import { getActiveRouteState } from './utils';

function createSharedElementEnabledNavigator(
createNavigator: any,
Expand Down Expand Up @@ -44,15 +45,22 @@ function createSharedElementEnabledNavigator(
position.interpolate({
inputRange: [index - 1, index],
outputRange: index > prevIndex ? [0, 1] : [2, 1],
})
}),
getActiveRouteState(transitionProps.scene.route),
getActiveRouteState(prevTransitionProps.scene.route)
);
if (navigatorConfig && navigatorConfig.onTransitionStart) {
navigatorConfig.onTransitionStart(transitionProps, prevTransitionProps);
navigatorConfig.onTransitionStart(
getActiveRouteState(transitionProps.scene.route),
getActiveRouteState(prevTransitionProps.scene.route)
);
}
},
onTransitionEnd: (transitionProps: any, prevTransitionProps: any) => {
// console.log('onTransitionEnd: ', transitionProps, prevTransitionProps);
rendererData.endTransition();
rendererData.endTransition(
transitionProps.scene.route,
prevTransitionProps.scene.route
);
if (navigatorConfig && navigatorConfig.onTransitionEnd) {
navigatorConfig.onTransitionEnd(transitionProps, prevTransitionProps);
}
Expand Down
Loading

0 comments on commit f154a93

Please sign in to comment.