|
|
@@ -6,6 +6,7 @@
|
|
|
//
|
|
|
|
|
|
import Cocoa
|
|
|
+import WebKit
|
|
|
|
|
|
private enum SidebarPage: Int {
|
|
|
case joinMeetings = 0
|
|
|
@@ -57,6 +58,8 @@ final class ViewController: NSViewController {
|
|
|
private var paywallPlanViews: [PremiumPlan: NSView] = [:]
|
|
|
private var premiumPlanByView = [ObjectIdentifier: PremiumPlan]()
|
|
|
private weak var paywallOfferLabel: NSTextField?
|
|
|
+ private weak var meetLinkField: NSTextField?
|
|
|
+ private var inAppBrowserWindowController: InAppBrowserWindowController?
|
|
|
|
|
|
private let darkModeDefaultsKey = "settings.darkModeEnabled"
|
|
|
private var darkModeEnabled: Bool {
|
|
|
@@ -179,6 +182,65 @@ private extension ViewController {
|
|
|
showPaywall()
|
|
|
}
|
|
|
|
|
|
+ @objc private func joinMeetClicked(_ sender: Any?) {
|
|
|
+ let rawInput = meetLinkField?.stringValue.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
|
|
+ guard rawInput.isEmpty == false else {
|
|
|
+ showSimpleAlert(title: "Join Meeting", message: "Please enter a Google Meet link.")
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ let normalized = normalizedURLString(from: rawInput)
|
|
|
+ guard let url = URL(string: normalized),
|
|
|
+ let host = url.host?.lowercased(),
|
|
|
+ host.contains("meet.google.com") else {
|
|
|
+ showSimpleAlert(title: "Invalid Link", message: "Please enter a valid Google Meet link.")
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ openInSafari(url: url)
|
|
|
+ }
|
|
|
+
|
|
|
+ @objc private func cancelMeetJoinClicked(_ sender: Any?) {
|
|
|
+ meetLinkField?.stringValue = ""
|
|
|
+ }
|
|
|
+
|
|
|
+ private func normalizedURLString(from value: String) -> String {
|
|
|
+ if value.lowercased().hasPrefix("http://") || value.lowercased().hasPrefix("https://") {
|
|
|
+ return value
|
|
|
+ }
|
|
|
+ return "https://\(value)"
|
|
|
+ }
|
|
|
+
|
|
|
+ private func openInAppBrowser(with url: URL) {
|
|
|
+ let browserController: InAppBrowserWindowController
|
|
|
+ if let existing = inAppBrowserWindowController {
|
|
|
+ browserController = existing
|
|
|
+ } else {
|
|
|
+ browserController = InAppBrowserWindowController()
|
|
|
+ inAppBrowserWindowController = browserController
|
|
|
+ }
|
|
|
+
|
|
|
+ browserController.load(url: url)
|
|
|
+ browserController.showWindow(nil)
|
|
|
+ browserController.window?.makeKeyAndOrderFront(nil)
|
|
|
+ browserController.window?.orderFrontRegardless()
|
|
|
+ NSApp.activate(ignoringOtherApps: true)
|
|
|
+ }
|
|
|
+
|
|
|
+ private func openInSafari(url: URL) {
|
|
|
+ guard let safariAppURL = NSWorkspace.shared.urlForApplication(withBundleIdentifier: "com.apple.Safari") else {
|
|
|
+ NSWorkspace.shared.open(url)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ let configuration = NSWorkspace.OpenConfiguration()
|
|
|
+ NSWorkspace.shared.open([url], withApplicationAt: safariAppURL, configuration: configuration) { _, error in
|
|
|
+ if let error {
|
|
|
+ self.showSimpleAlert(title: "Unable to Open Safari", message: error.localizedDescription)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
private func showSidebarPage(_ page: SidebarPage) {
|
|
|
selectedSidebarPage = page
|
|
|
updateSidebarAppearance()
|
|
|
@@ -663,6 +725,7 @@ private extension ViewController {
|
|
|
codeField.textColor = palette.textPrimary
|
|
|
codeField.placeholderString = "Enter Link"
|
|
|
codeInputShell.addSubview(codeField)
|
|
|
+ meetLinkField = codeField
|
|
|
codeCard.addSubview(codeTitle)
|
|
|
codeCard.addSubview(codeInputShell)
|
|
|
|
|
|
@@ -718,11 +781,40 @@ private extension ViewController {
|
|
|
let spacer = NSView()
|
|
|
spacer.translatesAutoresizingMaskIntoConstraints = false
|
|
|
row.addArrangedSubview(spacer)
|
|
|
- row.addArrangedSubview(actionButton(title: "Cancel", color: palette.cancelButton, textColor: palette.textSecondary, width: 110))
|
|
|
- row.addArrangedSubview(actionButton(title: "Join", color: palette.primaryBlue, textColor: .white, width: 116))
|
|
|
+ row.addArrangedSubview(meetActionButton(
|
|
|
+ title: "Cancel",
|
|
|
+ color: palette.cancelButton,
|
|
|
+ textColor: palette.textSecondary,
|
|
|
+ width: 110,
|
|
|
+ action: #selector(cancelMeetJoinClicked(_:))
|
|
|
+ ))
|
|
|
+ row.addArrangedSubview(meetActionButton(
|
|
|
+ title: "Join",
|
|
|
+ color: palette.primaryBlue,
|
|
|
+ textColor: .white,
|
|
|
+ width: 116,
|
|
|
+ action: #selector(joinMeetClicked(_:))
|
|
|
+ ))
|
|
|
return row
|
|
|
}
|
|
|
|
|
|
+ func meetActionButton(title: String, color: NSColor, textColor: NSColor, width: CGFloat, action: Selector) -> NSButton {
|
|
|
+ let button = NSButton(title: title, target: self, action: action)
|
|
|
+ button.translatesAutoresizingMaskIntoConstraints = false
|
|
|
+ button.isBordered = false
|
|
|
+ button.bezelStyle = .regularSquare
|
|
|
+ button.wantsLayer = true
|
|
|
+ button.layer?.cornerRadius = 9
|
|
|
+ button.layer?.backgroundColor = color.cgColor
|
|
|
+ button.layer?.borderColor = (title == "Cancel" ? palette.inputBorder : palette.primaryBlueBorder).cgColor
|
|
|
+ button.layer?.borderWidth = 1
|
|
|
+ button.font = typography.buttonText
|
|
|
+ button.contentTintColor = textColor
|
|
|
+ button.widthAnchor.constraint(equalToConstant: width).isActive = true
|
|
|
+ button.heightAnchor.constraint(equalToConstant: 36).isActive = true
|
|
|
+ return button
|
|
|
+ }
|
|
|
+
|
|
|
func makePaywallContent() -> NSView {
|
|
|
paywallPlanViews.removeAll()
|
|
|
premiumPlanByView.removeAll()
|
|
|
@@ -1451,9 +1543,7 @@ private extension ViewController {
|
|
|
/// Ensures `NSClickGestureRecognizer` on the row receives clicks instead of child label/image views swallowing them.
|
|
|
private class RowHitTestView: NSView {
|
|
|
override func hitTest(_ point: NSPoint) -> NSView? {
|
|
|
- guard let superview else { return nil }
|
|
|
- let local = convert(point, from: superview)
|
|
|
- return bounds.contains(local) ? self : nil
|
|
|
+ return bounds.contains(point) ? self : nil
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -2000,3 +2090,66 @@ private struct Typography {
|
|
|
let cardTime = NSFont.systemFont(ofSize: 12, weight: .regular)
|
|
|
}
|
|
|
|
|
|
+private final class InAppBrowserWindowController: NSWindowController {
|
|
|
+ private let browserViewController = InAppBrowserViewController()
|
|
|
+
|
|
|
+ init() {
|
|
|
+ let browserWindow = NSWindow(
|
|
|
+ contentRect: NSRect(x: 0, y: 0, width: 1100, height: 760),
|
|
|
+ styleMask: [.titled, .closable, .miniaturizable, .resizable],
|
|
|
+ backing: .buffered,
|
|
|
+ defer: false
|
|
|
+ )
|
|
|
+ browserWindow.title = "Google Meet"
|
|
|
+ browserWindow.center()
|
|
|
+ browserWindow.contentViewController = browserViewController
|
|
|
+ super.init(window: browserWindow)
|
|
|
+ }
|
|
|
+
|
|
|
+ @available(*, unavailable)
|
|
|
+ required init?(coder: NSCoder) {
|
|
|
+ nil
|
|
|
+ }
|
|
|
+
|
|
|
+ func load(url: URL) {
|
|
|
+ browserViewController.load(url: url)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+private final class InAppBrowserViewController: NSViewController, WKNavigationDelegate {
|
|
|
+ private let webView = WKWebView(frame: .zero)
|
|
|
+ private var lastLoadedURL: URL?
|
|
|
+
|
|
|
+ override func loadView() {
|
|
|
+ webView.navigationDelegate = self
|
|
|
+ webView.translatesAutoresizingMaskIntoConstraints = false
|
|
|
+ view = webView
|
|
|
+ }
|
|
|
+
|
|
|
+ func load(url: URL) {
|
|
|
+ lastLoadedURL = url
|
|
|
+ webView.load(URLRequest(url: url))
|
|
|
+ }
|
|
|
+
|
|
|
+ func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
|
|
|
+ let nsError = error as NSError
|
|
|
+ if nsError.code == NSURLErrorCancelled {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ let alert = NSAlert()
|
|
|
+ alert.messageText = "Unable to Open Link"
|
|
|
+ alert.informativeText = "Could not load this page in the in-app browser.\n\n\(error.localizedDescription)\n\nOpen in default browser instead?"
|
|
|
+ alert.addButton(withTitle: "Open in Browser")
|
|
|
+ alert.addButton(withTitle: "Cancel")
|
|
|
+ let result = alert.runModal()
|
|
|
+ if result == .alertFirstButtonReturn, let url = lastLoadedURL {
|
|
|
+ NSWorkspace.shared.open(url)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ func webViewWebContentProcessDidTerminate(_ webView: WKWebView) {
|
|
|
+ // WebKit process can be terminated by the OS; retry current page once.
|
|
|
+ webView.reload()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|