react-native-inapp-inspector 1.1.28 → 1.1.31

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 (77) hide show
  1. package/android/src/main/java/com/inappinspector/NetworkInspectorModule.java +29 -80
  2. package/dist/commonjs/components/AnalyticsDetail.js +668 -108
  3. package/dist/commonjs/components/AppHeaderLogo.js +15 -15
  4. package/dist/commonjs/components/ConsoleLogCard.js +112 -66
  5. package/dist/commonjs/components/CopyButton.js +15 -1
  6. package/dist/commonjs/components/DomainHeader.js +64 -32
  7. package/dist/commonjs/components/HighlightText.js +7 -1
  8. package/dist/commonjs/components/Inspector/AnalyticsTab.js +332 -221
  9. package/dist/commonjs/components/Inspector/ConsoleTab.js +162 -56
  10. package/dist/commonjs/components/Inspector/InspectorHeader.js +70 -119
  11. package/dist/commonjs/components/Inspector/LogDetail.js +53 -17
  12. package/dist/commonjs/components/Inspector/MainScreen.js +4 -0
  13. package/dist/commonjs/components/Inspector/NetworkDetail.js +24 -24
  14. package/dist/commonjs/components/Inspector/NetworkTab.js +4 -1
  15. package/dist/commonjs/components/Inspector/ReduxDetail.js +237 -42
  16. package/dist/commonjs/components/Inspector/ReduxTab.js +456 -35
  17. package/dist/commonjs/components/JsonViewer.js +26 -4
  18. package/dist/commonjs/components/LogCard.js +103 -11
  19. package/dist/commonjs/components/NetworkIcons.js +19 -10
  20. package/dist/commonjs/components/Toast.d.ts +3 -0
  21. package/dist/commonjs/components/Toast.js +152 -0
  22. package/dist/commonjs/components/TouchableScale.d.ts +1 -0
  23. package/dist/commonjs/components/TouchableScale.js +3 -3
  24. package/dist/commonjs/components/TreeNode.js +12 -2
  25. package/dist/commonjs/constants/index.js +6 -6
  26. package/dist/commonjs/constants/version.d.ts +1 -1
  27. package/dist/commonjs/constants/version.js +1 -1
  28. package/dist/commonjs/customHooks/reduxLogger.js +128 -5
  29. package/dist/commonjs/helpers/index.d.ts +4 -0
  30. package/dist/commonjs/helpers/index.js +107 -92
  31. package/dist/commonjs/helpers/toast.d.ts +4 -0
  32. package/dist/commonjs/helpers/toast.js +20 -0
  33. package/dist/commonjs/i18n/locales/en.json +49 -3
  34. package/dist/commonjs/styles/AppColors.d.ts +6 -0
  35. package/dist/commonjs/styles/AppColors.js +6 -0
  36. package/dist/commonjs/styles/index.d.ts +82 -15
  37. package/dist/commonjs/styles/index.js +92 -26
  38. package/dist/commonjs/types/interfaces.d.ts +7 -0
  39. package/dist/esm/components/AnalyticsDetail.js +670 -110
  40. package/dist/esm/components/AppHeaderLogo.js +15 -15
  41. package/dist/esm/components/ConsoleLogCard.js +114 -68
  42. package/dist/esm/components/CopyButton.js +15 -1
  43. package/dist/esm/components/DomainHeader.js +64 -32
  44. package/dist/esm/components/HighlightText.js +7 -1
  45. package/dist/esm/components/Inspector/AnalyticsTab.js +334 -223
  46. package/dist/esm/components/Inspector/ConsoleTab.js +163 -57
  47. package/dist/esm/components/Inspector/InspectorHeader.js +71 -120
  48. package/dist/esm/components/Inspector/LogDetail.js +55 -19
  49. package/dist/esm/components/Inspector/MainScreen.js +4 -0
  50. package/dist/esm/components/Inspector/NetworkDetail.js +25 -25
  51. package/dist/esm/components/Inspector/NetworkTab.js +4 -1
  52. package/dist/esm/components/Inspector/ReduxDetail.js +239 -44
  53. package/dist/esm/components/Inspector/ReduxTab.js +458 -37
  54. package/dist/esm/components/JsonViewer.js +26 -4
  55. package/dist/esm/components/LogCard.js +105 -13
  56. package/dist/esm/components/NetworkIcons.js +19 -10
  57. package/dist/esm/components/Toast.d.ts +3 -0
  58. package/dist/esm/components/Toast.js +117 -0
  59. package/dist/esm/components/TouchableScale.d.ts +1 -0
  60. package/dist/esm/components/TouchableScale.js +3 -3
  61. package/dist/esm/components/TreeNode.js +12 -2
  62. package/dist/esm/constants/index.js +6 -6
  63. package/dist/esm/constants/version.d.ts +1 -1
  64. package/dist/esm/constants/version.js +1 -1
  65. package/dist/esm/customHooks/reduxLogger.js +128 -5
  66. package/dist/esm/helpers/index.d.ts +4 -0
  67. package/dist/esm/helpers/index.js +102 -92
  68. package/dist/esm/helpers/toast.d.ts +4 -0
  69. package/dist/esm/helpers/toast.js +15 -0
  70. package/dist/esm/i18n/locales/en.json +49 -3
  71. package/dist/esm/styles/AppColors.d.ts +6 -0
  72. package/dist/esm/styles/AppColors.js +6 -0
  73. package/dist/esm/styles/index.d.ts +82 -15
  74. package/dist/esm/styles/index.js +92 -26
  75. package/dist/esm/types/interfaces.d.ts +7 -0
  76. package/ios/NetworkInspectorModule.m +0 -10
  77. package/package.json +13 -10
