feat: uploadmore

This commit is contained in:
jinyaqiu 2025-08-26 10:27:03 +08:00
parent 9a432f65ac
commit 6229ddcd6c
3 changed files with 113 additions and 37 deletions

View File

@ -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)"
}
}
}
///

View File

@ -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]
)
}
}
///

View File

@ -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: -