Browse Source

Added a new mascot dialog to replace banners

Arnaud Vergnet 1 year ago
parent
commit
761132732b

BIN
assets/mascot/mascot.png View File


BIN
assets/mascot/mascot_eyes_cute.png View File


BIN
assets/mascot/mascot_eyes_girly.png View File


BIN
assets/mascot/mascot_eyes_normal.png View File


BIN
assets/mascot/mascot_eyes_wink.png View File


BIN
assets/mascot/mascot_glasses.png View File


+ 4
- 50
src/components/Home/ActionsDashboardItem.js View File

@@ -1,61 +1,33 @@
1 1
 // @flow
2 2
 
3 3
 import * as React from 'react';
4
-import {Avatar, Card, List, withTheme} from 'react-native-paper';
5
-import {StyleSheet, View} from "react-native";
4
+import {List, withTheme} from 'react-native-paper';
5
+import {View} from "react-native";
6 6
 import type {CustomTheme} from "../../managers/ThemeManager";
7 7
 import i18n from 'i18n-js';
8 8
 import {StackNavigationProp} from "@react-navigation/stack";
9 9
 
10
-const ICON_AMICALE = require("../../../assets/amicale.png");
11
-
12 10
 type Props = {
13 11
     navigation: StackNavigationProp,
14 12
     theme: CustomTheme,
15
-    isLoggedIn: boolean,
16 13
 }
17 14
 
