react-native-inapp-inspector 1.1.11 → 1.1.13

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 (53) hide show
  1. package/dist/commonjs/components/AnalyticsDetail.js +93 -479
  2. package/dist/commonjs/components/AnalyticsEventCard.js +25 -25
  3. package/dist/commonjs/components/ConsoleLogCard.js +3 -1
  4. package/dist/commonjs/components/HighlightText.js +1 -1
  5. package/dist/commonjs/components/JsonViewer.d.ts +3 -1
  6. package/dist/commonjs/components/JsonViewer.js +327 -9
  7. package/dist/commonjs/components/MetaAccordion.d.ts +10 -1
  8. package/dist/commonjs/components/MetaAccordion.js +121 -24
  9. package/dist/commonjs/components/NetworkIcons.d.ts +1 -0
  10. package/dist/commonjs/components/NetworkIcons.js +8 -1
  11. package/dist/commonjs/components/ReduxTreeView.d.ts +12 -17
  12. package/dist/commonjs/components/ReduxTreeView.js +1045 -632
  13. package/dist/commonjs/components/SectionHeader.d.ts +1 -1
  14. package/dist/commonjs/components/SectionHeader.js +7 -1
  15. package/dist/commonjs/components/TouchableScale.js +8 -0
  16. package/dist/commonjs/constants/version.d.ts +1 -1
  17. package/dist/commonjs/constants/version.js +1 -1
  18. package/dist/commonjs/customHooks/reduxLogger.d.ts +2 -0
  19. package/dist/commonjs/customHooks/reduxLogger.js +2 -0
  20. package/dist/commonjs/helpers/index.d.ts +3 -0
  21. package/dist/commonjs/helpers/index.js +103 -1
  22. package/dist/commonjs/helpers/settingsStore.d.ts +1 -0
  23. package/dist/commonjs/helpers/settingsStore.js +4 -0
  24. package/dist/commonjs/index.js +385 -339
  25. package/dist/commonjs/types/index.d.ts +3 -0
  26. package/dist/esm/components/AnalyticsDetail.js +94 -480
  27. package/dist/esm/components/AnalyticsEventCard.js +25 -25
  28. package/dist/esm/components/ConsoleLogCard.js +3 -1
  29. package/dist/esm/components/HighlightText.js +3 -3
  30. package/dist/esm/components/JsonViewer.d.ts +3 -1
  31. package/dist/esm/components/JsonViewer.js +295 -10
  32. package/dist/esm/components/MetaAccordion.d.ts +10 -1
  33. package/dist/esm/components/MetaAccordion.js +90 -26
  34. package/dist/esm/components/NetworkIcons.d.ts +1 -0
  35. package/dist/esm/components/NetworkIcons.js +6 -0
  36. package/dist/esm/components/ReduxTreeView.d.ts +12 -17
  37. package/dist/esm/components/ReduxTreeView.js +1047 -629
  38. package/dist/esm/components/SectionHeader.d.ts +1 -1
  39. package/dist/esm/components/SectionHeader.js +8 -2
  40. package/dist/esm/components/TouchableScale.js +9 -1
  41. package/dist/esm/constants/version.d.ts +1 -1
  42. package/dist/esm/constants/version.js +1 -1
  43. package/dist/esm/customHooks/reduxLogger.d.ts +2 -0
  44. package/dist/esm/customHooks/reduxLogger.js +2 -0
  45. package/dist/esm/helpers/index.d.ts +3 -0
  46. package/dist/esm/helpers/index.js +100 -1
  47. package/dist/esm/helpers/settingsStore.d.ts +1 -0
  48. package/dist/esm/helpers/settingsStore.js +3 -0
  49. package/dist/esm/index.js +389 -343
  50. package/dist/esm/types/index.d.ts +3 -0
  51. package/example/package-lock.json +33 -74
  52. package/example/package.json +1 -1
  53. package/package.json +1 -1
package/dist/esm/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import React, { useEffect, useMemo, useRef, useState, useCallback } from 'react';
2
- import { Alert, Animated, StyleSheet, FlatList, LayoutAnimation, Modal, PanResponder, Platform, Pressable, ScrollView, Text, TextInput, View, Linking, Image, InteractionManager, ActivityIndicator, StatusBar, TouchableOpacity, UIManager, LogBox, } from 'react-native';
2
+ import { Alert, Animated, StyleSheet, FlatList, Modal, PanResponder, Platform, Pressable, ScrollView, Text, TextInput, View, Linking, Image, InteractionManager, ActivityIndicator, StatusBar, TouchableOpacity, UIManager, LogBox, } from 'react-native';
3
3
  import Svg, { Circle, Path } from 'react-native-svg';
4
4
  import LinearGradient from 'react-native-linear-gradient';
5
5
  import { useNavigationState, NavigationContext } from '@react-navigation/native';
@@ -11,7 +11,7 @@ import CopyButton from './components/CopyButton';
11
11
  import SectionHeader from './components/SectionHeader';
12
12
  import EmptyState from './components/EmptyState';
13
13
  import JsonViewer from './components/JsonViewer';
14
- import { ReduxTreeView, ReduxActionTimeline, ReduxTimelineIcon, ReduxTreeIcon, } from './components/ReduxTreeView';
14
+ import { ReduxTreeView, } from './components/ReduxTreeView';
15
15
  import DomainHeader from './components/DomainHeader';
16
16
  import DiffViewer from './components/DiffViewer';
17
17
  import LogCard from './components/LogCard';
@@ -22,11 +22,11 @@ import HighlightText from './components/HighlightText';
22
22
  import CodeSnippet from './components/CodeSnippet';
23
23
  import AnimatedEntrance from './components/AnimatedEntrance';
24
24
  // Helpers
25
- import { formatDateTime, getStatusColor, getNavigationInfo, deduplicateLogs, getDomainColor, formatDisplayUrl, getFetchCommand, getCurlCommand, getSize, } from './helpers';
25
+ import { formatDateTime, getStatusColor, getNavigationInfo, deduplicateLogs, getDomainColor, formatDisplayUrl, getFetchCommand, getCurlCommand, getSize, getBundleIdentifier, getAppName, } from './helpers';
26
26
  // #5 — settings persistence
27
27
  import { loadSettings, saveSettings, setCustomStorage, isPersistentStorageAvailable, clearPersistedSettings, } from './helpers/settingsStore';
