Sfoglia il codice sorgente

Add per-app desktop widget shortcuts and shared WidgetAppProfile

Replace generic widget action templates with product-specific URLs for Drive,
Workspace apps, Calendar, Maps, YouTube, and other Google shortcuts. Introduce
WidgetAppProfile to centralize host/name matching used by WidgetTemplates.

Variant IDs are preserved where possible; new presets include calendar_large
and youtube_medium.

Made-with: Cursor
huzaifahayat12 3 mesi fa
parent
commit
4cf5ee057d

+ 78 - 0
google_apps/Widgets/WidgetAppProfile.swift

@@ -0,0 +1,78 @@
+import Foundation
+
+/// Shared profile for widget templates and visual styling (host/name rules stay in sync).
+enum WidgetAppProfile: Hashable, Sendable {
+    case photos
+    case gmail
+    case drive
+    case docs
+    case sheets
+    case slides
+    case forms
+    case calendar
+    case maps
+    case youtube
+    case translate
+    case search
+    case earth
+    case shopping
+    case keep
+    case travel
+    case meet
+    case contacts
+    case blogger
+    case play
+    case news
+    case chat
+    case finance
+    case jamboard
+    case classroom
+    case artsCulture
+    case voice
+    case chromeWebStore
+    case fi
+    case ads
+    case remoteDesktop
+    case gemini
+    case books
+    case unknown
+
+    static func from(app: LauncherApp) -> WidgetAppProfile {
+        let host = app.webURL?.host?.lowercased() ?? ""
+        let name = app.name.lowercased()
+        if host.contains("photos.google.com") || name.contains("photos") { return .photos }
+        if host.contains("mail.google.com") || name == "gmail" { return .gmail }
+        if host.contains("drive.google.com") { return .drive }
+        if host.contains("docs.google.com") { return .docs }
+        if host.contains("sheets.google.com") { return .sheets }
+        if host.contains("slides.google.com") { return .slides }
+        if host.contains("forms.google.com") { return .forms }
+        if host.contains("calendar.google.com") { return .calendar }
+        if host.contains("maps.google.com") { return .maps }
+        if host.contains("youtube.com") { return .youtube }
+        if host.contains("translate.google.com") { return .translate }
+        if host == "www.google.com" || host == "google.com" { return .search }
+        if host.contains("earth.google.com") { return .earth }
+        if host.contains("shopping.google.com") { return .shopping }
+        if host.contains("keep.google.com") { return .keep }
+        if host.contains("travel.google.com") { return .travel }
+        if host.contains("meet.google.com") { return .meet }
+        if host.contains("contacts.google.com") { return .contacts }
+        if host.contains("blogger.com") { return .blogger }
+        if host.contains("play.google.com") { return .play }
+        if host.contains("news.google.com") { return .news }
+        if host.contains("chat.google.com") { return .chat }
+        if host.contains("/finance") || name.contains("finance") { return .finance }
+        if host.contains("jamboard.google.com") { return .jamboard }
+        if host.contains("classroom.google.com") { return .classroom }
+        if host.contains("artsandculture.google.com") { return .artsCulture }
+        if host.contains("voice.google.com") { return .voice }
+        if host.contains("chrome.google.com") { return .chromeWebStore }
+        if host.contains("fi.google.com") { return .fi }
+        if host.contains("ads.google.com") { return .ads }
+        if host.contains("remotedesktop.google.com") { return .remoteDesktop }
+        if host.contains("gemini.google.com") { return .gemini }
+        if host.contains("books.google.com") { return .books }
+        return .unknown
+    }
+}

+ 398 - 196
google_apps/Widgets/WidgetTemplates.swift

