@teardown/dev-client 2.0.44
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/android/build.gradle.kts +34 -0
- package/android/react-native.config.js +10 -0
- package/android/src/main/AndroidManifest.xml +7 -0
- package/android/src/main/java/com/teardown/devclient/DevSettingsModule.kt +130 -0
- package/android/src/main/java/com/teardown/devclient/ShakeDetectorModule.kt +118 -0
- package/android/src/main/java/com/teardown/devclient/TeardownDevClientPackage.kt +23 -0
- package/ios/TeardownDevClient/DevSettingsModule.swift +135 -0
- package/ios/TeardownDevClient/ShakeDetector.swift +102 -0
- package/ios/TeardownDevClient/TeardownDevClient.h +14 -0
- package/ios/TeardownDevClient/TeardownDevClient.mm +42 -0
- package/ios/TeardownDevClient.podspec +23 -0
- package/package.json +56 -0
- package/src/components/dev-menu/dev-menu.tsx +254 -0
- package/src/components/dev-menu/index.ts +5 -0
- package/src/components/error-overlay/error-overlay.tsx +256 -0
- package/src/components/error-overlay/index.ts +5 -0
- package/src/components/index.ts +7 -0
- package/src/components/splash-screen/index.ts +5 -0
- package/src/components/splash-screen/splash-screen.tsx +99 -0
- package/src/dev-client-provider.tsx +204 -0
- package/src/hooks/index.ts +24 -0
- package/src/hooks/use-bundler-status.ts +139 -0
- package/src/hooks/use-dev-menu.ts +306 -0
- package/src/hooks/use-splash-screen.ts +177 -0
- package/src/index.ts +77 -0
- package/src/native/dev-settings.ts +132 -0
- package/src/native/index.ts +16 -0
- package/src/native/shake-detector.ts +105 -0
- package/src/types.ts +235 -0
- package/src/utils/bundler-url.ts +103 -0
- package/src/utils/index.ts +19 -0
- package/src/utils/platform.ts +64 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
plugins {
|
|
2
|
+
id("com.android.library")
|
|
3
|
+
id("kotlin-android")
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
android {
|
|
7
|
+
namespace = "com.teardown.devclient"
|
|
8
|
+
compileSdk = 34
|
|
9
|
+
|
|
10
|
+
defaultConfig {
|
|
11
|
+
minSdk = 23
|
|
12
|
+
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
buildTypes {
|
|
16
|
+
release {
|
|
17
|
+
isMinifyEnabled = false
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
compileOptions {
|
|
22
|
+
sourceCompatibility = JavaVersion.VERSION_17
|
|
23
|
+
targetCompatibility = JavaVersion.VERSION_17
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
kotlinOptions {
|
|
27
|
+
jvmTarget = "17"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
dependencies {
|
|
32
|
+
implementation("com.facebook.react:react-android")
|
|
33
|
+
implementation("org.jetbrains.kotlin:kotlin-stdlib:1.9.22")
|
|
34
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
package com.teardown.devclient
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.*
|
|
4
|
+
import com.facebook.react.devsupport.DevInternalSettings
|
|
5
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Native module for controlling React Native dev settings
|
|
9
|
+
*/
|
|
10
|
+
class DevSettingsModule(private val reactContext: ReactApplicationContext) :
|
|
11
|
+
ReactContextBaseJavaModule(reactContext) {
|
|
12
|
+
|
|
13
|
+
override fun getName(): String = "TeardownDevSettings"
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Reload the JavaScript bundle
|
|
17
|
+
*/
|
|
18
|
+
@ReactMethod
|
|
19
|
+
fun reload() {
|
|
20
|
+
reactApplicationContext.runOnUiQueueThread {
|
|
21
|
+
try {
|
|
22
|
+
getDevSupportManager()?.handleReloadJS()
|
|
23
|
+
} catch (e: Exception) {
|
|
24
|
+
// Ignore errors if dev support is not available
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Open the debugger
|
|
31
|
+
*/
|
|
32
|
+
@ReactMethod
|
|
33
|
+
fun openDebugger() {
|
|
34
|
+
reactApplicationContext.runOnUiQueueThread {
|
|
35
|
+
try {
|
|
36
|
+
getDevSupportManager()?.showDevOptionsDialog()
|
|
37
|
+
} catch (e: Exception) {
|
|
38
|
+
// Ignore errors if dev support is not available
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Toggle element inspector overlay
|
|
45
|
+
*/
|
|
46
|
+
@ReactMethod
|
|
47
|
+
fun toggleElementInspector() {
|
|
48
|
+
reactApplicationContext.runOnUiQueueThread {
|
|
49
|
+
try {
|
|
50
|
+
getDevSupportManager()?.toggleElementInspector()
|
|
51
|
+
} catch (e: Exception) {
|
|
52
|
+
// Ignore errors if dev support is not available
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Set hot loading enabled state
|
|
59
|
+
*/
|
|
60
|
+
@ReactMethod
|
|
61
|
+
fun setHotLoadingEnabled(enabled: Boolean) {
|
|
62
|
+
try {
|
|
63
|
+
getDevSettings()?.isHotModuleReplacementEnabled = enabled
|
|
64
|
+
} catch (e: Exception) {
|
|
65
|
+
// Ignore errors
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Set fast refresh enabled state
|
|
71
|
+
*/
|
|
72
|
+
@ReactMethod
|
|
73
|
+
fun setFastRefreshEnabled(enabled: Boolean) {
|
|
74
|
+
// Fast refresh uses the same setting as hot loading
|
|
75
|
+
setHotLoadingEnabled(enabled)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Get current dev settings
|
|
80
|
+
*/
|
|
81
|
+
@ReactMethod
|
|
82
|
+
fun getDevSettings(promise: Promise) {
|
|
83
|
+
try {
|
|
84
|
+
val devSettings = getDevSettings()
|
|
85
|
+
val result = Arguments.createMap().apply {
|
|
86
|
+
putBoolean("isDebuggingRemotely", devSettings?.isRemoteJSDebugEnabled ?: false)
|
|
87
|
+
putBoolean("isElementInspectorShown", false) // Not easily accessible on Android
|
|
88
|
+
putBoolean("isHotLoadingEnabled", devSettings?.isHotModuleReplacementEnabled ?: false)
|
|
89
|
+
putBoolean("isFastRefreshEnabled", devSettings?.isHotModuleReplacementEnabled ?: false)
|
|
90
|
+
putBoolean("isPerfMonitorShown", devSettings?.isFpsDebugEnabled ?: false)
|
|
91
|
+
}
|
|
92
|
+
promise.resolve(result)
|
|
93
|
+
} catch (e: Exception) {
|
|
94
|
+
// Return defaults if dev settings not available
|
|
95
|
+
val result = Arguments.createMap().apply {
|
|
96
|
+
putBoolean("isDebuggingRemotely", false)
|
|
97
|
+
putBoolean("isElementInspectorShown", false)
|
|
98
|
+
putBoolean("isHotLoadingEnabled", false)
|
|
99
|
+
putBoolean("isFastRefreshEnabled", false)
|
|
100
|
+
putBoolean("isPerfMonitorShown", false)
|
|
101
|
+
}
|
|
102
|
+
promise.resolve(result)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Open native dev menu
|
|
108
|
+
*/
|
|
109
|
+
@ReactMethod
|
|
110
|
+
fun openDevMenu() {
|
|
111
|
+
reactApplicationContext.runOnUiQueueThread {
|
|
112
|
+
try {
|
|
113
|
+
getDevSupportManager()?.showDevOptionsDialog()
|
|
114
|
+
} catch (e: Exception) {
|
|
115
|
+
// Ignore errors if dev support is not available
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private fun getDevSupportManager() =
|
|
121
|
+
reactApplicationContext.currentActivity?.let { activity ->
|
|
122
|
+
(activity.application as? com.facebook.react.ReactApplication)
|
|
123
|
+
?.reactNativeHost
|
|
124
|
+
?.reactInstanceManager
|
|
125
|
+
?.devSupportManager
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private fun getDevSettings(): DevInternalSettings? =
|
|
129
|
+
getDevSupportManager()?.devSettings as? DevInternalSettings
|
|
130
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
package com.teardown.devclient
|
|
2
|
+
|
|
3
|
+
import android.hardware.Sensor
|
|
4
|
+
import android.hardware.SensorEvent
|
|
5
|
+
import android.hardware.SensorEventListener
|
|
6
|
+
import android.hardware.SensorManager
|
|
7
|
+
import android.content.Context
|
|
8
|
+
import com.facebook.react.bridge.*
|
|
9
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
10
|
+
import kotlin.math.sqrt
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Native module for detecting shake gestures
|
|
14
|
+
*/
|
|
15
|
+
class ShakeDetectorModule(private val reactContext: ReactApplicationContext) :
|
|
16
|
+
ReactContextBaseJavaModule(reactContext), SensorEventListener {
|
|
17
|
+
|
|
18
|
+
private var sensorManager: SensorManager? = null
|
|
19
|
+
private var accelerometer: Sensor? = null
|
|
20
|
+
private var isListening = false
|
|
21
|
+
|
|
22
|
+
// Shake detection parameters
|
|
23
|
+
private var lastShakeTime: Long = 0
|
|
24
|
+
private var lastX = 0f
|
|
25
|
+
private var lastY = 0f
|
|
26
|
+
private var lastZ = 0f
|
|
27
|
+
private var lastUpdate: Long = 0
|
|
28
|
+
|
|
29
|
+
companion object {
|
|
30
|
+
private const val SHAKE_THRESHOLD = 800
|
|
31
|
+
private const val SHAKE_SLOP_TIME_MS = 500
|
|
32
|
+
private const val SHAKE_COUNT_RESET_TIME_MS = 3000
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
override fun getName(): String = "TeardownShakeDetector"
|
|
36
|
+
|
|
37
|
+
override fun initialize() {
|
|
38
|
+
super.initialize()
|
|
39
|
+
sensorManager = reactContext.getSystemService(Context.SENSOR_SERVICE) as? SensorManager
|
|
40
|
+
accelerometer = sensorManager?.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Start listening for shake gestures
|
|
45
|
+
*/
|
|
46
|
+
@ReactMethod
|
|
47
|
+
fun startListening() {
|
|
48
|
+
if (isListening) return
|
|
49
|
+
|
|
50
|
+
accelerometer?.let { sensor ->
|
|
51
|
+
sensorManager?.registerListener(
|
|
52
|
+
this,
|
|
53
|
+
sensor,
|
|
54
|
+
SensorManager.SENSOR_DELAY_UI
|
|
55
|
+
)
|
|
56
|
+
isListening = true
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Stop listening for shake gestures
|
|
62
|
+
*/
|
|
63
|
+
@ReactMethod
|
|
64
|
+
fun stopListening() {
|
|
65
|
+
if (!isListening) return
|
|
66
|
+
|
|
67
|
+
sensorManager?.unregisterListener(this)
|
|
68
|
+
isListening = false
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
override fun onSensorChanged(event: SensorEvent?) {
|
|
72
|
+
event ?: return
|
|
73
|
+
|
|
74
|
+
val currentTime = System.currentTimeMillis()
|
|
75
|
+
|
|
76
|
+
if ((currentTime - lastUpdate) > 100) {
|
|
77
|
+
val diffTime = currentTime - lastUpdate
|
|
78
|
+
lastUpdate = currentTime
|
|
79
|
+
|
|
80
|
+
val x = event.values[0]
|
|
81
|
+
val y = event.values[1]
|
|
82
|
+
val z = event.values[2]
|
|
83
|
+
|
|
84
|
+
val speed = sqrt(
|
|
85
|
+
((x - lastX) * (x - lastX) +
|
|
86
|
+
(y - lastY) * (y - lastY) +
|
|
87
|
+
(z - lastZ) * (z - lastZ)).toDouble()
|
|
88
|
+
) / diffTime * 10000
|
|
89
|
+
|
|
90
|
+
if (speed > SHAKE_THRESHOLD) {
|
|
91
|
+
// Debounce shake events
|
|
92
|
+
if (currentTime - lastShakeTime > SHAKE_SLOP_TIME_MS) {
|
|
93
|
+
lastShakeTime = currentTime
|
|
94
|
+
emitShakeEvent()
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
lastX = x
|
|
99
|
+
lastY = y
|
|
100
|
+
lastZ = z
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
|
|
105
|
+
// Not needed
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private fun emitShakeEvent() {
|
|
109
|
+
reactContext
|
|
110
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
111
|
+
.emit("TeardownShakeEvent", null)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
override fun onCatalystInstanceDestroy() {
|
|
115
|
+
super.onCatalystInstanceDestroy()
|
|
116
|
+
stopListening()
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
package com.teardown.devclient
|
|
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
|
+
/**
|
|
9
|
+
* React Native package for Teardown Dev Client
|
|
10
|
+
*/
|
|
11
|
+
class TeardownDevClientPackage : ReactPackage {
|
|
12
|
+
|
|
13
|
+
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
|
14
|
+
return listOf(
|
|
15
|
+
DevSettingsModule(reactContext),
|
|
16
|
+
ShakeDetectorModule(reactContext)
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
21
|
+
return emptyList()
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
//
|
|
2
|
+
// DevSettingsModule.swift
|
|
3
|
+
// TeardownDevClient
|
|
4
|
+
//
|
|
5
|
+
// Native module for controlling React Native dev settings
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import Foundation
|
|
9
|
+
import React
|
|
10
|
+
|
|
11
|
+
@objc(TeardownDevSettings)
|
|
12
|
+
class DevSettingsModule: NSObject {
|
|
13
|
+
|
|
14
|
+
private var bridge: RCTBridge?
|
|
15
|
+
|
|
16
|
+
@objc
|
|
17
|
+
static func moduleName() -> String! {
|
|
18
|
+
return "TeardownDevSettings"
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@objc
|
|
22
|
+
func setBridge(_ bridge: RCTBridge) {
|
|
23
|
+
self.bridge = bridge
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/// Reload the JavaScript bundle
|
|
27
|
+
@objc
|
|
28
|
+
func reload() {
|
|
29
|
+
DispatchQueue.main.async {
|
|
30
|
+
RCTTriggerReloadCommandListeners("TeardownDevClient")
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/// Open the debugger
|
|
35
|
+
@objc
|
|
36
|
+
func openDebugger() {
|
|
37
|
+
DispatchQueue.main.async {
|
|
38
|
+
#if DEBUG
|
|
39
|
+
// In debug mode, this would open the debugger
|
|
40
|
+
// The actual implementation depends on the debugger being used
|
|
41
|
+
NotificationCenter.default.post(
|
|
42
|
+
name: NSNotification.Name("RCTShowDevMenuNotification"),
|
|
43
|
+
object: nil
|
|
44
|
+
)
|
|
45
|
+
#endif
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/// Toggle element inspector overlay
|
|
50
|
+
@objc
|
|
51
|
+
func toggleElementInspector() {
|
|
52
|
+
DispatchQueue.main.async {
|
|
53
|
+
#if DEBUG
|
|
54
|
+
if let devSettings = RCTDevSettings.sharedInstance() {
|
|
55
|
+
devSettings.toggleElementInspector()
|
|
56
|
+
}
|
|
57
|
+
#endif
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/// Set hot loading enabled state
|
|
62
|
+
@objc(setHotLoadingEnabled:)
|
|
63
|
+
func setHotLoadingEnabled(_ enabled: Bool) {
|
|
64
|
+
DispatchQueue.main.async {
|
|
65
|
+
#if DEBUG
|
|
66
|
+
if let devSettings = RCTDevSettings.sharedInstance() {
|
|
67
|
+
devSettings.isHotLoadingEnabled = enabled
|
|
68
|
+
}
|
|
69
|
+
#endif
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/// Set fast refresh enabled state
|
|
74
|
+
@objc(setFastRefreshEnabled:)
|
|
75
|
+
func setFastRefreshEnabled(_ enabled: Bool) {
|
|
76
|
+
// Fast refresh uses the same setting as hot loading in RN
|
|
77
|
+
setHotLoadingEnabled(enabled)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/// Get current dev settings
|
|
81
|
+
@objc(getDevSettings:rejecter:)
|
|
82
|
+
func getDevSettings(
|
|
83
|
+
_ resolve: @escaping RCTPromiseResolveBlock,
|
|
84
|
+
rejecter reject: @escaping RCTPromiseRejectBlock
|
|
85
|
+
) {
|
|
86
|
+
DispatchQueue.main.async {
|
|
87
|
+
#if DEBUG
|
|
88
|
+
guard let devSettings = RCTDevSettings.sharedInstance() else {
|
|
89
|
+
let settings: [String: Any] = [
|
|
90
|
+
"isDebuggingRemotely": false,
|
|
91
|
+
"isElementInspectorShown": false,
|
|
92
|
+
"isHotLoadingEnabled": true,
|
|
93
|
+
"isFastRefreshEnabled": true,
|
|
94
|
+
"isPerfMonitorShown": false
|
|
95
|
+
]
|
|
96
|
+
resolve(settings)
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let settings: [String: Any] = [
|
|
101
|
+
"isDebuggingRemotely": devSettings.isDebuggingRemotely,
|
|
102
|
+
"isElementInspectorShown": devSettings.isElementInspectorShown,
|
|
103
|
+
"isHotLoadingEnabled": devSettings.isHotLoadingEnabled,
|
|
104
|
+
"isFastRefreshEnabled": devSettings.isHotLoadingEnabled,
|
|
105
|
+
"isPerfMonitorShown": devSettings.isPerfMonitorShown
|
|
106
|
+
]
|
|
107
|
+
|
|
108
|
+
resolve(settings)
|
|
109
|
+
#else
|
|
110
|
+
// In release mode, return defaults
|
|
111
|
+
let settings: [String: Any] = [
|
|
112
|
+
"isDebuggingRemotely": false,
|
|
113
|
+
"isElementInspectorShown": false,
|
|
114
|
+
"isHotLoadingEnabled": false,
|
|
115
|
+
"isFastRefreshEnabled": false,
|
|
116
|
+
"isPerfMonitorShown": false
|
|
117
|
+
]
|
|
118
|
+
resolve(settings)
|
|
119
|
+
#endif
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/// Open native dev menu
|
|
124
|
+
@objc
|
|
125
|
+
func openDevMenu() {
|
|
126
|
+
DispatchQueue.main.async {
|
|
127
|
+
#if DEBUG
|
|
128
|
+
NotificationCenter.default.post(
|
|
129
|
+
name: NSNotification.Name("RCTShowDevMenuNotification"),
|
|
130
|
+
object: nil
|
|
131
|
+
)
|
|
132
|
+
#endif
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
//
|
|
2
|
+
// ShakeDetector.swift
|
|
3
|
+
// TeardownDevClient
|
|
4
|
+
//
|
|
5
|
+
// Native module for detecting shake gestures
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import Foundation
|
|
9
|
+
import UIKit
|
|
10
|
+
import React
|
|
11
|
+
|
|
12
|
+
@objc(TeardownShakeDetector)
|
|
13
|
+
class ShakeDetector: RCTEventEmitter {
|
|
14
|
+
|
|
15
|
+
private var isListening = false
|
|
16
|
+
private var motionManager: CMMotionManager?
|
|
17
|
+
|
|
18
|
+
override init() {
|
|
19
|
+
super.init()
|
|
20
|
+
setupShakeNotification()
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@objc
|
|
24
|
+
override static func moduleName() -> String! {
|
|
25
|
+
return "TeardownShakeDetector"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@objc
|
|
29
|
+
override static func requiresMainQueueSetup() -> Bool {
|
|
30
|
+
return true
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@objc
|
|
34
|
+
override func supportedEvents() -> [String]! {
|
|
35
|
+
return ["TeardownShakeEvent"]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private func setupShakeNotification() {
|
|
39
|
+
// Listen for shake motion events from the app delegate
|
|
40
|
+
NotificationCenter.default.addObserver(
|
|
41
|
+
self,
|
|
42
|
+
selector: #selector(handleShakeMotion),
|
|
43
|
+
name: NSNotification.Name("TeardownShakeMotion"),
|
|
44
|
+
object: nil
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@objc
|
|
49
|
+
private func handleShakeMotion() {
|
|
50
|
+
guard isListening else { return }
|
|
51
|
+
|
|
52
|
+
DispatchQueue.main.async {
|
|
53
|
+
self.sendEvent(withName: "TeardownShakeEvent", body: nil)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/// Start listening for shake gestures
|
|
58
|
+
@objc
|
|
59
|
+
func startListening() {
|
|
60
|
+
isListening = true
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/// Stop listening for shake gestures
|
|
64
|
+
@objc
|
|
65
|
+
func stopListening() {
|
|
66
|
+
isListening = false
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
deinit {
|
|
70
|
+
NotificationCenter.default.removeObserver(self)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// MARK: - UIWindow Extension for Shake Detection
|
|
75
|
+
|
|
76
|
+
// This extension should be added to the app's AppDelegate to enable shake detection
|
|
77
|
+
// Example usage in AppDelegate:
|
|
78
|
+
//
|
|
79
|
+
// override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
|
|
80
|
+
// if motion == .motionShake {
|
|
81
|
+
// NotificationCenter.default.post(name: NSNotification.Name("TeardownShakeMotion"), object: nil)
|
|
82
|
+
// }
|
|
83
|
+
// }
|
|
84
|
+
|
|
85
|
+
import CoreMotion
|
|
86
|
+
|
|
87
|
+
private var associatedMotionManagerKey: UInt8 = 0
|
|
88
|
+
|
|
89
|
+
extension UIWindow {
|
|
90
|
+
|
|
91
|
+
/// Override to detect shake motion
|
|
92
|
+
open override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
|
|
93
|
+
super.motionEnded(motion, with: event)
|
|
94
|
+
|
|
95
|
+
if motion == .motionShake {
|
|
96
|
+
NotificationCenter.default.post(
|
|
97
|
+
name: NSNotification.Name("TeardownShakeMotion"),
|
|
98
|
+
object: nil
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//
|
|
2
|
+
// TeardownDevClient.h
|
|
3
|
+
// TeardownDevClient
|
|
4
|
+
//
|
|
5
|
+
// Umbrella header for TeardownDevClient native module
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
#import <Foundation/Foundation.h>
|
|
9
|
+
|
|
10
|
+
//! Project version number for TeardownDevClient.
|
|
11
|
+
FOUNDATION_EXPORT double TeardownDevClientVersionNumber;
|
|
12
|
+
|
|
13
|
+
//! Project version string for TeardownDevClient.
|
|
14
|
+
FOUNDATION_EXPORT const unsigned char TeardownDevClientVersionString[];
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
//
|
|
2
|
+
// TeardownDevClient.mm
|
|
3
|
+
// TeardownDevClient
|
|
4
|
+
//
|
|
5
|
+
// Bridge file for React Native integration
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
#import <React/RCTBridgeModule.h>
|
|
9
|
+
#import <React/RCTEventEmitter.h>
|
|
10
|
+
|
|
11
|
+
// TeardownDevSettings module
|
|
12
|
+
@interface RCT_EXTERN_MODULE(TeardownDevSettings, NSObject)
|
|
13
|
+
|
|
14
|
+
RCT_EXTERN_METHOD(reload)
|
|
15
|
+
RCT_EXTERN_METHOD(openDebugger)
|
|
16
|
+
RCT_EXTERN_METHOD(toggleElementInspector)
|
|
17
|
+
RCT_EXTERN_METHOD(setHotLoadingEnabled:(BOOL)enabled)
|
|
18
|
+
RCT_EXTERN_METHOD(setFastRefreshEnabled:(BOOL)enabled)
|
|
19
|
+
RCT_EXTERN_METHOD(getDevSettings:(RCTPromiseResolveBlock)resolve
|
|
20
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
21
|
+
RCT_EXTERN_METHOD(openDevMenu)
|
|
22
|
+
|
|
23
|
+
+ (BOOL)requiresMainQueueSetup
|
|
24
|
+
{
|
|
25
|
+
return YES;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@end
|
|
29
|
+
|
|
30
|
+
// TeardownShakeDetector module
|
|
31
|
+
@interface RCT_EXTERN_MODULE(TeardownShakeDetector, RCTEventEmitter)
|
|
32
|
+
|
|
33
|
+
RCT_EXTERN_METHOD(startListening)
|
|
34
|
+
RCT_EXTERN_METHOD(stopListening)
|
|
35
|
+
RCT_EXTERN_METHOD(supportedEvents)
|
|
36
|
+
|
|
37
|
+
+ (BOOL)requiresMainQueueSetup
|
|
38
|
+
{
|
|
39
|
+
return YES;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = "TeardownDevClient"
|
|
7
|
+
s.version = package['version']
|
|
8
|
+
s.summary = "Development client for Teardown React Native apps"
|
|
9
|
+
s.description = <<-DESC
|
|
10
|
+
Provides development client functionality including dev menu,
|
|
11
|
+
splash screen control, and shake detection for Teardown apps.
|
|
12
|
+
DESC
|
|
13
|
+
s.homepage = "https://github.com/teardown-dev/teardown"
|
|
14
|
+
s.license = package['license']
|
|
15
|
+
s.authors = { "Teardown" => "dev@teardown.dev" }
|
|
16
|
+
s.platforms = { :ios => "13.4" }
|
|
17
|
+
s.source = { :git => "https://github.com/teardown-dev/teardown.git", :tag => "v#{s.version}" }
|
|
18
|
+
|
|
19
|
+
s.source_files = "TeardownDevClient/**/*.{h,m,mm,swift}"
|
|
20
|
+
s.swift_version = "5.0"
|
|
21
|
+
|
|
22
|
+
s.dependency "React-Core"
|
|
23
|
+
end
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@teardown/dev-client",
|
|
3
|
+
"version": "2.0.44",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"src/**/*",
|
|
11
|
+
"ios/**/*",
|
|
12
|
+
"android/**/*"
|
|
13
|
+
],
|
|
14
|
+
"main": "./src/index.ts",
|
|
15
|
+
"module": "./src/index.ts",
|
|
16
|
+
"types": "./src/index.ts",
|
|
17
|
+
"exports": {
|
|
18
|
+
"./package.json": "./package.json",
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./src/index.ts",
|
|
21
|
+
"import": "./src/index.ts",
|
|
22
|
+
"default": "./src/index.ts"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"typecheck": "tsc --noEmit --project ./tsconfig.json",
|
|
27
|
+
"lint": "bun x biome lint --write ./src",
|
|
28
|
+
"fmt": "bun x biome format --write ./src",
|
|
29
|
+
"check": "bun x biome check ./src",
|
|
30
|
+
"test": "bun test"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"eventemitter3": "^5.0.1"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@biomejs/biome": "2.3.11",
|
|
37
|
+
"@teardown/tsconfig": "2.0.44",
|
|
38
|
+
"@types/bun": "1.3.5",
|
|
39
|
+
"@types/react": "~19.1.0",
|
|
40
|
+
"react": "19.1.0",
|
|
41
|
+
"react-native": "0.81.5",
|
|
42
|
+
"typescript": "5.9.3"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"react": ">=18",
|
|
46
|
+
"react-native": ">=0.73"
|
|
47
|
+
},
|
|
48
|
+
"peerDependenciesMeta": {
|
|
49
|
+
"react": {
|
|
50
|
+
"optional": false
|
|
51
|
+
},
|
|
52
|
+
"react-native": {
|
|
53
|
+
"optional": false
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|