فهرست منبع

Connect launcher apps to web URLs and add premium modal flow.

Wire app tiles to open their respective Google web pages, present a dedicated premium screen from upgrade/close actions, and adjust window titlebar styling for a panel-style header.

Made-with: Cursor
huzaifahayat12 4 ماه پیش
والد
کامیت
d414090666
4فایلهای تغییر یافته به همراه267 افزوده شده و 35 حذف شده
  1. 2 2
      google_apps/AppDelegate.swift
  2. 20 19
      google_apps/LauncherApp.swift
  3. 40 14
      google_apps/LauncherRootView.swift
  4. 205 0
      google_apps/PremiumFeaturesView.swift

+ 2 - 2
google_apps/AppDelegate.swift

@@ -17,8 +17,8 @@ class AppDelegate: NSObject, NSApplicationDelegate {
         guard let window = NSApp.windows.first else { return }
         window.title = "My Apps"
         window.isOpaque = false
-        window.backgroundColor = .clear
-        window.titlebarAppearsTransparent = true
+        window.backgroundColor = NSColor.windowBackgroundColor
+        window.titlebarAppearsTransparent = false
     }
 
     func applicationWillTerminate(_ aNotification: Notification) {

+ 20 - 19
google_apps/LauncherApp.swift

@@ -7,28 +7,29 @@ struct LauncherApp: Identifiable, Hashable {
     let fallbackSymbolName: String
     let description: String
     let isCreateNew: Bool
+    let webURL: URL?
 }
 
 extension LauncherApp {
     static let sampleApps: [LauncherApp] = [
-        LauncherApp(name: "Google Shopping", assetIconName: "icon_google_shopping", fallbackSymbolName: "bag.fill", description: "Explore products and offers.", isCreateNew: false),
-        LauncherApp(name: "Gmail", assetIconName: "icon_gmail", fallbackSymbolName: "envelope.fill", description: "Read and send emails quickly.", isCreateNew: false),
-        LauncherApp(name: "Google Docs", assetIconName: "icon_google_docs", fallbackSymbolName: "doc.text.fill", description: "Create and edit documents.", isCreateNew: false),
-        LauncherApp(name: "Google Drive", assetIconName: "icon_google_drive", fallbackSymbolName: "externaldrive.fill", description: "Store and sync your files.", isCreateNew: false),
-        LauncherApp(name: "Google Earth", assetIconName: "icon_google_earth", fallbackSymbolName: "globe.americas.fill", description: "Explore places worldwide.", isCreateNew: false),
-        LauncherApp(name: "Google Photos", assetIconName: "icon_google_photos", fallbackSymbolName: "photo.stack.fill", description: "View and organize photos.", isCreateNew: false),
-        LauncherApp(name: "Google Maps", assetIconName: "icon_google_maps", fallbackSymbolName: "map.fill", description: "Find routes and places.", isCreateNew: false),
-        LauncherApp(name: "Google Translate", assetIconName: "icon_google_translate", fallbackSymbolName: "character.bubble.fill", description: "Translate text and speech.", isCreateNew: false),
-        LauncherApp(name: "Google Sheets", assetIconName: "icon_google_sheets", fallbackSymbolName: "tablecells.fill", description: "Work with spreadsheets.", isCreateNew: false),
-        LauncherApp(name: "Google Search", assetIconName: "icon_google_search", fallbackSymbolName: "magnifyingglass.circle.fill", description: "Search the web quickly.", isCreateNew: false),
-        LauncherApp(name: "YouTube", assetIconName: "icon_youtube", fallbackSymbolName: "play.rectangle.fill", description: "Watch videos and channels.", isCreateNew: false),
-        LauncherApp(name: "Google Calendar", assetIconName: "icon_google_calendar", fallbackSymbolName: "calendar", description: "Manage events and schedules.", isCreateNew: false),
-        LauncherApp(name: "Google Keep", assetIconName: "icon_google_keep", fallbackSymbolName: "lightbulb.fill", description: "Capture notes and reminders.", isCreateNew: false),
-        LauncherApp(name: "Google Slides", assetIconName: "icon_google_slides", fallbackSymbolName: "rectangle.on.rectangle.fill", description: "Build and present slides.", isCreateNew: false),
-        LauncherApp(name: "Google Forms", assetIconName: "icon_google_forms", fallbackSymbolName: "list.bullet.rectangle.fill", description: "Create forms and surveys.", isCreateNew: false),
-        LauncherApp(name: "Google Books", assetIconName: "icon_google_books", fallbackSymbolName: "book.fill", description: "Browse and read books.", isCreateNew: false),
-        LauncherApp(name: "Google Travel", assetIconName: "icon_google_travel", fallbackSymbolName: "airplane", description: "Plan and organize trips.", isCreateNew: false),
-        LauncherApp(name: "Google Meet", assetIconName: "icon_google_meet", fallbackSymbolName: "video.fill", description: "Start and join video meetings.", isCreateNew: false),
-        LauncherApp(name: "Create New App", assetIconName: "icon_create_new_app", fallbackSymbolName: "plus", description: "Create and add a custom app shortcut.", isCreateNew: true),
+        LauncherApp(name: "Google Shopping", assetIconName: "icon_google_shopping", fallbackSymbolName: "bag.fill", description: "Explore products and offers.", isCreateNew: false, webURL: URL(string: "https://shopping.google.com")),
+        LauncherApp(name: "Gmail", assetIconName: "icon_gmail", fallbackSymbolName: "envelope.fill", description: "Read and send emails quickly.", isCreateNew: false, webURL: URL(string: "https://mail.google.com")),
+        LauncherApp(name: "Google Docs", assetIconName: "icon_google_docs", fallbackSymbolName: "doc.text.fill", description: "Create and edit documents.", isCreateNew: false, webURL: URL(string: "https://docs.google.com")),
+        LauncherApp(name: "Google Drive", assetIconName: "icon_google_drive", fallbackSymbolName: "externaldrive.fill", description: "Store and sync your files.", isCreateNew: false, webURL: URL(string: "https://drive.google.com")),
+        LauncherApp(name: "Google Earth", assetIconName: "icon_google_earth", fallbackSymbolName: "globe.americas.fill", description: "Explore places worldwide.", isCreateNew: false, webURL: URL(string: "https://earth.google.com")),
+        LauncherApp(name: "Google Photos", assetIconName: "icon_google_photos", fallbackSymbolName: "photo.stack.fill", description: "View and organize photos.", isCreateNew: false, webURL: URL(string: "https://photos.google.com")),
+        LauncherApp(name: "Google Maps", assetIconName: "icon_google_maps", fallbackSymbolName: "map.fill", description: "Find routes and places.", isCreateNew: false, webURL: URL(string: "https://maps.google.com")),
+        LauncherApp(name: "Google Translate", assetIconName: "icon_google_translate", fallbackSymbolName: "character.bubble.fill", description: "Translate text and speech.", isCreateNew: false, webURL: URL(string: "https://translate.google.com")),
+        LauncherApp(name: "Google Sheets", assetIconName: "icon_google_sheets", fallbackSymbolName: "tablecells.fill", description: "Work with spreadsheets.", isCreateNew: false, webURL: URL(string: "https://sheets.google.com")),
+        LauncherApp(name: "Google Search", assetIconName: "icon_google_search", fallbackSymbolName: "magnifyingglass.circle.fill", description: "Search the web quickly.", isCreateNew: false, webURL: URL(string: "https://www.google.com")),
+        LauncherApp(name: "YouTube", assetIconName: "icon_youtube", fallbackSymbolName: "play.rectangle.fill", description: "Watch videos and channels.", isCreateNew: false, webURL: URL(string: "https://www.youtube.com")),
+        LauncherApp(name: "Google Calendar", assetIconName: "icon_google_calendar", fallbackSymbolName: "calendar", description: "Manage events and schedules.", isCreateNew: false, webURL: URL(string: "https://calendar.google.com")),
+        LauncherApp(name: "Google Keep", assetIconName: "icon_google_keep", fallbackSymbolName: "lightbulb.fill", description: "Capture notes and reminders.", isCreateNew: false, webURL: URL(string: "https://keep.google.com")),
+        LauncherApp(name: "Google Slides", assetIconName: "icon_google_slides", fallbackSymbolName: "rectangle.on.rectangle.fill", description: "Build and present slides.", isCreateNew: false, webURL: URL(string: "https://slides.google.com")),
+        LauncherApp(name: "Google Forms", assetIconName: "icon_google_forms", fallbackSymbolName: "list.bullet.rectangle.fill", description: "Create forms and surveys.", isCreateNew: false, webURL: URL(string: "https://forms.google.com")),
+        LauncherApp(name: "Google Books", assetIconName: "icon_google_books", fallbackSymbolName: "book.fill", description: "Browse and read books.", isCreateNew: false, webURL: URL(string: "https://books.google.com")),
+        LauncherApp(name: "Google Travel", assetIconName: "icon_google_travel", fallbackSymbolName: "airplane", description: "Plan and organize trips.", isCreateNew: false, webURL: URL(string: "https://travel.google.com")),
+        LauncherApp(name: "Google Meet", assetIconName: "icon_google_meet", fallbackSymbolName: "video.fill", description: "Start and join video meetings.", isCreateNew: false, webURL: URL(string: "https://meet.google.com")),
+        LauncherApp(name: "Create New App", assetIconName: "icon_create_new_app", fallbackSymbolName: "plus", description: "Create and add a custom app shortcut.", isCreateNew: true, webURL: nil),
     ]
 }

+ 40 - 14
google_apps/LauncherRootView.swift

@@ -4,6 +4,7 @@ import AppKit
 struct LauncherRootView: View {
     @State private var query = ""
     @State private var selectedApp: LauncherApp?
+    @State private var showingPremiumScreen = false
 
     private let apps = LauncherApp.sampleApps
     private let columns = [GridItem(.adaptive(minimum: 120, maximum: 145), spacing: 22)]
@@ -26,7 +27,10 @@ struct LauncherRootView: View {
                 SearchHeader(query: $query)
                     .padding(.top, 12)
 
-                PromoBanner()
+                PromoBanner(
+                    onCloseTap: { showingPremiumScreen = true },
+                    onUpgradeTap: { showingPremiumScreen = true }
+                )
 
                 if filteredApps.isEmpty {
                     ContentUnavailableView(
@@ -41,7 +45,7 @@ struct LauncherRootView: View {
                         LazyVGrid(columns: columns, spacing: 20) {
                             ForEach(filteredApps) { app in
                                 AppTileView(app: app) {
-                                    selectedApp = app
+                                    handleAppTap(app)
                                 }
                             }
                         }
@@ -57,6 +61,19 @@ struct LauncherRootView: View {
             AppDetailView(app: app)
                 .frame(minWidth: 360, minHeight: 220)
         }
+        .sheet(isPresented: $showingPremiumScreen) {
+            PremiumFeaturesView()
+                .frame(minWidth: 560, minHeight: 690)
+        }
+    }
+
+    private func handleAppTap(_ app: LauncherApp) {
+        if let webURL = app.webURL {
+            NSWorkspace.shared.open(webURL)
+            return
+        }
+
+        selectedApp = app
     }
 }
 
@@ -97,11 +114,17 @@ private struct SearchHeader: View {
 }
 
 private struct PromoBanner: View {
+    let onCloseTap: () -> Void
+    let onUpgradeTap: () -> Void
+
     var body: some View {
         HStack(spacing: 10) {
-            Image(systemName: "xmark")
-                .foregroundStyle(.blue.opacity(0.95))
-                .font(.system(size: 14, weight: .semibold))
+            Button(action: onCloseTap) {
+                Image(systemName: "xmark")
+                    .foregroundStyle(.blue.opacity(0.95))
+                    .font(.system(size: 14, weight: .semibold))
+            }
+            .buttonStyle(.plain)
 
             Text("Update to Premium to unlock all features and remove ads")
                 .font(.system(size: 13, weight: .medium))
@@ -110,15 +133,18 @@ private struct PromoBanner: View {
 
             Spacer()
 
-            Text("Upgrade")
-                .font(.system(size: 13, weight: .semibold))
-                .foregroundStyle(.white)
-                .padding(.horizontal, 12)
-                .padding(.vertical, 6)
-                .background(
-                    Capsule(style: .continuous)
-                        .fill(Color.blue.opacity(0.92))
-                )
+            Button(action: onUpgradeTap) {
+                Text("Upgrade")
+                    .font(.system(size: 13, weight: .semibold))
+                    .foregroundStyle(.white)
+                    .padding(.horizontal, 12)
+                    .padding(.vertical, 6)
+                    .background(
+                        Capsule(style: .continuous)
+                            .fill(Color.blue.opacity(0.92))
+                    )
+            }
+            .buttonStyle(.plain)
         }
         .padding(.horizontal, 11)
         .padding(.vertical, 8)

+ 205 - 0
google_apps/PremiumFeaturesView.swift

@@ -0,0 +1,205 @@
+import SwiftUI
+
+struct PremiumFeaturesView: View {
+    @Environment(\.dismiss) private var dismiss
+
+    private let featureRows: [(String, String)] = [
+        ("Desktop Widgets", "Add frequently used apps as desktop widgets"),
+        ("Custom web Apps", "Manage personalized applications"),
+        ("Hotkey", "Quickly launch specific apps by setting shortcuts"),
+        ("Status Bar Shortcuts", "Add app quick-access to status bar"),
+        ("Desktop Shortcuts", "Create one-click app shortcuts on desktop"),
+        ("Quick Search", "Search directly on Google without opening browser"),
+        ("Ad Blocker", "Intelligently blocks ads and pop-ups for better browsing"),
+        ("Auto Startup", "Launch automatically with system"),
+        ("App Pin", "Keep app windows always on top"),
+        ("Family Sharing", "One purchase for the whole family to share"),
+    ]
+
+    private let cardColors: [Color] = [
+        .blue, .purple, .red, .green, .orange,
+        .indigo, .pink, .cyan, .gray, .mint,
+    ]
+
+    var body: some View {
+        ZStack {
+            LinearGradient(
+                colors: [Color.black.opacity(0.9), Color(red: 0.08, green: 0.1, blue: 0.14)],
+                startPoint: .topLeading,
+                endPoint: .bottomTrailing
+            )
+            .ignoresSafeArea()
+
+            Rectangle()
+                .fill(
+                    RadialGradient(
+                        colors: [Color.blue.opacity(0.25), .clear],
+                        center: .bottomTrailing,
+                        startRadius: 40,
+                        endRadius: 380
+                    )
+                )
+                .ignoresSafeArea()
+
+            ScrollView {
+                VStack(alignment: .leading, spacing: 14) {
+                    HStack {
+                        Button(action: { dismiss() }) {
+                            Image(systemName: "xmark")
+                                .font(.system(size: 13, weight: .semibold))
+                                .foregroundStyle(.white.opacity(0.88))
+                        }
+                        .buttonStyle(.plain)
+
+                        Spacer()
+
+                        Text("Restore")
+                            .font(.system(size: 11, weight: .semibold))
+                            .foregroundStyle(.white.opacity(0.9))
+                            .padding(.horizontal, 8)
+                            .padding(.vertical, 3)
+                            .background(
+                                RoundedRectangle(cornerRadius: 7, style: .continuous)
+                                    .fill(Color.white.opacity(0.16))
+                            )
+                    }
+
+                    Text("Upgrade to unlock all\nPremium Features")
+                        .font(.system(size: 20, weight: .bold))
+                        .foregroundStyle(.white)
+                        .lineSpacing(1)
+
+                    LazyVGrid(
+                        columns: [GridItem(.flexible(), spacing: 10), GridItem(.flexible(), spacing: 10)],
+                        spacing: 10
+                    ) {
+                        ForEach(Array(featureRows.enumerated()), id: \.offset) { index, feature in
+                            featureCard(title: feature.0, subtitle: feature.1, color: cardColors[index % cardColors.count])
+                        }
+                    }
+
+                    priceCard(
+                        title: "Perpetual",
+                        price: "Rs 9,900.00",
+                        subtitle: "Own the software for lifetime",
+                        isPrimary: true
+                    )
+
+                    priceCard(
+                        title: "Yearly",
+                        price: "Rs 7,900.00/year",
+                        subtitle: "Rs 658.33/mo",
+                        isPrimary: false
+                    )
+
+                    Button(action: {}) {
+                        Text("Continue")
+                            .font(.system(size: 16, weight: .bold))
+                            .foregroundStyle(.white)
+                            .frame(maxWidth: .infinity)
+                            .padding(.vertical, 12)
+                            .background(
+                                RoundedRectangle(cornerRadius: 16, style: .continuous)
+                                    .fill(Color.blue.opacity(0.9))
+                            )
+                    }
+                    .buttonStyle(.plain)
+                    .padding(.top, 6)
+
+                    HStack {
+                        Spacer()
+                        Text("Privacy Policy")
+                            .font(.system(size: 11, weight: .medium))
+                            .foregroundStyle(.white.opacity(0.7))
+                        Spacer()
+                        Text("Terms of Service")
+                            .font(.system(size: 11, weight: .medium))
+                            .foregroundStyle(.white.opacity(0.7))
+                        Spacer()
+                    }
+                    .padding(.top, 2)
+                }
+                .padding(16)
+            }
+        }
+    }
+
+    private func featureCard(title: String, subtitle: String, color: Color) -> some View {
+        HStack(alignment: .top, spacing: 10) {
+            Circle()
+                .fill(color.opacity(0.95))
+                .frame(width: 28, height: 28)
+                .overlay(
+                    Image(systemName: "sparkles")
+                        .font(.system(size: 11, weight: .semibold))
+                        .foregroundStyle(.white)
+                )
+
+            VStack(alignment: .leading, spacing: 3) {
+                Text(title)
+                    .font(.system(size: 11.5, weight: .bold))
+                    .foregroundStyle(.white)
+                    .lineLimit(1)
+                Text(subtitle)
+                    .font(.system(size: 9.5, weight: .medium))
+                    .foregroundStyle(.white.opacity(0.85))
+                    .lineLimit(2)
+            }
+            Spacer(minLength: 0)
+        }
+        .padding(10)
+        .frame(maxWidth: .infinity, minHeight: 66, alignment: .leading)
+        .background(
+            RoundedRectangle(cornerRadius: 12, style: .continuous)
+                .fill(Color.white.opacity(0.14))
+        )
+    }
+
+    private func priceCard(title: String, price: String, subtitle: String, isPrimary: Bool) -> some View {
+        HStack(alignment: .center) {
+            VStack(alignment: .leading, spacing: 5) {
+                Text(title)
+                    .font(.system(size: 12, weight: .bold))
+                    .foregroundStyle(.white)
+                Text(subtitle)
+                    .font(.system(size: 7.5, weight: .medium))
+                    .foregroundStyle(.white.opacity(0.72))
+            }
+
+            Spacer()
+
+            VStack(alignment: .trailing, spacing: 7) {
+                Text(price)
+                    .font(.system(size: 10, weight: .bold))
+                    .foregroundStyle(.white)
+                    .minimumScaleFactor(0.7)
+                    .lineLimit(1)
+                if isPrimary {
+                    Text("One-time payment")
+                        .font(.system(size: 7.5, weight: .bold))
+                        .foregroundStyle(.white)
+                        .padding(.horizontal, 8)
+                        .padding(.vertical, 4)
+                        .background(
+                            RoundedRectangle(cornerRadius: 7, style: .continuous)
+                                .fill(Color.blue.opacity(0.95))
+                        )
+                }
+            }
+        }
+        .padding(10)
+        .background(
+            RoundedRectangle(cornerRadius: 14, style: .continuous)
+                .stroke(isPrimary ? Color.blue.opacity(0.95) : Color.white.opacity(0.4), lineWidth: 1.5)
+                .background(
+                    RoundedRectangle(cornerRadius: 14, style: .continuous)
+                        .fill(Color.white.opacity(0.05))
+                )
+        )
+    }
+}
+
+#Preview {
+    PremiumFeaturesView()
+        .frame(width: 560, height: 690)
+}