react-native-debug-toolkit 0.6.3 → 2.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.
Files changed (283) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +150 -212
  3. package/lib/commonjs/components/ClipboardTab.js +92 -0
  4. package/lib/commonjs/components/ClipboardTab.js.map +1 -0
  5. package/lib/commonjs/components/ConsoleLogTab.js +295 -0
  6. package/lib/commonjs/components/ConsoleLogTab.js.map +1 -0
  7. package/lib/commonjs/components/EnvironmentTab.js +288 -0
  8. package/lib/commonjs/components/EnvironmentTab.js.map +1 -0
  9. package/lib/commonjs/components/FloatPanelView.js +797 -0
  10. package/lib/commonjs/components/FloatPanelView.js.map +1 -0
  11. package/lib/commonjs/components/NavigationLogTab.js +131 -0
  12. package/lib/commonjs/components/NavigationLogTab.js.map +1 -0
  13. package/lib/commonjs/components/NetworkLogTab.js +575 -0
  14. package/lib/commonjs/components/NetworkLogTab.js.map +1 -0
  15. package/lib/commonjs/components/ThirdPartyLibsTab.js +97 -0
  16. package/lib/commonjs/components/ThirdPartyLibsTab.js.map +1 -0
  17. package/lib/commonjs/components/TrackLogTab.js +372 -0
  18. package/lib/commonjs/components/TrackLogTab.js.map +1 -0
  19. package/lib/commonjs/components/ZustandLogTab.js +451 -0
  20. package/lib/commonjs/components/ZustandLogTab.js.map +1 -0
  21. package/lib/commonjs/components/shared/CollapsibleSection.js +84 -0
  22. package/lib/commonjs/components/shared/CollapsibleSection.js.map +1 -0
  23. package/lib/commonjs/components/shared/CopyButton.js +64 -0
  24. package/lib/commonjs/components/shared/CopyButton.js.map +1 -0
  25. package/lib/commonjs/components/shared/JsonView.js +157 -0
  26. package/lib/commonjs/components/shared/JsonView.js.map +1 -0
  27. package/lib/commonjs/core/DebugToolkit.js +142 -0
  28. package/lib/commonjs/core/DebugToolkit.js.map +1 -0
  29. package/lib/commonjs/core/DebugToolkitProvider.js +64 -0
  30. package/lib/commonjs/core/DebugToolkitProvider.js.map +1 -0
  31. package/lib/commonjs/features/ClipboardFeature.js +17 -0
  32. package/lib/commonjs/features/ClipboardFeature.js.map +1 -0
  33. package/lib/commonjs/features/ConsoleLogFeature.js +98 -0
  34. package/lib/commonjs/features/ConsoleLogFeature.js.map +1 -0
  35. package/lib/commonjs/features/EnvironmentFeature.js +168 -0
  36. package/lib/commonjs/features/EnvironmentFeature.js.map +1 -0
  37. package/lib/commonjs/features/NavigationLogFeature.js +60 -0
  38. package/lib/commonjs/features/NavigationLogFeature.js.map +1 -0
  39. package/lib/commonjs/features/NetworkFeature.js +328 -0
  40. package/lib/commonjs/features/NetworkFeature.js.map +1 -0
  41. package/lib/commonjs/features/ThirdPartyLibsFeature.js +51 -0
  42. package/lib/commonjs/features/ThirdPartyLibsFeature.js.map +1 -0
  43. package/lib/commonjs/features/TrackFeature.js +55 -0
  44. package/lib/commonjs/features/TrackFeature.js.map +1 -0
  45. package/lib/commonjs/features/ZustandLogFeature.js +76 -0
  46. package/lib/commonjs/features/ZustandLogFeature.js.map +1 -0
  47. package/lib/commonjs/hooks/useNavigationLogger.js +78 -0
  48. package/lib/commonjs/hooks/useNavigationLogger.js.map +1 -0
  49. package/lib/commonjs/hooks/useSlideDetailAnimation.js +53 -0
  50. package/lib/commonjs/hooks/useSlideDetailAnimation.js.map +1 -0
  51. package/lib/commonjs/index.js +165 -0
  52. package/lib/commonjs/index.js.map +1 -0
  53. package/lib/commonjs/initialize.js +161 -0
  54. package/lib/commonjs/initialize.js.map +1 -0
  55. package/lib/commonjs/native/NativeDebugLibs.js +58 -0
  56. package/lib/commonjs/native/NativeDebugLibs.js.map +1 -0
  57. package/lib/commonjs/package.json +1 -0
  58. package/lib/commonjs/types/index.js +6 -0
  59. package/lib/commonjs/types/index.js.map +1 -0
  60. package/lib/commonjs/utils/constants.js +135 -0
  61. package/lib/commonjs/utils/constants.js.map +1 -0
  62. package/lib/commonjs/utils/copyToComputer.js +97 -0
  63. package/lib/commonjs/utils/copyToComputer.js.map +1 -0
  64. package/lib/commonjs/utils/createEventChannel.js +21 -0
  65. package/lib/commonjs/utils/createEventChannel.js.map +1 -0
  66. package/lib/commonjs/utils/createObservableStore.js +38 -0
  67. package/lib/commonjs/utils/createObservableStore.js.map +1 -0
  68. package/lib/commonjs/utils/safeStringify.js +27 -0
  69. package/lib/commonjs/utils/safeStringify.js.map +1 -0
  70. package/lib/module/components/ClipboardTab.js +86 -0
  71. package/lib/module/components/ClipboardTab.js.map +1 -0
  72. package/lib/module/components/ConsoleLogTab.js +290 -0
  73. package/lib/module/components/ConsoleLogTab.js.map +1 -0
  74. package/lib/module/components/EnvironmentTab.js +282 -0
  75. package/lib/module/components/EnvironmentTab.js.map +1 -0
  76. package/lib/module/components/FloatPanelView.js +791 -0
  77. package/lib/module/components/FloatPanelView.js.map +1 -0
  78. package/lib/module/components/NavigationLogTab.js +126 -0
  79. package/lib/module/components/NavigationLogTab.js.map +1 -0
  80. package/lib/module/components/NetworkLogTab.js +570 -0
  81. package/lib/module/components/NetworkLogTab.js.map +1 -0
  82. package/lib/module/components/ThirdPartyLibsTab.js +91 -0
  83. package/lib/module/components/ThirdPartyLibsTab.js.map +1 -0
  84. package/lib/module/components/TrackLogTab.js +367 -0
  85. package/lib/module/components/TrackLogTab.js.map +1 -0
  86. package/lib/module/components/ZustandLogTab.js +446 -0
  87. package/lib/module/components/ZustandLogTab.js.map +1 -0
  88. package/lib/module/components/shared/CollapsibleSection.js +78 -0
  89. package/lib/module/components/shared/CollapsibleSection.js.map +1 -0
  90. package/lib/module/components/shared/CopyButton.js +58 -0
  91. package/lib/module/components/shared/CopyButton.js.map +1 -0
  92. package/lib/module/components/shared/JsonView.js +152 -0
  93. package/lib/module/components/shared/JsonView.js.map +1 -0
  94. package/lib/module/core/DebugToolkit.js +137 -0
  95. package/lib/module/core/DebugToolkit.js.map +1 -0
  96. package/lib/module/core/DebugToolkitProvider.js +58 -0
  97. package/lib/module/core/DebugToolkitProvider.js.map +1 -0
  98. package/lib/module/features/ClipboardFeature.js +12 -0
  99. package/lib/module/features/ClipboardFeature.js.map +1 -0
  100. package/lib/module/features/ConsoleLogFeature.js +93 -0
  101. package/lib/module/features/ConsoleLogFeature.js.map +1 -0
  102. package/lib/module/features/EnvironmentFeature.js +164 -0
  103. package/lib/module/features/EnvironmentFeature.js.map +1 -0
  104. package/lib/module/features/NavigationLogFeature.js +54 -0
  105. package/lib/module/features/NavigationLogFeature.js.map +1 -0
  106. package/lib/module/features/NetworkFeature.js +322 -0
  107. package/lib/module/features/NetworkFeature.js.map +1 -0
  108. package/lib/module/features/ThirdPartyLibsFeature.js +46 -0
  109. package/lib/module/features/ThirdPartyLibsFeature.js.map +1 -0
  110. package/lib/module/features/TrackFeature.js +49 -0
  111. package/lib/module/features/TrackFeature.js.map +1 -0
  112. package/lib/module/features/ZustandLogFeature.js +69 -0
  113. package/lib/module/features/ZustandLogFeature.js.map +1 -0
  114. package/lib/module/hooks/useNavigationLogger.js +74 -0
  115. package/lib/module/hooks/useNavigationLogger.js.map +1 -0
  116. package/lib/module/hooks/useSlideDetailAnimation.js +50 -0
  117. package/lib/module/hooks/useSlideDetailAnimation.js.map +1 -0
  118. package/lib/module/index.js +29 -0
  119. package/lib/module/index.js.map +1 -0
  120. package/lib/module/initialize.js +156 -0
  121. package/lib/module/initialize.js.map +1 -0
  122. package/lib/module/native/NativeDebugLibs.js +54 -0
  123. package/lib/module/native/NativeDebugLibs.js.map +1 -0
  124. package/lib/module/package.json +1 -0
  125. package/lib/module/types/index.js +4 -0
  126. package/lib/module/types/index.js.map +1 -0
  127. package/lib/module/utils/constants.js +130 -0
  128. package/lib/module/utils/constants.js.map +1 -0
  129. package/lib/module/utils/copyToComputer.js +91 -0
  130. package/lib/module/utils/copyToComputer.js.map +1 -0
  131. package/lib/module/utils/createEventChannel.js +17 -0
  132. package/lib/module/utils/createEventChannel.js.map +1 -0
  133. package/lib/module/utils/createObservableStore.js +34 -0
  134. package/lib/module/utils/createObservableStore.js.map +1 -0
  135. package/lib/module/utils/safeStringify.js +23 -0
  136. package/lib/module/utils/safeStringify.js.map +1 -0
  137. package/lib/typescript/src/components/ClipboardTab.d.ts +4 -0
  138. package/lib/typescript/src/components/ClipboardTab.d.ts.map +1 -0
  139. package/lib/typescript/src/components/ConsoleLogTab.d.ts +4 -0
  140. package/lib/typescript/src/components/ConsoleLogTab.d.ts.map +1 -0
  141. package/lib/typescript/src/components/EnvironmentTab.d.ts +4 -0
  142. package/lib/typescript/src/components/EnvironmentTab.d.ts.map +1 -0
  143. package/lib/typescript/src/components/FloatPanelView.d.ts +64 -0
  144. package/lib/typescript/src/components/FloatPanelView.d.ts.map +1 -0
  145. package/lib/typescript/src/components/NavigationLogTab.d.ts +4 -0
  146. package/lib/typescript/src/components/NavigationLogTab.d.ts.map +1 -0
  147. package/lib/typescript/src/components/NetworkLogTab.d.ts +4 -0
  148. package/lib/typescript/src/components/NetworkLogTab.d.ts.map +1 -0
  149. package/lib/typescript/src/components/ThirdPartyLibsTab.d.ts +4 -0
  150. package/lib/typescript/src/components/ThirdPartyLibsTab.d.ts.map +1 -0
  151. package/lib/typescript/src/components/TrackLogTab.d.ts +4 -0
  152. package/lib/typescript/src/components/TrackLogTab.d.ts.map +1 -0
  153. package/lib/typescript/src/components/ZustandLogTab.d.ts +4 -0
  154. package/lib/typescript/src/components/ZustandLogTab.d.ts.map +1 -0
  155. package/lib/typescript/src/components/shared/CollapsibleSection.d.ts +9 -0
  156. package/lib/typescript/src/components/shared/CollapsibleSection.d.ts.map +1 -0
  157. package/lib/typescript/src/components/shared/CopyButton.d.ts +12 -0
  158. package/lib/typescript/src/components/shared/CopyButton.d.ts.map +1 -0
  159. package/lib/typescript/src/components/shared/JsonView.d.ts +6 -0
  160. package/lib/typescript/src/components/shared/JsonView.d.ts.map +1 -0
  161. package/lib/typescript/src/core/DebugToolkit.d.ts +27 -0
  162. package/lib/typescript/src/core/DebugToolkit.d.ts.map +1 -0
  163. package/lib/typescript/src/core/DebugToolkitProvider.d.ts +18 -0
  164. package/lib/typescript/src/core/DebugToolkitProvider.d.ts.map +1 -0
  165. package/lib/typescript/src/features/ClipboardFeature.d.ts +3 -0
  166. package/lib/typescript/src/features/ClipboardFeature.d.ts.map +1 -0
  167. package/lib/typescript/src/features/ConsoleLogFeature.d.ts +7 -0
  168. package/lib/typescript/src/features/ConsoleLogFeature.d.ts.map +1 -0
  169. package/lib/typescript/src/features/EnvironmentFeature.d.ts +8 -0
  170. package/lib/typescript/src/features/EnvironmentFeature.d.ts.map +1 -0
  171. package/lib/typescript/src/features/NavigationLogFeature.d.ts +8 -0
  172. package/lib/typescript/src/features/NavigationLogFeature.d.ts.map +1 -0
  173. package/lib/typescript/src/features/NetworkFeature.d.ts +28 -0
  174. package/lib/typescript/src/features/NetworkFeature.d.ts.map +1 -0
  175. package/lib/typescript/src/features/ThirdPartyLibsFeature.d.ts +3 -0
  176. package/lib/typescript/src/features/ThirdPartyLibsFeature.d.ts.map +1 -0
  177. package/lib/typescript/src/features/TrackFeature.d.ts +12 -0
  178. package/lib/typescript/src/features/TrackFeature.d.ts.map +1 -0
  179. package/lib/typescript/src/features/ZustandLogFeature.d.ts +29 -0
  180. package/lib/typescript/src/features/ZustandLogFeature.d.ts.map +1 -0
  181. package/lib/typescript/src/hooks/useNavigationLogger.d.ts +20 -0
  182. package/lib/typescript/src/hooks/useNavigationLogger.d.ts.map +1 -0
  183. package/lib/typescript/src/hooks/useSlideDetailAnimation.d.ts +11 -0
  184. package/lib/typescript/src/hooks/useSlideDetailAnimation.d.ts.map +1 -0
  185. package/lib/typescript/src/index.d.ts +26 -0
  186. package/lib/typescript/src/index.d.ts.map +1 -0
  187. package/lib/typescript/src/initialize.d.ts +51 -0
  188. package/lib/typescript/src/initialize.d.ts.map +1 -0
  189. package/lib/typescript/src/native/NativeDebugLibs.d.ts +11 -0
  190. package/lib/typescript/src/native/NativeDebugLibs.d.ts.map +1 -0
  191. package/lib/typescript/src/types/index.d.ts +112 -0
  192. package/lib/typescript/src/types/index.d.ts.map +1 -0
  193. package/lib/typescript/src/utils/constants.d.ts +96 -0
  194. package/lib/typescript/src/utils/constants.d.ts.map +1 -0
  195. package/lib/typescript/src/utils/copyToComputer.d.ts +30 -0
  196. package/lib/typescript/src/utils/copyToComputer.d.ts.map +1 -0
  197. package/lib/typescript/src/utils/createEventChannel.d.ts +7 -0
  198. package/lib/typescript/src/utils/createEventChannel.d.ts.map +1 -0
  199. package/lib/typescript/src/utils/createObservableStore.d.ts +9 -0
  200. package/lib/typescript/src/utils/createObservableStore.d.ts.map +1 -0
  201. package/lib/typescript/src/utils/safeStringify.d.ts +6 -0
  202. package/lib/typescript/src/utils/safeStringify.d.ts.map +1 -0
  203. package/package.json +63 -24
  204. package/src/components/ClipboardTab.tsx +81 -0
  205. package/src/components/ConsoleLogTab.tsx +209 -0
  206. package/src/components/EnvironmentTab.tsx +276 -0
  207. package/src/components/FloatPanelView.tsx +714 -0
  208. package/src/components/NavigationLogTab.tsx +66 -0
  209. package/src/components/NetworkLogTab.tsx +411 -0
  210. package/src/components/ThirdPartyLibsTab.tsx +63 -0
  211. package/src/components/TrackLogTab.tsx +245 -0
  212. package/src/components/ZustandLogTab.tsx +305 -0
  213. package/src/components/shared/CollapsibleSection.tsx +78 -0
  214. package/src/components/shared/CopyButton.tsx +68 -0
  215. package/src/components/shared/JsonView.tsx +125 -0
  216. package/src/core/DebugToolkit.tsx +174 -0
  217. package/src/core/DebugToolkitProvider.tsx +89 -0
  218. package/src/features/ClipboardFeature.ts +11 -0
  219. package/src/features/ConsoleLogFeature.ts +118 -0
  220. package/src/features/EnvironmentFeature.ts +194 -0
  221. package/src/features/NavigationLogFeature.ts +74 -0
  222. package/src/features/NetworkFeature.ts +488 -0
  223. package/src/features/ThirdPartyLibsFeature.ts +42 -0
  224. package/src/features/TrackFeature.ts +69 -0
  225. package/src/features/ZustandLogFeature.ts +127 -0
  226. package/src/hooks/useNavigationLogger.ts +107 -0
  227. package/src/hooks/useSlideDetailAnimation.ts +45 -0
  228. package/src/index.ts +52 -0
  229. package/src/initialize.ts +214 -0
  230. package/src/native/NativeDebugLibs.ts +74 -0
  231. package/src/types/index.ts +138 -0
  232. package/src/utils/constants.ts +91 -0
  233. package/src/utils/copyToComputer.ts +104 -0
  234. package/src/utils/createEventChannel.ts +22 -0
  235. package/src/utils/createObservableStore.ts +42 -0
  236. package/src/utils/safeStringify.ts +25 -0
  237. package/.cursor/rules/react-native.mdc +0 -41
  238. package/README.zh-CN.md +0 -230
  239. package/android/build.gradle +0 -34
  240. package/android/src/main/AndroidManifest.xml +0 -8
  241. package/android/src/main/java/com/reactnative/debuglibs/BuildTypeModule.java +0 -44
  242. package/android/src/main/java/com/reactnative/debuglibs/BuildTypePackage.java +0 -25
  243. package/android/src/main/java/com/reactnative/debuglibs/RNDebugLibsModule.java +0 -75
  244. package/android/src/main/java/com/reactnative/debuglibs/RNDebugLibsPackage.java +0 -28
  245. package/index.js +0 -41
  246. package/ios/BuildTypeModule.h +0 -9
  247. package/ios/BuildTypeModule.m +0 -42
  248. package/ios/RNDebugLibs.h +0 -10
  249. package/ios/RNDebugLibs.m +0 -79
  250. package/lib/DebugToolKit.js +0 -126
  251. package/lib/EnvironmentManager.ts +0 -80
  252. package/lib/NativeDebugLibs.js +0 -67
  253. package/lib/features/ConsoleLogFeature.js +0 -70
  254. package/lib/features/NavigationLogFeature.js +0 -45
  255. package/lib/features/NetworkFeature.js +0 -389
  256. package/lib/features/PerformanceFeature.js +0 -390
  257. package/lib/features/ThirdPartyLibsFeature.js +0 -63
  258. package/lib/features/TrackFeature.js +0 -94
  259. package/lib/features/ZustandLogFeature.js +0 -44
  260. package/lib/hooks/useNavigationLogger.js +0 -92
  261. package/lib/index.js +0 -114
  262. package/lib/navigation/NavigationLogger.js +0 -1
  263. package/lib/types/TrackTypes.ts +0 -92
  264. package/lib/utils/DebugConst.js +0 -67
  265. package/lib/utils/StorageUtils.js +0 -80
  266. package/lib/views/ConsoleLogDetails.js +0 -314
  267. package/lib/views/FloatPanelView.js +0 -697
  268. package/lib/views/HttpLogDetails.js +0 -648
  269. package/lib/views/NavigationLogDetails.js +0 -302
  270. package/lib/views/RestartModal.js +0 -75
  271. package/lib/views/SubViewConsoleLogs.js +0 -209
  272. package/lib/views/SubViewEnvironment.js +0 -73
  273. package/lib/views/SubViewHTTPLogs.js +0 -235
  274. package/lib/views/SubViewNavigationLogs.js +0 -199
  275. package/lib/views/SubViewPerformance.js +0 -515
  276. package/lib/views/SubViewThirdPartyLibs.js +0 -239
  277. package/lib/views/SubViewTrackLogs.js +0 -318
  278. package/lib/views/SubViewZustandLogs.js +0 -279
  279. package/lib/views/TabView.js +0 -66
  280. package/lib/views/TrackLogDetails.js +0 -481
  281. package/lib/views/ZustandLogDetails.js +0 -355
  282. package/react-native-debug-toolkit.podspec +0 -25
  283. package/react-native.config.js +0 -18
