|
@@ -10,26 +10,28 @@ import BasicLoadingScreen from "./BasicLoadingScreen";
|
10
|
10
|
import {withCollapsible} from "../../utils/withCollapsible";
|
11
|
11
|
import * as Animatable from 'react-native-animatable';
|
12
|
12
|
import CustomTabBar from "../Tabbar/CustomTabBar";
|
|
13
|
+import {Collapsible} from "react-navigation-collapsible";
|
13
|
14
|
|
14
|
15
|
type Props = {
|
15
|
|
- navigation: Object,
|
|
16
|
+ navigation: { [key: string]: any },
|
16
|
17
|
fetchUrl: string,
|
17
|
18
|
autoRefreshTime: number,
|
18
|
19
|
refreshOnFocus: boolean,
|
19
|
|
- renderItem: React.Node,
|
20
|
|
- renderSectionHeader: React.Node,
|
21
|
|
- stickyHeader: boolean,
|
22
|
|
- createDataset: Function,
|
23
|
|
- updateData: number,
|
24
|
|
- itemHeight: number | null,
|
25
|
|
- onScroll: Function,
|
26
|
|
- collapsibleStack: Object,
|
|
20
|
+ renderItem: (data: { [key: string]: any }) => React.Node,
|
|
21
|
+ createDataset: (data: { [key: string]: any }) => Array<Object>,
|
|
22
|
+ onScroll: (event: SyntheticEvent<EventTarget>) => void,
|
|
23
|
+ collapsibleStack: Collapsible,
|
|
24
|
+
|
|
25
|
+ itemHeight?: number,
|
|
26
|
+ updateData?: number,
|
|
27
|
+ renderSectionHeader?: (data: { [key: string]: any }) => React.Node,
|
|
28
|
+ stickyHeader?: boolean,
|
27
|
29
|
}
|
28
|
30
|
|
29
|
31
|
type State = {
|
30
|
32
|
refreshing: boolean,
|
31
|
33
|
firstLoading: boolean,
|
32
|
|
- fetchedData: ?Object,
|
|
34
|
+ fetchedData: { [key: string]: any } | null,
|
33
|
35
|
snackbarVisible: boolean
|
34
|
36
|
};
|
35
|
37
|
|
|
@@ -45,37 +47,21 @@ const MIN_REFRESH_TIME = 5 * 1000;
|
45
|
47
|
class WebSectionList extends React.PureComponent<Props, State> {
|
46
|
48
|
|
47
|
49
|
static defaultProps = {
|
48
|
|
- renderSectionHeader: null,
|
49
|
50
|
stickyHeader: false,
|
50
|
51
|
updateData: 0,
|
51
|
|
- itemHeight: null,
|
52
|
52
|
};
|
53
|
53
|
|
54
|
|
- scrollRef: Object;
|
|
54
|
+ scrollRef: { current: null | Animated.SectionList };
|
55
|
55
|
refreshInterval: IntervalID;
|
56
|
|
- lastRefresh: Date;
|
|
56
|
+ lastRefresh: Date | null;
|
57
|
57
|
|
58
|
58
|
state = {
|
59
|
59
|
refreshing: false,
|
60
|
60
|
firstLoading: true,
|
61
|
|
- fetchedData: undefined,
|
|
61
|
+ fetchedData: null,
|
62
|
62
|
snackbarVisible: false
|
63
|
63
|
};
|
64
|
64
|
|
65
|
|
- onRefresh: Function;
|
66
|
|
- onFetchSuccess: Function;
|
67
|
|
- onFetchError: Function;
|
68
|
|
- getEmptySectionHeader: Function;
|
69
|
|
-
|
70
|
|
- constructor() {
|
71
|
|
- super();
|
72
|
|
- // creating references to functions used in render()
|
73
|
|
- this.onRefresh = this.onRefresh.bind(this);
|
74
|
|
- this.onFetchSuccess = this.onFetchSuccess.bind(this);
|
75
|
|
- this.onFetchError = this.onFetchError.bind(this);
|
76
|
|
- this.getEmptySectionHeader = this.getEmptySectionHeader.bind(this);
|
77
|
|
- }
|
78
|
|
-
|
79
|
65
|
/**
|
80
|
66
|
* Registers react navigation events on first screen load.
|
81
|
67
|
* Allows to detect when the screen is focused
|
|
@@ -87,13 +73,14 @@ class WebSectionList extends React.PureComponent<Props, State> {
|
87
|
73
|
this.props.navigation.addListener('blur', onScreenBlur);
|
88
|
74
|
this.scrollRef = React.createRef();
|
89
|
75
|
this.onRefresh();
|
|
76
|
+ this.lastRefresh = null;
|
90
|
77
|
}
|
91
|
78
|
|
92
|
79
|
/**
|
93
|
80
|
* Refreshes data when focusing the screen and setup a refresh interval if asked to
|
94
|
81
|
*/
|
95
|
82
|
onScreenFocus() {
|
96
|
|
- if (this.props.refreshOnFocus && this.lastRefresh !== undefined)
|
|
83
|
+ if (this.props.refreshOnFocus && this.lastRefresh)
|
97
|
84
|
this.onRefresh();
|
98
|
85
|
if (this.props.autoRefreshTime > 0)
|
99
|
86
|
this.refreshInterval = setInterval(this.onRefresh, this.props.autoRefreshTime)
|
|
@@ -115,36 +102,37 @@ class WebSectionList extends React.PureComponent<Props, State> {
|
115
|
102
|
*
|
116
|
103
|
* @param fetchedData The newly fetched data
|
117
|
104
|
*/
|
118
|
|
- onFetchSuccess(fetchedData: Object) {
|
|
105
|
+ onFetchSuccess = (fetchedData: { [key: string]: any }) => {
|
119
|
106
|
this.setState({
|
120
|
107
|
fetchedData: fetchedData,
|
121
|
108
|
refreshing: false,
|
122
|
109
|
firstLoading: false
|
123
|
110
|
});
|
124
|
111
|
this.lastRefresh = new Date();
|
125
|
|
- }
|
|
112
|
+ };
|
126
|
113
|
|
127
|
114
|
/**
|
128
|
115
|
* Callback used when fetch encountered an error.
|
129
|
116
|
* It will reset the displayed data and show an error.
|
130
|
117
|
*/
|
131
|
|
- onFetchError() {
|
|
118
|
+ onFetchError = () => {
|
132
|
119
|
this.setState({
|
133
|
|
- fetchedData: undefined,
|
|
120
|
+ fetchedData: null,
|
134
|
121
|
refreshing: false,
|
135
|
122
|
firstLoading: false
|
136
|
123
|
});
|
137
|
124
|
this.showSnackBar();
|
138
|
|
- }
|
|
125
|
+ };
|
139
|
126
|
|
140
|
127
|
/**
|
141
|
128
|
* Refreshes data and shows an animations while doing it
|
142
|
129
|
*/
|
143
|
|
- onRefresh() {
|
|
130
|
+ onRefresh = () => {
|
144
|
131
|
let canRefresh;
|
145
|
|
- if (this.lastRefresh !== undefined)
|
146
|
|
- canRefresh = (new Date().getTime() - this.lastRefresh.getTime()) > MIN_REFRESH_TIME;
|
147
|
|
- else
|
|
132
|
+ if (this.lastRefresh != null) {
|
|
133
|
+ const last = this.lastRefresh;
|
|
134
|
+ canRefresh = (new Date().getTime() - last.getTime()) > MIN_REFRESH_TIME;
|
|
135
|
+ } else
|
148
|
136
|
canRefresh = true;
|
149
|
137
|
if (canRefresh) {
|
150
|
138
|
this.setState({refreshing: true});
|
|
@@ -152,17 +140,7 @@ class WebSectionList extends React.PureComponent<Props, State> {
|
152
|
140
|
.then(this.onFetchSuccess)
|
153
|
141
|
.catch(this.onFetchError);
|
154
|
142
|
}
|
155
|
|
- }
|
156
|
|
-
|
157
|
|
- /**
|
158
|
|
- * Gets an empty section header
|
159
|
|
- *
|
160
|
|
- * @param section The current section
|
161
|
|
- * @return {*}
|
162
|
|
- */
|
163
|
|
- getEmptySectionHeader({section}: Object) {
|
164
|
|
- return <View/>;
|
165
|
|
- }
|
|
143
|
+ };
|
166
|
144
|
|
167
|
145
|
/**
|
168
|
146
|
* Shows the error popup
|
|
@@ -174,25 +152,38 @@ class WebSectionList extends React.PureComponent<Props, State> {
|
174
|
152
|
*/
|
175
|
153
|
hideSnackBar = () => this.setState({snackbarVisible: false});
|
176
|
154
|
|
177
|
|
- itemLayout = (data: Object, index: number) => ({
|
178
|
|
- length: this.props.itemHeight,
|
179
|
|
- offset: this.props.itemHeight * index,
|
180
|
|
- index
|
181
|
|
- });
|
|
155
|
+ itemLayout = (data: { [key: string]: any }, index: number) => {
|
|
156
|
+ const height = this.props.itemHeight;
|
|
157
|
+ if (height == null)
|
|
158
|
+ return undefined;
|
|
159
|
+ return {
|
|
160
|
+ length: height,
|
|
161
|
+ offset: height * index,
|
|
162
|
+ index
|
|
163
|
+ }
|
|
164
|
+ };
|
182
|
165
|
|
183
|
|
- renderSectionHeader = (data: Object) => {
|
184
|
|
- return (
|
185
|
|
- <Animatable.View
|
186
|
|
- animation={"fadeInUp"}
|
187
|
|
- duration={500}
|
188
|
|
- useNativeDriver
|
189
|
|
- >
|
190
|
|
- {this.props.renderSectionHeader(data)}
|
191
|
|
- </Animatable.View>
|
192
|
|
- );
|
|
166
|
+ renderSectionHeader = (data: { section: { [key: string]: any } }) => {
|
|
167
|
+ if (this.props.renderSectionHeader != null) {
|
|
168
|
+ return (
|
|
169
|
+ <Animatable.View
|
|
170
|
+ animation={"fadeInUp"}
|
|
171
|
+ duration={500}
|
|
172
|
+ useNativeDriver
|
|
173
|
+ >
|
|
174
|
+ {this.props.renderSectionHeader(data)}
|
|
175
|
+ </Animatable.View>
|
|
176
|
+ );
|
|
177
|
+ } else
|
|
178
|
+ return null;
|
193
|
179
|
}
|
194
|
180
|
|
195
|
|
- renderItem = (data: Object) => {
|
|
181
|
+ renderItem = (data: {
|
|
182
|
+ item: { [key: string]: any },
|
|
183
|
+ index: number,
|
|
184
|
+ section: { [key: string]: any },
|
|
185
|
+ separators: { [key: string]: any },
|
|
186
|
+ }) => {
|
196
|
187
|
return (
|
197
|
188
|
<Animatable.View
|
198
|
189
|
animation={"fadeInUp"}
|
|
@@ -204,16 +195,15 @@ class WebSectionList extends React.PureComponent<Props, State> {
|
204
|
195
|
);
|
205
|
196
|
}
|
206
|
197
|
|
207
|
|
- onScroll = (event: Object) => {
|
|
198
|
+ onScroll = (event: SyntheticEvent<EventTarget>) => {
|
208
|
199
|
if (this.props.onScroll)
|
209
|
200
|
this.props.onScroll(event);
|
210
|
201
|
}
|
211
|
202
|
|
212
|
203
|
render() {
|
213
|
204
|
let dataset = [];
|
214
|
|
- if (this.state.fetchedData !== undefined)
|
|
205
|
+ if (this.state.fetchedData != null)
|
215
|
206
|
dataset = this.props.createDataset(this.state.fetchedData);
|
216
|
|
- const shouldRenderHeader = this.props.renderSectionHeader !== null;
|
217
|
207
|
const {containerPaddingTop, scrollIndicatorInsetTop, onScrollWithListener} = this.props.collapsibleStack;
|
218
|
208
|
return (
|
219
|
209
|
<View>
|
|
@@ -230,7 +220,7 @@ class WebSectionList extends React.PureComponent<Props, State> {
|
230
|
220
|
/>
|
231
|
221
|
}
|
232
|
222
|
//$FlowFixMe
|
233
|
|
- renderSectionHeader={shouldRenderHeader ? this.renderSectionHeader : this.getEmptySectionHeader}
|
|
223
|
+ renderSectionHeader={this.renderSectionHeader}
|
234
|
224
|
//$FlowFixMe
|
235
|
225
|
renderItem={this.renderItem}
|
236
|
226
|
stickySectionHeadersEnabled={this.props.stickyHeader}
|
|
@@ -242,7 +232,7 @@ class WebSectionList extends React.PureComponent<Props, State> {
|
242
|
232
|
errorCode={ERROR_TYPE.CONNECTION_ERROR}
|
243
|
233
|
onRefresh={this.onRefresh}/>
|
244
|
234
|
}
|
245
|
|
- getItemLayout={this.props.itemHeight !== null ? this.itemLayout : undefined}
|
|
235
|
+ getItemLayout={this.props.itemHeight != null ? this.itemLayout : undefined}
|
246
|
236
|
// Animations
|
247
|
237
|
onScroll={onScrollWithListener(this.onScroll)}
|
248
|
238
|
contentContainerStyle={{
|