Skip to content

Commit 248ff4b

Browse files
authored
feat(android): edge to edge (#10774)
1 parent 55d111c commit 248ff4b

File tree

51 files changed

+1736
-255
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1736
-255
lines changed

apps/toolbox/src/main-page.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo" class="page" statusBarStyle="dark">
1+
<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo" class="page" androidOverflowEdge="bottom" statusBarStyle="dark">
22
<Page.actionBar>
33
<ActionBar title="Dev Toolbox" icon="" class="action-bar" iosLargeTitle="true" iosShadow="false">
44
</ActionBar>
@@ -34,3 +34,4 @@
3434
</ScrollView>
3535
</StackLayout>
3636
</Page>
37+
Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Observable, Frame, StackLayout } from '@nativescript/core';
1+
import { Observable, Frame, StackLayout, AndroidOverflowInsetData } from '@nativescript/core';
22

33
export class HelloWorldModel extends Observable {
44
viewDemo(args) {
@@ -7,4 +7,16 @@ export class HelloWorldModel extends Observable {
77
moduleName: `pages/${args.object.text}`,
88
});
99
}
10+
11+
onInset(args: AndroidOverflowInsetData) {
12+
args.inset.top += 10; // add 10px to the top inset
13+
args.inset.bottom += 10; // add 10px to the bottom inset
14+
args.inset.left += 10; // add 10px to the left inset
15+
args.inset.right += 10; // add 10px to the right inset
16+
17+
args.inset.topConsumed = true; // consume the top inset
18+
args.inset.bottomConsumed = true; // consume the bottom inset
19+
args.inset.leftConsumed = true; // consume the left inset
20+
args.inset.rightConsumed = true; // consume the right inset
21+
}
1022
}

apps/toolbox/src/pages/list-page.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo" actionBarHidden="false">
1+
<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo" actionBarHidden="false" androidOverflowEdge="bottom">
22
<Page.actionBar>
33
<ActionBar>
44
<Label text="Components" class="header"/>

packages/core/core-types/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ import { makeValidator, makeParser } from './validators';
77
import { CubicBezierAnimationCurve } from './animation-types';
88

