Application Android et IOS pour l'amicale des élèves https://play.google.com/store/apps/details?id=fr.amicaleinsat.application
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

MascotPopup.tsx 5.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. /*
  2. * Copyright (c) 2019 - 2020 Arnaud Vergnet.
  3. *
  4. * This file is part of Campus INSAT.
  5. *
  6. * Campus INSAT is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * Campus INSAT is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
  18. */
  19. import React, { useEffect, useRef, useState } from 'react';
  20. import { Portal } from 'react-native-paper';
  21. import * as Animatable from 'react-native-animatable';
  22. import {
  23. BackHandler,
  24. Dimensions,
  25. StyleSheet,
  26. TouchableWithoutFeedback,
  27. View,
  28. } from 'react-native';
  29. import Mascot from './Mascot';
  30. import GENERAL_STYLES from '../../constants/Styles';
  31. import MascotSpeechBubble, {
  32. MascotSpeechBubbleProps,
  33. } from './MascotSpeechBubble';
  34. import { useMountEffect } from '../../utils/customHooks';
  35. import { useRoute } from '@react-navigation/core';
  36. import { useShouldShowMascot } from '../../context/preferencesContext';
  37. type PropsType = MascotSpeechBubbleProps & {
  38. emotion: number;
  39. visible?: boolean;
  40. };
  41. const styles = StyleSheet.create({
  42. background: {
  43. position: 'absolute',
  44. backgroundColor: 'rgba(0,0,0,0.7)',
  45. width: '100%',
  46. height: '100%',
  47. },
  48. container: {
  49. marginTop: -80,
  50. width: '100%',
  51. },
  52. });
  53. const MASCOT_SIZE = Dimensions.get('window').height / 6;
  54. const BUBBLE_HEIGHT = Dimensions.get('window').height / 3;
  55. /**
  56. * Component used to display a popup with the mascot.
  57. */
  58. function MascotPopup(props: PropsType) {
  59. const route = useRoute();
  60. const { shouldShow, setShouldShow } = useShouldShowMascot(route.name);
  61. const isVisible = () => {
  62. if (props.visible !== undefined) {
  63. return props.visible;
  64. } else {
  65. return shouldShow;
  66. }
  67. };
  68. const [shouldRenderDialog, setShouldRenderDialog] = useState(isVisible());
  69. const [dialogVisible, setDialogVisible] = useState(isVisible());
  70. const lastVisibleProps = useRef(props.visible);
  71. const lastVisibleState = useRef(dialogVisible);
  72. useMountEffect(() => {
  73. BackHandler.addEventListener('hardwareBackPress', onBackButtonPressAndroid);
  74. });
  75. useEffect(() => {
  76. if (props.visible && !dialogVisible) {
  77. setShouldRenderDialog(true);
  78. setDialogVisible(true);
  79. } else if (
  80. lastVisibleProps.current !== props.visible ||
  81. (!dialogVisible && dialogVisible !== lastVisibleState.current)
  82. ) {
  83. setDialogVisible(false);
  84. setTimeout(onAnimationEnd, 400);
  85. }
  86. lastVisibleProps.current = props.visible;
  87. lastVisibleState.current = dialogVisible;
  88. }, [props.visible, dialogVisible]);
  89. const onAnimationEnd = () => {
  90. setShouldRenderDialog(false);
  91. };
  92. const onBackButtonPressAndroid = (): boolean => {
  93. if (dialogVisible) {
  94. const { cancel } = props.buttons;
  95. const { action } = props.buttons;
  96. if (cancel) {
  97. onDismiss(cancel.onPress);
  98. } else if (action) {
  99. onDismiss(action.onPress);
  100. } else {
  101. onDismiss();
  102. }
  103. return true;
  104. }
  105. return false;
  106. };
  107. const getSpeechBubble = () => {
  108. return (
  109. <MascotSpeechBubble
  110. title={props.title}
  111. message={props.message}
  112. icon={props.icon}
  113. buttons={props.buttons}
  114. visible={dialogVisible}
  115. onDismiss={onDismiss}
  116. speechArrowPos={MASCOT_SIZE / 3}
  117. bubbleMaxHeight={BUBBLE_HEIGHT}
  118. />
  119. );
  120. };
  121. const getMascot = () => {
  122. return (
  123. <Animatable.View
  124. useNativeDriver
  125. animation={dialogVisible ? 'bounceInLeft' : 'bounceOutLeft'}
  126. duration={dialogVisible ? 1500 : 200}
  127. >
  128. <Mascot
  129. style={{ width: MASCOT_SIZE }}
  130. animated
  131. emotion={props.emotion}
  132. />
  133. </Animatable.View>
  134. );
  135. };
  136. const getBackground = () => {
  137. return (
  138. <TouchableWithoutFeedback
  139. onPress={() => {
  140. onDismiss(props.buttons.cancel?.onPress);
  141. }}
  142. >
  143. <Animatable.View
  144. style={styles.background}
  145. useNativeDriver
  146. animation={dialogVisible ? 'fadeIn' : 'fadeOut'}
  147. duration={dialogVisible ? 300 : 300}
  148. />
  149. </TouchableWithoutFeedback>
  150. );
  151. };
  152. const onDismiss = (callback?: () => void) => {
  153. setShouldShow(false);
  154. setDialogVisible(false);
  155. if (callback) {
  156. callback();
  157. }
  158. };
  159. if (shouldRenderDialog) {
  160. return (
  161. <Portal>
  162. {getBackground()}
  163. <View style={GENERAL_STYLES.centerVertical}>
  164. <View style={styles.container}>
  165. {getMascot()}
  166. {getSpeechBubble()}
  167. </View>
  168. </View>
  169. </Portal>
  170. );
  171. }
  172. return null;
  173. }
  174. export default MascotPopup;