Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update react-native-safe-area-context #8459

Merged
merged 5 commits into from May 23, 2020
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -1,19 +1,19 @@
package versioned.host.exp.exponent.modules.api.safeareacontext;

/* package */ class EdgeInsets {
public float top;
public float right;
public float bottom;
public float left;
float top;
float right;
float bottom;
float left;

public EdgeInsets(float top, float right, float bottom, float left) {
EdgeInsets(float top, float right, float bottom, float left) {
this.top = top;
this.right = right;
this.bottom = bottom;
this.left = left;
}

public boolean equalsToEdgeInsets(EdgeInsets other) {
boolean equalsToEdgeInsets(EdgeInsets other) {
if (this == other) {
return true;
}
Expand Down
Expand Up @@ -2,19 +2,20 @@

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.PixelUtil;
import com.facebook.react.uimanager.events.Event;
import com.facebook.react.uimanager.events.RCTEventEmitter;

/* package */ class InsetsChangeEvent extends Event<InsetsChangeEvent> {
public static final String EVENT_NAME = "topInsetsChange";
static final String EVENT_NAME = "topInsetsChange";

private EdgeInsets mInsets;
private Rect mFrame;

protected InsetsChangeEvent(int viewTag, EdgeInsets insets) {
InsetsChangeEvent(int viewTag, EdgeInsets insets, Rect frame) {
super(viewTag);

mInsets = insets;
mFrame = frame;
}

@Override
Expand All @@ -25,7 +26,8 @@ public String getEventName() {
@Override
public void dispatch(RCTEventEmitter rctEventEmitter) {
WritableMap event = Arguments.createMap();
event.putMap("insets", SafeAreaUtils.edgeInsetsToJsMap(mInsets));
event.putMap("insets", SerializationUtils.edgeInsetsToJsMap(mInsets));
event.putMap("frame", SerializationUtils.rectToJsMap(mFrame));
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), event);
}
}
@@ -0,0 +1,22 @@
package versioned.host.exp.exponent.modules.api.safeareacontext;

/* package */ class Rect {
float x;
float y;
float width;
float height;

Rect(float x, float y, float width, float height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}

boolean equalsToRect(Rect other) {
if (this == other) {
return true;
}
return this.x == other.x && this.y == other.y && this.width == other.width && this.height == other.height;
}
}
Expand Up @@ -5,6 +5,7 @@
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

Expand All @@ -21,7 +22,10 @@ public List<NativeModule> createNativeModules(@Nonnull ReactApplicationContext r
@Nonnull
@Override
public List<ViewManager> createViewManagers(@Nonnull ReactApplicationContext reactContext) {
return Collections.<ViewManager>singletonList(new SafeAreaViewManager(reactContext));
return Arrays.<ViewManager>asList(
new SafeAreaProviderManager(reactContext),
new SafeAreaViewManager()
);
}

}
@@ -0,0 +1,64 @@
package versioned.host.exp.exponent.modules.api.safeareacontext;

import android.annotation.SuppressLint;
import android.content.Context;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;

import com.facebook.infer.annotation.Assertions;
import com.facebook.react.views.view.ReactViewGroup;

import androidx.annotation.Nullable;

@SuppressLint("ViewConstructor")
public class SafeAreaProvider extends ReactViewGroup implements ViewTreeObserver.OnGlobalLayoutListener {
public interface OnInsetsChangeListener {
void onInsetsChange(SafeAreaProvider view, EdgeInsets insets, Rect frame);
}

private @Nullable OnInsetsChangeListener mInsetsChangeListener;
private @Nullable EdgeInsets mLastInsets;
private @Nullable Rect mLastFrame;

public SafeAreaProvider(Context context) {
super(context);
}

private void maybeUpdateInsets() {
EdgeInsets edgeInsets = SafeAreaUtils.getSafeAreaInsets(getRootView(), this);
Rect frame = SafeAreaUtils.getFrame((ViewGroup) getRootView(), this);
if (edgeInsets != null && frame != null &&
(mLastInsets == null ||
mLastFrame == null ||
!mLastInsets.equalsToEdgeInsets(edgeInsets) ||
!mLastFrame.equalsToRect(frame))) {
Assertions.assertNotNull(mInsetsChangeListener).onInsetsChange(this, edgeInsets, frame);
mLastInsets = edgeInsets;
mLastFrame = frame;
}
}

@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();

getViewTreeObserver().addOnGlobalLayoutListener(this);
maybeUpdateInsets();
}

@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();

getViewTreeObserver().removeOnGlobalLayoutListener(this);
}

@Override
public void onGlobalLayout() {
maybeUpdateInsets();
}

public void setOnInsetsChangeListener(OnInsetsChangeListener listener) {
mInsetsChangeListener = listener;
}
}
@@ -0,0 +1,87 @@
package versioned.host.exp.exponent.modules.api.safeareacontext;

import android.app.Activity;
import android.view.View;
import android.view.ViewGroup;

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.events.EventDispatcher;

import java.util.Map;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

public class SafeAreaProviderManager extends ViewGroupManager<SafeAreaProvider> {
private final ReactApplicationContext mContext;

public SafeAreaProviderManager(ReactApplicationContext context) {
super();

mContext = context;
}

@Override
@NonNull
public String getName() {
return "RNCSafeAreaProvider";
}

@Override
@NonNull
public SafeAreaProvider createViewInstance(@NonNull ThemedReactContext context) {
return new SafeAreaProvider(context);
}

@Override
protected void addEventEmitters(@NonNull ThemedReactContext reactContext, @NonNull final SafeAreaProvider view) {
final EventDispatcher dispatcher =
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher();
view.setOnInsetsChangeListener(new SafeAreaProvider.OnInsetsChangeListener() {
@Override
public void onInsetsChange(SafeAreaProvider view, EdgeInsets insets, Rect frame) {
dispatcher.dispatchEvent(new InsetsChangeEvent(view.getId(), insets, frame));
}
});
}

@Override
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
return MapBuilder.<String, Object>builder()
.put(InsetsChangeEvent.EVENT_NAME, MapBuilder.of("registrationName", "onInsetsChange"))
.build();
}

@Nullable
@Override
public Map<String, Object> getExportedViewConstants() {
Activity activity = mContext.getCurrentActivity();
if (activity == null) {
return null;
}

ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();
if (decorView == null) {
return null;
}

View contentView = decorView.findViewById(android.R.id.content);
EdgeInsets insets = SafeAreaUtils.getSafeAreaInsets(decorView, contentView);
Rect frame = SafeAreaUtils.getFrame(decorView, contentView);
if (insets == null || frame == null) {
return null;
}
return MapBuilder.<String, Object>of(
"initialWindowMetrics",
MapBuilder.<String, Object>of(
"insets",
SerializationUtils.edgeInsetsToJavaMap(insets),
"frame",
SerializationUtils.rectToJavaMap(frame)));

}
}
Expand Up @@ -2,88 +2,75 @@

import android.graphics.Rect;
import android.os.Build;
import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.view.WindowManager;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.PixelUtil;

import java.util.Map;

import androidx.annotation.Nullable;

/* package */ class SafeAreaUtils {
static WritableMap edgeInsetsToJsMap(EdgeInsets insets) {
WritableMap insetsMap = Arguments.createMap();
insetsMap.putDouble("top", PixelUtil.toDIPFromPixel(insets.top));
insetsMap.putDouble("right", PixelUtil.toDIPFromPixel(insets.right));
insetsMap.putDouble("bottom", PixelUtil.toDIPFromPixel(insets.bottom));
insetsMap.putDouble("left", PixelUtil.toDIPFromPixel(insets.left));
return insetsMap;
}

static Map<String, Float> edgeInsetsToJavaMap(EdgeInsets insets) {
return MapBuilder.of(
"top",
PixelUtil.toDIPFromPixel(insets.top),
"right",
PixelUtil.toDIPFromPixel(insets.right),
"bottom",
PixelUtil.toDIPFromPixel(insets.bottom),
"left",
PixelUtil.toDIPFromPixel(insets.left));
}

static @Nullable EdgeInsets getSafeAreaInsets(WindowManager windowManager, View rootView) {
// Window insets are parts of the window that are covered by system views (status bar,
// navigation bar, notches). There are no apis the get these values for android < M so we
// do a best effort polyfill.
EdgeInsets windowInsets;
private static @Nullable EdgeInsets getRootWindowInsetsCompat(View rootView) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
WindowInsets insets = rootView.getRootWindowInsets();
if (insets == null) {
return null;
}
windowInsets = new EdgeInsets(
return new EdgeInsets(
insets.getSystemWindowInsetTop(),
insets.getSystemWindowInsetRight(),
insets.getSystemWindowInsetBottom(),
// System insets are more reliable to account for notches but the
// system inset for bottom includes the soft keyboard which we don't
// want to be consistent with iOS. In practice it should always be
// correct since there cannot be a notch on this edge.
insets.getStableInsetBottom(),
insets.getSystemWindowInsetLeft());
} else {
int rotation = windowManager.getDefaultDisplay().getRotation();
int statusBarHeight = 0;
int resourceId = rootView.getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
statusBarHeight = rootView.getResources().getDimensionPixelSize(resourceId);
}
int navbarHeight = 0;
resourceId = rootView.getResources().getIdentifier("navigation_bar_height", "dimen", "android");
if (resourceId > 0) {
navbarHeight = rootView.getResources().getDimensionPixelSize(resourceId);
}
Rect visibleRect = new Rect();
rootView.getWindowVisibleDisplayFrame(visibleRect);
return new EdgeInsets(
visibleRect.top,
rootView.getWidth() - visibleRect.right,
rootView.getHeight() - visibleRect.bottom,
visibleRect.left);
}
}

windowInsets = new EdgeInsets(
statusBarHeight,
rotation == Surface.ROTATION_90 ? navbarHeight : 0,
rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180 ? navbarHeight : 0,
rotation == Surface.ROTATION_270 ? navbarHeight : 0);
static @Nullable EdgeInsets getSafeAreaInsets(View rootView, View view) {
EdgeInsets windowInsets = getRootWindowInsetsCompat(rootView);
if (windowInsets == null) {
return null;
}

// Calculate the part of the root view that overlaps with window insets.
View contentView = rootView.findViewById(android.R.id.content);
// Calculate the part of the view that overlaps with window insets.
float windowWidth = rootView.getWidth();
float windowHeight = rootView.getHeight();
Rect visibleRect = new Rect();
contentView.getGlobalVisibleRect(visibleRect);
view.getGlobalVisibleRect(visibleRect);

windowInsets.top = Math.max(windowInsets.top - visibleRect.top, 0);
windowInsets.left = Math.max(windowInsets.left - visibleRect.left, 0);
windowInsets.bottom = Math.max(visibleRect.top + contentView.getHeight() + windowInsets.bottom - windowHeight, 0);
windowInsets.right = Math.max(visibleRect.left + contentView.getWidth() + windowInsets.right - windowWidth, 0);
windowInsets.bottom = Math.max(visibleRect.top + view.getHeight() + windowInsets.bottom - windowHeight, 0);
windowInsets.right = Math.max(visibleRect.left + view.getWidth() + windowInsets.right - windowWidth, 0);
return windowInsets;
}

static @Nullable versioned.host.exp.exponent.modules.api.safeareacontext.Rect getFrame(ViewGroup rootView, View view) {
// This can happen while the view gets unmounted.
if (view.getParent() == null) {
return null;
}
Rect offset = new Rect();
view.getDrawingRect(offset);
try {
rootView.offsetDescendantRectToMyCoords(view, offset);
} catch (IllegalArgumentException ex) {
// This can throw if the view is not a descendant of rootView. This should not
// happen but avoid potential crashes.
ex.printStackTrace();
return null;
}

return new versioned.host.exp.exponent.modules.api.safeareacontext.Rect(offset.left, offset.top, view.getWidth(), view.getHeight());
}
}