@@ -1,13 +1,12 @@
1
1
  import React, { useState } from 'react';
2
2
  import { Image, Platform, StyleSheet, View, } from 'react-native';
3
3
  import BrandSquareIcon from './BrandSquareIcon';
4
- import { AppColors } from '../styles/AppColors';
5
4
  export const AppHeaderLogo = ({ size = 46, customIcon, }) => {
6
5
  const [loadError, setLoadError] = useState(false);
7
- // 1. If customIcon is explicitly provided, render it directly
6
+ // 1. If customIcon is explicitly provided, render it directly edge-to-edge
8
7
  if (customIcon) {
9
8
  return (<View style={[logoStyles.container, { width: size, height: size }]}>
10
- <Image source={customIcon} style={{ width: size - 4, height: size - 4, borderRadius: 10 }} resizeMode="cover"/>
9
+ <Image source={customIcon} style={logoStyles.image} resizeMode="cover"/>
11
10
  </View>);
12
11
  }
13
12
  // 2. Dynamically query installed native app icon (AppIcon on iOS, ic_launcher on Android)
@@ -18,28 +17,29 @@ export const AppHeaderLogo = ({ size = 46, customIcon, }) => {
18
17
  default: { uri: 'AppIcon' },
19
18
  });
20
19
  return (<View style={[logoStyles.container, { width: size, height: size }]}>
21
- <Image source={nativeSource} style={{ width: size - 4, height: size - 4, borderRadius: 10 }} resizeMode="cover" onError={() => setLoadError(true)}/>
20
+ <Image source={nativeSource} style={logoStyles.image} resizeMode="cover" onError={() => setLoadError(true)}/>
22
21
  </View>);
23
22
  }
24
23
  // 3. Fallback: Inspector's Signature Owl Brand Logo
25
24
  return (<View style={[logoStyles.container, { width: size, height: size }]}>
26
- <BrandSquareIcon size={size - 4}/>
25
+ <BrandSquareIcon size={size}/>
27
26
  </View>);
28
27
  };
29
28
  const logoStyles = StyleSheet.create({
30
29
  container: {
30
+ borderRadius: 10,
31
+ overflow: 'hidden',
32
+ backgroundColor: 'rgba(255, 255, 255, 0.15)',
33
+ borderWidth: 1,
34
+ borderColor: 'rgba(255, 255, 255, 0.28)',
35
+ padding: 2.5,
31
36
  alignItems: 'center',
32
37
  justifyContent: 'center',
33
- borderRadius: 12,
34
- backgroundColor: `${AppColors.white}24`,
35
- borderWidth: 1.5,
36
- borderColor: `${AppColors.white}4D`,
37
- overflow: 'hidden',
38
- shadowColor: AppColors.primaryBlack,
39
- shadowOffset: { width: 0, height: 2 },
40
- shadowOpacity: 0.15,
41
- shadowRadius: 4,
42
- elevation: 3,
38
+ },
39
+ image: {
40
+ width: '100%',
41
+ height: '100%',
42
+ borderRadius: 7.5,
43
43
  },
44
44
  });
