From 8b79a40ca9fdcabf30032da6a24693bee1bc55e6 Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Mon, 20 Mar 2023 23:44:07 -0700 Subject: [PATCH] feat: support for independent transition tags and cleanup wip --- apps/toolbox/src/pages/transitions.ts | 4 +- .../ui/transition/modal-transition.ios.ts | 269 +++++++++++++----- .../core/ui/transition/page-transition.ios.ts | 16 +- .../core/ui/transition/shared-transition.ts | 42 ++- 4 files changed, 238 insertions(+), 93 deletions(-) diff --git a/apps/toolbox/src/pages/transitions.ts b/apps/toolbox/src/pages/transitions.ts index a1db5edc8..adf7d7777 100644 --- a/apps/toolbox/src/pages/transitions.ts +++ b/apps/toolbox/src/pages/transitions.ts @@ -38,11 +38,11 @@ export class TransitionsModel extends Observable { finishThreshold: 0.5, }, }, - toPageStart: { + pageStart: { y: 200, // duration: 400, }, - fromPageEnd: { + pageReturn: { y: 100, // duration: 500, }, diff --git a/packages/core/ui/transition/modal-transition.ios.ts b/packages/core/ui/transition/modal-transition.ios.ts index 7d52fe1a7..af8a790dc 100644 --- a/packages/core/ui/transition/modal-transition.ios.ts +++ b/packages/core/ui/transition/modal-transition.ios.ts @@ -3,8 +3,9 @@ import { Screen } from '../../platform'; import { iOSNativeHelper } from '../../utils/native-helper'; import { isNumber } from '../../utils/types'; import { Transition } from '.'; -import { SharedTransition, SharedTransitionAnimationType, DEFAULT_DURATION, DEFAULT_SPRING } from './shared-transition'; +import { SharedTransition, SharedTransitionAnimationType, DEFAULT_DURATION, DEFAULT_SPRING, getRectFromProps } from './shared-transition'; import { PanGestureEventData, GestureStateTypes } from '../gestures'; +type SharedElementSettings = { view: View; startFrame: CGRect; endFrame?: CGRect; startOpacity?: number; endOpacity?: number; scale?: { x?: number; y?: number }; startTransform?: CGAffineTransform; snapshot?: UIImageView }; export class ModalTransition extends Transition { transitionController: ModalTransitionController; @@ -13,8 +14,10 @@ export class ModalTransition extends Transition { presented: UIViewController; presenting: UIViewController; sharedElements: { - presented?: Array<{ view: View; startFrame?: CGRect; endFrame?: CGRect; snapshot?: UIImageView; startOpacity?: number; endOpacity?: number }>; - presenting?: Array<{ view: View; startFrame: CGRect; endFrame?: CGRect; snapshot?: UIImageView; startOpacity?: number; endOpacity?: number }>; + presented?: Array; + presenting?: Array; + // independent sharedTransitionTags which are part of the shared transition but only on one page + independent?: Array; }; private _interactiveStartCallback: () => void; private _interactiveDismissGesture: (args: any /*PanGestureEventData*/) => void; @@ -132,7 +135,7 @@ class PercentInteractiveController extends UIPercentDrivenInteractiveTransition this.transitionContext.containerView.addSubview(p.snapshot); } const state = SharedTransition.getState(owner.id); - const toProps = state.toPageStart; + const toProps = state.pageStart; const startY = isNumber(toProps?.y) ? toProps?.y : Screen.mainScreen.heightDIPs; this.backgroundAnimation = UIViewPropertyAnimator.alloc().initWithDurationDampingRatioAnimations(1, 1, () => { for (const p of owner.sharedElements.presenting) { @@ -160,7 +163,7 @@ class PercentInteractiveController extends UIPercentDrivenInteractiveTransition } if (this.backgroundAnimation) { this.backgroundAnimation.reversed = true; - const duration = isNumber(state.toPageStart?.duration) ? state.toPageStart?.duration / 1000 : DEFAULT_DURATION; + const duration = isNumber(state.pageStart?.duration) ? state.pageStart?.duration / 1000 : DEFAULT_DURATION; this.backgroundAnimation.continueAnimationWithTimingParametersDurationFactor(null, duration); setTimeout(() => { for (const p of owner.sharedElements.presented) { @@ -192,7 +195,7 @@ class PercentInteractiveController extends UIPercentDrivenInteractiveTransition return; } - const duration = isNumber(state.fromPageEnd?.duration) ? state.fromPageEnd?.duration / 1000 : DEFAULT_DURATION; + const duration = isNumber(state.pageReturn?.duration) ? state.pageReturn?.duration / 1000 : DEFAULT_DURATION; this.backgroundAnimation.continueAnimationWithTimingParametersDurationFactor(null, duration); setTimeout(() => { for (const presenting of owner.sharedElements.presenting) { @@ -239,7 +242,7 @@ class ModalTransitionController extends NSObject implements UIViewControllerAnim transitionContext.containerView.addSubview(owner.presented.view); owner.presented.view.layoutIfNeeded(); - const { sharedElements, presented } = SharedTransition.getSharedElements(state.page, state.toPage); + const { sharedElements, presented, presenting } = SharedTransition.getSharedElements(state.page, state.toPage); if (SharedTransition.DEBUG) { console.log(' ModalTransition: Present'); @@ -251,11 +254,26 @@ class ModalTransitionController extends NSObject implements UIViewControllerAnim console.log(`2. Take snapshots of shared elements and position them based on presenting view:`); } + const pageStart = state.pageStart; + owner.presented.view.alpha = isNumber(pageStart?.opacity) ? pageStart?.opacity : 0; + + const startFrame = getRectFromProps(pageStart, { + x: 0, + y: Screen.mainScreen.heightDIPs, + width: Screen.mainScreen.widthDIPs, + height: Screen.mainScreen.heightDIPs, + }); + + const pageEnd = state.pageEnd; + const pageEndIndependentTags = Object.keys(pageEnd?.sharedTransitionTags || {}); + console.log('pageEndIndependentTags:', pageEndIndependentTags); + for (const presentingView of sharedElements) { if (!owner.sharedElements) { owner.sharedElements = { presented: [], presenting: [], + independent: [], }; } const presentingSharedElement = presentingView.ios; @@ -268,37 +286,37 @@ class ModalTransitionController extends NSObject implements UIViewControllerAnim const presentedView = presented.find((v) => v.sharedTransitionTag === presentingView.sharedTransitionTag); const presentedSharedElement = presentedView.ios; - const sharedElementSnapshot = UIImageView.alloc().init(); // WithImage(snapshotView(presentedSharedElement)); + const snapshot = UIImageView.alloc().init(); // 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', () => { - sharedElementSnapshot.image = iOSNativeHelper.snapshotView(presentedSharedElement, Screen.mainScreen.scale); - sharedElementSnapshot.tintColor = presentedSharedElement.tintColor; + snapshot.image = iOSNativeHelper.snapshotView(presentedSharedElement, Screen.mainScreen.scale); + snapshot.tintColor = presentedSharedElement.tintColor; }); - sharedElementSnapshot.tintColor = presentedSharedElement.tintColor; - sharedElementSnapshot.contentMode = presentedSharedElement.contentMode; + snapshot.tintColor = presentedSharedElement.tintColor; + snapshot.contentMode = presentedSharedElement.contentMode; } - iOSNativeHelper.copyLayerProperties(sharedElementSnapshot, presentingSharedElement); - sharedElementSnapshot.clipsToBounds = true; - // console.log('---> snapshot: ', sharedElementSnapshot); + iOSNativeHelper.copyLayerProperties(snapshot, presentingSharedElement); + snapshot.clipsToBounds = true; + // console.log('---> snapshot: ', snapshot); const startFrame = presentingSharedElement.convertRectToView(presentingSharedElement.bounds, transitionContext.containerView); const endFrame = presentedSharedElement.convertRectToView(presentedSharedElement.bounds, transitionContext.containerView); - sharedElementSnapshot.frame = startFrame; + snapshot.frame = startFrame; if (SharedTransition.DEBUG) { - console.log('---> ', presentingView.sharedTransitionTag, ' frame:', iOSNativeHelper.printCGRect(sharedElementSnapshot.frame)); + console.log('---> ', presentingView.sharedTransitionTag, ' frame:', iOSNativeHelper.printCGRect(snapshot.frame)); } owner.sharedElements.presenting.push({ view: presentingView, startFrame, endFrame, - snapshot: sharedElementSnapshot, + snapshot, startOpacity: presentingView.opacity, endOpacity: presentedView.opacity, }); @@ -311,14 +329,90 @@ class ModalTransitionController extends NSObject implements UIViewControllerAnim }); // set initial opacity to match the source view opacity - sharedElementSnapshot.alpha = presentingView.opacity; + snapshot.alpha = presentingView.opacity; // hide both while animating within the transition context presentingView.opacity = 0; presentedView.opacity = 0; // add snapshot to animate - transitionContext.containerView.addSubview(sharedElementSnapshot); + transitionContext.containerView.addSubview(snapshot); } + for (const tag of pageEndIndependentTags) { + // only consider start when there's a matching end + const pageStartIndependentProps = pageStart?.sharedTransitionTags[tag]; + console.log('start:', tag, pageStartIndependentProps); + const pageEndIndependentProps = pageEnd?.sharedTransitionTags[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; + + let snapshot: UIImageView; + if (isPresented) { + snapshot = UIImageView.alloc().init(); + } else { + snapshot = UIImageView.alloc().initWithImage(iOSNativeHelper.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 = startFrameAdjusted; + // } + if (SharedTransition.DEBUG) { + console.log('---> ', independentView.sharedTransitionTag, ' frame:', iOSNativeHelper.printCGRect(snapshot.frame)); + } + + const endFrameRect = getRectFromProps(pageEndIndependentProps); + + 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)); + owner.sharedElements.independent.push({ + view: independentView, + isPresented, + startFrame, + snapshot, + endFrame, + startTransform: independentSharedElement.transform, + scale: pageEndIndependentProps.scale, + startOpacity: independentView.opacity, + endOpacity: isNumber(pageEndIndependentProps.opacity) ? pageEndIndependentProps.opacity : 0, + }); + + independentView.opacity = 0; + // add snapshot to animate + transitionContext.containerView.addSubview(snapshot); + } + + // Important: always set after above shared element positions have had their start positions set + owner.presented.view.frame = CGRectMake(startFrame.x, startFrame.y, startFrame.width, startFrame.height); + const cleanupPresent = () => { for (const presented of owner.sharedElements.presented) { presented.view.opacity = presented.startOpacity; @@ -326,21 +420,35 @@ class ModalTransitionController extends NSObject implements UIViewControllerAnim for (const presenting of owner.sharedElements.presenting) { presenting.snapshot.removeFromSuperview(); } + for (const independent of owner.sharedElements.independent) { + independent.snapshot.removeFromSuperview(); + if (independent.isPresented) { + independent.view.opacity = independent.startOpacity; + } + } SharedTransition.updateState(owner.id, { activeType: SharedTransitionAnimationType.dismiss, }); transitionContext.completeTransition(true); }; - const updateFramePresent = () => { + + const animateProperties = () => { + if (SharedTransition.DEBUG) { + console.log('3. Animating shared elements:'); + } + owner.presented.view.alpha = isNumber(pageEnd?.opacity) ? pageEnd?.opacity : 1; + + const endFrame = getRectFromProps(pageEnd); + owner.presented.view.frame = CGRectMake(endFrame.x, endFrame.y, endFrame.width, endFrame.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(); - if (SharedTransition.DEBUG) { - console.log('3. Animating shared elements:'); - } + for (const presented of owner.sharedElements.presented) { const presentingMatch = owner.sharedElements.presenting.find((v) => v.view.sharedTransitionTag === presented.view.sharedTransitionTag); // Workaround wrong origin due ongoing layout process. @@ -359,51 +467,46 @@ class ModalTransitionController extends NSObject implements UIViewControllerAnim console.log(`---> ${presentingMatch.view.sharedTransitionTag} animate to: `, iOSNativeHelper.printCGRect(correctedEndFrame)); } } + for (const independent of owner.sharedElements.independent) { + let 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: `, iOSNativeHelper.printCGRect(independent.endFrame)); + } + } }; - // starting page properties - const toProps = state.toPageStart; - owner.presented.view.alpha = isNumber(toProps?.opacity) ? toProps?.opacity : 0; - - const startX = isNumber(toProps?.x) ? toProps?.x : 0; - const startY = isNumber(toProps?.y) ? toProps?.y : Screen.mainScreen.heightDIPs; - const startWidth = isNumber(toProps?.width) ? toProps?.width : Screen.mainScreen.widthDIPs; - const startHeight = isNumber(toProps?.height) ? toProps?.height : Screen.mainScreen.heightDIPs; - owner.presented.view.frame = CGRectMake(startX, startY, startWidth, startHeight); - - const animateProperties = () => { - const props = state.toPageEnd; - // animate page properties to the following: - owner.presented.view.alpha = isNumber(props?.opacity) ? props?.opacity : 1; - - const endX = isNumber(props?.x) ? props?.x : 0; - const endY = isNumber(props?.y) ? props?.y : 0; - const endWidth = isNumber(props?.width) ? props?.width : Screen.mainScreen.widthDIPs; - const endHeight = isNumber(props?.height) ? props?.height : Screen.mainScreen.heightDIPs; - owner.presented.view.frame = CGRectMake(endX, endY, endWidth, endHeight); - }; - - if (isNumber(toProps?.duration)) { + if (isNumber(pageEnd?.duration)) { // override spring and use only linear animation UIView.animateWithDurationAnimationsCompletion( - toProps?.duration / 1000, + pageEnd?.duration / 1000, () => { animateProperties(); - updateFramePresent(); }, () => { cleanupPresent(); } ); } else { - const spring = toProps?.spring; + const spring = pageEnd?.spring; iOSNativeHelper.animateWithSpring({ tension: isNumber(spring?.tension) ? spring?.tension : DEFAULT_SPRING.tension, - friction: isNumber(spring?.friction) ? spring?.tension : DEFAULT_SPRING.friction, + friction: isNumber(spring?.friction) ? spring?.friction : DEFAULT_SPRING.friction, + mass: isNumber(spring?.mass) ? spring?.mass : DEFAULT_SPRING.mass, + velocity: isNumber(spring?.velocity) ? spring?.velocity : DEFAULT_SPRING.velocity, + delay: isNumber(spring?.delay) ? spring?.delay : DEFAULT_SPRING.delay, animations: () => { animateProperties(); - - updateFramePresent(); }, completion: () => { cleanupPresent(); @@ -435,18 +538,40 @@ class ModalTransitionController extends NSObject implements UIViewControllerAnim p.snapshot.alpha = p.endOpacity; transitionContext.containerView.addSubview(p.snapshot); } + for (const independent of owner.sharedElements.independent) { + independent.snapshot.alpha = independent.endOpacity; + transitionContext.containerView.addSubview(independent.snapshot); + } + + const pageReturn = state.pageReturn; const cleanupDismiss = () => { for (const presenting of owner.sharedElements.presenting) { presenting.view.opacity = presenting.startOpacity; } + for (const independent of owner.sharedElements.independent) { + independent.view.opacity = independent.startOpacity; + } SharedTransition.finishState(owner.id); + owner.sharedElements = null; transitionContext.completeTransition(true); }; - const updateFrameDismiss = () => { + + const animateProperties = () => { if (SharedTransition.DEBUG) { console.log('3. Dismissing shared elements:'); } + + owner.presented.view.alpha = isNumber(pageReturn?.opacity) ? pageReturn?.opacity : 0; + + const endFrame = getRectFromProps(pageReturn, { + x: 0, + y: Screen.mainScreen.heightDIPs, + width: Screen.mainScreen.widthDIPs, + height: Screen.mainScreen.heightDIPs, + }); + owner.presented.view.frame = CGRectMake(endFrame.x, endFrame.y, endFrame.width, endFrame.height); + for (const presenting of owner.sharedElements.presenting) { iOSNativeHelper.copyLayerProperties(presenting.snapshot, presenting.view.ios); presenting.snapshot.frame = presenting.startFrame; @@ -456,41 +581,39 @@ class ModalTransitionController extends NSObject implements UIViewControllerAnim console.log(`---> ${presenting.view.sharedTransitionTag} animate to: `, iOSNativeHelper.printCGRect(presenting.startFrame)); } } + + for (const independent of owner.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: `, iOSNativeHelper.printCGRect(independent.startFrame)); + } + } }; - const props = state.fromPageEnd; - - const animateProperties = () => { - owner.presented.view.alpha = isNumber(props?.opacity) ? props?.opacity : 0; - - const endX = isNumber(props?.x) ? props?.x : 0; - const endY = isNumber(props?.y) ? props?.y : Screen.mainScreen.heightDIPs; - const endWidth = isNumber(props?.width) ? props?.width : Screen.mainScreen.widthDIPs; - const endHeight = isNumber(props?.height) ? props?.height : Screen.mainScreen.heightDIPs; - owner.presented.view.frame = CGRectMake(endX, endY, endWidth, endHeight); - }; - - if (isNumber(props?.duration)) { + if (isNumber(pageReturn?.duration)) { // override spring and use only linear animation UIView.animateWithDurationAnimationsCompletion( - props?.duration / 1000, + pageReturn?.duration / 1000, () => { animateProperties(); - updateFrameDismiss(); }, () => { cleanupDismiss(); } ); } else { - const spring = props?.spring; + const spring = pageReturn?.spring; iOSNativeHelper.animateWithSpring({ tension: isNumber(spring?.tension) ? spring?.tension : DEFAULT_SPRING.tension, - friction: isNumber(spring?.friction) ? spring?.tension : DEFAULT_SPRING.friction, + friction: isNumber(spring?.friction) ? spring?.friction : DEFAULT_SPRING.friction, animations: () => { animateProperties(); - - updateFrameDismiss(); }, completion: () => { cleanupDismiss(); diff --git a/packages/core/ui/transition/page-transition.ios.ts b/packages/core/ui/transition/page-transition.ios.ts index fa18e3204..786c9180d 100644 --- a/packages/core/ui/transition/page-transition.ios.ts +++ b/packages/core/ui/transition/page-transition.ios.ts @@ -148,7 +148,7 @@ class PercentInteractiveController extends UIPercentDrivenInteractiveTransition this.transitionContext.containerView.addSubview(p.snapshot); } const state = SharedTransition.getState(owner.id); - const props = state.fromPageEnd; + const props = state.pageReturn; this.backgroundAnimation = UIViewPropertyAnimator.alloc().initWithDurationDampingRatioAnimations(1, 1, () => { for (const p of owner.sharedElements.presenting) { p.snapshot.frame = p.startFrame; @@ -179,7 +179,7 @@ class PercentInteractiveController extends UIPercentDrivenInteractiveTransition } if (this.backgroundAnimation) { this.backgroundAnimation.reversed = true; - const duration = isNumber(state.toPageStart?.duration) ? state.toPageStart?.duration / 1000 : 0.35; + const duration = isNumber(state.pageStart?.duration) ? state.pageStart?.duration / 1000 : 0.35; this.backgroundAnimation.continueAnimationWithTimingParametersDurationFactor(null, duration); setTimeout(() => { for (const p of owner.sharedElements.presented) { @@ -211,7 +211,7 @@ class PercentInteractiveController extends UIPercentDrivenInteractiveTransition return; } - const duration = isNumber(state.fromPageEnd?.duration) ? state.fromPageEnd?.duration / 1000 : 0.35; + const duration = isNumber(state.pageReturn?.duration) ? state.pageReturn?.duration / 1000 : 0.35; this.backgroundAnimation.continueAnimationWithTimingParametersDurationFactor(null, duration); setTimeout(() => { for (const presenting of owner.sharedElements.presenting) { @@ -396,7 +396,7 @@ class PageTransitionController extends NSObject implements UIViewControllerAnima }; // starting page properties - const toProps = state.toPageStart; + const toProps = state.pageStart; owner.presented.view.alpha = isNumber(toProps?.opacity) ? toProps?.opacity : 0; const startX = isNumber(toProps?.x) ? toProps?.x : Screen.mainScreen.widthDIPs; @@ -406,7 +406,7 @@ class PageTransitionController extends NSObject implements UIViewControllerAnima owner.presented.view.frame = CGRectMake(startX, startY, startWidth, startHeight); const animateProperties = () => { - const props = state.toPageEnd; + const props = state.pageEnd; // animate page properties to the following: owner.presented.view.alpha = isNumber(props?.opacity) ? props?.opacity : 1; @@ -432,7 +432,7 @@ class PageTransitionController extends NSObject implements UIViewControllerAnima const spring = toProps?.spring; iOSNativeHelper.animateWithSpring({ tension: isNumber(spring?.tension) ? spring?.tension : DEFAULT_SPRING.tension, - friction: isNumber(spring?.friction) ? spring?.tension : DEFAULT_SPRING.friction, + friction: isNumber(spring?.friction) ? spring?.friction : DEFAULT_SPRING.friction, animations: () => { animateProperties(); @@ -492,7 +492,7 @@ class PageTransitionController extends NSObject implements UIViewControllerAnima } }; - const props = state.fromPageEnd; + const props = state.pageReturn; const animateProperties = () => { owner.presented.view.alpha = isNumber(props?.opacity) ? props?.opacity : 0; @@ -520,7 +520,7 @@ class PageTransitionController extends NSObject implements UIViewControllerAnima const spring = props?.spring; iOSNativeHelper.animateWithSpring({ tension: isNumber(spring?.tension) ? spring?.tension : DEFAULT_SPRING.tension, - friction: isNumber(spring?.friction) ? spring?.tension : DEFAULT_SPRING.friction, + friction: isNumber(spring?.friction) ? spring?.friction : DEFAULT_SPRING.friction, animations: () => { animateProperties(); diff --git a/packages/core/ui/transition/shared-transition.ts b/packages/core/ui/transition/shared-transition.ts index 5074802c9..2ab4605bc 100644 --- a/packages/core/ui/transition/shared-transition.ts +++ b/packages/core/ui/transition/shared-transition.ts @@ -1,4 +1,6 @@ import type { Transition } from '.'; +import { Screen } from '../../platform'; +import { isNumber } from '../../utils/types'; import { querySelectorAll, ViewBase } from '../core/view-base'; import type { View } from '../core/view'; import type { PanGestureEventData } from '../gestures'; @@ -7,6 +9,9 @@ export const DEFAULT_DURATION = 0.35; export const DEFAULT_SPRING = { tension: 140, friction: 10, + mass: 1, + velocity: 0, + delay: 0, }; // always increment when adding new transitions to be able to track their state export enum SharedTransitionAnimationType { @@ -50,15 +55,15 @@ export interface SharedTransitionConfig { /** * View settings to start your transition. */ - toPageStart?: SharedTransitionPageProperties; + pageStart?: SharedTransitionPageProperties; /** * View settings to end your transition. */ - toPageEnd?: SharedTransitionPageProperties; + pageEnd?: SharedTransitionPageProperties; /** * View settings to end your transition for the 'from' (aka outgoing or dismissed) page. */ - fromPageEnd?: SharedTransitionPageProperties; + pageReturn?: SharedTransitionPageProperties; } export interface SharedTransitionState extends SharedTransitionConfig { activeType?: SharedTransitionAnimationType; @@ -67,17 +72,15 @@ export interface SharedTransitionState extends SharedTransitionConfig { interactiveBegan?: boolean; interactiveCancelled?: boolean; } -type SharedTransitionPageProperties = { - x?: number; - y?: number; - width?: number; - height?: number; - opacity?: number; +export type SharedRect = { x?: number; y?: number; width?: number; height?: number }; +export type SharedProperties = SharedRect & { opacity?: number; scale?: { x?: number; y?: number } }; +type SharedTransitionPageProperties = SharedProperties & { /** * Linear duration in milliseconds * Note: When this is defined, it will override spring options and use only linear animation. */ duration?: number; + sharedTransitionTags?: { [key: string]: SharedProperties }; /** * Spring animation settings. * Defaults to 140 tension with 10 friction. @@ -173,7 +176,10 @@ export class SharedTransition { // 2. Presenting view: gather all sharedTransitionTag views const presentingSharedElements = >querySelectorAll(fromPage, 'sharedTransitionTag'); - // console.log('presenting sharedTransitionTag total:', presentingSharedElements.length); + console.log( + 'presenting sharedTransitionTags:', + presentingSharedElements.map((v) => v.sharedTransitionTag) + ); // 3. only handle sharedTransitionTag on presenting which match presented const presentedTags = presentedSharedElements.map((v) => v.sharedTransitionTag); @@ -184,3 +190,19 @@ export class SharedTransition { }; } } + +export function getRectFromProps(props: SharedTransitionPageProperties, defaults?: SharedRect): SharedRect { + defaults = { + x: 0, + y: 0, + width: Screen.mainScreen.widthDIPs, + height: Screen.mainScreen.heightDIPs, + ...(defaults || {}), + }; + return { + x: isNumber(props?.x) ? props?.x : defaults.x, + y: isNumber(props?.y) ? props?.y : defaults.y, + width: isNumber(props?.width) ? props?.width : defaults.width, + height: isNumber(props?.height) ? props?.height : defaults.height, + }; +}