react-native-inapp-inspector 2.3.8 → 2.3.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/dist/commonjs/components/Inspector/AnalyticsTab.js +27 -2
  2. package/dist/commonjs/components/Inspector/BundleTab.js +3 -0
  3. package/dist/commonjs/components/Inspector/ConsoleTab.js +24 -1
  4. package/dist/commonjs/components/Inspector/CrashTab.js +30 -1
  5. package/dist/commonjs/components/Inspector/FeatureUnderDevNotice.d.ts +7 -0
  6. package/dist/commonjs/components/Inspector/FeatureUnderDevNotice.js +111 -0
  7. package/dist/commonjs/components/Inspector/InspectorHeader.js +225 -108
  8. package/dist/commonjs/components/Inspector/NetworkDetail.js +2 -1
  9. package/dist/commonjs/components/Inspector/NetworkFilterModal.d.ts +19 -0
  10. package/dist/commonjs/components/Inspector/NetworkFilterModal.js +648 -0
  11. package/dist/commonjs/components/Inspector/NetworkTab.js +89 -147
  12. package/dist/commonjs/components/Inspector/NpmUpdateToast.js +1 -1
  13. package/dist/commonjs/components/Inspector/PerformanceTab.js +3 -0
  14. package/dist/commonjs/components/Inspector/ReduxTab.js +24 -1
  15. package/dist/commonjs/components/Inspector/SettingsPanel.js +73 -88
  16. package/dist/commonjs/components/Inspector/StorageTab.js +37 -11
  17. package/dist/commonjs/components/Inspector/UpdateAvailableModal.js +2 -0
  18. package/dist/commonjs/components/LogCard.js +35 -26
  19. package/dist/commonjs/components/NetworkIcons.d.ts +1 -0
  20. package/dist/commonjs/components/NetworkIcons.js +6 -1
  21. package/dist/commonjs/constants/index.js +3 -0
  22. package/dist/commonjs/constants/version.d.ts +1 -1
  23. package/dist/commonjs/constants/version.js +1 -1
  24. package/dist/commonjs/helpers/telemetry.js +34 -0
  25. package/dist/commonjs/index.js +3 -0
  26. package/dist/commonjs/styles/index.d.ts +11 -0
  27. package/dist/commonjs/styles/index.js +20 -5
  28. package/dist/commonjs/types/enums.d.ts +3 -0
  29. package/dist/commonjs/types/enums.js +3 -0
  30. package/dist/esm/components/Inspector/AnalyticsTab.js +29 -4
  31. package/dist/esm/components/Inspector/BundleTab.js +3 -0
  32. package/dist/esm/components/Inspector/ConsoleTab.js +26 -3
  33. package/dist/esm/components/Inspector/CrashTab.js +32 -3
  34. package/dist/esm/components/Inspector/FeatureUnderDevNotice.d.ts +7 -0
  35. package/dist/esm/components/Inspector/FeatureUnderDevNotice.js +74 -0
  36. package/dist/esm/components/Inspector/InspectorHeader.js +226 -109
  37. package/dist/esm/components/Inspector/NetworkDetail.js +2 -1
  38. package/dist/esm/components/Inspector/NetworkFilterModal.d.ts +19 -0
  39. package/dist/esm/components/Inspector/NetworkFilterModal.js +607 -0
  40. package/dist/esm/components/Inspector/NetworkTab.js +91 -149
  41. package/dist/esm/components/Inspector/NpmUpdateToast.js +1 -1
  42. package/dist/esm/components/Inspector/PerformanceTab.js +3 -0
  43. package/dist/esm/components/Inspector/ReduxTab.js +26 -3
  44. package/dist/esm/components/Inspector/SettingsPanel.js +73 -88
  45. package/dist/esm/components/Inspector/StorageTab.js +39 -13
  46. package/dist/esm/components/Inspector/UpdateAvailableModal.js +2 -0
  47. package/dist/esm/components/LogCard.js +36 -27
  48. package/dist/esm/components/NetworkIcons.d.ts +1 -0
  49. package/dist/esm/components/NetworkIcons.js +4 -0
  50. package/dist/esm/constants/index.js +3 -0
  51. package/dist/esm/constants/version.d.ts +1 -1
  52. package/dist/esm/constants/version.js +1 -1
  53. package/dist/esm/helpers/telemetry.js +34 -0
  54. package/dist/esm/index.js +3 -0
  55. package/dist/esm/styles/index.d.ts +11 -0
  56. package/dist/esm/styles/index.js +20 -5
  57. package/dist/esm/types/enums.d.ts +3 -0
  58. package/dist/esm/types/enums.js +3 -0
  59. package/package.json +2 -2
  60. package/src/components/Inspector/AnalyticsTab.tsx +71 -43
  61. package/src/components/Inspector/BundleTab.tsx +3 -0
  62. package/src/components/Inspector/ConsoleTab.tsx +28 -1
  63. package/src/components/Inspector/CrashTab.tsx +45 -14
  64. package/src/components/Inspector/FeatureUnderDevNotice.tsx +94 -0
  65. package/src/components/Inspector/InspectorHeader.tsx +257 -115
  66. package/src/components/Inspector/NetworkDetail.tsx +2 -1
  67. package/src/components/Inspector/NetworkFilterModal.tsx +710 -0
  68. package/src/components/Inspector/NetworkTab.tsx +127 -204
  69. package/src/components/Inspector/NpmUpdateToast.tsx +1 -1
  70. package/src/components/Inspector/PerformanceTab.tsx +3 -0
  71. package/src/components/Inspector/ReduxTab.tsx +28 -1
  72. package/src/components/Inspector/SettingsPanel.tsx +93 -132
  73. package/src/components/Inspector/StorageTab.tsx +56 -27
  74. package/src/components/Inspector/UpdateAvailableModal.tsx +2 -0
  75. package/src/components/LogCard.tsx +43 -31
  76. package/src/components/NetworkIcons.tsx +27 -0
  77. package/src/constants/index.ts +3 -0
  78. package/src/constants/version.ts +1 -1
  79. package/src/helpers/telemetry.ts +36 -0
  80. package/src/index.tsx +5 -0
  81. package/src/styles/index.ts +20 -5
  82. package/src/types/enums.ts +3 -0
