瀏覽代碼

Add hide and restore controls for launcher apps.

This lets users remove apps from view and add them back later while persisting hidden state and cleaning up pinned/status bar/widget references.

Made-with: Cursor
huzaifahayat12 4 月之前
父節點
當前提交
6b7137fd31
共有 2 個文件被更改,包括 102 次插入6 次删除
  1. 19 0
      google_apps/AppTileView.swift
  2. 83 6
      google_apps/LauncherRootView.swift

+ 19 - 0
google_apps/AppTileView.swift

@@ -4,6 +4,7 @@ import AppKit
 struct AppTileView: View {
     let app: LauncherApp
     let onTap: () -> Void
+    let onHide: (() -> Void)?
 
     @State private var isHovering = false
     @AppStorage("pinnedTileIDsData") private var pinnedTileIDsData = ""
@@ -44,6 +45,8 @@ struct AppTileView: View {
                     Button("Add to StatusBar") { addToStatusBar() }
                     Button("Add to desktop") { addToDesktop() }
                     Button("Add Widget") { addWidget() }
+                    Divider()
+                    Button("Remove from View") { onHide?() }
                 } label: {
                     Image(systemName: "ellipsis")
                         .font(.system(size: 13, weight: .semibold))
@@ -74,6 +77,8 @@ struct AppTileView: View {
                 Button("Add to StatusBar") { addToStatusBar() }
                 Button("Add to desktop") { addToDesktop() }
                 Button("Add Widget") { addWidget() }
+                Divider()
+                Button("Remove from View") { onHide?() }
             }
         }
         .onHover { hovering in
@@ -209,6 +214,20 @@ struct AppTileView: View {
     }
 }
 
+extension AppTileView {
+    init(app: LauncherApp, onTap: @escaping () -> Void) {
+        self.app = app
+        self.onTap = onTap
+        self.onHide = nil
+    }
+
+    init(app: LauncherApp, onTap: @escaping () -> Void, onHide: @escaping () -> Void) {
+        self.app = app
+        self.onTap = onTap
+        self.onHide = onHide
+    }
+}
+
 private struct HideMenuIndicatorIfAvailable: ViewModifier {
     func body(content: Content) -> some View {
         if #available(macOS 13.0, *) {

+ 83 - 6
google_apps/LauncherRootView.swift

@@ -20,9 +20,14 @@ struct LauncherRootView: View {
     @AppStorage("customLauncherAppsData") private var customAppsData = ""
     @AppStorage("launcherAppOrderData") private var appOrderData = ""
     @AppStorage("pinnedTileIDsData") private var pinnedTileIDsData = ""
+    @AppStorage("statusBarAppIDsData") private var statusBarAppIDsData = ""
+    @AppStorage("widgetAppIDsData") private var widgetAppIDsData = ""
+    @AppStorage("hiddenLauncherAppIDsData") private var hiddenLauncherAppIDsData = ""
 
     private var apps: [LauncherApp] {
-        LauncherApp.sampleApps + customApps + [LauncherApp.createNewTile]
+        let hiddenIDs = decodeUUIDSet(from: hiddenLauncherAppIDsData)
+        let visibleApps = (LauncherApp.sampleApps + customApps).filter { !hiddenIDs.contains($0.id) }
+        return visibleApps + [LauncherApp.createNewTile]
     }
     private var columns: [GridItem] {
         [GridItem(.adaptive(minimum: 120, maximum: 145), spacing: 22)]
@@ -67,6 +72,15 @@ struct LauncherRootView: View {
         }
     }
 
+    private var hiddenApps: [LauncherApp] {
+        let hiddenIDs = decodeUUIDSet(from: hiddenLauncherAppIDsData)
+        guard !hiddenIDs.isEmpty else { return [] }
+        let allApps = LauncherApp.sampleApps + customApps
+        return allApps
+            .filter { hiddenIDs.contains($0.id) }
+            .sorted { $0.name.localizedCaseInsensitiveCompare($1.name) == .orderedAscending }
+    }
+
     var body: some View {
         ZStack {
             VisualEffectBlur(material: .hudWindow, blendingMode: .behindWindow)
@@ -79,6 +93,7 @@ struct LauncherRootView: View {
                 SearchHeader(
                     query: $query,
                     isPanelMode: layoutMode == .panel,
+                    hiddenApps: hiddenApps,
                     onToggleLayout: {
                         layoutMode = layoutMode == .grid ? .panel : .grid
                     },
@@ -89,6 +104,12 @@ struct LauncherRootView: View {
                         if let url = URL(string: "https://www.google.com") {
                             InAppBrowserWindowManager.shared.open(url: url, title: "Google")
                         }
+                    },
+                    onRestoreApp: { appID in
+                        restoreHiddenApp(id: appID)
+                    },
+                    onRestoreAll: {
+                        restoreAllHiddenApps()
                     }
                 )
                 .padding(.top, 12)
@@ -166,15 +187,18 @@ struct LauncherRootView: View {
                     },
                     draggedAppID: $draggedAppID,
                     lastDropDestinationAppID: $lastDropDestinationAppID,
-                    onMove: moveApp
+                    onMove: moveApp,
+                    onHideApp: hideApp
                 )
             } else {
                 ScrollView {
                     LazyVGrid(columns: columns, spacing: 20) {
                         ForEach(filteredApps) { app in
-                            AppTileView(app: app) {
-                                handleAppTap(app)
-                            }
+                            AppTileView(
+                                app: app,
+                                onTap: { handleAppTap(app) },
+                                onHide: { hideApp(id: app.id) }
+                            )
                             .onDrag {
                                 draggedAppID = app.id
                                 lastDropDestinationAppID = nil
@@ -286,6 +310,37 @@ struct LauncherRootView: View {
         saveAppOrderToStorage()
     }
 
+    private func hideApp(id: UUID) {
+        var hiddenIDs = decodeUUIDSet(from: hiddenLauncherAppIDsData)
+        guard hiddenIDs.insert(id).inserted else { return }
+        hiddenLauncherAppIDsData = encodeUUIDSetGlobal(hiddenIDs)
+        removeIDFromLinkedCollections(id)
+    }
+
+    private func restoreHiddenApp(id: UUID) {
+        var hiddenIDs = decodeUUIDSet(from: hiddenLauncherAppIDsData)
+        guard hiddenIDs.remove(id) != nil else { return }
+        hiddenLauncherAppIDsData = encodeUUIDSetGlobal(hiddenIDs)
+    }
+
+    private func restoreAllHiddenApps() {
+        hiddenLauncherAppIDsData = ""
+    }
+
+    private func removeIDFromLinkedCollections(_ id: UUID) {
+        var pinnedIDs = decodeUUIDSet(from: pinnedTileIDsData)
+        pinnedIDs.remove(id)
+        pinnedTileIDsData = encodeUUIDSetGlobal(pinnedIDs)
+
+        var statusBarIDs = decodeUUIDSet(from: statusBarAppIDsData)
+        statusBarIDs.remove(id)
+        statusBarAppIDsData = encodeUUIDSetGlobal(statusBarIDs)
+
+        var widgetIDs = decodeUUIDSet(from: widgetAppIDsData)
+        widgetIDs.remove(id)
+        widgetAppIDsData = encodeUUIDSetGlobal(widgetIDs)
+    }
+
     private func loadAppOrderFromStorage() {
         guard !appOrderData.isEmpty else {
             appOrder = []
@@ -377,9 +432,12 @@ private struct TileReorderDropDelegate: DropDelegate {
 private struct SearchHeader: View {
     @Binding var query: String
     let isPanelMode: Bool
+    let hiddenApps: [LauncherApp]
     let onToggleLayout: () -> Void
     let onOpenPremium: () -> Void
     let onOpenGoogle: () -> Void
+    let onRestoreApp: (UUID) -> Void
+    let onRestoreAll: () -> Void
 
     var body: some View {
         HStack {
@@ -419,6 +477,20 @@ private struct SearchHeader: View {
                     Button("Open Google") {
                         onOpenGoogle()
                     }
+                    if !hiddenApps.isEmpty {
+                        Divider()
+                        Menu("Manage Hidden Apps") {
+                            ForEach(hiddenApps) { app in
+                                Button("Add Back \(app.name)") {
+                                    onRestoreApp(app.id)
+                                }
+                            }
+                            Divider()
+                            Button("Restore All Hidden Apps") {
+                                onRestoreAll()
+                            }
+                        }
+                    }
                 } label: {
                     Image(systemName: "ellipsis")
                 }
@@ -437,6 +509,7 @@ private struct IconPanelView: View {
     @Binding var draggedAppID: UUID?
     @Binding var lastDropDestinationAppID: UUID?
     let onMove: (UUID, UUID) -> Void
+    let onHideApp: (UUID) -> Void
 
     var body: some View {
         ScrollView {
@@ -447,7 +520,8 @@ private struct IconPanelView: View {
                         onAppTap: onAppTap,
                         draggedAppID: $draggedAppID,
                         lastDropDestinationAppID: $lastDropDestinationAppID,
-                        onMove: onMove
+                        onMove: onMove,
+                        onHideApp: onHideApp
                     )
                 }
             }
@@ -463,6 +537,7 @@ private struct PanelAppRowView: View {
     @Binding var draggedAppID: UUID?
     @Binding var lastDropDestinationAppID: UUID?
     let onMove: (UUID, UUID) -> Void
+    let onHideApp: (UUID) -> Void
 
     @AppStorage("pinnedTileIDsData") private var pinnedTileIDsData = ""
     @AppStorage("statusBarAppIDsData") private var statusBarAppIDsData = ""
@@ -521,6 +596,8 @@ private struct PanelAppRowView: View {
                     Button("Add to StatusBar") { addToStatusBar() }
                     Button("Add to desktop") { addToDesktop() }
                     Button("Add Widget") { addWidget() }
+                    Divider()
+                    Button("Remove from View") { onHideApp(app.id) }
                 } label: {
                     Image(systemName: "ellipsis")
                         .font(.system(size: 13, weight: .semibold))