@@ -32,13 +32,11 @@ struct WidgetVariant: Identifiable, Hashable {
 
 enum WidgetTemplates {
     static func variants(for app: LauncherApp) -> [WidgetVariant] {
-        let base = app.webURL
-
         func v(_ id: String, _ title: String, _ size: WidgetSize, _ showHeader: Bool, _ layout: WidgetLayoutMode, _ actions: [WidgetAction]) -> WidgetVariant {
             WidgetVariant(id: id, title: title, size: size, showHeader: showHeader, layoutMode: layout, actions: actions)
         }
 
-        let profile = appProfile(for: app)
+        let profile = WidgetAppProfile.from(app: app)
         switch profile {
         case .photos:
             return [
@@ -80,15 +78,105 @@ enum WidgetTemplates {
                 ]),
             ]
         case .drive:
-            return contentWorkspaceVariants(prefix: "drive", appName: "Drive", rootPath: "/drive/my-drive", searchPath: "/drive/search")
+            return [
+                v("drive_small", "My Drive", .small, false, .iconOnly(title: "My Drive"), [a("mydrive", "Open", "externaldrive.fill", "/drive/my-drive")]),
+                v("drive_medium", "Drive Shortcuts", .medium, true, .actionsGrid(columns: 2), [
+                    a("mydrive", "My Drive", "externaldrive.fill", "/drive/my-drive"),
+                    a("shared", "Shared", "person.2", "/drive/shared-with-me"),
+                    a("recent", "Recent", "clock.arrow.circlepath", "/drive/recent"),
+                    a("starred", "Starred", "star", "/drive/starred"),
+                ]),
+                v("drive_large", "Drive Hub", .large, true, .actionsGrid(columns: 2), [
+                    a("mydrive", "My Drive", "externaldrive.fill", "/drive/my-drive"),
+                    a("shared", "Shared with me", "person.2", "/drive/shared-with-me"),
+                    a("recent", "Recent", "clock.arrow.circlepath", "/drive/recent"),
+                    a("starred", "Starred", "star", "/drive/starred"),
+                    a("trash", "Trash", "trash", "/drive/trash"),
+                    a("storage", "Storage", "internaldrive", "/settings/storage"),
+                    a("search", "Search", "magnifyingglass", "/drive/search"),
+                    a("computers", "Computers", "desktopcomputer", "/drive/computers"),
+                ]),
+            ]
         case .docs:
-            return contentWorkspaceVariants(prefix: "docs", appName: "Docs", rootPath: "/document/u/0/", searchPath: "/document/u/0/")
+            return [
+                v("docs_small", "Docs", .small, false, .iconOnly(title: "Docs"), [a("home", "Open", "doc.text.fill", "/document/u/0/")]),
+                v("docs_medium", "Docs Shortcuts", .medium, true, .actionsGrid(columns: 2), [
+                    a("recent", "Recent", "clock.arrow.circlepath", "/document/u/0/"),
+                    a("new", "New doc", "plus", "/document/create"),
+                    a("templates", "Templates", "square.grid.2x2", "/document/u/0/templates/home"),
+                    a("search", "Search", "magnifyingglass", "/document/u/0/search"),
+                ]),
+                v("docs_large", "Docs Hub", .large, true, .actionsGrid(columns: 2), [
+                    a("recent", "Recent", "clock.arrow.circlepath", "/document/u/0/"),
+                    a("new", "New doc", "plus", "/document/create"),
+                    a("templates", "Templates", "square.grid.2x2", "/document/u/0/templates/home"),
+                    a("starred", "Starred", "star", "/document/u/0/starred"),
+                    a("shared", "Shared", "person.2", "/document/u/0/shared-with-me"),
+                    a("trash", "Trash", "trash", "/document/u/0/trash"),
+                    a("search", "Search", "magnifyingglass", "/document/u/0/search"),
+                    a("offline", "Offline", "arrow.down.circle", "/document/u/0/offline"),
+                ]),
+            ]
         case .sheets:
-            return contentWorkspaceVariants(prefix: "sheets", appName: "Sheets", rootPath: "/spreadsheets/u/0/", searchPath: "/spreadsheets/u/0/")
+            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"),
+                ]),
+            ]
         case .slides:
-            return contentWorkspaceVariants(prefix: "slides", appName: "Slides", rootPath: "/presentation/u/0/", searchPath: "/presentation/u/0/")
+            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"),
+                ]),
+            ]
         case .forms:
-            return contentWorkspaceVariants(prefix: "forms", appName: "Forms", rootPath: "/forms/u/0/", searchPath: "/forms/u/0/")
+            return [
+                v("forms_small", "Forms", .small, false, .iconOnly(title: "Forms"), [a("home", "Open", "list.bullet.rectangle.fill", "/forms/u/0/")]),
+                v("forms_medium", "Forms Shortcuts", .medium, 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("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"),
+                ]),
+            ]
         case .calendar:
             return [
                 v("calendar_small", "Today", .small, false, .actionsRow, [
@@ -97,22 +185,32 @@ enum WidgetTemplates {
                 ]),
                 v("calendar_medium", "Schedule", .medium, true, .actionsGrid(columns: 2), [
                     a("today", "Today", "calendar", "/calendar/u/0/r"),
-                    a("week", "Week", "calendar.badge.clock", "/calendar/u/0/r"),
+                    a("week", "Week", "calendar.badge.clock", "/calendar/u/0/r/week"),
+                    a("create", "New Event", "plus.circle", "/calendar/u/0/r/eventedit"),
+                    a("tasks", "Tasks", "checkmark.square", "https://tasks.google.com/"),
+                ]),
+                v("calendar_large", "Calendar Hub", .large, true, .actionsGrid(columns: 2), [
+                    a("today", "Today", "calendar", "/calendar/u/0/r"),
+                    a("week", "Week", "calendar.badge.clock", "/calendar/u/0/r/week"),
+                    a("month", "Month", "calendar", "/calendar/u/0/r/month"),
                     a("create", "New Event", "plus.circle", "/calendar/u/0/r/eventedit"),
-                    a("tasks", "Tasks", "checkmark.square", "/calendar/u/0/r"),
+                    a("tasks", "Tasks", "checkmark.square", "https://tasks.google.com/"),
+                    a("settings", "Settings", "gearshape", "/calendar/u/0/r/settings"),
+                    a("search", "Search", "magnifyingglass", "/calendar/u/0/r/search"),
+                    a("print", "Print", "printer", "/calendar/u/0/r/print"),
                 ]),
             ]
         case .maps:
             return [
-                v("maps_small", "Places", .small, false, .actionsRow, [
-                    a("search", "Search", "magnifyingglass", "/"),
-                    a("nearby", "Nearby", "mappin.and.ellipse", "/"),
+                v("maps_small", "Maps", .small, false, .actionsRow, [
+                    a("search", "Search", "magnifyingglass", "https://www.google.com/maps"),
+                    a("directions", "Directions", "arrow.triangle.turn.up.right.diamond", "https://www.google.com/maps/dir/"),
                 ]),
-                v("maps_medium", "Routing", .medium, true, .actionsGrid(columns: 2), [
-                    a("search", "Search", "magnifyingglass", "/"),
-                    a("directions", "Directions", "arrow.triangle.turn.up.right.diamond", "/"),
-                    a("saved", "Saved", "bookmark", "/"),
-                    a("traffic", "Traffic", "car", "/"),
+                v("maps_medium", "Places & routes", .medium, true, .actionsGrid(columns: 2), [
+                    a("search", "Search", "magnifyingglass", "https://www.google.com/maps"),
+                    a("directions", "Directions", "arrow.triangle.turn.up.right.diamond", "https://www.google.com/maps/dir/"),
+                    a("saved", "Saved", "bookmark", "https://www.google.com/maps/saved"),
+                    a("traffic", "Traffic", "car", "https://www.google.com/maps/@?layer=traffic"),
                 ]),
             ]
         case .youtube:
@@ -122,6 +220,12 @@ enum WidgetTemplates {
                     a("shorts", "Shorts", "bolt", "/shorts"),
                     a("subs", "Subs", "play.rectangle", "/feed/subscriptions"),
                 ]),
+                v("youtube_medium", "Browse", .medium, true, .actionsGrid(columns: 2), [
+                    a("home", "Home", "house", "/"),
+                    a("trending", "Trending", "flame", "/feed/trending"),
+                    a("shorts", "Shorts", "bolt", "/shorts"),
+                    a("subs", "Subscriptions", "play.rectangle", "/feed/subscriptions"),
+                ]),
                 v("youtube_large", "Library", .large, true, .actionsGrid(columns: 2), [
                     a("home", "Home", "house", "/"),
                     a("shorts", "Shorts", "play.square", "/shorts"),
@@ -132,53 +236,297 @@ enum WidgetTemplates {
                 ]),
             ]
         case .translate:
-            return utilityVariants(prefix: "translate", appName: "Translate", basePath: "/")
+            return [
+                v("translate_small", "Translate", .small, false, .actionsRow, [
+                    a("open", "Translator", "character.bubble.fill", "/"),
+                    a("history", "History", "clock", "/m/"),
+                    a("saved", "Phrasebook", "bookmark", "/m/"),
+                ]),
+                v("translate_medium", "Translate Tools", .medium, true, .actionsGrid(columns: 2), [
+                    a("open", "Web translator", "globe", "/"),
+                    a("docs", "Documents", "doc.text", "https://translate.google.com/?tr=f&hl=en&tab=wT"),
+                    a("conversation", "Conversation", "bubble.left.and.bubble.right", "/m/"),
+                    a("history", "History", "clock", "/m/"),
+                ]),
+            ]
         case .search:
-            return utilityVariants(prefix: "search", appName: "Search", basePath: "/")
+            return [
+                v("search_small", "Search", .small, false, .actionsRow, [
+                    a("web", "Web", "globe", "/"),
+                    a("images", "Images", "photo", "https://www.google.com/imghp"),
+                    a("maps", "Maps", "map", "https://maps.google.com/maps"),
+                ]),
+                v("search_medium", "Google Shortcuts", .medium, true, .actionsGrid(columns: 2), [
+                    a("web", "Web", "globe", "/"),
+                    a("images", "Images", "photo", "https://www.google.com/imghp"),
+                    a("shopping", "Shopping", "bag", "https://www.google.com/search?tbm=shop"),
+                    a("maps", "Maps", "map", "https://maps.google.com/maps"),
+                ]),
+            ]
         case .earth:
-            return utilityVariants(prefix: "earth", appName: "Earth", basePath: "/web/")
+            return [
+                v("earth_small", "Earth", .small, false, .actionsRow, [
+                    a("open", "Open", "globe.americas.fill", "/web/"),
+                    a("projects", "Projects", "folder", "/web/projects/"),
+                    a("search", "Search", "magnifyingglass", "/web/search"),
+                ]),
+                v("earth_medium", "Earth Explorer", .medium, true, .actionsGrid(columns: 2), [
+                    a("open", "Open", "globe.americas.fill", "/web/"),
+                    a("voyager", "Voyager", "sailboat", "/web/data/"),
+                    a("projects", "Projects", "folder", "/web/projects/"),
+                    a("search", "Search", "magnifyingglass", "/web/search"),
+                ]),
+            ]
         case .shopping:
-            return mediaVariants(prefix: "shopping", appName: "Shopping", basePath: "/")
+            return [
+                v("shopping_small", "Shopping", .small, false, .iconOnly(title: "Shopping"), [a("open", "Open", "bag.fill", "/")]),
+                v("shopping_medium", "Shopping Browse", .medium, true, .actionsGrid(columns: 2), [
+                    a("home", "Home", "house", "/"),
+                    a("deals", "Deals", "tag", "/"),
+                    a("saved", "Saved", "bookmark", "/"),
+                    a("search", "Search", "magnifyingglass", "/"),
+                ]),
+            ]
         case .keep:
-            return utilityVariants(prefix: "keep", appName: "Keep", basePath: "/")
+            return [
+                v("keep_small", "Keep", .small, false, .actionsRow, [
+                    a("notes", "Notes", "note.text", "/"),
+                    a("reminders", "Reminders", "bell", "/#reminders"),
+                    a("archive", "Archive", "archivebox", "/#archive"),
+                ]),
+                v("keep_medium", "Keep Hub", .medium, true, .actionsGrid(columns: 2), [
+                    a("notes", "Notes", "note.text", "/"),
+                    a("reminders", "Reminders", "bell", "/#reminders"),
+                    a("labels", "Labels", "tag", "/#label"),
+                    a("archive", "Archive", "archivebox", "/#archive"),
+                ]),
+            ]
         case .travel:
-            return mediaVariants(prefix: "travel", appName: "Travel", basePath: "/")
+            return [
+                v("travel_small", "Travel", .small, false, .iconOnly(title: "Travel"), [a("trips", "Trips", "airplane", "/travel")]),
+                v("travel_medium", "Trip Planner", .medium, true, .actionsGrid(columns: 2), [
+                    a("trips", "Trips", "airplane", "/travel"),
+                    a("explore", "Explore", "globe", "/travel/explore"),
+                    a("flights", "Flights", "airplane.departure", "/flights"),
+                    a("hotels", "Hotels", "bed.double", "/hotels"),
+                ]),
+            ]
         case .meet:
-            return communicationVariants(prefix: "meet", appName: "Meet", basePath: "/")
+            return [
+                v("meet_small", "Meet", .small, false, .actionsRow, [
+                    a("join", "Join", "video.badge.plus", "/"),
+                    a("new", "New", "plus.circle", "/new"),
+                    a("landing", "Home", "house", "/landing"),
+                ]),
+                v("meet_large", "Meet Hub", .large, true, .actionsGrid(columns: 2), [
+                    a("new", "New meeting", "video.badge.plus", "/new"),
+                    a("join", "Join", "person.wave.2", "/"),
+                    a("landing", "Home", "house", "/landing"),
+                    a("schedule", "Schedule", "calendar", "https://calendar.google.com/calendar/u/0/r/eventedit"),
+                ]),
+            ]
         case .contacts:
-            return utilityVariants(prefix: "contacts", appName: "Contacts", basePath: "/")
+            return [
+                v("contacts_small", "Contacts", .small, false, .actionsRow, [
+                    a("contacts", "Contacts", "person.crop.circle", "/"),
+                    a("duplicates", "Duplicates", "person.2", "/duplicates"),
+                    a("labels", "Labels", "tag", "/"),
+                ]),
+                v("contacts_medium", "Contacts Hub", .medium, true, .actionsGrid(columns: 2), [
+                    a("contacts", "All", "person.crop.circle", "/"),
+                    a("frequent", "Frequent", "clock", "/"),
+                    a("duplicates", "Duplicates", "person.2", "/duplicates"),
+                    a("trash", "Trash", "trash", "/trash"),
+                ]),
+            ]
         case .blogger:
-            return mediaVariants(prefix: "blogger", appName: "Blogger", basePath: "/")
+            return [
+                v("blogger_small", "Blogger", .small, false, .iconOnly(title: "Blogger"), [a("home", "Open", "text.book.closed.fill", "/")]),
+                v("blogger_medium", "Blogger Tools", .medium, true, .actionsGrid(columns: 2), [
+                    a("home", "Reading list", "list.bullet", "/"),
+                    a("create", "New post", "square.and.pencil", "/"),
+                    a("reading", "Reading list", "book", "/reading-list"),
+                    a("search", "Search", "magnifyingglass", "/"),
+                ]),
+            ]
         case .play:
-            return mediaVariants(prefix: "play", appName: "Play", basePath: "/")
+            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"),
+                ]),
+            ]
         case .news:
-            return mediaVariants(prefix: "news", appName: "News", basePath: "/")
+            return [
+                v("news_small", "News", .small, false, .iconOnly(title: "News"), [a("home", "Headlines", "newspaper.fill", "/home")]),
+                v("news_medium", "News Feeds", .medium, true, .actionsGrid(columns: 2), [
+                    a("foryou", "For you", "person.crop.circle", "/foryou"),
+                    a("headlines", "Headlines", "newspaper", "/topstories"),
+                    a("topics", "Topics", "square.grid.2x2", "/topics"),
+                    a("search", "Search", "magnifyingglass", "/search"),
+                ]),
+            ]
         case .chat:
-            return communicationVariants(prefix: "chat", appName: "Chat", basePath: "/")
+            return [
+                v("chat_small", "Chat", .small, false, .actionsRow, [
+                    a("home", "Home", "bubble.left.and.bubble.right.fill", "/u/0/"),
+                    a("spaces", "Spaces", "square.grid.3x3.fill", "/u/0/spaces"),
+                    a("meet", "Meet", "video", "https://meet.google.com/"),
+                ]),
+                v("chat_large", "Chat Hub", .large, true, .actionsGrid(columns: 2), [
+                    a("home", "Home", "house", "/u/0/"),
+                    a("spaces", "Spaces", "square.grid.3x3.fill", "/u/0/spaces"),
+                    a("browse", "Browse", "square.grid.2x2", "/u/0/browse"),
+                    a("meet", "Meet", "video", "https://meet.google.com/"),
+                ]),
+            ]
         case .finance:
-            return mediaVariants(prefix: "finance", appName: "Finance", basePath: "/finance")
+            return [
+                v("finance_small", "Finance", .small, false, .actionsRow, [
+                    a("home", "Markets", "chart.line.uptrend.xyaxis", "/finance"),
+                    a("markets", "Markets", "chart.bar", "/finance/markets"),
+                    a("portfolio", "Portfolio", "briefcase", "/finance/portfolio"),
+                ]),
+                v("finance_medium", "Finance Hub", .medium, true, .actionsGrid(columns: 2), [
+                    a("home", "Home", "house", "/finance"),
+                    a("markets", "Markets", "chart.bar", "/finance/markets"),
+                    a("portfolio", "Portfolio", "briefcase", "/finance/portfolio"),
+                    a("news", "News", "newspaper", "/finance/news"),
+                ]),
+            ]
         case .jamboard:
-            return communicationVariants(prefix: "jamboard", appName: "Jamboard", basePath: "/")
+            return [
+                v("jamboard_small", "Jamboard", .small, false, .actionsRow, [
+                    a("open", "Boards", "paintbrush.fill", "/"),
+                    a("create", "New jam", "plus", "/"),
+                    a("shared", "Shared", "person.2", "/"),
+                ]),
+                v("jamboard_large", "Jamboard", .large, true, .actionsGrid(columns: 2), [
+                    a("open", "Home", "house", "/"),
+                    a("create", "New jam", "plus", "/"),
+                    a("shared", "Shared with me", "person.2", "/"),
+                    a("trash", "Trash", "trash", "/"),
+                ]),
+            ]
         case .classroom:
-            return communicationVariants(prefix: "classroom", appName: "Classroom", basePath: "/")
+            return [
+                v("classroom_small", "Classroom", .small, false, .actionsRow, [
+                    a("home", "Classes", "graduationcap.fill", "/u/0/h/"),
+                    a("calendar", "Calendar", "calendar", "/u/0/calendar"),
+                    a("todo", "To-do", "checklist", "/u/0/notifications"),
+                ]),
+                v("classroom_large", "Classroom Hub", .large, true, .actionsGrid(columns: 2), [
+                    a("home", "Home", "house", "/u/0/h/"),
+                    a("calendar", "Calendar", "calendar", "/u/0/calendar"),
+                    a("archive", "Archive", "archivebox", "/u/0/archived"),
+                    a("settings", "Settings", "gearshape", "/u/0/settings"),
+                ]),
+            ]
         case .artsCulture:
-            return mediaVariants(prefix: "arts", appName: "Arts & Culture", basePath: "/")
+            return [
+                v("arts_small", "Arts & Culture", .small, false, .iconOnly(title: "Arts"), [a("home", "Explore", "building.columns.fill", "/")]),
+                v("arts_medium", "Arts Browse", .medium, true, .actionsGrid(columns: 2), [
+                    a("home", "Home", "house", "/"),
+                    a("explore", "Explore", "globe", "/explore"),
+                    a("nearby", "Near you", "mappin.and.ellipse", "/nearby"),
+                    a("search", "Search", "magnifyingglass", "/search"),
+                ]),
+            ]
         case .voice:
-            return communicationVariants(prefix: "voice", appName: "Voice", basePath: "/")
+            return [
+                v("voice_small", "Voice", .small, false, .actionsRow, [
+                    a("messages", "Messages", "bubble.left", "/messages"),
+                    a("calls", "Calls", "phone", "/calls"),
+                    a("voicemail", "Voicemail", "recordingtape", "/voicemail"),
+                ]),
+                v("voice_large", "Voice Hub", .large, true, .actionsGrid(columns: 2), [
+                    a("messages", "Messages", "bubble.left", "/messages"),
+                    a("calls", "Calls", "phone", "/calls"),
+                    a("voicemail", "Voicemail", "recordingtape", "/voicemail"),
+                    a("settings", "Settings", "gearshape", "/settings"),
+                ]),
+            ]
         case .chromeWebStore:
-            return mediaVariants(prefix: "webstore", appName: "Web Store", basePath: "/")
+            return [
+                v("webstore_small", "Web Store", .small, false, .iconOnly(title: "Web Store"), [a("home", "Store", "bag.fill", "/webstore")]),
+                v("webstore_medium", "Chrome Web Store", .medium, true, .actionsGrid(columns: 2), [
+                    a("extensions", "Extensions", "puzzlepiece.extension", "/webstore/category/extensions"),
+                    a("themes", "Themes", "paintpalette", "/webstore/category/themes"),
+                    a("home", "Home", "house", "/webstore"),
+                    a("search", "Search", "magnifyingglass", "/webstore/search"),
+                ]),
+            ]
         case .fi:
-            return utilityVariants(prefix: "fi", appName: "Fi", basePath: "/")
+            return [
+                v("fi_small", "Fi", .small, false, .actionsRow, [
+                    a("home", "Account", "antenna.radiowaves.left.and.right", "/"),
+                    a("billing", "Billing", "creditcard", "/billing"),
+                    a("coverage", "Coverage", "map", "/coverage"),
+                ]),
+                v("fi_medium", "Google Fi", .medium, true, .actionsGrid(columns: 2), [
+                    a("home", "Home", "house", "/"),
+                    a("billing", "Billing", "creditcard", "/billing"),
+                    a("coverage", "Coverage", "map", "/coverage"),
+                    a("support", "Support", "questionmark.circle", "/support"),
+                ]),
+            ]
         case .ads:
-            return utilityVariants(prefix: "ads", appName: "Ads", basePath: "/")
+            return [
+                v("ads_small", "Ads", .small, false, .actionsRow, [
+                    a("home", "Overview", "megaphone.fill", "/aw/"),
+                    a("campaigns", "Campaigns", "chart.bar", "/aw/campaigns"),
+                    a("reports", "Reports", "doc.text", "/aw/reports"),
+                ]),
+                v("ads_medium", "Google Ads", .medium, true, .actionsGrid(columns: 2), [
+                    a("home", "Home", "house", "/aw/"),
+                    a("campaigns", "Campaigns", "chart.bar", "/aw/campaigns"),
+                    a("tools", "Tools", "wrench", "/aw/tools"),
+                    a("billing", "Billing", "creditcard", "/aw/billing"),
+                ]),
+            ]
         case .remoteDesktop:
-            return utilityVariants(prefix: "remote", appName: "Remote Desktop", basePath: "/")
+            return [
+                v("remote_small", "Remote", .small, false, .actionsRow, [
+                    a("access", "Remote access", "desktopcomputer", "/access"),
+                    a("support", "Support", "person.crop.circle", "/support"),
+                    a("downloads", "Downloads", "arrow.down.circle", "/"),
+                ]),
+                v("remote_large", "Remote Desktop", .large, true, .actionsGrid(columns: 2), [
+                    a("access", "Remote access", "desktopcomputer", "/access"),
+                    a("support", "Support", "person.crop.circle", "/support"),
+                    a("downloads", "Downloads", "arrow.down.circle", "/"),
+                    a("help", "Help", "questionmark.circle", "/support"),
+                ]),
+            ]
         case .gemini:
-            return communicationVariants(prefix: "gemini", appName: "Gemini", basePath: "/")
+            return [
+                v("gemini_small", "Gemini", .small, false, .actionsRow, [
+                    a("app", "Gemini", "sparkles", "/app"),
+                    a("home", "Home", "house", "/"),
+                    a("extensions", "Extensions", "puzzlepiece", "/extensions"),
+                ]),
+                v("gemini_large", "Gemini Hub", .large, true, .actionsGrid(columns: 2), [
+                    a("app", "Chat", "sparkles", "/app"),
+                    a("home", "Home", "house", "/"),
+                    a("extensions", "Extensions", "puzzlepiece", "/extensions"),
+                    a("help", "Help", "questionmark.circle", "/help"),
+                ]),
+            ]
         case .books:
-            return mediaVariants(prefix: "books", appName: "Books", basePath: "/")
+            return [
+                v("books_small", "Books", .small, false, .iconOnly(title: "Books"), [a("home", "Library", "book.fill", "/ebooks")]),
+                v("books_medium", "Google Books", .medium, true, .actionsGrid(columns: 2), [
+                    a("library", "My books", "books.vertical", "/ebooks/library"),
+                    a("shop", "Shop", "cart", "/ebooks"),
+                    a("audiobooks", "Audiobooks", "headphones", "/audiobooks"),
+                    a("search", "Search", "magnifyingglass", "/ebooks/search"),
+                ]),
+            ]
         case .unknown:
-            return utilityVariants(prefix: "generic", appName: app.name, basePath: "/")
+            return genericUtilityVariants(prefix: "generic", appName: app.name)
         }
     }
 
