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>
This commit is contained in:
314
ios-app/Sources/Examples/IntegrationExample.swift
Normal file
314
ios-app/Sources/Examples/IntegrationExample.swift
Normal file
@@ -0,0 +1,314 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user