From da71eb5bccbdbd22da7cd74b6ddcec031f069482 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Thu, 28 May 2026 15:59:53 +0800
Subject: [PATCH] Stabilize empty-drop tag removal confirmation

---
 Apptag/AppGridCollectionView.swift |    4 +
 Apptag/ContentView.swift           |   28 +++++++++-----
 Apptag/EditModeViews.swift         |   36 ++++++++++++++---
 3 files changed, 50 insertions(+), 18 deletions(-)

diff --git a/Apptag/AppGridCollectionView.swift b/Apptag/AppGridCollectionView.swift
index b2884d7..3516782 100644
--- a/Apptag/AppGridCollectionView.swift
+++ b/Apptag/AppGridCollectionView.swift
@@ -339,7 +339,9 @@
         guard let coordinator,
               coordinator.displayStyle != .flat
         else { return }
-        coordinator.onDropOutsideGroup(path, source, copy)
+        DispatchQueue.main.async {
+            coordinator.onDropOutsideGroup(path, source, copy)
+        }
     }
 
     private func setup() {
diff --git a/Apptag/ContentView.swift b/Apptag/ContentView.swift
index d6ca151..3c68021 100644
--- a/Apptag/ContentView.swift
+++ b/Apptag/ContentView.swift
@@ -593,12 +593,11 @@
             quickSearchErrorMessage = nil
             refreshQuickSearchResults()
         }
-        .onChange(of: pendingUncategorizedDrop != nil) { _, active in
-            NotificationCenter.default.post(
-                name: .tagLauncherModalInteractionChanged,
-                object: nil,
-                userInfo: ["active": active]
-            )
+        .onChange(of: pendingUncategorizedDrop != nil) { _, _ in
+            publishModalInteractionState()
+        }
+        .onChange(of: pendingTagRemovalDrop != nil) { _, _ in
+            publishModalInteractionState()
         }
         .onDisappear {
             NotificationCenter.default.post(
@@ -607,6 +606,14 @@
                 userInfo: ["active": false]
             )
         }
+    }
+
+    private func publishModalInteractionState() {
+        NotificationCenter.default.post(
+            name: .tagLauncherModalInteractionChanged,
+            object: nil,
+            userInfo: ["active": pendingUncategorizedDrop != nil || pendingTagRemovalDrop != nil]
+        )
     }
 
     /// Set edit phase with synchronous notification BEFORE state change.
@@ -2111,7 +2118,6 @@
     }
 
     private func dismissTagRemovalDropConfirm() {
-        resetTransientDragState(keepingPendingTagRemovalDrop: true)
         tagRemovalDropSuppressFuturePrompt = false
         withAnimation(.easeOut(duration: 0.18)) {
             pendingTagRemovalDrop = nil
@@ -2122,15 +2128,17 @@
         guard let pendingDrop = pendingTagRemovalDrop else { return }
         let app = pendingDrop.app
         let tagName = pendingDrop.tagName
-        if tagRemovalDropSuppressFuturePrompt {
+        let shouldSuppressFuturePrompt = tagRemovalDropSuppressFuturePrompt
+        if shouldSuppressFuturePrompt {
             skipTagRemovalDropConfirm = true
         }
-        resetTransientDragState(keepingPendingTagRemovalDrop: true)
         tagRemovalDropSuppressFuturePrompt = false
         withAnimation(.easeOut(duration: 0.16)) {
             pendingTagRemovalDrop = nil
         }
-        removeTagFromDroppedApp(app: app, tagName: tagName)
+        DispatchQueue.main.async {
+            removeTagFromDroppedApp(app: app, tagName: tagName)
+        }
     }
 
     private func removeTagFromDroppedApp(app: AppInfo, tagName: String) {
diff --git a/Apptag/EditModeViews.swift b/Apptag/EditModeViews.swift
index 2a2c60e..fd4f68e 100644
--- a/Apptag/EditModeViews.swift
+++ b/Apptag/EditModeViews.swift
@@ -406,7 +406,7 @@
                     .foregroundStyle(.white)
                     .frame(maxWidth: .infinity, alignment: .leading)
 
-                Button(action: onCancel) {
+                Button(action: { DispatchQueue.main.async(execute: onCancel) }) {
                     Image(systemName: "xmark")
                         .font(.system(size: 12, weight: .bold))
                         .foregroundStyle(.white.opacity(0.82))
@@ -427,15 +427,37 @@
                 .fixedSize(horizontal: false, vertical: true)
                 .frame(maxWidth: .infinity, alignment: .leading)
 
-            Toggle(doNotRemindTitle, isOn: $doNotRemind)
-                .toggleStyle(.checkbox)
-                .font(.system(size: 13, weight: .medium))
-                .foregroundStyle(.white.opacity(0.80))
+            Button {
+                doNotRemind.toggle()
+            } label: {
+                HStack(spacing: 10) {
+                    ZStack {
+                        RoundedRectangle(cornerRadius: 4, style: .continuous)
+                            .fill(doNotRemind ? Color.accentColor : Color.white.opacity(0.10))
+                        RoundedRectangle(cornerRadius: 4, style: .continuous)
+                            .stroke(Color.white.opacity(0.92), lineWidth: 1.6)
+                        if doNotRemind {
+                            Image(systemName: "checkmark")
+                                .font(.system(size: 12, weight: .bold))
+                                .foregroundStyle(.white)
+                        }
+                    }
+                    .frame(width: 18, height: 18)
+
+                    Text(doNotRemindTitle)
+                        .font(.system(size: 13, weight: .medium))
+                        .foregroundStyle(.white.opacity(0.82))
+                        .lineLimit(2)
+                        .multilineTextAlignment(.leading)
+                }
+                .contentShape(Rectangle())
+            }
+            .buttonStyle(.plain)
 
             HStack(spacing: 12) {
                 Spacer(minLength: 0)
 
-                Button(action: onCancel) {
+                Button(action: { DispatchQueue.main.async(execute: onCancel) }) {
                     Text(cancelTitle)
                         .font(.system(size: 13, weight: .semibold))
                         .foregroundStyle(.white.opacity(0.86))
@@ -447,7 +469,7 @@
                 }
                 .buttonStyle(.plain)
 
-                Button(action: onConfirm) {
+                Button(action: { DispatchQueue.main.async(execute: onConfirm) }) {
                     Text(confirmTitle)
                         .font(.system(size: 13, weight: .semibold))
                         .foregroundStyle(.white)

--
Gitblit v1.9.3