react-native-inapp-inspector 2.3.18 → 2.3.20

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 (358) hide show
  1. package/android/src/main/java/com/inappinspector/NetworkInspectorModule.kt +57 -0
  2. package/dist/commonjs/analytics.js +1 -24
  3. package/dist/commonjs/bundle.js +1 -16
  4. package/dist/commonjs/capture.d.ts +2 -0
  5. package/dist/commonjs/capture.js +1 -32
  6. package/dist/commonjs/components/AnalyticsDetail.js +92 -623
  7. package/dist/commonjs/components/AnalyticsEventCard.js +54 -350
  8. package/dist/commonjs/components/AnalyticsGraph.js +70 -370
  9. package/dist/commonjs/components/AnimatedEntrance.js +1 -11
  10. package/dist/commonjs/components/AppHeaderLogo.js +7 -134
  11. package/dist/commonjs/components/BrandCircleIcon.js +73 -118
  12. package/dist/commonjs/components/BrandSquareIcon.js +63 -108
  13. package/dist/commonjs/components/CodeSnippet.js +48 -623
  14. package/dist/commonjs/components/ConsoleLogCard.js +63 -537
  15. package/dist/commonjs/components/CopyButton.js +3 -89
  16. package/dist/commonjs/components/DiffViewer.js +19 -83
  17. package/dist/commonjs/components/DomainHeader.js +27 -127
  18. package/dist/commonjs/components/EmptyState.d.ts +1 -0
  19. package/dist/commonjs/components/EmptyState.js +10 -87
  20. package/dist/commonjs/components/EndOfListFooter.js +7 -81
  21. package/dist/commonjs/components/ErrorBoundary.js +72 -566
  22. package/dist/commonjs/components/HeadersSection.js +31 -110
  23. package/dist/commonjs/components/HighlightText.js +17 -58
  24. package/dist/commonjs/components/Inspector/AboutModal.d.ts +6 -0
  25. package/dist/commonjs/components/Inspector/AboutModal.js +351 -0
  26. package/dist/commonjs/components/Inspector/AnalyticsFilterModal.js +98 -797
  27. package/dist/commonjs/components/Inspector/AnalyticsTab.js +284 -930
  28. package/dist/commonjs/components/Inspector/BundleTab.js +551 -2918
  29. package/dist/commonjs/components/Inspector/ConsoleTab.js +79 -492
  30. package/dist/commonjs/components/Inspector/CrashDetail.js +123 -672
  31. package/dist/commonjs/components/Inspector/CrashFilterModal.js +66 -498
  32. package/dist/commonjs/components/Inspector/CrashTab.js +98 -670
  33. package/dist/commonjs/components/Inspector/DebuggingTab.js +64 -871
  34. package/dist/commonjs/components/Inspector/DeviceInfoTab.js +143 -907
  35. package/dist/commonjs/components/Inspector/FabLauncher.js +85 -54
  36. package/dist/commonjs/components/Inspector/FeatureUnderDevNotice.js +6 -99
  37. package/dist/commonjs/components/Inspector/FeedbackModal.d.ts +9 -0
  38. package/dist/commonjs/components/Inspector/FeedbackModal.js +167 -521
  39. package/dist/commonjs/components/Inspector/InspectorContext.js +1 -21
  40. package/dist/commonjs/components/Inspector/InspectorHeader.js +219 -1093
  41. package/dist/commonjs/components/Inspector/LogDetail.js +168 -994
  42. package/dist/commonjs/components/Inspector/MainScreen.js +60 -332
  43. package/dist/commonjs/components/Inspector/MediaGalleryTab.js +136 -278
  44. package/dist/commonjs/components/Inspector/MediaPreviewModal.js +134 -361
  45. package/dist/commonjs/components/Inspector/NavigationTracker.js +1 -12
  46. package/dist/commonjs/components/Inspector/NetworkDetail.js +96 -432
  47. package/dist/commonjs/components/Inspector/NetworkFilterModal.js +60 -576
  48. package/dist/commonjs/components/Inspector/NetworkTab.js +84 -514
  49. package/dist/commonjs/components/Inspector/NpmStarPrompt.js +17 -433
  50. package/dist/commonjs/components/Inspector/NpmUpdateToast.js +25 -298
  51. package/dist/commonjs/components/Inspector/PerformanceTab.js +182 -1397
  52. package/dist/commonjs/components/Inspector/ReduxDetail.js +188 -1089
  53. package/dist/commonjs/components/Inspector/ReduxTab.js +85 -701
  54. package/dist/commonjs/components/Inspector/SettingsPanel.js +431 -3431
  55. package/dist/commonjs/components/Inspector/SkeletonPlaceholder.js +11 -180
  56. package/dist/commonjs/components/Inspector/StorageTab.js +93 -826
  57. package/dist/commonjs/components/Inspector/TabBar.js +47 -183
  58. package/dist/commonjs/components/Inspector/UpdateAvailableModal.js +34 -408
  59. package/dist/commonjs/components/JsonViewer.js +168 -380
  60. package/dist/commonjs/components/LogCard.js +57 -567
  61. package/dist/commonjs/components/LogSyntaxHighlighter.js +11 -130
  62. package/dist/commonjs/components/MetaAccordion.js +69 -238
  63. package/dist/commonjs/components/MiniBarChart.js +3 -56
  64. package/dist/commonjs/components/MiniLineChart.js +3 -58
  65. package/dist/commonjs/components/NetworkIcons.d.ts +7 -0
  66. package/dist/commonjs/components/NetworkIcons.js +479 -997
  67. package/dist/commonjs/components/QRCodeView.js +5 -79
  68. package/dist/commonjs/components/SectionHeader.js +15 -37
  69. package/dist/commonjs/components/SegmentedTabs.js +7 -61
  70. package/dist/commonjs/components/SkeletonPlaceholder.js +11 -180
  71. package/dist/commonjs/components/Slider.js +15 -243
  72. package/dist/commonjs/components/SourcePageCard.js +30 -89
  73. package/dist/commonjs/components/Toast.js +5 -147
  74. package/dist/commonjs/components/TouchableScale.js +3 -24
  75. package/dist/commonjs/components/TreeNode.js +55 -130
  76. package/dist/commonjs/console.js +1 -16
  77. package/dist/commonjs/constants/index.js +1 -32
  78. package/dist/commonjs/constants/telemetryConfig.js +1 -6
  79. package/dist/commonjs/constants/version.d.ts +1 -1
  80. package/dist/commonjs/constants/version.js +1 -4
  81. package/dist/commonjs/crash.js +1 -23
  82. package/dist/commonjs/customHooks/analyticsLogger.js +1 -250
  83. package/dist/commonjs/customHooks/bundleAnalyzer.js +1 -957
  84. package/dist/commonjs/customHooks/consoleLogger.js +6 -445
  85. package/dist/commonjs/customHooks/crashHandler.js +7 -656
  86. package/dist/commonjs/customHooks/logFilters.js +1 -23
  87. package/dist/commonjs/customHooks/networkLogger.js +2 -536
  88. package/dist/commonjs/customHooks/performanceTracker.js +63 -750
  89. package/dist/commonjs/customHooks/reduxLogger.js +2 -313
  90. package/dist/commonjs/customHooks/storageInspector.js +1 -435
  91. package/dist/commonjs/customHooks/useAccordion.js +1 -18
  92. package/dist/commonjs/decorators/index.js +1 -100
  93. package/dist/commonjs/helpers/gaAnalyticsRegistry.js +1 -148
  94. package/dist/commonjs/helpers/index.js +8 -921
  95. package/dist/commonjs/helpers/memoryManager.js +1 -99
  96. package/dist/commonjs/helpers/qrGenerator.js +1 -367
  97. package/dist/commonjs/helpers/remoteConfig.js +1 -165
  98. package/dist/commonjs/helpers/searchQueryParser.js +1 -62
  99. package/dist/commonjs/helpers/settingsStore.js +1 -144
  100. package/dist/commonjs/helpers/telemetry.js +1 -501
  101. package/dist/commonjs/helpers/toast.js +1 -20
  102. package/dist/commonjs/i18n/index.js +1 -90
  103. package/dist/commonjs/i18n/locales/en.json +1 -1122
  104. package/dist/commonjs/index.js +5 -2153
  105. package/dist/commonjs/metro.js +6 -468
  106. package/dist/commonjs/native/NativeInspector.d.ts +3 -0
  107. package/dist/commonjs/native/NativeInspector.js +1 -402
  108. package/dist/commonjs/native/NativeNetworkInspector.d.ts +1 -0
  109. package/dist/commonjs/native/NativeNetworkInspector.js +1 -4
  110. package/dist/commonjs/network.js +1 -13
  111. package/dist/commonjs/performance.js +1 -29
  112. package/dist/commonjs/redux.js +1 -20
  113. package/dist/commonjs/storage.js +1 -14
  114. package/dist/commonjs/styles/AppColors.js +1 -423
  115. package/dist/commonjs/styles/AppFonts.js +1 -17
  116. package/dist/commonjs/styles/common.js +1 -190
  117. package/dist/commonjs/styles/index.d.ts +6 -0
  118. package/dist/commonjs/styles/index.js +1 -1589
  119. package/dist/commonjs/types/enums.d.ts +2 -1
  120. package/dist/commonjs/types/enums.js +1 -150
  121. package/dist/commonjs/types/index.js +1 -18
  122. package/dist/commonjs/types/interfaces.d.ts +7 -0
  123. package/dist/commonjs/types/interfaces.js +1 -2
  124. package/dist/esm/analytics.js +1 -2
  125. package/dist/esm/bundle.js +1 -2
  126. package/dist/esm/capture.d.ts +2 -0
  127. package/dist/esm/capture.js +1 -29
  128. package/dist/esm/components/AnalyticsDetail.js +171 -664
  129. package/dist/esm/components/AnalyticsEventCard.js +77 -335
  130. package/dist/esm/components/AnalyticsGraph.js +95 -357
  131. package/dist/esm/components/AnimatedEntrance.js +1 -6
  132. package/dist/esm/components/AppHeaderLogo.js +7 -94
  133. package/dist/esm/components/BrandCircleIcon.js +114 -119
  134. package/dist/esm/components/BrandSquareIcon.js +73 -78
  135. package/dist/esm/components/CodeSnippet.js +58 -595
  136. package/dist/esm/components/ConsoleLogCard.js +91 -526
  137. package/dist/esm/components/CopyButton.js +3 -51
  138. package/dist/esm/components/DiffViewer.js +22 -48
  139. package/dist/esm/components/DomainHeader.js +43 -138
  140. package/dist/esm/components/EmptyState.d.ts +1 -0
  141. package/dist/esm/components/EmptyState.js +17 -56
  142. package/dist/esm/components/EndOfListFooter.js +15 -83
  143. package/dist/esm/components/ErrorBoundary.js +125 -582
  144. package/dist/esm/components/HeadersSection.js +52 -93
  145. package/dist/esm/components/HighlightText.js +17 -53
  146. package/dist/esm/components/Inspector/AboutModal.d.ts +6 -0
  147. package/dist/esm/components/Inspector/AboutModal.js +351 -0
  148. package/dist/esm/components/Inspector/AnalyticsFilterModal.js +225 -886
  149. package/dist/esm/components/Inspector/AnalyticsTab.js +296 -904
  150. package/dist/esm/components/Inspector/BundleTab.js +1175 -3502
  151. package/dist/esm/components/Inspector/ConsoleTab.js +113 -488
  152. package/dist/esm/components/Inspector/CrashDetail.js +236 -747
  153. package/dist/esm/components/Inspector/CrashFilterModal.js +149 -541
  154. package/dist/esm/components/Inspector/CrashTab.js +177 -711
  155. package/dist/esm/components/Inspector/DebuggingTab.js +156 -923
  156. package/dist/esm/components/Inspector/DeviceInfoTab.js +199 -924
  157. package/dist/esm/components/Inspector/FabLauncher.js +85 -49
  158. package/dist/esm/components/Inspector/FeatureUnderDevNotice.js +14 -70
  159. package/dist/esm/components/Inspector/FeedbackModal.d.ts +9 -0
  160. package/dist/esm/components/Inspector/FeedbackModal.js +182 -499
  161. package/dist/esm/components/Inspector/InspectorContext.js +1 -12
  162. package/dist/esm/components/Inspector/InspectorHeader.js +309 -1145
  163. package/dist/esm/components/Inspector/LogDetail.js +235 -1023
  164. package/dist/esm/components/Inspector/MainScreen.js +86 -320
  165. package/dist/esm/components/Inspector/MediaGalleryTab.js +157 -259
  166. package/dist/esm/components/Inspector/MediaPreviewModal.js +153 -340
  167. package/dist/esm/components/Inspector/NavigationTracker.js +1 -10
  168. package/dist/esm/components/Inspector/NetworkDetail.js +141 -472
  169. package/dist/esm/components/Inspector/NetworkFilterModal.js +122 -597
  170. package/dist/esm/components/Inspector/NetworkTab.js +125 -517
  171. package/dist/esm/components/Inspector/NpmStarPrompt.js +58 -434
  172. package/dist/esm/components/Inspector/NpmUpdateToast.js +45 -281
  173. package/dist/esm/components/Inspector/PerformanceTab.js +393 -1570
  174. package/dist/esm/components/Inspector/ReduxDetail.js +307 -1170
  175. package/dist/esm/components/Inspector/ReduxTab.js +144 -722
  176. package/dist/esm/components/Inspector/SettingsPanel.js +647 -3609
  177. package/dist/esm/components/Inspector/SkeletonPlaceholder.js +30 -163
  178. package/dist/esm/components/Inspector/StorageTab.js +172 -866
  179. package/dist/esm/components/Inspector/TabBar.js +52 -183
  180. package/dist/esm/components/Inspector/UpdateAvailableModal.js +79 -416
  181. package/dist/esm/components/JsonViewer.js +179 -353
  182. package/dist/esm/components/LogCard.js +93 -565
  183. package/dist/esm/components/LogSyntaxHighlighter.js +11 -123
  184. package/dist/esm/components/MetaAccordion.js +136 -267
  185. package/dist/esm/components/MiniBarChart.js +3 -18
  186. package/dist/esm/components/MiniLineChart.js +3 -20
  187. package/dist/esm/components/NetworkIcons.d.ts +7 -0
  188. package/dist/esm/components/NetworkIcons.js +481 -817
  189. package/dist/esm/components/QRCodeView.js +8 -45
  190. package/dist/esm/components/SectionHeader.js +22 -39
  191. package/dist/esm/components/SegmentedTabs.js +8 -57
  192. package/dist/esm/components/SkeletonPlaceholder.js +30 -163
  193. package/dist/esm/components/Slider.js +24 -213
  194. package/dist/esm/components/SourcePageCard.js +65 -86
  195. package/dist/esm/components/Toast.js +10 -117
  196. package/dist/esm/components/TouchableScale.js +3 -19
  197. package/dist/esm/components/TreeNode.js +58 -95
  198. package/dist/esm/console.js +1 -2
  199. package/dist/esm/constants/index.js +1 -28
  200. package/dist/esm/constants/telemetryConfig.js +1 -3
  201. package/dist/esm/constants/version.d.ts +1 -1
  202. package/dist/esm/constants/version.js +1 -1
  203. package/dist/esm/crash.js +1 -2
  204. package/dist/esm/customHooks/analyticsLogger.js +1 -230
  205. package/dist/esm/customHooks/bundleAnalyzer.js +1 -944
  206. package/dist/esm/customHooks/consoleLogger.js +6 -432
  207. package/dist/esm/customHooks/crashHandler.js +7 -630
  208. package/dist/esm/customHooks/logFilters.js +1 -20
  209. package/dist/esm/customHooks/networkLogger.js +2 -519
  210. package/dist/esm/customHooks/performanceTracker.js +63 -723
  211. package/dist/esm/customHooks/reduxLogger.js +2 -293
  212. package/dist/esm/customHooks/storageInspector.js +1 -418
  213. package/dist/esm/customHooks/useAccordion.js +1 -16
  214. package/dist/esm/decorators/index.js +1 -95
  215. package/dist/esm/helpers/gaAnalyticsRegistry.js +1 -143
  216. package/dist/esm/helpers/index.js +8 -861
  217. package/dist/esm/helpers/memoryManager.js +1 -91
  218. package/dist/esm/helpers/qrGenerator.js +1 -364
  219. package/dist/esm/helpers/remoteConfig.js +1 -160
  220. package/dist/esm/helpers/searchQueryParser.js +1 -59
  221. package/dist/esm/helpers/settingsStore.js +1 -134
  222. package/dist/esm/helpers/telemetry.js +1 -489
  223. package/dist/esm/helpers/toast.js +1 -15
  224. package/dist/esm/i18n/index.js +1 -78
  225. package/dist/esm/i18n/locales/en.json +1 -1122
  226. package/dist/esm/index.js +7 -1967
  227. package/dist/esm/metro.js +6 -459
  228. package/dist/esm/native/NativeInspector.d.ts +3 -0
  229. package/dist/esm/native/NativeInspector.js +1 -370
  230. package/dist/esm/native/NativeNetworkInspector.d.ts +1 -0
  231. package/dist/esm/native/NativeNetworkInspector.js +1 -2
  232. package/dist/esm/network.js +1 -1
  233. package/dist/esm/performance.js +1 -2
  234. package/dist/esm/redux.js +1 -1
  235. package/dist/esm/storage.js +1 -1
  236. package/dist/esm/styles/AppColors.js +1 -417
  237. package/dist/esm/styles/AppFonts.js +1 -13
  238. package/dist/esm/styles/common.js +1 -186
  239. package/dist/esm/styles/index.d.ts +6 -0
  240. package/dist/esm/styles/index.js +1 -1551
  241. package/dist/esm/types/enums.d.ts +2 -1
  242. package/dist/esm/types/enums.js +1 -147
  243. package/dist/esm/types/index.js +1 -2
  244. package/dist/esm/types/interfaces.d.ts +7 -0
  245. package/dist/esm/types/interfaces.js +1 -1
  246. package/ios/NetworkInspectorModule.mm +253 -136
  247. package/package.json +5 -3
  248. package/react-native-inapp-inspector.podspec +1 -1
  249. package/src/native/NativeInspector.ts +17 -0
  250. package/src/native/NativeNetworkInspector.ts +1 -0
  251. package/src/analytics.ts +0 -35
  252. package/src/bundle.ts +0 -25
  253. package/src/capture.ts +0 -101
  254. package/src/components/AnalyticsDetail.tsx +0 -870
  255. package/src/components/AnalyticsEventCard.tsx +0 -444
  256. package/src/components/AnalyticsGraph.tsx +0 -583
  257. package/src/components/AnimatedEntrance.tsx +0 -20
  258. package/src/components/AppHeaderLogo.tsx +0 -143
  259. package/src/components/BrandCircleIcon.tsx +0 -144
  260. package/src/components/BrandSquareIcon.tsx +0 -184
  261. package/src/components/CodeSnippet.tsx +0 -725
  262. package/src/components/ConsoleLogCard.tsx +0 -679
  263. package/src/components/CopyButton.tsx +0 -87
  264. package/src/components/DiffViewer.tsx +0 -82
  265. package/src/components/DomainHeader.tsx +0 -224
  266. package/src/components/EmptyState.tsx +0 -95
  267. package/src/components/EndOfListFooter.tsx +0 -100
  268. package/src/components/ErrorBoundary.tsx +0 -688
  269. package/src/components/HeadersSection.tsx +0 -172
  270. package/src/components/HighlightText.tsx +0 -100
  271. package/src/components/Inspector/AnalyticsFilterModal.tsx +0 -1250
  272. package/src/components/Inspector/AnalyticsTab.tsx +0 -1362
  273. package/src/components/Inspector/BundleTab.tsx +0 -4768
  274. package/src/components/Inspector/ConsoleTab.tsx +0 -726
  275. package/src/components/Inspector/CrashDetail.tsx +0 -997
  276. package/src/components/Inspector/CrashFilterModal.tsx +0 -721
  277. package/src/components/Inspector/CrashTab.tsx +0 -933
  278. package/src/components/Inspector/DebuggingTab.tsx +0 -1103
  279. package/src/components/Inspector/DeviceInfoTab.tsx +0 -1397
  280. package/src/components/Inspector/FabLauncher.tsx +0 -80
  281. package/src/components/Inspector/FeatureUnderDevNotice.tsx +0 -94
  282. package/src/components/Inspector/FeedbackModal.tsx +0 -626
  283. package/src/components/Inspector/InspectorContext.tsx +0 -28
  284. package/src/components/Inspector/InspectorHeader.tsx +0 -1545
  285. package/src/components/Inspector/LogDetail.tsx +0 -1431
  286. package/src/components/Inspector/MainScreen.tsx +0 -459
  287. package/src/components/Inspector/MediaGalleryTab.tsx +0 -355
  288. package/src/components/Inspector/MediaPreviewModal.tsx +0 -435
  289. package/src/components/Inspector/NavigationTracker.tsx +0 -13
  290. package/src/components/Inspector/NetworkDetail.tsx +0 -795
  291. package/src/components/Inspector/NetworkFilterModal.tsx +0 -730
  292. package/src/components/Inspector/NetworkTab.tsx +0 -820
  293. package/src/components/Inspector/NpmStarPrompt.tsx +0 -532
  294. package/src/components/Inspector/NpmUpdateToast.tsx +0 -387
  295. package/src/components/Inspector/PerformanceTab.tsx +0 -1967
  296. package/src/components/Inspector/ReduxDetail.tsx +0 -1651
  297. package/src/components/Inspector/ReduxTab.tsx +0 -987
  298. package/src/components/Inspector/SettingsPanel.tsx +0 -4580
  299. package/src/components/Inspector/SkeletonPlaceholder.tsx +0 -206
  300. package/src/components/Inspector/StorageTab.tsx +0 -1159
  301. package/src/components/Inspector/TabBar.tsx +0 -263
  302. package/src/components/Inspector/UpdateAvailableModal.tsx +0 -555
  303. package/src/components/JsonViewer.tsx +0 -547
  304. package/src/components/LogCard.tsx +0 -694
  305. package/src/components/LogSyntaxHighlighter.tsx +0 -178
  306. package/src/components/MetaAccordion.tsx +0 -355
  307. package/src/components/MiniBarChart.tsx +0 -42
  308. package/src/components/MiniLineChart.tsx +0 -33
  309. package/src/components/NetworkIcons.tsx +0 -2599
  310. package/src/components/QRCodeView.tsx +0 -75
  311. package/src/components/SectionHeader.tsx +0 -101
  312. package/src/components/SegmentedTabs.tsx +0 -83
  313. package/src/components/SkeletonPlaceholder.tsx +0 -206
  314. package/src/components/Slider.tsx +0 -300
  315. package/src/components/SourcePageCard.tsx +0 -150
  316. package/src/components/Toast.tsx +0 -131
  317. package/src/components/TouchableScale.tsx +0 -61
  318. package/src/components/TreeNode.tsx +0 -171
  319. package/src/console.ts +0 -24
  320. package/src/constants/index.ts +0 -41
  321. package/src/constants/telemetryConfig.ts +0 -4
  322. package/src/constants/version.ts +0 -3
  323. package/src/crash.ts +0 -32
  324. package/src/customHooks/analyticsLogger.ts +0 -336
  325. package/src/customHooks/bundleAnalyzer.ts +0 -1256
  326. package/src/customHooks/consoleLogger.ts +0 -530
  327. package/src/customHooks/crashHandler.ts +0 -944
  328. package/src/customHooks/logFilters.ts +0 -32
  329. package/src/customHooks/networkLogger.ts +0 -624
  330. package/src/customHooks/performanceTracker.ts +0 -1014
  331. package/src/customHooks/reduxLogger.ts +0 -406
  332. package/src/customHooks/storageInspector.ts +0 -513
  333. package/src/customHooks/useAccordion.tsx +0 -27
  334. package/src/decorators/index.ts +0 -184
  335. package/src/helpers/gaAnalyticsRegistry.ts +0 -204
  336. package/src/helpers/index.ts +0 -1092
  337. package/src/helpers/memoryManager.ts +0 -138
  338. package/src/helpers/qrGenerator.ts +0 -430
  339. package/src/helpers/remoteConfig.ts +0 -212
  340. package/src/helpers/searchQueryParser.ts +0 -84
  341. package/src/helpers/settingsStore.ts +0 -171
  342. package/src/helpers/telemetry.ts +0 -568
  343. package/src/helpers/toast.ts +0 -17
  344. package/src/i18n/index.ts +0 -104
  345. package/src/i18n/locales/en.json +0 -1125
  346. package/src/index.tsx +0 -2635
  347. package/src/metro.ts +0 -598
  348. package/src/network.ts +0 -22
  349. package/src/performance.ts +0 -38
  350. package/src/redux.ts +0 -23
  351. package/src/storage.ts +0 -18
  352. package/src/styles/AppColors.ts +0 -455
  353. package/src/styles/AppFonts.ts +0 -26
  354. package/src/styles/common.ts +0 -209
  355. package/src/styles/index.ts +0 -1606
  356. package/src/types/enums.ts +0 -208
  357. package/src/types/index.ts +0 -34
  358. package/src/types/interfaces.ts +0 -530
