react-native-debug-toolkit 2.0.0 → 2.1.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 (228) hide show
  1. package/README.md +72 -72
  2. package/README.zh-CN.md +209 -0
  3. package/lib/commonjs/components/ClipboardTab.js +15 -16
  4. package/lib/commonjs/components/ClipboardTab.js.map +1 -1
  5. package/lib/commonjs/components/ConsoleLogTab.js +69 -202
  6. package/lib/commonjs/components/ConsoleLogTab.js.map +1 -1
  7. package/lib/commonjs/components/DebugPanel.js +230 -0
  8. package/lib/commonjs/components/DebugPanel.js.map +1 -0
  9. package/lib/commonjs/components/DebugView.js +66 -0
  10. package/lib/commonjs/components/DebugView.js.map +1 -0
  11. package/lib/commonjs/components/EnvironmentTab.js +22 -23
  12. package/lib/commonjs/components/EnvironmentTab.js.map +1 -1
  13. package/lib/commonjs/components/FeatureTabBar.js +182 -0
  14. package/lib/commonjs/components/FeatureTabBar.js.map +1 -0
  15. package/lib/commonjs/components/FloatIcon.js +187 -0
  16. package/lib/commonjs/components/FloatIcon.js.map +1 -0
  17. package/lib/commonjs/components/FloatPanelView.js +140 -723
  18. package/lib/commonjs/components/FloatPanelView.js.map +1 -1
  19. package/lib/commonjs/components/NavigationLogTab.js +8 -8
  20. package/lib/commonjs/components/NavigationLogTab.js.map +1 -1
  21. package/lib/commonjs/components/NetworkLogTab.js +179 -350
  22. package/lib/commonjs/components/NetworkLogTab.js.map +1 -1
  23. package/lib/commonjs/components/ThirdPartyLibsTab.js +8 -8
  24. package/lib/commonjs/components/ThirdPartyLibsTab.js.map +1 -1
  25. package/lib/commonjs/components/TrackLogTab.js +106 -248
  26. package/lib/commonjs/components/TrackLogTab.js.map +1 -1
  27. package/lib/commonjs/components/ZustandLogTab.js +148 -288
  28. package/lib/commonjs/components/ZustandLogTab.js.map +1 -1
  29. package/lib/commonjs/components/shared/CollapsibleSection.js +4 -4
  30. package/lib/commonjs/components/shared/CollapsibleSection.js.map +1 -1
  31. package/lib/commonjs/components/shared/CopyButton.js +3 -3
  32. package/lib/commonjs/components/shared/CopyButton.js.map +1 -1
  33. package/lib/commonjs/components/shared/LogListScreen.js +174 -0
  34. package/lib/commonjs/components/shared/LogListScreen.js.map +1 -0
  35. package/lib/commonjs/core/DebugToolkit.js +92 -112
  36. package/lib/commonjs/core/DebugToolkit.js.map +1 -1
  37. package/lib/commonjs/core/DebugToolkitProvider.js +30 -28
  38. package/lib/commonjs/core/DebugToolkitProvider.js.map +1 -1
  39. package/lib/commonjs/features/ClipboardFeature.js +6 -2
  40. package/lib/commonjs/features/ClipboardFeature.js.map +1 -1
  41. package/lib/commonjs/features/ConsoleLogFeature.js +66 -49
  42. package/lib/commonjs/features/ConsoleLogFeature.js.map +1 -1
  43. package/lib/commonjs/features/EnvironmentFeature.js +5 -13
  44. package/lib/commonjs/features/EnvironmentFeature.js.map +1 -1
  45. package/lib/commonjs/features/NavigationLogFeature.js +17 -38
  46. package/lib/commonjs/features/NavigationLogFeature.js.map +1 -1
  47. package/lib/commonjs/features/NetworkFeature.js +35 -256
  48. package/lib/commonjs/features/NetworkFeature.js.map +1 -1
  49. package/lib/commonjs/features/TrackFeature.js +17 -38
  50. package/lib/commonjs/features/TrackFeature.js.map +1 -1
  51. package/lib/commonjs/features/ZustandLogFeature.js +21 -38
  52. package/lib/commonjs/features/ZustandLogFeature.js.map +1 -1
  53. package/lib/commonjs/hooks/useNavigationLogger.js.map +1 -1
  54. package/lib/commonjs/index.js +7 -20
  55. package/lib/commonjs/index.js.map +1 -1
  56. package/lib/commonjs/initialize.js +19 -96
  57. package/lib/commonjs/initialize.js.map +1 -1
  58. package/lib/commonjs/interceptors/networkInterceptor.js +466 -0
  59. package/lib/commonjs/interceptors/networkInterceptor.js.map +1 -0
  60. package/lib/commonjs/utils/colors.js +48 -0
  61. package/lib/commonjs/utils/colors.js.map +1 -0
  62. package/lib/commonjs/utils/createChannelFeature.js +48 -0
  63. package/lib/commonjs/utils/createChannelFeature.js.map +1 -0
  64. package/lib/commonjs/utils/layout.js +8 -0
  65. package/lib/commonjs/utils/layout.js.map +1 -0
  66. package/lib/commonjs/utils/urlRewriterRegistry.js +14 -0
  67. package/lib/commonjs/utils/urlRewriterRegistry.js.map +1 -0
  68. package/lib/module/components/ClipboardTab.js +8 -8
  69. package/lib/module/components/ClipboardTab.js.map +1 -1
  70. package/lib/module/components/ConsoleLogTab.js +66 -199
  71. package/lib/module/components/ConsoleLogTab.js.map +1 -1
  72. package/lib/module/components/DebugPanel.js +225 -0
  73. package/lib/module/components/DebugPanel.js.map +1 -0
  74. package/lib/module/components/DebugView.js +61 -0
  75. package/lib/module/components/DebugView.js.map +1 -0
  76. package/lib/module/components/EnvironmentTab.js +3 -3
  77. package/lib/module/components/EnvironmentTab.js.map +1 -1
  78. package/lib/module/components/FeatureTabBar.js +177 -0
  79. package/lib/module/components/FeatureTabBar.js.map +1 -0
  80. package/lib/module/components/FloatIcon.js +182 -0
  81. package/lib/module/components/FloatIcon.js.map +1 -0
  82. package/lib/module/components/FloatPanelView.js +141 -723
  83. package/lib/module/components/FloatPanelView.js.map +1 -1
  84. package/lib/module/components/NavigationLogTab.js +1 -1
  85. package/lib/module/components/NavigationLogTab.js.map +1 -1
  86. package/lib/module/components/NetworkLogTab.js +167 -338
  87. package/lib/module/components/NetworkLogTab.js.map +1 -1
  88. package/lib/module/components/ThirdPartyLibsTab.js +1 -1
  89. package/lib/module/components/ThirdPartyLibsTab.js.map +1 -1
  90. package/lib/module/components/TrackLogTab.js +94 -236
  91. package/lib/module/components/TrackLogTab.js.map +1 -1
  92. package/lib/module/components/ZustandLogTab.js +136 -276
  93. package/lib/module/components/ZustandLogTab.js.map +1 -1
  94. package/lib/module/components/shared/CollapsibleSection.js +1 -1
  95. package/lib/module/components/shared/CollapsibleSection.js.map +1 -1
  96. package/lib/module/components/shared/CopyButton.js +1 -1
  97. package/lib/module/components/shared/CopyButton.js.map +1 -1
  98. package/lib/module/components/shared/LogListScreen.js +169 -0
  99. package/lib/module/components/shared/LogListScreen.js.map +1 -0
  100. package/lib/module/core/DebugToolkit.js +92 -111
  101. package/lib/module/core/DebugToolkit.js.map +1 -1
  102. package/lib/module/core/DebugToolkitProvider.js +31 -29
  103. package/lib/module/core/DebugToolkitProvider.js.map +1 -1
  104. package/lib/module/features/ClipboardFeature.js +6 -2
  105. package/lib/module/features/ClipboardFeature.js.map +1 -1
  106. package/lib/module/features/ConsoleLogFeature.js +65 -49
  107. package/lib/module/features/ConsoleLogFeature.js.map +1 -1
  108. package/lib/module/features/EnvironmentFeature.js +5 -13
  109. package/lib/module/features/EnvironmentFeature.js.map +1 -1
  110. package/lib/module/features/NavigationLogFeature.js +16 -38
  111. package/lib/module/features/NavigationLogFeature.js.map +1 -1
  112. package/lib/module/features/NetworkFeature.js +34 -255
  113. package/lib/module/features/NetworkFeature.js.map +1 -1
  114. package/lib/module/features/TrackFeature.js +16 -38
  115. package/lib/module/features/TrackFeature.js.map +1 -1
  116. package/lib/module/features/ZustandLogFeature.js +22 -38
  117. package/lib/module/features/ZustandLogFeature.js.map +1 -1
  118. package/lib/module/hooks/useNavigationLogger.js.map +1 -1
  119. package/lib/module/index.js +2 -3
  120. package/lib/module/index.js.map +1 -1
  121. package/lib/module/initialize.js +19 -95
  122. package/lib/module/initialize.js.map +1 -1
  123. package/lib/module/interceptors/networkInterceptor.js +460 -0
  124. package/lib/module/interceptors/networkInterceptor.js.map +1 -0
  125. package/lib/module/utils/colors.js +43 -0
  126. package/lib/module/utils/colors.js.map +1 -0
  127. package/lib/module/utils/createChannelFeature.js +44 -0
  128. package/lib/module/utils/createChannelFeature.js.map +1 -0
  129. package/lib/module/utils/layout.js +4 -0
  130. package/lib/module/utils/layout.js.map +1 -0
  131. package/lib/module/utils/urlRewriterRegistry.js +10 -0
  132. package/lib/module/utils/urlRewriterRegistry.js.map +1 -0
  133. package/lib/typescript/src/components/ClipboardTab.d.ts.map +1 -1
  134. package/lib/typescript/src/components/ConsoleLogTab.d.ts.map +1 -1
  135. package/lib/typescript/src/components/DebugPanel.d.ts +9 -0
  136. package/lib/typescript/src/components/DebugPanel.d.ts.map +1 -0
  137. package/lib/typescript/src/components/DebugView.d.ts +19 -0
  138. package/lib/typescript/src/components/DebugView.d.ts.map +1 -0
  139. package/lib/typescript/src/components/EnvironmentTab.d.ts.map +1 -1
  140. package/lib/typescript/src/components/FeatureTabBar.d.ts +13 -0
  141. package/lib/typescript/src/components/FeatureTabBar.d.ts.map +1 -0
  142. package/lib/typescript/src/components/FloatIcon.d.ts +12 -0
  143. package/lib/typescript/src/components/FloatIcon.d.ts.map +1 -0
  144. package/lib/typescript/src/components/FloatPanelView.d.ts +4 -59
  145. package/lib/typescript/src/components/FloatPanelView.d.ts.map +1 -1
  146. package/lib/typescript/src/components/NetworkLogTab.d.ts.map +1 -1
  147. package/lib/typescript/src/components/ThirdPartyLibsTab.d.ts.map +1 -1
  148. package/lib/typescript/src/components/TrackLogTab.d.ts.map +1 -1
  149. package/lib/typescript/src/components/ZustandLogTab.d.ts.map +1 -1
  150. package/lib/typescript/src/components/shared/LogListScreen.d.ts +21 -0
  151. package/lib/typescript/src/components/shared/LogListScreen.d.ts.map +1 -0
  152. package/lib/typescript/src/core/DebugToolkit.d.ts +11 -18
  153. package/lib/typescript/src/core/DebugToolkit.d.ts.map +1 -1
  154. package/lib/typescript/src/core/DebugToolkitProvider.d.ts +2 -5
  155. package/lib/typescript/src/core/DebugToolkitProvider.d.ts.map +1 -1
  156. package/lib/typescript/src/features/ClipboardFeature.d.ts +4 -0
  157. package/lib/typescript/src/features/ClipboardFeature.d.ts.map +1 -1
  158. package/lib/typescript/src/features/ConsoleLogFeature.d.ts +2 -0
  159. package/lib/typescript/src/features/ConsoleLogFeature.d.ts.map +1 -1
  160. package/lib/typescript/src/features/EnvironmentFeature.d.ts.map +1 -1
  161. package/lib/typescript/src/features/NavigationLogFeature.d.ts +2 -0
  162. package/lib/typescript/src/features/NavigationLogFeature.d.ts.map +1 -1
  163. package/lib/typescript/src/features/NetworkFeature.d.ts +7 -20
  164. package/lib/typescript/src/features/NetworkFeature.d.ts.map +1 -1
  165. package/lib/typescript/src/features/TrackFeature.d.ts +2 -0
  166. package/lib/typescript/src/features/TrackFeature.d.ts.map +1 -1
  167. package/lib/typescript/src/features/ZustandLogFeature.d.ts +2 -0
  168. package/lib/typescript/src/features/ZustandLogFeature.d.ts.map +1 -1
  169. package/lib/typescript/src/hooks/useNavigationLogger.d.ts +1 -8
  170. package/lib/typescript/src/hooks/useNavigationLogger.d.ts.map +1 -1
  171. package/lib/typescript/src/index.d.ts +5 -5
  172. package/lib/typescript/src/index.d.ts.map +1 -1
  173. package/lib/typescript/src/initialize.d.ts +3 -22
  174. package/lib/typescript/src/initialize.d.ts.map +1 -1
  175. package/lib/typescript/src/interceptors/networkInterceptor.d.ts +18 -0
  176. package/lib/typescript/src/interceptors/networkInterceptor.d.ts.map +1 -0
  177. package/lib/typescript/src/types/index.d.ts +26 -29
  178. package/lib/typescript/src/types/index.d.ts.map +1 -1
  179. package/lib/typescript/src/utils/colors.d.ts +21 -0
  180. package/lib/typescript/src/utils/colors.d.ts.map +1 -0
  181. package/lib/typescript/src/utils/createChannelFeature.d.ts +18 -0
  182. package/lib/typescript/src/utils/createChannelFeature.d.ts.map +1 -0
  183. package/lib/typescript/src/utils/layout.d.ts +2 -0
  184. package/lib/typescript/src/utils/layout.d.ts.map +1 -0
  185. package/lib/typescript/src/utils/urlRewriterRegistry.d.ts +7 -0
  186. package/lib/typescript/src/utils/urlRewriterRegistry.d.ts.map +1 -0
  187. package/package.json +5 -1
  188. package/src/components/ClipboardTab.tsx +8 -8
  189. package/src/components/ConsoleLogTab.tsx +49 -163
  190. package/src/components/DebugPanel.tsx +215 -0
  191. package/src/components/DebugView.tsx +80 -0
  192. package/src/components/EnvironmentTab.tsx +3 -3
  193. package/src/components/FeatureTabBar.tsx +204 -0
  194. package/src/components/FloatIcon.tsx +171 -0
  195. package/src/components/FloatPanelView.tsx +135 -647
  196. package/src/components/NavigationLogTab.tsx +1 -1
  197. package/src/components/NetworkLogTab.tsx +128 -269
  198. package/src/components/ThirdPartyLibsTab.tsx +3 -3
  199. package/src/components/TrackLogTab.tsx +53 -188
  200. package/src/components/ZustandLogTab.tsx +79 -181
  201. package/src/components/shared/CollapsibleSection.tsx +1 -1
  202. package/src/components/shared/CopyButton.tsx +1 -1
  203. package/src/components/shared/LogListScreen.tsx +164 -0
  204. package/src/core/DebugToolkit.tsx +114 -138
  205. package/src/core/DebugToolkitProvider.tsx +32 -38
  206. package/src/features/ClipboardFeature.ts +6 -2
  207. package/src/features/ConsoleLogFeature.ts +66 -68
  208. package/src/features/EnvironmentFeature.ts +5 -13
  209. package/src/features/NavigationLogFeature.ts +12 -42
  210. package/src/features/NetworkFeature.ts +43 -405
  211. package/src/features/TrackFeature.ts +14 -49
  212. package/src/features/ZustandLogFeature.ts +16 -42
  213. package/src/hooks/useNavigationLogger.ts +1 -6
  214. package/src/index.ts +5 -9
  215. package/src/initialize.ts +28 -120
  216. package/src/interceptors/networkInterceptor.ts +646 -0
  217. package/src/types/index.ts +25 -36
  218. package/src/utils/colors.ts +38 -0
  219. package/src/utils/createChannelFeature.ts +55 -0
  220. package/src/utils/layout.ts +1 -0
  221. package/src/utils/urlRewriterRegistry.ts +10 -0
  222. package/lib/commonjs/utils/constants.js +0 -135
  223. package/lib/commonjs/utils/constants.js.map +0 -1
  224. package/lib/module/utils/constants.js +0 -130
  225. package/lib/module/utils/constants.js.map +0 -1
  226. package/lib/typescript/src/utils/constants.d.ts +0 -96
  227. package/lib/typescript/src/utils/constants.d.ts.map +0 -1
  228. package/src/utils/constants.ts +0 -91
