import SwiftUI struct DownloadsView: View { @StateObject private var viewModel = DownloadsViewModel() @State private var selectedTab: DownloadsViewModel.DownloadTab = .active @State private var showingClearAlert = false var body: some View { VStack(spacing: 0) { // Tab selector Picker("Tipo de descarga", selection: $selectedTab) { ForEach(DownloadsViewModel.DownloadTab.allCases, id: \.self) { tab in Label(tab.rawValue, systemImage: tab.icon) .tag(tab) } } .pickerStyle(.segmented) .padding() // Content ScrollView { VStack(spacing: 16) { switch selectedTab { case .active: activeDownloadsView case .completed: completedDownloadsView case .failed: failedDownloadsView } } .padding() } // Storage info footer storageInfoFooter } .navigationTitle("Descargas") .alert("Limpiar almacenamiento", isPresented: $showingClearAlert) { Button("Cancelar", role: .cancel) { } Button("Limpiar", role: .destructive) { viewModel.clearAllStorage() } } message: { Text("Esta acción eliminará todos los capítulos descargados. ¿Estás seguro?") } } private var activeDownloadsView: some View { VStack(spacing: 12) { if viewModel.downloadManager.activeDownloads.isEmpty { emptyStateView( icon: "arrow.down.circle", title: "No hay descargas activas", message: "Las descargas aparecerán aquí cuando comiences a descargar capítulos" ) } else { ForEach(viewModel.downloadManager.activeDownloads) { task in ActiveDownloadCard(task: task) } // Cancel all button if !viewModel.downloadManager.activeDownloads.isEmpty { Button(action: { viewModel.downloadManager.cancelAllDownloads() }) { Label("Cancelar todas", systemImage: "xmark.circle") .foregroundColor(.red) .frame(maxWidth: .infinity) .padding() .background( RoundedRectangle(cornerRadius: 10) .fill(Color.red.opacity(0.1)) ) } } } } } private var completedDownloadsView: some View { VStack(spacing: 12) { if viewModel.downloadManager.completedDownloads.isEmpty { emptyStateView( icon: "checkmark.circle", title: "No hay descargas completadas", message: "Los capítulos descargados aparecerán aquí" ) } else { ForEach(viewModel.downloadManager.completedDownloads.reversed()) { task in CompletedDownloadCard(task: task) } // Clear history button Button(action: { viewModel.downloadManager.clearCompletedHistory() }) { Text("Limpiar historial") .foregroundColor(.blue) .padding() } } } } private var failedDownloadsView: some View { VStack(spacing: 12) { if viewModel.downloadManager.failedDownloads.isEmpty { emptyStateView( icon: "exclamationmark.triangle", title: "No hay descargas fallidas", message: "Las descargas con errores aparecerán aquí" ) } else { ForEach(viewModel.downloadManager.failedDownloads.reversed()) { task in FailedDownloadCard(task: task) } // Clear history button Button(action: { viewModel.downloadManager.clearFailedHistory() }) { Text("Limpiar historial") .foregroundColor(.blue) .padding() } } } } private var storageInfoFooter: some View { VStack(spacing: 8) { Divider() HStack { VStack(alignment: .leading, spacing: 4) { Text("Almacenamiento usado") .font(.caption) .foregroundColor(.secondary) Text(viewModel.storageSizeString) .font(.headline) } Spacer() Button(action: { showingClearAlert = true }) { Text("Limpiar todo") .font(.caption) .foregroundColor(.red) .padding(.horizontal, 12) .padding(.vertical, 6) .background( RoundedRectangle(cornerRadius: 6) .fill(Color.red.opacity(0.1)) ) } } .padding() } .background(Color(.systemGray6)) } private func emptyStateView(icon: String, title: String, message: String) -> some View { VStack(spacing: 16) { Image(systemName: icon) .font(.system(size: 60)) .foregroundColor(.gray) VStack(spacing: 8) { Text(title) .font(.headline) .foregroundColor(.primary) Text(message) .font(.subheadline) .foregroundColor(.secondary) .multilineTextAlignment(.center) .padding(.horizontal, 32) } } .frame(maxWidth: .infinity, minHeight: 300) } } // MARK: - ViewModel @MainActor class DownloadsViewModel: ObservableObject { @Published var downloadManager = DownloadManager.shared @Published var storage = StorageService.shared @Published var storageSize: Int64 = 0 enum DownloadTab: String, CaseIterable { case active = "Activas" case completed = "Completadas" case failed = "Fallidas" var icon: String { switch self { case .active: return "arrow.down.circle" case .completed: return "checkmark.circle" case .failed: return "exclamationmark.triangle" } } } var storageSizeString: String { storage.formatFileSize(storage.getStorageSize()) } init() { updateStorageSize() } func clearAllStorage() { storage.clearAllDownloads() downloadManager.clearCompletedHistory() downloadManager.clearFailedHistory() updateStorageSize() } private func updateStorageSize() { storageSize = storage.getStorageSize() } } // MARK: - Download Cards struct ActiveDownloadCard: View { @ObservedObject var task: DownloadTask @StateObject private var downloadManager = DownloadManager.shared var body: some View { VStack(alignment: .leading, spacing: 12) { // Header HStack { VStack(alignment: .leading, spacing: 4) { Text(task.mangaTitle) .font(.headline) .lineLimit(1) Text("Capítulo \(task.chapterNumber)") .font(.subheadline) .foregroundColor(.secondary) } Spacer() // Cancel button Button(action: { downloadManager.cancelDownload(taskId: task.id) }) { Image(systemName: "xmark.circle.fill") .foregroundColor(.red) .font(.title3) } .buttonStyle(.plain) } // Progress bar VStack(alignment: .leading, spacing: 4) { ProgressView(value: task.progress) .progressViewStyle(.linear) .tint(.blue) HStack { Text("\(task.downloadedPages) de \(task.imageURLs.count) páginas") .font(.caption) .foregroundColor(.secondary) Spacer() Text("\(Int(task.progress * 100))%") .font(.caption) .fontWeight(.semibold) .foregroundColor(.blue) } } } .padding() .background( RoundedRectangle(cornerRadius: 12) .fill(Color(.systemBackground)) .shadow(color: .black.opacity(0.05), radius: 3, x: 0, y: 1) ) } } struct CompletedDownloadCard: View { let task: DownloadTask @StateObject private var storage = StorageService.shared var body: some View { HStack { VStack(alignment: .leading, spacing: 4) { Text(task.mangaTitle) .font(.headline) .lineLimit(1) Text("Capítulo \(task.chapterNumber)") .font(.subheadline) .foregroundColor(.secondary) Text("Completado") .font(.caption) .foregroundColor(.green) } Spacer() Image(systemName: "checkmark.circle.fill") .foregroundColor(.green) .font(.title3) } .padding() .background( RoundedRectangle(cornerRadius: 12) .fill(Color(.systemBackground)) .shadow(color: .black.opacity(0.05), radius: 3, x: 0, y: 1) ) } } struct FailedDownloadCard: View { let task: DownloadTask @StateObject private var downloadManager = DownloadManager.shared var body: some View { VStack(alignment: .leading, spacing: 12) { // Header HStack { VStack(alignment: .leading, spacing: 4) { Text(task.mangaTitle) .font(.headline) .lineLimit(1) Text("Capítulo \(task.chapterNumber)") .font(.subheadline) .foregroundColor(.secondary) } Spacer() Image(systemName: "exclamationmark.triangle.fill") .foregroundColor(.red) .font(.title3) } // Error message if let error = task.error { Text(error) .font(.caption) .foregroundColor(.red) .lineLimit(2) } // Retry button Button(action: { // TODO: Implement retry functionality print("Retry download for chapter \(task.chapterNumber)") }) { Label("Reintentar", systemImage: "arrow.clockwise") .font(.subheadline) .foregroundColor(.blue) .frame(maxWidth: .infinity) .padding() .background( RoundedRectangle(cornerRadius: 8) .fill(Color.blue.opacity(0.1)) ) } } .padding() .background( RoundedRectangle(cornerRadius: 12) .fill(Color(.systemBackground)) .shadow(color: .black.opacity(0.05), radius: 3, x: 0, y: 1) ) } } #Preview { NavigationView { DownloadsView() } }