Skip to content

Commit 84bb888

Browse files
committed
Merge branch 'indexed-stack-feature' into develop
2 parents 65f297c + f00f3fb commit 84bb888

File tree

16 files changed

+535
-353
lines changed

16 files changed

+535
-353
lines changed

src/core/refs.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { createRef } from 'react';
2+
import type { IndexedStackMethods } from '../ui/components/common/IndexedStack';
3+
4+
export enum DebuggerVisibility {
5+
Hidden = -1,
6+
Bubble = 0,
7+
Panel = 1,
8+
}
9+
10+
export enum PanelState {
11+
Network = 0,
12+
Console = 1,
13+
Details = 2,
14+
}
15+
16+
export enum HeaderState {
17+
Debugger = 0,
18+
Network = 1,
19+
Console = 2,
20+
}
21+
22+
const refs = {
23+
debugger: createRef<IndexedStackMethods<DebuggerVisibility>>(),
24+
panel: createRef<IndexedStackMethods<PanelState>>(),
25+
header: createRef<IndexedStackMethods<HeaderState>>(),
26+
};
27+
28+
export default refs;

src/core/utils.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import { URL } from 'react-native-url-polyfill';
22
import { NetworkType, type HttpRequest, type LogMessage, type WebSocketRequest } from '../types';
33
import colors from '../theme/colors';
44

