forked from vergnet/application-amicale
Added a new mascot dialog to replace banners
This commit is contained in:
parent
976684dfce
commit
761132732b
14 changed files with 489 additions and 190 deletions
BIN
assets/mascot/mascot.png
Normal file
BIN
assets/mascot/mascot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
BIN
assets/mascot/mascot_eyes_cute.png
Normal file
BIN
assets/mascot/mascot_eyes_cute.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.2 KiB |
BIN
assets/mascot/mascot_eyes_girly.png
Normal file
BIN
assets/mascot/mascot_eyes_girly.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.1 KiB |
BIN
assets/mascot/mascot_eyes_normal.png
Normal file
BIN
assets/mascot/mascot_eyes_normal.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
BIN
assets/mascot/mascot_eyes_wink.png
Normal file
BIN
assets/mascot/mascot_eyes_wink.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
BIN
assets/mascot/mascot_glasses.png
Normal file
BIN
assets/mascot/mascot_glasses.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.7 KiB |
|
@ -1,61 +1,33 @@
|
|||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Avatar, Card, List, withTheme} from 'react-native-paper';
|
||||
import {StyleSheet, View} from "react-native";
|
||||
import {List, withTheme} from 'react-native-paper';
|
||||
import {View} from "react-native";
|
||||
import type {CustomTheme} from "../../managers/ThemeManager";
|
||||
import i18n from 'i18n-js';
|
||||
import {StackNavigationProp} from "@react-navigation/stack";
|
||||
|
||||
const ICON_AMICALE = require("../../../assets/amicale.png");
|
||||
|
||||
type Props = {
|
||||
navigation: StackNavigationProp,
|
||||
theme: CustomTheme,
|
||||
isLoggedIn: boolean,
|
||||
}
|
||||
|
||||
class ActionsDashBoardItem extends React.Component<Props> {
|
||||
|
||||
shouldComponentUpdate(nextProps: Props): boolean {
|
||||
return (nextProps.theme.dark !== this.props.theme.dark)
|
||||
|| (nextProps.isLoggedIn !== this.props.isLoggedIn);
|
||||
return (nextProps.theme.dark !== this.props.theme.dark);
|
||||
}
|
||||
|
||||
render() {
|
||||
const isLoggedIn = this.props.isLoggedIn;
|
||||
return (
|
||||
<View>
|
||||
<Card style={{
|
||||
...styles.card,
|
||||
borderColor: this.props.theme.colors.primary,
|
||||
}}>
|
||||
<List.Item
|
||||
title={i18n.t("homeScreen.dashboard.amicaleTitle")}
|
||||
description={isLoggedIn
|
||||
? i18n.t("homeScreen.dashboard.amicaleConnected")
|
||||
: i18n.t("homeScreen.dashboard.amicaleConnect")}
|
||||
left={props => <Avatar.Image
|
||||
{...props}
|
||||
size={40}
|
||||
source={ICON_AMICALE}
|
||||
style={styles.avatar}/>}
|
||||
right={props => <List.Icon {...props} icon={isLoggedIn
|
||||
? "chevron-right"
|
||||
: "login"}/>}
|
||||
onPress={isLoggedIn
|
||||
? () => this.props.navigation.navigate("profile")
|
||||
: () => this.props.navigation.navigate("login", {nextScreen: "profile"})}
|
||||
style={styles.list}
|
||||
/>
|
||||
</Card>
|
||||
<List.Item
|
||||
title={i18n.t("feedbackScreen.homeButtonTitle")}
|
||||
description={i18n.t("feedbackScreen.homeButtonSubtitle")}
|
||||
left={props => <List.Icon {...props} icon={"bug"}/>}
|
||||
right={props => <List.Icon {...props} icon={"chevron-right"}/>}
|
||||
onPress={() => this.props.navigation.navigate("feedback")}
|
||||
style={{...styles.list, marginLeft: 10, marginRight: 10}}
|
||||
style={{paddingTop: 0, paddingBottom: 0, marginLeft: 10, marginRight: 10}}
|
||||
/>
|
||||
</View>
|
||||
|
||||
|
@ -63,22 +35,4 @@ class ActionsDashBoardItem extends React.Component<Props> {
|
|||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
card: {
|
||||
width: 'auto',
|
||||
margin: 10,
|
||||
borderWidth: 1,
|
||||
},
|
||||
avatar: {
|
||||
backgroundColor: 'transparent',
|
||||
marginTop: 'auto',
|
||||
marginBottom: 'auto',
|
||||
},
|
||||
list: {
|
||||
// height: 50,
|
||||
paddingTop: 0,
|
||||
paddingBottom: 0,
|
||||
}
|
||||
});
|
||||
|
||||
export default withTheme(ActionsDashBoardItem);
|
||||
|
|
148
src/components/Mascot/Mascot.js
Normal file
148
src/components/Mascot/Mascot.js
Normal file
|
@ -0,0 +1,148 @@
|
|||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import * as Animatable from "react-native-animatable";
|
||||
import {Image, View} from "react-native-animatable";
|
||||
|
||||
type Props = {
|
||||
size: number,
|
||||
emotion: number,
|
||||
animated: boolean,
|
||||
}
|
||||
|
||||
const MASCOT_IMAGE = require("../../../assets/mascot/mascot.png");
|
||||
const MASCOT_EYES_NORMAL = require("../../../assets/mascot/mascot_eyes_normal.png");
|
||||
const MASCOT_EYES_GIRLY = require("../../../assets/mascot/mascot_eyes_girly.png");
|
||||
const MASCOT_EYES_CUTE = require("../../../assets/mascot/mascot_eyes_cute.png");
|
||||
const MASCOT_EYES_WINK = require("../../../assets/mascot/mascot_eyes_wink.png");
|
||||
const MASCOT_GLASSES = require("../../../assets/mascot/mascot_glasses.png");
|
||||
|
||||
export const EYE_STYLE = {
|
||||
NORMAL: 0,
|
||||
GIRLY: 2,
|
||||
CUTE: 3,
|
||||
WINK: 4,
|
||||
}
|
||||
|
||||
export const MASCOT_STYLE = {
|
||||
NORMAL: 0,
|
||||
HAPPY: 1,
|
||||
GIRLY: 2,
|
||||
WINK: 3,
|
||||
CUTE: 4,
|
||||
INTELLO: 5,
|
||||
};
|
||||
|
||||
|
||||
class Mascot extends React.Component<Props> {
|
||||
|
||||
static defaultProps = {
|
||||
animated: false
|
||||
}
|
||||
|
||||
eyeList: { [key: number]: number | string }
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.eyeList = {};
|
||||
this.eyeList[EYE_STYLE.NORMAL] = MASCOT_EYES_NORMAL;
|
||||
this.eyeList[EYE_STYLE.GIRLY] = MASCOT_EYES_GIRLY;
|
||||
this.eyeList[EYE_STYLE.CUTE] = MASCOT_EYES_CUTE;
|
||||
this.eyeList[EYE_STYLE.WINK] = MASCOT_EYES_WINK;
|
||||
}
|
||||
|
||||
getGlasses() {
|
||||
return <Image
|
||||
key={"glasses"}
|
||||
source={MASCOT_GLASSES}
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: "15%",
|
||||
left: 0,
|
||||
width: this.props.size,
|
||||
height: this.props.size,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
|
||||
getEye(style: number, isRight: boolean) {
|
||||
const eye = this.eyeList[style];
|
||||
return <Image
|
||||
key={isRight ? "right" : "left"}
|
||||
source={eye != null ? eye : this.eyeList[EYE_STYLE.NORMAL]}
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: "15%",
|
||||
left: isRight ? "-11%" : "11%",
|
||||
width: this.props.size,
|
||||
height: this.props.size,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
|
||||
getEyes(emotion: number) {
|
||||
let final = [];
|
||||
final.push(<View
|
||||
key={"container"}
|
||||
style={{
|
||||
position: "absolute",
|
||||
width: this.props.size,
|
||||
height: this.props.size,
|
||||
}}/>);
|
||||
if (emotion === MASCOT_STYLE.CUTE) {
|
||||
final.push(this.getEye(EYE_STYLE.CUTE, true));
|
||||
final.push(this.getEye(EYE_STYLE.CUTE, false));
|
||||
} else if (emotion === MASCOT_STYLE.GIRLY) {
|
||||
final.push(this.getEye(EYE_STYLE.GIRLY, true));
|
||||
final.push(this.getEye(EYE_STYLE.GIRLY, false));
|
||||
} else if (emotion === MASCOT_STYLE.HAPPY) {
|
||||
final.push(this.getEye(EYE_STYLE.WINK, true));
|
||||
final.push(this.getEye(EYE_STYLE.WINK, false));
|
||||
} else if (emotion === MASCOT_STYLE.WINK) {
|
||||
final.push(this.getEye(EYE_STYLE.WINK, true));
|
||||
final.push(this.getEye(EYE_STYLE.NORMAL, false));
|
||||
} else {
|
||||
final.push(this.getEye(EYE_STYLE.NORMAL, true));
|
||||
final.push(this.getEye(EYE_STYLE.NORMAL, false));
|
||||
}
|
||||
|
||||
if (emotion === MASCOT_STYLE.INTELLO) {
|
||||
final.push(this.getGlasses())
|
||||
}
|
||||
final.push(<View key={"container2"}/>);
|
||||
return final;
|
||||
}
|
||||
|
||||
render() {
|
||||
const size = this.props.size;
|
||||
return (
|
||||
<Animatable.View
|
||||
style={{
|
||||
width: size,
|
||||
height: size,
|
||||
}}
|
||||
useNativeDriver={true}
|
||||
animation={this.props.animated ? "rubberBand" : null}
|
||||
duration={2000}
|
||||
>
|
||||
<View
|
||||
useNativeDriver={true}
|
||||
animation={this.props.animated ? "swing" : null}
|
||||
duration={2000}
|
||||
iterationCount={"infinite"}
|
||||
>
|
||||
<Image
|
||||
source={MASCOT_IMAGE}
|
||||
style={{
|
||||
width: size,
|
||||
height: size,
|
||||
}}
|
||||
/>
|
||||
{this.getEyes(this.props.emotion)}
|
||||
</View>
|
||||
</Animatable.View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Mascot;
|
225
src/components/Mascot/MascotPopup.js
Normal file
225
src/components/Mascot/MascotPopup.js
Normal file
|
@ -0,0 +1,225 @@
|
|||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Avatar, Button, Card, Paragraph, Portal, withTheme} from 'react-native-paper';
|
||||
import Mascot from "./Mascot";
|
||||
import * as Animatable from "react-native-animatable";
|
||||
import {Dimensions, ScrollView, TouchableWithoutFeedback, View} from "react-native";
|
||||
import type {CustomTheme} from "../../managers/ThemeManager";
|
||||
|
||||
type Props = {
|
||||
visible: boolean,
|
||||
theme: CustomTheme,
|
||||
icon: string,
|
||||
title: string,
|
||||
message: string,
|
||||
buttons: {
|
||||
action: {
|
||||
message: string,
|
||||
icon: string | null,
|
||||
color: string | null,
|
||||
onPress: () => void,
|
||||
},
|
||||
cancel: {
|
||||
message: string,
|
||||
icon: string | null,
|
||||
color: string | null,
|
||||
onPress: () => void,
|
||||
}
|
||||
},
|
||||
emotion: number,
|
||||
}
|
||||
|
||||
type State = {
|
||||
shouldShowDialog: boolean;
|
||||
}
|
||||
|
||||
|
||||
class MascotPopup extends React.Component<Props, State> {
|
||||
|
||||
mascotSize: number;
|
||||
windowWidth: number;
|
||||
windowHeight: number;
|
||||
|
||||
state = {
|
||||
shouldShowDialog: this.props.visible,
|
||||
};
|
||||
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.windowWidth = Dimensions.get('window').width;
|
||||
this.windowHeight = Dimensions.get('window').height;
|
||||
|
||||
this.mascotSize = Dimensions.get('window').height / 6;
|
||||
}
|
||||
|
||||
onAnimationEnd = () => {
|
||||
this.setState({
|
||||
shouldShowDialog: this.props.visible,
|
||||
})
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps: Props): boolean {
|
||||
if (nextProps.visible)
|
||||
this.state.shouldShowDialog = true;
|
||||
else if (nextProps.visible !== this.props.visible)
|
||||
setTimeout(this.onAnimationEnd, 300);
|
||||
return true;
|
||||
}
|
||||
|
||||
getSpeechBubble() {
|
||||
return (
|
||||
<Animatable.View
|
||||
style={{
|
||||
marginLeft: "10%",
|
||||
marginRight: "10%",
|
||||
}}
|
||||
useNativeDriver={true}
|
||||
animation={this.props.visible ? "bounceInLeft" : "bounceOutLeft"}
|
||||
duration={this.props.visible ? 1000 : 300}
|
||||
>
|
||||
<View style={{
|
||||
marginLeft: this.mascotSize / 3,
|
||||
width: 0,
|
||||
height: 0,
|
||||
borderLeftWidth: 0,
|
||||
borderRightWidth: 20,
|
||||
borderBottomWidth: 20,
|
||||
borderStyle: 'solid',
|
||||
backgroundColor: 'transparent',
|
||||
borderLeftColor: 'transparent',
|
||||
borderRightColor: 'transparent',
|
||||
borderBottomColor: this.props.theme.colors.mascotMessageArrow,
|
||||
}}/>
|
||||
<Card style={{
|
||||
borderColor: this.props.theme.colors.mascotMessageArrow,
|
||||
borderWidth: 4,
|
||||
borderRadius: 10,
|
||||
}}>
|
||||
<Card.Title
|
||||
title={this.props.title}
|
||||
left={this.props.icon != null ?
|
||||
(props) => <Avatar.Icon
|
||||
{...props}
|
||||
style={{backgroundColor: "transparent"}}
|
||||
color={this.props.theme.colors.primary}
|
||||
icon={this.props.icon}
|
||||
/>
|
||||
|
||||
: null}
|
||||
/>
|
||||
|
||||
<Card.Content style={{
|
||||
maxHeight: this.windowHeight / 3
|
||||
}}>
|
||||
<ScrollView>
|
||||
<Paragraph style={{marginBottom: 10}}>
|
||||
{this.props.message}
|
||||
</Paragraph>
|
||||
</ScrollView>
|
||||
</Card.Content>
|
||||
|
||||
<Card.Actions>
|
||||
{this.getButtons()}
|
||||
</Card.Actions>
|
||||
</Card>
|
||||
</Animatable.View>
|
||||
);
|
||||
}
|
||||
|
||||
getMascot() {
|
||||
return (
|
||||
<Animatable.View
|
||||
useNativeDriver={true}
|
||||
animation={this.props.visible ? "bounceInLeft" : "bounceOutLeft"}
|
||||
duration={this.props.visible ? 1500 : 200}
|
||||
>
|
||||
<Mascot
|
||||
size={this.mascotSize}
|
||||
animated={true}
|
||||
emotion={this.props.emotion}
|
||||
/>
|
||||
</Animatable.View>
|
||||
);
|
||||
}
|
||||
|
||||
getButtons() {
|
||||
const action = this.props.buttons.action;
|
||||
const cancel = this.props.buttons.cancel;
|
||||
return (
|
||||
<View style={{
|
||||
flexDirection: "row",
|
||||
marginLeft: "auto",
|
||||
marginRight: "auto",
|
||||
marginTop: "auto",
|
||||
marginBottom: "auto",
|
||||
}}>
|
||||
{cancel != null
|
||||
? <Button
|
||||
mode={"contained"}
|
||||
icon={cancel.icon}
|
||||
color={cancel.color}
|
||||
onPress={cancel.onPress}
|
||||
>
|
||||
{cancel.message}
|
||||
</Button>
|
||||
: null}
|
||||
{action != null
|
||||
? <Button
|
||||
style={{
|
||||
marginLeft: 20,
|
||||
}}
|
||||
mode={"contained"}
|
||||
icon={action.icon}
|
||||
color={action.color}
|
||||
onPress={action.onPress}
|
||||
>
|
||||
{action.message}
|
||||
</Button>
|
||||
: null}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
getBackground() {
|
||||
return (
|
||||
<TouchableWithoutFeedback onPress={this.props.buttons.cancel.onPress}>
|
||||
<Animatable.View
|
||||
style={{
|
||||
position: "absolute",
|
||||
backgroundColor: "rgba(0,0,0,0.7)",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
}}
|
||||
useNativeDriver={true}
|
||||
animation={this.props.visible ? "fadeIn" : "fadeOut"}
|
||||
duration={this.props.visible ? 300 : 300}
|
||||
/>
|
||||
</TouchableWithoutFeedback>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.shouldShowDialog) {
|
||||
return (
|
||||
<Portal>
|
||||
{this.getBackground()}
|
||||
<View style={{
|
||||
marginTop: "auto",
|
||||
marginBottom: "auto",
|
||||
}}>
|
||||
{this.getMascot()}
|
||||
{this.getSpeechBubble()}
|
||||
</View>
|
||||
</Portal>
|
||||
);
|
||||
} else
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export default withTheme(MascotPopup);
|
|
@ -55,6 +55,9 @@ export type CustomTheme = {
|
|||
tetrisZ: string,
|
||||
tetrisJ: string,
|
||||
tetrisL: string,
|
||||
|
||||
// Mascot Popup
|
||||
mascotMessageArrow: string,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -83,7 +86,7 @@ export default class ThemeManager {
|
|||
primary: '#be1522',
|
||||
accent: '#be1522',
|
||||
tabIcon: "#929292",
|
||||
card: "rgb(255, 255, 255)",
|
||||
card: "#fff",
|
||||
dividerBackground: '#e2e2e2',
|
||||
ripple: "rgba(0,0,0,0.2)",
|
||||
textDisabled: '#c1c1c1',
|
||||
|
@ -126,6 +129,9 @@ export default class ThemeManager {
|
|||
tetrisZ: '#ff0009',
|
||||
tetrisJ: '#2a67e3',
|
||||
tetrisL: '#da742d',
|
||||
|
||||
// Mascot Popup
|
||||
mascotMessageArrow: "#dedede",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -144,7 +150,7 @@ export default class ThemeManager {
|
|||
accent: '#be1522',
|
||||
tabBackground: "#181818",
|
||||
tabIcon: "#6d6d6d",
|
||||
card: "rgb(18, 18, 18)",
|
||||
card: "rgb(18,18,18)",
|
||||
dividerBackground: '#222222',
|
||||
ripple: "rgba(255,255,255,0.2)",
|
||||
textDisabled: '#5b5b5b',
|
||||
|
@ -186,6 +192,9 @@ export default class ThemeManager {
|
|||
tetrisZ: '#b50008',
|
||||
tetrisJ: '#0f37b9',
|
||||
tetrisL: '#b96226',
|
||||
|
||||
// Mascot Popup
|
||||
mascotMessageArrow: "#323232",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import {FlatList} from 'react-native';
|
|||
import i18n from "i18n-js";
|
||||
import DashboardItem from "../../components/Home/EventDashboardItem";
|
||||
import WebSectionList from "../../components/Screens/WebSectionList";
|
||||
import {Avatar, Banner, withTheme} from 'react-native-paper';
|
||||
import {withTheme} from 'react-native-paper';
|
||||
import FeedItem from "../../components/Home/FeedItem";
|
||||
import SquareDashboardItem from "../../components/Home/SmallDashboardItem";
|
||||
import PreviewEventDashboardItem from "../../components/Home/PreviewEventDashboardItem";
|
||||
|
@ -19,10 +19,10 @@ import type {CustomTheme} from "../../managers/ThemeManager";
|
|||
import {View} from "react-native-animatable";
|
||||
import ConnectionManager from "../../managers/ConnectionManager";
|
||||
import LogoutDialog from "../../components/Amicale/LogoutDialog";
|
||||
import {withCollapsible} from "../../utils/withCollapsible";
|
||||
import {Collapsible} from "react-navigation-collapsible";
|
||||
import AsyncStorageManager from "../../managers/AsyncStorageManager";
|
||||
import AvailableWebsites from "../../constants/AvailableWebsites";
|
||||
import {MASCOT_STYLE} from "../../components/Mascot/Mascot";
|
||||
import MascotPopup from "../../components/Mascot/MascotPopup";
|
||||
// import DATA from "../dashboard_data.json";
|
||||
|
||||
|
||||
|
@ -97,12 +97,11 @@ type Props = {
|
|||
navigation: StackNavigationProp,
|
||||
route: { params: any, ... },
|
||||
theme: CustomTheme,
|
||||
collapsibleStack: Collapsible,
|
||||
}
|
||||
|
||||
type State = {
|
||||
dialogVisible: boolean,
|
||||
bannerVisible: boolean,
|
||||
mascotDialogVisible: boolean,
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -115,16 +114,19 @@ class HomeScreen extends React.Component<Props, State> {
|
|||
fabRef: { current: null | AnimatedFAB };
|
||||
currentNewFeed: Array<feedItem>;
|
||||
|
||||
state = {
|
||||
dialogVisible: false,
|
||||
bannerVisible: false,
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.fabRef = React.createRef();
|
||||
this.currentNewFeed = [];
|
||||
this.isLoggedIn = null;
|
||||
this.isLoggedIn = ConnectionManager.getInstance().isLoggedIn();
|
||||
this.props.navigation.setOptions({
|
||||
headerRight: this.getHeaderButton,
|
||||
});
|
||||
this.state = {
|
||||
dialogVisible: false,
|
||||
mascotDialogVisible: AsyncStorageManager.getInstance().preferences.homeShowBanner.current === "1"
|
||||
&& !this.isLoggedIn,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -142,13 +144,6 @@ class HomeScreen extends React.Component<Props, State> {
|
|||
this.props.navigation.addListener('focus', this.onScreenFocus);
|
||||
// Handle link open when home is focused
|
||||
this.props.navigation.addListener('state', this.handleNavigationParams);
|
||||
setTimeout(this.onBannerTimeout, 2000);
|
||||
}
|
||||
|
||||
onBannerTimeout = () => {
|
||||
this.setState({
|
||||
bannerVisible: AsyncStorageManager.getInstance().preferences.homeShowBanner.current === "1"
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -161,9 +156,6 @@ class HomeScreen extends React.Component<Props, State> {
|
|||
headerRight: this.getHeaderButton,
|
||||
});
|
||||
}
|
||||
if (this.isLoggedIn) {
|
||||
this.setState({bannerVisible: false})
|
||||
}
|
||||
// handle link open when home is not focused or created
|
||||
this.handleNavigationParams();
|
||||
};
|
||||
|
@ -203,6 +195,14 @@ class HomeScreen extends React.Component<Props, State> {
|
|||
</MaterialHeaderButtons>;
|
||||
};
|
||||
|
||||
hideMascotDialog = () => {
|
||||
AsyncStorageManager.getInstance().savePref(
|
||||
AsyncStorageManager.getInstance().preferences.homeShowBanner.key,
|
||||
'0'
|
||||
);
|
||||
this.setState({mascotDialogVisible: false})
|
||||
};
|
||||
|
||||
showDisconnectDialog = () => this.setState({dialogVisible: true});
|
||||
|
||||
hideDisconnectDialog = () => this.setState({dialogVisible: false});
|
||||
|
@ -569,29 +569,16 @@ class HomeScreen extends React.Component<Props, State> {
|
|||
this.fabRef.current.onScroll(event);
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback used when closing the banner.
|
||||
* This hides the banner and saves to preferences to prevent it from reopening.
|
||||
*/
|
||||
onHideBanner = () => {
|
||||
this.setState({bannerVisible: false});
|
||||
AsyncStorageManager.getInstance().savePref(
|
||||
AsyncStorageManager.getInstance().preferences.homeShowBanner.key,
|
||||
'0'
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback when pressing the login button on the banner.
|
||||
* This hides the banner and takes the user to the login page.
|
||||
*/
|
||||
onLoginBanner = () => {
|
||||
this.onHideBanner();
|
||||
onLogin = () => {
|
||||
this.hideMascotDialog();
|
||||
this.props.navigation.navigate("login", {nextScreen: "profile"});
|
||||
}
|
||||
|
||||
render() {
|
||||
const {containerPaddingTop} = this.props.collapsibleStack;
|
||||
return (
|
||||
<View
|
||||
style={{flex: 1}}
|
||||
|
@ -613,6 +600,26 @@ class HomeScreen extends React.Component<Props, State> {
|
|||
showError={false}
|
||||
/>
|
||||
</View>
|
||||
<MascotPopup
|
||||
visible={this.state.mascotDialogVisible}
|
||||
title={i18n.t("homeScreen.loginBanner.title")}
|
||||
message={i18n.t("homeScreen.loginBanner.message")}
|
||||
icon={"check"}
|
||||
buttons={{
|
||||
action: {
|
||||
message: i18n.t("homeScreen.loginBanner.login"),
|
||||
icon: "login",
|
||||
onPress: this.onLogin,
|
||||
},
|
||||
cancel: {
|
||||
message: i18n.t("homeScreen.loginBanner.later"),
|
||||
icon: "close",
|
||||
color: this.props.theme.colors.warning,
|
||||
onPress: this.hideMascotDialog,
|
||||
}
|
||||
}}
|
||||
emotion={MASCOT_STYLE.CUTE}
|
||||
/>
|
||||
<AnimatedFAB
|
||||
{...this.props}
|
||||
ref={this.fabRef}
|
||||
|
@ -624,32 +631,9 @@ class HomeScreen extends React.Component<Props, State> {
|
|||
visible={this.state.dialogVisible}
|
||||
onDismiss={this.hideDisconnectDialog}
|
||||
/>
|
||||
<Banner
|
||||
style={{
|
||||
marginTop: containerPaddingTop,
|
||||
backgroundColor: this.props.theme.colors.surface
|
||||
}}
|
||||
visible={this.state.bannerVisible}
|
||||
actions={[
|
||||
{
|
||||
label: i18n.t('homeScreen.loginBanner.login'),
|
||||
onPress: this.onLoginBanner,
|
||||
},
|
||||
{
|
||||
label: i18n.t('homeScreen.loginBanner.later'),
|
||||
onPress: this.onHideBanner,
|
||||
},
|
||||
]}
|
||||
icon={() => <Avatar.Icon
|
||||
icon={'login'}
|
||||
size={50}
|
||||
/>}
|
||||
>
|
||||
{i18n.t('homeScreen.loginBanner.message')}
|
||||
</Banner>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withCollapsible(withTheme(HomeScreen));
|
||||
export default withTheme(HomeScreen);
|
||||
|
|
|
@ -4,30 +4,29 @@ import * as React from 'react';
|
|||
import type {CustomTheme} from "../../managers/ThemeManager";
|
||||
import ThemeManager from "../../managers/ThemeManager";
|
||||
import WebViewScreen from "../../components/Screens/WebViewScreen";
|
||||
import {Avatar, Banner, withTheme} from "react-native-paper";
|
||||
import {withTheme} from "react-native-paper";
|
||||
import i18n from "i18n-js";
|
||||
import {View} from "react-native";
|
||||
import AsyncStorageManager from "../../managers/AsyncStorageManager";
|
||||
import AlertDialog from "../../components/Dialogs/AlertDialog";
|
||||
import {withCollapsible} from "../../utils/withCollapsible";
|
||||
import {dateToString, getTimeOnlyString} from "../../utils/Planning";
|
||||
import DateManager from "../../managers/DateManager";
|
||||
import AnimatedBottomBar from "../../components/Animations/AnimatedBottomBar";
|
||||
import {CommonActions} from "@react-navigation/native";
|
||||
import ErrorView from "../../components/Screens/ErrorView";
|
||||
import {StackNavigationProp} from "@react-navigation/stack";
|
||||
import {Collapsible} from "react-navigation-collapsible";
|
||||
import type {group} from "./GroupSelectionScreen";
|
||||
import {MASCOT_STYLE} from "../../components/Mascot/Mascot";
|
||||
import MascotPopup from "../../components/Mascot/MascotPopup";
|
||||
|
||||
type Props = {
|
||||
navigation: StackNavigationProp,
|
||||
route: { params: { group: group } },
|
||||
theme: CustomTheme,
|
||||
collapsibleStack: Collapsible,
|
||||
}
|
||||
|
||||
type State = {
|
||||
bannerVisible: boolean,
|
||||
mascotDialogVisible: boolean,
|
||||
dialogVisible: boolean,
|
||||
dialogTitle: string,
|
||||
dialogMessage: string,
|
||||
|
@ -144,7 +143,9 @@ class PlanexScreen extends React.Component<Props, State> {
|
|||
props.navigation.setOptions({title: currentGroup.name})
|
||||
}
|
||||
this.state = {
|
||||
bannerVisible: false,
|
||||
mascotDialogVisible:
|
||||
AsyncStorageManager.getInstance().preferences.planexShowBanner.current === '1' &&
|
||||
AsyncStorageManager.getInstance().preferences.defaultStartScreen.current !== 'Planex',
|
||||
dialogVisible: false,
|
||||
dialogTitle: "",
|
||||
dialogMessage: "",
|
||||
|
@ -158,23 +159,14 @@ class PlanexScreen extends React.Component<Props, State> {
|
|||
*/
|
||||
componentDidMount() {
|
||||
this.props.navigation.addListener('focus', this.onScreenFocus);
|
||||
setTimeout(this.onBannerTimeout, 2000);
|
||||
}
|
||||
|
||||
onBannerTimeout = () => {
|
||||
this.setState({
|
||||
bannerVisible:
|
||||
AsyncStorageManager.getInstance().preferences.planexShowBanner.current === '1' &&
|
||||
AsyncStorageManager.getInstance().preferences.defaultStartScreen.current !== 'Planex'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback used when closing the banner.
|
||||
* This hides the banner and saves to preferences to prevent it from reopening
|
||||
*/
|
||||
onHideBanner = () => {
|
||||
this.setState({bannerVisible: false});
|
||||
onMascotDialogCancel = () => {
|
||||
this.setState({mascotDialogVisible: false});
|
||||
AsyncStorageManager.getInstance().savePref(
|
||||
AsyncStorageManager.getInstance().preferences.planexShowBanner.key,
|
||||
'0'
|
||||
|
@ -187,7 +179,7 @@ class PlanexScreen extends React.Component<Props, State> {
|
|||
* This will hide the banner and open the SettingsScreen
|
||||
*/
|
||||
onGoToSettings = () => {
|
||||
this.onHideBanner();
|
||||
this.onMascotDialogCancel();
|
||||
this.props.navigation.navigate('settings');
|
||||
};
|
||||
|
||||
|
@ -356,7 +348,6 @@ class PlanexScreen extends React.Component<Props, State> {
|
|||
}
|
||||
|
||||
render() {
|
||||
const {containerPaddingTop} = this.props.collapsibleStack;
|
||||
return (
|
||||
<View
|
||||
style={{flex: 1}}
|
||||
|
@ -371,30 +362,26 @@ class PlanexScreen extends React.Component<Props, State> {
|
|||
? this.getWebView()
|
||||
: <View style={{height: '100%'}}>{this.getWebView()}</View>}
|
||||
</View>
|
||||
<Banner
|
||||
style={{
|
||||
marginTop: containerPaddingTop,
|
||||
backgroundColor: this.props.theme.colors.surface
|
||||
}}
|
||||
visible={this.state.bannerVisible}
|
||||
actions={[
|
||||
{
|
||||
label: i18n.t('planexScreen.enableStartOK'),
|
||||
<MascotPopup
|
||||
visible={this.state.mascotDialogVisible}
|
||||
title={i18n.t("planexScreen.enableStartScreenTitle")}
|
||||
message={i18n.t("planexScreen.enableStartScreenMessage")}
|
||||
icon={"power"}
|
||||
buttons={{
|
||||
action: {
|
||||
message: i18n.t("planexScreen.enableStartOK"),
|
||||
icon: "settings",
|
||||
onPress: this.onGoToSettings,
|
||||
},
|
||||
{
|
||||
label: i18n.t('planexScreen.enableStartCancel'),
|
||||
onPress: this.onHideBanner,
|
||||
},
|
||||
|
||||
]}
|
||||
icon={() => <Avatar.Icon
|
||||
icon={'power'}
|
||||
size={40}
|
||||
/>}
|
||||
>
|
||||
{i18n.t('planexScreen.enableStartScreen')}
|
||||
</Banner>
|
||||
cancel: {
|
||||
message: i18n.t("planexScreen.enableStartCancel"),
|
||||
icon: "close",
|
||||
color: this.props.theme.colors.warning,
|
||||
onPress: this.onMascotDialogCancel,
|
||||
}
|
||||
}}
|
||||
emotion={MASCOT_STYLE.INTELLO}
|
||||
/>
|
||||
<AlertDialog
|
||||
visible={this.state.dialogVisible}
|
||||
onDismiss={this.hideDialog}
|
||||
|
@ -411,4 +398,4 @@ class PlanexScreen extends React.Component<Props, State> {
|
|||
}
|
||||
}
|
||||
|
||||
export default withCollapsible(withTheme(PlanexScreen));
|
||||
export default withTheme(PlanexScreen);
|
||||
|
|
|
@ -6,19 +6,19 @@ import i18n from "i18n-js";
|
|||
import WebSectionList from "../../components/Screens/WebSectionList";
|
||||
import * as Notifications from "../../utils/Notifications";
|
||||
import AsyncStorageManager from "../../managers/AsyncStorageManager";
|
||||
import {Avatar, Banner, Button, Card, Text, withTheme} from 'react-native-paper';
|
||||
import {Avatar, Button, Card, Text, withTheme} from 'react-native-paper';
|
||||
import ProxiwashListItem from "../../components/Lists/Proxiwash/ProxiwashListItem";
|
||||
import ProxiwashConstants from "../../constants/ProxiwashConstants";
|
||||
import CustomModal from "../../components/Overrides/CustomModal";
|
||||
import AprilFoolsManager from "../../managers/AprilFoolsManager";
|
||||
import MaterialHeaderButtons, {Item} from "../../components/Overrides/CustomHeaderButton";
|
||||
import ProxiwashSectionHeader from "../../components/Lists/Proxiwash/ProxiwashSectionHeader";
|
||||
import {withCollapsible} from "../../utils/withCollapsible";
|
||||
import type {CustomTheme} from "../../managers/ThemeManager";
|
||||
import {Collapsible} from "react-navigation-collapsible";
|
||||
import {StackNavigationProp} from "@react-navigation/stack";
|
||||
import {getCleanedMachineWatched, getMachineEndDate, isMachineWatched} from "../../utils/Proxiwash";
|
||||
import {Modalize} from "react-native-modalize";
|
||||
import {MASCOT_STYLE} from "../../components/Mascot/Mascot";
|
||||
import MascotPopup from "../../components/Mascot/MascotPopup";
|
||||
|
||||
const DATA_URL = "https://etud.insa-toulouse.fr/~amicale_app/v2/washinsa/washinsa_data.json";
|
||||
|
||||
|
@ -40,14 +40,13 @@ export type Machine = {
|
|||
type Props = {
|
||||
navigation: StackNavigationProp,
|
||||
theme: CustomTheme,
|
||||
collapsibleStack: Collapsible,
|
||||
}
|
||||
|
||||
type State = {
|
||||
refreshing: boolean,
|
||||
modalCurrentDisplayItem: React.Node,
|
||||
machinesWatched: Array<Machine>,
|
||||
bannerVisible: boolean,
|
||||
mascotDialogVisible: boolean,
|
||||
};
|
||||
|
||||
|
||||
|
@ -68,7 +67,7 @@ class ProxiwashScreen extends React.Component<Props, State> {
|
|||
refreshing: false,
|
||||
modalCurrentDisplayItem: null,
|
||||
machinesWatched: JSON.parse(AsyncStorageManager.getInstance().preferences.proxiwashWatchedMachines.current),
|
||||
bannerVisible: false,
|
||||
mascotDialogVisible: AsyncStorageManager.getInstance().preferences.proxiwashShowBanner.current === "1",
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -89,8 +88,8 @@ class ProxiwashScreen extends React.Component<Props, State> {
|
|||
* Callback used when closing the banner.
|
||||
* This hides the banner and saves to preferences to prevent it from reopening
|
||||
*/
|
||||
onHideBanner = () => {
|
||||
this.setState({bannerVisible: false});
|
||||
onHideMascotDialog = () => {
|
||||
this.setState({mascotDialogVisible: false});
|
||||
AsyncStorageManager.getInstance().savePref(
|
||||
AsyncStorageManager.getInstance().preferences.proxiwashShowBanner.key,
|
||||
'0'
|
||||
|
@ -107,11 +106,6 @@ class ProxiwashScreen extends React.Component<Props, State> {
|
|||
<Item title="information" iconName="information" onPress={this.onAboutPress}/>
|
||||
</MaterialHeaderButtons>,
|
||||
});
|
||||
setTimeout(this.onBannerTimeout, 2000);
|
||||
}
|
||||
|
||||
onBannerTimeout = () => {
|
||||
this.setState({bannerVisible: AsyncStorageManager.getInstance().preferences.proxiwashShowBanner.current === "1"})
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -401,7 +395,6 @@ class ProxiwashScreen extends React.Component<Props, State> {
|
|||
|
||||
render() {
|
||||
const nav = this.props.navigation;
|
||||
const {containerPaddingTop} = this.props.collapsibleStack;
|
||||
return (
|
||||
<View
|
||||
style={{flex: 1}}
|
||||
|
@ -421,25 +414,21 @@ class ProxiwashScreen extends React.Component<Props, State> {
|
|||
refreshOnFocus={true}
|
||||
updateData={this.state.machinesWatched.length}/>
|
||||
</View>
|
||||
<Banner
|
||||
style={{
|
||||
marginTop: containerPaddingTop,
|
||||
backgroundColor: this.props.theme.colors.surface
|
||||
<MascotPopup
|
||||
visible={this.state.mascotDialogVisible}
|
||||
title={i18n.t("proxiwashScreen.bannerTitle")}
|
||||
message={i18n.t("proxiwashScreen.enableNotificationsTip")}
|
||||
icon={"bell"}
|
||||
buttons={{
|
||||
action: null,
|
||||
cancel: {
|
||||
message: i18n.t("proxiwashScreen.bannerButton"),
|
||||
icon: "check",
|
||||
onPress: this.onHideMascotDialog,
|
||||
}
|
||||
}}
|
||||
visible={this.state.bannerVisible}
|
||||
actions={[
|
||||
{
|
||||
label: i18n.t('proxiwashScreen.bannerButton'),
|
||||
onPress: this.onHideBanner,
|
||||
},
|
||||
]}
|
||||
icon={() => <Avatar.Icon
|
||||
icon={'bell'}
|
||||
size={40}
|
||||
/>}
|
||||
>
|
||||
{i18n.t('proxiwashScreen.enableNotificationsTip')}
|
||||
</Banner>
|
||||
emotion={MASCOT_STYLE.NORMAL}
|
||||
/>
|
||||
<CustomModal onRef={this.onModalRef}>
|
||||
{this.state.modalCurrentDisplayItem}
|
||||
</CustomModal>
|
||||
|
@ -448,4 +437,4 @@ class ProxiwashScreen extends React.Component<Props, State> {
|
|||
}
|
||||
}
|
||||
|
||||
export default withCollapsible(withTheme(ProxiwashScreen));
|
||||
export default withTheme(ProxiwashScreen);
|
||||
|
|
|
@ -116,7 +116,8 @@
|
|||
"loginBanner": {
|
||||
"login": "Login",
|
||||
"later": "Later",
|
||||
"message": "Login to your Amicale account to get access to more services!"
|
||||
"title": "Welcome, you!",
|
||||
"message": "Login to your Amicale account to get access to more services!\n\nYou will still be able to login later."
|
||||
}
|
||||
},
|
||||
"aboutScreen": {
|
||||
|
@ -179,9 +180,10 @@
|
|||
"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.",
|
||||
"procedure": "Procedure",
|
||||
"tips": "Tips",
|
||||
"enableNotificationsTip": "Click on a running machine to enable notifications",
|
||||
"enableNotificationsTip": "Click on a running machine to enable notifications!\n\nYou will never forget your laundry again.",
|
||||
"numAvailable": "available",
|
||||
"numAvailablePlural": "available",
|
||||
"bannerTitle": "Notifications!",
|
||||
"bannerButton": "Got it!",
|
||||
"modal": {
|
||||
"enableNotifications": "Notify me",
|
||||
|
@ -215,7 +217,8 @@
|
|||
}
|
||||
},
|
||||
"planexScreen": {
|
||||
"enableStartScreen": "Come here often? Set it as default screen!",
|
||||
"enableStartScreenTitle": "Come here often?",
|
||||
"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.",
|
||||
"enableStartOK": "Yes please!",
|
||||
"enableStartCancel": "Later",
|
||||
"noGroupSelected": "No group selected. Please select your group using the big beautiful red button bellow.",
|
||||
|
|
Loading…
Reference in a new issue