瀏覽代碼

Finalize initial draft implementation for ios

Janic Duplessis 5 年之前
父節點
當前提交
894b6b7b40
共有 10 個文件被更改,包括 198 次插入380 次删除
  1. 2
    18
      .eslintrc.js
  2. 0
    114
      example/App.js
  3. 110
    0
      example/App.tsx
  4. 2
    6
      example/index.js
  5. 7
    15
      ios/SafeAreaView/RNCSafeAreaView.m
  6. 9
    0
      ios/SafeAreaView/RNCSafeAreaViewManager.h
  7. 23
    0
      ios/SafeAreaView/RNCSafeAreaViewManager.m
  8. 1
    1
      package.json
  9. 0
    226
      src/index.ts
  10. 44
    0
      src/index.tsx

+ 2
- 18
.eslintrc.js 查看文件

@@ -21,26 +21,10 @@ module.exports = {
21 21
         typescriptEslintPrettier.rules,
22 22
         {
23 23
           '@typescript-eslint/explicit-member-accessibility': 'off',
24
+          '@typescript-eslint/explicit-function-return-type': 'off',
25
+          '@typescript-eslint/no-use-before-define': 'off',
24 26
         },
25 27
       ),
26 28
     },
27
-    {
28
-      files: ['example/**/*.ts', 'example/**/*.tsx'],
29
-      rules: {
30
-        // Turn off rules which are useless and annoying for the example files files
31
-        '@typescript-eslint/explicit-function-return-type': 'off',
32
-        'react-native/no-inline-styles': 'off',
33
-      },
34
-    },
35
-    {
36
-      files: ['**/__tests__/**/*.ts', '**/*.spec.ts'],
37
-      env: {
38
-        jest: true,
39
-      },
40
-      rules: {
41
-        // Turn off rules which are useless and annoying for unit test files
42
-        '@typescript-eslint/explicit-function-return-type': 'off',
43
-      },
44
-    },
45 29
   ],
46 30
 };

+ 0
- 114
example/App.js 查看文件

@@ -1,114 +0,0 @@
1
-/**
2
- * Sample React Native App
3
- * https://github.com/facebook/react-native
4
- *
5
- * @format
6
- * @flow
7
- */
8
-
9
-import React, {Fragment} from 'react';
10
-import {
11
-  SafeAreaView,
12
-  StyleSheet,
13
-  ScrollView,
14
-  View,
15
-  Text,
16
-  StatusBar,
17
-} from 'react-native';
18
-
19
-import {
20
-  Header,
21
-  LearnMoreLinks,
22
-  Colors,
23
-  DebugInstructions,
24
-  ReloadInstructions,
25
-} from 'react-native/Libraries/NewAppScreen';
26
-
27
-const App = () => {
28
-  return (
29
-    <Fragment>
30
-      <StatusBar barStyle="dark-content" />
31
-      <SafeAreaView>
32
-        <ScrollView
33
-          contentInsetAdjustmentBehavior="automatic"
34
-          style={styles.scrollView}>
35
-          <Header />
36
-          {global.HermesInternal == null ? null : (
37
-            <View style={styles.engine}>
38
-              <Text style={styles.footer}>Engine: Hermes</Text>
39
-            </View>
40
-          )}
41
-          <View style={styles.body}>
42
-            <View style={styles.sectionContainer}>
43
-              <Text style={styles.sectionTitle}>Step One</Text>
44
-              <Text style={styles.sectionDescription}>
45
-                Edit <Text style={styles.highlight}>App.js</Text> to change this
46
-                screen and then come back to see your edits.
47
-              </Text>
48
-            </View>
49
-            <View style={styles.sectionContainer}>
50
-              <Text style={styles.sectionTitle}>See Your Changes</Text>
51
-              <Text style={styles.sectionDescription}>
52
-                <ReloadInstructions />
53
-              </Text>
54
-            </View>
55
-            <View style={styles.sectionContainer}>
56
-              <Text style={styles.sectionTitle}>Debug</Text>
57
-              <Text style={styles.sectionDescription}>
58
-                <DebugInstructions />
59
-              </Text>
60
-            </View>
61
-            <View style={styles.sectionContainer}>
62
-              <Text style={styles.sectionTitle}>Learn More</Text>
63
-              <Text style={styles.sectionDescription}>
64
-                Read the docs to discover what to do next:
65
-              </Text>
66
-            </View>
67
-            <LearnMoreLinks />
68
-          </View>
69
-        </ScrollView>
70
-      </SafeAreaView>
71
-    </Fragment>
72
-  );
73
-};
74
-
75
-const styles = StyleSheet.create({
76
-  scrollView: {
77
-    backgroundColor: Colors.lighter,
78
-  },
79
-  engine: {
80
-    position: 'absolute',
81
-    right: 0,
82
-  },
83
-  body: {
84
-    backgroundColor: Colors.white,
85
-  },
86
-  sectionContainer: {
87
-    marginTop: 32,
88
-    paddingHorizontal: 24,
89
-  },
90
-  sectionTitle: {
91
-    fontSize: 24,
92
-    fontWeight: '600',
93
-    color: Colors.black,
94
-  },
95
-  sectionDescription: {
96
-    marginTop: 8,
97
-    fontSize: 18,
98
-    fontWeight: '400',
99
-    color: Colors.dark,
100
-  },
101
-  highlight: {
102
-    fontWeight: '700',
103
-  },
104
-  footer: {
105
-    color: Colors.dark,
106
-    fontSize: 12,
107
-    fontWeight: '600',
108
-    padding: 4,
109
-    paddingRight: 12,
110
-    textAlign: 'right',
111
-  },
112
-});
113
-
114
-export default App;

