react-native-debug-toolkit 0.1.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.
@@ -0,0 +1,491 @@
1
+ import React, { Component } from 'react'
2
+ import {
3
+ View,
4
+ Text,
5
+ StyleSheet,
6
+ Animated,
7
+ PanResponder,
8
+ Dimensions,
9
+ Pressable,
10
+ SafeAreaView,
11
+ Settings,
12
+ } from 'react-native'
13
+ import { IconRadius, DebugColors } from '../utils/DebugConst'
14
+ import SubViewHTTPLogs from './SubViewHTTPLogs'
15
+ import TabView from './TabView'
16
+
17
+ const { width: screenWidth, height: screenHeight } = Dimensions.get('window')
18
+
19
+ export default class FloatPanelView extends Component {
20
+ constructor(props) {
21
+ super(props)
22
+
23
+ // Calculate initial position (right edge, middle of screen)
24
+ const initialPosition = {
25
+ x: screenWidth - IconRadius - 20,
26
+ y: screenHeight / 2 - IconRadius / 2,
27
+ }
28
+
29
+ this.state = {
30
+ isOpen: false,
31
+ toFloat: true,
32
+ pan: new Animated.ValueXY(initialPosition),
33
+ scale: new Animated.Value(1),
34
+ lastPosition: initialPosition,
35
+ activeTab: 0,
36
+ panelTranslateY: new Animated.Value(screenHeight),
37
+ backdropOpacity: new Animated.Value(0),
38
+ }
39
+
40
+ this.gestureResponder = PanResponder.create({
41
+ onStartShouldSetPanResponder: () => true,
42
+ onMoveShouldSetPanResponder: () => true,
43
+ onPanResponderGrant: this._handlePanResponderGrant,
44
+ onPanResponderMove: this._handlePanResponderMove,
45
+ onPanResponderRelease: this._handlePanResponderRelease,
46
+ onPanResponderTerminate: this._handlePanResponderRelease,
47
+ })
48
+
49
+ this.panelResponder = PanResponder.create({
50
+ onStartShouldSetPanResponder: () => true,
51
+ onMoveShouldSetPanResponder: (_, gestureState) => {
52
+ // Only respond to downward swipes
53
+ return gestureState.dy > 5
54
+ },
55
+ onPanResponderMove: (_, gestureState) => {
56
+ if (gestureState.dy > 0) {
57
+ this.state.panelTranslateY.setValue(gestureState.dy)
58
+ // Fade backdrop based on drag distance
59
+ const newOpacity = Math.max(0, 1 - gestureState.dy / 200)
60
+ this.state.backdropOpacity.setValue(newOpacity)
61
+ }
62
+ },
63
+ onPanResponderRelease: (_, gestureState) => {
64
+ if (gestureState.dy > 100) {
65
+ this._closePanel()
66
+ } else {
67
+ Animated.spring(this.state.panelTranslateY, {
68
+ toValue: 0,
69
+ friction: 8,
70
+ tension: 50,
71
+ useNativeDriver: true,
72
+ }).start()
73
+
74
+ Animated.timing(this.state.backdropOpacity, {
75
+ toValue: 1,
76
+ duration: 200,
77
+ useNativeDriver: true,
78
+ }).start()
79
+ }
80
+ },
81
+ })
82
+ }
83
+
84
+ async componentDidMount() {
85
+ try {
86
+ const savedPosition = Settings.get('@debug_toolkit_position')
87
+
88
+ if (savedPosition) {
89
+ const position = JSON.parse(savedPosition)
90
+
91
+ // Ensure position is within bounds
92
+ const boundedPosition = {
93
+ x: Math.max(0, Math.min(position.x, screenWidth - IconRadius)),
94
+ y: Math.max(0, Math.min(position.y, screenHeight - IconRadius)),
95
+ }
96
+
97
+ this.setState({ lastPosition: boundedPosition })
98
+ this.state.pan.setValue(boundedPosition)
99
+ }
100
+ } catch (error) {
101
+ console.error('Failed to load debug toolkit position:', error)
102
+ }
103
+ }
104
+
105
+ _handlePanResponderGrant = () => {
106
+ this.setState({ toFloat: true })
107
+ Animated.spring(this.state.scale, {
108
+ toValue: 1.2,
109
+ friction: 5,
110
+ useNativeDriver: true,
111
+ }).start()
112
+ }
113
+
114
+ _handlePanResponderMove = (e, gestureState) => {
115
+ const { dx, dy } = gestureState
116
+ const newX = this.state.lastPosition.x + dx
117
+ const newY = this.state.lastPosition.y + dy
118
+
119
+ // Keep within screen bounds while dragging
120
+ const boundedX = Math.max(0, Math.min(newX, screenWidth - IconRadius))
121
+ const boundedY = Math.max(0, Math.min(newY, screenHeight - IconRadius))
122
+
123
+ this.state.pan.setValue({
124
+ x: boundedX,
125
+ y: boundedY,
126
+ })
127
+ }
128
+
129
+ _handlePanResponderRelease = async (e, gestureState) => {
130
+ const { dx, dy } = gestureState
131
+
132
+ // If it's a tap (minimal movement)
133
+ if (Math.abs(dx) < 5 && Math.abs(dy) < 5) {
134
+ this._togglePanel()
135
+ return
136
+ }
137
+
138
+ // Animate button scale back to normal
139
+ Animated.spring(this.state.scale, {
140
+ toValue: 1,
141
+ friction: 5,
142
+ useNativeDriver: true,
143
+ }).start()
144
+
145
+ // Calculate final position
146
+ let newX = this.state.lastPosition.x + dx
147
+ let newY = this.state.lastPosition.y + dy
148
+
149
+ // Keep within screen bounds
150
+ newX = Math.max(0, Math.min(newX, screenWidth - IconRadius))
151
+ newY = Math.max(0, Math.min(newY, screenHeight - IconRadius))
152
+
153
+ const newPosition = { x: newX, y: newY }
154
+
155
+ // Save position
156
+ try {
157
+ Settings.set({
158
+ '@debug_toolkit_position': JSON.stringify(newPosition),
159
+ })
160
+ } catch (error) {
161
+ console.error('Failed to save debug toolkit position:', error)
162
+ }
163
+
164
+ this.setState({
165
+ lastPosition: newPosition,
166
+ toFloat: true,
167
+ })
168
+ }
169
+
170
+ _togglePanel = () => {
171
+ const { isOpen } = this.state
172
+
173
+ if (isOpen) {
174
+ this._closePanel()
175
+ } else {
176
+ this._openPanel()
177
+ }
178
+ }
179
+
180
+ _openPanel = () => {
181
+ this.setState({ isOpen: true, toFloat: false }, () => {
182
+ Animated.parallel([
183
+ Animated.spring(this.state.panelTranslateY, {
184
+ toValue: 0,
185
+ friction: 8,
186
+ tension: 65,
187
+ useNativeDriver: true,
188
+ }),
189
+ Animated.timing(this.state.backdropOpacity, {
190
+ toValue: 1,
191
+ duration: 250,
192
+ useNativeDriver: true,
193
+ }),
194
+ ]).start()
195
+ })
196
+ }
197
+
198
+ _closePanel = () => {
199
+ Animated.parallel([
200
+ Animated.spring(this.state.panelTranslateY, {
201
+ toValue: screenHeight,
202
+ friction: 8,
203
+ tension: 65,
204
+ useNativeDriver: true,
205
+ }),
206
+ Animated.timing(this.state.backdropOpacity, {
207
+ toValue: 0,
208
+ duration: 200,
209
+ useNativeDriver: true,
210
+ }),
211
+ ]).start(() => {
212
+ this.setState({ isOpen: false, toFloat: true })
213
+ })
214
+ }
215
+
216
+ renderFloatBtn() {
217
+ const { isOpen, toFloat } = this.state
218
+
219
+ if (!isOpen && toFloat) {
220
+ return (
221
+ <Animated.View
222
+ {...this.gestureResponder.panHandlers}
223
+ style={[
224
+ styles.floatBtn,
225
+ {
226
+ transform: [
227
+ { translateX: this.state.pan.x },
228
+ { translateY: this.state.pan.y },
229
+ { scale: this.state.scale },
230
+ ],
231
+ },
232
+ ]}>
233
+ <Pressable onPress={this._togglePanel} style={styles.floatBtnInner}>
234
+ <View style={styles.indicatorDot} />
235
+ </Pressable>
236
+ </Animated.View>
237
+ )
238
+ }
239
+
240
+ return null
241
+ }
242
+
243
+ getFeatureTabs() {
244
+ const { features } = this.props
245
+
246
+ if (!features || !features.length) {
247
+ return []
248
+ }
249
+
250
+ return features.map((feature) => ({
251
+ label: feature.label,
252
+ id: feature.name,
253
+ }))
254
+ }
255
+
256
+ renderFeatureContent(featureId) {
257
+ const { features } = this.props
258
+
259
+ if (!features || !features.length) {
260
+ return <Text style={styles.emptyText}>No features available</Text>
261
+ }
262
+
263
+ const feature = features.find((f) => f.name === featureId)
264
+
265
+ if (!feature) {
266
+ return <Text style={styles.emptyText}>Feature not found</Text>
267
+ }
268
+
269
+ const data = feature.getData()
270
+
271
+ // Special handling for network logs
272
+ if (feature.name === 'network') {
273
+ return <SubViewHTTPLogs logs={data} />
274
+ }
275
+
276
+ // Generic fallback for other feature types
277
+ return (
278
+ <View style={styles.genericContent}>
279
+ <View style={styles.contentHeader}>
280
+ <Text style={styles.contentTitle}>{feature.label}</Text>
281
+ </View>
282
+ <Text style={styles.jsonContent}>{JSON.stringify(data, null, 2)}</Text>
283
+ </View>
284
+ )
285
+ }
286
+
287
+ renderContent() {
288
+ const tabs = this.getFeatureTabs()
289
+ const { activeTab } = this.state
290
+
291
+ if (!tabs.length) {
292
+ return <Text style={styles.emptyText}>No debug features available</Text>
293
+ }
294
+
295
+ return this.renderFeatureContent(tabs[activeTab].id)
296
+ }
297
+
298
+ renderPanel() {
299
+ const { isOpen, panelTranslateY, backdropOpacity } = this.state
300
+ const tabs = this.getFeatureTabs()
301
+
302
+ if (!isOpen) {
303
+ return null
304
+ }
305
+
306
+ return (
307
+ <View style={styles.panelContainer}>
308
+ <Animated.View style={[styles.backdrop, { opacity: backdropOpacity }]}>
309
+ <Pressable
310
+ style={styles.backdropPressable}
311
+ onPress={this._closePanel}
312
+ />
313
+ </Animated.View>
314
+ <Animated.View
315
+ style={[
316
+ styles.panel,
317
+ { transform: [{ translateY: panelTranslateY }] },
318
+ ]}>
319
+ <View {...this.panelResponder.panHandlers} style={styles.dragHandle}>
320
+ <View style={styles.dragIndicator} />
321
+ </View>
322
+ <SafeAreaView style={styles.panelContent}>
323
+ <View style={styles.header}>
324
+ <Text style={styles.headerTitle}>Debug Panel</Text>
325
+ <Pressable onPress={this._closePanel} style={styles.closeButton}>
326
+ <Text style={styles.closeButtonText}>×</Text>
327
+ </Pressable>
328
+ </View>
329
+ <TabView
330
+ tabs={tabs}
331
+ activeTab={this.state.activeTab}
332
+ onTabChange={(index) => this.setState({ activeTab: index })}>
333
+ {this.renderContent()}
334
+ </TabView>
335
+ </SafeAreaView>
336
+ </Animated.View>
337
+ </View>
338
+ )
339
+ }
340
+
341
+ render() {
342
+ return (
343
+ <View style={styles.container} pointerEvents='box-none'>
344
+ {this.renderFloatBtn()}
345
+ {this.renderPanel()}
346
+ </View>
347
+ )
348
+ }
349
+ }
350
+
351
+ const styles = StyleSheet.create({
352
+ container: {
353
+ position: 'absolute',
354
+ top: 0,
355
+ left: 0,
356
+ right: 0,
357
+ bottom: 0,
358
+ zIndex: 999,
359
+ },
360
+ floatBtn: {
361
+ position: 'absolute',
362
+ width: IconRadius,
363
+ height: IconRadius,
364
+ borderRadius: IconRadius / 2,
365
+ backgroundColor: 'rgba(255, 255, 255, 0.7)',
366
+ borderWidth: 1,
367
+ borderColor: DebugColors.blue,
368
+ elevation: 5,
369
+ shadowColor: '#000',
370
+ shadowOffset: { width: 0, height: 2 },
371
+ shadowOpacity: 0.25,
372
+ shadowRadius: 3.84,
373
+ },
374
+ floatBtnInner: {
375
+ width: '100%',
376
+ height: '100%',
377
+ alignItems: 'center',
378
+ justifyContent: 'center',
379
+ },
380
+ indicatorDot: {
381
+ width: 12,
382
+ height: 12,
383
+ borderRadius: 6,
384
+ backgroundColor: DebugColors.blue,
385
+ },
386
+ panelContainer: {
387
+ position: 'absolute',
388
+ top: 0,
389
+ left: 0,
390
+ right: 0,
391
+ bottom: 0,
392
+ justifyContent: 'flex-end',
393
+ },
394
+ backdrop: {
395
+ position: 'absolute',
396
+ top: 0,
397
+ left: 0,
398
+ right: 0,
399
+ bottom: 0,
400
+ backgroundColor: 'rgba(0,0,0,0.5)',
401
+ },
402
+ backdropPressable: {
403
+ flex: 1,
404
+ },
405
+ panel: {
406
+ width: '100%',
407
+ height: '90%',
408
+ backgroundColor: DebugColors.white,
409
+ borderTopLeftRadius: 24,
410
+ borderTopRightRadius: 24,
411
+ overflow: 'hidden',
412
+ elevation: 24,
413
+ shadowColor: '#000',
414
+ shadowOffset: { width: 0, height: -5 },
415
+ shadowOpacity: 0.3,
416
+ shadowRadius: 10.0,
417
+ },
418
+ dragHandle: {
419
+ width: '100%',
420
+ height: 30,
421
+ alignItems: 'center',
422
+ justifyContent: 'center',
423
+ backgroundColor: DebugColors.white,
424
+ },
425
+ dragIndicator: {
426
+ width: 40,
427
+ height: 5,
428
+ borderRadius: 3,
429
+ backgroundColor: '#CCCCCC',
430
+ },
431
+ panelContent: {
432
+ flex: 1,
433
+ },
434
+ header: {
435
+ flexDirection: 'row',
436
+ justifyContent: 'space-between',
437
+ alignItems: 'center',
438
+ paddingHorizontal: 20,
439
+ paddingBottom: 4,
440
+ borderBottomWidth: 1,
441
+ borderBottomColor: DebugColors.border,
442
+ },
443
+ headerTitle: {
444
+ fontSize: 18,
445
+ fontWeight: 'bold',
446
+ color: DebugColors.text,
447
+ },
448
+ closeButton: {
449
+ padding: 8,
450
+ },
451
+ closeButtonText: {
452
+ fontSize: 24,
453
+ color: DebugColors.text,
454
+ fontWeight: 'bold',
455
+ },
456
+ emptyText: {
457
+ padding: 20,
458
+ textAlign: 'center',
459
+ color: '#999',
460
+ },
461
+ genericContent: {
462
+ padding: 15,
463
+ flex: 1,
464
+ },
465
+ contentHeader: {
466
+ flexDirection: 'row',
467
+ justifyContent: 'space-between',
468
+ alignItems: 'center',
469
+ marginBottom: 10,
470
+ },
471
+ contentTitle: {
472
+ fontSize: 16,
473
+ fontWeight: 'bold',
474
+ color: DebugColors.text,
475
+ },
476
+ copyButton: {
477
+ paddingHorizontal: 12,
478
+ paddingVertical: 6,
479
+ backgroundColor: DebugColors.blue,
480
+ borderRadius: 4,
481
+ },
482
+ copyButtonText: {
483
+ color: '#FFF',
484
+ fontSize: 14,
485
+ },
486
+ jsonContent: {
487
+ fontFamily: 'monospace',
488
+ fontSize: 12,
489
+ color: DebugColors.text,
490
+ },
491
+ })