@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.
- package/.claude/mcp.json +0 -0
- package/.claude/rules/abstract-skills-agents.mdc +25 -0
- package/.claude/rules/app-routing.mdc +47 -0
- package/.claude/rules/exports-at-end.mdc +55 -0
- package/.claude/rules/kebab-case-naming.mdc +29 -0
- package/.claude/rules/prettier-eslint-typescript.mdc +24 -0
- package/.claude/settings.json +0 -0
- package/.claude/skills/generate-api-model/SKILL.md +98 -0
- package/.claude/skills/generate-api-model-mock/SKILL.md +56 -0
- package/.env +1 -0
- package/.env.example +1 -0
- package/.husky/pre-commit +6 -0
- package/.secret/README.md +26 -0
- package/README.md +36 -0
- package/app.config.js +54 -0
- package/env.d.ts +7 -0
- package/eslint.config.js +14 -0
- package/i18next.d.ts +12 -0
- package/package.json +76 -0
- package/prettier.config.js +9 -0
- package/src/app/_app.tsx +18 -0
- package/src/app/_layout.tsx +20 -0
- package/src/app/_splash.tsx +22 -0
- package/src/entities/.gitkeep +0 -0
- package/src/features/.gitkeep +0 -0
- package/src/screens/.gitkeep +0 -0
- package/src/shared/assets/fonts/index.ts +7 -0
- package/src/shared/assets/icons/index.ts +3 -0
- package/src/shared/assets/icons/like.tsx +17 -0
- package/src/shared/assets/icons/type.ts +6 -0
- package/src/shared/assets/images/default.png +0 -0
- package/src/shared/assets/images/index.ts +11 -0
- package/src/shared/assets/images/logo-android-background.png +0 -0
- package/src/shared/assets/images/logo-android-foreground.png +0 -0
- package/src/shared/assets/images/logo-android-monochrome.png +0 -0
- package/src/shared/assets/images/logo-ios.png +0 -0
- package/src/shared/assets/images/logo-web.png +0 -0
- package/src/shared/assets/images/splash-dark.png +0 -0
- package/src/shared/assets/images/splash.png +0 -0
- package/src/shared/assets/index.ts +4 -0
- package/src/shared/assets/translations/en.ts +3 -0
- package/src/shared/assets/translations/index.ts +9 -0
- package/src/shared/assets/translations/ru.ts +3 -0
- package/src/shared/constants/environment.ts +13 -0
- package/src/shared/constants/index.ts +1 -0
- package/src/shared/hooks/index.ts +4 -0
- package/src/shared/hooks/use-animated.ts +194 -0
- package/src/shared/hooks/use-breakpoints.ts +25 -0
- package/src/shared/hooks/use-mixins.ts +36 -0
- package/src/shared/hooks/use-safe-area.ts +56 -0
- package/src/shared/lib/api/api.provider.tsx +47 -0
- package/src/shared/lib/api/api.ts +7 -0
- package/src/shared/lib/api/api.types.ts +9 -0
- package/src/shared/lib/api/constants/index.ts +6 -0
- package/src/shared/lib/api/entities/common.ts +28 -0
- package/src/shared/lib/api/entities/index.ts +1 -0
- package/src/shared/lib/api/hooks/common.ts +31 -0
- package/src/shared/lib/api/hooks/index.ts +0 -0
- package/src/shared/lib/api/index.ts +4 -0
- package/src/shared/lib/api/models/api-instance/api-instance.constants.ts +9 -0
- package/src/shared/lib/api/models/api-instance/api-instance.ts +118 -0
- package/src/shared/lib/api/models/api-instance/api-instance.types.ts +31 -0
- package/src/shared/lib/api/models/api-instance/api-instance.utils.ts +22 -0
- package/src/shared/lib/api/models/api-instance/index.ts +1 -0
- package/src/shared/lib/api/models/index.ts +1 -0
- package/src/shared/lib/api/repositories/auth/auth.ts +18 -0
- package/src/shared/lib/api/repositories/auth/auth.types.ts +22 -0
- package/src/shared/lib/api/repositories/auth/index.ts +1 -0
- package/src/shared/lib/api/repositories/index.ts +1 -0
- package/src/shared/lib/api/resources/common.ts +8 -0
- package/src/shared/lib/api/resources/index.ts +1 -0
- package/src/shared/lib/api/utils/index.ts +1 -0
- package/src/shared/lib/api/utils/storage.ts +9 -0
- package/src/shared/lib/index.ts +4 -0
- package/src/shared/lib/localization/components/index.ts +1 -0
- package/src/shared/lib/localization/components/localization-translate/index.ts +1 -0
- package/src/shared/lib/localization/components/localization-translate/localization-translate.tsx +5 -0
- package/src/shared/lib/localization/components/localization-translate/localization-translate.types.ts +0 -0
- package/src/shared/lib/localization/constants/index.ts +54 -0
- package/src/shared/lib/localization/hooks/index.ts +2 -0
- package/src/shared/lib/localization/hooks/use-localization-setup.ts +34 -0
- package/src/shared/lib/localization/hooks/use-localization-text.ts +9 -0
- package/src/shared/lib/localization/hooks/use-localization.ts +23 -0
- package/src/shared/lib/localization/index.ts +4 -0
- package/src/shared/lib/localization/localization.provider.tsx +30 -0
- package/src/shared/lib/localization/localization.types.ts +5 -0
- package/src/shared/lib/localization/utils/index.ts +1 -0
- package/src/shared/lib/localization/utils/storage.ts +11 -0
- package/src/shared/lib/navigation/components/index.ts +1 -0
- package/src/shared/lib/navigation/components/navigation-logger/index.ts +1 -0
- package/src/shared/lib/navigation/components/navigation-logger/navigation-logger.ts +17 -0
- package/src/shared/lib/navigation/components/navigation-logger/navigation-logger.types.ts +0 -0
- package/src/shared/lib/navigation/hooks/index.ts +3 -0
- package/src/shared/lib/navigation/hooks/use-navigation-options.ts +94 -0
- package/src/shared/lib/navigation/hooks/use-navigation-params.ts +9 -0
- package/src/shared/lib/navigation/hooks/use-navigation.ts +9 -0
- package/src/shared/lib/navigation/index.ts +7 -0
- package/src/shared/lib/navigation/navigation.provider.tsx +26 -0
- package/src/shared/lib/navigation/navigation.types.ts +5 -0
- package/src/shared/lib/theme/constants/borders.ts +8 -0
- package/src/shared/lib/theme/constants/colors.ts +57 -0
- package/src/shared/lib/theme/constants/delays.ts +8 -0
- package/src/shared/lib/theme/constants/durations.ts +8 -0
- package/src/shared/lib/theme/constants/fonts.ts +7 -0
- package/src/shared/lib/theme/constants/index.ts +54 -0
- package/src/shared/lib/theme/constants/shadows.ts +40 -0
- package/src/shared/lib/theme/constants/sizes.ts +32 -0
- package/src/shared/lib/theme/constants/transparencies.ts +10 -0
- package/src/shared/lib/theme/constants/weights.ts +13 -0
- package/src/shared/lib/theme/hooks/index.ts +2 -0
- package/src/shared/lib/theme/hooks/use-theme-mode.ts +9 -0
- package/src/shared/lib/theme/hooks/use-theme-setup.ts +49 -0
- package/src/shared/lib/theme/hooks/use-theme.ts +15 -0
- package/src/shared/lib/theme/index.ts +4 -0
- package/src/shared/lib/theme/theme.context.ts +6 -0
- package/src/shared/lib/theme/theme.provider.tsx +60 -0
- package/src/shared/lib/theme/theme.types.ts +15 -0
- package/src/shared/lib/theme/utils/index.ts +1 -0
- package/src/shared/lib/theme/utils/storage.ts +11 -0
- package/src/shared/ui/atoms/index.ts +1 -0
- package/src/shared/ui/index.ts +4 -0
- package/src/shared/ui/molecules/index.ts +2 -0
- package/src/shared/ui/molecules/keyboard-area/index.ts +2 -0
- package/src/shared/ui/molecules/keyboard-area/keyboard-area.styles.ts +20 -0
- package/src/shared/ui/molecules/keyboard-area/keyboard-area.tsx +34 -0
- package/src/shared/ui/molecules/keyboard-area/keyboard-area.types.ts +3 -0
- package/src/shared/ui/molecules/refresh-control/index.ts +2 -0
- package/src/shared/ui/molecules/refresh-control/refresh-control.tsx +32 -0
- package/src/shared/ui/molecules/refresh-control/refresh-control.types.ts +6 -0
- package/src/shared/ui/organisms/index.ts +1 -0
- package/src/shared/ui/templates/index.ts +1 -0
- package/src/shared/ui/templates/screen-container/index.ts +12 -0
- package/src/shared/ui/templates/screen-container/screen-container-list.tsx +23 -0
- package/src/shared/ui/templates/screen-container/screen-container-scroll.tsx +23 -0
- package/src/shared/ui/templates/screen-container/screen-container-section-list.tsx +24 -0
- package/src/shared/ui/templates/screen-container/screen-container.hooks.tsx +72 -0
- package/src/shared/ui/templates/screen-container/screen-container.styles.ts +45 -0
- package/src/shared/ui/templates/screen-container/screen-container.types.tsx +75 -0
- package/src/shared/utils/generators.ts +11 -0
- package/src/shared/utils/index.ts +1 -0
- package/src/widgets/.gitkeep +0 -0
- package/svg.d.ts +7 -0
- 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,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
|
|
Binary file
|
|
@@ -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 }
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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,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,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
|