// // DownloadsPopover.swift // Zyquo Atlas // // Author: Simon-Pierre Boucher // Mail: contact@spboucher.ai // // Toolbar downloads popover: in-flight and finished downloads with progress, // reveal-in-Finder, and open. Backed by the window's DownloadManager. // import SwiftUI struct DownloadsPopover: View { @ObservedObject var downloads: DownloadManager let onClose: () -> Void @EnvironmentObject private var themeEngine: ThemeEngine private var theme: AtlasTheme { themeEngine.theme } var body: some View { VStack(alignment: .leading, spacing: 0) { HStack { Text("Downloads").font(ZyquoFont.bodyEmphasis()).foregroundStyle(theme.textPrimary) Spacer() if downloads.items.contains(where: { $0.state == .finished }) { Button("Clear") { downloads.clearFinished() } .font(ZyquoFont.caption).foregroundStyle(theme.accent) } Button { onClose() } label: { Image(systemName: "xmark") } .buttonStyle(.plain).foregroundStyle(theme.textTertiary) } .padding(ZyquoSpacing.sm) Divider().overlay(theme.border) if downloads.items.isEmpty { Text("No downloads yet") .font(ZyquoFont.caption).foregroundStyle(theme.textTertiary) .frame(maxWidth: .infinity).padding(ZyquoSpacing.lg) } else { ScrollView { VStack(spacing: 0) { ForEach(downloads.items) { item in row(item) Divider().overlay(theme.border) } } } .frame(maxHeight: 320) } } .frame(width: 320) .background(RoundedRectangle(cornerRadius: ZyquoRadius.large).fill(theme.surface)) .overlay(RoundedRectangle(cornerRadius: ZyquoRadius.large).strokeBorder(theme.border, lineWidth: ZyquoMetrics.hairline)) .shadow(color: ZyquoShadow.soft.color, radius: ZyquoShadow.soft.radius, y: ZyquoShadow.soft.y) } private func row(_ item: DownloadItem) -> some View { HStack(spacing: ZyquoSpacing.sm) { Image(systemName: icon(item)).foregroundStyle(color(item)) VStack(alignment: .leading, spacing: 2) { Text(item.filename).font(ZyquoFont.body(size: 12)).lineLimit(1) .foregroundStyle(theme.textPrimary) if item.state == .inProgress { ProgressView(value: item.fraction).controlSize(.small).tint(theme.accent) } else { Text(item.state == .finished ? "Completed" : "Failed") .font(ZyquoFont.caption).foregroundStyle(theme.textTertiary) } } Spacer() if item.state == .finished { Button { downloads.open(item) } label: { Image(systemName: "arrow.up.forward.app") } .buttonStyle(.plain).foregroundStyle(theme.textSecondary) Button { downloads.reveal(item) } label: { Image(systemName: "magnifyingglass") } .buttonStyle(.plain).foregroundStyle(theme.textSecondary) } } .padding(ZyquoSpacing.sm) } private func icon(_ i: DownloadItem) -> String { switch i.state { case .inProgress: return "arrow.down.circle" case .finished: return "checkmark.circle.fill"; case .failed: return "exclamationmark.circle.fill" } } private func color(_ i: DownloadItem) -> Color { switch i.state { case .inProgress: return theme.accent case .finished: return theme.success; case .failed: return theme.danger } } }