react-native-inapp-inspector 2.2.9 → 2.3.1
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/AppHeaderLogo.js +45 -12
- package/dist/commonjs/components/BrandSquareIcon.js +92 -49
- package/dist/commonjs/components/EmptyState.d.ts +7 -2
- package/dist/commonjs/components/EmptyState.js +11 -5
- package/dist/commonjs/components/Inspector/AnalyticsTab.js +29 -18
- package/dist/commonjs/components/Inspector/ConsoleTab.js +6 -1
- package/dist/commonjs/components/Inspector/CrashDetail.js +43 -9
- package/dist/commonjs/components/Inspector/CrashTab.js +34 -3
- package/dist/commonjs/components/Inspector/DeviceInfoTab.js +80 -16
- package/dist/commonjs/components/Inspector/InspectorHeader.js +16 -3
- package/dist/commonjs/components/Inspector/MainScreen.js +7 -11
- package/dist/commonjs/components/Inspector/NetworkDetail.js +3 -2
- package/dist/commonjs/components/Inspector/NetworkTab.js +103 -4
- package/dist/commonjs/components/Inspector/ReduxTab.js +19 -3
- package/dist/commonjs/components/Inspector/SettingsPanel.js +4 -4
- package/dist/commonjs/components/Inspector/StorageTab.js +123 -92
- package/dist/commonjs/components/JsonViewer.js +2 -0
- package/dist/commonjs/components/MetaAccordion.js +15 -3
- package/dist/commonjs/components/SkeletonPlaceholder.d.ts +6 -0
- package/dist/commonjs/components/SkeletonPlaceholder.js +208 -0
- package/dist/commonjs/constants/version.d.ts +1 -1
- package/dist/commonjs/constants/version.js +1 -1
- package/dist/commonjs/customHooks/networkLogger.d.ts +3 -0
- package/dist/commonjs/customHooks/networkLogger.js +10 -1
- package/dist/commonjs/customHooks/storageInspector.d.ts +2 -2
- package/dist/commonjs/customHooks/storageInspector.js +17 -13
- package/dist/commonjs/helpers/index.d.ts +6 -0
- package/dist/commonjs/helpers/index.js +196 -6
- package/dist/commonjs/helpers/searchQueryParser.js +62 -20
- package/dist/commonjs/i18n/index.d.ts +13 -0
- package/dist/commonjs/i18n/index.js +35 -2
- package/dist/commonjs/index.d.ts +3 -0
- package/dist/commonjs/index.js +108 -35
- package/dist/commonjs/styles/AppColors.d.ts +4 -0
- package/dist/commonjs/styles/AppColors.js +11 -1
- package/dist/commonjs/styles/AppFonts.d.ts +11 -0
- package/dist/commonjs/styles/AppFonts.js +11 -1
- package/dist/commonjs/types/interfaces.d.ts +2 -2
- package/dist/esm/components/AppHeaderLogo.js +45 -12
- package/dist/esm/components/BrandSquareIcon.js +92 -49
- package/dist/esm/components/EmptyState.d.ts +7 -2
- package/dist/esm/components/EmptyState.js +11 -5
- package/dist/esm/components/Inspector/AnalyticsTab.js +30 -19
- package/dist/esm/components/Inspector/ConsoleTab.js +6 -1
- package/dist/esm/components/Inspector/CrashDetail.js +43 -9
- package/dist/esm/components/Inspector/CrashTab.js +34 -3
- package/dist/esm/components/Inspector/DeviceInfoTab.js +80 -16
- package/dist/esm/components/Inspector/InspectorHeader.js +17 -4
- package/dist/esm/components/Inspector/MainScreen.js +8 -12
- package/dist/esm/components/Inspector/NetworkDetail.js +3 -2
- package/dist/esm/components/Inspector/NetworkTab.js +103 -4
- package/dist/esm/components/Inspector/ReduxTab.js +19 -3
- package/dist/esm/components/Inspector/SettingsPanel.js +4 -4
- package/dist/esm/components/Inspector/StorageTab.js +124 -93
- package/dist/esm/components/JsonViewer.js +2 -0
- package/dist/esm/components/MetaAccordion.js +15 -3
- package/dist/esm/components/SkeletonPlaceholder.d.ts +6 -0
- package/dist/esm/components/SkeletonPlaceholder.js +173 -0
- package/dist/esm/constants/version.d.ts +1 -1
- package/dist/esm/constants/version.js +1 -1
- package/dist/esm/customHooks/networkLogger.d.ts +3 -0
- package/dist/esm/customHooks/networkLogger.js +8 -0
- package/dist/esm/customHooks/storageInspector.d.ts +2 -2
- package/dist/esm/customHooks/storageInspector.js +17 -13
- package/dist/esm/helpers/index.d.ts +6 -0
- package/dist/esm/helpers/index.js +193 -5
- package/dist/esm/helpers/searchQueryParser.js +62 -20
- package/dist/esm/i18n/index.d.ts +13 -0
- package/dist/esm/i18n/index.js +33 -3
- package/dist/esm/index.d.ts +3 -0
- package/dist/esm/index.js +97 -37
- package/dist/esm/styles/AppColors.d.ts +4 -0
- package/dist/esm/styles/AppColors.js +9 -0
- package/dist/esm/styles/AppFonts.d.ts +11 -0
- package/dist/esm/styles/AppFonts.js +9 -0
- package/dist/esm/types/interfaces.d.ts +2 -2
- package/package.json +1 -1
- package/src/components/AppHeaderLogo.tsx +46 -12
- package/src/components/BrandSquareIcon.tsx +127 -78
- package/src/components/EmptyState.tsx +27 -5
- package/src/components/Inspector/AnalyticsTab.tsx +48 -27
- package/src/components/Inspector/ConsoleTab.tsx +10 -0
- package/src/components/Inspector/CrashDetail.tsx +65 -9
- package/src/components/Inspector/CrashTab.tsx +41 -3
- package/src/components/Inspector/DeviceInfoTab.tsx +87 -16
- package/src/components/Inspector/InspectorHeader.tsx +24 -4
- package/src/components/Inspector/MainScreen.tsx +7 -11
- package/src/components/Inspector/NetworkDetail.tsx +4 -4
- package/src/components/Inspector/NetworkTab.tsx +129 -4
- package/src/components/Inspector/ReduxTab.tsx +26 -8
- package/src/components/Inspector/SettingsPanel.tsx +4 -4
- package/src/components/Inspector/StorageTab.tsx +191 -117
- package/src/components/JsonViewer.tsx +2 -0
- package/src/components/MetaAccordion.tsx +18 -3
- package/src/components/SkeletonPlaceholder.tsx +206 -0
- package/src/constants/version.ts +1 -1
- package/src/customHooks/networkLogger.ts +15 -0
- package/src/customHooks/storageInspector.ts +18 -14
- package/src/helpers/index.ts +213 -5
- package/src/helpers/searchQueryParser.ts +63 -20
- package/src/i18n/index.ts +38 -3
- package/src/index.tsx +124 -37
- package/src/styles/AppColors.ts +9 -0
- package/src/styles/AppFonts.ts +16 -0
- package/src/types/interfaces.ts +2 -2
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
View,
|
|
4
4
|
Text,
|
|
5
5
|
ScrollView,
|
|
6
|
+
FlatList,
|
|
6
7
|
TextInput,
|
|
7
8
|
Modal,
|
|
8
9
|
Alert,
|
|
@@ -43,6 +44,131 @@ import {
|
|
|
43
44
|
import {copyToClipboard} from '../../helpers';
|
|
44
45
|
import {showToast} from '../../helpers/toast';
|
|
45
46
|
|
|
47
|
+
const getTypeBadge = (type: StorageEntry['type']) => {
|
|
48
|
+
switch (type) {
|
|
49
|
+
case 'json':
|
|
50
|
+
return {label: 'JSON', color: AppColors.purple, bg: `${AppColors.purple}16`};
|
|
51
|
+
case 'number':
|
|
52
|
+
return {label: 'NUM', color: AppColors.warningIconGold, bg: `${AppColors.warningIconGold}16`};
|
|
53
|
+
case 'boolean':
|
|
54
|
+
return {label: 'BOOL', color: AppColors.emerald500, bg: `${AppColors.emerald500}16`};
|
|
55
|
+
case 'null':
|
|
56
|
+
return {label: 'NULL', color: AppColors.grayTextWeak, bg: `${AppColors.grayTextWeak}16`};
|
|
57
|
+
default:
|
|
58
|
+
return {label: 'STR', color: AppColors.blue500, bg: `${AppColors.blue500}16`};
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const StorageEntryCard = React.memo(function StorageEntryCard({
|
|
63
|
+
entry,
|
|
64
|
+
isExpanded,
|
|
65
|
+
onToggleExpand,
|
|
66
|
+
onCopy,
|
|
67
|
+
onEdit,
|
|
68
|
+
onDelete,
|
|
69
|
+
badge,
|
|
70
|
+
}: {
|
|
71
|
+
entry: StorageEntry;
|
|
72
|
+
isExpanded: boolean;
|
|
73
|
+
onToggleExpand: (key: string) => void;
|
|
74
|
+
onCopy: (entry: StorageEntry) => void;
|
|
75
|
+
onEdit: (entry: StorageEntry) => void;
|
|
76
|
+
onDelete: (key: string) => void;
|
|
77
|
+
badge: {label: string; color: string; bg: string};
|
|
78
|
+
}) {
|
|
79
|
+
const formattedBytes = useMemo(() => {
|
|
80
|
+
return entry.byteSize < 1024
|
|
81
|
+
? `${entry.byteSize} B`
|
|
82
|
+
: `${(entry.byteSize / 1024).toFixed(1)} KB`;
|
|
83
|
+
}, [entry.byteSize]);
|
|
84
|
+
|
|
85
|
+
// Lazy compute displayed value to avoid layout thrashing on large JSON strings
|
|
86
|
+
const displayValue = useMemo(() => {
|
|
87
|
+
if (!isExpanded) {
|
|
88
|
+
if (entry.value.length > 250) {
|
|
89
|
+
return entry.value.slice(0, 250) + '...';
|
|
90
|
+
}
|
|
91
|
+
return entry.value;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (entry.type === 'json') {
|
|
95
|
+
try {
|
|
96
|
+
const parsed = entry.parsedValue ?? JSON.parse(entry.value);
|
|
97
|
+
return JSON.stringify(parsed, null, 2);
|
|
98
|
+
} catch {
|
|
99
|
+
return entry.value;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return entry.value;
|
|
103
|
+
}, [isExpanded, entry.value, entry.type, entry.parsedValue]);
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<View style={styles.entryCard}>
|
|
107
|
+
{/* Entry Header: Key name, Type Badge, Size, Actions */}
|
|
108
|
+
<View style={styles.entryHeader}>
|
|
109
|
+
<View style={{flex: 1, marginRight: 8}}>
|
|
110
|
+
<View style={{flexDirection: 'row', alignItems: 'center', gap: 6, flexWrap: 'wrap'}}>
|
|
111
|
+
<Text style={styles.entryKey} numberOfLines={1} selectable>
|
|
112
|
+
{entry.key}
|
|
113
|
+
</Text>
|
|
114
|
+
<View
|
|
115
|
+
style={[
|
|
116
|
+
styles.typeBadge,
|
|
117
|
+
{backgroundColor: badge.bg, borderColor: `${badge.color}33`},
|
|
118
|
+
]}>
|
|
119
|
+
<Text style={[styles.typeBadgeText, {color: badge.color}]}>
|
|
120
|
+
{badge.label}
|
|
121
|
+
</Text>
|
|
122
|
+
</View>
|
|
123
|
+
<Text style={styles.sizeText}>{formattedBytes}</Text>
|
|
124
|
+
</View>
|
|
125
|
+
</View>
|
|
126
|
+
|
|
127
|
+
{/* Actions: Copy, Edit, Delete */}
|
|
128
|
+
<View style={styles.entryActions}>
|
|
129
|
+
<TouchableScale
|
|
130
|
+
hitSlop={6}
|
|
131
|
+
onPress={() => onCopy(entry)}
|
|
132
|
+
style={styles.entryActionBtn}>
|
|
133
|
+
<CopyIcon size={12} color={AppColors.grayText} />
|
|
134
|
+
</TouchableScale>
|
|
135
|
+
|
|
136
|
+
<TouchableScale
|
|
137
|
+
hitSlop={6}
|
|
138
|
+
onPress={() => onEdit(entry)}
|
|
139
|
+
style={styles.entryActionBtn}>
|
|
140
|
+
<PencilIcon size={12} color={AppColors.purple} />
|
|
141
|
+
</TouchableScale>
|
|
142
|
+
|
|
143
|
+
<TouchableScale
|
|
144
|
+
hitSlop={6}
|
|
145
|
+
onPress={() => onDelete(entry.key)}
|
|
146
|
+
style={[styles.entryActionBtn, {backgroundColor: `${AppColors.errorColor}12`}]}>
|
|
147
|
+
<TrashIcon size={12} color={AppColors.errorColor} />
|
|
148
|
+
</TouchableScale>
|
|
149
|
+
</View>
|
|
150
|
+
</View>
|
|
151
|
+
|
|
152
|
+
{/* Entry Value Preview / Viewer */}
|
|
153
|
+
<TouchableScale
|
|
154
|
+
onPress={() => onToggleExpand(entry.key)}
|
|
155
|
+
style={styles.valuePreviewBox}>
|
|
156
|
+
<Text
|
|
157
|
+
style={styles.valuePreviewText}
|
|
158
|
+
numberOfLines={isExpanded ? undefined : 3}
|
|
159
|
+
selectable={isExpanded}>
|
|
160
|
+
{displayValue}
|
|
161
|
+
</Text>
|
|
162
|
+
{entry.value.length > 80 && (
|
|
163
|
+
<Text style={styles.expandHint}>
|
|
164
|
+
{isExpanded ? '▲ Collapse' : '▼ Expand'}
|
|
165
|
+
</Text>
|
|
166
|
+
)}
|
|
167
|
+
</TouchableScale>
|
|
168
|
+
</View>
|
|
169
|
+
);
|
|
170
|
+
});
|
|
171
|
+
|
|
46
172
|
export const StorageTab = React.memo(() => {
|
|
47
173
|
const {t} = useTranslation();
|
|
48
174
|
const [activeDriver, setActiveDriver] = useState<StorageDriver>('asyncStorage');
|
|
@@ -235,20 +361,25 @@ export const StorageTab = React.memo(() => {
|
|
|
235
361
|
return `${(totalBytes / (1024 * 1024)).toFixed(2)} MB`;
|
|
236
362
|
}, [totalBytes]);
|
|
237
363
|
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
364
|
+
const handleCopyEntry = useCallback((entry: StorageEntry) => {
|
|
365
|
+
copyToClipboard(entry.value, entry.key);
|
|
366
|
+
showToast(`Copied value of "${entry.key}"`);
|
|
367
|
+
}, []);
|
|
368
|
+
|
|
369
|
+
const renderItem = useCallback(
|
|
370
|
+
({item}: {item: StorageEntry}) => (
|
|
371
|
+
<StorageEntryCard
|
|
372
|
+
entry={item}
|
|
373
|
+
isExpanded={Boolean(expandedKeys[item.key])}
|
|
374
|
+
onToggleExpand={toggleExpand}
|
|
375
|
+
onCopy={handleCopyEntry}
|
|
376
|
+
onEdit={handleOpenEdit}
|
|
377
|
+
onDelete={handleDeleteKey}
|
|
378
|
+
badge={getTypeBadge(item.type)}
|
|
379
|
+
/>
|
|
380
|
+
),
|
|
381
|
+
[expandedKeys, handleCopyEntry],
|
|
382
|
+
);
|
|
252
383
|
|
|
253
384
|
const isConnected =
|
|
254
385
|
activeDriver === 'asyncStorage'
|
|
@@ -357,9 +488,27 @@ export const StorageTab = React.memo(() => {
|
|
|
357
488
|
clearButtonMode="while-editing"
|
|
358
489
|
/>
|
|
359
490
|
{search.length > 0 && (
|
|
360
|
-
<
|
|
361
|
-
<
|
|
362
|
-
|
|
491
|
+
<View style={{flexDirection: 'row', alignItems: 'center', gap: 4}}>
|
|
492
|
+
<View
|
|
493
|
+
style={{
|
|
494
|
+
backgroundColor: `${AppColors.purple}20`,
|
|
495
|
+
borderRadius: 10,
|
|
496
|
+
paddingHorizontal: 6,
|
|
497
|
+
paddingVertical: 1.5,
|
|
498
|
+
}}>
|
|
499
|
+
<Text
|
|
500
|
+
style={{
|
|
501
|
+
color: AppColors.purple,
|
|
502
|
+
fontSize: 9.5,
|
|
503
|
+
fontFamily: AppFonts.interBold,
|
|
504
|
+
}}>
|
|
505
|
+
{filteredEntries.length}
|
|
506
|
+
</Text>
|
|
507
|
+
</View>
|
|
508
|
+
<TouchableScale onPress={() => setSearch('')} hitSlop={8}>
|
|
509
|
+
<ClearIcon size={13} color={AppColors.grayTextWeak} />
|
|
510
|
+
</TouchableScale>
|
|
511
|
+
</View>
|
|
363
512
|
)}
|
|
364
513
|
</View>
|
|
365
514
|
|
|
@@ -405,110 +554,35 @@ export const StorageTab = React.memo(() => {
|
|
|
405
554
|
<ActivityIndicator size="small" color={AppColors.purple} />
|
|
406
555
|
<Text style={styles.loadingText}>Loading storage entries...</Text>
|
|
407
556
|
</View>
|
|
408
|
-
) : filteredEntries.length === 0 ? (
|
|
409
|
-
<ScrollView
|
|
410
|
-
contentContainerStyle={styles.emptyContainer}
|
|
411
|
-
showsVerticalScrollIndicator={false}>
|
|
412
|
-
<View style={styles.emptyIconWrap}>
|
|
413
|
-
<DatabaseIcon size={28} color={AppColors.grayTextWeak} />
|
|
414
|
-
</View>
|
|
415
|
-
<Text style={styles.emptyTitle}>
|
|
416
|
-
{search ? 'No matching keys found' : `No ${activeDriver === 'asyncStorage' ? 'AsyncStorage' : 'MMKV'} Keys`}
|
|
417
|
-
</Text>
|
|
418
|
-
<Text style={styles.emptySubtitle}>
|
|
419
|
-
{search
|
|
420
|
-
? 'Try modifying your search query'
|
|
421
|
-
: `Storage is auto-detected. Tap "+ Add" above to create your first ${activeDriver === 'asyncStorage' ? 'AsyncStorage' : 'MMKV'} key!`}
|
|
422
|
-
</Text>
|
|
423
|
-
</ScrollView>
|
|
424
557
|
) : (
|
|
425
|
-
<
|
|
558
|
+
<FlatList
|
|
559
|
+
data={filteredEntries}
|
|
560
|
+
keyExtractor={item => item.key}
|
|
561
|
+
renderItem={renderItem}
|
|
562
|
+
initialNumToRender={10}
|
|
563
|
+
maxToRenderPerBatch={10}
|
|
564
|
+
windowSize={5}
|
|
565
|
+
removeClippedSubviews={Platform.OS === 'android'}
|
|
426
566
|
style={styles.scrollArea}
|
|
427
567
|
contentContainerStyle={styles.scrollContent}
|
|
428
|
-
showsVerticalScrollIndicator={false}
|
|
429
|
-
{
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
entry.byteSize < 1024
|
|
434
|
-
? `${entry.byteSize} B`
|
|
435
|
-
: `${(entry.byteSize / 1024).toFixed(1)} KB`;
|
|
436
|
-
|
|
437
|
-
return (
|
|
438
|
-
<View key={entry.key} style={styles.entryCard}>
|
|
439
|
-
{/* Entry Header: Key name, Type Badge, Size, Actions */}
|
|
440
|
-
<View style={styles.entryHeader}>
|
|
441
|
-
<View style={{flex: 1, marginRight: 8}}>
|
|
442
|
-
<View style={{flexDirection: 'row', alignItems: 'center', gap: 6, flexWrap: 'wrap'}}>
|
|
443
|
-
<Text style={styles.entryKey} numberOfLines={1} selectable>
|
|
444
|
-
{entry.key}
|
|
445
|
-
</Text>
|
|
446
|
-
<View
|
|
447
|
-
style={[
|
|
448
|
-
styles.typeBadge,
|
|
449
|
-
{backgroundColor: badge.bg, borderColor: `${badge.color}33`},
|
|
450
|
-
]}>
|
|
451
|
-
<Text style={[styles.typeBadgeText, {color: badge.color}]}>
|
|
452
|
-
{badge.label}
|
|
453
|
-
</Text>
|
|
454
|
-
</View>
|
|
455
|
-
<Text style={styles.sizeText}>{formattedBytes}</Text>
|
|
456
|
-
</View>
|
|
457
|
-
</View>
|
|
458
|
-
|
|
459
|
-
{/* Actions: Copy, Edit, Delete */}
|
|
460
|
-
<View style={styles.entryActions}>
|
|
461
|
-
<TouchableScale
|
|
462
|
-
hitSlop={6}
|
|
463
|
-
onPress={() => {
|
|
464
|
-
copyToClipboard(entry.value, entry.key);
|
|
465
|
-
showToast(`Copied value of "${entry.key}"`);
|
|
466
|
-
}}
|
|
467
|
-
style={styles.entryActionBtn}>
|
|
468
|
-
<CopyIcon size={12} color={AppColors.grayText} />
|
|
469
|
-
</TouchableScale>
|
|
470
|
-
|
|
471
|
-
<TouchableScale
|
|
472
|
-
hitSlop={6}
|
|
473
|
-
onPress={() => handleOpenEdit(entry)}
|
|
474
|
-
style={styles.entryActionBtn}>
|
|
475
|
-
<PencilIcon size={12} color={AppColors.purple} />
|
|
476
|
-
</TouchableScale>
|
|
477
|
-
|
|
478
|
-
<TouchableScale
|
|
479
|
-
hitSlop={6}
|
|
480
|
-
onPress={() => handleDeleteKey(entry.key)}
|
|
481
|
-
style={[styles.entryActionBtn, {backgroundColor: `${AppColors.errorColor}12`}]}>
|
|
482
|
-
<TrashIcon size={12} color={AppColors.errorColor} />
|
|
483
|
-
</TouchableScale>
|
|
484
|
-
</View>
|
|
485
|
-
</View>
|
|
486
|
-
|
|
487
|
-
{/* Entry Value Preview / Viewer */}
|
|
488
|
-
<TouchableScale
|
|
489
|
-
onPress={() => toggleExpand(entry.key)}
|
|
490
|
-
style={styles.valuePreviewBox}>
|
|
491
|
-
<Text
|
|
492
|
-
style={styles.valuePreviewText}
|
|
493
|
-
numberOfLines={isExpanded ? undefined : 3}
|
|
494
|
-
selectable>
|
|
495
|
-
{entry.type === 'json' && entry.parsedValue
|
|
496
|
-
? isExpanded
|
|
497
|
-
? JSON.stringify(entry.parsedValue, null, 2)
|
|
498
|
-
: entry.value
|
|
499
|
-
: entry.value}
|
|
500
|
-
</Text>
|
|
501
|
-
{entry.value.length > 80 && (
|
|
502
|
-
<Text style={styles.expandHint}>
|
|
503
|
-
{isExpanded ? '▲ Collapse' : '▼ Expand'}
|
|
504
|
-
</Text>
|
|
505
|
-
)}
|
|
506
|
-
</TouchableScale>
|
|
568
|
+
showsVerticalScrollIndicator={false}
|
|
569
|
+
ListEmptyComponent={
|
|
570
|
+
<View style={styles.emptyContainer}>
|
|
571
|
+
<View style={styles.emptyIconWrap}>
|
|
572
|
+
<DatabaseIcon size={28} color={AppColors.grayTextWeak} />
|
|
507
573
|
</View>
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
574
|
+
<Text style={styles.emptyTitle}>
|
|
575
|
+
{search ? 'No matching keys found' : `No ${activeDriver === 'asyncStorage' ? 'AsyncStorage' : 'MMKV'} Keys`}
|
|
576
|
+
</Text>
|
|
577
|
+
<Text style={styles.emptySubtitle}>
|
|
578
|
+
{search
|
|
579
|
+
? 'Try modifying your search query'
|
|
580
|
+
: `Storage is auto-detected. Tap "+ Add" above to create your first ${activeDriver === 'asyncStorage' ? 'AsyncStorage' : 'MMKV'} key!`}
|
|
581
|
+
</Text>
|
|
582
|
+
</View>
|
|
583
|
+
}
|
|
584
|
+
ListFooterComponent={<View style={{height: 60}} />}
|
|
585
|
+
/>
|
|
512
586
|
)}
|
|
513
587
|
|
|
514
588
|
{/* ── Create / Edit Key Modal ── */}
|
|
@@ -6,6 +6,7 @@ import {useTranslation} from '../i18n';
|
|
|
6
6
|
import TreeNode from './TreeNode';
|
|
7
7
|
import SegmentedTabs from './SegmentedTabs';
|
|
8
8
|
import HighlightText from './HighlightText';
|
|
9
|
+
import CopyButton from './CopyButton';
|
|
9
10
|
import {PrettyIcon, RawIcon, TableIcon} from './NetworkIcons';
|
|
10
11
|
|
|
11
12
|
// Styles
|
|
@@ -260,6 +261,7 @@ const JsonViewer = React.memo(({
|
|
|
260
261
|
setMode(key as 'pretty' | 'raw' | 'table')
|
|
261
262
|
}
|
|
262
263
|
/>
|
|
264
|
+
<CopyButton value={data} label="JSON" />
|
|
263
265
|
</View>
|
|
264
266
|
)}
|
|
265
267
|
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
|
|
28
28
|
// Components
|
|
29
29
|
import TouchableScale from './TouchableScale';
|
|
30
|
+
import CopyButton from './CopyButton';
|
|
30
31
|
|
|
31
32
|
// Stylesheet
|
|
32
33
|
import {AppColors} from '../styles/AppColors';
|
|
@@ -97,9 +98,23 @@ const MetaAccordion = ({
|
|
|
97
98
|
<Pressable onPress={toggleOpen} hitSlop={12}>
|
|
98
99
|
<View style={styles.metaHeader}>
|
|
99
100
|
<Text style={styles.metaTitle}>Metadata</Text>
|
|
100
|
-
<
|
|
101
|
-
<
|
|
102
|
-
|
|
101
|
+
<View style={{flexDirection: 'row', alignItems: 'center', gap: 8}}>
|
|
102
|
+
<CopyButton
|
|
103
|
+
value={{
|
|
104
|
+
status: status ?? 0,
|
|
105
|
+
duration: duration != null ? `${duration} ms` : '—',
|
|
106
|
+
size: size || '—',
|
|
107
|
+
triggeredAt: triggeredAt || '',
|
|
108
|
+
method: method || 'GET',
|
|
109
|
+
contentType: contentType || 'application/json',
|
|
110
|
+
url: url || '',
|
|
111
|
+
}}
|
|
112
|
+
label="Metadata"
|
|
113
|
+
/>
|
|
114
|
+
<Animated.View style={chevronStyle}>
|
|
115
|
+
<ChevronIcon color={AppColors.grayTextWeak} size={14} />
|
|
116
|
+
</Animated.View>
|
|
117
|
+
</View>
|
|
103
118
|
</View>
|
|
104
119
|
</Pressable>
|
|
105
120
|
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import React, {useEffect, useRef} from 'react';
|
|
2
|
+
import {Animated, StyleSheet, View} from 'react-native';
|
|
3
|
+
import {AppColors} from '../styles/AppColors';
|
|
4
|
+
|
|
5
|
+
interface SkeletonPlaceholderProps {
|
|
6
|
+
cardCount?: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const SkeletonPlaceholder = React.memo(function SkeletonPlaceholder({
|
|
10
|
+
cardCount = 4,
|
|
11
|
+
}: SkeletonPlaceholderProps) {
|
|
12
|
+
const shimmerAnim = useRef(new Animated.Value(0.35)).current;
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const animation = Animated.loop(
|
|
16
|
+
Animated.sequence([
|
|
17
|
+
Animated.timing(shimmerAnim, {
|
|
18
|
+
toValue: 0.85,
|
|
19
|
+
duration: 750,
|
|
20
|
+
useNativeDriver: true,
|
|
21
|
+
}),
|
|
22
|
+
Animated.timing(shimmerAnim, {
|
|
23
|
+
toValue: 0.35,
|
|
24
|
+
duration: 750,
|
|
25
|
+
useNativeDriver: true,
|
|
26
|
+
}),
|
|
27
|
+
]),
|
|
28
|
+
);
|
|
29
|
+
animation.start();
|
|
30
|
+
return () => animation.stop();
|
|
31
|
+
}, [shimmerAnim]);
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<View style={skeletonStyles.container}>
|
|
35
|
+
{/* ─── Search & Scope Toolbar Skeleton ─── */}
|
|
36
|
+
<View style={skeletonStyles.toolbarSkeleton}>
|
|
37
|
+
<Animated.View
|
|
38
|
+
style={[
|
|
39
|
+
skeletonStyles.searchBarSkeleton,
|
|
40
|
+
{opacity: shimmerAnim},
|
|
41
|
+
]}
|
|
42
|
+
/>
|
|
43
|
+
<View style={skeletonStyles.actionButtonsRow}>
|
|
44
|
+
<Animated.View
|
|
45
|
+
style={[skeletonStyles.iconButtonSkeleton, {opacity: shimmerAnim}]}
|
|
46
|
+
/>
|
|
47
|
+
<Animated.View
|
|
48
|
+
style={[skeletonStyles.iconButtonSkeleton, {opacity: shimmerAnim}]}
|
|
49
|
+
/>
|
|
50
|
+
</View>
|
|
51
|
+
</View>
|
|
52
|
+
|
|
53
|
+
{/* ─── Quick Filter Chips Skeleton Strip ─── */}
|
|
54
|
+
<View style={skeletonStyles.chipStripSkeleton}>
|
|
55
|
+
<Animated.View
|
|
56
|
+
style={[skeletonStyles.chipSkeleton, {width: 48, opacity: shimmerAnim}]}
|
|
57
|
+
/>
|
|
58
|
+
<Animated.View
|
|
59
|
+
style={[skeletonStyles.chipSkeleton, {width: 68, opacity: shimmerAnim}]}
|
|
60
|
+
/>
|
|
61
|
+
<Animated.View
|
|
62
|
+
style={[skeletonStyles.chipSkeleton, {width: 76, opacity: shimmerAnim}]}
|
|
63
|
+
/>
|
|
64
|
+
<Animated.View
|
|
65
|
+
style={[skeletonStyles.chipSkeleton, {width: 58, opacity: shimmerAnim}]}
|
|
66
|
+
/>
|
|
67
|
+
</View>
|
|
68
|
+
|
|
69
|
+
{/* ─── List Cards Skeleton ─── */}
|
|
70
|
+
{Array.from({length: cardCount}).map((_, i) => (
|
|
71
|
+
<Animated.View
|
|
72
|
+
key={`skeleton_card_${i}`}
|
|
73
|
+
style={[skeletonStyles.cardSkeleton, {opacity: shimmerAnim}]}>
|
|
74
|
+
{/* Top row: Status pill + Method + Time */}
|
|
75
|
+
<View style={skeletonStyles.cardTopRow}>
|
|
76
|
+
<View style={skeletonStyles.badgeGroup}>
|
|
77
|
+
<View style={skeletonStyles.statusBadgeSkeleton} />
|
|
78
|
+
<View style={skeletonStyles.methodBadgeSkeleton} />
|
|
79
|
+
</View>
|
|
80
|
+
<View style={skeletonStyles.timeSkeleton} />
|
|
81
|
+
</View>
|
|
82
|
+
|
|
83
|
+
{/* Middle row: URL lines */}
|
|
84
|
+
<View style={skeletonStyles.urlLineLong} />
|
|
85
|
+
<View style={skeletonStyles.urlLineShort} />
|
|
86
|
+
|
|
87
|
+
{/* Bottom row: Latency & Size */}
|
|
88
|
+
<View style={skeletonStyles.cardBottomRow}>
|
|
89
|
+
<View style={skeletonStyles.metaPillSkeleton} />
|
|
90
|
+
<View style={skeletonStyles.metaPillSkeleton} />
|
|
91
|
+
</View>
|
|
92
|
+
</Animated.View>
|
|
93
|
+
))}
|
|
94
|
+
</View>
|
|
95
|
+
);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const skeletonStyles = StyleSheet.create({
|
|
99
|
+
container: {
|
|
100
|
+
flex: 1,
|
|
101
|
+
paddingHorizontal: 12,
|
|
102
|
+
paddingTop: 8,
|
|
103
|
+
},
|
|
104
|
+
toolbarSkeleton: {
|
|
105
|
+
flexDirection: 'row',
|
|
106
|
+
alignItems: 'center',
|
|
107
|
+
gap: 8,
|
|
108
|
+
marginBottom: 8,
|
|
109
|
+
},
|
|
110
|
+
searchBarSkeleton: {
|
|
111
|
+
flex: 1,
|
|
112
|
+
height: 36,
|
|
113
|
+
borderRadius: 8,
|
|
114
|
+
backgroundColor: AppColors.graySurface,
|
|
115
|
+
borderWidth: 1,
|
|
116
|
+
borderColor: AppColors.dividerColor,
|
|
117
|
+
},
|
|
118
|
+
actionButtonsRow: {
|
|
119
|
+
flexDirection: 'row',
|
|
120
|
+
gap: 6,
|
|
121
|
+
},
|
|
122
|
+
iconButtonSkeleton: {
|
|
123
|
+
width: 36,
|
|
124
|
+
height: 36,
|
|
125
|
+
borderRadius: 8,
|
|
126
|
+
backgroundColor: AppColors.graySurface,
|
|
127
|
+
borderWidth: 1,
|
|
128
|
+
borderColor: AppColors.dividerColor,
|
|
129
|
+
},
|
|
130
|
+
chipStripSkeleton: {
|
|
131
|
+
flexDirection: 'row',
|
|
132
|
+
gap: 6,
|
|
133
|
+
marginBottom: 10,
|
|
134
|
+
},
|
|
135
|
+
chipSkeleton: {
|
|
136
|
+
height: 24,
|
|
137
|
+
borderRadius: 6,
|
|
138
|
+
backgroundColor: AppColors.graySurface,
|
|
139
|
+
borderWidth: 1,
|
|
140
|
+
borderColor: AppColors.dividerColor,
|
|
141
|
+
},
|
|
142
|
+
cardSkeleton: {
|
|
143
|
+
backgroundColor: AppColors.primaryLight,
|
|
144
|
+
borderRadius: 10,
|
|
145
|
+
padding: 12,
|
|
146
|
+
marginBottom: 8,
|
|
147
|
+
borderWidth: 1,
|
|
148
|
+
borderColor: AppColors.dividerColor,
|
|
149
|
+
},
|
|
150
|
+
cardTopRow: {
|
|
151
|
+
flexDirection: 'row',
|
|
152
|
+
justifyContent: 'space-between',
|
|
153
|
+
alignItems: 'center',
|
|
154
|
+
marginBottom: 8,
|
|
155
|
+
},
|
|
156
|
+
badgeGroup: {
|
|
157
|
+
flexDirection: 'row',
|
|
158
|
+
alignItems: 'center',
|
|
159
|
+
gap: 6,
|
|
160
|
+
},
|
|
161
|
+
statusBadgeSkeleton: {
|
|
162
|
+
width: 38,
|
|
163
|
+
height: 18,
|
|
164
|
+
borderRadius: 4,
|
|
165
|
+
backgroundColor: AppColors.graySurface,
|
|
166
|
+
},
|
|
167
|
+
methodBadgeSkeleton: {
|
|
168
|
+
width: 44,
|
|
169
|
+
height: 18,
|
|
170
|
+
borderRadius: 4,
|
|
171
|
+
backgroundColor: AppColors.graySurface,
|
|
172
|
+
},
|
|
173
|
+
timeSkeleton: {
|
|
174
|
+
width: 48,
|
|
175
|
+
height: 12,
|
|
176
|
+
borderRadius: 4,
|
|
177
|
+
backgroundColor: AppColors.graySurface,
|
|
178
|
+
},
|
|
179
|
+
urlLineLong: {
|
|
180
|
+
height: 13,
|
|
181
|
+
borderRadius: 4,
|
|
182
|
+
backgroundColor: AppColors.graySurface,
|
|
183
|
+
marginBottom: 5,
|
|
184
|
+
width: '90%',
|
|
185
|
+
},
|
|
186
|
+
urlLineShort: {
|
|
187
|
+
height: 11,
|
|
188
|
+
borderRadius: 4,
|
|
189
|
+
backgroundColor: AppColors.graySurface,
|
|
190
|
+
marginBottom: 8,
|
|
191
|
+
width: '55%',
|
|
192
|
+
},
|
|
193
|
+
cardBottomRow: {
|
|
194
|
+
flexDirection: 'row',
|
|
195
|
+
gap: 8,
|
|
196
|
+
marginTop: 2,
|
|
197
|
+
},
|
|
198
|
+
metaPillSkeleton: {
|
|
199
|
+
width: 52,
|
|
200
|
+
height: 14,
|
|
201
|
+
borderRadius: 4,
|
|
202
|
+
backgroundColor: AppColors.graySurface,
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
export default SkeletonPlaceholder;
|
package/src/constants/version.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import axios from "axios";
|
|
2
2
|
import {setupGlobalCrashHandler} from "./crashHandler";
|
|
3
|
+
import {RouteInfo} from "../types";
|
|
3
4
|
|
|
4
5
|
type NetworkLog = {
|
|
5
6
|
id: number;
|
|
@@ -14,12 +15,20 @@ type NetworkLog = {
|
|
|
14
15
|
client?: string; // ✅ Captures request client: axios, fetch, xhr, apollo, etc.
|
|
15
16
|
requestHeaders?: Record<string, string>;
|
|
16
17
|
responseHeaders?: Record<string, string>;
|
|
18
|
+
routeInfo?: RouteInfo;
|
|
17
19
|
};
|
|
18
20
|
|
|
19
21
|
let logs: NetworkLog[] = [];
|
|
20
22
|
let listeners: ((logs: NetworkLog[]) => void)[] = [];
|
|
21
23
|
let counter = 0;
|
|
22
24
|
let isNetworkModuleEnabled = true;
|
|
25
|
+
let currentRouteProvider: (() => RouteInfo | null) | null = null;
|
|
26
|
+
|
|
27
|
+
export const setRouteInfoProvider = (
|
|
28
|
+
provider: (() => RouteInfo | null) | null,
|
|
29
|
+
) => {
|
|
30
|
+
currentRouteProvider = provider;
|
|
31
|
+
};
|
|
23
32
|
|
|
24
33
|
export const setNetworkModuleEnabled = (enabled: boolean) => {
|
|
25
34
|
isNetworkModuleEnabled = enabled;
|
|
@@ -232,6 +241,8 @@ export const setupNetworkLogger = () => {
|
|
|
232
241
|
client = 'axios';
|
|
233
242
|
}
|
|
234
243
|
|
|
244
|
+
const currentRoute = currentRouteProvider ? currentRouteProvider() : null;
|
|
245
|
+
|
|
235
246
|
addOrUpdateLog({
|
|
236
247
|
id,
|
|
237
248
|
url: finalUrl,
|
|
@@ -239,6 +250,7 @@ export const setupNetworkLogger = () => {
|
|
|
239
250
|
startTime: start,
|
|
240
251
|
caller,
|
|
241
252
|
client,
|
|
253
|
+
routeInfo: currentRoute || undefined,
|
|
242
254
|
request: method === "GET" ? undefined : parseRequestData(options?.body),
|
|
243
255
|
requestHeaders,
|
|
244
256
|
});
|
|
@@ -347,6 +359,8 @@ export const addAxiosInterceptors = (axiosInstance: any) => {
|
|
|
347
359
|
config.__logStart = start;
|
|
348
360
|
config.__logCaller = caller;
|
|
349
361
|
|
|
362
|
+
const currentRoute = currentRouteProvider ? currentRouteProvider() : null;
|
|
363
|
+
|
|
350
364
|
addOrUpdateLog({
|
|
351
365
|
id,
|
|
352
366
|
url,
|
|
@@ -354,6 +368,7 @@ export const addAxiosInterceptors = (axiosInstance: any) => {
|
|
|
354
368
|
startTime: start,
|
|
355
369
|
caller,
|
|
356
370
|
client: 'axios',
|
|
371
|
+
routeInfo: currentRoute || undefined,
|
|
357
372
|
request: method === "GET" ? undefined : parseRequestData(config.data),
|
|
358
373
|
requestHeaders: normaliseHeaders(config.headers),
|
|
359
374
|
});
|