@@ -1,714 +1,202 @@
1
- import React, { Component } from 'react';
1
+ import React, { Component, useCallback, useEffect, useRef, useState } from 'react';
2
2
  import {
3
3
  View,
4
4
  Text,
5
5
  StyleSheet,
6
6
  Animated,
7
7
  PanResponder,
8
- Pressable,
9
- ScrollView,
10
- TouchableOpacity,
11
8
  Easing,
12
9
  } 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;
10
+ import type { AnyDebugFeature } from '../types';
11
+ import { FloatIcon } from './FloatIcon';
12
+ import { DebugPanel } from './DebugPanel';
13
+ import { FeatureTabBar } from './FeatureTabBar';
14
+ import type { TabItem } from './FeatureTabBar';
15
+
16
+ // ─── Error Boundary ────────────────────────────────────
17
+ interface ErrorBoundaryState {
18
+ hasError: boolean;
35
19
  }
36
20
 
37
- export class FloatPanelView extends Component<
38
- FloatPanelViewProps,
39
- FloatPanelViewState
21
+ class DebugErrorBoundary extends Component<
22
+ { children: React.ReactNode; onError: () => void },
23
+ ErrorBoundaryState
40
24
  > {
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 }> = [];
25
+ state: ErrorBoundaryState = { hasError: false };
54
26
 
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
- };
27
+ static getDerivedStateFromError(): ErrorBoundaryState {
28
+ return { hasError: true };
29
+ }
61
30
 
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
- };
31
+ componentDidCatch(error: Error, info: React.ErrorInfo) {
32
+ console.error('[DebugToolkit] Panel crashed:', error, info.componentStack);
33
+ this.props.onError();
34
+ }
76
35
 
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
- });
36
+ render() {
37
+ if (this.state.hasError) return null;
38
+ return this.props.children;
39
+ }
40
+ }
117
41
 
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
- });
42
+ interface FloatPanelViewProps {
43
+ features: AnyDebugFeature[];
44
+ onClearAll: () => void;
45
+ }
140
46
 
