Browse Source

Change api, fix android

Janic Duplessis 5 years ago
parent
commit
eb9147ac73

+ 1
- 0
.eslintrc.js View File

23
           '@typescript-eslint/explicit-member-accessibility': 'off',
23
           '@typescript-eslint/explicit-member-accessibility': 'off',
24
           '@typescript-eslint/explicit-function-return-type': 'off',
24
           '@typescript-eslint/explicit-function-return-type': 'off',
25
           '@typescript-eslint/no-use-before-define': 'off',
25
           '@typescript-eslint/no-use-before-define': 'off',
26
+          'react-native/no-inline-styles': 'off',
26
         },
27
         },
27
       ),
28
       ),
28
     },
29
     },

+ 7
- 0
android/src/main/java/com/th3rdwave/safeareaview/EdgeInsets.java View File

12
     this.bottom = bottom;
12
     this.bottom = bottom;
13
     this.left = left;
13
     this.left = left;
14
   }
14
   }
15
+
16
+  public boolean equalsToEdgeInsets(EdgeInsets other) {
17
+    if (this == other) {
18
+      return true;
19
+    }
20
+    return this.top == other.top && this.right == other.right && this.bottom == other.bottom && this.left == other.left;
21
+  }
15
 }
22
 }

+ 45
- 11
android/src/main/java/com/th3rdwave/safeareaview/SafeAreaView.java View File

1
 package com.th3rdwave.safeareaview;
1
 package com.th3rdwave.safeareaview;
2
 
2
 
3
+import android.app.Activity;
3
 import android.content.Context;
4
 import android.content.Context;
5
+import android.content.ContextWrapper;
4
 import android.os.Build;
6
 import android.os.Build;
5
-import android.util.DisplayMetrics;
6
 import android.view.Surface;
7
 import android.view.Surface;
8
+import android.view.View;
9
+import android.view.ViewTreeObserver;
10
+import android.view.Window;
7
 import android.view.WindowInsets;
11
 import android.view.WindowInsets;
8
 import android.view.WindowManager;
12
 import android.view.WindowManager;
9
 
13
 
10
 import com.facebook.infer.annotation.Assertions;
14
 import com.facebook.infer.annotation.Assertions;
11
-import com.facebook.react.uimanager.DisplayMetricsHolder;
12
 import com.facebook.react.views.view.ReactViewGroup;
15
 import com.facebook.react.views.view.ReactViewGroup;
13
 
16
 
14
 import androidx.annotation.Nullable;
17
 import androidx.annotation.Nullable;
15
 
18
 
16
-public class SafeAreaView extends ReactViewGroup {
19
+public class SafeAreaView extends ReactViewGroup implements ViewTreeObserver.OnGlobalLayoutListener {
17
   public interface OnInsetsChangeListener {
20
   public interface OnInsetsChangeListener {
18
     void onInsetsChange(SafeAreaView view, EdgeInsets insets);
21
     void onInsetsChange(SafeAreaView view, EdgeInsets insets);
19
   }
22
   }
20
 
23
 
21
   private @Nullable OnInsetsChangeListener mInsetsChangeListener;
24
   private @Nullable OnInsetsChangeListener mInsetsChangeListener;
22
-  WindowManager mWindowManager;
25
+  private WindowManager mWindowManager;
26
+  private @Nullable EdgeInsets mLastInsets;
23
 
27
 
24
   public SafeAreaView(Context context) {
28
   public SafeAreaView(Context context) {
25
     super(context);
29
     super(context);
27
     mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
31
     mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
28
   }
32
   }
29
 
33
 
34
+  private Activity getActivity() {
35
+    Context context = getContext();
36
+    while (context instanceof ContextWrapper) {
37
+      if (context instanceof Activity) {
38
+        return (Activity)context;
39
+      }
40
+      context = ((ContextWrapper)context).getBaseContext();
41
+    }
42
+    return null;
43
+  }
44
+
30
   private EdgeInsets getSafeAreaInsets() {
45
   private EdgeInsets getSafeAreaInsets() {
31
     // Window insets are parts of the window that are covered by system views (status bar,
46
     // Window insets are parts of the window that are covered by system views (status bar,
32
     // navigation bar, notches). There are no apis the get these values for android < M so we
47
     // navigation bar, notches). There are no apis the get these values for android < M so we
60
     }
75
     }
61
 
76
 
62
     // Calculate the part of the root view that overlaps with window insets.
77
     // Calculate the part of the root view that overlaps with window insets.
78
+    View rootView = getRootView();
79
+    View contentView = rootView.findViewById(android.R.id.content);
80
+    float windowWidth = rootView.getWidth();
81
+    float windowHeight = rootView.getHeight();
63
     int[] windowLocation = new int[2];
82
     int[] windowLocation = new int[2];
64
-    getLocationInWindow(windowLocation);
65
-    DisplayMetrics screenMetrics = DisplayMetricsHolder.getScreenDisplayMetrics();
83
+    contentView.getLocationInWindow(windowLocation);
66
     windowInsets.top = Math.max(windowInsets.top - windowLocation[1], 0);
84
     windowInsets.top = Math.max(windowInsets.top - windowLocation[1], 0);
67
     windowInsets.left = Math.max(windowInsets.left - windowLocation[0], 0);
85
     windowInsets.left = Math.max(windowInsets.left - windowLocation[0], 0);
68
-    windowInsets.bottom = Math.max(windowLocation[1] + getHeight() + windowInsets.bottom - screenMetrics.heightPixels, 0);
69
-    windowInsets.right = Math.max(windowLocation[0] + getWidth() + windowInsets.right - screenMetrics.widthPixels, 0);
86
+    windowInsets.bottom = Math.max(windowLocation[1] + contentView.getHeight() + windowInsets.bottom - windowHeight, 0);
87
+    windowInsets.right = Math.max(windowLocation[0] + contentView.getWidth() + windowInsets.right - windowWidth, 0);
70
     return windowInsets;
88
     return windowInsets;
71
   }
89
   }
