@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 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
@@ -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()) {
@@ -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 => "11.0" }
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