ソースを参照

Add Sheets, Slides, and Google Play desktop widgets

- Sheets & Slides: large dashboards (find + create shortcuts) like Forms.
- Google Play: medium search bar with Play Store search and open-store action.
- Wire new layout modes, panel sizing, and key-capable panels.

Made-with: Cursor
huzaifahayat12 3 ヶ月 前
コミット
27891790a1

+ 7 - 1
google_apps/Widgets/DesktopWidgetView.swift

@@ -41,6 +41,12 @@ struct DesktopWidgetView: View {
                 NewsDesktopWidgetView(app: app, mode: newsMode, isPreview: isPreview)
             } else if case .forms(let formsMode) = layoutMode {
                 FormsDesktopWidgetView(app: app, mode: formsMode, isPreview: isPreview)
+            } else if case .sheets(let sheetsMode) = layoutMode {
+                SheetsDesktopWidgetView(app: app, mode: sheetsMode, isPreview: isPreview)
+            } else if case .slides(let slidesMode) = layoutMode {
+                SlidesDesktopWidgetView(app: app, mode: slidesMode, isPreview: isPreview)
+            } else if case .googlePlay(let playMode) = layoutMode {
+                GooglePlayDesktopWidgetView(app: app, mode: playMode, isPreview: isPreview)
             } else if case .interactiveMap = layoutMode {
                 MapsInteractiveWidgetView(app: app, widgetSize: size, isPreview: isPreview)
             } else {
@@ -76,7 +82,7 @@ struct DesktopWidgetView: View {
                             }
                             gridActions(columns: columns, maxActions: maxActionsForSize)
 
-                        case .interactiveMap, .gmail, .drive, .keep, .translate, .earth, .googleSearch, .calendar, .books, .travel, .youtube, .meet, .news, .forms:
+                        case .interactiveMap, .gmail, .drive, .keep, .translate, .earth, .googleSearch, .calendar, .books, .travel, .youtube, .meet, .news, .forms, .sheets, .slides, .googlePlay:
                             EmptyView()
                         }
                     }

+ 133 - 0
google_apps/Widgets/GooglePlayDesktopWidgetView.swift

@@ -0,0 +1,133 @@
+import AppKit
+import SwiftUI
+
+private let playPanelBackground = Color(red: 0.05, green: 0.09, blue: 0.08)
+private let playAccent = Color(red: 0.0, green: 0.53, blue: 0.38)
+
+/// Medium: search the Play Store for apps; open the store home in-app.
+struct GooglePlayDesktopWidgetView: View {
+    let app: LauncherApp
+    let mode: GooglePlayWidgetMode
+    var isPreview: Bool = false
+
+    @State private var searchText: String = ""
+
+    private var baseURL: URL { app.webURL ?? URL(string: "https://play.google.com")! }
+
+    var body: some View {
+        Group {
+            switch mode {
+            case .searchBar:
+                searchBarRoot
+            }
+        }
+        .clipShape(RoundedRectangle(cornerRadius: 22, style: .continuous))
+    }
+
+    private var searchBarRoot: some View {
+        Group {
+            if isPreview {
+                playPreviewChrome
+            } else {
+                searchBarLive
+            }
+        }
+    }
+
+    private var playPreviewChrome: some View {
+        ZStack {
+            RoundedRectangle(cornerRadius: 22, style: .continuous)
+                .fill(
+                    LinearGradient(
+                        colors: [playAccent.opacity(0.85), playPanelBackground],
+                        startPoint: .topLeading,
+                        endPoint: .bottomTrailing
+                    )
+                )
+            VStack(spacing: 12) {
+                AppIconView(app: app, size: 48, showAppBackground: false, iconPaddingFactor: 0.08)
+                Text("Play Store search")
+                    .font(.system(size: 15, weight: .bold))
+                    .foregroundStyle(.white.opacity(0.95))
+                Text("Search apps and games; open the full Play Store in the in-app browser.")
+                    .font(.system(size: 11, weight: .medium))
+                    .foregroundStyle(.white.opacity(0.75))
+                    .multilineTextAlignment(.center)
+                    .padding(.horizontal, 8)
+            }
+            .padding(16)
+        }
+    }
+
+    private var searchBarLive: some View {
+        VStack(alignment: .leading, spacing: 10) {
+            HStack(spacing: 8) {
+                AppIconView(app: app, size: 28, showAppBackground: false, iconPaddingFactor: 0.08)
+                Text("Google Play")
+                    .font(.system(size: 14, weight: .bold))
+                    .foregroundStyle(.white.opacity(0.95))
+                Spacer(minLength: 0)
+            }
+
+            HStack(spacing: 8) {
+                TextField("Search apps & games", text: $searchText)
+                    .textFieldStyle(.plain)
+                    .font(.system(size: 13, weight: .medium))
+                    .padding(.horizontal, 12)
+                    .padding(.vertical, 8)
+                    .background(
+                        RoundedRectangle(cornerRadius: 12, style: .continuous)
+                            .fill(Color.white.opacity(0.1))
+                    )
+                    .foregroundStyle(.white.opacity(0.95))
+                    .onSubmit { submitSearch() }
+                Button("Search", action: submitSearch)
+                    .font(.system(size: 12, weight: .semibold))
+                    .buttonStyle(.borderedProminent)
+                    .tint(playAccent)
+                    .controlSize(.regular)
+            }
+
+            Button(action: openPlayStoreHome) {
+                HStack(spacing: 6) {
+                    Image(systemName: "bag.fill")
+                        .font(.system(size: 13, weight: .semibold))
+                    Text("Open Play Store")
+                        .font(.system(size: 12, weight: .bold))
+                }
+                .foregroundStyle(.white.opacity(0.95))
+                .frame(maxWidth: .infinity)
+                .padding(.vertical, 10)
+                .background(RoundedRectangle(cornerRadius: 12, style: .continuous).fill(Color.white.opacity(0.14)))
+            }
+            .buttonStyle(.plain)
+        }
+        .padding(14)
+        .frame(maxWidth: .infinity, maxHeight: .infinity)
+        .background(
+            RoundedRectangle(cornerRadius: 22, style: .continuous)
+                .fill(playPanelBackground)
+        )
+        .preferredColorScheme(.dark)
+    }
+
+    private func submitSearch() {
+        let q = searchText.trimmingCharacters(in: .whitespacesAndNewlines)
+        guard !q.isEmpty else { return }
+        NSApp.activate(ignoringOtherApps: true)
+        guard var c = URLComponents(url: baseURL, resolvingAgainstBaseURL: false) else { return }
+        c.path = "/store/search"
+        c.queryItems = [
+            URLQueryItem(name: "q", value: q),
+            URLQueryItem(name: "c", value: "apps"),
+        ]
+        guard let url = c.url else { return }
+        InAppBrowserWindowManager.shared.open(url: url, title: app.name)
+    }
+
+    private func openPlayStoreHome() {
+        NSApp.activate(ignoringOtherApps: true)
+        let url = WidgetDeepLinkURL.resolved(base: baseURL, actionPath: "/store/apps", accountSlot: 0)
+        InAppBrowserWindowManager.shared.open(url: url, title: app.name)
+    }
+}

