Browse Source

Implement initialWindowSafeAreaInsets on android

Janic Duplessis 4 years ago
parent
commit
0baefa5754

+ 1
- 6
android/src/main/java/com/th3rdwave/safeareacontext/InsetsChangeEvent.java View File

@@ -24,13 +24,8 @@ import com.facebook.react.uimanager.events.RCTEventEmitter;
24 24
 
25 25
   @Override
26 26
   public void dispatch(RCTEventEmitter rctEventEmitter) {
27
-    WritableMap insets = Arguments.createMap();
28
-    insets.putDouble("top", PixelUtil.toDIPFromPixel(mInsets.top));
29
-    insets.putDouble("right", PixelUtil.toDIPFromPixel(mInsets.right));
30
-    insets.putDouble("bottom", PixelUtil.toDIPFromPixel(mInsets.bottom));
31
-    insets.putDouble("left", PixelUtil.toDIPFromPixel(mInsets.left));
32 27
     WritableMap event = Arguments.createMap();
33
-    event.putMap("insets", insets);
28
+    event.putMap("insets", SafeAreaUtils.edgeInsetsToJsMap(mInsets));
34 29
     rctEventEmitter.receiveEvent(getViewTag(), getEventName(), event);
35 30
   }
36 31
 }

+ 1
- 1
android/src/main/java/com/th3rdwave/safeareacontext/SafeAreaContextPackage.java View File

@@ -21,7 +21,7 @@ public class SafeAreaContextPackage implements ReactPackage {
21 21
   @Nonnull
22 22
   @Override
23 23
   public List<ViewManager> createViewManagers(@Nonnull ReactApplicationContext reactContext) {
24
-    return Collections.<ViewManager>singletonList(new SafeAreaViewManager());
24
+    return Collections.<ViewManager>singletonList(new SafeAreaViewManager(reactContext));
25 25
   }
26 26
 
27 27
 }

+ 85
- 0
android/src/main/java/com/th3rdwave/safeareacontext/SafeAreaUtils.java View File

