react-native-inapp-inspector 1.1.35 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +123 -101
- package/android/build.gradle +13 -1
- package/android/src/main/java/com/inappinspector/NetworkInspectorModule.kt +890 -0
- package/android/src/main/java/com/inappinspector/NetworkInspectorPackage.kt +17 -0
- package/dist/commonjs/components/ConsoleLogCard.js +5 -5
- package/dist/commonjs/components/CopyButton.js +3 -0
- package/dist/commonjs/components/EndOfListFooter.js +1 -1
- package/dist/commonjs/components/ErrorBoundary.js +3 -0
- package/dist/commonjs/components/Inspector/AnalyticsTab.js +25 -73
- package/dist/commonjs/components/Inspector/BundleTab.js +671 -189
- package/dist/commonjs/components/Inspector/ConsoleTab.js +110 -99
- package/dist/commonjs/components/Inspector/CrashTab.js +99 -91
- package/dist/commonjs/components/Inspector/InspectorHeader.js +107 -20
- package/dist/commonjs/components/Inspector/MainScreen.js +111 -68
- package/dist/commonjs/components/Inspector/NetworkTab.js +151 -30
- package/dist/commonjs/components/Inspector/PerformanceTab.js +96 -3
- package/dist/commonjs/components/Inspector/ReduxTab.js +18 -10
- package/dist/commonjs/components/Inspector/SettingsPanel.js +1218 -1238
- package/dist/commonjs/components/Inspector/TabBar.js +4 -2
- package/dist/commonjs/components/JsonViewer.js +9 -4
- package/dist/commonjs/components/LogCard.js +31 -1
- package/dist/commonjs/components/LogSyntaxHighlighter.d.ts +16 -0
- package/dist/commonjs/components/LogSyntaxHighlighter.js +130 -0
- package/dist/commonjs/components/NetworkIcons.d.ts +3 -0
- package/dist/commonjs/components/NetworkIcons.js +15 -1
- package/dist/commonjs/components/Slider.d.ts +13 -0
- package/dist/commonjs/components/Slider.js +261 -0
- package/dist/commonjs/components/TouchableScale.d.ts +1 -0
- package/dist/commonjs/components/TouchableScale.js +30 -32
- package/dist/commonjs/constants/index.d.ts +1 -2
- package/dist/commonjs/constants/index.js +4 -0
- package/dist/commonjs/constants/version.d.ts +1 -1
- package/dist/commonjs/constants/version.js +1 -1
- package/dist/commonjs/customHooks/analyticsLogger.d.ts +4 -0
- package/dist/commonjs/customHooks/analyticsLogger.js +8 -6
- package/dist/commonjs/customHooks/crashHandler.js +8 -1
- package/dist/commonjs/helpers/index.d.ts +1 -0
- package/dist/commonjs/helpers/index.js +25 -1
- package/dist/commonjs/helpers/searchQueryParser.d.ts +6 -0
- package/dist/commonjs/helpers/searchQueryParser.js +236 -0
- package/dist/commonjs/helpers/settingsStore.d.ts +9 -0
- package/dist/commonjs/helpers/settingsStore.js +59 -3
- package/dist/commonjs/index.d.ts +1 -0
- package/dist/commonjs/index.js +220 -43
- package/dist/commonjs/native/NativeInspector.d.ts +125 -0
- package/dist/commonjs/native/NativeInspector.js +313 -0
- package/dist/commonjs/native/NativeNetworkInspector.d.ts +22 -0
- package/dist/commonjs/native/NativeNetworkInspector.js +4 -0
- package/dist/commonjs/styles/index.js +41 -41
- package/dist/commonjs/types/enums.d.ts +1 -1
- package/dist/commonjs/types/interfaces.d.ts +9 -0
- package/dist/esm/components/ConsoleLogCard.js +5 -5
- package/dist/esm/components/CopyButton.js +3 -0
- package/dist/esm/components/EndOfListFooter.js +2 -2
- package/dist/esm/components/ErrorBoundary.js +3 -0
- package/dist/esm/components/Inspector/AnalyticsTab.js +27 -75
- package/dist/esm/components/Inspector/BundleTab.js +671 -189
- package/dist/esm/components/Inspector/ConsoleTab.js +111 -100
- package/dist/esm/components/Inspector/CrashTab.js +99 -91
- package/dist/esm/components/Inspector/InspectorHeader.js +108 -21
- package/dist/esm/components/Inspector/MainScreen.js +113 -70
- package/dist/esm/components/Inspector/NetworkTab.js +152 -31
- package/dist/esm/components/Inspector/PerformanceTab.js +96 -3
- package/dist/esm/components/Inspector/ReduxTab.js +18 -10
- package/dist/esm/components/Inspector/SettingsPanel.js +1222 -1242
- package/dist/esm/components/Inspector/TabBar.js +4 -2
- package/dist/esm/components/JsonViewer.js +9 -4
- package/dist/esm/components/LogCard.js +32 -2
- package/dist/esm/components/LogSyntaxHighlighter.d.ts +16 -0
- package/dist/esm/components/LogSyntaxHighlighter.js +123 -0
- package/dist/esm/components/NetworkIcons.d.ts +3 -0
- package/dist/esm/components/NetworkIcons.js +11 -0
- package/dist/esm/components/Slider.d.ts +13 -0
- package/dist/esm/components/Slider.js +222 -0
- package/dist/esm/components/TouchableScale.d.ts +1 -0
- package/dist/esm/components/TouchableScale.js +31 -33
- package/dist/esm/constants/index.d.ts +1 -2
- package/dist/esm/constants/index.js +4 -0
- package/dist/esm/constants/version.d.ts +1 -1
- package/dist/esm/constants/version.js +1 -1
- package/dist/esm/customHooks/analyticsLogger.d.ts +4 -0
- package/dist/esm/customHooks/analyticsLogger.js +6 -5
- package/dist/esm/customHooks/crashHandler.js +8 -1
- package/dist/esm/helpers/index.d.ts +1 -0
- package/dist/esm/helpers/index.js +11 -1
- package/dist/esm/helpers/searchQueryParser.d.ts +6 -0
- package/dist/esm/helpers/searchQueryParser.js +233 -0
- package/dist/esm/helpers/settingsStore.d.ts +9 -0
- package/dist/esm/helpers/settingsStore.js +58 -3
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +207 -44
- package/dist/esm/native/NativeInspector.d.ts +125 -0
- package/dist/esm/native/NativeInspector.js +289 -0
- package/dist/esm/native/NativeNetworkInspector.d.ts +22 -0
- package/dist/esm/native/NativeNetworkInspector.js +2 -0
- package/dist/esm/styles/index.js +41 -41
- package/dist/esm/types/enums.d.ts +1 -1
- package/dist/esm/types/interfaces.d.ts +9 -0
- package/ios/NetworkInspectorModule.h +6 -0
- package/ios/NetworkInspectorModule.m +760 -5
- package/package.json +13 -2
- package/react-native-inapp-inspector.podspec +7 -2
- package/android/src/main/java/com/inappinspector/NetworkInspectorModule.java +0 -97
- package/android/src/main/java/com/inappinspector/NetworkInspectorPackage.java +0 -28
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Animated, ScrollView, Text, View } from 'react-native';
|
|
3
3
|
import { useInspector } from './InspectorContext';
|
|
4
|
-
import TouchableScale from '../TouchableScale';
|
|
5
4
|
import styles from '../../styles';
|
|
6
5
|
import { AppColors } from '../../styles/AppColors';
|
|
6
|
+
import TouchableScale from '../TouchableScale';
|
|
7
7
|
import { SignalIcon, TerminalIcon, AnalyticsIcon, PackageIcon, ReduxIcon, PerformanceIcon, CrashIcon, } from '../NetworkIcons';
|
|
8
8
|
import { isReduxConnected } from '../../customHooks/reduxLogger';
|
|
9
9
|
import { isAnalyticsConnected } from '../../customHooks/analyticsLogger';
|
|
@@ -80,7 +80,9 @@ const TabBar = React.memo(() => {
|
|
|
80
80
|
consoleLogs.length > lastReadLogsCount;
|
|
81
81
|
const hasUnreadCrashes = activeTab !== 'crash' &&
|
|
82
82
|
(crashRecords?.length || 0) > (lastReadCrashesCount || 0);
|
|
83
|
-
return (<TouchableScale key={tab.key} onPress={() =>
|
|
83
|
+
return (<TouchableScale key={tab.key} onPress={() => {
|
|
84
|
+
switchActiveTab(tab.key);
|
|
85
|
+
}} style={[
|
|
84
86
|
styles.contentTabButton,
|
|
85
87
|
isActive && styles.contentTabButtonActive,
|
|
86
88
|
]}>
|
|
@@ -68,20 +68,25 @@ const JsonViewer = React.memo(({ data, search, forceOpen, defaultExpandDepth = 1
|
|
|
68
68
|
}
|
|
69
69
|
};
|
|
70
70
|
const rawText = React.useMemo(() => {
|
|
71
|
+
if (data === undefined || data === null) {
|
|
72
|
+
return '';
|
|
73
|
+
}
|
|
71
74
|
if (typeof data === 'string') {
|
|
72
75
|
return data;
|
|
73
76
|
}
|
|
74
77
|
try {
|
|
75
|
-
|
|
78
|
+
const stringified = JSON.stringify(data);
|
|
79
|
+
return stringified ?? '';
|
|
76
80
|
}
|
|
77
81
|
catch {
|
|
78
|
-
return String(data);
|
|
82
|
+
return String(data) || '';
|
|
79
83
|
}
|
|
80
84
|
}, [data]);
|
|
81
85
|
const [showFullRaw, setShowFullRaw] = useState(false);
|
|
82
86
|
const RAW_LIMIT = 50000;
|
|
83
|
-
const
|
|
84
|
-
const
|
|
87
|
+
const safeRawText = typeof rawText === 'string' ? rawText : '';
|
|
88
|
+
const isTruncated = safeRawText.length > RAW_LIMIT && !showFullRaw;
|
|
89
|
+
const displayRawText = isTruncated ? safeRawText.slice(0, RAW_LIMIT) : safeRawText;
|
|
85
90
|
useEffect(() => {
|
|
86
91
|
if (externalMode) {
|
|
87
92
|
setInternalMode(externalMode);
|
|
@@ -4,7 +4,7 @@ import Svg, { Path } from 'react-native-svg';
|
|
|
4
4
|
import { AppColors } from '../styles/AppColors';
|
|
5
5
|
import { METHOD_COLORS } from '../constants';
|
|
6
6
|
import { getStatusColor, getDurationColor, formatDateTime, getSize, } from '../helpers';
|
|
7
|
-
import { CalendarIcon, ClockIcon, SizeIcon } from './NetworkIcons';
|
|
7
|
+
import { CalendarIcon, ClockIcon, SizeIcon, GlobeIcon, CircleCheckIcon, CircleAlertIcon, CircleXIcon, RepeatIcon, } from './NetworkIcons';
|
|
8
8
|
import { AppFonts } from '../styles/AppFonts';
|
|
9
9
|
import styles from '../styles';
|
|
10
10
|
import HighlightText from './HighlightText';
|
|
@@ -59,6 +59,22 @@ const LogCard = React.memo(function LogCard({ item, onPress, timelineMinStart, t
|
|
|
59
59
|
}).start();
|
|
60
60
|
}
|
|
61
61
|
}, [isNew]);
|
|
62
|
+
const renderStatusIcon = () => {
|
|
63
|
+
if (isLoading) {
|
|
64
|
+
return <ClockIcon color={AppColors.darkOrange} size={9}/>;
|
|
65
|
+
}
|
|
66
|
+
const num = typeof item.status === 'number' ? item.status : parseInt(String(item.status || 0), 10);
|
|
67
|
+
if (num >= 200 && num < 300) {
|
|
68
|
+
return <CircleCheckIcon color={AppColors.greenColor} size={9}/>;
|
|
69
|
+
}
|
|
70
|
+
if (num >= 300 && num < 400) {
|
|
71
|
+
return <RepeatIcon color={AppColors.amber700} size={9}/>;
|
|
72
|
+
}
|
|
73
|
+
if (num >= 400 && num < 500) {
|
|
74
|
+
return <CircleAlertIcon color={AppColors.darkOrange} size={9}/>;
|
|
75
|
+
}
|
|
76
|
+
return <CircleXIcon color={AppColors.errorColor} size={9}/>;
|
|
77
|
+
};
|
|
62
78
|
const triggeredAt = formatDateTime(item.startTime);
|
|
63
79
|
const isJson = item.url.split('?')[0].toLowerCase().endsWith('.json');
|
|
64
80
|
return (<TouchableScale onPress={onPress} style={[
|
|
@@ -210,6 +226,9 @@ const LogCard = React.memo(function LogCard({ item, onPress, timelineMinStart, t
|
|
|
210
226
|
<View style={[
|
|
211
227
|
styles.statusPill,
|
|
212
228
|
{
|
|
229
|
+
flexDirection: 'row',
|
|
230
|
+
alignItems: 'center',
|
|
231
|
+
gap: 3.5,
|
|
213
232
|
backgroundColor: isFailed
|
|
214
233
|
? `${AppColors.errorColor}15`
|
|
215
234
|
: isLoading
|
|
@@ -222,6 +241,7 @@ const LogCard = React.memo(function LogCard({ item, onPress, timelineMinStart, t
|
|
|
222
241
|
: `${statusColor}30`,
|
|
223
242
|
},
|
|
224
243
|
]}>
|
|
244
|
+
{renderStatusIcon()}
|
|
225
245
|
<Text style={[
|
|
226
246
|
styles.statusPillText,
|
|
227
247
|
{
|
|
@@ -244,7 +264,17 @@ const LogCard = React.memo(function LogCard({ item, onPress, timelineMinStart, t
|
|
|
244
264
|
{/* Row 2: Full URL Capsule Container (Clickable with link prompt) */}
|
|
245
265
|
<View style={styles.cardSlugBox}>
|
|
246
266
|
<Pressable onPress={handleOpenUrl} style={styles.slugLeft} hitSlop={6}>
|
|
247
|
-
<
|
|
267
|
+
<View style={{
|
|
268
|
+
width: 17,
|
|
269
|
+
height: 17,
|
|
270
|
+
borderRadius: 4,
|
|
271
|
+
backgroundColor: `${AppColors.skyBlue}1A`,
|
|
272
|
+
alignItems: 'center',
|
|
273
|
+
justifyContent: 'center',
|
|
274
|
+
marginRight: 4,
|
|
275
|
+
}}>
|
|
276
|
+
<GlobeIcon color={AppColors.skyBlue} size={11}/>
|
|
277
|
+
</View>
|
|
248
278
|
<HighlightText text={item.url} search={searchStr} style={[styles.slugText, { color: AppColors.skyBlue, textDecorationLine: 'underline' }]} highlightStyle={styles.highlight} numberOfLines={2} ellipsizeMode="middle"/>
|
|
249
279
|
</Pressable>
|
|
250
280
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { TextStyle } from 'react-native';
|
|
3
|
+
export interface LogSyntaxHighlighterProps {
|
|
4
|
+
text: string;
|
|
5
|
+
search?: string;
|
|
6
|
+
numberOfLines?: number;
|
|
7
|
+
style?: TextStyle | TextStyle[];
|
|
8
|
+
detectLinks?: boolean;
|
|
9
|
+
}
|
|
10
|
+
interface Token {
|
|
11
|
+
type: 'key' | 'string' | 'number' | 'boolean' | 'null' | 'url' | 'bracket' | 'tag' | 'plain';
|
|
12
|
+
value: string;
|
|
13
|
+
}
|
|
14
|
+
export declare const tokenizeText: (input: string) => Token[];
|
|
15
|
+
export declare const LogSyntaxHighlighter: React.FC<LogSyntaxHighlighterProps>;
|
|
16
|
+
export default LogSyntaxHighlighter;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { StyleSheet, Text } from 'react-native';
|
|
3
|
+
import { AppColors } from '../styles/AppColors';
|
|
4
|
+
import { AppFonts } from '../styles/AppFonts';
|
|
5
|
+
import { escapeRegex, handleOpenExternalLink } from '../helpers';
|
|
6
|
+
const TOKEN_REGEX = /("(?:\\.|[^"\\])*"(?=\s*:))|("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*')|(\b-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?\b)|(\b(?:true|false)\b)|(\b(?:null|undefined|NaN)\b)|(https?:\/\/[^\s"',]+)|(\[[\w\s_-]+\])|([{}[\],:])|([^"'\d\s{}[\],:]+|[ \t\r\n]+)/g;
|
|
7
|
+
export const tokenizeText = (input) => {
|
|
8
|
+
if (!input)
|
|
9
|
+
return [];
|
|
10
|
+
const tokens = [];
|
|
11
|
+
let match;
|
|
12
|
+
TOKEN_REGEX.lastIndex = 0;
|
|
13
|
+
while ((match = TOKEN_REGEX.exec(input)) !== null) {
|
|
14
|
+
if (match[1]) {
|
|
15
|
+
tokens.push({ type: 'key', value: match[1] });
|
|
16
|
+
}
|
|
17
|
+
else if (match[2]) {
|
|
18
|
+
tokens.push({ type: 'string', value: match[2] });
|
|
19
|
+
}
|
|
20
|
+
else if (match[3]) {
|
|
21
|
+
tokens.push({ type: 'number', value: match[3] });
|
|
22
|
+
}
|
|
23
|
+
else if (match[4]) {
|
|
24
|
+
tokens.push({ type: 'boolean', value: match[4] });
|
|
25
|
+
}
|
|
26
|
+
else if (match[5]) {
|
|
27
|
+
tokens.push({ type: 'null', value: match[5] });
|
|
28
|
+
}
|
|
29
|
+
else if (match[6]) {
|
|
30
|
+
tokens.push({ type: 'url', value: match[6] });
|
|
31
|
+
}
|
|
32
|
+
else if (match[7]) {
|
|
33
|
+
tokens.push({ type: 'tag', value: match[7] });
|
|
34
|
+
}
|
|
35
|
+
else if (match[8]) {
|
|
36
|
+
tokens.push({ type: 'bracket', value: match[8] });
|
|
37
|
+
}
|
|
38
|
+
else if (match[9]) {
|
|
39
|
+
tokens.push({ type: 'plain', value: match[9] });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return tokens;
|
|
43
|
+
};
|
|
44
|
+
const getTokenColor = (type) => {
|
|
45
|
+
switch (type) {
|
|
46
|
+
case 'key':
|
|
47
|
+
return AppColors.sky600 || '#0369a1';
|
|
48
|
+
case 'string':
|
|
49
|
+
return AppColors.emerald600 || '#059669';
|
|
50
|
+
case 'number':
|
|
51
|
+
return AppColors.amber600 || '#d97706';
|
|
52
|
+
case 'boolean':
|
|
53
|
+
return AppColors.purple || '#7c3aed';
|
|
54
|
+
case 'null':
|
|
55
|
+
return AppColors.red600 || '#dc2626';
|
|
56
|
+
case 'url':
|
|
57
|
+
return '#0284c7';
|
|
58
|
+
case 'tag':
|
|
59
|
+
return AppColors.violet600 || '#7c3aed';
|
|
60
|
+
case 'bracket':
|
|
61
|
+
return AppColors.slate500 || '#64748b';
|
|
62
|
+
case 'plain':
|
|
63
|
+
default:
|
|
64
|
+
return AppColors.primaryBlack || '#1e293b';
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
export const LogSyntaxHighlighter = React.memo(({ text, search, numberOfLines, style, detectLinks = true }) => {
|
|
68
|
+
if (!text)
|
|
69
|
+
return null;
|
|
70
|
+
const tokens = React.useMemo(() => tokenizeText(text), [text]);
|
|
71
|
+
const renderToken = (token, tokenIdx) => {
|
|
72
|
+
const baseColor = getTokenColor(token.type);
|
|
73
|
+
const isUrl = token.type === 'url';
|
|
74
|
+
const isKey = token.type === 'key';
|
|
75
|
+
const tokenStyle = {
|
|
76
|
+
color: baseColor,
|
|
77
|
+
fontFamily: isKey ? AppFonts.interBold : AppFonts.interRegular,
|
|
78
|
+
textDecorationLine: isUrl ? 'underline' : 'none',
|
|
79
|
+
};
|
|
80
|
+
if (!search || search.trim().length === 0) {
|
|
81
|
+
return (<Text key={tokenIdx} style={tokenStyle} onPress={isUrl && detectLinks
|
|
82
|
+
? () => handleOpenExternalLink(token.value)
|
|
83
|
+
: undefined}>
|
|
84
|
+
{token.value}
|
|
85
|
+
</Text>);
|
|
86
|
+
}
|
|
87
|
+
const regex = new RegExp(`(${escapeRegex(search.trim())})`, 'gi');
|
|
88
|
+
const parts = token.value.split(regex);
|
|
89
|
+
return (<Text key={tokenIdx} style={tokenStyle}>
|
|
90
|
+
{parts.map((part, partIdx) => {
|
|
91
|
+
const isMatch = part.toLowerCase() === search.trim().toLowerCase();
|
|
92
|
+
return isMatch ? (<Text key={partIdx} style={[
|
|
93
|
+
tokenStyle,
|
|
94
|
+
styles.searchHighlight,
|
|
95
|
+
]}>
|
|
96
|
+
{part}
|
|
97
|
+
</Text>) : (<Text key={partIdx} style={tokenStyle} onPress={isUrl && detectLinks
|
|
98
|
+
? () => handleOpenExternalLink(token.value)
|
|
99
|
+
: undefined}>
|
|
100
|
+
{part}
|
|
101
|
+
</Text>);
|
|
102
|
+
})}
|
|
103
|
+
</Text>);
|
|
104
|
+
};
|
|
105
|
+
return (<Text style={[styles.containerText, style]} numberOfLines={numberOfLines}>
|
|
106
|
+
{tokens.map(renderToken)}
|
|
107
|
+
</Text>);
|
|
108
|
+
});
|
|
109
|
+
const styles = StyleSheet.create({
|
|
110
|
+
containerText: {
|
|
111
|
+
fontFamily: AppFonts.interRegular,
|
|
112
|
+
fontSize: 12,
|
|
113
|
+
lineHeight: 18,
|
|
114
|
+
color: AppColors.primaryBlack,
|
|
115
|
+
},
|
|
116
|
+
searchHighlight: {
|
|
117
|
+
backgroundColor: '#fef08a',
|
|
118
|
+
color: '#854d0e',
|
|
119
|
+
fontFamily: AppFonts.interBold,
|
|
120
|
+
borderRadius: 2,
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
export default LogSyntaxHighlighter;
|
|
@@ -124,3 +124,6 @@ export declare const SmartphoneIcon: ({ color, size }: IconProps) => React.JSX.E
|
|
|
124
124
|
export declare const AlertTriangleIcon: ({ color, size }: IconProps) => React.JSX.Element;
|
|
125
125
|
export declare const BugIcon: ({ color, size }: IconProps) => React.JSX.Element;
|
|
126
126
|
export declare const TagIcon: ({ color, size }: IconProps) => React.JSX.Element;
|
|
127
|
+
export declare const NpmIcon: ({ color, size }: IconProps) => React.JSX.Element;
|
|
128
|
+
export declare const ResetIcon: ({ color, size }: IconProps) => React.JSX.Element;
|
|
129
|
+
export declare const ChevronDownIcon: ({ color, size }: IconProps) => React.JSX.Element;
|
|
@@ -700,3 +700,14 @@ export const TagIcon = ({ color = AppColors.grayTextWeak, size = 14 }) => (<Svg
|
|
|
700
700
|
<Path d="M12.586 2.586A2 2 0 0 0 11.172 2H4a2 2 0 0 0-2 2v7.172a2 2 0 0 0 .586 1.414l8 8a2 2 0 0 0 2.828 0l7.172-7.172a2 2 0 0 0 0-2.828l-8-8z" stroke={color} strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"/>
|
|
701
701
|
<Circle cx="7.5" cy="7.5" r="1.5" fill={color}/>
|
|
702
702
|
</Svg>);
|
|
703
|
+
export const NpmIcon = ({ color = '#CB3837', size = 14 }) => (<Svg width={size} height={size} viewBox="0 0 256 256" fill="none">
|
|
704
|
+
<Rect width="256" height="256" rx="32" fill={color}/>
|
|
705
|
+
<Path d="M48 48h160v160h-32V96h-32v112H48V48z" fill="#FFFFFF"/>
|
|
706
|
+
</Svg>);
|
|
707
|
+
export const ResetIcon = ({ color = AppColors.white, size = 16 }) => (<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
|
|
708
|
+
<Path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
|
709
|
+
<Path d="M3 3v5h5" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
|
710
|
+
</Svg>);
|
|
711
|
+
export const ChevronDownIcon = ({ color = AppColors.grayTextWeak, size = 14 }) => (<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
|
|
712
|
+
<Path d="M6 9l6 6 6-6" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
|
713
|
+
</Svg>);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface SliderProps {
|
|
3
|
+
value: number;
|
|
4
|
+
onValueChange: (value: number) => void;
|
|
5
|
+
min?: number;
|
|
6
|
+
max?: number;
|
|
7
|
+
step?: number;
|
|
8
|
+
formatLabel?: (value: number) => string;
|
|
9
|
+
quickPresets?: number[];
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare const Slider: React.FC<SliderProps>;
|
|
13
|
+
export default Slider;
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import React, { useRef, useState, useCallback, useMemo } from 'react';
|
|
2
|
+
import { View, Text, StyleSheet, PanResponder, } from 'react-native';
|
|
3
|
+
import { AppColors } from '../styles/AppColors';
|
|
4
|
+
import { AppFonts } from '../styles/AppFonts';
|
|
5
|
+
import TouchableScale from './TouchableScale';
|
|
6
|
+
export const Slider = React.memo(({ value, onValueChange, min = 50, max = 90, step = 5, formatLabel = val => `${Math.round(val)}%`, quickPresets = [50, 60, 70, 80, 90], disabled = false, }) => {
|
|
7
|
+
const trackWidthRef = useRef(0);
|
|
8
|
+
const [trackWidth, setTrackWidth] = useState(0);
|
|
9
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
10
|
+
const clampedValue = Math.max(min, Math.min(max, value));
|
|
11
|
+
const percentage = (clampedValue - min) / (max - min || 1);
|
|
12
|
+
const updateValueFromPosition = useCallback((pageX, trackPageX) => {
|
|
13
|
+
if (trackWidthRef.current <= 0)
|
|
14
|
+
return;
|
|
15
|
+
const relativeX = Math.max(0, Math.min(trackWidthRef.current, pageX - trackPageX));
|
|
16
|
+
const rawRatio = relativeX / trackWidthRef.current;
|
|
17
|
+
let newValue = min + rawRatio * (max - min);
|
|
18
|
+
if (step > 0) {
|
|
19
|
+
newValue = Math.round(newValue / step) * step;
|
|
20
|
+
}
|
|
21
|
+
newValue = Math.max(min, Math.min(max, newValue));
|
|
22
|
+
onValueChange(newValue);
|
|
23
|
+
}, [min, max, step, onValueChange]);
|
|
24
|
+
const trackContainerRef = useRef(null);
|
|
25
|
+
const trackPageXRef = useRef(0);
|
|
26
|
+
const measureTrack = useCallback(() => {
|
|
27
|
+
if (trackContainerRef.current) {
|
|
28
|
+
trackContainerRef.current.measure((_x, _y, _width, _height, pageX) => {
|
|
29
|
+
trackPageXRef.current = pageX;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}, []);
|
|
33
|
+
const panResponder = useMemo(() => PanResponder.create({
|
|
34
|
+
onStartShouldSetPanResponder: () => !disabled,
|
|
35
|
+
onMoveShouldSetPanResponder: () => !disabled,
|
|
36
|
+
onPanResponderGrant: (evt) => {
|
|
37
|
+
setIsDragging(true);
|
|
38
|
+
measureTrack();
|
|
39
|
+
updateValueFromPosition(evt.nativeEvent.pageX, trackPageXRef.current);
|
|
40
|
+
},
|
|
41
|
+
onPanResponderMove: (evt, _gestureState) => {
|
|
42
|
+
updateValueFromPosition(evt.nativeEvent.pageX, trackPageXRef.current);
|
|
43
|
+
},
|
|
44
|
+
onPanResponderRelease: () => {
|
|
45
|
+
setIsDragging(false);
|
|
46
|
+
},
|
|
47
|
+
onPanResponderTerminate: () => {
|
|
48
|
+
setIsDragging(false);
|
|
49
|
+
},
|
|
50
|
+
}), [disabled, measureTrack, updateValueFromPosition]);
|
|
51
|
+
const handleLayout = (e) => {
|
|
52
|
+
const w = e.nativeEvent.layout.width;
|
|
53
|
+
trackWidthRef.current = w;
|
|
54
|
+
setTrackWidth(w);
|
|
55
|
+
measureTrack();
|
|
56
|
+
};
|
|
57
|
+
const thumbPosition = percentage * (trackWidth || 0);
|
|
58
|
+
return (<View style={styles.container}>
|
|
59
|
+
{/* Value Display Row */}
|
|
60
|
+
<View style={styles.valueRow}>
|
|
61
|
+
<Text style={styles.rangeText}>{min}%</Text>
|
|
62
|
+
<View style={[
|
|
63
|
+
styles.currentValueBadge,
|
|
64
|
+
isDragging && styles.currentValueBadgeActive,
|
|
65
|
+
]}>
|
|
66
|
+
<Text style={styles.currentValueText}>{formatLabel(clampedValue)}</Text>
|
|
67
|
+
</View>
|
|
68
|
+
<Text style={styles.rangeText}>{max}%</Text>
|
|
69
|
+
</View>
|
|
70
|
+
|
|
71
|
+
{/* Interactive Track Area */}
|
|
72
|
+
<View ref={trackContainerRef} onLayout={handleLayout} {...panResponder.panHandlers} style={styles.touchArea}>
|
|
73
|
+
{/* Background Rail */}
|
|
74
|
+
<View style={styles.rail}>
|
|
75
|
+
{/* Active Filled Bar */}
|
|
76
|
+
<View style={[
|
|
77
|
+
styles.fillBar,
|
|
78
|
+
{ width: `${Math.round(percentage * 100)}%` },
|
|
79
|
+
]}/>
|
|
80
|
+
</View>
|
|
81
|
+
|
|
82
|
+
{/* Draggable Thumb */}
|
|
83
|
+
<View style={[
|
|
84
|
+
styles.thumb,
|
|
85
|
+
{
|
|
86
|
+
left: Math.max(0, Math.min((trackWidth || 0) - 24, thumbPosition - 12)),
|
|
87
|
+
},
|
|
88
|
+
isDragging && styles.thumbActive,
|
|
89
|
+
]}>
|
|
90
|
+
<View style={styles.thumbCenterDot}/>
|
|
91
|
+
</View>
|
|
92
|
+
</View>
|
|
93
|
+
|
|
94
|
+
{/* Quick Presets */}
|
|
95
|
+
{quickPresets && quickPresets.length > 0 && (<View style={styles.presetRow}>
|
|
96
|
+
{quickPresets.map(preset => {
|
|
97
|
+
const isSelected = Math.round(clampedValue) === preset;
|
|
98
|
+
return (<TouchableScale key={preset} onPress={() => onValueChange(preset)} style={[
|
|
99
|
+
styles.presetButton,
|
|
100
|
+
isSelected && styles.presetButtonActive,
|
|
101
|
+
]}>
|
|
102
|
+
<Text style={[
|
|
103
|
+
styles.presetText,
|
|
104
|
+
isSelected && styles.presetTextActive,
|
|
105
|
+
]}>
|
|
106
|
+
{preset}%
|
|
107
|
+
</Text>
|
|
108
|
+
</TouchableScale>);
|
|
109
|
+
})}
|
|
110
|
+
</View>)}
|
|
111
|
+
</View>);
|
|
112
|
+
});
|
|
113
|
+
const styles = StyleSheet.create({
|
|
114
|
+
container: {
|
|
115
|
+
paddingVertical: 4,
|
|
116
|
+
gap: 8,
|
|
117
|
+
},
|
|
118
|
+
valueRow: {
|
|
119
|
+
flexDirection: 'row',
|
|
120
|
+
alignItems: 'center',
|
|
121
|
+
justifyContent: 'space-between',
|
|
122
|
+
paddingHorizontal: 2,
|
|
123
|
+
},
|
|
124
|
+
rangeText: {
|
|
125
|
+
fontFamily: AppFonts.interMedium,
|
|
126
|
+
fontSize: 11,
|
|
127
|
+
color: AppColors.grayText,
|
|
128
|
+
},
|
|
129
|
+
currentValueBadge: {
|
|
130
|
+
backgroundColor: `${AppColors.purple}1A`,
|
|
131
|
+
borderColor: `${AppColors.purple}33`,
|
|
132
|
+
borderWidth: 1,
|
|
133
|
+
borderRadius: 8,
|
|
134
|
+
paddingHorizontal: 10,
|
|
135
|
+
paddingVertical: 2.5,
|
|
136
|
+
},
|
|
137
|
+
currentValueBadgeActive: {
|
|
138
|
+
backgroundColor: AppColors.purple,
|
|
139
|
+
borderColor: AppColors.purple,
|
|
140
|
+
transform: [{ scale: 1.05 }],
|
|
141
|
+
},
|
|
142
|
+
currentValueText: {
|
|
143
|
+
fontFamily: AppFonts.interBold,
|
|
144
|
+
fontSize: 12,
|
|
145
|
+
color: AppColors.purple,
|
|
146
|
+
},
|
|
147
|
+
touchArea: {
|
|
148
|
+
height: 36,
|
|
149
|
+
justifyContent: 'center',
|
|
150
|
+
position: 'relative',
|
|
151
|
+
},
|
|
152
|
+
rail: {
|
|
153
|
+
height: 8,
|
|
154
|
+
backgroundColor: `${AppColors.grayTextWeak}2B`,
|
|
155
|
+
borderRadius: 4,
|
|
156
|
+
overflow: 'hidden',
|
|
157
|
+
position: 'relative',
|
|
158
|
+
},
|
|
159
|
+
fillBar: {
|
|
160
|
+
height: '100%',
|
|
161
|
+
backgroundColor: AppColors.purple,
|
|
162
|
+
borderRadius: 4,
|
|
163
|
+
},
|
|
164
|
+
thumb: {
|
|
165
|
+
position: 'absolute',
|
|
166
|
+
width: 24,
|
|
167
|
+
height: 24,
|
|
168
|
+
borderRadius: 12,
|
|
169
|
+
backgroundColor: AppColors.white,
|
|
170
|
+
borderWidth: 2.5,
|
|
171
|
+
borderColor: AppColors.purple,
|
|
172
|
+
alignItems: 'center',
|
|
173
|
+
justifyContent: 'center',
|
|
174
|
+
shadowColor: AppColors.black,
|
|
175
|
+
shadowOffset: { width: 0, height: 2 },
|
|
176
|
+
shadowOpacity: 0.18,
|
|
177
|
+
shadowRadius: 3.5,
|
|
178
|
+
elevation: 4,
|
|
179
|
+
},
|
|
180
|
+
thumbActive: {
|
|
181
|
+
transform: [{ scale: 1.15 }],
|
|
182
|
+
shadowOpacity: 0.28,
|
|
183
|
+
shadowRadius: 5,
|
|
184
|
+
elevation: 6,
|
|
185
|
+
borderColor: AppColors.purple,
|
|
186
|
+
},
|
|
187
|
+
thumbCenterDot: {
|
|
188
|
+
width: 6,
|
|
189
|
+
height: 6,
|
|
190
|
+
borderRadius: 3,
|
|
191
|
+
backgroundColor: AppColors.purple,
|
|
192
|
+
},
|
|
193
|
+
presetRow: {
|
|
194
|
+
flexDirection: 'row',
|
|
195
|
+
alignItems: 'center',
|
|
196
|
+
justifyContent: 'space-between',
|
|
197
|
+
gap: 8,
|
|
198
|
+
marginTop: 2,
|
|
199
|
+
},
|
|
200
|
+
presetButton: {
|
|
201
|
+
flex: 1,
|
|
202
|
+
paddingVertical: 5,
|
|
203
|
+
borderRadius: 7,
|
|
204
|
+
alignItems: 'center',
|
|
205
|
+
backgroundColor: AppColors.grayBackground,
|
|
206
|
+
borderWidth: 1,
|
|
207
|
+
borderColor: AppColors.dividerColor,
|
|
208
|
+
},
|
|
209
|
+
presetButtonActive: {
|
|
210
|
+
backgroundColor: `${AppColors.purple}20`,
|
|
211
|
+
borderColor: AppColors.purple,
|
|
212
|
+
},
|
|
213
|
+
presetText: {
|
|
214
|
+
fontFamily: AppFonts.interBold,
|
|
215
|
+
fontSize: 10.5,
|
|
216
|
+
color: AppColors.grayText,
|
|
217
|
+
},
|
|
218
|
+
presetTextActive: {
|
|
219
|
+
color: AppColors.purple,
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
export default Slider;
|
|
@@ -7,6 +7,7 @@ export interface TouchableScaleProps extends PressableProps {
|
|
|
7
7
|
children?: React.ReactNode;
|
|
8
8
|
hitSlop?: any;
|
|
9
9
|
disabled?: boolean;
|
|
10
|
+
enableHaptics?: boolean;
|
|
10
11
|
}
|
|
11
12
|
declare const TouchableScale: React.NamedExoticComponent<TouchableScaleProps>;
|
|
12
13
|
export default TouchableScale;
|
|
@@ -1,47 +1,45 @@
|
|
|
1
1
|
import React, { useRef } from 'react';
|
|
2
|
-
import { Animated, Pressable
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
style,
|
|
7
|
-
{ opacity: pressed ? 0.75 : 1 },
|
|
8
|
-
]} {...rest}>
|
|
9
|
-
{children}
|
|
10
|
-
</Pressable>);
|
|
11
|
-
}
|
|
2
|
+
import { Animated, Pressable } from 'react-native';
|
|
3
|
+
import { triggerNativeHaptic } from '../native/NativeInspector';
|
|
4
|
+
const AnimatedPressable = Animated.createAnimatedComponent(Pressable);
|
|
5
|
+
const TouchableScale = React.memo(function TouchableScale({ onPress, onLongPress, style, children, hitSlop, disabled, enableHaptics = true, accessible, accessibilityRole, accessibilityLabel, accessibilityHint, accessibilityState, accessibilityValue, ...rest }) {
|
|
12
6
|
const scale = useRef(new Animated.Value(1)).current;
|
|
13
7
|
const opacity = useRef(new Animated.Value(1)).current;
|
|
14
|
-
const
|
|
8
|
+
const handlePressIn = () => {
|
|
9
|
+
if (disabled)
|
|
10
|
+
return;
|
|
11
|
+
if (enableHaptics) {
|
|
12
|
+
triggerNativeHaptic('light');
|
|
13
|
+
}
|
|
15
14
|
Animated.parallel([
|
|
16
|
-
Animated.
|
|
17
|
-
toValue:
|
|
18
|
-
|
|
19
|
-
tension: 120,
|
|
15
|
+
Animated.timing(scale, {
|
|
16
|
+
toValue: 0.94,
|
|
17
|
+
duration: 50,
|
|
20
18
|
useNativeDriver: true,
|
|
21
19
|
}),
|
|
22
20
|
Animated.timing(opacity, {
|
|
23
|
-
toValue:
|
|
24
|
-
duration:
|
|
21
|
+
toValue: 0.85,
|
|
22
|
+
duration: 50,
|
|
25
23
|
useNativeDriver: true,
|
|
26
24
|
}),
|
|
27
25
|
]).start();
|
|
28
26
|
};
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
27
|
+
const handlePressOut = () => {
|
|
28
|
+
Animated.parallel([
|
|
29
|
+
Animated.timing(scale, {
|
|
30
|
+
toValue: 1,
|
|
31
|
+
duration: 100,
|
|
32
|
+
useNativeDriver: true,
|
|
33
|
+
}),
|
|
34
|
+
Animated.timing(opacity, {
|
|
35
|
+
toValue: 1,
|
|
36
|
+
duration: 100,
|
|
37
|
+
useNativeDriver: true,
|
|
38
|
+
}),
|
|
39
|
+
]).start();
|
|
40
40
|
};
|
|
41
|
-
return (<
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
</Animated.View>
|
|
45
|
-
</Pressable>);
|
|
41
|
+
return (<AnimatedPressable accessible={accessible} accessibilityRole={accessibilityRole} accessibilityLabel={accessibilityLabel} accessibilityHint={accessibilityHint} accessibilityState={accessibilityState} accessibilityValue={accessibilityValue} disabled={disabled} style={[style, { opacity, transform: [{ scale }] }]} onPressIn={handlePressIn} onPressOut={handlePressOut} onPress={onPress} onLongPress={onLongPress} hitSlop={hitSlop} {...rest}>
|
|
42
|
+
{children}
|
|
43
|
+
</AnimatedPressable>);
|
|
46
44
|
});
|
|
47
45
|
export default TouchableScale;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Method } from '../types';
|
|
2
|
-
|
|
3
|
-
export declare const STATUS_FILTERS: StatusFilter[];
|
|
2
|
+
export declare const STATUS_FILTERS: string[];
|
|
4
3
|
export declare const METHOD_COLORS: Record<Method, string>;
|
|
5
4
|
export declare const DOMAIN_COLORS: string[];
|
|
6
5
|
export declare const DURATION_FAST_MS = 200;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const LIB_VERSION = "
|
|
1
|
+
export declare const LIB_VERSION = "2.0.1";
|
|
@@ -17,6 +17,10 @@ export declare const getCollectionEnabled: () => boolean;
|
|
|
17
17
|
* Useful for custom analytics wrappers or testing.
|
|
18
18
|
*/
|
|
19
19
|
export declare const logAnalyticsEvent: (name: string, params?: Record<string, any>, userProperties?: Record<string, any>) => void;
|
|
20
|
+
/**
|
|
21
|
+
* Returns true if analytics logging is active (always ready for custom and Firebase events).
|
|
22
|
+
*/
|
|
20
23
|
export declare const isAnalyticsConnected: () => boolean;
|
|
24
|
+
export declare const isFirebaseAnalyticsPatched: () => boolean;
|
|
21
25
|
export declare const setupAnalyticsLogger: (analyticsInstance: any) => void;
|
|
22
26
|
export declare const autoSetupAnalyticsLogger: () => boolean;
|