Ariver
2026-06-27 28be00ea53666fd5500a702a3c187c0e27dd90ad
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
@@ -16,19 +17,34 @@
            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(.bordered)
            .buttonStyle(.plain)
            .help(tr("edit.exit"))
            .shadow(color: theme.editButtonShadowColor, radius: 10, y: 4)
            Spacer(minLength: 20)
            VStack(alignment: .trailing, spacing: 5) {
                HStack(alignment: .center, spacing: 14) {
                    EditOperationPicker(
                        theme: theme,
                        operation: operation,
                        onSelect: onSelectOperation
                    )
                    EditConfirmButton(
                        theme: theme,
                        title: confirmTitle,
                        isDisabled: isConfirmDisabled,
                        action: onConfirm
@@ -37,9 +53,9 @@
                Text(hintText)
                    .font(.system(size: 11, weight: .medium))
                    .foregroundStyle(.secondary)
                    .lineLimit(1)
                    .truncationMode(.middle)
                    .foregroundStyle(theme.editSecondaryTextColor)
                    .lineLimit(2)
                    .multilineTextAlignment(.trailing)
                    .frame(maxWidth: 460, alignment: .trailing)
                    .help(hintText)
            }
@@ -48,10 +64,12 @@
        .padding(.horizontal, 24)
        .padding(.top, notchHeight > 0 ? notchHeight + 10 : 20)
        .padding(.bottom, 10)
        .background(theme.editToolbarSurfaceColor)
    }
}
private struct EditOperationPicker: View {
    let theme: AppGridTheme
    let operation: EditTagOperation
    let onSelect: (EditTagOperation) -> Void
@@ -59,16 +77,18 @@
        HStack(spacing: 10) {
            Text(tr("edit.operationLabel"))
                .font(.system(size: 12, weight: .medium))
                .foregroundStyle(.secondary)
                .foregroundStyle(theme.editSecondaryTextColor)
            HStack(spacing: 14) {
                EditOperationModeButton(
                    theme: theme,
                    operation: operation,
                    mode: .add,
                    titleKey: "edit.modeAdd",
                    onSelect: onSelect
                )
                EditOperationModeButton(
                    theme: theme,
                    operation: operation,
                    mode: .remove,
                    titleKey: "edit.modeRemove",
@@ -76,10 +96,22 @@
                )
            }
        }
        .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
@@ -97,23 +129,24 @@
                ZStack {
                    Circle()
                        .strokeBorder(
                            isActive ? Color.accentColor : Color.secondary.opacity(0.58),
                            isActive ? theme.editAccentColor : theme.editInactiveIndicatorColor,
                            lineWidth: 1.6
                        )
                        .frame(width: 13, height: 13)
                    if isActive {
                        Circle()
                            .fill(Color.accentColor)
                            .fill(theme.editAccentColor)
                            .frame(width: 7, height: 7)
                    }
                }
                Text(tr(titleKey))
                    .font(.system(size: 12, weight: .semibold))
                    .foregroundStyle(isActive ? Color.primary : Color.secondary)
                    .foregroundStyle(isActive ? theme.editPrimaryTextColor : theme.editSecondaryTextColor)
                    .lineLimit(1)
            }
            .padding(.vertical, 4)
            .padding(.horizontal, 2)
            .contentShape(Rectangle())
        }
        .buttonStyle(.plain)
