139 lines
4.8 KiB
Swift
139 lines
4.8 KiB
Swift
import SwiftUI
|
|
import os.log
|
|
|
|
class MediaUploader: ObservableObject {
|
|
@Published var selectedMedia: [MediaType] = []
|
|
@Published private(set) var isUploading = false
|
|
@Published private(set) var uploadProgress: [Int: Double] = [:] // 跟踪每个上传任务的进度
|
|
@Published var showError = false
|
|
@Published private(set) var errorMessage = ""
|
|
|
|
@Published var showMediaPicker = false
|
|
let maxSelection: Int
|
|
var onUploadComplete: ([(MediaType, URL)]?) -> Void
|
|
var uploadFunction: (MediaType, @escaping (Double) -> Void) async throws -> URL
|
|
|
|
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.example.app", category: "MediaUploader")
|
|
|
|
init(maxSelection: Int,
|
|
onUploadComplete: @escaping ([(MediaType, URL)]?) -> Void,
|
|
uploadFunction: @escaping (MediaType, @escaping (Double) -> Void) async throws -> URL) {
|
|
self.maxSelection = maxSelection
|
|
self.onUploadComplete = onUploadComplete
|
|
self.uploadFunction = uploadFunction
|
|
}
|
|
|
|
// 显示媒体选择器
|
|
func showPicker() {
|
|
DispatchQueue.main.async { [weak self] in
|
|
self?.showMediaPicker = true
|
|
}
|
|
}
|
|
|
|
// 公开的方法供外部调用
|
|
func startUpload() async -> [(MediaType, URL)]? {
|
|
guard !selectedMedia.isEmpty else { return nil }
|
|
|
|
isUploading = true
|
|
uploadProgress.removeAll()
|
|
|
|
do {
|
|
var uploadedResults: [(MediaType, URL)] = []
|
|
|
|
for (index, media) in selectedMedia.enumerated() {
|
|
let url = try await uploadFunction(media) { [weak self] progress in
|
|
DispatchQueue.main.async {
|
|
self?.uploadProgress[index] = progress
|
|
}
|
|
}
|
|
uploadedResults.append((media, url))
|
|
}
|
|
|
|
DispatchQueue.main.async { [weak self] in
|
|
guard let self = self else { return }
|
|
self.isUploading = false
|
|
self.onUploadComplete(uploadedResults)
|
|
self.selectedMedia.removeAll()
|
|
}
|
|
|
|
return uploadedResults
|
|
} catch {
|
|
logger.error("上传失败: \(error.localizedDescription)")
|
|
DispatchQueue.main.async { [weak self] in
|
|
guard let self = self else { return }
|
|
self.errorMessage = "上传失败: \(error.localizedDescription)"
|
|
self.showError = true
|
|
self.isUploading = false
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
struct MediaUploaderView: View {
|
|
@ObservedObject var mediaUploader: MediaUploader
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 16) {
|
|
// 显示已选媒体数量
|
|
if !mediaUploader.selectedMedia.isEmpty {
|
|
Text("已选择 \(mediaUploader.selectedMedia.count) 个文件")
|
|
.foregroundColor(.secondary)
|
|
}
|
|
}
|
|
.padding()
|
|
.background(Color(.systemBackground))
|
|
.cornerRadius(12)
|
|
.shadow(radius: 2)
|
|
.fullScreenCover(isPresented: $mediaUploader.showMediaPicker) {
|
|
MediaPicker(
|
|
selectedMedia: $mediaUploader.selectedMedia,
|
|
selectionLimit: mediaUploader.maxSelection
|
|
) {
|
|
mediaUploader.showMediaPicker = false
|
|
}
|
|
}
|
|
.alert("错误", isPresented: $mediaUploader.showError) {
|
|
Button("确定", role: .cancel) {}
|
|
} message: {
|
|
Text(mediaUploader.errorMessage)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - 预览
|
|
struct MediaUploader_Previews: PreviewProvider {
|
|
static var previews: some View {
|
|
Group {
|
|
MediaUploaderView(
|
|
mediaUploader: MediaUploader(
|
|
maxSelection: 5,
|
|
onUploadComplete: { _ in },
|
|
uploadFunction: { _, _ in
|
|
// 模拟上传
|
|
try await Task.sleep(nanoseconds: 1_000_000_000)
|
|
return URL(string: "https://example.com/uploaded")!
|
|
}
|
|
)
|
|
)
|
|
.padding()
|
|
.previewLayout(.sizeThatFits)
|
|
|
|
MediaUploaderView(
|
|
mediaUploader: MediaUploader(
|
|
maxSelection: 5,
|
|
onUploadComplete: { _ in },
|
|
uploadFunction: { _, _ in
|
|
// 模拟上传
|
|
try await Task.sleep(nanoseconds: 1_000_000_000)
|
|
return URL(string: "https://example.com/uploaded")!
|
|
}
|
|
)
|
|
)
|
|
.padding()
|
|
.previewLayout(.sizeThatFits)
|
|
.preferredColorScheme(.dark)
|
|
}
|
|
}
|
|
}
|