141
- // Horizontal swipe for tab switching
142
- this.swipeResponder = PanResponder.create({
47
+ export function FloatPanelView({ features, onClearAll }: FloatPanelViewProps) {
48
+ const [isOpen, setIsOpen] = useState(false);
49
+ const [activeTab, setActiveTab] = useState(0);
50
+
51
+ // Content slide animation
52
+ const contentOpacity = useRef(new Animated.Value(1)).current;
53
+ const contentTranslateX = useRef(new Animated.Value(0)).current;
54
+ const isSwitchingTab = useRef(false);
55
+
56
+ // Refs to avoid stale closures in PanResponder
57
+ const activeTabRef = useRef(0);
58
+ activeTabRef.current = activeTab;
59
+ const featuresLengthRef = useRef(features.length);
60
+ featuresLengthRef.current = features.length;
61
+ const switchTabRef = useRef<(index: number) => void>(() => {});
62
+
63
+ // Swipe-to-switch responder
64
+ const swipeResponder = useRef(
65
+ PanResponder.create({
143
66
  onStartShouldSetPanResponder: () => false,
144
67
  onMoveShouldSetPanResponder: (_, gs) => {
145
- if (this.isSwitchingTab) return false;
68
+ if (isSwitchingTab.current) return false;
146
69
  return Math.abs(gs.dx) > 25 && Math.abs(gs.dx) > Math.abs(gs.dy) * 2.5;
147
70
  },
148
71
  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
- }
72
+ const tab = activeTabRef.current;
73
+ if (gs.dx < -40 && tab < featuresLengthRef.current - 1) switchTabRef.current(tab + 1);
74
+ else if (gs.dx > 40 && tab > 0) switchTabRef.current(tab - 1);
155
75
  },
156
76
  onPanResponderTerminationRequest: () => true,
77
+ }),
78
+ ).current;
79
+
80
+ // Feature subscription → re-render on data changes
81
+ const [, setTick] = useState(0);
82
+ const refreshQueued = useRef(false);
83
+ const scheduleRefresh = useCallback(() => {
84
+ if (refreshQueued.current) return;
85
+ refreshQueued.current = true;
86
+ requestAnimationFrame(() => {
87
+ refreshQueued.current = false;
88
+ setTick((t) => t + 1);
157
89
  });
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
- }
90
+ }, []);
91
+
92
+ useEffect(() => {
93
+ const unsubs = features
94
+ .map((f) => f.subscribe?.(() => scheduleRefresh()))
95
+ .filter((u): u is () => void => typeof u === 'function');
96
+ return () => unsubs.forEach((u) => u());
97
+ }, [features, scheduleRefresh]);
98
+
99
+ // Reset activeTab if features shrink
100
+ useEffect(() => {
101
+ if (activeTab >= features.length) setActiveTab(0);
102
+ }, [features.length, activeTab]);
103
+
104
+ // Tab switching with content animation
105
+ const switchTab = useCallback(
106
+ (index: number) => {
107
+ if (isSwitchingTab.current || index === activeTabRef.current) return;
108
+ isSwitchingTab.current = true;
109
+ const direction = index > activeTabRef.current ? 1 : -1;
197
110
 
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
111
  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,
112
+ Animated.timing(contentOpacity, { toValue: 0, duration: 80, useNativeDriver: true }),
113
+ Animated.timing(contentTranslateX, {
114
+ toValue: -direction * 40,
115
+ duration: 80,
116
+ useNativeDriver: true,
217
117
  }),
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);
118
+ ]).start(() => {
119
+ setActiveTab(index);
120
+ contentTranslateX.setValue(direction * 40);
273
121
  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,
122
+ Animated.timing(contentOpacity, { toValue: 1, duration: 150, useNativeDriver: true }),
123
+ Animated.timing(contentTranslateX, {
124
+ toValue: 0,
125
+ duration: 200,
126
+ easing: Easing.out(Easing.cubic),
127
+ useNativeDriver: true,
279
128
  }),
280
129
  ]).start(() => {
281
- this.isSwitchingTab = false;
130
+ isSwitchingTab.current = false;
282
131
  });
