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" 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" iosLargeTitle="true" iosShadow="false">
</ActionBar>
Expand Down Expand Up @@ -34,3 +34,4 @@
</ScrollView>
</StackLayout>
</Page>

14 changes: 13 additions & 1 deletion apps/toolbox/src/main-view-model.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Observable, Frame, StackLayout } from '@nativescript/core';
import { Observable, Frame, StackLayout, AndroidOverflowInsetData } from '@nativescript/core';

export class HelloWorldModel extends Observable {
viewDemo(args) {
Expand All @@ -7,4 +7,16 @@ export class HelloWorldModel extends Observable {
moduleName: `pages/${args.object.text}`,
});
}

onInset(args: AndroidOverflowInsetData) {
args.inset.top += 10; // add 10px to the top inset
args.inset.bottom += 10; // add 10px to the bottom inset
args.inset.left += 10; // add 10px to the left inset
args.inset.right += 10; // add 10px to the right inset

args.inset.topConsumed = true; // consume the top inset
args.inset.bottomConsumed = true; // consume the bottom inset
args.inset.leftConsumed = true; // consume the left inset
args.inset.rightConsumed = true; // consume the right inset
}
}
2 changes: 1 addition & 1 deletion apps/toolbox/src/pages/list-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" actionBarHidden="false">
<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo" actionBarHidden="false" androidOverflowEdge="bottom">
<Page.actionBar>
<ActionBar>
<Label text="Components" class="header"/>
Expand Down
4 changes: 4 additions & 0 deletions packages/core/core-types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import { makeValidator, makeParser } from './validators';
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;
export type CSSWideKeywords = 'initial' | 'inherit' | 'unset' | 'revert';

/**
Expand Down
Binary file modified packages/core/platforms/android/widgets-release.aar
Binary file not shown.
10 changes: 5 additions & 5 deletions packages/core/ui/animation/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import type { AnimationDefinition, AnimationPromise } from './animation-types';
* Defines a animation set.
*/
export class Animation {
constructor(animationDefinitions: Array<AnimationDefinition>, playSequentially?: boolean);
public play: (resetOnFinish?: boolean) => AnimationPromise;
public cancel: () => void;
public isPlaying: boolean;
public _resolveAnimationCurve(curve: any): any;
constructor(animationDefinitions: Array<AnimationDefinition>, playSequentially?: boolean);
public play: (resetOnFinish?: boolean) => AnimationPromise;
public cancel: () => void;
public isPlaying: boolean;
public _resolveAnimationCurve(curve: any): any;
}

export function _resolveAnimationCurve(curve: any): any;
230 changes: 229 additions & 1 deletion packages/core/ui/core/view/index.android.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Point, Position } from './view-interfaces';
import type { GestureTypes, GestureEventData } from '../../gestures';
import { getNativeScriptGlobals } from '../../../globals/global-utils';
import { ViewCommon, isEnabledProperty, originXProperty, originYProperty, isUserInteractionEnabledProperty, testIDProperty, AndroidHelper, statusBarStyleProperty } from './view-common';
import { ViewCommon, isEnabledProperty, originXProperty, originYProperty, isUserInteractionEnabledProperty, testIDProperty, AndroidHelper, androidOverflowEdgeProperty, statusBarStyleProperty } from './view-common';
import { paddingLeftProperty, paddingTopProperty, paddingRightProperty, paddingBottomProperty } from '../../styling/style-properties';
import { Length } from '../../styling/length-shared';
import { layout } from '../../../utils';
Expand Down Expand Up @@ -320,6 +320,121 @@ 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 OverflowEdgeIgnore = -1;
const OverflowEdgeNone: number = 0;
const OverflowEdgeLeft: number = 1 << 1;
const OverflowEdgeTop: number = 1 << 2;
const OverflowEdgeRight: number = 1 << 3;
const OverflowEdgeBottom: number = 1 << 4;
const OverflowEdgeDontApply: number = 1 << 5;
const OverflowEdgeLeftDontConsume: number = 1 << 6;
const OverflowEdgeTopDontConsume: number = 1 << 7;
const OverflowEdgeRightDontConsume: number = 1 << 8;
const OverflowEdgeBottomDontConsume: number = 1 << 9;
const OverflowEdgeAllButLeft: number = 1 << 10;
const OverflowEdgeAllButTop: number = 1 << 11;
const OverflowEdgeAllButRight: number = 1 << 12;
const OverflowEdgeAllButBottom: number = 1 << 13;

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;

Expand All @@ -330,6 +445,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;

Expand All @@ -348,6 +465,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) {
Expand All @@ -359,6 +482,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 {
Expand Down Expand Up @@ -419,6 +579,35 @@ export class View extends ViewCommon {
return manager;
}

[androidOverflowEdgeProperty.setNative](value: CoreTypes.AndroidOverflow) {
const nativeView = this.nativeViewProtected as any;
if (typeof value !== 'string' || nativeView === null || nativeView == undefined) {
return;
}

if (!('setOverflowEdge' in nativeView)) {
return;
}

switch (value) {
case 'none':
nativeView.setOverflowEdge(OverflowEdgeNone);
break;
case 'ignore':
nativeView.setOverflowEdge(OverflowEdgeIgnore);
break;
default:
{
const edge = parseEdges(value);

if (edge != null) {
nativeView.setOverflowEdge(edge);
}
}
break;
}
}

@profile
public onLoaded() {
this._manager = null;
Expand Down Expand Up @@ -468,6 +657,10 @@ export class View extends ViewCommon {
if (this.needsOnLayoutChangeListener()) {
this.setOnLayoutChangeListener();
}

if (!this.insetListenerIsSet && this.needsInsetListener) {
this.setInsetListener();
}
}

public needsOnLayoutChangeListener() {
Expand Down Expand Up @@ -1356,8 +1549,43 @@ export class View extends ViewCommon {
}
}

const edgeMap: Record<string, number> = {
none: OverflowEdgeNone,
left: OverflowEdgeLeft,
top: OverflowEdgeTop,
right: OverflowEdgeRight,
bottom: OverflowEdgeBottom,
'dont-apply': OverflowEdgeDontApply,
'left-dont-consume': OverflowEdgeLeftDontConsume,
'top-dont-consume': OverflowEdgeTopDontConsume,
'right-dont-consume': OverflowEdgeRightDontConsume,
'bottom-dont-consume': OverflowEdgeBottomDontConsume,
'all-but-left': OverflowEdgeAllButLeft,
'all-but-top': OverflowEdgeAllButTop,
'all-but-right': OverflowEdgeAllButRight,
'all-but-bottom': OverflowEdgeAllButBottom,
};

function parseEdges(edges: string): number | null {
let result = 0;
const values = edges.split(',');
for (const raw of values) {
const value = edgeMap[raw.trim()];
if (value === undefined) continue;
// dont-apply overrides everything else
if (value === OverflowEdgeDontApply) return value;
result |= value;
}
return result === 0 ? null : result;
}

export class ContainerView extends View {
public iosOverflowSafeArea: boolean;

constructor() {
super();
this.androidOverflowEdge = 'none';
}
}

export class CustomLayoutView extends ContainerView {
Expand Down
12 changes: 12 additions & 0 deletions packages/core/ui/core/view/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -802,6 +809,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.
*/
Expand Down
Loading
Loading