Compare commits

...
Sign in to create a new pull request.

5 commits

Author SHA1 Message Date
Shekar Siri
ef69e5c422 change(react-native): include plugin file and version jump 2024-12-11 09:39:02 +01:00
Shekar Siri
817da3a088 change(react-native): version jump 2024-12-10 18:14:18 +01:00
Shekar Siri
314ff737ad change(react-native): swipe event fix 2024-12-10 18:12:03 +01:00
Shekar Siri
1f828817f0 change(react-native): updates to support expo 2024-12-10 17:34:56 +01:00
Shekar Siri
f8bfbc18e6 change(react-native): android version jump 2024-12-10 11:59:49 +01:00
5 changed files with 139 additions and 217 deletions

View file

@ -91,7 +91,7 @@ dependencies {
//noinspection GradleDynamicVersion //noinspection GradleDynamicVersion
implementation("com.facebook.react:react-native:0.20.1") implementation("com.facebook.react:react-native:0.20.1")
implementation("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") 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 { //allprojects {

View file

@ -10,18 +10,10 @@ import com.openreplay.tracker.models.OROptions
class ReactNativeModule(reactContext: ReactApplicationContext) : class ReactNativeModule(reactContext: ReactApplicationContext) :
ReactContextBaseJavaModule(reactContext) { ReactContextBaseJavaModule(reactContext) {
// private val context = reactContext.acti
override fun getName(): String { override fun getName(): String {
return NAME 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 { companion object {
const val NAME = "ORTrackerConnector" const val NAME = "ORTrackerConnector"
} }
@ -33,14 +25,13 @@ class ReactNativeModule(reactContext: ReactApplicationContext) :
val logs: Boolean = true, val logs: Boolean = true,
val screen: Boolean = true, val screen: Boolean = true,
val debugLogs: Boolean = false, 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 { private fun getBooleanOrDefault(map: ReadableMap, key: String, default: Boolean): Boolean {
return if (map.hasKey(key)) map.getBoolean(key) else default return if (map.hasKey(key)) map.getBoolean(key) else default
} }
// optionsMap: ReadableMap?,
@ReactMethod @ReactMethod
fun startSession( fun startSession(
projectKey: String, projectKey: String,
@ -97,8 +88,8 @@ class ReactNativeModule(reactContext: ReactApplicationContext) :
@ReactMethod @ReactMethod
fun getSessionID(promise: Promise) { fun getSessionID(promise: Promise) {
try { try {
val sessionId = OpenReplay.getSessionID() ?: "" val sessionId = OpenReplay.getSessionID()
promise.resolve(sessionId) // Resolve the promise with the session ID promise.resolve(sessionId)
} catch (e: Exception) { } catch (e: Exception) {
promise.reject("GET_SESSION_ID_ERROR", "Failed to retrieve session ID", e) promise.reject("GET_SESSION_ID_ERROR", "Failed to retrieve session ID", e)
} }
@ -111,8 +102,9 @@ class ReactNativeModule(reactContext: ReactApplicationContext) :
requestJSON: String, requestJSON: String,
responseJSON: String, responseJSON: String,
status: Int, 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)
} }
} }

View file

@ -1,13 +1,13 @@
package com.openreplay.reactnative package com.openreplay.reactnative
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.graphics.PointF import android.graphics.PointF
import android.util.Log
import android.view.GestureDetector
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout 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.ThemedReactContext
import com.facebook.react.uimanager.ViewGroupManager import com.facebook.react.uimanager.ViewGroupManager
import com.openreplay.tracker.listeners.Analytics import com.openreplay.tracker.listeners.Analytics
@ -15,151 +15,16 @@ import com.openreplay.tracker.listeners.SwipeDirection
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.sqrt 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>() { class RnTrackerTouchManager : ViewGroupManager<FrameLayout>() {
override fun getName(): String = "RnTrackerTouchView" override fun getName(): String = "RnTrackerTouchView"
override fun createViewInstance(reactContext: ThemedReactContext): FrameLayout { override fun createViewInstance(reactContext: ThemedReactContext): FrameLayout {
return ReactRootView(reactContext).apply { return RnTrackerRootLayout(reactContext)
// 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
// }
// }
}
} }
override fun addView(parent: FrameLayout, child: View, index: Int) { override fun addView(parent: FrameLayout, child: View, index: Int) {
child.isClickable = true parent.addView(child, index)
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)
} }
override fun getChildCount(parent: FrameLayout): Int = parent.childCount override fun getChildCount(parent: FrameLayout): Int = parent.childCount
@ -175,63 +40,102 @@ class RnTrackerTouchManager : ViewGroupManager<FrameLayout>() {
} }
} }
//class RnTrackerTouchManager : ViewGroupManager<FrameLayout>() { class RnTrackerRootLayout(context: Context) : FrameLayout(context) {
// override fun getName(): String = "RnTrackerTouchView" private val touchStart = PointF()
// private val gestureDetector: GestureDetector
// override fun createViewInstance(reactContext: ThemedReactContext): FrameLayout {
// return FrameLayout(reactContext).apply { private var currentTappedView: View? = null
// layoutParams = FrameLayout.LayoutParams(
// FrameLayout.LayoutParams.MATCH_PARENT, // Variables to track total movement
// FrameLayout.LayoutParams.MATCH_PARENT private var totalDeltaX: Float = 0f
// ) private var totalDeltaY: Float = 0f
// isClickable = true
// val touchStart = PointF() init {
// setOnTouchListener { view, event -> gestureDetector = GestureDetector(context, GestureListener())
// when (event.action) { }
// MotionEvent.ACTION_DOWN -> {
// touchStart.set(event.x, event.y) override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
// view.performClick() // Pass all touch events to the GestureDetector
// } gestureDetector.onTouchEvent(ev)
//
// MotionEvent.ACTION_UP -> { when (ev.action) {
// val deltaX = event.x - touchStart.x MotionEvent.ACTION_DOWN -> {
// val deltaY = event.y - touchStart.y // Record the starting point for potential swipe
// val distance = sqrt(deltaX * deltaX + deltaY * deltaY) touchStart.x = ev.x
// touchStart.y = ev.y
// if (distance > 10) { // Reset total movement
// val direction = if (abs(deltaX) > abs(deltaY)) { totalDeltaX = 0f
// if (deltaX > 0) "RIGHT" else "LEFT" totalDeltaY = 0f
// } else { // Find and store the view that was touched
// if (deltaY > 0) "DOWN" else "UP" currentTappedView = findViewAt(this, ev.x.toInt(), ev.y.toInt())
// } // Log.d(
// Analytics.sendSwipe(SwipeDirection.valueOf(direction), event.x, event.y) // "RnTrackerRootLayout",
// view.performClick() // "ACTION_DOWN at global: (${ev.rawX}, ${ev.rawY}) on view: $currentTappedView"
// } else { // )
// Analytics.sendClick(event) }
// view.performClick() MotionEvent.ACTION_MOVE -> {
// } // Accumulate movement
// true val deltaX = ev.x - touchStart.x
// } val deltaY = ev.y - touchStart.y
// totalDeltaX += deltaX
// else -> false 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")
// }
// override fun addView(parent: FrameLayout, child: View, index: Int) { MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
// parent.addView(child, index) // Determine if the accumulated movement qualifies as a swipe
// } val distance = sqrt(totalDeltaX * totalDeltaX + totalDeltaY * totalDeltaY)
//
// override fun getChildCount(parent: FrameLayout): Int = parent.childCount if (distance > SWIPE_DISTANCE_THRESHOLD) {
// val direction = if (abs(totalDeltaX) > abs(totalDeltaY)) {
// override fun getChildAt(parent: FrameLayout, index: Int): View = parent.getChildAt(index) if (totalDeltaX > 0) "RIGHT" else "LEFT"
// } else {
// override fun removeViewAt(parent: FrameLayout, index: Int) { if (totalDeltaY > 0) "DOWN" else "UP"
// parent.removeViewAt(index) }
// } Log.d("RnTrackerRootLayout", "Swipe detected: $direction")
// Analytics.sendSwipe(SwipeDirection.valueOf(direction), ev.rawX, ev.rawY)
// override fun removeAllViews(parent: FrameLayout) { }
// parent.removeAllViews() }
// } }
//}
// 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)
}
}
}

View 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;
});
};

View file

@ -1,6 +1,6 @@
{ {
"name": "@openreplay/react-native", "name": "@openreplay/react-native",
"version": "0.6.6", "version": "0.6.10",
"description": "Openreplay React-native connector for iOS applications", "description": "Openreplay React-native connector for iOS applications",
"main": "lib/commonjs/index", "main": "lib/commonjs/index",
"module": "lib/module/index", "module": "lib/module/index",
@ -13,6 +13,7 @@
"android", "android",
"ios", "ios",
"cpp", "cpp",
"app.plugin.js",
"*.podspec", "*.podspec",
"!ios/build", "!ios/build",
"!android/build", "!android/build",
@ -156,5 +157,8 @@
} }
] ]
] ]
},
"dependencies": {
"@expo/config-plugins": "^9.0.12"
} }
} }