Files
legado-ios/App/Features/Reader/EPUBIllustrationsView.swift
T
Aminark 67bc212ffd feat: 支持 EPUB 阅读并改善规则兼容与阅读稳定性
支持 EPUB 2/3 导入、目录、封面和章节插图,保留本地书管理与阅读进度。
修复 JSON 模板、动态替换与请求参数兼容,以及 RSS 翻页和连续滚动定位。
更新编辑器工具栏和回归测试,发布版本为 0.1.12(build 18)。
2026-10-24 00:58:11 +08:00

75 lines
3.1 KiB
Swift

import SwiftUI
import ImageIO
struct EPUBIllustrationsView: View {
@Environment(\.dismiss) private var dismiss
let book: Book
let chapter: BookChapter
@State private var images: [EPUBIllustration] = []
@State private var isLoading = true
@State private var errorMessage: String?
var body: some View {
ReadingNavigationStack {
ScrollView {
LazyVStack(spacing: 24) {
if isLoading {
ProgressView("正在读取插图…").padding()
} else if let errorMessage {
ReadingUnavailableView("无法读取插图", systemImage: "photo", description: Text(errorMessage))
} else if images.isEmpty {
ReadingUnavailableView("本章没有插图", systemImage: "photo", description: Text(chapter.title))
} else {
ForEach(images) { image in
VStack(spacing: 8) {
EPUBIllustrationImage(data: image.data)
Text("插图 \(image.id)\(image.caption.isEmpty ? "" : " · " + image.caption)")
.font(.caption).foregroundStyle(.secondary)
}
}
}
}.padding()
}
.background(ReadingTheme.canvas)
.navigationTitle("本章插图")
.navigationBarTitleDisplayMode(.inline)
.toolbar { ToolbarItem(placement: .confirmationAction) { Button("完成") { dismiss() } } }
.task(id: chapter.id) {
isLoading = true
errorMessage = nil
do {
images = try await Task.detached { try LocalBookService().loadIllustrations(book: book, chapter: chapter) }.value
} catch { errorMessage = error.localizedDescription }
isLoading = false
}
}
}
}
private struct EPUBIllustrationImage: View {
let data: Data
@State private var image: UIImage?
var body: some View {
Group {
if let image {
Image(uiImage: image).resizable().scaledToFit().accessibilityLabel("书籍插图")
} else {
Text("图片无法显示").foregroundStyle(.secondary)
}
}
.task {
// Decode a bounded thumbnail off the main thread, even for huge source dimensions.
image = await Task.detached {
guard let source = CGImageSourceCreateWithData(data as CFData, nil),
let thumbnail = CGImageSourceCreateThumbnailAtIndex(source, 0, [
kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceThumbnailMaxPixelSize: 2_048,
kCGImageSourceCreateThumbnailWithTransform: true
] as CFDictionary) else { return Optional<UIImage>.none }
return UIImage(cgImage: thumbnail)
}.value
}
}
}