+ 110
- 0
example/App.tsx 查看文件

@@ -0,0 +1,110 @@
1
+/**
2
+ * Sample React Native App
3
+ * https://github.com/facebook/react-native
4
+ *
5
+ * @format
6
+ * @flow
7
+ */
8
+
9
+import * as React from 'react';
10
+import { StyleSheet, ScrollView, View, Text, StatusBar } from 'react-native';
11
+import SafeAreaView from '..';
12
+
13
+import {
14
+  Header,
15
+  LearnMoreLinks,
16
+  Colors,
17
+  DebugInstructions,
18
+  ReloadInstructions,
19
+} from 'react-native/Libraries/NewAppScreen';
20
+
21
+const App = () => {
22
+  return (
23
+    <>
24
+      <StatusBar barStyle="dark-content" />
25
+      <SafeAreaView>
26
+        {insets => (
27
+          <ScrollView
28
+            contentInsetAdjustmentBehavior="automatic"
29
+            contentContainerStyle={{
30
+              paddingTop: insets.top,
31
+              paddingBottom: insets.bottom,
32
+            }}
33
+            style={styles.scrollView}
34
+          >
35
+            <Header />
36
+            <View style={styles.body}>
37
+              <View style={styles.sectionContainer}>
38
+                <Text style={styles.sectionTitle}>Step One</Text>
39
+                <Text style={styles.sectionDescription}>
40
+                  Edit <Text style={styles.highlight}>App.js</Text> to change
41
+                  this screen and then come back to see your edits.
42
+                </Text>
43
+              </View>
44
+              <View style={styles.sectionContainer}>
45
+                <Text style={styles.sectionTitle}>See Your Changes</Text>
46
+                <Text style={styles.sectionDescription}>
47
+                  <ReloadInstructions />
48
+                </Text>
49
+              </View>
50
+              <View style={styles.sectionContainer}>
51
+                <Text style={styles.sectionTitle}>Debug</Text>
52
+                <Text style={styles.sectionDescription}>
53
+                  <DebugInstructions />
54
+                </Text>
55
+              </View>
56
+              <View style={styles.sectionContainer}>
57
+                <Text style={styles.sectionTitle}>Learn More</Text>
58
+                <Text style={styles.sectionDescription}>
59
+                  Read the docs to discover what to do next:
60
+                </Text>
61
+              </View>
62
+              <LearnMoreLinks />
63
+            </View>
64
+          </ScrollView>
65
+        )}
66
+      </SafeAreaView>
67
+    </>
68
+  );
69
+};
70
+
71
+const styles = StyleSheet.create({
72
+  scrollView: {
73
+    backgroundColor: Colors.lighter,
74
+  },
75
+  engine: {
76
+    position: 'absolute',
77
+    right: 0,
78
+  },
79
+  body: {
80
+    backgroundColor: Colors.white,
81
+  },
82
+  sectionContainer: {
83
+    marginTop: 32,
84
+    paddingHorizontal: 24,
85
+  },
86
+  sectionTitle: {
87
+    fontSize: 24,
88
+    fontWeight: '600',
89
+    color: Colors.black,
90
+  },
91
+  sectionDescription: {
92
+    marginTop: 8,
93
+    fontSize: 18,
94
+    fontWeight: '400',
95
+    color: Colors.dark,
96
+  },
97
+  highlight: {
98
+    fontWeight: '700',
99
+  },
100
+  footer: {
101
+    color: Colors.dark,
102
+    fontSize: 12,
103
+    fontWeight: '600',
104
+    padding: 4,
105
+    paddingRight: 12,
106
+    textAlign: 'right',
107
+  },
108
+});
109
+
110
+export default App;

