Browse Source

Improve Home components to match linter

Arnaud Vergnet 3 years ago
parent
commit
6b12b4cde2

+ 36
- 27
src/components/Home/ActionsDashboardItem.js View File

@@ -2,37 +2,46 @@
2 2
 
3 3
 import * as React from 'react';
4 4
 import {List, withTheme} from 'react-native-paper';
5
-import {View} from "react-native";
6
-import type {CustomTheme} from "../../managers/ThemeManager";
5
+import {View} from 'react-native';
7 6
 import i18n from 'i18n-js';
8
-import {StackNavigationProp} from "@react-navigation/stack";
7
+import {StackNavigationProp} from '@react-navigation/stack';
8
+import type {CustomTheme} from '../../managers/ThemeManager';
9 9
 
10
-type Props = {
11
-    navigation: StackNavigationProp,
12
-    theme: CustomTheme,
13
-}
14
-
15
-class ActionsDashBoardItem extends React.Component<Props> {
16
-
17
-    shouldComponentUpdate(nextProps: Props): boolean {
18
-        return (nextProps.theme.dark !== this.props.theme.dark);
19
-    }
10
+type PropsType = {
11
+  navigation: StackNavigationProp,
12
+  theme: CustomTheme,
13
+};
20 14
 
21
-    render() {
22
-        return (
23
-            <View>
24
-                <List.Item
25
-                    title={i18n.t("screens.feedback.homeButtonTitle")}
26
-                    description={i18n.t("screens.feedback.homeButtonSubtitle")}
27
-                    left={props => <List.Icon {...props} icon={"comment-quote"}/>}
28
-                    right={props => <List.Icon {...props} icon={"chevron-right"}/>}
29
-                    onPress={() => this.props.navigation.navigate("feedback")}
30
-                    style={{paddingTop: 0, paddingBottom: 0, marginLeft: 10, marginRight: 10}}
31
-                />
32
-            </View>
15
+class ActionsDashBoardItem extends React.Component<PropsType> {
16
+  shouldComponentUpdate(nextProps: PropsType): boolean {
17
+    const {props} = this;
18
+    return nextProps.theme.dark !== props.theme.dark;
19
+  }
33 20
 
34
-        );
35
-    }
21
+  render(): React.Node {
22
+    const {props} = this;
23
+    return (
24
+      <View>
25
+        <List.Item
26
+          title={i18n.t('screens.feedback.homeButtonTitle')}
27
+          description={i18n.t('screens.feedback.homeButtonSubtitle')}
28
+          left={({size}: {size: number}): React.Node => (
29
+            <List.Icon size={size} icon="comment-quote" />
30
+          )}
31
+          right={({size}: {size: number}): React.Node => (
32
+            <List.Icon size={size} icon="chevron-right" />
33
+          )}
34
+          onPress={(): void => props.navigation.navigate('feedback')}
35
+          style={{
36
+            paddingTop: 0,
37
+            paddingBottom: 0,
38
+            marginLeft: 10,
39
+            marginRight: 10,
40
+          }}
41
+        />
42
+      </View>
43
+    );
44
+  }
36 45
 }
37 46
 
38 47
 export default withTheme(ActionsDashBoardItem);

+ 83
- 78
src/components/Home/EventDashboardItem.js View File

@@ -1,91 +1,96 @@
1 1
 // @flow
2 2
 
3 3
 import * as React from 'react';
4
-import {Avatar, Card, Text, TouchableRipple, withTheme} from 'react-native-paper';
5
-import {StyleSheet, View} from "react-native";
6
-import i18n from "i18n-js";
7
-import type {CustomTheme} from "../../managers/ThemeManager";
4
+import {
5
+  Avatar,
6
+  Card,
7
+  Text,
8
+  TouchableRipple,
9
+  withTheme,
10
+} from 'react-native-paper';
11
+import {StyleSheet, View} from 'react-native';
12
+import i18n from 'i18n-js';
13
+import type {CustomTheme} from '../../managers/ThemeManager';
8 14
 
9
-type Props = {
10
-    eventNumber: number;
11
-    clickAction: () => void,
12
-    theme: CustomTheme,
13
-    children?: React.Node
14
-}
15
+type PropsType = {
16
+  eventNumber: number,
17
+  clickAction: () => void,
18
+  theme: CustomTheme,
19
+  children?: React.Node,
20
+};
21
+
22
+const styles = StyleSheet.create({
23
+  card: {
24
+    width: 'auto',
25
+    marginLeft: 10,
26
+    marginRight: 10,
27
+    marginTop: 10,
28
+    overflow: 'hidden',
29
+  },
30
+  avatar: {
31
+    backgroundColor: 'transparent',
32
+  },
33
+});
15 34
 
16 35
 /**
17 36
  * Component used to display a dashboard item containing a preview event
18 37
  */
19
-class EventDashBoardItem extends React.Component<Props> {
38
+class EventDashBoardItem extends React.Component<PropsType> {
39
+  static defaultProps = {
40
+    children: null,
41
+  };
20 42
 
21
-    shouldComponentUpdate(nextProps: Props) {
22
-        return (nextProps.theme.dark !== this.props.theme.dark)
23
-            || (nextProps.eventNumber !== this.props.eventNumber);
24
-    }
25
-
26
-    render() {
27
-        const props = this.props;
28
-        const colors = props.theme.colors;
29
-        const isAvailable = props.eventNumber > 0;
30
-        const iconColor = isAvailable ?
31
-            colors.planningColor :
32
-            colors.textDisabled;
33
-        const textColor = isAvailable ?
34
-            colors.text :
35
-            colors.textDisabled;
36
-        let subtitle;
37
-        if (isAvailable) {
38
-            subtitle =
39
-                <Text>
40
-                    <Text style={{fontWeight: "bold"}}>{props.eventNumber}</Text>
41
-                    <Text>
42
-                        {props.eventNumber > 1
43
-                            ? i18n.t('screens.home.dashboard.todayEventsSubtitlePlural')
44
-                            : i18n.t('screens.home.dashboard.todayEventsSubtitle')}
45
-                    </Text>
46
-                </Text>;
47
-        } else
48
-            subtitle = i18n.t('screens.home.dashboard.todayEventsSubtitleNA');
49
-        return (
50
-            <Card style={styles.card}>
51
-                <TouchableRipple
52
-                    style={{flex: 1}}
53
-                    onPress={props.clickAction}>
54
-                    <View>
55
-                        <Card.Title
56
-                            title={i18n.t('screens.home.dashboard.todayEventsTitle')}
57
-                            titleStyle={{color: textColor}}
58
-                            subtitle={subtitle}
59
-                            subtitleStyle={{color: textColor}}
60
-                            left={() =>
61
-                                <Avatar.Icon
62
-                                    icon={'calendar-range'}
63
-                                    color={iconColor}
64
-                                    size={60}
65
-                                    style={styles.avatar}/>}
66
-                        />
67
-                        <Card.Content>
68
-                            {props.children}
69
-                        </Card.Content>
70
-                    </View>
71
-                </TouchableRipple>
72
-            </Card>
73
-        );
74
-    }
43
+  shouldComponentUpdate(nextProps: PropsType): boolean {
44
+    const {props} = this;
45
+    return (
46
+      nextProps.theme.dark !== props.theme.dark ||
47
+      nextProps.eventNumber !== props.eventNumber
48
+    );
49
+  }
75 50
 
51
+  render(): React.Node {
52
+    const {props} = this;
53
+    const {colors} = props.theme;
54
+    const isAvailable = props.eventNumber > 0;
55
+    const iconColor = isAvailable ? colors.planningColor : colors.textDisabled;
56
+    const textColor = isAvailable ? colors.text : colors.textDisabled;
57
+    let subtitle;
58
+    if (isAvailable) {
59
+      subtitle = (
60
+        <Text>
61
+          <Text style={{fontWeight: 'bold'}}>{props.eventNumber}</Text>
62
+          <Text>
63
+            {props.eventNumber > 1
64
+              ? i18n.t('screens.home.dashboard.todayEventsSubtitlePlural')
65
+              : i18n.t('screens.home.dashboard.todayEventsSubtitle')}
66
+          </Text>
67
+        </Text>
68
+      );
69
+    } else subtitle = i18n.t('screens.home.dashboard.todayEventsSubtitleNA');
70
+    return (
71
+      <Card style={styles.card}>
72
+        <TouchableRipple style={{flex: 1}} onPress={props.clickAction}>
73
+          <View>
74
+            <Card.Title
75
+              title={i18n.t('screens.home.dashboard.todayEventsTitle')}
76
+              titleStyle={{color: textColor}}
77
+              subtitle={subtitle}
78
+              subtitleStyle={{color: textColor}}
79
+              left={(): React.Node => (
80
+                <Avatar.Icon
81
+                  icon="calendar-range"
82
+                  color={iconColor}
83
+                  size={60}
84
+                  style={styles.avatar}
85
+                />
86
+              )}
87
+            />
88
+            <Card.Content>{props.children}</Card.Content>
89
+          </View>
90
+        </TouchableRipple>
91
+      </Card>
92
+    );
93
+  }
76 94
 }
77 95
 
78
-const styles = StyleSheet.create({
79
-    card: {
80
-        width: 'auto',
81
-        marginLeft: 10,
82
-        marginRight: 10,
83
-        marginTop: 10,
84
-        overflow: 'hidden',
85
-    },
86
-    avatar: {
87
-        backgroundColor: 'transparent'
88
-    }
89
-});
90
-
91 96
 export default withTheme(EventDashBoardItem);

+ 96
- 108
src/components/Home/FeedItem.js View File

@@ -2,126 +2,114 @@
2 2
 
3 3
 import * as React from 'react';
4 4
 import {Button, Card, Text, TouchableRipple} from 'react-native-paper';
5
-import {Image, View} from "react-native";
6
-import Autolink from "react-native-autolink";
7
-import i18n from "i18n-js";
5
+import {Image, View} from 'react-native';
6
+import Autolink from 'react-native-autolink';
7
+import i18n from 'i18n-js';
8 8
 import ImageModal from 'react-native-image-modal';
9
-import {StackNavigationProp} from "@react-navigation/stack";
10
-import type {CustomTheme} from "../../managers/ThemeManager";
11
-import type {feedItem} from "../../screens/Home/HomeScreen";
9
+import {StackNavigationProp} from '@react-navigation/stack';
10
+import type {FeedItemType} from '../../screens/Home/HomeScreen';
12 11
 
13 12
 const ICON_AMICALE = require('../../../assets/amicale.png');
14 13
 
15
-type Props = {
16
-    navigation: StackNavigationProp,
17
-    theme: CustomTheme,
18
-    item: feedItem,
19
-    title: string,
20
-    subtitle: string,
21
-    height: number,
22
-}
23
-
14
+type PropsType = {
15
+  navigation: StackNavigationProp,
16
+  item: FeedItemType,
17
+  title: string,
18
+  subtitle: string,
19
+  height: number,
20
+};
24 21
 
25 22
 /**
26 23
  * Component used to display a feed item
27 24
  */
28
-class FeedItem extends React.Component<Props> {
25
+class FeedItem extends React.Component<PropsType> {
26
+  shouldComponentUpdate(): boolean {
27
+    return false;
28
+  }
29
+
30
+  onPress = () => {
31
+    const {props} = this;
32
+    props.navigation.navigate('feed-information', {
33
+      data: props.item,
34
+      date: props.subtitle,
35
+    });
36
+  };
29 37
 
30
-    shouldComponentUpdate() {
31
-        return false;
32
-    }
38
+  render(): React.Node {
39
+    const {props} = this;
40
+    const {item} = props;
41
+    const hasImage =
42
+      item.full_picture !== '' && item.full_picture !== undefined;
33 43
 
34
-    /**
35
-     * Gets the amicale INSAT logo
36
-     *
37
-     * @return {*}
38
-     */
39
-    getAvatar() {
40
-        return (
41
-            <Image
42
-                size={48}
43
-                source={ICON_AMICALE}
44
-                style={{
44
+    const cardMargin = 10;
45
+    const cardHeight = props.height - 2 * cardMargin;
46
+    const imageSize = 250;
47
+    const titleHeight = 80;
48
+    const actionsHeight = 60;
49
+    const textHeight = hasImage
50
+      ? cardHeight - titleHeight - actionsHeight - imageSize
51
+      : cardHeight - titleHeight - actionsHeight;
52
+    return (
53
+      <Card
54
+        style={{
55
+          margin: cardMargin,
56
+          height: cardHeight,
57
+        }}>
58
+        <TouchableRipple style={{flex: 1}} onPress={this.onPress}>
59
+          <View>
60
+            <Card.Title
61
+              title={props.title}
62
+              subtitle={props.subtitle}
63
+              left={(): React.Node => (
64
+                <Image
65
+                  size={48}
66
+                  source={ICON_AMICALE}
67
+                  style={{
45 68
                     width: 48,
46 69
                     height: 48,
47
-                }}/>
48
-        );
49
-    }
50
-
51
-    onPress = () => {
52
-        this.props.navigation.navigate(
53
-            'feed-information',
54
-            {
55
-                data: this.props.item,
56
-                date: this.props.subtitle
57
-            });
58
-    };
59
-
60
-    render() {
61
-        const item = this.props.item;
62
-        const hasImage = item.full_picture !== '' && item.full_picture !== undefined;
63
-
64
-        const cardMargin = 10;
65
-        const cardHeight = this.props.height - 2 * cardMargin;
66
-        const imageSize = 250;
67
-        const titleHeight = 80;
68
-        const actionsHeight = 60;
69
-        const textHeight = hasImage
70
-            ? cardHeight - titleHeight - actionsHeight - imageSize
71
-            : cardHeight - titleHeight - actionsHeight;
72
-        return (
73
-            <Card
74
-                style={{
75
-                    margin: cardMargin,
76
-                    height: cardHeight,
77
-                }}
78
-            >
79
-                <TouchableRipple
80
-                    style={{flex: 1}}
81
-                    onPress={this.onPress}>
82
-                    <View>
83
-                        <Card.Title
84
-                            title={this.props.title}
85
-                            subtitle={this.props.subtitle}
86
-                            left={this.getAvatar}
87
-                            style={{height: titleHeight}}
88
-                        />
89
-                        {hasImage ?
90
-                            <View style={{marginLeft: 'auto', marginRight: 'auto'}}>
91
-                                <ImageModal
92
-                                    resizeMode="contain"
93
-                                    imageBackgroundColor={"#000"}
94
-                                    style={{
95
-                                        width: imageSize,
96
-                                        height: imageSize,
97
-                                    }}
98
-                                    source={{
99
-                                        uri: item.full_picture,
100
-                                    }}
101
-                                /></View> : null}
102
-                        <Card.Content>
103
-                            {item.message !== undefined ?
104
-                                <Autolink
105
-                                    text={item.message}
106
-                                    hashtag="facebook"
107
-                                    component={Text}
108
-                                    style={{height: textHeight}}
109
-                                /> : null
110
-                            }
111
-                        </Card.Content>
112
-                        <Card.Actions style={{height: actionsHeight}}>
113
-                            <Button
114
-                                onPress={this.onPress}
115
-                                icon={'plus'}
116
-                                style={{marginLeft: 'auto'}}>
117
-                                {i18n.t('screens.home.dashboard.seeMore')}
118
-                            </Button>
119
-                        </Card.Actions>
120
-                    </View>
121
-                </TouchableRipple>
122
-            </Card>
123
-        );
124
-    }
70
+                  }}
71
+                />
72
+              )}
73
+              style={{height: titleHeight}}
74
+            />
75
+            {hasImage ? (
76
+              <View style={{marginLeft: 'auto', marginRight: 'auto'}}>
77
+                <ImageModal
78
+                  resizeMode="contain"
79
+                  imageBackgroundColor="#000"
80
+                  style={{
81
+                    width: imageSize,
82
+                    height: imageSize,
83
+                  }}
84
+                  source={{
85
+                    uri: item.full_picture,
86
+                  }}
87
+                />
88
+              </View>
89
+            ) : null}
90
+            <Card.Content>
91
+              {item.message !== undefined ? (
92
+                <Autolink
93
+                  text={item.message}
94
+                  hashtag="facebook"
95
+                  component={Text}
96
+                  style={{height: textHeight}}
97
+                />
98
+              ) : null}
99
+            </Card.Content>
100
+            <Card.Actions style={{height: actionsHeight}}>
101
+              <Button
102
+                onPress={this.onPress}
103
+                icon="plus"
104
+                style={{marginLeft: 'auto'}}>
105
+                {i18n.t('screens.home.dashboard.seeMore')}
106
+              </Button>
107
+            </Card.Actions>
108
+          </View>
109
+        </TouchableRipple>
110
+      </Card>
111
+    );
112
+  }
125 113
 }
126 114
 
127 115
 export default FeedItem;

+ 84
- 78
src/components/Home/PreviewEventDashboardItem.js View File

@@ -1,94 +1,100 @@
1 1
 // @flow
2 2
 
3 3
 import * as React from 'react';
4
-import {StyleSheet, View} from "react-native";
5
-import i18n from "i18n-js";
4
+import {StyleSheet, View} from 'react-native';
5
+import i18n from 'i18n-js';
6 6
 import {Avatar, Button, Card, TouchableRipple} from 'react-native-paper';
7
-import {getFormattedEventTime, isDescriptionEmpty} from "../../utils/Planning";
8
-import CustomHTML from "../Overrides/CustomHTML";
9
-import type {CustomTheme} from "../../managers/ThemeManager";
10
-import type {event} from "../../screens/Home/HomeScreen";
7
+import {getFormattedEventTime, isDescriptionEmpty} from '../../utils/Planning';
8
+import CustomHTML from '../Overrides/CustomHTML';
9
+import type {EventType} from '../../screens/Home/HomeScreen';
11 10
 
12
-type Props = {
13
-    event?: event,
14
-    clickAction: () => void,
15
-    theme?: CustomTheme,
16
-}
11
+type PropsType = {
12
+  event?: EventType | null,
13
+  clickAction: () => void,
14
+};
15
+
16
+const styles = StyleSheet.create({
17
+  card: {
18
+    marginBottom: 10,
19
+  },
20
+  content: {
21
+    maxHeight: 150,
22
+    overflow: 'hidden',
23
+  },
24
+  actions: {
25
+    marginLeft: 'auto',
26
+    marginTop: 'auto',
27
+    flexDirection: 'row',
28
+  },
29
+  avatar: {
30
+    backgroundColor: 'transparent',
31
+  },
32
+});
17 33
 
18 34
 /**
19 35
  * Component used to display an event preview if an event is available
20 36
  */
21
-class PreviewEventDashboardItem extends React.Component<Props> {
37
+// eslint-disable-next-line react/prefer-stateless-function
38
+class PreviewEventDashboardItem extends React.Component<PropsType> {
39
+  static defaultProps = {
40
+    event: null,
41
+  };
22 42
 
23
-    render() {
24
-        const props = this.props;
25
-        const isEmpty = props.event == null
26
-            ? true
27
-            : isDescriptionEmpty(props.event.description);
43
+  render(): React.Node {
44
+    const {props} = this;
45
+    const {event} = props;
46
+    const isEmpty =
47
+      event == null ? true : isDescriptionEmpty(event.description);
28 48
 
29
-        if (props.event != null) {
30
-            const event = props.event;
31
-            const hasImage = event.logo !== '' && event.logo != null;
32
-            const getImage = () => <Avatar.Image
33
-                source={{uri: event.logo}}
34
-                size={50}
35
-                style={styles.avatar}/>;
36
-            return (
37
-                <Card
38
-                    style={styles.card}
39
-                    elevation={3}
40
-                >
41
-                    <TouchableRipple
42
-                        style={{flex: 1}}
43
-                        onPress={props.clickAction}>
44
-                        <View>
45
-                            {hasImage ?
46
-                                <Card.Title
47
-                                    title={event.title}
48
-                                    subtitle={getFormattedEventTime(event.date_begin, event.date_end)}
49
-                                    left={getImage}
50
-                                /> :
51
-                                <Card.Title
52
-                                    title={event.title}
53
-                                    subtitle={getFormattedEventTime(event.date_begin, event.date_end)}
54
-                                />}
55
-                            {!isEmpty ?
56
-                                <Card.Content style={styles.content}>
57
-                                    <CustomHTML html={event.description}/>
58
-                                </Card.Content> : null}
49
+    if (event != null) {
50
+      const hasImage = event.logo !== '' && event.logo != null;
51
+      const getImage = (): React.Node => (
52
+        <Avatar.Image
53
+          source={{uri: event.logo}}
54
+          size={50}
55
+          style={styles.avatar}
56
+        />
57
+      );
58
+      return (
59
+        <Card style={styles.card} elevation={3}>
60
+          <TouchableRipple style={{flex: 1}} onPress={props.clickAction}>
61
+            <View>
62
+              {hasImage ? (
63
+                <Card.Title
64
+                  title={event.title}
65
+                  subtitle={getFormattedEventTime(
66
+                    event.date_begin,
67
+                    event.date_end,
68
+                  )}
69
+                  left={getImage}
70
+                />
71
+              ) : (
72
+                <Card.Title
73
+                  title={event.title}
74
+                  subtitle={getFormattedEventTime(
75
+                    event.date_begin,
76
+                    event.date_end,
77
+                  )}
78
+                />
79
+              )}
80
+              {!isEmpty ? (
81
+                <Card.Content style={styles.content}>
82
+                  <CustomHTML html={event.description} />
83
+                </Card.Content>
84
+              ) : null}
59 85
 
60
-                            <Card.Actions style={styles.actions}>
61
-                                <Button
62
-                                    icon={'chevron-right'}
63
-                                >
64
-                                    {i18n.t("screens.home.dashboard.seeMore")}
65
-                                </Button>
66
-                            </Card.Actions>
67
-                        </View>
68
-                    </TouchableRipple>
69
-                </Card>
70
-            );
71
-        } else
72
-            return null;
86
+              <Card.Actions style={styles.actions}>
87
+                <Button icon="chevron-right">
88
+                  {i18n.t('screens.home.dashboard.seeMore')}
89
+                </Button>
90
+              </Card.Actions>
91
+            </View>
92
+          </TouchableRipple>
93
+        </Card>
94
+      );
73 95
     }
96
+    return null;
97
+  }
74 98
 }
75 99
 
76
-const styles = StyleSheet.create({
77
-    card: {
78
-        marginBottom: 10
79
-    },
80
-    content: {
81
-        maxHeight: 150,
82
-        overflow: 'hidden',
83
-    },
84
-    actions: {
85
-        marginLeft: 'auto',
86
-        marginTop: 'auto',
87
-        flexDirection: 'row'
88
-    },
89
-    avatar: {
90
-        backgroundColor: 'transparent'
91
-    }
92
-});
93
-
94 100
 export default PreviewEventDashboardItem;

+ 67
- 68
src/components/Home/SmallDashboardItem.js View File

@@ -2,15 +2,15 @@
2 2
 
3 3
 import * as React from 'react';
4 4
 import {Badge, TouchableRipple, withTheme} from 'react-native-paper';
5
-import {Dimensions, Image, View} from "react-native";
6
-import type {CustomTheme} from "../../managers/ThemeManager";
7
-import * as Animatable from "react-native-animatable";
5
+import {Dimensions, Image, View} from 'react-native';
6
+import * as Animatable from 'react-native-animatable';
7
+import type {CustomTheme} from '../../managers/ThemeManager';
8 8
 
9
-type Props = {
10
-    image: string,
11
-    onPress: () => void,
12
-    badgeCount: number | null,
13
-    theme: CustomTheme,
9
+type PropsType = {
10
+  image: string | null,
11
+  onPress: () => void | null,
12
+  badgeCount: number | null,
13
+  theme: CustomTheme,
14 14
 };
15 15
 
16 16
 const AnimatableBadge = Animatable.createAnimatableComponent(Badge);
@@ -18,69 +18,68 @@ const AnimatableBadge = Animatable.createAnimatableComponent(Badge);
18 18
 /**
19 19
  * Component used to render a small dashboard item
20 20
  */
21
-class SmallDashboardItem extends React.Component<Props> {
21
+class SmallDashboardItem extends React.Component<PropsType> {
22
+  itemSize: number;
22 23
 
23
-    itemSize: number;
24
+  constructor(props: PropsType) {
25
+    super(props);
26
+    this.itemSize = Dimensions.get('window').width / 8;
27
+  }
24 28
 
25
-    constructor(props: Props) {
26
-        super(props);
27
-        this.itemSize = Dimensions.get('window').width / 8;
28
-    }
29
-
30
-    shouldComponentUpdate(nextProps: Props) {
31
-        return (nextProps.theme.dark !== this.props.theme.dark)
32
-            || (nextProps.badgeCount !== this.props.badgeCount);
33
-    }
34
-
35
-    render() {
36
-        const props = this.props;
37
-        return (
38
-                <TouchableRipple
39
-                    onPress={this.props.onPress}
40
-                    borderless={true}
41
-                    style={{
42
-                        marginLeft: this.itemSize / 6,
43
-                        marginRight: this.itemSize / 6,
44
-                    }}
45
-                >
46
-                    <View style={{
47
-                        width: this.itemSize,
48
-                        height: this.itemSize,
49
-                    }}>
50
-                        <Image
51
-                            source={{uri: props.image}}
52
-                            style={{
53
-                                width: "80%",
54
-                                height: "80%",
55
-                                marginLeft: "auto",
56
-                                marginRight: "auto",
57
-                                marginTop: "auto",
58
-                                marginBottom: "auto",
59
-                            }}
60
-                        />
61
-                        {
62
-                            props.badgeCount != null && props.badgeCount > 0 ?
63
-                                <AnimatableBadge
64
-                                    animation={"zoomIn"}
65
-                                    duration={300}
66
-                                    useNativeDriver
67
-                                    style={{
68
-                                        position: 'absolute',
69
-                                        top: 0,
70
-                                        right: 0,
71
-                                        backgroundColor: props.theme.colors.primary,
72
-                                        borderColor: props.theme.colors.background,
73
-                                        borderWidth: 2,
74
-                                    }}>
75
-                                    {props.badgeCount}
76
-                                </AnimatableBadge> : null
77
-                        }
78
-                    </View>
79
-                </TouchableRipple>
80
-
81
-        );
82
-    }
29
+  shouldComponentUpdate(nextProps: PropsType): boolean {
30
+    const {props} = this;
31
+    return (
32
+      nextProps.theme.dark !== props.theme.dark ||
33
+      nextProps.badgeCount !== props.badgeCount
34
+    );
35
+  }
83 36
 
37
+  render(): React.Node {
38
+    const {props} = this;
39
+    return (
40
+      <TouchableRipple
41
+        onPress={props.onPress}
42
+        borderless
43
+        style={{
44
+          marginLeft: this.itemSize / 6,
45
+          marginRight: this.itemSize / 6,
46
+        }}>
47
+        <View
48
+          style={{
49
+            width: this.itemSize,
50
+            height: this.itemSize,
51
+          }}>
52
+          <Image
53
+            source={{uri: props.image}}
54
+            style={{
55
+              width: '80%',
56
+              height: '80%',
57
+              marginLeft: 'auto',
58
+              marginRight: 'auto',
59
+              marginTop: 'auto',
60
+              marginBottom: 'auto',
61
+            }}
62
+          />
63
+          {props.badgeCount != null && props.badgeCount > 0 ? (
64
+            <AnimatableBadge
65
+              animation="zoomIn"
66
+              duration={300}
67
+              useNativeDriver
68
+              style={{
69
+                position: 'absolute',
70
+                top: 0,
71
+                right: 0,
72
+                backgroundColor: props.theme.colors.primary,
73
+                borderColor: props.theme.colors.background,
74
+                borderWidth: 2,
75
+              }}>
76
+              {props.badgeCount}
77
+            </AnimatableBadge>
78
+          ) : null}
79
+        </View>
80
+      </TouchableRipple>
81
+    );
82
+  }
84 83
 }
85 84
 
86 85
 export default withTheme(SmallDashboardItem);

+ 93
- 87
src/screens/Home/FeedItemScreen.js View File

@@ -4,108 +4,114 @@ import * as React from 'react';
4 4
 import {Linking, View} from 'react-native';
5 5
 import {Avatar, Card, Text, withTheme} from 'react-native-paper';
6 6
 import ImageModal from 'react-native-image-modal';
7
-import Autolink from "react-native-autolink";
8
-import MaterialHeaderButtons, {Item} from "../../components/Overrides/CustomHeaderButton";
9
-import CustomTabBar from "../../components/Tabbar/CustomTabBar";
10
-import {StackNavigationProp} from "@react-navigation/stack";
11
-import type {feedItem} from "./HomeScreen";
12
-import CollapsibleScrollView from "../../components/Collapsible/CollapsibleScrollView";
7
+import Autolink from 'react-native-autolink';
8
+import {StackNavigationProp} from '@react-navigation/stack';
9
+import MaterialHeaderButtons, {
10
+  Item,
11
+} from '../../components/Overrides/CustomHeaderButton';
12
+import CustomTabBar from '../../components/Tabbar/CustomTabBar';
13
+import type {FeedItemType} from './HomeScreen';
14
+import CollapsibleScrollView from '../../components/Collapsible/CollapsibleScrollView';
13 15
 
14
-type Props = {
15
-    navigation: StackNavigationProp,
16
-    route: { params: { data: feedItem, date: string } }
16
+type PropsType = {
17
+  navigation: StackNavigationProp,
18
+  route: {params: {data: FeedItemType, date: string}},
17 19
 };
18 20
 
19 21
 const ICON_AMICALE = require('../../../assets/amicale.png');
22
+
20 23
 const NAME_AMICALE = 'Amicale INSA Toulouse';
21 24
 
22 25
 /**
23 26
  * Class defining a feed item page.
24 27
  */
25
-class FeedItemScreen extends React.Component<Props> {
26
-
27
-    displayData: feedItem;
28
-    date: string;
28
+class FeedItemScreen extends React.Component<PropsType> {
29
+  displayData: FeedItemType;
29 30
 
30
-    constructor(props) {
31
-        super(props);
32
-        this.displayData = props.route.params.data;
33
-        this.date = props.route.params.date;
34
-    }
31
+  date: string;
35 32
 
36
-    componentDidMount() {
37
-        this.props.navigation.setOptions({
38
-            headerRight: this.getHeaderButton,
39
-        });
40
-    }
33
+  constructor(props: PropsType) {
34
+    super(props);
35
+    this.displayData = props.route.params.data;
36
+    this.date = props.route.params.date;
37
+  }
41 38
 
42
-    /**
43
-     * Opens the feed item out link in browser or compatible app
44
-     */
45
-    onOutLinkPress = () => {
46
-        Linking.openURL(this.displayData.permalink_url);
47
-    };
39
+  componentDidMount() {
40
+    const {props} = this;
41
+    props.navigation.setOptions({
42
+      headerRight: this.getHeaderButton,
43
+    });
44
+  }
48 45
 
49
-    /**
50
-     * Gets the out link header button
51
-     *
52
-     * @returns {*}
53
-     */
54
-    getHeaderButton = () => {
55
-        return <MaterialHeaderButtons>
56
-            <Item title="main" iconName={'facebook'} color={"#2e88fe"} onPress={this.onOutLinkPress}/>
57
-        </MaterialHeaderButtons>;
58
-    };
46
+  /**
47
+   * Opens the feed item out link in browser or compatible app
48
+   */
49
+  onOutLinkPress = () => {
50
+    Linking.openURL(this.displayData.permalink_url);
51
+  };
59 52
 
60
-    /**
61
-     * Gets the Amicale INSA avatar
62
-     *
63
-     * @returns {*}
64
-     */
65
-    getAvatar() {
66
-        return (
67
-            <Avatar.Image size={48} source={ICON_AMICALE}
68
-                          style={{backgroundColor: 'transparent'}}/>
69
-        );
70
-    }
53
+  /**
54
+   * Gets the out link header button
55
+   *
56
+   * @returns {*}
57
+   */
58
+  getHeaderButton = (): React.Node => {
59
+    return (
60
+      <MaterialHeaderButtons>
61
+        <Item
62
+          title="main"
63
+          iconName="facebook"
64
+          color="#2e88fe"
65
+          onPress={this.onOutLinkPress}
66
+        />
67
+      </MaterialHeaderButtons>
68
+    );
69
+  };
71 70
 
72
-    render() {
73
-        const hasImage = this.displayData.full_picture !== '' && this.displayData.full_picture != null;
74
-        return (
75
-            <CollapsibleScrollView
76
-                style={{margin: 5,}}
77
-                hasTab={true}
78
-            >
79
-                <Card.Title
80
-                    title={NAME_AMICALE}
81
-                    subtitle={this.date}
82
-                    left={this.getAvatar}
83
-                />
84
-                {hasImage ?
85
-                    <View style={{marginLeft: 'auto', marginRight: 'auto'}}>
86
-                        <ImageModal
87
-                            resizeMode="contain"
88
-                            imageBackgroundColor={"#000"}
89
-                            style={{
90
-                                width: 250,
91
-                                height: 250,
92
-                            }}
93
-                            source={{
94
-                                uri: this.displayData.full_picture,
95
-                            }}
96
-                        /></View> : null}
97
-                <Card.Content style={{paddingBottom: CustomTabBar.TAB_BAR_HEIGHT + 20}}>
98
-                    {this.displayData.message !== undefined ?
99
-                        <Autolink
100
-                            text={this.displayData.message}
101
-                            hashtag="facebook"
102
-                            component={Text}
103
-                        /> : null
104
-                    }
105
-                </Card.Content>
106
-            </CollapsibleScrollView>
107
-        );
108
-    }
71
+  render(): React.Node {
72
+    const hasImage =
73
+      this.displayData.full_picture !== '' &&
74
+      this.displayData.full_picture != null;
75
+    return (
76
+      <CollapsibleScrollView style={{margin: 5}} hasTab>
77
+        <Card.Title
78
+          title={NAME_AMICALE}
79
+          subtitle={this.date}
80
+          left={(): React.Node => (
81
+            <Avatar.Image
82
+              size={48}
83
+              source={ICON_AMICALE}
84
+              style={{backgroundColor: 'transparent'}}
85
+            />
86
+          )}
87
+        />
88
+        {hasImage ? (
89
+          <View style={{marginLeft: 'auto', marginRight: 'auto'}}>
90
+            <ImageModal
91
+              resizeMode="contain"
92
+              imageBackgroundColor="#000"
93
+              style={{
94
+                width: 250,
95
+                height: 250,
96
+              }}
97
+              source={{
98
+                uri: this.displayData.full_picture,
99
+              }}
100
+            />
101
+          </View>
102
+        ) : null}
103
+        <Card.Content style={{paddingBottom: CustomTabBar.TAB_BAR_HEIGHT + 20}}>
104
+          {this.displayData.message !== undefined ? (
105
+            <Autolink
106
+              text={this.displayData.message}
107
+              hashtag="facebook"
108
+              component={Text}
109
+            />
110
+          ) : null}
111
+        </Card.Content>
112
+      </CollapsibleScrollView>
113
+    );
114
+  }
109 115
 }
110 116
 
111 117
 export default withTheme(FeedItemScreen);

+ 482
- 542
src/screens/Home/HomeScreen.js
File diff suppressed because it is too large
View File


+ 218
- 219
src/screens/Home/ScannerScreen.js View File

@@ -1,238 +1,237 @@
1 1
 // @flow
2 2
 
3 3
 import * as React from 'react';
4
-import {Linking, Platform, StyleSheet, View} from "react-native";
4
+import {Linking, Platform, StyleSheet, View} from 'react-native';
5 5
 import {Button, Text, withTheme} from 'react-native-paper';
6 6
 import {RNCamera} from 'react-native-camera';
7 7
 import {BarcodeMask} from '@nartc/react-native-barcode-mask';
8
-import URLHandler from "../../utils/URLHandler";
9
-import AlertDialog from "../../components/Dialogs/AlertDialog";
10 8
 import i18n from 'i18n-js';
11
-import CustomTabBar from "../../components/Tabbar/CustomTabBar";
12
-import LoadingConfirmDialog from "../../components/Dialogs/LoadingConfirmDialog";
13 9
 import {PERMISSIONS, request, RESULTS} from 'react-native-permissions';
14
-import {MASCOT_STYLE} from "../../components/Mascot/Mascot";
15
-import MascotPopup from "../../components/Mascot/MascotPopup";
16
-
17
-type Props = {};
18
-type State = {
19
-    hasPermission: boolean,
20
-    scanned: boolean,
21
-    dialogVisible: boolean,
22
-    mascotDialogVisible: boolean,
23
-    dialogTitle: string,
24
-    dialogMessage: string,
25
-    loading: boolean,
10
+import URLHandler from '../../utils/URLHandler';
11
+import AlertDialog from '../../components/Dialogs/AlertDialog';
12
+import CustomTabBar from '../../components/Tabbar/CustomTabBar';
13
+import LoadingConfirmDialog from '../../components/Dialogs/LoadingConfirmDialog';
14
+import {MASCOT_STYLE} from '../../components/Mascot/Mascot';
15
+import MascotPopup from '../../components/Mascot/MascotPopup';
16
+
17
+type StateType = {
18
+  hasPermission: boolean,
19
+  scanned: boolean,
20
+  dialogVisible: boolean,
21
+  mascotDialogVisible: boolean,
22
+  loading: boolean,
26 23
 };
27 24
 
28
-class ScannerScreen extends React.Component<Props, State> {
29
-
30
-    state = {
31
-        hasPermission: false,
32
-        scanned: false,
33
-        mascotDialogVisible: false,
34
-        dialogVisible: false,
35
-        dialogTitle: "",
36
-        dialogMessage: "",
37
-        loading: false,
38
-    };
39
-
40
-    constructor() {
41
-        super();
42
-    }
43
-
44
-    componentDidMount() {
45
-        this.requestPermissions();
46
-    }
47
-
48
-    /**
49
-     * Requests permission to use the camera
50
-     */
51
-    requestPermissions = () => {
52
-        if (Platform.OS === 'android')
53
-            request(PERMISSIONS.ANDROID.CAMERA).then(this.updatePermissionStatus)
54
-        else
55
-            request(PERMISSIONS.IOS.CAMERA).then(this.updatePermissionStatus)
56
-    };
57
-
58
-    /**
59
-     * Updates the state permission status
60
-     *
61
-     * @param result
62
-     */
63
-    updatePermissionStatus = (result) => this.setState({hasPermission: result === RESULTS.GRANTED});
64
-
65
-    /**
66
-     * Opens scanned link if it is a valid app link or shows and error dialog
67
-     *
68
-     * @param type The barcode type
69
-     * @param data The scanned value
70
-     */
71
-    handleCodeScanned = ({type, data}) => {
72
-        if (!URLHandler.isUrlValid(data))
73
-            this.showErrorDialog();
74
-        else {
75
-            this.showOpeningDialog();
76
-            Linking.openURL(data);
77
-        }
78
-    };
79
-
80
-    /**
81
-     * Gets a view asking user for permission to use the camera
82
-     *
83
-     * @returns {*}
84
-     */
85
-    getPermissionScreen() {
86
-        return <View style={{marginLeft: 10, marginRight: 10}}>
87
-            <Text>{i18n.t("screens.scanner.permissions.error")}</Text>
88
-            <Button
89
-                icon="camera"
90
-                mode="contained"
91
-                onPress={this.requestPermissions}
92
-                style={{
93
-                    marginTop: 10,
94
-                    marginLeft: 'auto',
95
-                    marginRight: 'auto',
96
-                }}
97
-            >
98
-                {i18n.t("screens.scanner.permissions.button")}
99
-            </Button>
100
-        </View>
101
-    }
102
-
103
-    /**
104
-     * Shows a dialog indicating how to use the scanner
105
-     */
106
-    showHelpDialog = () => {
107
-        this.setState({
108
-            mascotDialogVisible: true,
109
-            scanned: true,
110
-        });
111
-    };
25
+const styles = StyleSheet.create({
26
+  container: {
27
+    flex: 1,
28
+    justifyContent: 'center',
29
+  },
30
+  button: {
31
+    position: 'absolute',
32
+    bottom: 20,
33
+    width: '80%',
34
+    left: '10%',
35
+  },
36
+});
112 37
 
113
-    /**
114
-     * Shows a loading dialog
115
-     */
116
-    showOpeningDialog = () => {
117
-        this.setState({
118
-            loading: true,
119
-            scanned: true,
120
-        });
38
+class ScannerScreen extends React.Component<null, StateType> {
39
+  constructor() {
40
+    super();
41
+    this.state = {
42
+      hasPermission: false,
43
+      scanned: false,
44
+      mascotDialogVisible: false,
45
+      dialogVisible: false,
46
+      loading: false,
121 47
     };
122
-
123
-    /**
124
-     * Shows a dialog indicating the user the scanned code was invalid
125
-     */
126
-    showErrorDialog() {
127
-        this.setState({
128
-            dialogVisible: true,
129
-            scanned: true,
130
-        });
131
-    }
132
-
133
-    /**
134
-     * Hide any dialog
135
-     */
136
-    onDialogDismiss = () => this.setState({
137
-        dialogVisible: false,
138
-        scanned: false,
48
+  }
49
+
50
+  componentDidMount() {
51
+    this.requestPermissions();
52
+  }
53
+
54
+  /**
55
+   * Gets a view asking user for permission to use the camera
56
+   *
57
+   * @returns {*}
58
+   */
59
+  getPermissionScreen(): React.Node {
60
+    return (
61
+      <View style={{marginLeft: 10, marginRight: 10}}>
62
+        <Text>{i18n.t('screens.scanner.permissions.error')}</Text>
63
+        <Button
64
+          icon="camera"
65
+          mode="contained"
66
+          onPress={this.requestPermissions}
67
+          style={{
68
+            marginTop: 10,
69
+            marginLeft: 'auto',
70
+            marginRight: 'auto',
71
+          }}>
72
+          {i18n.t('screens.scanner.permissions.button')}
73
+        </Button>
74
+      </View>
75
+    );
76
+  }
77
+
78
+  /**
79
+   * Gets a view with the scanner.
80
+   * This scanner uses the back camera, can only scan qr codes and has a square mask on the center.
81
+   * The mask is only for design purposes as a code is scanned as soon as it enters the camera view
82
+   *
83
+   * @returns {*}
84
+   */
85
+  getScanner(): React.Node {
86
+    const {state} = this;
87
+    return (
88
+      <RNCamera
89
+        onBarCodeRead={state.scanned ? null : this.onCodeScanned}
90
+        type={RNCamera.Constants.Type.back}
91
+        barCodeScannerSettings={{
92
+          barCodeTypes: [RNCamera.Constants.BarCodeType.qr],
93
+        }}
94
+        style={StyleSheet.absoluteFill}
95
+        captureAudio={false}>
96
+        <BarcodeMask
97
+          backgroundColor="#000"
98
+          maskOpacity={0.5}
99
+          animatedLineThickness={1}
100
+          animationDuration={1000}
101
+          width={250}
102
+          height={250}
103
+        />
104
+      </RNCamera>
105
+    );
106
+  }
107
+
108
+  /**
109
+   * Requests permission to use the camera
110
+   */
111
+  requestPermissions = () => {
112
+    if (Platform.OS === 'android')
113
+      request(PERMISSIONS.ANDROID.CAMERA).then(this.updatePermissionStatus);
114
+    else request(PERMISSIONS.IOS.CAMERA).then(this.updatePermissionStatus);
115
+  };
116
+
117
+  /**
118
+   * Updates the state permission status
119
+   *
120
+   * @param result
121
+   */
122
+  updatePermissionStatus = (result: RESULTS) => {
123
+    this.setState({
124
+      hasPermission: result === RESULTS.GRANTED,
139 125
     });
140
-
141
-    onMascotDialogDismiss = () => this.setState({
142
-        mascotDialogVisible: false,
143
-        scanned: false,
126
+  };
127
+
128
+  /**
129
+   * Shows a dialog indicating the user the scanned code was invalid
130
+   */
131
+  // eslint-disable-next-line react/sort-comp
132
+  showErrorDialog() {
133
+    this.setState({
134
+      dialogVisible: true,
135
+      scanned: true,
144 136
     });
137
+  }
138
+
139
+  /**
140
+   * Shows a dialog indicating how to use the scanner
141
+   */
142
+  showHelpDialog = () => {
143
+    this.setState({
144
+      mascotDialogVisible: true,
145
+      scanned: true,
146
+    });
147
+  };
148
+
149
+  /**
150
+   * Shows a loading dialog
151
+   */
152
+  showOpeningDialog = () => {
153
+    this.setState({
154
+      loading: true,
155
+      scanned: true,
156
+    });
157
+  };
158
+
159
+  /**
160
+   * Hide any dialog
161
+   */
162
+  onDialogDismiss = () => {
163
+    this.setState({
164
+      dialogVisible: false,
165
+      scanned: false,
166
+    });
167
+  };
145 168
 
146
-    /**
147
-     * Gets a view with the scanner.
148
-     * This scanner uses the back camera, can only scan qr codes and has a square mask on the center.
149
-     * The mask is only for design purposes as a code is scanned as soon as it enters the camera view
150
-     *
151
-     * @returns {*}
152
-     */
153
-    getScanner() {
154
-        return (
155
-            <RNCamera
156
-                onBarCodeRead={this.state.scanned ? undefined : this.handleCodeScanned}
157
-                type={RNCamera.Constants.Type.back}
158
-                barCodeScannerSettings={{
159
-                    barCodeTypes: [RNCamera.Constants.BarCodeType.qr],
160
-                }}
161
-                style={StyleSheet.absoluteFill}
162
-                captureAudio={false}
163
-            >
164
-                <BarcodeMask
165
-                    backgroundColor={"#000"}
166
-                    maskOpacity={0.5}
167
-                    animatedLineThickness={1}
168
-                    animationDuration={1000}
169
-                    width={250}
170
-                    height={250}
171
-                />
172
-            </RNCamera>
173
-        );
174
-    }
175
-
176
-    render() {
177
-        return (
178
-            <View style={{
179
-                ...styles.container,
180
-                marginBottom: CustomTabBar.TAB_BAR_HEIGHT
181
-            }}>
182
-                {this.state.hasPermission
183
-                    ? this.getScanner()
184
-                    : this.getPermissionScreen()
185
-                }
186
-                <Button
187
-                    icon="information"
188
-                    mode="contained"
189
-                    onPress={this.showHelpDialog}
190
-                    style={styles.button}
191
-                >
192
-                    {i18n.t("screens.scanner.help.button")}
193
-                </Button>
194
-                <MascotPopup
195
-                    visible={this.state.mascotDialogVisible}
196
-                    title={i18n.t("screens.scanner.mascotDialog.title")}
197
-                    message={i18n.t("screens.scanner.mascotDialog.message")}
198
-                    icon={"camera-iris"}
199
-                    buttons={{
200
-                        action: null,
201
-                        cancel: {
202
-                            message: i18n.t("screens.scanner.mascotDialog.button"),
203
-                            icon: "check",
204
-                            onPress: this.onMascotDialogDismiss,
205
-                        }
206
-                    }}
207
-                    emotion={MASCOT_STYLE.NORMAL}
208
-                />
209
-                <AlertDialog
210
-                    visible={this.state.dialogVisible}
211
-                    onDismiss={this.onDialogDismiss}
212
-                    title={i18n.t("screens.scanner.error.title")}
213
-                    message={i18n.t("screens.scanner.error.message")}
214
-                />
215
-                <LoadingConfirmDialog
216
-                    visible={this.state.loading}
217
-                    titleLoading={i18n.t("general.loading")}
218
-                    startLoading={true}
219
-                />
220
-            </View>
221
-        );
169
+  onMascotDialogDismiss = () => {
170
+    this.setState({
171
+      mascotDialogVisible: false,
172
+      scanned: false,
173
+    });
174
+  };
175
+
176
+  /**
177
+   * Opens scanned link if it is a valid app link or shows and error dialog
178
+   *
179
+   * @param type The barcode type
180
+   * @param data The scanned value
181
+   */
182
+  onCodeScanned = ({data}: {data: string}) => {
183
+    if (!URLHandler.isUrlValid(data)) this.showErrorDialog();
184
+    else {
185
+      this.showOpeningDialog();
186
+      Linking.openURL(data);
222 187
     }
188
+  };
189
+
190
+  render(): React.Node {
191
+    const {state} = this;
192
+    return (
193
+      <View
194
+        style={{
195
+          ...styles.container,
196
+          marginBottom: CustomTabBar.TAB_BAR_HEIGHT,
197
+        }}>
198
+        {state.hasPermission ? this.getScanner() : this.getPermissionScreen()}
199
+        <Button
200
+          icon="information"
201
+          mode="contained"
202
+          onPress={this.showHelpDialog}
203
+          style={styles.button}>
204
+          {i18n.t('screens.scanner.help.button')}
205
+        </Button>
206
+        <MascotPopup
207
+          visible={state.mascotDialogVisible}
208
+          title={i18n.t('screens.scanner.mascotDialog.title')}
209
+          message={i18n.t('screens.scanner.mascotDialog.message')}
210
+          icon="camera-iris"
211
+          buttons={{
212
+            action: null,
213
+            cancel: {
214
+              message: i18n.t('screens.scanner.mascotDialog.button'),
215
+              icon: 'check',
216
+              onPress: this.onMascotDialogDismiss,
217
+            },
218
+          }}
219
+          emotion={MASCOT_STYLE.NORMAL}
220
+        />
221
+        <AlertDialog
222
+          visible={state.dialogVisible}
223
+          onDismiss={this.onDialogDismiss}
224
+          title={i18n.t('screens.scanner.error.title')}
225
+          message={i18n.t('screens.scanner.error.message')}
226
+        />
227
+        <LoadingConfirmDialog
228
+          visible={state.loading}
229
+          titleLoading={i18n.t('general.loading')}
230
+          startLoading
231
+        />
232
+      </View>
233
+    );
234
+  }
223 235
 }
224 236
 
225
-const styles = StyleSheet.create({
226
-    container: {
227
-        flex: 1,
228
-        justifyContent: 'center',
229
-    },
230
-    button: {
231
-        position: 'absolute',
232
-        bottom: 20,
233
-        width: '80%',
234
-        left: '10%'
235
-    },
236
-});
237
-
238 237
 export default withTheme(ScannerScreen);

+ 123
- 0
src/utils/Home.js View File

@@ -0,0 +1,123 @@
1
+// @flow
2
+
3
+import {stringToDate} from './Planning';
4
+import type {EventType} from '../screens/Home/HomeScreen';
5
+
6
+/**
7
+ * Gets the time limit depending on the current day:
8
+ * 17:30 for every day of the week except for thursday 11:30
9
+ * 00:00 on weekends
10
+ */
11
+export function getTodayEventTimeLimit(): Date {
12
+  const now = new Date();
13
+  if (now.getDay() === 4)
14
+    // Thursday
15
+    now.setHours(11, 30, 0);
16
+  else if (now.getDay() === 6 || now.getDay() === 0)
17
+    // Weekend
18
+    now.setHours(0, 0, 0);
19
+  else now.setHours(17, 30, 0);
20
+  return now;
21
+}
22
+
23
+/**
24
+ * Gets the duration (in milliseconds) of an event
25
+ *
26
+ * @param event {EventType}
27
+ * @return {number} The number of milliseconds
28
+ */
29
+export function getEventDuration(event: EventType): number {
30
+  const start = stringToDate(event.date_begin);
31
+  const end = stringToDate(event.date_end);
32
+  let duration = 0;
33
+  if (start != null && end != null) duration = end - start;
34
+  return duration;
35
+}
36
+
37
+/**
38
+ * Gets events starting after the limit
39
+ *
40
+ * @param events
41
+ * @param limit
42
+ * @return {Array<Object>}
43
+ */
44
+export function getEventsAfterLimit(
45
+  events: Array<EventType>,
46
+  limit: Date,
47
+): Array<EventType> {
48
+  const validEvents = [];
49
+  events.forEach((event: EventType) => {
50
+    const startDate = stringToDate(event.date_begin);
51
+    if (startDate != null && startDate >= limit) {
52
+      validEvents.push(event);
53
+    }
54
+  });
55
+  return validEvents;
56
+}
57
+
58
+/**
59
+ * Gets the event with the longest duration in the given array.
60
+ * If all events have the same duration, return the first in the array.
61
+ *
62
+ * @param events
63
+ */
64
+export function getLongestEvent(events: Array<EventType>): EventType {
65
+  let longestEvent = events[0];
66
+  let longestTime = 0;
67
+  events.forEach((event: EventType) => {
68
+    const time = getEventDuration(event);
69
+    if (time > longestTime) {
70
+      longestTime = time;
71
+      longestEvent = event;
72
+    }
73
+  });
74
+  return longestEvent;
75
+}
76
+
77
+/**
78
+ * Gets events that have not yet ended/started
79
+ *
80
+ * @param events
81
+ */
82
+export function getFutureEvents(events: Array<EventType>): Array<EventType> {
83
+  const validEvents = [];
84
+  const now = new Date();
85
+  events.forEach((event: EventType) => {
86
+    const startDate = stringToDate(event.date_begin);
87
+    const endDate = stringToDate(event.date_end);
88
+    if (startDate != null) {
89
+      if (startDate > now) validEvents.push(event);
90
+      else if (endDate != null) {
91
+        if (endDate > now || endDate < startDate)
92
+          // Display event if it ends the following day
93
+          validEvents.push(event);
94
+      }
95
+    }
96
+  });
97
+  return validEvents;
98
+}
99
+
100
+/**
101
+ * Gets the event to display in the preview
102
+ *
103
+ * @param events
104
+ * @return {EventType | null}
105
+ */
106
+export function getDisplayEvent(events: Array<EventType>): EventType | null {
107
+  let displayEvent = null;
108
+  if (events.length > 1) {
109
+    const eventsAfterLimit = getEventsAfterLimit(
110
+      events,
111
+      getTodayEventTimeLimit(),
112
+    );
113
+    if (eventsAfterLimit.length > 0) {
114
+      if (eventsAfterLimit.length === 1) [displayEvent] = eventsAfterLimit;
115
+      else displayEvent = getLongestEvent(events);
116
+    } else {
117
+      displayEvent = getLongestEvent(events);
118
+    }
119
+  } else if (events.length === 1) {
120
+    [displayEvent] = events;
121
+  }
122
+  return displayEvent;
123
+}

Loading…
Cancel
Save