28
28
  // Assets
29
- import { EmptyRadarIcon, FailIcon, SearchIcon, ScreenIcon, ClearIcon, SortArrowIcon, FilterIcon, InsightsIcon, GlobeIcon, DownloadIcon, CloseWhite, TrashIcon, WhiteBackNavigation, TerminalIcon, SignalIcon, AnalyticsIcon, SunIcon, MoonIcon, BrandCircleIcon, BrandSquareIcon, HtmlIcon, CssIcon, JsIcon, ClockIcon, EyeIcon, CheckIcon, SettingsIcon, RequestIcon, ResponseIcon, HeadersIcon, StatusIcon, ChevronIcon, WipeIcon, LayersIcon, UserIcon, InfoCircleIcon, WarningTriangleIcon, ErrorCircleIcon, TrendingUpIcon, MotionIcon, } from './components/NetworkIcons';
29
+ import { EmptyRadarIcon, FailIcon, SearchIcon, ScreenIcon, ClearIcon, SortArrowIcon, FilterIcon, InsightsIcon, GlobeIcon, DownloadIcon, CloseWhite, TrashIcon, WhiteBackNavigation, TerminalIcon, SignalIcon, AnalyticsIcon, SunIcon, MoonIcon, BrandCircleIcon, HtmlIcon, CssIcon, JsIcon, ClockIcon, EyeIcon, CheckIcon, SettingsIcon, RequestIcon, ResponseIcon, HeadersIcon, StatusIcon, ChevronIcon, WipeIcon, LayersIcon, UserIcon, InfoCircleIcon, WarningTriangleIcon, ErrorCircleIcon, TrendingUpIcon, MotionIcon, } from './components/NetworkIcons';
30
30
  import ErrorBoundary from './components/ErrorBoundary';
31
31
  // Stylesheet
32
32
  import { AppColors } from './styles/AppColors';
@@ -111,7 +111,8 @@ const NavigationTracker = ({ onStateChange }) => {
111
111
  return null;
112
112
  };
113
113
  const animateNextLayout = () => {
114
- LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
114
+ // Disabled LayoutAnimation to prevent iOS NSRangeException crashes under Fabric
115
+ // and Android rendering performance freezes.
115
116
  };
