react-native-inapp-inspector 1.1.11 → 1.1.12

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.
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,7 +22,7 @@ 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
@@ -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);
@@ -193,7 +193,7 @@ const NetworkInspector = ({ enabled = true, storage, navigationRef, }) => {
193
193
  const [maxConsoleLogs, setMaxConsoleLogs] = useState(200);
194
194
  const [showConsoleLevels, setShowConsoleLevels] = useState({
195
195
  info: true,
196
- warn: true,
196
+ warn: false,
197
197
  error: true,
198
198
  });
199
199
  const visibleConsoleLogs = useMemo(() => {
@@ -702,7 +702,13 @@ const NetworkInspector = ({ enabled = true, storage, navigationRef, }) => {
702
702
  const task = InteractionManager.runAfterInteractions(() => {
703
703
  setIsReady(true);
704
704
  });
705
- return () => task.cancel();
705
+ const fallbackTimer = setTimeout(() => {
706
+ setIsReady(true);
707
+ }, 350);
708
+ return () => {
709
+ task.cancel();
710
+ clearTimeout(fallbackTimer);
711
+ };
706
712
  }
707
713
  else {
708
714
  setIsReady(false);
@@ -715,9 +721,10 @@ const NetworkInspector = ({ enabled = true, storage, navigationRef, }) => {
715
721
  setupConsoleLogger();
716
722
  autoSetupAnalyticsLogger();
717
723
  let timeoutId;
724
+ let isFirstNetworkCall = true;
718
725
  const unsubscribe = subscribeNetworkLogs((raw) => {
719
726
  clearTimeout(timeoutId);
720
- timeoutId = setTimeout(() => {
727
+ const updateNetworkState = () => {
721
728
  const deduped = deduplicateLogs(raw);
722
729
  const incoming = new Set(deduped.map(l => l.id));
723
730
  const freshIds = new Set();
@@ -736,13 +743,24 @@ const NetworkInspector = ({ enabled = true, storage, navigationRef, }) => {
736
743
  setTimeout(() => setNewLogIds(new Set()), 1200);
737
744
  }
738
745
  setLogs(deduped);
739
- }, 250);
746
+ };
747
+ if (isFirstNetworkCall) {
748
+ isFirstNetworkCall = false;
749
+ const deduped = deduplicateLogs(raw);
750
+ const incoming = new Set(deduped.map(l => l.id));
751
+ prevLogIdsRef.current = incoming;
752
+ setLogs(deduped);
753
+ }
754
+ else {
755
+ timeoutId = setTimeout(updateNetworkState, 250);
756
+ }
740
757
  });
741
758
  // ─── Analytics subscription ──────────────────────────────────────────────
742
759
  let analyticsTimeoutId;
760
+ let isFirstAnalyticsCall = true;
743
761
  const unsubscribeAnalytics = subscribeAnalyticsEvents((raw) => {
744
762
  clearTimeout(analyticsTimeoutId);
745
- analyticsTimeoutId = setTimeout(() => {
763
+ const updateAnalyticsState = () => {
746
764
  const incoming = new Set(raw.map(e => e.id));
747
765
  const freshIds = new Set();
748
766
  incoming.forEach(id => {
@@ -760,15 +778,31 @@ const NetworkInspector = ({ enabled = true, storage, navigationRef, }) => {
760
778
  setTimeout(() => setNewEventIds(new Set()), 1200);
761
779
  }
762
780
  setAnalyticsEvents(raw);
763
- }, 200);
781
+ };
782
+ if (isFirstAnalyticsCall) {
783
+ isFirstAnalyticsCall = false;
784
+ const incoming = new Set(raw.map(e => e.id));
785
+ prevEventIdsRef.current = incoming;
786
+ setAnalyticsEvents(raw);
787
+ }
788
+ else {
789
+ analyticsTimeoutId = setTimeout(updateAnalyticsState, 200);
790
+ }
764
791
  });
765
792
  // ─── Console subscription ────────────────────────────────────────────────
766
793
  let consoleTimeoutId;
794
+ let isFirstConsoleCall = true;
767
795
  const unsubscribeConsole = subscribeConsoleLogs((raw) => {
768
796
  clearTimeout(consoleTimeoutId);
769
- consoleTimeoutId = setTimeout(() => {
797
+ if (isFirstConsoleCall) {
798
+ isFirstConsoleCall = false;
770
799
  setConsoleLogs(raw);
771
- }, 200);
800
+ }
801
+ else {
802
+ consoleTimeoutId = setTimeout(() => {
803
+ setConsoleLogs(raw);
804
+ }, 200);
805
+ }
772
806
  });
773
807
  setWebViewLogs(getWebViewLogs());
774
808
  setWebViewNavHistory(getWebViewNavHistory());
@@ -778,24 +812,34 @@ const NetworkInspector = ({ enabled = true, storage, navigationRef, }) => {
778
812
  setWebViewHtmlUrl(getWebViewHtmlUrl());
779
813
  // ─── WebView subscription ────────────────────────────────────────────────
780
814
  let webViewTimeoutId;
815
+ let isFirstWebViewCall = true;
781
816
  const unsubscribeWebView = subscribeWebView(() => {
782
817
  clearTimeout(webViewTimeoutId);
783
- webViewTimeoutId = setTimeout(() => {
818
+ const updateWebViewState = () => {
784
819
  setWebViewLogs(getWebViewLogs());
785
820
  setWebViewNavHistory(getWebViewNavHistory());
786
821
  setWebViewHtml(getWebViewHtml());
787
822
  setWebViewCss(getWebViewCss());
788
823
  setWebViewJs(getWebViewJs());
789
824
  setWebViewHtmlUrl(getWebViewHtmlUrl());
790
- }, 200);
825
+ };
826
+ if (isFirstWebViewCall) {
827
+ isFirstWebViewCall = false;
828
+ updateWebViewState();
829
+ }
830
+ else {
831
+ webViewTimeoutId = setTimeout(updateWebViewState, 200);
832
+ }
791
833
  });
792
- setReduxState(getReduxState());
834
+ const initialReduxState = getReduxState();
835
+ setReduxState(initialReduxState && typeof initialReduxState === 'object' ? { ...initialReduxState } : initialReduxState);
793
836
  setReduxActionHistory([...getActionHistory()]);
794
837
  setReduxLastActionMap({ ...getLastActionForReducer() });
795
838
  const unsubscribeRedux = subscribeReduxState(() => {
796
839
  // New references each time guarantee the Redux tab updates live, even when
797
840
  // the root state object reference is unchanged or auto-refresh is paused.
798
- setReduxState(getReduxState());
841
+ const freshState = getReduxState();
842
+ setReduxState(freshState && typeof freshState === 'object' ? { ...freshState } : freshState);
799
843
  setReduxActionHistory([...getActionHistory()]);
800
844
  setReduxLastActionMap({ ...getLastActionForReducer() });
801
845
  });
@@ -2106,6 +2150,97 @@ const NetworkInspector = ({ enabled = true, storage, navigationRef, }) => {
2106
2150
  </TouchableScale>
2107
2151
  </View>
2108
2152
 
2153
+ {/* Divider */}
2154
+ <View style={{
2155
+ height: 1,
2156
+ backgroundColor: AppColors.dividerColor,
2157
+ }}/>
2158
+
2159
+ {/* Logs Console Levels */}
2160
+ <View style={{
2161
+ paddingVertical: 12,
2162
+ paddingHorizontal: 14,
2163
+ }}>
2164
+ <View style={{
2165
+ flexDirection: 'row',
2166
+ alignItems: 'center',
2167
+ gap: 8,
2168
+ }}>
2169
+ <View style={{
2170
+ width: 20,
2171
+ height: 20,
2172
+ borderRadius: 6,
2173
+ backgroundColor: AppColors.purpleShade50,
2174
+ borderWidth: 1,
2175
+ borderColor: 'rgba(104,75,155,0.2)',
2176
+ alignItems: 'center',
2177
+ justifyContent: 'center',
2178
+ }}>
2179
+ <TerminalIcon color={AppColors.purple} size={11}/>
2180
+ </View>
2181
+ <View style={{ flex: 1 }}>
2182
+ <Text style={{
2183
+ fontFamily: AppFonts.interBold,
2184
+ fontSize: 13,
2185
+ color: AppColors.primaryBlack,
2186
+ }}>
2187
+ Logs Console Levels
2188
+ </Text>
2189
+ <Text style={{
2190
+ fontFamily: AppFonts.interRegular,
2191
+ fontSize: 11,
2192
+ color: AppColors.grayText,
2193
+ marginTop: 1,
2194
+ }}>
2195
+ Toggle which log levels are visible in the Logs tab
2196
+ </Text>
2197
+ </View>
2198
+ </View>
2199
+
2200
+ <View style={{
2201
+ flexDirection: 'row',
2202
+ gap: 8,
2203
+ marginTop: 10,
2204
+ }}>
2205
+ {([
2206
+ { key: 'info', label: 'Info', color: '#3B82F6' },
2207
+ { key: 'warn', label: 'Warning', color: '#F59E0B' },
2208
+ { key: 'error', label: 'Error', color: '#EF4444' },
2209
+ ]).map(level => {
2210
+ const isActive = showConsoleLevels[level.key];
2211
+ return (<TouchableScale key={level.key} onPress={() => setShowConsoleLevels(prev => ({
2212
+ ...prev,
2213
+ [level.key]: !prev[level.key],
2214
+ }))} style={{
2215
+ flex: 1,
2216
+ flexDirection: 'row',
2217
+ alignItems: 'center',
2218
+ justifyContent: 'center',
2219
+ gap: 6,
2220
+ paddingVertical: 8,
2221
+ borderRadius: 8,
2222
+ borderWidth: 1.5,
2223
+ borderColor: isActive ? level.color : AppColors.grayBorderSecondary,
2224
+ backgroundColor: isActive ? `${level.color}0D` : AppColors.primaryLight,
2225
+ }}>
2226
+ <View style={{
2227
+ width: 8,
2228
+ height: 8,
2229
+ borderRadius: 4,
2230
+ backgroundColor: isActive ? level.color : AppColors.grayBorderSecondary,
2231
+ }}/>
2232
+ <Text style={{
2233
+ fontFamily: AppFonts.interBold,
2234
+ fontSize: 11,
2235
+ color: isActive ? level.color : AppColors.grayText,
2236
+ }}>
2237
+ {level.label}
2238
+ </Text>
2239
+ </TouchableScale>);
2240
+ })}
2241
+ </View>
2242
+ </View>
2243
+
2109
2244
  {/* Divider */}
2110
2245
  <View style={{
2111
2246
  height: 1,
@@ -3435,129 +3570,8 @@ const NetworkInspector = ({ enabled = true, storage, navigationRef, }) => {
3435
3570
  <Text style={styles.emptySub}>Connected store state is empty.</Text>
3436
3571
  </View>);
3437
3572
  }
3438
- // Build hierarchical tree: Store -> Reducers -> Action -> Data.
3439
- // These come from component state (refreshed on every dispatch) so the view stays live.
3440
3573
  const lastActionMap = reduxLastActionMap;
3441
- const actionHistory = reduxActionHistory;
3442
3574
  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
3575
  {/* Search Bar */}
3562
3576
  <View style={{
3563
3577
  flexDirection: 'row',
@@ -3565,15 +3579,14 @@ const NetworkInspector = ({ enabled = true, storage, navigationRef, }) => {
3565
3579
  backgroundColor: AppColors.grayBackground,
3566
3580
  borderRadius: 8,
3567
3581
  marginHorizontal: 16,
3582
+ marginTop: 16,
3568
3583
  marginBottom: 12,
3569
3584
  paddingHorizontal: 10,
3570
3585
  borderWidth: 1,
3571
3586
  borderColor: AppColors.dividerColor,
3572
3587
  height: 36,
3573
3588
  }}>
3574
- <TextInput placeholder={reduxActiveSubTab === 'timeline'
3575
- ? 'Search actions or payloads...'
3576
- : 'Search Redux keys or values...'} placeholderTextColor={AppColors.grayTextWeak} value={reduxSearch} onChangeText={setReduxSearch} style={{
3589
+ <TextInput placeholder="Search Redux keys or values..." placeholderTextColor={AppColors.grayTextWeak} value={reduxSearch} onChangeText={setReduxSearch} style={{
3577
3590
  flex: 1,
3578
3591
  fontFamily: AppFonts.interRegular,
3579
3592
  fontSize: 12,
@@ -3594,7 +3607,7 @@ const NetworkInspector = ({ enabled = true, storage, navigationRef, }) => {
3594
3607
  marginHorizontal: 16,
3595
3608
  padding: 12,
3596
3609
  }}>
3597
- {reduxActiveSubTab === 'timeline' ? (<ReduxActionTimeline history={actionHistory} onClear={clearActionHistory} search={reduxSearch}/>) : (<ReduxTreeView state={reduxState} lastActionMap={lastActionMap} search={reduxSearch}/>)}
3610
+ <ReduxTreeView state={reduxState} lastActionMap={lastActionMap} search={reduxSearch}/>
3598
3611
  </View>
3599
3612
  </ScrollView>);
3600
3613
  };
@@ -3720,14 +3733,50 @@ const NetworkInspector = ({ enabled = true, storage, navigationRef, }) => {
3720
3733
  }}>
3721
3734
  <BrandSquareIcon size={45}/>
3722
3735
  </View>
3723
- <View style={{ gap: 3 }}>
3724
- <Text style={[styles.headerTitle]}>
3725
- RN InApp Inspector
3736
+ <View style={{ gap: 2, flex: 1 }}>
3737
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
3738
+ <Text style={[styles.headerTitle]} numberOfLines={1}>
3739
+ {getAppName()}{' '}
3740
+ <Text style={{ fontFamily: AppFonts.interRegular, fontSize: 13, color: 'rgba(255,255,255,0.6)' }}>
3741
+ (Inspector)
3742
+ </Text>
3743
+ </Text>
3744
+ {/* #1 — pulsing dot when a newer version is on NPM */}
3745
+ {updateAvailable && (<Pressable hitSlop={10} onPress={() => Alert.alert('Update Available', `react-native-inapp-inspector v${latestNpmVersion} is available on NPM (installed: v${LIB_VERSION}).`, [
3746
+ { text: 'Later', style: 'cancel' },
3747
+ {
3748
+ text: 'View on NPM',
3749
+ onPress: () => Linking.openURL('https://www.npmjs.com/package/react-native-inapp-inspector').catch(() => { }),
3750
+ },
3751
+ ])} style={{
3752
+ alignItems: 'center',
3753
+ justifyContent: 'center',
3754
+ }}>
3755
+ <Animated.View style={{
3756
+ width: 8,
3757
+ height: 8,
3758
+ borderRadius: 4,
3759
+ backgroundColor: '#4ADE80',
3760
+ borderWidth: 1,
3761
+ borderColor: 'rgba(255,255,255,0.9)',
3762
+ opacity: activePulseAnim,
3763
+ transform: [{ scale: unreadPulseAnim }],
3764
+ }}/>
3765
+ </Pressable>)}
3766
+ </View>
3767
+ <Text style={{
3768
+ fontFamily: AppFonts.interRegular,
3769
+ fontSize: 10.5,
3770
+ color: 'rgba(255, 255, 255, 0.6)',
3771
+ marginTop: 1,
3772
+ }} selectable={true} numberOfLines={1}>
3773
+ {getBundleIdentifier()}
3726
3774
  </Text>
3727
3775
  <View style={{
3728
3776
  flexDirection: 'row',
3729
3777
  alignItems: 'center',
3730
3778
  gap: 6,
3779
+ marginTop: 4,
3731
3780
  }}>
3732
3781
  {/* OS chip */}
3733
3782
  <View style={{
@@ -3804,29 +3853,6 @@ const NetworkInspector = ({ enabled = true, storage, navigationRef, }) => {
3804
3853
  </Text>
3805
3854
  </View>
3806
3855
  </View>
3807
-
3808
- {/* #1 — pulsing dot when a newer version is on NPM */}
3809
- {updateAvailable && (<Pressable hitSlop={10} onPress={() => Alert.alert('Update Available', `react-native-inapp-inspector v${latestNpmVersion} is available on NPM (installed: v${LIB_VERSION}).`, [
3810
- { text: 'Later', style: 'cancel' },
3811
- {
3812
- text: 'View on NPM',
3813
- onPress: () => Linking.openURL('https://www.npmjs.com/package/react-native-inapp-inspector').catch(() => { }),
3814
- },
3815
- ])} style={{
3816
- alignItems: 'center',
3817
- justifyContent: 'center',
3818
- }}>
3819
- <Animated.View style={{
3820
- width: 8,
3821
- height: 8,
3822
- borderRadius: 4,
3823
- backgroundColor: '#4ADE80',
3824
- borderWidth: 1,
3825
- borderColor: 'rgba(255,255,255,0.9)',
3826
- opacity: activePulseAnim,
3827
- transform: [{ scale: unreadPulseAnim }],
3828
- }}/>
3829
- </Pressable>)}
3830
3856
  </View>
3831
3857
  </View>
3832
3858
  </View>) : null}
@@ -4111,14 +4137,6 @@ const NetworkInspector = ({ enabled = true, storage, navigationRef, }) => {
4111
4137
  </Pressable>)}
4112
4138
  </View>
4113
4139
  {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
4140
  <TouchableScale style={{
4123
4141
  flexDirection: 'row',
4124
4142
  alignItems: 'center',
@@ -4249,109 +4267,118 @@ const NetworkInspector = ({ enabled = true, storage, navigationRef, }) => {
4249
4267
 
4250
4268
  {isReady ? (activeTab === 'insights' ? (<ScrollView style={styles.insightsContainer} contentContainerStyle={styles.insightsContent} showsVerticalScrollIndicator={false}>
4251
4269
  {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}
4270
+ </ScrollView>) : activeTab === 'analytics' ? (<View style={{ flex: 1 }}>
4271
+ {selectedEvent != null ? (<AnalyticsDetail event={selectedEvent}/>) : analyticsSubTab === 'top_events' ? (<FlatList data={topEventsArray} keyExtractor={item => item[0]} contentContainerStyle={[
4272
+ styles.listContent,
4273
+ { paddingHorizontal: 16, paddingTop: 12 },
4274
+ ]} renderItem={({ item: [name, count], index }) => {
4275
+ const maxCount = topEventsArray[0]?.[1] || 1;
4276
+ const pct = Math.max(6, (count / maxCount) * 100);
4277
+ const color = getEventColor(name);
4278
+ return (<AnimatedEntrance index={index} distance={8} style={[
4279
+ styles.analyticsTopEventsCard,
4280
+ { marginBottom: 6, paddingVertical: 10, paddingHorizontal: 12 },
4281
+ ]}>
4282
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 10 }}>
4283
+ <Text style={{
4284
+ fontFamily: AppFonts.interBold,
4285
+ fontSize: 11,
4286
+ color: color,
4287
+ width: 18,
4288
+ textAlign: 'center',
4289
+ }}>
4290
+ {index + 1}
4280
4291
  </Text>
4292
+ <View style={{ flex: 1, gap: 4 }}>
4293
+ <View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
4294
+ <Text style={{
4295
+ fontFamily: AppFonts.interMedium,
4296
+ fontSize: 12.5,
4297
+ color: AppColors.primaryBlack,
4298
+ flex: 1,
4299
+ }} numberOfLines={1}>
4300
+ {name}
4301
+ </Text>
4302
+ <Text style={{
4303
+ fontFamily: AppFonts.interBold,
4304
+ fontSize: 12,
4305
+ color: color,
4306
+ marginLeft: 8,
4307
+ }}>
4308
+ {count}
4309
+ </Text>
4310
+ </View>
4311
+ <View style={{ height: 3, backgroundColor: AppColors.grayBackground, borderRadius: 2, overflow: 'hidden' }}>
4312
+ <LinearGradient colors={[color, `${color}88`]} start={{ x: 0, y: 0 }} end={{ x: 1, y: 0 }} style={{
4313
+ height: '100%',
4314
+ borderRadius: 2,
4315
+ width: `${pct}%`,
4316
+ }}/>
4317
+ </View>
4318
+ </View>
4281
4319
  </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>
4320
+ </AnimatedEntrance>);
4321
+ }} ListEmptyComponent={<View style={styles.emptyContainer}>
4322
+ <View style={styles.emptyIconWrap}>
4323
+ <EmptyRadarIcon color={AppColors.purple} size={32}/>
4293
4324
  </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];
4325
+ <Text style={styles.emptyTitle}>No Top Events</Text>
4326
+ </View>}/>) : (<FlatList data={filteredAnalyticsEvents} keyExtractor={item => item.id.toString()} renderItem={({ item, index }) => {
4327
+ const prev = filteredAnalyticsEvents[index + 1];
4328
+ const next = filteredAnalyticsEvents[index - 1];
4329
+ const msSincePrev = prev
4330
+ ? item.timestamp - prev.timestamp
4331
+ : undefined;
4332
+ const thisMin = Math.floor(item.timestamp / 60000);
4333
+ const nextMin = next
4334
+ ? Math.floor(next.timestamp / 60000)
4335
+ : -1;
4336
+ const showTimestamp = index === 0 || thisMin !== nextMin;
4337
+ return (<AnimatedEntrance index={index} distance={8}>
4338
+ <AnalyticsEventCard event={item} onPress={() => {
4339
+ animateNextLayout();
4340
+ setSelectedEvent(item);
4341
+ }} isNew={newEventIds.has(item.id)} searchStr={analyticsSearch} isFirst={index === 0} isLast={index === filteredAnalyticsEvents.length - 1} msSincePrev={msSincePrev} showTimestamp={showTimestamp} computedScreenName={(() => {
4342
+ let screenName = item.screenName ||
4343
+ item.screenClass ||
4344
+ item.pageTitle ||
4345
+ item.pageLocation ||
4346
+ item.params?.firebase_screen ||
4347
+ item.params?.screen_name ||
4348
+ item.params?.firebase_screen_class ||
4349
+ item.params?.screen_class;
4350
+ const routeInfo = logRouteMapRef.current.get(item.id + 1000000);
4351
+ if (!screenName) {
4352
+ if (routeInfo &&
4353
+ routeInfo.path !== 'Navigators') {
4354
+ const parts = routeInfo.path.split(' ➔ ');
4355
+ screenName = parts[parts.length - 1];
4356
+ }
4330
4357
  }
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 }}>
4358
+ return screenName;
4359
+ })()}/>
4360
+ </AnimatedEntrance>);
4361
+ }} initialNumToRender={20} maxToRenderPerBatch={20} windowSize={5} removeClippedSubviews={false} ListEmptyComponent={<View style={styles.emptyContainer}>
4362
+ <View style={styles.emptyIconWrap}>
4363
+ <EmptyRadarIcon color={AppColors.purple} size={32}/>
4364
+ </View>
4365
+ <Text style={styles.emptyTitle}>
4366
+ {analyticsSearch.length > 0
4367
+ ? 'No matching events'
4368
+ : 'No analytics events yet'}
4369
+ </Text>
4370
+ <Text style={styles.emptySub}>
4371
+ {analyticsSearch.length > 0
4372
+ ? 'Try adjusting your search.'
4373
+ : 'Call setupAnalyticsLogger(analytics()) at app start.'}
4374
+ </Text>
4375
+ </View>} contentContainerStyle={[
4376
+ styles.listContent,
4377
+ filteredAnalyticsEvents.length === 0 && {
4378
+ flexGrow: 1,
4379
+ },
4380
+ ]} keyboardShouldPersistTaps="handled"/>)}
4381
+ </View>) : activeTab === 'apis' && selected == null ? (<View style={{ flex: 1 }}>
4355
4382
  <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
4383
  <View style={styles.toolbarRow}>
4357
4384
  <View style={styles.searchContainer}>