mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-14 10:01:08 +08:00
feat(transitions): support zIndex on ios shared elements + support page props on android (#10261)
This commit is contained in:
@ -11,8 +11,8 @@
|
||||
"nativescript-theme-core": "file:../../node_modules/nativescript-theme-core"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nativescript/android": "~8.4.0",
|
||||
"@nativescript/ios": "~8.4.0",
|
||||
"@nativescript/android": "~8.5.0",
|
||||
"@nativescript/ios": "~8.5.0",
|
||||
"@nativescript/webpack": "file:../../dist/packages/nativescript-webpack.tgz",
|
||||
"typescript": "~4.9.5"
|
||||
},
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { PageTransition, SharedTransition, SharedTransitionHelper, Transition } from '@nativescript/core';
|
||||
import { PageTransition, SharedTransition, SharedTransitionAnimationType, SharedTransitionHelper, Transition, Utils } from '@nativescript/core';
|
||||
import { CORE_ANIMATION_DEFAULTS } from '@nativescript/core/utils';
|
||||
|
||||
export class CustomTransition extends Transition {
|
||||
constructor(duration: number, curve: any) {
|
||||
@ -72,9 +73,24 @@ class PageTransitionController extends NSObject implements UIViewControllerAnima
|
||||
transitionDuration(transitionContext: UIViewControllerContextTransitioning): number {
|
||||
const owner = this.owner.deref();
|
||||
if (owner) {
|
||||
return owner.getDuration();
|
||||
const state = SharedTransition.getState(owner.id);
|
||||
switch (state?.activeType) {
|
||||
case SharedTransitionAnimationType.present:
|
||||
if (Utils.isNumber(state?.pageEnd?.duration)) {
|
||||
return state.pageEnd?.duration / 1000;
|
||||
} else {
|
||||
return Utils.getDurationWithDampingFromSpring(state.pageEnd?.spring).duration;
|
||||
}
|
||||
|
||||
case SharedTransitionAnimationType.dismiss:
|
||||
if (Utils.isNumber(state?.pageReturn?.duration)) {
|
||||
return state.pageReturn?.duration / 1000;
|
||||
} else {
|
||||
return Utils.getDurationWithDampingFromSpring(state.pageReturn?.spring).duration;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0.35;
|
||||
return CORE_ANIMATION_DEFAULTS.duration;
|
||||
}
|
||||
|
||||
animateTransition(transitionContext: UIViewControllerContextTransitioning): void {
|
||||
|
@ -52,8 +52,8 @@ export function test_Transitions() {
|
||||
// helper.navigateWithEntry({ create: mainPageFactory, clearHistory: true, animated: false });
|
||||
}
|
||||
|
||||
export function test_SharedElementTransitions() {
|
||||
helper.navigate(() => {
|
||||
export function test_SharedElementTransitions(done) {
|
||||
const mainPage = 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));
|
||||
@ -76,12 +76,12 @@ export function test_SharedElementTransitions() {
|
||||
|
||||
const navigationTransition = SharedTransition.custom(new CustomSharedElementPageTransition());
|
||||
|
||||
var testId = `SharedElementTransition[${JSON.stringify(navigationTransition)}]`;
|
||||
const testId = `SharedElementTransition[${JSON.stringify(navigationTransition)}]`;
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`Testing ${testId}`, Trace.categories.Test);
|
||||
}
|
||||
var navigationEntry: NavigationEntry = {
|
||||
create: function (): Page {
|
||||
const navigationEntry: NavigationEntry = {
|
||||
create(): 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));
|
||||
@ -99,11 +99,16 @@ export function test_SharedElementTransitions() {
|
||||
grid.addChild(sharedElement);
|
||||
|
||||
page.content = grid;
|
||||
|
||||
page.once('navigatedTo', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
return page;
|
||||
},
|
||||
animated: true,
|
||||
transition: navigationTransition,
|
||||
};
|
||||
|
||||
helper.navigateWithEntry(navigationEntry);
|
||||
mainPage.frame.navigate(navigationEntry);
|
||||
}
|
||||
|
@ -12,8 +12,8 @@
|
||||
"@nativescript/imagepicker": "^1.0.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nativescript/android": "~8.4.0",
|
||||
"@nativescript/ios": "~8.4.0",
|
||||
"@nativescript/android": "~8.5.0",
|
||||
"@nativescript/ios": "~8.5.0",
|
||||
"@nativescript/webpack": "file:../../dist/packages/nativescript-webpack.tgz",
|
||||
"typescript": "~4.9.5"
|
||||
}
|
||||
|
@ -6,17 +6,17 @@
|
||||
|
||||
<GridLayout>
|
||||
<Button text="Close" tap="{{close}}" visibility="{{ isModal ? 'visible' : 'collapsed' }}" horizontalAlignment="left" verticalAlignment="top" marginTop="20" marginLeft="20" />
|
||||
<ContentView width="80%" height="300" borderRadius="20" backgroundColor="green" sharedTransitionTag="fab" horizontalAlignment="center" verticalAlignment="top" marginTop="100" visibility="{{ example1 ? 'visible' : 'collapsed' }}" />
|
||||
<ContentView width="80%" height="300" borderRadius="20" backgroundColor="green" sharedTransitionTag="fab" sharedTransitionIgnore="{{!example1}}" horizontalAlignment="center" verticalAlignment="top" marginTop="100" visibility="{{ example1 ? 'visible' : 'collapsed' }}" />
|
||||
|
||||
<GridLayout visibility="{{ example2 ? 'visible' : 'collapsed' }}">
|
||||
<ContentView width="200" height="200" borderRadius="100" backgroundColor="purple" sharedTransitionTag="shape1" verticalAlignment="top" horizontalAlignment="right" marginRight="20" marginTop="20" iosIgnoreSafeArea="true" />
|
||||
<ContentView width="80" height="80" borderRadius="40" backgroundColor="orange" sharedTransitionTag="shape2" verticalAlignment="top" horizontalAlignment="left" marginLeft="20" marginTop="20" iosIgnoreSafeArea="true" />
|
||||
<ContentView width="20" height="20" borderRadius="10" backgroundColor="brown" sharedTransitionTag="shape3" verticalAlignment="bottom" horizontalAlignment="right" marginRight="20" iosIgnoreSafeArea="true" />
|
||||
<ContentView width="150" height="150" borderRadius="75" backgroundColor="yellow" sharedTransitionTag="shape4" verticalAlignment="bottom" horizontalAlignment="left" marginLeft="20" iosIgnoreSafeArea="true" />
|
||||
<ContentView width="200" height="200" borderRadius="100" backgroundColor="purple" sharedTransitionTag="shape1" sharedTransitionIgnore="{{!example2}}" verticalAlignment="top" horizontalAlignment="right" marginRight="20" marginTop="20" iosIgnoreSafeArea="true" />
|
||||
<ContentView width="80" height="80" borderRadius="40" backgroundColor="orange" sharedTransitionTag="shape2" sharedTransitionIgnore="{{!example2}}" verticalAlignment="top" horizontalAlignment="left" marginLeft="20" marginTop="20" iosIgnoreSafeArea="true" />
|
||||
<ContentView width="20" height="20" borderRadius="10" backgroundColor="brown" sharedTransitionTag="shape3" sharedTransitionIgnore="{{!example2}}" verticalAlignment="bottom" horizontalAlignment="right" marginRight="20" iosIgnoreSafeArea="true" />
|
||||
<ContentView width="150" height="150" borderRadius="75" backgroundColor="yellow" sharedTransitionTag="shape4" sharedTransitionIgnore="{{!example2}}" verticalAlignment="bottom" horizontalAlignment="left" marginLeft="20" iosIgnoreSafeArea="true" />
|
||||
</GridLayout>
|
||||
|
||||
<GridLayout visibility="{{ example3 ? 'visible' : 'collapsed' }}">
|
||||
<ContentView width="80%" height="200" borderRadius="20" backgroundColor="purple" sharedTransitionTag="{{dynamicTag}}" verticalAlignment="top" horizontalAlignment="center" marginRight="20" marginTop="20" iosIgnoreSafeArea="true" />
|
||||
<ContentView width="80%" height="200" borderRadius="20" backgroundColor="purple" sharedTransitionTag="{{dynamicTag}}" sharedTransitionIgnore="{{!example3}}" verticalAlignment="top" horizontalAlignment="center" marginRight="20" marginTop="20" iosIgnoreSafeArea="true" />
|
||||
</GridLayout>
|
||||
</GridLayout>
|
||||
</Page>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Observable, EventData, Page, ShowModalOptions, SharedTransition, ModalTransition, PageTransition, FadeTransition, SlideTransition, PropertyChangeData } from '@nativescript/core';
|
||||
import { Observable, EventData, Page, ShowModalOptions, SharedTransition, ModalTransition, PageTransition, FadeTransition, SlideTransition, PropertyChangeData, ListView } from '@nativescript/core';
|
||||
let page: Page;
|
||||
// SharedTransition.DEBUG = true;
|
||||
export function navigatingTo(args: EventData) {
|
||||
@ -64,6 +64,24 @@ export class TransitionsModel extends Observable {
|
||||
finishThreshold: 0.5,
|
||||
},
|
||||
},
|
||||
pageEnd: {
|
||||
sharedTransitionTags: {
|
||||
shape1: {
|
||||
// purple should appear "below" other shapes during transition
|
||||
zIndex: 0,
|
||||
},
|
||||
shape2: {
|
||||
zIndex: 1,
|
||||
},
|
||||
shape3: {
|
||||
zIndex: 3,
|
||||
},
|
||||
shape4: {
|
||||
// yellow should appear "below" shape3
|
||||
zIndex: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
// pageEnd: {
|
||||
// duration: 3000
|
||||
// },
|
||||
|
@ -5,7 +5,7 @@
|
||||
</Page.actionBar>
|
||||
|
||||
<GridLayout rows="auto,*">
|
||||
<SegmentedBar sharedTransitionTag="segmentbar" horizontalAlignment="center" selectedIndex="{{ segmentSelectedIndex }}" marginTop="20">
|
||||
<SegmentedBar horizontalAlignment="center" selectedIndex="{{ segmentSelectedIndex }}" marginTop="20">
|
||||
<SegmentedBarItem title="Example A" />
|
||||
<SegmentedBarItem title="Example B" />
|
||||
<SegmentedBarItem title="Example C" />
|
||||
|
@ -11,8 +11,8 @@
|
||||
"@nativescript/core": "file:../../packages/core"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nativescript/android": "~8.4.0",
|
||||
"@nativescript/ios": "~8.4.0",
|
||||
"@nativescript/android": "~8.5.0",
|
||||
"@nativescript/ios": "~8.5.0",
|
||||
"@nativescript/webpack": "file:../../dist/packages/nativescript-webpack.tgz",
|
||||
"typescript": "~4.9.5"
|
||||
},
|
||||
|
22
package.json
22
package.json
@ -18,21 +18,21 @@
|
||||
"url": "https://github.com/NativeScript/NativeScript.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nrwl/nx-cloud": "15.1.1",
|
||||
"@nrwl/nx-cloud": "15.3.5",
|
||||
"nativescript-theme-core": "^1.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nativescript/hook": "^2.0.0",
|
||||
"@nativescript/nx": "~4.2.0",
|
||||
"@nrwl/cli": "15.8.3",
|
||||
"@nrwl/eslint-plugin-nx": "15.8.3",
|
||||
"@nrwl/jest": "15.8.3",
|
||||
"@nrwl/node": "15.8.3",
|
||||
"@nrwl/workspace": "15.8.3",
|
||||
"@nrwl/cli": "15.9.2",
|
||||
"@nrwl/eslint-plugin-nx": "15.9.2",
|
||||
"@nrwl/jest": "15.9.2",
|
||||
"@nrwl/node": "15.9.2",
|
||||
"@nrwl/workspace": "15.9.2",
|
||||
"@nstudio/focus": "^15.0.0",
|
||||
"@nstudio/nps-i": "~2.0.0",
|
||||
"@prettier/plugin-xml": "^2.2.0",
|
||||
"@types/jest": "29.4.0",
|
||||
"@types/jest": "~29.5.0",
|
||||
"@types/node": "18.7.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.30.0",
|
||||
"@typescript-eslint/parser": "^5.30.0",
|
||||
@ -46,12 +46,12 @@
|
||||
"eslint-config-prettier": "^8.1.0",
|
||||
"gonzales": "^1.0.7",
|
||||
"husky": "^8.0.1",
|
||||
"jest": "29.4.3",
|
||||
"jest": "~29.5.0",
|
||||
"lint-staged": "^13.1.0",
|
||||
"module-alias": "^2.2.2",
|
||||
"nativescript": "~8.4.0",
|
||||
"nativescript": "~8.5.0",
|
||||
"nativescript-typedoc-theme": "1.1.0",
|
||||
"nx": "15.8.3",
|
||||
"nx": "15.9.2",
|
||||
"parse-css": "git+https://github.com/tabatkins/parse-css.git",
|
||||
"parserlib": "^1.1.1",
|
||||
"prettier": "^2.6.2",
|
||||
@ -59,7 +59,7 @@
|
||||
"sass": "^1.45.2",
|
||||
"shady-css-parser": "^0.1.0",
|
||||
"tree-kill": "^1.2.2",
|
||||
"ts-jest": "29.0.5",
|
||||
"ts-jest": "29.1.0",
|
||||
"ts-node": "10.9.1",
|
||||
"ts-patch": "^2.1.0",
|
||||
"tslib": "^2.5.0",
|
||||
|
3
packages/core/index.d.ts
vendored
3
packages/core/index.d.ts
vendored
@ -107,7 +107,7 @@ export type { InstrumentationMode, TimerInfo } from './profiling';
|
||||
export { encoding } from './text';
|
||||
export * from './trace';
|
||||
export * from './ui';
|
||||
import { GC, isFontIconURI, isDataURI, isFileOrResourcePath, executeOnMainThread, mainThreadify, isMainThread, dispatchToMainThread, executeOnUIThread, releaseNativeObject, getModuleName, openFile, openUrl, isRealDevice, layout, ad as androidUtils, iOSNativeHelper as iosUtils, Source, escapeRegexSymbols, convertString, dismissSoftInput, dismissKeyboard, queueMacrotask, queueGC, throttle, debounce, dataSerialize, dataDeserialize, copyToClipboard, getFileExtension, isEmoji } from './utils';
|
||||
import { GC, isFontIconURI, isDataURI, isFileOrResourcePath, executeOnMainThread, mainThreadify, isMainThread, dispatchToMainThread, executeOnUIThread, releaseNativeObject, getModuleName, openFile, openUrl, isRealDevice, layout, ad as androidUtils, iOSNativeHelper as iosUtils, Source, escapeRegexSymbols, convertString, dismissSoftInput, dismissKeyboard, queueMacrotask, queueGC, throttle, debounce, dataSerialize, dataDeserialize, copyToClipboard, getFileExtension, isEmoji, getDurationWithDampingFromSpring } from './utils';
|
||||
import { SDK_VERSION } from './utils/constants';
|
||||
import { ClassInfo, getClass, getBaseClasses, getClassInfo, isBoolean, isDefined, isFunction, isNullOrUndefined, isNumber, isObject, isString, isUndefined, toUIString, verifyCallback, numberHasDecimals, numberIs64Bit } from './utils/types';
|
||||
export declare const Utils: {
|
||||
@ -166,5 +166,6 @@ export declare const Utils: {
|
||||
dismissKeyboard: typeof dismissKeyboard;
|
||||
copyToClipboard: typeof copyToClipboard;
|
||||
isEmoji: typeof isEmoji;
|
||||
getDurationWithDampingFromSpring: typeof getDurationWithDampingFromSpring;
|
||||
};
|
||||
export { XmlParser, ParserEventType, ParserEvent } from './xml';
|
||||
|
@ -139,7 +139,7 @@ export * from './trace';
|
||||
|
||||
export * from './ui';
|
||||
|
||||
import { GC, isFontIconURI, isDataURI, isFileOrResourcePath, executeOnMainThread, mainThreadify, isMainThread, dispatchToMainThread, executeOnUIThread, queueMacrotask, queueGC, debounce, throttle, releaseNativeObject, getModuleName, openFile, openUrl, isRealDevice, layout, ad as androidUtils, iOSNativeHelper as iosUtils, Source, RESOURCE_PREFIX, FILE_PREFIX, escapeRegexSymbols, convertString, dismissSoftInput, dismissKeyboard, dataDeserialize, dataSerialize, copyToClipboard, getFileExtension, isEmoji } from './utils';
|
||||
import { GC, isFontIconURI, isDataURI, isFileOrResourcePath, executeOnMainThread, mainThreadify, isMainThread, dispatchToMainThread, executeOnUIThread, queueMacrotask, queueGC, debounce, throttle, releaseNativeObject, getModuleName, openFile, openUrl, isRealDevice, layout, ad as androidUtils, iOSNativeHelper as iosUtils, Source, RESOURCE_PREFIX, FILE_PREFIX, escapeRegexSymbols, convertString, dismissSoftInput, dismissKeyboard, dataDeserialize, dataSerialize, copyToClipboard, getFileExtension, isEmoji, getDurationWithDampingFromSpring } from './utils';
|
||||
import { SDK_VERSION } from './utils/constants';
|
||||
import { ClassInfo, getClass, getBaseClasses, getClassInfo, isBoolean, isDefined, isFunction, isNullOrUndefined, isNumber, isObject, isString, isUndefined, toUIString, verifyCallback, numberHasDecimals, numberIs64Bit } from './utils/types';
|
||||
|
||||
@ -202,6 +202,7 @@ export const Utils = {
|
||||
dismissKeyboard,
|
||||
copyToClipboard,
|
||||
isEmoji,
|
||||
getDurationWithDampingFromSpring,
|
||||
};
|
||||
|
||||
export { XmlParser, ParserEventType, ParserEvent } from './xml';
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nativescript/core",
|
||||
"version": "8.5.0",
|
||||
"version": "8.5.1-rc.3",
|
||||
"description": "A JavaScript library providing an easy to use api for interacting with iOS and Android platform APIs.",
|
||||
"main": "index",
|
||||
"types": "index.d.ts",
|
||||
|
@ -7,7 +7,7 @@ import { getBindingOptions, bindingConstants } from '../binding-builder';
|
||||
import { profile } from '../../../profiling';
|
||||
import * as debugModule from '../../../utils/debug';
|
||||
import { Device } from '../../../platform';
|
||||
import { sanitizeModuleName } from '../module-name-sanitizer';
|
||||
import { sanitizeModuleName } from '../../../utils/common';
|
||||
import { resolveModuleName } from '../../../module-name-resolver';
|
||||
|
||||
export interface ComponentModule {
|
||||
|
@ -7,10 +7,10 @@ import { Page } from '../page';
|
||||
// Types.
|
||||
import { debug } from '../../utils/debug';
|
||||
import { isDefined } from '../../utils/types';
|
||||
import { sanitizeModuleName } from '../../utils/common';
|
||||
import { setPropertyValue, getComponentModule } from './component-builder';
|
||||
import type { ComponentModule } from './component-builder';
|
||||
import { platformNames } from '../../platform';
|
||||
import { sanitizeModuleName } from './module-name-sanitizer';
|
||||
import { resolveModuleName } from '../../module-name-resolver';
|
||||
import { xml2ui } from './xml2ui';
|
||||
|
||||
|
@ -1,6 +0,0 @@
|
||||
/**
|
||||
* Helps sanitize a module name if it is prefixed with '~/', '~' or '/'
|
||||
* @param moduleName the name
|
||||
* @param removeExtension whether to remove extension
|
||||
*/
|
||||
export function sanitizeModuleName(moduleName: string, removeExtension?: boolean): string;
|
@ -1,24 +0,0 @@
|
||||
/**
|
||||
* Helps sanitize a module name if it is prefixed with '~/', '~' or '/'
|
||||
* @param moduleName the name
|
||||
* @param removeExtension whether to remove extension
|
||||
*/
|
||||
export function sanitizeModuleName(moduleName: string, removeExtension = true): string {
|
||||
moduleName = moduleName.trim();
|
||||
|
||||
if (moduleName.startsWith('~/')) {
|
||||
moduleName = moduleName.substring(2);
|
||||
} else if (moduleName.startsWith('~')) {
|
||||
moduleName = moduleName.substring(1);
|
||||
} else if (moduleName.startsWith('/')) {
|
||||
moduleName = moduleName.substring(1);
|
||||
}
|
||||
|
||||
if (removeExtension) {
|
||||
const extToRemove = ['js', 'ts', 'xml', 'html', 'css', 'scss'];
|
||||
const extensionRegEx = new RegExp(`(.*)\\.(?:${extToRemove.join('|')})`, 'i');
|
||||
moduleName = moduleName.replace(extensionRegEx, '$1');
|
||||
}
|
||||
|
||||
return moduleName;
|
||||
}
|
@ -208,7 +208,7 @@ export function getViewByDomId(view: ViewBaseDefinition, domId: number): ViewBas
|
||||
*/
|
||||
export function querySelectorAll(view: ViewBaseDefinition, selector: string): Array<ViewBaseDefinition> {
|
||||
if (!view) {
|
||||
return undefined;
|
||||
return [];
|
||||
}
|
||||
|
||||
const retVal: Array<ViewBaseDefinition> = [];
|
||||
|
@ -5,6 +5,7 @@ import { booleanConverter, ShowModalOptions, ViewBase } from '../view-base';
|
||||
import { getEventOrGestureName } from '../bindable';
|
||||
import { layout } from '../../../utils';
|
||||
import { isObject } from '../../../utils/types';
|
||||
import { sanitizeModuleName } from '../../../utils/common';
|
||||
import { Color } from '../../../color';
|
||||
import { Property, InheritedProperty } from '../properties';
|
||||
import { EventData } from '../../../data/observable';
|
||||
@ -18,7 +19,6 @@ import { observe as gestureObserve, GesturesObserver, GestureTypes, GestureEvent
|
||||
|
||||
import { CSSUtils } from '../../../css/system-classes';
|
||||
import { Builder } from '../../builder';
|
||||
import { sanitizeModuleName } from '../../builder/module-name-sanitizer';
|
||||
import { StyleScope } from '../../styling/style-scope';
|
||||
import { LinearGradient } from '../../styling/linear-gradient';
|
||||
|
||||
|
@ -8,6 +8,7 @@ import { FlipTransition } from '../transition/flip-transition';
|
||||
import { _resolveAnimationCurve } from '../animation';
|
||||
import lazy from '../../utils/lazy';
|
||||
import { Trace } from '../../trace';
|
||||
import { SharedTransition, SharedTransitionEventData, SharedTransitionAnimationType } from '../transition/shared-transition';
|
||||
|
||||
interface TransitionListener {
|
||||
new (entry: ExpandedEntry, transition: androidx.transition.Transition): ExpandedTransitionListener;
|
||||
@ -326,6 +327,27 @@ export function _reverseTransitions(previousEntry: ExpandedEntry, currentEntry:
|
||||
return transitionUsed;
|
||||
}
|
||||
|
||||
function notifySharedTransition(id: number, eventName: string) {
|
||||
const state = SharedTransition.getState(id);
|
||||
if (!state) {
|
||||
return;
|
||||
}
|
||||
SharedTransition.notifyEvent(eventName, {
|
||||
id,
|
||||
type: 'page',
|
||||
action: state.activeType === SharedTransitionAnimationType.present ? 'present' : 'dismiss',
|
||||
});
|
||||
if (eventName === SharedTransition.finishedEvent) {
|
||||
if (state.activeType === SharedTransitionAnimationType.present) {
|
||||
SharedTransition.updateState(id, {
|
||||
activeType: SharedTransitionAnimationType.dismiss,
|
||||
});
|
||||
} else {
|
||||
SharedTransition.finishState(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Transition listener can't be static because
|
||||
// android is cloning transitions and we can't expand them :(
|
||||
function getTransitionListener(entry: ExpandedEntry, transition: androidx.transition.Transition): ExpandedTransitionListener {
|
||||
@ -347,6 +369,9 @@ function getTransitionListener(entry: ExpandedEntry, transition: androidx.transi
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`START ${toShortString(transition)} transition for ${entry.fragmentTag}`, Trace.categories.Transition);
|
||||
}
|
||||
if (entry?.transition) {
|
||||
notifySharedTransition(entry.transition?.id, SharedTransition.startedEvent);
|
||||
}
|
||||
}
|
||||
|
||||
onTransitionEnd(transition: androidx.transition.Transition): void {
|
||||
@ -356,6 +381,9 @@ function getTransitionListener(entry: ExpandedEntry, transition: androidx.transi
|
||||
}
|
||||
entry.isAnimationRunning = false;
|
||||
transitionOrAnimationCompleted(entry, this.backEntry);
|
||||
if (entry?.transition) {
|
||||
notifySharedTransition(entry.transition?.id, SharedTransition.finishedEvent);
|
||||
}
|
||||
}
|
||||
|
||||
onTransitionResume(transition: androidx.transition.Transition): void {
|
||||
|
@ -8,7 +8,7 @@ import { frameStack, topmost as frameStackTopmost, _pushInFrameStack, _popFromFr
|
||||
import { viewMatchesModuleContext } from '../core/view/view-common';
|
||||
import { getAncestor } from '../core/view-base';
|
||||
import { Builder } from '../builder';
|
||||
import { sanitizeModuleName } from '../builder/module-name-sanitizer';
|
||||
import { sanitizeModuleName } from '../../utils/common';
|
||||
import { profile } from '../../profiling';
|
||||
import { FRAME_SYMBOL } from './frame-helpers';
|
||||
import { SharedTransition } from '../transition/shared-transition';
|
||||
@ -401,8 +401,13 @@ export class FrameBase extends CustomLayoutView {
|
||||
this._onNavigatingTo(backstackEntry, isBackNavigation);
|
||||
const navigationTransition = this._getNavigationTransition(backstackEntry.entry);
|
||||
if (navigationTransition?.instance) {
|
||||
const state = SharedTransition.getState(navigationTransition?.instance.id);
|
||||
SharedTransition.updateState(navigationTransition?.instance.id, {
|
||||
page: this.currentPage,
|
||||
// Allow setting custom page context to override default (from) page
|
||||
// helpful for deeply nested frame navigation setups (eg: Nested Tab Navigation)
|
||||
// when sharing elements in this condition, the (from) page would
|
||||
// get overridden on each frame preventing shared element matching
|
||||
page: state?.page || this.currentPage,
|
||||
toPage: this,
|
||||
});
|
||||
}
|
||||
|
@ -468,12 +468,6 @@ export class Frame extends FrameBase {
|
||||
navigationTransition?.instance?.androidFragmentTransactionCallback?.(transaction, currentEntry, newEntry);
|
||||
|
||||
transaction.commitAllowingStateLoss();
|
||||
|
||||
if (navigationTransition?.instance) {
|
||||
SharedTransition.updateState(navigationTransition?.instance?.id, {
|
||||
activeType: SharedTransitionAnimationType.dismiss,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public _goBackCore(backstackEntry: BackstackEntry & ExpandedEntry) {
|
||||
@ -499,10 +493,6 @@ export class Frame extends FrameBase {
|
||||
backstackEntry.transition?.androidFragmentTransactionCallback?.(transaction, this._currentEntry, backstackEntry);
|
||||
|
||||
transaction.commitAllowingStateLoss();
|
||||
|
||||
if (backstackEntry?.transition) {
|
||||
SharedTransition.finishState(backstackEntry.transition.id);
|
||||
}
|
||||
}
|
||||
|
||||
public _removeEntry(removed: BackstackEntry): void {
|
||||
|
@ -5,7 +5,7 @@ import { Page } from '../page';
|
||||
import { View } from '../core/view';
|
||||
import { IOSHelper } from '../core/view/view-helper';
|
||||
import { profile } from '../../profiling';
|
||||
import { iOSNativeHelper, layout } from '../../utils';
|
||||
import { CORE_ANIMATION_DEFAULTS, iOSNativeHelper, layout } from '../../utils';
|
||||
import { Trace } from '../../trace';
|
||||
import type { PageTransition } from '../transition/page-transition';
|
||||
import { SlideTransition } from '../transition/slide-transition';
|
||||
@ -391,8 +391,6 @@ class TransitionDelegate extends NSObject {
|
||||
};
|
||||
}
|
||||
|
||||
const _defaultTransitionDuration = 0.35;
|
||||
|
||||
@NativeClass
|
||||
class UINavigationControllerAnimatedDelegate extends NSObject implements UINavigationControllerDelegate {
|
||||
public static ObjCProtocols = [UINavigationControllerDelegate];
|
||||
@ -499,7 +497,7 @@ class UINavigationControllerImpl extends UINavigationController {
|
||||
}
|
||||
|
||||
private animateWithDuration(navigationTransition: NavigationTransition, nativeTransition: UIViewAnimationTransition, transitionType: string, baseCallback: Function): void {
|
||||
const duration = navigationTransition.duration ? navigationTransition.duration / 1000 : _defaultTransitionDuration;
|
||||
const duration = navigationTransition.duration ? navigationTransition.duration / 1000 : CORE_ANIMATION_DEFAULTS.duration;
|
||||
const curve = _getNativeCurve(navigationTransition);
|
||||
|
||||
const transitionTraced = Trace.isCategorySet(Trace.categories.Transition);
|
||||
|
@ -5,7 +5,7 @@ export type { AnimationDefinition } from './animation';
|
||||
export { Builder } from './builder';
|
||||
export type { LoadOptions } from './builder';
|
||||
export type { ComponentModule } from './builder/component-builder';
|
||||
export { sanitizeModuleName } from './builder/module-name-sanitizer';
|
||||
export { sanitizeModuleName } from '../utils/common';
|
||||
export { Button } from './button';
|
||||
export { ContentView } from './content-view';
|
||||
export { Binding } from './core/bindable';
|
||||
@ -81,6 +81,6 @@ 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 type { SharedTransitionConfig, SharedTransitionTagProperties } from './transition/shared-transition';
|
||||
export { WebView } from './web-view';
|
||||
export type { LoadEventData, WebViewNavigationType } from './web-view';
|
||||
|
@ -19,7 +19,7 @@ function ensureKeyframeAnimationModule() {
|
||||
}
|
||||
|
||||
import * as capm from './css-animation-parser';
|
||||
import { sanitizeModuleName } from '../builder/module-name-sanitizer';
|
||||
import { sanitizeModuleName } from '../../utils/common';
|
||||
import { resolveModuleName } from '../../module-name-resolver';
|
||||
import { cleanupImportantFlags } from './css-utils';
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Transition } from '.';
|
||||
import { DEFAULT_DURATION } from './shared-transition';
|
||||
import { CORE_ANIMATION_DEFAULTS } from '../../utils/common';
|
||||
import { Transition } from '.';
|
||||
|
||||
export class FadeTransition extends Transition {
|
||||
transitionController: FadeTransitionController;
|
||||
@ -33,7 +33,7 @@ export class FadeTransitionController extends NSObject implements UIViewControll
|
||||
if (owner) {
|
||||
return owner.getDuration();
|
||||
}
|
||||
return DEFAULT_DURATION;
|
||||
return CORE_ANIMATION_DEFAULTS.duration;
|
||||
}
|
||||
|
||||
animateTransition(transitionContext: UIViewControllerContextTransitioning): void {
|
||||
|
16
packages/core/ui/transition/index.d.ts
vendored
16
packages/core/ui/transition/index.d.ts
vendored
@ -1,6 +1,16 @@
|
||||
import type { View } from '../core/view';
|
||||
import type { BackstackEntry } from '../frame';
|
||||
export type SharedElementSettings = { view: View; startFrame: any; endFrame?: any; startOpacity?: number; endOpacity?: number; scale?: { x?: number; y?: number }; startTransform?: any; snapshot?: any };
|
||||
export type SharedTransitionTagPropertiesToMatch = {
|
||||
/**
|
||||
* View related properties
|
||||
*/
|
||||
view?: Array<string>;
|
||||
/**
|
||||
* For iOS, can be specific if CALayer related properties
|
||||
*/
|
||||
layer?: Array<string>;
|
||||
};
|
||||
export type SharedElementSettings = { view: View; startFrame: any; endFrame?: any; startOpacity?: number; endOpacity?: number; scale?: { x?: number; y?: number }; zIndex?: number; startTransform?: any; snapshot?: any; propertiesToMatch?: SharedTransitionTagPropertiesToMatch };
|
||||
export type TransitionNavigationType = 'page' | 'modal';
|
||||
export interface TransitionInteractiveState {
|
||||
started?: false;
|
||||
@ -11,6 +21,10 @@ export interface TransitionInteractiveState {
|
||||
|
||||
export declare class Transition {
|
||||
id: number;
|
||||
/**
|
||||
* (Optional) Provide a unique name to identify this transition
|
||||
*/
|
||||
name?: string;
|
||||
transitionController?: any;
|
||||
interactiveController?: any;
|
||||
presented?: any;
|
||||
|
@ -1,4 +1,5 @@
|
||||
import type { Transition as TransitionType } from '.';
|
||||
import { CORE_ANIMATION_DEFAULTS } from '../../utils/common';
|
||||
import type { Transition as TransitionType } from '.';
|
||||
|
||||
let transitionId = 0;
|
||||
export class Transition implements TransitionType {
|
||||
@ -8,7 +9,7 @@ export class Transition implements TransitionType {
|
||||
private _curve: UIViewAnimationCurve;
|
||||
|
||||
constructor(duration: number = 350, nativeCurve: UIViewAnimationCurve = UIViewAnimationCurve.EaseInOut) {
|
||||
this._duration = duration ? duration / 1000 : 0.35;
|
||||
this._duration = duration ? duration / 1000 : CORE_ANIMATION_DEFAULTS.duration;
|
||||
this._curve = nativeCurve;
|
||||
transitionId++;
|
||||
this.id = transitionId;
|
||||
|
@ -1,7 +1,8 @@
|
||||
import type { View } from '../core/view';
|
||||
import { CORE_ANIMATION_DEFAULTS, getDurationWithDampingFromSpring } from '../../utils/common';
|
||||
import { isNumber } from '../../utils/types';
|
||||
import { Transition, SharedElementSettings, TransitionInteractiveState } from '.';
|
||||
import { SharedTransition, DEFAULT_DURATION } from './shared-transition';
|
||||
import { SharedTransition, SharedTransitionAnimationType } from './shared-transition';
|
||||
import { SharedTransitionHelper } from './shared-transition-helper';
|
||||
import { PanGestureEventData, GestureStateTypes } from '../gestures';
|
||||
|
||||
@ -162,7 +163,26 @@ class ModalTransitionController extends NSObject implements UIViewControllerAnim
|
||||
}
|
||||
|
||||
transitionDuration(transitionContext: UIViewControllerContextTransitioning): number {
|
||||
return DEFAULT_DURATION;
|
||||
const owner = this.owner.deref();
|
||||
if (owner) {
|
||||
const state = SharedTransition.getState(owner.id);
|
||||
switch (state?.activeType) {
|
||||
case SharedTransitionAnimationType.present:
|
||||
if (isNumber(state?.pageEnd?.duration)) {
|
||||
return state.pageEnd?.duration / 1000;
|
||||
} else {
|
||||
return getDurationWithDampingFromSpring(state.pageEnd?.spring).duration;
|
||||
}
|
||||
|
||||
case SharedTransitionAnimationType.dismiss:
|
||||
if (isNumber(state?.pageReturn?.duration)) {
|
||||
return state.pageReturn?.duration / 1000;
|
||||
} else {
|
||||
return getDurationWithDampingFromSpring(state.pageReturn?.spring).duration;
|
||||
}
|
||||
}
|
||||
}
|
||||
return CORE_ANIMATION_DEFAULTS.duration;
|
||||
}
|
||||
|
||||
animateTransition(transitionContext: UIViewControllerContextTransitioning): void {
|
||||
|
@ -3,11 +3,13 @@ import { ViewBase } from '../core/view-base';
|
||||
import { BackstackEntry } from '../frame';
|
||||
import { isNumber } from '../../utils/types';
|
||||
import { FadeTransition } from './fade-transition';
|
||||
import { SharedTransition, SharedTransitionAnimationType } from './shared-transition';
|
||||
import { Transition } from '.';
|
||||
import { getPageStartDefaultsForType, getRectFromProps, SharedTransition, SharedTransitionAnimationType, SharedTransitionEventData } from './shared-transition';
|
||||
import { ImageSource } from '../../image-source';
|
||||
import { ContentView } from '../content-view';
|
||||
import { GridLayout } from '../layouts/grid-layout';
|
||||
import { ad } from '../../utils';
|
||||
import { Screen } from '../../platform';
|
||||
// import { Image } from '../image';
|
||||
|
||||
@NativeClass
|
||||
@ -74,7 +76,7 @@ function setTransitionName(view: ViewBase) {
|
||||
}
|
||||
}
|
||||
|
||||
export class PageTransition extends FadeTransition {
|
||||
export class PageTransition extends Transition {
|
||||
constructor(duration?: number, curve?: any) {
|
||||
// disable custom curves until we can fix the issue with the animation not completing
|
||||
if (curve) {
|
||||
@ -86,6 +88,89 @@ export class PageTransition extends FadeTransition {
|
||||
super(duration);
|
||||
}
|
||||
|
||||
createAndroidAnimator(transitionType: string) {
|
||||
const state = SharedTransition.getState(this.id);
|
||||
const pageStart = state.pageStart;
|
||||
const startFrame = getRectFromProps(pageStart, {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: Screen.mainScreen.widthPixels,
|
||||
height: Screen.mainScreen.heightPixels,
|
||||
});
|
||||
const pageEnd = state.pageEnd;
|
||||
const endFrame = getRectFromProps(pageEnd);
|
||||
const pageReturn = state.pageReturn;
|
||||
const returnFrame = getRectFromProps(pageReturn);
|
||||
|
||||
let customDuration = -1;
|
||||
if (state.activeType === SharedTransitionAnimationType.present && isNumber(pageEnd?.duration)) {
|
||||
customDuration = pageEnd.duration;
|
||||
} else if (isNumber(state.pageReturn?.duration)) {
|
||||
customDuration = state.pageReturn.duration;
|
||||
}
|
||||
|
||||
const animationSet = new android.animation.AnimatorSet();
|
||||
animationSet.setDuration(customDuration > -1 ? customDuration : this.getDuration());
|
||||
|
||||
const alphaValues = Array.create('float', 2);
|
||||
const translationXValues = Array.create('float', 2);
|
||||
const translationYValues = Array.create('float', 2);
|
||||
switch (transitionType) {
|
||||
case Transition.AndroidTransitionType.enter:
|
||||
// incoming page (to)
|
||||
alphaValues[0] = isNumber(pageStart?.opacity) ? pageStart?.opacity : 0;
|
||||
alphaValues[1] = isNumber(pageEnd?.opacity) ? pageEnd?.opacity : 1;
|
||||
translationYValues[0] = startFrame.y;
|
||||
translationYValues[1] = endFrame.y;
|
||||
translationXValues[0] = startFrame.x;
|
||||
translationXValues[1] = endFrame.x;
|
||||
break;
|
||||
case Transition.AndroidTransitionType.exit:
|
||||
// current page (from)
|
||||
alphaValues[0] = 1;
|
||||
alphaValues[1] = 0;
|
||||
translationYValues[0] = 0;
|
||||
translationYValues[1] = 0;
|
||||
break;
|
||||
case Transition.AndroidTransitionType.popEnter:
|
||||
// current page (returning to)
|
||||
alphaValues[0] = 0;
|
||||
alphaValues[1] = 1;
|
||||
break;
|
||||
case Transition.AndroidTransitionType.popExit:
|
||||
// removing page (to)
|
||||
alphaValues[0] = isNumber(pageEnd?.opacity) ? pageEnd?.opacity : 1;
|
||||
alphaValues[1] = isNumber(pageStart?.opacity) ? pageStart?.opacity : 0;
|
||||
translationYValues[0] = endFrame.y;
|
||||
translationYValues[1] = startFrame.y;
|
||||
translationXValues[0] = endFrame.x;
|
||||
translationXValues[1] = startFrame.x;
|
||||
break;
|
||||
}
|
||||
const properties = {
|
||||
alpha: alphaValues,
|
||||
translationX: translationXValues,
|
||||
translationY: translationYValues,
|
||||
};
|
||||
|
||||
const animations = new java.util.HashSet<any>();
|
||||
for (const prop in properties) {
|
||||
// console.log(prop, ' ', properties[prop][1]);
|
||||
const animator = android.animation.ObjectAnimator.ofFloat(null, prop, properties[prop]);
|
||||
if (customDuration) {
|
||||
// duration always overrides default spring
|
||||
animator.setInterpolator(new CustomLinearInterpolator());
|
||||
} else {
|
||||
animator.setInterpolator(new CustomSpringInterpolator());
|
||||
}
|
||||
animations.add(animator);
|
||||
}
|
||||
|
||||
animationSet.playTogether(animations);
|
||||
|
||||
return animationSet;
|
||||
}
|
||||
|
||||
androidFragmentTransactionCallback(fragmentTransaction: androidx.fragment.app.FragmentTransaction, currentEntry: BackstackEntry, newEntry: BackstackEntry) {
|
||||
const fromPage = currentEntry.resolvedPage;
|
||||
const toPage = newEntry.resolvedPage;
|
||||
@ -151,9 +236,10 @@ export class PageTransition extends FadeTransition {
|
||||
}
|
||||
|
||||
const transitionSet = new androidx.transition.TransitionSet();
|
||||
transitionSet.setDuration(customDuration || this.getDuration());
|
||||
transitionSet.setDuration(customDuration > -1 ? customDuration : this.getDuration());
|
||||
transitionSet.addTransition(new androidx.transition.ChangeBounds());
|
||||
transitionSet.addTransition(new androidx.transition.ChangeTransform());
|
||||
transitionSet.setOrdering(androidx.transition.TransitionSet.ORDERING_TOGETHER);
|
||||
|
||||
if (customDuration) {
|
||||
// duration always overrides default spring
|
||||
|
@ -1,8 +1,9 @@
|
||||
import type { View } from '../core/view';
|
||||
import { SharedElementSettings, TransitionInteractiveState, Transition } from '.';
|
||||
import { isNumber } from '../../utils/types';
|
||||
import { CORE_ANIMATION_DEFAULTS, getDurationWithDampingFromSpring } from '../../utils/common';
|
||||
import { PanGestureEventData, GestureStateTypes } from '../gestures';
|
||||
import { SharedTransition, DEFAULT_DURATION } from './shared-transition';
|
||||
import { SharedTransition, SharedTransitionAnimationType } from './shared-transition';
|
||||
import { SharedTransitionHelper } from './shared-transition-helper';
|
||||
|
||||
export class PageTransition extends Transition {
|
||||
@ -67,10 +68,7 @@ export class PageTransition extends Transition {
|
||||
const state = SharedTransition.getState(this.id);
|
||||
if (!state) {
|
||||
// cleanup and exit, already shutdown
|
||||
if (this._interactiveGestureTeardown) {
|
||||
this._interactiveGestureTeardown();
|
||||
this._interactiveGestureTeardown = null;
|
||||
}
|
||||
this._teardownGesture();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -100,10 +98,7 @@ export class PageTransition extends Transition {
|
||||
if (this.interactiveController) {
|
||||
const finishThreshold = isNumber(state.interactive?.dismiss?.finishThreshold) ? state.interactive.dismiss.finishThreshold : 0.5;
|
||||
if (percent > finishThreshold) {
|
||||
if (this._interactiveGestureTeardown) {
|
||||
this._interactiveGestureTeardown();
|
||||
this._interactiveGestureTeardown = null;
|
||||
}
|
||||
this._teardownGesture();
|
||||
this.interactiveController.finishInteractiveTransition();
|
||||
} else {
|
||||
SharedTransition.updateState(this.id, {
|
||||
@ -116,6 +111,13 @@ export class PageTransition extends Transition {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _teardownGesture() {
|
||||
if (this._interactiveGestureTeardown) {
|
||||
this._interactiveGestureTeardown();
|
||||
this._interactiveGestureTeardown = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NativeClass()
|
||||
@ -185,9 +187,24 @@ class PageTransitionController extends NSObject implements UIViewControllerAnima
|
||||
transitionDuration(transitionContext: UIViewControllerContextTransitioning): number {
|
||||
const owner = this.owner.deref();
|
||||
if (owner) {
|
||||
return owner.getDuration();
|
||||
const state = SharedTransition.getState(owner.id);
|
||||
switch (state?.activeType) {
|
||||
case SharedTransitionAnimationType.present:
|
||||
if (isNumber(state?.pageEnd?.duration)) {
|
||||
return state.pageEnd?.duration / 1000;
|
||||
} else {
|
||||
return getDurationWithDampingFromSpring(state.pageEnd?.spring).duration;
|
||||
}
|
||||
|
||||
case SharedTransitionAnimationType.dismiss:
|
||||
if (isNumber(state?.pageReturn?.duration)) {
|
||||
return state.pageReturn?.duration / 1000;
|
||||
} else {
|
||||
return getDurationWithDampingFromSpring(state.pageReturn?.spring).duration;
|
||||
}
|
||||
}
|
||||
}
|
||||
return DEFAULT_DURATION;
|
||||
return CORE_ANIMATION_DEFAULTS.duration;
|
||||
}
|
||||
|
||||
animateTransition(transitionContext: UIViewControllerContextTransitioning): void {
|
||||
|
@ -1,7 +1,8 @@
|
||||
import type { TransitionInteractiveState, TransitionNavigationType } from '.';
|
||||
import { getPageStartDefaultsForType, getRectFromProps, getSpringFromProps, SharedTransition, SharedTransitionAnimationType, SharedTransitionEventData, SharedTransitionState } from './shared-transition';
|
||||
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 { iOSNativeHelper } from '../../utils/native-helper';
|
||||
|
||||
interface PlatformTransitionInteractiveState extends TransitionInteractiveState {
|
||||
@ -12,20 +13,17 @@ interface PlatformTransitionInteractiveState extends TransitionInteractiveState
|
||||
export class SharedTransitionHelper {
|
||||
static animate(state: SharedTransitionState, transitionContext: UIViewControllerContextTransitioning, type: TransitionNavigationType) {
|
||||
const transition = state.instance;
|
||||
setTimeout(() => {
|
||||
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.events().notify<SharedTransitionEventData>({
|
||||
eventName: SharedTransition.startedEvent,
|
||||
data: {
|
||||
id: transition.id,
|
||||
type,
|
||||
action: 'present',
|
||||
},
|
||||
SharedTransition.notifyEvent(SharedTransition.startedEvent, {
|
||||
id: transition.id,
|
||||
type,
|
||||
action: 'present',
|
||||
});
|
||||
|
||||
if (type === 'modal') {
|
||||
@ -36,6 +34,7 @@ export class SharedTransitionHelper {
|
||||
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: [],
|
||||
@ -46,10 +45,7 @@ export class SharedTransitionHelper {
|
||||
|
||||
if (SharedTransition.DEBUG) {
|
||||
console.log(` ${type}: Present`);
|
||||
console.log(
|
||||
`1. Found sharedTransitionTags to animate:`,
|
||||
sharedElements.map((v) => v.sharedTransitionTag)
|
||||
);
|
||||
console.log(`1. Found sharedTransitionTags to animate:`, sharedElementTags);
|
||||
|
||||
console.log(`2. Take snapshots of shared elements and position them based on presenting view:`);
|
||||
}
|
||||
@ -59,142 +55,182 @@ export class SharedTransitionHelper {
|
||||
const startFrame = getRectFromProps(pageStart, getPageStartDefaultsForType(type));
|
||||
|
||||
const pageEnd = state.pageEnd;
|
||||
const pageEndIndependentTags = Object.keys(pageEnd?.sharedTransitionTags || {});
|
||||
const pageEndTags = pageEnd?.sharedTransitionTags || {};
|
||||
// console.log('pageEndIndependentTags:', pageEndIndependentTags);
|
||||
|
||||
for (const presentingView of sharedElements) {
|
||||
const presentingSharedElement = presentingView.ios;
|
||||
// console.log('fromTarget instanceof UIImageView:', fromTarget instanceof UIImageView)
|
||||
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)
|
||||
// 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 presentedView = presented.find((v) => v.sharedTransitionTag === presentingView.sharedTransitionTag);
|
||||
const presentedSharedElement = presentedView.ios;
|
||||
const pageEndProps = pageEndTags[presentingView.sharedTransitionTag];
|
||||
|
||||
const snapshot = UIImageView.alloc().init();
|
||||
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 = iOSNativeHelper.snapshotView(presentedSharedElement, Screen.mainScreen.scale);
|
||||
snapshot.tintColor = presentedSharedElement.tintColor;
|
||||
});
|
||||
|
||||
// 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 = iOSNativeHelper.snapshotView(presentedSharedElement, Screen.mainScreen.scale);
|
||||
snapshot.tintColor = presentedSharedElement.tintColor;
|
||||
snapshot.contentMode = presentedSharedElement.contentMode;
|
||||
}
|
||||
|
||||
iOSNativeHelper.copyLayerProperties(snapshot, presentingSharedElement, pageEndProps?.propertiesToMatch);
|
||||
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:', iOSNativeHelper.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,
|
||||
});
|
||||
|
||||
snapshot.tintColor = presentedSharedElement.tintColor;
|
||||
snapshot.contentMode = presentedSharedElement.contentMode;
|
||||
// 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;
|
||||
}
|
||||
};
|
||||
|
||||
iOSNativeHelper.copyLayerProperties(snapshot, presentingSharedElement);
|
||||
snapshot.clipsToBounds = true;
|
||||
// console.log('---> snapshot: ', snapshot);
|
||||
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;
|
||||
|
||||
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:', iOSNativeHelper.printCGRect(snapshot.frame));
|
||||
if (pageEndProps?.callback) {
|
||||
await pageEndProps?.callback(independentView, 'present');
|
||||
}
|
||||
|
||||
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 = startFrame; //startFrameAdjusted;
|
||||
// }
|
||||
if (SharedTransition.DEBUG) {
|
||||
console.log('---> ', independentView.sharedTransitionTag, ' frame:', iOSNativeHelper.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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
transition.sharedElements.presenting.push({
|
||||
view: presentingView,
|
||||
startFrame,
|
||||
endFrame,
|
||||
snapshot,
|
||||
startOpacity: presentingView.opacity,
|
||||
endOpacity: presentedView.opacity,
|
||||
});
|
||||
transition.sharedElements.presented.push({
|
||||
view: presentedView,
|
||||
startFrame: endFrame,
|
||||
endFrame: startFrame,
|
||||
startOpacity: presentedView.opacity,
|
||||
endOpacity: presentingView.opacity,
|
||||
});
|
||||
|
||||
// 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;
|
||||
// add snapshot to animate
|
||||
transitionContext.containerView.addSubview(snapshot);
|
||||
// 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 tag of pageEndIndependentTags) {
|
||||
// only consider start when there's a matching end
|
||||
const pageStartIndependentProps = pageStart?.sharedTransitionTags ? pageStart?.sharedTransitionTags[tag] : null;
|
||||
// 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 = startFrame; //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));
|
||||
transition.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;
|
||||
for (const data of snapshotData) {
|
||||
// add snapshot to animate
|
||||
transitionContext.containerView.addSubview(snapshot);
|
||||
transitionContext.containerView.addSubview(data.snapshot);
|
||||
}
|
||||
|
||||
// Important: always set after above shared element positions have had their start positions set
|
||||
@ -221,13 +257,10 @@ export class SharedTransitionHelper {
|
||||
transition.presenting.view.removeFromSuperview();
|
||||
}
|
||||
transitionContext.completeTransition(true);
|
||||
SharedTransition.events().notify<SharedTransitionEventData>({
|
||||
eventName: SharedTransition.finishedEvent,
|
||||
data: {
|
||||
id: transition?.id,
|
||||
type,
|
||||
action: 'present',
|
||||
},
|
||||
SharedTransition.notifyEvent(SharedTransition.finishedEvent, {
|
||||
id: transition.id,
|
||||
type,
|
||||
action: 'present',
|
||||
});
|
||||
};
|
||||
|
||||
@ -256,7 +289,7 @@ export class SharedTransitionHelper {
|
||||
presentingMatch.snapshot.frame = correctedEndFrame;
|
||||
|
||||
// apply view and layer properties to the snapshot view to match the source/presented view
|
||||
iOSNativeHelper.copyLayerProperties(presentingMatch.snapshot, presented.view.ios);
|
||||
iOSNativeHelper.copyLayerProperties(presentingMatch.snapshot, presented.view.ios, presented.propertiesToMatch);
|
||||
// create a snapshot of the presented view
|
||||
presentingMatch.snapshot.image = iOSNativeHelper.snapshotView(presented.view.ios, Screen.mainScreen.scale);
|
||||
// apply correct alpha
|
||||
@ -287,8 +320,10 @@ export class SharedTransitionHelper {
|
||||
|
||||
if (isNumber(pageEnd?.duration)) {
|
||||
// override spring and use only linear animation
|
||||
UIView.animateWithDurationAnimationsCompletion(
|
||||
UIView.animateWithDurationDelayOptionsAnimationsCompletion(
|
||||
pageEnd?.duration / 1000,
|
||||
0,
|
||||
UIViewAnimationOptions.CurveEaseInOut,
|
||||
() => {
|
||||
animateProperties();
|
||||
},
|
||||
@ -312,13 +347,10 @@ export class SharedTransitionHelper {
|
||||
}
|
||||
case SharedTransitionAnimationType.dismiss: {
|
||||
// console.log('-- Transition dismiss --');
|
||||
SharedTransition.events().notify<SharedTransitionEventData>({
|
||||
eventName: SharedTransition.startedEvent,
|
||||
data: {
|
||||
id: transition?.id,
|
||||
type,
|
||||
action: 'dismiss',
|
||||
},
|
||||
SharedTransition.notifyEvent(SharedTransition.startedEvent, {
|
||||
id: transition.id,
|
||||
type,
|
||||
action: 'dismiss',
|
||||
});
|
||||
if (type === 'page') {
|
||||
transitionContext.containerView.insertSubviewBelowSubview(transition.presenting.view, transition.presented.view);
|
||||
@ -336,19 +368,65 @@ export class SharedTransitionHelper {
|
||||
console.log(`2. Add back previously stored sharedElements to dismiss:`);
|
||||
}
|
||||
|
||||
const pageEnd = state.pageEnd;
|
||||
const pageEndTags = pageEnd?.sharedTransitionTags || {};
|
||||
const pageReturn = state.pageReturn;
|
||||
|
||||
for (const p of transition.sharedElements.presented) {
|
||||
p.view.opacity = 0;
|
||||
}
|
||||
for (const p of transition.sharedElements.presenting) {
|
||||
p.snapshot.alpha = p.endOpacity;
|
||||
transitionContext.containerView.addSubview(p.snapshot);
|
||||
}
|
||||
for (const independent of transition.sharedElements.independent) {
|
||||
independent.snapshot.alpha = independent.endOpacity;
|
||||
transitionContext.containerView.addSubview(independent.snapshot);
|
||||
|
||||
// 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,
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const pageReturn = state.pageReturn;
|
||||
// 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 = iOSNativeHelper.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) {
|
||||
@ -362,13 +440,10 @@ export class SharedTransitionHelper {
|
||||
SharedTransition.finishState(transition.id);
|
||||
transition.sharedElements = null;
|
||||
transitionContext.completeTransition(true);
|
||||
SharedTransition.events().notify<SharedTransitionEventData>({
|
||||
eventName: SharedTransition.finishedEvent,
|
||||
data: {
|
||||
id: transition?.id,
|
||||
type,
|
||||
action: 'dismiss',
|
||||
},
|
||||
SharedTransition.notifyEvent(SharedTransition.finishedEvent, {
|
||||
id: transition.id,
|
||||
type,
|
||||
action: 'dismiss',
|
||||
});
|
||||
};
|
||||
|
||||
@ -383,12 +458,12 @@ export class SharedTransitionHelper {
|
||||
transition.presented.view.frame = CGRectMake(endFrame.x, endFrame.y, endFrame.width, endFrame.height);
|
||||
|
||||
for (const presenting of transition.sharedElements.presenting) {
|
||||
iOSNativeHelper.copyLayerProperties(presenting.snapshot, presenting.view.ios);
|
||||
iOSNativeHelper.copyLayerProperties(presenting.snapshot, presenting.view.ios, presenting.propertiesToMatch);
|
||||
presenting.snapshot.frame = presenting.startFrame;
|
||||
presenting.snapshot.alpha = presenting.startOpacity;
|
||||
|
||||
if (SharedTransition.DEBUG) {
|
||||
console.log(`---> ${presenting.view.sharedTransitionTag} animate to: `, iOSNativeHelper.printCGRect(presenting.startFrame));
|
||||
console.log(`---> ${presenting.view.sharedTransitionTag} animate to: `, iOSNativeHelper.printCGRect(presenting.snapshot.frame));
|
||||
}
|
||||
}
|
||||
|
||||
@ -401,15 +476,17 @@ export class SharedTransitionHelper {
|
||||
}
|
||||
|
||||
if (SharedTransition.DEBUG) {
|
||||
console.log(`---> ${independent.view.sharedTransitionTag} animate to: `, iOSNativeHelper.printCGRect(independent.startFrame));
|
||||
console.log(`---> ${independent.view.sharedTransitionTag} animate to: `, iOSNativeHelper.printCGRect(independent.snapshot.frame));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (isNumber(pageReturn?.duration)) {
|
||||
// override spring and use only linear animation
|
||||
UIView.animateWithDurationAnimationsCompletion(
|
||||
UIView.animateWithDurationDelayOptionsAnimationsCompletion(
|
||||
pageReturn?.duration / 1000,
|
||||
0,
|
||||
UIViewAnimationOptions.CurveEaseInOut,
|
||||
() => {
|
||||
animateProperties();
|
||||
},
|
||||
@ -436,13 +513,10 @@ export class SharedTransitionHelper {
|
||||
}
|
||||
|
||||
static interactiveStart(state: SharedTransitionState, interactiveState: PlatformTransitionInteractiveState, type: TransitionNavigationType) {
|
||||
SharedTransition.events().notify<SharedTransitionEventData>({
|
||||
eventName: SharedTransition.startedEvent,
|
||||
data: {
|
||||
id: state?.instance?.id,
|
||||
type,
|
||||
action: 'interactiveStart',
|
||||
},
|
||||
SharedTransition.notifyEvent(SharedTransition.startedEvent, {
|
||||
id: state.instance.id,
|
||||
type,
|
||||
action: 'interactiveStart',
|
||||
});
|
||||
switch (type) {
|
||||
case 'page':
|
||||
@ -468,7 +542,7 @@ export class SharedTransitionHelper {
|
||||
interactiveState.propertyAnimator = UIViewPropertyAnimator.alloc().initWithDurationDampingRatioAnimations(1, 1, () => {
|
||||
for (const p of state.instance.sharedElements.presenting) {
|
||||
p.snapshot.frame = p.startFrame;
|
||||
iOSNativeHelper.copyLayerProperties(p.snapshot, p.view.ios);
|
||||
iOSNativeHelper.copyLayerProperties(p.snapshot, p.view.ios, p.propertiesToMatch);
|
||||
|
||||
p.snapshot.alpha = 1;
|
||||
}
|
||||
@ -477,20 +551,17 @@ export class SharedTransitionHelper {
|
||||
});
|
||||
}
|
||||
interactiveState.propertyAnimator.fractionComplete = percent;
|
||||
SharedTransition.events().notify<SharedTransitionEventData>({
|
||||
eventName: SharedTransition.interactiveUpdateEvent,
|
||||
data: {
|
||||
id: state?.instance?.id,
|
||||
type,
|
||||
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 : 0.35;
|
||||
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) {
|
||||
@ -504,12 +575,13 @@ export class SharedTransitionHelper {
|
||||
interactiveState.added = false;
|
||||
interactiveState.transitionContext.cancelInteractiveTransition();
|
||||
interactiveState.transitionContext.completeTransition(false);
|
||||
SharedTransition.events().notify<SharedTransitionEventData>({
|
||||
eventName: SharedTransition.interactiveCancelledEvent,
|
||||
data: {
|
||||
id: state?.instance?.id,
|
||||
type,
|
||||
},
|
||||
SharedTransition.updateState(state?.instance?.id, {
|
||||
interactiveBegan: false,
|
||||
interactiveCancelled: true,
|
||||
});
|
||||
SharedTransition.notifyEvent(SharedTransition.interactiveCancelledEvent, {
|
||||
id: state?.instance?.id,
|
||||
type,
|
||||
});
|
||||
}, duration * 1000);
|
||||
}
|
||||
@ -519,7 +591,7 @@ export class SharedTransitionHelper {
|
||||
if (state?.instance && interactiveState?.added && interactiveState?.propertyAnimator) {
|
||||
interactiveState.propertyAnimator.reversed = false;
|
||||
|
||||
const duration = isNumber(state.pageReturn?.duration) ? state.pageReturn?.duration / 1000 : 0.35;
|
||||
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) {
|
||||
@ -532,13 +604,10 @@ export class SharedTransitionHelper {
|
||||
interactiveState.added = false;
|
||||
interactiveState.transitionContext.finishInteractiveTransition();
|
||||
interactiveState.transitionContext.completeTransition(true);
|
||||
SharedTransition.events().notify<SharedTransitionEventData>({
|
||||
eventName: SharedTransition.finishedEvent,
|
||||
data: {
|
||||
id: state?.instance?.id,
|
||||
type,
|
||||
action: 'interactiveFinish',
|
||||
},
|
||||
SharedTransition.notifyEvent(SharedTransition.finishedEvent, {
|
||||
id: state?.instance?.id,
|
||||
type,
|
||||
action: 'interactiveFinish',
|
||||
});
|
||||
}, duration * 1000);
|
||||
}
|
||||
|
@ -1,28 +1,48 @@
|
||||
import type { Transition, TransitionNavigationType } from '.';
|
||||
import type { Transition, TransitionNavigationType, SharedTransitionTagPropertiesToMatch } from '.';
|
||||
import { Observable } from '../../data/observable';
|
||||
import { Screen } from '../../platform';
|
||||
import { isNumber } from '../../utils/types';
|
||||
import { CORE_ANIMATION_DEFAULTS } from '../../utils/common';
|
||||
import { querySelectorAll, ViewBase } from '../core/view-base';
|
||||
import type { View } from '../core/view';
|
||||
import type { PanGestureEventData } from '../gestures';
|
||||
|
||||
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 {
|
||||
present,
|
||||
dismiss,
|
||||
}
|
||||
type SharedTransitionEventAction = 'present' | 'dismiss' | 'interactiveStart' | 'interactiveFinish';
|
||||
export type SharedTransitionEventData = { eventName: string; data: { id: number; type: TransitionNavigationType; action?: SharedTransitionEventAction; percent?: number } };
|
||||
export type SharedTransitionEventDataPayload = { id: number; type: TransitionNavigationType; action?: SharedTransitionEventAction; percent?: number };
|
||||
export type SharedTransitionEventData = { eventName: string; data: SharedTransitionEventDataPayload };
|
||||
export type SharedRect = { x?: number; y?: number; width?: number; height?: number };
|
||||
export type SharedProperties = SharedRect & { opacity?: number; scale?: { x?: number; y?: number } };
|
||||
export type SharedProperties = SharedRect & {
|
||||
opacity?: number;
|
||||
scale?: { x?: number; y?: number };
|
||||
};
|
||||
/**
|
||||
* Properties which can be set on individual Shared Elements
|
||||
*/
|
||||
export type SharedTransitionTagProperties = SharedProperties & {
|
||||
/**
|
||||
* The visual stacking order where 0 is at the bottom.
|
||||
* Shared elements are stacked one on top of the other during each transition.
|
||||
* By default they are not ordered in any particular fashion.
|
||||
*/
|
||||
zIndex?: number;
|
||||
/**
|
||||
* Collection of properties to match and animate on each shared element.
|
||||
*
|
||||
* Defaults to: 'backgroundColor', 'cornerRadius', 'borderWidth', 'borderColor'
|
||||
*
|
||||
* Tip: Using an empty array, [], for view or layer will avoid copying any properties if desired.
|
||||
*/
|
||||
propertiesToMatch?: SharedTransitionTagPropertiesToMatch;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
callback?: (view: View, action: SharedTransitionEventAction) => Promise<void>;
|
||||
};
|
||||
export type SharedSpringProperties = {
|
||||
tension?: number;
|
||||
friction?: number;
|
||||
@ -36,7 +56,7 @@ type SharedTransitionPageProperties = SharedProperties & {
|
||||
* (iOS Only) Allow "independent" elements found only on one of the screens to take part in the animation.
|
||||
* Note: This feature will be brought to Android in a future release.
|
||||
*/
|
||||
sharedTransitionTags?: { [key: string]: SharedProperties };
|
||||
sharedTransitionTags?: { [key: string]: SharedTransitionTagProperties };
|
||||
/**
|
||||
* Spring animation settings.
|
||||
* Defaults to 140 tension with 10 friction.
|
||||
@ -87,7 +107,14 @@ export interface SharedTransitionConfig {
|
||||
/**
|
||||
* View settings to return to the original page with.
|
||||
*/
|
||||
pageReturn?: SharedTransitionPageWithDurationProperties;
|
||||
pageReturn?: SharedTransitionPageWithDurationProperties & {
|
||||
/**
|
||||
* In some cases you may want the returning animation to start with the original opacity,
|
||||
* instead of beginning where it ended up on pageEnd.
|
||||
* Note: you can try enabling this property in cases where your return animation doesn't appear correct.
|
||||
*/
|
||||
useStartOpacity?: boolean;
|
||||
};
|
||||
}
|
||||
export interface SharedTransitionState extends SharedTransitionConfig {
|
||||
/**
|
||||
@ -137,10 +164,18 @@ export class SharedTransition {
|
||||
});
|
||||
const pageEnd = options?.pageEnd;
|
||||
if (isNumber(pageEnd?.duration)) {
|
||||
transition.setDuration(pageEnd?.duration);
|
||||
// Android uses milliseconds/iOS uses seconds
|
||||
// users pass in milliseconds
|
||||
transition.setDuration(global.isIOS ? pageEnd?.duration / 1000 : pageEnd?.duration);
|
||||
}
|
||||
return { instance: transition };
|
||||
}
|
||||
/**
|
||||
* Whether a transition is in progress or not.
|
||||
* Note: used internally however exposed in case custom state ordering is needed.
|
||||
* Updated when transitions start/end/cancel.
|
||||
*/
|
||||
static inProgress: boolean;
|
||||
/**
|
||||
* Listen to various shared element transition events.
|
||||
* @returns Observable
|
||||
@ -168,6 +203,29 @@ export class SharedTransition {
|
||||
*/
|
||||
static interactiveUpdateEvent = 'SharedTransitionInteractiveUpdateEvent';
|
||||
|
||||
/**
|
||||
* Notify a Shared Transition event.
|
||||
* @param id transition instance id
|
||||
* @param eventName Shared Transition event name
|
||||
* @param type TransitionNavigationType
|
||||
* @param action SharedTransitionEventAction
|
||||
*/
|
||||
static notifyEvent(eventName: string, data: SharedTransitionEventDataPayload) {
|
||||
switch (eventName) {
|
||||
case SharedTransition.startedEvent:
|
||||
case SharedTransition.interactiveUpdateEvent:
|
||||
SharedTransition.inProgress = true;
|
||||
break;
|
||||
default:
|
||||
SharedTransition.inProgress = false;
|
||||
break;
|
||||
}
|
||||
SharedTransition.events().notify<SharedTransitionEventData>({
|
||||
eventName,
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable to see various console logging output of Shared Element Transition behavior.
|
||||
*/
|
||||
@ -224,11 +282,11 @@ export class SharedTransition {
|
||||
presenting: Array<View>;
|
||||
} {
|
||||
// 1. Presented view: gather all sharedTransitionTag views
|
||||
const presentedSharedElements = <Array<View>>querySelectorAll(toPage, 'sharedTransitionTag').filter((v) => !v.sharedTransitionIgnore);
|
||||
const presentedSharedElements = <Array<View>>querySelectorAll(toPage, 'sharedTransitionTag').filter((v) => !v.sharedTransitionIgnore && typeof v.sharedTransitionTag === 'string');
|
||||
// console.log('presented sharedTransitionTag total:', presentedSharedElements.length);
|
||||
|
||||
// 2. Presenting view: gather all sharedTransitionTag views
|
||||
const presentingSharedElements = <Array<View>>querySelectorAll(fromPage, 'sharedTransitionTag').filter((v) => !v.sharedTransitionIgnore);
|
||||
const presentingSharedElements = <Array<View>>querySelectorAll(fromPage, 'sharedTransitionTag').filter((v) => !v.sharedTransitionIgnore && typeof v.sharedTransitionTag === 'string');
|
||||
// console.log(
|
||||
// 'presenting sharedTransitionTags:',
|
||||
// presentingSharedElements.map((v) => v.sharedTransitionTag)
|
||||
@ -254,8 +312,8 @@ export function getRectFromProps(props: SharedTransitionPageProperties, defaults
|
||||
defaults = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: Screen.mainScreen.widthDIPs,
|
||||
height: Screen.mainScreen.heightDIPs,
|
||||
width: getPlatformWidth(),
|
||||
height: getPlatformHeight(),
|
||||
...(defaults || {}),
|
||||
};
|
||||
return {
|
||||
@ -273,11 +331,11 @@ export function getRectFromProps(props: SharedTransitionPageProperties, defaults
|
||||
*/
|
||||
export function getSpringFromProps(props: SharedSpringProperties) {
|
||||
return {
|
||||
tension: isNumber(props?.tension) ? props?.tension : DEFAULT_SPRING.tension,
|
||||
friction: isNumber(props?.friction) ? props?.friction : DEFAULT_SPRING.friction,
|
||||
mass: isNumber(props?.mass) ? props?.mass : DEFAULT_SPRING.mass,
|
||||
velocity: isNumber(props?.velocity) ? props?.velocity : DEFAULT_SPRING.velocity,
|
||||
delay: isNumber(props?.delay) ? props?.delay : DEFAULT_SPRING.delay,
|
||||
tension: isNumber(props?.tension) ? props?.tension : CORE_ANIMATION_DEFAULTS.spring.tension,
|
||||
friction: isNumber(props?.friction) ? props?.friction : CORE_ANIMATION_DEFAULTS.spring.friction,
|
||||
mass: isNumber(props?.mass) ? props?.mass : CORE_ANIMATION_DEFAULTS.spring.mass,
|
||||
velocity: isNumber(props?.velocity) ? props?.velocity : CORE_ANIMATION_DEFAULTS.spring.velocity,
|
||||
delay: isNumber(props?.delay) ? props?.delay : 0,
|
||||
};
|
||||
}
|
||||
|
||||
@ -288,9 +346,17 @@ export function getSpringFromProps(props: SharedSpringProperties) {
|
||||
*/
|
||||
export function getPageStartDefaultsForType(type: TransitionNavigationType) {
|
||||
return {
|
||||
x: type === 'page' ? Screen.mainScreen.widthDIPs : 0,
|
||||
y: type === 'page' ? 0 : Screen.mainScreen.heightDIPs,
|
||||
width: Screen.mainScreen.widthDIPs,
|
||||
height: Screen.mainScreen.heightDIPs,
|
||||
x: type === 'page' ? getPlatformWidth() : 0,
|
||||
y: type === 'page' ? 0 : getPlatformHeight(),
|
||||
width: getPlatformWidth(),
|
||||
height: getPlatformHeight(),
|
||||
};
|
||||
}
|
||||
|
||||
function getPlatformWidth() {
|
||||
return global.isAndroid ? Screen.mainScreen.widthPixels : Screen.mainScreen.widthDIPs;
|
||||
}
|
||||
|
||||
function getPlatformHeight() {
|
||||
return global.isAndroid ? Screen.mainScreen.heightPixels : Screen.mainScreen.heightDIPs;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Transition } from '.';
|
||||
import { Screen } from '../../platform';
|
||||
import { DEFAULT_DURATION } from './shared-transition';
|
||||
import { CORE_ANIMATION_DEFAULTS } from '../../utils/common';
|
||||
|
||||
export class SlideTransition extends Transition {
|
||||
transitionController: SlideTransitionController;
|
||||
@ -40,7 +40,7 @@ export class SlideTransitionController extends NSObject implements UIViewControl
|
||||
if (owner) {
|
||||
return owner.getDuration();
|
||||
}
|
||||
return DEFAULT_DURATION;
|
||||
return CORE_ANIMATION_DEFAULTS.duration;
|
||||
}
|
||||
|
||||
animateTransition(transitionContext: UIViewControllerContextTransitioning): void {
|
||||
|
@ -1,6 +1,5 @@
|
||||
import * as types from './types';
|
||||
import { dispatchToMainThread, dispatchToUIThread, isMainThread } from './mainthread-helper';
|
||||
import { sanitizeModuleName } from '../ui/builder/module-name-sanitizer';
|
||||
import emojiRegex from 'emoji-regex';
|
||||
|
||||
import { GC } from './index';
|
||||
@ -43,6 +42,31 @@ export function getModuleName(path: string): string {
|
||||
return sanitizeModuleName(moduleName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helps sanitize a module name if it is prefixed with '~/', '~' or '/'
|
||||
* @param moduleName the name
|
||||
* @param removeExtension whether to remove extension
|
||||
*/
|
||||
export function sanitizeModuleName(moduleName: string, removeExtension = true): string {
|
||||
moduleName = moduleName.trim();
|
||||
|
||||
if (moduleName.startsWith('~/')) {
|
||||
moduleName = moduleName.substring(2);
|
||||
} else if (moduleName.startsWith('~')) {
|
||||
moduleName = moduleName.substring(1);
|
||||
} else if (moduleName.startsWith('/')) {
|
||||
moduleName = moduleName.substring(1);
|
||||
}
|
||||
|
||||
if (removeExtension) {
|
||||
const extToRemove = ['js', 'ts', 'xml', 'html', 'css', 'scss'];
|
||||
const extensionRegEx = new RegExp(`(.*)\\.(?:${extToRemove.join('|')})`, 'i');
|
||||
moduleName = moduleName.replace(extensionRegEx, '$1');
|
||||
}
|
||||
|
||||
return moduleName;
|
||||
}
|
||||
|
||||
export function isFileOrResourcePath(path: string): boolean {
|
||||
if (!types.isString(path)) {
|
||||
return false;
|
||||
@ -210,3 +234,55 @@ export function isEmoji(value: string): boolean {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Unicode_Property_Escapes
|
||||
return emojiRegex().test(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Default animation values used throughout core
|
||||
*/
|
||||
export const CORE_ANIMATION_DEFAULTS = {
|
||||
duration: 0.35,
|
||||
spring: {
|
||||
tension: 140,
|
||||
friction: 10,
|
||||
mass: 1,
|
||||
velocity: 0,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a duration with damping value from various spring related settings.
|
||||
* Helpful when needing to convert spring settings to isolated duration value.
|
||||
* @param springSettings various spring settings
|
||||
* @returns calculated duration with damping from spring settings
|
||||
*/
|
||||
export function getDurationWithDampingFromSpring(springSettings?: { tension?: number; friction?: number; mass?: number; velocity?: number }) {
|
||||
// for convenience, default spring settings are provided
|
||||
const opt = {
|
||||
...CORE_ANIMATION_DEFAULTS.spring,
|
||||
...(springSettings || {}),
|
||||
};
|
||||
const damping = opt.friction / Math.sqrt(2 * opt.tension);
|
||||
const undampedFrequency = Math.sqrt(opt.tension / opt.mass);
|
||||
|
||||
// console.log({
|
||||
// damping,
|
||||
// undampedFrequency
|
||||
// })
|
||||
|
||||
const epsilon = 0.001;
|
||||
let duration = 0;
|
||||
|
||||
if (damping < 1) {
|
||||
// console.log('damping < 1');
|
||||
const a = Math.sqrt(1 - Math.pow(damping, 2));
|
||||
const b = opt.velocity / (a * undampedFrequency);
|
||||
const c = damping / a;
|
||||
const d = -((b - c) / epsilon);
|
||||
if (d > 0) {
|
||||
duration = Math.log(d) / (damping * undampedFrequency);
|
||||
}
|
||||
}
|
||||
return {
|
||||
duration,
|
||||
damping,
|
||||
};
|
||||
}
|
||||
|
3
packages/core/utils/native-helper.d.ts
vendored
3
packages/core/utils/native-helper.d.ts
vendored
@ -250,8 +250,9 @@ export namespace iOSNativeHelper {
|
||||
* Copy layer properties from one view to another.
|
||||
* @param view a view to copy layer properties to
|
||||
* @param toView a view to copy later properties from
|
||||
* @param (optional) custom properties to copy between both views
|
||||
*/
|
||||
export function copyLayerProperties(view: UIView, toView: UIView): void;
|
||||
export function copyLayerProperties(view: UIView, toView: UIView, customProperties?: { view?: Array<string> /* Array<keyof UIView> */; layer?: Array<string> /* Array<keyof CALayer> */ }): void;
|
||||
|
||||
/**
|
||||
* Animate views with a configurable spring effect
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Color } from '../color';
|
||||
import { Trace } from '../trace';
|
||||
import { CORE_ANIMATION_DEFAULTS, getDurationWithDampingFromSpring } from './common';
|
||||
import { getClass, isNullOrUndefined, numberHasDecimals, numberIs64Bit } from './types';
|
||||
|
||||
declare let UIImagePickerControllerSourceType: any;
|
||||
@ -374,9 +375,9 @@ export namespace iOSNativeHelper {
|
||||
return image;
|
||||
}
|
||||
|
||||
export function copyLayerProperties(view: UIView, toView: UIView) {
|
||||
const viewPropertiesToMatch: Array<keyof UIView> = ['backgroundColor'];
|
||||
const layerPropertiesToMatch: Array<keyof CALayer> = ['cornerRadius', 'borderWidth', 'borderColor'];
|
||||
export function copyLayerProperties(view: UIView, toView: UIView, customProperties?: { view?: Array<keyof UIView>; layer?: Array<keyof CALayer> }) {
|
||||
const viewPropertiesToMatch: Array<keyof UIView> = customProperties?.view || ['backgroundColor'];
|
||||
const layerPropertiesToMatch: Array<keyof CALayer> = customProperties?.layer || ['cornerRadius', 'borderWidth', 'borderColor'];
|
||||
|
||||
viewPropertiesToMatch.forEach((property) => {
|
||||
if (view[property] !== toView[property]) {
|
||||
@ -394,40 +395,16 @@ export namespace iOSNativeHelper {
|
||||
}
|
||||
|
||||
export function animateWithSpring(options?: { tension?: number; friction?: number; mass?: number; delay?: number; velocity?: number; animateOptions?: UIViewAnimationOptions; animations?: () => void; completion?: (finished?: boolean) => void }) {
|
||||
// for convenience, default spring settings are provided
|
||||
const opt = {
|
||||
tension: 140,
|
||||
friction: 10,
|
||||
mass: 1.0,
|
||||
...CORE_ANIMATION_DEFAULTS.spring,
|
||||
delay: 0,
|
||||
velocity: 0,
|
||||
animateOptions: null,
|
||||
animations: null,
|
||||
completion: null,
|
||||
...(options || {}),
|
||||
};
|
||||
|
||||
// console.log('createSpringAnimator', opt);
|
||||
const damping = opt.friction / Math.sqrt(2 * opt.tension);
|
||||
const undampedFrequency = Math.sqrt(opt.tension / opt.mass);
|
||||
|
||||
// console.log({
|
||||
// damping,
|
||||
// undampedFrequency
|
||||
// })
|
||||
|
||||
const epsilon = 0.001;
|
||||
let duration = 0;
|
||||
|
||||
if (damping < 1) {
|
||||
// console.log('damping < 1');
|
||||
const a = Math.sqrt(1 - Math.pow(damping, 2));
|
||||
const b = opt.velocity / (a * undampedFrequency);
|
||||
const c = damping / a;
|
||||
const d = -((b - c) / epsilon);
|
||||
if (d > 0) {
|
||||
duration = Math.log(d) / (damping * undampedFrequency);
|
||||
}
|
||||
}
|
||||
const { duration, damping } = getDurationWithDampingFromSpring(opt);
|
||||
|
||||
if (duration === 0) {
|
||||
UIView.animateWithDurationAnimationsCompletion(0, opt.animations, opt.completion);
|
||||
|
10
tools/assets/App_Resources/iOS/Podfile
Normal file
10
tools/assets/App_Resources/iOS/Podfile
Normal file
@ -0,0 +1,10 @@
|
||||
platform :ios, '13.0'
|
||||
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
target.build_configurations.each do |config|
|
||||
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'
|
||||
config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO'
|
||||
end
|
||||
end
|
||||
end
|
@ -4,4 +4,4 @@
|
||||
// To build for device with Xcode 8 you need to specify your development team. More info: https://developer.apple.com/library/prerelease/content/releasenotes/DeveloperTools/RN-Xcode/Introduction.html
|
||||
// DEVELOPMENT_TEAM = YOUR_TEAM_ID;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
|
Reference in New Issue
Block a user