import SwiftUI // 瞬时通知横幅(#2):覆盖在标签内容顶部,醒目、状态分明(成功绿 / 失败红 / 信息中性)、自动消失 // (EngineController.notify 安排 4s 后移除)、可点叉手动清除。取代原先散落在设备页 / 设置页、串页显示 // 且永不清除的灰色小字(deviceActionStatus / clipboardStatus)。由 EngineController.notices 驱动。 struct NoticeOverlay: View { @Environment(EngineController.self) private var engine var body: some View { VStack(spacing: 8) { ForEach(engine.notices) { notice in NoticeBanner(notice: notice) { engine.dismissNotice(notice.id) } .transition(.move(edge: .top).combined(with: .opacity)) } } .padding(.horizontal, 16) .padding(.top, 8) .animation(.spring(duration: 0.3), value: engine.notices) // 无通知时不拦截下方内容的点击;有通知时仅横幅自身(含叉按钮)可点。 .allowsHitTesting(!engine.notices.isEmpty) } } private struct NoticeBanner: View { let notice: Notice let onDismiss: () -> Void var body: some View { HStack(spacing: 10) { Image(systemName: symbol) .font(.headline) .foregroundStyle(tint) Text(notice.text) .font(.subheadline) .foregroundStyle(.primary) .lineLimit(2) Spacer(minLength: 8) Button { onDismiss() } label: { Image(systemName: "xmark") .font(.caption.weight(.bold)) .foregroundStyle(.secondary) } .buttonStyle(.plain) } .padding(.vertical, 12) .padding(.horizontal, 14) .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 14)) .overlay(RoundedRectangle(cornerRadius: 14).strokeBorder(tint.opacity(0.35), lineWidth: 1)) .shadow(color: .black.opacity(0.12), radius: 8, y: 2) } private var symbol: String { switch notice.kind { case .success: return "checkmark.circle.fill" case .error: return "exclamationmark.triangle.fill" case .info: return "info.circle.fill" } } private var tint: Color { switch notice.kind { case .success: return .green case .error: return .red case .info: return .cdropAccent } } }