300 lines
8.3 KiB
TypeScript
300 lines
8.3 KiB
TypeScript
/*
|
|
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
|
*
|
|
* This file is part of Campus INSAT.
|
|
*
|
|
* Campus INSAT is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Campus INSAT is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
import * as React from 'react';
|
|
import * as Animatable from 'react-native-animatable';
|
|
import {
|
|
Image,
|
|
StyleSheet,
|
|
TouchableWithoutFeedback,
|
|
View,
|
|
ViewStyle,
|
|
} from 'react-native';
|
|
import { AnimatableProperties } from 'react-native-animatable';
|
|
|
|
export type AnimatableViewRefType = {
|
|
current: null | (typeof Animatable.View & View);
|
|
};
|
|
|
|
type PropsType = {
|
|
emotion?: MASCOT_STYLE;
|
|
animated?: boolean;
|
|
style?: ViewStyle;
|
|
entryAnimation?: AnimatableProperties<ViewStyle>;
|
|
loopAnimation?: AnimatableProperties<ViewStyle>;
|
|
onPress?: null | ((viewRef: AnimatableViewRefType) => void);
|
|
onLongPress?: null | ((viewRef: AnimatableViewRefType) => void);
|
|
};
|
|
|
|
type StateType = {
|
|
currentEmotion: MASCOT_STYLE;
|
|
};
|
|
|
|
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');
|
|
|
|
enum EYE_STYLE {
|
|
NORMAL,
|
|
GIRLY,
|
|
CUTE,
|
|
WINK,
|
|
HEART,
|
|
ANGRY,
|
|
}
|
|
|
|
enum GLASSES_STYLE {
|
|
NORMAL,
|
|
COOl,
|
|
}
|
|
|
|
export enum MASCOT_STYLE {
|
|
NORMAL,
|
|
HAPPY,
|
|
GIRLY,
|
|
WINK,
|
|
CUTE,
|
|
INTELLO,
|
|
LOVE,
|
|
COOL,
|
|
ANGRY,
|
|
RANDOM = 999,
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
aspectRatio: 1,
|
|
},
|
|
mascot: {
|
|
width: '100%',
|
|
height: '100%',
|
|
},
|
|
glassesImage: {
|
|
position: 'absolute',
|
|
top: '15%',
|
|
left: 0,
|
|
width: '100%',
|
|
height: '100%',
|
|
},
|
|
eyesImage: {
|
|
position: 'absolute',
|
|
top: '15%',
|
|
width: '100%',
|
|
height: '100%',
|
|
},
|
|
eyesContainer: {
|
|
position: 'absolute',
|
|
width: '100%',
|
|
height: '100%',
|
|
},
|
|
});
|
|
|
|
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,
|
|
};
|
|
|
|
viewRef: AnimatableViewRefType;
|
|
|
|
eyeList: { [key in EYE_STYLE]: number };
|
|
|
|
glassesList: { [key in GLASSES_STYLE]: number };
|
|
|
|
onPress: (viewRef: AnimatableViewRefType) => void;
|
|
|
|
onLongPress: (viewRef: AnimatableViewRefType) => void;
|
|
|
|
initialEmotion: number;
|
|
|
|
constructor(props: PropsType) {
|
|
super(props);
|
|
this.viewRef = React.createRef();
|
|
this.eyeList = {
|
|
[EYE_STYLE.NORMAL]: MASCOT_EYES_NORMAL,
|
|
[EYE_STYLE.GIRLY]: MASCOT_EYES_GIRLY,
|
|
[EYE_STYLE.CUTE]: MASCOT_EYES_CUTE,
|
|
[EYE_STYLE.WINK]: MASCOT_EYES_WINK,
|
|
[EYE_STYLE.HEART]: MASCOT_EYES_HEART,
|
|
[EYE_STYLE.ANGRY]: MASCOT_EYES_ANGRY,
|
|
};
|
|
this.glassesList = {
|
|
[GLASSES_STYLE.NORMAL]: MASCOT_GLASSES,
|
|
[GLASSES_STYLE.COOl]: MASCOT_SUNGLASSES,
|
|
};
|
|
this.initialEmotion = props.emotion
|
|
? 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 && ref.rubberBand) {
|
|
this.setState({ currentEmotion: MASCOT_STYLE.LOVE });
|
|
ref.rubberBand(1500).then(() => {
|
|
this.setState({ currentEmotion: this.initialEmotion });
|
|
});
|
|
}
|
|
};
|
|
} else {
|
|
this.onPress = props.onPress;
|
|
}
|
|
|
|
if (props.onLongPress == null) {
|
|
this.onLongPress = (viewRef: AnimatableViewRefType) => {
|
|
const ref = viewRef.current;
|
|
if (ref && ref.tada) {
|
|
this.setState({ currentEmotion: MASCOT_STYLE.ANGRY });
|
|
ref.tada(1000).then(() => {
|
|
this.setState({ currentEmotion: this.initialEmotion });
|
|
});
|
|
}
|
|
};
|
|
} else {
|
|
this.onLongPress = props.onLongPress;
|
|
}
|
|
}
|
|
|
|
getGlasses(style: GLASSES_STYLE) {
|
|
const glasses = this.glassesList[style];
|
|
return (
|
|
<Image
|
|
key="glasses"
|
|
source={
|
|
glasses != null ? glasses : this.glassesList[GLASSES_STYLE.NORMAL]
|
|
}
|
|
style={styles.glassesImage}
|
|
/>
|
|
);
|
|
}
|
|
|
|
getEye(style: EYE_STYLE, isRight: boolean, rotation: string = '0deg') {
|
|
const eye = this.eyeList[style];
|
|
const left = isRight ? '-11%' : '11%';
|
|
return (
|
|
<Image
|
|
key={isRight ? 'right' : 'left'}
|
|
source={eye != null ? eye : this.eyeList[EYE_STYLE.NORMAL]}
|
|
style={{
|
|
...styles.eyesImage,
|
|
left: left,
|
|
transform: [{ rotateY: rotation }],
|
|
}}
|
|
/>
|
|
);
|
|
}
|
|
|
|
getEyes(emotion: MASCOT_STYLE) {
|
|
const final = [];
|
|
final.push(<View key="container" style={styles.eyesContainer} />);
|
|
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));
|
|
}
|
|
|
|
if (emotion === MASCOT_STYLE.INTELLO) {
|
|
// Needs to have normal eyes behind the glasses
|
|
final.push(this.getGlasses(GLASSES_STYLE.NORMAL));
|
|
}
|
|
final.push(<View key="container2" />);
|
|
return final;
|
|
}
|
|
|
|
render() {
|
|
const { props, state } = this;
|
|
const entryAnimation = props.animated ? props.entryAnimation : null;
|
|
const loopAnimation = props.animated ? props.loopAnimation : null;
|
|
return (
|
|
<Animatable.View
|
|
style={{
|
|
...styles.container,
|
|
...props.style,
|
|
}}
|
|
{...entryAnimation}
|
|
>
|
|
<TouchableWithoutFeedback
|
|
onPress={() => {
|
|
this.onPress(this.viewRef);
|
|
}}
|
|
onLongPress={() => {
|
|
this.onLongPress(this.viewRef);
|
|
}}
|
|
>
|
|
<Animatable.View ref={this.viewRef}>
|
|
<Animatable.View {...loopAnimation}>
|
|
<Image source={MASCOT_IMAGE} style={styles.mascot} />
|
|
{this.getEyes(state.currentEmotion)}
|
|
</Animatable.View>
|
|
</Animatable.View>
|
|
</TouchableWithoutFeedback>
|
|
</Animatable.View>
|
|
);
|
|
}
|
|
}
|
|
|
|
export default Mascot;
|