mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-16 11:42:04 +08:00
Merge pull request #2288 from NativeScript/atanasovg/decouple-fragment-class
Decouple Fragment implementation logic from the Extend call.
This commit is contained in:
49
tns-core-modules/ui/frame/fragment.android.ts
Normal file
49
tns-core-modules/ui/frame/fragment.android.ts
Normal file
@ -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);
|
@ -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 = <FragmentClass>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 = <FragmentClass>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) {
|
||||
(<any>fragment).frame = frame;
|
||||
(<any>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 : ""}>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
19
tns-core-modules/ui/frame/frame.d.ts
vendored
19
tns-core-modules/ui/frame/frame.d.ts
vendored
@ -122,8 +122,16 @@ 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;
|
||||
|
||||
/**
|
||||
* 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 +323,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.
|
||||
|
@ -344,8 +344,8 @@ export function _onFragmentHidden(fragment: any, isBack: boolean, destroyed: boo
|
||||
function _completePageAddition(fragment: any, isBack: boolean) {
|
||||
let expandedFragment = <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 = <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);
|
||||
|
@ -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",
|
||||
|
Reference in New Issue
Block a user