Browse Source

Improve Mascot components to match linter

Arnaud Vergnet 3 years ago
parent
commit
7b94afadcc
3 changed files with 538 additions and 489 deletions
  1. 227
    217
      src/components/Mascot/Mascot.js
  2. 275
    246
      src/components/Mascot/MascotPopup.js
  3. 36
    26
      src/components/Mascot/SpeechArrow.js

+ 227
- 217
src/components/Mascot/Mascot.js View File

@@ -1,259 +1,269 @@
1 1
 // @flow
2 2
 
3 3
 import * as React from 'react';
4
-import * as Animatable from "react-native-animatable";
5
-import {Image, TouchableWithoutFeedback, View} from "react-native";
6
-import type {ViewStyle} from "react-native/Libraries/StyleSheet/StyleSheet";
4
+import * as Animatable from 'react-native-animatable';
5
+import {Image, TouchableWithoutFeedback, View} from 'react-native';
6
+import type {ViewStyle} from 'react-native/Libraries/StyleSheet/StyleSheet';
7 7
 
8
-type Props = {
9
-    style?: ViewStyle,
10
-    emotion: number,
11
-    animated: boolean,
12
-    entryAnimation: Animatable.AnimatableProperties | null,
13
-    loopAnimation: Animatable.AnimatableProperties | null,
14
-    onPress?: (viewRef: AnimatableViewRef) => null,
15
-    onLongPress?: (viewRef: AnimatableViewRef) => null,
16
-}
8
+export type AnimatableViewRefType = {current: null | Animatable.View};
17 9
 
18
-type State = {
19
-    currentEmotion: number,
20
-}
10
+type PropsType = {
11
+  emotion?: number,
12
+  animated?: boolean,
13
+  style?: ViewStyle | null,
14
+  entryAnimation?: Animatable.AnimatableProperties | null,
15
+  loopAnimation?: Animatable.AnimatableProperties | null,
16
+  onPress?: null | ((viewRef: AnimatableViewRefType) => void),
17
+  onLongPress?: null | ((viewRef: AnimatableViewRefType) => void),
18
+};
21 19
 
22
-export type AnimatableViewRef = {current: null | Animatable.View};
20
+type StateType = {
21
+  currentEmotion: number,
22
+};
23 23
 
24
-const MASCOT_IMAGE = require("../../../assets/mascot/mascot.png");
25
-const MASCOT_EYES_NORMAL = require("../../../assets/mascot/mascot_eyes_normal.png");
26
-const MASCOT_EYES_GIRLY = require("../../../assets/mascot/mascot_eyes_girly.png");
27
-const MASCOT_EYES_CUTE = require("../../../assets/mascot/mascot_eyes_cute.png");
28
-const MASCOT_EYES_WINK = require("../../../assets/mascot/mascot_eyes_wink.png");
29
-const MASCOT_EYES_HEART = require("../../../assets/mascot/mascot_eyes_heart.png");
30
-const MASCOT_EYES_ANGRY = require("../../../assets/mascot/mascot_eyes_angry.png");
31
-const MASCOT_GLASSES = require("../../../assets/mascot/mascot_glasses.png");
32
-const MASCOT_SUNGLASSES = require("../../../assets/mascot/mascot_sunglasses.png");
24
+const MASCOT_IMAGE = require('../../../assets/mascot/mascot.png');
25
+const MASCOT_EYES_NORMAL = require('../../../assets/mascot/mascot_eyes_normal.png');
26
+const MASCOT_EYES_GIRLY = require('../../../assets/mascot/mascot_eyes_girly.png');
27
+const MASCOT_EYES_CUTE = require('../../../assets/mascot/mascot_eyes_cute.png');
28
+const MASCOT_EYES_WINK = require('../../../assets/mascot/mascot_eyes_wink.png');
29
+const MASCOT_EYES_HEART = require('../../../assets/mascot/mascot_eyes_heart.png');
30
+const MASCOT_EYES_ANGRY = require('../../../assets/mascot/mascot_eyes_angry.png');
31
+const MASCOT_GLASSES = require('../../../assets/mascot/mascot_glasses.png');
32
+const MASCOT_SUNGLASSES = require('../../../assets/mascot/mascot_sunglasses.png');
33 33
 
34 34
 export const EYE_STYLE = {
35
-    NORMAL: 0,
36
-    GIRLY: 2,
37
-    CUTE: 3,
38
-    WINK: 4,
39
-    HEART: 5,
40
-    ANGRY: 6,
41
-}
35
+  NORMAL: 0,
36
+  GIRLY: 2,
37
+  CUTE: 3,
38
+  WINK: 4,
39
+  HEART: 5,
40
+  ANGRY: 6,
41
+};
42 42
 
43 43
 const GLASSES_STYLE = {
44
-    NORMAL: 0,
45
-    COOl: 1
46
-}
44
+  NORMAL: 0,
45
+  COOl: 1,
46
+};
47 47
 
48 48
 export const MASCOT_STYLE = {
49
-    NORMAL: 0,
50
-    HAPPY: 1,
51
-    GIRLY: 2,
52
-    WINK: 3,
53
-    CUTE: 4,
54
-    INTELLO: 5,
55
-    LOVE: 6,
56
-    COOL: 7,
57
-    ANGRY: 8,
58
-    RANDOM: 999,
49
+  NORMAL: 0,
50
+  HAPPY: 1,
51
+  GIRLY: 2,
52
+  WINK: 3,
53
+  CUTE: 4,
54
+  INTELLO: 5,
55
+  LOVE: 6,
56
+  COOL: 7,
57
+  ANGRY: 8,
58
+  RANDOM: 999,
59 59
 };
