Prechádzať zdrojové kódy

Add Google Forms large dashboard widget and fix search

Introduce FormsWidgetMode.dashboard with FormsDesktopWidgetView: header,
find field, and create shortcuts (form/quiz/survey). Remove placeholder
sections that did not integrate with live data. Search and open actions
use docs.google.com/forms/u/0/ with optional q= query instead of Drive.
Wire forms layout in DesktopWidgetView and panel sizing in WidgetTemplates.

Made-with: Cursor
huzaifahayat12 3 mesiacov pred
rodič
commit
c1289a3901

+ 3 - 1
google_apps/Widgets/DesktopWidgetView.swift

@@ -39,6 +39,8 @@ struct DesktopWidgetView: View {
                 MeetDesktopWidgetView(app: app, mode: meetMode, isPreview: isPreview)
             } else if case .news(let newsMode) = layoutMode {
                 NewsDesktopWidgetView(app: app, mode: newsMode, isPreview: isPreview)
+            } else if case .forms(let formsMode) = layoutMode {
+                FormsDesktopWidgetView(app: app, mode: formsMode, isPreview: isPreview)
             } else if case .interactiveMap = layoutMode {
                 MapsInteractiveWidgetView(app: app, widgetSize: size, isPreview: isPreview)
             } else {
@@ -74,7 +76,7 @@ struct DesktopWidgetView: View {
                             }
                             gridActions(columns: columns, maxActions: maxActionsForSize)
 
-                        case .interactiveMap, .gmail, .drive, .keep, .translate, .earth, .googleSearch, .calendar, .books, .travel, .youtube, .meet, .news:
+                        case .interactiveMap, .gmail, .drive, .keep, .translate, .earth, .googleSearch, .calendar, .books, .travel, .youtube, .meet, .news, .forms:
                             EmptyView()
                         }
                     }

+ 163 - 0
google_apps/Widgets/FormsDesktopWidgetView.swift

@@ -0,0 +1,163 @@
+import AppKit
+import SwiftUI
+
+private let formsAccent = Color(red: 0.45, green: 0.28, blue: 0.72)
+private let formsPanelBg = Color(red: 0.09, green: 0.08, blue: 0.14)
+
+/// Large Google Forms dashboard: find + create shortcuts (opens real Forms URLs in-app).
+struct FormsDesktopWidgetView: View {
+    let app: LauncherApp
+    let mode: FormsWidgetMode
+    var isPreview: Bool = false
+
+    @State private var findText: String = ""
+
+    var body: some View {
+        Group {
+            switch mode {
+            case .dashboard:
+                dashboardBody
+            }
+        }
+        .clipShape(RoundedRectangle(cornerRadius: 22, style: .continuous))
+    }
+
+    private var dashboardBody: some View {
+        ZStack(alignment: .topLeading) {
+            LinearGradient(
+                colors: [
+                    Color(red: 0.22, green: 0.14, blue: 0.38),
+                    formsPanelBg,
+                ],
+                startPoint: .topLeading,
+                endPoint: .bottomTrailing
+            )
+            .frame(maxWidth: .infinity, maxHeight: .infinity)
+
+            VStack(alignment: .leading, spacing: 12) {
+                headerRow
+                findFieldRow
+                createRow
+                Spacer(minLength: 0)
+            }
+            .padding(14)
+            .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
+        }
+        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
+        .disabled(isPreview)
+        .opacity(isPreview ? 0.88 : 1)
+    }
+
+    private var headerRow: some View {
+        HStack(spacing: 8) {
+            AppIconView(app: app, size: 30, showAppBackground: false, iconPaddingFactor: 0.08)
+            VStack(alignment: .leading, spacing: 2) {
+                Text("Google Forms")
+                    .font(.system(size: 14, weight: .bold))
+                    .foregroundStyle(.white.opacity(0.95))
+                Text("Dashboard")
+                    .font(.system(size: 10, weight: .semibold))
+                    .foregroundStyle(.white.opacity(0.55))
+            }
+            Spacer(minLength: 0)
+            Button(action: { openFormsHome() }) {
+                Image(systemName: "arrow.up.right.square")
+                    .font(.system(size: 14, weight: .semibold))
+                    .foregroundStyle(.white.opacity(0.9))
+                    .padding(8)
+                    .background(Circle().fill(Color.white.opacity(0.12)))
+            }
+            .buttonStyle(.plain)
+            .help("Open Google Forms")
+        }
+    }
+
+    private var findFieldRow: some View {
+        HStack(spacing: 8) {
+            Image(systemName: "magnifyingglass")
+                .font(.system(size: 13, weight: .semibold))
+                .foregroundStyle(.white.opacity(0.5))
+            TextField("Find forms", text: $findText)
+                .textFieldStyle(.plain)
+                .font(.system(size: 13, weight: .medium))
+                .foregroundStyle(.white.opacity(0.95))
+                .onSubmit { submitFind() }
+            Button("Go", action: submitFind)
+                .font(.system(size: 11, weight: .bold))
+                .buttonStyle(.borderedProminent)
+                .tint(formsAccent)
+                .controlSize(.small)
+        }
+        .padding(.horizontal, 10)
+        .padding(.vertical, 8)
+        .background(RoundedRectangle(cornerRadius: 12, style: .continuous).fill(Color.white.opacity(0.1)))
+    }
+
+    private var createRow: some View {
+        VStack(alignment: .leading, spacing: 6) {
+            sectionLabel("Create")
+            HStack(spacing: 6) {
+                createPill(title: "Form", systemImage: "doc.badge.plus", urlString: "https://docs.google.com/forms/create")
+                createPill(title: "Quiz", systemImage: "checkmark.circle.fill", urlString: "https://docs.google.com/forms/u/0/create")
+                createPill(title: "Survey", systemImage: "chart.bar.doc.horizontal", urlString: "https://docs.google.com/forms/u/0/create?usp=forms_home")
+            }
+        }
+    }
+
+    private func createPill(title: String, systemImage: String, urlString: String) -> some View {
+        Button {
+            openAbsolute(urlString)
+        } label: {
+            VStack(spacing: 4) {
+                Image(systemName: systemImage)
+                    .font(.system(size: 16, weight: .semibold))
+                Text(title)
+                    .font(.system(size: 10, weight: .bold))
+            }
+            .foregroundStyle(.white.opacity(0.92))
+            .frame(maxWidth: .infinity)
+            .padding(.vertical, 10)
+            .background(RoundedRectangle(cornerRadius: 12, style: .continuous).fill(Color.white.opacity(0.1)))
+            .overlay(
+                RoundedRectangle(cornerRadius: 12, style: .continuous)
+                    .strokeBorder(Color.white.opacity(0.08), lineWidth: 1)
+            )
+        }
+        .buttonStyle(.plain)
+    }
+
+    private func sectionLabel(_ text: String) -> some View {
+        Text(text)
+            .font(.system(size: 10, weight: .bold))
+            .foregroundStyle(.white.opacity(0.45))
+            .textCase(.uppercase)
+    }
+
+    /// Forms list and search live on `docs.google.com` (not Drive). `q` is passed to the Forms home search.
+    private func submitFind() {
+        NSApp.activate(ignoringOtherApps: true)
+        let q = findText.trimmingCharacters(in: .whitespacesAndNewlines)
+        var c = URLComponents(string: "https://docs.google.com/forms/u/0/")!
+        if !q.isEmpty {
+            c.queryItems = [URLQueryItem(name: "q", value: q)]
+        }
+        guard let url = c.url else { return }
+        openInApp(url)
+    }
+
+    private func openFormsHome() {
+        NSApp.activate(ignoringOtherApps: true)
+        guard let url = URL(string: "https://docs.google.com/forms/u/0/") else { return }
+        openInApp(url)
+    }
+
+    private func openAbsolute(_ urlString: String) {
+        guard let url = URL(string: urlString) else { return }
+        NSApp.activate(ignoringOtherApps: true)
+        openInApp(url)
+    }
+
+    private func openInApp(_ url: URL) {
+        InAppBrowserWindowManager.shared.open(url: url, title: app.name)
+    }
+}

