Files
NativeScript/tns-core-modules/application/application.android.ts
Hristo Hristov af034089ca iOS Frame, Page and TabView measure/layout methods removed. We now rely on the framework positioning. This will result in a change that width, height, minWidth, minHeight, margins not respected on these controls
iOS layout positioning now respects native properties like automaticallyAdjustsScrollViewInsets, edgesForExtendedLayout, extendedLayoutIncludesOpaqueBars, navigationBar.translucent, tabBar.translucent
Removed frame-tests.ios.ts - those tests are now invalid
Added new layout tests inside page-tests.ios.ts
Commented few asserts in scroll-view-tests
View now expose ios namespace with layoutView method and UILayoutViewController used by page, tab-view and application module
ViewBase now expose viewController property that should be set from all widgets that are using viewcontrollers internally (like Page, Frame, TabView)
ViewBase now sets ios property to either the view returned from createNativeView or to nativeViewProptected
fragment.transitions now use animation/transition start to add fragments to waitingQueue. Before we did it manually in navigate/goBack. This way we can reuse the fragment.transition when calling showDialog. Also when animation/transition ends we check the animation/transition to see if this fragment should be set as current.
Frame expose new loadViewFromEntry method (to load a view from URI)
Frame navigation happens once frame is loaded
Frame now supports Page as a child in XML
Fixed GridLayout row, rowSpan, column, columnSpan properties type
Fixed bug in GridLayout where add/remove of columns/rows won't update the internal state of the grid (backport from android when GridLayout is recycled)
ListView will no longer invalidate layout when cell is removed
Fixed bug in ScrollView ios where effectiveMinWidth/Height was multiplied to density (it is already on device pixels so no need to multiply)
TabView android now calls loaded only on the selected child (not all)
Core refactoring
2017-12-15 13:06:34 +02:00

363 lines
15 KiB
TypeScript

