react-native-inapp-inspector 2.3.9 → 2.3.11

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 (65) hide show
  1. package/dist/commonjs/components/Inspector/DebuggingTab.d.ts +3 -0
  2. package/dist/commonjs/components/Inspector/DebuggingTab.js +682 -0
  3. package/dist/commonjs/components/Inspector/MainScreen.js +2 -0
  4. package/dist/commonjs/components/Inspector/NetworkDetail.js +2 -1
  5. package/dist/commonjs/components/Inspector/NetworkFilterModal.d.ts +19 -0
  6. package/dist/commonjs/components/Inspector/NetworkFilterModal.js +648 -0
  7. package/dist/commonjs/components/Inspector/NetworkTab.js +89 -147
  8. package/dist/commonjs/components/Inspector/SettingsPanel.js +12 -0
  9. package/dist/commonjs/components/Inspector/TabBar.js +9 -0
  10. package/dist/commonjs/components/LogCard.js +5 -26
  11. package/dist/commonjs/components/NetworkIcons.d.ts +2 -0
  12. package/dist/commonjs/components/NetworkIcons.js +13 -1
  13. package/dist/commonjs/components/QRCodeView.d.ts +9 -0
  14. package/dist/commonjs/components/QRCodeView.js +79 -0
  15. package/dist/commonjs/constants/index.js +3 -0
  16. package/dist/commonjs/constants/version.d.ts +1 -1
  17. package/dist/commonjs/constants/version.js +1 -1
  18. package/dist/commonjs/helpers/qrGenerator.d.ts +2 -0
  19. package/dist/commonjs/helpers/qrGenerator.js +170 -0
  20. package/dist/commonjs/index.js +2 -0
  21. package/dist/commonjs/styles/index.d.ts +11 -0
  22. package/dist/commonjs/styles/index.js +20 -5
  23. package/dist/commonjs/types/enums.d.ts +5 -0
  24. package/dist/commonjs/types/enums.js +5 -0
  25. package/dist/esm/components/Inspector/DebuggingTab.d.ts +3 -0
  26. package/dist/esm/components/Inspector/DebuggingTab.js +642 -0
  27. package/dist/esm/components/Inspector/MainScreen.js +2 -0
  28. package/dist/esm/components/Inspector/NetworkDetail.js +2 -1
  29. package/dist/esm/components/Inspector/NetworkFilterModal.d.ts +19 -0
  30. package/dist/esm/components/Inspector/NetworkFilterModal.js +607 -0
  31. package/dist/esm/components/Inspector/NetworkTab.js +91 -149
  32. package/dist/esm/components/Inspector/SettingsPanel.js +13 -1
  33. package/dist/esm/components/Inspector/TabBar.js +10 -1
  34. package/dist/esm/components/LogCard.js +5 -26
  35. package/dist/esm/components/NetworkIcons.d.ts +2 -0
  36. package/dist/esm/components/NetworkIcons.js +10 -0
  37. package/dist/esm/components/QRCodeView.d.ts +9 -0
  38. package/dist/esm/components/QRCodeView.js +42 -0
  39. package/dist/esm/constants/index.js +3 -0
  40. package/dist/esm/constants/version.d.ts +1 -1
  41. package/dist/esm/constants/version.js +1 -1
  42. package/dist/esm/helpers/qrGenerator.d.ts +2 -0
  43. package/dist/esm/helpers/qrGenerator.js +167 -0
  44. package/dist/esm/index.js +2 -0
  45. package/dist/esm/styles/index.d.ts +11 -0
  46. package/dist/esm/styles/index.js +20 -5
  47. package/dist/esm/types/enums.d.ts +5 -0
  48. package/dist/esm/types/enums.js +5 -0
  49. package/package.json +2 -2
  50. package/src/components/Inspector/DebuggingTab.tsx +755 -0
  51. package/src/components/Inspector/MainScreen.tsx +2 -0
  52. package/src/components/Inspector/NetworkDetail.tsx +2 -1
  53. package/src/components/Inspector/NetworkFilterModal.tsx +710 -0
  54. package/src/components/Inspector/NetworkTab.tsx +127 -204
  55. package/src/components/Inspector/SettingsPanel.tsx +20 -0
  56. package/src/components/Inspector/TabBar.tsx +11 -0
  57. package/src/components/LogCard.tsx +5 -30
  58. package/src/components/NetworkIcons.tsx +66 -0
  59. package/src/components/QRCodeView.tsx +71 -0
  60. package/src/constants/index.ts +3 -0
  61. package/src/constants/version.ts +1 -1
  62. package/src/helpers/qrGenerator.ts +184 -0
  63. package/src/index.tsx +2 -0
  64. package/src/styles/index.ts +20 -5
  65. package/src/types/enums.ts +5 -0
@@ -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;