From fde610608c3015f9813efa19f79307451aff3266 Mon Sep 17 00:00:00 2001 From: Vlad Narizhnyi Date: Mon, 15 Jan 2024 10:01:44 +0000 Subject: [PATCH] FEATURE | Add voiceover text & fix bugs (#8) https://jetup-digital.atlassian.net/jira/software/projects/TRUT/boards/90?selectedIssue=TRUT-18 Co-authored-by: Vlad Reviewed-on: https://gitlab.work-jetup.site/Vitalik/truthordare/pulls/8 Co-authored-by: Vlad Narizhnyi Co-committed-by: Vlad Narizhnyi --- .../app/src/main/assets/fonts/fontello.ttf | Bin 17324 -> 17624 bytes ios/GoogleService-Info.plist | 2 + ios/Podfile.lock | 6 + ios/Truth.xcodeproj/project.pbxproj | 4 +- package-lock.json | 11 ++ package.json | 1 + src/assets/resources/fonts/fontello.ttf | Bin 17324 -> 17624 bytes src/config/fontello.json | 6 + src/i18n/interfaces/common.interface.ts | 1 + .../interfaces/settings.types.interface.ts | 1 + src/i18n/locales/en/common.translation.ts | 1 + src/i18n/locales/en/settings.translation.ts | 1 + src/i18n/locales/ua/common.translation.ts | 11 +- .../questions-dares-list.ts | 36 +++--- .../common/typing/enums/storage-key.enum.ts | 1 + .../use-animation-truth-or-dare.hook.ts | 42 ++++++- src/module/game/components/index.ts | 2 +- .../components/player-field.component.tsx | 2 +- .../game/components/truth-or-dare-view.tsx | 53 --------- src/module/game/components/truth-or-dare.tsx | 104 ++++++++++++++++++ src/module/game/config/index.ts | 1 + src/module/game/config/voice.config.ts | 12 ++ .../hooks/get-current-truth-dares.hook.tsx | 12 +- src/module/game/hooks/index.ts | 1 + src/module/game/hooks/use-voice.hook.tsx | 42 +++++++ src/module/game/screens/game.screen.tsx | 4 +- src/module/game/screens/players.screen.tsx | 24 ++-- .../game/screens/truth-or-dare.screen.tsx | 24 ++-- .../animation/use-animation-list.hook.ts | 9 +- .../atoms/packages-page-separator.atom.tsx | 17 ++- .../root/navigations-groups/user.group.tsx | 1 + src/module/root/screens/loading-screen.tsx | 9 ++ src/module/settings/atoms/index.ts | 1 + .../atoms/switch-notifications.atom.tsx | 2 +- .../settings/atoms/switch-voiceover.atom.tsx | 33 ++++++ .../settings/config/settings.config.tsx | 11 +- .../settings/screens/settings.screen.tsx | 2 + src/store/slices/players.slice.ts | 21 +--- 38 files changed, 363 insertions(+), 148 deletions(-) delete mode 100644 src/module/game/components/truth-or-dare-view.tsx create mode 100644 src/module/game/components/truth-or-dare.tsx create mode 100644 src/module/game/config/voice.config.ts create mode 100644 src/module/game/hooks/use-voice.hook.tsx create mode 100644 src/module/settings/atoms/switch-voiceover.atom.tsx diff --git a/android/app/src/main/assets/fonts/fontello.ttf b/android/app/src/main/assets/fonts/fontello.ttf index 96a2c6d2c148587ccac2c3b3f7262f523c2d05cd..612bdbf15d8eb418fb584db89ce81c66f4b51f94 100644 GIT binary patch delta 947 zcmX|9T}V@57=FL=v#l}rbjhA&147`=P=_YUL#w)|{;t&}@28pco9(VME=REK8ywCTY!+XBBkID8jdEs7s z06_WxU^tPD^XuxPF4XS;sO`kevHtd3SbT~{Uys^~tQyPA zjUFYQ{sv&40Msp|Q}Lufd3^++L52Nr8V!|_xq`YKwI`jOo=rZyQ9M8+iAFjzk%+JU zdTvMkN!pN&&+=qb*9#D2P`mELv#G6Dl~t%c_<>ve#MJc8*Dvz`9G*cu{A7w>*l5`R z=!mZT9V9!#0Ga4r1A?0YUIKkw)gh}fz6K18qpt=PP(X!qH@kAjL>fuwRi>sbjUKKIf*jBfVB)L$65v$v6cZ9 zSjzwt);MDcU`EFGO8^V9uLO|#krF7dIotIT31er8nmHUkW-*lu=_fnDvs3#>!2{ zen+TqsFAH2-6-GeO7hKQm@*dj36%6?_4q|!@->orrDYfT0ybeN;H5CP6zF!8eb%n(GWv4El2L7JGwxNanx2{4 zF)Up`d;VMC3?ZEGqhW(^`SsvsLhQoNP>>>(f{R;Fu-SW4w-dr^u2QMwGx539r0||| QDcD3N6`#z1=RRov1M{2HSpWb4 delta 657 zcmYL`PiPZC6vn^VEG8*2o93U{6ly??pr&TCO(`gN&{HldNIfW+HGd0DvY540si}pc zD1wnWst5%^P@%XGPgU@wXg!Dr1r?;1J$NXhpiqc!vWf%W`_1>}z2WWbhimN7Ro30H za2a6702E6N!wjz8tdd^=q<5uRD@A!)<3A__t7ffncI@{>ATN^*)Qq-C`4F9dBl>DH z7pkH1>K;H|0``{c6{Ea6^Zo{~-=Xt~IwgD&xd3ESq8j*|aGKGifDm9@Lc5%QZf;F{TLTOWu0uK@iG4ZYnwS1~)i13lny z=+>`O>@z>XS|^vms2R8e&tSKYI7H>kkRj#|zy}Gkv(+H;m4&-UX;8_U{FBA4_1Po_B>or=q3^b>79hcdpX0dAQ*N9!H6pe zMqNQL;R-RL>I!jU+7-4DCtZPR=?l@n=Xq*(<;A3(Nv(%Q7q5G)Gs?lAP(0#i;OqUD zv;WD*v{BIq`T5`162@IBH#L=C(suEV=H-vIL&;zu_(fR?eGkuvS0Yotruth-or-dare-fcc54.appspot.com IS_ADS_ENABLED + ITSAppUsesNonExemptEncryption + IS_ANALYTICS_ENABLED IS_APPINVITE_ENABLED diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 30d2d7b..2c33042 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1286,6 +1286,8 @@ PODS: - RNVectorIcons (9.2.0): - React-Core - SocketRocket (0.6.1) + - TextToSpeech (4.1.0): + - React - Yoga (1.14.0) - YogaKit (1.18.1): - Yoga (~> 1.14) @@ -1368,6 +1370,7 @@ DEPENDENCIES: - RNScreens (from `../node_modules/react-native-screens`) - RNSVG (from `../node_modules/react-native-svg`) - RNVectorIcons (from `../node_modules/react-native-vector-icons`) + - TextToSpeech (from `../node_modules/react-native-tts`) - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) SPEC REPOS: @@ -1503,6 +1506,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-svg" RNVectorIcons: :path: "../node_modules/react-native-vector-icons" + TextToSpeech: + :path: "../node_modules/react-native-tts" Yoga: :path: "../node_modules/react-native/ReactCommon/yoga" @@ -1585,6 +1590,7 @@ SPEC CHECKSUMS: RNSVG: d7d7bc8229af3842c9cfc3a723c815a52cdd1105 RNVectorIcons: fcc2f6cb32f5735b586e66d14103a74ce6ad61f8 SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 + TextToSpeech: b3aa777ff5585705f179c0a2436bfd0926d1716e Yoga: 4c3aa327e4a6a23eeacd71f61c81df1bcdf677d5 YogaKit: f782866e155069a2cca2517aafea43200b01fd5a diff --git a/ios/Truth.xcodeproj/project.pbxproj b/ios/Truth.xcodeproj/project.pbxproj index 862d163..5591594 100644 --- a/ios/Truth.xcodeproj/project.pbxproj +++ b/ios/Truth.xcodeproj/project.pbxproj @@ -547,7 +547,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 5; DEVELOPMENT_TEAM = 7LY53JU2YB; ENABLE_BITCODE = NO; HEADER_SEARCH_PATHS = ( @@ -616,7 +616,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 5; DEVELOPMENT_TEAM = 7LY53JU2YB; HEADER_SEARCH_PATHS = ( "$(inherited)", diff --git a/package-lock.json b/package-lock.json index a9098a2..d60c34d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,6 +36,7 @@ "react-native-splash-screen": "^3.3.0", "react-native-svg": "^12.5.1", "react-native-svg-transformer": "^1.0.0", + "react-native-tts": "^4.1.0", "react-native-vector-icons": "^9.2.0", "react-redux": "^9.0.4", "validate.js": "^0.13.1" @@ -13517,6 +13518,11 @@ "react-native-svg": ">=12.0.0" } }, + "node_modules/react-native-tts": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/react-native-tts/-/react-native-tts-4.1.0.tgz", + "integrity": "sha512-tvf3lQ6u9MqztUie37qoPw5YQPqi0ql1lPhDsBBs/RRA6A/H1J9X9H/qb1A0Hx0ZpjavrEdtVSqQQ2JDZvZCTQ==" + }, "node_modules/react-native-vector-icons": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/react-native-vector-icons/-/react-native-vector-icons-9.2.0.tgz", @@ -25487,6 +25493,11 @@ "path-dirname": "^1.0.2" } }, + "react-native-tts": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/react-native-tts/-/react-native-tts-4.1.0.tgz", + "integrity": "sha512-tvf3lQ6u9MqztUie37qoPw5YQPqi0ql1lPhDsBBs/RRA6A/H1J9X9H/qb1A0Hx0ZpjavrEdtVSqQQ2JDZvZCTQ==" + }, "react-native-vector-icons": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/react-native-vector-icons/-/react-native-vector-icons-9.2.0.tgz", diff --git a/package.json b/package.json index 332617f..bbbe1d0 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "react-native-splash-screen": "^3.3.0", "react-native-svg": "^12.5.1", "react-native-svg-transformer": "^1.0.0", + "react-native-tts": "^4.1.0", "react-native-vector-icons": "^9.2.0", "react-redux": "^9.0.4", "validate.js": "^0.13.1" diff --git a/src/assets/resources/fonts/fontello.ttf b/src/assets/resources/fonts/fontello.ttf index 96a2c6d2c148587ccac2c3b3f7262f523c2d05cd..612bdbf15d8eb418fb584db89ce81c66f4b51f94 100644 GIT binary patch delta 947 zcmX|9T}V@57=FL=v#l}rbjhA&147`=P=_YUL#w)|{;t&}@28pco9(VME=REK8ywCTY!+XBBkID8jdEs7s z06_WxU^tPD^XuxPF4XS;sO`kevHtd3SbT~{Uys^~tQyPA zjUFYQ{sv&40Msp|Q}Lufd3^++L52Nr8V!|_xq`YKwI`jOo=rZyQ9M8+iAFjzk%+JU zdTvMkN!pN&&+=qb*9#D2P`mELv#G6Dl~t%c_<>ve#MJc8*Dvz`9G*cu{A7w>*l5`R z=!mZT9V9!#0Ga4r1A?0YUIKkw)gh}fz6K18qpt=PP(X!qH@kAjL>fuwRi>sbjUKKIf*jBfVB)L$65v$v6cZ9 zSjzwt);MDcU`EFGO8^V9uLO|#krF7dIotIT31er8nmHUkW-*lu=_fnDvs3#>!2{ zen+TqsFAH2-6-GeO7hKQm@*dj36%6?_4q|!@->orrDYfT0ybeN;H5CP6zF!8eb%n(GWv4El2L7JGwxNanx2{4 zF)Up`d;VMC3?ZEGqhW(^`SsvsLhQoNP>>>(f{R;Fu-SW4w-dr^u2QMwGx539r0||| QDcD3N6`#z1=RRov1M{2HSpWb4 delta 657 zcmYL`PiPZC6vn^VEG8*2o93U{6ly??pr&TCO(`gN&{HldNIfW+HGd0DvY540si}pc zD1wnWst5%^P@%XGPgU@wXg!Dr1r?;1J$NXhpiqc!vWf%W`_1>}z2WWbhimN7Ro30H za2a6702E6N!wjz8tdd^=q<5uRD@A!)<3A__t7ffncI@{>ATN^*)Qq-C`4F9dBl>DH z7pkH1>K;H|0``{c6{Ea6^Zo{~-=Xt~IwgD&xd3ESq8j*|aGKGifDm9@Lc5%QZf;F{TLTOWu0uK@iG4ZYnwS1~)i13lny z=+>`O>@z>XS|^vms2R8e&tSKYI7H>kkRj#|zy}Gkv(+H;m4&-UX;8_U{FBA4_1Po_B>or=q3^b>79hcdpX0dAQ*N9!H6pe zMqNQL;R-RL>I!jU+7-4DCtZPR=?l@n=Xq*(<;A3(Nv(%Q7q5G)Gs?lAP(0#i;OqUD zv;WD*v{BIq`T5`162@IBH#L=C(suEV=H-vIL&;zu_(fR?eGkuvS0Yo { +export const useAnimationTruthOrDare = (wordsArr: string[]) => { const animContainer = useRef(new Animated.Value(400)).current + const { i18n } = useTranslation() - const startAnimation = () => { + const animValues: Animated.Value[] = [] + + wordsArr.forEach((_, index) => (animValues[index] = new Animated.Value(0))) + + const startAnimTextByVoice = () => { + const animations = wordsArr.map((word, index) => { + const wordWithComma = word.includes(',') + + const isEnLanguage = i18n.language === Language.EN + + const duration = isEnLanguage ? 50 : 75 + + const durationWithComma = Platform.select({ + android: 75, + default: isEnLanguage ? 175 : 100, + }) + + return Animated.timing(animValues[index], { + toValue: 1, + duration: + word.length * + (wordWithComma ? durationWithComma : duration), + useNativeDriver: true, + }) + }) + + Animated.sequence(animations).start() + } + + const startAnimationCard = () => { Animated.timing(animContainer, { toValue: 0, duration: 400, @@ -21,7 +53,9 @@ export const useAnimationTruthOrDare = () => { } return { - startAnimation, + startAnimationCard, + startAnimTextByVoice, + animValues, animStyle: animStyleContainer, } } diff --git a/src/module/game/components/index.ts b/src/module/game/components/index.ts index 6ffa9b6..0f141c9 100644 --- a/src/module/game/components/index.ts +++ b/src/module/game/components/index.ts @@ -1,2 +1,2 @@ -export * from './truth-or-dare-view' +export * from './truth-or-dare' export * from './player-field.component'; diff --git a/src/module/game/components/player-field.component.tsx b/src/module/game/components/player-field.component.tsx index ed19195..6602a97 100644 --- a/src/module/game/components/player-field.component.tsx +++ b/src/module/game/components/player-field.component.tsx @@ -24,7 +24,7 @@ export const PlayerField: FC = ({ value, onChange, onDelete }) => { onChange={onChange} renderPostfix={renderClear} inputProps={{ - placeholder: t('customPack.placeholder'), + placeholder: t('common.placeholderPlayer'), placeholderTextColor: colors.darkPurple, }} inputStyle={styles.input} diff --git a/src/module/game/components/truth-or-dare-view.tsx b/src/module/game/components/truth-or-dare-view.tsx deleted file mode 100644 index 03c297f..0000000 --- a/src/module/game/components/truth-or-dare-view.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import React, { useEffect } from 'react' -import { Font, Icon, colors } from '../../common' -import { Animated, StyleSheet } from 'react-native' -import { useAnimationTruthOrDare } from '../animations' - -interface IProps { - item: string -} - -export const TruthOrDareView: React.FC = ({ item }) => { - const { animStyle, startAnimation } = useAnimationTruthOrDare() - - useEffect(() => { - startAnimation() - }, []) - return ( - - - {item} - - ) -} - -const styles = StyleSheet.create({ - container: { - paddingHorizontal: 16, - paddingVertical: 32, - backgroundColor: colors.darkPurple, - borderRadius: 20, - alignItems: 'center', - shadowColor: '#190f42', - shadowOffset: { width: 0, height: 2 }, - shadowOpacity: 1, - shadowRadius: 4, - elevation: 5, - marginTop: 88, - }, - starIcon: { - marginBottom: 14, - }, - text: { - color: colors.purple, - fontSize: 22, - lineHeight: 32, - textAlign: 'center', - fontFamily: Font.Roboto400, - }, -}) diff --git a/src/module/game/components/truth-or-dare.tsx b/src/module/game/components/truth-or-dare.tsx new file mode 100644 index 0000000..e6c6419 --- /dev/null +++ b/src/module/game/components/truth-or-dare.tsx @@ -0,0 +1,104 @@ +import React, { useEffect, useState } from 'react' +import { Font, Icon, StorageKey, colors, storageService } from '../../common' +import { Animated, StyleSheet, View } from 'react-native' +import { useAnimationTruthOrDare } from '../animations' +import Tts from 'react-native-tts' + +interface IProps { + item: string +} + +export const TruthOrDare: React.FC = ({ item }) => { + const wordsArr = item.trim().split(' ') + const [voiceover, setVoiceover] = useState(true) + + const { animStyle, animValues, startAnimationCard, startAnimTextByVoice } = + useAnimationTruthOrDare(wordsArr) + + useEffect(() => { + startAnimationCard() + + checkAndStartVoiceover() + }, [item]) + + const checkAndStartVoiceover = async () => { + const isVoicer = await storageService.get(StorageKey.Voiceover) + + setVoiceover(isVoicer) + + if (isVoicer) { + Tts.stop() + Tts.speak(item) + Tts.addEventListener('tts-start', startAnimTextByVoice) + } + } + + return ( + + + + {wordsArr?.map((word, index) => { + return ( + + {word + ' '} + + ) + })} + + + ) +} + +const styles = StyleSheet.create({ + container: { + paddingLeft: 21, + paddingRight: 11, + paddingVertical: 32, + backgroundColor: colors.darkPurple, + borderRadius: 20, + alignItems: 'center', + shadowColor: '#190f42', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 1, + shadowRadius: 4, + elevation: 5, + marginTop: 88, + }, + textWrap: { + flexDirection: 'row', + flexWrap: 'wrap', + justifyContent: 'center', + }, + starIcon: { + marginBottom: 14, + }, + text: { + color: colors.purple, + fontSize: 22, + lineHeight: 32, + textAlign: 'center', + fontFamily: Font.Roboto400, + }, +}) diff --git a/src/module/game/config/index.ts b/src/module/game/config/index.ts index c5e6cc5..aaa99d3 100644 --- a/src/module/game/config/index.ts +++ b/src/module/game/config/index.ts @@ -1 +1,2 @@ export * from './package-name.config'; +export * from './voice.config'; diff --git a/src/module/game/config/voice.config.ts b/src/module/game/config/voice.config.ts new file mode 100644 index 0000000..a94fc1b --- /dev/null +++ b/src/module/game/config/voice.config.ts @@ -0,0 +1,12 @@ +import { Language } from '~module/common' + +export const voiceConfig: any = { + [Language.UA]: { + language: 'uk-UA', + voice: 'com.apple.voice.compact.uk-UA.Lesya', + }, + [Language.EN]: { + language: 'en-US', + voice: 'com.apple.ttsbundle.Samantha-compact', + }, +} diff --git a/src/module/game/hooks/get-current-truth-dares.hook.tsx b/src/module/game/hooks/get-current-truth-dares.hook.tsx index 41623ad..d9f194b 100644 --- a/src/module/game/hooks/get-current-truth-dares.hook.tsx +++ b/src/module/game/hooks/get-current-truth-dares.hook.tsx @@ -1,4 +1,4 @@ -import { useEffect } from 'react' +import { useEffect, useMemo } from 'react' import { resetStepsByTruthOrDare, selectCustomPackage, @@ -64,8 +64,12 @@ export const useGetCurrentTruthOrDare = ({ return packageTruthsOrDares?.[currentStep]?.[i18n.language as Language] } - const customTruthsOrDares = getGameItemsByCustomPackage() - const packageTruthsOrDares = getGameItemsByPackage() + const customTruthsOrDares = useMemo(getGameItemsByCustomPackage, [ + customPackageShuffle, + customType, + ]) + + const packageTruthsOrDares = useMemo(getGameItemsByPackage, [gameItems]) const currentItem = getCurrentItem() const shuffleAndSavePackage = async () => { @@ -136,7 +140,7 @@ export const useGetCurrentTruthOrDare = ({ useEffect(() => { checkIsNeedShuffle() - }, [currentStep, customTruthsOrDares]) + }, [currentStep]) return currentItem } diff --git a/src/module/game/hooks/index.ts b/src/module/game/hooks/index.ts index 438ce0a..3199ece 100644 --- a/src/module/game/hooks/index.ts +++ b/src/module/game/hooks/index.ts @@ -1,2 +1,3 @@ export * from './get-current-truth-dares.hook' export * from './use-set-steps-by-package'; +export * from './use-voice.hook'; diff --git a/src/module/game/hooks/use-voice.hook.tsx b/src/module/game/hooks/use-voice.hook.tsx new file mode 100644 index 0000000..269cfe9 --- /dev/null +++ b/src/module/game/hooks/use-voice.hook.tsx @@ -0,0 +1,42 @@ +import { useEffect, useState } from 'react' +import Tts from 'react-native-tts' +import { voiceConfig } from '../config' +import { useTranslation } from 'react-i18next' + +export const useVoice = () => { + const { i18n } = useTranslation() + const [ttsStatus, setTtsStatus] = useState('initiliazing') + + useEffect(() => { + Tts.addEventListener('tts-finish', _event => setTtsStatus('finished')) + Tts.addEventListener('tts-cancel', _event => setTtsStatus('cancelled')) + Tts.getInitStatus().then(initTts) + + return () => { + if (ttsStatus === 'finished') { + Tts.removeEventListener('tts-start', _event => + setTtsStatus('started'), + ) + } + + Tts.removeEventListener('tts-finish', _event => + setTtsStatus('finished'), + ) + Tts.removeEventListener('tts-cancel', _event => + setTtsStatus('cancelled'), + ) + } + }, []) + + const initTts = async () => { + try { + await Tts.setDefaultLanguage(voiceConfig[i18n.language].language) + await Tts.setDefaultVoice(voiceConfig[i18n.language].voice) + await Tts.setDefaultRate(0.5) + } catch (err) { + //Samsung S9 has always this error: + //"Language is not supported" + console.log(`setDefaultLanguage error `, err) + } + } +} diff --git a/src/module/game/screens/game.screen.tsx b/src/module/game/screens/game.screen.tsx index 8c5158a..2bb5dc4 100644 --- a/src/module/game/screens/game.screen.tsx +++ b/src/module/game/screens/game.screen.tsx @@ -17,7 +17,7 @@ import { useIsFocused, useRoute } from '@react-navigation/native' import { useAnimationButton } from '../animations' import { PlayerName } from '../components/player-name.component' import { packageNameConfig } from '../config' -import { useSetStepsByPackage } from '../hooks' +import { useSetStepsByPackage, useVoice } from '../hooks' interface IRouteParams { packageType?: PackageType @@ -33,6 +33,8 @@ export const GameScreen: FC = () => { useSetStepsByPackage(packageType) + useVoice() + const randomGame = () => { const isTruthRandom = Math.random() < 0.5 const customRandom = isTruthRandom diff --git a/src/module/game/screens/players.screen.tsx b/src/module/game/screens/players.screen.tsx index 7bb853d..b51219a 100644 --- a/src/module/game/screens/players.screen.tsx +++ b/src/module/game/screens/players.screen.tsx @@ -37,7 +37,7 @@ export const PlayersScreen: FC = () => { const onDeletePlayer = (index: number) => { const updatedPlayers = [...players] - if (updatedPlayers.length === 1) return + if (updatedPlayers.length <= 2) return updatedPlayers.splice(index, 1) dispatch(setPlayers(updatedPlayers)) @@ -66,16 +66,13 @@ export const PlayersScreen: FC = () => { +
}> - + {players.map((player, index) => ( { onDelete={() => onDeletePlayer(index)} /> ))} + - - { dispatch(onNextPlayer()) dispatch(nextStep(choiceType)) saveLastStep() + Tts.stop() } const onAddGameItemToCustomPackage = () => { @@ -67,6 +69,7 @@ export const TruthOrDareScreen: React.FC = () => { } const onPressAddPlus = () => { + Tts.stop() const subtitleByChoice = choiceType === ChoiceType.Truth ? t('customPack.addCustomTruth') @@ -157,7 +160,7 @@ export const TruthOrDareScreen: React.FC = () => { - + {currentItem ? : null} { animation={animScale.func} animStyle={animScale.style} /> - - + {packageType !== PackageType.Custom && ( + + )} diff --git a/src/module/packages/animation/use-animation-list.hook.ts b/src/module/packages/animation/use-animation-list.hook.ts index c16b1b7..61358f8 100644 --- a/src/module/packages/animation/use-animation-list.hook.ts +++ b/src/module/packages/animation/use-animation-list.hook.ts @@ -1,10 +1,7 @@ import React, { useEffect, useRef } from 'react' import { Animated } from 'react-native' -import { useIsFocused } from '@react-navigation/native' export const useAnimationList = (delay: number) => { - const isFocus = useIsFocused() - const animTransformX = useRef(new Animated.Value(0)).current const animTransformY = useRef(new Animated.Value(0)).current @@ -33,12 +30,8 @@ export const useAnimationList = (delay: number) => { ), ]) - if (!isFocus) { - return animation.reset() - } - animation.start() - }, [isFocus]) + }, []) const animationStyleItem = { opacity: animTransformX, diff --git a/src/module/packages/atoms/packages-page-separator.atom.tsx b/src/module/packages/atoms/packages-page-separator.atom.tsx index 017031e..7ac2b63 100644 --- a/src/module/packages/atoms/packages-page-separator.atom.tsx +++ b/src/module/packages/atoms/packages-page-separator.atom.tsx @@ -8,20 +8,17 @@ export const PackagesPageSeparator = () => { useEffect(() => { Animated.spring(fadeInAnim, { toValue: 1, - useNativeDriver: true, - delay: 1100, + useNativeDriver: false, + stiffness: 50, + delay: 1400, }).start() }, []) const animStyle = { - transform: [ - { - translateY: fadeInAnim.interpolate({ - inputRange: [0, 1], - outputRange: [-900, 0], - }), - }, - ], + width: fadeInAnim.interpolate({ + inputRange: [0, 1], + outputRange: ['0%', '100%'], + }), } return ( diff --git a/src/module/root/navigations-groups/user.group.tsx b/src/module/root/navigations-groups/user.group.tsx index 41d0a52..e02d2d3 100644 --- a/src/module/root/navigations-groups/user.group.tsx +++ b/src/module/root/navigations-groups/user.group.tsx @@ -35,6 +35,7 @@ export const UserNavigationGroup: FC = () => { { return await storageService.get(StorageKey.FinishOnBoarding) } + const setVoiceover = async () => { + const isVoiceover = await storageService.get(StorageKey.Voiceover) + + if (isVoiceover === null) { + await storageService.set(StorageKey.Voiceover, true) + } + } + const init = async () => { const language = await getLanguage() const isOnBoard = await getOnboardEnd() @@ -56,6 +64,7 @@ export const LoadingScreen: FC = () => { useEffect(() => { init() + setVoiceover() }, []) return ( diff --git a/src/module/settings/atoms/index.ts b/src/module/settings/atoms/index.ts index 06c290f..50e0737 100644 --- a/src/module/settings/atoms/index.ts +++ b/src/module/settings/atoms/index.ts @@ -1,3 +1,4 @@ export * from './selected-language-in-settings.atom' export * from './switch-notifications.atom' export * from './purchases.atom' +export * from './switch-voiceover.atom'; diff --git a/src/module/settings/atoms/switch-notifications.atom.tsx b/src/module/settings/atoms/switch-notifications.atom.tsx index eb834a3..4b47e5d 100644 --- a/src/module/settings/atoms/switch-notifications.atom.tsx +++ b/src/module/settings/atoms/switch-notifications.atom.tsx @@ -6,7 +6,7 @@ export const SwitchNotificationsAtom = () => { const [isEnabled, setIsEnabled] = useState(true) const toggleSwitch = () => { - setIsEnabled(previousState => !previousState) + setIsEnabled(!isEnabled) } return ( diff --git a/src/module/settings/atoms/switch-voiceover.atom.tsx b/src/module/settings/atoms/switch-voiceover.atom.tsx new file mode 100644 index 0000000..e00a661 --- /dev/null +++ b/src/module/settings/atoms/switch-voiceover.atom.tsx @@ -0,0 +1,33 @@ +import React, { useEffect, useState } from 'react' +import { Switch } from 'react-native' +import { StorageKey, colors, storageService } from '../../common' + +export const SwitchVoiceoverAtom = () => { + const [voiceover, setVoiceover] = useState(true) + + const toggleSwitch = async () => { + setVoiceover(!voiceover) + await storageService.set(StorageKey.Voiceover, !voiceover) + } + + const getIsVoiceover = async () => { + const isVoiceover = await storageService.get(StorageKey.Voiceover) + + setVoiceover(isVoiceover) + } + + useEffect(() => { + getIsVoiceover() + }, []) + + return ( + + ) +} diff --git a/src/module/settings/config/settings.config.tsx b/src/module/settings/config/settings.config.tsx index 2c58c76..42ac54f 100644 --- a/src/module/settings/config/settings.config.tsx +++ b/src/module/settings/config/settings.config.tsx @@ -1,6 +1,10 @@ import React from 'react' import { SettingLocale } from '../../../i18n/interfaces/settings.types.interface' -import { SelectedLanguage, SwitchNotificationsAtom } from '../atoms' +import { + SelectedLanguage, + SwitchNotificationsAtom, + SwitchVoiceoverAtom, +} from '../atoms' const translatePath = (itemKey: keyof SettingLocale.Core) => `settingTranslation.${itemKey}` @@ -20,6 +24,11 @@ export const settingsConfig = [ image: 'notification', component: () => , }, + { + title: translatePath('voiceover'), + image: 'music', + component: () => , + }, { title: translatePath('write'), image: 'message', diff --git a/src/module/settings/screens/settings.screen.tsx b/src/module/settings/screens/settings.screen.tsx index 58021a8..b6cd1ad 100644 --- a/src/module/settings/screens/settings.screen.tsx +++ b/src/module/settings/screens/settings.screen.tsx @@ -67,6 +67,8 @@ export const SettingsScreen: FC = () => { break case 'notification': break + case 'voice': + break case 'message': nav.navigate(UserRouteKey.WriteToUs) break diff --git a/src/store/slices/players.slice.ts b/src/store/slices/players.slice.ts index 0b51e48..bb966a2 100644 --- a/src/store/slices/players.slice.ts +++ b/src/store/slices/players.slice.ts @@ -7,22 +7,18 @@ export interface PlayersState { players: string[] playerIndex: number currentPlayer: string - loaded: boolean - hasError: boolean } const initialState: PlayersState = { players: [], playerIndex: 0, currentPlayer: '', - loaded: false, - hasError: false, } export const getPlayersAsync = createAsyncThunk('get-players', async () => { const savedPlayers = await storageService.get(StorageKey.Players) - return savedPlayers ? savedPlayers : [''] + return savedPlayers ? savedPlayers : ['', ''] }) export const playersSlice = createSlice({ @@ -47,17 +43,10 @@ export const playersSlice = createSlice({ }, }, extraReducers(builder) { - builder - .addCase(getPlayersAsync.fulfilled, (state, action) => { - state.players = action.payload - state.currentPlayer = state.players[0] - }) - .addCase(getPlayersAsync.pending, state => { - state.loaded = false - }) - .addCase(getPlayersAsync.rejected, state => { - state.hasError = true - }) + builder.addCase(getPlayersAsync.fulfilled, (state, action) => { + state.players = action.payload + state.currentPlayer = state.players[0] + }) }, })