Browse Source

FEATURE | Custom pack & add animations (#4)

Co-authored-by: Vlad <vlad960706@gmail.com>
Reviewed-on: #4
Co-authored-by: Vlad Narizhnyi <vlad960706@gmail.com>
Co-committed-by: Vlad Narizhnyi <vlad960706@gmail.com>
pull/5/head
Vlad Narizhnyi 11 months ago committed by Vitalik Yatsenko
parent
commit
0652de5203
  1. 2
      android/app/build.gradle
  2. BIN
      android/app/src/main/assets/fonts/fontello.ttf
  3. 1
      android/app/src/main/java/com/truth/MainApplication.java
  4. 8
      android/build.gradle
  5. 2
      android/settings.gradle
  6. 20
      ios/Podfile.lock
  7. 1905
      package-lock.json
  8. 6
      package.json
  9. BIN
      src/assets/resources/fonts/fontello.ttf
  10. 12
      src/config/fontello.json
  11. 24
      src/i18n/interfaces/custom-pack.interface.ts
  12. 10
      src/i18n/interfaces/purchases.interface.ts
  13. 24
      src/i18n/locales/en/custom-pack.translation.ts
  14. 4
      src/i18n/locales/en/purchases.translation.ts
  15. 31
      src/i18n/locales/ua/custom-pack.translation.ts
  16. 8
      src/i18n/locales/ua/purchases.translation.ts
  17. 4
      src/i18n/locales/ua/settings.translation.ts
  18. 86
      src/module/common/components/buttons/button-primary.component.tsx
  19. 25
      src/module/common/components/buttons/button-with-icon.tsx
  20. 3
      src/module/common/components/buttons/go-back.component.tsx
  21. 10
      src/module/common/components/form/form-controll-wrap.component.tsx
  22. 46
      src/module/common/components/form/form-text-controll.component.tsx
  23. 23
      src/module/common/components/header/header.component.tsx
  24. 2
      src/module/common/components/icon/icon.component.tsx
  25. 7
      src/module/common/components/layout/screen-layout-content.component.tsx
  26. 4
      src/module/common/components/layout/screen-layout.component.tsx
  27. 29
      src/module/common/components/txt/txt.component.tsx
  28. 7
      src/module/common/svg-icons/icons-svg.tsx
  29. 1
      src/module/common/typing/enums/index.ts
  30. 10
      src/module/common/typing/enums/route-keys.enum.ts
  31. 1
      src/module/common/typing/enums/storage-key.enum.ts
  32. 4
      src/module/common/typing/enums/type-custom.enum.ts
  33. 8
      src/module/common/typing/interfaces/game-item.ts
  34. 33
      src/module/common/widgets/alert-confirm/alert-confirm-widget.component.tsx
  35. 30
      src/module/common/widgets/alert/alert-widget.component.tsx
  36. 7
      src/module/common/widgets/bottom-sheet/atoms/action-sheet-button.atom.tsx
  37. 11
      src/module/common/widgets/bottom-sheet/atoms/bottom-sheet-view.atom.tsx
  38. 90
      src/module/custom-package/atoms/custom-block.atom.tsx
  39. 34
      src/module/custom-package/atoms/empty-items.atom.tsx
  40. 2
      src/module/custom-package/atoms/index.ts
  41. 2
      src/module/custom-package/index.ts
  42. 268
      src/module/custom-package/screens/custom-package-editor.screen.tsx
  43. 119
      src/module/custom-package/screens/custom-package-play.screen.tsx
  44. 2
      src/module/custom-package/screens/index.ts
  45. 3
      src/module/game/animations/index.ts
  46. 39
      src/module/game/animations/use-animation-button.ts
  47. 48
      src/module/game/animations/use-animation-icons-button.hook.ts
  48. 74
      src/module/game/animations/use-animation-truth-or-dare.hook.ts
  49. 2
      src/module/game/components/index.ts
  50. 80
      src/module/game/components/question-block.tsx
  51. 86
      src/module/game/components/truth-or-dare-view.tsx
  52. 24
      src/module/game/helper/get-current-truth-dares.helper.ts
  53. 1
      src/module/game/helper/index.ts
  54. 2
      src/module/game/index.ts
  55. 95
      src/module/game/screens/game.screen.tsx
  56. 2
      src/module/game/screens/index.ts
  57. 78
      src/module/game/screens/questions.screen.tsx
  58. 100
      src/module/game/screens/truth-or-dare.screen.tsx
  59. 2
      src/module/packages/animation/index.ts
  60. 53
      src/module/packages/animation/use-animation-custom-item.hook.ts
  61. 61
      src/module/packages/animation/use-animation-list.hook.ts
  62. 64
      src/module/packages/atoms/animated-diamond-icon.atom.tsx
  63. 88
      src/module/packages/atoms/create-custom-package.atom.tsx
  64. 1
      src/module/packages/atoms/index.ts
  65. 35
      src/module/packages/atoms/packages-page-separator.atom.tsx
  66. 50
      src/module/packages/components/packages-item.component.tsx
  67. 1
      src/module/packages/index.ts
  68. 15
      src/module/packages/screens/packages-list.screen.tsx
  69. 7
      src/module/root/atoms/dots.atom.tsx
  70. 17
      src/module/root/atoms/on-boarding-button.component.tsx
  71. 10
      src/module/root/components/language-item.component.tsx
  72. 50
      src/module/root/navigations-groups/user.group.tsx
  73. 12
      src/module/root/screens/language-select.screen.tsx
  74. 9
      src/module/root/screens/loading-screen.tsx
  75. 16
      src/module/root/screens/on-boarding.screen.tsx
  76. 27
      src/module/settings/atoms/purchases.atom.tsx
  77. 8
      src/module/settings/atoms/selected-language-in-settings.atom.tsx
  78. 6
      src/module/settings/atoms/switch-notifications.atom.tsx
  79. 16
      src/module/settings/components/settings-item.component.tsx
  80. 6
      src/module/settings/config/purchases.config.ts
  81. 4
      src/module/settings/screens/privacy-policy.tsx
  82. 25
      src/module/settings/screens/purchases.screen.tsx
  83. 8
      src/module/settings/screens/settings.screen.tsx
  84. 5
      src/module/settings/screens/write-to-us.screen.tsx
  85. 54
      src/module/settings/services/purchases.service.ts
  86. 115
      src/store/slices/custom-package.slice.ts
  87. 52
      src/store/slices/dares-slice.ts
  88. 3
      src/store/slices/index.ts
  89. 12
      src/store/slices/posts.slice.ts
  90. 53
      src/store/slices/truth-slice.ts
  91. 13
      src/store/store.ts

2
android/app/build.gradle

@ -35,6 +35,7 @@ android { @@ -35,6 +35,7 @@ android {
versionCode 1
versionName "1.0"
multiDexEnabled true
missingDimensionStrategy "store", "play"
}
signingConfigs {
debug {
@ -61,6 +62,7 @@ android { @@ -61,6 +62,7 @@ android {
dependencies {
// The version of react-native is set by the React Native Gradle Plugin
implementation("com.facebook.react:react-android")
implementation project(':react-native-iap')
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}")
debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {

BIN
android/app/src/main/assets/fonts/fontello.ttf

Binary file not shown.

1
android/app/src/main/java/com/truth/MainApplication.java

@ -10,6 +10,7 @@ import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint; @@ -10,6 +10,7 @@ import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
import com.facebook.react.defaults.DefaultReactNativeHost;
import com.facebook.soloader.SoLoader;
import java.lang.reflect.InvocationTargetException;
import com.dooboolab.rniap.RNIapPackage;
import java.util.List;
public class MainApplication extends Application implements ReactApplication {

8
android/build.gradle

@ -3,12 +3,17 @@ @@ -3,12 +3,17 @@
buildscript {
ext {
buildToolsVersion = "33.0.0"
minSdkVersion = 21
minSdkVersion = 24
compileSdkVersion = 33
targetSdkVersion = 33
// We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP.
ndkVersion = "23.1.7779620"
//react-native-aip
supportLibVersion = "28.0.0"
androidXAnnotation = "1.1.0"
androidXBrowser = "1.0.0"
kotlinVersion = "1.8.0"
}
repositories {
google()
@ -18,6 +23,7 @@ buildscript { @@ -18,6 +23,7 @@ buildscript {
classpath("com.android.tools.build:gradle")
classpath("com.facebook.react:react-native-gradle-plugin")
classpath ("com.google.gms:google-services:4.3.14")
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}

2
android/settings.gradle

@ -1,4 +1,6 @@ @@ -1,4 +1,6 @@
rootProject.name = 'Truth'
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
include ':app'
include ':react-native-iap'
includeBuild('../node_modules/@react-native/gradle-plugin')
project(':react-native-iap').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-iap/android')

20
ios/Podfile.lock

@ -1147,7 +1147,7 @@ PODS: @@ -1147,7 +1147,7 @@ PODS:
- React-jsinspector (0.72.7)
- React-logger (0.72.7):
- glog
- react-native-safe-area-context (4.7.4):
- react-native-safe-area-context (4.8.1):
- React-Core
- react-native-splash-screen (3.3.0):
- React-Core
@ -1261,7 +1261,7 @@ PODS: @@ -1261,7 +1261,7 @@ PODS:
- React-jsi (= 0.72.7)
- React-logger (= 0.72.7)
- React-perflogger (= 0.72.7)
- RNCAsyncStorage (1.19.5):
- RNCAsyncStorage (1.21.0):
- React-Core
- RNFBApp (17.5.0):
- Firebase/CoreOnly (= 10.7.0)
@ -1273,12 +1273,12 @@ PODS: @@ -1273,12 +1273,12 @@ PODS:
- RNFBApp
- RNFlashList (1.6.3):
- React-Core
- RNGestureHandler (2.13.4):
- RNGestureHandler (2.14.0):
- RCT-Folly (= 2021.07.22.00)
- React-Core
- RNIap (12.11.0):
- RNIap (12.12.1):
- React-Core
- RNScreens (3.27.0):
- RNScreens (3.29.0):
- RCT-Folly (= 2021.07.22.00)
- React-Core
- RNSVG (12.5.1):
@ -1556,7 +1556,7 @@ SPEC CHECKSUMS: @@ -1556,7 +1556,7 @@ SPEC CHECKSUMS:
React-jsiexecutor: c49502e5d02112247ee4526bc3ccfc891ae3eb9b
React-jsinspector: 8baadae51f01d867c3921213a25ab78ab4fbcd91
React-logger: 8edc785c47c8686c7962199a307015e2ce9a0e4f
react-native-safe-area-context: 2cd91d532de12acdb0a9cbc8d43ac72a8e4c897c
react-native-safe-area-context: cd1169d797a2ef722a00bfc5af10748d5b6c94f9
react-native-splash-screen: 4312f786b13a81b5169ef346d76d33bc0c6dc457
React-NativeModulesApple: b6868ee904013a7923128892ee4a032498a1024a
React-perflogger: 31ea61077185eb1428baf60c0db6e2886f141a5a
@ -1575,13 +1575,13 @@ SPEC CHECKSUMS: @@ -1575,13 +1575,13 @@ SPEC CHECKSUMS:
React-runtimescheduler: 7649c3b46c8dee1853691ecf60146a16ae59253c
React-utils: 56838edeaaf651220d1e53cd0b8934fb8ce68415
ReactCommon: 5f704096ccf7733b390f59043b6fa9cc180ee4f6
RNCAsyncStorage: f2974eca860c16a3e56eea5771fda8d12e2d2057
RNCAsyncStorage: 618d03a5f52fbccb3d7010076bc54712844c18ef
RNFBApp: 0d8bf86673bbad0524d1ceac3944d71ccf48a0e4
RNFBFirestore: f16efbd47cd136fe84cb93dc87ad4155807db221
RNFlashList: 4b4b6b093afc0df60ae08f9cbf6ccd4c836c667a
RNGestureHandler: 6e46dde1f87e5f018a54fe5d40cd0e0b942b49ee
RNIap: fc9af04ee706894a80c9d8f979bae930b0dee191
RNScreens: 3c2d122f5e08c192e254c510b212306da97d2581
RNGestureHandler: 32a01c29ecc9bb0b5bf7bc0a33547f61b4dc2741
RNIap: 31bf0cfb84ec19a9e5cc6b53b88d159c5adb3c68
RNScreens: 3c5b9f4a9dcde752466854b6109b79c0e205dad3
RNSVG: d7d7bc8229af3842c9cfc3a723c815a52cdd1105
RNVectorIcons: fcc2f6cb32f5735b586e66d14103a74ce6ad61f8
SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17

1905
package-lock.json generated

File diff suppressed because it is too large Load Diff

6
package.json

@ -35,17 +35,17 @@ @@ -35,17 +35,17 @@
"react-native-actions-sheet": "*",
"react-native-animated-loader": "^1.0.0",
"react-native-gesture-handler": "^2.5.0",
"react-native-iap": "^12.11.0",
"react-native-iap": "^12.12.1",
"react-native-icomoon": "^0.1.1",
"react-native-keyboard-aware-scroll-view": "^0.9.5",
"react-native-modal": "^13.0.1",
"react-native-safe-area-context": "^4.3.1",
"react-native-safe-area-context": "^4.8.1",
"react-native-screens": "^3.27.0",
"react-native-splash-screen": "^3.3.0",
"react-native-svg": "^12.5.1",
"react-native-svg-transformer": "^1.0.0",
"react-native-vector-icons": "^9.2.0",
"react-redux": "^8.0.5",
"react-redux": "^9.0.4",
"validate.js": "^0.13.1"
},
"devDependencies": {

BIN
src/assets/resources/fonts/fontello.ttf

Binary file not shown.

12
src/config/fontello.json

@ -281,6 +281,18 @@ @@ -281,6 +281,18 @@
"width": 1045
},
"search": ["union"]
},
{
"uid": "bce90eadf19032d3dd0fc621dba3a2f2",
"css": "clear",
"code": 59448,
"src": "custom_icons",
"selected": true,
"svg": {
"path": "M363.6 11.4C246.6 11.4 155.9 34.6 95.2 95.2 34.6 155.9 11.4 246.6 11.4 363.6V636.4C11.4 753.4 34.6 844.1 95.2 904.8 155.9 965.4 246.6 988.6 363.6 988.6H636.4C753.4 988.6 844.1 965.4 904.8 904.8 965.4 844.1 988.6 753.4 988.6 636.4V363.6C988.6 246.6 965.4 155.9 904.8 95.2 844.1 34.6 753.4 11.4 636.4 11.4H363.6ZM79.5 363.6C79.5 253.4 101.8 185 143.4 143.4 185 101.8 253.4 79.5 363.6 79.5H636.4C746.6 79.5 815 101.8 856.6 143.4 898.2 185 920.5 253.4 920.5 363.6V636.4C920.5 746.6 898.2 815 856.6 856.6 815 898.2 746.6 920.5 636.4 920.5H363.6C253.4 920.5 185 898.2 143.4 856.6 101.8 815 79.5 746.6 79.5 636.4V363.6ZM652.7 347.3C666.1 360.6 666.1 382.2 652.7 395.5L548.2 500 652.7 604.5C666.1 617.8 666.1 639.4 652.7 652.7 639.4 666.1 617.8 666.1 604.5 652.7L500 548.2 395.5 652.7C382.2 666.1 360.6 666.1 347.3 652.7 333.9 639.4 333.9 617.8 347.3 604.5L451.8 500 347.3 395.5C333.9 382.2 333.9 360.6 347.3 347.3 360.6 333.9 382.2 333.9 395.5 347.3L500 451.8 604.5 347.3C617.8 333.9 639.4 333.9 652.7 347.3Z",
"width": 1000
},
"search": ["clear"]
}
]
}

24
src/i18n/interfaces/custom-pack.interface.ts

@ -1,4 +1,22 @@ @@ -1,4 +1,22 @@
export interface CustomPack {
title: string;
description: string;
}
title: string
label: string
description: string
play: string
editorBtn: string
editor: string
placeholder: string
addTruth: string
addDare: string
viewTruths: string
viewDares: string
alertCreateTitle: string
alertCreateDesc: string
alertSaveTitle: string
alertSaveDesc: string
alertSaveNo: string
alertSaveYes: string
alertEmptyTitle: string
alertEmptyTruthDesc: string
alertEmptyDaresDesc: string
}

10
src/i18n/interfaces/purchases.interface.ts

@ -1,6 +1,10 @@ @@ -1,6 +1,10 @@
export interface PurchasesTranslate {
alertSuccess: string
descSuccess: string
alertError: string
descError: string
descSuccess: string
alertError: string
descError: string
allPackage: string
crazy: string
under18: string
restore: string
}

24
src/i18n/locales/en/custom-pack.translation.ts

@ -1,5 +1,27 @@ @@ -1,5 +1,27 @@
export const customPack = {
import { CustomPack } from "~i18n/interfaces/custom-pack.interface";
export const customPack: CustomPack = {
label: 'Custom package',
title: 'Create custom pack',
description:
'Create your own custom pack with questions and task. It all depends on your imagination!',
editor: 'Editor',
placeholder: 'Write here...',
addTruth: 'Add a truth',
addDare: 'Add a dare',
viewTruths: 'View truths',
viewDares: 'View dares',
alertCreateTitle: 'Gratefully!',
alertCreateDesc: 'You can play your custom package now!',
alertSaveTitle: 'You have unsaved changes',
alertSaveDesc: 'Save changes?',
alertSaveNo: 'No',
alertSaveYes: 'Save',
alertEmptyTitle: 'Oops!',
alertEmptyTruthDesc:
'Your truths list is empty. You need have at least 1 truth',
alertEmptyDaresDesc:
'Your dares list is empty. You need have at least 1 dare',
editorBtn: 'Tasks and questions editor',
play: 'Play',
}

4
src/i18n/locales/en/purchases.translation.ts

@ -6,4 +6,8 @@ export const purchases: PurchasesTranslate = { @@ -6,4 +6,8 @@ export const purchases: PurchasesTranslate = {
alertError: 'Ops, purchase failed 😢',
descError:
'There was an error processing your purchase. Please try again later.',
allPackage: 'Open all packages',
crazy: 'Open package "Crazy"',
under18: 'Open package "Under 18"',
restore: 'Restore purchases',
}

31
src/i18n/locales/ua/custom-pack.translation.ts

@ -1,4 +1,27 @@ @@ -1,4 +1,27 @@
export const customPack = {
title: 'Створіть індивідуальний пакет',
description: 'Створіть свій власний пакет із запитаннями та завданнями. Все залежить від вашої фантазії!',
}
import { CustomPack } from '~i18n/interfaces/custom-pack.interface'
export const customPack: CustomPack = {
label: 'Власний пакет',
title: 'Створити власний пакет',
description:
'Створіть свій власний пакет з правд та дій. Все залежить від вашої уяви!',
editor: 'Редактор',
placeholder: 'Пишіть тут...',
addTruth: 'Додати питання',
addDare: 'Додати дію',
viewTruths: 'Питання',
viewDares: 'Дії',
alertCreateTitle: 'Чудово! 🎉',
alertCreateDesc: 'Зіграйте прямо зараз!',
alertSaveTitle: 'Ви маєте незбережені зміни',
alertSaveDesc: 'Зберегти зміни?',
alertSaveNo: 'Ні',
alertSaveYes: 'Так',
alertEmptyTitle: 'Ой! 👀',
alertEmptyTruthDesc:
'Ваш список правд порожній. Вам потрібно мати хоча б 1 правду',
alertEmptyDaresDesc:
'Ваш список викликів порожній. Вам потрібно мати хоча б 1 виклик',
editorBtn: 'Редактор правд та дій',
play: 'Грати',
}

8
src/i18n/locales/ua/purchases.translation.ts

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
import { PurchasesTranslate } from "~i18n/interfaces/purchases.interface";
import { PurchasesTranslate } from '~i18n/interfaces/purchases.interface'
export const purchases: PurchasesTranslate = {
alertSuccess: 'Ура! Готово!',
@ -6,6 +6,8 @@ export const purchases: PurchasesTranslate = { @@ -6,6 +6,8 @@ export const purchases: PurchasesTranslate = {
alertError: 'Упс, щось не так 😢',
descError:
'Виникла помилка обробки вашої покупки. Будь ласка, спробуйте пізніше.',
allPackage: 'Відкрити всі пакети',
crazy: 'Відкрити пакет "Божевільний"',
under18: 'Відкрити пакет "До 18"',
restore: 'Відновити покупки',
}

4
src/i18n/locales/ua/settings.translation.ts

@ -8,7 +8,5 @@ export const settingTranslation: SettingLocale.Core = { @@ -8,7 +8,5 @@ export const settingTranslation: SettingLocale.Core = {
rate: 'Оцініть нас',
share: 'Поділитися програмою',
policy: 'Політика конфіденційності',
term: 'Правила та умови',
information: 'Інформація',
label: 'Чим ми можемо допомогти',
label: 'Чим ми можемо допомогти?',
}

86
src/module/common/components/buttons/button-primary.component.tsx

@ -1,18 +1,17 @@ @@ -1,18 +1,17 @@
import React, { FC, PropsWithChildren } from 'react'
import React, { FC, PropsWithChildren, useEffect } from 'react'
import {
ActivityIndicator,
Animated,
DimensionValue,
StyleProp,
StyleSheet,
TextStyle,
TouchableOpacity,
View,
ViewStyle,
} from 'react-native'
import { Txt, TxtModType } from '../txt'
import { Font } from '../../typing'
import { $size } from '../../helpers'
import { colors } from '../../colors'
import { Icon } from '../icon'
type ButtonStyleMod = 'filled' | 'outline' | 'danger'
@ -29,13 +28,20 @@ interface IButtonPrimaryProps { @@ -29,13 +28,20 @@ interface IButtonPrimaryProps {
txtColor?: string
disabled?: boolean
styleTxt?: any
colorIcon?: string
iconName?: string
animationStyle?: any
isFocused?: boolean
animation?: () => void
animationAfterOnPress?: () => void
}
export const ButtonPrimary: FC<PropsWithChildren<IButtonPrimaryProps>> = ({
children,
style,
onPress,
isLoading,
onPress,
mb = 0,
width = '100%',
@ -44,6 +50,13 @@ export const ButtonPrimary: FC<PropsWithChildren<IButtonPrimaryProps>> = ({ @@ -44,6 +50,13 @@ export const ButtonPrimary: FC<PropsWithChildren<IButtonPrimaryProps>> = ({
txtColor = colors.textPrimary,
disabled = false,
styleTxt,
colorIcon = colors.textPrimary,
iconName,
animationStyle,
animation,
isFocused,
animationAfterOnPress,
}) => {
if (isLoading) {
return (
@ -55,24 +68,47 @@ export const ButtonPrimary: FC<PropsWithChildren<IButtonPrimaryProps>> = ({ @@ -55,24 +68,47 @@ export const ButtonPrimary: FC<PropsWithChildren<IButtonPrimaryProps>> = ({
)
}
useEffect(() => {
if (animation) animation()
}, [isFocused])
const onPressButton = () => {
onPress()
animationAfterOnPress && animationAfterOnPress()
}
return (
<TouchableOpacity
activeOpacity={0.6}
disabled={disabled}
onPress={onPress}
<Animated.View
style={[
styles.container,
style,
{ marginBottom: mb, width },
disabled && { backgroundColor: colors.blue },
{
marginBottom: mb,
width,
...animationStyle,
},
]}>
<Txt
font={txtFont}
mod={txtMod}
style={[styles.txtBtn, { color: txtColor }, styleTxt]}>
{children}
</Txt>
</TouchableOpacity>
<TouchableOpacity
activeOpacity={0.6}
disabled={disabled}
onPress={onPressButton}
style={[
styles.row,
disabled && {
backgroundColor: colors.blue,
},
]}>
<Txt
font={txtFont}
mod={txtMod}
style={[styles.txtBtn, { color: txtColor }, styleTxt]}>
{children}
</Txt>
{iconName && (
<Icon name={iconName} size={24} color={colorIcon} />
)}
</TouchableOpacity>
</Animated.View>
)
}
@ -81,15 +117,25 @@ const styles = StyleSheet.create({ @@ -81,15 +117,25 @@ const styles = StyleSheet.create({
backgroundColor: colors.red,
alignItems: 'center',
justifyContent: 'center',
height: 50,
borderRadius: 60,
height: $size(50),
},
txtBtn: {
color: colors.textPrimary,
lineHeight: 24,
},
loader: {
height: $size(56, 54),
height: 55,
},
row: {
flexDirection: 'row',
columnGap: 5,
flex: 1,
alignItems: 'center',
justifyContent: 'center',
width: '100%',
borderRadius: 60,
},
})

25
src/module/common/components/buttons/button-with-icon.tsx

@ -1,25 +1,36 @@ @@ -1,25 +1,36 @@
import React from 'react'
import { StyleSheet, TouchableOpacity, ViewStyle } from 'react-native'
import React, { useRef } from 'react'
import { Animated, StyleSheet, TouchableOpacity, ViewStyle } from 'react-native'
import { colors } from '../../colors'
import { Icon } from '../icon'
import { $size } from '../../helpers'
interface IProps {
iconName: string
onPress: () => void
styleBtn?: ViewStyle
animation?: () => void
animStyle?: ViewStyle
}
export const ButtonWithIcon: React.FC<IProps> = ({
iconName,
onPress,
styleBtn,
animation,
animStyle,
}) => {
const onPressIcon = () => {
onPress()
animation && animation()
}
return (
<TouchableOpacity
style={[styles.container, styleBtn]}
onPress={onPress}>
<Icon name={iconName} size={$size(24)} color={colors.textPrimary} />
onPress={onPressIcon}>
<Animated.View style={animStyle}>
<Icon name={iconName} size={24} color={colors.textPrimary} />
</Animated.View>
</TouchableOpacity>
)
}
@ -28,8 +39,8 @@ const styles = StyleSheet.create({ @@ -28,8 +39,8 @@ const styles = StyleSheet.create({
container: {
borderRadius: 60,
backgroundColor: colors.red,
height: $size(50),
width: $size(101),
height: 50,
width: 101,
justifyContent: 'center',
alignItems: 'center',
},

3
src/module/common/components/buttons/go-back.component.tsx

@ -2,7 +2,6 @@ import React, { FC } from 'react' @@ -2,7 +2,6 @@ import React, { FC } from 'react'
import { TouchableOpacity, ViewStyle } from 'react-native'
import { colors } from '../../colors/colors'
import { Icon } from '../icon'
import { $size } from '../../helpers'
interface IProps {
onPress: () => void
@ -11,7 +10,7 @@ interface IProps { @@ -11,7 +10,7 @@ interface IProps {
export const GoBackBtn: FC<IProps> = ({ onPress, style }) => {
return (
<TouchableOpacity onPress={onPress} style={style}>
<Icon name="arrow" size={$size(24)} color={colors.turquoise} />
<Icon name="arrow" size={24} color={colors.turquoise} />
</TouchableOpacity>
)
}

10
src/module/common/components/form/form-controll-wrap.component.tsx

@ -1,7 +1,6 @@ @@ -1,7 +1,6 @@
import React, { FC, PropsWithChildren } from 'react'
import { StyleSheet, View, ViewStyle } from 'react-native'
import { Txt } from '../txt'
import { $size } from '../../helpers'
import { colors } from '../../colors'
interface FormControllWrapProps {
@ -40,21 +39,20 @@ export const FormControllWrap: FC<PropsWithChildren<FormControllWrapProps>> = ({ @@ -40,21 +39,20 @@ export const FormControllWrap: FC<PropsWithChildren<FormControllWrapProps>> = ({
const styles = StyleSheet.create({
container: {
marginBottom: $size(12),
width: '100%',
},
labelWrap: {
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: $size(5),
marginBottom: 5,
},
label: {
color: colors.secondaryText,
marginBottom: $size(8),
marginBottom: 8,
},
error: {
color: colors.red,
fontSize: $size(13),
marginTop: $size(5),
fontSize: 13,
marginTop: 5,
},
})

46
src/module/common/components/form/form-text-controll.component.tsx

@ -6,10 +6,8 @@ import { @@ -6,10 +6,8 @@ import {
View,
ViewStyle,
} from 'react-native'
import { Txt } from '../txt'
import { FormControllWrap } from './form-controll-wrap.component'
import { Font } from '../../typing'
import { $size } from '../../helpers'
import { colors } from '../../colors'
interface FormTextControllProps {
@ -21,7 +19,7 @@ interface FormTextControllProps { @@ -21,7 +19,7 @@ interface FormTextControllProps {
label?: string
error?: string
postfix?: string
renderClearPostfix?: () => React.JSX.Element
subtext?: string
inputStyle?: ViewStyle
@ -37,21 +35,11 @@ export const FormTextControll: FC<FormTextControllProps> = ({ @@ -37,21 +35,11 @@ export const FormTextControll: FC<FormTextControllProps> = ({
label,
error,
postfix,
subtext,
renderClearPostfix,
inputStyle,
style,
}) => {
const renderPostfix = () => {
if (postfix)
return (
<View style={styles.rightComponent}>
<Txt style={styles.postfix}>{postfix}</Txt>
</View>
)
}
return (
<FormControllWrap label={label} error={error} style={style}>
<View style={styles.inputContainer}>
@ -66,9 +54,8 @@ export const FormTextControll: FC<FormTextControllProps> = ({ @@ -66,9 +54,8 @@ export const FormTextControll: FC<FormTextControllProps> = ({
placeholderTextColor="#A0A3BD"
{...inputProps}
/>
{renderPostfix()}
{renderClearPostfix && renderClearPostfix()}
</View>
{subtext ? <Txt style={styles.subtext}>{subtext}</Txt> : null}
</FormControllWrap>
)
}
@ -78,40 +65,19 @@ const styles = StyleSheet.create({ @@ -78,40 +65,19 @@ const styles = StyleSheet.create({
borderColor: '#FB5450',
},
inputContainer: {
position: 'relative',
paddingRight: 1,
},
input: {
borderColor: colors.secondaryText,
borderWidth: 1,
height: $size(50),
paddingHorizontal: $size(18),
height: 50,
paddingHorizontal: 18,
borderRadius: 16,
color: colors.textPrimary,
fontFamily: Font.Roboto400,
fontSize: $size(15),
lineHeight: $size(20),
fontSize: 16,
},
inputActive: {
borderColor: '#FB5450',
},
rightComponent: {
position: 'absolute',
right: 0,
top: 0,
height: 56,
justifyContent: 'center',
alignItems: 'center',
paddingRight: 20,
// width: 100,
},
postfix: {
color: '#9693AC',
fontSize: $size(15),
},
subtext: {
color: '#808080',
fontSize: $size(12),
marginBottom: 4,
},
})

23
src/module/common/components/header/header.component.tsx

@ -2,7 +2,6 @@ import React, { FC } from 'react' @@ -2,7 +2,6 @@ import React, { FC } from 'react'
import { StyleSheet, TouchableOpacity, View } from 'react-native'
import { colors } from '../../colors'
import { Icon } from '../icon'
import { $size } from '../../helpers'
import { Txt } from '../txt'
import { Font } from '../../typing'
import { useNav } from '~module/common/hooks'
@ -13,12 +12,14 @@ interface IProps { @@ -13,12 +12,14 @@ interface IProps {
onPressRight?: () => any
leftIcon?: string
rightIcon?: string
colorRightIcon?: string
gamer?: boolean
}
export const Header: FC<IProps> = ({
onPressLeft,
leftIcon = 'arrow',
rightIcon,
colorRightIcon = colors.turquoise,
title,
gamer,
onPressRight,
@ -38,7 +39,7 @@ export const Header: FC<IProps> = ({ @@ -38,7 +39,7 @@ export const Header: FC<IProps> = ({
onPress={onPressLeft || goBack}>
<Icon
name={leftIcon}
size={$size(24)}
size={24}
color={colors.turquoise}
/>
</TouchableOpacity>
@ -50,9 +51,7 @@ export const Header: FC<IProps> = ({ @@ -50,9 +51,7 @@ export const Header: FC<IProps> = ({
styles.titleContainer,
gamer && { backgroundColor: colors.darkPurple },
]}>
<Txt mod="xxl" style={styles.title}>
{title}
</Txt>
<Txt style={styles.title}>{title}</Txt>
</View>
<View style={styles.button}>
@ -62,8 +61,8 @@ export const Header: FC<IProps> = ({ @@ -62,8 +61,8 @@ export const Header: FC<IProps> = ({
onPress={onPressRight}>
<Icon
name={rightIcon}
size={$size(24)}
color={colors.turquoise}
size={24}
color={colorRightIcon}
/>
</TouchableOpacity>
)}
@ -74,7 +73,7 @@ export const Header: FC<IProps> = ({ @@ -74,7 +73,7 @@ export const Header: FC<IProps> = ({
const styles = StyleSheet.create({
header: {
height: $size(54),
height: 54,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
@ -83,17 +82,17 @@ const styles = StyleSheet.create({ @@ -83,17 +82,17 @@ const styles = StyleSheet.create({
title: {
color: colors.turquoise,
fontFamily: Font.Roboto700,
fontSize: 24,
lineHeight: 24,
},
titleContainer: {
borderRadius: 60,
minWidth: $size(130),
padding: $size(14),
minWidth: 130,
padding: 14,
justifyContent: 'center',
alignItems: 'center',
},
button: {
width: $size(24),
height: $size(24),
alignItems: 'center',
justifyContent: 'center',
},

2
src/module/common/components/icon/icon.component.tsx

@ -8,7 +8,7 @@ const FontelloIcon = createIconSetFromFontello(fontelloConfig) @@ -8,7 +8,7 @@ const FontelloIcon = createIconSetFromFontello(fontelloConfig)
interface IProps {
name: string
size: number
color?: ColorValue
color?: any
style?: any
onPress?: () => void
buttonStyle?: ViewStyle | ViewStyle[]

7
src/module/common/components/layout/screen-layout-content.component.tsx

@ -7,7 +7,6 @@ import { @@ -7,7 +7,6 @@ import {
ViewStyle,
} from 'react-native'
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view'
import { $size } from '../../helpers'
interface ScreenLayoutContentProps {
children: JSX.Element | JSX.Element[]
@ -28,7 +27,7 @@ interface ScreenLayoutContentProps { @@ -28,7 +27,7 @@ interface ScreenLayoutContentProps {
export const ScreenLayoutContent: FC<ScreenLayoutContentProps> = ({
children,
header,
paddingTop = $size(16),
paddingTop = 16,
paddingHorizontal = 16,
needScroll,
scrollStyle,
@ -51,7 +50,7 @@ export const ScreenLayoutContent: FC<ScreenLayoutContentProps> = ({ @@ -51,7 +50,7 @@ export const ScreenLayoutContent: FC<ScreenLayoutContentProps> = ({
contentContainerStyle={[
{
paddingTop,
paddingHorizontal: $size(paddingHorizontal),
paddingHorizontal: paddingHorizontal,
},
scrollStyle,
]}>
@ -72,7 +71,7 @@ export const ScreenLayoutContent: FC<ScreenLayoutContentProps> = ({ @@ -72,7 +71,7 @@ export const ScreenLayoutContent: FC<ScreenLayoutContentProps> = ({
viewStyle,
{
paddingTop,
paddingHorizontal: $size(paddingHorizontal),
paddingHorizontal: paddingHorizontal,
flex: 1,
},
]}

4
src/module/common/components/layout/screen-layout.component.tsx

@ -1,7 +1,6 @@ @@ -1,7 +1,6 @@
import React, { ReactElement } from 'react'
import {
ColorValue,
Platform,
StatusBar,
StyleSheet,
View,
@ -13,7 +12,6 @@ import { ScreenLayoutContent } from './screen-layout-content.component' @@ -13,7 +12,6 @@ import { ScreenLayoutContent } from './screen-layout-content.component'
import _ from 'lodash'
import { gcService } from '../../tools'
import { colors } from '../../colors'
import { $size } from '../../helpers'
export interface ScreenLayoutProps {
children: JSX.Element | JSX.Element[]
@ -47,7 +45,7 @@ export const ScreenLayout = ({ @@ -47,7 +45,7 @@ export const ScreenLayout = ({
const heightTopDefault = _.defaultTo(
Math.max(insets.top, gcService.get('insetsTop')),
$size(44),
44,
)
const safeAreaLayOut = () => {

29
src/module/common/components/txt/txt.component.tsx

@ -1,29 +1,28 @@ @@ -1,29 +1,28 @@
import React, { FC } from 'react'
import { Text, TextStyle, TextProps } from 'react-native'
import { $size } from '../../helpers'
import { Font } from '../../typing'
import { colors } from '../../colors'
export type TxtModType = 's' | 'es' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl'
const sizes = {
s: $size(10, 8),
es: $size(12, 10),
sm: $size(14, 12),
md: $size(16, 14),
lg: $size(18, 16),
xl: $size(20, 18),
xxl: $size(24, 22),
s: 10,
es: 12,
sm: 14,
md: 16,
lg: 18,
xl: 20,
xxl: 24,
}
const lineHeights = {
s: $size(12, 10),
es: $size(14, 12),
sm: $size(16, 14),
md: $size(18, 16),
lg: $size(20, 18),
xl: $size(22, 20),
xxl: $size(26, 24),
s: 12,
es: 14,
sm: 16,
md: 18,
lg: 20,
xl: 22,
xxl: 26,
}
export interface TxtProps extends TextProps {

7
src/module/common/svg-icons/icons-svg.tsx

@ -1,6 +1,5 @@ @@ -1,6 +1,5 @@
import React from 'react'
import { SvgXml } from 'react-native-svg'
import { $size } from '../helpers'
export const EngSvg = () => {
const svgXml = `
@ -22,7 +21,7 @@ export const EngSvg = () => { @@ -22,7 +21,7 @@ export const EngSvg = () => {
</svg>
`
return <SvgXml width={$size(24)} height={$size(24)} xml={svgXml} />
return <SvgXml width={24} height={24} xml={svgXml} />
}
export const UaeSvg = () => {
@ -43,7 +42,7 @@ export const UaeSvg = () => { @@ -43,7 +42,7 @@ export const UaeSvg = () => {
</svg>
`
return <SvgXml width={$size(24)} height={$size(24)} xml={svgXml} />
return <SvgXml width={24} height={24} xml={svgXml} />
}
export const UaSvg = () => {
@ -61,7 +60,7 @@ export const UaSvg = () => { @@ -61,7 +60,7 @@ export const UaSvg = () => {
</svg>
`
return <SvgXml width={$size(24)} height={$size(24)} xml={svgXml} />
return <SvgXml width={24} height={24} xml={svgXml} />
}
export const GlassSvg = () => {

1
src/module/common/typing/enums/index.ts

@ -3,3 +3,4 @@ export * from './fonts.enum' @@ -3,3 +3,4 @@ export * from './fonts.enum'
export * from './storage-key.enum'
export * from './language.enum'
export * from './products.enum';
export * from './type-custom.enum';

10
src/module/common/typing/enums/route-keys.enum.ts

@ -1,16 +1,14 @@ @@ -1,16 +1,14 @@
export enum RouteKey {
Onboarding = 'Onboarding',
LanguageSelect = 'LanguageSelect',
SettingsGroup = 'SettingsGroup',
Game = 'Game',
Loading = 'Loading',
Packages = 'Packages',
Questions = 'Questions',
}
export enum SettingsKey {
TruthOrDare = 'TruthOrDare',
CustomPackage = 'CustomPackage',
CustomEditor = 'CustomEditor',
Settings = 'Settings',
PrivacyPolicy = 'PrivacyPolicy',
Purchases = 'Purchases',
WriteToUs = 'WriteToUs'
WriteToUs = 'WriteToUs',
}

1
src/module/common/typing/enums/storage-key.enum.ts

@ -3,4 +3,5 @@ export enum StorageKey { @@ -3,4 +3,5 @@ export enum StorageKey {
Language = 'LANG_SELECTED',
Purchases = 'Purchases',
Products = 'Products',
CustomPackage = 'CustomPackage',
}

4
src/module/common/typing/enums/type-custom.enum.ts

@ -0,0 +1,4 @@ @@ -0,0 +1,4 @@
export enum TypeCustom {
Questions = 'questions',
Dares = 'dares',
}

8
src/module/common/typing/interfaces/game-item.ts

@ -1,7 +1,9 @@ @@ -1,7 +1,9 @@
import { Language } from "../enums";
export interface GameItem {
id: number;
isDare: boolean;
en: string;
ua: string;
hi: string;
en: Language;
ua: Language;
hi: Language;
}

33
src/module/common/widgets/alert-confirm/alert-confirm-widget.component.tsx

@ -3,7 +3,6 @@ import { StyleSheet, TouchableOpacity, View, ViewStyle } from 'react-native' @@ -3,7 +3,6 @@ import { StyleSheet, TouchableOpacity, View, ViewStyle } from 'react-native'
import _ from 'lodash'
import { ModalComponent, Txt } from '../../components'
import { Font } from '../../typing'
import { $size } from '../../helpers'
import { colors } from '../../colors'
import { AppEvents, appEvents } from '../../events'
import { useEventsListener } from '../../hooks'
@ -107,7 +106,7 @@ export const AlertConfirmWidget: FC = () => { @@ -107,7 +106,7 @@ export const AlertConfirmWidget: FC = () => {
useNativeDriverForBackdrop={true}
backdropTransitionOutTiming={400}
hideModalContentWhileAnimating={true}
backdropColor={'rgba(0,0,0,0.7)'}
backdropColor={'rgba(0,0,0,0.9)'}
animationIn="pulse"
isVisible={Boolean(isVisible)}
onBackdropPress={close}
@ -143,38 +142,38 @@ const styles = StyleSheet.create({ @@ -143,38 +142,38 @@ const styles = StyleSheet.create({
container: {
borderRadius: 16,
backgroundColor: colors.lightPurple,
paddingHorizontal: $size(24),
paddingVertical: $size(32),
paddingHorizontal: 24,
paddingVertical: 32,
width: '100%',
},
content: {
justifyContent: 'center',
alignItems: 'center',
gap: $size(8),
marginBottom: $size(32),
gap: 8,
marginBottom: 32,
},
title: {
fontSize: $size(24),
lineHeight: $size(34),
fontSize: 24,
lineHeight: 34,
color: colors.turquoise,
textAlign: 'center',
fontWeight: '700',
fontFamily: Font.Roboto700,
},
subtitle: {
textAlign: 'center',
fontWeight: '700',
fontSize: $size(18),
lineHeight: $size(28),
fontFamily: Font.Roboto700,
fontSize: 18,
lineHeight: 28,
},
buttonsContainer: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
columnGap: $size(11),
columnGap: 11,
},
red: {
@ -182,10 +181,10 @@ const styles = StyleSheet.create({ @@ -182,10 +181,10 @@ const styles = StyleSheet.create({
},
button: {
width: $size(134),
height: $size(50),
paddingHorizontal: $size(16),
paddingVertical: $size(12),
width: 134,
height: 50,
paddingHorizontal: 16,
paddingVertical: 12,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: colors.darkPurple,

30
src/module/common/widgets/alert/alert-widget.component.tsx

@ -1,11 +1,11 @@ @@ -1,11 +1,11 @@
import React, { useEffect, useRef, useState } from 'react'
import { StyleSheet, View } from 'react-native'
import _, { size } from 'lodash'
import _ from 'lodash'
import { ButtonPrimary, ModalComponent, Txt } from '../../components'
import { $size } from '../../helpers'
import { colors } from '../../colors'
import { AppEvents, appEvents } from '../../events'
import { useEventsListener } from '../../hooks'
import { Font } from '~module/common/typing'
interface IContent {
title: string
@ -83,7 +83,7 @@ export const AlertWidget = () => { @@ -83,7 +83,7 @@ export const AlertWidget = () => {
<ButtonPrimary
onPress={onConfirm}
mb={content?.bottomTxt ? $size(20) : 0}>
mb={content?.bottomTxt ? 20 : 0}>
{content.btnText}
</ButtonPrimary>
@ -97,36 +97,36 @@ const styles = StyleSheet.create({ @@ -97,36 +97,36 @@ const styles = StyleSheet.create({
container: {
borderRadius: 16,
backgroundColor: colors.lightPurple,
paddingHorizontal: $size(24),
paddingVertical: $size(32),
paddingHorizontal: 24,
paddingVertical: 32,
width: '100%',
},
content: {
justifyContent: 'center',
alignItems: 'center',
gap: $size(8),
marginBottom: $size(32),
gap: 8,
marginBottom: 32,
},
title: {
fontSize: $size(24),
lineHeight: $size(34),
fontSize: 24,
lineHeight: 34,
color: colors.turquoise,
textAlign: 'center',
fontWeight: '700',
fontFamily: Font.Roboto700,
},
subtitle: {
textAlign: 'center',
fontWeight: '700',
fontSize: $size(18),
lineHeight: $size(28),
fontFamily: Font.Roboto700,
fontSize: 18,
lineHeight: 28,
},
bottomTxt: {
fontSize: $size(16),
lineHeight: $size(24),
fontSize: 16,
lineHeight: 24,
textAlign: 'center',
},
})

7
src/module/common/widgets/bottom-sheet/atoms/action-sheet-button.atom.tsx

@ -3,7 +3,6 @@ import React, { FC } from 'react' @@ -3,7 +3,6 @@ import React, { FC } from 'react'
import { StyleSheet } from 'react-native'
import { TouchableOpacity } from 'react-native'
import { Icon, Txt } from '../../../components'
import { $size } from '../../../helpers'
import { colors } from '../../../colors'
interface IProps {
@ -22,7 +21,7 @@ export const ActionSheetButtonAtom: FC<IProps> = ({ @@ -22,7 +21,7 @@ export const ActionSheetButtonAtom: FC<IProps> = ({
}) => {
const renderIcon = () => {
if (iconName) {
return <Icon name={iconName} size={$size(28)} color={'#007AFF'} />
return <Icon name={iconName} size={28} color={'#007AFF'} />
}
if (icon) {
@ -50,9 +49,9 @@ const styles = StyleSheet.create({ @@ -50,9 +49,9 @@ const styles = StyleSheet.create({
flexDirection: 'row',
alignItems: 'center',
backgroundColor: colors.lightPurple,
padding: $size(15),
padding: 15,
},
label: {
marginLeft: $size(10),
marginLeft: 10,
},
})

11
src/module/common/widgets/bottom-sheet/atoms/bottom-sheet-view.atom.tsx

@ -4,7 +4,6 @@ import ActionSheet, { SheetProps } from 'react-native-actions-sheet' @@ -4,7 +4,6 @@ import ActionSheet, { SheetProps } from 'react-native-actions-sheet'
import { SheetManager } from 'react-native-actions-sheet'
import _ from 'lodash'
import { Txt } from '../../../components'
import { $size } from '../../../helpers'
import { ActionSheetButtonAtom } from './action-sheet-button.atom'
import { colors } from '../../../colors'
@ -54,7 +53,7 @@ export const BottomSheetView: FC<SheetProps<IPayloadSheet[]>> = ({ @@ -54,7 +53,7 @@ export const BottomSheetView: FC<SheetProps<IPayloadSheet[]>> = ({
const styles = StyleSheet.create({
button: {
paddingVertical: $size(18),
paddingVertical: 18,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: colors.darkPurple,
@ -62,8 +61,8 @@ const styles = StyleSheet.create({ @@ -62,8 +61,8 @@ const styles = StyleSheet.create({
},
container: {
backgroundColor: colors.lightPurple,
paddingHorizontal: $size(12),
paddingBottom: $size(35),
paddingHorizontal: 12,
paddingBottom: 35,
},
txtContent: {
color: '#FFFFFF',
@ -71,8 +70,8 @@ const styles = StyleSheet.create({ @@ -71,8 +70,8 @@ const styles = StyleSheet.create({
},
actionContainer: {
borderRadius: 16,
padding: $size(10),
padding: 10,
flexDirection: 'column',
marginBottom: $size(16),
marginBottom: 16,
},
})

90
src/module/custom-package/atoms/custom-block.atom.tsx

@ -0,0 +1,90 @@ @@ -0,0 +1,90 @@
import React, { FC, useEffect, useRef } from 'react'
import { Animated, LayoutAnimation, StyleSheet } from 'react-native'
import { TouchableOpacity } from 'react-native-gesture-handler'
import { Icon, Txt, colors } from '~module/common'
interface IProps {
text: string
onDelete: () => void
delay?: number
}
export const CustomBlock: FC<IProps> = ({ text, onDelete, delay }) => {
const deleteAnim = useRef(new Animated.Value(0)).current
const renderAnim = useRef(new Animated.Value(0)).current
const onPressDelete = () => {
Animated.spring(deleteAnim, {
toValue: 1,
useNativeDriver: true,
}).start()
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
setTimeout(onDelete, 200)
}
useEffect(() => {
Animated.spring(renderAnim, {
toValue: 1,
useNativeDriver: true,
delay,
}).start()
}, [])
return (
<Animated.View
style={[
styles.block,
{
transform: [
{
translateX: deleteAnim.interpolate({
inputRange: [0, 1],
outputRange: [0, 400],
}),
},
{
translateY: renderAnim.interpolate({
inputRange: [0, 1],
outputRange: [-100, 0],
}),
},
],
opacity:
renderAnim ||
deleteAnim.interpolate({
inputRange: [0, 1],
outputRange: [1, 0],
}),
},
]}>
<Txt style={styles.blockTxt}>{text}</Txt>
<TouchableOpacity
style={styles.iconContainer}
onPress={onPressDelete}>
<Icon name={'clear'} size={24} color={colors.purple} />
</TouchableOpacity>
</Animated.View>
)
}
const styles = StyleSheet.create({
block: {
paddingLeft: 16,
borderRadius: 20,
backgroundColor: colors.darkPurple,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
columnGap: 8,
},
blockTxt: {
fontSize: 16,
lineHeight: 24,
color: colors.purple,
},
iconContainer: {
padding: 16,
},
})

34
src/module/custom-package/atoms/empty-items.atom.tsx

@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
import React, { FC } from 'react'
import { StyleSheet, View } from 'react-native'
import { Font, Txt, colors } from '~module/common'
interface IProps {
currentText: string
}
export const EmptyItemsAtom: FC<IProps> = ({ currentText }) => {
return (
<View style={styles.container}>
<Txt
style={
styles.emptyTxt
}>{`Your list of ${currentText} is empty`}</Txt>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
emptyTxt: {
fontSize: 22,
lineHeight: 32,
fontFamily: Font.Roboto700,
color: colors.purple,
marginTop: -50,
},
})

2
src/module/custom-package/atoms/index.ts

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
export * from './empty-items.atom'
export * from './custom-block.atom';

2
src/module/custom-package/index.ts

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
export * from './screens'
export * from './atoms'

268
src/module/custom-package/screens/custom-package-editor.screen.tsx

@ -0,0 +1,268 @@ @@ -0,0 +1,268 @@
import React, { FC, useMemo, useState } from 'react'
import { StyleSheet, View, TouchableOpacity } from 'react-native'
import {
ButtonPrimary,
Font,
FormTextControll,
Header,
Icon,
ScreenLayout,
StorageKey,
appEvents,
colors,
TypeCustom,
useAppDispatch,
useNav,
RouteKey,
} from '~module/common'
import { CustomBlock, EmptyItemsAtom } from '../atoms'
import _ from 'lodash'
import { useSelector } from 'react-redux'
import {
selectCustomPackage,
selectCustomPackageFromStore,
setDares,
setQuestions,
updateCustomPackage,
updateCustomPackageFromStore,
} from '~store/slices'
import AsyncStorage from '@react-native-async-storage/async-storage'
import { useTranslation } from 'react-i18next'
export const CustomPackageEditorScreen: FC = () => {
const dispatch = useAppDispatch()
const { t } = useTranslation()
const nav = useNav()
const customPackage = useSelector(selectCustomPackage)
const customPackageFromStore = useSelector(selectCustomPackageFromStore)
const isHaveNewChanges = useMemo(
() => _.isEqual(customPackageFromStore, customPackage),
[customPackageFromStore, customPackage],
)
const [value, setValue] = useState('')
const [activeMod, setActiveMod] = useState(TypeCustom.Questions)
const onView = (mod: TypeCustom) => {
setActiveMod(mod)
}
const clearValue = () => {
setValue('')
}
const OnAddQuestion = () => {
if (!value) return
dispatch(setQuestions([...customPackage.questions, value]))
setActiveMod(TypeCustom.Questions)
clearValue()
}
const OnAddDare = () => {
if (!value) return
dispatch(setDares([...customPackage.dares, value]))
setActiveMod(TypeCustom.Dares)
clearValue()
}
const onDeleteItem = (indexItem: number) => {
const newCustomItems = customPackage[activeMod].filter(
(it, index) => index !== indexItem,
)
activeMod === TypeCustom.Questions
? dispatch(setQuestions(newCustomItems))
: dispatch(setDares(newCustomItems))
}
const saveCustomPackage = async () => {
try {
const newCustomPackage = JSON.stringify(customPackage)
await AsyncStorage.setItem(
StorageKey.CustomPackage,
newCustomPackage,
)
dispatch(updateCustomPackageFromStore(customPackage))
} catch (error) {
console.error('Error saving custom package:', error)
}
}
const onSubmit = () => {
if (isHaveNewChanges) return
saveCustomPackage()
nav.navigate(RouteKey.CustomPackage)
}
const goBack = () => {
if (isHaveNewChanges) return nav.goBack()
appEvents.emit('confirm', {
title: t('customPack.alertSaveTitle'),
subtitle: t('customPack.alertSaveDesc'),
cancelBtnText: t('customPack.alertSaveNo'),
confirmBtnText: t('customPack.alertSaveYes'),
isRedButton: true,
buttons: [
{
onPress: () => {
nav.goBack()
dispatch(updateCustomPackage(customPackageFromStore))
},
},
{
onPress: onSubmit,
},
],
})
}
const renderClear = () => (
<TouchableOpacity style={styles.clearIcon} onPress={clearValue}>
<Icon
name={'clear'}
size={24}
color={value ? colors.textPrimary : colors.darkPurple}
/>
</TouchableOpacity>
)
return (
<ScreenLayout
headerComponent={
<Header
title={t('customPack.editor')}
rightIcon="all_packages"
onPressLeft={goBack}
onPressRight={onSubmit}
colorRightIcon={
_.isEqual(customPackageFromStore, customPackage)
? colors.purple
: colors.turquoise
}
/>
}
needScroll
scrollStyle={{ flexGrow: 1 }}>
<FormTextControll
value={value}
onChange={val => setValue(val)}
inputProps={{
placeholder: t('customPack.placeholder'),
placeholderTextColor: colors.darkPurple,
}}
inputStyle={styles.input}
renderClearPostfix={renderClear}
/>
<View style={styles.line} />
<View style={styles.row}>
<ButtonPrimary
mb={0}
style={styles.btn}
onPress={OnAddQuestion}>
{t('customPack.addTruth')}
</ButtonPrimary>
<ButtonPrimary mb={0} style={styles.btn} onPress={OnAddDare}>
{t('customPack.addDare')}
</ButtonPrimary>
</View>
<View style={[styles.row, { marginTop: 15 }]}>
<ButtonPrimary
styleTxt={styles.txt}
style={styles.btnTxt}
txtColor={
activeMod === TypeCustom.Questions
? colors.turquoise
: colors.purple
}
onPress={() => onView(TypeCustom.Questions)}>
{t('customPack.viewTruths')}
</ButtonPrimary>
<ButtonPrimary
style={styles.btnTxt}
styleTxt={styles.txt}
txtColor={
activeMod === TypeCustom.Dares
? colors.turquoise
: colors.purple
}
onPress={() => onView(TypeCustom.Dares)}>
{t('customPack.viewDares')}
</ButtonPrimary>
</View>
{_.isEmpty(customPackage[activeMod]) ? (
<EmptyItemsAtom currentText={activeMod} />
) : (
<>
<View style={styles.customBody}>
{customPackage[activeMod].map((it, index) => (
<CustomBlock
key={it}
text={it}
delay={index * 100}
onDelete={() => onDeleteItem(index)}
/>
))}
</View>
</>
)}
</ScreenLayout>
)
}
const styles = StyleSheet.create({
input: {
borderRadius: 60,
backgroundColor: colors.lightPurple,
borderWidth: 0,
fontSize: 18,
color: colors.purple,
},
line: {
height: 1,
backgroundColor: colors.lightPurple,
marginVertical: 24,
},
row: {
flexDirection: 'row',
justifyContent: 'space-between',
columnGap: 15,
},
btn: {
backgroundColor: colors.darkPurple,
flex: 1,
},
btnTxt: {
backgroundColor: null,
flex: 1,
borderWidth: 2,
borderColor: colors.darkPurple,
},
txt: {
fontSize: 18,
fontFamily: Font.Roboto700,
lineHeight: 28,
},
clearIcon: {
position: 'absolute',
top: 3,
right: 10,
padding: 10,
},
customBody: {
marginTop: 20,
marginBottom: 20,
rowGap: 16,
},
})

119
src/module/custom-package/screens/custom-package-play.screen.tsx

@ -0,0 +1,119 @@ @@ -0,0 +1,119 @@
import _ from 'lodash'
import React, { FC } from 'react'
import { useTranslation } from 'react-i18next'
import { StyleSheet, View } from 'react-native'
import { useSelector } from 'react-redux'
import {
ButtonPrimary,
Header,
ProductsEnum,
RouteKey,
ScreenLayout,
appEvents,
colors,
useNav,
} from '~module/common'
import { purchasesService } from '~module/settings'
import { selectCustomPackage } from '~store/slices'
export const CustomPackagePreviewScreen: FC = () => {
const { t } = useTranslation()
const nav = useNav()
const customPackage = useSelector(selectCustomPackage)
const goToSettings = () => {
nav.navigate(RouteKey.Settings)
}
const goToCustomEditor = () => {
nav.navigate(RouteKey.CustomEditor)
}
const goToGame = () => {
const isPurchased = checkIsPurchasedCustomPack()
if (!isPurchased) return nav.navigate(RouteKey.Purchases)
const isFullCustomPack = checkIsFullCustomPack()
if (!isFullCustomPack) return
nav.navigate(RouteKey.Game, {
packageName: 'My package',
isCustom: true,
})
}
const checkIsPurchasedCustomPack = () => {
return purchasesService.purchasedProducts.includes(ProductsEnum.All)
}
const checkIsFullCustomPack = () => {
const isEmptyTruths = _.isEmpty(customPackage.questions)
const isEmptyDares = _.isEmpty(customPackage.dares)
if (isEmptyTruths) {
appEvents.emit('alert', {
title: t('customPack.alertEmptyTitle'),
subtitle: t('customPack.alertEmptyTruthDesc'),
onClose: () => goToCustomEditor(),
})
return false
}
if (isEmptyDares) {
appEvents.emit('alert', {
title: t('customPack.alertEmptyTitle'),
subtitle: t('customPack.alertEmptyDaresDesc'),
onClose: () => goToCustomEditor(),
})
return false
}
return true
}
return (
<ScreenLayout
headerComponent={
<Header
title={t('customPack.label')}
rightIcon="setting"
onPressRight={goToSettings}
/>
}>
<View style={styles.container}>
<ButtonPrimary
style={styles.editorBtn}
onPress={goToCustomEditor}>
{t('customPack.editorBtn')}
</ButtonPrimary>
<ButtonPrimary
style={styles.playBtn}
iconName="play"
onPress={goToGame}>
{t('customPack.play')}
</ButtonPrimary>
</View>
</ScreenLayout>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
rowGap: 24,
paddingBottom: 50,
},
playBtn: {
flexDirection: 'row',
columnGap: 8,
},
editorBtn: {
backgroundColor: colors.darkPurple,
},
})

2
src/module/custom-package/screens/index.ts

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
export * from './custom-package-play.screen';
export * from './custom-package-editor.screen';

3
src/module/game/animations/index.ts

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
export * from './use-animation-button'
export * from './use-animation-icons-button.hook';
export * from './use-animation-truth-or-dare.hook';

39
src/module/game/animations/use-animation-button.ts

@ -0,0 +1,39 @@ @@ -0,0 +1,39 @@
import { useRef } from 'react'
import { Animated, Easing } from 'react-native'
import { colors } from '~module/common'
export const useAnimationButton = () => {
const buttonConfigs = [
{ delay: 400, ref: useRef(new Animated.Value(0)) },
{ delay: 1200, ref: useRef(new Animated.Value(0)) },
{ delay: 800, ref: useRef(new Animated.Value(0)) },
]
const startAnimationsBorder = buttonConfigs.map(
({ delay, ref }) =>
() =>
Animated.timing(ref.current, {
toValue: 1,
duration: 600,
delay,
easing: Easing.linear,
useNativeDriver: true,
}).start(() => ref.current.setValue(0)),
)
const interpolatedColors = buttonConfigs.map(({ ref }) =>
ref.current.interpolate({
inputRange: [0, 0.5, 1],
outputRange: [colors.darkPurple, colors.blue, colors.darkPurple],
}),
)
const animBorderStyles = interpolatedColors.map(color => ({
borderColor: color,
}))
return {
animBorderStyles,
startAnimationsBorder,
}
}

48
src/module/game/animations/use-animation-icons-button.hook.ts

@ -0,0 +1,48 @@ @@ -0,0 +1,48 @@
import { useRef } from 'react'
import { Animated } from 'react-native'
export const useAnimationIconsButton = () => {
const rotateIcon = useRef(new Animated.Value(0)).current
const scaleIcon = useRef(new Animated.Value(0)).current
const animationRotate = () =>
Animated.spring(rotateIcon, {
toValue: 1,
useNativeDriver: true,
stiffness: 20,
}).start(() => rotateIcon.setValue(0))
const animationScale = () =>
Animated.spring(scaleIcon, {
toValue: 1,
useNativeDriver: true,
stiffness: 100,
}).start(() => scaleIcon.setValue(0))
const animRotateIconStyle = {
transform: [
{
rotate: rotateIcon.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '360deg'],
}),
},
],
}
const animScaleIconStyle = {
transform: [
{
scale: scaleIcon.interpolate({
inputRange: [0, 0.5, 1],
outputRange: [1, 1.2, 1],
}),
},
],
}
return {
animRotate: { style: animRotateIconStyle, func: animationRotate },
animScale: { style: animScaleIconStyle, func: animationScale },
}
}

74
src/module/game/animations/use-animation-truth-or-dare.hook.ts

@ -0,0 +1,74 @@ @@ -0,0 +1,74 @@
import { useRef } from 'react'
import { Animated } from 'react-native'
export const useAnimationTruthOrDare = () => {
const animValue = useRef(new Animated.Value(0)).current
const animText = useRef(new Animated.Value(1)).current
const startAnimation = () => {
Animated.sequence([
Animated.timing(animText, {
toValue: 0,
duration: 200,
useNativeDriver: true,
}),
Animated.timing(animText, {
toValue: 1,
delay: 400,
useNativeDriver: true,
}),
]).start()
Animated.sequence([
Animated.timing(animValue, {
toValue: 1,
useNativeDriver: true,
}),
Animated.timing(animValue, {
toValue: 0,
useNativeDriver: true,
}),
]).start()
}
const animStyleContainer = {
transform: [
{
translateY: animValue.interpolate({
inputRange: [0, 1],
outputRange: [0, 20],
}),
},
{
rotateX: animValue.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '180deg'],
}),
},
],
}
const animStyleText = {
transform: [
{
scale: animText.interpolate({
inputRange: [0, 0.5, 0.75, 1],
outputRange: [0, 0.1, 0.5, 1],
}),
},
{
translateY: animValue.interpolate({
inputRange: [0, 1],
outputRange: [0, 250],
}),
},
],
}
return {
startAnimation,
animStyle: {
text: animStyleText,
container: animStyleContainer,
},
}
}

2
src/module/game/components/index.ts

@ -1 +1 @@ @@ -1 +1 @@
export * from './question-block'
export * from './truth-or-dare-view'

80
src/module/game/components/question-block.tsx

@ -1,80 +0,0 @@ @@ -1,80 +0,0 @@
import { StyleSheet, View, Text } from 'react-native'
import { colors } from '../../common/colors'
import React, { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { GameItem } from '../../common/typing/interfaces/game-item'
import { $size, Icon, Txt, useAppDispatch, useAppSelector } from '../../common'
import {
getShuffledDares,
getShuffledTruths,
getStep,
resetSteps,
selectShuffled,
shuffleItems,
} from '../../../store/slices'
interface IProps {
isQuestions: boolean
}
export const QuestionBlock: React.FC<IProps> = ({ isQuestions }) => {
const dispatch = useAppDispatch()
const { i18n } = useTranslation()
const lang = i18n.language
const currentStep = useAppSelector(getStep)
const shuffledTruths = useAppSelector(getShuffledTruths)
const shuffledDares = useAppSelector(getShuffledDares)
const gameItems = useAppSelector(selectShuffled)
const dares = gameItems.filter(dare => dare.isDare)
const questions = gameItems.filter(question => !question.isDare)
console.log(dares)
useEffect(() => {
if (currentStep >= questions.length) {
dispatch(resetSteps())
dispatch(shuffleItems())
}
if (currentStep >= dares.length) {
dispatch(resetSteps())
dispatch(shuffleItems())
}
}, [currentStep])
return (
<View style={styles.container}>
<Icon name="magic-star" size={$size(24)} style={styles.starIcon} />
<Txt style={styles.text}>
{currentStep < questions.length &&
isQuestions &&
questions[currentStep][lang as keyof GameItem]}
{currentStep < dares.length &&
!isQuestions &&
dares[currentStep][lang as keyof GameItem]}
</Txt>
</View>
)
}
const styles = StyleSheet.create({
container: {
paddingHorizontal: $size(16),
paddingVertical: $size(32),
backgroundColor: colors.darkPurple,
borderRadius: 20,
alignItems: 'center',
},
starIcon: {
marginBottom: $size(14),
},
text: {
color: colors.purple,
fontSize: $size(22),
lineHeight: $size(32),
textAlign: 'center',
},
})

86
src/module/game/components/truth-or-dare-view.tsx

@ -0,0 +1,86 @@ @@ -0,0 +1,86 @@
import React, { useEffect, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import {
Font,
Icon,
Language,
TypeCustom,
colors,
useAppDispatch,
useAppSelector,
} from '../../common'
import {
getStep,
resetSteps,
shuffleCustom,
shuffleItems,
} from '../../../store/slices'
import { Animated, StyleSheet } from 'react-native'
import { useAnimationTruthOrDare } from '../animations'
interface IProps {
items: any[]
isCustom?: TypeCustom | undefined
}
export const TruthOrDareView: React.FC<IProps> = ({ items, isCustom }) => {
const dispatch = useAppDispatch()
const { i18n } = useTranslation()
const currentStep = useAppSelector(getStep)
const memoItems = useMemo(() => items, [items])
const { startAnimation, animStyle } = useAnimationTruthOrDare()
useEffect(() => {
if (currentStep == memoItems.length) {
dispatch(shuffleItems())
dispatch(resetSteps())
isCustom && dispatch(shuffleCustom())
}
startAnimation()
}, [currentStep])
if (currentStep === 0) {
startAnimation()
}
const content = isCustom
? memoItems?.[currentStep]
: memoItems?.[currentStep]?.[i18n.language as Language]
return (
<Animated.View style={[styles.container, animStyle.container]}>
<Icon
name="magic-star"
size={24}
color={colors.turquoise}
style={styles.starIcon}
/>
<Animated.Text style={[styles.text, animStyle.text]}>
{content}
</Animated.Text>
</Animated.View>
)
}
const styles = StyleSheet.create({
container: {
paddingHorizontal: 16,
paddingVertical: 32,
backgroundColor: colors.darkPurple,
borderRadius: 20,
alignItems: 'center',
marginTop: '50%',
},
starIcon: {
marginBottom: 14,
},
text: {
color: colors.purple,
fontSize: 22,
lineHeight: 32,
textAlign: 'center',
fontFamily: Font.Roboto400,
},
})

24
src/module/game/helper/get-current-truth-dares.helper.ts

@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
import { selectShuffled, selectShuffledCustom } from '../../../store/slices'
import { TypeCustom, useAppSelector } from '../../common'
interface UseTruthOrDareProps {
isTruth: boolean
customType: TypeCustom
}
export const getCurrentTruthOrDare = ({
isTruth,
customType,
}: UseTruthOrDareProps) => {
const gameItems = useAppSelector(selectShuffled)
const customPackage = useAppSelector(selectShuffledCustom)
const dares = gameItems.filter(dare => dare.isDare)
const questions = gameItems.filter(question => !question.isDare)
const packageTruthOrDare = isTruth ? questions : dares
const truthOrDareItems = customType
? customPackage[customType]
: packageTruthOrDare
return truthOrDareItems
}

1
src/module/game/helper/index.ts

@ -0,0 +1 @@ @@ -0,0 +1 @@
export * from './get-current-truth-dares.helper'

2
src/module/game/index.ts

@ -1,2 +1,4 @@ @@ -1,2 +1,4 @@
export * from './screens'
export * from './components'
export * from './animations';
export * from './helper';

95
src/module/game/screens/game.screen.tsx

@ -1,58 +1,85 @@ @@ -1,58 +1,85 @@
import { StyleSheet, TouchableOpacity, View } from 'react-native'
import React, { FC, useEffect } from 'react'
import { StyleSheet, View } from 'react-native'
import {
$size,
ButtonPrimary,
colors,
Font,
Header,
RouteKey,
ScreenLayout,
Txt,
TypeCustom,
useNav,
} from '../../common'
import React, { FC } from 'react'
import { useTranslation } from 'react-i18next'
import { useRoute } from '@react-navigation/native'
import { useIsFocused, useRoute } from '@react-navigation/native'
import { useAnimationButton } from '../animations'
export const GameScreen: FC = () => {
const { t } = useTranslation()
const nav = useNav()
const { params }: any = useRoute()
const packageName = params.packageName
const { packageName, isCustom } = params
const isFocused = useIsFocused()
const { animBorderStyles, startAnimationsBorder } = useAnimationButton()
const randomGame = () => {
const isQuestions = Math.random() < 0.5
nav.navigate(RouteKey.Questions, { isQuestions, packageName })
const isTruthRandom = Math.random() < 0.5
const customRandom = isTruthRandom
? TypeCustom.Questions
: TypeCustom.Dares
nav.navigate(RouteKey.TruthOrDare, {
isTruth: isTruthRandom,
packageName,
customType: isCustom ? customRandom : null,
})
}
const onChooseQuestion = () => {
nav.navigate(RouteKey.Questions, { isQuestions: true, packageName })
const onChooseTruth = () => {
nav.navigate(RouteKey.TruthOrDare, {
isTruth: true,
packageName,
customType: isCustom && TypeCustom.Questions,
})
}
const onChooseDare = () => {
nav.navigate(RouteKey.Questions, { packageName })
nav.navigate(RouteKey.TruthOrDare, {
packageName,
customType: isCustom && TypeCustom.Dares,
})
}
return (
<ScreenLayout headerComponent={<Header title={packageName} gamer />}>
<Txt mod="xxl" style={styles.playerName}>
Player
</Txt>
<View style={styles.wrapButtons}>
<ButtonPrimary
width={188}
style={styles.gameButton}
styleTxt={styles.styleTxtBtn}
onPress={onChooseQuestion}>
animationStyle={animBorderStyles[0]}
animation={startAnimationsBorder[0]}
isFocused={isFocused}
onPress={onChooseTruth}>
{t('buttonsTranslation.truth')}
</ButtonPrimary>
<TouchableOpacity onPress={randomGame}>
<Txt mod="lg">{t('buttonsTranslation.random')}</Txt>
</TouchableOpacity>
<ButtonPrimary
width={188}
styleTxt={styles.styleTxtBtn}
style={styles.randomButton}
styleTxt={{ fontFamily: Font.Roboto400 }}
animationStyle={animBorderStyles[1]}
animation={startAnimationsBorder[1]}
isFocused={isFocused}
onPress={randomGame}>
{t('buttonsTranslation.random')}
</ButtonPrimary>
<ButtonPrimary
width={188}
style={styles.gameButton}
styleTxt={styles.styleTxtBtn}
animation={startAnimationsBorder[2]}
animationStyle={animBorderStyles[2]}
isFocused={isFocused}
onPress={onChooseDare}>
{t('buttonsTranslation.dare')}
</ButtonPrimary>
@ -63,27 +90,37 @@ export const GameScreen: FC = () => { @@ -63,27 +90,37 @@ export const GameScreen: FC = () => {
const styles = StyleSheet.create({
playerName: {
fontWeight: '700',
fontFamily: Font.Roboto700,
textAlign: 'center',
marginBottom: $size(122),
marginBottom: 122,
},
wrapButtons: {
alignItems: 'center',
justifyContent: 'space-between',
rowGap: $size(40),
justifyContent: 'center',
rowGap: 40,
marginTop: '50%',
},
gameButton: {
height: $size(66),
height: 66,
width: 188,
borderRadius: 40,
backgroundColor: colors.primaryColor,
borderWidth: 1,
borderWidth: 2,
borderColor: colors.darkPurple,
},
randomButton: {
height: 66,
width: 188,
borderRadius: 40,
backgroundColor: 'transparent',
borderWidth: 2,
borderColor: colors.darkPurple,
},
styleTxtBtn: {
color: colors.red,
fontWeight: '800',
fontSize: $size(36),
lineHeight: $size(46),
fontFamily: Font.Roboto700,
fontSize: 36,
lineHeight: 46,
},
txt: {},
})

2
src/module/game/screens/index.ts

@ -1,2 +1,2 @@ @@ -1,2 +1,2 @@
export * from './game.screen'
export * from './questions.screen'
export * from './truth-or-dare.screen'

78
src/module/game/screens/questions.screen.tsx

@ -1,78 +0,0 @@ @@ -1,78 +0,0 @@
import React from 'react'
import { StyleSheet, View } from 'react-native'
import { useRoute } from '@react-navigation/native'
import {
$size,
ButtonWithIcon,
Header,
RouteKey,
ScreenLayout,
Txt,
useAppDispatch,
useNav,
} from '../../common'
import { nextStep, resetSteps, shuffleItems } from '../../../store/slices'
import { QuestionBlock } from '../components'
export const QuestionsScreen: React.FC = () => {
const nav = useNav()
const { params }: any = useRoute()
const { packageName, isQuestions } = params
const dispatch = useAppDispatch()
const goBack = () => {
nav.navigate(RouteKey.Game, { packageName })
}
const goGameScreen = () => {
nav.navigate(RouteKey.Packages)
dispatch(nextStep())
}
const refreshList = () => {
dispatch(shuffleItems())
dispatch(resetSteps())
}
return (
<ScreenLayout
headerComponent={
<Header
gamer
onPressLeft={() => goBack()}
title={packageName}
/>
}>
<View style={{ flex: 1 }}>
<Txt mod="xxl" style={styles.playerName}>
Player
</Txt>
<QuestionBlock isQuestions={isQuestions} />
<View style={styles.buttons}>
<ButtonWithIcon iconName="union" onPress={refreshList} />
<ButtonWithIcon iconName="play" onPress={goGameScreen} />
<ButtonWithIcon iconName="add-plus" onPress={() => null} />
</View>
</View>
</ScreenLayout>
)
}
const styles = StyleSheet.create({
playerName: {
fontWeight: '700',
textAlign: 'center',
marginBottom: $size(88),
},
buttons: {
alignItems: 'center',
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: $size(66),
marginTop: 'auto',
},
})

100
src/module/game/screens/truth-or-dare.screen.tsx

@ -0,0 +1,100 @@ @@ -0,0 +1,100 @@
import React, { useRef } from 'react'
import { Animated, StyleSheet, View } from 'react-native'
import { useRoute } from '@react-navigation/native'
import {
ButtonWithIcon,
Header,
RouteKey,
ScreenLayout,
TypeCustom,
useAppDispatch,
useAppSelector,
useNav,
} from '../../common'
import {
nextStep,
resetSteps,
selectShuffled,
selectShuffledCustom,
shuffleCustom,
shuffleItems,
} from '../../../store/slices'
import { TruthOrDareView } from '../components'
import { useAnimationIconsButton } from '../animations'
import { getCurrentTruthOrDare } from '../helper'
interface IRouteParams {
packageName?: string
isTruth?: boolean
customType?: TypeCustom
}
export const TruthOrDareScreen: React.FC = () => {
const nav = useNav()
const dispatch = useAppDispatch()
const { params } = useRoute()
const { packageName, isTruth, customType }: IRouteParams = params
const truthOrDareItems = getCurrentTruthOrDare({ isTruth, customType })
const { animRotate, animScale } = useAnimationIconsButton()
const goBack = () => {
nav.navigate(RouteKey.Game, { packageName })
}
const onNext = () => {
dispatch(nextStep())
}
const refreshList = () => {
!customType && dispatch(shuffleItems())
customType && dispatch(shuffleCustom())
dispatch(resetSteps())
}
return (
<ScreenLayout
headerComponent={
<Header
gamer
onPressLeft={() => goBack()}
title={packageName}
/>
}>
<View style={{ flex: 1 }}>
<TruthOrDareView
items={truthOrDareItems}
isCustom={customType}
/>
<View style={styles.buttons}>
<ButtonWithIcon
styleBtn={{ width: 101 }}
iconName="union"
onPress={refreshList}
animation={animRotate.func}
animStyle={animRotate.style}
/>
<ButtonWithIcon
styleBtn={{ width: 101 }}
iconName="play"
onPress={onNext}
animation={animScale.func}
animStyle={animScale.style}
/>
</View>
</View>
</ScreenLayout>
)
}
const styles = StyleSheet.create({
buttons: {
alignItems: 'center',
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: 66,
marginTop: 'auto',
},
})

2
src/module/packages/animation/index.ts

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
export * from './use-animation-list.hook';
export * from './use-animation-custom-item.hook';

53
src/module/packages/animation/use-animation-custom-item.hook.ts

@ -0,0 +1,53 @@ @@ -0,0 +1,53 @@
import React, { useEffect, useRef } from 'react'
import { Animated } from 'react-native'
export const useAnimationCustomItem = () => {
const animValue = useRef(new Animated.Value(0)).current
const scaleAnim = useRef(new Animated.Value(0)).current
const startAnimation = () => {
Animated.sequence([
Animated.timing(scaleAnim, {
toValue: 1,
duration: 100,
useNativeDriver: true,
}),
Animated.timing(scaleAnim, {
toValue: 0,
duration: 300,
useNativeDriver: true,
}),
]).start()
}
useEffect(() => {
Animated.spring(animValue, {
toValue: 1,
useNativeDriver: true,
delay: 1200,
}).start()
}, [])
const animationStyleItem = {
transform: [
{
translateY: animValue.interpolate({
inputRange: [0, 1],
outputRange: [300, 0],
}),
},
{
scale: scaleAnim.interpolate({
inputRange: [0, 1],
outputRange: [1, 1.2],
}),
},
],
opacity: scaleAnim.interpolate({
inputRange: [0, 1],
outputRange: [1, 0.1],
}),
}
return { animationStyleItem, startAnimation }
}

61
src/module/packages/animation/use-animation-list.hook.ts

@ -0,0 +1,61 @@ @@ -0,0 +1,61 @@
import React, { useEffect, useRef } from 'react'
import { Animated, Easing } 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
useEffect(() => {
const animation = Animated.parallel([
Animated.spring(animTransformX, {
toValue: 1,
useNativeDriver: true,
delay,
}),
Animated.loop(
Animated.sequence([
Animated.timing(animTransformY, {
toValue: -3,
duration: 2000,
delay,
useNativeDriver: true,
easing: Easing.linear,
}),
Animated.timing(animTransformY, {
toValue: 0,
duration: 2000,
delay,
useNativeDriver: true,
easing: Easing.linear,
}),
]),
),
])
if (!isFocus) {
return animation.reset()
}
animation.start()
}, [isFocus])
const animationStyleItem = {
opacity: animTransformX,
transform: [
{
translateX: animTransformX.interpolate({
inputRange: [0, 1],
outputRange: [-100, 0],
}),
},
{
translateY: animTransformY,
},
],
}
return { animationStyleItem }
}

64
src/module/packages/atoms/animated-diamond-icon.atom.tsx

@ -0,0 +1,64 @@ @@ -0,0 +1,64 @@
import React, { FC, useEffect, useRef } from 'react'
import { Animated, StyleSheet, View } from 'react-native'
import { colors, Icon } from '../../common'
import { useIsFocused } from '@react-navigation/native'
interface IProps {}
export const AnimatedDiamondIcon: FC<IProps> = ({}) => {
const rotateIcon = useRef(new Animated.Value(0)).current
const isFocus = useIsFocused()
useEffect(() => {
const loopAnimation = Animated.loop(
Animated.sequence([
Animated.spring(rotateIcon, {
toValue: 1,
useNativeDriver: true,
stiffness: 5,
}),
]),
)
if (!isFocus) {
return loopAnimation.stop()
}
loopAnimation.start()
}, [isFocus])
return (
<View style={styles.iconContainer}>
<Animated.View
style={{
transform: [
{
rotateY: rotateIcon.interpolate({
inputRange: [0, 0.5, 1],
outputRange: ['0deg', '360deg', '720deg'],
}),
},
{
rotate: rotateIcon.interpolate({
inputRange: [0, 0.5, 1],
outputRange: ['0deg', '15deg', '0deg'],
}),
},
],
}}>
<Icon name={'diamond'} size={24} color={colors.turquoise} />
</Animated.View>
</View>
)
}
const styles = StyleSheet.create({
iconContainer: {
backgroundColor: colors.primaryColor,
width: 40,
height: 40,
borderRadius: 10,
alignItems: 'center',
justifyContent: 'center',
marginRight: 16,
},
})

88
src/module/packages/atoms/create-custom-package.atom.tsx

@ -1,55 +1,85 @@ @@ -1,55 +1,85 @@
import React from 'react'
import React, { FC } from 'react'
import { useTranslation } from 'react-i18next'
import { StyleSheet, TouchableOpacity, View } from 'react-native'
import { $size, colors, Icon, Txt } from '../../common'
import { Animated, StyleSheet, TouchableOpacity, View } from 'react-native'
import { colors, Font, RouteKey, Txt, useNav } from '../../common'
import { AnimatedDiamondIcon } from './animated-diamond-icon.atom'
import { useAnimationCustomItem } from '../animation'
export const CustomPackage = () => {
interface IProps {}
export const CustomPackage: FC<IProps> = ({}) => {
const { t } = useTranslation()
const nav = useNav()
const { startAnimation, animationStyleItem } = useAnimationCustomItem()
const onPressCustomPackage = () => {
startAnimation()
nav.navigate(RouteKey.CustomPackage)
}
return (
<TouchableOpacity style={style.container}>
<View style={{ flexDirection: 'row' }}>
<View style={style.iconContainer}>
<Icon name="lock" size={$size(24)} color="white" />
</View>
<View style={style.textContainer}>
<Txt style={style.title}>{t('customPack.title')}</Txt>
<Txt style={style.description}>
<Animated.View style={[styles.container, animationStyleItem]}>
<TouchableOpacity
style={{ flexDirection: 'row' }}
onPress={onPressCustomPackage}>
<AnimatedDiamondIcon />
<View style={styles.textContainer}>
<Txt
style={styles.title}
font={Font.Roboto700}
color={colors.turquoise}>
{t('customPack.title')}
</Txt>
<Txt style={styles.description} color={colors.textPrimary}>
{t('customPack.description')}
</Txt>
</View>
</View>
</TouchableOpacity>
</TouchableOpacity>
</Animated.View>
)
}
const style = StyleSheet.create({
const styles = StyleSheet.create({
container: {
backgroundColor: '#99EDCC',
backgroundColor: colors.lightPurple,
borderRadius: 20,
padding: $size(16),
padding: 16,
overflow: 'hidden',
},
textContainer: {
flex: 1,
marginTop: -4,
},
title: {
fontWeight: '600',
fontSize: $size(22),
lineHeight: $size(32),
color: colors.darkPurple,
fontSize: 22,
lineHeight: 32,
marginBottom: 4,
},
description: {
fontSize: $size(16),
lineHeight: $size(24),
color: colors.darkPurple,
fontSize: 16,
lineHeight: 24,
},
iconContainer: {
backgroundColor: colors.primaryColor,
padding: $size(10),
width: $size(50),
height: $size(50),
borderRadius: 20,
width: 40,
height: 40,
borderRadius: 10,
alignItems: 'center',
justifyContent: 'center',
marginRight: $size(16),
marginRight: 16,
},
price: {
position: 'absolute',
top: 5,
right: -43,
height: 35,
width: 130,
transform: [{ rotate: '45deg' }],
backgroundColor: colors.red,
justifyContent: 'center',
alignItems: 'center',
},
priceTxt: {
fontSize: 16,
lineHeight: 24,
},
})

1
src/module/packages/atoms/index.ts

@ -1,2 +1,3 @@ @@ -1,2 +1,3 @@
export * from './create-custom-package.atom'
export * from './packages-page-separator.atom'
export * from './animated-diamond-icon.atom'

35
src/module/packages/atoms/packages-page-separator.atom.tsx

@ -1,14 +1,35 @@ @@ -1,14 +1,35 @@
import React from 'react'
import { Image, StyleSheet, Text, View } from 'react-native'
import { $size, Txt, colors } from '../../common'
import React, { useEffect } from 'react'
import { Animated, StyleSheet, View } from 'react-native'
import { Txt, colors } from '../../common'
export const PackagesPageSeparator = () => {
const fadeInAnim = new Animated.Value(0)
useEffect(() => {
Animated.spring(fadeInAnim, {
toValue: 1,
useNativeDriver: true,
delay: 1100,
}).start()
}, [])
const animStyle = {
transform: [
{
translateY: fadeInAnim.interpolate({
inputRange: [0, 1],
outputRange: [-900, 0],
}),
},
],
}
return (
<View style={styles.container}>
<Animated.View style={[styles.container, animStyle]}>
<View style={styles.dashedLine}></View>
<Txt style={styles.text}>OR</Txt>
<View style={styles.dashedLine}></View>
</View>
</Animated.View>
)
}
@ -16,7 +37,7 @@ const styles = StyleSheet.create({ @@ -16,7 +37,7 @@ const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: $size(20),
marginBottom: 20,
},
dashedLine: {
borderWidth: 1,
@ -28,7 +49,7 @@ const styles = StyleSheet.create({ @@ -28,7 +49,7 @@ const styles = StyleSheet.create({
text: {
textAlign: 'center',
color: colors.blue,
fontSize: $size(14),
fontSize: 14,
fontWeight: '900',
marginHorizontal: 16,
},

50
src/module/packages/components/packages-item.component.tsx

@ -1,15 +1,7 @@ @@ -1,15 +1,7 @@
import React, { FC } from 'react'
import { StyleSheet, View } from 'react-native'
import {
$size,
ButtonWithIcon,
RouteKey,
Txt,
colors,
useAppDispatch,
useNav,
} from '../../common'
import { resetSteps, shuffleDares, shuffleTruths } from '../../../store/slices'
import { Animated, StyleSheet, View } from 'react-native'
import { ButtonWithIcon, RouteKey, Txt, colors, useNav } from '../../common'
import { useAnimationList } from '../animation'
interface IType {
[key: string]: string
@ -20,25 +12,25 @@ interface IPackage { @@ -20,25 +12,25 @@ interface IPackage {
image: JSX.Element
questions: IType[]
actions: IType[]
delay: number
}
export const PackageItem: FC<IPackage> = ({
packageName,
description,
image,
delay,
}) => {
const nav = useNav()
const dispatch = useAppDispatch()
const play = () => {
const { animationStyleItem } = useAnimationList(delay)
const onPlay = () => {
nav.navigate(RouteKey.Game, { packageName })
dispatch(resetSteps())
dispatch(shuffleTruths())
dispatch(shuffleDares())
}
return (
<View style={styles.container}>
<Animated.View style={[styles.container, animationStyleItem]}>
<View style={styles.flex}>
<View style={styles.imgContainer}>{image}</View>
<View style={styles.textContainer}>
@ -50,10 +42,10 @@ export const PackageItem: FC<IPackage> = ({ @@ -50,10 +42,10 @@ export const PackageItem: FC<IPackage> = ({
</View>
<ButtonWithIcon
iconName="play"
onPress={play}
onPress={onPlay}
styleBtn={styles.play}
/>
</View>
</Animated.View>
)
}
@ -61,10 +53,10 @@ const styles = StyleSheet.create({ @@ -61,10 +53,10 @@ const styles = StyleSheet.create({
container: {
backgroundColor: '#2C205C',
borderRadius: 20,
padding: $size(16),
padding: 16,
position: 'relative',
width: '94%',
marginBottom: $size(16),
marginBottom: 16,
},
flex: {
flexDirection: 'row',
@ -73,27 +65,27 @@ const styles = StyleSheet.create({ @@ -73,27 +65,27 @@ const styles = StyleSheet.create({
title: {
fontWeight: '600',
color: colors.blue,
marginBottom: $size(5),
marginBottom: 5,
},
description: {
marginBottom: $size(5),
marginBottom: 5,
color: colors.secondaryText,
fontSize: $size(16),
lineHeight: $size(24),
fontSize: 16,
lineHeight: 24,
},
textContainer: {
marginLeft: $size(12),
marginLeft: 12,
},
imgContainer: {
backgroundColor: colors.primaryColor,
width: $size(40),
height: $size(40),
width: 40,
height: 40,
borderRadius: 10,
overflow: 'hidden',
},
play: {
borderRadius: 60,
width: $size(50),
width: 50,
position: 'absolute',
top: '50%',
right: -25,

1
src/module/packages/index.ts

@ -2,3 +2,4 @@ export * from './screens' @@ -2,3 +2,4 @@ export * from './screens'
export * from './components'
export * from './atoms'
export * from './config';
export * from './animation';

15
src/module/packages/screens/packages-list.screen.tsx

@ -1,22 +1,32 @@ @@ -1,22 +1,32 @@
import React, { FC } from 'react'
import { Header, RouteKey, ScreenLayout, useNav } from '../../common'
import {
Header,
ProductsEnum,
RouteKey,
ScreenLayout,
useNav,
} from '../../common'
import { StyleSheet, View } from 'react-native'
import { useTranslation } from 'react-i18next'
import { packageListConfig } from '../config'
import { PackageItem } from '../components'
import { CustomPackage, PackagesPageSeparator } from '../atoms'
import { useIsFocused } from '@react-navigation/native'
export const PackagesListScreen: FC = () => {
const nav = useNav()
const { i18n } = useTranslation()
const isFocus = useIsFocused()
return (
<ScreenLayout
needScroll
headerComponent={
<Header
leftIcon=""
rightIcon="setting"
onPressRight={() => nav.navigate(RouteKey.SettingsGroup)}
onPressRight={() => nav.navigate(RouteKey.Settings)}
/>
}>
<View style={styles.container}>
@ -28,6 +38,7 @@ export const PackagesListScreen: FC = () => { @@ -28,6 +38,7 @@ export const PackagesListScreen: FC = () => {
actions={item.actions}
image={item.image}
key={index}
delay={Number(`${4 * index}00`)}
/>
))}
<PackagesPageSeparator />

7
src/module/root/atoms/dots.atom.tsx

@ -1,6 +1,5 @@ @@ -1,6 +1,5 @@
import React, { FC } from 'react'
import { StyleSheet, View } from 'react-native'
import { $size } from '../../common'
import { onBoardingConfig } from '../config'
interface IProps {
@ -28,11 +27,11 @@ export const DotsAtom: FC<IProps> = ({ activeDot }) => { @@ -28,11 +27,11 @@ export const DotsAtom: FC<IProps> = ({ activeDot }) => {
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
marginBottom: $size(50),
marginBottom: 50,
},
point: {
height: $size(8),
width: $size(8),
height: 8,
width: 8,
marginRight: 10,
borderRadius: 100,
},

17
src/module/root/atoms/on-boarding-button.component.tsx

@ -2,14 +2,7 @@ import AsyncStorage from '@react-native-async-storage/async-storage' @@ -2,14 +2,7 @@ import AsyncStorage from '@react-native-async-storage/async-storage'
import React, { FC } from 'react'
import { useTranslation } from 'react-i18next'
import { StyleSheet, TouchableOpacity, View } from 'react-native'
import {
$size,
ButtonPrimary,
RouteKey,
StorageKey,
Txt,
useNav,
} from '../../common'
import { ButtonPrimary, RouteKey, StorageKey, Txt, useNav } from '../../common'
interface IProps {
isLastBlock: boolean
@ -48,12 +41,12 @@ export const OnBoardingBottom: FC<IProps> = ({ isLastBlock, onPressSkip }) => { @@ -48,12 +41,12 @@ export const OnBoardingBottom: FC<IProps> = ({ isLastBlock, onPressSkip }) => {
}
const styles = StyleSheet.create({
text: {
fontSize: $size(16),
lineHeight: $size(24),
marginTop: $size(8),
fontSize: 16,
lineHeight: 24,
marginTop: 8,
},
btnContainer: {
alignItems: 'center',
marginBottom: $size(20),
marginBottom: 20,
},
})

10
src/module/root/components/language-item.component.tsx

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
import React, { FC } from 'react'
import { StyleSheet, View, Text, TouchableOpacity } from 'react-native'
import { $size, Txt } from '../../common'
import { StyleSheet, View, TouchableOpacity } from 'react-native'
import { Txt } from '../../common'
interface IProps {
icon: JSX.Element
name: string
@ -20,15 +20,15 @@ const styles = StyleSheet.create({ @@ -20,15 +20,15 @@ const styles = StyleSheet.create({
backgroundColor: '#51418D',
borderRadius: 60,
flexDirection: 'row',
paddingVertical: $size(16),
paddingVertical: 16,
paddingLeft: 10,
alignItems: 'center',
marginBottom: $size(16),
marginBottom: 16,
width: '100%',
},
circleContainer: {
borderRadius: 120,
padding: $size(8),
padding: 8,
backgroundColor: '#37296B',
marginRight: 8,
},

50
src/module/root/navigations-groups/user.group.tsx

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
import React, { FC } from 'react'
import { createNativeStackNavigator } from '@react-navigation/native-stack'
import { RouteKey, SettingsKey } from '../../common'
import { GameScreen, QuestionsScreen } from '../../game'
import { RouteKey } from '../../common'
import { GameScreen, TruthOrDareScreen } from '../../game'
import { PackagesListScreen } from '../../packages'
import {
LanguageSelectScreen,
@ -14,6 +14,11 @@ import { @@ -14,6 +14,11 @@ import {
PurchasesScreen,
WriteToUsScreen,
} from '../../settings'
import {
CustomPackageEditorScreen,
CustomPackagePreviewScreen,
} from '~module/custom-package'
import { Platform } from 'react-native'
const Stack = createNativeStackNavigator()
@ -24,17 +29,20 @@ export const UserNavigationGroup: FC = () => { @@ -24,17 +29,20 @@ export const UserNavigationGroup: FC = () => {
<Stack.Navigator
screenOptions={{
headerShown: false,
animation: 'slide_from_left',
animation: Platform.select({
android: 'slide_from_right',
}),
}}
initialRouteName={RouteKey.Loading}>
<Stack.Screen
name={RouteKey.Questions}
component={QuestionsScreen}
name={RouteKey.TruthOrDare}
component={TruthOrDareScreen}
/>
<Stack.Screen
name={RouteKey.Packages}
component={PackagesListScreen}
/>
<Stack.Screen name={RouteKey.Game} component={GameScreen} />
<Stack.Screen
name={RouteKey.Onboarding}
component={OnboardingScreen}
@ -44,39 +52,33 @@ export const UserNavigationGroup: FC = () => { @@ -44,39 +52,33 @@ export const UserNavigationGroup: FC = () => {
name={RouteKey.LanguageSelect}
component={LanguageSelectScreen}
/>
<Stack.Screen name={RouteKey.Game} component={GameScreen} />
<Stack.Screen
name={RouteKey.SettingsGroup}
component={UserSettingsNavigationGroup}
name={RouteKey.CustomPackage}
component={CustomPackagePreviewScreen}
options={{ animation: 'fade_from_bottom' }}
/>
<Stack.Screen
name={RouteKey.CustomEditor}
component={CustomPackageEditorScreen}
/>
</Stack.Navigator>
)
}
const UserSettingsNavigationGroup = () => {
return (
<SettingsStack.Navigator
initialRouteName={SettingsKey.Settings}
screenOptions={{
headerShown: false,
animation: 'slide_from_left',
}}>
<SettingsStack.Screen
name={SettingsKey.Settings}
name={RouteKey.Settings}
component={SettingsScreen}
/>
<Stack.Screen
name={SettingsKey.PrivacyPolicy}
name={RouteKey.PrivacyPolicy}
component={PrivacyPolicyScreen}
/>
<Stack.Screen
name={SettingsKey.Purchases}
name={RouteKey.Purchases}
component={PurchasesScreen}
/>
<Stack.Screen
name={SettingsKey.WriteToUs}
name={RouteKey.WriteToUs}
component={WriteToUsScreen}
/>
</SettingsStack.Navigator>
</Stack.Navigator>
)
}

12
src/module/root/screens/language-select.screen.tsx

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
import AsyncStorage from '@react-native-async-storage/async-storage'
import React, { FC } from 'react'
import { useTranslation } from 'react-i18next'
import { StyleSheet, Text, View } from 'react-native'
import { StyleSheet, View } from 'react-native'
import {
RouteKey,
colors,
@ -10,9 +10,9 @@ import { @@ -10,9 +10,9 @@ import {
Language,
StorageKey,
Txt,
$size,
EngSvg,
UaSvg,
Font,
} from '../../common'
import { LanguageItem } from '../components'
@ -62,10 +62,10 @@ const styles = StyleSheet.create({ @@ -62,10 +62,10 @@ const styles = StyleSheet.create({
alignItems: 'center',
},
title: {
fontSize: $size(32),
fontWeight: '700',
lineHeight: $size(38),
marginBottom: $size(30),
fontSize: 32,
fontFamily: Font.Roboto700,
lineHeight: 38,
marginBottom: 30,
textAlign: 'center',
},
})

9
src/module/root/screens/loading-screen.tsx

@ -9,8 +9,8 @@ import { @@ -9,8 +9,8 @@ import {
} from '../../common'
import AsyncStorage from '@react-native-async-storage/async-storage'
import { useTranslation } from 'react-i18next'
import { PurchasesService, purchasesService } from '../../settings'
import { fetchPostsAsync } from '../../../store/slices'
import { purchasesService } from '../../settings'
import { fetchPostsAsync, getCustomPackage } from '../../../store/slices'
export const LoadingScreen: FC = () => {
const { i18n } = useTranslation()
@ -32,8 +32,8 @@ export const LoadingScreen: FC = () => { @@ -32,8 +32,8 @@ export const LoadingScreen: FC = () => {
let language = await getLanguage()
const isOnBoard = await getOnboardEnd()
dispatch(fetchPostsAsync())
console.log(language)
dispatch(getCustomPackage())
purchasesService.init()
if (language) {
i18n.changeLanguage(language)
@ -41,7 +41,6 @@ export const LoadingScreen: FC = () => { @@ -41,7 +41,6 @@ export const LoadingScreen: FC = () => {
if (isOnBoard && language) {
nav.navigate(RouteKey.Packages)
purchasesService.init()
} else if (language && !isOnBoard) {
nav.navigate(RouteKey.Onboarding)
} else if (!language) {

16
src/module/root/screens/on-boarding.screen.tsx

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
import React, { FC, useState } from 'react'
import {
$size,
Font,
Header,
RouteKey,
ScreenLayout,
@ -69,21 +69,21 @@ const styles = StyleSheet.create({ @@ -69,21 +69,21 @@ const styles = StyleSheet.create({
flex: 1,
},
title: {
fontSize: $size(32),
lineHeight: $size(38),
fontWeight: '700',
fontSize: 32,
lineHeight: 38,
fontFamily: Font.Roboto700,
textAlign: 'center',
marginVertical: $size(24),
marginVertical: 24,
},
description: {
color: colors.purple,
fontSize: $size(16),
fontSize: 16,
textAlign: 'center',
lineHeight: $size(24),
lineHeight: 24,
},
bottomContainer: {
alignItems: 'center',
marginBottom: $size(30),
marginBottom: 30,
marginTop: 'auto',
},
})

27
src/module/settings/atoms/purchases.atom.tsx

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
import React, { FC } from 'react'
import { StyleSheet, View } from 'react-native'
import { $size, ButtonWithIcon, Icon, Txt, colors } from '../../common'
import { ButtonWithIcon, Icon, Txt, colors } from '../../common'
import { TouchableOpacity } from 'react-native-gesture-handler'
interface IProps {
@ -31,10 +31,8 @@ export const PurchaseAtom: FC<IProps> = ({ @@ -31,10 +31,8 @@ export const PurchaseAtom: FC<IProps> = ({
return (
<TouchableOpacity style={styles.container} onPress={onPress}>
<View style={styles.row}>
<Icon name={iconName} size={$size(24)} color={colors.purple} />
<Txt mod="lg" color={colors.purple}>
{title}
</Txt>
<Icon name={iconName} size={24} color={colors.purple} />
<Txt style={styles.txt}>{title}</Txt>
{hasDiscount && renderDiscountAtom()}
</View>
{isPurchased ? (
@ -57,32 +55,37 @@ const styles = StyleSheet.create({ @@ -57,32 +55,37 @@ const styles = StyleSheet.create({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
height: $size(28),
marginBottom: $size(24),
marginBottom: 24,
},
row: {
flexDirection: 'row',
alignItems: 'center',
columnGap: 8,
flex: 1,
},
price: {
fontWeight: '600',
color: colors.purple,
},
discount: {
width: $size(49),
width: 49,
borderRadius: 40,
backgroundColor: colors.red,
justifyContent: 'center',
alignItems: 'center',
},
discountTxt: {
fontSize: $size(14),
lineHeight: $size(28),
fontSize: 14,
lineHeight: 28,
fontWeight: '900',
},
iconPlay: {
width: $size(60),
height: '100%'
width: 60,
height: '100%',
},
txt: {
lineHeight: 24,
color: colors.purple,
fontSize: 18,
},
})

8
src/module/settings/atoms/selected-language-in-settings.atom.tsx

@ -1,21 +1,19 @@ @@ -1,21 +1,19 @@
import React from 'react'
import { StyleSheet } from 'react-native'
import { useTranslation } from 'react-i18next'
import { $size, Txt, colors } from '../../common'
import { Txt, colors } from '../../common'
export const SelectedLanguage = () => {
const { i18n } = useTranslation()
console.log(i18n.language)
return <Txt style={styles.text}>{i18n.language}</Txt>
}
const styles = StyleSheet.create({
text: {
color: colors.purple,
fontWeight: '600',
fontSize: $size(18),
lineHeight: $size(28),
fontSize: 18,
lineHeight: 28,
textTransform: 'uppercase',
},
})

6
src/module/settings/atoms/switch-notifications.atom.tsx

@ -1,9 +1,9 @@ @@ -1,9 +1,9 @@
import React, { useState } from 'react'
import { Switch } from 'react-native'
import { $size, colors } from '../../common'
import { colors } from '../../common'
export const SwitchNotificationsAtom = () => {
const [isEnabled, setIsEnabled] = useState(false)
const [isEnabled, setIsEnabled] = useState(true)
const toggleSwitch = () => {
setIsEnabled(previousState => !previousState)
@ -15,7 +15,7 @@ export const SwitchNotificationsAtom = () => { @@ -15,7 +15,7 @@ export const SwitchNotificationsAtom = () => {
thumbColor={isEnabled ? colors.turquoise : colors.darkPurple}
ios_backgroundColor={colors.purple}
onValueChange={toggleSwitch}
style={{ width: $size(51) }}
style={{ width: 51 }}
value={isEnabled}
/>
)

16
src/module/settings/components/settings-item.component.tsx

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
import React, { FC } from 'react'
import { StyleSheet, View, TouchableOpacity } from 'react-native'
import { $size, Icon, Txt, colors } from '../../common'
import { Icon, Txt, colors } from '../../common'
interface IProps {
title: string
@ -18,10 +18,8 @@ export const SettingsItem: FC<IProps> = ({ @@ -18,10 +18,8 @@ export const SettingsItem: FC<IProps> = ({
return (
<TouchableOpacity style={styles.container} onPress={onPressSettingItem}>
<View style={styles.row}>
<Icon name={iconName} size={$size(24)} color={colors.purple} />
<Txt mod="lg" style={styles.text}>
{title}
</Txt>
<Icon name={iconName} size={24} color={colors.purple} />
<Txt style={styles.text}>{title}</Txt>
</View>
{component && component()}
</TouchableOpacity>
@ -33,14 +31,16 @@ const styles = StyleSheet.create({ @@ -33,14 +31,16 @@ const styles = StyleSheet.create({
alignContent: 'center',
},
text: {
fontSize: 18,
lineHeight: 24,
color: colors.purple,
marginLeft: $size(10),
marginLeft: 10,
},
container: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: $size(24),
height: $size(28),
marginBottom: 24,
height: 28,
justifyContent: 'space-between',
},
})

6
src/module/settings/config/purchases.config.ts

@ -2,15 +2,15 @@ import { ProductsEnum } from '../../common' @@ -2,15 +2,15 @@ import { ProductsEnum } from '../../common'
export const purchasesConfig: any = {
[ProductsEnum.Under18]: {
name: 'Open package "Under 18"',
name: 'purchases.under18',
icon: 'ghost',
},
[ProductsEnum.Crazy]: {
name: 'Open package "Crazy"',
name: 'purchases.crazy',
icon: 'crazy',
},
[ProductsEnum.All]: {
name: 'Open all packages',
name: 'purchases.allPackage',
icon: 'all_packages',
},
}

4
src/module/settings/screens/privacy-policy.tsx

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
import React, { FC } from 'react'
import { useTranslation } from 'react-i18next'
import { StyleSheet, View } from 'react-native'
import { $size, colors, Header, ScreenLayout, Txt } from '../../common'
import { colors, Header, ScreenLayout, Txt } from '../../common'
import { privacyConfig } from '../config'
export const PrivacyPolicyScreen: FC = () => {
@ -27,6 +27,6 @@ const styles = StyleSheet.create({ @@ -27,6 +27,6 @@ const styles = StyleSheet.create({
},
description: {
color: colors.purple,
lineHeight: $size(30),
lineHeight: 30,
},
})

25
src/module/settings/screens/purchases.screen.tsx

@ -7,7 +7,6 @@ import { @@ -7,7 +7,6 @@ import {
View,
} from 'react-native'
import {
$size,
appEvents,
colors,
Font,
@ -30,21 +29,21 @@ export const PurchasesScreen: FC = () => { @@ -30,21 +29,21 @@ export const PurchasesScreen: FC = () => {
const [isLoading, setLoading] = useState(false)
const purchaseProduct = async (productId: ProductsEnum) => {
try {
setLoading(true)
await purchasesService.purchaseProduct(productId)
setLoading(true)
const success = await purchasesService.purchaseProduct(productId)
if (success) {
appEvents.emit('alert', {
title: t('purchases.alertSuccess'),
subtitle: t('purchases.descSuccess'),
})
} catch (error) {
} else {
appEvents.emit('alert', {
title: t('purchases.alertError'),
subtitle: t('purchases.descError'),
})
} finally {
setLoading(false)
}
setLoading(false)
}
return (
@ -71,7 +70,7 @@ export const PurchasesScreen: FC = () => { @@ -71,7 +70,7 @@ export const PurchasesScreen: FC = () => {
return (
<PurchaseAtom
key={it.productId}
title={it.name}
title={t(it.name)}
price={it.price}
hasDiscount={it.productId === ProductsEnum.All}
iconName={it.icon}
@ -86,9 +85,9 @@ export const PurchasesScreen: FC = () => { @@ -86,9 +85,9 @@ export const PurchasesScreen: FC = () => {
})}
</>
<TouchableOpacity style={styles.row}>
<Icon name="restore" size={$size(24)} color={colors.purple} />
<Icon name="restore" size={24} color={colors.purple} />
<Txt mod="lg" color={colors.purple}>
Restore purchases
{t('purchases.restore')}
</Txt>
</TouchableOpacity>
</ScreenLayout>
@ -103,7 +102,7 @@ const styles = StyleSheet.create({ @@ -103,7 +102,7 @@ const styles = StyleSheet.create({
},
description: {
color: colors.purple,
lineHeight: $size(30),
lineHeight: 30,
},
row: {
flexDirection: 'row',
@ -113,7 +112,7 @@ const styles = StyleSheet.create({ @@ -113,7 +112,7 @@ const styles = StyleSheet.create({
modal: {
position: 'absolute',
top: 0,
bottom: $size(100),
bottom: 100,
left: 0,
right: 0,
flex: 1,
@ -123,7 +122,7 @@ const styles = StyleSheet.create({ @@ -123,7 +122,7 @@ const styles = StyleSheet.create({
body: {
width: '90%',
backgroundColor: colors.primaryColor,
height: $size(100),
height: 100,
justifyContent: 'center',
alignItems: 'center',
borderColor: colors.lightPurple,

8
src/module/settings/screens/settings.screen.tsx

@ -7,8 +7,8 @@ import { @@ -7,8 +7,8 @@ import {
EngSvg,
Header,
Language,
RouteKey,
ScreenLayout,
SettingsKey,
UaSvg,
useNav,
} from '../../common'
@ -57,7 +57,7 @@ export const SettingsScreen: FC = () => { @@ -57,7 +57,7 @@ export const SettingsScreen: FC = () => {
const onPressSettingItem = (key: string) => {
switch (key) {
case 'purchases':
nav.navigate(SettingsKey.Purchases)
nav.navigate(RouteKey.Purchases)
break
case 'lang':
openBottomSheetForChangeLanguage()
@ -65,10 +65,10 @@ export const SettingsScreen: FC = () => { @@ -65,10 +65,10 @@ export const SettingsScreen: FC = () => {
case 'notification':
break
case 'message':
nav.navigate(SettingsKey.WriteToUs)
nav.navigate(RouteKey.WriteToUs)
break
case 'privacy-policy':
nav.navigate(SettingsKey.PrivacyPolicy)
nav.navigate(RouteKey.PrivacyPolicy)
break
case 'rate':
break

5
src/module/settings/screens/write-to-us.screen.tsx

@ -2,7 +2,6 @@ import React, { FC } from 'react' @@ -2,7 +2,6 @@ import React, { FC } from 'react'
import { useTranslation } from 'react-i18next'
import { StyleSheet } from 'react-native'
import {
$size,
appEvents,
ButtonPrimary,
colors,
@ -40,7 +39,7 @@ export const WriteToUsScreen: FC = () => { @@ -40,7 +39,7 @@ export const WriteToUsScreen: FC = () => {
value={form.values.message}
onChange={val => form.setFormField('message', val)}
inputProps={{ multiline: true }}
inputStyle={{ height: $size(100) }}
inputStyle={{ height: 100 }}
error={form.errors['message']}
/>
<ButtonPrimary
@ -55,7 +54,7 @@ export const WriteToUsScreen: FC = () => { @@ -55,7 +54,7 @@ export const WriteToUsScreen: FC = () => {
const styles = StyleSheet.create({
button: {
marginTop: $size(20),
marginTop: 20,
backgroundColor: colors.lightPurple,
},
})

54
src/module/settings/services/purchases.service.ts

@ -6,10 +6,9 @@ import { @@ -6,10 +6,9 @@ import {
purchaseUpdatedListener,
finishTransaction,
} from 'react-native-iap'
import { ProductsEnum, StorageKey, appEvents } from '../../common'
import { ProductsEnum, StorageKey } from '../../common'
import { purchasesConfig } from '../config'
import AsyncStorage from '@react-native-async-storage/async-storage'
import { Alert } from 'react-native'
const ID_PRODUCTS = [ProductsEnum.All, ProductsEnum.Crazy, ProductsEnum.Under18]
@ -31,39 +30,20 @@ export class PurchasesService { @@ -31,39 +30,20 @@ export class PurchasesService {
this.getPurchasedProducts()
}
public async getProducts() {
const res = await AsyncStorage.getItem(StorageKey.Products)
return JSON.parse(res)
}
private async initializeIAP() {
try {
await initConnection()
} catch (error) {
console.error('Failed to initialize IAP:', error)
throw error
}
}
public async loadProducts() {
try {
const products: Product[] = await getProducts({ skus: ID_PRODUCTS })
const productss = this.transformProductsData(products)
const productFromAppStore: Product[] = await getProducts({
skus: ID_PRODUCTS,
})
const products = this.transformProductsData(productFromAppStore)
this.products = productss
await this.saveProductInStore(productss)
this.products = products
return products
} catch (error) {
console.error('Error loading products:', error)
}
}
private async saveProductInStore(products: ProductItem[]) {
await AsyncStorage.setItem(
StorageKey.Products,
JSON.stringify(products),
)
}
public async purchaseProduct(productId: ProductsEnum) {
try {
await requestPurchase({
@ -71,13 +51,24 @@ export class PurchasesService { @@ -71,13 +51,24 @@ export class PurchasesService {
})
this.purchaseListener()
await this.savePurchase(productId)
await purchasesService.loadProducts()
await this.loadProducts()
return true
} catch (error) {
console.error('Purchase error:', error)
return false
}
}
private async initializeIAP() {
try {
await initConnection()
} catch (error) {
console.error('Failed to initialize IAP:', error)
throw error
}
}
public async savePurchase(productId: ProductsEnum) {
private async savePurchase(productId: ProductsEnum) {
const newProductsId = [...this.purchasedProducts, productId]
const newProducts = JSON.stringify(newProductsId)
@ -86,9 +77,8 @@ export class PurchasesService { @@ -86,9 +77,8 @@ export class PurchasesService {
await AsyncStorage.setItem(StorageKey.Purchases, newProducts)
}
protected async getPurchasedProducts() {
private async getPurchasedProducts() {
const response = await AsyncStorage.getItem(StorageKey.Purchases)
this.purchasedProducts = response ? JSON.parse(response) : []
}
@ -99,8 +89,6 @@ export class PurchasesService { @@ -99,8 +89,6 @@ export class PurchasesService {
}
private transformProductsData = (products: Product[]) => {
console.log('transformProductsData', this.purchasedProducts)
return products
.map(product => {
const isPurchased = this.purchasedProducts.some(

115
src/store/slices/custom-package.slice.ts

@ -0,0 +1,115 @@ @@ -0,0 +1,115 @@
import { PayloadAction, createAsyncThunk, createSlice } from '@reduxjs/toolkit'
import { StorageKey, TypeCustom } from '../../module/common'
import { RootState } from '../store'
import _ from 'lodash'
import AsyncStorage from '@react-native-async-storage/async-storage'
interface ICustomPackage {
[TypeCustom.Questions]: string[]
[TypeCustom.Dares]: string[]
}
const defaultCustomPackage: ICustomPackage = {
[TypeCustom.Questions]: [],
[TypeCustom.Dares]: [],
}
export interface CustomPackageState {
customPackageFromStore: ICustomPackage
customPackage: ICustomPackage
shuffleCustomPackage: ICustomPackage
shuffled: ICustomPackage
loaded: boolean
hasError: boolean
}
const initialState: CustomPackageState = {
customPackage: defaultCustomPackage,
shuffleCustomPackage: defaultCustomPackage,
customPackageFromStore: defaultCustomPackage,
shuffled: defaultCustomPackage,
loaded: false,
hasError: false,
}
export const getCustomPackage = createAsyncThunk(
'get-custom-package',
async () => {
const response = await AsyncStorage.getItem(StorageKey.CustomPackage)
return response ? JSON.parse(response) : defaultCustomPackage
},
)
export const customPackageSlice = createSlice({
name: 'customPackage',
initialState,
reducers: {
shuffleCustom: state => {
const shuffleTruths = _.shuffle(state.customPackage.questions)
const shuffleDares = _.shuffle(state.customPackage.dares)
state.shuffleCustomPackage = {
dares: shuffleDares,
questions: shuffleTruths,
}
},
setQuestions: (state, action: PayloadAction<string[]>) => {
const updateCustomPackage = {
...state.customPackage,
questions: action.payload,
}
state.customPackage = updateCustomPackage
},
setDares: (state, action: PayloadAction<string[]>) => {
const updateCustomPackage = {
...state.customPackage,
dares: action.payload,
}
state.customPackage = updateCustomPackage
},
updateCustomPackage: (state, action: PayloadAction<ICustomPackage>) => {
state.customPackage = action.payload
},
updateCustomPackageFromStore: (
state,
action: PayloadAction<ICustomPackage>,
) => {
state.customPackageFromStore = action.payload
},
},
extraReducers(builder) {
builder
.addCase(getCustomPackage.fulfilled, (state, action) => {
state.customPackage = action.payload
state.customPackageFromStore = action.payload
state.hasError = false
state.loaded = true
})
.addCase(getCustomPackage.pending, state => {
state.loaded = false
})
.addCase(getCustomPackage.rejected, state => {
state.hasError = true
})
},
})
export const {
setQuestions,
setDares,
updateCustomPackageFromStore,
updateCustomPackage,
shuffleCustom,
} = customPackageSlice.actions
export const selectCustomPackage = (state: RootState) =>
state.customPackage.customPackage
export const selectCustomPackageFromStore = (state: RootState) =>
state.customPackage.customPackageFromStore
export const selectCustomLoaded = (state: RootState) =>
state.customPackage.loaded
export const selectShuffledCustom = (state: RootState) =>
state.customPackage.shuffleCustomPackage
export default customPackageSlice.reducer

52
src/store/slices/dares-slice.ts

@ -1,52 +0,0 @@ @@ -1,52 +0,0 @@
import { createSlice } from '@reduxjs/toolkit'
import { Dare } from '../../module/common'
import _ from 'lodash'
import { RootState } from '../store'
interface DaresState {
dares: Dare[]
shuffledDares: Dare[]
}
const initialState: DaresState = {
dares: [
{
id: 0,
dare: 'Дія 1',
},
{
id: 1,
dare: 'Дія 2',
},
{
id: 2,
dare: 'Дія 3',
},
{
id: 3,
dare: 'Дія 4',
},
{
id: 4,
dare: 'Дія 5',
},
],
shuffledDares: [],
}
export const DaresSlice = createSlice({
name: 'dares',
initialState,
reducers: {
shuffleDares: state => {
state.shuffledDares = _.shuffle(state.dares)
},
},
})
export const { shuffleDares } = DaresSlice.actions
export const getDares = (state: RootState) => state.dares.dares
export const getShuffledDares = (state: RootState) => state.dares.shuffledDares
export default DaresSlice.reducer

3
src/store/slices/index.ts

@ -1,4 +1,3 @@ @@ -1,4 +1,3 @@
export * from './current-step-slice'
export * from './dares-slice'
export * from './posts.slice'
export * from './truth-slice'
export * from './custom-package.slice';

12
src/store/slices/posts.slice.ts

@ -21,12 +21,12 @@ const initialState: PostsState = { @@ -21,12 +21,12 @@ const initialState: PostsState = {
export const fetchPostsAsync = createAsyncThunk(
'posts/fetchPosts',
async () => {
const data = [] as GameItem[]
let querySnapshot = await firestore().collection('GameItems').get()
querySnapshot.forEach(doc => {
const item = doc.data()
data.push(item as GameItem)
})
const querySnapshot = await firestore().collection('GameItems').get()
const data: GameItem[] = querySnapshot.docs.map(
doc => doc.data() as GameItem,
)
return data
},
)

53
src/store/slices/truth-slice.ts

@ -1,53 +0,0 @@ @@ -1,53 +0,0 @@
import { createSlice } from '@reduxjs/toolkit'
import { RootState } from '../store'
import { Truth } from '../../module/common'
import _ from 'lodash'
interface TruthsState {
truths: Truth[]
shuffledTruth: Truth[]
}
const initialState: TruthsState = {
truths: [
{
id: 0,
question: 'Питання 1',
},
{
id: 1,
question: 'Питання 2',
},
{
id: 2,
question: 'Питання 3',
},
{
id: 3,
question: 'Питання 4',
},
{
id: 4,
question: 'Питання 5',
},
],
shuffledTruth: [],
}
export const TruthSlice = createSlice({
name: 'truth',
initialState,
reducers: {
shuffleTruths: state => {
state.shuffledTruth = _.shuffle(state.truths)
},
},
})
export const { shuffleTruths } = TruthSlice.actions
export const getTruths = (state: RootState) => state.truth.truths
export const getShuffledTruths = (state: RootState) => state.truth.shuffledTruth
export default TruthSlice.reducer

13
src/store/store.ts

@ -1,24 +1,15 @@ @@ -1,24 +1,15 @@
import { Action, configureStore, ThunkAction } from '@reduxjs/toolkit'
import truthsSlice from './slices/truth-slice'
import currentStepSlice from './slices/current-step-slice'
import daresSlice from './slices/dares-slice'
import postsSlice from './slices/posts.slice'
import customPackage from './slices/custom-package.slice'
export const store = configureStore({
reducer: {
currentStep: currentStepSlice,
truth: truthsSlice,
dares: daresSlice,
gameItems: postsSlice,
customPackage,
},
})
export type AppDispatch = typeof store.dispatch
export type RootState = ReturnType<typeof store.getState>
export type AppThunk<ReturnType = void> = ThunkAction<
ReturnType,
RootState,
unknown,
Action<string>
>

Loading…
Cancel
Save