@mediacubeco/expo-template-fsd 2.3.0 → 2.4.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 (333) hide show
  1. package/.claude/commands/git-commit.md +164 -0
  2. package/.claude/rules/architecture.md +38 -0
  3. package/.claude/rules/{prettier-eslint-typescript.mdc → prettier-eslint-typescript.md} +5 -10
  4. package/.claude/settings.json +2 -2
  5. package/.claude/skills/api/SKILL.md +362 -0
  6. package/.claude/skills/api/references/client/overview.md +106 -0
  7. package/.claude/skills/api/references/hooks/index.ts +3 -0
  8. package/.claude/skills/api/references/hooks/use-api-health.ts +25 -0
  9. package/.claude/skills/api/references/hooks/use-api-post.ts +46 -0
  10. package/.claude/skills/api/references/hooks/use-api-posts.ts +24 -0
  11. package/.claude/skills/api/references/mock/post.mock.ts +29 -0
  12. package/.claude/skills/{generate-api-model/references → api/references/model}/entities/common.ts +5 -2
  13. package/.claude/skills/api/references/model/resources/common.ts +9 -0
  14. package/.claude/skills/api/references/queries/health.ts +14 -0
  15. package/.claude/skills/api/references/queries/index.ts +2 -0
  16. package/.claude/skills/api/references/queries/post.ts +23 -0
  17. package/.claude/skills/api/references/repository/health/health.ts +11 -0
  18. package/.claude/skills/api/references/repository/health/health.types.ts +25 -0
  19. package/.claude/skills/api/references/repository/health/index.ts +2 -0
  20. package/.claude/skills/api/references/repository/post/index.ts +9 -0
  21. package/.claude/skills/api/references/repository/post/post.ts +34 -0
  22. package/.claude/skills/api/references/repository/post/post.types.ts +61 -0
  23. package/.claude/skills/code-style/SKILL.md +1025 -1
  24. package/.claude/skills/code-style/references/components/payout-speed-option/index.ts +2 -0
  25. package/.claude/skills/code-style/references/components/payout-speed-option/payout-speed-option.styles.ts +76 -0
  26. package/.claude/skills/code-style/references/components/payout-speed-option/payout-speed-option.tsx +58 -0
  27. package/.claude/skills/code-style/references/components/payout-speed-option/payout-speed-option.types.ts +22 -0
  28. package/.claude/skills/code-style/references/components/post/index.ts +2 -0
  29. package/.claude/skills/code-style/references/components/post/post.styles.ts +19 -0
  30. package/.claude/skills/code-style/references/components/post/post.tsx +21 -0
  31. package/.claude/skills/code-style/references/components/post/post.types.ts +9 -0
  32. package/.claude/skills/code-style/references/components/wrap/index.ts +2 -0
  33. package/.claude/skills/code-style/references/components/wrap/wrap.styles.ts +15 -0
  34. package/.claude/skills/code-style/references/components/wrap/wrap.tsx +31 -0
  35. package/.claude/skills/code-style/references/components/wrap/wrap.types.ts +10 -0
  36. package/.claude/skills/code-style/references/hooks/use-posts.ts +37 -0
  37. package/.claude/skills/code-style/references/pages/post/index.ts +4 -0
  38. package/.claude/skills/code-style/references/pages/post/post-details/index.ts +1 -0
  39. package/.claude/skills/code-style/references/pages/post/post-details/post-details.tsx +13 -0
  40. package/.claude/skills/code-style/references/pages/post/post-guide/index.ts +1 -0
  41. package/.claude/skills/code-style/references/pages/post/post-guide/post-guide.tsx +17 -0
  42. package/.claude/skills/code-style/references/pages/post/post-list/index.ts +1 -0
  43. package/.claude/skills/code-style/references/pages/post/post-list/post-list.styles.ts +17 -0
  44. package/.claude/skills/code-style/references/pages/post/post-list/post-list.tsx +25 -0
  45. package/.claude/skills/code-style/references/pages/post/post-share/index.ts +1 -0
  46. package/.claude/skills/code-style/references/pages/post/post-share/post-share.tsx +13 -0
  47. package/.claude/skills/code-style/references/routes/(stack)/_layout.tsx +18 -0
  48. package/.claude/skills/code-style/references/routes/(stack)/details.tsx +8 -0
  49. package/.claude/skills/code-style/references/routes/(stack)/guide.tsx +8 -0
  50. package/.claude/skills/code-style/references/routes/(stack)/index.tsx +8 -0
  51. package/.claude/skills/code-style/references/routes/(stack)/share.tsx +8 -0
  52. package/.claude/skills/code-style/references/routes/(tabs)/(post-details)/index.tsx +8 -0
  53. package/.claude/skills/code-style/references/routes/(tabs)/(post-list)/index.tsx +8 -0
  54. package/.claude/skills/code-style/references/routes/(tabs)/_layout.tsx +50 -0
  55. package/.claude/skills/code-style/references/routes/_app.tsx +20 -0
  56. package/.claude/skills/code-style/references/routes/guide.tsx +8 -0
  57. package/.claude/skills/code-style/references/routes/share.tsx +8 -0
  58. package/.claude/skills/code-style/references/store/use-post-draft-store.ts +62 -0
  59. package/.claude/skills/design-style/SKILL.md +320 -0
  60. package/.env +3 -1
  61. package/.env.example +3 -1
  62. package/CLAUDE.md +5 -11
  63. package/app.json +6 -3
  64. package/axios.d.ts +5 -0
  65. package/env.d.ts +3 -1
  66. package/package.json +33 -40
  67. package/patches/react-native-screens+4.25.2.patch +118 -0
  68. package/react-query.d.ts +13 -0
  69. package/src/app/(root)/(home)/_layout.tsx +2 -2
  70. package/src/app/(root)/(home)/index.tsx +2 -2
  71. package/src/app/(root)/(settings)/_layout.tsx +2 -2
  72. package/src/app/(root)/(settings)/index.tsx +2 -2
  73. package/src/app/(root)/_layout.tsx +35 -32
  74. package/src/pages/home/home-main.tsx +3 -7
  75. package/src/pages/settings/settings-main.tsx +3 -7
  76. package/src/shared/assets/fonts/index.ts +3 -3
  77. package/src/shared/assets/fonts/sf-pro-text-bold.ttf +0 -0
  78. package/src/shared/assets/fonts/sf-pro-text-regular.ttf +0 -0
  79. package/src/shared/assets/fonts/sf-pro-text-semibold.ttf +0 -0
  80. package/src/shared/assets/icons/back.tsx +21 -0
  81. package/src/shared/assets/icons/close.tsx +21 -0
  82. package/src/shared/assets/icons/common.ts +8 -0
  83. package/src/shared/assets/icons/default.tsx +44 -0
  84. package/src/shared/assets/icons/index.ts +13 -1
  85. package/src/shared/assets/icons/refresh.tsx +23 -0
  86. package/src/shared/assets/icons/share.tsx +38 -0
  87. package/src/shared/assets/icons/type.ts +7 -0
  88. package/src/shared/assets/translations/index.ts +4 -4
  89. package/src/shared/constants/config.ts +13 -0
  90. package/src/shared/constants/environment.ts +16 -4
  91. package/src/shared/constants/index.ts +1 -0
  92. package/src/shared/hooks/index.ts +1 -4
  93. package/src/shared/hooks/use-compose.ts +23 -0
  94. package/src/shared/lib/api/api.client.ts +157 -0
  95. package/src/shared/lib/api/api.provider.tsx +14 -36
  96. package/src/shared/lib/api/api.ts +1 -5
  97. package/src/shared/lib/api/api.types.ts +6 -3
  98. package/src/shared/lib/api/hooks/index.ts +1 -0
  99. package/src/shared/lib/api/index.ts +2 -3
  100. package/src/shared/lib/api/models/api-instance/api-instance.client.ts +50 -0
  101. package/src/shared/lib/api/models/api-instance/api-instance.ts +30 -82
  102. package/src/shared/lib/api/models/api-instance/api-instance.types.ts +16 -18
  103. package/src/shared/lib/api/models/api-instance/api-instance.utils.ts +13 -20
  104. package/src/shared/lib/api/models/api-instance/index.ts +3 -0
  105. package/src/shared/lib/api/queries/index.ts +1 -0
  106. package/src/shared/lib/api/repositories/index.ts +1 -1
  107. package/src/shared/lib/localization/constants/index.ts +16 -5
  108. package/src/shared/lib/localization/index.ts +5 -1
  109. package/src/shared/lib/navigation/hooks/index.ts +8 -3
  110. package/src/shared/{hooks/use-breakpoints.ts → lib/navigation/hooks/use-navigation-breakpoints.ts} +2 -5
  111. package/src/shared/lib/navigation/hooks/use-navigation-dimensions.ts +12 -0
  112. package/src/shared/lib/navigation/hooks/use-navigation-options.tsx +175 -0
  113. package/src/shared/lib/navigation/hooks/{use-navigation.ts → use-navigation-route.ts} +2 -2
  114. package/src/shared/lib/navigation/hooks/use-navigation-safe-area.ts +61 -0
  115. package/src/shared/lib/navigation/hooks/use-navigation-tabs-native.ts +10 -0
  116. package/src/shared/lib/navigation/hooks/use-navigation-tabs.tsx +10 -0
  117. package/src/shared/lib/navigation/hooks/use-navigation-transition.ts +27 -0
  118. package/src/shared/lib/navigation/index.ts +11 -4
  119. package/src/shared/lib/navigation/navigation-tabs-native.context.tsx +5 -0
  120. package/src/shared/lib/navigation/navigation-tabs-native.provider.tsx +17 -0
  121. package/src/shared/lib/navigation/navigation-tabs.context.tsx +10 -0
  122. package/src/shared/lib/navigation/navigation-tabs.provider.tsx +22 -0
  123. package/src/shared/lib/navigation/navigation.provider.tsx +6 -1
  124. package/src/shared/lib/storage/index.ts +1 -1
  125. package/src/shared/lib/storage/storage.provider.tsx +4 -0
  126. package/src/shared/lib/storage/storage.ts +29 -25
  127. package/src/shared/lib/theme/constants/animations.ts +26 -0
  128. package/src/shared/lib/theme/constants/colors.ts +336 -43
  129. package/src/shared/lib/theme/constants/delays.ts +1 -1
  130. package/src/shared/lib/theme/constants/durations.ts +1 -1
  131. package/src/shared/lib/theme/constants/fonts.ts +2 -1
  132. package/src/shared/lib/theme/constants/index.ts +42 -20
  133. package/src/shared/lib/theme/constants/shadows.ts +7 -25
  134. package/src/shared/lib/theme/constants/sizes.ts +11 -2
  135. package/src/shared/lib/theme/constants/transparencies.ts +1 -1
  136. package/src/shared/lib/theme/hooks/use-theme-setup.ts +3 -1
  137. package/src/shared/lib/theme/index.ts +10 -1
  138. package/src/shared/lib/theme/theme.provider.tsx +45 -19
  139. package/src/shared/lib/theme/theme.scope.tsx +43 -0
  140. package/src/shared/lib/theme/theme.types.ts +15 -3
  141. package/src/shared/lib/theme/utils/color.ts +81 -0
  142. package/src/shared/lib/theme/utils/index.ts +2 -0
  143. package/src/shared/lib/theme/utils/snapshot.ts +17 -0
  144. package/src/shared/lib/theme/utils/storage.ts +5 -2
  145. package/src/shared/services/browser/browser.ts +66 -0
  146. package/src/shared/services/browser/index.ts +1 -0
  147. package/src/shared/services/cookies/cookies.ts +73 -0
  148. package/src/shared/services/cookies/index.ts +1 -0
  149. package/src/shared/services/index.ts +5 -0
  150. package/src/shared/services/linking/index.ts +1 -0
  151. package/src/shared/services/linking/linking.ts +22 -0
  152. package/src/shared/services/share/index.ts +1 -0
  153. package/src/shared/services/share/share.ts +28 -0
  154. package/src/shared/services/universal-links/index.ts +104 -0
  155. package/src/shared/services/universal-links/universal-links.ts +25 -0
  156. package/src/shared/services/universal-links/universal-links.types.ts +5 -0
  157. package/src/shared/ui/atoms/blur/blur.tsx +9 -4
  158. package/src/shared/ui/atoms/blur/blur.types.ts +1 -1
  159. package/src/shared/ui/atoms/blur-progress/blur-progress.styles.ts +16 -0
  160. package/src/shared/ui/atoms/blur-progress/blur-progress.tsx +30 -0
  161. package/src/shared/ui/atoms/blur-progress/blur-progress.types.tsx +5 -0
  162. package/src/shared/ui/atoms/blur-progress/index.ts +1 -0
  163. package/src/shared/ui/atoms/glass/glass.tsx +13 -5
  164. package/src/shared/ui/atoms/glass/glass.types.ts +1 -1
  165. package/src/shared/ui/atoms/icon/icon.styles.ts +82 -0
  166. package/src/shared/ui/atoms/icon/icon.tsx +21 -0
  167. package/src/shared/ui/atoms/icon/icon.types.ts +23 -0
  168. package/src/shared/ui/atoms/icon/index.ts +2 -0
  169. package/src/shared/ui/atoms/index.ts +7 -0
  170. package/src/shared/ui/atoms/skeleton/index.ts +2 -0
  171. package/src/shared/ui/atoms/skeleton/skeleton.styles.ts +69 -0
  172. package/src/shared/ui/atoms/skeleton/skeleton.tsx +26 -0
  173. package/src/shared/ui/atoms/skeleton/skeleton.types.ts +9 -0
  174. package/src/shared/ui/atoms/spinner/index.ts +2 -0
  175. package/src/shared/ui/atoms/spinner/spinner.styles.ts +102 -0
  176. package/src/shared/ui/atoms/spinner/spinner.tsx +48 -0
  177. package/src/shared/ui/atoms/spinner/spinner.types.ts +25 -0
  178. package/src/shared/ui/atoms/text/index.ts +2 -0
  179. package/src/shared/ui/atoms/text/text.styles.ts +186 -0
  180. package/src/shared/ui/atoms/text/text.tsx +37 -0
  181. package/src/shared/ui/atoms/text/text.types.ts +35 -0
  182. package/src/shared/ui/atoms/touch/components/haptics/haptics.styles.ts +21 -0
  183. package/src/shared/ui/atoms/touch/components/haptics/haptics.tsx +28 -0
  184. package/src/shared/ui/atoms/touch/components/haptics/haptics.types.ts +17 -0
  185. package/src/shared/ui/atoms/touch/components/haptics/index.ts +2 -0
  186. package/src/shared/ui/atoms/touch/components/index.ts +3 -0
  187. package/src/shared/ui/atoms/touch/components/ripple/index.ts +2 -0
  188. package/src/shared/ui/atoms/touch/components/ripple/ripple.styles.ts +78 -0
  189. package/src/shared/ui/atoms/touch/components/ripple/ripple.tsx +29 -0
  190. package/src/shared/ui/atoms/touch/components/ripple/ripple.types.ts +17 -0
  191. package/src/shared/ui/atoms/touch/components/spring/index.ts +2 -0
  192. package/src/shared/ui/atoms/touch/components/spring/spring.styles.ts +31 -0
  193. package/src/shared/ui/atoms/touch/components/spring/spring.tsx +28 -0
  194. package/src/shared/ui/atoms/touch/components/spring/spring.types.ts +17 -0
  195. package/src/shared/ui/atoms/touch/index.ts +2 -0
  196. package/src/shared/ui/atoms/touch/touch.styles.ts +14 -0
  197. package/src/shared/ui/atoms/touch/touch.tsx +137 -0
  198. package/src/shared/ui/atoms/touch/touch.types.ts +27 -0
  199. package/src/shared/ui/atoms/visibility/index.ts +2 -0
  200. package/src/shared/ui/atoms/visibility/visibility.styles.ts +85 -0
  201. package/src/shared/ui/atoms/visibility/visibility.tsx +61 -0
  202. package/src/shared/ui/atoms/visibility/visibility.types.ts +34 -0
  203. package/src/shared/ui/molecules/keyboard-area/keyboard-area.styles.ts +11 -9
  204. package/src/shared/ui/molecules/keyboard-area/keyboard-area.tsx +3 -6
  205. package/src/shared/ui/molecules/refresh-control/refresh-control.tsx +3 -5
  206. package/src/shared/ui/molecules/refresh-control/refresh-control.types.ts +1 -1
  207. package/src/shared/ui/molecules/slider/components/indicator/components/dot/dot.styles.ts +41 -0
  208. package/src/shared/ui/molecules/slider/components/indicator/components/dot/dot.tsx +12 -0
  209. package/src/shared/ui/molecules/slider/components/indicator/components/dot/dot.types.ts +13 -0
  210. package/src/shared/ui/molecules/slider/components/indicator/components/dot/index.ts +2 -0
  211. package/src/shared/ui/molecules/slider/components/indicator/index.ts +2 -0
  212. package/src/shared/ui/molecules/slider/components/indicator/indicator.styles.ts +20 -0
  213. package/src/shared/ui/molecules/slider/components/indicator/indicator.tsx +22 -0
  214. package/src/shared/ui/molecules/slider/components/indicator/indicator.types.ts +10 -0
  215. package/src/shared/ui/molecules/slider/index.ts +3 -0
  216. package/src/shared/ui/molecules/slider/slider.styles.ts +68 -0
  217. package/src/shared/ui/molecules/slider/slider.tsx +64 -0
  218. package/src/shared/ui/molecules/slider/slider.types.ts +28 -0
  219. package/src/shared/ui/organisms/bar/bar.styles.ts +100 -0
  220. package/src/shared/ui/organisms/bar/bar.tsx +89 -0
  221. package/src/shared/ui/organisms/bar/bar.types.ts +49 -0
  222. package/src/shared/ui/organisms/bar/index.ts +2 -0
  223. package/src/shared/ui/organisms/bar-tab/bar-tab.tsx +9 -0
  224. package/src/shared/ui/organisms/bar-tab/bar-tab.types.ts +5 -0
  225. package/src/shared/ui/organisms/bar-tab/components/index.ts +1 -0
  226. package/src/shared/ui/organisms/bar-tab/components/tab/index.ts +2 -0
  227. package/src/shared/ui/organisms/bar-tab/components/tab/tab.styles.ts +21 -0
  228. package/src/shared/ui/organisms/bar-tab/components/tab/tab.tsx +18 -0
  229. package/src/shared/ui/organisms/bar-tab/components/tab/tab.types.ts +5 -0
  230. package/src/shared/ui/organisms/bar-tab/index.ts +2 -0
  231. package/src/shared/ui/organisms/bar-tab-navigation/bar-tab-navigation.styles.ts +22 -0
  232. package/src/shared/ui/organisms/bar-tab-navigation/bar-tab-navigation.tsx +66 -0
  233. package/src/shared/ui/organisms/bar-tab-navigation/bar-tab-navigation.types.ts +5 -0
  234. package/src/shared/ui/organisms/bar-tab-navigation/components/index.ts +1 -0
  235. package/src/shared/ui/organisms/bar-tab-navigation/components/tab-navigation/index.ts +2 -0
  236. package/src/shared/ui/organisms/bar-tab-navigation/components/tab-navigation/tab-navigation.styles.ts +56 -0
  237. package/src/shared/ui/organisms/bar-tab-navigation/components/tab-navigation/tab-navigation.tsx +30 -0
  238. package/src/shared/ui/organisms/bar-tab-navigation/components/tab-navigation/tab-navigation.types.ts +7 -0
  239. package/src/shared/ui/organisms/bar-tab-navigation/index.ts +2 -0
  240. package/src/shared/ui/organisms/index.ts +4 -1
  241. package/src/shared/ui/organisms/slider/components/indicator/components/dot/dot.styles.ts +41 -0
  242. package/src/shared/ui/organisms/slider/components/indicator/components/dot/dot.tsx +12 -0
  243. package/src/shared/ui/organisms/slider/components/indicator/components/dot/dot.types.ts +13 -0
  244. package/src/shared/ui/organisms/slider/components/indicator/components/dot/index.ts +2 -0
  245. package/src/shared/ui/organisms/slider/components/indicator/index.ts +2 -0
  246. package/src/shared/ui/organisms/slider/components/indicator/indicator.styles.ts +20 -0
  247. package/src/shared/ui/organisms/slider/components/indicator/indicator.tsx +22 -0
  248. package/src/shared/ui/organisms/slider/components/indicator/indicator.types.ts +10 -0
  249. package/src/shared/ui/organisms/slider/index.ts +3 -0
  250. package/src/shared/ui/organisms/slider/slider.styles.ts +68 -0
  251. package/src/shared/ui/organisms/slider/slider.tsx +64 -0
  252. package/src/shared/ui/organisms/slider/slider.types.ts +28 -0
  253. package/src/shared/ui/templates/index.ts +3 -0
  254. package/src/shared/ui/templates/modal-container/index.ts +13 -0
  255. package/src/shared/ui/templates/modal-container/modal-container-list.tsx +45 -0
  256. package/src/shared/ui/templates/modal-container/modal-container-scroll.tsx +66 -0
  257. package/src/shared/ui/templates/modal-container/modal-container-section-list.tsx +46 -0
  258. package/src/shared/ui/templates/modal-container/modal-container.hooks.tsx +172 -0
  259. package/src/shared/ui/templates/modal-container/modal-container.styles.ts +121 -0
  260. package/src/shared/ui/templates/modal-container/modal-container.types.ts +69 -0
  261. package/src/shared/ui/templates/screen-container/index.ts +1 -1
  262. package/src/shared/ui/templates/screen-container/screen-container-list.tsx +28 -10
  263. package/src/shared/ui/templates/screen-container/screen-container-scroll.tsx +28 -10
  264. package/src/shared/ui/templates/screen-container/screen-container-section-list.tsx +29 -11
  265. package/src/shared/ui/templates/screen-container/screen-container.hooks.tsx +79 -8
  266. package/src/shared/ui/templates/screen-container/screen-container.styles.ts +105 -15
  267. package/src/shared/ui/templates/screen-container/screen-container.types.tsx +34 -7
  268. package/src/shared/ui/templates/screen-header/components/index.ts +6 -0
  269. package/src/shared/ui/templates/screen-header/components/screen-header-description/index.ts +2 -0
  270. package/src/shared/ui/templates/screen-header/components/screen-header-description/screen-header-description.tsx +15 -0
  271. package/src/shared/ui/templates/screen-header/components/screen-header-description/screen-header-description.types.ts +11 -0
  272. package/src/shared/ui/templates/screen-header/components/screen-header-icon/index.ts +2 -0
  273. package/src/shared/ui/templates/screen-header/components/screen-header-icon/screen-header-icon.styles.ts +44 -0
  274. package/src/shared/ui/templates/screen-header/components/screen-header-icon/screen-header-icon.tsx +65 -0
  275. package/src/shared/ui/templates/screen-header/components/screen-header-icon/screen-header-icon.types.ts +14 -0
  276. package/src/shared/ui/templates/screen-header/components/screen-header-icon-back/index.ts +2 -0
  277. package/src/shared/ui/templates/screen-header/components/screen-header-icon-back/screen-header-icon-back.tsx +17 -0
  278. package/src/shared/ui/templates/screen-header/components/screen-header-icon-back/screen-header-icon-back.types.ts +6 -0
  279. package/src/shared/ui/templates/screen-header/components/screen-header-icon-close/index.ts +2 -0
  280. package/src/shared/ui/templates/screen-header/components/screen-header-icon-close/screen-header-icon-close.tsx +27 -0
  281. package/src/shared/ui/templates/screen-header/components/screen-header-icon-close/screen-header-icon-close.types.ts +10 -0
  282. package/src/shared/ui/templates/screen-header/components/screen-header-info/index.ts +2 -0
  283. package/src/shared/ui/templates/screen-header/components/screen-header-info/screen-header-info.tsx +15 -0
  284. package/src/shared/ui/templates/screen-header/components/screen-header-info/screen-header-info.types.ts +10 -0
  285. package/src/shared/ui/templates/screen-header/components/screen-header-title/index.ts +2 -0
  286. package/src/shared/ui/templates/screen-header/components/screen-header-title/screen-header-title.tsx +19 -0
  287. package/src/shared/ui/templates/screen-header/components/screen-header-title/screen-header-title.types.ts +13 -0
  288. package/src/shared/ui/templates/screen-header/index.ts +3 -0
  289. package/src/shared/ui/templates/screen-header/screen-header.styles.ts +49 -0
  290. package/src/shared/ui/templates/screen-header/screen-header.tsx +26 -0
  291. package/src/shared/ui/templates/screen-header/screen-header.types.ts +15 -0
  292. package/src/shared/ui/templates/web-container/index.ts +2 -0
  293. package/src/shared/ui/templates/web-container/web-container.styles.ts +34 -0
  294. package/src/shared/ui/templates/web-container/web-container.tsx +172 -0
  295. package/src/shared/ui/templates/web-container/web-container.types.ts +21 -0
  296. package/.claude/rules/app-routing.mdc +0 -47
  297. package/.claude/rules/exports-at-end.mdc +0 -55
  298. package/.claude/rules/kebab-case-naming.mdc +0 -29
  299. package/.claude/rules/mediacubeco-packages.mdc +0 -56
  300. package/.claude/skills/generate-api-model/SKILL.md +0 -230
  301. package/.claude/skills/generate-api-model/references/resources/common.ts +0 -8
  302. package/.claude/skills/generate-api-model-mock/SKILL.md +0 -56
  303. package/.claude/skills/git-commit/SKILL.md +0 -139
  304. package/.gitignore +0 -34
  305. package/src/shared/assets/icons/like.tsx +0 -17
  306. package/src/shared/hooks/use-animated.ts +0 -200
  307. package/src/shared/hooks/use-mixins.ts +0 -36
  308. package/src/shared/hooks/use-safe-area.ts +0 -75
  309. package/src/shared/lib/api/repositories/auth/auth.ts +0 -23
  310. package/src/shared/lib/api/repositories/auth/auth.types.ts +0 -22
  311. package/src/shared/lib/api/repositories/auth/index.ts +0 -1
  312. package/src/shared/lib/navigation/hooks/use-navigation-native-tabs.ts +0 -10
  313. package/src/shared/lib/navigation/hooks/use-navigation-options.ts +0 -123
  314. package/src/shared/lib/navigation/navigation-native-tabs.context.tsx +0 -5
  315. package/src/shared/lib/navigation/navigation-native-tabs.provider.tsx +0 -12
  316. package/src/shared/lib/theme/constants/borders.ts +0 -8
  317. package/src/shared/lib/theme/constants/weights.ts +0 -13
  318. /package/.claude/skills/{generate-api-model/references → api/references/model}/entities/index.ts +0 -0
  319. /package/.claude/skills/{generate-api-model/references → api/references/model}/entities/post/index.ts +0 -0
  320. /package/.claude/skills/{generate-api-model/references → api/references/model}/entities/post/post-comment.ts +0 -0
  321. /package/.claude/skills/{generate-api-model/references → api/references/model}/entities/post/post.ts +0 -0
  322. /package/.claude/skills/{generate-api-model/references → api/references/model}/entities/user/index.ts +0 -0
  323. /package/.claude/skills/{generate-api-model/references → api/references/model}/entities/user/user-settings.ts +0 -0
  324. /package/.claude/skills/{generate-api-model/references → api/references/model}/entities/user/user-verification.ts +0 -0
  325. /package/.claude/skills/{generate-api-model/references → api/references/model}/entities/user/user.ts +0 -0
  326. /package/.claude/skills/{generate-api-model/references → api/references/model}/resources/index.ts +0 -0
  327. /package/.claude/skills/{generate-api-model/references → api/references/model}/resources/post/index.ts +0 -0
  328. /package/.claude/skills/{generate-api-model/references → api/references/model}/resources/post/post-comment.ts +0 -0
  329. /package/.claude/skills/{generate-api-model/references → api/references/model}/resources/post/post.ts +0 -0
  330. /package/.claude/skills/{generate-api-model/references → api/references/model}/resources/user/index.ts +0 -0
  331. /package/.claude/skills/{generate-api-model/references → api/references/model}/resources/user/user-settings.ts +0 -0
  332. /package/.claude/skills/{generate-api-model/references → api/references/model}/resources/user/user-verification.ts +0 -0
  333. /package/.claude/skills/{generate-api-model/references → api/references/model}/resources/user/user.ts +0 -0
