Sfoglia il codice sorgente

Drive large widget: shortcut hub instead of embedded web view

- Remove WKWebView from large tier; use scrollable native hub (folders, quick find, create grid, more tools)
- Search and shortcuts open Google Drive in the in-app browser; add Drawing, Sites, Gemini, Offline, etc.
- Drop WebKit from DriveDesktopWidgetView; update DriveWidgetMode comments

Made-with: Cursor
huzaifahayat12 3 mesi fa
parent
commit
43e50a93b3

+ 119 - 185
google_apps/Widgets/DriveDesktopWidgetView.swift

@@ -1,8 +1,7 @@
 import AppKit
 import SwiftUI
-import WebKit
 
-/// Tiered Google Drive desktop widget: recent files, starred preview, or full embedded Drive.
+/// Tiered Google Drive desktop widget: recent files, starred preview, or a large shortcut hub (opens Drive in the app browser).
 struct DriveDesktopWidgetView: View {
     let app: LauncherApp
     let mode: DriveWidgetMode
@@ -247,29 +246,16 @@ struct DriveDesktopWidgetView: View {
         .allowsHitTesting(!isPreview)
     }
 
-    // MARK: - Large (interactive)
+    // MARK: - Large (shortcut hub — opens Drive in app browser; no embedded web app)
 
     @State private var searchText = ""
-    @State private var webURL: URL
-
-    init(app: LauncherApp, mode: DriveWidgetMode, isPreview: Bool = false) {
-        self.app = app
-        self.mode = mode
-        self.isPreview = isPreview
-        let base = app.webURL ?? URL(string: "https://drive.google.com")!
-        _webURL = State(initialValue: Self.defaultDriveURL(base: base))
-    }
-
-    private static func defaultDriveURL(base: URL) -> URL {
-        WidgetDeepLinkURL.resolved(base: base, actionPath: "/drive/home", accountSlot: 0)
-    }
 
     private var interactiveBody: some View {
         Group {
             if isPreview {
                 interactivePreviewChrome
             } else {
-                interactiveLiveChrome
+                interactiveHubChrome
             }
         }
     }
@@ -289,7 +275,7 @@ struct DriveDesktopWidgetView: View {
                 Text("Google Drive")
                     .font(.system(size: 15, weight: .bold))
                     .foregroundStyle(.white.opacity(0.92))
-                Text("Search, folders, previews, and create options load on your desktop.")
+                Text("Search, folders, and create shortcuts open in the app browser — no embedded Drive page.")
                     .font(.system(size: 11, weight: .medium))
                     .foregroundStyle(.white.opacity(0.65))
                     .multilineTextAlignment(.center)
@@ -299,34 +285,43 @@ struct DriveDesktopWidgetView: View {
         }
     }
 
-    private var interactiveLiveChrome: some View {
-        ZStack {
-            RoundedRectangle(cornerRadius: 22, style: .continuous)
-                .fill(Color.black.opacity(0.32))
-
-            VStack(spacing: 0) {
-                VStack(spacing: 8) {
+    private var interactiveHubChrome: some View {
+        ZStack(alignment: .topLeading) {
+            driveGradientBackground
+                .frame(maxWidth: .infinity, maxHeight: .infinity)
+            ScrollView {
+                VStack(alignment: .leading, spacing: 14) {
                     interactiveHeader
                     interactiveSearchBar
+
+                    hubSectionTitle("Folders")
                     folderChipsRow
-                    createAndUploadRow
-                }
-                .padding(.horizontal, 6)
-                .padding(.vertical, 8)
-                .background(
-                    RoundedRectangle(cornerRadius: 16, style: .continuous)
-                        .fill(Color(red: 0.06, green: 0.1, blue: 0.09))
-                )
-                .zIndex(20)
 
-                DriveWebWidgetView(url: $webURL)
-                    .frame(minHeight: 280)
-                    .frame(maxWidth: .infinity, maxHeight: .infinity)
-                    .clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous))
-                    .zIndex(0)
+                    hubSectionTitle("Quick find")
+                    quickFindChipsRow
+
+                    hubSectionTitle("Create")
+                    createExpandedGrid
+
+                    hubSectionTitle("More")
+                    moreToolsRow
+
+                    drivePrimaryButton(title: "Open full Google Drive", systemImage: "safari") {
+                        openDrivePath("/drive/home")
+                    }
+                    .padding(.top, 4)
+                }
+                .padding(14)
             }
-            .padding(10)
+            .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
         }
+        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
+    }
+
+    private func hubSectionTitle(_ text: String) -> some View {
+        Text(text)
+            .font(.system(size: 11, weight: .bold))
+            .foregroundStyle(.white.opacity(0.58))
     }
 
     private var interactiveHeader: some View {
@@ -377,80 +372,116 @@ struct DriveDesktopWidgetView: View {
     private var folderChipsRow: some View {
         ScrollView(.horizontal, showsIndicators: false) {
             HStack(spacing: 8) {
+                driveChip("Home", path: "/drive/home")
                 driveChip("My Drive", path: "/drive/my-drive")
                 driveChip("Recent", path: "/drive/recent")
                 driveChip("Starred", path: "/drive/starred")
                 driveChip("Shared", path: "/drive/shared-with-me")
                 driveChip("Shared drives", path: "/drive/shared-drives")
                 driveChip("Computers", path: "/drive/computers")
+                driveChip("Offline", path: "/drive/offline")
                 driveChip("Trash", path: "/drive/trash")
             }
         }
     }
 
-    private var createAndUploadRow: some View {
-        VStack(alignment: .leading, spacing: 6) {
-            Text("Create")
-                .font(.system(size: 10, weight: .bold))
-                .foregroundStyle(.white.opacity(0.55))
-            ScrollView(.horizontal, showsIndicators: false) {
-                HStack(spacing: 8) {
-                    createChip("Doc", systemImage: "doc.text", url: "https://docs.google.com/document/create")
-                    createChip("Sheet", systemImage: "tablecells", url: "https://sheets.google.com/spreadsheets/create")
-                    createChip("Slides", systemImage: "rectangle.on.rectangle", url: "https://slides.google.com/presentation/create")
-                    createChip("Form", systemImage: "list.bullet.rectangle", url: "https://docs.google.com/forms/create")
-                    Button(action: {
-                        NSApp.activate(ignoringOtherApps: true)
-                        openDrivePath("/drive/home")
-                    }) {
-                        HStack(spacing: 4) {
-                            Image(systemName: "arrow.up.doc")
-                            Text("Upload")
-                        }
-                        .font(.system(size: 10, weight: .bold))
-                        .foregroundStyle(.white.opacity(0.92))
-                        .padding(.horizontal, 10)
-                        .padding(.vertical, 6)
-                        .background(Capsule().fill(Color.white.opacity(0.14)))
-                    }
-                    .buttonStyle(.plain)
-                    .help("Open Drive to upload files")
-                }
+    private var quickFindChipsRow: some View {
+        ScrollView(.horizontal, showsIndicators: false) {
+            HStack(spacing: 8) {
+                driveChip("PDFs", path: "/drive/search?q=type:pdf")
+                driveChip("Images", path: "/drive/search?q=type:image")
+                driveChip("Videos", path: "/drive/search?q=type:video")
+                driveChip("Sheets", path: "/drive/search?q=type:spreadsheet")
+                driveChip("Docs", path: "/drive/search?q=type:document")
             }
         }
     }
 
-    private func driveChip(_ title: String, path: String) -> some View {
+    private var createExpandedGrid: some View {
+        let cols = [GridItem(.flexible(minimum: 0), spacing: 8), GridItem(.flexible(minimum: 0), spacing: 8)]
+        return LazyVGrid(columns: cols, spacing: 8) {
+            createGridTile(title: "Doc", systemImage: "doc.text", url: "https://docs.google.com/document/create")
+            createGridTile(title: "Sheet", systemImage: "tablecells", url: "https://sheets.google.com/spreadsheets/create")
+            createGridTile(title: "Slides", systemImage: "rectangle.on.rectangle", url: "https://slides.google.com/presentation/create")
+            createGridTile(title: "Form", systemImage: "list.bullet.rectangle", url: "https://docs.google.com/forms/create")
+            createGridTile(title: "Drawing", systemImage: "pencil.and.outline", url: "https://docs.google.com/drawings/create")
+            createGridTile(title: "Sites", systemImage: "globe", url: "https://sites.google.com/new")
+            createGridTile(title: "Upload", systemImage: "arrow.up.doc", path: "/drive/home")
+            createGridTile(title: "Folder", systemImage: "folder.badge.plus", path: "/drive/home")
+        }
+    }
+
+    private var moreToolsRow: some View {
+        ScrollView(.horizontal, showsIndicators: false) {
+            HStack(spacing: 8) {
+                driveChip("Storage", path: "/settings/storage")
+                driveChip("Settings", path: "/drive/settings")
+                driveChip("Activity", path: "/drive/home")
+                driveChip("Gemini", path: "https://gemini.google.com/app")
+            }
+        }
+    }
+
+    private func createGridTile(title: String, systemImage: String, url: String) -> some View {
         Button(action: {
             NSApp.activate(ignoringOtherApps: true)
-            navigateWeb(to: path)
+            guard let u = URL(string: url) else { return }
+            InAppBrowserWindowManager.shared.open(url: u, title: app.name)
         }) {
-            Text(title)
-                .font(.system(size: 10, weight: .bold))
-                .foregroundStyle(.white.opacity(0.92))
-                .padding(.horizontal, 10)
-                .padding(.vertical, 6)
-                .background(Capsule().fill(Color.white.opacity(0.12)))
+            HStack(spacing: 8) {
+                Image(systemName: systemImage)
+                    .font(.system(size: 14, weight: .semibold))
+                    .foregroundStyle(Color(red: 0.75, green: 0.92, blue: 0.82))
+                Text(title)
+                    .font(.system(size: 12, weight: .bold))
+                    .foregroundStyle(.white.opacity(0.95))
+                Spacer(minLength: 0)
+            }
+            .padding(.horizontal, 12)
+            .padding(.vertical, 10)
+            .background(RoundedRectangle(cornerRadius: 12, style: .continuous).fill(Color.black.opacity(0.28)))
         }
         .buttonStyle(.plain)
     }
 
-    private func createChip(_ title: String, systemImage: String, url: String) -> some View {
+    private func createGridTile(title: String, systemImage: String, path: String) -> some View {
         Button(action: {
             NSApp.activate(ignoringOtherApps: true)
-            guard let u = URL(string: url) else { return }
-            InAppBrowserWindowManager.shared.open(url: u, title: app.name)
+            openDrivePath(path)
         }) {
-            HStack(spacing: 4) {
+            HStack(spacing: 8) {
                 Image(systemName: systemImage)
-                    .font(.system(size: 10, weight: .semibold))
+                    .font(.system(size: 14, weight: .semibold))
+                    .foregroundStyle(Color(red: 0.75, green: 0.92, blue: 0.82))
                 Text(title)
-                    .font(.system(size: 10, weight: .bold))
+                    .font(.system(size: 12, weight: .bold))
+                    .foregroundStyle(.white.opacity(0.95))
+                Spacer(minLength: 0)
             }
-            .foregroundStyle(.white.opacity(0.92))
-            .padding(.horizontal, 10)
-            .padding(.vertical, 6)
-            .background(Capsule().fill(Color.white.opacity(0.12)))
+            .padding(.horizontal, 12)
+            .padding(.vertical, 10)
+            .background(RoundedRectangle(cornerRadius: 12, style: .continuous).fill(Color.black.opacity(0.28)))
+        }
+        .buttonStyle(.plain)
+    }
+
+    private func driveChip(_ title: String, path: String) -> some View {
+        Button(action: {
+            NSApp.activate(ignoringOtherApps: true)
+            if path.hasPrefix("http://") || path.hasPrefix("https://") {
+                if let u = URL(string: path) {
+                    InAppBrowserWindowManager.shared.open(url: u, title: app.name)
+                }
+            } else {
+                openDrivePath(path)
+            }
+        }) {
+            Text(title)
+                .font(.system(size: 10, weight: .bold))
+                .foregroundStyle(.white.opacity(0.92))
+                .padding(.horizontal, 10)
+                .padding(.vertical, 6)
+                .background(Capsule().fill(Color.white.opacity(0.12)))
         }
         .buttonStyle(.plain)
     }
@@ -507,108 +538,11 @@ struct DriveDesktopWidgetView: View {
         openDrivePath(path)
     }
 
-    private func navigateWeb(to path: String) {
-        let url = WidgetDeepLinkURL.resolved(base: baseURL, actionPath: path, accountSlot: 0)
-        DispatchQueue.main.async {
-            webURL = url
-        }
-    }
-
     private func submitSearch() {
         let q = searchText.trimmingCharacters(in: .whitespacesAndNewlines)
         guard !q.isEmpty else { return }
+        NSApp.activate(ignoringOtherApps: true)
         let encoded = q.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) ?? q
-        navigateWeb(to: "/drive/search?q=\(encoded)")
-    }
-}
-
-// MARK: - WKWebView
-
-private final class DriveWebViewClippingContainer: NSView {
-    override var isFlipped: Bool { true }
-}
-
-private struct DriveWebWidgetView: NSViewRepresentable {
-    @Binding var url: URL
-
-    func makeCoordinator() -> Coordinator {
-        Coordinator()
-    }
-
-    func makeNSView(context: Context) -> NSView {
-        let container = DriveWebViewClippingContainer()
-        container.wantsLayer = true
-        container.layer?.masksToBounds = true
-        container.layer?.cornerRadius = 12
-        container.layer?.borderWidth = 0
-
-        let config = WKWebViewConfiguration()
-        config.defaultWebpagePreferences.allowsContentJavaScript = true
-        let wv = WKWebView(frame: .zero, configuration: config)
-        wv.translatesAutoresizingMaskIntoConstraints = false
-        wv.focusRingType = .none
-        wv.customUserAgent =
-            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15"
-        wv.navigationDelegate = context.coordinator
-
-        container.addSubview(wv)
-        NSLayoutConstraint.activate([
-            wv.leadingAnchor.constraint(equalTo: container.leadingAnchor),
-            wv.trailingAnchor.constraint(equalTo: container.trailingAnchor),
-            wv.topAnchor.constraint(equalTo: container.topAnchor),
-            wv.bottomAnchor.constraint(equalTo: container.bottomAnchor),
-        ])
-
-        context.coordinator.webView = wv
-        context.coordinator.lastLoadedURLString = url.absoluteString
-        wv.load(URLRequest(url: url))
-        DispatchQueue.main.async {
-            Self.stripWebKitOuterChrome(wv)
-        }
-        DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
-            Self.stripWebKitOuterChrome(wv)
-        }
-        return container
-    }
-
-    func updateNSView(_ container: NSView, context: Context) {
-        guard let wv = context.coordinator.webView else { return }
-        let next = url.absoluteString
-        guard context.coordinator.lastLoadedURLString != next else { return }
-        context.coordinator.lastLoadedURLString = next
-        wv.load(URLRequest(url: url))
-    }
-
-    private static func stripWebKitOuterChrome(_ webView: WKWebView) {
-        webView.focusRingType = .none
-        func walk(_ view: NSView) {
-            view.focusRingType = .none
-            view.layer?.borderWidth = 0
-            view.layer?.borderColor = NSColor.clear.cgColor
-            if let scroll = view as? NSScrollView {
-                scroll.borderType = .noBorder
-                scroll.drawsBackground = false
-                scroll.scrollerStyle = .overlay
-            }
-            if let clip = view as? NSClipView {
-                clip.drawsBackground = false
-            }
-            for sub in view.subviews {
-                walk(sub)
-            }
-        }
-        walk(webView)
-    }
-
-    final class Coordinator: NSObject, WKNavigationDelegate {
-        weak var webView: WKWebView?
-        var lastLoadedURLString: String?
-
-        func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
-            DriveWebWidgetView.stripWebKitOuterChrome(webView)
-            DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) {
-                DriveWebWidgetView.stripWebKitOuterChrome(webView)
-            }
-        }
+        openDrivePath("/drive/search?q=\(encoded)")
     }
 }

+ 2 - 2
google_apps/Widgets/WidgetTemplates.swift

@@ -30,7 +30,7 @@ enum DriveWidgetMode: Hashable, Sendable {
     case recentQuick
     /// Recent + starred sample rows with open / share.
     case filesPreview
-    /// Embedded Google Drive (`WKWebView`) with search, folders, create, and upload shortcut.
+    /// Shortcut hub: search, folders, quick find, create, and tools — open in app browser (no embedded Drive UI).
     case interactive
 }
 
@@ -42,7 +42,7 @@ enum WidgetLayoutMode: Hashable {
     case interactiveMap
     /// Tiered Gmail: compact preview, inbox list, or full embedded inbox.
     case gmail(GmailWidgetMode)
-    /// Tiered Google Drive: recent files, starred preview, or full embedded Drive.
+    /// Tiered Google Drive: recent files, starred preview, or large shortcut hub.
     case drive(DriveWidgetMode)
 }