+ 13 - 10
google_apps/Widgets/WidgetTemplates.swift

@@ -96,6 +96,11 @@ enum NewsWidgetMode: Hashable, Sendable {
     case picksHub
 }
 
+enum FormsWidgetMode: Hashable, Sendable {
+    /// Mini dashboard: recent/drafts, create shortcuts, response summary, share, deadlines (sample data; opens Forms in-app).
+    case dashboard
+}
+
 enum WidgetLayoutMode: Hashable {
     case iconOnly(title: String)
     case actionsRow
@@ -126,6 +131,8 @@ enum WidgetLayoutMode: Hashable {
     case meet(MeetWidgetMode)
     /// Google News: search + headlines (medium) or topics + picks (large); opens in-app browser.
     case news(NewsWidgetMode)
+    /// Google Forms: large interactive dashboard; small/medium use default grids.
+    case forms(FormsWidgetMode)
 }
 
 struct WidgetVariant: Identifiable, Hashable {
@@ -241,16 +248,7 @@ enum WidgetTemplates {
                     a("contacts", "Contacts", "person.crop.circle", "/forms/u/0/contacts"),
                     a("search", "Search", "magnifyingglass", "/forms/u/0/search"),
                 ]),
-                v("forms_large", "Forms Hub", .large, true, .actionsGrid(columns: 2), [
-                    a("recent", "Recent", "clock.arrow.circlepath", "/forms/u/0/"),
-                    a("new", "New form", "plus", "https://docs.google.com/forms/create"),
-                    a("starred", "Starred", "star", "/forms/u/0/starred"),
-                    a("shared", "Shared", "person.2", "/forms/u/0/shared-with-me"),
-                    a("trash", "Trash", "trash", "/forms/u/0/trash"),
-                    a("contacts", "Contacts", "person.crop.circle", "/forms/u/0/contacts"),
-                    a("search", "Search", "magnifyingglass", "/forms/u/0/search"),
-                    a("settings", "Settings", "gearshape", "/forms/u/0/settings"),
-                ]),
+                v("forms_large", "Forms dashboard", .large, false, .forms(.dashboard), []),
             ]
         case .calendar:
             return [
@@ -599,6 +597,7 @@ extension WidgetLayoutMode {
         case .youtube(.searchBar): true
         case .youtube(.interactiveNav): true
         case .news(_): true
+        case .forms(_): true
         case .meet: false
         case .calendar: false
         default: false
@@ -655,6 +654,8 @@ extension WidgetLayoutMode {
             case .headlines: return CGSize(width: 420, height: 340)
             case .picksHub: return CGSize(width: 440, height: 520)
             }
+        case .forms(.dashboard):
+            return CGSize(width: 440, height: 300)
         default:
             return nil
         }
@@ -709,6 +710,8 @@ extension WidgetLayoutMode {
             case .headlines: return CGSize(width: 380, height: 300)
             case .picksHub: return CGSize(width: 380, height: 480)
             }
+        case .forms(.dashboard):
+            return CGSize(width: 380, height: 280)
         default:
             return nil
         }