SPB Git

spb/zyquo-atlas Public License

The AI-native macOS web browser — every surface, intelligent.

Swift 75.2% JavaScript 22% Shell 2% Makefile 0.9%
3.8 KB · 90 lines swift
Raw Blame History
1//2//  DownloadsPopover.swift3//  Zyquo Atlas4//5//  Author: Simon-Pierre Boucher6//  Mail: contact@spboucher.ai7//8//  Toolbar downloads popover: in-flight and finished downloads with progress,9//  reveal-in-Finder, and open. Backed by the window's DownloadManager.10//1112import SwiftUI1314struct DownloadsPopover: View {15    @ObservedObject var downloads: DownloadManager16    let onClose: () -> Void17    @EnvironmentObject private var themeEngine: ThemeEngine18    private var theme: AtlasTheme { themeEngine.theme }1920    var body: some View {21        VStack(alignment: .leading, spacing: 0) {22            HStack {23                Text("Downloads").font(ZyquoFont.bodyEmphasis()).foregroundStyle(theme.textPrimary)24                Spacer()25                if downloads.items.contains(where: { $0.state == .finished }) {26                    Button("Clear") { downloads.clearFinished() }27                        .font(ZyquoFont.caption).foregroundStyle(theme.accent)28                }29                Button { onClose() } label: { Image(systemName: "xmark") }30                    .buttonStyle(.plain).foregroundStyle(theme.textTertiary)31            }32            .padding(ZyquoSpacing.sm)33            Divider().overlay(theme.border)3435            if downloads.items.isEmpty {36                Text("No downloads yet")37                    .font(ZyquoFont.caption).foregroundStyle(theme.textTertiary)38                    .frame(maxWidth: .infinity).padding(ZyquoSpacing.lg)39            } else {40                ScrollView {41                    VStack(spacing: 0) {42                        ForEach(downloads.items) { item in43                            row(item)44                            Divider().overlay(theme.border)45                        }46                    }47                }48                .frame(maxHeight: 320)49            }50        }51        .frame(width: 320)52        .background(RoundedRectangle(cornerRadius: ZyquoRadius.large).fill(theme.surface))53        .overlay(RoundedRectangle(cornerRadius: ZyquoRadius.large).strokeBorder(theme.border, lineWidth: ZyquoMetrics.hairline))54        .shadow(color: ZyquoShadow.soft.color, radius: ZyquoShadow.soft.radius, y: ZyquoShadow.soft.y)55    }5657    private func row(_ item: DownloadItem) -> some View {58        HStack(spacing: ZyquoSpacing.sm) {59            Image(systemName: icon(item)).foregroundStyle(color(item))60            VStack(alignment: .leading, spacing: 2) {61                Text(item.filename).font(ZyquoFont.body(size: 12)).lineLimit(1)62                    .foregroundStyle(theme.textPrimary)63                if item.state == .inProgress {64                    ProgressView(value: item.fraction).controlSize(.small).tint(theme.accent)65                } else {66                    Text(item.state == .finished ? "Completed" : "Failed")67                        .font(ZyquoFont.caption).foregroundStyle(theme.textTertiary)68                }69            }70            Spacer()71            if item.state == .finished {72                Button { downloads.open(item) } label: { Image(systemName: "arrow.up.forward.app") }73                    .buttonStyle(.plain).foregroundStyle(theme.textSecondary)74                Button { downloads.reveal(item) } label: { Image(systemName: "magnifyingglass") }75                    .buttonStyle(.plain).foregroundStyle(theme.textSecondary)76            }77        }78        .padding(ZyquoSpacing.sm)79    }8081    private func icon(_ i: DownloadItem) -> String {82        switch i.state { case .inProgress: return "arrow.down.circle"83        case .finished: return "checkmark.circle.fill"; case .failed: return "exclamationmark.circle.fill" }84    }85    private func color(_ i: DownloadItem) -> Color {86        switch i.state { case .inProgress: return theme.accent87        case .finished: return theme.success; case .failed: return theme.danger }88    }89}90