Browse Source

Improve Planning screen components to match linter

Arnaud Vergnet 3 years ago
parent
commit
3ce23726c2
4 changed files with 587 additions and 538 deletions
  1. 165
    148
      src/screens/Planning/PlanningDisplayScreen.js
  2. 247
    224
      src/screens/Planning/PlanningScreen.js
  3. 163
    156
      src/utils/Planning.js
  4. 12
    10
      src/utils/WebData.js

+ 165
- 148
src/screens/Planning/PlanningDisplayScreen.js View File

@@ -2,166 +2,183 @@
2 2
 
3 3
 import * as React from 'react';
4 4
 import {View} from 'react-native';
5
-import {getDateOnlyString, getFormattedEventTime} from '../../utils/Planning';
6 5
 import {Card, withTheme} from 'react-native-paper';
7
-import DateManager from "../../managers/DateManager";
8 6
 import ImageModal from 'react-native-image-modal';
9
-import BasicLoadingScreen from "../../components/Screens/BasicLoadingScreen";
10
-import {apiRequest, ERROR_TYPE} from "../../utils/WebData";
11
-import ErrorView from "../../components/Screens/ErrorView";
12
-import CustomHTML from "../../components/Overrides/CustomHTML";
13
-import CustomTabBar from "../../components/Tabbar/CustomTabBar";
14 7
 import i18n from 'i18n-js';