283
132
  });
284
- });
285
- };
133
+ },
134
+ [contentOpacity, contentTranslateX],
135
+ );
286
136
 
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
- }
137
+ // Keep ref in sync
138
+ switchTabRef.current = switchTab;
298
139
 
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
- }
140
+ // Badge (first feature that returns one)
141
+ const envBadge = features.map((f) => f.badge?.()).find((b) => b != null) ?? null;
142
+ const tabs: TabItem[] = features.map((f) => ({ label: f.label, id: f.name }));
306
143
 
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) {
144
+ // Render active feature content
145
+ const renderFeatureContent = () => {
146
+ if (features.length === 0) {
367
147
  return <Text style={styles.emptyText}>No debug features enabled</Text>;
368
148
  }
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
-
149
+ const feature = features[activeTab];
150
+ if (!feature) return <Text style={styles.emptyText}>Feature not found</Text>;
376
151
  const data = feature.getData();
377
152
  const TabComponent = feature.renderContent;
378
-
379
- if (TabComponent) {
380
- return <TabComponent data={data} feature={feature} />;
381
- }
382
-
153
+ if (TabComponent) return <TabComponent data={data} feature={feature} />;
383
154
  return (
384
155
  <View style={styles.genericContent}>
385
156
  <Text style={styles.jsonContent}>{JSON.stringify(data, null, 2)}</Text>
386
157
  </View>
387
158
  );
388
- }
389
-
390
- render() {
391
- const { isOpen, toFloat, pan, scale, panelTranslateY, backdropOpacity, activeTab, contentOpacity } = this.state;
392
- const tabs = this.getTabs();
159
+ };
393
160
 
394
- return (
161
+ return (
162
+ <DebugErrorBoundary onError={() => setIsOpen(false)}>
395
163
  <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 */}
164
+ <FloatIcon visible={!isOpen} onPress={() => setIsOpen(true)} badge={envBadge} />
432
165
  {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>
166
+ <DebugPanel onClose={() => setIsOpen(false)} onClearAll={onClearAll}>
167
+ <FeatureTabBar tabs={tabs} activeIndex={activeTab} onSelectTab={switchTab} />
437
168
  <Animated.View
438
- style={[styles.panel, { transform: [{ translateY: panelTranslateY }] }]}
169
+ style={[
170
+ styles.contentContainer,
171
+ { opacity: contentOpacity, transform: [{ translateX: contentTranslateX }] },
172
+ ]}
173
+ {...swipeResponder.panHandlers}
439
174
  >
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>
175
+ {renderFeatureContent()}
532
176
  </Animated.View>
533
- </View>
177
+ </DebugPanel>
534
178
  )}
535
179
  </View>
536
- );
537
- }
180
+ </DebugErrorBoundary>
181
+ );
538
182
  }
539
183
 
540
184
  const styles = StyleSheet.create({
541
185
  container: {
542
186
  position: 'absolute',
543
- top: 0, left: 0, right: 0, bottom: 0,
187
+ top: 0,
188
+ left: 0,
189
+ right: 0,
190
+ bottom: 0,
544
191
  zIndex: 999,
545
192
  },
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
193
  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
194
  emptyText: {
707
195
  padding: 20,
708
196
  textAlign: 'center',
709
- color: Colors.textLight,
197
+ color: '#C7C7CC',
710
198
  fontSize: 13,
711
199
  },
712
200
  genericContent: { padding: 16, flex: 1 },
713
- jsonContent: { fontFamily: 'monospace', fontSize: 12, color: Colors.text },
201
+ jsonContent: { fontFamily: 'monospace', fontSize: 12, color: '#1C1C1E' },
714
202
  });