@mediacubeco/expo-template-fsd 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/.claude/mcp.json +0 -0
  2. package/.claude/rules/abstract-skills-agents.mdc +25 -0
  3. package/.claude/rules/app-routing.mdc +47 -0
  4. package/.claude/rules/exports-at-end.mdc +55 -0
  5. package/.claude/rules/kebab-case-naming.mdc +29 -0
  6. package/.claude/rules/prettier-eslint-typescript.mdc +24 -0
  7. package/.claude/settings.json +0 -0
  8. package/.claude/skills/generate-api-model/SKILL.md +98 -0
  9. package/.claude/skills/generate-api-model-mock/SKILL.md +56 -0
  10. package/.env +1 -0
  11. package/.env.example +1 -0
  12. package/.husky/pre-commit +6 -0
  13. package/.secret/README.md +26 -0
  14. package/README.md +36 -0
  15. package/app.config.js +54 -0
  16. package/env.d.ts +7 -0
  17. package/eslint.config.js +14 -0
  18. package/i18next.d.ts +12 -0
  19. package/package.json +76 -0
  20. package/prettier.config.js +9 -0
  21. package/src/app/_app.tsx +18 -0
  22. package/src/app/_layout.tsx +20 -0
  23. package/src/app/_splash.tsx +22 -0
  24. package/src/entities/.gitkeep +0 -0
  25. package/src/features/.gitkeep +0 -0
  26. package/src/screens/.gitkeep +0 -0
  27. package/src/shared/assets/fonts/index.ts +7 -0
  28. package/src/shared/assets/icons/index.ts +3 -0
  29. package/src/shared/assets/icons/like.tsx +17 -0
  30. package/src/shared/assets/icons/type.ts +6 -0
  31. package/src/shared/assets/images/default.png +0 -0
  32. package/src/shared/assets/images/index.ts +11 -0
  33. package/src/shared/assets/images/logo-android-background.png +0 -0
  34. package/src/shared/assets/images/logo-android-foreground.png +0 -0
  35. package/src/shared/assets/images/logo-android-monochrome.png +0 -0
  36. package/src/shared/assets/images/logo-ios.png +0 -0
  37. package/src/shared/assets/images/logo-web.png +0 -0
  38. package/src/shared/assets/images/splash-dark.png +0 -0
  39. package/src/shared/assets/images/splash.png +0 -0
  40. package/src/shared/assets/index.ts +4 -0
  41. package/src/shared/assets/translations/en.ts +3 -0
  42. package/src/shared/assets/translations/index.ts +9 -0
  43. package/src/shared/assets/translations/ru.ts +3 -0
  44. package/src/shared/constants/environment.ts +13 -0
  45. package/src/shared/constants/index.ts +1 -0
  46. package/src/shared/hooks/index.ts +4 -0
  47. package/src/shared/hooks/use-animated.ts +194 -0
  48. package/src/shared/hooks/use-breakpoints.ts +25 -0
  49. package/src/shared/hooks/use-mixins.ts +36 -0
  50. package/src/shared/hooks/use-safe-area.ts +56 -0
  51. package/src/shared/lib/api/api.provider.tsx +47 -0
  52. package/src/shared/lib/api/api.ts +7 -0
  53. package/src/shared/lib/api/api.types.ts +9 -0
  54. package/src/shared/lib/api/constants/index.ts +6 -0
  55. package/src/shared/lib/api/entities/common.ts +28 -0
  56. package/src/shared/lib/api/entities/index.ts +1 -0
  57. package/src/shared/lib/api/hooks/common.ts +31 -0
  58. package/src/shared/lib/api/hooks/index.ts +0 -0
  59. package/src/shared/lib/api/index.ts +4 -0
  60. package/src/shared/lib/api/models/api-instance/api-instance.constants.ts +9 -0
  61. package/src/shared/lib/api/models/api-instance/api-instance.ts +118 -0
  62. package/src/shared/lib/api/models/api-instance/api-instance.types.ts +31 -0
  63. package/src/shared/lib/api/models/api-instance/api-instance.utils.ts +22 -0
  64. package/src/shared/lib/api/models/api-instance/index.ts +1 -0
  65. package/src/shared/lib/api/models/index.ts +1 -0
  66. package/src/shared/lib/api/repositories/auth/auth.ts +18 -0
  67. package/src/shared/lib/api/repositories/auth/auth.types.ts +22 -0
  68. package/src/shared/lib/api/repositories/auth/index.ts +1 -0
  69. package/src/shared/lib/api/repositories/index.ts +1 -0
  70. package/src/shared/lib/api/resources/common.ts +8 -0
  71. package/src/shared/lib/api/resources/index.ts +1 -0
  72. package/src/shared/lib/api/utils/index.ts +1 -0
  73. package/src/shared/lib/api/utils/storage.ts +9 -0
  74. package/src/shared/lib/index.ts +4 -0
  75. package/src/shared/lib/localization/components/index.ts +1 -0
  76. package/src/shared/lib/localization/components/localization-translate/index.ts +1 -0
  77. package/src/shared/lib/localization/components/localization-translate/localization-translate.tsx +5 -0
  78. package/src/shared/lib/localization/components/localization-translate/localization-translate.types.ts +0 -0
  79. package/src/shared/lib/localization/constants/index.ts +54 -0
  80. package/src/shared/lib/localization/hooks/index.ts +2 -0
  81. package/src/shared/lib/localization/hooks/use-localization-setup.ts +34 -0
  82. package/src/shared/lib/localization/hooks/use-localization-text.ts +9 -0
  83. package/src/shared/lib/localization/hooks/use-localization.ts +23 -0
  84. package/src/shared/lib/localization/index.ts +4 -0
  85. package/src/shared/lib/localization/localization.provider.tsx +30 -0
  86. package/src/shared/lib/localization/localization.types.ts +5 -0
  87. package/src/shared/lib/localization/utils/index.ts +1 -0
  88. package/src/shared/lib/localization/utils/storage.ts +11 -0
  89. package/src/shared/lib/navigation/components/index.ts +1 -0
  90. package/src/shared/lib/navigation/components/navigation-logger/index.ts +1 -0
  91. package/src/shared/lib/navigation/components/navigation-logger/navigation-logger.ts +17 -0
  92. package/src/shared/lib/navigation/components/navigation-logger/navigation-logger.types.ts +0 -0
  93. package/src/shared/lib/navigation/hooks/index.ts +3 -0
  94. package/src/shared/lib/navigation/hooks/use-navigation-options.ts +94 -0
  95. package/src/shared/lib/navigation/hooks/use-navigation-params.ts +9 -0
  96. package/src/shared/lib/navigation/hooks/use-navigation.ts +9 -0
  97. package/src/shared/lib/navigation/index.ts +7 -0
  98. package/src/shared/lib/navigation/navigation.provider.tsx +26 -0
  99. package/src/shared/lib/navigation/navigation.types.ts +5 -0
  100. package/src/shared/lib/theme/constants/borders.ts +8 -0
  101. package/src/shared/lib/theme/constants/colors.ts +57 -0
  102. package/src/shared/lib/theme/constants/delays.ts +8 -0
  103. package/src/shared/lib/theme/constants/durations.ts +8 -0
  104. package/src/shared/lib/theme/constants/fonts.ts +7 -0
  105. package/src/shared/lib/theme/constants/index.ts +54 -0
  106. package/src/shared/lib/theme/constants/shadows.ts +40 -0
  107. package/src/shared/lib/theme/constants/sizes.ts +32 -0
  108. package/src/shared/lib/theme/constants/transparencies.ts +10 -0
  109. package/src/shared/lib/theme/constants/weights.ts +13 -0
  110. package/src/shared/lib/theme/hooks/index.ts +2 -0
  111. package/src/shared/lib/theme/hooks/use-theme-mode.ts +9 -0
  112. package/src/shared/lib/theme/hooks/use-theme-setup.ts +49 -0
  113. package/src/shared/lib/theme/hooks/use-theme.ts +15 -0
  114. package/src/shared/lib/theme/index.ts +4 -0
  115. package/src/shared/lib/theme/theme.context.ts +6 -0
  116. package/src/shared/lib/theme/theme.provider.tsx +60 -0
  117. package/src/shared/lib/theme/theme.types.ts +15 -0
  118. package/src/shared/lib/theme/utils/index.ts +1 -0
  119. package/src/shared/lib/theme/utils/storage.ts +11 -0
  120. package/src/shared/ui/atoms/index.ts +1 -0
  121. package/src/shared/ui/index.ts +4 -0
  122. package/src/shared/ui/molecules/index.ts +2 -0
  123. package/src/shared/ui/molecules/keyboard-area/index.ts +2 -0
  124. package/src/shared/ui/molecules/keyboard-area/keyboard-area.styles.ts +20 -0
  125. package/src/shared/ui/molecules/keyboard-area/keyboard-area.tsx +34 -0
  126. package/src/shared/ui/molecules/keyboard-area/keyboard-area.types.ts +3 -0
  127. package/src/shared/ui/molecules/refresh-control/index.ts +2 -0
  128. package/src/shared/ui/molecules/refresh-control/refresh-control.tsx +32 -0
  129. package/src/shared/ui/molecules/refresh-control/refresh-control.types.ts +6 -0
  130. package/src/shared/ui/organisms/index.ts +1 -0
  131. package/src/shared/ui/templates/index.ts +1 -0
  132. package/src/shared/ui/templates/screen-container/index.ts +12 -0
  133. package/src/shared/ui/templates/screen-container/screen-container-list.tsx +23 -0
  134. package/src/shared/ui/templates/screen-container/screen-container-scroll.tsx +23 -0
  135. package/src/shared/ui/templates/screen-container/screen-container-section-list.tsx +24 -0
  136. package/src/shared/ui/templates/screen-container/screen-container.hooks.tsx +72 -0
  137. package/src/shared/ui/templates/screen-container/screen-container.styles.ts +45 -0
  138. package/src/shared/ui/templates/screen-container/screen-container.types.tsx +75 -0
  139. package/src/shared/utils/generators.ts +11 -0
  140. package/src/shared/utils/index.ts +1 -0
  141. package/src/widgets/.gitkeep +0 -0
  142. package/svg.d.ts +7 -0
  143. package/tsconfig.json +8 -0
