From ef0577ed56e73a0fdeeb12a13f414643ed0e704d Mon Sep 17 00:00:00 2001 From: atanasovg Date: Fri, 10 Jun 2016 18:01:47 +0300 Subject: [PATCH 1/2] Decouple Fragment implementation logic from the Extend call. --- tns-core-modules/ui/frame/fragment.android.ts | 49 ++++++ tns-core-modules/ui/frame/frame.android.ts | 152 ++++++++++-------- tns-core-modules/ui/frame/frame.d.ts | 13 ++ .../ui/transition/transition.android.ts | 8 +- tsconfig.json | 1 + 5 files changed, 155 insertions(+), 68 deletions(-) create mode 100644 tns-core-modules/ui/frame/fragment.android.ts diff --git a/tns-core-modules/ui/frame/fragment.android.ts b/tns-core-modules/ui/frame/fragment.android.ts new file mode 100644 index 000000000..a8b30fe42 --- /dev/null +++ b/tns-core-modules/ui/frame/fragment.android.ts @@ -0,0 +1,49 @@ +import * as frame from "ui/frame"; + +@JavaProxy("com.tns.FragmentClass") +class FragmentClass extends android.app.Fragment { + // This field is updated in the frame module upon `new` (although hacky this eases the Fragment->callbacks association a lot) + private _callbacks; + + 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 { + let result = this._callbacks.onCreateAnimator(this, transit, enter, nextAnim, super.onCreateAnimator); + return result; + } + + public onCreate(savedInstanceState: android.os.Bundle) { + super.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 { + return this._callbacks.toStringOverride(this, super.toString); + } +} + +frame.setFragmentClass(FragmentClass); \ No newline at end of file diff --git a/tns-core-modules/ui/frame/frame.android.ts b/tns-core-modules/ui/frame/frame.android.ts index 73f7c9e4f..40b8ebd3e 100644 --- a/tns-core-modules/ui/frame/frame.android.ts +++ b/tns-core-modules/ui/frame/frame.android.ts @@ -17,12 +17,14 @@ let navDepth = -1; let fragmentId = -1; let activityInitialized = false; const PAGE_FRAGMENT_TAG = "_fragmentTag"; +const CALLBACKS = "_callbacks"; -function onFragmentShown(fragment: FragmentClass) { +function onFragmentShown(fragment: android.app.Fragment) { if (trace.enabled) { trace.write(`SHOWN ${fragment}`, trace.categories.NativeLifecycle); } - if (fragment.clearHistory) { + let callbacks: FragmentCallbacksImplementation = fragment[CALLBACKS]; + if (callbacks.clearHistory) { // This is the fragment which was at the bottom of the stack (fragment0) when we cleared history and called // manager.popBackStack(firstEntryName, android.app.FragmentManager.POP_BACK_STACK_INCLUSIVE); if (trace.enabled) { @@ -33,9 +35,9 @@ function onFragmentShown(fragment: FragmentClass) { // TODO: consider putting entry and page in queue so we can safely extract them here. Pass the index of current navigation and extract it from here. // After extracting navigation info - remove this index from navigation stack. - var frame = fragment.frame; - var entry = fragment.entry; - var page = entry.resolvedPage; + let frame = callbacks.frame; + let entry = callbacks.entry; + let page = entry.resolvedPage; page[PAGE_FRAGMENT_TAG] = entry.fragmentTag; let currentNavigationContext; @@ -63,14 +65,14 @@ function onFragmentShown(fragment: FragmentClass) { transitionModule._onFragmentShown(fragment, isBack); } -function onFragmentHidden(fragment: FragmentClass, destroyed: boolean) { +function onFragmentHidden(fragment: android.app.Fragment, destroyed: boolean) { if (trace.enabled) { trace.write(`HIDDEN ${fragment}; destroyed: ${destroyed}`, trace.categories.NativeLifecycle); } - - var isBack = fragment.entry.isBack; - fragment.entry.isBack = undefined; - fragment.entry.resolvedPage[PAGE_FRAGMENT_TAG] = undefined; + let callbacks: FragmentCallbacksImplementation = fragment[CALLBACKS]; + let isBack = callbacks.entry.isBack; + callbacks.entry.isBack = undefined; + callbacks.entry.resolvedPage[PAGE_FRAGMENT_TAG] = undefined; // Handle page transitions. transitionModule._onFragmentHidden(fragment, isBack, destroyed); @@ -136,10 +138,10 @@ export class Frame extends frameCommon.Frame { let manager = activity.getFragmentManager(); // Current Fragment - var currentFragment: FragmentClass; + let currentFragment: android.app.Fragment; if (this._currentEntry) { this._currentEntry.isNavigation = true; - currentFragment = manager.findFragmentByTag(this._currentEntry.fragmentTag); + currentFragment = manager.findFragmentByTag(this._currentEntry.fragmentTag); } let clearHistory = backstackEntry.entry.clearHistory; @@ -150,13 +152,18 @@ export class Frame extends frameCommon.Frame { } navDepth++; fragmentId++; - var newFragmentTag = `fragment${fragmentId}[${navDepth}]`; - let newFragment = new FragmentClass(); + let newFragmentTag = `fragment${fragmentId}[${navDepth}]`; + ensureFragmentClass(); + let newFragment: android.app.Fragment = new fragmentClass(); + + let callbacks = new FragmentCallbacksImplementation(); + callbacks.frame = this; + callbacks.entry = backstackEntry; + + newFragment[CALLBACKS] = callbacks; let args = new android.os.Bundle(); args.putInt(FRAMEID, this._android.frameId); newFragment.setArguments(args); - newFragment.frame = this; - newFragment.entry = backstackEntry; // backstackEntry backstackEntry.isNavigation = true; @@ -185,7 +192,7 @@ export class Frame extends frameCommon.Frame { let emptyNativeBackStack = clearHistory && length > 0; if (emptyNativeBackStack) { for (let i = 0; i < length; i++) { - let fragmentToRemove = manager.findFragmentByTag(manager.getBackStackEntryAt(i).getName()); + let fragmentToRemove = manager.findFragmentByTag(manager.getBackStackEntryAt(i).getName()); Frame._clearHistory(fragmentToRemove); } if (currentFragment) { @@ -219,7 +226,7 @@ export class Frame extends frameCommon.Frame { // Add newFragment if (trace.enabled) { - trace.write(`\tADD ${newFragmentTag}<${newFragment.entry.resolvedPage}>`, trace.categories.Navigation); + trace.write(`\tADD ${newFragmentTag}<${callbacks.entry.resolvedPage}>`, trace.categories.Navigation); } fragmentTransaction.add(this.containerViewId, newFragment, newFragmentTag); @@ -257,14 +264,15 @@ export class Frame extends frameCommon.Frame { } } - private static _clearHistory(fragment: FragmentClass) { + private static _clearHistory(fragment: android.app.Fragment) { if (trace.enabled) { trace.write(`CLEAR HISTORY FOR ${fragment}`, trace.categories.Navigation); } - fragment.clearHistory = true; + let callbacks: FragmentCallbacksImplementation = fragment[CALLBACKS]; + callbacks.clearHistory = true; // This is hacky transitionModule._clearBackwardTransitions(fragment); transitionModule._clearForwardTransitions(fragment); - transitionModule._removePageNativeViewFromAndroidParent(fragment.entry.resolvedPage); + transitionModule._removePageNativeViewFromAndroidParent(callbacks.entry.resolvedPage); } public _goBackCore(backstackEntry: definition.BackstackEntry) { @@ -571,8 +579,9 @@ function findPageForFragment(fragment: android.app.Fragment, frame: Frame) { } if (page) { - (fragment).frame = frame; - (fragment).entry = entry; + let callbacks: FragmentCallbacksImplementation = fragment[CALLBACKS]; + callbacks.frame = frame; + callbacks.entry = entry; } else { //throw new Error(`Could not find a page for ${fragmentTag}.`); @@ -608,31 +617,46 @@ function ensureAnimationFixed() { } } -@JavaProxy("com.tns.FragmentClass") -class FragmentClass extends android.app.Fragment { +function ensureFragmentClass() { + if(fragmentClass) { + return; + } + + // this require will apply the FragmentClass implementation + require("ui/frame/fragment"); + + if(!fragmentClass) { + throw new Error("Failed to initialize the extended android.app.Fragment class"); + } +} + +let fragmentClass: any; +export function setFragmentClass(clazz: any) { + if(fragmentClass) { + throw new Error("Fragment class already initialized"); + } + + fragmentClass = clazz; +} +class FragmentCallbacksImplementation implements definition.AndroidFragmentCallbacks { public frame: Frame; public entry: definition.BackstackEntry; public clearHistory: boolean; - constructor() { - super(); - return global.__native(this); - } - - public onHiddenChanged(hidden: boolean): void { + public onHiddenChanged(fragment: android.app.Fragment, hidden: boolean, superFunc: Function): void { if (trace.enabled) { - trace.write(`${this}.onHiddenChanged(${hidden})`, trace.categories.NativeLifecycle); + trace.write(`${fragment}.onHiddenChanged(${hidden})`, trace.categories.NativeLifecycle); } - super.onHiddenChanged(hidden); + superFunc.call(fragment, hidden); if (hidden) { - onFragmentHidden(this, false); + onFragmentHidden(fragment, false); } else { - onFragmentShown(this); + onFragmentShown(fragment); } } - public onCreateAnimator(transit: number, enter: boolean, nextAnim: number): android.animation.Animator { + public onCreateAnimator(fragment: android.app.Fragment, transit: number, enter: boolean, nextAnim: number, superFunc: Function): android.animation.Animator { var nextAnimString: string; switch (nextAnim) { case -10: nextAnimString = "enter"; break; @@ -641,86 +665,86 @@ class FragmentClass extends android.app.Fragment { case -40: nextAnimString = "popExit"; break; } - var animator = transitionModule._onFragmentCreateAnimator(this, nextAnim); + var animator = transitionModule._onFragmentCreateAnimator(fragment, nextAnim); if (!animator) { - animator = super.onCreateAnimator(transit, enter, nextAnim); + animator = superFunc.call(fragment, transit, enter, nextAnim); } if (trace.enabled) { - trace.write(`${this}.onCreateAnimator(${transit}, ${enter ? "enter" : "exit"}, ${nextAnimString}): ${animator}`, trace.categories.NativeLifecycle); + trace.write(`${fragment}.onCreateAnimator(${transit}, ${enter ? "enter" : "exit"}, ${nextAnimString}): ${animator}`, trace.categories.NativeLifecycle); } return animator; } - public onCreate(savedInstanceState: android.os.Bundle): void { + public onCreate(fragment: android.app.Fragment, savedInstanceState: android.os.Bundle, superFunc: Function): void { if (trace.enabled) { - trace.write(`${this}.onCreate(${savedInstanceState})`, trace.categories.NativeLifecycle); + trace.write(`${fragment}.onCreate(${savedInstanceState})`, trace.categories.NativeLifecycle); } - super.onCreate(savedInstanceState); - super.setHasOptionsMenu(true); + + superFunc.call(fragment, savedInstanceState); // There is no entry set to the fragment, so this must be destroyed fragment that was recreated by Android. // We should find its corresponding page in our backstack and set it manually. if (!this.entry) { - let frameId = this.getArguments().getInt(FRAMEID); + let frameId = fragment.getArguments().getInt(FRAMEID); let frame = getFrameById(frameId); if (frame) { this.frame = frame; } else { - throw new Error(`Cannot find Frame for ${this}`); + throw new Error(`Cannot find Frame for ${fragment}`); } - findPageForFragment(this, this.frame); + findPageForFragment(fragment, this.frame); } } - public onCreateView(inflater: android.view.LayoutInflater, container: android.view.ViewGroup, savedInstanceState: android.os.Bundle): android.view.View { + public onCreateView(fragment: android.app.Fragment, inflater: android.view.LayoutInflater, container: android.view.ViewGroup, savedInstanceState: android.os.Bundle, superFunc: Function): android.view.View { if (trace.enabled) { - trace.write(`${this}.onCreateView(inflater, container, ${savedInstanceState})`, trace.categories.NativeLifecycle); + trace.write(`${fragment}.onCreateView(inflater, container, ${savedInstanceState})`, trace.categories.NativeLifecycle); } var entry = this.entry; var page = entry.resolvedPage; if (savedInstanceState && savedInstanceState.getBoolean(HIDDEN, false)) { - this.getFragmentManager().beginTransaction().hide(this).commit(); - page._onAttached(this.getActivity()); + fragment.getFragmentManager().beginTransaction().hide(fragment).commit(); + page._onAttached(fragment.getActivity()); } else { - onFragmentShown(this); + onFragmentShown(fragment); } return page._nativeView; } - public onSaveInstanceState(outState: android.os.Bundle): void { + public onSaveInstanceState(fragment: android.app.Fragment, outState: android.os.Bundle, superFunc: Function): void { if (trace.enabled) { - trace.write(`${this}.onSaveInstanceState(${outState})`, trace.categories.NativeLifecycle); + trace.write(`${fragment}.onSaveInstanceState(${outState})`, trace.categories.NativeLifecycle); } - super.onSaveInstanceState(outState); - if (this.isHidden()) { + superFunc.call(fragment, outState); + if (fragment.isHidden()) { outState.putBoolean(HIDDEN, true); } } - public onDestroyView(): void { + public onDestroyView(fragment: android.app.Fragment, superFunc: Function): void { if (trace.enabled) { - trace.write(`${this}.onDestroyView()`, trace.categories.NativeLifecycle); + trace.write(`${fragment}.onDestroyView()`, trace.categories.NativeLifecycle); } - super.onDestroyView(); + superFunc.call(fragment); // Detaching the page has been move in onFragmentHidden due to transitions. - onFragmentHidden(this, true); + onFragmentHidden(fragment, true); } - public onDestroy(): void { + public onDestroy(fragment: android.app.Fragment, superFunc: Function): void { if (trace.enabled) { - trace.write(`${this}.onDestroy()`, trace.categories.NativeLifecycle); + trace.write(`${fragment}.onDestroy()`, trace.categories.NativeLifecycle); } - super.onDestroy(); + superFunc.call(fragment); } - public toString(): string { - return `${this.getTag()}<${this.entry ? this.entry.resolvedPage : ""}>`; + public toStringOverride(fragment: android.app.Fragment, superFunc: Function): string { + return `${fragment.getTag()}<${this.entry ? this.entry.resolvedPage : ""}>`; } } diff --git a/tns-core-modules/ui/frame/frame.d.ts b/tns-core-modules/ui/frame/frame.d.ts index dc10a273c..694b73e4d 100644 --- a/tns-core-modules/ui/frame/frame.d.ts +++ b/tns-core-modules/ui/frame/frame.d.ts @@ -124,6 +124,8 @@ declare module "ui/frame" { export var activityCallbacks: AndroidActivityCallbacks; + export function setFragmentClass(clazz: any): void; + /** * Gets the topmost frame in the frames stack. An application will typically has one frame instance. Multiple frames handle nested (hierarchical) navigation scenarios. */ @@ -315,6 +317,17 @@ declare module "ui/frame" { onActivityResult(activity: any, requestCode: number, resultCode: number, data: any, superFunc: Function); } + 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; + toStringOverride(fragment: any, superFunc: Function): string; + } + /* tslint:disable */ /** * Represents the iOS-specific Frame object, aggregated within the common Frame one. diff --git a/tns-core-modules/ui/transition/transition.android.ts b/tns-core-modules/ui/transition/transition.android.ts index 11db4a5d2..fbb417269 100644 --- a/tns-core-modules/ui/transition/transition.android.ts +++ b/tns-core-modules/ui/transition/transition.android.ts @@ -344,8 +344,8 @@ export function _onFragmentHidden(fragment: any, isBack: boolean, destroyed: boo function _completePageAddition(fragment: any, isBack: boolean) { let expandedFragment = fragment; expandedFragment.completePageAdditionWhenTransitionEnds = undefined; - let frame = fragment.frame; - let entry: BackstackEntry = fragment.entry; + let frame = fragment._callbacks.frame; + let entry: BackstackEntry = fragment._callbacks.entry; let page: Page = entry.resolvedPage; if (trace.enabled) { trace.write(`STARTING ADDITION of ${page}...`, trace.categories.Transition); @@ -363,8 +363,8 @@ function _completePageAddition(fragment: any, isBack: boolean) { function _completePageRemoval(fragment: any, isBack: boolean) { let expandedFragment = fragment; expandedFragment.completePageRemovalWhenTransitionEnds = undefined; - let frame = fragment.frame; - let entry: BackstackEntry = fragment.entry; + let frame = fragment._callbacks.frame; + let entry: BackstackEntry = fragment._callbacks.entry; let page: Page = entry.resolvedPage; if (trace.enabled) { trace.write(`STARTING REMOVAL of ${page}...`, trace.categories.Transition); diff --git a/tsconfig.json b/tsconfig.json index 24f3dcc48..905b69a6a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -129,6 +129,7 @@ "tns-core-modules/ui/editable-text-base/editable-text-base.ios.ts", "tns-core-modules/ui/enums/enums.ts", "tns-core-modules/ui/frame/activity.android.ts", + "tns-core-modules/ui/frame/fragment.android.ts", "tns-core-modules/ui/frame/frame-common.ts", "tns-core-modules/ui/frame/frame.android.ts", "tns-core-modules/ui/frame/frame.ios.ts", From 2740be2b05ba29a68ad7f2e9b153cd78d54159b8 Mon Sep 17 00:00:00 2001 From: atanasovg Date: Mon, 13 Jun 2016 11:10:15 +0300 Subject: [PATCH 2/2] Add API ref comments. --- tns-core-modules/ui/frame/frame.d.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tns-core-modules/ui/frame/frame.d.ts b/tns-core-modules/ui/frame/frame.d.ts index 694b73e4d..7e9656289 100644 --- a/tns-core-modules/ui/frame/frame.d.ts +++ b/tns-core-modules/ui/frame/frame.d.ts @@ -122,8 +122,14 @@ declare module "ui/frame" { on(event: "optionSelected", callback: (args: observable.EventData) => void, thisArg?: any); } + /** + * Gets the default AndroidActivityCallbacks implementation, used to bridge Activity events to the Frame and navigation routine. This field is initialized only for the Android platform. + */ export var activityCallbacks: AndroidActivityCallbacks; + /** + * Sets the extended android.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; /**