@@ -199,152 +547,7 @@ enum WidgetTemplates {
 }
 
 private extension WidgetTemplates {
-    enum AppWidgetProfile {
-        case photos, gmail, drive, docs, sheets, slides, forms
-        case calendar, maps, youtube, translate, search, earth, shopping
-        case keep, travel, meet, contacts, blogger, play, news, chat, finance
-        case jamboard, classroom, artsCulture, voice, chromeWebStore, fi, ads
-        case remoteDesktop, gemini, books
-        case unknown
-    }
-
-    static func appProfile(for app: LauncherApp) -> AppWidgetProfile {
-        let host = app.webURL?.host?.lowercased() ?? ""
-        let name = app.name.lowercased()
-        if host.contains("photos.google.com") || name.contains("photos") { return .photos }
-        if host.contains("mail.google.com") || name == "gmail" { return .gmail }
-        if host.contains("drive.google.com") { return .drive }
-        if host.contains("docs.google.com") { return .docs }
-        if host.contains("sheets.google.com") { return .sheets }
-        if host.contains("slides.google.com") { return .slides }
-        if host.contains("forms.google.com") { return .forms }
-        if host.contains("calendar.google.com") { return .calendar }
-        if host.contains("maps.google.com") { return .maps }
-        if host.contains("youtube.com") { return .youtube }
-        if host.contains("translate.google.com") { return .translate }
-        if host == "www.google.com" || host == "google.com" { return .search }
-        if host.contains("earth.google.com") { return .earth }
-        if host.contains("shopping.google.com") { return .shopping }
-        if host.contains("keep.google.com") { return .keep }
-        if host.contains("travel.google.com") { return .travel }
-        if host.contains("meet.google.com") { return .meet }
-        if host.contains("contacts.google.com") { return .contacts }
-        if host.contains("blogger.com") { return .blogger }
-        if host.contains("play.google.com") { return .play }
-        if host.contains("news.google.com") { return .news }
-        if host.contains("chat.google.com") { return .chat }
-        if host.contains("/finance") || name.contains("finance") { return .finance }
-        if host.contains("jamboard.google.com") { return .jamboard }
-        if host.contains("classroom.google.com") { return .classroom }
-        if host.contains("artsandculture.google.com") { return .artsCulture }
-        if host.contains("voice.google.com") { return .voice }
-        if host.contains("chrome.google.com") { return .chromeWebStore }
-        if host.contains("fi.google.com") { return .fi }
-        if host.contains("ads.google.com") { return .ads }
-        if host.contains("remotedesktop.google.com") { return .remoteDesktop }
-        if host.contains("gemini.google.com") { return .gemini }
-        if host.contains("books.google.com") { return .books }
-        return .unknown
-    }
-
-    static func contentWorkspaceVariants(prefix: String, appName: String, rootPath: String, searchPath: String) -> [WidgetVariant] {
-        [
-            WidgetVariant(
-                id: "\(prefix)_small",
-                title: appName,
-                size: .small,
-                showHeader: false,
-                layoutMode: .iconOnly(title: appName),
-                actions: [a("open", "Open", "arrow.up.right", rootPath)]
-            ),
-            WidgetVariant(
-                id: "\(prefix)_medium",
-                title: "Recent",
-                size: .medium,
-                showHeader: true,
-                layoutMode: .actionsGrid(columns: 2),
-                actions: [
-                    a("open", "Open", "arrow.up.right", rootPath),
-                    a("search", "Search", "magnifyingglass", searchPath),
-                    a("favorites", "Favorites", "star", rootPath),
-                    a("shared", "Shared", "person.2", rootPath),
-                ]
-            ),
-            WidgetVariant(
-                id: "\(prefix)_large",
-                title: "Workspace",
-                size: .large,
-                showHeader: true,
-                layoutMode: .actionsGrid(columns: 2),
-                actions: [
-                    a("open", "Open", "arrow.up.right", rootPath),
-                    a("search", "Search", "magnifyingglass", searchPath),
-                    a("new", "New", "plus", rootPath),
-                    a("recent", "Recent", "clock.arrow.circlepath", rootPath),
-                    a("starred", "Starred", "star", rootPath),
-                    a("shared", "Shared", "person.2", rootPath),
-                ]
-            ),
-        ]
-    }
-
-    static func communicationVariants(prefix: String, appName: String, basePath: String) -> [WidgetVariant] {
-        [
-            WidgetVariant(
-                id: "\(prefix)_small",
-                title: "Quick",
-                size: .small,
-                showHeader: false,
-                layoutMode: .actionsRow,
-                actions: [
-                    a("open", "Open", "arrow.up.right", basePath),
-                    a("recent", "Recent", "clock", basePath),
-                    a("contacts", "Contacts", "person.2", basePath),
-                ]
-            ),
-            WidgetVariant(
-                id: "\(prefix)_large",
-                title: appName,
-                size: .large,
-                showHeader: true,
-                layoutMode: .actionsGrid(columns: 2),
-                actions: [
-                    a("open", "Open", "arrow.up.right", basePath),
-                    a("messages", "Messages", "bubble.left.and.bubble.right", basePath),
-                    a("meetings", "Meetings", "video", basePath),
-                    a("contacts", "Contacts", "person.2", basePath),
-                ]
-            ),
-        ]
-    }
-
-    static func mediaVariants(prefix: String, appName: String, basePath: String) -> [WidgetVariant] {
-        [
-            WidgetVariant(
-                id: "\(prefix)_small",
-                title: appName,
-                size: .small,
-                showHeader: false,
-                layoutMode: .iconOnly(title: appName),
-                actions: [a("open", "Open", "arrow.up.right", basePath)]
-            ),
-            WidgetVariant(
-                id: "\(prefix)_medium",
-                title: "Browse",
-                size: .medium,
-                showHeader: true,
-                layoutMode: .actionsGrid(columns: 2),
-                actions: [
-                    a("open", "Open", "arrow.up.right", basePath),
-                    a("trending", "Trending", "chart.line.uptrend.xyaxis", basePath),
-                    a("saved", "Saved", "bookmark", basePath),
-                    a("search", "Search", "magnifyingglass", basePath),
-                ]
-            ),
-        ]
-    }
-
-    static func utilityVariants(prefix: String, appName: String, basePath: String) -> [WidgetVariant] {
+    static func genericUtilityVariants(prefix: String, appName: String) -> [WidgetVariant] {
         [
             WidgetVariant(
                 id: "\(prefix)_small",
@@ -353,9 +556,9 @@ private extension WidgetTemplates {
                 showHeader: false,
                 layoutMode: .actionsRow,
                 actions: [
-                    a("open", "Open", "arrow.up.right", basePath),
-                    a("search", "Search", "magnifyingglass", basePath),
-                    a("favorites", "Favorites", "star", basePath),
+                    a("open", "Open", "arrow.up.right", "/"),
+                    a("search", "Search", "magnifyingglass", "/"),
+                    a("favorites", "Favorites", "star", "/"),
                 ]
             ),
             WidgetVariant(
@@ -365,10 +568,10 @@ private extension WidgetTemplates {
                 showHeader: true,
                 layoutMode: .actionsGrid(columns: 2),
                 actions: [
-                    a("open", "Open", "arrow.up.right", basePath),
-                    a("search", "Search", "magnifyingglass", basePath),
-                    a("favorites", "Favorites", "star", basePath),
-                    a("recent", "Recent", "clock.arrow.circlepath", basePath),
+                    a("open", "Open", "arrow.up.right", "/"),
+                    a("search", "Search", "magnifyingglass", "/"),
+                    a("favorites", "Favorites", "star", "/"),
+                    a("recent", "Recent", "clock.arrow.circlepath", "/"),
                 ]
             ),
         ]
@@ -378,4 +581,3 @@ private extension WidgetTemplates {
         WidgetAction(id: id, title: title, systemImage: image, urlPath: path)
     }
 }
-