@@ -1,14 +1,21 @@
1
- import { Platform } from '@mediacubeco/base-react-native'
2
1
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
3
2
  import { Appearance, StatusBar, useColorScheme } from 'react-native'
4
- import { themeStorage } from './utils'
5
- import { hasDarkMode, ThemeMode, ThemeModeStatusBar, ThemeModeSystem, themes } from './constants'
3
+ import { themeSnapshot, themeStorage } from './utils'
4
+ import {
5
+ ThemeAccent,
6
+ ThemeMode,
7
+ ThemeModeStatusBar,
8
+ ThemeModeSystem,
9
+ themesByAccent,
10
+ } from './constants'
6
11
  import { ThemeContext } from './theme.context'
7
12
  import { type ThemeProviderProps } from './theme.types'
8
13
 
14
+ /** Провайдер темы: разрешает режим отображения, применяет акцент и синхронизирует выбор с хранилищем. */
9
15
  const ThemeProvider = ({ children }: ThemeProviderProps) => {
10
16
  const didDefine = useRef(false)
11
- const [themeMode, setThemeMode] = useState(hasDarkMode ? ThemeMode.System : ThemeMode.Light)
17
+ const [themeMode, setThemeMode] = useState(ThemeMode.System)
18
+ const [themeAccent, setThemeAccent] = useState<ThemeAccent>(ThemeAccent.Mint)
12
19
  const colorScheme = useColorScheme()
13
20
 
14
21
  const themeModeSystem =
@@ -21,28 +28,29 @@ const ThemeProvider = ({ children }: ThemeProviderProps) => {
21
28
  }[themeMode]
22
29
 
23
30
  const theme = useMemo(() => {
24
- return themes[currentThemeMode]
25
- }, [currentThemeMode])
31
+ const theme = themesByAccent[themeAccent][currentThemeMode]
32
+
33
+ return theme
34
+ }, [currentThemeMode, themeAccent])
26
35
 