45
45
  export default AppHeaderLogo;
@@ -3,10 +3,10 @@ import { useTranslation } from '../i18n';
3
3
  import { Pressable, StyleSheet, Text, View, } from 'react-native';
4
4
  import { AppColors } from '../styles/AppColors';
5
5
  import { AppFonts } from '../styles/AppFonts';
6
- import { formatTime, getJsonContent, getJsonPreviewText, parseStackLine, } from '../helpers';
6
+ import { formatTime, getJsonContent, getJsonPreviewText, parseStackLine, openInVSCode, } from '../helpers';
7
7
  import HighlightText from './HighlightText';
8
8
  import CopyButton from './CopyButton';
9
- import { ChevronIcon } from './NetworkIcons';
9
+ import { ChevronIcon, ExternalLinkIcon } from './NetworkIcons';
10
10
  import { useInspector } from './Inspector/InspectorContext';
11
11
  const getLogMessageWithBadges = (message, searchStr, textStyle, highlightStyle, numberOfLines) => {
12
12
  if (!message)
@@ -20,37 +20,37 @@ const getLogMessageWithBadges = (message, searchStr, textStyle, highlightStyle,
20
20
  const getTagDecorator = (tag) => {
21
21
  const clean = tag.replace(/[\[\]]/g, '').trim().toUpperCase();
22
22
  if (clean === 'AXIOS') {
23
- return { color: '#059669', icon: '⚡', label: 'AXIOS' };
23
+ return { color: AppColors.emerald600, icon: '⚡', label: 'AXIOS' };
24
24
  }
25
25
  if (clean === 'API' || clean === 'FETCH' || clean === 'HTTP' || clean === 'NETWORK') {
26
- return { color: '#0284C7', icon: '🌐', label: clean };
26
+ return { color: AppColors.sky600, icon: '🌐', label: clean };
27
27
  }
28
28
  if (clean === 'SAMPLE' || clean === 'BATCH') {
29
- return { color: '#4F46E5', icon: '🎲', label: clean };
29
+ return { color: AppColors.indigo600Alt, icon: '🎲', label: clean };
30
30
  }
31
31
  if (clean === 'REDUX' || clean === 'STORE' || clean === 'STATE') {
32
- return { color: '#7C3AED', icon: '⚛️', label: clean };
32
+ return { color: AppColors.violet600, icon: '⚛️', label: clean };
33
33
  }
34
34
  if (clean === 'ANALYTICS' || clean === 'FIREBASE' || clean === 'GA4' || clean === 'GA') {
35
- return { color: '#0D9488', icon: '📊', label: clean };
35
+ return { color: AppColors.teal600, icon: '📊', label: clean };
36
36
  }
37
37
  if (clean === 'AUTH' || clean === 'TOKEN' || clean === 'SESSION') {
38
- return { color: '#D97706', icon: '🔑', label: clean };
38
+ return { color: AppColors.amber600, icon: '🔑', label: clean };
39
39
  }
40
40
  if (clean === 'APP') {
41
- return { color: '#4F46E5', icon: '📱', label: 'APP' };
41
+ return { color: AppColors.indigo600Alt, icon: '📱', label: 'APP' };
42
42
  }
43
43
  if (clean === 'TEST') {
44
- return { color: '#10B981', icon: '🧪', label: 'TEST' };
44
+ return { color: AppColors.emerald500, icon: '🧪', label: 'TEST' };
45
45
  }
46
46
  if (clean === 'WARN' || clean === 'WARNING') {
47
- return { color: '#D97706', icon: '⚠️', label: 'WARN' };
47
+ return { color: AppColors.amber600, icon: '⚠️', label: 'WARN' };
48
48
  }
49
49
  if (clean === 'ERROR' || clean === 'CRASH' || clean === 'BUG') {
50
- return { color: '#DC2626', icon: '💥', label: 'ERROR' };
50
+ return { color: AppColors.red600, icon: '💥', label: 'ERROR' };
51
51
  }
52
52
  if (clean === 'PERF' || clean === 'RENDER') {
53
- return { color: '#8B5CF6', icon: '⚡', label: clean };
53
+ return { color: AppColors.violet500, icon: '⚡', label: clean };
54
54
  }
55
55
  return { color: AppColors.slate600, icon: '🏷️', label: clean };
56
56
  };
@@ -89,51 +89,72 @@ export const ConsoleLogCard = React.memo(function ConsoleLogCard({ item, searchS
89
89
  ? parseStackLine(item.caller, true)
90
90
  : null;
91
91
  const getLogColors = () => {
92
- const isDark = AppColors.primaryLight !== AppColors.white;
93
- if (isAnalyticsError) {
92
+ const type = (item.type || 'log').toLowerCase();
93
+ const method = (item.sourceMethod || type).toLowerCase();
94
+ if (isAnalyticsError || type === 'error' || method === 'error') {
94
95
  return {
95
- border: AppColors.skyBlue,
96
- badgeBg: `${AppColors.skyBlue}15`,
97
- badgeText: AppColors.skyBlue,
96
+ border: '#EF4444',
97
+ badgeBg: '#FEE2E2',
98
+ badgeText: '#DC2626',
98
99
  label: 'ERROR',
99
- cardBg: isDark ? `${AppColors.skyBlue}26` : AppColors.blueTintBg,
100
+ cardBg: '#FEF2F2',
101
+ methodBorder: '#FECACA',
102
+ methodBg: '#FEE2E2',
103
+ methodText: '#DC2626',
104
+ textColor: '#991B1B',
100
105
  };
101
106
  }
102
- switch (item.type) {
103
- case 'error':
104
- return {
105
- border: AppColors.errorColor,
106
- badgeBg: `${AppColors.errorColor}15`,
107
- badgeText: AppColors.errorColor,
108
- label: 'ERROR',
109
- cardBg: isDark ? `${AppColors.errorColor}26` : AppColors.errorCardBg,
110
- };
111
- case 'warn':
112
- return {
113
- border: AppColors.lightOrange,
114
- badgeBg: `${AppColors.lightOrange}15`,
115
- badgeText: AppColors.darkOrange || AppColors.lightOrange,
116
- label: 'WARN',
117
- cardBg: isDark ? `${AppColors.amber500}26` : AppColors.warnCardBg,
118
- };
119
- default:
120
- if (isUserLog) {
121
- return {
122
- border: AppColors.slate600,
123
- badgeBg: isDark ? AppColors.gray700 : AppColors.slate200,
124
- badgeText: isDark ? AppColors.gray300 : AppColors.slate700,
125
- label: 'INFO',
126
- cardBg: isDark ? AppColors.primaryLight : AppColors.slate100,
127
- };
128
- }
129
- return {
130
- border: AppColors.purple,
131
- badgeBg: `${AppColors.purple}15`,
132
- badgeText: AppColors.purple,
133
- label: 'INFO',
134
- cardBg: isDark ? `${AppColors.purple}1F` : AppColors.purpleShade50,
135
- };
107
+ if (type === 'warn' || method === 'warn') {
108
+ return {
109
+ border: '#F59E0B',
110
+ badgeBg: '#FEF3C7',
111
+ badgeText: '#D97706',
112
+ label: 'WARN',
113
+ cardBg: '#FFFBEB',
114
+ methodBorder: '#FDE68A',
115
+ methodBg: '#FEF3C7',
116
+ methodText: '#D97706',
117
+ textColor: '#92400E',
118
+ };
119
+ }
120
+ if (type === 'debug' || method === 'debug') {
121
+ return {
122
+ border: '#8B5CF6',
123
+ badgeBg: '#EDE9FE',
124
+ badgeText: '#7C3AED',
125
+ label: 'DEBUG',
126
+ cardBg: '#F5F3FF',
127
+ methodBorder: '#DDD6FE',
128
+ methodBg: '#EDE9FE',
129
+ methodText: '#7C3AED',
130
+ textColor: '#4C1D95',
131
+ };
132
+ }
133
+ if (type === 'info' || method === 'info') {
134
+ return {
135
+ border: '#0EA5E9',
136
+ badgeBg: '#E0F2FE',
137
+ badgeText: '#0284C7',
138
+ label: 'INFO',
139
+ cardBg: '#F0F9FF',
140
+ methodBorder: '#BAE6FD',
141
+ methodBg: '#E0F2FE',
142
+ methodText: '#0284C7',
143
+ textColor: '#075985',
144
+ };
136
145
  }
146
+ // Default / Standard console.log
147
+ return {
148
+ border: '#6366F1',
149
+ badgeBg: '#EEF2FF',
150
+ badgeText: '#4F46E5',
151
+ label: 'LOG',
152
+ cardBg: '#FFFFFF',
153
+ methodBorder: '#E0E7FF',
154
+ methodBg: '#EEF2FF',
155
+ methodText: '#4F46E5',
156
+ textColor: '#0F172A',
157
+ };
137
158
  };
138
159
  const colors = getLogColors();
139
160
  const jsonPreview = jsonContent
@@ -147,6 +168,7 @@ export const ConsoleLogCard = React.memo(function ConsoleLogCard({ item, searchS
147
168
  styles.card,
148
169
  {
149
170
  borderLeftColor: colors.border,
171
+ borderLeftWidth: 3.5,
150
172
  backgroundColor: colors.cardBg,
151
173
  },
152
174
  pressed && styles.cardPressed,
@@ -155,18 +177,18 @@ export const ConsoleLogCard = React.memo(function ConsoleLogCard({ item, searchS
155
177
  <View style={styles.headerLeft}>
156
178
  <View style={[styles.typeChip, { backgroundColor: colors.badgeBg }]}>
157
179
  <View style={[styles.typeDot, { backgroundColor: colors.badgeText }]}/>
158
- <Text style={[styles.typeChipText, { color: colors.badgeText }]}>
180
+ <Text style={[styles.typeChipText, { color: colors.badgeText, fontFamily: AppFonts.interBold }]}>
159
181
  {colors.label}
160
182
  </Text>
161
183
  </View>
162
184
  <View style={[
163
185
  styles.metaChip,
164
186
  {
165
- backgroundColor: `${AppColors.brandPurple}12`,
166
- borderColor: `${AppColors.brandPurple}30`,
187
+ backgroundColor: colors.methodBg,
188
+ borderColor: colors.methodBorder,
167
189
  },
168
190
  ]}>
169
- <Text style={[styles.metaChipText, { color: AppColors.brandPurple }]}>
191
+ <Text style={[styles.metaChipText, { color: colors.methodText, fontFamily: AppFonts.interBold }]}>
170
192
  console.
171
193
  {('sourceMethod' in item ? item.sourceMethod : undefined) ||
172
194
  item.type ||
@@ -176,11 +198,11 @@ export const ConsoleLogCard = React.memo(function ConsoleLogCard({ item, searchS
176
198
  {jsonContent && (<View style={[
177
199
  styles.metaChip,
178
200
  {
179
- backgroundColor: `${AppColors.teal600}12`,
180
- borderColor: `${AppColors.teal600}2B`,
201
+ backgroundColor: '#ECFDF5',
202
+ borderColor: '#A7F3D0',
181
203
  },
182
204
  ]}>
183
- <Text style={[styles.metaChipText, { color: AppColors.teal600 }]}>
205
+ <Text style={[styles.metaChipText, { color: '#059669', fontFamily: AppFonts.interBold }]}>
184
206
  {Array.isArray(jsonContent.data)
185
207
  ? `Array[${jsonContent.data.length}]`
186
208
  : `Object{${Object.keys(jsonContent.data).length}}`}
@@ -195,7 +217,7 @@ export const ConsoleLogCard = React.memo(function ConsoleLogCard({ item, searchS
195
217
  borderColor: `${AppColors.purple}3D`,
196
218
  },
197
219
  ]}>
198
- <Text style={[styles.metaChipText, { color: AppColors.purple }]}>
220
+ <Text style={[styles.metaChipText, { color: AppColors.purple, fontFamily: AppFonts.interBold }]}>
199
221
  ×{item.duplicateCount}
200
222
  </Text>
201
223
  </View>)}
@@ -226,18 +248,34 @@ export const ConsoleLogCard = React.memo(function ConsoleLogCard({ item, searchS
226
248
  <Text style={styles.footerText}>{formatTime(item.timestamp)}</Text>
227
249
  {parsedCaller && (<>
228
250
  <View style={styles.footerDot}/>
229
- <View style={{ flexDirection: 'row', alignItems: 'center', gap: 4, maxWidth: 170 }}>
251
+ <Pressable onPress={e => {
252
+ e.stopPropagation?.();
253
+ openInVSCode(parsedCaller.rawFilePath ||
254
+ parsedCaller.fullPath ||
255
+ parsedCaller.fileName, parsedCaller.lineNumber, parsedCaller.columnNumber);
256
+ }} hitSlop={8} style={{
257
+ flexDirection: 'row',
258
+ alignItems: 'center',
259
+ gap: 4,
260
+ maxWidth: 185,
261
+ backgroundColor: `${AppColors.sky600}10`,
262
+ borderColor: `${AppColors.sky600}2B`,
263
+ borderWidth: 1,
264
+ borderRadius: 5,
265
+ paddingHorizontal: 5,
266
+ paddingVertical: 1.5,
267
+ }}>
230
268
  {parsedCaller.fileExt && parsedCaller.fileExt !== 'other' && (<View style={{
231
269
  backgroundColor: parsedCaller.fileExt === 'tsx' || parsedCaller.fileExt === 'ts'
232
- ? `${AppColors.brandPurple}18`
233
- : `${AppColors.teal600}18`,
270
+ ? `${AppColors.brandPurple}22`
271
+ : `${AppColors.teal600}22`,
234
272
  borderRadius: 3,
235
273
  paddingHorizontal: 3.5,
236
- paddingVertical: 1,
274
+ paddingVertical: 0.5,
237
275
  }}>
238
276
  <Text style={{
239
277
  fontFamily: AppFonts.interBold,
240
- fontSize: 8.5,
278
+ fontSize: 8,
241
279
  color: parsedCaller.fileExt === 'tsx' || parsedCaller.fileExt === 'ts'
242
280
  ? AppColors.brandPurple
243
281
  : AppColors.teal600,
@@ -245,11 +283,19 @@ export const ConsoleLogCard = React.memo(function ConsoleLogCard({ item, searchS
245
283
  {parsedCaller.fileExt.toUpperCase()}
246
284
  </Text>
247
285
  </View>)}
248
- <Text style={[styles.footerText, styles.footerCaller]} numberOfLines={1} ellipsizeMode="middle">
286
+ <Text style={[
287
+ styles.footerText,
288
+ {
289
+ fontFamily: AppFonts.interBold,
290
+ color: AppColors.sky600,
291
+ fontSize: 9.5,
292
+ },
293
+ ]} numberOfLines={1} ellipsizeMode="middle">
249
294
  {parsedCaller.fileName}
250
295
  {parsedCaller.lineNumber ? `:${parsedCaller.lineNumber}` : ''}
251
296
  </Text>
252
- </View>
297
+ <ExternalLinkIcon color={AppColors.sky600} size={8.5}/>
298
+ </Pressable>
253
299
  </>)}
254
300
  </View>
255
301
 
@@ -1,4 +1,5 @@
1
1
  import React, { useState, useCallback } from 'react';
2
+ import { Share } from 'react-native';
2
3
  // Components
3
4
  import TouchableScale from './TouchableScale';
4
5
  // Helpers
@@ -16,6 +17,19 @@ const CopyButton = React.memo(({ value, label, iconType = 'copy' }) => {
16
17
  setCopied(true);
17
18
  setTimeout(() => setCopied(false), 1200);
18
19
  }, [value, label]);
20
+ const handleLongPress = useCallback(() => {
21
+ try {
22
+ const resolvedValue = typeof value === 'function' ? value() : value;
23
+ const textToShare = typeof resolvedValue === 'string'
24
+ ? resolvedValue
25
+ : JSON.stringify(resolvedValue, null, 2);
26
+ Share.share({
27
+ message: textToShare,
28
+ title: label,
29
+ });
30
+ }
31
+ catch { }
32
+ }, [value, label]);
19
33
  const containerStyle = [
20
34
  styles.iconSquareBtn,
21
35
  copied && styles.iconSquareBtnSuccess,
@@ -25,7 +39,7 @@ const CopyButton = React.memo(({ value, label, iconType = 'copy' }) => {
25
39
  : iconType === 'fetch'
26
40
  ? FetchIcon
27
41
  : CopyIcon;
28
- return (<TouchableScale onPress={handlePress} hitSlop={12} style={containerStyle}>
42
+ return (<TouchableScale onPress={handlePress} onLongPress={handleLongPress} hitSlop={12} style={containerStyle}>
29
43
  {copied ? (<CheckIcon color={AppColors.greenColor} size={14}/>) : (<IconComponent color={AppColors.grayTextWeak} size={14}/>)}
30
44
  </TouchableScale>);
31
45
  });
@@ -2,6 +2,7 @@ import React, { useEffect, useRef } from 'react';
2
2
  import { Animated, Pressable, View, Text } from 'react-native';
3
3
  // Stylesheet
4
4
  import { AppColors } from '../styles/AppColors';
5
+ import { AppFonts } from '../styles/AppFonts';
5
6
  // Helpers
6
7
  import { formatDateTime } from '../helpers';
7
8
  // Assets
@@ -26,20 +27,75 @@ const DomainHeader = ({ pageName, color, stats, activeFilters, onToggleFilter, i
26
27
  const pctSuccess = total > 0 ? Math.round((stats.success / total) * 100) : 0;
27
28
  const pctFailed = total > 0 ? Math.round((stats.failed / total) * 100) : 0;
28
29
  const pctLoading = total > 0 ? Math.round((stats.loading / total) * 100) : 0;
29
- return (<Pressable style={styles.domainHeaderCard} onPress={() => onToggleCollapse(pageName)}>
30
+ return (<Pressable style={[
31
+ styles.domainHeaderCard,
32
+ !isCollapsed && styles.domainHeaderCardExpanded,
33
+ ]} onPress={() => onToggleCollapse(pageName)}>
30
34
  <View style={styles.domainHeaderTopRow}>
31
35
  <View style={styles.domainHeaderLeft}>
32
- <Animated.View style={{ transform: [{ rotate: chevronRotate }] }}>
36
+ <Animated.View style={{ transform: [{ rotate: chevronRotate }], flexShrink: 0 }}>
33
37
  <ChevronIcon color={AppColors.grayTextWeak} size={13}/>
34
38
  </Animated.View>
35
- <View style={[styles.domainIconWrap, { backgroundColor: `${color}18` }]}>
39
+ <View style={[styles.domainIconWrap, { backgroundColor: `${color}18`, flexShrink: 0 }]}>
36
40
  <ScreenIcon color={color} size={14}/>
37
41
  </View>
38
- <View style={{ flex: 1, justifyContent: 'center' }}>
39
- <Text style={styles.domainTitleText} numberOfLines={1}>
40
- {pageName}
41
- </Text>
42
- <Text style={styles.domainSummaryText} numberOfLines={1}>
42
+ <View style={{ flex: 1, justifyContent: 'center', minWidth: 0 }}>
43
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6, minWidth: 0 }}>
44
+ <Text style={[styles.domainTitleText, { flexShrink: 1 }]} numberOfLines={1} ellipsizeMode="tail">
45
+ {pageName}
46
+ </Text>
47
+ {total > 0 && (<View style={{
48
+ flexDirection: 'row',
49
+ alignItems: 'center',
50
+ gap: 4,
51
+ backgroundColor: stats.failed > 0
52
+ ? AppColors.red100
53
+ : stats.loading > 0
54
+ ? AppColors.amber100
55
+ : AppColors.emerald100,
56
+ paddingHorizontal: 5,
57
+ paddingVertical: 1.5,
58
+ borderRadius: 4,
59
+ flexShrink: 0,
60
+ }}>
61
+ <View style={{
62
+ width: 22,
63
+ height: 3.5,
64
+ borderRadius: 2,
65
+ backgroundColor: AppColors.gray200,
66
+ flexDirection: 'row',
67
+ overflow: 'hidden',
68
+ }}>
69
+ {pctSuccess > 0 && (<View style={{
70
+ width: `${pctSuccess}%`,
71
+ height: '100%',
72
+ backgroundColor: AppColors.emerald500,
73
+ }}/>)}
74
+ {pctFailed > 0 && (<View style={{
75
+ width: `${pctFailed}%`,
76
+ height: '100%',
77
+ backgroundColor: AppColors.red500,
78
+ }}/>)}
79
+ {pctLoading > 0 && (<View style={{
80
+ width: `${pctLoading}%`,
81
+ height: '100%',
82
+ backgroundColor: AppColors.amber500,
83
+ }}/>)}
84
+ </View>
85
+ <Text style={{
86
+ fontFamily: AppFonts.interBold,
87
+ fontSize: 8.5,
88
+ color: stats.failed > 0
89
+ ? AppColors.redErrorText
90
+ : stats.loading > 0
91
+ ? AppColors.amber800Warm
92
+ : AppColors.emerald700,
93
+ }}>
94
+ {pctSuccess}%
95
+ </Text>
96
+ </View>)}
97
+ </View>
98
+ <Text style={styles.domainSummaryText} numberOfLines={1} ellipsizeMode="tail">
43
99
  {formatDateTime(timestamp)}
44
100
  </Text>
45
101
  </View>
@@ -94,30 +150,6 @@ const DomainHeader = ({ pageName, color, stats, activeFilters, onToggleFilter, i
94
150
  })}
95
151
  </View>
96
152
  </View>
97
-
98
- {total > 0 && (<View style={styles.domainProgressTrack}>
99
- {pctSuccess > 0 && (<View style={[
100
- styles.domainProgressSegment,
101
- {
102
- width: `${pctSuccess}%`,
103
- backgroundColor: AppColors.greenColor,
104
- },
105
- ]}/>)}
106
- {pctFailed > 0 && (<View style={[
107
- styles.domainProgressSegment,
108
- {
109
- width: `${pctFailed}%`,
110
- backgroundColor: AppColors.errorColor,
111
- },
112
- ]}/>)}
113
- {pctLoading > 0 && (<View style={[
114
- styles.domainProgressSegment,
115
- {
116
- width: `${pctLoading}%`,
117
- backgroundColor: AppColors.darkOrange,
118
- },
119
- ]}/>)}
120
- </View>)}
121
153
  </Pressable>);
122
154
  };
123
155
  export default DomainHeader;
@@ -3,6 +3,12 @@ import { Text } from 'react-native';
3
3
  // Helpers
4
4
  import { escapeRegex, handleOpenExternalLink } from '../helpers';
5
5
  const HighlightText = React.memo(function HighlightText({ text, search, style, highlightStyle, detectLinks, numberOfLines, ...rest }) {
6
+ // Optimization: For huge strings (> 20KB) with no active search, bypass all splitting/regex overhead
7
+ if (!text || (text.length > 20000 && !search && !detectLinks)) {
8
+ return (<Text style={style} numberOfLines={numberOfLines} {...rest}>
9
+ {text}
10
+ </Text>);
11
+ }
6
12
  // Regex to detect absolute URLs
7
13
  const URL_REGEX = /(https?:\/\/[^\s]+)/g;
8
14
  const renderNormalOrHighlighted = (subText, keyPrefix) => {
@@ -17,7 +23,7 @@ const HighlightText = React.memo(function HighlightText({ text, search, style, h
17
23
  </Text>) : (<Text key={i}>{part}</Text>))}
18
24
  </Text>);
19
25
  };
20
- if (detectLinks && text) {
26
+ if (detectLinks && text && text.length < 20000) {
21
27
  const parts = text.split(URL_REGEX);
22
28
  return (<Text style={style} numberOfLines={numberOfLines} {...rest}>
23
29
  {parts.map((part, i) => {