Ver código fonte

Add large Google Search widget and simplify chrome

- New GoogleSearchDesktopWidgetView: toolbar with quick search, shortcuts,
  mic/open actions; results open in in-app browser (no embedded web view)
- WidgetLayoutMode.googleSearch(.interactive), panel and preview sizes
- DesktopWidgetView wiring; keep small/medium Search shortcut variants
- Remove extra translucent ZStack; single dark card; shorter panel height

Made-with: Cursor
huzaifahayat12 3 meses atrás
pai
commit
b569b86f81

+ 3 - 1
google_apps/Widgets/DesktopWidgetView.swift

@@ -25,6 +25,8 @@ struct DesktopWidgetView: View {
                 TranslateDesktopWidgetView(app: app, mode: translateMode, isPreview: isPreview)
             } else if case .earth(let earthMode) = layoutMode {
                 EarthDesktopWidgetView(app: app, mode: earthMode, isPreview: isPreview)
+            } else if case .googleSearch(let searchMode) = layoutMode {
+                GoogleSearchDesktopWidgetView(app: app, mode: searchMode, isPreview: isPreview)
             } else if case .interactiveMap = layoutMode {
                 MapsInteractiveWidgetView(app: app, widgetSize: size, isPreview: isPreview)
             } else {
@@ -60,7 +62,7 @@ struct DesktopWidgetView: View {
                             }
                             gridActions(columns: columns, maxActions: maxActionsForSize)
 
-                        case .interactiveMap, .gmail, .drive, .keep, .translate, .earth:
+                        case .interactiveMap, .gmail, .drive, .keep, .translate, .earth, .googleSearch:
                             EmptyView()
                         }
                     }

+ 188 - 0
google_apps/Widgets/GoogleSearchDesktopWidgetView.swift

@@ -0,0 +1,188 @@
+import AppKit
+import SwiftUI
+
+/// Large Google Search widget: compact launcher with quick search, voice shortcut, and category chips.
+/// Results open in the in-app browser (no embedded web view below the toolbar).
+struct GoogleSearchDesktopWidgetView: View {
+    let app: LauncherApp
+    let mode: GoogleSearchWidgetMode
+    var isPreview: Bool = false
+
+    @State private var quickQuery = ""
+
+    private var baseURL: URL { app.webURL ?? URL(string: "https://www.google.com")! }
+
+    var body: some View {
+        Group {
+            switch mode {
+            case .interactive:
+                interactiveBody
+            }
+        }
+        .clipShape(RoundedRectangle(cornerRadius: 22, style: .continuous))
+    }
+
+    private var interactiveBody: some View {
+        Group {
+            if isPreview {
+                searchPreviewChrome
+            } else {
+                searchLiveChrome
+            }
+        }
+    }
+
+    private var searchPreviewChrome: some View {
+        ZStack {
+            RoundedRectangle(cornerRadius: 22, style: .continuous)
+                .fill(
+                    LinearGradient(
+                        colors: [Color(red: 0.15, green: 0.45, blue: 0.95), Color(red: 0.05, green: 0.15, blue: 0.4)],
+                        startPoint: .topLeading,
+                        endPoint: .bottomTrailing
+                    )
+                )
+            VStack(spacing: 12) {
+                AppIconView(app: app, size: 48, showAppBackground: false, iconPaddingFactor: 0.08)
+                Text("Google Search")
+                    .font(.system(size: 15, weight: .bold))
+                    .foregroundStyle(.white.opacity(0.92))
+                Text("Quick search and shortcuts open results in your browser.")
+                    .font(.system(size: 11, weight: .medium))
+                    .foregroundStyle(.white.opacity(0.7))
+                    .multilineTextAlignment(.center)
+                    .padding(.horizontal, 8)
+            }
+            .padding(16)
+        }
+    }
+
+    private var searchLiveChrome: some View {
+        searchToolbar
+            .padding(12)
+    }
+
+    private var searchToolbar: some View {
+        VStack(alignment: .leading, spacing: 8) {
+            HStack(spacing: 8) {
+                AppIconView(app: app, size: 26, showAppBackground: false, iconPaddingFactor: 0.08)
+                Text("Google")
+                    .font(.system(size: 14, weight: .bold))
+                    .foregroundStyle(.white.opacity(0.95))
+                Spacer(minLength: 0)
+                Button(action: {
+                    NSApp.activate(ignoringOtherApps: true)
+                    openInAppBrowser(baseURL)
+                }) {
+                    Image(systemName: "mic.fill")
+                        .font(.system(size: 13, weight: .semibold))
+                        .foregroundStyle(.white.opacity(0.9))
+                        .padding(8)
+                        .background(Circle().fill(Color.white.opacity(0.12)))
+                }
+                .buttonStyle(.plain)
+                .help("Open Google in browser to use voice search")
+                Button(action: {
+                    NSApp.activate(ignoringOtherApps: true)
+                    openInAppBrowser(baseURL)
+                }) {
+                    Image(systemName: "arrow.up.right.square")
+                        .font(.system(size: 13, weight: .semibold))
+                        .foregroundStyle(.white.opacity(0.9))
+                        .padding(8)
+                        .background(Circle().fill(Color.white.opacity(0.12)))
+                }
+                .buttonStyle(.plain)
+                .help("Open Google in browser")
+            }
+
+            HStack(spacing: 8) {
+                TextField("Search", text: $quickQuery)
+                    .textFieldStyle(.plain)
+                    .font(.system(size: 12, weight: .medium))
+                    .foregroundStyle(.white.opacity(0.95))
+                    .padding(.horizontal, 10)
+                    .padding(.vertical, 8)
+                    .background(
+                        RoundedRectangle(cornerRadius: 10, style: .continuous)
+                            .fill(Color.white.opacity(0.1))
+                    )
+                    .onSubmit { submitQuickSearch() }
+                Button("Go") {
+                    submitQuickSearch()
+                }
+                .font(.system(size: 11, weight: .bold))
+                .foregroundStyle(Color(red: 0.25, green: 0.55, blue: 1))
+                .buttonStyle(.plain)
+            }
+
+            ScrollView(.horizontal, showsIndicators: false) {
+                HStack(spacing: 8) {
+                    searchChip("Trending", systemImage: "chart.line.uptrend.xyaxis") {
+                        openURL(URL(string: "https://trends.google.com/trending?geo=US")!)
+                    }
+                    searchChip("Images", systemImage: "photo") {
+                        openURL(WidgetDeepLinkURL.resolved(base: baseURL, actionPath: "/imghp", accountSlot: 0))
+                    }
+                    searchChip("Maps", systemImage: "map") {
+                        openURL(URL(string: "https://maps.google.com/maps")!)
+                    }
+                    searchChip("Shopping", systemImage: "bag") {
+                        openURL(WidgetDeepLinkURL.resolved(base: baseURL, actionPath: "/search?tbm=shop", accountSlot: 0))
+                    }
+                    searchChip("News", systemImage: "newspaper") {
+                        openURL(URL(string: "https://news.google.com")!)
+                    }
+                }
+            }
+        }
+        .padding(.horizontal, 10)
+        .padding(.vertical, 10)
+        .background(
+            RoundedRectangle(cornerRadius: 22, style: .continuous)
+                .fill(Color(red: 0.06, green: 0.1, blue: 0.18))
+        )
+    }
+
+    private func searchChip(_ title: String, systemImage: String, action: @escaping () -> Void) -> some View {
+        Button(action: {
+            NSApp.activate(ignoringOtherApps: true)
+            action()
+        }) {
+            HStack(spacing: 4) {
+                Image(systemName: systemImage)
+                    .font(.system(size: 11, weight: .semibold))
+                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)))
+            .contentShape(Capsule())
+        }
+        .buttonStyle(.plain)
+    }
+
+    private func submitQuickSearch() {
+        let q = quickQuery.trimmingCharacters(in: .whitespacesAndNewlines)
+        guard !q.isEmpty else { return }
+        navigateToSearch(q)
+    }
+
+    private func navigateToSearch(_ query: String) {
+        var c = URLComponents(url: baseURL, resolvingAgainstBaseURL: false)
+        c?.path = "/search"
+        c?.queryItems = [URLQueryItem(name: "q", value: query)]
+        guard let u = c?.url else { return }
+        openInAppBrowser(u)
+    }
+
+    private func openURL(_ url: URL) {
+        openInAppBrowser(url)
+    }
+
+    private func openInAppBrowser(_ url: URL) {
+        InAppBrowserWindowManager.shared.open(url: url, title: app.name)
+    }
+}

