@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,118 @@
|
|
|
1
|
+
import { ErrorHandler, Log } from '@mediacubeco/base/helpers'
|
|
2
|
+
import { API_TOKEN, API_URL } from '../../../../constants'
|
|
3
|
+
import { formatRoute, formatURI } from './api-instance.utils'
|
|
4
|
+
import { ApiInstanceMethod } from './api-instance.constants'
|
|
5
|
+
import {
|
|
6
|
+
ApiInstanceConfig,
|
|
7
|
+
ApiInstanceDefaultBody,
|
|
8
|
+
ApiInstanceDefaultHeaders,
|
|
9
|
+
ApiInstanceDefaultParams,
|
|
10
|
+
ApiInstanceRoute,
|
|
11
|
+
ApiInstanceURL,
|
|
12
|
+
ApiInstanceURN,
|
|
13
|
+
} from './api-instance.types'
|
|
14
|
+
|
|
15
|
+
abstract class ApiInstance {
|
|
16
|
+
private url: ApiInstanceURL
|
|
17
|
+
private urn: ApiInstanceURN
|
|
18
|
+
|
|
19
|
+
private headers: ApiInstanceDefaultHeaders
|
|
20
|
+
|
|
21
|
+
constructor(urn: ApiInstanceURN) {
|
|
22
|
+
this.url = API_URL
|
|
23
|
+
this.urn = urn
|
|
24
|
+
|
|
25
|
+
this.headers = {
|
|
26
|
+
'Accept': 'application/json',
|
|
27
|
+
'Content-Type': 'application/json',
|
|
28
|
+
'Authorization': `Bearer ${API_TOKEN}`,
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private formatHeaders(headers?: ApiInstanceDefaultHeaders) {
|
|
33
|
+
return { ...this.headers, ...headers }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private async request<Result>(
|
|
37
|
+
method: ApiInstanceMethod,
|
|
38
|
+
route: ApiInstanceRoute,
|
|
39
|
+
{ params, body, headers }: ApiInstanceConfig = {}
|
|
40
|
+
): Promise<Result> {
|
|
41
|
+
const uri = formatURI(this.url, this.urn, formatRoute(route, params))
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const response = await fetch(uri, {
|
|
45
|
+
method,
|
|
46
|
+
body: JSON.stringify(body),
|
|
47
|
+
headers: this.formatHeaders(headers),
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
if (!response.ok) {
|
|
51
|
+
ErrorHandler.throw('API Error', response)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const data = await response.json()
|
|
55
|
+
|
|
56
|
+
Log.formatted.success(method, uri, { data, params, body, headers })
|
|
57
|
+
|
|
58
|
+
return data as Result
|
|
59
|
+
} catch (error) {
|
|
60
|
+
ErrorHandler.forward(error, {
|
|
61
|
+
signal: method,
|
|
62
|
+
message: uri,
|
|
63
|
+
payload: { params, body, headers },
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
protected updateHeaders(headers: ApiInstanceDefaultHeaders) {
|
|
69
|
+
this.headers = { ...this.headers, ...headers }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
protected async get<
|
|
73
|
+
Result,
|
|
74
|
+
Params extends ApiInstanceDefaultParams = ApiInstanceDefaultParams,
|
|
75
|
+
Body extends ApiInstanceDefaultBody = ApiInstanceDefaultBody,
|
|
76
|
+
Headers extends ApiInstanceDefaultHeaders = ApiInstanceDefaultHeaders,
|
|
77
|
+
>(route: ApiInstanceRoute, config?: ApiInstanceConfig<Body, Params, Headers>) {
|
|
78
|
+
return await this.request<Result>(ApiInstanceMethod.Get, route, config)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
protected async post<
|
|
82
|
+
Result,
|
|
83
|
+
Params extends ApiInstanceDefaultParams = ApiInstanceDefaultParams,
|
|
84
|
+
Body extends ApiInstanceDefaultBody = ApiInstanceDefaultBody,
|
|
85
|
+
Headers extends ApiInstanceDefaultHeaders = ApiInstanceDefaultHeaders,
|
|
86
|
+
>(route: ApiInstanceRoute, config?: ApiInstanceConfig<Body, Params, Headers>) {
|
|
87
|
+
return await this.request<Result>(ApiInstanceMethod.Post, route, config)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
protected async put<
|
|
91
|
+
Result,
|
|
92
|
+
Params extends ApiInstanceDefaultParams = ApiInstanceDefaultParams,
|
|
93
|
+
Body extends ApiInstanceDefaultBody = ApiInstanceDefaultBody,
|
|
94
|
+
Headers extends ApiInstanceDefaultHeaders = ApiInstanceDefaultHeaders,
|
|
95
|
+
>(route: ApiInstanceRoute, config?: ApiInstanceConfig<Body, Params, Headers>) {
|
|
96
|
+
return await this.request<Result>(ApiInstanceMethod.Put, route, config)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
protected async patch<
|
|
100
|
+
Result,
|
|
101
|
+
Params extends ApiInstanceDefaultParams = ApiInstanceDefaultParams,
|
|
102
|
+
Body extends ApiInstanceDefaultBody = ApiInstanceDefaultBody,
|
|
103
|
+
Headers extends ApiInstanceDefaultHeaders = ApiInstanceDefaultHeaders,
|
|
104
|
+
>(route: ApiInstanceRoute, config?: ApiInstanceConfig<Body, Params, Headers>) {
|
|
105
|
+
return await this.request<Result>(ApiInstanceMethod.Patch, route, config)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
protected async delete<
|
|
109
|
+
Result,
|
|
110
|
+
Params extends ApiInstanceDefaultParams = ApiInstanceDefaultParams,
|
|
111
|
+
Body extends ApiInstanceDefaultBody = ApiInstanceDefaultBody,
|
|
112
|
+
Headers extends ApiInstanceDefaultHeaders = ApiInstanceDefaultHeaders,
|
|
113
|
+
>(route: ApiInstanceRoute, config?: ApiInstanceConfig<Body, Params, Headers>) {
|
|
114
|
+
return await this.request<Result>(ApiInstanceMethod.Delete, route, config)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export default ApiInstance
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
type ApiInstanceURL = `https://${string}`
|
|
2
|
+
|
|
3
|
+
type ApiInstanceURN = `${'/'}${string}`
|
|
4
|
+
|
|
5
|
+
type ApiInstanceRoute = `/${string}`
|
|
6
|
+
|
|
7
|
+
type ApiInstanceDefaultParams = Record<string, string>
|
|
8
|
+
|
|
9
|
+
type ApiInstanceDefaultBody = Record<string, any>
|
|
10
|
+
|
|
11
|
+
type ApiInstanceDefaultHeaders = Record<string, string>
|
|
12
|
+
|
|
13
|
+
type ApiInstanceConfig<
|
|
14
|
+
Params extends ApiInstanceDefaultParams = ApiInstanceDefaultParams,
|
|
15
|
+
Body extends ApiInstanceDefaultBody = ApiInstanceDefaultBody,
|
|
16
|
+
Headers extends ApiInstanceDefaultHeaders = ApiInstanceDefaultHeaders,
|
|
17
|
+
> = {
|
|
18
|
+
params?: Params
|
|
19
|
+
body?: Body
|
|
20
|
+
headers?: Headers
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type {
|
|
24
|
+
ApiInstanceURL,
|
|
25
|
+
ApiInstanceURN,
|
|
26
|
+
ApiInstanceRoute,
|
|
27
|
+
ApiInstanceDefaultBody,
|
|
28
|
+
ApiInstanceDefaultParams,
|
|
29
|
+
ApiInstanceDefaultHeaders,
|
|
30
|
+
ApiInstanceConfig,
|
|
31
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ApiInstanceDefaultParams,
|
|
3
|
+
ApiInstanceRoute,
|
|
4
|
+
ApiInstanceURL,
|
|
5
|
+
ApiInstanceURN,
|
|
6
|
+
} from './api-instance.types'
|
|
7
|
+
|
|
8
|
+
const formatURI = (url: ApiInstanceURL, urn: ApiInstanceURN, route: ApiInstanceRoute) => {
|
|
9
|
+
return [url, urn, route].join('')
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const formatRoute = <Params extends ApiInstanceDefaultParams>(
|
|
13
|
+
route: ApiInstanceRoute,
|
|
14
|
+
params?: Params
|
|
15
|
+
) => {
|
|
16
|
+
const query = new URLSearchParams(params).toString()
|
|
17
|
+
const validRoute = route.endsWith('/') ? route.slice(0, -1) : route
|
|
18
|
+
|
|
19
|
+
return [validRoute, query].join('?') as ApiInstanceRoute
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export { formatURI, formatRoute }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as ApiInstance } from './api-instance'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './api-instance'
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ApiInstance } from '../../models'
|
|
2
|
+
import { SignInConfig, SignUpConfig, SignInResponse, SignUpResponse } from './auth.types'
|
|
3
|
+
|
|
4
|
+
class AuthRepository extends ApiInstance {
|
|
5
|
+
async signIn(_: SignInConfig) {
|
|
6
|
+
const { data } = await this.post<SignInResponse>('/sign-in')
|
|
7
|
+
|
|
8
|
+
return data
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async signUp(_: SignUpConfig) {
|
|
12
|
+
const { data } = await this.post<SignUpResponse>('/sign-up')
|
|
13
|
+
|
|
14
|
+
return data
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default AuthRepository
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ApiResponse } from '../../api.types'
|
|
2
|
+
|
|
3
|
+
// config
|
|
4
|
+
|
|
5
|
+
type SignInConfig = {}
|
|
6
|
+
|
|
7
|
+
type SignUpConfig = {}
|
|
8
|
+
|
|
9
|
+
// response
|
|
10
|
+
|
|
11
|
+
type SignInResponse = ApiResponse<{}>
|
|
12
|
+
|
|
13
|
+
type SignUpResponse = ApiResponse<{}>
|
|
14
|
+
|
|
15
|
+
export type {
|
|
16
|
+
// config
|
|
17
|
+
SignInConfig,
|
|
18
|
+
SignUpConfig,
|
|
19
|
+
// response
|
|
20
|
+
SignInResponse,
|
|
21
|
+
SignUpResponse,
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as AuthRepository } from './auth'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './auth'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './common'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './storage'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './localization-translate'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as LocalizationTranslate } from './localization-translate'
|
|
File without changes
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { getLocales } from 'expo-localization'
|
|
2
|
+
import { LanguageDetectorAsyncModule } from 'i18next'
|
|
3
|
+
import { TRANSLATIONS } from '../../../assets'
|
|
4
|
+
import { localizationStorage } from '../utils'
|
|
5
|
+
|
|
6
|
+
const locales = getLocales().map(({ languageCode }) => languageCode)
|
|
7
|
+
|
|
8
|
+
enum LocalizationCode {
|
|
9
|
+
Russian = 'ru',
|
|
10
|
+
English = 'en',
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const LOCALIZATION_RESOURCES = {
|
|
14
|
+
en: { translation: TRANSLATIONS.EN },
|
|
15
|
+
ru: { translation: TRANSLATIONS.RU },
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const LOCALIZATION_SUPPORTED_LOCALE_LIST = Object.keys(LOCALIZATION_RESOURCES) as LocalizationCode[]
|
|
19
|
+
|
|
20
|
+
const LOCALIZATION_CODE = (locales.find(
|
|
21
|
+
(locale) => locale && LOCALIZATION_SUPPORTED_LOCALE_LIST.includes(locale as LocalizationCode)
|
|
22
|
+
) ?? LOCALIZATION_SUPPORTED_LOCALE_LIST[0]) as LocalizationCode
|
|
23
|
+
|
|
24
|
+
const LOCALIZATION_CODE_FALLBACK = (locales.find(
|
|
25
|
+
(locale) =>
|
|
26
|
+
locale &&
|
|
27
|
+
locale !== LOCALIZATION_CODE &&
|
|
28
|
+
LOCALIZATION_SUPPORTED_LOCALE_LIST.includes(locale as LocalizationCode)
|
|
29
|
+
) ?? LOCALIZATION_SUPPORTED_LOCALE_LIST[1]) as LocalizationCode
|
|
30
|
+
|
|
31
|
+
const languageDetector: LanguageDetectorAsyncModule = {
|
|
32
|
+
type: 'languageDetector' as const,
|
|
33
|
+
|
|
34
|
+
async: true,
|
|
35
|
+
|
|
36
|
+
init() {},
|
|
37
|
+
|
|
38
|
+
async detect(resolve) {
|
|
39
|
+
const localizationCode = await localizationStorage.code.get({ defaultValue: LOCALIZATION_CODE })
|
|
40
|
+
|
|
41
|
+
resolve(localizationCode)
|
|
42
|
+
|
|
43
|
+
return localizationCode
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export {
|
|
48
|
+
LocalizationCode,
|
|
49
|
+
LOCALIZATION_RESOURCES,
|
|
50
|
+
LOCALIZATION_SUPPORTED_LOCALE_LIST,
|
|
51
|
+
LOCALIZATION_CODE,
|
|
52
|
+
LOCALIZATION_CODE_FALLBACK,
|
|
53
|
+
languageDetector,
|
|
54
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Log } from '@mediacubeco/base/helpers'
|
|
2
|
+
import { useCallback } from 'react'
|
|
3
|
+
import { LocalizationCode } from '../constants'
|
|
4
|
+
import { useLocalization } from './use-localization'
|
|
5
|
+
|
|
6
|
+
const useLocalizationSetup = () => {
|
|
7
|
+
const { localizationCode, setLocalizationCode } = useLocalization()
|
|
8
|
+
|
|
9
|
+
const isLocalizationCodeEnglish = localizationCode === LocalizationCode.English
|
|
10
|
+
const isLocalizationCodeRussian = localizationCode === LocalizationCode.Russian
|
|
11
|
+
|
|
12
|
+
const setLocalizationCodeEnglish = useCallback(() => {
|
|
13
|
+
setLocalizationCode(LocalizationCode.English)
|
|
14
|
+
|
|
15
|
+
Log.formatted.info('english', 'localization')
|
|
16
|
+
}, [setLocalizationCode])
|
|
17
|
+
|
|
18
|
+
const setLocalizationCodeRussian = useCallback(() => {
|
|
19
|
+
setLocalizationCode(LocalizationCode.Russian)
|
|
20
|
+
|
|
21
|
+
Log.formatted.info('russian', 'localization')
|
|
22
|
+
}, [setLocalizationCode])
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
localizationCode,
|
|
26
|
+
isLocalizationCodeEnglish,
|
|
27
|
+
isLocalizationCodeRussian,
|
|
28
|
+
setLocalizationCode,
|
|
29
|
+
setLocalizationCodeEnglish,
|
|
30
|
+
setLocalizationCodeRussian,
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export { useLocalizationSetup }
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { useCallback } from 'react'
|
|
2
|
+
import { useTranslation } from 'react-i18next'
|
|
3
|
+
import { LocalizationCode } from '../constants'
|
|
4
|
+
import { localizationStorage } from '../utils'
|
|
5
|
+
|
|
6
|
+
const useLocalization = () => {
|
|
7
|
+
const { t, i18n } = useTranslation()
|
|
8
|
+
|
|
9
|
+
const localizationCode = i18n.language as LocalizationCode
|
|
10
|
+
|
|
11
|
+
const setLocalizationCode = useCallback(
|
|
12
|
+
(code: LocalizationCode) => {
|
|
13
|
+
localizationStorage.code.set(code)
|
|
14
|
+
|
|
15
|
+
i18n.changeLanguage(code)
|
|
16
|
+
},
|
|
17
|
+
[i18n]
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
return { localizationCode, setLocalizationCode, t }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export { useLocalization }
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { useMemo } from 'react'
|
|
2
|
+
import { initReactI18next } from 'react-i18next'
|
|
3
|
+
import i18n from 'i18next'
|
|
4
|
+
import {
|
|
5
|
+
languageDetector,
|
|
6
|
+
LOCALIZATION_CODE_FALLBACK,
|
|
7
|
+
LOCALIZATION_RESOURCES,
|
|
8
|
+
LOCALIZATION_SUPPORTED_LOCALE_LIST,
|
|
9
|
+
} from './constants'
|
|
10
|
+
import { LocalizationProviderProps } from './localization.types'
|
|
11
|
+
|
|
12
|
+
const LocalizationProvider = ({ children }: LocalizationProviderProps) => {
|
|
13
|
+
useMemo(() => {
|
|
14
|
+
return i18n
|
|
15
|
+
.use(languageDetector)
|
|
16
|
+
.use(initReactI18next)
|
|
17
|
+
.init({
|
|
18
|
+
fallbackLng: LOCALIZATION_CODE_FALLBACK,
|
|
19
|
+
supportedLngs: LOCALIZATION_SUPPORTED_LOCALE_LIST,
|
|
20
|
+
resources: LOCALIZATION_RESOURCES,
|
|
21
|
+
interpolation: {
|
|
22
|
+
escapeValue: false,
|
|
23
|
+
},
|
|
24
|
+
})
|
|
25
|
+
}, [])
|
|
26
|
+
|
|
27
|
+
return <>{children}</>
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default LocalizationProvider
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './storage'
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Storage } from '@mediacubeco/base/services'
|
|
2
|
+
import * as SecureStore from 'expo-secure-store'
|
|
3
|
+
import { LocalizationCode } from '../constants'
|
|
4
|
+
|
|
5
|
+
const localizationStorage = new Storage({
|
|
6
|
+
get: SecureStore.getItemAsync,
|
|
7
|
+
set: SecureStore.setItemAsync,
|
|
8
|
+
delete: SecureStore.deleteItemAsync,
|
|
9
|
+
}).create<'code', LocalizationCode>('code')
|
|
10
|
+
|
|
11
|
+
export { localizationStorage }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './navigation-logger'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as NavigationLogger } from './navigation-logger'
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Log } from '@mediacubeco/base/helpers'
|
|
2
|
+
import { useEffect } from 'react'
|
|
3
|
+
import { useLocalSearchParams, usePathname, useSegments } from 'expo-router'
|
|
4
|
+
|
|
5
|
+
const NavigationLogger = () => {
|
|
6
|
+
const segments = useSegments()
|
|
7
|
+
const pathname = usePathname()
|
|
8
|
+
const params = useLocalSearchParams()
|
|
9
|
+
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
Log.formatted.info(segments.join('/'), 'navigation', { pathname, params })
|
|
12
|
+
}, [segments, pathname, params])
|
|
13
|
+
|
|
14
|
+
return null
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default NavigationLogger
|
|
File without changes
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { useCallback } from 'react'
|
|
2
|
+
import { Platform } from 'react-native'
|
|
3
|
+
import { isGlassEffectAPIAvailable } from 'expo-glass-effect'
|
|
4
|
+
import { StackScreenProps } from 'expo-router'
|
|
5
|
+
import {
|
|
6
|
+
NativeTabsProps,
|
|
7
|
+
NativeTabsTriggerIconProps,
|
|
8
|
+
NativeTabsTriggerLabelProps,
|
|
9
|
+
NativeTabTriggerProps,
|
|
10
|
+
} from 'expo-router/unstable-native-tabs'
|
|
11
|
+
import { useThemeMode, useThemeSetup } from '../../theme'
|
|
12
|
+
|
|
13
|
+
const useNavigationStackOptions = () => {
|
|
14
|
+
const { COLORS } = useThemeMode()
|
|
15
|
+
const { themeModeSystem } = useThemeSetup()
|
|
16
|
+
|
|
17
|
+
const createStackOptions = useCallback(() => {
|
|
18
|
+
return {
|
|
19
|
+
headerShown: false,
|
|
20
|
+
headerTransparent: false,
|
|
21
|
+
headerShadowVisible: false,
|
|
22
|
+
contentStyle: {
|
|
23
|
+
backgroundColor: COLORS.FILL.BASE,
|
|
24
|
+
},
|
|
25
|
+
} satisfies StackScreenProps['options']
|
|
26
|
+
}, [COLORS])
|
|
27
|
+
|
|
28
|
+
const createScreenOptions = useCallback(
|
|
29
|
+
(options?: StackScreenProps['options']) => {
|
|
30
|
+
return {
|
|
31
|
+
gestureEnabled: true,
|
|
32
|
+
gestureDirection: 'horizontal',
|
|
33
|
+
animation: 'default',
|
|
34
|
+
headerShown: true,
|
|
35
|
+
headerBackButtonDisplayMode: 'minimal',
|
|
36
|
+
headerTransparent: Platform.OS === 'ios',
|
|
37
|
+
headerBlurEffect: isGlassEffectAPIAvailable() ? 'none' : themeModeSystem,
|
|
38
|
+
headerTintColor: COLORS.TYPOGRAPHY.BASE,
|
|
39
|
+
headerStyle: {
|
|
40
|
+
backgroundColor: Platform.select({
|
|
41
|
+
ios: COLORS.FILL.TRANSPARENT,
|
|
42
|
+
default: COLORS.FILL.CONTRAST,
|
|
43
|
+
}),
|
|
44
|
+
},
|
|
45
|
+
headerTitleStyle: {
|
|
46
|
+
color: COLORS.TYPOGRAPHY.BASE,
|
|
47
|
+
},
|
|
48
|
+
...options,
|
|
49
|
+
} satisfies StackScreenProps['options']
|
|
50
|
+
},
|
|
51
|
+
[COLORS, themeModeSystem]
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
return { createStackOptions, createScreenOptions }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const useNavigationTabBarOptions = () => {
|
|
58
|
+
const { COLORS } = useThemeMode()
|
|
59
|
+
const { isThemeModeDark } = useThemeSetup()
|
|
60
|
+
|
|
61
|
+
const createTabBarOptions = useCallback(() => {
|
|
62
|
+
return {
|
|
63
|
+
sidebarAdaptable: Platform.OS === 'ios',
|
|
64
|
+
disableTransparentOnScrollEdge: Platform.OS === 'ios',
|
|
65
|
+
labelVisibilityMode: 'labeled',
|
|
66
|
+
blurEffect: (isGlassEffectAPIAvailable() && 'none') || (isThemeModeDark && 'dark') || 'light',
|
|
67
|
+
} satisfies NativeTabsProps
|
|
68
|
+
}, [isThemeModeDark])
|
|
69
|
+
|
|
70
|
+
const createTabOptions = useCallback(() => {
|
|
71
|
+
const triggerProps = {} satisfies NativeTabTriggerProps
|
|
72
|
+
|
|
73
|
+
const triggerIconProps = {
|
|
74
|
+
selectedColor: COLORS.TYPOGRAPHY.PRIMARY,
|
|
75
|
+
} satisfies NativeTabsTriggerIconProps
|
|
76
|
+
|
|
77
|
+
const triggerLabelProps = {
|
|
78
|
+
hidden: Platform.OS !== 'web',
|
|
79
|
+
} satisfies NativeTabsTriggerLabelProps
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
triggerProps,
|
|
83
|
+
triggerIconProps,
|
|
84
|
+
triggerLabelProps,
|
|
85
|
+
}
|
|
86
|
+
}, [COLORS])
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
createTabBarOptions,
|
|
90
|
+
createTabOptions,
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export { useNavigationStackOptions, useNavigationTabBarOptions }
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { GestureHandlerRootView } from 'react-native-gesture-handler'
|
|
2
|
+
import { KeyboardProvider } from 'react-native-keyboard-controller'
|
|
3
|
+
import { initialWindowMetrics, SafeAreaProvider } from 'react-native-safe-area-context'
|
|
4
|
+
import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native'
|
|
5
|
+
import { useThemeSetup } from '../theme'
|
|
6
|
+
import { NavigationLogger } from './components'
|
|
7
|
+
import { NavigationProviderProps } from './navigation.types'
|
|
8
|
+
|
|
9
|
+
const NavigationProvider = ({ children }: NavigationProviderProps) => {
|
|
10
|
+
const { isThemeModeDark } = useThemeSetup()
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<ThemeProvider value={isThemeModeDark ? DarkTheme : DefaultTheme}>
|
|
14
|
+
<GestureHandlerRootView style={{ flex: 1 }}>
|
|
15
|
+
<SafeAreaProvider initialMetrics={initialWindowMetrics} style={{ flex: 1 }}>
|
|
16
|
+
<KeyboardProvider>
|
|
17
|
+
{children}
|
|
18
|
+
<NavigationLogger />
|
|
19
|
+
</KeyboardProvider>
|
|
20
|
+
</SafeAreaProvider>
|
|
21
|
+
</GestureHandlerRootView>
|
|
22
|
+
</ThemeProvider>
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default NavigationProvider
|