99
export namespace CoreTypes {
10+
type AndroidOverflowSingle = 'ignore' | 'none' | 'dont-apply';
11+
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';
12+
type AndroidOverflowStacked = AndroidOverflowSingle | `${AndroidOverflowSingle},${AndroidOverflowMultiple}`;
13+
export type AndroidOverflow = AndroidOverflowSingle | AndroidOverflowStacked;
1014
export type CSSWideKeywords = 'initial' | 'inherit' | 'unset' | 'revert';
1115

1216
/**
7.4 KB
Binary file not shown.

packages/core/ui/animation/index.d.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ import type { AnimationDefinition, AnimationPromise } from './animation-types';
66
* Defines a animation set.
77
*/
88
export class Animation {
9-
constructor(animationDefinitions: Array<AnimationDefinition>, playSequentially?: boolean);
10-
public play: (resetOnFinish?: boolean) => AnimationPromise;
11-
public cancel: () => void;
12-
public isPlaying: boolean;
13-
public _resolveAnimationCurve(curve: any): any;
9+
constructor(animationDefinitions: Array<AnimationDefinition>, playSequentially?: boolean);
10+
public play: (resetOnFinish?: boolean) => AnimationPromise;
11+
public cancel: () => void;
12+
public isPlaying: boolean;
13+
public _resolveAnimationCurve(curve: any): any;
1414
}
1515

1616
export function _resolveAnimationCurve(curve: any): any;

packages/core/ui/core/view/index.android.ts

Lines changed: 229 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { Point, Position } from './view-interfaces';
22
import type { GestureTypes, GestureEventData } from '../../gestures';
33
import { getNativeScriptGlobals } from '../../../globals/global-utils';
4-
import { ViewCommon, isEnabledProperty, originXProperty, originYProperty, isUserInteractionEnabledProperty, testIDProperty, AndroidHelper, statusBarStyleProperty } from './view-common';
4+
import { ViewCommon, isEnabledProperty, originXProperty, originYProperty, isUserInteractionEnabledProperty, testIDProperty, AndroidHelper, androidOverflowEdgeProperty, statusBarStyleProperty } from './view-common';
55
import { paddingLeftProperty, paddingTopProperty, paddingRightProperty, paddingBottomProperty } from '../../styling/style-properties';
66
import { Length } from '../../styling/length-shared';
77
import { layout } from '../../../utils';
@@ -320,6 +320,121 @@ function getModalOptions(domId: number): DialogOptions {
320320
return modalMap.get(domId);
321321
}
322322

323+
const INSET_LEFT = 0;
324+
const INSET_TOP = 4;
325+
const INSET_RIGHT = 8;
326+
const INSET_BOTTOM = 12;
327+
const INSET_LEFT_CONSUMED = 16;
328+
const INSET_TOP_CONSUMED = 20;
329+
const INSET_RIGHT_CONSUMED = 24;
330+
const INSET_BOTTOM_CONSUMED = 28;
331+
332+
const OverflowEdgeIgnore = -1;
333+
const OverflowEdgeNone: number = 0;
334+
const OverflowEdgeLeft: number = 1 << 1;
335+
const OverflowEdgeTop: number = 1 << 2;
336+
const OverflowEdgeRight: number = 1 << 3;
337+
const OverflowEdgeBottom: number = 1 << 4;
338+
const OverflowEdgeDontApply: number = 1 << 5;
339+
const OverflowEdgeLeftDontConsume: number = 1 << 6;
340+
const OverflowEdgeTopDontConsume: number = 1 << 7;
341+
const OverflowEdgeRightDontConsume: number = 1 << 8;
342+
const OverflowEdgeBottomDontConsume: number = 1 << 9;
343+
const OverflowEdgeAllButLeft: number = 1 << 10;
344+
const OverflowEdgeAllButTop: number = 1 << 11;
345+
const OverflowEdgeAllButRight: number = 1 << 12;
346+
const OverflowEdgeAllButBottom: number = 1 << 13;
347+
348+
class Inset {
349+
private view: DataView;
350+
private data: ArrayBuffer;
351+
constructor(data: java.nio.ByteBuffer) {
352+
this.data = (<any>ArrayBuffer).from(data);
353+
this.view = new DataView(this.data);
354+
}
355+
356+
public get left(): number {
357+
return this.view.getInt32(INSET_LEFT, true);
358+
}
359+
360+
public set left(value: number) {
361+
this.view.setInt32(INSET_LEFT, value, true);
362+
}
363+
364+
public get top(): number {
365+
return this.view.getInt32(INSET_TOP, true);
366+
}
367+
368+
public set top(value: number) {
369+
this.view.setInt32(INSET_TOP, value, true);
370+
}
371+
372+
public get right(): number {
373+
return this.view.getInt32(INSET_RIGHT, true);
374+
}
375+
376+
public set right(value: number) {
377+
this.view.setInt32(INSET_RIGHT, value, true);
378+
}
379+
380+
public get bottom(): number {
381+
return this.view.getInt32(INSET_BOTTOM, true);
382+
}
383+
384+
public set bottom(value: number) {
385+
this.view.setInt32(INSET_BOTTOM, value, true);
386+
}
387+
388+
public get leftConsumed(): boolean {
389+
return this.view.getInt32(INSET_LEFT_CONSUMED, true) > 0;
390+
}
391+
392+
public set leftConsumed(value: boolean) {
393+
this.view.setInt32(INSET_LEFT_CONSUMED, value ? 1 : 0, true);
394+
}
395+
396+
public get topConsumed(): boolean {
397+
return this.view.getInt32(INSET_TOP_CONSUMED, true) > 0;
398+
}
399+
400+
public set topConsumed(value: boolean) {
401+
this.view.setInt32(INSET_TOP_CONSUMED, value ? 1 : 0, true);
402+
}
403+
404+
public get rightConsumed(): boolean {
405+
return this.view.getInt32(INSET_RIGHT_CONSUMED, true) > 0;
406+
}
407+
408+
public set rightConsumed(value: boolean) {
409+
this.view.setInt32(INSET_RIGHT_CONSUMED, value ? 1 : 0, true);
410+
}
411+
412+
public get bottomConsumed(): boolean {
413+
return this.view.getInt32(INSET_BOTTOM_CONSUMED, true) > 0;
414+
}
415+
416+
public set bottomConsumed(value: boolean) {
417+
this.view.setInt32(INSET_BOTTOM_CONSUMED, value ? 1 : 0, true);
418+
}
419+
420+
toString() {
421+
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}`;
422+
}
423+
424+
toJSON() {
425+
return {
426+
left: this.left,
427+
top: this.top,
428+
right: this.right,
429+
bottom: this.bottom,
430+
leftConsumed: this.leftConsumed,
431+
topConsumed: this.topConsumed,
432+
rightConsumed: this.rightConsumed,
433+
bottomConsumed: this.bottomConsumed,
434+
};
435+
}
436+
}
437+
323438
export class View extends ViewCommon {
324439
public static androidBackPressedEvent = androidBackPressedEvent;
325440

@@ -330,6 +445,8 @@ export class View extends ViewCommon {
330445
private layoutChangeListenerIsSet: boolean;
331446
private layoutChangeListener: android.view.View.OnLayoutChangeListener;
332447
private _rootManager: androidx.fragment.app.FragmentManager;
448+
private insetListenerIsSet: boolean;
449+
private needsInsetListener: boolean;
333450

334451
nativeViewProtected: android.view.View;
335452

@@ -348,6 +465,12 @@ export class View extends ViewCommon {
348465
if (this.isLoaded && !this.layoutChangeListenerIsSet && isLayoutEvent) {
349466
this.setOnLayoutChangeListener();
350467
}
468+
469+
const isInsetEvent = typeof eventNames === 'string' ? eventNames.indexOf(ViewCommon.androidOverflowInsetEvent) !== -1 : false;
470+
// only avaiable on LayoutBase
471+
if (!this.insetListenerIsSet && isInsetEvent) {
472+
this.setInsetListener();
473+
}
351474
}
352475

353476
removeEventListener(eventNames: string, callback?: (data: EventData) => void, thisArg?: any) {
@@ -359,6 +482,43 @@ export class View extends ViewCommon {
359482
this.nativeViewProtected.removeOnLayoutChangeListener(this.layoutChangeListener);
360483
this.layoutChangeListenerIsSet = false;
361484
}
485+
486+
const isInsetEvent = typeof eventNames === 'string' ? eventNames.indexOf(ViewCommon.androidOverflowInsetEvent) !== -1 : false;
487+
488+
if (this.insetListenerIsSet && isInsetEvent && this.nativeViewProtected && (this.nativeViewProtected as any).setInsetListener) {
489+
(this.nativeViewProtected as any).setInsetListener(null);
490+
this.insetListenerIsSet = false;
491+
}
492+
}
493+
494+
private setInsetListener() {
495+
if (this.nativeViewProtected) {
496+
if ((this.nativeViewProtected as any).setInsetListener) {
497+
const ref = new WeakRef(this);
498+
(this.nativeViewProtected as any).setInsetListener(
499+
new org.nativescript.widgets.LayoutBase.WindowInsetListener({
500+
onApplyWindowInsets(param0) {
501+
const owner = ref.get();
502+
if (!owner) {
503+
return;
504+
}
505+
506+
const inset = new Inset(param0);
507+
const args = {
508+
eventName: ViewCommon.androidOverflowInsetEvent,
509+
object: this,
510+
inset,
511+
};
512+
owner.notify(args);
513+
},
514+
}),
515+
);
516+
this.insetListenerIsSet = true;
517+
}
518+
this.needsInsetListener = false;
519+
} else {
520+
this.needsInsetListener = true;
521+
}
362522
}
363523

364524
public _getChildFragmentManager(): androidx.fragment.app.FragmentManager {
@@ -419,6 +579,35 @@ export class View extends ViewCommon {
419579
return manager;
420580
}
421581

582+
[androidOverflowEdgeProperty.setNative](value: CoreTypes.AndroidOverflow) {
583+
const nativeView = this.nativeViewProtected as any;
584+
if (typeof value !== 'string' || nativeView === null || nativeView == undefined) {
585+
return;
586+
}
587+
588+
if (!('setOverflowEdge' in nativeView)) {
589+
return;
590+
}
591+
592+
switch (value) {
593+
case 'none':
594+
nativeView.setOverflowEdge(OverflowEdgeNone);
595+
break;
596+
case 'ignore':
597+
nativeView.setOverflowEdge(OverflowEdgeIgnore);
598+
break;
599+
default:
600+
{
601+
const edge = parseEdges(value);
602+
603+
if (edge != null) {
604+
nativeView.setOverflowEdge(edge);
605+
}
606+
}
607+
break;
608+
}
609+
}
610+
422611
@profile
423612
public onLoaded() {
424613
this._manager = null;
@@ -468,6 +657,10 @@ export class View extends ViewCommon {
468657
if (this.needsOnLayoutChangeListener()) {
469658
this.setOnLayoutChangeListener();
470659
}
660+
661+
if (!this.insetListenerIsSet && this.needsInsetListener) {
662+
this.setInsetListener();
663+
}
471664
}
472665

473666
public needsOnLayoutChangeListener() {
@@ -1356,8 +1549,43 @@ export class View extends ViewCommon {
13561549
}
13571550
}
13581551

1552+
const edgeMap: Record<string, number> = {
1553+
none: OverflowEdgeNone,
1554+
left: OverflowEdgeLeft,
1555+
top: OverflowEdgeTop,
1556+
right: OverflowEdgeRight,
1557+
bottom: OverflowEdgeBottom,
1558+
'dont-apply': OverflowEdgeDontApply,
1559+
'left-dont-consume': OverflowEdgeLeftDontConsume,
1560+
'top-dont-consume': OverflowEdgeTopDontConsume,
1561+
'right-dont-consume': OverflowEdgeRightDontConsume,
1562+
'bottom-dont-consume': OverflowEdgeBottomDontConsume,
1563+
'all-but-left': OverflowEdgeAllButLeft,
1564+
'all-but-top': OverflowEdgeAllButTop,
1565+
'all-but-right': OverflowEdgeAllButRight,
1566+
'all-but-bottom': OverflowEdgeAllButBottom,
1567+
};
1568+
1569+
function parseEdges(edges: string): number | null {
1570+
let result = 0;
1571+
const values = edges.split(',');
1572+
for (const raw of values) {
1573+
const value = edgeMap[raw.trim()];
1574+
if (value === undefined) continue;
1575+
// dont-apply overrides everything else
1576+
if (value === OverflowEdgeDontApply) return value;
1577+
result |= value;
1578+
}
1579+
return result === 0 ? null : result;
1580+
}
1581+
13591582
export class ContainerView extends View {
13601583
public iosOverflowSafeArea: boolean;
1584+
1585+
constructor() {
1586+
super();
1587+
this.androidOverflowEdge = 'none';
1588+
}
13611589
}
13621590

13631591
export class CustomLayoutView extends ContainerView {

packages/core/ui/core/view/index.d.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,13 @@ export abstract class View extends ViewCommon {
9292
*/
9393
public static accessibilityFocusChangedEvent: string;
9494

95+
/**
96+
* String value used when hooking to androidOverflowInset event.
97+
*
98+
* @nsEvent {EventDataValue} androidOverflowInset
99+
*/
100+
public static androidOverflowInsetEvent: string;
101+
95102
/**
96103
* Gets the android-specific native instance that lies behind this proxy. Will be available if running on an Android platform.
97104
*/
@@ -802,6 +809,11 @@ export abstract class View extends ViewCommon {
802809
*/
803810
on(event: 'shownModally', callback: (args: ShownModallyData) => void, thisArg?: any);
804811

812+
/**
813+
* Raised after the view is shown as a modal dialog.
814+
*/
815+
on(event: 'androidOverflowInset', callback: (args: ShownModallyData) => void, thisArg?: any);
816+
805817
/**
806818
* Returns the current modal view that this page is showing (is parent of), if any.
807819
*/

0 commit comments

Comments
 (0)