Ariver
2026-05-28 da71eb5bccbdbd22da7cd74b6ddcec031f069482
Stabilize empty-drop tag removal confirmation
3 files modified
68 ■■■■ changed files
Apptag/AppGridCollectionView.swift 4 ●●● patch | view | raw | blame | history
Apptag/ContentView.swift 28 ●●●●● patch | view | raw | blame | history
Apptag/EditModeViews.swift 36 ●●●● patch | view | raw | blame | history
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() {
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) {
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)