+ 2
- 6
example/index.js 查看文件

@@ -1,9 +1,5 @@
1
-/**
2
- * @format
3
- */
4
-
5
-import {AppRegistry} from 'react-native';
1
+import { AppRegistry } from 'react-native';
6 2
 import App from './App';
7
-import {name as appName} from './app.json';
3
+import { name as appName } from './app.json';
8 4
 
9 5
 AppRegistry.registerComponent(appName, () => App);

+ 7
- 15
ios/SafeAreaView/RNCSafeAreaView.m 查看文件

@@ -5,12 +5,7 @@
5 5
 #import <React/RCTBridge.h>
6 6
 #import <React/RCTUIManager.h>
7 7
 
8
-@implementation RNCSafeAreaView {
9
-  UIEdgeInsets _currentSafeAreaInsets;
10
-}
11
-
12
-RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)decoder)
13
-RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame)
8
+@implementation RNCSafeAreaView
14 9
 
15 10
 - (BOOL)isSupportedByOS
16 11
 {
@@ -54,14 +49,6 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame)
54 49
   return safeAreaInsets;
55 50
 }
56 51
 
57
-static BOOL UIEdgeInsetsEqualToEdgeInsetsWithThreshold(UIEdgeInsets insets1, UIEdgeInsets insets2, CGFloat threshold) {
58
-  return
59
-  ABS(insets1.left - insets2.left) <= threshold &&
60
-  ABS(insets1.right - insets2.right) <= threshold &&
61
-  ABS(insets1.top - insets2.top) <= threshold &&
62
-  ABS(insets1.bottom - insets2.bottom) <= threshold;
63
-}
64
-
65 52
 - (void)safeAreaInsetsDidChange
