react-native-inapp-inspector 1.0.0
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/LICENSE +21 -0
- package/README.md +97 -0
- package/dist/commonjs/components/AnalyticsDetail.d.ts +6 -0
- package/dist/commonjs/components/AnalyticsDetail.js +558 -0
- package/dist/commonjs/components/AnalyticsEventCard.d.ts +18 -0
- package/dist/commonjs/components/AnalyticsEventCard.js +327 -0
- package/dist/commonjs/components/AnalyticsGraph.d.ts +8 -0
- package/dist/commonjs/components/AnalyticsGraph.js +416 -0
- package/dist/commonjs/components/CodeSnippet.d.ts +8 -0
- package/dist/commonjs/components/CodeSnippet.js +427 -0
- package/dist/commonjs/components/ConsoleLogCard.d.ts +10 -0
- package/dist/commonjs/components/ConsoleLogCard.js +401 -0
- package/dist/commonjs/components/CopyButton.d.ts +4 -0
- package/dist/commonjs/components/CopyButton.js +69 -0
- package/dist/commonjs/components/DiffViewer.d.ts +7 -0
- package/dist/commonjs/components/DiffViewer.js +86 -0
- package/dist/commonjs/components/DomainHeader.d.ts +18 -0
- package/dist/commonjs/components/DomainHeader.js +136 -0
- package/dist/commonjs/components/EmptyState.d.ts +5 -0
- package/dist/commonjs/components/EmptyState.js +40 -0
- package/dist/commonjs/components/HeadersSection.d.ts +4 -0
- package/dist/commonjs/components/HeadersSection.js +155 -0
- package/dist/commonjs/components/HighlightText.d.ts +3 -0
- package/dist/commonjs/components/HighlightText.js +57 -0
- package/dist/commonjs/components/JsonViewer.d.ts +7 -0
- package/dist/commonjs/components/JsonViewer.js +19 -0
- package/dist/commonjs/components/LogCard.d.ts +4 -0
- package/dist/commonjs/components/LogCard.js +179 -0
- package/dist/commonjs/components/MetaAccordion.d.ts +4 -0
- package/dist/commonjs/components/MetaAccordion.js +113 -0
- package/dist/commonjs/components/MiniBarChart.d.ts +7 -0
- package/dist/commonjs/components/MiniBarChart.js +56 -0
- package/dist/commonjs/components/MiniLineChart.d.ts +6 -0
- package/dist/commonjs/components/MiniLineChart.js +58 -0
- package/dist/commonjs/components/NetworkIcons.d.ts +31 -0
- package/dist/commonjs/components/NetworkIcons.js +245 -0
- package/dist/commonjs/components/SectionHeader.d.ts +4 -0
- package/dist/commonjs/components/SectionHeader.js +87 -0
- package/dist/commonjs/components/SourcePageCard.d.ts +4 -0
- package/dist/commonjs/components/SourcePageCard.js +132 -0
- package/dist/commonjs/components/TouchableScale.d.ts +9 -0
- package/dist/commonjs/components/TouchableScale.js +44 -0
- package/dist/commonjs/components/TreeNode.d.ts +4 -0
- package/dist/commonjs/components/TreeNode.js +140 -0
- package/dist/commonjs/constants/index.d.ts +7 -0
- package/dist/commonjs/constants/index.js +35 -0
- package/dist/commonjs/customHooks/analyticsLogger.d.ts +21 -0
- package/dist/commonjs/customHooks/analyticsLogger.js +160 -0
- package/dist/commonjs/customHooks/consoleLogger.d.ts +5 -0
- package/dist/commonjs/customHooks/consoleLogger.js +141 -0
- package/dist/commonjs/customHooks/logFilters.d.ts +5 -0
- package/dist/commonjs/customHooks/logFilters.js +34 -0
- package/dist/commonjs/customHooks/networkLogger.d.ts +20 -0
- package/dist/commonjs/customHooks/networkLogger.js +272 -0
- package/dist/commonjs/customHooks/useAccordion.d.ts +17 -0
- package/dist/commonjs/customHooks/useAccordion.js +48 -0
- package/dist/commonjs/customHooks/webViewLogger.d.ts +22 -0
- package/dist/commonjs/customHooks/webViewLogger.js +412 -0
- package/dist/commonjs/helpers/index.d.ts +20 -0
- package/dist/commonjs/helpers/index.js +229 -0
- package/dist/commonjs/index.d.ts +7 -0
- package/dist/commonjs/index.js +2668 -0
- package/dist/commonjs/styles/AppColors.d.ts +27 -0
- package/dist/commonjs/styles/AppColors.js +30 -0
- package/dist/commonjs/styles/AppFonts.d.ts +7 -0
- package/dist/commonjs/styles/AppFonts.js +10 -0
- package/dist/commonjs/styles/index.d.ts +1488 -0
- package/dist/commonjs/styles/index.js +1357 -0
- package/dist/commonjs/types/index.d.ts +127 -0
- package/dist/commonjs/types/index.js +2 -0
- package/dist/esm/components/AnalyticsDetail.d.ts +6 -0
- package/dist/esm/components/AnalyticsDetail.js +520 -0
- package/dist/esm/components/AnalyticsEventCard.d.ts +18 -0
- package/dist/esm/components/AnalyticsEventCard.js +288 -0
- package/dist/esm/components/AnalyticsGraph.d.ts +8 -0
- package/dist/esm/components/AnalyticsGraph.js +378 -0
- package/dist/esm/components/CodeSnippet.d.ts +8 -0
- package/dist/esm/components/CodeSnippet.js +392 -0
- package/dist/esm/components/ConsoleLogCard.d.ts +10 -0
- package/dist/esm/components/ConsoleLogCard.js +362 -0
- package/dist/esm/components/CopyButton.d.ts +4 -0
- package/dist/esm/components/CopyButton.js +31 -0
- package/dist/esm/components/DiffViewer.d.ts +7 -0
- package/dist/esm/components/DiffViewer.js +48 -0
- package/dist/esm/components/DomainHeader.d.ts +18 -0
- package/dist/esm/components/DomainHeader.js +98 -0
- package/dist/esm/components/EmptyState.d.ts +5 -0
- package/dist/esm/components/EmptyState.js +35 -0
- package/dist/esm/components/HeadersSection.d.ts +4 -0
- package/dist/esm/components/HeadersSection.js +117 -0
- package/dist/esm/components/HighlightText.d.ts +3 -0
- package/dist/esm/components/HighlightText.js +52 -0
- package/dist/esm/components/JsonViewer.d.ts +7 -0
- package/dist/esm/components/JsonViewer.js +14 -0
- package/dist/esm/components/LogCard.d.ts +4 -0
- package/dist/esm/components/LogCard.js +141 -0
- package/dist/esm/components/MetaAccordion.d.ts +4 -0
- package/dist/esm/components/MetaAccordion.js +108 -0
- package/dist/esm/components/MiniBarChart.d.ts +7 -0
- package/dist/esm/components/MiniBarChart.js +18 -0
- package/dist/esm/components/MiniLineChart.d.ts +6 -0
- package/dist/esm/components/MiniLineChart.js +20 -0
- package/dist/esm/components/NetworkIcons.d.ts +31 -0
- package/dist/esm/components/NetworkIcons.js +176 -0
- package/dist/esm/components/SectionHeader.d.ts +4 -0
- package/dist/esm/components/SectionHeader.js +49 -0
- package/dist/esm/components/SourcePageCard.d.ts +4 -0
- package/dist/esm/components/SourcePageCard.js +94 -0
- package/dist/esm/components/TouchableScale.d.ts +9 -0
- package/dist/esm/components/TouchableScale.js +9 -0
- package/dist/esm/components/TreeNode.d.ts +4 -0
- package/dist/esm/components/TreeNode.js +102 -0
- package/dist/esm/constants/index.d.ts +7 -0
- package/dist/esm/constants/index.js +32 -0
- package/dist/esm/customHooks/analyticsLogger.d.ts +21 -0
- package/dist/esm/customHooks/analyticsLogger.js +152 -0
- package/dist/esm/customHooks/consoleLogger.d.ts +5 -0
- package/dist/esm/customHooks/consoleLogger.js +134 -0
- package/dist/esm/customHooks/logFilters.d.ts +5 -0
- package/dist/esm/customHooks/logFilters.js +31 -0
- package/dist/esm/customHooks/networkLogger.d.ts +20 -0
- package/dist/esm/customHooks/networkLogger.js +264 -0
- package/dist/esm/customHooks/useAccordion.d.ts +17 -0
- package/dist/esm/customHooks/useAccordion.js +46 -0
- package/dist/esm/customHooks/webViewLogger.d.ts +22 -0
- package/dist/esm/customHooks/webViewLogger.js +365 -0
- package/dist/esm/helpers/index.d.ts +20 -0
- package/dist/esm/helpers/index.js +207 -0
- package/dist/esm/index.d.ts +7 -0
- package/dist/esm/index.js +2611 -0
- package/dist/esm/styles/AppColors.d.ts +27 -0
- package/dist/esm/styles/AppColors.js +27 -0
- package/dist/esm/styles/AppFonts.d.ts +7 -0
- package/dist/esm/styles/AppFonts.js +7 -0
- package/dist/esm/styles/index.d.ts +1488 -0
- package/dist/esm/styles/index.js +1355 -0
- package/dist/esm/types/index.d.ts +127 -0
- package/dist/esm/types/index.js +1 -0
- package/fonts/Inter/Inter.ttc +0 -0
- package/fonts/Inter/InterVariable-Italic.ttf +0 -0
- package/fonts/Inter/InterVariable.ttf +0 -0
- package/fonts/Inter/LICENSE.txt +92 -0
- package/fonts/Inter/extras/otf/Inter-Black.otf +0 -0
- package/fonts/Inter/extras/otf/Inter-BlackItalic.otf +0 -0
- package/fonts/Inter/extras/otf/Inter-Bold.otf +0 -0
- package/fonts/Inter/extras/otf/Inter-BoldItalic.otf +0 -0
- package/fonts/Inter/extras/otf/Inter-ExtraBold.otf +0 -0
- package/fonts/Inter/extras/otf/Inter-ExtraBoldItalic.otf +0 -0
- package/fonts/Inter/extras/otf/Inter-ExtraLight.otf +0 -0
- package/fonts/Inter/extras/otf/Inter-ExtraLightItalic.otf +0 -0
- package/fonts/Inter/extras/otf/Inter-Italic.otf +0 -0
- package/fonts/Inter/extras/otf/Inter-Light.otf +0 -0
- package/fonts/Inter/extras/otf/Inter-LightItalic.otf +0 -0
- package/fonts/Inter/extras/otf/Inter-Medium.otf +0 -0
- package/fonts/Inter/extras/otf/Inter-MediumItalic.otf +0 -0
- package/fonts/Inter/extras/otf/Inter-Regular.otf +0 -0
- package/fonts/Inter/extras/otf/Inter-SemiBold.otf +0 -0
- package/fonts/Inter/extras/otf/Inter-SemiBoldItalic.otf +0 -0
- package/fonts/Inter/extras/otf/Inter-Thin.otf +0 -0
- package/fonts/Inter/extras/otf/Inter-ThinItalic.otf +0 -0
- package/fonts/Inter/extras/otf/InterDisplay-Black.otf +0 -0
- package/fonts/Inter/extras/otf/InterDisplay-BlackItalic.otf +0 -0
- package/fonts/Inter/extras/otf/InterDisplay-Bold.otf +0 -0
- package/fonts/Inter/extras/otf/InterDisplay-BoldItalic.otf +0 -0
- package/fonts/Inter/extras/otf/InterDisplay-ExtraBold.otf +0 -0
- package/fonts/Inter/extras/otf/InterDisplay-ExtraBoldItalic.otf +0 -0
- package/fonts/Inter/extras/otf/InterDisplay-ExtraLight.otf +0 -0
- package/fonts/Inter/extras/otf/InterDisplay-ExtraLightItalic.otf +0 -0
- package/fonts/Inter/extras/otf/InterDisplay-Italic.otf +0 -0
- package/fonts/Inter/extras/otf/InterDisplay-Light.otf +0 -0
- package/fonts/Inter/extras/otf/InterDisplay-LightItalic.otf +0 -0
- package/fonts/Inter/extras/otf/InterDisplay-Medium.otf +0 -0
- package/fonts/Inter/extras/otf/InterDisplay-MediumItalic.otf +0 -0
- package/fonts/Inter/extras/otf/InterDisplay-Regular.otf +0 -0
- package/fonts/Inter/extras/otf/InterDisplay-SemiBold.otf +0 -0
- package/fonts/Inter/extras/otf/InterDisplay-SemiBoldItalic.otf +0 -0
- package/fonts/Inter/extras/otf/InterDisplay-Thin.otf +0 -0
- package/fonts/Inter/extras/otf/InterDisplay-ThinItalic.otf +0 -0
- package/fonts/Inter/extras/ttf/Inter-Black.ttf +0 -0
- package/fonts/Inter/extras/ttf/Inter-BlackItalic.ttf +0 -0
- package/fonts/Inter/extras/ttf/Inter-Bold.ttf +0 -0
- package/fonts/Inter/extras/ttf/Inter-BoldItalic.ttf +0 -0
- package/fonts/Inter/extras/ttf/Inter-ExtraBold.ttf +0 -0
- package/fonts/Inter/extras/ttf/Inter-ExtraBoldItalic.ttf +0 -0
- package/fonts/Inter/extras/ttf/Inter-ExtraLight.ttf +0 -0
- package/fonts/Inter/extras/ttf/Inter-ExtraLightItalic.ttf +0 -0
- package/fonts/Inter/extras/ttf/Inter-Italic.ttf +0 -0
- package/fonts/Inter/extras/ttf/Inter-Light.ttf +0 -0
- package/fonts/Inter/extras/ttf/Inter-LightItalic.ttf +0 -0
- package/fonts/Inter/extras/ttf/Inter-Medium.ttf +0 -0
- package/fonts/Inter/extras/ttf/Inter-MediumItalic.ttf +0 -0
- package/fonts/Inter/extras/ttf/Inter-Regular.ttf +0 -0
- package/fonts/Inter/extras/ttf/Inter-SemiBold.ttf +0 -0
- package/fonts/Inter/extras/ttf/Inter-SemiBoldItalic.ttf +0 -0
- package/fonts/Inter/extras/ttf/Inter-Thin.ttf +0 -0
- package/fonts/Inter/extras/ttf/Inter-ThinItalic.ttf +0 -0
- package/fonts/Inter/extras/ttf/InterDisplay-Black.ttf +0 -0
- package/fonts/Inter/extras/ttf/InterDisplay-BlackItalic.ttf +0 -0
- package/fonts/Inter/extras/ttf/InterDisplay-Bold.ttf +0 -0
- package/fonts/Inter/extras/ttf/InterDisplay-BoldItalic.ttf +0 -0
- package/fonts/Inter/extras/ttf/InterDisplay-ExtraBold.ttf +0 -0
- package/fonts/Inter/extras/ttf/InterDisplay-ExtraBoldItalic.ttf +0 -0
- package/fonts/Inter/extras/ttf/InterDisplay-ExtraLight.ttf +0 -0
- package/fonts/Inter/extras/ttf/InterDisplay-ExtraLightItalic.ttf +0 -0
- package/fonts/Inter/extras/ttf/InterDisplay-Italic.ttf +0 -0
- package/fonts/Inter/extras/ttf/InterDisplay-Light.ttf +0 -0
- package/fonts/Inter/extras/ttf/InterDisplay-LightItalic.ttf +0 -0
- package/fonts/Inter/extras/ttf/InterDisplay-Medium.ttf +0 -0
- package/fonts/Inter/extras/ttf/InterDisplay-MediumItalic.ttf +0 -0
- package/fonts/Inter/extras/ttf/InterDisplay-Regular.ttf +0 -0
- package/fonts/Inter/extras/ttf/InterDisplay-SemiBold.ttf +0 -0
- package/fonts/Inter/extras/ttf/InterDisplay-SemiBoldItalic.ttf +0 -0
- package/fonts/Inter/extras/ttf/InterDisplay-Thin.ttf +0 -0
- package/fonts/Inter/extras/ttf/InterDisplay-ThinItalic.ttf +0 -0
- package/fonts/Inter/extras/woff-hinted/Inter-Black.woff2 +0 -0
- package/fonts/Inter/extras/woff-hinted/Inter-BlackItalic.woff2 +0 -0
- package/fonts/Inter/extras/woff-hinted/Inter-Bold.woff2 +0 -0
- package/fonts/Inter/extras/woff-hinted/Inter-BoldItalic.woff2 +0 -0
- package/fonts/Inter/extras/woff-hinted/Inter-ExtraBold.woff2 +0 -0
- package/fonts/Inter/extras/woff-hinted/Inter-ExtraBoldItalic.woff2 +0 -0
- package/fonts/Inter/extras/woff-hinted/Inter-ExtraLight.woff2 +0 -0
- package/fonts/Inter/extras/woff-hinted/Inter-ExtraLightItalic.woff2 +0 -0
- package/fonts/Inter/extras/woff-hinted/Inter-Italic.woff2 +0 -0
- package/fonts/Inter/extras/woff-hinted/Inter-Light.woff2 +0 -0
- package/fonts/Inter/extras/woff-hinted/Inter-LightItalic.woff2 +0 -0
- package/fonts/Inter/extras/woff-hinted/Inter-Medium.woff2 +0 -0
- package/fonts/Inter/extras/woff-hinted/Inter-MediumItalic.woff2 +0 -0
- package/fonts/Inter/extras/woff-hinted/Inter-Regular.woff2 +0 -0
- package/fonts/Inter/extras/woff-hinted/Inter-SemiBold.woff2 +0 -0
- package/fonts/Inter/extras/woff-hinted/Inter-SemiBoldItalic.woff2 +0 -0
- package/fonts/Inter/extras/woff-hinted/Inter-Thin.woff2 +0 -0
- package/fonts/Inter/extras/woff-hinted/Inter-ThinItalic.woff2 +0 -0
- package/fonts/Inter/extras/woff-hinted/InterDisplay-Black.woff2 +0 -0
- package/fonts/Inter/extras/woff-hinted/InterDisplay-BlackItalic.woff2 +0 -0
- package/fonts/Inter/extras/woff-hinted/InterDisplay-Bold.woff2 +0 -0
- package/fonts/Inter/extras/woff-hinted/InterDisplay-BoldItalic.woff2 +0 -0
- package/fonts/Inter/extras/woff-hinted/InterDisplay-ExtraBold.woff2 +0 -0
- package/fonts/Inter/extras/woff-hinted/InterDisplay-ExtraBoldItalic.woff2 +0 -0
- package/fonts/Inter/extras/woff-hinted/InterDisplay-ExtraLight.woff2 +0 -0
- package/fonts/Inter/extras/woff-hinted/InterDisplay-ExtraLightItalic.woff2 +0 -0
- package/fonts/Inter/extras/woff-hinted/InterDisplay-Italic.woff2 +0 -0
- package/fonts/Inter/extras/woff-hinted/InterDisplay-Light.woff2 +0 -0
- package/fonts/Inter/extras/woff-hinted/InterDisplay-LightItalic.woff2 +0 -0
- package/fonts/Inter/extras/woff-hinted/InterDisplay-Medium.woff2 +0 -0
- package/fonts/Inter/extras/woff-hinted/InterDisplay-MediumItalic.woff2 +0 -0
- package/fonts/Inter/extras/woff-hinted/InterDisplay-Regular.woff2 +0 -0
- package/fonts/Inter/extras/woff-hinted/InterDisplay-SemiBold.woff2 +0 -0
- package/fonts/Inter/extras/woff-hinted/InterDisplay-SemiBoldItalic.woff2 +0 -0
- package/fonts/Inter/extras/woff-hinted/InterDisplay-Thin.woff2 +0 -0
- package/fonts/Inter/extras/woff-hinted/InterDisplay-ThinItalic.woff2 +0 -0
- package/fonts/Inter/help.txt +165 -0
- package/fonts/Inter/web/Inter-Black.woff2 +0 -0
- package/fonts/Inter/web/Inter-BlackItalic.woff2 +0 -0
- package/fonts/Inter/web/Inter-Bold.woff2 +0 -0
- package/fonts/Inter/web/Inter-BoldItalic.woff2 +0 -0
- package/fonts/Inter/web/Inter-ExtraBold.woff2 +0 -0
- package/fonts/Inter/web/Inter-ExtraBoldItalic.woff2 +0 -0
- package/fonts/Inter/web/Inter-ExtraLight.woff2 +0 -0
- package/fonts/Inter/web/Inter-ExtraLightItalic.woff2 +0 -0
- package/fonts/Inter/web/Inter-Italic.woff2 +0 -0
- package/fonts/Inter/web/Inter-Light.woff2 +0 -0
- package/fonts/Inter/web/Inter-LightItalic.woff2 +0 -0
- package/fonts/Inter/web/Inter-Medium.woff2 +0 -0
- package/fonts/Inter/web/Inter-MediumItalic.woff2 +0 -0
- package/fonts/Inter/web/Inter-Regular.woff2 +0 -0
- package/fonts/Inter/web/Inter-SemiBold.woff2 +0 -0
- package/fonts/Inter/web/Inter-SemiBoldItalic.woff2 +0 -0
- package/fonts/Inter/web/Inter-Thin.woff2 +0 -0
- package/fonts/Inter/web/Inter-ThinItalic.woff2 +0 -0
- package/fonts/Inter/web/InterDisplay-Black.woff2 +0 -0
- package/fonts/Inter/web/InterDisplay-BlackItalic.woff2 +0 -0
- package/fonts/Inter/web/InterDisplay-Bold.woff2 +0 -0
- package/fonts/Inter/web/InterDisplay-BoldItalic.woff2 +0 -0
- package/fonts/Inter/web/InterDisplay-ExtraBold.woff2 +0 -0
- package/fonts/Inter/web/InterDisplay-ExtraBoldItalic.woff2 +0 -0
- package/fonts/Inter/web/InterDisplay-ExtraLight.woff2 +0 -0
- package/fonts/Inter/web/InterDisplay-ExtraLightItalic.woff2 +0 -0
- package/fonts/Inter/web/InterDisplay-Italic.woff2 +0 -0
- package/fonts/Inter/web/InterDisplay-Light.woff2 +0 -0
- package/fonts/Inter/web/InterDisplay-LightItalic.woff2 +0 -0
- package/fonts/Inter/web/InterDisplay-Medium.woff2 +0 -0
- package/fonts/Inter/web/InterDisplay-MediumItalic.woff2 +0 -0
- package/fonts/Inter/web/InterDisplay-Regular.woff2 +0 -0
- package/fonts/Inter/web/InterDisplay-SemiBold.woff2 +0 -0
- package/fonts/Inter/web/InterDisplay-SemiBoldItalic.woff2 +0 -0
- package/fonts/Inter/web/InterDisplay-Thin.woff2 +0 -0
- package/fonts/Inter/web/InterDisplay-ThinItalic.woff2 +0 -0
- package/fonts/Inter/web/InterVariable-Italic.woff2 +0 -0
- package/fonts/Inter/web/InterVariable.woff2 +0 -0
- package/fonts/Inter/web/inter.css +148 -0
- package/package.json +52 -0
|
@@ -0,0 +1,2611 @@
|
|
|
1
|
+
import React, { useEffect, useMemo, useRef, useState, useCallback } from 'react';
|
|
2
|
+
import { Alert, Animated, StyleSheet, FlatList, SectionList, Modal, Platform, Pressable, ScrollView, Text, TextInput, View, Linking, Image, InteractionManager, ActivityIndicator, StatusBar, } from 'react-native';
|
|
3
|
+
import Svg, { Circle, Path } from 'react-native-svg';
|
|
4
|
+
import LinearGradient from 'react-native-linear-gradient';
|
|
5
|
+
import { useNavigationState } from '@react-navigation/native';
|
|
6
|
+
// Components
|
|
7
|
+
import TouchableScale from './components/TouchableScale';
|
|
8
|
+
import useAccordion from './customHooks/useAccordion';
|
|
9
|
+
import MetaAccordion from './components/MetaAccordion';
|
|
10
|
+
import CopyButton from './components/CopyButton';
|
|
11
|
+
import MiniBarChart from './components/MiniBarChart';
|
|
12
|
+
import MiniLineChart from './components/MiniLineChart';
|
|
13
|
+
import SectionHeader from './components/SectionHeader';
|
|
14
|
+
import EmptyState from './components/EmptyState';
|
|
15
|
+
import JsonViewer from './components/JsonViewer';
|
|
16
|
+
import DomainHeader from './components/DomainHeader';
|
|
17
|
+
import DiffViewer from './components/DiffViewer';
|
|
18
|
+
import LogCard from './components/LogCard';
|
|
19
|
+
import HeadersSection from './components/HeadersSection';
|
|
20
|
+
import SourcePageCard from './components/SourcePageCard';
|
|
21
|
+
import { ConsoleLogCard } from './components/ConsoleLogCard';
|
|
22
|
+
import HighlightText from './components/HighlightText';
|
|
23
|
+
import CodeSnippet from './components/CodeSnippet';
|
|
24
|
+
// Helpers
|
|
25
|
+
import { formatDateTime, getStatusColor, getDurationColor, getNavigationInfo, deduplicateLogs, getDomainColor, formatDisplayUrl, getFetchCommand, getCurlCommand, getSize, } from './helpers';
|
|
26
|
+
// Assets
|
|
27
|
+
import { EmptyRadarIcon, FailIcon, SearchIcon, ScreenIcon, ClearIcon, SortArrowIcon, FilterIcon, MapPinIcon, GlobeIcon, DownloadIcon, ExpandCollapseIcon, ChevronIcon, CloseWhite, TrashIcon, WhiteBackNavigation, } from './components/NetworkIcons';
|
|
28
|
+
// Stylesheet
|
|
29
|
+
import { AppColors } from './styles/AppColors';
|
|
30
|
+
import { AppFonts } from './styles/AppFonts';
|
|
31
|
+
import styles from './styles';
|
|
32
|
+
// Network
|
|
33
|
+
import { setupNetworkLogger, clearNetworkLogs, subscribeNetworkLogs, } from './customHooks/networkLogger';
|
|
34
|
+
// Console
|
|
35
|
+
import { setupConsoleLogger, clearConsoleLogs, subscribeConsoleLogs, } from './customHooks/consoleLogger';
|
|
36
|
+
import { IGNORED_LOG_PREFIXES } from './customHooks/logFilters';
|
|
37
|
+
import { subscribeAnalyticsEvents, clearAnalyticsEvents, } from './customHooks/analyticsLogger';
|
|
38
|
+
import AnalyticsEventCard, { getEventColor, } from './components/AnalyticsEventCard';
|
|
39
|
+
import AnalyticsDetail from './components/AnalyticsDetail';
|
|
40
|
+
// WebView
|
|
41
|
+
import { getWebViewLogs, getWebViewNavHistory, getWebViewHtml, getWebViewCss, getWebViewJs, getWebViewHtmlUrl, clearWebViewData, subscribeWebView, } from './customHooks/webViewLogger';
|
|
42
|
+
import { METHOD_COLORS, STATUS_FILTERS } from './constants';
|
|
43
|
+
const NetworkInspector = () => {
|
|
44
|
+
const [logs, setLogs] = useState([]);
|
|
45
|
+
const [visible, setVisible] = useState(false);
|
|
46
|
+
const [isReady, setIsReady] = useState(false);
|
|
47
|
+
const [selected, setSelected] = useState(null);
|
|
48
|
+
const [selectedLogs, setSelectedLogs] = useState(new Set());
|
|
49
|
+
const [search, setSearch] = useState('');
|
|
50
|
+
const [detailSearch, setDetailSearch] = useState('');
|
|
51
|
+
const [statusFilters, setStatusFilters] = useState(new Set());
|
|
52
|
+
const [methodFilters, setMethodFilters] = useState(new Set());
|
|
53
|
+
const [sectionFilters, setSectionFilters] = useState({});
|
|
54
|
+
const [collapsedSections, setCollapsedSections] = useState(new Set());
|
|
55
|
+
const [showNetworkMenu, setShowNetworkMenu] = useState(false);
|
|
56
|
+
const [showUiMenu, setShowUiMenu] = useState(false);
|
|
57
|
+
const [sortOrder, setSortOrder] = useState('newest');
|
|
58
|
+
const [reqExpanded, setReqExpanded] = useState(undefined);
|
|
59
|
+
const [resExpanded, setResExpanded] = useState(undefined);
|
|
60
|
+
const [showReqDiff, setShowReqDiff] = useState(false);
|
|
61
|
+
const [showResDiff, setShowResDiff] = useState(false);
|
|
62
|
+
const filtersAccordion = useAccordion(false, 300, 260);
|
|
63
|
+
// ─── Analytics state ───────────────────────────────────────────────────────
|
|
64
|
+
const [activeTab, setActiveTab] = useState('apis');
|
|
65
|
+
const [analyticsEvents, setAnalyticsEvents] = useState([]);
|
|
66
|
+
// ─── Logs state ────────────────────────────────────────────────────────────
|
|
67
|
+
const [consoleLogs, setConsoleLogs] = useState([]);
|
|
68
|
+
const visibleConsoleLogs = useMemo(() => {
|
|
69
|
+
return consoleLogs.filter(log => {
|
|
70
|
+
const type = log.type;
|
|
71
|
+
const message = log.message || '';
|
|
72
|
+
const allPrefixes = [
|
|
73
|
+
...((IGNORED_LOG_PREFIXES && IGNORED_LOG_PREFIXES.info) || []),
|
|
74
|
+
...((IGNORED_LOG_PREFIXES && IGNORED_LOG_PREFIXES.warn) || []),
|
|
75
|
+
...((IGNORED_LOG_PREFIXES && IGNORED_LOG_PREFIXES.error) || []),
|
|
76
|
+
].filter(p => typeof p === 'string' && p.trim().length > 0);
|
|
77
|
+
const isIgnored = allPrefixes.some(prefix => message
|
|
78
|
+
.toLowerCase()
|
|
79
|
+
.trim()
|
|
80
|
+
.startsWith(prefix.toLowerCase().trim()) ||
|
|
81
|
+
message.toLowerCase().trim().includes(prefix.toLowerCase().trim()));
|
|
82
|
+
return !isIgnored;
|
|
83
|
+
});
|
|
84
|
+
}, [consoleLogs]);
|
|
85
|
+
const [logSearch, setLogSearch] = useState('');
|
|
86
|
+
const [logFilters, setLogFilters] = useState(new Set(['user-log']));
|
|
87
|
+
// ─── WebView state ─────────────────────────────────────────────────────────
|
|
88
|
+
const [webViewLogs, setWebViewLogs] = useState([]);
|
|
89
|
+
const [webViewNavHistory, setWebViewNavHistory] = useState([]);
|
|
90
|
+
const [webViewSubTab, setWebViewSubTab] = useState('html');
|
|
91
|
+
const [webViewSearch, setWebViewSearch] = useState('');
|
|
92
|
+
const [htmlSearch, setHtmlSearch] = useState('');
|
|
93
|
+
const [cssSearch, setCssSearch] = useState('');
|
|
94
|
+
const [jsSearch, setJsSearch] = useState('');
|
|
95
|
+
const [webViewHtml, setWebViewHtml] = useState('');
|
|
96
|
+
const [webViewCss, setWebViewCss] = useState('');
|
|
97
|
+
const [webViewJs, setWebViewJs] = useState('');
|
|
98
|
+
const [webViewHtmlUrl, setWebViewHtmlUrl] = useState('');
|
|
99
|
+
const [htmlSubTab, setHtmlSubTab] = useState('html');
|
|
100
|
+
const [selectedEvent, setSelectedEvent] = useState(null);
|
|
101
|
+
const [analyticsSearch, setAnalyticsSearch] = useState('');
|
|
102
|
+
const [hideScreenView, setHideScreenView] = useState(true);
|
|
103
|
+
const [analyticsSubTab, setAnalyticsSubTab] = useState('ga_events');
|
|
104
|
+
const [groupByScreen, setGroupByScreen] = useState(false);
|
|
105
|
+
const [expandedScreens, setExpandedScreens] = useState(new Set());
|
|
106
|
+
const [topEventsExpanded, setTopEventsExpanded] = useState(true);
|
|
107
|
+
const [newEventIds, setNewEventIds] = useState(new Set());
|
|
108
|
+
const prevEventIdsRef = useRef(new Set());
|
|
109
|
+
const navState = useNavigationState(state => state);
|
|
110
|
+
const currentRouteRef = useRef({
|
|
111
|
+
path: 'Navigators',
|
|
112
|
+
params: null,
|
|
113
|
+
});
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
currentRouteRef.current = getNavigationInfo(navState);
|
|
116
|
+
}, [navState]);
|
|
117
|
+
const logRouteMapRef = useRef(new Map());
|
|
118
|
+
const prevLogIdsRef = useRef(new Set());
|
|
119
|
+
const [newLogIds, setNewLogIds] = useState(new Set());
|
|
120
|
+
const pulseAnim = useRef(new Animated.Value(1)).current;
|
|
121
|
+
const badgeAnim = useRef(new Animated.Value(1)).current;
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
const loop = Animated.loop(Animated.sequence([
|
|
124
|
+
Animated.timing(pulseAnim, {
|
|
125
|
+
toValue: 1.2,
|
|
126
|
+
duration: 800,
|
|
127
|
+
useNativeDriver: true,
|
|
128
|
+
}),
|
|
129
|
+
Animated.timing(pulseAnim, {
|
|
130
|
+
toValue: 1,
|
|
131
|
+
duration: 800,
|
|
132
|
+
useNativeDriver: true,
|
|
133
|
+
}),
|
|
134
|
+
]));
|
|
135
|
+
loop.start();
|
|
136
|
+
return () => loop.stop();
|
|
137
|
+
}, [pulseAnim]);
|
|
138
|
+
useEffect(() => {
|
|
139
|
+
if ((logs.length > 0 || analyticsEvents.length > 0) && newLogIds.size > 0) {
|
|
140
|
+
badgeAnim.setValue(0.8);
|
|
141
|
+
Animated.spring(badgeAnim, {
|
|
142
|
+
toValue: 1,
|
|
143
|
+
tension: 300,
|
|
144
|
+
friction: 10,
|
|
145
|
+
useNativeDriver: true,
|
|
146
|
+
}).start();
|
|
147
|
+
}
|
|
148
|
+
}, [newLogIds]);
|
|
149
|
+
useEffect(() => {
|
|
150
|
+
if (visible) {
|
|
151
|
+
const task = InteractionManager.runAfterInteractions(() => {
|
|
152
|
+
setIsReady(true);
|
|
153
|
+
});
|
|
154
|
+
return () => task.cancel();
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
setIsReady(false);
|
|
158
|
+
setSelectedLogs(new Set());
|
|
159
|
+
}
|
|
160
|
+
}, [visible]);
|
|
161
|
+
useEffect(() => {
|
|
162
|
+
setupNetworkLogger();
|
|
163
|
+
clearNetworkLogs();
|
|
164
|
+
setupConsoleLogger();
|
|
165
|
+
// Note: setupAnalyticsLogger(analytics()) is called by the consumer at app startup
|
|
166
|
+
let timeoutId;
|
|
167
|
+
const unsubscribe = subscribeNetworkLogs((raw) => {
|
|
168
|
+
clearTimeout(timeoutId);
|
|
169
|
+
timeoutId = setTimeout(() => {
|
|
170
|
+
const deduped = deduplicateLogs(raw);
|
|
171
|
+
const incoming = new Set(deduped.map(l => l.id));
|
|
172
|
+
const freshIds = new Set();
|
|
173
|
+
incoming.forEach(id => {
|
|
174
|
+
if (!prevLogIdsRef.current.has(id))
|
|
175
|
+
freshIds.add(id);
|
|
176
|
+
});
|
|
177
|
+
prevLogIdsRef.current = incoming;
|
|
178
|
+
if (freshIds.size > 0) {
|
|
179
|
+
freshIds.forEach(id => {
|
|
180
|
+
if (!logRouteMapRef.current.has(id)) {
|
|
181
|
+
logRouteMapRef.current.set(id, currentRouteRef.current);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
setNewLogIds(freshIds);
|
|
185
|
+
setTimeout(() => setNewLogIds(new Set()), 1200);
|
|
186
|
+
}
|
|
187
|
+
setLogs(deduped);
|
|
188
|
+
}, 250);
|
|
189
|
+
});
|
|
190
|
+
// ─── Analytics subscription ──────────────────────────────────────────────
|
|
191
|
+
let analyticsTimeoutId;
|
|
192
|
+
const unsubscribeAnalytics = subscribeAnalyticsEvents((raw) => {
|
|
193
|
+
clearTimeout(analyticsTimeoutId);
|
|
194
|
+
analyticsTimeoutId = setTimeout(() => {
|
|
195
|
+
const incoming = new Set(raw.map(e => e.id));
|
|
196
|
+
const freshIds = new Set();
|
|
197
|
+
incoming.forEach(id => {
|
|
198
|
+
if (!prevEventIdsRef.current.has(id))
|
|
199
|
+
freshIds.add(id);
|
|
200
|
+
});
|
|
201
|
+
prevEventIdsRef.current = incoming;
|
|
202
|
+
if (freshIds.size > 0) {
|
|
203
|
+
freshIds.forEach(id => {
|
|
204
|
+
if (!logRouteMapRef.current.has(id + 1000000)) {
|
|
205
|
+
logRouteMapRef.current.set(id + 1000000, currentRouteRef.current);
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
setNewEventIds(freshIds);
|
|
209
|
+
setTimeout(() => setNewEventIds(new Set()), 1200);
|
|
210
|
+
}
|
|
211
|
+
setAnalyticsEvents(raw);
|
|
212
|
+
}, 200);
|
|
213
|
+
});
|
|
214
|
+
// ─── Console subscription ────────────────────────────────────────────────
|
|
215
|
+
let consoleTimeoutId;
|
|
216
|
+
const unsubscribeConsole = subscribeConsoleLogs((raw) => {
|
|
217
|
+
clearTimeout(consoleTimeoutId);
|
|
218
|
+
consoleTimeoutId = setTimeout(() => {
|
|
219
|
+
setConsoleLogs(raw);
|
|
220
|
+
}, 200);
|
|
221
|
+
});
|
|
222
|
+
setWebViewLogs(getWebViewLogs());
|
|
223
|
+
setWebViewNavHistory(getWebViewNavHistory());
|
|
224
|
+
setWebViewHtml(getWebViewHtml());
|
|
225
|
+
setWebViewCss(getWebViewCss());
|
|
226
|
+
setWebViewJs(getWebViewJs());
|
|
227
|
+
setWebViewHtmlUrl(getWebViewHtmlUrl());
|
|
228
|
+
// ─── WebView subscription ────────────────────────────────────────────────
|
|
229
|
+
let webViewTimeoutId;
|
|
230
|
+
const unsubscribeWebView = subscribeWebView(() => {
|
|
231
|
+
clearTimeout(webViewTimeoutId);
|
|
232
|
+
webViewTimeoutId = setTimeout(() => {
|
|
233
|
+
setWebViewLogs(getWebViewLogs());
|
|
234
|
+
setWebViewNavHistory(getWebViewNavHistory());
|
|
235
|
+
setWebViewHtml(getWebViewHtml());
|
|
236
|
+
setWebViewCss(getWebViewCss());
|
|
237
|
+
setWebViewJs(getWebViewJs());
|
|
238
|
+
setWebViewHtmlUrl(getWebViewHtmlUrl());
|
|
239
|
+
}, 200);
|
|
240
|
+
});
|
|
241
|
+
return () => {
|
|
242
|
+
unsubscribe();
|
|
243
|
+
clearTimeout(timeoutId);
|
|
244
|
+
unsubscribeAnalytics();
|
|
245
|
+
clearTimeout(analyticsTimeoutId);
|
|
246
|
+
unsubscribeConsole();
|
|
247
|
+
clearTimeout(consoleTimeoutId);
|
|
248
|
+
unsubscribeWebView();
|
|
249
|
+
clearTimeout(webViewTimeoutId);
|
|
250
|
+
};
|
|
251
|
+
}, []);
|
|
252
|
+
useEffect(() => {
|
|
253
|
+
setReqExpanded(undefined);
|
|
254
|
+
setResExpanded(undefined);
|
|
255
|
+
setShowReqDiff(false);
|
|
256
|
+
setShowResDiff(false);
|
|
257
|
+
setDetailSearch('');
|
|
258
|
+
}, [selected]);
|
|
259
|
+
const filteredLogs = useMemo(() => {
|
|
260
|
+
let result = logs.filter(log => {
|
|
261
|
+
// Status Filter Check
|
|
262
|
+
if (statusFilters.size > 0) {
|
|
263
|
+
const matched = [...statusFilters].some(f => {
|
|
264
|
+
if (f === 'ALL')
|
|
265
|
+
return true;
|
|
266
|
+
if (f === 'Failed')
|
|
267
|
+
return log.status === 0 || log.status == null;
|
|
268
|
+
return String(log.status)[0] === f[0];
|
|
269
|
+
});
|
|
270
|
+
if (!matched)
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
// Method Filter Check
|
|
274
|
+
if (methodFilters.size > 0) {
|
|
275
|
+
const matchedMethod = [...methodFilters].some(m => {
|
|
276
|
+
if (m === 'ALL')
|
|
277
|
+
return true;
|
|
278
|
+
return log.method?.toUpperCase() === m;
|
|
279
|
+
});
|
|
280
|
+
if (!matchedMethod)
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
// Search Bar Check
|
|
284
|
+
if (search && !log.url?.toLowerCase().includes(search.toLowerCase()))
|
|
285
|
+
return false;
|
|
286
|
+
return true;
|
|
287
|
+
});
|
|
288
|
+
if (sortOrder === 'oldest') {
|
|
289
|
+
result = [...result].reverse();
|
|
290
|
+
}
|
|
291
|
+
return result;
|
|
292
|
+
}, [logs, search, statusFilters, methodFilters, sortOrder]);
|
|
293
|
+
const availableMethods = useMemo(() => {
|
|
294
|
+
const methods = new Set();
|
|
295
|
+
logs.forEach(log => {
|
|
296
|
+
if (log.method)
|
|
297
|
+
methods.add(log.method.toUpperCase());
|
|
298
|
+
});
|
|
299
|
+
return ['ALL', ...Array.from(methods)];
|
|
300
|
+
}, [logs]);
|
|
301
|
+
const toggleSectionFilter = useCallback((pageName, filter) => {
|
|
302
|
+
setSectionFilters(prev => {
|
|
303
|
+
const current = prev[pageName] || new Set(['success', 'failed', 'loading']);
|
|
304
|
+
const next = new Set(current);
|
|
305
|
+
if (next.has(filter)) {
|
|
306
|
+
next.delete(filter);
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
next.add(filter);
|
|
310
|
+
}
|
|
311
|
+
return { ...prev, [pageName]: next };
|
|
312
|
+
});
|
|
313
|
+
}, []);
|
|
314
|
+
const toggleSectionCollapse = useCallback((pageName) => {
|
|
315
|
+
setCollapsedSections(prev => {
|
|
316
|
+
const next = new Set(prev);
|
|
317
|
+
if (next.has(pageName))
|
|
318
|
+
next.delete(pageName);
|
|
319
|
+
else
|
|
320
|
+
next.add(pageName);
|
|
321
|
+
return next;
|
|
322
|
+
});
|
|
323
|
+
}, []);
|
|
324
|
+
const groupedData = useMemo(() => {
|
|
325
|
+
const result = [];
|
|
326
|
+
const groups = [];
|
|
327
|
+
for (let i = 0; i < filteredLogs.length; i++) {
|
|
328
|
+
const log = filteredLogs[i];
|
|
329
|
+
const routeInfo = logRouteMapRef.current.get(log.id);
|
|
330
|
+
const pageName = routeInfo?.path || 'Navigators';
|
|
331
|
+
if (groups.length === 0 ||
|
|
332
|
+
groups[groups.length - 1].pageName !== pageName) {
|
|
333
|
+
groups.push({ pageName, color: getDomainColor(pageName), logs: [] });
|
|
334
|
+
}
|
|
335
|
+
groups[groups.length - 1].logs.push(log);
|
|
336
|
+
}
|
|
337
|
+
groups.forEach((g, idx) => {
|
|
338
|
+
let success = 0;
|
|
339
|
+
let failed = 0;
|
|
340
|
+
let loading = 0;
|
|
341
|
+
g.logs.forEach(l => {
|
|
342
|
+
if (l.status == null)
|
|
343
|
+
loading++;
|
|
344
|
+
else if (l.status === 0 || l.status >= 400)
|
|
345
|
+
failed++;
|
|
346
|
+
else
|
|
347
|
+
success++;
|
|
348
|
+
});
|
|
349
|
+
const activeFilters = sectionFilters[g.pageName] || new Set(['success', 'failed', 'loading']);
|
|
350
|
+
const isCollapsed = collapsedSections.has(g.pageName);
|
|
351
|
+
const timestamp = g.logs[0]?.startTime || 0;
|
|
352
|
+
result.push({
|
|
353
|
+
type: 'header',
|
|
354
|
+
id: `hdr-${g.logs[0]?.id || 'empty'}-${g.pageName}`,
|
|
355
|
+
pageName: g.pageName,
|
|
356
|
+
color: g.color,
|
|
357
|
+
stats: { success, failed, loading },
|
|
358
|
+
timestamp,
|
|
359
|
+
activeFilters,
|
|
360
|
+
isCollapsed,
|
|
361
|
+
isFirst: idx === 0,
|
|
362
|
+
});
|
|
363
|
+
if (!isCollapsed) {
|
|
364
|
+
const displayLogs = g.logs.filter(l => {
|
|
365
|
+
if (l.status == null)
|
|
366
|
+
return activeFilters.has('loading');
|
|
367
|
+
if (l.status === 0 || l.status >= 400)
|
|
368
|
+
return activeFilters.has('failed');
|
|
369
|
+
return activeFilters.has('success');
|
|
370
|
+
});
|
|
371
|
+
displayLogs.forEach((log, index) => {
|
|
372
|
+
result.push({
|
|
373
|
+
type: 'log',
|
|
374
|
+
id: log.id,
|
|
375
|
+
log,
|
|
376
|
+
isLast: index === displayLogs.length - 1,
|
|
377
|
+
color: g.color,
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
return result;
|
|
383
|
+
}, [filteredLogs, logs, sectionFilters, collapsedSections]);
|
|
384
|
+
const stats = useMemo(() => {
|
|
385
|
+
const total = logs.length;
|
|
386
|
+
const errors = filteredLogs.filter(l => (l.status != null && l.status >= 400) || l.status === 0).length;
|
|
387
|
+
const durations = filteredLogs
|
|
388
|
+
.filter(l => l.duration != null)
|
|
389
|
+
.map(l => l.duration);
|
|
390
|
+
const avgDuration = durations.length > 0
|
|
391
|
+
? Math.round(durations.reduce((a, b) => a + b, 0) / durations.length)
|
|
392
|
+
: null;
|
|
393
|
+
const totalBytes = filteredLogs.reduce((acc, log) => {
|
|
394
|
+
const resSize = log.response
|
|
395
|
+
? JSON.stringify(log.response)?.length || 0
|
|
396
|
+
: 0;
|
|
397
|
+
return acc + resSize;
|
|
398
|
+
}, 0);
|
|
399
|
+
const size = totalBytes < 1024
|
|
400
|
+
? `${totalBytes} B`
|
|
401
|
+
: totalBytes < 1048576
|
|
402
|
+
? `${(totalBytes / 1024).toFixed(1)} KB`
|
|
403
|
+
: `${(totalBytes / 1048576).toFixed(1)} MB`;
|
|
404
|
+
const recentLogs = [...filteredLogs].slice(0, 10).reverse();
|
|
405
|
+
const durationTrend = recentLogs.map(l => l.duration || 0);
|
|
406
|
+
const sizeTrend = recentLogs.map(l => JSON.stringify(l.response)?.length || 0);
|
|
407
|
+
const errorTrend = recentLogs.map(l => l.status === 0 || (l.status && l.status >= 400) ? 1 : 0);
|
|
408
|
+
const reqTrend = recentLogs.map(() => Math.random() * 5 + 5);
|
|
409
|
+
return {
|
|
410
|
+
total,
|
|
411
|
+
errors,
|
|
412
|
+
avgDuration,
|
|
413
|
+
filtered: filteredLogs.length,
|
|
414
|
+
size,
|
|
415
|
+
durationTrend,
|
|
416
|
+
sizeTrend,
|
|
417
|
+
errorTrend,
|
|
418
|
+
reqTrend,
|
|
419
|
+
};
|
|
420
|
+
}, [logs, filteredLogs]);
|
|
421
|
+
const hasErrors = useMemo(() => logs.some(l => (l.status != null && l.status >= 400) || l.status === 0), [logs]);
|
|
422
|
+
const { minStart, totalRange } = useMemo(() => {
|
|
423
|
+
if (filteredLogs.length === 0)
|
|
424
|
+
return { minStart: 0, totalRange: 0 };
|
|
425
|
+
const min = Math.min(...filteredLogs.map(l => l.startTime));
|
|
426
|
+
const max = Math.max(...filteredLogs.map(l => l.startTime + (l.duration || 10)));
|
|
427
|
+
return { minStart: min, totalRange: max - min };
|
|
428
|
+
}, [filteredLogs]);
|
|
429
|
+
const prevSameRequest = useMemo(() => {
|
|
430
|
+
if (!selected)
|
|
431
|
+
return null;
|
|
432
|
+
const index = logs.findIndex(l => l.id === selected.id);
|
|
433
|
+
if (index === -1)
|
|
434
|
+
return null;
|
|
435
|
+
for (let i = index + 1; i < logs.length; i++) {
|
|
436
|
+
const l = logs[i];
|
|
437
|
+
if (l.url === selected.url && l.method === selected.method) {
|
|
438
|
+
return l;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
return null;
|
|
442
|
+
}, [selected, logs]);
|
|
443
|
+
const prevRequestData = prevSameRequest ? prevSameRequest.request : null;
|
|
444
|
+
const prevResponseData = prevSameRequest ? prevSameRequest.response : null;
|
|
445
|
+
const filteredAnalyticsEvents = useMemo(() => {
|
|
446
|
+
let events = analyticsEvents;
|
|
447
|
+
if (hideScreenView) {
|
|
448
|
+
events = events.filter(e => e.name !== 'screen_view');
|
|
449
|
+
}
|
|
450
|
+
if (analyticsSearch) {
|
|
451
|
+
const s = analyticsSearch.toLowerCase();
|
|
452
|
+
events = events.filter(e => e.name.toLowerCase().includes(s) ||
|
|
453
|
+
JSON.stringify(e.params).toLowerCase().includes(s) ||
|
|
454
|
+
(e.pageTitle ?? '').toLowerCase().includes(s));
|
|
455
|
+
}
|
|
456
|
+
const deduplicatedEvents = [];
|
|
457
|
+
for (const e of events) {
|
|
458
|
+
if (deduplicatedEvents.length === 0) {
|
|
459
|
+
deduplicatedEvents.push({ ...e, count: 1 });
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
const last = deduplicatedEvents[deduplicatedEvents.length - 1];
|
|
463
|
+
if (last.name === e.name &&
|
|
464
|
+
JSON.stringify(last.params) === JSON.stringify(e.params) &&
|
|
465
|
+
JSON.stringify(last.userProperties) === JSON.stringify(e.userProperties)) {
|
|
466
|
+
last.count = (last.count || 1) + 1;
|
|
467
|
+
// Point to the newest timestamp and id
|
|
468
|
+
last.timestamp = e.timestamp;
|
|
469
|
+
last.id = e.id;
|
|
470
|
+
}
|
|
471
|
+
else {
|
|
472
|
+
deduplicatedEvents.push({ ...e, count: 1 });
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
return deduplicatedEvents;
|
|
476
|
+
}, [analyticsEvents, analyticsSearch, hideScreenView]);
|
|
477
|
+
const filteredConsoleLogs = useMemo(() => {
|
|
478
|
+
let result = visibleConsoleLogs;
|
|
479
|
+
// Filters check
|
|
480
|
+
if (logFilters.size > 0 && !logFilters.has('all')) {
|
|
481
|
+
result = result.filter(log => {
|
|
482
|
+
if (logFilters.has(log.type))
|
|
483
|
+
return true;
|
|
484
|
+
if (logFilters.has('user-log') && log.sourceMethod === 'log')
|
|
485
|
+
return true;
|
|
486
|
+
if (logFilters.has('analytics') &&
|
|
487
|
+
log.message.toLowerCase().includes('[analytics error]'))
|
|
488
|
+
return true;
|
|
489
|
+
return false;
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
// Search bar check
|
|
493
|
+
if (logSearch) {
|
|
494
|
+
const s = logSearch.toLowerCase();
|
|
495
|
+
result = result.filter(log => log.message.toLowerCase().includes(s) ||
|
|
496
|
+
(log.caller ?? '').toLowerCase().includes(s));
|
|
497
|
+
}
|
|
498
|
+
return result;
|
|
499
|
+
}, [visibleConsoleLogs, logFilters, logSearch]);
|
|
500
|
+
const filteredWebViewLogs = useMemo(() => {
|
|
501
|
+
let result = webViewLogs;
|
|
502
|
+
if (webViewSearch) {
|
|
503
|
+
const s = webViewSearch.toLowerCase();
|
|
504
|
+
result = result.filter(log => log.message.toLowerCase().includes(s));
|
|
505
|
+
}
|
|
506
|
+
return result;
|
|
507
|
+
}, [webViewLogs, webViewSearch]);
|
|
508
|
+
const filteredNavHistory = useMemo(() => {
|
|
509
|
+
let result = webViewNavHistory;
|
|
510
|
+
if (webViewSearch) {
|
|
511
|
+
const s = webViewSearch.toLowerCase();
|
|
512
|
+
result = result.filter(nav => nav.url.toLowerCase().includes(s) ||
|
|
513
|
+
(nav.title ?? '').toLowerCase().includes(s));
|
|
514
|
+
}
|
|
515
|
+
return result;
|
|
516
|
+
}, [webViewNavHistory, webViewSearch]);
|
|
517
|
+
const logCounts = useMemo(() => {
|
|
518
|
+
let searchedLogs = visibleConsoleLogs;
|
|
519
|
+
if (logSearch) {
|
|
520
|
+
const s = logSearch.toLowerCase();
|
|
521
|
+
searchedLogs = visibleConsoleLogs.filter(log => log.message.toLowerCase().includes(s) ||
|
|
522
|
+
(log.caller ?? '').toLowerCase().includes(s));
|
|
523
|
+
}
|
|
524
|
+
const total = visibleConsoleLogs.length;
|
|
525
|
+
return {
|
|
526
|
+
all: `${searchedLogs.length}/${total}`,
|
|
527
|
+
info: `${searchedLogs.filter(l => l.type === 'info').length}/${total}`,
|
|
528
|
+
warn: `${searchedLogs.filter(l => l.type === 'warn').length}/${total}`,
|
|
529
|
+
error: `${searchedLogs.filter(l => l.type === 'error').length}/${total}`,
|
|
530
|
+
'user-log': `${searchedLogs.filter(l => l.sourceMethod === 'log').length}/${total}`,
|
|
531
|
+
analytics: `${searchedLogs.filter(l => l.message.toLowerCase().includes('[analytics error]')).length}/${total}`,
|
|
532
|
+
};
|
|
533
|
+
}, [visibleConsoleLogs, logSearch]);
|
|
534
|
+
const groupedAnalyticsEvents = useMemo(() => {
|
|
535
|
+
if (!groupByScreen)
|
|
536
|
+
return [];
|
|
537
|
+
const map = new Map();
|
|
538
|
+
for (const e of filteredAnalyticsEvents) {
|
|
539
|
+
const routeInfo = logRouteMapRef.current.get(e.id + 1000000);
|
|
540
|
+
let screenName = e.screenName ||
|
|
541
|
+
e.screenClass ||
|
|
542
|
+
e.pageTitle ||
|
|
543
|
+
e.pageLocation ||
|
|
544
|
+
e.params?.firebase_screen ||
|
|
545
|
+
e.params?.screen_name ||
|
|
546
|
+
e.params?.firebase_screen_class ||
|
|
547
|
+
e.params?.screen_class;
|
|
548
|
+
if (!screenName) {
|
|
549
|
+
if (routeInfo && routeInfo.path !== 'Navigators') {
|
|
550
|
+
const parts = routeInfo.path.split(' ➔ ');
|
|
551
|
+
screenName = parts[parts.length - 1];
|
|
552
|
+
}
|
|
553
|
+
else {
|
|
554
|
+
screenName = 'Unknown Component';
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
if (!map.has(screenName))
|
|
558
|
+
map.set(screenName, []);
|
|
559
|
+
map.get(screenName).push(e);
|
|
560
|
+
}
|
|
561
|
+
const sections = [];
|
|
562
|
+
for (const [title, data] of map.entries()) {
|
|
563
|
+
sections.push({ title, data });
|
|
564
|
+
}
|
|
565
|
+
return sections;
|
|
566
|
+
}, [groupByScreen, filteredAnalyticsEvents]);
|
|
567
|
+
const topEventsArray = useMemo(() => {
|
|
568
|
+
const freq = {};
|
|
569
|
+
filteredAnalyticsEvents.forEach(e => {
|
|
570
|
+
if (e.name === 'screen_view')
|
|
571
|
+
return;
|
|
572
|
+
freq[e.name] = (freq[e.name] || 0) + 1;
|
|
573
|
+
});
|
|
574
|
+
return Object.entries(freq).sort((a, b) => b[1] - a[1]);
|
|
575
|
+
}, [filteredAnalyticsEvents]);
|
|
576
|
+
const groupedNetworkLogs = useMemo(() => {
|
|
577
|
+
if (!groupByScreen)
|
|
578
|
+
return [];
|
|
579
|
+
const map = new Map();
|
|
580
|
+
for (const l of filteredLogs) {
|
|
581
|
+
const routeInfo = logRouteMapRef.current.get(l.id);
|
|
582
|
+
let screenName = 'Unknown Origin';
|
|
583
|
+
if (routeInfo && routeInfo.path !== 'Navigators') {
|
|
584
|
+
const parts = routeInfo.path.split(' ➔ ');
|
|
585
|
+
screenName = parts[parts.length - 1];
|
|
586
|
+
}
|
|
587
|
+
if (!map.has(screenName))
|
|
588
|
+
map.set(screenName, []);
|
|
589
|
+
map.get(screenName).push(l);
|
|
590
|
+
}
|
|
591
|
+
const sections = [];
|
|
592
|
+
for (const [title, data] of map.entries()) {
|
|
593
|
+
sections.push({ title, data });
|
|
594
|
+
}
|
|
595
|
+
return sections;
|
|
596
|
+
}, [groupByScreen, filteredLogs]);
|
|
597
|
+
const toggleScreenAccordion = useCallback((title) => {
|
|
598
|
+
setExpandedScreens(prev => {
|
|
599
|
+
const next = new Set(prev);
|
|
600
|
+
if (next.has(title))
|
|
601
|
+
next.delete(title);
|
|
602
|
+
else
|
|
603
|
+
next.add(title);
|
|
604
|
+
return next;
|
|
605
|
+
});
|
|
606
|
+
}, []);
|
|
607
|
+
const renderScreenSectionHeader = useCallback(({ section: { title, data } }) => {
|
|
608
|
+
const isExpanded = expandedScreens.has(title);
|
|
609
|
+
return (<Pressable onPress={() => toggleScreenAccordion(title)} style={styles.screenSectionHeader}>
|
|
610
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
|
|
611
|
+
<View style={{
|
|
612
|
+
transform: [{ rotate: isExpanded ? '180deg' : '0deg' }],
|
|
613
|
+
}}>
|
|
614
|
+
<ExpandCollapseIcon color={AppColors.primaryBlack} size={14} isExpanded={false}/>
|
|
615
|
+
</View>
|
|
616
|
+
<Text style={styles.screenSectionTitle}>{title}</Text>
|
|
617
|
+
</View>
|
|
618
|
+
<Text style={styles.screenSectionCount}>{data.length} logs</Text>
|
|
619
|
+
</Pressable>);
|
|
620
|
+
}, [expandedScreens, toggleScreenAccordion]);
|
|
621
|
+
function closeModal() {
|
|
622
|
+
setVisible(false);
|
|
623
|
+
setSelected(null);
|
|
624
|
+
setSelectedEvent(null);
|
|
625
|
+
}
|
|
626
|
+
function handleClearAll() {
|
|
627
|
+
clearNetworkLogs();
|
|
628
|
+
setLogs([]);
|
|
629
|
+
setSelectedLogs(new Set());
|
|
630
|
+
setSectionFilters({});
|
|
631
|
+
setCollapsedSections(new Set());
|
|
632
|
+
setStatusFilters(new Set());
|
|
633
|
+
setMethodFilters(new Set());
|
|
634
|
+
prevLogIdsRef.current = new Set();
|
|
635
|
+
logRouteMapRef.current = new Map();
|
|
636
|
+
// Also clear analytics
|
|
637
|
+
clearAnalyticsEvents();
|
|
638
|
+
setAnalyticsEvents([]);
|
|
639
|
+
prevEventIdsRef.current = new Set();
|
|
640
|
+
// Also clear console logs
|
|
641
|
+
clearConsoleLogs();
|
|
642
|
+
setConsoleLogs([]);
|
|
643
|
+
// Also clear webview logs/nav history
|
|
644
|
+
clearWebViewData();
|
|
645
|
+
setWebViewLogs([]);
|
|
646
|
+
setWebViewNavHistory([]);
|
|
647
|
+
setWebViewHtml('');
|
|
648
|
+
setWebViewCss('');
|
|
649
|
+
setWebViewJs('');
|
|
650
|
+
setWebViewHtmlUrl('');
|
|
651
|
+
}
|
|
652
|
+
function handleDelete() {
|
|
653
|
+
if (activeTab === 'webview') {
|
|
654
|
+
Alert.alert('Clear WebView Data', 'Are you sure you want to clear all WebView logs and navigation history?', [
|
|
655
|
+
{ text: 'Cancel', style: 'cancel' },
|
|
656
|
+
{
|
|
657
|
+
text: 'Clear All',
|
|
658
|
+
onPress: () => {
|
|
659
|
+
clearWebViewData();
|
|
660
|
+
setWebViewLogs([]);
|
|
661
|
+
setWebViewNavHistory([]);
|
|
662
|
+
setWebViewHtml('');
|
|
663
|
+
setWebViewCss('');
|
|
664
|
+
setWebViewJs('');
|
|
665
|
+
setWebViewHtmlUrl('');
|
|
666
|
+
},
|
|
667
|
+
style: 'destructive',
|
|
668
|
+
},
|
|
669
|
+
]);
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
if (activeTab === 'logs') {
|
|
673
|
+
Alert.alert('Clear Logs', 'Are you sure you want to clear all console logs?', [
|
|
674
|
+
{ text: 'Cancel', style: 'cancel' },
|
|
675
|
+
{
|
|
676
|
+
text: 'Clear All',
|
|
677
|
+
onPress: () => {
|
|
678
|
+
clearConsoleLogs();
|
|
679
|
+
setConsoleLogs([]);
|
|
680
|
+
},
|
|
681
|
+
style: 'destructive',
|
|
682
|
+
},
|
|
683
|
+
]);
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
if (activeTab === 'analytics') {
|
|
687
|
+
Alert.alert('Clear Analytics', 'Are you sure you want to clear all analytics events?', [
|
|
688
|
+
{ text: 'Cancel', style: 'cancel' },
|
|
689
|
+
{
|
|
690
|
+
text: 'Clear All',
|
|
691
|
+
onPress: () => {
|
|
692
|
+
clearAnalyticsEvents();
|
|
693
|
+
setAnalyticsEvents([]);
|
|
694
|
+
setSelectedEvent(null);
|
|
695
|
+
prevEventIdsRef.current = new Set();
|
|
696
|
+
},
|
|
697
|
+
style: 'destructive',
|
|
698
|
+
},
|
|
699
|
+
]);
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
if (selectedLogs.size > 0) {
|
|
703
|
+
setLogs(prev => prev.filter(l => !selectedLogs.has(l.id)));
|
|
704
|
+
setSelectedLogs(new Set());
|
|
705
|
+
}
|
|
706
|
+
else {
|
|
707
|
+
Alert.alert('Clear Logs', 'Are you sure you want to clear all network logs?', [
|
|
708
|
+
{ text: 'Cancel', style: 'cancel' },
|
|
709
|
+
{ text: 'Clear All', onPress: handleClearAll, style: 'destructive' },
|
|
710
|
+
]);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
const detailTitle = useMemo(() => {
|
|
714
|
+
if (!selected)
|
|
715
|
+
return '';
|
|
716
|
+
try {
|
|
717
|
+
const path = new URL(selected.url).pathname;
|
|
718
|
+
const parts = path.split('/').filter(Boolean);
|
|
719
|
+
return parts.length > 0 ? `/${parts.slice(-2).join('/')}` : '/';
|
|
720
|
+
}
|
|
721
|
+
catch {
|
|
722
|
+
const parts = selected.url.split('/').filter(Boolean);
|
|
723
|
+
return parts.length > 0 ? `/${parts.slice(-2).join('/')}` : selected.url;
|
|
724
|
+
}
|
|
725
|
+
}, [selected]);
|
|
726
|
+
const detailDisplayUrl = useMemo(() => {
|
|
727
|
+
return selected ? formatDisplayUrl(selected.url) : '';
|
|
728
|
+
}, [selected]);
|
|
729
|
+
const toggleSelect = useCallback((id) => {
|
|
730
|
+
setSelectedLogs(prev => {
|
|
731
|
+
const next = new Set(prev);
|
|
732
|
+
if (next.has(id))
|
|
733
|
+
next.delete(id);
|
|
734
|
+
else
|
|
735
|
+
next.add(id);
|
|
736
|
+
return next;
|
|
737
|
+
});
|
|
738
|
+
}, []);
|
|
739
|
+
const renderItem = useCallback(({ item }) => {
|
|
740
|
+
if (item.type === 'header') {
|
|
741
|
+
return (<DomainHeader pageName={item.pageName} color={item.color} stats={item.stats} activeFilters={item.activeFilters} onToggleFilter={toggleSectionFilter} isCollapsed={item.isCollapsed} onToggleCollapse={toggleSectionCollapse} isFirst={item.isFirst} timestamp={item.timestamp}/>);
|
|
742
|
+
}
|
|
743
|
+
const { log, isLast, color } = item;
|
|
744
|
+
return (<View style={styles.treeNodeRow}>
|
|
745
|
+
<View style={styles.treeLines}>
|
|
746
|
+
<View style={[
|
|
747
|
+
styles.modernTreeLine,
|
|
748
|
+
{ borderColor: color },
|
|
749
|
+
isLast && styles.modernTreeLineLast,
|
|
750
|
+
]}/>
|
|
751
|
+
{!isLast && (<View style={[styles.modernTreeBranch, { borderColor: color }]}/>)}
|
|
752
|
+
</View>
|
|
753
|
+
<View style={styles.treeCardWrapper}>
|
|
754
|
+
<LogCard item={log} isSelected={selectedLogs.has(log.id)} onToggleSelect={toggleSelect} onPress={() => setSelected(log)} timelineMinStart={minStart} timelineTotalRange={totalRange} isNew={newLogIds.has(log.id)} searchStr={search}/>
|
|
755
|
+
</View>
|
|
756
|
+
</View>);
|
|
757
|
+
}, [
|
|
758
|
+
minStart,
|
|
759
|
+
totalRange,
|
|
760
|
+
newLogIds,
|
|
761
|
+
selectedLogs,
|
|
762
|
+
toggleSelect,
|
|
763
|
+
search,
|
|
764
|
+
toggleSectionFilter,
|
|
765
|
+
toggleSectionCollapse,
|
|
766
|
+
]);
|
|
767
|
+
return (<>
|
|
768
|
+
<TouchableScale style={styles.fabWrapper} onPress={() => setVisible(true)} hitSlop={10}>
|
|
769
|
+
<Animated.View style={[styles.fabPulseRing, { transform: [{ scale: pulseAnim }] }]}/>
|
|
770
|
+
<LinearGradient colors={[AppColors.purple, '#8F6EFF']} style={styles.fab}>
|
|
771
|
+
<Text style={styles.fabText}>API</Text>
|
|
772
|
+
</LinearGradient>
|
|
773
|
+
{(logs.length > 0 || analyticsEvents.length > 0) && (<Animated.View style={[
|
|
774
|
+
styles.fabBadge,
|
|
775
|
+
hasErrors ? styles.fabBadgeError : styles.fabBadgeNormal,
|
|
776
|
+
{ transform: [{ scale: badgeAnim }] },
|
|
777
|
+
]}>
|
|
778
|
+
<Text style={styles.fabBadgeText}>
|
|
779
|
+
{logs.length + analyticsEvents.length > 99
|
|
780
|
+
? '99+'
|
|
781
|
+
: logs.length + analyticsEvents.length}
|
|
782
|
+
</Text>
|
|
783
|
+
</Animated.View>)}
|
|
784
|
+
</TouchableScale>
|
|
785
|
+
|
|
786
|
+
<Modal visible={visible} animationType="slide" transparent>
|
|
787
|
+
{visible && (<View style={{ flex: 1, backgroundColor: AppColors.grayBackground }}>
|
|
788
|
+
<StatusBar translucent backgroundColor="transparent" barStyle="light-content"/>
|
|
789
|
+
|
|
790
|
+
<LinearGradient colors={[AppColors.purple, '#6B4EFF']} style={styles.headerGradient}>
|
|
791
|
+
<View style={[
|
|
792
|
+
styles.header,
|
|
793
|
+
{
|
|
794
|
+
paddingTop: Platform.OS === 'android'
|
|
795
|
+
? (StatusBar.currentHeight ?? 24) + 16
|
|
796
|
+
: 54,
|
|
797
|
+
},
|
|
798
|
+
]}>
|
|
799
|
+
<View style={[
|
|
800
|
+
styles.headerLeft,
|
|
801
|
+
{ flexDirection: 'row', alignItems: 'center', gap: 16 },
|
|
802
|
+
]}>
|
|
803
|
+
<TouchableScale onPress={() => {
|
|
804
|
+
requestAnimationFrame(() => {
|
|
805
|
+
setSelected(null);
|
|
806
|
+
setSelectedEvent(null);
|
|
807
|
+
});
|
|
808
|
+
}} hitSlop={15} style={[
|
|
809
|
+
styles.iconBtnMinimal,
|
|
810
|
+
selected == null &&
|
|
811
|
+
selectedEvent == null && { display: 'none' },
|
|
812
|
+
]}>
|
|
813
|
+
<WhiteBackNavigation />
|
|
814
|
+
</TouchableScale>
|
|
815
|
+
|
|
816
|
+
{selected == null && selectedEvent == null ? (<View style={styles.headerButtonGroup}>
|
|
817
|
+
{/* Network Dropdown */}
|
|
818
|
+
<Pressable onPress={() => {
|
|
819
|
+
setShowNetworkMenu(prev => !prev);
|
|
820
|
+
setShowUiMenu(false);
|
|
821
|
+
}} style={[
|
|
822
|
+
styles.headerGroupButton,
|
|
823
|
+
['apis', 'analytics', 'logs'].includes(activeTab) &&
|
|
824
|
+
styles.headerGroupButtonActive,
|
|
825
|
+
]}>
|
|
826
|
+
<Text style={[
|
|
827
|
+
styles.headerGroupButtonText,
|
|
828
|
+
['apis', 'analytics', 'logs'].includes(activeTab) && { color: '#FFFFFF' },
|
|
829
|
+
]}>
|
|
830
|
+
APIs
|
|
831
|
+
</Text>
|
|
832
|
+
<View style={{
|
|
833
|
+
transform: [
|
|
834
|
+
{ rotate: showNetworkMenu ? '180deg' : '0deg' },
|
|
835
|
+
],
|
|
836
|
+
}}>
|
|
837
|
+
<ChevronIcon color={['apis', 'analytics', 'logs'].includes(activeTab)
|
|
838
|
+
? '#FFFFFF'
|
|
839
|
+
: 'rgba(255, 255, 255, 0.6)'} size={10}/>
|
|
840
|
+
</View>
|
|
841
|
+
</Pressable>
|
|
842
|
+
|
|
843
|
+
{/* UI Dropdown */}
|
|
844
|
+
<Pressable onPress={() => {
|
|
845
|
+
setShowUiMenu(prev => !prev);
|
|
846
|
+
setShowNetworkMenu(false);
|
|
847
|
+
}} style={[
|
|
848
|
+
styles.headerGroupButton,
|
|
849
|
+
activeTab === 'webview' &&
|
|
850
|
+
styles.headerGroupButtonActive,
|
|
851
|
+
]}>
|
|
852
|
+
<Text style={[
|
|
853
|
+
styles.headerGroupButtonText,
|
|
854
|
+
activeTab === 'webview' && { color: '#FFFFFF' },
|
|
855
|
+
]}>
|
|
856
|
+
UI
|
|
857
|
+
</Text>
|
|
858
|
+
<View style={{
|
|
859
|
+
transform: [
|
|
860
|
+
{ rotate: showUiMenu ? '180deg' : '0deg' },
|
|
861
|
+
],
|
|
862
|
+
}}>
|
|
863
|
+
<ChevronIcon color={activeTab === 'webview'
|
|
864
|
+
? '#FFFFFF'
|
|
865
|
+
: 'rgba(255, 255, 255, 0.6)'} size={10}/>
|
|
866
|
+
</View>
|
|
867
|
+
</Pressable>
|
|
868
|
+
</View>) : null}
|
|
869
|
+
</View>
|
|
870
|
+
|
|
871
|
+
<View style={styles.headerCenter}>
|
|
872
|
+
{selected != null ? (<View style={styles.headerDetailCenter}>
|
|
873
|
+
<View style={styles.headerDetailRow}>
|
|
874
|
+
<View style={[
|
|
875
|
+
styles.headerMethodBadge,
|
|
876
|
+
{
|
|
877
|
+
backgroundColor: METHOD_COLORS[selected.method] ??
|
|
878
|
+
AppColors.grayText,
|
|
879
|
+
},
|
|
880
|
+
]}>
|
|
881
|
+
<Text style={styles.headerMethodText}>
|
|
882
|
+
{selected.method}
|
|
883
|
+
</Text>
|
|
884
|
+
</View>
|
|
885
|
+
<Text style={styles.headerDetailTitle} numberOfLines={1} ellipsizeMode="middle">
|
|
886
|
+
{detailTitle}
|
|
887
|
+
</Text>
|
|
888
|
+
</View>
|
|
889
|
+
<View style={styles.headerDetailSubRow}>
|
|
890
|
+
<View style={[
|
|
891
|
+
styles.headerStatusDot,
|
|
892
|
+
{ backgroundColor: getStatusColor(selected.status) },
|
|
893
|
+
]}/>
|
|
894
|
+
<Text style={styles.headerSubTitle}>
|
|
895
|
+
{selected.status === 0
|
|
896
|
+
? 'Failed'
|
|
897
|
+
: selected.status ?? 'Pending'}{' '}
|
|
898
|
+
•{' '}
|
|
899
|
+
{selected.duration != null
|
|
900
|
+
? `${selected.duration}ms`
|
|
901
|
+
: '-'}
|
|
902
|
+
</Text>
|
|
903
|
+
</View>
|
|
904
|
+
</View>) : selectedEvent != null ? (<View style={styles.headerDetailCenter}>
|
|
905
|
+
<View style={styles.headerDetailRow}>
|
|
906
|
+
<View style={[
|
|
907
|
+
styles.headerMethodBadge,
|
|
908
|
+
{
|
|
909
|
+
backgroundColor: selectedEvent.source === 'firebase'
|
|
910
|
+
? 'rgba(224,123,26,0.3)'
|
|
911
|
+
: 'rgba(124,92,191,0.3)',
|
|
912
|
+
},
|
|
913
|
+
]}>
|
|
914
|
+
<Text style={styles.headerMethodText}>
|
|
915
|
+
{selectedEvent.source === 'firebase' ? 'FB' : 'MAN'}
|
|
916
|
+
</Text>
|
|
917
|
+
</View>
|
|
918
|
+
<Text style={styles.headerDetailTitle} numberOfLines={1} ellipsizeMode="middle">
|
|
919
|
+
{selectedEvent.name}
|
|
920
|
+
</Text>
|
|
921
|
+
</View>
|
|
922
|
+
<View style={styles.headerDetailSubRow}>
|
|
923
|
+
<View style={[
|
|
924
|
+
styles.headerStatusDot,
|
|
925
|
+
{
|
|
926
|
+
backgroundColor: selectedEvent.source === 'firebase'
|
|
927
|
+
? '#E07B1A'
|
|
928
|
+
: AppColors.purple,
|
|
929
|
+
},
|
|
930
|
+
]}/>
|
|
931
|
+
<Text style={styles.headerSubTitle}>
|
|
932
|
+
{Object.keys(selectedEvent.params).length} param
|
|
933
|
+
{Object.keys(selectedEvent.params).length !== 1
|
|
934
|
+
? 's'
|
|
935
|
+
: ''}
|
|
936
|
+
{' · '}
|
|
937
|
+
{selectedEvent.source}
|
|
938
|
+
</Text>
|
|
939
|
+
</View>
|
|
940
|
+
</View>) : (<View style={styles.headerCountBadge}>
|
|
941
|
+
<Text style={styles.headerCountText}>
|
|
942
|
+
{activeTab === 'apis'
|
|
943
|
+
? `${logs.length} API${logs.length !== 1 ? 's' : ''}`
|
|
944
|
+
: activeTab === 'logs'
|
|
945
|
+
? `${visibleConsoleLogs.length} log${visibleConsoleLogs.length !== 1 ? 's' : ''}`
|
|
946
|
+
: activeTab === 'analytics'
|
|
947
|
+
? `${analyticsEvents.length} event${analyticsEvents.length !== 1 ? 's' : ''}`
|
|
948
|
+
: `${webViewLogs.length} log${webViewLogs.length !== 1 ? 's' : ''}`}
|
|
949
|
+
</Text>
|
|
950
|
+
</View>)}
|
|
951
|
+
</View>
|
|
952
|
+
|
|
953
|
+
<View style={styles.headerRight}>
|
|
954
|
+
<TouchableScale onPress={closeModal} hitSlop={15} style={styles.iconBtnMinimal}>
|
|
955
|
+
<CloseWhite />
|
|
956
|
+
</TouchableScale>
|
|
957
|
+
</View>
|
|
958
|
+
</View>
|
|
959
|
+
</LinearGradient>
|
|
960
|
+
|
|
961
|
+
{(showNetworkMenu || showUiMenu) && selected == null && (<Pressable style={{
|
|
962
|
+
position: 'absolute',
|
|
963
|
+
top: 0,
|
|
964
|
+
left: 0,
|
|
965
|
+
right: 0,
|
|
966
|
+
bottom: 0,
|
|
967
|
+
zIndex: 99,
|
|
968
|
+
}} onPress={() => {
|
|
969
|
+
setShowNetworkMenu(false);
|
|
970
|
+
setShowUiMenu(false);
|
|
971
|
+
}}/>)}
|
|
972
|
+
|
|
973
|
+
{selected == null && selectedEvent == null && (<View style={{
|
|
974
|
+
paddingHorizontal: 16,
|
|
975
|
+
paddingTop: 10,
|
|
976
|
+
paddingBottom: 2,
|
|
977
|
+
}}>
|
|
978
|
+
<Text style={{
|
|
979
|
+
fontFamily: AppFonts.interMedium,
|
|
980
|
+
fontSize: 11.5,
|
|
981
|
+
color: AppColors.grayTextWeak,
|
|
982
|
+
}}>
|
|
983
|
+
{['apis', 'analytics', 'logs'].includes(activeTab)
|
|
984
|
+
? 'APIs'
|
|
985
|
+
: 'UI'}
|
|
986
|
+
{' ➔ '}
|
|
987
|
+
<Text style={{
|
|
988
|
+
color: AppColors.purple,
|
|
989
|
+
fontFamily: AppFonts.interBold,
|
|
990
|
+
}}>
|
|
991
|
+
{activeTab === 'apis'
|
|
992
|
+
? 'APIs'
|
|
993
|
+
: activeTab === 'analytics'
|
|
994
|
+
? 'Analytics'
|
|
995
|
+
: activeTab === 'logs'
|
|
996
|
+
? 'Logs'
|
|
997
|
+
: 'WebView'}
|
|
998
|
+
</Text>
|
|
999
|
+
</Text>
|
|
1000
|
+
</View>)}
|
|
1001
|
+
|
|
1002
|
+
{showNetworkMenu && selected == null && selectedEvent == null && (<View style={[styles.menuDropdown, { left: 16 }]}>
|
|
1003
|
+
{['apis', 'analytics', 'logs'].map(tab => {
|
|
1004
|
+
const label = tab === 'apis'
|
|
1005
|
+
? 'APIs'
|
|
1006
|
+
: tab === 'analytics'
|
|
1007
|
+
? 'Analytics'
|
|
1008
|
+
: 'Logs';
|
|
1009
|
+
const count = tab === 'apis'
|
|
1010
|
+
? logs.length
|
|
1011
|
+
: tab === 'analytics'
|
|
1012
|
+
? analyticsEvents.length
|
|
1013
|
+
: visibleConsoleLogs.length;
|
|
1014
|
+
const isActive = activeTab === tab;
|
|
1015
|
+
return (<Pressable key={tab} style={[
|
|
1016
|
+
styles.dropdownItem,
|
|
1017
|
+
isActive && { backgroundColor: `${AppColors.purple}12` },
|
|
1018
|
+
]} onPress={() => {
|
|
1019
|
+
setActiveTab(tab);
|
|
1020
|
+
setShowNetworkMenu(false);
|
|
1021
|
+
}} hitSlop={10}>
|
|
1022
|
+
<Text style={{
|
|
1023
|
+
fontFamily: AppFonts.interMedium,
|
|
1024
|
+
color: isActive
|
|
1025
|
+
? AppColors.purple
|
|
1026
|
+
: AppColors.primaryBlack,
|
|
1027
|
+
}}>
|
|
1028
|
+
{label} {count > 0 ? `(${count})` : ''}
|
|
1029
|
+
</Text>
|
|
1030
|
+
</Pressable>);
|
|
1031
|
+
})}
|
|
1032
|
+
</View>)}
|
|
1033
|
+
|
|
1034
|
+
{showUiMenu && selected == null && selectedEvent == null && (<View style={[styles.menuDropdown, { left: 120 }]}>
|
|
1035
|
+
{['webview'].map(tab => {
|
|
1036
|
+
const isActive = activeTab === tab;
|
|
1037
|
+
return (<Pressable key={tab} style={[
|
|
1038
|
+
styles.dropdownItem,
|
|
1039
|
+
isActive && { backgroundColor: `${AppColors.purple}12` },
|
|
1040
|
+
]} onPress={() => {
|
|
1041
|
+
setActiveTab(tab);
|
|
1042
|
+
setShowUiMenu(false);
|
|
1043
|
+
}} hitSlop={10}>
|
|
1044
|
+
<Text style={{
|
|
1045
|
+
fontFamily: AppFonts.interMedium,
|
|
1046
|
+
color: isActive
|
|
1047
|
+
? AppColors.purple
|
|
1048
|
+
: AppColors.primaryBlack,
|
|
1049
|
+
}}>
|
|
1050
|
+
WebView{' '}
|
|
1051
|
+
{webViewLogs.length > 0
|
|
1052
|
+
? `(${webViewLogs.length})`
|
|
1053
|
+
: ''}
|
|
1054
|
+
</Text>
|
|
1055
|
+
</Pressable>);
|
|
1056
|
+
})}
|
|
1057
|
+
</View>)}
|
|
1058
|
+
|
|
1059
|
+
{/* ─── Secondary Tab Bar for Analytics ──────────────────────── */}
|
|
1060
|
+
{isReady && activeTab === 'analytics' && selectedEvent == null && (<View>
|
|
1061
|
+
{/* ─── Search + Shared Toolbar for Analytics ──────────────────────── */}
|
|
1062
|
+
<View style={[styles.toolbarRow, { marginTop: 12, marginBottom: 4 }]}>
|
|
1063
|
+
<View style={styles.searchContainer}>
|
|
1064
|
+
<SearchIcon color={AppColors.grayTextWeak} size={16}/>
|
|
1065
|
+
<TextInput placeholder="Search events..." placeholderTextColor={AppColors.grayTextWeak} value={analyticsSearch} onChangeText={setAnalyticsSearch} style={styles.searchInput} autoCorrect={false} autoCapitalize="none"/>
|
|
1066
|
+
{analyticsSearch.length > 0 && (<Pressable onPress={() => setAnalyticsSearch('')} hitSlop={10} style={styles.clearBtn}>
|
|
1067
|
+
<ClearIcon color={AppColors.grayTextWeak} size={14}/>
|
|
1068
|
+
</Pressable>)}
|
|
1069
|
+
</View>
|
|
1070
|
+
{analyticsSubTab === 'ga_events' && (<View style={styles.toolbarRight}>
|
|
1071
|
+
<TouchableScale style={[
|
|
1072
|
+
styles.toolbarBtn,
|
|
1073
|
+
groupByScreen && styles.toolbarBtnActive,
|
|
1074
|
+
]} onPress={() => setGroupByScreen(prev => !prev)} hitSlop={10}>
|
|
1075
|
+
<MapPinIcon color={groupByScreen
|
|
1076
|
+
? AppColors.purple
|
|
1077
|
+
: AppColors.grayTextStrong} size={18}/>
|
|
1078
|
+
</TouchableScale>
|
|
1079
|
+
<TouchableScale style={[
|
|
1080
|
+
styles.toolbarBtn,
|
|
1081
|
+
!hideScreenView && styles.toolbarBtnActive,
|
|
1082
|
+
]} onPress={() => setHideScreenView(prev => !prev)} hitSlop={10}>
|
|
1083
|
+
<ScreenIcon color={!hideScreenView
|
|
1084
|
+
? AppColors.purple
|
|
1085
|
+
: AppColors.grayTextStrong} size={18}/>
|
|
1086
|
+
</TouchableScale>
|
|
1087
|
+
<TouchableScale style={styles.toolbarBtn} onPress={handleDelete} hitSlop={10}>
|
|
1088
|
+
<TrashIcon color={AppColors.grayTextStrong} size={18}/>
|
|
1089
|
+
</TouchableScale>
|
|
1090
|
+
</View>)}
|
|
1091
|
+
</View>
|
|
1092
|
+
|
|
1093
|
+
{/* ─── Secondary Tab Bar for Analytics ──────────────────────── */}
|
|
1094
|
+
<View style={{
|
|
1095
|
+
marginHorizontal: 16,
|
|
1096
|
+
marginTop: 4,
|
|
1097
|
+
marginBottom: 8,
|
|
1098
|
+
backgroundColor: AppColors.grayBackground,
|
|
1099
|
+
borderRadius: 8,
|
|
1100
|
+
padding: 4,
|
|
1101
|
+
flexDirection: 'row',
|
|
1102
|
+
borderWidth: 1,
|
|
1103
|
+
borderColor: AppColors.grayBorderSecondary,
|
|
1104
|
+
}}>
|
|
1105
|
+
<Pressable style={[
|
|
1106
|
+
{
|
|
1107
|
+
flex: 1,
|
|
1108
|
+
paddingVertical: 8,
|
|
1109
|
+
borderRadius: 6,
|
|
1110
|
+
alignItems: 'center',
|
|
1111
|
+
},
|
|
1112
|
+
analyticsSubTab === 'ga_events' && {
|
|
1113
|
+
backgroundColor: AppColors.primaryLight,
|
|
1114
|
+
shadowColor: '#000',
|
|
1115
|
+
shadowOpacity: 0.1,
|
|
1116
|
+
shadowRadius: 3,
|
|
1117
|
+
shadowOffset: { width: 0, height: 1 },
|
|
1118
|
+
elevation: 2,
|
|
1119
|
+
},
|
|
1120
|
+
]} onPress={() => setAnalyticsSubTab('ga_events')}>
|
|
1121
|
+
<Text style={[
|
|
1122
|
+
{
|
|
1123
|
+
fontFamily: AppFonts.interMedium,
|
|
1124
|
+
fontSize: 13,
|
|
1125
|
+
color: AppColors.grayTextStrong,
|
|
1126
|
+
},
|
|
1127
|
+
analyticsSubTab === 'ga_events' && {
|
|
1128
|
+
fontFamily: AppFonts.interBold,
|
|
1129
|
+
color: AppColors.purple,
|
|
1130
|
+
},
|
|
1131
|
+
]}>
|
|
1132
|
+
GA Events (
|
|
1133
|
+
{analyticsSearch
|
|
1134
|
+
? filteredAnalyticsEvents.length
|
|
1135
|
+
: analyticsEvents.length}
|
|
1136
|
+
)
|
|
1137
|
+
</Text>
|
|
1138
|
+
</Pressable>
|
|
1139
|
+
<Pressable style={[
|
|
1140
|
+
{
|
|
1141
|
+
flex: 1,
|
|
1142
|
+
paddingVertical: 8,
|
|
1143
|
+
borderRadius: 6,
|
|
1144
|
+
alignItems: 'center',
|
|
1145
|
+
},
|
|
1146
|
+
analyticsSubTab === 'top_events' && {
|
|
1147
|
+
backgroundColor: AppColors.primaryLight,
|
|
1148
|
+
shadowColor: '#000',
|
|
1149
|
+
shadowOpacity: 0.1,
|
|
1150
|
+
shadowRadius: 3,
|
|
1151
|
+
shadowOffset: { width: 0, height: 1 },
|
|
1152
|
+
elevation: 2,
|
|
1153
|
+
},
|
|
1154
|
+
]} onPress={() => setAnalyticsSubTab('top_events')}>
|
|
1155
|
+
<Text style={[
|
|
1156
|
+
{
|
|
1157
|
+
fontFamily: AppFonts.interMedium,
|
|
1158
|
+
fontSize: 13,
|
|
1159
|
+
color: AppColors.grayTextStrong,
|
|
1160
|
+
},
|
|
1161
|
+
analyticsSubTab === 'top_events' && {
|
|
1162
|
+
fontFamily: AppFonts.interBold,
|
|
1163
|
+
color: AppColors.purple,
|
|
1164
|
+
},
|
|
1165
|
+
]}>
|
|
1166
|
+
Top Events ({topEventsArray.length})
|
|
1167
|
+
</Text>
|
|
1168
|
+
</Pressable>
|
|
1169
|
+
</View>
|
|
1170
|
+
</View>)}
|
|
1171
|
+
|
|
1172
|
+
{isReady ? (activeTab === 'analytics' ? (selectedEvent != null ? (<AnalyticsDetail event={selectedEvent}/>) : analyticsSubTab === 'top_events' ? (<FlatList data={topEventsArray} keyExtractor={item => item[0]} contentContainerStyle={[
|
|
1173
|
+
styles.listContent,
|
|
1174
|
+
{ paddingHorizontal: 16, paddingTop: 16 },
|
|
1175
|
+
]} renderItem={({ item: [name, count] }) => {
|
|
1176
|
+
const maxCount = topEventsArray[0]?.[1] || 1;
|
|
1177
|
+
const color = getEventColor(name);
|
|
1178
|
+
return (<View style={[
|
|
1179
|
+
analyticsListStyles.topEventsCard,
|
|
1180
|
+
{ marginBottom: 12, paddingVertical: 16 },
|
|
1181
|
+
]}>
|
|
1182
|
+
<View style={analyticsListStyles.topEventRow}>
|
|
1183
|
+
<View style={{
|
|
1184
|
+
flexDirection: 'row',
|
|
1185
|
+
alignItems: 'center',
|
|
1186
|
+
gap: 8,
|
|
1187
|
+
flex: 1,
|
|
1188
|
+
}}>
|
|
1189
|
+
<View style={[
|
|
1190
|
+
analyticsListStyles.iconCircle,
|
|
1191
|
+
{ backgroundColor: `${color}1A` },
|
|
1192
|
+
]}>
|
|
1193
|
+
<Svg width={14} height={14} viewBox="0 0 24 24" fill={color}>
|
|
1194
|
+
<Circle cx="12" cy="12" r="10" opacity="0.3"/>
|
|
1195
|
+
<Path d="M7 14l3-3 4 4 6-6" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" fill="none"/>
|
|
1196
|
+
</Svg>
|
|
1197
|
+
</View>
|
|
1198
|
+
<Text style={analyticsListStyles.topEventName} numberOfLines={2}>
|
|
1199
|
+
{name}
|
|
1200
|
+
</Text>
|
|
1201
|
+
</View>
|
|
1202
|
+
<View style={analyticsListStyles.topEventBarWrap}>
|
|
1203
|
+
<LinearGradient colors={[color, `${color}99`]} start={{ x: 0, y: 0 }} end={{ x: 1, y: 0 }} style={[
|
|
1204
|
+
analyticsListStyles.topEventBar,
|
|
1205
|
+
{
|
|
1206
|
+
width: `${Math.max(6, (count / maxCount) * 100)}%`,
|
|
1207
|
+
},
|
|
1208
|
+
]}/>
|
|
1209
|
+
</View>
|
|
1210
|
+
<Text style={analyticsListStyles.topEventCount}>
|
|
1211
|
+
{count}
|
|
1212
|
+
</Text>
|
|
1213
|
+
</View>
|
|
1214
|
+
</View>);
|
|
1215
|
+
}} ListEmptyComponent={<View style={styles.emptyContainer}>
|
|
1216
|
+
<View style={styles.emptyIconWrap}>
|
|
1217
|
+
<EmptyRadarIcon color={AppColors.purple} size={32}/>
|
|
1218
|
+
</View>
|
|
1219
|
+
<Text style={styles.emptyTitle}>No Top Events</Text>
|
|
1220
|
+
</View>}/>) : groupByScreen ? (<SectionList sections={groupedAnalyticsEvents} keyExtractor={item => item.id.toString()} renderSectionHeader={renderScreenSectionHeader} renderItem={({ item, index, section }) => {
|
|
1221
|
+
if (expandedScreens.has(section.title))
|
|
1222
|
+
return null;
|
|
1223
|
+
const events = section.data;
|
|
1224
|
+
const prev = events[index + 1];
|
|
1225
|
+
const next = events[index - 1];
|
|
1226
|
+
const msSincePrev = prev
|
|
1227
|
+
? item.timestamp - prev.timestamp
|
|
1228
|
+
: undefined;
|
|
1229
|
+
const thisMin = Math.floor(item.timestamp / 60000);
|
|
1230
|
+
const nextMin = next
|
|
1231
|
+
? Math.floor(next.timestamp / 60000)
|
|
1232
|
+
: -1;
|
|
1233
|
+
const showTimestamp = index === 0 || thisMin !== nextMin;
|
|
1234
|
+
return (<AnalyticsEventCard event={item} onPress={() => setSelectedEvent(item)} isNew={newEventIds.has(item.id)} searchStr={analyticsSearch} isFirst={index === 0} isLast={index === events.length - 1} msSincePrev={msSincePrev} showTimestamp={showTimestamp} computedScreenName={section.title}/>);
|
|
1235
|
+
}} initialNumToRender={20} maxToRenderPerBatch={20} windowSize={5} removeClippedSubviews ListHeaderComponent={null} ListEmptyComponent={<View style={styles.emptyContainer}>
|
|
1236
|
+
<View style={styles.emptyIconWrap}>
|
|
1237
|
+
<EmptyRadarIcon color={AppColors.purple} size={32}/>
|
|
1238
|
+
</View>
|
|
1239
|
+
<Text style={styles.emptyTitle}>
|
|
1240
|
+
{analyticsSearch.length > 0
|
|
1241
|
+
? 'No matching events'
|
|
1242
|
+
: 'No analytics events yet'}
|
|
1243
|
+
</Text>
|
|
1244
|
+
<Text style={styles.emptySub}>
|
|
1245
|
+
{analyticsSearch.length > 0
|
|
1246
|
+
? 'Try adjusting your search.'
|
|
1247
|
+
: 'Call setupAnalyticsLogger(analytics()) at app start.'}
|
|
1248
|
+
</Text>
|
|
1249
|
+
</View>} contentContainerStyle={[
|
|
1250
|
+
styles.listContent,
|
|
1251
|
+
filteredAnalyticsEvents.length === 0 && { flexGrow: 1 },
|
|
1252
|
+
]} keyboardShouldPersistTaps="handled"/>) : (<FlatList data={filteredAnalyticsEvents} keyExtractor={item => item.id.toString()} renderItem={({ item, index }) => {
|
|
1253
|
+
const prev = filteredAnalyticsEvents[index + 1];
|
|
1254
|
+
const next = filteredAnalyticsEvents[index - 1];
|
|
1255
|
+
const msSincePrev = prev
|
|
1256
|
+
? item.timestamp - prev.timestamp
|
|
1257
|
+
: undefined;
|
|
1258
|
+
const thisMin = Math.floor(item.timestamp / 60000);
|
|
1259
|
+
const nextMin = next
|
|
1260
|
+
? Math.floor(next.timestamp / 60000)
|
|
1261
|
+
: -1;
|
|
1262
|
+
const showTimestamp = index === 0 || thisMin !== nextMin;
|
|
1263
|
+
return (<AnalyticsEventCard event={item} onPress={() => setSelectedEvent(item)} isNew={newEventIds.has(item.id)} searchStr={analyticsSearch} isFirst={index === 0} isLast={index === filteredAnalyticsEvents.length - 1} msSincePrev={msSincePrev} showTimestamp={showTimestamp} computedScreenName={(() => {
|
|
1264
|
+
let screenName = item.screenName ||
|
|
1265
|
+
item.screenClass ||
|
|
1266
|
+
item.pageTitle ||
|
|
1267
|
+
item.pageLocation ||
|
|
1268
|
+
item.params?.firebase_screen ||
|
|
1269
|
+
item.params?.screen_name ||
|
|
1270
|
+
item.params?.firebase_screen_class ||
|
|
1271
|
+
item.params?.screen_class;
|
|
1272
|
+
const routeInfo = logRouteMapRef.current.get(item.id + 1000000);
|
|
1273
|
+
if (!screenName) {
|
|
1274
|
+
if (routeInfo &&
|
|
1275
|
+
routeInfo.path !== 'Navigators') {
|
|
1276
|
+
const parts = routeInfo.path.split(' ➔ ');
|
|
1277
|
+
screenName = parts[parts.length - 1];
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
return screenName;
|
|
1281
|
+
})()}/>);
|
|
1282
|
+
}} initialNumToRender={20} maxToRenderPerBatch={20} windowSize={5} removeClippedSubviews ListHeaderComponent={null} ListEmptyComponent={<View style={styles.emptyContainer}>
|
|
1283
|
+
<View style={styles.emptyIconWrap}>
|
|
1284
|
+
<EmptyRadarIcon color={AppColors.purple} size={32}/>
|
|
1285
|
+
</View>
|
|
1286
|
+
<Text style={styles.emptyTitle}>
|
|
1287
|
+
{analyticsSearch.length > 0
|
|
1288
|
+
? 'No matching events'
|
|
1289
|
+
: 'No analytics events yet'}
|
|
1290
|
+
</Text>
|
|
1291
|
+
<Text style={styles.emptySub}>
|
|
1292
|
+
{analyticsSearch.length > 0
|
|
1293
|
+
? 'Try adjusting your search.'
|
|
1294
|
+
: 'Call setupAnalyticsLogger(analytics()) at app start.'}
|
|
1295
|
+
</Text>
|
|
1296
|
+
</View>} contentContainerStyle={[
|
|
1297
|
+
styles.listContent,
|
|
1298
|
+
filteredAnalyticsEvents.length === 0 && { flexGrow: 1 },
|
|
1299
|
+
]} keyboardShouldPersistTaps="handled"/>)) : activeTab === 'apis' && selected == null ? (groupByScreen ? (<SectionList sections={groupedNetworkLogs} keyExtractor={item => item?.id?.toString()} renderSectionHeader={renderScreenSectionHeader} renderItem={({ item, section }) => {
|
|
1300
|
+
if (expandedScreens.has(section.title))
|
|
1301
|
+
return null;
|
|
1302
|
+
return renderItem({
|
|
1303
|
+
item: {
|
|
1304
|
+
type: 'log',
|
|
1305
|
+
log: item,
|
|
1306
|
+
color: AppColors.purple,
|
|
1307
|
+
},
|
|
1308
|
+
});
|
|
1309
|
+
}} initialNumToRender={10} maxToRenderPerBatch={10} windowSize={5} removeClippedSubviews={true} ListHeaderComponent={<View style={{ marginTop: 8 }}>
|
|
1310
|
+
{logs.length > 0 && (<View style={styles.dashboardCard}>
|
|
1311
|
+
<View style={styles.dashboardStatsRow}>
|
|
1312
|
+
<View style={styles.statBox}>
|
|
1313
|
+
<Text style={styles.statValue}>
|
|
1314
|
+
{stats.filtered < stats.total
|
|
1315
|
+
? stats.filtered
|
|
1316
|
+
: stats.total}
|
|
1317
|
+
</Text>
|
|
1318
|
+
<Text style={styles.statLabel}>
|
|
1319
|
+
{stats.filtered < stats.total
|
|
1320
|
+
? `of ${stats.total} Req`
|
|
1321
|
+
: 'Requests'}
|
|
1322
|
+
</Text>
|
|
1323
|
+
<View style={styles.miniGraphWrap}>
|
|
1324
|
+
<MiniBarChart data={stats.reqTrend} color={AppColors.purple}/>
|
|
1325
|
+
</View>
|
|
1326
|
+
</View>
|
|
1327
|
+
<View style={styles.dashboardStatDivider}/>
|
|
1328
|
+
<View style={styles.statBox}>
|
|
1329
|
+
<Text style={[
|
|
1330
|
+
styles.statValue,
|
|
1331
|
+
stats.errors > 0 && {
|
|
1332
|
+
color: AppColors.errorColor,
|
|
1333
|
+
},
|
|
1334
|
+
]}>
|
|
1335
|
+
{stats.errors}
|
|
1336
|
+
</Text>
|
|
1337
|
+
<Text style={styles.statLabel}>Errors</Text>
|
|
1338
|
+
<View style={styles.miniGraphWrap}>
|
|
1339
|
+
<MiniBarChart data={stats.errorTrend} color={AppColors.errorColor} maxVal={1}/>
|
|
1340
|
+
</View>
|
|
1341
|
+
</View>
|
|
1342
|
+
<View style={styles.dashboardStatDivider}/>
|
|
1343
|
+
<View style={styles.statBox}>
|
|
1344
|
+
<Text style={[
|
|
1345
|
+
styles.statValue,
|
|
1346
|
+
stats.avgDuration != null
|
|
1347
|
+
? {
|
|
1348
|
+
color: getDurationColor(stats.avgDuration),
|
|
1349
|
+
}
|
|
1350
|
+
: null,
|
|
1351
|
+
]}>
|
|
1352
|
+
{stats.avgDuration != null
|
|
1353
|
+
? `${stats.avgDuration}ms`
|
|
1354
|
+
: '—'}
|
|
1355
|
+
</Text>
|
|
1356
|
+
<Text style={styles.statLabel}>Avg Time</Text>
|
|
1357
|
+
<View style={styles.miniGraphWrap}>
|
|
1358
|
+
<MiniLineChart data={stats.durationTrend} color={AppColors.darkOrange}/>
|
|
1359
|
+
</View>
|
|
1360
|
+
</View>
|
|
1361
|
+
<View style={styles.dashboardStatDivider}/>
|
|
1362
|
+
<View style={styles.statBox}>
|
|
1363
|
+
<Text style={[
|
|
1364
|
+
styles.statValue,
|
|
1365
|
+
{ color: AppColors.skyBlue },
|
|
1366
|
+
]}>
|
|
1367
|
+
{stats.size}
|
|
1368
|
+
</Text>
|
|
1369
|
+
<Text style={styles.statLabel}>Payload</Text>
|
|
1370
|
+
<View style={styles.miniGraphWrap}>
|
|
1371
|
+
<MiniBarChart data={stats.sizeTrend} color={AppColors.skyBlue}/>
|
|
1372
|
+
</View>
|
|
1373
|
+
</View>
|
|
1374
|
+
</View>
|
|
1375
|
+
</View>)}
|
|
1376
|
+
|
|
1377
|
+
<View style={styles.toolbarRow}>
|
|
1378
|
+
<View style={styles.searchContainer}>
|
|
1379
|
+
<SearchIcon color={AppColors.grayTextWeak} size={16}/>
|
|
1380
|
+
<TextInput placeholder="Search endpoints..." placeholderTextColor={AppColors.grayTextWeak} value={search} onChangeText={setSearch} style={styles.searchInput} autoCorrect={false} autoCapitalize="none"/>
|
|
1381
|
+
{search.length > 0 && (<Pressable onPress={() => setSearch('')} hitSlop={10} style={styles.clearBtn}>
|
|
1382
|
+
<ClearIcon color={AppColors.grayTextWeak} size={14}/>
|
|
1383
|
+
</Pressable>)}
|
|
1384
|
+
</View>
|
|
1385
|
+
|
|
1386
|
+
<View style={styles.toolbarRight}>
|
|
1387
|
+
<TouchableScale style={styles.toolbarBtn} onPress={handleDelete} hitSlop={10}>
|
|
1388
|
+
<TrashIcon color={AppColors.grayTextStrong} size={18}/>
|
|
1389
|
+
{selectedLogs.size > 0 && (<View style={styles.trashBadge}>
|
|
1390
|
+
<Text style={styles.trashBadgeText}>
|
|
1391
|
+
{selectedLogs.size}
|
|
1392
|
+
</Text>
|
|
1393
|
+
</View>)}
|
|
1394
|
+
</TouchableScale>
|
|
1395
|
+
|
|
1396
|
+
<TouchableScale style={[
|
|
1397
|
+
styles.toolbarBtn,
|
|
1398
|
+
groupByScreen && styles.toolbarBtnActive,
|
|
1399
|
+
]} onPress={() => setGroupByScreen(prev => !prev)} hitSlop={10}>
|
|
1400
|
+
<MapPinIcon color={groupByScreen
|
|
1401
|
+
? AppColors.purple
|
|
1402
|
+
: AppColors.grayTextStrong} size={18}/>
|
|
1403
|
+
</TouchableScale>
|
|
1404
|
+
|
|
1405
|
+
<TouchableScale style={styles.toolbarBtn} onPress={() => setSortOrder(o => o === 'newest' ? 'oldest' : 'newest')} hitSlop={10}>
|
|
1406
|
+
<SortArrowIcon direction={sortOrder === 'newest' ? 'down' : 'up'} color={AppColors.grayTextStrong} size={18}/>
|
|
1407
|
+
</TouchableScale>
|
|
1408
|
+
|
|
1409
|
+
<TouchableScale style={[
|
|
1410
|
+
styles.toolbarBtn,
|
|
1411
|
+
filtersAccordion.isOpen &&
|
|
1412
|
+
styles.toolbarBtnActive,
|
|
1413
|
+
]} onPress={filtersAccordion.toggleOpen} hitSlop={10}>
|
|
1414
|
+
<FilterIcon color={filtersAccordion.isOpen
|
|
1415
|
+
? AppColors.purple
|
|
1416
|
+
: AppColors.grayTextStrong} size={18}/>
|
|
1417
|
+
</TouchableScale>
|
|
1418
|
+
</View>
|
|
1419
|
+
</View>
|
|
1420
|
+
|
|
1421
|
+
<Animated.View style={[
|
|
1422
|
+
filtersAccordion.bodyStyle,
|
|
1423
|
+
{ overflow: 'hidden' },
|
|
1424
|
+
]}>
|
|
1425
|
+
<View style={styles.filtersContainer}>
|
|
1426
|
+
<Text style={styles.filtersHeading}>STATUS</Text>
|
|
1427
|
+
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.statusRowContent}>
|
|
1428
|
+
{STATUS_FILTERS.map(filter => {
|
|
1429
|
+
const isAll = filter === 'ALL';
|
|
1430
|
+
const active = isAll
|
|
1431
|
+
? statusFilters.size === 0
|
|
1432
|
+
: statusFilters.has(filter);
|
|
1433
|
+
return (<TouchableScale key={filter} style={styles.statusFilterWrap} onPress={() => {
|
|
1434
|
+
if (isAll) {
|
|
1435
|
+
setStatusFilters(new Set());
|
|
1436
|
+
}
|
|
1437
|
+
else {
|
|
1438
|
+
setStatusFilters(prev => {
|
|
1439
|
+
const next = new Set(prev);
|
|
1440
|
+
next.has(filter)
|
|
1441
|
+
? next.delete(filter)
|
|
1442
|
+
: next.add(filter);
|
|
1443
|
+
return next;
|
|
1444
|
+
});
|
|
1445
|
+
}
|
|
1446
|
+
}} hitSlop={10}>
|
|
1447
|
+
{active ? (<LinearGradient colors={[
|
|
1448
|
+
AppColors.purpleShade50,
|
|
1449
|
+
'#EAE5FF',
|
|
1450
|
+
]} style={[
|
|
1451
|
+
styles.statusFilterChip,
|
|
1452
|
+
styles.statusFilterActive,
|
|
1453
|
+
]}>
|
|
1454
|
+
<Text style={[
|
|
1455
|
+
styles.statusFilterText,
|
|
1456
|
+
{ color: AppColors.purple },
|
|
1457
|
+
]}>
|
|
1458
|
+
{filter}
|
|
1459
|
+
</Text>
|
|
1460
|
+
</LinearGradient>) : (<View style={styles.statusFilterChip}>
|
|
1461
|
+
<Text style={styles.statusFilterText}>
|
|
1462
|
+
{filter}
|
|
1463
|
+
</Text>
|
|
1464
|
+
</View>)}
|
|
1465
|
+
</TouchableScale>);
|
|
1466
|
+
})}
|
|
1467
|
+
</ScrollView>
|
|
1468
|
+
|
|
1469
|
+
<Text style={[styles.filtersHeading, { marginTop: 16 }]}>
|
|
1470
|
+
METHOD
|
|
1471
|
+
</Text>
|
|
1472
|
+
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.statusRowContent}>
|
|
1473
|
+
{availableMethods.map(filter => {
|
|
1474
|
+
const isAll = filter === 'ALL';
|
|
1475
|
+
const active = isAll
|
|
1476
|
+
? methodFilters.size === 0
|
|
1477
|
+
: methodFilters.has(filter);
|
|
1478
|
+
return (<TouchableScale key={filter} style={styles.statusFilterWrap} onPress={() => {
|
|
1479
|
+
if (isAll) {
|
|
1480
|
+
setMethodFilters(new Set());
|
|
1481
|
+
}
|
|
1482
|
+
else {
|
|
1483
|
+
setMethodFilters(prev => {
|
|
1484
|
+
const next = new Set(prev);
|
|
1485
|
+
next.has(filter)
|
|
1486
|
+
? next.delete(filter)
|
|
1487
|
+
: next.add(filter);
|
|
1488
|
+
return next;
|
|
1489
|
+
});
|
|
1490
|
+
}
|
|
1491
|
+
}} hitSlop={10}>
|
|
1492
|
+
{active ? (<LinearGradient colors={[
|
|
1493
|
+
AppColors.purpleShade50,
|
|
1494
|
+
'#EAE5FF',
|
|
1495
|
+
]} style={[
|
|
1496
|
+
styles.statusFilterChip,
|
|
1497
|
+
styles.statusFilterActive,
|
|
1498
|
+
]}>
|
|
1499
|
+
<Text style={[
|
|
1500
|
+
styles.statusFilterText,
|
|
1501
|
+
{ color: AppColors.purple },
|
|
1502
|
+
]}>
|
|
1503
|
+
{filter}
|
|
1504
|
+
</Text>
|
|
1505
|
+
</LinearGradient>) : (<View style={styles.statusFilterChip}>
|
|
1506
|
+
<Text style={styles.statusFilterText}>
|
|
1507
|
+
{filter}
|
|
1508
|
+
</Text>
|
|
1509
|
+
</View>)}
|
|
1510
|
+
</TouchableScale>);
|
|
1511
|
+
})}
|
|
1512
|
+
</ScrollView>
|
|
1513
|
+
</View>
|
|
1514
|
+
</Animated.View>
|
|
1515
|
+
|
|
1516
|
+
{(search ||
|
|
1517
|
+
statusFilters.size > 0 ||
|
|
1518
|
+
methodFilters.size > 0) && (<Text style={styles.resultCount}>
|
|
1519
|
+
{filteredLogs.length === logs.length
|
|
1520
|
+
? `${logs.length} requests`
|
|
1521
|
+
: `${filteredLogs.length} of ${logs.length} filtered requests`}
|
|
1522
|
+
</Text>)}
|
|
1523
|
+
</View>} ListEmptyComponent={<EmptyState isSearch={search.length > 0 || statusFilters.size > 0}/>} contentContainerStyle={[
|
|
1524
|
+
styles.listContent,
|
|
1525
|
+
filteredLogs.length === 0 && { flexGrow: 1 },
|
|
1526
|
+
]} keyboardShouldPersistTaps="handled"/>) : (<FlatList data={groupedData} keyExtractor={item => item?.id?.toString()} renderItem={renderItem} initialNumToRender={10} maxToRenderPerBatch={10} windowSize={5} removeClippedSubviews={true} ListHeaderComponent={<View style={{ marginTop: 8 }}>
|
|
1527
|
+
{logs.length > 0 && (<View style={styles.dashboardCard}>
|
|
1528
|
+
<View style={styles.dashboardStatsRow}>
|
|
1529
|
+
<View style={styles.statBox}>
|
|
1530
|
+
<Text style={styles.statValue}>
|
|
1531
|
+
{stats.filtered < stats.total
|
|
1532
|
+
? stats.filtered
|
|
1533
|
+
: stats.total}
|
|
1534
|
+
</Text>
|
|
1535
|
+
<Text style={styles.statLabel}>
|
|
1536
|
+
{stats.filtered < stats.total
|
|
1537
|
+
? `of ${stats.total} Req`
|
|
1538
|
+
: 'Requests'}
|
|
1539
|
+
</Text>
|
|
1540
|
+
<View style={styles.miniGraphWrap}>
|
|
1541
|
+
<MiniBarChart data={stats.reqTrend} color={AppColors.purple}/>
|
|
1542
|
+
</View>
|
|
1543
|
+
</View>
|
|
1544
|
+
<View style={styles.dashboardStatDivider}/>
|
|
1545
|
+
<View style={styles.statBox}>
|
|
1546
|
+
<Text style={[
|
|
1547
|
+
styles.statValue,
|
|
1548
|
+
stats.errors > 0 && {
|
|
1549
|
+
color: AppColors.errorColor,
|
|
1550
|
+
},
|
|
1551
|
+
]}>
|
|
1552
|
+
{stats.errors}
|
|
1553
|
+
</Text>
|
|
1554
|
+
<Text style={styles.statLabel}>Errors</Text>
|
|
1555
|
+
<View style={styles.miniGraphWrap}>
|
|
1556
|
+
<MiniBarChart data={stats.errorTrend} color={AppColors.errorColor} maxVal={1}/>
|
|
1557
|
+
</View>
|
|
1558
|
+
</View>
|
|
1559
|
+
<View style={styles.dashboardStatDivider}/>
|
|
1560
|
+
<View style={styles.statBox}>
|
|
1561
|
+
<Text style={[
|
|
1562
|
+
styles.statValue,
|
|
1563
|
+
stats.avgDuration != null
|
|
1564
|
+
? {
|
|
1565
|
+
color: getDurationColor(stats.avgDuration),
|
|
1566
|
+
}
|
|
1567
|
+
: null,
|
|
1568
|
+
]}>
|
|
1569
|
+
{stats.avgDuration != null
|
|
1570
|
+
? `${stats.avgDuration}ms`
|
|
1571
|
+
: '—'}
|
|
1572
|
+
</Text>
|
|
1573
|
+
<Text style={styles.statLabel}>Avg Time</Text>
|
|
1574
|
+
<View style={styles.miniGraphWrap}>
|
|
1575
|
+
<MiniLineChart data={stats.durationTrend} color={AppColors.darkOrange}/>
|
|
1576
|
+
</View>
|
|
1577
|
+
</View>
|
|
1578
|
+
<View style={styles.dashboardStatDivider}/>
|
|
1579
|
+
<View style={styles.statBox}>
|
|
1580
|
+
<Text style={[
|
|
1581
|
+
styles.statValue,
|
|
1582
|
+
{ color: AppColors.skyBlue },
|
|
1583
|
+
]}>
|
|
1584
|
+
{stats.size}
|
|
1585
|
+
</Text>
|
|
1586
|
+
<Text style={styles.statLabel}>Payload</Text>
|
|
1587
|
+
<View style={styles.miniGraphWrap}>
|
|
1588
|
+
<MiniBarChart data={stats.sizeTrend} color={AppColors.skyBlue}/>
|
|
1589
|
+
</View>
|
|
1590
|
+
</View>
|
|
1591
|
+
</View>
|
|
1592
|
+
</View>)}
|
|
1593
|
+
|
|
1594
|
+
<View style={styles.toolbarRow}>
|
|
1595
|
+
<View style={styles.searchContainer}>
|
|
1596
|
+
<SearchIcon color={AppColors.grayTextWeak} size={16}/>
|
|
1597
|
+
<TextInput placeholder="Search endpoints..." placeholderTextColor={AppColors.grayTextWeak} value={search} onChangeText={setSearch} style={styles.searchInput} autoCorrect={false} autoCapitalize="none"/>
|
|
1598
|
+
{search.length > 0 && (<Pressable onPress={() => setSearch('')} hitSlop={10} style={styles.clearBtn}>
|
|
1599
|
+
<ClearIcon color={AppColors.grayTextWeak} size={14}/>
|
|
1600
|
+
</Pressable>)}
|
|
1601
|
+
</View>
|
|
1602
|
+
|
|
1603
|
+
<View style={styles.toolbarRight}>
|
|
1604
|
+
<TouchableScale style={styles.toolbarBtn} onPress={handleDelete} hitSlop={10}>
|
|
1605
|
+
<TrashIcon color={AppColors.grayTextStrong} size={18}/>
|
|
1606
|
+
{selectedLogs.size > 0 && (<View style={styles.trashBadge}>
|
|
1607
|
+
<Text style={styles.trashBadgeText}>
|
|
1608
|
+
{selectedLogs.size}
|
|
1609
|
+
</Text>
|
|
1610
|
+
</View>)}
|
|
1611
|
+
</TouchableScale>
|
|
1612
|
+
|
|
1613
|
+
<TouchableScale style={[
|
|
1614
|
+
styles.toolbarBtn,
|
|
1615
|
+
groupByScreen && styles.toolbarBtnActive,
|
|
1616
|
+
]} onPress={() => setGroupByScreen(prev => !prev)} hitSlop={10}>
|
|
1617
|
+
<MapPinIcon color={groupByScreen
|
|
1618
|
+
? AppColors.purple
|
|
1619
|
+
: AppColors.grayTextStrong} size={18}/>
|
|
1620
|
+
</TouchableScale>
|
|
1621
|
+
|
|
1622
|
+
<TouchableScale style={styles.toolbarBtn} onPress={() => setSortOrder(o => o === 'newest' ? 'oldest' : 'newest')} hitSlop={10}>
|
|
1623
|
+
<SortArrowIcon direction={sortOrder === 'newest' ? 'down' : 'up'} color={AppColors.grayTextStrong} size={18}/>
|
|
1624
|
+
</TouchableScale>
|
|
1625
|
+
|
|
1626
|
+
<TouchableScale style={[
|
|
1627
|
+
styles.toolbarBtn,
|
|
1628
|
+
filtersAccordion.isOpen &&
|
|
1629
|
+
styles.toolbarBtnActive,
|
|
1630
|
+
]} onPress={filtersAccordion.toggleOpen} hitSlop={10}>
|
|
1631
|
+
<FilterIcon color={filtersAccordion.isOpen
|
|
1632
|
+
? AppColors.purple
|
|
1633
|
+
: AppColors.grayTextStrong} size={18}/>
|
|
1634
|
+
</TouchableScale>
|
|
1635
|
+
</View>
|
|
1636
|
+
</View>
|
|
1637
|
+
|
|
1638
|
+
<Animated.View style={[
|
|
1639
|
+
filtersAccordion.bodyStyle,
|
|
1640
|
+
{ overflow: 'hidden' },
|
|
1641
|
+
]}>
|
|
1642
|
+
<View style={styles.filtersContainer}>
|
|
1643
|
+
<Text style={styles.filtersHeading}>STATUS</Text>
|
|
1644
|
+
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.statusRowContent}>
|
|
1645
|
+
{STATUS_FILTERS.map(filter => {
|
|
1646
|
+
const isAll = filter === 'ALL';
|
|
1647
|
+
const active = isAll
|
|
1648
|
+
? statusFilters.size === 0
|
|
1649
|
+
: statusFilters.has(filter);
|
|
1650
|
+
return (<TouchableScale key={filter} style={styles.statusFilterWrap} onPress={() => {
|
|
1651
|
+
if (isAll) {
|
|
1652
|
+
setStatusFilters(new Set());
|
|
1653
|
+
}
|
|
1654
|
+
else {
|
|
1655
|
+
setStatusFilters(prev => {
|
|
1656
|
+
const next = new Set(prev);
|
|
1657
|
+
next.has(filter)
|
|
1658
|
+
? next.delete(filter)
|
|
1659
|
+
: next.add(filter);
|
|
1660
|
+
return next;
|
|
1661
|
+
});
|
|
1662
|
+
}
|
|
1663
|
+
}} hitSlop={10}>
|
|
1664
|
+
{active ? (<LinearGradient colors={[
|
|
1665
|
+
AppColors.purpleShade50,
|
|
1666
|
+
'#EAE5FF',
|
|
1667
|
+
]} style={[
|
|
1668
|
+
styles.statusFilterChip,
|
|
1669
|
+
styles.statusFilterActive,
|
|
1670
|
+
]}>
|
|
1671
|
+
<Text style={[
|
|
1672
|
+
styles.statusFilterText,
|
|
1673
|
+
{ color: AppColors.purple },
|
|
1674
|
+
]}>
|
|
1675
|
+
{filter}
|
|
1676
|
+
</Text>
|
|
1677
|
+
</LinearGradient>) : (<View style={styles.statusFilterChip}>
|
|
1678
|
+
<Text style={styles.statusFilterText}>
|
|
1679
|
+
{filter}
|
|
1680
|
+
</Text>
|
|
1681
|
+
</View>)}
|
|
1682
|
+
</TouchableScale>);
|
|
1683
|
+
})}
|
|
1684
|
+
</ScrollView>
|
|
1685
|
+
|
|
1686
|
+
<Text style={[styles.filtersHeading, { marginTop: 16 }]}>
|
|
1687
|
+
METHOD
|
|
1688
|
+
</Text>
|
|
1689
|
+
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.statusRowContent}>
|
|
1690
|
+
{availableMethods.map(filter => {
|
|
1691
|
+
const isAll = filter === 'ALL';
|
|
1692
|
+
const active = isAll
|
|
1693
|
+
? methodFilters.size === 0
|
|
1694
|
+
: methodFilters.has(filter);
|
|
1695
|
+
return (<TouchableScale key={filter} style={styles.statusFilterWrap} onPress={() => {
|
|
1696
|
+
if (isAll) {
|
|
1697
|
+
setMethodFilters(new Set());
|
|
1698
|
+
}
|
|
1699
|
+
else {
|
|
1700
|
+
setMethodFilters(prev => {
|
|
1701
|
+
const next = new Set(prev);
|
|
1702
|
+
next.has(filter)
|
|
1703
|
+
? next.delete(filter)
|
|
1704
|
+
: next.add(filter);
|
|
1705
|
+
return next;
|
|
1706
|
+
});
|
|
1707
|
+
}
|
|
1708
|
+
}} hitSlop={10}>
|
|
1709
|
+
{active ? (<LinearGradient colors={[
|
|
1710
|
+
AppColors.purpleShade50,
|
|
1711
|
+
'#EAE5FF',
|
|
1712
|
+
]} style={[
|
|
1713
|
+
styles.statusFilterChip,
|
|
1714
|
+
styles.statusFilterActive,
|
|
1715
|
+
]}>
|
|
1716
|
+
<Text style={[
|
|
1717
|
+
styles.statusFilterText,
|
|
1718
|
+
{ color: AppColors.purple },
|
|
1719
|
+
]}>
|
|
1720
|
+
{filter}
|
|
1721
|
+
</Text>
|
|
1722
|
+
</LinearGradient>) : (<View style={styles.statusFilterChip}>
|
|
1723
|
+
<Text style={styles.statusFilterText}>
|
|
1724
|
+
{filter}
|
|
1725
|
+
</Text>
|
|
1726
|
+
</View>)}
|
|
1727
|
+
</TouchableScale>);
|
|
1728
|
+
})}
|
|
1729
|
+
</ScrollView>
|
|
1730
|
+
</View>
|
|
1731
|
+
</Animated.View>
|
|
1732
|
+
|
|
1733
|
+
{(search ||
|
|
1734
|
+
statusFilters.size > 0 ||
|
|
1735
|
+
methodFilters.size > 0) && (<Text style={styles.resultCount}>
|
|
1736
|
+
{filteredLogs.length === logs.length
|
|
1737
|
+
? `${logs.length} requests`
|
|
1738
|
+
: `${filteredLogs.length} of ${logs.length} filtered requests`}
|
|
1739
|
+
</Text>)}
|
|
1740
|
+
</View>} ListEmptyComponent={<EmptyState isSearch={search.length > 0 || statusFilters.size > 0}/>} contentContainerStyle={[
|
|
1741
|
+
styles.listContent,
|
|
1742
|
+
filteredLogs.length === 0 && { flexGrow: 1 },
|
|
1743
|
+
]} keyboardShouldPersistTaps="handled"/>)) : activeTab === 'logs' ? (<View style={{ flex: 1 }}>
|
|
1744
|
+
<View style={{
|
|
1745
|
+
backgroundColor: '#FFFFFF',
|
|
1746
|
+
borderBottomWidth: 1,
|
|
1747
|
+
borderBottomColor: AppColors.dividerColor,
|
|
1748
|
+
paddingBottom: 6,
|
|
1749
|
+
}}>
|
|
1750
|
+
<View style={[
|
|
1751
|
+
styles.toolbarRow,
|
|
1752
|
+
{ marginTop: 12, marginBottom: 8 },
|
|
1753
|
+
]}>
|
|
1754
|
+
<View style={styles.searchContainer}>
|
|
1755
|
+
<SearchIcon color={AppColors.grayTextWeak} size={16}/>
|
|
1756
|
+
<TextInput placeholder="Search logs..." placeholderTextColor={AppColors.grayTextWeak} value={logSearch} onChangeText={setLogSearch} style={styles.searchInput} autoCorrect={false} autoCapitalize="none"/>
|
|
1757
|
+
{logSearch.length > 0 && (<Pressable onPress={() => setLogSearch('')} hitSlop={10} style={styles.clearBtn}>
|
|
1758
|
+
<ClearIcon color={AppColors.grayTextWeak} size={14}/>
|
|
1759
|
+
</Pressable>)}
|
|
1760
|
+
</View>
|
|
1761
|
+
|
|
1762
|
+
<View style={styles.toolbarRight}>
|
|
1763
|
+
<TouchableScale style={styles.toolbarBtn} onPress={handleDelete} hitSlop={10}>
|
|
1764
|
+
<TrashIcon color={AppColors.grayTextStrong} size={18}/>
|
|
1765
|
+
</TouchableScale>
|
|
1766
|
+
</View>
|
|
1767
|
+
</View>
|
|
1768
|
+
|
|
1769
|
+
<ScrollView horizontal showsHorizontalScrollIndicator={false} style={{ marginVertical: 4, maxHeight: 46 }} contentContainerStyle={{
|
|
1770
|
+
paddingHorizontal: 16,
|
|
1771
|
+
paddingBottom: 4,
|
|
1772
|
+
flexDirection: 'row',
|
|
1773
|
+
alignItems: 'center',
|
|
1774
|
+
gap: 8,
|
|
1775
|
+
}}>
|
|
1776
|
+
{(() => {
|
|
1777
|
+
const active = logFilters.has('all');
|
|
1778
|
+
return (<TouchableScale onPress={() => {
|
|
1779
|
+
setLogFilters(new Set(['all']));
|
|
1780
|
+
}}>
|
|
1781
|
+
<View style={[
|
|
1782
|
+
styles.statusFilterChip,
|
|
1783
|
+
active && {
|
|
1784
|
+
borderColor: AppColors.purpleShade700,
|
|
1785
|
+
backgroundColor: '#F4EBFF',
|
|
1786
|
+
},
|
|
1787
|
+
]}>
|
|
1788
|
+
<Text numberOfLines={1} style={[
|
|
1789
|
+
styles.statusFilterText,
|
|
1790
|
+
active && {
|
|
1791
|
+
color: AppColors.purpleShade700,
|
|
1792
|
+
fontFamily: AppFonts.interBold,
|
|
1793
|
+
},
|
|
1794
|
+
]}>
|
|
1795
|
+
All ({logCounts.all})
|
|
1796
|
+
</Text>
|
|
1797
|
+
</View>
|
|
1798
|
+
</TouchableScale>);
|
|
1799
|
+
})()}
|
|
1800
|
+
|
|
1801
|
+
{(() => {
|
|
1802
|
+
const active = logFilters.has('user-log');
|
|
1803
|
+
return (<TouchableScale onPress={() => {
|
|
1804
|
+
setLogFilters(prev => {
|
|
1805
|
+
const next = new Set(prev);
|
|
1806
|
+
next.delete('all');
|
|
1807
|
+
next.has('user-log')
|
|
1808
|
+
? next.delete('user-log')
|
|
1809
|
+
: next.add('user-log');
|
|
1810
|
+
if (next.size === 0)
|
|
1811
|
+
next.add('all');
|
|
1812
|
+
return next;
|
|
1813
|
+
});
|
|
1814
|
+
}}>
|
|
1815
|
+
<View style={[
|
|
1816
|
+
styles.statusFilterChip,
|
|
1817
|
+
active && {
|
|
1818
|
+
borderColor: '#64748B',
|
|
1819
|
+
backgroundColor: '#F1F5F9',
|
|
1820
|
+
},
|
|
1821
|
+
]}>
|
|
1822
|
+
<Text numberOfLines={1} style={[
|
|
1823
|
+
styles.statusFilterText,
|
|
1824
|
+
active && {
|
|
1825
|
+
color: '#334155',
|
|
1826
|
+
fontFamily: AppFonts.interBold,
|
|
1827
|
+
},
|
|
1828
|
+
]}>
|
|
1829
|
+
User Log ({logCounts['user-log']})
|
|
1830
|
+
</Text>
|
|
1831
|
+
</View>
|
|
1832
|
+
</TouchableScale>);
|
|
1833
|
+
})()}
|
|
1834
|
+
|
|
1835
|
+
{(() => {
|
|
1836
|
+
const active = logFilters.has('info');
|
|
1837
|
+
return (<TouchableScale onPress={() => {
|
|
1838
|
+
setLogFilters(prev => {
|
|
1839
|
+
const next = new Set(prev);
|
|
1840
|
+
next.delete('all');
|
|
1841
|
+
next.has('info')
|
|
1842
|
+
? next.delete('info')
|
|
1843
|
+
: next.add('info');
|
|
1844
|
+
if (next.size === 0)
|
|
1845
|
+
next.add('all');
|
|
1846
|
+
return next;
|
|
1847
|
+
});
|
|
1848
|
+
}}>
|
|
1849
|
+
<View style={[
|
|
1850
|
+
styles.statusFilterChip,
|
|
1851
|
+
active && {
|
|
1852
|
+
borderColor: AppColors.purple,
|
|
1853
|
+
backgroundColor: AppColors.purpleShade50,
|
|
1854
|
+
},
|
|
1855
|
+
]}>
|
|
1856
|
+
<Text numberOfLines={1} style={[
|
|
1857
|
+
styles.statusFilterText,
|
|
1858
|
+
active && {
|
|
1859
|
+
color: AppColors.purple,
|
|
1860
|
+
fontFamily: AppFonts.interBold,
|
|
1861
|
+
},
|
|
1862
|
+
]}>
|
|
1863
|
+
Info ({logCounts.info})
|
|
1864
|
+
</Text>
|
|
1865
|
+
</View>
|
|
1866
|
+
</TouchableScale>);
|
|
1867
|
+
})()}
|
|
1868
|
+
|
|
1869
|
+
{(() => {
|
|
1870
|
+
const active = logFilters.has('warn');
|
|
1871
|
+
return (<TouchableScale onPress={() => {
|
|
1872
|
+
setLogFilters(prev => {
|
|
1873
|
+
const next = new Set(prev);
|
|
1874
|
+
next.delete('all');
|
|
1875
|
+
next.has('warn')
|
|
1876
|
+
? next.delete('warn')
|
|
1877
|
+
: next.add('warn');
|
|
1878
|
+
if (next.size === 0)
|
|
1879
|
+
next.add('all');
|
|
1880
|
+
return next;
|
|
1881
|
+
});
|
|
1882
|
+
}}>
|
|
1883
|
+
<View style={[
|
|
1884
|
+
styles.statusFilterChip,
|
|
1885
|
+
active && {
|
|
1886
|
+
borderColor: AppColors.lightOrange,
|
|
1887
|
+
backgroundColor: '#FFFDF6',
|
|
1888
|
+
},
|
|
1889
|
+
]}>
|
|
1890
|
+
<Text numberOfLines={1} style={[
|
|
1891
|
+
styles.statusFilterText,
|
|
1892
|
+
active && {
|
|
1893
|
+
color: AppColors.darkOrange ||
|
|
1894
|
+
AppColors.lightOrange,
|
|
1895
|
+
fontFamily: AppFonts.interBold,
|
|
1896
|
+
},
|
|
1897
|
+
]}>
|
|
1898
|
+
Warning ({logCounts.warn})
|
|
1899
|
+
</Text>
|
|
1900
|
+
</View>
|
|
1901
|
+
</TouchableScale>);
|
|
1902
|
+
})()}
|
|
1903
|
+
|
|
1904
|
+
{(() => {
|
|
1905
|
+
const active = logFilters.has('error');
|
|
1906
|
+
return (<TouchableScale onPress={() => {
|
|
1907
|
+
setLogFilters(prev => {
|
|
1908
|
+
const next = new Set(prev);
|
|
1909
|
+
next.delete('all');
|
|
1910
|
+
next.has('error')
|
|
1911
|
+
? next.delete('error')
|
|
1912
|
+
: next.add('error');
|
|
1913
|
+
if (next.size === 0)
|
|
1914
|
+
next.add('all');
|
|
1915
|
+
return next;
|
|
1916
|
+
});
|
|
1917
|
+
}}>
|
|
1918
|
+
<View style={[
|
|
1919
|
+
styles.statusFilterChip,
|
|
1920
|
+
active && {
|
|
1921
|
+
borderColor: AppColors.errorColor,
|
|
1922
|
+
backgroundColor: '#FFF5F6',
|
|
1923
|
+
},
|
|
1924
|
+
]}>
|
|
1925
|
+
<Text numberOfLines={1} style={[
|
|
1926
|
+
styles.statusFilterText,
|
|
1927
|
+
active && {
|
|
1928
|
+
color: AppColors.errorColor,
|
|
1929
|
+
fontFamily: AppFonts.interBold,
|
|
1930
|
+
},
|
|
1931
|
+
]}>
|
|
1932
|
+
Error ({logCounts.error})
|
|
1933
|
+
</Text>
|
|
1934
|
+
</View>
|
|
1935
|
+
</TouchableScale>);
|
|
1936
|
+
})()}
|
|
1937
|
+
|
|
1938
|
+
{(() => {
|
|
1939
|
+
const active = logFilters.has('analytics');
|
|
1940
|
+
return (<TouchableScale onPress={() => {
|
|
1941
|
+
setLogFilters(prev => {
|
|
1942
|
+
const next = new Set(prev);
|
|
1943
|
+
next.delete('all');
|
|
1944
|
+
next.has('analytics')
|
|
1945
|
+
? next.delete('analytics')
|
|
1946
|
+
: next.add('analytics');
|
|
1947
|
+
if (next.size === 0)
|
|
1948
|
+
next.add('all');
|
|
1949
|
+
return next;
|
|
1950
|
+
});
|
|
1951
|
+
}}>
|
|
1952
|
+
<View style={[
|
|
1953
|
+
styles.statusFilterChip,
|
|
1954
|
+
active && {
|
|
1955
|
+
borderColor: AppColors.skyBlue,
|
|
1956
|
+
backgroundColor: `${AppColors.skyBlue}15`,
|
|
1957
|
+
},
|
|
1958
|
+
]}>
|
|
1959
|
+
<Text numberOfLines={1} style={[
|
|
1960
|
+
styles.statusFilterText,
|
|
1961
|
+
active && {
|
|
1962
|
+
color: AppColors.skyBlue,
|
|
1963
|
+
fontFamily: AppFonts.interBold,
|
|
1964
|
+
},
|
|
1965
|
+
]}>
|
|
1966
|
+
Analytics ({logCounts.analytics})
|
|
1967
|
+
</Text>
|
|
1968
|
+
</View>
|
|
1969
|
+
</TouchableScale>);
|
|
1970
|
+
})()}
|
|
1971
|
+
</ScrollView>
|
|
1972
|
+
</View>
|
|
1973
|
+
|
|
1974
|
+
<FlatList data={filteredConsoleLogs} keyExtractor={item => item.id.toString()} ListHeaderComponent={(() => {
|
|
1975
|
+
const total = visibleConsoleLogs.length;
|
|
1976
|
+
const filtered = filteredConsoleLogs.length;
|
|
1977
|
+
const isAllSelected = logFilters.has('all') ||
|
|
1978
|
+
!Array.from(logFilters).some(f => f !== 'all');
|
|
1979
|
+
if (isAllSelected) {
|
|
1980
|
+
return (<Text style={[
|
|
1981
|
+
styles.resultCount,
|
|
1982
|
+
{ marginBottom: 4, marginTop: 12 },
|
|
1983
|
+
]}>
|
|
1984
|
+
Showing ({filtered}/{total}) logs showing
|
|
1985
|
+
</Text>);
|
|
1986
|
+
}
|
|
1987
|
+
else {
|
|
1988
|
+
const activeFilterNames = Array.from(logFilters)
|
|
1989
|
+
.filter(f => f !== 'all')
|
|
1990
|
+
.map(f => {
|
|
1991
|
+
if (f === 'user-log')
|
|
1992
|
+
return 'User Log';
|
|
1993
|
+
if (f === 'analytics')
|
|
1994
|
+
return 'Analytics';
|
|
1995
|
+
return f.charAt(0).toUpperCase() + f.slice(1);
|
|
1996
|
+
});
|
|
1997
|
+
return (<Text style={[
|
|
1998
|
+
styles.resultCount,
|
|
1999
|
+
{ marginBottom: 4, marginTop: 12 },
|
|
2000
|
+
]}>
|
|
2001
|
+
Filtering with {activeFilterNames.join(', ')} (
|
|
2002
|
+
{filtered}/{total}) logs is showing
|
|
2003
|
+
</Text>);
|
|
2004
|
+
}
|
|
2005
|
+
})()} renderItem={({ item }) => (<ConsoleLogCard item={item} searchStr={logSearch}/>)} initialNumToRender={15} maxToRenderPerBatch={15} windowSize={7} removeClippedSubviews={true} ListEmptyComponent={<EmptyState isSearch={logSearch.length > 0 || logFilters.size > 0}/>} contentContainerStyle={[
|
|
2006
|
+
styles.listContent,
|
|
2007
|
+
filteredConsoleLogs.length === 0 && { flexGrow: 1 },
|
|
2008
|
+
]} keyboardShouldPersistTaps="handled"/>
|
|
2009
|
+
</View>) : activeTab === 'webview' ? (<View style={{ flex: 1 }}>
|
|
2010
|
+
<View style={{
|
|
2011
|
+
backgroundColor: '#FFFFFF',
|
|
2012
|
+
borderBottomWidth: 1,
|
|
2013
|
+
borderBottomColor: AppColors.dividerColor,
|
|
2014
|
+
paddingBottom: 6,
|
|
2015
|
+
}}>
|
|
2016
|
+
{/* ─── WebView Sub-Tabs ─────────────────────────────────────── */}
|
|
2017
|
+
<View style={{
|
|
2018
|
+
marginHorizontal: 16,
|
|
2019
|
+
marginTop: webViewSubTab === 'navigation' ? 12 : 4,
|
|
2020
|
+
marginBottom: 8,
|
|
2021
|
+
backgroundColor: AppColors.grayBackground,
|
|
2022
|
+
borderRadius: 8,
|
|
2023
|
+
padding: 4,
|
|
2024
|
+
flexDirection: 'row',
|
|
2025
|
+
borderWidth: 1,
|
|
2026
|
+
borderColor: AppColors.grayBorderSecondary,
|
|
2027
|
+
}}>
|
|
2028
|
+
<Pressable style={[
|
|
2029
|
+
{
|
|
2030
|
+
flex: 1,
|
|
2031
|
+
paddingVertical: 8,
|
|
2032
|
+
borderRadius: 6,
|
|
2033
|
+
alignItems: 'center',
|
|
2034
|
+
},
|
|
2035
|
+
webViewSubTab === 'html' && {
|
|
2036
|
+
backgroundColor: AppColors.primaryLight,
|
|
2037
|
+
shadowColor: '#000',
|
|
2038
|
+
shadowOpacity: 0.1,
|
|
2039
|
+
shadowRadius: 3,
|
|
2040
|
+
shadowOffset: { width: 0, height: 1 },
|
|
2041
|
+
elevation: 2,
|
|
2042
|
+
},
|
|
2043
|
+
]} onPress={() => setWebViewSubTab('html')}>
|
|
2044
|
+
<Text style={[
|
|
2045
|
+
{
|
|
2046
|
+
fontFamily: AppFonts.interMedium,
|
|
2047
|
+
fontSize: 12,
|
|
2048
|
+
color: AppColors.grayTextStrong,
|
|
2049
|
+
},
|
|
2050
|
+
webViewSubTab === 'html' && {
|
|
2051
|
+
fontFamily: AppFonts.interBold,
|
|
2052
|
+
color: '#475569',
|
|
2053
|
+
},
|
|
2054
|
+
]}>
|
|
2055
|
+
HTML Source
|
|
2056
|
+
</Text>
|
|
2057
|
+
</Pressable>
|
|
2058
|
+
<Pressable style={[
|
|
2059
|
+
{
|
|
2060
|
+
flex: 1,
|
|
2061
|
+
paddingVertical: 8,
|
|
2062
|
+
borderRadius: 6,
|
|
2063
|
+
alignItems: 'center',
|
|
2064
|
+
},
|
|
2065
|
+
webViewSubTab === 'navigation' && {
|
|
2066
|
+
backgroundColor: AppColors.primaryLight,
|
|
2067
|
+
shadowColor: '#000',
|
|
2068
|
+
shadowOpacity: 0.1,
|
|
2069
|
+
shadowRadius: 3,
|
|
2070
|
+
shadowOffset: { width: 0, height: 1 },
|
|
2071
|
+
elevation: 2,
|
|
2072
|
+
},
|
|
2073
|
+
]} onPress={() => setWebViewSubTab('navigation')}>
|
|
2074
|
+
<Text style={[
|
|
2075
|
+
{
|
|
2076
|
+
fontFamily: AppFonts.interMedium,
|
|
2077
|
+
fontSize: 12,
|
|
2078
|
+
color: AppColors.grayTextStrong,
|
|
2079
|
+
},
|
|
2080
|
+
webViewSubTab === 'navigation' && {
|
|
2081
|
+
fontFamily: AppFonts.interBold,
|
|
2082
|
+
color: '#475569',
|
|
2083
|
+
},
|
|
2084
|
+
]}>
|
|
2085
|
+
Nav History ({webViewNavHistory.length})
|
|
2086
|
+
</Text>
|
|
2087
|
+
</Pressable>
|
|
2088
|
+
</View>
|
|
2089
|
+
</View>
|
|
2090
|
+
|
|
2091
|
+
{/* ─── Current Page Address Bar (Always visible at the top) ─── */}
|
|
2092
|
+
{(() => {
|
|
2093
|
+
const currentUrl = webViewNavHistory[0]?.url;
|
|
2094
|
+
if (!currentUrl)
|
|
2095
|
+
return null;
|
|
2096
|
+
return (<View style={{
|
|
2097
|
+
paddingHorizontal: 16,
|
|
2098
|
+
paddingTop: 10,
|
|
2099
|
+
paddingBottom: 10,
|
|
2100
|
+
backgroundColor: '#FFFFFF',
|
|
2101
|
+
borderBottomWidth: 1,
|
|
2102
|
+
borderBottomColor: AppColors.dividerColor,
|
|
2103
|
+
}}>
|
|
2104
|
+
<Text style={{
|
|
2105
|
+
fontFamily: AppFonts.interBold,
|
|
2106
|
+
fontSize: 10,
|
|
2107
|
+
color: '#64748B',
|
|
2108
|
+
textTransform: 'uppercase',
|
|
2109
|
+
letterSpacing: 0.5,
|
|
2110
|
+
marginBottom: 6,
|
|
2111
|
+
}}>
|
|
2112
|
+
Currently debugging for URL
|
|
2113
|
+
</Text>
|
|
2114
|
+
<View style={{
|
|
2115
|
+
flexDirection: 'row',
|
|
2116
|
+
alignItems: 'center',
|
|
2117
|
+
backgroundColor: '#F1F5F9',
|
|
2118
|
+
borderRadius: 20,
|
|
2119
|
+
borderWidth: 1,
|
|
2120
|
+
borderColor: '#E2E8F0',
|
|
2121
|
+
paddingHorizontal: 12,
|
|
2122
|
+
paddingVertical: 6,
|
|
2123
|
+
gap: 8,
|
|
2124
|
+
}}>
|
|
2125
|
+
{/* Left: Clickable Globe Icon to open browser */}
|
|
2126
|
+
<TouchableScale onPress={() => Linking.openURL(currentUrl)} hitSlop={8} style={{
|
|
2127
|
+
width: 24,
|
|
2128
|
+
height: 24,
|
|
2129
|
+
borderRadius: 12,
|
|
2130
|
+
backgroundColor: '#E2E8F0',
|
|
2131
|
+
alignItems: 'center',
|
|
2132
|
+
justifyContent: 'center',
|
|
2133
|
+
}} children={<GlobeIcon size={12} color="#475569"/>}/>
|
|
2134
|
+
|
|
2135
|
+
{/* Middle: URL text (Address style) */}
|
|
2136
|
+
<View style={{ flex: 1 }}>
|
|
2137
|
+
<HighlightText text={currentUrl} search={webViewSearch} numberOfLines={1} ellipsizeMode="tail" style={{
|
|
2138
|
+
fontFamily: AppFonts.interMedium,
|
|
2139
|
+
fontSize: 11,
|
|
2140
|
+
color: '#475569',
|
|
2141
|
+
}} highlightStyle={styles.highlight} detectLinks={false}/>
|
|
2142
|
+
</View>
|
|
2143
|
+
|
|
2144
|
+
{/* Right: Copy Button */}
|
|
2145
|
+
<CopyButton value={currentUrl} label="URL"/>
|
|
2146
|
+
</View>
|
|
2147
|
+
</View>);
|
|
2148
|
+
})()}
|
|
2149
|
+
|
|
2150
|
+
{webViewSubTab === 'html' ? (<View style={{ flex: 1 }}>
|
|
2151
|
+
{webViewHtml || webViewCss || webViewJs ? (<View style={{ flex: 1 }}>
|
|
2152
|
+
{/* Inner sub-tabs inside HTML source view */}
|
|
2153
|
+
<View style={{
|
|
2154
|
+
flexDirection: 'row',
|
|
2155
|
+
borderBottomWidth: 1,
|
|
2156
|
+
borderBottomColor: '#E2E8F0',
|
|
2157
|
+
backgroundColor: '#FFFFFF',
|
|
2158
|
+
paddingHorizontal: 16,
|
|
2159
|
+
}}>
|
|
2160
|
+
{['html', 'css', 'javascript'].map(tab => {
|
|
2161
|
+
const active = htmlSubTab === tab;
|
|
2162
|
+
const label = tab === 'html'
|
|
2163
|
+
? 'HTML'
|
|
2164
|
+
: tab === 'css'
|
|
2165
|
+
? 'CSS'
|
|
2166
|
+
: 'Javascript';
|
|
2167
|
+
return (<Pressable key={tab} onPress={() => setHtmlSubTab(tab)} style={{
|
|
2168
|
+
paddingVertical: 10,
|
|
2169
|
+
marginRight: 16,
|
|
2170
|
+
borderBottomWidth: 2,
|
|
2171
|
+
borderBottomColor: active
|
|
2172
|
+
? AppColors.purple
|
|
2173
|
+
: 'transparent',
|
|
2174
|
+
}}>
|
|
2175
|
+
<Text style={{
|
|
2176
|
+
fontFamily: active
|
|
2177
|
+
? AppFonts.interBold
|
|
2178
|
+
: AppFonts.interMedium,
|
|
2179
|
+
fontSize: 12,
|
|
2180
|
+
color: active
|
|
2181
|
+
? AppColors.purple
|
|
2182
|
+
: '#64748B',
|
|
2183
|
+
}}>
|
|
2184
|
+
{label}
|
|
2185
|
+
</Text>
|
|
2186
|
+
</Pressable>);
|
|
2187
|
+
})}
|
|
2188
|
+
</View>
|
|
2189
|
+
<View style={{ flex: 1, padding: 12 }}>
|
|
2190
|
+
{htmlSubTab === 'html' ? (webViewHtml ? (<View style={{ flex: 1 }}>
|
|
2191
|
+
<View style={{
|
|
2192
|
+
flexDirection: 'row',
|
|
2193
|
+
alignItems: 'center',
|
|
2194
|
+
marginBottom: 8,
|
|
2195
|
+
gap: 8,
|
|
2196
|
+
}}>
|
|
2197
|
+
<View style={[
|
|
2198
|
+
styles.searchContainer,
|
|
2199
|
+
{ flex: 1 },
|
|
2200
|
+
]}>
|
|
2201
|
+
<SearchIcon color={AppColors.grayTextWeak} size={16}/>
|
|
2202
|
+
<TextInput placeholder="Search HTML..." placeholderTextColor={AppColors.grayTextWeak} value={htmlSearch} onChangeText={setHtmlSearch} style={styles.searchInput} autoCorrect={false} autoCapitalize="none"/>
|
|
2203
|
+
{htmlSearch.length > 0 && (<Pressable onPress={() => setHtmlSearch('')} hitSlop={10} style={styles.clearBtn}>
|
|
2204
|
+
<ClearIcon color={AppColors.grayTextWeak} size={14}/>
|
|
2205
|
+
</Pressable>)}
|
|
2206
|
+
</View>
|
|
2207
|
+
<CopyButton value={webViewHtml} label="HTML Source"/>
|
|
2208
|
+
</View>
|
|
2209
|
+
<CodeSnippet code={webViewHtml} language="html" search={htmlSearch}/>
|
|
2210
|
+
</View>) : (<Text style={{
|
|
2211
|
+
fontFamily: 'monospace',
|
|
2212
|
+
fontSize: 11,
|
|
2213
|
+
color: '#94A3B8',
|
|
2214
|
+
padding: 12,
|
|
2215
|
+
}}>
|
|
2216
|
+
No HTML content captured.
|
|
2217
|
+
</Text>)) : htmlSubTab === 'css' ? (webViewCss ? (<View style={{ flex: 1 }}>
|
|
2218
|
+
<View style={{
|
|
2219
|
+
flexDirection: 'row',
|
|
2220
|
+
alignItems: 'center',
|
|
2221
|
+
marginBottom: 8,
|
|
2222
|
+
gap: 8,
|
|
2223
|
+
}}>
|
|
2224
|
+
<View style={[
|
|
2225
|
+
styles.searchContainer,
|
|
2226
|
+
{ flex: 1 },
|
|
2227
|
+
]}>
|
|
2228
|
+
<SearchIcon color={AppColors.grayTextWeak} size={16}/>
|
|
2229
|
+
<TextInput placeholder="Search CSS..." placeholderTextColor={AppColors.grayTextWeak} value={cssSearch} onChangeText={setCssSearch} style={styles.searchInput} autoCorrect={false} autoCapitalize="none"/>
|
|
2230
|
+
{cssSearch.length > 0 && (<Pressable onPress={() => setCssSearch('')} hitSlop={10} style={styles.clearBtn}>
|
|
2231
|
+
<ClearIcon color={AppColors.grayTextWeak} size={14}/>
|
|
2232
|
+
</Pressable>)}
|
|
2233
|
+
</View>
|
|
2234
|
+
<CopyButton value={webViewCss} label="CSS Source"/>
|
|
2235
|
+
</View>
|
|
2236
|
+
<CodeSnippet code={webViewCss} language="css" search={cssSearch}/>
|
|
2237
|
+
</View>) : (<Text style={{
|
|
2238
|
+
fontFamily: 'monospace',
|
|
2239
|
+
fontSize: 11,
|
|
2240
|
+
color: '#94A3B8',
|
|
2241
|
+
padding: 12,
|
|
2242
|
+
}}>
|
|
2243
|
+
No CSS styles detected on this page.
|
|
2244
|
+
</Text>)) : webViewJs ? (<View style={{ flex: 1 }}>
|
|
2245
|
+
<View style={{
|
|
2246
|
+
flexDirection: 'row',
|
|
2247
|
+
alignItems: 'center',
|
|
2248
|
+
marginBottom: 8,
|
|
2249
|
+
gap: 8,
|
|
2250
|
+
}}>
|
|
2251
|
+
<View style={[styles.searchContainer, { flex: 1 }]}>
|
|
2252
|
+
<SearchIcon color={AppColors.grayTextWeak} size={16}/>
|
|
2253
|
+
<TextInput placeholder="Search Javascript..." placeholderTextColor={AppColors.grayTextWeak} value={jsSearch} onChangeText={setJsSearch} style={styles.searchInput} autoCorrect={false} autoCapitalize="none"/>
|
|
2254
|
+
{jsSearch.length > 0 && (<Pressable onPress={() => setJsSearch('')} hitSlop={10} style={styles.clearBtn}>
|
|
2255
|
+
<ClearIcon color={AppColors.grayTextWeak} size={14}/>
|
|
2256
|
+
</Pressable>)}
|
|
2257
|
+
</View>
|
|
2258
|
+
<CopyButton value={webViewJs} label="JS Source"/>
|
|
2259
|
+
</View>
|
|
2260
|
+
<CodeSnippet code={webViewJs} language="javascript" search={jsSearch}/>
|
|
2261
|
+
</View>) : (<Text style={{
|
|
2262
|
+
fontFamily: 'monospace',
|
|
2263
|
+
fontSize: 11,
|
|
2264
|
+
color: '#94A3B8',
|
|
2265
|
+
padding: 12,
|
|
2266
|
+
}}>
|
|
2267
|
+
No scripts detected on this page.
|
|
2268
|
+
</Text>)}
|
|
2269
|
+
</View>
|
|
2270
|
+
</View>) : (<View style={styles.emptyContainer}>
|
|
2271
|
+
<View style={styles.emptyIconWrap}>
|
|
2272
|
+
<GlobeIcon color={AppColors.purple} size={32}/>
|
|
2273
|
+
</View>
|
|
2274
|
+
<Text style={styles.emptyTitle}>
|
|
2275
|
+
No Page Source Captured
|
|
2276
|
+
</Text>
|
|
2277
|
+
<Text style={styles.emptySub}>
|
|
2278
|
+
Load a page in the WebView to inspect its HTML, CSS,
|
|
2279
|
+
or Javascript source.
|
|
2280
|
+
</Text>
|
|
2281
|
+
</View>)}
|
|
2282
|
+
</View>) : (<FlatList data={filteredNavHistory} keyExtractor={(item, index) => `${index}-${item.timestamp}`} ListHeaderComponent={<View style={{
|
|
2283
|
+
paddingHorizontal: 16,
|
|
2284
|
+
paddingTop: 12,
|
|
2285
|
+
paddingBottom: 8,
|
|
2286
|
+
}}>
|
|
2287
|
+
<Text style={styles.resultCount}>
|
|
2288
|
+
Navigation History ({webViewNavHistory.length})
|
|
2289
|
+
</Text>
|
|
2290
|
+
</View>} renderItem={({ item, index, }) => {
|
|
2291
|
+
const isLatest = index === 0;
|
|
2292
|
+
const formatNavTime = (ts) => {
|
|
2293
|
+
const d = new Date(ts);
|
|
2294
|
+
return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String(d.getSeconds()).padStart(2, '0')}`;
|
|
2295
|
+
};
|
|
2296
|
+
return (<View style={{
|
|
2297
|
+
paddingHorizontal: 16,
|
|
2298
|
+
paddingVertical: 8,
|
|
2299
|
+
backgroundColor: isLatest ? '#F1F5F9' : '#FFFFFF',
|
|
2300
|
+
borderBottomWidth: 1,
|
|
2301
|
+
borderBottomColor: '#E2E8F0',
|
|
2302
|
+
flexDirection: 'row',
|
|
2303
|
+
alignItems: 'center',
|
|
2304
|
+
justifyContent: 'space-between',
|
|
2305
|
+
gap: 12,
|
|
2306
|
+
}}>
|
|
2307
|
+
<View style={{ flex: 1, gap: 2 }}>
|
|
2308
|
+
<View style={{
|
|
2309
|
+
flexDirection: 'row',
|
|
2310
|
+
alignItems: 'center',
|
|
2311
|
+
gap: 6,
|
|
2312
|
+
flexWrap: 'wrap',
|
|
2313
|
+
}}>
|
|
2314
|
+
<Text numberOfLines={1} ellipsizeMode="tail" style={{
|
|
2315
|
+
fontFamily: AppFonts.interBold,
|
|
2316
|
+
fontSize: 13,
|
|
2317
|
+
color: '#334155',
|
|
2318
|
+
flexShrink: 1,
|
|
2319
|
+
}}>
|
|
2320
|
+
{item.title || 'Untitled Page'}
|
|
2321
|
+
</Text>
|
|
2322
|
+
{isLatest && (<View style={{
|
|
2323
|
+
backgroundColor: '#E2E8F0',
|
|
2324
|
+
paddingHorizontal: 6,
|
|
2325
|
+
paddingVertical: 2,
|
|
2326
|
+
borderRadius: 4,
|
|
2327
|
+
}}>
|
|
2328
|
+
<Text style={{
|
|
2329
|
+
fontFamily: AppFonts.interBold,
|
|
2330
|
+
fontSize: 9,
|
|
2331
|
+
color: '#475569',
|
|
2332
|
+
}}>
|
|
2333
|
+
Active
|
|
2334
|
+
</Text>
|
|
2335
|
+
</View>)}
|
|
2336
|
+
</View>
|
|
2337
|
+
<HighlightText text={item.url} search={webViewSearch} numberOfLines={3} ellipsizeMode="tail" style={{
|
|
2338
|
+
fontFamily: AppFonts.interRegular,
|
|
2339
|
+
fontSize: 11,
|
|
2340
|
+
color: '#64748B',
|
|
2341
|
+
flexShrink: 1,
|
|
2342
|
+
}} highlightStyle={styles.highlight} detectLinks={true}/>
|
|
2343
|
+
<Text style={{
|
|
2344
|
+
fontFamily: AppFonts.interRegular,
|
|
2345
|
+
fontSize: 10,
|
|
2346
|
+
color: '#94A3B8',
|
|
2347
|
+
}}>
|
|
2348
|
+
{formatNavTime(item.timestamp)}
|
|
2349
|
+
</Text>
|
|
2350
|
+
</View>
|
|
2351
|
+
<CopyButton value={item.url} label="Copy URL"/>
|
|
2352
|
+
</View>);
|
|
2353
|
+
}} initialNumToRender={15} maxToRenderPerBatch={15} windowSize={7} removeClippedSubviews={true} ListEmptyComponent={<EmptyState isSearch={false}/>} contentContainerStyle={[
|
|
2354
|
+
styles.listContent,
|
|
2355
|
+
filteredNavHistory.length === 0 && { flexGrow: 1 },
|
|
2356
|
+
]} keyboardShouldPersistTaps="handled"/>)}
|
|
2357
|
+
</View>) : (<ScrollView style={styles.detailScroll} contentContainerStyle={styles.detailContent} showsVerticalScrollIndicator={true}>
|
|
2358
|
+
{(() => {
|
|
2359
|
+
const routeInfo = logRouteMapRef.current.get(selected.id);
|
|
2360
|
+
const screenPath = routeInfo && routeInfo.path !== 'Navigators'
|
|
2361
|
+
? routeInfo.path.split(' ➔ ')
|
|
2362
|
+
: [];
|
|
2363
|
+
const parts = ['APIs', 'APIs', ...screenPath];
|
|
2364
|
+
return (<View style={{ marginBottom: 12, marginTop: 4 }}>
|
|
2365
|
+
<Text style={{
|
|
2366
|
+
fontFamily: AppFonts.interMedium,
|
|
2367
|
+
fontSize: 11.5,
|
|
2368
|
+
color: AppColors.grayTextWeak,
|
|
2369
|
+
}}>
|
|
2370
|
+
{parts.map((part, index) => (<React.Fragment key={index}>
|
|
2371
|
+
{index > 0 && ' ➔ '}
|
|
2372
|
+
<Text style={index === parts.length - 1
|
|
2373
|
+
? {
|
|
2374
|
+
color: AppColors.purple,
|
|
2375
|
+
fontFamily: AppFonts.interBold,
|
|
2376
|
+
}
|
|
2377
|
+
: undefined}>
|
|
2378
|
+
{part}
|
|
2379
|
+
</Text>
|
|
2380
|
+
</React.Fragment>))}
|
|
2381
|
+
</Text>
|
|
2382
|
+
</View>);
|
|
2383
|
+
})()}
|
|
2384
|
+
|
|
2385
|
+
<View style={styles.detailInfoBar}>
|
|
2386
|
+
<View style={styles.detailInfoTop}>
|
|
2387
|
+
<View style={{
|
|
2388
|
+
flexDirection: 'row',
|
|
2389
|
+
alignItems: 'center',
|
|
2390
|
+
gap: 10,
|
|
2391
|
+
}}>
|
|
2392
|
+
<View style={[
|
|
2393
|
+
styles.methodBadge,
|
|
2394
|
+
{
|
|
2395
|
+
backgroundColor: `${METHOD_COLORS[selected.method] ??
|
|
2396
|
+
METHOD_COLORS.ALL}15`,
|
|
2397
|
+
},
|
|
2398
|
+
]}>
|
|
2399
|
+
<Text style={[
|
|
2400
|
+
styles.methodBadgeText,
|
|
2401
|
+
{
|
|
2402
|
+
color: METHOD_COLORS[selected.method] ??
|
|
2403
|
+
METHOD_COLORS.ALL,
|
|
2404
|
+
},
|
|
2405
|
+
]}>
|
|
2406
|
+
{selected.method}
|
|
2407
|
+
</Text>
|
|
2408
|
+
</View>
|
|
2409
|
+
|
|
2410
|
+
{selected.status != null && (<View style={[
|
|
2411
|
+
styles.chip,
|
|
2412
|
+
{
|
|
2413
|
+
backgroundColor: selected.status === 0
|
|
2414
|
+
? `${AppColors.errorColor}15`
|
|
2415
|
+
: `${getStatusColor(selected.status)}15`,
|
|
2416
|
+
borderColor: selected.status === 0
|
|
2417
|
+
? `${AppColors.errorColor}40`
|
|
2418
|
+
: `${getStatusColor(selected.status)}40`,
|
|
2419
|
+
},
|
|
2420
|
+
]}>
|
|
2421
|
+
{selected.status === 0 ? (<FailIcon size={8} color={AppColors.errorColor}/>) : (<Svg width={6} height={6} viewBox="0 0 10 10" fill="none">
|
|
2422
|
+
<Circle cx="5" cy="5" r="5" fill={getStatusColor(selected.status)}/>
|
|
2423
|
+
</Svg>)}
|
|
2424
|
+
<Text style={[
|
|
2425
|
+
styles.chipText,
|
|
2426
|
+
{
|
|
2427
|
+
color: selected.status === 0
|
|
2428
|
+
? AppColors.errorColor
|
|
2429
|
+
: getStatusColor(selected.status),
|
|
2430
|
+
},
|
|
2431
|
+
]}>
|
|
2432
|
+
{selected.status === 0
|
|
2433
|
+
? 'Failed'
|
|
2434
|
+
: String(selected.status)}
|
|
2435
|
+
</Text>
|
|
2436
|
+
</View>)}
|
|
2437
|
+
</View>
|
|
2438
|
+
<View style={styles.detailInfoRight}>
|
|
2439
|
+
<TouchableScale style={styles.iconSquareBtn} onPress={() => Linking.openURL(detailDisplayUrl)} hitSlop={12}>
|
|
2440
|
+
<GlobeIcon color={AppColors.grayTextWeak} size={14}/>
|
|
2441
|
+
</TouchableScale>
|
|
2442
|
+
<CopyButton value={getFetchCommand(selected)} label="fetch()" iconType="fetch"/>
|
|
2443
|
+
<CopyButton value={getCurlCommand(selected)} label="cURL" iconType="terminal"/>
|
|
2444
|
+
<CopyButton value={detailDisplayUrl} label="URL"/>
|
|
2445
|
+
</View>
|
|
2446
|
+
</View>
|
|
2447
|
+
|
|
2448
|
+
<Pressable style={styles.detailUrlContainer} onPress={() => Linking.openURL(detailDisplayUrl)}>
|
|
2449
|
+
<Text selectable={true} style={styles.detailUrl}>
|
|
2450
|
+
{detailDisplayUrl}
|
|
2451
|
+
</Text>
|
|
2452
|
+
</Pressable>
|
|
2453
|
+
</View>
|
|
2454
|
+
|
|
2455
|
+
<MetaAccordion status={selected.status} statusColor={getStatusColor(selected.status)} duration={selected.duration} size={getSize(selected.response)} triggeredAt={formatDateTime(selected.startTime)}/>
|
|
2456
|
+
|
|
2457
|
+
{(() => {
|
|
2458
|
+
const routeInfo = logRouteMapRef.current.get(selected.id);
|
|
2459
|
+
if (!routeInfo || routeInfo.path === 'Navigators')
|
|
2460
|
+
return null;
|
|
2461
|
+
return <SourcePageCard routeInfo={routeInfo}/>;
|
|
2462
|
+
})()}
|
|
2463
|
+
|
|
2464
|
+
<View style={styles.seperator}/>
|
|
2465
|
+
|
|
2466
|
+
{(() => {
|
|
2467
|
+
const cType = selected.responseHeaders?.['content-type'] ||
|
|
2468
|
+
selected.responseHeaders?.['Content-Type'];
|
|
2469
|
+
if (cType?.includes('image/')) {
|
|
2470
|
+
return (<View style={styles.imagePreviewWrapper}>
|
|
2471
|
+
<Image source={{ uri: selected.url }} style={styles.imagePreview} resizeMode="contain"/>
|
|
2472
|
+
<TouchableScale style={styles.imageDownloadBtn} onPress={() => Linking.openURL(selected.url)} hitSlop={10}>
|
|
2473
|
+
<DownloadIcon color={AppColors.purple} size={18}/>
|
|
2474
|
+
</TouchableScale>
|
|
2475
|
+
</View>);
|
|
2476
|
+
}
|
|
2477
|
+
return null;
|
|
2478
|
+
})()}
|
|
2479
|
+
|
|
2480
|
+
<View style={styles.detailSearchRow}>
|
|
2481
|
+
<View style={styles.detailSearchBox}>
|
|
2482
|
+
<TextInput placeholder="Search in request / response..." placeholderTextColor={AppColors.grayTextWeak} value={detailSearch} onChangeText={setDetailSearch} style={styles.detailSearchInput} autoCorrect={false} autoCapitalize="none"/>
|
|
2483
|
+
{detailSearch.length > 0 && (<Pressable onPress={() => setDetailSearch('')} hitSlop={10} style={{ padding: 8 }}>
|
|
2484
|
+
<ClearIcon color={AppColors.grayTextWeak} size={14}/>
|
|
2485
|
+
</Pressable>)}
|
|
2486
|
+
</View>
|
|
2487
|
+
<TouchableScale style={[styles.iconSquareBtn, { marginLeft: 8 }]} onPress={() => {
|
|
2488
|
+
const next = reqExpanded === true || resExpanded === true
|
|
2489
|
+
? false
|
|
2490
|
+
: true;
|
|
2491
|
+
setReqExpanded(next);
|
|
2492
|
+
setResExpanded(next);
|
|
2493
|
+
}} hitSlop={10}>
|
|
2494
|
+
<ExpandCollapseIcon isExpanded={reqExpanded === true || resExpanded === true} color={reqExpanded === true || resExpanded === true
|
|
2495
|
+
? AppColors.purple
|
|
2496
|
+
: AppColors.grayTextWeak} size={14}/>
|
|
2497
|
+
</TouchableScale>
|
|
2498
|
+
</View>
|
|
2499
|
+
|
|
2500
|
+
<HeadersSection title="Request Headers" headers={selected.requestHeaders} search={detailSearch} resetKey={selected.id}/>
|
|
2501
|
+
<HeadersSection title="Response Headers" headers={selected.responseHeaders} search={detailSearch} resetKey={selected.id}/>
|
|
2502
|
+
|
|
2503
|
+
{selected.request != null && selected.method !== 'GET' && (<View style={styles.sectionContainer}>
|
|
2504
|
+
<SectionHeader title="Request" value={selected.request} expanded={reqExpanded} onToggleExpand={() => setReqExpanded(v => !v)} showDiff={prevRequestData != null} isDiffing={showReqDiff} onToggleDiff={() => {
|
|
2505
|
+
setShowReqDiff(v => !v);
|
|
2506
|
+
if (!reqExpanded && !showReqDiff)
|
|
2507
|
+
setReqExpanded(true);
|
|
2508
|
+
}}/>
|
|
2509
|
+
{showReqDiff ? (<DiffViewer oldData={prevRequestData} newData={selected.request} forceOpen={reqExpanded}/>) : (<JsonViewer data={selected.request} search={detailSearch} forceOpen={reqExpanded}/>)}
|
|
2510
|
+
</View>)}
|
|
2511
|
+
|
|
2512
|
+
<View style={styles.sectionContainer}>
|
|
2513
|
+
<SectionHeader title="Response" value={selected.response} expanded={resExpanded} onToggleExpand={() => setResExpanded(v => !v)} showDiff={prevResponseData != null} isDiffing={showResDiff} onToggleDiff={() => {
|
|
2514
|
+
setShowResDiff(v => !v);
|
|
2515
|
+
if (!resExpanded && !showResDiff)
|
|
2516
|
+
setResExpanded(true);
|
|
2517
|
+
}}/>
|
|
2518
|
+
{showResDiff ? (<DiffViewer oldData={prevResponseData} newData={selected.response} forceOpen={resExpanded}/>) : (<JsonViewer data={selected.response} search={detailSearch} forceOpen={resExpanded}/>)}
|
|
2519
|
+
</View>
|
|
2520
|
+
</ScrollView>)) : (<View style={styles.empty}>
|
|
2521
|
+
<ActivityIndicator size="large" color={AppColors.purple}/>
|
|
2522
|
+
<Text style={[styles.emptySub, { marginTop: 12 }]}>
|
|
2523
|
+
Loading logs...
|
|
2524
|
+
</Text>
|
|
2525
|
+
</View>)}
|
|
2526
|
+
</View>)}
|
|
2527
|
+
</Modal>
|
|
2528
|
+
</>);
|
|
2529
|
+
};
|
|
2530
|
+
export default NetworkInspector;
|
|
2531
|
+
// ─── Analytics list styles (Firebase DebugView look) ─────────────────────────
|
|
2532
|
+
const analyticsListStyles = StyleSheet.create({
|
|
2533
|
+
cardRow: {},
|
|
2534
|
+
// ── Top Events summary card (mirrors Firebase DebugView right panel) ────────
|
|
2535
|
+
topEventsCard: {
|
|
2536
|
+
marginHorizontal: 16,
|
|
2537
|
+
marginBottom: 12,
|
|
2538
|
+
backgroundColor: AppColors.primaryLight,
|
|
2539
|
+
borderRadius: 12,
|
|
2540
|
+
borderWidth: 1,
|
|
2541
|
+
borderColor: AppColors.grayBorderSecondary,
|
|
2542
|
+
paddingHorizontal: 16,
|
|
2543
|
+
paddingVertical: 12,
|
|
2544
|
+
shadowColor: '#000',
|
|
2545
|
+
shadowOpacity: 0.04,
|
|
2546
|
+
shadowRadius: 4,
|
|
2547
|
+
shadowOffset: { width: 0, height: 1 },
|
|
2548
|
+
elevation: 1,
|
|
2549
|
+
},
|
|
2550
|
+
topEventsHeaderRow: {
|
|
2551
|
+
flexDirection: 'row',
|
|
2552
|
+
alignItems: 'center',
|
|
2553
|
+
justifyContent: 'space-between',
|
|
2554
|
+
marginBottom: 12,
|
|
2555
|
+
},
|
|
2556
|
+
topEventsTitle: {
|
|
2557
|
+
fontFamily: AppFonts.interBold,
|
|
2558
|
+
fontSize: 10,
|
|
2559
|
+
color: AppColors.grayTextWeak,
|
|
2560
|
+
letterSpacing: 0.8,
|
|
2561
|
+
textTransform: 'uppercase',
|
|
2562
|
+
},
|
|
2563
|
+
topEventsSubtitle: {
|
|
2564
|
+
fontFamily: AppFonts.interMedium,
|
|
2565
|
+
fontSize: 9,
|
|
2566
|
+
color: AppColors.grayTextWeak,
|
|
2567
|
+
letterSpacing: 0.4,
|
|
2568
|
+
},
|
|
2569
|
+
topEventRow: {
|
|
2570
|
+
flexDirection: 'row',
|
|
2571
|
+
alignItems: 'center',
|
|
2572
|
+
marginBottom: 8,
|
|
2573
|
+
gap: 8,
|
|
2574
|
+
},
|
|
2575
|
+
topEventName: {
|
|
2576
|
+
fontFamily: AppFonts.interMedium,
|
|
2577
|
+
fontSize: 12,
|
|
2578
|
+
color: AppColors.primaryBlack,
|
|
2579
|
+
flex: 1,
|
|
2580
|
+
},
|
|
2581
|
+
topEventBarWrap: {
|
|
2582
|
+
flex: 0.8,
|
|
2583
|
+
height: 3,
|
|
2584
|
+
backgroundColor: AppColors.grayBackground,
|
|
2585
|
+
borderRadius: 2,
|
|
2586
|
+
overflow: 'hidden',
|
|
2587
|
+
},
|
|
2588
|
+
topEventBar: {
|
|
2589
|
+
height: '100%',
|
|
2590
|
+
borderRadius: 2,
|
|
2591
|
+
},
|
|
2592
|
+
topEventCount: {
|
|
2593
|
+
fontFamily: AppFonts.interBold,
|
|
2594
|
+
fontSize: 11,
|
|
2595
|
+
color: AppColors.grayText,
|
|
2596
|
+
width: 24,
|
|
2597
|
+
textAlign: 'right',
|
|
2598
|
+
},
|
|
2599
|
+
iconCircle: {
|
|
2600
|
+
width: 24,
|
|
2601
|
+
height: 24,
|
|
2602
|
+
borderRadius: 12,
|
|
2603
|
+
alignItems: 'center',
|
|
2604
|
+
justifyContent: 'center',
|
|
2605
|
+
},
|
|
2606
|
+
});
|
|
2607
|
+
// Re-export public APIs
|
|
2608
|
+
export { setupNetworkLogger, clearNetworkLogs, subscribeNetworkLogs, } from './customHooks/networkLogger';
|
|
2609
|
+
export { setupConsoleLogger, clearConsoleLogs, subscribeConsoleLogs, } from './customHooks/consoleLogger';
|
|
2610
|
+
export { setupAnalyticsLogger, logAnalyticsEvent, subscribeAnalyticsEvents, clearAnalyticsEvents, } from './customHooks/analyticsLogger';
|
|
2611
|
+
export { getWebViewLogs, getWebViewNavHistory, getWebViewHtml, getWebViewCss, getWebViewJs, getWebViewHtmlUrl, clearWebViewData, subscribeWebView, } from './customHooks/webViewLogger';
|