72
 
90
 
73
   @Override
91
   @Override
74
-  protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
75
-    super.onLayout(changed, left, top, right, bottom);
92
+  protected void onAttachedToWindow() {
93
+    super.onAttachedToWindow();
94
+
95
+    getRootView().getViewTreeObserver().addOnGlobalLayoutListener(this);
96
+  }
97
+
98
+  @Override
99
+  protected void onDetachedFromWindow() {
100
+    super.onDetachedFromWindow();
101
+
102
+    getRootView().getViewTreeObserver().removeOnGlobalLayoutListener(this);
103
+  }
76
 
104
 
77
-    Assertions.assertNotNull(mInsetsChangeListener).onInsetsChange(this, getSafeAreaInsets());
105
+  @Override
106
+  public void onGlobalLayout() {
107
+    EdgeInsets edgeInsets = getSafeAreaInsets();
108
+    if (mLastInsets == null || !mLastInsets.equalsToEdgeInsets(edgeInsets)) {
109
+      Assertions.assertNotNull(mInsetsChangeListener).onInsetsChange(this, edgeInsets);
110
+      mLastInsets = edgeInsets;
111
+    }
78
   }
112
   }
79
 
113
 
80
   public void setOnInsetsChangeListener(OnInsetsChangeListener listener) {
114
   public void setOnInsetsChangeListener(OnInsetsChangeListener listener) {

+ 87
- 45
example/App.tsx View File

8
 
8
 
9
 import * as React from 'react';
9
 import * as React from 'react';
10
 import { StyleSheet, ScrollView, View, Text, StatusBar } from 'react-native';
10
 import { StyleSheet, ScrollView, View, Text, StatusBar } from 'react-native';
11
-import SafeAreaView from '..';
12
 
11
 
13
 import {
12
 import {
14
   Header,
13
   Header,
17
   DebugInstructions,
16
   DebugInstructions,
18
   ReloadInstructions,
17
   ReloadInstructions,
19
 } from 'react-native/Libraries/NewAppScreen';
18
 } from 'react-native/Libraries/NewAppScreen';
19
+import { SafeAreaProvider, useSafeArea } from '..';
20
+
21
+type ForceInsets = 'always' | 'never';
22
+
23
+const CompatSafeAreaView = ({
24
+  forceInsets = {},
25
+  children,
26
+}: {
27
+  forceInsets?: {
28
+    top?: ForceInsets;
29
+    right?: ForceInsets;
30
+    bottom?: ForceInsets;
31
+    left?: ForceInsets;
32
+  };
33
+  children?: React.ReactNode;
34
+}) => {
35
+  const insets = useSafeArea();
36
+  const top = forceInsets.top === 'never' ? 0 : insets.top;
37
+  const right = forceInsets.right === 'never' ? 0 : insets.right;
38
+  const bottom = forceInsets.bottom === 'never' ? 0 : insets.bottom;
39
+  const left = forceInsets.left === 'never' ? 0 : insets.left;
40
+  return (
41
+    <View
42
+      style={{
43
+        paddingTop: top,
44
+        paddingRight: right,
45
+        paddingBottom: bottom,
46
+        paddingLeft: left,
47
+      }}
48
+    >
49
+      {children}
50
+    </View>
51
+  );
52
+};
20
 
53
 
21
 const App = () => {
54
 const App = () => {
55
+  const insets = useSafeArea();
22
   return (
56
   return (
23
     <>
57
     <>
24
       <StatusBar
58
       <StatusBar
25
         barStyle="dark-content"
59
         barStyle="dark-content"
26
-        translucent
27
-        backgroundColor="rgba(0, 0, 0, 0.3)"
60
+        translucent={false}
61
+        backgroundColor="transparent"
28
       />
62
       />
29
-      <SafeAreaView>
30
-        {insets => (
31
-          <ScrollView
32
-            contentInsetAdjustmentBehavior="automatic"
33
-            contentContainerStyle={{
34
-              paddingTop: insets.top,
35
-              paddingBottom: insets.bottom,
36
-            }}
37
-            style={styles.scrollView}
38
-          >
39
-            <Header />
40
-            <View style={styles.body}>
41
-              <View style={styles.sectionContainer}>
42
-                <Text style={styles.sectionTitle}>Step One</Text>
43
-                <Text style={styles.sectionDescription}>
44
-                  Edit <Text style={styles.highlight}>App.js</Text> to change
45
-                  this screen and then come back to see your edits.
46
-                </Text>
47
-              </View>
48
-              <View style={styles.sectionContainer}>
49
-                <Text style={styles.sectionTitle}>See Your Changes</Text>
50
-                <Text style={styles.sectionDescription}>
51
-                  <ReloadInstructions />
52
-                </Text>
53
-              </View>
54
-              <View style={styles.sectionContainer}>
55
-                <Text style={styles.sectionTitle}>Debug</Text>
56
-                <Text style={styles.sectionDescription}>
57
-                  <DebugInstructions />
58
-                </Text>
59
-              </View>
60
-              <View style={styles.sectionContainer}>
61
-                <Text style={styles.sectionTitle}>Learn More</Text>
62
-                <Text style={styles.sectionDescription}>
63
-                  Read the docs to discover what to do next:
64
-                </Text>
65
-              </View>
66
-              <LearnMoreLinks />
63
+      <ScrollView
64
+        contentInsetAdjustmentBehavior="never"
65
+        style={styles.scrollView}
66
+      >
67
+        <CompatSafeAreaView>
68
+          <Header />
69
+          <View style={styles.body}>
70
+            <View style={styles.sectionContainer}>
71
+              <Text style={styles.sectionTitle}>Step One</Text>
72
+              <Text style={styles.sectionDescription}>
73
+                Edit <Text style={styles.highlight}>App.js</Text> to change this
74
+                screen and then come back to see your edits.
75
+              </Text>
67
             </View>
76
             </View>
68
-          </ScrollView>
69
-        )}
70
-      </SafeAreaView>
77
+            <View style={styles.sectionContainer}>
78
+              <Text style={styles.sectionTitle}>See Your Changes</Text>
79
+              <Text style={styles.sectionDescription}>
80
+                <ReloadInstructions />
81
+              </Text>
82
+            </View>
83
+            <View style={styles.sectionContainer}>
84
+              <Text style={styles.sectionTitle}>Debug</Text>
85
+              <Text style={styles.sectionDescription}>
86
+                <DebugInstructions />
87
+              </Text>
88
+            </View>
89
+            <View style={styles.sectionContainer}>
90
+              <Text style={styles.sectionTitle}>Learn More</Text>
91
+              <Text style={styles.sectionDescription}>
92
+                Read the docs to discover what to do next:
93
+              </Text>
94
+            </View>
95
+            <LearnMoreLinks />
96
+          </View>
97
+        </CompatSafeAreaView>
98
+      </ScrollView>
99
+      <View
100
+        style={{
101
+          position: 'absolute',
102
+          top: 0,
103
+          right: 0,
104
+          left: 0,
105
+          height: insets.top,
106
+          backgroundColor: 'rgba(0, 0, 0, 0.3)',
107
+        }}
108
+      />
71
     </>
109
     </>
72
   );
110
   );
73
 };
111
 };
111
   },
149
   },
