From 172b7e81877b9e9634e6759bfbaaa16b697f4de5 Mon Sep 17 00:00:00 2001 From: Arnaud Vergnet Date: Tue, 22 Sep 2020 18:25:19 +0200 Subject: [PATCH] Update mascot components to use TypeScript --- .../Mascot/{Mascot.js => Mascot.tsx} | 139 +++++++++--------- .../{MascotPopup.js => MascotPopup.tsx} | 84 +++++------ src/components/Mascot/SpeechArrow.js | 62 -------- .../Mascot/SpeechArrow.tsx} | 35 +++-- 4 files changed, 137 insertions(+), 183 deletions(-) rename src/components/Mascot/{Mascot.js => Mascot.tsx} (75%) rename src/components/Mascot/{MascotPopup.js => MascotPopup.tsx} (87%) delete mode 100644 src/components/Mascot/SpeechArrow.js rename src/{constants/PaperStyles.js => components/Mascot/SpeechArrow.tsx} (52%) diff --git a/src/components/Mascot/Mascot.js b/src/components/Mascot/Mascot.tsx similarity index 75% rename from src/components/Mascot/Mascot.js rename to src/components/Mascot/Mascot.tsx index 7ea5b49..6ad77d6 100644 --- a/src/components/Mascot/Mascot.js +++ b/src/components/Mascot/Mascot.tsx @@ -17,27 +17,27 @@ * along with Campus INSAT. If not, see . */ -// @flow - import * as React from 'react'; import * as Animatable from 'react-native-animatable'; -import {Image, TouchableWithoutFeedback, View} from 'react-native'; -import type {ViewStyle} from 'react-native/Libraries/StyleSheet/StyleSheet'; +import {Image, TouchableWithoutFeedback, View, ViewStyle} from 'react-native'; +import {AnimatableProperties} from 'react-native-animatable'; -export type AnimatableViewRefType = {current: null | Animatable.View}; +export type AnimatableViewRefType = { + current: null | (typeof Animatable.View & View); +}; type PropsType = { - emotion?: number, - animated?: boolean, - style?: ViewStyle | null, - entryAnimation?: Animatable.AnimatableProperties | null, - loopAnimation?: Animatable.AnimatableProperties | null, - onPress?: null | ((viewRef: AnimatableViewRefType) => void), - onLongPress?: null | ((viewRef: AnimatableViewRefType) => void), + emotion?: MASCOT_STYLE; + animated?: boolean; + style?: ViewStyle; + entryAnimation?: AnimatableProperties; + loopAnimation?: AnimatableProperties; + onPress?: null | ((viewRef: AnimatableViewRefType) => void); + onLongPress?: null | ((viewRef: AnimatableViewRefType) => void); }; type StateType = { - currentEmotion: number, + currentEmotion: MASCOT_STYLE; }; const MASCOT_IMAGE = require('../../../assets/mascot/mascot.png'); @@ -50,32 +50,32 @@ 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, -}; +enum EYE_STYLE { + NORMAL, + GIRLY, + CUTE, + WINK, + HEART, + ANGRY, +} -const GLASSES_STYLE = { - NORMAL: 0, - COOl: 1, -}; +enum GLASSES_STYLE { + NORMAL, + COOl, +} -export const MASCOT_STYLE = { - NORMAL: 0, - HAPPY: 1, - GIRLY: 2, - WINK: 3, - CUTE: 4, - INTELLO: 5, - LOVE: 6, - COOL: 7, - ANGRY: 8, - RANDOM: 999, -}; +export enum MASCOT_STYLE { + NORMAL, + HAPPY, + GIRLY, + WINK, + CUTE, + INTELLO, + LOVE, + COOL, + ANGRY, + RANDOM = 999, +} class Mascot extends React.Component { static defaultProps = { @@ -100,9 +100,9 @@ class Mascot extends React.Component { viewRef: AnimatableViewRefType; - eyeList: {[key: number]: number | string}; + eyeList: {[key in EYE_STYLE]: number}; - glassesList: {[key: number]: number | string}; + glassesList: {[key in GLASSES_STYLE]: number}; onPress: (viewRef: AnimatableViewRefType) => void; @@ -113,23 +113,25 @@ class Mascot extends React.Component { 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.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; - 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) + if (this.initialEmotion === MASCOT_STYLE.RANDOM) { this.initialEmotion = Math.floor(Math.random() * MASCOT_STYLE.ANGRY) + 1; + } this.state = { currentEmotion: this.initialEmotion, @@ -138,29 +140,33 @@ class Mascot extends React.Component { if (props.onPress == null) { this.onPress = (viewRef: AnimatableViewRefType) => { const ref = viewRef.current; - if (ref != null) { + if (ref && ref.rubberBand) { this.setState({currentEmotion: MASCOT_STYLE.LOVE}); ref.rubberBand(1500).then(() => { this.setState({currentEmotion: this.initialEmotion}); }); } }; - } else this.onPress = props.onPress; + } else { + this.onPress = props.onPress; + } if (props.onLongPress == null) { this.onLongPress = (viewRef: AnimatableViewRefType) => { const ref = viewRef.current; - if (ref != null) { + if (ref && ref.tada) { this.setState({currentEmotion: MASCOT_STYLE.ANGRY}); ref.tada(1000).then(() => { this.setState({currentEmotion: this.initialEmotion}); }); } }; - } else this.onLongPress = props.onLongPress; + } else { + this.onLongPress = props.onLongPress; + } } - getGlasses(style: number): React.Node { + getGlasses(style: GLASSES_STYLE) { const glasses = this.glassesList[style]; return ( { ); } - getEye( - style: number, - isRight: boolean, - rotation: string = '0deg', - ): React.Node { + getEye(style: EYE_STYLE, isRight: boolean, rotation: string = '0deg') { const eye = this.eyeList[style]; return ( { ); } - getEyes(emotion: number): React.Node { + getEyes(emotion: MASCOT_STYLE) { const final = []; final.push( { return final; } - render(): React.Node { + render() { const {props, state} = this; const entryAnimation = props.animated ? props.entryAnimation : null; const loopAnimation = props.animated ? props.loopAnimation : null; @@ -256,7 +258,6 @@ class Mascot extends React.Component { aspectRatio: 1, ...props.style, }} - // eslint-disable-next-line react/jsx-props-no-spreading {...entryAnimation}> { @@ -266,9 +267,7 @@ class Mascot extends React.Component { this.onLongPress(this.viewRef); }}> - + . */ -// @flow - import * as React from 'react'; import { Avatar, @@ -37,48 +35,42 @@ import { View, } from 'react-native'; import Mascot from './Mascot'; -import type {CustomThemeType} from '../../managers/ThemeManager'; import SpeechArrow from './SpeechArrow'; import AsyncStorageManager from '../../managers/AsyncStorageManager'; type PropsType = { - theme: CustomThemeType, - icon: string, - title: string, - message: string, + theme: ReactNativePaper.Theme; + 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, - visible?: boolean, - prefKey?: string, + action?: { + message: string; + icon?: string; + color?: string; + onPress?: () => void; + }; + cancel?: { + message: string; + icon?: string; + color?: string; + onPress?: () => void; + }; + }; + emotion: number; + visible?: boolean; + prefKey?: string; }; type StateType = { - shouldRenderDialog: boolean, // Used to stop rendering after hide animation - dialogVisible: boolean, + shouldRenderDialog: boolean; // Used to stop rendering after hide animation + dialogVisible: boolean; }; /** * Component used to display a popup with the mascot. */ class MascotPopup extends React.Component { - static defaultProps = { - visible: null, - prefKey: null, - }; - mascotSize: number; windowWidth: number; @@ -112,7 +104,7 @@ class MascotPopup extends React.Component { } } - componentDidMount(): * { + componentDidMount() { BackHandler.addEventListener( 'hardwareBackPress', this.onBackButtonPressAndroid, @@ -146,14 +138,20 @@ class MascotPopup extends React.Component { if (state.dialogVisible) { const {cancel} = props.buttons; const {action} = props.buttons; - if (cancel != null) this.onDismiss(cancel.onPress); - else this.onDismiss(action.onPress); + if (cancel) { + this.onDismiss(cancel.onPress); + } else if (action) { + this.onDismiss(action.onPress); + } else { + this.onDismiss(); + } + return true; } return false; }; - getSpeechBubble(): React.Node { + getSpeechBubble() { const {state, props} = this; return ( { title={props.title} left={ props.icon != null - ? (): React.Node => ( + ? () => ( { icon={props.icon} /> ) - : null + : undefined } /> { ); } - getMascot(): React.Node { + getMascot() { const {props, state} = this; return ( { ); } - getButtons(): React.Node { + getButtons() { const {props} = this; const {action} = props.buttons; const {cancel} = props.buttons; @@ -270,12 +268,12 @@ class MascotPopup extends React.Component { ); } - getBackground(): React.Node { + getBackground() { const {props, state} = this; return ( { - this.onDismiss(props.buttons.cancel.onPress); + this.onDismiss(props.buttons.cancel?.onPress); }}> { AsyncStorageManager.set(prefKey, false); this.setState({dialogVisible: false}); } - if (callback != null) callback(); + if (callback != null) { + callback(); + } }; - render(): React.Node { + render() { const {shouldRenderDialog} = this.state; if (shouldRenderDialog) { return ( diff --git a/src/components/Mascot/SpeechArrow.js b/src/components/Mascot/SpeechArrow.js deleted file mode 100644 index b336878..0000000 --- a/src/components/Mascot/SpeechArrow.js +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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 . - */ - -// @flow - -import * as React from 'react'; -import {View} from 'react-native'; -import type {ViewStyle} from 'react-native/Libraries/StyleSheet/StyleSheet'; - -type PropsType = { - style?: ViewStyle | null, - size: number, - color: string, -}; - -export default class SpeechArrow extends React.Component { - static defaultProps = { - style: null, - }; - - shouldComponentUpdate(): boolean { - return false; - } - - render(): React.Node { - const {props} = this; - return ( - - - - ); - } -} diff --git a/src/constants/PaperStyles.js b/src/components/Mascot/SpeechArrow.tsx similarity index 52% rename from src/constants/PaperStyles.js rename to src/components/Mascot/SpeechArrow.tsx index 28a29d8..464a193 100644 --- a/src/constants/PaperStyles.js +++ b/src/components/Mascot/SpeechArrow.tsx @@ -17,15 +17,32 @@ * along with Campus INSAT. If not, see . */ -// @flow +import * as React from 'react'; +import {View, ViewStyle} from 'react-native'; -import type {ViewStyleProp} from 'react-native/Libraries/StyleSheet/StyleSheet'; - -export type ListIconPropsType = { - color: string, - style: ViewStyleProp, +type PropsType = { + style?: ViewStyle; + size: number; + color: string; }; -export type CardTitleIconPropsType = { - size: number, -}; +export default function SpeechArrow(props: PropsType) { + return ( + + + + ); +}