nx-react-native-cli 2.7.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/lib/index.cjs +43 -43
  2. package/package.json +1 -1
  3. package/templates/19.7.0/apps/mobile/android/app/src/main/java/com/appsmobile/MainActivity.kt +5 -1
  4. package/templates/19.7.0/apps/mobile/ios/AppsMobile/AppDelegate.mm +6 -0
  5. package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/AppIcon.appiconset/AppIcon-20@2x.png +0 -0
  6. package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/AppIcon.appiconset/AppIcon-20@2x~ipad.png +0 -0
  7. package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/AppIcon.appiconset/AppIcon-20@3x.png +0 -0
  8. package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/AppIcon.appiconset/AppIcon-20~ipad.png +0 -0
  9. package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/AppIcon.appiconset/AppIcon-29.png +0 -0
  10. package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/AppIcon.appiconset/AppIcon-29@2x.png +0 -0
  11. package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/AppIcon.appiconset/AppIcon-29@2x~ipad.png +0 -0
  12. package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/AppIcon.appiconset/AppIcon-29@3x.png +0 -0
  13. package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/AppIcon.appiconset/AppIcon-29~ipad.png +0 -0
  14. package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/AppIcon.appiconset/AppIcon-40@2x.png +0 -0
  15. package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/AppIcon.appiconset/AppIcon-40@2x~ipad.png +0 -0
  16. package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/AppIcon.appiconset/AppIcon-40@3x.png +0 -0
  17. package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/AppIcon.appiconset/AppIcon-40~ipad.png +0 -0
  18. package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/AppIcon.appiconset/AppIcon-60@2x~car.png +0 -0
  19. package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/AppIcon.appiconset/AppIcon-60@3x~car.png +0 -0
  20. package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/AppIcon.appiconset/AppIcon-83.5@2x~ipad.png +0 -0
  21. package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/AppIcon.appiconset/AppIcon@2x.png +0 -0
  22. package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/AppIcon.appiconset/AppIcon@2x~ipad.png +0 -0
  23. package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/AppIcon.appiconset/AppIcon@3x.png +0 -0
  24. package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/AppIcon.appiconset/AppIcon~ios-marketing.png +0 -0
  25. package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/AppIcon.appiconset/AppIcon~ipad.png +0 -0
  26. package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/AppIcon.appiconset/Contents.json +134 -0
  27. package/templates/19.7.0/apps/mobile/ios/AppsMobile/Images.xcassets/Contents.json +6 -0
  28. package/templates/21.2.2/apps/mobile/android/app/src/main/java/com/mobile/MainActivity.kt +5 -1
  29. package/templates/21.2.2/apps/mobile/ios/Mobile/AppDelegate.mm +6 -0
  30. package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/AppIcon.appiconset/AppIcon-20@2x.png +0 -0
  31. package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/AppIcon.appiconset/AppIcon-20@2x~ipad.png +0 -0
  32. package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/AppIcon.appiconset/AppIcon-20@3x.png +0 -0
  33. package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/AppIcon.appiconset/AppIcon-20~ipad.png +0 -0
  34. package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/AppIcon.appiconset/AppIcon-29.png +0 -0
  35. package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/AppIcon.appiconset/AppIcon-29@2x.png +0 -0
  36. package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/AppIcon.appiconset/AppIcon-29@2x~ipad.png +0 -0
  37. package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/AppIcon.appiconset/AppIcon-29@3x.png +0 -0
  38. package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/AppIcon.appiconset/AppIcon-29~ipad.png +0 -0
  39. package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/AppIcon.appiconset/AppIcon-40@2x.png +0 -0
  40. package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/AppIcon.appiconset/AppIcon-40@2x~ipad.png +0 -0
  41. package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/AppIcon.appiconset/AppIcon-40@3x.png +0 -0
  42. package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/AppIcon.appiconset/AppIcon-40~ipad.png +0 -0
  43. package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/AppIcon.appiconset/AppIcon-60@2x~car.png +0 -0
  44. package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/AppIcon.appiconset/AppIcon-60@3x~car.png +0 -0
  45. package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/AppIcon.appiconset/AppIcon-83.5@2x~ipad.png +0 -0
  46. package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/AppIcon.appiconset/AppIcon@2x.png +0 -0
  47. package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/AppIcon.appiconset/AppIcon@2x~ipad.png +0 -0
  48. package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/AppIcon.appiconset/AppIcon@3x.png +0 -0
  49. package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/AppIcon.appiconset/AppIcon~ios-marketing.png +0 -0
  50. package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/AppIcon.appiconset/AppIcon~ipad.png +0 -0
  51. package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/AppIcon.appiconset/Contents.json +134 -0
  52. package/templates/21.2.2/apps/mobile/ios/Mobile/Images.xcassets/Contents.json +6 -0
  53. package/templates/shared/apps/mobile/android/app/src/main/AndroidManifest.xml +28 -0
  54. package/templates/shared/apps/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +6 -0
  55. package/templates/shared/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  56. package/templates/shared/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png +0 -0
  57. package/templates/shared/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png +0 -0
  58. package/templates/shared/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png +0 -0
  59. package/templates/shared/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  60. package/templates/shared/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png +0 -0
  61. package/templates/shared/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png +0 -0
  62. package/templates/shared/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png +0 -0
  63. package/templates/shared/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  64. package/templates/shared/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png +0 -0
  65. package/templates/shared/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png +0 -0
  66. package/templates/shared/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png +0 -0
  67. package/templates/shared/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  68. package/templates/shared/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png +0 -0
  69. package/templates/shared/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png +0 -0
  70. package/templates/shared/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png +0 -0
  71. package/templates/shared/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  72. package/templates/shared/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png +0 -0
  73. package/templates/shared/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png +0 -0
  74. package/templates/shared/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png +0 -0
  75. package/templates/shared/apps/mobile/android/app/src/main/res/styles.xml +14 -0
  76. package/templates/shared/apps/mobile/run-android.sh +2 -1
  77. package/templates/shared/apps/mobile/scripts/setup-ios-dev-scheme.rb +101 -1
  78. package/templates/shared/apps/mobile/src/app/index.tsx +16 -47
  79. package/templates/shared/apps/mobile/src/app/query-client.ts +40 -0
  80. package/templates/shared/apps/mobile/src/assets/images/logo.png +0 -0
  81. package/templates/shared/apps/mobile/src/components/atoms/AlertManager/alert-manager.component.tsx +134 -0
  82. package/templates/shared/apps/mobile/src/components/atoms/AlertManager/alert-manager.types.ts +18 -0
  83. package/templates/shared/apps/mobile/src/components/atoms/AlertManager/alert.service.ts +27 -0
  84. package/templates/shared/apps/mobile/src/components/atoms/AlertManager/index.ts +3 -0
  85. package/templates/shared/apps/mobile/src/components/atoms/BottomSheet/bottom-sheet.component.tsx +14 -8
  86. package/templates/shared/apps/mobile/src/components/atoms/Button/button.component.tsx +1 -1
  87. package/templates/shared/apps/mobile/src/components/atoms/DateModalInput/date-modal-input.component.tsx +69 -0
  88. package/templates/shared/apps/mobile/src/components/atoms/DateModalInput/index.ts +1 -0
  89. package/templates/shared/apps/mobile/src/components/atoms/DatePicker/date-picker.component.tsx +44 -0
  90. package/templates/shared/apps/mobile/src/components/atoms/DatePicker/index.ts +1 -0
  91. package/templates/shared/apps/mobile/src/components/atoms/DateTextInput/date-text-input.component.tsx +218 -0
  92. package/templates/shared/apps/mobile/src/components/atoms/DateTextInput/index.ts +1 -0
  93. package/templates/shared/apps/mobile/src/components/atoms/Divider/divider-component.tsx +1 -1
  94. package/templates/shared/apps/mobile/src/components/atoms/GradientBackground/gradient-background.component.tsx +45 -0
  95. package/templates/shared/apps/mobile/src/components/atoms/GradientBackground/index.ts +1 -0
  96. package/templates/shared/apps/mobile/src/components/atoms/InputLayout/input-layout.component.tsx +12 -4
  97. package/templates/shared/apps/mobile/src/components/atoms/KeyboardAccessory/keyboard-accessory.component.tsx +6 -3
  98. package/templates/shared/apps/mobile/src/components/atoms/KeyboardAwareScrollView/keyboard-aware-scroll-view.component.tsx +1 -0
  99. package/templates/shared/apps/mobile/src/components/atoms/Modal/modal.component.tsx +2 -0
  100. package/templates/shared/apps/mobile/src/components/atoms/ScreenLoader/screen-loader.component.tsx +6 -1
  101. package/templates/shared/apps/mobile/src/components/atoms/SelectDropdown/index.ts +1 -0
  102. package/templates/shared/apps/mobile/src/components/atoms/SelectDropdown/select-dropdown.component.tsx +223 -0
  103. package/templates/shared/apps/mobile/src/components/atoms/Skeleton/skeleton.component.tsx +1 -1
  104. package/templates/shared/apps/mobile/src/components/atoms/TextInput/bottom-sheet-text-input.component.tsx +4 -3
  105. package/templates/shared/apps/mobile/src/components/atoms/TextInput/text-input.component.tsx +8 -4
  106. package/templates/shared/apps/mobile/src/components/atoms/ThemeManager/index.ts +1 -0
  107. package/templates/shared/apps/mobile/src/components/atoms/ThemeManager/theme-manager.component.tsx +27 -0
  108. package/templates/shared/apps/mobile/src/components/atoms/ToastManager/index.ts +3 -0
  109. package/templates/shared/apps/mobile/src/components/atoms/ToastManager/toast-manager.component.tsx +109 -0
  110. package/templates/shared/apps/mobile/src/components/atoms/ToastManager/toast-manager.types.ts +10 -0
  111. package/templates/shared/apps/mobile/src/components/atoms/ToastManager/toast.service.ts +27 -0
  112. package/templates/shared/apps/mobile/src/components/atoms/Typography/typography.component.tsx +1 -1
  113. package/templates/shared/apps/mobile/src/components/atoms/index.ts +8 -0
  114. package/templates/shared/apps/mobile/src/components/molecules/BackButton/back-button.component.tsx +1 -1
  115. package/templates/shared/apps/mobile/src/components/molecules/ScreenContainer/screen-container.component.tsx +4 -24
  116. package/templates/shared/apps/mobile/src/components/molecules/ScreenHeader/screen-header.component.tsx +2 -2
  117. package/templates/shared/apps/mobile/src/hooks/index.ts +1 -0
  118. package/templates/shared/apps/mobile/src/hooks/usePushNotifications.hook.ts +104 -0
  119. package/templates/shared/apps/mobile/src/hooks/useToggleDarkMode.hook.tsx +24 -2
  120. package/templates/shared/apps/mobile/src/icons/alert-triangle.svg +5 -0
  121. package/templates/shared/apps/mobile/src/icons/check-circle.svg +4 -0
  122. package/templates/shared/apps/mobile/src/icons/chevron-down.svg +1 -0
  123. package/templates/shared/apps/mobile/src/icons/chevron-right.svg +1 -0
  124. package/templates/shared/apps/mobile/src/icons/index.ts +18 -1
  125. package/templates/shared/apps/mobile/src/icons/info.svg +5 -0
  126. package/templates/shared/apps/mobile/src/icons/x-circle.svg +5 -0
  127. package/templates/shared/apps/mobile/src/routes/index.tsx +26 -15
  128. package/templates/shared/apps/mobile/src/screens/LandingScreen/landing.screen.tsx +232 -8
  129. package/templates/shared/apps/mobile/src/stores/local-storage.store.ts +9 -5
  130. package/templates/shared/apps/mobile/src/stores/theme.slice.ts +15 -0
  131. package/templates/shared/apps/mobile/src/stores/user.slice.ts +5 -1
  132. package/templates/shared/apps/mobile/src/tailwind/index.ts +3 -3
  133. package/templates/shared/apps/mobile/tailwind.config.js +14 -0
  134. package/templates/shared/patches/react-native-animatable+1.4.0.patch +71 -0
  135. package/templates/shared/apps/mobile/src/assets/images/.gitkeep +0 -0
