Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 15 additions & 10 deletions tests/app/ui/tab-view/tab-view-root-tests.ts
Original file line number Diff line number Diff line change
@@ -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);
Expand Down Expand Up @@ -67,13 +66,19 @@ export function test_frame_topmost_matches_selectedIndex() {
create: () => tabView
};

waitUntilNavigatedToMaxTimeout([items[0].page], () => _resetRootView(entry));

TKUnit.assertEqual(topmost().id, "Tab0 Frame0");

waitUntilNavigatedToMaxTimeout([items[1].page], () => tabView.selectedIndex = 1);

TKUnit.assertEqual(topmost().id, "Tab1 Frame1");
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");

waitUntilNavigatedToMaxTimeout([items[1].page], () => tabView.selectedIndex = 1);
TKUnit.assertEqual(topmost().id, "Tab1 Frame1");
}
}

export function test_offset_zero_should_raise_same_events() {
Expand Down
49 changes: 44 additions & 5 deletions tns-core-modules/ui/core/view/view.android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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) {
Expand All @@ -294,6 +327,7 @@ export class View extends ViewCommon {
@profile
public onLoaded() {
this._manager = null;
this._rootManager = null;
super.onLoaded();
this.setOnTouchListener();
}
Expand All @@ -307,6 +341,7 @@ export class View extends ViewCommon {
}

this._manager = null;
this._rootManager = null;
super.onUnloaded();
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
Expand Down
24 changes: 22 additions & 2 deletions tns-core-modules/ui/frame/frame.android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ export class Frame extends FrameBase {
return this._android;
}

get _hasFragments(): boolean {
return true;
}

_onAttachedToWindow(): void {
super._onAttachedToWindow();
this._attachedToWindow = true;
Expand Down Expand Up @@ -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();
}
Expand All @@ -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;
}

Expand Down
4 changes: 4 additions & 0 deletions tns-core-modules/ui/frame/frame.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@ export class Frame extends View {
* @private
*/
_currentEntry: BackstackEntry;
/**
* @private
*/
_executingEntry: BackstackEntry;
/**
* @private
*/
Expand Down
40 changes: 40 additions & 0 deletions tns-core-modules/ui/tab-view/tab-view.android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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() };
}
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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;
Expand Down