@@ -123,6 +156,7 @@
}
private struct EditConfirmButton: View {
    let theme: AppGridTheme
    let title: String
    let isDisabled: Bool
    let action: () -> Void
@@ -131,7 +165,7 @@
        Button(action: action) {
            Text(title)
                .font(.system(size: 13, weight: .semibold))
                .foregroundStyle(Color.white.opacity(isDisabled ? 0.76 : 1.0))
                .foregroundStyle(isDisabled ? theme.editDisabledTextColor : theme.editConfirmForegroundColor)
                .lineLimit(1)
                .minimumScaleFactor(0.82)
                .frame(minWidth: 116)
@@ -139,20 +173,22 @@
                .padding(.horizontal, 8)
                .background(
                    RoundedRectangle(cornerRadius: 8, style: .continuous)
                        .fill(Color.accentColor.opacity(isDisabled ? 0.40 : 1.0))
                        .fill(isDisabled ? theme.editDisabledSurfaceColor : theme.editAccentColor)
                )
                .overlay(
                    RoundedRectangle(cornerRadius: 8, style: .continuous)
                        .stroke(Color.accentColor.opacity(isDisabled ? 0.18 : 0.24), lineWidth: 1)
                        .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
@@ -160,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)
@@ -168,7 +204,7 @@
            Text(tr("edit.dragHint"))
                .font(.caption2)
                .foregroundStyle(.tertiary)
                .foregroundStyle(theme.editTertiaryTextColor)
                .lineLimit(nil)
                .fixedSize(horizontal: false, vertical: true)
                .multilineTextAlignment(.leading)
@@ -188,6 +224,7 @@
    let operation: EditTagOperation
    let isSelected: Bool
    let isRemovableCandidate: Bool
    let theme: AppGridTheme
    let onToggle: () -> Void
    private var isEnabled: Bool {
@@ -205,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
@@ -217,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)
@@ -226,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 {
@@ -255,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 {
@@ -273,6 +314,7 @@
    let app: AppInfo
    let iconSize: CGFloat
    let isSelected: Bool
    let theme: AppGridTheme
    let onToggle: () -> Void
    var body: some View {
@@ -285,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
@@ -299,6 +341,7 @@
                Text(app.displayName)
                    .font(.system(size: 11, weight: .medium))
                    .foregroundStyle(theme.editPrimaryTextColor)
                    .lineLimit(1)
                    .truncationMode(.tail)
                    .frame(maxWidth: iconSize + 20)
@@ -307,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)
    }
@@ -391,9 +442,9 @@
            }
            Text(message)
                .font(.system(size: 15, weight: .medium))
                .lineSpacing(4)
                .foregroundStyle(.white.opacity(0.84))
                .font(.system(size: 17, weight: .semibold))
                .lineSpacing(5)
                .foregroundStyle(.white.opacity(0.70))
                .multilineTextAlignment(.leading)
                .fixedSize(horizontal: false, vertical: true)
                .frame(maxWidth: .infinity, alignment: .leading)
@@ -570,3 +621,222 @@
        .onExitCommand(perform: onCancel)
    }
}
struct UsageTipsCloseReminderBubble: View {
    let title: String
    let message: String
    let previewCallout: String
    let doNotRemindTitle: String
    @Binding var doNotRemind: Bool
    let confirmTitle: String
    let onCancel: () -> Void
    let onConfirm: () -> Void
    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            HStack(alignment: .top, spacing: 12) {
                Text(title)
                    .font(.system(size: 22, weight: .bold))
                    .foregroundStyle(.white)
                    .fixedSize(horizontal: false, vertical: true)
                    .frame(maxWidth: .infinity, alignment: .leading)
                Button(action: onCancel) {
                    Image(systemName: "xmark")
                        .font(.system(size: 12, weight: .bold))
                        .foregroundStyle(.white.opacity(0.82))
                        .frame(width: 26, height: 26)
                        .background(Circle().fill(Color.white.opacity(0.12)))
                }
                .buttonStyle(.plain)
                .help(tr("tag.cancel"))
            }
            UsageTipsCloseReminderSettingsPreview(callout: previewCallout)
            Text(message)
                .font(.system(size: 15, weight: .medium))
                .lineSpacing(4)
                .foregroundStyle(.white.opacity(0.84))
                .multilineTextAlignment(.leading)
                .fixedSize(horizontal: false, vertical: true)
                .frame(maxWidth: .infinity, alignment: .leading)
            HStack(alignment: .bottom, spacing: 16) {
                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: .semibold))
                            .foregroundStyle(.white.opacity(0.86))
                            .lineLimit(2)
                            .multilineTextAlignment(.leading)
                    }
                    .frame(maxWidth: .infinity, alignment: .leading)
                    .contentShape(Rectangle())
                }
                .buttonStyle(.plain)
                .frame(maxWidth: .infinity, alignment: .leading)
                Button(action: onConfirm) {
                    Text(confirmTitle)
                        .font(.system(size: 13, weight: .semibold))
                        .foregroundStyle(.white)
                        .frame(minWidth: 96)
                        .frame(height: 32)
                        .padding(.horizontal, 10)
                        .background(
                            RoundedRectangle(cornerRadius: 8, style: .continuous)
                                .fill(Color.accentColor)
                        )
                }
                .buttonStyle(.plain)
            }
            .padding(.top, 4)
        }
        .padding(.horizontal, 24)
        .padding(.vertical, 20)
        .background(
            RoundedRectangle(cornerRadius: 18, style: .continuous)
                .fill(Color.black.opacity(0.92))
                .shadow(color: .black.opacity(0.34), radius: 24, y: 16)
                .shadow(color: .black.opacity(0.18), radius: 8, y: 3)
        )
        .onExitCommand(perform: onCancel)
    }
}
private struct UsageTipsCloseReminderSettingsPreview: View {
    let callout: String
    var body: some View {
        VStack(alignment: .leading, spacing: 10) {
            HStack(spacing: 6) {
                Circle().fill(Color.red.opacity(0.78)).frame(width: 10, height: 10)
                Circle().fill(Color.gray.opacity(0.45)).frame(width: 10, height: 10)
                Circle().fill(Color.gray.opacity(0.36)).frame(width: 10, height: 10)
                Text(verbatim: "TagLauncher Settings")
                    .font(.system(size: 11, weight: .semibold))
                    .foregroundStyle(Color.black.opacity(0.62))
                    .padding(.leading, 6)
                Spacer()
            }
            HStack(spacing: 18) {
                previewTab(systemImage: "globe", title: tr("settings.language"), active: false)
                previewTab(systemImage: "gearshape", title: tr("settings.general"), active: true)
                previewTab(systemImage: "keyboard", title: tr("quickSearch.hotkeys"), active: false)
                previewTab(systemImage: "tag", title: tr("settings.tags"), active: false)
                previewTab(systemImage: "externaldrive.fill", title: tr("settings.data"), active: false)
                previewTab(systemImage: "info.circle", title: tr("settings.about"), active: false)
            }
            .frame(maxWidth: .infinity, alignment: .center)
            .padding(.vertical, 8)
            HStack(spacing: 12) {
                previewCheckbox(title: tr("settings.launchAtLogin"), highlighted: false)
                previewCheckbox(title: tr("settings.showInDock"), highlighted: false)
                previewCheckbox(title: tr("settings.hideAppNames"), highlighted: false)
                previewCheckbox(title: tr("settings.hideUsageTips"), highlighted: true)
            }
            .frame(maxWidth: .infinity, alignment: .center)
            .padding(10)
            .background(
                RoundedRectangle(cornerRadius: 10, style: .continuous)
                    .fill(Color.white.opacity(0.84))
            )
            HStack(spacing: 8) {
                Image(systemName: "arrow.up.left")
                    .font(.system(size: 12, weight: .bold))
                    .foregroundStyle(Color.accentColor)
                Text(callout)
                    .font(.system(size: 11, weight: .semibold))
                    .foregroundStyle(Color.accentColor)
                    .lineLimit(2)
                    .multilineTextAlignment(.center)
                    .fixedSize(horizontal: false, vertical: true)
            }
            .frame(maxWidth: .infinity, alignment: .trailing)
            .padding(.trailing, 44)
        }
        .padding(14)
        .background(
            RoundedRectangle(cornerRadius: 14, style: .continuous)
                .fill(
                    LinearGradient(
                        colors: [
                            Color.white.opacity(0.98),
                            Color(red: 0.93, green: 0.96, blue: 1.0).opacity(0.96)
                        ],
                        startPoint: .topLeading,
                        endPoint: .bottomTrailing
                    )
                )
        )
        .overlay(
            RoundedRectangle(cornerRadius: 14, style: .continuous)
                .stroke(Color.white.opacity(0.64), lineWidth: 1)
        )
    }
    private func previewTab(systemImage: String, title: String, active: Bool) -> some View {
        VStack(spacing: 4) {
            Image(systemName: systemImage)
                .font(.system(size: 18, weight: .semibold))
            Text(title)
                .font(.system(size: 9, weight: .semibold))
                .lineLimit(1)
                .minimumScaleFactor(0.62)
        }
        .foregroundStyle(active ? Color.accentColor : Color.gray.opacity(0.82))
        .frame(width: 70, height: 48)
        .background(
            RoundedRectangle(cornerRadius: 9, style: .continuous)
                .fill(active ? Color.accentColor.opacity(0.12) : Color.clear)
        )
    }
    private func previewCheckbox(title: String, highlighted: Bool) -> some View {
        HStack(spacing: 6) {
            ZStack {
                RoundedRectangle(cornerRadius: 4, style: .continuous)
                    .fill(highlighted ? Color.accentColor : Color.gray.opacity(0.24))
                Image(systemName: "checkmark")
                    .font(.system(size: 9, weight: .bold))
                    .foregroundStyle(.white)
            }
            .frame(width: 16, height: 16)
            Text(title)
                .font(.system(size: 10, weight: highlighted ? .bold : .semibold))
                .foregroundStyle(Color.black.opacity(0.72))
                .lineLimit(1)
                .minimumScaleFactor(0.58)
        }
        .padding(.horizontal, 8)
        .frame(height: 30)
        .background(
            RoundedRectangle(cornerRadius: 8, style: .continuous)
                .fill(highlighted ? Color.accentColor.opacity(0.12) : Color.clear)
        )
        .overlay(
            RoundedRectangle(cornerRadius: 8, style: .continuous)
                .stroke(highlighted ? Color.accentColor.opacity(0.72) : Color.clear, lineWidth: 1.5)
        )
    }
}