diff --git a/packages/core/core-types/index.ts b/packages/core/core-types/index.ts index 1bdb85f89..e7e574cf2 100644 --- a/packages/core/core-types/index.ts +++ b/packages/core/core-types/index.ts @@ -7,10 +7,6 @@ import { makeValidator, makeParser } from '../ui/core/properties'; import { CubicBezierAnimationCurve } from '../ui/animation/animation-interfaces'; 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'; /** diff --git a/packages/core/platforms/android/widgets-release.aar b/packages/core/platforms/android/widgets-release.aar index 6349e60a8..b38722892 100644 Binary files a/packages/core/platforms/android/widgets-release.aar and b/packages/core/platforms/android/widgets-release.aar differ diff --git a/packages/core/ui/core/view/index.android.ts b/packages/core/ui/core/view/index.android.ts index 674ee19c9..4d33c12cf 100644 --- a/packages/core/ui/core/view/index.android.ts +++ b/packages/core/ui/core/view/index.android.ts @@ -2,7 +2,7 @@ import type { Point, CustomLayoutView as CustomLayoutViewDefinition, Position } from '.'; import type { GestureTypes, GestureEventData } from '../../gestures'; -import { ViewCommon, isEnabledProperty, originXProperty, originYProperty, isUserInteractionEnabledProperty, testIDProperty, AndroidHelper, androidOverflowEdgeProperty } from './view-common'; +import { ViewCommon, isEnabledProperty, originXProperty, originYProperty, isUserInteractionEnabledProperty, testIDProperty, AndroidHelper } from './view-common'; import { paddingLeftProperty, paddingTopProperty, paddingRightProperty, paddingBottomProperty, Length } from '../../styling/style-properties'; import { layout } from '../../../utils'; import { Trace } from '../../../trace'; @@ -372,21 +372,20 @@ 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; +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; @@ -622,35 +621,134 @@ export class View extends ViewCommon { return manager; } - [androidOverflowEdgeProperty.setNative](value: CoreTypes.AndroidOverflow) { + 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 (typeof value !== 'string' || nativeView === null || nativeView == undefined) { - return; - } - - if (!('setOverflowEdge' in nativeView)) { - return; - } - - switch (value) { - case 'none': + if (nativeView && nativeView.setOverflowEdge) { + if (value === 'none') { nativeView.setOverflowEdge(OverflowEdgeNone); - break; - case 'ignore': - nativeView.setOverflowEdge(OverflowEdgeIgnore); - break; - default: - { - const edge = parseEdges(value); - - if (edge != null) { - nativeView.setOverflowEdge(edge); - } + } else { + const newValue = parseEdges(value); + if (newValue !== null) { + nativeView.setOverflowEdge(newValue); } - break; + } + } 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; @@ -704,6 +802,13 @@ export class View extends ViewCommon { 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() { @@ -1509,43 +1614,113 @@ export class View extends ViewCommon { } } -const edgeMap: Record = { - 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; + 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; + } } - return result === 0 ? null : result; + + if (newValue === -1) { + return null; + } + return newValue; } export class ContainerView extends View { public iosOverflowSafeArea: boolean; - - constructor() { - super(); - this.androidOverflowEdge = 'none'; - } } export class CustomLayoutView extends ContainerView implements CustomLayoutViewDefinition { diff --git a/packages/core/ui/core/view/view-common.ts b/packages/core/ui/core/view/view-common.ts index 9f867019f..88b97d5a5 100644 --- a/packages/core/ui/core/view/view-common.ts +++ b/packages/core/ui/core/view/view-common.ts @@ -987,7 +987,7 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition { public iosOverflowSafeArea: boolean; public iosOverflowSafeAreaEnabled: boolean; public iosIgnoreSafeArea: boolean; - public androidOverflowEdge: CoreTypes.AndroidOverflow; + public androidOverflowEdge: string; get isLayoutValid(): boolean { return this._isLayoutValid; @@ -1315,12 +1315,6 @@ export const iosIgnoreSafeAreaProperty = new InheritedProperty({ }); iosIgnoreSafeAreaProperty.register(ViewCommon); -export const androidOverflowEdgeProperty = new Property({ - name: 'androidOverflowEdge', - defaultValue: 'ignore', -}); -androidOverflowEdgeProperty.register(ViewCommon); - export const visionHoverStyleProperty = new Property({ name: 'visionHoverStyle', valueChanged(view, oldValue, newValue) { diff --git a/packages/core/ui/frame/index.android.ts b/packages/core/ui/frame/index.android.ts index 35fbb5ee6..8be62066d 100644 --- a/packages/core/ui/frame/index.android.ts +++ b/packages/core/ui/frame/index.android.ts @@ -93,7 +93,8 @@ export class Frame extends FrameBase { constructor() { super(); this._android = new AndroidFrame(this); - this.androidOverflowEdge = 'ignore'; + this._defaultOverflowEdge = 1 << 4; + this._defaultOverflowEdgeValue = 'dont-apply'; } public static reloadPage(context?: ModuleContext): void { diff --git a/packages/core/utils/android/index.ts b/packages/core/utils/android/index.ts index 39f61d92e..ddd0ebbe4 100644 --- a/packages/core/utils/android/index.ts +++ b/packages/core/utils/android/index.ts @@ -176,83 +176,56 @@ export function isRealDevice(): boolean { const DefaultLightScrim = new Color(0xe6, 0xff, 0xff, 0xff); const DefaultDarkScrim = new Color(0x80, 0x1b, 0x1b, 0x1b); -const DefaultStatusBarLight = new Color(0); -const DefaultStatusBarDark = new Color(0); -interface ISystemColor { - navigationBarLight: Color; - navigationBarDark: Color; - statusBarLight: Color; - statusBarDark: Color; - handler?: (bar: 'status' | 'navigation', resources: android.content.res.Resources) => boolean; -} -const systemColors = new WeakMap(); - -function setEnableEdgeToEdge(activity: androidx.appcompat.app.AppCompatActivity, existingColors: ISystemColor) { - enableEdgeToEdge(activity, { - statusBarLightColor: existingColors.statusBarLight, - statusBarDarkColor: existingColors.statusBarDark, - navigationBarLightColor: existingColors.navigationBarLight, - navigationBarDarkColor: existingColors.navigationBarDark, - handleDarkMode: existingColors?.handler ?? null, - }); -} - -export function setStatusBarColor(options?: { activity?: androidx.appcompat.app.AppCompatActivity; lightColor?: Color; darkColor?: Color }): void { - const statusBarLightColor = options?.lightColor ?? null; - const statusBarDarkColor = options?.darkColor ?? null; - const activity = options?.activity ?? getCurrentActivity(); +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) { - const existingColors = systemColors.get(activity) ?? { - navigationBarLight: DefaultLightScrim, - navigationBarDark: DefaultDarkScrim, - statusBarLight: DefaultStatusBarLight, - statusBarDark: DefaultStatusBarDark, - }; - existingColors.statusBarLight ??= statusBarLightColor; - existingColors.statusBarDark ??= statusBarDarkColor; - systemColors.set(getCurrentActivity(), existingColors); - - setEnableEdgeToEdge(activity, existingColors); + enableEdgeToEdge(activity, { + statusBarLightColor: lightColor, + statusBarDarkColor: darkColor, + navigationBarLightColor, + navigationBarDarkColor, + handleDarkMode: darkModeHandler, + }); } } -export function setNavigationBarColor(options?: { activity?: androidx.appcompat.app.AppCompatActivity; lightColor?: Color; darkColor?: Color }): void { - const navigationBarLightColor = options?.lightColor ?? null; - const navigationBarDarkColor = options?.darkColor ?? null; - const activity = options?.activity ?? getCurrentActivity(); - if (activity) { - const existingColors = systemColors.get(activity) ?? { - navigationBarLight: DefaultLightScrim, - navigationBarDark: DefaultDarkScrim, - statusBarLight: DefaultStatusBarLight, - statusBarDark: DefaultStatusBarDark, - }; - existingColors.navigationBarLight ??= navigationBarLightColor; - existingColors.navigationBarDark ??= navigationBarDarkColor; - systemColors.set(getCurrentActivity(), existingColors); +let navigationBarDarkColor: Color | null = null; +let navigationBarLightColor: Color | null = null; - setEnableEdgeToEdge(activity, existingColors); +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, + }); } } -export function setDarkModeHandler(options?: { activity?: androidx.appcompat.app.AppCompatActivity; handler: (bar: 'status' | 'navigation', resources: android.content.res.Resources) => boolean }): void { - const darkModeHandler = options?.handler ?? null; - const activity = options?.activity ?? getCurrentActivity(); +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) { - const existingColors = systemColors.get(activity) ?? { - navigationBarLight: DefaultLightScrim, - navigationBarDark: DefaultDarkScrim, - statusBarLight: DefaultStatusBarLight, - statusBarDark: DefaultStatusBarDark, - }; - - existingColors.handler ??= darkModeHandler; - - systemColors.set(getCurrentActivity(), existingColors); - - setEnableEdgeToEdge(activity, existingColors); + enableEdgeToEdge(activity, { + statusBarLightColor, + statusBarDarkColor, + navigationBarLightColor, + navigationBarDarkColor, + handleDarkMode: handler, + }); } } @@ -267,10 +240,21 @@ export function enableEdgeToEdge( }, ): 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; + 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({ diff --git a/packages/ui-mobile-base/android/widgets/src/main/java/org/nativescript/widgets/LayoutBase.java b/packages/ui-mobile-base/android/widgets/src/main/java/org/nativescript/widgets/LayoutBase.java index 6afa11488..d397233da 100644 --- a/packages/ui-mobile-base/android/widgets/src/main/java/org/nativescript/widgets/LayoutBase.java +++ b/packages/ui-mobile-base/android/widgets/src/main/java/org/nativescript/widgets/LayoutBase.java @@ -26,21 +26,20 @@ public abstract class LayoutBase extends ViewGroup { private boolean passThroughParent; boolean applyingEdges; - public static final int OverflowEdgeIgnore = -1; public static final int OverflowEdgeNone = 0; - public static final int OverflowEdgeLeft = 1 << 1; - public static final int OverflowEdgeTop = 1 << 2; - public static final int OverflowEdgeRight = 1 << 3; - public static final int OverflowEdgeBottom = 1 << 4; - public static final int OverflowEdgeDontApply = 1 << 5; - public static final int OverflowEdgeLeftDontConsume = 1 << 6; - public static final int OverflowEdgeTopDontConsume = 1 << 7; - public static final int OverflowEdgeRightDontConsume = 1 << 8; - public static final int OverflowEdgeBottomDontConsume = 1 << 9; - public static final int OverflowEdgeAllButLeft = 1 << 10; - public static final int OverflowEdgeAllButTop = 1 << 11; - public static final int OverflowEdgeAllButRight = 1 << 12; - public static final int OverflowEdgeAllButBottom = 1 << 13; + public static final int OverflowEdgeLeft = 1; + public static final int OverflowEdgeTop = 1 << 1; + public static final int OverflowEdgeRight = 1 << 2; + public static final int OverflowEdgeBottom = 1 << 3; + public static final int OverflowEdgeDontApply = 1 << 4; + public static final int OverflowEdgeLeftDontConsume = 1 << 5; + public static final int OverflowEdgeTopDontConsume = 1 << 6; + public static final int OverflowEdgeRightDontConsume = 1 << 7; + public static final int OverflowEdgeBottomDontConsume = 1 << 8; + public static final int OverflowEdgeAllButLeft = 1 << 9; + public static final int OverflowEdgeAllButTop = 1 << 10; + public static final int OverflowEdgeAllButRight = 1 << 11; + public static final int OverflowEdgeAllButBottom = 1 << 12; public static final class BufferOffset { public static final int INSET_LEFT = 0; @@ -54,6 +53,8 @@ public abstract class LayoutBase extends ViewGroup { public static final int INSET_BOTTOM_CONSUMED = 28; } + ; + int mPaddingLeft = 0; int mPaddingTop = 0; int mPaddingRight = 0; @@ -61,12 +62,11 @@ public abstract class LayoutBase extends ViewGroup { Insets edgeInsets = Insets.NONE; - int overflowEdge = OverflowEdgeIgnore; + int overflowEdge = OverflowEdgeNone; private final ByteBuffer insetBuffer = ByteBuffer.allocateDirect(32); private WindowInsetListener insetListener = null; - private androidx.core.view.OnApplyWindowInsetsListener windowInsetsListener = null; public void setInsetListener(@Nullable WindowInsetListener insetListener) { this.insetListener = insetListener; @@ -98,6 +98,199 @@ public abstract class LayoutBase extends ViewGroup { public LayoutBase(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); insetBuffer.order(ByteOrder.nativeOrder()); + // if incoming inset is empty and previous inset is empty return consumed + // an incoming empty inset is one way to detect a consumed inset e.g multiple views consumed top/bottom + androidx.core.view.OnApplyWindowInsetsListener windowInsetsListener = new androidx.core.view.OnApplyWindowInsetsListener() { + @NonNull + @Override + public WindowInsetsCompat onApplyWindowInsets(@NonNull View v, @NonNull WindowInsetsCompat insets) { + if (insets.isConsumed()) { + return insets; + } + if (v instanceof LayoutBase) { + LayoutBase base = (LayoutBase) v; + + Insets statusBar = insets.getInsets(WindowInsetsCompat.Type.statusBars()); + Insets navBar = insets.getInsets(WindowInsetsCompat.Type.navigationBars()); + Insets ime = insets.getInsets(WindowInsetsCompat.Type.ime()); + + int insetLeft = navBar.left; + int insetRight = navBar.right; + int insetBottom = Math.max(navBar.bottom, ime.bottom); + + insetBuffer.put(EMPTY_INSETS, 0, 32); + insetBuffer.rewind(); + + if (overflowEdge == OverflowEdgeNone) { + base.applyingEdges = true; + v.setPadding(mPaddingLeft + insetLeft, mPaddingTop + statusBar.top, mPaddingRight + insetRight, mPaddingBottom + insetBottom); + edgeInsets = Insets.of(insetLeft, statusBar.top, insetRight, insetBottom); + base.applyingEdges = false; + return WindowInsetsCompat.CONSUMED; + } + + if (base.insetListener != null) { + if (overflowEdge == OverflowEdgeDontApply) { + // if incoming inset is empty and previous inset is empty return consumed + // an incoming empty inset is one way to detect a consumed inset e.g multiple views consumed top/bottom + if (Insets.NONE.equals(statusBar) && Insets.NONE.equals(navBar) && Insets.NONE.equals(ime) && Insets.NONE.equals(edgeInsets)) { + return WindowInsetsCompat.CONSUMED; + } + + IntBuffer insetData = insetBuffer.asIntBuffer(); + + boolean leftPreviouslyConsumed = insetLeft == 0; + boolean topPreviouslyConsumed = statusBar.top == 0; + boolean rightPreviouslyConsumed = insetRight == 0; + boolean bottomPreviouslyConsumed = insetBottom == 0; + + + insetData.put(0, insetLeft).put(1, statusBar.top).put(2, insetRight).put(3, insetBottom).put(4, leftPreviouslyConsumed ? 1 : 0).put(5, topPreviouslyConsumed ? 1 : 0).put(6, rightPreviouslyConsumed ? 1 : 0).put(7, bottomPreviouslyConsumed ? 1 : 0); + + base.insetListener.onApplyWindowInsets(insetBuffer); + + int leftInset = insetData.get(0); + int topInset = insetData.get(1); + int rightInset = insetData.get(2); + int bottomInset = insetData.get(3); + + boolean leftConsumed = insetData.get(4) > 0; + boolean topConsumed = insetData.get(5) > 0; + boolean rightConsumed = insetData.get(6) > 0; + boolean bottomConsumed = insetData.get(7) > 0; + + if (leftConsumed && topConsumed && rightConsumed && bottomConsumed) { + edgeInsets = Insets.of(leftInset, topInset, rightInset, bottomInset); + base.setPadding(leftInset, topInset, rightInset, bottomInset); + return new WindowInsetsCompat.Builder().setInsets(WindowInsetsCompat.Type.systemBars(), Insets.NONE).build(); + } + + base.setPadding(leftPreviouslyConsumed ? 0 : leftInset, topPreviouslyConsumed ? 0 : topInset, rightPreviouslyConsumed ? 0 : rightInset, bottomPreviouslyConsumed ? 0 : bottomInset); + + // restore inset edge if not consumed + + if (!(leftPreviouslyConsumed || leftConsumed)) { + leftInset = insetLeft; + } + + if (!(topPreviouslyConsumed || topConsumed)) { + topInset = statusBar.top; + } + + if (!(rightPreviouslyConsumed || rightConsumed)) { + rightInset = insetRight; + } + + if (!(bottomPreviouslyConsumed || bottomConsumed)) { + bottomInset = insetBottom; + } + + edgeInsets = Insets.of(leftPreviouslyConsumed ? 0 : leftInset, topPreviouslyConsumed ? 0 : topInset, rightPreviouslyConsumed ? 0 : rightInset, bottomPreviouslyConsumed ? 0 : bottomInset); + + return new WindowInsetsCompat.Builder().setInsets(WindowInsetsCompat.Type.systemBars(), Insets.of(leftPreviouslyConsumed || leftConsumed ? 0 : leftInset, topPreviouslyConsumed || topConsumed ? 0 : topInset, rightPreviouslyConsumed || rightConsumed ? 0 : rightInset, bottomPreviouslyConsumed || bottomConsumed ? 0 : bottomInset)).build(); + } + } + + boolean overflowLeftConsume = (overflowEdge & OverflowEdgeLeft) == OverflowEdgeLeft; + boolean overflowTopConsume = (overflowEdge & OverflowEdgeTop) == OverflowEdgeTop; + boolean overflowRightConsume = (overflowEdge & OverflowEdgeRight) == OverflowEdgeRight; + boolean overflowBottomConsume = (overflowEdge & OverflowEdgeBottom) == OverflowEdgeBottom; + + boolean overflowLeft = (overflowEdge & OverflowEdgeLeftDontConsume) == OverflowEdgeLeftDontConsume; + boolean overflowTop = (overflowEdge & OverflowEdgeTopDontConsume) == OverflowEdgeTopDontConsume; + boolean overflowRight = (overflowEdge & OverflowEdgeRightDontConsume) == OverflowEdgeRightDontConsume; + boolean overflowBottom = (overflowEdge & OverflowEdgeBottomDontConsume) == OverflowEdgeBottomDontConsume; + + + boolean overflowAllButLeft = (overflowEdge & OverflowEdgeAllButLeft) == OverflowEdgeAllButLeft; + boolean overflowAllButTop = (overflowEdge & OverflowEdgeAllButTop) == OverflowEdgeAllButTop; + boolean overflowAllButRight = (overflowEdge & OverflowEdgeAllButRight) == OverflowEdgeAllButRight; + boolean overflowAllButBottom = (overflowEdge & OverflowEdgeAllButBottom) == OverflowEdgeAllButBottom; + + + WindowInsetsCompat ret = insets; + base.applyingEdges = true; + int left = 0; + int top = 0; + int right = 0; + int bottom = 0; + + + if (overflowAllButLeft || overflowAllButTop || overflowAllButRight || overflowAllButBottom) { + Insets newInset; + if (overflowAllButLeft) { + left = mPaddingLeft + insetLeft; + edgeInsets = Insets.of(insetLeft, 0, 0, 0); + newInset = Insets.of(0, statusBar.top, insetRight, insetBottom); + } else if (overflowAllButTop) { + top = mPaddingTop + statusBar.top; + edgeInsets = Insets.of(0, statusBar.top, 0, 0); + newInset = Insets.of(insetLeft, 0, insetRight, insetBottom); + } else if (overflowAllButRight) { + right = mPaddingRight + insetRight; + edgeInsets = Insets.of(0, 0, insetRight, 0); + newInset = Insets.of(insetLeft, statusBar.top, 0, insetBottom); + } else { + bottom = mPaddingBottom + insetBottom; + edgeInsets = Insets.of(0, 0, 0, insetBottom); + newInset = Insets.of(insetLeft, statusBar.top, insetRight, 0); + } + + ret = new WindowInsetsCompat.Builder().setInsets(WindowInsetsCompat.Type.systemBars(), newInset).build(); + base.setPadding(left, top, right, bottom); + base.applyingEdges = false; + if (newInset == Insets.NONE) { + return WindowInsetsCompat.CONSUMED; + } + return ret; + } + + if (overflowLeftConsume || overflowLeft) { + top = mPaddingTop + statusBar.top; + right = mPaddingRight + insetRight; + bottom = mPaddingBottom + insetBottom; + edgeInsets = Insets.of(insetLeft, statusBar.top, insetRight, insetBottom); + if (overflowRightConsume) { + ret = WindowInsetsCompat.CONSUMED; + } + } + if (overflowTopConsume || overflowTop) { + left = mPaddingLeft + insetLeft; + right = mPaddingRight + insetRight; + bottom = mPaddingBottom + insetBottom; + edgeInsets = Insets.of(insetLeft, statusBar.top, insetRight, insetBottom); + if (overflowTopConsume) { + ret = WindowInsetsCompat.CONSUMED; + } + } + if (overflowRightConsume || overflowRight) { + left = mPaddingLeft + insetLeft; + top = mPaddingTop + statusBar.top; + bottom = mPaddingBottom + insetBottom; + edgeInsets = Insets.of(insetLeft, statusBar.top, insetRight, insetBottom); + if (overflowRightConsume) { + ret = WindowInsetsCompat.CONSUMED; + } + } + if (overflowBottomConsume || overflowBottom) { + left = mPaddingLeft + insetLeft; + top = mPaddingTop + statusBar.top; + right = mPaddingRight + insetRight; + edgeInsets = Insets.of(insetLeft, statusBar.top, insetRight, insetBottom); + if (overflowBottomConsume) { + ret = WindowInsetsCompat.CONSUMED; + } + } + + base.setPadding(left, top, right, bottom); + + base.applyingEdges = false; + return ret; + } + return insets; + } + }; + ViewCompat.setOnApplyWindowInsetsListener(this, windowInsetsListener); } @Override @@ -107,6 +300,7 @@ public abstract class LayoutBase extends ViewGroup { public LayoutBase(Context context) { super(context); + } public Insets getEdgeInsets() { @@ -200,210 +394,6 @@ public abstract class LayoutBase extends ViewGroup { public void setOverflowEdge(int value) { overflowEdge = value; - - if (value == OverflowEdgeIgnore) { - ViewCompat.setOnApplyWindowInsetsListener(this, null); - } else if (windowInsetsListener == null) { - // if incoming inset is empty and previous inset is empty return consumed - // an incoming empty inset is one way to detect a consumed inset e.g multiple views consumed top/bottom - windowInsetsListener = new androidx.core.view.OnApplyWindowInsetsListener() { - @NonNull - @Override - public WindowInsetsCompat onApplyWindowInsets(@NonNull View v, @NonNull WindowInsetsCompat insets) { - if (insets.isConsumed()) { - return insets; - } - if (v instanceof LayoutBase) { - LayoutBase base = (LayoutBase) v; - - // should not occur but if it does return the inset - if (overflowEdge == OverflowEdgeIgnore) { - return insets; - } - - Insets statusBar = insets.getInsets(WindowInsetsCompat.Type.statusBars()); - Insets navBar = insets.getInsets(WindowInsetsCompat.Type.navigationBars()); - Insets ime = insets.getInsets(WindowInsetsCompat.Type.ime()); - - int insetLeft = navBar.left; - int insetRight = navBar.right; - int insetBottom = Math.max(navBar.bottom, ime.bottom); - - insetBuffer.put(EMPTY_INSETS, 0, 32); - insetBuffer.rewind(); - - if (overflowEdge == OverflowEdgeNone) { - base.applyingEdges = true; - v.setPadding(mPaddingLeft + insetLeft, mPaddingTop + statusBar.top, mPaddingRight + insetRight, mPaddingBottom + insetBottom); - edgeInsets = Insets.of(insetLeft, statusBar.top, insetRight, insetBottom); - base.applyingEdges = false; - return WindowInsetsCompat.CONSUMED; - } - - if (base.insetListener != null) { - if (overflowEdge == OverflowEdgeDontApply) { - // if incoming inset is empty and previous inset is empty return consumed - // an incoming empty inset is one way to detect a consumed inset e.g multiple views consumed top/bottom - if (Insets.NONE.equals(statusBar) && Insets.NONE.equals(navBar) && Insets.NONE.equals(ime) && Insets.NONE.equals(edgeInsets)) { - return WindowInsetsCompat.CONSUMED; - } - - IntBuffer insetData = insetBuffer.asIntBuffer(); - - boolean leftPreviouslyConsumed = insetLeft == 0; - boolean topPreviouslyConsumed = statusBar.top == 0; - boolean rightPreviouslyConsumed = insetRight == 0; - boolean bottomPreviouslyConsumed = insetBottom == 0; - - - insetData.put(0, insetLeft).put(1, statusBar.top).put(2, insetRight).put(3, insetBottom).put(4, leftPreviouslyConsumed ? 1 : 0).put(5, topPreviouslyConsumed ? 1 : 0).put(6, rightPreviouslyConsumed ? 1 : 0).put(7, bottomPreviouslyConsumed ? 1 : 0); - - base.insetListener.onApplyWindowInsets(insetBuffer); - - int leftInset = insetData.get(0); - int topInset = insetData.get(1); - int rightInset = insetData.get(2); - int bottomInset = insetData.get(3); - - boolean leftConsumed = insetData.get(4) > 0; - boolean topConsumed = insetData.get(5) > 0; - boolean rightConsumed = insetData.get(6) > 0; - boolean bottomConsumed = insetData.get(7) > 0; - - if (leftConsumed && topConsumed && rightConsumed && bottomConsumed) { - edgeInsets = Insets.of(leftInset, topInset, rightInset, bottomInset); - base.setPadding(leftInset, topInset, rightInset, bottomInset); - return new WindowInsetsCompat.Builder().setInsets(WindowInsetsCompat.Type.systemBars(), Insets.NONE).build(); - } - - base.setPadding(leftPreviouslyConsumed ? 0 : leftInset, topPreviouslyConsumed ? 0 : topInset, rightPreviouslyConsumed ? 0 : rightInset, bottomPreviouslyConsumed ? 0 : bottomInset); - - // restore inset edge if not consumed - - if (!(leftPreviouslyConsumed || leftConsumed)) { - leftInset = insetLeft; - } - - if (!(topPreviouslyConsumed || topConsumed)) { - topInset = statusBar.top; - } - - if (!(rightPreviouslyConsumed || rightConsumed)) { - rightInset = insetRight; - } - - if (!(bottomPreviouslyConsumed || bottomConsumed)) { - bottomInset = insetBottom; - } - - edgeInsets = Insets.of(leftPreviouslyConsumed ? 0 : leftInset, topPreviouslyConsumed ? 0 : topInset, rightPreviouslyConsumed ? 0 : rightInset, bottomPreviouslyConsumed ? 0 : bottomInset); - - return new WindowInsetsCompat.Builder().setInsets(WindowInsetsCompat.Type.systemBars(), Insets.of(leftPreviouslyConsumed || leftConsumed ? 0 : leftInset, topPreviouslyConsumed || topConsumed ? 0 : topInset, rightPreviouslyConsumed || rightConsumed ? 0 : rightInset, bottomPreviouslyConsumed || bottomConsumed ? 0 : bottomInset)).build(); - } - } - - boolean overflowLeftConsume = (overflowEdge & OverflowEdgeLeft) == OverflowEdgeLeft; - boolean overflowTopConsume = (overflowEdge & OverflowEdgeTop) == OverflowEdgeTop; - boolean overflowRightConsume = (overflowEdge & OverflowEdgeRight) == OverflowEdgeRight; - boolean overflowBottomConsume = (overflowEdge & OverflowEdgeBottom) == OverflowEdgeBottom; - - boolean overflowLeft = (overflowEdge & OverflowEdgeLeftDontConsume) == OverflowEdgeLeftDontConsume; - boolean overflowTop = (overflowEdge & OverflowEdgeTopDontConsume) == OverflowEdgeTopDontConsume; - boolean overflowRight = (overflowEdge & OverflowEdgeRightDontConsume) == OverflowEdgeRightDontConsume; - boolean overflowBottom = (overflowEdge & OverflowEdgeBottomDontConsume) == OverflowEdgeBottomDontConsume; - - - boolean overflowAllButLeft = (overflowEdge & OverflowEdgeAllButLeft) == OverflowEdgeAllButLeft; - boolean overflowAllButTop = (overflowEdge & OverflowEdgeAllButTop) == OverflowEdgeAllButTop; - boolean overflowAllButRight = (overflowEdge & OverflowEdgeAllButRight) == OverflowEdgeAllButRight; - boolean overflowAllButBottom = (overflowEdge & OverflowEdgeAllButBottom) == OverflowEdgeAllButBottom; - - - WindowInsetsCompat ret = insets; - base.applyingEdges = true; - int left = 0; - int top = 0; - int right = 0; - int bottom = 0; - - - if (overflowAllButLeft || overflowAllButTop || overflowAllButRight || overflowAllButBottom) { - Insets newInset; - if (overflowAllButLeft) { - left = mPaddingLeft + insetLeft; - edgeInsets = Insets.of(insetLeft, 0, 0, 0); - newInset = Insets.of(0, statusBar.top, insetRight, insetBottom); - } else if (overflowAllButTop) { - top = mPaddingTop + statusBar.top; - edgeInsets = Insets.of(0, statusBar.top, 0, 0); - newInset = Insets.of(insetLeft, 0, insetRight, insetBottom); - } else if (overflowAllButRight) { - right = mPaddingRight + insetRight; - edgeInsets = Insets.of(0, 0, insetRight, 0); - newInset = Insets.of(insetLeft, statusBar.top, 0, insetBottom); - } else { - bottom = mPaddingBottom + insetBottom; - edgeInsets = Insets.of(0, 0, 0, insetBottom); - newInset = Insets.of(insetLeft, statusBar.top, insetRight, 0); - } - - ret = new WindowInsetsCompat.Builder().setInsets(WindowInsetsCompat.Type.systemBars(), newInset).build(); - base.setPadding(left, top, right, bottom); - base.applyingEdges = false; - if (newInset == Insets.NONE) { - return WindowInsetsCompat.CONSUMED; - } - return ret; - } - - if (overflowLeftConsume || overflowLeft) { - top = mPaddingTop + statusBar.top; - right = mPaddingRight + insetRight; - bottom = mPaddingBottom + insetBottom; - edgeInsets = Insets.of(insetLeft, statusBar.top, insetRight, insetBottom); - if (overflowRightConsume) { - ret = WindowInsetsCompat.CONSUMED; - } - } - if (overflowTopConsume || overflowTop) { - left = mPaddingLeft + insetLeft; - right = mPaddingRight + insetRight; - bottom = mPaddingBottom + insetBottom; - edgeInsets = Insets.of(insetLeft, statusBar.top, insetRight, insetBottom); - if (overflowTopConsume) { - ret = WindowInsetsCompat.CONSUMED; - } - } - if (overflowRightConsume || overflowRight) { - left = mPaddingLeft + insetLeft; - top = mPaddingTop + statusBar.top; - bottom = mPaddingBottom + insetBottom; - edgeInsets = Insets.of(insetLeft, statusBar.top, insetRight, insetBottom); - if (overflowRightConsume) { - ret = WindowInsetsCompat.CONSUMED; - } - } - if (overflowBottomConsume || overflowBottom) { - left = mPaddingLeft + insetLeft; - top = mPaddingTop + statusBar.top; - right = mPaddingRight + insetRight; - edgeInsets = Insets.of(insetLeft, statusBar.top, insetRight, insetBottom); - if (overflowBottomConsume) { - ret = WindowInsetsCompat.CONSUMED; - } - } - - base.setPadding(left, top, right, bottom); - - base.applyingEdges = false; - return ret; - } - return insets; - } - }; - ViewCompat.setOnApplyWindowInsetsListener(this, windowInsetsListener); - } - if (pendingInsetApply) { return; }