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