Procházet zdrojové kódy

Improve Google Meet join link handling and browser launch reliability.

Made-with: Cursor
huzaifahayat12 před 2 týdny
rodič
revize
b7cc5ff60f
1 změnil soubory, kde provedl 158 přidání a 5 odebrání
  1. 158 5
      meetings_app/ViewController.swift

+ 158 - 5
meetings_app/ViewController.swift

@@ -6,6 +6,7 @@
6 6
 //
7 7
 
8 8
 import Cocoa
9
+import WebKit
9 10
 
10 11
 private enum SidebarPage: Int {
11 12
     case joinMeetings = 0
@@ -57,6 +58,8 @@ final class ViewController: NSViewController {
57 58
     private var paywallPlanViews: [PremiumPlan: NSView] = [:]
58 59
     private var premiumPlanByView = [ObjectIdentifier: PremiumPlan]()
59 60
     private weak var paywallOfferLabel: NSTextField?
61
+    private weak var meetLinkField: NSTextField?
62
+    private var inAppBrowserWindowController: InAppBrowserWindowController?
60 63
 
61 64
     private let darkModeDefaultsKey = "settings.darkModeEnabled"
62 65
     private var darkModeEnabled: Bool {
@@ -179,6 +182,65 @@ private extension ViewController {
179 182
         showPaywall()
180 183
     }
181 184
 
185
+    @objc private func joinMeetClicked(_ sender: Any?) {
186
+        let rawInput = meetLinkField?.stringValue.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
187
+        guard rawInput.isEmpty == false else {
188
+            showSimpleAlert(title: "Join Meeting", message: "Please enter a Google Meet link.")
189
+            return
190
+        }
191
+
192
+        let normalized = normalizedURLString(from: rawInput)
193
+        guard let url = URL(string: normalized),
194
+              let host = url.host?.lowercased(),
195
+              host.contains("meet.google.com") else {
196
+            showSimpleAlert(title: "Invalid Link", message: "Please enter a valid Google Meet link.")
197
+            return
198
+        }
199
+
200
+        openInSafari(url: url)
201
+    }
202
+
203
+    @objc private func cancelMeetJoinClicked(_ sender: Any?) {
204
+        meetLinkField?.stringValue = ""
205
+    }
206
+
207
+    private func normalizedURLString(from value: String) -> String {
208
+        if value.lowercased().hasPrefix("http://") || value.lowercased().hasPrefix("https://") {
209
+            return value
210
+        }
211
+        return "https://\(value)"
212
+    }
213
+
214
+    private func openInAppBrowser(with url: URL) {
215
+        let browserController: InAppBrowserWindowController
216
+        if let existing = inAppBrowserWindowController {
217
+            browserController = existing
218
+        } else {
219
+            browserController = InAppBrowserWindowController()
220
+            inAppBrowserWindowController = browserController
221
+        }
222
+
223
+        browserController.load(url: url)
224
+        browserController.showWindow(nil)
225
+        browserController.window?.makeKeyAndOrderFront(nil)
226
+        browserController.window?.orderFrontRegardless()
227
+        NSApp.activate(ignoringOtherApps: true)
228
+    }
229
+
230
+    private func openInSafari(url: URL) {
231
+        guard let safariAppURL = NSWorkspace.shared.urlForApplication(withBundleIdentifier: "com.apple.Safari") else {
232
+            NSWorkspace.shared.open(url)
233
+            return
234
+        }
235
+
236
+        let configuration = NSWorkspace.OpenConfiguration()
237
+        NSWorkspace.shared.open([url], withApplicationAt: safariAppURL, configuration: configuration) { _, error in
238
+            if let error {
239
+                self.showSimpleAlert(title: "Unable to Open Safari", message: error.localizedDescription)
240
+            }
241
+        }
242
+    }
243
+
182 244
     private func showSidebarPage(_ page: SidebarPage) {
183 245
         selectedSidebarPage = page
184 246
         updateSidebarAppearance()
@@ -663,6 +725,7 @@ private extension ViewController {
663 725
         codeField.textColor = palette.textPrimary
664 726
         codeField.placeholderString = "Enter Link"
665 727
         codeInputShell.addSubview(codeField)
728
+        meetLinkField = codeField
666 729
         codeCard.addSubview(codeTitle)
667 730
         codeCard.addSubview(codeInputShell)
668 731
 
@@ -718,11 +781,40 @@ private extension ViewController {
718 781
         let spacer = NSView()
719 782
         spacer.translatesAutoresizingMaskIntoConstraints = false
720 783
         row.addArrangedSubview(spacer)
721
-        row.addArrangedSubview(actionButton(title: "Cancel", color: palette.cancelButton, textColor: palette.textSecondary, width: 110))
722
-        row.addArrangedSubview(actionButton(title: "Join", color: palette.primaryBlue, textColor: .white, width: 116))
784
+        row.addArrangedSubview(meetActionButton(
785
+            title: "Cancel",
786
+            color: palette.cancelButton,
787
+            textColor: palette.textSecondary,
788
+            width: 110,
789
+            action: #selector(cancelMeetJoinClicked(_:))
790
+        ))
791
+        row.addArrangedSubview(meetActionButton(
792
+            title: "Join",
793
+            color: palette.primaryBlue,
794
+            textColor: .white,
795
+            width: 116,
796
+            action: #selector(joinMeetClicked(_:))
797
+        ))
723 798
         return row
724 799
     }
725 800
 
801
+    func meetActionButton(title: String, color: NSColor, textColor: NSColor, width: CGFloat, action: Selector) -> NSButton {
802
+        let button = NSButton(title: title, target: self, action: action)
803
+        button.translatesAutoresizingMaskIntoConstraints = false
804
+        button.isBordered = false
805
+        button.bezelStyle = .regularSquare
806
+        button.wantsLayer = true
807
+        button.layer?.cornerRadius = 9
808
+        button.layer?.backgroundColor = color.cgColor
809
+        button.layer?.borderColor = (title == "Cancel" ? palette.inputBorder : palette.primaryBlueBorder).cgColor
810
+        button.layer?.borderWidth = 1
811
+        button.font = typography.buttonText
812
+        button.contentTintColor = textColor
813
+        button.widthAnchor.constraint(equalToConstant: width).isActive = true
814
+        button.heightAnchor.constraint(equalToConstant: 36).isActive = true
815
+        return button
816
+    }
817
+
726 818
     func makePaywallContent() -> NSView {
727 819
         paywallPlanViews.removeAll()
728 820
         premiumPlanByView.removeAll()
@@ -1451,9 +1543,7 @@ private extension ViewController {
1451 1543
 /// Ensures `NSClickGestureRecognizer` on the row receives clicks instead of child label/image views swallowing them.
1452 1544
 private class RowHitTestView: NSView {
1453 1545
     override func hitTest(_ point: NSPoint) -> NSView? {
1454
-        guard let superview else { return nil }
1455
-        let local = convert(point, from: superview)
1456
-        return bounds.contains(local) ? self : nil
1546
+        return bounds.contains(point) ? self : nil
1457 1547
     }
1458 1548
 }
1459 1549
 
@@ -2000,3 +2090,66 @@ private struct Typography {
2000 2090
     let cardTime = NSFont.systemFont(ofSize: 12, weight: .regular)
2001 2091
 }
2002 2092
 
2093
+private final class InAppBrowserWindowController: NSWindowController {
2094
+    private let browserViewController = InAppBrowserViewController()
2095
+
2096
+    init() {
2097
+        let browserWindow = NSWindow(
2098
+            contentRect: NSRect(x: 0, y: 0, width: 1100, height: 760),
2099
+            styleMask: [.titled, .closable, .miniaturizable, .resizable],
2100
+            backing: .buffered,
2101
+            defer: false
2102
+        )
2103
+        browserWindow.title = "Google Meet"
2104
+        browserWindow.center()
2105
+        browserWindow.contentViewController = browserViewController
2106
+        super.init(window: browserWindow)
2107
+    }
2108
+
2109
+    @available(*, unavailable)
2110
+    required init?(coder: NSCoder) {
2111
+        nil
2112
+    }
2113
+
2114
+    func load(url: URL) {
2115
+        browserViewController.load(url: url)
2116
+    }
2117
+}
2118
+
2119
+private final class InAppBrowserViewController: NSViewController, WKNavigationDelegate {
2120
+    private let webView = WKWebView(frame: .zero)
2121
+    private var lastLoadedURL: URL?
2122
+
2123
+    override func loadView() {
2124
+        webView.navigationDelegate = self
2125
+        webView.translatesAutoresizingMaskIntoConstraints = false
2126
+        view = webView
2127
+    }
2128
+
2129
+    func load(url: URL) {
2130
+        lastLoadedURL = url
2131
+        webView.load(URLRequest(url: url))
2132
+    }
2133
+
2134
+    func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
2135
+        let nsError = error as NSError
2136
+        if nsError.code == NSURLErrorCancelled {
2137
+            return
2138
+        }
2139
+        let alert = NSAlert()
2140
+        alert.messageText = "Unable to Open Link"
2141
+        alert.informativeText = "Could not load this page in the in-app browser.\n\n\(error.localizedDescription)\n\nOpen in default browser instead?"
2142
+        alert.addButton(withTitle: "Open in Browser")
2143
+        alert.addButton(withTitle: "Cancel")
2144
+        let result = alert.runModal()
2145
+        if result == .alertFirstButtonReturn, let url = lastLoadedURL {
2146
+            NSWorkspace.shared.open(url)
2147
+        }
2148
+    }
2149
+
2150
+    func webViewWebContentProcessDidTerminate(_ webView: WKWebView) {
2151
+        // WebKit process can be terminated by the OS; retry current page once.
2152
+        webView.reload()
2153
+    }
2154
+}
2155
+