create-du-app 0.1.3 → 0.1.4

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 (414) hide show
  1. package/README.md +10 -7
  2. package/package.json +6 -5
  3. package/src/index.js +8 -8
  4. package/src/prompts.js +1 -1
  5. package/templates/mobile/expo/.env.example +5 -0
  6. package/templates/mobile/expo/.eslintrc.js +7 -0
  7. package/templates/mobile/expo/.prettierrc.js +7 -0
  8. package/templates/mobile/expo/.svgrrc.js +9 -0
  9. package/templates/mobile/expo/README.md +42 -7
  10. package/templates/mobile/expo/_gitignore +20 -0
  11. package/templates/mobile/expo/_package.json +62 -1
  12. package/templates/mobile/expo/app.json +18 -0
  13. package/templates/mobile/expo/babel.config.js +21 -0
  14. package/templates/mobile/expo/index.js +5 -0
  15. package/templates/mobile/expo/metro.config.js +31 -0
  16. package/templates/mobile/expo/src/app/App.tsx +24 -0
  17. package/templates/mobile/expo/src/app/app-provider.tsx +36 -0
  18. package/templates/mobile/expo/src/app/config/translation.ts +26 -0
  19. package/templates/mobile/expo/src/app/index.ts +1 -0
  20. package/templates/mobile/expo/src/app/navigation/app-route-type.ts +27 -0
  21. package/templates/mobile/expo/src/app/navigation/bottom-tabs.tsx +34 -0
  22. package/templates/mobile/expo/src/app/navigation/index.tsx +14 -0
  23. package/templates/mobile/expo/src/app/stores/auth.store.ts +33 -0
  24. package/templates/mobile/expo/src/app/stores/common.store.ts +19 -0
  25. package/templates/mobile/expo/src/app/stores/index.ts +3 -0
  26. package/templates/mobile/expo/src/app/stores/loading.store.ts +22 -0
  27. package/templates/mobile/expo/src/assets/Images/empty-list.png +0 -0
  28. package/templates/mobile/expo/src/assets/Images/index.ts +5 -0
  29. package/templates/mobile/expo/src/assets/Images/screen-bg-gradian.png +0 -0
  30. package/templates/mobile/expo/src/assets/i18n/en.json +12 -0
  31. package/templates/mobile/expo/src/assets/i18n/fr.json +12 -0
  32. package/templates/mobile/expo/src/assets/lotties/index.ts +3 -0
  33. package/templates/mobile/expo/src/assets/lotties/loading.json +1 -0
  34. package/templates/mobile/expo/src/assets/svgs/arrow-left.svg +3 -0
  35. package/templates/mobile/expo/src/assets/svgs/arrow-right.svg +3 -0
  36. package/templates/mobile/expo/src/assets/svgs/calendar.svg +12 -0
  37. package/templates/mobile/expo/src/assets/svgs/check.svg +3 -0
  38. package/templates/mobile/expo/src/assets/svgs/close.svg +3 -0
  39. package/templates/mobile/expo/src/assets/svgs/eye-hide.svg +3 -0
  40. package/templates/mobile/expo/src/assets/svgs/eye.svg +4 -0
  41. package/templates/mobile/expo/src/assets/svgs/index.ts +29 -0
  42. package/templates/mobile/expo/src/assets/svgs/minus.svg +3 -0
  43. package/templates/mobile/expo/src/assets/svgs/plus.svg +3 -0
  44. package/templates/mobile/expo/src/assets/svgs/search.svg +3 -0
  45. package/templates/mobile/expo/src/assets/svgs/toast-error.svg +5 -0
  46. package/templates/mobile/expo/src/assets/svgs/toast-success.svg +4 -0
  47. package/templates/mobile/expo/src/core/api/endpoints.ts +8 -0
  48. package/templates/mobile/expo/src/core/api/example.api.ts +53 -0
  49. package/templates/mobile/expo/src/core/api/index.ts +4 -0
  50. package/templates/mobile/expo/src/core/components/common/index.ts +1 -0
  51. package/templates/mobile/expo/src/core/components/common/list-empty/index.ts +2 -0
  52. package/templates/mobile/expo/src/core/components/common/list-empty/list-empty.tsx +30 -0
  53. package/templates/mobile/expo/src/core/components/common/list-empty/list-empty.type.ts +5 -0
  54. package/templates/mobile/expo/src/core/components/forms/hf-date-time.tsx +301 -0
  55. package/templates/mobile/expo/src/core/components/forms/hf-password-input.tsx +153 -0
  56. package/templates/mobile/expo/src/core/components/forms/hf-text-input.tsx +59 -0
  57. package/templates/mobile/expo/src/core/components/forms/hf-time-picker.tsx +117 -0
  58. package/templates/mobile/expo/src/core/components/forms/index.ts +4 -0
  59. package/templates/mobile/expo/src/core/components/index.ts +5 -0
  60. package/templates/mobile/expo/src/core/components/loading/index.ts +1 -0
  61. package/templates/mobile/expo/src/core/components/loading/loading-app/index.ts +1 -0
  62. package/templates/mobile/expo/src/core/components/loading/loading-app/loading-app.tsx +50 -0
  63. package/templates/mobile/expo/src/core/components/offline/index.ts +1 -0
  64. package/templates/mobile/expo/src/core/components/offline/offline-banner.tsx +186 -0
  65. package/templates/mobile/expo/src/core/components/screen/index.ts +1 -0
  66. package/templates/mobile/expo/src/core/components/screen/screen-container/index.ts +1 -0
  67. package/templates/mobile/expo/src/core/components/screen/screen-container/screen-container.tsx +252 -0
  68. package/templates/mobile/expo/src/core/components/splash/index.ts +1 -0
  69. package/templates/mobile/expo/src/core/components/splash/splash-overlay/index.ts +1 -0
  70. package/templates/mobile/expo/src/core/components/splash/splash-overlay/splash-overlay.tsx +93 -0
  71. package/templates/mobile/expo/src/core/components/ui/animated-list-item/animated-list-item.tsx +48 -0
  72. package/templates/mobile/expo/src/core/components/ui/animated-list-item/animated-list-item.type.ts +10 -0
  73. package/templates/mobile/expo/src/core/components/ui/animated-list-item/index.ts +2 -0
  74. package/templates/mobile/expo/src/core/components/ui/app-image/app-image.tsx +104 -0
  75. package/templates/mobile/expo/src/core/components/ui/app-image/app-image.type.ts +19 -0
  76. package/templates/mobile/expo/src/core/components/ui/app-image/index.ts +2 -0
  77. package/templates/mobile/expo/src/core/components/ui/asset-placeholder/asset-placeholder.tsx +76 -0
  78. package/templates/mobile/expo/src/core/components/ui/asset-placeholder/index.ts +1 -0
  79. package/templates/mobile/expo/src/core/components/ui/avatar-image/avatar-image.tsx +90 -0
  80. package/templates/mobile/expo/src/core/components/ui/avatar-image/index.ts +1 -0
  81. package/templates/mobile/expo/src/core/components/ui/bottom-sheet/bottom-sheet.tsx +145 -0
  82. package/templates/mobile/expo/src/core/components/ui/bottom-sheet/bottom-sheet.type.ts +10 -0
  83. package/templates/mobile/expo/src/core/components/ui/bottom-sheet/index.ts +2 -0
  84. package/templates/mobile/expo/src/core/components/ui/button/button.style.ts +146 -0
  85. package/templates/mobile/expo/src/core/components/ui/button/button.tsx +97 -0
  86. package/templates/mobile/expo/src/core/components/ui/button/button.type.ts +47 -0
  87. package/templates/mobile/expo/src/core/components/ui/button/index.ts +4 -0
  88. package/templates/mobile/expo/src/core/components/ui/checkbox/checkbox.tsx +127 -0
  89. package/templates/mobile/expo/src/core/components/ui/checkbox/checkbox.type.ts +24 -0
  90. package/templates/mobile/expo/src/core/components/ui/checkbox/index.ts +2 -0
  91. package/templates/mobile/expo/src/core/components/ui/collapsible-section/collapsible-section.tsx +141 -0
  92. package/templates/mobile/expo/src/core/components/ui/collapsible-section/collapsible-section.type.ts +10 -0
  93. package/templates/mobile/expo/src/core/components/ui/collapsible-section/index.ts +2 -0
  94. package/templates/mobile/expo/src/core/components/ui/components.registry.json +313 -0
  95. package/templates/mobile/expo/src/core/components/ui/field/field-frame.tsx +62 -0
  96. package/templates/mobile/expo/src/core/components/ui/field/field.shared.ts +31 -0
  97. package/templates/mobile/expo/src/core/components/ui/header/header.tsx +196 -0
  98. package/templates/mobile/expo/src/core/components/ui/header/header.type.ts +30 -0
  99. package/templates/mobile/expo/src/core/components/ui/header/index.ts +2 -0
  100. package/templates/mobile/expo/src/core/components/ui/icon-button/icon-button.style.ts +23 -0
  101. package/templates/mobile/expo/src/core/components/ui/icon-button/icon-button.tsx +66 -0
  102. package/templates/mobile/expo/src/core/components/ui/icon-button/icon-button.type.ts +25 -0
  103. package/templates/mobile/expo/src/core/components/ui/icon-button/index.ts +2 -0
  104. package/templates/mobile/expo/src/core/components/ui/image-slider/image-slider.tsx +107 -0
  105. package/templates/mobile/expo/src/core/components/ui/image-slider/image-slider.type.ts +10 -0
  106. package/templates/mobile/expo/src/core/components/ui/image-slider/index.ts +2 -0
  107. package/templates/mobile/expo/src/core/components/ui/index.ts +25 -0
  108. package/templates/mobile/expo/src/core/components/ui/label/index.ts +2 -0
  109. package/templates/mobile/expo/src/core/components/ui/label/label.tsx +36 -0
  110. package/templates/mobile/expo/src/core/components/ui/label/label.type.ts +12 -0
  111. package/templates/mobile/expo/src/core/components/ui/modal/index.ts +2 -0
  112. package/templates/mobile/expo/src/core/components/ui/modal/modal.tsx +62 -0
  113. package/templates/mobile/expo/src/core/components/ui/modal/modal.type.ts +11 -0
  114. package/templates/mobile/expo/src/core/components/ui/otp-input/index.ts +2 -0
  115. package/templates/mobile/expo/src/core/components/ui/otp-input/otp-input.tsx +129 -0
  116. package/templates/mobile/expo/src/core/components/ui/otp-input/otp-input.type.ts +20 -0
  117. package/templates/mobile/expo/src/core/components/ui/page-dots/index.ts +1 -0
  118. package/templates/mobile/expo/src/core/components/ui/page-dots/page-dots.tsx +60 -0
  119. package/templates/mobile/expo/src/core/components/ui/radio/index.ts +2 -0
  120. package/templates/mobile/expo/src/core/components/ui/radio/radio.tsx +121 -0
  121. package/templates/mobile/expo/src/core/components/ui/radio/radio.type.ts +20 -0
  122. package/templates/mobile/expo/src/core/components/ui/screen/index.ts +1 -0
  123. package/templates/mobile/expo/src/core/components/ui/screen/screen-gradient.tsx +33 -0
  124. package/templates/mobile/expo/src/core/components/ui/search-box/index.ts +2 -0
  125. package/templates/mobile/expo/src/core/components/ui/search-box/search-box.tsx +162 -0
  126. package/templates/mobile/expo/src/core/components/ui/search-box/search-box.type.ts +26 -0
  127. package/templates/mobile/expo/src/core/components/ui/segmented-control/index.ts +2 -0
  128. package/templates/mobile/expo/src/core/components/ui/segmented-control/segmented-control.tsx +86 -0
  129. package/templates/mobile/expo/src/core/components/ui/segmented-control/segmented-control.type.ts +22 -0
  130. package/templates/mobile/expo/src/core/components/ui/skeleton/index.ts +2 -0
  131. package/templates/mobile/expo/src/core/components/ui/skeleton/skeleton.tsx +106 -0
  132. package/templates/mobile/expo/src/core/components/ui/skeleton/skeleton.type.ts +8 -0
  133. package/templates/mobile/expo/src/core/components/ui/success-state/index.ts +1 -0
  134. package/templates/mobile/expo/src/core/components/ui/success-state/success-state.tsx +68 -0
  135. package/templates/mobile/expo/src/core/components/ui/tabs/index.ts +2 -0
  136. package/templates/mobile/expo/src/core/components/ui/tabs/tabs.tsx +273 -0
  137. package/templates/mobile/expo/src/core/components/ui/tabs/tabs.type.ts +21 -0
  138. package/templates/mobile/expo/src/core/components/ui/tag-input/index.ts +2 -0
  139. package/templates/mobile/expo/src/core/components/ui/tag-input/tag-input.tsx +146 -0
  140. package/templates/mobile/expo/src/core/components/ui/tag-input/tag-input.type.ts +22 -0
  141. package/templates/mobile/expo/src/core/components/ui/text-area/index.ts +2 -0
  142. package/templates/mobile/expo/src/core/components/ui/text-area/text-area.tsx +90 -0
  143. package/templates/mobile/expo/src/core/components/ui/text-area/text-area.type.ts +20 -0
  144. package/templates/mobile/expo/src/core/components/ui/text-field/index.ts +2 -0
  145. package/templates/mobile/expo/src/core/components/ui/text-field/text-field.tsx +116 -0
  146. package/templates/mobile/expo/src/core/components/ui/text-field/text-field.type.ts +21 -0
  147. package/templates/mobile/expo/src/core/components/ui/toggle/index.ts +2 -0
  148. package/templates/mobile/expo/src/core/components/ui/toggle/toggle.tsx +110 -0
  149. package/templates/mobile/expo/src/core/components/ui/toggle/toggle.type.ts +19 -0
  150. package/templates/mobile/expo/src/core/constants/external-urls.constant.ts +5 -0
  151. package/templates/mobile/expo/src/core/constants/hard-data.constant.ts +0 -0
  152. package/templates/mobile/expo/src/core/constants/index.ts +2 -0
  153. package/templates/mobile/expo/src/core/constants/type.constant.ts +3 -0
  154. package/templates/mobile/expo/src/core/context/index.ts +1 -0
  155. package/templates/mobile/expo/src/core/context/shared-transition-context.tsx +35 -0
  156. package/templates/mobile/expo/src/core/hook/index.ts +8 -0
  157. package/templates/mobile/expo/src/core/hook/useActiveRouteName.ts +63 -0
  158. package/templates/mobile/expo/src/core/hook/useAppNavigation.tsx +7 -0
  159. package/templates/mobile/expo/src/core/hook/useBottomInset.tsx +26 -0
  160. package/templates/mobile/expo/src/core/hook/useDebounce.tsx +16 -0
  161. package/templates/mobile/expo/src/core/hook/useEndReached.tsx +46 -0
  162. package/templates/mobile/expo/src/core/hook/useManualRefetch.ts +56 -0
  163. package/templates/mobile/expo/src/core/hook/useNetworkStatus.ts +68 -0
  164. package/templates/mobile/expo/src/core/hook/useTimeout.tsx +30 -0
  165. package/templates/mobile/expo/src/core/index.ts +7 -0
  166. package/templates/mobile/expo/src/core/services/api.service.ts +230 -0
  167. package/templates/mobile/expo/src/core/services/device-id.service.ts +23 -0
  168. package/templates/mobile/expo/src/core/services/index.ts +3 -0
  169. package/templates/mobile/expo/src/core/services/session-end.bridge.ts +26 -0
  170. package/templates/mobile/expo/src/core/theme/dark.theme.ts +10 -0
  171. package/templates/mobile/expo/src/core/theme/index.ts +5 -0
  172. package/templates/mobile/expo/src/core/theme/light.theme.ts +44 -0
  173. package/templates/mobile/expo/src/core/theme/theme-context.tsx +82 -0
  174. package/templates/mobile/expo/src/core/theme/theme.types.ts +26 -0
  175. package/templates/mobile/expo/src/core/theme/use-themed-styles.ts +25 -0
  176. package/templates/mobile/expo/src/core/utils/color.util.tsx +198 -0
  177. package/templates/mobile/expo/src/core/utils/date.util.ts +97 -0
  178. package/templates/mobile/expo/src/core/utils/device-locale.util.ts +22 -0
  179. package/templates/mobile/expo/src/core/utils/emitter/index.ts +161 -0
  180. package/templates/mobile/expo/src/core/utils/emitter/type.ts +40 -0
  181. package/templates/mobile/expo/src/core/utils/enum.util.tsx +15 -0
  182. package/templates/mobile/expo/src/core/utils/font.util.tsx +42 -0
  183. package/templates/mobile/expo/src/core/utils/func.util.ts +48 -0
  184. package/templates/mobile/expo/src/core/utils/greeting.util.ts +20 -0
  185. package/templates/mobile/expo/src/core/utils/image-format.util.ts +117 -0
  186. package/templates/mobile/expo/src/core/utils/image-picker.util.ts +84 -0
  187. package/templates/mobile/expo/src/core/utils/index.ts +18 -0
  188. package/templates/mobile/expo/src/core/utils/linking.util.ts +16 -0
  189. package/templates/mobile/expo/src/core/utils/navigation.util.tsx +100 -0
  190. package/templates/mobile/expo/src/core/utils/number-format.ts +28 -0
  191. package/templates/mobile/expo/src/core/utils/query-client.util.ts +35 -0
  192. package/templates/mobile/expo/src/core/utils/query-persister.util.ts +36 -0
  193. package/templates/mobile/expo/src/core/utils/schema.util.tsx +2713 -0
  194. package/templates/mobile/expo/src/core/utils/size.util.tsx +74 -0
  195. package/templates/mobile/expo/src/core/utils/storage.util.tsx +53 -0
  196. package/templates/mobile/expo/src/core/utils/toast.util.tsx +151 -0
  197. package/templates/mobile/expo/src/core/utils/translator.util.tsx +23 -0
  198. package/templates/mobile/expo/src/core/utils/typography.util.tsx +69 -0
  199. package/templates/mobile/expo/src/core/utils/validate.util.tsx +13 -0
  200. package/templates/mobile/expo/src/declarations.d.ts +54 -0
  201. package/templates/mobile/expo/src/modules/home/home.screen.tsx +33 -0
  202. package/templates/mobile/expo/src/modules/profile/profile.screen.tsx +29 -0
  203. package/templates/mobile/expo/src/scripts/link-fonts.js +60 -0
  204. package/templates/mobile/expo/src/scripts/sync-images.js +56 -0
  205. package/templates/mobile/expo/src/scripts/sync-svgs.js +50 -0
  206. package/templates/mobile/expo/src/types/models.d.ts +24 -0
  207. package/templates/mobile/expo/tsconfig.json +19 -0
  208. package/templates/mobile/rn/.env.example +5 -0
  209. package/templates/mobile/rn/.eslintrc.js +7 -0
  210. package/templates/mobile/rn/.prettierrc.js +7 -0
  211. package/templates/mobile/rn/.svgrrc.js +9 -0
  212. package/templates/mobile/rn/README.md +40 -7
  213. package/templates/mobile/rn/_gitignore +24 -0
  214. package/templates/mobile/rn/_package.json +67 -1
  215. package/templates/mobile/rn/app.json +4 -0
  216. package/templates/mobile/rn/babel.config.js +18 -0
  217. package/templates/mobile/rn/index.js +8 -0
  218. package/templates/mobile/rn/metro.config.js +33 -0
  219. package/templates/mobile/rn/src/app/App.tsx +24 -0
  220. package/templates/mobile/rn/src/app/app-provider.tsx +36 -0
  221. package/templates/mobile/rn/src/app/config/translation.ts +26 -0
  222. package/templates/mobile/rn/src/app/index.ts +1 -0
  223. package/templates/mobile/rn/src/app/navigation/app-route-type.ts +27 -0
  224. package/templates/mobile/rn/src/app/navigation/bottom-tabs.tsx +34 -0
  225. package/templates/mobile/rn/src/app/navigation/index.tsx +14 -0
  226. package/templates/mobile/rn/src/app/stores/auth.store.ts +33 -0
  227. package/templates/mobile/rn/src/app/stores/common.store.ts +19 -0
  228. package/templates/mobile/rn/src/app/stores/index.ts +3 -0
  229. package/templates/mobile/rn/src/app/stores/loading.store.ts +22 -0
  230. package/templates/mobile/rn/src/assets/Images/empty-list.png +0 -0
  231. package/templates/mobile/rn/src/assets/Images/index.ts +5 -0
  232. package/templates/mobile/rn/src/assets/Images/screen-bg-gradian.png +0 -0
  233. package/templates/mobile/rn/src/assets/i18n/en.json +12 -0
  234. package/templates/mobile/rn/src/assets/i18n/fr.json +12 -0
  235. package/templates/mobile/rn/src/assets/lotties/index.ts +3 -0
  236. package/templates/mobile/rn/src/assets/lotties/loading.json +1 -0
  237. package/templates/mobile/rn/src/assets/svgs/arrow-left.svg +3 -0
  238. package/templates/mobile/rn/src/assets/svgs/arrow-right.svg +3 -0
  239. package/templates/mobile/rn/src/assets/svgs/calendar.svg +12 -0
  240. package/templates/mobile/rn/src/assets/svgs/check.svg +3 -0
  241. package/templates/mobile/rn/src/assets/svgs/close.svg +3 -0
  242. package/templates/mobile/rn/src/assets/svgs/eye-hide.svg +3 -0
  243. package/templates/mobile/rn/src/assets/svgs/eye.svg +4 -0
  244. package/templates/mobile/rn/src/assets/svgs/index.ts +29 -0
  245. package/templates/mobile/rn/src/assets/svgs/minus.svg +3 -0
  246. package/templates/mobile/rn/src/assets/svgs/plus.svg +3 -0
  247. package/templates/mobile/rn/src/assets/svgs/search.svg +3 -0
  248. package/templates/mobile/rn/src/assets/svgs/toast-error.svg +5 -0
  249. package/templates/mobile/rn/src/assets/svgs/toast-success.svg +4 -0
  250. package/templates/mobile/rn/src/core/api/endpoints.ts +8 -0
  251. package/templates/mobile/rn/src/core/api/example.api.ts +53 -0
  252. package/templates/mobile/rn/src/core/api/index.ts +4 -0
  253. package/templates/mobile/rn/src/core/components/common/index.ts +1 -0
  254. package/templates/mobile/rn/src/core/components/common/list-empty/index.ts +2 -0
  255. package/templates/mobile/rn/src/core/components/common/list-empty/list-empty.tsx +30 -0
  256. package/templates/mobile/rn/src/core/components/common/list-empty/list-empty.type.ts +5 -0
  257. package/templates/mobile/rn/src/core/components/forms/hf-date-time.tsx +301 -0
  258. package/templates/mobile/rn/src/core/components/forms/hf-password-input.tsx +153 -0
  259. package/templates/mobile/rn/src/core/components/forms/hf-text-input.tsx +59 -0
  260. package/templates/mobile/rn/src/core/components/forms/hf-time-picker.tsx +117 -0
  261. package/templates/mobile/rn/src/core/components/forms/index.ts +4 -0
  262. package/templates/mobile/rn/src/core/components/index.ts +5 -0
  263. package/templates/mobile/rn/src/core/components/loading/index.ts +1 -0
  264. package/templates/mobile/rn/src/core/components/loading/loading-app/index.ts +1 -0
  265. package/templates/mobile/rn/src/core/components/loading/loading-app/loading-app.tsx +50 -0
  266. package/templates/mobile/rn/src/core/components/offline/index.ts +1 -0
  267. package/templates/mobile/rn/src/core/components/offline/offline-banner.tsx +186 -0
  268. package/templates/mobile/rn/src/core/components/screen/index.ts +1 -0
  269. package/templates/mobile/rn/src/core/components/screen/screen-container/index.ts +1 -0
  270. package/templates/mobile/rn/src/core/components/screen/screen-container/screen-container.tsx +252 -0
  271. package/templates/mobile/rn/src/core/components/splash/index.ts +1 -0
  272. package/templates/mobile/rn/src/core/components/splash/splash-overlay/index.ts +1 -0
  273. package/templates/mobile/rn/src/core/components/splash/splash-overlay/splash-overlay.tsx +93 -0
  274. package/templates/mobile/rn/src/core/components/ui/animated-list-item/animated-list-item.tsx +48 -0
  275. package/templates/mobile/rn/src/core/components/ui/animated-list-item/animated-list-item.type.ts +10 -0
  276. package/templates/mobile/rn/src/core/components/ui/animated-list-item/index.ts +2 -0
  277. package/templates/mobile/rn/src/core/components/ui/app-image/app-image.tsx +104 -0
  278. package/templates/mobile/rn/src/core/components/ui/app-image/app-image.type.ts +19 -0
  279. package/templates/mobile/rn/src/core/components/ui/app-image/index.ts +2 -0
  280. package/templates/mobile/rn/src/core/components/ui/asset-placeholder/asset-placeholder.tsx +76 -0
  281. package/templates/mobile/rn/src/core/components/ui/asset-placeholder/index.ts +1 -0
  282. package/templates/mobile/rn/src/core/components/ui/avatar-image/avatar-image.tsx +90 -0
  283. package/templates/mobile/rn/src/core/components/ui/avatar-image/index.ts +1 -0
  284. package/templates/mobile/rn/src/core/components/ui/bottom-sheet/bottom-sheet.tsx +145 -0
  285. package/templates/mobile/rn/src/core/components/ui/bottom-sheet/bottom-sheet.type.ts +10 -0
  286. package/templates/mobile/rn/src/core/components/ui/bottom-sheet/index.ts +2 -0
  287. package/templates/mobile/rn/src/core/components/ui/button/button.style.ts +146 -0
  288. package/templates/mobile/rn/src/core/components/ui/button/button.tsx +97 -0
  289. package/templates/mobile/rn/src/core/components/ui/button/button.type.ts +47 -0
  290. package/templates/mobile/rn/src/core/components/ui/button/index.ts +4 -0
  291. package/templates/mobile/rn/src/core/components/ui/checkbox/checkbox.tsx +127 -0
  292. package/templates/mobile/rn/src/core/components/ui/checkbox/checkbox.type.ts +24 -0
  293. package/templates/mobile/rn/src/core/components/ui/checkbox/index.ts +2 -0
  294. package/templates/mobile/rn/src/core/components/ui/collapsible-section/collapsible-section.tsx +141 -0
  295. package/templates/mobile/rn/src/core/components/ui/collapsible-section/collapsible-section.type.ts +10 -0
  296. package/templates/mobile/rn/src/core/components/ui/collapsible-section/index.ts +2 -0
  297. package/templates/mobile/rn/src/core/components/ui/components.registry.json +313 -0
  298. package/templates/mobile/rn/src/core/components/ui/field/field-frame.tsx +62 -0
  299. package/templates/mobile/rn/src/core/components/ui/field/field.shared.ts +31 -0
  300. package/templates/mobile/rn/src/core/components/ui/header/header.tsx +196 -0
  301. package/templates/mobile/rn/src/core/components/ui/header/header.type.ts +30 -0
  302. package/templates/mobile/rn/src/core/components/ui/header/index.ts +2 -0
  303. package/templates/mobile/rn/src/core/components/ui/icon-button/icon-button.style.ts +23 -0
  304. package/templates/mobile/rn/src/core/components/ui/icon-button/icon-button.tsx +66 -0
  305. package/templates/mobile/rn/src/core/components/ui/icon-button/icon-button.type.ts +25 -0
  306. package/templates/mobile/rn/src/core/components/ui/icon-button/index.ts +2 -0
  307. package/templates/mobile/rn/src/core/components/ui/image-slider/image-slider.tsx +107 -0
  308. package/templates/mobile/rn/src/core/components/ui/image-slider/image-slider.type.ts +10 -0
  309. package/templates/mobile/rn/src/core/components/ui/image-slider/index.ts +2 -0
  310. package/templates/mobile/rn/src/core/components/ui/index.ts +25 -0
  311. package/templates/mobile/rn/src/core/components/ui/label/index.ts +2 -0
  312. package/templates/mobile/rn/src/core/components/ui/label/label.tsx +36 -0
  313. package/templates/mobile/rn/src/core/components/ui/label/label.type.ts +12 -0
  314. package/templates/mobile/rn/src/core/components/ui/modal/index.ts +2 -0
  315. package/templates/mobile/rn/src/core/components/ui/modal/modal.tsx +62 -0
  316. package/templates/mobile/rn/src/core/components/ui/modal/modal.type.ts +11 -0
  317. package/templates/mobile/rn/src/core/components/ui/otp-input/index.ts +2 -0
  318. package/templates/mobile/rn/src/core/components/ui/otp-input/otp-input.tsx +129 -0
  319. package/templates/mobile/rn/src/core/components/ui/otp-input/otp-input.type.ts +20 -0
  320. package/templates/mobile/rn/src/core/components/ui/page-dots/index.ts +1 -0
  321. package/templates/mobile/rn/src/core/components/ui/page-dots/page-dots.tsx +60 -0
  322. package/templates/mobile/rn/src/core/components/ui/radio/index.ts +2 -0
  323. package/templates/mobile/rn/src/core/components/ui/radio/radio.tsx +121 -0
  324. package/templates/mobile/rn/src/core/components/ui/radio/radio.type.ts +20 -0
  325. package/templates/mobile/rn/src/core/components/ui/screen/index.ts +1 -0
  326. package/templates/mobile/rn/src/core/components/ui/screen/screen-gradient.tsx +33 -0
  327. package/templates/mobile/rn/src/core/components/ui/search-box/index.ts +2 -0
  328. package/templates/mobile/rn/src/core/components/ui/search-box/search-box.tsx +162 -0
  329. package/templates/mobile/rn/src/core/components/ui/search-box/search-box.type.ts +26 -0
  330. package/templates/mobile/rn/src/core/components/ui/segmented-control/index.ts +2 -0
  331. package/templates/mobile/rn/src/core/components/ui/segmented-control/segmented-control.tsx +86 -0
  332. package/templates/mobile/rn/src/core/components/ui/segmented-control/segmented-control.type.ts +22 -0
  333. package/templates/mobile/rn/src/core/components/ui/skeleton/index.ts +2 -0
  334. package/templates/mobile/rn/src/core/components/ui/skeleton/skeleton.tsx +106 -0
  335. package/templates/mobile/rn/src/core/components/ui/skeleton/skeleton.type.ts +8 -0
  336. package/templates/mobile/rn/src/core/components/ui/success-state/index.ts +1 -0
  337. package/templates/mobile/rn/src/core/components/ui/success-state/success-state.tsx +68 -0
  338. package/templates/mobile/rn/src/core/components/ui/tabs/index.ts +2 -0
  339. package/templates/mobile/rn/src/core/components/ui/tabs/tabs.tsx +273 -0
  340. package/templates/mobile/rn/src/core/components/ui/tabs/tabs.type.ts +21 -0
  341. package/templates/mobile/rn/src/core/components/ui/tag-input/index.ts +2 -0
  342. package/templates/mobile/rn/src/core/components/ui/tag-input/tag-input.tsx +146 -0
  343. package/templates/mobile/rn/src/core/components/ui/tag-input/tag-input.type.ts +22 -0
  344. package/templates/mobile/rn/src/core/components/ui/text-area/index.ts +2 -0
  345. package/templates/mobile/rn/src/core/components/ui/text-area/text-area.tsx +90 -0
  346. package/templates/mobile/rn/src/core/components/ui/text-area/text-area.type.ts +20 -0
  347. package/templates/mobile/rn/src/core/components/ui/text-field/index.ts +2 -0
  348. package/templates/mobile/rn/src/core/components/ui/text-field/text-field.tsx +116 -0
  349. package/templates/mobile/rn/src/core/components/ui/text-field/text-field.type.ts +21 -0
  350. package/templates/mobile/rn/src/core/components/ui/toggle/index.ts +2 -0
  351. package/templates/mobile/rn/src/core/components/ui/toggle/toggle.tsx +110 -0
  352. package/templates/mobile/rn/src/core/components/ui/toggle/toggle.type.ts +19 -0
  353. package/templates/mobile/rn/src/core/constants/external-urls.constant.ts +5 -0
  354. package/templates/mobile/rn/src/core/constants/hard-data.constant.ts +0 -0
  355. package/templates/mobile/rn/src/core/constants/index.ts +2 -0
  356. package/templates/mobile/rn/src/core/constants/type.constant.ts +3 -0
  357. package/templates/mobile/rn/src/core/context/index.ts +1 -0
  358. package/templates/mobile/rn/src/core/context/shared-transition-context.tsx +35 -0
  359. package/templates/mobile/rn/src/core/hook/index.ts +8 -0
  360. package/templates/mobile/rn/src/core/hook/useActiveRouteName.ts +63 -0
  361. package/templates/mobile/rn/src/core/hook/useAppNavigation.tsx +7 -0
  362. package/templates/mobile/rn/src/core/hook/useBottomInset.tsx +26 -0
  363. package/templates/mobile/rn/src/core/hook/useDebounce.tsx +16 -0
  364. package/templates/mobile/rn/src/core/hook/useEndReached.tsx +46 -0
  365. package/templates/mobile/rn/src/core/hook/useManualRefetch.ts +56 -0
  366. package/templates/mobile/rn/src/core/hook/useNetworkStatus.ts +68 -0
  367. package/templates/mobile/rn/src/core/hook/useTimeout.tsx +30 -0
  368. package/templates/mobile/rn/src/core/index.ts +7 -0
  369. package/templates/mobile/rn/src/core/services/api.service.ts +230 -0
  370. package/templates/mobile/rn/src/core/services/device-id.service.ts +23 -0
  371. package/templates/mobile/rn/src/core/services/index.ts +3 -0
  372. package/templates/mobile/rn/src/core/services/session-end.bridge.ts +26 -0
  373. package/templates/mobile/rn/src/core/theme/dark.theme.ts +10 -0
  374. package/templates/mobile/rn/src/core/theme/index.ts +5 -0
  375. package/templates/mobile/rn/src/core/theme/light.theme.ts +44 -0
  376. package/templates/mobile/rn/src/core/theme/theme-context.tsx +82 -0
  377. package/templates/mobile/rn/src/core/theme/theme.types.ts +26 -0
  378. package/templates/mobile/rn/src/core/theme/use-themed-styles.ts +25 -0
  379. package/templates/mobile/rn/src/core/utils/color.util.tsx +198 -0
  380. package/templates/mobile/rn/src/core/utils/date.util.ts +97 -0
  381. package/templates/mobile/rn/src/core/utils/device-locale.util.ts +22 -0
  382. package/templates/mobile/rn/src/core/utils/emitter/index.ts +161 -0
  383. package/templates/mobile/rn/src/core/utils/emitter/type.ts +40 -0
  384. package/templates/mobile/rn/src/core/utils/enum.util.tsx +15 -0
  385. package/templates/mobile/rn/src/core/utils/font.util.tsx +42 -0
  386. package/templates/mobile/rn/src/core/utils/func.util.ts +48 -0
  387. package/templates/mobile/rn/src/core/utils/greeting.util.ts +20 -0
  388. package/templates/mobile/rn/src/core/utils/image-format.util.ts +117 -0
  389. package/templates/mobile/rn/src/core/utils/image-picker.util.ts +84 -0
  390. package/templates/mobile/rn/src/core/utils/index.ts +18 -0
  391. package/templates/mobile/rn/src/core/utils/linking.util.ts +16 -0
  392. package/templates/mobile/rn/src/core/utils/navigation.util.tsx +100 -0
  393. package/templates/mobile/rn/src/core/utils/number-format.ts +28 -0
  394. package/templates/mobile/rn/src/core/utils/query-client.util.ts +35 -0
  395. package/templates/mobile/rn/src/core/utils/query-persister.util.ts +36 -0
  396. package/templates/mobile/rn/src/core/utils/schema.util.tsx +2713 -0
  397. package/templates/mobile/rn/src/core/utils/size.util.tsx +74 -0
  398. package/templates/mobile/rn/src/core/utils/storage.util.tsx +53 -0
  399. package/templates/mobile/rn/src/core/utils/toast.util.tsx +151 -0
  400. package/templates/mobile/rn/src/core/utils/translator.util.tsx +23 -0
  401. package/templates/mobile/rn/src/core/utils/typography.util.tsx +69 -0
  402. package/templates/mobile/rn/src/core/utils/validate.util.tsx +13 -0
  403. package/templates/mobile/rn/src/declarations.d.ts +54 -0
  404. package/templates/mobile/rn/src/modules/home/home.screen.tsx +33 -0
  405. package/templates/mobile/rn/src/modules/profile/profile.screen.tsx +29 -0
  406. package/templates/mobile/rn/src/scripts/link-fonts.js +60 -0
  407. package/templates/mobile/rn/src/scripts/sync-images.js +56 -0
  408. package/templates/mobile/rn/src/scripts/sync-svgs.js +50 -0
  409. package/templates/mobile/rn/src/types/models.d.ts +24 -0
  410. package/templates/mobile/rn/tsconfig.json +21 -0
  411. package/templates/shared/src/api-endpoints.ts +8 -0
  412. package/templates/shared/src/enums.ts +34 -0
  413. package/templates/shared/src/external-urls.ts +5 -0
  414. package/templates/shared/src/index.ts +6 -3