18 15
 class ActionsDashBoardItem extends React.Component<Props> {
19 16
 
20 17
     shouldComponentUpdate(nextProps: Props): boolean {
21
-        return (nextProps.theme.dark !== this.props.theme.dark)
22
-        || (nextProps.isLoggedIn !== this.props.isLoggedIn);
18
+        return (nextProps.theme.dark !== this.props.theme.dark);
23 19
     }
24 20
 
25 21
     render() {
26
-        const isLoggedIn = this.props.isLoggedIn;
27 22
         return (
28 23
             <View>
29
-                <Card style={{
30
-                    ...styles.card,
31
-                    borderColor: this.props.theme.colors.primary,
32
-                }}>
33
-                    <List.Item
34
-                        title={i18n.t("homeScreen.dashboard.amicaleTitle")}
35
-                        description={isLoggedIn
36
-                            ? i18n.t("homeScreen.dashboard.amicaleConnected")
37
-                            : i18n.t("homeScreen.dashboard.amicaleConnect")}
38
-                        left={props => <Avatar.Image
39
-                            {...props}
40
-                            size={40}
41
-                            source={ICON_AMICALE}
42
-                            style={styles.avatar}/>}
43
-                        right={props => <List.Icon {...props} icon={isLoggedIn
44
-                            ? "chevron-right"
45
-                            : "login"}/>}
46
-                        onPress={isLoggedIn
47
-                            ? () => this.props.navigation.navigate("profile")
48
-                            : () => this.props.navigation.navigate("login", {nextScreen: "profile"})}
49
-                        style={styles.list}
50
-                    />
51
-                </Card>
52 24
                 <List.Item
53 25
                     title={i18n.t("feedbackScreen.homeButtonTitle")}
54 26
                     description={i18n.t("feedbackScreen.homeButtonSubtitle")}
55 27
                     left={props => <List.Icon {...props} icon={"bug"}/>}
56 28
                     right={props => <List.Icon {...props} icon={"chevron-right"}/>}
57 29
                     onPress={() => this.props.navigation.navigate("feedback")}
58
-                    style={{...styles.list, marginLeft: 10, marginRight: 10}}
30
+                    style={{paddingTop: 0, paddingBottom: 0, marginLeft: 10, marginRight: 10}}
59 31
                 />
60 32
             </View>
61 33
 
@@ -63,22 +35,4 @@ class ActionsDashBoardItem extends React.Component<Props> {
63 35
     }
64 36
 }
65 37
 
66
-const styles = StyleSheet.create({
67
-    card: {
68
-        width: 'auto',
69
-        margin: 10,
70
-        borderWidth: 1,
71
-    },
72
-    avatar: {
73
-        backgroundColor: 'transparent',
74
-        marginTop: 'auto',
75
-        marginBottom: 'auto',
76
-    },
77
-    list: {
78
-        // height: 50,
79
-        paddingTop: 0,
80
-        paddingBottom: 0,
81
-    }
82
-});
83
-
84 38
 export default withTheme(ActionsDashBoardItem);

+ 148
- 0
src/components/Mascot/Mascot.js View File

@@ -0,0 +1,148 @@
1
+// @flow
2
+
3
+import * as React from 'react';
4
+import * as Animatable from "react-native-animatable";
5
+import {Image, View} from "react-native-animatable";
6
+
7
+type Props = {
8
+    size: number,
9
+    emotion: number,
10
+    animated: boolean,
11
+}
12
+
13
+const MASCOT_IMAGE = require("../../../assets/mascot/mascot.png");
14
+const MASCOT_EYES_NORMAL = require("../../../assets/mascot/mascot_eyes_normal.png");
15
+const MASCOT_EYES_GIRLY = require("../../../assets/mascot/mascot_eyes_girly.png");
16
+const MASCOT_EYES_CUTE = require("../../../assets/mascot/mascot_eyes_cute.png");
17
+const MASCOT_EYES_WINK = require("../../../assets/mascot/mascot_eyes_wink.png");
18
+const MASCOT_GLASSES = require("../../../assets/mascot/mascot_glasses.png");
19
+
20
+export const EYE_STYLE = {
21
+    NORMAL: 0,
22
+    GIRLY: 2,
23
+    CUTE: 3,
24
+    WINK: 4,
25
+}
26
+
27
+export const MASCOT_STYLE = {
28
+    NORMAL: 0,
29
+    HAPPY: 1,
30
+    GIRLY: 2,
31
+    WINK: 3,
32
+    CUTE: 4,
33
+    INTELLO: 5,
34
+};
35
+
36
+
37
+class Mascot extends React.Component<Props> {
38
+
39
+    static defaultProps = {
40
+        animated: false
41
+    }
42
+
43
+    eyeList: { [key: number]: number | string }
44
+
45
+    constructor(props: Props) {
46
+        super(props);
47
+        this.eyeList = {};
48
+        this.eyeList[EYE_STYLE.NORMAL] = MASCOT_EYES_NORMAL;
49
+        this.eyeList[EYE_STYLE.GIRLY] = MASCOT_EYES_GIRLY;
50
+        this.eyeList[EYE_STYLE.CUTE] = MASCOT_EYES_CUTE;
51
+        this.eyeList[EYE_STYLE.WINK] = MASCOT_EYES_WINK;
52
+    }
53
+
54
+    getGlasses() {
55
+        return <Image
56
+            key={"glasses"}
57
+            source={MASCOT_GLASSES}
58
+            style={{
59
+                position: "absolute",
60
+                top: "15%",
61
+                left: 0,
62
+                width: this.props.size,
63
+                height: this.props.size,
64
+            }}
65
+        />
66
+    }
67
+
68
+    getEye(style: number, isRight: boolean) {
69
+        const eye = this.eyeList[style];
70
+        return <Image
71
+            key={isRight ? "right" : "left"}
72
+            source={eye != null ? eye : this.eyeList[EYE_STYLE.NORMAL]}
73
+            style={{
74
+                position: "absolute",
75
+                top: "15%",
76
+                left: isRight ? "-11%" : "11%",
77
+                width: this.props.size,
78
+                height: this.props.size,
79
+            }}
80
+        />
81
+    }
82
+
83
+    getEyes(emotion: number) {
84
+        let final = [];
85
+        final.push(<View
86
+            key={"container"}
87
+            style={{
88
+            position: "absolute",
89
+            width: this.props.size,
90
+            height: this.props.size,
91
+        }}/>);
92
+        if (emotion === MASCOT_STYLE.CUTE) {
93
+            final.push(this.getEye(EYE_STYLE.CUTE, true));
94
+            final.push(this.getEye(EYE_STYLE.CUTE, false));
95
+        } else if (emotion === MASCOT_STYLE.GIRLY) {
96
+            final.push(this.getEye(EYE_STYLE.GIRLY, true));
97
+            final.push(this.getEye(EYE_STYLE.GIRLY, false));
98
+        } else if (emotion === MASCOT_STYLE.HAPPY) {
99
+            final.push(this.getEye(EYE_STYLE.WINK, true));
100
+            final.push(this.getEye(EYE_STYLE.WINK, false));
101
+        } else if (emotion === MASCOT_STYLE.WINK) {
102
+            final.push(this.getEye(EYE_STYLE.WINK, true));
103
+            final.push(this.getEye(EYE_STYLE.NORMAL, false));
104
+        } else {
105
+            final.push(this.getEye(EYE_STYLE.NORMAL, true));
106
+            final.push(this.getEye(EYE_STYLE.NORMAL, false));
107
+        }
108
+
109
+        if (emotion === MASCOT_STYLE.INTELLO) {
110
+            final.push(this.getGlasses())
111
+        }
112
+        final.push(<View key={"container2"}/>);
113
+        return final;
114
+    }
115
+
116
+    render() {
117
+        const size = this.props.size;
118
+        return (
119
+            <Animatable.View
120
+                style={{
121
+                    width: size,
122
+                    height: size,
123
+                }}
124
+                useNativeDriver={true}
125
+                animation={this.props.animated ? "rubberBand" : null}
126
+                duration={2000}
127
+            >
128
+                <View
129
+                    useNativeDriver={true}
130
+                    animation={this.props.animated ? "swing" : null}
131
+                    duration={2000}
132
+                    iterationCount={"infinite"}
133
+                >
134
+                    <Image
135
+                        source={MASCOT_IMAGE}
136
+                        style={{
137
+                            width: size,
138
+                            height: size,
139
+                        }}
140
+                    />
141
+                    {this.getEyes(this.props.emotion)}
142
+                </View>
143
+            </Animatable.View>
144
+        );
145
+    }
146
+}
147
+
148
+export default Mascot;

+ 225
- 0
src/components/Mascot/MascotPopup.js View File

@@ -0,0 +1,225 @@
1
+// @flow
2
+
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 {Dimensions, ScrollView, TouchableWithoutFeedback, View} from "react-native";
8
+import type {CustomTheme} from "../../managers/ThemeManager";
9
+
10
+type Props = {
11
+    visible: boolean,
12
+    theme: CustomTheme,
13
+    icon: string,
14
+    title: string,
15
+    message: string,
16
+    buttons: {
17
+        action: {
18
+            message: string,
19
+            icon: string | null,
20
+            color: string | null,
21
+            onPress: () => void,
22
+        },
23
+        cancel: {
24
+            message: string,
25
+            icon: string | null,
26
+            color: string | null,
27
+            onPress: () => void,
28
+        }
29
+    },
30
+    emotion: number,
31
+}
32
+
33
+type State = {
34
+    shouldShowDialog: boolean;
35
+}
36
+
37
+
38
+class MascotPopup extends React.Component<Props, State> {
39
+
40
+    mascotSize: number;
41
+    windowWidth: number;
42
+    windowHeight: number;
43
+
44
+    state = {
45
+        shouldShowDialog: this.props.visible,
46
+    };
47
+
48
+
49
+    constructor(props: Props) {
50
+        super(props);
51
+
52
+        this.windowWidth = Dimensions.get('window').width;
53
+        this.windowHeight = Dimensions.get('window').height;
54
+
55
+        this.mascotSize = Dimensions.get('window').height / 6;
56
+    }
57
+
58
+    onAnimationEnd = () => {
59
+        this.setState({
60
+            shouldShowDialog: this.props.visible,
61
+        })
62
+    }
63
+
64
+    shouldComponentUpdate(nextProps: Props): boolean {
65
+        if (nextProps.visible)
66
+            this.state.shouldShowDialog = true;
67
+        else if (nextProps.visible !== this.props.visible)
68
+            setTimeout(this.onAnimationEnd, 300);
69
+        return true;
70
+    }
71
+
72
+    getSpeechBubble() {
73
+        return (
74
+            <Animatable.View
75
+                style={{
76
+                    marginLeft: "10%",
77
+                    marginRight: "10%",
78
+                }}
79
+                useNativeDriver={true}
80
+                animation={this.props.visible ? "bounceInLeft" : "bounceOutLeft"}
81
+                duration={this.props.visible ? 1000 : 300}
82
+            >
83
+                <View style={{
84
+                    marginLeft: this.mascotSize / 3,
85
+                    width: 0,
86
+                    height: 0,
87
+                    borderLeftWidth: 0,
88
+                    borderRightWidth: 20,
89
+                    borderBottomWidth: 20,
90
+                    borderStyle: 'solid',
91
+                    backgroundColor: 'transparent',
92
+                    borderLeftColor: 'transparent',
93
+                    borderRightColor: 'transparent',
94
+                    borderBottomColor: this.props.theme.colors.mascotMessageArrow,
95
+                }}/>
96
+                <Card style={{
97
+                    borderColor: this.props.theme.colors.mascotMessageArrow,
98
+                    borderWidth: 4,
99
+                    borderRadius: 10,
100
+                }}>
101
+                    <Card.Title
102
+                        title={this.props.title}
103
+                        left={this.props.icon != null ?
104
+                            (props) => <Avatar.Icon
105
+                                {...props}
106
+                                style={{backgroundColor: "transparent"}}
107
+                                color={this.props.theme.colors.primary}
108
+                                icon={this.props.icon}
109
+                            />
110
+
111
+                            : null}
112
+                    />
113
+
114
+                    <Card.Content style={{
115
+                        maxHeight: this.windowHeight / 3
116
+                    }}>
117
+                        <ScrollView>
118
+                            <Paragraph style={{marginBottom: 10}}>
119
+                                {this.props.message}
120
+                            </Paragraph>
121
+                        </ScrollView>
122
+                    </Card.Content>
123
+
124
+                    <Card.Actions>
125
+                        {this.getButtons()}
126
+                    </Card.Actions>
127
+                </Card>
128
+            </Animatable.View>
129
+        );
130
+    }
131
+
132
+    getMascot() {
133
+        return (
134
+            <Animatable.View
135
+                useNativeDriver={true}
136
+                animation={this.props.visible ? "bounceInLeft" : "bounceOutLeft"}
137
+                duration={this.props.visible ? 1500 : 200}
138
+            >
139
+                <Mascot
140
+                    size={this.mascotSize}
141
+                    animated={true}
142
+                    emotion={this.props.emotion}
143
+                />
144
+            </Animatable.View>
145
+        );
146
+    }
147
+
148
+    getButtons() {
149
+        const action = this.props.buttons.action;
150
+        const cancel = this.props.buttons.cancel;
151
+        return (
152
+            <View style={{
153
+                flexDirection: "row",
154
+                marginLeft: "auto",
155
+                marginRight: "auto",
156
+                marginTop: "auto",
157
+                marginBottom: "auto",
158
+            }}>
159
+                {cancel != null
160
+                    ? <Button
161
+                        mode={"contained"}
162
+                        icon={cancel.icon}
163
+                        color={cancel.color}
164
+                        onPress={cancel.onPress}
165
+                    >
166
+                        {cancel.message}
167
+                    </Button>
168
+                    : null}
169
+                {action != null
170
+                    ? <Button
171
+                        style={{
172
+                            marginLeft: 20,
173
+                        }}
174
+                        mode={"contained"}
175
+                        icon={action.icon}
176
+                        color={action.color}
177
+                        onPress={action.onPress}
178
+                    >
179
+                        {action.message}
180
+                    </Button>
181
+                    : null}
182
+            </View>
183
+        );
184
+    }
185
+
186
+    getBackground() {
187
+        return (
188
+            <TouchableWithoutFeedback onPress={this.props.buttons.cancel.onPress}>
189
+                <Animatable.View
190
+                    style={{
191
+                        position: "absolute",
192
+                        backgroundColor: "rgba(0,0,0,0.7)",
193
+                        width: "100%",
194
+                        height: "100%",
195
+                    }}
196
+                    useNativeDriver={true}
197
+                    animation={this.props.visible ? "fadeIn" : "fadeOut"}
198
+                    duration={this.props.visible ? 300 : 300}
199
+                />
200
+            </TouchableWithoutFeedback>
201
+
202
+        );
203
+    }
204
+
205
+    render() {
206
+        if (this.state.shouldShowDialog) {
207
+            return (
208
+                <Portal>
209
+                    {this.getBackground()}
210
+                    <View style={{
211
+                        marginTop: "auto",
212
+                        marginBottom: "auto",
213
+                    }}>
214
+                        {this.getMascot()}
215
+                        {this.getSpeechBubble()}
216
+                    </View>
217
+                </Portal>
218
+            );
219
+        } else
220
+            return null;
221
+
222
+    }
223
+}
224
+
225
+export default withTheme(MascotPopup);

+ 11
- 2
src/managers/ThemeManager.js View File

@@ -55,6 +55,9 @@ export type CustomTheme = {
55 55
         tetrisZ: string,
56 56
         tetrisJ: string,
57 57
         tetrisL: string,
58
+
59
+        // Mascot Popup
60
+        mascotMessageArrow: string,
58 61
     },
59 62
 }
60 63
 
@@ -83,7 +86,7 @@ export default class ThemeManager {
83 86
                 primary: '#be1522',
84 87
                 accent: '#be1522',
85 88
                 tabIcon: "#929292",
86
-                card: "rgb(255, 255, 255)",
89
+                card: "#fff",
87 90
                 dividerBackground: '#e2e2e2',
88 91
                 ripple: "rgba(0,0,0,0.2)",
89 92
                 textDisabled: '#c1c1c1',
@@ -126,6 +129,9 @@ export default class ThemeManager {
126 129
                 tetrisZ: '#ff0009',
127 130
                 tetrisJ: '#2a67e3',
128 131
                 tetrisL: '#da742d',
132
+
133
+                // Mascot Popup
134
+                mascotMessageArrow: "#dedede",
129 135
             },
130 136
         };
131 137
     }
