diff --git a/Example/Base.lproj/Main.storyboard b/Example/Base.lproj/Main.storyboard index 821d493..3c2c3e0 100644 --- a/Example/Base.lproj/Main.storyboard +++ b/Example/Base.lproj/Main.storyboard @@ -157,48 +157,78 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -217,9 +247,6 @@ - - - diff --git a/Example/Example/ProductViewController.m b/Example/Example/ProductViewController.m index 4e00b84..46ad054 100644 --- a/Example/Example/ProductViewController.m +++ b/Example/Example/ProductViewController.m @@ -13,20 +13,16 @@ @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"]; + [self showToast:@"Product View event sent" duration:2]; } @@ -35,7 +31,16 @@ - (IBAction)addToCartButtonPressed:(id)sender { ATTNAddToCartEvent* addToCart = [[ATTNAddToCartEvent alloc] initWithItems:@[ item ]]; [[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://mydeeplink.com/products/32432423"; + + [[ATTNEventTracker sharedInstance] recordEvent:addToCart]; + [self showToast: [NSString stringWithFormat:@"Add To Cart event sent with requestURL(pd): '%@'", addToCart.deeplink] duration:4]; } - (IBAction)purchaseButtonPressed:(id)sender { @@ -50,7 +55,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 +72,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/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 86678e5..bf32d18 100644 --- a/Sources/Helpers/Extension/ATTNAddToCartEvent+Extension.swift +++ b/Sources/Helpers/Extension/ATTNAddToCartEvent+Extension.swift @@ -19,7 +19,17 @@ extension ATTNAddToCartEvent: ATTNEventRequestProvider { for item in items { var metadata = [String: Any]() item.addItem(toDictionary: &metadata, with: priceFormatter) - eventRequests.append(.init(metadata: metadata, eventNameAbbreviation: ATTNEventTypes.addToCart)) + + let eventRequest = ATTNEventRequest( + metadata: metadata, + eventNameAbbreviation: ATTNEventTypes.addToCart + ) + + if let deeplink { + eventRequest.deeplink = deeplink + } + + eventRequests.append(eventRequest) } return eventRequests diff --git a/Sources/Helpers/Extension/ATTNProductViewEvent+Extension.swift b/Sources/Helpers/Extension/ATTNProductViewEvent+Extension.swift index c77c347..3ce0df7 100644 --- a/Sources/Helpers/Extension/ATTNProductViewEvent+Extension.swift +++ b/Sources/Helpers/Extension/ATTNProductViewEvent+Extension.swift @@ -19,7 +19,17 @@ extension ATTNProductViewEvent: ATTNEventRequestProvider { for item in items { var metadata = [String: Any]() item.addItem(toDictionary: &metadata, with: priceFormatter) - eventRequests.append(.init(metadata: metadata, eventNameAbbreviation: ATTNEventTypes.productView)) + + let eventRequest = ATTNEventRequest( + metadata: metadata, + eventNameAbbreviation: ATTNEventTypes.productView + ) + + if let deeplink { + eventRequest.deeplink = deeplink + } + + eventRequests.append(eventRequest) } return eventRequests diff --git a/Sources/Public/Events/ATTNAddToCartEvent.swift b/Sources/Public/Events/ATTNAddToCartEvent.swift index c72c0b1..181f454 100644 --- a/Sources/Public/Events/ATTNAddToCartEvent.swift +++ b/Sources/Public/Events/ATTNAddToCartEvent.swift @@ -10,10 +10,17 @@ 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]) { + @objc(initWithItems:) + 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() } @@ -21,3 +28,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..7ac0176 100644 --- a/Sources/Public/Events/ATTNProductViewEvent.swift +++ b/Sources/Public/Events/ATTNProductViewEvent.swift @@ -10,10 +10,17 @@ 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]) { + 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() } @@ -21,3 +28,5 @@ public final class ATTNProductViewEvent: NSObject, ATTNEvent { fatalError("init() has not been implemented") } } + +extension ATTNProductViewEvent: ATTNDeeplinkHandling { } 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 new file mode 100644 index 0000000..2d58e8d --- /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?.deeplink) + } + + func testAddCart_GivenData_ShouldBuildURLWithRequestURL() { + let item = ATTNTestEventUtils.buildItem() + let addToCart = ATTNAddToCartEvent(items: [item]) + addToCart.deeplink = "https://mydeeplink.com/products/32432423" + XCTAssertFalse(addToCart.eventRequests.isEmpty) + 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 new file mode 100644 index 0000000..4050c40 --- /dev/null +++ b/Tests/TestCases/ATTNProductViewEventTests.swift @@ -0,0 +1,29 @@ +// +// 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?.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?.deeplink 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 a8bf9b0..7b21641 100644 --- a/attentive-ios-sdk.xcodeproj/project.pbxproj +++ b/attentive-ios-sdk.xcodeproj/project.pbxproj @@ -52,6 +52,9 @@ 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 */; }; + 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 */; }; @@ -156,6 +159,9 @@ 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 = ""; }; + 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 = ""; }; @@ -335,6 +341,8 @@ FB2984072C0FAE040039759C /* ATTNAPITests.swift */, FB90EF0A2C109CB4004DFC4A /* ATTNAPIITTests.swift */, FB6553692C1B72D4008DB3B1 /* ATTNSDKTests.swift */, + FB86C03D2C40611A00E35580 /* ATTNAddToCartEventTests.swift */, + FB86C03F2C40669400E35580 /* ATTNProductViewEventTests.swift */, ); path = TestCases; sourceTree = ""; @@ -437,6 +445,7 @@ FBA9F9F72C0A77AB00C65024 /* ATTNPrice.swift */, FBA9F9F82C0A77AB00C65024 /* ATTNProductViewEvent.swift */, FBA9F9F92C0A77AB00C65024 /* ATTNPurchaseEvent.swift */, + FB84E3022C3F30FF0011936B /* ATTNDeeplinkHandling.swift */, ); path = Events; sourceTree = ""; @@ -680,6 +689,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 */, @@ -700,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 */,