Skip to content

Commit 20ad439

Browse files
committed
AdaptyUI: add openIn param to public Action.openURL
Public enum case is now openURL(url:in:) carrying AdaptyUIBuilder.WebPresentation (externalBrowser/inAppBrowser). Default handlers honor it via SFSafariViewController for inAppBrowser and UIApplication.open for externalBrowser.
1 parent 767e795 commit 20ad439

10 files changed

Lines changed: 134 additions & 12 deletions

Sources.AdaptyUI/AdaptyUI+DefaultConfiguration.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ public extension AdaptyPaywallControllerDelegate {
2424
switch action {
2525
case .close:
2626
controller.dismiss(animated: true)
27-
case let .openURL(url):
28-
UIApplication.shared.open(url, options: [:])
27+
case let .openURL(url, openIn):
28+
url.open(in: openIn.toBuilderWebPresentation)
2929
case .custom:
3030
break
3131
}

Sources.AdaptyUI/AdaptyUI+Public.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public extension AdaptyUI {
3636
/// User pressed Close Button
3737
case close
3838
/// User pressed any button with URL
39-
case openURL(url: URL)
39+
case openURL(url: URL, in: AdaptyWebPresentation)
4040
/// User pressed any button with custom action (e.g. login)
4141
case custom(id: String)
4242
}

Sources.AdaptyUI/Paywalls/Logic/AdaptyEventsHandler.swift

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,10 +144,30 @@ extension AdaptyUIBuilder.Action {
144144
var adaptyUIAction: AdaptyUI.Action {
145145
switch self {
146146
case .close: .close
147-
case let .openURL(url): .openURL(url: url)
147+
case let .openURL(url, openIn): .openURL(url: url, in: openIn.toAdaptyWebPresentation)
148148
case let .custom(id): .custom(id: id)
149149
}
150150
}
151151
}
152152

153+
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, visionOS 1.0, *)
154+
extension AdaptyUIBuilder.WebPresentation {
155+
var toAdaptyWebPresentation: AdaptyWebPresentation {
156+
switch self {
157+
case .externalBrowser: .externalBrowser
158+
case .inAppBrowser: .inAppBrowser
159+
}
160+
}
161+
}
162+
163+
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, visionOS 1.0, *)
164+
extension AdaptyWebPresentation {
165+
var toBuilderWebPresentation: AdaptyUIBuilder.WebPresentation {
166+
switch self {
167+
case .externalBrowser: .externalBrowser
168+
case .inAppBrowser: .inAppBrowser
169+
}
170+
}
171+
}
172+
153173
#endif

