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
@@ -113,8 +113,8 @@ const MainScreen = () => {
113
113
  visible={visible}
114
114
  animationType={modalAnimationType}
115
115
  transparent
116
- statusBarTranslucent={true}>
117
- {visible && (
116
+ statusBarTranslucent={true}
117
+ onRequestClose={closeModal}>
118
118
  <ErrorBoundary onClose={closeModal}>
119
119
  <View style={styles.modalBackdrop}>
120
120
  <Pressable
@@ -246,7 +246,6 @@ const MainScreen = () => {
246
246
  </View>
247
247
  </View>
248
248
  </ErrorBoundary>
249
- )}
250
249
  {hasNavigationContext && (
251
250
  <NavigationTracker onStateChange={setNavState} />
252
251
  )}
@@ -227,94 +227,6 @@ const NetworkTab = React.memo(() => {
227
227
  [quickCounts],
228
228
  );
229
229
 
230
- const SEARCH_SCOPES: Array<{
231
- id: SearchScope;
232
- label: string;
233
- icon: (color: string) => React.JSX.Element;
234
- }> = [
235
- {
236
- id: 'all',
237
- label: 'All',
238
- icon: color => <LayersIcon size={9.5} color={color} />,
239
- },
240
- {
241
- id: 'url',
242
- label: 'URL / Path',
243
- icon: color => <GlobeIcon size={9.5} color={color} />,
244
- },
245
- {
246
- id: 'reqBody',
247
- label: 'Payload',
248
- icon: color => <RequestIcon size={9.5} color={color} />,
249
- },
250
- {
251
- id: 'resBody',
252
- label: 'Response',
253
- icon: color => <ResponseIcon size={9.5} color={color} />,
254
- },
255
- {
256
- id: 'headers',
257
- label: 'Headers',
258
- icon: color => <HeadersIcon size={9.5} color={color} />,
259
- },
260
- ];
261
-
262
- const QUICK_API_QUERY_TAGS = useMemo(
263
- () => [
264
- {
265
- label: 'Failed',
266
- query: 'is:error',
267
- icon: (color: string) => <CircleXIcon size={9.5} color={color} />,
268
- },
269
- {
270
- label: 'Slow (>1s)',
271
- query: 'slow:>1s',
272
- icon: (color: string) => <ClockIcon size={9.5} color={color} />,
273
- },
274
- {
275
- label: 'POST',
276
- query: 'm:POST',
277
- icon: (color: string) => <RequestIcon size={9.5} color={color} />,
278
- },
279
- {
280
- label: 'GET',
281
- query: 'm:GET',
282
- icon: (color: string) => <ResponseIcon size={9.5} color={color} />,
283
- },
284
- {
285
- label: 'GraphQL',
286
- query: 'is:graphql',
287
- icon: (color: string) => <AtomIcon size={9.5} color={color} />,
288
- },
289
- {
290
- label: 'Axios',
291
- query: 'client:axios',
292
- icon: (color: string) => <BoltIcon size={9.5} color={color} />,
293
- },
294
- {
295
- label: '200 OK',
296
- query: 'status:200',
297
- icon: (color: string) => <CircleCheckIcon size={9.5} color={color} />,
298
- },
299
- {
300
- label: '404',
301
- query: 'status:404',
302
- icon: (color: string) => <CircleAlertIcon size={9.5} color={color} />,
303
- },
304
- {
305
- label: '500',
306
- query: 'status:500',
307
- icon: (color: string) => <CircleXIcon size={9.5} color={color} />,
308
- },
309
- {
310
- label: 'HTTPS',
311
- query: 'is:https',
312
- icon: (color: string) => <ShieldAlertIcon size={9.5} color={color} />,
313
- },
314
- ],
315
- [],
316
- );
317
-
318
230
  const networkMetrics = useMemo(() => {
319
231
  if (!logs || logs.length === 0) return null;
320
232
  let successCount = 0;
@@ -523,7 +435,7 @@ const NetworkTab = React.memo(() => {
523
435
  size={15}
524
436
  />
525
437
  <TextInput
526
- placeholder="Search url, body, is:error, slow:>1s..."
438
+ placeholder="Search URL, status, method, headers, body..."
527
439
  placeholderTextColor={AppColors.grayTextWeak}
528
440
  value={search}
529
441
  onChangeText={setSearch}
@@ -534,69 +446,6 @@ const NetworkTab = React.memo(() => {
534
446
  autoCapitalize="none"
535
447
  />
536
448
 
537
- {/* Match Case (Aa) & Regex (.*) Quick Toggles */}
538
- <View
539
- style={{
540
- flexDirection: 'row',
541
- alignItems: 'center',
542
- gap: 3,
543
- marginRight: 2,
544
- }}>
545
- <Pressable
546
- hitSlop={8}
547
- onPress={() => setIsCaseSensitive(prev => !prev)}
548
- style={{
549
- paddingHorizontal: 4.5,
550
- paddingVertical: 2,
551
- borderRadius: 4,
552
- backgroundColor: isCaseSensitive
553
- ? AppColors.purple
554
- : 'transparent',
555
- borderWidth: 1,
556
- borderColor: isCaseSensitive
557
- ? AppColors.purple
558
- : `${AppColors.grayBorderSecondary}`,
559
- }}>
560
- <Text
561
- style={{
562
- fontFamily: AppFonts.interBold,
563
- fontSize: 9.5,
564
- color: isCaseSensitive
565
- ? AppColors.white
566
- : AppColors.grayTextWeak,
567
- }}>
568
- Aa
569
- </Text>
570
- </Pressable>
571
-
572
- <Pressable
573
- hitSlop={8}
574
- onPress={() => setIsRegexSearch(prev => !prev)}
575
- style={{
576
- paddingHorizontal: 4.5,
577
- paddingVertical: 2,
578
- borderRadius: 4,
579
- backgroundColor: isRegexSearch
580
- ? AppColors.purple
581
- : 'transparent',
582
- borderWidth: 1,
583
- borderColor: isRegexSearch
584
- ? AppColors.purple
585
- : `${AppColors.grayBorderSecondary}`,
586
- }}>
587
- <Text
588
- style={{
589
- fontFamily: AppFonts.interBold,
590
- fontSize: 9.5,
591
- color: isRegexSearch
592
- ? AppColors.white
593
- : AppColors.grayTextWeak,
594
- }}>
595
- .*
596
- </Text>
597
- </Pressable>
598
- </View>
599
-
600
449
  {search.length > 0 && (
601
450
  <View
602
451
  style={{
@@ -721,113 +570,6 @@ const NetworkTab = React.memo(() => {
721
570
  </View>
722
571
  </View>
723
572
 
724
- {/* Quick Search Scope & Filter Tags Row */}
725
- <View style={{paddingBottom: 4}}>
726
- <ScrollView
727
- horizontal
728
- showsHorizontalScrollIndicator={false}
729
- contentContainerStyle={{
730
- paddingHorizontal: 12,
731
- flexDirection: 'row',
732
- alignItems: 'center',
733
- gap: 5,
734
- paddingVertical: 2,
735
- }}>
736
- {/* Search Scopes */}
737
- {SEARCH_SCOPES.map(s => {
738
- const isActive = searchScope === s.id;
739
- return (
740
- <TouchableScale
741
- key={`scope_${s.id}`}
742
- onPress={() => setSearchScope(s.id)}
743
- hitSlop={6}
744
- style={{
745
- flexDirection: 'row',
746
- alignItems: 'center',
747
- gap: 3.5,
748
- paddingHorizontal: 7.5,
749
- paddingVertical: 3.5,
750
- borderRadius: 6,
751
- backgroundColor: isActive
752
- ? AppColors.purple
753
- : AppColors.grayBackground,
754
- borderWidth: 1,
755
- borderColor: isActive
756
- ? AppColors.purple
757
- : AppColors.grayBorderSecondary,
758
- }}>
759
- {s.icon(isActive ? AppColors.white : AppColors.grayTextWeak)}
760
- <Text
761
- style={{
762
- fontFamily: AppFonts.interBold,
763
- fontSize: 10,
764
- color: isActive ? AppColors.white : AppColors.grayText,
765
- }}>
766
- {s.label}
767
- </Text>
768
- </TouchableScale>
769
- );
770
- })}
771
-
772
- <View
773
- style={{
774
- width: 1,
775
- height: 14,
776
- backgroundColor: AppColors.dividerColor,
777
- marginHorizontal: 3,
778
- }}
779
- />
780
-
781
- {/* Quick Query Suggestions */}
782
- {QUICK_API_QUERY_TAGS.map(q => {
783
- const isActive = search.includes(q.query);
784
- return (
785
- <TouchableScale
786
- key={`quick_${q.query}`}
787
- onPress={() => {
788
- setSearch(prev => {
789
- if (prev.includes(q.query)) {
790
- return prev
791
- .replace(q.query, '')
792
- .replace(/\s+/g, ' ')
793
- .trim();
794
- }
795
- return `${prev} ${q.query}`.trim();
796
- });
797
- }}
798
- hitSlop={6}
799
- style={{
800
- flexDirection: 'row',
801
- alignItems: 'center',
802
- gap: 3.5,
803
- paddingHorizontal: 7.5,
804
- paddingVertical: 3.5,
805
- borderRadius: 6,
806
- backgroundColor: isActive
807
- ? `${AppColors.purple}20`
808
- : AppColors.grayBackground,
809
- borderWidth: 1,
810
- borderColor: isActive
811
- ? AppColors.purple
812
- : AppColors.grayBorderSecondary,
813
- }}>
814
- {q.icon(isActive ? AppColors.purple : AppColors.grayTextWeak)}
815
- <Text
816
- style={{
817
- fontFamily: AppFonts.interMedium,
818
- fontSize: 10,
819
- color: isActive
820
- ? AppColors.purple
821
- : AppColors.grayText,
822
- }}>
823
- {q.label}
824
- </Text>
825
- </TouchableScale>
826
- );
827
- })}
828
- </ScrollView>
829
- </View>
830
-
831
573
  {/* Quick Filter Horizontal Chips Bar with Live Counts */}
832
574
  <View style={{marginBottom: 8}}>
833
575
  <ScrollView
@@ -967,138 +709,6 @@ const NetworkTab = React.memo(() => {
967
709
  </ScrollView>
968
710
  </View>
969
711
 
970
- {/* Search Scope Selector Bar (Visible on search or focus) */}
971
- {(isSearchFocused || search.length > 0) && (
972
- <View
973
- style={{
974
- flexDirection: 'row',
975
- alignItems: 'center',
976
- paddingHorizontal: 12,
977
- marginBottom: 6,
978
- gap: 6,
979
- }}>
980
- <Text
981
- style={{
982
- fontFamily: AppFonts.interBold,
983
- fontSize: 9.5,
984
- color: AppColors.grayTextWeak,
985
- textTransform: 'uppercase',
986
- letterSpacing: 0.5,
987
- }}>
988
- Scope:
989
- </Text>
990
- <ScrollView
991
- horizontal
992
- showsHorizontalScrollIndicator={false}
993
- contentContainerStyle={{
994
- flexDirection: 'row',
995
- alignItems: 'center',
996
- gap: 5,
997
- }}>
998
- {SEARCH_SCOPES.map(s => {
999
- const isSelected = searchScope === s.id;
1000
- const iconColor = isSelected ? AppColors.white : AppColors.grayText;
1001
- return (
1002
- <TouchableScale
1003
- key={s.id}
1004
- onPress={() => setSearchScope(s.id)}>
1005
- <View
1006
- style={{
1007
- flexDirection: 'row',
1008
- alignItems: 'center',
1009
- gap: 4,
1010
- paddingHorizontal: 7,
1011
- paddingVertical: 2.5,
1012
- borderRadius: 5,
1013
- backgroundColor: isSelected
1014
- ? AppColors.purple
1015
- : `${AppColors.grayBorderSecondary}40`,
1016
- borderWidth: 1,
1017
- borderColor: isSelected
1018
- ? AppColors.purple
1019
- : AppColors.grayBorderSecondary,
1020
- }}>
1021
- {s.icon(iconColor)}
1022
- <Text
1023
- style={{
1024
- fontFamily: AppFonts.interBold,
1025
- fontSize: 9.5,
1026
- color: isSelected
1027
- ? AppColors.white
1028
- : AppColors.grayText,
1029
- }}>
1030
- {s.label}
1031
- </Text>
1032
- </View>
1033
- </TouchableScale>
1034
- );
1035
- })}
1036
- </ScrollView>
1037
- </View>
1038
- )}
1039
-
1040
- {/* Advanced Search Quick Query Suggestions Bar */}
1041
- {(isSearchFocused || search.length > 0) && (
1042
- <ScrollView
1043
- horizontal
1044
- showsHorizontalScrollIndicator={false}
1045
- style={{marginBottom: 8, maxHeight: 28}}
1046
- contentContainerStyle={{
1047
- paddingHorizontal: 12,
1048
- flexDirection: 'row',
1049
- alignItems: 'center',
1050
- gap: 5,
1051
- }}>
1052
- {QUICK_API_QUERY_TAGS.map(item => {
1053
- const isSelected = search.includes(item.query);
1054
- return (
1055
- <TouchableScale
1056
- key={item.query}
1057
- onPress={() => {
1058
- if (isSelected) {
1059
- setSearch(prev =>
1060
- prev.replace(item.query, '').trim(),
1061
- );
1062
- } else {
1063
- setSearch(prev =>
1064
- prev ? `${prev} ${item.query}`.trim() : item.query,
1065
- );
1066
- }
1067
- }}>
1068
- <View
1069
- style={{
1070
- flexDirection: 'row',
1071
- alignItems: 'center',
1072
- gap: 4,
1073
- paddingHorizontal: 7,
1074
- paddingVertical: 2.5,
1075
- borderRadius: 5,
1076
- backgroundColor: isSelected
1077
- ? AppColors.purple
1078
- : `${AppColors.purple}14`,
1079
- borderWidth: 1,
1080
- borderColor: isSelected
1081
- ? AppColors.purple
1082
- : `${AppColors.purple}30`,
1083
- }}>
1084
- {item.icon(isSelected ? AppColors.white : AppColors.purple)}
1085
- <Text
1086
- style={{
1087
- fontFamily: AppFonts.interBold,
1088
- fontSize: 9.5,
1089
- color: isSelected
1090
- ? AppColors.white
1091
- : AppColors.purple,
1092
- }}>
1093
- {item.label}
1094
- </Text>
1095
- </View>
1096
- </TouchableScale>
1097
- );
1098
- })}
1099
- </ScrollView>
1100
- )}
1101
-
1102
712
  {/* Active Filter Helper Status Bar */}
1103
713
  {(quickFilter !== 'all' ||
1104
714
  search.trim().length > 0 ||
@@ -1145,17 +755,6 @@ const NetworkTab = React.memo(() => {
1145
755
  </TouchableScale>
1146
756
  </View>
1147
757
  )}
1148
-
1149
- {(search ||
1150
- statusFilters.size > 0 ||
1151
- methodFilters.size > 0 ||
1152
- quickFilter !== 'all') && (
1153
- <Text style={styles.resultCount}>
1154
- {filteredLogs.length === logs.length
1155
- ? `${logs.length} requests`
1156
- : `${filteredLogs.length} of ${logs.length} filtered requests`}
1157
- </Text>
1158
- )}
1159
758
  </View>
1160
759
  }
1161
760
  ListEmptyComponent={
@@ -248,11 +248,19 @@ const PerformanceTab = React.memo(() => {
248
248
  // Filtered Events
249
249
  const filteredEvents = useMemo(() => {
250
250
  return events.filter(e => {
251
- const matchSearch =
252
- !search ||
253
- e.label.toLowerCase().includes(search.toLowerCase()) ||
254
- e.detail.toLowerCase().includes(search.toLowerCase()) ||
255
- (e.source && e.source.toLowerCase().includes(search.toLowerCase()));
251
+ let matchSearch = true;
252
+ if (search && search.trim().length > 0) {
253
+ const queryTokens = search.trim().toLowerCase().split(/\s+/).filter(Boolean);
254
+ const corpus = [
255
+ e.label || '',
256
+ e.detail || '',
257
+ e.source || '',
258
+ e.category || '',
259
+ e.severity || '',
260
+ e.bottleneckThread || '',
261
+ ].join(' ').toLowerCase();
262
+ matchSearch = queryTokens.every(tok => corpus.includes(tok));
263
+ }
256
264
 
257
265
  const matchFilter =
258
266
  filterCategory === 'ALL'
@@ -1052,10 +1060,31 @@ const PerformanceTab = React.memo(() => {
1052
1060
  {filteredEvents.length === 0 ? (
1053
1061
  <View style={perfStyles.emptyCard}>
1054
1062
  <BoltIcon color={AppColors.amber500} size={30} />
1055
- <Text style={perfStyles.emptyTitle}>{t('performance.emptyTitle')}</Text>
1063
+ <Text style={perfStyles.emptyTitle}>
1064
+ {search.trim().length > 0 ? 'No Matching Events' : t('performance.emptyTitle')}
1065
+ </Text>
1056
1066
  <Text style={perfStyles.emptySub}>
1057
- {t('performance.emptySubtitle')}
1067
+ {search.trim().length > 0
1068
+ ? `No performance events match "${search}"`
1069
+ : t('performance.emptySubtitle')}
1058
1070
  </Text>
1071
+ {search.trim().length > 0 && (
1072
+ <TouchableScale
1073
+ onPress={() => setSearch('')}
1074
+ style={{
1075
+ marginTop: 10,
1076
+ paddingHorizontal: 14,
1077
+ paddingVertical: 6,
1078
+ borderRadius: 8,
1079
+ backgroundColor: AppColors.grayBackground,
1080
+ borderWidth: 1,
1081
+ borderColor: AppColors.grayBorderSecondary,
1082
+ }}>
1083
+ <Text style={{color: AppColors.brandPurple, fontSize: 12, fontFamily: AppFonts.interBold}}>
1084
+ Clear Search
1085
+ </Text>
1086
+ </TouchableScale>
1087
+ )}
1059
1088
  </View>
1060
1089
  ) : (
1061
1090
  filteredEvents.map(event => {
@@ -670,7 +670,15 @@ const ReduxTab = React.memo(() => {
670
670
  renderToHardwareTextureAndroid={true}
671
671
  ListEmptyComponent={
672
672
  <EmptyState
673
- isSearch={reduxSearch.length > 0}
673
+ isSearch={reduxSearch.trim().length > 0}
674
+ searchQuery={reduxSearch}
675
+ onClearSearch={() => setReduxSearch('')}
676
+ customTitle={reduxSearch.trim().length > 0 ? 'No Matching Slices' : 'No Redux State Detected'}
677
+ customSub={
678
+ reduxSearch.trim().length > 0
679
+ ? `No Redux slices match "${reduxSearch}"`
680
+ : 'Attach the Redux store or dispatch actions to see state slices.'
681
+ }
674
682
  />
675
683
  }
676
684
  ListFooterComponent={
@@ -356,10 +356,11 @@ export const StorageTab = React.memo(() => {
356
356
  // Filter entries
357
357
  const filteredEntries = useMemo(() => {
358
358
  if (!search.trim()) return entries;
359
- const q = search.toLowerCase().trim();
360
- return entries.filter(
361
- e => e.key.toLowerCase().includes(q) || e.value.toLowerCase().includes(q),
362
- );
359
+ const tokens = search.toLowerCase().trim().split(/\s+/).filter(Boolean);
360
+ return entries.filter(e => {
361
+ const corpus = `${e.key} ${e.value}`.toLowerCase();
362
+ return tokens.every(tok => corpus.includes(tok));
363
+ });
363
364
  }, [entries, search]);
364
365
 
365
366
  const totalBytes = useMemo(() => {
@@ -585,13 +586,32 @@ export const StorageTab = React.memo(() => {
585
586
  <DatabaseIcon size={28} color={AppColors.grayTextWeak} />
586
587
  </View>
587
588
  <Text style={styles.emptyTitle}>
588
- {search ? 'No matching keys found' : `No ${activeDriver === 'asyncStorage' ? 'AsyncStorage' : 'MMKV'} Keys`}
589
+ {search.trim().length > 0
590
+ ? 'No matching keys found'
591
+ : `No ${activeDriver === 'asyncStorage' ? 'AsyncStorage' : 'MMKV'} Keys`}
589
592
  </Text>
590
593
  <Text style={styles.emptySubtitle}>
591
- {search
592
- ? 'Try modifying your search query'
594
+ {search.trim().length > 0
595
+ ? `No keys or values match "${search}"`
593
596
  : `Storage is auto-detected. Tap "+ Add" above to create your first ${activeDriver === 'asyncStorage' ? 'AsyncStorage' : 'MMKV'} key!`}
594
597
  </Text>
598
+ {search.trim().length > 0 && (
599
+ <TouchableScale
600
+ onPress={() => setSearch('')}
601
+ style={{
602
+ marginTop: 12,
603
+ paddingHorizontal: 14,
604
+ paddingVertical: 6,
605
+ borderRadius: 8,
606
+ backgroundColor: AppColors.grayBackground,
607
+ borderWidth: 1,
608
+ borderColor: AppColors.grayBorderSecondary,
609
+ }}>
610
+ <Text style={{color: AppColors.brandPurple, fontSize: 12, fontFamily: AppFonts.interBold}}>
611
+ Clear Search
612
+ </Text>
613
+ </TouchableScale>
614
+ )}
595
615
  </View>
596
616
  }
597
617
  ListFooterComponent={<View style={{height: 60}} />}
@@ -186,11 +186,24 @@ const JsonViewer = React.memo(({
186
186
  );
187
187
  }
188
188
 
189
- const keys = Object.keys(data as object);
189
+ let keys = Object.keys(data as object);
190
+ if (search && search.trim().length > 0) {
191
+ const q = search.trim().toLowerCase();
192
+ keys = keys.filter(key => {
193
+ const val = (data as any)[key];
194
+ const valStr = typeof val === 'object' && val !== null ? JSON.stringify(val) : String(val);
195
+ return key.toLowerCase().includes(q) || valStr.toLowerCase().includes(q);
196
+ });
197
+ }
198
+
190
199
  if (keys.length === 0) {
191
200
  return (
192
201
  <View style={localStyles.emptyTable}>
193
- <Text style={localStyles.emptyTableText}>{t('network.jsonViewer.emptyTable')}</Text>
202
+ <Text style={localStyles.emptyTableText}>
203
+ {search && search.trim().length > 0
204
+ ? 'No matching keys or values found'
205
+ : t('network.jsonViewer.emptyTable')}
206
+ </Text>
194
207
  </View>
195
208
  );
196
209
  }