feat: uploadmore
This commit is contained in:
parent
9a432f65ac
commit
6229ddcd6c
@ -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)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 媒体上传结果
|
||||
|
||||
@ -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]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// 移除指定索引的媒体
|
||||
|
||||
@ -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<Int>
|
||||
|
||||
// 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: - 预览
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user