@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
|
@@ -8,25 +8,120 @@
|
|
|
8
8
|
|
|
9
9
|
import Foundation
|
|
10
10
|
import attentive_ios_sdk
|
|
11
|
+
import UIKit
|
|
12
|
+
|
|
13
|
+
// Debug Event structure for session history
|
|
14
|
+
struct DebugEvent {
|
|
15
|
+
let id: UUID
|
|
16
|
+
let timestamp: Date
|
|
17
|
+
let eventType: String
|
|
18
|
+
let data: [String: Any]
|
|
19
|
+
|
|
20
|
+
init(eventType: String, data: [String: Any]) {
|
|
21
|
+
self.id = UUID()
|
|
22
|
+
self.timestamp = Date()
|
|
23
|
+
self.eventType = eventType
|
|
24
|
+
self.data = data
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Formats the debug event as a human-readable string for export
|
|
29
|
+
* @return A formatted string containing timestamp, event type, and data
|
|
30
|
+
*/
|
|
31
|
+
func formatForExport() -> String {
|
|
32
|
+
let formatter = DateFormatter()
|
|
33
|
+
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
|
|
34
|
+
let timeString = formatter.string(from: timestamp)
|
|
35
|
+
|
|
36
|
+
var output = "[\(timeString)] \(eventType)\n"
|
|
37
|
+
|
|
38
|
+
// Add summary information if available
|
|
39
|
+
let summary = getSummary()
|
|
40
|
+
if !summary.isEmpty {
|
|
41
|
+
output += "Summary: \(summary)\n"
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
output += "Data:\n"
|
|
45
|
+
|
|
46
|
+
// Format data as JSON for better readability
|
|
47
|
+
do {
|
|
48
|
+
let jsonData = try JSONSerialization.data(withJSONObject: data, options: .prettyPrinted)
|
|
49
|
+
if let jsonString = String(data: jsonData, encoding: .utf8) {
|
|
50
|
+
output += jsonString
|
|
51
|
+
} else {
|
|
52
|
+
output += "\(data)"
|
|
53
|
+
}
|
|
54
|
+
} catch {
|
|
55
|
+
output += "\(data)"
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return output + "\n" + String(repeating: "=", count: 50) + "\n"
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Generates a summary of the debug event for quick overview
|
|
63
|
+
* @return A brief summary string highlighting key information
|
|
64
|
+
*/
|
|
65
|
+
private func getSummary() -> String {
|
|
66
|
+
var summaryParts: [String] = []
|
|
67
|
+
|
|
68
|
+
if let itemsCount = data["items_count"] as? String {
|
|
69
|
+
summaryParts.append("Items: \(itemsCount)")
|
|
70
|
+
}
|
|
71
|
+
if let orderId = data["order_id"] as? String {
|
|
72
|
+
summaryParts.append("Order: \(orderId)")
|
|
73
|
+
}
|
|
74
|
+
if let creativeId = data["creativeId"] as? String {
|
|
75
|
+
summaryParts.append("Creative: \(creativeId)")
|
|
76
|
+
}
|
|
77
|
+
if let eventType = data["event_type"] as? String {
|
|
78
|
+
summaryParts.append("Type: \(eventType)")
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Always show payload size info
|
|
82
|
+
summaryParts.append("Payload: \(data.count) fields")
|
|
83
|
+
|
|
84
|
+
return summaryParts.joined(separator: " • ")
|
|
85
|
+
}
|
|
86
|
+
}
|
|
11
87
|
|
|
12
88
|
@objc public class ATTNNativeSDK: NSObject {
|
|
13
89
|
private let sdk: ATTNSDK
|
|
90
|
+
private var debuggingEnabled: Bool = false
|
|
91
|
+
private var debugOverlayWindow: UIWindow?
|
|
92
|
+
private var debugHistory: [DebugEvent] = []
|
|
14
93
|
|
|
15
|
-
@objc(initWithDomain:mode:skipFatigueOnCreatives:)
|
|
16
|
-
public init(domain: String, mode: String, skipFatigueOnCreatives: Bool) {
|
|
94
|
+
@objc(initWithDomain:mode:skipFatigueOnCreatives:enableDebugger:)
|
|
95
|
+
public init(domain: String, mode: String, skipFatigueOnCreatives: Bool, enableDebugger: Bool) {
|
|
17
96
|
self.sdk = ATTNSDK(domain: domain, mode: ATTNSDKMode(rawValue: mode) ?? .production)
|
|
18
97
|
self.sdk.skipFatigueOnCreative = skipFatigueOnCreatives ?? false
|
|
98
|
+
|
|
99
|
+
// Only enable debugging if both enableDebugger is true AND the app is running in debug mode
|
|
100
|
+
let enableDebuggerFromConfig = enableDebugger ?? false
|
|
101
|
+
#if DEBUG
|
|
102
|
+
let isDebugBuild = true
|
|
103
|
+
#else
|
|
104
|
+
let isDebugBuild = false
|
|
105
|
+
#endif
|
|
106
|
+
self.debuggingEnabled = enableDebuggerFromConfig && isDebugBuild
|
|
107
|
+
|
|
19
108
|
ATTNEventTracker.setup(with: sdk)
|
|
20
109
|
}
|
|
21
110
|
|
|
22
111
|
@objc(trigger:)
|
|
23
112
|
public func trigger(_ view: UIView) {
|
|
24
113
|
sdk.trigger(view)
|
|
114
|
+
if debuggingEnabled {
|
|
115
|
+
showDebugInfo(event: "Creative Triggered", data: ["type": "trigger", "creativeId": "default"])
|
|
116
|
+
}
|
|
25
117
|
}
|
|
26
118
|
|
|
27
119
|
@objc(trigger:creativeId:)
|
|
28
120
|
public func trigger(_ view: UIView, creativeId: String) {
|
|
29
121
|
sdk.trigger(view, creativeId:creativeId)
|
|
122
|
+
if debuggingEnabled {
|
|
123
|
+
showDebugInfo(event: "Creative Triggered", data: ["type": "trigger", "creativeId": creativeId])
|
|
124
|
+
}
|
|
30
125
|
}
|
|
31
126
|
|
|
32
127
|
@objc(updateDomain:)
|
|
@@ -43,15 +138,81 @@ import attentive_ios_sdk
|
|
|
43
138
|
public func clearUser() {
|
|
44
139
|
sdk.clearUser()
|
|
45
140
|
}
|
|
141
|
+
|
|
142
|
+
@objc
|
|
143
|
+
public func invokeAttentiveDebugHelper() {
|
|
144
|
+
if debuggingEnabled {
|
|
145
|
+
// Don't add to history - this is just for viewing existing debug data
|
|
146
|
+
DispatchQueue.main.async {
|
|
147
|
+
guard let keyWindow = UIApplication.shared.connectedScenes
|
|
148
|
+
.compactMap({ $0 as? UIWindowScene })
|
|
149
|
+
.first?.windows
|
|
150
|
+
.first(where: { $0.isKeyWindow }) else { return }
|
|
151
|
+
|
|
152
|
+
let debugVC = DebugOverlayViewController(currentEvent: "Manual Debug View", currentData: ["action": "manual_debug_call", "session_events": "\(self.debugHistory.count)"], history: self.debugHistory)
|
|
153
|
+
debugVC.modalPresentationStyle = .overFullScreen
|
|
154
|
+
debugVC.modalTransitionStyle = .crossDissolve
|
|
155
|
+
|
|
156
|
+
keyWindow.rootViewController?.present(debugVC, animated: true)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
46
161
|
}
|
|
47
162
|
|
|
48
163
|
public extension ATTNNativeSDK {
|
|
164
|
+
/**
|
|
165
|
+
* Exports the current debug session logs as a formatted string
|
|
166
|
+
* @return A comprehensive formatted string containing all debug events in the current session
|
|
167
|
+
*/
|
|
168
|
+
@objc
|
|
169
|
+
func exportDebugLogs() -> String {
|
|
170
|
+
guard debuggingEnabled else {
|
|
171
|
+
return "Debug logging is not enabled. Please enable debugging to export logs."
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if debugHistory.isEmpty {
|
|
175
|
+
return "No debug events recorded in this session."
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
let formatter = DateFormatter()
|
|
179
|
+
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
|
|
180
|
+
let exportDate = formatter.string(from: Date())
|
|
181
|
+
|
|
182
|
+
var exportContent = """
|
|
183
|
+
Attentive React Native SDK - Debug Session Export
|
|
184
|
+
Generated: \(exportDate)
|
|
185
|
+
Total Events: \(debugHistory.count)
|
|
186
|
+
|
|
187
|
+
\(String(repeating: "=", count: 60))
|
|
188
|
+
|
|
189
|
+
"""
|
|
190
|
+
|
|
191
|
+
// Add all events in chronological order (oldest first for better readability)
|
|
192
|
+
for (index, event) in debugHistory.enumerated() {
|
|
193
|
+
exportContent += "Event #\(index + 1)\n"
|
|
194
|
+
exportContent += event.formatForExport()
|
|
195
|
+
exportContent += "\n"
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
exportContent += """
|
|
199
|
+
\(String(repeating: "=", count: 60))
|
|
200
|
+
End of Debug Session Export
|
|
201
|
+
"""
|
|
202
|
+
|
|
203
|
+
return exportContent
|
|
204
|
+
}
|
|
205
|
+
|
|
49
206
|
@objc
|
|
50
207
|
func recordAddToCartEvent(_ attributes: [String: Any]) {
|
|
51
208
|
let items = parseItems(attributes["items"] as? [[String : Any]] ?? [])
|
|
52
209
|
let deeplink = attributes["deeplink"] as? String ?? ""
|
|
53
210
|
let event = ATTNAddToCartEvent(items: items, deeplink: deeplink)
|
|
54
211
|
ATTNEventTracker.sharedInstance()?.record(event: event)
|
|
212
|
+
|
|
213
|
+
if debuggingEnabled {
|
|
214
|
+
showDebugInfo(event: "Add To Cart Event", data: ["items_count": "\(items.count)", "deeplink": deeplink, "payload": attributes])
|
|
215
|
+
}
|
|
55
216
|
}
|
|
56
217
|
|
|
57
218
|
@objc
|
|
@@ -60,6 +221,10 @@ public extension ATTNNativeSDK {
|
|
|
60
221
|
let deeplink = attributes["deeplink"] as? String ?? ""
|
|
61
222
|
let event = ATTNProductViewEvent(items: items, deeplink: deeplink)
|
|
62
223
|
ATTNEventTracker.sharedInstance()?.record(event: event)
|
|
224
|
+
|
|
225
|
+
if debuggingEnabled {
|
|
226
|
+
showDebugInfo(event: "Product View Event", data: ["items_count": "\(items.count)", "deeplink": deeplink, "payload": attributes])
|
|
227
|
+
}
|
|
63
228
|
}
|
|
64
229
|
|
|
65
230
|
@objc
|
|
@@ -70,6 +235,10 @@ public extension ATTNNativeSDK {
|
|
|
70
235
|
let items = parseItems(attributes["items"] as? [[String : Any]] ?? [])
|
|
71
236
|
let event = ATTNPurchaseEvent(items: items, order: order)
|
|
72
237
|
ATTNEventTracker.sharedInstance()?.record(event: event)
|
|
238
|
+
|
|
239
|
+
if debuggingEnabled {
|
|
240
|
+
showDebugInfo(event: "Purchase Event", data: ["items_count": "\(items.count)", "order_id": orderId, "payload": attributes])
|
|
241
|
+
}
|
|
73
242
|
}
|
|
74
243
|
|
|
75
244
|
@objc
|
|
@@ -78,6 +247,10 @@ public extension ATTNNativeSDK {
|
|
|
78
247
|
let properties = attributes["properties"] as? [String: String] ?? [:]
|
|
79
248
|
guard let customEvent = ATTNCustomEvent(type: type, properties: properties) else { return }
|
|
80
249
|
ATTNEventTracker.sharedInstance()?.record(event: customEvent)
|
|
250
|
+
|
|
251
|
+
if debuggingEnabled {
|
|
252
|
+
showDebugInfo(event: "Custom Event", data: ["event_type": type, "properties_count": "\(properties.count)", "payload": attributes])
|
|
253
|
+
}
|
|
81
254
|
}
|
|
82
255
|
}
|
|
83
256
|
|
|
@@ -105,4 +278,599 @@ private extension ATTNNativeSDK {
|
|
|
105
278
|
|
|
106
279
|
return itemsToReturn
|
|
107
280
|
}
|
|
281
|
+
|
|
282
|
+
func showDebugInfo(event: String, data: [String: Any]) {
|
|
283
|
+
// Add to debug history
|
|
284
|
+
let debugEvent = DebugEvent(eventType: event, data: data)
|
|
285
|
+
debugHistory.append(debugEvent)
|
|
286
|
+
|
|
287
|
+
DispatchQueue.main.async {
|
|
288
|
+
// Create debug overlay with history
|
|
289
|
+
guard let keyWindow = UIApplication.shared.connectedScenes
|
|
290
|
+
.compactMap({ $0 as? UIWindowScene })
|
|
291
|
+
.first?.windows
|
|
292
|
+
.first(where: { $0.isKeyWindow }) else { return }
|
|
293
|
+
|
|
294
|
+
let debugVC = DebugOverlayViewController(currentEvent: event, currentData: data, history: self.debugHistory)
|
|
295
|
+
debugVC.modalPresentationStyle = .overFullScreen
|
|
296
|
+
debugVC.modalTransitionStyle = .crossDissolve
|
|
297
|
+
|
|
298
|
+
keyWindow.rootViewController?.present(debugVC, animated: true)
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Debug Overlay View Controller
|
|
304
|
+
class DebugOverlayViewController: UIViewController {
|
|
305
|
+
private let currentEvent: String
|
|
306
|
+
private let currentData: [String: Any]
|
|
307
|
+
private let history: [DebugEvent]
|
|
308
|
+
|
|
309
|
+
private var segmentedControl: UISegmentedControl!
|
|
310
|
+
private var containerView: UIView!
|
|
311
|
+
private var currentEventView: UIView!
|
|
312
|
+
private var historyView: UIView!
|
|
313
|
+
|
|
314
|
+
init(currentEvent: String, currentData: [String: Any], history: [DebugEvent]) {
|
|
315
|
+
self.currentEvent = currentEvent
|
|
316
|
+
self.currentData = currentData
|
|
317
|
+
self.history = history
|
|
318
|
+
super.init(nibName: nil, bundle: nil)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
required init?(coder: NSCoder) {
|
|
322
|
+
fatalError("init(coder:) has not been implemented")
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
override func viewDidLoad() {
|
|
326
|
+
super.viewDidLoad()
|
|
327
|
+
setupUI()
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
private func setupUI() {
|
|
331
|
+
view.backgroundColor = UIColor.black.withAlphaComponent(0.8)
|
|
332
|
+
|
|
333
|
+
// Main container
|
|
334
|
+
containerView = UIView()
|
|
335
|
+
containerView.backgroundColor = UIColor.systemBackground
|
|
336
|
+
containerView.layer.cornerRadius = 12
|
|
337
|
+
containerView.layer.shadowColor = UIColor.black.cgColor
|
|
338
|
+
containerView.layer.shadowOpacity = 0.3
|
|
339
|
+
containerView.layer.shadowOffset = CGSize(width: 0, height: 2)
|
|
340
|
+
containerView.layer.shadowRadius = 8
|
|
341
|
+
containerView.translatesAutoresizingMaskIntoConstraints = false
|
|
342
|
+
view.addSubview(containerView)
|
|
343
|
+
|
|
344
|
+
// Title
|
|
345
|
+
let titleLabel = UILabel()
|
|
346
|
+
titleLabel.text = "🐛 Attentive Debug Session"
|
|
347
|
+
titleLabel.font = UIFont.boldSystemFont(ofSize: 18)
|
|
348
|
+
titleLabel.textAlignment = .center
|
|
349
|
+
titleLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
350
|
+
containerView.addSubview(titleLabel)
|
|
351
|
+
|
|
352
|
+
// Segmented control for Current/History
|
|
353
|
+
segmentedControl = UISegmentedControl(items: ["Current Event", "Session History (\(history.count))"])
|
|
354
|
+
segmentedControl.selectedSegmentIndex = 0
|
|
355
|
+
segmentedControl.addTarget(self, action: #selector(segmentChanged), for: .valueChanged)
|
|
356
|
+
segmentedControl.translatesAutoresizingMaskIntoConstraints = false
|
|
357
|
+
containerView.addSubview(segmentedControl)
|
|
358
|
+
|
|
359
|
+
// Content container for switching views
|
|
360
|
+
let contentContainer = UIView()
|
|
361
|
+
contentContainer.translatesAutoresizingMaskIntoConstraints = false
|
|
362
|
+
containerView.addSubview(contentContainer)
|
|
363
|
+
|
|
364
|
+
// Setup current event view
|
|
365
|
+
setupCurrentEventView()
|
|
366
|
+
contentContainer.addSubview(currentEventView)
|
|
367
|
+
|
|
368
|
+
// Setup history view
|
|
369
|
+
setupHistoryView()
|
|
370
|
+
contentContainer.addSubview(historyView)
|
|
371
|
+
historyView.isHidden = true
|
|
372
|
+
|
|
373
|
+
// Share button in top-right corner (left of close button)
|
|
374
|
+
let shareButton = UIButton(type: .system)
|
|
375
|
+
shareButton.setTitle("↗", for: .normal) // iOS-style share symbol
|
|
376
|
+
shareButton.titleLabel?.font = UIFont.systemFont(ofSize: 18, weight: .medium)
|
|
377
|
+
shareButton.setTitleColor(.systemBlue, for: .normal)
|
|
378
|
+
shareButton.backgroundColor = UIColor.systemGray5
|
|
379
|
+
shareButton.layer.cornerRadius = 15
|
|
380
|
+
shareButton.addTarget(self, action: #selector(shareButtonTapped), for: .touchUpInside)
|
|
381
|
+
shareButton.translatesAutoresizingMaskIntoConstraints = false
|
|
382
|
+
containerView.addSubview(shareButton)
|
|
383
|
+
|
|
384
|
+
// X Close button in top-right corner
|
|
385
|
+
let closeButton = UIButton(type: .system)
|
|
386
|
+
closeButton.setTitle("✕", for: .normal)
|
|
387
|
+
closeButton.titleLabel?.font = UIFont.systemFont(ofSize: 18, weight: .medium)
|
|
388
|
+
closeButton.setTitleColor(.secondaryLabel, for: .normal)
|
|
389
|
+
closeButton.backgroundColor = UIColor.systemGray5
|
|
390
|
+
closeButton.layer.cornerRadius = 15
|
|
391
|
+
closeButton.addTarget(self, action: #selector(closeButtonTapped), for: .touchUpInside)
|
|
392
|
+
closeButton.translatesAutoresizingMaskIntoConstraints = false
|
|
393
|
+
containerView.addSubview(closeButton)
|
|
394
|
+
|
|
395
|
+
// Layout constraints - position at bottom and make larger
|
|
396
|
+
NSLayoutConstraint.activate([
|
|
397
|
+
containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
|
|
398
|
+
containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
|
|
399
|
+
containerView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -16),
|
|
400
|
+
containerView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.65),
|
|
401
|
+
|
|
402
|
+
closeButton.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 12),
|
|
403
|
+
closeButton.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -12),
|
|
404
|
+
closeButton.widthAnchor.constraint(equalToConstant: 30),
|
|
405
|
+
closeButton.heightAnchor.constraint(equalToConstant: 30),
|
|
406
|
+
|
|
407
|
+
shareButton.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 12),
|
|
408
|
+
shareButton.trailingAnchor.constraint(equalTo: closeButton.leadingAnchor, constant: -8),
|
|
409
|
+
shareButton.widthAnchor.constraint(equalToConstant: 30),
|
|
410
|
+
shareButton.heightAnchor.constraint(equalToConstant: 30),
|
|
411
|
+
|
|
412
|
+
titleLabel.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 16),
|
|
413
|
+
titleLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16),
|
|
414
|
+
titleLabel.trailingAnchor.constraint(equalTo: shareButton.leadingAnchor, constant: -8),
|
|
415
|
+
|
|
416
|
+
segmentedControl.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 16),
|
|
417
|
+
segmentedControl.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16),
|
|
418
|
+
segmentedControl.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -16),
|
|
419
|
+
|
|
420
|
+
contentContainer.topAnchor.constraint(equalTo: segmentedControl.bottomAnchor, constant: 16),
|
|
421
|
+
contentContainer.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16),
|
|
422
|
+
contentContainer.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -16),
|
|
423
|
+
contentContainer.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -16),
|
|
424
|
+
|
|
425
|
+
currentEventView.topAnchor.constraint(equalTo: contentContainer.topAnchor),
|
|
426
|
+
currentEventView.leadingAnchor.constraint(equalTo: contentContainer.leadingAnchor),
|
|
427
|
+
currentEventView.trailingAnchor.constraint(equalTo: contentContainer.trailingAnchor),
|
|
428
|
+
currentEventView.bottomAnchor.constraint(equalTo: contentContainer.bottomAnchor),
|
|
429
|
+
|
|
430
|
+
historyView.topAnchor.constraint(equalTo: contentContainer.topAnchor),
|
|
431
|
+
historyView.leadingAnchor.constraint(equalTo: contentContainer.leadingAnchor),
|
|
432
|
+
historyView.trailingAnchor.constraint(equalTo: contentContainer.trailingAnchor),
|
|
433
|
+
historyView.bottomAnchor.constraint(equalTo: contentContainer.bottomAnchor)
|
|
434
|
+
])
|
|
435
|
+
|
|
436
|
+
// Auto-dismiss after 8 seconds (longer for history viewing)
|
|
437
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 8.0) {
|
|
438
|
+
self.dismiss(animated: true)
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
private func formatData(_ data: [String: Any]) -> String {
|
|
443
|
+
do {
|
|
444
|
+
let jsonData = try JSONSerialization.data(withJSONObject: data, options: .prettyPrinted)
|
|
445
|
+
return String(data: jsonData, encoding: .utf8) ?? "Unable to format data"
|
|
446
|
+
} catch {
|
|
447
|
+
return "Error formatting data: \(error.localizedDescription)"
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
private func setupCurrentEventView() {
|
|
452
|
+
currentEventView = UIView()
|
|
453
|
+
currentEventView.translatesAutoresizingMaskIntoConstraints = false
|
|
454
|
+
|
|
455
|
+
let eventLabel = UILabel()
|
|
456
|
+
eventLabel.text = "Event: \(currentEvent)"
|
|
457
|
+
eventLabel.font = UIFont.systemFont(ofSize: 16, weight: .medium)
|
|
458
|
+
eventLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
459
|
+
currentEventView.addSubview(eventLabel)
|
|
460
|
+
|
|
461
|
+
let scrollView = UIScrollView()
|
|
462
|
+
scrollView.translatesAutoresizingMaskIntoConstraints = false
|
|
463
|
+
currentEventView.addSubview(scrollView)
|
|
464
|
+
|
|
465
|
+
let dataLabel = UILabel()
|
|
466
|
+
dataLabel.text = formatData(currentData)
|
|
467
|
+
dataLabel.font = UIFont.monospacedSystemFont(ofSize: 12, weight: .regular)
|
|
468
|
+
dataLabel.numberOfLines = 0
|
|
469
|
+
dataLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
470
|
+
scrollView.addSubview(dataLabel)
|
|
471
|
+
|
|
472
|
+
NSLayoutConstraint.activate([
|
|
473
|
+
eventLabel.topAnchor.constraint(equalTo: currentEventView.topAnchor),
|
|
474
|
+
eventLabel.leadingAnchor.constraint(equalTo: currentEventView.leadingAnchor),
|
|
475
|
+
eventLabel.trailingAnchor.constraint(equalTo: currentEventView.trailingAnchor),
|
|
476
|
+
|
|
477
|
+
scrollView.topAnchor.constraint(equalTo: eventLabel.bottomAnchor, constant: 16),
|
|
478
|
+
scrollView.leadingAnchor.constraint(equalTo: currentEventView.leadingAnchor),
|
|
479
|
+
scrollView.trailingAnchor.constraint(equalTo: currentEventView.trailingAnchor),
|
|
480
|
+
scrollView.bottomAnchor.constraint(equalTo: currentEventView.bottomAnchor),
|
|
481
|
+
|
|
482
|
+
dataLabel.topAnchor.constraint(equalTo: scrollView.topAnchor),
|
|
483
|
+
dataLabel.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
|
|
484
|
+
dataLabel.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
|
|
485
|
+
dataLabel.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
|
|
486
|
+
dataLabel.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
|
|
487
|
+
])
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
private func setupHistoryView() {
|
|
491
|
+
historyView = UIView()
|
|
492
|
+
historyView.translatesAutoresizingMaskIntoConstraints = false
|
|
493
|
+
|
|
494
|
+
if history.isEmpty {
|
|
495
|
+
let emptyLabel = UILabel()
|
|
496
|
+
emptyLabel.text = "No events recorded in this session yet."
|
|
497
|
+
emptyLabel.font = UIFont.systemFont(ofSize: 16)
|
|
498
|
+
emptyLabel.textColor = .secondaryLabel
|
|
499
|
+
emptyLabel.textAlignment = .center
|
|
500
|
+
emptyLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
501
|
+
historyView.addSubview(emptyLabel)
|
|
502
|
+
|
|
503
|
+
NSLayoutConstraint.activate([
|
|
504
|
+
emptyLabel.centerXAnchor.constraint(equalTo: historyView.centerXAnchor),
|
|
505
|
+
emptyLabel.centerYAnchor.constraint(equalTo: historyView.centerYAnchor),
|
|
506
|
+
])
|
|
507
|
+
} else {
|
|
508
|
+
let tableView = UITableView()
|
|
509
|
+
tableView.translatesAutoresizingMaskIntoConstraints = false
|
|
510
|
+
tableView.dataSource = self
|
|
511
|
+
tableView.delegate = self
|
|
512
|
+
tableView.register(DebugHistoryCell.self, forCellReuseIdentifier: "DebugHistoryCell")
|
|
513
|
+
tableView.backgroundColor = .clear
|
|
514
|
+
historyView.addSubview(tableView)
|
|
515
|
+
|
|
516
|
+
NSLayoutConstraint.activate([
|
|
517
|
+
tableView.topAnchor.constraint(equalTo: historyView.topAnchor),
|
|
518
|
+
tableView.leadingAnchor.constraint(equalTo: historyView.leadingAnchor),
|
|
519
|
+
tableView.trailingAnchor.constraint(equalTo: historyView.trailingAnchor),
|
|
520
|
+
tableView.bottomAnchor.constraint(equalTo: historyView.bottomAnchor),
|
|
521
|
+
])
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
@objc private func segmentChanged(_ sender: UISegmentedControl) {
|
|
526
|
+
if sender.selectedSegmentIndex == 0 {
|
|
527
|
+
currentEventView.isHidden = false
|
|
528
|
+
historyView.isHidden = true
|
|
529
|
+
} else {
|
|
530
|
+
currentEventView.isHidden = true
|
|
531
|
+
historyView.isHidden = false
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Handles the share button tap to export and share debug logs
|
|
537
|
+
*/
|
|
538
|
+
@objc private func shareButtonTapped() {
|
|
539
|
+
// Generate export content for the current history
|
|
540
|
+
let exportContent = generateExportContent()
|
|
541
|
+
|
|
542
|
+
// Create activity view controller for sharing
|
|
543
|
+
let activityVC = UIActivityViewController(activityItems: [exportContent], applicationActivities: nil)
|
|
544
|
+
|
|
545
|
+
// For iPad - prevent crash by setting popover presentation controller
|
|
546
|
+
if let popover = activityVC.popoverPresentationController {
|
|
547
|
+
// Find the share button view to anchor the popover
|
|
548
|
+
if let shareButton = view.subviews.first(where: { $0.accessibilityLabel == "shareButton" }) {
|
|
549
|
+
popover.sourceView = shareButton
|
|
550
|
+
popover.sourceRect = shareButton.bounds
|
|
551
|
+
} else {
|
|
552
|
+
popover.sourceView = view
|
|
553
|
+
popover.sourceRect = CGRect(x: view.bounds.midX, y: view.bounds.midY, width: 0, height: 0)
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
present(activityVC, animated: true)
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Generates formatted export content for sharing
|
|
562
|
+
* @return Formatted string containing all debug events
|
|
563
|
+
*/
|
|
564
|
+
private func generateExportContent() -> String {
|
|
565
|
+
if history.isEmpty {
|
|
566
|
+
return "No debug events recorded in this session."
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
let formatter = DateFormatter()
|
|
570
|
+
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
|
|
571
|
+
let exportDate = formatter.string(from: Date())
|
|
572
|
+
|
|
573
|
+
var exportContent = """
|
|
574
|
+
Attentive React Native SDK - Debug Session Export
|
|
575
|
+
Generated: \(exportDate)
|
|
576
|
+
Total Events: \(history.count)
|
|
577
|
+
|
|
578
|
+
\(String(repeating: "=", count: 60))
|
|
579
|
+
|
|
580
|
+
"""
|
|
581
|
+
|
|
582
|
+
// Add all events in chronological order (oldest first for better readability)
|
|
583
|
+
for (index, event) in history.enumerated() {
|
|
584
|
+
exportContent += "Event #\(index + 1)\n"
|
|
585
|
+
exportContent += event.formatForExport()
|
|
586
|
+
exportContent += "\n"
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
exportContent += """
|
|
590
|
+
\(String(repeating: "=", count: 60))
|
|
591
|
+
End of Debug Session Export
|
|
592
|
+
"""
|
|
593
|
+
|
|
594
|
+
return exportContent
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
@objc private func closeButtonTapped() {
|
|
598
|
+
dismiss(animated: true)
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// MARK: - TableView DataSource and Delegate
|
|
603
|
+
extension DebugOverlayViewController: UITableViewDataSource, UITableViewDelegate {
|
|
604
|
+
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
|
605
|
+
return history.count
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
|
609
|
+
let cell = tableView.dequeueReusableCell(withIdentifier: "DebugHistoryCell", for: indexPath) as! DebugHistoryCell
|
|
610
|
+
let event = history[history.count - 1 - indexPath.row] // Show newest first
|
|
611
|
+
cell.configure(with: event)
|
|
612
|
+
return cell
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
|
|
616
|
+
return UITableView.automaticDimension
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
|
620
|
+
tableView.deselectRow(at: indexPath, animated: true)
|
|
621
|
+
let event = history[history.count - 1 - indexPath.row]
|
|
622
|
+
|
|
623
|
+
// Show detailed view of selected event
|
|
624
|
+
let detailVC = EventDetailViewController(event: event)
|
|
625
|
+
detailVC.modalPresentationStyle = .overFullScreen
|
|
626
|
+
present(detailVC, animated: true)
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// MARK: - Debug History Cell
|
|
631
|
+
class DebugHistoryCell: UITableViewCell {
|
|
632
|
+
private let timeLabel = UILabel()
|
|
633
|
+
private let eventLabel = UILabel()
|
|
634
|
+
private let summaryLabel = UILabel()
|
|
635
|
+
|
|
636
|
+
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
|
637
|
+
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
|
638
|
+
setupUI()
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
required init?(coder: NSCoder) {
|
|
642
|
+
fatalError("init(coder:) has not been implemented")
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
private func setupUI() {
|
|
646
|
+
backgroundColor = .clear
|
|
647
|
+
|
|
648
|
+
timeLabel.font = UIFont.monospacedSystemFont(ofSize: 11, weight: .regular)
|
|
649
|
+
timeLabel.textColor = .secondaryLabel
|
|
650
|
+
timeLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
651
|
+
contentView.addSubview(timeLabel)
|
|
652
|
+
|
|
653
|
+
eventLabel.font = UIFont.systemFont(ofSize: 14, weight: .medium)
|
|
654
|
+
eventLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
655
|
+
contentView.addSubview(eventLabel)
|
|
656
|
+
|
|
657
|
+
summaryLabel.font = UIFont.systemFont(ofSize: 12)
|
|
658
|
+
summaryLabel.textColor = .secondaryLabel
|
|
659
|
+
summaryLabel.numberOfLines = 2
|
|
660
|
+
summaryLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
661
|
+
contentView.addSubview(summaryLabel)
|
|
662
|
+
|
|
663
|
+
NSLayoutConstraint.activate([
|
|
664
|
+
timeLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8),
|
|
665
|
+
timeLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
|
|
666
|
+
|
|
667
|
+
eventLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8),
|
|
668
|
+
eventLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
|
|
669
|
+
eventLabel.trailingAnchor.constraint(equalTo: timeLabel.leadingAnchor, constant: -8),
|
|
670
|
+
|
|
671
|
+
summaryLabel.topAnchor.constraint(equalTo: eventLabel.bottomAnchor, constant: 4),
|
|
672
|
+
summaryLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
|
|
673
|
+
summaryLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
|
|
674
|
+
summaryLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8),
|
|
675
|
+
])
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
func configure(with event: DebugEvent) {
|
|
679
|
+
let formatter = DateFormatter()
|
|
680
|
+
formatter.dateFormat = "HH:mm:ss"
|
|
681
|
+
timeLabel.text = formatter.string(from: event.timestamp)
|
|
682
|
+
|
|
683
|
+
eventLabel.text = event.eventType
|
|
684
|
+
|
|
685
|
+
// Create summary from data - always show payload info
|
|
686
|
+
var summaryParts: [String] = []
|
|
687
|
+
|
|
688
|
+
// Add key-value summary
|
|
689
|
+
if let itemsCount = event.data["items_count"] as? String {
|
|
690
|
+
summaryParts.append("Items: \(itemsCount)")
|
|
691
|
+
}
|
|
692
|
+
if let orderId = event.data["order_id"] as? String {
|
|
693
|
+
summaryParts.append("Order: \(orderId)")
|
|
694
|
+
}
|
|
695
|
+
if let creativeId = event.data["creativeId"] as? String {
|
|
696
|
+
summaryParts.append("Creative: \(creativeId)")
|
|
697
|
+
}
|
|
698
|
+
if let eventType = event.data["event_type"] as? String {
|
|
699
|
+
summaryParts.append("Type: \(eventType)")
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// Always show payload size info
|
|
703
|
+
let payloadInfo = "Payload: \(event.data.count) fields"
|
|
704
|
+
summaryParts.append(payloadInfo)
|
|
705
|
+
|
|
706
|
+
summaryLabel.text = summaryParts.joined(separator: " • ") + " (Tap for details)"
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// MARK: - Event Detail View Controller
|
|
711
|
+
class EventDetailViewController: UIViewController {
|
|
712
|
+
private let event: DebugEvent
|
|
713
|
+
|
|
714
|
+
init(event: DebugEvent) {
|
|
715
|
+
self.event = event
|
|
716
|
+
super.init(nibName: nil, bundle: nil)
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
required init?(coder: NSCoder) {
|
|
720
|
+
fatalError("init(coder:) has not been implemented")
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
override func viewDidLoad() {
|
|
724
|
+
super.viewDidLoad()
|
|
725
|
+
setupUI()
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
private func setupUI() {
|
|
729
|
+
view.backgroundColor = UIColor.black.withAlphaComponent(0.8)
|
|
730
|
+
|
|
731
|
+
let containerView = UIView()
|
|
732
|
+
containerView.backgroundColor = UIColor.systemBackground
|
|
733
|
+
containerView.layer.cornerRadius = 12
|
|
734
|
+
containerView.translatesAutoresizingMaskIntoConstraints = false
|
|
735
|
+
view.addSubview(containerView)
|
|
736
|
+
|
|
737
|
+
let titleLabel = UILabel()
|
|
738
|
+
titleLabel.text = event.eventType
|
|
739
|
+
titleLabel.font = UIFont.boldSystemFont(ofSize: 18)
|
|
740
|
+
titleLabel.textAlignment = .center
|
|
741
|
+
titleLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
742
|
+
containerView.addSubview(titleLabel)
|
|
743
|
+
|
|
744
|
+
let timeLabel = UILabel()
|
|
745
|
+
let formatter = DateFormatter()
|
|
746
|
+
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
|
|
747
|
+
timeLabel.text = "Timestamp: \(formatter.string(from: event.timestamp))"
|
|
748
|
+
timeLabel.font = UIFont.systemFont(ofSize: 14)
|
|
749
|
+
timeLabel.textColor = .secondaryLabel
|
|
750
|
+
timeLabel.textAlignment = .center
|
|
751
|
+
timeLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
752
|
+
containerView.addSubview(timeLabel)
|
|
753
|
+
|
|
754
|
+
let scrollView = UIScrollView()
|
|
755
|
+
scrollView.translatesAutoresizingMaskIntoConstraints = false
|
|
756
|
+
containerView.addSubview(scrollView)
|
|
757
|
+
|
|
758
|
+
let dataLabel = UILabel()
|
|
759
|
+
dataLabel.text = formatData(event.data)
|
|
760
|
+
dataLabel.font = UIFont.monospacedSystemFont(ofSize: 12, weight: .regular)
|
|
761
|
+
dataLabel.numberOfLines = 0
|
|
762
|
+
dataLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
763
|
+
scrollView.addSubview(dataLabel)
|
|
764
|
+
|
|
765
|
+
// Share button for single event
|
|
766
|
+
let shareButton = UIButton(type: .system)
|
|
767
|
+
shareButton.setTitle("↗", for: .normal) // iOS-style share symbol
|
|
768
|
+
shareButton.titleLabel?.font = UIFont.systemFont(ofSize: 18, weight: .medium)
|
|
769
|
+
shareButton.setTitleColor(.systemBlue, for: .normal)
|
|
770
|
+
shareButton.backgroundColor = UIColor.systemGray5
|
|
771
|
+
shareButton.layer.cornerRadius = 15
|
|
772
|
+
shareButton.addTarget(self, action: #selector(shareEventButtonTapped), for: .touchUpInside)
|
|
773
|
+
shareButton.translatesAutoresizingMaskIntoConstraints = false
|
|
774
|
+
containerView.addSubview(shareButton)
|
|
775
|
+
|
|
776
|
+
let closeButton = UIButton(type: .system)
|
|
777
|
+
closeButton.setTitle("✕", for: .normal)
|
|
778
|
+
closeButton.titleLabel?.font = UIFont.systemFont(ofSize: 18, weight: .medium)
|
|
779
|
+
closeButton.setTitleColor(.secondaryLabel, for: .normal)
|
|
780
|
+
closeButton.backgroundColor = UIColor.systemGray5
|
|
781
|
+
closeButton.layer.cornerRadius = 15
|
|
782
|
+
closeButton.addTarget(self, action: #selector(closeButtonTapped), for: .touchUpInside)
|
|
783
|
+
closeButton.translatesAutoresizingMaskIntoConstraints = false
|
|
784
|
+
containerView.addSubview(closeButton)
|
|
785
|
+
|
|
786
|
+
NSLayoutConstraint.activate([
|
|
787
|
+
containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
|
|
788
|
+
containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
|
|
789
|
+
containerView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -16),
|
|
790
|
+
containerView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.65),
|
|
791
|
+
|
|
792
|
+
closeButton.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 12),
|
|
793
|
+
closeButton.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -12),
|
|
794
|
+
closeButton.widthAnchor.constraint(equalToConstant: 30),
|
|
795
|
+
closeButton.heightAnchor.constraint(equalToConstant: 30),
|
|
796
|
+
|
|
797
|
+
shareButton.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 12),
|
|
798
|
+
shareButton.trailingAnchor.constraint(equalTo: closeButton.leadingAnchor, constant: -8),
|
|
799
|
+
shareButton.widthAnchor.constraint(equalToConstant: 30),
|
|
800
|
+
shareButton.heightAnchor.constraint(equalToConstant: 30),
|
|
801
|
+
|
|
802
|
+
titleLabel.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 16),
|
|
803
|
+
titleLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16),
|
|
804
|
+
titleLabel.trailingAnchor.constraint(equalTo: shareButton.leadingAnchor, constant: -8),
|
|
805
|
+
|
|
806
|
+
timeLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 8),
|
|
807
|
+
timeLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16),
|
|
808
|
+
timeLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -16),
|
|
809
|
+
|
|
810
|
+
scrollView.topAnchor.constraint(equalTo: timeLabel.bottomAnchor, constant: 16),
|
|
811
|
+
scrollView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16),
|
|
812
|
+
scrollView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -16),
|
|
813
|
+
scrollView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -16),
|
|
814
|
+
|
|
815
|
+
dataLabel.topAnchor.constraint(equalTo: scrollView.topAnchor),
|
|
816
|
+
dataLabel.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
|
|
817
|
+
dataLabel.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
|
|
818
|
+
dataLabel.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
|
|
819
|
+
dataLabel.widthAnchor.constraint(equalTo: scrollView.widthAnchor)
|
|
820
|
+
])
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
private func formatData(_ data: [String: Any]) -> String {
|
|
824
|
+
do {
|
|
825
|
+
let jsonData = try JSONSerialization.data(withJSONObject: data, options: .prettyPrinted)
|
|
826
|
+
return String(data: jsonData, encoding: .utf8) ?? "Unable to format data"
|
|
827
|
+
} catch {
|
|
828
|
+
return "Error formatting data: \(error.localizedDescription)"
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
/**
|
|
833
|
+
* Handles the share button tap to export and share a single debug event
|
|
834
|
+
*/
|
|
835
|
+
@objc private func shareEventButtonTapped() {
|
|
836
|
+
let exportContent = generateSingleEventExport()
|
|
837
|
+
|
|
838
|
+
// Create activity view controller for sharing
|
|
839
|
+
let activityVC = UIActivityViewController(activityItems: [exportContent], applicationActivities: nil)
|
|
840
|
+
|
|
841
|
+
// For iPad - prevent crash by setting popover presentation controller
|
|
842
|
+
if let popover = activityVC.popoverPresentationController {
|
|
843
|
+
popover.sourceView = view
|
|
844
|
+
popover.sourceRect = CGRect(x: view.bounds.midX, y: view.bounds.midY, width: 0, height: 0)
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
present(activityVC, animated: true)
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
/**
|
|
851
|
+
* Generates formatted export content for a single event
|
|
852
|
+
* @return Formatted string containing the single debug event
|
|
853
|
+
*/
|
|
854
|
+
private func generateSingleEventExport() -> String {
|
|
855
|
+
let formatter = DateFormatter()
|
|
856
|
+
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
|
|
857
|
+
let exportDate = formatter.string(from: Date())
|
|
858
|
+
|
|
859
|
+
let eventContent = """
|
|
860
|
+
Attentive React Native SDK - Single Event Export
|
|
861
|
+
Generated: \(exportDate)
|
|
862
|
+
|
|
863
|
+
\(String(repeating: "=", count: 60))
|
|
864
|
+
|
|
865
|
+
\(event.formatForExport())
|
|
866
|
+
\(String(repeating: "=", count: 60))
|
|
867
|
+
End of Single Event Export
|
|
868
|
+
"""
|
|
869
|
+
|
|
870
|
+
return eventContent
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
@objc private func closeButtonTapped() {
|
|
874
|
+
dismiss(animated: true)
|
|
875
|
+
}
|
|
108
876
|
}
|