mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-14 18:12:09 +08:00
637 lines
26 KiB
TypeScript
637 lines
26 KiB
TypeScript
import type { TransitionInteractiveState, TransitionNavigationType } from '.';
|
|
import { getPageStartDefaultsForType, getRectFromProps, getSpringFromProps, SharedTransition, SharedTransitionAnimationType, SharedTransitionState } from './shared-transition';
|
|
import { isNumber } from '../../utils/types';
|
|
import { Screen } from '../../platform';
|
|
import { CORE_ANIMATION_DEFAULTS } from '../../utils/common';
|
|
import { ios as iOSUtils } from '../../utils/native-helper';
|
|
|
|
interface PlatformTransitionInteractiveState extends TransitionInteractiveState {
|
|
transitionContext?: UIViewControllerContextTransitioning;
|
|
propertyAnimator?: UIViewPropertyAnimator;
|
|
}
|
|
|
|
export class SharedTransitionHelper {
|
|
static animate(state: SharedTransitionState, transitionContext: UIViewControllerContextTransitioning, type: TransitionNavigationType) {
|
|
const transition = state.instance;
|
|
setTimeout(async () => {
|
|
// Run on next tick
|
|
// ensures that existing UI state finishes before snapshotting
|
|
// (eg, button touch up state)
|
|
switch (state.activeType) {
|
|
case SharedTransitionAnimationType.present: {
|
|
// console.log('-- Transition present --');
|
|
SharedTransition.notifyEvent(SharedTransition.startedEvent, {
|
|
id: transition.id,
|
|
type,
|
|
action: 'present',
|
|
});
|
|
|
|
if (type === 'modal') {
|
|
transitionContext.containerView.addSubview(transition.presented.view);
|
|
} else if (type === 'page') {
|
|
transitionContext.containerView.insertSubviewAboveSubview(transition.presented.view, transition.presenting.view);
|
|
}
|
|
transition.presented.view.layoutIfNeeded();
|
|
|
|
const { sharedElements, presented, presenting } = SharedTransition.getSharedElements(state.page, state.toPage);
|
|
const sharedElementTags = sharedElements.map((v) => v.sharedTransitionTag);
|
|
if (!transition.sharedElements) {
|
|
transition.sharedElements = {
|
|
presented: [],
|
|
presenting: [],
|
|
independent: [],
|
|
};
|
|
}
|
|
|
|
if (SharedTransition.DEBUG) {
|
|
console.log(` ${type}: Present`);
|
|
console.log(`1. Found sharedTransitionTags to animate:`, sharedElementTags);
|
|
|
|
console.log(`2. Take snapshots of shared elements and position them based on presenting view:`);
|
|
}
|
|
|
|
const pageOut = state.pageOut;
|
|
const pageStart = state.pageStart;
|
|
|
|
const startFrame = getRectFromProps(pageStart, getPageStartDefaultsForType(type));
|
|
|
|
const pageEnd = state.pageEnd;
|
|
const pageEndTags = pageEnd?.sharedTransitionTags || {};
|
|
// console.log('pageEndIndependentTags:', pageEndIndependentTags);
|
|
|
|
const positionSharedTags = async () => {
|
|
for (const presentingView of sharedElements) {
|
|
const presentingSharedElement = presentingView.ios;
|
|
// console.log('fromTarget instanceof UIImageView:', fromTarget instanceof UIImageView)
|
|
|
|
// TODO: discuss whether we should check if UIImage/UIImageView type to always snapshot images or if other view types could be duped/added vs. snapshotted
|
|
// Note: snapshot may be most efficient/simple
|
|
// console.log('---> ', presentingView.sharedTransitionTag, ': ', presentingSharedElement)
|
|
|
|
const presentedView = presented.find((v) => v.sharedTransitionTag === presentingView.sharedTransitionTag);
|
|
const presentedSharedElement = presentedView.ios;
|
|
const pageEndProps = pageEndTags[presentingView.sharedTransitionTag];
|
|
|
|
const snapshot = UIImageView.alloc().init();
|
|
if (pageEndProps?.callback) {
|
|
await pageEndProps?.callback(presentedView, 'present');
|
|
}
|
|
|
|
// treat images differently...
|
|
if (presentedSharedElement instanceof UIImageView) {
|
|
// in case the image is loaded async, we need to update the snapshot when it changes
|
|
// todo: remove listener on transition end
|
|
presentedView.on('imageSourceChange', () => {
|
|
snapshot.image = iOSUtils.snapshotView(presentedSharedElement, Screen.mainScreen.scale);
|
|
snapshot.tintColor = presentedSharedElement.tintColor;
|
|
});
|
|
|
|
snapshot.tintColor = presentedSharedElement.tintColor;
|
|
snapshot.contentMode = presentedSharedElement.contentMode;
|
|
}
|
|
|
|
iOSUtils.copyLayerProperties(snapshot, presentingSharedElement, pageEndProps?.propertiesToMatch as any);
|
|
snapshot.clipsToBounds = true;
|
|
// console.log('---> snapshot: ', snapshot);
|
|
|
|
const startFrame = presentingSharedElement.convertRectToView(presentingSharedElement.bounds, transitionContext.containerView);
|
|
const endFrame = presentedSharedElement.convertRectToView(presentedSharedElement.bounds, transitionContext.containerView);
|
|
snapshot.frame = startFrame;
|
|
if (SharedTransition.DEBUG) {
|
|
console.log('---> ', presentingView.sharedTransitionTag, ' frame:', iOSUtils.printCGRect(snapshot.frame));
|
|
}
|
|
|
|
transition.sharedElements.presenting.push({
|
|
view: presentingView,
|
|
startFrame,
|
|
endFrame,
|
|
snapshot,
|
|
startOpacity: presentingView.opacity,
|
|
endOpacity: isNumber(pageEndProps?.opacity) ? pageEndProps.opacity : presentedView.opacity,
|
|
propertiesToMatch: pageEndProps?.propertiesToMatch,
|
|
zIndex: isNumber(pageEndProps?.zIndex) ? pageEndProps.zIndex : 0,
|
|
});
|
|
transition.sharedElements.presented.push({
|
|
view: presentedView,
|
|
startFrame: endFrame,
|
|
endFrame: startFrame,
|
|
startOpacity: presentedView.opacity,
|
|
endOpacity: presentingView.opacity,
|
|
propertiesToMatch: pageEndProps?.propertiesToMatch,
|
|
});
|
|
|
|
// set initial opacity to match the source view opacity
|
|
snapshot.alpha = presentingView.opacity;
|
|
// hide both while animating within the transition context
|
|
presentingView.opacity = 0;
|
|
presentedView.opacity = 0;
|
|
}
|
|
};
|
|
|
|
const positionIndependentTags = async () => {
|
|
// independent tags
|
|
for (const tag in pageEndTags) {
|
|
// only handle if independent (otherwise it's shared between both pages and handled above)
|
|
if (!sharedElementTags.includes(tag)) {
|
|
// only consider start when there's a matching end
|
|
const pageStartIndependentProps = pageStart?.sharedTransitionTags ? pageStart?.sharedTransitionTags[tag] : null;
|
|
// console.log('start:', tag, pageStartIndependentProps);
|
|
const pageEndProps = pageEndTags[tag];
|
|
let independentView = presenting.find((v) => v.sharedTransitionTag === tag);
|
|
let isPresented = false;
|
|
if (!independentView) {
|
|
independentView = presented.find((v) => v.sharedTransitionTag === tag);
|
|
if (!independentView) {
|
|
break;
|
|
}
|
|
isPresented = true;
|
|
}
|
|
const independentSharedElement: UIView = independentView.ios;
|
|
|
|
if (pageEndProps?.callback) {
|
|
await pageEndProps?.callback(independentView, 'present');
|
|
}
|
|
|
|
// let snapshot: UIImageView;
|
|
// if (isPresented) {
|
|
// snapshot = UIImageView.alloc().init();
|
|
// } else {
|
|
const snapshot = UIImageView.alloc().initWithImage(iOSUtils.snapshotView(independentSharedElement, Screen.mainScreen.scale));
|
|
// }
|
|
|
|
if (independentSharedElement instanceof UIImageView) {
|
|
// in case the image is loaded async, we need to update the snapshot when it changes
|
|
// todo: remove listener on transition end
|
|
// if (isPresented) {
|
|
// independentView.on('imageSourceChange', () => {
|
|
// snapshot.image = iOSNativeHelper.snapshotView(independentSharedElement, Screen.mainScreen.scale);
|
|
// snapshot.tintColor = independentSharedElement.tintColor;
|
|
// });
|
|
// }
|
|
|
|
snapshot.tintColor = independentSharedElement.tintColor;
|
|
snapshot.contentMode = independentSharedElement.contentMode;
|
|
}
|
|
snapshot.clipsToBounds = true;
|
|
|
|
const startFrame = independentSharedElement.convertRectToView(independentSharedElement.bounds, transitionContext.containerView);
|
|
const startFrameRect = getRectFromProps(pageStartIndependentProps);
|
|
// adjust for any specified start positions
|
|
const startFrameAdjusted = CGRectMake(startFrame.origin.x + startFrameRect.x, startFrame.origin.y + startFrameRect.y, startFrame.size.width, startFrame.size.height);
|
|
// console.log('startFrameAdjusted:', tag, iOSNativeHelper.printCGRect(startFrameAdjusted));
|
|
// if (pageStartIndependentProps?.scale) {
|
|
// snapshot.transform = CGAffineTransformConcat(CGAffineTransformMakeTranslation(startFrameAdjusted.origin.x, startFrameAdjusted.origin.y), CGAffineTransformMakeScale(pageStartIndependentProps.scale.x, pageStartIndependentProps.scale.y))
|
|
// } else {
|
|
snapshot.frame = startFrame; //startFrameAdjusted;
|
|
// }
|
|
if (SharedTransition.DEBUG) {
|
|
console.log('---> ', independentView.sharedTransitionTag, ' frame:', iOSUtils.printCGRect(snapshot.frame));
|
|
}
|
|
|
|
const endFrameRect = getRectFromProps(pageEndProps);
|
|
|
|
const endFrame = CGRectMake(startFrame.origin.x + endFrameRect.x, startFrame.origin.y + endFrameRect.y, startFrame.size.width, startFrame.size.height);
|
|
// console.log('endFrame:', tag, iOSNativeHelper.printCGRect(endFrame));
|
|
transition.sharedElements.independent.push({
|
|
view: independentView,
|
|
isPresented,
|
|
startFrame,
|
|
snapshot,
|
|
endFrame,
|
|
startTransform: independentSharedElement.transform,
|
|
scale: pageEndProps.scale,
|
|
startOpacity: independentView.opacity,
|
|
endOpacity: isNumber(pageEndProps.opacity) ? pageEndProps.opacity : 0,
|
|
propertiesToMatch: pageEndProps?.propertiesToMatch,
|
|
zIndex: isNumber(pageEndProps?.zIndex) ? pageEndProps.zIndex : 0,
|
|
});
|
|
|
|
independentView.opacity = 0;
|
|
}
|
|
}
|
|
};
|
|
|
|
// position all sharedTransitionTag elements
|
|
await positionSharedTags();
|
|
await positionIndependentTags();
|
|
// combine to order by zIndex and add to transition context
|
|
const snapshotData = transition.sharedElements.presenting.concat(transition.sharedElements.independent);
|
|
snapshotData.sort((a, b) => (a.zIndex > b.zIndex ? 1 : -1));
|
|
if (SharedTransition.DEBUG) {
|
|
console.log(
|
|
`zIndex settings:`,
|
|
snapshotData.map((s) => {
|
|
return {
|
|
sharedTransitionTag: s.view.sharedTransitionTag,
|
|
zIndex: s.zIndex,
|
|
};
|
|
})
|
|
);
|
|
}
|
|
|
|
for (const data of snapshotData) {
|
|
// add snapshot to animate
|
|
transitionContext.containerView.addSubview(data.snapshot);
|
|
}
|
|
|
|
// Important: always set after above shared element positions have had their start positions set
|
|
transition.presented.view.alpha = isNumber(pageStart?.opacity) ? pageStart?.opacity : 0;
|
|
transition.presented.view.frame = CGRectMake(startFrame.x, startFrame.y, startFrame.width, startFrame.height);
|
|
|
|
const cleanupPresent = () => {
|
|
for (const presented of transition.sharedElements.presented) {
|
|
presented.view.opacity = presented.startOpacity;
|
|
}
|
|
for (const presenting of transition.sharedElements.presenting) {
|
|
presenting.snapshot.removeFromSuperview();
|
|
}
|
|
for (const independent of transition.sharedElements.independent) {
|
|
independent.snapshot.removeFromSuperview();
|
|
if (independent.isPresented) {
|
|
independent.view.opacity = independent.startOpacity;
|
|
}
|
|
}
|
|
SharedTransition.updateState(transition.id, {
|
|
activeType: SharedTransitionAnimationType.dismiss,
|
|
});
|
|
if (type === 'page') {
|
|
transition.presenting.view.removeFromSuperview();
|
|
}
|
|
transitionContext.completeTransition(true);
|
|
SharedTransition.notifyEvent(SharedTransition.finishedEvent, {
|
|
id: transition.id,
|
|
type,
|
|
action: 'present',
|
|
});
|
|
};
|
|
|
|
const animateProperties = () => {
|
|
if (SharedTransition.DEBUG) {
|
|
console.log('3. Animating shared elements:');
|
|
}
|
|
transition.presented.view.alpha = isNumber(pageEnd?.opacity) ? pageEnd?.opacity : 1;
|
|
|
|
const endFrame = getRectFromProps(pageEnd);
|
|
transition.presented.view.frame = CGRectMake(endFrame.x, endFrame.y, endFrame.width, endFrame.height);
|
|
|
|
if (pageOut) {
|
|
if (isNumber(pageOut.opacity)) {
|
|
transition.presenting.view.alpha = pageOut?.opacity;
|
|
}
|
|
|
|
const outFrame = getRectFromProps(pageOut);
|
|
transition.presenting.view.frame = CGRectMake(outFrame.x, outFrame.y, outFrame.width, outFrame.height);
|
|
}
|
|
// animate page properties to the following:
|
|
// https://stackoverflow.com/a/27997678/1418981
|
|
// In order to have proper layout. Seems mostly needed when presenting.
|
|
// For instance during presentation, destination view doesn't account navigation bar height.
|
|
// Not sure if best to leave all the time?
|
|
// owner.presented.view.setNeedsLayout();
|
|
// owner.presented.view.layoutIfNeeded();
|
|
|
|
for (const presented of transition.sharedElements.presented) {
|
|
const presentingMatch = transition.sharedElements.presenting.find((v) => v.view.sharedTransitionTag === presented.view.sharedTransitionTag);
|
|
// Workaround wrong origin due ongoing layout process.
|
|
const updatedEndFrame = presented.view.ios.convertRectToView(presented.view.ios.bounds, transitionContext.containerView);
|
|
const correctedEndFrame = CGRectMake(updatedEndFrame.origin.x, updatedEndFrame.origin.y, presentingMatch.endFrame.size.width, presentingMatch.endFrame.size.height);
|
|
presentingMatch.snapshot.frame = correctedEndFrame;
|
|
|
|
// apply view and layer properties to the snapshot view to match the source/presented view
|
|
iOSUtils.copyLayerProperties(presentingMatch.snapshot, presented.view.ios, presented.propertiesToMatch as any);
|
|
// create a snapshot of the presented view
|
|
presentingMatch.snapshot.image = iOSUtils.snapshotView(presented.view.ios, Screen.mainScreen.scale);
|
|
// apply correct alpha
|
|
presentingMatch.snapshot.alpha = presentingMatch.endOpacity;
|
|
|
|
if (SharedTransition.DEBUG) {
|
|
console.log(`---> ${presentingMatch.view.sharedTransitionTag} animate to: `, iOSUtils.printCGRect(correctedEndFrame));
|
|
}
|
|
}
|
|
for (const independent of transition.sharedElements.independent) {
|
|
const endFrame: CGRect = independent.endFrame;
|
|
// if (independent.isPresented) {
|
|
// const updatedEndFrame = independent.view.ios.convertRectToView(independent.view.ios.bounds, transitionContext.containerView);
|
|
// endFrame = CGRectMake(updatedEndFrame.origin.x, updatedEndFrame.origin.y, independent.endFrame.size.width, independent.endFrame.size.height);
|
|
// }
|
|
if (independent.scale) {
|
|
independent.snapshot.transform = CGAffineTransformConcat(CGAffineTransformMakeTranslation(endFrame.origin.x, endFrame.origin.y), CGAffineTransformMakeScale(independent.scale.x, independent.scale.y));
|
|
} else {
|
|
independent.snapshot.frame = endFrame;
|
|
}
|
|
independent.snapshot.alpha = independent.endOpacity;
|
|
|
|
if (SharedTransition.DEBUG) {
|
|
console.log(`---> ${independent.view.sharedTransitionTag} animate to: `, iOSUtils.printCGRect(independent.endFrame));
|
|
}
|
|
}
|
|
};
|
|
|
|
if (isNumber(pageEnd?.duration)) {
|
|
// override spring and use only linear animation
|
|
UIView.animateWithDurationDelayOptionsAnimationsCompletion(
|
|
pageEnd?.duration / 1000,
|
|
0,
|
|
UIViewAnimationOptions.CurveEaseInOut,
|
|
() => {
|
|
animateProperties();
|
|
},
|
|
() => {
|
|
cleanupPresent();
|
|
}
|
|
);
|
|
} else {
|
|
iOSUtils.animateWithSpring({
|
|
...getSpringFromProps(pageEnd?.spring),
|
|
animations: () => {
|
|
animateProperties();
|
|
},
|
|
completion: () => {
|
|
cleanupPresent();
|
|
},
|
|
});
|
|
}
|
|
|
|
break;
|
|
}
|
|
case SharedTransitionAnimationType.dismiss: {
|
|
// console.log('-- Transition dismiss --');
|
|
SharedTransition.notifyEvent(SharedTransition.startedEvent, {
|
|
id: transition.id,
|
|
type,
|
|
action: 'dismiss',
|
|
});
|
|
if (type === 'page') {
|
|
transitionContext.containerView.insertSubviewBelowSubview(transition.presenting.view, transition.presented.view);
|
|
}
|
|
|
|
// console.log('transitionContext.containerView.subviews.count:', transitionContext.containerView.subviews.count);
|
|
|
|
if (SharedTransition.DEBUG) {
|
|
console.log(` ${type}: Dismiss`);
|
|
console.log(
|
|
`1. Dismiss sharedTransitionTags to animate:`,
|
|
transition.sharedElements.presented.map((p) => p.view.sharedTransitionTag)
|
|
);
|
|
|
|
console.log(`2. Add back previously stored sharedElements to dismiss:`);
|
|
}
|
|
|
|
const pageOut = state.pageOut;
|
|
const pageEnd = state.pageEnd;
|
|
const pageEndTags = pageEnd?.sharedTransitionTags || {};
|
|
const pageReturn = state.pageReturn;
|
|
|
|
for (const p of transition.sharedElements.presented) {
|
|
p.view.opacity = 0;
|
|
}
|
|
|
|
// combine to order by zIndex and add to transition context
|
|
const snapshotData = transition.sharedElements.presenting.concat(transition.sharedElements.independent);
|
|
snapshotData.sort((a, b) => (a.zIndex > b.zIndex ? 1 : -1));
|
|
if (SharedTransition.DEBUG) {
|
|
console.log(
|
|
`zIndex settings:`,
|
|
snapshotData.map((s) => {
|
|
return {
|
|
sharedTransitionTag: s.view.sharedTransitionTag,
|
|
zIndex: s.zIndex,
|
|
};
|
|
})
|
|
);
|
|
}
|
|
|
|
// first loop through all the shared elements and fire the callback
|
|
for (const data of snapshotData) {
|
|
const pageEndProps = pageEndTags[data.view.sharedTransitionTag];
|
|
if (pageEndProps?.callback) {
|
|
await pageEndProps?.callback(data.view, 'dismiss');
|
|
}
|
|
}
|
|
|
|
// now that all the callbacks had their chance to run, we can take the snapshots
|
|
for (const data of snapshotData) {
|
|
const view = data.view.ios;
|
|
const currentAlpha = view.alpha;
|
|
if (pageReturn?.useStartOpacity) {
|
|
// when desired, reset the alpha to the start value so the view is visible in the snapshot
|
|
view.alpha = data.startOpacity;
|
|
}
|
|
|
|
// take a new snapshot
|
|
data.snapshot.image = iOSUtils.snapshotView(view, Screen.mainScreen.scale);
|
|
|
|
// find the currently visible view with the same sharedTransitionTag
|
|
const fromView = transition.sharedElements.presented.find((p) => p.view.sharedTransitionTag === data.view.sharedTransitionTag)?.view;
|
|
if (fromView) {
|
|
// match the snapshot frame to the current frame of the fromView
|
|
data.snapshot.frame = fromView.ios.convertRectToView(fromView.ios.bounds, transitionContext.containerView);
|
|
}
|
|
|
|
// snapshot has been taken, we can restore the alpha
|
|
view.alpha = currentAlpha;
|
|
|
|
// we recalculate the startFrame because the view might have changed its position in the background
|
|
data.startFrame = view.convertRectToView(view.bounds, transitionContext.containerView);
|
|
|
|
// add snapshot to animate
|
|
transitionContext.containerView.addSubview(data.snapshot);
|
|
}
|
|
|
|
const cleanupDismiss = () => {
|
|
for (const presenting of transition.sharedElements.presenting) {
|
|
presenting.view.opacity = presenting.startOpacity;
|
|
presenting.snapshot.removeFromSuperview();
|
|
}
|
|
for (const independent of transition.sharedElements.independent) {
|
|
independent.view.opacity = independent.startOpacity;
|
|
independent.snapshot.removeFromSuperview();
|
|
}
|
|
SharedTransition.finishState(transition.id);
|
|
transition.sharedElements = null;
|
|
transitionContext.completeTransition(true);
|
|
SharedTransition.notifyEvent(SharedTransition.finishedEvent, {
|
|
id: transition.id,
|
|
type,
|
|
action: 'dismiss',
|
|
});
|
|
};
|
|
|
|
const animateProperties = () => {
|
|
if (SharedTransition.DEBUG) {
|
|
console.log('3. Dismissing shared elements:');
|
|
}
|
|
|
|
transition.presented.view.alpha = isNumber(pageReturn?.opacity) ? pageReturn?.opacity : 0;
|
|
|
|
const endFrame = getRectFromProps(pageReturn, getPageStartDefaultsForType(type));
|
|
transition.presented.view.frame = CGRectMake(endFrame.x, endFrame.y, endFrame.width, endFrame.height);
|
|
|
|
if (pageOut) {
|
|
// always return to defaults if pageOut had been used
|
|
transition.presenting.view.alpha = 1;
|
|
|
|
const outFrame = getRectFromProps(null);
|
|
transition.presenting.view.frame = CGRectMake(0, 0, outFrame.width, outFrame.height);
|
|
}
|
|
|
|
for (const presenting of transition.sharedElements.presenting) {
|
|
iOSUtils.copyLayerProperties(presenting.snapshot, presenting.view.ios, presenting.propertiesToMatch as any);
|
|
presenting.snapshot.frame = presenting.startFrame;
|
|
presenting.snapshot.alpha = presenting.startOpacity;
|
|
|
|
if (SharedTransition.DEBUG) {
|
|
console.log(`---> ${presenting.view.sharedTransitionTag} animate to: `, iOSUtils.printCGRect(presenting.snapshot.frame));
|
|
}
|
|
}
|
|
|
|
for (const independent of transition.sharedElements.independent) {
|
|
independent.snapshot.alpha = independent.startOpacity;
|
|
if (independent.scale) {
|
|
independent.snapshot.transform = independent.startTransform;
|
|
} else {
|
|
independent.snapshot.frame = independent.startFrame;
|
|
}
|
|
|
|
if (SharedTransition.DEBUG) {
|
|
console.log(`---> ${independent.view.sharedTransitionTag} animate to: `, iOSUtils.printCGRect(independent.snapshot.frame));
|
|
}
|
|
}
|
|
};
|
|
|
|
if (isNumber(pageReturn?.duration)) {
|
|
// override spring and use only linear animation
|
|
UIView.animateWithDurationDelayOptionsAnimationsCompletion(
|
|
pageReturn?.duration / 1000,
|
|
0,
|
|
UIViewAnimationOptions.CurveEaseInOut,
|
|
() => {
|
|
animateProperties();
|
|
},
|
|
() => {
|
|
cleanupDismiss();
|
|
}
|
|
);
|
|
} else {
|
|
iOSUtils.animateWithSpring({
|
|
...getSpringFromProps(pageReturn?.spring),
|
|
animations: () => {
|
|
animateProperties();
|
|
},
|
|
completion: () => {
|
|
cleanupDismiss();
|
|
},
|
|
});
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
static interactiveStart(state: SharedTransitionState, interactiveState: PlatformTransitionInteractiveState, type: TransitionNavigationType) {
|
|
SharedTransition.notifyEvent(SharedTransition.startedEvent, {
|
|
id: state.instance.id,
|
|
type,
|
|
action: 'interactiveStart',
|
|
});
|
|
switch (type) {
|
|
case 'page':
|
|
interactiveState.transitionContext.containerView.insertSubviewBelowSubview(state.instance.presenting.view, state.instance.presented.view);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static interactiveUpdate(state: SharedTransitionState, interactiveState: PlatformTransitionInteractiveState, type: TransitionNavigationType, percent: number) {
|
|
if (interactiveState) {
|
|
if (!interactiveState.added) {
|
|
interactiveState.added = true;
|
|
for (const p of state.instance.sharedElements.presented) {
|
|
p.view.opacity = 0;
|
|
}
|
|
for (const p of state.instance.sharedElements.presenting) {
|
|
p.snapshot.alpha = p.endOpacity;
|
|
interactiveState.transitionContext.containerView.addSubview(p.snapshot);
|
|
}
|
|
|
|
const pageStart = state.pageStart;
|
|
|
|
const startFrame = getRectFromProps(pageStart, getPageStartDefaultsForType(type));
|
|
interactiveState.propertyAnimator = UIViewPropertyAnimator.alloc().initWithDurationDampingRatioAnimations(1, 1, () => {
|
|
for (const p of state.instance.sharedElements.presenting) {
|
|
p.snapshot.frame = p.startFrame;
|
|
iOSUtils.copyLayerProperties(p.snapshot, p.view.ios, p.propertiesToMatch as any);
|
|
|
|
p.snapshot.alpha = 1;
|
|
}
|
|
state.instance.presented.view.alpha = isNumber(state.pageReturn?.opacity) ? state.pageReturn?.opacity : 0;
|
|
state.instance.presented.view.frame = CGRectMake(startFrame.x, startFrame.y, state.instance.presented.view.bounds.size.width, state.instance.presented.view.bounds.size.height);
|
|
});
|
|
}
|
|
|
|
interactiveState.propertyAnimator.fractionComplete = percent;
|
|
SharedTransition.notifyEvent(SharedTransition.interactiveUpdateEvent, {
|
|
id: state?.instance?.id,
|
|
type,
|
|
percent,
|
|
});
|
|
}
|
|
}
|
|
|
|
static interactiveCancel(state: SharedTransitionState, interactiveState: PlatformTransitionInteractiveState, type: TransitionNavigationType) {
|
|
if (state?.instance && interactiveState?.added && interactiveState?.propertyAnimator) {
|
|
interactiveState.propertyAnimator.reversed = true;
|
|
const duration = isNumber(state.pageEnd?.duration) ? state.pageEnd?.duration / 1000 : CORE_ANIMATION_DEFAULTS.duration;
|
|
interactiveState.propertyAnimator.continueAnimationWithTimingParametersDurationFactor(null, duration);
|
|
setTimeout(() => {
|
|
for (const p of state.instance.sharedElements.presented) {
|
|
p.view.opacity = 1;
|
|
}
|
|
for (const p of state.instance.sharedElements.presenting) {
|
|
p.snapshot.removeFromSuperview();
|
|
}
|
|
state.instance.presented.view.alpha = 1;
|
|
interactiveState.propertyAnimator = null;
|
|
interactiveState.added = false;
|
|
interactiveState.transitionContext.cancelInteractiveTransition();
|
|
interactiveState.transitionContext.completeTransition(false);
|
|
SharedTransition.updateState(state?.instance?.id, {
|
|
interactiveBegan: false,
|
|
interactiveCancelled: true,
|
|
});
|
|
SharedTransition.notifyEvent(SharedTransition.interactiveCancelledEvent, {
|
|
id: state?.instance?.id,
|
|
type,
|
|
});
|
|
}, duration * 1000);
|
|
}
|
|
}
|
|
|
|
static interactiveFinish(state: SharedTransitionState, interactiveState: PlatformTransitionInteractiveState, type: TransitionNavigationType) {
|
|
if (state?.instance && interactiveState?.added && interactiveState?.propertyAnimator) {
|
|
interactiveState.propertyAnimator.reversed = false;
|
|
|
|
const duration = isNumber(state.pageReturn?.duration) ? state.pageReturn?.duration / 1000 : CORE_ANIMATION_DEFAULTS.duration;
|
|
interactiveState.propertyAnimator.continueAnimationWithTimingParametersDurationFactor(null, duration);
|
|
setTimeout(() => {
|
|
for (const presenting of state.instance.sharedElements.presenting) {
|
|
presenting.view.opacity = presenting.startOpacity;
|
|
presenting.snapshot.removeFromSuperview();
|
|
}
|
|
|
|
SharedTransition.finishState(state.instance.id);
|
|
interactiveState.propertyAnimator = null;
|
|
interactiveState.added = false;
|
|
interactiveState.transitionContext.finishInteractiveTransition();
|
|
interactiveState.transitionContext.completeTransition(true);
|
|
SharedTransition.notifyEvent(SharedTransition.finishedEvent, {
|
|
id: state?.instance?.id,
|
|
type,
|
|
action: 'interactiveFinish',
|
|
});
|
|
}, duration * 1000);
|
|
}
|
|
}
|
|
}
|