Browse Source

Update basic screen components to use TypeScript

Arnaud Vergnet 1 year ago
parent
commit
f43dc55735

+ 3
- 1
src/components/Overrides/CustomHeaderButton.tsx View File

@@ -39,7 +39,9 @@ const MaterialHeaderButton = (props: HeaderButtonProps) => {
39 39
   );
40 40
 };
41 41
 
42
-const MaterialHeaderButtons = (props: HeaderButtonsProps) => {
42
+const MaterialHeaderButtons = (
43
+  props: HeaderButtonsProps & {children?: React.ReactNode},
44
+) => {
43 45
   return (
44 46
     <HeaderButtons {...props} HeaderButtonComponent={MaterialHeaderButton} />
45 47
   );

src/components/Screens/WebSectionList.js → src/components/Screens/WebSectionList.tsx View File

@@ -17,12 +17,15 @@
17 17
  * along with Campus INSAT.  If not, see <https://www.gnu.org/licenses/>.
18 18
  */
19 19
 
20
-// @flow
21
-
22 20
 import * as React from 'react';
23 21
 import i18n from 'i18n-js';
24 22
 import {Snackbar} from 'react-native-paper';
25
-import {RefreshControl, View} from 'react-native';
23
+import {
24
+  NativeSyntheticEvent,
25
+  RefreshControl,
26
+  SectionListData,
27
+  View,
28
+} from 'react-native';
26 29
 import * as Animatable from 'react-native-animatable';
27 30
 import {Collapsible} from 'react-navigation-collapsible';
28 31
 import {StackNavigationProp} from '@react-navigation/stack';
@@ -32,42 +35,43 @@ import withCollapsible from '../../utils/withCollapsible';
32 35
 import CustomTabBar from '../Tabbar/CustomTabBar';
33 36
 import {ERROR_TYPE, readData} from '../../utils/WebData';
34 37
 import CollapsibleSectionList from '../Collapsible/CollapsibleSectionList';
35
-import type {ApiGenericDataType} from '../../utils/WebData';
36 38
 
37
-export type SectionListDataType<T> = Array<{
38
-  title: string,
39
-  data: Array<T>,
40
-  keyExtractor?: (T) => string,
39
+export type SectionListDataType<ItemT> = Array<{
40
+  title: string;
41
+  data: Array<ItemT>;
42
+  keyExtractor?: (data: ItemT) => string;
41 43
 }>;
42 44
 
43
-type PropsType<T> = {
44
-  navigation: StackNavigationProp,
45
-  fetchUrl: string,
46
-  autoRefreshTime: number,
47
-  refreshOnFocus: boolean,
48
-  renderItem: (data: {item: T}) => React.Node,
45
+type PropsType<ItemT, RawData> = {
46
+  navigation: StackNavigationProp<any>;
47
+  fetchUrl: string;
48
+  autoRefreshTime: number;
49
+  refreshOnFocus: boolean;
50
+  renderItem: (data: {item: ItemT}) => React.ReactNode;
49 51
   createDataset: (
50
-    data: ApiGenericDataType | null,
52
+    data: RawData | null,
51 53
     isLoading?: boolean,
52
-  ) => SectionListDataType<T>,
53
-  onScroll: (event: SyntheticEvent<EventTarget>) => void,
54
-  collapsibleStack: Collapsible,
54
+  ) => SectionListDataType<ItemT>;
55
+  onScroll: (event: NativeSyntheticEvent<EventTarget>) => void;
56
+  collapsibleStack: Collapsible;
55 57
 
56
-  showError?: boolean,
57
-  itemHeight?: number | null,
58
-  updateData?: number,
59
-  renderListHeaderComponent?: (data: ApiGenericDataType | null) => React.Node,
58
+  showError?: boolean;
59
+  itemHeight?: number | null;
60
+  updateData?: number;
61
+  renderListHeaderComponent?: (
62
+    data: RawData | null,
63
+  ) => React.ComponentType<any> | React.ReactElement | null;
60 64
   renderSectionHeader?: (
61
-    data: {section: {title: string}},
65
+    data: {section: SectionListData<ItemT>},
62 66
     isLoading?: boolean,
63
-  ) => React.Node,
64
-  stickyHeader?: boolean,
67
+  ) => React.ReactElement | null;
68
+  stickyHeader?: boolean;
65 69
 };
66 70
 
67
-type StateType = {
68
-  refreshing: boolean,
69
-  fetchedData: ApiGenericDataType | null,
70
-  snackbarVisible: boolean,
71
+type StateType<RawData> = {
72
+  refreshing: boolean;
73
+  fetchedData: RawData | null;
74
+  snackbarVisible: boolean;
71 75
 };
72 76
 
73 77
 const MIN_REFRESH_TIME = 5 * 1000;
@@ -78,22 +82,25 @@ const MIN_REFRESH_TIME = 5 * 1000;
78 82
  * This is a pure component, meaning it will only update if a shallow comparison of state and props is different.
79 83
  * To force the component to update, change the value of updateData.
80 84
  */
81
-class WebSectionList<T> extends React.PureComponent<PropsType<T>, StateType> {
85
+class WebSectionList<ItemT, RawData> extends React.PureComponent<
86
+  PropsType<ItemT, RawData>,
87
+  StateType<RawData>
88
+> {
82 89
   static defaultProps = {
83 90
     showError: true,
84 91
     itemHeight: null,
85 92
     updateData: 0,
86
-    renderListHeaderComponent: (): React.Node => null,
87
-    renderSectionHeader: (): React.Node => null,
93
+    renderListHeaderComponent: () => null,
94
+    renderSectionHeader: () => null,
88 95
     stickyHeader: false,
89 96
   };
90 97
 
91
-  refreshInterval: IntervalID;
98
+  refreshInterval: NodeJS.Timeout | undefined;
92 99
 
93
-  lastRefresh: Date | null;
100
+  lastRefresh: Date | undefined;
94 101
 
95
-  constructor() {
96
-    super();
102
+  constructor(props: PropsType<ItemT, RawData>) {
103
+    super(props);
97 104
     this.state = {
98 105
       refreshing: false,
99 106
       fetchedData: null,
@@ -109,7 +116,7 @@ class WebSectionList<T> extends React.PureComponent<PropsType<T>, StateType> {
109 116
     const {navigation} = this.props;
110 117
     navigation.addListener('focus', this.onScreenFocus);
111 118
     navigation.addListener('blur', this.onScreenBlur);
112
-    this.lastRefresh = null;
119
+    this.lastRefresh = undefined;
113 120
     this.onRefresh();
114 121
   }
115 122
 
@@ -121,15 +128,18 @@ class WebSectionList<T> extends React.PureComponent<PropsType<T>, StateType> {
121 128
     if (props.refreshOnFocus && this.lastRefresh) {
122 129
       setTimeout(this.onRefresh, 200);
123 130
     }
124
-    if (props.autoRefreshTime > 0)
131
+    if (props.autoRefreshTime > 0) {
125 132
       this.refreshInterval = setInterval(this.onRefresh, props.autoRefreshTime);
133
+    }
126 134
   };
127 135
 
128 136
   /**
129 137
    * Removes any interval on un-focus
130 138
    */
131 139
   onScreenBlur = () => {
132
-    clearInterval(this.refreshInterval);
140
+    if (this.refreshInterval) {
141
+      clearInterval(this.refreshInterval);
142
+    }
133 143
   };
134 144
 
135 145
   /**
@@ -138,7 +148,7 @@ class WebSectionList<T> extends React.PureComponent<PropsType<T>, StateType> {
138 148
    *
139 149
    * @param fetchedData The newly fetched data
140 150
    */
141
-  onFetchSuccess = (fetchedData: ApiGenericDataType) => {
151
+  onFetchSuccess = (fetchedData: RawData) => {
142 152
     this.setState({
143 153
       fetchedData,
144 154
       refreshing: false,
@@ -167,7 +177,9 @@ class WebSectionList<T> extends React.PureComponent<PropsType<T>, StateType> {
167 177
     if (this.lastRefresh != null) {
168 178
       const last = this.lastRefresh;
169 179
       canRefresh = new Date().getTime() - last.getTime() > MIN_REFRESH_TIME;
170
-    } else canRefresh = true;
180
+    } else {
181
+      canRefresh = true;
182
+    }
171 183
     if (canRefresh) {
172 184
       this.setState({refreshing: true});
173 185
       readData(fetchUrl).then(this.onFetchSuccess).catch(this.onFetchError);
@@ -189,19 +201,18 @@ class WebSectionList<T> extends React.PureComponent<PropsType<T>, StateType> {
189 201
   };
190 202
 
191 203
   getItemLayout = (
192
-    data: T,
204
+    height: number,
205
+    data: Array<SectionListData<ItemT>> | null,
193 206
     index: number,
194
-  ): {length: number, offset: number, index: number} | null => {
195
-    const {itemHeight} = this.props;
196
-    if (itemHeight == null) return null;
207
+  ): {length: number; offset: number; index: number} => {
197 208
     return {
198
-      length: itemHeight,
199
-      offset: itemHeight * index,
209
+      length: height,
210
+      offset: height * index,
200 211
       index,
201 212
     };
202 213
   };
203 214
 
204
-  getRenderSectionHeader = (data: {section: {title: string}}): React.Node => {
215
+  getRenderSectionHeader = (data: {section: SectionListData<ItemT>}) => {
205 216
     const {renderSectionHeader} = this.props;
206 217
     const {refreshing} = this.state;
207 218
     if (renderSectionHeader != null) {
@@ -214,7 +225,7 @@ class WebSectionList<T> extends React.PureComponent<PropsType<T>, StateType> {
214 225
     return null;
215 226
   };
216 227
 
217
-  getRenderItem = (data: {item: T}): React.Node => {
228
+  getRenderItem = (data: {item: ItemT}) => {
218 229
     const {renderItem} = this.props;
219 230
     return (
220 231
       <Animatable.View animation="fadeInUp" duration={500} useNativeDriver>
@@ -223,19 +234,23 @@ class WebSectionList<T> extends React.PureComponent<PropsType<T>, StateType> {
223 234
     );
224 235
   };
225 236
 
226
-  onScroll = (event: SyntheticEvent<EventTarget>) => {
237
+  onScroll = (event: NativeSyntheticEvent<EventTarget>) => {
227 238
     const {onScroll} = this.props;
228
-    if (onScroll != null) onScroll(event);
239
+    if (onScroll != null) {
240
+      onScroll(event);
241
+    }
229 242
   };
230 243
 
231
-  render(): React.Node {
244
+  render() {
232 245
     const {props, state} = this;
233
-    let dataset = [];
246
+    const {itemHeight} = props;
247
+    let dataset: SectionListDataType<ItemT> = [];
234 248
     if (
235 249
       state.fetchedData != null ||
236 250
       (state.fetchedData == null && !props.showError)
237
-    )
251
+    ) {
238 252
       dataset = props.createDataset(state.fetchedData, state.refreshing);
253
+    }
239 254
 
240 255
     const {containerPaddingTop} = props.collapsibleStack;
241 256
     return (
@@ -270,7 +285,11 @@ class WebSectionList<T> extends React.PureComponent<PropsType<T>, StateType> {
270 285
               />
271 286
             )
272 287
           }
273
-          getItemLayout={props.itemHeight != null ? this.getItemLayout : null}
288
+          getItemLayout={
289
+            itemHeight
290
+              ? (data, index) => this.getItemLayout(itemHeight, data, index)
291
+              : undefined
292
+          }
274 293
           onScroll={this.onScroll}
275 294
           hasTab
276 295
         />

src/components/Screens/WebViewScreen.js → src/components/Screens/WebViewScreen.tsx View File

@@ -17,8 +17,6 @@
17 17
  * along with Campus INSAT.  If not, see <https://www.gnu.org/licenses/>.
18 18
  */
19 19
 
20
-// @flow
21
-
22 20
 import * as React from 'react';
23 21
 import WebView from 'react-native-webview';
24 22
 import {
@@ -27,12 +25,17 @@ import {
27 25
   OverflowMenu,
28 26
 } from 'react-navigation-header-buttons';
29 27
 import i18n from 'i18n-js';
30
-import {Animated, BackHandler, Linking} from 'react-native';
28
+import {
29
+  Animated,
30
+  BackHandler,
31
+  Linking,
32
+  NativeScrollEvent,
33
+  NativeSyntheticEvent,
34
+} from 'react-native';
31 35
 import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
32 36
 import {withTheme} from 'react-native-paper';
33 37
 import {StackNavigationProp} from '@react-navigation/stack';
34 38
 import {Collapsible} from 'react-navigation-collapsible';
35
-import type {CustomThemeType} from '../../managers/ThemeManager';
36 39
 import withCollapsible from '../../utils/withCollapsible';
37 40
 import MaterialHeaderButtons, {Item} from '../Overrides/CustomHeaderButton';
38 41
 import {ERROR_TYPE} from '../../utils/WebData';
@@ -40,15 +43,15 @@ import ErrorView from './ErrorView';
40 43
 import BasicLoadingScreen from './BasicLoadingScreen';
41 44
 
42 45
 type PropsType = {
43
-  navigation: StackNavigationProp,
44
-  theme: CustomThemeType,
45
-  url: string,
46
-  collapsibleStack: Collapsible,
47
-  onMessage: (event: {nativeEvent: {data: string}}) => void,
48
-  onScroll: (event: SyntheticEvent<EventTarget>) => void,
49
-  customJS?: string,
50
-  customPaddingFunction?: null | ((padding: number) => string),
51
-  showAdvancedControls?: boolean,
46
+  navigation: StackNavigationProp<any>;
47
+  theme: ReactNativePaper.Theme;
48
+  url: string;
49
+  collapsibleStack: Collapsible;
50
+  onMessage: (event: {nativeEvent: {data: string}}) => void;
51
+  onScroll: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
52
+  customJS?: string;
53
+  customPaddingFunction?: null | ((padding: number) => string);
54
+  showAdvancedControls?: boolean;
52 55
 };
53 56
 
54 57
 const AnimatedWebView = Animated.createAnimatedComponent(WebView);
@@ -67,8 +70,8 @@ class WebViewScreen extends React.PureComponent<PropsType> {
67 70
 
68 71
   canGoBack: boolean;
69 72
 
70
-  constructor() {
71
-    super();
73
+  constructor(props: PropsType) {
74
+    super(props);
72 75
     this.webviewRef = React.createRef();
73 76
     this.canGoBack = false;
74 77
   }
@@ -115,7 +118,7 @@ class WebViewScreen extends React.PureComponent<PropsType> {
115 118
    *
116 119
    * @return {*}
117 120
    */
118
-  getBasicButton = (): React.Node => {
121
+  getBasicButton = () => {
119 122
     return (
120 123
       <MaterialHeaderButtons>
121 124
         <Item
@@ -138,7 +141,7 @@ class WebViewScreen extends React.PureComponent<PropsType> {
138 141
    *
139 142
    * @returns {*}
140 143
    */
141
-  getAdvancedButtons = (): React.Node => {
144
+  getAdvancedButtons = () => {
142 145
     const {props} = this;
143 146
     return (
144 147
       <MaterialHeaderButtons>
@@ -179,7 +182,7 @@ class WebViewScreen extends React.PureComponent<PropsType> {
179 182
    *
180 183
    * @return {*}
181 184
    */
182
-  getRenderLoading = (): React.Node => <BasicLoadingScreen isAbsolute />;
185
+  getRenderLoading = () => <BasicLoadingScreen isAbsolute />;
183 186
 
184 187
   /**
185 188
    * Gets the javascript needed to generate a padding on top of the page
@@ -201,15 +204,21 @@ class WebViewScreen extends React.PureComponent<PropsType> {
201 204
    * Callback to use when refresh button is clicked. Reloads the webview.
202 205
    */
203 206
   onRefreshClicked = () => {
204
-    if (this.webviewRef.current != null) this.webviewRef.current.reload();
207
+    if (this.webviewRef.current != null) {
208
+      this.webviewRef.current.reload();
209
+    }
205 210
   };
206 211
 
207 212
   onGoBackClicked = () => {
208
-    if (this.webviewRef.current != null) this.webviewRef.current.goBack();
213
+    if (this.webviewRef.current != null) {
214
+      this.webviewRef.current.goBack();
215
+    }
209 216
   };
210 217
 
211 218
   onGoForwardClicked = () => {
212
-    if (this.webviewRef.current != null) this.webviewRef.current.goForward();
219
+    if (this.webviewRef.current != null) {
220
+      this.webviewRef.current.goForward();
221
+    }
213 222
   };
214 223
 
215 224
   onOpenClicked = () => {
@@ -217,9 +226,11 @@ class WebViewScreen extends React.PureComponent<PropsType> {
217 226
     Linking.openURL(url);
218 227
   };
219 228
 
220
-  onScroll = (event: SyntheticEvent<EventTarget>) => {
229
+  onScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
221 230
     const {onScroll} = this.props;
222
-    if (onScroll) onScroll(event);
231
+    if (onScroll) {
232
+      onScroll(event);
233
+    }
223 234
   };
224 235
 
225 236
   /**
@@ -228,11 +239,12 @@ class WebViewScreen extends React.PureComponent<PropsType> {
228 239
    * @param script The script to inject
229 240
    */
230 241
   injectJavaScript = (script: string) => {
231
-    if (this.webviewRef.current != null)
242
+    if (this.webviewRef.current != null) {
232 243
       this.webviewRef.current.injectJavaScript(script);
244
+    }
233 245
   };
234 246
 
235
-  render(): React.Node {
247
+  render() {
236 248
     const {props} = this;
237 249
     const {containerPaddingTop, onScrollWithListener} = props.collapsibleStack;
238 250
     return (
@@ -243,7 +255,7 @@ class WebViewScreen extends React.PureComponent<PropsType> {
243 255
         injectedJavaScript={props.customJS}
244 256
         javaScriptEnabled
245 257
         renderLoading={this.getRenderLoading}
246
-        renderError={(): React.Node => (
258
+        renderError={() => (
247 259
           <ErrorView
248 260
             errorCode={ERROR_TYPE.CONNECTION_ERROR}
249 261
             onRefresh={this.onRefreshClicked}
@@ -257,7 +269,7 @@ class WebViewScreen extends React.PureComponent<PropsType> {
257 269
           this.injectJavaScript(this.getJavascriptPadding(containerPaddingTop));
258 270
         }}
259 271
         // Animations
260
-        onScroll={onScrollWithListener(this.onScroll)}
272
+        onScroll={(event) => onScrollWithListener(this.onScroll)(event)}
261 273
       />
262 274
     );
263 275
   }

Loading…
Cancel
Save