@@ -157,6 +157,24 @@ def upsert_info_plist_display_name(app_target, ios_dir, project_name)
157
157
  updated
158
158
  end
159
159
 
160
+ def upsert_info_plist_encryption_flag(app_target, ios_dir, project_name)
161
+ paths = resolve_info_plist_paths(app_target, ios_dir, project_name)
162
+ updated = []
163
+
164
+ paths.each do |path|
165
+ next unless File.exist?(path)
166
+
167
+ plist = Xcodeproj::Plist.read_from_path(path) || {}
168
+ next if plist['ITSAppUsesNonExemptEncryption'] == false
169
+
170
+ plist['ITSAppUsesNonExemptEncryption'] = false
171
+ Xcodeproj::Plist.write_to_path(plist, path)
172
+ updated << "Info.plist ITSAppUsesNonExemptEncryption=false at #{path}"
173
+ end
174
+
175
+ updated
176
+ end
177
+
160
178
  def update_scheme_configuration(doc, action_name, build_configuration)
161
179
  action = REXML::XPath.first(doc, "//#{action_name}")
162
180
  raise "Missing #{action_name} in source scheme." unless action
@@ -301,6 +319,81 @@ def upsert_firebase_environment_script_phase(app_target)
301
319
  phase_status
302
320
  end