@@ -144,7 +150,7 @@ export default class ThemeManager {
144 150
                 accent: '#be1522',
145 151
                 tabBackground: "#181818",
146 152
                 tabIcon: "#6d6d6d",
147
-                card: "rgb(18, 18, 18)",
153
+                card: "rgb(18,18,18)",
148 154
                 dividerBackground: '#222222',
149 155
                 ripple: "rgba(255,255,255,0.2)",
150 156
                 textDisabled: '#5b5b5b',
@@ -186,6 +192,9 @@ export default class ThemeManager {
186 192
                 tetrisZ: '#b50008',
187 193
                 tetrisJ: '#0f37b9',
188 194
                 tetrisL: '#b96226',
195
+
196
+                // Mascot Popup
197
+                mascotMessageArrow: "#323232",
189 198
             },
190 199
         };
191 200
     }

+ 44
- 60
src/screens/Home/HomeScreen.js View File

@@ -5,7 +5,7 @@ import {FlatList} from 'react-native';
5 5
 import i18n from "i18n-js";
6 6
 import DashboardItem from "../../components/Home/EventDashboardItem";
7 7
 import WebSectionList from "../../components/Screens/WebSectionList";
8
-import {Avatar, Banner, withTheme} from 'react-native-paper';
8
+import {withTheme} from 'react-native-paper';
9 9
 import FeedItem from "../../components/Home/FeedItem";
