Ariver
2026-06-25 ab59aa3a800139e2fe80f7e802dfada2258ab0f6
src/Apptag/ContentView.swift
@@ -328,6 +328,8 @@
    var pendingTagRemovalDrop: PendingTagRemovalDrop? = nil
    var uncategorizedDropSuppressFuturePrompt = false
    var tagRemovalDropSuppressFuturePrompt = false
    var usageTipsCloseReminderVisible = false
    var usageTipsCloseSuppressFuturePrompt = false
    var appDragResetToken = 0
    var bubbleDraftNote = ""
    var tagNavigationHoveredGroupName: String? = nil
@@ -438,6 +440,7 @@
    @AppStorage("useAppKitTagNavigation") private var useAppKitTagNavigation = AppDefaults.useAppKitTagNavigation
    @AppStorage("skipTagRemovalDropConfirm") private var skipTagRemovalDropConfirm = false
    @AppStorage("skipUncategorizedDropConfirm") private var skipUncategorizedDropConfirm = false
    @AppStorage("skipUsageTipsCloseReminder") private var skipUsageTipsCloseReminder = false
    private let editSidebarWidth: CGFloat = 188
    private let editSidebarHorizontalInset: CGFloat = 12
@@ -449,6 +452,7 @@
            || appGridInteraction.appDragModeActive
            || appGridInteraction.pendingUncategorizedDrop != nil
            || appGridInteraction.pendingTagRemovalDrop != nil
            || appGridInteraction.usageTipsCloseReminderVisible
    }
    private var appGridHighlightedGroupName: String? {
        appGridInteraction.tagNavigationHoveredGroupName
@@ -514,68 +518,13 @@
            }
            if shouldRenderAppGridBehindQuickSearch {
                if notchHeight > 0 {
                    VStack {
                        Rectangle().fill(.black)
                            .frame(height: notchHeight)
                            .ignoresSafeArea(edges: .top)
                        Spacer()
                    }
                    .allowsHitTesting(false)
                }
                switch editPhase {
                case .none:
                    normalContent
                case .editingTags:
                    editTagsView
                case .editingApps:
                    editAppsView
                }
                uncommonAppBubbleOverlay
                smartStartNoticeOverlay
                editActionFeedbackOverlay
                uncategorizedDropConfirmOverlay
                tagRemovalDropConfirmOverlay
                appGridInteractionLayer
            }
            quickSearchOverlay
            if shouldRenderAppGridBehindQuickSearch, let message = appGridInteraction.dropWarningToast {
                Text(message)
                    .font(.system(size: 16, weight: .semibold))
                    .foregroundStyle(.primary)
                    .padding(.horizontal, 22)
                    .padding(.vertical, 12)
                    .background(
                        RoundedRectangle(cornerRadius: 10)
                            .fill(.ultraThickMaterial)
                            .shadow(color: .black.opacity(0.22), radius: 18, y: 10)
                    )
                    .transition(.scale(scale: 0.96).combined(with: .opacity))
                    .allowsHitTesting(false)
            }
            if shouldRenderAppGridBehindQuickSearch && appGridInteraction.dropRefreshVisible {
                Color.black.opacity(0.08)
                    .ignoresSafeArea()
                    .transition(.opacity)
                    .allowsHitTesting(false)
                ProgressView()
                    .progressViewStyle(.circular)
                    .controlSize(.large)
                    .scaleEffect(1.15)
                    .padding(24)
                    .background(
                        RoundedRectangle(cornerRadius: 12)
                            .fill(.ultraThickMaterial)
                            .shadow(color: .black.opacity(0.26), radius: 24, y: 12)
                    )
                    .transition(.scale(scale: 0.92).combined(with: .opacity))
                    .allowsHitTesting(false)
            }
            dropWarningToastOverlay
            dropRefreshOverlay
        }
        .onAppear {
            refreshNotchHeight()
@@ -681,6 +630,76 @@
        }
    }
    @ViewBuilder
    private var appGridInteractionLayer: some View {
        if notchHeight > 0 {
            VStack {
                Rectangle().fill(.black)
                    .frame(height: notchHeight)
                    .ignoresSafeArea(edges: .top)
                Spacer()
            }
            .allowsHitTesting(false)
        }
        switch editPhase {
        case .none:
            normalContent
        case .editingTags:
            editTagsView
        case .editingApps:
            editAppsView
        }
        uncommonAppBubbleOverlay
        smartStartNoticeOverlay
        editActionFeedbackOverlay
        uncategorizedDropConfirmOverlay
        tagRemovalDropConfirmOverlay
        usageTipsCloseReminderOverlay
    }
    @ViewBuilder
    private var dropWarningToastOverlay: some View {
        if shouldRenderAppGridBehindQuickSearch, let message = appGridInteraction.dropWarningToast {
            Text(message)
                .font(.system(size: 16, weight: .semibold))
                .foregroundStyle(.primary)
                .padding(.horizontal, 22)
                .padding(.vertical, 12)
                .background(
                    RoundedRectangle(cornerRadius: 10)
                        .fill(.ultraThickMaterial)
                        .shadow(color: .black.opacity(0.22), radius: 18, y: 10)
                )
                .transition(.scale(scale: 0.96).combined(with: .opacity))
                .allowsHitTesting(false)
        }
    }
    @ViewBuilder
    private var dropRefreshOverlay: some View {
        if shouldRenderAppGridBehindQuickSearch && appGridInteraction.dropRefreshVisible {
            Color.black.opacity(0.08)
                .ignoresSafeArea()
                .transition(.opacity)
                .allowsHitTesting(false)
            ProgressView()
                .progressViewStyle(.circular)
                .controlSize(.large)
                .scaleEffect(1.15)
                .padding(24)
                .background(
                    RoundedRectangle(cornerRadius: 12)
                        .fill(.ultraThickMaterial)
                        .shadow(color: .black.opacity(0.26), radius: 24, y: 12)
                )
                .transition(.scale(scale: 0.92).combined(with: .opacity))
                .allowsHitTesting(false)
        }
    }
    private func publishModalInteractionState() {
        NotificationCenter.default.post(
            name: .tagLauncherModalInteractionChanged,
@@ -689,6 +708,7 @@
                "active": smartStartNotice != nil
                    || appGridInteraction.pendingUncategorizedDrop != nil
                    || appGridInteraction.pendingTagRemovalDrop != nil
                    || appGridInteraction.usageTipsCloseReminderVisible
            ]
        )
    }
