|
|
@@ -0,0 +1,243 @@
|
|
|
+import Foundation
|
|
|
+
|
|
|
+struct WidgetAction: Identifiable, Hashable {
|
|
|
+ let id: String
|
|
|
+ let title: String
|
|
|
+ let systemImage: String
|
|
|
+ /// If nil, opens the app base URL.
|
|
|
+ let urlPath: String?
|
|
|
+
|
|
|
+ init(id: String, title: String, systemImage: String, urlPath: String? = nil) {
|
|
|
+ self.id = id
|
|
|
+ self.title = title
|
|
|
+ self.systemImage = systemImage
|
|
|
+ self.urlPath = urlPath
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+enum WidgetTemplateKind: String, CaseIterable, Codable, Hashable {
|
|
|
+ case quickActions
|
|
|
+}
|
|
|
+
|
|
|
+enum WidgetLayoutMode: Hashable {
|
|
|
+ case iconOnly(title: String)
|
|
|
+ case actionsRow(maxActions: Int, columns: Int = 3)
|
|
|
+ case actionsGrid(columns: Int, maxActions: Int)
|
|
|
+}
|
|
|
+
|
|
|
+struct WidgetLayoutConfig: Hashable {
|
|
|
+ let mode: WidgetLayoutMode
|
|
|
+ let showHeader: Bool
|
|
|
+}
|
|
|
+
|
|
|
+enum WidgetTemplates {
|
|
|
+ static func actions(for app: LauncherApp, kind: WidgetTemplateKind = .quickActions) -> [WidgetAction] {
|
|
|
+ guard kind == .quickActions else { return defaultActions(for: app) }
|
|
|
+
|
|
|
+ let name = app.name.lowercased()
|
|
|
+ let host = app.webURL?.host?.lowercased() ?? ""
|
|
|
+
|
|
|
+ if name.contains("photos") || host.contains("photos.google.com") {
|
|
|
+ // Matches the example UI.
|
|
|
+ return [
|
|
|
+ WidgetAction(id: "photos", title: "Photos", systemImage: "photo.on.rectangle", urlPath: "/"),
|
|
|
+ WidgetAction(id: "albums", title: "Albums", systemImage: "rectangle.stack", urlPath: "/albums"),
|
|
|
+ WidgetAction(id: "explore", title: "Explore", systemImage: "magnifyingglass", urlPath: "/search"),
|
|
|
+ WidgetAction(id: "archive", title: "Archive", systemImage: "archivebox", urlPath: "/archive"),
|
|
|
+ WidgetAction(id: "sharing", title: "Sharing", systemImage: "person.2", urlPath: "/sharing"),
|
|
|
+ WidgetAction(id: "locked", title: "Locked Folder", systemImage: "lock", urlPath: "/lockedfolder"),
|
|
|
+ WidgetAction(id: "favorites", title: "Favorites", systemImage: "star", urlPath: "/favorites"),
|
|
|
+ WidgetAction(id: "trash", title: "Trash", systemImage: "trash", urlPath: "/trash"),
|
|
|
+ ]
|
|
|
+ }
|
|
|
+
|
|
|
+ if name.contains("gmail") || host.contains("mail.google.com") {
|
|
|
+ return [
|
|
|
+ WidgetAction(id: "compose", title: "Compose", systemImage: "square.and.pencil", urlPath: "/mail/u/0/#inbox?compose=new"),
|
|
|
+ WidgetAction(id: "inbox", title: "Inbox", systemImage: "tray", urlPath: "/mail/u/0/#inbox"),
|
|
|
+ WidgetAction(id: "starred", title: "Starred", systemImage: "star", urlPath: "/mail/u/0/#starred"),
|
|
|
+ WidgetAction(id: "sent", title: "Sent", systemImage: "paperplane", urlPath: "/mail/u/0/#sent"),
|
|
|
+ WidgetAction(id: "drafts", title: "Drafts", systemImage: "doc.plaintext", urlPath: "/mail/u/0/#drafts"),
|
|
|
+ WidgetAction(id: "snoozed", title: "Snoozed", systemImage: "clock", urlPath: "/mail/u/0/#snoozed"),
|
|
|
+ WidgetAction(id: "important", title: "Important", systemImage: "exclamationmark.circle", urlPath: "/mail/u/0/#imp"),
|
|
|
+ WidgetAction(id: "trash", title: "Trash", systemImage: "trash", urlPath: "/mail/u/0/#trash"),
|
|
|
+ ]
|
|
|
+ }
|
|
|
+
|
|
|
+ if name.contains("drive") || host.contains("drive.google.com") {
|
|
|
+ return [
|
|
|
+ WidgetAction(id: "myDrive", title: "My Drive", systemImage: "externaldrive", urlPath: "/drive/my-drive"),
|
|
|
+ WidgetAction(id: "recent", title: "Recent", systemImage: "clock.arrow.circlepath", urlPath: "/drive/recent"),
|
|
|
+ WidgetAction(id: "starred", title: "Starred", systemImage: "star", urlPath: "/drive/starred"),
|
|
|
+ WidgetAction(id: "shared", title: "Shared", systemImage: "person.2", urlPath: "/drive/shared-with-me"),
|
|
|
+ WidgetAction(id: "trash", title: "Trash", systemImage: "trash", urlPath: "/drive/trash"),
|
|
|
+ WidgetAction(id: "new", title: "New", systemImage: "plus", urlPath: "/drive/my-drive"),
|
|
|
+ ]
|
|
|
+ }
|
|
|
+
|
|
|
+ if name.contains("docs") || host.contains("docs.google.com") {
|
|
|
+ return [
|
|
|
+ WidgetAction(id: "newDoc", title: "New Doc", systemImage: "doc.badge.plus", urlPath: "/document/u/0/"),
|
|
|
+ WidgetAction(id: "recent", title: "Recent", systemImage: "clock.arrow.circlepath", urlPath: "/document/u/0/"),
|
|
|
+ WidgetAction(id: "templates", title: "Templates", systemImage: "square.grid.2x2", urlPath: "/document/u/0/"),
|
|
|
+ WidgetAction(id: "shared", title: "Shared", systemImage: "person.2", urlPath: "/document/u/0/"),
|
|
|
+ ]
|
|
|
+ }
|
|
|
+
|
|
|
+ if name.contains("sheets") || host.contains("sheets.google.com") {
|
|
|
+ return [
|
|
|
+ WidgetAction(id: "newSheet", title: "New Sheet", systemImage: "tablecells.badge.ellipsis", urlPath: "/spreadsheets/u/0/"),
|
|
|
+ WidgetAction(id: "recent", title: "Recent", systemImage: "clock.arrow.circlepath", urlPath: "/spreadsheets/u/0/"),
|
|
|
+ WidgetAction(id: "templates", title: "Templates", systemImage: "square.grid.2x2", urlPath: "/spreadsheets/u/0/"),
|
|
|
+ ]
|
|
|
+ }
|
|
|
+
|
|
|
+ if name.contains("calendar") || host.contains("calendar.google.com") {
|
|
|
+ return [
|
|
|
+ WidgetAction(id: "today", title: "Today", systemImage: "calendar", urlPath: "/calendar/u/0/r"),
|
|
|
+ WidgetAction(id: "create", title: "Create", systemImage: "plus.circle", urlPath: "/calendar/u/0/r/eventedit"),
|
|
|
+ WidgetAction(id: "schedule", title: "Schedule", systemImage: "list.bullet.rectangle", urlPath: "/calendar/u/0/r"),
|
|
|
+ ]
|
|
|
+ }
|
|
|
+
|
|
|
+ if name.contains("youtube") || host.contains("youtube.com") {
|
|
|
+ return [
|
|
|
+ WidgetAction(id: "home", title: "Home", systemImage: "house", urlPath: "/"),
|
|
|
+ WidgetAction(id: "subscriptions", title: "Subs", systemImage: "play.rectangle", urlPath: "/feed/subscriptions"),
|
|
|
+ WidgetAction(id: "library", title: "Library", systemImage: "books.vertical", urlPath: "/feed/library"),
|
|
|
+ WidgetAction(id: "history", title: "History", systemImage: "clock", urlPath: "/feed/history"),
|
|
|
+ WidgetAction(id: "watchLater", title: "Watch Later", systemImage: "bookmark", urlPath: "/playlist?list=WL"),
|
|
|
+ WidgetAction(id: "shorts", title: "Shorts", systemImage: "bolt", urlPath: "/shorts"),
|
|
|
+ ]
|
|
|
+ }
|
|
|
+
|
|
|
+ if name.contains("maps") || host.contains("maps.google.com") {
|
|
|
+ return [
|
|
|
+ WidgetAction(id: "search", title: "Search", systemImage: "magnifyingglass", urlPath: "/"),
|
|
|
+ WidgetAction(id: "directions", title: "Directions", systemImage: "arrow.triangle.turn.up.right.diamond", urlPath: "/"),
|
|
|
+ WidgetAction(id: "saved", title: "Saved", systemImage: "bookmark", urlPath: "/"),
|
|
|
+ ]
|
|
|
+ }
|
|
|
+
|
|
|
+ return defaultActions(for: app)
|
|
|
+ }
|
|
|
+
|
|
|
+ static func defaultActions(for app: LauncherApp) -> [WidgetAction] {
|
|
|
+ [
|
|
|
+ WidgetAction(id: "open", title: "Open", systemImage: "arrow.up.right", urlPath: nil),
|
|
|
+ WidgetAction(id: "search", title: "Search", systemImage: "magnifyingglass", urlPath: nil),
|
|
|
+ WidgetAction(id: "favorites", title: "Favorites", systemImage: "star", urlPath: nil),
|
|
|
+ ]
|
|
|
+ }
|
|
|
+
|
|
|
+ static func layout(for app: LauncherApp, size: WidgetSize) -> WidgetLayoutConfig {
|
|
|
+ let name = app.name.lowercased()
|
|
|
+ let host = app.webURL?.host?.lowercased() ?? ""
|
|
|
+ let isPhotos = name.contains("photos") || host.contains("photos.google.com")
|
|
|
+ let isGmail = name.contains("gmail") || host.contains("mail.google.com")
|
|
|
+ let isDrive = name.contains("drive") || host.contains("drive.google.com")
|
|
|
+ let isDocs = name.contains("docs") || host.contains("docs.google.com")
|
|
|
+ let isSheets = name.contains("sheets") || host.contains("sheets.google.com")
|
|
|
+ let isCalendar = name.contains("calendar") || host.contains("calendar.google.com")
|
|
|
+ let isYouTube = name.contains("youtube") || host.contains("youtube.com")
|
|
|
+ let isMaps = name.contains("maps") || host.contains("maps.google.com")
|
|
|
+
|
|
|
+ if isPhotos {
|
|
|
+ switch size {
|
|
|
+ case .small:
|
|
|
+ return WidgetLayoutConfig(mode: .iconOnly(title: "Photo"), showHeader: false)
|
|
|
+ case .medium:
|
|
|
+ return WidgetLayoutConfig(mode: .iconOnly(title: "Google Photo"), showHeader: false)
|
|
|
+ case .large:
|
|
|
+ return WidgetLayoutConfig(mode: .actionsGrid(columns: 2, maxActions: 8), showHeader: true)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if isGmail {
|
|
|
+ switch size {
|
|
|
+ case .small:
|
|
|
+ return WidgetLayoutConfig(mode: .actionsRow(maxActions: 3), showHeader: false)
|
|
|
+ case .medium:
|
|
|
+ return WidgetLayoutConfig(mode: .actionsGrid(columns: 2, maxActions: 6), showHeader: true)
|
|
|
+ case .large:
|
|
|
+ return WidgetLayoutConfig(mode: .actionsGrid(columns: 3, maxActions: 9), showHeader: true)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if isDrive {
|
|
|
+ switch size {
|
|
|
+ case .small:
|
|
|
+ return WidgetLayoutConfig(mode: .iconOnly(title: "Drive"), showHeader: false)
|
|
|
+ case .medium:
|
|
|
+ return WidgetLayoutConfig(mode: .actionsRow(maxActions: 3), showHeader: true)
|
|
|
+ case .large:
|
|
|
+ return WidgetLayoutConfig(mode: .actionsGrid(columns: 2, maxActions: 6), showHeader: true)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if isDocs {
|
|
|
+ switch size {
|
|
|
+ case .small:
|
|
|
+ return WidgetLayoutConfig(mode: .iconOnly(title: "Docs"), showHeader: false)
|
|
|
+ case .medium:
|
|
|
+ return WidgetLayoutConfig(mode: .actionsGrid(columns: 2, maxActions: 4), showHeader: true)
|
|
|
+ case .large:
|
|
|
+ return WidgetLayoutConfig(mode: .actionsGrid(columns: 3, maxActions: 6), showHeader: true)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if isSheets {
|
|
|
+ switch size {
|
|
|
+ case .small:
|
|
|
+ return WidgetLayoutConfig(mode: .iconOnly(title: "Sheets"), showHeader: false)
|
|
|
+ case .medium:
|
|
|
+ return WidgetLayoutConfig(mode: .actionsRow(maxActions: 3), showHeader: true)
|
|
|
+ case .large:
|
|
|
+ return WidgetLayoutConfig(mode: .actionsGrid(columns: 2, maxActions: 6), showHeader: true)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if isCalendar {
|
|
|
+ switch size {
|
|
|
+ case .small:
|
|
|
+ return WidgetLayoutConfig(mode: .actionsRow(maxActions: 2), showHeader: false)
|
|
|
+ case .medium:
|
|
|
+ return WidgetLayoutConfig(mode: .actionsGrid(columns: 2, maxActions: 3), showHeader: true)
|
|
|
+ case .large:
|
|
|
+ return WidgetLayoutConfig(mode: .actionsGrid(columns: 3, maxActions: 6), showHeader: true)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if isYouTube {
|
|
|
+ switch size {
|
|
|
+ case .small:
|
|
|
+ return WidgetLayoutConfig(mode: .iconOnly(title: "YouTube"), showHeader: false)
|
|
|
+ case .medium:
|
|
|
+ return WidgetLayoutConfig(mode: .actionsGrid(columns: 2, maxActions: 4), showHeader: true)
|
|
|
+ case .large:
|
|
|
+ return WidgetLayoutConfig(mode: .actionsGrid(columns: 3, maxActions: 6), showHeader: true)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if isMaps {
|
|
|
+ switch size {
|
|
|
+ case .small:
|
|
|
+ return WidgetLayoutConfig(mode: .actionsRow(maxActions: 3), showHeader: false)
|
|
|
+ case .medium:
|
|
|
+ return WidgetLayoutConfig(mode: .actionsGrid(columns: 2, maxActions: 4), showHeader: true)
|
|
|
+ case .large:
|
|
|
+ return WidgetLayoutConfig(mode: .actionsGrid(columns: 3, maxActions: 6), showHeader: true)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ switch size {
|
|
|
+ case .small:
|
|
|
+ return WidgetLayoutConfig(mode: .actionsRow(maxActions: 3), showHeader: false)
|
|
|
+ case .medium:
|
|
|
+ return WidgetLayoutConfig(mode: .actionsGrid(columns: 2, maxActions: 6), showHeader: true)
|
|
|
+ case .large:
|
|
|
+ return WidgetLayoutConfig(mode: .actionsGrid(columns: 3, maxActions: 9), showHeader: true)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|