@capgo/inappbrowser 7.16.15 → 7.16.17

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.
@@ -1,816 +0,0 @@
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
- public let identifier = "InAppBrowserPlugin"
28
- public let jsName = "InAppBrowser"
29
- public let pluginMethods: [CAPPluginMethod] = [
30
- CAPPluginMethod(name: "open", returnType: CAPPluginReturnPromise),
31
- CAPPluginMethod(name: "openWebView", returnType: CAPPluginReturnPromise),
32
- CAPPluginMethod(name: "clearCookies", returnType: CAPPluginReturnPromise),
33
- CAPPluginMethod(name: "getCookies", returnType: CAPPluginReturnPromise),
34
- CAPPluginMethod(name: "clearAllCookies", returnType: CAPPluginReturnPromise),
35
- CAPPluginMethod(name: "clearCache", returnType: CAPPluginReturnPromise),
36
- CAPPluginMethod(name: "reload", returnType: CAPPluginReturnPromise),
37
- CAPPluginMethod(name: "setUrl", returnType: CAPPluginReturnPromise),
38
- CAPPluginMethod(name: "show", returnType: CAPPluginReturnPromise),
39
- CAPPluginMethod(name: "close", returnType: CAPPluginReturnPromise),
40
- CAPPluginMethod(name: "executeScript", returnType: CAPPluginReturnPromise),
41
- CAPPluginMethod(name: "postMessage", returnType: CAPPluginReturnPromise)
42
- ]
43
- var navigationWebViewController: UINavigationController?
44
- private var privacyScreen: UIImageView?
45
- private var isSetupDone = false
46
- var currentPluginCall: CAPPluginCall?
47
- var isPresentAfterPageLoad = false
48
- var webViewController: WKWebViewController?
49
- private var closeModalTitle: String?
50
- private var closeModalDescription: String?
51
- private var closeModalOk: String?
52
- private var closeModalCancel: String?
53
-
54
- private func setup() {
55
- self.isSetupDone = true
56
-
57
- #if swift(>=4.2)
58
- NotificationCenter.default.addObserver(self, selector: #selector(appDidBecomeActive(_:)), name: UIApplication.didBecomeActiveNotification, object: nil)
59
- NotificationCenter.default.addObserver(self, selector: #selector(appWillResignActive(_:)), name: UIApplication.willResignActiveNotification, object: nil)
60
- #else
61
- NotificationCenter.default.addObserver(self, selector: #selector(appDidBecomeActive(_:)), name: .UIApplicationDidBecomeActive, object: nil)
62
- NotificationCenter.default.addObserver(self, selector: #selector(appWillResignActive(_:)), name: .UIApplicationWillResignActive, object: nil)
63
- #endif
64
- }
65
-
66
- func presentView(isAnimated: Bool = true) {
67
- guard let navigationController = self.navigationWebViewController else {
68
- self.currentPluginCall?.reject("Navigation controller is not initialized")
69
- return
70
- }
71
-
72
- self.bridge?.viewController?.present(navigationController, animated: isAnimated, completion: {
73
- self.currentPluginCall?.resolve()
74
- })
75
- }
76
-
77
- @objc func clearAllCookies(_ call: CAPPluginCall) {
78
- DispatchQueue.main.async {
79
- let dataStore = WKWebsiteDataStore.default()
80
- let dataTypes = Set([WKWebsiteDataTypeCookies])
81
-
82
- dataStore.removeData(ofTypes: dataTypes,
83
- modifiedSince: Date(timeIntervalSince1970: 0)) {
84
- call.resolve()
85
- }
86
- }
87
- }
88
-
89
- @objc func clearCache(_ call: CAPPluginCall) {
90
- DispatchQueue.main.async {
91
- let dataStore = WKWebsiteDataStore.default()
92
- let dataTypes = Set([WKWebsiteDataTypeDiskCache, WKWebsiteDataTypeMemoryCache])
93
-
94
- dataStore.removeData(ofTypes: dataTypes,
95
- modifiedSince: Date(timeIntervalSince1970: 0)) {
96
- call.resolve()
97
- }
98
- }
99
- }
100
-
101
- @objc func clearCookies(_ call: CAPPluginCall) {
102
- guard let url = call.getString("url"),
103
- let host = URL(string: url)?.host else {
104
- call.reject("Invalid URL")
105
- return
106
- }
107
-
108
- DispatchQueue.main.async {
109
- WKWebsiteDataStore.default().httpCookieStore.getAllCookies { cookies in
110
- let group = DispatchGroup()
111
- for cookie in cookies {
112
- if cookie.domain == host || cookie.domain.hasSuffix(".\(host)") || host.hasSuffix(cookie.domain) {
113
- group.enter()
114
- WKWebsiteDataStore.default().httpCookieStore.delete(cookie) {
115
- group.leave()
116
- }
117
- }
118
- }
119
-
120
- group.notify(queue: .main) {
121
- call.resolve()
122
- }
123
- }
124
- }
125
- }
126
-
127
- @objc func getCookies(_ call: CAPPluginCall) {
128
- let urlString = call.getString("url") ?? ""
129
- let includeHttpOnly = call.getBool("includeHttpOnly") ?? true
130
-
131
- guard let url = URL(string: urlString), let host = url.host else {
132
- call.reject("Invalid URL")
133
- return
134
- }
135
-
136
- DispatchQueue.main.async {
137
- WKWebsiteDataStore.default().httpCookieStore.getAllCookies { cookies in
138
- var cookieDict = [String: String]()
139
- for cookie in cookies {
140
-
141
- if (includeHttpOnly || !cookie.isHTTPOnly) && (cookie.domain == host || cookie.domain.hasSuffix(".\(host)") || host.hasSuffix(cookie.domain)) {
142
- cookieDict[cookie.name] = cookie.value
143
- }
144
- }
145
- call.resolve(cookieDict)
146
- }
147
- }
148
-
149
- }
150
-
151
- @objc func openWebView(_ call: CAPPluginCall) {
152
- if !self.isSetupDone {
153
- self.setup()
154
- }
155
- self.currentPluginCall = call
156
-
157
- guard let urlString = call.getString("url") else {
158
- call.reject("Must provide a URL to open")
159
- return
160
- }
161
-
162
- if urlString.isEmpty {
163
- call.reject("URL must not be empty")
164
- return
165
- }
166
-
167
- var buttonNearDoneIcon: UIImage?
168
- if let buttonNearDoneSettings = call.getObject("buttonNearDone") {
169
- guard let iosSettingsRaw = buttonNearDoneSettings["ios"] else {
170
- call.reject("IOS settings not found")
171
- return
172
- }
173
- if !(iosSettingsRaw is JSObject) {
174
- call.reject("IOS settings are not an object")
175
- return
176
- }
177
- let iosSettings = iosSettingsRaw as! JSObject
178
-
179
- guard let iconType = iosSettings["iconType"] as? String else {
180
- call.reject("buttonNearDone.iconType is empty")
181
- return
182
- }
183
- if iconType != "sf-symbol" && iconType != "asset" {
184
- call.reject("IconType is neither 'sf-symbol' nor 'asset'")
185
- return
186
- }
187
- guard let icon = iosSettings["icon"] as? String else {
188
- call.reject("buttonNearDone.icon is empty")
189
- return
190
- }
191
-
192
- if iconType == "sf-symbol" {
193
- buttonNearDoneIcon = UIImage(systemName: icon)?.withRenderingMode(.alwaysTemplate)
194
- print("[DEBUG] Set buttonNearDone SF Symbol icon: \(icon)")
195
- } else {
196
- // Look in app's web assets/public directory
197
- guard let webDir = Bundle.main.resourceURL?.appendingPathComponent("public") else {
198
- print("[DEBUG] Failed to locate web assets directory")
199
- return
200
- }
201
-
202
- // Try several path combinations to find the asset
203
- let paths = [
204
- icon, // Just the icon name
205
- "public/\(icon)", // With public/ prefix
206
- icon.replacingOccurrences(of: "public/", with: "") // Without public/ prefix
207
- ]
208
-
209
- var foundImage = false
210
-
211
- for path in paths {
212
- // Try as a direct path from web assets dir
213
- let assetPath = path.replacingOccurrences(of: "public/", with: "")
214
- let fileURL = webDir.appendingPathComponent(assetPath)
215
-
216
- print("[DEBUG] Trying to load from: \(fileURL.path)")
217
-
218
- if FileManager.default.fileExists(atPath: fileURL.path),
219
- let data = try? Data(contentsOf: fileURL),
220
- let img = UIImage(data: data) {
221
- buttonNearDoneIcon = img.withRenderingMode(.alwaysTemplate)
222
- print("[DEBUG] Successfully loaded buttonNearDone from web assets: \(fileURL.path)")
223
- foundImage = true
224
- break
225
- }
226
-
227
- // Try with www directory as an alternative
228
- if let wwwDir = Bundle.main.resourceURL?.appendingPathComponent("www") {
229
- let wwwFileURL = wwwDir.appendingPathComponent(assetPath)
230
-
231
- print("[DEBUG] Trying to load from www dir: \(wwwFileURL.path)")
232
-
233
- if FileManager.default.fileExists(atPath: wwwFileURL.path),
234
- let data = try? Data(contentsOf: wwwFileURL),
235
- let img = UIImage(data: data) {
236
- buttonNearDoneIcon = img.withRenderingMode(.alwaysTemplate)
237
- print("[DEBUG] Successfully loaded buttonNearDone from www dir: \(wwwFileURL.path)")
238
- foundImage = true
239
- break
240
- }
241
- }
242
-
243
- // Try looking in app bundle assets
244
- if let iconImage = UIImage(named: path) {
245
- buttonNearDoneIcon = iconImage.withRenderingMode(.alwaysTemplate)
246
- print("[DEBUG] Successfully loaded buttonNearDone from app bundle: \(path)")
247
- foundImage = true
248
- break
249
- }
250
- }
251
-
252
- if !foundImage {
253
- print("[DEBUG] Failed to load buttonNearDone icon: \(icon)")
254
-
255
- // Debug info
256
- if let resourceURL = Bundle.main.resourceURL {
257
- print("[DEBUG] Resource URL: \(resourceURL.path)")
258
-
259
- // List directories to help debugging
260
- do {
261
- let contents = try FileManager.default.contentsOfDirectory(atPath: resourceURL.path)
262
- print("[DEBUG] Root bundle contents: \(contents)")
263
-
264
- // Check if public or www directories exist
265
- if contents.contains("public") {
266
- let publicContents = try FileManager.default.contentsOfDirectory(
267
- atPath: resourceURL.appendingPathComponent("public").path)
268
- print("[DEBUG] Public dir contents: \(publicContents)")
269
- }
270
-
271
- if contents.contains("www") {
272
- let wwwContents = try FileManager.default.contentsOfDirectory(
273
- atPath: resourceURL.appendingPathComponent("www").path)
274
- print("[DEBUG] WWW dir contents: \(wwwContents)")
275
- }
276
- } catch {
277
- print("[DEBUG] Error listing directories: \(error)")
278
- }
279
- }
280
- }
281
- }
282
- }
283
-
284
- let headers = call.getObject("headers", [:]).mapValues { String(describing: $0 as Any) }
285
- let closeModal = call.getBool("closeModal", false)
286
- let closeModalTitle = call.getString("closeModalTitle", "Close")
287
- let closeModalDescription = call.getString("closeModalDescription", "Are you sure you want to close this window?")
288
- let closeModalOk = call.getString("closeModalOk", "OK")
289
- let closeModalCancel = call.getString("closeModalCancel", "Cancel")
290
- let isInspectable = call.getBool("isInspectable", false)
291
- let preventDeeplink = call.getBool("preventDeeplink", false)
292
- let isAnimated = call.getBool("isAnimated", true)
293
- let enabledSafeBottomMargin = call.getBool("enabledSafeBottomMargin", false)
294
-
295
- // Validate preShowScript requires isPresentAfterPageLoad
296
- if call.getString("preShowScript") != nil && !call.getBool("isPresentAfterPageLoad", false) {
297
- call.reject("preShowScript requires isPresentAfterPageLoad to be true")
298
- return
299
- }
300
-
301
- // Validate closeModal options
302
- if closeModal {
303
- if call.getString("closeModalTitle") != nil ||
304
- call.getString("closeModalDescription") != nil ||
305
- call.getString("closeModalOk") != nil ||
306
- call.getString("closeModalCancel") != nil {
307
- // Store the values to be set after proper initialization
308
- self.closeModalTitle = closeModalTitle
309
- self.closeModalDescription = closeModalDescription
310
- self.closeModalOk = closeModalOk
311
- self.closeModalCancel = closeModalCancel
312
- }
313
- } else {
314
- // Reject if closeModal is false but closeModal options are provided
315
- if call.getString("closeModalTitle") != nil ||
316
- call.getString("closeModalDescription") != nil ||
317
- call.getString("closeModalOk") != nil ||
318
- call.getString("closeModalCancel") != nil {
319
- call.reject("closeModal options require closeModal to be true")
320
- return
321
- }
322
- }
323
-
324
- // Validate shareDisclaimer requires shareSubject
325
- if call.getString("shareSubject") == nil && call.getObject("shareDisclaimer") != nil {
326
- call.reject("shareDisclaimer requires shareSubject to be provided")
327
- return
328
- }
329
-
330
- // Validate buttonNearDone compatibility with toolbar type
331
- if call.getString("buttonNearDone") != nil {
332
- let toolbarType = call.getString("toolbarType", "")
333
- if toolbarType == "activity" || toolbarType == "navigation" || toolbarType == "blank" {
334
- call.reject("buttonNearDone is not compatible with toolbarType: " + toolbarType)
335
- return
336
- }
337
- }
338
-
339
- var disclaimerContent: JSObject?
340
- if let shareDisclaimerRaw = call.getObject("shareDisclaimer"), !shareDisclaimerRaw.isEmpty {
341
- disclaimerContent = shareDisclaimerRaw
342
- }
343
-
344
- let toolbarType = call.getString("toolbarType", "")
345
- let backgroundColor = call.getString("backgroundColor", "black") == "white" ? UIColor.white : UIColor.black
346
-
347
- // Don't null out shareDisclaimer regardless of toolbarType
348
- // if toolbarType != "activity" {
349
- // disclaimerContent = nil
350
- // }
351
-
352
- let ignoreUntrustedSSLError = call.getBool("ignoreUntrustedSSLError", false)
353
- let enableGooglePaySupport = call.getBool("enableGooglePaySupport", false)
354
-
355
- self.isPresentAfterPageLoad = call.getBool("isPresentAfterPageLoad", false)
356
- let showReloadButton = call.getBool("showReloadButton", false)
357
-
358
- let credentials = self.readCredentials(call)
359
-
360
- DispatchQueue.main.async {
361
- guard let url = URL(string: urlString) else {
362
- call.reject("Invalid URL format")
363
- return
364
- }
365
-
366
- self.webViewController = WKWebViewController.init(url: url, headers: headers, isInspectable: isInspectable, credentials: credentials, preventDeeplink: preventDeeplink, blankNavigationTab: toolbarType == "blank", enabledSafeBottomMargin: enabledSafeBottomMargin)
367
-
368
- guard let webViewController = self.webViewController else {
369
- call.reject("Failed to initialize WebViewController")
370
- return
371
- }
372
-
373
- if self.bridge?.statusBarVisible == true {
374
- let subviews = self.bridge?.webView?.superview?.subviews
375
- if let emptyStatusBarIndex = subviews?.firstIndex(where: { $0.subviews.isEmpty }) {
376
- if let emptyStatusBar = subviews?[emptyStatusBarIndex] {
377
- webViewController.capacitorStatusBar = emptyStatusBar
378
- emptyStatusBar.removeFromSuperview()
379
- }
380
- }
381
- }
382
-
383
- webViewController.source = .remote(url)
384
- webViewController.leftNavigationBarItemTypes = []
385
-
386
- // Configure close button based on showArrow
387
- let showArrow = call.getBool("showArrow", false)
388
- if showArrow {
389
- // When showArrow is true, put arrow on left
390
- webViewController.doneBarButtonItemPosition = .left
391
- webViewController.showArrowAsClose = true
392
- } else {
393
- // Default X on right
394
- webViewController.doneBarButtonItemPosition = toolbarType == "activity" ? .none : .right
395
- webViewController.showArrowAsClose = false
396
- }
397
-
398
- // Configure navigation buttons based on toolbarType
399
- if toolbarType == "activity" {
400
- // Activity mode should ONLY have:
401
- // 1. Close button (if not hidden by doneBarButtonItemPosition)
402
- // 2. Share button (if shareSubject is provided)
403
- webViewController.leftNavigationBarItemTypes = [] // Clear any left items
404
- webViewController.rightNavigaionBarItemTypes = [] // Clear any right items
405
-
406
- // Only add share button if subject is provided
407
- if call.getString("shareSubject") != nil {
408
- // Add share button to right bar
409
- webViewController.rightNavigaionBarItemTypes.append(.activity)
410
- print("[DEBUG] Activity mode: Added share button, shareSubject: \(call.getString("shareSubject") ?? "nil")")
411
- } else {
412
- // In activity mode, always make the share button visible by setting a default shareSubject
413
- webViewController.shareSubject = "Share"
414
- webViewController.rightNavigaionBarItemTypes.append(.activity)
415
- print("[DEBUG] Activity mode: Setting default shareSubject")
416
- }
417
-
418
- // Set done button position based on showArrow
419
- if showArrow {
420
- webViewController.doneBarButtonItemPosition = .left
421
- } else {
422
- // In activity mode, keep the done button visible even when showArrow is false
423
- webViewController.doneBarButtonItemPosition = .right
424
- }
425
- } else if toolbarType == "navigation" {
426
- // Navigation mode puts back/forward on the left
427
- webViewController.leftNavigationBarItemTypes = [.back, .forward]
428
- if showReloadButton {
429
- webViewController.leftNavigationBarItemTypes.append(.reload)
430
- }
431
-
432
- // Only add share button if subject is provided
433
- if call.getString("shareSubject") != nil {
434
- // Add share button to right navigation bar
435
- webViewController.rightNavigaionBarItemTypes.append(.activity)
436
- }
437
- } else {
438
- // Other modes may have reload button
439
- if showReloadButton {
440
- webViewController.leftNavigationBarItemTypes.append(.reload)
441
- }
442
-
443
- // Only add share button if subject is provided
444
- if call.getString("shareSubject") != nil {
445
- // Add share button to right navigation bar
446
- webViewController.rightNavigaionBarItemTypes.append(.activity)
447
- }
448
- }
449
-
450
- // Set buttonNearDoneIcon if provided
451
- if let buttonNearDoneIcon = buttonNearDoneIcon {
452
- webViewController.buttonNearDoneIcon = buttonNearDoneIcon
453
- print("[DEBUG] Button near done icon set: \(buttonNearDoneIcon)")
454
- }
455
-
456
- webViewController.capBrowserPlugin = self
457
- webViewController.title = call.getString("title", "New Window")
458
- // Only set shareSubject if not already set for activity mode
459
- if webViewController.shareSubject == nil {
460
- webViewController.shareSubject = call.getString("shareSubject")
461
- }
462
- webViewController.shareDisclaimer = disclaimerContent
463
-
464
- // Debug shareDisclaimer
465
- if let disclaimer = disclaimerContent {
466
- print("[DEBUG] Share disclaimer set: \(disclaimer)")
467
- } else {
468
- print("[DEBUG] No share disclaimer set")
469
- }
470
-
471
- webViewController.preShowScript = call.getString("preShowScript")
472
- webViewController.websiteTitleInNavigationBar = call.getBool("visibleTitle", true)
473
- webViewController.ignoreUntrustedSSLError = ignoreUntrustedSSLError
474
-
475
- // Set Google Pay support
476
- webViewController.enableGooglePaySupport = enableGooglePaySupport
477
-
478
- // Set text zoom if specified
479
- if let textZoom = call.getInt("textZoom") {
480
- webViewController.textZoom = textZoom
481
- }
482
-
483
- // Set closeModal properties after proper initialization
484
- if closeModal {
485
- webViewController.closeModal = true
486
- webViewController.closeModalTitle = self.closeModalTitle ?? closeModalTitle
487
- webViewController.closeModalDescription = self.closeModalDescription ?? closeModalDescription
488
- webViewController.closeModalOk = self.closeModalOk ?? closeModalOk
489
- webViewController.closeModalCancel = self.closeModalCancel ?? closeModalCancel
490
- }
491
-
492
- self.navigationWebViewController = UINavigationController.init(rootViewController: webViewController)
493
- self.navigationWebViewController?.navigationBar.isTranslucent = false
494
- self.navigationWebViewController?.toolbar.isTranslucent = false
495
-
496
- // Ensure no lines or borders appear by default
497
- self.navigationWebViewController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
498
- self.navigationWebViewController?.navigationBar.shadowImage = UIImage()
499
- self.navigationWebViewController?.navigationBar.setValue(true, forKey: "hidesShadow")
500
- self.navigationWebViewController?.toolbar.setShadowImage(UIImage(), forToolbarPosition: .any)
501
-
502
- // Handle web view background color
503
- webViewController.view.backgroundColor = backgroundColor
504
-
505
- // Handle toolbar color
506
- if let toolbarColor = call.getString("toolbarColor"), self.isHexColorCode(toolbarColor) {
507
- // If specific color provided, use it
508
- let color = UIColor(hexString: toolbarColor)
509
-
510
- // Apply to status bar and navigation bar area with a single colored view
511
- webViewController.setupStatusBarBackground(color: color)
512
-
513
- // Set status bar style based on toolbar color
514
- let isDark = self.isDarkColor(color)
515
- webViewController.statusBarStyle = isDark ? .lightContent : .darkContent
516
- webViewController.updateStatusBarStyle()
517
-
518
- // Apply text color
519
- let textColor: UIColor
520
- if let toolbarTextColor = call.getString("toolbarTextColor"), self.isHexColorCode(toolbarTextColor) {
521
- textColor = UIColor(hexString: toolbarTextColor)
522
- } else {
523
- textColor = isDark ? UIColor.white : UIColor.black
524
- }
525
-
526
- // Apply tint color to all UI elements without changing background
527
- self.navigationWebViewController?.navigationBar.tintColor = textColor
528
- webViewController.tintColor = textColor
529
- self.navigationWebViewController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: textColor]
530
- } else {
531
- // Use system appearance
532
- let isDarkMode = UITraitCollection.current.userInterfaceStyle == .dark
533
- let backgroundColor = isDarkMode ? UIColor.black : UIColor.white
534
- let textColor: UIColor
535
-
536
- if let toolbarTextColor = call.getString("toolbarTextColor"), self.isHexColorCode(toolbarTextColor) {
537
- textColor = UIColor(hexString: toolbarTextColor)
538
- } else {
539
- textColor = isDarkMode ? UIColor.white : UIColor.black
540
- }
541
-
542
- // Apply colors
543
- webViewController.setupStatusBarBackground(color: backgroundColor)
544
- webViewController.tintColor = textColor
545
- self.navigationWebViewController?.navigationBar.tintColor = textColor
546
- self.navigationWebViewController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: textColor]
547
- webViewController.statusBarStyle = isDarkMode ? .lightContent : .darkContent
548
- webViewController.updateStatusBarStyle()
549
-
550
- }
551
-
552
- self.navigationWebViewController?.modalPresentationStyle = .overCurrentContext
553
- self.navigationWebViewController?.modalTransitionStyle = .crossDissolve
554
- if toolbarType == "blank" {
555
- self.navigationWebViewController?.navigationBar.isHidden = true
556
- webViewController.blankNavigationTab = true
557
-
558
- // Even with hidden navigation bar, we need to set proper status bar appearance
559
- // If toolbarColor is explicitly set, use that for status bar style
560
- if let toolbarColor = call.getString("toolbarColor"), self.isHexColorCode(toolbarColor) {
561
- let color = UIColor(hexString: toolbarColor)
562
- let isDark = self.isDarkColor(color)
563
- webViewController.statusBarStyle = isDark ? .lightContent : .darkContent
564
- webViewController.updateStatusBarStyle()
565
-
566
- // Apply status bar background color via the special view
567
- webViewController.setupStatusBarBackground(color: color)
568
-
569
- // Apply background color to whole view to ensure no gaps
570
- webViewController.view.backgroundColor = color
571
- self.navigationWebViewController?.view.backgroundColor = color
572
-
573
- // Apply status bar background color
574
- if let navController = self.navigationWebViewController {
575
- navController.view.backgroundColor = color
576
- }
577
- } else {
578
- // Follow system appearance if no specific color
579
- let isDarkMode = UITraitCollection.current.userInterfaceStyle == .dark
580
- let backgroundColor = isDarkMode ? UIColor.black : UIColor.white
581
- webViewController.statusBarStyle = isDarkMode ? .lightContent : .darkContent
582
- webViewController.updateStatusBarStyle()
583
-
584
- // Apply status bar background color via the special view
585
- webViewController.setupStatusBarBackground(color: backgroundColor)
586
-
587
- // Set appropriate background color
588
- if let navController = self.navigationWebViewController {
589
- navController.view.backgroundColor = backgroundColor
590
- }
591
- }
592
-
593
- }
594
-
595
- // We don't use the toolbar anymore, always hide it
596
- self.navigationWebViewController?.setToolbarHidden(true, animated: false)
597
-
598
- if !self.isPresentAfterPageLoad {
599
- self.presentView(isAnimated: isAnimated)
600
- }
601
- call.resolve()
602
- }
603
- }
604
-
605
- @objc func reload(_ call: CAPPluginCall) {
606
- self.webViewController?.reload()
607
- call.resolve()
608
- }
609
-
610
- @objc func setUrl(_ call: CAPPluginCall) {
611
- guard let urlString = call.getString("url") else {
612
- call.reject("Cannot get new url to set")
613
- return
614
- }
615
-
616
- guard let url = URL(string: urlString) else {
617
- call.reject("Invalid URL")
618
- return
619
- }
620
-
621
- self.webViewController?.load(remote: url)
622
- call.resolve()
623
- }
624
-
625
- @objc func executeScript(_ call: CAPPluginCall) {
626
- guard let script = call.getString("code") else {
627
- call.reject("Cannot get script to execute")
628
- return
629
- }
630
- DispatchQueue.main.async {
631
- self.webViewController?.executeScript(script: script)
632
- call.resolve()
633
- }
634
- }
635
-
636
- @objc func postMessage(_ call: CAPPluginCall) {
637
- let eventData = call.getObject("detail", [:])
638
- // Check if eventData is empty
639
- if eventData.isEmpty {
640
- call.reject("Event data must not be empty")
641
- return
642
- }
643
- print("Event data: \(eventData)")
644
-
645
- DispatchQueue.main.async {
646
- self.webViewController?.postMessageToJS(message: eventData)
647
- }
648
- call.resolve()
649
- }
650
-
651
- func isHexColorCode(_ input: String) -> Bool {
652
- let hexColorRegex = "^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$"
653
-
654
- do {
655
- let regex = try NSRegularExpression(pattern: hexColorRegex)
656
- let range = NSRange(location: 0, length: input.utf16.count)
657
- if let _ = regex.firstMatch(in: input, options: [], range: range) {
658
- return true
659
- }
660
- } catch {
661
- print("Error creating regular expression: \(error)")
662
- }
663
-
664
- return false
665
- }
666
-
667
- @objc func open(_ call: CAPPluginCall) {
668
- if !self.isSetupDone {
669
- self.setup()
670
- }
671
-
672
- let isInspectable = call.getBool("isInspectable", false)
673
- let preventDeeplink = call.getBool("preventDeeplink", false)
674
- self.isPresentAfterPageLoad = call.getBool("isPresentAfterPageLoad", false)
675
-
676
- self.currentPluginCall = call
677
-
678
- guard let urlString = call.getString("url") else {
679
- call.reject("Must provide a URL to open")
680
- return
681
- }
682
-
683
- if urlString.isEmpty {
684
- call.reject("URL must not be empty")
685
- return
686
- }
687
-
688
- let headers = call.getObject("headers", [:]).mapValues { String(describing: $0 as Any) }
689
- let credentials = self.readCredentials(call)
690
-
691
- DispatchQueue.main.async {
692
- guard let url = URL(string: urlString) else {
693
- call.reject("Invalid URL format")
694
- return
695
- }
696
-
697
- self.webViewController = WKWebViewController.init(url: url, headers: headers, isInspectable: isInspectable, credentials: credentials, preventDeeplink: preventDeeplink, blankNavigationTab: true, enabledSafeBottomMargin: false)
698
-
699
- guard let webViewController = self.webViewController else {
700
- call.reject("Failed to initialize WebViewController")
701
- return
702
- }
703
-
704
- if self.bridge?.statusBarVisible == true {
705
- let subviews = self.bridge?.webView?.superview?.subviews
706
- if let emptyStatusBarIndex = subviews?.firstIndex(where: { $0.subviews.isEmpty }) {
707
- if let emptyStatusBar = subviews?[emptyStatusBarIndex] {
708
- webViewController.capacitorStatusBar = emptyStatusBar
709
- emptyStatusBar.removeFromSuperview()
710
- }
711
- }
712
- }
713
-
714
- webViewController.source = .remote(url)
715
- webViewController.leftNavigationBarItemTypes = [.back, .forward, .reload]
716
- webViewController.capBrowserPlugin = self
717
- webViewController.hasDynamicTitle = true
718
-
719
- self.navigationWebViewController = UINavigationController.init(rootViewController: webViewController)
720
- self.navigationWebViewController?.navigationBar.isTranslucent = false
721
-
722
- // Ensure no lines or borders appear by default
723
- self.navigationWebViewController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
724
- self.navigationWebViewController?.navigationBar.shadowImage = UIImage()
725
- self.navigationWebViewController?.navigationBar.setValue(true, forKey: "hidesShadow")
726
-
727
- // Use system appearance
728
- let isDarkMode = UITraitCollection.current.userInterfaceStyle == .dark
729
- let backgroundColor = isDarkMode ? UIColor.black : UIColor.white
730
- let textColor = isDarkMode ? UIColor.white : UIColor.black
731
-
732
- // Apply colors
733
- webViewController.setupStatusBarBackground(color: backgroundColor)
734
- webViewController.tintColor = textColor
735
- self.navigationWebViewController?.navigationBar.tintColor = textColor
736
- self.navigationWebViewController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: textColor]
737
- webViewController.statusBarStyle = isDarkMode ? .lightContent : .darkContent
738
- webViewController.updateStatusBarStyle()
739
-
740
- // Always hide toolbar to ensure no bottom bar
741
- self.navigationWebViewController?.setToolbarHidden(true, animated: false)
742
-
743
- self.navigationWebViewController?.modalPresentationStyle = .overCurrentContext
744
- self.navigationWebViewController?.modalTransitionStyle = .crossDissolve
745
-
746
- if !self.isPresentAfterPageLoad {
747
- self.presentView()
748
- }
749
- call.resolve()
750
- }
751
- }
752
-
753
- @objc func close(_ call: CAPPluginCall) {
754
- DispatchQueue.main.async {
755
- let currentUrl = self.webViewController?.url?.absoluteString ?? ""
756
-
757
- self.webViewController?.cleanupWebView()
758
-
759
- self.navigationWebViewController?.dismiss(animated: true) {
760
- self.webViewController = nil
761
- self.navigationWebViewController = nil
762
- }
763
-
764
- self.notifyListeners("closeEvent", data: ["url": currentUrl])
765
- call.resolve()
766
- }
767
- }
768
-
769
- private func showPrivacyScreen() {
770
- if privacyScreen == nil {
771
- self.privacyScreen = UIImageView()
772
- if let launchImage = UIImage(named: "LaunchImage") {
773
- privacyScreen!.image = launchImage
774
- privacyScreen!.frame = UIScreen.main.bounds
775
- privacyScreen!.contentMode = .scaleAspectFill
776
- privacyScreen!.isUserInteractionEnabled = false
777
- } else if let launchImage = UIImage(named: "Splash") {
778
- privacyScreen!.image = launchImage
779
- privacyScreen!.frame = UIScreen.main.bounds
780
- privacyScreen!.contentMode = .scaleAspectFill
781
- privacyScreen!.isUserInteractionEnabled = false
782
- }
783
- }
784
- self.navigationWebViewController?.view.addSubview(self.privacyScreen!)
785
- }
786
-
787
- private func hidePrivacyScreen() {
788
- self.privacyScreen?.removeFromSuperview()
789
- }
790
-
791
- @objc func appDidBecomeActive(_ notification: NSNotification) {
792
- self.hidePrivacyScreen()
793
- }
794
-
795
- @objc func appWillResignActive(_ notification: NSNotification) {
796
- self.showPrivacyScreen()
797
- }
798
-
799
- private func readCredentials(_ call: CAPPluginCall) -> WKWebViewCredentials? {
800
- var credentials: WKWebViewCredentials?
801
- let credentialsDict = call.getObject("credentials", [:]).mapValues { String(describing: $0 as Any) }
802
- if !credentialsDict.isEmpty, let username = credentialsDict["username"], let password = credentialsDict["password"] {
803
- credentials = WKWebViewCredentials(username: username, password: password)
804
- }
805
- return credentials
806
- }
807
-
808
- private func isDarkColor(_ color: UIColor) -> Bool {
809
- let components = color.cgColor.components ?? []
810
- let red = components[0]
811
- let green = components[1]
812
- let blue = components[2]
813
- let brightness = (red * 299 + green * 587 + blue * 114) / 1000
814
- return brightness < 0.5
815
- }
816
- }