@@ -0,0 +1,22 @@
1
+ import { useEffect } from 'react'
2
+ import * as SplashScreen from 'expo-splash-screen'
3
+ import { useFonts } from 'expo-font'
4
+ import { FONTS } from '~shared/assets'
5
+
6
+ SplashScreen.preventAutoHideAsync()
7
+
8
+ const Splash = () => {
9
+ const [isFontLoaded] = useFonts(FONTS)
10
+
11
+ const isReady = isFontLoaded
12
+
13
+ useEffect(() => {
14
+ if (isReady) {
15
+ setTimeout(() => SplashScreen.hideAsync())
16
+ }
17
+ }, [isReady])
18
+
19
+ return null
20
+ }
21
+
22
+ export default Splash
File without changes
File without changes
File without changes
@@ -0,0 +1,7 @@
1
+ const FONTS = {
2
+ // BASE_MEDIUM: require('./manrope-medium.ttf'),
3
+ // BASE_SEMIBOLD: require('./manrope-semibold.ttf'),
4
+ // BASE_BOLD: require('./manrope-bold.ttf'),
5
+ }
6
+
7
+ export { FONTS }
@@ -0,0 +1,3 @@
1
+ const ICONS = {}
2
+
3
+ export { ICONS }
@@ -0,0 +1,17 @@
1
+ import { Path, Svg } from 'react-native-svg'
2
+ import { IconProps } from './type'
3
+
4
+ const Like = ({ size, color }: IconProps) => {
5
+ return (
6
+ <Svg width={size} height={size} viewBox={'0 0 24 24'} fill={'none'}>
7
+ <Path
8
+ d={
9
+ 'M12.0817 17.2109L11.9999 17.2927L11.91 17.2109C8.0272 13.6878 5.46044 11.3581 5.46044 8.99567C5.46044 7.36079 6.6866 6.13463 8.32147 6.13463C9.58033 6.13463 10.8065 6.95207 11.2397 8.06379H12.7602C13.1934 6.95207 14.4196 6.13463 15.6784 6.13463C17.3133 6.13463 18.5395 7.36079 18.5395 8.99567C18.5395 11.3581 15.9727 13.6878 12.0817 17.2109ZM15.6784 4.49976C14.2561 4.49976 12.891 5.16188 11.9999 6.20003C11.1089 5.16188 9.74382 4.49976 8.32147 4.49976C5.80376 4.49976 3.82556 6.46978 3.82556 8.99567C3.82556 12.0774 6.60485 14.6033 10.8147 18.4207L11.9999 19.4998L13.1852 18.4207C17.395 14.6033 20.1743 12.0774 20.1743 8.99567C20.1743 6.46978 18.1961 4.49976 15.6784 4.49976Z'
10
+ }
11
+ fill={color}
12
+ />
13
+ </Svg>
14
+ )
15
+ }
16
+
17
+ export default Like
@@ -0,0 +1,6 @@
1
+ type IconProps = {
2
+ size?: number
3
+ color?: string
4
+ }
5
+
6
+ export type { IconProps }
@@ -0,0 +1,11 @@
1
+ const IMAGES = {
2
+ LOGO_WEB: require('./logo-web.png'),
3
+ LOGO_IOS: require('./logo-ios.png'),
4
+ LOGO_ANDROID_BACKGROUND: require('./logo-android-background.png'),
5
+ LOGO_ANDROID_FOREGROUND: require('./logo-android-foreground.png'),
6
+ LOGO_ANDROID_MONOCHROME: require('./logo-android-monochrome.png'),
7
+ SPLASH: require('./splash.png'),
8
+ DEFAULT: require('./default.png'),
9
+ }
10
+
11
+ export { IMAGES }
@@ -0,0 +1,4 @@
1
+ export * from './fonts'
2
+ export * from './icons'
3
+ export * from './images'
4
+ export * from './translations'
@@ -0,0 +1,3 @@
1
+ const en = {}
2
+
3
+ export default en
@@ -0,0 +1,9 @@
1
+ import EN from './en'
2
+ import RU from './ru'
3
+
4
+ const TRANSLATIONS = {
5
+ EN,
6
+ RU,
7
+ }
8
+
9
+ export { TRANSLATIONS }
@@ -0,0 +1,3 @@
1
+ const ru = {}
2
+
3
+ export default ru
@@ -0,0 +1,13 @@
1
+ const API_URL = process.env.EXPO_PUBLIC_API_URL as `https://${string}`
2
+
3
+ if (!API_URL || !API_URL.startsWith('https://')) {
4
+ throw new Error(`API_URL is invalid: ${API_URL}`)
5
+ }
6
+
7
+ const API_TOKEN = process.env.EXPO_PUBLIC_API_TOKEN
8
+
9
+ if (!API_TOKEN) {
10
+ throw new Error(`API_TOKEN is invalid: ${API_TOKEN}`)
11
+ }
12
+
13
+ export { API_URL, API_TOKEN }
@@ -0,0 +1 @@
1
+ export * from './environment'
@@ -0,0 +1,4 @@
1
+ export * from './use-animated'
2
+ export * from './use-breakpoints'
3
+ export * from './use-mixins'
4
+ export * from './use-safe-area'
@@ -0,0 +1,194 @@
1
+ import { useCallback } from 'react'
2
+ import {
3
+ createAnimatedComponent as animate,
4
+ Extrapolation as AnimatedExtrapolation,
5
+ cancelAnimation,
6
+ interpolate as reInterpolate,
7
+ interpolateColor as reInterpolateColor,
8
+ useAnimatedStyle,
9
+ useSharedValue,
10
+ withClamp,
11
+ withDelay,
12
+ withRepeat,
13
+ withSequence,
14
+ withSpring,
15
+ withTiming,
16
+ } from 'react-native-reanimated'
17
+ import { runOnJS } from 'react-native-worklets'
18
+ import { Theme, useThemeMode } from '../lib'
19
+
20
+ type AnimatedState = string | number
21
+
22
+ type AnimatedStateRange = string[] | number[]
23
+
24
+ type AnimatedConfig = {
25
+ delay?: keyof Theme['DELAYS']
26
+ duration?: keyof Theme['DURATIONS']
27
+ repeat?: number
28
+ clamp?: [min?: AnimatedState, max?: AnimatedState]
29
+ }
30
+
31
+ const useAnimated = <Value extends AnimatedState = AnimatedState>(
32
+ initValue: Value,
33
+ config?: AnimatedConfig
34
+ ) => {
35
+ const value = useSharedValue(initValue)
36
+ const { DELAYS, DURATIONS } = useThemeMode()
37
+
38
+ const {
39
+ delay: defaultDelay = 'EMPTY',
40
+ duration: defaultDuration = 'BASE',
41
+ repeat: defaultRepeat = 1,
42
+ clamp: defaultCamp = [],
43
+ } = config ?? {}
44
+
45
+ const timing = useCallback(
46
+ async (newValue: Value, config?: AnimatedConfig) => {
47
+ const {
48
+ delay = defaultDelay,
49
+ duration = defaultDuration,
50
+ repeat = defaultRepeat,
51
+ clamp = defaultCamp,
52
+ } = config ?? {}
53
+
54
+ return new Promise((resolve) => {
55
+ value.value = withDelay(
56
+ DELAYS[delay],
57
+ withRepeat(
58
+ withClamp(
59
+ { min: clamp[0], max: clamp[1] },
60
+ withTiming(newValue, { duration: DURATIONS[duration] })
61
+ ),
62
+ repeat,
63
+ false,
64
+ () => runOnJS(resolve)(undefined)
65
+ )
66
+ ) as Value
67
+ })
68
+ },
69
+ [value, defaultDelay, defaultDuration, defaultRepeat, defaultCamp, DELAYS, DURATIONS]
70
+ )
71
+
72
+ const spring = useCallback(
73
+ async (newValue: Value, config?: AnimatedConfig) => {
74
+ const {
75
+ delay = defaultDelay,
76
+ duration = defaultDuration,
77
+ repeat = defaultRepeat,
78
+ clamp = defaultCamp,
79
+ } = config ?? {}
80
+
81
+ return new Promise((resolve) => {
82
+ value.value = withDelay(
83
+ DELAYS[delay],
84
+ withRepeat(
85
+ withClamp(
86
+ { min: clamp[0], max: clamp[1] },
87
+ withSpring(newValue, { duration: DURATIONS[duration] })
88
+ ),
89
+ repeat,
90
+ false,
91
+ () => runOnJS(resolve)(undefined)
92
+ ) as Value
93
+ )
94
+ })
95
+ },
96
+ [value, defaultDelay, defaultDuration, defaultRepeat, defaultCamp, DELAYS, DURATIONS]
97
+ )
98
+
99
+ const sequenceTiming = useCallback(
100
+ (newValue: Value[], config?: AnimatedConfig) => {
101
+ const {
102
+ delay = defaultDelay,
103
+ duration = defaultDuration,
104
+ repeat = defaultRepeat,
105
+ clamp = defaultCamp,
106
+ } = config ?? {}
107
+
108
+ return new Promise((resolve) => {
109
+ value.value = withDelay(
110
+ DELAYS[delay],
111
+ withRepeat(
112
+ withClamp(
113
+ { min: clamp[0], max: clamp[1] },
114
+ withSequence(
115
+ ...newValue.map((value) => withTiming(value, { duration: DURATIONS[duration] }))
116
+ )
117
+ ),
118
+ repeat,
119
+ false,
120
+ () => runOnJS(resolve)(undefined)
121
+ ) as Value
122
+ )
123
+ })
124
+ },
125
+ [value, defaultDelay, defaultDuration, defaultRepeat, defaultCamp, DELAYS, DURATIONS]
126
+ )
127
+
128
+ const sequenceSpring = useCallback(
129
+ (newValue: Value[], config?: AnimatedConfig) => {
130
+ const {
131
+ delay = defaultDelay,
132
+ duration = defaultDuration,
133
+ repeat = defaultRepeat,
134
+ clamp = defaultCamp,
135
+ } = config ?? {}
136
+
137
+ return new Promise((resolve) => {
138
+ value.value = withDelay(
139
+ DELAYS[delay],
140
+ withRepeat(
141
+ withClamp(
142
+ { min: clamp[0], max: clamp[1] },
143
+ withSequence(
144
+ ...newValue.map((value) => withSpring(value, { duration: DURATIONS[duration] }))
145
+ )
146
+ ),
147
+ repeat,
148
+ false,
149
+ () => runOnJS(resolve)(undefined)
150
+ ) as Value
151
+ )
152
+ })
153
+ },
154
+ [value, defaultDelay, defaultDuration, defaultRepeat, defaultCamp, DELAYS, DURATIONS]
155
+ )
156
+
157
+ const stop = useCallback(() => {
158
+ 'worklet'
159
+
160
+ cancelAnimation(value)
161
+ }, [value])
162
+
163
+ const interpolate = useCallback(
164
+ <Range extends AnimatedStateRange>(
165
+ range: Range,
166
+ inputRange?: number[],
167
+ extrapolation?: AnimatedExtrapolation
168
+ ): Range[number] => {
169
+ 'worklet'
170
+
171
+ const defaultInputRange = range.map((_, index, { length }) => (1 / (length - 1)) * index)
172
+
173
+ if (typeof range[0] === 'number') {
174
+ return reInterpolate(
175
+ value.value as number,
176
+ inputRange ?? defaultInputRange,
177
+ range as number[],
178
+ extrapolation
179
+ )
180
+ } else {
181
+ return reInterpolateColor(
182
+ value.value as number,
183
+ inputRange ?? defaultInputRange,
184
+ range as string[]
185
+ )
186
+ }
187
+ },
188
+ [value]
189
+ )
190
+
191
+ return [value, { timing, spring, sequenceTiming, sequenceSpring, stop, interpolate }] as const
192
+ }
193
+
194
+ export { useAnimated, useAnimatedStyle, animate, AnimatedExtrapolation }
@@ -0,0 +1,25 @@
1
+ import { useWindowDimensions } from 'react-native'
2
+
3
+ const useBreakpoints = () => {
4
+ const { width } = useWindowDimensions()
5
+
6
+ return {
7
+ isXXLMaxBreakpoint: width <= 1999,
8
+ isXLMaxBreakpoint: width <= 1399,
9
+ isLMaxBreakpoint: width <= 1199,
10
+ isMMaxBreakpoint: width <= 991,
11
+ isSMaxBreakpoint: width <= 767,
12
+ isXSMaxBreakpoint: width <= 575,
13
+ isXXLMinBreakpoint: width >= 2000,
14
+ isXLMinBreakpoint: width >= 1400,
15
+ isLMinBreakpoint: width >= 1200,
16
+ isMMinBreakpoint: width >= 992,
17
+ isSMinBreakpoint: width >= 768,
18
+ isXSMinBreakpoint: width >= 576,
19
+ }
20
+ }
21
+
22
+ type Breakpoints = ReturnType<typeof useBreakpoints>
23
+
24
+ export { useBreakpoints }
25
+ export type { Breakpoints }
@@ -0,0 +1,36 @@
1
+ import { useCallback } from 'react'
2
+ import { useWindowDimensions } from 'react-native'
3
+
4
+ const useMixins = () => {
5
+ const { width, height } = useWindowDimensions()
6
+
7
+ /**
8
+ * @default 1
9
+ **/
10
+ const byWindowWidth = useCallback(
11
+ (value: number = 1) => {
12
+ return width * value
13
+ },
14
+ [width]
15
+ )
16
+
17
+ /**
18
+ * @default 1
19
+ **/
20
+ const byWindowHeight = useCallback(
21
+ (value: number = 1) => {
22
+ return height * value
23
+ },
24
+ [height]
25
+ )
26
+
27
+ return {
28
+ byWindowWidth,
29
+ byWindowHeight,
30
+ }
31
+ }
32
+
33
+ type Mixins = ReturnType<typeof useMixins>
34
+
35
+ export { useMixins }
36
+ export type { Mixins }
@@ -0,0 +1,56 @@
1
+ import { Platform } from 'react-native'
2
+ import { useSafeAreaInsets } from 'react-native-safe-area-context'
3
+ import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs'
4
+ import { useHeaderHeight } from '@react-navigation/elements'
5
+
6
+ const ANDROID_TAB_BAR_DEFAULT_HEIGHT = 56
7
+ const IOS_TAB_BAR_DEFAULT_HEIGHT = 50
8
+
9
+ const useNavigationHeaderHeight = (safeArea: number = 0) => {
10
+ try {
11
+ const height = useHeaderHeight()
12
+
13
+ return Platform.select({
14
+ android: height ? 0 : safeArea,
15
+ ios: height || safeArea,
16
+ default: safeArea,
17
+ })
18
+ } catch (_) {
19
+ return safeArea
20
+ }
21
+ }
22
+
23
+ const useNavigationFooterHeight = (safeArea: number = 0) => {
24
+ try {
25
+ const height = useBottomTabBarHeight()
26
+
27
+ return Platform.select({
28
+ android: (height || ANDROID_TAB_BAR_DEFAULT_HEIGHT) + safeArea,
29
+ ios: (height || IOS_TAB_BAR_DEFAULT_HEIGHT) + safeArea,
30
+ default: safeArea,
31
+ })
32
+ } catch (_) {
33
+ return Platform.select({
34
+ android: 0,
35
+ default: safeArea,
36
+ })
37
+ }
38
+ }
39
+
40
+ const useSafeArea = () => {
41
+ const { top, bottom, left, right } = useSafeAreaInsets()
42
+ const header = useNavigationHeaderHeight(top)
43
+ const footer = useNavigationFooterHeight(bottom)
44
+
45
+ return {
46
+ TOP: header,
47
+ BOTTOM: footer,
48
+ LEFT: left,
49
+ RIGHT: right,
50
+ }
51
+ }
52
+
53
+ type SafeArea = ReturnType<typeof useSafeArea>
54
+
55
+ export { useSafeArea }
56
+ export type { SafeArea }
@@ -0,0 +1,47 @@
1
+ import { listenAppState } from '@mediacubeco/base-react-native/utils'
2
+ import { createAsyncStoragePersister } from '@tanstack/query-async-storage-persister'
3
+ import { focusManager, QueryClient } from '@tanstack/react-query'
4
+ import {
5
+ PersistQueryClientProvider,
6
+ PersistQueryClientProviderProps,
7
+ } from '@tanstack/react-query-persist-client'
8
+ import { apiAsyncStorage } from './utils'
9
+ import { ApiProviderProps } from './api.types'
10
+
11
+ const client = new QueryClient({
12
+ defaultOptions: {
13
+ queries: {
14
+ retry: 2,
15
+ gcTime: 1000 * 60 * 60 * 24,
16
+ },
17
+ },
18
+ })
19
+
20
+ const persistOptions = {
21
+ maxAge: 1000 * 60 * 60 * 24,
22
+ dehydrateOptions: {
23
+ shouldDehydrateQuery: (query) => {
24
+ return query.state.status === 'success' && query.meta?.persist === true
25
+ },
26
+ },
27
+ persister: createAsyncStoragePersister({
28
+ storage: apiAsyncStorage,
29
+ throttleTime: 1000,
30
+ }),
31
+ } satisfies PersistQueryClientProviderProps['persistOptions']
32
+
33
+ listenAppState({
34
+ onChange: (status) => {
35
+ focusManager.setFocused(status === 'active')
36
+ },
37
+ })
38
+
39
+ const ApiProvider = ({ children }: ApiProviderProps) => {
40
+ return (
41
+ <PersistQueryClientProvider client={client} persistOptions={persistOptions}>
42
+ {children}
43
+ </PersistQueryClientProvider>
44
+ )
45
+ }
46
+
47
+ export default ApiProvider
@@ -0,0 +1,7 @@
1
+ import { AuthRepository } from './repositories'
2
+
3
+ class Api {
4
+ static authRepository = new AuthRepository('/auth')
5
+ }
6
+
7
+ export { Api }
@@ -0,0 +1,9 @@
1
+ import { PropsWithChildren } from 'react'
2
+
3
+ type ApiResponse<Response = {}> = {
4
+ data: Partial<Response>
5
+ }
6
+
7
+ type ApiProviderProps = PropsWithChildren
8
+
9
+ export type { ApiResponse, ApiProviderProps }
@@ -0,0 +1,6 @@
1
+ const QUERY_KEYS = {
2
+ POSTS: 'posts',
3
+ POST: 'post',
4
+ }
5
+
6
+ export { QUERY_KEYS }
@@ -0,0 +1,28 @@
1
+ import { generateIdFactory } from '../../../utils'
2
+ import { BaseResource } from '../resources'
3
+
4
+ const generateId = generateIdFactory()
5
+
6
+ class BaseEntity<Resource extends BaseResource> {
7
+ protected readonly resource?: Resource
8
+ protected readonly context?: BaseResource
9
+
10
+ id: string
11
+ createdAt: Date
12
+ updatedAt: Date
13
+
14
+ constructor(resource?: Resource, context?: BaseResource) {
15
+ this.resource = resource
16
+ this.context = context
17
+
18
+ this.id = resource?.id ?? generateId()
19
+ this.createdAt = resource?.createdAt ?? new Date()
20
+ this.updatedAt = resource?.updatedAt ?? new Date()
21
+ }
22
+
23
+ get isPending() {
24
+ return this.resource?.isPending ?? this.context?.isPending ?? false
25
+ }
26
+ }
27
+
28
+ export { BaseEntity }
@@ -0,0 +1 @@
1
+ export * from './common'
@@ -0,0 +1,31 @@
1
+ import { useCallback } from 'react'
2
+ import { useInfiniteQuery } from '@tanstack/react-query'
3
+
4
+ const useInfinite = <Config extends ReturnType<typeof useInfiniteQuery>, Parsed>(
5
+ config: Config,
6
+ parseData: (data: Config['data']) => Parsed
7
+ ) => {
8
+ const { data, hasNextPage, refetch, fetchNextPage, ...restConfig } = config
9
+
10
+ const refresh = useCallback(async () => {
11
+ await refetch()
12
+ }, [refetch])
13
+
14
+ const reach = useCallback(() => {
15
+ fetchNextPage()
16
+ }, [fetchNextPage])
17
+
18
+ return {
19
+ data: parseData(data),
20
+ hasMore: hasNextPage,
21
+ refresh,
22
+ reach,
23
+ //
24
+ hasNextPage,
25
+ refetch,
26
+ fetchNextPage,
27
+ ...restConfig,
28
+ }
29
+ }
30
+
31
+ export { useInfinite }
File without changes
@@ -0,0 +1,4 @@
1
+ export {} from './entities'
2
+ export {} from './resources'
3
+ export {} from './hooks'
4
+ export { default as ApiProvider } from './api.provider'
@@ -0,0 +1,9 @@
1
+ enum ApiInstanceMethod {
2
+ Get = 'get',
3
+ Post = 'post',
4
+ Put = 'put',
5
+ Patch = 'patch',
6
+ Delete = 'delete',
7
+ }
8
+
9
+ export { ApiInstanceMethod }