112
 });
150
 });
113
 
151
 
114
-export default App;
152
+export default () => (
153
+  <SafeAreaProvider>
154
+    <App />
155
+  </SafeAreaProvider>
156
+);

+ 18
- 0
ios/SafeAreaView/RNCSafeAreaView.m View File

5
 #import <React/RCTBridge.h>
5
 #import <React/RCTBridge.h>
6
 #import <React/RCTUIManager.h>
6
 #import <React/RCTUIManager.h>
7
 
7
 
8
+static BOOL UIEdgeInsetsEqualToEdgeInsetsWithThreshold(UIEdgeInsets insets1, UIEdgeInsets insets2, CGFloat threshold) {
9
+  return
10
+  ABS(insets1.left - insets2.left) <= threshold &&
11
+  ABS(insets1.right - insets2.right) <= threshold &&
12
+  ABS(insets1.top - insets2.top) <= threshold &&
13
+  ABS(insets1.bottom - insets2.bottom) <= threshold;
14
+}
15
+
8
 @implementation RNCSafeAreaView
16
 @implementation RNCSafeAreaView
17
+{
18
+  UIEdgeInsets _currentSafeAreaInsets;
19
+}
9
 
20
 
10
 - (BOOL)isSupportedByOS
21
 - (BOOL)isSupportedByOS
