@rematter/pylon-react-native 0.1.4
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 +503 -0
- package/RNPylonChat.podspec +33 -0
- package/android/build.gradle +74 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +4 -0
- package/android/src/main/java/com/pylon/chatwidget/Pylon.kt +149 -0
- package/android/src/main/java/com/pylon/chatwidget/PylonChat.kt +715 -0
- package/android/src/main/java/com/pylon/chatwidget/PylonChatController.kt +63 -0
- package/android/src/main/java/com/pylon/chatwidget/PylonChatListener.kt +76 -0
- package/android/src/main/java/com/pylon/chatwidget/PylonChatView.kt +7 -0
- package/android/src/main/java/com/pylon/chatwidget/PylonConfig.kt +62 -0
- package/android/src/main/java/com/pylon/chatwidget/PylonDebugView.kt +76 -0
- package/android/src/main/java/com/pylon/chatwidget/PylonUser.kt +41 -0
- package/android/src/main/java/com/pylonchat/reactnative/RNPylonChatPackage.kt +17 -0
- package/android/src/main/java/com/pylonchat/reactnative/RNPylonChatView.kt +298 -0
- package/android/src/main/java/com/pylonchat/reactnative/RNPylonChatViewManager.kt +201 -0
- package/ios/PylonChat/PylonChat.swift +865 -0
- package/ios/RNPylonChatView.swift +332 -0
- package/ios/RNPylonChatViewManager.m +55 -0
- package/ios/RNPylonChatViewManager.swift +23 -0
- package/lib/PylonChatView.d.ts +27 -0
- package/lib/PylonChatView.js +78 -0
- package/lib/PylonChatWidget.android.d.ts +19 -0
- package/lib/PylonChatWidget.android.js +144 -0
- package/lib/PylonChatWidget.ios.d.ts +14 -0
- package/lib/PylonChatWidget.ios.js +79 -0
- package/lib/PylonModule.d.ts +32 -0
- package/lib/PylonModule.js +44 -0
- package/lib/index.d.ts +5 -0
- package/lib/index.js +15 -0
- package/lib/types.d.ts +34 -0
- package/lib/types.js +2 -0
- package/package.json +39 -0
- package/src/PylonChatView.tsx +170 -0
- package/src/PylonChatWidget.android.tsx +165 -0
- package/src/PylonChatWidget.d.ts +15 -0
- package/src/PylonChatWidget.ios.tsx +79 -0
- package/src/PylonModule.ts +52 -0
- package/src/index.ts +15 -0
- package/src/types.ts +37 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
package com.pylon.chatwidget
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Lightweight handle returned by [Pylon.createChat] so developers can interact
|
|
5
|
+
* with the underlying [PylonChat] view without keeping a direct reference to
|
|
6
|
+
* the view class (useful when wiring things in Jetpack Compose or XML layouts).
|
|
7
|
+
*/
|
|
8
|
+
class PylonChatController internal constructor(
|
|
9
|
+
val view: PylonChat
|
|
10
|
+
) {
|
|
11
|
+
|
|
12
|
+
fun setListener(listener: PylonChatListener?) {
|
|
13
|
+
view.setListener(listener)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
fun openChat() {
|
|
17
|
+
view.openChat()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
fun closeChat() {
|
|
21
|
+
view.closeChat()
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
fun showChatBubble() {
|
|
25
|
+
view.showChatBubble()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
fun hideChatBubble() {
|
|
29
|
+
view.hideChatBubble()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
fun updateUser(user: PylonUser) {
|
|
33
|
+
view.updateUser(user)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
fun setNewIssueCustomFields(fields: Map<String, Any?>) {
|
|
37
|
+
view.setNewIssueCustomFields(fields)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
fun setTicketFormFields(fields: Map<String, Any?>) {
|
|
41
|
+
view.setTicketFormFields(fields)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
fun showNewMessage(message: String, isHtml: Boolean = false) {
|
|
45
|
+
view.showNewMessage(message, isHtml)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
fun showTicketForm(ticketFormSlug: String) {
|
|
49
|
+
view.showTicketForm(ticketFormSlug)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
fun showKnowledgeBaseArticle(articleId: String) {
|
|
53
|
+
view.showKnowledgeBaseArticle(articleId)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
fun setEmailHash(emailHash: String?) {
|
|
57
|
+
view.updateEmailHash(emailHash)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
fun destroy() {
|
|
61
|
+
view.destroy()
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
package com.pylon.chatwidget
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Listener interface for Pylon widget events
|
|
5
|
+
*/
|
|
6
|
+
interface PylonChatListener {
|
|
7
|
+
/**
|
|
8
|
+
* Called when the Pylon widget has finished loading
|
|
9
|
+
*/
|
|
10
|
+
fun onPylonLoaded()
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Called when Pylon is initialized with user data
|
|
14
|
+
*/
|
|
15
|
+
fun onPylonInitialized()
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Called when Pylon JavaScript is ready
|
|
19
|
+
*/
|
|
20
|
+
fun onPylonReady()
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Called when a message is received
|
|
24
|
+
*/
|
|
25
|
+
fun onMessageReceived(message: String)
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Called when the chat is opened
|
|
29
|
+
*/
|
|
30
|
+
fun onChatOpened()
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Called when the chat is closed
|
|
34
|
+
*/
|
|
35
|
+
fun onChatClosed()
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Called when there's an error loading Pylon
|
|
39
|
+
*/
|
|
40
|
+
fun onPylonError(error: String)
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Called when a file chooser is launched.
|
|
44
|
+
* This is only called when PylonChat is used from a non-Activity context.
|
|
45
|
+
*
|
|
46
|
+
* Developers can simply forward activity results to [Pylon.handleActivityResult]
|
|
47
|
+
* and ignore request codes altogether. The provided request code is only kept
|
|
48
|
+
* for backwards compatibility.
|
|
49
|
+
*
|
|
50
|
+
* @param requestCode The legacy request code identifier.
|
|
51
|
+
*/
|
|
52
|
+
fun onFileChooserLaunched(requestCode: Int) {
|
|
53
|
+
// Default implementation - can be overridden if needed
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Called whenever the unread message count changes.
|
|
58
|
+
*/
|
|
59
|
+
fun onUnreadCountChanged(unreadCount: Int) {
|
|
60
|
+
// Default implementation - can be overridden if needed
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Called whenever interactive element bounds change.
|
|
65
|
+
* Used by React Native to position proxy touch targets.
|
|
66
|
+
*
|
|
67
|
+
* @param selector The CSS selector of the interactive element
|
|
68
|
+
* @param left The left coordinate in pixels
|
|
69
|
+
* @param top The top coordinate in pixels
|
|
70
|
+
* @param right The right coordinate in pixels
|
|
71
|
+
* @param bottom The bottom coordinate in pixels
|
|
72
|
+
*/
|
|
73
|
+
fun onInteractiveBoundsChanged(selector: String, left: Float, top: Float, right: Float, bottom: Float) {
|
|
74
|
+
// Default implementation - can be overridden if needed
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
package com.pylon.chatwidget
|
|
2
|
+
|
|
3
|
+
// Typealias for compatibility with React Native bridge.
|
|
4
|
+
// The Android SDK uses PylonChat, while iOS uses PylonChatView.
|
|
5
|
+
// This allows the RN bridge to use a consistent name across platforms.
|
|
6
|
+
typealias PylonChatView = PylonChat
|
|
7
|
+
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
package com.pylon.chatwidget
|
|
2
|
+
|
|
3
|
+
import java.net.URLEncoder
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Immutable configuration for the Pylon SDK. Use the [Builder] to customise
|
|
7
|
+
* the settings and pass the result to [Pylon.initialize].
|
|
8
|
+
*/
|
|
9
|
+
data class PylonConfig internal constructor(
|
|
10
|
+
val appId: String,
|
|
11
|
+
val enableLogging: Boolean,
|
|
12
|
+
val primaryColor: String?,
|
|
13
|
+
val debugMode: Boolean,
|
|
14
|
+
val widgetBaseUrl: String,
|
|
15
|
+
val widgetScriptUrl: String
|
|
16
|
+
) {
|
|
17
|
+
|
|
18
|
+
class Builder internal constructor(private val appId: String) {
|
|
19
|
+
var enableLogging: Boolean = true
|
|
20
|
+
var primaryColor: String? = null
|
|
21
|
+
var debugMode: Boolean = false
|
|
22
|
+
var widgetBaseUrl: String = DEFAULT_WIDGET_BASE_URL
|
|
23
|
+
var widgetScriptUrl: String = defaultScriptUrl(appId)
|
|
24
|
+
|
|
25
|
+
internal fun build(): PylonConfig {
|
|
26
|
+
val scriptUrl = widgetScriptUrl.ifBlank { defaultScriptUrl(appId) }
|
|
27
|
+
return PylonConfig(
|
|
28
|
+
appId = appId,
|
|
29
|
+
enableLogging = enableLogging,
|
|
30
|
+
primaryColor = primaryColor,
|
|
31
|
+
debugMode = debugMode,
|
|
32
|
+
widgetBaseUrl = widgetBaseUrl.ifBlank { DEFAULT_WIDGET_BASE_URL },
|
|
33
|
+
widgetScriptUrl = scriptUrl
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
companion object {
|
|
39
|
+
private const val DEFAULT_WIDGET_BASE_URL = "https://widget.usepylon.com"
|
|
40
|
+
|
|
41
|
+
private fun defaultScriptUrl(appId: String): String {
|
|
42
|
+
val encodedAppId = URLEncoder.encode(appId, "UTF-8")
|
|
43
|
+
return "$DEFAULT_WIDGET_BASE_URL/widget/$encodedAppId"
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
fun build(appId: String, block: Builder.() -> Unit = {}): PylonConfig {
|
|
47
|
+
return Builder(appId).apply(block).build()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
fun from(existing: PylonConfig, block: Builder.() -> Unit): PylonConfig {
|
|
51
|
+
val builder = Builder(existing.appId).apply {
|
|
52
|
+
enableLogging = existing.enableLogging
|
|
53
|
+
primaryColor = existing.primaryColor
|
|
54
|
+
debugMode = existing.debugMode
|
|
55
|
+
widgetBaseUrl = existing.widgetBaseUrl
|
|
56
|
+
widgetScriptUrl = existing.widgetScriptUrl
|
|
57
|
+
}
|
|
58
|
+
builder.block()
|
|
59
|
+
return builder.build()
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
package com.pylon.chatwidget
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.graphics.Canvas
|
|
5
|
+
import android.graphics.Color
|
|
6
|
+
import android.graphics.Paint
|
|
7
|
+
import android.graphics.Rect
|
|
8
|
+
import android.view.View
|
|
9
|
+
|
|
10
|
+
class DebugOverlayView(context: Context) : View(context) {
|
|
11
|
+
private val paint = Paint().apply {
|
|
12
|
+
style = Paint.Style.STROKE
|
|
13
|
+
strokeWidth = 4f
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
private val fillPaint = Paint().apply {
|
|
17
|
+
style = Paint.Style.FILL
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
private val textPaint = Paint().apply {
|
|
21
|
+
textSize = 24f
|
|
22
|
+
isAntiAlias = true
|
|
23
|
+
setShadowLayer(2f, 0f, 0f, Color.BLACK) // Add shadow for readability
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
var bounds: Map<String, Rect> = emptyMap()
|
|
27
|
+
set(value) {
|
|
28
|
+
field = value
|
|
29
|
+
invalidate() // Trigger redraw
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
override fun onDraw(canvas: Canvas) {
|
|
33
|
+
super.onDraw(canvas)
|
|
34
|
+
|
|
35
|
+
bounds.forEach { (selector, rect) ->
|
|
36
|
+
if (!rect.isEmpty) {
|
|
37
|
+
val color = getColorForSelector(selector)
|
|
38
|
+
|
|
39
|
+
// Update paint colors
|
|
40
|
+
fillPaint.color = Color.argb(30, Color.red(color), Color.green(color), Color.blue(color))
|
|
41
|
+
paint.color = color
|
|
42
|
+
textPaint.color = color
|
|
43
|
+
|
|
44
|
+
// Draw filled rectangle
|
|
45
|
+
canvas.drawRect(rect, fillPaint)
|
|
46
|
+
// Draw border
|
|
47
|
+
canvas.drawRect(rect, paint)
|
|
48
|
+
// Draw label
|
|
49
|
+
canvas.drawText(
|
|
50
|
+
selector,
|
|
51
|
+
rect.left.toFloat(),
|
|
52
|
+
rect.top.toFloat() - 10f,
|
|
53
|
+
textPaint
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private fun getColorForSelector(selector: String): Int {
|
|
60
|
+
// Generate consistent color from string hash
|
|
61
|
+
val hash = selector.hashCode()
|
|
62
|
+
|
|
63
|
+
// Use HSV to ensure colors are vibrant and distinct
|
|
64
|
+
val hue = (hash and 0xFFFF) % 360f // 0-359 degrees
|
|
65
|
+
val saturation = 0.7f + ((hash shr 16) and 0xFF) / 255f * 0.3f // 0.7-1.0
|
|
66
|
+
val value = 0.8f + ((hash shr 24) and 0xFF) / 255f * 0.2f // 0.8-1.0
|
|
67
|
+
|
|
68
|
+
return Color.HSVToColor(floatArrayOf(hue, saturation, value))
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
init {
|
|
72
|
+
setWillNotDraw(false) // Enable drawing
|
|
73
|
+
isClickable = false
|
|
74
|
+
isFocusable = false
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
package com.pylon.chatwidget
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* User information for Pylon Chat
|
|
5
|
+
*/
|
|
6
|
+
data class PylonUser(
|
|
7
|
+
val email: String,
|
|
8
|
+
val name: String,
|
|
9
|
+
val avatarUrl: String? = null,
|
|
10
|
+
val emailHash: String? = null,
|
|
11
|
+
val accountId: String? = null,
|
|
12
|
+
val accountExternalId: String? = null
|
|
13
|
+
) {
|
|
14
|
+
|
|
15
|
+
class Builder internal constructor(
|
|
16
|
+
private val email: String,
|
|
17
|
+
private val name: String
|
|
18
|
+
) {
|
|
19
|
+
var avatarUrl: String? = null
|
|
20
|
+
var emailHash: String? = null
|
|
21
|
+
var accountId: String? = null
|
|
22
|
+
var accountExternalId: String? = null
|
|
23
|
+
|
|
24
|
+
fun build(): PylonUser = PylonUser(
|
|
25
|
+
email = email,
|
|
26
|
+
name = name,
|
|
27
|
+
avatarUrl = avatarUrl,
|
|
28
|
+
emailHash = emailHash,
|
|
29
|
+
accountId = accountId,
|
|
30
|
+
accountExternalId = accountExternalId
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
companion object {
|
|
35
|
+
@JvmStatic
|
|
36
|
+
@JvmOverloads
|
|
37
|
+
fun build(email: String, name: String, block: Builder.() -> Unit = {}): PylonUser {
|
|
38
|
+
return Builder(email, name).apply(block).build()
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
package com.pylonchat.reactnative
|
|
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
|
+
class RNPylonChatPackage : ReactPackage {
|
|
9
|
+
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
|
10
|
+
return emptyList()
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
14
|
+
return listOf(RNPylonChatViewManager())
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
package com.pylonchat.reactnative
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.view.MotionEvent
|
|
5
|
+
import android.view.ViewGroup
|
|
6
|
+
import android.widget.FrameLayout
|
|
7
|
+
import com.facebook.react.bridge.Arguments
|
|
8
|
+
import com.facebook.react.bridge.ReactContext
|
|
9
|
+
import com.facebook.react.bridge.WritableMap
|
|
10
|
+
import com.facebook.react.uimanager.events.RCTEventEmitter
|
|
11
|
+
import com.pylon.chatwidget.PylonChatListener
|
|
12
|
+
import com.pylon.chatwidget.PylonChatView
|
|
13
|
+
import com.pylon.chatwidget.PylonConfig
|
|
14
|
+
import com.pylon.chatwidget.PylonUser
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* React Native wrapper for PylonChatView.
|
|
18
|
+
* This is kept as minimal as possible to avoid interfering with touch pass-through.
|
|
19
|
+
*/
|
|
20
|
+
class RNPylonChatView(context: Context) : FrameLayout(context) {
|
|
21
|
+
|
|
22
|
+
private var pylonChatView: PylonChatView? = null
|
|
23
|
+
private var config: PylonConfig? = null
|
|
24
|
+
private var user: PylonUser? = null
|
|
25
|
+
|
|
26
|
+
// Config properties
|
|
27
|
+
var appId: String? = null
|
|
28
|
+
set(value) {
|
|
29
|
+
field = value
|
|
30
|
+
updateConfig()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
var widgetBaseUrl: String? = null
|
|
34
|
+
set(value) {
|
|
35
|
+
field = value
|
|
36
|
+
updateConfig()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
var widgetScriptUrl: String? = null
|
|
40
|
+
set(value) {
|
|
41
|
+
field = value
|
|
42
|
+
updateConfig()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
var enableLogging: Boolean = true
|
|
46
|
+
set(value) {
|
|
47
|
+
field = value
|
|
48
|
+
updateConfig()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
var debugMode: Boolean = false
|
|
52
|
+
set(value) {
|
|
53
|
+
field = value
|
|
54
|
+
updateConfig()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
var primaryColor: String? = null
|
|
58
|
+
set(value) {
|
|
59
|
+
field = value
|
|
60
|
+
updateConfig()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// User properties
|
|
64
|
+
var userEmail: String? = null
|
|
65
|
+
set(value) {
|
|
66
|
+
field = value
|
|
67
|
+
updateUser()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
var userName: String? = null
|
|
71
|
+
set(value) {
|
|
72
|
+
field = value
|
|
73
|
+
updateUser()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
var userAvatarUrl: String? = null
|
|
77
|
+
set(value) {
|
|
78
|
+
field = value
|
|
79
|
+
updateUser()
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
var userEmailHash: String? = null
|
|
83
|
+
set(value) {
|
|
84
|
+
field = value
|
|
85
|
+
updateUser()
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
var userAccountId: String? = null
|
|
89
|
+
set(value) {
|
|
90
|
+
field = value
|
|
91
|
+
updateUser()
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
var userAccountExternalId: String? = null
|
|
95
|
+
set(value) {
|
|
96
|
+
field = value
|
|
97
|
+
updateUser()
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
init {
|
|
101
|
+
layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Track pointer events setting
|
|
105
|
+
private var pointerEventsMode = "auto"
|
|
106
|
+
|
|
107
|
+
fun setPointerEventsMode(mode: String) {
|
|
108
|
+
pointerEventsMode = mode
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* This is the CRITICAL method for pointerEvents.
|
|
113
|
+
* By returning false here when pointerEvents="none", we tell React Native's
|
|
114
|
+
* touch system to pass the touch to views BEHIND this one, not just children.
|
|
115
|
+
*/
|
|
116
|
+
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
|
|
117
|
+
when (pointerEventsMode) {
|
|
118
|
+
"none" -> {
|
|
119
|
+
// Don't handle ANY touches - pass through to views BEHIND this one
|
|
120
|
+
return false
|
|
121
|
+
}
|
|
122
|
+
"box-none" -> {
|
|
123
|
+
// This view doesn't handle touches, but children can
|
|
124
|
+
// Try children first, if they don't handle it, pass through
|
|
125
|
+
val handled = super.dispatchTouchEvent(ev)
|
|
126
|
+
return handled // If children didn't handle it, return false passes through
|
|
127
|
+
}
|
|
128
|
+
"box-only" -> {
|
|
129
|
+
// Only this view handles touches, not children
|
|
130
|
+
// Don't dispatch to children
|
|
131
|
+
return onTouchEvent(ev)
|
|
132
|
+
}
|
|
133
|
+
else -> {
|
|
134
|
+
// "auto" - normal behavior
|
|
135
|
+
return super.dispatchTouchEvent(ev)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private fun updateConfig() {
|
|
141
|
+
val id = appId ?: return
|
|
142
|
+
|
|
143
|
+
config = PylonConfig.build(id) {
|
|
144
|
+
this.enableLogging = this@RNPylonChatView.enableLogging
|
|
145
|
+
this.primaryColor = this@RNPylonChatView.primaryColor
|
|
146
|
+
this.debugMode = this@RNPylonChatView.debugMode
|
|
147
|
+
this@RNPylonChatView.widgetBaseUrl?.let { this.widgetBaseUrl = it }
|
|
148
|
+
this@RNPylonChatView.widgetScriptUrl?.let { this.widgetScriptUrl = it }
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
recreatePylonView()
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private fun updateUser() {
|
|
155
|
+
val email = userEmail ?: return
|
|
156
|
+
val name = userName ?: return
|
|
157
|
+
|
|
158
|
+
user = PylonUser(
|
|
159
|
+
email = email,
|
|
160
|
+
name = name,
|
|
161
|
+
avatarUrl = userAvatarUrl,
|
|
162
|
+
emailHash = userEmailHash,
|
|
163
|
+
accountId = userAccountId,
|
|
164
|
+
accountExternalId = userAccountExternalId
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
recreatePylonView()
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private fun recreatePylonView() {
|
|
171
|
+
val cfg = config ?: return
|
|
172
|
+
val usr = user ?: return
|
|
173
|
+
|
|
174
|
+
// Remove old view
|
|
175
|
+
pylonChatView?.let { removeView(it) }
|
|
176
|
+
|
|
177
|
+
// Create new PylonChatView
|
|
178
|
+
val newView = PylonChatView(context, cfg, usr)
|
|
179
|
+
newView.setListener(object : PylonChatListener {
|
|
180
|
+
override fun onPylonLoaded() {
|
|
181
|
+
sendEvent("onPylonLoaded", Arguments.createMap())
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
override fun onPylonInitialized() {
|
|
185
|
+
sendEvent("onPylonInitialized", Arguments.createMap())
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
override fun onPylonReady() {
|
|
189
|
+
sendEvent("onPylonReady", Arguments.createMap())
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
override fun onMessageReceived(message: String) {
|
|
193
|
+
val params = Arguments.createMap()
|
|
194
|
+
params.putString("message", message)
|
|
195
|
+
sendEvent("onMessageReceived", params)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
override fun onChatOpened() {
|
|
199
|
+
sendEvent("onChatOpened", Arguments.createMap())
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
override fun onChatClosed() {
|
|
203
|
+
val params = Arguments.createMap()
|
|
204
|
+
params.putBoolean("wasOpen", true)
|
|
205
|
+
sendEvent("onChatClosed", params)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
override fun onInteractiveBoundsChanged(selector: String, left: Float, top: Float, right: Float, bottom: Float) {
|
|
209
|
+
// Convert pixels to density-independent pixels (dp) for React Native.
|
|
210
|
+
val density = resources.displayMetrics.density
|
|
211
|
+
val params = Arguments.createMap()
|
|
212
|
+
params.putString("selector", selector)
|
|
213
|
+
params.putDouble("left", (left / density).toDouble())
|
|
214
|
+
params.putDouble("top", (top / density).toDouble())
|
|
215
|
+
params.putDouble("right", (right / density).toDouble())
|
|
216
|
+
params.putDouble("bottom", (bottom / density).toDouble())
|
|
217
|
+
sendEvent("onInteractiveBoundsChanged", params)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
override fun onPylonError(error: String) {
|
|
221
|
+
val params = Arguments.createMap()
|
|
222
|
+
params.putString("error", error)
|
|
223
|
+
sendEvent("onPylonError", params)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
override fun onUnreadCountChanged(count: Int) {
|
|
227
|
+
val params = Arguments.createMap()
|
|
228
|
+
params.putInt("count", count)
|
|
229
|
+
sendEvent("onUnreadCountChanged", params)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
override fun onFileChooserLaunched(requestCode: Int) {
|
|
233
|
+
// Handle file chooser if needed
|
|
234
|
+
}
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
newView.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
|
|
238
|
+
addView(newView)
|
|
239
|
+
pylonChatView = newView
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private fun sendEvent(eventName: String, params: WritableMap) {
|
|
243
|
+
val reactContext = context as ReactContext
|
|
244
|
+
reactContext
|
|
245
|
+
.getJSModule(RCTEventEmitter::class.java)
|
|
246
|
+
.receiveEvent(id, eventName, params)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Imperative methods
|
|
250
|
+
fun openChat() {
|
|
251
|
+
pylonChatView?.openChat()
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
fun closeChat() {
|
|
255
|
+
pylonChatView?.closeChat()
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
fun showChatBubble() {
|
|
259
|
+
pylonChatView?.showChatBubble()
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
fun hideChatBubble() {
|
|
263
|
+
pylonChatView?.hideChatBubble()
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
fun showNewMessage(message: String, isHtml: Boolean) {
|
|
267
|
+
pylonChatView?.showNewMessage(message, isHtml)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
fun setNewIssueCustomFields(fields: Map<String, Any?>) {
|
|
271
|
+
@Suppress("UNCHECKED_CAST")
|
|
272
|
+
pylonChatView?.setNewIssueCustomFields(fields as Map<String, Any>)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
fun setTicketFormFields(fields: Map<String, Any?>) {
|
|
276
|
+
@Suppress("UNCHECKED_CAST")
|
|
277
|
+
pylonChatView?.setTicketFormFields(fields as Map<String, Any>)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
fun updateEmailHash(emailHash: String?) {
|
|
281
|
+
pylonChatView?.setEmailHash(emailHash)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
fun showTicketForm(slug: String) {
|
|
285
|
+
pylonChatView?.showTicketForm(slug)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
fun showKnowledgeBaseArticle(articleId: String) {
|
|
289
|
+
pylonChatView?.showKnowledgeBaseArticle(articleId)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
fun clickElementAtSelector(selector: String) {
|
|
293
|
+
// Trigger a click on the element with the given ID selector.
|
|
294
|
+
// This is used for Android's proxy-based touch pass-through system.
|
|
295
|
+
pylonChatView?.clickElementBySelector(selector)
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|