✨ 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>
315 lines
9.6 KiB
Swift
315 lines
9.6 KiB
Swift
import SwiftUI
|
|
|
|
// MARK: - Ejemplo de Integración del Sistema de Descargas
|
|
|
|
// Este archivo muestra cómo integrar el sistema de descargas en tu app
|
|
|
|
/// Ejemplo 1: Agregar DownloadsView a un TabView
|
|
struct MainTabViewExample: View {
|
|
@State private var selectedTab = 0
|
|
|
|
var body: some View {
|
|
TabView(selection: $selectedTab) {
|
|
// Home/Library
|
|
ContentView()
|
|
.tabItem {
|
|
Label("Biblioteca", systemImage: "books.vertical")
|
|
}
|
|
.tag(0)
|
|
|
|
// Downloads
|
|
DownloadsView()
|
|
.tabItem {
|
|
Label("Descargas", systemImage: "arrow.down.circle")
|
|
}
|
|
.tag(1)
|
|
|
|
// Settings
|
|
SettingsView()
|
|
.tabItem {
|
|
Label("Ajustes", systemImage: "gear")
|
|
}
|
|
.tag(2)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Ejemplo 2: Navegación desde MangaDetailView
|
|
struct MangaDetailViewWithNavigation: View {
|
|
let manga: Manga
|
|
|
|
var body: some View {
|
|
MangaDetailView(manga: manga)
|
|
.toolbar {
|
|
ToolbarItem(placement: .navigationBarTrailing) {
|
|
Menu {
|
|
Button {
|
|
// Navegar a descargas
|
|
} label: {
|
|
Label("Ver Descargas", systemImage: "arrow.down.circle")
|
|
}
|
|
|
|
Button {
|
|
// Descargar último capítulo
|
|
} label: {
|
|
Label("Descargar último", systemImage: "arrow.down.doc")
|
|
}
|
|
} label: {
|
|
Image(systemName: "ellipsis.circle")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Ejemplo 3: Badge en TabView para mostrar descargas activas
|
|
struct MainTabViewWithBadge: View {
|
|
@StateObject private var downloadManager = DownloadManager.shared
|
|
@State private var selectedTab = 0
|
|
|
|
var body: some View {
|
|
TabView(selection: $selectedTab) {
|
|
ContentView()
|
|
.tabItem {
|
|
Label("Biblioteca", systemImage: "books.vertical")
|
|
}
|
|
.tag(0)
|
|
|
|
DownloadsView()
|
|
.tabItem {
|
|
Label("Descargas", systemImage: "arrow.down.circle")
|
|
}
|
|
.badge(downloadManager.activeDownloads.count)
|
|
.tag(1)
|
|
|
|
SettingsView()
|
|
.tabItem {
|
|
Label("Ajustes", systemImage: "gear")
|
|
}
|
|
.tag(2)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Ejemplo 4: Sheet para mostrar descargas desde cualquier vista
|
|
struct DownloadsSheetExample: View {
|
|
@State private var showingDownloads = false
|
|
@StateObject private var downloadManager = DownloadManager.shared
|
|
|
|
var body: some View {
|
|
VStack {
|
|
Text("Contenido Principal")
|
|
|
|
Button("Ver Descargas") {
|
|
showingDownloads = true
|
|
}
|
|
.disabled(downloadManager.activeDownloads.isEmpty)
|
|
}
|
|
.sheet(isPresented: $showingDownloads) {
|
|
NavigationView {
|
|
DownloadsView()
|
|
.toolbar {
|
|
ToolbarItem(placement: .navigationBarTrailing) {
|
|
Button("Cerrar") {
|
|
showingDownloads = false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Ejemplo 5: Vista de configuración con opciones de descarga
|
|
struct SettingsView: View {
|
|
@StateObject private var storage = StorageService.shared
|
|
@StateObject private var downloadManager = DownloadManager.shared
|
|
@State private var showingClearAlert = false
|
|
|
|
var body: some View {
|
|
Form {
|
|
Section("Descargas") {
|
|
HStack {
|
|
Text("Almacenamiento usado")
|
|
Spacer()
|
|
Text(storage.formatFileSize(storage.getStorageSize()))
|
|
.foregroundColor(.secondary)
|
|
}
|
|
|
|
Button(role: .destructive) {
|
|
showingClearAlert = true
|
|
} label: {
|
|
Label("Limpiar todas las descargas", systemImage: "trash")
|
|
}
|
|
.disabled(storage.getStorageSize() == 0)
|
|
}
|
|
|
|
Section("Estadísticas") {
|
|
HStack {
|
|
Text("Descargas activas")
|
|
Spacer()
|
|
Text("\(downloadManager.activeDownloads.count)")
|
|
.foregroundColor(.secondary)
|
|
}
|
|
|
|
HStack {
|
|
Text("Completadas")
|
|
Spacer()
|
|
Text("\(downloadManager.completedDownloads.count)")
|
|
.foregroundColor(.secondary)
|
|
}
|
|
|
|
HStack {
|
|
Text("Fallidas")
|
|
Spacer()
|
|
Text("\(downloadManager.failedDownloads.count)")
|
|
.foregroundColor(.secondary)
|
|
}
|
|
}
|
|
|
|
Section("Preferencias") {
|
|
Toggle("Descargar solo en Wi-Fi", isOn: .constant(true))
|
|
Toggle("Notificar descargas completadas", isOn: .constant(true))
|
|
}
|
|
}
|
|
.navigationTitle("Ajustes")
|
|
.alert("Limpiar descargas", isPresented: $showingClearAlert) {
|
|
Button("Cancelar", role: .cancel) { }
|
|
Button("Limpiar", role: .destructive) {
|
|
storage.clearAllDownloads()
|
|
downloadManager.clearCompletedHistory()
|
|
downloadManager.clearFailedHistory()
|
|
}
|
|
} message: {
|
|
Text("Esta acción eliminará todos los capítulos descargados. ¿Estás seguro?")
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Ejemplo 6: Widget de descargas activas en home
|
|
struct ActiveDownloadsWidget: View {
|
|
@ObservedObject var downloadManager = DownloadManager.shared
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
Text("Descargas Activas")
|
|
.font(.headline)
|
|
|
|
if downloadManager.activeDownloads.isEmpty {
|
|
Text("No hay descargas activas")
|
|
.font(.caption)
|
|
.foregroundColor(.secondary)
|
|
} else {
|
|
ForEach(downloadManager.activeDownloads.prefix(3)) { task in
|
|
HStack {
|
|
VStack(alignment: .leading) {
|
|
Text(task.mangaTitle)
|
|
.font(.subheadline)
|
|
.lineLimit(1)
|
|
|
|
Text("Cap. \(task.chapterNumber)")
|
|
.font(.caption)
|
|
.foregroundColor(.secondary)
|
|
}
|
|
|
|
Spacer()
|
|
|
|
ProgressView(value: task.progress)
|
|
.frame(width: 50)
|
|
}
|
|
}
|
|
|
|
if downloadManager.activeDownloads.count > 3 {
|
|
Text("+\(downloadManager.activeDownloads.count - 3) más")
|
|
.font(.caption)
|
|
.foregroundColor(.secondary)
|
|
}
|
|
|
|
Button("Ver todas") {
|
|
// Navegar a DownloadsView
|
|
}
|
|
.buttonStyle(.bordered)
|
|
}
|
|
}
|
|
.padding()
|
|
.background(
|
|
RoundedRectangle(cornerRadius: 12)
|
|
.fill(Color(.systemGray6))
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Ejemplo 7: Modificador para mostrar banner de descargas activas
|
|
struct ActiveDownloadsBannerModifier: ViewModifier {
|
|
@ObservedObject var downloadManager = DownloadManager.shared
|
|
@State private var isVisible = false
|
|
|
|
func body(content: Content) -> some View {
|
|
ZStack(alignment: .top) {
|
|
content
|
|
|
|
if downloadManager.hasActiveDownloads && isVisible {
|
|
HStack {
|
|
Image(systemName: "arrow.down.circle.fill")
|
|
.foregroundColor(.blue)
|
|
|
|
Text("\(downloadManager.activeDownloads.count) descarga(s) en progreso")
|
|
.font(.caption)
|
|
|
|
Spacer()
|
|
|
|
Button("Ver") {
|
|
// Navegar a DownloadsView
|
|
}
|
|
.font(.caption)
|
|
}
|
|
.padding()
|
|
.background(
|
|
RoundedRectangle(cornerRadius: 10)
|
|
.fill(.blue)
|
|
.shadow(color: .black.opacity(0.2), radius: 5, x: 0, y: 2)
|
|
)
|
|
.foregroundColor(.white)
|
|
.padding(.horizontal)
|
|
.padding(.top, 50)
|
|
.transition(.move(edge: .top).combined(with: .opacity))
|
|
}
|
|
}
|
|
.onAppear {
|
|
if downloadManager.hasActiveDownloads {
|
|
withAnimation(.spring()) {
|
|
isVisible = true
|
|
}
|
|
}
|
|
}
|
|
.onChange(of: downloadManager.hasActiveDownloads) { hasActive in
|
|
withAnimation(.spring()) {
|
|
isVisible = hasActive
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
extension View {
|
|
func activeDownloadsBanner() -> some View {
|
|
modifier(ActiveDownloadsBannerModifier())
|
|
}
|
|
}
|
|
|
|
// MARK: - Preview
|
|
|
|
#Preview {
|
|
MainTabViewWithBadge()
|
|
}
|
|
|
|
#Preview("Downloads Widget") {
|
|
ActiveDownloadsWidget()
|
|
.padding()
|
|
}
|
|
|
|
#Preview("Settings") {
|
|
NavigationView {
|
|
SettingsView()
|
|
}
|
|
}
|