Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/toolbox/src/main-page.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo" class="page" androidOverflowEdge="bottom" statusBarStyle="dark">
<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo" class="page" androidOverflowEdge="bottom" statusBarStyle="dark">
<Page.actionBar>
<ActionBar title="Dev Toolbox" icon="" class="action-bar">
</ActionBar>
Expand Down Expand Up @@ -33,6 +33,7 @@
<Button text="fs-helper" tap="{{ viewDemo }}" class="btn btn-primary btn-view-demo" />
<Button text="webview" tap="{{ viewDemo }}" class="btn btn-primary btn-view-demo" />
<Button text="winter-tc" tap="{{ viewDemo }}" class="btn btn-primary btn-view-demo" />
<Button text="ete" tap="{{ viewDemo }}" class="btn btn-primary btn-view-demo" />
</StackLayout>
</ScrollView>
</StackLayout>
Expand Down
83 changes: 83 additions & 0 deletions apps/toolbox/src/pages/ete.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { Page, Observable, EventData, Dialogs, Button, View } from '@nativescript/core';

let page: Page;

export function navigatingTo(args: EventData) {
page = <Page>args.object;
page.bindingContext = new ETEData();
}

export class ETEData extends Observable {
showActionBar: boolean = true;
color = 'white';
viewColor = 'white';
overflowTopBottom: boolean = false;
overflowManual = false;
overflowPage: boolean = false;

togglePageOverflow(args) {
const button = args.object as Button;
const page = button.page;
this.overflowPage = !this.overflowPage;
page.frame.androidOverflowEdge = this.overflowPage ? 'dont-apply' : 'none';
}

toggleActionBar() {
this.showActionBar = !this.showActionBar;
this.notifyPropertyChange('showActionBar', this.showActionBar);
}

randomColor() {
const letters = '0123456789ABCDEF';
let color = '#';
let viewColor = '#';
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
viewColor += letters[Math.floor(Math.random() * 16)];
}
this.set('color', color);
this.set('viewColor', viewColor);
}

toggleOverflowTopBottom(args) {
const button = args.object as Button;
const page = button.page;
this.overflowTopBottom = !this.overflowTopBottom;
page.androidOverflowEdge = this.overflowTopBottom ? 'top,bottom' : 'none';
}

padding = '0';

inset = {
top: 0,
left: 0,
right: 0,
bottom: 0,
ime: {
//keyboard insets
top: 0,
left: 0,
right: 0,
bottom: 0,
},
};

onInset(args) {
this.inset.top = args.inset.top;
this.inset.left = args.inset.left;
this.inset.right = args.inset.right;
this.inset.bottom = args.inset.bottom;
this.padding = `${this.inset.top}px ${this.inset.right}px ${this.inset.bottom}px ${this.inset.left}px`;
this.set('padding', this.padding);
}