@@ -0,0 +1,710 @@
1
+ import React, {useEffect, useMemo, useState} from 'react';
2
+ import {
3
+ Modal,
4
+ Pressable,
5
+ ScrollView,
6
+ StyleSheet,
7
+ Text,
8
+ TouchableOpacity,
9
+ View,
10
+ } from 'react-native';
11
+ import {useInspector} from './InspectorContext';
12
+ import {Method} from '../../types';
13
+ import {AppColors} from '../../styles/AppColors';
14
+ import {AppFonts} from '../../styles/AppFonts';
15
+ import {useTranslation} from '../../i18n';
16
+ import {METHOD_COLORS} from '../../constants';
17
+ import {
18
+ FilterIcon,
19
+ CloseWhite,
20
+ ClockIcon,
21
+ CircleCheckIcon,
22
+ CircleAlertIcon,
23
+ CircleXIcon,
24
+ LayersIcon,
25
+ RepeatIcon,
26
+ ShieldAlertIcon,
27
+ BoltIcon,
28
+ GlobeIcon,
29
+ LockIcon,
30
+ } from '../NetworkIcons';
31
+ import TouchableScale from '../TouchableScale';
32
+
33
+ export interface NetworkFilterState {
34
+ statusCodes: Set<string>;
35
+ methods: Set<string>;
36
+ latency: 'all' | 'fast' | 'normal' | 'slow';
37
+ protocol: 'all' | 'https' | 'http';
38
+ sortBy: 'time_desc' | 'time_asc' | 'duration_desc' | 'duration_asc' | 'size_desc';
39
+ }
40
+
41
+ export const DEFAULT_NETWORK_FILTERS: NetworkFilterState = {
42
+ statusCodes: new Set(['all']),
43
+ methods: new Set(['all']),
44
+ latency: 'all',
45
+ protocol: 'all',
46
+ sortBy: 'time_desc',
47
+ };
48
+
49
+ export const isNetworkFiltersDefault = (f: NetworkFilterState): boolean =>
50
+ (f.statusCodes.has('all') || f.statusCodes.size === 0) &&
51
+ (f.methods.has('all') || f.methods.size === 0) &&
52
+ f.latency === 'all' &&
53
+ f.protocol === 'all' &&
54
+ f.sortBy === 'time_desc';
55
+
56
+ interface NetworkFilterModalProps {
57
+ visible: boolean;
58
+ onClose: () => void;
59
+ filters: NetworkFilterState;
60
+ onApply: (filters: NetworkFilterState) => void;
61
+ searchQuery?: string;
62
+ }
63
+
64
+ export const NetworkFilterModal: React.FC<NetworkFilterModalProps> = ({
65
+ visible,
66
+ onClose,
67
+ filters,
68
+ onApply,
69
+ searchQuery = '',
70
+ }) => {
71
+ const {t} = useTranslation();
72
+ const {logs} = useInspector();
73
+
74
+ const [draft, setDraft] = useState<NetworkFilterState>(() => ({
75
+ ...filters,
76
+ statusCodes: new Set(filters.statusCodes),
77
+ methods: new Set(filters.methods),
78
+ }));
79
+
80
+ useEffect(() => {
81
+ if (visible) {
82
+ setDraft({
83
+ ...filters,
84
+ statusCodes: new Set(filters.statusCodes),
85
+ methods: new Set(filters.methods),
86
+ });
87
+ }
88
+ }, [visible, filters]);
89
+
90
+ // Live count calculators
91
+ const counts = useMemo(() => {
92
+ const statusMap: Record<string, number> = {
93
+ all: logs.length,
94
+ '2xx': 0,
95
+ '3xx': 0,
96
+ '4xx': 0,
97
+ '5xx': 0,
98
+ failed: 0,
99
+ };
100
+ const methodMap: Record<string, number> = {all: logs.length};
101
+ let fastCount = 0;
102
+ let normalCount = 0;
103
+ let slowCount = 0;
104
+ let httpsCount = 0;
105
+ let httpCount = 0;
106
+
107
+ for (const log of logs) {
108
+ const s = typeof log.status === 'number' ? log.status : parseInt(String(log.status), 10);
109
+ if (log.status === 0 || log.status == null) {
110
+ statusMap.failed = (statusMap.failed || 0) + 1;
111
+ } else if (!isNaN(s)) {
112
+ if (s >= 200 && s < 300) statusMap['2xx'] = (statusMap['2xx'] || 0) + 1;
113
+ else if (s >= 300 && s < 400) statusMap['3xx'] = (statusMap['3xx'] || 0) + 1;
114
+ else if (s >= 400 && s < 500) statusMap['4xx'] = (statusMap['4xx'] || 0) + 1;
115
+ else if (s >= 500) statusMap['5xx'] = (statusMap['5xx'] || 0) + 1;
116
+ }
117
+
118
+ const m = (log.method || 'GET').toUpperCase();
119
+ methodMap[m] = (methodMap[m] || 0) + 1;
120
+
121
+ const dur = log.duration || 0;
122
+ if (dur < 200) fastCount++;
123
+ else if (dur <= 500) normalCount++;
124
+ else slowCount++;
125
+
126
+ if ((log.url || '').toLowerCase().startsWith('https')) httpsCount++;
127
+ else httpCount++;
128
+ }
129
+
130
+ return {
131
+ statusMap,
132
+ methodMap,
133
+ fastCount,
134
+ normalCount,
135
+ slowCount,
136
+ httpsCount,
137
+ httpCount,
138
+ };
139
+ }, [logs]);
140
+
141
+ // Count matching results with current draft filters
142
+ const previewMatchCount = useMemo(() => {
143
+ return logs.filter(log => {
144
+ // Status filter
145
+ if (!draft.statusCodes.has('all') && draft.statusCodes.size > 0) {
146
+ const s = typeof log.status === 'number' ? log.status : parseInt(String(log.status), 10);
147
+ let matches = false;
148
+ if (draft.statusCodes.has('failed') && (log.status === 0 || log.status == null)) matches = true;
149
+ if (draft.statusCodes.has('2xx') && !isNaN(s) && s >= 200 && s < 300) matches = true;
150
+ if (draft.statusCodes.has('3xx') && !isNaN(s) && s >= 300 && s < 400) matches = true;
151
+ if (draft.statusCodes.has('4xx') && !isNaN(s) && s >= 400 && s < 500) matches = true;
152
+ if (draft.statusCodes.has('5xx') && !isNaN(s) && s >= 500) matches = true;
153
+ if (!matches) return false;
154
+ }
155
+
156
+ // Method filter
157
+ if (!draft.methods.has('all') && draft.methods.size > 0) {
158
+ const m = (log.method || 'GET').toUpperCase();
159
+ if (!draft.methods.has(m)) return false;
160
+ }
161
+
162
+ // Latency filter
163
+ const dur = log.duration || 0;
164
+ if (draft.latency === 'fast' && dur >= 200) return false;
165
+ if (draft.latency === 'normal' && (dur < 200 || dur > 500)) return false;
166
+ if (draft.latency === 'slow' && dur <= 500) return false;
167
+
168
+ // Protocol filter
169
+ const isHttps = (log.url || '').toLowerCase().startsWith('https');
170
+ if (draft.protocol === 'https' && !isHttps) return false;
171
+ if (draft.protocol === 'http' && isHttps) return false;
172
+
173
+ // Search query
174
+ if (searchQuery.trim().length > 0) {
175
+ const q = searchQuery.toLowerCase();
176
+ const u = (log.url || '').toLowerCase();
177
+ if (!u.includes(q)) return false;
178
+ }
179
+
180
+ return true;
181
+ }).length;
182
+ }, [logs, draft, searchQuery]);
183
+
184
+ const toggleStatus = (key: string) => {
185
+ setDraft(prev => {
186
+ const next = new Set(prev.statusCodes);
187
+ if (key === 'all') {
188
+ return {...prev, statusCodes: new Set(['all'])};
189
+ }
190
+ next.delete('all');
191
+ if (next.has(key)) {
192
+ next.delete(key);
193
+ if (next.size === 0) next.add('all');
194
+ } else {
195
+ next.add(key);
196
+ }
197
+ return {...prev, statusCodes: next};
198
+ });
199
+ };
200
+
201
+ const toggleMethod = (key: string) => {
202
+ setDraft(prev => {
203
+ const next = new Set(prev.methods);
204
+ if (key === 'all') {
205
+ return {...prev, methods: new Set(['all'])};
206
+ }
207
+ next.delete('all');
208
+ if (next.has(key)) {
209
+ next.delete(key);
210
+ if (next.size === 0) next.add('all');
211
+ } else {
212
+ next.add(key);
213
+ }
214
+ return {...prev, methods: next};
215
+ });
216
+ };
217
+
218
+ const handleReset = () => {
219
+ setDraft({
220
+ statusCodes: new Set(['all']),
221
+ methods: new Set(['all']),
222
+ latency: 'all',
223
+ protocol: 'all',
224
+ sortBy: 'time_desc',
225
+ });
226
+ };
227
+
228
+ const handleApply = () => {
229
+ onApply(draft);
230
+ onClose();
231
+ };
232
+
233
+ const activeFiltersCount = useMemo(() => {
234
+ let count = 0;
235
+ if (!draft.statusCodes.has('all') && draft.statusCodes.size > 0) count += draft.statusCodes.size;
236
+ if (!draft.methods.has('all') && draft.methods.size > 0) count += draft.methods.size;
237
+ if (draft.latency !== 'all') count++;
238
+ if (draft.protocol !== 'all') count++;
239
+ if (draft.sortBy !== 'time_desc') count++;
240
+ return count;
241
+ }, [draft]);
242
+
243
+ return (
244
+ <Modal
245
+ visible={visible}
246
+ transparent
247
+ animationType="fade"
248
+ onRequestClose={onClose}>
249
+ <Pressable style={styles.overlay} onPress={onClose}>
250
+ <Pressable style={styles.modalCard} onPress={e => e.stopPropagation()}>
251
+ {/* Header */}
252
+ <View style={styles.header}>
253
+ <View style={styles.headerLeft}>
254
+ <View style={styles.iconCircle}>
255
+ <FilterIcon size={16} color={AppColors.purple} />
256
+ </View>
257
+ <View>
258
+ <View style={{flexDirection: 'row', alignItems: 'center', gap: 6}}>
259
+ <Text style={styles.title}>Network Filters</Text>
260
+ {activeFiltersCount > 0 && (
261
+ <View style={styles.activeBadge}>
262
+ <Text style={styles.activeBadgeText}>{activeFiltersCount} active</Text>
263
+ </View>
264
+ )}
265
+ </View>
266
+ <Text style={styles.subtitle}>Filter requests by status, method & speed</Text>
267
+ </View>
268
+ </View>
269
+ <TouchableOpacity onPress={onClose} hitSlop={10} style={styles.closeBtn}>
270
+ <CloseWhite size={14} color={AppColors.grayText} />
271
+ </TouchableOpacity>
272
+ </View>
273
+
274
+ <ScrollView
275
+ style={styles.scrollArea}
276
+ contentContainerStyle={styles.scrollContent}
277
+ showsVerticalScrollIndicator={false}>
278
+ {/* Section 1: HTTP Status Codes */}
279
+ <View style={styles.section}>
280
+ <Text style={styles.sectionTitle}>HTTP STATUS CODE</Text>
281
+ <View style={styles.chipsWrap}>
282
+ {[
283
+ {key: 'all', label: 'All Statuses', color: AppColors.purple, icon: LayersIcon, count: counts.statusMap.all},
284
+ {key: '2xx', label: '2xx Success', color: AppColors.greenColor, icon: CircleCheckIcon, count: counts.statusMap['2xx'] || 0},
285
+ {key: '3xx', label: '3xx Redirect', color: AppColors.warningIconGold, icon: RepeatIcon, count: counts.statusMap['3xx'] || 0},
286
+ {key: '4xx', label: '4xx Client Error', color: AppColors.darkOrange, icon: CircleAlertIcon, count: counts.statusMap['4xx'] || 0},
287
+ {key: '5xx', label: '5xx Server Error', color: AppColors.errorColor, icon: CircleXIcon, count: counts.statusMap['5xx'] || 0},
288
+ {key: 'failed', label: 'Network Failed / 0', color: AppColors.errorColor, icon: ShieldAlertIcon, count: counts.statusMap.failed || 0},
289
+ ].map(item => {
290
+ const isSelected = draft.statusCodes.has(item.key);
291
+ const IconComp = item.icon;
292
+ return (
293
+ <TouchableOpacity
294
+ key={item.key}
295
+ activeOpacity={0.7}
296
+ onPress={() => toggleStatus(item.key)}
297
+ style={[
298
+ styles.chip,
299
+ isSelected && {
300
+ backgroundColor: `${item.color}15`,
301
+ borderColor: item.color,
302
+ },
303
+ ]}>
304
+ <IconComp size={13} color={isSelected ? item.color : AppColors.grayTextWeak} />
305
+ <Text
306
+ style={[
307
+ styles.chipLabel,
308
+ isSelected && {color: item.color, fontFamily: AppFonts.interBold},
309
+ ]}>
310
+ {item.label}
311
+ </Text>
312
+ <View
313
+ style={[
314
+ styles.countPill,
315
+ isSelected && {backgroundColor: `${item.color}25`},
316
+ ]}>
317
+ <Text
318
+ style={[
319
+ styles.countPillText,
320
+ isSelected && {color: item.color},
321
+ ]}>
322
+ {item.count}
323
+ </Text>
324
+ </View>
325
+ </TouchableOpacity>
326
+ );
327
+ })}
328
+ </View>
329
+ </View>
330
+
331
+ {/* Section 2: HTTP Method */}
332
+ <View style={styles.section}>
333
+ <Text style={styles.sectionTitle}>HTTP METHOD</Text>
334
+ <View style={styles.chipsWrap}>
335
+ <TouchableOpacity
336
+ activeOpacity={0.7}
337
+ onPress={() => toggleMethod('all')}
338
+ style={[
339
+ styles.chip,
340
+ draft.methods.has('all') && {
341
+ backgroundColor: `${AppColors.purple}15`,
342
+ borderColor: AppColors.purple,
343
+ },
344
+ ]}>
345
+ <LayersIcon size={12} color={draft.methods.has('all') ? AppColors.purple : AppColors.grayTextWeak} />
346
+ <Text
347
+ style={[
348
+ styles.chipLabel,
349
+ draft.methods.has('all') && {color: AppColors.purple, fontFamily: AppFonts.interBold},
350
+ ]}>
351
+ All Methods
352
+ </Text>
353
+ <View style={[styles.countPill, draft.methods.has('all') && {backgroundColor: `${AppColors.purple}25`}]}>
354
+ <Text style={[styles.countPillText, draft.methods.has('all') && {color: AppColors.purple}]}>
355
+ {counts.methodMap.all}
356
+ </Text>
357
+ </View>
358
+ </TouchableOpacity>
359
+
360
+ {(['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'QUERY', 'OPTIONS', 'HEAD'] as const).map(m => {
361
+ const isSelected = draft.methods.has(m);
362
+ const mColor = METHOD_COLORS[m as Method] || AppColors.grayText;
363
+ const count = counts.methodMap[m] || 0;
364
+ return (
365
+ <TouchableOpacity
366
+ key={m}
367
+ activeOpacity={0.7}
368
+ onPress={() => toggleMethod(m)}
369
+ style={[
370
+ styles.chip,
371
+ isSelected && {
372
+ backgroundColor: `${mColor}18`,
373
+ borderColor: mColor,
374
+ },
375
+ ]}>
376
+ <View style={[styles.methodDot, {backgroundColor: mColor}]} />
377
+ <Text
378
+ style={[
379
+ styles.chipLabel,
380
+ isSelected && {color: mColor, fontFamily: AppFonts.interBold},
381
+ ]}>
382
+ {m}
383
+ </Text>
384
+ <View
385
+ style={[
386
+ styles.countPill,
387
+ isSelected && {backgroundColor: `${mColor}25`},
388
+ ]}>
389
+ <Text
390
+ style={[
391
+ styles.countPillText,
392
+ isSelected && {color: mColor},
393
+ ]}>
394
+ {count}
395
+ </Text>
396
+ </View>
397
+ </TouchableOpacity>
398
+ );
399
+ })}
400
+ </View>
401
+ </View>
402
+
403
+ {/* Section 3: Latency & Speed */}
404
+ <View style={styles.section}>
405
+ <Text style={styles.sectionTitle}>LATENCY & SPEED</Text>
406
+ <View style={styles.chipsWrap}>
407
+ {[
408
+ {key: 'all' as const, label: 'All Speeds', color: AppColors.purple},
409
+ {key: 'fast' as const, label: '⚡ Fast (<200ms)', color: AppColors.greenColor, count: counts.fastCount},
410
+ {key: 'normal' as const, label: '⏱️ Normal (200-500ms)', color: AppColors.skyBlue, count: counts.normalCount},
411
+ {key: 'slow' as const, label: '⚠️ Slow (>500ms)', color: AppColors.errorColor, count: counts.slowCount},
412
+ ].map(item => {
413
+ const isSelected = draft.latency === item.key;
414
+ return (
415
+ <TouchableOpacity
416
+ key={item.key}
417
+ activeOpacity={0.7}
418
+ onPress={() => setDraft(prev => ({...prev, latency: item.key}))}
419
+ style={[
420
+ styles.chip,
421
+ isSelected && {
422
+ backgroundColor: `${item.color}15`,
423
+ borderColor: item.color,
424
+ },
425
+ ]}>
426
+ <Text
427
+ style={[
428
+ styles.chipLabel,
429
+ isSelected && {color: item.color, fontFamily: AppFonts.interBold},
430
+ ]}>
431
+ {item.label}
432
+ </Text>
433
+ {item.count != null && (
434
+ <View style={[styles.countPill, isSelected && {backgroundColor: `${item.color}25`}]}>
435
+ <Text style={[styles.countPillText, isSelected && {color: item.color}]}>
436
+ {item.count}
437
+ </Text>
438
+ </View>
439
+ )}
440
+ </TouchableOpacity>
441
+ );
442
+ })}
443
+ </View>
444
+ </View>
445
+
446
+ {/* Section 4: Protocol (HTTPS / HTTP) */}
447
+ <View style={styles.section}>
448
+ <Text style={styles.sectionTitle}>PROTOCOL</Text>
449
+ <View style={styles.chipsWrap}>
450
+ {[
451
+ {key: 'all' as const, label: 'All Protocols', color: AppColors.purple},
452
+ {key: 'https' as const, label: '🔒 HTTPS Secure', color: AppColors.mintGreenText, count: counts.httpsCount},
453
+ {key: 'http' as const, label: '🔓 HTTP Insecure', color: AppColors.amber700, count: counts.httpCount},
454
+ ].map(item => {
455
+ const isSelected = draft.protocol === item.key;
456
+ return (
457
+ <TouchableOpacity
458
+ key={item.key}
459
+ activeOpacity={0.7}
460
+ onPress={() => setDraft(prev => ({...prev, protocol: item.key}))}
461
+ style={[
462
+ styles.chip,
463
+ isSelected && {
464
+ backgroundColor: `${item.color}15`,
465
+ borderColor: item.color,
466
+ },
467
+ ]}>
468
+ <Text
469
+ style={[
470
+ styles.chipLabel,
471
+ isSelected && {color: item.color, fontFamily: AppFonts.interBold},
472
+ ]}>
473
+ {item.label}
474
+ </Text>
475
+ {item.count != null && (
476
+ <View style={[styles.countPill, isSelected && {backgroundColor: `${item.color}25`}]}>
477
+ <Text style={[styles.countPillText, isSelected && {color: item.color}]}>
478
+ {item.count}
479
+ </Text>
480
+ </View>
481
+ )}
482
+ </TouchableOpacity>
483
+ );
484
+ })}
485
+ </View>
486
+ </View>
487
+
488
+ {/* Section 5: Sort By */}
489
+ <View style={styles.section}>
490
+ <Text style={styles.sectionTitle}>SORT BY</Text>
491
+ <View style={styles.chipsWrap}>
492
+ {[
493
+ {key: 'time_desc' as const, label: '⏱️ Newest First (Default)'},
494
+ {key: 'time_asc' as const, label: '⌛ Oldest First'},
495
+ {key: 'duration_desc' as const, label: '🐢 Slowest Response'},
496
+ {key: 'duration_asc' as const, label: '🚀 Fastest Response'},
497
+ {key: 'size_desc' as const, label: '📦 Largest Response Size'},
498
+ ].map(item => {
499
+ const isSelected = draft.sortBy === item.key;
500
+ return (
501
+ <TouchableOpacity
502
+ key={item.key}
503
+ activeOpacity={0.7}
504
+ onPress={() => setDraft(prev => ({...prev, sortBy: item.key}))}
505
+ style={[
506
+ styles.chip,
507
+ isSelected && {
508
+ backgroundColor: `${AppColors.purple}15`,
509
+ borderColor: AppColors.purple,
510
+ },
511
+ ]}>
512
+ <Text
513
+ style={[
514
+ styles.chipLabel,
515
+ isSelected && {color: AppColors.purple, fontFamily: AppFonts.interBold},
516
+ ]}>
517
+ {item.label}
518
+ </Text>
519
+ </TouchableOpacity>
520
+ );
521
+ })}
522
+ </View>
523
+ </View>
524
+ </ScrollView>
525
+
526
+ {/* Footer Action Buttons */}
527
+ <View style={styles.footer}>
528
+ <TouchableScale onPress={handleReset} style={styles.resetBtn}>
529
+ <Text style={styles.resetBtnText}>Reset All</Text>
530
+ </TouchableScale>
531
+
532
+ <TouchableScale onPress={handleApply} style={styles.applyBtn}>
533
+ <Text style={styles.applyBtnText}>
534
+ Apply ({previewMatchCount} Request{previewMatchCount === 1 ? '' : 's'})
535
+ </Text>
536
+ </TouchableScale>
537
+ </View>
538
+ </Pressable>
539
+ </Pressable>
540
+ </Modal>
541
+ );
542
+ };
543
+
544
+ const styles = StyleSheet.create({
545
+ overlay: {
546
+ flex: 1,
547
+ backgroundColor: 'rgba(0, 0, 0, 0.55)',
548
+ justifyContent: 'flex-end',
549
+ },
550
+ modalCard: {
551
+ backgroundColor: AppColors.primaryLight,
552
+ borderTopLeftRadius: 20,
553
+ borderTopRightRadius: 20,
554
+ maxHeight: '85%',
555
+ shadowColor: AppColors.black,
556
+ shadowOffset: {width: 0, height: -4},
557
+ shadowOpacity: 0.15,
558
+ shadowRadius: 10,
559
+ elevation: 8,
560
+ },
561
+ header: {
562
+ flexDirection: 'row',
563
+ alignItems: 'center',
564
+ justifyContent: 'space-between',
565
+ paddingHorizontal: 16,
566
+ paddingTop: 16,
567
+ paddingBottom: 12,
568
+ borderBottomWidth: 1,
569
+ borderBottomColor: AppColors.grayBorderSecondary,
570
+ },
571
+ headerLeft: {
572
+ flexDirection: 'row',
573
+ alignItems: 'center',
574
+ gap: 10,
575
+ flex: 1,
576
+ },
577
+ iconCircle: {
578
+ width: 34,
579
+ height: 34,
580
+ borderRadius: 10,
581
+ backgroundColor: `${AppColors.purple}15`,
582
+ alignItems: 'center',
583
+ justifyContent: 'center',
584
+ },
585
+ title: {
586
+ fontFamily: AppFonts.interBold,
587
+ fontSize: 16,
588
+ color: AppColors.primaryBlack,
589
+ },
590
+ subtitle: {
591
+ fontFamily: AppFonts.interRegular,
592
+ fontSize: 11,
593
+ color: AppColors.grayText,
594
+ marginTop: 1,
595
+ },
596
+ activeBadge: {
597
+ backgroundColor: `${AppColors.purple}20`,
598
+ paddingHorizontal: 6,
599
+ paddingVertical: 1.5,
600
+ borderRadius: 8,
601
+ borderWidth: 1,
602
+ borderColor: `${AppColors.purple}40`,
603
+ },
604
+ activeBadgeText: {
605
+ fontFamily: AppFonts.interBold,
606
+ fontSize: 9.5,
607
+ color: AppColors.purple,
608
+ },
609
+ closeBtn: {
610
+ padding: 6,
611
+ },
612
+ scrollArea: {
613
+ maxHeight: 460,
614
+ },
615
+ scrollContent: {
616
+ padding: 16,
617
+ gap: 16,
618
+ },
619
+ section: {
620
+ gap: 8,
621
+ },
622
+ sectionTitle: {
623
+ fontFamily: AppFonts.interBold,
624
+ fontSize: 11,
625
+ color: AppColors.grayTextWeak,
626
+ letterSpacing: 0.8,
627
+ },
628
+ chipsWrap: {
629
+ flexDirection: 'row',
630
+ flexWrap: 'wrap',
631
+ gap: 8,
632
+ },
633
+ chip: {
634
+ flexDirection: 'row',
635
+ alignItems: 'center',
636
+ gap: 6,
637
+ paddingHorizontal: 10,
638
+ paddingVertical: 6,
639
+ borderRadius: 8,
640
+ backgroundColor: AppColors.grayBackground,
641
+ borderWidth: 1,
642
+ borderColor: AppColors.grayBorderSecondary,
643
+ },
644
+ chipLabel: {
645
+ fontFamily: AppFonts.interMedium,
646
+ fontSize: 12,
647
+ color: AppColors.primaryBlack,
648
+ },
649
+ methodDot: {
650
+ width: 6.5,
651
+ height: 6.5,
652
+ borderRadius: 3.5,
653
+ },
654
+ countPill: {
655
+ backgroundColor: `${AppColors.grayBorderSecondary}50`,
656
+ paddingHorizontal: 5,
657
+ paddingVertical: 1,
658
+ borderRadius: 6,
659
+ },
660
+ countPillText: {
661
+ fontFamily: AppFonts.interBold,
662
+ fontSize: 9.5,
663
+ color: AppColors.grayText,
664
+ },
665
+ footer: {
666
+ flexDirection: 'row',
667
+ alignItems: 'center',
668
+ gap: 10,
669
+ paddingHorizontal: 16,
670
+ paddingVertical: 14,
671
+ borderTopWidth: 1,
672
+ borderTopColor: AppColors.grayBorderSecondary,
673
+ backgroundColor: AppColors.primaryLight,
674
+ },
675
+ resetBtn: {
676
+ flex: 1,
677
+ paddingVertical: 11,
678
+ borderRadius: 10,
679
+ borderWidth: 1,
680
+ borderColor: AppColors.grayBorderSecondary,
681
+ alignItems: 'center',
682
+ justifyContent: 'center',
683
+ backgroundColor: AppColors.grayBackground,
684
+ },
685
+ resetBtnText: {
686
+ fontFamily: AppFonts.interBold,
687
+ fontSize: 13,
688
+ color: AppColors.grayTextStrong,
689
+ },
690
+ applyBtn: {
691
+ flex: 2,
692
+ paddingVertical: 11,
693
+ borderRadius: 10,
694
+ alignItems: 'center',
695
+ justifyContent: 'center',
696
+ backgroundColor: AppColors.purple,
697
+ shadowColor: AppColors.purple,
698
+ shadowOffset: {width: 0, height: 2},
699
+ shadowOpacity: 0.25,
700
+ shadowRadius: 4,
701
+ elevation: 3,
702
+ },
703
+ applyBtnText: {
704
+ fontFamily: AppFonts.interBold,
705
+ fontSize: 13,
706
+ color: AppColors.white,
707
+ },
708
+ });
709
+
710
+ export default NetworkFilterModal;