10 10
 import SquareDashboardItem from "../../components/Home/SmallDashboardItem";
11 11
 import PreviewEventDashboardItem from "../../components/Home/PreviewEventDashboardItem";
@@ -19,10 +19,10 @@ import type {CustomTheme} from "../../managers/ThemeManager";
19 19
 import {View} from "react-native-animatable";
20 20
 import ConnectionManager from "../../managers/ConnectionManager";
21 21
 import LogoutDialog from "../../components/Amicale/LogoutDialog";
22
-import {withCollapsible} from "../../utils/withCollapsible";
23
-import {Collapsible} from "react-navigation-collapsible";
24 22
 import AsyncStorageManager from "../../managers/AsyncStorageManager";
25 23
 import AvailableWebsites from "../../constants/AvailableWebsites";
24
+import {MASCOT_STYLE} from "../../components/Mascot/Mascot";
25
+import MascotPopup from "../../components/Mascot/MascotPopup";
26 26
 // import DATA from "../dashboard_data.json";
27 27
 
28 28
 
@@ -97,12 +97,11 @@ type Props = {
97 97
     navigation: StackNavigationProp,
98 98
     route: { params: any, ... },
99 99
     theme: CustomTheme,
100
-    collapsibleStack: Collapsible,
101 100
 }
102 101
 
103 102
 type State = {
104 103
     dialogVisible: boolean,
105
-    bannerVisible: boolean,
104
+    mascotDialogVisible: boolean,
106 105
 }
107 106
 
