|
|
@@ -15,6 +15,7 @@ struct LauncherRootView: View {
|
|
|
@State private var customApps: [LauncherApp] = []
|
|
|
@State private var appOrder: [UUID] = []
|
|
|
@State private var draggedAppID: UUID?
|
|
|
+ @State private var lastDropDestinationAppID: UUID?
|
|
|
@State private var layoutMode: LayoutMode = .grid
|
|
|
@AppStorage("customLauncherAppsData") private var customAppsData = ""
|
|
|
@AppStorage("launcherAppOrderData") private var appOrderData = ""
|
|
|
@@ -114,9 +115,15 @@ struct LauncherRootView: View {
|
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
|
} else {
|
|
|
if layoutMode == .panel {
|
|
|
- IconPanelView(apps: filteredApps) { app in
|
|
|
- handleAppTap(app)
|
|
|
- }
|
|
|
+ IconPanelView(
|
|
|
+ apps: filteredApps,
|
|
|
+ onAppTap: { app in
|
|
|
+ handleAppTap(app)
|
|
|
+ },
|
|
|
+ draggedAppID: $draggedAppID,
|
|
|
+ lastDropDestinationAppID: $lastDropDestinationAppID,
|
|
|
+ onMove: moveApp
|
|
|
+ )
|
|
|
} else {
|
|
|
ScrollView {
|
|
|
LazyVGrid(columns: columns, spacing: 20) {
|
|
|
@@ -126,6 +133,7 @@ struct LauncherRootView: View {
|
|
|
}
|
|
|
.onDrag {
|
|
|
draggedAppID = app.id
|
|
|
+ lastDropDestinationAppID = nil
|
|
|
return NSItemProvider(object: app.id.uuidString as NSString)
|
|
|
}
|
|
|
.onDrop(
|
|
|
@@ -133,6 +141,7 @@ struct LauncherRootView: View {
|
|
|
delegate: TileReorderDropDelegate(
|
|
|
destinationAppID: app.id,
|
|
|
draggedAppID: $draggedAppID,
|
|
|
+ lastDropDestinationAppID: $lastDropDestinationAppID,
|
|
|
onMove: moveApp
|
|
|
)
|
|
|
)
|
|
|
@@ -324,16 +333,22 @@ private struct PersistedCustomApp: Codable {
|
|
|
private struct TileReorderDropDelegate: DropDelegate {
|
|
|
let destinationAppID: UUID
|
|
|
@Binding var draggedAppID: UUID?
|
|
|
+ @Binding var lastDropDestinationAppID: UUID?
|
|
|
let onMove: (UUID, UUID) -> Void
|
|
|
|
|
|
func dropEntered(info: DropInfo) {
|
|
|
guard let draggedAppID else { return }
|
|
|
guard draggedAppID != destinationAppID else { return }
|
|
|
+ // SwiftUI may call `dropEntered` multiple times while hovering the same tile.
|
|
|
+ // Avoid reordering repeatedly to keep drag-to-reorder stable.
|
|
|
+ guard lastDropDestinationAppID != destinationAppID else { return }
|
|
|
+ lastDropDestinationAppID = destinationAppID
|
|
|
onMove(draggedAppID, destinationAppID)
|
|
|
}
|
|
|
|
|
|
func performDrop(info: DropInfo) -> Bool {
|
|
|
draggedAppID = nil
|
|
|
+ lastDropDestinationAppID = nil
|
|
|
return true
|
|
|
}
|
|
|
}
|
|
|
@@ -398,6 +413,9 @@ private struct SearchHeader: View {
|
|
|
private struct IconPanelView: View {
|
|
|
let apps: [LauncherApp]
|
|
|
let onAppTap: (LauncherApp) -> Void
|
|
|
+ @Binding var draggedAppID: UUID?
|
|
|
+ @Binding var lastDropDestinationAppID: UUID?
|
|
|
+ let onMove: (UUID, UUID) -> Void
|
|
|
|
|
|
private let actions = [
|
|
|
"house", "magnifyingglass", "target", "map", "heart", "person.2", "clock",
|
|
|
@@ -408,7 +426,7 @@ private struct IconPanelView: View {
|
|
|
ScrollView {
|
|
|
HStack(alignment: .top, spacing: 20) {
|
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
|
- ForEach(Array(apps.prefix(10))) { app in
|
|
|
+ ForEach(apps) { app in
|
|
|
Button(action: { onAppTap(app) }) {
|
|
|
HStack(spacing: 14) {
|
|
|
AppIconGlyph(app: app)
|
|
|
@@ -422,13 +440,28 @@ private struct IconPanelView: View {
|
|
|
.padding(.vertical, 3)
|
|
|
}
|
|
|
.buttonStyle(.plain)
|
|
|
+ .onDrag {
|
|
|
+ // Used by the reorder drop delegate (no need to parse the drop payload).
|
|
|
+ draggedAppID = app.id
|
|
|
+ lastDropDestinationAppID = nil
|
|
|
+ return NSItemProvider(object: app.id.uuidString as NSString)
|
|
|
+ }
|
|
|
+ .onDrop(
|
|
|
+ of: [UTType.text],
|
|
|
+ delegate: TileReorderDropDelegate(
|
|
|
+ destinationAppID: app.id,
|
|
|
+ draggedAppID: $draggedAppID,
|
|
|
+ lastDropDestinationAppID: $lastDropDestinationAppID,
|
|
|
+ onMove: onMove
|
|
|
+ )
|
|
|
+ )
|
|
|
}
|
|
|
Spacer(minLength: 0)
|
|
|
}
|
|
|
.frame(width: 320, alignment: .topLeading)
|
|
|
|
|
|
VStack(alignment: .trailing, spacing: 14) {
|
|
|
- ForEach(Array(apps.prefix(10).enumerated()), id: \.offset) { index, _ in
|
|
|
+ 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]
|