+ 163 - 0
google_apps/Widgets/SheetsDesktopWidgetView.swift

@@ -0,0 +1,163 @@
+import AppKit
+import SwiftUI
+
+private let sheetsAccent = Color(red: 0.13, green: 0.59, blue: 0.38)
+private let sheetsPanelBg = Color(red: 0.06, green: 0.12, blue: 0.09)
+
+private enum SheetsCreateURL {
+    static let newSheet = "https://sheets.new"
+    static let templates = "https://docs.google.com/templates?type=spreadsheets"
+    static let homeList = "https://docs.google.com/spreadsheets/u/0/"
+}
+
+/// Large Google Sheets dashboard: find + create (same pattern as Forms).
+struct SheetsDesktopWidgetView: View {
+    let app: LauncherApp
+    let mode: SheetsWidgetMode
+    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.08, green: 0.32, blue: 0.22),
+                    sheetsPanelBg,
+                ],
+                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 Sheets")
+                    .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: { openAbsolute(SheetsCreateURL.homeList) }) {
+                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 Sheets")
+        }
+    }
+
+    private var findFieldRow: some View {
+        HStack(spacing: 8) {
+            Image(systemName: "magnifyingglass")
+                .font(.system(size: 13, weight: .semibold))
+                .foregroundStyle(.white.opacity(0.5))
+            TextField("Find spreadsheets", 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(sheetsAccent)
+                .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: "Sheet", systemImage: "tablecells", urlString: SheetsCreateURL.newSheet, help: "New spreadsheet (sheets.new)")
+                createPill(title: "Templates", systemImage: "square.grid.2x2", urlString: SheetsCreateURL.templates, help: "Spreadsheet templates")
+                createPill(title: "Browse", systemImage: "folder", urlString: SheetsCreateURL.homeList, help: "All spreadsheets")
+            }
+        }
+    }
+
+    private func createPill(title: String, systemImage: String, urlString: String, help: 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)
+        .help(help)
+    }
+
+    private func sectionLabel(_ text: String) -> some View {
+        Text(text)
+            .font(.system(size: 10, weight: .bold))
+            .foregroundStyle(.white.opacity(0.45))
+            .textCase(.uppercase)
+    }
+
+    private func submitFind() {
+        NSApp.activate(ignoringOtherApps: true)
+        let q = findText.trimmingCharacters(in: .whitespacesAndNewlines)
+        var c = URLComponents(string: SheetsCreateURL.homeList)!
+        if !q.isEmpty {
+            c.queryItems = [URLQueryItem(name: "q", value: q)]
+        }
+        guard let url = c.url 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)
+    }
+}

