react-native-inapp-inspector 2.3.14 → 2.3.15

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/AnimatedEntrance.js +3 -3
  2. package/dist/commonjs/components/ConsoleLogCard.js +174 -100
  3. package/dist/commonjs/components/Inspector/ConsoleTab.js +25 -16
  4. package/dist/commonjs/components/Inspector/CrashTab.js +19 -5
  5. package/dist/commonjs/components/Inspector/DeviceInfoTab.js +198 -98
  6. package/dist/commonjs/components/Inspector/InspectorHeader.js +2 -2
  7. package/dist/commonjs/components/Inspector/MainScreen.js +56 -56
  8. package/dist/commonjs/components/Inspector/NetworkTab.js +1 -335
  9. package/dist/commonjs/components/Inspector/PerformanceTab.js +32 -6
  10. package/dist/commonjs/components/Inspector/ReduxTab.js +3 -1
  11. package/dist/commonjs/components/Inspector/StorageTab.js +23 -5
  12. package/dist/commonjs/components/JsonViewer.js +14 -2
  13. package/dist/commonjs/components/LogCard.js +428 -227
  14. package/dist/commonjs/constants/version.d.ts +1 -1
  15. package/dist/commonjs/constants/version.js +1 -1
  16. package/dist/commonjs/customHooks/analyticsLogger.js +1 -1
  17. package/dist/commonjs/customHooks/consoleLogger.js +34 -2
  18. package/dist/commonjs/customHooks/crashHandler.js +1 -1
  19. package/dist/commonjs/customHooks/networkLogger.js +184 -26
  20. package/dist/commonjs/helpers/searchQueryParser.d.ts +1 -1
  21. package/dist/commonjs/helpers/searchQueryParser.js +37 -291
  22. package/dist/commonjs/index.js +141 -27
  23. package/dist/esm/components/AnimatedEntrance.js +3 -3
  24. package/dist/esm/components/ConsoleLogCard.js +141 -100
  25. package/dist/esm/components/Inspector/ConsoleTab.js +25 -16
  26. package/dist/esm/components/Inspector/CrashTab.js +19 -5
  27. package/dist/esm/components/Inspector/DeviceInfoTab.js +198 -98
  28. package/dist/esm/components/Inspector/InspectorHeader.js +3 -3
  29. package/dist/esm/components/Inspector/MainScreen.js +56 -56
  30. package/dist/esm/components/Inspector/NetworkTab.js +2 -336
  31. package/dist/esm/components/Inspector/PerformanceTab.js +32 -6
  32. package/dist/esm/components/Inspector/ReduxTab.js +3 -1
  33. package/dist/esm/components/Inspector/StorageTab.js +23 -5
  34. package/dist/esm/components/JsonViewer.js +14 -2
  35. package/dist/esm/components/LogCard.js +422 -221
  36. package/dist/esm/constants/version.d.ts +1 -1
  37. package/dist/esm/constants/version.js +1 -1
  38. package/dist/esm/customHooks/analyticsLogger.js +1 -1
  39. package/dist/esm/customHooks/consoleLogger.js +34 -2
  40. package/dist/esm/customHooks/crashHandler.js +1 -1
  41. package/dist/esm/customHooks/networkLogger.js +184 -26
  42. package/dist/esm/helpers/searchQueryParser.d.ts +1 -1
  43. package/dist/esm/helpers/searchQueryParser.js +37 -291
  44. package/dist/esm/index.js +141 -27
  45. package/package.json +1 -1
  46. package/src/components/AnimatedEntrance.tsx +3 -3
  47. package/src/components/ConsoleLogCard.tsx +154 -103
  48. package/src/components/Inspector/ConsoleTab.tsx +27 -18
  49. package/src/components/Inspector/CrashTab.tsx +19 -5
  50. package/src/components/Inspector/DeviceInfoTab.tsx +224 -102
  51. package/src/components/Inspector/InspectorHeader.tsx +5 -3
  52. package/src/components/Inspector/MainScreen.tsx +2 -3
  53. package/src/components/Inspector/NetworkTab.tsx +1 -402
  54. package/src/components/Inspector/PerformanceTab.tsx +36 -7
  55. package/src/components/Inspector/ReduxTab.tsx +9 -1
  56. package/src/components/Inspector/StorageTab.tsx +27 -7
  57. package/src/components/JsonViewer.tsx +15 -2
  58. package/src/components/LogCard.tsx +528 -339
  59. package/src/constants/version.ts +1 -1
  60. package/src/customHooks/analyticsLogger.ts +1 -1
  61. package/src/customHooks/consoleLogger.ts +36 -3
  62. package/src/customHooks/crashHandler.ts +1 -1
  63. package/src/customHooks/networkLogger.ts +214 -26
  64. package/src/helpers/searchQueryParser.ts +40 -285
  65. package/src/index.tsx +332 -211
