diff --git a/wake/View/Components/Upload/MediaPicker.swift b/wake/View/Components/Upload/MediaPicker.swift index b3dd945..a6a0a74 100644 --- a/wake/View/Components/Upload/MediaPicker.swift +++ b/wake/View/Components/Upload/MediaPicker.swift @@ -4,11 +4,11 @@ import AVFoundation import os.log /// 媒体类型 -enum MediaType: Equatable { +public enum MediaType: Equatable { case image(UIImage) case video(URL, UIImage?) // URL 是视频地址,UIImage 是视频缩略图 - var thumbnail: UIImage? { + public var thumbnail: UIImage? { switch self { case .image(let image): return image @@ -17,14 +17,14 @@ enum MediaType: Equatable { } } - var isVideo: Bool { + public var isVideo: Bool { if case .video = self { return true } return false } - static func == (lhs: MediaType, rhs: MediaType) -> Bool { + public static func == (lhs: MediaType, rhs: MediaType) -> Bool { switch (lhs, rhs) { case (.image(let lhsImage), .image(let rhsImage)): return lhsImage.pngData() == rhsImage.pngData() diff --git a/wake/View/Examples/MediaDemo.swift b/wake/View/Examples/MediaDemo.swift new file mode 100644 index 0000000..25f8239 --- /dev/null +++ b/wake/View/Examples/MediaDemo.swift @@ -0,0 +1,184 @@ +import SwiftUI + +struct MediaUploadDemo: View { + @StateObject private var uploadManager = MediaUploadManager() + @State private var showMediaPicker = false + @State private var showUploadAlert = false + @State private var isUploading = false + + var body: some View { + NavigationView { + VStack(spacing: 20) { + // 上传按钮 + Button(action: { + showMediaPicker = true + }) { + Label("添加图片或视频", systemImage: "plus.circle.fill") + .font(.headline) + .frame(maxWidth: .infinity) + .padding() + .background(Color.blue) + .foregroundColor(.white) + .cornerRadius(10) + } + .padding(.horizontal) + .sheet(isPresented: $showMediaPicker) { + MediaPickerWithLogging( + selectedMedia: $uploadManager.selectedMedia, + selectionLimit: 10, + onDismiss: { + showMediaPicker = false + // 当媒体选择器关闭时,如果有选中的媒体,开始上传 + if !uploadManager.selectedMedia.isEmpty { + isUploading = true + uploadManager.startUpload() + } + } + ) + } + + // 预览区域 + if uploadManager.selectedMedia.isEmpty { + VStack(spacing: 16) { + Image(systemName: "photo.on.rectangle.angled") + .font(.system(size: 60)) + .foregroundColor(.gray) + Text("暂无媒体文件") + .font(.headline) + .foregroundColor(.gray) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + } else { + ScrollView { + LazyVGrid(columns: [GridItem(.adaptive(minimum: 120), spacing: 10)], spacing: 10) { + ForEach(0.. Double? in + if case .uploading(let progress) = status { return progress } + return nil + }).first { + ProgressView(value: progress, total: 1.0) + .padding(.horizontal) + Text("上传中 \(Int(progress * 100))%") + .font(.subheadline) + .foregroundColor(.gray) + } else { + ProgressView() + .progressViewStyle(CircularProgressViewStyle()) + .scaleEffect(1.5) + Text("正在准备上传...") + .font(.subheadline) + .foregroundColor(.gray) + .padding(.top, 8) + } + } + .frame(maxWidth: .infinity) + .padding() + } + } + .alert(isPresented: $showUploadAlert) { + Alert( + title: Text(uploadManager.isAllUploaded ? "上传完成" : "上传状态"), + message: Text(uploadManager.isAllUploaded ? + "所有文件上传完成!" : + "正在处理上传..."), + dismissButton: .default(Text("确定")) + ) + } + .onChange(of: uploadManager.uploadStatus) { _ in + // 检查是否所有上传都已完成或失败 + let allFinished = uploadManager.uploadStatus.values.allSatisfy { status in + if case .completed = status { return true } + if case .failed = status { return true } + return false + } + + if allFinished && !uploadManager.uploadStatus.isEmpty { + isUploading = false + showUploadAlert = true + } + } + } + } +} + +// 媒体项视图 +struct MediaItemView: View { + let media: MediaType + let status: MediaUploadStatus + + var body: some View { + ZStack(alignment: .bottom) { + // 缩略图 + if let thumbnail = media.thumbnail { + Image(uiImage: thumbnail) + .resizable() + .scaledToFill() + .frame(width: 120, height: 120) + .cornerRadius(8) + .clipped() + + // 视频标识 + if media.isVideo { + Image(systemName: "play.circle.fill") + .font(.system(size: 30)) + .foregroundColor(.white) + .shadow(radius: 5) + } + + // 上传状态 + VStack { + Spacer() + if case .uploading(let progress) = status { + ProgressView(value: progress, total: 1.0) + .progressViewStyle(LinearProgressViewStyle()) + .frame(height: 4) + .padding(.horizontal, 4) + } else if case .completed = status { + Image(systemName: "checkmark.circle.fill") + .foregroundColor(.green) + .padding(4) + .background(Circle().fill(Color.white)) + } else if case .failed = status { + Image(systemName: "exclamationmark.circle.fill") + .foregroundColor(.red) + .padding(4) + .background(Circle().fill(Color.white)) + } + } + .padding(4) + } + } + .frame(width: 120, height: 120) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(Color.gray.opacity(0.3), lineWidth: 1) + ) + } +} + +// 预览 +#Preview { + MediaUploadDemo() + .environmentObject(AuthState.shared) +} \ No newline at end of file diff --git a/wake/View/Examples/MediaUpload.swift b/wake/View/Examples/MediaUpload.swift index 4690c40..2585f68 100644 --- a/wake/View/Examples/MediaUpload.swift +++ b/wake/View/Examples/MediaUpload.swift @@ -1,54 +1,161 @@ import SwiftUI import os.log -/// 媒体上传示例视图 -struct ExampleView: View { - /// 是否显示媒体选择器 - @State private var showMediaPicker = false - /// 已选媒体文件 - @State private var selectedMedia: [MediaType] = [] - /// 上传状态 - @State private var uploadStatus: [String: UploadStatus] = [:] - /// 上传管理器 - private let uploader = ImageUploadService() +/// 媒体上传状态 +public enum MediaUploadStatus: Equatable { + case pending + case uploading(progress: Double) + case completed(fileId: String) + case failed(Error) - /// 上传状态 - private enum UploadStatus: Equatable { - case pending - case uploading(progress: Double) - case completed(fileId: String) - case failed(Error) - - static func == (lhs: UploadStatus, rhs: UploadStatus) -> Bool { - switch (lhs, rhs) { - case (.pending, .pending): - return true - case (.uploading(let lhsProgress), .uploading(let rhsProgress)): - return lhsProgress == rhsProgress - case (.completed(let lhsId), .completed(let rhsId)): - return lhsId == rhsId - default: - return false - } - } - - var description: String { - switch self { - case .pending: return "等待上传" - case .uploading(let progress): return "上传中 \(Int(progress * 100))%" - case .completed(let fileId): return "上传完成 (ID: \(fileId.prefix(8))...)" - case .failed(let error): return "上传失败: \(error.localizedDescription)" - } + public static func == (lhs: MediaUploadStatus, rhs: MediaUploadStatus) -> Bool { + switch (lhs, rhs) { + case (.pending, .pending): + return true + case (.uploading(let lhsProgress), .uploading(let rhsProgress)): + return lhsProgress == rhsProgress + case (.completed(let lhsId), .completed(let rhsId)): + return lhsId == rhsId + case (.failed, .failed): + return false // Errors don't need to be equatable + default: + return false } } + public var description: String { + switch self { + case .pending: return "等待上传" + case .uploading(let progress): return "上传中 \(Int(progress * 100))%" + case .completed(let fileId): return "上传完成 (ID: \(fileId.prefix(8))...)" + case .failed(let error): return "上传失败: \(error.localizedDescription)" + } + } +} + +/// 媒体上传管理器 +public class MediaUploadManager: ObservableObject { + /// 已选媒体文件 + @Published public var selectedMedia: [MediaType] = [] + /// 上传状态 + @Published public var uploadStatus: [String: MediaUploadStatus] = [:] + + private let uploader = ImageUploadService() + + public init() {} + + /// 添加上传媒体 + public func addMedia(_ media: [MediaType]) { + selectedMedia.append(contentsOf: media) + } + + /// 移除指定索引的媒体 + public func removeMedia(at index: Int) { + guard index < selectedMedia.count else { return } + selectedMedia.remove(at: index) + // 更新状态字典 + var newStatus: [String: MediaUploadStatus] = [:] + uploadStatus.forEach { key, value in + if let keyInt = Int(key), keyInt < index { + newStatus[key] = value + } else if let keyInt = Int(key), keyInt > index { + newStatus["\(keyInt - 1)"] = value + } + } + uploadStatus = newStatus + } + + /// 清空所有媒体 + public func clearAllMedia() { + selectedMedia.removeAll() + uploadStatus.removeAll() + } + + /// 开始上传所有选中的媒体 + public func startUpload() { + print("🔄 开始批量上传 \(selectedMedia.count) 个文件") + // 重置上传状态 + uploadStatus.removeAll() + + for (index, media) in selectedMedia.enumerated() { + let id = "\(index)" + uploadStatus[id] = .pending + + // Convert MediaType to ImageUploadService.MediaType + let uploadMediaType: ImageUploadService.MediaType + switch media { + case .image(let image): + uploadMediaType = .image(image) + case .video(let url, let thumbnail): + uploadMediaType = .video(url, thumbnail) + } + uploadMedia(uploadMediaType, id: id) + } + } + + /// 获取上传结果 + public func getUploadResults() -> [String: String] { + var results: [String: String] = [:] + for (id, status) in uploadStatus { + if case .completed(let fileId) = status { + results[id] = fileId + } + } + return results + } + + /// 检查是否所有上传都已完成 + public var isAllUploaded: Bool { + guard !selectedMedia.isEmpty else { return false } + return uploadStatus.allSatisfy { _, status in + if case .completed = status { return true } + return false + } + } + + // MARK: - Private Methods + + private func uploadMedia(_ media: ImageUploadService.MediaType, id: String) { + print("🔄 开始处理媒体: \(id)") + uploadStatus[id] = .uploading(progress: 0) + + uploader.uploadMedia( + media, + progress: { progress in + print("📊 上传进度 (\(id)): \(progress.current)%") + DispatchQueue.main.async { + self.uploadStatus[id] = .uploading(progress: progress.progress) + } + }, + completion: { [weak self] result in + guard let self = self else { return } + DispatchQueue.main.async { + switch result { + case .success(let uploadResult): + print("✅ 上传成功 (\(id)): \(uploadResult.fileId)") + self.uploadStatus[id] = .completed(fileId: uploadResult.fileId) + case .failure(let error): + print("❌ 上传失败 (\(id)): \(error.localizedDescription)") + self.uploadStatus[id] = .failed(error) + } + } + } + ) + } +} + +// MARK: - Preview Helper + +/// 示例视图,展示如何使用 MediaUploadManager +struct MediaUploadExample: View { + @StateObject private var uploadManager = MediaUploadManager() + @State private var showMediaPicker = false + var body: some View { NavigationView { VStack(spacing: 20) { // 选择媒体按钮 - Button(action: { - showMediaPicker = true - }) { + Button(action: { showMediaPicker = true }) { Label("选择媒体", systemImage: "photo.on.rectangle") .font(.headline) .frame(maxWidth: .infinity) @@ -59,121 +166,95 @@ struct ExampleView: View { } .padding(.horizontal) - // 显示已选媒体数量 - if !selectedMedia.isEmpty { - VStack(spacing: 10) { - Text("已选择 \(selectedMedia.count) 个媒体文件") - .font(.headline) - - // 显示媒体缩略图和上传状态 - List { - ForEach(0.. Color { + private func statusColor(_ status: MediaUploadStatus) -> Color { switch status { case .pending: return .secondary case .uploading: return .blue @@ -212,6 +293,6 @@ private struct MediaThumbnailView: View { } #Preview { - ExampleView() + MediaUploadExample() .environmentObject(AuthState.shared) } \ No newline at end of file diff --git a/wake/WakeApp.swift b/wake/WakeApp.swift index 98fd3b9..3c17ecd 100644 --- a/wake/WakeApp.swift +++ b/wake/WakeApp.swift @@ -46,7 +46,7 @@ struct WakeApp: App { // 已登录:显示userInfo页面 // UserInfo() // .environmentObject(authState) - ExampleView() + MediaUploadDemo() .environmentObject(authState) } else { // 未登录:显示登录界面