@@ -1,4768 +0,0 @@
1
- import React, {useState, useEffect, useMemo, useCallback} from 'react';
2
- import {
3
- ActivityIndicator,
4
- Image,
5
- Linking,
6
- Platform,
7
- Pressable,
8
- ScrollView,
9
- Share,
10
- StyleSheet,
11
- Text,
12
- TextInput,
13
- View,
14
- } from 'react-native';
15
- import {useTranslation, t} from '../../i18n';
16
- import TouchableScale from '../TouchableScale';
17
- import CopyButton from '../CopyButton';
18
- import HighlightText from '../HighlightText';
19
- import SegmentedTabs from '../SegmentedTabs';
20
- import AnimatedEntrance from '../AnimatedEntrance';
21
- import EndOfListFooter from '../EndOfListFooter';
22
- import FeatureUnderDevNotice from './FeatureUnderDevNotice';
23
- import {AppColors} from '../../styles/AppColors';
24
- import {AppFonts} from '../../styles/AppFonts';
25
- import styles from '../../styles';
26
- import {copyToClipboard} from '../../helpers';
27
- import {
28
- PackageIcon,
29
- SearchIcon,
30
- ClearIcon,
31
- CheckIcon,
32
- CircleAlertIcon,
33
- LayersIcon,
34
- TimelineIcon,
35
- LiveStateIcon,
36
- StorageIcon,
37
- MetadataIcon,
38
- FolderIcon,
39
- FolderOpenIcon,
40
- TrashIcon,
41
- LightbulbIcon,
42
- SparkleIcon,
43
- ClockIcon,
44
- BoltIcon,
45
- BanIcon,
46
- RefreshCcwIcon,
47
- ExternalLinkIcon,
48
- AppleIcon,
49
- AndroidIcon,
50
- DownloadIcon,
51
- ChevronIcon,
52
- } from '../NetworkIcons';
53
-
54
- import Svg, {
55
- Path,
56
- Rect,
57
- Circle,
58
- Ellipse,
59
- G,
60
- Defs,
61
- LinearGradient,
62
- Stop,
63
- } from 'react-native-svg';
64
- import {
65
- analyzeHostAppBundle,
66
- getCachedBundleAnalysis,
67
- getInitialBundleAnalysis,
68
- getHostScriptURL,
69
- HostBundleAnalysisResult,
70
- } from '../../customHooks/bundleAnalyzer';
71
-
72
- export type BundleSubTab =
73
- | 'overview'
74
- | 'production'
75
- | 'files'
76
- | 'packages'
77
- | 'media'
78
- | 'optimizer';
79
-
80
- export type FileTypeCategory =
81
- | 'image'
82
- | 'typescript'
83
- | 'javascript'
84
- | 'font'
85
- | 'json';
86
-
87
- export interface BundleFileItem {
88
- id: string;
89
- name: string;
90
- path: string;
91
- ext: string;
92
- category: FileTypeCategory;
93
- sizeKb: number;
94
- meta: string;
95
- color: string;
96
- status?: 'optimal' | 'warning' | 'info';
97
- advice?: string;
98
- previewUri?: string;
99
- isConsumed?: boolean;
100
- }
101
-
102
- export interface BundlePackageItem {
103
- id: string;
104
- name: string;
105
- version: string;
106
- sizeKb: number;
107
- percentage: number;
108
- type: 'direct' | 'peer' | 'transitive';
109
- category: 'core' | 'navigation' | 'network' | 'ui' | 'utils';
110
- color: string;
111
- description: string;
112
- logoUri?: string;
113
- latestVersion?: string;
114
- isDeprecated?: boolean;
115
- deprecationReason?: string;
116
- lastActive?: string;
117
- npmUrl?: string;
118
- }
119
-
120
- // ─── High-Fidelity Vector Package & NPM Logos ────────────────────────────────
121
-
122
- const PackageLogoRenderer: React.FC<{
123
- name: string;
124
- color?: string;
125
- size?: number;
126
- }> = ({name, size = 28}) => {
127
- const lowerName = name.toLowerCase();
128
-
129
- // 1. React & React Native (Official Cyan Atom Orbital)
130
- if (
131
- lowerName === 'react' ||
132
- (lowerName.includes('react-native') &&
133
- !lowerName.includes('svg') &&
134
- !lowerName.includes('screens') &&
135
- !lowerName.includes('gradient'))
136
- ) {
137
- return (
138
- <View
139
- style={[
140
- bundleStyles.pkgLogoWrap,
141
- {width: size, height: size, backgroundColor: AppColors.npmDark},
142
- ]}>
143
- <Svg
144
- width={size - 6}
145
- height={size - 6}
146
- viewBox="0 0 100 100"
147
- fill="none">
148
- <Circle cx="50" cy="50" r="9" fill={AppColors.reactCyan} />
149
- <Ellipse
150
- cx="50"
151
- cy="50"
152
- rx="42"
153
- ry="16"
154
- stroke={AppColors.reactCyan}
155
- strokeWidth="6"
156
- />
157
- <Ellipse
158
- cx="50"
159
- cy="50"
160
- rx="42"
161
- ry="16"
162
- stroke={AppColors.reactCyan}
163
- strokeWidth="6"
164
- transform="rotate(60 50 50)"
165
- />
166
- <Ellipse
167
- cx="50"
168
- cy="50"
169
- rx="42"
170
- ry="16"
171
- stroke={AppColors.reactCyan}
172
- strokeWidth="6"
173
- transform="rotate(120 50 50)"
174
- />
175
- </Svg>
176
- </View>
177
- );
178
- }
179
-
180
- // 2. React Navigation (Official Violet/Cyan Compass)
181
- if (lowerName.includes('navigation')) {
182
- return (
183
- <View
184
- style={[
185
- bundleStyles.pkgLogoWrap,
186
- {width: size, height: size, backgroundColor: AppColors.hermesPurple},
187
- ]}>
188
- <Svg
189
- width={size - 8}
190
- height={size - 8}
191
- viewBox="0 0 100 100"
192
- fill="none">
193
- <Circle
194
- cx="50"
195
- cy="50"
196
- r="38"
197
- stroke={AppColors.white}
198
- strokeWidth="8"
199
- />
200
- <Path d="M50 20 L68 50 L50 80 L32 50 Z" fill={AppColors.reactCyan} />
201
- <Circle cx="50" cy="50" r="6" fill={AppColors.white} />
202
- </Svg>
203
- </View>
204
- );
205
- }
206
-
207
- // 3. React Native SVG (Bezier Path Vector Art)
208
- if (lowerName.includes('svg')) {
209
- return (
210
- <View
211
- style={[
212
- bundleStyles.pkgLogoWrap,
213
- {width: size, height: size, backgroundColor: AppColors.pink400},
214
- ]}>
215
- <Svg
216
- width={size - 8}
217
- height={size - 8}
218
- viewBox="0 0 100 100"
219
- fill="none">
220
- <Path
221
- d="M20 75 C35 25, 65 25, 80 75"
222
- stroke={AppColors.white}
223
- strokeWidth="10"
224
- strokeLinecap="round"
225
- />
226
- <Circle cx="20" cy="75" r="8" fill={AppColors.white} />
227
- <Circle cx="80" cy="75" r="8" fill={AppColors.white} />
228
- <Circle
229
- cx="50"
230
- cy="38"
231
- r="7"
232
- fill="#FCE7F3"
233
- stroke={AppColors.white}
234
- strokeWidth="3"
235
- />
236
- </Svg>
237
- </View>
238
- );
239
- }
240
-
241
- // 4. Axios (Official Indigo & Cyan Network Adapter)
242
- if (lowerName.includes('axios')) {
243
- return (
244
- <View
245
- style={[
246
- bundleStyles.pkgLogoWrap,
247
- {width: size, height: size, backgroundColor: AppColors.expoViolet},
248
- ]}>
249
- <Svg
250
- width={size - 6}
251
- height={size - 6}
252
- viewBox="0 0 100 100"
253
- fill="none">
254
- <Path
255
- d="M22 75 L50 20 L78 75 M33 58 L67 58"
256
- stroke={AppColors.white}
257
- strokeWidth="12"
258
- strokeLinecap="round"
259
- strokeLinejoin="round"
260
- />
261
- <Circle cx="50" cy="20" r="7" fill={AppColors.reactCyan} />
262
- </Svg>
263
- </View>
264
- );
265
- }
266
-
267
- // 5. React Native Screens (Software Mansion Deep Indigo Stack)
268
- if (lowerName.includes('screens') || lowerName.includes('software-mansion')) {
269
- return (
270
- <View
271
- style={[
272
- bundleStyles.pkgLogoWrap,
273
- {
274
- width: size,
275
- height: size,
276
- backgroundColor: AppColors.reanimatedNavy,
277
- },
278
- ]}>
279
- <Svg
280
- width={size - 8}
281
- height={size - 8}
282
- viewBox="0 0 100 100"
283
- fill="none">
284
- <Path d="M50 15 L88 35 L50 55 L12 35 Z" fill={AppColors.sky400} />
285
- <Path
286
- d="M12 50 L50 70 L88 50"
287
- stroke={AppColors.white}
288
- strokeWidth="8"
289
- strokeLinecap="round"
290
- />
291
- <Path
292
- d="M12 65 L50 85 L88 65"
293
- stroke="#93C5FD"
294
- strokeWidth="8"
295
- strokeLinecap="round"
296
- />
297
- </Svg>
298
- </View>
299
- );
300
- }
301
-
302
- // 6. Linear Gradient (Vibrant Color Spectrum)
303
- if (lowerName.includes('gradient')) {
304
- return (
305
- <View
306
- style={[
307
- bundleStyles.pkgLogoWrap,
308
- {width: size, height: size, backgroundColor: AppColors.rose600},
309
- ]}>
310
- <Svg
311
- width={size - 4}
312
- height={size - 4}
313
- viewBox="0 0 100 100"
314
- fill="none">
315
- <Defs>
316
- <LinearGradient
317
- id="pkgGrad"
318
- x1="0"
319
- y1="0"
320
- x2="100"
321
- y2="100"
322
- gradientUnits="userSpaceOnUse">
323
- <Stop offset="0" stopColor="#FF0080" />
324
- <Stop offset="0.5" stopColor="#7928CA" />
325
- <Stop offset="1" stopColor="#0070F3" />
326
- </LinearGradient>
327
- </Defs>
328
- <Rect width="100" height="100" rx="18" fill="url(#pkgGrad)" />
329
- </Svg>
330
- </View>
331
- );
332
- }
333
-
334
- // 7. i18next & Translations (Emerald Globe)
335
- if (lowerName.includes('i18n')) {
336
- return (
337
- <View
338
- style={[
339
- bundleStyles.pkgLogoWrap,
340
- {width: size, height: size, backgroundColor: AppColors.emerald600},
341
- ]}>
342
- <Svg
343
- width={size - 8}
344
- height={size - 8}
345
- viewBox="0 0 100 100"
346
- fill="none">
347
- <Circle
348
- cx="50"
349
- cy="50"
350
- r="38"
351
- stroke={AppColors.white}
352
- strokeWidth="8"
353
- />
354
- <Ellipse
355
- cx="50"
356
- cy="50"
357
- rx="18"
358
- ry="38"
359
- stroke={AppColors.white}
360
- strokeWidth="6"
361
- />
362
- <Path d="M14 50 L86 50" stroke={AppColors.white} strokeWidth="7" />
363
- </Svg>
364
- </View>
365
- );
366
- }
367
-
368
- // 8. Official Red NPM Logo Box (Universal Default NPM Branding)
369
- return (
370
- <View
371
- style={[
372
- bundleStyles.pkgLogoWrap,
373
- {width: size, height: size, backgroundColor: AppColors.npmRed},
374
- ]}>
375
- <Svg width={size - 4} height={size - 4} viewBox="0 0 100 100" fill="none">
376
- <Rect width="100" height="100" rx="14" fill={AppColors.npmRed} />
377
- <Path d="M18 24 H82 V76 H50 V40 H38 V76 H18 Z" fill={AppColors.white} />
378
- </Svg>
379
- </View>
380
- );
381
- };
382
-
383
- // ─── Vector Category Icons (font-independent, render everywhere) ────────────
384
-
385
- export type BundleIconType =
386
- | 'source'
387
- | 'deps'
388
- | 'media'
389
- | 'overhead'
390
- | 'image'
391
- | 'typescript'
392
- | 'javascript'
393
- | 'font'
394
- | 'json'
395
- | 'folder'
396
- | 'ban'
397
- | 'bolt';
398
-
399
- const BundleCategoryIcon: React.FC<{
400
- type: BundleIconType;
401
- size?: number;
402
- color?: string;
403
- }> = ({type, size = 16, color = AppColors.purple}) => {
404
- const stroke = {
405
- stroke: color,
406
- strokeWidth: 1.7,
407
- strokeLinecap: 'round' as const,
408
- strokeLinejoin: 'round' as const,
409
- };
410
- const renderGlyph = () => {
411
- switch (type) {
412
- case 'folder':
413
- return (
414
- <G>
415
- <Path
416
- d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"
417
- {...stroke}
418
- />
419
- </G>
420
- );
421
- case 'ban':
422
- return (
423
- <G>
424
- <Circle cx="12" cy="12" r="8.5" {...stroke} />
425
- <Path d="M5.5 5.5 18.5 18.5" {...stroke} />
426
- </G>
427
- );
428
- case 'bolt':
429
- return (
430
- <G>
431
- <Path d="M13 2 4.5 13.5 H10.5 L9 22 19.5 9.5 H13.5 Z" {...stroke} />
432
- </G>
433
- );
434
- case 'source':
435
- return (
436
- <G>
437
- <Path d="M8.5 7 L4 12 L8.5 17" {...stroke} />
438
- <Path d="M15.5 7 L20 12 L15.5 17" {...stroke} />
439
- <Path d="M13.5 4.5 L10.5 19.5" {...stroke} />
440
- </G>
441
- );
442
- case 'deps':
443
- return (
444
- <G>
445
- <Rect x="4" y="4" width="16" height="16" rx="2.5" {...stroke} />
446
- <Path d="M4 9.5 H20" {...stroke} />
447
- <Path d="M9.5 9.5 V20" {...stroke} />
448
- </G>
449
- );
450
- case 'media':
451
- case 'image':
452
- return (
453
- <G>
454
- <Rect x="3.5" y="4.5" width="17" height="15" rx="2" {...stroke} />
455
- <Circle cx="9" cy="10" r="1.5" {...stroke} />
456
- <Path
457
- d="M3.5 17.5 L9.5 12.5 L14 16.5 L17 13.5 L20.5 17.5"
458
- {...stroke}
459
- />
460
- </G>
461
- );
462
- case 'overhead':
463
- return (
464
- <G>
465
- <Circle cx="12" cy="12" r="3.2" {...stroke} />
466
- <Path
467
- d="M12 2.5 V5.5 M12 18.5 V21.5 M2.5 12 H5.5 M18.5 12 H21.5 M5.3 5.3 L7.4 7.4 M16.6 16.6 L18.7 18.7 M18.7 5.3 L16.6 7.4 M7.4 16.6 L5.3 18.7"
468
- {...stroke}
469
- />
470
- </G>
471
- );
472
- case 'typescript':
473
- return (
474
- <G>
475
- <Path d="M6.5 3.5 H14 L18.5 8 V20.5 H6.5 Z" {...stroke} />
476
- <Path d="M14 3.5 V8 H18.5" {...stroke} />
477
- <Path d="M9.5 13.5 H15 M12 13.5 V16.5 M9.5 16.5 H13" {...stroke} />
478
- </G>
479
- );
480
- case 'javascript':
481
- return (
482
- <G>
483
- <Path
484
- d="M9.5 3.5 C7.5 3.5 7.5 6.5 7.5 9 C7.5 11 6.5 12 5 12 C6.5 12 7.5 13 7.5 15 C7.5 17.5 7.5 20.5 9.5 20.5"
485
- {...stroke}
486
- />
487
- <Path
488
- d="M14.5 3.5 C16.5 3.5 16.5 6.5 16.5 9 C16.5 11 17.5 12 19 12 C17.5 12 16.5 13 16.5 15 C16.5 17.5 16.5 20.5 14.5 20.5"
489
- {...stroke}
490
- />
491
- </G>
492
- );
493
- case 'font':
494
- return (
495
- <G>
496
- <Path d="M8 19 L12 5.5 L16 19" {...stroke} />
497
- <Path d="M9.4 14.5 H14.6" {...stroke} />
498
- </G>
499
- );
500
- case 'json':
501
- return (
502
- <G>
503
- <Path
504
- d="M9 3.5 C7 3.5 7 6 7 8.5 C7 10.5 6 11.5 5 12 C6 12.5 7 13.5 7 15.5 C7 18 7 20.5 9 20.5"
505
- {...stroke}
506
- />
507
- <Circle cx="12" cy="12" r="1.1" fill={color} />
508
- <Path
509
- d="M15 3.5 C17 3.5 17 6 17 8.5 C17 10.5 18 11.5 19 12 C18 12.5 17 13.5 17 15.5 C17 18 17 20.5 15 20.5"
510
- {...stroke}
511
- />
512
- </G>
513
- );
514
- default:
515
- return null;
516
- }
517
- };
518
- return (
519
- <Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
520
- {renderGlyph()}
521
- </Svg>
522
- );
523
- };
524
-
525
- // ─── Media File Thumbnail Component ──────────────────────────────────────────
526
-
527
- const MediaPreviewThumbnail: React.FC<{file: BundleFileItem}> = ({file}) => {
528
- const [loadError, setLoadError] = useState(false);
529
-
530
- // 1. If it's an SVG file, render an inline vector thumbnail preview
531
- if (file.ext === 'SVG' || file.name.toLowerCase().endsWith('.svg')) {
532
- return (
533
- <View
534
- style={[
535
- bundleStyles.mediaThumbWrap,
536
- {
537
- backgroundColor: AppColors.errorCardBg,
538
- alignItems: 'center',
539
- justifyContent: 'center',
540
- borderColor: AppColors.errorBorder,
541
- },
542
- ]}>
543
- <Svg width={22} height={22} viewBox="0 0 100 100" fill="none">
544
- <Defs>
545
- <LinearGradient
546
- id="svgGradThumb"
547
- x1="0"
548
- y1="0"
549
- x2="100"
550
- y2="100"
551
- gradientUnits="userSpaceOnUse">
552
- <Stop offset="0" stopColor={AppColors.rose500} />
553
- <Stop offset="1" stopColor={AppColors.rose600} />
554
- </LinearGradient>
555
- </Defs>
556
- <Circle cx="50" cy="50" r="44" fill="url(#svgGradThumb)" />
557
- {/* Vector bezier path pen graphic */}
558
- <Path
559
- d="M25 70 C35 30, 65 30, 75 70"
560
- stroke={AppColors.white}
561
- strokeWidth="8"
562
- strokeLinecap="round"
563
- />
564
- <Circle cx="25" cy="70" r="7" fill={AppColors.white} />
565
- <Circle cx="75" cy="70" r="7" fill={AppColors.white} />
566
- <Circle
567
- cx="50"
568
- cy="40"
569
- r="6"
570
- fill="#FFE4E6"
571
- stroke={AppColors.white}
572
- strokeWidth="2"
573
- />
574
- </Svg>
575
- </View>
576
- );
577
- }
578
-
579
- // 2. Raster Image (PNG, JPG, WebP) with valid preview URI
580
- if (file.category === 'image' && file.previewUri && !loadError) {
581
- return (
582
- <View style={bundleStyles.mediaThumbWrap}>
583
- <Image
584
- source={{uri: file.previewUri}}
585
- style={bundleStyles.mediaThumbImg}
586
- resizeMode="cover"
587
- onError={() => setLoadError(true)}
588
- />
589
- </View>
590
- );
591
- }
592
-
593
- const categoryMeta =
594
- CATEGORY_COLORS[file.category] || CATEGORY_COLORS.typescript;
595
- return (
596
- <View
597
- style={[
598
- bundleStyles.extChip,
599
- {
600
- backgroundColor: categoryMeta.bg,
601
- borderColor: `${categoryMeta.color}40`,
602
- },
603
- ]}>
604
- <Text style={[bundleStyles.extChipText, {color: categoryMeta.color}]}>
605
- {file.ext}
606
- </Text>
607
- </View>
608
- );
609
- };
610
-
611
- // ─── Directory Tree Hierarchy Data Structure & Component ─────────────────────
612
-
613
- export interface TreeNode {
614
- id: string;
615
- name: string;
616
- fullPath: string;
617
- isFolder: boolean;
618
- fileItem?: BundleFileItem;
619
- sizeKb: number;
620
- fileCount: number;
621
- children: TreeNode[];
622
- }
623
-
624
- function buildBundleFileTree(files: BundleFileItem[]): TreeNode[] {
625
- const rootMap = new Map<string, any>();
626
-
627
- for (const file of files) {
628
- const cleanPath = file.path.replace(/^\/+/, '');
629
- const parts = cleanPath.split('/').filter(Boolean);
630
- if (parts.length === 0) continue;
631
- let currentLevel = rootMap;
632
-
633
- for (let i = 0; i < parts.length; i++) {
634
- const part = parts[i];
635
- const isFile = i === parts.length - 1;
636
-
637
- if (!currentLevel.has(part)) {
638
- currentLevel.set(part, {
639
- name: part,
640
- fullPath: parts.slice(0, i + 1).join('/'),
641
- isFile,
642
- fileItem: isFile ? file : undefined,
643
- children: new Map<string, any>(),
644
- });
645
- }
646
-
647
- if (!isFile) {
648
- currentLevel = currentLevel.get(part).children;
649
- }
650
- }
651
- }
652
-
653
- function convertMapToNodes(map: Map<string, any>): TreeNode[] {
654
- const nodes: TreeNode[] = [];
655
- for (const [, val] of map.entries()) {
656
- const children = convertMapToNodes(val.children);
657
- const isFolder = !val.isFile;
658
- const fileCount = isFolder
659
- ? children.reduce((acc, c) => acc + (c.isFolder ? c.fileCount : 1), 0)
660
- : 1;
661
- const sizeKb = isFolder
662
- ? children.reduce((acc, c) => acc + c.sizeKb, 0)
663
- : val.fileItem
664
- ? val.fileItem.sizeKb
665
- : 0;
666
-
667
- nodes.push({
668
- id: val.fullPath,
669
- name: val.name,
670
- fullPath: val.fullPath,
671
- isFolder,
672
- fileItem: val.fileItem,
673
- sizeKb,
674
- fileCount,
675
- children,
676
- });
677
- }
678
-
679
- // Sort folders first, then files alphabetically
680
- return nodes.sort((a, b) => {
681
- if (a.isFolder && !b.isFolder) return -1;
682
- if (!a.isFolder && b.isFolder) return 1;
683
- return a.name.localeCompare(b.name);
684
- });
685
- }
686
-
687
- return convertMapToNodes(rootMap);
688
- }
689
-
690
- export const downloadBundleFile = async (
691
- file: BundleFileItem,
692
- scriptURL?: string,
693
- ): Promise<void> => {
694
- let content = '';
695
-
696
- // 1. Attempt to fetch real source code from Metro dev server dynamically
697
- try {
698
- let devServerOrigin = '';
699
- const activeUrl = scriptURL || getHostScriptURL();
700
- if (activeUrl && activeUrl.startsWith('http')) {
701
- const match = activeUrl.match(/^(https?:\/\/[^/]+)/);
702
- if (match) {
703
- devServerOrigin = match[1];
704
- }
705
- }
706
- if (devServerOrigin) {
707
- const cleanPath = file.path.replace(/^\/+/, '');
708
- const url = `${devServerOrigin}/${cleanPath}`;
709
-
710
- const res = await fetch(url);
711
- if (res.ok) {
712
- const text = await res.text();
713
- if (text && text.length > 0) {
714
- content = text;
715
- }
716
- }
717
- }
718
- } catch {}
719
-
720
- // 2. If dev server direct fetch is unavailable, provide structured module descriptor with full path
721
- if (!content) {
722
- content = `// ─────────────────────────────────────────────────────────────
723
- // File: ${file.name}
724
- // Path: ${file.path}
725
- // Category: ${file.category.toUpperCase()}
726
- // Size: ${file.sizeKb} KB
727
- // Status: ${
728
- file.status === 'optimal'
729
- ? 'Active in Dependency Graph'
730
- : 'Not consumed / Static'
731
- }
732
- // Meta: ${file.meta}
733
- // ─────────────────────────────────────────────────────────────
734
-
735
- export default {
736
- id: ${JSON.stringify(file.id)},
737
- name: ${JSON.stringify(file.name)},
738
- path: ${JSON.stringify(file.path)},
739
- category: ${JSON.stringify(file.category)},
740
- sizeKb: ${file.sizeKb},
741
- meta: ${JSON.stringify(file.meta)},
742
- status: ${JSON.stringify(file.status || 'optimal')},
743
- };
744
- `;
745
- }
746
-
747
- // 3. Trigger native Share / Download sheet
748
- try {
749
- await Share.share(
750
- Platform.OS === 'ios'
751
- ? {
752
- title: file.name,
753
- message: content,
754
- }
755
- : {
756
- title: `Download ${file.name}`,
757
- message: content,
758
- },
759
- {
760
- dialogTitle: `Download / Save ${file.name}`,
761
- },
762
- );
763
- } catch {}
764
-
765
- // 4. Also copy directly to clipboard for quick paste in VS Code or external editor
766
- try {
767
- copyToClipboard(content, file.name);
768
- } catch {}
769
- };
770
-
771
- const BundleTreeNodeView: React.FC<{
772
- node: TreeNode;
773
- level: number;
774
- search: string;
775
- totalBundleKb: number;
776
- expandedFolders: Record<string, boolean>;
777
- toggleFolder: (path: string) => void;
778
- isLastChild: boolean;
779
- onDownload?: (file: BundleFileItem) => void;
780
- }> = ({
781
- node,
782
- level,
783
- search,
784
- totalBundleKb,
785
- expandedFolders,
786
- toggleFolder,
787
- isLastChild,
788
- onDownload,
789
- }) => {
790
- const isExpanded = search ? true : !!expandedFolders[node.fullPath];
791
- const isCollapsed = !isExpanded;
792
- const indentLeft = level * 16 + 10;
793
-
794
- if (node.isFolder) {
795
- return (
796
- <View style={bundleStyles.treeFolderBlock}>
797
- <TouchableScale
798
- accessible={true}
799
- accessibilityRole="button"
800
- accessibilityLabel={`Folder ${node.name}, ${node.fileCount} ${
801
- node.fileCount === 1 ? 'file' : 'files'
802
- }, ${
803
- node.sizeKb >= 1024
804
- ? `${(node.sizeKb / 1024).toFixed(2)} MB`
805
- : `${node.sizeKb} KB`
806
- }`}
807
- accessibilityState={{expanded: isExpanded}}
808
- accessibilityHint="Double tap to expand or collapse folder"
809
- onPress={() => toggleFolder(node.fullPath)}
810
- style={[bundleStyles.treeFolderRow, {paddingLeft: indentLeft}]}>
811
- <View style={bundleStyles.treeRowLeft}>
812
- <View style={{marginRight: 4}}>
813
- <ChevronIcon
814
- direction={isCollapsed ? 'right' : 'down'}
815
- size={12}
816
- color={AppColors.grayTextWeak}
817
- />
818
- </View>
819
- {isCollapsed ? (
820
- <FolderIcon color={AppColors.amber500} size={15} />
821
- ) : (
822
- <FolderOpenIcon color={AppColors.amber500} size={15} />
823
- )}
824
- <HighlightText
825
- text={node.name + '/'}
826
- search={search}
827
- style={bundleStyles.treeFolderName}
828
- highlightStyle={bundleStyles.searchHighlight}
829
- />
830
- <View style={bundleStyles.treeCountBadge}>
831
- <Text style={bundleStyles.treeCountBadgeText}>
832
- {node.fileCount}{' '}
833
- {node.fileCount === 1 ? t('bundle.file') : t('bundle.files')}
834
- </Text>
835
- </View>
836
- </View>
837
-
838
- <View style={bundleStyles.treeRowRight}>
839
- <Text style={bundleStyles.treeFolderSize}>
840
- {node.sizeKb >= 1024
841
- ? t('bundle.fileSizeMb', {
842
- size: (node.sizeKb / 1024).toFixed(2),
843
- })
844
- : t('bundle.fileSizeKb', {size: node.sizeKb})}
845
- </Text>
846
- <CopyButton
847
- value={() => ({
848
- folder: node.fullPath,
849
- sizeKb: node.sizeKb,
850
- fileCount: node.fileCount,
851
- })}
852
- label={t('bundle.copyFileInfo', {name: node.name})}
853
- />
854
- </View>
855
- </TouchableScale>
856
-
857
- {!isCollapsed && (
858
- <View style={bundleStyles.treeChildrenContainer}>
859
- {node.children.map((child, cIdx) => (
860
- <BundleTreeNodeView
861
- key={child.id}
862
- node={child}
863
- level={level + 1}
864
- search={search}
865
- totalBundleKb={totalBundleKb}
866
- expandedFolders={expandedFolders}
867
- toggleFolder={toggleFolder}
868
- isLastChild={cIdx === node.children.length - 1}
869
- onDownload={onDownload}
870
- />
871
- ))}
872
- </View>
873
- )}
874
- </View>
875
- );
876
- }
877
-
878
- // File item inside tree
879
- const file = node.fileItem;
880
- if (!file) return null;
881
-
882
- const pctOfTotal =
883
- totalBundleKb > 0 ? ((file.sizeKb / totalBundleKb) * 100).toFixed(1) : '0';
884
- const isUnused = file.isConsumed === false;
885
-
886
- return (
887
- <View
888
- style={[
889
- bundleStyles.treeFileRow,
890
- {paddingLeft: indentLeft},
891
- isUnused && bundleStyles.treeFileRowUnused,
892
- ]}>
893
- <View style={bundleStyles.treeRowLeft}>
894
- <View style={bundleStyles.treeBranchGuide}>
895
- <Text style={bundleStyles.treeBranchGuideText}>
896
- {isLastChild ? '└' : '├'}
897
- </Text>
898
- </View>
899
-
900
- <MediaPreviewThumbnail file={file} />
901
-
902
- <HighlightText
903
- text={file.name}
904
- search={search}
905
- style={bundleStyles.treeFileName}
906
- highlightStyle={bundleStyles.searchHighlight}
907
- />
908
-
909
- {isUnused && (
910
- <View style={bundleStyles.treeUnusedPill}>
911
- <Text style={bundleStyles.treeUnusedPillText}>
912
- {t('bundle.notConsumed')}
913
- </Text>
914
- </View>
915
- )}
916
- </View>
917
-
918
- <View style={bundleStyles.treeRowRight}>
919
- <View style={bundleStyles.treeFileSizeGroup}>
920
- <Text
921
- style={[
922
- bundleStyles.treeFileSizeText,
923
- isUnused && {color: AppColors.red600},
924
- ]}>
925
- {file.sizeKb >= 1024
926
- ? t('bundle.fileSizeMb', {size: (file.sizeKb / 1024).toFixed(2)})
927
- : t('bundle.fileSizeKb', {size: file.sizeKb})}
928
- </Text>
929
- <Text style={bundleStyles.treeFilePctText}>{pctOfTotal}%</Text>
930
- </View>
931
-
932
- <View style={{flexDirection: 'row', alignItems: 'center', gap: 5}}>
933
- {onDownload && (
934
- <TouchableScale
935
- accessible={true}
936
- accessibilityRole="button"
937
- accessibilityLabel={`Download source code for ${file.name}`}
938
- accessibilityHint="Fetches module source code from Metro dev server"
939
- onPress={() => onDownload(file)}
940
- style={bundleStyles.treeActionBtn}
941
- hitSlop={8}>
942
- <DownloadIcon size={13} color={AppColors.sky500} />
943
- </TouchableScale>
944
- )}
945
-
946
- <CopyButton value={() => file} label={t('bundle.fileDetailsJson')} />
947
- </View>
948
- </View>
949
- </View>
950
- );
951
- };
952
-
953
- const CATEGORY_TABS = [
954
- {key: 'ALL', labelKey: 'bundle.catAll', iconType: 'folder' as const},
955
- {key: 'UNUSED', labelKey: 'bundle.catUnused', iconType: 'ban' as const},
956
- {key: 'CONSUMED', labelKey: 'bundle.catConsumed', iconType: 'bolt' as const},
957
- {key: 'image', labelKey: 'bundle.catImages', iconType: 'image' as const},
958
- {
959
- key: 'typescript',
960
- labelKey: 'bundle.catTypescript',
961
- iconType: 'typescript' as const,
962
- },
963
- {
964
- key: 'javascript',
965
- labelKey: 'bundle.catJavascript',
966
- iconType: 'javascript' as const,
967
- },
968
- {key: 'font', labelKey: 'bundle.catFonts', iconType: 'font' as const},
969
- {key: 'json', labelKey: 'bundle.catJson', iconType: 'json' as const},
970
- ];
971
-
972
- const CATEGORY_COLORS: Record<
973
- FileTypeCategory,
974
- {label: string; color: string; bg: string}
975
- > = {
976
- image: {
977
- label: 'Images & Media',
978
- color: AppColors.pink500,
979
- bg: AppColors.pink100,
980
- },
981
- typescript: {
982
- label: 'TypeScript',
983
- color: AppColors.sky500,
984
- bg: AppColors.sky100,
985
- },
986
- javascript: {
987
- label: 'JavaScript',
988
- color: AppColors.indigo500,
989
- bg: AppColors.indigo50,
990
- },
991
- font: {label: 'Fonts', color: AppColors.purple500, bg: AppColors.purple100},
992
- json: {
993
- label: 'JSON / Data',
994
- color: AppColors.emerald500,
995
- bg: AppColors.emerald100,
996
- },
997
- };
998
-
999
- const BundleTab = React.memo(() => {
1000
- const {t} = useTranslation();
1001
- const [activeSubTab, setActiveSubTab] = useState<BundleSubTab>('overview');
1002
- const [prodPlatform, setProdPlatform] = useState<
1003
- 'ios' | 'androidAab' | 'androidApk'
1004
- >('ios');
1005
- const [search, setSearch] = useState('');
1006
- const [activeCategory, setActiveCategory] = useState<string>('ALL');
1007
-
1008
- // ─── Live Host App Bundle Analysis (fetched from Metro / runtime) ─────────
1009
- const [analysis, setAnalysis] = useState<HostBundleAnalysisResult>(
1010
- () => getCachedBundleAnalysis() || getInitialBundleAnalysis(),
1011
- );
1012
- const [isAnalyzing, setIsAnalyzing] = useState<boolean>(false);
1013
-
1014
- const refreshAnalysis = useCallback(() => {
1015
- setIsAnalyzing(true);
1016
- analyzeHostAppBundle(true)
1017
- .then(result => {
1018
- setAnalysis(result);
1019
- setIsAnalyzing(false);
1020
- })
1021
- .catch(() => {
1022
- setIsAnalyzing(false);
1023
- });
1024
- }, []);
1025
-
1026
- useEffect(() => {
1027
- let mounted = true;
1028
- analyzeHostAppBundle()
1029
- .then(result => {
1030
- if (mounted) {
1031
- setAnalysis(result);
1032
- setIsAnalyzing(false);
1033
- }
1034
- })
1035
- .catch(() => {
1036
- if (mounted) {
1037
- setIsAnalyzing(false);
1038
- }
1039
- });
1040
- return () => {
1041
- mounted = false;
1042
- };
1043
- }, []);
1044
-
1045
- // Check if Hermes is running
1046
- const isHermes = Boolean((global as any).HermesInternal);
1047
-
1048
- // Dynamic files & packages derived from the analyzed host bundle
1049
- const bundleFiles: BundleFileItem[] = useMemo(
1050
- () => (analysis ? analysis.files : []),
1051
- [analysis],
1052
- );
1053
- const bundlePackages: BundlePackageItem[] = useMemo(
1054
- () => (analysis ? analysis.packages : []),
1055
- [analysis],
1056
- );
1057
-
1058
- // Comprehensive bundle summary (full Metro bundle calculation e.g. 6.0 MB)
1059
- const summary = useMemo(() => {
1060
- const totalKb =
1061
- analysis?.totalDevKb ||
1062
- (analysis?.totalDevBytes
1063
- ? Math.round(analysis.totalDevBytes / 1024)
1064
- : 6144);
1065
- const totalMb = analysis?.totalDevMb
1066
- ? analysis.totalDevMb.toFixed(2)
1067
- : (totalKb / 1024).toFixed(2);
1068
-
1069
- const nodeModulesKb =
1070
- analysis?.splitUp?.nodeModules?.kb || Math.round(totalKb * 0.52);
1071
- const nodeModulesPct = analysis?.splitUp?.nodeModules?.pct || 52.0;
1072
-
1073
- const metroKb =
1074
- analysis?.splitUp?.metroDevOverhead?.kb || Math.round(totalKb * 0.21);
1075
- const metroPct = analysis?.splitUp?.metroDevOverhead?.pct || 21.0;
1076
-
1077
- const appSourceKb =
1078
- analysis?.splitUp?.appSource?.kb || Math.round(totalKb * 0.15);
1079
- const appSourcePct = analysis?.splitUp?.appSource?.pct || 15.0;
1080
-
1081
- const assetsMediaKb =
1082
- analysis?.splitUp?.assetsMedia?.kb || Math.round(totalKb * 0.12);
1083
- const assetsMediaPct = analysis?.splitUp?.assetsMedia?.pct || 12.0;
1084
-
1085
- const categories = [
1086
- {
1087
- key: 'node_modules',
1088
- iconType: 'javascript' as const,
1089
- title: 'NPM Dependencies',
1090
- targetTab: 'packages' as const,
1091
- count: analysis?.packageCount || bundlePackages.length || 53,
1092
- sizeKb: nodeModulesKb,
1093
- sizeMb: (nodeModulesKb / 1024).toFixed(2),
1094
- pct: nodeModulesPct,
1095
- color: AppColors.indigo500,
1096
- desc: `${
1097
- analysis?.packageCount || bundlePackages.length || 53
1098
- } node_modules packages (React Native, React, navigation, axios)`,
1099
- },
1100
- {
1101
- key: 'metro_runtime',
1102
- iconType: 'bolt' as const,
1103
- title: 'Metro Dev Runtime',
1104
- targetTab: 'optimizer' as const,
1105
- count: 1,
1106
- sizeKb: metroKb,
1107
- sizeMb: (metroKb / 1024).toFixed(2),
1108
- pct: metroPct,
1109
- color: AppColors.amber500,
1110
- desc: 'Module loader, polyfills, HMR client, and prelude runtime',
1111
- },
1112
- {
1113
- key: 'app_source',
1114
- iconType: 'typescript' as const,
1115
- title: 'App Source Code (TSX/JS)',
1116
- targetTab: 'files' as const,
1117
- count:
1118
- bundleFiles.filter(
1119
- f => f.category === 'typescript' || f.category === 'javascript',
1120
- ).length || 7,
1121
- sizeKb: appSourceKb,
1122
- sizeMb: (appSourceKb / 1024).toFixed(2),
1123
- pct: appSourcePct,
1124
- color: AppColors.sky500,
1125
- desc: 'Screens, components, navigation, Redux store, and hooks',
1126
- },
1127
- {
1128
- key: 'assets_media',
1129
- iconType: 'image' as const,
1130
- title: 'Images & Static Media',
1131
- targetTab: 'media' as const,
1132
- count:
1133
- bundleFiles.filter(
1134
- f =>
1135
- f.category === 'image' ||
1136
- f.category === 'font' ||
1137
- f.category === 'json',
1138
- ).length || 3,
1139
- sizeKb: assetsMediaKb,
1140
- sizeMb: (assetsMediaKb / 1024).toFixed(2),
1141
- pct: assetsMediaPct,
1142
- color: AppColors.pink500,
1143
- desc: 'Bundled PNG/JPG/SVG drawables, custom fonts, and JSON assets',
1144
- },
1145
- ];
1146
-
1147
- return {
1148
- totalKb,
1149
- totalMb,
1150
- totalCount: (analysis?.packageCount || 53) + bundleFiles.length + 1,
1151
- categories,
1152
- nodeModulesKb,
1153
- nodeModulesPct,
1154
- metroKb,
1155
- metroPct,
1156
- appSourceKb,
1157
- appSourcePct,
1158
- assetsMediaKb,
1159
- assetsMediaPct,
1160
- images: {
1161
- count: bundleFiles.filter(f => f.category === 'image').length,
1162
- kb: assetsMediaKb,
1163
- pct: assetsMediaPct,
1164
- },
1165
- ts: {
1166
- count: bundleFiles.filter(f => f.category === 'typescript').length,
1167
- kb: appSourceKb,
1168
- pct: appSourcePct,
1169
- },
1170
- js: {
1171
- count: (analysis?.packageCount || 53) + 1,
1172
- kb: nodeModulesKb + metroKb,
1173
- pct: Number((nodeModulesPct + metroPct).toFixed(1)),
1174
- },
1175
- fonts: {
1176
- count: bundleFiles.filter(f => f.category === 'font').length,
1177
- kb: 0,
1178
- pct: 0,
1179
- },
1180
- json: {
1181
- count: bundleFiles.filter(f => f.category === 'json').length,
1182
- kb: Math.round(assetsMediaKb * 0.1),
1183
- pct: Number((assetsMediaPct * 0.1).toFixed(1)),
1184
- },
1185
- };
1186
- }, [analysis, bundleFiles, bundlePackages]);
1187
-
1188
- // Filtered files
1189
- const filteredFiles = useMemo(() => {
1190
- return bundleFiles.filter(file => {
1191
- const matchSearch =
1192
- !search ||
1193
- file.name.toLowerCase().includes(search.toLowerCase()) ||
1194
- file.path.toLowerCase().includes(search.toLowerCase()) ||
1195
- file.ext.toLowerCase().includes(search.toLowerCase()) ||
1196
- file.meta.toLowerCase().includes(search.toLowerCase());
1197
- const matchCategory =
1198
- activeCategory === 'ALL'
1199
- ? true
1200
- : activeCategory === 'UNUSED'
1201
- ? file.isConsumed === false
1202
- : activeCategory === 'CONSUMED'
1203
- ? file.isConsumed !== false
1204
- : file.category === activeCategory;
1205
- return matchSearch && matchCategory;
1206
- });
1207
- }, [search, activeCategory, bundleFiles]);
1208
-
1209
- const filteredTotalKb = useMemo(() => {
1210
- return filteredFiles.reduce((acc, f) => acc + f.sizeKb, 0);
1211
- }, [filteredFiles]);
1212
-
1213
- // Tree View State & Node Generation (Top folders expanded by default)
1214
- const [filesViewMode, setFilesViewMode] = useState<'tree' | 'list'>('tree');
1215
- const [expandedFolders, setExpandedFolders] = useState<
1216
- Record<string, boolean>
1217
- >({});
1218
-
1219
- const fileTreeNodes = useMemo(() => {
1220
- return buildBundleFileTree(filteredFiles);
1221
- }, [filteredFiles]);
1222
-
1223
- useEffect(() => {
1224
- if (fileTreeNodes.length > 0 && Object.keys(expandedFolders).length === 0) {
1225
- const initial: Record<string, boolean> = {};
1226
- fileTreeNodes.forEach(node => {
1227
- if (node.isFolder) {
1228
- initial[node.fullPath] = true;
1229
- node.children.forEach(child => {
1230
- if (child.isFolder) {
1231
- initial[child.fullPath] = true;
1232
- }
1233
- });
1234
- }
1235
- });
1236
- setExpandedFolders(initial);
1237
- }
1238
- }, [fileTreeNodes]);
1239
-
1240
- const toggleFolder = useCallback((path: string) => {
1241
- setExpandedFolders(prev => ({
1242
- ...prev,
1243
- [path]: !prev[path],
1244
- }));
1245
- }, []);
1246
-
1247
- const expandAllFolders = useCallback(() => {
1248
- const all: Record<string, boolean> = {};
1249
- function collect(nodes: TreeNode[]) {
1250
- for (const n of nodes) {
1251
- if (n.isFolder) {
1252
- all[n.fullPath] = true;
1253
- collect(n.children);
1254
- }
1255
- }
1256
- }
1257
- collect(fileTreeNodes);
1258
- setExpandedFolders(all);
1259
- }, [fileTreeNodes]);
1260
-
1261
- const collapseAllFolders = useCallback(() => {
1262
- setExpandedFolders({});
1263
- }, []);
1264
-
1265
- // Filtered Packages
1266
- const filteredPackages = useMemo(() => {
1267
- if (!search) return bundlePackages;
1268
- const q = search.toLowerCase();
1269
- return bundlePackages.filter(
1270
- pkg =>
1271
- pkg.name.toLowerCase().includes(q) ||
1272
- pkg.description.toLowerCase().includes(q) ||
1273
- pkg.category.toLowerCase().includes(q),
1274
- );
1275
- }, [search, bundlePackages]);
1276
-
1277
- const packagesTotalKb = useMemo(() => {
1278
- return filteredPackages.reduce((acc, p) => acc + p.sizeKb, 0);
1279
- }, [filteredPackages]);
1280
-
1281
- // Media files (Images + Fonts)
1282
- const mediaFiles = useMemo(() => {
1283
- return bundleFiles.filter(
1284
- f => f.category === 'image' || f.category === 'font',
1285
- );
1286
- }, [bundleFiles]);
1287
-
1288
- const mediaTotalKb = useMemo(() => {
1289
- return mediaFiles.reduce((acc, m) => acc + m.sizeKb, 0);
1290
- }, [mediaFiles]);
1291
-
1292
- const subTabs = [
1293
- {
1294
- key: 'overview',
1295
- label: t('bundle.tabOverview'),
1296
- icon: (isActive: boolean) => (
1297
- <LiveStateIcon
1298
- color={isActive ? AppColors.white : AppColors.purple}
1299
- size={12}
1300
- />
1301
- ),
1302
- },
1303
- {
1304
- key: 'production',
1305
- label: t('bundle.tabProduction', {count: 3}),
1306
- icon: (isActive: boolean) => (
1307
- <TimelineIcon
1308
- color={isActive ? AppColors.white : AppColors.purple}
1309
- size={12}
1310
- />
1311
- ),
1312
- },
1313
- {
1314
- key: 'files',
1315
- label: t('bundle.tabFiles', {count: bundleFiles.length}),
1316
- icon: (isActive: boolean) => (
1317
- <LayersIcon
1318
- color={isActive ? AppColors.white : AppColors.purple}
1319
- size={12}
1320
- />
1321
- ),
1322
- },
1323
- {
1324
- key: 'packages',
1325
- label: t('bundle.tabPackages', {count: bundlePackages.length}),
1326
- icon: (isActive: boolean) => (
1327
- <PackageIcon
1328
- color={isActive ? AppColors.white : AppColors.purple}
1329
- size={12}
1330
- />
1331
- ),
1332
- },
1333
- {
1334
- key: 'media',
1335
- label: t('bundle.tabMedia', {count: mediaFiles.length}),
1336
- icon: (isActive: boolean) => (
1337
- <StorageIcon
1338
- color={isActive ? AppColors.white : AppColors.purple}
1339
- size={12}
1340
- />
1341
- ),
1342
- },
1343
- {
1344
- key: 'optimizer',
1345
- label: t('bundle.tabOptimizer'),
1346
- icon: (isActive: boolean) => (
1347
- <MetadataIcon
1348
- color={isActive ? AppColors.white : AppColors.purple}
1349
- size={12}
1350
- />
1351
- ),
1352
- },
1353
- ];
1354
-
1355
- return (
1356
- <View style={{flex: 1, backgroundColor: AppColors.grayBackground}}>
1357
- {/* ─── Top Sub-Tabs Navigation Bar (Scrollable Pill Bar + Dynamic Refresh) ─── */}
1358
- <View style={bundleStyles.topTabWrap}>
1359
- <ScrollView
1360
- horizontal
1361
- showsHorizontalScrollIndicator={false}
1362
- style={{flex: 1}}
1363
- contentContainerStyle={bundleStyles.subTabsScroll}>
1364
- {subTabs.map(tab => {
1365
- const isActive = activeSubTab === tab.key;
1366
- return (
1367
- <TouchableScale
1368
- key={tab.key}
1369
- accessible={true}
1370
- accessibilityRole="tab"
1371
- accessibilityLabel={`${tab.label} tab`}
1372
- accessibilityState={{selected: isActive}}
1373
- accessibilityHint={`Switches to ${tab.label} tab`}
1374
- onPress={() => setActiveSubTab(tab.key as BundleSubTab)}
1375
- style={[
1376
- bundleStyles.subTabPill,
1377
- isActive && bundleStyles.subTabPillActive,
1378
- ]}>
1379
- {tab.icon(isActive)}
1380
- <Text
1381
- style={[
1382
- bundleStyles.subTabPillText,
1383
- isActive && bundleStyles.subTabPillTextActive,
1384
- ]}>
1385
- {tab.label}
1386
- </Text>
1387
- </TouchableScale>
1388
- );
1389
- })}
1390
- </ScrollView>
1391
- <TouchableScale
1392
- accessible={true}
1393
- accessibilityRole="button"
1394
- accessibilityLabel={
1395
- isAnalyzing ? 'Analyzing Metro bundle' : 'Reload bundle analysis'
1396
- }
1397
- accessibilityHint="Re-probes Metro dev server and refreshes bundle statistics"
1398
- onPress={refreshAnalysis}
1399
- style={bundleStyles.reloadButton}
1400
- hitSlop={{top: 8, bottom: 8, left: 8, right: 8}}>
1401
- {isAnalyzing ? (
1402
- <ActivityIndicator size="small" color={AppColors.brandPurple} />
1403
- ) : (
1404
- <RefreshCcwIcon color={AppColors.brandPurple} size={14} />
1405
- )}
1406
- </TouchableScale>
1407
- </View>
1408
-
1409
- <FeatureUnderDevNotice featureName="Metro Bundle Analyzer" />
1410
-
1411
- {/* ══════════════════════════════════════════════════════════════════════ */}
1412
- {/* ── 1. TAB: OVERVIEW & TREEMAP ───────────────────────────────────────── */}
1413
- {/* ══════════════════════════════════════════════════════════════════════ */}
1414
- {activeSubTab === 'overview' && (
1415
- <ScrollView
1416
- style={{flex: 1}}
1417
- contentContainerStyle={bundleStyles.contentContainer}
1418
- keyboardShouldPersistTaps="handled">
1419
- {/* Hero Overview Card */}
1420
- <View style={bundleStyles.heroCard}>
1421
- <View style={bundleStyles.heroTopRow}>
1422
- <View style={bundleStyles.heroIconWrap}>
1423
- <PackageIcon color={AppColors.brandPurple} size={24} />
1424
- </View>
1425
- <View style={{flex: 1}}>
1426
- <Text style={bundleStyles.heroTitle}>
1427
- {t('bundle.heroTitle')}
1428
- </Text>
1429
- {analysis?.isLive && analysis.scriptURL ? (
1430
- <View style={{marginTop: 4, gap: 4}}>
1431
- <Text style={bundleStyles.heroSubtitle}>
1432
- {t('bundle.heroSubtitle')}
1433
- </Text>
1434
- <Pressable
1435
- accessible={true}
1436
- accessibilityRole="link"
1437
- accessibilityLabel={`Live Metro bundle URL: ${analysis.scriptURL}`}
1438
- accessibilityHint="Opens Metro bundle script in browser"
1439
- style={{
1440
- backgroundColor: `${AppColors.skyBlue}10`,
1441
- borderColor: `${AppColors.skyBlue}30`,
1442
- borderWidth: 1,
1443
- borderRadius: 8,
1444
- padding: 8,
1445
- marginTop: 2,
1446
- }}
1447
- onPress={() => Linking.openURL(analysis.scriptURL)}>
1448
- <View
1449
- style={{
1450
- flexDirection: 'row',
1451
- alignItems: 'center',
1452
- justifyContent: 'space-between',
1453
- marginBottom: 3,
1454
- }}>
1455
- <View
1456
- style={{
1457
- flexDirection: 'row',
1458
- alignItems: 'center',
1459
- gap: 4,
1460
- }}>
1461
- <ExternalLinkIcon
1462
- color={AppColors.skyBlue}
1463
- size={11}
1464
- />
1465
- <Text
1466
- style={{
1467
- fontFamily: AppFonts.interBold,
1468
- fontSize: 9.5,
1469
- color: AppColors.skyBlue,
1470
- letterSpacing: 0.3,
1471
- textTransform: 'uppercase',
1472
- }}>
1473
- Live Metro URL
1474
- </Text>
1475
- </View>
1476
- <Text
1477
- style={{
1478
- fontFamily: AppFonts.interRegular,
1479
- fontSize: 9.5,
1480
- color: AppColors.grayTextWeak,
1481
- }}>
1482
- {analysis.scriptURL.length} chars
1483
- </Text>
1484
- </View>
1485
- <Text
1486
- selectable={true}
1487
- numberOfLines={1}
1488
- ellipsizeMode="tail"
1489
- style={{
1490
- fontFamily: AppFonts.interMedium,
1491
- fontSize: 11,
1492
- color: AppColors.skyBlue,
1493
- textDecorationLine: 'underline',
1494
- lineHeight: 16,
1495
- }}>
1496
- {analysis.scriptURL}
1497
- </Text>
1498
- </Pressable>
1499
- </View>
1500
- ) : (
1501
- <Text style={bundleStyles.heroSubtitle}>
1502
- {t('bundle.heroSubtitle')}
1503
- </Text>
1504
- )}
1505
- </View>
1506
- <CopyButton
1507
- value={() => ({...summary, bundle: analysis})}
1508
- label={t('bundle.bundleOverviewJson')}
1509
- />
1510
- </View>
1511
-
1512
- {/* 4-Stat Metric Grid */}
1513
- <View style={bundleStyles.metricsGrid}>
1514
- <View style={bundleStyles.metricBox}>
1515
- <Text style={bundleStyles.metricLabel}>
1516
- {t('bundle.devBundleSize')}
1517
- </Text>
1518
- <Text style={bundleStyles.metricValue}>
1519
- {t('bundle.mbValue', {
1520
- size: analysis ? analysis.totalDevMb : summary.totalMb,
1521
- })}
1522
- </Text>
1523
- <Text style={bundleStyles.metricHint}>
1524
- {analysis
1525
- ? t('bundle.devBundleHint', {
1526
- kb: analysis.totalDevKb,
1527
- modules: analysis.moduleCount,
1528
- })
1529
- : t('bundle.trackedFilesHint', {count: bundleFiles.length})}
1530
- </Text>
1531
- </View>
1532
-
1533
- <View style={bundleStyles.metricBox}>
1534
- <Text style={bundleStyles.metricLabel}>
1535
- {t('bundle.imagesMedia')}
1536
- </Text>
1537
- <Text
1538
- style={[
1539
- bundleStyles.metricValue,
1540
- {color: AppColors.pink500},
1541
- ]}>
1542
- {t('bundle.fileSizeKb', {size: summary.images.kb})}
1543
- </Text>
1544
- <Text style={bundleStyles.metricHint}>
1545
- {t('bundle.pctOfTrackedAssets', {pct: summary.images.pct})}
1546
- </Text>
1547
- </View>
1548
-
1549
- <View style={bundleStyles.metricBox}>
1550
- <Text style={bundleStyles.metricLabel}>
1551
- {t('bundle.tsJsSource')}
1552
- </Text>
1553
- <Text
1554
- style={[bundleStyles.metricValue, {color: AppColors.sky500}]}>
1555
- {t('bundle.fileSizeKb', {
1556
- size: summary.ts.kb + summary.js.kb,
1557
- })}
1558
- </Text>
1559
- <Text style={bundleStyles.metricHint}>
1560
- {t('bundle.pctOfTrackedAssets', {
1561
- pct: (summary.ts.pct + summary.js.pct).toFixed(1),
1562
- })}
1563
- </Text>
1564
- </View>
1565
-
1566
- <View style={bundleStyles.metricBox}>
1567
- <Text style={bundleStyles.metricLabel}>
1568
- {t('bundle.jsEngine')}
1569
- </Text>
1570
- <View
1571
- style={{
1572
- flexDirection: 'row',
1573
- alignItems: 'center',
1574
- gap: 5,
1575
- marginTop: 2,
1576
- }}>
1577
- <View
1578
- style={[
1579
- bundleStyles.statusIndicator,
1580
- {
1581
- backgroundColor: isHermes
1582
- ? AppColors.greenColor
1583
- : AppColors.darkOrange,
1584
- },
1585
- ]}
1586
- />
1587
- <Text style={bundleStyles.metricValue}>
1588
- {isHermes ? 'Hermes' : 'JSC'}
1589
- </Text>
1590
- </View>
1591
- <Text style={bundleStyles.metricHint}>
1592
- {isHermes ? t('bundle.bytecodeAot') : t('bundle.standard')}
1593
- </Text>
1594
- </View>
1595
- </View>
1596
- </View>
1597
-
1598
- {/* Live analysis unavailable warning */}
1599
- {analysis && !analysis.isLive && (
1600
- <View style={bundleStyles.liveWarningCard}>
1601
- <View
1602
- style={{
1603
- flexDirection: 'row',
1604
- alignItems: 'center',
1605
- gap: 6,
1606
- flex: 1,
1607
- }}>
1608
- <CircleAlertIcon color={AppColors.amber700} size={16} />
1609
- <View style={{flex: 1}}>
1610
- <Text style={bundleStyles.liveWarningTitle}>
1611
- {t('bundle.liveAnalysisUnavailable')}
1612
- </Text>
1613
- <Text style={bundleStyles.liveWarningDesc}>
1614
- {t('bundle.liveAnalysisUnavailableSub')}
1615
- </Text>
1616
- </View>
1617
- <TouchableScale
1618
- onPress={refreshAnalysis}
1619
- style={{
1620
- backgroundColor: `${AppColors.amber700}18`,
1621
- paddingHorizontal: 8,
1622
- paddingVertical: 5,
1623
- borderRadius: 6,
1624
- borderWidth: 1,
1625
- borderColor: `${AppColors.amber700}30`,
1626
- flexDirection: 'row',
1627
- alignItems: 'center',
1628
- gap: 4,
1629
- }}>
1630
- {isAnalyzing ? (
1631
- <ActivityIndicator size="small" color={AppColors.amber700} />
1632
- ) : (
1633
- <>
1634
- <RefreshCcwIcon color={AppColors.amber700} size={11} />
1635
- <Text
1636
- style={{
1637
- fontSize: 10.5,
1638
- fontFamily: AppFonts.interBold,
1639
- color: AppColors.amber700,
1640
- }}>
1641
- Retry
1642
- </Text>
1643
- </>
1644
- )}
1645
- </TouchableScale>
1646
- </View>
1647
- </View>
1648
- )}
1649
-
1650
- {/* Visual Type Distribution Ratio Bar */}
1651
- <View style={bundleStyles.sectionCard}>
1652
- <View style={bundleStyles.sectionHeaderRow}>
1653
- <View style={{flex: 1, paddingRight: 4}}>
1654
- <Text style={bundleStyles.sectionTitle}>
1655
- {t('bundle.treemapTitle')}
1656
- </Text>
1657
- <Text style={bundleStyles.sectionSub}>
1658
- {t('bundle.treemapSub', {mb: summary.totalMb})}
1659
- </Text>
1660
- </View>
1661
- <CopyButton
1662
- value={() => ({
1663
- totalMb: summary.totalMb,
1664
- totalKb: summary.totalKb,
1665
- nodeModules: {
1666
- kb: summary.nodeModulesKb,
1667
- pct: summary.nodeModulesPct,
1668
- },
1669
- metroRuntime: {kb: summary.metroKb, pct: summary.metroPct},
1670
- appSource: {
1671
- kb: summary.appSourceKb,
1672
- pct: summary.appSourcePct,
1673
- },
1674
- assetsMedia: {
1675
- kb: summary.assetsMediaKb,
1676
- pct: summary.assetsMediaPct,
1677
- },
1678
- })}
1679
- label={t('bundle.treemapJson')}
1680
- />
1681
- </View>
1682
-
1683
- {/* Stacked Colored Bar for Full 6.0 MB Bundle */}
1684
- <View style={bundleStyles.treemapBar}>
1685
- <View
1686
- style={{
1687
- flex: summary.nodeModulesPct,
1688
- backgroundColor: AppColors.indigo500,
1689
- height: 16,
1690
- }}
1691
- />
1692
- <View
1693
- style={{
1694
- flex: summary.metroPct,
1695
- backgroundColor: AppColors.amber500,
1696
- height: 16,
1697
- }}
1698
- />
1699
- <View
1700
- style={{
1701
- flex: summary.appSourcePct,
1702
- backgroundColor: AppColors.sky500,
1703
- height: 16,
1704
- }}
1705
- />
1706
- <View
1707
- style={{
1708
- flex: summary.assetsMediaPct,
1709
- backgroundColor: AppColors.pink500,
1710
- height: 16,
1711
- }}
1712
- />
1713
- </View>
1714
-
1715
- {/* Legend Grid */}
1716
- <View style={bundleStyles.legendGrid}>
1717
- <View style={bundleStyles.legendItem}>
1718
- <BundleCategoryIcon
1719
- type="javascript"
1720
- size={13}
1721
- color={AppColors.indigo500}
1722
- />
1723
- <Text style={bundleStyles.legendText}>
1724
- Node Modules{' '}
1725
- <Text style={bundleStyles.legendVal}>
1726
- {summary.nodeModulesKb >= 1024
1727
- ? `${(summary.nodeModulesKb / 1024).toFixed(1)} MB`
1728
- : `${summary.nodeModulesKb} KB`}{' '}
1729
- ({summary.nodeModulesPct}%)
1730
- </Text>
1731
- </Text>
1732
- </View>
1733
- <View style={bundleStyles.legendItem}>
1734
- <BundleCategoryIcon
1735
- type="overhead"
1736
- size={13}
1737
- color={AppColors.amber500}
1738
- />
1739
- <Text style={bundleStyles.legendText}>
1740
- Metro Runtime{' '}
1741
- <Text style={bundleStyles.legendVal}>
1742
- {summary.metroKb >= 1024
1743
- ? `${(summary.metroKb / 1024).toFixed(1)} MB`
1744
- : `${summary.metroKb} KB`}{' '}
1745
- ({summary.metroPct}%)
1746
- </Text>
1747
- </Text>
1748
- </View>
1749
- <View style={bundleStyles.legendItem}>
1750
- <BundleCategoryIcon
1751
- type="typescript"
1752
- size={13}
1753
- color={AppColors.sky500}
1754
- />
1755
- <Text style={bundleStyles.legendText}>
1756
- App Source{' '}
1757
- <Text style={bundleStyles.legendVal}>
1758
- {summary.appSourceKb >= 1024
1759
- ? `${(summary.appSourceKb / 1024).toFixed(1)} MB`
1760
- : `${summary.appSourceKb} KB`}{' '}
1761
- ({summary.appSourcePct}%)
1762
- </Text>
1763
- </Text>
1764
- </View>
1765
- <View style={bundleStyles.legendItem}>
1766
- <BundleCategoryIcon
1767
- type="image"
1768
- size={13}
1769
- color={AppColors.pink500}
1770
- />
1771
- <Text style={bundleStyles.legendText}>
1772
- Assets & Media{' '}
1773
- <Text style={bundleStyles.legendVal}>
1774
- {summary.assetsMediaKb >= 1024
1775
- ? `${(summary.assetsMediaKb / 1024).toFixed(1)} MB`
1776
- : `${summary.assetsMediaKb} KB`}{' '}
1777
- ({summary.assetsMediaPct}%)
1778
- </Text>
1779
- </Text>
1780
- </View>
1781
- </View>
1782
- </View>
1783
-
1784
- {/* Detailed Category Footprint Cards */}
1785
- <View style={bundleStyles.sectionCard}>
1786
- <View style={bundleStyles.sectionHeaderRow}>
1787
- <Text style={bundleStyles.sectionTitle}>
1788
- {t('bundle.categoryBreakdown')}
1789
- </Text>
1790
- <CopyButton
1791
- value={() => summary}
1792
- label={t('bundle.categoriesJson')}
1793
- />
1794
- </View>
1795
-
1796
- {summary.categories.map((cat, cIdx) => (
1797
- <Pressable
1798
- key={cIdx}
1799
- accessible={true}
1800
- accessibilityRole="button"
1801
- accessibilityLabel={`${cat.title}, ${
1802
- cat.sizeKb >= 1024 ? `${cat.sizeMb} MB` : `${cat.sizeKb} KB`
1803
- }, ${cat.pct}% of bundle`}
1804
- accessibilityHint={`Double tap to view ${cat.title} details`}
1805
- style={({pressed}) => [
1806
- bundleStyles.catRowCard,
1807
- pressed && {opacity: 0.85, transform: [{scale: 0.99}]},
1808
- ]}
1809
- onPress={() => setActiveSubTab(cat.targetTab)}>
1810
- <View style={bundleStyles.catRowTop}>
1811
- <View
1812
- style={[
1813
- bundleStyles.catIconBox,
1814
- {
1815
- backgroundColor: `${cat.color}15`,
1816
- borderColor: `${cat.color}30`,
1817
- },
1818
- ]}>
1819
- <BundleCategoryIcon
1820
- type={cat.iconType}
1821
- size={18}
1822
- color={cat.color}
1823
- />
1824
- </View>
1825
-
1826
- <View style={bundleStyles.catRowTitleWrap}>
1827
- <View
1828
- style={{
1829
- flexDirection: 'row',
1830
- alignItems: 'center',
1831
- justifyContent: 'space-between',
1832
- gap: 6,
1833
- }}>
1834
- <Text style={bundleStyles.catRowTitle} numberOfLines={1}>
1835
- {cat.title}
1836
- </Text>
1837
- <View
1838
- style={{
1839
- flexDirection: 'row',
1840
- alignItems: 'center',
1841
- gap: 6,
1842
- }}>
1843
- <Text style={bundleStyles.catRowSize}>
1844
- {cat.sizeKb >= 1024
1845
- ? `${cat.sizeMb} MB`
1846
- : `${cat.sizeKb} KB`}
1847
- </Text>
1848
- <View
1849
- style={[
1850
- bundleStyles.catPctPill,
1851
- {backgroundColor: `${cat.color}18`},
1852
- ]}>
1853
- <Text
1854
- style={[
1855
- bundleStyles.catPctText,
1856
- {color: cat.color},
1857
- ]}>
1858
- {cat.pct}%
1859
- </Text>
1860
- </View>
1861
- </View>
1862
- </View>
1863
-
1864
- <Text style={bundleStyles.catRowDesc} numberOfLines={2}>
1865
- {cat.count} items • {cat.desc}
1866
- </Text>
1867
- </View>
1868
- </View>
1869
-
1870
- {/* Modern Rounded Progress Bar */}
1871
- <View style={bundleStyles.catProgressTrack}>
1872
- <View
1873
- style={[
1874
- bundleStyles.catProgressBar,
1875
- {width: `${cat.pct}%`, backgroundColor: cat.color},
1876
- ]}
1877
- />
1878
- </View>
1879
- </Pressable>
1880
- ))}
1881
-
1882
- {/* Executive Total Summation Card */}
1883
- <View style={bundleStyles.totalSummaryCard}>
1884
- <View style={bundleStyles.totalSummaryRow}>
1885
- <View style={bundleStyles.totalSummaryLabelWrap}>
1886
- <View style={bundleStyles.totalIconBadge}>
1887
- <PackageIcon color={AppColors.brandPurple} size={16} />
1888
- </View>
1889
- <View>
1890
- <Text style={bundleStyles.totalSummaryLabel}>
1891
- TOTAL BUNDLE ASSETS
1892
- </Text>
1893
- <Text style={bundleStyles.totalSummarySub}>
1894
- {summary.totalCount} Tracked Modules & Media Assets
1895
- </Text>
1896
- </View>
1897
- </View>
1898
- <View style={{alignItems: 'flex-end'}}>
1899
- <Text style={bundleStyles.totalSummaryValue}>
1900
- {summary.totalMb} MB
1901
- </Text>
1902
- <Text style={bundleStyles.totalSummaryKb}>
1903
- {summary.totalKb.toLocaleString()} KB
1904
- </Text>
1905
- </View>
1906
- </View>
1907
-
1908
- {/* 4-Pill Chip Breakdown */}
1909
- <View style={bundleStyles.totalFormulaGrid}>
1910
- <View style={bundleStyles.totalChip}>
1911
- <View
1912
- style={[
1913
- bundleStyles.totalDot,
1914
- {backgroundColor: AppColors.indigo500},
1915
- ]}
1916
- />
1917
- <Text style={bundleStyles.totalChipText}>
1918
- NPM: {(summary.nodeModulesKb / 1024).toFixed(1)}M (
1919
- {summary.nodeModulesPct}%)
1920
- </Text>
1921
- </View>
1922
- <View style={bundleStyles.totalChip}>
1923
- <View
1924
- style={[
1925
- bundleStyles.totalDot,
1926
- {backgroundColor: AppColors.amber500},
1927
- ]}
1928
- />
1929
- <Text style={bundleStyles.totalChipText}>
1930
- Metro: {(summary.metroKb / 1024).toFixed(1)}M (
1931
- {summary.metroPct}%)
1932
- </Text>
1933
- </View>
1934
- <View style={bundleStyles.totalChip}>
1935
- <View
1936
- style={[
1937
- bundleStyles.totalDot,
1938
- {backgroundColor: AppColors.sky500},
1939
- ]}
1940
- />
1941
- <Text style={bundleStyles.totalChipText}>
1942
- Source: {(summary.appSourceKb / 1024).toFixed(1)}M (
1943
- {summary.appSourcePct}%)
1944
- </Text>
1945
- </View>
1946
- <View style={bundleStyles.totalChip}>
1947
- <View
1948
- style={[
1949
- bundleStyles.totalDot,
1950
- {backgroundColor: AppColors.pink500},
1951
- ]}
1952
- />
1953
- <Text style={bundleStyles.totalChipText}>
1954
- Media: {summary.assetsMediaKb}K ({summary.assetsMediaPct}%)
1955
- </Text>
1956
- </View>
1957
- </View>
1958
- </View>
1959
- </View>
1960
-
1961
- {/* Detailed Subsystem Architecture Split-up */}
1962
- <View style={bundleStyles.sectionCard}>
1963
- <View style={bundleStyles.sectionHeaderRow}>
1964
- <View style={{flex: 1, paddingRight: 4}}>
1965
- <Text style={bundleStyles.sectionTitle}>
1966
- Bundle Subsystem Breakdown
1967
- </Text>
1968
- <Text style={bundleStyles.sectionSub}>
1969
- Granular analysis of packages, application source, engine
1970
- runtime & assets
1971
- </Text>
1972
- </View>
1973
- <CopyButton
1974
- value={() => ({
1975
- directPackages: bundlePackages.filter(
1976
- p => p.type === 'direct',
1977
- ).length,
1978
- transitivePackages: bundlePackages.filter(
1979
- p => p.type !== 'direct',
1980
- ).length,
1981
- hermesEngine: isHermes ? 'Hermes (Bytecode)' : 'JSC (Source)',
1982
- jsDevKb: summary.totalKb,
1983
- jsHermesReleaseKb: Math.round(summary.totalKb * 0.38),
1984
- appSourceFiles: bundleFiles.filter(
1985
- f =>
1986
- f.category === 'typescript' ||
1987
- f.category === 'javascript',
1988
- ).length,
1989
- mediaAssets: bundleFiles.filter(
1990
- f =>
1991
- f.category === 'image' ||
1992
- f.category === 'font' ||
1993
- f.category === 'json',
1994
- ).length,
1995
- })}
1996
- label="Subsystem Split JSON"
1997
- />
1998
- </View>
1999
-
2000
- {/* 1. NPM Dependencies Layer */}
2001
- <View style={bundleStyles.subsystemCard}>
2002
- <View style={bundleStyles.subsystemHeader}>
2003
- <View
2004
- style={{flexDirection: 'row', alignItems: 'center', gap: 6}}>
2005
- <BundleCategoryIcon
2006
- type="deps"
2007
- size={16}
2008
- color={AppColors.indigo500}
2009
- />
2010
- <Text style={bundleStyles.subsystemTitle}>
2011
- NPM & Third-Party Dependencies
2012
- </Text>
2013
- </View>
2014
- <View
2015
- style={[
2016
- bundleStyles.subsystemBadge,
2017
- {backgroundColor: `${AppColors.indigo500}18`},
2018
- ]}>
2019
- <Text
2020
- style={[
2021
- bundleStyles.subsystemBadgeText,
2022
- {color: AppColors.indigo500},
2023
- ]}>
2024
- {(summary.nodeModulesKb / 1024).toFixed(1)} MB •{' '}
2025
- {summary.nodeModulesPct}%
2026
- </Text>
2027
- </View>
2028
- </View>
2029
- <View style={bundleStyles.subsystemGrid}>
2030
- <View style={bundleStyles.subsystemItem}>
2031
- <Text style={bundleStyles.subsystemItemText}>
2032
- Direct:{' '}
2033
- <Text style={bundleStyles.subsystemItemVal}>
2034
- {bundlePackages.filter(p => p.type === 'direct').length ||
2035
- 12}{' '}
2036
- pkgs
2037
- </Text>
2038
- </Text>
2039
- </View>
2040
- <View style={bundleStyles.subsystemItem}>
2041
- <Text style={bundleStyles.subsystemItemText}>
2042
- Transitive:{' '}
2043
- <Text style={bundleStyles.subsystemItemVal}>
2044
- {bundlePackages.filter(p => p.type !== 'direct').length ||
2045
- 41}{' '}
2046
- pkgs
2047
- </Text>
2048
- </Text>
2049
- </View>
2050
- <View style={bundleStyles.subsystemItem}>
2051
- <Text style={bundleStyles.subsystemItemText}>
2052
- Top Package:{' '}
2053
- <Text style={bundleStyles.subsystemItemVal}>
2054
- {bundlePackages[0]?.name || 'react-native'} (
2055
- {bundlePackages[0]?.sizeKb || 1240} KB)
2056
- </Text>
2057
- </Text>
2058
- </View>
2059
- </View>
2060
- </View>
2061
-
2062
- {/* 2. App Source Code Layer */}
2063
- <View style={bundleStyles.subsystemCard}>
2064
- <View style={bundleStyles.subsystemHeader}>
2065
- <View
2066
- style={{flexDirection: 'row', alignItems: 'center', gap: 6}}>
2067
- <BundleCategoryIcon
2068
- type="source"
2069
- size={16}
2070
- color={AppColors.sky500}
2071
- />
2072
- <Text style={bundleStyles.subsystemTitle}>
2073
- Application Source Code
2074
- </Text>
2075
- </View>
2076
- <View
2077
- style={[
2078
- bundleStyles.subsystemBadge,
2079
- {backgroundColor: `${AppColors.sky500}18`},
2080
- ]}>
2081
- <Text
2082
- style={[
2083
- bundleStyles.subsystemBadgeText,
2084
- {color: AppColors.sky500},
2085
- ]}>
2086
- {(summary.appSourceKb / 1024).toFixed(1)} MB •{' '}
2087
- {summary.appSourcePct}%
2088
- </Text>
2089
- </View>
2090
- </View>
2091
- <View style={bundleStyles.subsystemGrid}>
2092
- <View style={bundleStyles.subsystemItem}>
2093
- <Text style={bundleStyles.subsystemItemText}>
2094
- TypeScript/TSX:{' '}
2095
- <Text style={bundleStyles.subsystemItemVal}>
2096
- {bundleFiles.filter(f => f.category === 'typescript')
2097
- .length || 14}{' '}
2098
- files
2099
- </Text>
2100
- </Text>
2101
- </View>
2102
- <View style={bundleStyles.subsystemItem}>
2103
- <Text style={bundleStyles.subsystemItemText}>
2104
- JavaScript/JSX:{' '}
2105
- <Text style={bundleStyles.subsystemItemVal}>
2106
- {bundleFiles.filter(f => f.category === 'javascript')
2107
- .length || 3}{' '}
2108
- files
2109
- </Text>
2110
- </Text>
2111
- </View>
2112
- <View style={bundleStyles.subsystemItem}>
2113
- <Text style={bundleStyles.subsystemItemText}>
2114
- Tracked Modules:{' '}
2115
- <Text style={bundleStyles.subsystemItemVal}>
2116
- {bundleFiles.length || 18} files
2117
- </Text>
2118
- </Text>
2119
- </View>
2120
- </View>
2121
- </View>
2122
-
2123
- {/* 3. Hermes Optimization & Bytecode Engine */}
2124
- <View style={bundleStyles.subsystemCard}>
2125
- <View style={bundleStyles.subsystemHeader}>
2126
- <View
2127
- style={{flexDirection: 'row', alignItems: 'center', gap: 6}}>
2128
- <BundleCategoryIcon
2129
- type="bolt"
2130
- size={16}
2131
- color={AppColors.emerald600}
2132
- />
2133
- <Text style={bundleStyles.subsystemTitle}>
2134
- Hermes Bytecode Engine
2135
- </Text>
2136
- </View>
2137
- <View
2138
- style={[
2139
- bundleStyles.subsystemBadge,
2140
- {backgroundColor: `${AppColors.emerald500}18`},
2141
- ]}>
2142
- <Text
2143
- style={[
2144
- bundleStyles.subsystemBadgeText,
2145
- {color: AppColors.emerald700},
2146
- ]}>
2147
- ~62% AOT Compression
2148
- </Text>
2149
- </View>
2150
- </View>
2151
- <View style={bundleStyles.subsystemGrid}>
2152
- <View style={bundleStyles.subsystemItem}>
2153
- <Text style={bundleStyles.subsystemItemText}>
2154
- Dev JS Size:{' '}
2155
- <Text style={bundleStyles.subsystemItemVal}>
2156
- {summary.totalMb} MB
2157
- </Text>
2158
- </Text>
2159
- </View>
2160
- <View style={bundleStyles.subsystemItem}>
2161
- <Text style={bundleStyles.subsystemItemText}>
2162
- Hermes Bytecode:{' '}
2163
- <Text style={bundleStyles.subsystemItemVal}>
2164
- {((summary.totalKb * 0.38) / 1024).toFixed(2)} MB
2165
- </Text>
2166
- </Text>
2167
- </View>
2168
- <View style={bundleStyles.subsystemItem}>
2169
- <Text style={bundleStyles.subsystemItemText}>
2170
- Execution:{' '}
2171
- <Text style={bundleStyles.subsystemItemVal}>
2172
- {isHermes ? 'Hermes JSI Direct' : 'JSC Standard'}
2173
- </Text>
2174
- </Text>
2175
- </View>
2176
- </View>
2177
- </View>
2178
-
2179
- {/* 4. Assets & Media Layer */}
2180
- <View style={bundleStyles.subsystemCard}>
2181
- <View style={bundleStyles.subsystemHeader}>
2182
- <View
2183
- style={{flexDirection: 'row', alignItems: 'center', gap: 6}}>
2184
- <BundleCategoryIcon
2185
- type="media"
2186
- size={16}
2187
- color={AppColors.pink500}
2188
- />
2189
- <Text style={bundleStyles.subsystemTitle}>
2190
- Images, Fonts & Static Assets
2191
- </Text>
2192
- </View>
2193
- <View
2194
- style={[
2195
- bundleStyles.subsystemBadge,
2196
- {backgroundColor: `${AppColors.pink500}18`},
2197
- ]}>
2198
- <Text
2199
- style={[
2200
- bundleStyles.subsystemBadgeText,
2201
- {color: AppColors.pink500},
2202
- ]}>
2203
- {summary.assetsMediaKb} KB • {summary.assetsMediaPct}%
2204
- </Text>
2205
- </View>
2206
- </View>
2207
- <View style={bundleStyles.subsystemGrid}>
2208
- <View style={bundleStyles.subsystemItem}>
2209
- <Text style={bundleStyles.subsystemItemText}>
2210
- Images & Icons:{' '}
2211
- <Text style={bundleStyles.subsystemItemVal}>
2212
- {bundleFiles.filter(f => f.category === 'image').length ||
2213
- 2}{' '}
2214
- assets
2215
- </Text>
2216
- </Text>
2217
- </View>
2218
- <View style={bundleStyles.subsystemItem}>
2219
- <Text style={bundleStyles.subsystemItemText}>
2220
- JSON Data:{' '}
2221
- <Text style={bundleStyles.subsystemItemVal}>
2222
- {bundleFiles.filter(f => f.category === 'json').length ||
2223
- 1}{' '}
2224
- files
2225
- </Text>
2226
- </Text>
2227
- </View>
2228
- <View style={bundleStyles.subsystemItem}>
2229
- <Text style={bundleStyles.subsystemItemText}>
2230
- Custom Fonts:{' '}
2231
- <Text style={bundleStyles.subsystemItemVal}>
2232
- {bundleFiles.filter(f => f.category === 'font').length ||
2233
- 0}{' '}
2234
- fonts
2235
- </Text>
2236
- </Text>
2237
- </View>
2238
- </View>
2239
- </View>
2240
- </View>
2241
- </ScrollView>
2242
- )}
2243
-
2244
- {/* ══════════════════════════════════════════════════════════════════════ */}
2245
- {/* ── 2. TAB: PRODUCTION BUILDS & PLATFORMS ───────────────────────────── */}
2246
- {/* ══════════════════════════════════════════════════════════════════════ */}
2247
- {activeSubTab === 'production' && (
2248
- <ScrollView
2249
- style={{flex: 1}}
2250
- contentContainerStyle={bundleStyles.contentContainer}
2251
- keyboardShouldPersistTaps="handled">
2252
- {/* Platform Segmented Switcher (Horizontally Scrollable) */}
2253
- <ScrollView
2254
- horizontal
2255
- showsHorizontalScrollIndicator={false}
2256
- contentContainerStyle={bundleStyles.prodPlatformScroll}>
2257
- {[
2258
- {
2259
- key: 'ios' as const,
2260
- label: 'iOS App (.ipa)',
2261
- badge: `${
2262
- analysis?.production.ios.totalDownloadMb || 111.9
2263
- } MB`,
2264
- },
2265
- {
2266
- key: 'androidAab' as const,
2267
- label: 'Android AAB (.aab)',
2268
- badge: `${
2269
- analysis?.production.androidAab.totalDownloadMb || 38.5
2270
- } MB`,
2271
- },
2272
- {
2273
- key: 'androidApk' as const,
2274
- label: 'Universal APK (.apk)',
2275
- badge: `${
2276
- analysis?.production.androidApk.totalInstallMb || 364.0
2277
- } MB`,
2278
- },
2279
- ].map(tab => {
2280
- const isSelected = prodPlatform === tab.key;
2281
- return (
2282
- <TouchableScale
2283
- key={tab.key}
2284
- accessible={true}
2285
- accessibilityRole="tab"
2286
- accessibilityLabel={`${tab.label}, ${tab.badge}`}
2287
- accessibilityState={{selected: isSelected}}
2288
- accessibilityHint={`Switches production estimate to ${tab.label}`}
2289
- onPress={() => setProdPlatform(tab.key)}
2290
- style={[
2291
- bundleStyles.prodPlatformBtn,
2292
- isSelected && bundleStyles.prodPlatformBtnActive,
2293
- ]}>
2294
- <Text
2295
- style={[
2296
- bundleStyles.prodPlatformBtnText,
2297
- isSelected && bundleStyles.prodPlatformBtnTextActive,
2298
- ]}>
2299
- {tab.label}
2300
- </Text>
2301
- <View
2302
- style={[
2303
- bundleStyles.prodPlatformBadge,
2304
- isSelected && bundleStyles.prodPlatformBadgeActive,
2305
- ]}>
2306
- <Text
2307
- style={[
2308
- bundleStyles.prodPlatformBadgeText,
2309
- isSelected && bundleStyles.prodPlatformBadgeTextActive,
2310
- ]}>
2311
- {tab.badge}
2312
- </Text>
2313
- </View>
2314
- </TouchableScale>
2315
- );
2316
- })}
2317
- </ScrollView>
2318
-
2319
- {/* Production Platform Metrics Hero */}
2320
- {analysis && (
2321
- <View style={bundleStyles.heroCard}>
2322
- <View style={bundleStyles.heroTopRow}>
2323
- <View style={bundleStyles.heroIconWrap}>
2324
- <PackageIcon color={AppColors.brandPurple} size={24} />
2325
- </View>
2326
- <View style={{flex: 1}}>
2327
- <Text style={bundleStyles.heroTitle}>
2328
- {prodPlatform === 'ios'
2329
- ? 'iOS Production Binary Footprint'
2330
- : prodPlatform === 'androidAab'
2331
- ? 'Google Play App Bundle (.aab)'
2332
- : 'Universal Standalone APK (.apk)'}
2333
- </Text>
2334
- <Text style={bundleStyles.heroSubtitle}>
2335
- {prodPlatform === 'ios'
2336
- ? 'App Store install & over-the-air cellular download estimates'
2337
- : prodPlatform === 'androidAab'
2338
- ? 'Optimized per-device dynamic delivery split APK architecture'
2339
- : 'Multi-ABI universal install archive for sideloading & direct distribution'}
2340
- </Text>
2341
- </View>
2342
- <CopyButton
2343
- value={() => analysis.production[prodPlatform]}
2344
- label="Production Binary JSON"
2345
- />
2346
- </View>
2347
-
2348
- {/* 4-Stat Balanced 2x2 Metric Grid */}
2349
- <View style={bundleStyles.metricsGrid}>
2350
- <View style={bundleStyles.metricBox}>
2351
- <Text style={bundleStyles.metricLabel}>INSTALL SIZE</Text>
2352
- <Text
2353
- style={[
2354
- bundleStyles.metricValue,
2355
- {color: AppColors.purple},
2356
- ]}>
2357
- {analysis.production[prodPlatform].totalInstallMb} MB
2358
- </Text>
2359
- <Text style={bundleStyles.metricHint}>
2360
- On-device uncompressed footprint
2361
- </Text>
2362
- </View>
2363
-
2364
- <View style={bundleStyles.metricBox}>
2365
- <Text style={bundleStyles.metricLabel}>DOWNLOAD SIZE</Text>
2366
- <Text
2367
- style={[
2368
- bundleStyles.metricValue,
2369
- {color: AppColors.emerald600},
2370
- ]}>
2371
- ~{analysis.production[prodPlatform].totalDownloadMb} MB
2372
- </Text>
2373
- <Text style={bundleStyles.metricHint}>
2374
- Store network transfer payload
2375
- </Text>
2376
- </View>
2377
-
2378
- <View style={bundleStyles.metricBox}>
2379
- <Text style={bundleStyles.metricLabel}>COMPRESSION</Text>
2380
- <Text
2381
- style={[
2382
- bundleStyles.metricValue,
2383
- {color: AppColors.sky500},
2384
- ]}>
2385
- {analysis.production[prodPlatform].compressionRatioPct}%
2386
- </Text>
2387
- <Text style={bundleStyles.metricHint}>
2388
- Bytecode & asset ratio
2389
- </Text>
2390
- </View>
2391
-
2392
- <View style={bundleStyles.metricBox}>
2393
- <Text style={bundleStyles.metricLabel}>
2394
- FORMAT ARCHITECTURE
2395
- </Text>
2396
- <Text
2397
- style={[
2398
- bundleStyles.metricValue,
2399
- {color: AppColors.amber700},
2400
- ]}>
2401
- {prodPlatform === 'ios'
2402
- ? 'Mach-O 64-bit'
2403
- : prodPlatform === 'androidAab'
2404
- ? 'Split APKs'
2405
- : 'FAT Multi-ABI'}
2406
- </Text>
2407
- <Text style={bundleStyles.metricHint}>
2408
- {prodPlatform === 'ios'
2409
- ? 'Apple ARM64 runtime'
2410
- : prodPlatform === 'androidAab'
2411
- ? 'Dynamic Google Play delivery'
2412
- : 'Sideload universal package'}
2413
- </Text>
2414
- </View>
2415
- </View>
2416
- </View>
2417
- )}
2418
-
2419
- {/* Component Breakdown Cards */}
2420
- {analysis && (
2421
- <View style={bundleStyles.sectionCard}>
2422
- <View style={bundleStyles.sectionHeaderRow}>
2423
- <View style={{flex: 1, paddingRight: 4}}>
2424
- <Text style={bundleStyles.sectionTitle}>
2425
- Binary Component Architecture
2426
- </Text>
2427
- <Text style={bundleStyles.sectionSub}>
2428
- Compiled native libraries, runtime bytecodes, assets, and
2429
- signature blocks
2430
- </Text>
2431
- </View>
2432
- <CopyButton
2433
- value={() => analysis.production[prodPlatform].components}
2434
- label="Components JSON"
2435
- />
2436
- </View>
2437
-
2438
- {/* Visual Binary Component Ratio Bar */}
2439
- <View style={bundleStyles.prodTreemapBar}>
2440
- {analysis.production[prodPlatform].components.map(
2441
- (comp, cIdx) => (
2442
- <View
2443
- key={cIdx}
2444
- style={{
2445
- flex: Math.max(comp.pct, 2),
2446
- backgroundColor: comp.color,
2447
- height: 16,
2448
- }}
2449
- />
2450
- ),
2451
- )}
2452
- </View>
2453
-
2454
- {/* Production Binary Legend */}
2455
- <View style={[bundleStyles.legendGrid, {marginBottom: 12}]}>
2456
- {analysis.production[prodPlatform].components.map(
2457
- (comp, cIdx) => (
2458
- <View key={cIdx} style={bundleStyles.legendItem}>
2459
- <View
2460
- style={{
2461
- width: 8,
2462
- height: 8,
2463
- borderRadius: 4,
2464
- backgroundColor: comp.color,
2465
- }}
2466
- />
2467
- <Text style={bundleStyles.legendText} numberOfLines={1}>
2468
- {comp.name}{' '}
2469
- <Text style={bundleStyles.legendVal}>
2470
- {comp.sizeMb} MB ({comp.pct}%)
2471
- </Text>
2472
- </Text>
2473
- </View>
2474
- ),
2475
- )}
2476
- </View>
2477
-
2478
- {analysis.production[prodPlatform].components.map((comp, idx) => (
2479
- <View key={idx} style={bundleStyles.catRowCard}>
2480
- <View style={bundleStyles.catRowTop}>
2481
- <View
2482
- style={[
2483
- bundleStyles.catIconBox,
2484
- {
2485
- backgroundColor: `${comp.color}15`,
2486
- borderColor: `${comp.color}30`,
2487
- },
2488
- ]}>
2489
- <BundleCategoryIcon
2490
- type={
2491
- comp.category === 'native'
2492
- ? 'source'
2493
- : comp.category === 'frameworks'
2494
- ? 'deps'
2495
- : comp.category === 'assets'
2496
- ? 'media'
2497
- : comp.category === 'js'
2498
- ? 'javascript'
2499
- : 'overhead'
2500
- }
2501
- size={18}
2502
- color={comp.color}
2503
- />
2504
- </View>
2505
-
2506
- <View style={bundleStyles.catRowTitleWrap}>
2507
- <View
2508
- style={{
2509
- flexDirection: 'row',
2510
- alignItems: 'center',
2511
- justifyContent: 'space-between',
2512
- gap: 6,
2513
- }}>
2514
- <Text
2515
- style={bundleStyles.catRowTitle}
2516
- numberOfLines={1}>
2517
- {comp.name}
2518
- </Text>
2519
- <View
2520
- style={{
2521
- flexDirection: 'row',
2522
- alignItems: 'center',
2523
- gap: 6,
2524
- }}>
2525
- <Text style={bundleStyles.catRowSize}>
2526
- {comp.sizeMb} MB
2527
- </Text>
2528
- <View
2529
- style={[
2530
- bundleStyles.catPctPill,
2531
- {backgroundColor: `${comp.color}18`},
2532
- ]}>
2533
- <Text
2534
- style={[
2535
- bundleStyles.catPctText,
2536
- {color: comp.color},
2537
- ]}>
2538
- {comp.pct}%
2539
- </Text>
2540
- </View>
2541
- </View>
2542
- </View>
2543
-
2544
- <Text style={bundleStyles.catRowDesc} numberOfLines={2}>
2545
- {comp.description}
2546
- </Text>
2547
- </View>
2548
- </View>
2549
-
2550
- <View style={bundleStyles.catProgressTrack}>
2551
- <View
2552
- style={[
2553
- bundleStyles.catProgressBar,
2554
- {width: `${comp.pct}%`, backgroundColor: comp.color},
2555
- ]}
2556
- />
2557
- </View>
2558
-
2559
- {comp.advice ? (
2560
- <View
2561
- style={[
2562
- bundleStyles.adviceBadge,
2563
- bundleStyles.adviceOptimal,
2564
- {marginTop: 6},
2565
- ]}>
2566
- <LightbulbIcon color={AppColors.emerald700} size={13} />
2567
- <Text
2568
- style={[
2569
- bundleStyles.adviceText,
2570
- {color: AppColors.emerald700, flex: 1, marginLeft: 4},
2571
- ]}>
2572
- {comp.advice}
2573
- </Text>
2574
- </View>
2575
- ) : null}
2576
- </View>
2577
- ))}
2578
-
2579
- {/* Total Platform Binary Summary with JS Source Bundle Comparison */}
2580
- <View style={bundleStyles.totalSummaryCard}>
2581
- <View style={bundleStyles.totalSummaryRow}>
2582
- <View style={bundleStyles.totalSummaryLabelWrap}>
2583
- <View style={bundleStyles.totalIconBadge}>
2584
- <PackageIcon color={AppColors.brandPurple} size={16} />
2585
- </View>
2586
- <View>
2587
- <Text style={bundleStyles.totalSummaryLabel}>
2588
- {prodPlatform === 'ios'
2589
- ? 'TOTAL IOS APP BINARY'
2590
- : prodPlatform === 'androidAab'
2591
- ? 'TOTAL ANDROID AAB BUNDLE'
2592
- : 'TOTAL UNIVERSAL APK'}
2593
- </Text>
2594
- <Text style={bundleStyles.totalSummarySub}>
2595
- {prodPlatform === 'ios'
2596
- ? 'App Store install & cellular download estimates'
2597
- : prodPlatform === 'androidAab'
2598
- ? 'Google Play dynamic delivery split APK'
2599
- : 'Universal standalone sideload package'}
2600
- </Text>
2601
- </View>
2602
- </View>
2603
- </View>
2604
-
2605
- {/* Platform Metric Grid Chips */}
2606
- <View style={bundleStyles.totalFormulaGrid}>
2607
- <View style={bundleStyles.totalChip}>
2608
- <View
2609
- style={[
2610
- bundleStyles.totalDot,
2611
- {backgroundColor: AppColors.purple},
2612
- ]}
2613
- />
2614
- <Text style={bundleStyles.totalChipText}>
2615
- Install:{' '}
2616
- {analysis.production[prodPlatform].totalInstallMb} MB
2617
- </Text>
2618
- </View>
2619
- <View style={bundleStyles.totalChip}>
2620
- <View
2621
- style={[
2622
- bundleStyles.totalDot,
2623
- {backgroundColor: AppColors.sky500},
2624
- ]}
2625
- />
2626
- <Text style={bundleStyles.totalChipText}>
2627
- Download: ~
2628
- {analysis.production[prodPlatform].totalDownloadMb} MB
2629
- </Text>
2630
- </View>
2631
- <View style={bundleStyles.totalChip}>
2632
- <View
2633
- style={[
2634
- bundleStyles.totalDot,
2635
- {backgroundColor: AppColors.emerald500},
2636
- ]}
2637
- />
2638
- <Text style={bundleStyles.totalChipText}>
2639
- Compression:{' '}
2640
- {analysis.production[prodPlatform].compressionRatioPct}%
2641
- </Text>
2642
- </View>
2643
- </View>
2644
- </View>
2645
- </View>
2646
- )}
2647
- </ScrollView>
2648
- )}
2649
-
2650
- {/* ══════════════════════════════════════════════════════════════════════ */}
2651
- {/* ── 2. TAB: FILES BREAKDOWN ─────────────────────────────────────────── */}
2652
- {/* ══════════════════════════════════════════════════════════════════════ */}
2653
- {activeSubTab === 'files' && (
2654
- <View style={{flex: 1}}>
2655
- {/* Search & Category Filter */}
2656
- <View style={bundleStyles.filterContainer}>
2657
- <View style={bundleStyles.searchRow}>
2658
- <SearchIcon color={AppColors.grayTextWeak} size={15} />
2659
- <TextInput
2660
- accessible={true}
2661
- accessibilityRole="search"
2662
- accessibilityLabel="Search files"
2663
- accessibilityHint="Type a filename, path, or extension to filter"
2664
- placeholder={t('bundle.searchFilesPlaceholder')}
2665
- placeholderTextColor={AppColors.grayTextWeak}
2666
- value={search}
2667
- onChangeText={setSearch}
2668
- style={bundleStyles.searchInput}
2669
- autoCorrect={false}
2670
- autoCapitalize="none"
2671
- />
2672
- {search.length > 0 && (
2673
- <Pressable
2674
- accessible={true}
2675
- accessibilityRole="button"
2676
- accessibilityLabel="Clear search input"
2677
- onPress={() => setSearch('')}
2678
- hitSlop={10}>
2679
- <ClearIcon color={AppColors.grayTextWeak} size={14} />
2680
- </Pressable>
2681
- )}
2682
- <CopyButton
2683
- value={() => filteredFiles}
2684
- label={t('bundle.filteredFilesJson')}
2685
- />
2686
- </View>
2687
-
2688
- {/* Category Filter Chips */}
2689
- <ScrollView
2690
- horizontal
2691
- showsHorizontalScrollIndicator={false}
2692
- contentContainerStyle={bundleStyles.categoryPillsScroll}>
2693
- {CATEGORY_TABS.map(tab => {
2694
- const isActive = activeCategory === tab.key;
2695
- const count =
2696
- tab.key === 'ALL'
2697
- ? bundleFiles.length
2698
- : tab.key === 'UNUSED'
2699
- ? bundleFiles.filter(f => f.isConsumed === false).length
2700
- : tab.key === 'CONSUMED'
2701
- ? bundleFiles.filter(f => f.isConsumed !== false).length
2702
- : bundleFiles.filter(f => f.category === tab.key).length;
2703
-
2704
- return (
2705
- <TouchableScale
2706
- key={tab.key}
2707
- accessible={true}
2708
- accessibilityRole="button"
2709
- accessibilityLabel={`Filter by ${t(
2710
- tab.labelKey,
2711
- )}, ${count} items`}
2712
- accessibilityState={{selected: isActive}}
2713
- accessibilityHint={`Filters files list to ${t(
2714
- tab.labelKey,
2715
- )}`}
2716
- onPress={() => setActiveCategory(tab.key)}
2717
- style={[
2718
- bundleStyles.catPill,
2719
- isActive && bundleStyles.catPillActive,
2720
- tab.key === 'UNUSED' &&
2721
- isActive && {
2722
- backgroundColor: AppColors.red500,
2723
- borderColor: AppColors.red600,
2724
- },
2725
- tab.key === 'CONSUMED' &&
2726
- isActive && {
2727
- backgroundColor: AppColors.emerald600,
2728
- borderColor: AppColors.emerald700,
2729
- },
2730
- ]}>
2731
- <BundleCategoryIcon
2732
- type={tab.iconType}
2733
- size={12}
2734
- color={
2735
- isActive ? AppColors.white : AppColors.grayTextStrong
2736
- }
2737
- />
2738
- <Text
2739
- style={[
2740
- bundleStyles.catPillText,
2741
- isActive && bundleStyles.catPillTextActive,
2742
- ]}>
2743
- {t(tab.labelKey)} ({count})
2744
- </Text>
2745
- </TouchableScale>
2746
- );
2747
- })}
2748
- </ScrollView>
2749
- </View>
2750
-
2751
- {/* Files List / Tree Content */}
2752
- <ScrollView
2753
- style={{flex: 1}}
2754
- contentContainerStyle={bundleStyles.contentContainer}
2755
- keyboardShouldPersistTaps="handled">
2756
- {/* View Mode Toggle Header Bar */}
2757
- <View style={bundleStyles.treeHeaderRow}>
2758
- <View style={bundleStyles.viewModeToggleGroup}>
2759
- <TouchableScale
2760
- accessible={true}
2761
- accessibilityRole="button"
2762
- accessibilityLabel="Tree folder view"
2763
- accessibilityState={{selected: filesViewMode === 'tree'}}
2764
- onPress={() => setFilesViewMode('tree')}
2765
- style={[
2766
- bundleStyles.viewModeBtn,
2767
- filesViewMode === 'tree' && bundleStyles.viewModeBtnActive,
2768
- ]}>
2769
- <Text
2770
- style={[
2771
- bundleStyles.viewModeBtnText,
2772
- filesViewMode === 'tree' &&
2773
- bundleStyles.viewModeBtnTextActive,
2774
- ]}>
2775
- {t('bundle.treeView')}
2776
- </Text>
2777
- </TouchableScale>
2778
- <TouchableScale
2779
- accessible={true}
2780
- accessibilityRole="button"
2781
- accessibilityLabel="Flat list view"
2782
- accessibilityState={{selected: filesViewMode === 'list'}}
2783
- onPress={() => setFilesViewMode('list')}
2784
- style={[
2785
- bundleStyles.viewModeBtn,
2786
- filesViewMode === 'list' && bundleStyles.viewModeBtnActive,
2787
- ]}>
2788
- <Text
2789
- style={[
2790
- bundleStyles.viewModeBtnText,
2791
- filesViewMode === 'list' &&
2792
- bundleStyles.viewModeBtnTextActive,
2793
- ]}>
2794
- {t('bundle.flatList')}
2795
- </Text>
2796
- </TouchableScale>
2797
- </View>
2798
-
2799
- {filesViewMode === 'tree' ? (
2800
- <View
2801
- style={{flexDirection: 'row', alignItems: 'center', gap: 8}}>
2802
- <TouchableScale
2803
- accessible={true}
2804
- accessibilityRole="button"
2805
- accessibilityLabel="Expand all folders"
2806
- onPress={expandAllFolders}>
2807
- <Text style={bundleStyles.treeActionLink}>
2808
- {t('bundle.expand')}
2809
- </Text>
2810
- </TouchableScale>
2811
- <Text style={{color: AppColors.grayTextWeak, fontSize: 11}}>
2812
- •
2813
- </Text>
2814
- <TouchableScale
2815
- accessible={true}
2816
- accessibilityRole="button"
2817
- accessibilityLabel="Collapse all folders"
2818
- onPress={collapseAllFolders}>
2819
- <Text style={bundleStyles.treeActionLink}>
2820
- {t('bundle.collapse')}
2821
- </Text>
2822
- </TouchableScale>
2823
- </View>
2824
- ) : (
2825
- <Text style={bundleStyles.listHeaderCount}>
2826
- {t('bundle.showingFilesOf', {
2827
- shown: filteredFiles.length,
2828
- total: bundleFiles.length,
2829
- })}
2830
- </Text>
2831
- )}
2832
- </View>
2833
-
2834
- {/* ── MODE 1: DIRECTORY TREE VIEW ── */}
2835
- {filesViewMode === 'tree' ? (
2836
- <View style={bundleStyles.treeRootCard}>
2837
- {fileTreeNodes.map((node, nIdx) => (
2838
- <BundleTreeNodeView
2839
- key={node.id}
2840
- node={node}
2841
- level={0}
2842
- search={search}
2843
- totalBundleKb={summary.totalKb}
2844
- expandedFolders={expandedFolders}
2845
- toggleFolder={toggleFolder}
2846
- isLastChild={nIdx === fileTreeNodes.length - 1}
2847
- onDownload={f => downloadBundleFile(f, analysis?.scriptURL)}
2848
- />
2849
- ))}
2850
- </View>
2851
- ) : (
2852
- /* ── MODE 2: FLAT LIST VIEW ── */
2853
- filteredFiles.map((file, index) => {
2854
- const categoryMeta =
2855
- CATEGORY_COLORS[file.category] || CATEGORY_COLORS.typescript;
2856
- const pctOfTotal = (
2857
- (file.sizeKb / summary.totalKb) *
2858
- 100
2859
- ).toFixed(1);
2860
- const isUnused = file.isConsumed === false;
2861
-
2862
- return (
2863
- <View
2864
- key={file.id}
2865
- style={[
2866
- bundleStyles.fileCard,
2867
- isUnused && bundleStyles.treeFileCardUnused,
2868
- ]}>
2869
- <View style={bundleStyles.fileCardTop}>
2870
- <View style={bundleStyles.sNoBadge}>
2871
- <Text style={bundleStyles.sNoText}>#{index + 1}</Text>
2872
- </View>
2873
-
2874
- <MediaPreviewThumbnail file={file} />
2875
-
2876
- <View style={{flex: 1, paddingHorizontal: 4}}>
2877
- <View
2878
- style={{
2879
- flexDirection: 'row',
2880
- alignItems: 'center',
2881
- gap: 6,
2882
- flexWrap: 'wrap',
2883
- }}>
2884
- <HighlightText
2885
- text={file.name}
2886
- search={search}
2887
- style={bundleStyles.fileName}
2888
- highlightStyle={bundleStyles.searchHighlight}
2889
- />
2890
- {isUnused ? (
2891
- <View style={bundleStyles.unusedBadge}>
2892
- <Text style={bundleStyles.unusedBadgeText}>
2893
- {t('bundle.notConsumed')}
2894
- </Text>
2895
- </View>
2896
- ) : (
2897
- <View style={bundleStyles.consumedBadge}>
2898
- <Text style={bundleStyles.consumedBadgeText}>
2899
- {t('bundle.consumed')}
2900
- </Text>
2901
- </View>
2902
- )}
2903
- </View>
2904
- <HighlightText
2905
- text={file.path}
2906
- search={search}
2907
- style={bundleStyles.filePath}
2908
- highlightStyle={bundleStyles.searchHighlight}
2909
- numberOfLines={1}
2910
- />
2911
- </View>
2912
-
2913
- <View
2914
- style={{
2915
- flexDirection: 'row',
2916
- alignItems: 'center',
2917
- gap: 6,
2918
- }}>
2919
- <View style={bundleStyles.fileSizeBox}>
2920
- <Text
2921
- style={[
2922
- bundleStyles.fileSizeKb,
2923
- isUnused && {color: AppColors.amber800Warm},
2924
- ]}>
2925
- {file.sizeKb >= 1024
2926
- ? t('bundle.fileSizeMb', {
2927
- size: (file.sizeKb / 1024).toFixed(2),
2928
- })
2929
- : t('bundle.fileSizeKb', {size: file.sizeKb})}
2930
- </Text>
2931
- <Text style={bundleStyles.filePercent}>
2932
- {pctOfTotal}%
2933
- </Text>
2934
- </View>
2935
- <TouchableScale
2936
- onPress={() =>
2937
- downloadBundleFile(file, analysis?.scriptURL)
2938
- }
2939
- style={bundleStyles.downloadFileBtn}
2940
- hitSlop={8}>
2941
- <DownloadIcon size={14} color={AppColors.sky500} />
2942
- </TouchableScale>
2943
- <CopyButton
2944
- value={() => file}
2945
- label={t('bundle.fileDetailsJson')}
2946
- />
2947
- </View>
2948
- </View>
2949
-
2950
- <View style={bundleStyles.fileProgressTrack}>
2951
- <View
2952
- style={[
2953
- bundleStyles.fileProgressBar,
2954
- {
2955
- width: `${Math.min(
2956
- 100,
2957
- Math.max(4, Number(pctOfTotal) * 4),
2958
- )}%`,
2959
- backgroundColor: isUnused
2960
- ? AppColors.red500
2961
- : categoryMeta.color,
2962
- },
2963
- ]}
2964
- />
2965
- </View>
2966
-
2967
- <View style={bundleStyles.fileCardBottom}>
2968
- <Text style={bundleStyles.fileMetaText}>{file.meta}</Text>
2969
- {file.advice && (
2970
- <View
2971
- style={[
2972
- bundleStyles.adviceBadge,
2973
- file.status === 'warning' &&
2974
- bundleStyles.adviceWarning,
2975
- file.status === 'optimal' &&
2976
- bundleStyles.adviceOptimal,
2977
- isUnused && bundleStyles.adviceUnused,
2978
- ]}>
2979
- {isUnused ? (
2980
- <TrashIcon color={AppColors.red600} size={12} />
2981
- ) : file.status === 'warning' ? (
2982
- <LightbulbIcon
2983
- color={AppColors.amber800Warm}
2984
- size={12}
2985
- />
2986
- ) : (
2987
- <SparkleIcon
2988
- color={AppColors.emerald700}
2989
- size={12}
2990
- />
2991
- )}
2992
- <Text
2993
- style={[
2994
- bundleStyles.adviceText,
2995
- file.status === 'warning' && {
2996
- color: AppColors.amber800Warm,
2997
- },
2998
- file.status === 'optimal' && {
2999
- color: AppColors.emerald700,
3000
- },
3001
- isUnused && {color: AppColors.red600},
3002
- ]}>
3003
- {file.advice}
3004
- </Text>
3005
- </View>
3006
- )}
3007
- </View>
3008
- </View>
3009
- );
3010
- })
3011
- )}
3012
- </ScrollView>
3013
-
3014
- {/* Sticky Total Bundle Size Footer */}
3015
- <View style={bundleStyles.stickyFooterBar}>
3016
- <View style={{flexDirection: 'row', alignItems: 'center', gap: 6}}>
3017
- <LayersIcon color={AppColors.purple} size={14} />
3018
- <Text style={bundleStyles.footerTitle}>
3019
- {t('bundle.filesOf', {
3020
- count: filteredFiles.length,
3021
- total: summary.totalCount,
3022
- })}
3023
- </Text>
3024
- </View>
3025
- <Text style={bundleStyles.footerSizeVal}>
3026
- {t('bundle.kbMbValue', {
3027
- kb: filteredTotalKb,
3028
- mb: (filteredTotalKb / 1024).toFixed(2),
3029
- })}
3030
- </Text>
3031
- </View>
3032
- </View>
3033
- )}
3034
-
3035
- {/* ══════════════════════════════════════════════════════════════════════ */}
3036
- {/* ── 3. TAB: PACKAGES & NODE_MODULES ─────────────────────────────────── */}
3037
- {/* ══════════════════════════════════════════════════════════════════════ */}
3038
- {activeSubTab === 'packages' && (
3039
- <View style={{flex: 1}}>
3040
- <ScrollView
3041
- style={{flex: 1}}
3042
- contentContainerStyle={bundleStyles.contentContainer}
3043
- keyboardShouldPersistTaps="handled">
3044
- <View style={bundleStyles.filterContainer}>
3045
- <View style={bundleStyles.searchRow}>
3046
- <SearchIcon color={AppColors.grayTextWeak} size={15} />
3047
- <TextInput
3048
- accessible={true}
3049
- accessibilityRole="search"
3050
- accessibilityLabel="Search packages"
3051
- accessibilityHint="Type a package name or description to filter"
3052
- placeholder={t('bundle.searchPackagesPlaceholder')}
3053
- placeholderTextColor={AppColors.grayTextWeak}
3054
- value={search}
3055
- onChangeText={setSearch}
3056
- style={bundleStyles.searchInput}
3057
- autoCorrect={false}
3058
- autoCapitalize="none"
3059
- />
3060
- {search.length > 0 && (
3061
- <Pressable
3062
- accessible={true}
3063
- accessibilityRole="button"
3064
- accessibilityLabel="Clear search input"
3065
- onPress={() => setSearch('')}
3066
- hitSlop={10}>
3067
- <ClearIcon color={AppColors.grayTextWeak} size={14} />
3068
- </Pressable>
3069
- )}
3070
- <CopyButton
3071
- value={() => filteredPackages}
3072
- label={t('bundle.dependenciesJson')}
3073
- />
3074
- </View>
3075
- </View>
3076
-
3077
- <Text style={bundleStyles.listHeaderCount}>
3078
- {t('bundle.showingDependencies', {
3079
- count: filteredPackages.length,
3080
- })}
3081
- </Text>
3082
-
3083
- {filteredPackages.map((pkg, index) => {
3084
- const cleanInstalledVersion = pkg.version
3085
- ? pkg.version.replace(/^\^/, '')
3086
- : '';
3087
- const hasUpdate =
3088
- !!pkg.latestVersion &&
3089
- !!cleanInstalledVersion &&
3090
- pkg.latestVersion !== cleanInstalledVersion;
3091
-
3092
- return (
3093
- <View
3094
- key={pkg.id}
3095
- style={[
3096
- bundleStyles.fileCard,
3097
- pkg.isDeprecated && {
3098
- borderColor: AppColors.errorBorder,
3099
- backgroundColor: AppColors.errorCardBg,
3100
- },
3101
- ]}>
3102
- {/* Header Row: #s.no + Logo + Package Name + Version Pill + Update/Deprecated Badges + Size & Copy */}
3103
- <View style={bundleStyles.fileCardTop}>
3104
- <View style={bundleStyles.sNoBadge}>
3105
- <Text style={bundleStyles.sNoText}>#{index + 1}</Text>
3106
- </View>
3107
-
3108
- <PackageLogoRenderer
3109
- name={pkg.name}
3110
- color={pkg.color}
3111
- size={28}
3112
- />
3113
-
3114
- <View style={{flex: 1, paddingHorizontal: 4}}>
3115
- <View
3116
- style={{
3117
- flexDirection: 'row',
3118
- alignItems: 'center',
3119
- gap: 6,
3120
- flexWrap: 'wrap',
3121
- }}>
3122
- <HighlightText
3123
- text={pkg.name}
3124
- search={search}
3125
- style={bundleStyles.fileName}
3126
- highlightStyle={bundleStyles.searchHighlight}
3127
- />
3128
- <View style={bundleStyles.versionPill}>
3129
- <Text style={bundleStyles.versionPillText}>
3130
- {cleanInstalledVersion
3131
- ? t('bundle.versionPrefix', {
3132
- version: cleanInstalledVersion,
3133
- })
3134
- : t('bundle.bundled')}
3135
- </Text>
3136
- </View>
3137
-
3138
- {pkg.isDeprecated ? (
3139
- <View style={bundleStyles.deprecatedBadge}>
3140
- <Text style={bundleStyles.deprecatedBadgeText}>
3141
- {t('bundle.deprecated')}
3142
- </Text>
3143
- </View>
3144
- ) : hasUpdate ? (
3145
- <View style={bundleStyles.updateBadge}>
3146
- <Text style={bundleStyles.updateBadgeText}>
3147
- {t('bundle.updateAvailable', {
3148
- version: pkg.latestVersion,
3149
- })}
3150
- </Text>
3151
- </View>
3152
- ) : cleanInstalledVersion ? (
3153
- <View
3154
- style={[
3155
- bundleStyles.updateBadge,
3156
- {
3157
- backgroundColor: AppColors.gray100,
3158
- borderColor: AppColors.gray200,
3159
- },
3160
- ]}>
3161
- <Text
3162
- style={[
3163
- bundleStyles.updateBadgeText,
3164
- {color: AppColors.gray600},
3165
- ]}>
3166
- {t('bundle.upToDate')}
3167
- </Text>
3168
- </View>
3169
- ) : (
3170
- <View
3171
- style={[
3172
- bundleStyles.updateBadge,
3173
- {
3174
- backgroundColor: AppColors.gray100,
3175
- borderColor: AppColors.gray200,
3176
- },
3177
- ]}>
3178
- <Text
3179
- style={[
3180
- bundleStyles.updateBadgeText,
3181
- {color: AppColors.gray600},
3182
- ]}>
3183
- {t('bundle.bundledBadge')}
3184
- </Text>
3185
- </View>
3186
- )}
3187
- </View>
3188
- </View>
3189
-
3190
- <View
3191
- style={{
3192
- flexDirection: 'row',
3193
- alignItems: 'center',
3194
- gap: 6,
3195
- }}>
3196
- <View style={bundleStyles.fileSizeBox}>
3197
- <Text style={bundleStyles.fileSizeKb}>
3198
- {t('bundle.fileSizeKb', {size: pkg.sizeKb})}
3199
- </Text>
3200
- <Text style={bundleStyles.filePercent}>
3201
- {pkg.percentage}%
3202
- </Text>
3203
- </View>
3204
- <CopyButton
3205
- value={() => pkg}
3206
- label={t('bundle.packageDetailsJson')}
3207
- />
3208
- </View>
3209
- </View>
3210
-
3211
- {/* Subtitle Row: Category Chip + Description */}
3212
- <View
3213
- style={{
3214
- flexDirection: 'row',
3215
- alignItems: 'center',
3216
- gap: 6,
3217
- marginTop: 4,
3218
- marginBottom: 2,
3219
- }}>
3220
- <View
3221
- style={[
3222
- bundleStyles.pkgBadge,
3223
- {
3224
- backgroundColor: `${pkg.color}1F`,
3225
- borderColor: `${pkg.color}4D`,
3226
- },
3227
- ]}>
3228
- <Text
3229
- style={[bundleStyles.pkgBadgeText, {color: pkg.color}]}>
3230
- {pkg.category.toUpperCase()}
3231
- </Text>
3232
- </View>
3233
- <Text
3234
- style={[bundleStyles.filePath, {flex: 1, marginTop: 0}]}
3235
- numberOfLines={1}>
3236
- {pkg.description}
3237
- </Text>
3238
- </View>
3239
-
3240
- {/* Deprecation Warning Banner if deprecated */}
3241
- {pkg.isDeprecated && pkg.deprecationReason && (
3242
- <View style={bundleStyles.deprecationBox}>
3243
- <View
3244
- style={{
3245
- flexDirection: 'row',
3246
- alignItems: 'center',
3247
- gap: 5,
3248
- }}>
3249
- <CircleAlertIcon
3250
- color={AppColors.errorColor}
3251
- size={12}
3252
- />
3253
- <Text style={bundleStyles.deprecationText}>
3254
- {pkg.deprecationReason}
3255
- </Text>
3256
- </View>
3257
- </View>
3258
- )}
3259
-
3260
- <View style={bundleStyles.fileProgressTrack}>
3261
- <View
3262
- style={[
3263
- bundleStyles.fileProgressBar,
3264
- {
3265
- width: `${pkg.percentage * 2}%`,
3266
- backgroundColor: pkg.isDeprecated
3267
- ? AppColors.red500
3268
- : pkg.color,
3269
- },
3270
- ]}
3271
- />
3272
- </View>
3273
-
3274
- {/* Footer Row: Type Tag + Last Active + NPM Link Button */}
3275
- <View
3276
- style={{
3277
- flexDirection: 'row',
3278
- justifyContent: 'space-between',
3279
- alignItems: 'center',
3280
- marginTop: 3,
3281
- }}>
3282
- <View
3283
- style={{
3284
- flexDirection: 'row',
3285
- alignItems: 'center',
3286
- gap: 8,
3287
- }}>
3288
- <View style={bundleStyles.typeTag}>
3289
- <Text style={bundleStyles.typeTagText}>
3290
- {pkg.type === 'direct'
3291
- ? t('bundle.direct')
3292
- : t('bundle.transitive')}
3293
- </Text>
3294
- </View>
3295
- {pkg.lastActive && (
3296
- <View
3297
- style={{
3298
- flexDirection: 'row',
3299
- alignItems: 'center',
3300
- gap: 4,
3301
- }}>
3302
- <ClockIcon color={AppColors.grayTextWeak} size={11} />
3303
- <Text style={bundleStyles.lastActiveText}>
3304
- {pkg.lastActive}
3305
- </Text>
3306
- </View>
3307
- )}
3308
- </View>
3309
-
3310
- <View
3311
- style={{
3312
- flexDirection: 'row',
3313
- alignItems: 'center',
3314
- gap: 8,
3315
- }}>
3316
- <Text style={bundleStyles.fileMetaText}>
3317
- {t('bundle.minified', {size: pkg.sizeKb})}
3318
- </Text>
3319
- <TouchableScale
3320
- accessible={true}
3321
- accessibilityRole="link"
3322
- accessibilityLabel={`Open ${pkg.name} on NPM`}
3323
- accessibilityHint="Opens NPM package page in browser"
3324
- onPress={() => {
3325
- const url =
3326
- pkg.npmUrl ||
3327
- `https://www.npmjs.com/package/${pkg.name}`;
3328
- Linking.openURL(url).catch(() => {});
3329
- }}
3330
- style={bundleStyles.npmLinkBtn}>
3331
- <Text style={bundleStyles.npmLinkText}>
3332
- {t('bundle.npmLink')}
3333
- </Text>
3334
- </TouchableScale>
3335
- </View>
3336
- </View>
3337
- </View>
3338
- );
3339
- })}
3340
- </ScrollView>
3341
-
3342
- {/* Sticky Total Package Size Footer */}
3343
- <View style={bundleStyles.stickyFooterBar}>
3344
- <View style={{flexDirection: 'row', alignItems: 'center', gap: 6}}>
3345
- <PackageIcon color={AppColors.purple} size={14} />
3346
- <Text style={bundleStyles.footerTitle}>
3347
- {t('bundle.dependenciesCount', {
3348
- count: filteredPackages.length,
3349
- })}
3350
- </Text>
3351
- </View>
3352
- <Text style={bundleStyles.footerSizeVal}>
3353
- {t('bundle.kbMbValue', {
3354
- kb: packagesTotalKb,
3355
- mb: (packagesTotalKb / 1024).toFixed(2),
3356
- })}
3357
- </Text>
3358
- </View>
3359
- </View>
3360
- )}
3361
-
3362
- {/* ══════════════════════════════════════════════════════════════════════ */}
3363
- {/* ── 4. TAB: MEDIA & ASSET AUDITOR ───────────────────────────────────── */}
3364
- {/* ══════════════════════════════════════════════════════════════════════ */}
3365
- {activeSubTab === 'media' && (
3366
- <View style={{flex: 1}}>
3367
- <ScrollView
3368
- style={{flex: 1}}
3369
- contentContainerStyle={bundleStyles.contentContainer}
3370
- keyboardShouldPersistTaps="handled">
3371
- <View style={bundleStyles.tipsCard}>
3372
- <View
3373
- style={{
3374
- flexDirection: 'row',
3375
- alignItems: 'center',
3376
- justifyContent: 'space-between',
3377
- marginBottom: 8,
3378
- }}>
3379
- <View
3380
- style={{flexDirection: 'row', alignItems: 'center', gap: 6}}>
3381
- <StorageIcon color={AppColors.purple} size={18} />
3382
- <Text style={bundleStyles.tipsHeading}>
3383
- {t('bundle.mediaAuditorTitle')}
3384
- </Text>
3385
- </View>
3386
- <CopyButton
3387
- value={() => mediaFiles}
3388
- label={t('bundle.mediaAssetsJson')}
3389
- />
3390
- </View>
3391
- <Text style={bundleStyles.tipDesc}>
3392
- {t('bundle.mediaAuditorPrefix')}{' '}
3393
- <Text
3394
- style={{
3395
- fontFamily: AppFonts.interBold,
3396
- color: AppColors.purple,
3397
- }}>
3398
- {summary.images.pct + summary.fonts.pct}%
3399
- </Text>{' '}
3400
- {t('bundle.mediaAuditorMid', {
3401
- kb: summary.images.kb + summary.fonts.kb,
3402
- })}{' '}
3403
- <Text
3404
- style={{
3405
- fontFamily: AppFonts.interBold,
3406
- color: AppColors.emerald700,
3407
- }}>
3408
- {t('bundle.mediaSavings', {size: 540})}
3409
- </Text>{' '}
3410
- {t('bundle.mediaAuditorSuffix')}
3411
- </Text>
3412
- </View>
3413
-
3414
- <Text style={bundleStyles.listHeaderCount}>
3415
- {t('bundle.mediaAssetsList', {count: mediaFiles.length})}
3416
- </Text>
3417
-
3418
- {mediaFiles.map((file, index) => (
3419
- <View key={file.id} style={bundleStyles.fileCard}>
3420
- <View style={bundleStyles.fileCardTop}>
3421
- <View style={bundleStyles.sNoBadge}>
3422
- <Text style={bundleStyles.sNoText}>#{index + 1}</Text>
3423
- </View>
3424
-
3425
- <MediaPreviewThumbnail file={file} />
3426
-
3427
- <View style={{flex: 1}}>
3428
- <Text style={bundleStyles.fileName}>{file.name}</Text>
3429
- <Text style={bundleStyles.filePath}>{file.path}</Text>
3430
- </View>
3431
-
3432
- <View
3433
- style={{
3434
- flexDirection: 'row',
3435
- alignItems: 'center',
3436
- gap: 6,
3437
- }}>
3438
- <View style={bundleStyles.fileSizeBox}>
3439
- <Text style={bundleStyles.fileSizeKb}>
3440
- {t('bundle.fileSizeKb', {size: file.sizeKb})}
3441
- </Text>
3442
- </View>
3443
- <TouchableScale
3444
- accessible={true}
3445
- accessibilityRole="button"
3446
- accessibilityLabel={`Download ${file.name}`}
3447
- accessibilityHint="Downloads media asset file"
3448
- onPress={() =>
3449
- downloadBundleFile(file, analysis?.scriptURL)
3450
- }
3451
- style={bundleStyles.downloadFileBtn}
3452
- hitSlop={8}>
3453
- <DownloadIcon size={14} color={AppColors.sky500} />
3454
- </TouchableScale>
3455
- <CopyButton
3456
- value={() => file}
3457
- label={t('bundle.mediaItemJson')}
3458
- />
3459
- </View>
3460
- </View>
3461
-
3462
- <View style={bundleStyles.fileCardBottom}>
3463
- <Text style={bundleStyles.fileMetaText}>{file.meta}</Text>
3464
- {file.advice && (
3465
- <View
3466
- style={[
3467
- bundleStyles.adviceBadge,
3468
- file.status === 'warning' && bundleStyles.adviceWarning,
3469
- file.status === 'optimal' && bundleStyles.adviceOptimal,
3470
- ]}>
3471
- {file.status === 'warning' ? (
3472
- <LightbulbIcon
3473
- color={AppColors.amber800Warm}
3474
- size={12}
3475
- />
3476
- ) : (
3477
- <SparkleIcon color={AppColors.emerald700} size={12} />
3478
- )}
3479
- <Text
3480
- style={[
3481
- bundleStyles.adviceText,
3482
- file.status === 'warning' && {
3483
- color: AppColors.amber800Warm,
3484
- },
3485
- file.status === 'optimal' && {
3486
- color: AppColors.emerald700,
3487
- },
3488
- ]}>
3489
- {file.advice}
3490
- </Text>
3491
- </View>
3492
- )}
3493
- </View>
3494
- </View>
3495
- ))}
3496
- </ScrollView>
3497
-
3498
- {/* Sticky Total Media Size Footer */}
3499
- <View style={bundleStyles.stickyFooterBar}>
3500
- <View style={{flexDirection: 'row', alignItems: 'center', gap: 6}}>
3501
- <StorageIcon color={AppColors.purple} size={14} />
3502
- <Text style={bundleStyles.footerTitle}>
3503
- {t('bundle.mediaAndFonts', {count: mediaFiles.length})}
3504
- </Text>
3505
- </View>
3506
- <Text style={bundleStyles.footerSizeVal}>
3507
- {t('bundle.kbMbValue', {
3508
- kb: mediaTotalKb,
3509
- mb: (mediaTotalKb / 1024).toFixed(2),
3510
- })}
3511
- </Text>
3512
- </View>
3513
- </View>
3514
- )}
3515
-
3516
- {/* ══════════════════════════════════════════════════════════════════════ */}
3517
- {/* ── 5. TAB: OPTIMIZER CHECKLIST ─────────────────────────────────────── */}
3518
- {/* ══════════════════════════════════════════════════════════════════════ */}
3519
- {activeSubTab === 'optimizer' && (
3520
- <ScrollView
3521
- style={{flex: 1}}
3522
- contentContainerStyle={bundleStyles.contentContainer}
3523
- keyboardShouldPersistTaps="handled">
3524
- <View style={bundleStyles.tipsCard}>
3525
- <View
3526
- style={{
3527
- flexDirection: 'row',
3528
- alignItems: 'center',
3529
- justifyContent: 'space-between',
3530
- marginBottom: 12,
3531
- }}>
3532
- <View
3533
- style={{flexDirection: 'row', alignItems: 'center', gap: 6}}>
3534
- <CircleAlertIcon color={AppColors.purple} size={18} />
3535
- <Text style={bundleStyles.tipsHeading}>
3536
- {t('bundle.optimizerTitle')}
3537
- </Text>
3538
- </View>
3539
- <CopyButton
3540
- value={() => [
3541
- t('bundle.optTip1Title'),
3542
- `${t('bundle.optTip2Title')} (${
3543
- isHermes ? t('bundle.active') : t('bundle.actionRequired')
3544
- })`,
3545
- t('bundle.optTip3Title'),
3546
- t('bundle.optTip4Title'),
3547
- t('bundle.optTip5Title'),
3548
- ]}
3549
- label={t('bundle.optimizationChecklist')}
3550
- />
3551
- </View>
3552
-
3553
- {[
3554
- {
3555
- iconType: 'image' as const,
3556
- iconColor: AppColors.pink500,
3557
- title: t('bundle.optTip1Title'),
3558
- desc: t('bundle.optTip1Desc'),
3559
- badge: t('bundle.highImpact'),
3560
- badgeColor: AppColors.pink500,
3561
- },
3562
- {
3563
- iconType: 'bolt' as const,
3564
- iconColor: isHermes ? AppColors.emerald500 : AppColors.amber500,
3565
- title: t('bundle.optTip2Title'),
3566
- desc: isHermes
3567
- ? t('bundle.optTip2DescActive')
3568
- : t('bundle.optTip2DescInactive'),
3569
- badge: isHermes
3570
- ? t('bundle.active')
3571
- : t('bundle.actionRequired'),
3572
- badgeColor: isHermes
3573
- ? AppColors.emerald500
3574
- : AppColors.amber500,
3575
- },
3576
- {
3577
- iconType: 'font' as const,
3578
- iconColor: AppColors.purple500,
3579
- title: t('bundle.optTip3Title'),
3580
- desc: t('bundle.optTip3Desc'),
3581
- badge: t('bundle.mediumImpact'),
3582
- badgeColor: AppColors.purple500,
3583
- },
3584
- {
3585
- iconType: 'source' as const,
3586
- iconColor: AppColors.sky500,
3587
- title: t('bundle.optTip4Title'),
3588
- desc: t('bundle.optTip4Desc'),
3589
- badge: t('bundle.bestPractice'),
3590
- badgeColor: AppColors.sky500,
3591
- },
3592
- {
3593
- iconType: 'overhead' as const,
3594
- iconColor: AppColors.indigo500,
3595
- title: t('bundle.optTip5Title'),
3596
- desc: t('bundle.optTip5Desc'),
3597
- badge: t('bundle.bestPractice'),
3598
- badgeColor: AppColors.indigo500,
3599
- },
3600
- ].map((tip, tIdx) => (
3601
- <View key={tIdx} style={bundleStyles.tipItem}>
3602
- <View style={bundleStyles.tipItemHeader}>
3603
- <View style={bundleStyles.tipTitleWrap}>
3604
- <View style={bundleStyles.sNoBadge}>
3605
- <Text style={bundleStyles.sNoText}>#{tIdx + 1}</Text>
3606
- </View>
3607
- <BundleCategoryIcon
3608
- type={tip.iconType}
3609
- size={14}
3610
- color={tip.iconColor}
3611
- />
3612
- <Text style={bundleStyles.tipTitle}>{tip.title}</Text>
3613
- </View>
3614
- <View style={bundleStyles.tipActionWrap}>
3615
- <View
3616
- style={[
3617
- bundleStyles.impactBadge,
3618
- {
3619
- backgroundColor: `${tip.badgeColor}18`,
3620
- borderColor: `${tip.badgeColor}40`,
3621
- },
3622
- ]}>
3623
- <Text
3624
- style={[
3625
- bundleStyles.impactBadgeText,
3626
- {color: tip.badgeColor},
3627
- ]}>
3628
- {tip.badge}
3629
- </Text>
3630
- </View>
3631
- <CopyButton
3632
- value={() => tip}
3633
- label={t('bundle.tipDetails')}
3634
- />
3635
- </View>
3636
- </View>
3637
- <Text style={bundleStyles.tipDesc}>{tip.desc}</Text>
3638
- </View>
3639
- ))}
3640
- </View>
3641
- </ScrollView>
3642
- )}
3643
- </View>
3644
- );
3645
- });
3646
-
3647
- const bundleStyles = StyleSheet.create({
3648
- topTabWrap: {
3649
- backgroundColor: AppColors.white,
3650
- paddingVertical: 8,
3651
- borderBottomWidth: 1,
3652
- borderBottomColor: AppColors.dividerColor,
3653
- flexDirection: 'row',
3654
- alignItems: 'center',
3655
- paddingRight: 10,
3656
- },
3657
- reloadButton: {
3658
- padding: 8,
3659
- borderRadius: 20,
3660
- backgroundColor: AppColors.grayBackground,
3661
- borderWidth: 1,
3662
- borderColor: AppColors.dividerColor,
3663
- alignItems: 'center',
3664
- justifyContent: 'center',
3665
- marginLeft: 4,
3666
- },
3667
- analyzingContainer: {
3668
- flex: 1,
3669
- alignItems: 'center',
3670
- justifyContent: 'center',
3671
- gap: 8,
3672
- padding: 24,
3673
- },
3674
- analyzingText: {
3675
- fontFamily: AppFonts.interBold,
3676
- fontSize: 13,
3677
- color: AppColors.primaryBlack,
3678
- },
3679
- analyzingHint: {
3680
- fontFamily: AppFonts.interRegular,
3681
- fontSize: 11,
3682
- color: AppColors.grayTextWeak,
3683
- },
3684
- subTabsScroll: {
3685
- paddingHorizontal: 12,
3686
- gap: 8,
3687
- flexDirection: 'row',
3688
- alignItems: 'center',
3689
- },
3690
- subTabPill: {
3691
- flexDirection: 'row',
3692
- alignItems: 'center',
3693
- gap: 6,
3694
- paddingHorizontal: 12,
3695
- paddingVertical: 7,
3696
- borderRadius: 20,
3697
- backgroundColor: AppColors.grayBackground,
3698
- borderWidth: 1,
3699
- borderColor: AppColors.dividerColor,
3700
- },
3701
- subTabPillActive: {
3702
- backgroundColor: AppColors.brandPurple,
3703
- borderColor: AppColors.brandPurple,
3704
- shadowColor: AppColors.brandPurple,
3705
- shadowOffset: {width: 0, height: 2},
3706
- shadowOpacity: 0.2,
3707
- shadowRadius: 3,
3708
- elevation: 2,
3709
- },
3710
- subTabPillText: {
3711
- fontFamily: AppFonts.interMedium,
3712
- fontSize: 11.5,
3713
- color: AppColors.grayTextStrong,
3714
- },
3715
- subTabPillTextActive: {
3716
- fontFamily: AppFonts.interBold,
3717
- color: AppColors.white,
3718
- },
3719
- contentContainer: {
3720
- padding: 16,
3721
- paddingBottom: 40,
3722
- gap: 14,
3723
- },
3724
- heroCard: {
3725
- backgroundColor: AppColors.white,
3726
- borderRadius: 14,
3727
- padding: 16,
3728
- borderWidth: 1,
3729
- borderColor: AppColors.dividerColor,
3730
- shadowColor: AppColors.primaryBlack,
3731
- shadowOffset: {width: 0, height: 2},
3732
- shadowOpacity: 0.04,
3733
- shadowRadius: 6,
3734
- elevation: 2,
3735
- },
3736
- heroTopRow: {
3737
- flexDirection: 'row',
3738
- alignItems: 'flex-start',
3739
- gap: 12,
3740
- marginBottom: 16,
3741
- },
3742
- heroIconWrap: {
3743
- width: 44,
3744
- height: 44,
3745
- borderRadius: 12,
3746
- backgroundColor: `${AppColors.brandPurple}12`,
3747
- alignItems: 'center',
3748
- justifyContent: 'center',
3749
- borderWidth: 1,
3750
- borderColor: `${AppColors.brandPurple}25`,
3751
- },
3752
- heroTitle: {
3753
- fontFamily: AppFonts.interBold,
3754
- fontSize: 16,
3755
- color: AppColors.primaryBlack,
3756
- },
3757
- heroSubtitle: {
3758
- fontFamily: AppFonts.interRegular,
3759
- fontSize: 12,
3760
- color: AppColors.grayTextWeak,
3761
- marginTop: 2,
3762
- },
3763
- metricsGrid: {
3764
- flexDirection: 'row',
3765
- flexWrap: 'wrap',
3766
- gap: 8,
3767
- },
3768
- metricBox: {
3769
- flex: 1,
3770
- minWidth: '45%',
3771
- backgroundColor: AppColors.grayBackground,
3772
- borderRadius: 10,
3773
- padding: 10,
3774
- borderWidth: 1,
3775
- borderColor: AppColors.dividerColor,
3776
- },
3777
- metricLabel: {
3778
- fontFamily: AppFonts.interBold,
3779
- fontSize: 9.5,
3780
- color: AppColors.grayTextWeak,
3781
- letterSpacing: 0.5,
3782
- },
3783
- metricValue: {
3784
- fontFamily: AppFonts.interBold,
3785
- fontSize: 14,
3786
- color: AppColors.primaryBlack,
3787
- marginTop: 3,
3788
- },
3789
- metricHint: {
3790
- fontFamily: AppFonts.interMedium,
3791
- fontSize: 10.5,
3792
- color: AppColors.grayText,
3793
- marginTop: 2,
3794
- },
3795
- statusIndicator: {
3796
- width: 8,
3797
- height: 8,
3798
- borderRadius: 4,
3799
- },
3800
- prodPlatformScroll: {
3801
- flexDirection: 'row',
3802
- alignItems: 'center',
3803
- gap: 8,
3804
- paddingBottom: 4,
3805
- },
3806
- prodPlatformBtn: {
3807
- flexDirection: 'row',
3808
- alignItems: 'center',
3809
- paddingVertical: 7,
3810
- paddingHorizontal: 12,
3811
- borderRadius: 20,
3812
- backgroundColor: AppColors.white,
3813
- borderWidth: 1,
3814
- borderColor: AppColors.dividerColor,
3815
- gap: 6,
3816
- },
3817
- prodPlatformBtnActive: {
3818
- backgroundColor: AppColors.brandPurple,
3819
- borderColor: AppColors.brandPurple,
3820
- shadowColor: AppColors.brandPurple,
3821
- shadowOffset: {width: 0, height: 2},
3822
- shadowOpacity: 0.18,
3823
- shadowRadius: 3,
3824
- elevation: 2,
3825
- },
3826
- prodPlatformBtnText: {
3827
- fontFamily: AppFonts.interBold,
3828
- fontSize: 11.5,
3829
- color: AppColors.grayTextStrong,
3830
- },
3831
- prodPlatformBtnTextActive: {
3832
- color: AppColors.white,
3833
- },
3834
- prodPlatformBadge: {
3835
- paddingHorizontal: 6,
3836
- paddingVertical: 2,
3837
- borderRadius: 10,
3838
- backgroundColor: `${AppColors.purple}14`,
3839
- },
3840
- prodPlatformBadgeActive: {
3841
- backgroundColor: 'rgba(255, 255, 255, 0.25)',
3842
- },
3843
- prodPlatformBadgeText: {
3844
- fontFamily: AppFonts.interBold,
3845
- fontSize: 9.5,
3846
- color: AppColors.purple,
3847
- },
3848
- prodPlatformBadgeTextActive: {
3849
- color: AppColors.white,
3850
- },
3851
- sectionCard: {
3852
- backgroundColor: AppColors.white,
3853
- borderRadius: 14,
3854
- padding: 14,
3855
- borderWidth: 1,
3856
- borderColor: AppColors.dividerColor,
3857
- overflow: 'hidden',
3858
- },
3859
- sectionHeaderRow: {
3860
- flexDirection: 'row',
3861
- justifyContent: 'space-between',
3862
- alignItems: 'center',
3863
- marginBottom: 10,
3864
- gap: 8,
3865
- },
3866
- sectionTitle: {
3867
- fontFamily: AppFonts.interBold,
3868
- fontSize: 13,
3869
- color: AppColors.primaryBlack,
3870
- },
3871
- sectionSub: {
3872
- fontFamily: AppFonts.interMedium,
3873
- fontSize: 11,
3874
- color: AppColors.grayTextWeak,
3875
- },
3876
- treemapBar: {
3877
- flexDirection: 'row',
3878
- borderRadius: 6,
3879
- overflow: 'hidden',
3880
- height: 16,
3881
- marginBottom: 12,
3882
- backgroundColor: AppColors.slate200,
3883
- },
3884
- prodTreemapBar: {
3885
- flexDirection: 'row',
3886
- borderRadius: 6,
3887
- overflow: 'hidden',
3888
- height: 16,
3889
- marginBottom: 12,
3890
- backgroundColor: AppColors.slate200,
3891
- },
3892
- legendGrid: {
3893
- flexDirection: 'row',
3894
- flexWrap: 'wrap',
3895
- gap: 8,
3896
- },
3897
- legendItem: {
3898
- flexDirection: 'row',
3899
- alignItems: 'center',
3900
- gap: 6,
3901
- paddingHorizontal: 8,
3902
- paddingVertical: 5,
3903
- borderRadius: 8,
3904
- backgroundColor: `${AppColors.slate200}40`,
3905
- borderWidth: 1,
3906
- borderColor: `${AppColors.dividerColor}`,
3907
- flexShrink: 0,
3908
- },
3909
- legendText: {
3910
- fontFamily: AppFonts.interMedium,
3911
- fontSize: 11,
3912
- color: AppColors.grayTextStrong,
3913
- },
3914
- legendVal: {
3915
- fontFamily: AppFonts.interBold,
3916
- color: AppColors.primaryBlack,
3917
- },
3918
- subsystemCard: {
3919
- backgroundColor: AppColors.white,
3920
- borderRadius: 12,
3921
- padding: 12,
3922
- marginBottom: 8,
3923
- borderWidth: 1,
3924
- borderColor: AppColors.dividerColor,
3925
- gap: 8,
3926
- },
3927
- subsystemHeader: {
3928
- flexDirection: 'row',
3929
- alignItems: 'center',
3930
- justifyContent: 'space-between',
3931
- gap: 8,
3932
- },
3933
- subsystemTitle: {
3934
- fontFamily: AppFonts.interBold,
3935
- fontSize: 12.5,
3936
- color: AppColors.primaryBlack,
3937
- },
3938
- subsystemBadge: {
3939
- paddingHorizontal: 6,
3940
- paddingVertical: 2,
3941
- borderRadius: 6,
3942
- },
3943
- subsystemBadgeText: {
3944
- fontFamily: AppFonts.interBold,
3945
- fontSize: 10,
3946
- },
3947
- subsystemGrid: {
3948
- flexDirection: 'row',
3949
- flexWrap: 'wrap',
3950
- gap: 6,
3951
- },
3952
- subsystemItem: {
3953
- flexDirection: 'row',
3954
- alignItems: 'center',
3955
- gap: 5,
3956
- paddingHorizontal: 8,
3957
- paddingVertical: 4,
3958
- borderRadius: 6,
3959
- backgroundColor: AppColors.grayBackground,
3960
- borderWidth: 1,
3961
- borderColor: AppColors.dividerColor,
3962
- },
3963
- subsystemItemText: {
3964
- fontFamily: AppFonts.interMedium,
3965
- fontSize: 10.5,
3966
- color: AppColors.grayTextStrong,
3967
- },
3968
- subsystemItemVal: {
3969
- fontFamily: AppFonts.interBold,
3970
- color: AppColors.primaryBlack,
3971
- },
3972
- catRowCard: {
3973
- backgroundColor: AppColors.grayBackground,
3974
- borderRadius: 12,
3975
- padding: 12,
3976
- marginBottom: 8,
3977
- borderWidth: 1,
3978
- borderColor: AppColors.dividerColor,
3979
- gap: 8,
3980
- },
3981
- catRowTop: {
3982
- flexDirection: 'row',
3983
- alignItems: 'flex-start',
3984
- gap: 10,
3985
- },
3986
- catIconBox: {
3987
- width: 36,
3988
- height: 36,
3989
- borderRadius: 10,
3990
- borderWidth: 1,
3991
- alignItems: 'center',
3992
- justifyContent: 'center',
3993
- flexShrink: 0,
3994
- },
3995
- catRowTitleWrap: {
3996
- flex: 1,
3997
- minWidth: 0,
3998
- gap: 2,
3999
- },
4000
- catRowTitle: {
4001
- fontFamily: AppFonts.interBold,
4002
- fontSize: 13,
4003
- color: AppColors.primaryBlack,
4004
- flex: 1,
4005
- },
4006
- catRowDesc: {
4007
- fontFamily: AppFonts.interRegular,
4008
- fontSize: 10.5,
4009
- color: AppColors.grayTextWeak,
4010
- lineHeight: 14.5,
4011
- },
4012
- catRowSize: {
4013
- fontFamily: AppFonts.interBold,
4014
- fontSize: 12.5,
4015
- color: AppColors.primaryBlack,
4016
- },
4017
- catRowRight: {
4018
- alignItems: 'flex-end',
4019
- justifyContent: 'center',
4020
- flexShrink: 0,
4021
- minWidth: 64,
4022
- },
4023
- catRowPct: {
4024
- fontFamily: AppFonts.interMedium,
4025
- fontSize: 10,
4026
- color: AppColors.grayTextWeak,
4027
- },
4028
- catPctPill: {
4029
- paddingHorizontal: 5.5,
4030
- paddingVertical: 1.5,
4031
- borderRadius: 5,
4032
- },
4033
- catPctText: {
4034
- fontFamily: AppFonts.interBold,
4035
- fontSize: 9.5,
4036
- },
4037
- catProgressTrack: {
4038
- height: 5,
4039
- borderRadius: 2.5,
4040
- backgroundColor: `${AppColors.slate200}`,
4041
- overflow: 'hidden',
4042
- marginTop: 2,
4043
- },
4044
- catProgressBar: {
4045
- height: '100%',
4046
- borderRadius: 2.5,
4047
- },
4048
- filterContainer: {
4049
- backgroundColor: AppColors.white,
4050
- borderRadius: 12,
4051
- padding: 10,
4052
- marginHorizontal: 16,
4053
- marginTop: 12,
4054
- borderWidth: 1,
4055
- borderColor: AppColors.dividerColor,
4056
- gap: 8,
4057
- },
4058
- searchRow: {
4059
- flexDirection: 'row',
4060
- alignItems: 'center',
4061
- backgroundColor: AppColors.grayBackground,
4062
- borderRadius: 8,
4063
- paddingHorizontal: 10,
4064
- height: 36,
4065
- gap: 8,
4066
- },
4067
- searchInput: {
4068
- flex: 1,
4069
- fontFamily: AppFonts.interRegular,
4070
- fontSize: 12,
4071
- color: AppColors.primaryBlack,
4072
- padding: 0,
4073
- },
4074
- categoryPillsScroll: {
4075
- gap: 6,
4076
- paddingVertical: 2,
4077
- },
4078
- catPill: {
4079
- flexDirection: 'row',
4080
- alignItems: 'center',
4081
- gap: 4,
4082
- paddingHorizontal: 10,
4083
- paddingVertical: 5,
4084
- borderRadius: 16,
4085
- backgroundColor: AppColors.grayBackground,
4086
- borderWidth: 1,
4087
- borderColor: AppColors.dividerColor,
4088
- },
4089
- catPillActive: {
4090
- backgroundColor: AppColors.brandPurple,
4091
- borderColor: AppColors.brandPurple,
4092
- },
4093
- catPillText: {
4094
- fontFamily: AppFonts.interMedium,
4095
- fontSize: 11,
4096
- color: AppColors.grayTextStrong,
4097
- },
4098
- catPillTextActive: {
4099
- fontFamily: AppFonts.interBold,
4100
- color: AppColors.white,
4101
- },
4102
- listHeaderCount: {
4103
- fontFamily: AppFonts.interMedium,
4104
- fontSize: 11,
4105
- color: AppColors.grayTextWeak,
4106
- paddingHorizontal: 4,
4107
- },
4108
- treeHeaderRow: {
4109
- flexDirection: 'row',
4110
- alignItems: 'center',
4111
- justifyContent: 'space-between',
4112
- paddingHorizontal: 2,
4113
- marginBottom: 6,
4114
- },
4115
- viewModeToggleGroup: {
4116
- flexDirection: 'row',
4117
- backgroundColor: AppColors.grayBackground,
4118
- borderRadius: 8,
4119
- padding: 2,
4120
- borderWidth: 1,
4121
- borderColor: AppColors.dividerColor,
4122
- },
4123
- viewModeBtn: {
4124
- paddingHorizontal: 10,
4125
- paddingVertical: 5,
4126
- borderRadius: 6,
4127
- },
4128
- viewModeBtnActive: {
4129
- backgroundColor: AppColors.brandPurple,
4130
- shadowColor: AppColors.brandPurple,
4131
- shadowOffset: {width: 0, height: 1},
4132
- shadowOpacity: 0.2,
4133
- shadowRadius: 2,
4134
- elevation: 2,
4135
- },
4136
- viewModeBtnText: {
4137
- fontFamily: AppFonts.interMedium,
4138
- fontSize: 11,
4139
- color: AppColors.grayTextStrong,
4140
- },
4141
- viewModeBtnTextActive: {
4142
- fontFamily: AppFonts.interBold,
4143
- color: AppColors.white,
4144
- },
4145
- treeActionLink: {
4146
- fontFamily: AppFonts.interBold,
4147
- fontSize: 11.5,
4148
- color: AppColors.purple,
4149
- },
4150
- treeRootCard: {
4151
- backgroundColor: AppColors.white,
4152
- borderRadius: 12,
4153
- borderWidth: 1,
4154
- borderColor: AppColors.dividerColor,
4155
- overflow: 'hidden',
4156
- marginBottom: 16,
4157
- },
4158
- treeFolderBlock: {
4159
- borderBottomWidth: StyleSheet.hairlineWidth,
4160
- borderBottomColor: AppColors.dividerColor,
4161
- },
4162
- treeFolderRow: {
4163
- flexDirection: 'row',
4164
- alignItems: 'center',
4165
- justifyContent: 'space-between',
4166
- paddingVertical: 8,
4167
- paddingRight: 10,
4168
- backgroundColor: `${AppColors.purple}06`,
4169
- },
4170
- treeRowLeft: {
4171
- flexDirection: 'row',
4172
- alignItems: 'center',
4173
- gap: 6,
4174
- flex: 1,
4175
- marginRight: 6,
4176
- },
4177
- treeRowRight: {
4178
- flexDirection: 'row',
4179
- alignItems: 'center',
4180
- gap: 6,
4181
- },
4182
- treeChevron: {
4183
- fontSize: 12,
4184
- color: AppColors.purple,
4185
- fontFamily: AppFonts.interBold,
4186
- width: 12,
4187
- textAlign: 'center',
4188
- },
4189
- treeFolderName: {
4190
- fontFamily: AppFonts.interBold,
4191
- fontSize: 12.5,
4192
- color: AppColors.primaryBlack,
4193
- },
4194
- treeCountBadge: {
4195
- paddingHorizontal: 5,
4196
- paddingVertical: 1,
4197
- borderRadius: 8,
4198
- backgroundColor: `${AppColors.purple}14`,
4199
- },
4200
- treeCountBadgeText: {
4201
- fontFamily: AppFonts.interMedium,
4202
- fontSize: 9.5,
4203
- color: AppColors.purple,
4204
- },
4205
- treeFolderSize: {
4206
- fontFamily: AppFonts.interMedium,
4207
- fontSize: 11,
4208
- color: AppColors.grayTextStrong,
4209
- },
4210
- treeChildrenContainer: {
4211
- backgroundColor: AppColors.white,
4212
- },
4213
- treeFileRow: {
4214
- flexDirection: 'row',
4215
- alignItems: 'center',
4216
- justifyContent: 'space-between',
4217
- paddingVertical: 6,
4218
- paddingRight: 10,
4219
- borderBottomWidth: StyleSheet.hairlineWidth,
4220
- borderBottomColor: `${AppColors.dividerColor}80`,
4221
- backgroundColor: AppColors.white,
4222
- },
4223
- treeFileRowUnused: {
4224
- backgroundColor: `${AppColors.red500}06`,
4225
- },
4226
- treeBranchGuide: {
4227
- width: 12,
4228
- alignItems: 'center',
4229
- justifyContent: 'center',
4230
- },
4231
- treeBranchGuideText: {
4232
- fontFamily: AppFonts.interRegular,
4233
- fontSize: 11,
4234
- color: AppColors.dividerColor,
4235
- },
4236
- treeFileName: {
4237
- fontFamily: AppFonts.interMedium,
4238
- fontSize: 12,
4239
- color: AppColors.primaryBlack,
4240
- flexShrink: 1,
4241
- },
4242
- treeUnusedPill: {
4243
- paddingHorizontal: 5,
4244
- paddingVertical: 1,
4245
- borderRadius: 4,
4246
- backgroundColor: `${AppColors.red500}18`,
4247
- },
4248
- treeUnusedPillText: {
4249
- fontFamily: AppFonts.interBold,
4250
- fontSize: 9,
4251
- color: AppColors.red600,
4252
- },
4253
- treeFileSizeGroup: {
4254
- alignItems: 'flex-end',
4255
- },
4256
- treeFileSizeText: {
4257
- fontFamily: AppFonts.interMedium,
4258
- fontSize: 11,
4259
- color: AppColors.primaryBlack,
4260
- },
4261
- treeFilePctText: {
4262
- fontFamily: AppFonts.interRegular,
4263
- fontSize: 9,
4264
- color: AppColors.grayTextWeak,
4265
- },
4266
- fileCard: {
4267
- backgroundColor: AppColors.white,
4268
- borderRadius: 12,
4269
- padding: 12,
4270
- borderWidth: 1,
4271
- borderColor: AppColors.dividerColor,
4272
- gap: 6,
4273
- marginBottom: 8,
4274
- },
4275
- fileCardTop: {
4276
- flexDirection: 'row',
4277
- alignItems: 'center',
4278
- gap: 8,
4279
- },
4280
- sNoBadge: {
4281
- paddingHorizontal: 6,
4282
- paddingVertical: 2,
4283
- borderRadius: 4,
4284
- backgroundColor: `${AppColors.purple}14`,
4285
- borderWidth: 1,
4286
- borderColor: `${AppColors.purple}30`,
4287
- minWidth: 26,
4288
- alignItems: 'center',
4289
- justifyContent: 'center',
4290
- },
4291
- sNoText: {
4292
- fontFamily: AppFonts.interBold,
4293
- fontSize: 10,
4294
- color: AppColors.purple,
4295
- },
4296
- extChip: {
4297
- paddingHorizontal: 6,
4298
- paddingVertical: 3,
4299
- borderRadius: 5,
4300
- borderWidth: 1,
4301
- },
4302
- extChipText: {
4303
- fontFamily: AppFonts.interBold,
4304
- fontSize: 10,
4305
- letterSpacing: 0.4,
4306
- },
4307
- pkgBadge: {
4308
- paddingHorizontal: 6,
4309
- paddingVertical: 3,
4310
- borderRadius: 5,
4311
- borderWidth: 1,
4312
- },
4313
- pkgBadgeText: {
4314
- fontFamily: AppFonts.interBold,
4315
- fontSize: 9,
4316
- letterSpacing: 0.4,
4317
- },
4318
- pkgLogoWrap: {
4319
- borderRadius: 7,
4320
- borderWidth: 1,
4321
- alignItems: 'center',
4322
- justifyContent: 'center',
4323
- backgroundColor: AppColors.white,
4324
- borderColor: AppColors.dividerColor,
4325
- overflow: 'hidden',
4326
- },
4327
- pkgLogoFallbackText: {
4328
- fontFamily: AppFonts.interBold,
4329
- },
4330
- mediaThumbWrap: {
4331
- width: 32,
4332
- height: 32,
4333
- borderRadius: 6,
4334
- overflow: 'hidden',
4335
- backgroundColor: AppColors.grayBackground,
4336
- borderWidth: 1,
4337
- borderColor: AppColors.dividerColor,
4338
- },
4339
- mediaThumbImg: {
4340
- width: '100%',
4341
- height: '100%',
4342
- },
4343
- fileName: {
4344
- fontFamily: AppFonts.interBold,
4345
- fontSize: 12.5,
4346
- color: AppColors.primaryBlack,
4347
- },
4348
- versionPill: {
4349
- paddingHorizontal: 5,
4350
- paddingVertical: 1,
4351
- borderRadius: 4,
4352
- backgroundColor: `${AppColors.purple}14`,
4353
- borderWidth: 1,
4354
- borderColor: `${AppColors.purple}26`,
4355
- },
4356
- versionPillText: {
4357
- fontFamily: AppFonts.interMedium,
4358
- fontSize: 10,
4359
- color: AppColors.purple,
4360
- },
4361
- pkgVersion: {
4362
- fontFamily: AppFonts.interMedium,
4363
- fontSize: 11,
4364
- color: AppColors.grayTextWeak,
4365
- },
4366
- filePath: {
4367
- fontFamily: AppFonts.interRegular,
4368
- fontSize: 10.5,
4369
- color: AppColors.grayTextWeak,
4370
- marginTop: 1,
4371
- },
4372
- fileSizeBox: {
4373
- alignItems: 'flex-end',
4374
- },
4375
- downloadFileBtn: {
4376
- padding: 6,
4377
- borderRadius: 6,
4378
- backgroundColor: `${AppColors.sky500}14`,
4379
- alignItems: 'center',
4380
- justifyContent: 'center',
4381
- borderWidth: 1,
4382
- borderColor: `${AppColors.sky500}28`,
4383
- },
4384
- treeActionBtn: {
4385
- padding: 4,
4386
- borderRadius: 4,
4387
- backgroundColor: `${AppColors.sky500}14`,
4388
- alignItems: 'center',
4389
- justifyContent: 'center',
4390
- borderWidth: 1,
4391
- borderColor: `${AppColors.sky500}28`,
4392
- },
4393
- fileSizeKb: {
4394
- fontFamily: AppFonts.interBold,
4395
- fontSize: 12,
4396
- color: AppColors.primaryBlack,
4397
- },
4398
- filePercent: {
4399
- fontFamily: AppFonts.interMedium,
4400
- fontSize: 10,
4401
- color: AppColors.grayTextWeak,
4402
- },
4403
- fileProgressTrack: {
4404
- height: 4,
4405
- borderRadius: 2,
4406
- backgroundColor: AppColors.grayBackground,
4407
- overflow: 'hidden',
4408
- marginVertical: 2,
4409
- },
4410
- fileProgressBar: {
4411
- height: '100%',
4412
- borderRadius: 2,
4413
- },
4414
- fileCardBottom: {
4415
- gap: 4,
4416
- },
4417
- fileMetaText: {
4418
- fontFamily: AppFonts.interRegular,
4419
- fontSize: 10.5,
4420
- color: AppColors.grayTextWeak,
4421
- },
4422
- typeTag: {
4423
- backgroundColor: AppColors.grayBackground,
4424
- paddingHorizontal: 6,
4425
- paddingVertical: 2,
4426
- borderRadius: 4,
4427
- borderWidth: 1,
4428
- borderColor: AppColors.dividerColor,
4429
- },
4430
- typeTagText: {
4431
- fontFamily: AppFonts.interMedium,
4432
- fontSize: 9.5,
4433
- color: AppColors.grayText,
4434
- },
4435
- npmLinkBtn: {
4436
- flexDirection: 'row',
4437
- alignItems: 'center',
4438
- gap: 3,
4439
- paddingHorizontal: 7,
4440
- paddingVertical: 2,
4441
- borderRadius: 5,
4442
- backgroundColor: AppColors.npmRedTint,
4443
- borderWidth: 1,
4444
- borderColor: AppColors.npmRedBorder,
4445
- },
4446
- npmLinkText: {
4447
- fontFamily: AppFonts.interBold,
4448
- fontSize: 9.5,
4449
- color: AppColors.npmRed,
4450
- },
4451
- deprecatedBadge: {
4452
- paddingHorizontal: 6,
4453
- paddingVertical: 1.5,
4454
- borderRadius: 4,
4455
- backgroundColor: AppColors.red100,
4456
- borderWidth: 1,
4457
- borderColor: AppColors.errorBorder,
4458
- },
4459
- deprecatedBadgeText: {
4460
- fontFamily: AppFonts.interBold,
4461
- fontSize: 9,
4462
- color: AppColors.red600,
4463
- letterSpacing: 0.3,
4464
- },
4465
- updateBadge: {
4466
- paddingHorizontal: 6,
4467
- paddingVertical: 1.5,
4468
- borderRadius: 4,
4469
- backgroundColor: AppColors.green100,
4470
- borderWidth: 1,
4471
- borderColor: AppColors.greenBorder,
4472
- },
4473
- updateBadgeText: {
4474
- fontFamily: AppFonts.interBold,
4475
- fontSize: 9,
4476
- color: AppColors.emerald700,
4477
- },
4478
- lastActiveText: {
4479
- fontFamily: AppFonts.interRegular,
4480
- fontSize: 10,
4481
- color: AppColors.grayTextWeak,
4482
- },
4483
- deprecationBox: {
4484
- backgroundColor: AppColors.errorCardBg,
4485
- borderRadius: 6,
4486
- paddingHorizontal: 8,
4487
- paddingVertical: 5,
4488
- borderWidth: 1,
4489
- borderColor: AppColors.errorBorder,
4490
- marginTop: 3,
4491
- },
4492
- deprecationText: {
4493
- fontFamily: AppFonts.interMedium,
4494
- fontSize: 10.5,
4495
- color: AppColors.rose700,
4496
- lineHeight: 14,
4497
- },
4498
- unusedBadge: {
4499
- paddingHorizontal: 6,
4500
- paddingVertical: 1.5,
4501
- borderRadius: 4,
4502
- backgroundColor: AppColors.red100,
4503
- borderWidth: 1,
4504
- borderColor: AppColors.errorBorder,
4505
- },
4506
- unusedBadgeText: {
4507
- fontFamily: AppFonts.interBold,
4508
- fontSize: 9.5,
4509
- color: AppColors.red600,
4510
- },
4511
- consumedBadge: {
4512
- paddingHorizontal: 6,
4513
- paddingVertical: 1.5,
4514
- borderRadius: 4,
4515
- backgroundColor: AppColors.green100,
4516
- borderWidth: 1,
4517
- borderColor: AppColors.greenBorder,
4518
- },
4519
- consumedBadgeText: {
4520
- fontFamily: AppFonts.interBold,
4521
- fontSize: 9.5,
4522
- color: AppColors.emerald600,
4523
- },
4524
- treeFolderChevronWrap: {
4525
- width: 18,
4526
- height: 18,
4527
- borderRadius: 9,
4528
- backgroundColor: `${AppColors.purple}14`,
4529
- alignItems: 'center',
4530
- justifyContent: 'center',
4531
- },
4532
- treeFileCardUnused: {
4533
- borderColor: AppColors.errorBorder,
4534
- backgroundColor: AppColors.red50,
4535
- },
4536
- adviceUnused: {
4537
- backgroundColor: AppColors.errorCardBg,
4538
- borderWidth: 1,
4539
- borderColor: AppColors.errorBorder,
4540
- },
4541
- liveWarningCard: {
4542
- flexDirection: 'row',
4543
- alignItems: 'center',
4544
- backgroundColor: AppColors.amber100,
4545
- borderWidth: 1,
4546
- borderColor: AppColors.amber200,
4547
- borderRadius: 10,
4548
- padding: 12,
4549
- marginBottom: 12,
4550
- },
4551
- liveWarningTitle: {
4552
- fontFamily: AppFonts.interBold,
4553
- fontSize: 12,
4554
- color: AppColors.amber800Warm,
4555
- marginBottom: 2,
4556
- },
4557
- liveWarningDesc: {
4558
- fontFamily: AppFonts.interRegular,
4559
- fontSize: 10.5,
4560
- lineHeight: 14,
4561
- color: AppColors.amber800Warm,
4562
- },
4563
- adviceBadge: {
4564
- flexDirection: 'row',
4565
- alignItems: 'center',
4566
- paddingHorizontal: 8,
4567
- paddingVertical: 5,
4568
- borderRadius: 6,
4569
- marginTop: 4,
4570
- gap: 6,
4571
- },
4572
- adviceWarning: {
4573
- backgroundColor: AppColors.amber100,
4574
- borderWidth: 1,
4575
- borderColor: AppColors.amber200,
4576
- },
4577
- adviceOptimal: {
4578
- backgroundColor: AppColors.green100,
4579
- borderWidth: 1,
4580
- borderColor: AppColors.greenBorder,
4581
- },
4582
- adviceText: {
4583
- fontFamily: AppFonts.interMedium,
4584
- fontSize: 10.5,
4585
- lineHeight: 14,
4586
- flex: 1,
4587
- },
4588
- impactBadge: {
4589
- paddingHorizontal: 6,
4590
- paddingVertical: 2,
4591
- borderRadius: 4,
4592
- borderWidth: 1,
4593
- },
4594
- impactBadgeText: {
4595
- fontFamily: AppFonts.interBold,
4596
- fontSize: 9,
4597
- },
4598
- searchHighlight: {
4599
- backgroundColor: AppColors.yellow200,
4600
- color: AppColors.yellow800,
4601
- fontFamily: AppFonts.interBold,
4602
- },
4603
- tipsCard: {
4604
- backgroundColor: AppColors.purple50,
4605
- borderRadius: 14,
4606
- padding: 14,
4607
- borderWidth: 1,
4608
- borderColor: AppColors.purple200,
4609
- },
4610
- tipsHeading: {
4611
- fontFamily: AppFonts.interBold,
4612
- fontSize: 13.5,
4613
- color: AppColors.purple800,
4614
- },
4615
- totalSummaryCard: {
4616
- marginTop: 10,
4617
- backgroundColor: `${AppColors.brandPurple}0B`,
4618
- borderRadius: 12,
4619
- padding: 12,
4620
- borderWidth: 1,
4621
- borderColor: `${AppColors.brandPurple}25`,
4622
- gap: 8,
4623
- },
4624
- totalSummaryRow: {
4625
- flexDirection: 'row',
4626
- justifyContent: 'space-between',
4627
- alignItems: 'center',
4628
- },
4629
- totalSummaryLabelWrap: {
4630
- flexDirection: 'row',
4631
- alignItems: 'center',
4632
- gap: 8,
4633
- },
4634
- totalIconBadge: {
4635
- width: 32,
4636
- height: 32,
4637
- borderRadius: 8,
4638
- backgroundColor: `${AppColors.brandPurple}15`,
4639
- alignItems: 'center',
4640
- justifyContent: 'center',
4641
- },
4642
- totalSummaryLabel: {
4643
- fontFamily: AppFonts.interBold,
4644
- fontSize: 11.5,
4645
- color: AppColors.brandPurple,
4646
- letterSpacing: 0.6,
4647
- },
4648
- totalSummarySub: {
4649
- fontFamily: AppFonts.interRegular,
4650
- fontSize: 10,
4651
- color: AppColors.grayTextWeak,
4652
- marginTop: 1,
4653
- },
4654
- totalSummaryValue: {
4655
- fontFamily: AppFonts.interBold,
4656
- fontSize: 15,
4657
- color: AppColors.primaryBlack,
4658
- },
4659
- totalSummaryKb: {
4660
- fontFamily: AppFonts.interMedium,
4661
- fontSize: 10,
4662
- color: AppColors.grayTextWeak,
4663
- },
4664
- totalFormulaGrid: {
4665
- flexDirection: 'row',
4666
- flexWrap: 'wrap',
4667
- gap: 6,
4668
- paddingTop: 6,
4669
- borderTopWidth: 1,
4670
- borderTopColor: `${AppColors.brandPurple}15`,
4671
- },
4672
- totalChip: {
4673
- flexDirection: 'row',
4674
- alignItems: 'center',
4675
- gap: 4,
4676
- backgroundColor: AppColors.white,
4677
- borderRadius: 6,
4678
- paddingHorizontal: 6,
4679
- paddingVertical: 3,
4680
- borderWidth: 1,
4681
- borderColor: AppColors.dividerColor,
4682
- },
4683
- totalDot: {
4684
- width: 6,
4685
- height: 6,
4686
- borderRadius: 3,
4687
- },
4688
- totalChipText: {
4689
- fontFamily: AppFonts.interBold,
4690
- fontSize: 9.5,
4691
- color: AppColors.primaryBlack,
4692
- },
4693
- totalSummaryFormula: {
4694
- fontFamily: AppFonts.interRegular,
4695
- fontSize: 10.5,
4696
- color: AppColors.grayText,
4697
- lineHeight: 15,
4698
- },
4699
- stickyFooterBar: {
4700
- flexDirection: 'row',
4701
- justifyContent: 'space-between',
4702
- alignItems: 'center',
4703
- backgroundColor: AppColors.white,
4704
- paddingHorizontal: 16,
4705
- paddingTop: 10,
4706
- paddingBottom: Platform.OS === 'ios' ? 24 : 12,
4707
- borderTopWidth: 1,
4708
- borderTopColor: AppColors.dividerColor,
4709
- shadowColor: AppColors.primaryBlack,
4710
- shadowOffset: {width: 0, height: -2},
4711
- shadowOpacity: 0.05,
4712
- shadowRadius: 4,
4713
- elevation: 3,
4714
- },
4715
- footerTitle: {
4716
- fontFamily: AppFonts.interBold,
4717
- fontSize: 12,
4718
- color: AppColors.primaryBlack,
4719
- },
4720
- footerSizeVal: {
4721
- fontFamily: AppFonts.interBold,
4722
- fontSize: 12.5,
4723
- color: AppColors.purple,
4724
- },
4725
- tipItem: {
4726
- marginBottom: 10,
4727
- backgroundColor: AppColors.white,
4728
- padding: 12,
4729
- borderRadius: 10,
4730
- borderWidth: 1,
4731
- borderColor: AppColors.purple200,
4732
- gap: 6,
4733
- },
4734
- tipItemHeader: {
4735
- flexDirection: 'row',
4736
- alignItems: 'flex-start',
4737
- justifyContent: 'space-between',
4738
- gap: 8,
4739
- },
4740
- tipTitleWrap: {
4741
- flexDirection: 'row',
4742
- alignItems: 'center',
4743
- gap: 6,
4744
- flex: 1,
4745
- flexShrink: 1,
4746
- },
4747
- tipActionWrap: {
4748
- flexDirection: 'row',
4749
- alignItems: 'center',
4750
- gap: 6,
4751
- flexShrink: 0,
4752
- },
4753
- tipTitle: {
4754
- fontFamily: AppFonts.interBold,
4755
- fontSize: 12,
4756
- color: AppColors.purple900,
4757
- flex: 1,
4758
- flexShrink: 1,
4759
- },
4760
- tipDesc: {
4761
- fontFamily: AppFonts.interRegular,
4762
- fontSize: 11,
4763
- color: AppColors.gray500,
4764
- lineHeight: 16,
4765
- },
4766
- });
4767
-
4768
- export default BundleTab;