feat(react-native): expo support (#2850)
* change(react-native): android version jump * change(react-native): updates to support expo * change(react-native): swipe event fix * change(react-native): version jump * change(react-native): include plugin file and version jump
This commit is contained in:
parent
e74effe24d
commit
9a37ba0739
5 changed files with 139 additions and 217 deletions
|
|
@ -91,7 +91,7 @@ dependencies {
|
|||
//noinspection GradleDynamicVersion
|
||||
implementation("com.facebook.react:react-native:0.20.1")
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version")
|
||||
implementation("com.github.openreplay:android-tracker:v1.1.2")
|
||||
implementation("com.github.openreplay:android-tracker:v1.1.3")
|
||||
}
|
||||
|
||||
//allprojects {
|
||||
|
|
|
|||
|
|
@ -10,18 +10,10 @@ import com.openreplay.tracker.models.OROptions
|
|||
class ReactNativeModule(reactContext: ReactApplicationContext) :
|
||||
ReactContextBaseJavaModule(reactContext) {
|
||||
|
||||
// private val context = reactContext.acti
|
||||
override fun getName(): String {
|
||||
return NAME
|
||||
}
|
||||
|
||||
// Example method
|
||||
// See https://reactnative.dev/docs/native-modules-android
|
||||
@ReactMethod
|
||||
fun multiply(a: Double, b: Double, promise: Promise) {
|
||||
promise.resolve(a * b * 2)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val NAME = "ORTrackerConnector"
|
||||
}
|
||||
|
|
@ -33,14 +25,13 @@ class ReactNativeModule(reactContext: ReactApplicationContext) :
|
|||
val logs: Boolean = true,
|
||||
val screen: Boolean = true,
|
||||
val debugLogs: Boolean = false,
|
||||
val wifiOnly: Boolean = true // assuming you want this as well
|
||||
val wifiOnly: Boolean = true
|
||||
)
|
||||
|
||||
private fun getBooleanOrDefault(map: ReadableMap, key: String, default: Boolean): Boolean {
|
||||
return if (map.hasKey(key)) map.getBoolean(key) else default
|
||||
}
|
||||
|
||||
// optionsMap: ReadableMap?,
|
||||
@ReactMethod
|
||||
fun startSession(
|
||||
projectKey: String,
|
||||
|
|
@ -97,8 +88,8 @@ class ReactNativeModule(reactContext: ReactApplicationContext) :
|
|||
@ReactMethod
|
||||
fun getSessionID(promise: Promise) {
|
||||
try {
|
||||
val sessionId = OpenReplay.getSessionID() ?: ""
|
||||
promise.resolve(sessionId) // Resolve the promise with the session ID
|
||||
val sessionId = OpenReplay.getSessionID()
|
||||
promise.resolve(sessionId)
|
||||
} catch (e: Exception) {
|
||||
promise.reject("GET_SESSION_ID_ERROR", "Failed to retrieve session ID", e)
|
||||
}
|
||||
|
|
@ -111,8 +102,9 @@ class ReactNativeModule(reactContext: ReactApplicationContext) :
|
|||
requestJSON: String,
|
||||
responseJSON: String,
|
||||
status: Int,
|
||||
duration: ULong
|
||||
duration: Double
|
||||
) {
|
||||
OpenReplay.networkRequest(url, method, requestJSON, responseJSON, status, duration)
|
||||
val durationULong = duration.toLong().toULong()
|
||||
OpenReplay.networkRequest(url, method, requestJSON, responseJSON, status, durationULong)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
package com.openreplay.reactnative
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.graphics.PointF
|
||||
import android.util.Log
|
||||
import android.view.GestureDetector
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.Toast
|
||||
import com.facebook.react.uimanager.SimpleViewManager
|
||||
import com.facebook.react.uimanager.ThemedReactContext
|
||||
import com.facebook.react.uimanager.ViewGroupManager
|
||||
import com.openreplay.tracker.listeners.Analytics
|
||||
|
|
@ -15,151 +15,16 @@ import com.openreplay.tracker.listeners.SwipeDirection
|
|||
import kotlin.math.abs
|
||||
import kotlin.math.sqrt
|
||||
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import android.view.GestureDetector
|
||||
import com.facebook.react.ReactRootView
|
||||
|
||||
//class RnTrackerTouchManager : ViewGroupManager<TouchableFrameLayout>() {
|
||||
// override fun getName(): String = "RnTrackerTouchView"
|
||||
//
|
||||
// override fun createViewInstance(reactContext: ThemedReactContext): TouchableFrameLayout {
|
||||
// return TouchableFrameLayout(reactContext)
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//class TouchableFrameLayout(context: Context) : FrameLayout(context) {
|
||||
// private var gestureDetector: GestureDetector
|
||||
// private var handler = Handler(Looper.getMainLooper())
|
||||
// private var isScrolling = false
|
||||
// private var lastX: Float = 0f
|
||||
// private var lastY: Float = 0f
|
||||
// private var swipeDirection: SwipeDirection = SwipeDirection.UNDEFINED
|
||||
//
|
||||
// init {
|
||||
// gestureDetector = GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() {
|
||||
// override fun onSingleTapUp(e: MotionEvent): Boolean {
|
||||
// Analytics.sendClick(e)
|
||||
// return true
|
||||
// }
|
||||
//
|
||||
// override fun onDown(e: MotionEvent): Boolean = true
|
||||
//
|
||||
// override fun onScroll(e1: MotionEvent?, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
|
||||
// if (!isScrolling) {
|
||||
// isScrolling = true
|
||||
// }
|
||||
//
|
||||
// swipeDirection = SwipeDirection.fromDistances(distanceX, distanceY)
|
||||
// lastX = e2.x
|
||||
// lastY = e2.y
|
||||
//
|
||||
// handler.removeCallbacksAndMessages(null)
|
||||
// handler.postDelayed({
|
||||
// if (isScrolling) {
|
||||
// isScrolling = false
|
||||
// Analytics.sendSwipe(swipeDirection, lastX, lastY)
|
||||
// }
|
||||
// }, 200)
|
||||
// return true
|
||||
// }
|
||||
// })
|
||||
//
|
||||
// setOnTouchListener { _, event ->
|
||||
// Log.d("TouchEvent", "Event: ${event.actionMasked}, X: ${event.x}, Y: ${event.y}")
|
||||
// gestureDetector.onTouchEvent(event)
|
||||
// this.performClick()
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
class RnTrackerTouchManager : ViewGroupManager<FrameLayout>() {
|
||||
|
||||
override fun getName(): String = "RnTrackerTouchView"
|
||||
|
||||
override fun createViewInstance(reactContext: ThemedReactContext): FrameLayout {
|
||||
return ReactRootView(reactContext).apply {
|
||||
// layoutParams = FrameLayout.LayoutParams(
|
||||
// FrameLayout.LayoutParams.MATCH_PARENT,
|
||||
// FrameLayout.LayoutParams.MATCH_PARENT
|
||||
// )
|
||||
// isClickable = true
|
||||
// val touchStart = PointF()
|
||||
// setOnTouchListener { view, event ->
|
||||
// when (event.action) {
|
||||
// MotionEvent.ACTION_DOWN -> {
|
||||
// touchStart.set(event.x, event.y)
|
||||
// true
|
||||
// }
|
||||
//
|
||||
// MotionEvent.ACTION_UP -> {
|
||||
// val deltaX = event.x - touchStart.x
|
||||
// val deltaY = event.y - touchStart.y
|
||||
// val distance = sqrt(deltaX * deltaX + deltaY * deltaY)
|
||||
//
|
||||
// if (distance > 10) {
|
||||
// val direction = if (abs(deltaX) > abs(deltaY)) {
|
||||
// if (deltaX > 0) "RIGHT" else "LEFT"
|
||||
// } else {
|
||||
// if (deltaY > 0) "DOWN" else "UP"
|
||||
// }
|
||||
// Analytics.sendSwipe(SwipeDirection.valueOf(direction), event.x, event.y)
|
||||
// } else {
|
||||
// Analytics.sendClick(event)
|
||||
// view.performClick() // Perform click for accessibility
|
||||
// }
|
||||
// true
|
||||
// }
|
||||
//
|
||||
// else -> false
|
||||
// }
|
||||
// }
|
||||
}
|
||||
return RnTrackerRootLayout(reactContext)
|
||||
}
|
||||
|
||||
override fun addView(parent: FrameLayout, child: View, index: Int) {
|
||||
child.isClickable = true
|
||||
child.isFocusable = true
|
||||
// child.layoutParams = FrameLayout.LayoutParams(
|
||||
// FrameLayout.LayoutParams.MATCH_PARENT,
|
||||
// FrameLayout.LayoutParams.MATCH_PARENT
|
||||
// )
|
||||
val touchStart = PointF()
|
||||
child.setOnTouchListener(
|
||||
View.OnTouchListener { view, event ->
|
||||
when (event.action) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
view.performClick()
|
||||
Analytics.sendClick(event)
|
||||
true
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_UP -> {
|
||||
val deltaX = event.x - touchStart.x
|
||||
val deltaY = event.y - touchStart.y
|
||||
val distance = sqrt(deltaX * deltaX + deltaY * deltaY)
|
||||
|
||||
if (distance > 10) {
|
||||
val direction = if (abs(deltaX) > abs(deltaY)) {
|
||||
if (deltaX > 0) "RIGHT" else "LEFT"
|
||||
} else {
|
||||
if (deltaY > 0) "DOWN" else "UP"
|
||||
}
|
||||
Analytics.sendSwipe(SwipeDirection.valueOf(direction), event.x, event.y)
|
||||
} else {
|
||||
Analytics.sendClick(event)
|
||||
view.performClick() // Perform click for accessibility
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
)
|
||||
parent.addView(child)
|
||||
parent.addView(child, index)
|
||||
}
|
||||
|
||||
override fun getChildCount(parent: FrameLayout): Int = parent.childCount
|
||||
|
|
@ -175,63 +40,102 @@ class RnTrackerTouchManager : ViewGroupManager<FrameLayout>() {
|
|||
}
|
||||
}
|
||||
|
||||
//class RnTrackerTouchManager : ViewGroupManager<FrameLayout>() {
|
||||
// override fun getName(): String = "RnTrackerTouchView"
|
||||
//
|
||||
// override fun createViewInstance(reactContext: ThemedReactContext): FrameLayout {
|
||||
// return FrameLayout(reactContext).apply {
|
||||
// layoutParams = FrameLayout.LayoutParams(
|
||||
// FrameLayout.LayoutParams.MATCH_PARENT,
|
||||
// FrameLayout.LayoutParams.MATCH_PARENT
|
||||
// )
|
||||
// isClickable = true
|
||||
// val touchStart = PointF()
|
||||
// setOnTouchListener { view, event ->
|
||||
// when (event.action) {
|
||||
// MotionEvent.ACTION_DOWN -> {
|
||||
// touchStart.set(event.x, event.y)
|
||||
// view.performClick()
|
||||
// }
|
||||
//
|
||||
// MotionEvent.ACTION_UP -> {
|
||||
// val deltaX = event.x - touchStart.x
|
||||
// val deltaY = event.y - touchStart.y
|
||||
// val distance = sqrt(deltaX * deltaX + deltaY * deltaY)
|
||||
//
|
||||
// if (distance > 10) {
|
||||
// val direction = if (abs(deltaX) > abs(deltaY)) {
|
||||
// if (deltaX > 0) "RIGHT" else "LEFT"
|
||||
// } else {
|
||||
// if (deltaY > 0) "DOWN" else "UP"
|
||||
// }
|
||||
// Analytics.sendSwipe(SwipeDirection.valueOf(direction), event.x, event.y)
|
||||
// view.performClick()
|
||||
// } else {
|
||||
// Analytics.sendClick(event)
|
||||
// view.performClick()
|
||||
// }
|
||||
// true
|
||||
// }
|
||||
//
|
||||
// else -> false
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// override fun addView(parent: FrameLayout, child: View, index: Int) {
|
||||
// parent.addView(child, index)
|
||||
// }
|
||||
//
|
||||
// override fun getChildCount(parent: FrameLayout): Int = parent.childCount
|
||||
//
|
||||
// override fun getChildAt(parent: FrameLayout, index: Int): View = parent.getChildAt(index)
|
||||
//
|
||||
// override fun removeViewAt(parent: FrameLayout, index: Int) {
|
||||
// parent.removeViewAt(index)
|
||||
// }
|
||||
//
|
||||
// override fun removeAllViews(parent: FrameLayout) {
|
||||
// parent.removeAllViews()
|
||||
// }
|
||||
//}
|
||||
class RnTrackerRootLayout(context: Context) : FrameLayout(context) {
|
||||
private val touchStart = PointF()
|
||||
private val gestureDetector: GestureDetector
|
||||
|
||||
private var currentTappedView: View? = null
|
||||
|
||||
// Variables to track total movement
|
||||
private var totalDeltaX: Float = 0f
|
||||
private var totalDeltaY: Float = 0f
|
||||
|
||||
init {
|
||||
gestureDetector = GestureDetector(context, GestureListener())
|
||||
}
|
||||
|
||||
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
|
||||
// Pass all touch events to the GestureDetector
|
||||
gestureDetector.onTouchEvent(ev)
|
||||
|
||||
when (ev.action) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
// Record the starting point for potential swipe
|
||||
touchStart.x = ev.x
|
||||
touchStart.y = ev.y
|
||||
// Reset total movement
|
||||
totalDeltaX = 0f
|
||||
totalDeltaY = 0f
|
||||
// Find and store the view that was touched
|
||||
currentTappedView = findViewAt(this, ev.x.toInt(), ev.y.toInt())
|
||||
// Log.d(
|
||||
// "RnTrackerRootLayout",
|
||||
// "ACTION_DOWN at global: (${ev.rawX}, ${ev.rawY}) on view: $currentTappedView"
|
||||
// )
|
||||
}
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
// Accumulate movement
|
||||
val deltaX = ev.x - touchStart.x
|
||||
val deltaY = ev.y - touchStart.y
|
||||
totalDeltaX += deltaX
|
||||
totalDeltaY += deltaY
|
||||
// Update touchStart for the next move event
|
||||
touchStart.x = ev.x
|
||||
touchStart.y = ev.y
|
||||
// Log.d("RnTrackerRootLayout", "Accumulated movement - X: $totalDeltaX, Y: $totalDeltaY")
|
||||
}
|
||||
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
|
||||
// Determine if the accumulated movement qualifies as a swipe
|
||||
val distance = sqrt(totalDeltaX * totalDeltaX + totalDeltaY * totalDeltaY)
|
||||
|
||||
if (distance > SWIPE_DISTANCE_THRESHOLD) {
|
||||
val direction = if (abs(totalDeltaX) > abs(totalDeltaY)) {
|
||||
if (totalDeltaX > 0) "RIGHT" else "LEFT"
|
||||
} else {
|
||||
if (totalDeltaY > 0) "DOWN" else "UP"
|
||||
}
|
||||
Log.d("RnTrackerRootLayout", "Swipe detected: $direction")
|
||||
Analytics.sendSwipe(SwipeDirection.valueOf(direction), ev.rawX, ev.rawY)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure normal event propagation
|
||||
return super.dispatchTouchEvent(ev)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val SWIPE_DISTANCE_THRESHOLD = 100f // Adjust as needed
|
||||
}
|
||||
|
||||
private fun findViewAt(parent: ViewGroup, x: Int, y: Int): View? {
|
||||
for (i in parent.childCount - 1 downTo 0) {
|
||||
val child = parent.getChildAt(i)
|
||||
if (isPointInsideView(x, y, child)) {
|
||||
if (child is ViewGroup) {
|
||||
val childX = x - child.left
|
||||
val childY = y - child.top
|
||||
val result = findViewAt(child, childX, childY)
|
||||
return result ?: child
|
||||
} else {
|
||||
return child
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun isPointInsideView(x: Int, y: Int, view: View): Boolean {
|
||||
return x >= view.left && x <= view.right && y >= view.top && y <= view.bottom
|
||||
}
|
||||
|
||||
inner class GestureListener : GestureDetector.SimpleOnGestureListener() {
|
||||
override fun onSingleTapUp(e: MotionEvent): Boolean {
|
||||
Log.d("GestureListener", "Single tap detected at: (${e.rawX}, ${e.rawY})")
|
||||
val label = currentTappedView?.contentDescription?.toString() ?: "Button"
|
||||
Analytics.sendClick(e, label)
|
||||
currentTappedView?.performClick()
|
||||
return super.onSingleTapUp(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
22
tracker/tracker-reactnative/app.plugin.js
Normal file
22
tracker/tracker-reactnative/app.plugin.js
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
const { withMainApplication } = require('@expo/config-plugins');
|
||||
|
||||
function addPackageToMainApplication(src) {
|
||||
console.log('Adding OpenReplay package to MainApplication.java', src);
|
||||
// Insert `packages.add(new ReactNativePackage());` before return packages;
|
||||
if (src.includes('packages.add(new ReactNativePackage())')) {
|
||||
return src;
|
||||
}
|
||||
return src.replace(
|
||||
'return packages;',
|
||||
`packages.add(new com.openreplay.reactnative.ReactNativePackage());\n return packages;`
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = function configPlugin(config) {
|
||||
return withMainApplication(config, (config) => {
|
||||
if (config.modResults.contents) {
|
||||
config.modResults.contents = addPackageToMainApplication(config.modResults.contents);
|
||||
}
|
||||
return config;
|
||||
});
|
||||
};
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@openreplay/react-native",
|
||||
"version": "0.6.6",
|
||||
"version": "0.6.10",
|
||||
"description": "Openreplay React-native connector for iOS applications",
|
||||
"main": "lib/commonjs/index",
|
||||
"module": "lib/module/index",
|
||||
|
|
@ -13,6 +13,7 @@
|
|||
"android",
|
||||
"ios",
|
||||
"cpp",
|
||||
"app.plugin.js",
|
||||
"*.podspec",
|
||||
"!ios/build",
|
||||
"!android/build",
|
||||
|
|
@ -156,5 +157,8 @@
|
|||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo/config-plugins": "^9.0.12"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue