react-native-inapp-inspector 1.1.35 → 2.0.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.
Files changed (104) hide show
  1. package/README.md +123 -101
  2. package/android/build.gradle +13 -1
  3. package/android/src/main/java/com/inappinspector/NetworkInspectorModule.kt +890 -0
  4. package/android/src/main/java/com/inappinspector/NetworkInspectorPackage.kt +17 -0
  5. package/dist/commonjs/components/ConsoleLogCard.js +5 -5
  6. package/dist/commonjs/components/CopyButton.js +3 -0
  7. package/dist/commonjs/components/EndOfListFooter.js +1 -1
  8. package/dist/commonjs/components/ErrorBoundary.js +3 -0
  9. package/dist/commonjs/components/Inspector/AnalyticsTab.js +25 -73
  10. package/dist/commonjs/components/Inspector/BundleTab.js +671 -189
  11. package/dist/commonjs/components/Inspector/ConsoleTab.js +110 -99
  12. package/dist/commonjs/components/Inspector/CrashTab.js +99 -91
  13. package/dist/commonjs/components/Inspector/InspectorHeader.js +107 -20
  14. package/dist/commonjs/components/Inspector/MainScreen.js +111 -68
  15. package/dist/commonjs/components/Inspector/NetworkTab.js +151 -30
  16. package/dist/commonjs/components/Inspector/PerformanceTab.js +96 -3
  17. package/dist/commonjs/components/Inspector/ReduxTab.js +18 -10
  18. package/dist/commonjs/components/Inspector/SettingsPanel.js +1218 -1238
  19. package/dist/commonjs/components/Inspector/TabBar.js +4 -2
  20. package/dist/commonjs/components/JsonViewer.js +9 -4
  21. package/dist/commonjs/components/LogCard.js +31 -1
  22. package/dist/commonjs/components/LogSyntaxHighlighter.d.ts +16 -0
  23. package/dist/commonjs/components/LogSyntaxHighlighter.js +130 -0
  24. package/dist/commonjs/components/NetworkIcons.d.ts +3 -0
  25. package/dist/commonjs/components/NetworkIcons.js +15 -1
  26. package/dist/commonjs/components/Slider.d.ts +13 -0
  27. package/dist/commonjs/components/Slider.js +261 -0
  28. package/dist/commonjs/components/TouchableScale.d.ts +1 -0
  29. package/dist/commonjs/components/TouchableScale.js +30 -32
  30. package/dist/commonjs/constants/index.d.ts +1 -2
  31. package/dist/commonjs/constants/index.js +4 -0
  32. package/dist/commonjs/constants/version.d.ts +1 -1
  33. package/dist/commonjs/constants/version.js +1 -1
  34. package/dist/commonjs/customHooks/analyticsLogger.d.ts +4 -0
  35. package/dist/commonjs/customHooks/analyticsLogger.js +8 -6
  36. package/dist/commonjs/customHooks/crashHandler.js +8 -1
  37. package/dist/commonjs/helpers/index.d.ts +1 -0
  38. package/dist/commonjs/helpers/index.js +25 -1
  39. package/dist/commonjs/helpers/searchQueryParser.d.ts +6 -0
  40. package/dist/commonjs/helpers/searchQueryParser.js +236 -0
  41. package/dist/commonjs/helpers/settingsStore.d.ts +9 -0
  42. package/dist/commonjs/helpers/settingsStore.js +59 -3
  43. package/dist/commonjs/index.d.ts +1 -0
  44. package/dist/commonjs/index.js +220 -43
  45. package/dist/commonjs/native/NativeInspector.d.ts +125 -0
  46. package/dist/commonjs/native/NativeInspector.js +313 -0
  47. package/dist/commonjs/native/NativeNetworkInspector.d.ts +22 -0
  48. package/dist/commonjs/native/NativeNetworkInspector.js +4 -0
  49. package/dist/commonjs/styles/index.js +41 -41
  50. package/dist/commonjs/types/enums.d.ts +1 -1
  51. package/dist/commonjs/types/interfaces.d.ts +9 -0
  52. package/dist/esm/components/ConsoleLogCard.js +5 -5
  53. package/dist/esm/components/CopyButton.js +3 -0
  54. package/dist/esm/components/EndOfListFooter.js +2 -2
  55. package/dist/esm/components/ErrorBoundary.js +3 -0
  56. package/dist/esm/components/Inspector/AnalyticsTab.js +27 -75
  57. package/dist/esm/components/Inspector/BundleTab.js +671 -189
  58. package/dist/esm/components/Inspector/ConsoleTab.js +111 -100
  59. package/dist/esm/components/Inspector/CrashTab.js +99 -91
  60. package/dist/esm/components/Inspector/InspectorHeader.js +108 -21
  61. package/dist/esm/components/Inspector/MainScreen.js +113 -70
  62. package/dist/esm/components/Inspector/NetworkTab.js +152 -31
  63. package/dist/esm/components/Inspector/PerformanceTab.js +96 -3
  64. package/dist/esm/components/Inspector/ReduxTab.js +18 -10
  65. package/dist/esm/components/Inspector/SettingsPanel.js +1222 -1242
  66. package/dist/esm/components/Inspector/TabBar.js +4 -2
  67. package/dist/esm/components/JsonViewer.js +9 -4
  68. package/dist/esm/components/LogCard.js +32 -2
  69. package/dist/esm/components/LogSyntaxHighlighter.d.ts +16 -0
  70. package/dist/esm/components/LogSyntaxHighlighter.js +123 -0
  71. package/dist/esm/components/NetworkIcons.d.ts +3 -0
  72. package/dist/esm/components/NetworkIcons.js +11 -0
  73. package/dist/esm/components/Slider.d.ts +13 -0
  74. package/dist/esm/components/Slider.js +222 -0
  75. package/dist/esm/components/TouchableScale.d.ts +1 -0
  76. package/dist/esm/components/TouchableScale.js +31 -33
  77. package/dist/esm/constants/index.d.ts +1 -2
  78. package/dist/esm/constants/index.js +4 -0
  79. package/dist/esm/constants/version.d.ts +1 -1
  80. package/dist/esm/constants/version.js +1 -1
  81. package/dist/esm/customHooks/analyticsLogger.d.ts +4 -0
  82. package/dist/esm/customHooks/analyticsLogger.js +6 -5
  83. package/dist/esm/customHooks/crashHandler.js +8 -1
  84. package/dist/esm/helpers/index.d.ts +1 -0
  85. package/dist/esm/helpers/index.js +11 -1
  86. package/dist/esm/helpers/searchQueryParser.d.ts +6 -0
  87. package/dist/esm/helpers/searchQueryParser.js +233 -0
  88. package/dist/esm/helpers/settingsStore.d.ts +9 -0
  89. package/dist/esm/helpers/settingsStore.js +58 -3
  90. package/dist/esm/index.d.ts +1 -0
  91. package/dist/esm/index.js +207 -44
  92. package/dist/esm/native/NativeInspector.d.ts +125 -0
  93. package/dist/esm/native/NativeInspector.js +289 -0
  94. package/dist/esm/native/NativeNetworkInspector.d.ts +22 -0
  95. package/dist/esm/native/NativeNetworkInspector.js +2 -0
  96. package/dist/esm/styles/index.js +41 -41
  97. package/dist/esm/types/enums.d.ts +1 -1
  98. package/dist/esm/types/interfaces.d.ts +9 -0
  99. package/ios/NetworkInspectorModule.h +6 -0
  100. package/ios/NetworkInspectorModule.m +760 -5
  101. package/package.json +13 -2
  102. package/react-native-inapp-inspector.podspec +7 -2
  103. package/android/src/main/java/com/inappinspector/NetworkInspectorModule.java +0 -97
  104. package/android/src/main/java/com/inappinspector/NetworkInspectorPackage.java +0 -28
@@ -10,7 +10,7 @@ import EndOfListFooter from '../EndOfListFooter';
10
10
  import styles from '../../styles';
11
11
  import { AppColors } from '../../styles/AppColors';
12
12
  import { AppFonts } from '../../styles/AppFonts';
13
- import { SearchIcon, ClearIcon, SortArrowIcon, TrashIcon, LayersIcon, CheckIcon, UserIcon, InfoCircleIcon, WarningTriangleIcon, ErrorCircleIcon, AnalyticsIcon, HeaderPauseIcon, } from '../NetworkIcons';
13
+ import { SearchIcon, ClearIcon, SortArrowIcon, TrashIcon, LayersIcon, UserIcon, InfoCircleIcon, WarningTriangleIcon, ErrorCircleIcon, AnalyticsIcon, HeaderPauseIcon, } from '../NetworkIcons';
14
14
  const ConsoleTab = React.memo(() => {
15
15
  const { t } = useTranslation();
16
16
  const { logSearch, setLogSearch, logSortOrder, setLogSortOrder, handleDelete, logFilters, setLogFilters, logCounts, filteredConsoleLogs, visibleConsoleLogs, isConsolePaused, setIsConsolePaused, } = useInspector();
@@ -51,6 +51,8 @@ const ConsoleTab = React.memo(() => {
51
51
  </Text>);
52
52
  }
53
53
  }, [visibleConsoleLogs.length, filteredConsoleLogs.length, logFilters]);
