From 71d56f44f35191f6903b6c72059698169284b418 Mon Sep 17 00:00:00 2001 From: Brent Vatne Date: Sat, 23 May 2020 13:31:49 -0700 Subject: [PATCH 1/5] Update react-native-safe-area-context --- .../api/safeareacontext/EdgeInsets.java | 12 +- .../safeareacontext/InsetsChangeEvent.java | 10 +- .../modules/api/safeareacontext/Rect.java | 22 +++ .../SafeAreaContextPackage.java | 6 +- .../api/safeareacontext/SafeAreaProvider.java | 64 +++++++++ .../SafeAreaProviderManager.java | 87 ++++++++++++ .../api/safeareacontext/SafeAreaUtils.java | 101 ++++++-------- .../api/safeareacontext/SafeAreaView.java | 68 +++++++--- .../safeareacontext/SafeAreaViewEdges.java | 8 ++ .../SafeAreaViewLocalData.java | 21 +++ .../safeareacontext/SafeAreaViewManager.java | 83 +++++------- .../SafeAreaViewShadowNode.java | 126 ++++++++++++++++++ .../safeareacontext/SerializationUtils.java | 52 ++++++++ apps/bare-expo/package.json | 2 +- apps/native-component-list/package.json | 2 +- ios/Exponent.xcodeproj/project.pbxproj | 36 +++++ .../SafeAreaContext/RCTView+SafeAreaCompat.h | 21 +++ .../SafeAreaContext/RCTView+SafeAreaCompat.m | 49 +++++++ .../Api/SafeAreaContext/RNCSafeAreaProvider.h | 13 ++ .../Api/SafeAreaContext/RNCSafeAreaProvider.m | 65 +++++++++ .../RNCSafeAreaProviderManager.h | 9 ++ .../RNCSafeAreaProviderManager.m | 47 +++++++ .../SafeAreaContext/RNCSafeAreaShadowView.h | 9 ++ .../SafeAreaContext/RNCSafeAreaShadowView.m | 120 +++++++++++++++++ .../Api/SafeAreaContext/RNCSafeAreaView.h | 13 +- .../Api/SafeAreaContext/RNCSafeAreaView.m | 124 ++++++++--------- .../SafeAreaContext/RNCSafeAreaViewEdges.h | 9 ++ .../SafeAreaContext/RNCSafeAreaViewEdges.m | 13 ++ .../RNCSafeAreaViewLocalData.h | 14 ++ .../RNCSafeAreaViewLocalData.m | 15 +++ .../SafeAreaContext/RNCSafeAreaViewManager.m | 31 ++--- packages/expo/bundledNativeModules.json | 2 +- yarn.lock | 8 +- 33 files changed, 1030 insertions(+), 232 deletions(-) create mode 100644 android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/Rect.java create mode 100644 android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/SafeAreaProvider.java create mode 100644 android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/SafeAreaProviderManager.java create mode 100644 android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/SafeAreaViewEdges.java create mode 100644 android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/SafeAreaViewLocalData.java create mode 100644 android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/SafeAreaViewShadowNode.java create mode 100644 android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/SerializationUtils.java create mode 100644 ios/Exponent/Versioned/Core/Api/SafeAreaContext/RCTView+SafeAreaCompat.h create mode 100644 ios/Exponent/Versioned/Core/Api/SafeAreaContext/RCTView+SafeAreaCompat.m create mode 100644 ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaProvider.h create mode 100644 ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaProvider.m create mode 100644 ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaProviderManager.h create mode 100644 ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaProviderManager.m create mode 100644 ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaShadowView.h create mode 100644 ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaShadowView.m create mode 100644 ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaViewEdges.h create mode 100644 ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaViewEdges.m create mode 100644 ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaViewLocalData.h create mode 100644 ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaViewLocalData.m diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/EdgeInsets.java b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/EdgeInsets.java index 1780d312bb147..9e61f304370ce 100644 --- a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/EdgeInsets.java +++ b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/EdgeInsets.java @@ -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; } diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/InsetsChangeEvent.java b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/InsetsChangeEvent.java index 8fcda1f8e5897..e20a76ea39905 100644 --- a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/InsetsChangeEvent.java +++ b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/InsetsChangeEvent.java @@ -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 { - 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 @@ -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); } } diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/Rect.java b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/Rect.java new file mode 100644 index 0000000000000..4ca84725211b1 --- /dev/null +++ b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/Rect.java @@ -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; + } +} \ No newline at end of file diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/SafeAreaContextPackage.java b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/SafeAreaContextPackage.java index d945a6a4dbb1d..2cbf9c85b0e73 100644 --- a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/SafeAreaContextPackage.java +++ b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/SafeAreaContextPackage.java @@ -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; @@ -21,7 +22,10 @@ public List createNativeModules(@Nonnull ReactApplicationContext r @Nonnull @Override public List createViewManagers(@Nonnull ReactApplicationContext reactContext) { - return Collections.singletonList(new SafeAreaViewManager(reactContext)); + return Arrays.asList( + new SafeAreaProviderManager(reactContext), + new SafeAreaViewManager() + ); } } diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/SafeAreaProvider.java b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/SafeAreaProvider.java new file mode 100644 index 0000000000000..c070dfe440d8b --- /dev/null +++ b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/SafeAreaProvider.java @@ -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; + } +} diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/SafeAreaProviderManager.java b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/SafeAreaProviderManager.java new file mode 100644 index 0000000000000..71c1b6da621fd --- /dev/null +++ b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/SafeAreaProviderManager.java @@ -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 { + 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 getExportedCustomDirectEventTypeConstants() { + return MapBuilder.builder() + .put(InsetsChangeEvent.EVENT_NAME, MapBuilder.of("registrationName", "onInsetsChange")) + .build(); + } + + @Nullable + @Override + public Map 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.of( + "initialWindowMetrics", + MapBuilder.of( + "insets", + SerializationUtils.edgeInsetsToJavaMap(insets), + "frame", + SerializationUtils.rectToJavaMap(frame))); + + } +} diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/SafeAreaUtils.java b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/SafeAreaUtils.java index 6ba06bce4cdb5..29bda000ff235 100644 --- a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/SafeAreaUtils.java +++ b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/SafeAreaUtils.java @@ -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 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()); + } } diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/SafeAreaView.java b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/SafeAreaView.java index 13e616c17fff4..895e36f4ac477 100644 --- a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/SafeAreaView.java +++ b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/SafeAreaView.java @@ -1,39 +1,65 @@ package versioned.host.exp.exponent.modules.api.safeareacontext; +import android.annotation.SuppressLint; import android.content.Context; -import android.graphics.Rect; -import android.os.Build; -import android.view.Surface; +import android.content.ContextWrapper; import android.view.View; import android.view.ViewTreeObserver; -import android.view.WindowInsets; -import android.view.WindowManager; -import com.facebook.infer.annotation.Assertions; +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.uimanager.UIManagerModule; import com.facebook.react.views.view.ReactViewGroup; +import java.util.EnumSet; + import androidx.annotation.Nullable; +@SuppressLint("ViewConstructor") public class SafeAreaView extends ReactViewGroup implements ViewTreeObserver.OnGlobalLayoutListener { - public interface OnInsetsChangeListener { - void onInsetsChange(SafeAreaView view, EdgeInsets insets); + private @Nullable EdgeInsets mInsets; + private @Nullable EnumSet mEdges; + + public SafeAreaView(Context context) { + super(context); } - private @Nullable OnInsetsChangeListener mInsetsChangeListener; - private final WindowManager mWindowManager; - private @Nullable EdgeInsets mLastInsets; + /** + * UIManagerHelper.getReactContext only exists in RN 0.63+ so vendor it here for a while. + */ + private static ReactContext getReactContext(View view) { + Context context = view.getContext(); + if (!(context instanceof ReactContext) && context instanceof ContextWrapper) { + context = ((ContextWrapper) context).getBaseContext(); + } + return (ReactContext) context; + } - public SafeAreaView(Context context, WindowManager windowManager) { - super(context); + private void updateInsets() { + if (mInsets != null) { + EnumSet edges = mEdges != null + ? mEdges + : EnumSet.allOf(SafeAreaViewEdges.class); - mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + SafeAreaViewLocalData localData = new SafeAreaViewLocalData(mInsets, edges); + + ReactContext reactContext = getReactContext(this); + UIManagerModule uiManager = reactContext.getNativeModule(UIManagerModule.class); + if (uiManager != null) { + uiManager.setViewLocalData(getId(), localData); + } + } + } + + public void setEdges(EnumSet edges) { + mEdges = edges; + updateInsets(); } private void maybeUpdateInsets() { - EdgeInsets edgeInsets = SafeAreaUtils.getSafeAreaInsets(mWindowManager, getRootView()); - if (edgeInsets != null && (mLastInsets == null || !mLastInsets.equalsToEdgeInsets(edgeInsets))) { - Assertions.assertNotNull(mInsetsChangeListener).onInsetsChange(this, edgeInsets); - mLastInsets = edgeInsets; + EdgeInsets edgeInsets = SafeAreaUtils.getSafeAreaInsets(getRootView(), this); + if (edgeInsets != null && (mInsets == null || !mInsets.equalsToEdgeInsets(edgeInsets))) { + mInsets = edgeInsets; + updateInsets(); } } @@ -57,7 +83,9 @@ public void onGlobalLayout() { maybeUpdateInsets(); } - public void setOnInsetsChangeListener(OnInsetsChangeListener listener) { - mInsetsChangeListener = listener; + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + maybeUpdateInsets(); + super.onLayout(changed, left, top, right, bottom); } } diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/SafeAreaViewEdges.java b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/SafeAreaViewEdges.java new file mode 100644 index 0000000000000..4c8ad58126d47 --- /dev/null +++ b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/SafeAreaViewEdges.java @@ -0,0 +1,8 @@ +package versioned.host.exp.exponent.modules.api.safeareacontext; + +public enum SafeAreaViewEdges { + TOP, + RIGHT, + BOTTOM, + LEFT +} diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/SafeAreaViewLocalData.java b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/SafeAreaViewLocalData.java new file mode 100644 index 0000000000000..2a69110053b36 --- /dev/null +++ b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/SafeAreaViewLocalData.java @@ -0,0 +1,21 @@ +package versioned.host.exp.exponent.modules.api.safeareacontext; + +import java.util.EnumSet; + +public class SafeAreaViewLocalData { + private EdgeInsets mInsets; + private EnumSet mEdges; + + public SafeAreaViewLocalData(EdgeInsets insets, EnumSet edges) { + mInsets = insets; + mEdges = edges; + } + + public EdgeInsets getInsets() { + return mInsets; + } + + public EnumSet getEdges() { + return mEdges; + } +} diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/SafeAreaViewManager.java b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/SafeAreaViewManager.java index 63e1a154d900f..d1d32d58fff7c 100644 --- a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/SafeAreaViewManager.java +++ b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/SafeAreaViewManager.java @@ -1,31 +1,20 @@ package versioned.host.exp.exponent.modules.api.safeareacontext; -import android.app.Activity; -import android.content.Context; -import android.view.View; -import android.view.WindowManager; +import androidx.annotation.NonNull; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.common.MapBuilder; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.uimanager.LayoutShadowNode; 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 com.facebook.react.uimanager.annotations.ReactProp; -import java.util.Map; +import java.util.EnumSet; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; +import javax.annotation.Nullable; public class SafeAreaViewManager extends ViewGroupManager { - private final ReactApplicationContext mContext; - private final WindowManager mWindowManager; - - public SafeAreaViewManager(ReactApplicationContext context) { + public SafeAreaViewManager() { super(); - - mContext = context; - mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); } @Override @@ -37,48 +26,44 @@ public String getName() { @Override @NonNull public SafeAreaView createViewInstance(@NonNull ThemedReactContext context) { - return new SafeAreaView(context, mWindowManager); + return new SafeAreaView(context); } @Override - protected void addEventEmitters(@NonNull ThemedReactContext reactContext, @NonNull final SafeAreaView view) { - final EventDispatcher dispatcher = - reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher(); - view.setOnInsetsChangeListener(new SafeAreaView.OnInsetsChangeListener() { - @Override - public void onInsetsChange(SafeAreaView view, EdgeInsets insets) { - dispatcher.dispatchEvent(new InsetsChangeEvent(view.getId(), insets)); - } - }); + @NonNull + public SafeAreaViewShadowNode createShadowNodeInstance() { + return new SafeAreaViewShadowNode(); } @Override - public Map getExportedCustomDirectEventTypeConstants() { - return MapBuilder.builder() - .put(InsetsChangeEvent.EVENT_NAME, MapBuilder.of("registrationName", "onInsetsChange")) - .build(); + public Class getShadowNodeClass() { + return SafeAreaViewShadowNode.class; } - @Nullable - @Override - public Map getExportedViewConstants() { - Activity activity = mContext.getCurrentActivity(); - if (activity == null) { - return null; - } + @ReactProp(name = "edges") + public void setEdges(SafeAreaView view, @Nullable ReadableArray propList) { + EnumSet edges = EnumSet.noneOf(SafeAreaViewEdges.class); - View decorView = activity.getWindow().getDecorView(); - if (decorView == null) { - return null; + if (propList != null) { + for (int i = 0; i < propList.size(); i += 1) { + String edgeName = propList.getString(i); + if ("top".equals(edgeName)) { + edges.add(SafeAreaViewEdges.TOP); + } else if ("right".equals(edgeName)) { + edges.add(SafeAreaViewEdges.RIGHT); + } else if ("bottom".equals(edgeName)) { + edges.add(SafeAreaViewEdges.BOTTOM); + } else if ("left".equals(edgeName)) { + edges.add(SafeAreaViewEdges.LEFT); + } + } } - EdgeInsets insets = SafeAreaUtils.getSafeAreaInsets(mWindowManager, decorView); - if (insets == null) { - return null; - } - return MapBuilder.of( - "initialWindowSafeAreaInsets", - SafeAreaUtils.edgeInsetsToJavaMap(insets)); + view.setEdges(edges); + } + @ReactProp(name = "emulateUnlessSupported") + public void setEmulateUnlessSupported(SafeAreaView view, boolean propList) { + // Ignore } } diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/SafeAreaViewShadowNode.java b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/SafeAreaViewShadowNode.java new file mode 100644 index 0000000000000..4df5a980948b1 --- /dev/null +++ b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/SafeAreaViewShadowNode.java @@ -0,0 +1,126 @@ +package versioned.host.exp.exponent.modules.api.safeareacontext; + +import com.facebook.react.bridge.Dynamic; +import com.facebook.react.bridge.ReadableType; +import com.facebook.react.uimanager.Spacing; +import com.facebook.react.uimanager.annotations.ReactPropGroup; +import com.facebook.react.uimanager.LayoutShadowNode; +import com.facebook.react.uimanager.ViewProps; + +import java.util.EnumSet; + +public class SafeAreaViewShadowNode extends LayoutShadowNode { + private EdgeInsets mInsets = new EdgeInsets(0, 0, 0, 0); + private EnumSet mEdges = EnumSet.noneOf(SafeAreaViewEdges.class); + private float mPadding = Float.NaN; + private float mPaddingVertical = Float.NaN; + private float mPaddingHorizontal = Float.NaN; + private float mPaddingTop = Float.NaN; + private float mPaddingRight = Float.NaN; + private float mPaddingBottom = Float.NaN; + private float mPaddingLeft = Float.NaN; + + private void updateInsets() { + float paddingTop = 0; + float paddingRight = 0; + float paddingBottom = 0; + float paddingLeft = 0; + + if (!Float.isNaN(mPadding)) { + paddingTop = mPadding; + paddingRight = mPadding; + paddingBottom = mPadding; + paddingLeft = mPadding; + } + + if (!Float.isNaN(mPaddingVertical)) { + paddingTop = mPaddingVertical; + paddingBottom = mPaddingVertical; + } + + if (!Float.isNaN(mPaddingHorizontal)) { + paddingRight = mPaddingHorizontal; + paddingLeft = mPaddingHorizontal; + } + + if (!Float.isNaN(mPaddingTop)) { + paddingTop = mPaddingTop; + } + + if (!Float.isNaN(mPaddingRight)) { + paddingRight = mPaddingRight; + } + + if (!Float.isNaN(mPaddingBottom)) { + paddingBottom = mPaddingBottom; + } + + if (!Float.isNaN(mPaddingLeft)) { + paddingLeft = mPaddingLeft; + } + + float insetTop = mEdges.contains(SafeAreaViewEdges.TOP) ? mInsets.top : 0; + float insetRight = mEdges.contains(SafeAreaViewEdges.RIGHT) ? mInsets.right : 0; + float insetBottom = mEdges.contains(SafeAreaViewEdges.BOTTOM) ? mInsets.bottom : 0; + float insetLeft = mEdges.contains(SafeAreaViewEdges.LEFT) ? mInsets.left : 0; + + setPadding(Spacing.TOP, insetTop + paddingTop); + setPadding(Spacing.RIGHT, insetRight + paddingRight); + setPadding(Spacing.BOTTOM, insetBottom + paddingBottom); + setPadding(Spacing.LEFT, insetLeft + paddingLeft); + } + + @Override + public void setLocalData(Object data) { + if (data instanceof SafeAreaViewLocalData) { + mInsets = ((SafeAreaViewLocalData) data).getInsets(); + mEdges = ((SafeAreaViewLocalData) data).getEdges(); + updateInsets(); + } + } + + @Override + @ReactPropGroup( + names = { + ViewProps.PADDING, + ViewProps.PADDING_VERTICAL, + ViewProps.PADDING_HORIZONTAL, + ViewProps.PADDING_TOP, + ViewProps.PADDING_RIGHT, + ViewProps.PADDING_BOTTOM, + ViewProps.PADDING_LEFT, + }) + public void setPaddings(int index, Dynamic padding) { + float value = padding.getType() == ReadableType.Number + ? (float)padding.asDouble() + : Float.NaN; + + switch (index) { + case 0: + mPadding = value; + break; + case 1: + mPaddingVertical = value; + break; + case 2: + mPaddingHorizontal = value; + break; + case 3: + mPaddingTop = value; + break; + case 4: + mPaddingRight = value; + break; + case 5: + mPaddingBottom = value; + break; + case 6: + mPaddingLeft = value; + break; + default: + break; + } + + updateInsets(); + } +} diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/SerializationUtils.java b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/SerializationUtils.java new file mode 100644 index 0000000000000..aa55315e9d2db --- /dev/null +++ b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/safeareacontext/SerializationUtils.java @@ -0,0 +1,52 @@ +package versioned.host.exp.exponent.modules.api.safeareacontext; + +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; + +/* package */ class SerializationUtils { + 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 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 WritableMap rectToJsMap(Rect rect) { + WritableMap rectMap = Arguments.createMap(); + rectMap.putDouble("x", PixelUtil.toDIPFromPixel(rect.x)); + rectMap.putDouble("y", PixelUtil.toDIPFromPixel(rect.y)); + rectMap.putDouble("width", PixelUtil.toDIPFromPixel(rect.width)); + rectMap.putDouble("height", PixelUtil.toDIPFromPixel(rect.height)); + return rectMap; + } + + static Map rectToJavaMap(Rect rect) { + return MapBuilder.of( + "x", + PixelUtil.toDIPFromPixel(rect.x), + "y", + PixelUtil.toDIPFromPixel(rect.y), + "width", + PixelUtil.toDIPFromPixel(rect.width), + "height", + PixelUtil.toDIPFromPixel(rect.height)); + } +} diff --git a/apps/bare-expo/package.json b/apps/bare-expo/package.json index 93db78a712322..a613281b44e2d 100644 --- a/apps/bare-expo/package.json +++ b/apps/bare-expo/package.json @@ -92,7 +92,7 @@ "react-native-appearance": "~0.3.3", "react-native-gesture-handler": "~1.6.0", "react-native-reanimated": "~1.9.0", - "react-native-safe-area-context": "0.7.3", + "react-native-safe-area-context": "2.0.0", "react-native-screens": "~2.8.0", "react-native-unimodules": "~0.9.1", "react-native-web": "^0.11.4", diff --git a/apps/native-component-list/package.json b/apps/native-component-list/package.json index b643c2edc1c99..169f65743df77 100644 --- a/apps/native-component-list/package.json +++ b/apps/native-component-list/package.json @@ -90,7 +90,7 @@ "react-native-paper": "github:brentvatne/react-native-paper#@brent/fix-bottom-navigation-rn-57", "react-native-platform-touchable": "^1.1.1", "react-native-reanimated": "~1.9.0", - "react-native-safe-area-context": "0.7.3", + "react-native-safe-area-context": "2.0.0", "react-native-safe-area-view": "^0.14.8", "react-native-screens": "~2.8.0", "react-native-shared-element": "0.7.0", diff --git a/ios/Exponent.xcodeproj/project.pbxproj b/ios/Exponent.xcodeproj/project.pbxproj index e42d8ee4f80d9..39bc23743ef9c 100644 --- a/ios/Exponent.xcodeproj/project.pbxproj +++ b/ios/Exponent.xcodeproj/project.pbxproj @@ -344,6 +344,12 @@ F581B81CB601426CB5658AEE /* RNSharedElementCornerRadii.m in Sources */ = {isa = PBXBuildFile; fileRef = 780F0BF062134A048B33344C /* RNSharedElementCornerRadii.m */; settings = {COMPILER_FLAGS = "-w"; }; }; 97635343A010490BBF92C76F /* RNCSegmentedControl.m in Sources */ = {isa = PBXBuildFile; fileRef = C5DF76A1DCE14D2F9460F314 /* RNCSegmentedControl.m */; settings = {COMPILER_FLAGS = "-w"; }; }; E75397FDFB6B4FF780EA612A /* RNCSegmentedControlManager.m in Sources */ = {isa = PBXBuildFile; fileRef = F502F71E43A44658A6B20C26 /* RNCSegmentedControlManager.m */; settings = {COMPILER_FLAGS = "-w"; }; }; + FBF5C30408A44FD49CDC222C /* RCTView+SafeAreaCompat.m in Sources */ = {isa = PBXBuildFile; fileRef = 674353F5B0804C83B049187B /* RCTView+SafeAreaCompat.m */; settings = {COMPILER_FLAGS = "-w"; }; }; + BC0D2E02E636419D90DE8C5D /* RNCSafeAreaProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 44144BD59CC84557B028AF50 /* RNCSafeAreaProvider.m */; settings = {COMPILER_FLAGS = "-w"; }; }; + BBB4CAB8F7974F3698F1DBB0 /* RNCSafeAreaProviderManager.m in Sources */ = {isa = PBXBuildFile; fileRef = F44F28C4083648059AE2AD36 /* RNCSafeAreaProviderManager.m */; settings = {COMPILER_FLAGS = "-w"; }; }; + F21BCC2E320640BABF9A60E0 /* RNCSafeAreaShadowView.m in Sources */ = {isa = PBXBuildFile; fileRef = C5BACBCE8F7B46FDBD2C475C /* RNCSafeAreaShadowView.m */; settings = {COMPILER_FLAGS = "-w"; }; }; + EAC0DEB0A0374CB0B6F64CEE /* RNCSafeAreaViewEdges.m in Sources */ = {isa = PBXBuildFile; fileRef = 530ADBDE1E1F4709A49EC50F /* RNCSafeAreaViewEdges.m */; settings = {COMPILER_FLAGS = "-w"; }; }; + 4853ADEA7831444595D46BB1 /* RNCSafeAreaViewLocalData.m in Sources */ = {isa = PBXBuildFile; fileRef = 043260C5726842E09AC83B33 /* RNCSafeAreaViewLocalData.m */; settings = {COMPILER_FLAGS = "-w"; }; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -1052,6 +1058,18 @@ C5DF76A1DCE14D2F9460F314 /* RNCSegmentedControl.m */ = {isa = PBXFileReference; name = "RNCSegmentedControl.m"; path = "RNCSegmentedControl.m"; sourceTree = ""; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; explicitFileType = undefined; includeInIndex = 0; }; 9F02F7D4DDFF4D76804115FF /* RNCSegmentedControlManager.h */ = {isa = PBXFileReference; name = "RNCSegmentedControlManager.h"; path = "RNCSegmentedControlManager.h"; sourceTree = ""; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; explicitFileType = undefined; includeInIndex = 0; }; F502F71E43A44658A6B20C26 /* RNCSegmentedControlManager.m */ = {isa = PBXFileReference; name = "RNCSegmentedControlManager.m"; path = "RNCSegmentedControlManager.m"; sourceTree = ""; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; explicitFileType = undefined; includeInIndex = 0; }; + 0E4F332F35534147B996A40D /* RCTView+SafeAreaCompat.h */ = {isa = PBXFileReference; name = "RCTView+SafeAreaCompat.h"; path = "RCTView+SafeAreaCompat.h"; sourceTree = ""; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; explicitFileType = undefined; includeInIndex = 0; }; + 674353F5B0804C83B049187B /* RCTView+SafeAreaCompat.m */ = {isa = PBXFileReference; name = "RCTView+SafeAreaCompat.m"; path = "RCTView+SafeAreaCompat.m"; sourceTree = ""; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; explicitFileType = undefined; includeInIndex = 0; }; + 3608A2276F754201BB1ABC27 /* RNCSafeAreaProvider.h */ = {isa = PBXFileReference; name = "RNCSafeAreaProvider.h"; path = "RNCSafeAreaProvider.h"; sourceTree = ""; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; explicitFileType = undefined; includeInIndex = 0; }; + 44144BD59CC84557B028AF50 /* RNCSafeAreaProvider.m */ = {isa = PBXFileReference; name = "RNCSafeAreaProvider.m"; path = "RNCSafeAreaProvider.m"; sourceTree = ""; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; explicitFileType = undefined; includeInIndex = 0; }; + 5891A7A0286A42BA99087952 /* RNCSafeAreaProviderManager.h */ = {isa = PBXFileReference; name = "RNCSafeAreaProviderManager.h"; path = "RNCSafeAreaProviderManager.h"; sourceTree = ""; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; explicitFileType = undefined; includeInIndex = 0; }; + F44F28C4083648059AE2AD36 /* RNCSafeAreaProviderManager.m */ = {isa = PBXFileReference; name = "RNCSafeAreaProviderManager.m"; path = "RNCSafeAreaProviderManager.m"; sourceTree = ""; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; explicitFileType = undefined; includeInIndex = 0; }; + 619BDE28C8B243E18ACE7B1C /* RNCSafeAreaShadowView.h */ = {isa = PBXFileReference; name = "RNCSafeAreaShadowView.h"; path = "RNCSafeAreaShadowView.h"; sourceTree = ""; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; explicitFileType = undefined; includeInIndex = 0; }; + C5BACBCE8F7B46FDBD2C475C /* RNCSafeAreaShadowView.m */ = {isa = PBXFileReference; name = "RNCSafeAreaShadowView.m"; path = "RNCSafeAreaShadowView.m"; sourceTree = ""; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; explicitFileType = undefined; includeInIndex = 0; }; + 4E4E84930DA04F4FB3148C35 /* RNCSafeAreaViewEdges.h */ = {isa = PBXFileReference; name = "RNCSafeAreaViewEdges.h"; path = "RNCSafeAreaViewEdges.h"; sourceTree = ""; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; explicitFileType = undefined; includeInIndex = 0; }; + 530ADBDE1E1F4709A49EC50F /* RNCSafeAreaViewEdges.m */ = {isa = PBXFileReference; name = "RNCSafeAreaViewEdges.m"; path = "RNCSafeAreaViewEdges.m"; sourceTree = ""; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; explicitFileType = undefined; includeInIndex = 0; }; + 0EE28DB9348E472683FA2CC0 /* RNCSafeAreaViewLocalData.h */ = {isa = PBXFileReference; name = "RNCSafeAreaViewLocalData.h"; path = "RNCSafeAreaViewLocalData.h"; sourceTree = ""; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; explicitFileType = undefined; includeInIndex = 0; }; + 043260C5726842E09AC83B33 /* RNCSafeAreaViewLocalData.m */ = {isa = PBXFileReference; name = "RNCSafeAreaViewLocalData.m"; path = "RNCSafeAreaViewLocalData.m"; sourceTree = ""; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; explicitFileType = undefined; includeInIndex = 0; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -1143,6 +1161,18 @@ 3151637723146FC300AA029B /* RNCSafeAreaView.m */, 3151637A23146FC300AA029B /* RNCSafeAreaViewManager.h */, 3151637823146FC300AA029B /* RNCSafeAreaViewManager.m */, + 0E4F332F35534147B996A40D /* RCTView+SafeAreaCompat.h */, + 674353F5B0804C83B049187B /* RCTView+SafeAreaCompat.m */, + 3608A2276F754201BB1ABC27 /* RNCSafeAreaProvider.h */, + 44144BD59CC84557B028AF50 /* RNCSafeAreaProvider.m */, + 5891A7A0286A42BA99087952 /* RNCSafeAreaProviderManager.h */, + F44F28C4083648059AE2AD36 /* RNCSafeAreaProviderManager.m */, + 619BDE28C8B243E18ACE7B1C /* RNCSafeAreaShadowView.h */, + C5BACBCE8F7B46FDBD2C475C /* RNCSafeAreaShadowView.m */, + 4E4E84930DA04F4FB3148C35 /* RNCSafeAreaViewEdges.h */, + 530ADBDE1E1F4709A49EC50F /* RNCSafeAreaViewEdges.m */, + 0EE28DB9348E472683FA2CC0 /* RNCSafeAreaViewLocalData.h */, + 043260C5726842E09AC83B33 /* RNCSafeAreaViewLocalData.m */, ); path = SafeAreaContext; sourceTree = ""; @@ -2987,6 +3017,12 @@ F581B81CB601426CB5658AEE /* RNSharedElementCornerRadii.m in Sources */, 97635343A010490BBF92C76F /* RNCSegmentedControl.m in Sources */, E75397FDFB6B4FF780EA612A /* RNCSegmentedControlManager.m in Sources */, + FBF5C30408A44FD49CDC222C /* RCTView+SafeAreaCompat.m in Sources */, + BC0D2E02E636419D90DE8C5D /* RNCSafeAreaProvider.m in Sources */, + BBB4CAB8F7974F3698F1DBB0 /* RNCSafeAreaProviderManager.m in Sources */, + F21BCC2E320640BABF9A60E0 /* RNCSafeAreaShadowView.m in Sources */, + EAC0DEB0A0374CB0B6F64CEE /* RNCSafeAreaViewEdges.m in Sources */, + 4853ADEA7831444595D46BB1 /* RNCSafeAreaViewLocalData.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RCTView+SafeAreaCompat.h b/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RCTView+SafeAreaCompat.h new file mode 100644 index 0000000000000..0750100599dae --- /dev/null +++ b/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RCTView+SafeAreaCompat.h @@ -0,0 +1,21 @@ +#import +#import + +static BOOL UIEdgeInsetsEqualToEdgeInsetsWithThreshold(UIEdgeInsets insets1, UIEdgeInsets insets2, CGFloat threshold) +{ + return ABS(insets1.left - insets2.left) <= threshold && + ABS(insets1.right - insets2.right) <= threshold && + ABS(insets1.top - insets2.top) <= threshold && + ABS(insets1.bottom - insets2.bottom) <= threshold; +} + +NS_ASSUME_NONNULL_BEGIN + +@interface RCTView(SafeAreaCompat) + +- (BOOL)nativeSafeAreaSupport; +- (UIEdgeInsets)realOrEmulateSafeAreaInsets:(BOOL)emulate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RCTView+SafeAreaCompat.m b/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RCTView+SafeAreaCompat.m new file mode 100644 index 0000000000000..2a76101b6db72 --- /dev/null +++ b/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RCTView+SafeAreaCompat.m @@ -0,0 +1,49 @@ +#import "RCTView+SafeAreaCompat.h" + +#import + +@implementation RCTView(SafeAreaCompat) + +- (BOOL)nativeSafeAreaSupport +{ + return [self respondsToSelector:@selector(safeAreaInsets)]; +} + +- (UIEdgeInsets)realOrEmulateSafeAreaInsets:(BOOL)emulate +{ + #if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */ + if (self.nativeSafeAreaSupport) { + if (@available(iOS 11.0, *)) { + return self.safeAreaInsets; + } + } + #endif + return emulate ? self.emulatedSafeAreaInsets : UIEdgeInsetsZero; +} + +- (UIEdgeInsets)emulatedSafeAreaInsets +{ + UIViewController* vc = self.reactViewController; + + if (!vc) { + return UIEdgeInsetsZero; + } + + CGFloat topLayoutOffset = vc.topLayoutGuide.length; + CGFloat bottomLayoutOffset = vc.bottomLayoutGuide.length; + CGRect safeArea = vc.view.bounds; + safeArea.origin.y += topLayoutOffset; + safeArea.size.height -= topLayoutOffset + bottomLayoutOffset; + CGRect localSafeArea = [vc.view convertRect:safeArea toView:self]; + UIEdgeInsets safeAreaInsets = UIEdgeInsetsMake(0, 0, 0, 0); + if (CGRectGetMinY(localSafeArea) > CGRectGetMinY(self.bounds)) { + safeAreaInsets.top = CGRectGetMinY(localSafeArea) - CGRectGetMinY(self.bounds); + } + if (CGRectGetMaxY(localSafeArea) < CGRectGetMaxY(self.bounds)) { + safeAreaInsets.bottom = CGRectGetMaxY(self.bounds) - CGRectGetMaxY(localSafeArea); + } + + return safeAreaInsets; +} + +@end diff --git a/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaProvider.h b/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaProvider.h new file mode 100644 index 0000000000000..f31adb780d3d1 --- /dev/null +++ b/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaProvider.h @@ -0,0 +1,13 @@ +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface RNCSafeAreaProvider : RCTView + +@property (nonatomic, copy) RCTBubblingEventBlock onInsetsChange; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaProvider.m b/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaProvider.m new file mode 100644 index 0000000000000..eca1d2cf573b9 --- /dev/null +++ b/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaProvider.m @@ -0,0 +1,65 @@ +#import "RNCSafeAreaProvider.h" + +#import +#import +#import "RCTView+SafeAreaCompat.h" + +@implementation RNCSafeAreaProvider +{ + UIEdgeInsets _currentSafeAreaInsets; + CGRect _currentFrame; + BOOL _initialInsetsSent; +} + +- (void)safeAreaInsetsDidChange +{ + [self invalidateSafeAreaInsets]; +} + +- (void)invalidateSafeAreaInsets +{ + // This gets called before the view size is set by react-native so + // make sure to wait so we don't set wrong insets to JS. + if (CGSizeEqualToSize(self.frame.size, CGSizeZero)) { + return; + } + + UIEdgeInsets safeAreaInsets = [self realOrEmulateSafeAreaInsets:true]; + CGRect frame = [self convertRect:self.bounds toView:nil]; + + if ( + _initialInsetsSent && + UIEdgeInsetsEqualToEdgeInsetsWithThreshold(safeAreaInsets, _currentSafeAreaInsets, 1.0 / RCTScreenScale()) && + CGRectEqualToRect(frame, _currentFrame) + ) { + return; + } + + _initialInsetsSent = YES; + _currentSafeAreaInsets = safeAreaInsets; + _currentFrame = frame; + + self.onInsetsChange(@{ + @"insets": @{ + @"top": @(safeAreaInsets.top), + @"right": @(safeAreaInsets.right), + @"bottom": @(safeAreaInsets.bottom), + @"left": @(safeAreaInsets.left), + }, + @"frame": @{ + @"x": @(frame.origin.x), + @"y": @(frame.origin.y), + @"width": @(frame.size.width), + @"height": @(frame.size.height), + }, + }); +} + +- (void)layoutSubviews +{ + [super layoutSubviews]; + + [self invalidateSafeAreaInsets]; +} + +@end diff --git a/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaProviderManager.h b/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaProviderManager.h new file mode 100644 index 0000000000000..d83c32754008d --- /dev/null +++ b/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaProviderManager.h @@ -0,0 +1,9 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface RNCSafeAreaProviderManager : RCTViewManager + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaProviderManager.m b/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaProviderManager.m new file mode 100644 index 0000000000000..7b1ad0674246d --- /dev/null +++ b/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaProviderManager.m @@ -0,0 +1,47 @@ +#import "RNCSafeAreaProviderManager.h" + +#import "RNCSafeAreaProvider.h" + +@implementation RNCSafeAreaProviderManager + +RCT_EXPORT_MODULE(RNCSafeAreaProvider) + +RCT_EXPORT_VIEW_PROPERTY(onInsetsChange, RCTBubblingEventBlock) + ++ (BOOL)requiresMainQueueSetup +{ + return YES; +} + +- (UIView *)view +{ + return [RNCSafeAreaProvider new]; +} + +- (NSDictionary *)constantsToExport +{ + if (@available(iOS 11.0, *)) { + UIWindow* window = [[UIApplication sharedApplication] keyWindow]; + UIEdgeInsets safeAreaInsets = window.safeAreaInsets; + return @{ + @"initialWindowMetrics": @{ + @"insets": @{ + @"top": @(safeAreaInsets.top), + @"right": @(safeAreaInsets.right), + @"bottom": @(safeAreaInsets.bottom), + @"left": @(safeAreaInsets.left), + }, + @"frame": @{ + @"x": @(window.frame.origin.x), + @"y": @(window.frame.origin.y), + @"width": @(window.frame.size.width), + @"height": @(window.frame.size.height), + }, + } + }; + } else { + return @{ @"initialWindowMetrics": [NSNull null] }; + } +} + +@end diff --git a/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaShadowView.h b/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaShadowView.h new file mode 100644 index 0000000000000..a3474cda70e98 --- /dev/null +++ b/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaShadowView.h @@ -0,0 +1,9 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface RNCSafeAreaShadowView : RCTShadowView + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaShadowView.m b/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaShadowView.m new file mode 100644 index 0000000000000..a2777ccd13450 --- /dev/null +++ b/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaShadowView.m @@ -0,0 +1,120 @@ +#import "RNCSafeAreaShadowView.h" + +#import +#include + +#import "RNCSafeAreaViewLocalData.h" +#import "RNCSafeAreaViewEdges.h" + +@implementation RNCSafeAreaShadowView { + UIEdgeInsets _insets; + RNCSafeAreaViewEdges _edges; + CGFloat _padding; + CGFloat _paddingHorizontal; + CGFloat _paddingVertical; + CGFloat _paddingTop; + CGFloat _paddingRight; + CGFloat _paddingBottom; + CGFloat _paddingLeft; +} + +- (instancetype)init +{ + self = [super init]; + if (self) { + _padding = NAN; + _paddingHorizontal = NAN; + _paddingVertical = NAN; + _paddingTop = NAN; + _paddingRight = NAN; + _paddingBottom = NAN; + _paddingLeft = NAN; + } + return self; +} + +- (void)updateInsets +{ + CGFloat paddingTop = 0; + CGFloat paddingRight = 0; + CGFloat paddingBottom = 0; + CGFloat paddingLeft = 0; + + if (!isnan(_padding)) { + paddingTop = _padding; + paddingRight = _padding; + paddingBottom = _padding; + paddingLeft = _padding; + } + + if (!isnan(_paddingHorizontal)) { + paddingRight = _paddingHorizontal; + paddingLeft = _paddingHorizontal; + } + + if (!isnan(_paddingVertical)) { + paddingTop = _paddingVertical; + paddingBottom = _paddingVertical; + } + + if (!isnan(_paddingTop)) { + paddingTop = _paddingTop; + } + + if (!isnan(_paddingRight)) { + paddingRight = _paddingRight; + } + + if (!isnan(_paddingBottom)) { + paddingBottom = _paddingBottom; + } + + if (!isnan(_paddingLeft)) { + paddingLeft = _paddingLeft; + } + + CGFloat insetTop = (_edges & RNCSafeAreaViewEdgesTop) ? _insets.top : 0; + CGFloat insetRight = (_edges & RNCSafeAreaViewEdgesRight) ? _insets.right : 0; + CGFloat insetBottom = (_edges & RNCSafeAreaViewEdgesBottom) ? _insets.bottom : 0; + CGFloat insetLeft = (_edges & RNCSafeAreaViewEdgesLeft) ? _insets.left : 0; + + super.paddingTop = (YGValue){insetTop + paddingTop, YGUnitPoint}; + super.paddingRight = (YGValue){insetRight + paddingRight, YGUnitPoint}; + super.paddingBottom = (YGValue){insetBottom + paddingBottom, YGUnitPoint}; + super.paddingLeft = (YGValue){insetLeft + paddingLeft, YGUnitPoint}; + + [self didSetProps:@[@"paddingTop", @"paddingRight", @"paddingBottom", @"paddingLeft"]]; +} + +- (void)setLocalData:(RNCSafeAreaViewLocalData *)localData +{ + RCTAssert( + [localData isKindOfClass:[RNCSafeAreaViewLocalData class]], + @"Local data object for `RCTRNCSafeAreaShadowView` must be `RCTRNCSafeAreaViewLocalData` instance." + ); + + _insets = localData.insets; + _edges = localData.edges; + [self updateInsets]; +} + +/** + * Removing support for setting padding from any outside code + * to prevent interferring this with local data. + */ +#define SHADOW_VIEW_PADDING_PROPERTY(prop) \ +- (void)setPadding##prop:(YGValue)value \ +{ \ + _padding##prop = value.unit == YGUnitPoint ? value.value : NAN; \ + [self updateInsets]; \ +} + +SHADOW_VIEW_PADDING_PROPERTY(); +SHADOW_VIEW_PADDING_PROPERTY(Top); +SHADOW_VIEW_PADDING_PROPERTY(Right); +SHADOW_VIEW_PADDING_PROPERTY(Bottom); +SHADOW_VIEW_PADDING_PROPERTY(Left); +SHADOW_VIEW_PADDING_PROPERTY(Horizontal); +SHADOW_VIEW_PADDING_PROPERTY(Vertical); + +@end diff --git a/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaView.h b/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaView.h index 4e8365860ddf2..152ac3fe49c28 100644 --- a/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaView.h +++ b/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaView.h @@ -1,12 +1,19 @@ #import - #import +#import + +#import "RNCSafeAreaViewEdges.h" NS_ASSUME_NONNULL_BEGIN -@interface RNCSafeAreaView : RCTView +@class RNCSafeAreaView; + +@interface RNCSafeAreaView: RCTView + +- (instancetype)initWithBridge:(RCTBridge *)bridge; -@property (nonatomic, copy) RCTBubblingEventBlock onInsetsChange; +@property (nonatomic, assign) BOOL emulateUnlessSupported; +@property (nonatomic, assign) RNCSafeAreaViewEdges edges; @end diff --git a/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaView.m b/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaView.m index 3cc744d4895b3..a269b1dd92c23 100644 --- a/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaView.m +++ b/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaView.m @@ -1,64 +1,46 @@ -// Simplified version of https://github.com/facebook/react-native/blob/master/React/Views/SafeAreaView/RCTSafeAreaView.m - #import "RNCSafeAreaView.h" #import #import -static BOOL UIEdgeInsetsEqualToEdgeInsetsWithThreshold(UIEdgeInsets insets1, UIEdgeInsets insets2, CGFloat threshold) { - return - ABS(insets1.left - insets2.left) <= threshold && - ABS(insets1.right - insets2.right) <= threshold && - ABS(insets1.top - insets2.top) <= threshold && - ABS(insets1.bottom - insets2.bottom) <= threshold; -} +#import "RNCSafeAreaViewLocalData.h" +#import "RNCSafeAreaViewEdges.h" +#import "RCTView+SafeAreaCompat.h" -@implementation RNCSafeAreaView -{ +@implementation RNCSafeAreaView { + __weak RCTBridge *_bridge; UIEdgeInsets _currentSafeAreaInsets; - BOOL _initialInsetsSent; + RNCSafeAreaViewEdges _edges; } -- (BOOL)isSupportedByOS +- (instancetype)initWithBridge:(RCTBridge *)bridge { - return [self respondsToSelector:@selector(safeAreaInsets)]; -} - -- (UIEdgeInsets)realOrEmulateSafeAreaInsets -{ -#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */ - if (self.isSupportedByOS) { - if (@available(iOS 11.0, *)) { - return self.safeAreaInsets; - } + if (self = [super initWithFrame:CGRectZero]) { + _bridge = bridge; + // Defaults + _emulateUnlessSupported = YES; + _edges = RNCSafeAreaViewEdgesAll; } -#endif - return self.emulatedSafeAreaInsets; + + return self; } -- (UIEdgeInsets)emulatedSafeAreaInsets -{ - UIViewController* vc = self.reactViewController; +RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : (NSCoder *)decoder) +RCT_NOT_IMPLEMENTED(-(instancetype)initWithFrame : (CGRect)frame) - if (!vc) { - return UIEdgeInsetsZero; - } +- (NSString *)description +{ + NSString *superDescription = [super description]; - CGFloat topLayoutOffset = vc.topLayoutGuide.length; - CGFloat bottomLayoutOffset = vc.bottomLayoutGuide.length; - CGRect safeArea = vc.view.bounds; - safeArea.origin.y += topLayoutOffset; - safeArea.size.height -= topLayoutOffset + bottomLayoutOffset; - CGRect localSafeArea = [vc.view convertRect:safeArea toView:self]; - UIEdgeInsets safeAreaInsets = UIEdgeInsetsMake(0, 0, 0, 0); - if (CGRectGetMinY(localSafeArea) > CGRectGetMinY(self.bounds)) { - safeAreaInsets.top = CGRectGetMinY(localSafeArea) - CGRectGetMinY(self.bounds); - } - if (CGRectGetMaxY(localSafeArea) < CGRectGetMaxY(self.bounds)) { - safeAreaInsets.bottom = CGRectGetMaxY(self.bounds) - CGRectGetMaxY(localSafeArea); + // Cutting the last `>` character. + if (superDescription.length > 0 && [superDescription characterAtIndex:superDescription.length - 1] == '>') { + superDescription = [superDescription substringToIndex:superDescription.length - 1]; } - return safeAreaInsets; + return [NSString stringWithFormat:@"%@; RNCSafeAreaInsets = %@; appliedRNCSafeAreaInsets = %@>", + superDescription, + NSStringFromUIEdgeInsets([self realOrEmulateSafeAreaInsets:self.emulateUnlessSupported]), + NSStringFromUIEdgeInsets(_currentSafeAreaInsets)]; } - (void)safeAreaInsetsDidChange @@ -68,36 +50,54 @@ - (void)safeAreaInsetsDidChange - (void)invalidateSafeAreaInsets { - // This gets called before the view size is set by react-native so - // make sure to wait so we don't set wrong insets to JS. - if (CGSizeEqualToSize(self.frame.size, CGSizeZero)) { - return; + [self setSafeAreaInsets:[self realOrEmulateSafeAreaInsets:self.emulateUnlessSupported]]; +} + +- (void)layoutSubviews +{ + [super layoutSubviews]; + + if (!self.nativeSafeAreaSupport && self.emulateUnlessSupported) { + [self invalidateSafeAreaInsets]; } +} - UIEdgeInsets safeAreaInsets = [self realOrEmulateSafeAreaInsets]; +- (void)updateLocalData +{ + RNCSafeAreaViewLocalData *localData = [[RNCSafeAreaViewLocalData alloc] initWithInsets:_currentSafeAreaInsets + edges:_edges]; + [_bridge.uiManager setLocalData:localData forView:self]; +} - if (_initialInsetsSent && UIEdgeInsetsEqualToEdgeInsetsWithThreshold(safeAreaInsets, _currentSafeAreaInsets, 1.0 / RCTScreenScale())) { +- (void)setSafeAreaInsets:(UIEdgeInsets)safeAreaInsets +{ + if (UIEdgeInsetsEqualToEdgeInsetsWithThreshold(safeAreaInsets, _currentSafeAreaInsets, 1.0 / RCTScreenScale())) { return; } - _initialInsetsSent = YES; _currentSafeAreaInsets = safeAreaInsets; - - self.onInsetsChange(@{ - @"insets": @{ - @"top": @(safeAreaInsets.top), - @"right": @(safeAreaInsets.right), - @"bottom": @(safeAreaInsets.bottom), - @"left": @(safeAreaInsets.left), - } - }); + [self updateLocalData]; } -- (void)layoutSubviews +- (void)setEmulateUnlessSupported:(BOOL)emulateUnlessSupported { - [super layoutSubviews]; + if (_emulateUnlessSupported == emulateUnlessSupported) { + return; + } + + _emulateUnlessSupported = emulateUnlessSupported; + + if ([self nativeSafeAreaSupport]) { + return; + } [self invalidateSafeAreaInsets]; } +- (void)setEdges:(RNCSafeAreaViewEdges)edges { + _edges = edges; + + [self updateLocalData]; +} + @end diff --git a/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaViewEdges.h b/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaViewEdges.h new file mode 100644 index 0000000000000..e0ec09522593c --- /dev/null +++ b/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaViewEdges.h @@ -0,0 +1,9 @@ +#import + +typedef NS_ENUM(NSInteger, RNCSafeAreaViewEdges) { + RNCSafeAreaViewEdgesTop = 0b1000, + RNCSafeAreaViewEdgesRight = 0b0100, + RNCSafeAreaViewEdgesBottom = 0b0010, + RNCSafeAreaViewEdgesLeft = 0b0001, + RNCSafeAreaViewEdgesAll = 0b1111, +}; diff --git a/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaViewEdges.m b/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaViewEdges.m new file mode 100644 index 0000000000000..f5cc325cae02e --- /dev/null +++ b/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaViewEdges.m @@ -0,0 +1,13 @@ +#import +#import "RNCSafeAreaViewEdges.h" + +@implementation RCTConvert (RNCSafeAreaView) + +RCT_MULTI_ENUM_CONVERTER(RNCSafeAreaViewEdges, (@{ + @"top": @(RNCSafeAreaViewEdgesTop), + @"right": @(RNCSafeAreaViewEdgesRight), + @"bottom": @(RNCSafeAreaViewEdgesBottom), + @"left": @(RNCSafeAreaViewEdgesLeft), +}), RNCSafeAreaViewEdgesAll, integerValue); + +@end diff --git a/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaViewLocalData.h b/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaViewLocalData.h new file mode 100644 index 0000000000000..f1898afa7680c --- /dev/null +++ b/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaViewLocalData.h @@ -0,0 +1,14 @@ +#import "RNCSafeAreaViewEdges.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface RNCSafeAreaViewLocalData : NSObject + +- (instancetype)initWithInsets:(UIEdgeInsets)insets edges:(RNCSafeAreaViewEdges)edges; + +@property (atomic, readonly) UIEdgeInsets insets; +@property (atomic, readonly) RNCSafeAreaViewEdges edges; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaViewLocalData.m b/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaViewLocalData.m new file mode 100644 index 0000000000000..9aebb761f3344 --- /dev/null +++ b/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaViewLocalData.m @@ -0,0 +1,15 @@ +#import "RNCSafeAreaViewLocalData.h" + +@implementation RNCSafeAreaViewLocalData + +- (instancetype)initWithInsets:(UIEdgeInsets)insets edges:(RNCSafeAreaViewEdges)edges +{ + if (self = [super init]) { + _insets = insets; + _edges = edges; + } + + return self; +} + +@end diff --git a/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaViewManager.m b/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaViewManager.m index 0f33b15f4a972..0f2592b7893ec 100644 --- a/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaViewManager.m +++ b/ios/Exponent/Versioned/Core/Api/SafeAreaContext/RNCSafeAreaViewManager.m @@ -1,39 +1,24 @@ #import "RNCSafeAreaViewManager.h" +#import "RNCSafeAreaShadowView.h" #import "RNCSafeAreaView.h" +#import "RNCSafeAreaViewEdges.h" @implementation RNCSafeAreaViewManager RCT_EXPORT_MODULE(RNCSafeAreaView) -RCT_EXPORT_VIEW_PROPERTY(onInsetsChange, RCTBubblingEventBlock) - -+ (BOOL)requiresMainQueueSetup -{ - return YES; -} - - (UIView *)view { - return [RNCSafeAreaView new]; + return [[RNCSafeAreaView alloc] initWithBridge:self.bridge]; } -- (NSDictionary *)constantsToExport +- (RNCSafeAreaShadowView *)shadowView { - if (@available(iOS 11.0, *)) { - UIWindow* window = [[UIApplication sharedApplication] keyWindow]; - UIEdgeInsets safeAreaInsets = window.safeAreaInsets; - return @{ - @"initialWindowSafeAreaInsets": @{ - @"top": @(safeAreaInsets.top), - @"right": @(safeAreaInsets.right), - @"bottom": @(safeAreaInsets.bottom), - @"left": @(safeAreaInsets.left), - } - }; - } else { - return @{ @"initialWindowSafeAreaInsets": [NSNull null] }; - } + return [RNCSafeAreaShadowView new]; } +RCT_EXPORT_VIEW_PROPERTY(emulateUnlessSupported, BOOL) +RCT_EXPORT_VIEW_PROPERTY(edges, RNCSafeAreaViewEdges) + @end diff --git a/packages/expo/bundledNativeModules.json b/packages/expo/bundledNativeModules.json index 6937c255ac7ef..f24ede4b5fd61 100644 --- a/packages/expo/bundledNativeModules.json +++ b/packages/expo/bundledNativeModules.json @@ -78,7 +78,7 @@ "expo-in-app-purchases": "~8.1.0", "expo-branch": "~2.1.1", "react-native-appearance": "~0.3.3", - "react-native-safe-area-context": "0.7.3", + "react-native-safe-area-context": "2.0.0", "react-native-shared-element": "0.7.0", "expo-application": "~2.1.1", "expo-battery": "~2.1.1", diff --git a/yarn.lock b/yarn.lock index bdac1236179f9..2f01e8704725d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14082,10 +14082,10 @@ react-native-reanimated@~1.9.0: dependencies: fbjs "^1.0.0" -react-native-safe-area-context@0.7.3: - version "0.7.3" - resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-0.7.3.tgz#ad6bd4abbabe195332c53810e4ce5851eb21aa2a" - integrity sha512-9Uqu1vlXPi+2cKW/CW6OnHxA76mWC4kF3wvlqzq4DY8hn37AeiXtLFs2WkxH4yXQRrnJdP6ivc65Lz+MqwRZAA== +react-native-safe-area-context@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-2.0.0.tgz#7ef48e5a83a1e2f7fe9d5321493822b6765fd1ab" + integrity sha512-5VtCI3Nluzm7QfTcB/3j4YeWqt25QO1u5KTA1jEg1ckJzV19qCZFyHIpCCkS5+VEX+2JEHfdczhCdwE5sPgyEw== react-native-safe-area-view@^0.14, react-native-safe-area-view@^0.14.8, react-native-safe-area-view@^0.14.9: version "0.14.9" From 959b2130888ed3f7af1ccacb6c887814cc2144f5 Mon Sep 17 00:00:00 2001 From: Brent Vatne Date: Sat, 23 May 2020 14:58:34 -0700 Subject: [PATCH 2/5] Update NCL and docs --- apps/native-component-list/App.tsx | 9 +- .../src/navigation/ExpoApisStackNavigator.ts | 7 +- .../ExpoComponentsStackNavigator.ts | 7 +- .../ReactNativeCoreStackNavigator.ts | 5 +- .../navigation/SafeAreaNavigationWrapper.tsx | 17 --- .../ReactNativeCoreScreen.web.tsx | 5 +- .../src/screens/SafeAreaContextScreen.tsx | 54 ++++++--- .../unversioned/sdk/safe-area-context.md | 109 +++++++++++++----- 8 files changed, 136 insertions(+), 77 deletions(-) delete mode 100644 apps/native-component-list/src/navigation/SafeAreaNavigationWrapper.tsx diff --git a/apps/native-component-list/App.tsx b/apps/native-component-list/App.tsx index 7e61518a2b53a..b5e9e9172ecf9 100644 --- a/apps/native-component-list/App.tsx +++ b/apps/native-component-list/App.tsx @@ -2,6 +2,7 @@ import { AppLoading } from 'expo'; import * as React from 'react'; import { Platform, StatusBar } from 'react-native'; import { AppearanceProvider } from 'react-native-appearance'; +import { SafeAreaProvider } from 'react-native-safe-area-context'; import { enableScreens } from 'react-native-screens'; import RootNavigation from './src/navigation/RootNavigation'; @@ -13,9 +14,11 @@ if (Platform.OS === 'android') { export default function AppContainer() { return ( - - - + + + + + ); } diff --git a/apps/native-component-list/src/navigation/ExpoApisStackNavigator.ts b/apps/native-component-list/src/navigation/ExpoApisStackNavigator.ts index c941e9715ce78..a6172f9ed6ec3 100644 --- a/apps/native-component-list/src/navigation/ExpoApisStackNavigator.ts +++ b/apps/native-component-list/src/navigation/ExpoApisStackNavigator.ts @@ -1,10 +1,9 @@ -import createStackNavigator from './createStackNavigator'; -import StackConfig from './StackConfig'; import ExpoApis from '../screens/ExpoApisScreen'; import { Screens } from './ExpoApis'; -import SafeAreaNavigationWrapper from './SafeAreaNavigationWrapper'; import LoadAssetsNavigationWrapper from './LoadAssetsNavigationWrapper'; +import StackConfig from './StackConfig'; +import createStackNavigator from './createStackNavigator'; const ExpoApisStackNavigator = createStackNavigator({ ExpoApis, ...Screens }, StackConfig); -export default LoadAssetsNavigationWrapper(SafeAreaNavigationWrapper(ExpoApisStackNavigator)); +export default LoadAssetsNavigationWrapper(ExpoApisStackNavigator); diff --git a/apps/native-component-list/src/navigation/ExpoComponentsStackNavigator.ts b/apps/native-component-list/src/navigation/ExpoComponentsStackNavigator.ts index e15670ff83fd7..47524eea51300 100644 --- a/apps/native-component-list/src/navigation/ExpoComponentsStackNavigator.ts +++ b/apps/native-component-list/src/navigation/ExpoComponentsStackNavigator.ts @@ -1,9 +1,8 @@ import ExpoComponents from '../screens/ExpoComponentsScreen'; -import createStackNavigator from './createStackNavigator'; -import StackConfig from './StackConfig'; import { Screens } from './ExpoComponents'; -import SafeAreaNavigationWrapper from './SafeAreaNavigationWrapper'; import LoadAssetsNavigationWrapper from './LoadAssetsNavigationWrapper'; +import StackConfig from './StackConfig'; +import createStackNavigator from './createStackNavigator'; const ExpoComponentsStackNavigator = createStackNavigator( { @@ -13,4 +12,4 @@ const ExpoComponentsStackNavigator = createStackNavigator( StackConfig ); -export default LoadAssetsNavigationWrapper(SafeAreaNavigationWrapper(ExpoComponentsStackNavigator)); +export default LoadAssetsNavigationWrapper(ExpoComponentsStackNavigator); diff --git a/apps/native-component-list/src/navigation/ReactNativeCoreStackNavigator.ts b/apps/native-component-list/src/navigation/ReactNativeCoreStackNavigator.ts index a2ef3c878e416..c6c9c6172d0b0 100644 --- a/apps/native-component-list/src/navigation/ReactNativeCoreStackNavigator.ts +++ b/apps/native-component-list/src/navigation/ReactNativeCoreStackNavigator.ts @@ -1,7 +1,6 @@ import ReactNativeCore from '../screens/ReactNativeCore/ReactNativeCoreScreen'; -import createStackNavigator from './createStackNavigator'; import StackConfig from './StackConfig'; -import SafeAreaNavigationWrapper from './SafeAreaNavigationWrapper'; +import createStackNavigator from './createStackNavigator'; const ReactNativeCoreStackNavigator = createStackNavigator( { @@ -10,4 +9,4 @@ const ReactNativeCoreStackNavigator = createStackNavigator( StackConfig ); -export default SafeAreaNavigationWrapper(ReactNativeCoreStackNavigator); +export default ReactNativeCoreStackNavigator; diff --git a/apps/native-component-list/src/navigation/SafeAreaNavigationWrapper.tsx b/apps/native-component-list/src/navigation/SafeAreaNavigationWrapper.tsx deleted file mode 100644 index b558984310997..0000000000000 --- a/apps/native-component-list/src/navigation/SafeAreaNavigationWrapper.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; -import { NavigationNavigator } from 'react-navigation'; -import { SafeAreaProvider } from 'react-native-safe-area-context'; - -export default function SafeAreaNavigationWrapper(InnerNavigator: NavigationNavigator) { - function CustomNavigator(props: object) { - return ( - - - - ); - } - - CustomNavigator.router = InnerNavigator.router; - - return CustomNavigator; -} diff --git a/apps/native-component-list/src/screens/ReactNativeCore/ReactNativeCoreScreen.web.tsx b/apps/native-component-list/src/screens/ReactNativeCore/ReactNativeCoreScreen.web.tsx index f30da1afe24f3..b1b014628efdc 100644 --- a/apps/native-component-list/src/screens/ReactNativeCore/ReactNativeCoreScreen.web.tsx +++ b/apps/native-component-list/src/screens/ReactNativeCore/ReactNativeCoreScreen.web.tsx @@ -18,13 +18,12 @@ import { TouchableOpacityProps, View, } from 'react-native'; +import WebView from 'react-native-webview'; +import { NavigationScreenProps } from 'react-navigation'; import Colors from '../../constants/Colors'; import Layout from '../../constants/Layout'; -import { NavigationScreenProps } from 'react-navigation'; -import WebView from 'react-native-webview'; - export default class ReactNativeCoreScreen extends React.Component { static path = ''; diff --git a/apps/native-component-list/src/screens/SafeAreaContextScreen.tsx b/apps/native-component-list/src/screens/SafeAreaContextScreen.tsx index 9710eb60a383a..f0c76a5a1b973 100644 --- a/apps/native-component-list/src/screens/SafeAreaContextScreen.tsx +++ b/apps/native-component-list/src/screens/SafeAreaContextScreen.tsx @@ -1,27 +1,55 @@ import React from 'react'; -import { ScrollView, Text } from 'react-native'; -import { useSafeArea, SafeAreaConsumer } from 'react-native-safe-area-context'; +import { Button, Platform, ScrollView, Text, View } from 'react-native'; +import { SafeAreaView, useSafeArea, SafeAreaConsumer } from 'react-native-safe-area-context'; import HeadingText from '../components/HeadingText'; -export default function SafeAreaContextScreen() { +export default function SafeAreaContextScreen({ navigation }: { navigation: any }) { + const [focused, setFocused] = React.useState<'hook' | 'view'>('hook'); const insets = useSafeArea(); - return ( - - Using useSafeArea hook - {JSON.stringify(insets, null, 2)} - Using SafeAreaConsumer component + + if (focused === 'hook') { + return ( {consumerInsets => ( - {JSON.stringify(consumerInsets, null, 2)} + + Using useSafeArea hook + {JSON.stringify(insets, null, 2)} + Using SafeAreaConsumer component + {JSON.stringify(consumerInsets, null, 2)} +