react-native-inapp-inspector 2.3.19 → 2.3.21

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 (353) hide show
  1. package/README.md +14 -14
  2. package/android/src/main/java/com/inappinspector/NetworkInspectorModule.kt +67 -0
  3. package/dist/commonjs/analytics.js +1 -24
  4. package/dist/commonjs/bundle.js +1 -16
  5. package/dist/commonjs/capture.js +1 -35
  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.js +10 -87
  19. package/dist/commonjs/components/EndOfListFooter.js +7 -81
  20. package/dist/commonjs/components/ErrorBoundary.js +72 -566
  21. package/dist/commonjs/components/HeadersSection.js +31 -110
  22. package/dist/commonjs/components/HighlightText.js +17 -58
  23. package/dist/commonjs/components/Inspector/AboutModal.d.ts +6 -0
  24. package/dist/commonjs/components/Inspector/AboutModal.js +243 -0
  25. package/dist/commonjs/components/Inspector/AnalyticsFilterModal.js +98 -797
  26. package/dist/commonjs/components/Inspector/AnalyticsTab.js +284 -930
  27. package/dist/commonjs/components/Inspector/BundleTab.js +551 -2918
  28. package/dist/commonjs/components/Inspector/ConsoleTab.js +79 -492
  29. package/dist/commonjs/components/Inspector/CrashDetail.js +123 -672
  30. package/dist/commonjs/components/Inspector/CrashFilterModal.js +66 -498
  31. package/dist/commonjs/components/Inspector/CrashTab.js +98 -670
  32. package/dist/commonjs/components/Inspector/DebuggingTab.js +74 -875
  33. package/dist/commonjs/components/Inspector/DeviceInfoTab.js +143 -907
  34. package/dist/commonjs/components/Inspector/FabLauncher.js +42 -466
  35. package/dist/commonjs/components/Inspector/FeatureUnderDevNotice.js +6 -99
  36. package/dist/commonjs/components/Inspector/InspectorContext.js +1 -21
  37. package/dist/commonjs/components/Inspector/InspectorHeader.js +191 -1110
  38. package/dist/commonjs/components/Inspector/LogDetail.js +168 -994
  39. package/dist/commonjs/components/Inspector/MainScreen.js +52 -335
  40. package/dist/commonjs/components/Inspector/MediaGalleryTab.js +97 -644
  41. package/dist/commonjs/components/Inspector/MediaPreviewModal.js +90 -605
  42. package/dist/commonjs/components/Inspector/NavigationTracker.js +1 -12
  43. package/dist/commonjs/components/Inspector/NetworkDetail.js +89 -411
  44. package/dist/commonjs/components/Inspector/NetworkFilterModal.js +60 -576
  45. package/dist/commonjs/components/Inspector/NetworkTab.js +84 -514
  46. package/dist/commonjs/components/Inspector/NpmStarPrompt.js +18 -434
  47. package/dist/commonjs/components/Inspector/NpmUpdateToast.js +25 -298
  48. package/dist/commonjs/components/Inspector/PerformanceTab.js +182 -1397
  49. package/dist/commonjs/components/Inspector/ReduxDetail.js +188 -1089
  50. package/dist/commonjs/components/Inspector/ReduxTab.js +85 -701
  51. package/dist/commonjs/components/Inspector/SettingsPanel.js +427 -3807
  52. package/dist/commonjs/components/Inspector/SkeletonPlaceholder.js +11 -180
  53. package/dist/commonjs/components/Inspector/StorageTab.js +93 -826
  54. package/dist/commonjs/components/Inspector/TabBar.js +36 -334
  55. package/dist/commonjs/components/Inspector/UpdateAvailableModal.js +37 -412
  56. package/dist/commonjs/components/JsonViewer.js +168 -381
  57. package/dist/commonjs/components/LogCard.js +57 -567
  58. package/dist/commonjs/components/LogSyntaxHighlighter.js +11 -130
  59. package/dist/commonjs/components/MetaAccordion.js +69 -238
  60. package/dist/commonjs/components/MiniBarChart.js +3 -56
  61. package/dist/commonjs/components/MiniLineChart.js +3 -58
  62. package/dist/commonjs/components/NetworkIcons.d.ts +13 -0
  63. package/dist/commonjs/components/NetworkIcons.js +521 -1022
  64. package/dist/commonjs/components/QRCodeView.js +5 -79
  65. package/dist/commonjs/components/SectionHeader.js +15 -37
  66. package/dist/commonjs/components/SegmentedTabs.js +7 -62
  67. package/dist/commonjs/components/SkeletonPlaceholder.js +11 -180
  68. package/dist/commonjs/components/Slider.js +15 -243
  69. package/dist/commonjs/components/SourcePageCard.js +30 -89
  70. package/dist/commonjs/components/Toast.js +5 -147
  71. package/dist/commonjs/components/TouchableScale.js +3 -24
  72. package/dist/commonjs/components/TreeNode.js +55 -130
  73. package/dist/commonjs/console.js +1 -16
  74. package/dist/commonjs/constants/index.js +1 -32
  75. package/dist/commonjs/constants/telemetryConfig.js +1 -6
  76. package/dist/commonjs/constants/version.d.ts +1 -1
  77. package/dist/commonjs/constants/version.js +1 -4
  78. package/dist/commonjs/crash.js +1 -23
  79. package/dist/commonjs/customHooks/analyticsLogger.js +1 -250
  80. package/dist/commonjs/customHooks/bundleAnalyzer.js +1 -957
  81. package/dist/commonjs/customHooks/consoleLogger.js +6 -445
  82. package/dist/commonjs/customHooks/crashHandler.js +7 -656
  83. package/dist/commonjs/customHooks/logFilters.js +1 -23
  84. package/dist/commonjs/customHooks/networkLogger.js +2 -536
  85. package/dist/commonjs/customHooks/performanceTracker.js +63 -750
  86. package/dist/commonjs/customHooks/reduxLogger.js +2 -313
  87. package/dist/commonjs/customHooks/storageInspector.js +1 -435
  88. package/dist/commonjs/customHooks/useAccordion.js +1 -18
  89. package/dist/commonjs/decorators/index.js +1 -100
  90. package/dist/commonjs/helpers/gaAnalyticsRegistry.js +1 -148
  91. package/dist/commonjs/helpers/index.d.ts +1 -0
  92. package/dist/commonjs/helpers/index.js +8 -921
  93. package/dist/commonjs/helpers/memoryManager.js +1 -99
  94. package/dist/commonjs/helpers/qrGenerator.js +1 -367
  95. package/dist/commonjs/helpers/remoteConfig.js +1 -165
  96. package/dist/commonjs/helpers/searchQueryParser.js +1 -62
  97. package/dist/commonjs/helpers/settingsStore.js +1 -144
  98. package/dist/commonjs/helpers/telemetry.js +1 -501
  99. package/dist/commonjs/helpers/toast.js +1 -20
  100. package/dist/commonjs/i18n/index.js +1 -90
  101. package/dist/commonjs/i18n/locales/en.json +1 -1140
  102. package/dist/commonjs/index.js +5 -2168
  103. package/dist/commonjs/metro.js +6 -468
  104. package/dist/commonjs/native/NativeInspector.d.ts +1 -0
  105. package/dist/commonjs/native/NativeInspector.js +1 -415
  106. package/dist/commonjs/native/NativeNetworkInspector.js +1 -4
  107. package/dist/commonjs/network.js +1 -13
  108. package/dist/commonjs/performance.js +1 -29
  109. package/dist/commonjs/redux.js +1 -20
  110. package/dist/commonjs/storage.js +1 -14
  111. package/dist/commonjs/styles/AppColors.js +1 -423
  112. package/dist/commonjs/styles/AppFonts.js +1 -17
  113. package/dist/commonjs/styles/common.js +1 -190
  114. package/dist/commonjs/styles/index.d.ts +6 -0
  115. package/dist/commonjs/styles/index.js +1 -1589
  116. package/dist/commonjs/types/enums.d.ts +0 -1
  117. package/dist/commonjs/types/enums.js +1 -152
  118. package/dist/commonjs/types/index.js +1 -18
  119. package/dist/commonjs/types/interfaces.d.ts +2 -2
  120. package/dist/commonjs/types/interfaces.js +1 -2
  121. package/dist/esm/analytics.js +1 -2
  122. package/dist/esm/bundle.js +1 -2
  123. package/dist/esm/capture.js +1 -32
  124. package/dist/esm/components/AnalyticsDetail.js +171 -664
  125. package/dist/esm/components/AnalyticsEventCard.js +77 -335
  126. package/dist/esm/components/AnalyticsGraph.js +95 -357
  127. package/dist/esm/components/AnimatedEntrance.js +1 -6
  128. package/dist/esm/components/AppHeaderLogo.js +7 -94
  129. package/dist/esm/components/BrandCircleIcon.js +114 -119
  130. package/dist/esm/components/BrandSquareIcon.js +73 -78
  131. package/dist/esm/components/CodeSnippet.js +58 -595
  132. package/dist/esm/components/ConsoleLogCard.js +91 -526
  133. package/dist/esm/components/CopyButton.js +3 -51
  134. package/dist/esm/components/DiffViewer.js +22 -48
  135. package/dist/esm/components/DomainHeader.js +43 -138
  136. package/dist/esm/components/EmptyState.js +17 -56
  137. package/dist/esm/components/EndOfListFooter.js +15 -83
  138. package/dist/esm/components/ErrorBoundary.js +125 -582
  139. package/dist/esm/components/HeadersSection.js +52 -93
  140. package/dist/esm/components/HighlightText.js +17 -53
  141. package/dist/esm/components/Inspector/AboutModal.d.ts +6 -0
  142. package/dist/esm/components/Inspector/AboutModal.js +243 -0
  143. package/dist/esm/components/Inspector/AnalyticsFilterModal.js +225 -886
  144. package/dist/esm/components/Inspector/AnalyticsTab.js +296 -904
  145. package/dist/esm/components/Inspector/BundleTab.js +1175 -3502
  146. package/dist/esm/components/Inspector/ConsoleTab.js +113 -488
  147. package/dist/esm/components/Inspector/CrashDetail.js +236 -747
  148. package/dist/esm/components/Inspector/CrashFilterModal.js +149 -541
  149. package/dist/esm/components/Inspector/CrashTab.js +177 -711
  150. package/dist/esm/components/Inspector/DebuggingTab.js +164 -925
  151. package/dist/esm/components/Inspector/DeviceInfoTab.js +199 -924
  152. package/dist/esm/components/Inspector/FabLauncher.js +68 -454
  153. package/dist/esm/components/Inspector/FeatureUnderDevNotice.js +14 -70
  154. package/dist/esm/components/Inspector/InspectorContext.js +1 -12
  155. package/dist/esm/components/Inspector/InspectorHeader.js +282 -1163
  156. package/dist/esm/components/Inspector/LogDetail.js +235 -1023
  157. package/dist/esm/components/Inspector/MainScreen.js +78 -323
  158. package/dist/esm/components/Inspector/MediaGalleryTab.js +143 -650
  159. package/dist/esm/components/Inspector/MediaPreviewModal.js +138 -613
  160. package/dist/esm/components/Inspector/NavigationTracker.js +1 -10
  161. package/dist/esm/components/Inspector/NetworkDetail.js +138 -455
  162. package/dist/esm/components/Inspector/NetworkFilterModal.js +122 -597
  163. package/dist/esm/components/Inspector/NetworkTab.js +125 -517
  164. package/dist/esm/components/Inspector/NpmStarPrompt.js +58 -434
  165. package/dist/esm/components/Inspector/NpmUpdateToast.js +45 -281
  166. package/dist/esm/components/Inspector/PerformanceTab.js +393 -1570
  167. package/dist/esm/components/Inspector/ReduxDetail.js +307 -1170
  168. package/dist/esm/components/Inspector/ReduxTab.js +144 -722
  169. package/dist/esm/components/Inspector/SettingsPanel.js +643 -3985
  170. package/dist/esm/components/Inspector/SkeletonPlaceholder.js +30 -163
  171. package/dist/esm/components/Inspector/StorageTab.js +172 -866
  172. package/dist/esm/components/Inspector/TabBar.js +45 -305
  173. package/dist/esm/components/Inspector/UpdateAvailableModal.js +80 -418
  174. package/dist/esm/components/JsonViewer.js +179 -354
  175. package/dist/esm/components/LogCard.js +93 -565
  176. package/dist/esm/components/LogSyntaxHighlighter.js +11 -123
  177. package/dist/esm/components/MetaAccordion.js +136 -267
  178. package/dist/esm/components/MiniBarChart.js +3 -18
  179. package/dist/esm/components/MiniLineChart.js +3 -20
  180. package/dist/esm/components/NetworkIcons.d.ts +13 -0
  181. package/dist/esm/components/NetworkIcons.js +523 -838
  182. package/dist/esm/components/QRCodeView.js +8 -45
  183. package/dist/esm/components/SectionHeader.js +22 -39
  184. package/dist/esm/components/SegmentedTabs.js +8 -58
  185. package/dist/esm/components/SkeletonPlaceholder.js +30 -163
  186. package/dist/esm/components/Slider.js +24 -213
  187. package/dist/esm/components/SourcePageCard.js +65 -86
  188. package/dist/esm/components/Toast.js +10 -117
  189. package/dist/esm/components/TouchableScale.js +3 -19
  190. package/dist/esm/components/TreeNode.js +58 -95
  191. package/dist/esm/console.js +1 -2
  192. package/dist/esm/constants/index.js +1 -28
  193. package/dist/esm/constants/telemetryConfig.js +1 -3
  194. package/dist/esm/constants/version.d.ts +1 -1
  195. package/dist/esm/constants/version.js +1 -1
  196. package/dist/esm/crash.js +1 -2
  197. package/dist/esm/customHooks/analyticsLogger.js +1 -230
  198. package/dist/esm/customHooks/bundleAnalyzer.js +1 -944
  199. package/dist/esm/customHooks/consoleLogger.js +6 -432
  200. package/dist/esm/customHooks/crashHandler.js +7 -630
  201. package/dist/esm/customHooks/logFilters.js +1 -20
  202. package/dist/esm/customHooks/networkLogger.js +2 -519
  203. package/dist/esm/customHooks/performanceTracker.js +63 -723
  204. package/dist/esm/customHooks/reduxLogger.js +2 -293
  205. package/dist/esm/customHooks/storageInspector.js +1 -418
  206. package/dist/esm/customHooks/useAccordion.js +1 -16
  207. package/dist/esm/decorators/index.js +1 -95
  208. package/dist/esm/helpers/gaAnalyticsRegistry.js +1 -143
  209. package/dist/esm/helpers/index.d.ts +1 -0
  210. package/dist/esm/helpers/index.js +8 -861
  211. package/dist/esm/helpers/memoryManager.js +1 -91
  212. package/dist/esm/helpers/qrGenerator.js +1 -364
  213. package/dist/esm/helpers/remoteConfig.js +1 -160
  214. package/dist/esm/helpers/searchQueryParser.js +1 -59
  215. package/dist/esm/helpers/settingsStore.js +1 -134
  216. package/dist/esm/helpers/telemetry.js +1 -489
  217. package/dist/esm/helpers/toast.js +1 -15
  218. package/dist/esm/i18n/index.js +1 -78
  219. package/dist/esm/i18n/locales/en.json +1 -1140
  220. package/dist/esm/index.js +7 -1982
  221. package/dist/esm/metro.js +6 -459
  222. package/dist/esm/native/NativeInspector.d.ts +1 -0
  223. package/dist/esm/native/NativeInspector.js +1 -382
  224. package/dist/esm/native/NativeNetworkInspector.js +1 -2
  225. package/dist/esm/network.js +1 -1
  226. package/dist/esm/performance.js +1 -2
  227. package/dist/esm/redux.js +1 -1
  228. package/dist/esm/storage.js +1 -1
  229. package/dist/esm/styles/AppColors.js +1 -417
  230. package/dist/esm/styles/AppFonts.js +1 -13
  231. package/dist/esm/styles/common.js +1 -186
  232. package/dist/esm/styles/index.d.ts +6 -0
  233. package/dist/esm/styles/index.js +1 -1551
  234. package/dist/esm/types/enums.d.ts +0 -1
  235. package/dist/esm/types/enums.js +1 -149
  236. package/dist/esm/types/index.js +1 -2
  237. package/dist/esm/types/interfaces.d.ts +2 -2
  238. package/dist/esm/types/interfaces.js +1 -1
  239. package/ios/NetworkInspectorModule.mm +60 -12
  240. package/package.json +5 -3
  241. package/src/native/NativeInspector.ts +18 -0
  242. package/dist/commonjs/components/Inspector/FeedbackModal.d.ts +0 -7
  243. package/dist/commonjs/components/Inspector/FeedbackModal.js +0 -552
  244. package/dist/esm/components/Inspector/FeedbackModal.d.ts +0 -7
  245. package/dist/esm/components/Inspector/FeedbackModal.js +0 -515
  246. package/src/analytics.ts +0 -35
  247. package/src/bundle.ts +0 -25
  248. package/src/capture.ts +0 -109
  249. package/src/components/AnalyticsDetail.tsx +0 -870
  250. package/src/components/AnalyticsEventCard.tsx +0 -444
  251. package/src/components/AnalyticsGraph.tsx +0 -583
  252. package/src/components/AnimatedEntrance.tsx +0 -20
  253. package/src/components/AppHeaderLogo.tsx +0 -143
  254. package/src/components/BrandCircleIcon.tsx +0 -144
  255. package/src/components/BrandSquareIcon.tsx +0 -184
  256. package/src/components/CodeSnippet.tsx +0 -725
  257. package/src/components/ConsoleLogCard.tsx +0 -679
  258. package/src/components/CopyButton.tsx +0 -87
  259. package/src/components/DiffViewer.tsx +0 -82
  260. package/src/components/DomainHeader.tsx +0 -224
  261. package/src/components/EmptyState.tsx +0 -97
  262. package/src/components/EndOfListFooter.tsx +0 -100
  263. package/src/components/ErrorBoundary.tsx +0 -688
  264. package/src/components/HeadersSection.tsx +0 -172
  265. package/src/components/HighlightText.tsx +0 -100
  266. package/src/components/Inspector/AnalyticsFilterModal.tsx +0 -1250
  267. package/src/components/Inspector/AnalyticsTab.tsx +0 -1362
  268. package/src/components/Inspector/BundleTab.tsx +0 -4768
  269. package/src/components/Inspector/ConsoleTab.tsx +0 -726
  270. package/src/components/Inspector/CrashDetail.tsx +0 -997
  271. package/src/components/Inspector/CrashFilterModal.tsx +0 -721
  272. package/src/components/Inspector/CrashTab.tsx +0 -933
  273. package/src/components/Inspector/DebuggingTab.tsx +0 -1103
  274. package/src/components/Inspector/DeviceInfoTab.tsx +0 -1397
  275. package/src/components/Inspector/FabLauncher.tsx +0 -590
  276. package/src/components/Inspector/FeatureUnderDevNotice.tsx +0 -94
  277. package/src/components/Inspector/FeedbackModal.tsx +0 -626
  278. package/src/components/Inspector/InspectorContext.tsx +0 -28
  279. package/src/components/Inspector/InspectorHeader.tsx +0 -1583
  280. package/src/components/Inspector/LogDetail.tsx +0 -1431
  281. package/src/components/Inspector/MainScreen.tsx +0 -460
  282. package/src/components/Inspector/MediaGalleryTab.tsx +0 -829
  283. package/src/components/Inspector/MediaPreviewModal.tsx +0 -783
  284. package/src/components/Inspector/NavigationTracker.tsx +0 -13
  285. package/src/components/Inspector/NetworkDetail.tsx +0 -778
  286. package/src/components/Inspector/NetworkFilterModal.tsx +0 -730
  287. package/src/components/Inspector/NetworkTab.tsx +0 -820
  288. package/src/components/Inspector/NpmStarPrompt.tsx +0 -532
  289. package/src/components/Inspector/NpmUpdateToast.tsx +0 -387
  290. package/src/components/Inspector/PerformanceTab.tsx +0 -1967
  291. package/src/components/Inspector/ReduxDetail.tsx +0 -1651
  292. package/src/components/Inspector/ReduxTab.tsx +0 -987
  293. package/src/components/Inspector/SettingsPanel.tsx +0 -5136
  294. package/src/components/Inspector/SkeletonPlaceholder.tsx +0 -206
  295. package/src/components/Inspector/StorageTab.tsx +0 -1159
  296. package/src/components/Inspector/TabBar.tsx +0 -431
  297. package/src/components/Inspector/UpdateAvailableModal.tsx +0 -555
  298. package/src/components/JsonViewer.tsx +0 -549
  299. package/src/components/LogCard.tsx +0 -694
  300. package/src/components/LogSyntaxHighlighter.tsx +0 -178
  301. package/src/components/MetaAccordion.tsx +0 -355
  302. package/src/components/MiniBarChart.tsx +0 -42
  303. package/src/components/MiniLineChart.tsx +0 -33
  304. package/src/components/NetworkIcons.tsx +0 -2655
  305. package/src/components/QRCodeView.tsx +0 -75
  306. package/src/components/SectionHeader.tsx +0 -101
  307. package/src/components/SegmentedTabs.tsx +0 -84
  308. package/src/components/SkeletonPlaceholder.tsx +0 -206
  309. package/src/components/Slider.tsx +0 -300
  310. package/src/components/SourcePageCard.tsx +0 -150
  311. package/src/components/Toast.tsx +0 -131
  312. package/src/components/TouchableScale.tsx +0 -61
  313. package/src/components/TreeNode.tsx +0 -171
  314. package/src/console.ts +0 -24
  315. package/src/constants/index.ts +0 -41
  316. package/src/constants/telemetryConfig.ts +0 -4
  317. package/src/constants/version.ts +0 -3
  318. package/src/crash.ts +0 -32
  319. package/src/customHooks/analyticsLogger.ts +0 -336
  320. package/src/customHooks/bundleAnalyzer.ts +0 -1256
  321. package/src/customHooks/consoleLogger.ts +0 -530
  322. package/src/customHooks/crashHandler.ts +0 -944
  323. package/src/customHooks/logFilters.ts +0 -32
  324. package/src/customHooks/networkLogger.ts +0 -624
  325. package/src/customHooks/performanceTracker.ts +0 -1014
  326. package/src/customHooks/reduxLogger.ts +0 -406
  327. package/src/customHooks/storageInspector.ts +0 -513
  328. package/src/customHooks/useAccordion.tsx +0 -27
  329. package/src/decorators/index.ts +0 -184
  330. package/src/helpers/gaAnalyticsRegistry.ts +0 -204
  331. package/src/helpers/index.ts +0 -1092
  332. package/src/helpers/memoryManager.ts +0 -138
  333. package/src/helpers/qrGenerator.ts +0 -430
  334. package/src/helpers/remoteConfig.ts +0 -212
  335. package/src/helpers/searchQueryParser.ts +0 -84
  336. package/src/helpers/settingsStore.ts +0 -171
  337. package/src/helpers/telemetry.ts +0 -568
  338. package/src/helpers/toast.ts +0 -17
  339. package/src/i18n/index.ts +0 -104
  340. package/src/i18n/locales/en.json +0 -1141
  341. package/src/index.tsx +0 -2650
  342. package/src/metro.ts +0 -598
  343. package/src/network.ts +0 -22
  344. package/src/performance.ts +0 -38
  345. package/src/redux.ts +0 -23
  346. package/src/storage.ts +0 -18
  347. package/src/styles/AppColors.ts +0 -455
  348. package/src/styles/AppFonts.ts +0 -26
  349. package/src/styles/common.ts +0 -209
  350. package/src/styles/index.ts +0 -1606
  351. package/src/types/enums.ts +0 -210
  352. package/src/types/index.ts +0 -34
  353. package/src/types/interfaces.ts +0 -535
@@ -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;