mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-11-05 13:26:48 +08:00
feat: Scoped Packages (#7911)
* chore: move tns-core-modules to nativescript-core * chore: preparing compat generate script * chore: add missing definitions * chore: no need for http-request to be private * chore: packages chore * test: generate tests for tns-core-modules * chore: add anroid module for consistency * chore: add .npmignore * chore: added privateModulesWhitelist * chore(webpack): added bundle-entry-points * chore: scripts * chore: tests changed to use @ns/core * test: add scoped-packages test project * test: fix types * test: update test project * chore: build scripts * chore: update build script * chore: npm scripts cleanup * chore: make the compat pgk work with old wp config * test: generate diff friendly tests * chore: create barrel exports * chore: move files after rebase * chore: typedoc config * chore: compat mode * chore: review of barrels * chore: remove tns-core-modules import after rebase * chore: dev workflow setup * chore: update developer-workflow * docs: experiment with API extractor * chore: api-extractor and barrel exports * chore: api-extractor configs * chore: generate d.ts rollup with api-extractor * refactor: move methods inside Frame * chore: fic tests to use Frame static methods * refactor: create Builder class * refactor: use Builder class in tests * refactor: include Style in ui barrel * chore: separate compat build script * chore: fix tslint errors * chore: update NATIVESCRIPT_CORE_ARGS * chore: fix compat pack * chore: fix ui-test-app build with linked modules * chore: Application, ApplicationSettings, Connectivity and Http * chore: export Trace, Profiling and Utils * refactor: Static create methods for ImageSource * chore: fix deprecated usages of ImageSource * chore: move Span and FormattedString to ui * chore: add events-args and ImageSource to index files * chore: check for CLI >= 6.2 when building for IOS * chore: update travis build * chore: copy Pod file to compat package * chore: update error msg ui-tests-app * refactor: Apply suggestions from code review Co-Authored-By: Martin Yankov <m.i.yankov@gmail.com> * chore: typings and refs * chore: add missing d.ts files for public API * chore: adress code review FB * chore: update api-report * chore: dev-workflow for other apps * chore: api update * chore: update api-report
This commit is contained in:
committed by
GitHub
parent
6c7139477e
commit
cc97a16800
32
nativescript-core/ui/frame/Readme.md
Normal file
32
nativescript-core/ui/frame/Readme.md
Normal file
@@ -0,0 +1,32 @@
|
||||
Use the frame in the following way:
|
||||
|
||||
### To navigate to the starting page of the application
|
||||
```js
|
||||
// put this in the bootstrap.js
|
||||
var app = require("application");
|
||||
var frameModule = require("ui/frame");
|
||||
|
||||
app.onLaunch = function(context) {
|
||||
var frame = new frameModule.Frame();
|
||||
frame.navigate("testPage");
|
||||
}
|
||||
|
||||
// or use the mainModule property of the application module
|
||||
// in this a Frame instance is internally created and used to navigate to the main page module
|
||||
app.mainModule = "testPage";
|
||||
```
|
||||
|
||||
### To navigate to a new Page
|
||||
```js
|
||||
// take the frame from an existing (and navigatedTo) Page instance
|
||||
var frame = page.frame;
|
||||
frame.navigate("newPage");
|
||||
```
|
||||
|
||||
### To navigate to a new Activity (Android)
|
||||
```js
|
||||
// create a new Frame instance
|
||||
var frameModule = require("ui/frame");
|
||||
var frame = new frameModule.Frame();
|
||||
frame.navigate("newPage");
|
||||
```
|
||||
68
nativescript-core/ui/frame/activity.android.ts
Normal file
68
nativescript-core/ui/frame/activity.android.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { setActivityCallbacks, AndroidActivityCallbacks } from "./frame";
|
||||
import * as globals from "../../globals";
|
||||
import * as appModule from "../../application";
|
||||
|
||||
if (global.__snapshot) {
|
||||
globals.install();
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
@JavaProxy("com.tns.NativeScriptActivity")
|
||||
class NativeScriptActivity extends androidx.appcompat.app.AppCompatActivity {
|
||||
private _callbacks: AndroidActivityCallbacks;
|
||||
public isNativeScriptActivity;
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
return global.__native(this);
|
||||
}
|
||||
|
||||
public onCreate(savedInstanceState: android.os.Bundle): void {
|
||||
appModule.android.init(this.getApplication());
|
||||
|
||||
// Set isNativeScriptActivity in onCreate.
|
||||
// The JS constructor might not be called because the activity is created from Android.
|
||||
this.isNativeScriptActivity = true;
|
||||
if (!this._callbacks) {
|
||||
setActivityCallbacks(this);
|
||||
}
|
||||
|
||||
this._callbacks.onCreate(this, savedInstanceState, this.getIntent(), super.onCreate);
|
||||
}
|
||||
|
||||
public onNewIntent(intent: android.content.Intent): void {
|
||||
this._callbacks.onNewIntent(this, intent, super.setIntent, super.onNewIntent);
|
||||
}
|
||||
|
||||
public onSaveInstanceState(outState: android.os.Bundle): void {
|
||||
this._callbacks.onSaveInstanceState(this, outState, super.onSaveInstanceState);
|
||||
}
|
||||
|
||||
public onStart(): void {
|
||||
this._callbacks.onStart(this, super.onStart);
|
||||
}
|
||||
|
||||
public onStop(): void {
|
||||
this._callbacks.onStop(this, super.onStop);
|
||||
}
|
||||
|
||||
public onDestroy(): void {
|
||||
this._callbacks.onDestroy(this, super.onDestroy);
|
||||
}
|
||||
|
||||
public onPostResume(): void {
|
||||
this._callbacks.onPostResume(this, super.onPostResume);
|
||||
}
|
||||
|
||||
public onBackPressed(): void {
|
||||
this._callbacks.onBackPressed(this, super.onBackPressed);
|
||||
}
|
||||
|
||||
public onRequestPermissionsResult(requestCode: number, permissions: Array<string>, grantResults: Array<number>): void {
|
||||
this._callbacks.onRequestPermissionsResult(this, requestCode, permissions, grantResults, undefined /*TODO: Enable if needed*/);
|
||||
}
|
||||
|
||||
public onActivityResult(requestCode: number, resultCode: number, data: android.content.Intent): void {
|
||||
this._callbacks.onActivityResult(this, requestCode, resultCode, data, super.onActivityResult);
|
||||
}
|
||||
}
|
||||
67
nativescript-core/ui/frame/fragment.android.ts
Normal file
67
nativescript-core/ui/frame/fragment.android.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { AndroidFragmentCallbacks, setFragmentCallbacks, setFragmentClass } from "./frame";
|
||||
|
||||
@JavaProxy("com.tns.FragmentClass")
|
||||
class FragmentClass extends org.nativescript.widgets.FragmentBase {
|
||||
// This field is updated in the frame module upon `new` (although hacky this eases the Fragment->callbacks association a lot)
|
||||
private _callbacks: AndroidFragmentCallbacks;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
return global.__native(this);
|
||||
}
|
||||
|
||||
public onHiddenChanged(hidden: boolean): void {
|
||||
this._callbacks.onHiddenChanged(this, hidden, super.onHiddenChanged);
|
||||
}
|
||||
|
||||
public onCreateAnimator(transit: number, enter: boolean, nextAnim: number): android.animation.Animator {
|
||||
return this._callbacks.onCreateAnimator(this, transit, enter, nextAnim, super.onCreateAnimator);
|
||||
}
|
||||
|
||||
public onStop(): void {
|
||||
this._callbacks.onStop(this, super.onStop);
|
||||
}
|
||||
|
||||
public onPause(): void {
|
||||
this._callbacks.onPause(this, super.onStop);
|
||||
}
|
||||
|
||||
public onCreate(savedInstanceState: android.os.Bundle) {
|
||||
if (!this._callbacks) {
|
||||
setFragmentCallbacks(this);
|
||||
}
|
||||
|
||||
this.setHasOptionsMenu(true);
|
||||
this._callbacks.onCreate(this, savedInstanceState, super.onCreate);
|
||||
}
|
||||
|
||||
public onCreateView(inflater: android.view.LayoutInflater, container: android.view.ViewGroup, savedInstanceState: android.os.Bundle) {
|
||||
let result = this._callbacks.onCreateView(this, inflater, container, savedInstanceState, super.onCreateView);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public onSaveInstanceState(outState: android.os.Bundle) {
|
||||
this._callbacks.onSaveInstanceState(this, outState, super.onSaveInstanceState);
|
||||
}
|
||||
|
||||
public onDestroyView() {
|
||||
this._callbacks.onDestroyView(this, super.onDestroyView);
|
||||
}
|
||||
|
||||
public onDestroy() {
|
||||
this._callbacks.onDestroy(this, super.onDestroy);
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
const callbacks = this._callbacks;
|
||||
if (callbacks) {
|
||||
return callbacks.toStringOverride(this, super.toString);
|
||||
} else {
|
||||
super.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setFragmentClass(FragmentClass);
|
||||
728
nativescript-core/ui/frame/fragment.transitions.android.ts
Normal file
728
nativescript-core/ui/frame/fragment.transitions.android.ts
Normal file
@@ -0,0 +1,728 @@
|
||||
/// <reference path="transition-definitions.android.d.ts"/>
|
||||
|
||||
// Definitions.
|
||||
import { NavigationType } from "./frame-common";
|
||||
import { NavigationTransition, BackstackEntry } from "../frame";
|
||||
|
||||
// Types.
|
||||
import { Transition, AndroidTransitionType } from "../transition/transition";
|
||||
import { FlipTransition } from "../transition/flip-transition";
|
||||
import { _resolveAnimationCurve } from "../animation";
|
||||
import lazy from "../../utils/lazy";
|
||||
import { isEnabled as traceEnabled, write as traceWrite, categories as traceCategories } from "../../trace";
|
||||
|
||||
interface TransitionListener {
|
||||
new(entry: ExpandedEntry, transition: androidx.transition.Transition): ExpandedTransitionListener;
|
||||
}
|
||||
|
||||
const defaultInterpolator = lazy(() => new android.view.animation.AccelerateDecelerateInterpolator());
|
||||
|
||||
export const waitingQueue = new Map<number, Set<ExpandedEntry>>();
|
||||
export const completedEntries = new Map<number, ExpandedEntry>();
|
||||
|
||||
let TransitionListener: TransitionListener;
|
||||
let AnimationListener: android.animation.Animator.AnimatorListener;
|
||||
|
||||
interface ExpandedTransitionListener extends androidx.transition.Transition.TransitionListener {
|
||||
entry: ExpandedEntry;
|
||||
transition: androidx.transition.Transition;
|
||||
}
|
||||
|
||||
interface ExpandedAnimator extends android.animation.Animator {
|
||||
entry: ExpandedEntry;
|
||||
transitionType?: string;
|
||||
}
|
||||
|
||||
interface ExpandedEntry extends BackstackEntry {
|
||||
|
||||
enterTransitionListener: ExpandedTransitionListener;
|
||||
exitTransitionListener: ExpandedTransitionListener;
|
||||
reenterTransitionListener: ExpandedTransitionListener;
|
||||
returnTransitionListener: ExpandedTransitionListener;
|
||||
|
||||
enterAnimator: ExpandedAnimator;
|
||||
exitAnimator: ExpandedAnimator;
|
||||
popEnterAnimator: ExpandedAnimator;
|
||||
popExitAnimator: ExpandedAnimator;
|
||||
|
||||
transition: Transition;
|
||||
transitionName: string;
|
||||
frameId: number;
|
||||
|
||||
isNestedDefaultTransition: boolean;
|
||||
}
|
||||
|
||||
export function _setAndroidFragmentTransitions(
|
||||
animated: boolean,
|
||||
navigationTransition: NavigationTransition,
|
||||
currentEntry: ExpandedEntry,
|
||||
newEntry: ExpandedEntry,
|
||||
frameId: number,
|
||||
fragmentTransaction: any,
|
||||
isNestedDefaultTransition?: boolean): void {
|
||||
|
||||
const currentFragment: androidx.fragment.app.Fragment = currentEntry ? currentEntry.fragment : null;
|
||||
const newFragment: androidx.fragment.app.Fragment = newEntry.fragment;
|
||||
const entries = waitingQueue.get(frameId);
|
||||
if (entries && entries.size > 0) {
|
||||
throw new Error("Calling navigation before previous navigation finish.");
|
||||
}
|
||||
|
||||
allowTransitionOverlap(currentFragment);
|
||||
allowTransitionOverlap(newFragment);
|
||||
|
||||
let name = "";
|
||||
let transition: Transition;
|
||||
|
||||
if (navigationTransition) {
|
||||
transition = navigationTransition.instance;
|
||||
name = navigationTransition.name ? navigationTransition.name.toLowerCase() : "";
|
||||
}
|
||||
|
||||
if (!animated) {
|
||||
name = "none";
|
||||
} else if (transition) {
|
||||
name = "custom";
|
||||
} else if (name.indexOf("slide") !== 0 && name !== "fade" && name.indexOf("flip") !== 0 && name.indexOf("explode") !== 0) {
|
||||
// If we are given name that doesn't match any of ours - fallback to default.
|
||||
name = "default";
|
||||
}
|
||||
|
||||
let currentFragmentNeedsDifferentAnimation = false;
|
||||
if (currentEntry) {
|
||||
_updateTransitions(currentEntry);
|
||||
if (currentEntry.transitionName !== name ||
|
||||
currentEntry.transition !== transition || isNestedDefaultTransition) {
|
||||
clearExitAndReenterTransitions(currentEntry, true);
|
||||
currentFragmentNeedsDifferentAnimation = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (name === "none") {
|
||||
const noTransition = new NoTransition(0, null);
|
||||
|
||||
// Setup empty/immediate animator when transitioning to nested frame for first time.
|
||||
// Also setup empty/immediate transition to be executed when navigating back to this page.
|
||||
// TODO: Consider removing empty/immediate animator when migrating to official androidx.fragment.app.Fragment:1.2.
|
||||
if (isNestedDefaultTransition) {
|
||||
fragmentTransaction.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out);
|
||||
setupAllAnimation(newEntry, noTransition);
|
||||
setupNewFragmentCustomTransition({ duration: 0, curve: null }, newEntry, noTransition);
|
||||
} else {
|
||||
setupNewFragmentCustomTransition({ duration: 0, curve: null }, newEntry, noTransition);
|
||||
}
|
||||
|
||||
newEntry.isNestedDefaultTransition = isNestedDefaultTransition;
|
||||
|
||||
if (currentFragmentNeedsDifferentAnimation) {
|
||||
setupCurrentFragmentCustomTransition({ duration: 0, curve: null }, currentEntry, noTransition);
|
||||
}
|
||||
} else if (name === "custom") {
|
||||
setupNewFragmentCustomTransition({ duration: transition.getDuration(), curve: transition.getCurve() }, newEntry, transition);
|
||||
if (currentFragmentNeedsDifferentAnimation) {
|
||||
setupCurrentFragmentCustomTransition({ duration: transition.getDuration(), curve: transition.getCurve() }, currentEntry, transition);
|
||||
}
|
||||
} else if (name === "default") {
|
||||
setupNewFragmentFadeTransition({ duration: 150, curve: null }, newEntry);
|
||||
if (currentFragmentNeedsDifferentAnimation) {
|
||||
setupCurrentFragmentFadeTransition({ duration: 150, curve: null }, currentEntry);
|
||||
}
|
||||
} else if (name.indexOf("slide") === 0) {
|
||||
setupNewFragmentSlideTransition(navigationTransition, newEntry, name);
|
||||
if (currentFragmentNeedsDifferentAnimation) {
|
||||
setupCurrentFragmentSlideTransition(navigationTransition, currentEntry, name);
|
||||
}
|
||||
} else if (name === "fade") {
|
||||
setupNewFragmentFadeTransition(navigationTransition, newEntry);
|
||||
if (currentFragmentNeedsDifferentAnimation) {
|
||||
setupCurrentFragmentFadeTransition(navigationTransition, currentEntry);
|
||||
}
|
||||
} else if (name === "explode") {
|
||||
setupNewFragmentExplodeTransition(navigationTransition, newEntry);
|
||||
if (currentFragmentNeedsDifferentAnimation) {
|
||||
setupCurrentFragmentExplodeTransition(navigationTransition, currentEntry);
|
||||
}
|
||||
} else if (name === "flip") {
|
||||
const direction = name.substr("flip".length) || "right"; //Extract the direction from the string
|
||||
const flipTransition = new FlipTransition(direction, navigationTransition.duration, navigationTransition.curve);
|
||||
|
||||
setupNewFragmentCustomTransition(navigationTransition, newEntry, flipTransition);
|
||||
if (currentFragmentNeedsDifferentAnimation) {
|
||||
setupCurrentFragmentCustomTransition(navigationTransition, currentEntry, flipTransition);
|
||||
}
|
||||
}
|
||||
|
||||
newEntry.transitionName = name;
|
||||
|
||||
if (currentEntry) {
|
||||
currentEntry.transitionName = name;
|
||||
if (name === "custom") {
|
||||
currentEntry.transition = transition;
|
||||
}
|
||||
}
|
||||
|
||||
printTransitions(currentEntry);
|
||||
printTransitions(newEntry);
|
||||
}
|
||||
|
||||
function setupAllAnimation(entry: ExpandedEntry, transition: Transition): void {
|
||||
setupExitAndPopEnterAnimation(entry, transition);
|
||||
const listener = getAnimationListener();
|
||||
|
||||
// setupAllAnimation is called only for new fragments so we don't
|
||||
// need to clearAnimationListener for enter & popExit animators.
|
||||
const enterAnimator = <ExpandedAnimator>transition.createAndroidAnimator(AndroidTransitionType.enter);
|
||||
enterAnimator.transitionType = AndroidTransitionType.enter;
|
||||
enterAnimator.entry = entry;
|
||||
enterAnimator.addListener(listener);
|
||||
entry.enterAnimator = enterAnimator;
|
||||
|
||||
const popExitAnimator = <ExpandedAnimator>transition.createAndroidAnimator(AndroidTransitionType.popExit);
|
||||
popExitAnimator.transitionType = AndroidTransitionType.popExit;
|
||||
popExitAnimator.entry = entry;
|
||||
popExitAnimator.addListener(listener);
|
||||
entry.popExitAnimator = popExitAnimator;
|
||||
}
|
||||
|
||||
function setupExitAndPopEnterAnimation(entry: ExpandedEntry, transition: Transition): void {
|
||||
const listener = getAnimationListener();
|
||||
|
||||
// remove previous listener if we are changing the animator.
|
||||
clearAnimationListener(entry.exitAnimator, listener);
|
||||
clearAnimationListener(entry.popEnterAnimator, listener);
|
||||
|
||||
const exitAnimator = <ExpandedAnimator>transition.createAndroidAnimator(AndroidTransitionType.exit);
|
||||
exitAnimator.transitionType = AndroidTransitionType.exit;
|
||||
exitAnimator.entry = entry;
|
||||
exitAnimator.addListener(listener);
|
||||
entry.exitAnimator = exitAnimator;
|
||||
|
||||
const popEnterAnimator = <ExpandedAnimator>transition.createAndroidAnimator(AndroidTransitionType.popEnter);
|
||||
popEnterAnimator.transitionType = AndroidTransitionType.popEnter;
|
||||
popEnterAnimator.entry = entry;
|
||||
popEnterAnimator.addListener(listener);
|
||||
entry.popEnterAnimator = popEnterAnimator;
|
||||
}
|
||||
|
||||
function getAnimationListener(): android.animation.Animator.AnimatorListener {
|
||||
if (!AnimationListener) {
|
||||
@Interfaces([android.animation.Animator.AnimatorListener])
|
||||
class AnimationListenerImpl extends java.lang.Object implements android.animation.Animator.AnimatorListener {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
return global.__native(this);
|
||||
}
|
||||
|
||||
onAnimationStart(animator: ExpandedAnimator): void {
|
||||
const entry = animator.entry;
|
||||
addToWaitingQueue(entry);
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`START ${animator.transitionType} for ${entry.fragmentTag}`, traceCategories.Transition);
|
||||
}
|
||||
}
|
||||
|
||||
onAnimationRepeat(animator: ExpandedAnimator): void {
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`REPEAT ${animator.transitionType} for ${animator.entry.fragmentTag}`, traceCategories.Transition);
|
||||
}
|
||||
}
|
||||
|
||||
onAnimationEnd(animator: ExpandedAnimator): void {
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`END ${animator.transitionType} for ${animator.entry.fragmentTag}`, traceCategories.Transition);
|
||||
}
|
||||
transitionOrAnimationCompleted(animator.entry);
|
||||
}
|
||||
|
||||
onAnimationCancel(animator: ExpandedAnimator): void {
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`CANCEL ${animator.transitionType} for ${animator.entry.fragmentTag}`, traceCategories.Transition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AnimationListener = new AnimationListenerImpl();
|
||||
}
|
||||
|
||||
return AnimationListener;
|
||||
}
|
||||
|
||||
function clearAnimationListener(animator: ExpandedAnimator, listener: android.animation.Animator.AnimatorListener): void {
|
||||
if (!animator) {
|
||||
return;
|
||||
}
|
||||
|
||||
animator.removeListener(listener);
|
||||
|
||||
if (animator.entry && traceEnabled()) {
|
||||
const entry = animator.entry;
|
||||
traceWrite(`Clear ${animator.transitionType} - ${entry.transition} for ${entry.fragmentTag}`, traceCategories.Transition);
|
||||
}
|
||||
|
||||
animator.entry = null;
|
||||
}
|
||||
|
||||
export function _getAnimatedEntries(frameId: number): Set<BackstackEntry> {
|
||||
return waitingQueue.get(frameId);
|
||||
}
|
||||
|
||||
export function _updateTransitions(entry: ExpandedEntry): void {
|
||||
const fragment = entry.fragment;
|
||||
const enterTransitionListener = entry.enterTransitionListener;
|
||||
if (enterTransitionListener && fragment) {
|
||||
fragment.setEnterTransition(enterTransitionListener.transition);
|
||||
}
|
||||
|
||||
const exitTransitionListener = entry.exitTransitionListener;
|
||||
if (exitTransitionListener && fragment) {
|
||||
fragment.setExitTransition(exitTransitionListener.transition);
|
||||
}
|
||||
|
||||
const reenterTransitionListener = entry.reenterTransitionListener;
|
||||
if (reenterTransitionListener && fragment) {
|
||||
fragment.setReenterTransition(reenterTransitionListener.transition);
|
||||
}
|
||||
|
||||
const returnTransitionListener = entry.returnTransitionListener;
|
||||
if (returnTransitionListener && fragment) {
|
||||
fragment.setReturnTransition(returnTransitionListener.transition);
|
||||
}
|
||||
}
|
||||
|
||||
export function _reverseTransitions(previousEntry: ExpandedEntry, currentEntry: ExpandedEntry): boolean {
|
||||
const previousFragment = previousEntry.fragment;
|
||||
const currentFragment = currentEntry.fragment;
|
||||
let transitionUsed = false;
|
||||
|
||||
const returnTransitionListener = currentEntry.returnTransitionListener;
|
||||
if (returnTransitionListener) {
|
||||
transitionUsed = true;
|
||||
currentFragment.setExitTransition(returnTransitionListener.transition);
|
||||
} else {
|
||||
currentFragment.setExitTransition(null);
|
||||
}
|
||||
|
||||
const reenterTransitionListener = previousEntry.reenterTransitionListener;
|
||||
if (reenterTransitionListener) {
|
||||
transitionUsed = true;
|
||||
previousFragment.setEnterTransition(reenterTransitionListener.transition);
|
||||
} else {
|
||||
previousFragment.setEnterTransition(null);
|
||||
}
|
||||
|
||||
return transitionUsed;
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if (!TransitionListener) {
|
||||
@Interfaces([(<any>androidx).transition.Transition.TransitionListener])
|
||||
class TransitionListenerImpl extends java.lang.Object implements androidx.transition.Transition.TransitionListener {
|
||||
constructor(public entry: ExpandedEntry, public transition: androidx.transition.Transition) {
|
||||
super();
|
||||
|
||||
return global.__native(this);
|
||||
}
|
||||
|
||||
public onTransitionStart(transition: androidx.transition.Transition): void {
|
||||
const entry = this.entry;
|
||||
addToWaitingQueue(entry);
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`START ${toShortString(transition)} transition for ${entry.fragmentTag}`, traceCategories.Transition);
|
||||
}
|
||||
}
|
||||
|
||||
onTransitionEnd(transition: androidx.transition.Transition): void {
|
||||
const entry = this.entry;
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`END ${toShortString(transition)} transition for ${entry.fragmentTag}`, traceCategories.Transition);
|
||||
}
|
||||
|
||||
transitionOrAnimationCompleted(entry);
|
||||
}
|
||||
|
||||
onTransitionResume(transition: androidx.transition.Transition): void {
|
||||
if (traceEnabled()) {
|
||||
const fragment = this.entry.fragmentTag;
|
||||
traceWrite(`RESUME ${toShortString(transition)} transition for ${fragment}`, traceCategories.Transition);
|
||||
}
|
||||
}
|
||||
|
||||
onTransitionPause(transition: androidx.transition.Transition): void {
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`PAUSE ${toShortString(transition)} transition for ${this.entry.fragmentTag}`, traceCategories.Transition);
|
||||
}
|
||||
}
|
||||
|
||||
onTransitionCancel(transition: androidx.transition.Transition): void {
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`CANCEL ${toShortString(transition)} transition for ${this.entry.fragmentTag}`, traceCategories.Transition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TransitionListener = TransitionListenerImpl;
|
||||
}
|
||||
|
||||
return new TransitionListener(entry, transition);
|
||||
}
|
||||
|
||||
function addToWaitingQueue(entry: ExpandedEntry): void {
|
||||
const frameId = entry.frameId;
|
||||
let entries = waitingQueue.get(frameId);
|
||||
if (!entries) {
|
||||
entries = new Set<ExpandedEntry>();
|
||||
waitingQueue.set(frameId, entries);
|
||||
}
|
||||
|
||||
entries.add(entry);
|
||||
}
|
||||
|
||||
function clearExitAndReenterTransitions(entry: ExpandedEntry, removeListener: boolean): void {
|
||||
const fragment: androidx.fragment.app.Fragment = entry.fragment;
|
||||
const exitListener = entry.exitTransitionListener;
|
||||
if (exitListener) {
|
||||
const exitTransition = fragment.getExitTransition();
|
||||
if (exitTransition) {
|
||||
if (removeListener) {
|
||||
exitTransition.removeListener(exitListener);
|
||||
}
|
||||
|
||||
fragment.setExitTransition(null);
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`Cleared Exit ${exitTransition.getClass().getSimpleName()} transition for ${fragment}`, traceCategories.Transition);
|
||||
}
|
||||
}
|
||||
|
||||
if (removeListener) {
|
||||
entry.exitTransitionListener = null;
|
||||
}
|
||||
}
|
||||
|
||||
const reenterListener = entry.reenterTransitionListener;
|
||||
if (reenterListener) {
|
||||
const reenterTransition = fragment.getReenterTransition();
|
||||
if (reenterTransition) {
|
||||
if (removeListener) {
|
||||
reenterTransition.removeListener(reenterListener);
|
||||
}
|
||||
|
||||
fragment.setReenterTransition(null);
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`Cleared Reenter ${reenterTransition.getClass().getSimpleName()} transition for ${fragment}`, traceCategories.Transition);
|
||||
}
|
||||
}
|
||||
|
||||
if (removeListener) {
|
||||
entry.reenterTransitionListener = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function _clearFragment(entry: ExpandedEntry): void {
|
||||
clearEntry(entry, false);
|
||||
}
|
||||
|
||||
export function _clearEntry(entry: ExpandedEntry): void {
|
||||
clearEntry(entry, true);
|
||||
}
|
||||
|
||||
function clearEntry(entry: ExpandedEntry, removeListener: boolean): void {
|
||||
clearExitAndReenterTransitions(entry, removeListener);
|
||||
|
||||
const fragment: androidx.fragment.app.Fragment = entry.fragment;
|
||||
const enterListener = entry.enterTransitionListener;
|
||||
if (enterListener) {
|
||||
const enterTransition = fragment.getEnterTransition();
|
||||
if (enterTransition) {
|
||||
if (removeListener) {
|
||||
enterTransition.removeListener(enterListener);
|
||||
}
|
||||
|
||||
fragment.setEnterTransition(null);
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`Cleared Enter ${enterTransition.getClass().getSimpleName()} transition for ${fragment}`, traceCategories.Transition);
|
||||
}
|
||||
}
|
||||
|
||||
if (removeListener) {
|
||||
entry.enterTransitionListener = null;
|
||||
}
|
||||
}
|
||||
|
||||
const returnListener = entry.returnTransitionListener;
|
||||
if (returnListener) {
|
||||
const returnTransition = fragment.getReturnTransition();
|
||||
if (returnTransition) {
|
||||
if (removeListener) {
|
||||
returnTransition.removeListener(returnListener);
|
||||
}
|
||||
|
||||
fragment.setReturnTransition(null);
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`Cleared Return ${returnTransition.getClass().getSimpleName()} transition for ${fragment}`, traceCategories.Transition);
|
||||
}
|
||||
}
|
||||
|
||||
if (removeListener) {
|
||||
entry.returnTransitionListener = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function allowTransitionOverlap(fragment: androidx.fragment.app.Fragment): void {
|
||||
if (fragment) {
|
||||
fragment.setAllowEnterTransitionOverlap(true);
|
||||
fragment.setAllowReturnTransitionOverlap(true);
|
||||
}
|
||||
}
|
||||
|
||||
function setEnterTransition(navigationTransition: NavigationTransition, entry: ExpandedEntry, transition: androidx.transition.Transition): void {
|
||||
setUpNativeTransition(navigationTransition, transition);
|
||||
const listener = addNativeTransitionListener(entry, transition);
|
||||
|
||||
// attach listener to JS object so that it will be alive as long as entry.
|
||||
entry.enterTransitionListener = listener;
|
||||
const fragment: androidx.fragment.app.Fragment = entry.fragment;
|
||||
fragment.setEnterTransition(transition);
|
||||
}
|
||||
|
||||
function setExitTransition(navigationTransition: NavigationTransition, entry: ExpandedEntry, transition: androidx.transition.Transition): void {
|
||||
setUpNativeTransition(navigationTransition, transition);
|
||||
const listener = addNativeTransitionListener(entry, transition);
|
||||
|
||||
// attach listener to JS object so that it will be alive as long as entry.
|
||||
entry.exitTransitionListener = listener;
|
||||
const fragment: androidx.fragment.app.Fragment = entry.fragment;
|
||||
fragment.setExitTransition(transition);
|
||||
}
|
||||
|
||||
function setReenterTransition(navigationTransition: NavigationTransition, entry: ExpandedEntry, transition: androidx.transition.Transition): void {
|
||||
setUpNativeTransition(navigationTransition, transition);
|
||||
const listener = addNativeTransitionListener(entry, transition);
|
||||
|
||||
// attach listener to JS object so that it will be alive as long as entry.
|
||||
entry.reenterTransitionListener = listener;
|
||||
const fragment: androidx.fragment.app.Fragment = entry.fragment;
|
||||
fragment.setReenterTransition(transition);
|
||||
}
|
||||
|
||||
function setReturnTransition(navigationTransition: NavigationTransition, entry: ExpandedEntry, transition: androidx.transition.Transition): void {
|
||||
setUpNativeTransition(navigationTransition, transition);
|
||||
const listener = addNativeTransitionListener(entry, transition);
|
||||
|
||||
// attach listener to JS object so that it will be alive as long as entry.
|
||||
entry.returnTransitionListener = listener;
|
||||
const fragment: androidx.fragment.app.Fragment = entry.fragment;
|
||||
fragment.setReturnTransition(transition);
|
||||
}
|
||||
|
||||
function setupNewFragmentSlideTransition(navTransition: NavigationTransition, entry: ExpandedEntry, name: string): void {
|
||||
setupCurrentFragmentSlideTransition(navTransition, entry, name);
|
||||
const direction = name.substr("slide".length) || "left"; //Extract the direction from the string
|
||||
switch (direction) {
|
||||
case "left":
|
||||
setEnterTransition(navTransition, entry, new androidx.transition.Slide(android.view.Gravity.RIGHT));
|
||||
setReturnTransition(navTransition, entry, new androidx.transition.Slide(android.view.Gravity.RIGHT));
|
||||
break;
|
||||
|
||||
case "right":
|
||||
setEnterTransition(navTransition, entry, new androidx.transition.Slide(android.view.Gravity.LEFT));
|
||||
setReturnTransition(navTransition, entry, new androidx.transition.Slide(android.view.Gravity.LEFT));
|
||||
break;
|
||||
|
||||
case "top":
|
||||
setEnterTransition(navTransition, entry, new androidx.transition.Slide(android.view.Gravity.BOTTOM));
|
||||
setReturnTransition(navTransition, entry, new androidx.transition.Slide(android.view.Gravity.BOTTOM));
|
||||
break;
|
||||
|
||||
case "bottom":
|
||||
setEnterTransition(navTransition, entry, new androidx.transition.Slide(android.view.Gravity.TOP));
|
||||
setReturnTransition(navTransition, entry, new androidx.transition.Slide(android.view.Gravity.TOP));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function setupCurrentFragmentSlideTransition(navTransition: NavigationTransition, entry: ExpandedEntry, name: string): void {
|
||||
const direction = name.substr("slide".length) || "left"; //Extract the direction from the string
|
||||
switch (direction) {
|
||||
case "left":
|
||||
setExitTransition(navTransition, entry, new androidx.transition.Slide(android.view.Gravity.LEFT));
|
||||
setReenterTransition(navTransition, entry, new androidx.transition.Slide(android.view.Gravity.LEFT));
|
||||
break;
|
||||
|
||||
case "right":
|
||||
setExitTransition(navTransition, entry, new androidx.transition.Slide(android.view.Gravity.RIGHT));
|
||||
setReenterTransition(navTransition, entry, new androidx.transition.Slide(android.view.Gravity.RIGHT));
|
||||
break;
|
||||
|
||||
case "top":
|
||||
setExitTransition(navTransition, entry, new androidx.transition.Slide(android.view.Gravity.TOP));
|
||||
setReenterTransition(navTransition, entry, new androidx.transition.Slide(android.view.Gravity.TOP));
|
||||
break;
|
||||
|
||||
case "bottom":
|
||||
setExitTransition(navTransition, entry, new androidx.transition.Slide(android.view.Gravity.BOTTOM));
|
||||
setReenterTransition(navTransition, entry, new androidx.transition.Slide(android.view.Gravity.BOTTOM));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function setupCurrentFragmentCustomTransition(navTransition: NavigationTransition, entry: ExpandedEntry, transition: Transition): void {
|
||||
const exitAnimator = transition.createAndroidAnimator(AndroidTransitionType.exit);
|
||||
const exitTransition = new org.nativescript.widgets.CustomTransition(exitAnimator, transition.constructor.name + AndroidTransitionType.exit.toString());
|
||||
|
||||
setExitTransition(navTransition, entry, exitTransition);
|
||||
|
||||
const reenterAnimator = transition.createAndroidAnimator(AndroidTransitionType.popEnter);
|
||||
const reenterTransition = new org.nativescript.widgets.CustomTransition(reenterAnimator, transition.constructor.name + AndroidTransitionType.popEnter.toString());
|
||||
|
||||
setReenterTransition(navTransition, entry, reenterTransition);
|
||||
}
|
||||
|
||||
function setupNewFragmentCustomTransition(navTransition: NavigationTransition, entry: ExpandedEntry, transition: Transition): void {
|
||||
setupCurrentFragmentCustomTransition(navTransition, entry, transition);
|
||||
|
||||
const enterAnimator = transition.createAndroidAnimator(AndroidTransitionType.enter);
|
||||
const enterTransition = new org.nativescript.widgets.CustomTransition(enterAnimator, transition.constructor.name + AndroidTransitionType.enter.toString());
|
||||
setEnterTransition(navTransition, entry, enterTransition);
|
||||
|
||||
const returnAnimator = transition.createAndroidAnimator(AndroidTransitionType.popExit);
|
||||
const returnTransition = new org.nativescript.widgets.CustomTransition(returnAnimator, transition.constructor.name + AndroidTransitionType.popExit.toString());
|
||||
setReturnTransition(navTransition, entry, returnTransition);
|
||||
|
||||
}
|
||||
|
||||
function setupNewFragmentFadeTransition(navTransition: NavigationTransition, entry: ExpandedEntry): void {
|
||||
setupCurrentFragmentFadeTransition(navTransition, entry);
|
||||
|
||||
const fadeInEnter = new androidx.transition.Fade(androidx.transition.Fade.IN);
|
||||
setEnterTransition(navTransition, entry, fadeInEnter);
|
||||
|
||||
const fadeOutReturn = new androidx.transition.Fade(androidx.transition.Fade.OUT);
|
||||
setReturnTransition(navTransition, entry, fadeOutReturn);
|
||||
}
|
||||
|
||||
function setupCurrentFragmentFadeTransition(navTransition: NavigationTransition, entry: ExpandedEntry): void {
|
||||
const fadeOutExit = new androidx.transition.Fade(androidx.transition.Fade.OUT);
|
||||
setExitTransition(navTransition, entry, fadeOutExit);
|
||||
|
||||
// NOTE: There is a bug in Fade transition so we need to set all 4
|
||||
// otherwise back navigation will complete immediately (won't run the reverse transition).
|
||||
const fadeInReenter = new androidx.transition.Fade(androidx.transition.Fade.IN);
|
||||
setReenterTransition(navTransition, entry, fadeInReenter);
|
||||
}
|
||||
|
||||
function setupCurrentFragmentExplodeTransition(navTransition: NavigationTransition, entry: ExpandedEntry): void {
|
||||
setExitTransition(navTransition, entry, new androidx.transition.Explode());
|
||||
setReenterTransition(navTransition, entry, new androidx.transition.Explode());
|
||||
}
|
||||
|
||||
function setupNewFragmentExplodeTransition(navTransition: NavigationTransition, entry: ExpandedEntry): void {
|
||||
setupCurrentFragmentExplodeTransition(navTransition, entry);
|
||||
|
||||
setEnterTransition(navTransition, entry, new androidx.transition.Explode());
|
||||
setReturnTransition(navTransition, entry, new androidx.transition.Explode());
|
||||
}
|
||||
|
||||
function setUpNativeTransition(navigationTransition: NavigationTransition, nativeTransition: androidx.transition.Transition) {
|
||||
if (navigationTransition.duration) {
|
||||
nativeTransition.setDuration(navigationTransition.duration);
|
||||
}
|
||||
|
||||
const interpolator = navigationTransition.curve ? _resolveAnimationCurve(navigationTransition.curve) : defaultInterpolator();
|
||||
nativeTransition.setInterpolator(interpolator);
|
||||
}
|
||||
|
||||
export function addNativeTransitionListener(entry: ExpandedEntry, nativeTransition: androidx.transition.Transition): ExpandedTransitionListener {
|
||||
const listener = getTransitionListener(entry, nativeTransition);
|
||||
nativeTransition.addListener(listener);
|
||||
|
||||
return listener;
|
||||
}
|
||||
|
||||
function transitionOrAnimationCompleted(entry: ExpandedEntry): void {
|
||||
const frameId = entry.frameId;
|
||||
const entries = waitingQueue.get(frameId);
|
||||
// https://github.com/NativeScript/NativeScript/issues/5759
|
||||
// https://github.com/NativeScript/NativeScript/issues/5780
|
||||
// transitionOrAnimationCompleted fires again (probably bug in android)
|
||||
// NOTE: we cannot reproduce this issue so this is a blind fix
|
||||
if (!entries) {
|
||||
return;
|
||||
}
|
||||
|
||||
entries.delete(entry);
|
||||
if (entries.size === 0) {
|
||||
const frame = entry.resolvedPage.frame;
|
||||
|
||||
// We have 0 or 1 entry per frameId in completedEntries
|
||||
// So there is no need to make it to Set like waitingQueue
|
||||
const previousCompletedAnimationEntry = completedEntries.get(frameId);
|
||||
completedEntries.delete(frameId);
|
||||
waitingQueue.delete(frameId);
|
||||
|
||||
const navigationContext = frame._executingContext || { navigationType: NavigationType.back };
|
||||
let current = frame.isCurrent(entry) ? previousCompletedAnimationEntry : entry;
|
||||
current = current || entry;
|
||||
// Will be null if Frame is shown modally...
|
||||
// transitionOrAnimationCompleted fires again (probably bug in android).
|
||||
if (current) {
|
||||
setTimeout(() => frame.setCurrent(current, navigationContext.navigationType));
|
||||
}
|
||||
} else {
|
||||
completedEntries.set(frameId, entry);
|
||||
}
|
||||
}
|
||||
|
||||
function toShortString(nativeTransition: androidx.transition.Transition): string {
|
||||
return `${nativeTransition.getClass().getSimpleName()}@${nativeTransition.hashCode().toString(16)}`;
|
||||
}
|
||||
|
||||
function printTransitions(entry: ExpandedEntry) {
|
||||
if (entry && traceEnabled()) {
|
||||
let result = `${entry.fragmentTag} Transitions:`;
|
||||
if (entry.transitionName) {
|
||||
result += `transitionName=${entry.transitionName}, `;
|
||||
}
|
||||
|
||||
const fragment = entry.fragment;
|
||||
result += `${fragment.getEnterTransition() ? " enter=" + toShortString(fragment.getEnterTransition()) : ""}`;
|
||||
result += `${fragment.getExitTransition() ? " exit=" + toShortString(fragment.getExitTransition()) : ""}`;
|
||||
result += `${fragment.getReenterTransition() ? " popEnter=" + toShortString(fragment.getReenterTransition()) : ""}`;
|
||||
result += `${fragment.getReturnTransition() ? " popExit=" + toShortString(fragment.getReturnTransition()) : ""}`;
|
||||
|
||||
traceWrite(result, traceCategories.Transition);
|
||||
}
|
||||
}
|
||||
|
||||
function javaObjectArray(...params: java.lang.Object[]) {
|
||||
const nativeArray = Array.create(java.lang.Object, params.length);
|
||||
params.forEach((value, i) => nativeArray[i] = value);
|
||||
|
||||
return nativeArray;
|
||||
}
|
||||
|
||||
function createDummyZeroDurationAnimator(duration: number): android.animation.AnimatorSet {
|
||||
const animatorSet = new android.animation.AnimatorSet();
|
||||
const objectAnimators = Array.create(android.animation.Animator, 1);
|
||||
|
||||
const values = Array.create("float", 2);
|
||||
values[0] = 0.0;
|
||||
values[1] = 1.0;
|
||||
|
||||
const animator = <android.animation.Animator>android.animation.ObjectAnimator.ofFloat(null, "alpha", values);
|
||||
animator.setDuration(duration);
|
||||
objectAnimators[0] = animator;
|
||||
animatorSet.playTogether(objectAnimators);
|
||||
|
||||
return animatorSet;
|
||||
}
|
||||
|
||||
class NoTransition extends Transition {
|
||||
public createAndroidAnimator(transitionType: string): android.animation.AnimatorSet {
|
||||
return createDummyZeroDurationAnimator(this.getDuration());
|
||||
}
|
||||
}
|
||||
60
nativescript-core/ui/frame/fragment.transitions.d.ts
vendored
Normal file
60
nativescript-core/ui/frame/fragment.transitions.d.ts
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* @module "ui/transition"
|
||||
*/ /** */
|
||||
|
||||
import { NavigationTransition, BackstackEntry } from "../frame";
|
||||
// Types.
|
||||
import { Transition, AndroidTransitionType } from "../transition/transition";
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
export function _setAndroidFragmentTransitions(
|
||||
animated: boolean,
|
||||
navigationTransition: NavigationTransition,
|
||||
currentEntry: BackstackEntry,
|
||||
newEntry: BackstackEntry,
|
||||
frameId: number,
|
||||
fragmentTransaction: any,
|
||||
isNestedDefaultTransition?: boolean): void;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
export function _getAnimatedEntries(frameId: number): Set<BackstackEntry>;
|
||||
/**
|
||||
* @private
|
||||
* Called once fragment is recreated after it was destroyed.
|
||||
* Reapply animations and transitions from entry to fragment if any.
|
||||
*/
|
||||
export function _updateTransitions(entry: BackstackEntry): void;
|
||||
/**
|
||||
* @private
|
||||
* Called once fragment is going to reappear from backstack.
|
||||
* Reverse transitions from entry to fragment if any.
|
||||
*/
|
||||
export function _reverseTransitions(previousEntry: BackstackEntry, currentEntry: BackstackEntry): boolean;
|
||||
/**
|
||||
* @private
|
||||
* Called when entry is removed from backstack (either back navigation or
|
||||
* navigate with clear history). Removes all animations and transitions from entry
|
||||
* and fragment and clears all listeners in order to prevent memory leaks.
|
||||
*/
|
||||
export function _clearEntry(entry: BackstackEntry): void;
|
||||
/**
|
||||
* @private
|
||||
* Called when fragment is destroyed because activity is destroyed.
|
||||
* Removes all animations and transitions but keeps them on the entry
|
||||
* in order to reapply them when new fragment is created for the same entry.
|
||||
*/
|
||||
export function _clearFragment(entry: BackstackEntry): void;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
export function _createIOSAnimatedTransitioning(navigationTransition: NavigationTransition, nativeCurve: any, operation: number, fromVC: any, toVC: any): any;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* nativeTransition: androidx.transition.Transition
|
||||
*/
|
||||
export function addNativeTransitionListener(entry: any, nativeTransition: any): any;
|
||||
//@endprivate
|
||||
89
nativescript-core/ui/frame/fragment.transitions.ios.ts
Normal file
89
nativescript-core/ui/frame/fragment.transitions.ios.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { NavigationTransition } from "./frame";
|
||||
import { Transition } from "../transition/transition";
|
||||
import { SlideTransition } from "../transition/slide-transition";
|
||||
import { FadeTransition } from "../transition/fade-transition";
|
||||
|
||||
import { isEnabled as traceEnabled, write as traceWrite, categories as traceCategories } from "../../trace";
|
||||
|
||||
module UIViewControllerAnimatedTransitioningMethods {
|
||||
const methodSignature = NSMethodSignature.signatureWithObjCTypes("v@:c");
|
||||
const invocation = NSInvocation.invocationWithMethodSignature(methodSignature);
|
||||
invocation.selector = "completeTransition:";
|
||||
|
||||
export function completeTransition(didComplete: boolean) {
|
||||
const didCompleteReference = new interop.Reference(interop.types.bool, didComplete);
|
||||
invocation.setArgumentAtIndex(didCompleteReference, 2);
|
||||
invocation.invokeWithTarget(this);
|
||||
}
|
||||
}
|
||||
|
||||
class AnimatedTransitioning extends NSObject implements UIViewControllerAnimatedTransitioning {
|
||||
public static ObjCProtocols = [UIViewControllerAnimatedTransitioning];
|
||||
|
||||
private _transition: Transition;
|
||||
private _operation: UINavigationControllerOperation;
|
||||
private _fromVC: UIViewController;
|
||||
private _toVC: UIViewController;
|
||||
private _transitionType: string;
|
||||
|
||||
public static init(transition: Transition, operation: UINavigationControllerOperation, fromVC: UIViewController, toVC: UIViewController): AnimatedTransitioning {
|
||||
const impl = <AnimatedTransitioning>AnimatedTransitioning.new();
|
||||
impl._transition = transition;
|
||||
impl._operation = operation;
|
||||
impl._fromVC = fromVC;
|
||||
impl._toVC = toVC;
|
||||
|
||||
return impl;
|
||||
}
|
||||
|
||||
public animateTransition(transitionContext: any): void {
|
||||
const containerView = transitionContext.valueForKey("containerView");
|
||||
const completion = UIViewControllerAnimatedTransitioningMethods.completeTransition.bind(transitionContext);
|
||||
switch (this._operation) {
|
||||
case UINavigationControllerOperation.Push: this._transitionType = "push"; break;
|
||||
case UINavigationControllerOperation.Pop: this._transitionType = "pop"; break;
|
||||
case UINavigationControllerOperation.None: this._transitionType = "none"; break;
|
||||
}
|
||||
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`START ${this._transition} ${this._transitionType}`, traceCategories.Transition);
|
||||
}
|
||||
this._transition.animateIOSTransition(containerView, this._fromVC.view, this._toVC.view, this._operation, completion);
|
||||
}
|
||||
|
||||
public transitionDuration(transitionContext: UIViewControllerContextTransitioning): number {
|
||||
return this._transition.getDuration();
|
||||
}
|
||||
|
||||
public animationEnded(transitionCompleted: boolean): void {
|
||||
if (transitionCompleted) {
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`END ${this._transition} ${this._transitionType}`, traceCategories.Transition);
|
||||
}
|
||||
} else {
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`CANCEL ${this._transition} ${this._transitionType}`, traceCategories.Transition);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function _createIOSAnimatedTransitioning(navigationTransition: NavigationTransition, nativeCurve: UIViewAnimationCurve, operation: UINavigationControllerOperation, fromVC: UIViewController, toVC: UIViewController): UIViewControllerAnimatedTransitioning {
|
||||
const instance = navigationTransition.instance;
|
||||
let transition: Transition;
|
||||
|
||||
if (instance) {
|
||||
// Instance transition should take precedence even if the given name match existing transition.
|
||||
transition = instance;
|
||||
} else if (navigationTransition.name) {
|
||||
const name = navigationTransition.name.toLowerCase();
|
||||
if (name.indexOf("slide") === 0) {
|
||||
const direction = name.substr("slide".length) || "left"; //Extract the direction from the string
|
||||
transition = new SlideTransition(direction, navigationTransition.duration, nativeCurve);
|
||||
} else if (name === "fade") {
|
||||
transition = new FadeTransition(navigationTransition.duration, nativeCurve);
|
||||
}
|
||||
}
|
||||
|
||||
return transition ? AnimatedTransitioning.init(transition, operation, fromVC, toVC) : undefined;
|
||||
}
|
||||
11
nativescript-core/ui/frame/fragment.transitions.types.ts
Normal file
11
nativescript-core/ui/frame/fragment.transitions.types.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* @module "ui/fragment.transition.types"
|
||||
* @private
|
||||
*/ /** */
|
||||
//@private
|
||||
export const enum AnimationType {
|
||||
enterFakeResourceId = -10,
|
||||
exitFakeResourceId = -20,
|
||||
popEnterFakeResourceId = -30,
|
||||
popExitFakeResourceId = -40
|
||||
}
|
||||
734
nativescript-core/ui/frame/frame-common.ts
Normal file
734
nativescript-core/ui/frame/frame-common.ts
Normal file
@@ -0,0 +1,734 @@
|
||||
// Definitions.
|
||||
import { Frame as FrameDefinition, NavigationEntry, BackstackEntry, NavigationTransition } from ".";
|
||||
import { Page } from "../page";
|
||||
|
||||
// Types.
|
||||
import { getAncestor, viewMatchesModuleContext } from "../core/view/view-common";
|
||||
import { View, CustomLayoutView, isIOS, isAndroid, traceEnabled, traceWrite, traceCategories, Property, CSSType } from "../core/view";
|
||||
import { Builder } from "../builder";
|
||||
import { profile } from "../../profiling";
|
||||
|
||||
import { frameStack, topmost as frameStackTopmost, _pushInFrameStack, _popFromFrameStack, _removeFromFrameStack } from "./frame-stack";
|
||||
import { sanitizeModuleName } from "../builder/module-name-sanitizer";
|
||||
export * from "../core/view";
|
||||
|
||||
export enum NavigationType {
|
||||
back,
|
||||
forward,
|
||||
replace
|
||||
}
|
||||
|
||||
function buildEntryFromArgs(arg: any): NavigationEntry {
|
||||
let entry: NavigationEntry;
|
||||
if (typeof arg === "string") {
|
||||
entry = {
|
||||
moduleName: arg
|
||||
};
|
||||
} else if (typeof arg === "function") {
|
||||
entry = {
|
||||
create: arg
|
||||
};
|
||||
} else {
|
||||
entry = arg;
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
export interface NavigationContext {
|
||||
entry: BackstackEntry;
|
||||
// TODO: remove isBackNavigation for NativeScript 6.0
|
||||
isBackNavigation: boolean;
|
||||
navigationType: NavigationType;
|
||||
}
|
||||
|
||||
@CSSType("Frame")
|
||||
export class FrameBase extends CustomLayoutView implements FrameDefinition {
|
||||
public static androidOptionSelectedEvent = "optionSelected";
|
||||
|
||||
private _animated: boolean;
|
||||
private _transition: NavigationTransition;
|
||||
private _backStack = new Array<BackstackEntry>();
|
||||
private _navigationQueue = new Array<NavigationContext>();
|
||||
|
||||
public actionBarVisibility: "auto" | "never" | "always";
|
||||
public _currentEntry: BackstackEntry;
|
||||
public _executingContext: NavigationContext;
|
||||
public _isInFrameStack = false;
|
||||
public static defaultAnimatedNavigation = true;
|
||||
public static defaultTransition: NavigationTransition;
|
||||
|
||||
static getFrameById(id: string): FrameBase {
|
||||
return frameStack.find((frame) => frame.id && frame.id === id);
|
||||
}
|
||||
|
||||
static topmost(): FrameBase {
|
||||
return frameStackTopmost();
|
||||
}
|
||||
|
||||
static goBack(): boolean {
|
||||
const top = FrameBase.topmost();
|
||||
if (top && top.canGoBack()) {
|
||||
top.goBack();
|
||||
|
||||
return true;
|
||||
} else if (top) {
|
||||
let parentFrameCanGoBack = false;
|
||||
let parentFrame = <FrameBase>getAncestor(top, "Frame");
|
||||
|
||||
while (parentFrame && !parentFrameCanGoBack) {
|
||||
if (parentFrame && parentFrame.canGoBack()) {
|
||||
parentFrameCanGoBack = true;
|
||||
} else {
|
||||
parentFrame = <FrameBase>getAncestor(parentFrame, "Frame");
|
||||
}
|
||||
}
|
||||
|
||||
if (parentFrame && parentFrameCanGoBack) {
|
||||
parentFrame.goBack();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (frameStack.length > 1) {
|
||||
top._popFromFrameStack();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
static reloadPage(): void {
|
||||
// Implemented in plat-specific file - only for android.
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
static _stack(): Array<FrameBase> {
|
||||
return frameStack;
|
||||
}
|
||||
|
||||
// TODO: Currently our navigation will not be synchronized in case users directly call native navigation methods like Activity.startActivity.
|
||||
public _addChildFromBuilder(name: string, value: any) {
|
||||
throw new Error(`Frame should not have a view. Use 'defaultPage' property instead.`);
|
||||
}
|
||||
|
||||
@profile
|
||||
public onLoaded() {
|
||||
super.onLoaded();
|
||||
|
||||
this._processNextNavigationEntry();
|
||||
}
|
||||
|
||||
public canGoBack(): boolean {
|
||||
let backstack = this._backStack.length;
|
||||
let previousForwardNotInBackstack = false;
|
||||
this._navigationQueue.forEach(item => {
|
||||
const entry = item.entry;
|
||||
const isBackNavigation = item.navigationType === NavigationType.back;
|
||||
if (isBackNavigation) {
|
||||
previousForwardNotInBackstack = false;
|
||||
if (!entry) {
|
||||
backstack--;
|
||||
} else {
|
||||
const backstackIndex = this._backStack.indexOf(entry);
|
||||
if (backstackIndex !== -1) {
|
||||
backstack = backstackIndex;
|
||||
} else {
|
||||
// NOTE: We don't search for entries in navigationQueue because there is no way for
|
||||
// developer to get reference to BackstackEntry unless transition is completed.
|
||||
// At that point the entry is put in the backstack array.
|
||||
// If we start to return Backstack entry from navigate method then
|
||||
// here we should check also navigationQueue as well.
|
||||
backstack--;
|
||||
}
|
||||
}
|
||||
} else if (entry.entry.clearHistory) {
|
||||
previousForwardNotInBackstack = false;
|
||||
backstack = 0;
|
||||
} else {
|
||||
backstack++;
|
||||
if (previousForwardNotInBackstack) {
|
||||
backstack--;
|
||||
}
|
||||
|
||||
previousForwardNotInBackstack = entry.entry.backstackVisible === false;
|
||||
}
|
||||
});
|
||||
|
||||
// this is our first navigation which is not completed yet.
|
||||
if (this._navigationQueue.length > 0 && !this._currentEntry) {
|
||||
backstack--;
|
||||
}
|
||||
|
||||
return backstack > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates to the previous entry (if any) in the back stack.
|
||||
* @param to The backstack entry to navigate back to.
|
||||
*/
|
||||
public goBack(backstackEntry?: BackstackEntry): void {
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`GO BACK`, traceCategories.Navigation);
|
||||
}
|
||||
|
||||
if (!this.canGoBack()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (backstackEntry) {
|
||||
const index = this._backStack.indexOf(backstackEntry);
|
||||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const navigationContext: NavigationContext = {
|
||||
entry: backstackEntry,
|
||||
isBackNavigation: true,
|
||||
navigationType: NavigationType.back
|
||||
};
|
||||
|
||||
this._navigationQueue.push(navigationContext);
|
||||
this._processNextNavigationEntry();
|
||||
}
|
||||
|
||||
public _removeEntry(removed: BackstackEntry): void {
|
||||
const page = removed.resolvedPage;
|
||||
const frame = page.frame;
|
||||
page._frame = null;
|
||||
if (frame) {
|
||||
frame._removeView(page);
|
||||
} else {
|
||||
page._tearDownUI(true);
|
||||
}
|
||||
|
||||
removed.resolvedPage = null;
|
||||
}
|
||||
|
||||
public navigate(param: any) {
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`NAVIGATE`, traceCategories.Navigation);
|
||||
}
|
||||
|
||||
const entry = buildEntryFromArgs(param);
|
||||
const page = Builder.createViewFromEntry(entry) as Page;
|
||||
|
||||
this._pushInFrameStack();
|
||||
|
||||
const backstackEntry: BackstackEntry = {
|
||||
entry: entry,
|
||||
resolvedPage: page,
|
||||
navDepth: undefined,
|
||||
fragmentTag: undefined
|
||||
};
|
||||
|
||||
const navigationContext: NavigationContext = {
|
||||
entry: backstackEntry,
|
||||
isBackNavigation: false,
|
||||
navigationType: NavigationType.forward
|
||||
};
|
||||
|
||||
this._navigationQueue.push(navigationContext);
|
||||
this._processNextNavigationEntry();
|
||||
}
|
||||
|
||||
public isCurrent(entry: BackstackEntry): boolean {
|
||||
return this._currentEntry === entry;
|
||||
}
|
||||
|
||||
public setCurrent(entry: BackstackEntry, navigationType: NavigationType): void {
|
||||
const newPage = entry.resolvedPage;
|
||||
// In case we navigated forward to a page that was in the backstack
|
||||
// with clearHistory: true
|
||||
if (!newPage.frame) {
|
||||
this._addView(newPage);
|
||||
newPage._frame = this;
|
||||
}
|
||||
|
||||
this._currentEntry = entry;
|
||||
|
||||
const isBack = navigationType === NavigationType.back;
|
||||
if (isBack) {
|
||||
this._pushInFrameStack();
|
||||
}
|
||||
|
||||
newPage.onNavigatedTo(isBack);
|
||||
|
||||
// Reset executing context after NavigatedTo is raised;
|
||||
// we do not want to execute two navigations in parallel in case
|
||||
// additional navigation is triggered from the NavigatedTo handler.
|
||||
this._executingContext = null;
|
||||
}
|
||||
|
||||
public _updateBackstack(entry: BackstackEntry, navigationType: NavigationType): void {
|
||||
const isBack = navigationType === NavigationType.back;
|
||||
const isReplace = navigationType === NavigationType.replace;
|
||||
this.raiseCurrentPageNavigatedEvents(isBack);
|
||||
const current = this._currentEntry;
|
||||
|
||||
// Do nothing for Hot Module Replacement
|
||||
if (isBack) {
|
||||
const index = this._backStack.indexOf(entry);
|
||||
this._backStack.splice(index + 1).forEach(e => this._removeEntry(e));
|
||||
this._backStack.pop();
|
||||
} else if (!isReplace) {
|
||||
if (entry.entry.clearHistory) {
|
||||
this._backStack.forEach(e => this._removeEntry(e));
|
||||
this._backStack.length = 0;
|
||||
} else if (FrameBase._isEntryBackstackVisible(current)) {
|
||||
this._backStack.push(current);
|
||||
}
|
||||
}
|
||||
|
||||
if (current && this._backStack.indexOf(current) < 0) {
|
||||
this._removeEntry(current);
|
||||
}
|
||||
}
|
||||
|
||||
private isNestedWithin(parentFrameCandidate: FrameBase): boolean {
|
||||
let frameAncestor: FrameBase = this;
|
||||
while (frameAncestor) {
|
||||
frameAncestor = <FrameBase>getAncestor(frameAncestor, FrameBase);
|
||||
if (frameAncestor === parentFrameCandidate) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private raiseCurrentPageNavigatedEvents(isBack: boolean) {
|
||||
const page = this.currentPage;
|
||||
if (page) {
|
||||
if (page.isLoaded) {
|
||||
// Forward navigation does not remove page from frame so we raise unloaded manually.
|
||||
page.callUnloaded();
|
||||
}
|
||||
|
||||
page.onNavigatedFrom(isBack);
|
||||
}
|
||||
}
|
||||
|
||||
public _processNavigationQueue(page: Page) {
|
||||
if (this._navigationQueue.length === 0) {
|
||||
// This could happen when showing recreated page after activity has been destroyed.
|
||||
return;
|
||||
}
|
||||
|
||||
const entry = this._navigationQueue[0].entry;
|
||||
const currentNavigationPage = entry.resolvedPage;
|
||||
if (page !== currentNavigationPage) {
|
||||
// If the page is not the one that requested navigation - skip it.
|
||||
return;
|
||||
}
|
||||
|
||||
// remove completed operation.
|
||||
this._navigationQueue.shift();
|
||||
this._processNextNavigationEntry();
|
||||
this._updateActionBar();
|
||||
}
|
||||
|
||||
public _findEntryForTag(fragmentTag: string): BackstackEntry {
|
||||
let entry: BackstackEntry;
|
||||
if (this._currentEntry && this._currentEntry.fragmentTag === fragmentTag) {
|
||||
entry = this._currentEntry;
|
||||
} else {
|
||||
entry = this._backStack.find((value) => value.fragmentTag === fragmentTag);
|
||||
// on API 26 fragments are recreated lazily after activity is destroyed.
|
||||
if (!entry) {
|
||||
const navigationItem = this._navigationQueue.find((value) => value.entry.fragmentTag === fragmentTag);
|
||||
entry = navigationItem ? navigationItem.entry : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
public navigationQueueIsEmpty(): boolean {
|
||||
return this._navigationQueue.length === 0;
|
||||
}
|
||||
|
||||
public static _isEntryBackstackVisible(entry: BackstackEntry): boolean {
|
||||
if (!entry) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const backstackVisibleValue = entry.entry.backstackVisible;
|
||||
const backstackHidden = backstackVisibleValue !== undefined && !backstackVisibleValue;
|
||||
|
||||
return !backstackHidden;
|
||||
}
|
||||
|
||||
public _updateActionBar(page?: Page, disableNavBarAnimation?: boolean) {
|
||||
//traceWrite("calling _updateActionBar on Frame", traceCategories.Navigation);
|
||||
}
|
||||
|
||||
protected _processNextNavigationEntry() {
|
||||
if (!this.isLoaded || this._executingContext) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._navigationQueue.length > 0) {
|
||||
const navigationContext = this._navigationQueue[0];
|
||||
const isBackNavigation = navigationContext.navigationType === NavigationType.back;
|
||||
if (isBackNavigation) {
|
||||
this.performGoBack(navigationContext);
|
||||
} else {
|
||||
this.performNavigation(navigationContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@profile
|
||||
public performNavigation(navigationContext: NavigationContext) {
|
||||
this._executingContext = navigationContext;
|
||||
|
||||
const backstackEntry = navigationContext.entry;
|
||||
const isBackNavigation = navigationContext.navigationType === NavigationType.back;
|
||||
this._onNavigatingTo(backstackEntry, isBackNavigation);
|
||||
this._navigateCore(backstackEntry);
|
||||
}
|
||||
|
||||
@profile
|
||||
private performGoBack(navigationContext: NavigationContext) {
|
||||
let backstackEntry = navigationContext.entry;
|
||||
const backstack = this._backStack;
|
||||
if (!backstackEntry) {
|
||||
backstackEntry = backstack[backstack.length - 1];
|
||||
navigationContext.entry = backstackEntry;
|
||||
}
|
||||
|
||||
this._executingContext = navigationContext;
|
||||
this._onNavigatingTo(backstackEntry, true);
|
||||
this._goBackCore(backstackEntry);
|
||||
}
|
||||
|
||||
public _goBackCore(backstackEntry: BackstackEntry) {
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`GO BACK CORE(${this._backstackEntryTrace(backstackEntry)}); currentPage: ${this.currentPage}`, traceCategories.Navigation);
|
||||
}
|
||||
}
|
||||
|
||||
public _navigateCore(backstackEntry: BackstackEntry) {
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`NAVIGATE CORE(${this._backstackEntryTrace(backstackEntry)}); currentPage: ${this.currentPage}`, traceCategories.Navigation);
|
||||
}
|
||||
}
|
||||
|
||||
public _onNavigatingTo(backstackEntry: BackstackEntry, isBack: boolean) {
|
||||
if (this.currentPage) {
|
||||
this.currentPage.onNavigatingFrom(isBack);
|
||||
}
|
||||
|
||||
backstackEntry.resolvedPage.onNavigatingTo(backstackEntry.entry.context, isBack, backstackEntry.entry.bindingContext);
|
||||
}
|
||||
|
||||
public get animated(): boolean {
|
||||
return this._animated;
|
||||
}
|
||||
|
||||
public set animated(value: boolean) {
|
||||
this._animated = value;
|
||||
}
|
||||
|
||||
public get transition(): NavigationTransition {
|
||||
return this._transition;
|
||||
}
|
||||
|
||||
public set transition(value: NavigationTransition) {
|
||||
this._transition = value;
|
||||
}
|
||||
|
||||
get backStack(): Array<BackstackEntry> {
|
||||
return this._backStack.slice();
|
||||
}
|
||||
|
||||
get currentPage(): Page {
|
||||
if (this._currentEntry) {
|
||||
return this._currentEntry.resolvedPage;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
get currentEntry(): NavigationEntry {
|
||||
if (this._currentEntry) {
|
||||
return this._currentEntry.entry;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public _pushInFrameStackRecursive() {
|
||||
this._pushInFrameStack();
|
||||
|
||||
// make sure nested frames order is kept intact i.e. the nested one should always be on top;
|
||||
// see https://github.com/NativeScript/nativescript-angular/issues/1596 for more information
|
||||
const framesToPush = [];
|
||||
for (const frame of frameStack) {
|
||||
if (frame.isNestedWithin(this)) {
|
||||
framesToPush.push(frame);
|
||||
}
|
||||
}
|
||||
|
||||
for (const frame of framesToPush) {
|
||||
frame._pushInFrameStack();
|
||||
}
|
||||
}
|
||||
|
||||
public _pushInFrameStack() {
|
||||
_pushInFrameStack(this);
|
||||
}
|
||||
|
||||
public _popFromFrameStack() {
|
||||
_popFromFrameStack(this);
|
||||
}
|
||||
|
||||
public _removeFromFrameStack() {
|
||||
_removeFromFrameStack(this);
|
||||
}
|
||||
|
||||
public _dialogClosed(): void {
|
||||
// No super call as we do not support nested frames to clean up
|
||||
this._removeFromFrameStack();
|
||||
}
|
||||
|
||||
public _onRootViewReset(): void {
|
||||
super._onRootViewReset();
|
||||
this._removeFromFrameStack();
|
||||
}
|
||||
|
||||
get _childrenCount(): number {
|
||||
if (this.currentPage) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public eachChildView(callback: (child: View) => boolean) {
|
||||
const page = this.currentPage;
|
||||
if (page) {
|
||||
callback(page);
|
||||
}
|
||||
}
|
||||
|
||||
public _getIsAnimatedNavigation(entry: NavigationEntry): boolean {
|
||||
if (entry && entry.animated !== undefined) {
|
||||
return entry.animated;
|
||||
}
|
||||
|
||||
if (this.animated !== undefined) {
|
||||
return this.animated;
|
||||
}
|
||||
|
||||
return FrameBase.defaultAnimatedNavigation;
|
||||
}
|
||||
|
||||
public _getNavigationTransition(entry: NavigationEntry): NavigationTransition {
|
||||
if (entry) {
|
||||
if (isIOS && entry.transitioniOS !== undefined) {
|
||||
return entry.transitioniOS;
|
||||
}
|
||||
|
||||
if (isAndroid && entry.transitionAndroid !== undefined) {
|
||||
return entry.transitionAndroid;
|
||||
}
|
||||
|
||||
if (entry.transition !== undefined) {
|
||||
return entry.transition;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.transition !== undefined) {
|
||||
return this.transition;
|
||||
}
|
||||
|
||||
return FrameBase.defaultTransition;
|
||||
}
|
||||
|
||||
public get navigationBarHeight(): number {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public _getNavBarVisible(page: Page): boolean {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
// We don't need to put Page as visual child. Don't call super.
|
||||
public _addViewToNativeVisualTree(child: View): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
// We don't need to put Page as visual child. Don't call super.
|
||||
public _removeViewFromNativeVisualTree(child: View): void {
|
||||
child._isAddedToNativeVisualTree = false;
|
||||
}
|
||||
|
||||
public _printFrameBackStack() {
|
||||
const length = this.backStack.length;
|
||||
let i = length - 1;
|
||||
console.log(`Frame Back Stack: `);
|
||||
while (i >= 0) {
|
||||
let backstackEntry = <BackstackEntry>this.backStack[i--];
|
||||
console.log(`\t${backstackEntry.resolvedPage}`);
|
||||
}
|
||||
}
|
||||
|
||||
public _backstackEntryTrace(b: BackstackEntry): string {
|
||||
let result = `${b.resolvedPage}`;
|
||||
|
||||
const backstackVisible = FrameBase._isEntryBackstackVisible(b);
|
||||
if (!backstackVisible) {
|
||||
result += ` | INVISIBLE`;
|
||||
}
|
||||
|
||||
if (b.entry.clearHistory) {
|
||||
result += ` | CLEAR HISTORY`;
|
||||
}
|
||||
|
||||
const animated = this._getIsAnimatedNavigation(b.entry);
|
||||
if (!animated) {
|
||||
result += ` | NOT ANIMATED`;
|
||||
}
|
||||
|
||||
const t = this._getNavigationTransition(b.entry);
|
||||
if (t) {
|
||||
result += ` | Transition[${JSON.stringify(t)}]`;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public _onLivesync(context?: ModuleContext): boolean {
|
||||
if (super._onLivesync(context)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fallback
|
||||
if (!context) {
|
||||
return this.legacyLivesync();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public _handleLivesync(context?: ModuleContext): boolean {
|
||||
if (super._handleLivesync(context)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle markup/script changes in currentPage
|
||||
if (this.currentPage &&
|
||||
viewMatchesModuleContext(this.currentPage, context, ["markup", "script"])) {
|
||||
|
||||
traceWrite(`Change Handled: Replacing page ${context.path}`, traceCategories.Livesync);
|
||||
|
||||
this.replacePage(context.path);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private legacyLivesync(): boolean {
|
||||
// Reset activity/window content when:
|
||||
// + Changes are not handled on View
|
||||
// + There is no ModuleContext
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`${this}._onLivesync()`, traceCategories.Livesync);
|
||||
}
|
||||
|
||||
if (!this._currentEntry || !this._currentEntry.entry) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const currentEntry = this._currentEntry.entry;
|
||||
const newEntry: NavigationEntry = {
|
||||
animated: false,
|
||||
clearHistory: true,
|
||||
context: currentEntry.context,
|
||||
create: currentEntry.create,
|
||||
moduleName: currentEntry.moduleName,
|
||||
backstackVisible: currentEntry.backstackVisible
|
||||
};
|
||||
|
||||
// If create returns the same page instance we can't recreate it.
|
||||
// Instead of navigation set activity content.
|
||||
// This could happen if current page was set in XML as a Page instance.
|
||||
if (newEntry.create) {
|
||||
const page = newEntry.create();
|
||||
if (page === this.currentPage) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
this.navigate(newEntry);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected replacePage(pagePath: string): void {
|
||||
const currentBackstackEntry = this._currentEntry;
|
||||
const contextModuleName = sanitizeModuleName(pagePath);
|
||||
|
||||
const newPage = <Page>Builder.createViewFromEntry({ moduleName: contextModuleName });
|
||||
const newBackstackEntry: BackstackEntry = {
|
||||
entry: currentBackstackEntry.entry,
|
||||
resolvedPage: newPage,
|
||||
navDepth: currentBackstackEntry.navDepth,
|
||||
fragmentTag: currentBackstackEntry.fragmentTag,
|
||||
frameId: currentBackstackEntry.frameId
|
||||
};
|
||||
|
||||
const navigationContext: NavigationContext = {
|
||||
entry: newBackstackEntry,
|
||||
isBackNavigation: false,
|
||||
navigationType: NavigationType.replace
|
||||
};
|
||||
|
||||
this._navigationQueue.push(navigationContext);
|
||||
this._processNextNavigationEntry();
|
||||
}
|
||||
}
|
||||
|
||||
export function getFrameById(id: string): FrameBase {
|
||||
console.log("getFrameById() is deprecated. Use Frame.getFrameById() instead.");
|
||||
|
||||
return FrameBase.getFrameById(id);
|
||||
}
|
||||
|
||||
export function topmost(): FrameBase {
|
||||
console.log("topmost() is deprecated. Use Frame.topmost() instead.");
|
||||
|
||||
return FrameBase.topmost();
|
||||
}
|
||||
|
||||
export function goBack(): boolean {
|
||||
console.log("goBack() is deprecated. Use Frame.goBack() instead.");
|
||||
|
||||
return FrameBase.goBack();
|
||||
}
|
||||
|
||||
export function _stack(): Array<FrameBase> {
|
||||
console.log("_stack() is deprecated. Use Frame._stack() instead.");
|
||||
|
||||
return FrameBase._stack();
|
||||
}
|
||||
|
||||
export const defaultPage = new Property<FrameBase, string>({
|
||||
name: "defaultPage", valueChanged: (frame: FrameBase, oldValue: string, newValue: string) => {
|
||||
frame.navigate({ moduleName: newValue });
|
||||
}
|
||||
});
|
||||
defaultPage.register(FrameBase);
|
||||
|
||||
export const actionBarVisibilityProperty = new Property<FrameBase, "auto" | "never" | "always">({ name: "actionBarVisibility", defaultValue: "auto", affectsLayout: isIOS });
|
||||
actionBarVisibilityProperty.register(FrameBase);
|
||||
50
nativescript-core/ui/frame/frame-stack.ts
Normal file
50
nativescript-core/ui/frame/frame-stack.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
// Types.
|
||||
import { FrameBase } from "./frame-common";
|
||||
|
||||
export let frameStack: Array<FrameBase> = [];
|
||||
|
||||
export function topmost(): FrameBase {
|
||||
if (frameStack.length > 0) {
|
||||
return frameStack[frameStack.length - 1];
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function _pushInFrameStack(frame: FrameBase): void {
|
||||
if (frame._isInFrameStack && frameStack[frameStack.length - 1] === frame) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (frame._isInFrameStack) {
|
||||
const indexOfFrame = frameStack.indexOf(frame);
|
||||
frameStack.splice(indexOfFrame, 1);
|
||||
}
|
||||
|
||||
frameStack.push(frame);
|
||||
frame._isInFrameStack = true;
|
||||
}
|
||||
|
||||
export function _popFromFrameStack(frame: FrameBase): void {
|
||||
if (!frame._isInFrameStack) {
|
||||
return;
|
||||
}
|
||||
|
||||
const top = topmost();
|
||||
if (top !== frame) {
|
||||
throw new Error("Cannot pop a Frame which is not at the top of the navigation stack.");
|
||||
}
|
||||
|
||||
frameStack.pop();
|
||||
frame._isInFrameStack = false;
|
||||
}
|
||||
|
||||
export function _removeFromFrameStack(frame: FrameBase): void {
|
||||
if (!frame._isInFrameStack) {
|
||||
return;
|
||||
}
|
||||
|
||||
const index = frameStack.indexOf(frame);
|
||||
frameStack.splice(index, 1);
|
||||
frame._isInFrameStack = false;
|
||||
}
|
||||
1383
nativescript-core/ui/frame/frame.android.ts
Normal file
1383
nativescript-core/ui/frame/frame.android.ts
Normal file
File diff suppressed because it is too large
Load Diff
498
nativescript-core/ui/frame/frame.d.ts
vendored
Normal file
498
nativescript-core/ui/frame/frame.d.ts
vendored
Normal file
@@ -0,0 +1,498 @@
|
||||
/**
|
||||
* Contains the Frame class, which represents the logical View unit that is responsible for navigation within an application.
|
||||
* @module "ui/frame"
|
||||
*/ /** */
|
||||
|
||||
import { NavigationType } from "./frame-common";
|
||||
import { Page, View, Observable, EventData } from "../page";
|
||||
import { Transition } from "../transition";
|
||||
|
||||
export * from "../page";
|
||||
|
||||
/**
|
||||
* Represents the logical View unit that is responsible for navigation within an application.
|
||||
* Nested frames are supported, enabling hierarchical navigation scenarios.
|
||||
*/
|
||||
export class Frame extends View {
|
||||
/**
|
||||
* Gets a frame by id.
|
||||
*/
|
||||
static getFrameById(id: string): Frame;
|
||||
|
||||
/**
|
||||
* Gets the topmost frame in the frames stack. An application will typically has one frame instance. Multiple frames handle nested (hierarchical) navigation scenarios.
|
||||
*/
|
||||
static topmost(): Frame;
|
||||
|
||||
/**
|
||||
* Navigates back using the navigation hierarchy (if any). Updates the Frame stack as needed.
|
||||
* This method will start from the topmost Frame and will recursively search for an instance that has the canGoBack operation available.
|
||||
*/
|
||||
static goBack();
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
static reloadPage(context?: ModuleContext): void;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
static _stack(): Array<Frame>;
|
||||
|
||||
/**
|
||||
* Navigates to the previous entry (if any) in the back stack.
|
||||
* @param to The backstack entry to navigate back to.
|
||||
*/
|
||||
goBack(to?: BackstackEntry);
|
||||
|
||||
/**
|
||||
* Checks whether the goBack operation is available.
|
||||
*/
|
||||
canGoBack(): boolean;
|
||||
|
||||
/**
|
||||
* Navigates to a Page instance as described by the module name.
|
||||
* This method will require the module and will check for a Page property in the exports of the module.
|
||||
* @param pageModuleName The name of the module to require starting from the application root.
|
||||
* For example if you want to navigate to page called "myPage.js" in a folder called "subFolder" and your root folder is "app" you can call navigate method like this:
|
||||
* const frames = require("tns-core-modules/ui/frame");
|
||||
* frames.topmost().navigate("app/subFolder/myPage");
|
||||
*/
|
||||
navigate(pageModuleName: string);
|
||||
|
||||
/**
|
||||
* Creates a new Page instance using the provided callback and navigates to that Page.
|
||||
* @param create The function to be used to create the new Page instance.
|
||||
*/
|
||||
navigate(create: () => Page);
|
||||
|
||||
/**
|
||||
* Navigates to a Page resolved by the provided NavigationEntry object.
|
||||
* Since there are a couple of ways to specify a Page instance through an entry, there is a resolution priority:
|
||||
* 1. entry.moduleName
|
||||
* 2. entry.create()
|
||||
* @param entry The NavigationEntry instance.
|
||||
*/
|
||||
navigate(entry: NavigationEntry);
|
||||
|
||||
/**
|
||||
* Used to control the visibility the Navigation Bar in iOS and the Action Bar in Android.
|
||||
*/
|
||||
public actionBarVisibility: "auto" | "never" | "always";
|
||||
|
||||
/**
|
||||
* Gets the back stack of this instance.
|
||||
*/
|
||||
backStack: Array<BackstackEntry>;
|
||||
|
||||
/**
|
||||
* Gets the Page instance the Frame is currently navigated to.
|
||||
*/
|
||||
currentPage: Page;
|
||||
|
||||
/**
|
||||
* Gets the NavigationEntry instance the Frame is currently navigated to.
|
||||
*/
|
||||
currentEntry: NavigationEntry;
|
||||
|
||||
/**
|
||||
* Gets or sets if navigation transitions should be animated.
|
||||
*/
|
||||
animated: boolean;
|
||||
|
||||
/**
|
||||
* Gets or sets the default navigation transition for this frame.
|
||||
*/
|
||||
transition: NavigationTransition;
|
||||
|
||||
/**
|
||||
* Gets or sets if navigation transitions should be animated globally.
|
||||
*/
|
||||
static defaultAnimatedNavigation: boolean;
|
||||
|
||||
/**
|
||||
* Gets or sets the default NavigationTransition for all frames across the app.
|
||||
*/
|
||||
static defaultTransition: NavigationTransition;
|
||||
|
||||
/**
|
||||
* Gets the AndroidFrame object that represents the Android-specific APIs for this Frame. Valid when running on Android OS.
|
||||
*/
|
||||
android: AndroidFrame;
|
||||
|
||||
/**
|
||||
* Gets the iOSFrame object that represents the iOS-specific APIs for this Frame. Valid when running on iOS.
|
||||
*/
|
||||
ios: iOSFrame;
|
||||
|
||||
//@private
|
||||
/**
|
||||
* @private
|
||||
* @param entry to check
|
||||
*/
|
||||
isCurrent(entry: BackstackEntry): boolean;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param entry to set as current
|
||||
* @param navigationType
|
||||
*/
|
||||
setCurrent(entry: BackstackEntry, navigationType: NavigationType): void;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
navigationQueueIsEmpty(): boolean;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
navigationBarHeight: number;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_currentEntry: BackstackEntry;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_executingContext: NavigationContext;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_processNavigationQueue(page: Page);
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_getIsAnimatedNavigation(entry: NavigationEntry): boolean;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_getNavigationTransition(entry: NavigationEntry): NavigationTransition;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_updateActionBar(page?: Page, disableNavBarAnimation?: boolean);
|
||||
/**
|
||||
* @private
|
||||
* @param navigationContext
|
||||
*/
|
||||
public performNavigation(navigationContext: NavigationContext): void;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_getNavBarVisible(page: Page): boolean;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_findEntryForTag(fragmentTag: string): BackstackEntry;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_updateBackstack(entry: BackstackEntry, navigationType: NavigationType): void;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_pushInFrameStack();
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_pushInFrameStackRecursive();
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_removeFromFrameStack();
|
||||
//@endprivate
|
||||
|
||||
/**
|
||||
* A basic method signature to hook an event listener (shortcut alias to the addEventListener method).
|
||||
* @param eventNames - String corresponding to events (e.g. "propertyChange"). Optionally could be used more events separated by `,` (e.g. "propertyChange", "change").
|
||||
* @param callback - Callback function which will be executed when event is raised.
|
||||
* @param thisArg - An optional parameter which will be used as `this` context for callback execution.
|
||||
*/
|
||||
on(eventNames: string, callback: (args: EventData) => void, thisArg?: any);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the extended androidx.fragment.app.Fragment class to the Frame and navigation routine. An instance of this class will be created to represent the Page currently visible on the srceen. This method is available only for the Android platform.
|
||||
*/
|
||||
export function setFragmentClass(clazz: any): void;
|
||||
|
||||
/**
|
||||
* @deprecated Use Frame.getFrameById() instead.
|
||||
*
|
||||
* Gets a frame by id.
|
||||
*/
|
||||
export function getFrameById(id: string): Frame;
|
||||
|
||||
/**
|
||||
* @deprecated Use Frame.topmost() instead.
|
||||
*
|
||||
* Gets the topmost frame in the frames stack. An application will typically has one frame instance. Multiple frames handle nested (hierarchical) navigation scenarios.
|
||||
*/
|
||||
export function topmost(): Frame;
|
||||
|
||||
/**
|
||||
* @deprecated Use Frame.goBack() instead.
|
||||
*
|
||||
* Navigates back using the navigation hierarchy (if any). Updates the Frame stack as needed.
|
||||
* This method will start from the topmost Frame and will recursively search for an instance that has the canGoBack operation available.
|
||||
*/
|
||||
export function goBack();
|
||||
|
||||
//@private
|
||||
/**
|
||||
* @deprecated Use Frame._stack() instead.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
export function _stack(): Array<Frame>;
|
||||
//@endprivate
|
||||
|
||||
/**
|
||||
* Represents an entry to be used to create a view or load it form file
|
||||
*/
|
||||
export interface ViewEntry {
|
||||
/**
|
||||
* The name of the module containing the View instance to load. Optional.
|
||||
*/
|
||||
moduleName?: string;
|
||||
|
||||
/**
|
||||
* A function used to create the View instance. Optional.
|
||||
*/
|
||||
create?: () => View;
|
||||
}
|
||||
/**
|
||||
* Represents an entry in passed to navigate method.
|
||||
*/
|
||||
export interface NavigationEntry extends ViewEntry {
|
||||
/**
|
||||
* An object passed to the onNavigatedTo callback of the Page. Typically this is used to pass some data among pages. Optional.
|
||||
*/
|
||||
context?: any;
|
||||
|
||||
/**
|
||||
* An object to become the binding context of the page navigating to. Optional.
|
||||
*/
|
||||
bindingContext?: any;
|
||||
|
||||
/**
|
||||
* True to navigate to the new Page using animated transitions, false otherwise.
|
||||
*/
|
||||
animated?: boolean;
|
||||
|
||||
/**
|
||||
* Specifies an optional navigation transition for all platforms. If not specified, the default platform transition will be used.
|
||||
*/
|
||||
transition?: NavigationTransition;
|
||||
|
||||
/**
|
||||
* Specifies an optional navigation transition for iOS. If not specified, the default platform transition will be used.
|
||||
*/
|
||||
transitioniOS?: NavigationTransition;
|
||||
|
||||
/**
|
||||
* Specifies an optional navigation transition for Android. If not specified, the default platform transition will be used.
|
||||
*/
|
||||
transitionAndroid?: NavigationTransition;
|
||||
|
||||
/**
|
||||
* True to record the navigation in the backstack, false otherwise.
|
||||
* If the parameter is set to false then the Page will be displayed but once navigated from it will not be able to be navigated back to.
|
||||
*/
|
||||
backstackVisible?: boolean;
|
||||
|
||||
/**
|
||||
* True to clear the navigation history, false otherwise. Very useful when navigating away from login pages.
|
||||
*/
|
||||
clearHistory?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a context passed to navigation methods.
|
||||
*/
|
||||
export interface NavigationContext {
|
||||
entry: BackstackEntry;
|
||||
isBackNavigation: boolean;
|
||||
navigationType: NavigationType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an object specifying a page navigation transition.
|
||||
*/
|
||||
export interface NavigationTransition {
|
||||
/**
|
||||
* Can be one of the built-in transitions:
|
||||
* - curl (same as curlUp) (iOS only)
|
||||
* - curlUp (iOS only)
|
||||
* - curlDown (iOS only)
|
||||
* - explode (Android Lollipop(21) and up only)
|
||||
* - fade
|
||||
* - flip (same as flipRight)
|
||||
* - flipRight
|
||||
* - flipLeft
|
||||
* - slide (same as slideLeft)
|
||||
* - slideLeft
|
||||
* - slideRight
|
||||
* - slideTop
|
||||
* - slideBottom
|
||||
*/
|
||||
name?: string;
|
||||
|
||||
/**
|
||||
* An user-defined instance of the "ui/transition".Transition class.
|
||||
*/
|
||||
instance?: Transition;
|
||||
|
||||
/**
|
||||
* The length of the transition in milliseconds. If you do not specify this, the default platform transition duration will be used.
|
||||
*/
|
||||
duration?: number;
|
||||
|
||||
/**
|
||||
* An optional transition animation curve. Possible values are contained in the [AnimationCurve enumeration](https://docs.nativescript.org/api-reference/modules/_ui_enums_.animationcurve.html).
|
||||
* Alternatively, you can pass an instance of type UIViewAnimationCurve for iOS or android.animation.TimeInterpolator for Android.
|
||||
*/
|
||||
curve?: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an entry in the back stack of a Frame object.
|
||||
*/
|
||||
export interface BackstackEntry {
|
||||
entry: NavigationEntry;
|
||||
resolvedPage: Page;
|
||||
|
||||
//@private
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
navDepth: number;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
fragmentTag: string;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
fragment?: any;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
viewSavedState?: any;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
frameId?: number;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
recreated?: boolean;
|
||||
//@endprivate
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the Android-specific Frame object, aggregated within the common Frame one.
|
||||
* In Android there are two types of navigation - using new Activity instances or using Fragments within the main Activity.
|
||||
* To start a new Activity, a new Frame instance should be created and navigated to the desired Page.
|
||||
*/
|
||||
export interface AndroidFrame extends Observable {
|
||||
/**
|
||||
* Gets the native [android ViewGroup](http://developer.android.com/reference/android/view/ViewGroup.html) instance that represents the root layout part of the Frame.
|
||||
*/
|
||||
rootViewGroup: any /* android.view.ViewGroup */;
|
||||
|
||||
/**
|
||||
* Gets the native [android Activity](http://developer.android.com/reference/android/app/Activity.html) instance associated with this Frame. In case of nested Frame objects, this property points to the activity of the root Frame.
|
||||
*/
|
||||
activity: any /* androidx.appcompat.app.AppCompatActivity */;
|
||||
|
||||
/**
|
||||
* Gets the current (foreground) activity for the application. This property will recursively traverse all existing Frame objects and check for own Activity property.
|
||||
*/
|
||||
currentActivity: any /* androidx.appcompat.app.AppCompatActivity */;
|
||||
|
||||
/**
|
||||
* Gets the actionBar property of the currentActivity.
|
||||
*/
|
||||
actionBar: any /* android.app.ActionBar */;
|
||||
|
||||
/**
|
||||
* Determines whether the Activity associated with this Frame will display an action bar or not.
|
||||
*/
|
||||
showActionBar: boolean;
|
||||
|
||||
/**
|
||||
* Finds the native androidx.fragment.app.Fragment instance created for the specified Page.
|
||||
* @param page The Page instance to search for.
|
||||
*/
|
||||
fragmentForPage(entry: BackstackEntry): any;
|
||||
}
|
||||
|
||||
export interface AndroidActivityCallbacks {
|
||||
getRootView(): View;
|
||||
resetActivityContent(activity: any): void;
|
||||
|
||||
onCreate(activity: any, savedInstanceState: any, intent: any, superFunc: Function): void;
|
||||
onSaveInstanceState(activity: any, outState: any, superFunc: Function): void;
|
||||
onStart(activity: any, superFunc: Function): void;
|
||||
onStop(activity: any, superFunc: Function): void;
|
||||
onPostResume(activity: any, superFunc: Function): void;
|
||||
onDestroy(activity: any, superFunc: Function): void;
|
||||
onBackPressed(activity: any, superFunc: Function): void;
|
||||
onRequestPermissionsResult(activity: any, requestCode: number, permissions: Array<String>, grantResults: Array<number>, superFunc: Function): void;
|
||||
onActivityResult(activity: any, requestCode: number, resultCode: number, data: any, superFunc: Function);
|
||||
onNewIntent(activity: any, intent: any, superSetIntentFunc: Function, superFunc: Function): void;
|
||||
}
|
||||
|
||||
export interface AndroidFragmentCallbacks {
|
||||
onHiddenChanged(fragment: any, hidden: boolean, superFunc: Function): void;
|
||||
onCreateAnimator(fragment: any, transit: number, enter: boolean, nextAnim: number, superFunc: Function): any;
|
||||
onCreate(fragment: any, savedInstanceState: any, superFunc: Function): void;
|
||||
onCreateView(fragment: any, inflater: any, container: any, savedInstanceState: any, superFunc: Function): any;
|
||||
onSaveInstanceState(fragment: any, outState: any, superFunc: Function): void;
|
||||
onDestroyView(fragment: any, superFunc: Function): void;
|
||||
onDestroy(fragment: any, superFunc: Function): void;
|
||||
onPause(fragment: any, superFunc: Function): void;
|
||||
onStop(fragment: any, superFunc: Function): void;
|
||||
toStringOverride(fragment: any, superFunc: Function): string;
|
||||
}
|
||||
|
||||
/* tslint:disable */
|
||||
/**
|
||||
* Represents the iOS-specific Frame object, aggregated within the common Frame one.
|
||||
* In iOS the native controller, associated with a Frame object is UINavigationController.
|
||||
* The navigation controller will automatically hide/show its navigation bar depending on the back stack of the Frame.
|
||||
*/
|
||||
export interface iOSFrame {
|
||||
/**
|
||||
* Gets the native [UINavigationController](https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UINavigationController_Class/index.html) instance associated with this Frame.
|
||||
*/
|
||||
controller: any /* UINavigationController */;
|
||||
|
||||
/**
|
||||
* Gets or sets the visibility of navigationBar.
|
||||
* Use NavBarVisibility enumeration - auto, never, always
|
||||
*/
|
||||
navBarVisibility: "auto" | "never" | "always";
|
||||
|
||||
//@private
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_disableNavBarAnimation: boolean;
|
||||
//@endprivate
|
||||
}
|
||||
|
||||
export function setActivityCallbacks(activity: any /*androidx.appcompat.app.AppCompatActivity*/): void;
|
||||
//@private
|
||||
/**
|
||||
* @deprecated Use Frame.reloadPage() instead.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
export function reloadPage(context?: ModuleContext): void;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
export function setFragmentCallbacks(fragment: any /*androidx.fragment.app.Fragment*/): void;
|
||||
//@endprivate
|
||||
661
nativescript-core/ui/frame/frame.ios.ts
Normal file
661
nativescript-core/ui/frame/frame.ios.ts
Normal file
@@ -0,0 +1,661 @@
|
||||
// Definitions.
|
||||
import {
|
||||
iOSFrame as iOSFrameDefinition, BackstackEntry, NavigationTransition
|
||||
} from ".";
|
||||
import { ios as iosView } from "../core/view";
|
||||
import { Page } from "../page";
|
||||
import { profile } from "../../profiling";
|
||||
|
||||
//Types.
|
||||
import {
|
||||
FrameBase, View, isCategorySet, layout,
|
||||
NavigationType, traceCategories, traceEnabled, traceWrite
|
||||
} from "./frame-common";
|
||||
import { _createIOSAnimatedTransitioning } from "./fragment.transitions";
|
||||
|
||||
import * as utils from "../../utils/utils";
|
||||
|
||||
export * from "./frame-common";
|
||||
|
||||
const majorVersion = utils.ios.MajorVersion;
|
||||
|
||||
const ENTRY = "_entry";
|
||||
const DELEGATE = "_delegate";
|
||||
const NAV_DEPTH = "_navDepth";
|
||||
const TRANSITION = "_transition";
|
||||
const NON_ANIMATED_TRANSITION = "non-animated";
|
||||
const HMR_REPLACE_TRANSITION = "fade";
|
||||
|
||||
let navDepth = -1;
|
||||
|
||||
export class Frame extends FrameBase {
|
||||
public viewController: UINavigationControllerImpl;
|
||||
public _animatedDelegate = <UINavigationControllerDelegate>UINavigationControllerAnimatedDelegate.new();
|
||||
public _ios: iOSFrame;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._ios = new iOSFrame(this);
|
||||
this.viewController = this._ios.controller;
|
||||
}
|
||||
|
||||
createNativeView() {
|
||||
return this.viewController.view;
|
||||
}
|
||||
|
||||
public disposeNativeView() {
|
||||
this._removeFromFrameStack();
|
||||
this.viewController = null;
|
||||
this._ios.controller = null;
|
||||
super.disposeNativeView();
|
||||
}
|
||||
|
||||
public get ios(): iOSFrame {
|
||||
return this._ios;
|
||||
}
|
||||
|
||||
public setCurrent(entry: BackstackEntry, navigationType: NavigationType): void {
|
||||
const current = this._currentEntry;
|
||||
const currentEntryChanged = current !== entry;
|
||||
if (currentEntryChanged) {
|
||||
this._updateBackstack(entry, navigationType);
|
||||
|
||||
super.setCurrent(entry, navigationType);
|
||||
}
|
||||
}
|
||||
|
||||
@profile
|
||||
public _navigateCore(backstackEntry: BackstackEntry) {
|
||||
super._navigateCore(backstackEntry);
|
||||
|
||||
let viewController: UIViewController = backstackEntry.resolvedPage.ios;
|
||||
if (!viewController) {
|
||||
throw new Error("Required page does not have a viewController created.");
|
||||
}
|
||||
|
||||
let clearHistory = backstackEntry.entry.clearHistory;
|
||||
if (clearHistory) {
|
||||
navDepth = -1;
|
||||
}
|
||||
|
||||
const isReplace = this._executingContext && this._executingContext.navigationType === NavigationType.replace;
|
||||
if (!isReplace) {
|
||||
navDepth++;
|
||||
}
|
||||
|
||||
let navigationTransition: NavigationTransition;
|
||||
let animated = this.currentPage ? this._getIsAnimatedNavigation(backstackEntry.entry) : false;
|
||||
if (isReplace) {
|
||||
animated = true;
|
||||
navigationTransition = { name: HMR_REPLACE_TRANSITION, duration: 100 };
|
||||
viewController[TRANSITION] = navigationTransition;
|
||||
} else if (animated) {
|
||||
navigationTransition = this._getNavigationTransition(backstackEntry.entry);
|
||||
if (navigationTransition) {
|
||||
viewController[TRANSITION] = navigationTransition;
|
||||
}
|
||||
} else {
|
||||
//https://github.com/NativeScript/NativeScript/issues/1787
|
||||
viewController[TRANSITION] = { name: NON_ANIMATED_TRANSITION };
|
||||
}
|
||||
|
||||
let nativeTransition = _getNativeTransition(navigationTransition, true);
|
||||
if (!nativeTransition && navigationTransition) {
|
||||
this._ios.controller.delegate = this._animatedDelegate;
|
||||
viewController[DELEGATE] = this._animatedDelegate;
|
||||
}
|
||||
else {
|
||||
viewController[DELEGATE] = null;
|
||||
this._ios.controller.delegate = null;
|
||||
}
|
||||
|
||||
backstackEntry[NAV_DEPTH] = navDepth;
|
||||
viewController[ENTRY] = backstackEntry;
|
||||
|
||||
if (!animated && majorVersion > 10) {
|
||||
// Reset back button title before pushing view controller to prevent
|
||||
// displaying default 'back' title (when NavigaitonButton custom title is set).
|
||||
let barButtonItem = UIBarButtonItem.alloc().initWithTitleStyleTargetAction("", UIBarButtonItemStyle.Plain, null, null);
|
||||
viewController.navigationItem.backBarButtonItem = barButtonItem;
|
||||
}
|
||||
|
||||
// First navigation.
|
||||
if (!this._currentEntry) {
|
||||
// Update action-bar with disabled animations before the initial navigation.
|
||||
this._updateActionBar(backstackEntry.resolvedPage, true);
|
||||
this._ios.controller.pushViewControllerAnimated(viewController, animated);
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`${this}.pushViewControllerAnimated(${viewController}, ${animated}); depth = ${navDepth}`, traceCategories.Navigation);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// We should clear the entire history.
|
||||
if (clearHistory) {
|
||||
viewController.navigationItem.hidesBackButton = true;
|
||||
const newControllers = NSMutableArray.alloc().initWithCapacity(1);
|
||||
newControllers.addObject(viewController);
|
||||
|
||||
// Mark all previous ViewControllers as cleared
|
||||
const oldControllers = this._ios.controller.viewControllers;
|
||||
for (let i = 0; i < oldControllers.count; i++) {
|
||||
(<any>oldControllers.objectAtIndex(i)).isBackstackCleared = true;
|
||||
}
|
||||
|
||||
this._ios.controller.setViewControllersAnimated(newControllers, animated);
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`${this}.setViewControllersAnimated([${viewController}], ${animated}); depth = ${navDepth}`, traceCategories.Navigation);
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
// We should hide the current entry from the back stack.
|
||||
// This is the case for HMR when NavigationType.replace.
|
||||
if (!Frame._isEntryBackstackVisible(this._currentEntry) || isReplace) {
|
||||
let newControllers = NSMutableArray.alloc<UIViewController>().initWithArray(this._ios.controller.viewControllers);
|
||||
if (newControllers.count === 0) {
|
||||
throw new Error("Wrong controllers count.");
|
||||
}
|
||||
|
||||
// the code below fixes a phantom animation that appears on the Back button in this case
|
||||
// TODO: investigate why the animation happens at first place before working around it
|
||||
viewController.navigationItem.hidesBackButton = this.backStack.length === 0;
|
||||
|
||||
// swap the top entry with the new one
|
||||
const skippedNavController = newControllers.lastObject;
|
||||
(<any>skippedNavController).isBackstackSkipped = true;
|
||||
newControllers.removeLastObject();
|
||||
newControllers.addObject(viewController);
|
||||
|
||||
// replace the controllers instead of pushing directly
|
||||
this._ios.controller.setViewControllersAnimated(newControllers, animated);
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`${this}.setViewControllersAnimated([originalControllers - lastController + ${viewController}], ${animated}); depth = ${navDepth}`, traceCategories.Navigation);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// General case.
|
||||
this._ios.controller.pushViewControllerAnimated(viewController, animated);
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`${this}.pushViewControllerAnimated(${viewController}, ${animated}); depth = ${navDepth}`, traceCategories.Navigation);
|
||||
}
|
||||
}
|
||||
|
||||
public _goBackCore(backstackEntry: BackstackEntry) {
|
||||
super._goBackCore(backstackEntry);
|
||||
navDepth = backstackEntry[NAV_DEPTH];
|
||||
|
||||
let controller = backstackEntry.resolvedPage.ios;
|
||||
let animated = this._currentEntry ? this._getIsAnimatedNavigation(this._currentEntry.entry) : false;
|
||||
|
||||
this._updateActionBar(backstackEntry.resolvedPage);
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`${this}.popToViewControllerAnimated(${controller}, ${animated}); depth = ${navDepth}`, traceCategories.Navigation);
|
||||
}
|
||||
|
||||
this._ios.controller.popToViewControllerAnimated(controller, animated);
|
||||
}
|
||||
|
||||
public _updateActionBar(page?: Page, disableNavBarAnimation: boolean = false): void {
|
||||
super._updateActionBar(page);
|
||||
|
||||
if (page && this.currentPage && this.currentPage.modal === page) {
|
||||
return;
|
||||
}
|
||||
|
||||
page = page || this.currentPage;
|
||||
let newValue = this._getNavBarVisible(page);
|
||||
let disableNavBarAnimationCache = this._ios._disableNavBarAnimation;
|
||||
|
||||
if (disableNavBarAnimation) {
|
||||
this._ios._disableNavBarAnimation = true;
|
||||
}
|
||||
|
||||
this._ios.showNavigationBar = newValue;
|
||||
|
||||
if (disableNavBarAnimation) {
|
||||
this._ios._disableNavBarAnimation = disableNavBarAnimationCache;
|
||||
}
|
||||
|
||||
if (this._ios.controller.navigationBar) {
|
||||
this._ios.controller.navigationBar.userInteractionEnabled = this.navigationQueueIsEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
public _getNavBarVisible(page: Page): boolean {
|
||||
switch (this.actionBarVisibility) {
|
||||
case "always":
|
||||
return true;
|
||||
|
||||
case "never":
|
||||
return false;
|
||||
|
||||
case "auto":
|
||||
switch (this._ios.navBarVisibility) {
|
||||
case "always":
|
||||
return true;
|
||||
|
||||
case "never":
|
||||
return false;
|
||||
|
||||
case "auto":
|
||||
let newValue: boolean;
|
||||
|
||||
if (page && page.actionBarHidden !== undefined) {
|
||||
newValue = !page.actionBarHidden;
|
||||
}
|
||||
else {
|
||||
newValue = this.ios.controller.viewControllers.count > 1 || (page && page.actionBar && !page.actionBar._isEmpty());
|
||||
}
|
||||
|
||||
newValue = !!newValue;
|
||||
|
||||
return newValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static get defaultAnimatedNavigation(): boolean {
|
||||
return FrameBase.defaultAnimatedNavigation;
|
||||
}
|
||||
public static set defaultAnimatedNavigation(value: boolean) {
|
||||
FrameBase.defaultAnimatedNavigation = value;
|
||||
}
|
||||
|
||||
public static get defaultTransition(): NavigationTransition {
|
||||
return FrameBase.defaultTransition;
|
||||
}
|
||||
public static set defaultTransition(value: NavigationTransition) {
|
||||
FrameBase.defaultTransition = value;
|
||||
}
|
||||
|
||||
public onMeasure(widthMeasureSpec: number, heightMeasureSpec: number): void {
|
||||
const width = layout.getMeasureSpecSize(widthMeasureSpec);
|
||||
const widthMode = layout.getMeasureSpecMode(widthMeasureSpec);
|
||||
|
||||
const height = layout.getMeasureSpecSize(heightMeasureSpec);
|
||||
const heightMode = layout.getMeasureSpecMode(heightMeasureSpec);
|
||||
|
||||
const widthAndState = View.resolveSizeAndState(width, width, widthMode, 0);
|
||||
const heightAndState = View.resolveSizeAndState(height, height, heightMode, 0);
|
||||
|
||||
this.setMeasuredDimension(widthAndState, heightAndState);
|
||||
}
|
||||
|
||||
public layoutNativeView(left: number, top: number, right: number, bottom: number): void {
|
||||
//
|
||||
}
|
||||
|
||||
public _setNativeViewFrame(nativeView: UIView, frame: CGRect) {
|
||||
//
|
||||
}
|
||||
|
||||
public _onNavigatingTo(backstackEntry: BackstackEntry, isBack: boolean) {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
let transitionDelegates = new Array<TransitionDelegate>();
|
||||
|
||||
class TransitionDelegate extends NSObject {
|
||||
private _id: string;
|
||||
|
||||
public static initWithOwnerId(id: string): TransitionDelegate {
|
||||
let delegate = <TransitionDelegate>TransitionDelegate.new();
|
||||
delegate._id = id;
|
||||
transitionDelegates.push(delegate);
|
||||
|
||||
return delegate;
|
||||
}
|
||||
|
||||
public animationWillStart(animationID: string, context: any): void {
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`START ${this._id}`, traceCategories.Transition);
|
||||
}
|
||||
}
|
||||
|
||||
public animationDidStop(animationID: string, finished: boolean, context: any): void {
|
||||
if (finished) {
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`END ${this._id}`, traceCategories.Transition);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`CANCEL ${this._id}`, traceCategories.Transition);
|
||||
}
|
||||
}
|
||||
|
||||
let index = transitionDelegates.indexOf(this);
|
||||
if (index > -1) {
|
||||
transitionDelegates.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public static ObjCExposedMethods = {
|
||||
"animationWillStart": { returns: interop.types.void, params: [NSString, NSObject] },
|
||||
"animationDidStop": { returns: interop.types.void, params: [NSString, NSNumber, NSObject] }
|
||||
};
|
||||
}
|
||||
|
||||
const _defaultTransitionDuration = 0.35;
|
||||
|
||||
class UINavigationControllerAnimatedDelegate extends NSObject implements UINavigationControllerDelegate {
|
||||
public static ObjCProtocols = [UINavigationControllerDelegate];
|
||||
|
||||
navigationControllerAnimationControllerForOperationFromViewControllerToViewController(
|
||||
navigationController: UINavigationController,
|
||||
operation: number,
|
||||
fromVC: UIViewController,
|
||||
toVC: UIViewController): UIViewControllerAnimatedTransitioning {
|
||||
|
||||
let viewController: UIViewController;
|
||||
switch (operation) {
|
||||
case UINavigationControllerOperation.Push:
|
||||
viewController = toVC;
|
||||
break;
|
||||
case UINavigationControllerOperation.Pop:
|
||||
viewController = fromVC;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!viewController) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let navigationTransition = <NavigationTransition>viewController[TRANSITION];
|
||||
if (!navigationTransition) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`UINavigationControllerImpl.navigationControllerAnimationControllerForOperationFromViewControllerToViewController(${operation}, ${fromVC}, ${toVC}), transition: ${JSON.stringify(navigationTransition)}`, traceCategories.NativeLifecycle);
|
||||
}
|
||||
|
||||
let curve = _getNativeCurve(navigationTransition);
|
||||
let animationController = _createIOSAnimatedTransitioning(navigationTransition, curve, operation, fromVC, toVC);
|
||||
|
||||
return animationController;
|
||||
}
|
||||
}
|
||||
|
||||
class UINavigationControllerImpl extends UINavigationController {
|
||||
private _owner: WeakRef<Frame>;
|
||||
|
||||
public static initWithOwner(owner: WeakRef<Frame>): UINavigationControllerImpl {
|
||||
let controller = <UINavigationControllerImpl>UINavigationControllerImpl.new();
|
||||
controller._owner = owner;
|
||||
|
||||
return controller;
|
||||
}
|
||||
|
||||
get owner(): Frame {
|
||||
return this._owner.get();
|
||||
}
|
||||
|
||||
@profile
|
||||
public viewWillAppear(animated: boolean): void {
|
||||
super.viewWillAppear(animated);
|
||||
const owner = this._owner.get();
|
||||
if (owner && !owner.isLoaded && !owner.parent) {
|
||||
owner.callLoaded();
|
||||
}
|
||||
}
|
||||
|
||||
@profile
|
||||
public viewDidDisappear(animated: boolean): void {
|
||||
super.viewDidDisappear(animated);
|
||||
const owner = this._owner.get();
|
||||
if (owner && owner.isLoaded && !owner.parent && !this.presentedViewController) {
|
||||
owner.callUnloaded();
|
||||
owner._tearDownUI(true);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private animateWithDuration(navigationTransition: NavigationTransition,
|
||||
nativeTransition: UIViewAnimationTransition,
|
||||
transitionType: string,
|
||||
baseCallback: Function): void {
|
||||
|
||||
let duration = navigationTransition.duration ? navigationTransition.duration / 1000 : _defaultTransitionDuration;
|
||||
let curve = _getNativeCurve(navigationTransition);
|
||||
|
||||
let transitionTraced = isCategorySet(traceCategories.Transition);
|
||||
let transitionDelegate: TransitionDelegate;
|
||||
if (transitionTraced) {
|
||||
let id = _getTransitionId(nativeTransition, transitionType);
|
||||
transitionDelegate = TransitionDelegate.initWithOwnerId(id);
|
||||
}
|
||||
|
||||
UIView.animateWithDurationAnimations(duration, () => {
|
||||
if (transitionTraced) {
|
||||
UIView.setAnimationDelegate(transitionDelegate);
|
||||
}
|
||||
|
||||
UIView.setAnimationWillStartSelector("animationWillStart");
|
||||
UIView.setAnimationDidStopSelector("animationDidStop");
|
||||
UIView.setAnimationCurve(curve);
|
||||
baseCallback();
|
||||
UIView.setAnimationTransitionForViewCache(nativeTransition, this.view, true);
|
||||
});
|
||||
}
|
||||
|
||||
@profile
|
||||
public pushViewControllerAnimated(viewController: UIViewController, animated: boolean): void {
|
||||
let navigationTransition = <NavigationTransition>viewController[TRANSITION];
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`UINavigationControllerImpl.pushViewControllerAnimated(${viewController}, ${animated}); transition: ${JSON.stringify(navigationTransition)}`, traceCategories.NativeLifecycle);
|
||||
}
|
||||
|
||||
let nativeTransition = _getNativeTransition(navigationTransition, true);
|
||||
if (!animated || !navigationTransition || !nativeTransition) {
|
||||
super.pushViewControllerAnimated(viewController, animated);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.animateWithDuration(navigationTransition, nativeTransition, "push", () => {
|
||||
super.pushViewControllerAnimated(viewController, false);
|
||||
});
|
||||
}
|
||||
|
||||
@profile
|
||||
public setViewControllersAnimated(viewControllers: NSArray<any>, animated: boolean): void {
|
||||
let viewController = viewControllers.lastObject;
|
||||
let navigationTransition = <NavigationTransition>viewController[TRANSITION];
|
||||
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`UINavigationControllerImpl.setViewControllersAnimated(${viewControllers}, ${animated}); transition: ${JSON.stringify(navigationTransition)}`, traceCategories.NativeLifecycle);
|
||||
}
|
||||
|
||||
let nativeTransition = _getNativeTransition(navigationTransition, true);
|
||||
if (!animated || !navigationTransition || !nativeTransition) {
|
||||
super.setViewControllersAnimated(viewControllers, animated);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.animateWithDuration(navigationTransition, nativeTransition, "set", () => {
|
||||
super.setViewControllersAnimated(viewControllers, false);
|
||||
});
|
||||
}
|
||||
|
||||
public popViewControllerAnimated(animated: boolean): UIViewController {
|
||||
let lastViewController = this.viewControllers.lastObject;
|
||||
let navigationTransition = <NavigationTransition>lastViewController[TRANSITION];
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`UINavigationControllerImpl.popViewControllerAnimated(${animated}); transition: ${JSON.stringify(navigationTransition)}`, traceCategories.NativeLifecycle);
|
||||
}
|
||||
|
||||
if (navigationTransition && navigationTransition.name === NON_ANIMATED_TRANSITION) {
|
||||
//https://github.com/NativeScript/NativeScript/issues/1787
|
||||
return super.popViewControllerAnimated(false);
|
||||
}
|
||||
|
||||
let nativeTransition = _getNativeTransition(navigationTransition, false);
|
||||
if (!animated || !navigationTransition || !nativeTransition) {
|
||||
return super.popViewControllerAnimated(animated);
|
||||
}
|
||||
|
||||
this.animateWithDuration(navigationTransition, nativeTransition, "pop", () => {
|
||||
super.popViewControllerAnimated(false);
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public popToViewControllerAnimated(viewController: UIViewController, animated: boolean): NSArray<UIViewController> {
|
||||
let lastViewController = this.viewControllers.lastObject;
|
||||
let navigationTransition = <NavigationTransition>lastViewController[TRANSITION];
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`UINavigationControllerImpl.popToViewControllerAnimated(${viewController}, ${animated}); transition: ${JSON.stringify(navigationTransition)}`, traceCategories.NativeLifecycle);
|
||||
}
|
||||
|
||||
if (navigationTransition && navigationTransition.name === NON_ANIMATED_TRANSITION) {
|
||||
//https://github.com/NativeScript/NativeScript/issues/1787
|
||||
return super.popToViewControllerAnimated(viewController, false);
|
||||
}
|
||||
|
||||
let nativeTransition = _getNativeTransition(navigationTransition, false);
|
||||
if (!animated || !navigationTransition || !nativeTransition) {
|
||||
return super.popToViewControllerAnimated(viewController, animated);
|
||||
}
|
||||
|
||||
this.animateWithDuration(navigationTransition, nativeTransition, "popTo", () => {
|
||||
super.popToViewControllerAnimated(viewController, false);
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Mind implementation for other controllers
|
||||
public traitCollectionDidChange(previousTraitCollection: UITraitCollection): void {
|
||||
super.traitCollectionDidChange(previousTraitCollection);
|
||||
|
||||
if (majorVersion >= 13) {
|
||||
const owner = this._owner.get();
|
||||
if (owner && this.traitCollection.hasDifferentColorAppearanceComparedToTraitCollection(previousTraitCollection)) {
|
||||
owner.notify({ eventName: iosView.traitCollectionColorAppearanceChangedEvent, object: owner });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _getTransitionId(nativeTransition: UIViewAnimationTransition, transitionType: string): string {
|
||||
let name;
|
||||
switch (nativeTransition) {
|
||||
case UIViewAnimationTransition.CurlDown: name = "CurlDown"; break;
|
||||
case UIViewAnimationTransition.CurlUp: name = "CurlUp"; break;
|
||||
case UIViewAnimationTransition.FlipFromLeft: name = "FlipFromLeft"; break;
|
||||
case UIViewAnimationTransition.FlipFromRight: name = "FlipFromRight"; break;
|
||||
case UIViewAnimationTransition.None: name = "None"; break;
|
||||
}
|
||||
|
||||
return `${name} ${transitionType}`;
|
||||
}
|
||||
|
||||
function _getNativeTransition(navigationTransition: NavigationTransition, push: boolean): UIViewAnimationTransition {
|
||||
if (navigationTransition && navigationTransition.name) {
|
||||
switch (navigationTransition.name.toLowerCase()) {
|
||||
case "flip":
|
||||
case "flipright":
|
||||
return push ? UIViewAnimationTransition.FlipFromRight : UIViewAnimationTransition.FlipFromLeft;
|
||||
case "flipleft":
|
||||
return push ? UIViewAnimationTransition.FlipFromLeft : UIViewAnimationTransition.FlipFromRight;
|
||||
case "curl":
|
||||
case "curlup":
|
||||
return push ? UIViewAnimationTransition.CurlUp : UIViewAnimationTransition.CurlDown;
|
||||
case "curldown":
|
||||
return push ? UIViewAnimationTransition.CurlDown : UIViewAnimationTransition.CurlUp;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function _getNativeCurve(transition: NavigationTransition): UIViewAnimationCurve {
|
||||
if (transition.curve) {
|
||||
switch (transition.curve) {
|
||||
case "easeIn":
|
||||
if (traceEnabled()) {
|
||||
traceWrite("Transition curve resolved to UIViewAnimationCurve.EaseIn.", traceCategories.Transition);
|
||||
}
|
||||
|
||||
return UIViewAnimationCurve.EaseIn;
|
||||
|
||||
case "easeOut":
|
||||
if (traceEnabled()) {
|
||||
traceWrite("Transition curve resolved to UIViewAnimationCurve.EaseOut.", traceCategories.Transition);
|
||||
}
|
||||
|
||||
return UIViewAnimationCurve.EaseOut;
|
||||
|
||||
case "easeInOut":
|
||||
if (traceEnabled()) {
|
||||
traceWrite("Transition curve resolved to UIViewAnimationCurve.EaseInOut.", traceCategories.Transition);
|
||||
}
|
||||
|
||||
return UIViewAnimationCurve.EaseInOut;
|
||||
|
||||
case "linear":
|
||||
if (traceEnabled()) {
|
||||
traceWrite("Transition curve resolved to UIViewAnimationCurve.Linear.", traceCategories.Transition);
|
||||
}
|
||||
|
||||
return UIViewAnimationCurve.Linear;
|
||||
|
||||
default:
|
||||
if (traceEnabled()) {
|
||||
traceWrite("Transition curve resolved to original: " + transition.curve, traceCategories.Transition);
|
||||
}
|
||||
|
||||
return transition.curve;
|
||||
}
|
||||
}
|
||||
|
||||
return UIViewAnimationCurve.EaseInOut;
|
||||
}
|
||||
|
||||
/* tslint:disable */
|
||||
class iOSFrame implements iOSFrameDefinition {
|
||||
/* tslint:enable */
|
||||
private _controller: UINavigationControllerImpl;
|
||||
private _showNavigationBar: boolean;
|
||||
private _navBarVisibility: "auto" | "never" | "always" = "auto";
|
||||
|
||||
// TabView uses this flag to disable animation while showing/hiding the navigation bar because of the "< More" bar.
|
||||
// See the TabView._handleTwoNavigationBars method for more details.
|
||||
public _disableNavBarAnimation: boolean;
|
||||
|
||||
constructor(frame: Frame) {
|
||||
this._controller = UINavigationControllerImpl.initWithOwner(new WeakRef(frame));
|
||||
}
|
||||
|
||||
public get controller() {
|
||||
return this._controller;
|
||||
}
|
||||
public set controller(value: UINavigationControllerImpl) {
|
||||
this._controller = value;
|
||||
}
|
||||
|
||||
public get showNavigationBar(): boolean {
|
||||
return this._showNavigationBar;
|
||||
}
|
||||
public set showNavigationBar(value: boolean) {
|
||||
this._showNavigationBar = value;
|
||||
this._controller.setNavigationBarHiddenAnimated(!value, !this._disableNavBarAnimation);
|
||||
}
|
||||
|
||||
public get navBarVisibility(): "auto" | "never" | "always" {
|
||||
return this._navBarVisibility;
|
||||
}
|
||||
public set navBarVisibility(value: "auto" | "never" | "always") {
|
||||
this._navBarVisibility = value;
|
||||
}
|
||||
}
|
||||
5
nativescript-core/ui/frame/package.json
Normal file
5
nativescript-core/ui/frame/package.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "frame",
|
||||
"main": "frame",
|
||||
"types": "frame.d.ts"
|
||||
}
|
||||
59
nativescript-core/ui/frame/transition-definitions.android.d.ts
vendored
Normal file
59
nativescript-core/ui/frame/transition-definitions.android.d.ts
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* @module "ui/frame/transition-definitions.android
|
||||
* @private
|
||||
*/ /** */
|
||||
|
||||
// Definitions for Android API lvl 21 transitions
|
||||
declare module android {
|
||||
export module transition {
|
||||
export abstract class Transition extends java.lang.Object {
|
||||
addListener(transition: Transition.TransitionListener): Transition;
|
||||
removeListener(transition: Transition.TransitionListener): Transition;
|
||||
setDuration(duration: number): Transition;
|
||||
setInterpolator(interpolator: android.animation.TimeInterpolator): Transition;
|
||||
}
|
||||
|
||||
export abstract class Visibility extends android.transition.Transition {
|
||||
constructor();
|
||||
}
|
||||
|
||||
export class Slide extends Visibility {
|
||||
constructor(slideEdge: number);
|
||||
}
|
||||
|
||||
export class Fade extends Visibility {
|
||||
constructor(fadingMode: number);
|
||||
static IN: number;
|
||||
static OUT: number;
|
||||
}
|
||||
|
||||
export class Explode extends Visibility {
|
||||
constructor();
|
||||
}
|
||||
|
||||
export module Transition {
|
||||
export interface TransitionListener {
|
||||
onTransitionStart(transition: android.transition.Transition): void;
|
||||
onTransitionEnd(transition: android.transition.Transition): void;
|
||||
onTransitionResume(transition: android.transition.Transition): void;
|
||||
onTransitionPause(transition: android.transition.Transition): void;
|
||||
onTransitionCancel(transition: android.transition.Transition): void;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export module app {
|
||||
export interface Fragment {
|
||||
getEnterTransition(): android.transition.Transition;
|
||||
getExitTransition(): android.transition.Transition;
|
||||
getReenterTransition(): android.transition.Transition;
|
||||
getReturnTransition(): android.transition.Transition;
|
||||
setEnterTransition(transition: android.transition.Transition): void;
|
||||
setExitTransition(transition: android.transition.Transition): void;
|
||||
setReenterTransition(transition: android.transition.Transition): void;
|
||||
setReturnTransition(transition: android.transition.Transition): void;
|
||||
setAllowEnterTransitionOverlap(allow: boolean): void;
|
||||
setAllowReturnTransitionOverlap(allow: boolean): void;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user