108 107
 /**
@@ -115,16 +114,19 @@ class HomeScreen extends React.Component<Props, State> {
115 114
     fabRef: { current: null | AnimatedFAB };
116 115
     currentNewFeed: Array<feedItem>;
117 116
 
118
-    state = {
119
-        dialogVisible: false,
120
-        bannerVisible: false,
121
-    }
122
-
123 117
     constructor(props) {
124 118
         super(props);
125 119
         this.fabRef = React.createRef();
126 120
         this.currentNewFeed = [];
127
-        this.isLoggedIn = null;
121
+        this.isLoggedIn = ConnectionManager.getInstance().isLoggedIn();
122
+        this.props.navigation.setOptions({
123
+            headerRight: this.getHeaderButton,
124
+        });
125
+        this.state = {
126
+            dialogVisible: false,
127
+            mascotDialogVisible: AsyncStorageManager.getInstance().preferences.homeShowBanner.current === "1"
128
+                && !this.isLoggedIn,
129
+        }
128 130
     }
129 131
 
130 132
     /**
@@ -142,13 +144,6 @@ class HomeScreen extends React.Component<Props, State> {
142 144
         this.props.navigation.addListener('focus', this.onScreenFocus);
143 145
         // Handle link open when home is focused
144 146
         this.props.navigation.addListener('state', this.handleNavigationParams);
145
-        setTimeout(this.onBannerTimeout, 2000);
146
-    }
147
-
148
-    onBannerTimeout = () => {
149
-        this.setState({
150
-            bannerVisible: AsyncStorageManager.getInstance().preferences.homeShowBanner.current === "1"
151
-        })
152 147
     }
153 148
 
154 149
     /**
@@ -161,9 +156,6 @@ class HomeScreen extends React.Component<Props, State> {
161 156
                 headerRight: this.getHeaderButton,
162 157
             });
163 158
         }
164
-        if (this.isLoggedIn) {
165
-            this.setState({bannerVisible: false})
166
-        }
167 159
         // handle link open when home is not focused or created
168 160
         this.handleNavigationParams();
169 161
     };
@@ -203,6 +195,14 @@ class HomeScreen extends React.Component<Props, State> {
203 195
         </MaterialHeaderButtons>;
204 196
     };
205 197
 
198
+    hideMascotDialog = () => {
199
+        AsyncStorageManager.getInstance().savePref(
200
+            AsyncStorageManager.getInstance().preferences.homeShowBanner.key,
201
+            '0'
202
+        );
203
+        this.setState({mascotDialogVisible: false})
204
+    };
205
+
206 206
     showDisconnectDialog = () => this.setState({dialogVisible: true});
207 207
 
208 208
     hideDisconnectDialog = () => this.setState({dialogVisible: false});
@@ -570,28 +570,15 @@ class HomeScreen extends React.Component<Props, State> {
570 570
     };
571 571
 
572 572
     /**
573
-     * Callback used when closing the banner.
574
-     * This hides the banner and saves to preferences to prevent it from reopening.
575
-     */
576
-    onHideBanner = () => {
577
-        this.setState({bannerVisible: false});
578
-        AsyncStorageManager.getInstance().savePref(
579
-            AsyncStorageManager.getInstance().preferences.homeShowBanner.key,
580
-            '0'
581
-        );
582
-    };
583
-
584
-    /**
585 573
      * Callback when pressing the login button on the banner.
586 574
      * This hides the banner and takes the user to the login page.
587 575
      */