toggleManualInsets(args) {
const button = args.object as Button;
const page = button.page;
this.overflowManual = !this.overflowManual;
page.off(View.androidOverflowInsetEvent, this.onInset, this);
page.on(View.androidOverflowInsetEvent, this.onInset, this);
this.overflowManual ? (this.padding = '0') : (this.padding = `${this.inset.top}px ${this.inset.right}px ${this.inset.bottom}px ${this.inset.left}px`);
page.androidOverflowEdge = this.overflowManual ? 'dont-apply' : 'none';
}
}
15 changes: 15 additions & 0 deletions apps/toolbox/src/pages/ete.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Page backgroundColor="{{ color }}" xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo" class="page" actionBarHidden="{{ !showActionBar}}">
<Page.actionBar>
<ActionBar title="Edge to Edge" class="action-bar">
</ActionBar>
</Page.actionBar>
<ScrollView>
<StackLayout padding="{{ padding }}" backgroundColor="{{ viewColor }}">
<Button tap="{{togglePageOverflow}}" text="Disable Overflow" />
<Button tap="{{toggleActionBar}}" text="Toggle Action Bar" />
<Button tap="{{randomColor}}" text="Update BG to a random color" />
<Button tap="{{toggleOverflowTopBottom}}" text="Toggle Overflow Top and Bottom" />
<Button tap="{{toggleManualInsets}}" text="Toggle manual overflow handling" />
</StackLayout>
</ScrollView>
</Page>
4 changes: 2 additions & 2 deletions packages/core/core-types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import { CubicBezierAnimationCurve } from './animation-types';
export namespace CoreTypes {
type AndroidOverflowSingle = 'ignore' | 'none' | 'dont-apply';
type AndroidOverflowMultiple = 'left' | 'right' | 'top' | 'bottom' | 'left-dont-consume' | 'top-dont-consume' | 'right-dont-consume' | 'bottom-dont-consume' | 'all-but-left' | 'all-but-top' | 'all-but-right' | 'all-but-bottom';
type AndroidOverflowStacked = AndroidOverflowSingle | `${AndroidOverflowSingle},${AndroidOverflowMultiple}`;
export type AndroidOverflow = AndroidOverflowSingle | AndroidOverflowStacked;
type AndroidOverflowStacked = AndroidOverflowSingle | `${AndroidOverflowSingle},${AndroidOverflowSingle}` | `${AndroidOverflowSingle},${AndroidOverflowMultiple}` | `${AndroidOverflowMultiple},${AndroidOverflowSingle}` | `${AndroidOverflowMultiple},${AndroidOverflowMultiple}`;
export type AndroidOverflow = AndroidOverflowSingle | AndroidOverflowMultiple | AndroidOverflowStacked;
export type CSSWideKeywords = 'initial' | 'inherit' | 'unset' | 'revert';

/**
Expand Down
Binary file modified packages/core/platforms/android/widgets-release.aar
Binary file not shown.
23 changes: 21 additions & 2 deletions packages/core/ui/core/view/index.android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ let OnBackPressedCallback;
if (SDK_VERSION >= 33) {
OnBackPressedCallback = (androidx.activity.OnBackPressedCallback as any).extend({
handleOnBackPressed() {
console.log('OnBackPressedCallback handleOnBackPressed called');
const dialog = this['_dialog']?.get();

if (!dialog) {
Expand Down Expand Up @@ -268,6 +267,8 @@ function initializeDialogFragment() {

const dialog = new DialogImpl(this, this.getActivity(), theme);

Utils.android.enableEdgeToEdge(this.getActivity(), dialog.getWindow());

// do not override alignment unless fullscreen modal will be shown;
// otherwise we might break component-level layout:
// https://github.com/NativeScript/NativeScript/issues/5392
Expand Down Expand Up @@ -382,10 +383,12 @@ const INSET_LEFT = 0;
const INSET_TOP = 4;
const INSET_RIGHT = 8;
const INSET_BOTTOM = 12;
const INSET_BOTTOM_IME = 32;
const INSET_LEFT_CONSUMED = 16;
const INSET_TOP_CONSUMED = 20;
const INSET_RIGHT_CONSUMED = 24;
const INSET_BOTTOM_CONSUMED = 28;
const INSET_BOTTOM_IME_CONSUMED = 36;

const OverflowEdgeIgnore = -1;
const OverflowEdgeNone: number = 0;
Expand Down Expand Up @@ -443,6 +446,14 @@ class Inset {
this.view.setInt32(INSET_BOTTOM, value, true);
}

public get ime(): number {
return this.view.getInt32(INSET_BOTTOM_IME, true);
}

public set ime(value: number) {
this.view.setInt32(INSET_BOTTOM, value, true);
}

public get leftConsumed(): boolean {
return this.view.getInt32(INSET_LEFT_CONSUMED, true) > 0;
}
Expand Down Expand Up @@ -475,6 +486,14 @@ class Inset {
this.view.setInt32(INSET_BOTTOM_CONSUMED, value ? 1 : 0, true);
}

public get imeBottomConsumed(): boolean {
return this.view.getInt32(INSET_BOTTOM_IME_CONSUMED, true) > 0;
}

public set imeBottomConsumed(value: boolean) {
this.view.setInt32(INSET_BOTTOM_IME_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}`;
}
Expand Down Expand Up @@ -564,7 +583,7 @@ export class View extends ViewCommon {
const inset = new Inset(param0);
const args = {
eventName: ViewCommon.androidOverflowInsetEvent,
object: this,
object: owner,
inset,
};
owner.notify(args);
Expand Down
73 changes: 49 additions & 24 deletions packages/core/utils/native-helper-for-android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -378,50 +378,75 @@ export function setDarkModeHandler(options?: { activity?: androidx.appcompat.app
}
}

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 {
export type EdgeToEdgeOptions = {
statusBarLightColor?: Color;
statusBarDarkColor?: Color;
navigationBarLightColor?: Color;
navigationBarDarkColor?: Color;
handleDarkMode?: (bar: 'status' | 'navigation', resources: android.content.res.Resources) => boolean;
};

export type WindowOrOptions = android.view.Window | EdgeToEdgeOptions;

export function enableEdgeToEdge(activity: androidx.appcompat.app.AppCompatActivity, windowOrOptions?: WindowOrOptions, options?: EdgeToEdgeOptions): void {
let handleDarkMode: org.nativescript.widgets.Utils.HandleDarkMode;
let statusBarLight: number = 0;
let statusBarDark: number = 0;
let navigationBarLight: number = DefaultLightScrim.android;
let navigationBarDark: number = DefaultDarkScrim.android;
if (options) {
if (typeof options.handleDarkMode === 'function') {
let opts: EdgeToEdgeOptions = windowOrOptions as never;
let isWindow = false;

if (windowOrOptions instanceof android.view.Window) {
opts = options;
isWindow = true;
}

if (opts) {
if (typeof opts.handleDarkMode === 'function') {
handleDarkMode = new org.nativescript.widgets.Utils.HandleDarkMode({
onHandle(bar, resources) {
if (bar === 0) {
return options.handleDarkMode('status', resources);
return opts.handleDarkMode('status', resources);
} else {
return options.handleDarkMode('navigation', resources);
return opts.handleDarkMode('navigation', resources);
}
},
});
}
if (options.statusBarLightColor instanceof Color) {
statusBarLight = options.statusBarLightColor.android;
if (opts.statusBarLightColor instanceof Color) {
statusBarLight = opts.statusBarLightColor.android;
}
if (options.statusBarDarkColor instanceof Color) {
statusBarDark = options.statusBarDarkColor.android;
if (opts.statusBarDarkColor instanceof Color) {
statusBarDark = opts.statusBarDarkColor.android;
}
if (options.navigationBarLightColor instanceof Color) {
navigationBarLight = options.navigationBarLightColor.android;
if (opts.navigationBarLightColor instanceof Color) {
navigationBarLight = opts.navigationBarLightColor.android;
}
if (options.navigationBarDarkColor instanceof Color) {
navigationBarDark = options.navigationBarDarkColor.android;
if (opts.navigationBarDarkColor instanceof Color) {
navigationBarDark = opts.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);
if (isWindow) {
org.nativescript.widgets.Utils.enableEdgeToEdge(activity, windowOrOptions as never, 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), 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));
if (isWindow) {
org.nativescript.widgets.Utils.enableEdgeToEdge(activity, windowOrOptions as never);
} 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));
}
}
}

export function getIgnoreEdgeToEdgeOnOlderDevices(): boolean {
return org.nativescript.widgets.Utils.ignoreEdgeToEdgeOnOlderDevices;
}

export function setIgnoreEdgeToEdgeOnOlderDevices(value: boolean) {
org.nativescript.widgets.Utils.ignoreEdgeToEdgeOnOlderDevices = value;
}
4 changes: 3 additions & 1 deletion packages/core/utils/native-helper.android.ts
Original file line number Diff line number Diff line change
@@ -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, enableEdgeToEdge, setDarkModeHandler, setNavigationBarColor, setStatusBarColor } from './native-helper-for-android';
import { resources, collections, getWindow, getApplication, getCurrentActivity, getApplicationContext, getResources, getPackageName, getInputMethodManager, showSoftInput, dismissSoftInput, enableEdgeToEdge, setDarkModeHandler, setNavigationBarColor, setStatusBarColor, getIgnoreEdgeToEdgeOnOlderDevices, setIgnoreEdgeToEdgeOnOlderDevices } from './native-helper-for-android';
export { dataSerialize, dataDeserialize } from './native-helper-for-android';

export { getWindow } from './native-helper-for-android';
Expand All @@ -22,6 +22,8 @@ export const android = {
setStatusBarColor,
setNavigationBarColor,
setDarkModeHandler,
getIgnoreEdgeToEdgeOnOlderDevices,
setIgnoreEdgeToEdgeOnOlderDevices,
};

/**
Expand Down
29 changes: 28 additions & 1 deletion packages/core/utils/native-helper.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ export const android: {
* @param options Optional configuration for status and navigation bar colors.
*/
enableEdgeToEdge(
activity: androidx.appcompat.app.AppCompatActivity,
activityOrWindow: androidx.activity.ComponentActivity,
options?: {
statusBarLightColor?: Color;
statusBarDarkColor?: Color;
Expand All @@ -137,6 +137,33 @@ export const android: {
handleDarkMode?: (bar: 'status' | 'navigation', resources: android.content.res.Resources) => boolean;
},
): void;

/**
* Enables edge-to-edge navigation for the provided Window.
* @param activity The activity to enable edge-to-edge navigation for.
* @param options Optional configuration for status and navigation bar colors.
*/
enableEdgeToEdge(
activity: androidx.activity.ComponentActivity,
window: android.view.Window,
options?: {
statusBarLightColor?: Color;
statusBarDarkColor?: Color;
navigationBarLightColor?: Color;
navigationBarDarkColor?: Color;
handleDarkMode?: (bar: 'status' | 'navigation', resources: android.content.res.Resources) => boolean;
},
): void;

/**
* Gets whether edge-to-edge is ignored on older devices (API 34 an older).
*/
getIgnoreEdgeToEdgeOnOlderDevices(): boolean;

/**
* Sets whether to ignore edge-to-edge on older devices (API 34 an older).
*/
setIgnoreEdgeToEdgeOnOlderDevices(value: boolean): void;
};

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -764,6 +764,9 @@ declare module org {
public static enableEdgeToEdge(activity: androidx.activity.ComponentActivity, handleDarkMode: org.nativescript.widgets.Utils.HandleDarkMode): void;
public static enableEdgeToEdge(activity: androidx.activity.ComponentActivity, statusBarLight: java.lang.Integer, statusBarDark: java.lang.Integer, navigationBarLight: java.lang.Integer, navigationBarDark: java.lang.Integer): void;
public static enableEdgeToEdge(activity: androidx.activity.ComponentActivity, statusBarLight: java.lang.Integer, statusBarDark: java.lang.Integer, navigationBarLight: java.lang.Integer, navigationBarDark: java.lang.Integer, handleDarkMode: org.nativescript.widgets.Utils.HandleDarkMode): void;
public static enableEdgeToEdge(activity: androidx.activity.ComponentActivity, window: android.view.Window, handleDarkMode: org.nativescript.widgets.Utils.HandleDarkMode): void;
public static enableEdgeToEdge(activity: androidx.activity.ComponentActivity, window: android.view.Window): void;
public static ignoreEdgeToEdgeOnOlderDevices: boolean;
public constructor();
}
export module Utils {
Expand Down
4 changes: 2 additions & 2 deletions packages/ui-mobile-base/android/widgetdemo/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ plugins {

android {
namespace = "org.nativescript.widgetsdemo"
compileSdk = 35
compileSdk = 36

defaultConfig {
applicationId = "org.nativescript.widgetsdemo"
minSdk = 21
targetSdk = 35
targetSdk = 36
versionCode = 1
versionName = "1.0"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
android:theme="@style/Theme.Android">
<activity
android:name=".MainActivity"
android:exported="true">
android:exported="true"
android:windowSoftInputMode="adjustResize"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />

Expand Down
Loading