@attentive-mobile/attentive-react-native-sdk 1.0.3-beta.1 → 1.0.5
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 +24 -0
- package/android/build.gradle +3 -0
- package/android/src/main/java/com/attentivereactnativesdk/AttentiveReactNativeSdkModule.java +63 -0
- package/android/src/main/kotlin/com/attentivereactnativesdk/debug/AttentiveDebugHelper.kt +422 -0
- package/android/src/main/kotlin/com/attentivereactnativesdk/debug/DebugEvent.kt +76 -0
- package/attentive-react-native-sdk.podspec +1 -2
- package/ios/AttentiveReactNativeSdk.h +6 -6
- package/ios/AttentiveReactNativeSdk.mm +18 -8
- package/ios/AttentiveReactNativeSdk.xcodeproj/project.pbxproj +2 -2
- package/ios/AttentiveReactNativeSdk.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/ios/AttentiveReactNativeSdk.xcodeproj/project.xcworkspace/xcuserdata/zheref.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/ios/AttentiveReactNativeSdk.xcodeproj/xcuserdata/zheref.xcuserdatad/xcschemes/xcschememanagement.plist +14 -0
- package/ios/Bridging/ATTNNativeSDK.swift +770 -2
- package/ios/Podfile +1 -1
- package/lib/commonjs/eventTypes.js.map +1 -1
- package/lib/commonjs/index.js +16 -2
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/eventTypes.js.map +1 -1
- package/lib/module/index.js +14 -1
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/index.d.ts +8 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/index.tsx +14 -0
package/README.md
CHANGED
|
@@ -36,6 +36,30 @@ const config : AttentiveConfiguration = {
|
|
|
36
36
|
}
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
+
### Debugging Features
|
|
40
|
+
|
|
41
|
+
The SDK includes debugging helpers to show what data is being sent and received. Enable debugging by setting `enableDebugger: true`:
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
const config : AttentiveConfiguration = {
|
|
45
|
+
attentiveDomain: 'YOUR_ATTENTIVE_DOMAIN',
|
|
46
|
+
mode: Mode.Debug,
|
|
47
|
+
enableDebugger: true, // Shows debug overlays for events and creatives
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
When enabled, debug overlays will automatically appear when:
|
|
52
|
+
- Creatives are triggered
|
|
53
|
+
- Events are recorded (product views, purchases, etc.)
|
|
54
|
+
|
|
55
|
+
You can also manually invoke the debug helper:
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
Attentive.invokeAttentiveDebugHelper();
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
See [DEBUGGING.md](./DEBUGGING.md) for detailed information about debugging features.
|
|
62
|
+
|
|
39
63
|
### Initialize the SDK
|
|
40
64
|
|
|
41
65
|
```typescript
|
package/android/build.gradle
CHANGED
|
@@ -6,6 +6,7 @@ buildscript {
|
|
|
6
6
|
|
|
7
7
|
dependencies {
|
|
8
8
|
classpath "com.android.tools.build:gradle:7.3.1"
|
|
9
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.10"
|
|
9
10
|
}
|
|
10
11
|
}
|
|
11
12
|
|
|
@@ -14,6 +15,7 @@ def isNewArchitectureEnabled() {
|
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
apply plugin: "com.android.library"
|
|
18
|
+
apply plugin: "kotlin-android"
|
|
17
19
|
|
|
18
20
|
|
|
19
21
|
def appProject = rootProject.allprojects.find { it.plugins.hasPlugin('com.android.application') }
|
|
@@ -81,6 +83,7 @@ dependencies {
|
|
|
81
83
|
//noinspection GradleDynamicVersion
|
|
82
84
|
implementation "com.facebook.react:react-native:+"
|
|
83
85
|
implementation 'com.attentive:attentive-android-sdk:1.0.1'
|
|
86
|
+
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.9.10"
|
|
84
87
|
}
|
|
85
88
|
|
|
86
89
|
if (isNewArchitectureEnabled()) {
|
package/android/src/main/java/com/attentivereactnativesdk/AttentiveReactNativeSdkModule.java
CHANGED
|
@@ -33,6 +33,7 @@ import java.util.HashMap;
|
|
|
33
33
|
import java.util.List;
|
|
34
34
|
import java.util.Locale;
|
|
35
35
|
import java.util.Map;
|
|
36
|
+
import com.attentivereactnativesdk.debug.AttentiveDebugHelper;
|
|
36
37
|
|
|
37
38
|
@ReactModule(name = AttentiveReactNativeSdkModule.NAME)
|
|
38
39
|
public class AttentiveReactNativeSdkModule extends ReactContextBaseJavaModule {
|
|
@@ -41,9 +42,11 @@ public class AttentiveReactNativeSdkModule extends ReactContextBaseJavaModule {
|
|
|
41
42
|
|
|
42
43
|
private AttentiveConfig attentiveConfig;
|
|
43
44
|
private Creative creative;
|
|
45
|
+
private AttentiveDebugHelper debugHelper;
|
|
44
46
|
|
|
45
47
|
public AttentiveReactNativeSdkModule(ReactApplicationContext reactContext) {
|
|
46
48
|
super(reactContext);
|
|
49
|
+
this.debugHelper = new AttentiveDebugHelper(reactContext);
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
@Override
|
|
@@ -62,6 +65,11 @@ public class AttentiveReactNativeSdkModule extends ReactContextBaseJavaModule {
|
|
|
62
65
|
final String domain = config.getString("attentiveDomain");
|
|
63
66
|
final Boolean skipFatigue = config.hasKey("skipFatigueOnCreatives") ?
|
|
64
67
|
config.getBoolean("skipFatigueOnCreatives") : false;
|
|
68
|
+
|
|
69
|
+
// Initialize debug helper
|
|
70
|
+
final Boolean enableDebuggerFromConfig = config.hasKey("enableDebugger") ?
|
|
71
|
+
config.getBoolean("enableDebugger") : false;
|
|
72
|
+
debugHelper.initialize(enableDebuggerFromConfig);
|
|
65
73
|
|
|
66
74
|
attentiveConfig = new AttentiveConfig.Builder()
|
|
67
75
|
.context(this.getReactApplicationContext())
|
|
@@ -89,6 +97,12 @@ public class AttentiveReactNativeSdkModule extends ReactContextBaseJavaModule {
|
|
|
89
97
|
UiThreadUtil.runOnUiThread(() -> {
|
|
90
98
|
creative = new Creative(attentiveConfig, rootView);
|
|
91
99
|
creative.trigger(null, creativeId);
|
|
100
|
+
if (debugHelper.isDebuggingEnabled()) {
|
|
101
|
+
Map<String, Object> debugData = new HashMap<>();
|
|
102
|
+
debugData.put("type", "trigger");
|
|
103
|
+
debugData.put("creativeId", creativeId != null ? creativeId : "default");
|
|
104
|
+
debugHelper.showDebugInfo("Creative Triggered", debugData);
|
|
105
|
+
}
|
|
92
106
|
});
|
|
93
107
|
} else {
|
|
94
108
|
Log.w(TAG, "Could not trigger the Attentive Creative because the current Activity was null");
|
|
@@ -159,6 +173,14 @@ public class AttentiveReactNativeSdkModule extends ReactContextBaseJavaModule {
|
|
|
159
173
|
ProductViewEvent productViewEvent = new ProductViewEvent.Builder(items).deeplink(deeplink).build();
|
|
160
174
|
|
|
161
175
|
AttentiveEventTracker.getInstance().recordEvent(productViewEvent);
|
|
176
|
+
|
|
177
|
+
if (debugHelper.isDebuggingEnabled()) {
|
|
178
|
+
Map<String, Object> debugData = new HashMap<>();
|
|
179
|
+
debugData.put("items_count", String.valueOf(items.size()));
|
|
180
|
+
debugData.put("deeplink", deeplink);
|
|
181
|
+
debugData.put("payload", productViewAttrs.toHashMap());
|
|
182
|
+
debugHelper.showDebugInfo("Product View Event", debugData);
|
|
183
|
+
}
|
|
162
184
|
}
|
|
163
185
|
|
|
164
186
|
@ReactMethod
|
|
@@ -170,6 +192,14 @@ public class AttentiveReactNativeSdkModule extends ReactContextBaseJavaModule {
|
|
|
170
192
|
PurchaseEvent purchaseEvent = new PurchaseEvent.Builder(items, order).build();
|
|
171
193
|
|
|
172
194
|
AttentiveEventTracker.getInstance().recordEvent(purchaseEvent);
|
|
195
|
+
|
|
196
|
+
if (debugHelper.isDebuggingEnabled()) {
|
|
197
|
+
Map<String, Object> debugData = new HashMap<>();
|
|
198
|
+
debugData.put("items_count", String.valueOf(items.size()));
|
|
199
|
+
debugData.put("order_id", order.getOrderId());
|
|
200
|
+
debugData.put("payload", purchaseAttrs.toHashMap());
|
|
201
|
+
debugHelper.showDebugInfo("Purchase Event", debugData);
|
|
202
|
+
}
|
|
173
203
|
}
|
|
174
204
|
|
|
175
205
|
@ReactMethod
|
|
@@ -181,6 +211,14 @@ public class AttentiveReactNativeSdkModule extends ReactContextBaseJavaModule {
|
|
|
181
211
|
AddToCartEvent addToCartEvent = new AddToCartEvent.Builder(items).deeplink(deeplink).build();
|
|
182
212
|
|
|
183
213
|
AttentiveEventTracker.getInstance().recordEvent(addToCartEvent);
|
|
214
|
+
|
|
215
|
+
if (debugHelper.isDebuggingEnabled()) {
|
|
216
|
+
Map<String, Object> debugData = new HashMap<>();
|
|
217
|
+
debugData.put("items_count", String.valueOf(items.size()));
|
|
218
|
+
debugData.put("deeplink", deeplink);
|
|
219
|
+
debugData.put("payload", addToCartAttrs.toHashMap());
|
|
220
|
+
debugHelper.showDebugInfo("Add To Cart Event", debugData);
|
|
221
|
+
}
|
|
184
222
|
}
|
|
185
223
|
|
|
186
224
|
@ReactMethod
|
|
@@ -194,8 +232,32 @@ public class AttentiveReactNativeSdkModule extends ReactContextBaseJavaModule {
|
|
|
194
232
|
CustomEvent customEvent = new CustomEvent.Builder(customEventAttrs.getString("type"), properties).build();
|
|
195
233
|
|
|
196
234
|
AttentiveEventTracker.getInstance().recordEvent(customEvent);
|
|
235
|
+
|
|
236
|
+
if (debugHelper.isDebuggingEnabled()) {
|
|
237
|
+
Map<String, Object> debugData = new HashMap<>();
|
|
238
|
+
debugData.put("event_type", customEventAttrs.getString("type"));
|
|
239
|
+
debugData.put("properties_count", String.valueOf(properties.size()));
|
|
240
|
+
debugData.put("payload", customEventAttrs.toHashMap());
|
|
241
|
+
debugHelper.showDebugInfo("Custom Event", debugData);
|
|
242
|
+
}
|
|
197
243
|
}
|
|
198
244
|
|
|
245
|
+
@ReactMethod
|
|
246
|
+
public void invokeAttentiveDebugHelper() {
|
|
247
|
+
debugHelper.invokeDebugHelper();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
@ReactMethod
|
|
251
|
+
public void exportDebugLogs(com.facebook.react.bridge.Promise promise) {
|
|
252
|
+
try {
|
|
253
|
+
String exportContent = debugHelper.exportDebugLogs();
|
|
254
|
+
promise.resolve(exportContent);
|
|
255
|
+
} catch (Exception e) {
|
|
256
|
+
promise.reject("EXPORT_ERROR", "Failed to export debug logs: " + e.getMessage(), e);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
|
|
199
261
|
private Map<String, String> convertToStringMap(Map<String, Object> inputMap) {
|
|
200
262
|
Map<String, String> outputMap = new HashMap<>();
|
|
201
263
|
for (Map.Entry<String, Object> entry : inputMap.entrySet()) {
|
|
@@ -244,4 +306,5 @@ public class AttentiveReactNativeSdkModule extends ReactContextBaseJavaModule {
|
|
|
244
306
|
|
|
245
307
|
return items;
|
|
246
308
|
}
|
|
309
|
+
|
|
247
310
|
}
|
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
package com.attentivereactnativesdk.debug
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.content.Intent
|
|
5
|
+
import android.graphics.Color
|
|
6
|
+
import android.graphics.Typeface
|
|
7
|
+
import android.graphics.drawable.GradientDrawable
|
|
8
|
+
import android.util.DisplayMetrics
|
|
9
|
+
import android.util.Log
|
|
10
|
+
import android.view.Gravity
|
|
11
|
+
import android.view.View
|
|
12
|
+
import android.view.ViewGroup
|
|
13
|
+
import android.widget.Button
|
|
14
|
+
import android.widget.FrameLayout
|
|
15
|
+
import android.widget.LinearLayout
|
|
16
|
+
import android.widget.ScrollView
|
|
17
|
+
import android.widget.TextView
|
|
18
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
19
|
+
import com.facebook.react.bridge.UiThreadUtil
|
|
20
|
+
import org.json.JSONObject
|
|
21
|
+
import java.text.SimpleDateFormat
|
|
22
|
+
import java.util.Date
|
|
23
|
+
import java.util.Locale
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Kotlin-based debug helper for the Attentive React Native SDK.
|
|
27
|
+
*
|
|
28
|
+
* This class provides comprehensive debugging capabilities including:
|
|
29
|
+
* - Session history tracking
|
|
30
|
+
* - Debug overlay with tabbed interface
|
|
31
|
+
* - Export and share functionality
|
|
32
|
+
* - Real-time debug information display
|
|
33
|
+
*/
|
|
34
|
+
class AttentiveDebugHelper(private val reactContext: ReactApplicationContext) {
|
|
35
|
+
|
|
36
|
+
companion object {
|
|
37
|
+
private const val TAG = "AttentiveDebugHelper"
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private val debugHistory = mutableListOf<DebugEvent>()
|
|
41
|
+
var isDebuggingEnabled = false
|
|
42
|
+
private set
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Initializes debugging based on configuration and build type.
|
|
46
|
+
*
|
|
47
|
+
* @param enableDebuggerFromConfig Whether debugging is enabled in configuration
|
|
48
|
+
*/
|
|
49
|
+
fun initialize(enableDebuggerFromConfig: Boolean) {
|
|
50
|
+
val isDebugBuild = (reactContext.applicationInfo.flags and
|
|
51
|
+
android.content.pm.ApplicationInfo.FLAG_DEBUGGABLE) != 0
|
|
52
|
+
isDebuggingEnabled = enableDebuggerFromConfig && isDebugBuild
|
|
53
|
+
|
|
54
|
+
Log.i(TAG, "Debug initialization - enableDebuggerFromConfig: $enableDebuggerFromConfig, " +
|
|
55
|
+
"isDebugBuild: $isDebugBuild, debuggingEnabled: $isDebuggingEnabled")
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Shows debug information by adding to history and displaying the debug dialog.
|
|
60
|
+
*
|
|
61
|
+
* @param event The event name/type
|
|
62
|
+
* @param data The event data
|
|
63
|
+
*/
|
|
64
|
+
fun showDebugInfo(event: String, data: Map<String, Any?>) {
|
|
65
|
+
if (!isDebuggingEnabled) return
|
|
66
|
+
|
|
67
|
+
Log.i(TAG, "showDebugInfo called for event: $event, data: $data")
|
|
68
|
+
|
|
69
|
+
// Add to debug history
|
|
70
|
+
val debugEvent = DebugEvent(event, data)
|
|
71
|
+
debugHistory.add(debugEvent)
|
|
72
|
+
|
|
73
|
+
val currentActivity = reactContext.currentActivity
|
|
74
|
+
if (currentActivity != null) {
|
|
75
|
+
Log.i(TAG, "Current activity found, showing debug dialog on UI thread")
|
|
76
|
+
UiThreadUtil.runOnUiThread {
|
|
77
|
+
showDebugDialog(currentActivity, event, data)
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
Log.w(TAG, "Current activity is null, cannot show debug dialog")
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Manually invokes the debug helper overlay.
|
|
86
|
+
* Shows existing debug session data without adding to history.
|
|
87
|
+
*/
|
|
88
|
+
fun invokeDebugHelper() {
|
|
89
|
+
if (!isDebuggingEnabled) return
|
|
90
|
+
|
|
91
|
+
val currentActivity = reactContext.currentActivity
|
|
92
|
+
if (currentActivity != null) {
|
|
93
|
+
UiThreadUtil.runOnUiThread {
|
|
94
|
+
val debugData = mapOf(
|
|
95
|
+
"action" to "manual_debug_call",
|
|
96
|
+
"session_events" to debugHistory.size.toString()
|
|
97
|
+
)
|
|
98
|
+
showDebugDialog(currentActivity, "Manual Debug View", debugData)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Exports the current debug session logs as a formatted string.
|
|
105
|
+
* @return A comprehensive formatted string containing all debug events in the current session
|
|
106
|
+
*/
|
|
107
|
+
fun exportDebugLogs(): String {
|
|
108
|
+
if (!isDebuggingEnabled) {
|
|
109
|
+
return "Debug logging is not enabled. Please enable debugging to export logs."
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (debugHistory.isEmpty()) {
|
|
113
|
+
return "No debug events recorded in this session."
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
val formatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
|
|
117
|
+
val exportDate = formatter.format(Date())
|
|
118
|
+
|
|
119
|
+
return buildString {
|
|
120
|
+
appendLine("Attentive React Native SDK - Debug Session Export")
|
|
121
|
+
appendLine("Generated: $exportDate")
|
|
122
|
+
appendLine("Total Events: ${debugHistory.size}")
|
|
123
|
+
appendLine()
|
|
124
|
+
appendLine("=".repeat(60))
|
|
125
|
+
appendLine()
|
|
126
|
+
|
|
127
|
+
// Add all events in chronological order (oldest first for better readability)
|
|
128
|
+
debugHistory.forEachIndexed { index, event ->
|
|
129
|
+
appendLine("Event #${index + 1}")
|
|
130
|
+
append(event.formatForExport())
|
|
131
|
+
appendLine()
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
appendLine("=".repeat(60))
|
|
135
|
+
append("End of Debug Session Export")
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Shares debug logs using the Android share intent.
|
|
141
|
+
* @param context The current activity context
|
|
142
|
+
* @param content The content to share
|
|
143
|
+
*/
|
|
144
|
+
private fun shareDebugLogs(context: Activity, content: String) {
|
|
145
|
+
val shareIntent = Intent().apply {
|
|
146
|
+
action = Intent.ACTION_SEND
|
|
147
|
+
type = "text/plain"
|
|
148
|
+
putExtra(Intent.EXTRA_TEXT, content)
|
|
149
|
+
putExtra(Intent.EXTRA_SUBJECT, "Attentive React Native SDK - Debug Session Export")
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
val chooser = Intent.createChooser(shareIntent, "Share Debug Logs")
|
|
153
|
+
if (shareIntent.resolveActivity(context.packageManager) != null) {
|
|
154
|
+
context.startActivity(chooser)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Creates and displays the comprehensive debug overlay with tabbed interface.
|
|
160
|
+
* Features:
|
|
161
|
+
* - Current event details
|
|
162
|
+
* - Session history with chronological listing
|
|
163
|
+
* - Export/share functionality
|
|
164
|
+
* - Material Design styling with rounded corners and proper spacing
|
|
165
|
+
*/
|
|
166
|
+
private fun showDebugDialog(activity: Activity, currentEvent: String, currentData: Map<String, Any?>) {
|
|
167
|
+
Log.i(TAG, "showDebugDialog called for event: $currentEvent")
|
|
168
|
+
|
|
169
|
+
// Get the root view of the current activity to add our overlay directly
|
|
170
|
+
val rootView = activity.window.decorView.rootView as ViewGroup
|
|
171
|
+
Log.i(TAG, "Root view obtained: $rootView")
|
|
172
|
+
|
|
173
|
+
// Create custom view for tabbed interface - this will be added directly to the activity
|
|
174
|
+
val mainLayout = LinearLayout(activity).apply {
|
|
175
|
+
orientation = LinearLayout.VERTICAL
|
|
176
|
+
setPadding(32, 32, 32, 32)
|
|
177
|
+
setBackgroundColor(Color.TRANSPARENT) // Completely transparent background
|
|
178
|
+
layoutParams = FrameLayout.LayoutParams(
|
|
179
|
+
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
180
|
+
FrameLayout.LayoutParams.MATCH_PARENT
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Create content container that will be positioned at bottom
|
|
185
|
+
val contentContainer = LinearLayout(activity).apply {
|
|
186
|
+
orientation = LinearLayout.VERTICAL
|
|
187
|
+
setBackgroundColor(0xFFFFFFFF.toInt())
|
|
188
|
+
setPadding(24, 24, 24, 24)
|
|
189
|
+
|
|
190
|
+
// Round corners with discrete outline border
|
|
191
|
+
val background = GradientDrawable().apply {
|
|
192
|
+
setColor(0xFFFFFFFF.toInt()) // White background
|
|
193
|
+
cornerRadius = 24f // Rounded corners
|
|
194
|
+
setStroke(2, 0xFFE0E0E0.toInt()) // Discrete light gray border (2dp width)
|
|
195
|
+
}
|
|
196
|
+
setBackground(background)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Header with title, share button, and close button
|
|
200
|
+
val headerLayout = LinearLayout(activity).apply {
|
|
201
|
+
orientation = LinearLayout.HORIZONTAL
|
|
202
|
+
gravity = Gravity.CENTER_VERTICAL
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
val titleText = TextView(activity).apply {
|
|
206
|
+
text = "🐛 Attentive Debug Session"
|
|
207
|
+
textSize = 18f
|
|
208
|
+
typeface = Typeface.DEFAULT_BOLD
|
|
209
|
+
setTextColor(Color.BLACK)
|
|
210
|
+
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
|
|
211
|
+
}
|
|
212
|
+
headerLayout.addView(titleText)
|
|
213
|
+
|
|
214
|
+
// Share button - Material Design share icon, circular
|
|
215
|
+
val shareButton = createCircularButton(activity, "↗", 0xFF1976D2.toInt(), 0xFFF0F0F0.toInt()).apply {
|
|
216
|
+
layoutParams = LinearLayout.LayoutParams(72, 72).apply {
|
|
217
|
+
setMargins(0, 0, 20, 0)
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
headerLayout.addView(shareButton)
|
|
221
|
+
|
|
222
|
+
// Close button - circular with prominent X and destructive styling
|
|
223
|
+
val closeButton = createCircularButton(activity, "×", 0xFFD32F2F.toInt(), 0xFFFFF0F0.toInt()).apply {
|
|
224
|
+
textSize = 32f
|
|
225
|
+
layoutParams = LinearLayout.LayoutParams(72, 72)
|
|
226
|
+
}
|
|
227
|
+
headerLayout.addView(closeButton)
|
|
228
|
+
|
|
229
|
+
contentContainer.addView(headerLayout)
|
|
230
|
+
|
|
231
|
+
// Tab buttons
|
|
232
|
+
val tabLayout = LinearLayout(activity).apply {
|
|
233
|
+
orientation = LinearLayout.HORIZONTAL
|
|
234
|
+
setPadding(0, 20, 0, 0)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
val currentTab = Button(activity).apply {
|
|
238
|
+
text = "Current Event"
|
|
239
|
+
setTextColor(Color.BLACK)
|
|
240
|
+
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
val historyTab = Button(activity).apply {
|
|
244
|
+
text = "History (${debugHistory.size})"
|
|
245
|
+
setTextColor(Color.BLACK)
|
|
246
|
+
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
tabLayout.addView(currentTab)
|
|
250
|
+
tabLayout.addView(historyTab)
|
|
251
|
+
contentContainer.addView(tabLayout)
|
|
252
|
+
|
|
253
|
+
// Content area
|
|
254
|
+
val contentScroll = ScrollView(activity)
|
|
255
|
+
val contentText = TextView(activity).apply {
|
|
256
|
+
typeface = Typeface.MONOSPACE
|
|
257
|
+
textSize = 12f
|
|
258
|
+
setTextColor(0xFF333333.toInt()) // Dark gray text for better readability
|
|
259
|
+
setPadding(0, 20, 0, 0)
|
|
260
|
+
}
|
|
261
|
+
contentScroll.addView(contentText)
|
|
262
|
+
|
|
263
|
+
// Make content area larger to fill most of the screen
|
|
264
|
+
contentScroll.layoutParams = LinearLayout.LayoutParams(
|
|
265
|
+
LinearLayout.LayoutParams.MATCH_PARENT, 0, 1f
|
|
266
|
+
)
|
|
267
|
+
contentContainer.addView(contentScroll)
|
|
268
|
+
|
|
269
|
+
// Position content container at bottom with minimum height (55% of screen - slightly shorter)
|
|
270
|
+
val displayMetrics = DisplayMetrics()
|
|
271
|
+
activity.windowManager.defaultDisplay.getMetrics(displayMetrics)
|
|
272
|
+
val screenHeight = displayMetrics.heightPixels
|
|
273
|
+
val minHeight = (screenHeight * 0.55).toInt() // 55% of screen height - slightly shorter
|
|
274
|
+
|
|
275
|
+
contentContainer.apply {
|
|
276
|
+
minimumHeight = minHeight
|
|
277
|
+
layoutParams = LinearLayout.LayoutParams(
|
|
278
|
+
LinearLayout.LayoutParams.MATCH_PARENT,
|
|
279
|
+
LinearLayout.LayoutParams.WRAP_CONTENT
|
|
280
|
+
).apply {
|
|
281
|
+
gravity = Gravity.BOTTOM
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
Log.i(TAG, "Debug overlay minimum height set to: ${minHeight}px (55% of ${screenHeight}px)")
|
|
286
|
+
|
|
287
|
+
// Add spacer to push content to bottom
|
|
288
|
+
val spacer = View(activity).apply {
|
|
289
|
+
layoutParams = LinearLayout.LayoutParams(
|
|
290
|
+
LinearLayout.LayoutParams.MATCH_PARENT, 0, 1f
|
|
291
|
+
)
|
|
292
|
+
}
|
|
293
|
+
mainLayout.addView(spacer)
|
|
294
|
+
mainLayout.addView(contentContainer)
|
|
295
|
+
|
|
296
|
+
// Set initial content to current event
|
|
297
|
+
updateCurrentEventContent(contentText, currentEvent, currentData)
|
|
298
|
+
|
|
299
|
+
// Tab click listeners
|
|
300
|
+
currentTab.setOnClickListener {
|
|
301
|
+
updateCurrentEventContent(contentText, currentEvent, currentData)
|
|
302
|
+
currentTab.isEnabled = false
|
|
303
|
+
historyTab.isEnabled = true
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
historyTab.setOnClickListener {
|
|
307
|
+
updateHistoryContent(contentText)
|
|
308
|
+
currentTab.isEnabled = true
|
|
309
|
+
historyTab.isEnabled = false
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Set initial tab state
|
|
313
|
+
currentTab.isEnabled = false
|
|
314
|
+
historyTab.isEnabled = true
|
|
315
|
+
|
|
316
|
+
// Setup share button to export and share debug logs
|
|
317
|
+
shareButton.setOnClickListener {
|
|
318
|
+
val exportContent = exportDebugLogs()
|
|
319
|
+
shareDebugLogs(activity, exportContent)
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Setup close button to remove the overlay from the root view
|
|
323
|
+
closeButton.setOnClickListener {
|
|
324
|
+
rootView.removeView(mainLayout)
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Add the overlay directly to the activity's root view
|
|
328
|
+
rootView.addView(mainLayout)
|
|
329
|
+
Log.i(TAG, "Debug overlay added to root view successfully")
|
|
330
|
+
|
|
331
|
+
// Note: No auto-dismiss to match iOS behavior - overlay stays until user closes it
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Creates a circular button with specified text, colors, and styling.
|
|
336
|
+
*/
|
|
337
|
+
private fun createCircularButton(activity: Activity, text: String, textColor: Int, backgroundColor: Int): Button {
|
|
338
|
+
return Button(activity).apply {
|
|
339
|
+
this.text = text
|
|
340
|
+
textSize = 28f
|
|
341
|
+
setTextColor(textColor)
|
|
342
|
+
setBackgroundColor(backgroundColor)
|
|
343
|
+
typeface = Typeface.DEFAULT_BOLD
|
|
344
|
+
gravity = Gravity.CENTER
|
|
345
|
+
includeFontPadding = false
|
|
346
|
+
minWidth = 0
|
|
347
|
+
minHeight = 0
|
|
348
|
+
minimumWidth = 0
|
|
349
|
+
minimumHeight = 0
|
|
350
|
+
textAlignment = View.TEXT_ALIGNMENT_CENTER
|
|
351
|
+
setSingleLine(true)
|
|
352
|
+
setLines(1)
|
|
353
|
+
setPadding(0, -20, 0, 8)
|
|
354
|
+
|
|
355
|
+
val drawable = GradientDrawable().apply {
|
|
356
|
+
shape = GradientDrawable.OVAL
|
|
357
|
+
setColor(backgroundColor)
|
|
358
|
+
setStroke(3, textColor)
|
|
359
|
+
}
|
|
360
|
+
background = drawable
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Updates the content text view with current event information.
|
|
366
|
+
*/
|
|
367
|
+
private fun updateCurrentEventContent(contentText: TextView, event: String, data: Map<String, Any?>) {
|
|
368
|
+
val content = buildString {
|
|
369
|
+
appendLine("Event: $event")
|
|
370
|
+
appendLine()
|
|
371
|
+
|
|
372
|
+
try {
|
|
373
|
+
val jsonObject = JSONObject(data)
|
|
374
|
+
append(jsonObject.toString(2))
|
|
375
|
+
} catch (e: Exception) {
|
|
376
|
+
append("Data: $data")
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
contentText.text = content
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Updates the content text view with session history information.
|
|
385
|
+
*/
|
|
386
|
+
private fun updateHistoryContent(contentText: TextView) {
|
|
387
|
+
if (debugHistory.isEmpty()) {
|
|
388
|
+
contentText.text = "No events recorded in this session yet."
|
|
389
|
+
return
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
val content = buildString {
|
|
393
|
+
appendLine("Session History (${debugHistory.size} events):")
|
|
394
|
+
appendLine()
|
|
395
|
+
|
|
396
|
+
// Show events in reverse order (newest first)
|
|
397
|
+
debugHistory.asReversed().forEach { event ->
|
|
398
|
+
appendLine("───────────────────────────────")
|
|
399
|
+
appendLine("[${event.getFormattedTime()}] ${event.eventType}")
|
|
400
|
+
|
|
401
|
+
// Add summary
|
|
402
|
+
val summary = event.getSummary()
|
|
403
|
+
if (summary.isNotEmpty()) {
|
|
404
|
+
appendLine("Summary: $summary")
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
appendLine()
|
|
408
|
+
appendLine("Payload:")
|
|
409
|
+
try {
|
|
410
|
+
val jsonObject = JSONObject(event.data)
|
|
411
|
+
appendLine(jsonObject.toString(2))
|
|
412
|
+
} catch (e: Exception) {
|
|
413
|
+
appendLine(event.data.toString())
|
|
414
|
+
}
|
|
415
|
+
appendLine()
|
|
416
|
+
appendLine()
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
contentText.text = content
|
|
421
|
+
}
|
|
422
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
package com.attentivereactnativesdk.debug
|
|
2
|
+
|
|
3
|
+
import org.json.JSONObject
|
|
4
|
+
import java.text.SimpleDateFormat
|
|
5
|
+
import java.util.Date
|
|
6
|
+
import java.util.Locale
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Data class representing a debug event with timestamp and formatting capabilities.
|
|
10
|
+
*
|
|
11
|
+
* @param eventType The type/name of the event
|
|
12
|
+
* @param data The event data as a map
|
|
13
|
+
*/
|
|
14
|
+
data class DebugEvent(
|
|
15
|
+
val eventType: String,
|
|
16
|
+
val data: Map<String, Any?>
|
|
17
|
+
) {
|
|
18
|
+
val timestamp: Long = System.currentTimeMillis()
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Returns formatted time string for display.
|
|
22
|
+
*/
|
|
23
|
+
fun getFormattedTime(): String {
|
|
24
|
+
val formatter = SimpleDateFormat("HH:mm:ss", Locale.getDefault())
|
|
25
|
+
return formatter.format(Date(timestamp))
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Generates a summary string with key information from the event data.
|
|
30
|
+
*/
|
|
31
|
+
fun getSummary(): String {
|
|
32
|
+
val summaryParts = mutableListOf<String>()
|
|
33
|
+
|
|
34
|
+
data["items_count"]?.let { summaryParts.add("Items: $it") }
|
|
35
|
+
data["order_id"]?.let { summaryParts.add("Order: $it") }
|
|
36
|
+
data["creativeId"]?.let { summaryParts.add("Creative: $it") }
|
|
37
|
+
data["event_type"]?.let { summaryParts.add("Type: $it") }
|
|
38
|
+
|
|
39
|
+
// Always show payload size info
|
|
40
|
+
summaryParts.add("Payload: ${data.size} fields")
|
|
41
|
+
|
|
42
|
+
return summaryParts.joinToString(" • ")
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Formats the debug event as a human-readable string for export.
|
|
47
|
+
* @return A formatted string containing timestamp, event type, and data
|
|
48
|
+
*/
|
|
49
|
+
fun formatForExport(): String {
|
|
50
|
+
val formatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault())
|
|
51
|
+
val timeString = formatter.format(Date(timestamp))
|
|
52
|
+
|
|
53
|
+
return buildString {
|
|
54
|
+
appendLine("[$timeString] $eventType")
|
|
55
|
+
|
|
56
|
+
// Add summary information if available
|
|
57
|
+
val summary = getSummary()
|
|
58
|
+
if (summary.isNotEmpty()) {
|
|
59
|
+
appendLine("Summary: $summary")
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
appendLine("Data:")
|
|
63
|
+
|
|
64
|
+
// Format data as JSON for better readability
|
|
65
|
+
try {
|
|
66
|
+
val jsonObject = JSONObject(data)
|
|
67
|
+
appendLine(jsonObject.toString(2))
|
|
68
|
+
} catch (e: Exception) {
|
|
69
|
+
appendLine(data.toString())
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
appendLine("=".repeat(50))
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
}
|
|
@@ -11,13 +11,12 @@ Pod::Spec.new do |s|
|
|
|
11
11
|
s.license = package["license"]
|
|
12
12
|
s.authors = package["author"]
|
|
13
13
|
|
|
14
|
-
s.platforms = { :ios => "
|
|
14
|
+
s.platforms = { :ios => "14.0" }
|
|
15
15
|
s.source = { :git => "https://github.com/attentive-mobile/attentive-react-native-sdk.git", :tag => "#{s.version}" }
|
|
16
16
|
|
|
17
17
|
s.source_files = "ios/**/*.{h,m,mm,swift}"
|
|
18
18
|
|
|
19
19
|
s.dependency 'attentive-ios-sdk', '1.0.0'
|
|
20
|
-
s.ios.deployment_target = '14.0'
|
|
21
20
|
s.swift_versions = ['5']
|
|
22
21
|
s.dependency "React-Core"
|
|
23
22
|
|