66 53
 {
67 54
   [self invalidateSafeAreaInsets];
@@ -71,7 +58,12 @@ static BOOL UIEdgeInsetsEqualToEdgeInsetsWithThreshold(UIEdgeInsets insets1, UIE
71 58
 {
72 59
   UIEdgeInsets safeAreaInsets = [self realOrEmulateSafeAreaInsets];
73 60
   self.onInsetsChange(@{
74
-    @"top": @(safeAreaInsets.top),
61
+    @"insets": @{
62
+      @"top": @(safeAreaInsets.top),
63
+      @"right": @(safeAreaInsets.right),
64
+      @"bottom": @(safeAreaInsets.bottom),
65
+      @"left": @(safeAreaInsets.left),
66
+    }
75 67
   });
76 68
 }
77 69
 

+ 9
- 0
ios/SafeAreaView/RNCSafeAreaViewManager.h 查看文件

@@ -0,0 +1,9 @@
1
+#import <React/RCTViewManager.h>
2
+
3
+NS_ASSUME_NONNULL_BEGIN
4
+
5
+@interface RNCSafeAreaViewManager : RCTViewManager
6
+
7
+@end
8
+
9
+NS_ASSUME_NONNULL_END

+ 23
- 0
ios/SafeAreaView/RNCSafeAreaViewManager.m 查看文件

@@ -0,0 +1,23 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+#import "RNCSafeAreaViewManager.h"
9
+
10
+#import "RNCSafeAreaView.h"
11
+
12
+@implementation RNCSafeAreaViewManager
13
+
14
+RCT_EXPORT_MODULE(RNCSafeAreaView)
15
+
16
+RCT_EXPORT_VIEW_PROPERTY(onInsetsChange, RCTBubblingEventBlock)
17
+
18
+- (UIView *)view
19
+{
20
+  return [RNCSafeAreaView new];
21
+}
22
+
23
+@end

+ 1
- 1
package.json 查看文件

@@ -2,7 +2,7 @@
2 2
   "name": "@th3rdwave/safe-area-view",
3 3
   "version": "0.1.0",
4 4
   "description": "A more flexible <SafeAreaView>",
5
-  "main": "src/index.ts",
5
+  "main": "src/index.tsx",
6 6
   "files": [
7 7
     "/android",
8 8
     "!/android/build",

+ 0
- 226
src/index.ts 查看文件

@@ -1,226 +0,0 @@
1
-/**
2
- * Copyright (c) Facebook, Inc. and its affiliates.
3
- *
4
- * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- *
7
- * @format
8
- */
9
-
10
-import {useState, useEffect} from 'react';
11
-import DeprecatedUtils from './internal/deprecatedUtils';
12
-import DeprecatedState from './internal/deprecatedState';
13
-import * as DeprecatedTypes from './internal/deprecatedTypes';
14
-import State from './internal/state';
15
-import * as Types from './internal/types';
16
-
17
-// Call the setup methods of the two state modules right away
18
-State.setup();
19
-DeprecatedState.setup();
20
-
21
-const _isConnectedListeners = new Map<
22
-  DeprecatedTypes.IsConnectedHandler,
23
-  /// @ts-ignore Typescript des not like the trailing comma that Prettier insists upon
24
-  Types.NetInfoChangeHandler
25
->();
26
-
27
-/**
28
- * Returns a `Promise` that resolves to a `NetInfoState` object.
29
- *
30
- * @returns A Promise which contains the current connection state.
31
- */
32
-export function fetch(): Promise<Types.NetInfoState> {
33
-  return State.latest();
34
-}
35
-
36
-/**
37
- * Subscribe to connection information. The callback is called with a parameter of type
38
- * [`NetInfoState`](README.md#netinfostate) whenever the connection state changes. Your listener
39
- * will be called with the latest information soon after you subscribe and then with any
40
- * subsequent changes afterwards. You should not assume that the listener is called in the same
41
- * way across devices or platforms.
42
- *
43
- * @param listener The listener which is called when the network state changes.
44
- *
45
- * @returns An ofunction which can be called to unsubscribe.
46
- */
47
-export function addEventListener(
48
-  listener: Types.NetInfoChangeHandler,
49
-): Types.NetInfoSubscription;
50
-
51
-/**
52
- * Deprecated network state listener. You should remove the event name and change your handler to
53
- * use the new state shape.
54
- *
55
- * @deprecated
56
- *
57
- * @param type The event type.
58
- * @param deprecatedHandler The listener.
59
- *
60
- * @returns An object with a remove function which can be called to unsubscribe.
61
- */
62
-export function addEventListener(
63
-  type: string,
64
-  deprecatedHandler: DeprecatedTypes.ChangeHandler,
65
-): DeprecatedTypes.Subscription;
66
-
67
-// Implementation of the overloaded methods above
68
-export function addEventListener(
69
-  listenerOrType: Types.NetInfoChangeHandler | string,
70
-  deprecatedHandler: DeprecatedTypes.ChangeHandler | undefined = undefined,
71
-): Types.NetInfoSubscription | DeprecatedTypes.Subscription {
72
-  if (typeof listenerOrType === 'string') {
73
-    DeprecatedUtils.warnOnce();
74
-
75
-    if (
76
-      listenerOrType === DeprecatedTypes.CHANGE_EVENT_NAME &&
77
-      deprecatedHandler
78
-    ) {
79
-      DeprecatedState.add(deprecatedHandler);
80
-      return {
81
-        remove: (): void => {
82
-          DeprecatedState.remove(deprecatedHandler);
83
-        },
84
-      };
85
-    } else {
86
-      return {
87
-        remove: (): void => {},
88
-      };
89
-    }
90
-  } else {
91
-    const listener = listenerOrType;
92
-    State.add(listener);
93
-    return (): void => {
94
-      State.remove(listener);
95
-    };
96
-  }
97
-}
98
-
99
-/**
100
- * A React Hook which updates when the connection state changes.
101
- *
102
- * @returns The connection state.
103
- */
104
-export function useNetInfo(): Types.NetInfoState {
105
-  const [netInfo, setNetInfo] = useState<Types.NetInfoState>({
106
-    type: Types.NetInfoStateType.unknown,
107
-    isConnected: false,
108
-    isInternetReachable: false,
109
-    details: null,
110
-  });
111
-
112
-  useEffect((): (() => void) => {
113
-    return addEventListener(setNetInfo);
114
-  }, []);
115
-
116
-  return netInfo;
117
-}
118
-
119
-/**
120
- * Deprecated method to remove the listener. You should upgrade to the new API.
121
- *
122
- * @deprecated
123
- *
124
- * @param type The event type.
125
- * @param handler The event listener.
126
- */
127
-export function removeEventListener(
128
-  type: string,
129
-  handler: DeprecatedTypes.ChangeHandler,
130
-): void {
131
-  DeprecatedUtils.warnOnce();
132
-
133
-  if (type === DeprecatedTypes.CHANGE_EVENT_NAME) {
134
-    DeprecatedState.remove(handler);
135
-  }
136
-}
137
-
138
-/**
139
- * Deprecated method to get the current state. You should upgrade to the new `fetch` method and
140
- * handle the new state type.
141
- *
142
- * @deprecated
143
- */
144
-export function getConnectionInfo(): Promise<DeprecatedTypes.NetInfoData> {
145
-  DeprecatedUtils.warnOnce();
146
-  return DeprecatedState.latest();
147
-}
148
-
149
-/**
150
- * Deprecated method to tell if the current connection is "expensive". Only available on Android.
151
- * You should now call the `fetch` method and look at the `details.isConnectionExpensive` property.
152
- *
153
- * @deprecated
154
- */
155
-export function isConnectionExpensive(): Promise<boolean> {
156
-  DeprecatedUtils.warnOnce();
157
-  return State.latest().then(DeprecatedUtils.isConnectionExpensive);
158
-}
159
-
160
-export const isConnected = {
161
-  /**
162
-   * Deprecated method to listen for changes to the connected boolean. You should now use the
163
-   * normal `addEventListener` method and look at the `isConnected` property.
164
-   *
165
-   * @deprecated
166
-   */
167
-  addEventListener: (
168
-    eventName: string,
169
-    handler: DeprecatedTypes.IsConnectedHandler,
170
-  ): DeprecatedTypes.Subscription => {
171
-    if (eventName !== DeprecatedTypes.CHANGE_EVENT_NAME) {
172
-      return {remove: (): void => {}};
173
-    }
174
-
175
-    const listener = (state: Types.NetInfoState): void => {
176
-      handler(DeprecatedUtils.isConnected(state));
177
-    };
178
-
179
-    _isConnectedListeners.set(handler, listener);
180
-    State.add(listener);
181
-
182
-    return {
183
-      remove: (): void => {
184
-        State.remove(listener);
185
-      },
186
-    };
187
-  },
188
-
189
-  /**
190
-   * Deprecated method to stop listening for changes to the connected boolean. You should now use
191
-   * the normal `addEventListener` method and look at the `isConnected` property.
192
-   *
193
-   * @deprecated
194
-   */
195
-  removeEventListener: (
196
-    _eventName: string,
197
-    handler: DeprecatedTypes.IsConnectedHandler,
198
-  ): void => {
199
-    const listener = _isConnectedListeners.get(handler);
200
-    listener && State.remove(listener);
201
-    _isConnectedListeners.delete(handler);
202
-  },
203
-
204
-  /**
205
-   * Deprecated method to get the current is connected boolean. You should now use the normal
206
-   * `fetch` method and look at the `isConnected` property.
207
-   *
208
-   * @deprecated
209
-   */
210
-  fetch: (): Promise<boolean> => {
211
-    return State.latest().then(DeprecatedUtils.isConnected);
212
-  },
213
-};
214
-
215
-export * from './internal/types';
216
-export * from './internal/deprecatedTypes';
217
-
218
-export default {
219
-  fetch,
220
-  addEventListener,
221
-  useNetInfo,
222
-  removeEventListener,
223
-  getConnectionInfo,
224
-  isConnectionExpensive,
225
-  isConnected,
226
-};

+ 44
- 0
src/index.tsx 查看文件

@@ -0,0 +1,44 @@
1
+import * as React from 'react';
2
+import {
3
+  requireNativeComponent,
4
+  NativeSyntheticEvent,
5
+  ViewStyle,
6
+  StyleSheet,
7
+} from 'react-native';
8
+
9
+const NativeSafeAreaView = requireNativeComponent('RNCSafeAreaView');
10
+
11
+export interface EdgeInsets {
12
+  top: number;
13
+  right: number;
14
+  bottom: number;
15
+  left: number;
16
+}
17
+
18
+export interface SafeAreaViewProps {
19
+  children: (insets: EdgeInsets) => React.ReactNode;
20
+  style?: ViewStyle;
21
+}
22
+
23
+export default function SafeAreaView({ children, style }: SafeAreaViewProps) {
24
+  const [insets, setInsets] = React.useState<EdgeInsets | null>(null);
25
+  const onInsetsChange = React.useCallback(
26
+    (event: NativeSyntheticEvent<{ insets: EdgeInsets }>) => {
27
+      setInsets(event.nativeEvent.insets);
28
+    },
29
+    [],
30
+  );
31
+
32
+  return (
33
+    <NativeSafeAreaView
34
+      style={[styles.fill, style]}
35
+      onInsetsChange={onInsetsChange}
36
+    >
37
+      {insets !== null ? children(insets) : null}
38
+    </NativeSafeAreaView>
39
+  );
40
+}
41
+
42
+const styles = StyleSheet.create({
43
+  fill: { flex: 1 },
44
+});