60 60
 
61
+class Mascot extends React.Component<PropsType, StateType> {
62
+  static defaultProps = {
63
+    emotion: MASCOT_STYLE.NORMAL,
64
+    animated: false,
65
+    style: null,
66
+    entryAnimation: {
67
+      useNativeDriver: true,
68
+      animation: 'rubberBand',
69
+      duration: 2000,
70
+    },
71
+    loopAnimation: {
72
+      useNativeDriver: true,
73
+      animation: 'swing',
74
+      duration: 2000,
75
+      iterationDelay: 250,
76
+      iterationCount: 'infinite',
77
+    },
78
+    onPress: null,
79
+    onLongPress: null,
80
+  };
61 81
 
62
-class Mascot extends React.Component<Props, State> {
63
-
64
-    static defaultProps = {
65
-        animated: false,
66
-        entryAnimation: {
67
-            useNativeDriver: true,
68
-            animation: "rubberBand",
69
-            duration: 2000,
70
-        },
71
-        loopAnimation: {
72
-            useNativeDriver: true,
73
-            animation: "swing",
74
-            duration: 2000,
75
-            iterationDelay: 250,
76
-            iterationCount: "infinite",
77
-        },
78
-        clickAnimation: {
79
-            useNativeDriver: true,
80
-            animation: "rubberBand",
81
-            duration: 2000,
82
-        },
83
-    }
82
+  viewRef: AnimatableViewRefType;
84 83
 
85
-    viewRef: AnimatableViewRef;
86
-    eyeList: { [key: number]: number | string };
87
-    glassesList: { [key: number]: number | string };
84
+  eyeList: {[key: number]: number | string};
88 85
 
89
-    onPress: (viewRef: AnimatableViewRef) => null;
90
-    onLongPress: (viewRef: AnimatableViewRef) => null;
86
+  glassesList: {[key: number]: number | string};
91 87
 
92
-    initialEmotion: number;
88
+  onPress: (viewRef: AnimatableViewRefType) => void;
93 89
 
94
-    constructor(props: Props) {
95
-        super(props);
96
-        this.viewRef = React.createRef();
97
-        this.eyeList = {};
98
-        this.glassesList = {};
99
-        this.eyeList[EYE_STYLE.NORMAL] = MASCOT_EYES_NORMAL;
100
-        this.eyeList[EYE_STYLE.GIRLY] = MASCOT_EYES_GIRLY;
101
-        this.eyeList[EYE_STYLE.CUTE] = MASCOT_EYES_CUTE;
102
-        this.eyeList[EYE_STYLE.WINK] = MASCOT_EYES_WINK;
103
-        this.eyeList[EYE_STYLE.HEART] = MASCOT_EYES_HEART;
104
-        this.eyeList[EYE_STYLE.ANGRY] = MASCOT_EYES_ANGRY;
90
+  onLongPress: (viewRef: AnimatableViewRefType) => void;
105 91
 
106
-        this.glassesList[GLASSES_STYLE.NORMAL] = MASCOT_GLASSES;
107
-        this.glassesList[GLASSES_STYLE.COOl] = MASCOT_SUNGLASSES;
92
+  initialEmotion: number;
108 93
 
109
-        this.initialEmotion = this.props.emotion;
94
+  constructor(props: PropsType) {
95
+    super(props);
96
+    this.viewRef = React.createRef();
97
+    this.eyeList = {};
98
+    this.glassesList = {};
99
+    this.eyeList[EYE_STYLE.NORMAL] = MASCOT_EYES_NORMAL;
100
+    this.eyeList[EYE_STYLE.GIRLY] = MASCOT_EYES_GIRLY;
101
+    this.eyeList[EYE_STYLE.CUTE] = MASCOT_EYES_CUTE;
102
+    this.eyeList[EYE_STYLE.WINK] = MASCOT_EYES_WINK;
103
+    this.eyeList[EYE_STYLE.HEART] = MASCOT_EYES_HEART;
104
+    this.eyeList[EYE_STYLE.ANGRY] = MASCOT_EYES_ANGRY;
110 105
 
111
-        if (this.initialEmotion === MASCOT_STYLE.RANDOM)
112
-            this.initialEmotion = Math.floor(Math.random() * MASCOT_STYLE.ANGRY) + 1;
106
+    this.glassesList[GLASSES_STYLE.NORMAL] = MASCOT_GLASSES;
107
+    this.glassesList[GLASSES_STYLE.COOl] = MASCOT_SUNGLASSES;
113 108
 
114
-        this.state = {
115
-            currentEmotion: this.initialEmotion
116
-        }
109
+    this.initialEmotion =
110
+      props.emotion != null ? props.emotion : Mascot.defaultProps.emotion;
117 111
 
118
-        if (this.props.onPress == null) {
119
-            this.onPress = (viewRef: AnimatableViewRef) => {
120
-                let ref = viewRef.current;
121
-                if (ref != null) {
122
-                    this.setState({currentEmotion: MASCOT_STYLE.LOVE});
123
-                    ref.rubberBand(1500).then(() => {
124
-                        this.setState({currentEmotion: this.initialEmotion});
125
-                    });
112
+    if (this.initialEmotion === MASCOT_STYLE.RANDOM)
113
+      this.initialEmotion = Math.floor(Math.random() * MASCOT_STYLE.ANGRY) + 1;
126 114
 
127
-                }
128
-                return null;
129
-            }
130
-        } else
131
-            this.onPress = this.props.onPress;
115
+    this.state = {
116
+      currentEmotion: this.initialEmotion,
117
+    };
132 118
 
133
-        if (this.props.onLongPress == null) {
134
-            this.onLongPress = (viewRef: AnimatableViewRef) => {
135
-                let ref = viewRef.current;
136
-                if (ref != null) {
137
-                    this.setState({currentEmotion: MASCOT_STYLE.ANGRY});
138
-                    ref.tada(1000).then(() => {
139
-                        this.setState({currentEmotion: this.initialEmotion});
140
-                    });
119
+    if (props.onPress == null) {
120
+      this.onPress = (viewRef: AnimatableViewRefType) => {
121
+        const ref = viewRef.current;
122
+        if (ref != null) {
123
+          this.setState({currentEmotion: MASCOT_STYLE.LOVE});
124
+          ref.rubberBand(1500).then(() => {
125
+            this.setState({currentEmotion: this.initialEmotion});
126
+          });
127
+        }
128
+      };
129
+    } else this.onPress = props.onPress;
141 130
 
142
-                }
143
-                return null;
144
-            }
145
-        } else
146
-            this.onLongPress = this.props.onLongPress;
131
+    if (props.onLongPress == null) {
132
+      this.onLongPress = (viewRef: AnimatableViewRefType) => {
133
+        const ref = viewRef.current;
134
+        if (ref != null) {
135
+          this.setState({currentEmotion: MASCOT_STYLE.ANGRY});
136
+          ref.tada(1000).then(() => {
137
+            this.setState({currentEmotion: this.initialEmotion});
138
+          });
139
+        }
140
+      };
141
+    } else this.onLongPress = props.onLongPress;
142
+  }
147 143
 
148
-    }
144
+  getGlasses(style: number): React.Node {
145
+    const glasses = this.glassesList[style];
146
+    return (
147
+      <Image
148
+        key="glasses"
149
+        source={
150
+          glasses != null ? glasses : this.glassesList[GLASSES_STYLE.NORMAL]
151
+        }
152
+        style={{
153
+          position: 'absolute',
154
+          top: '15%',
155
+          left: 0,
156
+          width: '100%',
157
+          height: '100%',
158
+        }}
159
+      />
160
+    );
161
+  }
149 162
 
150
-    getGlasses(style: number) {
151
-        const glasses = this.glassesList[style];
152
-        return <Image
153
-            key={"glasses"}
154
-            source={glasses != null ? glasses : this.glassesList[GLASSES_STYLE.NORMAL]}
155
-            style={{
156
-                position: "absolute",
157
-                top: "15%",
158
-                left: 0,
159
-                width: "100%",
160
-                height: "100%",
161
-            }}
162
-        />
163
-    }
163
+  getEye(
164
+    style: number,
165
+    isRight: boolean,
166
+    rotation: string = '0deg',
167
+  ): React.Node {
168
+    const eye = this.eyeList[style];
169
+    return (
170
+      <Image
171
+        key={isRight ? 'right' : 'left'}
172
+        source={eye != null ? eye : this.eyeList[EYE_STYLE.NORMAL]}
173
+        style={{
174
+          position: 'absolute',
175
+          top: '15%',
176
+          left: isRight ? '-11%' : '11%',
177
+          width: '100%',
178
+          height: '100%',
179
+          transform: [{rotateY: rotation}],
180
+        }}
181
+      />
182
+    );
183
+  }
164 184
 
165
-    getEye(style: number, isRight: boolean, rotation: string="0deg") {
166
-        const eye = this.eyeList[style];
167
-        return <Image
168
-            key={isRight ? "right" : "left"}
169
-            source={eye != null ? eye : this.eyeList[EYE_STYLE.NORMAL]}
170
-            style={{
171
-                position: "absolute",
172
-                top: "15%",
173
-                left: isRight ? "-11%" : "11%",
174
-                width: "100%",
175
-                height: "100%",
176
-                transform: [{rotateY: rotation}]
177
-            }}
178
-        />
185
+  getEyes(emotion: number): React.Node {
186
+    const final = [];
187
+    final.push(
188
+      <View
189
+        key="container"
190
+        style={{
191
+          position: 'absolute',
192
+          width: '100%',
193
+          height: '100%',
194
+        }}
195
+      />,
196
+    );
197
+    if (emotion === MASCOT_STYLE.CUTE) {
198
+      final.push(this.getEye(EYE_STYLE.CUTE, true));
199
+      final.push(this.getEye(EYE_STYLE.CUTE, false));
200
+    } else if (emotion === MASCOT_STYLE.GIRLY) {
201
+      final.push(this.getEye(EYE_STYLE.GIRLY, true));
202
+      final.push(this.getEye(EYE_STYLE.GIRLY, false));
203
+    } else if (emotion === MASCOT_STYLE.HAPPY) {
204
+      final.push(this.getEye(EYE_STYLE.WINK, true));
205
+      final.push(this.getEye(EYE_STYLE.WINK, false));
206
+    } else if (emotion === MASCOT_STYLE.WINK) {
207
+      final.push(this.getEye(EYE_STYLE.WINK, true));
208
+      final.push(this.getEye(EYE_STYLE.NORMAL, false));
209
+    } else if (emotion === MASCOT_STYLE.LOVE) {
210
+      final.push(this.getEye(EYE_STYLE.HEART, true));
211
+      final.push(this.getEye(EYE_STYLE.HEART, false));
212
+    } else if (emotion === MASCOT_STYLE.ANGRY) {
213
+      final.push(this.getEye(EYE_STYLE.ANGRY, true));
214
+      final.push(this.getEye(EYE_STYLE.ANGRY, false, '180deg'));
215
+    } else if (emotion === MASCOT_STYLE.COOL) {
216
+      final.push(this.getGlasses(GLASSES_STYLE.COOl));
217
+    } else {
218
+      final.push(this.getEye(EYE_STYLE.NORMAL, true));
219
+      final.push(this.getEye(EYE_STYLE.NORMAL, false));
179 220
     }
180 221
 
181
-    getEyes(emotion: number) {
182
-        let final = [];
183
-        final.push(<View
184
-            key={"container"}
185
-            style={{
186
-                position: "absolute",
187
-                width: "100%",
188
-                height: "100%",
189
-            }}/>);
190
-        if (emotion === MASCOT_STYLE.CUTE) {
191
-            final.push(this.getEye(EYE_STYLE.CUTE, true));
192
-            final.push(this.getEye(EYE_STYLE.CUTE, false));
193
-        } else if (emotion === MASCOT_STYLE.GIRLY) {
194
-            final.push(this.getEye(EYE_STYLE.GIRLY, true));
195
-            final.push(this.getEye(EYE_STYLE.GIRLY, false));
196
-        } else if (emotion === MASCOT_STYLE.HAPPY) {
197
-            final.push(this.getEye(EYE_STYLE.WINK, true));
198
-            final.push(this.getEye(EYE_STYLE.WINK, false));
199
-        } else if (emotion === MASCOT_STYLE.WINK) {
200
-            final.push(this.getEye(EYE_STYLE.WINK, true));
201
-            final.push(this.getEye(EYE_STYLE.NORMAL, false));
202
-        } else if (emotion === MASCOT_STYLE.LOVE) {
203
-            final.push(this.getEye(EYE_STYLE.HEART, true));
204
-            final.push(this.getEye(EYE_STYLE.HEART, false));
205
-        } else if (emotion === MASCOT_STYLE.ANGRY) {
206
-            final.push(this.getEye(EYE_STYLE.ANGRY, true));
207
-            final.push(this.getEye(EYE_STYLE.ANGRY, false, "180deg"));
208
-        } else if (emotion === MASCOT_STYLE.COOL) {
209
-            final.push(this.getGlasses(GLASSES_STYLE.COOl));
210
-        } else {
211
-            final.push(this.getEye(EYE_STYLE.NORMAL, true));
212
-            final.push(this.getEye(EYE_STYLE.NORMAL, false));
213
-        }
214
-
215
-        if (emotion === MASCOT_STYLE.INTELLO) { // Needs to have normal eyes behind the glasses
216
-            final.push(this.getGlasses(GLASSES_STYLE.NORMAL));
217
-        }
218
-        final.push(<View key={"container2"}/>);
219
-        return final;
222
+    if (emotion === MASCOT_STYLE.INTELLO) {
223
+      // Needs to have normal eyes behind the glasses
224
+      final.push(this.getGlasses(GLASSES_STYLE.NORMAL));
220 225
     }
226
+    final.push(<View key="container2" />);
227
+    return final;
228
+  }
221 229
 
222
-    render() {
223
-        const entryAnimation = this.props.animated ? this.props.entryAnimation : null;
224
-        const loopAnimation = this.props.animated ? this.props.loopAnimation : null;
225
-        return (
230
+  render(): React.Node {
231
+    const {props, state} = this;
232
+    const entryAnimation = props.animated ? props.entryAnimation : null;
233
+    const loopAnimation = props.animated ? props.loopAnimation : null;
234
+    return (
235
+      <Animatable.View
236
+        style={{
237
+          aspectRatio: 1,
238
+          ...props.style,
239
+        }}
240
+        // eslint-disable-next-line react/jsx-props-no-spreading
241
+        {...entryAnimation}>
242
+        <TouchableWithoutFeedback
243
+          onPress={() => {
244
+            this.onPress(this.viewRef);
245
+          }}
246
+          onLongPress={() => {
247
+            this.onLongPress(this.viewRef);
248
+          }}>
249
+          <Animatable.View ref={this.viewRef}>
226 250
             <Animatable.View
251
+              // eslint-disable-next-line react/jsx-props-no-spreading
252
+              {...loopAnimation}>
253
+              <Image
254
+                source={MASCOT_IMAGE}
227 255
                 style={{
228
-                    aspectRatio: 1,
229
-                    ...this.props.style
256
+                  width: '100%',
257
+                  height: '100%',
230 258
                 }}
231
-                {...entryAnimation}
232
-            >
233
-                <TouchableWithoutFeedback
234
-                    onPress={() => this.onPress(this.viewRef)}
235
-                    onLongPress={() => this.onLongPress(this.viewRef)}
236
-                >
237
-                    <Animatable.View
238
-                        ref={this.viewRef}
239
-                    >
240
-                        <Animatable.View
241
-                            {...loopAnimation}
242
-                        >
243
-                            <Image
244
-                                source={MASCOT_IMAGE}
245
-                                style={{
246
-                                    width: "100%",
247
-                                    height:"100%",
248
-                                }}
249
-                            />
250
-                            {this.getEyes(this.state.currentEmotion)}
251
-                        </Animatable.View>
252
-                    </Animatable.View>
253
-                </TouchableWithoutFeedback>
259
+              />
260
+              {this.getEyes(state.currentEmotion)}
254 261
             </Animatable.View>
255
-        );
256
-    }
262
+          </Animatable.View>
263
+        </TouchableWithoutFeedback>
264
+      </Animatable.View>
265
+    );
266
+  }
257 267
 }
258 268
 
259 269
 export default Mascot;

+ 275
- 246
src/components/Mascot/MascotPopup.js View File

@@ -1,283 +1,312 @@
1 1
 // @flow
2 2
 
3 3
 import * as React from 'react';
4
-import {Avatar, Button, Card, Paragraph, Portal, withTheme} from 'react-native-paper';
5
-import Mascot from "./Mascot";
6
-import * as Animatable from "react-native-animatable";
7
-import {BackHandler, Dimensions, ScrollView, TouchableWithoutFeedback, View} from "react-native";
8
-import type {CustomTheme} from "../../managers/ThemeManager";
9
-import SpeechArrow from "./SpeechArrow";
10
-import AsyncStorageManager from "../../managers/AsyncStorageManager";
4
+import {
5
+  Avatar,
6
+  Button,
7
+  Card,
8
+  Paragraph,
9
+  Portal,
10
+  withTheme,
11
+} from 'react-native-paper';
12
+import * as Animatable from 'react-native-animatable';
13
+import {
14
+  BackHandler,
15
+  Dimensions,
16
+  ScrollView,
17
+  TouchableWithoutFeedback,
18
+  View,
19
+} from 'react-native';
20
+import Mascot from './Mascot';
21
+import type {CustomTheme} from '../../managers/ThemeManager';
22
+import SpeechArrow from './SpeechArrow';
23
+import AsyncStorageManager from '../../managers/AsyncStorageManager';
11 24
 
12
-type Props = {
13
-    theme: CustomTheme,
14
-    icon: string,
15
-    title: string,
16
-    message: string,
17
-    buttons: {
18
-        action: {
19
-            message: string,
20
-            icon: string | null,
21
-            color: string | null,
22
-            onPress?: () => void,
23
-        },
24
-        cancel: {
25
-            message: string,
26
-            icon: string | null,
27
-            color: string | null,
28
-            onPress?: () => void,
29
-        }
25
+type PropsType = {
26
+  theme: CustomTheme,
27
+  icon: string,
28
+  title: string,
29
+  message: string,
30
+  buttons: {
31
+    action: {
32
+      message: string,
33
+      icon: string | null,
34
+      color: string | null,
35
+      onPress?: () => void,
30 36
     },
31
-    emotion: number,
32
-    visible?: boolean,
33
-    prefKey?: string,
34
-}
37
+    cancel: {
38
+      message: string,
39
+      icon: string | null,
40
+      color: string | null,
41
+      onPress?: () => void,
42
+    },
43
+  },
44
+  emotion: number,
45
+  visible?: boolean,
46
+  prefKey?: string,
47
+};
35 48
 
36
-type State = {
37
-    shouldRenderDialog: boolean, // Used to stop rendering after hide animation
38
-    dialogVisible: boolean,
39
-}
49
+type StateType = {
50
+  shouldRenderDialog: boolean, // Used to stop rendering after hide animation
51
+  dialogVisible: boolean,
52
+};
40 53
 
41 54
 /**
42 55
  * Component used to display a popup with the mascot.
43 56
  */
44
-class MascotPopup extends React.Component<Props, State> {
57
+class MascotPopup extends React.Component<PropsType, StateType> {
58
+  static defaultProps = {
59
+    visible: null,
60
+    prefKey: null,
61
+  };
45 62
 
46
-    mascotSize: number;
47
-    windowWidth: number;
48
-    windowHeight: number;
63
+  mascotSize: number;
49 64
 
50
-    constructor(props: Props) {
51
-        super(props);
65
+  windowWidth: number;
52 66
 
53
-        this.windowWidth = Dimensions.get('window').width;
54
-        this.windowHeight = Dimensions.get('window').height;
67
+  windowHeight: number;
55 68
 
56
-        this.mascotSize = Dimensions.get('window').height / 6;
69
+  constructor(props: PropsType) {
70
+    super(props);
57 71
 
58
-        if (this.props.visible != null) {
59
-            this.state = {
60
-                shouldRenderDialog: this.props.visible,
61
-                dialogVisible: this.props.visible,
62
-            };
63
-        } else if (this.props.prefKey != null) {
64
-            const visible = AsyncStorageManager.getBool(this.props.prefKey);
65
-            this.state = {
66
-                shouldRenderDialog: visible,
67
-                dialogVisible: visible,
68
-            };
69
-        } else {
70
-            this.state = {
71
-                shouldRenderDialog: false,
72
-                dialogVisible: false,
73
-            };
74
-        }
72
+    this.windowWidth = Dimensions.get('window').width;
73
+    this.windowHeight = Dimensions.get('window').height;
75 74
 
76
-    }
75
+    this.mascotSize = Dimensions.get('window').height / 6;
77 76
 
78
-    onAnimationEnd = () => {
79
-        this.setState({
80
-            shouldRenderDialog: false,
81
-        })
77
+    if (props.visible != null) {
78
+      this.state = {
79
+        shouldRenderDialog: props.visible,
80
+        dialogVisible: props.visible,
81
+      };
82
+    } else if (props.prefKey != null) {
83
+      const visible = AsyncStorageManager.getBool(props.prefKey);
84
+      this.state = {
85
+        shouldRenderDialog: visible,
86
+        dialogVisible: visible,
87
+      };
88
+    } else {
89
+      this.state = {
90
+        shouldRenderDialog: false,
91
+        dialogVisible: false,
92
+      };
82 93
     }
94
+  }
83 95
 
84
-    shouldComponentUpdate(nextProps: Props, nextState: State): boolean {
85
-        if (nextProps.visible) {
86
-            this.state.shouldRenderDialog = true;
87
-            this.state.dialogVisible = true;
88
-        } else if (nextProps.visible !== this.props.visible
89
-            || (!nextState.dialogVisible && nextState.dialogVisible !== this.state.dialogVisible)) {
90
-            this.state.dialogVisible = false;
91
-            setTimeout(this.onAnimationEnd, 300);
92
-        }
93
-        return true;
94
-    }
96
+  componentDidMount(): * {
97
+    BackHandler.addEventListener(
98
+      'hardwareBackPress',
99
+      this.onBackButtonPressAndroid,
100
+    );
101
+  }
95 102
 
96
-    componentDidMount(): * {
97
-        BackHandler.addEventListener(
98
-            'hardwareBackPress',
99
-            this.onBackButtonPressAndroid
100
-        )
103
+  shouldComponentUpdate(nextProps: PropsType, nextState: StateType): boolean {
104
+    const {props, state} = this;
105
+    if (nextProps.visible) {
106
+      this.state.shouldRenderDialog = true;
107
+      this.state.dialogVisible = true;
108
+    } else if (
109
+      nextProps.visible !== props.visible ||
110
+      (!nextState.dialogVisible &&
111
+        nextState.dialogVisible !== state.dialogVisible)
112
+    ) {
113
+      this.state.dialogVisible = false;
114
+      setTimeout(this.onAnimationEnd, 300);
101 115
     }
116
+    return true;
117
+  }
102 118
 
103
-    onBackButtonPressAndroid = () => {
104
-        if (this.state.dialogVisible) {
105
-            const cancel = this.props.buttons.cancel;
106
-            const action = this.props.buttons.action;
107
-            if (cancel != null)
108
-                this.onDismiss(cancel.onPress);
109
-            else
110
-                this.onDismiss(action.onPress);
111
-            return true;
112
-        } else {
113
-            return false;
114
-        }
115
-    };
119
+  onAnimationEnd = () => {
120
+    this.setState({
121
+      shouldRenderDialog: false,
122
+    });
123
+  };
116 124
 
117
-    getSpeechBubble() {
118
-        return (
119
-            <Animatable.View
120
-                style={{
121
-                    marginLeft: "10%",
122
-                    marginRight: "10%",
123
-                }}
124
-                useNativeDriver={true}
125
-                animation={this.state.dialogVisible ? "bounceInLeft" : "bounceOutLeft"}
126
-                duration={this.state.dialogVisible ? 1000 : 300}
127
-            >
128
-                <SpeechArrow
129
-                    style={{marginLeft: this.mascotSize / 3}}
130
-                    size={20}
131
-                    color={this.props.theme.colors.mascotMessageArrow}
132
-                />
133
-                <Card style={{
134
-                    borderColor: this.props.theme.colors.mascotMessageArrow,
135
-                    borderWidth: 4,
136
-                    borderRadius: 10,
137
-                }}>
138
-                    <Card.Title
139
-                        title={this.props.title}
140
-                        left={this.props.icon != null ?
141
-                            (props) => <Avatar.Icon
142
-                                {...props}
143
-                                size={48}
144
-                                style={{backgroundColor: "transparent"}}
145
-                                color={this.props.theme.colors.primary}
146
-                                icon={this.props.icon}
147
-                            />
125
+  onBackButtonPressAndroid = (): boolean => {
126
+    const {state, props} = this;
127
+    if (state.dialogVisible) {
128
+      const {cancel} = props.buttons;
129
+      const {action} = props.buttons;
130
+      if (cancel != null) this.onDismiss(cancel.onPress);
131
+      else this.onDismiss(action.onPress);
132
+      return true;
133
+    }
134
+    return false;
135
+  };
148 136
 
149
-                            : null}
137
+  getSpeechBubble(): React.Node {
138
+    const {state, props} = this;
139
+    return (
140
+      <Animatable.View
141
+        style={{
142
+          marginLeft: '10%',
143
+          marginRight: '10%',
144
+        }}
145
+        useNativeDriver
146
+        animation={state.dialogVisible ? 'bounceInLeft' : 'bounceOutLeft'}
147
+        duration={state.dialogVisible ? 1000 : 300}>
148
+        <SpeechArrow
149
+          style={{marginLeft: this.mascotSize / 3}}
150
+          size={20}
151
+          color={props.theme.colors.mascotMessageArrow}
152
+        />
153
+        <Card
154
+          style={{
155
+            borderColor: props.theme.colors.mascotMessageArrow,
156
+            borderWidth: 4,
157
+            borderRadius: 10,
158
+          }}>
159
+          <Card.Title
160
+            title={props.title}
161
+            left={
162
+              props.icon != null
163
+                ? (): React.Node => (
164
+                    <Avatar.Icon
165
+                      size={48}
166
+                      style={{backgroundColor: 'transparent'}}
167
+                      color={props.theme.colors.primary}
168
+                      icon={props.icon}
150 169
                     />
170
+                  )
171
+                : null
172
+            }
173
+          />
174
+          <Card.Content
175
+            style={{
176
+              maxHeight: this.windowHeight / 3,
177
+            }}>
178
+            <ScrollView>
179
+              <Paragraph style={{marginBottom: 10}}>{props.message}</Paragraph>
180
+            </ScrollView>
181
+          </Card.Content>
151 182
 
152
-                    <Card.Content style={{
153
-                        maxHeight: this.windowHeight / 3
154
-                    }}>
155
-                        <ScrollView>
156
-                            <Paragraph style={{marginBottom: 10}}>
157
-                                {this.props.message}
158
-                            </Paragraph>
159
-                        </ScrollView>
160
-                    </Card.Content>
183
+          <Card.Actions style={{marginTop: 10, marginBottom: 10}}>
184
+            {this.getButtons()}
185
+          </Card.Actions>
186
+        </Card>
187
+      </Animatable.View>
188
+    );
189
+  }
161 190
 
162
-                    <Card.Actions style={{marginTop: 10, marginBottom: 10}}>
163
-                        {this.getButtons()}
164
-                    </Card.Actions>
165
-                </Card>
166
-            </Animatable.View>
167
-        );
168
-    }
169
-
170
-    getMascot() {
171
-        return (
172
-            <Animatable.View
173
-                useNativeDriver={true}
174
-                animation={this.state.dialogVisible ? "bounceInLeft" : "bounceOutLeft"}
175
-                duration={this.state.dialogVisible ? 1500 : 200}
176
-            >
177
-                <Mascot
178
-                    style={{width: this.mascotSize}}
179
-                    animated={true}
180
-                    emotion={this.props.emotion}
181
-                />
182
-            </Animatable.View>
183
-        );
184
-    }
191
+  getMascot(): React.Node {
192
+    const {props, state} = this;
193
+    return (
194
+      <Animatable.View
195
+        useNativeDriver
196
+        animation={state.dialogVisible ? 'bounceInLeft' : 'bounceOutLeft'}
197
+        duration={state.dialogVisible ? 1500 : 200}>
198
+        <Mascot
199
+          style={{width: this.mascotSize}}
200
+          animated
201
+          emotion={props.emotion}
202
+        />
203
+      </Animatable.View>
204
+    );
205
+  }
185 206
 
186
-    getButtons() {
187
-        const action = this.props.buttons.action;
188
-        const cancel = this.props.buttons.cancel;
189
-        return (
190
-            <View style={{
191
-                marginLeft: "auto",
192
-                marginRight: "auto",
193
-                marginTop: "auto",
194
-                marginBottom: "auto",
207
+  getButtons(): React.Node {
208
+    const {props} = this;
209
+    const {action} = props.buttons;
210
+    const {cancel} = props.buttons;
211
+    return (
212
+      <View
213
+        style={{
214
+          marginLeft: 'auto',
215
+          marginRight: 'auto',
216
+          marginTop: 'auto',
217
+          marginBottom: 'auto',
218
+        }}>
219
+        {action != null ? (
220
+          <Button
221
+            style={{
222
+              marginLeft: 'auto',
223
+              marginRight: 'auto',
224
+              marginBottom: 10,
225
+            }}
226
+            mode="contained"
227
+            icon={action.icon}
228
+            color={action.color}
229
+            onPress={() => {
230
+              this.onDismiss(action.onPress);
195 231
             }}>
196
-                {action != null
197
-                    ? <Button
198
-                        style={{
199
-                            marginLeft: 'auto',
200
-                            marginRight: 'auto',
201
-                            marginBottom: 10,
202
-                        }}
203
-                        mode={"contained"}
204
-                        icon={action.icon}
205
-                        color={action.color}
206
-                        onPress={() => this.onDismiss(action.onPress)}
207
-                    >
208
-                        {action.message}
209
-                    </Button>
210
-                    : null}
211
-                {cancel != null
212
-                    ? <Button
213
-                        style={{
214
-                            marginLeft: 'auto',
215
-                            marginRight: 'auto',
216
-                        }}
217
-                        mode={"contained"}
218
-                        icon={cancel.icon}
219
-                        color={cancel.color}
220
-                        onPress={() => this.onDismiss(cancel.onPress)}
221
-                    >
222
-                        {cancel.message}
223
-                    </Button>
224
-                    : null}
225
-            </View>
226
-        );
227
-    }
228
-
229
-    getBackground() {
230
-        return (
231
-            <TouchableWithoutFeedback onPress={() => this.onDismiss(this.props.buttons.cancel.onPress)}>
232
-                <Animatable.View
233
-                    style={{
234
-                        position: "absolute",
235
-                        backgroundColor: "rgba(0,0,0,0.7)",
236
-                        width: "100%",
237
-                        height: "100%",
238
-                    }}
239
-                    useNativeDriver={true}
240
-                    animation={this.state.dialogVisible ? "fadeIn" : "fadeOut"}
241
-                    duration={this.state.dialogVisible ? 300 : 300}
242
-                />
243
-            </TouchableWithoutFeedback>
232
+            {action.message}
233
+          </Button>
234
+        ) : null}
235
+        {cancel != null ? (
236
+          <Button
237
+            style={{
238
+              marginLeft: 'auto',
239
+              marginRight: 'auto',
240
+            }}
241
+            mode="contained"
242
+            icon={cancel.icon}
243
+            color={cancel.color}
244
+            onPress={() => {
245
+              this.onDismiss(cancel.onPress);
246
+            }}>
247
+            {cancel.message}
248
+          </Button>
249
+        ) : null}
250
+      </View>
251
+    );
252
+  }
244 253
 
245
-        );
246
-    }
254
+  getBackground(): React.Node {
255
+    const {props, state} = this;
256
+    return (
257
+      <TouchableWithoutFeedback
258
+        onPress={() => {
259
+          this.onDismiss(props.buttons.cancel.onPress);
260
+        }}>
261
+        <Animatable.View
262
+          style={{
263
+            position: 'absolute',
264
+            backgroundColor: 'rgba(0,0,0,0.7)',
265
+            width: '100%',
266
+            height: '100%',
267
+          }}
268
+          useNativeDriver
269
+          animation={state.dialogVisible ? 'fadeIn' : 'fadeOut'}
270
+          duration={state.dialogVisible ? 300 : 300}
271
+        />
272
+      </TouchableWithoutFeedback>
273
+    );
274
+  }
247 275
 
248
-    onDismiss = (callback?: ()=> void) => {
249
-        if (this.props.prefKey != null) {
250
-            AsyncStorageManager.set(this.props.prefKey, false);
251
-            this.setState({dialogVisible: false});
252
-        }
253
-        if (callback != null)
254
-            callback();
276
+  onDismiss = (callback?: () => void) => {
277
+    const {prefKey} = this.props;
278
+    if (prefKey != null) {
279
+      AsyncStorageManager.set(prefKey, false);
280
+      this.setState({dialogVisible: false});
255 281
     }
282
+    if (callback != null) callback();
283
+  };
256 284
 
257
-    render() {
258
-        if (this.state.shouldRenderDialog) {
259
-            return (
260
-                <Portal>
261
-                    {this.getBackground()}
262
-                    <View style={{
263
-                        marginTop: "auto",
264
-                        marginBottom: "auto",
265
-                    }}>
266
-                        <View style={{
267
-                            marginTop: -80,
268
-                            width: "100%"
269
-                        }}>
270
-                            {this.getMascot()}
271
-                            {this.getSpeechBubble()}
272
-                        </View>
273
-
274
-                    </View>
275
-                </Portal>
276
-            );
277
-        } else
278
-            return null;
279
-
285
+  render(): React.Node {
286
+    const {shouldRenderDialog} = this.state;
287
+    if (shouldRenderDialog) {
288
+      return (
289
+        <Portal>
290
+          {this.getBackground()}
291
+          <View
292
+            style={{
293
+              marginTop: 'auto',
294
+              marginBottom: 'auto',
295
+            }}>
296
+            <View
297
+              style={{
298
+                marginTop: -80,
299
+                width: '100%',
300
+              }}>
301
+              {this.getMascot()}
302
+              {this.getSpeechBubble()}
303
+            </View>
304
+          </View>
305
+        </Portal>
306
+      );
280 307
     }
308
+    return null;
309
+  }
281 310
 }
282 311
 
283 312
 export default withTheme(MascotPopup);

+ 36
- 26
src/components/Mascot/SpeechArrow.js View File

@@ -1,33 +1,43 @@
1 1
 // @flow
2 2
 
3 3
 import * as React from 'react';
4
-import {View} from "react-native";
5
-import type {ViewStyle} from "react-native/Libraries/StyleSheet/StyleSheet";
4
+import {View} from 'react-native';
5
+import type {ViewStyle} from 'react-native/Libraries/StyleSheet/StyleSheet';
6 6
 
7
-type Props = {
8
-    style?: ViewStyle,
9
-    size: number,
10
-    color: string,
11
-}
7
+type PropsType = {
8
+  style?: ViewStyle | null,
9
+  size: number,
10
+  color: string,
11
+};
12
+
13
+export default class SpeechArrow extends React.Component<PropsType> {
14
+  static defaultProps = {
15
+    style: null,
16
+  };
12 17
 
13
-export default class SpeechArrow extends React.Component<Props> {
18
+  shouldComponentUpdate(): boolean {
19
+    return false;
20
+  }
14 21
 
15
-    render() {
16
-        return (
17
-            <View style={this.props.style}>
18
-                <View style={{
19
-                    width: 0,
20
-                    height: 0,
21
-                    borderLeftWidth: 0,
22
-                    borderRightWidth: this.props.size,
23
-                    borderBottomWidth: this.props.size,
24
-                    borderStyle: 'solid',
25
-                    backgroundColor: 'transparent',
26
-                    borderLeftColor: 'transparent',
27
-                    borderRightColor: 'transparent',
28
-                    borderBottomColor: this.props.color,
29
-                }}/>
30
-            </View>
31
-        );
32
-    }
22
+  render(): React.Node {
23
+    const {props} = this;
24
+    return (
25
+      <View style={props.style}>
26
+        <View
27
+          style={{
28
+            width: 0,
29
+            height: 0,
30
+            borderLeftWidth: 0,
31
+            borderRightWidth: props.size,
32
+            borderBottomWidth: props.size,
33
+            borderStyle: 'solid',
34
+            backgroundColor: 'transparent',
35
+            borderLeftColor: 'transparent',
36
+            borderRightColor: 'transparent',
37
+            borderBottomColor: props.color,
38
+          }}
39
+        />
40
+      </View>
41
+    );
42
+  }
33 43
 }

Loading…
Cancel
Save