import SwiftUI import UniformTypeIdentifiers struct ContentView: View { @EnvironmentObject var model: AppModel var body: some View { VStack(spacing: 0) { if !model.encoderReady { banner("cjxl not found — run: brew install jpeg-xl", color: .red) } controls Divider() dropZone Divider() footer } } private var controls: some View { HStack(spacing: 16) { Toggle("Lossless", isOn: $model.lossless) if !model.lossless { HStack { Text("Quality") Slider(value: $model.distance, in: 0.5...3.0) .frame(width: 120) Text(String(format: "d%.1f", model.distance)).monospacedDigit() } } Stepper("Effort \(model.effort)", value: $model.effort, in: 1...9) Toggle("Keep original", isOn: $model.keepOriginal) Spacer() }.padding(10) } private var dropZone: some View { ZStack { if model.items.isEmpty { VStack(spacing: 6) { Image(systemName: "square.and.arrow.down").font(.system(size: 34)) Text("Drop JPG / PNG / GIF here").foregroundColor(.secondary) } } else { List(model.items) { ImageRow(item: $0) } } } .frame(maxWidth: .infinity, maxHeight: .infinity) .onDrop(of: [UTType.fileURL], isTargeted: nil) { providers in for p in providers { _ = p.loadObject(ofClass: URL.self) { url, _ in if let url { DispatchQueue.main.async { model.add(urls: [url]) } } } } return true } } private var footer: some View { HStack { if model.busy { ProgressView().scaleEffect(0.6); Text("Compressing…") } Spacer() Text("Saved \(human(model.totalSaved))").bold() }.padding(8) } private func banner(_ s: String, color: Color) -> some View { Text(s).foregroundColor(.white).frame(maxWidth: .infinity) .padding(6).background(color) } } struct ImageRow: View { @ObservedObject var item: ImageItem var body: some View { HStack { icon Text(item.url.lastPathComponent).lineLimit(1) Spacer() switch item.status { case .done: Text("\(human(item.originalBytes)) → \(human(item.newBytes))").foregroundColor(.secondary) Text(String(format: "−%.0f%%", item.savedFraction * 100)).foregroundColor(.green).bold() case .failed: Text(item.message).foregroundColor(.red).lineLimit(1) case .skipped: Text("no gain").foregroundColor(.secondary) case .working: ProgressView().scaleEffect(0.5) case .pending: Text("queued").foregroundColor(.secondary) } }.monospacedDigit() } @ViewBuilder var icon: some View { switch item.status { case .done: Image(systemName: "checkmark.circle.fill").foregroundColor(.green) case .failed: Image(systemName: "xmark.circle.fill").foregroundColor(.red) default: Image(systemName: "photo") } } }