// // DropZoneView.swift // MioIsland AirDrop Plugin // // Click-to-choose zone at the center of the panel. Tapping anywhere // inside opens NSOpenPanel → user picks files → NSSharingService // fires the AirDrop chooser sheet. // // Drag-and-drop was intentionally removed: macOS Dynamic Island // panels auto-collapse when the user clicks outside them, which // prevents the Cmd-Tab → Finder → grab file → drag back workflow. // Rather than fight the host's event routing, we commit to the // click-to-choose path which is rock-solid. // import SwiftUI struct DropZoneView: View { @ObservedObject var state: AirDropState = .shared private static let lime = Color( red: 0xCA / 255.0, green: 0xFF / 255.0, blue: 0x00 / 255.0 ) private static let ink = Color.white var body: some View { ZStack { RoundedRectangle(cornerRadius: 14, style: .continuous) .fill(fillColor) RoundedRectangle(cornerRadius: 14, style: .continuous) .strokeBorder(strokeColor, lineWidth: 1) centerContent } .frame(maxWidth: .infinity, maxHeight: .infinity) .contentShape(Rectangle()) .onTapGesture { guard case .idle = state.phase else { return } state.chooseAndSend() } .animation(.easeInOut(duration: 0.25), value: state.phase) } // MARK: - Reactive styling private var fillColor: Color { switch state.phase { case .sent: return Self.lime.opacity(0.14) case .showingPicker: return Color.white.opacity(0.08) case .error: return Color.red.opacity(0.08) case .idle: return Color.white.opacity(0.04) } } private var strokeColor: Color { switch state.phase { case .sent: return Self.lime.opacity(0.9) case .showingPicker: return Self.lime.opacity(0.6) case .error: return Color.red.opacity(0.7) case .idle: return Color.white.opacity(0.18) } } // MARK: - Center content (icon + label) @ViewBuilder private var centerContent: some View { VStack(spacing: 8) { Image(systemName: iconName) .font(.system(size: 32, weight: .light)) .foregroundColor(iconColor) Text(primaryText) .font(.system(size: 13, weight: .semibold)) .foregroundColor(primaryColor) .multilineTextAlignment(.center) if let sub = secondaryText { Text(sub) .font(.system(size: 11)) .foregroundColor(Self.ink.opacity(0.5)) } } .padding(.horizontal, 20) } private var iconName: String { switch state.phase { case .sent: return "checkmark.circle.fill" case .error: return "exclamationmark.triangle.fill" case .showingPicker: return "airplayaudio" case .idle: return "airplayaudio" } } private var iconColor: Color { switch state.phase { case .sent: return Self.lime case .error: return Color.red.opacity(0.8) case .showingPicker: return Self.ink.opacity(0.85) case .idle: return Self.ink.opacity(0.7) } } private var primaryText: String { switch state.phase { case .sent(let n): return L10n.sentCount(n) case .error(let m): return m case .showingPicker: return L10n.opening case .idle: return L10n.chooseFiles } } private var primaryColor: Color { switch state.phase { case .sent: return Self.lime case .error: return Color.red.opacity(0.9) default: return Self.ink.opacity(0.85) } } private var secondaryText: String? { guard case .idle = state.phase else { return nil } return L10n.clickToChoose } }