import {
AndroidActivityBundleEventData, AndroidActivityEventData, ApplicationEventData, OrientationChangedEventData,
AndroidApplication as AndroidApplicationDefinition,
AndroidActivityResultEventData, AndroidActivityBackPressedEventData, AndroidActivityRequestPermissionsEventData
} from ".";
import {
notify, hasListeners, lowMemoryEvent, orientationChangedEvent, suspendEvent, resumeEvent, displayedEvent,
setApplication, livesync, Observable
} from "./application-common";
import { profile } from "../profiling";
// First reexport so that app module is initialized.
export * from "./application-common";
import { NavigationEntry } from "../ui/frame";
const ActivityCreated = "activityCreated";
const ActivityDestroyed = "activityDestroyed";
const ActivityStarted = "activityStarted";
const ActivityPaused = "activityPaused";
const ActivityResumed = "activityResumed";
const ActivityStopped = "activityStopped";
const SaveActivityState = "saveActivityState";
const ActivityResult = "activityResult";
const ActivityBackPressed = "activityBackPressed";
const ActivityRequestPermissions = "activityRequestPermissions";
export class AndroidApplication extends Observable implements AndroidApplicationDefinition {
public static activityCreatedEvent = ActivityCreated;
public static activityDestroyedEvent = ActivityDestroyed;
public static activityStartedEvent = ActivityStarted;
public static activityPausedEvent = ActivityPaused;
public static activityResumedEvent = ActivityResumed;
public static activityStoppedEvent = ActivityStopped;
public static saveActivityStateEvent = SaveActivityState;
public static activityResultEvent = ActivityResult;
public static activityBackPressedEvent = ActivityBackPressed;
public static activityRequestPermissionsEvent = ActivityRequestPermissions;
public paused: boolean;
public nativeApp: android.app.Application;
public context: android.content.Context;
public foregroundActivity: android.app.Activity;
public startActivity: android.app.Activity;
public packageName: string;
public get currentContext(): android.content.Context {
return this.foregroundActivity;
}
public init(nativeApp: android.app.Application) {
if (this.nativeApp === nativeApp) {
return;
}
if (this.nativeApp) {
throw new Error("application.android already initialized.");
}
this.nativeApp = nativeApp;
this.packageName = nativeApp.getPackageName();
this.context = nativeApp.getApplicationContext();
let lifecycleCallbacks = initLifecycleCallbacks();
let componentCallbacks = initComponentCallbacks();
this.nativeApp.registerActivityLifecycleCallbacks(lifecycleCallbacks);
this.nativeApp.registerComponentCallbacks(componentCallbacks);
this._registerPendingReceivers();
}
private _registeredReceivers = {};
private _pendingReceiverRegistrations = new Array<(context: android.content.Context) => void>();
private _registerPendingReceivers() {
this._pendingReceiverRegistrations.forEach(func => func(this.context));
this._pendingReceiverRegistrations.length = 0;
}
public registerBroadcastReceiver(intentFilter: string, onReceiveCallback: (context: android.content.Context, intent: android.content.Intent) => void) {
ensureBroadCastReceiverClass();
const that = this;
const registerFunc = function (context: android.content.Context) {
const receiver: android.content.BroadcastReceiver = new BroadcastReceiverClass(onReceiveCallback);
context.registerReceiver(receiver, new android.content.IntentFilter(intentFilter));
that._registeredReceivers[intentFilter] = receiver;
};
if (this.context) {
registerFunc(this.context);
}
else {
this._pendingReceiverRegistrations.push(registerFunc);
}
}
public unregisterBroadcastReceiver(intentFilter: string) {
const receiver = this._registeredReceivers[intentFilter];
if (receiver) {
this.context.unregisterReceiver(receiver);
this._registeredReceivers[intentFilter] = undefined;
delete this._registeredReceivers[intentFilter];
}
}
}
export interface AndroidApplication {
on(eventNames: string, callback: (data: AndroidActivityEventData) => void, thisArg?: any);
on(event: "activityCreated", callback: (args: AndroidActivityBundleEventData) => void, thisArg?: any);
on(event: "activityDestroyed", callback: (args: AndroidActivityEventData) => void, thisArg?: any);
on(event: "activityStarted", callback: (args: AndroidActivityEventData) => void, thisArg?: any);
on(event: "activityPaused", callback: (args: AndroidActivityEventData) => void, thisArg?: any);
on(event: "activityResumed", callback: (args: AndroidActivityEventData) => void, thisArg?: any);
on(event: "activityStopped", callback: (args: AndroidActivityEventData) => void, thisArg?: any);
on(event: "saveActivityState", callback: (args: AndroidActivityBundleEventData) => void, thisArg?: any);
on(event: "activityResult", callback: (args: AndroidActivityResultEventData) => void, thisArg?: any);
on(event: "activityBackPressed", callback: (args: AndroidActivityBackPressedEventData) => void, thisArg?: any);
on(event: "activityRequestPermissions", callback: (args: AndroidActivityRequestPermissionsEventData) => void, thisArg?: any);
}
const androidApp = new AndroidApplication();
// use the exports object instead of 'export var' due to global namespace collision
exports.android = androidApp;
setApplication(androidApp);
let mainEntry: NavigationEntry;
let started = false;
// NOTE: for backwards compatibility. Remove for 4.0.0.
let createRootFrame = true;
export function start(entry?: NavigationEntry | string) {
if (started) {
throw new Error("Application is already started.");
}
started = true;
mainEntry = typeof entry === "string" ? { moduleName: entry } : entry;
if (!androidApp.nativeApp) {
const nativeApp = getNativeApplication();
androidApp.init(nativeApp);
}
}
export function shouldCreateRootFrame(): boolean {
return createRootFrame;
}
export function run(entry?: NavigationEntry | string) {
createRootFrame = false;
start(entry);
}
export function getMainEntry() {
return mainEntry;
}
export function getNativeApplication(): android.app.Application {
// Try getting it from module - check whether application.android.init has been explicitly called
let nativeApp = androidApp.nativeApp;
if (!nativeApp) {
// check whether the com.tns.NativeScriptApplication type exists
if (!nativeApp && com.tns.NativeScriptApplication) {
nativeApp = com.tns.NativeScriptApplication.getInstance();
}
// the getInstance might return null if com.tns.NativeScriptApplication exists but is not the starting app type
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?
const clazz = java.lang.Class.forName("android.app.ActivityThread");
if (clazz) {
const method = clazz.getMethod("currentApplication", null);
if (method) {
nativeApp = method.invoke(null, null);
}
}
}
// we cannot work without having the app instance
if (!nativeApp) {
throw new Error("Failed to retrieve native Android Application object. If you have a custom android.app.Application type implemented make sure that you've called the '<application-module>.android.init' method.")
}
}
return nativeApp;
}
global.__onLiveSync = function () {
if (androidApp && androidApp.paused) {
return;
}
livesync();
};
function initLifecycleCallbacks() {
const setThemeOnLaunch = profile("setThemeOnLaunch", (activity: android.app.Activity) => {
// Set app theme after launch screen was used during startup
const activityInfo = activity.getPackageManager().getActivityInfo(activity.getComponentName(), android.content.pm.PackageManager.GET_META_DATA);
if (activityInfo.metaData) {
const setThemeOnLaunch = activityInfo.metaData.getInt("SET_THEME_ON_LAUNCH", -1);
if (setThemeOnLaunch !== -1) {
activity.setTheme(setThemeOnLaunch);
}
}
});
const notifyActivityCreated = profile("notifyActivityCreated", function(activity: android.app.Activity, savedInstanceState: android.os.Bundle) {
androidApp.notify(<AndroidActivityBundleEventData>{ eventName: ActivityCreated, object: androidApp, activity, bundle: savedInstanceState });
});
const subscribeForGlobalLayout = profile("subscribeForGlobalLayout", function(activity: android.app.Activity) {
const rootView = activity.getWindow().getDecorView().getRootView();
let onGlobalLayoutListener = new android.view.ViewTreeObserver.OnGlobalLayoutListener({
onGlobalLayout() {
notify({ eventName: displayedEvent, object: androidApp, activity });
let viewTreeObserver = rootView.getViewTreeObserver();
viewTreeObserver.removeOnGlobalLayoutListener(onGlobalLayoutListener);
}
});
rootView.getViewTreeObserver().addOnGlobalLayoutListener(onGlobalLayoutListener);
});
const lifecycleCallbacks = new android.app.Application.ActivityLifecycleCallbacks({
onActivityCreated: profile("onActivityCreated", function (activity: android.app.Activity, savedInstanceState: android.os.Bundle) {
setThemeOnLaunch(activity);
if (!androidApp.startActivity) {
androidApp.startActivity = activity;
}
notifyActivityCreated(activity, savedInstanceState);
if (hasListeners(displayedEvent)) {
subscribeForGlobalLayout(activity);
}
}),
onActivityDestroyed: profile("onActivityDestroyed", function (activity: android.app.Activity) {
if (activity === androidApp.foregroundActivity) {
androidApp.foregroundActivity = undefined;
}
if (activity === androidApp.startActivity) {
androidApp.startActivity = undefined;
}
androidApp.notify(<AndroidActivityEventData>{ eventName: ActivityDestroyed, object: androidApp, activity: activity });
// TODO: This is a temporary workaround to force the V8's Garbage Collector, which will force the related Java Object to be collected.
gc();
}),
onActivityPaused: profile("onActivityPaused", function (activity: android.app.Activity) {
if ((<any>activity).isNativeScriptActivity) {
androidApp.paused = true;
notify(<ApplicationEventData>{ eventName: suspendEvent, object: androidApp, android: activity });
}
androidApp.notify(<AndroidActivityEventData>{ eventName: ActivityPaused, object: androidApp, activity: activity });
}),
onActivityResumed: profile("onActivityResumed", function (activity: android.app.Activity) {
androidApp.foregroundActivity = activity;
if ((<any>activity).isNativeScriptActivity) {
notify(<ApplicationEventData>{ eventName: resumeEvent, object: androidApp, android: activity });
androidApp.paused = false;
}
androidApp.notify(<AndroidActivityEventData>{ eventName: ActivityResumed, object: androidApp, activity: activity });
}),
onActivitySaveInstanceState: profile("onActivityResumed", function (activity: android.app.Activity, outState: android.os.Bundle) {
androidApp.notify(<AndroidActivityBundleEventData>{ eventName: SaveActivityState, object: androidApp, activity: activity, bundle: outState });
}),
onActivityStarted: profile("onActivityStarted", function (activity: android.app.Activity) {
androidApp.notify(<AndroidActivityEventData>{ eventName: ActivityStarted, object: androidApp, activity: activity });
}),
onActivityStopped: profile("onActivityStopped", function (activity: android.app.Activity) {
androidApp.notify(<AndroidActivityEventData>{ eventName: ActivityStopped, object: androidApp, activity: activity });
})
});
return lifecycleCallbacks;
}
let currentOrientation: number;
function initComponentCallbacks() {
let componentCallbacks = new android.content.ComponentCallbacks2({
onLowMemory: profile("onLowMemory", function () {
gc();
java.lang.System.gc();
notify(<ApplicationEventData>{ eventName: lowMemoryEvent, object: this, android: this });
}),
onTrimMemory: profile("onTrimMemory", function (level: number) {
// TODO: This is skipped for now, test carefully for OutOfMemory exceptions
}),
onConfigurationChanged: profile("onConfigurationChanged", function (newConfig: android.content.res.Configuration) {
const newOrientation = newConfig.orientation;
if (newOrientation === currentOrientation) {
return;
}
currentOrientation = newOrientation;
let newValue;
switch (newOrientation) {
case android.content.res.Configuration.ORIENTATION_LANDSCAPE:
newValue = "landscape";
break;
case android.content.res.Configuration.ORIENTATION_PORTRAIT:
newValue = "portrait";
break;
default:
newValue = "unknown";
break;
}
notify(<OrientationChangedEventData>{
eventName: orientationChangedEvent,
android: androidApp.nativeApp,
newValue: newValue,
object: androidApp
});
})
});
return componentCallbacks;
}
let BroadcastReceiverClass;
function ensureBroadCastReceiverClass() {
if (BroadcastReceiverClass) {
return;
}
class BroadcastReceiver extends android.content.BroadcastReceiver {
private _onReceiveCallback: (context: android.content.Context, intent: android.content.Intent) => void;
constructor(onReceiveCallback: (context: android.content.Context, intent: android.content.Intent) => void) {
super();
this._onReceiveCallback = onReceiveCallback;
return global.__native(this);
}
public onReceive(context: android.content.Context, intent: android.content.Intent) {
if (this._onReceiveCallback) {
this._onReceiveCallback(context, intent);
}
}
}
BroadcastReceiverClass = BroadcastReceiver;
}
declare namespace com {
namespace tns {
class NativeScriptApplication extends android.app.Application {
static getInstance(): NativeScriptApplication;
}
}
}