Skip to content

Commit 276de2d

Browse files
committed
added delegate for passing parentViewController and enableAutoPopupPresentation flag
1 parent a0cdc6e commit 276de2d

File tree

9 files changed

+216
-27
lines changed

9 files changed

+216
-27
lines changed

REES46/Classes/InAppNotification/view/NotificationWidget.swift

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@ public class NotificationWidget: InAppNotificationProtocol {
44
private var parentViewController: UIViewController
55
private var snackbar: UIView?
66
private var popup: Popup?
7+
private var onDismiss: (() -> Void)?
78

89
public init(
910
parentViewController: UIViewController,
10-
popup: Popup? = nil
11+
popup: Popup? = nil,
12+
onDismiss: (() -> Void)? = nil
1113
) {
1214
self.parentViewController = parentViewController
1315
self.popup = popup
16+
self.onDismiss = onDismiss
1417

1518
if let popup = popup {
1619
showPopup(popup)
@@ -103,7 +106,8 @@ public class NotificationWidget: InAppNotificationProtocol {
103106
imageUrl: imageUrl,
104107
confirmButtonText: confirmButtonText,
105108
dismissButtonText: dismissButtonText,
106-
onConfirmButtonClick: onConfirmButtonClick
109+
onConfirmButtonClick: onConfirmButtonClick,
110+
onDismiss: onDismiss
107111
)
108112
let dialog = AlertDialog(viewModel: viewModel)
109113
dialog.modalPresentationStyle = .overFullScreen
@@ -124,7 +128,8 @@ public class NotificationWidget: InAppNotificationProtocol {
124128
imageUrl: imageUrl,
125129
confirmButtonText: confirmButtonText,
126130
dismissButtonText: dismissButtonText,
127-
onConfirmButtonClick: onConfirmButtonClick
131+
onConfirmButtonClick: onConfirmButtonClick,
132+
onDismiss: onDismiss
128133
)
129134
let dialog = BottomDialog(viewModel: viewModel)
130135
parentViewController.present(dialog, animated: true, completion: nil)
@@ -144,7 +149,8 @@ public class NotificationWidget: InAppNotificationProtocol {
144149
imageUrl: imageUrl,
145150
confirmButtonText: confirmButtonText,
146151
dismissButtonText: dismissButtonText,
147-
onConfirmButtonClick: onConfirmButtonClick
152+
onConfirmButtonClick: onConfirmButtonClick,
153+
onDismiss: onDismiss
148154
)
149155
let dialog = TopDialog(viewModel: viewModel)
150156
dialog.modalPresentationStyle = .overFullScreen

REES46/Classes/InAppNotification/view/component/dialog/base/BaseDialog.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,12 +132,16 @@ class BaseDialog: UIViewController {
132132
}
133133

134134
@objc internal func dismissDialog() {
135-
dismiss(animated: true, completion: nil)
135+
dismiss(animated: true, completion: { [weak self] in
136+
self?.viewModel.onDismiss?()
137+
})
136138
}
137139

138140
@objc func onConfirmButtonTapped() {
139141
viewModel.onConfirmButtonClick?()
140-
dismiss(animated: true, completion: nil)
142+
dismiss(animated: true, completion: { [weak self] in
143+
self?.viewModel.onDismiss?()
144+
})
141145
}
142146

143147
@objc func onDismissButtonTapped() {

REES46/Classes/InAppNotification/view/component/dialog/base/DialogViewModel.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public class DialogViewModel {
1414
buttonState == .noButtons
1515
}
1616
var onConfirmButtonClick: (() -> Void)?
17+
var onDismiss: (() -> Void)?
1718

1819
private var hasConfirmButton: Bool { confirmButtonText != nil }
1920
private var hasDismissButton: Bool { dismissButtonText != nil }
@@ -26,7 +27,8 @@ public class DialogViewModel {
2627
imageUrl: String,
2728
confirmButtonText: String?,
2829
dismissButtonText: String?,
29-
onConfirmButtonClick: (() -> Void)?
30+
onConfirmButtonClick: (() -> Void)?,
31+
onDismiss: (() -> Void)? = nil
3032
) {
3133
self.titleText = titleText
3234
self.messageText = messageText
@@ -35,6 +37,7 @@ public class DialogViewModel {
3537
self.dismissButtonText = dismissButtonText
3638
self.isImageContainerHidden = imageUrl.isEmpty
3739
self.onConfirmButtonClick = onConfirmButtonClick
40+
self.onDismiss = onDismiss
3841
}
3942

4043
func determineButtonState() -> ButtonState {

REES46/Classes/InAppNotification/view/component/dialog/types/TopDialog.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ class TopDialog: BaseDialog {
3434
UIView.animate(withDuration: AppDimensions.Animation.duration, animations: {
3535
self.view.frame.origin.y = -self.view.frame.height
3636
}, completion: { _ in
37-
self.dismiss(animated: false, completion: nil)
37+
self.dismiss(animated: false, completion: { [weak self] in
38+
self?.viewModel.onDismiss?()
39+
})
3840
})
3941
}
4042
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
//
2+
// PopupPresenter.swift
3+
// REES46
4+
//
5+
// Created by REES46
6+
// Copyright (c) 2023. All rights reserved.
7+
//
8+
9+
import UIKit
10+
11+
// MARK: - Popup Presenter Service
12+
13+
public class PopupPresenter {
14+
private weak var sdk: AnyObject? // Use AnyObject to avoid circular dependency
15+
private var currentPopup: NotificationWidget?
16+
private var popupQueue: [Popup] = []
17+
private let serialQueue = DispatchQueue(label: "com.rees46.popup.presenter")
18+
19+
public init(sdk: AnyObject) {
20+
self.sdk = sdk
21+
}
22+
23+
// MARK: - Public Interface
24+
25+
/// Main entry point - call this to present any popup
26+
public func presentPopup(_ popup: Popup) {
27+
serialQueue.async { [weak self] in
28+
guard let self = self else { return }
29+
30+
if self.currentPopup != nil {
31+
// Queue popup if one is already showing
32+
self.popupQueue.append(popup)
33+
} else {
34+
self.showPopupNow(popup)
35+
}
36+
}
37+
}
38+
39+
/// Dismiss the current popup and show the next one in queue
40+
public func dismissCurrentPopup() {
41+
DispatchQueue.main.async { [weak self] in
42+
guard let self = self else { return }
43+
self.currentPopup = nil
44+
45+
// Present next queued popup if any
46+
self.serialQueue.async {
47+
if let nextPopup = self.popupQueue.first {
48+
self.popupQueue.removeFirst()
49+
self.showPopupNow(nextPopup)
50+
}
51+
}
52+
}
53+
}
54+
55+
// MARK: - Private Methods
56+
57+
private func showPopupNow(_ popup: Popup) {
58+
guard let presentingVC = getPresentingViewController(for: popup) else {
59+
return // No VC available or delegate prevented presentation
60+
}
61+
62+
DispatchQueue.main.async { [weak self] in
63+
guard let self = self else { return }
64+
self.currentPopup = NotificationWidget(
65+
parentViewController: presentingVC,
66+
popup: popup,
67+
onDismiss: { [weak self] in
68+
self?.dismissCurrentPopup()
69+
}
70+
)
71+
}
72+
}
73+
74+
private func getPresentingViewController(for popup: Popup) -> UIViewController? {
75+
// Fallback chain:
76+
77+
// 1. Check delegate first (if set, delegate has full control)
78+
if let sdk = sdk as? PersonalizationSDK,
79+
let delegate = sdk.popupPresentationDelegate {
80+
return delegate.sdk(sdk, shouldPresentPopup: popup)
81+
}
82+
83+
// 2. Check if auto-presentation is enabled
84+
guard let sdk = sdk as? PersonalizationSDK,
85+
sdk.enableAutoPopupPresentation == true else {
86+
return nil // Auto-presentation disabled, no delegate = no presentation
87+
}
88+
89+
// 3. Check parentViewController (backward compatibility)
90+
if let parentVC = sdk.parentViewController {
91+
return parentVC
92+
}
93+
94+
// 4. Auto-discover from window hierarchy (must be on main thread)
95+
var topVC: UIViewController?
96+
DispatchQueue.main.sync {
97+
topVC = getTopViewController()
98+
}
99+
return topVC
100+
}
101+
102+
private func getTopViewController() -> UIViewController? {
103+
// Get key window's root view controller
104+
if #available(iOS 13.0, *) {
105+
// iOS 13+ approach using UIWindowScene
106+
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
107+
let window = windowScene.windows.first(where: { $0.isKeyWindow }),
108+
let rootViewController = window.rootViewController else {
109+
return nil
110+
}
111+
return findTopViewController(from: rootViewController)
112+
} else {
113+
// iOS 12 and earlier approach using UIApplication.shared.keyWindow
114+
guard let window = UIApplication.shared.keyWindow,
115+
let rootViewController = window.rootViewController else {
116+
return nil
117+
}
118+
return findTopViewController(from: rootViewController)
119+
}
120+
}
121+
122+
private func findTopViewController(from viewController: UIViewController) -> UIViewController {
123+
// Traverse to find the topmost presented view controller
124+
if let presented = viewController.presentedViewController {
125+
return findTopViewController(from: presented)
126+
}
127+
128+
if let navigationController = viewController as? UINavigationController {
129+
if let visible = navigationController.visibleViewController {
130+
return findTopViewController(from: visible)
131+
}
132+
}
133+
134+
if let tabBarController = viewController as? UITabBarController {
135+
if let selected = tabBarController.selectedViewController {
136+
return findTopViewController(from: selected)
137+
}
138+
}
139+
140+
return viewController
141+
}
142+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//
2+
// PopupPresentationDelegate.swift
3+
// REES46
4+
//
5+
// Created by REES46
6+
// Copyright (c) 2023. All rights reserved.
7+
//
8+
9+
import UIKit
10+
11+
// MARK: - Popup Presentation Delegate Protocol
12+
13+
public protocol PopupPresentationDelegate: AnyObject {
14+
/// Called when SDK has a popup to present
15+
/// Return presenting UIViewController to allow SDK to present, or nil to prevent presentation
16+
func sdk(_ sdk: PersonalizationSDK, shouldPresentPopup popup: Popup) -> UIViewController?
17+
}

REES46/Classes/Sdk/impl/SimplePersonalizationSDK.swift

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,17 @@ import AppTrackingTransparency
55

66
public var global_EL: Bool = true
77

8+
// MARK: - SimplePersonalizationSDK
9+
810
class SimplePersonalizationSDK: PersonalizationSDK {
911
private var global_EL: Bool = false
1012

1113
var parentViewController: UIViewController?
1214
var notificationWidget: NotificationWidget?
1315

16+
weak var popupPresentationDelegate: PopupPresentationDelegate?
17+
var enableAutoPopupPresentation: Bool
18+
1419
var storiesCode: String?
1520
var shopId: String
1621
var deviceId: String
@@ -74,6 +79,10 @@ class SimplePersonalizationSDK: PersonalizationSDK {
7479
)
7580
}()
7681

82+
lazy var popupPresenter: PopupPresenter = {
83+
return PopupPresenter(sdk: self)
84+
}()
85+
7786
init(
7887
shopId: String,
7988
userEmail: String? = nil,
@@ -84,13 +93,15 @@ class SimplePersonalizationSDK: PersonalizationSDK {
8493
enableLogs: Bool = false,
8594
autoSendPushToken: Bool = true,
8695
sendAdvertisingId: Bool = false,
87-
parentViewController: UIViewController?,
96+
parentViewController: UIViewController? = nil,
97+
enableAutoPopupPresentation: Bool = true,
8898
needReInitialization: Bool = false,
8999
completion: ((SdkError?) -> Void)? = nil
90100
) {
91101
self.shopId = shopId
92102
self.autoSendPushToken = autoSendPushToken
93103
self.parentViewController = parentViewController
104+
self.enableAutoPopupPresentation = enableAutoPopupPresentation
94105

95106
global_EL = enableLogs
96107
self.baseURL = "https://" + apiDomain + "/"
@@ -118,13 +129,8 @@ class SimplePersonalizationSDK: PersonalizationSDK {
118129
self.userSeance = response.seance
119130
self.deviceId = response.deviceId
120131

121-
if let popup = response.popup, let parentViewController {
122-
DispatchQueue.main.async {
123-
self.notificationWidget = NotificationWidget(
124-
parentViewController: parentViewController,
125-
popup: popup
126-
)
127-
}
132+
if let popup = response.popup {
133+
self.popupPresenter.presentPopup(popup)
128134
}
129135

130136
// Handle push token if autoSendPushToken is true
@@ -162,9 +168,12 @@ class SimplePersonalizationSDK: PersonalizationSDK {
162168
return deviceId
163169
}
164170

171+
@available(*, deprecated, message: "Use enableAutoPopupPresentation or popupPresentationDelegate instead")
165172
func setParentViewController(controller: UIViewController, completion: @escaping () -> Void) {
166173
self.parentViewController = controller
167-
completion()
174+
DispatchQueue.main.async {
175+
completion()
176+
}
168177
}
169178

170179
func getNotificationWidget() -> NotificationWidget? {

REES46/Classes/Sdk/protocol/PersonalizationSDK.protocol.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import Foundation
1010
import UIKit
1111

12+
// MARK: - Main SDK Protocol
13+
1214
public protocol PersonalizationSDK {
1315
var shopId: String { get }
1416
var deviceId: String { get }
@@ -19,6 +21,11 @@ public protocol PersonalizationSDK {
1921
var parentViewController: UIViewController? {get}
2022
var urlSession: URLSession { get set }
2123

24+
// New popup presentation properties
25+
var popupPresentationDelegate: PopupPresentationDelegate? { get set }
26+
var enableAutoPopupPresentation: Bool { get set }
27+
var popupPresenter: PopupPresenter { get }
28+
2229
func postRequest(path: String, params: [String: Any], completion: @escaping (Result<[String: Any], SdkError>) -> Void)
2330
func getRequest(path: String, params: [String: String], _ isInit: Bool, completion: @escaping (Result<[String: Any], SdkError>) -> Void)
2431
func configureURLSession(configuration: URLSessionConfiguration)
@@ -34,6 +41,7 @@ public protocol PersonalizationSDK {
3441
func getProductsFromCart(completion: @escaping(Result<[CartItem], SdkError>) -> Void)
3542
func getProductInfo(id: String, completion: @escaping(Result<ProductInfo, SdkError>) -> Void)
3643
func getDeviceId() -> String
44+
@available(*, deprecated, message: "Use enableAutoPopupPresentation or popupPresentationDelegate instead")
3745
func setParentViewController(controller: UIViewController, completion: @escaping () -> Void)
3846
func getNotificationWidget() -> NotificationWidget?
3947
func getSession() -> String
@@ -320,6 +328,7 @@ public func createPersonalizationSDK(
320328
autoSendPushToken: Bool = true,
321329
sendAdvertisingId: Bool = false,
322330
parentViewController: UIViewController? = nil,
331+
enableAutoPopupPresentation: Bool = true,
323332
needReInitialization: Bool = false,
324333
_ completion: ((SdkError?) -> Void)? = nil
325334
) -> PersonalizationSDK {
@@ -334,6 +343,7 @@ public func createPersonalizationSDK(
334343
autoSendPushToken: autoSendPushToken,
335344
sendAdvertisingId: sendAdvertisingId,
336345
parentViewController: parentViewController,
346+
enableAutoPopupPresentation: enableAutoPopupPresentation,
337347
needReInitialization: needReInitialization,
338348
completion: completion
339349
)

0 commit comments

Comments
 (0)