588
-    onLoginBanner = () => {
589
-        this.onHideBanner();
576
+    onLogin = () => {
577
+        this.hideMascotDialog();
590 578
         this.props.navigation.navigate("login", {nextScreen: "profile"});
591 579
     }
592 580
 
593 581
     render() {
594
-        const {containerPaddingTop} = this.props.collapsibleStack;
595 582
         return (
596 583
             <View
597 584
                 style={{flex: 1}}
@@ -613,6 +600,26 @@ class HomeScreen extends React.Component<Props, State> {
613 600
                         showError={false}
614 601
                     />
615 602
                 </View>
603
+                <MascotPopup
604
+                    visible={this.state.mascotDialogVisible}
605
+                    title={i18n.t("homeScreen.loginBanner.title")}
606
+                    message={i18n.t("homeScreen.loginBanner.message")}
607
+                    icon={"check"}
608
+                    buttons={{
609
+                        action: {
610
+                            message: i18n.t("homeScreen.loginBanner.login"),
611
+                            icon: "login",
612
+                            onPress: this.onLogin,
613
+                        },
614
+                        cancel: {
615
+                            message: i18n.t("homeScreen.loginBanner.later"),
616
+                            icon: "close",
617
+                            color: this.props.theme.colors.warning,
618
+                            onPress: this.hideMascotDialog,
619
+                        }
620
+                    }}
621
+                    emotion={MASCOT_STYLE.CUTE}
622
+                />
616 623
                 <AnimatedFAB
617 624
                     {...this.props}
618 625
                     ref={this.fabRef}
@@ -624,32 +631,9 @@ class HomeScreen extends React.Component<Props, State> {
624 631
                     visible={this.state.dialogVisible}
625 632
                     onDismiss={this.hideDisconnectDialog}
626 633
                 />
627
-                <Banner
628
-                    style={{
629
-                        marginTop: containerPaddingTop,
630
-                        backgroundColor: this.props.theme.colors.surface
631
-                    }}
632
-                    visible={this.state.bannerVisible}
633
-                    actions={[
634
-                        {
635
-                            label: i18n.t('homeScreen.loginBanner.login'),
636
-                            onPress: this.onLoginBanner,
637
-                        },
638
-                        {
639
-                            label: i18n.t('homeScreen.loginBanner.later'),
640
-                            onPress: this.onHideBanner,
641
-                        },
642
-                    ]}
643
-                    icon={() => <Avatar.Icon
644
-                        icon={'login'}
645
-                        size={50}
646
-                    />}
647
-                >
648
-                    {i18n.t('homeScreen.loginBanner.message')}
649
-                </Banner>
650 634
             </View>
651 635
         );
652 636
     }
653 637
 }
654 638
 
655
-export default withCollapsible(withTheme(HomeScreen));
639
+export default withTheme(HomeScreen);

+ 29
- 42
src/screens/Planex/PlanexScreen.js View File

@@ -4,30 +4,29 @@ import * as React from 'react';
4 4
 import type {CustomTheme} from "../../managers/ThemeManager";
5 5
 import ThemeManager from "../../managers/ThemeManager";
6 6
 import WebViewScreen from "../../components/Screens/WebViewScreen";
7
-import {Avatar, Banner, withTheme} from "react-native-paper";
7
+import {withTheme} from "react-native-paper";
8 8
 import i18n from "i18n-js";
9 9
 import {View} from "react-native";
10 10
 import AsyncStorageManager from "../../managers/AsyncStorageManager";
11 11
 import AlertDialog from "../../components/Dialogs/AlertDialog";
12
-import {withCollapsible} from "../../utils/withCollapsible";
13 12
 import {dateToString, getTimeOnlyString} from "../../utils/Planning";
14 13
 import DateManager from "../../managers/DateManager";
15 14
 import AnimatedBottomBar from "../../components/Animations/AnimatedBottomBar";
16 15
 import {CommonActions} from "@react-navigation/native";
17 16
 import ErrorView from "../../components/Screens/ErrorView";
18 17
 import {StackNavigationProp} from "@react-navigation/stack";
19
-import {Collapsible} from "react-navigation-collapsible";
20 18
 import type {group} from "./GroupSelectionScreen";
19
+import {MASCOT_STYLE} from "../../components/Mascot/Mascot";
20
+import MascotPopup from "../../components/Mascot/MascotPopup";
21 21
 
22 22
 type Props = {
23 23
     navigation: StackNavigationProp,
24 24
     route: { params: { group: group } },
25 25
     theme: CustomTheme,
26
-    collapsibleStack: Collapsible,
27 26
 }
28 27
 
29 28
 type State = {
30
-    bannerVisible: boolean,
29
+    mascotDialogVisible: boolean,
31 30
     dialogVisible: boolean,
32 31
     dialogTitle: string,
33 32
     dialogMessage: string,
@@ -144,7 +143,9 @@ class PlanexScreen extends React.Component<Props, State> {
144 143
             props.navigation.setOptions({title: currentGroup.name})
145 144
         }
146 145
         this.state = {
147
-            bannerVisible: false,
146
+            mascotDialogVisible:
147
+                AsyncStorageManager.getInstance().preferences.planexShowBanner.current === '1' &&
148
+                AsyncStorageManager.getInstance().preferences.defaultStartScreen.current !== 'Planex',
148 149
             dialogVisible: false,
149 150
             dialogTitle: "",
150 151
             dialogMessage: "",
@@ -158,23 +159,14 @@ class PlanexScreen extends React.Component<Props, State> {
158 159
      */
159 160
     componentDidMount() {
160 161
         this.props.navigation.addListener('focus', this.onScreenFocus);
161
-        setTimeout(this.onBannerTimeout, 2000);
162
-    }
163
-
164
-    onBannerTimeout = () => {
165
-        this.setState({
166
-            bannerVisible:
167
-                AsyncStorageManager.getInstance().preferences.planexShowBanner.current === '1' &&
168
-                AsyncStorageManager.getInstance().preferences.defaultStartScreen.current !== 'Planex'
169
-        })
170 162
     }
171 163
 
172 164
     /**
173 165
      * Callback used when closing the banner.
174 166
      * This hides the banner and saves to preferences to prevent it from reopening
175 167
      */
176
-    onHideBanner = () => {
177
-        this.setState({bannerVisible: false});
168
+    onMascotDialogCancel = () => {
169
+        this.setState({mascotDialogVisible: false});
178 170
         AsyncStorageManager.getInstance().savePref(
179 171
             AsyncStorageManager.getInstance().preferences.planexShowBanner.key,
180 172
             '0'
@@ -187,7 +179,7 @@ class PlanexScreen extends React.Component<Props, State> {
187 179
      * This will hide the banner and open the SettingsScreen
188 180
      */
189 181
     onGoToSettings = () => {
190
-        this.onHideBanner();
182
+        this.onMascotDialogCancel();
191 183
         this.props.navigation.navigate('settings');
192 184
     };
193 185
 
@@ -356,7 +348,6 @@ class PlanexScreen extends React.Component<Props, State> {
356 348
     }
357 349
 
358 350
     render() {
359
-        const {containerPaddingTop} = this.props.collapsibleStack;
360 351
         return (
361 352
             <View
362 353
                 style={{flex: 1}}
@@ -371,30 +362,26 @@ class PlanexScreen extends React.Component<Props, State> {
371 362
                         ? this.getWebView()
372 363
                         : <View style={{height: '100%'}}>{this.getWebView()}</View>}
373 364
                 </View>
374
-                <Banner
375
-                    style={{
376
-                        marginTop: containerPaddingTop,
377
-                        backgroundColor: this.props.theme.colors.surface
378
-                    }}
379
-                    visible={this.state.bannerVisible}
380
-                    actions={[
381
-                        {
382
-                            label: i18n.t('planexScreen.enableStartOK'),
365
+                <MascotPopup
366
+                    visible={this.state.mascotDialogVisible}
367
+                    title={i18n.t("planexScreen.enableStartScreenTitle")}
368
+                    message={i18n.t("planexScreen.enableStartScreenMessage")}
369
+                    icon={"power"}
370
+                    buttons={{
371
+                        action: {
372
+                            message: i18n.t("planexScreen.enableStartOK"),
373
+                            icon: "settings",
383 374
                             onPress: this.onGoToSettings,
384 375
                         },
385
-                        {
386
-                            label: i18n.t('planexScreen.enableStartCancel'),
387
-                            onPress: this.onHideBanner,
388
-                        },
389
-
390
-                    ]}
391
-                    icon={() => <Avatar.Icon
392
-                        icon={'power'}
393
-                        size={40}
394
-                    />}
395
-                >
396
-                    {i18n.t('planexScreen.enableStartScreen')}
397
-                </Banner>
376
+                        cancel: {
377
+                            message: i18n.t("planexScreen.enableStartCancel"),
378
+                            icon: "close",
379
+                            color: this.props.theme.colors.warning,
380
+                            onPress: this.onMascotDialogCancel,
381
+                        }
382
+                    }}
383
+                    emotion={MASCOT_STYLE.INTELLO}
384
+                />
398 385
                 <AlertDialog
399 386
                     visible={this.state.dialogVisible}
400 387
                     onDismiss={this.hideDialog}
@@ -411,4 +398,4 @@ class PlanexScreen extends React.Component<Props, State> {
411 398
     }
412 399
 }
413 400
 
414
-export default withCollapsible(withTheme(PlanexScreen));
401
+export default withTheme(PlanexScreen);

+ 22
- 33
src/screens/Proxiwash/ProxiwashScreen.js View File

@@ -6,19 +6,19 @@ import i18n from "i18n-js";
6 6
 import WebSectionList from "../../components/Screens/WebSectionList";
7 7
 import * as Notifications from "../../utils/Notifications";
8 8
 import AsyncStorageManager from "../../managers/AsyncStorageManager";
9
-import {Avatar, Banner, Button, Card, Text, withTheme} from 'react-native-paper';
9
+import {Avatar, Button, Card, Text, withTheme} from 'react-native-paper';
10 10
 import ProxiwashListItem from "../../components/Lists/Proxiwash/ProxiwashListItem";
11 11
 import ProxiwashConstants from "../../constants/ProxiwashConstants";
12 12
 import CustomModal from "../../components/Overrides/CustomModal";
13 13
 import AprilFoolsManager from "../../managers/AprilFoolsManager";
14 14
 import MaterialHeaderButtons, {Item} from "../../components/Overrides/CustomHeaderButton";
15 15
 import ProxiwashSectionHeader from "../../components/Lists/Proxiwash/ProxiwashSectionHeader";
16
-import {withCollapsible} from "../../utils/withCollapsible";
17 16
 import type {CustomTheme} from "../../managers/ThemeManager";
18
-import {Collapsible} from "react-navigation-collapsible";
19 17
 import {StackNavigationProp} from "@react-navigation/stack";
20 18
 import {getCleanedMachineWatched, getMachineEndDate, isMachineWatched} from "../../utils/Proxiwash";
21 19
 import {Modalize} from "react-native-modalize";
20
+import {MASCOT_STYLE} from "../../components/Mascot/Mascot";
21
+import MascotPopup from "../../components/Mascot/MascotPopup";
22 22
 
23 23
 const DATA_URL = "https://etud.insa-toulouse.fr/~amicale_app/v2/washinsa/washinsa_data.json";
24 24
 
@@ -40,14 +40,13 @@ export type Machine = {
40 40
 type Props = {
41 41
     navigation: StackNavigationProp,
42 42
     theme: CustomTheme,
43
-    collapsibleStack: Collapsible,
44 43
 }
45 44
 
46 45
 type State = {
47 46
     refreshing: boolean,
48 47
     modalCurrentDisplayItem: React.Node,
49 48
     machinesWatched: Array<Machine>,
50
-    bannerVisible: boolean,
49
+    mascotDialogVisible: boolean,
51 50
 };
52 51
 
53 52
 
@@ -68,7 +67,7 @@ class ProxiwashScreen extends React.Component<Props, State> {
68 67
         refreshing: false,
69 68
         modalCurrentDisplayItem: null,
70 69
         machinesWatched: JSON.parse(AsyncStorageManager.getInstance().preferences.proxiwashWatchedMachines.current),
71
-        bannerVisible: false,
70
+        mascotDialogVisible: AsyncStorageManager.getInstance().preferences.proxiwashShowBanner.current === "1",
72 71
     };
73 72
 
74 73
     /**
@@ -89,8 +88,8 @@ class ProxiwashScreen extends React.Component<Props, State> {
89 88
      * Callback used when closing the banner.
90 89
      * This hides the banner and saves to preferences to prevent it from reopening
91 90
      */
92
-    onHideBanner = () => {
93
-        this.setState({bannerVisible: false});
91
+    onHideMascotDialog = () => {
92
+        this.setState({mascotDialogVisible: false});
94 93
         AsyncStorageManager.getInstance().savePref(
95 94
             AsyncStorageManager.getInstance().preferences.proxiwashShowBanner.key,
96 95
             '0'
@@ -107,11 +106,6 @@ class ProxiwashScreen extends React.Component<Props, State> {
107 106
                     <Item title="information" iconName="information" onPress={this.onAboutPress}/>
108 107
                 </MaterialHeaderButtons>,
109 108
         });
110
-        setTimeout(this.onBannerTimeout, 2000);
111
-    }
112
-
113
-    onBannerTimeout = () => {
114
-        this.setState({bannerVisible: AsyncStorageManager.getInstance().preferences.proxiwashShowBanner.current === "1"})
115 109
     }
116 110
 
117 111
     /**
@@ -401,7 +395,6 @@ class ProxiwashScreen extends React.Component<Props, State> {
401 395
 
402 396
     render() {
403 397
         const nav = this.props.navigation;
404
-        const {containerPaddingTop} = this.props.collapsibleStack;
405 398
         return (
406 399
             <View
407 400
                 style={{flex: 1}}
@@ -421,25 +414,21 @@ class ProxiwashScreen extends React.Component<Props, State> {
421 414
                         refreshOnFocus={true}
422 415
                         updateData={this.state.machinesWatched.length}/>
423 416
                 </View>
424
-                <Banner
425
-                    style={{
426
-                        marginTop: containerPaddingTop,
427
-                        backgroundColor: this.props.theme.colors.surface
417
+                <MascotPopup
418
+                    visible={this.state.mascotDialogVisible}
419
+                    title={i18n.t("proxiwashScreen.bannerTitle")}
420
+                    message={i18n.t("proxiwashScreen.enableNotificationsTip")}
421
+                    icon={"bell"}
422
+                    buttons={{
423
+                        action: null,
424
+                        cancel: {
425
+                            message: i18n.t("proxiwashScreen.bannerButton"),
426
+                            icon: "check",
427
+                            onPress: this.onHideMascotDialog,
428
+                        }
428 429
                     }}
429
-                    visible={this.state.bannerVisible}
430
-                    actions={[
431
-                        {
432
-                            label: i18n.t('proxiwashScreen.bannerButton'),
433
-                            onPress: this.onHideBanner,
434
-                        },
435
-                    ]}
436
-                    icon={() => <Avatar.Icon
437
-                        icon={'bell'}
438
-                        size={40}
439
-                    />}
440
-                >
441
-                    {i18n.t('proxiwashScreen.enableNotificationsTip')}
442
-                </Banner>
430
+                    emotion={MASCOT_STYLE.NORMAL}
431
+                />
443 432
                 <CustomModal onRef={this.onModalRef}>
444 433
                     {this.state.modalCurrentDisplayItem}
445 434
                 </CustomModal>
@@ -448,4 +437,4 @@ class ProxiwashScreen extends React.Component<Props, State> {
448 437
     }
449 438
 }
450 439
 
451
-export default withCollapsible(withTheme(ProxiwashScreen));
440
+export default withTheme(ProxiwashScreen);

+ 6
- 3
translations/en.json View File

@@ -116,7 +116,8 @@
116 116
     "loginBanner": {
117 117
       "login": "Login",
118 118
       "later": "Later",
119
-      "message": "Login to your Amicale account to get access to more services!"
119
+      "title": "Welcome, you!",
120
+      "message": "Login to your Amicale account to get access to more services!\n\nYou will still be able to login later."
120 121
     }
121 122
   },
122 123
   "aboutScreen": {
@@ -179,9 +180,10 @@
179 180
     "dryerTips": "The advised dryer length is 35 minutes for 14 kg of laundry. You can choose a shorter length if the dryer is not fully charged.",
180 181
     "procedure": "Procedure",
181 182
     "tips": "Tips",
182
-    "enableNotificationsTip": "Click on a running machine to enable notifications",
183
+    "enableNotificationsTip": "Click on a running machine to enable notifications!\n\nYou will never forget your laundry again.",
183 184
     "numAvailable": "available",
184 185
     "numAvailablePlural": "available",
186
+    "bannerTitle": "Notifications!",
185 187
     "bannerButton": "Got it!",
186 188
     "modal": {
187 189
       "enableNotifications": "Notify me",
@@ -215,7 +217,8 @@
215 217
     }
216 218
   },
217 219
   "planexScreen": {
218
-    "enableStartScreen": "Come here often? Set it as default screen!",
220
+    "enableStartScreenTitle": "Come here often?",
221
+    "enableStartScreenMessage": "Set it as default screen!\n\nCampus will start on Planex so you never miss a class. Click on the button bellow to navigate to the settings page.",
219 222
     "enableStartOK": "Yes please!",
220 223
     "enableStartCancel": "Later",
221 224
     "noGroupSelected": "No group selected. Please select your group using the big beautiful red button bellow.",

Loading…
Cancel
Save