11
 {
22
 {
57
 - (void)invalidateSafeAreaInsets
68
 - (void)invalidateSafeAreaInsets
58
 {
69
 {
59
   UIEdgeInsets safeAreaInsets = [self realOrEmulateSafeAreaInsets];
70
   UIEdgeInsets safeAreaInsets = [self realOrEmulateSafeAreaInsets];
71
+
72
+  if (UIEdgeInsetsEqualToEdgeInsetsWithThreshold(safeAreaInsets, _currentSafeAreaInsets, 1.0 / RCTScreenScale())) {
73
+    return;
74
+  }
75
+
76
+  _currentSafeAreaInsets = safeAreaInsets;
77
+
60
   self.onInsetsChange(@{
78
   self.onInsetsChange(@{
61
     @"insets": @{
79
     @"insets": @{
62
       @"top": @(safeAreaInsets.top),
80
       @"top": @(safeAreaInsets.top),

+ 20
- 11
src/index.tsx View File

2
 import {
2
 import {
3
   requireNativeComponent,
3
   requireNativeComponent,
4
   NativeSyntheticEvent,
4
   NativeSyntheticEvent,
5
-  ViewStyle,
6
   StyleSheet,
5
   StyleSheet,
7
 } from 'react-native';
6
 } from 'react-native';
8
 
7
 
15
   left: number;
14
   left: number;
16
 }
15
 }
17
 
16
 
17
+const SafeAreaContext = React.createContext<EdgeInsets | null>(null);
18
+
18
 export interface SafeAreaViewProps {
19
 export interface SafeAreaViewProps {
19
-  children: (insets: EdgeInsets) => React.ReactNode;
20
-  style?: ViewStyle;
20
+  children?: React.ReactNode;
21
 }
21
 }
22
 
22
 
23
-export default function SafeAreaView({ children, style }: SafeAreaViewProps) {
23
+export function SafeAreaProvider({ children }: SafeAreaViewProps) {
24
   const [insets, setInsets] = React.useState<EdgeInsets | null>(null);
24
   const [insets, setInsets] = React.useState<EdgeInsets | null>(null);
25
   const onInsetsChange = React.useCallback(
25
   const onInsetsChange = React.useCallback(
26
     (event: NativeSyntheticEvent<{ insets: EdgeInsets }>) => {
26
     (event: NativeSyntheticEvent<{ insets: EdgeInsets }>) => {
29
     [],
29
     [],
30
   );
30
   );
31
 
31
 
32
-  console.warn(insets);
33
-
34
   return (
32
   return (
35
-    <NativeSafeAreaView
36
-      style={[styles.fill, style]}
37
-      onInsetsChange={onInsetsChange}
38
-    >
39
-      {insets !== null ? children(insets) : null}
33
+    <NativeSafeAreaView style={styles.fill} onInsetsChange={onInsetsChange}>
34
+      {insets !== null ? (
35
+        <SafeAreaContext.Provider value={insets}>
36
+          {children}
37
+        </SafeAreaContext.Provider>
38
+      ) : null}
40
     </NativeSafeAreaView>
39
     </NativeSafeAreaView>
41
   );
40
   );
42
 }
41
 }
44
 const styles = StyleSheet.create({
43
 const styles = StyleSheet.create({
45
   fill: { flex: 1 },
44
   fill: { flex: 1 },
46
 });
45
 });
46
+
47
+export function useSafeArea(): EdgeInsets {
48
+  const safeArea = React.useContext(SafeAreaContext);
49
+  if (safeArea == null) {
50
+    throw new Error(
51
+      'No safe area value available. Make sure you are rendering `<SafeAreaProvider>` at the top of your app.',
52
+    );
53
+  }
54
+  return safeArea;
55
+}