27
36
  const value = useMemo(() => {
28
- return { theme, themeMode, themeModeSystem, setThemeMode }
29
- }, [theme, themeMode, themeModeSystem, setThemeMode])
37
+ return { theme, themeMode, themeModeSystem, themeAccent, setThemeMode, setThemeAccent }
38
+ }, [theme, themeMode, themeModeSystem, themeAccent, setThemeMode, setThemeAccent])
30
39
 
31
40
  const updateThemeModeSystem = useCallback((themeMode: ThemeMode) => {
32
- if (Platform.isIos) {
33
- if (!hasDarkMode) {
34
- Appearance.setColorScheme('light')
35
- } else if (themeMode === ThemeMode.System) {
36
- Appearance.setColorScheme('unspecified')
37
- } else {
38
- Appearance.setColorScheme(themeMode)
39
- }
41
+ if (themeMode === ThemeMode.System) {
42
+ Appearance.setColorScheme('unspecified')
43
+ } else {
44
+ Appearance.setColorScheme(themeMode)
40
45
  }
41
46
  }, [])
42
47
 
43
48
  useEffect(() => {
44
- const defineThemeMode = async () => {
45
- const storageThemeMode = await themeStorage.mode.get()
49
+ const defineTheme = async () => {
50
+ const [storageThemeMode, storageThemeAccent] = await Promise.all([
51
+ themeStorage.mode.get(),
52
+ themeStorage.accent.get(),
53
+ ])
46
54
 
47
55
  if (storageThemeMode) {
48
56
  setThemeMode(storageThemeMode)
@@ -50,10 +58,14 @@ const ThemeProvider = ({ children }: ThemeProviderProps) => {
50
58
  updateThemeModeSystem(storageThemeMode)
51
59
  }
52
60
 
61
+ if (storageThemeAccent && themesByAccent[storageThemeAccent]) {
62
+ setThemeAccent(storageThemeAccent)
63
+ }
64
+
53
65
  didDefine.current = true
54
66
  }
55
67
 
56
- defineThemeMode()
68
+ defineTheme()
57
69
  }, [updateThemeModeSystem])
58
70
 
59
71
  useEffect(() => {
@@ -68,6 +80,20 @@ const ThemeProvider = ({ children }: ThemeProviderProps) => {
68
80
  updateThemeMode()
69
81
  }, [themeMode, updateThemeModeSystem])
70
82
 
83
+ useEffect(() => {
84
+ const updateThemeAccent = async () => {
85
+ if (didDefine.current) {
86
+ await themeStorage.accent.set(themeAccent)
87
+ }
88
+ }
89
+
90
+ updateThemeAccent()
91
+ }, [themeAccent])
92
+
93
+ useEffect(() => {
94
+ themeSnapshot.set(theme)
95
+ }, [theme])
96
+
71
97
  useEffect(() => {
72
98
  StatusBar.setBarStyle(
73
99
  currentThemeMode === ThemeModeSystem.Dark ? ThemeModeStatusBar.Light : ThemeModeStatusBar.Dark
@@ -0,0 +1,43 @@
1
+ import { useMemo } from 'react'
2
+ import { ThemeMode, ThemeModeSystem, themesByAccent } from './constants'
3
+ import { ThemeContext } from './theme.context'
4
+ import { useTheme } from './hooks/use-theme'
5
+ import { type ThemeScopeProps, type ThemeSomeScopeProps } from './theme.types'
6
+
7
+ /** Режим темы приложения, соответствующий режиму области. */
8
+ const THEME_MODE_BY_SCOPE_MODE = {
9
+ [ThemeModeSystem.Light]: ThemeMode.Light,
10
+ [ThemeModeSystem.Dark]: ThemeMode.Dark,
11
+ }
12
+
13
+ /**
14
+ * Локальная область темы: рендерит поддерево в заданном режиме независимо от темы
15
+ * приложения. Для контента, свёрстанного под конкретную схему — например, графики
16
+ * онбординг-слайдов, задизайненной в светлой. Акцент и сеттеры наследуются от
17
+ * родительского провайдера.
18
+ */
19
+ const ThemeScope = ({ mode, children }: ThemeScopeProps) => {
20
+ const context = useTheme()
21
+
22
+ const value = useMemo(() => {
23
+ return {
24
+ ...context,
25
+ theme: themesByAccent[context.themeAccent][mode],
26
+ themeMode: THEME_MODE_BY_SCOPE_MODE[mode],
27
+ themeModeSystem: mode,
28
+ }
29
+ }, [context, mode])
30
+
31
+ return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>
32
+ }
33
+
34
+ const ThemeScopeLight = ({ children }: ThemeSomeScopeProps) => {
35
+ return <ThemeScope mode={ThemeModeSystem.Light}>{children}</ThemeScope>
36
+ }
37
+
38
+ const ThemeScopeDark = ({ children }: ThemeSomeScopeProps) => {
39
+ return <ThemeScope mode={ThemeModeSystem.Dark}>{children}</ThemeScope>
40
+ }
41
+
42
+ export { ThemeScopeLight, ThemeScopeDark }
43
+ export default ThemeScope
@@ -1,15 +1,27 @@
1
1
  import { type Dispatch, type PropsWithChildren, type SetStateAction } from 'react'
2
- import { type ThemeMode, type ThemeModeSystem, type themes } from './constants'
2
+ import { type ThemeAccent, type ThemeMode, type ThemeModeSystem, type themes } from './constants'
3
3
 
4
+ /** Набор токенов активной темы. */
4
5
  type Theme = (typeof themes)[ThemeModeSystem]
5
6
 
6
- interface ThemeContextValue {
7
+ /** Значение контекста темы. */
8
+ type ThemeContextValue = {
7
9
  theme: Theme
8
10
  themeMode: ThemeMode
9
11
  themeModeSystem: ThemeModeSystem
12
+ themeAccent: ThemeAccent
10
13
  setThemeMode: Dispatch<SetStateAction<ThemeMode>>
14
+ setThemeAccent: Dispatch<SetStateAction<ThemeAccent>>
11
15
  }
12
16
 
17
+ /** Пропсы провайдера темы. */
13
18
  type ThemeProviderProps = PropsWithChildren
14
19
 
15
- export type { Theme, ThemeContextValue, ThemeProviderProps }
20
+ /** Пропсы локальной области темы. */
21
+ type ThemeScopeProps = PropsWithChildren<{
22
+ mode: ThemeModeSystem
23
+ }>
24
+
25
+ type ThemeSomeScopeProps = PropsWithChildren
26
+
27
+ export type { Theme, ThemeContextValue, ThemeProviderProps, ThemeScopeProps, ThemeSomeScopeProps }
@@ -0,0 +1,81 @@
1
+ import { clamp } from '@mediacubeco/base/utils'
2
+
3
+ const transparencyCache = new Map<string, string>()
4
+
5
+ const parseColor = (color: string) => {
6
+ const sanitized = color.trim().toLowerCase()
7
+
8
+ if (sanitized === 'transparent') {
9
+ return { red: 0, green: 0, blue: 0 }
10
+ }
11
+
12
+ const isRgb = sanitized.startsWith('rgb(')
13
+ const isRgba = sanitized.startsWith('rgba(')
14
+ const isClosed = sanitized.endsWith(')')
15
+
16
+ if ((!isRgb && !isRgba) || !isClosed) {
17
+ throw new Error(`Unsupported color format: ${color}`)
18
+ }
19
+
20
+ const valuesBody = sanitized.slice(sanitized.indexOf('(') + 1, sanitized.length - 1)
21
+ const valueList = valuesBody.split(',').map((value) => value.trim())
22
+
23
+ if (valueList.length !== 3 && valueList.length !== 4) {
24
+ throw new Error(`Unsupported color format: ${color}`)
25
+ }
26
+
27
+ const redValue = Number(valueList[0])
28
+ const greenValue = Number(valueList[1])
29
+ const blueValue = Number(valueList[2])
30
+
31
+ if (Number.isNaN(redValue) || Number.isNaN(greenValue) || Number.isNaN(blueValue)) {
32
+ throw new Error(`Unsupported color format: ${color}`)
33
+ }
34
+
35
+ const red = clamp(Math.round(redValue), 0, 255)
36
+ const green = clamp(Math.round(greenValue), 0, 255)
37
+ const blue = clamp(Math.round(blueValue), 0, 255)
38
+
39
+ return { red, green, blue }
40
+ }
41
+
42
+ const brightnessCache = new Map<string, string>()
43
+
44
+ /** Затемняет (factor < 1) или осветляет (factor > 1) цвет умножением RGB-каналов. */
45
+ const withBrightness = (color: string, factor: number) => {
46
+ const cacheKey = `${color}|${factor}`
47
+ const cachedColor = brightnessCache.get(cacheKey)
48
+
49
+ if (cachedColor) {
50
+ return cachedColor
51
+ }
52
+
53
+ const { red, green, blue } = parseColor(color)
54
+ const scaledRed = clamp(Math.round(red * factor), 0, 255)
55
+ const scaledGreen = clamp(Math.round(green * factor), 0, 255)
56
+ const scaledBlue = clamp(Math.round(blue * factor), 0, 255)
57
+ const scaledColor = `rgba(${scaledRed}, ${scaledGreen}, ${scaledBlue}, 1)`
58
+
59
+ brightnessCache.set(cacheKey, scaledColor)
60
+
61
+ return scaledColor
62
+ }
63
+
64
+ const withTransparency = (color: string, alpha: number) => {
65
+ const cacheKey = `${color}|${alpha}`
66
+ const cachedColor = transparencyCache.get(cacheKey)
67
+
68
+ if (cachedColor) {
69
+ return cachedColor
70
+ }
71
+
72
+ const { red, green, blue } = parseColor(color)
73
+ const clampedAlpha = clamp(alpha, 0, 1)
74
+ const transparentColor = `rgba(${red}, ${green}, ${blue}, ${clampedAlpha})`
75
+
76
+ transparencyCache.set(cacheKey, transparentColor)
77
+
78
+ return transparentColor
79
+ }
80
+
81
+ export { withBrightness, withTransparency }
@@ -1 +1,3 @@
1
+ export { withBrightness, withTransparency } from './color'
2
+ export { themeSnapshot } from './snapshot'
1
3
  export { themeStorage } from './storage'
@@ -0,0 +1,17 @@
1
+ import { ThemeModeSystem, themes } from '../constants'
2
+ import { type Theme } from '../theme.types'
3
+
4
+ /** Актуальная тема приложения; до инициализации провайдера — светлая. */
5
+ let currentTheme: Theme = themes[ThemeModeSystem.Light]
6
+
7
+ /** Снапшот текущей темы для кода вне React-дерева (сервисы, хелперы); обновляется из ThemeProvider. */
8
+ const themeSnapshot = {
9
+ get() {
10
+ return currentTheme
11
+ },
12
+ set: (theme: Theme) => {
13
+ currentTheme = theme
14
+ },
15
+ }
16
+
17
+ export { themeSnapshot }
@@ -1,7 +1,10 @@
1
1
  import { Storage } from '@mediacubeco/base/services'
2
2
  import { storageModel } from '../../storage'
3
- import { type ThemeMode } from '../constants'
3
+ import { type ThemeAccent, type ThemeMode } from '../constants'
4
4
 
5
- const themeStorage = new Storage(storageModel).create<'mode', ThemeMode>('mode')
5
+ /** Хранилище настроек темы: режим и акцентный цвет. */
6
+ const themeStorage = new Storage(storageModel)
7
+ .create<'mode', ThemeMode>('mode')
8
+ .create<'accent', ThemeAccent>('accent')
6
9
 
7
10
  export { themeStorage }
@@ -0,0 +1,66 @@
1
+ import { ErrorHandler, Log } from '@mediacubeco/base/helpers'
2
+ import * as WebBrowser from 'expo-web-browser'
3
+ import { themeSnapshot } from '../../lib'
4
+
5
+ /** Работа с системным in-app браузером. */
6
+ class Browser {
7
+ /** Завершает подвисшую auth-сессию браузера после возврата в приложение. */
8
+ static async init() {
9
+ try {
10
+ WebBrowser.maybeCompleteAuthSession()
11
+
12
+ Log.formatted.success('browser', 'init success')
13
+ } catch (error) {
14
+ ErrorHandler.capture(error, {
15
+ signal: 'browser',
16
+ message: 'init error',
17
+ })
18
+ }
19
+ }
20
+
21
+ /** Открывает URL в in-app браузере (iOS — page sheet, Android — Custom Tabs) в цветах текущей темы. */
22
+ static async open(url: string) {
23
+ try {
24
+ const { COLORS } = themeSnapshot.get()
25
+
26
+ await WebBrowser.openBrowserAsync(url, {
27
+ showTitle: true,
28
+ createTask: true,
29
+ readerMode: false,
30
+ showInRecents: true,
31
+ useProxyActivity: true,
32
+ enableBarCollapsing: true,
33
+ enableDefaultShareMenuItem: true,
34
+ dismissButtonStyle: 'close',
35
+ toolbarColor: COLORS.BACKGROUND.BASE,
36
+ secondaryToolbarColor: COLORS.BACKGROUND.SECONDARY,
37
+ controlsColor: COLORS.TYPOGRAPHY.PRIMARY,
38
+ presentationStyle: WebBrowser.WebBrowserPresentationStyle.PAGE_SHEET,
39
+ })
40
+
41
+ Log.formatted.success('browser', 'open success', { url })
42
+ } catch (error) {
43
+ ErrorHandler.capture(error, {
44
+ signal: 'browser',
45
+ message: 'open error',
46
+ payload: { url },
47
+ })
48
+ }
49
+ }
50
+
51
+ /** Закрывает открытый in-app браузер. */
52
+ static async close() {
53
+ try {
54
+ await WebBrowser.dismissBrowser()
55
+
56
+ Log.formatted.success('browser', 'close success')
57
+ } catch (error) {
58
+ ErrorHandler.capture(error, {
59
+ signal: 'browser',
60
+ message: 'close error',
61
+ })
62
+ }
63
+ }
64
+ }
65
+
66
+ export default Browser
@@ -0,0 +1 @@
1
+ export { default as Browser } from './browser'
@@ -0,0 +1,73 @@
1
+ import { ErrorHandler, Log } from '@mediacubeco/base/helpers'
2
+ import NitroCookies, { type Cookie } from 'react-native-nitro-cookies'
3
+ import { APP_URL } from '../../constants'
4
+
5
+ /** Управление cookie в нативном хранилище, общем с WebView. */
6
+ class Cookies {
7
+ /** Базовый URL веб-приложения, для которого управляются cookie. */
8
+ private static url = ''
9
+
10
+ /** Запоминает базовый URL веб-приложения из окружения. */
11
+ static async init() {
12
+ Cookies.url = APP_URL
13
+
14
+ Log.formatted.success('cookies', 'init success', { url: Cookies.url })
15
+ }
16
+
17
+ /** Записывает cookie; `useWebKit` пишет в хранилище WKWebView (iOS). */
18
+ static async set(cookie: Cookie, useWebKit?: boolean) {
19
+ try {
20
+ if (!Cookies.url) {
21
+ ErrorHandler.throw('Cookies not initialized')
22
+ }
23
+
24
+ await NitroCookies.set(Cookies.url, cookie, useWebKit)
25
+
26
+ Log.formatted.success('cookies', 'set success', { name: cookie.name, useWebKit })
27
+ } catch (error) {
28
+ ErrorHandler.forward(error, {
29
+ signal: 'cookies',
30
+ message: 'set error',
31
+ payload: { name: cookie.name, useWebKit },
32
+ })
33
+ }
34
+ }
35
+
36
+ /** Возвращает cookie, доступные для базового URL. */
37
+ static async get(useWebKit?: boolean) {
38
+ try {
39
+ if (!Cookies.url) {
40
+ ErrorHandler.throw('Cookies not initialized')
41
+ }
42
+
43
+ const cookies = await NitroCookies.get(Cookies.url, useWebKit)
44
+
45
+ Log.formatted.success('cookies', 'get success', { useWebKit })
46
+
47
+ return cookies
48
+ } catch (error) {
49
+ ErrorHandler.forward(error, {
50
+ signal: 'cookies',
51
+ message: 'get error',
52
+ payload: { useWebKit },
53
+ })
54
+ }
55
+ }
56
+
57
+ /** Полностью очищает cookie-хранилище (например, при логауте). */
58
+ static async clear(useWebKit?: boolean) {
59
+ try {
60
+ await NitroCookies.clearAll(useWebKit)
61
+
62
+ Log.formatted.success('cookies', 'clear success', { useWebKit })
63
+ } catch (error) {
64
+ ErrorHandler.forward(error, {
65
+ signal: 'cookies',
66
+ message: 'clear error',
67
+ payload: { useWebKit },
68
+ })
69
+ }
70
+ }
71
+ }
72
+
73
+ export default Cookies
@@ -0,0 +1 @@
1
+ export { default as Cookies } from './cookies'
@@ -0,0 +1,5 @@
1
+ export * from './browser'
2
+ export * from './cookies'
3
+ export * from './linking'
4
+ export * from './share'
5
+ export * from './universal-links'
@@ -0,0 +1 @@
1
+ export { default as Linking } from './linking'
@@ -0,0 +1,22 @@
1
+ import { ErrorHandler, Log } from '@mediacubeco/base/helpers'
2
+ import { openURL } from 'expo-linking'
3
+
4
+ /** Открытие внешних ссылок в соответствующем системном приложении. */
5
+ class Linking {
6
+ /** Открывает URL вне приложения (системный браузер, App Store, Google Play и т. п.). */
7
+ static async open(url: string) {
8
+ try {
9
+ await openURL(url)
10
+
11
+ Log.formatted.success('linking', 'open success', { url })
12
+ } catch (error) {
13
+ ErrorHandler.capture(error, {
14
+ signal: 'linking',
15
+ message: 'open error',
16
+ payload: { url },
17
+ })
18
+ }
19
+ }
20
+ }
21
+
22
+ export default Linking
@@ -0,0 +1 @@
1
+ export { default as Share } from './share'
@@ -0,0 +1,28 @@
1
+ import { Platform } from '@mediacubeco/base-react-native/helpers'
2
+ import { Share as ShareNative } from 'react-native'
3
+ import * as Sharing from 'expo-sharing'
4
+ import { File, Paths } from 'expo-file-system'
5
+
6
+ /** Шаринг файлов и текста через системный share sheet. */
7
+ class Share {
8
+ /** Отдаёт base64-содержимое в share sheet, предварительно записав его во временный файл кэша. */
9
+ static async openBase64(base64: string, fileName: string) {
10
+ const file = new File(Paths.cache, fileName)
11
+
12
+ file.write(base64, { encoding: 'base64' })
13
+
14
+ await Sharing.shareAsync(file.uri)
15
+ }
16
+
17
+ /** Отдаёт текстовое сообщение в share sheet. */
18
+ static async openMessage(message: string) {
19
+ await ShareNative.share({ message })
20
+ }
21
+
22
+ /** Отдаёт ссылку в share sheet: на iOS нативным полем `url`, на остальных платформах текстом. */
23
+ static async openUrl(url: string) {
24
+ await ShareNative.share(Platform.isIos ? { url } : { message: url })
25
+ }
26
+ }
27
+
28
+ export default Share
@@ -0,0 +1,104 @@
1
+ export { default as UniversalLinks } from './universal-links'
2
+ export type { UniversalLinksHandler } from './universal-links.types'
3
+
4
+ /**
5
+ * Backend hosting (required to verify domain ownership):
6
+ *
7
+ * ──────────────────────────────────────────────────────────────────────────
8
+ * iOS — Universal Links
9
+ * Host at: https://garna.io/.well-known/apple-app-site-association
10
+ * - no file extension (NOT .json)
11
+ * - Content-Type: application/json
12
+ * - HTTPS, no redirects
13
+ * - replace <APPLE_TEAM_ID> with the value from EXPO_PUBLIC_APPLE_TEAM_ID
14
+ *
15
+ * {
16
+ * "applinks": {
17
+ * "apps": [],
18
+ * "details": [
19
+ * {
20
+ * "appID": "<APPLE_TEAM_ID>.com.mediacube.application",
21
+ * "paths": ["/auth/*", "/invite/*", "/app/*"]
22
+ * }
23
+ * ]
24
+ * }
25
+ * }
26
+ *
27
+ * ──────────────────────────────────────────────────────────────────────────
28
+ * Android — App Links
29
+ * Host at: https://garna.io/.well-known/assetlinks.json
30
+ * - Content-Type: application/json
31
+ * - HTTPS, no redirects
32
+ * - replace <SHA256_CERT_FINGERPRINT> with the signing fingerprint(s) from
33
+ * `eas credentials` and Play Console → App integrity (App signing key)
34
+ *
35
+ * [
36
+ * {
37
+ * "relation": ["delegate_permission/common.handle_all_urls"],
38
+ * "target": {
39
+ * "namespace": "android_app",
40
+ * "package_name": "com.mediacube.application",
41
+ * "sha256_cert_fingerprints": ["<SHA256_CERT_FINGERPRINT>"]
42
+ * }
43
+ * }
44
+ * ]
45
+ * ──────────────────────────────────────────────────────────────────────────
46
+ *
47
+ * `getAppleAssociation()` / `getAndroidAssociation()` below build these exact
48
+ * payloads at runtime once the env value and fingerprints are filled in.
49
+ *
50
+ * ══════════════════════════════════════════════════════════════════════════
51
+ * Ignoring domains and paths
52
+ * ══════════════════════════════════════════════════════════════════════════
53
+ *
54
+ * The app only claims what is explicitly declared — anything else opens in the
55
+ * browser. Rules differ per platform and per scope (domain vs path).
56
+ *
57
+ * ── Ignore a whole domain / subdomain ─────────────────────────────────────
58
+ * - iOS: omit it from app.json `ios.associatedDomains`. Avoid the wildcard
59
+ * `applinks:*.garna.io` if some subdomains must stay in the browser; list
60
+ * explicit hosts instead, e.g. ["applinks:app.garna.io", "applinks:garna.io"].
61
+ * - Android: omit its `host` from app.json `android.intentFilters[].data`.
62
+ *
63
+ * ── Ignore paths on iOS (edit AASA, app.json untouched) ───────────────────
64
+ * First match wins, so put excludes first.
65
+ *
66
+ * Modern format (iOS 13+, recommended) — `exclude: true`:
67
+ * {
68
+ * "applinks": {
69
+ * "details": [
70
+ * {
71
+ * "appIDs": ["<APPLE_TEAM_ID>.com.mediacube.application"],
72
+ * "components": [
73
+ * { "/": "/help/*", "exclude": true, "comment": "open in Safari" },
74
+ * { "/": "/blog/*", "exclude": true },
75
+ * { "/": "/auth/*" },
76
+ * { "/": "/invite/*" },
77
+ * { "/": "/app/*" }
78
+ * ]
79
+ * }
80
+ * ]
81
+ * }
82
+ * }
83
+ *
84
+ * Legacy format — `NOT ` prefix:
85
+ * "paths": ["NOT /help/*", "NOT /blog/*", "/auth/*", "/invite/*", "/app/*"]
86
+ *
87
+ * ── Ignore paths on Android (edit app.json, assetlinks untouched) ─────────
88
+ * No "exclude" exists — whitelist allowed paths via pathPrefix / pathPattern.
89
+ * Everything outside the listed prefixes opens in the browser:
90
+ *
91
+ * "intentFilters": [
92
+ * {
93
+ * "action": "VIEW",
94
+ * "autoVerify": true,
95
+ * "category": ["BROWSABLE", "DEFAULT"],
96
+ * "data": [
97
+ * { "scheme": "https", "host": "garna.io", "pathPrefix": "/auth" },
98
+ * { "scheme": "https", "host": "garna.io", "pathPrefix": "/invite" },
99
+ * { "scheme": "https", "host": "garna.io", "pathPrefix": "/app" }
100
+ * ]
101
+ * }
102
+ * ]
103
+ * ──────────────────────────────────────────────────────────────────────────
104
+ */
@@ -0,0 +1,25 @@
1
+ import { type EmitterSubscription } from 'react-native'
2
+ import * as Linking from 'expo-linking'
3
+ import { type UniversalLinksHandler } from './universal-links.types'
4
+
5
+ class UniversalLinks {
6
+ static subscription: EmitterSubscription
7
+
8
+ static get() {
9
+ return Linking.getInitialURL()
10
+ }
11
+
12
+ static parse(url: string) {
13
+ return Linking.parse(url)
14
+ }
15
+
16
+ static subscribe(handler: UniversalLinksHandler) {
17
+ UniversalLinks.subscription = Linking.addEventListener('url', handler)
18
+ }
19
+
20
+ static unsubscribe() {
21
+ UniversalLinks.subscription.remove()
22
+ }
23
+ }
24
+
25
+ export default UniversalLinks
@@ -0,0 +1,5 @@
1
+ import { type URLListener } from 'expo-linking'
2
+
3
+ type UniversalLinksHandler = URLListener
4
+
5
+ export type { UniversalLinksHandler }