@@ -1,18 +1,32 @@
1
- import React, { useEffect, useRef } from 'react';
1
+ import React, { useEffect, useRef, useMemo } from 'react';
2
2
  import { Alert, Animated, Linking, View, Pressable, Text, StyleSheet, } from 'react-native';
3
3
  import Svg, { Path } from 'react-native-svg';
4
4
  import { AppColors } from '../styles/AppColors';
5
5
  import { METHOD_COLORS } from '../constants';
6
- import { getStatusColor, getDurationColor, formatDateTime, getSize, } from '../helpers';
7
- import { CalendarIcon, ClockIcon, SizeIcon, GlobeIcon, CircleCheckIcon, CircleAlertIcon, CircleXIcon, RepeatIcon, PinIcon, } from './NetworkIcons';
6
+ import { getStatusColor, getDurationColor, formatDateTime, getSize, getPath, getBaseUrl, } from '../helpers';
7
+ import { CalendarIcon, ClockIcon, SizeIcon, CircleCheckIcon, CircleAlertIcon, CircleXIcon, RepeatIcon, PinIcon, } from './NetworkIcons';
8
8
  import { AppFonts } from '../styles/AppFonts';
9
- import styles from '../styles';
10
9
  import HighlightText from './HighlightText';
11
10
  import TouchableScale from './TouchableScale';
12
11
  import { useTranslation } from '../i18n';