54
+ const [isSearchFocused, setIsSearchFocused] = React.useState(false);
55
+ const QUICK_TAG_SUGGESTIONS = useMemo(() => ['[AXIOS]', '[API]', '[REDUX]', '[ANALYTICS]', '[AUTH]', '[WARN]', '[ERROR]'], []);
54
56
  return (<View style={{ flex: 1 }}>
55
57
  <View style={{
56
58
  backgroundColor: AppColors.white,
@@ -62,12 +64,38 @@ const ConsoleTab = React.memo(() => {
62
64
  styles.toolbarRow,
63
65
  { marginTop: 12, marginBottom: 8 },
64
66
  ]}>
65
- <View style={styles.searchContainer}>
66
- <SearchIcon color={AppColors.grayTextWeak} size={16}/>
67
- <TextInput placeholder={t('console.searchPlaceholder')} placeholderTextColor={AppColors.grayTextWeak} value={logSearch} onChangeText={setLogSearch} style={styles.searchInput} autoCorrect={false} autoCapitalize="none"/>
68
- {logSearch.length > 0 && (<Pressable onPress={() => setLogSearch('')} hitSlop={10} style={styles.clearBtn}>
69
- <ClearIcon color={AppColors.grayTextWeak} size={14}/>
70
- </Pressable>)}
67
+ <View style={[
68
+ styles.searchContainer,
69
+ isSearchFocused && {
70
+ borderColor: AppColors.purple,
71
+ borderWidth: 1.5,
72
+ },
73
+ ]}>
74
+ <SearchIcon color={isSearchFocused ? AppColors.purple : AppColors.grayTextWeak} size={16}/>
75
+ <TextInput placeholder={t('console.searchPlaceholder')} placeholderTextColor={AppColors.grayTextWeak} value={logSearch} onChangeText={setLogSearch} onFocus={() => setIsSearchFocused(true)} onBlur={() => setIsSearchFocused(false)} style={styles.searchInput} autoCorrect={false} autoCapitalize="none"/>
76
+ {logSearch.length > 0 && (<View style={{
77
+ flexDirection: 'row',
78
+ alignItems: 'center',
79
+ gap: 4,
80
+ }}>
81
+ <View style={{
82
+ backgroundColor: `${AppColors.purple}20`,
83
+ borderRadius: 10,
84
+ paddingHorizontal: 6,
85
+ paddingVertical: 2,
86
+ }}>
87
+ <Text style={{
88
+ color: AppColors.purple,
89
+ fontSize: 10,
90
+ fontFamily: AppFonts.interBold,
91
+ }}>
92
+ {filteredConsoleLogs.length}
93
+ </Text>
94
+ </View>
95
+ <Pressable onPress={() => setLogSearch('')} hitSlop={10} style={styles.clearBtn}>
96
+ <ClearIcon color={AppColors.grayTextWeak} size={14}/>
97
+ </Pressable>
98
+ </View>)}
71
99
  </View>
72
100
 
73
101
  <View style={styles.toolbarRight}>
@@ -97,6 +125,49 @@ const ConsoleTab = React.memo(() => {
97
125
  </View>
98
126
  </View>
99
127
 
128
+ {/* Quick Tag Search Suggestions Bar */}
129
+ {(isSearchFocused || logSearch.length > 0) && (<ScrollView horizontal showsHorizontalScrollIndicator={false} style={{ marginBottom: 6, maxHeight: 30 }} contentContainerStyle={{
130
+ paddingHorizontal: 16,
131
+ flexDirection: 'row',
132
+ alignItems: 'center',
133
+ gap: 6,
134
+ }}>
135
+ {QUICK_TAG_SUGGESTIONS.map(tag => {
136
+ const isSelected = logSearch.includes(tag);
137
+ return (<TouchableScale key={tag} onPress={() => {
138
+ if (isSelected) {
139
+ setLogSearch(prev => prev.replace(tag, '').trim());
140
+ }
141
+ else {
142
+ setLogSearch(prev => prev ? `${prev} ${tag}`.trim() : tag);
143
+ }
144
+ }}>
145
+ <View style={{
146
+ paddingHorizontal: 8,
147
+ paddingVertical: 3,
148
+ borderRadius: 6,
149
+ backgroundColor: isSelected
150
+ ? AppColors.purple
151
+ : `${AppColors.purple}14`,
152
+ borderWidth: 1,
153
+ borderColor: isSelected
154
+ ? AppColors.purple
155
+ : `${AppColors.purple}30`,
156
+ }}>
157
+ <Text style={{
158
+ fontFamily: AppFonts.interBold,
159
+ fontSize: 10,
160
+ color: isSelected
161
+ ? AppColors.white
162
+ : AppColors.purple,
163
+ }}>
164
+ {tag}
165
+ </Text>
166
+ </View>
167
+ </TouchableScale>);
168
+ })}
169
+ </ScrollView>)}
170
+
100
171
  <ScrollView horizontal showsHorizontalScrollIndicator={false} style={{ marginVertical: 4, maxHeight: 46 }} contentContainerStyle={{
101
172
  paddingHorizontal: 16,
102
173
  paddingBottom: 4,
@@ -115,7 +186,9 @@ const ConsoleTab = React.memo(() => {
115
186
  {
116
187
  backgroundColor: active ? AppColors.indigo600Alt : AppColors.indigo50,
117
188
  borderColor: active ? AppColors.indigo600 : AppColors.indigo400,
118
- borderRadius: 7,
189
+ borderRadius: 8,
190
+ paddingHorizontal: 10,
191
+ paddingVertical: 6,
119
192
  },
120
193
  active && {
121
194
  shadowColor: AppColors.indigo600Alt,
@@ -128,21 +201,9 @@ const ConsoleTab = React.memo(() => {
128
201
  <View style={{
129
202
  flexDirection: 'row',
130
203
  alignItems: 'center',
131
- gap: 5,
132
- }}>
133
- <View style={{
134
- width: 12,
135
- height: 12,
136
- borderRadius: 3,
137
- borderWidth: 1.2,
138
- borderColor: active ? AppColors.white : AppColors.indigo400,
139
- backgroundColor: active ? AppColors.white : 'transparent',
140
- alignItems: 'center',
141
- justifyContent: 'center',
204
+ gap: 6,
142
205
  }}>
143
- {active && <CheckIcon color={AppColors.indigo600Alt} size={8}/>}
144
- </View>
145
- <LayersIcon size={12} color={active ? AppColors.white : AppColors.indigo600Alt}/>
206
+ <LayersIcon size={13} color={active ? AppColors.white : AppColors.indigo600Alt}/>
146
207
  <Text numberOfLines={1} style={[
147
208
  styles.statusFilterText,
148
209
  {
@@ -177,7 +238,9 @@ const ConsoleTab = React.memo(() => {
177
238
  {
178
239
  backgroundColor: active ? AppColors.teal600 : AppColors.teal100,
179
240
  borderColor: active ? AppColors.teal600 : AppColors.teal400,
180
- borderRadius: 7,
241
+ borderRadius: 8,
242
+ paddingHorizontal: 10,
243
+ paddingVertical: 6,
181
244
  },
182
245
  active && {
183
246
  shadowColor: AppColors.teal600,
@@ -190,21 +253,9 @@ const ConsoleTab = React.memo(() => {
190
253
  <View style={{
191
254
  flexDirection: 'row',
192
255
  alignItems: 'center',
193
- gap: 5,
256
+ gap: 6,
194
257
  }}>
195
- <View style={{
196
- width: 12,
197
- height: 12,
198
- borderRadius: 3,
199
- borderWidth: 1.2,
200
- borderColor: active ? AppColors.white : AppColors.teal400,
201
- backgroundColor: active ? AppColors.white : 'transparent',
202
- alignItems: 'center',
203
- justifyContent: 'center',
204
- }}>
205
- {active && <CheckIcon color={AppColors.teal600} size={8}/>}
206
- </View>
207
- <UserIcon size={12} color={active ? AppColors.white : AppColors.teal600}/>
258
+ <UserIcon size={13} color={active ? AppColors.white : AppColors.teal600}/>
208
259
  <Text numberOfLines={1} style={[
209
260
  styles.statusFilterText,
210
261
  {
@@ -239,7 +290,9 @@ const ConsoleTab = React.memo(() => {
239
290
  {
240
291
  backgroundColor: active ? AppColors.sky600 : AppColors.sky100,
241
292
  borderColor: active ? AppColors.blue700 : AppColors.sky400,
242
- borderRadius: 7,
293
+ borderRadius: 8,
294
+ paddingHorizontal: 10,
295
+ paddingVertical: 6,
243
296
  },
244
297
  active && {
245
298
  shadowColor: AppColors.sky600,
@@ -252,21 +305,9 @@ const ConsoleTab = React.memo(() => {
252
305
  <View style={{
253
306
  flexDirection: 'row',
254
307
  alignItems: 'center',
255
- gap: 5,
308
+ gap: 6,
256
309
  }}>
257
- <View style={{
258
- width: 12,
259
- height: 12,
260
- borderRadius: 3,
261
- borderWidth: 1.2,
262
- borderColor: active ? AppColors.white : AppColors.sky400,
263
- backgroundColor: active ? AppColors.white : 'transparent',
264
- alignItems: 'center',
265
- justifyContent: 'center',
266
- }}>
267
- {active && <CheckIcon color={AppColors.sky600} size={8}/>}
268
- </View>
269
- <InfoCircleIcon size={12} color={active ? AppColors.white : AppColors.sky600}/>
310
+ <InfoCircleIcon size={13} color={active ? AppColors.white : AppColors.sky600}/>
270
311
  <Text numberOfLines={1} style={[
271
312
  styles.statusFilterText,
272
313
  {
@@ -301,7 +342,9 @@ const ConsoleTab = React.memo(() => {
301
342
  {
302
343
  backgroundColor: active ? AppColors.amber600 : AppColors.amber100,
303
344
  borderColor: active ? AppColors.amber700 : AppColors.amber200,
304
- borderRadius: 7,
345
+ borderRadius: 8,
346
+ paddingHorizontal: 10,
347
+ paddingVertical: 6,
305
348
  },
306
349
  active && {
307
350
  shadowColor: AppColors.amber600,
@@ -314,21 +357,9 @@ const ConsoleTab = React.memo(() => {
314
357
  <View style={{
315
358
  flexDirection: 'row',
316
359
  alignItems: 'center',
317
- gap: 5,
318
- }}>
319
- <View style={{
320
- width: 12,
321
- height: 12,
322
- borderRadius: 3,
323
- borderWidth: 1.2,
324
- borderColor: active ? AppColors.white : AppColors.amber500,
325
- backgroundColor: active ? AppColors.white : 'transparent',
326
- alignItems: 'center',
327
- justifyContent: 'center',
360
+ gap: 6,
328
361
  }}>
329
- {active && <CheckIcon color={AppColors.amber600} size={8}/>}
330
- </View>
331
- <WarningTriangleIcon size={12} color={active ? AppColors.white : AppColors.amber700}/>
362
+ <WarningTriangleIcon size={13} color={active ? AppColors.white : AppColors.amber700}/>
332
363
  <Text numberOfLines={1} style={[
333
364
  styles.statusFilterText,
334
365
  {
@@ -363,7 +394,9 @@ const ConsoleTab = React.memo(() => {
363
394
  {
364
395
  backgroundColor: active ? AppColors.red500 : AppColors.red100,
365
396
  borderColor: active ? AppColors.red600 : AppColors.errorBorder,
366
- borderRadius: 7,
397
+ borderRadius: 8,
398
+ paddingHorizontal: 10,
399
+ paddingVertical: 6,
367
400
  },
368
401
  active && {
369
402
  shadowColor: AppColors.red500,
@@ -376,21 +409,9 @@ const ConsoleTab = React.memo(() => {
376
409
  <View style={{
377
410
  flexDirection: 'row',
378
411
  alignItems: 'center',
379
- gap: 5,
380
- }}>
381
- <View style={{
382
- width: 12,
383
- height: 12,
384
- borderRadius: 3,
385
- borderWidth: 1.2,
386
- borderColor: active ? AppColors.white : AppColors.red500,
387
- backgroundColor: active ? AppColors.white : 'transparent',
388
- alignItems: 'center',
389
- justifyContent: 'center',
412
+ gap: 6,
390
413
  }}>
391
- {active && <CheckIcon color={AppColors.red500} size={8}/>}
392
- </View>
393
- <ErrorCircleIcon size={12} color={active ? AppColors.white : AppColors.red500}/>
414
+ <ErrorCircleIcon size={13} color={active ? AppColors.white : AppColors.red500}/>
394
415
  <Text numberOfLines={1} style={[
395
416
  styles.statusFilterText,
396
417
  {
@@ -425,7 +446,9 @@ const ConsoleTab = React.memo(() => {
425
446
  {
426
447
  backgroundColor: active ? AppColors.violet600 : AppColors.purple100,
427
448
  borderColor: active ? AppColors.purple700 : AppColors.purple200,
428
- borderRadius: 7,
449
+ borderRadius: 8,
450
+ paddingHorizontal: 10,
451
+ paddingVertical: 6,
429
452
  },
430
453
  active && {
431
454
  shadowColor: AppColors.violet600,
@@ -438,29 +461,17 @@ const ConsoleTab = React.memo(() => {
438
461
  <View style={{
439
462
  flexDirection: 'row',
440
463
  alignItems: 'center',
441
- gap: 5,
442
- }}>
443
- <View style={{
444
- width: 12,
445
- height: 12,
446
- borderRadius: 3,
447
- borderWidth: 1.2,
448
- borderColor: active ? AppColors.white : AppColors.purple400,
449
- backgroundColor: active ? AppColors.white : 'transparent',
450
- alignItems: 'center',
451
- justifyContent: 'center',
464
+ gap: 6,
452
465
  }}>
453
- {active && <CheckIcon color={AppColors.violet600} size={8}/>}
454
- </View>
455
- <AnalyticsIcon size={12} color={active ? AppColors.white : AppColors.purple}/>
466
+ <AnalyticsIcon size={13} color={active ? AppColors.white : AppColors.violet600}/>
456
467
  <Text numberOfLines={1} style={[
457
468
  styles.statusFilterText,
458
469
  {
459
- color: active ? AppColors.white : AppColors.brandPurple,
470
+ color: active ? AppColors.white : AppColors.purpleText,
460
471
  fontFamily: AppFonts.interBold,
461
472
  },
462
473
  ]}>
463
- {t('tabs.analytics', 'Analytics')} ({logCounts.analytics})
474
+ {t('console.analytics', 'Analytics')} ({logCounts.analytics})
464
475
  </Text>
465
476
  </View>
466
477
  </View>
@@ -469,7 +480,7 @@ const ConsoleTab = React.memo(() => {
469
480
  </ScrollView>
470
481
  </View>
471
482
 
472
- <FlatList data={filteredConsoleLogs} keyExtractor={keyExtractor} ListHeaderComponent={listHeader} renderItem={renderItem} initialNumToRender={15} maxToRenderPerBatch={15} windowSize={7} removeClippedSubviews={true} ListEmptyComponent={<EmptyState isSearch={logSearch.length > 0 || logFilters.size > 0}/>} ListFooterComponent={filteredConsoleLogs.length > 0 ? (<EndOfListFooter count={filteredConsoleLogs.length} label="logs"/>) : null} contentContainerStyle={[
483
+ <FlatList data={filteredConsoleLogs} keyExtractor={keyExtractor} ListHeaderComponent={listHeader} renderItem={renderItem} initialNumToRender={12} maxToRenderPerBatch={8} windowSize={5} removeClippedSubviews={true} renderToHardwareTextureAndroid={true} ListEmptyComponent={<EmptyState isSearch={logSearch.length > 0 || logFilters.size > 0}/>} ListFooterComponent={filteredConsoleLogs.length > 0 ? (<EndOfListFooter count={filteredConsoleLogs.length} label="logs"/>) : null} contentContainerStyle={[
473
484
  styles.listContent,
474
485
  filteredConsoleLogs.length === 0 && { flexGrow: 1 },
475
486
  ]} keyboardShouldPersistTaps="handled"/>
@@ -109,11 +109,16 @@ const CrashTab = React.memo(() => {
109
109
  }
110
110
  // Search filter
111
111
  if (searchQuery.trim().length > 0) {
112
- const q = searchQuery.toLowerCase();
113
- const msg = (item.message || '').toLowerCase();
114
- const name = (item.name || '').toLowerCase();
115
- const stack = (item.stack || '').toLowerCase();
116
- return msg.includes(q) || name.includes(q) || stack.includes(q);
112
+ const queryTokens = searchQuery.trim().toLowerCase().split(/\s+/).filter(Boolean);
113
+ const searchTarget = [
114
+ item.message || '',
115
+ item.name || '',
116
+ item.stack || '',
117
+ item.type || '',
118
+ item.deviceInfo?.platform || '',
119
+ ...(item.breadcrumbs || []).map(b => `${b.type || ''} ${b.message || ''}`),
120
+ ].join(' ').toLowerCase();
121
+ return queryTokens.every(token => searchTarget.includes(token));
117
122
  }
118
123
  return true;
119
124
  });
@@ -250,74 +255,75 @@ const CrashTab = React.memo(() => {
250
255
  </AnimatedEntrance>);
251
256
  }, [searchQuery, setSelectedCrash]);
252
257
  return (<View style={localStyles.container}>
253
- {/* ─── Top Stats Bar ─── */}
254
- <View style={localStyles.statsBar}>
255
- <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={localStyles.statsScrollContent}>
256
- <View style={[localStyles.statChip, localStyles.statChipActive]}>
257
- <Text style={localStyles.statChipValue}>{stats.total}</Text>
258
- <Text style={localStyles.statChipLabel}>{t('crash.statCrashes')}</Text>
259
- </View>
258
+ {crashRecords.length > 0 && (<>
259
+ {/* ─── Top Stats Bar ─── */}
260
+ <View style={localStyles.statsBar}>
261
+ <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={localStyles.statsScrollContent}>
262
+ <View style={[localStyles.statChip, localStyles.statChipActive]}>
263
+ <Text style={localStyles.statChipValue}>{stats.total}</Text>
264
+ <Text style={localStyles.statChipLabel}>{t('crash.statCrashes')}</Text>
265
+ </View>
260
266
 
261
- <View style={localStyles.statChip}>
262
- <Text style={[localStyles.statChipValue, { color: AppColors.errorColor }]}>
263
- {stats.fatalCount}
264
- </Text>
265
- <Text style={localStyles.statChipLabel}>{t('crash.statFatal')}</Text>
266
- </View>
267
+ <View style={localStyles.statChip}>
268
+ <Text style={[localStyles.statChipValue, { color: AppColors.errorColor }]}>
269
+ {stats.fatalCount}
270
+ </Text>
271
+ <Text style={localStyles.statChipLabel}>{t('crash.statFatal')}</Text>
272
+ </View>
267
273
 
268
- <View style={localStyles.statChip}>
269
- <Text style={[localStyles.statChipValue, { color: AppColors.offerPurple }]}>
270
- {stats.jsCount}
271
- </Text>
272
- <Text style={localStyles.statChipLabel}>{t('crash.statJsErrors')}</Text>
273
- </View>
274
+ <View style={localStyles.statChip}>
275
+ <Text style={[localStyles.statChipValue, { color: AppColors.offerPurple }]}>
276
+ {stats.jsCount}
277
+ </Text>
278
+ <Text style={localStyles.statChipLabel}>{t('crash.statJsErrors')}</Text>
279
+ </View>
274
280
 
275
- <View style={localStyles.statChip}>
276
- <Text style={[localStyles.statChipValue, { color: AppColors.darkOrange }]}>
277
- {stats.promiseCount}
278
- </Text>
279
- <Text style={localStyles.statChipLabel}>{t('crash.statPromises')}</Text>
280
- </View>
281
+ <View style={localStyles.statChip}>
282
+ <Text style={[localStyles.statChipValue, { color: AppColors.darkOrange }]}>
283
+ {stats.promiseCount}
284
+ </Text>
285
+ <Text style={localStyles.statChipLabel}>{t('crash.statPromises')}</Text>
286
+ </View>
281
287
 
282
- <View style={localStyles.statChip}>
283
- <Text style={[localStyles.statChipValue, { color: AppColors.purple }]}>
284
- {stats.renderCount}
285
- </Text>
286
- <Text style={localStyles.statChipLabel}>{t('crash.statRender')}</Text>
287
- </View>
288
+ <View style={localStyles.statChip}>
289
+ <Text style={[localStyles.statChipValue, { color: AppColors.purple }]}>
290
+ {stats.renderCount}
291
+ </Text>
292
+ <Text style={localStyles.statChipLabel}>{t('crash.statRender')}</Text>
293
+ </View>
288
294
 
289
- <View style={localStyles.statChip}>
290
- <Text style={[localStyles.statChipValue, { color: AppColors.skyBlue }]}>
291
- {stats.nativeCount}
292
- </Text>
293
- <Text style={localStyles.statChipLabel}>{t('crash.statNative')}</Text>
295
+ <View style={localStyles.statChip}>
296
+ <Text style={[localStyles.statChipValue, { color: AppColors.skyBlue }]}>
297
+ {stats.nativeCount}
298
+ </Text>
299
+ <Text style={localStyles.statChipLabel}>{t('crash.statNative')}</Text>
300
+ </View>
301
+ </ScrollView>
294
302
  </View>
295
- </ScrollView>
296
- </View>
297
303
 
298
- {/* ─── Search & Controls ─── */}
299
- <View style={localStyles.controlsContainer}>
300
- <View style={localStyles.searchRow}>
301
- <View style={localStyles.searchInputWrapper}>
302
- <SearchIcon color={AppColors.grayTextWeak} size={14}/>
303
- <TextInput value={searchQuery} onChangeText={setSearchQuery} placeholder={t('crash.searchPlaceholder')} placeholderTextColor={AppColors.grayTextWeak} style={localStyles.searchInput} autoCapitalize="none" autoCorrect={false}/>
304
- {searchQuery.length > 0 && (<TouchableOpacity onPress={() => setSearchQuery('')} hitSlop={8}>
305
- <ClearIcon color={AppColors.grayTextWeak} size={14}/>
306
- </TouchableOpacity>)}
307
- </View>
304
+ {/* ─── Search & Controls ─── */}
305
+ <View style={localStyles.controlsContainer}>
306
+ <View style={localStyles.searchRow}>
307
+ <View style={localStyles.searchInputWrapper}>
308
+ <SearchIcon color={AppColors.grayTextWeak} size={14}/>
309
+ <TextInput value={searchQuery} onChangeText={setSearchQuery} placeholder={t('crash.searchPlaceholder')} placeholderTextColor={AppColors.grayTextWeak} style={localStyles.searchInput} autoCapitalize="none" autoCorrect={false}/>
310
+ {searchQuery.length > 0 && (<TouchableOpacity onPress={() => setSearchQuery('')} hitSlop={8}>
311
+ <ClearIcon color={AppColors.grayTextWeak} size={14}/>
312
+ </TouchableOpacity>)}
313
+ </View>
308
314
 
309
- {/* Crash Filters */}
310
- {crashRecords.length > 0 && (<TouchableScale onPress={() => setIsFilterModalOpen(true)} style={[
315
+ {/* Crash Filters */}
316
+ <TouchableScale onPress={() => setIsFilterModalOpen(true)} style={[
311
317
  localStyles.filterButton,
312
318
  !isCrashFiltersDefault(crashFilters) && {
313
319
  borderColor: `${AppColors.brandPurple}60`,
314
320
  backgroundColor: `${AppColors.brandPurple}15`,
315
321
  },
316
322
  ]}>
317
- <FilterIcon size={14} color={isCrashFiltersDefault(crashFilters)
323
+ <FilterIcon size={14} color={isCrashFiltersDefault(crashFilters)
318
324
  ? AppColors.grayTextStrong
319
325
  : AppColors.brandPurple}/>
320
- {!isCrashFiltersDefault(crashFilters) && (<View style={{
326
+ {!isCrashFiltersDefault(crashFilters) && (<View style={{
321
327
  position: 'absolute',
322
328
  top: 2,
323
329
  right: 2,
@@ -328,40 +334,41 @@ const CrashTab = React.memo(() => {
328
334
  borderWidth: 1,
329
335
  borderColor: AppColors.white,
330
336
  }}/>)}
331
- </TouchableScale>)}
337
+ </TouchableScale>
332
338
 
333
- {/* Clear Crashes */}
334
- {crashRecords.length > 0 && (<TouchableScale onPress={handleClearAll} style={localStyles.clearButton}>
335
- <TrashIcon size={14} color={AppColors.errorColor}/>
336
- </TouchableScale>)}
337
- </View>
339
+ {/* Clear Crashes */}
340
+ <TouchableScale onPress={handleClearAll} style={localStyles.clearButton}>
341
+ <TrashIcon size={14} color={AppColors.errorColor}/>
342
+ </TouchableScale>
343
+ </View>
338
344
 
339
- {/* ─── Filter Chips ─── */}
340
- <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={localStyles.filterScrollContent}>
341
- {[
342
- { key: 'all', label: t('crash.filterAll'), Icon: LayersIcon },
343
- { key: 'fatal', label: t('crash.filterFatal'), Icon: SkullIcon },
344
- { key: 'js', label: t('crash.filterJsError'), Icon: JsIcon },
345
- { key: 'promise', label: t('crash.filterPromise'), Icon: HourglassIcon },
346
- { key: 'render', label: t('crash.filterRender'), Icon: LayoutIcon },
347
- { key: 'native', label: t('crash.filterNative'), Icon: ChipIcon },
348
- ].map(chip => {
349
- const isActive = filterType === chip.key;
350
- return (<TouchableOpacity key={chip.key} onPress={() => setFilterType(chip.key)} style={[
351
- localStyles.filterChip,
352
- isActive && localStyles.filterChipActive,
353
- ]}>
354
- <chip.Icon size={12} color={isActive ? AppColors.white : AppColors.grayTextStrong}/>
355
- <Text style={[
356
- localStyles.filterChipText,
357
- isActive && localStyles.filterChipTextActive,
358
- ]}>
359
- {chip.label}
360
- </Text>
361
- </TouchableOpacity>);
362
- })}
363
- </ScrollView>
364
- </View>
345
+ {/* ─── Filter Chips ─── */}
346
+ <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={localStyles.filterScrollContent}>
347
+ {[
348
+ { key: 'all', label: t('crash.filterAll'), Icon: LayersIcon },
349
+ { key: 'fatal', label: t('crash.filterFatal'), Icon: SkullIcon },
350
+ { key: 'js', label: t('crash.filterJsError'), Icon: JsIcon },
351
+ { key: 'promise', label: t('crash.filterPromise'), Icon: HourglassIcon },
352
+ { key: 'render', label: t('crash.filterRender'), Icon: LayoutIcon },
353
+ { key: 'native', label: t('crash.filterNative'), Icon: ChipIcon },
354
+ ].map(chip => {
355
+ const isActive = filterType === chip.key;
356
+ return (<TouchableOpacity key={chip.key} onPress={() => setFilterType(chip.key)} style={[
357
+ localStyles.filterChip,
358
+ isActive && localStyles.filterChipActive,
359
+ ]}>
360
+ <chip.Icon size={12} color={isActive ? AppColors.white : AppColors.grayTextStrong}/>
361
+ <Text style={[
362
+ localStyles.filterChipText,
363
+ isActive && localStyles.filterChipTextActive,
364
+ ]}>
365
+ {chip.label}
366
+ </Text>
367
+ </TouchableOpacity>);
368
+ })}
369
+ </ScrollView>
370
+ </View>
371
+ </>)}
365
372
 
366
373
  {/* ─── Crash List or Empty State ─── */}
367
374
  {filteredList.length === 0 ? (<ScrollView contentContainerStyle={localStyles.emptyContainer} showsVerticalScrollIndicator={false}>
@@ -374,7 +381,7 @@ const CrashTab = React.memo(() => {
374
381
  ? t('crash.emptySearchSubtitle')
375
382
  : t('crash.emptySubtitle')}
376
383
  </Text>
377
- </ScrollView>) : (<FlatList data={filteredList} keyExtractor={item => item.id} renderItem={renderCard} contentContainerStyle={localStyles.listContent} showsVerticalScrollIndicator={false} ListFooterComponent={<EndOfListFooter />}/>)}
384
+ </ScrollView>) : (<FlatList data={filteredList} keyExtractor={item => item.id} renderItem={renderCard} initialNumToRender={12} maxToRenderPerBatch={8} windowSize={5} removeClippedSubviews={true} renderToHardwareTextureAndroid={true} contentContainerStyle={localStyles.listContent} showsVerticalScrollIndicator={false} ListFooterComponent={<EndOfListFooter />}/>)}
378
385
 
379
386
  {/* Filter Modal */}
380
387
  <CrashFilterModal visible={isFilterModalOpen} onClose={() => setIsFilterModalOpen(false)} filters={crashFilters} onApply={setCrashFilters} searchQuery={searchQuery}/>
@@ -650,10 +657,11 @@ const localStyles = StyleSheet.create({
650
657
  color: AppColors.grayTextWeak,
651
658
  },
652
659
  emptyContainer: {
660
+ flexGrow: 1,
653
661
  alignItems: 'center',
654
662
  justifyContent: 'center',
655
663
  padding: 32,
656
- marginTop: 40,
664
+ paddingBottom: 80,
657
665
  },
658
666
  emptyIconCircle: {
659
667
  width: 72,