15
-import {StackNavigationProp} from "@react-navigation/stack";
16
-import type {CustomTheme} from "../../managers/ThemeManager";
17
-import CollapsibleScrollView from "../../components/Collapsible/CollapsibleScrollView";
18
-
19
-type Props = {
20
-    navigation: StackNavigationProp,
21
-    route: { params: { data: Object, id: number, eventId: number } },
22
-    theme: CustomTheme
8
+import {StackNavigationProp} from '@react-navigation/stack';
9
+import {getDateOnlyString, getFormattedEventTime} from '../../utils/Planning';
10
+import DateManager from '../../managers/DateManager';
11
+import BasicLoadingScreen from '../../components/Screens/BasicLoadingScreen';
12
+import {apiRequest, ERROR_TYPE} from '../../utils/WebData';
13
+import ErrorView from '../../components/Screens/ErrorView';
14
+import CustomHTML from '../../components/Overrides/CustomHTML';
15
+import CustomTabBar from '../../components/Tabbar/CustomTabBar';
16
+import type {CustomThemeType} from '../../managers/ThemeManager';
17
+import CollapsibleScrollView from '../../components/Collapsible/CollapsibleScrollView';
18
+import type {PlanningEventType} from '../../utils/Planning';
19
+
20
+type PropsType = {
21
+  navigation: StackNavigationProp,
22
+  route: {params: {data: PlanningEventType, id: number, eventId: number}},
23
+  theme: CustomThemeType,
23 24
 };
24 25
 
25
-type State = {
26
-    loading: boolean
26
+type StateType = {
27
+  loading: boolean,
27 28
 };
28 29
 
29
-const CLUB_INFO_PATH = "event/info";
30
+const EVENT_INFO_URL = 'event/info';
30 31
 
31 32
 /**
32 33
  * Class defining a planning event information page.
33 34
  */
34
-class PlanningDisplayScreen extends React.Component<Props, State> {
35
-
36
-    displayData: Object;
37
-    shouldFetchData: boolean;
38
-    eventId: number;
39
-    errorCode: number;
40
-
41
-    /**
42
-     * Generates data depending on whether the screen was opened from the planning or from a link
43
-     *
44
-     * @param props
45
-     */
46
-    constructor(props) {
47
-        super(props);
48
-
49
-        if (this.props.route.params.data != null) {
50
-            this.displayData = this.props.route.params.data;
51
-            this.eventId = this.displayData.id;
52
-            this.shouldFetchData = false;
53
-            this.errorCode = 0;
54
-            this.state = {
55
-                loading: false,
56
-            };
57
-        } else {
58
-            this.displayData = null;
59
-            this.eventId = this.props.route.params.eventId;
60
-            this.shouldFetchData = true;
61
-            this.errorCode = 0;
62
-            this.state = {
63
-                loading: true,
64
-            };
65
-            this.fetchData();
66
-
67
-        }
68
-    }
69
-
70
-    /**
71
-     * Fetches data for the current event id from the API
72
-     */
73
-    fetchData = () => {
74
-        this.setState({loading: true});
75
-        apiRequest(CLUB_INFO_PATH, 'POST', {id: this.eventId})
76
-            .then(this.onFetchSuccess)
77
-            .catch(this.onFetchError);
78
-    };
79
-
80
-    /**
81
-     * Hides loading and saves fetched data
82
-     *
83
-     * @param data Received data
84
-     */
85
-    onFetchSuccess = (data: Object) => {
86
-        this.displayData = data;
87
-        this.setState({loading: false});
88
-    };
89
-
90
-    /**
91
-     * Hides loading and saves the error code
92
-     *
93
-     * @param error
94
-     */
95
-    onFetchError = (error: number) => {
96
-        this.errorCode = error;
97
-        this.setState({loading: false});
98
-    };
99
-
100
-    /**
101
-     * Gets content to display
102
-     *
103
-     * @returns {*}
104
-     */
105
-    getContent() {
106
-        let subtitle = getFormattedEventTime(
107
-            this.displayData["date_begin"], this.displayData["date_end"]);
108
-        let dateString = getDateOnlyString(this.displayData["date_begin"]);
109
-        if (dateString !== null)
110
-            subtitle += ' | ' + DateManager.getInstance().getTranslatedDate(dateString);
111
-        return (
112
-            <CollapsibleScrollView
113
-                style={{paddingLeft: 5, paddingRight: 5}}
114
-                hasTab={true}
115
-            >
116
-                <Card.Title
117
-                    title={this.displayData.title}
118
-                    subtitle={subtitle}
119
-                />
120
-                {this.displayData.logo !== null ?
121
-                    <View style={{marginLeft: 'auto', marginRight: 'auto'}}>
122
-                        <ImageModal
123
-                            resizeMode="contain"
124
-                            imageBackgroundColor={this.props.theme.colors.background}
125
-                            style={{
126
-                                width: 300,
127
-                                height: 300,
128
-                            }}
129
-                            source={{
130
-                                uri: this.displayData.logo,
131
-                            }}
132
-                        /></View>
133
-                    : <View/>}
134
-
135
-                {this.displayData.description !== null ?
136
-                    <Card.Content style={{paddingBottom: CustomTabBar.TAB_BAR_HEIGHT + 20}}>
137
-                        <CustomHTML html={this.displayData.description}/>
138
-                    </Card.Content>
139
-                    : <View/>}
140
-            </CollapsibleScrollView>
141
-        );
142
-    }
143
-
144
-    /**
145
-     * Shows an error view and use a custom message if the event does not exist
146
-     *
147
-     * @returns {*}
148
-     */
149
-    getErrorView() {
150
-        if (this.errorCode === ERROR_TYPE.BAD_INPUT)
151
-            return <ErrorView {...this.props} showRetryButton={false} message={i18n.t("screens.planning.invalidEvent")}
152
-                              icon={"calendar-remove"}/>;
153
-        else
154
-            return <ErrorView {...this.props} errorCode={this.errorCode} onRefresh={this.fetchData}/>;
155
-    }
156
-
157
-    render() {
158
-        if (this.state.loading)
159
-            return <BasicLoadingScreen/>;
160
-        else if (this.errorCode === 0)
161
-            return this.getContent();
162
-        else
163
-            return this.getErrorView();
35
+class PlanningDisplayScreen extends React.Component<PropsType, StateType> {
36
+  displayData: null | PlanningEventType;
37
+
38
+  shouldFetchData: boolean;
39
+
40
+  eventId: number;
41
+
42
+  errorCode: number;
43
+
44
+  /**
45
+   * Generates data depending on whether the screen was opened from the planning or from a link
46
+   *
47
+   * @param props
48
+   */
49
+  constructor(props: PropsType) {
50
+    super(props);
51
+
52
+    if (props.route.params.data != null) {
53
+      this.displayData = props.route.params.data;
54
+      this.eventId = this.displayData.id;
55
+      this.shouldFetchData = false;
56
+      this.errorCode = 0;
57
+      this.state = {
58
+        loading: false,
59
+      };
60
+    } else {
61
+      this.displayData = null;
62
+      this.eventId = props.route.params.eventId;
63
+      this.shouldFetchData = true;
64
+      this.errorCode = 0;
65
+      this.state = {
66
+        loading: true,
67
+      };
68
+      this.fetchData();
164 69
     }
70
+  }
71
+
72
+  /**
73
+   * Hides loading and saves fetched data
74
+   *
75
+   * @param data Received data
76
+   */
77
+  onFetchSuccess = (data: PlanningEventType) => {
78
+    this.displayData = data;
79
+    this.setState({loading: false});
80
+  };
81
+
82
+  /**
83
+   * Hides loading and saves the error code
84
+   *
85
+   * @param error
86
+   */
87
+  onFetchError = (error: number) => {
88
+    this.errorCode = error;
89
+    this.setState({loading: false});
90
+  };
91
+
92
+  /**
93
+   * Gets content to display
94
+   *
95
+   * @returns {*}
96
+   */
97
+  getContent(): React.Node {
98
+    const {theme} = this.props;
99
+    const {displayData} = this;
100
+    if (displayData == null) return null;
101
+    let subtitle = getFormattedEventTime(
102
+      displayData.date_begin,
103
+      displayData.date_end,
104
+    );
105
+    const dateString = getDateOnlyString(displayData.date_begin);
106
+    if (dateString !== null)
107
+      subtitle += ` | ${DateManager.getInstance().getTranslatedDate(
108
+        dateString,
109
+      )}`;
110
+    return (
111
+      <CollapsibleScrollView style={{paddingLeft: 5, paddingRight: 5}} hasTab>
112
+        <Card.Title title={displayData.title} subtitle={subtitle} />
113
+        {displayData.logo !== null ? (
114
+          <View style={{marginLeft: 'auto', marginRight: 'auto'}}>
115
+            <ImageModal
116
+              resizeMode="contain"
117
+              imageBackgroundColor={theme.colors.background}
118
+              style={{
119
+                width: 300,
120
+                height: 300,
121
+              }}
122
+              source={{
123
+                uri: displayData.logo,
124
+              }}
125
+            />
126
+          </View>
127
+        ) : null}
128
+
129
+        {displayData.description !== null ? (
130
+          <Card.Content
131
+            style={{paddingBottom: CustomTabBar.TAB_BAR_HEIGHT + 20}}>
132
+            <CustomHTML html={displayData.description} />
133
+          </Card.Content>
134
+        ) : (
135
+          <View />
136
+        )}
137
+      </CollapsibleScrollView>
138
+    );
139
+  }
140
+
141
+  /**
142
+   * Shows an error view and use a custom message if the event does not exist
143
+   *
144
+   * @returns {*}
145
+   */
146
+  getErrorView(): React.Node {
147
+    const {navigation} = this.props;
148
+    if (this.errorCode === ERROR_TYPE.BAD_INPUT)
149
+      return (
150
+        <ErrorView
151
+          navigation={navigation}
152
+          showRetryButton={false}
153
+          message={i18n.t('screens.planning.invalidEvent')}
154
+          icon="calendar-remove"
155
+        />
156
+      );
157
+    return (
158
+      <ErrorView
159
+        navigation={navigation}
160
+        errorCode={this.errorCode}
161
+        onRefresh={this.fetchData}
162
+      />
163
+    );
164
+  }
165
+
166
+  /**
167
+   * Fetches data for the current event id from the API
168
+   */
169
+  fetchData = () => {
170
+    this.setState({loading: true});
171
+    apiRequest(EVENT_INFO_URL, 'POST', {id: this.eventId})
172
+      .then(this.onFetchSuccess)
173
+      .catch(this.onFetchError);
174
+  };
175
+
176
+  render(): React.Node {
177
+    const {loading} = this.state;
178
+    if (loading) return <BasicLoadingScreen />;
179
+    if (this.errorCode === 0) return this.getContent();
180
+    return this.getErrorView();
181
+  }
165 182
 }
166 183
 
167 184
 export default withTheme(PlanningDisplayScreen);

+ 247
- 224
src/screens/Planning/PlanningScreen.js View File

@@ -2,259 +2,282 @@
2 2
 
3 3
 import * as React from 'react';
4 4
 import {BackHandler, View} from 'react-native';
5
-import i18n from "i18n-js";
6
-import {LocaleConfig} from 'react-native-calendars';
7
-import {readData} from "../../utils/WebData";
8
-import type {eventObject} from "../../utils/Planning";
5
+import i18n from 'i18n-js';
6
+import {Agenda, LocaleConfig} from 'react-native-calendars';
7
+import {Avatar, Divider, List} from 'react-native-paper';
8
+import {StackNavigationProp} from '@react-navigation/stack';
9
+import {readData} from '../../utils/WebData';
10
+import type {PlanningEventType} from '../../utils/Planning';
9 11
 import {
10
-    generateEventAgenda,
11
-    getCurrentDateString,
12
-    getDateOnlyString,
13
-    getFormattedEventTime,
12
+  generateEventAgenda,
13
+  getCurrentDateString,
14
+  getDateOnlyString,
15
+  getFormattedEventTime,
14 16
 } from '../../utils/Planning';
15
-import {Avatar, Divider, List} from 'react-native-paper';
16
-import CustomAgenda from "../../components/Overrides/CustomAgenda";
17
-import {StackNavigationProp} from "@react-navigation/stack";
18
-import {MASCOT_STYLE} from "../../components/Mascot/Mascot";
19
-import MascotPopup from "../../components/Mascot/MascotPopup";
20
-import AsyncStorageManager from "../../managers/AsyncStorageManager";
17
+import CustomAgenda from '../../components/Overrides/CustomAgenda';
18
+import {MASCOT_STYLE} from '../../components/Mascot/Mascot';
19
+import MascotPopup from '../../components/Mascot/MascotPopup';
20
+import AsyncStorageManager from '../../managers/AsyncStorageManager';
21 21
 
22
-LocaleConfig.locales['fr'] = {
23
-    monthNames: ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'],
24
-    monthNamesShort: ['Janv.', 'Févr.', 'Mars', 'Avril', 'Mai', 'Juin', 'Juil.', 'Août', 'Sept.', 'Oct.', 'Nov.', 'Déc.'],
25
-    dayNames: ['Dimanche', 'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi'],
26
-    dayNamesShort: ['Dim', 'Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam'],
27
-    today: 'Aujourd\'hui'
22
+LocaleConfig.locales.fr = {
23
+  monthNames: [
24
+    'Janvier',
25
+    'Février',
26
+    'Mars',
27
+    'Avril',
28
+    'Mai',
29
+    'Juin',
30
+    'Juillet',
31
+    'Août',
32
+    'Septembre',
33
+    'Octobre',
34
+    'Novembre',
35
+    'Décembre',
36
+  ],
37
+  monthNamesShort: [
38
+    'Janv.',
39
+    'Févr.',
40
+    'Mars',
41
+    'Avril',
42
+    'Mai',
43
+    'Juin',
44
+    'Juil.',
45
+    'Août',
46
+    'Sept.',
47
+    'Oct.',
48
+    'Nov.',
49
+    'Déc.',
50
+  ],
51
+  dayNames: [
52
+    'Dimanche',
53
+    'Lundi',
54
+    'Mardi',
55
+    'Mercredi',
56
+    'Jeudi',
57
+    'Vendredi',
58
+    'Samedi',
59
+  ],
60
+  dayNamesShort: ['Dim', 'Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam'],
61
+  today: "Aujourd'hui",
28 62
 };
29 63
 
64
+type PropsType = {
65
+  navigation: StackNavigationProp,
66
+};
30 67
 
31
-type Props = {
32
-    navigation: StackNavigationProp,
33
-}
34
-
35
-type State = {
36
-    refreshing: boolean,
37
-    agendaItems: Object,
38
-    calendarShowing: boolean,
68
+type StateType = {
69
+  refreshing: boolean,
70
+  agendaItems: {[key: string]: Array<PlanningEventType>},
71
+  calendarShowing: boolean,
39 72
 };
40 73
 
41
-const FETCH_URL = "https://www.amicale-insat.fr/api/event/list";
74
+const FETCH_URL = 'https://www.amicale-insat.fr/api/event/list';
42 75
 const AGENDA_MONTH_SPAN = 3;
43 76
 
44 77
 /**
45 78
  * Class defining the app's planning screen
46 79
  */
47
-class PlanningScreen extends React.Component<Props, State> {
80
+class PlanningScreen extends React.Component<PropsType, StateType> {
81
+  agendaRef: null | Agenda;
48 82
 
49
-    agendaRef: Object;
83
+  lastRefresh: Date;
50 84
 
51
-    lastRefresh: Date;
52
-    minTimeBetweenRefresh = 60;
85
+  minTimeBetweenRefresh = 60;
53 86
 
54
-    state = {
55
-        refreshing: false,
56
-        agendaItems: {},
57
-        calendarShowing: false,
58
-    };
59
-
60
-    currentDate = getDateOnlyString(getCurrentDateString());
61
-
62
-    constructor(props: any) {
63
-        super(props);
64
-        if (i18n.currentLocale().startsWith("fr")) {
65
-            LocaleConfig.defaultLocale = 'fr';
66
-        }
67
-    }
87
+  currentDate = getDateOnlyString(getCurrentDateString());
68 88
 
69
-    /**
70
-     * Captures focus and blur events to hook on android back button
71
-     */
72
-    componentDidMount() {
73
-        this.onRefresh();
74
-        this.props.navigation.addListener(
75
-            'focus',
76
-            () =>
77
-                BackHandler.addEventListener(
78
-                    'hardwareBackPress',
79
-                    this.onBackButtonPressAndroid
80
-                )
81
-        );
82
-        this.props.navigation.addListener(
83
-            'blur',
84
-            () =>
85
-                BackHandler.removeEventListener(
86
-                    'hardwareBackPress',
87
-                    this.onBackButtonPressAndroid
88
-                )
89
-        );
89
+  constructor(props: PropsType) {
90
+    super(props);
91
+    if (i18n.currentLocale().startsWith('fr')) {
92
+      LocaleConfig.defaultLocale = 'fr';
90 93
     }
91
-
92
-    /**
93
-     * Overrides default android back button behaviour to close the calendar if it was open.
94
-     *
95
-     * @return {boolean}
96
-     */
97
-    onBackButtonPressAndroid = () => {
98
-        if (this.state.calendarShowing) {
99
-            this.agendaRef.chooseDay(this.agendaRef.state.selectedDay);
100
-            return true;
101
-        } else {
102
-            return false;
103
-        }
94
+    this.state = {
95
+      refreshing: false,
96
+      agendaItems: {},
97
+      calendarShowing: false,
104 98
     };
99
+  }
105 100
 
106
-    /**
107
-     * Function used to check if a row has changed
108
-     *
109
-     * @param r1
110
-     * @param r2
111
-     * @return {boolean}
112
-     */
113
-    rowHasChanged(r1: Object, r2: Object) {
114
-        return false;
115
-        // if (r1 !== undefined && r2 !== undefined)
116
-        //     return r1.title !== r2.title;
117
-        // else return !(r1 === undefined && r2 === undefined);
118
-    }
101
+  /**
102
+   * Captures focus and blur events to hook on android back button
103
+   */
104
+  componentDidMount() {
105
+    const {navigation} = this.props;
106
+    this.onRefresh();
107
+    navigation.addListener('focus', () => {
108
+      BackHandler.addEventListener(
109
+        'hardwareBackPress',
110
+        this.onBackButtonPressAndroid,
111
+      );
112
+    });
113
+    navigation.addListener('blur', () => {
114
+      BackHandler.removeEventListener(
115
+        'hardwareBackPress',
116
+        this.onBackButtonPressAndroid,
117
+      );
118
+    });
119
+  }
119 120
 
120
-    /**
121
-     * Refreshes data and shows an animation while doing it
122
-     */
123
-    onRefresh = () => {
124
-        let canRefresh;
125
-        if (this.lastRefresh !== undefined)
126
-            canRefresh = (new Date().getTime() - this.lastRefresh.getTime()) / 1000 > this.minTimeBetweenRefresh;
127
-        else
128
-            canRefresh = true;
121
+  /**
122
+   * Overrides default android back button behaviour to close the calendar if it was open.
123
+   *
124
+   * @return {boolean}
125
+   */
126
+  onBackButtonPressAndroid = (): boolean => {
127
+    const {calendarShowing} = this.state;
128
+    if (calendarShowing && this.agendaRef != null) {
129
+      this.agendaRef.chooseDay(this.agendaRef.state.selectedDay);
130
+      return true;
131
+    }
132
+    return false;
133
+  };
129 134
 
130
-        if (canRefresh) {
131
-            this.setState({refreshing: true});
132
-            readData(FETCH_URL)
133
-                .then((fetchedData) => {
134
-                    this.setState({
135
-                        refreshing: false,
136
-                        agendaItems: generateEventAgenda(fetchedData, AGENDA_MONTH_SPAN)
137
-                    });
138
-                    this.lastRefresh = new Date();
139
-                })
140
-                .catch(() => {
141
-                    this.setState({
142
-                        refreshing: false,
143
-                    });
144
-                });
145
-        }
146
-    };
135
+  /**
136
+   * Refreshes data and shows an animation while doing it
137
+   */
138
+  onRefresh = () => {
139
+    let canRefresh;
140
+    if (this.lastRefresh !== undefined)
141
+      canRefresh =
142
+        (new Date().getTime() - this.lastRefresh.getTime()) / 1000 >
143
+        this.minTimeBetweenRefresh;
144
+    else canRefresh = true;
147 145
 
148
-    /**
149
-     * Callback used when receiving the agenda ref
150
-     *
151
-     * @param ref
152
-     */
153
-    onAgendaRef = (ref: Object) => {
154
-        this.agendaRef = ref;
146
+    if (canRefresh) {
147
+      this.setState({refreshing: true});
148
+      readData(FETCH_URL)
149
+        .then((fetchedData: Array<PlanningEventType>) => {
150
+          this.setState({
151
+            refreshing: false,
152
+            agendaItems: generateEventAgenda(fetchedData, AGENDA_MONTH_SPAN),
153
+          });
154
+          this.lastRefresh = new Date();
155
+        })
156
+        .catch(() => {
157
+          this.setState({
158
+            refreshing: false,
159
+          });
160
+        });
155 161
     }
162
+  };
156 163
 
157
-    /**
158
-     * Callback used when a button is pressed to toggle the calendar
159
-     *
160
-     * @param isCalendarOpened True is the calendar is already open, false otherwise
161
-     */
162
-    onCalendarToggled = (isCalendarOpened: boolean) => {
163
-        this.setState({calendarShowing: isCalendarOpened});
164
-    }
164
+  /**
165
+   * Callback used when receiving the agenda ref
166
+   *
167
+   * @param ref
168
+   */
169
+  onAgendaRef = (ref: Agenda) => {
170
+    this.agendaRef = ref;
171
+  };
172
+
173
+  /**
174
+   * Callback used when a button is pressed to toggle the calendar
175
+   *
176
+   * @param isCalendarOpened True is the calendar is already open, false otherwise
177
+   */
178
+  onCalendarToggled = (isCalendarOpened: boolean) => {
179
+    this.setState({calendarShowing: isCalendarOpened});
180
+  };
165 181
 
166
-    /**
167
-     * Gets an event render item
168
-     *
169
-     * @param item The current event to render
170
-     * @return {*}
171
-     */
172
-    getRenderItem = (item: eventObject) => {
173
-        const onPress = this.props.navigation.navigate.bind(this, 'planning-information', {data: item});
174
-        if (item.logo !== null) {
175
-            return (
176
-                <View>
177
-                    <Divider/>
178
-                    <List.Item
179
-                        title={item.title}
180
-                        description={getFormattedEventTime(item["date_begin"], item["date_end"])}
181
-                        left={() => <Avatar.Image
182
-                            source={{uri: item.logo}}
183
-                            style={{backgroundColor: 'transparent'}}
184
-                        />}
185
-                        onPress={onPress}
186
-                    />
187
-                </View>
188
-            );
189
-        } else {
190
-            return (
191
-                <View>
192
-                    <Divider/>
193
-                    <List.Item
194
-                        title={item.title}
195
-                        description={getFormattedEventTime(item["date_begin"], item["date_end"])}
196
-                        onPress={onPress}
197
-                    />
198
-                </View>
199
-            );
200
-        }
182
+  /**
183
+   * Gets an event render item
184
+   *
185
+   * @param item The current event to render
186
+   * @return {*}
187
+   */
188
+  getRenderItem = (item: PlanningEventType): React.Node => {
189
+    const {navigation} = this.props;
190
+    const onPress = () => {
191
+      navigation.navigate('planning-information', {
192
+        data: item,
193
+      });
194
+    };
195
+    if (item.logo !== null) {
196
+      return (
197
+        <View>
198
+          <Divider />
199
+          <List.Item
200
+            title={item.title}
201
+            description={getFormattedEventTime(item.date_begin, item.date_end)}
202
+            left={(): React.Node => (
203
+              <Avatar.Image
204
+                source={{uri: item.logo}}
205
+                style={{backgroundColor: 'transparent'}}
206
+              />
207
+            )}
208
+            onPress={onPress}
209
+          />
210
+        </View>
211
+      );
201 212
     }
213
+    return (
214
+      <View>
215
+        <Divider />
216
+        <List.Item
217
+          title={item.title}
218
+          description={getFormattedEventTime(item.date_begin, item.date_end)}
219
+          onPress={onPress}
220
+        />
221
+      </View>
222
+    );
223
+  };
202 224
 
203
-    /**
204
-     * Gets an empty render item for an empty date
205
-     *
206
-     * @return {*}
207
-     */
208
-    getRenderEmptyDate = () => <Divider/>;
225
+  /**
226
+   * Gets an empty render item for an empty date
227
+   *
228
+   * @return {*}
229
+   */
230
+  getRenderEmptyDate = (): React.Node => <Divider />;
209 231
 
210
-    render() {
211
-        return (
212
-            <View style={{flex: 1}}>
213
-                <CustomAgenda
214
-                    {...this.props}
215
-                    // the list of items that have to be displayed in agenda. If you want to render item as empty date
216
-                    // the value of date key kas to be an empty array []. If there exists no value for date key it is
217
-                    // considered that the date in question is not yet loaded
218
-                    items={this.state.agendaItems}
219
-                    // initially selected day
220
-                    selected={this.currentDate}
221
-                    // Minimum date that can be selected, dates before minDate will be grayed out. Default = undefined
222
-                    minDate={this.currentDate}
223
-                    // Max amount of months allowed to scroll to the past. Default = 50
224
-                    pastScrollRange={1}
225
-                    // Max amount of months allowed to scroll to the future. Default = 50
226
-                    futureScrollRange={AGENDA_MONTH_SPAN}
227
-                    // If provided, a standard RefreshControl will be added for "Pull to Refresh" functionality. Make sure to also set the refreshing prop correctly.
228
-                    onRefresh={this.onRefresh}
229
-                    // callback that fires when the calendar is opened or closed
230
-                    onCalendarToggled={this.onCalendarToggled}
231
-                    // Set this true while waiting for new data from a refresh
232
-                    refreshing={this.state.refreshing}
233
-                    renderItem={this.getRenderItem}
234
-                    renderEmptyDate={this.getRenderEmptyDate}
235
-                    rowHasChanged={this.rowHasChanged}
236
-                    // If firstDay=1 week starts from Monday. Note that dayNames and dayNamesShort should still start from Sunday.
237
-                    firstDay={1}
238
-                    // ref to this agenda in order to handle back button event
239
-                    onRef={this.onAgendaRef}
240
-                />
241
-                <MascotPopup
242
-                    prefKey={AsyncStorageManager.PREFERENCES.eventsShowBanner.key}
243
-                    title={i18n.t("screens.planning.mascotDialog.title")}
244
-                    message={i18n.t("screens.planning.mascotDialog.message")}
245
-                    icon={"party-popper"}
246
-                    buttons={{
247
-                        action: null,
248
-                        cancel: {
249
-                            message: i18n.t("screens.planning.mascotDialog.button"),
250
-                            icon: "check",
251
-                        }
252
-                    }}
253
-                    emotion={MASCOT_STYLE.HAPPY}
254
-                />
255
-            </View>
256
-        );
257
-    }
232
+  render(): React.Node {
233
+    const {state, props} = this;
234
+    return (
235
+      <View style={{flex: 1}}>
236
+        <CustomAgenda
237
+          // eslint-disable-next-line react/jsx-props-no-spreading
238
+          {...props}
239
+          // the list of items that have to be displayed in agenda. If you want to render item as empty date
240
+          // the value of date key kas to be an empty array []. If there exists no value for date key it is
241
+          // considered that the date in question is not yet loaded
242
+          items={state.agendaItems}
243
+          // initially selected day
244
+          selected={this.currentDate}
245
+          // Minimum date that can be selected, dates before minDate will be grayed out. Default = undefined
246
+          minDate={this.currentDate}
247
+          // Max amount of months allowed to scroll to the past. Default = 50
248
+          pastScrollRange={1}
249
+          // Max amount of months allowed to scroll to the future. Default = 50
250
+          futureScrollRange={AGENDA_MONTH_SPAN}
251
+          // If provided, a standard RefreshControl will be added for "Pull to Refresh" functionality. Make sure to also set the refreshing prop correctly.
252
+          onRefresh={this.onRefresh}
253
+          // callback that fires when the calendar is opened or closed
254
+          onCalendarToggled={this.onCalendarToggled}
255
+          // Set this true while waiting for new data from a refresh
256
+          refreshing={state.refreshing}
257
+          renderItem={this.getRenderItem}
258
+          renderEmptyDate={this.getRenderEmptyDate}
259
+          // If firstDay=1 week starts from Monday. Note that dayNames and dayNamesShort should still start from Sunday.
260
+          firstDay={1}
261
+          // ref to this agenda in order to handle back button event
262
+          onRef={this.onAgendaRef}
263
+        />
264
+        <MascotPopup
265
+          prefKey={AsyncStorageManager.PREFERENCES.eventsShowBanner.key}
266
+          title={i18n.t('screens.planning.mascotDialog.title')}
267
+          message={i18n.t('screens.planning.mascotDialog.message')}
268
+          icon="party-popper"
269
+          buttons={{
270
+            action: null,
271
+            cancel: {
272
+              message: i18n.t('screens.planning.mascotDialog.button'),
273
+              icon: 'check',
274
+            },
275
+          }}
276
+          emotion={MASCOT_STYLE.HAPPY}
277
+        />
278
+      </View>
279
+    );
280
+  }
258 281
 }
259 282
 
260 283
 export default PlanningScreen;

+ 163
- 156
src/utils/Planning.js View File

@@ -1,28 +1,85 @@
1 1
 // @flow
2 2
 
3
-export type eventObject = {
4
-    id: number,
5
-    title: string,
6
-    logo: string,
7
-    date_begin: string,
8
-    date_end: string,
9
-    description: string,
10
-    club: string,
11
-    category_id: number,
12
-    url: string,
3
+export type PlanningEventType = {
4
+  id: number,
5
+  title: string,
6
+  logo: string,
7
+  date_begin: string,
8
+  date_end: string,
9
+  description: string,
10
+  club: string,
11
+  category_id: number,
12
+  url: string,
13 13
 };
14 14
 
15 15
 // Regex used to check date string validity
16 16
 const dateRegExp = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/;
17 17
 
18 18
 /**
19
+ * Checks if the given date string is in the format
20
+ * YYYY-MM-DD HH:MM
21
+ *
22
+ * @param dateString The string to check
23
+ * @return {boolean}
24
+ */
25
+export function isEventDateStringFormatValid(dateString: ?string): boolean {
26
+  return (
27
+    dateString !== undefined &&
28
+    dateString !== null &&
29
+    dateRegExp.test(dateString)
30
+  );
31
+}
32
+
33
+/**
34
+ * Converts the given date string to a date object.<br>
35
+ * Accepted format: YYYY-MM-DD HH:MM
36
+ *
37
+ * @param dateString The string to convert
38
+ * @return {Date|null} The date object or null if the given string is invalid
39
+ */
40
+export function stringToDate(dateString: string): Date | null {
41
+  let date = new Date();
42
+  if (isEventDateStringFormatValid(dateString)) {
43
+    const stringArray = dateString.split(' ');
44
+    const dateArray = stringArray[0].split('-');
45
+    const timeArray = stringArray[1].split(':');
46
+    date.setFullYear(
47
+      parseInt(dateArray[0], 10),
48
+      parseInt(dateArray[1], 10) - 1, // Month range from 0 to 11
49
+      parseInt(dateArray[2], 10),
50
+    );
51
+    date.setHours(parseInt(timeArray[0], 10), parseInt(timeArray[1], 10), 0, 0);
52
+  } else date = null;
53
+
54
+  return date;
55
+}
56
+
57
+/**
58
+ * Converts a date object to a string in the format
59
+ * YYYY-MM-DD HH-MM
60
+ *
61
+ * @param date The date object to convert
62
+ * @param isUTC Whether to treat the date as UTC
63
+ * @return {string} The converted string
64
+ */
65
+export function dateToString(date: Date, isUTC: boolean): string {
66
+  const day = String(date.getDate()).padStart(2, '0');
67
+  const month = String(date.getMonth() + 1).padStart(2, '0'); // January is 0!
68
+  const year = date.getFullYear();
69
+  const h = isUTC ? date.getUTCHours() : date.getHours();
70
+  const hours = String(h).padStart(2, '0');
71
+  const minutes = String(date.getMinutes()).padStart(2, '0');
72
+  return `${year}-${month}-${day} ${hours}:${minutes}`;
73
+}
74
+
75
+/**
19 76
  * Gets the current day string representation in the format
20 77
  * YYYY-MM-DD
21 78
  *
22 79
  * @return {string} The string representation
23 80
  */
24 81
 export function getCurrentDateString(): string {
25
-    return dateToString(new Date(Date.now()), false);
82
+  return dateToString(new Date(Date.now()), false);
26 83
 }
27 84
 
28 85
 /**
@@ -33,12 +90,10 @@ export function getCurrentDateString(): string {
33 90
  * @return {boolean}
34 91
  */
35 92
 export function isEventBefore(event1Date: string, event2Date: string): boolean {
36
-    let date1 = stringToDate(event1Date);
37
-    let date2 = stringToDate(event2Date);
38
-    if (date1 !== null && date2 !== null)
39
-        return date1 < date2;
40
-    else
41
-        return false;
93
+  const date1 = stringToDate(event1Date);
94
+  const date2 = stringToDate(event2Date);
95
+  if (date1 !== null && date2 !== null) return date1 < date2;
96
+  return false;
42 97
 }
43 98
 
44 99
 /**
@@ -49,10 +104,8 @@ export function isEventBefore(event1Date: string, event2Date: string): boolean {
49 104
  * @return {string|null} Date in format YYYY:MM:DD or null if given string is invalid
50 105
  */
51 106
 export function getDateOnlyString(dateString: string): string | null {
52
-    if (isEventDateStringFormatValid(dateString))
53
-        return dateString.split(" ")[0];
54
-    else
55
-        return null;
107
+  if (isEventDateStringFormatValid(dateString)) return dateString.split(' ')[0];
108
+  return null;
56 109
 }
57 110
 
58 111
 /**
@@ -63,71 +116,8 @@ export function getDateOnlyString(dateString: string): string | null {
63 116
  * @return {string|null} Time in format HH:MM or null if given string is invalid
64 117
  */
65 118
 export function getTimeOnlyString(dateString: string): string | null {
66
-    if (isEventDateStringFormatValid(dateString))
67
-        return dateString.split(" ")[1];
68
-    else
69
-        return null;
70
-}
71
-
72
-/**
73
- * Checks if the given date string is in the format
74
- * YYYY-MM-DD HH:MM
75
- *
76
- * @param dateString The string to check
77
- * @return {boolean}
78
- */
79
-export function isEventDateStringFormatValid(dateString: ?string): boolean {
80
-    return dateString !== undefined
81
-        && dateString !== null
82
-        && dateRegExp.test(dateString);
83
-}
84
-
85
-/**
86
- * Converts the given date string to a date object.<br>
87
- * Accepted format: YYYY-MM-DD HH:MM
88
- *
89
- * @param dateString The string to convert
90
- * @return {Date|null} The date object or null if the given string is invalid
91
- */
92
-export function stringToDate(dateString: string): Date | null {
93
-    let date = new Date();
94
-    if (isEventDateStringFormatValid(dateString)) {
95
-        let stringArray = dateString.split(' ');
96
-        let dateArray = stringArray[0].split('-');
97
-        let timeArray = stringArray[1].split(':');
98
-        date.setFullYear(
99
-            parseInt(dateArray[0]),
100
-            parseInt(dateArray[1]) - 1, // Month range from 0 to 11
101
-            parseInt(dateArray[2])
102
-        );
103
-        date.setHours(
104
-            parseInt(timeArray[0]),
105
-            parseInt(timeArray[1]),
106
-            0,
107
-            0,
108
-        );
109
-    } else
110
-        date = null;
111
-
112
-    return date;
113
-}
114
-
115
-/**
116
- * Converts a date object to a string in the format
117
- * YYYY-MM-DD HH-MM
118
- *
119
- * @param date The date object to convert
120
- * @param isUTC Whether to treat the date as UTC
121
- * @return {string} The converted string
122
- */
123
-export function dateToString(date: Date, isUTC: boolean): string {
124
-    const day = String(date.getDate()).padStart(2, '0');
125
-    const month = String(date.getMonth() + 1).padStart(2, '0'); //January is 0!
126
-    const year = date.getFullYear();
127
-    const h = isUTC ? date.getUTCHours() : date.getHours();
128
-    const hours = String(h).padStart(2, '0');
129
-    const minutes = String(date.getMinutes()).padStart(2, '0');
130
-    return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes;
119
+  if (isEventDateStringFormatValid(dateString)) return dateString.split(' ')[1];
120
+  return null;
131 121
 }
132 122
 
133 123
 /**
@@ -144,26 +134,34 @@ export function dateToString(date: Date, isUTC: boolean): string {
144 134
  * @return {string} Formatted string or "/ - /" on error
145 135
  */
146 136
 export function getFormattedEventTime(start: string, end: string): string {
147
-    let formattedStr = '/ - /';
148
-    let startDate = stringToDate(start);
149
-    let endDate = stringToDate(end);
137
+  let formattedStr = '/ - /';
138
+  const startDate = stringToDate(start);
139
+  const endDate = stringToDate(end);
150 140
 
151
-    if (startDate !== null && endDate !== null && startDate.getTime() !== endDate.getTime()) {
152
-        formattedStr = String(startDate.getHours()).padStart(2, '0') + ':'
153
-            + String(startDate.getMinutes()).padStart(2, '0') + ' - ';
154
-        if (endDate.getFullYear() > startDate.getFullYear()
155
-            || endDate.getMonth() > startDate.getMonth()
156
-            || endDate.getDate() > startDate.getDate())
157
-            formattedStr += '23:59';
158
-        else
159
-            formattedStr += String(endDate.getHours()).padStart(2, '0') + ':'
160
-                + String(endDate.getMinutes()).padStart(2, '0');
161
-    } else if (startDate !== null)
162
-        formattedStr =
163
-            String(startDate.getHours()).padStart(2, '0') + ':'
164
-            + String(startDate.getMinutes()).padStart(2, '0');
141
+  if (
142
+    startDate !== null &&
143
+    endDate !== null &&
144
+    startDate.getTime() !== endDate.getTime()
145
+  ) {
146
+    formattedStr = `${String(startDate.getHours()).padStart(2, '0')}:${String(
147
+      startDate.getMinutes(),
148
+    ).padStart(2, '0')} - `;
149
+    if (
150
+      endDate.getFullYear() > startDate.getFullYear() ||
151
+      endDate.getMonth() > startDate.getMonth() ||
152
+      endDate.getDate() > startDate.getDate()
153
+    )
154
+      formattedStr += '23:59';
155
+    else
156
+      formattedStr += `${String(endDate.getHours()).padStart(2, '0')}:${String(
157
+        endDate.getMinutes(),
158
+      ).padStart(2, '0')}`;
159
+  } else if (startDate !== null)
160
+    formattedStr = `${String(startDate.getHours()).padStart(2, '0')}:${String(
161
+      startDate.getMinutes(),
162
+    ).padStart(2, '0')}`;
165 163
 
166
-    return formattedStr
164
+  return formattedStr;
167 165
 }
168 166
 
169 167
 /**
@@ -176,13 +174,19 @@ export function getFormattedEventTime(start: string, end: string): string {
176 174
  * @return {boolean}
177 175
  */
178 176
 export function isDescriptionEmpty(description: ?string): boolean {
179
-    if (description !== undefined && description !== null) {
180
-        return description
181
-            .split('<p>').join('') // Equivalent to a replace all
182
-            .split('</p>').join('')
183
-            .split('<br>').join('').trim() === '';
184
-    } else
185
-        return true;
177
+  if (description !== undefined && description !== null) {
178
+    return (
179
+      description
180
+        .split('<p>')
181
+        .join('') // Equivalent to a replace all
182
+        .split('</p>')
183
+        .join('')
184
+        .split('<br>')
185
+        .join('')
186
+        .trim() === ''
187
+    );
188
+  }
189
+  return true;
186 190
 }
187 191
 
188 192
 /**
@@ -193,17 +197,43 @@ export function isDescriptionEmpty(description: ?string): boolean {
193 197
  * @param numberOfMonths The number of months to create, starting from the current date
194 198
  * @return {Object}
195 199
  */
196
-export function generateEmptyCalendar(numberOfMonths: number): Object {
197
-    let end = new Date(Date.now());
198
-    end.setMonth(end.getMonth() + numberOfMonths);
199
-    let daysOfYear = {};
200
-    for (let d = new Date(Date.now()); d <= end; d.setDate(d.getDate() + 1)) {
201
-        const dateString = getDateOnlyString(
202
-            dateToString(new Date(d), false));
203
-        if (dateString !== null)
204
-            daysOfYear[dateString] = []
200
+export function generateEmptyCalendar(
201
+  numberOfMonths: number,
202
+): {[key: string]: Array<PlanningEventType>} {
203
+  const end = new Date(Date.now());
204
+  end.setMonth(end.getMonth() + numberOfMonths);
205
+  const daysOfYear = {};
206
+  for (let d = new Date(Date.now()); d <= end; d.setDate(d.getDate() + 1)) {
207
+    const dateString = getDateOnlyString(dateToString(new Date(d), false));
208
+    if (dateString !== null) daysOfYear[dateString] = [];
209
+  }
210
+  return daysOfYear;
211
+}
212
+
213
+/**
214
+ * Adds events to the given array depending on their starting date.
215
+ *
216
+ * Events starting before are added at the front.
217
+ *
218
+ * @param eventArray The array to hold sorted events
219
+ * @param event The event to add to the array
220
+ */
221
+export function pushEventInOrder(
222
+  eventArray: Array<PlanningEventType>,
223
+  event: PlanningEventType,
224
+) {
225
+  if (eventArray.length === 0) eventArray.push(event);
226
+  else {
227
+    for (let i = 0; i < eventArray.length; i += 1) {
228
+      if (isEventBefore(event.date_begin, eventArray[i].date_begin)) {
229
+        eventArray.splice(i, 0, event);
230
+        break;
231
+      } else if (i === eventArray.length - 1) {
232
+        eventArray.push(event);
233
+        break;
234
+      }
205 235
     }
206
-    return daysOfYear;
236
+  }
207 237
 }
208 238
 
209 239
 /**
@@ -217,40 +247,17 @@ export function generateEmptyCalendar(numberOfMonths: number): Object {
217 247
  * @param numberOfMonths The number of months to create the agenda for
218 248
  * @return {Object}
219 249
  */
220
-export function generateEventAgenda(eventList: Array<eventObject>, numberOfMonths: number): Object {
221
-    let agendaItems = generateEmptyCalendar(numberOfMonths);
222
-    for (let i = 0; i < eventList.length; i++) {
223
-        const dateString = getDateOnlyString(eventList[i].date_begin);
224
-        if (dateString !== null) {
225
-            const eventArray = agendaItems[dateString];
226
-            if (eventArray !== undefined)
227
-                pushEventInOrder(eventArray, eventList[i]);
228
-        }
229
-
230
-    }
231
-    return agendaItems;
232
-}
233
-
234
-/**
235
- * Adds events to the given array depending on their starting date.
236
- *
237
- * Events starting before are added at the front.
238
- *
239
- * @param eventArray The array to hold sorted events
240
- * @param event The event to add to the array
241
- */
242
-export function pushEventInOrder(eventArray: Array<eventObject>, event: eventObject): Object {
243
-    if (eventArray.length === 0)
244
-        eventArray.push(event);
245
-    else {
246
-        for (let i = 0; i < eventArray.length; i++) {
247
-            if (isEventBefore(event.date_begin, eventArray[i].date_begin)) {
248
-                eventArray.splice(i, 0, event);
249
-                break;
250
-            } else if (i === eventArray.length - 1) {
251
-                eventArray.push(event);
252
-                break;
253
-            }
254
-        }
250
+export function generateEventAgenda(
251
+  eventList: Array<PlanningEventType>,
252
+  numberOfMonths: number,
253
+): {[key: string]: Array<PlanningEventType>} {
254
+  const agendaItems = generateEmptyCalendar(numberOfMonths);
255
+  for (let i = 0; i < eventList.length; i += 1) {
256
+    const dateString = getDateOnlyString(eventList[i].date_begin);
257
+    if (dateString != null) {
258
+      const eventArray = agendaItems[dateString];
259
+      if (eventArray != null) pushEventInOrder(eventArray, eventList[i]);
255 260
     }
261
+  }
262
+  return agendaItems;
256 263
 }

+ 12
- 10
src/utils/WebData.js View File

@@ -100,15 +100,17 @@ export async function apiRequest(
100 100
  * If no data was found, returns an empty object
101 101
  *
102 102
  * @param url The urls to fetch data from
103
- * @return Promise<{...}>
103
+ * @return Promise<any>
104 104
  */
105
-export async function readData(url: string): Promise<{...}> {
106
-  return new Promise(
107
-    (resolve: (response: {...}) => void, reject: () => void) => {
108
-      fetch(url)
109
-        .then(async (response: Response): Promise<{...}> => response.json())
110
-        .then((data: {...}): void => resolve(data))
111
-        .catch((): void => reject());
112
-    },
113
-  );
105
+// eslint-disable-next-line flowtype/no-weak-types
106
+export async function readData(url: string): Promise<any> {
107
+  // eslint-disable-next-line flowtype/no-weak-types
108
+  return new Promise((resolve: (response: any) => void, reject: () => void) => {
109
+    fetch(url)
110
+      // eslint-disable-next-line flowtype/no-weak-types
111
+      .then(async (response: Response): Promise<any> => response.json())
112
+      // eslint-disable-next-line flowtype/no-weak-types
113
+      .then((data: any): void => resolve(data))
114
+      .catch((): void => reject());
115
+  });
114 116
 }

Loading…
Cancel
Save