@@ -0,0 +1,714 @@
1
+ import React, { Component } from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ StyleSheet,
6
+ Animated,
7
+ PanResponder,
8
+ Pressable,
9
+ ScrollView,
10
+ TouchableOpacity,
11
+ Easing,
12
+ } from 'react-native';
13
+ import { Layout, Colors } from '../utils/constants';
14
+ import type { DebugFeature } from '../types';
15
+
16
+ interface FloatPanelViewProps {
17
+ features: DebugFeature[];
18
+ onClose: () => void;
19
+ onClearAll: () => void;
20
+ }
21
+
22
+ interface FloatPanelViewState {
23
+ isOpen: boolean;
24
+ toFloat: boolean;
25
+ pan: Animated.ValueXY;
26
+ scale: Animated.Value;
27
+ lastPosition: { x: number; y: number };
28
+ activeTab: number;
29
+ panelTranslateY: Animated.Value;
30
+ backdropOpacity: Animated.Value;
31
+ contentOpacity: Animated.Value;
32
+ contentTranslateX: Animated.Value;
33
+ underlineTranslateX: Animated.Value;
34
+ underlineWidth: number;
35
+ }
36
+
37
+ export class FloatPanelView extends Component<
38
+ FloatPanelViewProps,
39
+ FloatPanelViewState
40
+ > {
41
+ private tabScrollViewRef = React.createRef<ScrollView>();
42
+ private gestureResponder: ReturnType<typeof PanResponder.create>;
43
+ private panelResponder: ReturnType<typeof PanResponder.create>;
44
+ private swipeResponder: ReturnType<typeof PanResponder.create>;
45
+ private unsubscribeFns: Array<() => void> = [];
46
+ private refreshTimeout: ReturnType<typeof setTimeout> | null = null;
47
+ private isDisposed = false;
48
+ private isSwitchingTab = false;
49
+ private underlineInitialized = false;
50
+ private tabScrollOffset = 0;
51
+ private tabViewportWidth = 0;
52
+ private tabContentWidth = 0;
53
+ private tabLayouts: Array<{ x: number; width: number }> = [];
54
+
55
+ constructor(props: FloatPanelViewProps) {
56
+ super(props);
57
+ const initialPosition = {
58
+ x: Layout.screenWidth - Layout.iconSize - 16,
59
+ y: Layout.screenHeight / 2 - Layout.iconSize / 2,
60
+ };
61
+
62
+ this.state = {
63
+ isOpen: false,
64
+ toFloat: true,
65
+ pan: new Animated.ValueXY(initialPosition),
66
+ scale: new Animated.Value(1),
67
+ lastPosition: initialPosition,
68
+ activeTab: 0,
69
+ panelTranslateY: new Animated.Value(Layout.screenHeight),
70
+ backdropOpacity: new Animated.Value(0),
71
+ contentOpacity: new Animated.Value(1),
72
+ contentTranslateX: new Animated.Value(0),
73
+ underlineTranslateX: new Animated.Value(0),
74
+ underlineWidth: 0,
75
+ };
76
+
77
+ this.gestureResponder = PanResponder.create({
78
+ onStartShouldSetPanResponder: () => true,
79
+ onMoveShouldSetPanResponder: () => true,
80
+ onPanResponderGrant: () => {
81
+ this.setState({ toFloat: true });
82
+ Animated.spring(this.state.scale, {
83
+ toValue: 0.9,
84
+ friction: 5,
85
+ useNativeDriver: true,
86
+ }).start();
87
+ },
88
+ onPanResponderMove: (_: unknown, gs: { dx: number; dy: number }) => {
89
+ const { lastPosition } = this.state;
90
+ const x = Math.max(0, Math.min(lastPosition.x + gs.dx, Layout.screenWidth - Layout.iconSize));
91
+ const y = Math.max(0, Math.min(lastPosition.y + gs.dy, Layout.screenHeight - Layout.iconSize));
92
+ this.state.pan.setValue({ x, y });
93
+ },
94
+ onPanResponderRelease: (_: unknown, gs: { dx: number; dy: number }) => {
95
+ if (Math.abs(gs.dx) < 5 && Math.abs(gs.dy) < 5) {
96
+ Animated.spring(this.state.scale, { toValue: 1, friction: 5, useNativeDriver: true }).start();
97
+ this.togglePanel();
98
+ return;
99
+ }
100
+ Animated.spring(this.state.scale, { toValue: 1, friction: 5, useNativeDriver: true }).start();
101
+ const { lastPosition } = this.state;
102
+ const newPosition = {
103
+ x: Math.max(0, Math.min(lastPosition.x + gs.dx, Layout.screenWidth - Layout.iconSize)),
104
+ y: Math.max(0, Math.min(lastPosition.y + gs.dy, Layout.screenHeight - Layout.iconSize)),
105
+ };
106
+ this.setState({ lastPosition: newPosition, toFloat: true });
107
+ },
108
+ onPanResponderTerminate: (_: unknown, gs: { dx: number; dy: number }) => {
109
+ const { lastPosition } = this.state;
110
+ const newPosition = {
111
+ x: Math.max(0, Math.min(lastPosition.x + gs.dx, Layout.screenWidth - Layout.iconSize)),
112
+ y: Math.max(0, Math.min(lastPosition.y + gs.dy, Layout.screenHeight - Layout.iconSize)),
113
+ };
114
+ this.setState({ lastPosition: newPosition });
115
+ },
116
+ });
117
+
118
+ this.panelResponder = PanResponder.create({
119
+ onStartShouldSetPanResponder: () => true,
120
+ onMoveShouldSetPanResponder: (_, gs) => gs.dy > 5,
121
+ onPanResponderMove: (_, gs) => {
122
+ if (gs.dy > 0) {
123
+ this.state.panelTranslateY.setValue(gs.dy);
124
+ this.state.backdropOpacity.setValue(Math.max(0, 1 - gs.dy / 200));
125
+ }
126
+ },
127
+ onPanResponderRelease: (_, gs) => {
128
+ if (gs.dy > 100) {
129
+ this.closePanel();
130
+ } else {
131
+ Animated.spring(this.state.panelTranslateY, {
132
+ toValue: 0, friction: 8, tension: 50, useNativeDriver: true,
133
+ }).start();
134
+ Animated.timing(this.state.backdropOpacity, {
135
+ toValue: 1, duration: 200, useNativeDriver: true,
136
+ }).start();
137
+ }
138
+ },
139
+ });
140
+
141
+ // Horizontal swipe for tab switching
142
+ this.swipeResponder = PanResponder.create({
143
+ onStartShouldSetPanResponder: () => false,
144
+ onMoveShouldSetPanResponder: (_, gs) => {
145
+ if (this.isSwitchingTab) return false;
146
+ return Math.abs(gs.dx) > 25 && Math.abs(gs.dx) > Math.abs(gs.dy) * 2.5;
147
+ },
148
+ onPanResponderRelease: (_, gs) => {
149
+ const tabs = this.getTabs();
150
+ if (gs.dx < -40 && this.state.activeTab < tabs.length - 1) {
151
+ this.switchTab(this.state.activeTab + 1);
152
+ } else if (gs.dx > 40 && this.state.activeTab > 0) {
153
+ this.switchTab(this.state.activeTab - 1);
154
+ }
155
+ },
156
+ onPanResponderTerminationRequest: () => true,
157
+ });
158
+ }
159
+
160
+ componentDidMount(): void {
161
+ this.isDisposed = false;
162
+ this.subscribeToFeatures(this.props.features);
163
+ }
164
+
165
+ componentDidUpdate(prevProps: FloatPanelViewProps): void {
166
+ if (prevProps.features !== this.props.features) {
167
+ this.unsubscribeFromFeatures();
168
+ this.subscribeToFeatures(this.props.features);
169
+ if (this.state.activeTab >= this.props.features.length) {
170
+ this.setState({ activeTab: 0 });
171
+ }
172
+ }
173
+ }
174
+
175
+ componentWillUnmount(): void {
176
+ this.isDisposed = true;
177
+ if (this.refreshTimeout) {
178
+ clearTimeout(this.refreshTimeout);
179
+ this.refreshTimeout = null;
180
+ }
181
+ this.unsubscribeFromFeatures();
182
+ }
183
+
184
+ private scheduleRefresh() {
185
+ if (this.isDisposed || this.refreshTimeout) return;
186
+ this.refreshTimeout = setTimeout(() => {
187
+ this.refreshTimeout = null;
188
+ if (!this.isDisposed) this.forceUpdate();
189
+ }, 0);
190
+ }
191
+
192
+ private subscribeToFeatures(features: DebugFeature[]) {
193
+ this.unsubscribeFns = features
194
+ .map((feature) => feature.subscribe?.(() => this.scheduleRefresh()))
195
+ .filter((unsubscribe): unsubscribe is () => void => typeof unsubscribe === 'function');
196
+ }
197
+
198
+ private unsubscribeFromFeatures() {
199
+ this.unsubscribeFns.forEach((unsubscribe) => unsubscribe());
200
+ this.unsubscribeFns = [];
201
+ }
202
+
203
+ private togglePanel = () => {
204
+ if (this.state.isOpen) this.closePanel();
205
+ else this.openPanel();
206
+ };
207
+
208
+ private openPanel = () => {
209
+ this.resetTabMeasurements();
210
+ this.setState({ isOpen: true, toFloat: false, underlineWidth: 0 }, () => {
211
+ Animated.parallel([
212
+ Animated.spring(this.state.panelTranslateY, {
213
+ toValue: 0, friction: 8, tension: 65, useNativeDriver: true,
214
+ }),
215
+ Animated.timing(this.state.backdropOpacity, {
216
+ toValue: 1, duration: 250, useNativeDriver: true,
217
+ }),
218
+ ]).start();
219
+ });
220
+ };
221
+
222
+ private closePanel = () => {
223
+ Animated.parallel([
224
+ Animated.spring(this.state.panelTranslateY, {
225
+ toValue: Layout.screenHeight, friction: 8, tension: 65, useNativeDriver: true,
226
+ }),
227
+ Animated.timing(this.state.backdropOpacity, {
228
+ toValue: 0, duration: 200, useNativeDriver: true,
229
+ }),
230
+ ]).start(() => {
231
+ this.setState({ isOpen: false, toFloat: true });
232
+ });
233
+ };
234
+
235
+ private resetTabMeasurements() {
236
+ this.underlineInitialized = false;
237
+ this.tabScrollOffset = 0;
238
+ this.tabViewportWidth = 0;
239
+ this.tabContentWidth = 0;
240
+ this.tabLayouts = [];
241
+ this.state.underlineTranslateX.setValue(0);
242
+ }
243
+
244
+ private switchTab = (index: number) => {
245
+ if (this.isSwitchingTab || index === this.state.activeTab) return;
246
+ this.isSwitchingTab = true;
247
+
248
+ const direction = index > this.state.activeTab ? 1 : -1;
249
+
250
+ // Auto-scroll tab bar first, then animate underline
251
+ const layout = this.tabLayouts[index];
252
+ if (layout) {
253
+ const targetScrollX = this.getTabScrollTarget(index);
254
+ this.tabScrollOffset = targetScrollX;
255
+ if (this.tabScrollViewRef.current) {
256
+ this.tabScrollViewRef.current.scrollTo({ x: targetScrollX, animated: true });
257
+ }
258
+ this.animateUnderline(index, targetScrollX);
259
+ }
260
+
261
+ // Animate content: slide out + fade out
262
+ Animated.parallel([
263
+ Animated.timing(this.state.contentOpacity, {
264
+ toValue: 0, duration: 80, useNativeDriver: true,
265
+ }),
266
+ Animated.timing(this.state.contentTranslateX, {
267
+ toValue: -direction * 40, duration: 80, useNativeDriver: true,
268
+ }),
269
+ ]).start(() => {
270
+ this.setState({ activeTab: index }, () => {
271
+ // Reset translateX to opposite side instantly, then slide in + fade in
272
+ this.state.contentTranslateX.setValue(direction * 40);
273
+ Animated.parallel([
274
+ Animated.timing(this.state.contentOpacity, {
275
+ toValue: 1, duration: 150, useNativeDriver: true,
276
+ }),
277
+ Animated.timing(this.state.contentTranslateX, {
278
+ toValue: 0, duration: 200, easing: Easing.out(Easing.cubic), useNativeDriver: true,
279
+ }),
280
+ ]).start(() => {
281
+ this.isSwitchingTab = false;
282
+ });
283
+ });
284
+ });
285
+ };
286
+
287
+ private animateUnderline(index: number, scrollOffset?: number) {
288
+ const layout = this.tabLayouts[index];
289
+ if (!layout) {
290
+ return;
291
+ }
292
+ const offsetX = this.getClampedTabScrollOffset(scrollOffset ?? this.tabScrollOffset);
293
+ this.setState({ underlineWidth: layout.width });
294
+ Animated.spring(this.state.underlineTranslateX, {
295
+ toValue: layout.x - offsetX, friction: 7, tension: 50, useNativeDriver: true,
296
+ }).start();
297
+ }
298
+
299
+ private getMaxTabScrollOffset() {
300
+ return Math.max(0, this.tabContentWidth - this.tabViewportWidth);
301
+ }
302
+
303
+ private getClampedTabScrollOffset(offset: number) {
304
+ return Math.min(Math.max(0, offset), this.getMaxTabScrollOffset());
305
+ }
306
+
307
+ private getTabScrollTarget(index: number) {
308
+ const layout = this.tabLayouts[index];
309
+ if (!layout) {
310
+ return this.getClampedTabScrollOffset(this.tabScrollOffset);
311
+ }
312
+ return this.getClampedTabScrollOffset(layout.x - 60);
313
+ }
314
+
315
+ private tryInitializeTabBar() {
316
+ if (this.underlineInitialized || this.tabViewportWidth <= 0 || this.tabContentWidth <= 0) {
317
+ return;
318
+ }
319
+
320
+ const layout = this.tabLayouts[this.state.activeTab];
321
+ if (!layout) {
322
+ return;
323
+ }
324
+
325
+ const targetScrollX = this.getTabScrollTarget(this.state.activeTab);
326
+ this.tabScrollOffset = targetScrollX;
327
+ if (this.tabScrollViewRef.current) {
328
+ this.tabScrollViewRef.current.scrollTo({ x: targetScrollX, animated: false });
329
+ }
330
+ this.state.underlineTranslateX.setValue(layout.x - targetScrollX);
331
+ this.setState({ underlineWidth: layout.width });
332
+ this.underlineInitialized = true;
333
+ }
334
+
335
+ private syncUnderlineToActiveTab() {
336
+ const layout = this.tabLayouts[this.state.activeTab];
337
+ if (!layout) {
338
+ return;
339
+ }
340
+
341
+ const clampedOffset = this.getClampedTabScrollOffset(this.tabScrollOffset);
342
+ if (clampedOffset !== this.tabScrollOffset) {
343
+ this.tabScrollOffset = clampedOffset;
344
+ }
345
+
346
+ this.state.underlineTranslateX.setValue(layout.x - clampedOffset);
347
+ if (this.state.underlineWidth !== layout.width) {
348
+ this.setState({ underlineWidth: layout.width });
349
+ }
350
+ }
351
+
352
+ private handleTabLayout = (index: number, event: { nativeEvent: { layout: { x: number; width: number } } }) => {
353
+ const { x, width } = event.nativeEvent.layout;
354
+ this.tabLayouts[index] = { x, width };
355
+ if (index === this.state.activeTab) {
356
+ this.tryInitializeTabBar();
357
+ }
358
+ };
359
+
360
+ private getTabs() {
361
+ return this.props.features.map((f) => ({ label: f.label, id: f.name }));
362
+ }
363
+
364
+ private renderFeatureContent() {
365
+ const tabs = this.getTabs();
366
+ if (tabs.length === 0) {
367
+ return <Text style={styles.emptyText}>No debug features enabled</Text>;
368
+ }
369
+
370
+ const featureId = tabs[this.state.activeTab]?.id;
371
+ const feature = this.props.features.find((f) => f.name === featureId);
372
+ if (!feature) {
373
+ return <Text style={styles.emptyText}>Feature not found</Text>;
374
+ }
375
+
376
+ const data = feature.getData();
377
+ const TabComponent = feature.renderContent;
378
+
379
+ if (TabComponent) {
380
+ return <TabComponent data={data} feature={feature} />;
381
+ }
382
+
383
+ return (
384
+ <View style={styles.genericContent}>
385
+ <Text style={styles.jsonContent}>{JSON.stringify(data, null, 2)}</Text>
386
+ </View>
387
+ );
388
+ }
389
+
390
+ render() {
391
+ const { isOpen, toFloat, pan, scale, panelTranslateY, backdropOpacity, activeTab, contentOpacity } = this.state;
392
+ const tabs = this.getTabs();
393
+
394
+ return (
395
+ <View style={styles.container} pointerEvents="box-none">
396
+ {/* Float button */}
397
+ {!isOpen && toFloat && (
398
+ <Animated.View
399
+ {...this.gestureResponder.panHandlers}
400
+ style={[
401
+ styles.floatBtn,
402
+ { transform: [{ translateX: pan.x }, { translateY: pan.y }, { scale }] },
403
+ ]}
404
+ >
405
+ <Pressable onPress={this.togglePanel} style={styles.floatBtnInner}>
406
+ <View style={styles.iconGrid}>
407
+ <View style={styles.iconRow}>
408
+ <View style={[styles.iconCell, { backgroundColor: Colors.primary }]} />
409
+ <View style={[styles.iconCell, { backgroundColor: Colors.success }]} />
410
+ </View>
411
+ <View style={styles.iconRow}>
412
+ <View style={[styles.iconCell, { backgroundColor: Colors.warning }]} />
413
+ <View style={[styles.iconCell, { backgroundColor: Colors.purple }]} />
414
+ </View>
415
+ </View>
416
+ {(() => {
417
+ const envBadge = this.props.features
418
+ .map((f) => f.badge?.())
419
+ .find((b) => b != null);
420
+ if (!envBadge) return null;
421
+ return (
422
+ <View style={[styles.envBadge, { backgroundColor: envBadge.color }]}>
423
+ <Text style={styles.envBadgeText}>{envBadge.label}</Text>
424
+ </View>
425
+ );
426
+ })()}
427
+ </Pressable>
428
+ </Animated.View>
429
+ )}
430
+
431
+ {/* Panel */}
432
+ {isOpen && (
433
+ <View style={styles.panelContainer}>
434
+ <Animated.View style={[styles.backdrop, { opacity: backdropOpacity }]}>
435
+ <Pressable style={styles.backdropPressable} onPress={this.closePanel} />
436
+ </Animated.View>
437
+ <Animated.View
438
+ style={[styles.panel, { transform: [{ translateY: panelTranslateY }] }]}
439
+ >
440
+ {/* Drag zone: only handle + header responds to vertical drag to close */}
441
+ <View {...this.panelResponder.panHandlers}>
442
+ <View style={styles.dragHandle}>
443
+ <View style={styles.dragIndicator} />
444
+ </View>
445
+ <View style={styles.header}>
446
+ <Text style={styles.headerTitle}>Debug Toolkit</Text>
447
+ <View style={styles.headerButtons}>
448
+ <TouchableOpacity
449
+ onPress={() => { this.props.onClearAll(); this.closePanel(); }}
450
+ style={styles.clearAllButton}
451
+ activeOpacity={0.6}
452
+ >
453
+ <Text style={styles.clearButtonText}>Clear</Text>
454
+ </TouchableOpacity>
455
+ <Pressable onPress={this.closePanel} style={styles.closeButton}>
456
+ <Text style={styles.closeButtonText}>✕</Text>
457
+ </Pressable>
458
+ </View>
459
+ </View>
460
+ </View>
461
+
462
+ <View style={styles.panelContent}>
463
+ {/* Tab bar - iOS 17 sliding underline style */}
464
+ <View style={styles.tabBarContainer}>
465
+ <ScrollView
466
+ ref={this.tabScrollViewRef}
467
+ horizontal
468
+ showsHorizontalScrollIndicator={false}
469
+ contentContainerStyle={styles.tabsScrollContainer}
470
+ onLayout={(e) => {
471
+ this.tabViewportWidth = e.nativeEvent.layout.width;
472
+ this.tryInitializeTabBar();
473
+ }}
474
+ onContentSizeChange={(width) => {
475
+ this.tabContentWidth = width;
476
+ this.tryInitializeTabBar();
477
+ }}
478
+ onScroll={(e) => {
479
+ this.tabScrollOffset = this.getClampedTabScrollOffset(
480
+ e.nativeEvent.contentOffset.x
481
+ );
482
+ // Keep underline aligned with active tab during manual scroll
483
+ if (!this.isSwitchingTab) {
484
+ this.syncUnderlineToActiveTab();
485
+ }
486
+ }}
487
+ scrollEventThrottle={16}
488
+ >
489
+ {tabs.map((tab, index) => (
490
+ <TouchableOpacity
491
+ key={tab.id}
492
+ style={styles.tab}
493
+ onPress={() => this.switchTab(index)}
494
+ activeOpacity={0.7}
495
+ onLayout={(e) => this.handleTabLayout(index, e)}
496
+ >
497
+ <Text
498
+ style={[styles.tabText, activeTab === index && styles.activeTabText]}
499
+ numberOfLines={1}
500
+ >
501
+ {tab.label}
502
+ </Text>
503
+ </TouchableOpacity>
504
+ ))}
505
+ </ScrollView>
506
+ {/* Sliding underline */}
507
+ <Animated.View
508
+ style={[
509
+ styles.slidingUnderline,
510
+ {
511
+ width: this.state.underlineWidth,
512
+ transform: [{ translateX: this.state.underlineTranslateX }],
513
+ },
514
+ ]}
515
+ />
516
+ </View>
517
+
518
+ {/* Content with swipe gesture + slide + fade animation */}
519
+ <Animated.View
520
+ style={[
521
+ styles.contentContainer,
522
+ {
523
+ opacity: contentOpacity,
524
+ transform: [{ translateX: this.state.contentTranslateX }],
525
+ },
526
+ ]}
527
+ {...this.swipeResponder.panHandlers}
528
+ >
529
+ {this.renderFeatureContent()}
530
+ </Animated.View>
531
+ </View>
532
+ </Animated.View>
533
+ </View>
534
+ )}
535
+ </View>
536
+ );
537
+ }
538
+ }
539
+
540
+ const styles = StyleSheet.create({
541
+ container: {
542
+ position: 'absolute',
543
+ top: 0, left: 0, right: 0, bottom: 0,
544
+ zIndex: 999,
545
+ },
546
+
547
+ // Float button
548
+ floatBtn: {
549
+ position: 'absolute',
550
+ width: Layout.iconSize,
551
+ height: Layout.iconSize,
552
+ borderRadius: Layout.iconSize / 2,
553
+ backgroundColor: '#FFFFFF',
554
+ elevation: 6,
555
+ shadowColor: '#000',
556
+ shadowOffset: { width: 0, height: 2 },
557
+ shadowOpacity: 0.12,
558
+ shadowRadius: 8,
559
+ },
560
+ floatBtnInner: {
561
+ width: '100%', height: '100%',
562
+ alignItems: 'center', justifyContent: 'center',
563
+ },
564
+ iconGrid: {
565
+ width: 22, height: 22,
566
+ justifyContent: 'space-between',
567
+ },
568
+ iconRow: {
569
+ flexDirection: 'row',
570
+ justifyContent: 'space-between',
571
+ },
572
+ iconCell: {
573
+ width: 8, height: 8,
574
+ borderRadius: 2.5,
575
+ },
576
+ envBadge: {
577
+ position: 'absolute',
578
+ top: -4, right: -4,
579
+ minWidth: 20, height: 18,
580
+ borderRadius: 9,
581
+ paddingHorizontal: 5,
582
+ alignItems: 'center', justifyContent: 'center',
583
+ borderWidth: 2, borderColor: '#FFF',
584
+ elevation: 4,
585
+ },
586
+ envBadgeText: {
587
+ color: '#FFF', fontSize: 9, fontWeight: '700',
588
+ },
589
+
590
+ // Panel
591
+ panelContainer: {
592
+ position: 'absolute',
593
+ top: 0, left: 0, right: 0, bottom: 0,
594
+ justifyContent: 'flex-end',
595
+ },
596
+ backdrop: {
597
+ position: 'absolute',
598
+ top: 0, left: 0, right: 0, bottom: 0,
599
+ backgroundColor: 'rgba(0,0,0,0.35)',
600
+ },
601
+ backdropPressable: { flex: 1 },
602
+ panel: {
603
+ width: '100%',
604
+ height: '90%',
605
+ backgroundColor: Colors.background,
606
+ borderTopLeftRadius: 28,
607
+ borderTopRightRadius: 28,
608
+ overflow: 'hidden',
609
+ elevation: 24,
610
+ shadowColor: '#000',
611
+ shadowOffset: { width: 0, height: -4 },
612
+ shadowOpacity: 0.08,
613
+ shadowRadius: 12,
614
+ },
615
+ dragHandle: {
616
+ width: '100%', height: 20,
617
+ alignItems: 'center', justifyContent: 'center',
618
+ backgroundColor: Colors.surface,
619
+ },
620
+ dragIndicator: {
621
+ width: 40, height: 4,
622
+ borderRadius: 2,
623
+ backgroundColor: '#D1D1D6',
624
+ },
625
+ panelContent: { flex: 1 },
626
+ contentContainer: { flex: 1 },
627
+
628
+ // Header
629
+ header: {
630
+ flexDirection: 'row',
631
+ justifyContent: 'space-between',
632
+ alignItems: 'center',
633
+ paddingHorizontal: 20,
634
+ paddingTop: 6,
635
+ paddingBottom: 10,
636
+ backgroundColor: Colors.surface,
637
+ },
638
+ headerTitle: {
639
+ fontSize: 17,
640
+ fontWeight: '600',
641
+ color: Colors.text,
642
+ },
643
+ headerButtons: {
644
+ flexDirection: 'row',
645
+ alignItems: 'center',
646
+ gap: 12,
647
+ },
648
+ clearAllButton: {
649
+ paddingHorizontal: 12,
650
+ paddingVertical: 6,
651
+ borderRadius: 8,
652
+ backgroundColor: 'rgba(255,59,48,0.06)',
653
+ },
654
+ clearButtonText: {
655
+ color: Colors.error,
656
+ fontSize: 14,
657
+ fontWeight: '500',
658
+ },
659
+ closeButton: {
660
+ width: 30, height: 30,
661
+ borderRadius: 15,
662
+ backgroundColor: Colors.background,
663
+ alignItems: 'center', justifyContent: 'center',
664
+ },
665
+ closeButtonText: {
666
+ fontSize: 16,
667
+ fontWeight: '400',
668
+ color: Colors.textSecondary,
669
+ lineHeight: 16,
670
+ },
671
+
672
+ // Tab bar - iOS 17 sliding underline style
673
+ tabBarContainer: {
674
+ backgroundColor: Colors.surface,
675
+ borderBottomWidth: StyleSheet.hairlineWidth,
676
+ borderBottomColor: Colors.border,
677
+ },
678
+ tabsScrollContainer: {
679
+ paddingHorizontal: 20,
680
+ flexDirection: 'row',
681
+ },
682
+ tab: {
683
+ paddingVertical: 10,
684
+ paddingHorizontal: 14,
685
+ marginRight: 8,
686
+ },
687
+ tabText: {
688
+ fontSize: 14,
689
+ fontWeight: '500',
690
+ color: Colors.textLight,
691
+ },
692
+ activeTabText: {
693
+ color: Colors.primary,
694
+ fontWeight: '600',
695
+ },
696
+ slidingUnderline: {
697
+ position: 'absolute',
698
+ bottom: 0,
699
+ left: 0,
700
+ height: 2.5,
701
+ borderRadius: 1.25,
702
+ backgroundColor: Colors.primary,
703
+ },
704
+
705
+ // Misc
706
+ emptyText: {
707
+ padding: 20,
708
+ textAlign: 'center',
709
+ color: Colors.textLight,
710
+ fontSize: 13,
711
+ },
712
+ genericContent: { padding: 16, flex: 1 },
713
+ jsonContent: { fontFamily: 'monospace', fontSize: 12, color: Colors.text },
714
+ });