@@ -0,0 +1,68 @@
1
+ import { useEffect, useState } from 'react';
2
+ import NetInfo, { NetInfoState } from '@react-native-community/netinfo';
3
+
4
+ export interface NetworkStatus {
5
+ /** The device has an active network interface (wifi / cellular). */
6
+ isConnected: boolean;
7
+ /**
8
+ * The interface can actually reach the internet — distinguishes a captive
9
+ * portal or a "connected but no data" wifi from a real connection. `null`
10
+ * while NetInfo is still probing (treated as reachable to avoid a false
11
+ * offline flash on launch).
12
+ */
13
+ isInternetReachable: boolean | null;
14
+ /**
15
+ * The single flag screens should branch on: truly offline only when there
16
+ * is no interface, or the interface is confirmed unable to reach the
17
+ * internet. A `null` (probing) reachability does NOT count as offline.
18
+ */
19
+ isOffline: boolean;
20
+ }
21
+
22
+ /**
23
+ * Maps a raw NetInfo state to our {@link NetworkStatus}. Exported for unit
24
+ * testing the offline-detection rule (connected + reachable handling).
25
+ */
26
+ export const deriveNetworkStatus = (state: NetInfoState): NetworkStatus => {
27
+ const isConnected = state.isConnected ?? true;
28
+ const isInternetReachable = state.isInternetReachable;
29
+ const isOffline = !isConnected || isInternetReachable === false;
30
+ return { isConnected, isInternetReachable, isOffline };
31
+ };
32
+
33
+ /**
34
+ * Latest known offline state, kept in sync by a module-scope NetInfo listener.
35
+ * Lets non-React code (the player store / queue transport, which runs outside
36
+ * the component tree) read offline status synchronously via {@link isDeviceOffline}
37
+ * — the React hook below can't reach those call sites. Starts `false` so we never
38
+ * report a false "offline" before NetInfo's first probe lands.
39
+ */
40
+ let deviceOffline = false;
41
+ NetInfo.addEventListener(state => {
42
+ deviceOffline = deriveNetworkStatus(state).isOffline;
43
+ });
44
+
45
+ /**
46
+ * Synchronous offline check for non-React callers (e.g. the player queue, which
47
+ * must decide whether stepping to the next track is playable without internet).
48
+ * Components should use {@link useNetworkStatus} instead so they re-render on change.
49
+ */
50
+ export const isDeviceOffline = (): boolean => deviceOffline;
51
+
52
+ export const useNetworkStatus = (): NetworkStatus => {
53
+ const [status, setStatus] = useState<NetworkStatus>({
54
+ isConnected: true,
55
+ isInternetReachable: null,
56
+ isOffline: false,
57
+ });
58
+
59
+ useEffect(() => {
60
+ const unsubscribe = NetInfo.addEventListener(state => {
61
+ setStatus(deriveNetworkStatus(state));
62
+ });
63
+
64
+ return unsubscribe;
65
+ }, []);
66
+
67
+ return status;
68
+ };
@@ -0,0 +1,30 @@
1
+ import { useCallback, useEffect, useRef } from 'react';
2
+
3
+ export default function useTimeout(callback: () => void, delay: number) {
4
+ const callbackRef = useRef(callback);
5
+ const timeoutRef = useRef<any>(null);
6
+
7
+ useEffect(() => {
8
+ callbackRef.current = callback;
9
+ }, [callback]);
10
+
11
+ const set = useCallback(() => {
12
+ timeoutRef.current = setTimeout(() => callbackRef.current(), delay);
13
+ }, [delay]);
14
+
15
+ const clear = useCallback(() => {
16
+ timeoutRef.current && clearTimeout(timeoutRef.current);
17
+ }, []);
18
+
19
+ useEffect(() => {
20
+ set();
21
+ return clear;
22
+ }, [delay, set, clear]);
23
+
24
+ const reset = useCallback(() => {
25
+ clear();
26
+ set();
27
+ }, [clear, set]);
28
+
29
+ return { reset, clear };
30
+ }
@@ -0,0 +1,7 @@
1
+ export * from './api';
2
+ export * from './components';
3
+ export * from './constants';
4
+ export * from './context';
5
+ export * from './services';
6
+ export * from './utils';
7
+ export * from './theme';
@@ -0,0 +1,230 @@
1
+ import { useAuthStore, useLoadingStore } from '@src/app/stores';
2
+ import axios, { AxiosRequestConfig, AxiosResponse } from 'axios';
3
+ import qs from 'qs';
4
+ import { Alert } from 'react-native';
5
+ import Config from 'react-native-config';
6
+ import { Screen } from '@src/app/navigation/app-route-type';
7
+ import { NavigationUtil, navigationRef } from '@src/core/utils/navigation.util';
8
+ import { Translator } from '@src/core/utils/translator.util';
9
+ import { getDeviceId } from './device-id.service';
10
+ import { triggerSessionEnd } from './session-end.bridge';
11
+
12
+ // Backend supports French and English; map anything else to English so OTP
13
+ // emails and other localized responses match the user's selected language
14
+ // (LIFEMASTER-369). i18n.language is the source of truth — the profile language
15
+ // switch drives it directly.
16
+ const acceptLanguage = (): string => (Translator.currentLanguage().startsWith('fr') ? 'fr' : 'en');
17
+
18
+ // API Configuration
19
+ export const API_VERSION = 'v1';
20
+ export const BaseURL = `${Config.BASE_URL}${API_VERSION}/`;
21
+ // Create axios instance with default configuration
22
+ const axiosInstance = axios.create({
23
+ timeout: 10000,
24
+ baseURL: BaseURL,
25
+ });
26
+
27
+ axiosInstance.interceptors.request.use(async (config) => {
28
+ try {
29
+ const deviceId = await getDeviceId();
30
+ config.headers.set('X-Device-Id', deviceId);
31
+ } catch {
32
+ // Device ID resolution failed — proceed without header
33
+ }
34
+ return config;
35
+ });
36
+
37
+ let sessionExpiredAlertShown = false;
38
+
39
+ const handleSessionExpired = async () => {
40
+ if (sessionExpiredAlertShown) return;
41
+ // Full session teardown on a 401 (token, premium lease, player audio, React
42
+ // Query cache, library, favorites…). Routed through the bridge — the actual
43
+ // `endSession` lives in the app layer and pulls feature stores, so importing
44
+ // it here would create a core → app import cycle.
45
+ triggerSessionEnd();
46
+ // Cold launch: the navigation container isn't mounted yet (App renders null
47
+ // until the bootstrap route resolves), so a 401 from the bootstrap resume
48
+ // fetch must NOT pop a "session expired" alert over Onboarding — bootstrap
49
+ // already routes there. Clear silently and let it own the routing.
50
+ if (!navigationRef.current) return;
51
+ sessionExpiredAlertShown = true;
52
+ NavigationUtil.reset(Screen.SignIn);
53
+ Alert.alert(
54
+ Translator.translate('auth.sessionExpired.title'),
55
+ Translator.translate('auth.sessionExpired.message'),
56
+ [{
57
+ text: Translator.translate('auth.sessionExpired.cta'),
58
+ onPress: () => { sessionExpiredAlertShown = false; },
59
+ }],
60
+ { cancelable: false, onDismiss: () => { sessionExpiredAlertShown = false; } },
61
+ );
62
+ };
63
+
64
+ axiosInstance.interceptors.response.use(
65
+ (response: AxiosResponse) => response,
66
+ (error) => {
67
+ if (error?.response?.status === 401 && useAuthStore.getState().token) {
68
+ handleSessionExpired();
69
+ }
70
+ return Promise.reject(error);
71
+ }
72
+ );
73
+
74
+ // Types
75
+ interface ApiError {
76
+ message: string;
77
+ code?: string;
78
+ success: boolean;
79
+ status?: number;
80
+ }
81
+
82
+ interface ApiResponse<T = any> {
83
+ data?: T;
84
+ success: boolean;
85
+ errors?: Array<{ message: string; code?: string }>;
86
+ }
87
+
88
+ // Error handling utility
89
+ const handleApiError = (error: any): ApiError => {
90
+ const status = error?.response?.status;
91
+ if (error.response?.data) {
92
+ const { data } = error.response;
93
+ const { errors, success } = data;
94
+
95
+ if (!success && errors?.length > 0) {
96
+ const { message, code } = errors[0];
97
+ return {
98
+ message: message?.message || message || 'An error occurred',
99
+ code,
100
+ success: false,
101
+ status,
102
+ };
103
+ }
104
+
105
+ return { ...data, status };
106
+ }
107
+
108
+ return {
109
+ message: error.message || 'Network error occurred',
110
+ success: false,
111
+ status,
112
+ };
113
+ };
114
+
115
+ // Response preprocessing utility
116
+ const preprocessResponse = <T>(result: ApiResponse<T>): T | ApiError => {
117
+ const { success, errors } = result || {};
118
+
119
+ if (success) {
120
+ return (result) as T;
121
+ }
122
+
123
+ if (errors && errors.length > 0) {
124
+ return { ...errors[0], success: false };
125
+ }
126
+
127
+ return result as T;
128
+ };
129
+
130
+ export class ApiService {
131
+ // Get authorization headers
132
+ private static getAuthHeaders(): Record<string, string> {
133
+ const token = useAuthStore.getState().token;
134
+ return {
135
+ Accept: 'application/json',
136
+ 'Content-Type': 'application/json',
137
+ 'Accept-Language': acceptLanguage(),
138
+ ...(token && { Authorization: `Bearer ${token}` }),
139
+ };
140
+ }
141
+
142
+ // Form-data headers. Content-Type is intentionally OMITTED: axios must set it
143
+ // itself so it appends the `; boundary=...` the server needs to split the
144
+ // parts. Hardcoding `multipart/form-data` (no boundary) made the backend fail
145
+ // to parse the file — the avatar upload then errored on some photos but not
146
+ // others depending on the payload (LIFEMASTER-260).
147
+ private static getFormDataHeaders(): Record<string, string> {
148
+ const token = useAuthStore.getState().token;
149
+ return {
150
+ Accept: 'application/json',
151
+ 'Accept-Language': acceptLanguage(),
152
+ ...(token && { Authorization: `Bearer ${token}` }),
153
+ };
154
+ }
155
+
156
+ // Generic request method. Drives the global loading overlay via the
157
+ // ref-counted loading store; pass `silent: true` on the config to opt out
158
+ // (e.g. background refetch / polling) so it doesn't block the UI.
159
+ private static async makeRequest<T>(
160
+ config: AxiosRequestConfig & { silent?: boolean },
161
+ ): Promise<T> {
162
+ const { silent, ...axiosConfig } = config;
163
+ if (!silent) useLoadingStore.getState().showLoading();
164
+ try {
165
+ const response = await axiosInstance(axiosConfig);
166
+ return preprocessResponse(response.data) as T;
167
+ } catch (error) {
168
+ throw handleApiError(error);
169
+ } finally {
170
+ if (!silent) useLoadingStore.getState().hideLoading();
171
+ }
172
+ }
173
+
174
+ // GET request — pass silent=true to skip the global loading overlay.
175
+ static async get<T = any>(url: string, params?: any, silent = false): Promise<IResponse<T>> {
176
+ return this.makeRequest<IResponse<T>>({
177
+ method: 'GET',
178
+ url,
179
+ headers: this.getAuthHeaders(),
180
+ params,
181
+ paramsSerializer: (p) => qs.stringify(p, { arrayFormat: 'repeat' }),
182
+ silent,
183
+ });
184
+ }
185
+
186
+ // POST request
187
+ static async post<T = any>(url: string, data?: any, isFormData = false, silent = false): Promise<IResponse<T>> {
188
+ return this.makeRequest<IResponse<T>>({
189
+ method: 'POST',
190
+ url,
191
+ headers: isFormData ? this.getFormDataHeaders() : this.getAuthHeaders(),
192
+ data,
193
+ silent,
194
+ });
195
+ }
196
+
197
+ // PUT request
198
+ static async put<T = any>(url: string, data?: any, silent = false): Promise<IResponse<T>> {
199
+ return this.makeRequest<IResponse<T>>({
200
+ method: 'PUT',
201
+ url,
202
+ headers: this.getAuthHeaders(),
203
+ data,
204
+ silent,
205
+ });
206
+ }
207
+
208
+ // DELETE request
209
+ static async delete<T = any>(url: string, data?: any, silent = false): Promise<IResponse<T>> {
210
+ return this.makeRequest<IResponse<T>>({
211
+ method: 'DELETE',
212
+ url,
213
+ headers: this.getAuthHeaders(),
214
+ data,
215
+ silent,
216
+ });
217
+ }
218
+
219
+ // PATCH request (added for completeness)
220
+ static async patch<T = any>(url: string, data?: any, silent = false): Promise<IResponse<T>> {
221
+ return this.makeRequest<IResponse<T>>({
222
+ method: 'PATCH',
223
+ url,
224
+ headers: this.getAuthHeaders(),
225
+ data,
226
+ silent,
227
+ });
228
+ }
229
+
230
+ }
@@ -0,0 +1,23 @@
1
+ import AsyncStorage from '@react-native-async-storage/async-storage';
2
+ import DeviceInfo from 'react-native-device-info';
3
+
4
+ const DEVICE_ID_KEY = 'device_id';
5
+
6
+ let cachedDeviceId: string | null = null;
7
+
8
+ export const getDeviceId = async (): Promise<string> => {
9
+ if (cachedDeviceId) return cachedDeviceId;
10
+
11
+ const stored = await AsyncStorage.getItem(DEVICE_ID_KEY);
12
+ if (stored) {
13
+ cachedDeviceId = stored;
14
+ return stored;
15
+ }
16
+
17
+ const nativeId = await DeviceInfo.getUniqueId();
18
+ await AsyncStorage.setItem(DEVICE_ID_KEY, nativeId);
19
+ cachedDeviceId = nativeId;
20
+ return nativeId;
21
+ };
22
+
23
+ export const getCachedDeviceId = (): string | null => cachedDeviceId;
@@ -0,0 +1,3 @@
1
+ export * from './api.service'
2
+ export * from './device-id.service'
3
+ export * from './session-end.bridge'
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Session-teardown bridge.
3
+ *
4
+ * The 401 response interceptor in `api.service` must trigger a FULL session
5
+ * reset (tokens, premium lease, player audio, React Query cache, library,
6
+ * favorites…). That teardown (`endSession`) lives in the app layer and pulls in
7
+ * feature stores, so importing it here would create a `core → app → core` import
8
+ * cycle — which this codebase deliberately avoids.
9
+ *
10
+ * Instead the app registers its teardown once at startup ({@link App}) and the
11
+ * interceptor fires it through this shim, keeping the import graph one-way
12
+ * (app → core). Until something registers, {@link triggerSessionEnd} is a no-op.
13
+ */
14
+ type SessionEndHandler = () => void;
15
+
16
+ let handler: SessionEndHandler | null = null;
17
+
18
+ /** Wire the app-level `endSession` in. Called once at app startup. */
19
+ export const registerSessionEndHandler = (fn: SessionEndHandler): void => {
20
+ handler = fn;
21
+ };
22
+
23
+ /** Run the registered teardown (no-op if the app hasn't registered one yet). */
24
+ export const triggerSessionEnd = (): void => {
25
+ handler?.();
26
+ };
@@ -0,0 +1,10 @@
1
+ import { Colors } from '@src/core/utils';
2
+ import { ThemeColors } from './theme.types';
3
+
4
+ /**
5
+ * Dark palette. The existing static `Colors` object already encodes the dark
6
+ * look (light text on dark surfaces), so the dark theme reuses it verbatim —
7
+ * dark mode therefore renders identically to the pre-theme app, with zero
8
+ * visual regression.
9
+ */
10
+ export const darkColors: ThemeColors = Colors;
@@ -0,0 +1,5 @@
1
+ export * from './theme.types';
2
+ export * from './dark.theme';
3
+ export * from './light.theme';
4
+ export * from './theme-context';
5
+ export * from './use-themed-styles';
@@ -0,0 +1,44 @@
1
+ import { darkColors } from './dark.theme';
2
+ import { ThemeColors } from './theme.types';
3
+
4
+ /**
5
+ * Light palette. Built by spreading the dark theme and overriding only the
6
+ * neutral surface / text / border tokens with light values — brand tokens
7
+ * (primary purple, secondary blue, semantic colors) stay identical across
8
+ * themes so the brand reads the same in both modes.
9
+ *
10
+ * TODO(design): these light DS values are a pragmatic default derived from the
11
+ * existing legacy light tokens. Confirm the exact light palette against the
12
+ * Figma "Life-Master-Design" light variables when available.
13
+ */
14
+ export const lightColors: ThemeColors = {
15
+ ...darkColors,
16
+
17
+ // --- foreground (text/icon) ---
18
+ fg_neutral_normal: '#0F0F0F', // primary text on light surface
19
+ fg_neutral_faded: '#525252', // muted / disabled text
20
+ fg_neutral_reverse: '#FBFAFF', // text on dark/reverse fill
21
+ fg_neutral_placeholder: '#A8A8A8',
22
+
23
+ // --- background (fill) ---
24
+ bg_elevation_reverse: '#141414', // dark fill (neutral solid) on light
25
+ bg_elevation_level_1_normal: '#FFFFFF', // base surface
26
+ bg_elevation_level_2_normal: '#F5F5F5', // raised / disabled surface
27
+ bg_input: 'rgba(0, 0, 0, 0.06)', // input field fill on light
28
+ bg_disable: '#E5E5E5',
29
+
30
+ // --- border ---
31
+ bd_neutral_normal: '#E5E5E5',
32
+ bd_neutral_faded: '#C6C6C6',
33
+
34
+ // --- danger surfaces (destructive actions) — softened for light surface ---
35
+ bg_danger_faded: '#FDE7EC', // light danger fill (logout button bg)
36
+ bd_danger_faded: '#F43F5E',
37
+ fg_danger_faded_ds: '#BE123C', // readable danger text on light fill
38
+
39
+ // --- translucent card overlays (rows over the light background) ---
40
+ bg_overlay_card: 'rgba(0, 0, 0, 0.04)',
41
+ bg_overlay_card_medium: 'rgba(0, 0, 0, 0.05)',
42
+ bg_overlay_card_strong: 'rgba(0, 0, 0, 0.06)',
43
+ bg_progress_track: 'rgba(0, 0, 0, 0.1)',
44
+ };
@@ -0,0 +1,82 @@
1
+ import React, {
2
+ createContext,
3
+ PropsWithChildren,
4
+ useCallback,
5
+ useContext,
6
+ useEffect,
7
+ useMemo,
8
+ useState,
9
+ } from 'react';
10
+ import { useColorScheme } from 'react-native';
11
+ import { AppStorageUtil, StorageKeys } from '@src/core/utils';
12
+ import { darkColors } from './dark.theme';
13
+ import { lightColors } from './light.theme';
14
+ import {
15
+ ResolvedScheme,
16
+ ThemeContextValue,
17
+ ThemeScheme,
18
+ } from './theme.types';
19
+
20
+ // Only the dark theme is shipped for now — the light palette is a draft
21
+ // pending design. Default to dark and (below) lock the resolved scheme to dark
22
+ // so the OS/light never leaks through. To re-enable light later: set this back
23
+ // to 'system' and remove the `LIGHT_THEME_ENABLED` lock in `resolved`.
24
+ const DEFAULT_SCHEME: ThemeScheme = 'dark';
25
+ const LIGHT_THEME_ENABLED = false;
26
+
27
+ const ThemeContext = createContext<ThemeContextValue | undefined>(undefined);
28
+
29
+ /**
30
+ * Provides theme tokens app-wide. Defaults to following the OS appearance and
31
+ * hydrates the persisted user override (AsyncStorage) after mount. Mount this
32
+ * once near the app root, above the navigation tree.
33
+ */
34
+ export const ThemeProvider = ({ children }: PropsWithChildren) => {
35
+ const osScheme = useColorScheme();
36
+ const [scheme, setSchemeState] = useState<ThemeScheme>(DEFAULT_SCHEME);
37
+
38
+ // Hydrate the persisted preference once on mount (AsyncStorage is async, so
39
+ // the first frame uses the system default and corrects itself here).
40
+ useEffect(() => {
41
+ let active = true;
42
+ AppStorageUtil.getItem(StorageKeys.THEME).then(saved => {
43
+ if (active && (saved === 'light' || saved === 'dark' || saved === 'system')) {
44
+ setSchemeState(saved);
45
+ }
46
+ });
47
+ return () => {
48
+ active = false;
49
+ };
50
+ }, []);
51
+
52
+ const setScheme = useCallback((next: ThemeScheme) => {
53
+ setSchemeState(next);
54
+ AppStorageUtil.setItem(StorageKeys.THEME, next);
55
+ }, []);
56
+
57
+ const requested: ResolvedScheme =
58
+ scheme === 'system' ? (osScheme === 'dark' ? 'dark' : 'light') : scheme;
59
+ // Light theme is not shipped yet — force dark until it is.
60
+ const resolved: ResolvedScheme = LIGHT_THEME_ENABLED ? requested : 'dark';
61
+
62
+ const value = useMemo<ThemeContextValue>(
63
+ () => ({
64
+ colors: resolved === 'dark' ? darkColors : lightColors,
65
+ scheme,
66
+ resolved,
67
+ setScheme,
68
+ }),
69
+ [resolved, scheme, setScheme],
70
+ );
71
+
72
+ return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
73
+ };
74
+
75
+ /** Access the active theme. Throws if used outside `ThemeProvider`. */
76
+ export const useTheme = (): ThemeContextValue => {
77
+ const ctx = useContext(ThemeContext);
78
+ if (!ctx) {
79
+ throw new Error('useTheme must be used within a ThemeProvider');
80
+ }
81
+ return ctx;
82
+ };
@@ -0,0 +1,26 @@
1
+ import { Colors } from '@src/core/utils';
2
+
3
+ /**
4
+ * The theme color contract. Both the light and dark palettes must expose the
5
+ * exact same keys as the static `Colors` object, so design-system components
6
+ * can keep referencing token names (`colors.bg_primary_normal`) unchanged —
7
+ * only the *source* of the value switches with the active theme.
8
+ */
9
+ export type ThemeColors = typeof Colors;
10
+
11
+ /** What the user can pick: an explicit theme or "follow the OS". */
12
+ export type ThemeScheme = 'light' | 'dark' | 'system';
13
+
14
+ /** The concrete theme actually rendered after resolving `system`. */
15
+ export type ResolvedScheme = 'light' | 'dark';
16
+
17
+ export interface ThemeContextValue {
18
+ /** Active color tokens for the resolved scheme. */
19
+ colors: ThemeColors;
20
+ /** The user's preference (may be `system`). */
21
+ scheme: ThemeScheme;
22
+ /** The concrete scheme being rendered (`system` already resolved). */
23
+ resolved: ResolvedScheme;
24
+ /** Persist and apply a new preference. */
25
+ setScheme: (next: ThemeScheme) => void;
26
+ }
@@ -0,0 +1,25 @@
1
+ import { useMemo } from 'react';
2
+ import { useTheme } from './theme-context';
3
+ import { ThemeColors } from './theme.types';
4
+
5
+ /**
6
+ * Build a themed StyleSheet that recomputes only when the active theme flips.
7
+ *
8
+ * Usage:
9
+ * ```ts
10
+ * const makeStyles = (c: ThemeColors) =>
11
+ * StyleSheet.create({ box: { backgroundColor: c.bg_elevation_level_1_normal } });
12
+ *
13
+ * const Component = () => {
14
+ * const styles = useThemedStyles(makeStyles);
15
+ * return <View style={styles.box} />;
16
+ * };
17
+ * ```
18
+ *
19
+ * The factory must be a stable module-scope function (not an inline arrow) so
20
+ * the memo key stays the theme reference, not a new function each render.
21
+ */
22
+ export const useThemedStyles = <T>(factory: (colors: ThemeColors) => T): T => {
23
+ const { colors } = useTheme();
24
+ return useMemo(() => factory(colors), [colors, factory]);
25
+ };