diff --git a/wake/View/Components/Upload/ImageUploadService.swift b/wake/View/Components/Upload/ImageUploadService.swift index b2f0ab0..f86432c 100644 --- a/wake/View/Components/Upload/ImageUploadService.swift +++ b/wake/View/Components/Upload/ImageUploadService.swift @@ -334,9 +334,19 @@ public class ImageUploadService { // MARK: - Supporting Types /// 媒体类型 - public enum MediaType { + public enum MediaType: Equatable { case image(UIImage) case video(URL, UIImage?) + + // 确保 id 计算属性存在 + var id: String { + switch self { + case .image(let uiImage): + return "image_\(uiImage.hashValue)" + case .video(let url, _): + return "video_\(url.absoluteString.hashValue)" + } + } } /// 媒体上传结果 diff --git a/wake/View/Components/Upload/MediaUpload.swift b/wake/View/Components/Upload/MediaUpload.swift index e6bde66..b1881ce 100644 --- a/wake/View/Components/Upload/MediaUpload.swift +++ b/wake/View/Components/Upload/MediaUpload.swift @@ -46,7 +46,27 @@ public class MediaUploadManager: ObservableObject { /// 添加上传媒体 public func addMedia(_ media: [MediaType]) { - selectedMedia.append(contentsOf: media) + print("Adding \(media.count) media items") + let newMedia = media.filter { newItem in + !selectedMedia.contains { $0.id == newItem.id } + } + print("After filtering duplicates: \(newMedia.count) new items") + + let isFirstMedia = selectedMedia.isEmpty + selectedMedia.append(contentsOf: newMedia) + + for item in newMedia { + uploadStatus[item.id] = .pending + } + + // 如果是第一次添加媒体,发送通知 + if isFirstMedia, let firstMedia = newMedia.first { + NotificationCenter.default.post( + name: .didAddFirstMedia, + object: nil, + userInfo: ["media": firstMedia] + ) + } } /// 移除指定索引的媒体 diff --git a/wake/View/Upload/MediaUploadView.swift b/wake/View/Upload/MediaUploadView.swift index 3f4a11b..f365f3c 100644 --- a/wake/View/Upload/MediaUploadView.swift +++ b/wake/View/Upload/MediaUploadView.swift @@ -1,5 +1,8 @@ import SwiftUI +extension Notification.Name { + static let didAddFirstMedia = Notification.Name("didAddFirstMedia") +} /// 主上传视图 /// 提供媒体选择、预览和上传功能 @MainActor @@ -30,8 +33,7 @@ struct MediaUploadView: View { MainUploadArea( uploadManager: uploadManager, showMediaPicker: $showMediaPicker, - selectedMedia: $selectedMedia, - selectedIndices: $selectedIndices + selectedMedia: $selectedMedia ) .padding() .id("mainUploadArea\(uploadManager.selectedMedia.count)") @@ -262,12 +264,10 @@ struct MainUploadArea: View { @Binding var showMediaPicker: Bool /// 当前选中的媒体 @Binding var selectedMedia: MediaType? - /// 当前选中的媒体索引 - @Binding var selectedIndices: Set // MARK: - 视图主体 - var body: some View { + var body: some View { VStack(spacing: 16) { // 标题 Text("Click to upload 20 images and 5 videos to generate your next blind box.") @@ -276,16 +276,43 @@ struct MainUploadArea: View { .foregroundColor(.black) .multilineTextAlignment(.center) .padding(.horizontal) - - // 上传提示视图 - UploadPromptView(showMediaPicker: $showMediaPicker) - + // 主显示区域 + if let mediaToDisplay = selectedMedia ?? uploadManager.selectedMedia.first { + MediaPreview(media: mediaToDisplay, uploadManager: uploadManager) + .id(mediaToDisplay.id) + .frame(width: 225, height: 225) + .overlay( + RoundedRectangle(cornerRadius: 16) + .stroke(Color.themePrimary, lineWidth: 5) // 使用主题色添加2点宽的实线边框 + ) + .cornerRadius(16) + .shadow(radius: 4) + .padding(.horizontal) + .transition(.opacity) + } else { + UploadPromptView(showMediaPicker: $showMediaPicker) + } // 媒体预览区域 mediaPreviewSection } + .onAppear { + print("MainUploadArea appeared") + print("Selected media count: \(uploadManager.selectedMedia.count)") + + if selectedMedia == nil, let firstMedia = uploadManager.selectedMedia.first { + print("Selecting first media: \(firstMedia.id)") + selectedMedia = firstMedia + } + } + .onReceive(NotificationCenter.default.publisher(for: .didAddFirstMedia)) { notification in + if let media = notification.userInfo?["media"] as? MediaType, selectedMedia == nil { + selectedMedia = media + } + } .background(Color.white) .cornerRadius(16) .shadow(radius: 2) + .animation(.default, value: selectedMedia?.id) // 当 selectedMedia 的 id 变化时添加动画 } // MARK: - 子视图 @@ -371,20 +398,31 @@ struct MainUploadArea: View { .padding([.top, .leading], 0), alignment: .topLeading ) + // 在 mediaItemView 函数中,更新 onTapGesture: + // .onTapGesture { + // print("Tapped media at index: \(index)") // 添加日志 + // withAnimation { + // selectedIndices = [index] + // selectedMedia = media + // } + // } + // .contentShape(Rectangle()) // 确保整个区域都可点击 + // 在 mediaItemView 中,更新 onTapGesture: .onTapGesture { - // 更新选中的媒体 - selectedIndices = [index] - selectedMedia = media + print("点击了媒体项,索引: \(index)") + withAnimation { + selectedMedia = media // 直接更新选中的媒体 + } } + .contentShape(Rectangle()) // 确保整个区域都可点击 } // 右上角关闭按钮 Button(action: { // 移除选中的媒体项 uploadManager.selectedMedia.remove(at: index) - // 更新选中状态 - if selectedIndices.contains(index) { - selectedIndices = [] + // 重置选中的媒体 + if selectedMedia == media { selectedMedia = nil } }) { @@ -594,7 +632,7 @@ struct MediaPreview: View { var body: some View { ZStack { - // 显示图片或错误状态 + // 1. 显示图片或视频缩略图 if let image = image { loadedImageView(image) @@ -602,16 +640,30 @@ struct MediaPreview: View { if case .video = media { playButton } - } else if case .failure(let error) = loadState { - // 加载失败状态 - errorView(error: error) + + // 上传进度条(仅当正在上传且进度小于100%时显示) + if isUploading && uploadProgress < 1.0 { + VStack { + Spacer() + ProgressView(value: uploadProgress, total: 1.0) + .progressViewStyle(LinearProgressViewStyle(tint: .white)) + .frame(height: 2) + .padding(.horizontal, 4) + .padding(.bottom, 2) + } + } } else { - // 初始加载时显示占位图 + // 2. 加载中的占位图 placeholderView .onAppear { loadImage() } } + + // 3. 显示错误状态(如果有) + if case .failure(let error) = loadState, image == nil { + errorView(error: error) + } } .aspectRatio(1, contentMode: .fill) .clipped() @@ -622,18 +674,12 @@ struct MediaPreview: View { ) } - // MARK: - 子视图 - - /// 加载中的占位图 + // 加载中的占位图 private var placeholderView: some View { Color.gray.opacity(0.1) - .overlay( - ProgressView() - .progressViewStyle(CircularProgressViewStyle()) - ) } - /// 加载完成的图片视图 + // 加载完成的图片视图 private func loadedImageView(_ image: UIImage) -> some View { Image(uiImage: image) .resizable() @@ -736,15 +782,15 @@ struct MediaPreview: View { /// 扩展 MediaType 以支持 Identifiable 协议 extension MediaType: Identifiable { /// 唯一标识符 - public var id: String { - switch self { - case .image(let uiImage): - return "image_\(uiImage.hashValue)" - case .video(let url, _): - return "video_\(url.absoluteString)" - } + public var id: String { + switch self { + case .image(let uiImage): + return "image_\(uiImage.hashValue)" + case .video(let url, _): + return "video_\(url.absoluteString.hashValue)" } } +} // MARK: - 预览