Skip to content

Commit

Permalink
change(android): suppport jetpack compose
Browse files Browse the repository at this point in the history
  • Loading branch information
shekarsiri committed Jul 23, 2024
1 parent 7b5f13b commit d4ce11a
Show file tree
Hide file tree
Showing 8 changed files with 187 additions and 58 deletions.
1 change: 0 additions & 1 deletion app/src/main/java/com/openreplay/sampleapp/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ class MainActivity : AppCompatActivity() {
navView.setupWithNavController(navController)

OpenReplay.setupGestureDetector(this)

OpenReplay.serverURL = BuildConfig.SERVER_URL
OpenReplay.start(
this,
Expand Down
8 changes: 7 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[versions]
agp = "8.3.2"
agp = "8.5.1"
kotlin = "1.9.0"
coreKtx = "1.12.0"
junit = "4.13.2"
Expand All @@ -15,6 +15,9 @@ navigationUiKtx = "2.6.0"
okhttp = "5.0.0-alpha.9"
gson = "2.10.1"
commonsCompress = "1.26.1"
uiAndroid = "1.6.8"
foundationAndroid = "1.6.8"
material3Android = "1.2.1"

[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
Expand All @@ -32,6 +35,9 @@ okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhtt
gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" }
commons-compress = { group = "org.apache.commons", name = "commons-compress", version.ref = "commonsCompress" }
kotlin-stdlib = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib", version.ref = "kotlin" }
androidx-ui-android = { group = "androidx.compose.ui", name = "ui-android", version.ref = "uiAndroid" }
androidx-foundation-android = { group = "androidx.compose.foundation", name = "foundation-android", version.ref = "foundationAndroid" }
androidx-material3-android = { group = "androidx.compose.material3", name = "material3-android", version.ref = "material3Android" }

[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#Tue Apr 23 14:21:54 CEST 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
3 changes: 3 additions & 0 deletions tracker/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ dependencies {
implementation(libs.gson)
implementation(libs.commons.compress)
implementation(libs.kotlin.stdlib)
implementation(libs.androidx.ui.android)
implementation(libs.androidx.foundation.android)
implementation(libs.androidx.material3.android)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
Expand Down
57 changes: 55 additions & 2 deletions tracker/src/main/java/com/openreplay/tracker/ORTracker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import android.os.Build
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.AbstractComposeView
import androidx.compose.ui.platform.LocalContext
import com.google.gson.Gson
import com.openreplay.tracker.listeners.Analytics
import com.openreplay.tracker.listeners.Crash
Expand Down Expand Up @@ -241,10 +245,59 @@ object OpenReplay {
MessageCollector.sendMessage(message)
}

// fun setupGestureDetector(context: Context) {
// val rootView = (context as Activity).window.decorView.rootView
// val gestureListener = ORGestureListener(rootView)
// this.gestureDetector = GestureDetector(context, gestureListener)
// }

// @Composable
// fun GestureDetectorBox(onGestureDetected: () -> Unit) {
// val context = LocalContext.current
// setupGestureDetector(context) {
// onGestureDetected()
// }
//
// Box(
// modifier = Modifier
// .fillMaxSize()
// .pointerInput(Unit) {
// detectTapGestures(
// onTap = {
// onGestureDetected()
// }
// )
// }
// ) {
// Text(text = "Tap me")
// }
// }

fun setupGestureDetector(context: Context) {
val rootView = (context as Activity).window.decorView.rootView
val activity = context as Activity
val rootView = activity.window.decorView.rootView

val gestureListener = ORGestureListener(rootView)
this.gestureDetector = GestureDetector(context, gestureListener)
val gestureDetector = GestureDetector(context, gestureListener)

// Set up gesture detection for legacy Android views
rootView.setOnTouchListener { v, event ->
gestureDetector.onTouchEvent(event)
}

// Handle Jetpack Compose views
if (rootView is ViewGroup) {
println("jetpack view listener")
for (i in 0 until rootView.childCount) {
val child = rootView.getChildAt(i)
if (child is AbstractComposeView) {
println("child listener")
child.setOnTouchListener { v, event ->
gestureDetector.onTouchEvent(event)
}
}
}
}
}

fun onTouchEvent(event: MotionEvent) {
Expand Down
112 changes: 88 additions & 24 deletions tracker/src/main/java/com/openreplay/tracker/listeners/Analytics.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,21 @@ import android.widget.Button
import android.widget.EditText
import android.widget.FrameLayout
import android.widget.TextView
import androidx.activity.ComponentActivity
import androidx.appcompat.app.AppCompatActivity
import androidx.core.widget.doAfterTextChanged
import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.pointerInput
import androidx.lifecycle.*
import com.openreplay.tracker.OpenReplay
import com.openreplay.tracker.managers.DebugUtils
import com.openreplay.tracker.managers.MessageCollector
import com.openreplay.tracker.managers.ScreenshotManager
import com.openreplay.tracker.models.script.*
import kotlin.math.abs
import kotlin.math.atan2
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.ui.composed

import androidx.compose.foundation.text.BasicTextField
import androidx.compose.runtime.*
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.text.input.TextFieldValue

enum class SwipeDirection {
LEFT, RIGHT, UP, DOWN, UNDEFINED;
Expand Down Expand Up @@ -58,7 +62,6 @@ object Analytics {
fun sendClick(ev: MotionEvent, label: String? = null) {
if (!enabled) return


val message = ORMobileClickEvent(label = label ?: "Button", x = ev.x, y = ev.y)
MessageCollector.sendMessage(message)
}
Expand All @@ -67,10 +70,18 @@ object Analytics {
if (!enabled) return

val message = ORMobileSwipeEvent(
direction = direction.name.lowercase(),
x = x,
y = y,
label = "Swipe"
direction = direction.name.lowercase(), x = x, y = y, label = "Swipe"
)
MessageCollector.sendMessage(message)
}

fun sendTextInput(value: String, label: String?, masked: Boolean = false) {
if (!enabled) return

val message = ORMobileInputEvent(
value = value,
valueMasked = masked,
label = label ?: "Input"
)
MessageCollector.sendMessage(message)
}
Expand Down Expand Up @@ -130,10 +141,7 @@ open class TrackingActivity : AppCompatActivity() {
}

override fun onScroll(
e1: MotionEvent?,
e2: MotionEvent,
distanceX: Float,
distanceY: Float
e1: MotionEvent?, e2: MotionEvent, distanceX: Float, distanceY: Float
): Boolean {
if (!isScrolling) {
isScrolling = true
Expand Down Expand Up @@ -184,10 +192,7 @@ open class TrackingActivity : AppCompatActivity() {
val location = IntArray(2)
child.getLocationOnScreen(location)
val rect = Rect(
location[0],
location[1],
location[0] + child.width,
location[1] + child.height
location[0], location[1], location[0] + child.width, location[1] + child.height
)
if (rect.contains(x.toInt(), y.toInt())) {
val foundView = findViewAtPosition(child, x, y)
Expand Down Expand Up @@ -405,10 +410,7 @@ class ORGestureListener(private val rootView: View) : GestureDetector.SimpleOnGe
}

override fun onScroll(
e1: MotionEvent?,
e2: MotionEvent,
distanceX: Float,
distanceY: Float
e1: MotionEvent?, e2: MotionEvent, distanceX: Float, distanceY: Float
): Boolean {
if (!isScrolling) {
isScrolling = true
Expand Down Expand Up @@ -448,4 +450,66 @@ class ORGestureListener(private val rootView: View) : GestureDetector.SimpleOnGe
else -> view?.javaClass?.simpleName ?: "Unknown View"
}
}
}
}

fun Modifier.trackTouchEvents(label: String? = "Unknown"): Modifier {
var initialX = 0f
var initialY = 0f
var currentX = 0f
var currentY = 0f
return this.pointerInput(Unit) {
awaitPointerEventScope {
while (true) {
val event = awaitPointerEvent()
event.changes.forEach { change ->
if (change.pressed) {
// position = "X: ${change.position.x}, Y: ${change.position.y}"
println("$label X: ${change.position.x}, Y: ${change.position.y}")
change.consume()
Analytics.sendClick(
MotionEvent.obtain(
0,
0,
MotionEvent.ACTION_UP,
change.position.x,
change.position.y,
0
), label
)
}
}
}
}
}.pointerInput(Unit) {
detectDragGestures(onDragStart = { offset ->
initialX = offset.x
initialY = offset.y
println("onDragStart at ${offset.x}, ${offset.y}")
}, onDragEnd = {
val distanceX = currentX - initialX
val distanceY = currentY - initialY
val direction = SwipeDirection.fromDistances(distanceX, distanceY)
println("onDragEnd with swipe direction: $direction at ($currentX, $currentY)")
Analytics.sendSwipe(direction, currentX, currentY)
}, onDragCancel = {
println("onDragCancel")
}, onDrag = { change, _ ->
currentX = change.position.x
currentY = change.position.y
})
}
}

fun Modifier.trackTextInputChanges(
label: String,
value: String? = null,
isMasked: Boolean = false
): Modifier {
return this.onFocusChanged {
if (!it.isFocused) {
Analytics.sendTextInput(value ?: "", label, isMasked)
}
}
}


Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ object NetworkManager {
}

fun sendLateMessage(content: ByteArray, completion: (Boolean) -> Unit) {
println(">>>sending late messages")
// println(">>>sending late messages")
val token = UserDefaults.lastToken ?: run {
println("! No last token found")
completion(false)
Expand Down Expand Up @@ -177,9 +177,9 @@ object NetworkManager {
method = "POST", path = IMAGES_URL, body = requestBodyBuilder.build()
).newBuilder().addHeader("Authorization", "Bearer $token").build()


callAPI(request, onSuccess = {
completion(true)
println(">>>>>> sending ${request.body?.contentLength()} bytes")
}, onError = {
completion(false)
})
Expand Down Expand Up @@ -212,8 +212,6 @@ object NetworkManager {

private fun appendLocalFile(data: ByteArray) {
if (OpenReplay.options.debugLogs) {
println("appendInFile ${data.size} bytes")

val filePath = "/Users/shekarsiri/Desktop/session.dat" // TODO fix this
try {
File(filePath).apply {
Expand All @@ -227,10 +225,8 @@ object NetworkManager {
stream.write(data)
}
}
println("Data successfully appended to file.")
} catch (e: IOException) {
e.printStackTrace()
println("Failed to append data to file.")
}
}
}
Expand Down
Loading

0 comments on commit d4ce11a

Please sign in to comment.