From 55ca68877b2c9bc6ac5d93d4e38b34838c0ecf1b Mon Sep 17 00:00:00 2001 From: Vladimir Espinola Lezcano Date: Thu, 27 Jun 2024 16:00:01 -0400 Subject: [PATCH 01/25] Revert "Revert "Release 1.0.0 Beta 1 (#102)" (#104)" (#105) This reverts commit 43230768fc66c2e5179c5cf702c04bafaf5f5341. --- ATTNSDKFramework.podspec | 2 +- CHANGELOG.md | 11 +++++++++++ README.md | 6 +++--- Sources/ATTNConstants.swift | 2 +- attentive-ios-sdk.podspec | 2 +- 5 files changed, 17 insertions(+), 6 deletions(-) diff --git a/ATTNSDKFramework.podspec b/ATTNSDKFramework.podspec index c02b74d..fb0309c 100644 --- a/ATTNSDKFramework.podspec +++ b/ATTNSDKFramework.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'ATTNSDKFramework' - s.version = '0.6.0' + s.version = '1.0.0-beta.1' s.summary = 'Attentive IOS SDK' # This description is used to generate tags and improve search results. diff --git a/CHANGELOG.md b/CHANGELOG.md index 487ef52..849fea7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +## [1.0.0-beta.1](https://github.com/attentive-mobile/attentive-ios-sdk/compare/0.6.0...1.0.0-beta.1) (2024-06-27) + +### Breaking Changes +* Upgraded SDK minimum deployment target from iOS 11 to iOS 14 to support new features from iOS Frameworks. + +### Feature +* Add a log level of verbose/standard/light to the SDK Config ([101](https://github.com/attentive-mobile/attentive-ios-sdk/pull/101)) ([0a40997](https://github.com/attentive-mobile/attentive-ios-sdk/commit/0a40997e62803d83731cc35d3b9c95aa5996893f)) +* Create (or update) test suite ([100](https://github.com/attentive-mobile/attentive-ios-sdk/pull/100)) ([4e562cd](https://github.com/attentive-mobile/attentive-ios-sdk/commit/4e562cd93f4e17f6f7fd3498076895ec5e8e6fa8)) +* Force show creatives on client apps for testing ([97](https://github.com/attentive-mobile/attentive-ios-sdk/pull/97)) ([1a2d81c](https://github.com/attentive-mobile/attentive-ios-sdk/commit/1a2d81cbfbb440638f5c5342225aeb58f11a236e)) +* Mobile App Domain Switching ([95](https://github.com/attentive-mobile/attentive-ios-sdk/pull/95)) ([e12028d](e12028d7892ca5fbd785e26b05dfeda4ab187024)) + ## [0.6.0](https://github.com/attentive-mobile/attentive-ios-sdk/compare/0.5.1...0.6.0) (2024-06-13) ### Breaking Changes diff --git a/README.md b/README.md index e51d761..4164f6b 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ The attentive-ios-sdk is available through [CocoaPods](https://cocoapods.org). T ```ruby target 'MyApp' do - pod 'ATTNSDKFramework', '~> 0.6' + pod 'ATTNSDKFramework', '1.0.0-beta.1' end ``` @@ -40,7 +40,7 @@ In your applications `Package.swift` file, add the attentive-ios-sdk as a depend ```swift dependencies: [ // your other app dependencies - .package(url: "https://github.com/attentive-mobile/attentive-ios-sdk", from: "0.6.0"), + .package(url: "https://github.com/attentive-mobile/attentive-ios-sdk", from: "1.0.0-beta.1"), ], ``` @@ -284,4 +284,4 @@ When/if the user logs back in, `identify` should be called again with the user's ## Changelog -Click [here](https://github.com/attentive-mobile/attentive-ios-sdk/blob/main/CHANGELOG.md) for a complete change log of every released version \ No newline at end of file +Click [here](https://github.com/attentive-mobile/attentive-ios-sdk/blob/main/CHANGELOG.md) for a complete change log of every released version diff --git a/Sources/ATTNConstants.swift b/Sources/ATTNConstants.swift index 3ad804d..6053df4 100644 --- a/Sources/ATTNConstants.swift +++ b/Sources/ATTNConstants.swift @@ -10,6 +10,6 @@ import Foundation struct ATTNConstants { private init() { } - static let sdkVersion = "0.6.0" + static let sdkVersion = "1.0.0-beta.1" static let skipFatigueEnvKey = "SKIP_FATIGUE_ON_CREATIVE" } diff --git a/attentive-ios-sdk.podspec b/attentive-ios-sdk.podspec index 0551468..b7e8e23 100644 --- a/attentive-ios-sdk.podspec +++ b/attentive-ios-sdk.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'attentive-ios-sdk' - s.version = '0.6.0' + s.version = '1.0.0-beta.1' s.summary = 'Attentive IOS SDK' # This description is used to generate tags and improve search results. From cc2bb3ee50f552f45fcb451eabd5fa908234518a Mon Sep 17 00:00:00 2001 From: Vladimir Espinola Date: Thu, 27 Jun 2024 15:13:01 -0400 Subject: [PATCH 02/25] fixed deployment target for ATTNSDKFramework --- ATTNSDKFramework.podspec | 3 ++- attentive-ios-sdk.podspec | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ATTNSDKFramework.podspec b/ATTNSDKFramework.podspec index fb0309c..3a32a8b 100644 --- a/ATTNSDKFramework.podspec +++ b/ATTNSDKFramework.podspec @@ -28,7 +28,8 @@ The Attentive IOS SDK provides the functionality to render Attentive signup unit s.source = { :git => 'https://github.com/attentive-mobile/attentive-ios-sdk.git', :tag => s.version.to_s } # s.social_media_url = 'https://twitter.com/' - s.ios.deployment_target = '10.0' + s.ios.deployment_target = '14.0' + s.swift_versions = ['5'] s.source_files = 'Sources/**/*.swift', 'Objc/**/*' diff --git a/attentive-ios-sdk.podspec b/attentive-ios-sdk.podspec index b7e8e23..2a6bc54 100644 --- a/attentive-ios-sdk.podspec +++ b/attentive-ios-sdk.podspec @@ -29,6 +29,7 @@ The Attentive IOS SDK provides the functionality to render Attentive signup unit # s.social_media_url = 'https://twitter.com/' s.ios.deployment_target = '14.0' + s.swift_versions = ['5'] s.source_files = 'Sources/**/*.swift', 'Objc/**/*' From 0855820c1def3b2de48a2599f484e61bc5a103c9 Mon Sep 17 00:00:00 2001 From: Vladimir Espinola Date: Thu, 27 Jun 2024 16:13:08 -0400 Subject: [PATCH 03/25] bumped version --- ATTNSDKFramework.podspec | 2 +- README.md | 4 ++-- Sources/ATTNConstants.swift | 2 +- attentive-ios-sdk.podspec | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ATTNSDKFramework.podspec b/ATTNSDKFramework.podspec index 3a32a8b..2b47b95 100644 --- a/ATTNSDKFramework.podspec +++ b/ATTNSDKFramework.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'ATTNSDKFramework' - s.version = '1.0.0-beta.1' + s.version = '1.0.0-beta.2' s.summary = 'Attentive IOS SDK' # This description is used to generate tags and improve search results. diff --git a/README.md b/README.md index 4164f6b..e2ebe6f 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ The attentive-ios-sdk is available through [CocoaPods](https://cocoapods.org). T ```ruby target 'MyApp' do - pod 'ATTNSDKFramework', '1.0.0-beta.1' + pod 'ATTNSDKFramework', '1.0.0-beta.2' end ``` @@ -40,7 +40,7 @@ In your applications `Package.swift` file, add the attentive-ios-sdk as a depend ```swift dependencies: [ // your other app dependencies - .package(url: "https://github.com/attentive-mobile/attentive-ios-sdk", from: "1.0.0-beta.1"), + .package(url: "https://github.com/attentive-mobile/attentive-ios-sdk", from: "1.0.0-beta.2"), ], ``` diff --git a/Sources/ATTNConstants.swift b/Sources/ATTNConstants.swift index 6053df4..375e443 100644 --- a/Sources/ATTNConstants.swift +++ b/Sources/ATTNConstants.swift @@ -10,6 +10,6 @@ import Foundation struct ATTNConstants { private init() { } - static let sdkVersion = "1.0.0-beta.1" + static let sdkVersion = "1.0.0-beta.2" static let skipFatigueEnvKey = "SKIP_FATIGUE_ON_CREATIVE" } diff --git a/attentive-ios-sdk.podspec b/attentive-ios-sdk.podspec index 2a6bc54..e803016 100644 --- a/attentive-ios-sdk.podspec +++ b/attentive-ios-sdk.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'attentive-ios-sdk' - s.version = '1.0.0-beta.1' + s.version = '1.0.0-beta.2' s.summary = 'Attentive IOS SDK' # This description is used to generate tags and improve search results. From 995e5fc983b3c53776894bf7ed1d2fe6eca1d911 Mon Sep 17 00:00:00 2001 From: Vladimir Espinola Date: Thu, 27 Jun 2024 16:21:33 -0400 Subject: [PATCH 04/25] updated changelog with 1.0.0-beta.2 details --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 849fea7..e47edb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## [1.0.0-beta.2](https://github.com/attentive-mobile/attentive-ios-sdk/compare/1.0.0-beta.1...1.0.0-beta.2) (2024-06-27) + +### Bug Fixes +* Release 1.0.0 Beta 1 - Fix ([103](https://github.com/attentive-mobile/attentive-ios-sdk/pull/103)) ([c49d8cc](https://github.com/attentive-mobile/attentive-ios-sdk/commit/c49d8cca52d77a1fc2441ccc0896f97c67ae08dd)) + ## [1.0.0-beta.1](https://github.com/attentive-mobile/attentive-ios-sdk/compare/0.6.0...1.0.0-beta.1) (2024-06-27) ### Breaking Changes From a243bffe3997ff12177823131d721da61c0dc6ad Mon Sep 17 00:00:00 2001 From: Vladimir Espinola Date: Mon, 1 Jul 2024 11:28:06 -0400 Subject: [PATCH 05/25] updated init log to expose setup details --- Sources/Public/SDK/ATTNSDK.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Public/SDK/ATTNSDK.swift b/Sources/Public/SDK/ATTNSDK.swift index afea2ae..03192b3 100644 --- a/Sources/Public/SDK/ATTNSDK.swift +++ b/Sources/Public/SDK/ATTNSDK.swift @@ -55,7 +55,7 @@ public final class ATTNSDK: NSObject { @objc public var skipFatigueOnCreative: Bool = false public init(domain: String, mode: ATTNSDKMode) { - Loggers.creative.debug("Init ATTNSDKFramework v\(ATTNConstants.sdkVersion), Mode: \(mode.rawValue), Domain: \(domain)") + Loggers.creative.debug("Init ATTNSDKFramework v\(ATTNConstants.sdkVersion, privacy: .public), Mode: \(mode.rawValue, privacy: .public), Domain: \(domain, privacy: .public)") self.domain = domain self.mode = mode From a55173a7349dcac2079a78685f4650aa384b3ca8 Mon Sep 17 00:00:00 2001 From: Vladimir Espinola Date: Wed, 3 Jul 2024 11:47:14 -0400 Subject: [PATCH 06/25] clean up ci files --- .github/workflows/ci.yml | 1 - .github/workflows/lint.yml | 1 - .github/workflows/ui.yml | 1 - 3 files changed, 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index adcec3d..a42bc61 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,6 @@ on: branches: - main - "feature/*" - - swift-migration workflow_dispatch: concurrency: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 15ba3ed..a780aa6 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -7,7 +7,6 @@ on: - "feature/*" pull_request: branches: - - swift-migration - main - "feature/*" diff --git a/.github/workflows/ui.yml b/.github/workflows/ui.yml index 24ad8d1..2fe40e8 100644 --- a/.github/workflows/ui.yml +++ b/.github/workflows/ui.yml @@ -3,7 +3,6 @@ name: Run UI Test Cases on: pull_request: branches: - - swift-migration - main - "feature/*" From 72424c277c609369e727ae4859394ff757e3f776 Mon Sep 17 00:00:00 2001 From: Vladimir Espinola Date: Wed, 3 Jul 2024 11:54:01 -0400 Subject: [PATCH 07/25] added condition to verify release branches --- .github/workflows/ci.yml | 2 ++ .github/workflows/lint.yml | 2 ++ .github/workflows/ui.yml | 1 + 3 files changed, 5 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a42bc61..cfc8933 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,10 +5,12 @@ on: branches: - main - "feature/*" + - "release/*" pull_request: branches: - main - "feature/*" + - "release/*" workflow_dispatch: concurrency: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index a780aa6..64464b7 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -5,10 +5,12 @@ on: branches: - main - "feature/*" + - "release/*" pull_request: branches: - main - "feature/*" + - "release/*" concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/.github/workflows/ui.yml b/.github/workflows/ui.yml index 2fe40e8..f443de0 100644 --- a/.github/workflows/ui.yml +++ b/.github/workflows/ui.yml @@ -5,6 +5,7 @@ on: branches: - main - "feature/*" + - "release/*" concurrency: group: ${{ github.workflow }}-${{ github.ref }} From 7f4c6955a6e2968e8a6b6f734457d5c19f14f3b5 Mon Sep 17 00:00:00 2001 From: Vladimir Espinola Date: Thu, 4 Jul 2024 12:15:23 -0400 Subject: [PATCH 08/25] created class to hide creative management complexity --- Sources/ATTNInfoEvent.swift | 4 +- Sources/ATTNWebViewHandling.swift | 216 ++++++++++++++++++++ Sources/Public/SDK/ATTNSDK.swift | 19 +- attentive-ios-sdk.xcodeproj/project.pbxproj | 4 + 4 files changed, 236 insertions(+), 7 deletions(-) create mode 100644 Sources/ATTNWebViewHandling.swift diff --git a/Sources/ATTNInfoEvent.swift b/Sources/ATTNInfoEvent.swift index 45aca0e..0ad9683 100644 --- a/Sources/ATTNInfoEvent.swift +++ b/Sources/ATTNInfoEvent.swift @@ -7,6 +7,6 @@ import Foundation -public final class ATTNInfoEvent: NSObject, ATTNEvent { - public override init() {} +final class ATTNInfoEvent: NSObject, ATTNEvent { + override init() {} } diff --git a/Sources/ATTNWebViewHandling.swift b/Sources/ATTNWebViewHandling.swift new file mode 100644 index 0000000..8381763 --- /dev/null +++ b/Sources/ATTNWebViewHandling.swift @@ -0,0 +1,216 @@ +// +// ATTNWebViewHandling.swift +// attentive-ios-sdk-framework +// +// Created by Vladimir - Work on 2024-07-04. +// + +import Foundation +import WebKit + +protocol ATTNWebViewHandling { + func launchCreative(parentView view: UIView, creativeId: String?, handler: ATTNCreativeTriggerCompletionHandler?) + func closeCreative() +} + +final class ATTNWebViewHandler: NSObject, ATTNWebViewHandling { + private enum Constants { + static var visibilityEvent: String { "document-visibility:" } + static var scriptMessageHandlerName: String { "log" } + } + + private enum ScriptStatus { + case success + case timeout + case unknown(String) + + static func getRawValue(from value: Any) -> ScriptStatus? { + guard let stringValue = value as? String else { return nil } + switch stringValue { + case "SUCCESS": + return .success + case "TIMED OUT": + return .timeout + default: + return .unknown(stringValue) + } + } + } + + private weak var sdk: ATTNSDK? + private var urlBuilder: ATTNCreativeUrlProviding + + init(sdk: ATTNSDK, creativeUrlBuilder: ATTNCreativeUrlProviding) { + self.sdk = sdk + self.urlBuilder = creativeUrlBuilder + } + + func launchCreative( + parentView view: UIView, + creativeId: String? = nil, + handler: ATTNCreativeTriggerCompletionHandler? = nil + ) { + guard let sdk = sdk else { + Loggers.creative.debug("Not showing the Attentive creative because the iOS version is too old.") + sdk?.triggerHandler?(ATTNCreativeTriggerStatus.notOpened) + return + } + + sdk.parentView = view + sdk.triggerHandler = handler + + let domain = sdk.getDomain() + let mode = sdk.getMode() + let userIdentity = sdk.userIdentity + + Loggers.creative.debug("Called showWebView in creativeSDK with domain: \(domain, privacy: .public)") + + guard !ATTNSDK.isCreativeOpen else { + Loggers.creative.debug("Attempted to trigger creative, but creative is currently open. Taking no action") + return + } + + Loggers.creative.debug("The iOS version is new enough, continuing to show the Attentive creative.") + + let creativePageUrl = urlBuilder.buildCompanyCreativeUrl( + configuration: ATTNCreativeUrlConfig( + domain: domain, + creativeId: creativeId, + skipFatigue: sdk.skipFatigueOnCreative, + mode: mode.rawValue, + userIdentity: userIdentity + ) + ) + + Loggers.creative.debug("Requesting creative page url: \(creativePageUrl)" ) + + guard let url = URL(string: creativePageUrl) else { + Loggers.creative.debug("URL could not be created.") + return + } + + let request = URLRequest(url: url) + + let configuration = WKWebViewConfiguration() + configuration.userContentController.add(self, name: Constants.scriptMessageHandlerName) + + let userScriptWithEventListener = String(format: "window.addEventListener('message', (event) => {if (event.data && event.data.__attentive) {window.webkit.messageHandlers.log.postMessage(event.data.__attentive.action);}}, false);window.addEventListener('visibilitychange', (event) => {window.webkit.messageHandlers.log.postMessage(`%@ ${document.hidden}`);}, false);", Constants.visibilityEvent) + let userScript = WKUserScript(source: userScriptWithEventListener, injectionTime: .atDocumentStart, forMainFrameOnly: false) + configuration.userContentController.addUserScript(userScript) + + sdk.webView = WKWebView(frame: view.frame, configuration: configuration) + + guard let webView = sdk.webView else { return } + + webView.navigationDelegate = self + webView.load(request) + + if mode == .debug { + sdk.parentView?.addSubview(webView) + } else { + webView.isOpaque = false + webView.backgroundColor = .clear + } + } + + func closeCreative() { + sdk?.removeWebView() + ATTNSDK.isCreativeOpen = false + sdk?.triggerHandler?(ATTNCreativeTriggerStatus.closed) + Loggers.creative.debug("Successfully closed creative") + } +} + +extension ATTNWebViewHandler: WKNavigationDelegate { + func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { + guard #available(iOS 14.0, *) else { return } + let asyncJs = + """ + var p = new Promise(resolve => { + var timeoutHandle = null; + const interval = setInterval(function() { + e = document.querySelector('iframe'); + if(e && e.id === 'attentive_creative') { + clearInterval(interval); + resolve('SUCCESS'); + if (timeoutHandle != null) { + clearTimeout(timeoutHandle); + } + } + }, 100); + timeoutHandle = setTimeout(function() { + clearInterval(interval); + resolve('TIMED OUT'); + }, 5000); + }); + var status = await p; + return status; + """ + webView.callAsyncJavaScript( + asyncJs, + in: nil, + in: .defaultClient + ) { [weak self] result in + guard let self = self, let sdk = self.sdk else { return } + guard case let .success(statusAny) = result else { + Loggers.creative.debug("No status returned from JS. Not showing WebView.") + self.sdk?.triggerHandler?(ATTNCreativeTriggerStatus.notOpened) + return + } + + switch ScriptStatus.getRawValue(from: statusAny) { + case .success: + Loggers.creative.debug("Found creative iframe, showing WebView.") + if sdk.getMode() == .production { + sdk.parentView?.addSubview(webView) + } + sdk.triggerHandler?(ATTNCreativeTriggerStatus.opened) + case .timeout: + Loggers.creative.error("Creative timed out. Not showing WebView.") + sdk.triggerHandler?(ATTNCreativeTriggerStatus.notOpened) + case .unknown(let statusString): + Loggers.creative.error("Received unknown status: \(statusString). Not showing WebView") + sdk.triggerHandler?(ATTNCreativeTriggerStatus.notOpened) + default: break + } + } + } + + func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { + guard let url = navigationAction.request.url else { + decisionHandler(.cancel) + return + } + + if url.scheme == "sms" { + UIApplication.shared.open(url) + decisionHandler(.cancel) + } else if let scheme = url.scheme?.lowercased(), scheme == "http" || scheme == "https" { + if navigationAction.targetFrame == nil { + UIApplication.shared.open(url) + decisionHandler(.cancel) + } else { + decisionHandler(.allow) + } + } else { + decisionHandler(.allow) + } + } +} + +extension ATTNWebViewHandler: WKScriptMessageHandler { + func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { + let messageBody = message.body as? String ?? "" + Loggers.creative.debug("Web event message: \(messageBody). isCreativeOpen: \(ATTNSDK.isCreativeOpen ? "YES" : "NO")") + + if messageBody == "CLOSE" { + closeCreative() + } else if messageBody == "IMPRESSION" { + Loggers.creative.debug("Creative opened and generated impression event") + ATTNSDK.isCreativeOpen = true + } else if messageBody == String(format: "%@ true", Constants.visibilityEvent), ATTNSDK.isCreativeOpen { + Loggers.creative.debug("Nav away from creative, closing") + closeCreative() + } + } +} diff --git a/Sources/Public/SDK/ATTNSDK.swift b/Sources/Public/SDK/ATTNSDK.swift index 03192b3..814ad85 100644 --- a/Sources/Public/SDK/ATTNSDK.swift +++ b/Sources/Public/SDK/ATTNSDK.swift @@ -37,13 +37,13 @@ public final class ATTNSDK: NSObject { } // MARK: Static Properties - private static var isCreativeOpen = false + static var isCreativeOpen = false // MARK: Instance Properties - private var parentView: UIView? - private var webView: WKWebView? - private var triggerHandler: ATTNCreativeTriggerCompletionHandler? - + var parentView: UIView? + var triggerHandler: ATTNCreativeTriggerCompletionHandler? + var webView: WKWebView? + private(set) var api: ATTNAPIProtocol private(set) var userIdentity: ATTNUserIdentity @@ -316,4 +316,13 @@ extension ATTNSDK { func getDomain() -> String { domain } + + func getMode() -> ATTNSDKMode { + mode + } + + func removeWebView() { + webView?.removeFromSuperview() + webView = nil + } } diff --git a/attentive-ios-sdk.xcodeproj/project.pbxproj b/attentive-ios-sdk.xcodeproj/project.pbxproj index c305064..68fbfc8 100644 --- a/attentive-ios-sdk.xcodeproj/project.pbxproj +++ b/attentive-ios-sdk.xcodeproj/project.pbxproj @@ -43,6 +43,7 @@ FB35C1972C0E52F3009FA048 /* ATTNProductViewEvent+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB35C1962C0E52F3009FA048 /* ATTNProductViewEvent+Extension.swift */; }; FB35C1992C0E5365009FA048 /* ATTNInfoEvent+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB35C1982C0E5365009FA048 /* ATTNInfoEvent+Extension.swift */; }; FB35C19B2C0E53F9009FA048 /* ATTNCustomEvent+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB35C19A2C0E53F9009FA048 /* ATTNCustomEvent+Extension.swift */; }; + FB4E3FE52C36F7BF004B8FF0 /* ATTNWebViewHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB4E3FE42C36F7BF004B8FF0 /* ATTNWebViewHandling.swift */; }; FB56D4DA2C208BAD00AF7530 /* ATTNSDK+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB56D4D92C208BAD00AF7530 /* ATTNSDK+Extension.swift */; }; FB56D4DC2C208D6100AF7530 /* Boolean+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB56D4DB2C208D6100AF7530 /* Boolean+Extension.swift */; }; FB56D4DE2C208DC100AF7530 /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB56D4DD2C208DC100AF7530 /* String+Extension.swift */; }; @@ -144,6 +145,7 @@ FB35C1962C0E52F3009FA048 /* ATTNProductViewEvent+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ATTNProductViewEvent+Extension.swift"; sourceTree = ""; }; FB35C1982C0E5365009FA048 /* ATTNInfoEvent+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ATTNInfoEvent+Extension.swift"; sourceTree = ""; }; FB35C19A2C0E53F9009FA048 /* ATTNCustomEvent+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ATTNCustomEvent+Extension.swift"; sourceTree = ""; }; + FB4E3FE42C36F7BF004B8FF0 /* ATTNWebViewHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ATTNWebViewHandling.swift; sourceTree = ""; }; FB56D4D92C208BAD00AF7530 /* ATTNSDK+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ATTNSDK+Extension.swift"; sourceTree = ""; }; FB56D4DB2C208D6100AF7530 /* Boolean+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Boolean+Extension.swift"; sourceTree = ""; }; FB56D4DD2C208DC100AF7530 /* String+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extension.swift"; sourceTree = ""; }; @@ -471,6 +473,7 @@ FBA9F9EE2C0A77AB00C65024 /* ATTNUserAgentBuilder.swift */, FBA9F9EF2C0A77AB00C65024 /* ATTNVisitorService.swift */, FB35C17C2C0E039E009FA048 /* ATTNConstants.swift */, + FB4E3FE42C36F7BF004B8FF0 /* ATTNWebViewHandling.swift */, FBA9F9FF2C0A77AB00C65024 /* Public */, FBA9FA022C0A77AB00C65024 /* Resources */, ); @@ -636,6 +639,7 @@ FBA9FA122C0A77AB00C65024 /* ATTNCustomEvent.swift in Sources */, FB35C1992C0E5365009FA048 /* ATTNInfoEvent+Extension.swift in Sources */, FB35C1892C0E3AF8009FA048 /* ATTNExternalVendorTypes.swift in Sources */, + FB4E3FE52C36F7BF004B8FF0 /* ATTNWebViewHandling.swift in Sources */, FB080C7F2C1CC9E500834BAA /* ATTNCreativeUrlConfig.swift in Sources */, FBA9FA092C0A77AB00C65024 /* ATTNAppInfo.swift in Sources */, FB35C1792C0E030E009FA048 /* ATTNCreativeTriggerStatus.swift in Sources */, From e905125fbfb9c8d7f9bf91fd72417d59d7810cae Mon Sep 17 00:00:00 2001 From: Vladimir Espinola Date: Thu, 4 Jul 2024 14:23:48 -0400 Subject: [PATCH 09/25] removed delegates from SDK --- Sources/ATTNWebViewHandling.swift | 2 +- Sources/Public/SDK/ATTNSDK.swift | 202 ++---------------------------- 2 files changed, 9 insertions(+), 195 deletions(-) diff --git a/Sources/ATTNWebViewHandling.swift b/Sources/ATTNWebViewHandling.swift index 8381763..bdf75af 100644 --- a/Sources/ATTNWebViewHandling.swift +++ b/Sources/ATTNWebViewHandling.swift @@ -40,7 +40,7 @@ final class ATTNWebViewHandler: NSObject, ATTNWebViewHandling { private weak var sdk: ATTNSDK? private var urlBuilder: ATTNCreativeUrlProviding - init(sdk: ATTNSDK, creativeUrlBuilder: ATTNCreativeUrlProviding) { + init(sdk: ATTNSDK, creativeUrlBuilder: ATTNCreativeUrlProviding = ATTNCreativeUrlProvider()) { self.sdk = sdk self.urlBuilder = creativeUrlBuilder } diff --git a/Sources/Public/SDK/ATTNSDK.swift b/Sources/Public/SDK/ATTNSDK.swift index 814ad85..d15ba85 100644 --- a/Sources/Public/SDK/ATTNSDK.swift +++ b/Sources/Public/SDK/ATTNSDK.swift @@ -12,30 +12,6 @@ public typealias ATTNCreativeTriggerCompletionHandler = (String) -> Void @objc(ATTNSDK) public final class ATTNSDK: NSObject { - // MARK: Constants - private enum Constants { - static var visibilityEvent: String { "document-visibility:" } - static var scriptMessageHandlerName: String { "log" } - } - - private enum ScriptStatus { - case success - case timeout - case unknown(String) - - static func getRawValue(from value: Any) -> ScriptStatus? { - guard let stringValue = value as? String else { return nil } - switch stringValue { - case "SUCCESS": - return .success - case "TIMED OUT": - return .timeout - default: - return .unknown(stringValue) - } - } - } - // MARK: Static Properties static var isCreativeOpen = false @@ -43,13 +19,13 @@ public final class ATTNSDK: NSObject { var parentView: UIView? var triggerHandler: ATTNCreativeTriggerCompletionHandler? var webView: WKWebView? - + private(set) var api: ATTNAPIProtocol private(set) var userIdentity: ATTNUserIdentity private var domain: String private var mode: ATTNSDKMode - private var urlBuilder: ATTNCreativeUrlProviding = ATTNCreativeUrlProvider() + private var webViewHandler: ATTNWebViewHandling? /// Determinates if fatigue rules evaluation will be skipped for Creative. Default value is false. @objc public var skipFatigueOnCreative: Bool = false @@ -65,6 +41,7 @@ public final class ATTNSDK: NSObject { super.init() + self.webViewHandler = ATTNWebViewHandler(sdk: self) self.sendInfoEvent() self.initializeSkipFatigueOnCreatives() } @@ -131,171 +108,12 @@ fileprivate extension ATTNSDK { api.send(event: ATTNInfoEvent(), userIdentity: userIdentity) } - func closeCreative() { - webView?.removeFromSuperview() - webView = nil - ATTNSDK.isCreativeOpen = false - triggerHandler?(ATTNCreativeTriggerStatus.closed) - Loggers.creative.debug("Successfully closed creative") - } - func launchCreative( parentView view: UIView, creativeId: String? = nil, handler: ATTNCreativeTriggerCompletionHandler? = nil ) { - parentView = view - triggerHandler = handler - - Loggers.creative.debug("Called showWebView in creativeSDK with domain: \(self.domain, privacy: .public)") - - guard !ATTNSDK.isCreativeOpen else { - Loggers.creative.debug("Attempted to trigger creative, but creative is currently open. Taking no action") - return - } - - guard #available(iOS 14, *) else { - Loggers.creative.debug("Not showing the Attentive creative because the iOS version is too old.") - triggerHandler?(ATTNCreativeTriggerStatus.notOpened) - return - } - Loggers.creative.debug("The iOS version is new enough, continuing to show the Attentive creative.") - - let creativePageUrl = urlBuilder.buildCompanyCreativeUrl( - configuration: ATTNCreativeUrlConfig( - domain: domain, - creativeId: creativeId, - skipFatigue: skipFatigueOnCreative, - mode: mode.rawValue, - userIdentity: userIdentity - ) - ) - - Loggers.creative.debug("Requesting creative page url: \(creativePageUrl)" ) - - guard let url = URL(string: creativePageUrl) else { - Loggers.creative.debug("URL could not be created.") - return - } - - let request = URLRequest(url: url) - - let configuration = WKWebViewConfiguration() - configuration.userContentController.add(self, name: Constants.scriptMessageHandlerName) - - let userScriptWithEventListener = String(format: "window.addEventListener('message', (event) => {if (event.data && event.data.__attentive) {window.webkit.messageHandlers.log.postMessage(event.data.__attentive.action);}}, false);window.addEventListener('visibilitychange', (event) => {window.webkit.messageHandlers.log.postMessage(`%@ ${document.hidden}`);}, false);", Constants.visibilityEvent) - let userScript = WKUserScript(source: userScriptWithEventListener, injectionTime: .atDocumentStart, forMainFrameOnly: false) - configuration.userContentController.addUserScript(userScript) - - webView = WKWebView(frame: view.frame, configuration: configuration) - - guard let webView = webView else { return } - - webView.navigationDelegate = self - webView.load(request) - - if mode == .debug { - parentView?.addSubview(webView) - } else { - webView.isOpaque = false - webView.backgroundColor = .clear - } - } -} - -// MARK: WKScriptMessageHandler -extension ATTNSDK: WKScriptMessageHandler { - public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { - let messageBody = message.body as? String ?? "" - Loggers.creative.debug("Web event message: \(messageBody). isCreativeOpen: \(ATTNSDK.isCreativeOpen ? "YES" : "NO")") - - if messageBody == "CLOSE" { - closeCreative() - } else if messageBody == "IMPRESSION" { - Loggers.creative.debug("Creative opened and generated impression event") - ATTNSDK.isCreativeOpen = true - } else if messageBody == String(format: "%@ true", Constants.visibilityEvent), ATTNSDK.isCreativeOpen { - Loggers.creative.debug("Nav away from creative, closing") - closeCreative() - } - } -} - -// MARK: WKNavigationDelegate -extension ATTNSDK: WKNavigationDelegate { - public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { - guard #available(iOS 14.0, *) else { return } - let asyncJs = - """ - var p = new Promise(resolve => { - var timeoutHandle = null; - const interval = setInterval(function() { - e = document.querySelector('iframe'); - if(e && e.id === 'attentive_creative') { - clearInterval(interval); - resolve('SUCCESS'); - if (timeoutHandle != null) { - clearTimeout(timeoutHandle); - } - } - }, 100); - timeoutHandle = setTimeout(function() { - clearInterval(interval); - resolve('TIMED OUT'); - }, 5000); - }); - var status = await p; - return status; - """ - webView.callAsyncJavaScript( - asyncJs, - in: nil, - in: .defaultClient - ) { [weak self] result in - guard let self = self else { return } - guard case let .success(statusAny) = result else { - Loggers.creative.debug("No status returned from JS. Not showing WebView.") - self.triggerHandler?(ATTNCreativeTriggerStatus.notOpened) - return - } - - switch ScriptStatus.getRawValue(from: statusAny) { - case .success: - Loggers.creative.debug("Found creative iframe, showing WebView.") - if self.mode == .production { - self.parentView?.addSubview(webView) - } - self.triggerHandler?(ATTNCreativeTriggerStatus.opened) - case .timeout: - Loggers.creative.error("Creative timed out. Not showing WebView.") - self.triggerHandler?(ATTNCreativeTriggerStatus.notOpened) - case .unknown(let statusString): - Loggers.creative.error("Received unknown status: \(statusString). Not showing WebView") - self.triggerHandler?(ATTNCreativeTriggerStatus.notOpened) - default: break - } - } - } - - public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { - guard let url = navigationAction.request.url else { - decisionHandler(.cancel) - return - } - - if url.scheme == "sms" { - UIApplication.shared.open(url) - decisionHandler(.cancel) - } else if let scheme = url.scheme?.lowercased(), scheme == "http" || scheme == "https" { - if navigationAction.targetFrame == nil { - UIApplication.shared.open(url) - decisionHandler(.cancel) - } else { - decisionHandler(.allow) - } - } else { - decisionHandler(.allow) - } + webViewHandler?.launchCreative(parentView: view, creativeId: creativeId, handler: handler) } } @@ -303,23 +121,19 @@ extension ATTNSDK: WKNavigationDelegate { extension ATTNSDK { convenience init(domain: String, mode: ATTNSDKMode, urlBuilder: ATTNCreativeUrlProviding) { self.init(domain: domain, mode: mode) - self.urlBuilder = urlBuilder + self.webViewHandler = ATTNWebViewHandler(sdk: self, creativeUrlBuilder: urlBuilder) } convenience init(api: ATTNAPIProtocol, urlBuilder: ATTNCreativeUrlProviding? = nil) { self.init(domain: api.domain) self.api = api guard let urlBuilder = urlBuilder else { return } - self.urlBuilder = urlBuilder + self.webViewHandler = ATTNWebViewHandler(sdk: self, creativeUrlBuilder: urlBuilder) } - func getDomain() -> String { - domain - } + func getDomain() -> String { domain } - func getMode() -> ATTNSDKMode { - mode - } + func getMode() -> ATTNSDKMode { mode } func removeWebView() { webView?.removeFromSuperview() From 5546913243be56bd4a38cc8037fba9e9c63fa66f Mon Sep 17 00:00:00 2001 From: Vladimir Espinola Date: Thu, 4 Jul 2024 15:49:30 -0400 Subject: [PATCH 10/25] removed dependency with sdk --- Sources/ATTNWebViewHandling.swift | 83 +++++++++++++-------- Sources/ATTNWebViewProviding.swift | 20 +++++ Sources/Public/SDK/ATTNSDK.swift | 27 ++++--- attentive-ios-sdk.xcodeproj/project.pbxproj | 4 + 4 files changed, 89 insertions(+), 45 deletions(-) create mode 100644 Sources/ATTNWebViewProviding.swift diff --git a/Sources/ATTNWebViewHandling.swift b/Sources/ATTNWebViewHandling.swift index bdf75af..fe8b941 100644 --- a/Sources/ATTNWebViewHandling.swift +++ b/Sources/ATTNWebViewHandling.swift @@ -37,11 +37,11 @@ final class ATTNWebViewHandler: NSObject, ATTNWebViewHandling { } } - private weak var sdk: ATTNSDK? + private weak var webViewProvider: ATTNWebViewProviding? private var urlBuilder: ATTNCreativeUrlProviding - init(sdk: ATTNSDK, creativeUrlBuilder: ATTNCreativeUrlProviding = ATTNCreativeUrlProvider()) { - self.sdk = sdk + init(webViewProvider: ATTNWebViewProviding, creativeUrlBuilder: ATTNCreativeUrlProviding = ATTNCreativeUrlProvider()) { + self.webViewProvider = webViewProvider self.urlBuilder = creativeUrlBuilder } @@ -50,22 +50,18 @@ final class ATTNWebViewHandler: NSObject, ATTNWebViewHandling { creativeId: String? = nil, handler: ATTNCreativeTriggerCompletionHandler? = nil ) { - guard let sdk = sdk else { + guard let webViewProvider = webViewProvider else { Loggers.creative.debug("Not showing the Attentive creative because the iOS version is too old.") - sdk?.triggerHandler?(ATTNCreativeTriggerStatus.notOpened) + webViewProvider?.triggerHandler?(ATTNCreativeTriggerStatus.notOpened) return } - sdk.parentView = view - sdk.triggerHandler = handler + webViewProvider.parentView = view + webViewProvider.triggerHandler = handler - let domain = sdk.getDomain() - let mode = sdk.getMode() - let userIdentity = sdk.userIdentity + Loggers.creative.debug("Called showWebView in creativeSDK with domain: \(self.domain, privacy: .public)") - Loggers.creative.debug("Called showWebView in creativeSDK with domain: \(domain, privacy: .public)") - - guard !ATTNSDK.isCreativeOpen else { + guard !isCreativeOpen else { Loggers.creative.debug("Attempted to trigger creative, but creative is currently open. Taking no action") return } @@ -76,7 +72,7 @@ final class ATTNWebViewHandler: NSObject, ATTNWebViewHandling { configuration: ATTNCreativeUrlConfig( domain: domain, creativeId: creativeId, - skipFatigue: sdk.skipFatigueOnCreative, + skipFatigue: webViewProvider.skipFatigueOnCreative, mode: mode.rawValue, userIdentity: userIdentity ) @@ -98,15 +94,15 @@ final class ATTNWebViewHandler: NSObject, ATTNWebViewHandling { let userScript = WKUserScript(source: userScriptWithEventListener, injectionTime: .atDocumentStart, forMainFrameOnly: false) configuration.userContentController.addUserScript(userScript) - sdk.webView = WKWebView(frame: view.frame, configuration: configuration) + webViewProvider.webView = WKWebView(frame: view.frame, configuration: configuration) - guard let webView = sdk.webView else { return } + guard let webView = webViewProvider.webView else { return } webView.navigationDelegate = self webView.load(request) if mode == .debug { - sdk.parentView?.addSubview(webView) + webViewProvider.parentView?.addSubview(webView) } else { webView.isOpaque = false webView.backgroundColor = .clear @@ -114,9 +110,11 @@ final class ATTNWebViewHandler: NSObject, ATTNWebViewHandling { } func closeCreative() { - sdk?.removeWebView() - ATTNSDK.isCreativeOpen = false - sdk?.triggerHandler?(ATTNCreativeTriggerStatus.closed) + webViewProvider?.webView?.removeFromSuperview() + webViewProvider?.webView = nil + + isCreativeOpen = false + webViewProvider?.triggerHandler?(ATTNCreativeTriggerStatus.closed) Loggers.creative.debug("Successfully closed creative") } } @@ -151,26 +149,26 @@ extension ATTNWebViewHandler: WKNavigationDelegate { in: nil, in: .defaultClient ) { [weak self] result in - guard let self = self, let sdk = self.sdk else { return } + guard let self = self, let webViewProvider = self.webViewProvider else { return } guard case let .success(statusAny) = result else { Loggers.creative.debug("No status returned from JS. Not showing WebView.") - self.sdk?.triggerHandler?(ATTNCreativeTriggerStatus.notOpened) + webViewProvider.triggerHandler?(ATTNCreativeTriggerStatus.notOpened) return } switch ScriptStatus.getRawValue(from: statusAny) { case .success: Loggers.creative.debug("Found creative iframe, showing WebView.") - if sdk.getMode() == .production { - sdk.parentView?.addSubview(webView) + if self.mode == .production { + webViewProvider.parentView?.addSubview(webView) } - sdk.triggerHandler?(ATTNCreativeTriggerStatus.opened) + webViewProvider.triggerHandler?(ATTNCreativeTriggerStatus.opened) case .timeout: Loggers.creative.error("Creative timed out. Not showing WebView.") - sdk.triggerHandler?(ATTNCreativeTriggerStatus.notOpened) + webViewProvider.triggerHandler?(ATTNCreativeTriggerStatus.notOpened) case .unknown(let statusString): Loggers.creative.error("Received unknown status: \(statusString). Not showing WebView") - sdk.triggerHandler?(ATTNCreativeTriggerStatus.notOpened) + webViewProvider.triggerHandler?(ATTNCreativeTriggerStatus.notOpened) default: break } } @@ -200,17 +198,40 @@ extension ATTNWebViewHandler: WKNavigationDelegate { extension ATTNWebViewHandler: WKScriptMessageHandler { func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { - let messageBody = message.body as? String ?? "" - Loggers.creative.debug("Web event message: \(messageBody). isCreativeOpen: \(ATTNSDK.isCreativeOpen ? "YES" : "NO")") + let messageBody = message.body as? String ?? "'Empty'" + Loggers.creative.debug("Web event message: \(messageBody). isCreativeOpen: \(self.isCreativeOpen ? "YES" : "NO")") if messageBody == "CLOSE" { closeCreative() } else if messageBody == "IMPRESSION" { Loggers.creative.debug("Creative opened and generated impression event") - ATTNSDK.isCreativeOpen = true - } else if messageBody == String(format: "%@ true", Constants.visibilityEvent), ATTNSDK.isCreativeOpen { + isCreativeOpen = true + } else if messageBody == String(format: "%@ true", Constants.visibilityEvent), isCreativeOpen { Loggers.creative.debug("Nav away from creative, closing") closeCreative() } } } + +fileprivate extension ATTNWebViewHandler { + var domain: String { + webViewProvider?.getDomain() ?? "" + } + + var mode: ATTNSDKMode { + webViewProvider?.getMode() ?? .production + } + + var userIdentity: ATTNUserIdentity { + webViewProvider?.getUserIdentity() ?? .init() + } + + var skipFatigueOnCreative: Bool { + webViewProvider?.skipFatigueOnCreative ?? false + } + + var isCreativeOpen: Bool { + get { webViewProvider?.isCreativeOpen ?? false } + set { webViewProvider?.isCreativeOpen = newValue } + } +} diff --git a/Sources/ATTNWebViewProviding.swift b/Sources/ATTNWebViewProviding.swift new file mode 100644 index 0000000..78454d8 --- /dev/null +++ b/Sources/ATTNWebViewProviding.swift @@ -0,0 +1,20 @@ +// +// ATTNWebViewProviding.swift +// attentive-ios-sdk-framework +// +// Created by Vladimir - Work on 2024-07-04. +// + +import WebKit + +protocol ATTNWebViewProviding: NSObjectProtocol { + var parentView: UIView? { get set } + var webView: WKWebView? { get set } + var skipFatigueOnCreative: Bool { get set } + var triggerHandler: ATTNCreativeTriggerCompletionHandler? { get set } + var isCreativeOpen: Bool { get set } + + func getDomain() -> String + func getMode() -> ATTNSDKMode + func getUserIdentity() -> ATTNUserIdentity +} diff --git a/Sources/Public/SDK/ATTNSDK.swift b/Sources/Public/SDK/ATTNSDK.swift index d15ba85..c572175 100644 --- a/Sources/Public/SDK/ATTNSDK.swift +++ b/Sources/Public/SDK/ATTNSDK.swift @@ -12,8 +12,7 @@ public typealias ATTNCreativeTriggerCompletionHandler = (String) -> Void @objc(ATTNSDK) public final class ATTNSDK: NSObject { - // MARK: Static Properties - static var isCreativeOpen = false + var isCreativeOpen = false // MARK: Instance Properties var parentView: UIView? @@ -41,7 +40,7 @@ public final class ATTNSDK: NSObject { super.init() - self.webViewHandler = ATTNWebViewHandler(sdk: self) + self.webViewHandler = ATTNWebViewHandler(webViewProvider: self) self.sendInfoEvent() self.initializeSkipFatigueOnCreatives() } @@ -102,6 +101,15 @@ public final class ATTNSDK: NSObject { } } +// MARK: ATTNWebViewProviding +extension ATTNSDK: ATTNWebViewProviding { + func getDomain() -> String { domain } + + func getMode() -> ATTNSDKMode { mode } + + func getUserIdentity() -> ATTNUserIdentity { userIdentity } +} + // MARK: Private Helpers fileprivate extension ATTNSDK { func sendInfoEvent() { @@ -121,22 +129,13 @@ fileprivate extension ATTNSDK { extension ATTNSDK { convenience init(domain: String, mode: ATTNSDKMode, urlBuilder: ATTNCreativeUrlProviding) { self.init(domain: domain, mode: mode) - self.webViewHandler = ATTNWebViewHandler(sdk: self, creativeUrlBuilder: urlBuilder) + self.webViewHandler = ATTNWebViewHandler(webViewProvider: self, creativeUrlBuilder: urlBuilder) } convenience init(api: ATTNAPIProtocol, urlBuilder: ATTNCreativeUrlProviding? = nil) { self.init(domain: api.domain) self.api = api guard let urlBuilder = urlBuilder else { return } - self.webViewHandler = ATTNWebViewHandler(sdk: self, creativeUrlBuilder: urlBuilder) - } - - func getDomain() -> String { domain } - - func getMode() -> ATTNSDKMode { mode } - - func removeWebView() { - webView?.removeFromSuperview() - webView = nil + self.webViewHandler = ATTNWebViewHandler(webViewProvider: self, creativeUrlBuilder: urlBuilder) } } diff --git a/attentive-ios-sdk.xcodeproj/project.pbxproj b/attentive-ios-sdk.xcodeproj/project.pbxproj index 68fbfc8..a8bf9b0 100644 --- a/attentive-ios-sdk.xcodeproj/project.pbxproj +++ b/attentive-ios-sdk.xcodeproj/project.pbxproj @@ -44,6 +44,7 @@ FB35C1992C0E5365009FA048 /* ATTNInfoEvent+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB35C1982C0E5365009FA048 /* ATTNInfoEvent+Extension.swift */; }; FB35C19B2C0E53F9009FA048 /* ATTNCustomEvent+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB35C19A2C0E53F9009FA048 /* ATTNCustomEvent+Extension.swift */; }; FB4E3FE52C36F7BF004B8FF0 /* ATTNWebViewHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB4E3FE42C36F7BF004B8FF0 /* ATTNWebViewHandling.swift */; }; + FB4E3FE72C372C54004B8FF0 /* ATTNWebViewProviding.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB4E3FE62C372C54004B8FF0 /* ATTNWebViewProviding.swift */; }; FB56D4DA2C208BAD00AF7530 /* ATTNSDK+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB56D4D92C208BAD00AF7530 /* ATTNSDK+Extension.swift */; }; FB56D4DC2C208D6100AF7530 /* Boolean+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB56D4DB2C208D6100AF7530 /* Boolean+Extension.swift */; }; FB56D4DE2C208DC100AF7530 /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB56D4DD2C208DC100AF7530 /* String+Extension.swift */; }; @@ -146,6 +147,7 @@ FB35C1982C0E5365009FA048 /* ATTNInfoEvent+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ATTNInfoEvent+Extension.swift"; sourceTree = ""; }; FB35C19A2C0E53F9009FA048 /* ATTNCustomEvent+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ATTNCustomEvent+Extension.swift"; sourceTree = ""; }; FB4E3FE42C36F7BF004B8FF0 /* ATTNWebViewHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ATTNWebViewHandling.swift; sourceTree = ""; }; + FB4E3FE62C372C54004B8FF0 /* ATTNWebViewProviding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ATTNWebViewProviding.swift; sourceTree = ""; }; FB56D4D92C208BAD00AF7530 /* ATTNSDK+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ATTNSDK+Extension.swift"; sourceTree = ""; }; FB56D4DB2C208D6100AF7530 /* Boolean+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Boolean+Extension.swift"; sourceTree = ""; }; FB56D4DD2C208DC100AF7530 /* String+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extension.swift"; sourceTree = ""; }; @@ -474,6 +476,7 @@ FBA9F9EF2C0A77AB00C65024 /* ATTNVisitorService.swift */, FB35C17C2C0E039E009FA048 /* ATTNConstants.swift */, FB4E3FE42C36F7BF004B8FF0 /* ATTNWebViewHandling.swift */, + FB4E3FE62C372C54004B8FF0 /* ATTNWebViewProviding.swift */, FBA9F9FF2C0A77AB00C65024 /* Public */, FBA9FA022C0A77AB00C65024 /* Resources */, ); @@ -674,6 +677,7 @@ FBA9FA112C0A77AB00C65024 /* ATTNCart.swift in Sources */, FBA9FA102C0A77AB00C65024 /* ATTNAddToCartEvent.swift in Sources */, FB35C17B2C0E0353009FA048 /* ATTNIdentifierType.swift in Sources */, + FB4E3FE72C372C54004B8FF0 /* ATTNWebViewProviding.swift in Sources */, FBA9FA0A2C0A77AB00C65024 /* ATTNCreativeUrlProvider.swift in Sources */, FB65536C2C1B74A9008DB3B1 /* ATTNAPIProtocol.swift in Sources */, FB35C17D2C0E039E009FA048 /* ATTNConstants.swift in Sources */, From 91dc14c8d127205702a26512634df536040cc96e Mon Sep 17 00:00:00 2001 From: Vladimir Espinola Lezcano Date: Mon, 8 Jul 2024 14:00:55 -0400 Subject: [PATCH 11/25] Release 1.0.0 Beta 3 (#110) * bumped version to 1.0.0-beta.3 * updated changelog --- ATTNSDKFramework.podspec | 2 +- CHANGELOG.md | 6 ++++++ README.md | 4 ++-- Sources/ATTNConstants.swift | 2 +- attentive-ios-sdk.podspec | 2 +- 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/ATTNSDKFramework.podspec b/ATTNSDKFramework.podspec index 2b47b95..26fa36a 100644 --- a/ATTNSDKFramework.podspec +++ b/ATTNSDKFramework.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'ATTNSDKFramework' - s.version = '1.0.0-beta.2' + s.version = '1.0.0-beta.3' s.summary = 'Attentive IOS SDK' # This description is used to generate tags and improve search results. diff --git a/CHANGELOG.md b/CHANGELOG.md index e47edb7..879a242 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## [1.0.0-beta.3](https://github.com/attentive-mobile/attentive-ios-sdk/compare/1.0.0-beta.2...1.0.0-beta.3) (2024-07-08) + +### Feature +* Create documentation for developers that details how to set up the apps locally, test, and release ([108](https://github.com/attentive-mobile/attentive-ios-sdk/pull/108)) ([2b7a08c](https://github.com/attentive-mobile/attentive-ios-sdk/commit/2b7a08cef032942ba4848b662fc86f71453f03dc)) +* Reduce complexity in ATTNSDK ([109](https://github.com/attentive-mobile/attentive-ios-sdk/pull/109)) ([c48edeb](https://github.com/attentive-mobile/attentive-ios-sdk/commit/c48edeb25d3798b0553cc00cd85a17c47e4890bb)) + ## [1.0.0-beta.2](https://github.com/attentive-mobile/attentive-ios-sdk/compare/1.0.0-beta.1...1.0.0-beta.2) (2024-06-27) ### Bug Fixes diff --git a/README.md b/README.md index e2ebe6f..2bd35af 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ The attentive-ios-sdk is available through [CocoaPods](https://cocoapods.org). T ```ruby target 'MyApp' do - pod 'ATTNSDKFramework', '1.0.0-beta.2' + pod 'ATTNSDKFramework', '1.0.0-beta.3' end ``` @@ -40,7 +40,7 @@ In your applications `Package.swift` file, add the attentive-ios-sdk as a depend ```swift dependencies: [ // your other app dependencies - .package(url: "https://github.com/attentive-mobile/attentive-ios-sdk", from: "1.0.0-beta.2"), + .package(url: "https://github.com/attentive-mobile/attentive-ios-sdk", from: "1.0.0-beta.3"), ], ``` diff --git a/Sources/ATTNConstants.swift b/Sources/ATTNConstants.swift index 375e443..a6220e6 100644 --- a/Sources/ATTNConstants.swift +++ b/Sources/ATTNConstants.swift @@ -10,6 +10,6 @@ import Foundation struct ATTNConstants { private init() { } - static let sdkVersion = "1.0.0-beta.2" + static let sdkVersion = "1.0.0-beta.3" static let skipFatigueEnvKey = "SKIP_FATIGUE_ON_CREATIVE" } diff --git a/attentive-ios-sdk.podspec b/attentive-ios-sdk.podspec index e803016..f98c1e8 100644 --- a/attentive-ios-sdk.podspec +++ b/attentive-ios-sdk.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'attentive-ios-sdk' - s.version = '1.0.0-beta.2' + s.version = '1.0.0-beta.3' s.summary = 'Attentive IOS SDK' # This description is used to generate tags and improve search results. From 3eb82128708a73b9174d36e62943ec518d30e03d Mon Sep 17 00:00:00 2001 From: Vladimir Espinola Date: Wed, 10 Jul 2024 17:20:15 -0400 Subject: [PATCH 12/25] added deeplink property to events --- .../Extension/ATTNAddToCartEvent+Extension.swift | 5 +++++ .../Extension/ATTNProductViewEvent+Extension.swift | 5 +++++ Sources/Public/Events/ATTNAddToCartEvent.swift | 3 +++ Sources/Public/Events/ATTNDeeplinkHandling.swift | 12 ++++++++++++ Sources/Public/Events/ATTNProductViewEvent.swift | 3 +++ attentive-ios-sdk.xcodeproj/project.pbxproj | 4 ++++ 6 files changed, 32 insertions(+) create mode 100644 Sources/Public/Events/ATTNDeeplinkHandling.swift diff --git a/Sources/Helpers/Extension/ATTNAddToCartEvent+Extension.swift b/Sources/Helpers/Extension/ATTNAddToCartEvent+Extension.swift index 86678e5..f4c7a1c 100644 --- a/Sources/Helpers/Extension/ATTNAddToCartEvent+Extension.swift +++ b/Sources/Helpers/Extension/ATTNAddToCartEvent+Extension.swift @@ -19,6 +19,11 @@ extension ATTNAddToCartEvent: ATTNEventRequestProvider { for item in items { var metadata = [String: Any]() item.addItem(toDictionary: &metadata, with: priceFormatter) + + if let deeplink { + metadata["requestURL"] = deeplink + } + eventRequests.append(.init(metadata: metadata, eventNameAbbreviation: ATTNEventTypes.addToCart)) } diff --git a/Sources/Helpers/Extension/ATTNProductViewEvent+Extension.swift b/Sources/Helpers/Extension/ATTNProductViewEvent+Extension.swift index c77c347..be59244 100644 --- a/Sources/Helpers/Extension/ATTNProductViewEvent+Extension.swift +++ b/Sources/Helpers/Extension/ATTNProductViewEvent+Extension.swift @@ -19,6 +19,11 @@ extension ATTNProductViewEvent: ATTNEventRequestProvider { for item in items { var metadata = [String: Any]() item.addItem(toDictionary: &metadata, with: priceFormatter) + + if let deeplink { + metadata["requestURL"] = deeplink + } + eventRequests.append(.init(metadata: metadata, eventNameAbbreviation: ATTNEventTypes.productView)) } diff --git a/Sources/Public/Events/ATTNAddToCartEvent.swift b/Sources/Public/Events/ATTNAddToCartEvent.swift index c72c0b1..4323338 100644 --- a/Sources/Public/Events/ATTNAddToCartEvent.swift +++ b/Sources/Public/Events/ATTNAddToCartEvent.swift @@ -10,6 +10,7 @@ import Foundation @objc(ATTNAddToCartEvent) public final class ATTNAddToCartEvent: NSObject, ATTNEvent { @objc public let items: [ATTNItem] + @objc public var deeplink: String? @objc public init(items: [ATTNItem]) { @@ -21,3 +22,5 @@ public final class ATTNAddToCartEvent: NSObject, ATTNEvent { fatalError("init() has not been implemented") } } + +extension ATTNAddToCartEvent: ATTNDeeplinkHandling { } diff --git a/Sources/Public/Events/ATTNDeeplinkHandling.swift b/Sources/Public/Events/ATTNDeeplinkHandling.swift new file mode 100644 index 0000000..a2d7d17 --- /dev/null +++ b/Sources/Public/Events/ATTNDeeplinkHandling.swift @@ -0,0 +1,12 @@ +// +// ATTNDeeplinkHandling.swift +// attentive-ios-sdk-framework +// +// Created by Vladimir - Work on 2024-07-10. +// + +import Foundation + +protocol ATTNDeeplinkHandling { + var deeplink: String? { get set } +} diff --git a/Sources/Public/Events/ATTNProductViewEvent.swift b/Sources/Public/Events/ATTNProductViewEvent.swift index 23af889..22b0cb6 100644 --- a/Sources/Public/Events/ATTNProductViewEvent.swift +++ b/Sources/Public/Events/ATTNProductViewEvent.swift @@ -10,6 +10,7 @@ import Foundation @objc(ATTNProductViewEvent) public final class ATTNProductViewEvent: NSObject, ATTNEvent { @objc public let items: [ATTNItem] + @objc public var deeplink: String? @objc(initWithItems:) public init(items: [ATTNItem]) { @@ -21,3 +22,5 @@ public final class ATTNProductViewEvent: NSObject, ATTNEvent { fatalError("init() has not been implemented") } } + +extension ATTNProductViewEvent: ATTNDeeplinkHandling { } diff --git a/attentive-ios-sdk.xcodeproj/project.pbxproj b/attentive-ios-sdk.xcodeproj/project.pbxproj index a8bf9b0..ae17bc1 100644 --- a/attentive-ios-sdk.xcodeproj/project.pbxproj +++ b/attentive-ios-sdk.xcodeproj/project.pbxproj @@ -52,6 +52,7 @@ FB60AF0B2C1211C700C61537 /* ATTNEventURLProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60AF0A2C1211C700C61537 /* ATTNEventURLProvider.swift */; }; FB65536A2C1B72D4008DB3B1 /* ATTNSDKTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB6553692C1B72D4008DB3B1 /* ATTNSDKTests.swift */; }; FB65536C2C1B74A9008DB3B1 /* ATTNAPIProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB65536B2C1B74A9008DB3B1 /* ATTNAPIProtocol.swift */; }; + FB84E3032C3F30FF0011936B /* ATTNDeeplinkHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB84E3022C3F30FF0011936B /* ATTNDeeplinkHandling.swift */; }; FB90EF0B2C109CB4004DFC4A /* ATTNAPIITTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB90EF0A2C109CB4004DFC4A /* ATTNAPIITTests.swift */; }; FB90EF0D2C10A7F7004DFC4A /* NSURLSessionDataTaskMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB90EF0C2C10A7F7004DFC4A /* NSURLSessionDataTaskMock.swift */; }; FB90EF0F2C10A81E004DFC4A /* NSURLSessionMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB90EF0E2C10A81E004DFC4A /* NSURLSessionMock.swift */; }; @@ -156,6 +157,7 @@ FB6553692C1B72D4008DB3B1 /* ATTNSDKTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ATTNSDKTests.swift; sourceTree = ""; }; FB65536B2C1B74A9008DB3B1 /* ATTNAPIProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ATTNAPIProtocol.swift; sourceTree = ""; }; FB65536D2C1B7747008DB3B1 /* ATTNAPISpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ATTNAPISpy.swift; sourceTree = ""; }; + FB84E3022C3F30FF0011936B /* ATTNDeeplinkHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ATTNDeeplinkHandling.swift; sourceTree = ""; }; FB90EF0A2C109CB4004DFC4A /* ATTNAPIITTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ATTNAPIITTests.swift; sourceTree = ""; }; FB90EF0C2C10A7F7004DFC4A /* NSURLSessionDataTaskMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSURLSessionDataTaskMock.swift; sourceTree = ""; }; FB90EF0E2C10A81E004DFC4A /* NSURLSessionMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSURLSessionMock.swift; sourceTree = ""; }; @@ -437,6 +439,7 @@ FBA9F9F72C0A77AB00C65024 /* ATTNPrice.swift */, FBA9F9F82C0A77AB00C65024 /* ATTNProductViewEvent.swift */, FBA9F9F92C0A77AB00C65024 /* ATTNPurchaseEvent.swift */, + FB84E3022C3F30FF0011936B /* ATTNDeeplinkHandling.swift */, ); path = Events; sourceTree = ""; @@ -680,6 +683,7 @@ FB4E3FE72C372C54004B8FF0 /* ATTNWebViewProviding.swift in Sources */, FBA9FA0A2C0A77AB00C65024 /* ATTNCreativeUrlProvider.swift in Sources */, FB65536C2C1B74A9008DB3B1 /* ATTNAPIProtocol.swift in Sources */, + FB84E3032C3F30FF0011936B /* ATTNDeeplinkHandling.swift in Sources */, FB35C17D2C0E039E009FA048 /* ATTNConstants.swift in Sources */, FB56D4DC2C208D6100AF7530 /* Boolean+Extension.swift in Sources */, FBA9FA082C0A77AB00C65024 /* Dictionary+Extension.swift in Sources */, From 4b119a3cee81715097747f7ea085ace618b16f1f Mon Sep 17 00:00:00 2001 From: Vladimir Espinola Date: Thu, 11 Jul 2024 15:24:13 -0400 Subject: [PATCH 13/25] added unit test --- Example/Base.lproj/Main.storyboard | 110 +++++++++++------- Example/Example/ProductViewController.m | 23 ++-- Tests/TestCases/ATTNAddToCartEventTests.swift | 28 +++++ .../TestCases/ATTNProductViewEventTests.swift | 30 +++++ attentive-ios-sdk.xcodeproj/project.pbxproj | 8 ++ 5 files changed, 152 insertions(+), 47 deletions(-) create mode 100644 Tests/TestCases/ATTNAddToCartEventTests.swift create mode 100644 Tests/TestCases/ATTNProductViewEventTests.swift diff --git a/Example/Base.lproj/Main.storyboard b/Example/Base.lproj/Main.storyboard index 821d493..85567ee 100644 --- a/Example/Base.lproj/Main.storyboard +++ b/Example/Base.lproj/Main.storyboard @@ -157,48 +157,80 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/Example/ProductViewController.m b/Example/Example/ProductViewController.m index 4e00b84..6abbab4 100644 --- a/Example/Example/ProductViewController.m +++ b/Example/Example/ProductViewController.m @@ -26,16 +26,26 @@ - (void)viewDidLoad { - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; - [self showToast:@"Product View event sent"]; + [self showToast:@"Product View event sent" duration:2]; } - (IBAction)addToCartButtonPressed:(id)sender { ATTNItem* item = [self buildItem]; ATTNAddToCartEvent* addToCart = [[ATTNAddToCartEvent alloc] initWithItems:@[ item ]]; + addToCart.deeplink = @"https://www.clientapp.com/flow=payment"; [[ATTNEventTracker sharedInstance] recordEvent:addToCart]; - [self showToast:@"Add To Cart event sent"]; + [self showToast:@"Add To Cart event sent" duration:2]; +} + +- (IBAction)addToCartWithDeeplinkButtonPressed:(id)sender { + ATTNItem* item = [self buildItem]; + ATTNAddToCartEvent* addToCart = [[ATTNAddToCartEvent alloc] initWithItems:@[ item ]]; + addToCart.deeplink = @"https://www.clientapp.com/flow=payment"; + + [[ATTNEventTracker sharedInstance] recordEvent:addToCart]; + [self showToast: [NSString stringWithFormat:@"Add To Cart event sent with requestURL: '%@'", addToCart.deeplink] duration:4]; } - (IBAction)purchaseButtonPressed:(id)sender { @@ -50,7 +60,7 @@ - (IBAction)purchaseButtonPressed:(id)sender { [[ATTNEventTracker sharedInstance] recordEvent:purchase]; - [self showToast:@"Purchase event sent"]; + [self showToast:@"Purchase event sent" duration:2]; } - (ATTNItem*)buildItem { @@ -67,19 +77,16 @@ - (IBAction)customEventButtonPressed:(id)sender { [[ATTNEventTracker sharedInstance] recordEvent:customEvent]; - [self showToast:@"Custom event sent"]; + [self showToast:@"Custom event sent" duration:2]; } - -- (void)showToast:(NSString*)message { +- (void)showToast:(NSString*)message duration:(int)duration { UIAlertController* alert = [UIAlertController alertControllerWithTitle:nil message:message preferredStyle:UIAlertControllerStyleAlert]; [self presentViewController:alert animated:YES completion:nil]; - int duration = 1; // duration in seconds - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, duration * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ [alert dismissViewControllerAnimated:YES completion:nil]; }); diff --git a/Tests/TestCases/ATTNAddToCartEventTests.swift b/Tests/TestCases/ATTNAddToCartEventTests.swift new file mode 100644 index 0000000..21b7cd3 --- /dev/null +++ b/Tests/TestCases/ATTNAddToCartEventTests.swift @@ -0,0 +1,28 @@ +// +// ATTNAddToCartEventTests.swift +// attentive-ios-sdk Tests +// +// Created by Vladimir - Work on 2024-07-11. +// + +import XCTest +@testable import ATTNSDKFramework + +final class ATTNAddToCartEventTests: XCTestCase { + func testAddCart_GivenData_ShouldBuildURL() { + let item = ATTNTestEventUtils.buildItem() + let addToCart = ATTNAddToCartEvent(items: [item]) + XCTAssertFalse(addToCart.eventRequests.isEmpty) + XCTAssertNil(addToCart.eventRequests.first?.metadata["requestURL"]) + } + + func testAddCart_GivenData_ShouldBuildURLWithRequestURL() { + let item = ATTNTestEventUtils.buildItem() + let addToCart = ATTNAddToCartEvent(items: [item]) + addToCart.deeplink = "https://www.clientapp.com/flow=payment" + XCTAssertFalse(addToCart.eventRequests.isEmpty) + let requestURL = addToCart.eventRequests.first?.metadata["requestURL"] as? String + XCTAssertNotNil(requestURL) + XCTAssertFalse(requestURL?.isEmpty ?? true) + } +} diff --git a/Tests/TestCases/ATTNProductViewEventTests.swift b/Tests/TestCases/ATTNProductViewEventTests.swift new file mode 100644 index 0000000..3ba71e7 --- /dev/null +++ b/Tests/TestCases/ATTNProductViewEventTests.swift @@ -0,0 +1,30 @@ +// +// ATTNProductViewEventTests.swift +// attentive-ios-sdk Tests +// +// Created by Vladimir - Work on 2024-07-11. +// + +import Foundation + +import XCTest +@testable import ATTNSDKFramework + +final class ATTNProductViewEventTests: XCTestCase { + func testProductView_GivenData_ShouldBuildURL() { + let item = ATTNTestEventUtils.buildItem() + let productView = ATTNProductViewEvent(items: [item]) + XCTAssertFalse(productView.eventRequests.isEmpty) + XCTAssertNil(productView.eventRequests.first?.metadata["requestURL"]) + } + + func testProductView_GivenData_ShouldBuildURLWithRequestURL() { + let item = ATTNTestEventUtils.buildItem() + let productView = ATTNProductViewEvent(items: [item]) + productView.deeplink = "https://www.clientapp.com/flow=payment" + XCTAssertFalse(productView.eventRequests.isEmpty) + let requestURL = productView.eventRequests.first?.metadata["requestURL"] as? String + XCTAssertNotNil(requestURL) + XCTAssertFalse(requestURL?.isEmpty ?? true) + } +} diff --git a/attentive-ios-sdk.xcodeproj/project.pbxproj b/attentive-ios-sdk.xcodeproj/project.pbxproj index ae17bc1..7b21641 100644 --- a/attentive-ios-sdk.xcodeproj/project.pbxproj +++ b/attentive-ios-sdk.xcodeproj/project.pbxproj @@ -53,6 +53,8 @@ FB65536A2C1B72D4008DB3B1 /* ATTNSDKTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB6553692C1B72D4008DB3B1 /* ATTNSDKTests.swift */; }; FB65536C2C1B74A9008DB3B1 /* ATTNAPIProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB65536B2C1B74A9008DB3B1 /* ATTNAPIProtocol.swift */; }; FB84E3032C3F30FF0011936B /* ATTNDeeplinkHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB84E3022C3F30FF0011936B /* ATTNDeeplinkHandling.swift */; }; + FB86C03E2C40611A00E35580 /* ATTNAddToCartEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB86C03D2C40611A00E35580 /* ATTNAddToCartEventTests.swift */; }; + FB86C0402C40669400E35580 /* ATTNProductViewEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB86C03F2C40669400E35580 /* ATTNProductViewEventTests.swift */; }; FB90EF0B2C109CB4004DFC4A /* ATTNAPIITTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB90EF0A2C109CB4004DFC4A /* ATTNAPIITTests.swift */; }; FB90EF0D2C10A7F7004DFC4A /* NSURLSessionDataTaskMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB90EF0C2C10A7F7004DFC4A /* NSURLSessionDataTaskMock.swift */; }; FB90EF0F2C10A81E004DFC4A /* NSURLSessionMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB90EF0E2C10A81E004DFC4A /* NSURLSessionMock.swift */; }; @@ -158,6 +160,8 @@ FB65536B2C1B74A9008DB3B1 /* ATTNAPIProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ATTNAPIProtocol.swift; sourceTree = ""; }; FB65536D2C1B7747008DB3B1 /* ATTNAPISpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ATTNAPISpy.swift; sourceTree = ""; }; FB84E3022C3F30FF0011936B /* ATTNDeeplinkHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ATTNDeeplinkHandling.swift; sourceTree = ""; }; + FB86C03D2C40611A00E35580 /* ATTNAddToCartEventTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ATTNAddToCartEventTests.swift; sourceTree = ""; }; + FB86C03F2C40669400E35580 /* ATTNProductViewEventTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ATTNProductViewEventTests.swift; sourceTree = ""; }; FB90EF0A2C109CB4004DFC4A /* ATTNAPIITTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ATTNAPIITTests.swift; sourceTree = ""; }; FB90EF0C2C10A7F7004DFC4A /* NSURLSessionDataTaskMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSURLSessionDataTaskMock.swift; sourceTree = ""; }; FB90EF0E2C10A81E004DFC4A /* NSURLSessionMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSURLSessionMock.swift; sourceTree = ""; }; @@ -337,6 +341,8 @@ FB2984072C0FAE040039759C /* ATTNAPITests.swift */, FB90EF0A2C109CB4004DFC4A /* ATTNAPIITTests.swift */, FB6553692C1B72D4008DB3B1 /* ATTNSDKTests.swift */, + FB86C03D2C40611A00E35580 /* ATTNAddToCartEventTests.swift */, + FB86C03F2C40669400E35580 /* ATTNProductViewEventTests.swift */, ); path = TestCases; sourceTree = ""; @@ -704,7 +710,9 @@ FB90EF0F2C10A81E004DFC4A /* NSURLSessionMock.swift in Sources */, FB2983E42C0F73990039759C /* ATTNAppInfoMock.swift in Sources */, FB2983F82C0F7FB60039759C /* ATTNUserAgentBuilderTests.swift in Sources */, + FB86C03E2C40611A00E35580 /* ATTNAddToCartEventTests.swift in Sources */, FB2983F62C0F7CF10039759C /* ATTNUserIdentityTests.swift in Sources */, + FB86C0402C40669400E35580 /* ATTNProductViewEventTests.swift in Sources */, FB2983F42C0F759D0039759C /* ATTNVisitorServiceTests.swift in Sources */, FB2983FA2C0F80A30039759C /* ATTNPersistentStorageTests.swift in Sources */, FB080C722C1C755F00834BAA /* ATTNCreativeUrlProviderSpy.swift in Sources */, From 656b473454fa8774d504b3d343696390d908ba1a Mon Sep 17 00:00:00 2001 From: Vladimir Espinola Date: Thu, 11 Jul 2024 15:38:31 -0400 Subject: [PATCH 14/25] updated events init signatures to accept deeplink --- Sources/Public/Events/ATTNAddToCartEvent.swift | 10 ++++++++-- Sources/Public/Events/ATTNProductViewEvent.swift | 8 +++++++- Tests/TestCases/ATTNProductViewEventTests.swift | 3 +-- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/Sources/Public/Events/ATTNAddToCartEvent.swift b/Sources/Public/Events/ATTNAddToCartEvent.swift index 4323338..7e02c09 100644 --- a/Sources/Public/Events/ATTNAddToCartEvent.swift +++ b/Sources/Public/Events/ATTNAddToCartEvent.swift @@ -12,9 +12,15 @@ public final class ATTNAddToCartEvent: NSObject, ATTNEvent { @objc public let items: [ATTNItem] @objc public var deeplink: String? - @objc - public init(items: [ATTNItem]) { + @objc(initWithItems:) + public convenience init(items: [ATTNItem]) { + self.init(items: items) + } + + @objc(initWithItems:deeplink:) + public init(items: [ATTNItem], deeplink: String?) { self.items = items + self.deeplink = deeplink super.init() } diff --git a/Sources/Public/Events/ATTNProductViewEvent.swift b/Sources/Public/Events/ATTNProductViewEvent.swift index 22b0cb6..7ac0176 100644 --- a/Sources/Public/Events/ATTNProductViewEvent.swift +++ b/Sources/Public/Events/ATTNProductViewEvent.swift @@ -13,8 +13,14 @@ public final class ATTNProductViewEvent: NSObject, ATTNEvent { @objc public var deeplink: String? @objc(initWithItems:) - public init(items: [ATTNItem]) { + public convenience init(items: [ATTNItem]) { + self.init(items: items, deeplink: nil) + } + + @objc(initWithItems:deeplink:) + public init(items: [ATTNItem], deeplink: String?) { self.items = items + self.deeplink = deeplink super.init() } diff --git a/Tests/TestCases/ATTNProductViewEventTests.swift b/Tests/TestCases/ATTNProductViewEventTests.swift index 3ba71e7..33ebf6b 100644 --- a/Tests/TestCases/ATTNProductViewEventTests.swift +++ b/Tests/TestCases/ATTNProductViewEventTests.swift @@ -20,8 +20,7 @@ final class ATTNProductViewEventTests: XCTestCase { func testProductView_GivenData_ShouldBuildURLWithRequestURL() { let item = ATTNTestEventUtils.buildItem() - let productView = ATTNProductViewEvent(items: [item]) - productView.deeplink = "https://www.clientapp.com/flow=payment" + let productView = ATTNProductViewEvent(items: [item], deeplink: "https://www.clientapp.com/flow=payment") XCTAssertFalse(productView.eventRequests.isEmpty) let requestURL = productView.eventRequests.first?.metadata["requestURL"] as? String XCTAssertNotNil(requestURL) From 9d7e131783dde9cd8f641fde8a25cbddbd6854c5 Mon Sep 17 00:00:00 2001 From: Vladimir Espinola Date: Thu, 11 Jul 2024 15:57:14 -0400 Subject: [PATCH 15/25] fixed convenience init method --- Sources/Public/Events/ATTNAddToCartEvent.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Public/Events/ATTNAddToCartEvent.swift b/Sources/Public/Events/ATTNAddToCartEvent.swift index 7e02c09..181f454 100644 --- a/Sources/Public/Events/ATTNAddToCartEvent.swift +++ b/Sources/Public/Events/ATTNAddToCartEvent.swift @@ -14,7 +14,7 @@ public final class ATTNAddToCartEvent: NSObject, ATTNEvent { @objc(initWithItems:) public convenience init(items: [ATTNItem]) { - self.init(items: items) + self.init(items: items, deeplink: nil) } @objc(initWithItems:deeplink:) From c11d53cfc34b7564dfe6369a5947bd8b623daaf8 Mon Sep 17 00:00:00 2001 From: Vladimir Espinola Date: Fri, 12 Jul 2024 10:11:31 -0400 Subject: [PATCH 16/25] updated param name to pd --- Example/Example/ProductViewController.m | 2 +- .../Helpers/Extension/ATTNAddToCartEvent+Extension.swift | 2 +- .../Helpers/Extension/ATTNProductViewEvent+Extension.swift | 2 +- Tests/TestCases/ATTNAddToCartEventTests.swift | 6 +++--- Tests/TestCases/ATTNProductViewEventTests.swift | 6 +++--- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Example/Example/ProductViewController.m b/Example/Example/ProductViewController.m index 6abbab4..e6e2ef9 100644 --- a/Example/Example/ProductViewController.m +++ b/Example/Example/ProductViewController.m @@ -45,7 +45,7 @@ - (IBAction)addToCartWithDeeplinkButtonPressed:(id)sender { addToCart.deeplink = @"https://www.clientapp.com/flow=payment"; [[ATTNEventTracker sharedInstance] recordEvent:addToCart]; - [self showToast: [NSString stringWithFormat:@"Add To Cart event sent with requestURL: '%@'", addToCart.deeplink] duration:4]; + [self showToast: [NSString stringWithFormat:@"Add To Cart event sent with requestURL(pd): '%@'", addToCart.deeplink] duration:4]; } - (IBAction)purchaseButtonPressed:(id)sender { diff --git a/Sources/Helpers/Extension/ATTNAddToCartEvent+Extension.swift b/Sources/Helpers/Extension/ATTNAddToCartEvent+Extension.swift index f4c7a1c..0ae0a89 100644 --- a/Sources/Helpers/Extension/ATTNAddToCartEvent+Extension.swift +++ b/Sources/Helpers/Extension/ATTNAddToCartEvent+Extension.swift @@ -21,7 +21,7 @@ extension ATTNAddToCartEvent: ATTNEventRequestProvider { item.addItem(toDictionary: &metadata, with: priceFormatter) if let deeplink { - metadata["requestURL"] = deeplink + metadata["pd"] = deeplink } eventRequests.append(.init(metadata: metadata, eventNameAbbreviation: ATTNEventTypes.addToCart)) diff --git a/Sources/Helpers/Extension/ATTNProductViewEvent+Extension.swift b/Sources/Helpers/Extension/ATTNProductViewEvent+Extension.swift index be59244..30b9020 100644 --- a/Sources/Helpers/Extension/ATTNProductViewEvent+Extension.swift +++ b/Sources/Helpers/Extension/ATTNProductViewEvent+Extension.swift @@ -21,7 +21,7 @@ extension ATTNProductViewEvent: ATTNEventRequestProvider { item.addItem(toDictionary: &metadata, with: priceFormatter) if let deeplink { - metadata["requestURL"] = deeplink + metadata["pd"] = deeplink } eventRequests.append(.init(metadata: metadata, eventNameAbbreviation: ATTNEventTypes.productView)) diff --git a/Tests/TestCases/ATTNAddToCartEventTests.swift b/Tests/TestCases/ATTNAddToCartEventTests.swift index 21b7cd3..80850f1 100644 --- a/Tests/TestCases/ATTNAddToCartEventTests.swift +++ b/Tests/TestCases/ATTNAddToCartEventTests.swift @@ -13,15 +13,15 @@ final class ATTNAddToCartEventTests: XCTestCase { let item = ATTNTestEventUtils.buildItem() let addToCart = ATTNAddToCartEvent(items: [item]) XCTAssertFalse(addToCart.eventRequests.isEmpty) - XCTAssertNil(addToCart.eventRequests.first?.metadata["requestURL"]) + XCTAssertNil(addToCart.eventRequests.first?.metadata["pd"]) } func testAddCart_GivenData_ShouldBuildURLWithRequestURL() { let item = ATTNTestEventUtils.buildItem() let addToCart = ATTNAddToCartEvent(items: [item]) - addToCart.deeplink = "https://www.clientapp.com/flow=payment" + addToCart.deeplink = "https://mydeeplink.com/products/32432423" XCTAssertFalse(addToCart.eventRequests.isEmpty) - let requestURL = addToCart.eventRequests.first?.metadata["requestURL"] as? String + let requestURL = addToCart.eventRequests.first?.metadata["pd"] as? String XCTAssertNotNil(requestURL) XCTAssertFalse(requestURL?.isEmpty ?? true) } diff --git a/Tests/TestCases/ATTNProductViewEventTests.swift b/Tests/TestCases/ATTNProductViewEventTests.swift index 33ebf6b..3af1f1d 100644 --- a/Tests/TestCases/ATTNProductViewEventTests.swift +++ b/Tests/TestCases/ATTNProductViewEventTests.swift @@ -15,14 +15,14 @@ final class ATTNProductViewEventTests: XCTestCase { let item = ATTNTestEventUtils.buildItem() let productView = ATTNProductViewEvent(items: [item]) XCTAssertFalse(productView.eventRequests.isEmpty) - XCTAssertNil(productView.eventRequests.first?.metadata["requestURL"]) + XCTAssertNil(productView.eventRequests.first?.metadata["pd"]) } func testProductView_GivenData_ShouldBuildURLWithRequestURL() { let item = ATTNTestEventUtils.buildItem() - let productView = ATTNProductViewEvent(items: [item], deeplink: "https://www.clientapp.com/flow=payment") + let productView = ATTNProductViewEvent(items: [item], deeplink: "https://mydeeplink.com/products/32432423") XCTAssertFalse(productView.eventRequests.isEmpty) - let requestURL = productView.eventRequests.first?.metadata["requestURL"] as? String + let requestURL = productView.eventRequests.first?.metadata["pd"] as? String XCTAssertNotNil(requestURL) XCTAssertFalse(requestURL?.isEmpty ?? true) } From c163c88757219d80b88ee1b9f69e8ced406a1a59 Mon Sep 17 00:00:00 2001 From: Vladimir Espinola Date: Fri, 12 Jul 2024 12:17:13 -0400 Subject: [PATCH 17/25] updated pd parameter position --- Example/Example/ProductViewController.m | 12 ++++-------- Sources/API/ATTNEventRequest.swift | 1 + .../Extension/ATTNAddToCartEvent+Extension.swift | 9 +++++++-- .../Extension/ATTNProductViewEvent+Extension.swift | 9 +++++++-- Sources/URLProviders/ATTNEventURLProvider.swift | 4 ++++ Tests/TestCases/ATTNAddToCartEventTests.swift | 4 ++-- Tests/TestCases/ATTNProductViewEventTests.swift | 4 ++-- 7 files changed, 27 insertions(+), 16 deletions(-) diff --git a/Example/Example/ProductViewController.m b/Example/Example/ProductViewController.m index e6e2ef9..32cefa9 100644 --- a/Example/Example/ProductViewController.m +++ b/Example/Example/ProductViewController.m @@ -13,18 +13,14 @@ @interface ProductViewController () @end @implementation ProductViewController - -- (void)viewDidLoad { - [super viewDidLoad]; +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; ATTNItem* item = [self buildItem]; ATTNProductViewEvent* productView = [[ATTNProductViewEvent alloc] initWithItems:@[ item ]]; + productView.deeplink = @"https://mydeeplink.com/products/32432423"; [[ATTNEventTracker sharedInstance] recordEvent:productView]; -} - -- (void)viewDidAppear:(BOOL)animated { - [super viewDidAppear:animated]; [self showToast:@"Product View event sent" duration:2]; } @@ -42,7 +38,7 @@ - (IBAction)addToCartButtonPressed:(id)sender { - (IBAction)addToCartWithDeeplinkButtonPressed:(id)sender { ATTNItem* item = [self buildItem]; ATTNAddToCartEvent* addToCart = [[ATTNAddToCartEvent alloc] initWithItems:@[ item ]]; - addToCart.deeplink = @"https://www.clientapp.com/flow=payment"; + addToCart.deeplink = @"https://mydeeplink.com/products/32432423"; [[ATTNEventTracker sharedInstance] recordEvent:addToCart]; [self showToast: [NSString stringWithFormat:@"Add To Cart event sent with requestURL(pd): '%@'", addToCart.deeplink] duration:4]; diff --git a/Sources/API/ATTNEventRequest.swift b/Sources/API/ATTNEventRequest.swift index 17b33a7..4abc5b1 100644 --- a/Sources/API/ATTNEventRequest.swift +++ b/Sources/API/ATTNEventRequest.swift @@ -11,6 +11,7 @@ import Foundation final class ATTNEventRequest { var metadata: [String: Any] let eventNameAbbreviation: String + var deeplink: String? init(metadata: [String: Any], eventNameAbbreviation: String) { self.metadata = metadata diff --git a/Sources/Helpers/Extension/ATTNAddToCartEvent+Extension.swift b/Sources/Helpers/Extension/ATTNAddToCartEvent+Extension.swift index 0ae0a89..bf32d18 100644 --- a/Sources/Helpers/Extension/ATTNAddToCartEvent+Extension.swift +++ b/Sources/Helpers/Extension/ATTNAddToCartEvent+Extension.swift @@ -20,11 +20,16 @@ extension ATTNAddToCartEvent: ATTNEventRequestProvider { var metadata = [String: Any]() item.addItem(toDictionary: &metadata, with: priceFormatter) + let eventRequest = ATTNEventRequest( + metadata: metadata, + eventNameAbbreviation: ATTNEventTypes.addToCart + ) + if let deeplink { - metadata["pd"] = deeplink + eventRequest.deeplink = deeplink } - eventRequests.append(.init(metadata: metadata, eventNameAbbreviation: ATTNEventTypes.addToCart)) + eventRequests.append(eventRequest) } return eventRequests diff --git a/Sources/Helpers/Extension/ATTNProductViewEvent+Extension.swift b/Sources/Helpers/Extension/ATTNProductViewEvent+Extension.swift index 30b9020..3ce0df7 100644 --- a/Sources/Helpers/Extension/ATTNProductViewEvent+Extension.swift +++ b/Sources/Helpers/Extension/ATTNProductViewEvent+Extension.swift @@ -20,11 +20,16 @@ extension ATTNProductViewEvent: ATTNEventRequestProvider { var metadata = [String: Any]() item.addItem(toDictionary: &metadata, with: priceFormatter) + let eventRequest = ATTNEventRequest( + metadata: metadata, + eventNameAbbreviation: ATTNEventTypes.productView + ) + if let deeplink { - metadata["pd"] = deeplink + eventRequest.deeplink = deeplink } - eventRequests.append(.init(metadata: metadata, eventNameAbbreviation: ATTNEventTypes.productView)) + eventRequests.append(eventRequest) } return eventRequests diff --git a/Sources/URLProviders/ATTNEventURLProvider.swift b/Sources/URLProviders/ATTNEventURLProvider.swift index d0dbd65..3072978 100644 --- a/Sources/URLProviders/ATTNEventURLProvider.swift +++ b/Sources/URLProviders/ATTNEventURLProvider.swift @@ -39,6 +39,10 @@ struct ATTNEventURLProvider: ATTNEventURLProviding { queryParams["m"] = try? ATTNJsonUtils.convertObjectToJson(combinedMetadata) ?? "{}" queryParams["t"] = eventRequest.eventNameAbbreviation + if let deeplink = eventRequest.deeplink { + queryParams["pd"] = deeplink + } + urlComponents.queryItems = queryParams.map { .init(name: $0.key, value: $0.value) } return urlComponents.url } diff --git a/Tests/TestCases/ATTNAddToCartEventTests.swift b/Tests/TestCases/ATTNAddToCartEventTests.swift index 80850f1..2d58e8d 100644 --- a/Tests/TestCases/ATTNAddToCartEventTests.swift +++ b/Tests/TestCases/ATTNAddToCartEventTests.swift @@ -13,7 +13,7 @@ final class ATTNAddToCartEventTests: XCTestCase { let item = ATTNTestEventUtils.buildItem() let addToCart = ATTNAddToCartEvent(items: [item]) XCTAssertFalse(addToCart.eventRequests.isEmpty) - XCTAssertNil(addToCart.eventRequests.first?.metadata["pd"]) + XCTAssertNil(addToCart.eventRequests.first?.deeplink) } func testAddCart_GivenData_ShouldBuildURLWithRequestURL() { @@ -21,7 +21,7 @@ final class ATTNAddToCartEventTests: XCTestCase { let addToCart = ATTNAddToCartEvent(items: [item]) addToCart.deeplink = "https://mydeeplink.com/products/32432423" XCTAssertFalse(addToCart.eventRequests.isEmpty) - let requestURL = addToCart.eventRequests.first?.metadata["pd"] as? String + let requestURL = addToCart.eventRequests.first?.deeplink as? String XCTAssertNotNil(requestURL) XCTAssertFalse(requestURL?.isEmpty ?? true) } diff --git a/Tests/TestCases/ATTNProductViewEventTests.swift b/Tests/TestCases/ATTNProductViewEventTests.swift index 3af1f1d..4050c40 100644 --- a/Tests/TestCases/ATTNProductViewEventTests.swift +++ b/Tests/TestCases/ATTNProductViewEventTests.swift @@ -15,14 +15,14 @@ final class ATTNProductViewEventTests: XCTestCase { let item = ATTNTestEventUtils.buildItem() let productView = ATTNProductViewEvent(items: [item]) XCTAssertFalse(productView.eventRequests.isEmpty) - XCTAssertNil(productView.eventRequests.first?.metadata["pd"]) + XCTAssertNil(productView.eventRequests.first?.deeplink) } func testProductView_GivenData_ShouldBuildURLWithRequestURL() { let item = ATTNTestEventUtils.buildItem() let productView = ATTNProductViewEvent(items: [item], deeplink: "https://mydeeplink.com/products/32432423") XCTAssertFalse(productView.eventRequests.isEmpty) - let requestURL = productView.eventRequests.first?.metadata["pd"] as? String + let requestURL = productView.eventRequests.first?.deeplink as? String XCTAssertNotNil(requestURL) XCTAssertFalse(requestURL?.isEmpty ?? true) } From a89a43917ec7b117d8a22b7374c6b31d34c35a49 Mon Sep 17 00:00:00 2001 From: Vladimir Espinola Date: Fri, 12 Jul 2024 13:31:41 -0400 Subject: [PATCH 18/25] cleaned up product screen --- Example/Example/ProductViewController.m | 1 - 1 file changed, 1 deletion(-) diff --git a/Example/Example/ProductViewController.m b/Example/Example/ProductViewController.m index 32cefa9..46ad054 100644 --- a/Example/Example/ProductViewController.m +++ b/Example/Example/ProductViewController.m @@ -29,7 +29,6 @@ - (void)viewDidAppear:(BOOL)animated { - (IBAction)addToCartButtonPressed:(id)sender { ATTNItem* item = [self buildItem]; ATTNAddToCartEvent* addToCart = [[ATTNAddToCartEvent alloc] initWithItems:@[ item ]]; - addToCart.deeplink = @"https://www.clientapp.com/flow=payment"; [[ATTNEventTracker sharedInstance] recordEvent:addToCart]; [self showToast:@"Add To Cart event sent" duration:2]; From b717c3b8dc367f506c618d8511eea732568744b2 Mon Sep 17 00:00:00 2001 From: Vladimir Espinola Date: Fri, 12 Jul 2024 13:48:35 -0400 Subject: [PATCH 19/25] adjusted product view layout --- Example/Base.lproj/Main.storyboard | 39 +++++++++++++----------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/Example/Base.lproj/Main.storyboard b/Example/Base.lproj/Main.storyboard index 85567ee..3c2c3e0 100644 --- a/Example/Base.lproj/Main.storyboard +++ b/Example/Base.lproj/Main.storyboard @@ -158,28 +158,27 @@ - + - - + + - + - - - - - - - + - + - - + +