react-native-inapp-inspector 1.1.21 → 1.1.23
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.
- package/dist/commonjs/components/AnalyticsDetail.js +5 -3
- package/dist/commonjs/components/AnalyticsEventCard.d.ts +0 -1
- package/dist/commonjs/components/AnalyticsEventCard.js +89 -97
- package/dist/commonjs/components/ErrorBoundary.js +17 -8
- package/dist/commonjs/components/Inspector/BundleTab.d.ts +1 -0
- package/dist/commonjs/components/Inspector/BundleTab.js +607 -685
- package/dist/commonjs/components/Inspector/LogDetail.js +12 -6
- package/dist/commonjs/components/Inspector/PerformanceTab.js +195 -254
- package/dist/commonjs/components/Inspector/ReduxDetail.js +2 -2
- package/dist/commonjs/components/Inspector/ReduxTab.js +9 -6
- package/dist/commonjs/components/NetworkIcons.d.ts +12 -0
- package/dist/commonjs/components/NetworkIcons.js +92 -1
- package/dist/commonjs/components/TreeNode.js +1 -1
- package/dist/commonjs/constants/version.d.ts +1 -1
- package/dist/commonjs/constants/version.js +1 -1
- package/dist/commonjs/customHooks/bundleAnalyzer.d.ts +115 -0
- package/dist/commonjs/customHooks/bundleAnalyzer.js +561 -0
- package/dist/commonjs/customHooks/performanceTracker.d.ts +84 -0
- package/dist/commonjs/customHooks/performanceTracker.js +541 -0
- package/dist/commonjs/helpers/index.d.ts +15 -0
- package/dist/commonjs/helpers/index.js +112 -1
- package/dist/commonjs/i18n/locales/en.json +225 -2
- package/dist/commonjs/styles/AppColors.d.ts +110 -0
- package/dist/commonjs/styles/AppColors.js +114 -0
- package/dist/esm/components/AnalyticsDetail.js +5 -3
- package/dist/esm/components/AnalyticsEventCard.d.ts +0 -1
- package/dist/esm/components/AnalyticsEventCard.js +89 -96
- package/dist/esm/components/ErrorBoundary.js +17 -8
- package/dist/esm/components/Inspector/BundleTab.d.ts +1 -0
- package/dist/esm/components/Inspector/BundleTab.js +612 -690
- package/dist/esm/components/Inspector/LogDetail.js +13 -7
- package/dist/esm/components/Inspector/PerformanceTab.js +196 -255
- package/dist/esm/components/Inspector/ReduxDetail.js +2 -2
- package/dist/esm/components/Inspector/ReduxTab.js +10 -7
- package/dist/esm/components/NetworkIcons.d.ts +12 -0
- package/dist/esm/components/NetworkIcons.js +79 -0
- package/dist/esm/components/TreeNode.js +2 -2
- package/dist/esm/constants/version.d.ts +1 -1
- package/dist/esm/constants/version.js +1 -1
- package/dist/esm/customHooks/bundleAnalyzer.d.ts +115 -0
- package/dist/esm/customHooks/bundleAnalyzer.js +554 -0
- package/dist/esm/customHooks/performanceTracker.d.ts +84 -0
- package/dist/esm/customHooks/performanceTracker.js +537 -0
- package/dist/esm/helpers/index.d.ts +15 -0
- package/dist/esm/helpers/index.js +107 -0
- package/dist/esm/i18n/locales/en.json +225 -2
- package/dist/esm/styles/AppColors.d.ts +110 -0
- package/dist/esm/styles/AppColors.js +114 -0
- package/package.json +1 -1
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import React, { useState, useMemo, useCallback } from 'react';
|
|
2
|
-
import { Image, Linking, Pressable, ScrollView, StyleSheet, Text, TextInput, View, } from 'react-native';
|
|
3
|
-
import { useTranslation } from '../../i18n';
|
|
1
|
+
import React, { useState, useEffect, useMemo, useCallback } from 'react';
|
|
2
|
+
import { ActivityIndicator, Image, Linking, Pressable, ScrollView, StyleSheet, Text, TextInput, View, } from 'react-native';
|
|
3
|
+
import { useTranslation, t } from '../../i18n';
|
|
4
4
|
import TouchableScale from '../TouchableScale';
|
|
5
5
|
import CopyButton from '../CopyButton';
|
|
6
6
|
import HighlightText from '../HighlightText';
|
|
7
7
|
import { AppColors } from '../../styles/AppColors';
|
|
8
8
|
import { AppFonts } from '../../styles/AppFonts';
|
|
9
|
-
import { PackageIcon, SearchIcon, ClearIcon, CircleAlertIcon, LayersIcon, LiveStateIcon, StorageIcon, MetadataIcon, } from '../NetworkIcons';
|
|
10
|
-
import Svg, { Path, Rect, Circle, Ellipse, Defs, LinearGradient, Stop, } from 'react-native-svg';
|
|
9
|
+
import { PackageIcon, SearchIcon, ClearIcon, CircleAlertIcon, LayersIcon, LiveStateIcon, StorageIcon, MetadataIcon, FolderIcon, FolderOpenIcon, TrashIcon, LightbulbIcon, SparkleIcon, ClockIcon, } from '../NetworkIcons';
|
|
10
|
+
import Svg, { Path, Rect, Circle, Ellipse, G, Defs, LinearGradient, Stop, } from 'react-native-svg';
|
|
11
|
+
import { analyzeHostAppBundle, getCachedBundleAnalysis, } from '../../customHooks/bundleAnalyzer';
|
|
11
12
|
// ─── High-Fidelity Vector Package & NPM Logos ────────────────────────────────
|
|
12
13
|
const PackageLogoRenderer = ({ name, size = 28 }) => {
|
|
13
14
|
const lowerName = name.toLowerCase();
|
|
@@ -19,13 +20,13 @@ const PackageLogoRenderer = ({ name, size = 28 }) => {
|
|
|
19
20
|
!lowerName.includes('gradient'))) {
|
|
20
21
|
return (<View style={[
|
|
21
22
|
bundleStyles.pkgLogoWrap,
|
|
22
|
-
{ width: size, height: size, backgroundColor:
|
|
23
|
+
{ width: size, height: size, backgroundColor: AppColors.npmDark },
|
|
23
24
|
]}>
|
|
24
25
|
<Svg width={size - 6} height={size - 6} viewBox="0 0 100 100" fill="none">
|
|
25
|
-
<Circle cx="50" cy="50" r="9" fill=
|
|
26
|
-
<Ellipse cx="50" cy="50" rx="42" ry="16" stroke=
|
|
27
|
-
<Ellipse cx="50" cy="50" rx="42" ry="16" stroke=
|
|
28
|
-
<Ellipse cx="50" cy="50" rx="42" ry="16" stroke=
|
|
26
|
+
<Circle cx="50" cy="50" r="9" fill={AppColors.reactCyan}/>
|
|
27
|
+
<Ellipse cx="50" cy="50" rx="42" ry="16" stroke={AppColors.reactCyan} strokeWidth="6"/>
|
|
28
|
+
<Ellipse cx="50" cy="50" rx="42" ry="16" stroke={AppColors.reactCyan} strokeWidth="6" transform="rotate(60 50 50)"/>
|
|
29
|
+
<Ellipse cx="50" cy="50" rx="42" ry="16" stroke={AppColors.reactCyan} strokeWidth="6" transform="rotate(120 50 50)"/>
|
|
29
30
|
</Svg>
|
|
30
31
|
</View>);
|
|
31
32
|
}
|
|
@@ -33,12 +34,12 @@ const PackageLogoRenderer = ({ name, size = 28 }) => {
|
|
|
33
34
|
if (lowerName.includes('navigation')) {
|
|
34
35
|
return (<View style={[
|
|
35
36
|
bundleStyles.pkgLogoWrap,
|
|
36
|
-
{ width: size, height: size, backgroundColor:
|
|
37
|
+
{ width: size, height: size, backgroundColor: AppColors.hermesPurple },
|
|
37
38
|
]}>
|
|
38
39
|
<Svg width={size - 8} height={size - 8} viewBox="0 0 100 100" fill="none">
|
|
39
|
-
<Circle cx="50" cy="50" r="38" stroke=
|
|
40
|
-
<Path d="M50 20 L68 50 L50 80 L32 50 Z" fill=
|
|
41
|
-
<Circle cx="50" cy="50" r="6" fill=
|
|
40
|
+
<Circle cx="50" cy="50" r="38" stroke={AppColors.white} strokeWidth="8"/>
|
|
41
|
+
<Path d="M50 20 L68 50 L50 80 L32 50 Z" fill={AppColors.reactCyan}/>
|
|
42
|
+
<Circle cx="50" cy="50" r="6" fill={AppColors.white}/>
|
|
42
43
|
</Svg>
|
|
43
44
|
</View>);
|
|
44
45
|
}
|
|
@@ -46,13 +47,13 @@ const PackageLogoRenderer = ({ name, size = 28 }) => {
|
|
|
46
47
|
if (lowerName.includes('svg')) {
|
|
47
48
|
return (<View style={[
|
|
48
49
|
bundleStyles.pkgLogoWrap,
|
|
49
|
-
{ width: size, height: size, backgroundColor:
|
|
50
|
+
{ width: size, height: size, backgroundColor: AppColors.pink400 },
|
|
50
51
|
]}>
|
|
51
52
|
<Svg width={size - 8} height={size - 8} viewBox="0 0 100 100" fill="none">
|
|
52
|
-
<Path d="M20 75 C35 25, 65 25, 80 75" stroke=
|
|
53
|
-
<Circle cx="20" cy="75" r="8" fill=
|
|
54
|
-
<Circle cx="80" cy="75" r="8" fill=
|
|
55
|
-
<Circle cx="50" cy="38" r="7" fill="#FCE7F3" stroke=
|
|
53
|
+
<Path d="M20 75 C35 25, 65 25, 80 75" stroke={AppColors.white} strokeWidth="10" strokeLinecap="round"/>
|
|
54
|
+
<Circle cx="20" cy="75" r="8" fill={AppColors.white}/>
|
|
55
|
+
<Circle cx="80" cy="75" r="8" fill={AppColors.white}/>
|
|
56
|
+
<Circle cx="50" cy="38" r="7" fill="#FCE7F3" stroke={AppColors.white} strokeWidth="3"/>
|
|
56
57
|
</Svg>
|
|
57
58
|
</View>);
|
|
58
59
|
}
|
|
@@ -60,11 +61,11 @@ const PackageLogoRenderer = ({ name, size = 28 }) => {
|
|
|
60
61
|
if (lowerName.includes('axios')) {
|
|
61
62
|
return (<View style={[
|
|
62
63
|
bundleStyles.pkgLogoWrap,
|
|
63
|
-
{ width: size, height: size, backgroundColor:
|
|
64
|
+
{ width: size, height: size, backgroundColor: AppColors.expoViolet },
|
|
64
65
|
]}>
|
|
65
66
|
<Svg width={size - 6} height={size - 6} viewBox="0 0 100 100" fill="none">
|
|
66
|
-
<Path d="M22 75 L50 20 L78 75 M33 58 L67 58" stroke=
|
|
67
|
-
<Circle cx="50" cy="20" r="7" fill=
|
|
67
|
+
<Path d="M22 75 L50 20 L78 75 M33 58 L67 58" stroke={AppColors.white} strokeWidth="12" strokeLinecap="round" strokeLinejoin="round"/>
|
|
68
|
+
<Circle cx="50" cy="20" r="7" fill={AppColors.reactCyan}/>
|
|
68
69
|
</Svg>
|
|
69
70
|
</View>);
|
|
70
71
|
}
|
|
@@ -72,11 +73,11 @@ const PackageLogoRenderer = ({ name, size = 28 }) => {
|
|
|
72
73
|
if (lowerName.includes('screens') || lowerName.includes('software-mansion')) {
|
|
73
74
|
return (<View style={[
|
|
74
75
|
bundleStyles.pkgLogoWrap,
|
|
75
|
-
{ width: size, height: size, backgroundColor:
|
|
76
|
+
{ width: size, height: size, backgroundColor: AppColors.reanimatedNavy },
|
|
76
77
|
]}>
|
|
77
78
|
<Svg width={size - 8} height={size - 8} viewBox="0 0 100 100" fill="none">
|
|
78
|
-
<Path d="M50 15 L88 35 L50 55 L12 35 Z" fill=
|
|
79
|
-
<Path d="M12 50 L50 70 L88 50" stroke=
|
|
79
|
+
<Path d="M50 15 L88 35 L50 55 L12 35 Z" fill={AppColors.sky400}/>
|
|
80
|
+
<Path d="M12 50 L50 70 L88 50" stroke={AppColors.white} strokeWidth="8" strokeLinecap="round"/>
|
|
80
81
|
<Path d="M12 65 L50 85 L88 65" stroke="#93C5FD" strokeWidth="8" strokeLinecap="round"/>
|
|
81
82
|
</Svg>
|
|
82
83
|
</View>);
|
|
@@ -85,7 +86,7 @@ const PackageLogoRenderer = ({ name, size = 28 }) => {
|
|
|
85
86
|
if (lowerName.includes('gradient')) {
|
|
86
87
|
return (<View style={[
|
|
87
88
|
bundleStyles.pkgLogoWrap,
|
|
88
|
-
{ width: size, height: size, backgroundColor:
|
|
89
|
+
{ width: size, height: size, backgroundColor: AppColors.rose600 },
|
|
89
90
|
]}>
|
|
90
91
|
<Svg width={size - 4} height={size - 4} viewBox="0 0 100 100" fill="none">
|
|
91
92
|
<Defs>
|
|
@@ -103,26 +104,97 @@ const PackageLogoRenderer = ({ name, size = 28 }) => {
|
|
|
103
104
|
if (lowerName.includes('i18n')) {
|
|
104
105
|
return (<View style={[
|
|
105
106
|
bundleStyles.pkgLogoWrap,
|
|
106
|
-
{ width: size, height: size, backgroundColor:
|
|
107
|
+
{ width: size, height: size, backgroundColor: AppColors.emerald600 },
|
|
107
108
|
]}>
|
|
108
109
|
<Svg width={size - 8} height={size - 8} viewBox="0 0 100 100" fill="none">
|
|
109
|
-
<Circle cx="50" cy="50" r="38" stroke=
|
|
110
|
-
<Ellipse cx="50" cy="50" rx="18" ry="38" stroke=
|
|
111
|
-
<Path d="M14 50 L86 50" stroke=
|
|
110
|
+
<Circle cx="50" cy="50" r="38" stroke={AppColors.white} strokeWidth="8"/>
|
|
111
|
+
<Ellipse cx="50" cy="50" rx="18" ry="38" stroke={AppColors.white} strokeWidth="6"/>
|
|
112
|
+
<Path d="M14 50 L86 50" stroke={AppColors.white} strokeWidth="7"/>
|
|
112
113
|
</Svg>
|
|
113
114
|
</View>);
|
|
114
115
|
}
|
|
115
116
|
// 8. Official Red NPM Logo Box (Universal Default NPM Branding)
|
|
116
117
|
return (<View style={[
|
|
117
118
|
bundleStyles.pkgLogoWrap,
|
|
118
|
-
{ width: size, height: size, backgroundColor:
|
|
119
|
+
{ width: size, height: size, backgroundColor: AppColors.npmRed },
|
|
119
120
|
]}>
|
|
120
121
|
<Svg width={size - 4} height={size - 4} viewBox="0 0 100 100" fill="none">
|
|
121
|
-
<Rect width="100" height="100" rx="14" fill=
|
|
122
|
-
<Path d="M18 24 H82 V76 H50 V40 H38 V76 H18 Z" fill=
|
|
122
|
+
<Rect width="100" height="100" rx="14" fill={AppColors.npmRed}/>
|
|
123
|
+
<Path d="M18 24 H82 V76 H50 V40 H38 V76 H18 Z" fill={AppColors.white}/>
|
|
123
124
|
</Svg>
|
|
124
125
|
</View>);
|
|
125
126
|
};
|
|
127
|
+
const BundleCategoryIcon = ({ type, size = 16, color = AppColors.purple }) => {
|
|
128
|
+
const stroke = { stroke: color, strokeWidth: 1.7, strokeLinecap: 'round', strokeLinejoin: 'round' };
|
|
129
|
+
const renderGlyph = () => {
|
|
130
|
+
switch (type) {
|
|
131
|
+
case 'folder':
|
|
132
|
+
return (<G>
|
|
133
|
+
<Path 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" {...stroke}/>
|
|
134
|
+
</G>);
|
|
135
|
+
case 'ban':
|
|
136
|
+
return (<G>
|
|
137
|
+
<Circle cx="12" cy="12" r="8.5" {...stroke}/>
|
|
138
|
+
<Path d="M5.5 5.5 18.5 18.5" {...stroke}/>
|
|
139
|
+
</G>);
|
|
140
|
+
case 'bolt':
|
|
141
|
+
return (<G>
|
|
142
|
+
<Path d="M13 2 4.5 13.5 H10.5 L9 22 19.5 9.5 H13.5 Z" {...stroke}/>
|
|
143
|
+
</G>);
|
|
144
|
+
case 'source':
|
|
145
|
+
return (<G>
|
|
146
|
+
<Path d="M8.5 7 L4 12 L8.5 17" {...stroke}/>
|
|
147
|
+
<Path d="M15.5 7 L20 12 L15.5 17" {...stroke}/>
|
|
148
|
+
<Path d="M13.5 4.5 L10.5 19.5" {...stroke}/>
|
|
149
|
+
</G>);
|
|
150
|
+
case 'deps':
|
|
151
|
+
return (<G>
|
|
152
|
+
<Rect x="4" y="4" width="16" height="16" rx="2.5" {...stroke}/>
|
|
153
|
+
<Path d="M4 9.5 H20" {...stroke}/>
|
|
154
|
+
<Path d="M9.5 9.5 V20" {...stroke}/>
|
|
155
|
+
</G>);
|
|
156
|
+
case 'media':
|
|
157
|
+
case 'image':
|
|
158
|
+
return (<G>
|
|
159
|
+
<Rect x="3.5" y="4.5" width="17" height="15" rx="2" {...stroke}/>
|
|
160
|
+
<Circle cx="9" cy="10" r="1.5" {...stroke}/>
|
|
161
|
+
<Path d="M3.5 17.5 L9.5 12.5 L14 16.5 L17 13.5 L20.5 17.5" {...stroke}/>
|
|
162
|
+
</G>);
|
|
163
|
+
case 'overhead':
|
|
164
|
+
return (<G>
|
|
165
|
+
<Circle cx="12" cy="12" r="3.2" {...stroke}/>
|
|
166
|
+
<Path 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" {...stroke}/>
|
|
167
|
+
</G>);
|
|
168
|
+
case 'typescript':
|
|
169
|
+
return (<G>
|
|
170
|
+
<Path d="M6.5 3.5 H14 L18.5 8 V20.5 H6.5 Z" {...stroke}/>
|
|
171
|
+
<Path d="M14 3.5 V8 H18.5" {...stroke}/>
|
|
172
|
+
<Path d="M9.5 13.5 H15 M12 13.5 V16.5 M9.5 16.5 H13" {...stroke}/>
|
|
173
|
+
</G>);
|
|
174
|
+
case 'javascript':
|
|
175
|
+
return (<G>
|
|
176
|
+
<Path 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" {...stroke}/>
|
|
177
|
+
<Path 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" {...stroke}/>
|
|
178
|
+
</G>);
|
|
179
|
+
case 'font':
|
|
180
|
+
return (<G>
|
|
181
|
+
<Path d="M8 19 L12 5.5 L16 19" {...stroke}/>
|
|
182
|
+
<Path d="M9.4 14.5 H14.6" {...stroke}/>
|
|
183
|
+
</G>);
|
|
184
|
+
case 'json':
|
|
185
|
+
return (<G>
|
|
186
|
+
<Path 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" {...stroke}/>
|
|
187
|
+
<Circle cx="12" cy="12" r="1.1" fill={color}/>
|
|
188
|
+
<Path 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" {...stroke}/>
|
|
189
|
+
</G>);
|
|
190
|
+
default:
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
return (<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
|
|
195
|
+
{renderGlyph()}
|
|
196
|
+
</Svg>);
|
|
197
|
+
};
|
|
126
198
|
// ─── Media File Thumbnail Component ──────────────────────────────────────────
|
|
127
199
|
const MediaPreviewThumbnail = ({ file }) => {
|
|
128
200
|
const [loadError, setLoadError] = useState(false);
|
|
@@ -131,25 +203,25 @@ const MediaPreviewThumbnail = ({ file }) => {
|
|
|
131
203
|
return (<View style={[
|
|
132
204
|
bundleStyles.mediaThumbWrap,
|
|
133
205
|
{
|
|
134
|
-
backgroundColor:
|
|
206
|
+
backgroundColor: AppColors.errorCardBg,
|
|
135
207
|
alignItems: 'center',
|
|
136
208
|
justifyContent: 'center',
|
|
137
|
-
borderColor:
|
|
209
|
+
borderColor: AppColors.errorBorder,
|
|
138
210
|
},
|
|
139
211
|
]}>
|
|
140
212
|
<Svg width={22} height={22} viewBox="0 0 100 100" fill="none">
|
|
141
213
|
<Defs>
|
|
142
214
|
<LinearGradient id="svgGradThumb" x1="0" y1="0" x2="100" y2="100" gradientUnits="userSpaceOnUse">
|
|
143
|
-
<Stop offset="0" stopColor=
|
|
144
|
-
<Stop offset="1" stopColor=
|
|
215
|
+
<Stop offset="0" stopColor={AppColors.rose500}/>
|
|
216
|
+
<Stop offset="1" stopColor={AppColors.rose600}/>
|
|
145
217
|
</LinearGradient>
|
|
146
218
|
</Defs>
|
|
147
219
|
<Circle cx="50" cy="50" r="44" fill="url(#svgGradThumb)"/>
|
|
148
220
|
{/* Vector bezier path pen graphic */}
|
|
149
|
-
<Path d="M25 70 C35 30, 65 30, 75 70" stroke=
|
|
150
|
-
<Circle cx="25" cy="70" r="7" fill=
|
|
151
|
-
<Circle cx="75" cy="70" r="7" fill=
|
|
152
|
-
<Circle cx="50" cy="40" r="6" fill="#FFE4E6" stroke=
|
|
221
|
+
<Path d="M25 70 C35 30, 65 30, 75 70" stroke={AppColors.white} strokeWidth="8" strokeLinecap="round"/>
|
|
222
|
+
<Circle cx="25" cy="70" r="7" fill={AppColors.white}/>
|
|
223
|
+
<Circle cx="75" cy="70" r="7" fill={AppColors.white}/>
|
|
224
|
+
<Circle cx="50" cy="40" r="6" fill="#FFE4E6" stroke={AppColors.white} strokeWidth="2"/>
|
|
153
225
|
</Svg>
|
|
154
226
|
</View>);
|
|
155
227
|
}
|
|
@@ -243,11 +315,11 @@ const BundleTreeNodeView = ({ node, level, search, totalBundleKb, collapsedFolde
|
|
|
243
315
|
{isCollapsed ? '▶' : '▼'}
|
|
244
316
|
</Text>
|
|
245
317
|
</View>
|
|
246
|
-
<
|
|
318
|
+
{isCollapsed ? (<FolderIcon color={AppColors.amber500} size={16}/>) : (<FolderOpenIcon color={AppColors.amber500} size={16}/>)}
|
|
247
319
|
<HighlightText text={node.name + '/'} search={search} style={bundleStyles.treeFolderName} highlightStyle={bundleStyles.searchHighlight}/>
|
|
248
320
|
<View style={bundleStyles.treeCountBadge}>
|
|
249
321
|
<Text style={bundleStyles.treeCountBadgeText}>
|
|
250
|
-
{node.fileCount} {node.fileCount === 1 ? 'file' : 'files'}
|
|
322
|
+
{node.fileCount} {node.fileCount === 1 ? t('bundle.file') : t('bundle.files')}
|
|
251
323
|
</Text>
|
|
252
324
|
</View>
|
|
253
325
|
</View>
|
|
@@ -255,14 +327,14 @@ const BundleTreeNodeView = ({ node, level, search, totalBundleKb, collapsedFolde
|
|
|
255
327
|
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
|
|
256
328
|
<Text style={bundleStyles.treeFolderSize}>
|
|
257
329
|
{node.sizeKb >= 1024
|
|
258
|
-
?
|
|
259
|
-
:
|
|
330
|
+
? t('bundle.fileSizeMb', { size: (node.sizeKb / 1024).toFixed(2) })
|
|
331
|
+
: t('bundle.fileSizeKb', { size: node.sizeKb })}
|
|
260
332
|
</Text>
|
|
261
333
|
<CopyButton value={() => ({
|
|
262
334
|
folder: node.fullPath,
|
|
263
335
|
sizeKb: node.sizeKb,
|
|
264
336
|
fileCount: node.fileCount,
|
|
265
|
-
})} label={
|
|
337
|
+
})} label={t('bundle.copyFileInfo', { name: node.name })}/>
|
|
266
338
|
</View>
|
|
267
339
|
</TouchableScale>
|
|
268
340
|
|
|
@@ -291,9 +363,9 @@ const BundleTreeNodeView = ({ node, level, search, totalBundleKb, collapsedFolde
|
|
|
291
363
|
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6, flexWrap: 'wrap' }}>
|
|
292
364
|
<HighlightText text={file.name} search={search} style={bundleStyles.fileName} highlightStyle={bundleStyles.searchHighlight}/>
|
|
293
365
|
{isUnused ? (<View style={bundleStyles.unusedBadge}>
|
|
294
|
-
<Text style={bundleStyles.unusedBadgeText}
|
|
366
|
+
<Text style={bundleStyles.unusedBadgeText}>{t('bundle.notConsumed')}</Text>
|
|
295
367
|
</View>) : (<View style={bundleStyles.consumedBadge}>
|
|
296
|
-
<Text style={bundleStyles.consumedBadgeText}
|
|
368
|
+
<Text style={bundleStyles.consumedBadgeText}>{t('bundle.consumed')}</Text>
|
|
297
369
|
</View>)}
|
|
298
370
|
</View>
|
|
299
371
|
<Text style={bundleStyles.filePath} numberOfLines={1}>
|
|
@@ -303,14 +375,14 @@ const BundleTreeNodeView = ({ node, level, search, totalBundleKb, collapsedFolde
|
|
|
303
375
|
|
|
304
376
|
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
|
|
305
377
|
<View style={bundleStyles.fileSizeBox}>
|
|
306
|
-
<Text style={[bundleStyles.fileSizeKb, isUnused && { color:
|
|
378
|
+
<Text style={[bundleStyles.fileSizeKb, isUnused && { color: AppColors.amber800Warm }]}>
|
|
307
379
|
{file.sizeKb >= 1024
|
|
308
|
-
?
|
|
309
|
-
:
|
|
380
|
+
? t('bundle.fileSizeMb', { size: (file.sizeKb / 1024).toFixed(2) })
|
|
381
|
+
: t('bundle.fileSizeKb', { size: file.sizeKb })}
|
|
310
382
|
</Text>
|
|
311
383
|
<Text style={bundleStyles.filePercent}>{pctOfTotal}%</Text>
|
|
312
384
|
</View>
|
|
313
|
-
<CopyButton value={() => file} label=
|
|
385
|
+
<CopyButton value={() => file} label={t('bundle.fileDetailsJson')}/>
|
|
314
386
|
</View>
|
|
315
387
|
</View>
|
|
316
388
|
|
|
@@ -321,429 +393,62 @@ const BundleTreeNodeView = ({ node, level, search, totalBundleKb, collapsedFolde
|
|
|
321
393
|
isUnused && bundleStyles.adviceUnused,
|
|
322
394
|
{ marginLeft: 26, marginTop: 4 },
|
|
323
395
|
]}>
|
|
324
|
-
<
|
|
396
|
+
<View style={{ flexDirection: 'row', alignItems: 'flex-start', gap: 5 }}>
|
|
397
|
+
{isUnused ? (<TrashIcon color={AppColors.red600} size={12}/>) : file.status === 'warning' ? (<LightbulbIcon color={AppColors.amber800Warm} size={12}/>) : (<SparkleIcon color={AppColors.emerald700} size={12}/>)}
|
|
398
|
+
<Text style={[
|
|
325
399
|
bundleStyles.adviceText,
|
|
326
|
-
file.status === 'warning' && { color:
|
|
327
|
-
file.status === 'optimal' && { color:
|
|
328
|
-
isUnused && { color:
|
|
400
|
+
file.status === 'warning' && { color: AppColors.amber800Warm },
|
|
401
|
+
file.status === 'optimal' && { color: AppColors.emerald700 },
|
|
402
|
+
isUnused && { color: AppColors.red600 },
|
|
329
403
|
]}>
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
</
|
|
404
|
+
{file.advice}
|
|
405
|
+
</Text>
|
|
406
|
+
</View>
|
|
333
407
|
</View>)}
|
|
334
408
|
</View>);
|
|
335
409
|
};
|
|
336
|
-
// ─── Sample Bundle Files Data ────────────────────────────────────────────────
|
|
337
|
-
const BUNDLE_FILES_DATA = [
|
|
338
|
-
// ── Images & Media ──
|
|
339
|
-
{
|
|
340
|
-
id: 'img-1',
|
|
341
|
-
name: 'banner_dark.png',
|
|
342
|
-
path: 'assets/images/banner_dark.png',
|
|
343
|
-
ext: 'PNG',
|
|
344
|
-
category: 'image',
|
|
345
|
-
sizeKb: 460,
|
|
346
|
-
meta: '1200×630px @2x • Raster Image',
|
|
347
|
-
color: '#EC4899',
|
|
348
|
-
status: 'warning',
|
|
349
|
-
advice: 'Convert to .webp to save ~65% (160 KB)',
|
|
350
|
-
previewUri: 'https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?w=200&auto=format&fit=crop&q=80',
|
|
351
|
-
},
|
|
352
|
-
{
|
|
353
|
-
id: 'img-2',
|
|
354
|
-
name: 'banner_light.svg',
|
|
355
|
-
path: 'assets/images/banner_light.svg',
|
|
356
|
-
ext: 'SVG',
|
|
357
|
-
category: 'image',
|
|
358
|
-
sizeKb: 34,
|
|
359
|
-
meta: 'Scalable Vector Graphic',
|
|
360
|
-
color: '#F43F5E',
|
|
361
|
-
status: 'optimal',
|
|
362
|
-
advice: 'Optimal vector format for sharp rendering',
|
|
363
|
-
previewUri: 'https://images.unsplash.com/photo-1579783902614-a3fb3927b675?w=200&auto=format&fit=crop&q=80',
|
|
364
|
-
},
|
|
365
|
-
{
|
|
366
|
-
id: 'img-3',
|
|
367
|
-
name: 'logo_hero@3x.png',
|
|
368
|
-
path: 'assets/brand/logo_hero@3x.png',
|
|
369
|
-
ext: 'PNG',
|
|
370
|
-
category: 'image',
|
|
371
|
-
sizeKb: 285,
|
|
372
|
-
meta: '768×768px @3x Retina Asset',
|
|
373
|
-
color: '#EC4899',
|
|
374
|
-
status: 'warning',
|
|
375
|
-
advice: 'High density PNG. Consider tintable SVG vector',
|
|
376
|
-
previewUri: 'https://images.unsplash.com/photo-1634017839464-5c339ebe3cb4?w=200&auto=format&fit=crop&q=80',
|
|
377
|
-
},
|
|
378
|
-
{
|
|
379
|
-
id: 'img-4',
|
|
380
|
-
name: 'avatar_placeholder.webp',
|
|
381
|
-
path: 'assets/placeholders/avatar_placeholder.webp',
|
|
382
|
-
ext: 'WEBP',
|
|
383
|
-
category: 'image',
|
|
384
|
-
sizeKb: 28,
|
|
385
|
-
meta: '256×256px Lossy WebP',
|
|
386
|
-
color: '#F472B6',
|
|
387
|
-
status: 'warning',
|
|
388
|
-
advice: 'Unused in bundle: 0 active imports detected (28 KB unused)',
|
|
389
|
-
previewUri: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=200&auto=format&fit=crop&q=80',
|
|
390
|
-
isConsumed: false,
|
|
391
|
-
},
|
|
392
|
-
{
|
|
393
|
-
id: 'img-5',
|
|
394
|
-
name: 'walkthrough_thumb.jpg',
|
|
395
|
-
path: 'assets/previews/walkthrough_thumb.jpg',
|
|
396
|
-
ext: 'JPG',
|
|
397
|
-
category: 'image',
|
|
398
|
-
sizeKb: 175,
|
|
399
|
-
meta: '1080×600px Progressive JPEG',
|
|
400
|
-
color: '#FB7185',
|
|
401
|
-
status: 'warning',
|
|
402
|
-
advice: 'Dead asset: 0 component references (175 KB unused)',
|
|
403
|
-
previewUri: 'https://images.unsplash.com/photo-1550745165-9bc0b252726f?w=200&auto=format&fit=crop&q=80',
|
|
404
|
-
isConsumed: false,
|
|
405
|
-
},
|
|
406
|
-
// ── TypeScript & JSX ──
|
|
407
|
-
{
|
|
408
|
-
id: 'ts-1',
|
|
409
|
-
name: 'LogDetail.tsx',
|
|
410
|
-
path: 'src/components/Inspector/LogDetail.tsx',
|
|
411
|
-
ext: 'TSX',
|
|
412
|
-
category: 'typescript',
|
|
413
|
-
sizeKb: 44,
|
|
414
|
-
meta: '1,238 lines • React Component',
|
|
415
|
-
color: '#38BDF8',
|
|
416
|
-
status: 'optimal',
|
|
417
|
-
advice: 'Clean React.memo and useCallback optimization',
|
|
418
|
-
},
|
|
419
|
-
{
|
|
420
|
-
id: 'ts-2',
|
|
421
|
-
name: 'ReduxDetail.tsx',
|
|
422
|
-
path: 'src/components/Inspector/ReduxDetail.tsx',
|
|
423
|
-
ext: 'TSX',
|
|
424
|
-
category: 'typescript',
|
|
425
|
-
sizeKb: 38,
|
|
426
|
-
meta: '680 lines • Slice state tree & action timeline',
|
|
427
|
-
color: '#38BDF8',
|
|
428
|
-
status: 'optimal',
|
|
429
|
-
advice: 'Multi-tab subview with live state diffing',
|
|
430
|
-
},
|
|
431
|
-
{
|
|
432
|
-
id: 'ts-3',
|
|
433
|
-
name: 'NetworkTab.tsx',
|
|
434
|
-
path: 'src/components/Inspector/NetworkTab.tsx',
|
|
435
|
-
ext: 'TSX',
|
|
436
|
-
category: 'typescript',
|
|
437
|
-
sizeKb: 15,
|
|
438
|
-
meta: '486 lines • Network request virtualization list',
|
|
439
|
-
color: '#0EA5E9',
|
|
440
|
-
status: 'optimal',
|
|
441
|
-
advice: 'FlatList windowSize=7 tuned for 60fps',
|
|
442
|
-
},
|
|
443
|
-
{
|
|
444
|
-
id: 'ts-4',
|
|
445
|
-
name: 'SettingsPanel.tsx',
|
|
446
|
-
path: 'src/components/Inspector/SettingsPanel.tsx',
|
|
447
|
-
ext: 'TSX',
|
|
448
|
-
category: 'typescript',
|
|
449
|
-
sizeKb: 65,
|
|
450
|
-
meta: '1,845 lines • Preferences & storage manager',
|
|
451
|
-
color: '#38BDF8',
|
|
452
|
-
status: 'info',
|
|
453
|
-
advice: 'Self-contained settings subviews',
|
|
454
|
-
},
|
|
455
|
-
{
|
|
456
|
-
id: 'ts-5',
|
|
457
|
-
name: 'consoleLogger.ts',
|
|
458
|
-
path: 'src/customHooks/consoleLogger.ts',
|
|
459
|
-
ext: 'TS',
|
|
460
|
-
category: 'typescript',
|
|
461
|
-
sizeKb: 12,
|
|
462
|
-
meta: '360 lines • Metro symbolication bridge',
|
|
463
|
-
color: '#0284C7',
|
|
464
|
-
status: 'optimal',
|
|
465
|
-
advice: 'Zero-overhead in-memory stack capture',
|
|
466
|
-
},
|
|
467
|
-
{
|
|
468
|
-
id: 'ts-6',
|
|
469
|
-
name: 'App.tsx (Playground)',
|
|
470
|
-
path: 'example/App.tsx',
|
|
471
|
-
ext: 'TSX',
|
|
472
|
-
category: 'typescript',
|
|
473
|
-
sizeKb: 25,
|
|
474
|
-
meta: '820 lines • Interactive test scenarios',
|
|
475
|
-
color: '#38BDF8',
|
|
476
|
-
status: 'optimal',
|
|
477
|
-
advice: 'Sample harness for logs, APIs, and Redux',
|
|
478
|
-
},
|
|
479
|
-
// ── JavaScript & Node Modules ──
|
|
480
|
-
{
|
|
481
|
-
id: 'js-1',
|
|
482
|
-
name: 'react-native/Libraries/Renderer',
|
|
483
|
-
path: 'node_modules/react-native/Libraries/Renderer/implementations/ReactNativeRenderer-prod.js',
|
|
484
|
-
ext: 'JS',
|
|
485
|
-
category: 'javascript',
|
|
486
|
-
sizeKb: 480,
|
|
487
|
-
meta: 'Fabric & Paper UI reconciler bundle',
|
|
488
|
-
color: '#6366F1',
|
|
489
|
-
status: 'optimal',
|
|
490
|
-
advice: 'AOT compiled with Hermes Bytecode',
|
|
491
|
-
},
|
|
492
|
-
{
|
|
493
|
-
id: 'js-2',
|
|
494
|
-
name: '@react-navigation/native-stack',
|
|
495
|
-
path: 'node_modules/@react-navigation/native-stack/lib/module/index.js',
|
|
496
|
-
ext: 'JS',
|
|
497
|
-
category: 'javascript',
|
|
498
|
-
sizeKb: 195,
|
|
499
|
-
meta: 'Native screen animation & transition engine',
|
|
500
|
-
color: '#818CF8',
|
|
501
|
-
status: 'optimal',
|
|
502
|
-
advice: 'Optimized modular ES bundle',
|
|
503
|
-
},
|
|
504
|
-
{
|
|
505
|
-
id: 'js-3',
|
|
506
|
-
name: 'axios/dist/esm/axios.js',
|
|
507
|
-
path: 'node_modules/axios/dist/esm/axios.js',
|
|
508
|
-
ext: 'JS',
|
|
509
|
-
category: 'javascript',
|
|
510
|
-
sizeKb: 88,
|
|
511
|
-
meta: 'HTTP client core with interceptors',
|
|
512
|
-
color: '#F59E0B',
|
|
513
|
-
status: 'optimal',
|
|
514
|
-
advice: 'Standard lightweight network adapter',
|
|
515
|
-
},
|
|
516
|
-
{
|
|
517
|
-
id: 'js-4',
|
|
518
|
-
name: 'react-native-svg',
|
|
519
|
-
path: 'node_modules/react-native-svg/lib/module/index.js',
|
|
520
|
-
ext: 'JS',
|
|
521
|
-
category: 'javascript',
|
|
522
|
-
sizeKb: 140,
|
|
523
|
-
meta: 'Native SVG drawing and vector paths',
|
|
524
|
-
color: '#4F46E5',
|
|
525
|
-
status: 'optimal',
|
|
526
|
-
advice: 'Native Fabric/Paper vector bridge',
|
|
527
|
-
},
|
|
528
|
-
// ── Fonts & Typography ──
|
|
529
|
-
{
|
|
530
|
-
id: 'font-1',
|
|
531
|
-
name: 'Inter-Bold.ttf',
|
|
532
|
-
path: 'fonts/Inter-Bold.ttf',
|
|
533
|
-
ext: 'TTF',
|
|
534
|
-
category: 'font',
|
|
535
|
-
sizeKb: 165,
|
|
536
|
-
meta: 'Custom UI Font • Full Latin Charset (480 glyphs)',
|
|
537
|
-
color: '#8B5CF6',
|
|
538
|
-
status: 'info',
|
|
539
|
-
advice: 'High readability modern typography',
|
|
540
|
-
},
|
|
541
|
-
{
|
|
542
|
-
id: 'font-2',
|
|
543
|
-
name: 'Inter-Regular.ttf',
|
|
544
|
-
path: 'fonts/Inter-Regular.ttf',
|
|
545
|
-
ext: 'TTF',
|
|
546
|
-
category: 'font',
|
|
547
|
-
sizeKb: 160,
|
|
548
|
-
meta: 'Custom Body Font • 400 Weight (480 glyphs)',
|
|
549
|
-
color: '#8B5CF6',
|
|
550
|
-
status: 'info',
|
|
551
|
-
advice: 'Applied across inspector UI cards and text',
|
|
552
|
-
},
|
|
553
|
-
{
|
|
554
|
-
id: 'font-3',
|
|
555
|
-
name: 'JetBrainsMono-Regular.ttf',
|
|
556
|
-
path: 'fonts/JetBrainsMono-Regular.ttf',
|
|
557
|
-
ext: 'TTF',
|
|
558
|
-
category: 'font',
|
|
559
|
-
sizeKb: 95,
|
|
560
|
-
meta: 'Monospace Code Font • Raw / Table / Stack Traces',
|
|
561
|
-
color: '#A855F7',
|
|
562
|
-
status: 'optimal',
|
|
563
|
-
advice: 'Crisp font for code snippets and stack traces',
|
|
564
|
-
},
|
|
565
|
-
// ── JSON & Localizations ──
|
|
566
|
-
{
|
|
567
|
-
id: 'json-1',
|
|
568
|
-
name: 'en.json (i18n Translations)',
|
|
569
|
-
path: 'src/i18n/locales/en.json',
|
|
570
|
-
ext: 'JSON',
|
|
571
|
-
category: 'json',
|
|
572
|
-
sizeKb: 18,
|
|
573
|
-
meta: '124 localization translation keys',
|
|
574
|
-
color: '#10B981',
|
|
575
|
-
status: 'optimal',
|
|
576
|
-
advice: 'Static JSON locale dictionary',
|
|
577
|
-
},
|
|
578
|
-
{
|
|
579
|
-
id: 'json-2',
|
|
580
|
-
name: 'package.json',
|
|
581
|
-
path: 'package.json',
|
|
582
|
-
ext: 'JSON',
|
|
583
|
-
category: 'json',
|
|
584
|
-
sizeKb: 3,
|
|
585
|
-
meta: 'Manifest & peer dependencies config',
|
|
586
|
-
color: '#059669',
|
|
587
|
-
status: 'optimal',
|
|
588
|
-
advice: 'Stripped during production bundle export',
|
|
589
|
-
},
|
|
590
|
-
];
|
|
591
|
-
// ─── Packages / Node Modules Breakdown Data ──────────────────────────────────
|
|
592
|
-
const BUNDLE_PACKAGES_DATA = [
|
|
593
|
-
{
|
|
594
|
-
id: 'pkg-1',
|
|
595
|
-
name: 'react-native',
|
|
596
|
-
version: '0.85.3',
|
|
597
|
-
latestVersion: '0.85.3',
|
|
598
|
-
sizeKb: 680,
|
|
599
|
-
percentage: 38.5,
|
|
600
|
-
type: 'direct',
|
|
601
|
-
category: 'core',
|
|
602
|
-
color: '#38BDF8',
|
|
603
|
-
description: 'React Native runtime, Fabric UI engine, Native Bridge, and Turbomodules.',
|
|
604
|
-
logoUri: 'https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/React-icon.svg/512px-React-icon.svg.png',
|
|
605
|
-
lastActive: '3 days ago',
|
|
606
|
-
npmUrl: 'https://www.npmjs.com/package/react-native',
|
|
607
|
-
isDeprecated: false,
|
|
608
|
-
},
|
|
609
|
-
{
|
|
610
|
-
id: 'pkg-2',
|
|
611
|
-
name: '@react-navigation/native-stack',
|
|
612
|
-
version: '^7.16.0',
|
|
613
|
-
latestVersion: '7.16.0',
|
|
614
|
-
sizeKb: 245,
|
|
615
|
-
percentage: 13.9,
|
|
616
|
-
type: 'direct',
|
|
617
|
-
category: 'navigation',
|
|
618
|
-
color: '#818CF8',
|
|
619
|
-
description: 'Native screen router, UIViewController / Fragment animations and transitions.',
|
|
620
|
-
logoUri: 'https://reactnavigation.org/img/spiro.svg',
|
|
621
|
-
lastActive: '1 week ago',
|
|
622
|
-
npmUrl: 'https://www.npmjs.com/package/@react-navigation/native-stack',
|
|
623
|
-
isDeprecated: false,
|
|
624
|
-
},
|
|
625
|
-
{
|
|
626
|
-
id: 'pkg-3',
|
|
627
|
-
name: 'react',
|
|
628
|
-
version: '19.2.3',
|
|
629
|
-
latestVersion: '19.2.3',
|
|
630
|
-
sizeKb: 185,
|
|
631
|
-
percentage: 10.5,
|
|
632
|
-
type: 'direct',
|
|
633
|
-
category: 'core',
|
|
634
|
-
color: '#06B6D4',
|
|
635
|
-
description: 'React core virtual DOM reconciler, hooks engine, and JSX runtime.',
|
|
636
|
-
logoUri: 'https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/React-icon.svg/512px-React-icon.svg.png',
|
|
637
|
-
lastActive: '5 days ago',
|
|
638
|
-
npmUrl: 'https://www.npmjs.com/package/react',
|
|
639
|
-
isDeprecated: false,
|
|
640
|
-
},
|
|
641
|
-
{
|
|
642
|
-
id: 'pkg-4',
|
|
643
|
-
name: 'react-native-svg',
|
|
644
|
-
version: '^15.15.5',
|
|
645
|
-
latestVersion: '15.16.0',
|
|
646
|
-
sizeKb: 140,
|
|
647
|
-
percentage: 7.9,
|
|
648
|
-
type: 'direct',
|
|
649
|
-
category: 'ui',
|
|
650
|
-
color: '#F472B6',
|
|
651
|
-
description: 'Scalable Vector Graphics XML parser and native canvas path rasterizer.',
|
|
652
|
-
logoUri: 'https://upload.wikimedia.org/wikipedia/commons/thumb/0/02/SVG_logo.svg/512px-SVG_logo.svg.png',
|
|
653
|
-
lastActive: '2 days ago',
|
|
654
|
-
npmUrl: 'https://www.npmjs.com/package/react-native-svg',
|
|
655
|
-
isDeprecated: false,
|
|
656
|
-
},
|
|
657
|
-
{
|
|
658
|
-
id: 'pkg-5',
|
|
659
|
-
name: 'react-native-screens',
|
|
660
|
-
version: '^4.25.2',
|
|
661
|
-
latestVersion: '4.25.2',
|
|
662
|
-
sizeKb: 110,
|
|
663
|
-
percentage: 6.2,
|
|
664
|
-
type: 'transitive',
|
|
665
|
-
category: 'navigation',
|
|
666
|
-
color: '#A78BFA',
|
|
667
|
-
description: 'Native navigation container primitives for iOS and Android memory management.',
|
|
668
|
-
logoUri: 'https://reactnavigation.org/img/spiro.svg',
|
|
669
|
-
lastActive: '2 weeks ago',
|
|
670
|
-
npmUrl: 'https://www.npmjs.com/package/react-native-screens',
|
|
671
|
-
isDeprecated: false,
|
|
672
|
-
},
|
|
673
|
-
{
|
|
674
|
-
id: 'pkg-6',
|
|
675
|
-
name: 'axios',
|
|
676
|
-
version: '^1.17.0',
|
|
677
|
-
latestVersion: '1.18.0',
|
|
678
|
-
sizeKb: 88,
|
|
679
|
-
percentage: 5.0,
|
|
680
|
-
type: 'direct',
|
|
681
|
-
category: 'network',
|
|
682
|
-
color: '#F59E0B',
|
|
683
|
-
description: 'HTTP client, request/response interceptors, and adapter pipelines.',
|
|
684
|
-
logoUri: 'https://axios-http.com/assets/logo.svg',
|
|
685
|
-
lastActive: '4 days ago',
|
|
686
|
-
npmUrl: 'https://www.npmjs.com/package/axios',
|
|
687
|
-
isDeprecated: false,
|
|
688
|
-
},
|
|
689
|
-
{
|
|
690
|
-
id: 'pkg-7',
|
|
691
|
-
name: 'react-native-linear-gradient',
|
|
692
|
-
version: '^2.8.3',
|
|
693
|
-
latestVersion: '2.8.3',
|
|
694
|
-
sizeKb: 45,
|
|
695
|
-
percentage: 2.5,
|
|
696
|
-
type: 'direct',
|
|
697
|
-
category: 'ui',
|
|
698
|
-
color: '#FB7185',
|
|
699
|
-
description: 'Hardware accelerated native gradient drawing shaders.',
|
|
700
|
-
logoUri: 'https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/React-icon.svg/512px-React-icon.svg.png',
|
|
701
|
-
lastActive: '2 years ago',
|
|
702
|
-
npmUrl: 'https://www.npmjs.com/package/react-native-linear-gradient',
|
|
703
|
-
isDeprecated: true,
|
|
704
|
-
deprecationReason: 'Package maintenance is in transition. Consider expo-linear-gradient or @react-native-community/linear-gradient for Fabric support.',
|
|
705
|
-
},
|
|
706
|
-
{
|
|
707
|
-
id: 'pkg-8',
|
|
708
|
-
name: 'i18next & react-i18next',
|
|
709
|
-
version: '^26.3.6',
|
|
710
|
-
latestVersion: '26.3.6',
|
|
711
|
-
sizeKb: 62,
|
|
712
|
-
percentage: 3.5,
|
|
713
|
-
type: 'direct',
|
|
714
|
-
category: 'utils',
|
|
715
|
-
color: '#10B981',
|
|
716
|
-
description: 'Internationalization framework, language interpolator, and locale switcher.',
|
|
717
|
-
logoUri: 'https://raw.githubusercontent.com/i18next/i18next/master/assets/i18next-logo.png',
|
|
718
|
-
lastActive: '6 days ago',
|
|
719
|
-
npmUrl: 'https://www.npmjs.com/package/i18next',
|
|
720
|
-
isDeprecated: false,
|
|
721
|
-
},
|
|
722
|
-
];
|
|
723
410
|
const CATEGORY_TABS = [
|
|
724
|
-
{ key: 'ALL',
|
|
725
|
-
{ key: 'UNUSED',
|
|
726
|
-
{ key: 'CONSUMED',
|
|
727
|
-
{ key: 'image',
|
|
728
|
-
{ key: 'typescript',
|
|
729
|
-
{ key: 'javascript',
|
|
730
|
-
{ key: 'font',
|
|
731
|
-
{ key: 'json',
|
|
411
|
+
{ key: 'ALL', labelKey: 'bundle.catAll', iconType: 'folder' },
|
|
412
|
+
{ key: 'UNUSED', labelKey: 'bundle.catUnused', iconType: 'ban' },
|
|
413
|
+
{ key: 'CONSUMED', labelKey: 'bundle.catConsumed', iconType: 'bolt' },
|
|
414
|
+
{ key: 'image', labelKey: 'bundle.catImages', iconType: 'image' },
|
|
415
|
+
{ key: 'typescript', labelKey: 'bundle.catTypescript', iconType: 'typescript' },
|
|
416
|
+
{ key: 'javascript', labelKey: 'bundle.catJavascript', iconType: 'javascript' },
|
|
417
|
+
{ key: 'font', labelKey: 'bundle.catFonts', iconType: 'font' },
|
|
418
|
+
{ key: 'json', labelKey: 'bundle.catJson', iconType: 'json' },
|
|
732
419
|
];
|
|
733
420
|
const CATEGORY_COLORS = {
|
|
734
|
-
image: { label: 'Images & Media', color:
|
|
735
|
-
typescript: { label: 'TypeScript', color:
|
|
736
|
-
javascript: { label: 'JavaScript', color:
|
|
737
|
-
font: { label: 'Fonts', color:
|
|
738
|
-
json: { label: 'JSON / Data', color:
|
|
421
|
+
image: { label: 'Images & Media', color: AppColors.pink500, bg: AppColors.pink100 },
|
|
422
|
+
typescript: { label: 'TypeScript', color: AppColors.sky500, bg: AppColors.sky100 },
|
|
423
|
+
javascript: { label: 'JavaScript', color: AppColors.indigo500, bg: AppColors.indigo50 },
|
|
424
|
+
font: { label: 'Fonts', color: AppColors.purple500, bg: AppColors.purple100 },
|
|
425
|
+
json: { label: 'JSON / Data', color: AppColors.emerald500, bg: AppColors.emerald100 },
|
|
739
426
|
};
|
|
740
427
|
const BundleTab = React.memo(() => {
|
|
741
428
|
const { t } = useTranslation();
|
|
742
429
|
const [activeSubTab, setActiveSubTab] = useState('overview');
|
|
743
430
|
const [search, setSearch] = useState('');
|
|
744
431
|
const [activeCategory, setActiveCategory] = useState('ALL');
|
|
432
|
+
// ─── Live Host App Bundle Analysis (fetched from Metro / runtime) ─────────
|
|
433
|
+
const [analysis, setAnalysis] = useState(() => getCachedBundleAnalysis());
|
|
434
|
+
const [isAnalyzing, setIsAnalyzing] = useState(!getCachedBundleAnalysis());
|
|
435
|
+
useEffect(() => {
|
|
436
|
+
let mounted = true;
|
|
437
|
+
analyzeHostAppBundle().then(result => {
|
|
438
|
+
if (mounted) {
|
|
439
|
+
setAnalysis(result);
|
|
440
|
+
setIsAnalyzing(false);
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
return () => {
|
|
444
|
+
mounted = false;
|
|
445
|
+
};
|
|
446
|
+
}, []);
|
|
745
447
|
// Check if Hermes is running
|
|
746
448
|
const isHermes = Boolean(global.HermesInternal);
|
|
449
|
+
// Dynamic files & packages derived from the analyzed host bundle
|
|
450
|
+
const bundleFiles = useMemo(() => (analysis ? analysis.files : []), [analysis]);
|
|
451
|
+
const bundlePackages = useMemo(() => (analysis ? analysis.packages : []), [analysis]);
|
|
747
452
|
// Group summary calculations
|
|
748
453
|
const summary = useMemo(() => {
|
|
749
454
|
let imagesKb = 0;
|
|
@@ -756,7 +461,7 @@ const BundleTab = React.memo(() => {
|
|
|
756
461
|
let jsCount = 0;
|
|
757
462
|
let fontsCount = 0;
|
|
758
463
|
let jsonCount = 0;
|
|
759
|
-
|
|
464
|
+
bundleFiles.forEach(f => {
|
|
760
465
|
if (f.category === 'image') {
|
|
761
466
|
imagesKb += f.sizeKb;
|
|
762
467
|
imagesCount++;
|
|
@@ -782,37 +487,37 @@ const BundleTab = React.memo(() => {
|
|
|
782
487
|
return {
|
|
783
488
|
totalKb,
|
|
784
489
|
totalMb: (totalKb / 1024).toFixed(2),
|
|
785
|
-
totalCount:
|
|
490
|
+
totalCount: bundleFiles.length,
|
|
786
491
|
images: {
|
|
787
492
|
count: imagesCount,
|
|
788
493
|
kb: imagesKb,
|
|
789
|
-
pct: Number(((imagesKb / totalKb) * 100).toFixed(1)),
|
|
494
|
+
pct: totalKb ? Number(((imagesKb / totalKb) * 100).toFixed(1)) : 0,
|
|
790
495
|
},
|
|
791
496
|
ts: {
|
|
792
497
|
count: tsCount,
|
|
793
498
|
kb: tsKb,
|
|
794
|
-
pct: Number(((tsKb / totalKb) * 100).toFixed(1)),
|
|
499
|
+
pct: totalKb ? Number(((tsKb / totalKb) * 100).toFixed(1)) : 0,
|
|
795
500
|
},
|
|
796
501
|
js: {
|
|
797
502
|
count: jsCount,
|
|
798
503
|
kb: jsKb,
|
|
799
|
-
pct: Number(((jsKb / totalKb) * 100).toFixed(1)),
|
|
504
|
+
pct: totalKb ? Number(((jsKb / totalKb) * 100).toFixed(1)) : 0,
|
|
800
505
|
},
|
|
801
506
|
fonts: {
|
|
802
507
|
count: fontsCount,
|
|
803
508
|
kb: fontsKb,
|
|
804
|
-
pct: Number(((fontsKb / totalKb) * 100).toFixed(1)),
|
|
509
|
+
pct: totalKb ? Number(((fontsKb / totalKb) * 100).toFixed(1)) : 0,
|
|
805
510
|
},
|
|
806
511
|
json: {
|
|
807
512
|
count: jsonCount,
|
|
808
513
|
kb: jsonKb,
|
|
809
|
-
pct: Number(((jsonKb / totalKb) * 100).toFixed(1)),
|
|
514
|
+
pct: totalKb ? Number(((jsonKb / totalKb) * 100).toFixed(1)) : 0,
|
|
810
515
|
},
|
|
811
516
|
};
|
|
812
|
-
}, []);
|
|
517
|
+
}, [bundleFiles]);
|
|
813
518
|
// Filtered files
|
|
814
519
|
const filteredFiles = useMemo(() => {
|
|
815
|
-
return
|
|
520
|
+
return bundleFiles.filter(file => {
|
|
816
521
|
const matchSearch = !search ||
|
|
817
522
|
file.name.toLowerCase().includes(search.toLowerCase()) ||
|
|
818
523
|
file.path.toLowerCase().includes(search.toLowerCase()) ||
|
|
@@ -827,7 +532,7 @@ const BundleTab = React.memo(() => {
|
|
|
827
532
|
: file.category === activeCategory;
|
|
828
533
|
return matchSearch && matchCategory;
|
|
829
534
|
});
|
|
830
|
-
}, [search, activeCategory]);
|
|
535
|
+
}, [search, activeCategory, bundleFiles]);
|
|
831
536
|
const filteredTotalKb = useMemo(() => {
|
|
832
537
|
return filteredFiles.reduce((acc, f) => acc + f.sizeKb, 0);
|
|
833
538
|
}, [filteredFiles]);
|
|
@@ -862,46 +567,46 @@ const BundleTab = React.memo(() => {
|
|
|
862
567
|
// Filtered Packages
|
|
863
568
|
const filteredPackages = useMemo(() => {
|
|
864
569
|
if (!search)
|
|
865
|
-
return
|
|
570
|
+
return bundlePackages;
|
|
866
571
|
const q = search.toLowerCase();
|
|
867
|
-
return
|
|
572
|
+
return bundlePackages.filter(pkg => pkg.name.toLowerCase().includes(q) ||
|
|
868
573
|
pkg.description.toLowerCase().includes(q) ||
|
|
869
574
|
pkg.category.toLowerCase().includes(q));
|
|
870
|
-
}, [search]);
|
|
575
|
+
}, [search, bundlePackages]);
|
|
871
576
|
const packagesTotalKb = useMemo(() => {
|
|
872
577
|
return filteredPackages.reduce((acc, p) => acc + p.sizeKb, 0);
|
|
873
578
|
}, [filteredPackages]);
|
|
874
579
|
// Media files (Images + Fonts)
|
|
875
580
|
const mediaFiles = useMemo(() => {
|
|
876
|
-
return
|
|
877
|
-
}, []);
|
|
581
|
+
return bundleFiles.filter(f => f.category === 'image' || f.category === 'font');
|
|
582
|
+
}, [bundleFiles]);
|
|
878
583
|
const mediaTotalKb = useMemo(() => {
|
|
879
584
|
return mediaFiles.reduce((acc, m) => acc + m.sizeKb, 0);
|
|
880
585
|
}, [mediaFiles]);
|
|
881
586
|
const subTabs = [
|
|
882
587
|
{
|
|
883
588
|
key: 'overview',
|
|
884
|
-
label: '
|
|
589
|
+
label: t('bundle.tabOverview'),
|
|
885
590
|
icon: (isActive) => (<LiveStateIcon color={isActive ? AppColors.white : AppColors.purple} size={12}/>),
|
|
886
591
|
},
|
|
887
592
|
{
|
|
888
593
|
key: 'files',
|
|
889
|
-
label:
|
|
594
|
+
label: t('bundle.tabFiles', { count: bundleFiles.length }),
|
|
890
595
|
icon: (isActive) => (<LayersIcon color={isActive ? AppColors.white : AppColors.purple} size={12}/>),
|
|
891
596
|
},
|
|
892
597
|
{
|
|
893
598
|
key: 'packages',
|
|
894
|
-
label:
|
|
599
|
+
label: t('bundle.tabPackages', { count: bundlePackages.length }),
|
|
895
600
|
icon: (isActive) => (<PackageIcon color={isActive ? AppColors.white : AppColors.purple} size={12}/>),
|
|
896
601
|
},
|
|
897
602
|
{
|
|
898
603
|
key: 'media',
|
|
899
|
-
label:
|
|
604
|
+
label: t('bundle.tabMedia', { count: mediaFiles.length }),
|
|
900
605
|
icon: (isActive) => (<StorageIcon color={isActive ? AppColors.white : AppColors.purple} size={12}/>),
|
|
901
606
|
},
|
|
902
607
|
{
|
|
903
608
|
key: 'optimizer',
|
|
904
|
-
label: '
|
|
609
|
+
label: t('bundle.tabOptimizer'),
|
|
905
610
|
icon: (isActive) => (<MetadataIcon color={isActive ? AppColors.white : AppColors.purple} size={12}/>),
|
|
906
611
|
},
|
|
907
612
|
];
|
|
@@ -927,10 +632,21 @@ const BundleTab = React.memo(() => {
|
|
|
927
632
|
</ScrollView>
|
|
928
633
|
</View>
|
|
929
634
|
|
|
635
|
+
{/* ─── Live Bundle Analysis Loading State ─── */}
|
|
636
|
+
{isAnalyzing && (<View style={bundleStyles.analyzingContainer}>
|
|
637
|
+
<ActivityIndicator size="small" color={AppColors.brandPurple}/>
|
|
638
|
+
<Text style={bundleStyles.analyzingText}>
|
|
639
|
+
{t('bundle.analyzingTitle')}
|
|
640
|
+
</Text>
|
|
641
|
+
<Text style={bundleStyles.analyzingHint}>
|
|
642
|
+
{t('bundle.analyzingHint')}
|
|
643
|
+
</Text>
|
|
644
|
+
</View>)}
|
|
645
|
+
|
|
930
646
|
{/* ══════════════════════════════════════════════════════════════════════ */}
|
|
931
647
|
{/* ── 1. TAB: OVERVIEW & TREEMAP ───────────────────────────────────────── */}
|
|
932
648
|
{/* ══════════════════════════════════════════════════════════════════════ */}
|
|
933
|
-
{activeSubTab === 'overview' && (<ScrollView style={{ flex: 1 }} contentContainerStyle={bundleStyles.contentContainer} keyboardShouldPersistTaps="handled">
|
|
649
|
+
{activeSubTab === 'overview' && !isAnalyzing && (<ScrollView style={{ flex: 1 }} contentContainerStyle={bundleStyles.contentContainer} keyboardShouldPersistTaps="handled">
|
|
934
650
|
|
|
935
651
|
{/* Hero Overview Card */}
|
|
936
652
|
<View style={bundleStyles.heroCard}>
|
|
@@ -939,42 +655,59 @@ const BundleTab = React.memo(() => {
|
|
|
939
655
|
<PackageIcon color={AppColors.brandPurple} size={24}/>
|
|
940
656
|
</View>
|
|
941
657
|
<View style={{ flex: 1 }}>
|
|
942
|
-
<Text style={bundleStyles.heroTitle}>
|
|
658
|
+
<Text style={bundleStyles.heroTitle}>{t('bundle.heroTitle')}</Text>
|
|
943
659
|
<Text style={bundleStyles.heroSubtitle}>
|
|
944
|
-
|
|
660
|
+
{analysis?.isLive
|
|
661
|
+
? t('bundle.heroSubtitleLive', { scriptUrl: analysis.scriptURL })
|
|
662
|
+
: t('bundle.heroSubtitle')}
|
|
945
663
|
</Text>
|
|
946
664
|
</View>
|
|
947
|
-
<CopyButton value={() => summary} label=
|
|
665
|
+
<CopyButton value={() => ({ ...summary, bundle: analysis })} label={t('bundle.bundleOverviewJson')}/>
|
|
948
666
|
</View>
|
|
949
667
|
|
|
950
668
|
{/* 4-Stat Metric Grid */}
|
|
951
669
|
<View style={bundleStyles.metricsGrid}>
|
|
952
670
|
<View style={bundleStyles.metricBox}>
|
|
953
|
-
<Text style={bundleStyles.metricLabel}>
|
|
954
|
-
<Text style={bundleStyles.metricValue}
|
|
955
|
-
|
|
671
|
+
<Text style={bundleStyles.metricLabel}>{t('bundle.devBundleSize')}</Text>
|
|
672
|
+
<Text style={bundleStyles.metricValue}>
|
|
673
|
+
{t('bundle.mbValue', {
|
|
674
|
+
size: analysis ? analysis.totalDevMb : summary.totalMb,
|
|
675
|
+
})}
|
|
676
|
+
</Text>
|
|
677
|
+
<Text style={bundleStyles.metricHint}>
|
|
678
|
+
{analysis
|
|
679
|
+
? t('bundle.devBundleHint', {
|
|
680
|
+
kb: analysis.totalDevKb,
|
|
681
|
+
modules: analysis.moduleCount,
|
|
682
|
+
})
|
|
683
|
+
: t('bundle.trackedFilesHint', { count: bundleFiles.length })}
|
|
684
|
+
</Text>
|
|
956
685
|
</View>
|
|
957
686
|
|
|
958
687
|
<View style={bundleStyles.metricBox}>
|
|
959
|
-
<Text style={bundleStyles.metricLabel}>
|
|
960
|
-
<Text style={[bundleStyles.metricValue, { color:
|
|
961
|
-
{summary.images.kb}
|
|
688
|
+
<Text style={bundleStyles.metricLabel}>{t('bundle.imagesMedia')}</Text>
|
|
689
|
+
<Text style={[bundleStyles.metricValue, { color: AppColors.pink500 }]}>
|
|
690
|
+
{t('bundle.fileSizeKb', { size: summary.images.kb })}
|
|
691
|
+
</Text>
|
|
692
|
+
<Text style={bundleStyles.metricHint}>
|
|
693
|
+
{t('bundle.pctOfTrackedAssets', { pct: summary.images.pct })}
|
|
962
694
|
</Text>
|
|
963
|
-
<Text style={bundleStyles.metricHint}>{summary.images.pct}% of package</Text>
|
|
964
695
|
</View>
|
|
965
696
|
|
|
966
697
|
<View style={bundleStyles.metricBox}>
|
|
967
|
-
<Text style={bundleStyles.metricLabel}>
|
|
968
|
-
<Text style={[bundleStyles.metricValue, { color:
|
|
969
|
-
{summary.ts.kb + summary.js.kb}
|
|
698
|
+
<Text style={bundleStyles.metricLabel}>{t('bundle.tsJsSource')}</Text>
|
|
699
|
+
<Text style={[bundleStyles.metricValue, { color: AppColors.sky500 }]}>
|
|
700
|
+
{t('bundle.fileSizeKb', { size: summary.ts.kb + summary.js.kb })}
|
|
970
701
|
</Text>
|
|
971
702
|
<Text style={bundleStyles.metricHint}>
|
|
972
|
-
{(
|
|
703
|
+
{t('bundle.pctOfTrackedAssets', {
|
|
704
|
+
pct: (summary.ts.pct + summary.js.pct).toFixed(1),
|
|
705
|
+
})}
|
|
973
706
|
</Text>
|
|
974
707
|
</View>
|
|
975
708
|
|
|
976
709
|
<View style={bundleStyles.metricBox}>
|
|
977
|
-
<Text style={bundleStyles.metricLabel}>
|
|
710
|
+
<Text style={bundleStyles.metricLabel}>{t('bundle.jsEngine')}</Text>
|
|
978
711
|
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 5, marginTop: 2 }}>
|
|
979
712
|
<View style={[
|
|
980
713
|
bundleStyles.statusIndicator,
|
|
@@ -984,17 +717,109 @@ const BundleTab = React.memo(() => {
|
|
|
984
717
|
{isHermes ? 'Hermes' : 'JSC'}
|
|
985
718
|
</Text>
|
|
986
719
|
</View>
|
|
987
|
-
<Text style={bundleStyles.metricHint}>
|
|
720
|
+
<Text style={bundleStyles.metricHint}>
|
|
721
|
+
{isHermes ? t('bundle.bytecodeAot') : t('bundle.standard')}
|
|
722
|
+
</Text>
|
|
988
723
|
</View>
|
|
989
724
|
</View>
|
|
990
725
|
</View>
|
|
991
726
|
|
|
727
|
+
{/* Development Bundle Split-Up (derived from the real running bundle) */}
|
|
728
|
+
{analysis && (<View style={bundleStyles.sectionCard}>
|
|
729
|
+
<View style={bundleStyles.sectionHeaderRow}>
|
|
730
|
+
<View>
|
|
731
|
+
<Text style={bundleStyles.sectionTitle}>{t('bundle.splitUpTitle')}</Text>
|
|
732
|
+
<Text style={bundleStyles.sectionSub}>
|
|
733
|
+
{t('bundle.splitUpSub', {
|
|
734
|
+
mb: analysis.totalDevMb,
|
|
735
|
+
files: analysis.filesCount,
|
|
736
|
+
packages: analysis.packageCount,
|
|
737
|
+
})}
|
|
738
|
+
</Text>
|
|
739
|
+
</View>
|
|
740
|
+
<CopyButton value={() => analysis.splitUp} label={t('bundle.splitUpJson')}/>
|
|
741
|
+
</View>
|
|
742
|
+
|
|
743
|
+
{[
|
|
744
|
+
{
|
|
745
|
+
iconType: 'source',
|
|
746
|
+
label: t('bundle.splitAppSource'),
|
|
747
|
+
color: AppColors.sky500,
|
|
748
|
+
...analysis.splitUp.appSource,
|
|
749
|
+
},
|
|
750
|
+
{
|
|
751
|
+
iconType: 'deps',
|
|
752
|
+
label: t('bundle.splitNodeModules'),
|
|
753
|
+
color: AppColors.indigo500,
|
|
754
|
+
...analysis.splitUp.nodeModules,
|
|
755
|
+
},
|
|
756
|
+
{
|
|
757
|
+
iconType: 'media',
|
|
758
|
+
label: t('bundle.splitAssetsMedia'),
|
|
759
|
+
color: AppColors.pink500,
|
|
760
|
+
...analysis.splitUp.assetsMedia,
|
|
761
|
+
},
|
|
762
|
+
{
|
|
763
|
+
iconType: 'overhead',
|
|
764
|
+
label: t('bundle.splitMetroOverhead'),
|
|
765
|
+
color: AppColors.amber500,
|
|
766
|
+
...analysis.splitUp.metroDevOverhead,
|
|
767
|
+
},
|
|
768
|
+
].map((part, idx) => (<View key={idx} style={bundleStyles.catRowCard}>
|
|
769
|
+
<View style={bundleStyles.catRowTop}>
|
|
770
|
+
<BundleCategoryIcon type={part.iconType} size={18} color={part.color}/>
|
|
771
|
+
<View style={{ flex: 1 }}>
|
|
772
|
+
<Text style={bundleStyles.catRowTitle}>{part.label}</Text>
|
|
773
|
+
<Text style={bundleStyles.catRowDesc}>
|
|
774
|
+
{t('bundle.splitSize', { kb: part.kb, mb: part.mb })}
|
|
775
|
+
</Text>
|
|
776
|
+
</View>
|
|
777
|
+
<View style={{ alignItems: 'flex-end' }}>
|
|
778
|
+
<Text style={bundleStyles.catRowSize}>{part.pct}%</Text>
|
|
779
|
+
</View>
|
|
780
|
+
</View>
|
|
781
|
+
<View style={bundleStyles.catProgressTrack}>
|
|
782
|
+
<View style={[
|
|
783
|
+
bundleStyles.catProgressBar,
|
|
784
|
+
{ width: `${part.pct}%`, backgroundColor: part.color },
|
|
785
|
+
]}/>
|
|
786
|
+
</View>
|
|
787
|
+
</View>))}
|
|
788
|
+
|
|
789
|
+
{/* Production Binary Footprint */}
|
|
790
|
+
<View style={bundleStyles.totalSummaryCard}>
|
|
791
|
+
<View style={bundleStyles.totalSummaryRow}>
|
|
792
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
|
|
793
|
+
<PackageIcon color={AppColors.purple} size={16}/>
|
|
794
|
+
<Text style={bundleStyles.totalSummaryLabel}>
|
|
795
|
+
{t('bundle.productionFootprint')}
|
|
796
|
+
</Text>
|
|
797
|
+
</View>
|
|
798
|
+
<Text style={bundleStyles.totalSummaryValue}>
|
|
799
|
+
{t('bundle.productionValue', {
|
|
800
|
+
ios: analysis.production.ios.totalInstallMb,
|
|
801
|
+
android: analysis.production.android.totalInstallMb,
|
|
802
|
+
})}
|
|
803
|
+
</Text>
|
|
804
|
+
</View>
|
|
805
|
+
<Text style={bundleStyles.totalSummaryFormula}>
|
|
806
|
+
{t('bundle.productionDownload', {
|
|
807
|
+
ios: analysis.production.ios.totalDownloadMb,
|
|
808
|
+
aab: analysis.production.androidAab.totalDownloadMb,
|
|
809
|
+
apk: analysis.production.androidApk.totalDownloadMb,
|
|
810
|
+
})}
|
|
811
|
+
</Text>
|
|
812
|
+
</View>
|
|
813
|
+
</View>)}
|
|
814
|
+
|
|
992
815
|
{/* Visual Type Distribution Ratio Bar */}
|
|
993
816
|
<View style={bundleStyles.sectionCard}>
|
|
994
817
|
<View style={bundleStyles.sectionHeaderRow}>
|
|
995
818
|
<View>
|
|
996
|
-
<Text style={bundleStyles.sectionTitle}>
|
|
997
|
-
<Text style={bundleStyles.sectionSub}>
|
|
819
|
+
<Text style={bundleStyles.sectionTitle}>{t('bundle.treemapTitle')}</Text>
|
|
820
|
+
<Text style={bundleStyles.sectionSub}>
|
|
821
|
+
{t('bundle.treemapSub', { mb: summary.totalMb })}
|
|
822
|
+
</Text>
|
|
998
823
|
</View>
|
|
999
824
|
<CopyButton value={() => ({
|
|
1000
825
|
totalMb: summary.totalMb,
|
|
@@ -1004,48 +829,63 @@ const BundleTab = React.memo(() => {
|
|
|
1004
829
|
js: summary.js,
|
|
1005
830
|
fonts: summary.fonts,
|
|
1006
831
|
json: summary.json,
|
|
1007
|
-
})} label=
|
|
832
|
+
})} label={t('bundle.treemapJson')}/>
|
|
1008
833
|
</View>
|
|
1009
834
|
|
|
1010
835
|
{/* Stacked Colored Bar */}
|
|
1011
836
|
<View style={bundleStyles.treemapBar}>
|
|
1012
|
-
<View style={{ flex: summary.images.pct, backgroundColor:
|
|
1013
|
-
<View style={{ flex: summary.ts.pct, backgroundColor:
|
|
1014
|
-
<View style={{ flex: summary.js.pct, backgroundColor:
|
|
1015
|
-
<View style={{ flex: summary.fonts.pct, backgroundColor:
|
|
1016
|
-
<View style={{ flex: summary.json.pct, backgroundColor:
|
|
837
|
+
<View style={{ flex: summary.images.pct, backgroundColor: AppColors.pink500, height: 16 }}/>
|
|
838
|
+
<View style={{ flex: summary.ts.pct, backgroundColor: AppColors.sky500, height: 16 }}/>
|
|
839
|
+
<View style={{ flex: summary.js.pct, backgroundColor: AppColors.indigo500, height: 16 }}/>
|
|
840
|
+
<View style={{ flex: summary.fonts.pct, backgroundColor: AppColors.purple500, height: 16 }}/>
|
|
841
|
+
<View style={{ flex: summary.json.pct, backgroundColor: AppColors.emerald500, height: 16 }}/>
|
|
1017
842
|
</View>
|
|
1018
843
|
|
|
1019
844
|
{/* Legend Grid */}
|
|
1020
845
|
<View style={bundleStyles.legendGrid}>
|
|
1021
846
|
<View style={bundleStyles.legendItem}>
|
|
1022
|
-
<
|
|
847
|
+
<BundleCategoryIcon type="image" size={13} color={AppColors.pink500}/>
|
|
1023
848
|
<Text style={bundleStyles.legendText}>
|
|
1024
|
-
|
|
849
|
+
{t('bundle.legendImages')}{' '}
|
|
850
|
+
<Text style={bundleStyles.legendVal}>
|
|
851
|
+
{t('bundle.legendValue', { kb: summary.images.kb, pct: summary.images.pct })}
|
|
852
|
+
</Text>
|
|
1025
853
|
</Text>
|
|
1026
854
|
</View>
|
|
1027
855
|
<View style={bundleStyles.legendItem}>
|
|
1028
|
-
<
|
|
856
|
+
<BundleCategoryIcon type="typescript" size={13} color={AppColors.sky500}/>
|
|
1029
857
|
<Text style={bundleStyles.legendText}>
|
|
1030
|
-
|
|
858
|
+
{t('bundle.legendTs')}{' '}
|
|
859
|
+
<Text style={bundleStyles.legendVal}>
|
|
860
|
+
{t('bundle.legendValue', { kb: summary.ts.kb, pct: summary.ts.pct })}
|
|
861
|
+
</Text>
|
|
1031
862
|
</Text>
|
|
1032
863
|
</View>
|
|
1033
864
|
<View style={bundleStyles.legendItem}>
|
|
1034
|
-
<
|
|
865
|
+
<BundleCategoryIcon type="javascript" size={13} color={AppColors.indigo500}/>
|
|
1035
866
|
<Text style={bundleStyles.legendText}>
|
|
1036
|
-
|
|
867
|
+
{t('bundle.legendJs')}{' '}
|
|
868
|
+
<Text style={bundleStyles.legendVal}>
|
|
869
|
+
{t('bundle.legendValue', { kb: summary.js.kb, pct: summary.js.pct })}
|
|
870
|
+
</Text>
|
|
1037
871
|
</Text>
|
|
1038
872
|
</View>
|
|
1039
873
|
<View style={bundleStyles.legendItem}>
|
|
1040
|
-
<
|
|
874
|
+
<BundleCategoryIcon type="font" size={13} color={AppColors.purple500}/>
|
|
1041
875
|
<Text style={bundleStyles.legendText}>
|
|
1042
|
-
|
|
876
|
+
{t('bundle.legendFonts')}{' '}
|
|
877
|
+
<Text style={bundleStyles.legendVal}>
|
|
878
|
+
{t('bundle.legendValue', { kb: summary.fonts.kb, pct: summary.fonts.pct })}
|
|
879
|
+
</Text>
|
|
1043
880
|
</Text>
|
|
1044
881
|
</View>
|
|
1045
882
|
<View style={bundleStyles.legendItem}>
|
|
1046
|
-
<
|
|
883
|
+
<BundleCategoryIcon type="json" size={13} color={AppColors.emerald500}/>
|
|
1047
884
|
<Text style={bundleStyles.legendText}>
|
|
1048
|
-
|
|
885
|
+
{t('bundle.legendJson')}{' '}
|
|
886
|
+
<Text style={bundleStyles.legendVal}>
|
|
887
|
+
{t('bundle.legendValue', { kb: summary.json.kb, pct: summary.json.pct })}
|
|
888
|
+
</Text>
|
|
1049
889
|
</Text>
|
|
1050
890
|
</View>
|
|
1051
891
|
</View>
|
|
@@ -1054,7 +894,7 @@ const BundleTab = React.memo(() => {
|
|
|
1054
894
|
{/* Detailed Category Footprint Cards */}
|
|
1055
895
|
<View style={bundleStyles.sectionCard}>
|
|
1056
896
|
<View style={bundleStyles.sectionHeaderRow}>
|
|
1057
|
-
<Text style={bundleStyles.sectionTitle}>
|
|
897
|
+
<Text style={bundleStyles.sectionTitle}>{t('bundle.categoryBreakdown')}</Text>
|
|
1058
898
|
<CopyButton value={() => ({
|
|
1059
899
|
totalAssets: summary.totalCount,
|
|
1060
900
|
totalSizeKb: summary.totalKb,
|
|
@@ -1065,61 +905,66 @@ const BundleTab = React.memo(() => {
|
|
|
1065
905
|
typescript: summary.ts,
|
|
1066
906
|
json: summary.json,
|
|
1067
907
|
},
|
|
1068
|
-
})} label=
|
|
908
|
+
})} label={t('bundle.categoriesJson')}/>
|
|
1069
909
|
</View>
|
|
1070
910
|
|
|
1071
911
|
{[
|
|
1072
912
|
{
|
|
1073
|
-
|
|
1074
|
-
title: '
|
|
913
|
+
iconType: 'image',
|
|
914
|
+
title: t('bundle.catImagesTitle'),
|
|
1075
915
|
count: summary.images.count,
|
|
1076
|
-
size:
|
|
916
|
+
size: t('bundle.fileSizeKb', { size: summary.images.kb }),
|
|
1077
917
|
pct: summary.images.pct,
|
|
1078
|
-
color:
|
|
1079
|
-
desc: '
|
|
918
|
+
color: AppColors.pink500,
|
|
919
|
+
desc: t('bundle.catImagesDesc'),
|
|
1080
920
|
},
|
|
1081
921
|
{
|
|
1082
|
-
|
|
1083
|
-
title: '
|
|
922
|
+
iconType: 'javascript',
|
|
923
|
+
title: t('bundle.catJsTitle'),
|
|
1084
924
|
count: summary.js.count,
|
|
1085
|
-
size:
|
|
925
|
+
size: t('bundle.fileSizeKb', { size: summary.js.kb }),
|
|
1086
926
|
pct: summary.js.pct,
|
|
1087
|
-
color:
|
|
1088
|
-
desc: '
|
|
927
|
+
color: AppColors.indigo500,
|
|
928
|
+
desc: t('bundle.catJsDesc'),
|
|
1089
929
|
},
|
|
1090
930
|
{
|
|
1091
|
-
|
|
1092
|
-
title: '
|
|
931
|
+
iconType: 'font',
|
|
932
|
+
title: t('bundle.catFontsTitle'),
|
|
1093
933
|
count: summary.fonts.count,
|
|
1094
|
-
size:
|
|
934
|
+
size: t('bundle.fileSizeKb', { size: summary.fonts.kb }),
|
|
1095
935
|
pct: summary.fonts.pct,
|
|
1096
|
-
color:
|
|
1097
|
-
desc:
|
|
936
|
+
color: AppColors.purple500,
|
|
937
|
+
desc: bundleFiles
|
|
938
|
+
.filter(f => f.category === 'font')
|
|
939
|
+
.map(f => f.name)
|
|
940
|
+
.join(', ') || t('bundle.catFontsDesc'),
|
|
1098
941
|
},
|
|
1099
942
|
{
|
|
1100
|
-
|
|
1101
|
-
title: '
|
|
943
|
+
iconType: 'typescript',
|
|
944
|
+
title: t('bundle.catTsTitle'),
|
|
1102
945
|
count: summary.ts.count,
|
|
1103
|
-
size:
|
|
946
|
+
size: t('bundle.fileSizeKb', { size: summary.ts.kb }),
|
|
1104
947
|
pct: summary.ts.pct,
|
|
1105
|
-
color:
|
|
1106
|
-
desc: '
|
|
948
|
+
color: AppColors.sky500,
|
|
949
|
+
desc: t('bundle.catTsDesc'),
|
|
1107
950
|
},
|
|
1108
951
|
{
|
|
1109
|
-
|
|
1110
|
-
title: '
|
|
952
|
+
iconType: 'json',
|
|
953
|
+
title: t('bundle.catJsonTitle'),
|
|
1111
954
|
count: summary.json.count,
|
|
1112
|
-
size:
|
|
955
|
+
size: t('bundle.fileSizeKb', { size: summary.json.kb }),
|
|
1113
956
|
pct: summary.json.pct,
|
|
1114
|
-
color:
|
|
1115
|
-
desc: '
|
|
957
|
+
color: AppColors.emerald500,
|
|
958
|
+
desc: t('bundle.catJsonDesc'),
|
|
1116
959
|
},
|
|
1117
960
|
].map((cat, cIdx) => (<View key={cIdx} style={bundleStyles.catRowCard}>
|
|
1118
961
|
<View style={bundleStyles.catRowTop}>
|
|
1119
|
-
<
|
|
962
|
+
<BundleCategoryIcon type={cat.iconType} size={18} color={cat.color}/>
|
|
1120
963
|
<View style={{ flex: 1 }}>
|
|
1121
964
|
<Text style={bundleStyles.catRowTitle}>{cat.title}</Text>
|
|
1122
|
-
<Text style={bundleStyles.catRowDesc}>
|
|
965
|
+
<Text style={bundleStyles.catRowDesc}>
|
|
966
|
+
{t('bundle.catRowSub', { count: cat.count, desc: cat.desc })}
|
|
967
|
+
</Text>
|
|
1123
968
|
</View>
|
|
1124
969
|
<View style={{ alignItems: 'flex-end' }}>
|
|
1125
970
|
<Text style={bundleStyles.catRowSize}>{cat.size}</Text>
|
|
@@ -1139,14 +984,24 @@ const BundleTab = React.memo(() => {
|
|
|
1139
984
|
<View style={bundleStyles.totalSummaryRow}>
|
|
1140
985
|
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
|
|
1141
986
|
<PackageIcon color={AppColors.purple} size={16}/>
|
|
1142
|
-
<Text style={bundleStyles.totalSummaryLabel}>
|
|
987
|
+
<Text style={bundleStyles.totalSummaryLabel}>
|
|
988
|
+
{t('bundle.totalBundleAssets')}
|
|
989
|
+
</Text>
|
|
1143
990
|
</View>
|
|
1144
991
|
<Text style={bundleStyles.totalSummaryValue}>
|
|
1145
|
-
{summary.totalKb
|
|
992
|
+
{t('bundle.totalBundleValue', { kb: summary.totalKb, mb: summary.totalMb })}
|
|
1146
993
|
</Text>
|
|
1147
994
|
</View>
|
|
1148
995
|
<Text style={bundleStyles.totalSummaryFormula}>
|
|
1149
|
-
|
|
996
|
+
{t('bundle.totalFormula', {
|
|
997
|
+
images: summary.images.kb,
|
|
998
|
+
js: summary.js.kb,
|
|
999
|
+
fonts: summary.fonts.kb,
|
|
1000
|
+
ts: summary.ts.kb,
|
|
1001
|
+
json: summary.json.kb,
|
|
1002
|
+
total: summary.totalKb,
|
|
1003
|
+
count: summary.totalCount,
|
|
1004
|
+
})}
|
|
1150
1005
|
</Text>
|
|
1151
1006
|
</View>
|
|
1152
1007
|
</View>
|
|
@@ -1155,16 +1010,16 @@ const BundleTab = React.memo(() => {
|
|
|
1155
1010
|
{/* ══════════════════════════════════════════════════════════════════════ */}
|
|
1156
1011
|
{/* ── 2. TAB: FILES BREAKDOWN ─────────────────────────────────────────── */}
|
|
1157
1012
|
{/* ══════════════════════════════════════════════════════════════════════ */}
|
|
1158
|
-
{activeSubTab === 'files' && (<View style={{ flex: 1 }}>
|
|
1013
|
+
{activeSubTab === 'files' && !isAnalyzing && (<View style={{ flex: 1 }}>
|
|
1159
1014
|
{/* Search & Category Filter */}
|
|
1160
1015
|
<View style={bundleStyles.filterContainer}>
|
|
1161
1016
|
<View style={bundleStyles.searchRow}>
|
|
1162
1017
|
<SearchIcon color={AppColors.grayTextWeak} size={15}/>
|
|
1163
|
-
<TextInput placeholder=
|
|
1018
|
+
<TextInput placeholder={t('bundle.searchFilesPlaceholder')} placeholderTextColor={AppColors.grayTextWeak} value={search} onChangeText={setSearch} style={bundleStyles.searchInput} autoCorrect={false} autoCapitalize="none"/>
|
|
1164
1019
|
{search.length > 0 && (<Pressable onPress={() => setSearch('')} hitSlop={10}>
|
|
1165
1020
|
<ClearIcon color={AppColors.grayTextWeak} size={14}/>
|
|
1166
1021
|
</Pressable>)}
|
|
1167
|
-
<CopyButton value={() => filteredFiles} label=
|
|
1022
|
+
<CopyButton value={() => filteredFiles} label={t('bundle.filteredFilesJson')}/>
|
|
1168
1023
|
</View>
|
|
1169
1024
|
|
|
1170
1025
|
{/* Category Filter Chips */}
|
|
@@ -1172,24 +1027,24 @@ const BundleTab = React.memo(() => {
|
|
|
1172
1027
|
{CATEGORY_TABS.map(tab => {
|
|
1173
1028
|
const isActive = activeCategory === tab.key;
|
|
1174
1029
|
const count = tab.key === 'ALL'
|
|
1175
|
-
?
|
|
1030
|
+
? bundleFiles.length
|
|
1176
1031
|
: tab.key === 'UNUSED'
|
|
1177
|
-
?
|
|
1032
|
+
? bundleFiles.filter(f => f.isConsumed === false).length
|
|
1178
1033
|
: tab.key === 'CONSUMED'
|
|
1179
|
-
?
|
|
1180
|
-
:
|
|
1034
|
+
? bundleFiles.filter(f => f.isConsumed !== false).length
|
|
1035
|
+
: bundleFiles.filter(f => f.category === tab.key).length;
|
|
1181
1036
|
return (<TouchableScale key={tab.key} onPress={() => setActiveCategory(tab.key)} style={[
|
|
1182
1037
|
bundleStyles.catPill,
|
|
1183
1038
|
isActive && bundleStyles.catPillActive,
|
|
1184
|
-
tab.key === 'UNUSED' && isActive && { backgroundColor:
|
|
1185
|
-
tab.key === 'CONSUMED' && isActive && { backgroundColor:
|
|
1039
|
+
tab.key === 'UNUSED' && isActive && { backgroundColor: AppColors.red500, borderColor: AppColors.red600 },
|
|
1040
|
+
tab.key === 'CONSUMED' && isActive && { backgroundColor: AppColors.emerald600, borderColor: AppColors.emerald700 },
|
|
1186
1041
|
]}>
|
|
1187
|
-
<
|
|
1042
|
+
<BundleCategoryIcon type={tab.iconType} size={12} color={isActive ? AppColors.white : AppColors.grayTextStrong}/>
|
|
1188
1043
|
<Text style={[
|
|
1189
1044
|
bundleStyles.catPillText,
|
|
1190
1045
|
isActive && bundleStyles.catPillTextActive,
|
|
1191
1046
|
]}>
|
|
1192
|
-
{tab.
|
|
1047
|
+
{t(tab.labelKey)} ({count})
|
|
1193
1048
|
</Text>
|
|
1194
1049
|
</TouchableScale>);
|
|
1195
1050
|
})}
|
|
@@ -1210,7 +1065,7 @@ const BundleTab = React.memo(() => {
|
|
|
1210
1065
|
bundleStyles.viewModeBtnText,
|
|
1211
1066
|
filesViewMode === 'tree' && bundleStyles.viewModeBtnTextActive,
|
|
1212
1067
|
]}>
|
|
1213
|
-
|
|
1068
|
+
{t('bundle.treeView')}
|
|
1214
1069
|
</Text>
|
|
1215
1070
|
</TouchableScale>
|
|
1216
1071
|
<TouchableScale onPress={() => setFilesViewMode('list')} style={[
|
|
@@ -1221,21 +1076,24 @@ const BundleTab = React.memo(() => {
|
|
|
1221
1076
|
bundleStyles.viewModeBtnText,
|
|
1222
1077
|
filesViewMode === 'list' && bundleStyles.viewModeBtnTextActive,
|
|
1223
1078
|
]}>
|
|
1224
|
-
|
|
1079
|
+
{t('bundle.flatList')}
|
|
1225
1080
|
</Text>
|
|
1226
1081
|
</TouchableScale>
|
|
1227
1082
|
</View>
|
|
1228
1083
|
|
|
1229
1084
|
{filesViewMode === 'tree' ? (<View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
|
|
1230
1085
|
<TouchableScale onPress={expandAllFolders}>
|
|
1231
|
-
<Text style={bundleStyles.treeActionLink}>
|
|
1086
|
+
<Text style={bundleStyles.treeActionLink}>{t('bundle.expand')}</Text>
|
|
1232
1087
|
</TouchableScale>
|
|
1233
1088
|
<Text style={{ color: AppColors.grayTextWeak, fontSize: 11 }}>•</Text>
|
|
1234
1089
|
<TouchableScale onPress={collapseAllFolders}>
|
|
1235
|
-
<Text style={bundleStyles.treeActionLink}>
|
|
1090
|
+
<Text style={bundleStyles.treeActionLink}>{t('bundle.collapse')}</Text>
|
|
1236
1091
|
</TouchableScale>
|
|
1237
1092
|
</View>) : (<Text style={bundleStyles.listHeaderCount}>
|
|
1238
|
-
|
|
1093
|
+
{t('bundle.showingFilesOf', {
|
|
1094
|
+
shown: filteredFiles.length,
|
|
1095
|
+
total: bundleFiles.length,
|
|
1096
|
+
})}
|
|
1239
1097
|
</Text>)}
|
|
1240
1098
|
</View>
|
|
1241
1099
|
|
|
@@ -1260,9 +1118,9 @@ const BundleTab = React.memo(() => {
|
|
|
1260
1118
|
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6, flexWrap: 'wrap' }}>
|
|
1261
1119
|
<HighlightText text={file.name} search={search} style={bundleStyles.fileName} highlightStyle={bundleStyles.searchHighlight}/>
|
|
1262
1120
|
{isUnused ? (<View style={bundleStyles.unusedBadge}>
|
|
1263
|
-
<Text style={bundleStyles.unusedBadgeText}
|
|
1121
|
+
<Text style={bundleStyles.unusedBadgeText}>{t('bundle.notConsumed')}</Text>
|
|
1264
1122
|
</View>) : (<View style={bundleStyles.consumedBadge}>
|
|
1265
|
-
<Text style={bundleStyles.consumedBadgeText}
|
|
1123
|
+
<Text style={bundleStyles.consumedBadgeText}>{t('bundle.consumed')}</Text>
|
|
1266
1124
|
</View>)}
|
|
1267
1125
|
</View>
|
|
1268
1126
|
<HighlightText text={file.path} search={search} style={bundleStyles.filePath} highlightStyle={bundleStyles.searchHighlight} numberOfLines={1}/>
|
|
@@ -1270,14 +1128,14 @@ const BundleTab = React.memo(() => {
|
|
|
1270
1128
|
|
|
1271
1129
|
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
|
|
1272
1130
|
<View style={bundleStyles.fileSizeBox}>
|
|
1273
|
-
<Text style={[bundleStyles.fileSizeKb, isUnused && { color:
|
|
1131
|
+
<Text style={[bundleStyles.fileSizeKb, isUnused && { color: AppColors.amber800Warm }]}>
|
|
1274
1132
|
{file.sizeKb >= 1024
|
|
1275
|
-
?
|
|
1276
|
-
:
|
|
1133
|
+
? t('bundle.fileSizeMb', { size: (file.sizeKb / 1024).toFixed(2) })
|
|
1134
|
+
: t('bundle.fileSizeKb', { size: file.sizeKb })}
|
|
1277
1135
|
</Text>
|
|
1278
1136
|
<Text style={bundleStyles.filePercent}>{pctOfTotal}%</Text>
|
|
1279
1137
|
</View>
|
|
1280
|
-
<CopyButton value={() => file} label=
|
|
1138
|
+
<CopyButton value={() => file} label={t('bundle.fileDetailsJson')}/>
|
|
1281
1139
|
</View>
|
|
1282
1140
|
</View>
|
|
1283
1141
|
|
|
@@ -1286,7 +1144,7 @@ const BundleTab = React.memo(() => {
|
|
|
1286
1144
|
bundleStyles.fileProgressBar,
|
|
1287
1145
|
{
|
|
1288
1146
|
width: `${Math.min(100, Math.max(4, Number(pctOfTotal) * 4))}%`,
|
|
1289
|
-
backgroundColor: isUnused ?
|
|
1147
|
+
backgroundColor: isUnused ? AppColors.red500 : categoryMeta.color,
|
|
1290
1148
|
},
|
|
1291
1149
|
]}/>
|
|
1292
1150
|
</View>
|
|
@@ -1299,15 +1157,17 @@ const BundleTab = React.memo(() => {
|
|
|
1299
1157
|
file.status === 'optimal' && bundleStyles.adviceOptimal,
|
|
1300
1158
|
isUnused && bundleStyles.adviceUnused,
|
|
1301
1159
|
]}>
|
|
1160
|
+
<View style={{ flexDirection: 'row', alignItems: 'flex-start', gap: 5 }}>
|
|
1161
|
+
{isUnused ? (<TrashIcon color={AppColors.red600} size={12}/>) : file.status === 'warning' ? (<LightbulbIcon color={AppColors.amber800Warm} size={12}/>) : (<SparkleIcon color={AppColors.emerald700} size={12}/>)}
|
|
1302
1162
|
<Text style={[
|
|
1303
1163
|
bundleStyles.adviceText,
|
|
1304
|
-
file.status === 'warning' && { color:
|
|
1305
|
-
file.status === 'optimal' && { color:
|
|
1306
|
-
isUnused && { color:
|
|
1164
|
+
file.status === 'warning' && { color: AppColors.amber800Warm },
|
|
1165
|
+
file.status === 'optimal' && { color: AppColors.emerald700 },
|
|
1166
|
+
isUnused && { color: AppColors.red600 },
|
|
1307
1167
|
]}>
|
|
1308
|
-
{isUnused ? '🗑️ ' : file.status === 'warning' ? '💡 ' : '✨ '}
|
|
1309
1168
|
{file.advice}
|
|
1310
1169
|
</Text>
|
|
1170
|
+
</View>
|
|
1311
1171
|
</View>)}
|
|
1312
1172
|
</View>
|
|
1313
1173
|
</View>);
|
|
@@ -1319,11 +1179,17 @@ const BundleTab = React.memo(() => {
|
|
|
1319
1179
|
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
|
|
1320
1180
|
<LayersIcon color={AppColors.purple} size={14}/>
|
|
1321
1181
|
<Text style={bundleStyles.footerTitle}>
|
|
1322
|
-
{
|
|
1182
|
+
{t('bundle.filesOf', {
|
|
1183
|
+
count: filteredFiles.length,
|
|
1184
|
+
total: summary.totalCount,
|
|
1185
|
+
})}
|
|
1323
1186
|
</Text>
|
|
1324
1187
|
</View>
|
|
1325
1188
|
<Text style={bundleStyles.footerSizeVal}>
|
|
1326
|
-
{
|
|
1189
|
+
{t('bundle.kbMbValue', {
|
|
1190
|
+
kb: filteredTotalKb,
|
|
1191
|
+
mb: (filteredTotalKb / 1024).toFixed(2),
|
|
1192
|
+
})}
|
|
1327
1193
|
</Text>
|
|
1328
1194
|
</View>
|
|
1329
1195
|
</View>)}
|
|
@@ -1331,28 +1197,29 @@ const BundleTab = React.memo(() => {
|
|
|
1331
1197
|
{/* ══════════════════════════════════════════════════════════════════════ */}
|
|
1332
1198
|
{/* ── 3. TAB: PACKAGES & NODE_MODULES ─────────────────────────────────── */}
|
|
1333
1199
|
{/* ══════════════════════════════════════════════════════════════════════ */}
|
|
1334
|
-
{activeSubTab === 'packages' && (<View style={{ flex: 1 }}>
|
|
1200
|
+
{activeSubTab === 'packages' && !isAnalyzing && (<View style={{ flex: 1 }}>
|
|
1335
1201
|
<ScrollView style={{ flex: 1 }} contentContainerStyle={bundleStyles.contentContainer} keyboardShouldPersistTaps="handled">
|
|
1336
1202
|
|
|
1337
1203
|
<View style={bundleStyles.filterContainer}>
|
|
1338
1204
|
<View style={bundleStyles.searchRow}>
|
|
1339
1205
|
<SearchIcon color={AppColors.grayTextWeak} size={15}/>
|
|
1340
|
-
<TextInput placeholder=
|
|
1206
|
+
<TextInput placeholder={t('bundle.searchPackagesPlaceholder')} placeholderTextColor={AppColors.grayTextWeak} value={search} onChangeText={setSearch} style={bundleStyles.searchInput} autoCorrect={false} autoCapitalize="none"/>
|
|
1341
1207
|
{search.length > 0 && (<Pressable onPress={() => setSearch('')} hitSlop={10}>
|
|
1342
1208
|
<ClearIcon color={AppColors.grayTextWeak} size={14}/>
|
|
1343
1209
|
</Pressable>)}
|
|
1344
|
-
<CopyButton value={() => filteredPackages} label=
|
|
1210
|
+
<CopyButton value={() => filteredPackages} label={t('bundle.dependenciesJson')}/>
|
|
1345
1211
|
</View>
|
|
1346
1212
|
</View>
|
|
1347
1213
|
|
|
1348
1214
|
<Text style={bundleStyles.listHeaderCount}>
|
|
1349
|
-
|
|
1215
|
+
{t('bundle.showingDependencies', { count: filteredPackages.length })}
|
|
1350
1216
|
</Text>
|
|
1351
1217
|
|
|
1352
1218
|
{filteredPackages.map((pkg, index) => {
|
|
1353
|
-
const cleanInstalledVersion = pkg.version.replace(/^\^/, '');
|
|
1354
|
-
const hasUpdate = pkg.latestVersion &&
|
|
1355
|
-
|
|
1219
|
+
const cleanInstalledVersion = pkg.version ? pkg.version.replace(/^\^/, '') : '';
|
|
1220
|
+
const hasUpdate = !!pkg.latestVersion && !!cleanInstalledVersion &&
|
|
1221
|
+
pkg.latestVersion !== cleanInstalledVersion;
|
|
1222
|
+
return (<View key={pkg.id} style={[bundleStyles.fileCard, pkg.isDeprecated && { borderColor: AppColors.errorBorder, backgroundColor: '#FFFDFD' }]}>
|
|
1356
1223
|
{/* Header Row: #s.no + Logo + Package Name + Version Pill + Update/Deprecated Badges + Size & Copy */}
|
|
1357
1224
|
<View style={bundleStyles.fileCardTop}>
|
|
1358
1225
|
<View style={bundleStyles.sNoBadge}>
|
|
@@ -1366,26 +1233,40 @@ const BundleTab = React.memo(() => {
|
|
|
1366
1233
|
<HighlightText text={pkg.name} search={search} style={bundleStyles.fileName} highlightStyle={bundleStyles.searchHighlight}/>
|
|
1367
1234
|
<View style={bundleStyles.versionPill}>
|
|
1368
1235
|
<Text style={bundleStyles.versionPillText}>
|
|
1369
|
-
|
|
1236
|
+
{cleanInstalledVersion
|
|
1237
|
+
? t('bundle.versionPrefix', { version: cleanInstalledVersion })
|
|
1238
|
+
: t('bundle.bundled')}
|
|
1370
1239
|
</Text>
|
|
1371
1240
|
</View>
|
|
1372
1241
|
|
|
1373
1242
|
{pkg.isDeprecated ? (<View style={bundleStyles.deprecatedBadge}>
|
|
1374
|
-
<Text style={bundleStyles.deprecatedBadgeText}
|
|
1243
|
+
<Text style={bundleStyles.deprecatedBadgeText}>
|
|
1244
|
+
{t('bundle.deprecated')}
|
|
1245
|
+
</Text>
|
|
1375
1246
|
</View>) : hasUpdate ? (<View style={bundleStyles.updateBadge}>
|
|
1376
|
-
<Text style={bundleStyles.updateBadgeText}
|
|
1377
|
-
|
|
1378
|
-
|
|
1247
|
+
<Text style={bundleStyles.updateBadgeText}>
|
|
1248
|
+
{t('bundle.updateAvailable', { version: pkg.latestVersion })}
|
|
1249
|
+
</Text>
|
|
1250
|
+
</View>) : cleanInstalledVersion ? (<View style={[bundleStyles.updateBadge, { backgroundColor: AppColors.gray100, borderColor: AppColors.gray200 }]}>
|
|
1251
|
+
<Text style={[bundleStyles.updateBadgeText, { color: AppColors.gray600 }]}>
|
|
1252
|
+
{t('bundle.upToDate')}
|
|
1253
|
+
</Text>
|
|
1254
|
+
</View>) : (<View style={[bundleStyles.updateBadge, { backgroundColor: AppColors.gray100, borderColor: AppColors.gray200 }]}>
|
|
1255
|
+
<Text style={[bundleStyles.updateBadgeText, { color: AppColors.gray600 }]}>
|
|
1256
|
+
{t('bundle.bundledBadge')}
|
|
1257
|
+
</Text>
|
|
1379
1258
|
</View>)}
|
|
1380
1259
|
</View>
|
|
1381
1260
|
</View>
|
|
1382
1261
|
|
|
1383
1262
|
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
|
|
1384
1263
|
<View style={bundleStyles.fileSizeBox}>
|
|
1385
|
-
<Text style={bundleStyles.fileSizeKb}>
|
|
1264
|
+
<Text style={bundleStyles.fileSizeKb}>
|
|
1265
|
+
{t('bundle.fileSizeKb', { size: pkg.sizeKb })}
|
|
1266
|
+
</Text>
|
|
1386
1267
|
<Text style={bundleStyles.filePercent}>{pkg.percentage}%</Text>
|
|
1387
1268
|
</View>
|
|
1388
|
-
<CopyButton value={() => pkg} label=
|
|
1269
|
+
<CopyButton value={() => pkg} label={t('bundle.packageDetailsJson')}/>
|
|
1389
1270
|
</View>
|
|
1390
1271
|
</View>
|
|
1391
1272
|
|
|
@@ -1403,15 +1284,18 @@ const BundleTab = React.memo(() => {
|
|
|
1403
1284
|
|
|
1404
1285
|
{/* Deprecation Warning Banner if deprecated */}
|
|
1405
1286
|
{pkg.isDeprecated && pkg.deprecationReason && (<View style={bundleStyles.deprecationBox}>
|
|
1406
|
-
<
|
|
1407
|
-
|
|
1408
|
-
|
|
1287
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 5 }}>
|
|
1288
|
+
<CircleAlertIcon color={AppColors.errorColor} size={12}/>
|
|
1289
|
+
<Text style={bundleStyles.deprecationText}>
|
|
1290
|
+
{pkg.deprecationReason}
|
|
1291
|
+
</Text>
|
|
1292
|
+
</View>
|
|
1409
1293
|
</View>)}
|
|
1410
1294
|
|
|
1411
1295
|
<View style={bundleStyles.fileProgressTrack}>
|
|
1412
1296
|
<View style={[
|
|
1413
1297
|
bundleStyles.fileProgressBar,
|
|
1414
|
-
{ width: `${pkg.percentage * 2}%`, backgroundColor: pkg.isDeprecated ?
|
|
1298
|
+
{ width: `${pkg.percentage * 2}%`, backgroundColor: pkg.isDeprecated ? AppColors.red500 : pkg.color },
|
|
1415
1299
|
]}/>
|
|
1416
1300
|
</View>
|
|
1417
1301
|
|
|
@@ -1420,23 +1304,26 @@ const BundleTab = React.memo(() => {
|
|
|
1420
1304
|
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
|
|
1421
1305
|
<View style={bundleStyles.typeTag}>
|
|
1422
1306
|
<Text style={bundleStyles.typeTagText}>
|
|
1423
|
-
{pkg.type === 'direct' ? '
|
|
1307
|
+
{pkg.type === 'direct' ? t('bundle.direct') : t('bundle.transitive')}
|
|
1424
1308
|
</Text>
|
|
1425
1309
|
</View>
|
|
1426
|
-
{pkg.lastActive && (<
|
|
1427
|
-
|
|
1428
|
-
|
|
1310
|
+
{pkg.lastActive && (<View style={{ flexDirection: 'row', alignItems: 'center', gap: 4 }}>
|
|
1311
|
+
<ClockIcon color={AppColors.grayTextWeak} size={11}/>
|
|
1312
|
+
<Text style={bundleStyles.lastActiveText}>
|
|
1313
|
+
{pkg.lastActive}
|
|
1314
|
+
</Text>
|
|
1315
|
+
</View>)}
|
|
1429
1316
|
</View>
|
|
1430
1317
|
|
|
1431
1318
|
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
|
|
1432
1319
|
<Text style={bundleStyles.fileMetaText}>
|
|
1433
|
-
|
|
1320
|
+
{t('bundle.minified', { size: pkg.sizeKb })}
|
|
1434
1321
|
</Text>
|
|
1435
1322
|
<TouchableScale onPress={() => {
|
|
1436
1323
|
const url = pkg.npmUrl || `https://www.npmjs.com/package/${pkg.name}`;
|
|
1437
1324
|
Linking.openURL(url).catch(() => { });
|
|
1438
1325
|
}} style={bundleStyles.npmLinkBtn}>
|
|
1439
|
-
<Text style={bundleStyles.npmLinkText}>
|
|
1326
|
+
<Text style={bundleStyles.npmLinkText}>{t('bundle.npmLink')}</Text>
|
|
1440
1327
|
</TouchableScale>
|
|
1441
1328
|
</View>
|
|
1442
1329
|
</View>
|
|
@@ -1449,11 +1336,14 @@ const BundleTab = React.memo(() => {
|
|
|
1449
1336
|
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
|
|
1450
1337
|
<PackageIcon color={AppColors.purple} size={14}/>
|
|
1451
1338
|
<Text style={bundleStyles.footerTitle}>
|
|
1452
|
-
{filteredPackages.length}
|
|
1339
|
+
{t('bundle.dependenciesCount', { count: filteredPackages.length })}
|
|
1453
1340
|
</Text>
|
|
1454
1341
|
</View>
|
|
1455
1342
|
<Text style={bundleStyles.footerSizeVal}>
|
|
1456
|
-
{
|
|
1343
|
+
{t('bundle.kbMbValue', {
|
|
1344
|
+
kb: packagesTotalKb,
|
|
1345
|
+
mb: (packagesTotalKb / 1024).toFixed(2),
|
|
1346
|
+
})}
|
|
1457
1347
|
</Text>
|
|
1458
1348
|
</View>
|
|
1459
1349
|
</View>)}
|
|
@@ -1461,24 +1351,32 @@ const BundleTab = React.memo(() => {
|
|
|
1461
1351
|
{/* ══════════════════════════════════════════════════════════════════════ */}
|
|
1462
1352
|
{/* ── 4. TAB: MEDIA & ASSET AUDITOR ───────────────────────────────────── */}
|
|
1463
1353
|
{/* ══════════════════════════════════════════════════════════════════════ */}
|
|
1464
|
-
{activeSubTab === 'media' && (<View style={{ flex: 1 }}>
|
|
1354
|
+
{activeSubTab === 'media' && !isAnalyzing && (<View style={{ flex: 1 }}>
|
|
1465
1355
|
<ScrollView style={{ flex: 1 }} contentContainerStyle={bundleStyles.contentContainer} keyboardShouldPersistTaps="handled">
|
|
1466
1356
|
|
|
1467
1357
|
<View style={bundleStyles.tipsCard}>
|
|
1468
1358
|
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8 }}>
|
|
1469
1359
|
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
|
|
1470
1360
|
<StorageIcon color={AppColors.purple} size={18}/>
|
|
1471
|
-
<Text style={bundleStyles.tipsHeading}>
|
|
1361
|
+
<Text style={bundleStyles.tipsHeading}>{t('bundle.mediaAuditorTitle')}</Text>
|
|
1472
1362
|
</View>
|
|
1473
|
-
<CopyButton value={() => mediaFiles} label=
|
|
1363
|
+
<CopyButton value={() => mediaFiles} label={t('bundle.mediaAssetsJson')}/>
|
|
1474
1364
|
</View>
|
|
1475
1365
|
<Text style={bundleStyles.tipDesc}>
|
|
1476
|
-
|
|
1366
|
+
{t('bundle.mediaAuditorPrefix')}{' '}
|
|
1367
|
+
<Text style={{ fontFamily: AppFonts.interBold, color: AppColors.purple }}>
|
|
1368
|
+
{summary.images.pct + summary.fonts.pct}%
|
|
1369
|
+
</Text>{' '}
|
|
1370
|
+
{t('bundle.mediaAuditorMid', { kb: summary.images.kb + summary.fonts.kb })}{' '}
|
|
1371
|
+
<Text style={{ fontFamily: AppFonts.interBold, color: AppColors.emerald700 }}>
|
|
1372
|
+
{t('bundle.mediaSavings', { size: 540 })}
|
|
1373
|
+
</Text>{' '}
|
|
1374
|
+
{t('bundle.mediaAuditorSuffix')}
|
|
1477
1375
|
</Text>
|
|
1478
1376
|
</View>
|
|
1479
1377
|
|
|
1480
1378
|
<Text style={bundleStyles.listHeaderCount}>
|
|
1481
|
-
|
|
1379
|
+
{t('bundle.mediaAssetsList', { count: mediaFiles.length })}
|
|
1482
1380
|
</Text>
|
|
1483
1381
|
|
|
1484
1382
|
{mediaFiles.map((file, index) => (<View key={file.id} style={bundleStyles.fileCard}>
|
|
@@ -1496,9 +1394,11 @@ const BundleTab = React.memo(() => {
|
|
|
1496
1394
|
|
|
1497
1395
|
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
|
|
1498
1396
|
<View style={bundleStyles.fileSizeBox}>
|
|
1499
|
-
<Text style={bundleStyles.fileSizeKb}>
|
|
1397
|
+
<Text style={bundleStyles.fileSizeKb}>
|
|
1398
|
+
{t('bundle.fileSizeKb', { size: file.sizeKb })}
|
|
1399
|
+
</Text>
|
|
1500
1400
|
</View>
|
|
1501
|
-
<CopyButton value={() => file} label=
|
|
1401
|
+
<CopyButton value={() => file} label={t('bundle.mediaItemJson')}/>
|
|
1502
1402
|
</View>
|
|
1503
1403
|
</View>
|
|
1504
1404
|
|
|
@@ -1509,14 +1409,16 @@ const BundleTab = React.memo(() => {
|
|
|
1509
1409
|
file.status === 'warning' && bundleStyles.adviceWarning,
|
|
1510
1410
|
file.status === 'optimal' && bundleStyles.adviceOptimal,
|
|
1511
1411
|
]}>
|
|
1512
|
-
<
|
|
1412
|
+
<View style={{ flexDirection: 'row', alignItems: 'flex-start', gap: 5 }}>
|
|
1413
|
+
{file.status === 'warning' ? (<LightbulbIcon color={AppColors.amber800Warm} size={12}/>) : (<SparkleIcon color={AppColors.emerald700} size={12}/>)}
|
|
1414
|
+
<Text style={[
|
|
1513
1415
|
bundleStyles.adviceText,
|
|
1514
|
-
file.status === 'warning' && { color:
|
|
1515
|
-
file.status === 'optimal' && { color:
|
|
1416
|
+
file.status === 'warning' && { color: AppColors.amber800Warm },
|
|
1417
|
+
file.status === 'optimal' && { color: AppColors.emerald700 },
|
|
1516
1418
|
]}>
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
</
|
|
1419
|
+
{file.advice}
|
|
1420
|
+
</Text>
|
|
1421
|
+
</View>
|
|
1520
1422
|
</View>)}
|
|
1521
1423
|
</View>
|
|
1522
1424
|
</View>))}
|
|
@@ -1527,11 +1429,14 @@ const BundleTab = React.memo(() => {
|
|
|
1527
1429
|
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
|
|
1528
1430
|
<StorageIcon color={AppColors.purple} size={14}/>
|
|
1529
1431
|
<Text style={bundleStyles.footerTitle}>
|
|
1530
|
-
{mediaFiles.length}
|
|
1432
|
+
{t('bundle.mediaAndFonts', { count: mediaFiles.length })}
|
|
1531
1433
|
</Text>
|
|
1532
1434
|
</View>
|
|
1533
1435
|
<Text style={bundleStyles.footerSizeVal}>
|
|
1534
|
-
{
|
|
1436
|
+
{t('bundle.kbMbValue', {
|
|
1437
|
+
kb: mediaTotalKb,
|
|
1438
|
+
mb: (mediaTotalKb / 1024).toFixed(2),
|
|
1439
|
+
})}
|
|
1535
1440
|
</Text>
|
|
1536
1441
|
</View>
|
|
1537
1442
|
</View>)}
|
|
@@ -1545,49 +1450,59 @@ const BundleTab = React.memo(() => {
|
|
|
1545
1450
|
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: 12 }}>
|
|
1546
1451
|
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
|
|
1547
1452
|
<CircleAlertIcon color={AppColors.purple} size={18}/>
|
|
1548
|
-
<Text style={bundleStyles.tipsHeading}>
|
|
1453
|
+
<Text style={bundleStyles.tipsHeading}>{t('bundle.optimizerTitle')}</Text>
|
|
1549
1454
|
</View>
|
|
1550
1455
|
<CopyButton value={() => [
|
|
1551
|
-
'
|
|
1552
|
-
|
|
1553
|
-
'
|
|
1554
|
-
'
|
|
1555
|
-
'
|
|
1556
|
-
]} label=
|
|
1456
|
+
t('bundle.optTip1Title'),
|
|
1457
|
+
`${t('bundle.optTip2Title')} (${isHermes ? t('bundle.active') : t('bundle.actionRequired')})`,
|
|
1458
|
+
t('bundle.optTip3Title'),
|
|
1459
|
+
t('bundle.optTip4Title'),
|
|
1460
|
+
t('bundle.optTip5Title'),
|
|
1461
|
+
]} label={t('bundle.optimizationChecklist')}/>
|
|
1557
1462
|
</View>
|
|
1558
1463
|
|
|
1559
1464
|
{[
|
|
1560
1465
|
{
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1466
|
+
iconType: 'image',
|
|
1467
|
+
iconColor: AppColors.pink500,
|
|
1468
|
+
title: t('bundle.optTip1Title'),
|
|
1469
|
+
desc: t('bundle.optTip1Desc'),
|
|
1470
|
+
badge: t('bundle.highImpact'),
|
|
1471
|
+
badgeColor: AppColors.pink500,
|
|
1565
1472
|
},
|
|
1566
1473
|
{
|
|
1567
|
-
|
|
1474
|
+
iconType: 'bolt',
|
|
1475
|
+
iconColor: isHermes ? AppColors.emerald500 : AppColors.amber500,
|
|
1476
|
+
title: t('bundle.optTip2Title'),
|
|
1568
1477
|
desc: isHermes
|
|
1569
|
-
? '
|
|
1570
|
-
: '
|
|
1571
|
-
badge: isHermes ? '
|
|
1572
|
-
badgeColor: isHermes ?
|
|
1478
|
+
? t('bundle.optTip2DescActive')
|
|
1479
|
+
: t('bundle.optTip2DescInactive'),
|
|
1480
|
+
badge: isHermes ? t('bundle.active') : t('bundle.actionRequired'),
|
|
1481
|
+
badgeColor: isHermes ? AppColors.emerald500 : AppColors.amber500,
|
|
1573
1482
|
},
|
|
1574
1483
|
{
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1484
|
+
iconType: 'font',
|
|
1485
|
+
iconColor: AppColors.purple500,
|
|
1486
|
+
title: t('bundle.optTip3Title'),
|
|
1487
|
+
desc: t('bundle.optTip3Desc'),
|
|
1488
|
+
badge: t('bundle.mediumImpact'),
|
|
1489
|
+
badgeColor: AppColors.purple500,
|
|
1579
1490
|
},
|
|
1580
1491
|
{
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1492
|
+
iconType: 'source',
|
|
1493
|
+
iconColor: AppColors.sky500,
|
|
1494
|
+
title: t('bundle.optTip4Title'),
|
|
1495
|
+
desc: t('bundle.optTip4Desc'),
|
|
1496
|
+
badge: t('bundle.bestPractice'),
|
|
1497
|
+
badgeColor: AppColors.sky500,
|
|
1585
1498
|
},
|
|
1586
1499
|
{
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1500
|
+
iconType: 'overhead',
|
|
1501
|
+
iconColor: AppColors.indigo500,
|
|
1502
|
+
title: t('bundle.optTip5Title'),
|
|
1503
|
+
desc: t('bundle.optTip5Desc'),
|
|
1504
|
+
badge: t('bundle.bestPractice'),
|
|
1505
|
+
badgeColor: AppColors.indigo500,
|
|
1591
1506
|
},
|
|
1592
1507
|
].map((tip, tIdx) => (<View key={tIdx} style={bundleStyles.tipItem}>
|
|
1593
1508
|
<View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 2 }}>
|
|
@@ -1595,6 +1510,7 @@ const BundleTab = React.memo(() => {
|
|
|
1595
1510
|
<View style={bundleStyles.sNoBadge}>
|
|
1596
1511
|
<Text style={bundleStyles.sNoText}>#{tIdx + 1}</Text>
|
|
1597
1512
|
</View>
|
|
1513
|
+
<BundleCategoryIcon type={tip.iconType} size={14} color={tip.iconColor}/>
|
|
1598
1514
|
<Text style={bundleStyles.tipTitle}>{tip.title}</Text>
|
|
1599
1515
|
</View>
|
|
1600
1516
|
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
|
|
@@ -1603,7 +1519,7 @@ const BundleTab = React.memo(() => {
|
|
|
1603
1519
|
{tip.badge}
|
|
1604
1520
|
</Text>
|
|
1605
1521
|
</View>
|
|
1606
|
-
<CopyButton value={() => tip} label=
|
|
1522
|
+
<CopyButton value={() => tip} label={t('bundle.tipDetails')}/>
|
|
1607
1523
|
</View>
|
|
1608
1524
|
</View>
|
|
1609
1525
|
<Text style={bundleStyles.tipDesc}>{tip.desc}</Text>
|
|
@@ -1619,6 +1535,23 @@ const bundleStyles = StyleSheet.create({
|
|
|
1619
1535
|
borderBottomWidth: 1,
|
|
1620
1536
|
borderBottomColor: AppColors.dividerColor,
|
|
1621
1537
|
},
|
|
1538
|
+
analyzingContainer: {
|
|
1539
|
+
flex: 1,
|
|
1540
|
+
alignItems: 'center',
|
|
1541
|
+
justifyContent: 'center',
|
|
1542
|
+
gap: 8,
|
|
1543
|
+
padding: 24,
|
|
1544
|
+
},
|
|
1545
|
+
analyzingText: {
|
|
1546
|
+
fontFamily: AppFonts.interBold,
|
|
1547
|
+
fontSize: 13,
|
|
1548
|
+
color: AppColors.primaryBlack,
|
|
1549
|
+
},
|
|
1550
|
+
analyzingHint: {
|
|
1551
|
+
fontFamily: AppFonts.interRegular,
|
|
1552
|
+
fontSize: 11,
|
|
1553
|
+
color: AppColors.grayTextWeak,
|
|
1554
|
+
},
|
|
1622
1555
|
subTabsScroll: {
|
|
1623
1556
|
paddingHorizontal: 12,
|
|
1624
1557
|
gap: 8,
|
|
@@ -1777,11 +1710,6 @@ const bundleStyles = StyleSheet.create({
|
|
|
1777
1710
|
gap: 5,
|
|
1778
1711
|
minWidth: '45%',
|
|
1779
1712
|
},
|
|
1780
|
-
legendDot: {
|
|
1781
|
-
width: 8,
|
|
1782
|
-
height: 8,
|
|
1783
|
-
borderRadius: 4,
|
|
1784
|
-
},
|
|
1785
1713
|
legendText: {
|
|
1786
1714
|
fontFamily: AppFonts.interMedium,
|
|
1787
1715
|
fontSize: 11,
|
|
@@ -1805,9 +1733,6 @@ const bundleStyles = StyleSheet.create({
|
|
|
1805
1733
|
gap: 8,
|
|
1806
1734
|
marginBottom: 8,
|
|
1807
1735
|
},
|
|
1808
|
-
catRowIcon: {
|
|
1809
|
-
fontSize: 16,
|
|
1810
|
-
},
|
|
1811
1736
|
catRowTitle: {
|
|
1812
1737
|
fontFamily: AppFonts.interBold,
|
|
1813
1738
|
fontSize: 12,
|
|
@@ -1883,9 +1808,6 @@ const bundleStyles = StyleSheet.create({
|
|
|
1883
1808
|
backgroundColor: AppColors.brandPurple,
|
|
1884
1809
|
borderColor: AppColors.brandPurple,
|
|
1885
1810
|
},
|
|
1886
|
-
catPillIcon: {
|
|
1887
|
-
fontSize: 11,
|
|
1888
|
-
},
|
|
1889
1811
|
catPillText: {
|
|
1890
1812
|
fontFamily: AppFonts.interMedium,
|
|
1891
1813
|
fontSize: 11,
|
|
@@ -2174,41 +2096,41 @@ const bundleStyles = StyleSheet.create({
|
|
|
2174
2096
|
paddingHorizontal: 7,
|
|
2175
2097
|
paddingVertical: 2,
|
|
2176
2098
|
borderRadius: 5,
|
|
2177
|
-
backgroundColor:
|
|
2099
|
+
backgroundColor: AppColors.npmRedTint,
|
|
2178
2100
|
borderWidth: 1,
|
|
2179
|
-
borderColor:
|
|
2101
|
+
borderColor: AppColors.npmRedBorder,
|
|
2180
2102
|
},
|
|
2181
2103
|
npmLinkText: {
|
|
2182
2104
|
fontFamily: AppFonts.interBold,
|
|
2183
2105
|
fontSize: 9.5,
|
|
2184
|
-
color:
|
|
2106
|
+
color: AppColors.npmRed,
|
|
2185
2107
|
},
|
|
2186
2108
|
deprecatedBadge: {
|
|
2187
2109
|
paddingHorizontal: 6,
|
|
2188
2110
|
paddingVertical: 1.5,
|
|
2189
2111
|
borderRadius: 4,
|
|
2190
|
-
backgroundColor:
|
|
2112
|
+
backgroundColor: AppColors.red100,
|
|
2191
2113
|
borderWidth: 1,
|
|
2192
|
-
borderColor:
|
|
2114
|
+
borderColor: AppColors.errorBorder,
|
|
2193
2115
|
},
|
|
2194
2116
|
deprecatedBadgeText: {
|
|
2195
2117
|
fontFamily: AppFonts.interBold,
|
|
2196
2118
|
fontSize: 9,
|
|
2197
|
-
color:
|
|
2119
|
+
color: AppColors.red600,
|
|
2198
2120
|
letterSpacing: 0.3,
|
|
2199
2121
|
},
|
|
2200
2122
|
updateBadge: {
|
|
2201
2123
|
paddingHorizontal: 6,
|
|
2202
2124
|
paddingVertical: 1.5,
|
|
2203
2125
|
borderRadius: 4,
|
|
2204
|
-
backgroundColor:
|
|
2126
|
+
backgroundColor: AppColors.green100,
|
|
2205
2127
|
borderWidth: 1,
|
|
2206
|
-
borderColor:
|
|
2128
|
+
borderColor: AppColors.greenBorder,
|
|
2207
2129
|
},
|
|
2208
2130
|
updateBadgeText: {
|
|
2209
2131
|
fontFamily: AppFonts.interBold,
|
|
2210
2132
|
fontSize: 9,
|
|
2211
|
-
color:
|
|
2133
|
+
color: AppColors.emerald700,
|
|
2212
2134
|
},
|
|
2213
2135
|
lastActiveText: {
|
|
2214
2136
|
fontFamily: AppFonts.interRegular,
|
|
@@ -2216,45 +2138,45 @@ const bundleStyles = StyleSheet.create({
|
|
|
2216
2138
|
color: AppColors.grayTextWeak,
|
|
2217
2139
|
},
|
|
2218
2140
|
deprecationBox: {
|
|
2219
|
-
backgroundColor:
|
|
2141
|
+
backgroundColor: AppColors.errorCardBg,
|
|
2220
2142
|
borderRadius: 6,
|
|
2221
2143
|
paddingHorizontal: 8,
|
|
2222
2144
|
paddingVertical: 5,
|
|
2223
2145
|
borderWidth: 1,
|
|
2224
|
-
borderColor:
|
|
2146
|
+
borderColor: AppColors.errorBorder,
|
|
2225
2147
|
marginTop: 3,
|
|
2226
2148
|
},
|
|
2227
2149
|
deprecationText: {
|
|
2228
2150
|
fontFamily: AppFonts.interMedium,
|
|
2229
2151
|
fontSize: 10.5,
|
|
2230
|
-
color:
|
|
2152
|
+
color: AppColors.rose700,
|
|
2231
2153
|
lineHeight: 14,
|
|
2232
2154
|
},
|
|
2233
2155
|
unusedBadge: {
|
|
2234
2156
|
paddingHorizontal: 6,
|
|
2235
2157
|
paddingVertical: 1.5,
|
|
2236
2158
|
borderRadius: 4,
|
|
2237
|
-
backgroundColor:
|
|
2159
|
+
backgroundColor: AppColors.red100,
|
|
2238
2160
|
borderWidth: 1,
|
|
2239
|
-
borderColor:
|
|
2161
|
+
borderColor: AppColors.errorBorder,
|
|
2240
2162
|
},
|
|
2241
2163
|
unusedBadgeText: {
|
|
2242
2164
|
fontFamily: AppFonts.interBold,
|
|
2243
2165
|
fontSize: 9.5,
|
|
2244
|
-
color:
|
|
2166
|
+
color: AppColors.red600,
|
|
2245
2167
|
},
|
|
2246
2168
|
consumedBadge: {
|
|
2247
2169
|
paddingHorizontal: 6,
|
|
2248
2170
|
paddingVertical: 1.5,
|
|
2249
2171
|
borderRadius: 4,
|
|
2250
|
-
backgroundColor:
|
|
2172
|
+
backgroundColor: AppColors.green100,
|
|
2251
2173
|
borderWidth: 1,
|
|
2252
|
-
borderColor:
|
|
2174
|
+
borderColor: AppColors.greenBorder,
|
|
2253
2175
|
},
|
|
2254
2176
|
consumedBadgeText: {
|
|
2255
2177
|
fontFamily: AppFonts.interBold,
|
|
2256
2178
|
fontSize: 9.5,
|
|
2257
|
-
color:
|
|
2179
|
+
color: AppColors.emerald600,
|
|
2258
2180
|
},
|
|
2259
2181
|
treeFolderChevronWrap: {
|
|
2260
2182
|
width: 18,
|
|
@@ -2265,13 +2187,13 @@ const bundleStyles = StyleSheet.create({
|
|
|
2265
2187
|
justifyContent: 'center',
|
|
2266
2188
|
},
|
|
2267
2189
|
treeFileCardUnused: {
|
|
2268
|
-
borderColor:
|
|
2269
|
-
backgroundColor:
|
|
2190
|
+
borderColor: AppColors.errorBorder,
|
|
2191
|
+
backgroundColor: AppColors.red50,
|
|
2270
2192
|
},
|
|
2271
2193
|
adviceUnused: {
|
|
2272
|
-
backgroundColor:
|
|
2194
|
+
backgroundColor: AppColors.errorCardBg,
|
|
2273
2195
|
borderWidth: 1,
|
|
2274
|
-
borderColor:
|
|
2196
|
+
borderColor: AppColors.errorBorder,
|
|
2275
2197
|
},
|
|
2276
2198
|
adviceBadge: {
|
|
2277
2199
|
paddingHorizontal: 8,
|
|
@@ -2280,14 +2202,14 @@ const bundleStyles = StyleSheet.create({
|
|
|
2280
2202
|
marginTop: 2,
|
|
2281
2203
|
},
|
|
2282
2204
|
adviceWarning: {
|
|
2283
|
-
backgroundColor:
|
|
2205
|
+
backgroundColor: AppColors.amber100,
|
|
2284
2206
|
borderWidth: 1,
|
|
2285
|
-
borderColor:
|
|
2207
|
+
borderColor: AppColors.amber200,
|
|
2286
2208
|
},
|
|
2287
2209
|
adviceOptimal: {
|
|
2288
|
-
backgroundColor:
|
|
2210
|
+
backgroundColor: AppColors.green100,
|
|
2289
2211
|
borderWidth: 1,
|
|
2290
|
-
borderColor:
|
|
2212
|
+
borderColor: AppColors.greenBorder,
|
|
2291
2213
|
},
|
|
2292
2214
|
adviceText: {
|
|
2293
2215
|
fontFamily: AppFonts.interMedium,
|
|
@@ -2305,21 +2227,21 @@ const bundleStyles = StyleSheet.create({
|
|
|
2305
2227
|
fontSize: 9,
|
|
2306
2228
|
},
|
|
2307
2229
|
searchHighlight: {
|
|
2308
|
-
backgroundColor:
|
|
2309
|
-
color:
|
|
2230
|
+
backgroundColor: AppColors.yellow200,
|
|
2231
|
+
color: AppColors.yellow800,
|
|
2310
2232
|
fontFamily: AppFonts.interBold,
|
|
2311
2233
|
},
|
|
2312
2234
|
tipsCard: {
|
|
2313
|
-
backgroundColor:
|
|
2235
|
+
backgroundColor: AppColors.purple50,
|
|
2314
2236
|
borderRadius: 14,
|
|
2315
2237
|
padding: 14,
|
|
2316
2238
|
borderWidth: 1,
|
|
2317
|
-
borderColor:
|
|
2239
|
+
borderColor: AppColors.purple200,
|
|
2318
2240
|
},
|
|
2319
2241
|
tipsHeading: {
|
|
2320
2242
|
fontFamily: AppFonts.interBold,
|
|
2321
2243
|
fontSize: 13.5,
|
|
2322
|
-
color:
|
|
2244
|
+
color: AppColors.purple800,
|
|
2323
2245
|
},
|
|
2324
2246
|
totalSummaryCard: {
|
|
2325
2247
|
marginTop: 10,
|
|
@@ -2383,17 +2305,17 @@ const bundleStyles = StyleSheet.create({
|
|
|
2383
2305
|
padding: 10,
|
|
2384
2306
|
borderRadius: 8,
|
|
2385
2307
|
borderWidth: 1,
|
|
2386
|
-
borderColor:
|
|
2308
|
+
borderColor: AppColors.purple200,
|
|
2387
2309
|
},
|
|
2388
2310
|
tipTitle: {
|
|
2389
2311
|
fontFamily: AppFonts.interBold,
|
|
2390
2312
|
fontSize: 12,
|
|
2391
|
-
color:
|
|
2313
|
+
color: AppColors.purple900,
|
|
2392
2314
|
},
|
|
2393
2315
|
tipDesc: {
|
|
2394
2316
|
fontFamily: AppFonts.interRegular,
|
|
2395
2317
|
fontSize: 11,
|
|
2396
|
-
color:
|
|
2318
|
+
color: AppColors.gray500,
|
|
2397
2319
|
lineHeight: 16,
|
|
2398
2320
|
marginTop: 4,
|
|
2399
2321
|
},
|