Browse Source

Improved doc and typing and removed unused file

Arnaud Vergnet 3 years ago
parent
commit
b66e50eaf8

+ 7
- 7
src/screens/About/AboutDependenciesScreen.js View File

4
 import {FlatList} from "react-native";
4
 import {FlatList} from "react-native";
5
 import packageJson from '../../../package';
5
 import packageJson from '../../../package';
6
 import {List} from 'react-native-paper';
6
 import {List} from 'react-native-paper';
7
+import {StackNavigationProp} from "@react-navigation/stack";
7
 
8
 
8
 type listItem = {
9
 type listItem = {
9
     name: string,
10
     name: string,
16
  * @param object The raw json
17
  * @param object The raw json
17
  * @return {Array<listItem>}
18
  * @return {Array<listItem>}
18
  */
19
  */
19
-function generateListFromObject(object: { [string]: string }): Array<listItem> {
20
+function generateListFromObject(object: { [key: string]: string }): Array<listItem> {
20
     let list = [];
21
     let list = [];
21
     let keys = Object.keys(object);
22
     let keys = Object.keys(object);
22
     let values = Object.values(object);
23
     let values = Object.values(object);
28
 }
29
 }
29
 
30
 
30
 type Props = {
31
 type Props = {
31
-    navigation: Object,
32
-    route: Object
32
+    navigation: StackNavigationProp,
33
 }
33
 }
34
 
34
 
35
 const LIST_ITEM_HEIGHT = 64;
35
 const LIST_ITEM_HEIGHT = 64;
39
  */
39
  */
40
 export default class AboutDependenciesScreen extends React.Component<Props> {
40
 export default class AboutDependenciesScreen extends React.Component<Props> {
41
 
41
 
42
-    data: Array<Object>;
42
+    data: Array<listItem>;
43
 
43
 
44
     constructor() {
44
     constructor() {
45
         super();
45
         super();
46
         this.data = generateListFromObject(packageJson.dependencies);
46
         this.data = generateListFromObject(packageJson.dependencies);
47
     }
47
     }
48
 
48
 
49
-    keyExtractor = (item: Object) => item.name;
49
+    keyExtractor = (item: listItem) => item.name;
50
 
50
 
51
-    renderItem = ({item}: Object) =>
51
+    renderItem = ({item}: { item: listItem }) =>
52
         <List.Item
52
         <List.Item
53
             title={item.name}
53
             title={item.name}
54
             description={item.version.replace('^', '').replace('~', '')}
54
             description={item.version.replace('^', '').replace('~', '')}
55
             style={{height: LIST_ITEM_HEIGHT}}
55
             style={{height: LIST_ITEM_HEIGHT}}
56
         />;
56
         />;
57
 
57
 
58
-    itemLayout = (data, index) => ({length: LIST_ITEM_HEIGHT, offset: LIST_ITEM_HEIGHT * index, index});
58
+    itemLayout = (data: any, index: number) => ({length: LIST_ITEM_HEIGHT, offset: LIST_ITEM_HEIGHT * index, index});
59
 
59
 
60
     render() {
60
     render() {
61
         return (
61
         return (

+ 21
- 20
src/screens/About/AboutScreen.js View File

5
 import i18n from "i18n-js";
5
 import i18n from "i18n-js";
6
 import {Avatar, Card, List, Title, withTheme} from 'react-native-paper';
6
 import {Avatar, Card, List, Title, withTheme} from 'react-native-paper';
7
 import packageJson from "../../../package.json";
7
 import packageJson from "../../../package.json";
8
+import {StackNavigationProp} from "@react-navigation/stack";
9
+
10
+type ListItem = {
11
+    onPressCallback: () => void,
12
+    icon: string,
13
+    text: string,
14
+    showChevron: boolean
15
+};
8
 
16
 
9
 const links = {
17
 const links = {
10
     appstore: 'https://apps.apple.com/us/app/campus-amicale-insat/id1477722148',
18
     appstore: 'https://apps.apple.com/us/app/campus-amicale-insat/id1477722148',
29
 };
37
 };
30
 
38
 
31
 type Props = {
39
 type Props = {
32
-    navigation: Object,
40
+    navigation: StackNavigationProp,
33
 };
41
 };
34
 
42
 
35
 /**
43
 /**
48
     /**
56
     /**
49
      * Data to be displayed in the app card
57
      * Data to be displayed in the app card
50
      */
58
      */
51
-    appData: Array<Object> = [
59
+    appData = [
52
         {
60
         {
53
             onPressCallback: () => openWebLink(Platform.OS === "ios" ? links.appstore : links.playstore),
61
             onPressCallback: () => openWebLink(Platform.OS === "ios" ? links.appstore : links.playstore),
54
             icon: Platform.OS === "ios" ? 'apple' : 'google-play',
62
             icon: Platform.OS === "ios" ? 'apple' : 'google-play',
83
     /**
91
     /**
84
      * Data to be displayed in the author card
92
      * Data to be displayed in the author card
85
      */
93
      */
86
-    authorData: Array<Object> = [
94
+    authorData = [
87
         {
95
         {
88
             onPressCallback: () => openWebLink(links.meme),
96
             onPressCallback: () => openWebLink(links.meme),
89
             icon: 'account-circle',
97
             icon: 'account-circle',
106
     /**
114
     /**
107
      * Data to be displayed in the additional developer card
115
      * Data to be displayed in the additional developer card
108
      */
116
      */
109
-    additionalDevData: Array<Object> = [
117
+    additionalDevData = [
110
         {
118
         {
111
             onPressCallback: () => console.log('Meme this'),
119
             onPressCallback: () => console.log('Meme this'),
112
             icon: 'account',
120
             icon: 'account',
129
     /**
137
     /**
130
      * Data to be displayed in the technologies card
138
      * Data to be displayed in the technologies card
131
      */
139
      */
132
-    technoData: Array<Object> = [
140
+    technoData = [
133
         {
141
         {
134
             onPressCallback: () => openWebLink(links.react),
142
             onPressCallback: () => openWebLink(links.react),
135
             icon: 'react',
143
             icon: 'react',
146
     /**
154
     /**
147
      * Order of information cards
155
      * Order of information cards
148
      */
156
      */
149
-    dataOrder: Array<Object> = [
157
+    dataOrder = [
150
         {
158
         {
151
             id: 'app',
159
             id: 'app',
152
         },
160
         },
158
         },
166
         },
159
     ];
167
     ];
160
 
168
 
161
-
162
-    colors: Object;
163
-
164
-    constructor(props) {
165
-        super(props);
166
-        this.colors = props.theme.colors;
167
-    }
168
-
169
     /**
169
     /**
170
      * Gets the app icon
170
      * Gets the app icon
171
+     *
171
      * @param props
172
      * @param props
172
      * @return {*}
173
      * @return {*}
173
      */
174
      */
187
      * @param item The item to extract the key from
188
      * @param item The item to extract the key from
188
      * @return {string} The extracted key
189
      * @return {string} The extracted key
189
      */
190
      */
190
-    keyExtractor(item: Object): string {
191
+    keyExtractor(item: ListItem): string {
191
         return item.icon;
192
         return item.icon;
192
     }
193
     }
193
 
194
 
271
      * @param props
272
      * @param props
272
      * @return {*}
273
      * @return {*}
273
      */
274
      */
274
-    getChevronIcon(props: Object) {
275
+    getChevronIcon(props) {
275
         return (
276
         return (
276
             <List.Icon {...props} icon={'chevron-right'}/>
277
             <List.Icon {...props} icon={'chevron-right'}/>
277
         );
278
         );
284
      * @param props
285
      * @param props
285
      * @return {*}
286
      * @return {*}
286
      */
287
      */
287
-    getItemIcon(item: Object, props: Object) {
288
+    getItemIcon(item: ListItem, props) {
288
         return (
289
         return (
289
             <List.Icon {...props} icon={item.icon}/>
290
             <List.Icon {...props} icon={item.icon}/>
290
         );
291
         );
291
     }
292
     }
292
 
293
 
293
     /**
294
     /**
294
-     * Get a clickable card item to be rendered inside a card.
295
+     * Gets a clickable card item to be rendered inside a card.
295
      *
296
      *
296
      * @returns {*}
297
      * @returns {*}
297
      */
298
      */
298
-    getCardItem = ({item}: Object) => {
299
+    getCardItem = ({item}: { item: ListItem }) => {
299
         const getItemIcon = this.getItemIcon.bind(this, item);
300
         const getItemIcon = this.getItemIcon.bind(this, item);
300
         if (item.showChevron) {
301
         if (item.showChevron) {
301
             return (
302
             return (
323
      * @param item The item to show
324
      * @param item The item to show
324
      * @return {*}
325
      * @return {*}
325
      */
326
      */
326
-    getMainCard = ({item}: Object) => {
327
+    getMainCard = ({item}: { item: { id: string } }) => {
327
         switch (item.id) {
328
         switch (item.id) {
328
             case 'app':
329
             case 'app':
329
                 return this.getAppCard();
330
                 return this.getAppCard();

+ 36
- 19
src/screens/About/DebugScreen.js View File

5
 import AsyncStorageManager from "../../managers/AsyncStorageManager";
5
 import AsyncStorageManager from "../../managers/AsyncStorageManager";
6
 import CustomModal from "../../components/Overrides/CustomModal";
6
 import CustomModal from "../../components/Overrides/CustomModal";
7
 import {Button, List, Subheading, TextInput, Title, withTheme} from 'react-native-paper';
7
 import {Button, List, Subheading, TextInput, Title, withTheme} from 'react-native-paper';
8
+import {StackNavigationProp} from "@react-navigation/stack";
9
+import {Modalize} from "react-native-modalize";
10
+import type {CustomTheme} from "../../managers/ThemeManager";
11
+
12
+type PreferenceItem = {
13
+    key: string,
14
+    default: string,
15
+    current: string,
16
+}
8
 
17
 
9
 type Props = {
18
 type Props = {
10
-    navigation: Object,
19
+    navigation: StackNavigationProp,
20
+    theme: CustomTheme
11
 };
21
 };
12
 
22
 
13
 type State = {
23
 type State = {
14
-    modalCurrentDisplayItem: Object,
15
-    currentPreferences: Array<Object>,
24
+    modalCurrentDisplayItem: PreferenceItem,
25
+    currentPreferences: Array<PreferenceItem>,
16
 }
26
 }
17
 
27
 
18
 /**
28
 /**
21
  */
31
  */
22
 class DebugScreen extends React.Component<Props, State> {
32
 class DebugScreen extends React.Component<Props, State> {
23
 
33
 
24
-    modalRef: Object;
25
-    modalInputValue = '';
26
-
27
-    onModalRef: Function;
28
-
29
-    colors: Object;
34
+    modalRef: Modalize;
35
+    modalInputValue: string;
30
 
36
 
37
+    /**
38
+     * Copies user preferences to state for easier manipulation
39
+     *
40
+     * @param props
41
+     */
31
     constructor(props) {
42
     constructor(props) {
32
         super(props);
43
         super(props);
33
-        this.onModalRef = this.onModalRef.bind(this);
34
-        this.colors = props.theme.colors;
44
+        this.modalInputValue = "";
35
         let copy = {...AsyncStorageManager.getInstance().preferences};
45
         let copy = {...AsyncStorageManager.getInstance().preferences};
36
-        let currentPreferences = [];
37
-        Object.values(copy).map((object) => {
46
+        let currentPreferences : Array<PreferenceItem> = [];
47
+        Object.values(copy).map((object: any) => {
38
             currentPreferences.push(object);
48
             currentPreferences.push(object);
39
         });
49
         });
40
         this.state = {
50
         this.state = {
44
     }
54
     }
45
 
55
 
46
     /**
56
     /**
47
-     * Show the edit modal
57
+     * Shows the edit modal
58
+     *
48
      * @param item
59
      * @param item
49
      */
60
      */
50
-    showEditModal(item: Object) {
61
+    showEditModal(item: PreferenceItem) {
51
         this.setState({
62
         this.setState({
52
             modalCurrentDisplayItem: item
63
             modalCurrentDisplayItem: item
53
         });
64
         });
81
                     <Button
92
                     <Button
82
                         mode="contained"
93
                         mode="contained"
83
                         dark={true}
94
                         dark={true}
84
-                        color={this.colors.success}
95
+                        color={this.props.theme.colors.success}
85
                         onPress={() => this.saveNewPrefs(this.state.modalCurrentDisplayItem.key, this.modalInputValue)}>
96
                         onPress={() => this.saveNewPrefs(this.state.modalCurrentDisplayItem.key, this.modalInputValue)}>
86
                         Save new value
97
                         Save new value
87
                     </Button>
98
                     </Button>
88
                     <Button
99
                     <Button
89
                         mode="contained"
100
                         mode="contained"
90
                         dark={true}
101
                         dark={true}
91
-                        color={this.colors.danger}
102
+                        color={this.props.theme.colors.danger}
92
                         onPress={() => this.saveNewPrefs(this.state.modalCurrentDisplayItem.key, this.state.modalCurrentDisplayItem.default)}>
103
                         onPress={() => this.saveNewPrefs(this.state.modalCurrentDisplayItem.key, this.state.modalCurrentDisplayItem.default)}>
93
                         Reset to default
104
                         Reset to default
94
                     </Button>
105
                     </Button>
98
         );
109
         );
99
     }
110
     }
100
 
111
 
112
+    /**
113
+     * Finds the index of the given key in the preferences array
114
+     *
115
+     * @param key THe key to find the index of
116
+     * @returns {number}
117
+     */
101
     findIndexOfKey(key: string) {
118
     findIndexOfKey(key: string) {
102
         let index = -1;
119
         let index = -1;
103
         for (let i = 0; i < this.state.currentPreferences.length; i++) {
120
         for (let i = 0; i < this.state.currentPreferences.length; i++) {
130
      *
147
      *
131
      * @param ref
148
      * @param ref
132
      */
149
      */
133
-    onModalRef(ref: Object) {
150
+    onModalRef = (ref: Modalize) => {
134
         this.modalRef = ref;
151
         this.modalRef = ref;
135
     }
152
     }
136
 
153
 
137
-    renderItem = ({item}: Object) => {
154
+    renderItem = ({item}: {item: PreferenceItem}) => {
138
         return (
155
         return (
139
             <List.Item
156
             <List.Item
140
                 title={item.key}
157
                 title={item.key}

+ 10
- 13
src/screens/Amicale/AmicaleContactScreen.js View File

12
     collapsibleStack: Collapsible
12
     collapsibleStack: Collapsible
13
 };
13
 };
14
 
14
 
15
-type State = {};
15
+type DatasetItem = {
16
+    name: string,
17
+    email: string,
18
+    icon: string,
19
+}
16
 
20
 
17
 /**
21
 /**
18
  * Class defining a planning event information page.
22
  * Class defining a planning event information page.
19
  */
23
  */
20
-class AmicaleContactScreen extends React.Component<Props, State> {
21
-
24
+class AmicaleContactScreen extends React.Component<Props> {
22
 
25
 
26
+    // Dataset containing information about contacts
23
     CONTACT_DATASET = [
27
     CONTACT_DATASET = [
24
         {
28
         {
25
             name: i18n.t("amicaleAbout.roles.interSchools"),
29
             name: i18n.t("amicaleAbout.roles.interSchools"),
68
         },
72
         },
69
     ];
73
     ];
70
 
74
 
71
-    colors: Object;
72
-
73
-    constructor(props) {
74
-        super(props);
75
-        this.colors = props.theme.colors;
76
-    }
77
-
78
-    keyExtractor = (item: Object) => item.email;
75
+    keyExtractor = (item: DatasetItem) => item.email;
79
 
76
 
80
-    getChevronIcon = (props: Object) => <List.Icon {...props} icon={'chevron-right'}/>;
77
+    getChevronIcon = (props) => <List.Icon {...props} icon={'chevron-right'}/>;
81
 
78
 
82
-    renderItem = ({item}: Object) => {
79
+    renderItem = ({item}: { item: DatasetItem }) => {
83
         const onPress = () => Linking.openURL('mailto:' + item.email);
80
         const onPress = () => Linking.openURL('mailto:' + item.email);
84
         return <List.Item
81
         return <List.Item
85
             title={item.name}
82
             title={item.name}

+ 0
- 85
src/screens/Amicale/AmicaleHomeScreen.js View File

1
-// @flow
2
-
3
-import * as React from 'react';
4
-import {ScrollView, StyleSheet} from "react-native";
5
-import {Button, withTheme} from 'react-native-paper';
6
-
7
-type Props = {
8
-    navigation: Object,
9
-    route: Object,
10
-}
11
-
12
-type State = {}
13
-
14
-class AmicaleHomeScreen extends React.Component<Props, State> {
15
-
16
-    state = {};
17
-
18
-    colors: Object;
19
-
20
-    constructor(props) {
21
-        super(props);
22
-
23
-        this.colors = props.theme.colors;
24
-    }
25
-
26
-    render() {
27
-        const nav = this.props.navigation;
28
-        return (
29
-            <ScrollView>
30
-                <Button
31
-                    icon={"login"}
32
-                    onPress={() => nav.navigate("login")}
33
-                >
34
-                    LOGIN
35
-                </Button>
36
-                <Button
37
-                    icon={"information"}
38
-                    onPress={() => nav.navigate("amicale-contact")}
39
-                >
40
-                    INFO
41
-                </Button>
42
-                <Button
43
-                    icon={"information"}
44
-                    onPress={() => nav.navigate("club-list")}
45
-                >
46
-                    CLUBS
47
-                </Button>
48
-                <Button
49
-                    icon={"information"}
50
-                    onPress={() => nav.navigate("profile")}
51
-                >
52
-                    PROFILE
53
-                </Button>
54
-                <Button
55
-                    icon={"information"}
56
-                    onPress={() => nav.navigate("vote")}
57
-                >
58
-                    VOTE
59
-                </Button>
60
-            </ScrollView>
61
-        );
62
-    }
63
-}
64
-
65
-const styles = StyleSheet.create({
66
-    container: {
67
-        flex: 1,
68
-        flexDirection: 'column',
69
-        justifyContent: 'center',
70
-    },
71
-    card: {
72
-        margin: 10,
73
-    },
74
-    header: {
75
-        fontSize: 36,
76
-        marginBottom: 48
77
-    },
78
-    textInput: {},
79
-    btnContainer: {
80
-        marginTop: 5,
81
-        marginBottom: 10,
82
-    }
83
-});
84
-
85
-export default withTheme(AmicaleHomeScreen);

+ 2
- 16
src/screens/Amicale/Clubs/ClubAboutScreen.js View File

6
 import i18n from 'i18n-js';
6
 import i18n from 'i18n-js';
7
 import Autolink from "react-native-autolink";
7
 import Autolink from "react-native-autolink";
8
 
8
 
9
-type Props = {
10
-};
11
-
12
-type State = {
13
-};
9
+type Props = {};
14
 
10
 
15
 const CONTACT_LINK = 'clubs@amicale-insat.fr';
11
 const CONTACT_LINK = 'clubs@amicale-insat.fr';
16
 
12
 
17
-/**
18
- * Class defining a planning event information page.
19
- */
20
-class ClubAboutScreen extends React.Component<Props, State> {
21
-
22
-    colors: Object;
23
-
24
-    constructor(props) {
25
-        super(props);
26
-        this.colors = props.theme.colors;
27
-    }
13
+class ClubAboutScreen extends React.Component<Props> {
28
 
14
 
29
     render() {
15
     render() {
30
         return (
16
         return (

+ 44
- 11
src/screens/Amicale/Clubs/ClubDisplayScreen.js View File

65
         }
65
         }
66
     }
66
     }
67
 
67
 
68
+    /**
69
+     * Gets the name of the category with the given ID
70
+     *
71
+     * @param id The category's ID
72
+     * @returns {string|*}
73
+     */
68
     getCategoryName(id: number) {
74
     getCategoryName(id: number) {
69
         if (this.categories !== null) {
75
         if (this.categories !== null) {
70
             for (let i = 0; i < this.categories.length; i++) {
76
             for (let i = 0; i < this.categories.length; i++) {
75
         return "";
81
         return "";
76
     }
82
     }
77
 
83
 
84
+    /**
85
+     * Gets the view for rendering categories
86
+     *
87
+     * @param categories The categories to display (max 2)
88
+     * @returns {null|*}
89
+     */
78
     getCategoriesRender(categories: [number, number]) {
90
     getCategoriesRender(categories: [number, number]) {
79
         if (this.categories === null)
91
         if (this.categories === null)
80
             return null;
92
             return null;
95
         return <View style={{flexDirection: 'row', marginTop: 5}}>{final}</View>;
107
         return <View style={{flexDirection: 'row', marginTop: 5}}>{final}</View>;
96
     }
108
     }
97
 
109
 
98
-    getManagersRender(resp: Array<string>, email: string | null) {
99
-        let final = [];
100
-        for (let i = 0; i < resp.length; i++) {
101
-            final.push(<Paragraph key={i.toString()}>{resp[i]}</Paragraph>)
110
+    /**
111
+     * Gets the view for rendering club managers if any
112
+     *
113
+     * @param managers The list of manager names
114
+     * @param email The club contact email
115
+     * @returns {*}
116
+     */
117
+    getManagersRender(managers: Array<string>, email: string | null) {
118
+        let managersListView = [];
119
+        for (let i = 0; i < managers.length; i++) {
120
+            managersListView.push(<Paragraph key={i.toString()}>{managers[i]}</Paragraph>)
102
         }
121
         }
103
-        const hasManagers = resp.length > 0;
122
+        const hasManagers = managers.length > 0;
104
         return (
123
         return (
105
             <Card style={{marginTop: 10, marginBottom: CustomTabBar.TAB_BAR_HEIGHT + 20}}>
124
             <Card style={{marginTop: 10, marginBottom: CustomTabBar.TAB_BAR_HEIGHT + 20}}>
106
                 <Card.Title
125
                 <Card.Title
113
                         icon="account-tie"/>}
132
                         icon="account-tie"/>}
114
                 />
133
                 />
115
                 <Card.Content>
134
                 <Card.Content>
116
-                    {final}
135
+                    {managersListView}
117
                     {this.getEmailButton(email, hasManagers)}
136
                     {this.getEmailButton(email, hasManagers)}
118
                 </Card.Content>
137
                 </Card.Content>
119
             </Card>
138
             </Card>
120
         );
139
         );
121
     }
140
     }
122
 
141
 
142
+    /**
143
+     * Gets the email button to contact the club, or the amicale if the club does not have any managers
144
+     *
145
+     * @param email The club contact email
146
+     * @param hasManagers True if the club has managers
147
+     * @returns {*}
148
+     */
123
     getEmailButton(email: string | null, hasManagers: boolean) {
149
     getEmailButton(email: string | null, hasManagers: boolean) {
124
         const destinationEmail = email != null && hasManagers
150
         const destinationEmail = email != null && hasManagers
125
             ? email
151
             ? email
141
         );
167
         );
142
     }
168
     }
143
 
169
 
144
-    updateHeaderTitle(data: Object) {
170
+    /**
171
+     * Updates the header title to match the given club
172
+     *
173
+     * @param data The club data
174
+     */
175
+    updateHeaderTitle(data: club) {
145
         this.props.navigation.setOptions({title: data.name})
176
         this.props.navigation.setOptions({title: data.name})
146
     }
177
     }
147
 
178
 
148
-    getScreen = (response: Array<Object>) => {
149
-        let data: club = response[0];
150
-        this.updateHeaderTitle(data);
179
+    getScreen = (response: Array<{ [key: string]: any } | null>) => {
180
+        let data: club | null = null;
181
+        if (response[0] != null) {
182
+            data = response[0];
183
+            this.updateHeaderTitle(data);
184
+        }
151
         if (data != null) {
185
         if (data != null) {
152
             return (
186
             return (
153
                 <ScrollView style={{paddingLeft: 5, paddingRight: 5}}>
187
                 <ScrollView style={{paddingLeft: 5, paddingRight: 5}}>
184
             );
218
             );
185
         } else
219
         } else
186
             return null;
220
             return null;
187
-
188
     };
221
     };
189
 
222
 
190
     render() {
223
     render() {

+ 26
- 0
src/screens/Amicale/Clubs/ClubListScreen.js View File

131
 
131
 
132
     onChipSelect = (id: number) => this.updateFilteredData(null, id);
132
     onChipSelect = (id: number) => this.updateFilteredData(null, id);
133
 
133
 
134
+    /**
135
+     * Updates the search string and category filter, saving them to the State.
136
+     *
137
+     * If the given category is already in the filter, it removes it.
138
+     * Otherwise it adds it to the filter.
139
+     *
140
+     * @param filterStr The new filter string to use
141
+     * @param categoryId The category to add/remove from the filter
142
+     */
134
     updateFilteredData(filterStr: string | null, categoryId: number | null) {
143
     updateFilteredData(filterStr: string | null, categoryId: number | null) {
135
         let newCategoriesState = [...this.state.currentlySelectedCategories];
144
         let newCategoriesState = [...this.state.currentlySelectedCategories];
136
         let newStrState = this.state.currentSearchString;
145
         let newStrState = this.state.currentSearchString;
150
             })
159
             })
151
     }
160
     }
152
 
161
 
162
+    /**
163
+     * Gets the list header, with controls to change the categories filter
164
+     *
165
+     * @returns {*}
166
+     */
153
     getListHeader() {
167
     getListHeader() {
154
         return <ClubListHeader
168
         return <ClubListHeader
155
             categories={this.categories}
169
             categories={this.categories}
158
         />;
172
         />;
159
     }
173
     }
160
 
174
 
175
+    /**
176
+     * Gets the category object of the given ID
177
+     *
178
+     * @param id The ID of the category to find
179
+     * @returns {*}
180
+     */
161
     getCategoryOfId = (id: number) => {
181
     getCategoryOfId = (id: number) => {
162
         for (let i = 0; i < this.categories.length; i++) {
182
         for (let i = 0; i < this.categories.length; i++) {
163
             if (id === this.categories[i].id)
183
             if (id === this.categories[i].id)
165
         }
185
         }
166
     };
186
     };
167
 
187
 
188
+    /**
189
+     * Checks if the given item should be rendered according to current name and category filters
190
+     *
191
+     * @param item The club to check
192
+     * @returns {boolean}
193
+     */
168
     shouldRenderItem(item: club) {
194
     shouldRenderItem(item: club) {
169
         let shouldRender = this.state.currentlySelectedCategories.length === 0
195
         let shouldRender = this.state.currentlySelectedCategories.length === 0
170
             || isItemInCategoryFilter(this.state.currentlySelectedCategories, item.category);
196
             || isItemInCategoryFilter(this.state.currentlySelectedCategories, item.category);

+ 93
- 10
src/screens/Amicale/LoginScreen.js View File

11
 import CustomTabBar from "../../components/Tabbar/CustomTabBar";
11
 import CustomTabBar from "../../components/Tabbar/CustomTabBar";
12
 import type {CustomTheme} from "../../managers/ThemeManager";
12
 import type {CustomTheme} from "../../managers/ThemeManager";
13
 import AsyncStorageManager from "../../managers/AsyncStorageManager";
13
 import AsyncStorageManager from "../../managers/AsyncStorageManager";
14
+import {StackNavigationProp} from "@react-navigation/stack";
14
 
15
 
15
 type Props = {
16
 type Props = {
16
-    navigation: Object,
17
-    route: Object,
17
+    navigation: StackNavigationProp,
18
+    route: { params: { nextScreen: string } },
18
     collapsibleStack: Collapsible,
19
     collapsibleStack: Collapsible,
19
     theme: CustomTheme
20
     theme: CustomTheme
20
 }
21
 }
47
         dialogError: 0,
48
         dialogError: 0,
48
     };
49
     };
49
 
50
 
50
-    onEmailChange: Function;
51
-    onPasswordChange: Function;
52
-    passwordInputRef: Object;
51
+    onEmailChange: (value: string) => null;
52
+    onPasswordChange: (value: string) => null;
53
+    passwordInputRef: { current: null | TextInput };
53
 
54
 
54
     nextScreen: string | null;
55
     nextScreen: string | null;
55
 
56
 
64
         this.handleNavigationParams();
65
         this.handleNavigationParams();
65
     };
66
     };
66
 
67
 
67
-    handleNavigationParams () {
68
+    /**
69
+     * Saves the screen to navigate to after a successful login if one was provided in navigation parameters
70
+     */
71
+    handleNavigationParams() {
68
         if (this.props.route.params != null) {
72
         if (this.props.route.params != null) {
69
             if (this.props.route.params.nextScreen != null)
73
             if (this.props.route.params.nextScreen != null)
70
                 this.nextScreen = this.props.route.params.nextScreen;
74
                 this.nextScreen = this.props.route.params.nextScreen;
73
         }
77
         }
74
     }
78
     }
75
 
79
 
80
+    /**
81
+     * Shows an error dialog with the corresponding login error
82
+     *
83
+     * @param error The error given by the login request
84
+     */
76
     showErrorDialog = (error: number) =>
85
     showErrorDialog = (error: number) =>
77
         this.setState({
86
         this.setState({
78
             dialogVisible: true,
87
             dialogVisible: true,
81
 
90
 
82
     hideErrorDialog = () => this.setState({dialogVisible: false});
91
     hideErrorDialog = () => this.setState({dialogVisible: false});
83
 
92
 
93
+    /**
94
+     * Navigates to the screen specified in navigation parameters or simply go back tha stack.
95
+     * Saves in user preferences to not show the login banner again.
96
+     */
84
     handleSuccess = () => {
97
     handleSuccess = () => {
85
         // Do not show the login banner again
98
         // Do not show the login banner again
86
         AsyncStorageManager.getInstance().savePref(
99
         AsyncStorageManager.getInstance().savePref(
93
             this.props.navigation.replace(this.nextScreen);
106
             this.props.navigation.replace(this.nextScreen);
94
     };
107
     };
95
 
108
 
109
+    /**
110
+     * Navigates to the Amicale website screen with the reset password link as navigation parameters
111
+     */
96
     onResetPasswordClick = () => this.props.navigation.navigate('amicale-website', {path: RESET_PASSWORD_PATH});
112
     onResetPasswordClick = () => this.props.navigation.navigate('amicale-website', {path: RESET_PASSWORD_PATH});
97
 
113
 
114
+    /**
115
+     * The user has unfocused the input, his email is ready to be validated
116
+     */
98
     validateEmail = () => this.setState({isEmailValidated: true});
117
     validateEmail = () => this.setState({isEmailValidated: true});
99
 
118
 
119
+    /**
120
+     * Checks if the entered email is valid (matches the regex)
121
+     *
122
+     * @returns {boolean}
123
+     */
100
     isEmailValid() {
124
     isEmailValid() {
101
         return emailRegex.test(this.state.email);
125
         return emailRegex.test(this.state.email);
102
     }
126
     }
103
 
127
 
128
+    /**
129
+     * Checks if we should tell the user his email is invalid.
130
+     * We should only show this if his email is invalid and has been checked when un-focusing the input
131
+     *
132
+     * @returns {boolean|boolean}
133
+     */
104
     shouldShowEmailError() {
134
     shouldShowEmailError() {
105
         return this.state.isEmailValidated && !this.isEmailValid();
135
         return this.state.isEmailValidated && !this.isEmailValid();
106
     }
136
     }
107
 
137
 
138
+    /**
139
+     * The user has unfocused the input, his password is ready to be validated
140
+     */
108
     validatePassword = () => this.setState({isPasswordValidated: true});
141
     validatePassword = () => this.setState({isPasswordValidated: true});
109
 
142
 
143
+    /**
144
+     * Checks if the user has entered a password
145
+     *
146
+     * @returns {boolean}
147
+     */
110
     isPasswordValid() {
148
     isPasswordValid() {
111
         return this.state.password !== '';
149
         return this.state.password !== '';
112
     }
150
     }
113
 
151
 
152
+    /**
153
+     * Checks if we should tell the user his password is invalid.
154
+     * We should only show this if his password is invalid and has been checked when un-focusing the input
155
+     *
156
+     * @returns {boolean|boolean}
157
+     */
114
     shouldShowPasswordError() {
158
     shouldShowPasswordError() {
115
         return this.state.isPasswordValidated && !this.isPasswordValid();
159
         return this.state.isPasswordValidated && !this.isPasswordValid();
116
     }
160
     }
117
 
161
 
162
+    /**
163
+     * If the email and password are valid, and we are not loading a request, then the login button can be enabled
164
+     *
165
+     * @returns {boolean}
166
+     */
118
     shouldEnableLogin() {
167
     shouldEnableLogin() {
119
         return this.isEmailValid() && this.isPasswordValid() && !this.state.loading;
168
         return this.isEmailValid() && this.isPasswordValid() && !this.state.loading;
120
     }
169
     }
121
 
170
 
171
+    /**
172
+     * Called when the user input changes in the email or password field.
173
+     * This saves the new value in the State and disabled input validation (to prevent errors to show while typing)
174
+     *
175
+     * @param isEmail True if the field is the email field
176
+     * @param value The new field value
177
+     */
122
     onInputChange(isEmail: boolean, value: string) {
178
     onInputChange(isEmail: boolean, value: string) {
123
         if (isEmail) {
179
         if (isEmail) {
124
             this.setState({
180
             this.setState({
133
         }
189
         }
134
     }
190
     }
135
 
191
 
136
-    onEmailSubmit = () => this.passwordInputRef.focus();
192
+    /**
193
+     * Focuses the password field when the email field is done
194
+     *
195
+     * @returns {*}
196
+     */
197
+    onEmailSubmit = () => {
198
+        if (this.passwordInputRef.current != null)
199
+            this.passwordInputRef.current.focus();
200
+    }
137
 
201
 
202
+    /**
203
+     * Called when the user clicks on login or finishes to type his password.
204
+     *
205
+     * Checks if we should allow the user to login,
206
+     * then makes the login request and enters a loading state until the request finishes
207
+     *
208
+     */
138
     onSubmit = () => {
209
     onSubmit = () => {
139
         if (this.shouldEnableLogin()) {
210
         if (this.shouldEnableLogin()) {
140
             this.setState({loading: true});
211
             this.setState({loading: true});
147
         }
218
         }
148
     };
219
     };
149
 
220
 
221
+    /**
222
+     * Gets the form input
223
+     *
224
+     * @returns {*}
225
+     */
150
     getFormInput() {
226
     getFormInput() {
151
         return (
227
         return (
152
             <View>
228
             <View>
173
                     {i18n.t("loginScreen.emailError")}
249
                     {i18n.t("loginScreen.emailError")}
174
                 </HelperText>
250
                 </HelperText>
175
                 <TextInput
251
                 <TextInput
176
-                    ref={(ref) => {
177
-                        this.passwordInputRef = ref;
178
-                    }}
252
+                    ref={this.passwordInputRef}
179
                     label={i18n.t("loginScreen.password")}
253
                     label={i18n.t("loginScreen.password")}
180
                     mode='outlined'
254
                     mode='outlined'
181
                     value={this.state.password}
255
                     value={this.state.password}
201
         );
275
         );
202
     }
276
     }
203
 
277
 
278
+    /**
279
+     * Gets the card containing the input form
280
+     * @returns {*}
281
+     */
204
     getMainCard() {
282
     getMainCard() {
205
         return (
283
         return (
206
             <Card style={styles.card}>
284
             <Card style={styles.card}>
239
         );
317
         );
240
     }
318
     }
241
 
319
 
320
+    /**
321
+     * Gets the card containing the information about the Amicale account
322
+     *
323
+     * @returns {*}
324
+     */
242
     getSecondaryCard() {
325
     getSecondaryCard() {
243
         return (
326
         return (
244
             <Card style={styles.card}>
327
             <Card style={styles.card}>

+ 54
- 12
src/screens/Amicale/ProfileScreen.js View File

12
 import {withCollapsible} from "../../utils/withCollapsible";
12
 import {withCollapsible} from "../../utils/withCollapsible";
13
 import type {cardList} from "../../components/Lists/CardList/CardList";
13
 import type {cardList} from "../../components/Lists/CardList/CardList";
14
 import CardList from "../../components/Lists/CardList/CardList";
14
 import CardList from "../../components/Lists/CardList/CardList";
15
+import {StackNavigationProp} from "@react-navigation/stack";
16
+import type {CustomTheme} from "../../managers/ThemeManager";
15
 
17
 
16
 type Props = {
18
 type Props = {
17
-    navigation: Object,
18
-    theme: Object,
19
+    navigation: StackNavigationProp,
20
+    theme: CustomTheme,
19
     collapsibleStack: Collapsible,
21
     collapsibleStack: Collapsible,
20
 }
22
 }
21
 
23
 
23
     dialogVisible: boolean,
25
     dialogVisible: boolean,
24
 }
26
 }
25
 
27
 
28
+type ProfileData = {
29
+    first_name: string,
30
+    last_name: string,
31
+    email: string,
32
+    birthday: string,
33
+    phone: string,
34
+    branch: string,
35
+    link: string,
36
+    validity: boolean,
37
+    clubs: Array<Club>,
38
+}
39
+type Club = {
40
+    id: number,
41
+    name: string,
42
+    is_manager: boolean,
43
+}
44
+
26
 const CLUBS_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/Clubs.png";
45
 const CLUBS_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/Clubs.png";
27
 const VOTE_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/Vote.png";
46
 const VOTE_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/Vote.png";
28
 
47
 
34
         dialogVisible: false,
53
         dialogVisible: false,
35
     };
54
     };
36
 
55
 
37
-    data: Object;
56
+    data: ProfileData;
38
 
57
 
39
-    flatListData: Array<Object>;
58
+    flatListData: Array<{ id: string }>;
40
     amicaleDataset: cardList;
59
     amicaleDataset: cardList;
41
 
60
 
42
     constructor() {
61
     constructor() {
79
 
98
 
80
     hideDisconnectDialog = () => this.setState({dialogVisible: false});
99
     hideDisconnectDialog = () => this.setState({dialogVisible: false});
81
 
100
 
101
+    /**
102
+     * Gets the logout header button
103
+     *
104
+     * @returns {*}
105
+     */
82
     getHeaderButton = () => <MaterialHeaderButtons>
106
     getHeaderButton = () => <MaterialHeaderButtons>
83
         <Item title="logout" iconName="logout" onPress={this.showDisconnectDialog}/>
107
         <Item title="logout" iconName="logout" onPress={this.showDisconnectDialog}/>
84
     </MaterialHeaderButtons>;
108
     </MaterialHeaderButtons>;
85
 
109
 
86
-    getScreen = (data: Object) => {
87
-        this.data = data[0];
110
+    /**
111
+     * Gets the main screen component with the fetched data
112
+     *
113
+     * @param data The data fetched from the server
114
+     * @returns {*}
115
+     */
116
+    getScreen = (data: Array<{ [key: string]: any } | null>) => {
117
+        if (data[0] != null) {
118
+            this.data = data[0];
119
+        }
88
         const {containerPaddingTop, scrollIndicatorInsetTop, onScroll} = this.props.collapsibleStack;
120
         const {containerPaddingTop, scrollIndicatorInsetTop, onScroll} = this.props.collapsibleStack;
89
         return (
121
         return (
90
             <View style={{flex: 1}}>
122
             <View style={{flex: 1}}>
109
         )
141
         )
110
     };
142
     };
111
 
143
 
112
-    getRenderItem = ({item}: Object) => {
144
+    getRenderItem = ({item}: { item: { id: string } }) => {
113
         switch (item.id) {
145
         switch (item.id) {
114
             case '0':
146
             case '0':
115
                 return this.getWelcomeCard();
147
                 return this.getWelcomeCard();
122
         }
154
         }
123
     };
155
     };
124
 
156
 
157
+    /**
158
+     * Gets the list of services available with the Amicale account
159
+     *
160
+     * @returns {*}
161
+     */
125
     getServicesList() {
162
     getServicesList() {
126
         return (
163
         return (
127
             <CardList
164
             <CardList
131
         );
168
         );
132
     }
169
     }
133
 
170
 
171
+    /**
172
+     * Gets a card welcoming the user to his account
173
+     *
174
+     * @returns {*}
175
+     */
134
     getWelcomeCard() {
176
     getWelcomeCard() {
135
         return (
177
         return (
136
             <Card style={styles.card}>
178
             <Card style={styles.card}>
137
                 <Card.Title
179
                 <Card.Title
138
                     title={i18n.t("profileScreen.welcomeTitle", {name: this.data.first_name})}
180
                     title={i18n.t("profileScreen.welcomeTitle", {name: this.data.first_name})}
139
-                    left={(props) => <Avatar.Image
181
+                    left={() => <Avatar.Image
140
                         size={64}
182
                         size={64}
141
                         source={ICON_AMICALE}
183
                         source={ICON_AMICALE}
142
                         style={{backgroundColor: 'transparent',}}
184
                         style={{backgroundColor: 'transparent',}}
340
      * @param item The club to render
382
      * @param item The club to render
341
      * @return {*}
383
      * @return {*}
342
      */
384
      */
343
-    clubListItem = ({item}: Object) => {
385
+    clubListItem = ({item}: { item: Club }) => {
344
         const onPress = () => this.openClubDetailsScreen(item.id);
386
         const onPress = () => this.openClubDetailsScreen(item.id);
345
         let description = i18n.t("profileScreen.isMember");
387
         let description = i18n.t("profileScreen.isMember");
346
         let icon = (props) => <List.Icon {...props} icon="chevron-right"/>;
388
         let icon = (props) => <List.Icon {...props} icon="chevron-right"/>;
356
         />;
398
         />;
357
     };
399
     };
358
 
400
 
359
-    clubKeyExtractor = (item: Object) => item.name;
401
+    clubKeyExtractor = (item: Club) => item.name;
360
 
402
 
361
-    sortClubList = (a: Object, b: Object) => a.is_manager ? -1 : 1;
403
+    sortClubList = (a: Club, b: Club) => a.is_manager ? -1 : 1;
362
 
404
 
363
     /**
405
     /**
364
      * Renders the list of clubs the user is part of
406
      * Renders the list of clubs the user is part of
366
      * @param list The club list
408
      * @param list The club list
367
      * @return {*}
409
      * @return {*}
368
      */
410
      */
369
-    getClubList(list: Array<Object>) {
411
+    getClubList(list: Array<Club>) {
370
         list.sort(this.sortClubList);
412
         list.sort(this.sortClubList);
371
         return (
413
         return (
372
             //$FlowFixMe
414
             //$FlowFixMe

+ 36
- 7
src/screens/Amicale/VoteScreen.js View File

9
 import VoteSelect from "../../components/Amicale/Vote/VoteSelect";
9
 import VoteSelect from "../../components/Amicale/Vote/VoteSelect";
10
 import VoteResults from "../../components/Amicale/Vote/VoteResults";
10
 import VoteResults from "../../components/Amicale/Vote/VoteResults";
11
 import VoteWait from "../../components/Amicale/Vote/VoteWait";
11
 import VoteWait from "../../components/Amicale/Vote/VoteWait";
12
+import {StackNavigationProp} from "@react-navigation/stack";
12
 
13
 
13
 export type team = {
14
 export type team = {
14
     id: number,
15
     id: number,
86
 const MIN_REFRESH_TIME = 5 * 1000;
87
 const MIN_REFRESH_TIME = 5 * 1000;
87
 
88
 
88
 type Props = {
89
 type Props = {
89
-    navigation: Object
90
+    navigation: StackNavigationProp
90
 }
91
 }
91
 
92
 
92
 type State = {
93
 type State = {
93
     hasVoted: boolean,
94
     hasVoted: boolean,
94
 }
95
 }
95
 
96
 
97
+/**
98
+ * Screen displaying vote information and controls
99
+ */
96
 export default class VoteScreen extends React.Component<Props, State> {
100
 export default class VoteScreen extends React.Component<Props, State> {
97
 
101
 
98
     state = {
102
     state = {
107
     today: Date;
111
     today: Date;
108
 
112
 
109
     mainFlatListData: Array<{ key: string }>;
113
     mainFlatListData: Array<{ key: string }>;
110
-    lastRefresh: Date;
114
+    lastRefresh: Date | null;
111
 
115
 
112
     authRef: { current: null | AuthenticatedScreen };
116
     authRef: { current: null | AuthenticatedScreen };
113
 
117
 
116
         this.hasVoted = false;
120
         this.hasVoted = false;
117
         this.today = new Date();
121
         this.today = new Date();
118
         this.authRef = React.createRef();
122
         this.authRef = React.createRef();
123
+        this.lastRefresh = null;
119
         this.mainFlatListData = [
124
         this.mainFlatListData = [
120
             {key: 'main'},
125
             {key: 'main'},
121
             {key: 'info'},
126
             {key: 'info'},
122
         ]
127
         ]
123
     }
128
     }
124
 
129
 
130
+    /**
131
+     * Reloads vote data if last refresh delta is smaller than the minimum refresh time
132
+     */
125
     reloadData = () => {
133
     reloadData = () => {
126
         let canRefresh;
134
         let canRefresh;
127
-        if (this.lastRefresh !== undefined)
128
-            canRefresh = (new Date().getTime() - this.lastRefresh.getTime()) > MIN_REFRESH_TIME;
135
+        const lastRefresh = this.lastRefresh;
136
+        if (lastRefresh != null)
137
+            canRefresh = (new Date().getTime() - lastRefresh.getTime()) > MIN_REFRESH_TIME;
129
         else
138
         else
130
             canRefresh = true;
139
             canRefresh = true;
131
         if (canRefresh && this.authRef.current != null)
140
         if (canRefresh && this.authRef.current != null)
132
             this.authRef.current.reload()
141
             this.authRef.current.reload()
133
     };
142
     };
134
 
143
 
144
+    /**
145
+     * Generates the objects containing string and Date representations of key vote dates
146
+     */
135
     generateDateObject() {
147
     generateDateObject() {
136
         const strings = this.datesString;
148
         const strings = this.datesString;
137
         if (strings != null) {
149
         if (strings != null) {
152
             this.dates = null;
164
             this.dates = null;
153
     }
165
     }
154
 
166
 
167
+    /**
168
+     * Gets the string representation of the given date.
169
+     *
170
+     * If the given date is the same day as today, only return the tile.
171
+     * Otherwise, return the full date.
172
+     *
173
+     * @param date The Date object representation of the wanted date
174
+     * @param dateString The string representation of the wanted date
175
+     * @returns {string}
176
+     */
155
     getDateString(date: Date, dateString: string): string {
177
     getDateString(date: Date, dateString: string): string {
156
         if (this.today.getDate() === date.getDate()) {
178
         if (this.today.getDate() === date.getDate()) {
157
             const str = getTimeOnlyString(dateString);
179
             const str = getTimeOnlyString(dateString);
176
         return this.dates != null && this.today > this.dates.date_result_begin;
198
         return this.dates != null && this.today > this.dates.date_result_begin;
177
     }
199
     }
178
 
200
 
179
-    mainRenderItem = ({item}: Object) => {
201
+    mainRenderItem = ({item}: { item: { key: string } }) => {
180
         if (item.key === 'info')
202
         if (item.key === 'info')
181
             return <VoteTitle/>;
203
             return <VoteTitle/>;
182
         else if (item.key === 'main' && this.dates != null)
204
         else if (item.key === 'main' && this.dates != null)
190
         // data[1] = FAKE_DATE;
212
         // data[1] = FAKE_DATE;
191
         this.lastRefresh = new Date();
213
         this.lastRefresh = new Date();
192
 
214
 
193
-        const teams : teamResponse | null = data[0];
194
-        const dateStrings : stringVoteDates | null = data[1];
215
+        const teams: teamResponse | null = data[0];
216
+        const dateStrings: stringVoteDates | null = data[1];
195
 
217
 
196
         if (dateStrings != null && dateStrings.date_begin == null)
218
         if (dateStrings != null && dateStrings.date_begin == null)
197
             this.datesString = null;
219
             this.datesString = null;
282
                          isVoteRunning={this.isVoteRunning()}/>;
304
                          isVoteRunning={this.isVoteRunning()}/>;
283
     }
305
     }
284
 
306
 
307
+    /**
308
+     * Renders the authenticated screen.
309
+     *
310
+     * Teams and dates are not mandatory to allow showing the information box even if api requests fail
311
+     *
312
+     * @returns {*}
313
+     */
285
     render() {
314
     render() {
286
         return (
315
         return (
287
             <AuthenticatedScreen
316
             <AuthenticatedScreen

+ 24
- 15
src/screens/Home/FeedItemScreen.js View File

7
 import Autolink from "react-native-autolink";
7
 import Autolink from "react-native-autolink";
8
 import MaterialHeaderButtons, {Item} from "../../components/Overrides/CustomHeaderButton";
8
 import MaterialHeaderButtons, {Item} from "../../components/Overrides/CustomHeaderButton";
9
 import CustomTabBar from "../../components/Tabbar/CustomTabBar";
9
 import CustomTabBar from "../../components/Tabbar/CustomTabBar";
10
+import {StackNavigationProp} from "@react-navigation/stack";
11
+import type {feedItem} from "./HomeScreen";
10
 
12
 
11
 type Props = {
13
 type Props = {
12
-    navigation: Object,
13
-    route: Object
14
+    navigation: StackNavigationProp,
15
+    route: { params: { data: feedItem, date: string } }
14
 };
16
 };
15
 
17
 
16
 const ICON_AMICALE = require('../../../assets/amicale.png');
18
 const ICON_AMICALE = require('../../../assets/amicale.png');
17
 const NAME_AMICALE = 'Amicale INSA Toulouse';
19
 const NAME_AMICALE = 'Amicale INSA Toulouse';
20
+
18
 /**
21
 /**
19
- * Class defining a planning event information page.
22
+ * Class defining a feed item page.
20
  */
23
  */
21
 class FeedItemScreen extends React.Component<Props> {
24
 class FeedItemScreen extends React.Component<Props> {
22
 
25
 
23
-    displayData: Object;
26
+    displayData: feedItem;
24
     date: string;
27
     date: string;
25
 
28
 
26
-    colors: Object;
27
-
28
     constructor(props) {
29
     constructor(props) {
29
         super(props);
30
         super(props);
30
-        this.colors = props.theme.colors;
31
-        this.displayData = this.props.route.params.data;
32
-        this.date = this.props.route.params.date;
31
+        this.displayData = props.route.params.data;
32
+        this.date = props.route.params.date;
33
     }
33
     }
34
 
34
 
35
     componentDidMount() {
35
     componentDidMount() {
38
         });
38
         });
39
     }
39
     }
40
 
40
 
41
+    /**
42
+     * Opens the feed item out link in browser or compatible app
43
+     */
41
     onOutLinkPress = () => {
44
     onOutLinkPress = () => {
42
         Linking.openURL(this.displayData.permalink_url);
45
         Linking.openURL(this.displayData.permalink_url);
43
     };
46
     };
44
 
47
 
48
+    /**
49
+     * Gets the out link header button
50
+     *
51
+     * @returns {*}
52
+     */
45
     getHeaderButton = () => {
53
     getHeaderButton = () => {
46
         return <MaterialHeaderButtons>
54
         return <MaterialHeaderButtons>
47
             <Item title="main" iconName={'facebook'} color={"#2e88fe"} onPress={this.onOutLinkPress}/>
55
             <Item title="main" iconName={'facebook'} color={"#2e88fe"} onPress={this.onOutLinkPress}/>
48
         </MaterialHeaderButtons>;
56
         </MaterialHeaderButtons>;
49
     };
57
     };
50
 
58
 
59
+    /**
60
+     * Gets the Amicale INSA avatar
61
+     *
62
+     * @returns {*}
63
+     */
51
     getAvatar() {
64
     getAvatar() {
52
         return (
65
         return (
53
             <Avatar.Image size={48} source={ICON_AMICALE}
66
             <Avatar.Image size={48} source={ICON_AMICALE}
55
         );
68
         );
56
     }
69
     }
57
 
70
 
58
-    getContent() {
59
-        const hasImage = this.displayData.full_picture !== '' && this.displayData.full_picture !== undefined;
71
+    render() {
72
+        const hasImage = this.displayData.full_picture !== '' && this.displayData.full_picture != null;
60
         return (
73
         return (
61
             <ScrollView style={{margin: 5,}}>
74
             <ScrollView style={{margin: 5,}}>
62
                 <Card.Title
75
                 <Card.Title
89
             </ScrollView>
102
             </ScrollView>
90
         );
103
         );
91
     }
104
     }
92
-
93
-    render() {
94
-        return this.getContent();
95
-    }
96
 }
105
 }
97
 
106
 
98
 export default withTheme(FeedItemScreen);
107
 export default withTheme(FeedItemScreen);

+ 34
- 11
src/screens/Home/HomeScreen.js View File

111
  */
111
  */
112
 class HomeScreen extends React.Component<Props, State> {
112
 class HomeScreen extends React.Component<Props, State> {
113
 
113
 
114
-    colors: Object;
115
-
116
     isLoggedIn: boolean | null;
114
     isLoggedIn: boolean | null;
117
 
115
 
118
     fabRef: { current: null | AnimatedFAB };
116
     fabRef: { current: null | AnimatedFAB };
125
 
123
 
126
     constructor(props) {
124
     constructor(props) {
127
         super(props);
125
         super(props);
128
-        this.colors = props.theme.colors;
129
         this.fabRef = React.createRef();
126
         this.fabRef = React.createRef();
130
         this.currentNewFeed = [];
127
         this.currentNewFeed = [];
131
         this.isLoggedIn = null;
128
         this.isLoggedIn = null;
155
         })
152
         })
156
     }
153
     }
157
 
154
 
155
+    /**
156
+     * Updates login state and navigation parameters on screen focus
157
+     */
158
     onScreenFocus = () => {
158
     onScreenFocus = () => {
159
         if (ConnectionManager.getInstance().isLoggedIn() !== this.isLoggedIn) {
159
         if (ConnectionManager.getInstance().isLoggedIn() !== this.isLoggedIn) {
160
             this.isLoggedIn = ConnectionManager.getInstance().isLoggedIn();
160
             this.isLoggedIn = ConnectionManager.getInstance().isLoggedIn();
169
         this.handleNavigationParams();
169
         this.handleNavigationParams();
170
     };
170
     };
171
 
171
 
172
+    /**
173
+     * Navigates to the a new screen if navigation parameters specify one
174
+     */
172
     handleNavigationParams = () => {
175
     handleNavigationParams = () => {
173
         if (this.props.route.params != null) {
176
         if (this.props.route.params != null) {
174
             if (this.props.route.params.nextScreen != null) {
177
             if (this.props.route.params.nextScreen != null) {
179
         }
182
         }
180
     };
183
     };
181
 
184
 
185
+    /**
186
+     * Gets header buttons based on login state
187
+     *
188
+     * @returns {*}
189
+     */
182
     getHeaderButton = () => {
190
     getHeaderButton = () => {
183
         let onPressLog = () => this.props.navigation.navigate("login", {nextScreen: "profile"});
191
         let onPressLog = () => this.props.navigation.navigate("login", {nextScreen: "profile"});
184
         let logIcon = "login";
192
         let logIcon = "login";
262
                         id: 'washers',
270
                         id: 'washers',
263
                         data: dashboardData == null ? 0 : dashboardData.available_machines.washers,
271
                         data: dashboardData == null ? 0 : dashboardData.available_machines.washers,
264
                         icon: 'washing-machine',
272
                         icon: 'washing-machine',
265
-                        color: this.colors.proxiwashColor,
273
+                        color: this.props.theme.colors.proxiwashColor,
266
                         onPress: this.onProxiwashClick,
274
                         onPress: this.onProxiwashClick,
267
                         isAvailable: dashboardData == null ? false : dashboardData.available_machines.washers > 0
275
                         isAvailable: dashboardData == null ? false : dashboardData.available_machines.washers > 0
268
                     },
276
                     },
270
                         id: 'dryers',
278
                         id: 'dryers',
271
                         data: dashboardData == null ? 0 : dashboardData.available_machines.dryers,
279
                         data: dashboardData == null ? 0 : dashboardData.available_machines.dryers,
272
                         icon: 'tumble-dryer',
280
                         icon: 'tumble-dryer',
273
-                        color: this.colors.proxiwashColor,
281
+                        color: this.props.theme.colors.proxiwashColor,
274
                         onPress: this.onProxiwashClick,
282
                         onPress: this.onProxiwashClick,
275
                         isAvailable: dashboardData == null ? false : dashboardData.available_machines.dryers > 0
283
                         isAvailable: dashboardData == null ? false : dashboardData.available_machines.dryers > 0
276
                     },
284
                     },
278
                         id: 'available_tutorials',
286
                         id: 'available_tutorials',
279
                         data: dashboardData == null ? 0 : dashboardData.available_tutorials,
287
                         data: dashboardData == null ? 0 : dashboardData.available_tutorials,
280
                         icon: 'school',
288
                         icon: 'school',
281
-                        color: this.colors.tutorinsaColor,
289
+                        color: this.props.theme.colors.tutorinsaColor,
282
                         onPress: this.onTutorInsaClick,
290
                         onPress: this.onTutorInsaClick,
283
                         isAvailable: dashboardData == null ? false : dashboardData.available_tutorials > 0
291
                         isAvailable: dashboardData == null ? false : dashboardData.available_tutorials > 0
284
                     },
292
                     },
286
                         id: 'proximo_articles',
294
                         id: 'proximo_articles',
287
                         data: dashboardData == null ? 0 : dashboardData.proximo_articles,
295
                         data: dashboardData == null ? 0 : dashboardData.proximo_articles,
288
                         icon: 'shopping',
296
                         icon: 'shopping',
289
-                        color: this.colors.proximoColor,
297
+                        color: this.props.theme.colors.proximoColor,
290
                         onPress: this.onProximoClick,
298
                         onPress: this.onProximoClick,
291
                         isAvailable: dashboardData == null ? false : dashboardData.proximo_articles > 0
299
                         isAvailable: dashboardData == null ? false : dashboardData.proximo_articles > 0
292
                     },
300
                     },
294
                         id: 'today_menu',
302
                         id: 'today_menu',
295
                         data: dashboardData == null ? [] : dashboardData.today_menu,
303
                         data: dashboardData == null ? [] : dashboardData.today_menu,
296
                         icon: 'silverware-fork-knife',
304
                         icon: 'silverware-fork-knife',
297
-                        color: this.colors.menuColor,
305
+                        color: this.props.theme.colors.menuColor,
298
                         onPress: this.onMenuClick,
306
                         onPress: this.onMenuClick,
299
                         isAvailable: dashboardData == null ? false : dashboardData.today_menu.length > 0
307
                         isAvailable: dashboardData == null ? false : dashboardData.today_menu.length > 0
300
                     },
308
                     },
324
             return this.getDashboardActions();
332
             return this.getDashboardActions();
325
     }
333
     }
326
 
334
 
335
+    /**
336
+     * Gets a dashboard item with action buttons
337
+     *
338
+     * @returns {*}
339
+     */
327
     getDashboardActions() {
340
     getDashboardActions() {
328
         return <ActionsDashBoardItem {...this.props} isLoggedIn={this.isLoggedIn}/>;
341
         return <ActionsDashBoardItem {...this.props} isLoggedIn={this.isLoggedIn}/>;
329
     }
342
     }
446
     onEventContainerClick = () => this.props.navigation.navigate('planning');
459
     onEventContainerClick = () => this.props.navigation.navigate('planning');
447
 
460
 
448
     /**
461
     /**
449
-     * Gets the event render item.
462
+     * Gets the event dashboard render item.
450
      * If a preview is available, it will be rendered inside
463
      * If a preview is available, it will be rendered inside
451
      *
464
      *
452
      * @param content
465
      * @param content
473
         );
486
         );
474
     }
487
     }
475
 
488
 
489
+    /**
490
+     * Gets a dashboard shortcut item
491
+     *
492
+     * @param item
493
+     * @returns {*}
494
+     */
476
     dashboardRowRenderItem = ({item}: { item: dashboardSmallItem }) => {
495
     dashboardRowRenderItem = ({item}: { item: dashboardSmallItem }) => {
477
         return (
496
         return (
478
             <SquareDashboardItem
497
             <SquareDashboardItem
486
     };
505
     };
487
 
506
 
488
     /**
507
     /**
489
-     * Gets a classic dashboard item.
508
+     * Gets a dashboard item with a row of shortcut buttons.
490
      *
509
      *
491
      * @param content
510
      * @param content
492
      * @return {*}
511
      * @return {*}
553
 
572
 
554
     /**
573
     /**
555
      * Callback used when closing the banner.
574
      * Callback used when closing the banner.
556
-     * This hides the banner and saves to preferences to prevent it from reopening
575
+     * This hides the banner and saves to preferences to prevent it from reopening.
557
      */
576
      */
558
     onHideBanner = () => {
577
     onHideBanner = () => {
559
         this.setState({bannerVisible: false});
578
         this.setState({bannerVisible: false});
563
         );
582
         );
564
     };
583
     };
565
 
584
 
585
+    /**
586
+     * Callback when pressing the login button on the banner.
587
+     * This hides the banner and takes the user to the login page.
588
+     */
566
     onLoginBanner = () => {
589
     onLoginBanner = () => {
567
         this.onHideBanner();
590
         this.onHideBanner();
568
         this.props.navigation.navigate("login", {nextScreen: "profile"});
591
         this.props.navigation.navigate("login", {nextScreen: "profile"});

+ 38
- 0
src/screens/Home/ScannerScreen.js View File

41
         this.requestPermissions();
41
         this.requestPermissions();
42
     }
42
     }
43
 
43
 
44
+    /**
45
+     * Requests permission to use the camera
46
+     */
44
     requestPermissions = () => {
47
     requestPermissions = () => {
45
         if (Platform.OS === 'android')
48
         if (Platform.OS === 'android')
46
             request(PERMISSIONS.ANDROID.CAMERA).then(this.updatePermissionStatus)
49
             request(PERMISSIONS.ANDROID.CAMERA).then(this.updatePermissionStatus)
48
             request(PERMISSIONS.IOS.CAMERA).then(this.updatePermissionStatus)
51
             request(PERMISSIONS.IOS.CAMERA).then(this.updatePermissionStatus)
49
     };
52
     };
50
 
53
 
54
+    /**
55
+     * Updates the state permission status
56
+     *
57
+     * @param result
58
+     */
51
     updatePermissionStatus = (result) => this.setState({hasPermission: result === RESULTS.GRANTED});
59
     updatePermissionStatus = (result) => this.setState({hasPermission: result === RESULTS.GRANTED});
52
 
60
 
61
+    /**
62
+     * Opens scanned link if it is a valid app link or shows and error dialog
63
+     *
64
+     * @param type The barcode type
65
+     * @param data The scanned value
66
+     */
53
     handleCodeScanned = ({type, data}) => {
67
     handleCodeScanned = ({type, data}) => {
54
         if (!URLHandler.isUrlValid(data))
68
         if (!URLHandler.isUrlValid(data))
55
             this.showErrorDialog();
69
             this.showErrorDialog();
59
         }
73
         }
60
     };
74
     };
61
 
75
 
76
+    /**
77
+     * Gets a view asking user for permission to use the camera
78
+     *
79
+     * @returns {*}
80
+     */
62
     getPermissionScreen() {
81
     getPermissionScreen() {
63
         return <View style={{marginLeft: 10, marginRight: 10}}>
82
         return <View style={{marginLeft: 10, marginRight: 10}}>
64
             <Text>{i18n.t("scannerScreen.errorPermission")}</Text>
83
             <Text>{i18n.t("scannerScreen.errorPermission")}</Text>
77
         </View>
96
         </View>
78
     }
97
     }
79
 
98
 
99
+    /**
100
+     * Shows a dialog indicating how to use the scanner
101
+     */
80
     showHelpDialog = () => {
102
     showHelpDialog = () => {
81
         this.setState({
103
         this.setState({
82
             dialogVisible: true,
104
             dialogVisible: true,
86
         });
108
         });
87
     };
109
     };
88
 
110
 
111
+    /**
112
+     * Shows a loading dialog
113
+     */
89
     showOpeningDialog = () => {
114
     showOpeningDialog = () => {
90
         this.setState({
115
         this.setState({
91
             loading: true,
116
             loading: true,
93
         });
118
         });
94
     };
119
     };
95
 
120
 
121
+    /**
122
+     * Shows a dialog indicating the user the scanned code was invalid
123
+     */
96
     showErrorDialog() {
124
     showErrorDialog() {
97
         this.setState({
125
         this.setState({
98
             dialogVisible: true,
126
             dialogVisible: true,
102
         });
130
         });
103
     }
131
     }
104
 
132
 
133
+    /**
134
+     * Hide any dialog
135
+     */
105
     onDialogDismiss = () => this.setState({
136
     onDialogDismiss = () => this.setState({
106
         dialogVisible: false,
137
         dialogVisible: false,
107
         scanned: false,
138
         scanned: false,
108
     });
139
     });
109
 
140
 
141
+    /**
142
+     * Gets a view with the scanner.
143
+     * This scanner uses the back camera, can only scan qr codes and has a square mask on the center.
144
+     * The mask is only for design purposes as a code is scanned as soon as it enters the camera view
145
+     *
146
+     * @returns {*}
147
+     */
110
     getScanner() {
148
     getScanner() {
111
         return (
149
         return (
112
             <RNCamera
150
             <RNCamera

+ 2
- 2
src/utils/Search.js View File

32
  * Checks if the given arrays have an item in common
32
  * Checks if the given arrays have an item in common
33
  *
33
  *
34
  * @param filter The filter array
34
  * @param filter The filter array
35
- * @param categories The item's categories array
35
+ * @param categories The item's categories tuple
36
  * @returns {boolean} True if at least one entry is in both arrays
36
  * @returns {boolean} True if at least one entry is in both arrays
37
  */
37
  */
38
-export function isItemInCategoryFilter(filter: Array<string>, categories: Array<string>) {
38
+export function isItemInCategoryFilter(filter: Array<number>, categories: [number, number]) {
39
     for (const category of categories) {
39
     for (const category of categories) {
40
         if (filter.indexOf(category) !== -1)
40
         if (filter.indexOf(category) !== -1)
41
             return true;
41
             return true;

Loading…
Cancel
Save