116
117
  const NetworkInspector = ({ enabled = true, storage, navigationRef, }) => {
117
118
  // Set custom storage synchronously during render phase
@@ -135,7 +136,6 @@ const NetworkInspector = ({ enabled = true, storage, navigationRef, }) => {
135
136
  const [search, setSearch] = useState('');
136
137
  const [detailSearch, setDetailSearch] = useState('');
137
138
  const [reduxSearch, setReduxSearch] = useState('');
138
- const [reduxActiveSubTab, setReduxActiveSubTab] = useState('timeline');
139
139
  const [apiDetailActiveTab, setApiDetailActiveTab] = useState('response');
140
140
  useEffect(() => {
141
141
  LogBox.ignoreAllLogs(enabled);
@@ -154,6 +154,7 @@ const NetworkInspector = ({ enabled = true, storage, navigationRef, }) => {
154
154
  const [sectionFilters, setSectionFilters] = useState({});
155
155
  const [collapsedSections, setCollapsedSections] = useState(new Set());
156
156
  const [showNetworkMenu, setShowNetworkMenu] = useState(false);
157
+ const [showHeaderInfo, setShowHeaderInfo] = useState(false);
157
158
  const [showUiMenu, setShowUiMenu] = useState(false);
158
159
  const [sortOrder, setSortOrder] = useState('newest');
159
160
  // #7 — sort order for the Logs (console) tab
@@ -193,7 +194,7 @@ const NetworkInspector = ({ enabled = true, storage, navigationRef, }) => {
193
194
  const [maxConsoleLogs, setMaxConsoleLogs] = useState(200);
194
195
  const [showConsoleLevels, setShowConsoleLevels] = useState({
195
196
  info: true,
196
- warn: true,
197
+ warn: false,
197
198
  error: true,
198
199
  });
199
200
  const visibleConsoleLogs = useMemo(() => {
@@ -702,7 +703,13 @@ const NetworkInspector = ({ enabled = true, storage, navigationRef, }) => {
702
703
  const task = InteractionManager.runAfterInteractions(() => {
703
704
  setIsReady(true);
704
705
  });
705
- return () => task.cancel();
706
+ const fallbackTimer = setTimeout(() => {
707
+ setIsReady(true);
708
+ }, 350);
709
+ return () => {
710
+ task.cancel();
711
+ clearTimeout(fallbackTimer);
712
+ };
706
713
  }
707
714
  else {
708
715
  setIsReady(false);
@@ -715,9 +722,10 @@ const NetworkInspector = ({ enabled = true, storage, navigationRef, }) => {
715
722
  setupConsoleLogger();
716
723
  autoSetupAnalyticsLogger();
717
724
  let timeoutId;
725
+ let isFirstNetworkCall = true;
718
726
  const unsubscribe = subscribeNetworkLogs((raw) => {
719
727
  clearTimeout(timeoutId);
720
- timeoutId = setTimeout(() => {
728
+ const updateNetworkState = () => {
721
729
  const deduped = deduplicateLogs(raw);
722
730
  const incoming = new Set(deduped.map(l => l.id));
723
731
  const freshIds = new Set();
@@ -736,13 +744,24 @@ const NetworkInspector = ({ enabled = true, storage, navigationRef, }) => {
736
744
  setTimeout(() => setNewLogIds(new Set()), 1200);
737
745
  }
738
746
  setLogs(deduped);
739
- }, 250);
747
+ };
748
+ if (isFirstNetworkCall) {
749
+ isFirstNetworkCall = false;
750
+ const deduped = deduplicateLogs(raw);
751
+ const incoming = new Set(deduped.map(l => l.id));
752
+ prevLogIdsRef.current = incoming;
753
+ setLogs(deduped);
754
+ }
755
+ else {
756
+ timeoutId = setTimeout(updateNetworkState, 250);
757
+ }
740
758
  });
741
759
  // ─── Analytics subscription ──────────────────────────────────────────────
742
760
  let analyticsTimeoutId;
761
+ let isFirstAnalyticsCall = true;
743
762
  const unsubscribeAnalytics = subscribeAnalyticsEvents((raw) => {
744
763
  clearTimeout(analyticsTimeoutId);
745
- analyticsTimeoutId = setTimeout(() => {
764
+ const updateAnalyticsState = () => {
746
765
  const incoming = new Set(raw.map(e => e.id));
747
766
  const freshIds = new Set();
748
767
  incoming.forEach(id => {
@@ -760,15 +779,31 @@ const NetworkInspector = ({ enabled = true, storage, navigationRef, }) => {
760
779
  setTimeout(() => setNewEventIds(new Set()), 1200);
761
780
  }
762
781
  setAnalyticsEvents(raw);
763
- }, 200);
782
+ };
783
+ if (isFirstAnalyticsCall) {
784
+ isFirstAnalyticsCall = false;
785
+ const incoming = new Set(raw.map(e => e.id));
786
+ prevEventIdsRef.current = incoming;
787
+ setAnalyticsEvents(raw);
788
+ }
789
+ else {
790
+ analyticsTimeoutId = setTimeout(updateAnalyticsState, 200);
791
+ }
764
792
  });
765
793
  // ─── Console subscription ────────────────────────────────────────────────
766
794
  let consoleTimeoutId;
795
+ let isFirstConsoleCall = true;
767
796
  const unsubscribeConsole = subscribeConsoleLogs((raw) => {
768
797
  clearTimeout(consoleTimeoutId);
769
- consoleTimeoutId = setTimeout(() => {
798
+ if (isFirstConsoleCall) {
799
+ isFirstConsoleCall = false;
770
800
  setConsoleLogs(raw);
771
- }, 200);
801
+ }
802
+ else {
803
+ consoleTimeoutId = setTimeout(() => {
804
+ setConsoleLogs(raw);
805
+ }, 200);
806
+ }
772
807
  });
773
808
  setWebViewLogs(getWebViewLogs());
774
809
  setWebViewNavHistory(getWebViewNavHistory());
@@ -778,24 +813,34 @@ const NetworkInspector = ({ enabled = true, storage, navigationRef, }) => {
778
813
  setWebViewHtmlUrl(getWebViewHtmlUrl());
779
814
  // ─── WebView subscription ────────────────────────────────────────────────
780
815
  let webViewTimeoutId;
816
+ let isFirstWebViewCall = true;
781
817
  const unsubscribeWebView = subscribeWebView(() => {
782
818
  clearTimeout(webViewTimeoutId);
783
- webViewTimeoutId = setTimeout(() => {
819
+ const updateWebViewState = () => {
784
820
  setWebViewLogs(getWebViewLogs());
785
821
  setWebViewNavHistory(getWebViewNavHistory());
786
822
  setWebViewHtml(getWebViewHtml());
787
823
  setWebViewCss(getWebViewCss());
788
824
  setWebViewJs(getWebViewJs());
789
825
  setWebViewHtmlUrl(getWebViewHtmlUrl());
790
- }, 200);
826
+ };
827
+ if (isFirstWebViewCall) {
828
+ isFirstWebViewCall = false;
829
+ updateWebViewState();
830
+ }
831
+ else {
832
+ webViewTimeoutId = setTimeout(updateWebViewState, 200);
833
+ }
791
834
  });
792
- setReduxState(getReduxState());
835
+ const initialReduxState = getReduxState();
836
+ setReduxState(initialReduxState && typeof initialReduxState === 'object' ? { ...initialReduxState } : initialReduxState);
793
837
  setReduxActionHistory([...getActionHistory()]);
794
838
  setReduxLastActionMap({ ...getLastActionForReducer() });
795
839
  const unsubscribeRedux = subscribeReduxState(() => {
796
840
  // New references each time guarantee the Redux tab updates live, even when
797
841
  // the root state object reference is unchanged or auto-refresh is paused.
798
- setReduxState(getReduxState());
842
+ const freshState = getReduxState();
843
+ setReduxState(freshState && typeof freshState === 'object' ? { ...freshState } : freshState);
799
844
  setReduxActionHistory([...getActionHistory()]);
800
845
  setReduxLastActionMap({ ...getLastActionForReducer() });
801
846
  });
@@ -2106,6 +2151,97 @@ const NetworkInspector = ({ enabled = true, storage, navigationRef, }) => {
2106
2151
  </TouchableScale>
2107
2152
  </View>
2108
2153
 
2154
+ {/* Divider */}
2155
+ <View style={{
2156
+ height: 1,
2157
+ backgroundColor: AppColors.dividerColor,
2158
+ }}/>
2159
+
2160
+ {/* Logs Console Levels */}
2161
+ <View style={{
2162
+ paddingVertical: 12,
2163
+ paddingHorizontal: 14,
2164
+ }}>
2165
+ <View style={{
2166
+ flexDirection: 'row',
2167
+ alignItems: 'center',
2168
+ gap: 8,
2169
+ }}>
2170
+ <View style={{
2171
+ width: 20,
2172
+ height: 20,
2173
+ borderRadius: 6,
2174
+ backgroundColor: AppColors.purpleShade50,
2175
+ borderWidth: 1,
2176
+ borderColor: 'rgba(104,75,155,0.2)',
2177
+ alignItems: 'center',
2178
+ justifyContent: 'center',
2179
+ }}>
2180
+ <TerminalIcon color={AppColors.purple} size={11}/>
2181
+ </View>
2182
+ <View style={{ flex: 1 }}>
2183
+ <Text style={{
2184
+ fontFamily: AppFonts.interBold,
2185
+ fontSize: 13,
2186
+ color: AppColors.primaryBlack,
2187
+ }}>
2188
+ Logs Console Levels
2189
+ </Text>
2190
+ <Text style={{
2191
+ fontFamily: AppFonts.interRegular,
2192
+ fontSize: 11,
2193
+ color: AppColors.grayText,
2194
+ marginTop: 1,
2195
+ }}>
2196
+ Toggle which log levels are visible in the Logs tab
2197
+ </Text>
2198
+ </View>
2199
+ </View>
2200
+
2201
+ <View style={{
2202
+ flexDirection: 'row',
2203
+ gap: 8,
2204
+ marginTop: 10,
2205
+ }}>
2206
+ {([
2207
+ { key: 'info', label: 'Info', color: '#3B82F6' },
2208
+ { key: 'warn', label: 'Warning', color: '#F59E0B' },
2209
+ { key: 'error', label: 'Error', color: '#EF4444' },
2210
+ ]).map(level => {
2211
+ const isActive = showConsoleLevels[level.key];
2212
+ return (<TouchableScale key={level.key} onPress={() => setShowConsoleLevels(prev => ({
2213
+ ...prev,
2214
+ [level.key]: !prev[level.key],
2215
+ }))} style={{
2216
+ flex: 1,
2217
+ flexDirection: 'row',
2218
+ alignItems: 'center',
2219
+ justifyContent: 'center',
2220
+ gap: 6,
2221
+ paddingVertical: 8,
2222
+ borderRadius: 8,
2223
+ borderWidth: 1.5,
2224
+ borderColor: isActive ? level.color : AppColors.grayBorderSecondary,
2225
+ backgroundColor: isActive ? `${level.color}0D` : AppColors.primaryLight,
2226
+ }}>
2227
+ <View style={{
2228
+ width: 8,
2229
+ height: 8,
2230
+ borderRadius: 4,
2231
+ backgroundColor: isActive ? level.color : AppColors.grayBorderSecondary,
2232
+ }}/>
2233
+ <Text style={{
2234
+ fontFamily: AppFonts.interBold,
2235
+ fontSize: 11,
2236
+ color: isActive ? level.color : AppColors.grayText,
2237
+ }}>
2238
+ {level.label}
2239
+ </Text>
2240
+ </TouchableScale>);
2241
+ })}
2242
+ </View>
2243
+ </View>
2244
+
2109
2245
  {/* Divider */}
2110
2246
  <View style={{
2111
2247
  height: 1,
@@ -3435,129 +3571,8 @@ const NetworkInspector = ({ enabled = true, storage, navigationRef, }) => {
3435
3571
  <Text style={styles.emptySub}>Connected store state is empty.</Text>
3436
3572
  </View>);
3437
3573
  }
3438
- // Build hierarchical tree: Store -> Reducers -> Action -> Data.
3439
- // These come from component state (refreshed on every dispatch) so the view stays live.
3440
3574
  const lastActionMap = reduxLastActionMap;
3441
- const actionHistory = reduxActionHistory;
3442
3575
  return (<ScrollView style={styles.detailScroll} contentContainerStyle={{ paddingBottom: 24 }}>
3443
- {/* Tab View Selection Segments */}
3444
- <View style={{
3445
- flexDirection: 'row',
3446
- backgroundColor: AppColors.grayBackground,
3447
- borderRadius: 10,
3448
- padding: 3,
3449
- marginHorizontal: 16,
3450
- marginTop: 12,
3451
- marginBottom: 12,
3452
- borderWidth: 1,
3453
- borderColor: AppColors.dividerColor,
3454
- }}>
3455
- <TouchableOpacity onPress={() => {
3456
- animateNextLayout();
3457
- setReduxActiveSubTab('timeline');
3458
- }} style={{
3459
- flex: 1,
3460
- paddingVertical: 6,
3461
- alignItems: 'center',
3462
- justifyContent: 'center',
3463
- borderRadius: 8,
3464
- backgroundColor: reduxActiveSubTab === 'timeline'
3465
- ? AppColors.purple
3466
- : 'transparent',
3467
- }}>
3468
- <View style={{
3469
- flexDirection: 'row',
3470
- alignItems: 'center',
3471
- justifyContent: 'center',
3472
- gap: 6,
3473
- }}>
3474
- <ReduxTimelineIcon color={reduxActiveSubTab === 'timeline'
3475
- ? '#FFFFFF'
3476
- : AppColors.grayText} size={13}/>
3477
- <Text style={{
3478
- fontFamily: AppFonts.interBold,
3479
- fontSize: 11,
3480
- color: reduxActiveSubTab === 'timeline'
3481
- ? '#FFFFFF'
3482
- : AppColors.grayText,
3483
- }}>
3484
- Action Timeline
3485
- </Text>
3486
- <View style={{
3487
- minWidth: 18,
3488
- paddingHorizontal: 5,
3489
- height: 16,
3490
- borderRadius: 8,
3491
- alignItems: 'center',
3492
- justifyContent: 'center',
3493
- backgroundColor: reduxActiveSubTab === 'timeline'
3494
- ? 'rgba(255,255,255,0.28)'
3495
- : AppColors.dividerColor,
3496
- }}>
3497
- <Text style={{
3498
- fontFamily: AppFonts.interBold,
3499
- fontSize: 9,
3500
- color: reduxActiveSubTab === 'timeline'
3501
- ? '#FFFFFF'
3502
- : AppColors.grayText,
3503
- }}>
3504
- {actionHistory.length}
3505
- </Text>
3506
- </View>
3507
- </View>
3508
- </TouchableOpacity>
3509
- <TouchableOpacity onPress={() => {
3510
- animateNextLayout();
3511
- setReduxActiveSubTab('tree');
3512
- }} style={{
3513
- flex: 1,
3514
- paddingVertical: 6,
3515
- alignItems: 'center',
3516
- justifyContent: 'center',
3517
- borderRadius: 8,
3518
- backgroundColor: reduxActiveSubTab === 'tree' ? AppColors.purple : 'transparent',
3519
- }}>
3520
- <View style={{
3521
- flexDirection: 'row',
3522
- alignItems: 'center',
3523
- justifyContent: 'center',
3524
- gap: 6,
3525
- }}>
3526
- <ReduxTreeIcon color={reduxActiveSubTab === 'tree' ? '#FFFFFF' : AppColors.grayText} size={13}/>
3527
- <Text style={{
3528
- fontFamily: AppFonts.interBold,
3529
- fontSize: 11,
3530
- color: reduxActiveSubTab === 'tree'
3531
- ? '#FFFFFF'
3532
- : AppColors.grayText,
3533
- }}>
3534
- Store Tree
3535
- </Text>
3536
- <View style={{
3537
- minWidth: 18,
3538
- paddingHorizontal: 5,
3539
- height: 16,
3540
- borderRadius: 8,
3541
- alignItems: 'center',
3542
- justifyContent: 'center',
3543
- backgroundColor: reduxActiveSubTab === 'tree'
3544
- ? 'rgba(255,255,255,0.28)'
3545
- : AppColors.dividerColor,
3546
- }}>
3547
- <Text style={{
3548
- fontFamily: AppFonts.interBold,
3549
- fontSize: 9,
3550
- color: reduxActiveSubTab === 'tree'
3551
- ? '#FFFFFF'
3552
- : AppColors.grayText,
3553
- }}>
3554
- {reducerKeys.length}
3555
- </Text>
3556
- </View>
3557
- </View>
3558
- </TouchableOpacity>
3559
- </View>
3560
-
3561
3576
  {/* Search Bar */}
3562
3577
  <View style={{
3563
3578
  flexDirection: 'row',
@@ -3565,15 +3580,14 @@ const NetworkInspector = ({ enabled = true, storage, navigationRef, }) => {
3565
3580
  backgroundColor: AppColors.grayBackground,
3566
3581
  borderRadius: 8,
3567
3582
  marginHorizontal: 16,
3583
+ marginTop: 16,
3568
3584
  marginBottom: 12,
3569
3585
  paddingHorizontal: 10,
3570
3586
  borderWidth: 1,
3571
3587
  borderColor: AppColors.dividerColor,
3572
3588
  height: 36,
3573
3589
  }}>
3574
- <TextInput placeholder={reduxActiveSubTab === 'timeline'
3575
- ? 'Search actions or payloads...'
3576
- : 'Search Redux keys or values...'} placeholderTextColor={AppColors.grayTextWeak} value={reduxSearch} onChangeText={setReduxSearch} style={{
3590
+ <TextInput placeholder="Search Redux keys or values..." placeholderTextColor={AppColors.grayTextWeak} value={reduxSearch} onChangeText={setReduxSearch} style={{
3577
3591
  flex: 1,
3578
3592
  fontFamily: AppFonts.interRegular,
3579
3593
  fontSize: 12,
@@ -3594,7 +3608,7 @@ const NetworkInspector = ({ enabled = true, storage, navigationRef, }) => {
3594
3608
  marginHorizontal: 16,
3595
3609
  padding: 12,
3596
3610
  }}>
3597
- {reduxActiveSubTab === 'timeline' ? (<ReduxActionTimeline history={actionHistory} onClear={clearActionHistory} search={reduxSearch}/>) : (<ReduxTreeView state={reduxState} lastActionMap={lastActionMap} search={reduxSearch}/>)}
3611
+ <ReduxTreeView state={reduxState} lastActionMap={lastActionMap} search={reduxSearch}/>
3598
3612
  </View>
3599
3613
  </ScrollView>);
3600
3614
  };
@@ -3698,16 +3712,16 @@ const NetworkInspector = ({ enabled = true, storage, navigationRef, }) => {
3698
3712
  <WhiteBackNavigation />
3699
3713
  </TouchableScale>
3700
3714
 
3701
- {selected == null && selectedEvent == null ? (<View style={{
3715
+ {selected == null && selectedEvent == null ? (<TouchableScale onPress={() => setShowHeaderInfo(prev => !prev)} style={{
3702
3716
  flexDirection: 'row',
3703
3717
  alignItems: 'center',
3704
- gap: 14,
3718
+ gap: 10,
3705
3719
  flex: 1,
3706
3720
  }}>
3707
3721
  <View style={{
3708
- width: 50,
3709
- height: 50,
3710
- borderRadius: 10,
3722
+ width: 38,
3723
+ height: 38,
3724
+ borderRadius: 19,
3711
3725
  backgroundColor: 'rgba(255,255,255,0.13)',
3712
3726
  borderWidth: 1.5,
3713
3727
  borderColor: 'rgba(255,255,255,0.25)',
@@ -3718,94 +3732,13 @@ const NetworkInspector = ({ enabled = true, storage, navigationRef, }) => {
3718
3732
  shadowRadius: 4,
3719
3733
  shadowOffset: { width: 0, height: 2 },
3720
3734
  }}>
3721
- <BrandSquareIcon size={45}/>
3735
+ <BrandCircleIcon size={34}/>
3722
3736
  </View>
3723
- <View style={{ gap: 3 }}>
3724
- <Text style={[styles.headerTitle]}>
3725
- RN InApp Inspector
3726
- </Text>
3727
- <View style={{
3728
- flexDirection: 'row',
3729
- alignItems: 'center',
3730
- gap: 6,
3731
- }}>
3732
- {/* OS chip */}
3733
- <View style={{
3734
- flexDirection: 'row',
3735
- alignItems: 'center',
3736
- borderRadius: 6,
3737
- overflow: 'hidden',
3738
- borderWidth: 1,
3739
- borderColor: 'rgba(255,255,255,0.18)',
3740
- }}>
3741
- <View style={{
3742
- paddingHorizontal: 5,
3743
- paddingVertical: 2,
3744
- backgroundColor: 'rgba(255,255,255,0.28)',
3745
- }}>
3746
- <Text style={{
3747
- fontFamily: AppFonts.interBold,
3748
- fontSize: 9,
3749
- color: '#FFFFFF',
3750
- letterSpacing: 0.3,
3751
- }}>
3752
- {Platform.OS === 'ios' ? 'iOS' : 'Android'}
3753
- </Text>
3754
- </View>
3755
- <View style={{
3756
- paddingHorizontal: 5,
3757
- paddingVertical: 2,
3758
- backgroundColor: 'rgba(255,255,255,0.12)',
3759
- }}>
3760
- <Text style={{
3761
- fontFamily: AppFonts.interMedium,
3762
- fontSize: 9.5,
3763
- color: 'rgba(255,255,255,0.92)',
3764
- }}>
3765
- {String(Platform.Version)}
3766
- </Text>
3767
- </View>
3768
- </View>
3769
-
3770
- {/* npm chip */}
3771
- <View style={{
3772
- flexDirection: 'row',
3773
- alignItems: 'center',
3774
- borderRadius: 6,
3775
- overflow: 'hidden',
3776
- borderWidth: 1,
3777
- borderColor: 'rgba(255,255,255,0.18)',
3778
- }}>
3779
- <View style={{
3780
- paddingHorizontal: 5,
3781
- paddingVertical: 2,
3782
- backgroundColor: 'rgba(255,255,255,0.28)',
3783
- }}>
3784
- <Text style={{
3785
- fontFamily: AppFonts.interBold,
3786
- fontSize: 9,
3787
- color: '#FFFFFF',
3788
- letterSpacing: 0.3,
3789
- }}>
3790
- npm
3791
- </Text>
3792
- </View>
3793
- <View style={{
3794
- paddingHorizontal: 5,
3795
- paddingVertical: 2,
3796
- backgroundColor: 'rgba(255,255,255,0.12)',
3797
- }}>
3798
- <Text style={{
3799
- fontFamily: AppFonts.interMedium,
3800
- fontSize: 9.5,
3801
- color: 'rgba(255,255,255,0.92)',
3802
- }}>
3803
- v{LIB_VERSION}
3804
- </Text>
3805
- </View>
3806
- </View>
3807
-
3808
- {/* #1 — pulsing dot when a newer version is on NPM */}
3737
+ <View style={{ gap: 2, flex: 1 }}>
3738
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
3739
+ <Text style={[styles.headerTitle]} numberOfLines={1}>
3740
+ {getAppName()}
3741
+ </Text>
3809
3742
  {updateAvailable && (<Pressable hitSlop={10} onPress={() => Alert.alert('Update Available', `react-native-inapp-inspector v${latestNpmVersion} is available on NPM (installed: v${LIB_VERSION}).`, [
3810
3743
  { text: 'Later', style: 'cancel' },
3811
3744
  {
@@ -3828,8 +3761,120 @@ const NetworkInspector = ({ enabled = true, storage, navigationRef, }) => {
3828
3761
  }}/>
3829
3762
  </Pressable>)}
3830
3763
  </View>
3764
+ <View style={{
3765
+ flexDirection: 'row',
3766
+ alignItems: 'center',
3767
+ alignSelf: 'flex-start',
3768
+ backgroundColor: 'rgba(255,255,255,0.1)',
3769
+ borderRadius: 10,
3770
+ paddingHorizontal: 8,
3771
+ paddingVertical: 3,
3772
+ gap: 5,
3773
+ borderWidth: 1,
3774
+ borderColor: 'rgba(255,255,255,0.15)',
3775
+ }}>
3776
+ <View style={{
3777
+ width: 6,
3778
+ height: 6,
3779
+ borderRadius: 3,
3780
+ backgroundColor: '#4ADE80',
3781
+ }}/>
3782
+ <Text style={{
3783
+ fontFamily: AppFonts.interMedium,
3784
+ fontSize: 10,
3785
+ color: 'rgba(255, 255, 255, 0.85)',
3786
+ letterSpacing: 0.2,
3787
+ }} numberOfLines={1}>
3788
+ {getBundleIdentifier()}
3789
+ </Text>
3790
+ <Animated.View style={{
3791
+ transform: [{ rotate: showHeaderInfo ? '180deg' : '0deg' }],
3792
+ marginLeft: 2,
3793
+ }}>
3794
+ <ChevronIcon color="rgba(255,255,255,0.6)" size={12}/>
3795
+ </Animated.View>
3796
+ </View>
3797
+ {showHeaderInfo && (<View style={{
3798
+ flexDirection: 'row',
3799
+ alignItems: 'center',
3800
+ gap: 6,
3801
+ marginTop: 4,
3802
+ }}>
3803
+ <View style={{
3804
+ flexDirection: 'row',
3805
+ alignItems: 'center',
3806
+ borderRadius: 6,
3807
+ overflow: 'hidden',
3808
+ borderWidth: 1,
3809
+ borderColor: 'rgba(255,255,255,0.18)',
3810
+ }}>
3811
+ <View style={{
3812
+ paddingHorizontal: 5,
3813
+ paddingVertical: 2,
3814
+ backgroundColor: 'rgba(255,255,255,0.28)',
3815
+ }}>
3816
+ <Text style={{
3817
+ fontFamily: AppFonts.interBold,
3818
+ fontSize: 9,
3819
+ color: '#FFFFFF',
3820
+ letterSpacing: 0.3,
3821
+ }}>
3822
+ {Platform.OS === 'ios' ? 'iOS' : 'Android'}
3823
+ </Text>
3824
+ </View>
3825
+ <View style={{
3826
+ paddingHorizontal: 5,
3827
+ paddingVertical: 2,
3828
+ backgroundColor: 'rgba(255,255,255,0.12)',
3829
+ }}>
3830
+ <Text style={{
3831
+ fontFamily: AppFonts.interMedium,
3832
+ fontSize: 9.5,
3833
+ color: 'rgba(255,255,255,0.92)',
3834
+ }}>
3835
+ {String(Platform.Version)}
3836
+ </Text>
3837
+ </View>
3838
+ </View>
3839
+ <View style={{
3840
+ flexDirection: 'row',
3841
+ alignItems: 'center',
3842
+ borderRadius: 6,
3843
+ overflow: 'hidden',
3844
+ borderWidth: 1,
3845
+ borderColor: 'rgba(255,255,255,0.18)',
3846
+ }}>
3847
+ <View style={{
3848
+ paddingHorizontal: 5,
3849
+ paddingVertical: 2,
3850
+ backgroundColor: 'rgba(255,255,255,0.28)',
3851
+ }}>
3852
+ <Text style={{
3853
+ fontFamily: AppFonts.interBold,
3854
+ fontSize: 9,
3855
+ color: '#FFFFFF',
3856
+ letterSpacing: 0.3,
3857
+ }}>
3858
+ npm
3859
+ </Text>
3860
+ </View>
3861
+ <View style={{
3862
+ paddingHorizontal: 5,
3863
+ paddingVertical: 2,
3864
+ backgroundColor: 'rgba(255,255,255,0.12)',
3865
+ }}>
3866
+ <Text style={{
3867
+ fontFamily: AppFonts.interMedium,
3868
+ fontSize: 9.5,
3869
+ color: 'rgba(255,255,255,0.92)',
3870
+ }}>
3871
+ v{LIB_VERSION}
3872
+ </Text>
3873
+ </View>
3874
+ </View>
3875
+ </View>)}
3831
3876
  </View>
3832
- </View>) : null}
3877
+ </TouchableScale>) : null}
3833
3878
  </View>
3834
3879
 
3835
3880
  <View style={styles.headerCenter}>
@@ -4111,14 +4156,6 @@ const NetworkInspector = ({ enabled = true, storage, navigationRef, }) => {
4111
4156
  </Pressable>)}
4112
4157
  </View>
4113
4158
  {analyticsSubTab === 'ga_events' && (<View style={styles.toolbarRight}>
4114
- <TouchableScale style={[
4115
- styles.toolbarBtn,
4116
- !hideScreenView && styles.toolbarBtnActive,
4117
- ]} onPress={() => setHideScreenView(prev => !prev)} hitSlop={10}>
4118
- <ScreenIcon color={!hideScreenView
4119
- ? AppColors.purple
4120
- : AppColors.grayTextStrong} size={18}/>
4121
- </TouchableScale>
4122
4159
  <TouchableScale style={{
4123
4160
  flexDirection: 'row',
4124
4161
  alignItems: 'center',
@@ -4249,109 +4286,118 @@ const NetworkInspector = ({ enabled = true, storage, navigationRef, }) => {
4249
4286
 
4250
4287
  {isReady ? (activeTab === 'insights' ? (<ScrollView style={styles.insightsContainer} contentContainerStyle={styles.insightsContent} showsVerticalScrollIndicator={false}>
4251
4288
  {renderInsightsDashboard()}
4252
- </ScrollView>) : activeTab === 'analytics' ? (selectedEvent != null ? (<AnalyticsDetail event={selectedEvent}/>) : analyticsSubTab === 'top_events' ? (<FlatList data={topEventsArray} keyExtractor={item => item[0]} contentContainerStyle={[
4253
- styles.listContent,
4254
- { paddingHorizontal: 16, paddingTop: 16 },
4255
- ]} renderItem={({ item: [name, count], index }) => {
4256
- const maxCount = topEventsArray[0]?.[1] || 1;
4257
- const color = getEventColor(name);
4258
- return (<AnimatedEntrance index={index} distance={8} style={[
4259
- styles.analyticsTopEventsCard,
4260
- { marginBottom: 12, paddingVertical: 16 },
4261
- ]}>
4262
- <View style={styles.analyticsTopEventRow}>
4263
- <View style={{
4264
- flexDirection: 'row',
4265
- alignItems: 'center',
4266
- gap: 8,
4267
- flex: 1,
4268
- }}>
4269
- <View style={[
4270
- styles.analyticsIconCircle,
4271
- { backgroundColor: `${color}1A` },
4272
- ]}>
4273
- <Svg width={14} height={14} viewBox="0 0 24 24" fill={color}>
4274
- <Circle cx="12" cy="12" r="10" opacity="0.3"/>
4275
- <Path d="M7 14l3-3 4 4 6-6" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" fill="none"/>
4276
- </Svg>
4277
- </View>
4278
- <Text style={styles.analyticsTopEventName} numberOfLines={2}>
4279
- {name}
4289
+ </ScrollView>) : activeTab === 'analytics' ? (<View style={{ flex: 1 }}>
4290
+ {selectedEvent != null ? (<AnalyticsDetail event={selectedEvent}/>) : analyticsSubTab === 'top_events' ? (<FlatList data={topEventsArray} keyExtractor={item => item[0]} contentContainerStyle={[
4291
+ styles.listContent,
4292
+ { paddingHorizontal: 16, paddingTop: 12 },
4293
+ ]} renderItem={({ item: [name, count], index }) => {
4294
+ const maxCount = topEventsArray[0]?.[1] || 1;
4295
+ const pct = Math.max(6, (count / maxCount) * 100);
4296
+ const color = getEventColor(name);
4297
+ return (<AnimatedEntrance index={index} distance={8} style={[
4298
+ styles.analyticsTopEventsCard,
4299
+ { marginBottom: 6, paddingVertical: 10, paddingHorizontal: 12 },
4300
+ ]}>
4301
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 10 }}>
4302
+ <Text style={{
4303
+ fontFamily: AppFonts.interBold,
4304
+ fontSize: 11,
4305
+ color: color,
4306
+ width: 18,
4307
+ textAlign: 'center',
4308
+ }}>
4309
+ {index + 1}
4280
4310
  </Text>
4311
+ <View style={{ flex: 1, gap: 4 }}>
4312
+ <View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
4313
+ <Text style={{
4314
+ fontFamily: AppFonts.interMedium,
4315
+ fontSize: 12.5,
4316
+ color: AppColors.primaryBlack,
4317
+ flex: 1,
4318
+ }} numberOfLines={1}>
4319
+ {name}
4320
+ </Text>
4321
+ <Text style={{
4322
+ fontFamily: AppFonts.interBold,
4323
+ fontSize: 12,
4324
+ color: color,
4325
+ marginLeft: 8,
4326
+ }}>
4327
+ {count}
4328
+ </Text>
4329
+ </View>
4330
+ <View style={{ height: 3, backgroundColor: AppColors.grayBackground, borderRadius: 2, overflow: 'hidden' }}>
4331
+ <LinearGradient colors={[color, `${color}88`]} start={{ x: 0, y: 0 }} end={{ x: 1, y: 0 }} style={{
4332
+ height: '100%',
4333
+ borderRadius: 2,
4334
+ width: `${pct}%`,
4335
+ }}/>
4336
+ </View>
4337
+ </View>
4281
4338
  </View>
4282
- <View style={styles.analyticsTopEventBarWrap}>
4283
- <LinearGradient colors={[color, `${color}99`]} start={{ x: 0, y: 0 }} end={{ x: 1, y: 0 }} style={[
4284
- styles.analyticsTopEventBar,
4285
- {
4286
- width: `${Math.max(6, (count / maxCount) * 100)}%`,
4287
- },
4288
- ]}/>
4289
- </View>
4290
- <Text style={styles.analyticsTopEventCount}>
4291
- {count}
4292
- </Text>
4339
+ </AnimatedEntrance>);
4340
+ }} ListEmptyComponent={<View style={styles.emptyContainer}>
4341
+ <View style={styles.emptyIconWrap}>
4342
+ <EmptyRadarIcon color={AppColors.purple} size={32}/>
4293
4343
  </View>
4294
- </AnimatedEntrance>);
4295
- }} ListEmptyComponent={<View style={styles.emptyContainer}>
4296
- <View style={styles.emptyIconWrap}>
4297
- <EmptyRadarIcon color={AppColors.purple} size={32}/>
4298
- </View>
4299
- <Text style={styles.emptyTitle}>No Top Events</Text>
4300
- </View>}/>) : (<FlatList data={filteredAnalyticsEvents} keyExtractor={item => item.id.toString()} renderItem={({ item, index }) => {
4301
- const prev = filteredAnalyticsEvents[index + 1];
4302
- const next = filteredAnalyticsEvents[index - 1];
4303
- const msSincePrev = prev
4304
- ? item.timestamp - prev.timestamp
4305
- : undefined;
4306
- const thisMin = Math.floor(item.timestamp / 60000);
4307
- const nextMin = next
4308
- ? Math.floor(next.timestamp / 60000)
4309
- : -1;
4310
- const showTimestamp = index === 0 || thisMin !== nextMin;
4311
- return (<AnimatedEntrance index={index} distance={8}>
4312
- <AnalyticsEventCard event={item} onPress={() => {
4313
- animateNextLayout();
4314
- setSelectedEvent(item);
4315
- }} isNew={newEventIds.has(item.id)} searchStr={analyticsSearch} isFirst={index === 0} isLast={index === filteredAnalyticsEvents.length - 1} msSincePrev={msSincePrev} showTimestamp={showTimestamp} computedScreenName={(() => {
4316
- let screenName = item.screenName ||
4317
- item.screenClass ||
4318
- item.pageTitle ||
4319
- item.pageLocation ||
4320
- item.params?.firebase_screen ||
4321
- item.params?.screen_name ||
4322
- item.params?.firebase_screen_class ||
4323
- item.params?.screen_class;
4324
- const routeInfo = logRouteMapRef.current.get(item.id + 1000000);
4325
- if (!screenName) {
4326
- if (routeInfo &&
4327
- routeInfo.path !== 'Navigators') {
4328
- const parts = routeInfo.path.split(' ➔ ');
4329
- screenName = parts[parts.length - 1];
4344
+ <Text style={styles.emptyTitle}>No Top Events</Text>
4345
+ </View>}/>) : (<FlatList data={filteredAnalyticsEvents} keyExtractor={item => item.id.toString()} renderItem={({ item, index }) => {
4346
+ const prev = filteredAnalyticsEvents[index + 1];
4347
+ const next = filteredAnalyticsEvents[index - 1];
4348
+ const msSincePrev = prev
4349
+ ? item.timestamp - prev.timestamp
4350
+ : undefined;
4351
+ const thisMin = Math.floor(item.timestamp / 60000);
4352
+ const nextMin = next
4353
+ ? Math.floor(next.timestamp / 60000)
4354
+ : -1;
4355
+ const showTimestamp = index === 0 || thisMin !== nextMin;
4356
+ return (<AnimatedEntrance index={index} distance={8}>
4357
+ <AnalyticsEventCard event={item} onPress={() => {
4358
+ animateNextLayout();
4359
+ setSelectedEvent(item);
4360
+ }} isNew={newEventIds.has(item.id)} searchStr={analyticsSearch} isFirst={index === 0} isLast={index === filteredAnalyticsEvents.length - 1} msSincePrev={msSincePrev} showTimestamp={showTimestamp} computedScreenName={(() => {
4361
+ let screenName = item.screenName ||
4362
+ item.screenClass ||
4363
+ item.pageTitle ||
4364
+ item.pageLocation ||
4365
+ item.params?.firebase_screen ||
4366
+ item.params?.screen_name ||
4367
+ item.params?.firebase_screen_class ||
4368
+ item.params?.screen_class;
4369
+ const routeInfo = logRouteMapRef.current.get(item.id + 1000000);
4370
+ if (!screenName) {
4371
+ if (routeInfo &&
4372
+ routeInfo.path !== 'Navigators') {
4373
+ const parts = routeInfo.path.split(' ➔ ');
4374
+ screenName = parts[parts.length - 1];
4375
+ }
4330
4376
  }
4331
- }
4332
- return screenName;
4333
- })()}/>
4334
- </AnimatedEntrance>);
4335
- }} initialNumToRender={20} maxToRenderPerBatch={20} windowSize={5} removeClippedSubviews ListEmptyComponent={<View style={styles.emptyContainer}>
4336
- <View style={styles.emptyIconWrap}>
4337
- <EmptyRadarIcon color={AppColors.purple} size={32}/>
4338
- </View>
4339
- <Text style={styles.emptyTitle}>
4340
- {analyticsSearch.length > 0
4341
- ? 'No matching events'
4342
- : 'No analytics events yet'}
4343
- </Text>
4344
- <Text style={styles.emptySub}>
4345
- {analyticsSearch.length > 0
4346
- ? 'Try adjusting your search.'
4347
- : 'Call setupAnalyticsLogger(analytics()) at app start.'}
4348
- </Text>
4349
- </View>} contentContainerStyle={[
4350
- styles.listContent,
4351
- filteredAnalyticsEvents.length === 0 && {
4352
- flexGrow: 1,
4353
- },
4354
- ]} keyboardShouldPersistTaps="handled"/>)) : activeTab === 'apis' && selected == null ? (<View style={{ flex: 1 }}>
4377
+ return screenName;
4378
+ })()}/>
4379
+ </AnimatedEntrance>);
4380
+ }} initialNumToRender={20} maxToRenderPerBatch={20} windowSize={5} removeClippedSubviews={false} ListEmptyComponent={<View style={styles.emptyContainer}>
4381
+ <View style={styles.emptyIconWrap}>
4382
+ <EmptyRadarIcon color={AppColors.purple} size={32}/>
4383
+ </View>
4384
+ <Text style={styles.emptyTitle}>
4385
+ {analyticsSearch.length > 0
4386
+ ? 'No matching events'
4387
+ : 'No analytics events yet'}
4388
+ </Text>
4389
+ <Text style={styles.emptySub}>
4390
+ {analyticsSearch.length > 0
4391
+ ? 'Try adjusting your search.'
4392
+ : 'Call setupAnalyticsLogger(analytics()) at app start.'}
4393
+ </Text>
4394
+ </View>} contentContainerStyle={[
4395
+ styles.listContent,
4396
+ filteredAnalyticsEvents.length === 0 && {
4397
+ flexGrow: 1,
4398
+ },
4399
+ ]} keyboardShouldPersistTaps="handled"/>)}
4400
+ </View>) : activeTab === 'apis' && selected == null ? (<View style={{ flex: 1 }}>
4355
4401
  <FlatList ref={apisListRef} data={groupedData} keyExtractor={item => item?.id?.toString()} renderItem={renderItem} initialNumToRender={10} maxToRenderPerBatch={10} windowSize={5} removeClippedSubviews={true} ListHeaderComponent={<View style={{ marginTop: 8 }}>
4356
4402
  <View style={styles.toolbarRow}>
4357
4403
  <View style={styles.searchContainer}>