baodz-network-logger-debug-controller 0.0.0-stage → 0.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.
- package/README.md +69 -2
- package/android/build.gradle +41 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/java/com/baodz/networkloggerdebugcontroller/BaodzNetworkLoggerDebugControllerModule.java +377 -0
- package/android/src/main/java/com/baodz/networkloggerdebugcontroller/BaodzNetworkLoggerDebugControllerPackage.java +24 -0
- package/baodz-network-logger-debug-controller.podspec +16 -0
- package/ios/BaodzNetworkLoggerDebugController.h +5 -0
- package/ios/BaodzNetworkLoggerDebugController.m +234 -0
- package/package.json +33 -4
- package/react-native.config.js +13 -0
- package/src/index.js +151 -0
- package/src/native-module.js +54 -0
- package/types/index.d.ts +23 -0
package/README.md
CHANGED
|
@@ -1,3 +1,70 @@
|
|
|
1
|
-
#
|
|
1
|
+
# baodz-network-logger-debug-controller
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
React Native controller for [`react-native-network-logger`](https://www.npmjs.com/package/react-native-network-logger). It starts request capture, shows a native floating `LOG` button, toggles the button with a device shake, and opens a full-screen request viewer.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install baodz-network-logger-debug-controller react-native-network-logger react-native-safe-area-context
|
|
9
|
+
cd ios && pod install
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
The package autolinks its iOS and Android native modules. It requires React Native 0.73 or later and React 18 or later.
|
|
13
|
+
The native module uses React Native's classic bridge; the two IVIE projects currently have the New Architecture disabled.
|
|
14
|
+
|
|
15
|
+
## Use
|
|
16
|
+
|
|
17
|
+
Mount one controller under `SafeAreaProvider`, near the app root:
|
|
18
|
+
|
|
19
|
+
```jsx
|
|
20
|
+
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
|
21
|
+
import { NetworkLoggerDebugController } from 'baodz-network-logger-debug-controller';
|
|
22
|
+
|
|
23
|
+
function App() {
|
|
24
|
+
return (
|
|
25
|
+
<SafeAreaProvider>
|
|
26
|
+
<NetworkLoggerDebugController enabled={__DEV__} />
|
|
27
|
+
{/* The rest of the app */}
|
|
28
|
+
</SafeAreaProvider>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The controller starts request capture when enabled. To start capture earlier from the app entry point, call `startNetworkLogger` directly; it is safe to call more than once:
|
|
34
|
+
|
|
35
|
+
```js
|
|
36
|
+
import { startNetworkLogger } from 'baodz-network-logger-debug-controller';
|
|
37
|
+
|
|
38
|
+
startNetworkLogger({ enabled: __DEV__, maxRequests: 200 });
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
On Android, the floating button uses the system “Display over other apps” permission. Request it from a user action before mounting the controller:
|
|
42
|
+
|
|
43
|
+
```js
|
|
44
|
+
import { requestOverlayPermission } from 'baodz-network-logger-debug-controller';
|
|
45
|
+
|
|
46
|
+
await requestOverlayPermission();
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The library manifest declares `SYSTEM_ALERT_WINDOW`; the user still needs to grant the special permission in Android Settings. iOS does not require this permission.
|
|
50
|
+
|
|
51
|
+
## API
|
|
52
|
+
|
|
53
|
+
- `NetworkLoggerDebugController`: props `enabled` (default `true`), `maxRequests` (default `200`), and `maxRows` (default `200`).
|
|
54
|
+
- `startNetworkLogger({ enabled, maxRequests })`: starts capture once and returns whether it started.
|
|
55
|
+
- `stopNetworkLogger()`: stops capture and returns whether it stopped.
|
|
56
|
+
- `hasOverlayPermission()`: checks Android overlay access; resolves `true` on iOS.
|
|
57
|
+
- `requestOverlayPermission()`: opens the Android overlay permission settings; resolves `true` on iOS.
|
|
58
|
+
|
|
59
|
+
## Publish
|
|
60
|
+
|
|
61
|
+
From the package directory, authenticate to the intended npm registry and publish the version in `package.json`:
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
npm login
|
|
65
|
+
npm publish
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The package name is unscoped, so the registry must allow publishing `baodz-network-logger-debug-controller`. The podspec is intended for React Native autolinking from the installed npm package; add a repository `s.source` if you later want to distribute this as a standalone CocoaPods pod.
|
|
69
|
+
|
|
70
|
+
The package metadata is marked `UNLICENSED` because no distribution license was specified. Set the intended license before publishing it to a public registry.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
if (project == rootProject) {
|
|
3
|
+
repositories {
|
|
4
|
+
google()
|
|
5
|
+
mavenCentral()
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
dependencies {
|
|
9
|
+
classpath 'com.android.tools.build:gradle:8.2.1'
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
apply plugin: 'com.android.library'
|
|
15
|
+
|
|
16
|
+
def safeExtGet(name, fallback) {
|
|
17
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : fallback
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
android {
|
|
21
|
+
namespace 'com.baodz.networkloggerdebugcontroller'
|
|
22
|
+
compileSdkVersion safeExtGet('compileSdkVersion', 34)
|
|
23
|
+
|
|
24
|
+
defaultConfig {
|
|
25
|
+
minSdkVersion safeExtGet('minSdkVersion', 21)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
compileOptions {
|
|
29
|
+
sourceCompatibility JavaVersion.VERSION_1_8
|
|
30
|
+
targetCompatibility JavaVersion.VERSION_1_8
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
repositories {
|
|
35
|
+
google()
|
|
36
|
+
mavenCentral()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
dependencies {
|
|
40
|
+
implementation 'com.facebook.react:react-android'
|
|
41
|
+
}
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
package com.baodz.networkloggerdebugcontroller;
|
|
2
|
+
|
|
3
|
+
import android.app.Activity;
|
|
4
|
+
import android.content.Context;
|
|
5
|
+
import android.content.Intent;
|
|
6
|
+
import android.graphics.Color;
|
|
7
|
+
import android.graphics.PixelFormat;
|
|
8
|
+
import android.graphics.Rect;
|
|
9
|
+
import android.graphics.drawable.GradientDrawable;
|
|
10
|
+
import android.hardware.Sensor;
|
|
11
|
+
import android.hardware.SensorEvent;
|
|
12
|
+
import android.hardware.SensorEventListener;
|
|
13
|
+
import android.hardware.SensorManager;
|
|
14
|
+
import android.net.Uri;
|
|
15
|
+
import android.os.Build;
|
|
16
|
+
import android.os.Handler;
|
|
17
|
+
import android.os.Looper;
|
|
18
|
+
import android.provider.Settings;
|
|
19
|
+
import android.view.Gravity;
|
|
20
|
+
import android.view.MotionEvent;
|
|
21
|
+
import android.view.View;
|
|
22
|
+
import android.view.ViewConfiguration;
|
|
23
|
+
import android.view.WindowManager;
|
|
24
|
+
import android.util.DisplayMetrics;
|
|
25
|
+
import android.widget.TextView;
|
|
26
|
+
|
|
27
|
+
import com.facebook.react.bridge.ActivityEventListener;
|
|
28
|
+
import com.facebook.react.bridge.BaseActivityEventListener;
|
|
29
|
+
import com.facebook.react.bridge.Promise;
|
|
30
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
31
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
|
32
|
+
import com.facebook.react.bridge.ReactMethod;
|
|
33
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
|
34
|
+
|
|
35
|
+
public class BaodzNetworkLoggerDebugControllerModule extends ReactContextBaseJavaModule
|
|
36
|
+
implements SensorEventListener {
|
|
37
|
+
private static final String EVENT_BUTTON_PRESS = "NetworkLoggerButtonPress";
|
|
38
|
+
private static final int OVERLAY_PERMISSION_REQUEST_CODE = 5678;
|
|
39
|
+
private static final int BUTTON_SIZE_DP = 52;
|
|
40
|
+
private static final float SHAKE_THRESHOLD_G = 2.7f;
|
|
41
|
+
private static final long SHAKE_DEBOUNCE_MS = 900L;
|
|
42
|
+
|
|
43
|
+
private final ReactApplicationContext reactContext;
|
|
44
|
+
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
|
45
|
+
private final SensorManager sensorManager;
|
|
46
|
+
private final Sensor accelerometer;
|
|
47
|
+
private final WindowManager windowManager;
|
|
48
|
+
private TextView button;
|
|
49
|
+
private WindowManager.LayoutParams buttonLayoutParams;
|
|
50
|
+
private int buttonX;
|
|
51
|
+
private int buttonY;
|
|
52
|
+
private boolean buttonVisible;
|
|
53
|
+
private boolean shakeToggleStarted;
|
|
54
|
+
private long lastShakeAt;
|
|
55
|
+
private Promise overlayPermissionPromise;
|
|
56
|
+
|
|
57
|
+
private final ActivityEventListener activityEventListener = new BaseActivityEventListener() {
|
|
58
|
+
@Override
|
|
59
|
+
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent intent) {
|
|
60
|
+
if (requestCode != OVERLAY_PERMISSION_REQUEST_CODE || overlayPermissionPromise == null) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
overlayPermissionPromise.resolve(canDrawOverlays());
|
|
65
|
+
overlayPermissionPromise = null;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
BaodzNetworkLoggerDebugControllerModule(ReactApplicationContext context) {
|
|
70
|
+
super(context);
|
|
71
|
+
reactContext = context;
|
|
72
|
+
sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
|
|
73
|
+
accelerometer = sensorManager == null
|
|
74
|
+
? null
|
|
75
|
+
: sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
|
|
76
|
+
windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
|
|
77
|
+
buttonX = dp(16);
|
|
78
|
+
buttonY = dp(144);
|
|
79
|
+
context.addActivityEventListener(activityEventListener);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
@Override
|
|
83
|
+
public String getName() {
|
|
84
|
+
return "BaodzNetworkLoggerDebugController";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private int dp(int value) {
|
|
88
|
+
return Math.round(value * reactContext.getResources().getDisplayMetrics().density);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private boolean canDrawOverlays() {
|
|
92
|
+
return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Settings.canDrawOverlays(reactContext);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private int getOverlayWindowType() {
|
|
96
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
97
|
+
return WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
|
|
98
|
+
}
|
|
99
|
+
return WindowManager.LayoutParams.TYPE_PHONE;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private WindowManager.LayoutParams createButtonLayoutParams() {
|
|
103
|
+
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
|
|
104
|
+
dp(BUTTON_SIZE_DP),
|
|
105
|
+
dp(BUTTON_SIZE_DP),
|
|
106
|
+
getOverlayWindowType(),
|
|
107
|
+
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
|
|
108
|
+
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
|
|
109
|
+
PixelFormat.TRANSLUCENT);
|
|
110
|
+
params.gravity = Gravity.TOP | Gravity.START;
|
|
111
|
+
params.x = buttonX;
|
|
112
|
+
params.y = buttonY;
|
|
113
|
+
return params;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private int[] getScreenSize() {
|
|
117
|
+
if (windowManager == null) {
|
|
118
|
+
return new int[] {0, 0};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
122
|
+
Rect bounds = windowManager.getCurrentWindowMetrics().getBounds();
|
|
123
|
+
return new int[] {bounds.width(), bounds.height()};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
DisplayMetrics metrics = new DisplayMetrics();
|
|
127
|
+
windowManager.getDefaultDisplay().getRealMetrics(metrics);
|
|
128
|
+
return new int[] {metrics.widthPixels, metrics.heightPixels};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private int[] clampButtonPosition(int requestedX, int requestedY) {
|
|
132
|
+
int[] screenSize = getScreenSize();
|
|
133
|
+
int buttonSize = dp(BUTTON_SIZE_DP);
|
|
134
|
+
int maxX = Math.max(0, screenSize[0] - buttonSize);
|
|
135
|
+
int maxY = Math.max(0, screenSize[1] - buttonSize);
|
|
136
|
+
return new int[] {
|
|
137
|
+
Math.max(0, Math.min(requestedX, maxX)),
|
|
138
|
+
Math.max(0, Math.min(requestedY, maxY))
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private TextView createButton() {
|
|
143
|
+
TextView view = new TextView(reactContext);
|
|
144
|
+
view.setText("LOG");
|
|
145
|
+
view.setTextColor(Color.WHITE);
|
|
146
|
+
view.setTextSize(10);
|
|
147
|
+
view.setGravity(Gravity.CENTER);
|
|
148
|
+
view.setElevation(dp(8));
|
|
149
|
+
|
|
150
|
+
GradientDrawable background = new GradientDrawable();
|
|
151
|
+
background.setColor(Color.rgb(17, 24, 39));
|
|
152
|
+
background.setShape(GradientDrawable.OVAL);
|
|
153
|
+
background.setCornerRadius(dp(BUTTON_SIZE_DP) / 2f);
|
|
154
|
+
background.setStroke(dp(1), Color.rgb(52, 211, 153));
|
|
155
|
+
view.setBackground(background);
|
|
156
|
+
view.setTypeface(view.getTypeface(), android.graphics.Typeface.BOLD);
|
|
157
|
+
view.setOnClickListener(clickedView -> emitButtonPress());
|
|
158
|
+
enableDragging(view);
|
|
159
|
+
return view;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private void enableDragging(TextView view) {
|
|
163
|
+
view.setOnTouchListener(new View.OnTouchListener() {
|
|
164
|
+
private float startRawX;
|
|
165
|
+
private float startRawY;
|
|
166
|
+
private int startX;
|
|
167
|
+
private int startY;
|
|
168
|
+
private boolean dragged;
|
|
169
|
+
private final int touchSlop = ViewConfiguration.get(reactContext).getScaledTouchSlop();
|
|
170
|
+
|
|
171
|
+
@Override
|
|
172
|
+
public boolean onTouch(View touchedView, MotionEvent event) {
|
|
173
|
+
switch (event.getActionMasked()) {
|
|
174
|
+
case MotionEvent.ACTION_DOWN:
|
|
175
|
+
startRawX = event.getRawX();
|
|
176
|
+
startRawY = event.getRawY();
|
|
177
|
+
startX = buttonX;
|
|
178
|
+
startY = buttonY;
|
|
179
|
+
dragged = false;
|
|
180
|
+
return true;
|
|
181
|
+
case MotionEvent.ACTION_MOVE:
|
|
182
|
+
float deltaX = event.getRawX() - startRawX;
|
|
183
|
+
float deltaY = event.getRawY() - startRawY;
|
|
184
|
+
if (Math.hypot(deltaX, deltaY) >= touchSlop) {
|
|
185
|
+
dragged = true;
|
|
186
|
+
}
|
|
187
|
+
if (dragged) {
|
|
188
|
+
updateButtonPosition(startX + Math.round(deltaX), startY + Math.round(deltaY));
|
|
189
|
+
}
|
|
190
|
+
return true;
|
|
191
|
+
case MotionEvent.ACTION_UP:
|
|
192
|
+
if (!dragged) {
|
|
193
|
+
touchedView.performClick();
|
|
194
|
+
}
|
|
195
|
+
return true;
|
|
196
|
+
case MotionEvent.ACTION_CANCEL:
|
|
197
|
+
return true;
|
|
198
|
+
default:
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private void updateButtonPosition(int x, int y) {
|
|
206
|
+
if (windowManager == null || button == null || buttonLayoutParams == null) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
int[] position = clampButtonPosition(x, y);
|
|
211
|
+
buttonX = position[0];
|
|
212
|
+
buttonY = position[1];
|
|
213
|
+
buttonLayoutParams.x = buttonX;
|
|
214
|
+
buttonLayoutParams.y = buttonY;
|
|
215
|
+
try {
|
|
216
|
+
windowManager.updateViewLayout(button, buttonLayoutParams);
|
|
217
|
+
} catch (IllegalArgumentException | SecurityException ignored) {
|
|
218
|
+
hideNetworkLoggerButtonOnMain();
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private void showNetworkLoggerButtonOnMain() {
|
|
223
|
+
if (!canDrawOverlays() || windowManager == null || buttonVisible) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
button = createButton();
|
|
228
|
+
buttonLayoutParams = createButtonLayoutParams();
|
|
229
|
+
try {
|
|
230
|
+
windowManager.addView(button, buttonLayoutParams);
|
|
231
|
+
buttonVisible = true;
|
|
232
|
+
} catch (IllegalArgumentException | SecurityException ignored) {
|
|
233
|
+
button = null;
|
|
234
|
+
buttonLayoutParams = null;
|
|
235
|
+
buttonVisible = false;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private void hideNetworkLoggerButtonOnMain() {
|
|
240
|
+
if (windowManager != null && button != null) {
|
|
241
|
+
try {
|
|
242
|
+
windowManager.removeView(button);
|
|
243
|
+
} catch (IllegalArgumentException | SecurityException ignored) {
|
|
244
|
+
// The system can remove an application overlay when permission changes.
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
button = null;
|
|
248
|
+
buttonLayoutParams = null;
|
|
249
|
+
buttonVisible = false;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
private void toggleNetworkLoggerButton() {
|
|
253
|
+
mainHandler.post(() -> {
|
|
254
|
+
if (buttonVisible) {
|
|
255
|
+
hideNetworkLoggerButtonOnMain();
|
|
256
|
+
} else {
|
|
257
|
+
showNetworkLoggerButtonOnMain();
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private void emitButtonPress() {
|
|
263
|
+
if (!reactContext.hasActiveCatalystInstance()) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
reactContext
|
|
267
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
|
268
|
+
.emit(EVENT_BUTTON_PRESS, null);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
@ReactMethod
|
|
272
|
+
public void showNetworkLoggerButton() {
|
|
273
|
+
mainHandler.post(this::showNetworkLoggerButtonOnMain);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
@ReactMethod
|
|
277
|
+
public void hideNetworkLoggerButton() {
|
|
278
|
+
mainHandler.post(this::hideNetworkLoggerButtonOnMain);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
@ReactMethod
|
|
282
|
+
public void startNetworkLoggerShakeToggle(boolean initiallyVisible) {
|
|
283
|
+
mainHandler.post(() -> {
|
|
284
|
+
if (initiallyVisible) {
|
|
285
|
+
showNetworkLoggerButtonOnMain();
|
|
286
|
+
} else {
|
|
287
|
+
hideNetworkLoggerButtonOnMain();
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
if (sensorManager != null && accelerometer != null && !shakeToggleStarted) {
|
|
292
|
+
sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_UI);
|
|
293
|
+
shakeToggleStarted = true;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
@ReactMethod
|
|
298
|
+
public void stopNetworkLoggerShakeToggle() {
|
|
299
|
+
if (sensorManager != null && shakeToggleStarted) {
|
|
300
|
+
sensorManager.unregisterListener(this);
|
|
301
|
+
}
|
|
302
|
+
shakeToggleStarted = false;
|
|
303
|
+
mainHandler.post(this::hideNetworkLoggerButtonOnMain);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
@ReactMethod
|
|
307
|
+
public void addListener(String eventName) {
|
|
308
|
+
// Required by NativeEventEmitter on Android.
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
@ReactMethod
|
|
312
|
+
public void removeListeners(Integer count) {
|
|
313
|
+
// Required by NativeEventEmitter on Android.
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
@ReactMethod
|
|
317
|
+
public void hasOverlayPermission(Promise promise) {
|
|
318
|
+
promise.resolve(canDrawOverlays());
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
@ReactMethod
|
|
322
|
+
public void requestOverlayPermission(Promise promise) {
|
|
323
|
+
if (canDrawOverlays()) {
|
|
324
|
+
promise.resolve(true);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
Activity activity = getCurrentActivity();
|
|
329
|
+
if (activity == null) {
|
|
330
|
+
promise.resolve(false);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (overlayPermissionPromise != null) {
|
|
335
|
+
overlayPermissionPromise.reject("E_OVERLAY_PERMISSION", "A permission request is already active.");
|
|
336
|
+
}
|
|
337
|
+
overlayPermissionPromise = promise;
|
|
338
|
+
|
|
339
|
+
Intent intent = new Intent(
|
|
340
|
+
Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
|
|
341
|
+
Uri.parse("package:" + reactContext.getPackageName()));
|
|
342
|
+
try {
|
|
343
|
+
activity.startActivityForResult(intent, OVERLAY_PERMISSION_REQUEST_CODE);
|
|
344
|
+
} catch (RuntimeException error) {
|
|
345
|
+
overlayPermissionPromise = null;
|
|
346
|
+
promise.reject("E_OVERLAY_PERMISSION", error);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
@Override
|
|
351
|
+
public void onSensorChanged(SensorEvent event) {
|
|
352
|
+
if (event.sensor.getType() != Sensor.TYPE_ACCELEROMETER) {
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
float gx = event.values[0] / SensorManager.GRAVITY_EARTH;
|
|
357
|
+
float gy = event.values[1] / SensorManager.GRAVITY_EARTH;
|
|
358
|
+
float gz = event.values[2] / SensorManager.GRAVITY_EARTH;
|
|
359
|
+
double gForce = Math.sqrt(gx * gx + gy * gy + gz * gz);
|
|
360
|
+
if (gForce < SHAKE_THRESHOLD_G) {
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
long now = System.currentTimeMillis();
|
|
365
|
+
if (now - lastShakeAt < SHAKE_DEBOUNCE_MS) {
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
lastShakeAt = now;
|
|
370
|
+
toggleNetworkLoggerButton();
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
@Override
|
|
374
|
+
public void onAccuracyChanged(Sensor sensor, int accuracy) {
|
|
375
|
+
// No accuracy adjustment is needed for shake detection.
|
|
376
|
+
}
|
|
377
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
package com.baodz.networkloggerdebugcontroller;
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.ReactPackage;
|
|
4
|
+
import com.facebook.react.bridge.NativeModule;
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
6
|
+
import com.facebook.react.uimanager.ViewManager;
|
|
7
|
+
|
|
8
|
+
import java.util.ArrayList;
|
|
9
|
+
import java.util.Collections;
|
|
10
|
+
import java.util.List;
|
|
11
|
+
|
|
12
|
+
public class BaodzNetworkLoggerDebugControllerPackage implements ReactPackage {
|
|
13
|
+
@Override
|
|
14
|
+
public List<NativeModule> createNativeModules(ReactApplicationContext context) {
|
|
15
|
+
List<NativeModule> modules = new ArrayList<>();
|
|
16
|
+
modules.add(new BaodzNetworkLoggerDebugControllerModule(context));
|
|
17
|
+
return modules;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@Override
|
|
21
|
+
public List<ViewManager> createViewManagers(ReactApplicationContext context) {
|
|
22
|
+
return Collections.emptyList();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Pod::Spec.new do |s|
|
|
2
|
+
s.name = 'BaodzNetworkLoggerDebugController'
|
|
3
|
+
s.version = '0.1.0'
|
|
4
|
+
s.summary = 'React Native network logger debug controller.'
|
|
5
|
+
s.description = 'A React Native debug controller with native shake-toggle floating buttons.'
|
|
6
|
+
s.homepage = 'https://www.npmjs.com/package/baodz-network-logger-debug-controller'
|
|
7
|
+
s.license = { :type => 'UNLICENSED' }
|
|
8
|
+
s.author = 'baodz'
|
|
9
|
+
# React Native autolinking consumes this podspec from the installed npm package path.
|
|
10
|
+
s.platforms = { :ios => '12.0' }
|
|
11
|
+
s.source_files = 'ios/**/*.{h,m,mm}'
|
|
12
|
+
s.requires_arc = true
|
|
13
|
+
|
|
14
|
+
s.frameworks = 'CoreMotion', 'UIKit'
|
|
15
|
+
s.dependency 'React-Core'
|
|
16
|
+
end
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
#import "BaodzNetworkLoggerDebugController.h"
|
|
2
|
+
|
|
3
|
+
#import <CoreMotion/CoreMotion.h>
|
|
4
|
+
#import <UIKit/UIKit.h>
|
|
5
|
+
#import <math.h>
|
|
6
|
+
|
|
7
|
+
static NSString *const NetworkLoggerButtonPressEvent = @"NetworkLoggerButtonPress";
|
|
8
|
+
static NSTimeInterval const ShakeDebounceSeconds = 0.9;
|
|
9
|
+
static double const ShakeThresholdG = 2.7;
|
|
10
|
+
static CGFloat const NetworkLoggerButtonSize = 52.0;
|
|
11
|
+
|
|
12
|
+
@interface BaodzNetworkLoggerDebugController ()
|
|
13
|
+
@property (nonatomic, strong) CMMotionManager *motionManager;
|
|
14
|
+
@property (nonatomic, strong) UIWindow *networkLoggerWindow;
|
|
15
|
+
@property (nonatomic, assign) BOOL networkLoggerButtonVisible;
|
|
16
|
+
@property (nonatomic, assign) NSTimeInterval lastShakeAt;
|
|
17
|
+
@end
|
|
18
|
+
|
|
19
|
+
@implementation BaodzNetworkLoggerDebugController
|
|
20
|
+
|
|
21
|
+
RCT_EXPORT_MODULE(BaodzNetworkLoggerDebugController);
|
|
22
|
+
|
|
23
|
+
- (dispatch_queue_t)methodQueue
|
|
24
|
+
{
|
|
25
|
+
return dispatch_get_main_queue();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
- (NSArray<NSString *> *)supportedEvents
|
|
29
|
+
{
|
|
30
|
+
return @[NetworkLoggerButtonPressEvent];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
- (instancetype)init
|
|
34
|
+
{
|
|
35
|
+
self = [super init];
|
|
36
|
+
if (self) {
|
|
37
|
+
_motionManager = [CMMotionManager new];
|
|
38
|
+
_motionManager.accelerometerUpdateInterval = 0.12;
|
|
39
|
+
}
|
|
40
|
+
return self;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
- (UIWindowScene *)activeWindowScene
|
|
44
|
+
{
|
|
45
|
+
if (@available(iOS 13.0, *)) {
|
|
46
|
+
for (UIScene *scene in UIApplication.sharedApplication.connectedScenes) {
|
|
47
|
+
if (scene.activationState == UISceneActivationStateForegroundActive &&
|
|
48
|
+
[scene isKindOfClass:UIWindowScene.class]) {
|
|
49
|
+
return (UIWindowScene *)scene;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return nil;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
- (UIWindow *)makeNetworkLoggerWindow
|
|
57
|
+
{
|
|
58
|
+
CGRect frame = CGRectMake(16, 144, NetworkLoggerButtonSize, NetworkLoggerButtonSize);
|
|
59
|
+
UIWindow *window;
|
|
60
|
+
if (@available(iOS 13.0, *)) {
|
|
61
|
+
UIWindowScene *windowScene = [self activeWindowScene];
|
|
62
|
+
if (windowScene != nil) {
|
|
63
|
+
window = [[UIWindow alloc] initWithWindowScene:windowScene];
|
|
64
|
+
window.frame = frame;
|
|
65
|
+
} else {
|
|
66
|
+
window = [[UIWindow alloc] initWithFrame:frame];
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
window = [[UIWindow alloc] initWithFrame:frame];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
window.windowLevel = UIWindowLevelAlert + 2;
|
|
73
|
+
window.backgroundColor = UIColor.clearColor;
|
|
74
|
+
window.hidden = YES;
|
|
75
|
+
|
|
76
|
+
UIViewController *viewController = [UIViewController new];
|
|
77
|
+
viewController.view.backgroundColor = UIColor.clearColor;
|
|
78
|
+
|
|
79
|
+
UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
|
|
80
|
+
button.frame = CGRectMake(0, 0, NetworkLoggerButtonSize, NetworkLoggerButtonSize);
|
|
81
|
+
button.backgroundColor = [UIColor colorWithRed:17.0 / 255.0 green:24.0 / 255.0 blue:39.0 / 255.0 alpha:1.0];
|
|
82
|
+
button.layer.cornerRadius = NetworkLoggerButtonSize / 2.0;
|
|
83
|
+
button.layer.borderColor = [UIColor colorWithRed:52.0 / 255.0 green:211.0 / 255.0 blue:153.0 / 255.0 alpha:1.0].CGColor;
|
|
84
|
+
button.layer.borderWidth = 1;
|
|
85
|
+
button.titleLabel.font = [UIFont boldSystemFontOfSize:10];
|
|
86
|
+
[button setTitle:@"LOG" forState:UIControlStateNormal];
|
|
87
|
+
[button setTitleColor:UIColor.whiteColor forState:UIControlStateNormal];
|
|
88
|
+
[button addTarget:self action:@selector(onNetworkLoggerButtonPress) forControlEvents:UIControlEventTouchUpInside];
|
|
89
|
+
|
|
90
|
+
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self
|
|
91
|
+
action:@selector(handleNetworkLoggerPan:)];
|
|
92
|
+
[button addGestureRecognizer:panGesture];
|
|
93
|
+
[viewController.view addSubview:button];
|
|
94
|
+
|
|
95
|
+
window.rootViewController = viewController;
|
|
96
|
+
return window;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
- (void)ensureNetworkLoggerWindow
|
|
100
|
+
{
|
|
101
|
+
if (self.networkLoggerWindow == nil) {
|
|
102
|
+
self.networkLoggerWindow = [self makeNetworkLoggerWindow];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
- (void)showNetworkLoggerButtonOnMain
|
|
107
|
+
{
|
|
108
|
+
[self ensureNetworkLoggerWindow];
|
|
109
|
+
self.networkLoggerWindow.hidden = NO;
|
|
110
|
+
self.networkLoggerButtonVisible = YES;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
- (void)hideNetworkLoggerButtonOnMain
|
|
114
|
+
{
|
|
115
|
+
self.networkLoggerWindow.hidden = YES;
|
|
116
|
+
self.networkLoggerButtonVisible = NO;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
- (void)toggleNetworkLoggerButton
|
|
120
|
+
{
|
|
121
|
+
if (self.networkLoggerButtonVisible) {
|
|
122
|
+
[self hideNetworkLoggerButtonOnMain];
|
|
123
|
+
} else {
|
|
124
|
+
[self showNetworkLoggerButtonOnMain];
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
- (CGRect)networkLoggerMovementBounds
|
|
129
|
+
{
|
|
130
|
+
if (@available(iOS 13.0, *)) {
|
|
131
|
+
UIWindowScene *windowScene = self.networkLoggerWindow.windowScene ?: [self activeWindowScene];
|
|
132
|
+
if (windowScene != nil) {
|
|
133
|
+
return windowScene.coordinateSpace.bounds;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return UIScreen.mainScreen.bounds;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
- (CGRect)clampedNetworkLoggerFrame:(CGRect)frame
|
|
140
|
+
{
|
|
141
|
+
CGRect bounds = [self networkLoggerMovementBounds];
|
|
142
|
+
CGFloat maxX = MAX(CGRectGetMinX(bounds), CGRectGetMaxX(bounds) - CGRectGetWidth(frame));
|
|
143
|
+
CGFloat maxY = MAX(CGRectGetMinY(bounds), CGRectGetMaxY(bounds) - CGRectGetHeight(frame));
|
|
144
|
+
frame.origin.x = MIN(MAX(CGRectGetMinX(bounds), frame.origin.x), maxX);
|
|
145
|
+
frame.origin.y = MIN(MAX(CGRectGetMinY(bounds), frame.origin.y), maxY);
|
|
146
|
+
return frame;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
- (void)handleNetworkLoggerPan:(UIPanGestureRecognizer *)gesture
|
|
150
|
+
{
|
|
151
|
+
if (self.networkLoggerWindow == nil) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
CGPoint translation = [gesture translationInView:nil];
|
|
156
|
+
CGRect frame = CGRectOffset(self.networkLoggerWindow.frame, translation.x, translation.y);
|
|
157
|
+
self.networkLoggerWindow.frame = [self clampedNetworkLoggerFrame:frame];
|
|
158
|
+
[gesture setTranslation:CGPointZero inView:nil];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
- (void)onNetworkLoggerButtonPress
|
|
162
|
+
{
|
|
163
|
+
[self sendEventWithName:NetworkLoggerButtonPressEvent body:nil];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
RCT_EXPORT_METHOD(showNetworkLoggerButton)
|
|
167
|
+
{
|
|
168
|
+
[self showNetworkLoggerButtonOnMain];
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
RCT_EXPORT_METHOD(hideNetworkLoggerButton)
|
|
172
|
+
{
|
|
173
|
+
[self hideNetworkLoggerButtonOnMain];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
RCT_EXPORT_METHOD(startNetworkLoggerShakeToggle:(BOOL)initiallyVisible)
|
|
177
|
+
{
|
|
178
|
+
if (initiallyVisible) {
|
|
179
|
+
[self showNetworkLoggerButtonOnMain];
|
|
180
|
+
} else {
|
|
181
|
+
[self hideNetworkLoggerButtonOnMain];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (!self.motionManager.accelerometerAvailable || self.motionManager.accelerometerActive) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
__weak BaodzNetworkLoggerDebugController *weakSelf = self;
|
|
189
|
+
[self.motionManager startAccelerometerUpdatesToQueue:[NSOperationQueue mainQueue]
|
|
190
|
+
withHandler:^(CMAccelerometerData *data, NSError *error) {
|
|
191
|
+
BaodzNetworkLoggerDebugController *strongSelf = weakSelf;
|
|
192
|
+
if (strongSelf == nil || data == nil || error != nil) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
double x = data.acceleration.x;
|
|
197
|
+
double y = data.acceleration.y;
|
|
198
|
+
double z = data.acceleration.z;
|
|
199
|
+
double gForce = sqrt(x * x + y * y + z * z);
|
|
200
|
+
if (gForce < ShakeThresholdG) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
NSTimeInterval now = NSDate.date.timeIntervalSince1970;
|
|
205
|
+
if (now - strongSelf.lastShakeAt < ShakeDebounceSeconds) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
strongSelf.lastShakeAt = now;
|
|
210
|
+
[strongSelf toggleNetworkLoggerButton];
|
|
211
|
+
}];
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
RCT_EXPORT_METHOD(stopNetworkLoggerShakeToggle)
|
|
215
|
+
{
|
|
216
|
+
if (self.motionManager.accelerometerActive) {
|
|
217
|
+
[self.motionManager stopAccelerometerUpdates];
|
|
218
|
+
}
|
|
219
|
+
[self hideNetworkLoggerButtonOnMain];
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
RCT_EXPORT_METHOD(hasOverlayPermission:(RCTPromiseResolveBlock)resolve
|
|
223
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
224
|
+
{
|
|
225
|
+
resolve(@YES);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
RCT_EXPORT_METHOD(requestOverlayPermission:(RCTPromiseResolveBlock)resolve
|
|
229
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
230
|
+
{
|
|
231
|
+
resolve(@YES);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
@end
|
package/package.json
CHANGED
|
@@ -1,6 +1,35 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "baodz-network-logger-debug-controller",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "React Native network logger debug controller with a shake-toggle floating button.",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"react-native": "src/index.js",
|
|
7
|
+
"types": "types/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"android/",
|
|
10
|
+
"ios/",
|
|
11
|
+
"src/",
|
|
12
|
+
"types/",
|
|
13
|
+
"*.podspec",
|
|
14
|
+
"react-native.config.js",
|
|
15
|
+
"README.md"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"pack:check": "npm pack --dry-run"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"react-native",
|
|
22
|
+
"network-logger",
|
|
23
|
+
"debug"
|
|
24
|
+
],
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"react": ">=18.2.0",
|
|
27
|
+
"react-native": ">=0.73.0",
|
|
28
|
+
"react-native-network-logger": ">=3.0.0 <4.0.0",
|
|
29
|
+
"react-native-safe-area-context": ">=4.10.0"
|
|
30
|
+
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=18"
|
|
33
|
+
},
|
|
34
|
+
"license": "UNLICENSED"
|
|
35
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
dependency: {
|
|
3
|
+
platforms: {
|
|
4
|
+
android: {
|
|
5
|
+
packageImportPath:
|
|
6
|
+
'import com.baodz.networkloggerdebugcontroller.BaodzNetworkLoggerDebugControllerPackage;',
|
|
7
|
+
packageInstance:
|
|
8
|
+
'new BaodzNetworkLoggerDebugControllerPackage()',
|
|
9
|
+
},
|
|
10
|
+
ios: {},
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
};
|
package/src/index.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Modal,
|
|
4
|
+
StyleSheet,
|
|
5
|
+
Text,
|
|
6
|
+
TouchableOpacity,
|
|
7
|
+
View,
|
|
8
|
+
} from 'react-native';
|
|
9
|
+
import NetworkLogger, {
|
|
10
|
+
startNetworkLogging,
|
|
11
|
+
stopNetworkLogging,
|
|
12
|
+
} from 'react-native-network-logger';
|
|
13
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
hasOverlayPermission,
|
|
17
|
+
hideNetworkLoggerButton,
|
|
18
|
+
NETWORK_LOGGER_BUTTON_PRESS_EVENT,
|
|
19
|
+
requestOverlayPermission,
|
|
20
|
+
showNetworkLoggerButton,
|
|
21
|
+
startNetworkLoggerShakeToggle,
|
|
22
|
+
stopNetworkLoggerShakeToggle,
|
|
23
|
+
subscribeNetworkLoggerButtonPress,
|
|
24
|
+
} from './native-module';
|
|
25
|
+
|
|
26
|
+
export const NETWORK_LOGGER_MAX_REQUESTS = 200;
|
|
27
|
+
|
|
28
|
+
let networkLoggingStarted = false;
|
|
29
|
+
|
|
30
|
+
export const startNetworkLogger = ({
|
|
31
|
+
enabled = true,
|
|
32
|
+
maxRequests = NETWORK_LOGGER_MAX_REQUESTS,
|
|
33
|
+
} = {}) => {
|
|
34
|
+
if (!enabled || networkLoggingStarted) return false;
|
|
35
|
+
|
|
36
|
+
startNetworkLogging({ maxRequests });
|
|
37
|
+
networkLoggingStarted = true;
|
|
38
|
+
return true;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const stopNetworkLogger = () => {
|
|
42
|
+
if (!networkLoggingStarted) return false;
|
|
43
|
+
|
|
44
|
+
stopNetworkLogging();
|
|
45
|
+
networkLoggingStarted = false;
|
|
46
|
+
return true;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export { hasOverlayPermission, requestOverlayPermission };
|
|
50
|
+
export { NETWORK_LOGGER_BUTTON_PRESS_EVENT };
|
|
51
|
+
|
|
52
|
+
export const NetworkLoggerDebugController = ({
|
|
53
|
+
enabled = true,
|
|
54
|
+
maxRequests = NETWORK_LOGGER_MAX_REQUESTS,
|
|
55
|
+
maxRows = NETWORK_LOGGER_MAX_REQUESTS,
|
|
56
|
+
} = {}) => {
|
|
57
|
+
const insets = useSafeAreaInsets();
|
|
58
|
+
const [visible, setVisible] = useState(false);
|
|
59
|
+
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (!enabled) return undefined;
|
|
62
|
+
|
|
63
|
+
startNetworkLogger({ maxRequests });
|
|
64
|
+
startNetworkLoggerShakeToggle({ initiallyVisible: true });
|
|
65
|
+
const subscription = subscribeNetworkLoggerButtonPress(() => {
|
|
66
|
+
hideNetworkLoggerButton();
|
|
67
|
+
setVisible(true);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return () => {
|
|
71
|
+
subscription?.remove?.();
|
|
72
|
+
stopNetworkLoggerShakeToggle();
|
|
73
|
+
stopNetworkLogger();
|
|
74
|
+
};
|
|
75
|
+
}, [enabled, maxRequests]);
|
|
76
|
+
|
|
77
|
+
const closeViewer = () => {
|
|
78
|
+
setVisible(false);
|
|
79
|
+
if (enabled) showNetworkLoggerButton();
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
if (!enabled) return null;
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<Modal
|
|
86
|
+
animationType="slide"
|
|
87
|
+
onRequestClose={closeViewer}
|
|
88
|
+
presentationStyle="fullScreen"
|
|
89
|
+
statusBarTranslucent
|
|
90
|
+
transparent={false}
|
|
91
|
+
visible={visible}
|
|
92
|
+
>
|
|
93
|
+
<View
|
|
94
|
+
style={[
|
|
95
|
+
styles.panel,
|
|
96
|
+
{ paddingBottom: insets.bottom, paddingTop: insets.top },
|
|
97
|
+
]}
|
|
98
|
+
>
|
|
99
|
+
<View style={styles.header}>
|
|
100
|
+
<Text style={styles.title}>Network Logs</Text>
|
|
101
|
+
<TouchableOpacity
|
|
102
|
+
accessibilityLabel="Đóng network logs"
|
|
103
|
+
accessibilityRole="button"
|
|
104
|
+
onPress={closeViewer}
|
|
105
|
+
style={styles.closeButton}
|
|
106
|
+
>
|
|
107
|
+
<Text style={styles.closeText}>Đóng</Text>
|
|
108
|
+
</TouchableOpacity>
|
|
109
|
+
</View>
|
|
110
|
+
<View style={styles.logger}>
|
|
111
|
+
<NetworkLogger compact maxRows={maxRows} sort="desc" theme="dark" />
|
|
112
|
+
</View>
|
|
113
|
+
</View>
|
|
114
|
+
</Modal>
|
|
115
|
+
);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const styles = StyleSheet.create({
|
|
119
|
+
panel: {
|
|
120
|
+
backgroundColor: '#111827',
|
|
121
|
+
flex: 1,
|
|
122
|
+
},
|
|
123
|
+
header: {
|
|
124
|
+
alignItems: 'center',
|
|
125
|
+
borderBottomColor: '#374151',
|
|
126
|
+
borderBottomWidth: StyleSheet.hairlineWidth,
|
|
127
|
+
flexDirection: 'row',
|
|
128
|
+
justifyContent: 'space-between',
|
|
129
|
+
paddingHorizontal: 16,
|
|
130
|
+
paddingVertical: 12,
|
|
131
|
+
},
|
|
132
|
+
title: {
|
|
133
|
+
color: '#FFFFFF',
|
|
134
|
+
fontSize: 16,
|
|
135
|
+
fontWeight: '700',
|
|
136
|
+
},
|
|
137
|
+
closeButton: {
|
|
138
|
+
paddingHorizontal: 8,
|
|
139
|
+
paddingVertical: 6,
|
|
140
|
+
},
|
|
141
|
+
closeText: {
|
|
142
|
+
color: '#34D399',
|
|
143
|
+
fontSize: 15,
|
|
144
|
+
fontWeight: '600',
|
|
145
|
+
},
|
|
146
|
+
logger: {
|
|
147
|
+
flex: 1,
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
export default NetworkLoggerDebugController;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { NativeEventEmitter, NativeModules, Platform } from 'react-native';
|
|
2
|
+
|
|
3
|
+
const MODULE_NAME = 'BaodzNetworkLoggerDebugController';
|
|
4
|
+
const nativeModule = NativeModules[MODULE_NAME];
|
|
5
|
+
export const NETWORK_LOGGER_BUTTON_PRESS_EVENT = 'NetworkLoggerButtonPress';
|
|
6
|
+
|
|
7
|
+
const callNative = (methodName, ...args) => {
|
|
8
|
+
const method = nativeModule?.[methodName];
|
|
9
|
+
if (typeof method !== 'function') return false;
|
|
10
|
+
|
|
11
|
+
method(...args);
|
|
12
|
+
return true;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const showNetworkLoggerButton = () =>
|
|
16
|
+
callNative('showNetworkLoggerButton');
|
|
17
|
+
|
|
18
|
+
export const hideNetworkLoggerButton = () =>
|
|
19
|
+
callNative('hideNetworkLoggerButton');
|
|
20
|
+
|
|
21
|
+
export const startNetworkLoggerShakeToggle = ({
|
|
22
|
+
initiallyVisible = false,
|
|
23
|
+
} = {}) =>
|
|
24
|
+
callNative('startNetworkLoggerShakeToggle', Boolean(initiallyVisible));
|
|
25
|
+
|
|
26
|
+
export const stopNetworkLoggerShakeToggle = () =>
|
|
27
|
+
callNative('stopNetworkLoggerShakeToggle');
|
|
28
|
+
|
|
29
|
+
export const subscribeNetworkLoggerButtonPress = (listener) => {
|
|
30
|
+
if (!nativeModule) return null;
|
|
31
|
+
|
|
32
|
+
return new NativeEventEmitter(nativeModule).addListener(
|
|
33
|
+
NETWORK_LOGGER_BUTTON_PRESS_EVENT,
|
|
34
|
+
listener,
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const hasOverlayPermission = async () => {
|
|
39
|
+
if (Platform.OS !== 'android') return true;
|
|
40
|
+
|
|
41
|
+
const method = nativeModule?.hasOverlayPermission;
|
|
42
|
+
if (typeof method !== 'function') return false;
|
|
43
|
+
|
|
44
|
+
return method();
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const requestOverlayPermission = async () => {
|
|
48
|
+
if (Platform.OS !== 'android') return true;
|
|
49
|
+
|
|
50
|
+
const method = nativeModule?.requestOverlayPermission;
|
|
51
|
+
if (typeof method !== 'function') return false;
|
|
52
|
+
|
|
53
|
+
return method();
|
|
54
|
+
};
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ComponentType } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface NetworkLoggerDebugControllerProps {
|
|
4
|
+
/** Enable request capture, the floating button, and the viewer. */
|
|
5
|
+
enabled?: boolean;
|
|
6
|
+
/** Maximum number of requests retained by react-native-network-logger. */
|
|
7
|
+
maxRequests?: number;
|
|
8
|
+
/** Maximum number of request rows rendered in the viewer. */
|
|
9
|
+
maxRows?: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export declare const NETWORK_LOGGER_MAX_REQUESTS: number;
|
|
13
|
+
export declare const NETWORK_LOGGER_BUTTON_PRESS_EVENT: string;
|
|
14
|
+
export declare const NetworkLoggerDebugController: ComponentType<NetworkLoggerDebugControllerProps>;
|
|
15
|
+
export declare const startNetworkLogger: (options?: {
|
|
16
|
+
enabled?: boolean;
|
|
17
|
+
maxRequests?: number;
|
|
18
|
+
}) => boolean;
|
|
19
|
+
export declare const stopNetworkLogger: () => boolean;
|
|
20
|
+
export declare const hasOverlayPermission: () => Promise<boolean>;
|
|
21
|
+
export declare const requestOverlayPermission: () => Promise<boolean>;
|
|
22
|
+
|
|
23
|
+
export default NetworkLoggerDebugController;
|