5-
export const getNetworkUtils = (data: HttpRequest | WebSocketRequest) => {
5+
export const getNetworkUtils = (data?: HttpRequest | WebSocketRequest) => {
6+
if (!data || !data.url) return {};
7+
68
const isHttp = data?.type !== NetworkType.WS;
79
const requestUrl = new URL(data.url);
810

src/types/common.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,7 @@ export interface NetworkRequest {
2323
export type DetailTab = 'overview' | 'headers' | 'request' | 'response' | 'messages' | 'logMessage';
2424

2525
export interface DebuggerState {
26-
visibility: 'hidden' | 'bubble' | 'panel';
2726
position: 'top' | 'bottom';
28-
selectedPanel: DebuggerPanel | null;
2927
detailsData:
3028
| {
3129
type: DebuggerPanel.Network;

src/ui/Xenon.tsx

Lines changed: 21 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
import { enableMapSet } from 'immer';
2-
import { createRef, memo, useImperativeHandle, useMemo, useRef } from 'react';
3-
import { Animated, Platform, StyleSheet, useWindowDimensions } from 'react-native';
2+
import { createRef, memo, useImperativeHandle, useMemo } from 'react';
3+
import { Platform, StyleSheet, useWindowDimensions } from 'react-native';
44
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
5+
import { FullWindowOverlay } from 'react-native-screens';
56
import { useImmer } from 'use-immer';
67
import MainContext from '../contexts/MainContext';
7-
import { getVerticalSafeMargin } from '../core/utils';
8+
import refs, { DebuggerVisibility } from '../core/refs';
89
import { useConsoleInterceptor, useNetworkInterceptor } from '../hooks';
910
import colors from '../theme/colors';
10-
import { DebuggerPanel, type DebuggerState } from '../types';
11-
import { Bubble, ConsolePanel, DebuggerHeader, DetailsViewer, NetworkPanel } from './components';
12-
import { FullWindowOverlay } from 'react-native-screens';
11+
import { type DebuggerState } from '../types';
12+
import { Bubble } from './components';
13+
import IndexedStack from './components/common/IndexedStack';
14+
import Header from './components/headers/Header';
15+
import Panel from './components/panels/Panel';
1316

1417
namespace Xenon {
1518
interface Methods {
@@ -57,20 +60,12 @@ namespace Xenon {
5760
idleBubbleOpacity = 0.5,
5861
}: Props) => {
5962
const { width, height } = useWindowDimensions();
60-
const pan = useRef(new Animated.ValueXY({ x: 0, y: getVerticalSafeMargin(height) }));
6163

6264
const [debuggerState, setDebuggerState] = useImmer<DebuggerState>({
63-
visibility: 'hidden',
6465
position: 'bottom',
65-
selectedPanel: DebuggerPanel.Network,
6666
detailsData: null,
6767
});
6868

69-
const detailsShown = useMemo(
70-
() => !debuggerState.selectedPanel && !!debuggerState.detailsData,
71-
[debuggerState.detailsData, debuggerState.selectedPanel],
72-
);
73-
7469
const containerStyle = useMemo(
7570
() => [
7671
styles.container,
@@ -84,55 +79,50 @@ namespace Xenon {
8479
});
8580

8681
const consoleInterceptor = useConsoleInterceptor({
82+
// Disable console log tracking in development to prevent infinite loops
83+
// autoEnabled: __DEV__ ? false : autoInspectConsoleEnabled,
8784
autoEnabled: autoInspectConsoleEnabled,
8885
});
8986

9087
useImperativeHandle(ref, () => {
91-
const changeVisibility = (condition: boolean, value: DebuggerState['visibility']) => {
88+
const changeVisibility = (condition: boolean, value: DebuggerVisibility) => {
9289
if (!condition) return;
9390

94-
setDebuggerState(draft => {
95-
draft.visibility = value;
96-
});
91+
refs.debugger.current?.setCurrentIndex(value);
9792
};
9893

9994
return {
10095
isVisible() {
101-
return debuggerState.visibility !== 'hidden';
96+
return refs.debugger.current?.getCurrentIndex() !== DebuggerVisibility.Hidden;
10297
},
10398
show() {
104-
changeVisibility(!this.isVisible(), 'bubble');
99+
changeVisibility(!this.isVisible(), DebuggerVisibility.Bubble);
105100
},
106101
hide() {
107-
changeVisibility(this.isVisible(), 'hidden');
102+
changeVisibility(this.isVisible(), DebuggerVisibility.Hidden);
108103
},
109104
};
110-
}, [debuggerState.visibility, setDebuggerState]);
105+
}, []);
111106

112107
return (
113108
<MainContext.Provider
114109
value={{ debuggerState, setDebuggerState, networkInterceptor, consoleInterceptor }}
115110
>
116-
{debuggerState.visibility === 'bubble' && (
111+
<IndexedStack defaultIndex={DebuggerVisibility.Hidden} id="debugger" ref={refs.debugger}>
117112
<Bubble
118113
bubbleSize={bubbleSize}
119114
idleBubbleOpacity={idleBubbleOpacity}
120-
pan={pan}
121115
screenWidth={width}
122116
screenHeight={height}
123117
/>
124-
)}
125118

126-
{debuggerState.visibility === 'panel' && (
127119
<SafeAreaProvider style={containerStyle}>
128120
<SafeAreaView style={styles.safeArea}>
129-
<DebuggerHeader />
130-
{debuggerState.selectedPanel === DebuggerPanel.Network && <NetworkPanel />}
131-
{debuggerState.selectedPanel === DebuggerPanel.Console && <ConsolePanel />}
132-
{detailsShown && <DetailsViewer />}
121+
<Header />
122+
<Panel />
133123
</SafeAreaView>
134124
</SafeAreaProvider>
135-
)}
125+
</IndexedStack>
136126
</MainContext.Provider>
137127
);
138128
},
Lines changed: 96 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
import { useContext, useMemo, useRef, useState, type MutableRefObject } from 'react';
1+
import { forwardRef, useMemo, useRef, useState } from 'react';
22
import {
33
Animated,
44
PanResponder,
55
StyleSheet,
66
View,
77
type PanResponderGestureState,
8+
type StyleProp,
9+
type ViewStyle,
810
} from 'react-native';
9-
import { MainContext } from '../../../contexts';
11+
import refs, { DebuggerVisibility } from '../../../core/refs';
1012
import { clamp, getVerticalSafeMargin } from '../../../core/utils';
1113
import colors from '../../../theme/colors';
1214
import icons from '../../../theme/icons';
@@ -15,104 +17,106 @@ import Icon from '../common/Icon';
1517
interface BubbleProps {
1618
bubbleSize: number;
1719
idleBubbleOpacity: number;
18-
pan: MutableRefObject<Animated.ValueXY>;
1920
screenWidth: number;
2021
screenHeight: number;
22+
style?: StyleProp<ViewStyle>;
2123
}
2224

23-
export default function Bubble({
24-
bubbleSize,
25-
idleBubbleOpacity,
26-
pan,
27-
screenWidth,
28-
screenHeight,
29-
}: BubbleProps) {
30-
const { setDebuggerState } = useContext(MainContext)!;
31-
const [idleOpacity, setIdleOpacity] = useState(idleBubbleOpacity);
32-
const opacityTimer = useRef<NodeJS.Timeout | null>(null);
33-
34-
const panResponder = useMemo(() => {
35-
const clearTimer = () => {
36-
if (opacityTimer.current) clearTimeout(opacityTimer.current);
37-
opacityTimer.current = null;
38-
};
39-
40-
return PanResponder.create({
41-
onStartShouldSetPanResponder: () => true,
42-
onMoveShouldSetPanResponder: () => true,
43-
onPanResponderGrant: () => {
44-
pan.current.setOffset({
45-
// @ts-ignore
46-
x: pan.current.x._value,
47-
// @ts-ignore
48-
y: pan.current.y._value,
49-
});
50-
51-
pan.current.setValue({ x: 0, y: 0 });
52-
53-
clearTimer();
54-
setIdleOpacity(1);
55-
},
56-
onPanResponderMove: Animated.event([null, { dx: pan.current.x, dy: pan.current.y }], {
57-
useNativeDriver: false,
58-
}),
59-
onPanResponderRelease: (_, gesture: PanResponderGestureState) => {
60-
const isTapGesture =
61-
gesture.dx > -10 && gesture.dx < 10 && gesture.dy > -10 && gesture.dy < 10;
62-
63-
if (isTapGesture)
64-
setDebuggerState(draft => {
65-
draft.visibility = 'panel';
66-
});
25+
const Bubble = forwardRef<View, BubbleProps>(
26+
({ bubbleSize, idleBubbleOpacity, screenWidth, screenHeight, style }, ref) => {
27+
const [idleOpacity, setIdleOpacity] = useState(idleBubbleOpacity);
28+
const pan = useRef(new Animated.ValueXY({ x: 0, y: getVerticalSafeMargin(screenHeight) }));
29+
const opacityTimer = useRef<NodeJS.Timeout | null>(null);
6730

68-
pan.current.flattenOffset();
31+
const panResponder = useMemo(() => {
32+
const clearTimer = () => {
33+
if (opacityTimer.current) clearTimeout(opacityTimer.current);
34+
opacityTimer.current = null;
35+
};
6936

70-
const finalX =
71-
gesture.moveX < (screenWidth - bubbleSize) / 2 ? 0 : screenWidth - bubbleSize;
37+
const blur = () => {
38+
opacityTimer.current = setTimeout(() => {
39+
setIdleOpacity(0.5);
40+
clearTimer();
41+
}, 1000);
42+
};
7243

73-
const verticalSafeMargin = getVerticalSafeMargin(screenHeight);
44+
return PanResponder.create({
45+
onStartShouldSetPanResponder: () => true,
46+
onMoveShouldSetPanResponder: () => true,
47+
onPanResponderGrant: () => {
48+
pan.current.setOffset({
49+
// @ts-ignore
50+
x: pan.current.x._value,
51+
// @ts-ignore
52+
y: pan.current.y._value,
53+
});
7454

75-
const finalY = clamp(
76-
gesture.moveY,
77-
verticalSafeMargin,
78-
screenHeight - verticalSafeMargin - bubbleSize,
79-
);
55+
pan.current.setValue({ x: 0, y: 0 });
8056

81-
Animated.spring(pan.current, {
82-
toValue: { x: finalX, y: finalY },
57+
clearTimer();
58+
setIdleOpacity(1);
59+
},
60+
onPanResponderMove: Animated.event([null, { dx: pan.current.x, dy: pan.current.y }], {
8361
useNativeDriver: false,
84-
}).start(({ finished }) => {
85-
if (!finished) return;
86-
87-
opacityTimer.current = setTimeout(() => {
88-
setIdleOpacity(0.5);
89-
clearTimer();
90-
}, 1000);
91-
});
92-
},
93-
});
94-
}, [bubbleSize, pan, screenHeight, screenWidth, setDebuggerState]);
95-
96-
return (
97-
<View style={styles.bubbleBackdrop}>
98-
<Animated.View
99-
{...panResponder.panHandlers}
100-
style={[
101-
styles.bubble,
102-
{
103-
width: bubbleSize,
104-
height: bubbleSize,
105-
borderRadius: bubbleSize / 2,
106-
transform: pan.current.getTranslateTransform(),
107-
opacity: idleOpacity,
108-
},
109-
]}
110-
>
111-
<Icon source={icons.bug} size={bubbleSize * 0.65} />
112-
</Animated.View>
113-
</View>
114-
);
115-
}
62+
}),
63+
onPanResponderRelease: (_, gesture: PanResponderGestureState) => {
64+
pan.current.flattenOffset();
65+
66+
const isTapGesture =
67+
gesture.dx > -10 && gesture.dx < 10 && gesture.dy > -10 && gesture.dy < 10;
68+
if (isTapGesture) {
69+
refs.debugger.current?.setCurrentIndex(DebuggerVisibility.Panel);
70+
}
71+
72+
if (gesture.moveX === 0 && gesture.moveY === 0) {
73+
blur();
74+
return;
75+
}
76+
77+
const finalX =
78+
gesture.moveX < (screenWidth - bubbleSize) / 2 ? 0 : screenWidth - bubbleSize;
79+
80+
const verticalSafeMargin = getVerticalSafeMargin(screenHeight);
81+
82+
const finalY = clamp(
83+
gesture.moveY,
84+
verticalSafeMargin,
85+
screenHeight - verticalSafeMargin - bubbleSize,
86+
);
87+
88+
Animated.spring(pan.current, {
89+
toValue: { x: finalX, y: finalY },
90+
useNativeDriver: false,
91+
}).start(({ finished }) => {
92+
if (!finished) return;
93+
blur();
94+
});
95+
},
96+
});
97+
}, [bubbleSize, pan, screenHeight, screenWidth]);
98+
99+
return (
100+
<View ref={ref} style={[styles.bubbleBackdrop, style]}>
101+
<Animated.View
102+
{...panResponder.panHandlers}
103+
style={[
104+
styles.bubble,
105+
{
106+
width: bubbleSize,
107+
height: bubbleSize,
108+
borderRadius: bubbleSize / 2,
109+
transform: pan.current.getTranslateTransform(),
110+
opacity: idleOpacity,
111+
},
112+
]}
113+
>
114+
<Icon source={icons.bug} size={bubbleSize * 0.65} />
115+
</Animated.View>
116+
</View>
117+
);
118+
},
119+
);
116120

117121
const styles = StyleSheet.create({
118122
bubbleBackdrop: {
@@ -127,3 +131,5 @@ const styles = StyleSheet.create({
127131
alignItems: 'center',
128132
},
129133
});
134+
135+
export default Bubble;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { StyleSheet, View } from 'react-native';
2+
import colors from '../../../theme/colors';
3+
4+
export default function Divider({ type }: { type: 'horizontal' | 'vertical' }) {
5+
return (
6+
<View
7+
style={{
8+
[type === 'horizontal' ? 'height' : 'width']: StyleSheet.hairlineWidth,
9+
backgroundColor: colors.gray,
10+
}}
11+
/>
12+
);
13+
}

0 commit comments

Comments
 (0)