@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,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,8 @@
1
+ type BaseResource = {
2
+ id?: string
3
+ createdAt?: Date
4
+ updatedAt?: Date
5
+ isPending?: boolean
6
+ }
7
+
8
+ export type { BaseResource }
@@ -0,0 +1 @@
1
+ export * from './common'
@@ -0,0 +1 @@
1
+ export * from './storage'
@@ -0,0 +1,9 @@
1
+ import * as SecureStore from 'expo-secure-store'
2
+
3
+ const apiAsyncStorage = {
4
+ getItem: SecureStore.getItemAsync,
5
+ setItem: SecureStore.setItemAsync,
6
+ removeItem: SecureStore.deleteItemAsync,
7
+ }
8
+
9
+ export { apiAsyncStorage }
@@ -0,0 +1,4 @@
1
+ export * from './api'
2
+ export * from './localization'
3
+ export * from './navigation'
4
+ export * from './theme'
@@ -0,0 +1 @@
1
+ export * from './localization-translate'
@@ -0,0 +1 @@
1
+ export { default as LocalizationTranslate } from './localization-translate'
@@ -0,0 +1,5 @@
1
+ import { Trans } from 'react-i18next'
2
+
3
+ const LocalizationTranslate = Trans
4
+
5
+ export default LocalizationTranslate
@@ -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,2 @@
1
+ export * from './use-localization-setup'
2
+ export * from './use-localization-text'
@@ -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,9 @@
1
+ import { useLocalization } from './use-localization'
2
+
3
+ const useLocalizationText = () => {
4
+ const { t } = useLocalization()
5
+
6
+ return t
7
+ }
8
+
9
+ export { useLocalizationText }
@@ -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,4 @@
1
+ export { LocalizationTranslate } from './components'
2
+ export { useLocalizationSetup, useLocalizationText } from './hooks'
3
+ export { LocalizationCode } from './constants'
4
+ export { default as LocalizationProvider } from './localization.provider'
@@ -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,5 @@
1
+ import { PropsWithChildren } from 'react'
2
+
3
+ type LocalizationProviderProps = PropsWithChildren
4
+
5
+ export type { LocalizationProviderProps }
@@ -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
@@ -0,0 +1,3 @@
1
+ export * from './use-navigation'
2
+ export * from './use-navigation-params'
3
+ export * from './use-navigation-options'
@@ -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,9 @@
1
+ import { useLocalSearchParams } from 'expo-router'
2
+
3
+ const useNavigationParams = <Params extends object>() => {
4
+ const params = useLocalSearchParams<{ data: string }>()
5
+
6
+ return JSON.parse(params.data) as Params
7
+ }
8
+
9
+ export { useNavigationParams }
@@ -0,0 +1,9 @@
1
+ import { useRouter } from 'expo-router'
2
+
3
+ const useNavigation = () => {
4
+ const navigation = useRouter()
5
+
6
+ return navigation
7
+ }
8
+
9
+ export { useNavigation }
@@ -0,0 +1,7 @@
1
+ export {
2
+ useNavigation,
3
+ useNavigationParams,
4
+ useNavigationStackOptions,
5
+ useNavigationTabBarOptions,
6
+ } from './hooks'
7
+ export { default as NavigationProvider } from './navigation.provider'
@@ -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
@@ -0,0 +1,5 @@
1
+ import { PropsWithChildren } from 'react'
2
+
3
+ type NavigationProviderProps = PropsWithChildren
4
+
5
+ export type { NavigationProviderProps }
@@ -0,0 +1,8 @@
1
+ const BORDERS = {
2
+ EMPTY: 0,
3
+ THIN: 0.5,
4
+ BASE: 1,
5
+ FAT: 2,
6
+ }
7
+
8
+ export { BORDERS }