@capgo/inappbrowser 8.0.0 → 8.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CapgoInappbrowser.podspec +2 -2
- package/LICENSE +373 -21
- package/Package.swift +28 -0
- package/README.md +600 -74
- package/android/build.gradle +17 -16
- package/android/src/main/AndroidManifest.xml +14 -2
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/InAppBrowserPlugin.java +952 -204
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/Options.java +478 -81
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewCallbacks.java +10 -4
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewDialog.java +3023 -226
- package/android/src/main/res/drawable/ic_refresh.xml +9 -0
- package/android/src/main/res/drawable/ic_share.xml +10 -0
- package/android/src/main/res/layout/activity_browser.xml +10 -0
- package/android/src/main/res/layout/content_browser.xml +3 -2
- package/android/src/main/res/layout/tool_bar.xml +44 -7
- package/android/src/main/res/values/strings.xml +4 -0
- package/android/src/main/res/values/themes.xml +27 -0
- package/android/src/main/res/xml/file_paths.xml +14 -0
- package/dist/docs.json +1289 -149
- package/dist/esm/definitions.d.ts +614 -25
- package/dist/esm/definitions.js +17 -1
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.js +4 -4
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/web.d.ts +16 -3
- package/dist/esm/web.js +43 -7
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +60 -8
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +60 -8
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/InAppBrowserPlugin/InAppBrowserPlugin.swift +952 -0
- package/ios/Sources/InAppBrowserPlugin/WKWebViewController.swift +2006 -0
- package/package.json +30 -30
- package/ios/Plugin/Assets.xcassets/Back.imageset/Back.png +0 -0
- package/ios/Plugin/Assets.xcassets/Back.imageset/Back@2x.png +0 -0
- package/ios/Plugin/Assets.xcassets/Back.imageset/Back@3x.png +0 -0
- package/ios/Plugin/Assets.xcassets/Back.imageset/Contents.json +0 -26
- package/ios/Plugin/Assets.xcassets/Contents.json +0 -6
- package/ios/Plugin/Assets.xcassets/Forward.imageset/Contents.json +0 -26
- package/ios/Plugin/Assets.xcassets/Forward.imageset/Forward.png +0 -0
- package/ios/Plugin/Assets.xcassets/Forward.imageset/Forward@2x.png +0 -0
- package/ios/Plugin/Assets.xcassets/Forward.imageset/Forward@3x.png +0 -0
- package/ios/Plugin/InAppBrowserPlugin.h +0 -10
- package/ios/Plugin/InAppBrowserPlugin.m +0 -17
- package/ios/Plugin/InAppBrowserPlugin.swift +0 -203
- package/ios/Plugin/Info.plist +0 -24
- package/ios/Plugin/WKWebViewController.swift +0 -784
- /package/ios/{Plugin → Sources/InAppBrowserPlugin}/Enums.swift +0 -0
|
@@ -0,0 +1,952 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Capacitor
|
|
3
|
+
import WebKit
|
|
4
|
+
|
|
5
|
+
extension UIColor {
|
|
6
|
+
|
|
7
|
+
convenience init(hexString: String) {
|
|
8
|
+
let hex = hexString.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
|
|
9
|
+
var int = UInt64()
|
|
10
|
+
Scanner(string: hex).scanHexInt64(&int)
|
|
11
|
+
let components = (
|
|
12
|
+
R: CGFloat((int >> 16) & 0xff) / 255,
|
|
13
|
+
G: CGFloat((int >> 08) & 0xff) / 255,
|
|
14
|
+
B: CGFloat((int >> 00) & 0xff) / 255
|
|
15
|
+
)
|
|
16
|
+
self.init(red: components.R, green: components.G, blue: components.B, alpha: 1)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Please read the Capacitor iOS Plugin Development Guide
|
|
23
|
+
* here: https://capacitorjs.com/docs/plugins/ios
|
|
24
|
+
*/
|
|
25
|
+
@objc(InAppBrowserPlugin)
|
|
26
|
+
public class InAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
27
|
+
private let pluginVersion: String = "8.0.1"
|
|
28
|
+
public let identifier = "InAppBrowserPlugin"
|
|
29
|
+
public let jsName = "InAppBrowser"
|
|
30
|
+
public let pluginMethods: [CAPPluginMethod] = [
|
|
31
|
+
CAPPluginMethod(name: "goBack", returnType: CAPPluginReturnPromise),
|
|
32
|
+
CAPPluginMethod(name: "open", returnType: CAPPluginReturnPromise),
|
|
33
|
+
CAPPluginMethod(name: "openWebView", returnType: CAPPluginReturnPromise),
|
|
34
|
+
CAPPluginMethod(name: "clearCookies", returnType: CAPPluginReturnPromise),
|
|
35
|
+
CAPPluginMethod(name: "getCookies", returnType: CAPPluginReturnPromise),
|
|
36
|
+
CAPPluginMethod(name: "clearAllCookies", returnType: CAPPluginReturnPromise),
|
|
37
|
+
CAPPluginMethod(name: "clearCache", returnType: CAPPluginReturnPromise),
|
|
38
|
+
CAPPluginMethod(name: "reload", returnType: CAPPluginReturnPromise),
|
|
39
|
+
CAPPluginMethod(name: "setUrl", returnType: CAPPluginReturnPromise),
|
|
40
|
+
CAPPluginMethod(name: "show", returnType: CAPPluginReturnPromise),
|
|
41
|
+
CAPPluginMethod(name: "close", returnType: CAPPluginReturnPromise),
|
|
42
|
+
CAPPluginMethod(name: "executeScript", returnType: CAPPluginReturnPromise),
|
|
43
|
+
CAPPluginMethod(name: "postMessage", returnType: CAPPluginReturnPromise),
|
|
44
|
+
CAPPluginMethod(name: "updateDimensions", returnType: CAPPluginReturnPromise),
|
|
45
|
+
CAPPluginMethod(name: "getPluginVersion", returnType: CAPPluginReturnPromise)
|
|
46
|
+
]
|
|
47
|
+
var navigationWebViewController: UINavigationController?
|
|
48
|
+
private var privacyScreen: UIImageView?
|
|
49
|
+
private var isSetupDone = false
|
|
50
|
+
var currentPluginCall: CAPPluginCall?
|
|
51
|
+
var isPresentAfterPageLoad = false
|
|
52
|
+
var webViewController: WKWebViewController?
|
|
53
|
+
private var closeModalTitle: String?
|
|
54
|
+
private var closeModalDescription: String?
|
|
55
|
+
private var closeModalOk: String?
|
|
56
|
+
private var closeModalCancel: String?
|
|
57
|
+
|
|
58
|
+
private func setup() {
|
|
59
|
+
self.isSetupDone = true
|
|
60
|
+
|
|
61
|
+
#if swift(>=4.2)
|
|
62
|
+
NotificationCenter.default.addObserver(self, selector: #selector(appDidBecomeActive(_:)), name: UIApplication.didBecomeActiveNotification, object: nil)
|
|
63
|
+
NotificationCenter.default.addObserver(self, selector: #selector(appWillResignActive(_:)), name: UIApplication.willResignActiveNotification, object: nil)
|
|
64
|
+
#else
|
|
65
|
+
NotificationCenter.default.addObserver(self, selector: #selector(appDidBecomeActive(_:)), name: .UIApplicationDidBecomeActive, object: nil)
|
|
66
|
+
NotificationCenter.default.addObserver(self, selector: #selector(appWillResignActive(_:)), name: .UIApplicationWillResignActive, object: nil)
|
|
67
|
+
#endif
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
func presentView(isAnimated: Bool = true) {
|
|
71
|
+
guard let navigationController = self.navigationWebViewController else {
|
|
72
|
+
self.currentPluginCall?.reject("Navigation controller is not initialized")
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
self.bridge?.viewController?.present(navigationController, animated: isAnimated, completion: {
|
|
77
|
+
self.currentPluginCall?.resolve()
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@objc func clearAllCookies(_ call: CAPPluginCall) {
|
|
82
|
+
DispatchQueue.main.async {
|
|
83
|
+
let dataStore = WKWebsiteDataStore.default()
|
|
84
|
+
let dataTypes = Set([WKWebsiteDataTypeCookies])
|
|
85
|
+
|
|
86
|
+
dataStore.removeData(ofTypes: dataTypes,
|
|
87
|
+
modifiedSince: Date(timeIntervalSince1970: 0)) {
|
|
88
|
+
call.resolve()
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
@objc func clearCache(_ call: CAPPluginCall) {
|
|
94
|
+
DispatchQueue.main.async {
|
|
95
|
+
let dataStore = WKWebsiteDataStore.default()
|
|
96
|
+
let dataTypes = Set([WKWebsiteDataTypeDiskCache, WKWebsiteDataTypeMemoryCache])
|
|
97
|
+
|
|
98
|
+
dataStore.removeData(ofTypes: dataTypes,
|
|
99
|
+
modifiedSince: Date(timeIntervalSince1970: 0)) {
|
|
100
|
+
call.resolve()
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
@objc func clearCookies(_ call: CAPPluginCall) {
|
|
106
|
+
guard let url = call.getString("url"),
|
|
107
|
+
let host = URL(string: url)?.host else {
|
|
108
|
+
call.reject("Invalid URL")
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
DispatchQueue.main.async {
|
|
113
|
+
WKWebsiteDataStore.default().httpCookieStore.getAllCookies { cookies in
|
|
114
|
+
let group = DispatchGroup()
|
|
115
|
+
for cookie in cookies {
|
|
116
|
+
if cookie.domain == host || cookie.domain.hasSuffix(".\(host)") || host.hasSuffix(cookie.domain) {
|
|
117
|
+
group.enter()
|
|
118
|
+
WKWebsiteDataStore.default().httpCookieStore.delete(cookie) {
|
|
119
|
+
group.leave()
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
group.notify(queue: .main) {
|
|
125
|
+
call.resolve()
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
@objc func getCookies(_ call: CAPPluginCall) {
|
|
132
|
+
let urlString = call.getString("url") ?? ""
|
|
133
|
+
let includeHttpOnly = call.getBool("includeHttpOnly") ?? true
|
|
134
|
+
|
|
135
|
+
guard let url = URL(string: urlString), let host = url.host else {
|
|
136
|
+
call.reject("Invalid URL")
|
|
137
|
+
return
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
DispatchQueue.main.async {
|
|
141
|
+
WKWebsiteDataStore.default().httpCookieStore.getAllCookies { cookies in
|
|
142
|
+
var cookieDict = [String: String]()
|
|
143
|
+
for cookie in cookies {
|
|
144
|
+
|
|
145
|
+
if (includeHttpOnly || !cookie.isHTTPOnly) && (cookie.domain == host || cookie.domain.hasSuffix(".\(host)") || host.hasSuffix(cookie.domain)) {
|
|
146
|
+
cookieDict[cookie.name] = cookie.value
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
call.resolve(cookieDict)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
@objc func openWebView(_ call: CAPPluginCall) {
|
|
156
|
+
if !self.isSetupDone {
|
|
157
|
+
self.setup()
|
|
158
|
+
}
|
|
159
|
+
self.currentPluginCall = call
|
|
160
|
+
|
|
161
|
+
guard let urlString = call.getString("url") else {
|
|
162
|
+
call.reject("Must provide a URL to open")
|
|
163
|
+
return
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if urlString.isEmpty {
|
|
167
|
+
call.reject("URL must not be empty")
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
var buttonNearDoneIcon: UIImage?
|
|
172
|
+
if let buttonNearDoneSettings = call.getObject("buttonNearDone") {
|
|
173
|
+
guard let iosSettingsRaw = buttonNearDoneSettings["ios"] else {
|
|
174
|
+
call.reject("IOS settings not found")
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
if !(iosSettingsRaw is JSObject) {
|
|
178
|
+
call.reject("IOS settings are not an object")
|
|
179
|
+
return
|
|
180
|
+
}
|
|
181
|
+
let iosSettings = iosSettingsRaw as! JSObject
|
|
182
|
+
|
|
183
|
+
guard let iconType = iosSettings["iconType"] as? String else {
|
|
184
|
+
call.reject("buttonNearDone.iconType is empty")
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
if iconType != "sf-symbol" && iconType != "asset" {
|
|
188
|
+
call.reject("IconType is neither 'sf-symbol' nor 'asset'")
|
|
189
|
+
return
|
|
190
|
+
}
|
|
191
|
+
guard let icon = iosSettings["icon"] as? String else {
|
|
192
|
+
call.reject("buttonNearDone.icon is empty")
|
|
193
|
+
return
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if iconType == "sf-symbol" {
|
|
197
|
+
buttonNearDoneIcon = UIImage(systemName: icon)?.withRenderingMode(.alwaysTemplate)
|
|
198
|
+
print("[DEBUG] Set buttonNearDone SF Symbol icon: \(icon)")
|
|
199
|
+
} else {
|
|
200
|
+
// Look in app's web assets/public directory
|
|
201
|
+
guard let webDir = Bundle.main.resourceURL?.appendingPathComponent("public") else {
|
|
202
|
+
print("[DEBUG] Failed to locate web assets directory")
|
|
203
|
+
return
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Try several path combinations to find the asset
|
|
207
|
+
let paths = [
|
|
208
|
+
icon, // Just the icon name
|
|
209
|
+
"public/\(icon)", // With public/ prefix
|
|
210
|
+
icon.replacingOccurrences(of: "public/", with: "") // Without public/ prefix
|
|
211
|
+
]
|
|
212
|
+
|
|
213
|
+
var foundImage = false
|
|
214
|
+
|
|
215
|
+
for path in paths {
|
|
216
|
+
// Try as a direct path from web assets dir
|
|
217
|
+
let assetPath = path.replacingOccurrences(of: "public/", with: "")
|
|
218
|
+
let fileURL = webDir.appendingPathComponent(assetPath)
|
|
219
|
+
|
|
220
|
+
print("[DEBUG] Trying to load from: \(fileURL.path)")
|
|
221
|
+
|
|
222
|
+
if FileManager.default.fileExists(atPath: fileURL.path),
|
|
223
|
+
let data = try? Data(contentsOf: fileURL),
|
|
224
|
+
let img = UIImage(data: data) {
|
|
225
|
+
buttonNearDoneIcon = img.withRenderingMode(.alwaysTemplate)
|
|
226
|
+
print("[DEBUG] Successfully loaded buttonNearDone from web assets: \(fileURL.path)")
|
|
227
|
+
foundImage = true
|
|
228
|
+
break
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Try with www directory as an alternative
|
|
232
|
+
if let wwwDir = Bundle.main.resourceURL?.appendingPathComponent("www") {
|
|
233
|
+
let wwwFileURL = wwwDir.appendingPathComponent(assetPath)
|
|
234
|
+
|
|
235
|
+
print("[DEBUG] Trying to load from www dir: \(wwwFileURL.path)")
|
|
236
|
+
|
|
237
|
+
if FileManager.default.fileExists(atPath: wwwFileURL.path),
|
|
238
|
+
let data = try? Data(contentsOf: wwwFileURL),
|
|
239
|
+
let img = UIImage(data: data) {
|
|
240
|
+
buttonNearDoneIcon = img.withRenderingMode(.alwaysTemplate)
|
|
241
|
+
print("[DEBUG] Successfully loaded buttonNearDone from www dir: \(wwwFileURL.path)")
|
|
242
|
+
foundImage = true
|
|
243
|
+
break
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Try looking in app bundle assets
|
|
248
|
+
if let iconImage = UIImage(named: path) {
|
|
249
|
+
buttonNearDoneIcon = iconImage.withRenderingMode(.alwaysTemplate)
|
|
250
|
+
print("[DEBUG] Successfully loaded buttonNearDone from app bundle: \(path)")
|
|
251
|
+
foundImage = true
|
|
252
|
+
break
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if !foundImage {
|
|
257
|
+
print("[DEBUG] Failed to load buttonNearDone icon: \(icon)")
|
|
258
|
+
|
|
259
|
+
// Debug info
|
|
260
|
+
if let resourceURL = Bundle.main.resourceURL {
|
|
261
|
+
print("[DEBUG] Resource URL: \(resourceURL.path)")
|
|
262
|
+
|
|
263
|
+
// List directories to help debugging
|
|
264
|
+
do {
|
|
265
|
+
let contents = try FileManager.default.contentsOfDirectory(atPath: resourceURL.path)
|
|
266
|
+
print("[DEBUG] Root bundle contents: \(contents)")
|
|
267
|
+
|
|
268
|
+
// Check if public or www directories exist
|
|
269
|
+
if contents.contains("public") {
|
|
270
|
+
let publicContents = try FileManager.default.contentsOfDirectory(
|
|
271
|
+
atPath: resourceURL.appendingPathComponent("public").path)
|
|
272
|
+
print("[DEBUG] Public dir contents: \(publicContents)")
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if contents.contains("www") {
|
|
276
|
+
let wwwContents = try FileManager.default.contentsOfDirectory(
|
|
277
|
+
atPath: resourceURL.appendingPathComponent("www").path)
|
|
278
|
+
print("[DEBUG] WWW dir contents: \(wwwContents)")
|
|
279
|
+
}
|
|
280
|
+
} catch {
|
|
281
|
+
print("[DEBUG] Error listing directories: \(error)")
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
let headers = call.getObject("headers", [:]).mapValues { String(describing: $0 as Any) }
|
|
289
|
+
let closeModal = call.getBool("closeModal", false)
|
|
290
|
+
let closeModalTitle = call.getString("closeModalTitle", "Close")
|
|
291
|
+
let closeModalDescription = call.getString("closeModalDescription", "Are you sure you want to close this window?")
|
|
292
|
+
let closeModalOk = call.getString("closeModalOk", "OK")
|
|
293
|
+
let closeModalCancel = call.getString("closeModalCancel", "Cancel")
|
|
294
|
+
let isInspectable = call.getBool("isInspectable", false)
|
|
295
|
+
let preventDeeplink = call.getBool("preventDeeplink", false)
|
|
296
|
+
let isAnimated = call.getBool("isAnimated", true)
|
|
297
|
+
let enabledSafeBottomMargin = call.getBool("enabledSafeBottomMargin", false)
|
|
298
|
+
|
|
299
|
+
// Validate preShowScript requires isPresentAfterPageLoad
|
|
300
|
+
if call.getString("preShowScript") != nil && !call.getBool("isPresentAfterPageLoad", false) {
|
|
301
|
+
call.reject("preShowScript requires isPresentAfterPageLoad to be true")
|
|
302
|
+
return
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Validate closeModal options
|
|
306
|
+
if closeModal {
|
|
307
|
+
if call.getString("closeModalTitle") != nil ||
|
|
308
|
+
call.getString("closeModalDescription") != nil ||
|
|
309
|
+
call.getString("closeModalOk") != nil ||
|
|
310
|
+
call.getString("closeModalCancel") != nil {
|
|
311
|
+
// Store the values to be set after proper initialization
|
|
312
|
+
self.closeModalTitle = closeModalTitle
|
|
313
|
+
self.closeModalDescription = closeModalDescription
|
|
314
|
+
self.closeModalOk = closeModalOk
|
|
315
|
+
self.closeModalCancel = closeModalCancel
|
|
316
|
+
}
|
|
317
|
+
} else {
|
|
318
|
+
// Reject if closeModal is false but closeModal options are provided
|
|
319
|
+
if call.getString("closeModalTitle") != nil ||
|
|
320
|
+
call.getString("closeModalDescription") != nil ||
|
|
321
|
+
call.getString("closeModalOk") != nil ||
|
|
322
|
+
call.getString("closeModalCancel") != nil {
|
|
323
|
+
call.reject("closeModal options require closeModal to be true")
|
|
324
|
+
return
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Validate shareDisclaimer requires shareSubject
|
|
329
|
+
if call.getString("shareSubject") == nil && call.getObject("shareDisclaimer") != nil {
|
|
330
|
+
call.reject("shareDisclaimer requires shareSubject to be provided")
|
|
331
|
+
return
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Validate buttonNearDone compatibility with toolbar type
|
|
335
|
+
if call.getString("buttonNearDone") != nil {
|
|
336
|
+
let toolbarType = call.getString("toolbarType", "")
|
|
337
|
+
if toolbarType == "activity" || toolbarType == "navigation" || toolbarType == "blank" {
|
|
338
|
+
call.reject("buttonNearDone is not compatible with toolbarType: " + toolbarType)
|
|
339
|
+
return
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
var disclaimerContent: JSObject?
|
|
344
|
+
if let shareDisclaimerRaw = call.getObject("shareDisclaimer"), !shareDisclaimerRaw.isEmpty {
|
|
345
|
+
disclaimerContent = shareDisclaimerRaw
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
let toolbarType = call.getString("toolbarType", "")
|
|
349
|
+
let backgroundColor = call.getString("backgroundColor", "black") == "white" ? UIColor.white : UIColor.black
|
|
350
|
+
|
|
351
|
+
// Don't null out shareDisclaimer regardless of toolbarType
|
|
352
|
+
// if toolbarType != "activity" {
|
|
353
|
+
// disclaimerContent = nil
|
|
354
|
+
// }
|
|
355
|
+
|
|
356
|
+
let ignoreUntrustedSSLError = call.getBool("ignoreUntrustedSSLError", false)
|
|
357
|
+
let enableGooglePaySupport = call.getBool("enableGooglePaySupport", false)
|
|
358
|
+
let activeNativeNavigationForWebview = call.getBool("activeNativeNavigationForWebview", true)
|
|
359
|
+
|
|
360
|
+
self.isPresentAfterPageLoad = call.getBool("isPresentAfterPageLoad", false)
|
|
361
|
+
let showReloadButton = call.getBool("showReloadButton", false)
|
|
362
|
+
|
|
363
|
+
let blockedHostsRaw = call.getArray("blockedHosts", [])
|
|
364
|
+
let blockedHosts = blockedHostsRaw.compactMap { $0 as? String }
|
|
365
|
+
|
|
366
|
+
let authorizedAppLinksRaw = call.getArray("authorizedAppLinks", [])
|
|
367
|
+
let authorizedAppLinks = authorizedAppLinksRaw.compactMap { $0 as? String }
|
|
368
|
+
|
|
369
|
+
let credentials = self.readCredentials(call)
|
|
370
|
+
|
|
371
|
+
// Read dimension options
|
|
372
|
+
let width = call.getFloat("width")
|
|
373
|
+
let height = call.getFloat("height")
|
|
374
|
+
let x = call.getFloat("x")
|
|
375
|
+
let y = call.getFloat("y")
|
|
376
|
+
|
|
377
|
+
// Validate dimension parameters
|
|
378
|
+
if width != nil && height == nil {
|
|
379
|
+
call.reject("Height must be specified when width is provided")
|
|
380
|
+
return
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
DispatchQueue.main.async {
|
|
384
|
+
guard let url = URL(string: urlString) else {
|
|
385
|
+
call.reject("Invalid URL format")
|
|
386
|
+
return
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
self.webViewController = WKWebViewController.init(
|
|
390
|
+
url: url,
|
|
391
|
+
headers: headers,
|
|
392
|
+
isInspectable: isInspectable,
|
|
393
|
+
credentials: credentials,
|
|
394
|
+
preventDeeplink: preventDeeplink,
|
|
395
|
+
blankNavigationTab: toolbarType == "blank",
|
|
396
|
+
enabledSafeBottomMargin: enabledSafeBottomMargin,
|
|
397
|
+
blockedHosts: blockedHosts,
|
|
398
|
+
authorizedAppLinks: authorizedAppLinks,
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
guard let webViewController = self.webViewController else {
|
|
402
|
+
call.reject("Failed to initialize WebViewController")
|
|
403
|
+
return
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Set dimensions if provided
|
|
407
|
+
if let width = width {
|
|
408
|
+
webViewController.customWidth = CGFloat(width)
|
|
409
|
+
}
|
|
410
|
+
if let height = height {
|
|
411
|
+
webViewController.customHeight = CGFloat(height)
|
|
412
|
+
}
|
|
413
|
+
if let x = x {
|
|
414
|
+
webViewController.customX = CGFloat(x)
|
|
415
|
+
}
|
|
416
|
+
if let y = y {
|
|
417
|
+
webViewController.customY = CGFloat(y)
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Set native navigation gestures before view loads
|
|
421
|
+
webViewController.activeNativeNavigationForWebview = activeNativeNavigationForWebview
|
|
422
|
+
|
|
423
|
+
// Update the webview's gesture property (if webview already exists)
|
|
424
|
+
webViewController.updateNavigationGestures()
|
|
425
|
+
|
|
426
|
+
if self.bridge?.statusBarVisible == true {
|
|
427
|
+
let subviews = self.bridge?.webView?.superview?.subviews
|
|
428
|
+
if let emptyStatusBarIndex = subviews?.firstIndex(where: { $0.subviews.isEmpty }) {
|
|
429
|
+
if let emptyStatusBar = subviews?[emptyStatusBarIndex] {
|
|
430
|
+
webViewController.capacitorStatusBar = emptyStatusBar
|
|
431
|
+
emptyStatusBar.removeFromSuperview()
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
webViewController.source = .remote(url)
|
|
437
|
+
webViewController.leftNavigationBarItemTypes = []
|
|
438
|
+
|
|
439
|
+
// Configure close button based on showArrow
|
|
440
|
+
let showArrow = call.getBool("showArrow", false)
|
|
441
|
+
if showArrow {
|
|
442
|
+
// When showArrow is true, put arrow on left
|
|
443
|
+
webViewController.doneBarButtonItemPosition = .left
|
|
444
|
+
webViewController.showArrowAsClose = true
|
|
445
|
+
} else {
|
|
446
|
+
// Default X on right
|
|
447
|
+
webViewController.doneBarButtonItemPosition = toolbarType == "activity" ? .none : .right
|
|
448
|
+
webViewController.showArrowAsClose = false
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Configure navigation buttons based on toolbarType
|
|
452
|
+
if toolbarType == "activity" {
|
|
453
|
+
// Activity mode should ONLY have:
|
|
454
|
+
// 1. Close button (if not hidden by doneBarButtonItemPosition)
|
|
455
|
+
// 2. Share button (if shareSubject is provided)
|
|
456
|
+
webViewController.leftNavigationBarItemTypes = [] // Clear any left items
|
|
457
|
+
webViewController.rightNavigaionBarItemTypes = [] // Clear any right items
|
|
458
|
+
|
|
459
|
+
// Only add share button if subject is provided
|
|
460
|
+
if call.getString("shareSubject") != nil {
|
|
461
|
+
// Add share button to right bar
|
|
462
|
+
webViewController.rightNavigaionBarItemTypes.append(.activity)
|
|
463
|
+
print("[DEBUG] Activity mode: Added share button, shareSubject: \(call.getString("shareSubject") ?? "nil")")
|
|
464
|
+
} else {
|
|
465
|
+
// In activity mode, always make the share button visible by setting a default shareSubject
|
|
466
|
+
webViewController.shareSubject = "Share"
|
|
467
|
+
webViewController.rightNavigaionBarItemTypes.append(.activity)
|
|
468
|
+
print("[DEBUG] Activity mode: Setting default shareSubject")
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Set done button position based on showArrow
|
|
472
|
+
if showArrow {
|
|
473
|
+
webViewController.doneBarButtonItemPosition = .left
|
|
474
|
+
} else {
|
|
475
|
+
// In activity mode, keep the done button visible even when showArrow is false
|
|
476
|
+
webViewController.doneBarButtonItemPosition = .right
|
|
477
|
+
}
|
|
478
|
+
} else if toolbarType == "navigation" {
|
|
479
|
+
// Navigation mode puts back/forward on the left
|
|
480
|
+
webViewController.leftNavigationBarItemTypes = [.back, .forward]
|
|
481
|
+
if showReloadButton {
|
|
482
|
+
webViewController.leftNavigationBarItemTypes.append(.reload)
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Only add share button if subject is provided
|
|
486
|
+
if call.getString("shareSubject") != nil {
|
|
487
|
+
// Add share button to right navigation bar
|
|
488
|
+
webViewController.rightNavigaionBarItemTypes.append(.activity)
|
|
489
|
+
}
|
|
490
|
+
} else {
|
|
491
|
+
// Other modes may have reload button
|
|
492
|
+
if showReloadButton {
|
|
493
|
+
webViewController.leftNavigationBarItemTypes.append(.reload)
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Only add share button if subject is provided
|
|
497
|
+
if call.getString("shareSubject") != nil {
|
|
498
|
+
// Add share button to right navigation bar
|
|
499
|
+
webViewController.rightNavigaionBarItemTypes.append(.activity)
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Set buttonNearDoneIcon if provided
|
|
504
|
+
if let buttonNearDoneIcon = buttonNearDoneIcon {
|
|
505
|
+
webViewController.buttonNearDoneIcon = buttonNearDoneIcon
|
|
506
|
+
print("[DEBUG] Button near done icon set: \(buttonNearDoneIcon)")
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
webViewController.capBrowserPlugin = self
|
|
510
|
+
webViewController.title = call.getString("title", "New Window")
|
|
511
|
+
// Only set shareSubject if not already set for activity mode
|
|
512
|
+
if webViewController.shareSubject == nil {
|
|
513
|
+
webViewController.shareSubject = call.getString("shareSubject")
|
|
514
|
+
}
|
|
515
|
+
webViewController.shareDisclaimer = disclaimerContent
|
|
516
|
+
|
|
517
|
+
// Debug shareDisclaimer
|
|
518
|
+
if let disclaimer = disclaimerContent {
|
|
519
|
+
print("[DEBUG] Share disclaimer set: \(disclaimer)")
|
|
520
|
+
} else {
|
|
521
|
+
print("[DEBUG] No share disclaimer set")
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
webViewController.preShowScript = call.getString("preShowScript")
|
|
525
|
+
webViewController.preShowScriptInjectionTime = call.getString("preShowScriptInjectionTime", "pageLoad")
|
|
526
|
+
|
|
527
|
+
// If script should be injected at document start, inject it now
|
|
528
|
+
if webViewController.preShowScriptInjectionTime == "documentStart" {
|
|
529
|
+
webViewController.injectPreShowScriptAtDocumentStart()
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
webViewController.websiteTitleInNavigationBar = call.getBool("visibleTitle", true)
|
|
533
|
+
webViewController.ignoreUntrustedSSLError = ignoreUntrustedSSLError
|
|
534
|
+
|
|
535
|
+
// Set Google Pay support
|
|
536
|
+
webViewController.enableGooglePaySupport = enableGooglePaySupport
|
|
537
|
+
|
|
538
|
+
// Set text zoom if specified
|
|
539
|
+
if let textZoom = call.getInt("textZoom") {
|
|
540
|
+
webViewController.textZoom = textZoom
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Set closeModal properties after proper initialization
|
|
544
|
+
if closeModal {
|
|
545
|
+
webViewController.closeModal = true
|
|
546
|
+
webViewController.closeModalTitle = self.closeModalTitle ?? closeModalTitle
|
|
547
|
+
webViewController.closeModalDescription = self.closeModalDescription ?? closeModalDescription
|
|
548
|
+
webViewController.closeModalOk = self.closeModalOk ?? closeModalOk
|
|
549
|
+
webViewController.closeModalCancel = self.closeModalCancel ?? closeModalCancel
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
self.navigationWebViewController = UINavigationController.init(rootViewController: webViewController)
|
|
553
|
+
self.navigationWebViewController?.navigationBar.isTranslucent = false
|
|
554
|
+
self.navigationWebViewController?.toolbar.isTranslucent = false
|
|
555
|
+
|
|
556
|
+
// Ensure no lines or borders appear by default
|
|
557
|
+
self.navigationWebViewController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
|
|
558
|
+
self.navigationWebViewController?.navigationBar.shadowImage = UIImage()
|
|
559
|
+
self.navigationWebViewController?.navigationBar.setValue(true, forKey: "hidesShadow")
|
|
560
|
+
self.navigationWebViewController?.toolbar.setShadowImage(UIImage(), forToolbarPosition: .any)
|
|
561
|
+
|
|
562
|
+
// Handle web view background color
|
|
563
|
+
webViewController.view.backgroundColor = backgroundColor
|
|
564
|
+
|
|
565
|
+
// Handle toolbar color
|
|
566
|
+
if let toolbarColor = call.getString("toolbarColor"), self.isHexColorCode(toolbarColor) {
|
|
567
|
+
// If specific color provided, use it
|
|
568
|
+
let color = UIColor(hexString: toolbarColor)
|
|
569
|
+
|
|
570
|
+
// Apply to status bar and navigation bar area with a single colored view
|
|
571
|
+
webViewController.setupStatusBarBackground(color: color)
|
|
572
|
+
|
|
573
|
+
// Set status bar style based on toolbar color
|
|
574
|
+
let isDark = self.isDarkColor(color)
|
|
575
|
+
webViewController.statusBarStyle = isDark ? .lightContent : .darkContent
|
|
576
|
+
webViewController.updateStatusBarStyle()
|
|
577
|
+
|
|
578
|
+
// Apply text color
|
|
579
|
+
let textColor: UIColor
|
|
580
|
+
if let toolbarTextColor = call.getString("toolbarTextColor"), self.isHexColorCode(toolbarTextColor) {
|
|
581
|
+
textColor = UIColor(hexString: toolbarTextColor)
|
|
582
|
+
} else {
|
|
583
|
+
textColor = isDark ? UIColor.white : UIColor.black
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// Apply tint color to all UI elements without changing background
|
|
587
|
+
self.navigationWebViewController?.navigationBar.tintColor = textColor
|
|
588
|
+
webViewController.tintColor = textColor
|
|
589
|
+
self.navigationWebViewController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: textColor]
|
|
590
|
+
} else {
|
|
591
|
+
// Use system appearance
|
|
592
|
+
let isDarkMode = UITraitCollection.current.userInterfaceStyle == .dark
|
|
593
|
+
let backgroundColor = isDarkMode ? UIColor.black : UIColor.white
|
|
594
|
+
let textColor: UIColor
|
|
595
|
+
|
|
596
|
+
if let toolbarTextColor = call.getString("toolbarTextColor"), self.isHexColorCode(toolbarTextColor) {
|
|
597
|
+
textColor = UIColor(hexString: toolbarTextColor)
|
|
598
|
+
} else {
|
|
599
|
+
textColor = isDarkMode ? UIColor.white : UIColor.black
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// Apply colors
|
|
603
|
+
webViewController.setupStatusBarBackground(color: backgroundColor)
|
|
604
|
+
webViewController.tintColor = textColor
|
|
605
|
+
self.navigationWebViewController?.navigationBar.tintColor = textColor
|
|
606
|
+
self.navigationWebViewController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: textColor]
|
|
607
|
+
webViewController.statusBarStyle = isDarkMode ? .lightContent : .darkContent
|
|
608
|
+
webViewController.updateStatusBarStyle()
|
|
609
|
+
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// Configure modal presentation for touch passthrough if custom dimensions are set
|
|
613
|
+
if width != nil || height != nil {
|
|
614
|
+
self.navigationWebViewController?.modalPresentationStyle = .overFullScreen
|
|
615
|
+
|
|
616
|
+
// Create a pass-through container
|
|
617
|
+
let containerView = PassThroughView()
|
|
618
|
+
containerView.backgroundColor = .clear
|
|
619
|
+
|
|
620
|
+
// Calculate dimensions - use screen width if only height is provided
|
|
621
|
+
let finalWidth = width != nil ? CGFloat(width!) : UIScreen.main.bounds.width
|
|
622
|
+
let finalHeight = height != nil ? CGFloat(height!) : UIScreen.main.bounds.height
|
|
623
|
+
|
|
624
|
+
containerView.targetFrame = CGRect(
|
|
625
|
+
x: CGFloat(x ?? 0),
|
|
626
|
+
y: CGFloat(y ?? 0),
|
|
627
|
+
width: finalWidth,
|
|
628
|
+
height: finalHeight
|
|
629
|
+
)
|
|
630
|
+
|
|
631
|
+
// Replace the navigation controller's view with our pass-through container
|
|
632
|
+
if let navController = self.navigationWebViewController {
|
|
633
|
+
let originalView = navController.view!
|
|
634
|
+
navController.view = containerView
|
|
635
|
+
containerView.addSubview(originalView)
|
|
636
|
+
originalView.frame = CGRect(
|
|
637
|
+
x: CGFloat(x ?? 0),
|
|
638
|
+
y: CGFloat(y ?? 0),
|
|
639
|
+
width: finalWidth,
|
|
640
|
+
height: finalHeight
|
|
641
|
+
)
|
|
642
|
+
}
|
|
643
|
+
} else {
|
|
644
|
+
self.navigationWebViewController?.modalPresentationStyle = .overCurrentContext
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
self.navigationWebViewController?.modalTransitionStyle = .crossDissolve
|
|
648
|
+
if toolbarType == "blank" {
|
|
649
|
+
self.navigationWebViewController?.navigationBar.isHidden = true
|
|
650
|
+
webViewController.blankNavigationTab = true
|
|
651
|
+
|
|
652
|
+
// Even with hidden navigation bar, we need to set proper status bar appearance
|
|
653
|
+
// If toolbarColor is explicitly set, use that for status bar style
|
|
654
|
+
if let toolbarColor = call.getString("toolbarColor"), self.isHexColorCode(toolbarColor) {
|
|
655
|
+
let color = UIColor(hexString: toolbarColor)
|
|
656
|
+
let isDark = self.isDarkColor(color)
|
|
657
|
+
webViewController.statusBarStyle = isDark ? .lightContent : .darkContent
|
|
658
|
+
webViewController.updateStatusBarStyle()
|
|
659
|
+
|
|
660
|
+
// Apply status bar background color via the special view
|
|
661
|
+
webViewController.setupStatusBarBackground(color: color)
|
|
662
|
+
|
|
663
|
+
// Apply background color to whole view to ensure no gaps
|
|
664
|
+
webViewController.view.backgroundColor = color
|
|
665
|
+
self.navigationWebViewController?.view.backgroundColor = color
|
|
666
|
+
|
|
667
|
+
// Apply status bar background color
|
|
668
|
+
if let navController = self.navigationWebViewController {
|
|
669
|
+
navController.view.backgroundColor = color
|
|
670
|
+
}
|
|
671
|
+
} else {
|
|
672
|
+
// Follow system appearance if no specific color
|
|
673
|
+
let isDarkMode = UITraitCollection.current.userInterfaceStyle == .dark
|
|
674
|
+
let backgroundColor = isDarkMode ? UIColor.black : UIColor.white
|
|
675
|
+
webViewController.statusBarStyle = isDarkMode ? .lightContent : .darkContent
|
|
676
|
+
webViewController.updateStatusBarStyle()
|
|
677
|
+
|
|
678
|
+
// Apply status bar background color via the special view
|
|
679
|
+
webViewController.setupStatusBarBackground(color: backgroundColor)
|
|
680
|
+
|
|
681
|
+
// Set appropriate background color
|
|
682
|
+
if let navController = self.navigationWebViewController {
|
|
683
|
+
navController.view.backgroundColor = backgroundColor
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// We don't use the toolbar anymore, always hide it
|
|
690
|
+
self.navigationWebViewController?.setToolbarHidden(true, animated: false)
|
|
691
|
+
|
|
692
|
+
if !self.isPresentAfterPageLoad {
|
|
693
|
+
self.presentView(isAnimated: isAnimated)
|
|
694
|
+
}
|
|
695
|
+
call.resolve()
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
@objc func goBack(_ call: CAPPluginCall) {
|
|
700
|
+
DispatchQueue.main.async {
|
|
701
|
+
guard let webViewController = self.webViewController else {
|
|
702
|
+
call.resolve(["canGoBack": false])
|
|
703
|
+
return
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
let canGoBack = webViewController.goBack()
|
|
707
|
+
call.resolve(["canGoBack": canGoBack])
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
@objc func reload(_ call: CAPPluginCall) {
|
|
712
|
+
self.webViewController?.reload()
|
|
713
|
+
call.resolve()
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
@objc func setUrl(_ call: CAPPluginCall) {
|
|
717
|
+
guard let urlString = call.getString("url") else {
|
|
718
|
+
call.reject("Cannot get new url to set")
|
|
719
|
+
return
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
guard let url = URL(string: urlString) else {
|
|
723
|
+
call.reject("Invalid URL")
|
|
724
|
+
return
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
self.webViewController?.load(remote: url)
|
|
728
|
+
call.resolve()
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
@objc func executeScript(_ call: CAPPluginCall) {
|
|
732
|
+
guard let script = call.getString("code") else {
|
|
733
|
+
call.reject("Cannot get script to execute")
|
|
734
|
+
return
|
|
735
|
+
}
|
|
736
|
+
DispatchQueue.main.async {
|
|
737
|
+
self.webViewController?.executeScript(script: script)
|
|
738
|
+
call.resolve()
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
@objc func postMessage(_ call: CAPPluginCall) {
|
|
743
|
+
let eventData = call.getObject("detail", [:])
|
|
744
|
+
// Check if eventData is empty
|
|
745
|
+
if eventData.isEmpty {
|
|
746
|
+
call.reject("Event data must not be empty")
|
|
747
|
+
return
|
|
748
|
+
}
|
|
749
|
+
print("Event data: \(eventData)")
|
|
750
|
+
|
|
751
|
+
DispatchQueue.main.async {
|
|
752
|
+
self.webViewController?.postMessageToJS(message: eventData)
|
|
753
|
+
}
|
|
754
|
+
call.resolve()
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
func isHexColorCode(_ input: String) -> Bool {
|
|
758
|
+
let hexColorRegex = "^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$"
|
|
759
|
+
|
|
760
|
+
do {
|
|
761
|
+
let regex = try NSRegularExpression(pattern: hexColorRegex)
|
|
762
|
+
let range = NSRange(location: 0, length: input.utf16.count)
|
|
763
|
+
if let _ = regex.firstMatch(in: input, options: [], range: range) {
|
|
764
|
+
return true
|
|
765
|
+
}
|
|
766
|
+
} catch {
|
|
767
|
+
print("Error creating regular expression: \(error)")
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
return false
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
@objc func open(_ call: CAPPluginCall) {
|
|
774
|
+
if !self.isSetupDone {
|
|
775
|
+
self.setup()
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
let isInspectable = call.getBool("isInspectable", false)
|
|
779
|
+
let preventDeeplink = call.getBool("preventDeeplink", false)
|
|
780
|
+
self.isPresentAfterPageLoad = call.getBool("isPresentAfterPageLoad", false)
|
|
781
|
+
|
|
782
|
+
self.currentPluginCall = call
|
|
783
|
+
|
|
784
|
+
guard let urlString = call.getString("url") else {
|
|
785
|
+
call.reject("Must provide a URL to open")
|
|
786
|
+
return
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
if urlString.isEmpty {
|
|
790
|
+
call.reject("URL must not be empty")
|
|
791
|
+
return
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
let headers = call.getObject("headers", [:]).mapValues { String(describing: $0 as Any) }
|
|
795
|
+
let credentials = self.readCredentials(call)
|
|
796
|
+
|
|
797
|
+
DispatchQueue.main.async {
|
|
798
|
+
guard let url = URL(string: urlString) else {
|
|
799
|
+
call.reject("Invalid URL format")
|
|
800
|
+
return
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
self.webViewController = WKWebViewController.init(url: url, headers: headers, isInspectable: isInspectable, credentials: credentials, preventDeeplink: preventDeeplink, blankNavigationTab: true, enabledSafeBottomMargin: false)
|
|
804
|
+
|
|
805
|
+
guard let webViewController = self.webViewController else {
|
|
806
|
+
call.reject("Failed to initialize WebViewController")
|
|
807
|
+
return
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
if self.bridge?.statusBarVisible == true {
|
|
811
|
+
let subviews = self.bridge?.webView?.superview?.subviews
|
|
812
|
+
if let emptyStatusBarIndex = subviews?.firstIndex(where: { $0.subviews.isEmpty }) {
|
|
813
|
+
if let emptyStatusBar = subviews?[emptyStatusBarIndex] {
|
|
814
|
+
webViewController.capacitorStatusBar = emptyStatusBar
|
|
815
|
+
emptyStatusBar.removeFromSuperview()
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
webViewController.source = .remote(url)
|
|
821
|
+
webViewController.leftNavigationBarItemTypes = [.back, .forward, .reload]
|
|
822
|
+
webViewController.capBrowserPlugin = self
|
|
823
|
+
webViewController.hasDynamicTitle = true
|
|
824
|
+
|
|
825
|
+
self.navigationWebViewController = UINavigationController.init(rootViewController: webViewController)
|
|
826
|
+
self.navigationWebViewController?.navigationBar.isTranslucent = false
|
|
827
|
+
|
|
828
|
+
// Ensure no lines or borders appear by default
|
|
829
|
+
self.navigationWebViewController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
|
|
830
|
+
self.navigationWebViewController?.navigationBar.shadowImage = UIImage()
|
|
831
|
+
self.navigationWebViewController?.navigationBar.setValue(true, forKey: "hidesShadow")
|
|
832
|
+
|
|
833
|
+
// Use system appearance
|
|
834
|
+
let isDarkMode = UITraitCollection.current.userInterfaceStyle == .dark
|
|
835
|
+
let backgroundColor = isDarkMode ? UIColor.black : UIColor.white
|
|
836
|
+
let textColor = isDarkMode ? UIColor.white : UIColor.black
|
|
837
|
+
|
|
838
|
+
// Apply colors
|
|
839
|
+
webViewController.setupStatusBarBackground(color: backgroundColor)
|
|
840
|
+
webViewController.tintColor = textColor
|
|
841
|
+
self.navigationWebViewController?.navigationBar.tintColor = textColor
|
|
842
|
+
self.navigationWebViewController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: textColor]
|
|
843
|
+
webViewController.statusBarStyle = isDarkMode ? .lightContent : .darkContent
|
|
844
|
+
webViewController.updateStatusBarStyle()
|
|
845
|
+
|
|
846
|
+
// Always hide toolbar to ensure no bottom bar
|
|
847
|
+
self.navigationWebViewController?.setToolbarHidden(true, animated: false)
|
|
848
|
+
|
|
849
|
+
self.navigationWebViewController?.modalPresentationStyle = .overCurrentContext
|
|
850
|
+
self.navigationWebViewController?.modalTransitionStyle = .crossDissolve
|
|
851
|
+
|
|
852
|
+
if !self.isPresentAfterPageLoad {
|
|
853
|
+
self.presentView()
|
|
854
|
+
}
|
|
855
|
+
call.resolve()
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
@objc func close(_ call: CAPPluginCall) {
|
|
860
|
+
let isAnimated = call.getBool("isAnimated", true)
|
|
861
|
+
|
|
862
|
+
DispatchQueue.main.async {
|
|
863
|
+
let currentUrl = self.webViewController?.url?.absoluteString ?? ""
|
|
864
|
+
|
|
865
|
+
self.webViewController?.cleanupWebView()
|
|
866
|
+
|
|
867
|
+
self.navigationWebViewController?.dismiss(animated: isAnimated) {
|
|
868
|
+
self.webViewController = nil
|
|
869
|
+
self.navigationWebViewController = nil
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
self.notifyListeners("closeEvent", data: ["url": currentUrl])
|
|
873
|
+
call.resolve()
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
private func showPrivacyScreen() {
|
|
878
|
+
if privacyScreen == nil {
|
|
879
|
+
self.privacyScreen = UIImageView()
|
|
880
|
+
if let launchImage = UIImage(named: "LaunchImage") {
|
|
881
|
+
privacyScreen!.image = launchImage
|
|
882
|
+
privacyScreen!.frame = UIScreen.main.bounds
|
|
883
|
+
privacyScreen!.contentMode = .scaleAspectFill
|
|
884
|
+
privacyScreen!.isUserInteractionEnabled = false
|
|
885
|
+
} else if let launchImage = UIImage(named: "Splash") {
|
|
886
|
+
privacyScreen!.image = launchImage
|
|
887
|
+
privacyScreen!.frame = UIScreen.main.bounds
|
|
888
|
+
privacyScreen!.contentMode = .scaleAspectFill
|
|
889
|
+
privacyScreen!.isUserInteractionEnabled = false
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
self.navigationWebViewController?.view.addSubview(self.privacyScreen!)
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
private func hidePrivacyScreen() {
|
|
896
|
+
self.privacyScreen?.removeFromSuperview()
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
@objc func appDidBecomeActive(_ notification: NSNotification) {
|
|
900
|
+
self.hidePrivacyScreen()
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
@objc func appWillResignActive(_ notification: NSNotification) {
|
|
904
|
+
self.showPrivacyScreen()
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
private func readCredentials(_ call: CAPPluginCall) -> WKWebViewCredentials? {
|
|
908
|
+
var credentials: WKWebViewCredentials?
|
|
909
|
+
let credentialsDict = call.getObject("credentials", [:]).mapValues { String(describing: $0 as Any) }
|
|
910
|
+
if !credentialsDict.isEmpty, let username = credentialsDict["username"], let password = credentialsDict["password"] {
|
|
911
|
+
credentials = WKWebViewCredentials(username: username, password: password)
|
|
912
|
+
}
|
|
913
|
+
return credentials
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
private func isDarkColor(_ color: UIColor) -> Bool {
|
|
917
|
+
let components = color.cgColor.components ?? []
|
|
918
|
+
let red = components[0]
|
|
919
|
+
let green = components[1]
|
|
920
|
+
let blue = components[2]
|
|
921
|
+
let brightness = (red * 299 + green * 587 + blue * 114) / 1000
|
|
922
|
+
return brightness < 0.5
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
@objc func getPluginVersion(_ call: CAPPluginCall) {
|
|
926
|
+
call.resolve(["version": self.pluginVersion])
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
@objc func updateDimensions(_ call: CAPPluginCall) {
|
|
930
|
+
let width = call.getFloat("width")
|
|
931
|
+
let height = call.getFloat("height")
|
|
932
|
+
let x = call.getFloat("x")
|
|
933
|
+
let y = call.getFloat("y")
|
|
934
|
+
|
|
935
|
+
DispatchQueue.main.async {
|
|
936
|
+
guard let webViewController = self.webViewController else {
|
|
937
|
+
call.reject("WebView is not initialized")
|
|
938
|
+
return
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
webViewController.updateDimensions(
|
|
942
|
+
width: width != nil ? CGFloat(width!) : nil,
|
|
943
|
+
height: height != nil ? CGFloat(height!) : nil,
|
|
944
|
+
x: x != nil ? CGFloat(x!) : nil,
|
|
945
|
+
y: y != nil ? CGFloat(y!) : nil
|
|
946
|
+
)
|
|
947
|
+
|
|
948
|
+
call.resolve()
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
}
|