mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-18 22:01:42 +08:00
refactor: circular deps part 13
This commit is contained in:
@ -13,6 +13,7 @@ import type { StyleScope } from '../ui/styling/style-scope';
|
||||
import type { AndroidApplication as AndroidApplicationType, iOSApplication as iOSApplicationType } from '.';
|
||||
import type { ApplicationEventData, CssChangedEventData, DiscardedErrorEventData, FontScaleChangedEventData, InitRootViewEventData, LaunchEventData, LoadAppCSSEventData, NativeScriptError, OrientationChangedEventData, SystemAppearanceChangedEventData, UnhandledErrorEventData } from './application-interfaces';
|
||||
import { readyInitAccessibilityCssHelper, readyInitFontScale } from '../accessibility/accessibility-common';
|
||||
import { isAppInBackground, setAppInBackground } from './helpers-common';
|
||||
|
||||
// prettier-ignore
|
||||
const ORIENTATION_CSS_CLASSES = [
|
||||
@ -575,14 +576,12 @@ export class ApplicationCommon {
|
||||
rootView._onCssStateChange();
|
||||
}
|
||||
|
||||
private _inBackground: boolean = false;
|
||||
|
||||
get inBackground() {
|
||||
return this._inBackground;
|
||||
return isAppInBackground();
|
||||
}
|
||||
|
||||
setInBackground(value: boolean, additonalData?: any) {
|
||||
this._inBackground = value;
|
||||
setAppInBackground(value);
|
||||
|
||||
this.notify(<ApplicationEventData>{
|
||||
eventName: value ? this.backgroundEvent : this.foregroundEvent,
|
||||
|
@ -1,4 +1,3 @@
|
||||
import type { ApplicationCommon } from './application-common';
|
||||
import type { EventData, Observable } from '../data/observable';
|
||||
import type { View } from '../ui/core/view';
|
||||
|
||||
@ -42,7 +41,7 @@ export interface ApplicationEventData {
|
||||
/**
|
||||
* The instance that has raised the event.
|
||||
*/
|
||||
object: ApplicationCommon | Observable;
|
||||
object: any; // Application;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -43,6 +43,8 @@ import {
|
||||
isA11yEnabled,
|
||||
setA11yEnabled,
|
||||
} from '../accessibility/accessibility-common';
|
||||
import { androidGetForegroundActivity, androidGetStartActivity, androidPendingReceiverRegistrations, androidRegisterBroadcastReceiver, androidRegisteredReceivers, androidSetForegroundActivity, androidSetStartActivity, androidUnregisterBroadcastReceiver, applyContentDescription } from './helpers';
|
||||
import { getRootView, setA11yUpdatePropertiesCallback, setApplicationPropertiesCallback, setNativeApp, setRootView, setToggleApplicationEventListenersCallback } from './helpers-common';
|
||||
|
||||
declare namespace com {
|
||||
namespace tns {
|
||||
@ -58,38 +60,6 @@ declare namespace com {
|
||||
}
|
||||
}
|
||||
|
||||
declare class BroadcastReceiver extends android.content.BroadcastReceiver {
|
||||
constructor(onReceiveCallback: (context: android.content.Context, intent: android.content.Intent) => void);
|
||||
}
|
||||
|
||||
let BroadcastReceiver_: typeof BroadcastReceiver;
|
||||
function initBroadcastReceiver() {
|
||||
if (BroadcastReceiver_) {
|
||||
return BroadcastReceiver_;
|
||||
}
|
||||
|
||||
@NativeClass
|
||||
class BroadcastReceiverImpl 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BroadcastReceiver_ = BroadcastReceiverImpl;
|
||||
return BroadcastReceiver_;
|
||||
}
|
||||
|
||||
declare class NativeScriptLifecycleCallbacks extends android.app.Application.ActivityLifecycleCallbacks {}
|
||||
|
||||
let NativeScriptLifecycleCallbacks_: typeof NativeScriptLifecycleCallbacks;
|
||||
@ -365,6 +335,7 @@ export class AndroidApplication extends ApplicationCommon {
|
||||
}
|
||||
|
||||
this._nativeApp = nativeApp;
|
||||
setNativeApp(nativeApp);
|
||||
this._context = nativeApp.getApplicationContext();
|
||||
this._packageName = nativeApp.getPackageName();
|
||||
|
||||
@ -378,11 +349,9 @@ export class AndroidApplication extends ApplicationCommon {
|
||||
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;
|
||||
androidPendingReceiverRegistrations.forEach((func) => func(this.context));
|
||||
androidPendingReceiverRegistrations.length = 0;
|
||||
}
|
||||
|
||||
onConfigurationChanged(configuration: android.content.res.Configuration): void {
|
||||
@ -445,23 +414,20 @@ export class AndroidApplication extends ApplicationCommon {
|
||||
}
|
||||
}
|
||||
|
||||
private _startActivity: androidx.appcompat.app.AppCompatActivity;
|
||||
private _foregroundActivity: androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
get startActivity() {
|
||||
return this._startActivity;
|
||||
return androidGetStartActivity();
|
||||
}
|
||||
|
||||
get foregroundActivity() {
|
||||
return this._foregroundActivity;
|
||||
return androidGetForegroundActivity();
|
||||
}
|
||||
|
||||
setStartActivity(value: androidx.appcompat.app.AppCompatActivity) {
|
||||
this._startActivity = value;
|
||||
androidSetStartActivity(value);
|
||||
}
|
||||
|
||||
setForegroundActivity(value: androidx.appcompat.app.AppCompatActivity) {
|
||||
this._foregroundActivity = value;
|
||||
androidSetForegroundActivity(value);
|
||||
}
|
||||
|
||||
get paused(): boolean {
|
||||
@ -485,34 +451,15 @@ export class AndroidApplication extends ApplicationCommon {
|
||||
// RECEIVER_NOT_EXPORTED (4)
|
||||
// RECEIVER_VISIBLE_TO_INSTANT_APPS (1)
|
||||
public registerBroadcastReceiver(intentFilter: string, onReceiveCallback: (context: android.content.Context, intent: android.content.Intent) => void, flags = 2): void {
|
||||
const registerFunc = (context: android.content.Context) => {
|
||||
const receiver: android.content.BroadcastReceiver = new (initBroadcastReceiver())(onReceiveCallback);
|
||||
if (SDK_VERSION >= 26) {
|
||||
context.registerReceiver(receiver, new android.content.IntentFilter(intentFilter), flags);
|
||||
} else {
|
||||
context.registerReceiver(receiver, new android.content.IntentFilter(intentFilter));
|
||||
}
|
||||
this._registeredReceivers[intentFilter] = receiver;
|
||||
};
|
||||
|
||||
if (this.context) {
|
||||
registerFunc(this.context);
|
||||
} else {
|
||||
this._pendingReceiverRegistrations.push(registerFunc);
|
||||
}
|
||||
androidRegisterBroadcastReceiver(intentFilter, onReceiveCallback, flags);
|
||||
}
|
||||
|
||||
public unregisterBroadcastReceiver(intentFilter: string): void {
|
||||
const receiver = this._registeredReceivers[intentFilter];
|
||||
if (receiver) {
|
||||
this.context.unregisterReceiver(receiver);
|
||||
this._registeredReceivers[intentFilter] = undefined;
|
||||
delete this._registeredReceivers[intentFilter];
|
||||
}
|
||||
androidUnregisterBroadcastReceiver(intentFilter);
|
||||
}
|
||||
|
||||
public getRegisteredBroadcastReceiver(intentFilter: string): android.content.BroadcastReceiver | undefined {
|
||||
return this._registeredReceivers[intentFilter];
|
||||
return androidRegisteredReceivers[intentFilter];
|
||||
}
|
||||
|
||||
getRootView(): View {
|
||||
@ -522,7 +469,8 @@ export class AndroidApplication extends ApplicationCommon {
|
||||
}
|
||||
const callbacks: AndroidActivityCallbacks = activity['_callbacks'];
|
||||
|
||||
return callbacks ? callbacks.getRootView() : undefined;
|
||||
setRootView(callbacks ? callbacks.getRootView() : undefined);
|
||||
return getRootView();
|
||||
}
|
||||
|
||||
resetRootView(entry?: NavigationEntry | string): void {
|
||||
@ -1298,10 +1246,6 @@ export function isAccessibilityServiceEnabled(): boolean {
|
||||
return accessibilityServiceEnabled;
|
||||
}
|
||||
|
||||
export function setupAccessibleView(view: View): void {
|
||||
updateAccessibilityProperties(view);
|
||||
}
|
||||
|
||||
let updateAccessibilityPropertiesMicroTask;
|
||||
let pendingViews = new Set<View>();
|
||||
export function updateAccessibilityProperties(view: View) {
|
||||
@ -1325,6 +1269,7 @@ export function updateAccessibilityProperties(view: View) {
|
||||
_pendingViews = [];
|
||||
});
|
||||
}
|
||||
setA11yUpdatePropertiesCallback(updateAccessibilityProperties);
|
||||
|
||||
export function sendAccessibilityEvent(view: View, eventType: AndroidAccessibilityEvent, text?: string): void {
|
||||
if (!isAccessibilityServiceEnabled()) {
|
||||
@ -1405,14 +1350,6 @@ export function sendAccessibilityEvent(view: View, eventType: AndroidAccessibili
|
||||
accessibilityManager.sendAccessibilityEvent(accessibilityEvent);
|
||||
}
|
||||
|
||||
export function updateContentDescription(view: View, forceUpdate?: boolean): string | null {
|
||||
if (!view.nativeViewProtected) {
|
||||
return;
|
||||
}
|
||||
|
||||
return applyContentDescription(view, forceUpdate);
|
||||
}
|
||||
|
||||
function setAccessibilityDelegate(view: View): void {
|
||||
if (!view.nativeViewProtected) {
|
||||
return;
|
||||
@ -1439,105 +1376,21 @@ function setAccessibilityDelegate(view: View): void {
|
||||
androidView.setAccessibilityDelegate(TNSAccessibilityDelegate);
|
||||
}
|
||||
|
||||
function applyContentDescription(view: View, forceUpdate?: boolean) {
|
||||
let androidView = view.nativeViewProtected as android.view.View;
|
||||
if (!androidView || (androidView instanceof android.widget.TextView && !view._androidContentDescriptionUpdated)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (androidView instanceof androidx.appcompat.widget.Toolbar) {
|
||||
const numChildren = androidView.getChildCount();
|
||||
|
||||
for (let i = 0; i < numChildren; i += 1) {
|
||||
const childAndroidView = androidView.getChildAt(i);
|
||||
if (childAndroidView instanceof androidx.appcompat.widget.AppCompatTextView) {
|
||||
androidView = childAndroidView;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const cls = `applyContentDescription(${view})`;
|
||||
|
||||
const titleValue = view['title'] as string;
|
||||
const textValue = view['text'] as string;
|
||||
|
||||
if (!forceUpdate && view._androidContentDescriptionUpdated === false && textValue === view['_lastText'] && titleValue === view['_lastTitle']) {
|
||||
// prevent updating this too much
|
||||
return androidView.getContentDescription();
|
||||
}
|
||||
|
||||
const contentDescriptionBuilder = new Array<string>();
|
||||
|
||||
// Workaround: TalkBack won't read the checked state for fake Switch.
|
||||
if (view.accessibilityRole === AccessibilityRole.Switch) {
|
||||
const androidSwitch = new android.widget.Switch(Application.android.context);
|
||||
if (view.accessibilityState === AccessibilityState.Checked) {
|
||||
contentDescriptionBuilder.push(androidSwitch.getTextOn());
|
||||
const applicationEvents: string[] = [Application.orientationChangedEvent, Application.systemAppearanceChangedEvent];
|
||||
function toggleApplicationEventListeners(toAdd: boolean, callback: (args: ApplicationEventData) => void) {
|
||||
for (const eventName of applicationEvents) {
|
||||
if (toAdd) {
|
||||
Application.on(eventName, callback);
|
||||
} else {
|
||||
contentDescriptionBuilder.push(androidSwitch.getTextOff());
|
||||
Application.off(eventName, callback);
|
||||
}
|
||||
}
|
||||
|
||||
if (view.accessibilityLabel) {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`${cls} - have accessibilityLabel`, Trace.categories.Accessibility);
|
||||
}
|
||||
|
||||
contentDescriptionBuilder.push(`${view.accessibilityLabel}`);
|
||||
}
|
||||
|
||||
if (view.accessibilityValue) {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`${cls} - have accessibilityValue`, Trace.categories.Accessibility);
|
||||
}
|
||||
|
||||
contentDescriptionBuilder.push(`${view.accessibilityValue}`);
|
||||
} else if (textValue) {
|
||||
if (textValue !== view.accessibilityLabel) {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`${cls} - don't have accessibilityValue - use 'text' value`, Trace.categories.Accessibility);
|
||||
}
|
||||
|
||||
contentDescriptionBuilder.push(`${textValue}`);
|
||||
}
|
||||
} else if (titleValue) {
|
||||
if (titleValue !== view.accessibilityLabel) {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`${cls} - don't have accessibilityValue - use 'title' value`, Trace.categories.Accessibility);
|
||||
}
|
||||
|
||||
contentDescriptionBuilder.push(`${titleValue}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (view.accessibilityHint) {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`${cls} - have accessibilityHint`, Trace.categories.Accessibility);
|
||||
}
|
||||
|
||||
contentDescriptionBuilder.push(`${view.accessibilityHint}`);
|
||||
}
|
||||
|
||||
const contentDescription = contentDescriptionBuilder.join('. ').trim().replace(/^\.$/, '');
|
||||
|
||||
if (contentDescription) {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`${cls} - set to "${contentDescription}"`, Trace.categories.Accessibility);
|
||||
}
|
||||
|
||||
androidView.setContentDescription(contentDescription);
|
||||
} else {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`${cls} - remove value`, Trace.categories.Accessibility);
|
||||
}
|
||||
|
||||
androidView.setContentDescription(null);
|
||||
}
|
||||
|
||||
view['_lastTitle'] = titleValue;
|
||||
view['_lastText'] = textValue;
|
||||
view._androidContentDescriptionUpdated = false;
|
||||
|
||||
return contentDescription;
|
||||
}
|
||||
setToggleApplicationEventListenersCallback(toggleApplicationEventListeners);
|
||||
|
||||
setApplicationPropertiesCallback(() => {
|
||||
return {
|
||||
orientation: Application.orientation(),
|
||||
systemAppearance: Application.systemAppearance(),
|
||||
};
|
||||
});
|
||||
|
10
packages/core/application/application.d.ts
vendored
10
packages/core/application/application.d.ts
vendored
@ -198,11 +198,6 @@ export const VALID_FONT_SCALES: number[];
|
||||
export function getCurrentFontScale(): number;
|
||||
export function getAndroidAccessibilityManager(): android.view.accessibility.AccessibilityManager | null;
|
||||
|
||||
/**
|
||||
* Initialize accessibility for View. This should be called on loaded-event.
|
||||
*/
|
||||
export function setupAccessibleView(view: View): void;
|
||||
|
||||
/**
|
||||
* Update accessibility properties on nativeView
|
||||
*/
|
||||
@ -213,11 +208,6 @@ export function updateAccessibilityProperties(view: View): void;
|
||||
*/
|
||||
export function sendAccessibilityEvent(View: View, eventName: AndroidAccessibilityEvent, text?: string): void;
|
||||
|
||||
/**
|
||||
* Android: Update the content description for views
|
||||
*/
|
||||
export function updateContentDescription(View: View, forceUpdate?: boolean): string | null;
|
||||
|
||||
/**
|
||||
* Is Android TalkBack or iOS VoiceOver enabled?
|
||||
*/
|
||||
|
@ -45,6 +45,8 @@ import {
|
||||
setA11yEnabled,
|
||||
enforceArray,
|
||||
} from '../accessibility/accessibility-common';
|
||||
import { iosAddNotificationObserver, iosRemoveNotificationObserver } from './helpers';
|
||||
import { getiOSWindow, setA11yUpdatePropertiesCallback, setApplicationPropertiesCallback, setiOSWindow, setRootView, setToggleApplicationEventListenersCallback } from './helpers-common';
|
||||
|
||||
@NativeClass
|
||||
class CADisplayLinkTarget extends NSObject {
|
||||
@ -78,26 +80,6 @@ class CADisplayLinkTarget extends NSObject {
|
||||
};
|
||||
}
|
||||
|
||||
@NativeClass
|
||||
class NotificationObserver extends NSObject {
|
||||
private _onReceiveCallback: (notification: NSNotification) => void;
|
||||
|
||||
public static initWithCallback(onReceiveCallback: (notification: NSNotification) => void): NotificationObserver {
|
||||
const observer = <NotificationObserver>super.new();
|
||||
observer._onReceiveCallback = onReceiveCallback;
|
||||
|
||||
return observer;
|
||||
}
|
||||
|
||||
public onReceive(notification: NSNotification): void {
|
||||
this._onReceiveCallback(notification);
|
||||
}
|
||||
|
||||
public static ObjCExposedMethods = {
|
||||
onReceive: { returns: interop.types.void, params: [NSNotification] },
|
||||
};
|
||||
}
|
||||
|
||||
@NativeClass
|
||||
class Responder extends UIResponder implements UIApplicationDelegate {
|
||||
get window(): UIWindow {
|
||||
@ -114,8 +96,6 @@ class Responder extends UIResponder implements UIApplicationDelegate {
|
||||
export class iOSApplication extends ApplicationCommon {
|
||||
private _delegate: UIApplicationDelegate;
|
||||
private _delegateHandlers = new Map<string, Array<Function>>();
|
||||
private _window: UIWindow;
|
||||
private _notificationObservers: NotificationObserver[] = [];
|
||||
private _rootView: View;
|
||||
|
||||
displayedOnce = false;
|
||||
@ -167,6 +147,7 @@ export class iOSApplication extends ApplicationCommon {
|
||||
return;
|
||||
}
|
||||
this._rootView = rootView;
|
||||
setRootView(rootView);
|
||||
// Attach to the existing iOS app
|
||||
const window = getWindow() as UIWindow;
|
||||
|
||||
@ -281,12 +262,12 @@ export class iOSApplication extends ApplicationCommon {
|
||||
// TODO: consideration
|
||||
// may not want to cache this value given the potential of multiple scenes
|
||||
// particularly with SwiftUI app lifecycle based apps
|
||||
if (!this._window) {
|
||||
if (!getiOSWindow()) {
|
||||
// Note: NativeScriptViewFactory.getKeyWindow will always be used in SwiftUI app lifecycle based apps
|
||||
this._window = getWindow() as UIWindow;
|
||||
setiOSWindow(getWindow() as UIWindow);
|
||||
}
|
||||
|
||||
return this._window;
|
||||
return getiOSWindow();
|
||||
}
|
||||
|
||||
get delegate(): UIApplicationDelegate & { prototype: UIApplicationDelegate } {
|
||||
@ -342,19 +323,11 @@ export class iOSApplication extends ApplicationCommon {
|
||||
}
|
||||
|
||||
addNotificationObserver(notificationName: string, onReceiveCallback: (notification: NSNotification) => void) {
|
||||
const observer = NotificationObserver.initWithCallback(onReceiveCallback);
|
||||
NSNotificationCenter.defaultCenter.addObserverSelectorNameObject(observer, 'onReceive', notificationName, null);
|
||||
this._notificationObservers.push(observer);
|
||||
|
||||
return observer;
|
||||
return iosAddNotificationObserver(notificationName, onReceiveCallback);
|
||||
}
|
||||
|
||||
removeNotificationObserver(observer: any, notificationName: string) {
|
||||
const index = this._notificationObservers.indexOf(observer);
|
||||
if (index >= 0) {
|
||||
this._notificationObservers.splice(index, 1);
|
||||
NSNotificationCenter.defaultCenter.removeObserverNameObject(observer, notificationName, null);
|
||||
}
|
||||
removeNotificationObserver(observer: any /* NotificationObserver */, notificationName: string) {
|
||||
iosRemoveNotificationObserver(observer, notificationName);
|
||||
}
|
||||
|
||||
protected getSystemAppearance(): 'light' | 'dark' {
|
||||
@ -405,12 +378,12 @@ export class iOSApplication extends ApplicationCommon {
|
||||
ios: notification?.userInfo?.objectForKey('UIApplicationLaunchOptionsLocalNotificationKey') ?? null,
|
||||
});
|
||||
|
||||
if (this._window) {
|
||||
if (getiOSWindow()) {
|
||||
if (root !== null && !isEmbedded()) {
|
||||
this.setWindowContent(root);
|
||||
}
|
||||
} else {
|
||||
this._window = this.window; // UIApplication.sharedApplication.keyWindow;
|
||||
setiOSWindow(this.window);
|
||||
}
|
||||
}
|
||||
|
||||
@ -438,6 +411,7 @@ export class iOSApplication extends ApplicationCommon {
|
||||
const controller = this.getViewController(rootView);
|
||||
|
||||
this._rootView = rootView;
|
||||
setRootView(rootView);
|
||||
|
||||
// setup view as styleScopeHost
|
||||
rootView._setupAsRootView({});
|
||||
@ -468,11 +442,11 @@ export class iOSApplication extends ApplicationCommon {
|
||||
private didFinishLaunchingWithOptions(notification: NSNotification) {
|
||||
this.setMaxRefreshRate();
|
||||
// ensures window is assigned to proper window scene
|
||||
this._window = this.window;
|
||||
setiOSWindow(this.window);
|
||||
|
||||
if (!this._window) {
|
||||
if (!getiOSWindow()) {
|
||||
// if still no window, create one
|
||||
this._window = UIWindow.alloc().initWithFrame(UIScreen.mainScreen.bounds);
|
||||
setiOSWindow(UIWindow.alloc().initWithFrame(UIScreen.mainScreen.bounds));
|
||||
}
|
||||
|
||||
if (!__VISIONOS__) {
|
||||
@ -746,21 +720,6 @@ function ensureNativeClasses() {
|
||||
});
|
||||
}
|
||||
|
||||
export function setupAccessibleView(view: View): void {
|
||||
const uiView = view.nativeViewProtected as UIView;
|
||||
if (!uiView) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* We need to map back from the UIView to the NativeScript View.
|
||||
*
|
||||
* We do that by setting the uiView's tag to the View's domId.
|
||||
* This way we can do reverse lookup.
|
||||
*/
|
||||
uiView.tag = view._domId;
|
||||
}
|
||||
|
||||
export function updateAccessibilityProperties(view: View): void {
|
||||
const uiView = view.nativeViewProtected as UIView;
|
||||
if (!uiView) {
|
||||
@ -847,9 +806,9 @@ export function updateAccessibilityProperties(view: View): void {
|
||||
|
||||
uiView.accessibilityTraits = a11yTraits;
|
||||
}
|
||||
setA11yUpdatePropertiesCallback(updateAccessibilityProperties);
|
||||
|
||||
export const sendAccessibilityEvent = (): void => {};
|
||||
export const updateContentDescription = (): string | null => null;
|
||||
|
||||
export function isAccessibilityServiceEnabled(): boolean {
|
||||
const accessibilityServiceEnabled = isA11yEnabled();
|
||||
@ -1065,3 +1024,22 @@ export function initAccessibilityCssHelper(): void {
|
||||
accessibilityServiceObservable.on(AccessibilityServiceEnabledObservable.propertyChangeEvent, () => updateCurrentHelperClasses(applyRootCssClass));
|
||||
}
|
||||
setInitAccessibilityCssHelper(initAccessibilityCssHelper);
|
||||
|
||||
const applicationEvents: string[] = [Application.orientationChangedEvent, Application.systemAppearanceChangedEvent];
|
||||
function toggleApplicationEventListeners(toAdd: boolean, callback: (args: ApplicationEventData) => void) {
|
||||
for (const eventName of applicationEvents) {
|
||||
if (toAdd) {
|
||||
Application.on(eventName, callback);
|
||||
} else {
|
||||
Application.off(eventName, callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
setToggleApplicationEventListenersCallback(toggleApplicationEventListeners);
|
||||
|
||||
setApplicationPropertiesCallback(() => {
|
||||
return {
|
||||
orientation: Application.orientation(),
|
||||
systemAppearance: Application.systemAppearance(),
|
||||
};
|
||||
});
|
||||
|
86
packages/core/application/helpers-common.ts
Normal file
86
packages/core/application/helpers-common.ts
Normal file
@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Keep this helper file slim to avoid circular dependencies.
|
||||
* Used to define helper functions and variables that are shared between Android and iOS
|
||||
* without introducing platform-specific code directly.
|
||||
* It should not import platform-specific modules directly.
|
||||
*/
|
||||
let nativeApp: UIApplication | android.app.Application;
|
||||
|
||||
/**
|
||||
* Get the current application instance.
|
||||
* @returns current application instance, UIApplication on iOS or android.app.Application on Android.
|
||||
*/
|
||||
export function getNativeApp() {
|
||||
return nativeApp;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called internally to set the native application instance.
|
||||
* You typically do not need to call this directly.
|
||||
* However, it's exposed for special case purposes, such as custom application initialization.
|
||||
* @param app The native application instance to set.
|
||||
* This should be called once during application startup to set the native app instance.
|
||||
*/
|
||||
export function setNativeApp(app: UIApplication | android.app.Application) {
|
||||
nativeApp = app;
|
||||
}
|
||||
|
||||
let rootView: any;
|
||||
|
||||
export function getRootView() {
|
||||
return rootView;
|
||||
}
|
||||
|
||||
export function setRootView(view: any) {
|
||||
rootView = view;
|
||||
}
|
||||
|
||||
let _appInBackground: boolean = false;
|
||||
export function isAppInBackground() {
|
||||
return _appInBackground;
|
||||
}
|
||||
export function setAppInBackground(value: boolean) {
|
||||
_appInBackground = value;
|
||||
}
|
||||
|
||||
let _iosWindow: UIWindow;
|
||||
export function getiOSWindow(): UIWindow {
|
||||
return _iosWindow;
|
||||
}
|
||||
export function setiOSWindow(value: UIWindow) {
|
||||
_iosWindow = value;
|
||||
}
|
||||
|
||||
// Aids avoiding circular dependencies by allowing the application event listeners to be toggled
|
||||
let _toggleApplicationEventListenersHandler: (toAdd: boolean, callback: (args: any) => void) => void;
|
||||
export function toggleApplicationEventListeners(toAdd: boolean, callback: (args: any) => void) {
|
||||
if (_toggleApplicationEventListenersHandler) {
|
||||
_toggleApplicationEventListenersHandler(toAdd, callback);
|
||||
}
|
||||
}
|
||||
export function setToggleApplicationEventListenersCallback(callback: (toAdd: boolean, callback: (args: any) => void) => void) {
|
||||
_toggleApplicationEventListenersHandler = callback;
|
||||
}
|
||||
|
||||
// Aids avoiding circular dependencies by allowing the application properties to be retrieved
|
||||
type ApplicationPropertyValues = { orientation: 'portrait' | 'landscape' | 'unknown'; systemAppearance: 'dark' | 'light' | null };
|
||||
let _applicationPropertiesCallback: () => ApplicationPropertyValues;
|
||||
export function getApplicationProperties(): ApplicationPropertyValues {
|
||||
if (_applicationPropertiesCallback) {
|
||||
return _applicationPropertiesCallback();
|
||||
}
|
||||
return { orientation: 'unknown', systemAppearance: null };
|
||||
}
|
||||
export function setApplicationPropertiesCallback(callback: () => ApplicationPropertyValues) {
|
||||
_applicationPropertiesCallback = callback;
|
||||
}
|
||||
|
||||
let _a11yUpdatePropertiesCallback: (view: any /* View */) => void;
|
||||
export function setA11yUpdatePropertiesCallback(callback: (view: any /* View */) => void) {
|
||||
_a11yUpdatePropertiesCallback = callback;
|
||||
}
|
||||
export function updateA11yPropertiesCallback(view: any /* View */) {
|
||||
if (_a11yUpdatePropertiesCallback) {
|
||||
_a11yUpdatePropertiesCallback(view);
|
||||
}
|
||||
}
|
204
packages/core/application/helpers.android.ts
Normal file
204
packages/core/application/helpers.android.ts
Normal file
@ -0,0 +1,204 @@
|
||||
import { SDK_VERSION } from '../utils/constants';
|
||||
import { getNativeApp, updateA11yPropertiesCallback } from './helpers-common';
|
||||
import { AccessibilityRole, AccessibilityState } from '../accessibility/accessibility-common';
|
||||
import { Trace } from '../trace';
|
||||
|
||||
let _startActivity: androidx.appcompat.app.AppCompatActivity;
|
||||
let _foregroundActivity: androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
export function androidGetCurrentActivity(): androidx.appcompat.app.AppCompatActivity {
|
||||
return _foregroundActivity || _startActivity;
|
||||
}
|
||||
export function androidGetForegroundActivity(): androidx.appcompat.app.AppCompatActivity {
|
||||
return _foregroundActivity;
|
||||
}
|
||||
export function androidSetForegroundActivity(activity: androidx.appcompat.app.AppCompatActivity): void {
|
||||
_foregroundActivity = activity;
|
||||
}
|
||||
export function androidGetStartActivity(): androidx.appcompat.app.AppCompatActivity {
|
||||
return _startActivity;
|
||||
}
|
||||
export function androidSetStartActivity(activity: androidx.appcompat.app.AppCompatActivity): void {
|
||||
_startActivity = activity;
|
||||
}
|
||||
|
||||
function getApplicationContext(): android.content.Context {
|
||||
return (getNativeApp() as android.app.Application).getApplicationContext();
|
||||
}
|
||||
|
||||
export const androidRegisteredReceivers: { [key: string]: android.content.BroadcastReceiver } = {};
|
||||
export const androidPendingReceiverRegistrations = new Array<(context: android.content.Context) => void>();
|
||||
|
||||
declare class BroadcastReceiver extends android.content.BroadcastReceiver {
|
||||
constructor(onReceiveCallback: (context: android.content.Context, intent: android.content.Intent) => void);
|
||||
}
|
||||
|
||||
let BroadcastReceiver_: typeof BroadcastReceiver;
|
||||
function initBroadcastReceiver() {
|
||||
if (BroadcastReceiver_) {
|
||||
return BroadcastReceiver_;
|
||||
}
|
||||
|
||||
@NativeClass
|
||||
class BroadcastReceiverImpl 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BroadcastReceiver_ = BroadcastReceiverImpl;
|
||||
return BroadcastReceiver_;
|
||||
}
|
||||
|
||||
export function androidRegisterBroadcastReceiver(intentFilter: string, onReceiveCallback: (context: android.content.Context, intent: android.content.Intent) => void, flags = 2): void {
|
||||
const registerFunc = (context: android.content.Context) => {
|
||||
const receiver: android.content.BroadcastReceiver = new (initBroadcastReceiver())(onReceiveCallback);
|
||||
if (SDK_VERSION >= 26) {
|
||||
context.registerReceiver(receiver, new android.content.IntentFilter(intentFilter), flags);
|
||||
} else {
|
||||
context.registerReceiver(receiver, new android.content.IntentFilter(intentFilter));
|
||||
}
|
||||
androidRegisteredReceivers[intentFilter] = receiver;
|
||||
};
|
||||
|
||||
if (getApplicationContext()) {
|
||||
registerFunc(getApplicationContext());
|
||||
} else {
|
||||
androidPendingReceiverRegistrations.push(registerFunc);
|
||||
}
|
||||
}
|
||||
|
||||
export function androidUnregisterBroadcastReceiver(intentFilter: string): void {
|
||||
const receiver = androidRegisteredReceivers[intentFilter];
|
||||
if (receiver) {
|
||||
getApplicationContext().unregisterReceiver(receiver);
|
||||
androidRegisteredReceivers[intentFilter] = undefined;
|
||||
delete androidRegisteredReceivers[intentFilter];
|
||||
}
|
||||
}
|
||||
|
||||
export function updateContentDescription(view: any /* View */, forceUpdate?: boolean): string | null {
|
||||
if (!view.nativeViewProtected) {
|
||||
return;
|
||||
}
|
||||
|
||||
return applyContentDescription(view, forceUpdate);
|
||||
}
|
||||
|
||||
export function applyContentDescription(view: any /* View */, forceUpdate?: boolean) {
|
||||
let androidView = view.nativeViewProtected as android.view.View;
|
||||
if (!androidView || (androidView instanceof android.widget.TextView && !view._androidContentDescriptionUpdated)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (androidView instanceof androidx.appcompat.widget.Toolbar) {
|
||||
const numChildren = androidView.getChildCount();
|
||||
|
||||
for (let i = 0; i < numChildren; i += 1) {
|
||||
const childAndroidView = androidView.getChildAt(i);
|
||||
if (childAndroidView instanceof androidx.appcompat.widget.AppCompatTextView) {
|
||||
androidView = childAndroidView;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const cls = `applyContentDescription(${view})`;
|
||||
|
||||
const titleValue = view['title'] as string;
|
||||
const textValue = view['text'] as string;
|
||||
|
||||
if (!forceUpdate && view._androidContentDescriptionUpdated === false && textValue === view['_lastText'] && titleValue === view['_lastTitle']) {
|
||||
// prevent updating this too much
|
||||
return androidView.getContentDescription();
|
||||
}
|
||||
|
||||
const contentDescriptionBuilder = new Array<string>();
|
||||
|
||||
// Workaround: TalkBack won't read the checked state for fake Switch.
|
||||
if (view.accessibilityRole === AccessibilityRole.Switch) {
|
||||
const androidSwitch = new android.widget.Switch(getApplicationContext());
|
||||
if (view.accessibilityState === AccessibilityState.Checked) {
|
||||
contentDescriptionBuilder.push(androidSwitch.getTextOn());
|
||||
} else {
|
||||
contentDescriptionBuilder.push(androidSwitch.getTextOff());
|
||||
}
|
||||
}
|
||||
|
||||
if (view.accessibilityLabel) {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`${cls} - have accessibilityLabel`, Trace.categories.Accessibility);
|
||||
}
|
||||
|
||||
contentDescriptionBuilder.push(`${view.accessibilityLabel}`);
|
||||
}
|
||||
|
||||
if (view.accessibilityValue) {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`${cls} - have accessibilityValue`, Trace.categories.Accessibility);
|
||||
}
|
||||
|
||||
contentDescriptionBuilder.push(`${view.accessibilityValue}`);
|
||||
} else if (textValue) {
|
||||
if (textValue !== view.accessibilityLabel) {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`${cls} - don't have accessibilityValue - use 'text' value`, Trace.categories.Accessibility);
|
||||
}
|
||||
|
||||
contentDescriptionBuilder.push(`${textValue}`);
|
||||
}
|
||||
} else if (titleValue) {
|
||||
if (titleValue !== view.accessibilityLabel) {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`${cls} - don't have accessibilityValue - use 'title' value`, Trace.categories.Accessibility);
|
||||
}
|
||||
|
||||
contentDescriptionBuilder.push(`${titleValue}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (view.accessibilityHint) {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`${cls} - have accessibilityHint`, Trace.categories.Accessibility);
|
||||
}
|
||||
|
||||
contentDescriptionBuilder.push(`${view.accessibilityHint}`);
|
||||
}
|
||||
|
||||
const contentDescription = contentDescriptionBuilder.join('. ').trim().replace(/^\.$/, '');
|
||||
|
||||
if (contentDescription) {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`${cls} - set to "${contentDescription}"`, Trace.categories.Accessibility);
|
||||
}
|
||||
|
||||
androidView.setContentDescription(contentDescription);
|
||||
} else {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`${cls} - remove value`, Trace.categories.Accessibility);
|
||||
}
|
||||
|
||||
androidView.setContentDescription(null);
|
||||
}
|
||||
|
||||
view['_lastTitle'] = titleValue;
|
||||
view['_lastText'] = textValue;
|
||||
view._androidContentDescriptionUpdated = false;
|
||||
|
||||
return contentDescription;
|
||||
}
|
||||
|
||||
export function setupAccessibleView(view: any /* any */): void {
|
||||
updateA11yPropertiesCallback(view);
|
||||
}
|
25
packages/core/application/helpers.d.ts
vendored
Normal file
25
packages/core/application/helpers.d.ts
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Initialize accessibility for View. This should be called on loaded-event.
|
||||
*/
|
||||
export function setupAccessibleView(view: View): void;
|
||||
/**
|
||||
* Android: Update the content description for views
|
||||
*/
|
||||
export const updateContentDescription: (view: any /* View */, forceUpdate?: boolean) => string | null;
|
||||
export function applyContentDescription(view: any /* View */, forceUpdate?: boolean);
|
||||
/* Android app-wide helpers */
|
||||
export const androidRegisteredReceivers: { [key: string]: android.content.BroadcastReceiver };
|
||||
export const androidPendingReceiverRegistrations: Array<(context: android.content.Context) => void>;
|
||||
export function androidRegisterBroadcastReceiver(intentFilter: string, onReceiveCallback: (context: android.content.Context, intent: android.content.Intent) => void, flags = 2): void;
|
||||
export function androidUnregisterBroadcastReceiver(intentFilter: string): void;
|
||||
export function androidGetCurrentActivity(): androidx.appcompat.app.AppCompatActivity;
|
||||
export function androidGetForegroundActivity(): androidx.appcompat.app.AppCompatActivity;
|
||||
export function androidSetForegroundActivity(activity: androidx.appcompat.app.AppCompatActivity): void;
|
||||
export function androidGetStartActivity(): androidx.appcompat.app.AppCompatActivity;
|
||||
export function androidSetStartActivity(activity: androidx.appcompat.app.AppCompatActivity): void;
|
||||
|
||||
/* iOS app-wide helpers */
|
||||
export const iosNotificationObservers: NotificationObserver[];
|
||||
class NotificationObserver extends NSObject {}
|
||||
export function iosAddNotificationObserver(notificationName: string, onReceiveCallback: (notification: NSNotification) => void): NotificationObserver;
|
||||
export function iosRemoveNotificationObserver(observer: NotificationObserver, notificationName: string): void;
|
68
packages/core/application/helpers.ios.ts
Normal file
68
packages/core/application/helpers.ios.ts
Normal file
@ -0,0 +1,68 @@
|
||||
// stubs to avoid bundler warnings
|
||||
export const updateContentDescription = (view: any /* View */, forceUpdate?: boolean): string | null => null;
|
||||
export function applyContentDescription(view: any /* View */, forceUpdate?: boolean) {
|
||||
return null;
|
||||
}
|
||||
export const androidRegisteredReceivers = undefined;
|
||||
export const androidPendingReceiverRegistrations = undefined;
|
||||
export function androidRegisterBroadcastReceiver(intentFilter: string, onReceiveCallback: (context: android.content.Context, intent: android.content.Intent) => void, flags = 2): void {}
|
||||
export function androidUnregisterBroadcastReceiver(intentFilter: string): void {}
|
||||
export function androidGetCurrentActivity() {}
|
||||
export function androidGetForegroundActivity() {}
|
||||
export function androidSetForegroundActivity(activity: androidx.appcompat.app.AppCompatActivity): void {}
|
||||
export function androidGetStartActivity() {}
|
||||
export function androidSetStartActivity(activity: androidx.appcompat.app.AppCompatActivity): void {}
|
||||
|
||||
@NativeClass
|
||||
class NotificationObserver extends NSObject {
|
||||
private _onReceiveCallback: (notification: NSNotification) => void;
|
||||
|
||||
public static initWithCallback(onReceiveCallback: (notification: NSNotification) => void): NotificationObserver {
|
||||
const observer = <NotificationObserver>super.new();
|
||||
observer._onReceiveCallback = onReceiveCallback;
|
||||
|
||||
return observer;
|
||||
}
|
||||
|
||||
public onReceive(notification: NSNotification): void {
|
||||
this._onReceiveCallback(notification);
|
||||
}
|
||||
|
||||
public static ObjCExposedMethods = {
|
||||
onReceive: { returns: interop.types.void, params: [NSNotification] },
|
||||
};
|
||||
}
|
||||
|
||||
export const iosNotificationObservers: NotificationObserver[] = [];
|
||||
export function iosAddNotificationObserver(notificationName: string, onReceiveCallback: (notification: NSNotification) => void) {
|
||||
const observer = NotificationObserver.initWithCallback(onReceiveCallback);
|
||||
NSNotificationCenter.defaultCenter.addObserverSelectorNameObject(observer, 'onReceive', notificationName, null);
|
||||
iosNotificationObservers.push(observer);
|
||||
|
||||
return observer;
|
||||
}
|
||||
|
||||
export function iosRemoveNotificationObserver(observer: NotificationObserver, notificationName: string) {
|
||||
// TODO: test if this finds the right observer instance match everytime
|
||||
// after circular dependencies are resolved
|
||||
const index = iosNotificationObservers.indexOf(observer);
|
||||
if (index >= 0) {
|
||||
iosNotificationObservers.splice(index, 1);
|
||||
NSNotificationCenter.defaultCenter.removeObserverNameObject(observer, notificationName, null);
|
||||
}
|
||||
}
|
||||
|
||||
export function setupAccessibleView(view: any /* any */): void {
|
||||
const uiView = view.nativeViewProtected as UIView;
|
||||
if (!uiView) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* We need to map back from the UIView to the NativeScript View.
|
||||
*
|
||||
* We do that by setting the uiView's tag to the View's domId.
|
||||
* This way we can do reverse lookup.
|
||||
*/
|
||||
uiView.tag = view._domId;
|
||||
}
|
Reference in New Issue
Block a user