Sources.AdaptyUI/Paywalls/Rendering/AdaptyPaywallView.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ public struct AdaptyPaywallView<AlertItem>: View where AlertItem: Identifiable {
8181
switch action {
8282
case .close:
8383
presentationMode.wrappedValue.dismiss()
84-
case let .openURL(url):
85-
UIApplication.shared.open(url, options: [:])
84+
case let .openURL(url, openIn):
85+
url.open(in: openIn.toBuilderWebPresentation)
8686
case .custom:
8787
break
8888
}

Sources.UIBuilder/UIBuilder/AdaptyUIBuilder+Models.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,14 @@
1010
import Foundation
1111

1212
public extension AdaptyUIBuilder {
13+
enum WebPresentation: String, Sendable {
14+
case externalBrowser
15+
case inAppBrowser
16+
}
17+
1318
enum Action {
1419
case close
15-
case openURL(url: URL)
20+
case openURL(url: URL, in: WebPresentation)
1621
case custom(id: String)
1722
}
1823
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
//
2+
// AdaptyUIBuilder+OpenURL.swift
3+
// AdaptyUIBuilder
4+
//
5+
// Created by Alexey Goncharov on 06.05.2026.
6+
//
7+
8+
#if canImport(UIKit)
9+
10+
import Foundation
11+
import UIKit
12+
13+
#if canImport(SafariServices) && !os(visionOS)
14+
import SafariServices
15+
#endif
16+
17+
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, visionOS 1.0, *)
18+
@MainActor
19+
package extension URL {
20+
func open(in presentation: AdaptyUIBuilder.WebPresentation) {
21+
switch presentation {
22+
case .externalBrowser:
23+
UIApplication.shared.open(self, options: [:])
24+
case .inAppBrowser:
25+
#if canImport(SafariServices) && os(iOS)
26+
guard let topViewController = UIApplication.shared.topPresentedController else {
27+
UIApplication.shared.open(self, options: [:])
28+
return
29+
}
30+
topViewController.present(SFSafariViewController(url: self), animated: true)
31+
#else
32+
UIApplication.shared.open(self, options: [:])
33+
#endif
34+
}
35+
}
36+
}
37+
38+
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, visionOS 1.0, *)
39+
@MainActor
40+
private extension UIApplication {
41+
var topPresentedController: UIViewController? {
42+
let scene = connectedScenes
43+
.compactMap { $0 as? UIWindowScene }
44+
.first(where: { $0.activationState == .foregroundActive })
45+
46+
return scene?
47+
.windows
48+
.first(where: { $0.isKeyWindow })?
49+
.rootViewController?
50+
.topPresentedController()
51+
}
52+
}
53+
54+
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, visionOS 1.0, *)
55+
@MainActor
56+
private extension UIViewController {
57+
func topPresentedController() -> UIViewController {
58+
var top: UIViewController = self
59+
while let presented = top.presentedViewController {
60+
top = presented
61+
}
62+
return top.drillDownToChild()
63+
}
64+
65+
private func drillDownToChild() -> UIViewController {
66+
if let nav = self as? UINavigationController {
67+
return nav.visibleViewController?.drillDownToChild() ?? nav
68+
}
69+
if let tab = self as? UITabBarController {
70+
return tab.selectedViewController?.drillDownToChild() ?? tab
71+
}
72+
return self
73+
}
74+
}
75+
76+
#endif
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//
2+
// AdaptyUIBuilder+WebPresentation.swift
3+
// AdaptyUIBuilder
4+
//
5+
// Created by Alexey Goncharov on 06.05.2026.
6+
//
7+
8+
#if canImport(UIKit)
9+
10+
import Foundation
11+
12+
extension VC.WebOpenInParameter {
13+
var toWebPresentation: AdaptyUIBuilder.WebPresentation {
14+
switch self {
15+
case .browserOutApp: .externalBrowser
16+
case .browserInApp: .inAppBrowser
17+
}
18+
}
19+
}
20+
21+
#endif

Sources.UIBuilder/UIBuilder/Logic/AdaptyUIActionsViewModel.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,12 @@ package final class AdaptyUIActionsViewModel: ObservableObject {
2727
logic.reportDidPerformAction(.close)
2828
}
2929

30-
func openUrlActionOccurred(url urlString: String?) {
30+
func openUrlActionOccurred(url urlString: String?, openIn: VC.WebOpenInParameter) {
3131
guard let urlString, let url = URL(string: urlString) else {
3232
Log.ui.warn("#\(logId)# can't parse url: \(urlString ?? "null")")
3333
return
3434
}
35-
logic.reportDidPerformAction(.openURL(url: url))
35+
logic.reportDidPerformAction(.openURL(url: url, in: openIn.toWebPresentation))
3636
}
3737

3838
func customActionOccurred(id: String) {

Sources.UIBuilder/UIBuilder/Public/AdaptyUIBuilder+PaywallView.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,8 @@ public struct AdaptyUIPaywallView: View {
176176
switch action {
177177
case .close:
178178
presentationMode.wrappedValue.dismiss()
179-
case let .openURL(url):
180-
UIApplication.shared.open(url, options: [:])
179+
case let .openURL(url, openIn):
180+
url.open(in: openIn)
181181
case .custom:
182182
break
183183
}

Sources.UIBuilder/UIBuilder/Rendering/Elements/AdaptyUIButtonView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ extension VC.Action {
129129
case .close:
130130
actionsViewModel.closeActionOccurred()
131131
case let .openUrl(url, openIn):
132-
actionsViewModel.openUrlActionOccurred(url: url)
132+
actionsViewModel.openUrlActionOccurred(url: url, openIn: openIn)
133133
case let .custom(id):
134134
actionsViewModel.customActionOccurred(id: id)
135135
}

0 commit comments

Comments
 (0)