303
321
 
322
+ def upsert_podfile_node_require_and_permissions(podfile_path)
323
+ return :missing unless File.exist?(podfile_path)
324
+
325
+ content = File.read(podfile_path)
326
+ updated = content.dup
327
+ changes = []
328
+
329
+ # Step 1: Replace old inline require with node_require function if needed
330
+ unless updated.include?('def node_require(script)')
331
+ old_require_pattern = /# Resolve react_native_pods\.rb with node to allow for hoisting\nrequire Pod::Executable\.execute_command\('node', \['-p',\n\s*'require\.resolve\(\n\s*"react-native\/scripts\/react_native_pods\.rb",\n\s*\{paths: \[process\.argv\[1\]\]\},\n\s*\)', __dir__\]\)\.strip/m
332
+
333
+ node_require_block = <<~RUBY.chomp
334
+ def node_require(script)
335
+ # Resolve script with node to allow for hoisting
336
+ require Pod::Executable.execute_command('node', ['-p',
337
+ "require.resolve(
338
+ '\#{script}',
339
+ {paths: [process.argv[1]]},
340
+ )", __dir__]).strip
341
+ end
342
+
343
+ # Use it to require both react-native's and this package's scripts:
344
+ node_require('react-native/scripts/react_native_pods.rb')
345
+ node_require('react-native-permissions/scripts/setup.rb')
346
+ RUBY
347
+
348
+ if updated.match?(old_require_pattern)
349
+ updated.sub!(old_require_pattern, node_require_block)
350
+ changes << 'node_require function (replaced inline require)'
351
+ end
352
+ end
353
+
354
+ # Step 2: Add setup_permissions after prepare_react_native_project! if not present
355
+ unless updated.include?('setup_permissions')
356
+ permissions_block = <<~RUBY
357
+
358
+ # Uncomment the permissions you need
359
+ setup_permissions([
360
+ # 'AppTrackingTransparency',
361
+ # 'Bluetooth',
362
+ # 'Calendars',
363
+ # 'CalendarsWriteOnly',
364
+ # 'Camera',
365
+ # 'Contacts',
366
+ # 'FaceID',
367
+ # 'LocationAccuracy',
368
+ # 'LocationAlways',
369
+ # 'LocationWhenInUse',
370
+ # 'MediaLibrary',
371
+ # 'Microphone',
372
+ # 'Motion',
373
+ 'Notifications',
374
+ # 'PhotoLibrary',
375
+ # 'PhotoLibraryAddOnly',
376
+ # 'Reminders',
377
+ # 'Siri',
378
+ # 'SpeechRecognition',
379
+ # 'StoreKit',
380
+ ])
381
+ RUBY
382
+
383
+ if updated.include?('prepare_react_native_project!')
384
+ updated.sub!(/prepare_react_native_project!\s*\n/) do |match|
385
+ "#{match}#{permissions_block}"
386
+ end
387
+ changes << 'setup_permissions block'
388
+ end
389
+ end
390
+
391
+ return :unchanged if updated == content
392
+
393
+ File.write(podfile_path, updated)
394
+ changes
395
+ end
396
+
304
397
  ios_dir = File.join(Dir.pwd, 'ios')
305
398
  project_path = Dir.glob(File.join(ios_dir, '*.xcodeproj')).first
306
399
  raise 'Could not find an .xcodeproj under ios/.' unless project_path
@@ -330,6 +423,7 @@ changes.concat(upsert_product_bundle_identifier(app_target, base_bundle_identifi
330
423
  base_product_name = extract_base_product_name(app_target)
331
424
  changes.concat(upsert_product_name(app_target, base_product_name))
332
425
  changes.concat(upsert_info_plist_display_name(app_target, ios_dir, project_name))
426
+ changes.concat(upsert_info_plist_encryption_flag(app_target, ios_dir, project_name))
333
427
  firebase_phase_status = upsert_firebase_environment_script_phase(app_target)
334
428
  changes << "Firebase plist run script #{firebase_phase_status}" unless firebase_phase_status == 'existing'
335
429
 
@@ -359,9 +453,15 @@ formatter.write(scheme_doc.root, output)
359
453
  output << "\n"
360
454
  File.write(dev_scheme_path, output)
361
455
 
362
- podfile_status = upsert_podfile_project_mapping(File.join(ios_dir, 'Podfile'), project_name)
456
+ podfile_path = File.join(ios_dir, 'Podfile')
457
+ podfile_status = upsert_podfile_project_mapping(podfile_path, project_name)
363
458
  changes << 'Podfile project mapping' if podfile_status == :updated
364
459
 
460
+ node_require_status = upsert_podfile_node_require_and_permissions(podfile_path)
461
+ if node_require_status.is_a?(Array)
462
+ node_require_status.each { |change| changes << "Podfile #{change}" }
463
+ end
464
+
365
465
  puts 'Configured iOS build configurations and Dev scheme.'
366
466
  if changes.empty?
367
467
  puts 'No new build configurations were created (already configured).'
@@ -1,69 +1,38 @@
1
- import { createAsyncStoragePersister } from '@tanstack/query-async-storage-persister';
2
- import { QueryClient } from '@tanstack/react-query';
3
- import { AsyncStorage, PersistQueryClientProvider } from '@tanstack/react-query-persist-client';
1
+ import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client';
4
2
  import React from 'react';
5
3
  import { LogBox } from 'react-native';
6
4
  import { GestureHandlerRootView } from 'react-native-gesture-handler';
7
5
  import 'react-native-get-random-values';
8
6
  import { KeyboardProvider } from 'react-native-keyboard-controller';
9
- import { createMMKV } from 'react-native-mmkv';
10
7
  import { SafeAreaProvider } from 'react-native-safe-area-context';
11
- import { useDeviceContext } from 'twrnc';
12
8
 
13
- import { StorageManager } from '@/components';
14
- import CONFIG from '@/config';
9
+ import { persistOptions, queryClient } from './query-client';
10
+
11
+ import { AlertManager, StorageManager, ThemeManager, ToastManager } from '@/components';
15
12
  import ApplicationRoutes from '@/routes';
16
13
  import { tw } from '@/tailwind';
17
14
  import 'react-native-url-polyfill/auto';
18
15
 
19
- LogBox.ignoreLogs(['VirtualizedLists', 'onAnimatedValueUpdate']);
20
-
21
- const CACHE_TIME = 0;
22
- const STALE_TIME = 0;
23
-
24
- const queryClient = new QueryClient({
25
- defaultOptions: {
26
- queries: {
27
- staleTime: STALE_TIME,
28
- },
29
- },
30
- });
31
-
32
- const storage = createMMKV({
33
- encryptionKey: CONFIG.STORAGE_KEY,
34
- id: 'react-query-persist',
35
- });
36
-
37
- export const MmkvStorage: AsyncStorage = {
38
- getItem: (name) => {
39
- const value = storage.getString(name);
40
-
41
- return value ?? null;
42
- },
43
- removeItem: (name) => {
44
- storage.remove(name);
45
- },
46
- setItem: (name, value) => storage.set(name, value),
47
- };
48
-
49
- const persister = createAsyncStoragePersister({
50
- storage: MmkvStorage as AsyncStorage,
51
- });
52
-
53
- const persistOptions = { maxAge: CACHE_TIME, persister };
16
+ LogBox.ignoreLogs([
17
+ 'VirtualizedLists',
18
+ 'onAnimatedValueUpdate',
19
+ 'InteractionManager',
20
+ 'This method is deprecated (as well as all React Native Firebase namespaced API)',
21
+ ]);
54
22
 
55
23
  function Application() {
56
- useDeviceContext(tw, {
57
- initialColorScheme: 'light',
58
- });
59
-
60
24
  return (
61
25
  <GestureHandlerRootView style={tw`flex-1`}>
62
26
  <SafeAreaProvider>
63
27
  <KeyboardProvider navigationBarTranslucent statusBarTranslucent>
64
28
  <PersistQueryClientProvider client={queryClient} persistOptions={persistOptions}>
65
29
  <StorageManager>
66
- <ApplicationRoutes />
30
+ <>
31
+ <ThemeManager />
32
+ <ApplicationRoutes />
33
+ <AlertManager />
34
+ <ToastManager />
35
+ </>
67
36
  </StorageManager>
68
37
  </PersistQueryClientProvider>
69
38
  </KeyboardProvider>
@@ -0,0 +1,40 @@
1
+ import { createAsyncStoragePersister } from '@tanstack/query-async-storage-persister';
2
+ import { QueryClient } from '@tanstack/react-query';
3
+ import { AsyncStorage } from '@tanstack/react-query-persist-client';
4
+ import { createMMKV } from 'react-native-mmkv';
5
+
6
+ import CONFIG from '@/config';
7
+
8
+ const CACHE_TIME = 0;
9
+ const STALE_TIME = 0;
10
+
11
+ export const queryClient = new QueryClient({
12
+ defaultOptions: {
13
+ queries: {
14
+ staleTime: STALE_TIME,
15
+ },
16
+ },
17
+ });
18
+
19
+ const storage = createMMKV({
20
+ encryptionKey: CONFIG.STORAGE_KEY,
21
+ id: 'react-query-persist',
22
+ });
23
+
24
+ export const MmkvStorage: AsyncStorage = {
25
+ getItem: (name) => {
26
+ const value = storage.getString(name);
27
+
28
+ return value ?? null;
29
+ },
30
+ removeItem: (name) => {
31
+ storage.remove(name);
32
+ },
33
+ setItem: (name, value) => storage.set(name, value),
34
+ };
35
+
36
+ const persister = createAsyncStoragePersister({
37
+ storage: MmkvStorage as AsyncStorage,
38
+ });
39
+
40
+ export const persistOptions = { maxAge: CACHE_TIME, persister };
@@ -0,0 +1,134 @@
1
+ import React, { useCallback, useImperativeHandle, useRef, useState } from 'react';
2
+ import { Pressable, View } from 'react-native';
3
+ import { SvgProps } from 'react-native-svg';
4
+
5
+ import { AlertManagerRef, AlertOptions, AlertVariant } from './alert-manager.types';
6
+ import { Alert } from './alert.service';
7
+
8
+ import { Modal } from '@/components/atoms';
9
+ import { Typography } from '@/components/atoms/Typography';
10
+ import { AlertTriangleIcon, CheckCircleIcon, InfoIcon, XCircleIcon } from '@/icons';
11
+ import { colors, tw } from '@/tailwind';
12
+
13
+ type QueuedAlert = AlertOptions & {
14
+ variant: AlertVariant;
15
+ };
16
+
17
+ type VariantConfig = {
18
+ color: string;
19
+ icon: React.FC<SvgProps>;
20
+ };
21
+
22
+ const ICON_SIZE = 24;
23
+
24
+ const VARIANT_CONFIG: Record<AlertVariant, VariantConfig> = {
25
+ error: { color: colors.error, icon: XCircleIcon },
26
+ info: { color: colors.secondary[500], icon: InfoIcon },
27
+ success: { color: colors.success, icon: CheckCircleIcon },
28
+ warning: { color: colors.primary[500], icon: AlertTriangleIcon },
29
+ };
30
+
31
+ export function AlertManager() {
32
+ const [queue, setQueue] = useState<QueuedAlert[]>([]);
33
+ const ref = useRef<AlertManagerRef>(null);
34
+
35
+ const currentAlert = queue[0];
36
+ const isVisible = !!currentAlert;
37
+
38
+ const handleDismiss = useCallback(() => {
39
+ setQueue((prev) => prev.slice(1));
40
+ }, []);
41
+
42
+ const handleActionPress = useCallback(
43
+ (onPress?: () => void) => {
44
+ handleDismiss();
45
+ onPress?.();
46
+ },
47
+ [handleDismiss],
48
+ );
49
+
50
+ useImperativeHandle(ref, () => ({
51
+ show: (variant: AlertVariant, options: AlertOptions) => {
52
+ setQueue((prev) => [...prev, { ...options, variant }]);
53
+ },
54
+ }));
55
+
56
+ React.useEffect(() => {
57
+ Alert.setRef({
58
+ show: (variant: AlertVariant, options: AlertOptions) => {
59
+ setQueue((prev) => [...prev, { ...options, variant }]);
60
+ },
61
+ });
62
+ }, []);
63
+
64
+ if (!currentAlert) {
65
+ return null;
66
+ }
67
+
68
+ const config = VARIANT_CONFIG[currentAlert.variant];
69
+ const IconComponent = config.icon;
70
+ const showIcon = !currentAlert.hideIcon;
71
+ const actions = currentAlert.actions?.length
72
+ ? currentAlert.actions
73
+ : [{ label: 'Ok', variant: 'default' as const }];
74
+
75
+ return (
76
+ <Modal isVisible={isVisible} onBackButtonPress={handleDismiss} onBackdropPress={handleDismiss}>
77
+ <View pointerEvents="box-none" style={tw`flex-1 items-center justify-center`}>
78
+ <View
79
+ style={tw`dark:border-divider dark:bg-sheet mx-8 w-[85%] rounded-2xl border border-gray-200 bg-white p-6`}
80
+ >
81
+ {/* Icon */}
82
+ {showIcon && (
83
+ <View
84
+ style={tw`dark:bg-surface mb-4 h-10 w-10 items-center justify-center rounded-full bg-gray-100`}
85
+ >
86
+ <IconComponent color={config.color} height={ICON_SIZE} width={ICON_SIZE} />
87
+ </View>
88
+ )}
89
+
90
+ {/* Title */}
91
+ <Typography style={tw`dark:text-foreground text-lg font-semibold text-gray-900`}>
92
+ {currentAlert.title}
93
+ </Typography>
94
+
95
+ {/* Message */}
96
+ {currentAlert.message ? (
97
+ <Typography style={tw`dark:text-subtitle mt-4 text-sm leading-relaxed text-gray-500`}>
98
+ {currentAlert.message}
99
+ </Typography>
100
+ ) : null}
101
+
102
+ {/* Action buttons */}
103
+ <View style={tw`mt-6 gap-3`}>
104
+ {actions.map((action) =>
105
+ action.variant === 'cancel' ? (
106
+ <Pressable
107
+ key={action.label}
108
+ style={tw`dark:border-divider dark:bg-surface items-center rounded-xl border border-gray-200 bg-gray-50 py-3.5`}
109
+ onPress={() => handleActionPress(action.onPress)}
110
+ >
111
+ <Typography
112
+ style={tw`dark:text-foreground text-base font-semibold text-gray-700`}
113
+ >
114
+ {action.label}
115
+ </Typography>
116
+ </Pressable>
117
+ ) : (
118
+ <Pressable
119
+ key={action.label}
120
+ style={tw`bg-primary-500 items-center rounded-xl py-3.5`}
121
+ onPress={() => handleActionPress(action.onPress)}
122
+ >
123
+ <Typography style={tw`text-base font-semibold text-white`}>
124
+ {action.label}
125
+ </Typography>
126
+ </Pressable>
127
+ ),
128
+ )}
129
+ </View>
130
+ </View>
131
+ </View>
132
+ </Modal>
133
+ );
134
+ }
@@ -0,0 +1,18 @@
1
+ export type AlertVariant = 'error' | 'info' | 'success' | 'warning';
2
+
3
+ export type AlertAction = {
4
+ label: string;
5
+ onPress?: () => void;
6
+ variant?: 'cancel' | 'default';
7
+ };
8
+
9
+ export type AlertOptions = {
10
+ actions?: AlertAction[];
11
+ hideIcon?: boolean;
12
+ message?: string;
13
+ title: string;
14
+ };
15
+
16
+ export type AlertManagerRef = {
17
+ show: (variant: AlertVariant, options: AlertOptions) => void;
18
+ };
@@ -0,0 +1,27 @@
1
+ import { AlertManagerRef, AlertOptions } from './alert-manager.types';
2
+
3
+ class AlertService {
4
+ private static ref: AlertManagerRef | null = null;
5
+
6
+ static error(options: AlertOptions) {
7
+ AlertService.ref?.show('error', options);
8
+ }
9
+
10
+ static info(options: AlertOptions) {
11
+ AlertService.ref?.show('info', options);
12
+ }
13
+
14
+ static setRef(ref: AlertManagerRef) {
15
+ AlertService.ref = ref;
16
+ }
17
+
18
+ static success(options: AlertOptions) {
19
+ AlertService.ref?.show('success', options);
20
+ }
21
+
22
+ static warning(options: AlertOptions) {
23
+ AlertService.ref?.show('warning', options);
24
+ }
25
+ }
26
+
27
+ export { AlertService as Alert };
@@ -0,0 +1,3 @@
1
+ export * from './alert-manager.component';
2
+ export * from './alert-manager.types';
3
+ export * from './alert.service';
@@ -24,6 +24,7 @@ export type BottomSheetProps = DefaultComponentProps & {
24
24
  enableDynamicSizing?: boolean;
25
25
  enablePanDownToClose?: boolean;
26
26
  handleComponent?: FC<BottomSheetHandleProps> | null;
27
+ hasScrollView?: boolean;
27
28
  onExpand?: () => void;
28
29
  sheetRef: RefObject<BottomSheetModal>;
29
30
  snapPoints?: string[];
@@ -83,6 +84,7 @@ export function BottomSheet(props: BottomSheetProps) {
83
84
  enableDynamicSizing = false,
84
85
  enablePanDownToClose = true,
85
86
  handleComponent = BottomSheetHandle,
87
+ hasScrollView = true,
86
88
  sheetRef,
87
89
  snapPoints = DEFAULT_SNAP_POINTS,
88
90
  style,
@@ -104,11 +106,11 @@ export function BottomSheet(props: BottomSheetProps) {
104
106
  ref={sheetRef}
105
107
  android_keyboardInputMode="adjustResize"
106
108
  backdropComponent={renderBackdrop}
107
- backgroundStyle={[tw`bg-gray-50`, backgroundStyle]}
109
+ backgroundStyle={[tw`dark:bg-sheet bg-white`, backgroundStyle]}
108
110
  enableDynamicSizing={enableDynamicSizing}
109
111
  enablePanDownToClose={enablePanDownToClose}
110
112
  handleComponent={handleComponent}
111
- handleIndicatorStyle={tw`bg-gray-50`}
113
+ handleIndicatorStyle={tw`dark:bg-divider bg-gray-300`}
112
114
  handleStyle={tw`rounded-tl-xl rounded-tr-xl`}
113
115
  keyboardBehavior="interactive"
114
116
  snapPoints={points}
@@ -120,12 +122,16 @@ export function BottomSheet(props: BottomSheetProps) {
120
122
  style,
121
123
  ]}
122
124
  >
123
- <BottomSheetScrollView
124
- contentContainerStyle={contentContainerStyle}
125
- keyboardShouldPersistTaps="handled"
126
- >
127
- {children}
128
- </BottomSheetScrollView>
125
+ {hasScrollView ? (
126
+ <BottomSheetScrollView
127
+ contentContainerStyle={contentContainerStyle}
128
+ keyboardShouldPersistTaps="handled"
129
+ >
130
+ {children}
131
+ </BottomSheetScrollView>
132
+ ) : (
133
+ children
134
+ )}
129
135
  </BottomSheetModal>
130
136
  );
131
137
  }
@@ -64,7 +64,7 @@ export function OutlinedButton(props: Props) {
64
64
  return (
65
65
  <Button
66
66
  {...rest}
67
- buttonStyle={tw`border-primary-500 border-2 bg-white`}
67
+ buttonStyle={tw`border-primary-500 border-2 bg-transparent`}
68
68
  textStyle={tw`text-primary-500`}
69
69
  />
70
70
  );
@@ -0,0 +1,69 @@
1
+ import dayjs from 'dayjs';
2
+ import React, { ReactNode, useState } from 'react';
3
+ import { StyleProp, TouchableOpacity, ViewStyle } from 'react-native';
4
+
5
+ import { DatePicker } from '@/components/atoms/DatePicker';
6
+ import { Typography } from '@/components/atoms/Typography';
7
+ import { defaultInputContainerStyle, defaultInputTextStyle, tw } from '@/tailwind';
8
+
9
+ const DEFAULT_FORMAT = 'MMMM D, YYYY';
10
+
11
+ type Props = {
12
+ error?: string;
13
+ format?: string;
14
+ maximumDate?: Date;
15
+ minimumDate?: Date;
16
+ onChange: (date: Date) => void;
17
+ placeholder?: string;
18
+ renderRight?: (value: Date) => ReactNode;
19
+ value: Date | undefined;
20
+ };
21
+
22
+ export function DateModalInput(props: Props) {
23
+ const {
24
+ error,
25
+ format = DEFAULT_FORMAT,
26
+ maximumDate,
27
+ minimumDate,
28
+ onChange,
29
+ placeholder = 'Select a date',
30
+ renderRight,
31
+ value,
32
+ } = props;
33
+ const [showDatePicker, setShowDatePicker] = useState(false);
34
+
35
+ const containerStyle: StyleProp<ViewStyle> = [
36
+ defaultInputContainerStyle,
37
+ tw`dark:border-divider dark:bg-surface items-center`,
38
+ error && tw`border-red-500`,
39
+ ];
40
+
41
+ return (
42
+ <>
43
+ <TouchableOpacity
44
+ activeOpacity={0.7}
45
+ style={containerStyle}
46
+ onPress={() => setShowDatePicker(true)}
47
+ >
48
+ <Typography
49
+ style={[defaultInputTextStyle, tw`dark:text-foreground`, !value && tw`text-placeholder`]}
50
+ >
51
+ {value ? dayjs(value).format(format) : placeholder}
52
+ </Typography>
53
+ {value && renderRight?.(value)}
54
+ </TouchableOpacity>
55
+ <DatePicker
56
+ date={value || new Date()}
57
+ isVisible={showDatePicker}
58
+ maximumDate={maximumDate}
59
+ minimumDate={minimumDate}
60
+ mode="date"
61
+ onCancel={() => setShowDatePicker(false)}
62
+ onConfirm={(selectedDate) => {
63
+ setShowDatePicker(false);
64
+ onChange(selectedDate);
65
+ }}
66
+ />
67
+ </>
68
+ );
69
+ }
@@ -0,0 +1 @@
1
+ export * from './date-modal-input.component';
@@ -0,0 +1,44 @@
1
+ import React from 'react';
2
+ import { TouchableHighlight } from 'react-native';
3
+ import DateTimePickerModal, {
4
+ ReactNativeModalDateTimePickerProps,
5
+ } from 'react-native-modal-datetime-picker';
6
+
7
+ import { Typography } from '@/components/atoms/Typography';
8
+ import { useLocalStorageStore } from '@/stores';
9
+ import { colors, tw } from '@/tailwind';
10
+
11
+ type Props = Omit<ReactNativeModalDateTimePickerProps, 'onCancel' | 'onConfirm'> & {
12
+ onCancel: () => void;
13
+ onConfirm: (date: Date) => void;
14
+ };
15
+
16
+ export function DatePicker(props: Props) {
17
+ const { onCancel, onConfirm, ...rest } = props;
18
+ const colorScheme = useLocalStorageStore((s) => s.colorScheme);
19
+ const isDark = colorScheme === 'dark';
20
+
21
+ return (
22
+ <DateTimePickerModal
23
+ backdropStyleIOS={tw`bg-black/40`}
24
+ buttonTextColorIOS={colors.primary[500]}
25
+ customCancelButtonIOS={({ onPress }) => (
26
+ <TouchableHighlight
27
+ style={tw`dark:bg-surface mt-2 items-center rounded-2xl bg-white py-4`}
28
+ underlayColor={isDark ? colors.background : colors.gray[100]}
29
+ onPress={onPress}
30
+ >
31
+ <Typography style={tw`dark:text-subtitle text-base font-semibold text-gray-500`}>
32
+ Cancel
33
+ </Typography>
34
+ </TouchableHighlight>
35
+ )}
36
+ isDarkModeEnabled={isDark}
37
+ modalStyleIOS={tw`rounded-2xl pb-4`}
38
+ pickerContainerStyleIOS={tw`dark:bg-sheet items-center justify-center bg-white`}
39
+ onCancel={onCancel}
40
+ onConfirm={onConfirm}
41
+ {...rest}
42
+ />
43
+ );
44
+ }
@@ -0,0 +1 @@
1
+ export * from './date-picker.component';