mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-11-05 13:26:48 +08:00
refactor: circular deps part 15
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { isEmbedded, getEmbeddedView } from '../embedding';
|
||||
import { setFragmentCallbacks } from '.';
|
||||
import { setFragmentCallbacks } from './frame-helper-for-android';
|
||||
|
||||
declare const com: any;
|
||||
|
||||
|
||||
342
packages/core/ui/frame/frame-helper-for-android.ts
Normal file
342
packages/core/ui/frame/frame-helper-for-android.ts
Normal file
@@ -0,0 +1,342 @@
|
||||
import { Trace } from '../../trace';
|
||||
import { _clearEntry, _clearFragment, _getAnimatedEntries, _reverseTransitions, _setAndroidFragmentTransitions, _updateTransitions } from './fragment.transitions';
|
||||
import type { BackstackEntry } from '.';
|
||||
import { profile } from '../../profiling';
|
||||
import { getNativeApp } from '../../application/helpers-common';
|
||||
import { Color } from '../../color';
|
||||
import type { Page } from '../page';
|
||||
import type { AndroidFrame as Frame } from '.';
|
||||
export const FRAMEID = '_frameId';
|
||||
export const CALLBACKS = '_callbacks';
|
||||
export const framesCache = new Array<WeakRef<any>>();
|
||||
|
||||
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;
|
||||
onPause(fragment: any, superFunc: Function): void;
|
||||
onResume(fragment: any, superFunc: Function): void;
|
||||
onStop(fragment: any, superFunc: Function): void;
|
||||
toStringOverride(fragment: any, superFunc: Function): string;
|
||||
}
|
||||
|
||||
function findPageForFragment(fragment: androidx.fragment.app.Fragment, frame: Frame) {
|
||||
const fragmentTag = fragment.getTag();
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`Finding page for ${fragmentTag}.`, Trace.categories.NativeLifecycle);
|
||||
}
|
||||
|
||||
let entry: BackstackEntry;
|
||||
const current = frame._currentEntry;
|
||||
const executingContext = frame._executingContext;
|
||||
if (current && current.fragmentTag === fragmentTag) {
|
||||
entry = current;
|
||||
} else if (executingContext && executingContext.entry && executingContext.entry.fragmentTag === fragmentTag) {
|
||||
entry = executingContext.entry;
|
||||
}
|
||||
|
||||
let page: Page;
|
||||
if (entry) {
|
||||
entry.recreated = true;
|
||||
page = entry.resolvedPage;
|
||||
}
|
||||
|
||||
if (page) {
|
||||
const callbacks: FragmentCallbacksImplementation = fragment[CALLBACKS];
|
||||
callbacks.frame = frame;
|
||||
callbacks.entry = entry;
|
||||
entry.fragment = fragment;
|
||||
_updateTransitions(entry);
|
||||
} else {
|
||||
throw new Error(`Could not find a page for ${fragmentTag}.`);
|
||||
}
|
||||
}
|
||||
|
||||
export class FragmentCallbacksImplementation implements AndroidFragmentCallbacks {
|
||||
public frame: Frame;
|
||||
public entry: BackstackEntry;
|
||||
private backgroundBitmap: android.graphics.Bitmap = null;
|
||||
|
||||
@profile
|
||||
public onHiddenChanged(fragment: androidx.fragment.app.Fragment, hidden: boolean, superFunc: Function): void {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`${fragment}.onHiddenChanged(${hidden})`, Trace.categories.NativeLifecycle);
|
||||
}
|
||||
superFunc.call(fragment, hidden);
|
||||
}
|
||||
|
||||
@profile
|
||||
public onCreateAnimator(fragment: androidx.fragment.app.Fragment, transit: number, enter: boolean, nextAnim: number, superFunc: Function): android.animation.Animator {
|
||||
let animator = null;
|
||||
const entry = <any>this.entry;
|
||||
|
||||
// Return enterAnimator only when new (no current entry) nested transition.
|
||||
if (enter && entry.isNestedDefaultTransition) {
|
||||
animator = entry.enterAnimator;
|
||||
entry.isNestedDefaultTransition = false;
|
||||
}
|
||||
|
||||
return animator || superFunc.call(fragment, transit, enter, nextAnim);
|
||||
}
|
||||
|
||||
@profile
|
||||
public onCreate(fragment: androidx.fragment.app.Fragment, savedInstanceState: android.os.Bundle, superFunc: Function): void {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`${fragment}.onCreate(${savedInstanceState})`, Trace.categories.NativeLifecycle);
|
||||
}
|
||||
|
||||
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) {
|
||||
const args = fragment.getArguments();
|
||||
const frameId = args.getInt(FRAMEID);
|
||||
const frame = getFrameByNumberId(frameId);
|
||||
if (!frame) {
|
||||
throw new Error(`Cannot find Frame for ${fragment}`);
|
||||
}
|
||||
|
||||
findPageForFragment(fragment, frame);
|
||||
}
|
||||
}
|
||||
|
||||
@profile
|
||||
public onCreateView(fragment: androidx.fragment.app.Fragment, inflater: android.view.LayoutInflater, container: android.view.ViewGroup, savedInstanceState: android.os.Bundle, superFunc: Function): android.view.View {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`${fragment}.onCreateView(inflater, container, ${savedInstanceState})`, Trace.categories.NativeLifecycle);
|
||||
}
|
||||
|
||||
const entry = this.entry;
|
||||
if (!entry) {
|
||||
Trace.error(`${fragment}.onCreateView: entry is null or undefined`);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const page = entry.resolvedPage;
|
||||
if (!page) {
|
||||
Trace.error(`${fragment}.onCreateView: entry has no resolvedPage`);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const frame = this.frame;
|
||||
if (!frame) {
|
||||
Trace.error(`${fragment}.onCreateView: this.frame is null or undefined`);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
frame._resolvedPage = page;
|
||||
|
||||
// @ts-ignore
|
||||
if (page.parent === frame) {
|
||||
(frame as Frame)._inheritStyles(page);
|
||||
|
||||
// If we are navigating to a page that was destroyed
|
||||
// reinitialize its UI.
|
||||
if (!page._context) {
|
||||
const context = (container && container.getContext()) || (inflater && inflater.getContext());
|
||||
page._setupUI(context);
|
||||
}
|
||||
|
||||
if ((frame as Frame).isLoaded && !page.isLoaded) {
|
||||
page.callLoaded();
|
||||
}
|
||||
} else {
|
||||
if (!page.parent) {
|
||||
if (!frame._styleScope) {
|
||||
// Make sure page will have styleScope even if parents don't.
|
||||
page._updateStyleScope();
|
||||
}
|
||||
|
||||
frame._addView(page);
|
||||
} else {
|
||||
throw new Error('Page is already shown on another frame.');
|
||||
}
|
||||
}
|
||||
|
||||
const savedState = entry.viewSavedState;
|
||||
if (savedState) {
|
||||
(<android.view.View>page.nativeViewProtected).restoreHierarchyState(savedState);
|
||||
entry.viewSavedState = 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.removeAllViews();
|
||||
}
|
||||
}
|
||||
|
||||
return page.nativeViewProtected;
|
||||
}
|
||||
|
||||
@profile
|
||||
public onSaveInstanceState(fragment: androidx.fragment.app.Fragment, outState: android.os.Bundle, superFunc: Function): void {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`${fragment}.onSaveInstanceState(${outState})`, Trace.categories.NativeLifecycle);
|
||||
}
|
||||
superFunc.call(fragment, outState);
|
||||
}
|
||||
|
||||
@profile
|
||||
public onDestroyView(fragment: org.nativescript.widgets.FragmentBase, superFunc: Function): void {
|
||||
try {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`${fragment}.onDestroyView()`, Trace.categories.NativeLifecycle);
|
||||
}
|
||||
|
||||
const hasRemovingParent = fragment.getRemovingParentFragment();
|
||||
|
||||
if (hasRemovingParent) {
|
||||
const nativeFrameView = this.frame.nativeViewProtected;
|
||||
if (nativeFrameView) {
|
||||
const bitmapDrawable = new android.graphics.drawable.BitmapDrawable((getNativeApp() as android.app.Application).getApplicationContext().getResources(), this.backgroundBitmap);
|
||||
this.frame._originalBackground = this.frame.backgroundColor || new Color('White');
|
||||
nativeFrameView.setBackgroundDrawable(bitmapDrawable);
|
||||
this.backgroundBitmap = null;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
superFunc.call(fragment);
|
||||
}
|
||||
}
|
||||
|
||||
@profile
|
||||
public onDestroy(fragment: androidx.fragment.app.Fragment, superFunc: Function): void {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`${fragment}.onDestroy()`, Trace.categories.NativeLifecycle);
|
||||
}
|
||||
|
||||
superFunc.call(fragment);
|
||||
|
||||
const entry = this.entry;
|
||||
if (!entry) {
|
||||
Trace.error(`${fragment}.onDestroy: entry is null or undefined`);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// [nested frames / fragments] see https://github.com/NativeScript/NativeScript/issues/6629
|
||||
// retaining reference to a destroyed fragment here somehow causes a cryptic
|
||||
// "IllegalStateException: Failure saving state: active fragment has cleared index: -1"
|
||||
// in a specific mixed parent / nested frame navigation scenario
|
||||
entry.fragment = null;
|
||||
|
||||
const page = entry.resolvedPage;
|
||||
if (!page) {
|
||||
// todo: check why this happens when using shared element transition!!!
|
||||
// commented out the Trace.error to prevent a crash (the app will still work interestingly)
|
||||
console.log(`${fragment}.onDestroy: entry has no resolvedPage`);
|
||||
// Trace.error(`${fragment}.onDestroy: entry has no resolvedPage`);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@profile
|
||||
public onPause(fragment: org.nativescript.widgets.FragmentBase, superFunc: Function): void {
|
||||
try {
|
||||
// Get view as bitmap and set it as background. This is workaround for the disapearing nested fragments.
|
||||
// TODO: Consider removing it when update to androidx.fragment:1.2.0
|
||||
const hasRemovingParent = fragment.getRemovingParentFragment();
|
||||
|
||||
if (hasRemovingParent) {
|
||||
this.backgroundBitmap = this.loadBitmapFromView(this.frame.nativeViewProtected);
|
||||
}
|
||||
} finally {
|
||||
superFunc.call(fragment);
|
||||
}
|
||||
}
|
||||
|
||||
@profile
|
||||
public onResume(fragment: org.nativescript.widgets.FragmentBase, superFunc: Function): void {
|
||||
const frame = this.entry.resolvedPage.frame;
|
||||
// on some cases during the first navigation on nested frames the animation doesn't trigger
|
||||
// we depend on the animation (even None animation) to set the entry as the current entry
|
||||
// animation should start between start and resume, so if we have an executing navigation here it probably means the animation was skipped
|
||||
// so we manually set the entry
|
||||
// also, to be compatible with fragments 1.2.x we need this setTimeout as animations haven't run on onResume yet
|
||||
const weakRef = new WeakRef(this);
|
||||
setTimeout(() => {
|
||||
const owner = weakRef.get();
|
||||
if (!owner) {
|
||||
return;
|
||||
}
|
||||
if (frame._executingContext && !(<any>owner.entry).isAnimationRunning) {
|
||||
frame.setCurrent(owner.entry, frame._executingContext.navigationType);
|
||||
}
|
||||
}, 0);
|
||||
|
||||
superFunc.call(fragment);
|
||||
}
|
||||
|
||||
@profile
|
||||
public onStop(fragment: androidx.fragment.app.Fragment, superFunc: Function): void {
|
||||
superFunc.call(fragment);
|
||||
}
|
||||
|
||||
@profile
|
||||
public toStringOverride(fragment: androidx.fragment.app.Fragment, superFunc: Function): string {
|
||||
const entry = this.entry;
|
||||
if (entry) {
|
||||
return `${entry.fragmentTag}<${entry.resolvedPage}>`;
|
||||
} else {
|
||||
return 'NO ENTRY, ' + superFunc.call(fragment);
|
||||
}
|
||||
}
|
||||
|
||||
private loadBitmapFromView(view: android.view.View): android.graphics.Bitmap {
|
||||
// Don't try to create bitmaps with no dimensions as this causes a crash
|
||||
// This might happen when showing and closing dialogs fast.
|
||||
if (!(view && view.getWidth() > 0 && view.getHeight() > 0)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Another way to get view bitmap. Test performance vs setDrawingCacheEnabled
|
||||
// const width = view.getWidth();
|
||||
// const height = view.getHeight();
|
||||
// const bitmap = android.graphics.Bitmap.createBitmap(width, height, android.graphics.Bitmap.Config.ARGB_8888);
|
||||
// const canvas = new android.graphics.Canvas(bitmap);
|
||||
// view.layout(0, 0, width, height);
|
||||
// view.draw(canvas);
|
||||
|
||||
// view.setDrawingCacheEnabled(true);
|
||||
// const drawCache = view.getDrawingCache();
|
||||
// const bitmap = android.graphics.Bitmap.createBitmap(drawCache);
|
||||
// view.setDrawingCacheEnabled(false);
|
||||
return org.nativescript.widgets.Utils.getBitmapFromView(view);
|
||||
}
|
||||
}
|
||||
|
||||
export function getFrameByNumberId(frameId: number): Frame {
|
||||
// Find the frame for this activity.
|
||||
for (let i = 0; i < framesCache.length; i++) {
|
||||
const aliveFrame = framesCache[i].get();
|
||||
if (aliveFrame && aliveFrame.frameId === frameId) {
|
||||
return aliveFrame.owner;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function setFragmentCallbacks(fragment: androidx.fragment.app.Fragment): void {
|
||||
fragment[CALLBACKS] = new FragmentCallbacksImplementation();
|
||||
}
|
||||
@@ -11,18 +11,16 @@ import { android as androidUtils } from '../../utils/native-helper';
|
||||
import type { ExpandedEntry } from './fragment.transitions.android';
|
||||
import { ensureFragmentClass, fragmentClass } from './fragment';
|
||||
import { getAppMainEntry } from '../../application/helpers-common';
|
||||
import { getNativeApp } from '../../application/helpers-common';
|
||||
import { Color } from '../../color';
|
||||
|
||||
import { AndroidActivityBackPressedEventData, AndroidActivityNewIntentEventData, AndroidActivityRequestPermissionsEventData, AndroidActivityResultEventData } from '../../application/application-interfaces';
|
||||
import { Application } from '../../application/application';
|
||||
import { isEmbedded, setEmbeddedView } from '../embedding';
|
||||
import { CALLBACKS, FRAMEID, framesCache, setFragmentCallbacks } from './frame-helper-for-android';
|
||||
|
||||
export * from './frame-common';
|
||||
export { setFragmentClass } from './fragment';
|
||||
|
||||
const INTENT_EXTRA = 'com.tns.activity';
|
||||
const FRAMEID = '_frameId';
|
||||
const CALLBACKS = '_callbacks';
|
||||
|
||||
const ownerSymbol = Symbol('_owner');
|
||||
|
||||
@@ -648,7 +646,6 @@ function restoreTransitionState(entry: BackstackEntry, snapshot: TransitionState
|
||||
}
|
||||
|
||||
let framesCounter = 0;
|
||||
const framesCache = new Array<WeakRef<AndroidFrame>>();
|
||||
|
||||
class AndroidFrame extends Observable implements AndroidFrameDefinition {
|
||||
public rootViewGroup: android.view.ViewGroup;
|
||||
@@ -760,18 +757,6 @@ function startActivity(activity: androidx.appcompat.app.AppCompatActivity, frame
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
|
||||
export function getFrameByNumberId(frameId: number): Frame {
|
||||
// Find the frame for this activity.
|
||||
for (let i = 0; i < framesCache.length; i++) {
|
||||
const aliveFrame = framesCache[i].get();
|
||||
if (aliveFrame && aliveFrame.frameId === frameId) {
|
||||
return aliveFrame.owner;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const activityRootViewsMap = new Map<number, WeakRef<View>>();
|
||||
const ROOT_VIEW_ID_EXTRA = 'com.tns.activity.rootViewId';
|
||||
|
||||
@@ -1063,310 +1048,6 @@ export class ActivityCallbacksImplementation implements AndroidActivityCallbacks
|
||||
}
|
||||
}
|
||||
|
||||
export class FragmentCallbacksImplementation implements AndroidFragmentCallbacks {
|
||||
public frame: Frame;
|
||||
public entry: BackstackEntry;
|
||||
private backgroundBitmap: android.graphics.Bitmap = null;
|
||||
|
||||
@profile
|
||||
public onHiddenChanged(fragment: androidx.fragment.app.Fragment, hidden: boolean, superFunc: Function): void {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`${fragment}.onHiddenChanged(${hidden})`, Trace.categories.NativeLifecycle);
|
||||
}
|
||||
superFunc.call(fragment, hidden);
|
||||
}
|
||||
|
||||
@profile
|
||||
public onCreateAnimator(fragment: androidx.fragment.app.Fragment, transit: number, enter: boolean, nextAnim: number, superFunc: Function): android.animation.Animator {
|
||||
let animator = null;
|
||||
const entry = <any>this.entry;
|
||||
|
||||
// Return enterAnimator only when new (no current entry) nested transition.
|
||||
if (enter && entry.isNestedDefaultTransition) {
|
||||
animator = entry.enterAnimator;
|
||||
entry.isNestedDefaultTransition = false;
|
||||
}
|
||||
|
||||
return animator || superFunc.call(fragment, transit, enter, nextAnim);
|
||||
}
|
||||
|
||||
@profile
|
||||
public onCreate(fragment: androidx.fragment.app.Fragment, savedInstanceState: android.os.Bundle, superFunc: Function): void {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`${fragment}.onCreate(${savedInstanceState})`, Trace.categories.NativeLifecycle);
|
||||
}
|
||||
|
||||
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) {
|
||||
const args = fragment.getArguments();
|
||||
const frameId = args.getInt(FRAMEID);
|
||||
const frame = getFrameByNumberId(frameId);
|
||||
if (!frame) {
|
||||
throw new Error(`Cannot find Frame for ${fragment}`);
|
||||
}
|
||||
|
||||
findPageForFragment(fragment, frame);
|
||||
}
|
||||
}
|
||||
|
||||
@profile
|
||||
public onCreateView(fragment: androidx.fragment.app.Fragment, inflater: android.view.LayoutInflater, container: android.view.ViewGroup, savedInstanceState: android.os.Bundle, superFunc: Function): android.view.View {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`${fragment}.onCreateView(inflater, container, ${savedInstanceState})`, Trace.categories.NativeLifecycle);
|
||||
}
|
||||
|
||||
const entry = this.entry;
|
||||
if (!entry) {
|
||||
Trace.error(`${fragment}.onCreateView: entry is null or undefined`);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const page = entry.resolvedPage;
|
||||
if (!page) {
|
||||
Trace.error(`${fragment}.onCreateView: entry has no resolvedPage`);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const frame = this.frame;
|
||||
if (!frame) {
|
||||
Trace.error(`${fragment}.onCreateView: this.frame is null or undefined`);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
frame._resolvedPage = page;
|
||||
|
||||
if (page.parent === frame) {
|
||||
frame._inheritStyles(page);
|
||||
|
||||
// If we are navigating to a page that was destroyed
|
||||
// reinitialize its UI.
|
||||
if (!page._context) {
|
||||
const context = (container && container.getContext()) || (inflater && inflater.getContext());
|
||||
page._setupUI(context);
|
||||
}
|
||||
|
||||
if (frame.isLoaded && !page.isLoaded) {
|
||||
page.callLoaded();
|
||||
}
|
||||
} else {
|
||||
if (!page.parent) {
|
||||
if (!frame._styleScope) {
|
||||
// Make sure page will have styleScope even if parents don't.
|
||||
page._updateStyleScope();
|
||||
}
|
||||
|
||||
frame._addView(page);
|
||||
} else {
|
||||
throw new Error('Page is already shown on another frame.');
|
||||
}
|
||||
}
|
||||
|
||||
const savedState = entry.viewSavedState;
|
||||
if (savedState) {
|
||||
(<android.view.View>page.nativeViewProtected).restoreHierarchyState(savedState);
|
||||
entry.viewSavedState = 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.removeAllViews();
|
||||
}
|
||||
}
|
||||
|
||||
return page.nativeViewProtected;
|
||||
}
|
||||
|
||||
@profile
|
||||
public onSaveInstanceState(fragment: androidx.fragment.app.Fragment, outState: android.os.Bundle, superFunc: Function): void {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`${fragment}.onSaveInstanceState(${outState})`, Trace.categories.NativeLifecycle);
|
||||
}
|
||||
superFunc.call(fragment, outState);
|
||||
}
|
||||
|
||||
@profile
|
||||
public onDestroyView(fragment: org.nativescript.widgets.FragmentBase, superFunc: Function): void {
|
||||
try {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`${fragment}.onDestroyView()`, Trace.categories.NativeLifecycle);
|
||||
}
|
||||
|
||||
const hasRemovingParent = fragment.getRemovingParentFragment();
|
||||
|
||||
if (hasRemovingParent) {
|
||||
const nativeFrameView = this.frame.nativeViewProtected;
|
||||
if (nativeFrameView) {
|
||||
const bitmapDrawable = new android.graphics.drawable.BitmapDrawable((getNativeApp() as android.app.Application).getApplicationContext().getResources(), this.backgroundBitmap);
|
||||
this.frame._originalBackground = this.frame.backgroundColor || new Color('White');
|
||||
nativeFrameView.setBackgroundDrawable(bitmapDrawable);
|
||||
this.backgroundBitmap = null;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
superFunc.call(fragment);
|
||||
}
|
||||
}
|
||||
|
||||
@profile
|
||||
public onDestroy(fragment: androidx.fragment.app.Fragment, superFunc: Function): void {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`${fragment}.onDestroy()`, Trace.categories.NativeLifecycle);
|
||||
}
|
||||
|
||||
superFunc.call(fragment);
|
||||
|
||||
const entry = this.entry;
|
||||
if (!entry) {
|
||||
Trace.error(`${fragment}.onDestroy: entry is null or undefined`);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// [nested frames / fragments] see https://github.com/NativeScript/NativeScript/issues/6629
|
||||
// retaining reference to a destroyed fragment here somehow causes a cryptic
|
||||
// "IllegalStateException: Failure saving state: active fragment has cleared index: -1"
|
||||
// in a specific mixed parent / nested frame navigation scenario
|
||||
entry.fragment = null;
|
||||
|
||||
const page = entry.resolvedPage;
|
||||
if (!page) {
|
||||
// todo: check why this happens when using shared element transition!!!
|
||||
// commented out the Trace.error to prevent a crash (the app will still work interestingly)
|
||||
console.log(`${fragment}.onDestroy: entry has no resolvedPage`);
|
||||
// Trace.error(`${fragment}.onDestroy: entry has no resolvedPage`);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@profile
|
||||
public onPause(fragment: org.nativescript.widgets.FragmentBase, superFunc: Function): void {
|
||||
try {
|
||||
// Get view as bitmap and set it as background. This is workaround for the disapearing nested fragments.
|
||||
// TODO: Consider removing it when update to androidx.fragment:1.2.0
|
||||
const hasRemovingParent = fragment.getRemovingParentFragment();
|
||||
|
||||
if (hasRemovingParent) {
|
||||
this.backgroundBitmap = this.loadBitmapFromView(this.frame.nativeViewProtected);
|
||||
}
|
||||
} finally {
|
||||
superFunc.call(fragment);
|
||||
}
|
||||
}
|
||||
|
||||
@profile
|
||||
public onResume(fragment: org.nativescript.widgets.FragmentBase, superFunc: Function): void {
|
||||
const frame = this.entry.resolvedPage.frame;
|
||||
// on some cases during the first navigation on nested frames the animation doesn't trigger
|
||||
// we depend on the animation (even None animation) to set the entry as the current entry
|
||||
// animation should start between start and resume, so if we have an executing navigation here it probably means the animation was skipped
|
||||
// so we manually set the entry
|
||||
// also, to be compatible with fragments 1.2.x we need this setTimeout as animations haven't run on onResume yet
|
||||
const weakRef = new WeakRef(this);
|
||||
setTimeout(() => {
|
||||
const owner = weakRef.get();
|
||||
if (!owner) {
|
||||
return;
|
||||
}
|
||||
if (frame._executingContext && !(<any>owner.entry).isAnimationRunning) {
|
||||
frame.setCurrent(owner.entry, frame._executingContext.navigationType);
|
||||
}
|
||||
}, 0);
|
||||
|
||||
superFunc.call(fragment);
|
||||
}
|
||||
|
||||
@profile
|
||||
public onStop(fragment: androidx.fragment.app.Fragment, superFunc: Function): void {
|
||||
superFunc.call(fragment);
|
||||
}
|
||||
|
||||
@profile
|
||||
public toStringOverride(fragment: androidx.fragment.app.Fragment, superFunc: Function): string {
|
||||
const entry = this.entry;
|
||||
if (entry) {
|
||||
return `${entry.fragmentTag}<${entry.resolvedPage}>`;
|
||||
} else {
|
||||
return 'NO ENTRY, ' + superFunc.call(fragment);
|
||||
}
|
||||
}
|
||||
|
||||
private loadBitmapFromView(view: android.view.View): android.graphics.Bitmap {
|
||||
// Don't try to create bitmaps with no dimensions as this causes a crash
|
||||
// This might happen when showing and closing dialogs fast.
|
||||
if (!(view && view.getWidth() > 0 && view.getHeight() > 0)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Another way to get view bitmap. Test performance vs setDrawingCacheEnabled
|
||||
// const width = view.getWidth();
|
||||
// const height = view.getHeight();
|
||||
// const bitmap = android.graphics.Bitmap.createBitmap(width, height, android.graphics.Bitmap.Config.ARGB_8888);
|
||||
// const canvas = new android.graphics.Canvas(bitmap);
|
||||
// view.layout(0, 0, width, height);
|
||||
// view.draw(canvas);
|
||||
|
||||
// view.setDrawingCacheEnabled(true);
|
||||
// const drawCache = view.getDrawingCache();
|
||||
// const bitmap = android.graphics.Bitmap.createBitmap(drawCache);
|
||||
// view.setDrawingCacheEnabled(false);
|
||||
return org.nativescript.widgets.Utils.getBitmapFromView(view);
|
||||
}
|
||||
}
|
||||
|
||||
function findPageForFragment(fragment: androidx.fragment.app.Fragment, frame: Frame) {
|
||||
const fragmentTag = fragment.getTag();
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`Finding page for ${fragmentTag}.`, Trace.categories.NativeLifecycle);
|
||||
}
|
||||
|
||||
let entry: BackstackEntry;
|
||||
const current = frame._currentEntry;
|
||||
const executingContext = frame._executingContext;
|
||||
if (current && current.fragmentTag === fragmentTag) {
|
||||
entry = current;
|
||||
} else if (executingContext && executingContext.entry && executingContext.entry.fragmentTag === fragmentTag) {
|
||||
entry = executingContext.entry;
|
||||
}
|
||||
|
||||
let page: Page;
|
||||
if (entry) {
|
||||
entry.recreated = true;
|
||||
page = entry.resolvedPage;
|
||||
}
|
||||
|
||||
if (page) {
|
||||
const callbacks: FragmentCallbacksImplementation = fragment[CALLBACKS];
|
||||
callbacks.frame = frame;
|
||||
callbacks.entry = entry;
|
||||
entry.fragment = fragment;
|
||||
_updateTransitions(entry);
|
||||
} else {
|
||||
throw new Error(`Could not find a page for ${fragmentTag}.`);
|
||||
}
|
||||
}
|
||||
|
||||
export function setActivityCallbacks(activity: androidx.appcompat.app.AppCompatActivity): void {
|
||||
activity[CALLBACKS] = new ActivityCallbacksImplementation();
|
||||
}
|
||||
|
||||
export function setFragmentCallbacks(fragment: androidx.fragment.app.Fragment): void {
|
||||
fragment[CALLBACKS] = new FragmentCallbacksImplementation();
|
||||
}
|
||||
|
||||
35
packages/core/ui/frame/index.d.ts
vendored
35
packages/core/ui/frame/index.d.ts
vendored
@@ -1,8 +1,8 @@
|
||||
import { NavigationType, FrameBase } from './frame-common';
|
||||
import type { NavigationType, FrameBase } from './frame-common';
|
||||
import type { NavigatedData, Page } from '../page';
|
||||
import { Observable, EventData } from '../../data/observable';
|
||||
import { Property, View } from '../core/view';
|
||||
import { Transition } from '../transition';
|
||||
import type { Observable, EventData } from '../../data/observable';
|
||||
import type { Property, View } from '../core/view';
|
||||
import type { Transition } from '../transition';
|
||||
|
||||
export * from './frame-interfaces';
|
||||
|
||||
@@ -521,6 +521,19 @@ export interface AndroidFrame extends Observable {
|
||||
* @param page The Page instance to search for.
|
||||
*/
|
||||
fragmentForPage(entry: BackstackEntry): any;
|
||||
|
||||
// common properties
|
||||
_resolvedPage?: Page;
|
||||
_currentEntry?: BackstackEntry;
|
||||
_executingContext?: NavigationContext;
|
||||
_inheritStyles?(page: Page): void;
|
||||
isLoaded?: boolean;
|
||||
_styleScope?: any;
|
||||
_addView?(view: View): void;
|
||||
nativeViewProtected?: any /* android.view.View */;
|
||||
_originalBackground?: any /* android.graphics.drawable.Drawable */;
|
||||
backgroundColor?: any;
|
||||
owner?: any;
|
||||
}
|
||||
|
||||
export interface AndroidActivityCallbacks {
|
||||
@@ -539,20 +552,6 @@ export interface AndroidActivityCallbacks {
|
||||
onNewIntent(activity: any, intent: any, superSetIntentFunc: Function, superFunc: Function): void;
|
||||
}
|
||||
|
||||
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;
|
||||
onPause(fragment: any, superFunc: Function): void;
|
||||
onResume(fragment: any, superFunc: Function): void;
|
||||
onStop(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.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Only put platform-agnostic logic here.
|
||||
|
||||
import { CoreTypes } from '../../core-types';
|
||||
import { layout } from '../../utils';
|
||||
import { layout } from '../../utils/layout-helper';
|
||||
import { isCssWideKeyword } from '../core/properties/property-shared';
|
||||
|
||||
function equalsCommon(a: CoreTypes.LengthType, b: CoreTypes.LengthType): boolean;
|
||||
|
||||
Reference in New Issue
Block a user