13
12
  const LogCard = React.memo(function LogCard({ item, onPress, timelineMinStart, timelineTotalRange, isNew, isSelected, onToggleSelect, searchStr, }) {
14
13
  const { t } = useTranslation();
15
14
  const methodColor = METHOD_COLORS[item.method] ?? METHOD_COLORS.ALL;
15
+ const urlParsed = useMemo(() => {
16
+ try {
17
+ const u = new URL(item.url);
18
+ const host = u.host;
19
+ const path = u.pathname + (u.search ? u.search : '');
20
+ const isHttps = u.protocol === 'https:';
21
+ return { host, path: path || '/', isHttps };
22
+ }
23
+ catch {
24
+ const isHttps = item.url.toLowerCase().startsWith('https:');
25
+ const path = getPath(item.url);
26
+ const host = getBaseUrl(item.url).replace(/^https?:\/\//, '');
27
+ return { host, path: path || item.url, isHttps };
28
+ }
29
+ }, [item.url]);
16
30
  const handleOpenUrl = (e) => {
17
31
  e?.stopPropagation?.();
18
32
  Alert.alert(t('common.openInBrowser') || 'Open in Browser', `${t('common.openInBrowserPrompt') || 'Are you sure you want to open this URL in your external browser?'}\n\n${item.url}`, [
@@ -43,12 +57,6 @@ const LogCard = React.memo(function LogCard({ item, onPress, timelineMinStart, t
43
57
  : isFailed
44
58
  ? AppColors.errorColor
45
59
  : AppColors.greenColor;
46
- const leftPercent = timelineTotalRange > 0
47
- ? ((item.startTime - timelineMinStart) / timelineTotalRange) * 100
48
- : 0;
49
- const widthPercent = timelineTotalRange > 0
50
- ? ((item.duration || 10) / timelineTotalRange) * 100
51
- : 100;
52
60
  const shimmerOpacity = useRef(new Animated.Value(isNew ? 0.35 : 0)).current;
53
61
  useEffect(() => {
54
62
  if (isNew) {
@@ -59,53 +67,82 @@ const LogCard = React.memo(function LogCard({ item, onPress, timelineMinStart, t
59
67
  }).start();
60
68
  }
61
69
  }, [isNew]);
70
+ const getStatusText = () => {
71
+ if (isLoading)
72
+ return '...';
73
+ if (item.status === 0 || item.status == null) {
74
+ return t('network.statusFailed') || 'FAILED';
75
+ }
76
+ const num = typeof item.status === 'number' ? item.status : parseInt(String(item.status || 0), 10);
77
+ if (num === 200)
78
+ return '200 OK';
79
+ if (num === 201)
80
+ return '201 Created';
81
+ if (num === 204)
82
+ return '204 No Content';
83
+ if (num === 304)
84
+ return '304 Not Modified';
85
+ if (num === 400)
86
+ return '400 Bad Req';
87
+ if (num === 401)
88
+ return '401 Unauth';
89
+ if (num === 403)
90
+ return '403 Forbidden';
91
+ if (num === 404)
92
+ return '404 Not Found';
93
+ if (num === 500)
94
+ return '500 Error';
95
+ return String(item.status);
96
+ };
62
97
  const renderStatusIcon = () => {
63
98
  if (isLoading) {
64
- return <ClockIcon color={AppColors.darkOrange} size={9}/>;
99
+ return <ClockIcon color={AppColors.darkOrange} size={10}/>;
65
100
  }
66
101
  const num = typeof item.status === 'number' ? item.status : parseInt(String(item.status || 0), 10);
67
102
  if (num >= 200 && num < 300) {
68
- return <CircleCheckIcon color={AppColors.greenColor} size={9}/>;
103
+ return <CircleCheckIcon color={AppColors.greenColor} size={10}/>;
69
104
  }
70
105
  if (num >= 300 && num < 400) {
71
- return <RepeatIcon color={AppColors.amber700} size={9}/>;
106
+ return <RepeatIcon color={AppColors.amber700} size={10}/>;
72
107
  }
73
108
  if (num >= 400 && num < 500) {
74
- return <CircleAlertIcon color={AppColors.darkOrange} size={9}/>;
109
+ return <CircleAlertIcon color={AppColors.darkOrange} size={10}/>;
75
110
  }
76
- return <CircleXIcon color={AppColors.errorColor} size={9}/>;
111
+ return <CircleXIcon color={AppColors.errorColor} size={10}/>;
77
112
  };
78
113
  const triggeredAt = formatDateTime(item.startTime);
79
114
  const isJson = item.url.split('?')[0].toLowerCase().endsWith('.json');
80
- return (<TouchableScale onPress={onPress} style={[
115
+ return (<View style={styles.container}>
116
+ <TouchableScale onPress={onPress} style={[
81
117
  styles.card,
82
118
  {
83
119
  borderLeftWidth: 3.5,
84
120
  borderLeftColor: cardStatusColor,
121
+ backgroundColor: isFailed ? '#FFF8F8' : AppColors.white,
85
122
  },
86
123
  ]}>
87
- <View style={styles.cardBody}>
88
-
89
- <View style={styles.cardHeaderRow}>
90
- <View style={styles.cardHeaderLeft}>
91
- <Pressable onPress={() => onToggleSelect(item.id)} hitSlop={12} style={[
124
+ <View style={styles.cardBody}>
125
+
126
+ <View style={styles.cardHeaderRow}>
127
+ <View style={styles.cardHeaderLeft}>
128
+ <Pressable onPress={() => onToggleSelect(item.id)} hitSlop={12} style={[
92
129
  styles.smallCheckbox,
93
130
  isSelected && styles.smallCheckboxChecked,
94
131
  ]}>
95
- {isSelected && (<Svg width={9} height={9} viewBox="0 0 24 24" fill="none">
96
- <Path d="M20 6L9 17l-5-5" stroke={AppColors.white} strokeWidth="4" strokeLinecap="round" strokeLinejoin="round"/>
97
- </Svg>)}
98
- </Pressable>
132
+ {isSelected && (<Svg width={9} height={9} viewBox="0 0 24 24" fill="none">
133
+ <Path d="M20 6L9 17l-5-5" stroke={AppColors.white} strokeWidth="4" strokeLinecap="round" strokeLinejoin="round"/>
134
+ </Svg>)}
135
+ </Pressable>
99
136
 
100
- <Text style={styles.serialNumber}>#{item.id + 1}</Text>
137
+ <Text style={styles.serialNumber}>#{item.id + 1}</Text>
101
138
 
102
- <View style={[styles.methodBadge, { backgroundColor: methodColor }]}>
103
- <Text style={[styles.methodBadgeText, { color: AppColors.white }]}>
104
- {item.method}
105
- </Text>
106
- </View>
139
+ <View style={[styles.methodBadge, { backgroundColor: methodColor }]}>
140
+ <Text style={styles.methodBadgeText}>
141
+ {item.method}
142
+ </Text>
143
+ </View>
107
144
 
108
- {item.client && (<View style={[
145
+ {item.client && (<View style={[
109
146
  styles.chip,
110
147
  {
111
148
  backgroundColor: item.client === 'axios'
@@ -122,16 +159,11 @@ const LogCard = React.memo(function LogCard({ item, onPress, timelineMinStart, t
122
159
  : item.client === 'xhr'
123
160
  ? `${AppColors.amber500}30`
124
161
  : `${AppColors.sky500}30`,
125
- paddingHorizontal: 4.5,
126
- paddingVertical: 1,
127
- borderRadius: 4,
128
162
  },
129
163
  ]}>
130
- <Text style={[
164
+ <Text style={[
131
165
  styles.chipText,
132
166
  {
133
- fontFamily: AppFonts.interBold,
134
- fontSize: 8.5,
135
167
  color: item.client === 'axios'
136
168
  ? AppColors.purple
137
169
  : item.client === 'apollo' || item.client === 'graphql'
@@ -141,86 +173,46 @@ const LogCard = React.memo(function LogCard({ item, onPress, timelineMinStart, t
141
173
  : AppColors.sky600,
142
174
  },
143
175
  ]}>
144
- {item.client.toUpperCase()}
145
- </Text>
146
- </View>)}
176
+ {item.client.toUpperCase()}
177
+ </Text>
178
+ </View>)}
147
179
 
148
-
149
- <View style={[
150
- styles.chip,
151
- {
152
- backgroundColor: item.url.toLowerCase().startsWith('https')
153
- ? AppColors.mintGreenBg
154
- : AppColors.amberWarmBg,
155
- borderColor: item.url.toLowerCase().startsWith('https')
156
- ? AppColors.mintGreenBorder
157
- : AppColors.amberWarmBorder,
158
- paddingHorizontal: 4,
159
- paddingVertical: 1,
160
- borderRadius: 4,
161
- },
162
- ]}>
163
- <Text style={[
164
- styles.chipText,
165
- {
166
- fontFamily: AppFonts.interBold,
167
- fontSize: 8,
168
- color: item.url.toLowerCase().startsWith('https')
169
- ? AppColors.mintGreenText
170
- : AppColors.amber700,
171
- },
172
- ]}>
173
- {item.url.toLowerCase().startsWith('https') ? 'HTTPS' : 'HTTP'}
174
- </Text>
175
- </View>
176
-
177
-
178
- {(item.url.toLowerCase().includes('graphql') ||
180
+
181
+ {(item.url.toLowerCase().includes('graphql') ||
179
182
  item.client === 'apollo' ||
180
183
  item.client === 'graphql') && (<View style={[
181
184
  styles.chip,
182
185
  {
183
186
  backgroundColor: AppColors.roseBg,
184
187
  borderColor: AppColors.roseBorder,
185
- paddingHorizontal: 4,
186
- paddingVertical: 1,
187
- borderRadius: 4,
188
188
  },
189
189
  ]}>
190
- <Text style={[
190
+ <Text style={[
191
191
  styles.chipText,
192
- {
193
- fontFamily: AppFonts.interBold,
194
- fontSize: 8,
195
- color: AppColors.pink600,
196
- },
192
+ { color: AppColors.pink600 },
197
193
  ]}>
198
- GQL
199
- </Text>
200
- </View>)}
201
- </View>
194
+ GQL
195
+ </Text>
196
+ </View>)}
197
+ </View>
202
198
 
203
- <View style={[
199
+ <View style={[
204
200
  styles.statusPill,
205
201
  {
206
- flexDirection: 'row',
207
- alignItems: 'center',
208
- gap: 3.5,
209
202
  backgroundColor: isFailed
210
- ? `${AppColors.errorColor}15`
203
+ ? `${AppColors.errorColor}14`
211
204
  : isLoading
212
- ? `${AppColors.darkOrange}15`
213
- : `${statusColor}15`,
205
+ ? `${AppColors.darkOrange}14`
206
+ : `${statusColor}14`,
214
207
  borderColor: isFailed
215
- ? `${AppColors.errorColor}30`
208
+ ? `${AppColors.errorColor}33`
216
209
  : isLoading
217
- ? `${AppColors.darkOrange}30`
218
- : `${statusColor}30`,
219
- flexShrink: 0,
210
+ ? `${AppColors.darkOrange}33`
211
+ : `${statusColor}33`,
220
212
  },
221
213
  ]}>
222
- {renderStatusIcon()}
223
- <Text style={[
214
+ {renderStatusIcon()}
215
+ <Text style={[
224
216
  styles.statusPillText,
225
217
  {
226
218
  color: isFailed
@@ -230,154 +222,363 @@ const LogCard = React.memo(function LogCard({ item, onPress, timelineMinStart, t
230
222
  : statusColor,
231
223
  },
232
224
  ]}>
233
- {isLoading
234
- ? '...'
235
- : isFailed
236
- ? (item.status ? `${item.status}` : t('network.statusFailed'))
237
- : item.status}
238
- </Text>
225
+ {getStatusText()}
226
+ </Text>
227
+ </View>
239
228
  </View>
240
- </View>
241
229
 
242
-
243
- <View style={styles.cardSlugBox}>
244
- <Pressable onPress={handleOpenUrl} style={styles.slugLeft} hitSlop={6}>
245
- <View style={{
246
- width: 17,
247
- height: 17,
248
- borderRadius: 4,
249
- backgroundColor: `${AppColors.skyBlue}1A`,
250
- alignItems: 'center',
251
- justifyContent: 'center',
252
- marginRight: 4,
253
- flexShrink: 0,
254
- }}>
255
- <GlobeIcon color={AppColors.skyBlue} size={11}/>
230
+
231
+ <Pressable onPress={handleOpenUrl} hitSlop={6} style={styles.urlBox}>
232
+ <View style={styles.urlMainRow}>
233
+ <HighlightText text={urlParsed.path} search={searchStr} style={styles.pathText} highlightStyle={styles.highlight} numberOfLines={2} ellipsizeMode="middle"/>
234
+ <View style={styles.urlBadgeRow}>
235
+ {isJson && (<View style={styles.jsonBadge}>
236
+ <Text style={styles.jsonBadgeText}>.json</Text>
237
+ </View>)}
238
+ {item.duplicateCount != null && item.duplicateCount > 1 && (<View style={styles.dupBadge}>
239
+ <Text style={styles.dupBadgeText}>×{item.duplicateCount}</Text>
240
+ </View>)}
241
+ </View>
256
242
  </View>
257
- <HighlightText text={item.url} search={searchStr} style={[styles.slugText, { color: AppColors.skyBlue, textDecorationLine: 'underline' }]} highlightStyle={styles.highlight} numberOfLines={2} ellipsizeMode="middle"/>
258
- </Pressable>
259
243
 
260
- <View style={styles.slugRight}>
261
- {isJson && (<View style={[
262
- styles.chip,
244
+ {urlParsed.host ? (<View style={styles.hostRow}>
245
+ <View style={[
246
+ styles.protoBadge,
263
247
  {
264
- backgroundColor: `${AppColors.darkOrange}12`,
265
- borderColor: `${AppColors.darkOrange}28`,
248
+ backgroundColor: urlParsed.isHttps
249
+ ? `${AppColors.emerald600}12`
250
+ : `${AppColors.amber600}12`,
251
+ borderColor: urlParsed.isHttps
252
+ ? `${AppColors.emerald600}2B`
253
+ : `${AppColors.amber600}2B`,
266
254
  },
267
255
  ]}>
268
- <Text style={[styles.chipText, { color: AppColors.darkOrange }]}>
269
- .json
270
- </Text>
271
- </View>)}
272
-
273
- {item.duplicateCount != null && item.duplicateCount > 1 && (<View style={[
274
- styles.chip,
256
+ <Text style={[
257
+ styles.protoBadgeText,
275
258
  {
276
- backgroundColor: `${AppColors.purple}12`,
277
- borderColor: `${AppColors.purple}28`,
259
+ color: urlParsed.isHttps
260
+ ? AppColors.emerald600
261
+ : AppColors.amber700,
278
262
  },
279
263
  ]}>
280
- <Text style={[
281
- styles.chipText,
282
- { color: AppColors.purple, fontWeight: '700' },
283
- ]}>
284
- ×{item.duplicateCount}
264
+ {urlParsed.isHttps ? 'HTTPS' : 'HTTP'}
265
+ </Text>
266
+ </View>
267
+ <Text style={styles.hostText} numberOfLines={1} ellipsizeMode="tail">
268
+ {urlParsed.host}
285
269
  </Text>
286
- </View>)}
287
- </View>
288
- </View>
270
+ </View>) : null}
271
+ </Pressable>
272
+
273
+
274
+ <View style={styles.cardFooterRow}>
275
+ <View style={styles.footerLeft}>
276
+ <View style={styles.cardDateRow}>
277
+ <CalendarIcon color={AppColors.grayTextWeak} size={10}/>
278
+ <Text style={styles.cardDateText}>{triggeredAt}</Text>
279
+ </View>
289
280
 
290
-
291
- <View style={styles.cardFooterRow}>
292
- <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6, flex: 1, minWidth: 0, marginRight: 8 }}>
293
- <View style={[styles.cardDateRow, { flexShrink: 0 }]}>
294
- <CalendarIcon color={AppColors.grayTextWeak} size={10}/>
295
- <Text style={styles.cardDateText}>{triggeredAt}</Text>
281
+ {item.caller && item.caller !== 'Unknown' && (<View style={styles.callerChip}>
282
+ <PinIcon color={AppColors.sky600} size={8.5}/>
283
+ <Text style={styles.callerChipText} numberOfLines={1} ellipsizeMode="middle">
284
+ {item.caller}
285
+ </Text>
286
+ </View>)}
296
287
  </View>
297
288
 
298
- {item.caller && item.caller !== 'Unknown' && (<View style={[
299
- styles.chip,
300
- {
301
- flexDirection: 'row',
302
- alignItems: 'center',
303
- gap: 3,
304
- backgroundColor: `${AppColors.skyBlue}14`,
305
- borderColor: `${AppColors.skyBlue}30`,
306
- paddingHorizontal: 5,
307
- paddingVertical: 1,
308
- borderRadius: 4,
309
- flexShrink: 1,
310
- minWidth: 0,
311
- },
312
- ]}>
313
- <PinIcon color={AppColors.skyBlue} size={8.5}/>
314
- <Text style={[
315
- styles.chipText,
316
- {
317
- color: AppColors.skyBlue,
318
- fontSize: 8.5,
319
- fontFamily: AppFonts.interMedium,
320
- },
321
- ]} numberOfLines={1} ellipsizeMode="middle">
322
- {item.caller}
323
- </Text>
324
- </View>)}
325
- </View>
289
+ <View style={styles.footerRight}>
290
+ {item.response != null && (<View style={styles.metaStatChip}>
291
+ <SizeIcon color={AppColors.purple} size={9}/>
292
+ <Text style={styles.metaStatText}>
293
+ {getSize(item.response)}
294
+ </Text>
295
+ </View>)}
326
296
 
327
- <View style={styles.cardFooterRight}>
328
- {item.response != null && (<View style={[
329
- styles.chip,
297
+ {item.duration != null && !isFailed && (<View style={[
298
+ styles.metaStatChip,
330
299
  {
331
- backgroundColor: `${AppColors.purple}12`,
332
- borderColor: `${AppColors.purple}2E`,
300
+ backgroundColor: `${durationColor}12`,
301
+ borderColor: `${durationColor}2E`,
333
302
  },
334
303
  ]}>
335
- <SizeIcon color={AppColors.purple} size={9}/>
336
- <Text style={[
337
- styles.chipText,
338
- { color: AppColors.purple, fontSize: 9.5 },
339
- ]}>
340
- {getSize(item.response)}
341
- </Text>
342
- </View>)}
304
+ <ClockIcon color={durationColor} size={9}/>
305
+ <Text style={[styles.metaStatText, { color: durationColor }]}>
306
+ {item.duration}ms
307
+ </Text>
308
+ </View>)}
309
+ </View>
310
+ </View>
343
311
 
344
- {item.duration != null && !isFailed && (<View style={[
345
- styles.chip,
312
+
313
+ {typeof item.duration === 'number' && item.duration > 0 && !isFailed && (<View style={styles.waterfallContainer}>
314
+ <View style={[
315
+ styles.waterfallBar,
346
316
  {
347
- backgroundColor: `${durationColor}15`,
348
- borderColor: `${durationColor}30`,
317
+ width: `${Math.min(100, Math.max(6, (item.duration / 1200) * 100))}%`,
318
+ backgroundColor: durationColor,
349
319
  },
350
- ]}>
351
- <ClockIcon color={durationColor} size={9}/>
352
- <Text style={[styles.chipText, { color: durationColor, fontSize: 9.5 }]}>
353
- {item.duration}ms
354
- </Text>
355
- </View>)}
356
- </View>
320
+ ]}/>
321
+ </View>)}
357
322
  </View>
358
323
 
359
-
360
- {typeof item.duration === 'number' && item.duration > 0 && !isFailed && (<View style={{ marginTop: 6, gap: 2 }}>
361
- <View style={{
362
- height: 2.5,
363
- width: '100%',
364
- backgroundColor: AppColors.grayBorderSecondary,
365
- borderRadius: 1.5,
366
- overflow: 'hidden',
367
- }}>
368
- <View style={{
369
- height: '100%',
370
- width: `${Math.min(100, Math.max(6, (item.duration / 1200) * 100))}%`,
371
- backgroundColor: durationColor,
372
- borderRadius: 1.5,
373
- }}/>
374
- </View>
375
- </View>)}
376
- </View>
377
- <Animated.View pointerEvents="none" style={[
324
+ <Animated.View pointerEvents="none" style={[
378
325
  StyleSheet.absoluteFill,
379
326
  { backgroundColor: methodColor, opacity: shimmerOpacity },
380
327
  ]}/>
381
- </TouchableScale>);
328
+ </TouchableScale>
329
+ </View>);
330
+ });
331
+ const styles = StyleSheet.create({
332
+ container: {
333
+ paddingHorizontal: 10,
334
+ paddingVertical: 3.5,
335
+ },
336
+ card: {
337
+ alignSelf: 'stretch',
338
+ borderRadius: 11,
339
+ overflow: 'hidden',
340
+ shadowColor: AppColors.black,
341
+ shadowOffset: { width: 0, height: 1.5 },
342
+ shadowOpacity: 0.04,
343
+ shadowRadius: 3,
344
+ elevation: 1.5,
345
+ borderWidth: 1,
346
+ borderColor: AppColors.grayBorderSecondary,
347
+ backgroundColor: AppColors.white,
348
+ },
349
+ cardBody: {
350
+ paddingHorizontal: 12,
351
+ paddingTop: 9,
352
+ paddingBottom: 8,
353
+ },
354
+ cardHeaderRow: {
355
+ flexDirection: 'row',
356
+ alignItems: 'center',
357
+ justifyContent: 'space-between',
358
+ marginBottom: 6,
359
+ minHeight: 22,
360
+ },
361
+ cardHeaderLeft: {
362
+ flexDirection: 'row',
363
+ alignItems: 'center',
364
+ flex: 1,
365
+ marginRight: 6,
366
+ gap: 5.5,
367
+ minWidth: 0,
368
+ flexWrap: 'wrap',
369
+ },
370
+ smallCheckbox: {
371
+ width: 15,
372
+ height: 15,
373
+ borderRadius: 4,
374
+ borderWidth: 1.6,
375
+ borderColor: AppColors.grayTextWeak,
376
+ alignItems: 'center',
377
+ justifyContent: 'center',
378
+ backgroundColor: AppColors.white,
379
+ },
380
+ smallCheckboxChecked: {
381
+ backgroundColor: AppColors.purple,
382
+ borderColor: AppColors.purple,
383
+ },
384
+ serialNumber: {
385
+ fontFamily: AppFonts.interBold,
386
+ color: AppColors.grayTextWeak,
387
+ fontSize: 10,
388
+ },
389
+ methodBadge: {
390
+ paddingHorizontal: 6.5,
391
+ paddingVertical: 2,
392
+ borderRadius: 5,
393
+ alignItems: 'center',
394
+ justifyContent: 'center',
395
+ },
396
+ methodBadgeText: {
397
+ fontFamily: AppFonts.interBold,
398
+ fontSize: 9.5,
399
+ letterSpacing: 0.5,
400
+ color: AppColors.white,
401
+ },
402
+ chip: {
403
+ paddingHorizontal: 5,
404
+ paddingVertical: 1.5,
405
+ borderRadius: 4,
406
+ borderWidth: 1,
407
+ },
408
+ chipText: {
409
+ fontFamily: AppFonts.interBold,
410
+ fontSize: 8.5,
411
+ },
412
+ statusPill: {
413
+ flexDirection: 'row',
414
+ alignItems: 'center',
415
+ gap: 4,
416
+ paddingHorizontal: 7,
417
+ paddingVertical: 2.5,
418
+ borderRadius: 6,
419
+ borderWidth: 1,
420
+ flexShrink: 0,
421
+ },
422
+ statusPillText: {
423
+ fontFamily: AppFonts.interBold,
424
+ fontSize: 9.5,
425
+ },
426
+ urlBox: {
427
+ backgroundColor: AppColors.grayBackground,
428
+ borderRadius: 8,
429
+ borderWidth: 1,
430
+ borderColor: AppColors.dividerColor,
431
+ paddingHorizontal: 9,
432
+ paddingVertical: 6,
433
+ marginVertical: 4,
434
+ gap: 4,
435
+ },
436
+ urlMainRow: {
437
+ flexDirection: 'row',
438
+ alignItems: 'flex-start',
439
+ justifyContent: 'space-between',
440
+ gap: 6,
441
+ },
442
+ pathText: {
443
+ fontFamily: AppFonts.interBold,
444
+ fontSize: 12,
445
+ lineHeight: 16.5,
446
+ color: AppColors.primaryBlack,
447
+ flex: 1,
448
+ },
449
+ urlBadgeRow: {
450
+ flexDirection: 'row',
451
+ alignItems: 'center',
452
+ gap: 4,
453
+ flexShrink: 0,
454
+ },
455
+ jsonBadge: {
456
+ backgroundColor: `${AppColors.darkOrange}14`,
457
+ borderColor: `${AppColors.darkOrange}2E`,
458
+ borderWidth: 1,
459
+ paddingHorizontal: 4,
460
+ paddingVertical: 1,
461
+ borderRadius: 3,
462
+ },
463
+ jsonBadgeText: {
464
+ fontFamily: AppFonts.interBold,
465
+ fontSize: 8.5,
466
+ color: AppColors.darkOrange,
467
+ },
468
+ dupBadge: {
469
+ backgroundColor: `${AppColors.purple}14`,
470
+ borderColor: `${AppColors.purple}2E`,
471
+ borderWidth: 1,
472
+ paddingHorizontal: 4,
473
+ paddingVertical: 1,
474
+ borderRadius: 3,
475
+ },
476
+ dupBadgeText: {
477
+ fontFamily: AppFonts.interBold,
478
+ fontSize: 8.5,
479
+ color: AppColors.purple,
480
+ },
481
+ hostRow: {
482
+ flexDirection: 'row',
483
+ alignItems: 'center',
484
+ gap: 5,
485
+ },
486
+ protoBadge: {
487
+ paddingHorizontal: 4,
488
+ paddingVertical: 1,
489
+ borderRadius: 3,
490
+ borderWidth: 1,
491
+ },
492
+ protoBadgeText: {
493
+ fontFamily: AppFonts.interBold,
494
+ fontSize: 7.5,
495
+ },
496
+ hostText: {
497
+ fontFamily: AppFonts.interMedium,
498
+ fontSize: 10.5,
499
+ color: AppColors.grayText,
500
+ flex: 1,
501
+ },
502
+ highlight: {
503
+ backgroundColor: AppColors.yellowHighlight,
504
+ color: AppColors.primaryBlack,
505
+ borderRadius: 2,
506
+ },
507
+ cardFooterRow: {
508
+ flexDirection: 'row',
509
+ alignItems: 'center',
510
+ justifyContent: 'space-between',
511
+ marginTop: 4,
512
+ gap: 6,
513
+ },
514
+ footerLeft: {
515
+ flexDirection: 'row',
516
+ alignItems: 'center',
517
+ gap: 6,
518
+ flex: 1,
519
+ minWidth: 0,
520
+ },
521
+ footerRight: {
522
+ flexDirection: 'row',
523
+ alignItems: 'center',
524
+ gap: 5,
525
+ flexShrink: 0,
526
+ },
527
+ cardDateRow: {
528
+ flexDirection: 'row',
529
+ alignItems: 'center',
530
+ gap: 3.5,
531
+ },
532
+ cardDateText: {
533
+ fontFamily: AppFonts.interRegular,
534
+ fontSize: 9.5,
535
+ color: AppColors.grayTextWeak,
536
+ },
537
+ callerChip: {
538
+ flexDirection: 'row',
539
+ alignItems: 'center',
540
+ gap: 3,
541
+ backgroundColor: `${AppColors.sky600}10`,
542
+ borderColor: `${AppColors.sky600}2B`,
543
+ borderWidth: 1,
544
+ paddingHorizontal: 5,
545
+ paddingVertical: 1,
546
+ borderRadius: 4,
547
+ flexShrink: 1,
548
+ minWidth: 0,
549
+ },
550
+ callerChipText: {
551
+ color: AppColors.sky600,
552
+ fontSize: 8.5,
553
+ fontFamily: AppFonts.interBold,
554
+ },
555
+ metaStatChip: {
556
+ flexDirection: 'row',
557
+ alignItems: 'center',
558
+ gap: 3,
559
+ backgroundColor: `${AppColors.purple}10`,
560
+ borderColor: `${AppColors.purple}28`,
561
+ borderWidth: 1,
562
+ paddingHorizontal: 5,
563
+ paddingVertical: 1.5,
564
+ borderRadius: 4,
565
+ },
566
+ metaStatText: {
567
+ fontFamily: AppFonts.interBold,
568
+ color: AppColors.purple,
569
+ fontSize: 9,
570
+ },
571
+ waterfallContainer: {
572
+ height: 2.5,
573
+ width: '100%',
574
+ backgroundColor: AppColors.grayBorderSecondary,
575
+ borderRadius: 1.5,
576
+ overflow: 'hidden',
577
+ marginTop: 6,
578
+ },
579
+ waterfallBar: {
580
+ height: '100%',
581
+ borderRadius: 1.5,
582
+ },
382
583
  });
383
584
  export default LogCard;