diff --git a/DemoApp/REES46Demo/MainViewController.swift b/DemoApp/REES46Demo/MainViewController.swift index 76adb536..e5f76b16 100644 --- a/DemoApp/REES46Demo/MainViewController.swift +++ b/DemoApp/REES46Demo/MainViewController.swift @@ -25,6 +25,7 @@ class MainViewController: UIViewController, UIScrollViewDelegate { @IBOutlet private weak var resetDidButton: UIButton! @IBOutlet private weak var showStoriesButton: UIButton! @IBOutlet private weak var showSnackBarButton: UIButton! + private var showTestPopupButton: UIButton! public var waitIndicator: SdkActivityIndicator! @@ -188,6 +189,48 @@ class MainViewController: UIViewController, UIScrollViewDelegate { storiesCollectionView.showStories() } + @objc + private func showTestPopup() { + guard let sdk = globalSDK else { + return + } + + let componentsDict: [String: Any] = [ + "header": "Test Popup", + "text": "This is a test popup for iOS SDK" + ] + + let componentsJSON: String + if let componentsData = try? JSONSerialization.data(withJSONObject: componentsDict), + let componentsString = String(data: componentsData, encoding: .utf8) { + componentsJSON = componentsString + } else { + componentsJSON = "{}" + } + + let testPopupData: [String: Any] = [ + "id": 999, + "channels": ["email"], + "position": "centered", + "delay": 0, + "html": """ +
This is a test popup for iOS SDK
+ """, + "components": componentsJSON, + "web_push_system": false, + "popup_actions": "{}" + ] + + let testPopup = Popup(json: testPopupData) + + sdk.popupPresenter.dismissCurrentPopup() + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + sdk.popupPresenter.presentPopup(testPopup) + } + } + func setupSdkDemoAppViews() { navigationController?.navigationBar.isHidden = true scrollView.contentSize = CGSize(width: UIScreen.main.bounds.size.width, height: 2000) @@ -199,6 +242,8 @@ class MainViewController: UIViewController, UIScrollViewDelegate { resetDidButton.addTarget(self, action: #selector(didTapReset), for: .touchUpInside) showStoriesButton.addTarget(self, action: #selector(showStories), for: .touchUpInside) + setupTestPopupButton() + fontInterPreload() DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { self.setupSdkLabels() @@ -231,6 +276,24 @@ class MainViewController: UIViewController, UIScrollViewDelegate { self.waitIndicator.hideIndicatorWhenStopped = true } + func setupTestPopupButton() { + showTestPopupButton = DemoShopButton(type: .system) + showTestPopupButton.setTitle("Show Test Popup", for: .normal) + showTestPopupButton.setTitleColor(.white, for: .normal) + showTestPopupButton.translatesAutoresizingMaskIntoConstraints = false + scrollView.addSubview(showTestPopupButton) + + // Place button next to other test buttons + NSLayoutConstraint.activate([ + showTestPopupButton.topAnchor.constraint(equalTo: showStoriesButton.bottomAnchor, constant: 10), + showTestPopupButton.leadingAnchor.constraint(equalTo: showStoriesButton.leadingAnchor), + showTestPopupButton.widthAnchor.constraint(equalTo: showStoriesButton.widthAnchor), + showTestPopupButton.heightAnchor.constraint(equalTo: showStoriesButton.heightAnchor) + ]) + + showTestPopupButton.addTarget(self, action: #selector(showTestPopup), for: .touchUpInside) + } + func fontInterPreload() { fcmTokenLabel.font = SdkDynamicFont.dynamicFont(textStyle: .headline, weight: .bold) pushTokenLabel .font = SdkDynamicFont.dynamicFont(textStyle: .headline, weight: .bold) diff --git a/DemoApp/Rees46DemoTests/SearchServiceTests.swift b/DemoApp/Rees46DemoTests/SearchServiceTests.swift index 2662c1d3..370a1108 100644 --- a/DemoApp/Rees46DemoTests/SearchServiceTests.swift +++ b/DemoApp/Rees46DemoTests/SearchServiceTests.swift @@ -6,17 +6,11 @@ class SearchServiceImplTests: XCTestCase { var sdk: PersonalizationSDK! var shopId: String { - guard let value = ProcessInfo.processInfo.environment[Constants.testShopIdKey] else { - fatalError("Environment variable TEST_SHOP_ID is not set") - } - return value + return ProcessInfo.processInfo.environment[Constants.testShopIdKey] ?? Constants.testShopId } var apiDomain: String { - guard let value = ProcessInfo.processInfo.environment[Constants.testApiUrlKey] else { - fatalError("Environment variable TEST_API_URL is not set") - } - return value + return ProcessInfo.processInfo.environment[Constants.testApiUrlKey] ?? Constants.testApiDomain } override func setUp() { diff --git a/REES46/Classes/Presentation/PopupPresenter.swift b/REES46/Classes/Presentation/PopupPresenter.swift index c8870d5e..1a52602f 100644 --- a/REES46/Classes/Presentation/PopupPresenter.swift +++ b/REES46/Classes/Presentation/PopupPresenter.swift @@ -15,6 +15,7 @@ public class PopupPresenter { private var currentPopup: NotificationWidget? private var popupQueue: [Popup] = [] private let serialQueue = DispatchQueue(label: "com.rees46.popup.presenter") + private var popupShownFlags: [Int: Date] = [:] public init(sdk: AnyObject) { self.sdk = sdk @@ -55,6 +56,14 @@ public class PopupPresenter { // MARK: - Private Methods private func showPopupNow(_ popup: Popup) { + // Check if popup was shown in the last 60 seconds + if let shownDate = popupShownFlags[popup.id] { + let timeSinceShown = Date().timeIntervalSince(shownDate) + if timeSinceShown < 60 { + return // Popup was already shown, skip + } + } + guard let presentingVC = getPresentingViewController(for: popup) else { return // No VC available or delegate prevented presentation } @@ -68,6 +77,21 @@ public class PopupPresenter { self?.dismissCurrentPopup() } ) + + // Store popup shown flag in memory for 60 seconds + self.popupShownFlags[popup.id] = Date() + + // Remove flag after 60 seconds + DispatchQueue.main.asyncAfter(deadline: .now() + 60) { [weak self] in + self?.popupShownFlags.removeValue(forKey: popup.id) + } + + // Send popup shown event to server + if let sdk = self.sdk as? PersonalizationSDK { + sdk.trackPopupShown(popupId: popup.id) { _ in + // Handle result (log if needed) + } + } } } diff --git a/REES46/Classes/Sdk/Extensions/Popup.extension.swift b/REES46/Classes/Sdk/Extensions/Popup.extension.swift index ee517ff2..3d771fad 100644 --- a/REES46/Classes/Sdk/Extensions/Popup.extension.swift +++ b/REES46/Classes/Sdk/Extensions/Popup.extension.swift @@ -1,6 +1,6 @@ import Foundation extension Popup { - init(json: [String: Any]) { + public init(json: [String: Any]) { self.id = json["id"] as? Int ?? 0 self.channels = json["channels"] as? [String] ?? [] self.position = json["position"] as? String ?? "" @@ -17,7 +17,7 @@ extension Popup { } } - func extractTitleAndSubtitle() -> (title: String?, subTitle: String?) { + public func extractTitleAndSubtitle() -> (title: String?, subTitle: String?) { let title = RegexHelper.extract( using: RegexPattern.title, from: html diff --git a/REES46/Classes/Sdk/Model/Popup.struct.swift b/REES46/Classes/Sdk/Model/Popup.struct.swift index 699d804f..19a2ab34 100644 --- a/REES46/Classes/Sdk/Model/Popup.struct.swift +++ b/REES46/Classes/Sdk/Model/Popup.struct.swift @@ -1,22 +1,22 @@ import Foundation public struct Popup: Codable { - enum Position: String { + public enum Position: String { case centered = "centered" case bottom = "fixed_bottom" case top = "top" } - let id: Int - let channels: [String] - let position: String - let delay: Int - let html: String - let components: PopupComponents? - let web_push_system: Bool - let popup_actions: String + public let id: Int + public let channels: [String] + public let position: String + public let delay: Int + public let html: String + public let components: PopupComponents? + public let web_push_system: Bool + public let popup_actions: String - func getParsedPopupActions() -> PopupActions? { + public func getParsedPopupActions() -> PopupActions? { guard let data = popup_actions.data(using: .utf8) else { return nil } let decoder = JSONDecoder() do { diff --git a/REES46/Classes/Sdk/impl/SimplePersonalizationSDK.swift b/REES46/Classes/Sdk/impl/SimplePersonalizationSDK.swift index 9f7318ae..67c6c21d 100644 --- a/REES46/Classes/Sdk/impl/SimplePersonalizationSDK.swift +++ b/REES46/Classes/Sdk/impl/SimplePersonalizationSDK.swift @@ -408,6 +408,10 @@ class SimplePersonalizationSDK: PersonalizationSDK { trackEventService.trackEvent(event: event, category: category, label: label, value: value, completion: completion) } + func trackPopupShown(popupId: Int, completion: @escaping (Result