Files
MangaReader/ios-app/Sources/Extensions/DownloadExtensions.swift
renato97 b474182dd9 Initial commit: MangaReader iOS App
 Features:
- App iOS completa para leer manga sin publicidad
- Scraper con WKWebView para manhwaweb.com
- Sistema de descargas offline
- Lector con zoom y navegación
- Favoritos y progreso de lectura
- Compatible con iOS 15+ y Sideloadly/3uTools

📦 Contenido:
- Backend Node.js con Puppeteer (opcional)
- App iOS con SwiftUI
- Scraper de capítulos e imágenes
- Sistema de almacenamiento local
- Testing completo
- Documentación exhaustiva

🧪 Prueba: Capítulo 789 de One Piece descargado exitosamente
  - 21 páginas descargadas
  - 4.68 MB total
  - URLs verificadas y funcionales

🎉 Generated with Claude Code (https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-04 15:34:18 +01:00

155 lines
4.7 KiB
Swift

import Foundation
import UIKit
// MARK: - Download Extensions
extension DownloadTask {
/// Formatea el tamaño total de la descarga
var formattedSize: String {
let formatter = ByteCountFormatter()
formatter.allowedUnits = [.useBytes, .useKB, .useMB]
formatter.countStyle = .file
return formatter.string(fromByteCount: Int64(imageURLs.count * 500_000)) // Estimación de 500KB por imagen
}
/// Retorna el tiempo estimado restante
var estimatedTimeRemaining: String? {
guard progress > 0 && progress < 1 else { return nil }
let downloadedPages = Double(imageURLs.count) * progress
let remainingPages = Double(imageURLs.count) - downloadedPages
// Estimación: 2 segundos por página
let estimatedSeconds = remainingPages * 2
if estimatedSeconds < 60 {
return "\(Int(estimatedSeconds))s restantes"
} else {
let minutes = Int(estimatedSeconds / 60)
return "\(min)m restantes"
}
}
}
extension DownloadManager {
/// Obtiene estadísticas de descarga
var downloadStats: DownloadStats {
let activeCount = activeDownloads.count
let completedCount = completedDownloads.count
let failedCount = failedDownloads.count
return DownloadStats(
activeDownloads: activeCount,
completedDownloads: completedCount,
failedDownloads: failedCount,
totalProgress: totalProgress
)
}
/// Verifica si hay descargas activas
var hasActiveDownloads: Bool {
!activeDownloads.isEmpty
}
/// Obtiene el número total de descargas
var totalDownloads: Int {
activeDownloads.count + completedDownloads.count + failedDownloads.count
}
}
// MARK: - Download Stats Model
struct DownloadStats {
let activeDownloads: Int
let completedDownloads: Int
let failedDownloads: Int
let totalProgress: Double
var totalDownloads: Int {
activeDownloads + completedDownloads + failedDownloads
}
var successRate: Double {
guard totalDownloads > 0 else { return 0 }
return Double(completedDownloads) / Double(totalDownloads)
}
}
// MARK: - UIImage Extension for Compression
extension UIImage {
/// Comprime la imagen con una calidad específica
func compressedData(quality: CGFloat = 0.8) -> Data? {
return jpegData(compressionQuality: quality)
}
/// Redimensiona la imagen a un tamaño máximo
func resized(maxWidth: CGFloat, maxHeight: CGFloat) -> UIImage? {
let size = size
let widthRatio = maxWidth / size.width
let heightRatio = maxHeight / size.height
let ratio = min(widthRatio, heightRatio)
let newSize = CGSize(
width: size.width * ratio,
height: size.height * ratio
)
UIGraphicsBeginImageContextWithOptions(newSize, false, 1.0)
draw(in: CGRect(origin: .zero, size: newSize))
let resizedImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return resizedImage
}
/// Optimiza la imagen para almacenamiento
func optimizedForStorage() -> Data? {
// Redimensionar si es muy grande
let maxDimension: CGFloat = 2048
let resized: UIImage
if size.width > maxDimension || size.height > maxDimension {
resized = self.resized(maxWidth: maxDimension, maxHeight: maxDimension) ?? self
} else {
resized = self
}
// Comprimir con calidad balanceada
return resized.compressedData(quality: 0.75)
}
}
// MARK: - Notification Names
extension Notification.Name {
static let downloadDidStart = Notification.Name("downloadDidStart")
static let downloadDidUpdate = Notification.Name("downloadDidUpdate")
static let downloadDidComplete = Notification.Name("downloadDidComplete")
static let downloadDidFail = Notification.Name("downloadDidFail")
static let downloadDidCancel = Notification.Name("downloadDidCancel")
}
// MARK: - Download Progress Notification
struct DownloadProgressNotification {
let taskId: String
let progress: Double
let downloadedPages: Int
let totalPages: Int
}
// MARK: - URLSession Extension for Download Tracking
extension URLSession {
/// Configura una URLSession para descargas con timeout
static func downloadSession() -> URLSession {
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = 30
configuration.timeoutIntervalForResource = 300
configuration.requestCachePolicy = .reloadIgnoringLocalCacheData
return URLSession(configuration: configuration)
}
}