Skip to content

Commit

Permalink
Add layout.insets option (#7416)
Browse files Browse the repository at this point in the history
This feature allows adding insets to the topmost view controller in the hierarchy, it is useful for presenting a banner across the app with overlay without having to re-render it each time a new screen is being pushed.

Co-authored-by: Ward Abbass <swabbass@gmail.com>
  • Loading branch information
yogevbd and swabbass authored Jan 4, 2022
1 parent 403fcf1 commit 9bb70d8
Show file tree
Hide file tree
Showing 33 changed files with 490 additions and 81 deletions.
22 changes: 22 additions & 0 deletions e2e/AndroidUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,28 @@ const utils = {
executeShellCommand: (command) => {
exec.execSync(`adb -s ${device.id} shell ${command}`);
},
setDemoMode: () => {
// enter demo mode
utils.executeShellCommand('settings put global sysui_demo_allowed 1');
// display time 12:00
utils.executeShellCommand(
'am broadcast -a com.android.systemui.demo -e command clock -e hhmm 1200'
);
// Display full mobile data with 4g type and no wifi
utils.executeShellCommand(
'am broadcast -a com.android.systemui.demo -e command network -e mobile show -e level 4 -e datatype 4g -e wifi false'
);
// Hide notifications
utils.executeShellCommand(
'am broadcast -a com.android.systemui.demo -e command notifications -e visible false'
);
// Disable pointer location
utils.executeShellCommand('settings put system pointer_location 0');
// Show full battery but not in charging state
utils.executeShellCommand(
'am broadcast -a com.android.systemui.demo -e command battery -e plugged false -e level 100'
);
},
};

export default utils;
24 changes: 23 additions & 1 deletion e2e/Overlay.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import Utils from './Utils';
import TestIDs from '../playground/src/testIDs';
import Android from './AndroidUtils';

const { elementByLabel, elementById } = Utils;
const { elementByLabel, elementById, expectImagesToBeEqual,expectImagesToBeNotEqual } = Utils;

describe('Overlay', () => {
beforeEach(async () => {
Expand Down Expand Up @@ -54,6 +55,27 @@ describe('Overlay', () => {
await elementById(TestIDs.HIDE_TOP_BAR_BUTTON).tap();
await expect(elementById(TestIDs.TOP_BAR_ELEMENT)).toBeVisible();
});

it.e2e(':android: should show banner overlay and not block the screen', async () => {
const snapshottedImagePath = './e2e/assets/overlay_banner_padding.png';
Android.setDemoMode();
let expected = await device.takeScreenshot('without_banner');
await elementById(TestIDs.SHOW_BANNER_OVERLAY).tap();
await expect(elementById(TestIDs.BANNER_OVERLAY)).toBeVisible();
const actual = await device.takeScreenshot('with_banner');
expectImagesToBeNotEqual(expected, actual)
await elementById(TestIDs.SET_LAYOUT_BOTTOM_INSETS).tap();
expected = await device.takeScreenshot('with_banner');
expectImagesToBeEqual(expected, snapshottedImagePath)
});

it.e2e(':ios: should show banner overlay and not block the screen', async () => {
await elementById(TestIDs.SHOW_BANNER_OVERLAY).tap();
await expect(elementById(TestIDs.BANNER_OVERLAY)).toBeVisible();
await expect(elementById(TestIDs.FOOTER_TEXT)).toBeNotVisible();
await elementById(TestIDs.SET_LAYOUT_BOTTOM_INSETS).tap();
await expect(elementById(TestIDs.FOOTER_TEXT)).toBeVisible();
});
});

describe('Overlay Dismiss all', () => {
Expand Down
23 changes: 23 additions & 0 deletions e2e/Utils.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
import { readFileSync } from 'fs';
function bitmapDiff(imagePath, expectedImagePath) {
const PNG = require('pngjs').PNG;
const pixelmatch = require('pixelmatch');
const img1 = PNG.sync.read(readFileSync(imagePath));
const img2 = PNG.sync.read(readFileSync(expectedImagePath));
const {width, height} = img1;
const diff = new PNG({width, height});

return pixelmatch(img1.data, img2.data, diff.data, width, height, {threshold: 0.0})
}
const utils = {
elementByLabel: (label) => {
return element(by.text(label));
Expand All @@ -18,6 +29,18 @@ const utils = {
}
},
sleep: (ms) => new Promise((res) => setTimeout(res, ms)),
expectImagesToBeEqual:(imagePath, expectedImagePath)=>{
let diff = bitmapDiff(imagePath,expectedImagePath);
if(diff!==0){
throw Error(`${imagePath} should be the same as ${expectedImagePath}, with diff: ${diff}`)
}
} ,
expectImagesToBeNotEqual:(imagePath, expectedImagePath)=>{
let diff = bitmapDiff(imagePath,expectedImagePath);
if(diff===0){
throw Error(`${imagePath} should be the same as ${expectedImagePath}, with diff: ${diff}`)
}
}
};

export default utils;
Binary file added e2e/assets/overlay_banner_padding.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions e2e/iOSUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { execSync } from 'shell-utils/src/exec';

const utils = {
setDemoMode: () => {
execSync(
'xcrun simctl status_bar "iPhone 11" override --time "12:00" --batteryState charged --batteryLevel 100 --wifiBars 3 --cellularMode active --cellularBars 4'
);
},
};

export default utils;

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import android.content.Context;


import com.reactnativenavigation.options.layout.LayoutOptions;
import com.reactnativenavigation.options.params.NullNumber;
import com.reactnativenavigation.options.params.NullText;
import com.reactnativenavigation.options.parsers.TypefaceLoader;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.reactnativenavigation.options.layout

import com.reactnativenavigation.utils.dp
import org.json.JSONObject

class LayoutInsets(
var top: Int?=null,
var left: Int?=null,
var bottom: Int?=null,
var right: Int?=null
) {
fun merge(toMerge: LayoutInsets?, defaults: LayoutInsets?) {
toMerge?.let { options->
options.top?.let { this.top = it }
options.bottom?.let { this.bottom = it }
options.left?.let { this.left = it }
options.right?.let { this.right = it }
}

defaults?.let {
options->
top = top?:options.top
left = left?:options.left
right = right?:options.right
bottom = bottom?:options.bottom
}
}

companion object{
fun parse(jsonObject: JSONObject?): LayoutInsets {
return LayoutInsets(
jsonObject?.optInt("top")?.dp,
jsonObject?.optInt("left")?.dp,
jsonObject?.optInt("bottom")?.dp,
jsonObject?.optInt("right")?.dp
)
}
}

fun hasValue(): Boolean {
return top!=null || bottom!=null || left!=null || right!=null
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.reactnativenavigation.options.layout

import android.content.Context
import com.reactnativenavigation.options.LayoutDirection
import com.reactnativenavigation.options.OrientationOptions
import com.reactnativenavigation.options.params.*
import com.reactnativenavigation.options.params.Number
import com.reactnativenavigation.options.parsers.BoolParser
import com.reactnativenavigation.options.parsers.NumberParser
import org.json.JSONObject

class LayoutOptions {
@JvmField
var backgroundColor: ThemeColour = NullThemeColour()

@JvmField
var componentBackgroundColor: ThemeColour = NullThemeColour()

@JvmField
var topMargin: Number = NullNumber()

@JvmField
var adjustResize: Bool = NullBool()

@JvmField
var orientation = OrientationOptions()

@JvmField
var direction = LayoutDirection.DEFAULT

var insets: LayoutInsets = LayoutInsets()


fun mergeWith(other: LayoutOptions) {
if (other.backgroundColor.hasValue()) backgroundColor = other.backgroundColor
if (other.componentBackgroundColor.hasValue()) componentBackgroundColor = other.componentBackgroundColor
if (other.topMargin.hasValue()) topMargin = other.topMargin
if (other.orientation.hasValue()) orientation = other.orientation
if (other.direction.hasValue()) direction = other.direction
if (other.adjustResize.hasValue()) adjustResize = other.adjustResize
insets.merge(other.insets, null)
}

fun mergeWithDefault(defaultOptions: LayoutOptions) {
if (!backgroundColor.hasValue()) backgroundColor = defaultOptions.backgroundColor
if (!componentBackgroundColor.hasValue()) componentBackgroundColor = defaultOptions.componentBackgroundColor
if (!topMargin.hasValue()) topMargin = defaultOptions.topMargin
if (!orientation.hasValue()) orientation = defaultOptions.orientation
if (!direction.hasValue()) direction = defaultOptions.direction
if (!adjustResize.hasValue()) adjustResize = defaultOptions.adjustResize
insets.merge(null, defaultOptions.insets)

}

companion object {
@JvmStatic
fun parse(context: Context?, json: JSONObject?): LayoutOptions {
val result = LayoutOptions()
if (json == null) return result
result.backgroundColor = ThemeColour.parse(context!!, json.optJSONObject("backgroundColor"))
result.componentBackgroundColor = ThemeColour.parse(context, json.optJSONObject("componentBackgroundColor"))
result.topMargin = NumberParser.parse(json, "topMargin")
result.insets = LayoutInsets.parse(json.optJSONObject("insets"))
result.orientation = OrientationOptions.parse(json)
result.direction = LayoutDirection.fromString(json.optString("direction", ""))
result.adjustResize = BoolParser.parse(json, "adjustResize")
return result
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.reactnativenavigation.utils

import android.content.res.Resources

val Int.dp: Int
get() = (this * Resources.getSystem().displayMetrics.density).toInt()

val Float.dp: Int
get() = (this * Resources.getSystem().displayMetrics.density).toInt()
Original file line number Diff line number Diff line change
Expand Up @@ -144,16 +144,17 @@ public void applyBottomInset() {
protected WindowInsetsCompat onApplyWindowInsets(View view, WindowInsetsCompat insets) {
ViewController<?> viewController = findController(view);
if (viewController == null || viewController.getView() == null) return insets;
final Insets keyboardInsets = insets.getInsets( WindowInsetsCompat.Type.ime());
final int keyboardBottomInset = options.layout.adjustResize.get(true) ? insets.getInsets( WindowInsetsCompat.Type.ime()).bottom : 0;
final Insets systemBarsInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars() );
final int visibleNavBar = resolveCurrentOptions(presenter.defaultOptions).navigationBar.isVisible.isTrueOrUndefined()?1:0;
final WindowInsetsCompat finalInsets = new WindowInsetsCompat.Builder().setInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.ime(),
Insets.of(systemBarsInsets.left,
0,
systemBarsInsets.right,
Math.max(visibleNavBar*systemBarsInsets.bottom,keyboardInsets.bottom))
Math.max(visibleNavBar*systemBarsInsets.bottom,keyboardBottomInset))
).build();
return ViewCompat.onApplyWindowInsets(viewController.getView(), finalInsets);
ViewCompat.onApplyWindowInsets(viewController.getView(), finalInsets);
return insets;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
import android.content.res.Configuration;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.view.WindowInsetsCompat;

import com.facebook.react.ReactInstanceManager;
import com.reactnativenavigation.options.Options;
Expand Down Expand Up @@ -82,6 +84,8 @@ public Navigator(final Activity activity, ChildControllersRegistry childRegistry
overlaysLayout = new CoordinatorLayout(getActivity());
}



public void bindViews() {
modalStack.setModalsLayout(modalsLayout);
modalStack.setRootLayout(rootLayout);
Expand Down Expand Up @@ -147,6 +151,7 @@ public void setRoot(final ViewController<?> appearing, CommandListener commandLi
final ViewController<?> disappearing = previousRoot;
root = appearing;
root.setOverlay(new RootOverlay(getActivity(), contentLayout));
root.setParentController(this);
rootPresenter.setRoot(appearing, disappearing, defaultOptions, new CommandListenerAdapter(commandListener) {
@Override
public void onSuccess(String childId) {
Expand Down
Loading

0 comments on commit 9bb70d8

Please sign in to comment.