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 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 { _resetRootView } from "tns-core-modules/application/";
|
||||||
import { Frame, NavigationEntry, topmost } from "tns-core-modules/ui/frame";
|
import { Frame, NavigationEntry, topmost } from "tns-core-modules/ui/frame";
|
||||||
import { Page } from "tns-core-modules/ui/page";
|
import { Page } from "tns-core-modules/ui/page";
|
||||||
import { TabView, TabViewItem } from "tns-core-modules/ui/tab-view";
|
import { TabView, TabViewItem } from "tns-core-modules/ui/tab-view";
|
||||||
|
|
||||||
function waitUntilNavigatedToMaxTimeout(pages: Page[], action: Function) {
|
function waitUntilNavigatedToMaxTimeout(pages: Page[], action: Function) {
|
||||||
const maxTimeout = 5;
|
const maxTimeout = 8;
|
||||||
let completed = 0;
|
let completed = 0;
|
||||||
function navigatedTo(args) {
|
function navigatedTo(args) {
|
||||||
args.object.page.off("navigatedTo", navigatedTo);
|
args.object.page.off("navigatedTo", navigatedTo);
|
||||||
@@ -67,13 +66,19 @@ export function test_frame_topmost_matches_selectedIndex() {
|
|||||||
create: () => tabView
|
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");
|
||||||
|
|
||||||
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");
|
||||||
|
|
||||||
waitUntilNavigatedToMaxTimeout([items[1].page], () => tabView.selectedIndex = 1);
|
waitUntilNavigatedToMaxTimeout([items[1].page], () => tabView.selectedIndex = 1);
|
||||||
|
TKUnit.assertEqual(topmost().id, "Tab1 Frame1");
|
||||||
TKUnit.assertEqual(topmost().id, "Tab1 Frame1");
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function test_offset_zero_should_raise_same_events() {
|
export function test_offset_zero_should_raise_same_events() {
|
||||||
|
|||||||
@@ -234,6 +234,7 @@ export class View extends ViewCommon {
|
|||||||
private layoutChangeListenerIsSet: boolean;
|
private layoutChangeListenerIsSet: boolean;
|
||||||
private layoutChangeListener: android.view.View.OnLayoutChangeListener;
|
private layoutChangeListener: android.view.View.OnLayoutChangeListener;
|
||||||
private _manager: android.support.v4.app.FragmentManager;
|
private _manager: android.support.v4.app.FragmentManager;
|
||||||
|
private _rootManager: android.support.v4.app.FragmentManager;
|
||||||
|
|
||||||
nativeViewProtected: android.view.View;
|
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 {
|
public _getFragmentManager(): android.support.v4.app.FragmentManager {
|
||||||
let manager = this._manager;
|
let manager = this._manager;
|
||||||
if (!manager) {
|
if (!manager) {
|
||||||
let view: View = this;
|
let view: View = this;
|
||||||
|
let frameOrTabViewItemFound = false;
|
||||||
while (view) {
|
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;
|
const dialogFragment = view._dialogFragment;
|
||||||
if (dialogFragment) {
|
if (dialogFragment) {
|
||||||
manager = dialogFragment.getChildFragmentManager();
|
manager = dialogFragment.getChildFragmentManager();
|
||||||
break;
|
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) {
|
if (!manager && this._context) {
|
||||||
@@ -294,6 +327,7 @@ export class View extends ViewCommon {
|
|||||||
@profile
|
@profile
|
||||||
public onLoaded() {
|
public onLoaded() {
|
||||||
this._manager = null;
|
this._manager = null;
|
||||||
|
this._rootManager = null;
|
||||||
super.onLoaded();
|
super.onLoaded();
|
||||||
this.setOnTouchListener();
|
this.setOnTouchListener();
|
||||||
}
|
}
|
||||||
@@ -307,6 +341,7 @@ export class View extends ViewCommon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this._manager = null;
|
this._manager = null;
|
||||||
|
this._rootManager = null;
|
||||||
super.onUnloaded();
|
super.onUnloaded();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -405,6 +440,10 @@ export class View extends ViewCommon {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get _hasFragments(): boolean {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public layoutNativeView(left: number, top: number, right: number, bottom: number): void {
|
public layoutNativeView(left: number, top: number, right: number, bottom: number): void {
|
||||||
if (this.nativeViewProtected) {
|
if (this.nativeViewProtected) {
|
||||||
this.nativeViewProtected.layout(left, top, right, bottom);
|
this.nativeViewProtected.layout(left, top, right, bottom);
|
||||||
@@ -571,7 +610,7 @@ export class View extends ViewCommon {
|
|||||||
this._dialogFragment = df;
|
this._dialogFragment = df;
|
||||||
this._raiseShowingModallyEvent();
|
this._raiseShowingModallyEvent();
|
||||||
|
|
||||||
this._dialogFragment.show(parent._getFragmentManager(), this._domId.toString());
|
this._dialogFragment.show(parent._getRootFragmentManager(), this._domId.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected _hideNativeModalView(parent: View) {
|
protected _hideNativeModalView(parent: View) {
|
||||||
|
|||||||
@@ -121,6 +121,10 @@ export class Frame extends FrameBase {
|
|||||||
return this._android;
|
return this._android;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get _hasFragments(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
_onAttachedToWindow(): void {
|
_onAttachedToWindow(): void {
|
||||||
super._onAttachedToWindow();
|
super._onAttachedToWindow();
|
||||||
this._attachedToWindow = true;
|
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();
|
this.disposeCurrentFragment();
|
||||||
super._onRootViewReset();
|
super._onRootViewReset();
|
||||||
}
|
}
|
||||||
@@ -186,7 +199,14 @@ export class Frame extends FrameBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private disposeCurrentFragment(): void {
|
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;
|
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
|
* @private
|
||||||
*/
|
*/
|
||||||
_currentEntry: BackstackEntry;
|
_currentEntry: BackstackEntry;
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_executingEntry: BackstackEntry;
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -260,6 +260,10 @@ export class TabViewItem extends TabViewItemBase {
|
|||||||
public index: number;
|
public index: number;
|
||||||
private _defaultTransformationMethod: android.text.method.TransformationMethod;
|
private _defaultTransformationMethod: android.text.method.TransformationMethod;
|
||||||
|
|
||||||
|
get _hasFragments(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public initNativeView(): void {
|
public initNativeView(): void {
|
||||||
super.initNativeView();
|
super.initNativeView();
|
||||||
if (this.nativeViewProtected) {
|
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 } {
|
[fontSizeProperty.getDefault](): { nativeSize: number } {
|
||||||
return { nativeSize: this.nativeViewProtected.getTextSize() };
|
return { nativeSize: this.nativeViewProtected.getTextSize() };
|
||||||
}
|
}
|
||||||
@@ -361,6 +383,10 @@ export class TabView extends TabViewBase {
|
|||||||
tabs.push(new WeakRef(this));
|
tabs.push(new WeakRef(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get _hasFragments(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public onItemsChanged(oldItems: TabViewItem[], newItems: TabViewItem[]): void {
|
public onItemsChanged(oldItems: TabViewItem[], newItems: TabViewItem[]): void {
|
||||||
super.onItemsChanged(oldItems, newItems);
|
super.onItemsChanged(oldItems, newItems);
|
||||||
|
|
||||||
@@ -512,6 +538,20 @@ export class TabView extends TabViewBase {
|
|||||||
return false;
|
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>) {
|
private shouldUpdateAdapter(items: Array<TabViewItemDefinition>) {
|
||||||
if (!this._pagerAdapter) {
|
if (!this._pagerAdapter) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
Reference in New Issue
Block a user