mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-06 17:28:29 +08:00
feat(core): ability to embed into platform host projects (#10465)
This commit is contained in:
3
packages/core/.gitignore
vendored
3
packages/core/.gitignore
vendored
@ -9,4 +9,5 @@ dist
|
|||||||
css/parser.js*
|
css/parser.js*
|
||||||
css/system-classes.js*
|
css/system-classes.js*
|
||||||
!css-value/**/*.*
|
!css-value/**/*.*
|
||||||
!fetch/**/*.*
|
!fetch/**/*.*
|
||||||
|
/coverage
|
@ -1,5 +1,6 @@
|
|||||||
import { profile } from '../profiling';
|
import { profile } from '../profiling';
|
||||||
import { View } from '../ui';
|
import { View } from '../ui/core/view';
|
||||||
|
import { isEmbedded } from '../ui/embedding';
|
||||||
import { AndroidActivityCallbacks, NavigationEntry } from '../ui/frame/frame-common';
|
import { AndroidActivityCallbacks, NavigationEntry } from '../ui/frame/frame-common';
|
||||||
import type { AndroidApplication as IAndroidApplication } from './application';
|
import type { AndroidApplication as IAndroidApplication } from './application';
|
||||||
import { ApplicationCommon } from './application-common';
|
import { ApplicationCommon } from './application-common';
|
||||||
@ -10,6 +11,12 @@ declare namespace com {
|
|||||||
class NativeScriptApplication extends android.app.Application {
|
class NativeScriptApplication extends android.app.Application {
|
||||||
static getInstance(): NativeScriptApplication;
|
static getInstance(): NativeScriptApplication;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace embedding {
|
||||||
|
class ApplicationHolder {
|
||||||
|
static getInstance(): android.app.Application;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -358,6 +365,10 @@ export class AndroidApplication extends ApplicationCommon implements IAndroidApp
|
|||||||
nativeApp = com.tns.NativeScriptApplication.getInstance();
|
nativeApp = com.tns.NativeScriptApplication.getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!nativeApp && isEmbedded()) {
|
||||||
|
nativeApp = com.tns.embedding.ApplicationHolder.getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
// the getInstance might return null if com.tns.NativeScriptApplication exists but is not the starting app type
|
// the getInstance might return null if com.tns.NativeScriptApplication exists but is not the starting app type
|
||||||
if (!nativeApp) {
|
if (!nativeApp) {
|
||||||
// TODO: Should we handle the case when a custom application type is provided and the user has not explicitly initialized the application module?
|
// TODO: Should we handle the case when a custom application type is provided and the user has not explicitly initialized the application module?
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { profile } from '../profiling';
|
import { profile } from '../profiling';
|
||||||
import { View } from '../ui';
|
import { View } from '../ui/core/view';
|
||||||
|
import { isEmbedded } from '../ui/embedding';
|
||||||
import { IOSHelper } from '../ui/core/view/view-helper';
|
import { IOSHelper } from '../ui/core/view/view-helper';
|
||||||
import { NavigationEntry } from '../ui/frame/frame-interfaces';
|
import { NavigationEntry } from '../ui/frame/frame-interfaces';
|
||||||
import * as Utils from '../utils';
|
import * as Utils from '../utils';
|
||||||
@ -367,7 +368,7 @@ export class iOSApplication extends ApplicationCommon implements IiOSApplication
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (this._window) {
|
if (this._window) {
|
||||||
if (root !== null && !NativeScriptEmbedder.sharedInstance().delegate) {
|
if (root !== null && !isEmbedded()) {
|
||||||
this.setWindowContent(root);
|
this.setWindowContent(root);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nativescript/core",
|
"name": "@nativescript/core",
|
||||||
"version": "8.8.0",
|
"version": "8.8.0-embed.1",
|
||||||
"description": "A JavaScript library providing an easy to use api for interacting with iOS and Android platform APIs.",
|
"description": "A JavaScript library providing an easy to use api for interacting with iOS and Android platform APIs.",
|
||||||
"main": "index",
|
"main": "index",
|
||||||
"types": "index.d.ts",
|
"types": "index.d.ts",
|
||||||
|
26
packages/core/ui/embedding/index.android.ts
Normal file
26
packages/core/ui/embedding/index.android.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import type { View } from '../../ui/core/view';
|
||||||
|
|
||||||
|
declare namespace org {
|
||||||
|
namespace nativescript {
|
||||||
|
class Bootstrap {
|
||||||
|
static isEmbeddedNativeScript: boolean;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isEmbedded(): boolean {
|
||||||
|
return org.nativescript?.Bootstrap?.isEmbeddedNativeScript;
|
||||||
|
}
|
||||||
|
|
||||||
|
let embeddedView: View | undefined;
|
||||||
|
|
||||||
|
export function setEmbeddedView(view: View | undefined): void {
|
||||||
|
embeddedView = view;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getEmbeddedView(): View {
|
||||||
|
if (!embeddedView) {
|
||||||
|
throw new Error("{N} Core: Fragment content view not set or set to 'undefined'");
|
||||||
|
}
|
||||||
|
return embeddedView;
|
||||||
|
}
|
10
packages/core/ui/embedding/index.d.ts
vendored
Normal file
10
packages/core/ui/embedding/index.d.ts
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import type { View } from '../../ui/core/view';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the app is embedded into a host project or standalone project
|
||||||
|
*/
|
||||||
|
export function isEmbedded(): boolean;
|
||||||
|
|
||||||
|
export function setEmbeddedView(view: View | undefined): void;
|
||||||
|
|
||||||
|
export function getEmbeddedView(): View;
|
3
packages/core/ui/embedding/index.ios.ts
Normal file
3
packages/core/ui/embedding/index.ios.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export function isEmbedded(): boolean {
|
||||||
|
return !!NativeScriptEmbedder.sharedInstance().delegate;
|
||||||
|
}
|
@ -1,62 +1,135 @@
|
|||||||
import '../../globals';
|
import '../../globals';
|
||||||
import { setActivityCallbacks, AndroidActivityCallbacks } from '.';
|
import { setActivityCallbacks } from '.';
|
||||||
import { Application } from '../../application';
|
import { Application } from '../../application';
|
||||||
|
import { isEmbedded } from '../embedding';
|
||||||
|
|
||||||
/**
|
const EMPTY_FN = () => {};
|
||||||
* NOTE: We cannot use NativeClass here because this is used in appComponents in webpack.config
|
declare const com: any;
|
||||||
* Whereby it bypasses the decorator transformation, hence pure es5 style written here
|
|
||||||
*/
|
|
||||||
const superProto = androidx.appcompat.app.AppCompatActivity.prototype;
|
|
||||||
(<any>androidx.appcompat.app.AppCompatActivity).extend('com.tns.NativeScriptActivity', {
|
|
||||||
init() {
|
|
||||||
// init must at least be defined
|
|
||||||
},
|
|
||||||
onCreate(savedInstanceState: android.os.Bundle): void {
|
|
||||||
Application.android.init(this.getApplication());
|
|
||||||
|
|
||||||
// Set isNativeScriptActivity in onCreate.
|
if (!isEmbedded()) {
|
||||||
// The JS constructor might not be called because the activity is created from Android.
|
/**
|
||||||
this.isNativeScriptActivity = true;
|
* NOTE: We cannot use NativeClass here because this is used in appComponents in webpack.config
|
||||||
if (!this._callbacks) {
|
* Whereby it bypasses the decorator transformation, hence pure es5 style written here
|
||||||
setActivityCallbacks(this);
|
*/
|
||||||
}
|
const superProto = androidx.appcompat.app.AppCompatActivity.prototype;
|
||||||
|
(<any>androidx.appcompat.app.AppCompatActivity).extend('com.tns.NativeScriptActivity', {
|
||||||
|
init() {
|
||||||
|
// init must at least be defined
|
||||||
|
},
|
||||||
|
onCreate(savedInstanceState: android.os.Bundle): void {
|
||||||
|
Application.android.init(this.getApplication());
|
||||||
|
|
||||||
this._callbacks.onCreate(this, savedInstanceState, this.getIntent(), superProto.onCreate);
|
// Set isNativeScriptActivity in onCreate.
|
||||||
},
|
// The JS constructor might not be called because the activity is created from Android.
|
||||||
|
this.isNativeScriptActivity = true;
|
||||||
|
if (!this._callbacks) {
|
||||||
|
setActivityCallbacks(this);
|
||||||
|
}
|
||||||
|
|
||||||
onNewIntent(intent: android.content.Intent): void {
|
this._callbacks.onCreate(this, savedInstanceState, this.getIntent(), superProto.onCreate);
|
||||||
this._callbacks.onNewIntent(this, intent, superProto.setIntent, superProto.onNewIntent);
|
},
|
||||||
},
|
|
||||||
|
|
||||||
onSaveInstanceState(outState: android.os.Bundle): void {
|
onNewIntent(intent: android.content.Intent): void {
|
||||||
this._callbacks.onSaveInstanceState(this, outState, superProto.onSaveInstanceState);
|
this._callbacks.onNewIntent(this, intent, superProto.setIntent, superProto.onNewIntent);
|
||||||
},
|
},
|
||||||
|
|
||||||
onStart(): void {
|
onSaveInstanceState(outState: android.os.Bundle): void {
|
||||||
this._callbacks.onStart(this, superProto.onStart);
|
this._callbacks.onSaveInstanceState(this, outState, superProto.onSaveInstanceState);
|
||||||
},
|
},
|
||||||
|
|
||||||
onStop(): void {
|
onStart(): void {
|
||||||
this._callbacks.onStop(this, superProto.onStop);
|
this._callbacks.onStart(this, superProto.onStart);
|
||||||
},
|
},
|
||||||
|
|
||||||
onDestroy(): void {
|
onStop(): void {
|
||||||
this._callbacks.onDestroy(this, superProto.onDestroy);
|
this._callbacks.onStop(this, superProto.onStop);
|
||||||
},
|
},
|
||||||
|
|
||||||
onPostResume(): void {
|
onDestroy(): void {
|
||||||
this._callbacks.onPostResume(this, superProto.onPostResume);
|
this._callbacks.onDestroy(this, superProto.onDestroy);
|
||||||
},
|
},
|
||||||
|
|
||||||
onBackPressed(): void {
|
onPostResume(): void {
|
||||||
this._callbacks.onBackPressed(this, superProto.onBackPressed);
|
this._callbacks.onPostResume(this, superProto.onPostResume);
|
||||||
},
|
},
|
||||||
|
|
||||||
onRequestPermissionsResult(requestCode: number, permissions: Array<string>, grantResults: Array<number>): void {
|
onBackPressed(): void {
|
||||||
this._callbacks.onRequestPermissionsResult(this, requestCode, permissions, grantResults, undefined /*TODO: Enable if needed*/);
|
this._callbacks.onBackPressed(this, superProto.onBackPressed);
|
||||||
},
|
},
|
||||||
|
|
||||||
onActivityResult(requestCode: number, resultCode: number, data: android.content.Intent): void {
|
onRequestPermissionsResult(requestCode: number, permissions: Array<string>, grantResults: Array<number>): void {
|
||||||
this._callbacks.onActivityResult(this, requestCode, resultCode, data, superProto.onActivityResult);
|
this._callbacks.onRequestPermissionsResult(this, requestCode, permissions, grantResults, undefined /*TODO: Enable if needed*/);
|
||||||
},
|
},
|
||||||
});
|
|
||||||
|
onActivityResult(requestCode: number, resultCode: number, data: android.content.Intent): void {
|
||||||
|
this._callbacks.onActivityResult(this, requestCode, resultCode, data, superProto.onActivityResult);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const Callbacks = com.tns.embedding.EmbeddableActivityCallbacks.extend({
|
||||||
|
init() {
|
||||||
|
// init must at least be defined
|
||||||
|
},
|
||||||
|
onCreate(savedInstanceState: android.os.Bundle): void {
|
||||||
|
const activity = this.getActivity();
|
||||||
|
|
||||||
|
Application.android.init(activity.getApplication());
|
||||||
|
|
||||||
|
// Set isNativeScriptActivity in onCreate.
|
||||||
|
// The JS constructor might not be called because the activity is created from Android.
|
||||||
|
activity.isNativeScriptActivity = true;
|
||||||
|
if (!activity._callbacks) {
|
||||||
|
setActivityCallbacks(activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
activity._callbacks.onCreate(activity, savedInstanceState, activity.getIntent(), EMPTY_FN);
|
||||||
|
},
|
||||||
|
|
||||||
|
onNewIntent(intent: android.content.Intent): void {
|
||||||
|
const activity = this.getActivity();
|
||||||
|
activity._callbacks.onNewIntent(activity, intent, EMPTY_FN, EMPTY_FN);
|
||||||
|
},
|
||||||
|
|
||||||
|
onSaveInstanceState(outState: android.os.Bundle): void {
|
||||||
|
const activity = this.getActivity();
|
||||||
|
activity._callbacks.onSaveInstanceState(activity, outState, EMPTY_FN);
|
||||||
|
},
|
||||||
|
|
||||||
|
onStart(): void {
|
||||||
|
const activity = this.getActivity();
|
||||||
|
activity._callbacks.onStart(activity, EMPTY_FN);
|
||||||
|
},
|
||||||
|
|
||||||
|
onStop(): void {
|
||||||
|
const activity = this.getActivity();
|
||||||
|
activity._callbacks.onStop(activity, EMPTY_FN);
|
||||||
|
},
|
||||||
|
|
||||||
|
onDestroy(): void {
|
||||||
|
const activity = this.getActivity();
|
||||||
|
activity._callbacks.onDestroy(activity, EMPTY_FN);
|
||||||
|
},
|
||||||
|
|
||||||
|
onPostResume(): void {
|
||||||
|
const activity = this.getActivity();
|
||||||
|
activity._callbacks.onPostResume(activity, EMPTY_FN);
|
||||||
|
},
|
||||||
|
|
||||||
|
onBackPressed(): void {
|
||||||
|
const activity = this.getActivity();
|
||||||
|
activity._callbacks.onBackPressed(activity, EMPTY_FN);
|
||||||
|
},
|
||||||
|
|
||||||
|
onRequestPermissionsResult(requestCode: number, permissions: Array<string>, grantResults: Array<number>): void {
|
||||||
|
const activity = this.getActivity();
|
||||||
|
activity._callbacks.onRequestPermissionsResult(activity, requestCode, permissions, grantResults, undefined /*TODO: Enable if needed*/);
|
||||||
|
},
|
||||||
|
|
||||||
|
onActivityResult(requestCode: number, resultCode: number, data: android.content.Intent): void {
|
||||||
|
const activity = this.getActivity();
|
||||||
|
activity._callbacks.onActivityResult(activity, requestCode, resultCode, data, EMPTY_FN);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
com.tns.embedding.CallbacksStore.setActivityCallbacks(new Callbacks());
|
||||||
|
}
|
||||||
|
303
packages/core/ui/frame/callbacks/activity-callbacks.ts
Normal file
303
packages/core/ui/frame/callbacks/activity-callbacks.ts
Normal file
@ -0,0 +1,303 @@
|
|||||||
|
import { AndroidActivityCallbacks, Frame } from '..';
|
||||||
|
|
||||||
|
import { AndroidActivityBackPressedEventData, AndroidActivityNewIntentEventData, AndroidActivityRequestPermissionsEventData, AndroidActivityResultEventData, Application } from '../../../application';
|
||||||
|
|
||||||
|
import { Trace } from '../../../trace';
|
||||||
|
import { View } from '../../core/view';
|
||||||
|
|
||||||
|
import { _clearEntry, _clearFragment, _getAnimatedEntries, _reverseTransitions, _setAndroidFragmentTransitions, _updateTransitions } from '../fragment.transitions';
|
||||||
|
|
||||||
|
import { profile } from '../../../profiling';
|
||||||
|
import { isEmbedded, setEmbeddedView } from '../../embedding';
|
||||||
|
|
||||||
|
const activityRootViewsMap = new Map<number, WeakRef<View>>();
|
||||||
|
const INTENT_EXTRA = 'com.tns.activity';
|
||||||
|
const ROOT_VIEW_ID_EXTRA = 'com.tns.activity.rootViewId';
|
||||||
|
|
||||||
|
export let moduleLoaded: boolean;
|
||||||
|
|
||||||
|
export class ActivityCallbacksImplementation implements AndroidActivityCallbacks {
|
||||||
|
private _rootView: View;
|
||||||
|
|
||||||
|
public getRootView(): View {
|
||||||
|
return this._rootView;
|
||||||
|
}
|
||||||
|
|
||||||
|
@profile
|
||||||
|
public onCreate(activity: androidx.appcompat.app.AppCompatActivity, savedInstanceState: android.os.Bundle, intentOrSuperFunc: android.content.Intent | Function, superFunc?: Function): void {
|
||||||
|
if (Trace.isEnabled()) {
|
||||||
|
Trace.write(`Activity.onCreate(${savedInstanceState})`, Trace.categories.NativeLifecycle);
|
||||||
|
}
|
||||||
|
|
||||||
|
const intent: android.content.Intent = superFunc ? <android.content.Intent>intentOrSuperFunc : undefined;
|
||||||
|
|
||||||
|
if (!superFunc) {
|
||||||
|
console.log('AndroidActivityCallbacks.onCreate(activity: any, savedInstanceState: any, superFunc: Function) ' + 'is deprecated. Use AndroidActivityCallbacks.onCreate(activity: any, savedInstanceState: any, intent: any, superFunc: Function) instead.');
|
||||||
|
superFunc = <Function>intentOrSuperFunc;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is savedInstanceState this call will recreate all fragments that were previously in the navigation.
|
||||||
|
// We take care of associating them with a Page from our backstack in the onAttachFragment callback.
|
||||||
|
// If there is savedInstanceState and moduleLoaded is false we are restarted but process was killed.
|
||||||
|
// For now we treat it like first run (e.g. we are not passing savedInstanceState so no fragments are being restored).
|
||||||
|
// When we add support for application save/load state - revise this logic.
|
||||||
|
const isRestart = !!savedInstanceState && moduleLoaded;
|
||||||
|
superFunc.call(activity, isRestart ? savedInstanceState : null);
|
||||||
|
|
||||||
|
// Try to get the rootViewId form the saved state in case the activity
|
||||||
|
// was destroyed and we are now recreating it.
|
||||||
|
if (savedInstanceState) {
|
||||||
|
const rootViewId = savedInstanceState.getInt(ROOT_VIEW_ID_EXTRA, -1);
|
||||||
|
if (rootViewId !== -1 && activityRootViewsMap.has(rootViewId)) {
|
||||||
|
this._rootView = activityRootViewsMap.get(rootViewId)?.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (intent && intent.getAction()) {
|
||||||
|
Application.android.notify(<AndroidActivityNewIntentEventData>{
|
||||||
|
eventName: Application.AndroidApplication.activityNewIntentEvent,
|
||||||
|
object: Application.android,
|
||||||
|
activity,
|
||||||
|
intent,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setActivityContent(activity, savedInstanceState, true);
|
||||||
|
moduleLoaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@profile
|
||||||
|
public onSaveInstanceState(activity: androidx.appcompat.app.AppCompatActivity, outState: android.os.Bundle, superFunc: Function): void {
|
||||||
|
superFunc.call(activity, outState);
|
||||||
|
const rootView = this._rootView;
|
||||||
|
if (rootView instanceof Frame) {
|
||||||
|
outState.putInt(INTENT_EXTRA, rootView.android.frameId);
|
||||||
|
rootView._saveFragmentsState();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rootView) {
|
||||||
|
outState.putInt(ROOT_VIEW_ID_EXTRA, rootView._domId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@profile
|
||||||
|
public onNewIntent(activity: androidx.appcompat.app.AppCompatActivity, intent: android.content.Intent, superSetIntentFunc: Function, superFunc: Function): void {
|
||||||
|
superFunc.call(activity, intent);
|
||||||
|
superSetIntentFunc.call(activity, intent);
|
||||||
|
|
||||||
|
Application.android.notify(<AndroidActivityNewIntentEventData>{
|
||||||
|
eventName: Application.AndroidApplication.activityNewIntentEvent,
|
||||||
|
object: Application.android,
|
||||||
|
activity,
|
||||||
|
intent,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@profile
|
||||||
|
public onStart(activity: any, superFunc: Function): void {
|
||||||
|
superFunc.call(activity);
|
||||||
|
|
||||||
|
if (Trace.isEnabled()) {
|
||||||
|
Trace.write('NativeScriptActivity.onStart();', Trace.categories.NativeLifecycle);
|
||||||
|
}
|
||||||
|
|
||||||
|
const rootView = this._rootView;
|
||||||
|
if (rootView && !rootView.isLoaded && !isEmbedded()) {
|
||||||
|
rootView.callLoaded();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@profile
|
||||||
|
public onStop(activity: any, superFunc: Function): void {
|
||||||
|
superFunc.call(activity);
|
||||||
|
|
||||||
|
if (Trace.isEnabled()) {
|
||||||
|
Trace.write('NativeScriptActivity.onStop();', Trace.categories.NativeLifecycle);
|
||||||
|
}
|
||||||
|
|
||||||
|
const rootView = this._rootView;
|
||||||
|
if (rootView && rootView.isLoaded && !isEmbedded()) {
|
||||||
|
rootView.callUnloaded();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@profile
|
||||||
|
public onPostResume(activity: any, superFunc: Function): void {
|
||||||
|
superFunc.call(activity);
|
||||||
|
|
||||||
|
if (Trace.isEnabled()) {
|
||||||
|
Trace.write('NativeScriptActivity.onPostResume();', Trace.categories.NativeLifecycle);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: activity.onPostResume() is called when activity resume is complete and we can
|
||||||
|
// safely raise the application resume event;
|
||||||
|
// onActivityResumed(...) lifecycle callback registered in application is called too early
|
||||||
|
// and raising the application resume event there causes issues like
|
||||||
|
// https://github.com/NativeScript/NativeScript/issues/6708
|
||||||
|
if ((<any>activity).isNativeScriptActivity) {
|
||||||
|
Application.setSuspended(false, {
|
||||||
|
// todo: deprecate in favor of using event.activity instead.
|
||||||
|
android: activity,
|
||||||
|
activity,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@profile
|
||||||
|
public onDestroy(activity: any, superFunc: Function): void {
|
||||||
|
try {
|
||||||
|
if (Trace.isEnabled()) {
|
||||||
|
Trace.write('NativeScriptActivity.onDestroy();', Trace.categories.NativeLifecycle);
|
||||||
|
}
|
||||||
|
|
||||||
|
const rootView = this._rootView;
|
||||||
|
if (rootView) {
|
||||||
|
rootView._tearDownUI(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// this may happen when the user changes the system theme
|
||||||
|
// In such case, isFinishing() is false (and isChangingConfigurations is true), and the app will start again (onCreate) with a savedInstanceState
|
||||||
|
// as a result, launchEvent will never be called
|
||||||
|
// possible alternative: always fire launchEvent and exitEvent, but pass extra flags to make it clear what kind of launch/destroy is happening
|
||||||
|
if (activity.isFinishing()) {
|
||||||
|
const exitArgs = {
|
||||||
|
eventName: Application.exitEvent,
|
||||||
|
object: Application.android,
|
||||||
|
android: activity,
|
||||||
|
};
|
||||||
|
Application.notify(exitArgs);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
superFunc.call(activity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@profile
|
||||||
|
public onBackPressed(activity: any, superFunc: Function): void {
|
||||||
|
if (Trace.isEnabled()) {
|
||||||
|
Trace.write('NativeScriptActivity.onBackPressed;', Trace.categories.NativeLifecycle);
|
||||||
|
}
|
||||||
|
|
||||||
|
const args = <AndroidActivityBackPressedEventData>{
|
||||||
|
eventName: 'activityBackPressed',
|
||||||
|
object: Application,
|
||||||
|
android: Application.android,
|
||||||
|
activity: activity,
|
||||||
|
cancel: false,
|
||||||
|
};
|
||||||
|
Application.android.notify(args);
|
||||||
|
if (args.cancel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const view = this._rootView;
|
||||||
|
let callSuper = false;
|
||||||
|
|
||||||
|
const viewArgs = <AndroidActivityBackPressedEventData>{
|
||||||
|
eventName: 'activityBackPressed',
|
||||||
|
object: view,
|
||||||
|
activity: activity,
|
||||||
|
cancel: false,
|
||||||
|
};
|
||||||
|
view.notify(viewArgs);
|
||||||
|
|
||||||
|
// In the case of Frame, use this callback only if it was overridden, since the original will cause navigation issues
|
||||||
|
if (!viewArgs.cancel && (view.onBackPressed === Frame.prototype.onBackPressed || !view.onBackPressed())) {
|
||||||
|
callSuper = view instanceof Frame ? !Frame.goBack() : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (callSuper) {
|
||||||
|
superFunc.call(activity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@profile
|
||||||
|
public onRequestPermissionsResult(activity: any, requestCode: number, permissions: Array<string>, grantResults: Array<number>, superFunc: Function): void {
|
||||||
|
if (Trace.isEnabled()) {
|
||||||
|
Trace.write('NativeScriptActivity.onRequestPermissionsResult;', Trace.categories.NativeLifecycle);
|
||||||
|
}
|
||||||
|
|
||||||
|
Application.android.notify(<AndroidActivityRequestPermissionsEventData>{
|
||||||
|
eventName: 'activityRequestPermissions',
|
||||||
|
object: Application,
|
||||||
|
android: Application.android,
|
||||||
|
activity: activity,
|
||||||
|
requestCode: requestCode,
|
||||||
|
permissions: permissions,
|
||||||
|
grantResults: grantResults,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@profile
|
||||||
|
public onActivityResult(activity: any, requestCode: number, resultCode: number, data: android.content.Intent, superFunc: Function): void {
|
||||||
|
superFunc.call(activity, requestCode, resultCode, data);
|
||||||
|
if (Trace.isEnabled()) {
|
||||||
|
Trace.write(`NativeScriptActivity.onActivityResult(${requestCode}, ${resultCode}, ${data})`, Trace.categories.NativeLifecycle);
|
||||||
|
}
|
||||||
|
|
||||||
|
Application.android.notify(<AndroidActivityResultEventData>{
|
||||||
|
eventName: 'activityResult',
|
||||||
|
object: Application,
|
||||||
|
android: Application.android,
|
||||||
|
activity: activity,
|
||||||
|
requestCode: requestCode,
|
||||||
|
resultCode: resultCode,
|
||||||
|
intent: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public resetActivityContent(activity: androidx.appcompat.app.AppCompatActivity): void {
|
||||||
|
if (this._rootView) {
|
||||||
|
const manager = this._rootView._getFragmentManager();
|
||||||
|
manager.executePendingTransactions();
|
||||||
|
|
||||||
|
this._rootView._onRootViewReset();
|
||||||
|
}
|
||||||
|
// Delete previously cached root view in order to recreate it.
|
||||||
|
this._rootView = null;
|
||||||
|
this.setActivityContent(activity, null, false);
|
||||||
|
this._rootView.callLoaded();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paths that go trough this method:
|
||||||
|
// 1. Application initial start - there is no rootView in callbacks.
|
||||||
|
// 2. Application revived after Activity is destroyed. this._rootView should have been restored by id in onCreate.
|
||||||
|
// 3. Livesync if rootView has no custom _onLivesync. this._rootView should have been cleared upfront. Launch event should not fired
|
||||||
|
// 4. resetRootView method. this._rootView should have been cleared upfront. Launch event should not fired
|
||||||
|
private setActivityContent(activity: androidx.appcompat.app.AppCompatActivity, savedInstanceState: android.os.Bundle, fireLaunchEvent: boolean): void {
|
||||||
|
let rootView = this._rootView;
|
||||||
|
|
||||||
|
if (Trace.isEnabled()) {
|
||||||
|
Trace.write(`Frame.setActivityContent rootView: ${rootView} shouldCreateRootFrame: false fireLaunchEvent: ${fireLaunchEvent}`, Trace.categories.NativeLifecycle);
|
||||||
|
}
|
||||||
|
|
||||||
|
const intent = activity.getIntent();
|
||||||
|
rootView = Application.createRootView(rootView, fireLaunchEvent, {
|
||||||
|
// todo: deprecate in favor of args.intent?
|
||||||
|
android: intent,
|
||||||
|
intent,
|
||||||
|
savedInstanceState,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!rootView) {
|
||||||
|
// no root view created
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
activityRootViewsMap.set(rootView._domId, new WeakRef(rootView));
|
||||||
|
|
||||||
|
// setup view as styleScopeHost
|
||||||
|
rootView._setupAsRootView(activity);
|
||||||
|
|
||||||
|
if (isEmbedded()) {
|
||||||
|
setEmbeddedView(rootView);
|
||||||
|
} else {
|
||||||
|
activity.setContentView(rootView.nativeViewProtected, new org.nativescript.widgets.CommonLayoutParams());
|
||||||
|
}
|
||||||
|
|
||||||
|
this._rootView = rootView;
|
||||||
|
|
||||||
|
// sets root classes once rootView is ready...
|
||||||
|
Application.initRootView(rootView);
|
||||||
|
}
|
||||||
|
}
|
310
packages/core/ui/frame/callbacks/fragment-callbacks.ts
Normal file
310
packages/core/ui/frame/callbacks/fragment-callbacks.ts
Normal file
@ -0,0 +1,310 @@
|
|||||||
|
import { profile } from '../../../profiling';
|
||||||
|
import { AndroidFragmentCallbacks, BackstackEntry, Frame, getFrameByNumberId } from '..';
|
||||||
|
import { Trace } from '../../../trace';
|
||||||
|
import { Application } from '../../../application';
|
||||||
|
import { Color } from '../../../color';
|
||||||
|
import { _updateTransitions } from '../fragment.transitions';
|
||||||
|
import { Page } from '../../page';
|
||||||
|
|
||||||
|
const FRAMEID = '_frameId';
|
||||||
|
const CALLBACKS = '_callbacks';
|
||||||
|
|
||||||
|
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(Application.android.context.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}.`);
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,7 @@
|
|||||||
import { AndroidFragmentCallbacks, setFragmentCallbacks, setFragmentClass } from '.';
|
import { isEmbedded, getEmbeddedView } from '../embedding';
|
||||||
|
import { setFragmentCallbacks } from '.';
|
||||||
|
|
||||||
|
declare const com: any;
|
||||||
|
|
||||||
const superProto = org.nativescript.widgets.FragmentBase.prototype;
|
const superProto = org.nativescript.widgets.FragmentBase.prototype;
|
||||||
const FragmentClass = (<any>org.nativescript.widgets.FragmentBase).extend('com.tns.FragmentClass', {
|
const FragmentClass = (<any>org.nativescript.widgets.FragmentBase).extend('com.tns.FragmentClass', {
|
||||||
@ -33,9 +36,7 @@ const FragmentClass = (<any>org.nativescript.widgets.FragmentBase).extend('com.t
|
|||||||
},
|
},
|
||||||
|
|
||||||
onCreateView(inflater: android.view.LayoutInflater, container: android.view.ViewGroup, savedInstanceState: android.os.Bundle) {
|
onCreateView(inflater: android.view.LayoutInflater, container: android.view.ViewGroup, savedInstanceState: android.os.Bundle) {
|
||||||
const result = this._callbacks.onCreateView(this, inflater, container, savedInstanceState, superProto.onCreateView);
|
return this._callbacks.onCreateView(this, inflater, container, savedInstanceState, superProto.onCreateView);
|
||||||
|
|
||||||
return result;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onSaveInstanceState(outState: android.os.Bundle) {
|
onSaveInstanceState(outState: android.os.Bundle) {
|
||||||
@ -61,4 +62,49 @@ const FragmentClass = (<any>org.nativescript.widgets.FragmentBase).extend('com.t
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export let fragmentClass: any;
|
||||||
|
|
||||||
|
export function ensureFragmentClass() {
|
||||||
|
if (fragmentClass) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this require will apply the FragmentClass implementation
|
||||||
|
require('./fragment');
|
||||||
|
|
||||||
|
if (!fragmentClass) {
|
||||||
|
throw new Error('Failed to initialize the extended androidx.fragment.app.Fragment class');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setFragmentClass(clazz: any) {
|
||||||
|
if (fragmentClass) {
|
||||||
|
throw new Error('Fragment class already initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isEmbedded()) {
|
||||||
|
attachEmbeddableFragmentCallbacks();
|
||||||
|
}
|
||||||
|
|
||||||
|
fragmentClass = clazz;
|
||||||
|
}
|
||||||
|
|
||||||
|
function attachEmbeddableFragmentCallbacks() {
|
||||||
|
const Callbacks = com.tns.embedding.EmbeddableFragmentCallbacks.extend({
|
||||||
|
init() {
|
||||||
|
// init must at least be defined
|
||||||
|
},
|
||||||
|
onCreateView() {
|
||||||
|
return getEmbeddedView().nativeViewProtected;
|
||||||
|
},
|
||||||
|
onResume() {
|
||||||
|
getEmbeddedView().callLoaded();
|
||||||
|
},
|
||||||
|
onPause() {
|
||||||
|
getEmbeddedView().callUnloaded();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
com.tns.embedding.CallbacksStore.setFragmentCallbacks(new Callbacks());
|
||||||
|
}
|
||||||
|
|
||||||
setFragmentClass(FragmentClass);
|
setFragmentClass(FragmentClass);
|
||||||
|
5
packages/core/ui/frame/fragment.d.ts
vendored
Normal file
5
packages/core/ui/frame/fragment.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export function ensureFragmentClass(): void;
|
||||||
|
|
||||||
|
export function setFragmentClass(clazz: any): void;
|
||||||
|
|
||||||
|
export let fragmentClass: any;
|
5
packages/core/ui/frame/fragment.ios.ts
Normal file
5
packages/core/ui/frame/fragment.ios.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export function ensureFragmentClass(): void {}
|
||||||
|
|
||||||
|
export function setFragmentClass(clazz: any): void {}
|
||||||
|
|
||||||
|
export let fragmentClass: any;
|
@ -1,12 +1,11 @@
|
|||||||
// Definitions.
|
// Definitions.
|
||||||
import { AndroidActivityCallbacks, AndroidFragmentCallbacks, AndroidFrame as AndroidFrameDefinition, BackstackEntry, NavigationTransition } from '.';
|
import { AndroidActivityCallbacks, AndroidFrame as AndroidFrameDefinition, BackstackEntry, NavigationTransition } from '.';
|
||||||
import { Page } from '../page';
|
import { Page } from '../page';
|
||||||
import { TransitionState } from './frame-common';
|
import { TransitionState } from './frame-common';
|
||||||
|
|
||||||
// Types.
|
// Types.
|
||||||
import { AndroidActivityBackPressedEventData, AndroidActivityNewIntentEventData, AndroidActivityRequestPermissionsEventData, AndroidActivityResultEventData, Application } from '../../application';
|
import { Application } from '../../application';
|
||||||
|
|
||||||
import { Color } from '../../color';
|
|
||||||
import { Observable } from '../../data/observable';
|
import { Observable } from '../../data/observable';
|
||||||
import { Trace } from '../../trace';
|
import { Trace } from '../../trace';
|
||||||
import { View } from '../core/view';
|
import { View } from '../core/view';
|
||||||
@ -17,21 +16,23 @@ import { _clearEntry, _clearFragment, _getAnimatedEntries, _reverseTransitions,
|
|||||||
import { profile } from '../../profiling';
|
import { profile } from '../../profiling';
|
||||||
import { android as androidUtils } from '../../utils/native-helper';
|
import { android as androidUtils } from '../../utils/native-helper';
|
||||||
import type { ExpandedEntry } from './fragment.transitions.android';
|
import type { ExpandedEntry } from './fragment.transitions.android';
|
||||||
|
import { ensureFragmentClass, fragmentClass } from './fragment';
|
||||||
|
import { FragmentCallbacksImplementation } from './callbacks/fragment-callbacks';
|
||||||
|
import { ActivityCallbacksImplementation } from './callbacks/activity-callbacks';
|
||||||
|
|
||||||
export * from './frame-common';
|
export * from './frame-common';
|
||||||
|
export { setFragmentClass } from './fragment';
|
||||||
|
|
||||||
const INTENT_EXTRA = 'com.tns.activity';
|
const INTENT_EXTRA = 'com.tns.activity';
|
||||||
const ROOT_VIEW_ID_EXTRA = 'com.tns.activity.rootViewId';
|
|
||||||
const FRAMEID = '_frameId';
|
const FRAMEID = '_frameId';
|
||||||
const CALLBACKS = '_callbacks';
|
const CALLBACKS = '_callbacks';
|
||||||
|
|
||||||
const ownerSymbol = Symbol('_owner');
|
const ownerSymbol = Symbol('_owner');
|
||||||
const activityRootViewsMap = new Map<number, WeakRef<View>>();
|
|
||||||
|
|
||||||
let navDepth = -1;
|
let navDepth = -1;
|
||||||
let fragmentId = -1;
|
let fragmentId = -1;
|
||||||
|
|
||||||
export let moduleLoaded: boolean;
|
export { moduleLoaded } from './callbacks/activity-callbacks';
|
||||||
|
|
||||||
export let attachStateChangeListener: android.view.View.OnAttachStateChangeListener;
|
export let attachStateChangeListener: android.view.View.OnAttachStateChangeListener;
|
||||||
|
|
||||||
@ -756,38 +757,6 @@ class AndroidFrame extends Observable implements AndroidFrameDefinition {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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}.`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function startActivity(activity: androidx.appcompat.app.AppCompatActivity, frameId: number) {
|
function startActivity(activity: androidx.appcompat.app.AppCompatActivity, frameId: number) {
|
||||||
// TODO: Implicitly, we will open the same activity type as the current one
|
// TODO: Implicitly, we will open the same activity type as the current one
|
||||||
const intent = new android.content.Intent(activity, activity.getClass());
|
const intent = new android.content.Intent(activity, activity.getClass());
|
||||||
@ -798,7 +767,7 @@ function startActivity(activity: androidx.appcompat.app.AppCompatActivity, frame
|
|||||||
activity.startActivity(intent);
|
activity.startActivity(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFrameByNumberId(frameId: number): Frame {
|
export function getFrameByNumberId(frameId: number): Frame {
|
||||||
// Find the frame for this activity.
|
// Find the frame for this activity.
|
||||||
for (let i = 0; i < framesCache.length; i++) {
|
for (let i = 0; i < framesCache.length; i++) {
|
||||||
const aliveFrame = framesCache[i].get();
|
const aliveFrame = framesCache[i].get();
|
||||||
@ -810,578 +779,6 @@ function getFrameByNumberId(frameId: number): Frame {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureFragmentClass() {
|
|
||||||
if (fragmentClass) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// this require will apply the FragmentClass implementation
|
|
||||||
require('./fragment');
|
|
||||||
|
|
||||||
if (!fragmentClass) {
|
|
||||||
throw new Error('Failed to initialize the extended androidx.fragment.app.Fragment class');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let fragmentClass: any;
|
|
||||||
export function setFragmentClass(clazz: any) {
|
|
||||||
if (fragmentClass) {
|
|
||||||
throw new Error('Fragment class already initialized');
|
|
||||||
}
|
|
||||||
|
|
||||||
fragmentClass = clazz;
|
|
||||||
}
|
|
||||||
|
|
||||||
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(Application.android.context.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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ActivityCallbacksImplementation implements AndroidActivityCallbacks {
|
|
||||||
private _rootView: View;
|
|
||||||
|
|
||||||
public getRootView(): View {
|
|
||||||
return this._rootView;
|
|
||||||
}
|
|
||||||
|
|
||||||
@profile
|
|
||||||
public onCreate(activity: androidx.appcompat.app.AppCompatActivity, savedInstanceState: android.os.Bundle, intentOrSuperFunc: android.content.Intent | Function, superFunc?: Function): void {
|
|
||||||
if (Trace.isEnabled()) {
|
|
||||||
Trace.write(`Activity.onCreate(${savedInstanceState})`, Trace.categories.NativeLifecycle);
|
|
||||||
}
|
|
||||||
|
|
||||||
const intent: android.content.Intent = superFunc ? <android.content.Intent>intentOrSuperFunc : undefined;
|
|
||||||
|
|
||||||
if (!superFunc) {
|
|
||||||
console.log('AndroidActivityCallbacks.onCreate(activity: any, savedInstanceState: any, superFunc: Function) ' + 'is deprecated. Use AndroidActivityCallbacks.onCreate(activity: any, savedInstanceState: any, intent: any, superFunc: Function) instead.');
|
|
||||||
superFunc = <Function>intentOrSuperFunc;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there is savedInstanceState this call will recreate all fragments that were previously in the navigation.
|
|
||||||
// We take care of associating them with a Page from our backstack in the onAttachFragment callback.
|
|
||||||
// If there is savedInstanceState and moduleLoaded is false we are restarted but process was killed.
|
|
||||||
// For now we treat it like first run (e.g. we are not passing savedInstanceState so no fragments are being restored).
|
|
||||||
// When we add support for application save/load state - revise this logic.
|
|
||||||
const isRestart = !!savedInstanceState && moduleLoaded;
|
|
||||||
superFunc.call(activity, isRestart ? savedInstanceState : null);
|
|
||||||
|
|
||||||
// Try to get the rootViewId form the saved state in case the activity
|
|
||||||
// was destroyed and we are now recreating it.
|
|
||||||
if (savedInstanceState) {
|
|
||||||
const rootViewId = savedInstanceState.getInt(ROOT_VIEW_ID_EXTRA, -1);
|
|
||||||
if (rootViewId !== -1 && activityRootViewsMap.has(rootViewId)) {
|
|
||||||
this._rootView = activityRootViewsMap.get(rootViewId)?.get();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (intent && intent.getAction()) {
|
|
||||||
Application.android.notify(<AndroidActivityNewIntentEventData>{
|
|
||||||
eventName: Application.AndroidApplication.activityNewIntentEvent,
|
|
||||||
object: Application.android,
|
|
||||||
activity,
|
|
||||||
intent,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setActivityContent(activity, savedInstanceState, true);
|
|
||||||
moduleLoaded = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@profile
|
|
||||||
public onSaveInstanceState(activity: androidx.appcompat.app.AppCompatActivity, outState: android.os.Bundle, superFunc: Function): void {
|
|
||||||
superFunc.call(activity, outState);
|
|
||||||
const rootView = this._rootView;
|
|
||||||
if (rootView instanceof Frame) {
|
|
||||||
outState.putInt(INTENT_EXTRA, rootView.android.frameId);
|
|
||||||
rootView._saveFragmentsState();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rootView) {
|
|
||||||
outState.putInt(ROOT_VIEW_ID_EXTRA, rootView._domId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@profile
|
|
||||||
public onNewIntent(activity: androidx.appcompat.app.AppCompatActivity, intent: android.content.Intent, superSetIntentFunc: Function, superFunc: Function): void {
|
|
||||||
superFunc.call(activity, intent);
|
|
||||||
superSetIntentFunc.call(activity, intent);
|
|
||||||
|
|
||||||
Application.android.notify(<AndroidActivityNewIntentEventData>{
|
|
||||||
eventName: Application.AndroidApplication.activityNewIntentEvent,
|
|
||||||
object: Application.android,
|
|
||||||
activity,
|
|
||||||
intent,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@profile
|
|
||||||
public onStart(activity: any, superFunc: Function): void {
|
|
||||||
superFunc.call(activity);
|
|
||||||
|
|
||||||
if (Trace.isEnabled()) {
|
|
||||||
Trace.write('NativeScriptActivity.onStart();', Trace.categories.NativeLifecycle);
|
|
||||||
}
|
|
||||||
|
|
||||||
const rootView = this._rootView;
|
|
||||||
if (rootView && !rootView.isLoaded) {
|
|
||||||
rootView.callLoaded();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@profile
|
|
||||||
public onStop(activity: any, superFunc: Function): void {
|
|
||||||
superFunc.call(activity);
|
|
||||||
|
|
||||||
if (Trace.isEnabled()) {
|
|
||||||
Trace.write('NativeScriptActivity.onStop();', Trace.categories.NativeLifecycle);
|
|
||||||
}
|
|
||||||
|
|
||||||
const rootView = this._rootView;
|
|
||||||
if (rootView && rootView.isLoaded) {
|
|
||||||
rootView.callUnloaded();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@profile
|
|
||||||
public onPostResume(activity: any, superFunc: Function): void {
|
|
||||||
superFunc.call(activity);
|
|
||||||
|
|
||||||
if (Trace.isEnabled()) {
|
|
||||||
Trace.write('NativeScriptActivity.onPostResume();', Trace.categories.NativeLifecycle);
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: activity.onPostResume() is called when activity resume is complete and we can
|
|
||||||
// safely raise the application resume event;
|
|
||||||
// onActivityResumed(...) lifecycle callback registered in application is called too early
|
|
||||||
// and raising the application resume event there causes issues like
|
|
||||||
// https://github.com/NativeScript/NativeScript/issues/6708
|
|
||||||
if ((<any>activity).isNativeScriptActivity) {
|
|
||||||
Application.setSuspended(false, {
|
|
||||||
// todo: deprecate in favor of using event.activity instead.
|
|
||||||
android: activity,
|
|
||||||
activity,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@profile
|
|
||||||
public onDestroy(activity: any, superFunc: Function): void {
|
|
||||||
try {
|
|
||||||
if (Trace.isEnabled()) {
|
|
||||||
Trace.write('NativeScriptActivity.onDestroy();', Trace.categories.NativeLifecycle);
|
|
||||||
}
|
|
||||||
|
|
||||||
const rootView = this._rootView;
|
|
||||||
if (rootView) {
|
|
||||||
rootView._tearDownUI(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// this may happen when the user changes the system theme
|
|
||||||
// In such case, isFinishing() is false (and isChangingConfigurations is true), and the app will start again (onCreate) with a savedInstanceState
|
|
||||||
// as a result, launchEvent will never be called
|
|
||||||
// possible alternative: always fire launchEvent and exitEvent, but pass extra flags to make it clear what kind of launch/destroy is happening
|
|
||||||
if (activity.isFinishing()) {
|
|
||||||
const exitArgs = {
|
|
||||||
eventName: Application.exitEvent,
|
|
||||||
object: Application.android,
|
|
||||||
android: activity,
|
|
||||||
};
|
|
||||||
Application.notify(exitArgs);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
superFunc.call(activity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@profile
|
|
||||||
public onBackPressed(activity: any, superFunc: Function): void {
|
|
||||||
if (Trace.isEnabled()) {
|
|
||||||
Trace.write('NativeScriptActivity.onBackPressed;', Trace.categories.NativeLifecycle);
|
|
||||||
}
|
|
||||||
|
|
||||||
const args = <AndroidActivityBackPressedEventData>{
|
|
||||||
eventName: 'activityBackPressed',
|
|
||||||
object: Application,
|
|
||||||
android: Application.android,
|
|
||||||
activity: activity,
|
|
||||||
cancel: false,
|
|
||||||
};
|
|
||||||
Application.android.notify(args);
|
|
||||||
if (args.cancel) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const view = this._rootView;
|
|
||||||
let callSuper = false;
|
|
||||||
|
|
||||||
const viewArgs = <AndroidActivityBackPressedEventData>{
|
|
||||||
eventName: 'activityBackPressed',
|
|
||||||
object: view,
|
|
||||||
activity: activity,
|
|
||||||
cancel: false,
|
|
||||||
};
|
|
||||||
view.notify(viewArgs);
|
|
||||||
|
|
||||||
// In the case of Frame, use this callback only if it was overridden, since the original will cause navigation issues
|
|
||||||
if (!viewArgs.cancel && (view.onBackPressed === Frame.prototype.onBackPressed || !view.onBackPressed())) {
|
|
||||||
callSuper = view instanceof Frame ? !FrameBase.goBack() : true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (callSuper) {
|
|
||||||
superFunc.call(activity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@profile
|
|
||||||
public onRequestPermissionsResult(activity: any, requestCode: number, permissions: Array<string>, grantResults: Array<number>, superFunc: Function): void {
|
|
||||||
if (Trace.isEnabled()) {
|
|
||||||
Trace.write('NativeScriptActivity.onRequestPermissionsResult;', Trace.categories.NativeLifecycle);
|
|
||||||
}
|
|
||||||
|
|
||||||
Application.android.notify(<AndroidActivityRequestPermissionsEventData>{
|
|
||||||
eventName: 'activityRequestPermissions',
|
|
||||||
object: Application,
|
|
||||||
android: Application.android,
|
|
||||||
activity: activity,
|
|
||||||
requestCode: requestCode,
|
|
||||||
permissions: permissions,
|
|
||||||
grantResults: grantResults,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@profile
|
|
||||||
public onActivityResult(activity: any, requestCode: number, resultCode: number, data: android.content.Intent, superFunc: Function): void {
|
|
||||||
superFunc.call(activity, requestCode, resultCode, data);
|
|
||||||
if (Trace.isEnabled()) {
|
|
||||||
Trace.write(`NativeScriptActivity.onActivityResult(${requestCode}, ${resultCode}, ${data})`, Trace.categories.NativeLifecycle);
|
|
||||||
}
|
|
||||||
|
|
||||||
Application.android.notify(<AndroidActivityResultEventData>{
|
|
||||||
eventName: 'activityResult',
|
|
||||||
object: Application,
|
|
||||||
android: Application.android,
|
|
||||||
activity: activity,
|
|
||||||
requestCode: requestCode,
|
|
||||||
resultCode: resultCode,
|
|
||||||
intent: data,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public resetActivityContent(activity: androidx.appcompat.app.AppCompatActivity): void {
|
|
||||||
if (this._rootView) {
|
|
||||||
const manager = this._rootView._getFragmentManager();
|
|
||||||
manager.executePendingTransactions();
|
|
||||||
|
|
||||||
this._rootView._onRootViewReset();
|
|
||||||
}
|
|
||||||
// Delete previously cached root view in order to recreate it.
|
|
||||||
this._rootView = null;
|
|
||||||
this.setActivityContent(activity, null, false);
|
|
||||||
this._rootView.callLoaded();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Paths that go trough this method:
|
|
||||||
// 1. Application initial start - there is no rootView in callbacks.
|
|
||||||
// 2. Application revived after Activity is destroyed. this._rootView should have been restored by id in onCreate.
|
|
||||||
// 3. Livesync if rootView has no custom _onLivesync. this._rootView should have been cleared upfront. Launch event should not fired
|
|
||||||
// 4. resetRootView method. this._rootView should have been cleared upfront. Launch event should not fired
|
|
||||||
private setActivityContent(activity: androidx.appcompat.app.AppCompatActivity, savedInstanceState: android.os.Bundle, fireLaunchEvent: boolean): void {
|
|
||||||
let rootView = this._rootView;
|
|
||||||
|
|
||||||
if (Trace.isEnabled()) {
|
|
||||||
Trace.write(`Frame.setActivityContent rootView: ${rootView} shouldCreateRootFrame: false fireLaunchEvent: ${fireLaunchEvent}`, Trace.categories.NativeLifecycle);
|
|
||||||
}
|
|
||||||
|
|
||||||
const intent = activity.getIntent();
|
|
||||||
rootView = Application.createRootView(rootView, fireLaunchEvent, {
|
|
||||||
// todo: deprecate in favor of args.intent?
|
|
||||||
android: intent,
|
|
||||||
intent,
|
|
||||||
savedInstanceState,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!rootView) {
|
|
||||||
// no root view created
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
activityRootViewsMap.set(rootView._domId, new WeakRef(rootView));
|
|
||||||
|
|
||||||
// setup view as styleScopeHost
|
|
||||||
rootView._setupAsRootView(activity);
|
|
||||||
|
|
||||||
activity.setContentView(rootView.nativeViewProtected, new org.nativescript.widgets.CommonLayoutParams());
|
|
||||||
|
|
||||||
this._rootView = rootView;
|
|
||||||
|
|
||||||
// sets root classes once rootView is ready...
|
|
||||||
Application.initRootView(rootView);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setActivityCallbacks(activity: androidx.appcompat.app.AppCompatActivity): void {
|
export function setActivityCallbacks(activity: androidx.appcompat.app.AppCompatActivity): void {
|
||||||
activity[CALLBACKS] = new ActivityCallbacksImplementation();
|
activity[CALLBACKS] = new ActivityCallbacksImplementation();
|
||||||
}
|
}
|
||||||
|
18
packages/core/ui/frame/index.d.ts
vendored
18
packages/core/ui/frame/index.d.ts
vendored
@ -17,6 +17,16 @@ export interface NavigationData extends EventData {
|
|||||||
* Nested frames are supported, enabling hierarchical navigation scenarios.
|
* Nested frames are supported, enabling hierarchical navigation scenarios.
|
||||||
*/
|
*/
|
||||||
export class Frame extends FrameBase {
|
export class Frame extends FrameBase {
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_originalBackground?: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_saveFragmentsState?();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a frame by id.
|
* Gets a frame by id.
|
||||||
*/
|
*/
|
||||||
@ -259,6 +269,12 @@ export function setFragmentClass(clazz: any): void;
|
|||||||
*/
|
*/
|
||||||
export function getFrameById(id: string): Frame;
|
export function getFrameById(id: string): Frame;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* (Android Only) Gets a frame by internally tracked id.
|
||||||
|
*/
|
||||||
|
export function getFrameByNumberId(frameId: number): Frame;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated Use Frame.topmost() instead.
|
* @deprecated Use Frame.topmost() instead.
|
||||||
*
|
*
|
||||||
@ -432,6 +448,8 @@ export interface BackstackEntry {
|
|||||||
* To start a new Activity, a new Frame instance should be created and navigated to the desired Page.
|
* To start a new Activity, a new Frame instance should be created and navigated to the desired Page.
|
||||||
*/
|
*/
|
||||||
export interface AndroidFrame extends Observable {
|
export interface AndroidFrame extends Observable {
|
||||||
|
frameId?: any;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the native [android ViewGroup](http://developer.android.com/reference/android/view/ViewGroup.html) instance that represents the root layout part of the Frame.
|
* Gets the native [android ViewGroup](http://developer.android.com/reference/android/view/ViewGroup.html) instance that represents the root layout part of the Frame.
|
||||||
*/
|
*/
|
||||||
|
@ -24,6 +24,7 @@ export { DialogStrings, action, alert, confirm, login, prompt, getCurrentPage, D
|
|||||||
export type { DialogOptions, CancelableOptions, AlertOptions, PromptResult, PromptOptions, ActionOptions, ConfirmOptions, LoginResult, LoginOptions } from './dialogs';
|
export type { DialogOptions, CancelableOptions, AlertOptions, PromptResult, PromptOptions, ActionOptions, ConfirmOptions, LoginResult, LoginOptions } from './dialogs';
|
||||||
|
|
||||||
export * from './editable-text-base';
|
export * from './editable-text-base';
|
||||||
|
export { isEmbedded } from './embedding';
|
||||||
export { Frame, setActivityCallbacks } from './frame';
|
export { Frame, setActivityCallbacks } from './frame';
|
||||||
export type { NavigationEntry, NavigationContext, NavigationTransition, BackstackEntry, ViewEntry, AndroidActivityCallbacks } from './frame';
|
export type { NavigationEntry, NavigationContext, NavigationTransition, BackstackEntry, ViewEntry, AndroidActivityCallbacks } from './frame';
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { parseCSSStroke } from './css-stroke';
|
import { parseCSSStroke } from './css-stroke';
|
||||||
import { CoreTypes } from '../../core-types';
|
|
||||||
import { Length } from './style-properties';
|
import { Length } from './style-properties';
|
||||||
import { Color } from '../../color';
|
import { Color } from '../../color';
|
||||||
|
|
||||||
|
@ -2,6 +2,9 @@ import { INativeScriptPlatform } from "../helpers/platform";
|
|||||||
import { env } from '../';
|
import { env } from '../';
|
||||||
|
|
||||||
function getDistPath() {
|
function getDistPath() {
|
||||||
|
if (process.env.USER_PROJECT_PLATFORMS_ANDROID) {
|
||||||
|
return `${process.env.USER_PROJECT_PLATFORMS_ANDROID}/${process.env.USER_PROJECT_PLATFORMS_ANDROID_MODULE}/src/nativescript/assets/app`;
|
||||||
|
}
|
||||||
return `${env.buildPath ?? "platforms"}/android/app/src/main/assets/app`;
|
return `${env.buildPath ?? "platforms"}/android/app/src/main/assets/app`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,10 +11,11 @@ function sanitizeName(appName: string): string {
|
|||||||
).join("");
|
).join("");
|
||||||
}
|
}
|
||||||
function getDistPath() {
|
function getDistPath() {
|
||||||
// try projectName from nativescript.config.ts, if not set, use original method
|
// if nativescript.config projectName is defined, use that custom name
|
||||||
|
// otherwise, default to base project directory name for project name
|
||||||
const appName = getValue('projectName') ?? sanitizeName(basename(getProjectRootPath()));
|
const appName = getValue('projectName') ?? sanitizeName(basename(getProjectRootPath()));
|
||||||
|
const platform = process.env.USER_PROJECT_PLATFORMS_IOS ? process.env.USER_PROJECT_PLATFORMS_IOS : `${env.buildPath ?? "platforms"}/ios`;
|
||||||
return `${env.buildPath ?? "platforms"}/ios/${appName}/app`;
|
return `${platform}/${appName}/app`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const iOSPlatform: INativeScriptPlatform = {
|
const iOSPlatform: INativeScriptPlatform = {
|
||||||
|
Reference in New Issue
Block a user