Forráskód Böngészése

Polish Google Photos medium widget UI

- Add shell gradient, rounded border, and header divider before WebKit.
- Redesign header with icon tile, Recent/Library line, and Open capsule.
- Add Photos-style accent strip and equal-width shortcut cards with icons.
- Refresh widget picker preview chrome.

Made-with: Cursor
huzaifahayat12 3 hónapja
szülő
commit
0cfb13caef
1 módosított fájl, 237 hozzáadás és 61 törlés
  1. 237 61
      google_apps/Widgets/PhotosDesktopWidgetView.swift

+ 237 - 61
google_apps/Widgets/PhotosDesktopWidgetView.swift

@@ -11,20 +11,219 @@ struct PhotosDesktopWidgetView: View {
         app.webURL ?? URL(string: "https://photos.google.com/")!
     }
 
+    /// Google Photos–style dark shell (deep blue-grey).
+    private var shellGradient: LinearGradient {
+        LinearGradient(
+            colors: [
+                Color(red: 0.07, green: 0.10, blue: 0.14),
+                Color(red: 0.05, green: 0.07, blue: 0.11),
+            ],
+            startPoint: .topLeading,
+            endPoint: .bottomTrailing
+        )
+    }
+
     var body: some View {
         Group {
             if isPreview {
                 photosPreviewChrome
             } else {
-                VStack(spacing: 0) {
-                    galleryHeader(title: "Recent", subtitle: "photos.google.com")
-                    GooglePhotosEmbeddedWebView(startURL: baseURL)
-                        .frame(maxWidth: .infinity, maxHeight: .infinity)
-                    mediumShortcutRow
+                ZStack {
+                    RoundedRectangle(cornerRadius: 22, style: .continuous)
+                        .fill(shellGradient)
+
+                    VStack(spacing: 0) {
+                        galleryChromeHeader
+                        headerDivider
+                        GooglePhotosEmbeddedWebView(startURL: baseURL)
+                            .frame(maxWidth: .infinity, maxHeight: .infinity)
+                        bottomChromeBar
+                    }
                 }
             }
         }
         .clipShape(RoundedRectangle(cornerRadius: 22, style: .continuous))
+        .overlay(
+            RoundedRectangle(cornerRadius: 22, style: .continuous)
+                .strokeBorder(Color.white.opacity(0.10), lineWidth: 1)
+        )
+    }
+
+    private var headerDivider: some View {
+        Rectangle()
+            .fill(
+                LinearGradient(
+                    colors: [Color.white.opacity(0.14), Color.white.opacity(0.05)],
+                    startPoint: .leading,
+                    endPoint: .trailing
+                )
+            )
+            .frame(height: 1)
+    }
+
+    private var galleryChromeHeader: some View {
+        HStack(alignment: .center, spacing: 12) {
+            ZStack {
+                RoundedRectangle(cornerRadius: 10, style: .continuous)
+                    .fill(
+                        LinearGradient(
+                            colors: [Color.white.opacity(0.14), Color.white.opacity(0.06)],
+                            startPoint: .topLeading,
+                            endPoint: .bottomTrailing
+                        )
+                    )
+                    .frame(width: 40, height: 40)
+                    .overlay(
+                        RoundedRectangle(cornerRadius: 10, style: .continuous)
+                            .stroke(Color.white.opacity(0.12), lineWidth: 1)
+                    )
+                AppIconView(app: app, size: 30, showAppBackground: false, iconPaddingFactor: 0.08)
+            }
+
+            VStack(alignment: .leading, spacing: 3) {
+                Text(app.name)
+                    .font(.system(size: 15, weight: .bold, design: .rounded))
+                    .foregroundStyle(.white.opacity(0.96))
+                HStack(spacing: 6) {
+                    Text("Recent")
+                        .font(.system(size: 11, weight: .semibold, design: .rounded))
+                        .foregroundStyle(Color(red: 0.55, green: 0.82, blue: 0.95).opacity(0.95))
+                    Text("·")
+                        .foregroundStyle(.white.opacity(0.35))
+                    Text("Library")
+                        .font(.system(size: 11, weight: .medium, design: .rounded))
+                        .foregroundStyle(.white.opacity(0.55))
+                }
+            }
+
+            Spacer(minLength: 8)
+
+            Button(action: openPhotosHome) {
+                HStack(spacing: 5) {
+                    Text("Open")
+                        .font(.system(size: 12, weight: .bold, design: .rounded))
+                    Image(systemName: "arrow.up.right.circle.fill")
+                        .font(.system(size: 14, weight: .semibold))
+                }
+                .foregroundStyle(.white.opacity(0.95))
+                .padding(.horizontal, 11)
+                .padding(.vertical, 7)
+                .background(
+                    Capsule(style: .continuous)
+                        .fill(
+                            LinearGradient(
+                                colors: [Color.white.opacity(0.22), Color.white.opacity(0.10)],
+                                startPoint: .top,
+                                endPoint: .bottom
+                            )
+                        )
+                )
+                .overlay(
+                    Capsule(style: .continuous)
+                        .stroke(Color.white.opacity(0.18), lineWidth: 1)
+                )
+            }
+            .buttonStyle(.plain)
+        }
+        .padding(.horizontal, 14)
+        .padding(.vertical, 12)
+        .background(
+            LinearGradient(
+                colors: [Color.black.opacity(0.42), Color.black.opacity(0.22)],
+                startPoint: .top,
+                endPoint: .bottom
+            )
+        )
+    }
+
+    /// Subtle multi-hue strip echoing Google Photos branding.
+    private var photosAccentStrip: some View {
+        LinearGradient(
+            colors: [
+                Color(red: 0.26, green: 0.52, blue: 0.96),
+                Color(red: 0.18, green: 0.72, blue: 0.45),
+                Color(red: 0.98, green: 0.75, blue: 0.18),
+                Color(red: 0.92, green: 0.35, blue: 0.25),
+            ],
+            startPoint: .leading,
+            endPoint: .trailing
+        )
+        .frame(height: 2)
+        .opacity(0.85)
+    }
+
+    private var bottomChromeBar: some View {
+        VStack(spacing: 0) {
+            photosAccentStrip
+            HStack(spacing: 10) {
+                PhotosShortcutChip(title: "Albums", systemImage: "rectangle.stack.fill") {
+                    openPhotosPath("/albums")
+                }
+                PhotosShortcutChip(title: "Memories", systemImage: "sparkles") {
+                    openPhotosPath("/memories")
+                }
+                PhotosShortcutChip(title: "Search", systemImage: "magnifyingglass") {
+                    openPhotosPath("/search")
+                }
+            }
+            .padding(.horizontal, 12)
+            .padding(.vertical, 12)
+            .background(
+                LinearGradient(
+                    colors: [Color.black.opacity(0.55), Color.black.opacity(0.38)],
+                    startPoint: .top,
+                    endPoint: .bottom
+                )
+            )
+        }
+    }
+
+    private struct PhotosShortcutChip: View {
+        let title: String
+        let systemImage: String
+        let action: () -> Void
+
+        var body: some View {
+            Button(action: action) {
+                VStack(spacing: 6) {
+                    Image(systemName: systemImage)
+                        .font(.system(size: 15, weight: .semibold))
+                        .foregroundStyle(
+                            LinearGradient(
+                                colors: [
+                                    Color.white.opacity(0.95),
+                                    Color.white.opacity(0.72),
+                                ],
+                                startPoint: .top,
+                                endPoint: .bottom
+                            )
+                        )
+                    Text(title)
+                        .font(.system(size: 10, weight: .bold, design: .rounded))
+                        .foregroundStyle(.white.opacity(0.88))
+                        .lineLimit(1)
+                        .minimumScaleFactor(0.85)
+                }
+                .frame(maxWidth: .infinity)
+                .padding(.vertical, 11)
+                .padding(.horizontal, 6)
+                .background(
+                    RoundedRectangle(cornerRadius: 14, style: .continuous)
+                        .fill(
+                            LinearGradient(
+                                colors: [Color.white.opacity(0.14), Color.white.opacity(0.05)],
+                                startPoint: .top,
+                                endPoint: .bottom
+                            )
+                        )
+                )
+                .overlay(
+                    RoundedRectangle(cornerRadius: 14, style: .continuous)
+                        .stroke(Color.white.opacity(0.14), lineWidth: 1)
+                )
+            }
+            .buttonStyle(.plain)
+        }
     }
 
     private var photosPreviewChrome: some View {
@@ -32,72 +231,49 @@ struct PhotosDesktopWidgetView: View {
             RoundedRectangle(cornerRadius: 22, style: .continuous)
                 .fill(
                     LinearGradient(
-                        colors: [Color(red: 0.12, green: 0.16, blue: 0.22), Color(red: 0.05, green: 0.08, blue: 0.12)],
+                        colors: [
+                            Color(red: 0.10, green: 0.14, blue: 0.20),
+                            Color(red: 0.04, green: 0.06, blue: 0.10),
+                        ],
                         startPoint: .topLeading,
                         endPoint: .bottomTrailing
                     )
                 )
-            VStack(spacing: 12) {
-                AppIconView(app: app, size: 48, showAppBackground: false, iconPaddingFactor: 0.08)
+            VStack(spacing: 14) {
+                ZStack {
+                    Circle()
+                        .fill(
+                            LinearGradient(
+                                colors: [Color.white.opacity(0.12), Color.white.opacity(0.04)],
+                                startPoint: .topLeading,
+                                endPoint: .bottomTrailing
+                            )
+                        )
+                        .frame(width: 72, height: 72)
+                        .overlay(Circle().stroke(Color.white.opacity(0.14), lineWidth: 1))
+                    AppIconView(app: app, size: 44, showAppBackground: false, iconPaddingFactor: 0.08)
+                }
                 Text("Recent gallery")
-                    .font(.system(size: 15, weight: .bold))
-                    .foregroundStyle(.white.opacity(0.92))
-                Text("Live Google Photos grid in the panel after you sign in.")
-                    .font(.system(size: 11, weight: .medium))
-                    .foregroundStyle(.white.opacity(0.65))
+                    .font(.system(size: 16, weight: .bold, design: .rounded))
+                    .foregroundStyle(.white.opacity(0.94))
+                Text("Your library appears here after you sign in to Google Photos in the panel.")
+                    .font(.system(size: 11, weight: .medium, design: .rounded))
+                    .foregroundStyle(.white.opacity(0.62))
                     .multilineTextAlignment(.center)
-                    .padding(.horizontal, 12)
-            }
-            .padding(16)
-        }
-    }
-
-    private func galleryHeader(title: String, subtitle: String) -> some View {
-        HStack(spacing: 10) {
-            AppIconView(app: app, size: 32, showAppBackground: false, iconPaddingFactor: 0.08)
-            VStack(alignment: .leading, spacing: 2) {
-                Text(app.name)
-                    .font(.system(size: 14, weight: .bold))
-                    .foregroundStyle(.white.opacity(0.95))
-                Text(title)
-                    .font(.system(size: 11, weight: .semibold))
-                    .foregroundStyle(.white.opacity(0.65))
+                    .lineSpacing(2)
+                    .padding(.horizontal, 18)
             }
-            Spacer(minLength: 0)
-            Text(subtitle)
-                .font(.system(size: 9, weight: .medium))
-                .foregroundStyle(.white.opacity(0.4))
+            .padding(20)
         }
-        .padding(.horizontal, 14)
-        .padding(.vertical, 10)
-        .background(Color.black.opacity(0.35))
+        .overlay(
+            RoundedRectangle(cornerRadius: 22, style: .continuous)
+                .strokeBorder(Color.white.opacity(0.10), lineWidth: 1)
+        )
     }
 
-    private var mediumShortcutRow: some View {
-        HStack(spacing: 8) {
-            shortcutPill(title: "Albums", systemImage: "rectangle.stack", path: "/albums")
-            shortcutPill(title: "Memories", systemImage: "sparkles", path: "/memories")
-            shortcutPill(title: "Search", systemImage: "magnifyingglass", path: "/search")
-        }
-        .padding(.horizontal, 12)
-        .padding(.vertical, 10)
-        .background(Color.black.opacity(0.4))
-    }
-
-    private func shortcutPill(title: String, systemImage: String, path: String) -> some View {
-        Button(action: { openPhotosPath(path) }) {
-            HStack(spacing: 6) {
-                Image(systemName: systemImage)
-                    .font(.system(size: 11, weight: .semibold))
-                Text(title)
-                    .font(.system(size: 11, weight: .bold))
-            }
-            .foregroundStyle(.white.opacity(0.92))
-            .padding(.horizontal, 12)
-            .padding(.vertical, 8)
-            .background(Capsule().fill(Color.white.opacity(0.12)))
-        }
-        .buttonStyle(.plain)
+    private func openPhotosHome() {
+        NSApp.activate(ignoringOtherApps: true)
+        InAppBrowserWindowManager.shared.open(url: baseURL, title: app.name)
     }
 
     private func openPhotosPath(_ path: String) {