diff --git a/locales/en.json b/locales/en.json index 6ecb6cf..b8bfbd7 100644 --- a/locales/en.json +++ b/locales/en.json @@ -379,18 +379,10 @@ "title": "Stay up to date", "text": "CAMPUS allows you to be aware of any event occurring on the campus, from pancake sales to Enfoiros concerts!" }, - "slideProxiwash": { - "title": "Never forget your laundry", - "text": "CAMPUS will inform you on the availability of washing machines and will remind you just before yours finishes!" - }, "slidePlanex": { "title": "Planex", "text": "Lookup your next course on CAMPUS with a mobile friendly timetable" }, - "slideRU": { - "title": "RU Menu", - "text": "For the hungry, check this week's menu!" - }, "slideServices": { "title": "More services!", "text": "You can do much more with CAMPUS, explore the app to find out" diff --git a/locales/fr.json b/locales/fr.json index beec7fd..c8d9524 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -372,32 +372,24 @@ }, "intro": { "slideMain": { - "title": "Bienvenue sur CAMPUS", - "text": "La nouvelle appli à consulter pendant la pause café pour être au courant de la vie du campus !" - }, - "slideEvents": { - "title": "Restez informés", - "text": "CAMPUS vous permet d'être au courant de tous les événements qui ont lieu sur le campus, de la vente de crêpes jusqu'aux concerts enfoiros !" - }, - "slideProxiwash": { - "title": "N'oubliez plus votre linge !", - "text": "CAMPUS vous informe de la disponibilité des machines et vous permet d'être notifié lorsque la vôtre se termine bientôt !" + "title": "Bienvenue sur CAMPUS !", + "text": "L'appli du campus de l'INSA Toulouse ! Laisse toi guider pour comprendre tout ce que tu peux faire." }, "slidePlanex": { - "title": "Planex", - "text": "Vérifiez votre prochain cours sur CAMPUS avec un emploi du temps adapté mobile" + "title": "Planex tout beau", + "text": "Regarde ton emploi du temps et celui de tes amis avec un Planex adapté mobile !" }, - "slideRU": { - "title": "Menu du RU", - "text": "Pour ceux qui ont faim, vérifiez le menu du RU de la semaine !" + "slideEvents": { + "title": "Plein d'infos", + "text": "Sois au courant de tout ce qui se passe sur le campus, de la vente de crêpes jusqu'aux concerts Enfoiros !" }, "slideServices": { - "title": "Encore plus de services !", - "text": "CAMPUS vous permet de faire bien plus, explorez l'appli pour savoir quoi" + "title": "Et plus encore !", + "text": "Tu peux faire bien plus avec CAMPUS, mais je n'ai pas le temps de tout dire ici. Balade toi sur l'appli pour tout découvrir !" }, "slideDone": { - "title": "Fait par un étudiant", - "text": "Cette appli à été réalisée par un seul étudiant (avec un peu d'aide par-ci par-là), donc tous les retours sont les bienvenus !" + "title": "Réalisé par un étudiant", + "text": "Cette appli à été réalisée par un seul étudiant (avec un peu d'aide par-ci par-là), donc tes retours sont les bienvenus !" }, "updateSlide0": { "title": "Nouveau dans cette mise à jour !", diff --git a/src/components/Mascot/Mascot.js b/src/components/Mascot/Mascot.js index 6977779..d29528c 100644 --- a/src/components/Mascot/Mascot.js +++ b/src/components/Mascot/Mascot.js @@ -2,26 +2,46 @@ import * as React from 'react'; import * as Animatable from "react-native-animatable"; -import {Image, View} from "react-native-animatable"; +import {Image, TouchableWithoutFeedback, View} from "react-native"; type Props = { size: number, emotion: number, animated: boolean, + entryAnimation: Animatable.AnimatableProperties | null, + loopAnimation: Animatable.AnimatableProperties | null, + onPress?: (viewRef: AnimatableViewRef) => null, + onLongPress?: (viewRef: AnimatableViewRef) => null, } +type State = { + currentEmotion: number, +} + +export type AnimatableViewRef = {current: null | Animatable.View}; + 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"); 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 = { @@ -31,30 +51,96 @@ export const MASCOT_STYLE = { WINK: 3, CUTE: 4, INTELLO: 5, + LOVE: 6, + COOL: 7, + ANGRY: 8, }; -class Mascot extends React.Component { +class Mascot extends React.Component { - static defaultProps = { - animated: false + state = { + currentEmotion: this.props.emotion } - eyeList: { [key: number]: number | string } + static defaultProps = { + animated: false, + entryAnimation: { + useNativeDriver: true, + animation: "rubberBand", + duration: 2000, + }, + loopAnimation: { + useNativeDriver: true, + animation: "swing", + duration: 2000, + iterationDelay: 250, + iterationCount: "infinite", + }, + clickAnimation: { + useNativeDriver: true, + animation: "rubberBand", + duration: 2000, + }, + } + + viewRef: AnimatableViewRef; + eyeList: { [key: number]: number | string }; + glassesList: { [key: number]: number | string }; + + onPress: (viewRef: AnimatableViewRef) => null; + onLongPress: (viewRef: AnimatableViewRef) => null; constructor(props: Props) { 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; + + if (this.props.onPress == null) { + this.onPress = (viewRef: AnimatableViewRef) => { + if (viewRef.current != null) { + this.setState({currentEmotion: MASCOT_STYLE.LOVE}); + viewRef.current.rubberBand(1500).then(() => { + this.setState({currentEmotion: this.props.emotion}); + }); + + } + return null; + } + } else + this.onPress = this.props.onPress; + + if (this.props.onLongPress == null) { + this.onLongPress = (viewRef: AnimatableViewRef) => { + if (viewRef.current != null) { + this.setState({currentEmotion: MASCOT_STYLE.ANGRY}); + viewRef.current.tada(1000).then(() => { + this.setState({currentEmotion: this.props.emotion}); + }); + + } + return null; + } + } else + this.onLongPress = this.props.onLongPress; + } - getGlasses() { + getGlasses(style: number) { + const glasses = this.glassesList[style]; return { /> } - getEye(style: number, isRight: boolean) { + getEye(style: number, isRight: boolean, rotation: string="0deg") { const eye = this.eyeList[style]; return { left: isRight ? "-11%" : "11%", width: this.props.size, height: this.props.size, + transform: [{rotateY: rotation}] }} /> } @@ -85,10 +172,10 @@ class Mascot extends React.Component { final.push(); + 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)); @@ -101,13 +188,21 @@ class Mascot extends React.Component { } 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) { - final.push(this.getGlasses()) + if (emotion === MASCOT_STYLE.INTELLO) { // Needs to have normal eyes behind the glasses + final.push(this.getGlasses(GLASSES_STYLE.NORMAL)); } final.push(); return final; @@ -115,31 +210,37 @@ class Mascot extends React.Component { render() { const size = this.props.size; + const entryAnimation = this.props.animated ? this.props.entryAnimation : null; + const loopAnimation = this.props.animated ? this.props.loopAnimation : null; return ( - this.onPress(this.viewRef)} + onLongPress={() => this.onLongPress(this.viewRef)} > - - {this.getEyes(this.props.emotion)} - + + + + {this.getEyes(this.state.currentEmotion)} + + + ); } diff --git a/src/components/Overrides/CustomIntroSlider.js b/src/components/Overrides/CustomIntroSlider.js index 86a3650..2925602 100644 --- a/src/components/Overrides/CustomIntroSlider.js +++ b/src/components/Overrides/CustomIntroSlider.js @@ -1,14 +1,17 @@ // @flow import * as React from 'react'; -import {Image, Platform, StatusBar, StyleSheet, View} from "react-native"; +import {Platform, StatusBar, StyleSheet, View} from "react-native"; +import type {MaterialCommunityIconsGlyphs} from "react-native-vector-icons/MaterialCommunityIcons"; import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons"; -import {Text} from "react-native-paper"; import i18n from 'i18n-js'; import AppIntroSlider from "react-native-app-intro-slider"; import Update from "../../constants/Update"; import ThemeManager from "../../managers/ThemeManager"; import LinearGradient from 'react-native-linear-gradient'; +import Mascot, {MASCOT_STYLE} from "../Mascot/Mascot"; +import * as Animatable from "react-native-animatable"; +import {Card} from "react-native-paper"; type Props = { onDone: Function, @@ -16,17 +19,34 @@ type Props = { isAprilFools: boolean, }; +type State = { + currentSlide: number, +} + +type Slide = { + key: string, + title: string, + text: string, + view: () => React.Node, + mascotStyle: number, + colors: [string, string] +}; + /** * Class used to create intro slides */ -export default class CustomIntroSlider extends React.Component { +export default class CustomIntroSlider extends React.Component { - sliderRef: {current: null | AppIntroSlider}; + state = { + currentSlide: 0, + } - introSlides: Array; - updateSlides: Array; - aprilFoolsSlides: Array; - currentSlides: Array; + sliderRef: { current: null | AppIntroSlider }; + + introSlides: Array; + updateSlides: Array; + aprilFoolsSlides: Array; + currentSlides: Array; /** * Generates intro slides @@ -36,53 +56,44 @@ export default class CustomIntroSlider extends React.Component { this.sliderRef = React.createRef(); this.introSlides = [ { - key: 'main', + key: '0', // Mascot title: i18n.t('intro.slideMain.title'), text: i18n.t('intro.slideMain.text'), - image: require('../../../assets/splash.png'), - colors: ['#be1522', '#740d15'], + view: this.getWelcomeView, + mascotStyle: MASCOT_STYLE.NORMAL, + colors: ['#be1522', '#57080e'], }, { - key: 'Planex', + key: '1', title: i18n.t('intro.slidePlanex.title'), text: i18n.t('intro.slidePlanex.text'), - icon: 'timetable', - colors: ['#e77020', '#803e12'], + view: () => this.getIconView("calendar-clock"), + mascotStyle: MASCOT_STYLE.INTELLO, + colors: ['#be1522', '#57080e'], }, { - key: 'RU', - title: i18n.t('intro.slideRU.title'), - text: i18n.t('intro.slideRU.text'), - icon: 'silverware-fork-knife', - colors: ['#dcac18', '#8b6a15'], - }, - { - key: 'events', + key: '2', title: i18n.t('intro.slideEvents.title'), text: i18n.t('intro.slideEvents.text'), - icon: 'calendar-range', - colors: ['#41a006', '#095c03'], + view: () => this.getIconView("calendar-star",), + mascotStyle: MASCOT_STYLE.HAPPY, + colors: ['#be1522', '#57080e'], }, { - key: 'proxiwash', - title: i18n.t('intro.slideProxiwash.title'), - text: i18n.t('intro.slideProxiwash.text'), - icon: 'washing-machine', - colors: ['#1fa5ee', '#06537d'], - }, - { - key: 'services', + key: '3', title: i18n.t('intro.slideServices.title'), text: i18n.t('intro.slideServices.text'), - icon: 'view-dashboard-variant', - colors: ['#6737c1', '#281a5a'], + view: () => this.getIconView("view-dashboard-variant",), + mascotStyle: MASCOT_STYLE.CUTE, + colors: ['#be1522', '#57080e'], }, { - key: 'done', + key: '4', title: i18n.t('intro.slideDone.title'), text: i18n.t('intro.slideDone.text'), - icon: 'account-heart', - colors: ['#b837c1', '#501a5a'], + view: () => this.getIconView("account-heart",), + mascotStyle: MASCOT_STYLE.COOL, + colors: ['#9c165b', '#3e042b'], }, ]; this.updateSlides = []; @@ -103,50 +114,127 @@ export default class CustomIntroSlider extends React.Component { key: '1', title: i18n.t('intro.aprilFoolsSlide.title'), text: i18n.t('intro.aprilFoolsSlide.text'), - icon: 'fish', + view: () => , + mascotStyle: MASCOT_STYLE.NORMAL, colors: ['#e01928', '#be1522'], }, ]; } - /** * Render item to be used for the intro introSlides * * @param item The item to be displayed * @param dimensions Dimensions of the item */ - static getIntroRenderItem({item, dimensions}: Object) { + getIntroRenderItem = ({item, dimensions}: { item: Slide, dimensions: { width: number, height: number } }) => { + const index = parseInt(item.key); return ( - {item.image !== undefined ? - - - - : } - - {item.title} - {item.text} - + {this.state.currentSlide === index + ? + + {item.view()} + + + {index !== 0 + ? + + : null} + + + + + + {item.title} + + + {item.text} + + + + + : null} ); } + getWelcomeView = () => { + return ( + + + + + + ) + } + + getIconView(icon: MaterialCommunityIconsGlyphs) { + return ( + + + + + + ) + } + setStatusBarColor(color: string) { if (Platform.OS === 'android') StatusBar.setBackgroundColor(color, true); @@ -154,12 +242,13 @@ export default class CustomIntroSlider extends React.Component { onSlideChange = (index: number, lastIndex: number) => { this.setStatusBarColor(this.currentSlides[index].colors[0]); + this.setState({currentSlide: index}); }; onSkip = () => { - this.setStatusBarColor(this.currentSlides[this.currentSlides.length-1].colors[0]); + this.setStatusBarColor(this.currentSlides[this.currentSlides.length - 1].colors[0]); if (this.sliderRef.current != null) - this.sliderRef.current.goToSlide(this.currentSlides.length-1); + this.sliderRef.current.goToSlide(this.currentSlides.length - 1); } onDone = () => { @@ -167,6 +256,42 @@ export default class CustomIntroSlider extends React.Component { this.props.onDone(); } + renderNextButton = () => { + return ( + + + + ) + } + + renderDoneButton = () => { + return ( + + + + ) + } + render() { this.currentSlides = this.introSlides; if (this.props.isUpdate) @@ -177,16 +302,16 @@ export default class CustomIntroSlider extends React.Component { return ( ); } @@ -196,15 +321,7 @@ export default class CustomIntroSlider extends React.Component { const styles = StyleSheet.create({ mainContent: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - paddingBottom: 100 - }, - image: { - width: 300, - height: 300, - marginBottom: -50, + paddingBottom: 100, }, text: { color: 'rgba(255, 255, 255, 0.8)', @@ -219,4 +336,10 @@ const styles = StyleSheet.create({ textAlign: 'center', marginBottom: 16, }, + center: { + marginTop: 'auto', + marginBottom: 'auto', + marginRight: 'auto', + marginLeft: 'auto', + } });