mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-11-05 13:26:48 +08:00
fix: nested fragments interact thru child fragment manager (#6293)
This commit is contained in:
@@ -1,13 +1,12 @@
|
||||
import * as helper from "../helper";
|
||||
import TKUnit = require("../../TKUnit");
|
||||
import { isIOS, isAndroid } from "tns-core-modules/platform";
|
||||
import { isAndroid } from "tns-core-modules/platform";
|
||||
import { _resetRootView } from "tns-core-modules/application/";
|
||||
import { Frame, NavigationEntry, topmost } from "tns-core-modules/ui/frame";
|
||||
import { Page } from "tns-core-modules/ui/page";
|
||||
import { TabView, TabViewItem } from "tns-core-modules/ui/tab-view";
|
||||
|
||||
function waitUntilNavigatedToMaxTimeout(pages: Page[], action: Function) {
|
||||
const maxTimeout = 5;
|
||||
const maxTimeout = 8;
|
||||
let completed = 0;
|
||||
function navigatedTo(args) {
|
||||
args.object.page.off("navigatedTo", navigatedTo);
|
||||
@@ -67,13 +66,19 @@ export function test_frame_topmost_matches_selectedIndex() {
|
||||
create: () => tabView
|
||||
};
|
||||
|
||||
waitUntilNavigatedToMaxTimeout([items[0].page], () => _resetRootView(entry));
|
||||
if (isAndroid) {
|
||||
waitUntilNavigatedToMaxTimeout([items[0].page, items[1].page], () => _resetRootView(entry));
|
||||
TKUnit.assertEqual(topmost().id, "Tab0 Frame0");
|
||||
|
||||
tabView.selectedIndex = 1;
|
||||
TKUnit.assertEqual(topmost().id, "Tab1 Frame1");
|
||||
} else {
|
||||
waitUntilNavigatedToMaxTimeout([items[0].page], () => _resetRootView(entry));
|
||||
TKUnit.assertEqual(topmost().id, "Tab0 Frame0");
|
||||
|
||||
TKUnit.assertEqual(topmost().id, "Tab0 Frame0");
|
||||
|
||||
waitUntilNavigatedToMaxTimeout([items[1].page], () => tabView.selectedIndex = 1);
|
||||
|
||||
TKUnit.assertEqual(topmost().id, "Tab1 Frame1");
|
||||
waitUntilNavigatedToMaxTimeout([items[1].page], () => tabView.selectedIndex = 1);
|
||||
TKUnit.assertEqual(topmost().id, "Tab1 Frame1");
|
||||
}
|
||||
}
|
||||
|
||||
export function test_offset_zero_should_raise_same_events() {
|
||||
|
||||
@@ -234,6 +234,7 @@ export class View extends ViewCommon {
|
||||
private layoutChangeListenerIsSet: boolean;
|
||||
private layoutChangeListener: android.view.View.OnLayoutChangeListener;
|
||||
private _manager: android.support.v4.app.FragmentManager;
|
||||
private _rootManager: android.support.v4.app.FragmentManager;
|
||||
|
||||
nativeViewProtected: android.view.View;
|
||||
|
||||
@@ -265,20 +266,52 @@ export class View extends ViewCommon {
|
||||
}
|
||||
}
|
||||
|
||||
public _getChildFragmentManager(): android.support.v4.app.FragmentManager {
|
||||
return null;
|
||||
}
|
||||
|
||||
public _getRootFragmentManager(): android.support.v4.app.FragmentManager {
|
||||
if (!this._rootManager && this._context) {
|
||||
this._rootManager = (<android.support.v4.app.FragmentActivity>this._context).getSupportFragmentManager();
|
||||
}
|
||||
|
||||
return this._rootManager;
|
||||
}
|
||||
|
||||
public _getFragmentManager(): android.support.v4.app.FragmentManager {
|
||||
let manager = this._manager;
|
||||
if (!manager) {
|
||||
let view: View = this;
|
||||
let frameOrTabViewItemFound = false;
|
||||
while (view) {
|
||||
// when interacting with nested fragments instead of using getSupportFragmentManager
|
||||
// we must always use getChildFragmentManager instead;
|
||||
// we have three sources of fragments -- Frame fragments, TabViewItem fragments, and
|
||||
// modal dialog fragments
|
||||
|
||||
// modal -> frame / tabview (frame / tabview use modal CHILD fm)
|
||||
const dialogFragment = view._dialogFragment;
|
||||
if (dialogFragment) {
|
||||
manager = dialogFragment.getChildFragmentManager();
|
||||
break;
|
||||
} else {
|
||||
// the case is needed because _dialogFragment is on View
|
||||
// but parent may be ViewBase.
|
||||
view = view.parent as View;
|
||||
}
|
||||
|
||||
// - frame1 -> frame2 (frame2 uses frame1 CHILD fm)
|
||||
// - tabview -> frame1 (frame1 uses tabview item CHILD fm)
|
||||
// - frame1 -> tabview (tabview uses frame1 CHILD fm)
|
||||
// - frame1 -> tabview -> frame2 (tabview uses frame1 CHILD fm; frame2 uses tabview item CHILD fm)
|
||||
if (view._hasFragments) {
|
||||
if (frameOrTabViewItemFound) {
|
||||
manager = view._getChildFragmentManager();
|
||||
break;
|
||||
}
|
||||
|
||||
frameOrTabViewItemFound = true;
|
||||
}
|
||||
|
||||
// the case is needed because _dialogFragment is on View
|
||||
// but parent may be ViewBase.
|
||||
view = view.parent as View;
|
||||
}
|
||||
|
||||
if (!manager && this._context) {
|
||||
@@ -294,6 +327,7 @@ export class View extends ViewCommon {
|
||||
@profile
|
||||
public onLoaded() {
|
||||
this._manager = null;
|
||||
this._rootManager = null;
|
||||
super.onLoaded();
|
||||
this.setOnTouchListener();
|
||||
}
|
||||
@@ -307,6 +341,7 @@ export class View extends ViewCommon {
|
||||
}
|
||||
|
||||
this._manager = null;
|
||||
this._rootManager = null;
|
||||
super.onUnloaded();
|
||||
}
|
||||
|
||||
@@ -405,6 +440,10 @@ export class View extends ViewCommon {
|
||||
return false;
|
||||
}
|
||||
|
||||
get _hasFragments(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
public layoutNativeView(left: number, top: number, right: number, bottom: number): void {
|
||||
if (this.nativeViewProtected) {
|
||||
this.nativeViewProtected.layout(left, top, right, bottom);
|
||||
@@ -571,7 +610,7 @@ export class View extends ViewCommon {
|
||||
this._dialogFragment = df;
|
||||
this._raiseShowingModallyEvent();
|
||||
|
||||
this._dialogFragment.show(parent._getFragmentManager(), this._domId.toString());
|
||||
this._dialogFragment.show(parent._getRootFragmentManager(), this._domId.toString());
|
||||
}
|
||||
|
||||
protected _hideNativeModalView(parent: View) {
|
||||
|
||||
@@ -121,6 +121,10 @@ export class Frame extends FrameBase {
|
||||
return this._android;
|
||||
}
|
||||
|
||||
get _hasFragments(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
_onAttachedToWindow(): void {
|
||||
super._onAttachedToWindow();
|
||||
this._attachedToWindow = true;
|
||||
@@ -175,7 +179,16 @@ export class Frame extends FrameBase {
|
||||
}
|
||||
}
|
||||
|
||||
_onRootViewReset(): void {
|
||||
public _getChildFragmentManager() {
|
||||
const backstackEntry = this._executingEntry || this._currentEntry;
|
||||
if (backstackEntry && backstackEntry.fragment && backstackEntry.fragment.isAdded()) {
|
||||
return backstackEntry.fragment.getChildFragmentManager();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public _onRootViewReset(): void {
|
||||
this.disposeCurrentFragment();
|
||||
super._onRootViewReset();
|
||||
}
|
||||
@@ -186,7 +199,14 @@ export class Frame extends FrameBase {
|
||||
}
|
||||
|
||||
private disposeCurrentFragment(): void {
|
||||
if (!this._currentEntry || !this._currentEntry.fragment) {
|
||||
// when interacting with nested fragments it seems Android is smart enough
|
||||
// to automatically remove child fragments when parent fragment is removed;
|
||||
// however, we must add a fragment.isAdded() guard as our logic will try to
|
||||
// explicitly remove the already removed child fragment causing an
|
||||
// IllegalStateException: Fragment has not been attached yet.
|
||||
if (!this._currentEntry ||
|
||||
!this._currentEntry.fragment ||
|
||||
!this._currentEntry.fragment.isAdded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
4
tns-core-modules/ui/frame/frame.d.ts
vendored
4
tns-core-modules/ui/frame/frame.d.ts
vendored
@@ -125,6 +125,10 @@ export class Frame extends View {
|
||||
* @private
|
||||
*/
|
||||
_currentEntry: BackstackEntry;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_executingEntry: BackstackEntry;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
|
||||
@@ -260,6 +260,10 @@ export class TabViewItem extends TabViewItemBase {
|
||||
public index: number;
|
||||
private _defaultTransformationMethod: android.text.method.TransformationMethod;
|
||||
|
||||
get _hasFragments(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
public initNativeView(): void {
|
||||
super.initNativeView();
|
||||
if (this.nativeViewProtected) {
|
||||
@@ -297,6 +301,24 @@ export class TabViewItem extends TabViewItemBase {
|
||||
}
|
||||
}
|
||||
|
||||
public _getChildFragmentManager(): android.support.v4.app.FragmentManager {
|
||||
const tabView = this.parent as TabView;
|
||||
let tabFragment = null;
|
||||
const fragmentManager = tabView._getFragmentManager();
|
||||
for (let fragment of (<Array<any>>fragmentManager.getFragments().toArray())) {
|
||||
if (fragment.index === this.index) {
|
||||
tabFragment = fragment;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!tabFragment) {
|
||||
throw new Error(`Could not get child fragment manager for tab item with index ${this.index}`);
|
||||
}
|
||||
|
||||
return tabFragment.getChildFragmentManager();
|
||||
}
|
||||
|
||||
[fontSizeProperty.getDefault](): { nativeSize: number } {
|
||||
return { nativeSize: this.nativeViewProtected.getTextSize() };
|
||||
}
|
||||
@@ -361,6 +383,10 @@ export class TabView extends TabViewBase {
|
||||
tabs.push(new WeakRef(this));
|
||||
}
|
||||
|
||||
get _hasFragments(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
public onItemsChanged(oldItems: TabViewItem[], newItems: TabViewItem[]): void {
|
||||
super.onItemsChanged(oldItems, newItems);
|
||||
|
||||
@@ -512,6 +538,20 @@ export class TabView extends TabViewBase {
|
||||
return false;
|
||||
}
|
||||
|
||||
public _onRootViewReset(): void {
|
||||
this.disposeCurrentFragments();
|
||||
super._onRootViewReset();
|
||||
}
|
||||
|
||||
private disposeCurrentFragments(): void {
|
||||
const fragmentManager = this._getFragmentManager();
|
||||
const transaction = fragmentManager.beginTransaction();
|
||||
for (let fragment of (<Array<any>>fragmentManager.getFragments().toArray())) {
|
||||
transaction.remove(fragment);
|
||||
}
|
||||
transaction.commitNowAllowingStateLoss();
|
||||
}
|
||||
|
||||
private shouldUpdateAdapter(items: Array<TabViewItemDefinition>) {
|
||||
if (!this._pagerAdapter) {
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user