+ 163 - 0
google_apps/Widgets/SlidesDesktopWidgetView.swift

@@ -0,0 +1,163 @@
+import AppKit
+import SwiftUI
+
+private let slidesAccent = Color(red: 0.96, green: 0.65, blue: 0.14)
+private let slidesPanelBg = Color(red: 0.12, green: 0.09, blue: 0.04)
+
+private enum SlidesCreateURL {
+    static let newDeck = "https://slides.new"
+    static let templates = "https://docs.google.com/templates?type=slides"
+    static let homeList = "https://docs.google.com/presentation/u/0/"
+}
+
+/// Large Google Slides dashboard: find + create (same pattern as Forms).
+struct SlidesDesktopWidgetView: View {
+    let app: LauncherApp
+    let mode: SlidesWidgetMode
+    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.42, green: 0.28, blue: 0.08),
+                    slidesPanelBg,
+                ],
+                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 Slides")
+                    .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: { openAbsolute(SlidesCreateURL.homeList) }) {
+                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 Slides")
+        }
+    }
+
+    private var findFieldRow: some View {
+        HStack(spacing: 8) {
+            Image(systemName: "magnifyingglass")
+                .font(.system(size: 13, weight: .semibold))
+                .foregroundStyle(.white.opacity(0.5))
+            TextField("Find presentations", 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(slidesAccent)
+                .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: "Deck", systemImage: "rectangle.on.rectangle", urlString: SlidesCreateURL.newDeck, help: "New presentation (slides.new)")
+                createPill(title: "Templates", systemImage: "square.grid.2x2", urlString: SlidesCreateURL.templates, help: "Slide templates")
+                createPill(title: "Browse", systemImage: "folder", urlString: SlidesCreateURL.homeList, help: "All presentations")
+            }
+        }
+    }
+
+    private func createPill(title: String, systemImage: String, urlString: String, help: 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)
+        .help(help)
+    }
+
+    private func sectionLabel(_ text: String) -> some View {
+        Text(text)
+            .font(.system(size: 10, weight: .bold))
+            .foregroundStyle(.white.opacity(0.45))
+            .textCase(.uppercase)
+    }
+
+    private func submitFind() {
+        NSApp.activate(ignoringOtherApps: true)
+        let q = findText.trimmingCharacters(in: .whitespacesAndNewlines)
+        var c = URLComponents(string: SlidesCreateURL.homeList)!
+        if !q.isEmpty {
+            c.queryItems = [URLQueryItem(name: "q", value: q)]
+        }
+        guard let url = c.url 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)
+    }
+}

+ 39 - 38
google_apps/Widgets/WidgetTemplates.swift

@@ -101,6 +101,21 @@ enum FormsWidgetMode: Hashable, Sendable {
     case dashboard
 }
 