@@ -0,0 +1,85 @@
1
+package com.th3rdwave.safeareacontext;
2
+
3
+import android.graphics.Rect;
4
+import android.os.Build;
5
+import android.view.Surface;
6
+import android.view.View;
7
+import android.view.WindowInsets;
8
+import android.view.WindowManager;
9
+
10
+import com.facebook.react.bridge.Arguments;
11
+import com.facebook.react.bridge.ReadableMap;
12
+import com.facebook.react.bridge.WritableMap;
13
+import com.facebook.react.common.MapBuilder;
14
+import com.facebook.react.uimanager.PixelUtil;
15
+
16
+import java.util.Map;
17
+
18
+/* package */ class SafeAreaUtils {
19
+  static ReadableMap edgeInsetsToJsMap(EdgeInsets insets) {
20
+    WritableMap insetsMap = Arguments.createMap();
21
+    insetsMap.putDouble("top", PixelUtil.toDIPFromPixel(insets.top));
22
+    insetsMap.putDouble("right", PixelUtil.toDIPFromPixel(insets.right));
23
+    insetsMap.putDouble("bottom", PixelUtil.toDIPFromPixel(insets.bottom));
24
+    insetsMap.putDouble("left", PixelUtil.toDIPFromPixel(insets.left));
25
+    return insetsMap;
26
+  }
27
+
28
+  static Map<String, Float> edgeInsetsToJavaMap(EdgeInsets insets) {
29
+    return MapBuilder.of(
30
+        "top",
31
+        PixelUtil.toDIPFromPixel(insets.top),
32
+        "right",
33
+        PixelUtil.toDIPFromPixel(insets.right),
34
+        "bottom",
35
+        PixelUtil.toDIPFromPixel(insets.bottom),
36
+        "left",
37
+        PixelUtil.toDIPFromPixel(insets.left));
38
+  }
39
+
40
+  static EdgeInsets getSafeAreaInsets(WindowManager windowManager, View rootView) {
41
+    // Window insets are parts of the window that are covered by system views (status bar,
42
+    // navigation bar, notches). There are no apis the get these values for android < M so we
43
+    // do a best effort polyfill.
44
+    EdgeInsets windowInsets;
45
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
46
+      WindowInsets insets = rootView.getRootWindowInsets();
47
+      windowInsets = new EdgeInsets(
48
+          insets.getSystemWindowInsetTop(),
49
+          insets.getSystemWindowInsetRight(),
50
+          insets.getSystemWindowInsetBottom(),
51
+          insets.getSystemWindowInsetLeft());
52
+    } else {
53
+      int rotation = windowManager.getDefaultDisplay().getRotation();
54
+      int statusBarHeight = 0;
55
+      int resourceId = rootView.getResources().getIdentifier("status_bar_height", "dimen", "android");
56
+      if (resourceId > 0) {
57
+        statusBarHeight = rootView.getResources().getDimensionPixelSize(resourceId);
58
+      }
59
+      int navbarHeight = 0;
60
+      resourceId = rootView.getResources().getIdentifier("navigation_bar_height", "dimen", "android");
61
+      if (resourceId > 0) {
62
+        navbarHeight = rootView.getResources().getDimensionPixelSize(resourceId);
63
+      }
64
+
65
+      windowInsets = new EdgeInsets(
66
+          statusBarHeight,
67
+          rotation == Surface.ROTATION_90 ? navbarHeight : 0,
68
+          rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180 ? navbarHeight : 0,
69
+          rotation == Surface.ROTATION_270 ? navbarHeight : 0);
70
+    }
71
+
72
+    // Calculate the part of the root view that overlaps with window insets.
73
+    View contentView = rootView.findViewById(android.R.id.content);
74
+    float windowWidth = rootView.getWidth();
75
+    float windowHeight = rootView.getHeight();
76
+    Rect visibleRect = new Rect();
77
+    contentView.getGlobalVisibleRect(visibleRect);
78
+
79
+    windowInsets.top = Math.max(windowInsets.top - visibleRect.top, 0);
80
+    windowInsets.left = Math.max(windowInsets.left - visibleRect.left, 0);
81
+    windowInsets.bottom = Math.max(visibleRect.top + contentView.getHeight() + windowInsets.bottom - windowHeight, 0);
82
+    windowInsets.right = Math.max(visibleRect.left + contentView.getWidth() + windowInsets.right - windowWidth, 0);
83
+    return windowInsets;
84
+  }
85
+}

+ 3
- 50
android/src/main/java/com/th3rdwave/safeareacontext/SafeAreaView.java View File

@@ -20,64 +20,17 @@ public class SafeAreaView extends ReactViewGroup implements ViewTreeObserver.OnG
20 20
   }
21 21
 
22 22
   private @Nullable OnInsetsChangeListener mInsetsChangeListener;
23
-  private WindowManager mWindowManager;
23
+  private final WindowManager mWindowManager;
24 24
   private @Nullable EdgeInsets mLastInsets;
25 25
 
