feat(android): edge-to-edge

Squashed from feat/edge-to-edge and resolved conflicts
This commit is contained in:
Nathan Walker
2025-09-02 09:19:58 -07:00
parent a12fe192d5
commit 84e9190f87
57 changed files with 1914 additions and 260 deletions

View File

@@ -40,11 +40,12 @@ export namespace CoreTypes {
unit: 'px',
};
export type KeyboardInputType = 'datetime' | 'phone' | 'number' | 'url' | 'email' | 'integer';
export type KeyboardInputType = 'datetime' | 'phone' | 'number' | 'decimal' | 'url' | 'email' | 'integer';
export namespace KeyboardType {
export const datetime = 'datetime';
export const phone = 'phone';
export const number = 'number';
export const decimal = 'decimal';
export const url = 'url';
export const email = 'email';
export const integer = 'integer';

View File

Binary file not shown.

View File

@@ -316,6 +316,120 @@ function getModalOptions(domId: number): DialogOptions {
return modalMap.get(domId);
}
const INSET_LEFT = 0;
const INSET_TOP = 4;
const INSET_RIGHT = 8;
const INSET_BOTTOM = 12;
const INSET_LEFT_CONSUMED = 16;
const INSET_TOP_CONSUMED = 20;
const INSET_RIGHT_CONSUMED = 24;
const INSET_BOTTOM_CONSUMED = 28;
const OverflowEdgeNone: number = 0;
const OverflowEdgeLeft: number = 1;
const OverflowEdgeTop: number = 1 << 1;
const OverflowEdgeRight: number = 1 << 2;
const OverflowEdgeBottom: number = 1 << 3;
const OverflowEdgeDontApply: number = 1 << 4;
const OverflowEdgeLeftDontConsume: number = 1 << 5;
const OverflowEdgeTopDontConsume: number = 1 << 6;
const OverflowEdgeRightDontConsume: number = 1 << 7;
const OverflowEdgeBottomDontConsume: number = 1 << 8;
const OverflowEdgeAllButLeft: number = 1 << 9;
const OverflowEdgeAllButTop: number = 1 << 10;
const OverflowEdgeAllButRight: number = 1 << 11;
const OverflowEdgeAllButBottom: number = 1 << 12;
class Inset {
private view: DataView;
private data: ArrayBuffer;
constructor(data: java.nio.ByteBuffer) {
this.data = (<any>ArrayBuffer).from(data);
this.view = new DataView(this.data);
}
public get left(): number {
return this.view.getInt32(INSET_LEFT, true);
}
public set left(value: number) {
this.view.setInt32(INSET_LEFT, value, true);
}
public get top(): number {
return this.view.getInt32(INSET_TOP, true);
}
public set top(value: number) {
this.view.setInt32(INSET_TOP, value, true);
}
public get right(): number {
return this.view.getInt32(INSET_RIGHT, true);
}
public set right(value: number) {
this.view.setInt32(INSET_RIGHT, value, true);
}
public get bottom(): number {
return this.view.getInt32(INSET_BOTTOM, true);
}
public set bottom(value: number) {
this.view.setInt32(INSET_BOTTOM, value, true);
}
public get leftConsumed(): boolean {
return this.view.getInt32(INSET_LEFT_CONSUMED, true) > 0;
}
public set leftConsumed(value: boolean) {
this.view.setInt32(INSET_LEFT_CONSUMED, value ? 1 : 0, true);
}
public get topConsumed(): boolean {
return this.view.getInt32(INSET_TOP_CONSUMED, true) > 0;
}
public set topConsumed(value: boolean) {
this.view.setInt32(INSET_TOP_CONSUMED, value ? 1 : 0, true);
}
public get rightConsumed(): boolean {
return this.view.getInt32(INSET_RIGHT_CONSUMED, true) > 0;
}
public set rightConsumed(value: boolean) {
this.view.setInt32(INSET_RIGHT_CONSUMED, value ? 1 : 0, true);
}
public get bottomConsumed(): boolean {
return this.view.getInt32(INSET_BOTTOM_CONSUMED, true) > 0;
}
public set bottomConsumed(value: boolean) {
this.view.setInt32(INSET_BOTTOM_CONSUMED, value ? 1 : 0, true);
}
toString() {
return `Inset: left=${this.left}, top=${this.top}, right=${this.right}, bottom=${this.bottom}, ` + `leftConsumed=${this.leftConsumed}, topConsumed=${this.topConsumed}, ` + `rightConsumed=${this.rightConsumed}, bottomConsumed=${this.bottomConsumed}`;
}
toJSON() {
return {
left: this.left,
top: this.top,
right: this.right,
bottom: this.bottom,
leftConsumed: this.leftConsumed,
topConsumed: this.topConsumed,
rightConsumed: this.rightConsumed,
bottomConsumed: this.bottomConsumed,
};
}
}
export class View extends ViewCommon {
public static androidBackPressedEvent = androidBackPressedEvent;
@@ -326,6 +440,8 @@ export class View extends ViewCommon {
private layoutChangeListenerIsSet: boolean;
private layoutChangeListener: android.view.View.OnLayoutChangeListener;
private _rootManager: androidx.fragment.app.FragmentManager;
private insetListenerIsSet: boolean;
private needsInsetListener: boolean;
nativeViewProtected: android.view.View;
@@ -344,6 +460,12 @@ export class View extends ViewCommon {
if (this.isLoaded && !this.layoutChangeListenerIsSet && isLayoutEvent) {
this.setOnLayoutChangeListener();
}
const isInsetEvent = typeof eventNames === 'string' ? eventNames.indexOf(ViewCommon.androidOverflowInsetEvent) !== -1 : false;
// only avaiable on LayoutBase
if (!this.insetListenerIsSet && isInsetEvent) {
this.setInsetListener();
}
}
removeEventListener(eventNames: string, callback?: (data: EventData) => void, thisArg?: any) {
@@ -355,6 +477,43 @@ export class View extends ViewCommon {
this.nativeViewProtected.removeOnLayoutChangeListener(this.layoutChangeListener);
this.layoutChangeListenerIsSet = false;
}
const isInsetEvent = typeof eventNames === 'string' ? eventNames.indexOf(ViewCommon.androidOverflowInsetEvent) !== -1 : false;
if (this.insetListenerIsSet && isInsetEvent && this.nativeViewProtected && (this.nativeViewProtected as any).setInsetListener) {
(this.nativeViewProtected as any).setInsetListener(null);
this.insetListenerIsSet = false;
}
}
private setInsetListener() {
if (this.nativeViewProtected) {
if ((this.nativeViewProtected as any).setInsetListener) {
const ref = new WeakRef(this);
(this.nativeViewProtected as any).setInsetListener(
new org.nativescript.widgets.LayoutBase.WindowInsetListener({
onApplyWindowInsets(param0) {
const owner = ref.get();
if (!owner) {
return;
}
const inset = new Inset(param0);
const args = {
eventName: ViewCommon.androidOverflowInsetEvent,
object: this,
inset,
};
owner.notify(args);
},
}),
);
this.insetListenerIsSet = true;
}
this.needsInsetListener = false;
} else {
this.needsInsetListener = true;
}
}
public _getChildFragmentManager(): androidx.fragment.app.FragmentManager {
@@ -415,6 +574,134 @@ export class View extends ViewCommon {
return manager;
}
protected _defaultOverflowEdge: number = OverflowEdgeNone;
protected _defaultOverflowEdgeValue: string = 'none';
// @ts-ignore
public set androidOverflowEdge(value: string) {
if (typeof value !== 'string') {
return;
}
const nativeView = this.nativeViewProtected as any;
if (nativeView && nativeView.setOverflowEdge) {
if (value === 'none') {
nativeView.setOverflowEdge(OverflowEdgeNone);
} else {
const newValue = parseEdges(value);
if (newValue !== null) {
nativeView.setOverflowEdge(newValue);
}
}
} else {
const edge = parseEdges(value);
if (edge === null) {
return;
}
this._defaultOverflowEdgeValue = value;
this._defaultOverflowEdge = edge;
}
}
public get androidOverflowEdge() {
const nativeView = this.nativeViewProtected as any;
if (nativeView && nativeView.getOverflowEdge) {
const overflowEdge = nativeView.getOverflowEdge();
switch (overflowEdge) {
case OverflowEdgeNone:
return 'none';
case OverflowEdgeLeft:
return 'left';
case OverflowEdgeTop:
return 'top';
case OverflowEdgeRight:
return 'right';
case OverflowEdgeBottom:
return 'bottom';
case OverflowEdgeDontApply:
return 'dont-apply';
case OverflowEdgeLeftDontConsume:
return 'left-dont-consume';
case OverflowEdgeTopDontConsume:
return 'top-dont-consume';
case OverflowEdgeRightDontConsume:
return 'right-dont-consume';
case OverflowEdgeBottomDontConsume:
return 'bottom-dont-consume';
case OverflowEdgeAllButLeft:
return 'all-but-left';
case OverflowEdgeAllButTop:
return 'all-but-top';
case OverflowEdgeAllButRight:
return 'all-but-right';
case OverflowEdgeAllButBottom:
return 'all-but-bottom';
default:
{
let value = '';
const overflowLeftConsume = (overflowEdge & OverflowEdgeLeft) == OverflowEdgeLeft;
const overflowTopConsume = (overflowEdge & OverflowEdgeTop) == OverflowEdgeTop;
const overflowRightConsume = (overflowEdge & OverflowEdgeRight) == OverflowEdgeRight;
const overflowBottomConsume = (overflowEdge & OverflowEdgeBottom) == OverflowEdgeBottom;
const overflowLeft = (overflowEdge & OverflowEdgeLeftDontConsume) == OverflowEdgeLeftDontConsume;
const overflowTop = (overflowEdge & OverflowEdgeTopDontConsume) == OverflowEdgeTopDontConsume;
const overflowRight = (overflowEdge & OverflowEdgeRightDontConsume) == OverflowEdgeRightDontConsume;
const overflowBottom = (overflowEdge & OverflowEdgeBottomDontConsume) == OverflowEdgeBottomDontConsume;
if (overflowLeftConsume) {
value += 'left';
}
if (overflowTopConsume) {
if (value.length > 0) {
value += ',';
}
value += 'top';
}
if (overflowRightConsume) {
if (value.length > 0) {
value += ',';
}
value += 'right';
}
if (overflowBottomConsume) {
if (value.length > 0) {
value += ',';
}
value += 'bottom';
}
if (overflowLeft) {
value += 'left-dont-consume';
}
if (overflowTop) {
if (value.length > 0) {
value += ',';
}
value += 'top-dont-consume';
}
if (overflowRight) {
if (value.length > 0) {
value += ',';
}
value += 'right-dont-consume';
}
if (overflowBottom) {
if (value.length > 0) {
value += ',';
}
value += 'bottom-dont-consume';
}
}
break;
}
} else {
if (this._defaultOverflowEdgeValue) {
return this._defaultOverflowEdgeValue;
}
}
return 'none';
}
@profile
public onLoaded() {
this._manager = null;
@@ -464,6 +751,17 @@ export class View extends ViewCommon {
if (this.needsOnLayoutChangeListener()) {
this.setOnLayoutChangeListener();
}
if (!this.insetListenerIsSet && this.needsInsetListener) {
this.setInsetListener();
}
const nativeView = this.nativeViewProtected as any;
if (typeof this._defaultOverflowEdge === 'number') {
if (nativeView && nativeView.setOverflowEdge) {
nativeView.setOverflowEdge(this._defaultOverflowEdge);
}
}
}
public needsOnLayoutChangeListener() {
@@ -1269,6 +1567,111 @@ export class View extends ViewCommon {
}
}
function parseEdges(edges: string): number | null {
const values = edges.trim().split(',');
let newValue = -1;
for (let value of values) {
const trimmedValue = value.trim();
switch (trimmedValue) {
case 'none':
if (newValue === -1) {
newValue = OverflowEdgeNone;
} else {
newValue |= OverflowEdgeNone;
}
break;
case 'left':
if (newValue === -1) {
newValue = OverflowEdgeLeft;
} else {
newValue |= OverflowEdgeLeft;
}
break;
case 'top':
if (newValue === -1) {
newValue = OverflowEdgeTop;
} else {
newValue |= OverflowEdgeTop;
}
break;
case 'right':
if (newValue === -1) {
newValue = OverflowEdgeRight;
} else {
newValue |= OverflowEdgeRight;
}
break;
case 'bottom':
if (newValue === -1) {
newValue = OverflowEdgeBottom;
} else {
newValue |= OverflowEdgeBottom;
}
break;
case 'dont-apply':
newValue = OverflowEdgeDontApply;
break;
case 'left-dont-consume':
if (newValue === -1) {
newValue = OverflowEdgeLeftDontConsume;
} else {
newValue |= OverflowEdgeLeftDontConsume;
}
break;
case 'top-dont-consume':
if (newValue === -1) {
newValue = OverflowEdgeTopDontConsume;
} else {
newValue |= OverflowEdgeTopDontConsume;
}
break;
case 'right-dont-consume':
if (newValue === -1) {
newValue = OverflowEdgeRightDontConsume;
} else {
newValue |= OverflowEdgeRightDontConsume;
}
break;
case 'bottom-dont-consume':
if (newValue === -1) {
newValue = OverflowEdgeBottomDontConsume;
} else {
newValue |= OverflowEdgeBottomDontConsume;
}
case 'all-but-left':
if (newValue === -1) {
newValue = OverflowEdgeAllButLeft;
} else {
newValue |= OverflowEdgeAllButLeft;
}
case 'all-but-top':
if (newValue === -1) {
newValue = OverflowEdgeAllButTop;
} else {
newValue |= OverflowEdgeAllButTop;
}
case 'all-but-right':
if (newValue === -1) {
newValue = OverflowEdgeAllButRight;
} else {
newValue |= OverflowEdgeAllButRight;
}
case 'all-but-bottom':
if (newValue === -1) {
newValue = OverflowEdgeAllButBottom;
} else {
newValue |= OverflowEdgeAllButBottom;
}
break;
}
}
if (newValue === -1) {
return null;
}
return newValue;
}
export class ContainerView extends View {
public iosOverflowSafeArea: boolean;
}

View File

@@ -92,6 +92,13 @@ export abstract class View extends ViewCommon {
*/
public static accessibilityFocusChangedEvent: string;
/**
* String value used when hooking to androidOverflowInset event.
*
* @nsEvent {EventDataValue} androidOverflowInset
*/
public static androidOverflowInsetEvent: string;
/**
* Gets the android-specific native instance that lies behind this proxy. Will be available if running on an Android platform.
*/
@@ -786,6 +793,11 @@ export abstract class View extends ViewCommon {
*/
on(event: 'shownModally', callback: (args: ShownModallyData) => void, thisArg?: any);
/**
* Raised after the view is shown as a modal dialog.
*/
on(event: 'androidOverflowInset', callback: (args: ShownModallyData) => void, thisArg?: any);
/**
* Returns the current modal view that this page is showing (is parent of), if any.
*/

View File

@@ -79,6 +79,7 @@ export abstract class ViewCommon extends ViewBase {
public static accessibilityFocusEvent = accessibilityFocusEvent;
public static accessibilityFocusChangedEvent = accessibilityFocusChangedEvent;
public static accessibilityPerformEscapeEvent = accessibilityPerformEscapeEvent;
public static androidOverflowInsetEvent = 'androidOverflowInset';
public accessibilityIdentifier: string;
public accessibilityLabel: string;
@@ -979,6 +980,7 @@ export abstract class ViewCommon extends ViewBase {
public iosOverflowSafeArea: boolean;
public iosOverflowSafeAreaEnabled: boolean;
public iosIgnoreSafeArea: boolean;
public androidOverflowEdge: string;
get isLayoutValid(): boolean {
return this._isLayoutValid;

View File

@@ -59,3 +59,24 @@ export interface Size {
*/
height: number;
}
export interface Inset {
top: number;
right: number;
bottom: number;
left: number;
topConsumed: boolean;
rightConsumed: boolean;
bottomConsumed: boolean;
leftConsumed: boolean;
}
/**
* Defines the data for the androidOverflowInset event.
*/
export interface AndroidOverflowInsetData extends EventData {
/**
* The inset values passed to the view to consume or update.
*/
inset?: Inset;
}

View File

@@ -67,7 +67,7 @@ export const placeholderColorProperty = new CssProperty<Style, Color>({
});
placeholderColorProperty.register(Style);
const keyboardTypeConverter = makeParser<CoreTypes.KeyboardInputType>(makeValidator<CoreTypes.KeyboardInputType>(CoreTypes.KeyboardType.datetime, CoreTypes.KeyboardType.phone, CoreTypes.KeyboardType.number, CoreTypes.KeyboardType.url, CoreTypes.KeyboardType.email, CoreTypes.KeyboardType.integer), true);
const keyboardTypeConverter = makeParser<CoreTypes.KeyboardInputType>(makeValidator<CoreTypes.KeyboardInputType>(CoreTypes.KeyboardType.datetime, CoreTypes.KeyboardType.phone, CoreTypes.KeyboardType.number, CoreTypes.KeyboardType.decimal, CoreTypes.KeyboardType.url, CoreTypes.KeyboardType.email, CoreTypes.KeyboardType.integer), true);
export const autofillTypeProperty = new Property<EditableTextBase, CoreTypes.AutofillType>({ name: 'autofillType' });
autofillTypeProperty.register(EditableTextBase);

View File

@@ -204,7 +204,7 @@ export abstract class EditableTextBase extends EditableTextBaseCommon {
[keyboardTypeProperty.getDefault](): number {
return this.nativeTextViewProtected.getInputType();
}
[keyboardTypeProperty.setNative](value: 'datetime' | 'phone' | 'number' | 'url' | 'email' | 'integer' | number) {
[keyboardTypeProperty.setNative](value: 'datetime' | 'phone' | 'number' | 'decimal' | 'url' | 'email' | 'integer' | number) {
let newInputType;
switch (value) {
@@ -220,6 +220,10 @@ export abstract class EditableTextBase extends EditableTextBaseCommon {
newInputType = android.text.InputType.TYPE_CLASS_NUMBER | android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL | android.text.InputType.TYPE_NUMBER_FLAG_SIGNED | android.text.InputType.TYPE_NUMBER_FLAG_DECIMAL;
break;
case 'decimal':
newInputType = android.text.InputType.TYPE_CLASS_NUMBER | android.text.InputType.TYPE_NUMBER_FLAG_DECIMAL | android.text.InputType.TYPE_NUMBER_FLAG_SIGNED;
break;
case 'url':
newInputType = android.text.InputType.TYPE_CLASS_TEXT | android.text.InputType.TYPE_TEXT_VARIATION_URI;
break;

View File

@@ -34,7 +34,7 @@ export abstract class EditableTextBase extends EditableTextBaseCommon {
return keyboardType.toString();
}
}
[keyboardTypeProperty.setNative](value: 'datetime' | 'phone' | 'number' | 'url' | 'email' | 'integer' | string) {
[keyboardTypeProperty.setNative](value: 'datetime' | 'phone' | 'number' | 'decimal' | 'url' | 'email' | 'integer' | string) {
let newKeyboardType: UIKeyboardType;
switch (value) {
case 'datetime':
@@ -49,6 +49,10 @@ export abstract class EditableTextBase extends EditableTextBaseCommon {
newKeyboardType = UIKeyboardType.NumbersAndPunctuation;
break;
case 'decimal':
newKeyboardType = UIKeyboardType.DecimalPad;
break;
case 'url':
newKeyboardType = UIKeyboardType.URL;
break;

View File

@@ -2,7 +2,7 @@ import '../../globals';
import { setActivityCallbacks } from '.';
import { Application } from '../../application';
import { isEmbedded } from '../embedding';
import { enableEdgeToEdge } from '../../utils/android';
const EMPTY_FN = () => {};
declare const com: any;
@@ -22,6 +22,7 @@ if (!isEmbedded()) {
// Set isNativeScriptActivity in onCreate.
// The JS constructor might not be called because the activity is created from Android.
this.isNativeScriptActivity = true;
enableEdgeToEdge(this);
if (!this._callbacks) {
setActivityCallbacks(this);
}
@@ -78,6 +79,7 @@ if (!isEmbedded()) {
// Set isNativeScriptActivity in onCreate.
// The JS constructor might not be called because the activity is created from Android.
activity.isNativeScriptActivity = true;
enableEdgeToEdge(this);
if (!activity._callbacks) {
setActivityCallbacks(activity);
}

View File

@@ -74,6 +74,8 @@ export class Frame extends FrameBase {
private _containerViewId = -1;
private _tearDownPending = false;
private _attachedToWindow = false;
_defaultOverflowEdge: number;
_defaultOverflowEdgeValue: string;
/**
* This property indicates that the view is to be reused as a root view or has been previously disposed.
*/
@@ -84,6 +86,8 @@ export class Frame extends FrameBase {
constructor() {
super();
this._android = new AndroidFrame(this);
this._defaultOverflowEdge = 1 << 4;
this._defaultOverflowEdgeValue = 'dont-apply';
}
public static reloadPage(context?: ModuleContext): void {

View File

@@ -17,7 +17,7 @@ export { ViewBase, eachDescendant, getAncestor, getViewById, booleanConverter, q
export type { ShowModalOptions } from './core/view-base';
export { View, CSSType, ContainerView, ViewHelper, AndroidHelper, IOSHelper, isUserInteractionEnabledProperty, PseudoClassHandler, CustomLayoutView } from './core/view';
export type { Template, KeyedTemplate, AddArrayFromBuilder, AddChildFromBuilder, GlassEffectConfig, GlassEffectType, GlassEffectVariant } from './core/view';
export type { ShownModallyData, Size } from './core/view/view-interfaces';
export type { ShownModallyData, Size, AndroidOverflowInsetData } from './core/view/view-interfaces';
export { Property, CoercibleProperty, InheritedProperty, CssProperty, InheritedCssProperty, ShorthandProperty, CssAnimationProperty, makeParser, makeValidator } from './core/properties';
export { unsetValue } from './core/properties/property-shared';
export { addWeakEventListener, removeWeakEventListener } from './core/weak-event-listener';

View File

@@ -78,6 +78,9 @@ export class TextField extends TextFieldBase {
case 'number':
inputType = android.text.InputType.TYPE_CLASS_NUMBER | android.text.InputType.TYPE_NUMBER_VARIATION_NORMAL | android.text.InputType.TYPE_NUMBER_FLAG_SIGNED | android.text.InputType.TYPE_NUMBER_FLAG_DECIMAL;
break;
case 'decimal':
inputType = android.text.InputType.TYPE_CLASS_NUMBER | android.text.InputType.TYPE_NUMBER_FLAG_DECIMAL | android.text.InputType.TYPE_NUMBER_FLAG_SIGNED;
break;
case 'url':
inputType = android.text.InputType.TYPE_CLASS_TEXT | android.text.InputType.TYPE_TEXT_VARIATION_URI;
break;

View File

@@ -3,6 +3,7 @@ import { getNativeApp } from '../application/helpers-common';
import { androidGetCurrentActivity } from '../application/helpers';
import { Trace } from '../trace';
import { topmost } from '../ui/frame/frame-stack';
import { Color } from '../color';
export function dataDeserialize(nativeData?: any) {
if (nativeData === null || typeof nativeData !== 'object') {
@@ -294,3 +295,117 @@ export function isRealDevice(): boolean {
return fingerprint != null && (fingerprint.indexOf('vbox') > -1 || fingerprint.indexOf('generic') > -1);
}
const DefaultLightScrim = new Color(0xe6, 0xff, 0xff, 0xff);
const DefaultDarkScrim = new Color(0x80, 0x1b, 0x1b, 0x1b);
let statusBarDarkColor: Color | null = null;
let statusBarLightColor: Color | null = null;
export function setStatusBarColor(lightColor: Color | null = null, darkColor: Color | null = null): void {
statusBarLightColor = lightColor;
statusBarDarkColor = darkColor;
const activity = getCurrentActivity();
if (activity) {
enableEdgeToEdge(activity, {
statusBarLightColor: lightColor,
statusBarDarkColor: darkColor,
navigationBarLightColor,
navigationBarDarkColor,
handleDarkMode: darkModeHandler,
});
}
}
let navigationBarDarkColor: Color | null = null;
let navigationBarLightColor: Color | null = null;
export function setNavigationBarColor(lightColor: Color | null = null, darkColor: Color | null = null): void {
navigationBarLightColor = lightColor;
navigationBarDarkColor = darkColor;
const activity = getCurrentActivity();
if (activity) {
enableEdgeToEdge(activity, {
statusBarLightColor,
statusBarDarkColor,
navigationBarLightColor: navigationBarLightColor,
navigationBarDarkColor: navigationBarDarkColor,
handleDarkMode: darkModeHandler,
});
}
}
let darkModeHandler: ((bar: 'status' | 'navigation', resources: android.content.res.Resources) => boolean) | null = null;
export function setDarkModeHandler(handler: (bar: 'status' | 'navigation', resources: android.content.res.Resources) => boolean): void {
darkModeHandler = handler;
const activity = getCurrentActivity();
if (activity) {
enableEdgeToEdge(activity, {
statusBarLightColor,
statusBarDarkColor,
navigationBarLightColor,
navigationBarDarkColor,
handleDarkMode: handler,
});
}
}
export function enableEdgeToEdge(
activity: androidx.appcompat.app.AppCompatActivity,
options?: {
statusBarLightColor?: Color;
statusBarDarkColor?: Color;
navigationBarLightColor?: Color;
navigationBarDarkColor?: Color;
handleDarkMode?: (bar: 'status' | 'navigation', resources: android.content.res.Resources) => boolean;
},
): void {
let handleDarkMode: org.nativescript.widgets.Utils.HandleDarkMode;
let statusBarLight: number = statusBarLightColor?.android ?? 0;
let statusBarDark: number = statusBarDarkColor?.android ?? 0;
let navigationBarLight: number = navigationBarLightColor?.android ?? DefaultLightScrim.android;
let navigationBarDark: number = navigationBarDarkColor?.android ?? DefaultDarkScrim.android;
if (darkModeHandler) {
handleDarkMode = new org.nativescript.widgets.Utils.HandleDarkMode({
onHandle(bar, resources) {
if (bar === 0) {
return darkModeHandler('status', resources);
} else {
return darkModeHandler('navigation', resources);
}
},
});
}
if (options) {
if (typeof options.handleDarkMode === 'function') {
handleDarkMode = new org.nativescript.widgets.Utils.HandleDarkMode({
onHandle(bar, resources) {
if (bar === 0) {
return options.handleDarkMode('status', resources);
} else {
return options.handleDarkMode('navigation', resources);
}
},
});
}
if (options.statusBarLightColor instanceof Color) {
statusBarLight = options.statusBarLightColor.android;
}
if (options.statusBarDarkColor instanceof Color) {
statusBarDark = options.statusBarDarkColor.android;
}
if (options.navigationBarLightColor instanceof Color) {
navigationBarLight = options.navigationBarLightColor.android;
}
if (options.navigationBarDarkColor instanceof Color) {
navigationBarDark = options.navigationBarDarkColor.android;
}
}
if (handleDarkMode) {
org.nativescript.widgets.Utils.enableEdgeToEdge(activity, java.lang.Integer.valueOf(statusBarLight), java.lang.Integer.valueOf(statusBarDark), java.lang.Integer.valueOf(navigationBarLight), java.lang.Integer.valueOf(navigationBarDark), handleDarkMode);
} else {
org.nativescript.widgets.Utils.enableEdgeToEdge(activity, java.lang.Integer.valueOf(statusBarLight), java.lang.Integer.valueOf(statusBarDark), java.lang.Integer.valueOf(navigationBarLight), java.lang.Integer.valueOf(navigationBarDark));
}
}

View File

@@ -1,7 +1,7 @@
import { platformCheck } from './platform-check';
// importing this helper as a separate file avoids "android" symbol clash with the global android object
import { resources, collections, getWindow, getApplication, getCurrentActivity, getApplicationContext, getResources, getPackageName, getInputMethodManager, showSoftInput, dismissSoftInput } from './native-helper-for-android';
import { resources, collections, getWindow, getApplication, getCurrentActivity, getApplicationContext, getResources, getPackageName, getInputMethodManager, showSoftInput, dismissSoftInput, setStatusBarColor, setNavigationBarColor, setDarkModeHandler } from './native-helper-for-android';
export { dataSerialize, dataDeserialize } from './native-helper-for-android';
export { getWindow } from './native-helper-for-android';
@@ -18,6 +18,9 @@ export const android = {
getInputMethodManager,
showSoftInput,
dismissSoftInput,
setStatusBarColor,
setNavigationBarColor,
setDarkModeHandler,
};
/**

View File

@@ -107,6 +107,9 @@ export const android: {
* Hides the soft input method, usually a soft keyboard.
*/
dismissSoftInput: (nativeView?: android.view.View) => void;
setStatusBarColor: (lightColor: Color | null = null, darkColor: Color | null = null) => void;
setNavigationBarColor: (lightColor: Color | null = null, darkColor: Color | null = null) => void;
setDarkModeHandler: (handler: (bar: 'status' | 'navigation', resources: android.content.res.Resources) => boolean) => void;
};
/**