From 8a6ba6e2a0e344be0ada139760b6517c18eaa26f Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Mon, 27 Mar 2023 20:39:49 -0700 Subject: [PATCH] chore: automated e2e tests for shared element transitions --- .../src/navigation/custom-transition.d.ts | 4 +- .../src/navigation/custom-transition.ios.ts | 70 +++++++++++++++++-- .../src/navigation/transition-tests.ts | 69 +++++++++++++++--- packages/core/ui/index.ts | 1 + .../shared-transition-helper.android.ts | 6 +- .../transition/shared-transition-helper.d.ts | 16 +++-- 6 files changed, 144 insertions(+), 22 deletions(-) diff --git a/apps/automated/src/navigation/custom-transition.d.ts b/apps/automated/src/navigation/custom-transition.d.ts index 0c470ea60..cb5694fd6 100644 --- a/apps/automated/src/navigation/custom-transition.d.ts +++ b/apps/automated/src/navigation/custom-transition.d.ts @@ -1,6 +1,8 @@ -import { Transition } from '@nativescript/core/ui/transition'; +import { Transition, PageTransition } from '@nativescript/core'; export class CustomTransition extends Transition { constructor(); constructor(duration: number, curve: any); } + +export class CustomSharedElementPageTransition extends PageTransition {} diff --git a/apps/automated/src/navigation/custom-transition.ios.ts b/apps/automated/src/navigation/custom-transition.ios.ts index e1143cd85..2191afb1e 100644 --- a/apps/automated/src/navigation/custom-transition.ios.ts +++ b/apps/automated/src/navigation/custom-transition.ios.ts @@ -1,20 +1,22 @@ -import * as transition from '@nativescript/core/ui/transition'; +import { PageTransition, SharedTransition, SharedTransitionHelper, Transition } from '@nativescript/core'; -export class CustomTransition extends transition.Transition { +export class CustomTransition extends Transition { constructor(duration: number, curve: any) { super(duration, curve); } - public animateIOSTransition(containerView: UIView, fromView: UIView, toView: UIView, operation: UINavigationControllerOperation, completion: (finished: boolean) => void): void { + animateIOSTransition(transitionContext: UIViewControllerContextTransitioning, fromViewCtrl: UIViewController, toViewCtrl: UIViewController, operation: UINavigationControllerOperation): void { + const toView = toViewCtrl.view; + const fromView = fromViewCtrl.view; toView.transform = CGAffineTransformMakeScale(0, 0); fromView.transform = CGAffineTransformIdentity; switch (operation) { case UINavigationControllerOperation.Push: - containerView.insertSubviewAboveSubview(toView, fromView); + transitionContext.containerView.insertSubviewAboveSubview(toView, fromView); break; case UINavigationControllerOperation.Pop: - containerView.insertSubviewBelowSubview(toView, fromView); + transitionContext.containerView.insertSubviewBelowSubview(toView, fromView); break; } @@ -27,7 +29,63 @@ export class CustomTransition extends transition.Transition { toView.transform = CGAffineTransformIdentity; fromView.transform = CGAffineTransformMakeScale(0, 0); }, - completion + (finished) => { + transitionContext.completeTransition(finished); + } ); } } + +export class CustomSharedElementPageTransition extends PageTransition { + transitionController: PageTransitionController; + interactiveController: UIPercentDrivenInteractiveTransition; + presented: UIViewController; + presenting: UIViewController; + navigationController: UINavigationController; + operation: number; + + iosNavigatedController(navigationController: UINavigationController, operation: number, fromVC: UIViewController, toVC: UIViewController): UIViewControllerAnimatedTransitioning { + this.navigationController = navigationController; + if (!this.transitionController) { + this.presented = toVC; + this.presenting = fromVC; + } + this.transitionController = PageTransitionController.initWithOwner(new WeakRef(this)); + // console.log('iosNavigatedController presenting:', this.presenting); + + this.operation = operation; + return this.transitionController; + } +} + +@NativeClass() +class PageTransitionController extends NSObject implements UIViewControllerAnimatedTransitioning { + static ObjCProtocols = [UIViewControllerAnimatedTransitioning]; + owner: WeakRef; + + static initWithOwner(owner: WeakRef) { + const ctrl = PageTransitionController.new(); + ctrl.owner = owner; + return ctrl; + } + + transitionDuration(transitionContext: UIViewControllerContextTransitioning): number { + const owner = this.owner.deref(); + if (owner) { + return owner.getDuration(); + } + return 0.35; + } + + animateTransition(transitionContext: UIViewControllerContextTransitioning): void { + const owner = this.owner.deref(); + if (owner) { + // console.log('--- PageTransitionController animateTransition'); + const state = SharedTransition.getState(owner.id); + if (!state) { + return; + } + SharedTransitionHelper.animate(state, transitionContext, 'page'); + } + } +} diff --git a/apps/automated/src/navigation/transition-tests.ts b/apps/automated/src/navigation/transition-tests.ts index abb722d38..d2bc895ad 100644 --- a/apps/automated/src/navigation/transition-tests.ts +++ b/apps/automated/src/navigation/transition-tests.ts @@ -1,10 +1,6 @@ import * as helper from '../ui-helper'; -import * as platform from '@nativescript/core/platform'; -import { Trace, CoreTypes } from '@nativescript/core'; -import { Color } from '@nativescript/core/color'; -import { NavigationEntry, NavigationTransition } from '@nativescript/core/ui/frame'; -import { Page } from '@nativescript/core/ui/page'; -import { CustomTransition } from './custom-transition'; +import { Color, Device, CoreTypes, Trace, SharedTransition, NavigationEntry, NavigationTransition, Page, platformNames, GridLayout } from '@nativescript/core'; +import { CustomTransition, CustomSharedElementPageTransition } from './custom-transition'; function _testTransition(navigationTransition: NavigationTransition) { var testId = `Transition[${JSON.stringify(navigationTransition)}]`; @@ -36,10 +32,10 @@ export function test_Transitions() { }); var transitions; - if (platform.Device.os === platform.platformNames.ios) { + if (Device.os === platformNames.ios) { transitions = ['curl']; } else { - const _sdkVersion = parseInt(platform.Device.sdkVersion); + const _sdkVersion = parseInt(Device.sdkVersion); transitions = _sdkVersion >= 21 ? ['explode'] : []; } @@ -55,3 +51,60 @@ export function test_Transitions() { // helper.navigateWithEntry({ create: mainPageFactory, clearHistory: true, animated: false }); } + +export function test_SharedElementTransitions() { + helper.navigate(() => { + const page = new Page(); + page.id = 'SharedelementTransitionsTestPage_MAIN'; + page.style.backgroundColor = new Color(255, Math.round(Math.random() * 255), Math.round(Math.random() * 255), Math.round(Math.random() * 255)); + const grid = new GridLayout(); + + const sharedElement = new GridLayout(); + sharedElement.width = { unit: 'dip', value: 200 }; + sharedElement.height = { unit: 'dip', value: 200 }; + sharedElement.marginTop = 20; + sharedElement.borderRadius = 20; + sharedElement.verticalAlignment = 'top'; + sharedElement.iosOverflowSafeArea = false; + sharedElement.backgroundColor = new Color('yellow'); + sharedElement.sharedTransitionTag = 'testing'; + grid.addChild(sharedElement); + + page.content = grid; + return page; + }); + + const navigationTransition = SharedTransition.custom(new CustomSharedElementPageTransition()); + + var testId = `SharedElementTransition[${JSON.stringify(navigationTransition)}]`; + // if (Trace.isEnabled()) { + // Trace.write(`Testing ${testId}`, Trace.categories.Test); + console.log(`Testing ${testId}`); + // } + var navigationEntry: NavigationEntry = { + create: function (): Page { + let page = new Page(); + page.id = testId; + page.style.backgroundColor = new Color(255, Math.round(Math.random() * 255), Math.round(Math.random() * 255), Math.round(Math.random() * 255)); + const grid = new GridLayout(); + + const sharedElement = new GridLayout(); + sharedElement.width = { unit: 'dip', value: 60 }; + sharedElement.height = { unit: 'dip', value: 60 }; + sharedElement.marginTop = 20; + sharedElement.borderRadius = 30; + sharedElement.verticalAlignment = 'top'; + sharedElement.iosOverflowSafeArea = false; + sharedElement.backgroundColor = new Color('purple'); + sharedElement.sharedTransitionTag = 'testing'; + grid.addChild(sharedElement); + + page.content = grid; + return page; + }, + animated: true, + transition: navigationTransition, + }; + + helper.navigateWithEntry(navigationEntry); +} diff --git a/packages/core/ui/index.ts b/packages/core/ui/index.ts index 6b724547a..afa3ebb73 100644 --- a/packages/core/ui/index.ts +++ b/packages/core/ui/index.ts @@ -80,6 +80,7 @@ export { PageTransition } from './transition/page-transition'; export { FadeTransition } from './transition/fade-transition'; export { SlideTransition } from './transition/slide-transition'; export { SharedTransition, SharedTransitionAnimationType } from './transition/shared-transition'; +export { SharedTransitionHelper } from './transition/shared-transition-helper'; export type { SharedTransitionConfig } from './transition/shared-transition'; export { WebView } from './web-view'; export type { LoadEventData, WebViewNavigationType } from './web-view'; diff --git a/packages/core/ui/transition/shared-transition-helper.android.ts b/packages/core/ui/transition/shared-transition-helper.android.ts index 8113f943b..42fd23987 100644 --- a/packages/core/ui/transition/shared-transition-helper.android.ts +++ b/packages/core/ui/transition/shared-transition-helper.android.ts @@ -1,8 +1,12 @@ -import { TransitionNavigationType } from '.'; +import type { TransitionInteractiveState, TransitionNavigationType } from '.'; import { SharedTransitionState } from './shared-transition'; export class SharedTransitionHelper { static animate(state: SharedTransitionState, transitionContext: any, type: TransitionNavigationType) { // may be able to consolidate android handling here in future } + static interactiveStart(state: SharedTransitionState, interactiveState: TransitionInteractiveState, type: TransitionNavigationType): void {} + static interactiveUpdate(state: SharedTransitionState, interactiveState: TransitionInteractiveState, type: TransitionNavigationType, percent: number): void {} + static interactiveCancel(state: SharedTransitionState, interactiveState: TransitionInteractiveState, type: TransitionNavigationType): void {} + static interactiveFinish(state: SharedTransitionState, interactiveState: TransitionInteractiveState, type: TransitionNavigationType): void {} } diff --git a/packages/core/ui/transition/shared-transition-helper.d.ts b/packages/core/ui/transition/shared-transition-helper.d.ts index c45122376..7c7a41539 100644 --- a/packages/core/ui/transition/shared-transition-helper.d.ts +++ b/packages/core/ui/transition/shared-transition-helper.d.ts @@ -1,10 +1,14 @@ -import type { SharedTransitionType, SharedInteractiveState } from '.'; +import type { TransitionInteractiveState, TransitionNavigationType } from '.'; import type { SharedTransitionState } from './shared-transition'; +/** + * Platform helper to aid in creating your own custom Shared Element Transition classes. + * (iOS Only) + */ export declare class SharedTransitionHelper { - static animate(state: SharedTransitionState, transitionContext: any /* iOS: UIViewControllerContextTransitioning */, type: SharedTransitionType): void; - static interactiveStart(state: SharedTransitionState, interactiveState: SharedInteractiveState, type: SharedTransitionType): void; - static interactiveUpdate(state: SharedTransitionState, interactiveState: SharedInteractiveState, type: SharedTransitionType, percent: number): void; - static interactiveCancel(state: SharedTransitionState, interactiveState: SharedInteractiveState, type: SharedTransitionType): void; - static interactiveFinish(state: SharedTransitionState, interactiveState: SharedInteractiveState, type: SharedTransitionType): void; + static animate(state: SharedTransitionState, transitionContext: any /* iOS: UIViewControllerContextTransitioning */, type: TransitionNavigationType): void; + static interactiveStart(state: SharedTransitionState, interactiveState: TransitionInteractiveState, type: TransitionNavigationType): void; + static interactiveUpdate(state: SharedTransitionState, interactiveState: TransitionInteractiveState, type: TransitionNavigationType, percent: number): void; + static interactiveCancel(state: SharedTransitionState, interactiveState: TransitionInteractiveState, type: TransitionNavigationType): void; + static interactiveFinish(state: SharedTransitionState, interactiveState: TransitionInteractiveState, type: TransitionNavigationType): void; }