26
-  public SafeAreaView(Context context) {
26
+  public SafeAreaView(Context context, WindowManager windowManager) {
27 27
     super(context);
28 28
 
29 29
     mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
30 30
   }
31 31
 
32
-  private EdgeInsets getSafeAreaInsets() {
33
-    // Window insets are parts of the window that are covered by system views (status bar,
34
-    // navigation bar, notches). There are no apis the get these values for android < M so we
35
-    // do a best effort polyfill.
36
-    EdgeInsets windowInsets;
37
-    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
38
-      WindowInsets insets = getRootWindowInsets();
39
-      windowInsets = new EdgeInsets(
40
-          insets.getSystemWindowInsetTop(),
41
-          insets.getSystemWindowInsetRight(),
42
-          insets.getSystemWindowInsetBottom(),
43
-          insets.getSystemWindowInsetLeft());
44
-    } else {
45
-      int rotation = mWindowManager.getDefaultDisplay().getRotation();
46
-      int statusBarHeight = 0;
47
-      int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
48
-      if (resourceId > 0) {
49
-        statusBarHeight = getResources().getDimensionPixelSize(resourceId);
50
-      }
51
-      int navbarHeight = 0;
52
-      resourceId = getResources().getIdentifier("navigation_bar_height", "dimen", "android");
53
-      if (resourceId > 0) {
54
-        navbarHeight = getResources().getDimensionPixelSize(resourceId);
55
-      }
56
-
57
-      windowInsets = new EdgeInsets(
58
-          statusBarHeight,
59
-          rotation == Surface.ROTATION_90 ? navbarHeight : 0,
60
-          rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180 ? navbarHeight : 0,
61
-          rotation == Surface.ROTATION_270 ? navbarHeight : 0);
62
-    }
63
-
64
-    // Calculate the part of the root view that overlaps with window insets.
65
-    View rootView = getRootView();
66
-    View contentView = rootView.findViewById(android.R.id.content);
67
-    float windowWidth = rootView.getWidth();
68
-    float windowHeight = rootView.getHeight();
69
-    Rect visibleRect = new Rect();
70
-    contentView.getGlobalVisibleRect(visibleRect);
71
-
72
-    windowInsets.top = Math.max(windowInsets.top - visibleRect.top, 0);
73
-    windowInsets.left = Math.max(windowInsets.left - visibleRect.left, 0);
74
-    windowInsets.bottom = Math.max(visibleRect.top + contentView.getHeight() + windowInsets.bottom - windowHeight, 0);
75
-    windowInsets.right = Math.max(visibleRect.left + contentView.getWidth() + windowInsets.right - windowWidth, 0);
76
-    return windowInsets;
77
-  }
78
-
79 32
   private void maybeUpdateInsets() {
80
-    EdgeInsets edgeInsets = getSafeAreaInsets();
33
+    EdgeInsets edgeInsets = SafeAreaUtils.getSafeAreaInsets(mWindowManager, getRootView());
81 34
     if (mLastInsets == null || !mLastInsets.equalsToEdgeInsets(edgeInsets)) {
82 35
       Assertions.assertNotNull(mInsetsChangeListener).onInsetsChange(this, edgeInsets);
83 36
       mLastInsets = edgeInsets;

+ 31
- 1
android/src/main/java/com/th3rdwave/safeareacontext/SafeAreaViewManager.java View File

@@ -1,5 +1,10 @@
1 1
 package com.th3rdwave.safeareacontext;
2 2
 
3
+import android.app.Activity;
4
+import android.content.Context;
5
+import android.view.WindowManager;
6
+
7
+import com.facebook.react.bridge.ReactApplicationContext;
3 8
 import com.facebook.react.common.MapBuilder;
4 9
 import com.facebook.react.uimanager.ThemedReactContext;
5 10
 import com.facebook.react.uimanager.UIManagerModule;
@@ -9,8 +14,19 @@ import com.facebook.react.uimanager.events.EventDispatcher;
9 14
 import java.util.Map;
10 15
 
11 16
 import androidx.annotation.NonNull;
17
+import androidx.annotation.Nullable;
12 18
 
13 19
 public class SafeAreaViewManager extends ViewGroupManager<SafeAreaView> {
20
+  private final ReactApplicationContext mContext;
21
+  private final WindowManager mWindowManager;
22
+
23
+  public SafeAreaViewManager(ReactApplicationContext context) {
24
+    super();
25
+
26
+    mContext = context;
27
+    mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
28
+  }
29
+
14 30
   @Override
15 31
   @NonNull
16 32
   public String getName() {
@@ -20,7 +36,7 @@ public class SafeAreaViewManager extends ViewGroupManager<SafeAreaView> {
20 36
   @Override
21 37
   @NonNull
22 38
   public SafeAreaView createViewInstance(@NonNull ThemedReactContext context) {
23
-    return new SafeAreaView(context);
39
+    return new SafeAreaView(context, mWindowManager);
24 40
   }
25 41
 
26 42
   @Override
@@ -41,4 +57,18 @@ public class SafeAreaViewManager extends ViewGroupManager<SafeAreaView> {
41 57
         .put(InsetsChangeEvent.EVENT_NAME, MapBuilder.of("registrationName", "onInsetsChange"))
42 58
         .build();
43 59
   }
60
+
61
+  @Nullable
62
+  @Override
63
+  public Map<String, Object> getExportedViewConstants() {
64
+    Activity activity = mContext.getCurrentActivity();
65
+    if (activity == null) {
66
+      return null;
67
+    }
68
+
69
+    EdgeInsets insets = SafeAreaUtils.getSafeAreaInsets(mWindowManager, activity.getWindow().getDecorView());
70
+    return MapBuilder.<String, Object>of(
71
+        "initialWindowSafeAreaInsets",
72
+        SafeAreaUtils.edgeInsetsToJavaMap(insets));
73
+  }
44 74
 }

+ 9
- 1
example/App.tsx View File

@@ -2,7 +2,11 @@ import * as React from 'react';
2 2
 import { View, Text, StatusBar, Button } from 'react-native';
3 3
 
4 4
 // import { SafeAreaProvider, useSafeArea } from 'react-native-safe-area-context'; in your app.
5
-import { SafeAreaProvider, useSafeArea } from '../src';
5
+import {
6
+  SafeAreaProvider,
7
+  useSafeArea,
8
+  initialWindowSafeAreaInsets,
9
+} from '../src';
6 10
 
7 11
 const Screen = () => {
8 12
   const insets = useSafeArea();
@@ -33,6 +37,10 @@ const Screen = () => {
33 37
           }}
34 38
         >
35 39
           <Text>Insets: {JSON.stringify(insets, null, 2)}</Text>
40
+          <Text>
41
+            Initial insets:{' '}
42
+            {JSON.stringify(initialWindowSafeAreaInsets, null, 2)}
43
+          </Text>
36 44
         </View>
37 45
       </View>
38 46
     </>

+ 0
- 3
src/InitialWindowSafeAreaInsets.ios.ts View File

@@ -1,3 +0,0 @@
1
-import { NativeModules } from 'react-native';
2
-
3
-export default NativeModules.RNCSafeAreaView.initialWindowSafeAreaInsets;

+ 9
- 2
src/InitialWindowSafeAreaInsets.ts View File

@@ -1,4 +1,11 @@
1
+import { UIManager } from 'react-native';
1 2
 import { EdgeInsets } from './SafeArea.types';
2 3
 
3
-const initialWindowSafeAreaInsets: EdgeInsets | null = null;
4
-export default initialWindowSafeAreaInsets;
4
+const RNCSafeAreaViewConfig = UIManager.getViewManagerConfig(
5
+  'RNCSafeAreaView',
6
+  // eslint-disable-next-line @typescript-eslint/no-explicit-any
7
+) as any;
8
+
9
+export default (RNCSafeAreaViewConfig.Constants != null
10
+  ? RNCSafeAreaViewConfig.Constants.initialWindowSafeAreaInsets
11
+  : null) as EdgeInsets | null;

+ 4
- 0
src/InitialWindowSafeAreaInsets.web.ts View File

@@ -0,0 +1,4 @@
1
+import { EdgeInsets } from './SafeArea.types';
2
+
3
+const initialWindowSafeAreaInsets: EdgeInsets | null = null;
4
+export default initialWindowSafeAreaInsets;

+ 3
- 1
src/index.tsx View File

@@ -3,7 +3,9 @@ import { StyleSheet, View, ViewProps } from 'react-native';
3 3
 import { EdgeInsets as EdgeInsetsT, InsetChangedEvent } from './SafeArea.types';
4 4
 import NativeSafeAreaView from './NativeSafeAreaView';
5 5
 
6
-export { default as initialWindowSafeAreaInsets } from './InitialWindowSafeAreaInsets';
6
+export {
7
+  default as initialWindowSafeAreaInsets,
8
+} from './InitialWindowSafeAreaInsets';
7 9
 
8 10
 export const SafeAreaContext = React.createContext<EdgeInsetsT | null>(null);
9 11