From 4765dd93028427f0ee7f1e718e7583b85360d284 Mon Sep 17 00:00:00 2001 From: Manol Donev Date: Fri, 26 Oct 2018 13:32:30 +0300 Subject: [PATCH 01/29] fix-next(android): exit fragment animation (#6421) --- .../ui/frame/fragment.transitions.android.ts | 5 +- tns-core-modules/ui/frame/frame.android.ts | 74 +++++++++++++++---- .../ui/tab-view/tab-view.android.ts | 7 +- 3 files changed, 68 insertions(+), 18 deletions(-) diff --git a/tns-core-modules/ui/frame/fragment.transitions.android.ts b/tns-core-modules/ui/frame/fragment.transitions.android.ts index bfb61ea50..43eefeeff 100644 --- a/tns-core-modules/ui/frame/fragment.transitions.android.ts +++ b/tns-core-modules/ui/frame/fragment.transitions.android.ts @@ -151,7 +151,8 @@ export function _setAndroidFragmentTransitions( // Having transition means we have custom animation if (transition) { - fragmentTransaction.setCustomAnimations(AnimationType.enterFakeResourceId, AnimationType.exitFakeResourceId, AnimationType.popEnterFakeResourceId, AnimationType.popExitFakeResourceId); + // we do not use Android backstack so setting popEnter / popExit is meaningless (3rd and 4th optional args) + fragmentTransaction.setCustomAnimations(AnimationType.enterFakeResourceId, AnimationType.exitFakeResourceId); setupAllAnimation(newEntry, transition); if (currentFragmentNeedsDifferentAnimation) { setupExitAndPopEnterAnimation(currentEntry, transition); @@ -375,7 +376,7 @@ function clearAnimationListener(animator: ExpandedAnimator, listener: android.an animator.removeListener(listener); - if (traceEnabled()) { + if (animator.entry && traceEnabled()) { const entry = animator.entry; traceWrite(`Clear ${animator.transitionType} - ${entry.transition} for ${entry.fragmentTag}`, traceCategories.Transition); } diff --git a/tns-core-modules/ui/frame/frame.android.ts b/tns-core-modules/ui/frame/frame.android.ts index 2e2d7e65f..bfb911d2c 100644 --- a/tns-core-modules/ui/frame/frame.android.ts +++ b/tns-core-modules/ui/frame/frame.android.ts @@ -24,6 +24,14 @@ import { createViewFromEntry } from "../builder"; export * from "./frame-common"; +interface AnimatorState { + enterAnimator: android.animation.Animator; + exitAnimator: android.animation.Animator; + popEnterAnimator: android.animation.Animator; + popExitAnimator: android.animation.Animator; + transitionName: string; +} + const INTENT_EXTRA = "com.tns.activity"; const ROOT_VIEW_ID_EXTRA = "com.tns.activity.rootViewId"; const FRAMEID = "_frameId"; @@ -93,6 +101,7 @@ export class Frame extends FrameBase { private _tearDownPending = false; private _attachedToWindow = false; public _isBack: boolean = true; + private _cachedAnimatorState: AnimatorState; constructor() { super(); @@ -170,6 +179,17 @@ export class Frame extends FrameBase { const entry = this._currentEntry; if (entry && manager && !manager.findFragmentByTag(entry.fragmentTag)) { // Simulate first navigation (e.g. no animations or transitions) + // we need to cache the original animation settings so we can restore them later; otherwise as the + // simulated first navigation is not animated (it is actually a zero duration animator) the "popExit" animation + // is broken when transaction.setCustomAnimations(...) is used in a scenario with: + // 1) forward navigation + // 2) suspend / resume app + // 3) back navigation -- the exiting fragment is erroneously animated with the exit animator from the + // simulated navigation (NoTransition, zero duration animator) and thus the fragment immediately disappears; + // the user only sees the animation of the entering fragment as per its specific enter animation settings. + // NOTE: we are restoring the animation settings in Frame.setCurrent(...) as navigation completes asynchronously + this._cachedAnimatorState = getAnimatorState(this._currentEntry); + this._currentEntry = null; // NavigateCore will eventually call _processNextNavigationEntry again. this._navigateCore(entry); @@ -194,8 +214,12 @@ export class Frame extends FrameBase { } onUnloaded() { - this.disposeCurrentFragment(); super.onUnloaded(); + + // calling dispose fragment after super.onUnloaded() means we are not relying on the built-in Android logic + // to automatically remove child fragments when parent fragment is removed; + // this fixes issue with missing nested fragment on app suspend / resume; + this.disposeCurrentFragment(); } private disposeCurrentFragment(): void { @@ -278,6 +302,14 @@ export class Frame extends FrameBase { // Continue with next item in the queue. this._processNextNavigationEntry(); } + + // restore cached animation settings if we just completed simulated first navigation (no animation) + if (this._cachedAnimatorState) { + restoreAnimatorState(this._currentEntry, this._cachedAnimatorState); + + this._cachedAnimatorState = null; + } + } public onBackPressed(): boolean { @@ -332,7 +364,7 @@ export class Frame extends FrameBase { const newFragmentTag = `fragment${fragmentId}[${navDepth}]`; const newFragment = this.createFragment(newEntry, newFragmentTag); const transaction = manager.beginTransaction(); - const animated = this._getIsAnimatedNavigation(newEntry.entry); + const animated = currentEntry ? this._getIsAnimatedNavigation(newEntry.entry) : false; // NOTE: Don't use transition for the initial navigation (same as on iOS) // On API 21+ transition won't be triggered unless there was at least one // layout pass so we will wait forever for transitionCompleted handler... @@ -346,7 +378,7 @@ export class Frame extends FrameBase { } transaction.replace(this.containerViewId, newFragment, newFragmentTag); - transaction.commit(); + transaction.commitAllowingStateLoss(); } public _goBackCore(backstackEntry: BackstackEntry) { @@ -369,11 +401,12 @@ export class Frame extends FrameBase { const transitionReversed = _reverseTransitions(backstackEntry, this._currentEntry); if (!transitionReversed) { // If transition were not reversed then use animations. - transaction.setCustomAnimations(AnimationType.popEnterFakeResourceId, AnimationType.popExitFakeResourceId, AnimationType.enterFakeResourceId, AnimationType.exitFakeResourceId); + // we do not use Android backstack so setting popEnter / popExit is meaningless (3rd and 4th optional args) + transaction.setCustomAnimations(AnimationType.popEnterFakeResourceId, AnimationType.popExitFakeResourceId); } transaction.replace(this.containerViewId, backstackEntry.fragment, backstackEntry.fragmentTag); - transaction.commit(); + transaction.commitAllowingStateLoss(); } public _removeEntry(removed: BackstackEntry): void { @@ -470,6 +503,27 @@ export class Frame extends FrameBase { } } +function getAnimatorState(entry: BackstackEntry): AnimatorState { + const expandedEntry = entry; + const animatorState = {}; + animatorState.enterAnimator = expandedEntry.enterAnimator; + animatorState.exitAnimator = expandedEntry.exitAnimator; + animatorState.popEnterAnimator = expandedEntry.popEnterAnimator; + animatorState.popExitAnimator = expandedEntry.popExitAnimator; + animatorState.transitionName = expandedEntry.transitionName; + + return animatorState; +} + +function restoreAnimatorState(entry: BackstackEntry, snapshot: AnimatorState): void { + const expandedEntry = entry; + expandedEntry.enterAnimator = snapshot.enterAnimator; + expandedEntry.exitAnimator = snapshot.exitAnimator; + expandedEntry.popEnterAnimator = snapshot.popEnterAnimator; + expandedEntry.popExitAnimator = snapshot.popExitAnimator; + expandedEntry.transitionName = snapshot.transitionName; +} + function clearEntry(entry: BackstackEntry): void { if (entry.fragment) { _clearFragment(entry); @@ -786,16 +840,6 @@ class FragmentCallbacksImplementation implements AndroidFragmentCallbacks { traceWrite(`${fragment}.onDestroyView()`, traceCategories.NativeLifecycle); } - // fixes 'java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first'. - // on app resume in nested frame scenarios with support library version greater than 26.0.0 - const view = fragment.getView(); - if (view != null) { - const viewParent = view.getParent(); - if (viewParent instanceof android.view.ViewGroup) { - viewParent.removeView(view); - } - } - superFunc.call(fragment); } diff --git a/tns-core-modules/ui/tab-view/tab-view.android.ts b/tns-core-modules/ui/tab-view/tab-view.android.ts index f12e115ac..041b7a716 100644 --- a/tns-core-modules/ui/tab-view/tab-view.android.ts +++ b/tns-core-modules/ui/tab-view/tab-view.android.ts @@ -317,8 +317,13 @@ export class TabViewItem extends TabViewItemBase { } } + // TODO: can happen in a modal tabview scenario when the modal dialog fragment is already removed if (!tabFragment) { - throw new Error(`Could not get child fragment manager for tab item with index ${this.index}`); + if (traceEnabled()) { + traceWrite(`Could not get child fragment manager for tab item with index ${this.index}`, traceCategory); + } + + return (tabView)._getRootFragmentManager(); } return tabFragment.getChildFragmentManager(); From e48782511daa25ce1ce05157e65bc2dcfa1f79e0 Mon Sep 17 00:00:00 2001 From: Martin Yankov Date: Fri, 26 Oct 2018 14:05:14 +0300 Subject: [PATCH 02/29] fix-next: handle action bar safe area nesting (#6455) --- apps/app/ui-tests-app/main-page.ts | 1 + .../nested-frames/full-screen-n-n.xml | 10 +++++ .../nested-frames/full-screen-n-y.xml | 10 +++++ .../nested-frames/full-screen-y-n.xml | 10 +++++ .../nested-frames/full-screen-y-y.xml | 10 +++++ .../ui-tests-app/nested-frames/main-page.ts | 28 ++++++++++++++ .../ui-tests-app/nested-frames/main-page.xml | 6 +++ .../nested-frames/mid-screen-n-n.xml | 12 ++++++ .../nested-frames/mid-screen-n-y.xml | 12 ++++++ .../nested-frames/mid-screen-y-n.xml | 12 ++++++ .../nested-frames/mid-screen-y-y.xml | 12 ++++++ .../nested-frames/nested-page.xml | 13 +++++++ .../ui-tests-app/nested-frames/tab-n-n.xml | 14 +++++++ .../ui-tests-app/nested-frames/tab-n-y.xml | 14 +++++++ .../ui-tests-app/nested-frames/tab-y-n.xml | 14 +++++++ .../ui-tests-app/nested-frames/tab-y-y.xml | 14 +++++++ tns-core-modules/ui/core/view/view.ios.ts | 38 +++++++++++-------- tns-core-modules/ui/frame/frame.ios.ts | 2 +- tns-core-modules/ui/page/page.ios.ts | 24 ++++++++++++ 19 files changed, 240 insertions(+), 16 deletions(-) create mode 100644 apps/app/ui-tests-app/nested-frames/full-screen-n-n.xml create mode 100644 apps/app/ui-tests-app/nested-frames/full-screen-n-y.xml create mode 100644 apps/app/ui-tests-app/nested-frames/full-screen-y-n.xml create mode 100644 apps/app/ui-tests-app/nested-frames/full-screen-y-y.xml create mode 100644 apps/app/ui-tests-app/nested-frames/main-page.ts create mode 100644 apps/app/ui-tests-app/nested-frames/main-page.xml create mode 100644 apps/app/ui-tests-app/nested-frames/mid-screen-n-n.xml create mode 100644 apps/app/ui-tests-app/nested-frames/mid-screen-n-y.xml create mode 100644 apps/app/ui-tests-app/nested-frames/mid-screen-y-n.xml create mode 100644 apps/app/ui-tests-app/nested-frames/mid-screen-y-y.xml create mode 100644 apps/app/ui-tests-app/nested-frames/nested-page.xml create mode 100644 apps/app/ui-tests-app/nested-frames/tab-n-n.xml create mode 100644 apps/app/ui-tests-app/nested-frames/tab-n-y.xml create mode 100644 apps/app/ui-tests-app/nested-frames/tab-y-n.xml create mode 100644 apps/app/ui-tests-app/nested-frames/tab-y-y.xml diff --git a/apps/app/ui-tests-app/main-page.ts b/apps/app/ui-tests-app/main-page.ts index 027c174e3..1fd62f2a5 100644 --- a/apps/app/ui-tests-app/main-page.ts +++ b/apps/app/ui-tests-app/main-page.ts @@ -35,6 +35,7 @@ export function pageLoaded(args: EventData) { examples.set("webview", "web-view/main-page"); examples.set("progress-bar", "progress-bar/main-page"); examples.set("date-picker", "date-picker/date-picker"); + examples.set("nested-frames", "nested-frames/main-page"); page.bindingContext = new MainPageViewModel(wrapLayout, examples); const parent = page.getViewById("parentLayout"); diff --git a/apps/app/ui-tests-app/nested-frames/full-screen-n-n.xml b/apps/app/ui-tests-app/nested-frames/full-screen-n-n.xml new file mode 100644 index 000000000..3260e8f93 --- /dev/null +++ b/apps/app/ui-tests-app/nested-frames/full-screen-n-n.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/apps/app/ui-tests-app/nested-frames/full-screen-n-y.xml b/apps/app/ui-tests-app/nested-frames/full-screen-n-y.xml new file mode 100644 index 000000000..87b5d0c06 --- /dev/null +++ b/apps/app/ui-tests-app/nested-frames/full-screen-n-y.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/apps/app/ui-tests-app/nested-frames/full-screen-y-n.xml b/apps/app/ui-tests-app/nested-frames/full-screen-y-n.xml new file mode 100644 index 000000000..071ac75f0 --- /dev/null +++ b/apps/app/ui-tests-app/nested-frames/full-screen-y-n.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/apps/app/ui-tests-app/nested-frames/full-screen-y-y.xml b/apps/app/ui-tests-app/nested-frames/full-screen-y-y.xml new file mode 100644 index 000000000..fd53bf93c --- /dev/null +++ b/apps/app/ui-tests-app/nested-frames/full-screen-y-y.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/apps/app/ui-tests-app/nested-frames/main-page.ts b/apps/app/ui-tests-app/nested-frames/main-page.ts new file mode 100644 index 000000000..1c01b725c --- /dev/null +++ b/apps/app/ui-tests-app/nested-frames/main-page.ts @@ -0,0 +1,28 @@ +import { EventData } from "tns-core-modules/data/observable"; +import { SubMainPageViewModel } from "../sub-main-page-view-model"; +import { WrapLayout } from "tns-core-modules/ui/layouts/wrap-layout"; +import { Page } from "tns-core-modules/ui/page"; + +export function pageLoaded(args: EventData) { + const page = args.object; + const wrapLayout = page.getViewById("wrapLayoutWithExamples"); + page.bindingContext = new SubMainPageViewModel(wrapLayout, loadExamples()); +} + +export function loadExamples() { + const examples = new Map(); + examples.set("full-screen-n-n", "nested-frames/full-screen-n-n"); + examples.set("full-screen-n-y", "nested-frames/full-screen-n-y"); + examples.set("full-screen-y-n", "nested-frames/full-screen-y-n"); + examples.set("full-screen-y-y", "nested-frames/full-screen-y-y"); + examples.set("mid-screen-n-n", "nested-frames/mid-screen-n-n"); + examples.set("mid-screen-n-y", "nested-frames/mid-screen-n-y"); + examples.set("mid-screen-y-n", "nested-frames/mid-screen-y-n"); + examples.set("mid-screen-y-y", "nested-frames/mid-screen-y-y"); + examples.set("tab-y-y", "nested-frames/tab-y-y"); + examples.set("tab-n-y", "nested-frames/tab-n-y"); + examples.set("tab-y-n", "nested-frames/tab-y-n"); + examples.set("tab-n-n", "nested-frames/tab-n-n"); + + return examples; +} \ No newline at end of file diff --git a/apps/app/ui-tests-app/nested-frames/main-page.xml b/apps/app/ui-tests-app/nested-frames/main-page.xml new file mode 100644 index 000000000..33306f0d0 --- /dev/null +++ b/apps/app/ui-tests-app/nested-frames/main-page.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/apps/app/ui-tests-app/nested-frames/mid-screen-n-n.xml b/apps/app/ui-tests-app/nested-frames/mid-screen-n-n.xml new file mode 100644 index 000000000..4f9548a0f --- /dev/null +++ b/apps/app/ui-tests-app/nested-frames/mid-screen-n-n.xml @@ -0,0 +1,12 @@ + + + + + + + + + + > + + \ No newline at end of file diff --git a/apps/app/ui-tests-app/nested-frames/mid-screen-n-y.xml b/apps/app/ui-tests-app/nested-frames/mid-screen-n-y.xml new file mode 100644 index 000000000..83d94d5e2 --- /dev/null +++ b/apps/app/ui-tests-app/nested-frames/mid-screen-n-y.xml @@ -0,0 +1,12 @@ + + + + + + + + + + > + + \ No newline at end of file diff --git a/apps/app/ui-tests-app/nested-frames/mid-screen-y-n.xml b/apps/app/ui-tests-app/nested-frames/mid-screen-y-n.xml new file mode 100644 index 000000000..09a123533 --- /dev/null +++ b/apps/app/ui-tests-app/nested-frames/mid-screen-y-n.xml @@ -0,0 +1,12 @@ + + + + + + + + + + > + + \ No newline at end of file diff --git a/apps/app/ui-tests-app/nested-frames/mid-screen-y-y.xml b/apps/app/ui-tests-app/nested-frames/mid-screen-y-y.xml new file mode 100644 index 000000000..80d59b599 --- /dev/null +++ b/apps/app/ui-tests-app/nested-frames/mid-screen-y-y.xml @@ -0,0 +1,12 @@ + + + + + + + + + + > + + \ No newline at end of file diff --git a/apps/app/ui-tests-app/nested-frames/nested-page.xml b/apps/app/ui-tests-app/nested-frames/nested-page.xml new file mode 100644 index 000000000..baa973660 --- /dev/null +++ b/apps/app/ui-tests-app/nested-frames/nested-page.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/app/ui-tests-app/nested-frames/tab-n-n.xml b/apps/app/ui-tests-app/nested-frames/tab-n-n.xml new file mode 100644 index 000000000..4c90b270f --- /dev/null +++ b/apps/app/ui-tests-app/nested-frames/tab-n-n.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/app/ui-tests-app/nested-frames/tab-n-y.xml b/apps/app/ui-tests-app/nested-frames/tab-n-y.xml new file mode 100644 index 000000000..0f3b8096a --- /dev/null +++ b/apps/app/ui-tests-app/nested-frames/tab-n-y.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/app/ui-tests-app/nested-frames/tab-y-n.xml b/apps/app/ui-tests-app/nested-frames/tab-y-n.xml new file mode 100644 index 000000000..f9733fb12 --- /dev/null +++ b/apps/app/ui-tests-app/nested-frames/tab-y-n.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/app/ui-tests-app/nested-frames/tab-y-y.xml b/apps/app/ui-tests-app/nested-frames/tab-y-y.xml new file mode 100644 index 000000000..2d06aab71 --- /dev/null +++ b/apps/app/ui-tests-app/nested-frames/tab-y-y.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tns-core-modules/ui/core/view/view.ios.ts b/tns-core-modules/ui/core/view/view.ios.ts index 13abd0766..0c1a5b15b 100644 --- a/tns-core-modules/ui/core/view/view.ios.ts +++ b/tns-core-modules/ui/core/view/view.ios.ts @@ -704,21 +704,6 @@ export namespace ios { } export function layoutView(controller: UIViewController, owner: View): void { - if (majorVersion >= 11) { - // apply parent page additional top insets if any. The scenario is when there is a parent page with action bar. - const parentPage = getAncestor(owner, "Page"); - if (parentPage) { - const parentPageInsetsTop = parentPage.viewController.view.safeAreaInsets.top; - const currentInsetsTop = controller.view.safeAreaInsets.top; - const additionalInsetsTop = parentPageInsetsTop - currentInsetsTop; - - if (additionalInsetsTop > 0) { - const additionalInsets = new UIEdgeInsets({ top: additionalInsetsTop, left: 0, bottom: 0, right: 0 }); - controller.additionalSafeAreaInsets = additionalInsets; - } - } - } - let layoutGuide = controller.view.safeAreaLayoutGuide; if (!layoutGuide) { traceWrite(`safeAreaLayoutGuide during layout of ${owner}. Creating fallback constraints, but layout might be wrong.`, @@ -897,6 +882,29 @@ export namespace ios { super.viewDidLayoutSubviews(); const owner = this.owner.get(); if (owner) { + if (majorVersion >= 11) { + // Handle nested UILayoutViewController safe area application. + // Currently, UILayoutViewController can be nested only in a TabView. + // The TabView itself is handled by the OS, so we check the TabView's parent (usually a Page, but can be a Layout). + const tabViewItem = owner.parent; + const tabView = tabViewItem && tabViewItem.parent; + const parent = tabView && tabView.parent; + if (parent) { + const parentPageInsetsTop = parent.nativeViewProtected.safeAreaInsets.top; + const currentInsetsTop = this.view.safeAreaInsets.top; + const additionalInsetsTop = Math.max(parentPageInsetsTop - currentInsetsTop, 0); + + const parentPageInsetsBottom = parent.nativeViewProtected.safeAreaInsets.bottom; + const currentInsetsBottom = this.view.safeAreaInsets.bottom; + const additionalInsetsBottom = Math.max(parentPageInsetsBottom - currentInsetsBottom, 0); + + if (additionalInsetsTop > 0 || additionalInsetsBottom > 0) { + const additionalInsets = new UIEdgeInsets({ top: additionalInsetsTop, left: 0, bottom: additionalInsetsBottom, right: 0 }); + this.additionalSafeAreaInsets = additionalInsets; + } + } + } + layoutView(this, owner); } } diff --git a/tns-core-modules/ui/frame/frame.ios.ts b/tns-core-modules/ui/frame/frame.ios.ts index 7b826c421..28ba4a2f6 100644 --- a/tns-core-modules/ui/frame/frame.ios.ts +++ b/tns-core-modules/ui/frame/frame.ios.ts @@ -602,7 +602,7 @@ class iOSFrame implements iOSFrameDefinition { } public set showNavigationBar(value: boolean) { this._showNavigationBar = value; - this._controller.setNavigationBarHiddenAnimated(!value, true); + this._controller.setNavigationBarHiddenAnimated(!value, !this._disableNavBarAnimation); } public get navBarVisibility(): "auto" | "never" | "always" { diff --git a/tns-core-modules/ui/page/page.ios.ts b/tns-core-modules/ui/page/page.ios.ts index 6d1e8f9c0..4e737ce36 100644 --- a/tns-core-modules/ui/page/page.ios.ts +++ b/tns-core-modules/ui/page/page.ios.ts @@ -217,6 +217,30 @@ class UIViewControllerImpl extends UIViewController { if (owner) { // layout(owner.actionBar) // layout(owner.content) + + if (majorVersion >= 11) { + // Handle nested Page safe area insets application. + // A Page is nested if its Frame has a parent. + // If the Page is nested, cross check safe area insets on top and bottom with Frame parent. + const frame = owner.parent; + // There is a legacy scenario where Page is not in a Frame - the root of a Modal View, so it has no parent. + const frameParent = frame && frame.parent; + if (frameParent) { + const parentPageInsetsTop = frameParent.nativeViewProtected.safeAreaInsets.top; + const currentInsetsTop = this.view.safeAreaInsets.top; + const additionalInsetsTop = Math.max(parentPageInsetsTop - currentInsetsTop, 0); + + const parentPageInsetsBottom = frameParent.nativeViewProtected.safeAreaInsets.bottom; + const currentInsetsBottom = this.view.safeAreaInsets.bottom; + const additionalInsetsBottom = Math.max(parentPageInsetsBottom - currentInsetsBottom, 0); + + if (additionalInsetsTop > 0 || additionalInsetsBottom > 0) { + const additionalInsets = new UIEdgeInsets({ top: additionalInsetsTop, left: 0, bottom: additionalInsetsBottom, right: 0 }); + this.additionalSafeAreaInsets = additionalInsets; + } + } + } + iosView.layoutView(this, owner); } } From 745a79f5fa040e65deb2094f39fbc42f6ec36ba9 Mon Sep 17 00:00:00 2001 From: daviditsygin Date: Fri, 26 Oct 2018 04:47:35 -0700 Subject: [PATCH 03/29] docs(iOS): update HtmlView documentation link (#6335) The old link was a 404. --- tns-core-modules/ui/html-view/html-view.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tns-core-modules/ui/html-view/html-view.d.ts b/tns-core-modules/ui/html-view/html-view.d.ts index dfebe1621..f553ed7b2 100644 --- a/tns-core-modules/ui/html-view/html-view.d.ts +++ b/tns-core-modules/ui/html-view/html-view.d.ts @@ -7,7 +7,7 @@ import { View, Property } from "../core/view"; /** * Represents a view with html content. Use this component instead WebView when you want to show just static HTML content. - * [iOS support](https://developer.apple.com/library/ios/documentation/UIKit/Reference/NSAttributedString_UIKit_Additions/#//apple_ref/occ/instm/NSAttributedString/initWithData:options:documentAttributes:error:) + * [iOS support](https://developer.apple.com/documentation/foundation/nsattributedstring/1524613-initwithdata) * [android support](http://developer.android.com/reference/android/text/Html.html) */ export class HtmlView extends View { From f5cca13a7caacd29ebf0442f909d7f96dbf6867f Mon Sep 17 00:00:00 2001 From: Todor Petrov Date: Fri, 26 Oct 2018 17:03:23 +0300 Subject: [PATCH 04/29] fix-next: correct raising of layoutChanged event (#6457) Currently the layoutChanged event can be raised even when there is no change due to safe area calculations. --- tns-core-modules/ui/core/view/view.ios.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tns-core-modules/ui/core/view/view.ios.ts b/tns-core-modules/ui/core/view/view.ios.ts index 0c1a5b15b..80e74267b 100644 --- a/tns-core-modules/ui/core/view/view.ios.ts +++ b/tns-core-modules/ui/core/view/view.ios.ts @@ -154,7 +154,8 @@ export class View extends ViewCommon { } public _setNativeViewFrame(nativeView: UIView, frame: CGRect): void { - if (!CGRectEqualToRect(nativeView.frame, frame)) { + let oldFrame = this._cachedFrame || nativeView.frame; + if (!CGRectEqualToRect(oldFrame, frame)) { if (traceEnabled()) { traceWrite(this + " :_setNativeViewFrame: " + JSON.stringify(ios.getPositionFromFrame(frame)), traceCategories.Layout); } From 448936d6e6bf03c79e17648e8912a4f0eea0b43f Mon Sep 17 00:00:00 2001 From: Manol Donev Date: Fri, 26 Oct 2018 17:09:19 +0300 Subject: [PATCH 05/29] fix-next(android): restore fade animation for simulated nav (#6463) --- tns-core-modules/ui/frame/frame.android.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tns-core-modules/ui/frame/frame.android.ts b/tns-core-modules/ui/frame/frame.android.ts index bfb911d2c..d1b7f1f4a 100644 --- a/tns-core-modules/ui/frame/frame.android.ts +++ b/tns-core-modules/ui/frame/frame.android.ts @@ -364,7 +364,7 @@ export class Frame extends FrameBase { const newFragmentTag = `fragment${fragmentId}[${navDepth}]`; const newFragment = this.createFragment(newEntry, newFragmentTag); const transaction = manager.beginTransaction(); - const animated = currentEntry ? this._getIsAnimatedNavigation(newEntry.entry) : false; + const animated = this._getIsAnimatedNavigation(newEntry.entry); // NOTE: Don't use transition for the initial navigation (same as on iOS) // On API 21+ transition won't be triggered unless there was at least one // layout pass so we will wait forever for transitionCompleted handler... From 7625d6cb217c0947a0009b2ccd8e68f342188e83 Mon Sep 17 00:00:00 2001 From: Martin Yankov Date: Mon, 29 Oct 2018 19:22:30 +0200 Subject: [PATCH 06/29] fix-next: handle view controller nesting in ng (#6472) --- tns-core-modules/ui/core/view/view.ios.ts | 10 +++++++++- tns-core-modules/ui/page/page.ios.ts | 10 +++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/tns-core-modules/ui/core/view/view.ios.ts b/tns-core-modules/ui/core/view/view.ios.ts index 80e74267b..327d748ed 100644 --- a/tns-core-modules/ui/core/view/view.ios.ts +++ b/tns-core-modules/ui/core/view/view.ios.ts @@ -889,7 +889,15 @@ export namespace ios { // The TabView itself is handled by the OS, so we check the TabView's parent (usually a Page, but can be a Layout). const tabViewItem = owner.parent; const tabView = tabViewItem && tabViewItem.parent; - const parent = tabView && tabView.parent; + let parent = tabView && tabView.parent; + + // Handle Angular scenario where TabView is in a ProxyViewContainer + // Not using instanceof ProxyViewContainer to avoid circular dependency + // TODO: Try moving UILayoutViewController out of view module + if (parent && !parent.nativeViewProtected) { + parent = parent.parent; + } + if (parent) { const parentPageInsetsTop = parent.nativeViewProtected.safeAreaInsets.top; const currentInsetsTop = this.view.safeAreaInsets.top; diff --git a/tns-core-modules/ui/page/page.ios.ts b/tns-core-modules/ui/page/page.ios.ts index 4e737ce36..bf89a2cbc 100644 --- a/tns-core-modules/ui/page/page.ios.ts +++ b/tns-core-modules/ui/page/page.ios.ts @@ -224,7 +224,15 @@ class UIViewControllerImpl extends UIViewController { // If the Page is nested, cross check safe area insets on top and bottom with Frame parent. const frame = owner.parent; // There is a legacy scenario where Page is not in a Frame - the root of a Modal View, so it has no parent. - const frameParent = frame && frame.parent; + let frameParent = frame && frame.parent; + + // Handle Angular scenario where TabView is in a ProxyViewContainer + // Not using instanceof ProxyViewContainer to avoid circular dependency + // TODO: Try moving UIViewControllerImpl out of page module + if (frameParent && !frameParent.nativeViewProtected) { + frameParent = frameParent.parent; + } + if (frameParent) { const parentPageInsetsTop = frameParent.nativeViewProtected.safeAreaInsets.top; const currentInsetsTop = this.view.safeAreaInsets.top; From 95c4ec5a96e95230da50ce51385fb96ec1225bb1 Mon Sep 17 00:00:00 2001 From: Manol Donev Date: Mon, 29 Oct 2018 19:43:34 +0200 Subject: [PATCH 07/29] Revert "fix-next(android): restore fade animation for simulated nav (#6463)" (#6473) This reverts commit 448936d6e6bf03c79e17648e8912a4f0eea0b43f. --- tns-core-modules/ui/frame/frame.android.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tns-core-modules/ui/frame/frame.android.ts b/tns-core-modules/ui/frame/frame.android.ts index d1b7f1f4a..bfb911d2c 100644 --- a/tns-core-modules/ui/frame/frame.android.ts +++ b/tns-core-modules/ui/frame/frame.android.ts @@ -364,7 +364,7 @@ export class Frame extends FrameBase { const newFragmentTag = `fragment${fragmentId}[${navDepth}]`; const newFragment = this.createFragment(newEntry, newFragmentTag); const transaction = manager.beginTransaction(); - const animated = this._getIsAnimatedNavigation(newEntry.entry); + const animated = currentEntry ? this._getIsAnimatedNavigation(newEntry.entry) : false; // NOTE: Don't use transition for the initial navigation (same as on iOS) // On API 21+ transition won't be triggered unless there was at least one // layout pass so we will wait forever for transitionCompleted handler... From 58c9d424f5b23b5321d67de36627abc05ea6975c Mon Sep 17 00:00:00 2001 From: Martin Yankov Date: Tue, 30 Oct 2018 14:00:00 +0200 Subject: [PATCH 08/29] fix-next(ios): handle nesting in proxyViewContainer ng (#6475) --- tns-core-modules/ui/core/view/view.ios.ts | 3 ++- tns-core-modules/ui/page/page.ios.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tns-core-modules/ui/core/view/view.ios.ts b/tns-core-modules/ui/core/view/view.ios.ts index 327d748ed..8352f7ae6 100644 --- a/tns-core-modules/ui/core/view/view.ios.ts +++ b/tns-core-modules/ui/core/view/view.ios.ts @@ -892,9 +892,10 @@ export namespace ios { let parent = tabView && tabView.parent; // Handle Angular scenario where TabView is in a ProxyViewContainer + // It is possible to wrap components in ProxyViewContainers indefinitely // Not using instanceof ProxyViewContainer to avoid circular dependency // TODO: Try moving UILayoutViewController out of view module - if (parent && !parent.nativeViewProtected) { + while (parent && !parent.nativeViewProtected) { parent = parent.parent; } diff --git a/tns-core-modules/ui/page/page.ios.ts b/tns-core-modules/ui/page/page.ios.ts index bf89a2cbc..3ca6fe887 100644 --- a/tns-core-modules/ui/page/page.ios.ts +++ b/tns-core-modules/ui/page/page.ios.ts @@ -227,9 +227,10 @@ class UIViewControllerImpl extends UIViewController { let frameParent = frame && frame.parent; // Handle Angular scenario where TabView is in a ProxyViewContainer + // It is possible to wrap components in ProxyViewContainers indefinitely // Not using instanceof ProxyViewContainer to avoid circular dependency // TODO: Try moving UIViewControllerImpl out of page module - if (frameParent && !frameParent.nativeViewProtected) { + while (frameParent && !frameParent.nativeViewProtected) { frameParent = frameParent.parent; } From 371fc9b647cb13a9970d3217746e6531a7ca2681 Mon Sep 17 00:00:00 2001 From: Alexander Djenkov Date: Tue, 30 Oct 2018 14:24:55 +0200 Subject: [PATCH 09/29] fix(tab-view-android): change androidOffscreenTabLimit to 1 when using bottom tabs (#6476) --- tns-core-modules/ui/tab-view/tab-view.android.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tns-core-modules/ui/tab-view/tab-view.android.ts b/tns-core-modules/ui/tab-view/tab-view.android.ts index 041b7a716..18165feb6 100644 --- a/tns-core-modules/ui/tab-view/tab-view.android.ts +++ b/tns-core-modules/ui/tab-view/tab-view.android.ts @@ -482,7 +482,7 @@ export class TabView extends TabViewBase { public _loadUnloadTabItems(newIndex: number) { const items = this.items; const lastIndex = this.items.length - 1; - const offsideItems = this.androidTabsPosition === "top" ? this.androidOffscreenTabLimit : 0; + const offsideItems = this.androidTabsPosition === "top" ? this.androidOffscreenTabLimit : 1; let toUnload = []; let toLoad = []; From eca938d9defb3dd32af6e96829a19a6a4ba8df0b Mon Sep 17 00:00:00 2001 From: Dan Bock Date: Thu, 1 Nov 2018 09:44:10 -0400 Subject: [PATCH 10/29] chore(apps): add tslib dep and update tsconfig.json file (#6464) * fix: don't crash on startup due to tslib not being found * fix: prevent error 'sourceMap' cannot be specified with option 'inlineSourceMap' * Revert "fix: prevent error 'sourceMap' cannot be specified with option 'inlineSourceMap'" This reverts commit c9d96de10ad9256aa7c42b1db55b5117443e2027. * chore: updates tsconfig.json file --- apps/package.json | 3 ++- apps/tsconfig.json | 39 ++++++++++++++++++++++----------------- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/apps/package.json b/apps/package.json index 70c68f984..5d2c10461 100644 --- a/apps/package.json +++ b/apps/package.json @@ -13,7 +13,8 @@ } }, "dependencies": { - "tns-core-modules": "*" + "tns-core-modules": "*", + "tslib": "^1.9.3" }, "devDependencies": { "babel-traverse": "6.10.4", diff --git a/apps/tsconfig.json b/apps/tsconfig.json index 4f5d9b3d1..ac7907a6f 100644 --- a/apps/tsconfig.json +++ b/apps/tsconfig.json @@ -1,23 +1,28 @@ { - "extends": "../tsconfig.shared", - "exclude": [ - "node_modules", - "platforms" - ], "compilerOptions": { - "baseUrl": ".", - "paths": { - "*": [ - "./node_modules/tns-core-modules/*", - "./node_modules/*" - ], - "~/*": [ - "app/*" - ] - }, + "module": "commonjs", + "target": "es5", + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "noEmitHelpers": true, + "noEmitOnError": true, "lib": [ "es6", "dom" - ] - } + ], + "baseUrl": ".", + "paths": { + "~/*": [ + "app/*" + ], + "*": [ + "./node_modules/tns-core-modules/*", + "./node_modules/*" + ] + } + }, + "exclude": [ + "node_modules", + "platforms" + ] } \ No newline at end of file From f54f7c39854f982f70143ad3e17132fc2d6ba3dc Mon Sep 17 00:00:00 2001 From: Vasil Chimev Date: Fri, 2 Nov 2018 10:52:55 +0200 Subject: [PATCH 11/29] Revert "chore(apps): add tslib dep and update tsconfig.json file (#6464)" This reverts commit eca938d9defb3dd32af6e96829a19a6a4ba8df0b. --- apps/package.json | 3 +-- apps/tsconfig.json | 41 ++++++++++++++++++----------------------- 2 files changed, 19 insertions(+), 25 deletions(-) diff --git a/apps/package.json b/apps/package.json index 5d2c10461..70c68f984 100644 --- a/apps/package.json +++ b/apps/package.json @@ -13,8 +13,7 @@ } }, "dependencies": { - "tns-core-modules": "*", - "tslib": "^1.9.3" + "tns-core-modules": "*" }, "devDependencies": { "babel-traverse": "6.10.4", diff --git a/apps/tsconfig.json b/apps/tsconfig.json index ac7907a6f..4f5d9b3d1 100644 --- a/apps/tsconfig.json +++ b/apps/tsconfig.json @@ -1,28 +1,23 @@ { - "compilerOptions": { - "module": "commonjs", - "target": "es5", - "experimentalDecorators": true, - "emitDecoratorMetadata": true, - "noEmitHelpers": true, - "noEmitOnError": true, - "lib": [ - "es6", - "dom" - ], - "baseUrl": ".", - "paths": { - "~/*": [ - "app/*" - ], - "*": [ - "./node_modules/tns-core-modules/*", - "./node_modules/*" - ] - } - }, + "extends": "../tsconfig.shared", "exclude": [ "node_modules", "platforms" - ] + ], + "compilerOptions": { + "baseUrl": ".", + "paths": { + "*": [ + "./node_modules/tns-core-modules/*", + "./node_modules/*" + ], + "~/*": [ + "app/*" + ] + }, + "lib": [ + "es6", + "dom" + ] + } } \ No newline at end of file From 2933a9a9340eb40a612b29e40b4e307d9fd4ab2e Mon Sep 17 00:00:00 2001 From: Vladimir Amiorkov Date: Fri, 2 Nov 2018 15:33:17 +0200 Subject: [PATCH 12/29] fix: Resolve incorrect name of listener when unsubscribing (#6487) --- tns-core-modules/ui/styling/style-scope.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tns-core-modules/ui/styling/style-scope.ts b/tns-core-modules/ui/styling/style-scope.ts index 8e0df7dd1..8f3cf607c 100644 --- a/tns-core-modules/ui/styling/style-scope.ts +++ b/tns-core-modules/ui/styling/style-scope.ts @@ -506,7 +506,7 @@ export class CssState { this._appliedChangeMap.forEach((changes, view) => { if (changes.attributes) { changes.attributes.forEach(attribute => { - view.removeEventListener("onPropertyChanged:" + attribute, this._onDynamicStateChangeHandler); + view.removeEventListener(attribute + "Change", this._onDynamicStateChangeHandler); }); } if (changes.pseudoClasses) { From fac970e75304995bbb6d83010eaef712da664fec Mon Sep 17 00:00:00 2001 From: Manol Donev Date: Fri, 2 Nov 2018 17:31:52 +0200 Subject: [PATCH 13/29] fix(android): back navigation on app suspend/resume (#6489) --- tns-core-modules/ui/frame/frame.android.ts | 31 +++++++++++++++------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/tns-core-modules/ui/frame/frame.android.ts b/tns-core-modules/ui/frame/frame.android.ts index bfb911d2c..3f98a8ef0 100644 --- a/tns-core-modules/ui/frame/frame.android.ts +++ b/tns-core-modules/ui/frame/frame.android.ts @@ -25,10 +25,10 @@ import { createViewFromEntry } from "../builder"; export * from "./frame-common"; interface AnimatorState { - enterAnimator: android.animation.Animator; - exitAnimator: android.animation.Animator; - popEnterAnimator: android.animation.Animator; - popExitAnimator: android.animation.Animator; + enterAnimator: any; + exitAnimator: any; + popEnterAnimator: any; + popExitAnimator: any; transitionName: string; } @@ -306,10 +306,8 @@ export class Frame extends FrameBase { // restore cached animation settings if we just completed simulated first navigation (no animation) if (this._cachedAnimatorState) { restoreAnimatorState(this._currentEntry, this._cachedAnimatorState); - this._cachedAnimatorState = null; } - } public onBackPressed(): boolean { @@ -503,13 +501,26 @@ export class Frame extends FrameBase { } } +function cloneExpandedAnimator(expandedAnimator: any) { + if (!expandedAnimator) { + return null; + } + + const clone = expandedAnimator.clone(); + clone.entry = expandedAnimator.entry; + clone.transitionType = expandedAnimator.transitionType; + + return clone; +} + function getAnimatorState(entry: BackstackEntry): AnimatorState { const expandedEntry = entry; const animatorState = {}; - animatorState.enterAnimator = expandedEntry.enterAnimator; - animatorState.exitAnimator = expandedEntry.exitAnimator; - animatorState.popEnterAnimator = expandedEntry.popEnterAnimator; - animatorState.popExitAnimator = expandedEntry.popExitAnimator; + + animatorState.enterAnimator = cloneExpandedAnimator(expandedEntry.enterAnimator); + animatorState.exitAnimator = cloneExpandedAnimator(expandedEntry.exitAnimator); + animatorState.popEnterAnimator = cloneExpandedAnimator(expandedEntry.popEnterAnimator); + animatorState.popExitAnimator = cloneExpandedAnimator(expandedEntry.popExitAnimator); animatorState.transitionName = expandedEntry.transitionName; return animatorState; From c41e3e05a0a6d82786b752ad768af2af8129b6b0 Mon Sep 17 00:00:00 2001 From: Vasil Chimev Date: Mon, 5 Nov 2018 15:52:34 +0200 Subject: [PATCH 14/29] docs: add 5.0.0 changelog (#6485) * docs: add 5.0.0 changelog * docs: merge 5.0.0 RC and 5.0.0 changelogs --- CHANGELOG.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc93ce497..76d4473ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,17 +1,20 @@ Cross Platform Modules Changelog ============================== - -## [5.0.0 - RC](https://github.com/NativeScript/NativeScript/compare/4.2.1...5.0.0-rc) (2018-10-17) + +# [5.0.0](https://github.com/NativeScript/NativeScript/compare/4.2.1...5.0.0) (2018-11-01) ### Bug Fixes * don't crash on missing resources in tab-view and action-bar ([#6388](https://github.com/NativeScript/NativeScript/issues/6388)) ([56a1b12](https://github.com/NativeScript/NativeScript/commit/56a1b12)) * nested fragments interact through child fragment manager ([#6293](https://github.com/NativeScript/NativeScript/issues/6293)) ([3071720](https://github.com/NativeScript/NativeScript/commit/3071720)) * Page and Frame isLoaded undefined checks ([#6255](https://github.com/NativeScript/NativeScript/issues/6255)) ([12fade7](https://github.com/NativeScript/NativeScript/commit/12fade7)) +* **connectivity:** making startMonitoring() behave on iOS as on Android ([#6373](https://github.com/NativeScript/NativeScript/issues/6373)) ([a58fc52](https://github.com/NativeScript/NativeScript/commit/a58fc52)) +* **observable-array:** reduce no longer ignores zero as initial value ([#6402](https://github.com/NativeScript/NativeScript/issues/6402)) ([c0438df](https://github.com/NativeScript/NativeScript/commit/c0438df)) * **modals:** application activityBackPressed event not fired for modals ([#6261](https://github.com/NativeScript/NativeScript/issues/6261)) ([8575c60](https://github.com/NativeScript/NativeScript/commit/8575c60)) * **list-view:** Layout list-view items on request ([#6159](https://github.com/NativeScript/NativeScript/issues/6159)) ([ec24c5a](https://github.com/NativeScript/NativeScript/commit/ec24c5a)) * **tab-view:** Title and icon positioning ([#6362](https://github.com/NativeScript/NativeScript/issues/6362)) ([e3d5f0d](https://github.com/NativeScript/NativeScript/commit/e3d5f0d)) +* **tab-view:** change androidOffscreenTabLimit to 1 when using bottom tabs for android([#6476](https://github.com/NativeScript/NativeScript/issues/6476)) ([371fc9b](https://github.com/NativeScript/NativeScript/commit/371fc9b)) * **android:** HEAD request should return statusCode ([7e89f94](https://github.com/NativeScript/NativeScript/commit/7e89f94)) * **android:** nested frames on app suspend/resume ([#6339](https://github.com/NativeScript/NativeScript/issues/6339)) ([0bf6dc2](https://github.com/NativeScript/NativeScript/commit/0bf6dc2)) * **android:** parallel navigations should not be triggered ([#6275](https://github.com/NativeScript/NativeScript/issues/6275)) ([6c9fa16](https://github.com/NativeScript/NativeScript/commit/6c9fa16)) @@ -26,6 +29,9 @@ Cross Platform Modules Changelog * add ability to pass touch event through parent view ([#6204](https://github.com/NativeScript/NativeScript/issues/6204)) ([2625683](https://github.com/NativeScript/NativeScript/commit/2625683)) * implement capitalization type option for prompt dialogs ([#6325](https://github.com/NativeScript/NativeScript/issues/6325)) ([ae6a661](https://github.com/NativeScript/NativeScript/commit/ae6a661)) * **application-settings:** implemented allKeys method ([#6371](https://github.com/NativeScript/NativeScript/issues/6371)) ([829d18b](https://github.com/NativeScript/NativeScript/commit/829d18b)) +* **frame:** add new actionBarVisibility property ([#6449](https://github.com/NativeScript/NativeScript/issues/6449)) ([0002624](https://github.com/NativeScript/NativeScript/commit/0002624)) +* **frame:** hardware back in parent frame when back states available ([#6446](https://github.com/NativeScript/NativeScript/issues/6446)) ([af651d6](https://github.com/NativeScript/NativeScript/commit/af651d6)) +* **grid:** implement addChildAtCell ([#6411](https://github.com/NativeScript/NativeScript/issues/6411)) ([a3f1493](https://github.com/NativeScript/NativeScript/commit/a3f1493)) * **image-asset-ios:** add autoScaleFactor option to switch auto scaling ([#6127](https://github.com/NativeScript/NativeScript/issues/6127)) ([81e63ee](https://github.com/NativeScript/NativeScript/commit/81e63ee)) * **styling:** Add two functions to control applicationAdditionalSelectors ([#6124](https://github.com/NativeScript/NativeScript/issues/6124)) ([85b8c01](https://github.com/NativeScript/NativeScript/commit/85b8c01)) * **tslib:** add tslib helpers to global ([#6351](https://github.com/NativeScript/NativeScript/issues/6351)) ([1232d1e](https://github.com/NativeScript/NativeScript/commit/1232d1e)) @@ -87,6 +93,8 @@ export function pageLoaded(args: EventData) { wrapLayout = page.getViewById("wrapLayout"); // or wrapLayout = page.getViewById("wrapLayout"); } ``` +* **android:** change androidOffscreenTabLimit to 1 when using bottom tabs of tab-view([#6476](https://github.com/NativeScript/NativeScript/issues/6476)) ([371fc9b](https://github.com/NativeScript/NativeScript/commit/371fc9b)) + * **ios:** widgets native view lifecycle refactoring - native view is now created right before they are added to visual tree ([#6102](https://github.com/NativeScript/NativeScript/issues/6102)) ([46705ee](https://github.com/NativeScript/NativeScript/commit/46705ee)): The iOS widgets native view lifecycle now matches the Android widgets. Before, the iOS native view was created in the widget constructor and you could manipulate the native view right after the widget is instantiated. After the refactoring, the widget's native view will be created when it's added to the visual tree. The most correct way to manipulate the native view is in the `loaded` event handler. From 41ba93de41d91b59cddebf5f27a3b54d0d1a7154 Mon Sep 17 00:00:00 2001 From: Manol Donev Date: Mon, 5 Nov 2018 16:17:51 +0200 Subject: [PATCH 15/29] fix(android): IllegalStateException with tabview&nested frames (#6495) --- tns-core-modules/ui/frame/frame.android.ts | 31 ++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tns-core-modules/ui/frame/frame.android.ts b/tns-core-modules/ui/frame/frame.android.ts index 3f98a8ef0..81d6cf7fc 100644 --- a/tns-core-modules/ui/frame/frame.android.ts +++ b/tns-core-modules/ui/frame/frame.android.ts @@ -859,7 +859,38 @@ class FragmentCallbacksImplementation implements AndroidFragmentCallbacks { if (traceEnabled()) { traceWrite(`${fragment}.onDestroy()`, traceCategories.NativeLifecycle); } + superFunc.call(fragment); + + const entry = this.entry; + if (!entry) { + traceError(`${fragment}.onDestroy: entry is null or undefined`); + return null; + } + + const page = entry.resolvedPage; + if (!page) { + traceError(`${fragment}.onDestroy: entry has no resolvedPage`); + return null; + } + + // fixes 'java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first'. + // on app resume in nested frame scenarios with support library version greater than 26.0.0 + // HACK: this whole code block shouldn't be necessary as the native view is supposedly removed from its parent + // right after onDestroyView(...) is called but for some reason the fragment view (page) still thinks it has a + // parent while its supposed parent believes it properly removed its children; in order to "force" the child to + // lose its parent we temporarily add it to the parent, and then remove it (addViewInLayout doesn't trigger layout pass) + const nativeView = page.nativeViewProtected; + if (nativeView != null) { + const parentView = nativeView.getParent(); + if (parentView instanceof android.view.ViewGroup) { + if (parentView.getChildCount() === 0) { + parentView.addViewInLayout(nativeView, -1, new org.nativescript.widgets.CommonLayoutParams()); + } + + parentView.removeView(nativeView); + } + } } @profile From 148544512375e5e976badf371cbe2f86bf3bb016 Mon Sep 17 00:00:00 2001 From: Todor Petrov Date: Tue, 6 Nov 2018 11:26:23 +0200 Subject: [PATCH 16/29] test(safe-area): add layoutChanged test (#6462) --- tests/app/ui/layouts/safe-area-tests.ts | 26 +++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/app/ui/layouts/safe-area-tests.ts b/tests/app/ui/layouts/safe-area-tests.ts index ec6e7aeca..befc69138 100644 --- a/tests/app/ui/layouts/safe-area-tests.ts +++ b/tests/app/ui/layouts/safe-area-tests.ts @@ -6,6 +6,8 @@ import * as platform from "tns-core-modules/platform"; import { ios as iosUtils } from "tns-core-modules/utils/utils"; import * as helper from "../helper"; import { parse } from "tns-core-modules/ui/builder"; +import { Page } from "tns-core-modules/ui/page"; +import { Label } from "tns-core-modules/ui/label"; import { dipToDp, left, top, right, bottom, height, width, equal, closeEnough, lessOrCloseEnough, greaterOrCloseEnough, check, @@ -37,6 +39,30 @@ export class SafeAreaTests extends testModule.UITest { // no operation }; + public test_layout_changed_event_count() { + const page = parse(` + + + + + + `); + let gridLayoutChangedCounter = 0; + let labelLayoutChangedCounter = 0; + const grid = page.getViewById("grid"); + grid.on(view.View.layoutChangedEvent, () => { + gridLayoutChangedCounter++; + }); + const label =