+ 13 - 0
google_apps/Widgets/WidgetTemplates.swift

@@ -51,6 +51,11 @@ enum EarthWidgetMode: Hashable, Sendable {
     case interactive
 }
 
+enum GoogleSearchWidgetMode: Hashable, Sendable {
+    /// Embedded google.com (`WKWebView`): search, voice, trends, recents.
+    case interactive
+}
+
 enum WidgetLayoutMode: Hashable {
     case iconOnly(title: String)
     case actionsRow
@@ -67,6 +72,8 @@ enum WidgetLayoutMode: Hashable {
     case translate(TranslateWidgetMode)
     /// Embedded Google Earth Web: full globe UI in-panel.
     case earth(EarthWidgetMode)
+    /// Embedded Google Search (`WKWebView`): full search experience in-panel.
+    case googleSearch(GoogleSearchWidgetMode)
 }
 
 struct WidgetVariant: Identifiable, Hashable {
@@ -273,6 +280,7 @@ enum WidgetTemplates {
                     a("shopping", "Shopping", "bag", "https://www.google.com/search?tbm=shop"),
                     a("maps", "Maps", "map", "https://maps.google.com/maps"),
                 ]),
+                v("search_large", "Full Search", .large, false, .googleSearch(.interactive), []),
             ]
         case .earth:
             return [
@@ -590,6 +598,7 @@ extension WidgetLayoutMode {
         case .keep(.interactive): true
         case .translate(.interactive): true
         case .earth(.interactive): true
+        case .googleSearch(.interactive): true
         default: false
         }
     }
@@ -618,6 +627,8 @@ extension WidgetLayoutMode {
             return CGSize(width: 560, height: 640)
         case .earth(.interactive):
             return CGSize(width: 580, height: 640)
+        case .googleSearch(.interactive):
+            return CGSize(width: 480, height: 220)
         default:
             return nil
         }
@@ -646,6 +657,8 @@ extension WidgetLayoutMode {
             return CGSize(width: 480, height: 460)
         case .earth(.interactive):
             return CGSize(width: 490, height: 470)
+        case .googleSearch(.interactive):
+            return CGSize(width: 440, height: 210)
         default:
             return nil
         }