+enum SheetsWidgetMode: Hashable, Sendable {
+    /// Find + create shortcuts (sheets.new, templates, home); opens in-app browser.
+    case dashboard
+}
+
+enum SlidesWidgetMode: Hashable, Sendable {
+    /// Find + create shortcuts (slides.new, templates, home); opens in-app browser.
+    case dashboard
+}
+
+enum GooglePlayWidgetMode: Hashable, Sendable {
+    /// Medium: app search + shortcut to open the Play Store in-app browser.
+    case searchBar
+}
+
 enum WidgetLayoutMode: Hashable {
     case iconOnly(title: String)
     case actionsRow
@@ -133,6 +148,12 @@ enum WidgetLayoutMode: Hashable {
     case news(NewsWidgetMode)
     /// Google Forms: large interactive dashboard; small/medium use default grids.
     case forms(FormsWidgetMode)
+    /// Google Sheets: large dashboard like Forms.
+    case sheets(SheetsWidgetMode)
+    /// Google Slides: large dashboard like Forms.
+    case slides(SlidesWidgetMode)
+    /// Google Play: medium search bar + open store.
+    case googlePlay(GooglePlayWidgetMode)
 }
 
 struct WidgetVariant: Identifiable, Hashable {
@@ -202,42 +223,12 @@ enum WidgetTemplates {
         case .sheets:
             return [
                 v("sheets_small", "Sheets", .small, false, .iconOnly(title: "Sheets"), [a("home", "Open", "tablecells.fill", "/spreadsheets/u/0/")]),
-                v("sheets_medium", "Sheets Shortcuts", .medium, true, .actionsGrid(columns: 2), [
-                    a("recent", "Recent", "clock.arrow.circlepath", "/spreadsheets/u/0/"),
-                    a("new", "New sheet", "plus", "/spreadsheets/create"),
-                    a("templates", "Templates", "square.grid.2x2", "/spreadsheets/u/0/templates"),
-                    a("search", "Search", "magnifyingglass", "/spreadsheets/u/0/search"),
-                ]),
-                v("sheets_large", "Sheets Hub", .large, true, .actionsGrid(columns: 2), [
-                    a("recent", "Recent", "clock.arrow.circlepath", "/spreadsheets/u/0/"),
-                    a("new", "New sheet", "plus", "/spreadsheets/create"),
-                    a("templates", "Templates", "square.grid.2x2", "/spreadsheets/u/0/templates"),
-                    a("starred", "Starred", "star", "/spreadsheets/u/0/starred"),
-                    a("shared", "Shared", "person.2", "/spreadsheets/u/0/shared-with-me"),
-                    a("trash", "Trash", "trash", "/spreadsheets/u/0/trash"),
-                    a("search", "Search", "magnifyingglass", "/spreadsheets/u/0/search"),
-                    a("offline", "Offline", "arrow.down.circle", "/spreadsheets/u/0/offline"),
-                ]),
+                v("sheets_large", "Sheets dashboard", .large, false, .sheets(.dashboard), []),
             ]
         case .slides:
             return [
                 v("slides_small", "Slides", .small, false, .iconOnly(title: "Slides"), [a("home", "Open", "rectangle.on.rectangle.fill", "/presentation/u/0/")]),
-                v("slides_medium", "Slides Shortcuts", .medium, true, .actionsGrid(columns: 2), [
-                    a("recent", "Recent", "clock.arrow.circlepath", "/presentation/u/0/"),
-                    a("new", "New deck", "plus", "/presentation/create"),
-                    a("templates", "Templates", "square.grid.2x2", "/presentation/u/0/templates"),
-                    a("search", "Search", "magnifyingglass", "/presentation/u/0/search"),
-                ]),
-                v("slides_large", "Slides Hub", .large, true, .actionsGrid(columns: 2), [
-                    a("recent", "Recent", "clock.arrow.circlepath", "/presentation/u/0/"),
-                    a("new", "New deck", "plus", "/presentation/create"),
-                    a("templates", "Templates", "square.grid.2x2", "/presentation/u/0/templates"),
-                    a("starred", "Starred", "star", "/presentation/u/0/starred"),
-                    a("shared", "Shared", "person.2", "/presentation/u/0/shared-with-me"),
-                    a("trash", "Trash", "trash", "/presentation/u/0/trash"),
-                    a("search", "Search", "magnifyingglass", "/presentation/u/0/search"),
-                    a("offline", "Offline", "arrow.down.circle", "/presentation/u/0/offline"),
-                ]),
+                v("slides_large", "Slides dashboard", .large, false, .slides(.dashboard), []),
             ]
         case .forms:
             return [
@@ -355,12 +346,7 @@ enum WidgetTemplates {
         case .play:
             return [
                 v("play_small", "Play", .small, false, .iconOnly(title: "Play"), [a("store", "Store", "play.circle.fill", "/store/apps")]),
-                v("play_medium", "Play Store", .medium, true, .actionsGrid(columns: 2), [
-                    a("apps", "Apps", "app.badge", "/store/apps"),
-                    a("games", "Games", "gamecontroller", "/store/games"),
-                    a("movies", "Movies", "film", "/store/movies"),
-                    a("books", "Books", "book", "/store/books"),
-                ]),
+                v("play_medium", "Play search", .medium, false, .googlePlay(.searchBar), []),
             ]
         case .news:
             return [
@@ -592,6 +578,9 @@ extension WidgetLayoutMode {
         case .youtube(.interactiveNav): true
         case .news(_): true
         case .forms(_): true
+        case .sheets(_): true
+        case .slides(_): true
+        case .googlePlay(_): true
         case .meet: false
         case .calendar: false
         default: false
@@ -650,6 +639,12 @@ extension WidgetLayoutMode {
             }
         case .forms(.dashboard):
             return CGSize(width: 440, height: 300)
+        case .sheets(.dashboard):
+            return CGSize(width: 440, height: 300)
+        case .slides(.dashboard):
+            return CGSize(width: 440, height: 300)
+        case .googlePlay(.searchBar):
+            return CGSize(width: 400, height: 168)
         default:
             return nil
         }
@@ -706,6 +701,12 @@ extension WidgetLayoutMode {
             }
         case .forms(.dashboard):
             return CGSize(width: 380, height: 280)
+        case .sheets(.dashboard):
+            return CGSize(width: 380, height: 280)
+        case .slides(.dashboard):
+            return CGSize(width: 380, height: 280)
+        case .googlePlay(.searchBar):
+            return CGSize(width: 360, height: 158)
         default:
             return nil
         }