Selaa lähdekoodia

Refine LauncherRootView for panel ordering

Made-with: Cursor
huzaifahayat12 4 kuukautta sitten
vanhempi
sitoutus
290bd9731f
1 muutettua tiedostoa jossa 68 lisäystä ja 12 poistoa
  1. 68 12
      google_apps/LauncherRootView.swift

+ 68 - 12
google_apps/LauncherRootView.swift

@@ -416,6 +416,7 @@ private struct IconPanelView: View {
     @Binding var draggedAppID: UUID?
     @Binding var lastDropDestinationAppID: UUID?
     let onMove: (UUID, UUID) -> Void
+    @AppStorage("pinnedTileIDsData") private var pinnedTileIDsData = ""
 
     private let actions = [
         "house", "magnifyingglass", "target", "map", "heart", "person.2", "clock",
@@ -427,7 +428,9 @@ private struct IconPanelView: View {
             HStack(alignment: .top, spacing: 20) {
                 VStack(alignment: .leading, spacing: 12) {
                     ForEach(apps) { app in
-                        Button(action: { onAppTap(app) }) {
+                        HStack(spacing: 14) {
+                            // Tap-to-open area (kept separate from the pin menu so
+                            // clicking "Pin/Unpin" doesn't also trigger open).
                             HStack(spacing: 14) {
                                 AppIconGlyph(app: app)
                                 Text(app.name)
@@ -435,13 +438,15 @@ private struct IconPanelView: View {
                                     .foregroundStyle(.white.opacity(0.94))
                                     .lineLimit(1)
                                     .truncationMode(.tail)
-                                Spacer(minLength: 0)
                             }
-                            .padding(.vertical, 3)
+                            .contentShape(Rectangle())
+                            .onTapGesture { onAppTap(app) }
+
+                            Spacer(minLength: 0)
                         }
-                        .buttonStyle(.plain)
+                        .padding(.vertical, 3)
                         .onDrag {
-                            // Used by the reorder drop delegate (no need to parse the drop payload).
+                            // Used by the reorder drop delegate.
                             draggedAppID = app.id
                             lastDropDestinationAppID = nil
                             return NSItemProvider(object: app.id.uuidString as NSString)
@@ -462,14 +467,30 @@ private struct IconPanelView: View {
 
                 VStack(alignment: .trailing, spacing: 14) {
                     ForEach(Array(apps.enumerated()), id: \.element.id) { index, _ in
-                        HStack(spacing: 12) {
-                            ForEach(0..<8, id: \.self) { col in
-                                let symbol = actions[(index * 3 + col) % actions.count]
-                                ActionBubble(symbol: symbol, highlighted: col == 0 && index % 3 == 0)
+                        HStack(spacing: 10) {
+                            // Reduce the number of action icons and add a working pin/unpin button.
+                            // Layout: [pin/unpin] + 6 action bubbles
+                            ForEach(0..<7, id: \.self) { col in
+                                let app = apps[index]
+                                if col == 0 {
+                                    let isPinned = pinnedIDs.contains(app.id)
+                                    Button(action: { togglePin(app) }) {
+                                        ActionBubble(
+                                            symbol: isPinned ? "pin.fill" : "pin",
+                                            highlighted: isPinned
+                                        )
+                                    }
+                                    .buttonStyle(.plain)
+                                } else {
+                                    let actionCol = col - 1
+                                    let symbol = actions[(index * 3 + actionCol) % actions.count]
+                                    let highlighted = actionCol == 0 && index % 3 == 0
+                                    ActionBubble(symbol: symbol, highlighted: highlighted)
+                                }
                             }
                             Image(systemName: "info.circle")
                                 .foregroundStyle(.white.opacity(0.6))
-                                .font(.system(size: 17))
+                                .font(.system(size: 14))
                                 .frame(width: 20)
                         }
                     }
@@ -480,6 +501,20 @@ private struct IconPanelView: View {
             .padding(.bottom, 18)
         }
     }
+
+    private var pinnedIDs: Set<UUID> {
+        decodeUUIDSetGlobal(from: pinnedTileIDsData)
+    }
+
+    private func togglePin(_ app: LauncherApp) {
+        var updated = pinnedIDs
+        if updated.contains(app.id) {
+            updated.remove(app.id)
+        } else {
+            updated.insert(app.id)
+        }
+        pinnedTileIDsData = encodeUUIDSetGlobal(updated)
+    }
 }
 
 private struct AppIconGlyph: View {
@@ -523,9 +558,30 @@ private struct ActionBubble: View {
             .overlay(
                 Image(systemName: symbol)
                     .foregroundStyle(.white.opacity(0.9))
-                    .font(.system(size: 15, weight: .medium))
+                    .font(.system(size: 13, weight: .medium))
             )
-            .frame(width: 42, height: 42)
+            .frame(width: 34, height: 34)
+    }
+}
+
+// MARK: - Pinned app persistence helpers (panel mode)
+private func decodeUUIDSetGlobal(from dataString: String) -> Set<UUID> {
+    guard !dataString.isEmpty, let data = dataString.data(using: .utf8) else { return [] }
+    do {
+        let strings = try JSONDecoder().decode([String].self, from: data)
+        return Set(strings.compactMap(UUID.init(uuidString:)))
+    } catch {
+        return []
+    }
+}
+
+private func encodeUUIDSetGlobal(_ set: Set<UUID>) -> String {
+    do {
+        let strings = set.map(\.uuidString).sorted()
+        let data = try JSONEncoder().encode(strings)
+        return String(decoding: data, as: UTF8.self)
+    } catch {
+        return ""
     }
 }