Ariver
2026-06-24 c1d6cfc2b59b2ce4f1ebf235397cdf43a4292996
src/Apptag/EditModeViews.swift
@@ -2,6 +2,7 @@
import AppKit
struct EditAppsHeaderView: View {
    let theme: AppGridTheme
    let operation: EditTagOperation
    let hintText: String
    let confirmTitle: String
@@ -12,99 +13,182 @@
    let onConfirm: () -> Void
    var body: some View {
        HStack(alignment: .center, spacing: 14) {
            HStack(alignment: .center, spacing: 12) {
                Button(action: onExit) {
                    Label(tr("edit.exit"), systemImage: "rectangle.portrait.and.arrow.right")
                        .font(.system(size: 12))
                }
                .buttonStyle(.bordered)
                EditOperationPicker(
                    operation: operation,
                    onSelect: onSelectOperation
                )
        HStack(alignment: .top, spacing: 14) {
            Button(action: onExit) {
                Label(tr("edit.exit"), systemImage: "rectangle.portrait.and.arrow.right")
                    .font(.system(size: 12))
                    .foregroundStyle(theme.editPrimaryTextColor)
                    .padding(.horizontal, 8)
                    .frame(height: 28)
                    .background(
                        RoundedRectangle(cornerRadius: 7, style: .continuous)
                            .fill(theme.editControlSurfaceColor)
                    )
                    .overlay(
                        RoundedRectangle(cornerRadius: 7, style: .continuous)
                            .stroke(theme.editControlStrokeColor, lineWidth: 1)
                    )
            }
            .buttonStyle(.plain)
            .help(tr("edit.exit"))
            .shadow(color: theme.editButtonShadowColor, radius: 10, y: 4)
            Spacer(minLength: 16)
            Spacer(minLength: 20)
            Text(hintText)
                .font(.system(size: 12, weight: .medium))
                .foregroundStyle(.secondary)
                .lineLimit(1)
                .truncationMode(.middle)
                .frame(minWidth: 0, maxWidth: 760, alignment: .trailing)
                .layoutPriority(1)
                .help(hintText)
            VStack(alignment: .trailing, spacing: 5) {
                HStack(alignment: .center, spacing: 14) {
                    EditOperationPicker(
                        theme: theme,
                        operation: operation,
                        onSelect: onSelectOperation
                    )
            Button(confirmTitle, action: onConfirm)
                .buttonStyle(.borderedProminent)
                .disabled(isConfirmDisabled)
                .fixedSize(horizontal: true, vertical: false)
                .layoutPriority(2)
                    EditConfirmButton(
                        theme: theme,
                        title: confirmTitle,
                        isDisabled: isConfirmDisabled,
                        action: onConfirm
                    )
                }
                Text(hintText)
                    .font(.system(size: 11, weight: .medium))
                    .foregroundStyle(theme.editSecondaryTextColor)
                    .lineLimit(2)
                    .multilineTextAlignment(.trailing)
                    .frame(maxWidth: 460, alignment: .trailing)
                    .help(hintText)
            }
            .layoutPriority(1)
        }
        .padding(.horizontal, 24)
        .padding(.top, notchHeight > 0 ? notchHeight + 10 : 20)
        .padding(.bottom, 12)
        .padding(.bottom, 10)
        .background(theme.editToolbarSurfaceColor)
    }
}
private struct EditOperationPicker: View {
    let theme: AppGridTheme
    let operation: EditTagOperation
    let onSelect: (EditTagOperation) -> Void
    var body: some View {
        HStack(spacing: 8) {
        HStack(spacing: 10) {
            Text(tr("edit.operationLabel"))
                .font(.system(size: 12, weight: .medium))
                .foregroundStyle(.secondary)
                .foregroundStyle(theme.editSecondaryTextColor)
            HStack(spacing: 6) {
            HStack(spacing: 14) {
                EditOperationModeButton(
                    theme: theme,
                    operation: operation,
                    mode: .add,
                    titleKey: "edit.modeAdd",
                    systemImage: "plus.circle.fill",
                    isActive: operation == .add,
                    onSelect: onSelect
                )
                EditOperationModeButton(
                    theme: theme,
                    operation: operation,
                    mode: .remove,
                    titleKey: "edit.modeRemove",
                    systemImage: "minus.circle.fill",
                    isActive: operation == .remove,
                    onSelect: onSelect
                )
            }
        }
        .padding(.horizontal, 10)
        .frame(height: 34)
        .background(
            RoundedRectangle(cornerRadius: 8, style: .continuous)
                .fill(theme.editControlSurfaceColor)
        )
        .overlay(
            RoundedRectangle(cornerRadius: 8, style: .continuous)
                .stroke(theme.editControlStrokeColor, lineWidth: 1)
        )
        .shadow(color: theme.editButtonShadowColor, radius: 10, y: 4)
    }
}
private struct EditOperationModeButton: View {
    let theme: AppGridTheme
    let operation: EditTagOperation
    let mode: EditTagOperation
    let titleKey: String
    let systemImage: String
    let isActive: Bool
    let onSelect: (EditTagOperation) -> Void
    private var isActive: Bool {
        operation == mode
    }
    var body: some View {
        Button {
            onSelect(mode)
        } label: {
            Label(tr(titleKey), systemImage: systemImage)
                .font(.system(size: 12, weight: .semibold))
                .padding(.horizontal, 12)
                .padding(.vertical, 6)
                .foregroundStyle(isActive ? Color.white : Color.primary)
            HStack(spacing: 6) {
                ZStack {
                    Circle()
                        .strokeBorder(
                            isActive ? theme.editAccentColor : theme.editInactiveIndicatorColor,
                            lineWidth: 1.6
                        )
                        .frame(width: 13, height: 13)
                    if isActive {
                        Circle()
                            .fill(theme.editAccentColor)
                            .frame(width: 7, height: 7)
                    }
                }
                Text(tr(titleKey))
                    .font(.system(size: 12, weight: .semibold))
                    .foregroundStyle(isActive ? theme.editPrimaryTextColor : theme.editSecondaryTextColor)
                    .lineLimit(1)
            }
            .padding(.vertical, 4)
            .padding(.horizontal, 2)
            .contentShape(Rectangle())
        }
        .buttonStyle(.plain)
        .accessibilityAddTraits(isActive ? .isSelected : [])
        .help(tr(titleKey))
    }
}
private struct EditConfirmButton: View {
    let theme: AppGridTheme
    let title: String
    let isDisabled: Bool
    let action: () -> Void
    var body: some View {
        Button(action: action) {
            Text(title)
                .font(.system(size: 13, weight: .semibold))
                .foregroundStyle(isDisabled ? theme.editDisabledTextColor : theme.editConfirmForegroundColor)
                .lineLimit(1)
                .minimumScaleFactor(0.82)
                .frame(minWidth: 116)
                .frame(height: 34)
                .padding(.horizontal, 8)
                .background(
                    Capsule(style: .continuous)
                        .fill(isActive ? Color.accentColor : Color.secondary.opacity(0.12))
                    RoundedRectangle(cornerRadius: 8, style: .continuous)
                        .fill(isDisabled ? theme.editDisabledSurfaceColor : theme.editAccentColor)
                )
                .overlay(
                    RoundedRectangle(cornerRadius: 8, style: .continuous)
                        .stroke(isDisabled ? theme.editControlStrokeColor : theme.editAccentColor.opacity(0.72), lineWidth: 1)
                )
        }
        .buttonStyle(.plain)
        .disabled(isDisabled)
        .contentShape(RoundedRectangle(cornerRadius: 8, style: .continuous))
        .shadow(color: isDisabled ? Color.clear : theme.editButtonShadowColor, radius: 12, y: 5)
    }
}
struct EditAppsSidebarIntroView: View {
    let theme: AppGridTheme
    let width: CGFloat
    let horizontalInset: CGFloat
@@ -112,7 +196,7 @@
        VStack(alignment: .leading, spacing: 6) {
            Text(tr("edit.selectTags"))
                .font(.caption)
                .foregroundStyle(.secondary)
                .foregroundStyle(theme.editSecondaryTextColor)
                .lineLimit(2)
                .fixedSize(horizontal: false, vertical: true)
                .multilineTextAlignment(.leading)
@@ -120,7 +204,7 @@
            Text(tr("edit.dragHint"))
                .font(.caption2)
                .foregroundStyle(.tertiary)
                .foregroundStyle(theme.editTertiaryTextColor)
                .lineLimit(nil)
                .fixedSize(horizontal: false, vertical: true)
                .multilineTextAlignment(.leading)
@@ -140,6 +224,7 @@
    let operation: EditTagOperation
    let isSelected: Bool
    let isRemovableCandidate: Bool
    let theme: AppGridTheme
    let onToggle: () -> Void
    private var isEnabled: Bool {
@@ -157,7 +242,7 @@
    var body: some View {
        HStack(spacing: 6) {
            Circle()
                .fill(isSelected ? Color.accentColor : Color.secondary.opacity(isEnabled ? 0.3 : 0.18))
                .fill(isSelected ? theme.editAccentColor : theme.editInactiveIndicatorColor.opacity(isEnabled ? 1.0 : 0.56))
                .frame(width: 16, height: 16)
                .overlay(
                    isSelected
@@ -169,7 +254,7 @@
            Text(displayName)
                .font(.system(size: 13, weight: isUncommon ? .semibold : .medium))
                .foregroundStyle(isEnabled ? Color.primary : Color.secondary.opacity(0.66))
                .foregroundStyle(isEnabled ? theme.editPrimaryTextColor : theme.editDisabledTextColor)
                .lineLimit(1)
                .truncationMode(.tail)
                .frame(maxWidth: .infinity, alignment: .leading)
@@ -178,13 +263,13 @@
                if isUncommon {
                    Image(systemName: "questionmark.bubble.fill")
                        .font(.system(size: 11, weight: .semibold))
                        .foregroundStyle(isEnabled ? Color.secondary : Color.secondary.opacity(0.55))
                        .foregroundStyle(isEnabled ? theme.editSecondaryTextColor : theme.editDisabledTextColor)
                        .frame(width: 16, height: 16)
                }
                if showsPendingRemoval {
                    Image(systemName: "trash.fill")
                        .font(.system(size: 11, weight: .bold))
                        .foregroundStyle(.red)
                        .foregroundStyle(Color.red.opacity(theme.usesDarkGlass ? 0.96 : 0.84))
                        .frame(width: 16, height: 16)
                        .transition(.scale(scale: 0.85).combined(with: .opacity))
                } else if operation == .remove {
@@ -207,9 +292,13 @@
                .fill(
                    Color(
                        nsColor: TagColor.nsColor(for: colorIndex)
                            .withAlphaComponent(isEnabled ? (isUncommon ? 0.22 : 0.3) : 0.12)
                            .withAlphaComponent(isEnabled ? (isUncommon ? 0.26 : 0.34) : 0.16)
                    )
                )
        )
        .overlay(
            RoundedRectangle(cornerRadius: 6)
                .stroke(theme.editControlStrokeColor.opacity(isSelected ? 1.0 : 0.44), lineWidth: isSelected ? 1.1 : 0.6)
        )
        .contentShape(RoundedRectangle(cornerRadius: 6))
        .onTapGesture {
@@ -225,6 +314,7 @@
    let app: AppInfo
    let iconSize: CGFloat
    let isSelected: Bool
    let theme: AppGridTheme
    let onToggle: () -> Void
    var body: some View {
@@ -237,7 +327,7 @@
                        .frame(width: iconSize, height: iconSize)
                    Circle()
                        .fill(isSelected ? Color.accentColor : Color.secondary.opacity(0.3))
                        .fill(isSelected ? theme.editAccentColor : theme.editInactiveIndicatorColor)
                        .frame(width: 20, height: 20)
                        .overlay(
                            isSelected
@@ -251,6 +341,7 @@
                Text(app.displayName)
                    .font(.system(size: 11, weight: .medium))
                    .foregroundStyle(theme.editPrimaryTextColor)
                    .lineLimit(1)
                    .truncationMode(.tail)
                    .frame(maxWidth: iconSize + 20)
@@ -259,6 +350,14 @@
            .padding(.horizontal, 4)
            .contentShape(RoundedRectangle(cornerRadius: 10))
            .opacity(isSelected ? 1.0 : 0.65)
            .background(
                RoundedRectangle(cornerRadius: 10, style: .continuous)
                    .fill(isSelected ? theme.editControlSurfaceColor : Color.clear)
            )
            .overlay(
                RoundedRectangle(cornerRadius: 10, style: .continuous)
                    .stroke(isSelected ? theme.editControlStrokeColor : Color.clear, lineWidth: 1)
            )
        }
        .buttonStyle(.plain)
    }