forked from vergnet/application-amicale
Improve Mascot components to match linter
This commit is contained in:
parent
1cc0802c12
commit
7b94afadcc
3 changed files with 559 additions and 510 deletions
|
@ -1,259 +1,269 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import * as Animatable from "react-native-animatable";
|
import * as Animatable from 'react-native-animatable';
|
||||||
import {Image, TouchableWithoutFeedback, View} from "react-native";
|
import {Image, TouchableWithoutFeedback, View} from 'react-native';
|
||||||
import type {ViewStyle} from "react-native/Libraries/StyleSheet/StyleSheet";
|
import type {ViewStyle} from 'react-native/Libraries/StyleSheet/StyleSheet';
|
||||||
|
|
||||||
type Props = {
|
export type AnimatableViewRefType = {current: null | Animatable.View};
|
||||||
style?: ViewStyle,
|
|
||||||
emotion: number,
|
|
||||||
animated: boolean,
|
|
||||||
entryAnimation: Animatable.AnimatableProperties | null,
|
|
||||||
loopAnimation: Animatable.AnimatableProperties | null,
|
|
||||||
onPress?: (viewRef: AnimatableViewRef) => null,
|
|
||||||
onLongPress?: (viewRef: AnimatableViewRef) => null,
|
|
||||||
}
|
|
||||||
|
|
||||||
type State = {
|
type PropsType = {
|
||||||
currentEmotion: number,
|
emotion?: number,
|
||||||
}
|
animated?: boolean,
|
||||||
|
style?: ViewStyle | null,
|
||||||
export type AnimatableViewRef = {current: null | Animatable.View};
|
entryAnimation?: Animatable.AnimatableProperties | null,
|
||||||
|
loopAnimation?: Animatable.AnimatableProperties | null,
|
||||||
const MASCOT_IMAGE = require("../../../assets/mascot/mascot.png");
|
onPress?: null | ((viewRef: AnimatableViewRefType) => void),
|
||||||
const MASCOT_EYES_NORMAL = require("../../../assets/mascot/mascot_eyes_normal.png");
|
onLongPress?: null | ((viewRef: AnimatableViewRefType) => void),
|
||||||
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_EYES_HEART = require("../../../assets/mascot/mascot_eyes_heart.png");
|
|
||||||
const MASCOT_EYES_ANGRY = require("../../../assets/mascot/mascot_eyes_angry.png");
|
|
||||||
const MASCOT_GLASSES = require("../../../assets/mascot/mascot_glasses.png");
|
|
||||||
const MASCOT_SUNGLASSES = require("../../../assets/mascot/mascot_sunglasses.png");
|
|
||||||
|
|
||||||
export const EYE_STYLE = {
|
|
||||||
NORMAL: 0,
|
|
||||||
GIRLY: 2,
|
|
||||||
CUTE: 3,
|
|
||||||
WINK: 4,
|
|
||||||
HEART: 5,
|
|
||||||
ANGRY: 6,
|
|
||||||
}
|
|
||||||
|
|
||||||
const GLASSES_STYLE = {
|
|
||||||
NORMAL: 0,
|
|
||||||
COOl: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
export const MASCOT_STYLE = {
|
|
||||||
NORMAL: 0,
|
|
||||||
HAPPY: 1,
|
|
||||||
GIRLY: 2,
|
|
||||||
WINK: 3,
|
|
||||||
CUTE: 4,
|
|
||||||
INTELLO: 5,
|
|
||||||
LOVE: 6,
|
|
||||||
COOL: 7,
|
|
||||||
ANGRY: 8,
|
|
||||||
RANDOM: 999,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type StateType = {
|
||||||
|
currentEmotion: number,
|
||||||
|
};
|
||||||
|
|
||||||
class Mascot extends React.Component<Props, State> {
|
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_EYES_HEART = require('../../../assets/mascot/mascot_eyes_heart.png');
|
||||||
|
const MASCOT_EYES_ANGRY = require('../../../assets/mascot/mascot_eyes_angry.png');
|
||||||
|
const MASCOT_GLASSES = require('../../../assets/mascot/mascot_glasses.png');
|
||||||
|
const MASCOT_SUNGLASSES = require('../../../assets/mascot/mascot_sunglasses.png');
|
||||||
|
|
||||||
static defaultProps = {
|
export const EYE_STYLE = {
|
||||||
animated: false,
|
NORMAL: 0,
|
||||||
entryAnimation: {
|
GIRLY: 2,
|
||||||
useNativeDriver: true,
|
CUTE: 3,
|
||||||
animation: "rubberBand",
|
WINK: 4,
|
||||||
duration: 2000,
|
HEART: 5,
|
||||||
},
|
ANGRY: 6,
|
||||||
loopAnimation: {
|
};
|
||||||
useNativeDriver: true,
|
|
||||||
animation: "swing",
|
|
||||||
duration: 2000,
|
|
||||||
iterationDelay: 250,
|
|
||||||
iterationCount: "infinite",
|
|
||||||
},
|
|
||||||
clickAnimation: {
|
|
||||||
useNativeDriver: true,
|
|
||||||
animation: "rubberBand",
|
|
||||||
duration: 2000,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
viewRef: AnimatableViewRef;
|
const GLASSES_STYLE = {
|
||||||
eyeList: { [key: number]: number | string };
|
NORMAL: 0,
|
||||||
glassesList: { [key: number]: number | string };
|
COOl: 1,
|
||||||
|
};
|
||||||
|
|
||||||
onPress: (viewRef: AnimatableViewRef) => null;
|
export const MASCOT_STYLE = {
|
||||||
onLongPress: (viewRef: AnimatableViewRef) => null;
|
NORMAL: 0,
|
||||||
|
HAPPY: 1,
|
||||||
|
GIRLY: 2,
|
||||||
|
WINK: 3,
|
||||||
|
CUTE: 4,
|
||||||
|
INTELLO: 5,
|
||||||
|
LOVE: 6,
|
||||||
|
COOL: 7,
|
||||||
|
ANGRY: 8,
|
||||||
|
RANDOM: 999,
|
||||||
|
};
|
||||||
|
|
||||||
initialEmotion: number;
|
class Mascot extends React.Component<PropsType, StateType> {
|
||||||
|
static defaultProps = {
|
||||||
|
emotion: MASCOT_STYLE.NORMAL,
|
||||||
|
animated: false,
|
||||||
|
style: null,
|
||||||
|
entryAnimation: {
|
||||||
|
useNativeDriver: true,
|
||||||
|
animation: 'rubberBand',
|
||||||
|
duration: 2000,
|
||||||
|
},
|
||||||
|
loopAnimation: {
|
||||||
|
useNativeDriver: true,
|
||||||
|
animation: 'swing',
|
||||||
|
duration: 2000,
|
||||||
|
iterationDelay: 250,
|
||||||
|
iterationCount: 'infinite',
|
||||||
|
},
|
||||||
|
onPress: null,
|
||||||
|
onLongPress: null,
|
||||||
|
};
|
||||||
|
|
||||||
constructor(props: Props) {
|
viewRef: AnimatableViewRefType;
|
||||||
super(props);
|
|
||||||
this.viewRef = React.createRef();
|
|
||||||
this.eyeList = {};
|
|
||||||
this.glassesList = {};
|
|
||||||
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;
|
|
||||||
this.eyeList[EYE_STYLE.HEART] = MASCOT_EYES_HEART;
|
|
||||||
this.eyeList[EYE_STYLE.ANGRY] = MASCOT_EYES_ANGRY;
|
|
||||||
|
|
||||||
this.glassesList[GLASSES_STYLE.NORMAL] = MASCOT_GLASSES;
|
eyeList: {[key: number]: number | string};
|
||||||
this.glassesList[GLASSES_STYLE.COOl] = MASCOT_SUNGLASSES;
|
|
||||||
|
|
||||||
this.initialEmotion = this.props.emotion;
|
glassesList: {[key: number]: number | string};
|
||||||
|
|
||||||
if (this.initialEmotion === MASCOT_STYLE.RANDOM)
|
onPress: (viewRef: AnimatableViewRefType) => void;
|
||||||
this.initialEmotion = Math.floor(Math.random() * MASCOT_STYLE.ANGRY) + 1;
|
|
||||||
|
|
||||||
this.state = {
|
onLongPress: (viewRef: AnimatableViewRefType) => void;
|
||||||
currentEmotion: this.initialEmotion
|
|
||||||
|
initialEmotion: number;
|
||||||
|
|
||||||
|
constructor(props: PropsType) {
|
||||||
|
super(props);
|
||||||
|
this.viewRef = React.createRef();
|
||||||
|
this.eyeList = {};
|
||||||
|
this.glassesList = {};
|
||||||
|
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;
|
||||||
|
this.eyeList[EYE_STYLE.HEART] = MASCOT_EYES_HEART;
|
||||||
|
this.eyeList[EYE_STYLE.ANGRY] = MASCOT_EYES_ANGRY;
|
||||||
|
|
||||||
|
this.glassesList[GLASSES_STYLE.NORMAL] = MASCOT_GLASSES;
|
||||||
|
this.glassesList[GLASSES_STYLE.COOl] = MASCOT_SUNGLASSES;
|
||||||
|
|
||||||
|
this.initialEmotion =
|
||||||
|
props.emotion != null ? props.emotion : Mascot.defaultProps.emotion;
|
||||||
|
|
||||||
|
if (this.initialEmotion === MASCOT_STYLE.RANDOM)
|
||||||
|
this.initialEmotion = Math.floor(Math.random() * MASCOT_STYLE.ANGRY) + 1;
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
currentEmotion: this.initialEmotion,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (props.onPress == null) {
|
||||||
|
this.onPress = (viewRef: AnimatableViewRefType) => {
|
||||||
|
const ref = viewRef.current;
|
||||||
|
if (ref != null) {
|
||||||
|
this.setState({currentEmotion: MASCOT_STYLE.LOVE});
|
||||||
|
ref.rubberBand(1500).then(() => {
|
||||||
|
this.setState({currentEmotion: this.initialEmotion});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
} else this.onPress = props.onPress;
|
||||||
|
|
||||||
if (this.props.onPress == null) {
|
if (props.onLongPress == null) {
|
||||||
this.onPress = (viewRef: AnimatableViewRef) => {
|
this.onLongPress = (viewRef: AnimatableViewRefType) => {
|
||||||
let ref = viewRef.current;
|
const ref = viewRef.current;
|
||||||
if (ref != null) {
|
if (ref != null) {
|
||||||
this.setState({currentEmotion: MASCOT_STYLE.LOVE});
|
this.setState({currentEmotion: MASCOT_STYLE.ANGRY});
|
||||||
ref.rubberBand(1500).then(() => {
|
ref.tada(1000).then(() => {
|
||||||
this.setState({currentEmotion: this.initialEmotion});
|
this.setState({currentEmotion: this.initialEmotion});
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
this.onPress = this.props.onPress;
|
|
||||||
|
|
||||||
if (this.props.onLongPress == null) {
|
|
||||||
this.onLongPress = (viewRef: AnimatableViewRef) => {
|
|
||||||
let ref = viewRef.current;
|
|
||||||
if (ref != null) {
|
|
||||||
this.setState({currentEmotion: MASCOT_STYLE.ANGRY});
|
|
||||||
ref.tada(1000).then(() => {
|
|
||||||
this.setState({currentEmotion: this.initialEmotion});
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
this.onLongPress = this.props.onLongPress;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
getGlasses(style: number) {
|
|
||||||
const glasses = this.glassesList[style];
|
|
||||||
return <Image
|
|
||||||
key={"glasses"}
|
|
||||||
source={glasses != null ? glasses : this.glassesList[GLASSES_STYLE.NORMAL]}
|
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
top: "15%",
|
|
||||||
left: 0,
|
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
|
|
||||||
getEye(style: number, isRight: boolean, rotation: string="0deg") {
|
|
||||||
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: "100%",
|
|
||||||
height: "100%",
|
|
||||||
transform: [{rotateY: rotation}]
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
|
|
||||||
getEyes(emotion: number) {
|
|
||||||
let final = [];
|
|
||||||
final.push(<View
|
|
||||||
key={"container"}
|
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
}}/>);
|
|
||||||
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 if (emotion === MASCOT_STYLE.LOVE) {
|
|
||||||
final.push(this.getEye(EYE_STYLE.HEART, true));
|
|
||||||
final.push(this.getEye(EYE_STYLE.HEART, false));
|
|
||||||
} else if (emotion === MASCOT_STYLE.ANGRY) {
|
|
||||||
final.push(this.getEye(EYE_STYLE.ANGRY, true));
|
|
||||||
final.push(this.getEye(EYE_STYLE.ANGRY, false, "180deg"));
|
|
||||||
} else if (emotion === MASCOT_STYLE.COOL) {
|
|
||||||
final.push(this.getGlasses(GLASSES_STYLE.COOl));
|
|
||||||
} else {
|
|
||||||
final.push(this.getEye(EYE_STYLE.NORMAL, true));
|
|
||||||
final.push(this.getEye(EYE_STYLE.NORMAL, false));
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
} else this.onLongPress = props.onLongPress;
|
||||||
|
}
|
||||||
|
|
||||||
if (emotion === MASCOT_STYLE.INTELLO) { // Needs to have normal eyes behind the glasses
|
getGlasses(style: number): React.Node {
|
||||||
final.push(this.getGlasses(GLASSES_STYLE.NORMAL));
|
const glasses = this.glassesList[style];
|
||||||
|
return (
|
||||||
|
<Image
|
||||||
|
key="glasses"
|
||||||
|
source={
|
||||||
|
glasses != null ? glasses : this.glassesList[GLASSES_STYLE.NORMAL]
|
||||||
}
|
}
|
||||||
final.push(<View key={"container2"}/>);
|
style={{
|
||||||
return final;
|
position: 'absolute',
|
||||||
|
top: '15%',
|
||||||
|
left: 0,
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getEye(
|
||||||
|
style: number,
|
||||||
|
isRight: boolean,
|
||||||
|
rotation: string = '0deg',
|
||||||
|
): React.Node {
|
||||||
|
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: '100%',
|
||||||
|
height: '100%',
|
||||||
|
transform: [{rotateY: rotation}],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getEyes(emotion: number): React.Node {
|
||||||
|
const final = [];
|
||||||
|
final.push(
|
||||||
|
<View
|
||||||
|
key="container"
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
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 if (emotion === MASCOT_STYLE.LOVE) {
|
||||||
|
final.push(this.getEye(EYE_STYLE.HEART, true));
|
||||||
|
final.push(this.getEye(EYE_STYLE.HEART, false));
|
||||||
|
} else if (emotion === MASCOT_STYLE.ANGRY) {
|
||||||
|
final.push(this.getEye(EYE_STYLE.ANGRY, true));
|
||||||
|
final.push(this.getEye(EYE_STYLE.ANGRY, false, '180deg'));
|
||||||
|
} else if (emotion === MASCOT_STYLE.COOL) {
|
||||||
|
final.push(this.getGlasses(GLASSES_STYLE.COOl));
|
||||||
|
} else {
|
||||||
|
final.push(this.getEye(EYE_STYLE.NORMAL, true));
|
||||||
|
final.push(this.getEye(EYE_STYLE.NORMAL, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
if (emotion === MASCOT_STYLE.INTELLO) {
|
||||||
const entryAnimation = this.props.animated ? this.props.entryAnimation : null;
|
// Needs to have normal eyes behind the glasses
|
||||||
const loopAnimation = this.props.animated ? this.props.loopAnimation : null;
|
final.push(this.getGlasses(GLASSES_STYLE.NORMAL));
|
||||||
return (
|
}
|
||||||
|
final.push(<View key="container2" />);
|
||||||
|
return final;
|
||||||
|
}
|
||||||
|
|
||||||
|
render(): React.Node {
|
||||||
|
const {props, state} = this;
|
||||||
|
const entryAnimation = props.animated ? props.entryAnimation : null;
|
||||||
|
const loopAnimation = props.animated ? props.loopAnimation : null;
|
||||||
|
return (
|
||||||
|
<Animatable.View
|
||||||
|
style={{
|
||||||
|
aspectRatio: 1,
|
||||||
|
...props.style,
|
||||||
|
}}
|
||||||
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
|
{...entryAnimation}>
|
||||||
|
<TouchableWithoutFeedback
|
||||||
|
onPress={() => {
|
||||||
|
this.onPress(this.viewRef);
|
||||||
|
}}
|
||||||
|
onLongPress={() => {
|
||||||
|
this.onLongPress(this.viewRef);
|
||||||
|
}}>
|
||||||
|
<Animatable.View ref={this.viewRef}>
|
||||||
<Animatable.View
|
<Animatable.View
|
||||||
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
|
{...loopAnimation}>
|
||||||
|
<Image
|
||||||
|
source={MASCOT_IMAGE}
|
||||||
style={{
|
style={{
|
||||||
aspectRatio: 1,
|
width: '100%',
|
||||||
...this.props.style
|
height: '100%',
|
||||||
}}
|
}}
|
||||||
{...entryAnimation}
|
/>
|
||||||
>
|
{this.getEyes(state.currentEmotion)}
|
||||||
<TouchableWithoutFeedback
|
|
||||||
onPress={() => this.onPress(this.viewRef)}
|
|
||||||
onLongPress={() => this.onLongPress(this.viewRef)}
|
|
||||||
>
|
|
||||||
<Animatable.View
|
|
||||||
ref={this.viewRef}
|
|
||||||
>
|
|
||||||
<Animatable.View
|
|
||||||
{...loopAnimation}
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
source={MASCOT_IMAGE}
|
|
||||||
style={{
|
|
||||||
width: "100%",
|
|
||||||
height:"100%",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{this.getEyes(this.state.currentEmotion)}
|
|
||||||
</Animatable.View>
|
|
||||||
</Animatable.View>
|
|
||||||
</TouchableWithoutFeedback>
|
|
||||||
</Animatable.View>
|
</Animatable.View>
|
||||||
);
|
</Animatable.View>
|
||||||
}
|
</TouchableWithoutFeedback>
|
||||||
|
</Animatable.View>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Mascot;
|
export default Mascot;
|
||||||
|
|
|
@ -1,283 +1,312 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {Avatar, Button, Card, Paragraph, Portal, withTheme} from 'react-native-paper';
|
import {
|
||||||
import Mascot from "./Mascot";
|
Avatar,
|
||||||
import * as Animatable from "react-native-animatable";
|
Button,
|
||||||
import {BackHandler, Dimensions, ScrollView, TouchableWithoutFeedback, View} from "react-native";
|
Card,
|
||||||
import type {CustomTheme} from "../../managers/ThemeManager";
|
Paragraph,
|
||||||
import SpeechArrow from "./SpeechArrow";
|
Portal,
|
||||||
import AsyncStorageManager from "../../managers/AsyncStorageManager";
|
withTheme,
|
||||||
|
} from 'react-native-paper';
|
||||||
|
import * as Animatable from 'react-native-animatable';
|
||||||
|
import {
|
||||||
|
BackHandler,
|
||||||
|
Dimensions,
|
||||||
|
ScrollView,
|
||||||
|
TouchableWithoutFeedback,
|
||||||
|
View,
|
||||||
|
} from 'react-native';
|
||||||
|
import Mascot from './Mascot';
|
||||||
|
import type {CustomTheme} from '../../managers/ThemeManager';
|
||||||
|
import SpeechArrow from './SpeechArrow';
|
||||||
|
import AsyncStorageManager from '../../managers/AsyncStorageManager';
|
||||||
|
|
||||||
type Props = {
|
type PropsType = {
|
||||||
theme: CustomTheme,
|
theme: CustomTheme,
|
||||||
icon: string,
|
icon: string,
|
||||||
title: string,
|
title: string,
|
||||||
message: string,
|
message: string,
|
||||||
buttons: {
|
buttons: {
|
||||||
action: {
|
action: {
|
||||||
message: string,
|
message: string,
|
||||||
icon: string | null,
|
icon: string | null,
|
||||||
color: string | null,
|
color: string | null,
|
||||||
onPress?: () => void,
|
onPress?: () => void,
|
||||||
},
|
|
||||||
cancel: {
|
|
||||||
message: string,
|
|
||||||
icon: string | null,
|
|
||||||
color: string | null,
|
|
||||||
onPress?: () => void,
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
emotion: number,
|
cancel: {
|
||||||
visible?: boolean,
|
message: string,
|
||||||
prefKey?: string,
|
icon: string | null,
|
||||||
}
|
color: string | null,
|
||||||
|
onPress?: () => void,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emotion: number,
|
||||||
|
visible?: boolean,
|
||||||
|
prefKey?: string,
|
||||||
|
};
|
||||||
|
|
||||||
type State = {
|
type StateType = {
|
||||||
shouldRenderDialog: boolean, // Used to stop rendering after hide animation
|
shouldRenderDialog: boolean, // Used to stop rendering after hide animation
|
||||||
dialogVisible: boolean,
|
dialogVisible: boolean,
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component used to display a popup with the mascot.
|
* Component used to display a popup with the mascot.
|
||||||
*/
|
*/
|
||||||
class MascotPopup extends React.Component<Props, State> {
|
class MascotPopup extends React.Component<PropsType, StateType> {
|
||||||
|
static defaultProps = {
|
||||||
|
visible: null,
|
||||||
|
prefKey: null,
|
||||||
|
};
|
||||||
|
|
||||||
mascotSize: number;
|
mascotSize: number;
|
||||||
windowWidth: number;
|
|
||||||
windowHeight: number;
|
|
||||||
|
|
||||||
constructor(props: Props) {
|
windowWidth: number;
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.windowWidth = Dimensions.get('window').width;
|
windowHeight: number;
|
||||||
this.windowHeight = Dimensions.get('window').height;
|
|
||||||
|
|
||||||
this.mascotSize = Dimensions.get('window').height / 6;
|
constructor(props: PropsType) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
if (this.props.visible != null) {
|
this.windowWidth = Dimensions.get('window').width;
|
||||||
this.state = {
|
this.windowHeight = Dimensions.get('window').height;
|
||||||
shouldRenderDialog: this.props.visible,
|
|
||||||
dialogVisible: this.props.visible,
|
|
||||||
};
|
|
||||||
} else if (this.props.prefKey != null) {
|
|
||||||
const visible = AsyncStorageManager.getBool(this.props.prefKey);
|
|
||||||
this.state = {
|
|
||||||
shouldRenderDialog: visible,
|
|
||||||
dialogVisible: visible,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
this.state = {
|
|
||||||
shouldRenderDialog: false,
|
|
||||||
dialogVisible: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
this.mascotSize = Dimensions.get('window').height / 6;
|
||||||
|
|
||||||
|
if (props.visible != null) {
|
||||||
|
this.state = {
|
||||||
|
shouldRenderDialog: props.visible,
|
||||||
|
dialogVisible: props.visible,
|
||||||
|
};
|
||||||
|
} else if (props.prefKey != null) {
|
||||||
|
const visible = AsyncStorageManager.getBool(props.prefKey);
|
||||||
|
this.state = {
|
||||||
|
shouldRenderDialog: visible,
|
||||||
|
dialogVisible: visible,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
this.state = {
|
||||||
|
shouldRenderDialog: false,
|
||||||
|
dialogVisible: false,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onAnimationEnd = () => {
|
componentDidMount(): * {
|
||||||
this.setState({
|
BackHandler.addEventListener(
|
||||||
shouldRenderDialog: false,
|
'hardwareBackPress',
|
||||||
})
|
this.onBackButtonPressAndroid,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldComponentUpdate(nextProps: PropsType, nextState: StateType): boolean {
|
||||||
|
const {props, state} = this;
|
||||||
|
if (nextProps.visible) {
|
||||||
|
this.state.shouldRenderDialog = true;
|
||||||
|
this.state.dialogVisible = true;
|
||||||
|
} else if (
|
||||||
|
nextProps.visible !== props.visible ||
|
||||||
|
(!nextState.dialogVisible &&
|
||||||
|
nextState.dialogVisible !== state.dialogVisible)
|
||||||
|
) {
|
||||||
|
this.state.dialogVisible = false;
|
||||||
|
setTimeout(this.onAnimationEnd, 300);
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps: Props, nextState: State): boolean {
|
onAnimationEnd = () => {
|
||||||
if (nextProps.visible) {
|
this.setState({
|
||||||
this.state.shouldRenderDialog = true;
|
shouldRenderDialog: false,
|
||||||
this.state.dialogVisible = true;
|
});
|
||||||
} else if (nextProps.visible !== this.props.visible
|
};
|
||||||
|| (!nextState.dialogVisible && nextState.dialogVisible !== this.state.dialogVisible)) {
|
|
||||||
this.state.dialogVisible = false;
|
onBackButtonPressAndroid = (): boolean => {
|
||||||
setTimeout(this.onAnimationEnd, 300);
|
const {state, props} = this;
|
||||||
}
|
if (state.dialogVisible) {
|
||||||
return true;
|
const {cancel} = props.buttons;
|
||||||
|
const {action} = props.buttons;
|
||||||
|
if (cancel != null) this.onDismiss(cancel.onPress);
|
||||||
|
else this.onDismiss(action.onPress);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
componentDidMount(): * {
|
getSpeechBubble(): React.Node {
|
||||||
BackHandler.addEventListener(
|
const {state, props} = this;
|
||||||
'hardwareBackPress',
|
return (
|
||||||
this.onBackButtonPressAndroid
|
<Animatable.View
|
||||||
)
|
style={{
|
||||||
}
|
marginLeft: '10%',
|
||||||
|
marginRight: '10%',
|
||||||
onBackButtonPressAndroid = () => {
|
}}
|
||||||
if (this.state.dialogVisible) {
|
useNativeDriver
|
||||||
const cancel = this.props.buttons.cancel;
|
animation={state.dialogVisible ? 'bounceInLeft' : 'bounceOutLeft'}
|
||||||
const action = this.props.buttons.action;
|
duration={state.dialogVisible ? 1000 : 300}>
|
||||||
if (cancel != null)
|
<SpeechArrow
|
||||||
this.onDismiss(cancel.onPress);
|
style={{marginLeft: this.mascotSize / 3}}
|
||||||
else
|
size={20}
|
||||||
this.onDismiss(action.onPress);
|
color={props.theme.colors.mascotMessageArrow}
|
||||||
return true;
|
/>
|
||||||
} else {
|
<Card
|
||||||
return false;
|
style={{
|
||||||
}
|
borderColor: props.theme.colors.mascotMessageArrow,
|
||||||
};
|
borderWidth: 4,
|
||||||
|
borderRadius: 10,
|
||||||
getSpeechBubble() {
|
}}>
|
||||||
return (
|
<Card.Title
|
||||||
<Animatable.View
|
title={props.title}
|
||||||
style={{
|
left={
|
||||||
marginLeft: "10%",
|
props.icon != null
|
||||||
marginRight: "10%",
|
? (): React.Node => (
|
||||||
}}
|
<Avatar.Icon
|
||||||
useNativeDriver={true}
|
size={48}
|
||||||
animation={this.state.dialogVisible ? "bounceInLeft" : "bounceOutLeft"}
|
style={{backgroundColor: 'transparent'}}
|
||||||
duration={this.state.dialogVisible ? 1000 : 300}
|
color={props.theme.colors.primary}
|
||||||
>
|
icon={props.icon}
|
||||||
<SpeechArrow
|
|
||||||
style={{marginLeft: this.mascotSize / 3}}
|
|
||||||
size={20}
|
|
||||||
color={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}
|
|
||||||
size={48}
|
|
||||||
style={{backgroundColor: "transparent"}}
|
|
||||||
color={this.props.theme.colors.primary}
|
|
||||||
icon={this.props.icon}
|
|
||||||
/>
|
|
||||||
|
|
||||||
: null}
|
|
||||||
/>
|
/>
|
||||||
|
)
|
||||||
<Card.Content style={{
|
: null
|
||||||
maxHeight: this.windowHeight / 3
|
}
|
||||||
}}>
|
/>
|
||||||
<ScrollView>
|
<Card.Content
|
||||||
<Paragraph style={{marginBottom: 10}}>
|
style={{
|
||||||
{this.props.message}
|
maxHeight: this.windowHeight / 3,
|
||||||
</Paragraph>
|
|
||||||
</ScrollView>
|
|
||||||
</Card.Content>
|
|
||||||
|
|
||||||
<Card.Actions style={{marginTop: 10, marginBottom: 10}}>
|
|
||||||
{this.getButtons()}
|
|
||||||
</Card.Actions>
|
|
||||||
</Card>
|
|
||||||
</Animatable.View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
getMascot() {
|
|
||||||
return (
|
|
||||||
<Animatable.View
|
|
||||||
useNativeDriver={true}
|
|
||||||
animation={this.state.dialogVisible ? "bounceInLeft" : "bounceOutLeft"}
|
|
||||||
duration={this.state.dialogVisible ? 1500 : 200}
|
|
||||||
>
|
|
||||||
<Mascot
|
|
||||||
style={{width: 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={{
|
|
||||||
marginLeft: "auto",
|
|
||||||
marginRight: "auto",
|
|
||||||
marginTop: "auto",
|
|
||||||
marginBottom: "auto",
|
|
||||||
}}>
|
}}>
|
||||||
{action != null
|
<ScrollView>
|
||||||
? <Button
|
<Paragraph style={{marginBottom: 10}}>{props.message}</Paragraph>
|
||||||
style={{
|
</ScrollView>
|
||||||
marginLeft: 'auto',
|
</Card.Content>
|
||||||
marginRight: 'auto',
|
|
||||||
marginBottom: 10,
|
<Card.Actions style={{marginTop: 10, marginBottom: 10}}>
|
||||||
}}
|
{this.getButtons()}
|
||||||
mode={"contained"}
|
</Card.Actions>
|
||||||
icon={action.icon}
|
</Card>
|
||||||
color={action.color}
|
</Animatable.View>
|
||||||
onPress={() => this.onDismiss(action.onPress)}
|
);
|
||||||
>
|
}
|
||||||
{action.message}
|
|
||||||
</Button>
|
getMascot(): React.Node {
|
||||||
: null}
|
const {props, state} = this;
|
||||||
{cancel != null
|
return (
|
||||||
? <Button
|
<Animatable.View
|
||||||
style={{
|
useNativeDriver
|
||||||
marginLeft: 'auto',
|
animation={state.dialogVisible ? 'bounceInLeft' : 'bounceOutLeft'}
|
||||||
marginRight: 'auto',
|
duration={state.dialogVisible ? 1500 : 200}>
|
||||||
}}
|
<Mascot
|
||||||
mode={"contained"}
|
style={{width: this.mascotSize}}
|
||||||
icon={cancel.icon}
|
animated
|
||||||
color={cancel.color}
|
emotion={props.emotion}
|
||||||
onPress={() => this.onDismiss(cancel.onPress)}
|
/>
|
||||||
>
|
</Animatable.View>
|
||||||
{cancel.message}
|
);
|
||||||
</Button>
|
}
|
||||||
: null}
|
|
||||||
|
getButtons(): React.Node {
|
||||||
|
const {props} = this;
|
||||||
|
const {action} = props.buttons;
|
||||||
|
const {cancel} = props.buttons;
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
marginLeft: 'auto',
|
||||||
|
marginRight: 'auto',
|
||||||
|
marginTop: 'auto',
|
||||||
|
marginBottom: 'auto',
|
||||||
|
}}>
|
||||||
|
{action != null ? (
|
||||||
|
<Button
|
||||||
|
style={{
|
||||||
|
marginLeft: 'auto',
|
||||||
|
marginRight: 'auto',
|
||||||
|
marginBottom: 10,
|
||||||
|
}}
|
||||||
|
mode="contained"
|
||||||
|
icon={action.icon}
|
||||||
|
color={action.color}
|
||||||
|
onPress={() => {
|
||||||
|
this.onDismiss(action.onPress);
|
||||||
|
}}>
|
||||||
|
{action.message}
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
|
{cancel != null ? (
|
||||||
|
<Button
|
||||||
|
style={{
|
||||||
|
marginLeft: 'auto',
|
||||||
|
marginRight: 'auto',
|
||||||
|
}}
|
||||||
|
mode="contained"
|
||||||
|
icon={cancel.icon}
|
||||||
|
color={cancel.color}
|
||||||
|
onPress={() => {
|
||||||
|
this.onDismiss(cancel.onPress);
|
||||||
|
}}>
|
||||||
|
{cancel.message}
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getBackground(): React.Node {
|
||||||
|
const {props, state} = this;
|
||||||
|
return (
|
||||||
|
<TouchableWithoutFeedback
|
||||||
|
onPress={() => {
|
||||||
|
this.onDismiss(props.buttons.cancel.onPress);
|
||||||
|
}}>
|
||||||
|
<Animatable.View
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
backgroundColor: 'rgba(0,0,0,0.7)',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
}}
|
||||||
|
useNativeDriver
|
||||||
|
animation={state.dialogVisible ? 'fadeIn' : 'fadeOut'}
|
||||||
|
duration={state.dialogVisible ? 300 : 300}
|
||||||
|
/>
|
||||||
|
</TouchableWithoutFeedback>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onDismiss = (callback?: () => void) => {
|
||||||
|
const {prefKey} = this.props;
|
||||||
|
if (prefKey != null) {
|
||||||
|
AsyncStorageManager.set(prefKey, false);
|
||||||
|
this.setState({dialogVisible: false});
|
||||||
|
}
|
||||||
|
if (callback != null) callback();
|
||||||
|
};
|
||||||
|
|
||||||
|
render(): React.Node {
|
||||||
|
const {shouldRenderDialog} = this.state;
|
||||||
|
if (shouldRenderDialog) {
|
||||||
|
return (
|
||||||
|
<Portal>
|
||||||
|
{this.getBackground()}
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
marginTop: 'auto',
|
||||||
|
marginBottom: 'auto',
|
||||||
|
}}>
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
marginTop: -80,
|
||||||
|
width: '100%',
|
||||||
|
}}>
|
||||||
|
{this.getMascot()}
|
||||||
|
{this.getSpeechBubble()}
|
||||||
</View>
|
</View>
|
||||||
);
|
</View>
|
||||||
}
|
</Portal>
|
||||||
|
);
|
||||||
getBackground() {
|
|
||||||
return (
|
|
||||||
<TouchableWithoutFeedback onPress={() => this.onDismiss(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.state.dialogVisible ? "fadeIn" : "fadeOut"}
|
|
||||||
duration={this.state.dialogVisible ? 300 : 300}
|
|
||||||
/>
|
|
||||||
</TouchableWithoutFeedback>
|
|
||||||
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
onDismiss = (callback?: ()=> void) => {
|
|
||||||
if (this.props.prefKey != null) {
|
|
||||||
AsyncStorageManager.set(this.props.prefKey, false);
|
|
||||||
this.setState({dialogVisible: false});
|
|
||||||
}
|
|
||||||
if (callback != null)
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
if (this.state.shouldRenderDialog) {
|
|
||||||
return (
|
|
||||||
<Portal>
|
|
||||||
{this.getBackground()}
|
|
||||||
<View style={{
|
|
||||||
marginTop: "auto",
|
|
||||||
marginBottom: "auto",
|
|
||||||
}}>
|
|
||||||
<View style={{
|
|
||||||
marginTop: -80,
|
|
||||||
width: "100%"
|
|
||||||
}}>
|
|
||||||
{this.getMascot()}
|
|
||||||
{this.getSpeechBubble()}
|
|
||||||
</View>
|
|
||||||
|
|
||||||
</View>
|
|
||||||
</Portal>
|
|
||||||
);
|
|
||||||
} else
|
|
||||||
return null;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withTheme(MascotPopup);
|
export default withTheme(MascotPopup);
|
||||||
|
|
|
@ -1,33 +1,43 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {View} from "react-native";
|
import {View} from 'react-native';
|
||||||
import type {ViewStyle} from "react-native/Libraries/StyleSheet/StyleSheet";
|
import type {ViewStyle} from 'react-native/Libraries/StyleSheet/StyleSheet';
|
||||||
|
|
||||||
type Props = {
|
type PropsType = {
|
||||||
style?: ViewStyle,
|
style?: ViewStyle | null,
|
||||||
size: number,
|
size: number,
|
||||||
color: string,
|
color: string,
|
||||||
}
|
};
|
||||||
|
|
||||||
export default class SpeechArrow extends React.Component<Props> {
|
export default class SpeechArrow extends React.Component<PropsType> {
|
||||||
|
static defaultProps = {
|
||||||
render() {
|
style: null,
|
||||||
return (
|
};
|
||||||
<View style={this.props.style}>
|
|
||||||
<View style={{
|
shouldComponentUpdate(): boolean {
|
||||||
width: 0,
|
return false;
|
||||||
height: 0,
|
}
|
||||||
borderLeftWidth: 0,
|
|
||||||
borderRightWidth: this.props.size,
|
render(): React.Node {
|
||||||
borderBottomWidth: this.props.size,
|
const {props} = this;
|
||||||
borderStyle: 'solid',
|
return (
|
||||||
backgroundColor: 'transparent',
|
<View style={props.style}>
|
||||||
borderLeftColor: 'transparent',
|
<View
|
||||||
borderRightColor: 'transparent',
|
style={{
|
||||||
borderBottomColor: this.props.color,
|
width: 0,
|
||||||
}}/>
|
height: 0,
|
||||||
</View>
|
borderLeftWidth: 0,
|
||||||
);
|
borderRightWidth: props.size,
|
||||||
}
|
borderBottomWidth: props.size,
|
||||||
|
borderStyle: 'solid',
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
borderLeftColor: 'transparent',
|
||||||
|
borderRightColor: 'transparent',
|
||||||
|
borderBottomColor: props.color,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue