refactor(core): zero circulars + esm ready (#10770)

This commit is contained in:
Nathan Walker
2025-09-18 17:03:23 -07:00
committed by GitHub
parent 1e54baf198
commit c2ff8c1ae7
306 changed files with 9136 additions and 9889 deletions

View File

@ -1,18 +1,21 @@
import { initAccessibilityCssHelper } from '../accessibility/accessibility-css-helper';
import { initAccessibilityFontScale } from '../accessibility/font-scale';
import { CoreTypes } from '../core-types';
import { CSSUtils } from '../css/system-classes';
import { Device, Screen } from '../platform';
import { profile } from '../profiling';
import { Trace } from '../trace';
import { clearResolverCache, prepareAppForModuleResolver, _setResolver } from '../module-name-resolver/helpers';
import { Builder } from '../ui/builder';
import * as bindableResources from '../ui/core/bindable/bindable-resources';
import type { View } from '../ui/core/view';
import type { Frame } from '../ui/frame';
import type { NavigationEntry } from '../ui/frame/frame-interfaces';
import type { StyleScope } from '../ui/styling/style-scope';
import type { AndroidApplication as IAndroidApplication, iOSApplication as IiOSApplication } from './';
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 { getAppMainEntry, isAppInBackground, setAppInBackground, setAppMainEntry } from './helpers-common';
import { getNativeScriptGlobals } from '../globals/global-utils';
import { SDK_VERSION } from '../utils/constants';
// prettier-ignore
const ORIENTATION_CSS_CLASSES = [
@ -27,7 +30,55 @@ const SYSTEM_APPEARANCE_CSS_CLASSES = [
`${CSSUtils.CLASS_PREFIX}${CoreTypes.SystemAppearance.dark}`,
];
const globalEvents = global.NativeScriptGlobals.events;
// SDK Version CSS classes
let sdkVersionClasses: string[] = [];
export function initializeSdkVersionClass(rootView: View): void {
const majorVersion = Math.floor(SDK_VERSION);
sdkVersionClasses = [];
let platformPrefix = '';
if (__APPLE__) {
platformPrefix = __VISIONOS__ ? 'ns-visionos' : 'ns-ios';
} else if (__ANDROID__) {
platformPrefix = 'ns-android';
}
if (platformPrefix) {
// Add exact version class (e.g., .ns-ios-26 or .ns-android-36)
// this acts like 'gte' for that major version range
// e.g., if user wants iOS 27, they can add .ns-ios-27 specifiers
sdkVersionClasses.push(`${platformPrefix}-${majorVersion}`);
}
// Apply the SDK version classes to root views
applySdkVersionClass(rootView);
}
function applySdkVersionClass(rootView: View): void {
if (!sdkVersionClasses.length) {
return;
}
if (!rootView) {
return;
}
// Batch apply all SDK version classes to root view for better performance
const classesToAdd = sdkVersionClasses.filter((className) => !rootView.cssClasses.has(className));
classesToAdd.forEach((className) => rootView.cssClasses.add(className));
// Apply to modal views only if there are any
const rootModalViews = <Array<View>>rootView._getRootModalViews();
if (rootModalViews.length > 0) {
rootModalViews.forEach((rootModalView) => {
const modalClassesToAdd = sdkVersionClasses.filter((className) => !rootModalView.cssClasses.has(className));
modalClassesToAdd.forEach((className) => rootModalView.cssClasses.add(className));
});
}
}
const globalEvents = getNativeScriptGlobals().events;
// helper interface to correctly type Application event handlers
interface ApplicationEvents {
@ -160,7 +211,7 @@ export class ApplicationCommon {
* @internal - should not be constructed by the user.
*/
constructor() {
global.NativeScriptGlobals.appInstanceReady = true;
getNativeScriptGlobals().appInstanceReady = true;
global.__onUncaughtError = (error: NativeScriptError) => {
this.notify({
@ -309,13 +360,11 @@ export class ApplicationCommon {
// implement in platform specific files (iOS only for now)
}
protected mainEntry: NavigationEntry;
/**
* @returns The main entry of the application
*/
getMainEntry() {
return this.mainEntry;
return getAppMainEntry();
}
@profile
@ -349,11 +398,11 @@ export class ApplicationCommon {
if (!rootView) {
// try to navigate to the mainEntry (if specified)
if (!this.mainEntry) {
if (!getAppMainEntry()) {
throw new Error('Main entry is missing. App cannot be started. Verify app bootstrap.');
}
rootView = Builder.createViewFromEntry(this.mainEntry);
rootView = Builder.createViewFromEntry(getAppMainEntry());
}
}
@ -365,14 +414,14 @@ export class ApplicationCommon {
}
resetRootView(entry?: NavigationEntry | string) {
this.mainEntry = typeof entry === 'string' ? { moduleName: entry } : entry;
setAppMainEntry(typeof entry === 'string' ? { moduleName: entry } : entry);
// rest of implementation is platform specific
}
initRootView(rootView: View) {
this.setRootViewCSSClasses(rootView);
initAccessibilityCssHelper();
initAccessibilityFontScale();
readyInitAccessibilityCssHelper();
readyInitFontScale();
this.notify(<InitRootViewEventData>{ eventName: this.initRootViewEvent, rootView });
}
@ -508,7 +557,7 @@ export class ApplicationCommon {
}
hasLaunched(): boolean {
return global.NativeScriptGlobals && global.NativeScriptGlobals.launched;
return getNativeScriptGlobals().launched;
}
private _systemAppearance: 'dark' | 'light' | null;
@ -575,14 +624,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,
@ -614,11 +661,11 @@ export class ApplicationCommon {
public started = false;
get android(): IAndroidApplication {
get android(): AndroidApplicationType {
return undefined;
}
get ios(): IiOSApplication {
get ios(): iOSApplicationType {
return undefined;
}
@ -630,3 +677,10 @@ export class ApplicationCommon {
return this.ios;
}
}
prepareAppForModuleResolver(() => {
ApplicationCommon.on('livesync', (args) => clearResolverCache());
ApplicationCommon.on('orientationChanged', (args) => {
_setResolver(undefined);
});
});

View File

@ -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;
}
/**

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,5 @@
import { ApplicationCommon } from './application-common';
import { FontScaleCategory } from '../accessibility/font-scale-common';
export * from './application-common';
export * from './application-interfaces';
@ -192,3 +193,27 @@ export class iOSApplication extends ApplicationCommon {
*/
removeNotificationObserver(observer: any, notificationName: string);
}
export const VALID_FONT_SCALES: number[];
export function getCurrentFontScale(): number;
export function getAndroidAccessibilityManager(): android.view.accessibility.AccessibilityManager | null;
/**
* Update accessibility properties on nativeView
*/
export function updateAccessibilityProperties(view: View): void;
/**
* Android: helper function for triggering accessibility events
*/
export function sendAccessibilityEvent(View: View, eventName: AndroidAccessibilityEvent, text?: string): void;
/**
* Is Android TalkBack or iOS VoiceOver enabled?
*/
export function isAccessibilityServiceEnabled(): boolean;
/**
* Find the last view focused on a page.
*/
export function getLastFocusedViewOnPage(page: Page): View | null;

View File

@ -1,12 +1,53 @@
import { profile } from '../profiling';
import { View } from '../ui/core/view';
import type { View } from '../ui/core/view';
import { isEmbedded } from '../ui/embedding';
import { IOSHelper } from '../ui/core/view/view-helper';
import { NavigationEntry } from '../ui/frame/frame-interfaces';
import * as Utils from '../utils';
import type { iOSApplication as IiOSApplication } from './application';
import { ApplicationCommon } from './application-common';
import type { NavigationEntry } from '../ui/frame/frame-interfaces';
import { getWindow } from '../utils/native-helper';
import { SDK_VERSION } from '../utils/constants';
import { ios as iosUtils } from '../utils/native-helper';
import { ApplicationCommon, initializeSdkVersionClass } from './application-common';
import { ApplicationEventData } from './application-interfaces';
import { Observable } from '../data/observable';
import { Trace } from '../trace';
import {
AccessibilityServiceEnabledPropName,
CommonA11YServiceEnabledObservable,
SharedA11YObservable,
a11yServiceClasses,
a11yServiceDisabledClass,
a11yServiceEnabledClass,
fontScaleCategoryClasses,
fontScaleExtraLargeCategoryClass,
fontScaleExtraSmallCategoryClass,
fontScaleMediumCategoryClass,
getCurrentA11YServiceClass,
getCurrentFontScaleCategory,
getCurrentFontScaleClass,
getFontScaleCssClasses,
setCurrentA11YServiceClass,
setCurrentFontScaleCategory,
setCurrentFontScaleClass,
setFontScaleCssClasses,
FontScaleCategory,
getClosestValidFontScale,
VALID_FONT_SCALES,
setFontScale,
getFontScale,
setInitFontScale,
getFontScaleCategory,
setInitAccessibilityCssHelper,
notifyAccessibilityFocusState,
AccessibilityLiveRegion,
AccessibilityRole,
AccessibilityState,
AccessibilityTrait,
isA11yEnabled,
setA11yEnabled,
enforceArray,
} from '../accessibility/accessibility-common';
import { iosAddNotificationObserver, iosRemoveNotificationObserver } from './helpers';
import { getiOSWindow, setA11yUpdatePropertiesCallback, setApplicationPropertiesCallback, setAppMainEntry, setiOSWindow, setRootView, setToggleApplicationEventListenersCallback } from './helpers-common';
@NativeClass
class CADisplayLinkTarget extends NSObject {
@ -40,26 +81,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 {
@ -73,11 +94,9 @@ class Responder extends UIResponder implements UIApplicationDelegate {
static ObjCProtocols = [UIApplicationDelegate];
}
export class iOSApplication extends ApplicationCommon implements IiOSApplication {
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;
@ -108,7 +127,7 @@ export class iOSApplication extends ApplicationCommon implements IiOSApplication
}
run(entry?: string | NavigationEntry): void {
this.mainEntry = typeof entry === 'string' ? { moduleName: entry } : entry;
setAppMainEntry(typeof entry === 'string' ? { moduleName: entry } : entry);
this.started = true;
if (this.nativeApp) {
@ -129,8 +148,9 @@ export class iOSApplication extends ApplicationCommon implements IiOSApplication
return;
}
this._rootView = rootView;
setRootView(rootView);
// Attach to the existing iOS app
const window = Utils.ios.getWindow();
const window = getWindow() as UIWindow;
if (!window) {
return;
@ -155,7 +175,7 @@ export class iOSApplication extends ApplicationCommon implements IiOSApplication
this.setViewControllerView(rootView);
embedderDelegate.presentNativeScriptApp(controller);
} else {
const visibleVC = Utils.ios.getVisibleViewController(rootController);
const visibleVC = iosUtils.getVisibleViewController(rootController);
visibleVC.presentViewControllerAnimatedCompletion(controller, true, null);
}
@ -198,7 +218,7 @@ export class iOSApplication extends ApplicationCommon implements IiOSApplication
if (minFrameRateDisabled) {
let max = 120;
const deviceMaxFrames = Utils.ios.getMainScreen().maximumFramesPerSecond;
const deviceMaxFrames = iosUtils.getMainScreen().maximumFramesPerSecond;
if (options?.max) {
if (deviceMaxFrames) {
// iOS 10.3
@ -209,7 +229,7 @@ export class iOSApplication extends ApplicationCommon implements IiOSApplication
}
}
if (Utils.SDK_VERSION >= 15 || __VISIONOS__) {
if (SDK_VERSION >= 15 || __VISIONOS__) {
const min = options?.min || max / 2;
const preferred = options?.preferred || max;
this.displayedLink.preferredFrameRateRange = CAFrameRateRangeMake(min, max, preferred);
@ -243,12 +263,12 @@ export class iOSApplication extends ApplicationCommon implements IiOSApplication
// 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 = Utils.ios.getWindow();
setiOSWindow(getWindow() as UIWindow);
}
return this._window;
return getiOSWindow();
}
get delegate(): UIApplicationDelegate & { prototype: UIApplicationDelegate } {
@ -304,24 +324,16 @@ export class iOSApplication extends ApplicationCommon implements IiOSApplication
}
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' {
// userInterfaceStyle is available on UITraitCollection since iOS 12.
if ((!__VISIONOS__ && Utils.SDK_VERSION <= 11) || !this.rootController) {
if ((!__VISIONOS__ && SDK_VERSION <= 11) || !this.rootController) {
return null;
}
@ -367,12 +379,12 @@ export class iOSApplication extends ApplicationCommon implements IiOSApplication
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);
}
}
@ -400,6 +412,7 @@ export class iOSApplication extends ApplicationCommon implements IiOSApplication
const controller = this.getViewController(rootView);
this._rootView = rootView;
setRootView(rootView);
// setup view as styleScopeHost
rootView._setupAsRootView({});
@ -428,17 +441,31 @@ export class iOSApplication extends ApplicationCommon implements IiOSApplication
// Observers
@profile
private didFinishLaunchingWithOptions(notification: NSNotification) {
if (__DEV__) {
/**
* v9+ runtime crash handling
* When crash occurs during boot, we let runtime take over
*/
if (notification.userInfo) {
const isBootCrash = notification.userInfo.objectForKey('NativeScriptBootCrash');
if (isBootCrash) {
// fatal crash will show in console without app exiting
// allowing hot reload fixes to continue
return;
}
}
}
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__) {
this.window.backgroundColor = Utils.SDK_VERSION <= 12 || !UIColor.systemBackgroundColor ? UIColor.whiteColor : UIColor.systemBackgroundColor;
this.window.backgroundColor = SDK_VERSION <= 12 || !UIColor.systemBackgroundColor ? UIColor.whiteColor : UIColor.systemBackgroundColor;
}
this.notifyAppStarted(notification);
@ -514,3 +541,523 @@ global.__onLiveSyncCore = function (context?: ModuleContext) {
export * from './application-common';
export const Application = iosApp;
export const AndroidApplication = undefined;
function fontScaleChanged(origFontScale: number) {
const oldValue = getFontScale();
setFontScale(getClosestValidFontScale(origFontScale));
const currentFontScale = getFontScale();
if (oldValue !== currentFontScale) {
Application.notify({
eventName: Application.fontScaleChangedEvent,
object: Application,
newValue: currentFontScale,
});
}
}
export function getCurrentFontScale(): number {
setupConfigListener();
return getFontScale();
}
const sizeMap = new Map<string, number>([
[UIContentSizeCategoryExtraSmall, 0.5],
[UIContentSizeCategorySmall, 0.7],
[UIContentSizeCategoryMedium, 0.85],
[UIContentSizeCategoryLarge, 1],
[UIContentSizeCategoryExtraLarge, 1.15],
[UIContentSizeCategoryExtraExtraLarge, 1.3],
[UIContentSizeCategoryExtraExtraExtraLarge, 1.5],
[UIContentSizeCategoryAccessibilityMedium, 2],
[UIContentSizeCategoryAccessibilityLarge, 2.5],
[UIContentSizeCategoryAccessibilityExtraLarge, 3],
[UIContentSizeCategoryAccessibilityExtraExtraLarge, 3.5],
[UIContentSizeCategoryAccessibilityExtraExtraExtraLarge, 4],
]);
function contentSizeUpdated(fontSize: string) {
if (sizeMap.has(fontSize)) {
fontScaleChanged(sizeMap.get(fontSize));
return;
}
fontScaleChanged(1);
}
function useIOSFontScale() {
if (Application.ios.nativeApp) {
contentSizeUpdated(Application.ios.nativeApp.preferredContentSizeCategory);
} else {
fontScaleChanged(1);
}
}
let fontSizeObserver;
function setupConfigListener(attempt = 0) {
if (fontSizeObserver) {
return;
}
if (!Application.ios.nativeApp) {
if (attempt > 100) {
fontScaleChanged(1);
return;
}
// Couldn't get launchEvent to trigger.
setTimeout(() => setupConfigListener(attempt + 1), 1);
return;
}
fontSizeObserver = Application.ios.addNotificationObserver(UIContentSizeCategoryDidChangeNotification, (args) => {
const fontSize = args.userInfo.valueForKey(UIContentSizeCategoryNewValueKey);
contentSizeUpdated(fontSize);
});
Application.on(Application.exitEvent, () => {
if (fontSizeObserver) {
Application.ios.removeNotificationObserver(fontSizeObserver, UIContentSizeCategoryDidChangeNotification);
fontSizeObserver = null;
}
Application.off(Application.resumeEvent, useIOSFontScale);
});
Application.on(Application.resumeEvent, useIOSFontScale);
useIOSFontScale();
}
setInitFontScale(setupConfigListener);
/**
* Convert array of values into a bitmask.
*
* @param values string values
* @param map map lower-case name to integer value.
*/
function inputArrayToBitMask(values: string | string[], map: Map<string, number>): number {
return (
enforceArray(values)
.filter((value) => !!value)
.map((value) => `${value}`.toLocaleLowerCase())
.filter((value) => map.has(value))
.reduce((res, value) => res | map.get(value), 0) || 0
);
}
let AccessibilityTraitsMap: Map<string, number>;
let RoleTypeMap: Map<AccessibilityRole, number>;
let nativeFocusedNotificationObserver;
let lastFocusedView: WeakRef<View>;
function ensureNativeClasses() {
if (AccessibilityTraitsMap && nativeFocusedNotificationObserver) {
return;
}
AccessibilityTraitsMap = new Map<AccessibilityTrait, number>([
[AccessibilityTrait.AllowsDirectInteraction, UIAccessibilityTraitAllowsDirectInteraction],
[AccessibilityTrait.CausesPageTurn, UIAccessibilityTraitCausesPageTurn],
[AccessibilityTrait.NotEnabled, UIAccessibilityTraitNotEnabled],
[AccessibilityTrait.Selected, UIAccessibilityTraitSelected],
[AccessibilityTrait.UpdatesFrequently, UIAccessibilityTraitUpdatesFrequently],
]);
RoleTypeMap = new Map<AccessibilityRole, number>([
[AccessibilityRole.Adjustable, UIAccessibilityTraitAdjustable],
[AccessibilityRole.Button, UIAccessibilityTraitButton],
[AccessibilityRole.Checkbox, UIAccessibilityTraitButton],
[AccessibilityRole.Header, UIAccessibilityTraitHeader],
[AccessibilityRole.KeyboardKey, UIAccessibilityTraitKeyboardKey],
[AccessibilityRole.Image, UIAccessibilityTraitImage],
[AccessibilityRole.ImageButton, UIAccessibilityTraitImage | UIAccessibilityTraitButton],
[AccessibilityRole.Link, UIAccessibilityTraitLink],
[AccessibilityRole.None, UIAccessibilityTraitNone],
[AccessibilityRole.PlaysSound, UIAccessibilityTraitPlaysSound],
[AccessibilityRole.RadioButton, UIAccessibilityTraitButton],
[AccessibilityRole.Search, UIAccessibilityTraitSearchField],
[AccessibilityRole.StaticText, UIAccessibilityTraitStaticText],
[AccessibilityRole.StartsMediaSession, UIAccessibilityTraitStartsMediaSession],
[AccessibilityRole.Summary, UIAccessibilityTraitSummaryElement],
[AccessibilityRole.Switch, UIAccessibilityTraitButton],
]);
nativeFocusedNotificationObserver = Application.ios.addNotificationObserver(UIAccessibilityElementFocusedNotification, (args: NSNotification) => {
const uiView = args.userInfo?.objectForKey(UIAccessibilityFocusedElementKey) as UIView;
if (!uiView?.tag) {
return;
}
const rootView = Application.getRootView();
// We use the UIView's tag to find the NativeScript View by its domId.
let view = rootView.getViewByDomId<View>(uiView?.tag);
if (!view) {
for (const modalView of <Array<View>>rootView._getRootModalViews()) {
view = modalView.getViewByDomId(uiView?.tag);
if (view) {
break;
}
}
}
if (!view) {
return;
}
const lastView = lastFocusedView?.deref();
if (lastView && view !== lastView) {
const lastFocusedUIView = lastView.nativeViewProtected as UIView;
if (lastFocusedUIView) {
lastFocusedView = null;
notifyAccessibilityFocusState(lastView, false, true);
}
}
lastFocusedView = new WeakRef(view);
notifyAccessibilityFocusState(view, true, false);
});
Application.on(Application.exitEvent, () => {
if (nativeFocusedNotificationObserver) {
Application.ios.removeNotificationObserver(nativeFocusedNotificationObserver, UIAccessibilityElementFocusedNotification);
}
nativeFocusedNotificationObserver = null;
lastFocusedView = null;
});
}
export function updateAccessibilityProperties(view: View): void {
const uiView = view.nativeViewProtected as UIView;
if (!uiView) {
return;
}
ensureNativeClasses();
const accessibilityRole = view.accessibilityRole;
const accessibilityState = view.accessibilityState;
if (!view.accessible || view.accessibilityHidden) {
uiView.accessibilityTraits = UIAccessibilityTraitNone;
return;
}
// NOTE: left here for various core inspection passes while running the toolbox app
// console.log('--- Accessible element: ', view.constructor.name);
// console.log('accessibilityLabel: ', view.accessibilityLabel);
// console.log('accessibilityRole: ', accessibilityRole);
// console.log('accessibilityState: ', accessibilityState);
// console.log('accessibilityValue: ', view.accessibilityValue);
let a11yTraits = UIAccessibilityTraitNone;
if (RoleTypeMap.has(accessibilityRole)) {
a11yTraits |= RoleTypeMap.get(accessibilityRole);
}
switch (accessibilityRole) {
case AccessibilityRole.Checkbox:
case AccessibilityRole.RadioButton:
case AccessibilityRole.Switch: {
if (accessibilityState === AccessibilityState.Checked) {
a11yTraits |= AccessibilityTraitsMap.get(AccessibilityTrait.Selected);
}
break;
}
default: {
if (accessibilityState === AccessibilityState.Selected) {
a11yTraits |= AccessibilityTraitsMap.get(AccessibilityTrait.Selected);
}
if (accessibilityState === AccessibilityState.Disabled) {
a11yTraits |= AccessibilityTraitsMap.get(AccessibilityTrait.NotEnabled);
}
break;
}
}
const UpdatesFrequentlyTrait = AccessibilityTraitsMap.get(AccessibilityTrait.UpdatesFrequently);
switch (view.accessibilityLiveRegion) {
case AccessibilityLiveRegion.Polite:
case AccessibilityLiveRegion.Assertive: {
a11yTraits |= UpdatesFrequentlyTrait;
break;
}
default: {
a11yTraits &= ~UpdatesFrequentlyTrait;
break;
}
}
// NOTE: left here for various core inspection passes while running the toolbox app
// if (view.accessibilityLiveRegion) {
// console.log('accessibilityLiveRegion:', view.accessibilityLiveRegion);
// }
if (view.accessibilityMediaSession) {
a11yTraits |= RoleTypeMap.get(AccessibilityRole.StartsMediaSession);
}
// NOTE: There were duplicated types in traits and roles previously which we conslidated
// not sure if this is still needed
// accessibilityTraits used to be stored on {N} view component but if the above
// is combining all traits fresh each time through, don't believe we need to keep track or previous traits
// if (view.accessibilityTraits) {
// a11yTraits |= inputArrayToBitMask(view.accessibilityTraits, AccessibilityTraitsMap);
// }
// NOTE: left here for various core inspection passes while running the toolbox app
// console.log('a11yTraits:', a11yTraits);
// console.log(' ');
uiView.accessibilityTraits = a11yTraits;
}
setA11yUpdatePropertiesCallback(updateAccessibilityProperties);
export const sendAccessibilityEvent = (): void => {};
export function isAccessibilityServiceEnabled(): boolean {
const accessibilityServiceEnabled = isA11yEnabled();
if (typeof accessibilityServiceEnabled === 'boolean') {
return accessibilityServiceEnabled;
}
let isVoiceOverRunning: () => boolean;
if (typeof UIAccessibilityIsVoiceOverRunning === 'function') {
isVoiceOverRunning = UIAccessibilityIsVoiceOverRunning;
} else {
// iOS is too old to tell us if voice over is enabled
if (typeof UIAccessibilityIsVoiceOverRunning !== 'function') {
setA11yEnabled(false);
return isA11yEnabled();
}
}
setA11yEnabled(isVoiceOverRunning());
let voiceOverStatusChangedNotificationName: string | null = null;
if (typeof UIAccessibilityVoiceOverStatusDidChangeNotification !== 'undefined') {
voiceOverStatusChangedNotificationName = UIAccessibilityVoiceOverStatusDidChangeNotification;
} else if (typeof UIAccessibilityVoiceOverStatusChanged !== 'undefined') {
voiceOverStatusChangedNotificationName = UIAccessibilityVoiceOverStatusChanged;
}
if (voiceOverStatusChangedNotificationName) {
nativeObserver = Application.ios.addNotificationObserver(voiceOverStatusChangedNotificationName, () => {
setA11yEnabled(isVoiceOverRunning());
});
Application.on(Application.exitEvent, () => {
if (nativeObserver) {
Application.ios.removeNotificationObserver(nativeObserver, voiceOverStatusChangedNotificationName);
}
setA11yEnabled(undefined);
nativeObserver = null;
});
}
Application.on(Application.resumeEvent, () => {
setA11yEnabled(isVoiceOverRunning());
});
return isA11yEnabled();
}
export function getAndroidAccessibilityManager(): null {
return null;
}
let sharedA11YObservable: SharedA11YObservable;
let nativeObserver;
function getSharedA11YObservable(): SharedA11YObservable {
if (sharedA11YObservable) {
return sharedA11YObservable;
}
sharedA11YObservable = new SharedA11YObservable();
let isVoiceOverRunning: () => boolean;
if (typeof UIAccessibilityIsVoiceOverRunning === 'function') {
isVoiceOverRunning = UIAccessibilityIsVoiceOverRunning;
} else {
if (typeof UIAccessibilityIsVoiceOverRunning !== 'function') {
Trace.write(`UIAccessibilityIsVoiceOverRunning() - is not a function`, Trace.categories.Accessibility, Trace.messageType.error);
isVoiceOverRunning = () => false;
}
}
sharedA11YObservable.set(AccessibilityServiceEnabledPropName, isVoiceOverRunning());
let voiceOverStatusChangedNotificationName: string | null = null;
if (typeof UIAccessibilityVoiceOverStatusDidChangeNotification !== 'undefined') {
// iOS 11+
voiceOverStatusChangedNotificationName = UIAccessibilityVoiceOverStatusDidChangeNotification;
} else if (typeof UIAccessibilityVoiceOverStatusChanged !== 'undefined') {
// iOS <11
voiceOverStatusChangedNotificationName = UIAccessibilityVoiceOverStatusChanged;
}
if (voiceOverStatusChangedNotificationName) {
nativeObserver = Application.ios.addNotificationObserver(voiceOverStatusChangedNotificationName, () => {
sharedA11YObservable?.set(AccessibilityServiceEnabledPropName, isVoiceOverRunning());
});
Application.on(Application.exitEvent, () => {
if (nativeObserver) {
Application.ios.removeNotificationObserver(nativeObserver, voiceOverStatusChangedNotificationName);
}
nativeObserver = null;
if (sharedA11YObservable) {
sharedA11YObservable.removeEventListener(Observable.propertyChangeEvent);
sharedA11YObservable = null;
}
});
}
Application.on(Application.resumeEvent, () => sharedA11YObservable.set(AccessibilityServiceEnabledPropName, isVoiceOverRunning()));
return sharedA11YObservable;
}
export class AccessibilityServiceEnabledObservable extends CommonA11YServiceEnabledObservable {
constructor() {
super(getSharedA11YObservable());
}
}
let accessibilityServiceObservable: AccessibilityServiceEnabledObservable;
export function ensureClasses() {
if (accessibilityServiceObservable) {
return;
}
setFontScaleCssClasses(new Map(VALID_FONT_SCALES.map((fs) => [fs, `a11y-fontscale-${Number(fs * 100).toFixed(0)}`])));
accessibilityServiceObservable = new AccessibilityServiceEnabledObservable();
// Initialize SDK version CSS class once
initializeSdkVersionClass(Application.getRootView());
}
export function updateCurrentHelperClasses(applyRootCssClass: (cssClasses: string[], newCssClass: string) => void): void {
const fontScale = getFontScale();
const fontScaleCategory = getFontScaleCategory();
const fontScaleCssClasses = getFontScaleCssClasses();
const oldFontScaleClass = getCurrentFontScaleClass();
if (fontScaleCssClasses.has(fontScale)) {
setCurrentFontScaleClass(fontScaleCssClasses.get(fontScale));
} else {
setCurrentFontScaleClass(fontScaleCssClasses.get(1));
}
if (oldFontScaleClass !== getCurrentFontScaleClass()) {
applyRootCssClass([...fontScaleCssClasses.values()], getCurrentFontScaleClass());
}
const oldActiveFontScaleCategory = getCurrentFontScaleCategory();
switch (fontScaleCategory) {
case FontScaleCategory.ExtraSmall: {
setCurrentFontScaleCategory(fontScaleExtraSmallCategoryClass);
break;
}
case FontScaleCategory.Medium: {
setCurrentFontScaleCategory(fontScaleMediumCategoryClass);
break;
}
case FontScaleCategory.ExtraLarge: {
setCurrentFontScaleCategory(fontScaleExtraLargeCategoryClass);
break;
}
default: {
setCurrentFontScaleCategory(fontScaleMediumCategoryClass);
break;
}
}
if (oldActiveFontScaleCategory !== getCurrentFontScaleCategory()) {
applyRootCssClass(fontScaleCategoryClasses, getCurrentFontScaleCategory());
}
const oldA11YStatusClass = getCurrentA11YServiceClass();
if (accessibilityServiceObservable.accessibilityServiceEnabled) {
setCurrentA11YServiceClass(a11yServiceEnabledClass);
} else {
setCurrentA11YServiceClass(a11yServiceDisabledClass);
}
if (oldA11YStatusClass !== getCurrentA11YServiceClass()) {
applyRootCssClass(a11yServiceClasses, getCurrentA11YServiceClass());
}
}
function applyRootCssClass(cssClasses: string[], newCssClass: string): void {
const rootView = Application.getRootView();
if (!rootView) {
return;
}
Application.applyCssClass(rootView, cssClasses, newCssClass);
const rootModalViews = <Array<View>>rootView._getRootModalViews();
rootModalViews.forEach((rootModalView) => Application.applyCssClass(rootModalView, cssClasses, newCssClass));
}
function applyFontScaleToRootViews(): void {
const rootView = Application.getRootView();
if (!rootView) {
return;
}
const fontScale = getCurrentFontScale();
rootView.style.fontScaleInternal = fontScale;
const rootModalViews = <Array<View>>rootView._getRootModalViews();
rootModalViews.forEach((rootModalView) => (rootModalView.style.fontScaleInternal = fontScale));
}
export function initAccessibilityCssHelper(): void {
ensureClasses();
Application.on(Application.fontScaleChangedEvent, () => {
updateCurrentHelperClasses(applyRootCssClass);
applyFontScaleToRootViews();
});
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(),
};
});

View File

@ -0,0 +1,191 @@
/**
* Do not import other files here to avoid circular dependencies.
* Used to define helper functions and variables that are shared between Android and iOS.
* This file can declare cross platform types and use bundler platform checks.
* For example, use `__ANDROID__` or `__APPLE__` to check the platform.
*/
/**
* Application type. UIApplication on iOS or android.app.Application on Android.
*/
type NativeApp = UIApplication | android.app.Application;
let nativeApp: NativeApp;
declare namespace com {
namespace tns {
class NativeScriptApplication extends android.app.Application {
static getInstance(): NativeScriptApplication;
}
namespace embedding {
class ApplicationHolder {
static getInstance(): android.app.Application;
}
}
}
}
function isEmbedded(): boolean {
if (__APPLE__) {
return !!NativeScriptEmbedder.sharedInstance().delegate;
} else {
// @ts-ignore
// Check if the Bootstrap class exists and has the isEmbeddedNativeScript property
// This is a way to determine if the app is embedded in a host project.
return org.nativescript?.Bootstrap?.isEmbeddedNativeScript;
}
}
/**
* Get the current application instance.
* @returns current application instance, UIApplication on iOS or android.app.Application on Android.
*/
export function getNativeApp<T extends NativeApp>(): T {
if (__ANDROID__) {
if (!nativeApp) {
// Try getting it from module - check whether application.android.init has been explicitly called
// check whether the com.tns.NativeScriptApplication type exists
if (com.tns.NativeScriptApplication) {
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
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);
}
}
}
}
}
return nativeApp! as T;
}
/**
* 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: NativeApp) {
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;
}
let _appMainEntry: any /* NavigationEntry */;
export function getAppMainEntry(): any /* NavigationEntry */ {
return _appMainEntry;
}
export function setAppMainEntry(entry: any /* NavigationEntry */) {
_appMainEntry = entry;
}
// 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);
}
}
/**
* Internal Android app helpers
*/
let _imageFetcher: org.nativescript.widgets.image.Fetcher;
export function getImageFetcher(): org.nativescript.widgets.image.Fetcher {
return _imageFetcher;
}
export function setImageFetcher(fetcher: org.nativescript.widgets.image.Fetcher) {
_imageFetcher = fetcher;
}
export enum CacheMode {
none,
memory,
diskAndMemory,
}
let _currentCacheMode: CacheMode;
export function initImageCache(context: android.content.Context, mode = CacheMode.diskAndMemory, memoryCacheSize = 0.25, diskCacheSize: number = 10 * 1024 * 1024): void {
if (_currentCacheMode === mode) {
return;
}
_currentCacheMode = mode;
if (!getImageFetcher()) {
setImageFetcher(org.nativescript.widgets.image.Fetcher.getInstance(context));
} else {
getImageFetcher().clearCache();
}
const params = new org.nativescript.widgets.image.Cache.CacheParams();
params.memoryCacheEnabled = mode !== CacheMode.none;
params.setMemCacheSizePercent(memoryCacheSize); // Set memory cache to % of app memory
params.diskCacheEnabled = mode === CacheMode.diskAndMemory;
params.diskCacheSize = diskCacheSize;
const imageCache = org.nativescript.widgets.image.Cache.getInstance(params);
getImageFetcher().addImageCache(imageCache);
getImageFetcher().initCache();
}

View File

@ -0,0 +1,209 @@
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<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);
}
// stubs
export const iosNotificationObservers: Array<any> = [];
export function iosAddNotificationObserver(notificationName: string, onReceiveCallback: (notification: any) => void) {}
export function iosRemoveNotificationObserver(observer: any, notificationName: string) {}

25
packages/core/application/helpers.d.ts vendored Normal file
View 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;

View 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;
}