@@ -1242,7 +1262,7 @@
                    },
                    onScrollActivity: handleAppGridScrollActivity,
                    onDragModeChange: { setAppDragMode($0) },
                    onHideUsageTips: { hideUsageTips = true },
                    onHideUsageTips: requestHideUsageTips,
                    onUsageTipsHoverChange: handleUsageTipsHoverChange
                )
            }
@@ -1689,6 +1709,34 @@
        }
        .ignoresSafeArea()
        .allowsHitTesting(appGridInteraction.pendingTagRemovalDrop != nil)
    }
    private var usageTipsCloseReminderOverlay: some View {
        GeometryReader { proxy in
            if appGridInteraction.usageTipsCloseReminderVisible {
                ZStack {
                    Color.black.opacity(0.14)
                        .ignoresSafeArea()
                    UsageTipsCloseReminderBubble(
                        title: tr("usageTips.closeReminder.title"),
                        message: tr("usageTips.closeReminder.message"),
                        previewCallout: tr("usageTips.closeReminder.previewCallout"),
                        doNotRemindTitle: tr("usageTips.closeReminder.doNotRemind"),
                        doNotRemind: $appGridInteraction.usageTipsCloseSuppressFuturePrompt,
                        confirmTitle: tr("edit.confirm"),
                        onCancel: dismissUsageTipsCloseReminder,
                        onConfirm: confirmUsageTipsCloseReminder
                    )
                    .frame(width: min(760, max(420, proxy.size.width - 120)))
                    .position(x: proxy.size.width / 2, y: proxy.size.height / 2)
                    .transition(.scale(scale: 0.94).combined(with: .opacity))
                }
                .zIndex(712)
            }
        }
        .ignoresSafeArea()
        .allowsHitTesting(appGridInteraction.usageTipsCloseReminderVisible)
    }
    private func buildEditActionFeedback(for selectedApps: [AppInfo], tags: [String]) -> EditActionFeedback {
@@ -2496,6 +2544,40 @@
        }
    }
    private func requestHideUsageTips() {
        guard !hideUsageTips else { return }
        guard !skipUsageTipsCloseReminder else {
            hideUsageTips = true
            return
        }
        clearAppBubbleState()
        appGridInteraction.usageTipsCloseSuppressFuturePrompt = false
        withAnimation(.spring(response: 0.24, dampingFraction: 0.84)) {
            appGridInteraction.usageTipsCloseReminderVisible = true
        }
        publishModalInteractionState()
    }
    private func dismissUsageTipsCloseReminder() {
        appGridInteraction.usageTipsCloseSuppressFuturePrompt = false
        withAnimation(.easeOut(duration: 0.18)) {
            appGridInteraction.usageTipsCloseReminderVisible = false
        }
        publishModalInteractionState()
    }
    private func confirmUsageTipsCloseReminder() {
        if appGridInteraction.usageTipsCloseSuppressFuturePrompt {
            skipUsageTipsCloseReminder = true
        }
        appGridInteraction.usageTipsCloseSuppressFuturePrompt = false
        withAnimation(.easeOut(duration: 0.16)) {
            appGridInteraction.usageTipsCloseReminderVisible = false
        }
        hideUsageTips = true
        publishModalInteractionState()
    }
    private func removeTagFromDroppedApp(app: AppInfo, tagName: String) {
        guard isRemovableRegularTag(tagName),
              appHasTag(app, tagName: tagName)