feat: 多选先关闭弹窗再上传

This commit is contained in:
jinyaqiu 2025-08-25 14:46:10 +08:00
parent daf8476dd4
commit 2a9ef552fa
2 changed files with 136 additions and 54 deletions

View File

@ -57,6 +57,7 @@ struct MediaPicker: UIViewControllerRepresentable {
let onDismiss: (() -> Void)?
let allowedMediaTypes: MediaTypeFilter
let selectionMode: SelectionMode
let onUploadProgress: ((Int, Double) -> Void)?
///
enum SelectionMode {
@ -72,19 +73,22 @@ struct MediaPicker: UIViewControllerRepresentable {
}
init(selectedMedia: Binding<[MediaType]>,
imageSelectionLimit: Int = 10,
videoSelectionLimit: Int = 10,
allowedMediaTypes: MediaTypeFilter = .all,
selectionMode: SelectionMode = .multiple,
onDismiss: (() -> Void)? = nil) {
imageSelectionLimit: Int = 10,
videoSelectionLimit: Int = 10,
allowedMediaTypes: MediaTypeFilter = .all,
selectionMode: SelectionMode = .multiple,
onDismiss: (() -> Void)? = nil,
onUploadProgress: ((Int, Double) -> Void)? = nil) {
self._selectedMedia = selectedMedia
self.imageSelectionLimit = imageSelectionLimit
self.videoSelectionLimit = videoSelectionLimit
self.allowedMediaTypes = allowedMediaTypes
self.selectionMode = selectionMode
self.onDismiss = onDismiss
self.onUploadProgress = onUploadProgress
}
func makeUIViewController(context: Context) -> PHPickerViewController {
var configuration = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared())
configuration.filter = allowedMediaTypes.pickerFilter
@ -182,17 +186,26 @@ struct MediaPicker: UIViewControllerRepresentable {
return
}
//
processSelectedMedia(results: results, picker: picker, processedMedia: &processedMedia)
//
picker.dismiss(animated: true) { [weak self] in
guard let self = self else { return }
//
var processedMedia = self.parent.selectionMode == .single ? [] : self.parent.selectedMedia
self.processSelectedMedia(results: results, picker: picker, processedMedia: &processedMedia)
// onDismiss
self.parent.onDismiss?()
}
}
private func processSelectedMedia(results: [PHPickerResult],
picker: PHPickerViewController,
processedMedia: inout [MediaType]) {
picker: PHPickerViewController,
processedMedia: inout [MediaType]) {
let group = DispatchGroup()
let mediaCollector = MediaCollector()
for result in results {
for (index, result) in results.enumerated() {
let itemProvider = result.itemProvider
if itemProvider.hasItemConformingToTypeIdentifier(UTType.image.identifier) {
@ -202,6 +215,10 @@ struct MediaPicker: UIViewControllerRepresentable {
processImage(itemProvider: itemProvider) { media in
if let media = media {
mediaCollector.add(media: media)
//
DispatchQueue.main.async {
self.parent.onUploadProgress?(index, 1.0) //
}
}
group.leave()
}
@ -212,21 +229,19 @@ struct MediaPicker: UIViewControllerRepresentable {
processVideo(itemProvider: itemProvider) { media in
if let media = media {
mediaCollector.add(media: media)
//
DispatchQueue.main.async {
self.parent.onUploadProgress?(index, 1.0) //
}
}
group.leave()
}
}
}
// Create a local copy of the parent reference
let parent = self.parent
group.notify(queue: .main) {
let finalMedia = mediaCollector.mediaItems
parent.selectedMedia = finalMedia
picker.dismiss(animated: true) {
parent.onDismiss?()
}
self.parent.selectedMedia = finalMedia
}
}

View File

@ -35,10 +35,11 @@ struct MediaUploadView: View {
.padding(.trailing, 16)
}
.background(Color.themeTextWhiteSecondary)
.padding(.bottom, -24)
.padding(.horizontal)
.padding(.bottom, -20)
.zIndex(1) //
//
HStack(spacing: 20) {
HStack() {
Text("The upload process will take approximately 2 minutes. Thank you for your patience. ")
.font(Typography.font(for: .caption))
.foregroundColor(.black)
@ -95,9 +96,13 @@ struct MediaUploadView: View {
.sheet(isPresented: $showMediaPicker) {
MediaPicker(
selectedMedia: $uploadManager.selectedMedia,
imageSelectionLimit: 10,
videoSelectionLimit: 10,
onDismiss: handleMediaPickerDismiss
imageSelectionLimit: 20,
videoSelectionLimit: 5,
onDismiss: handleMediaPickerDismiss,
onUploadProgress: { index, progress in
//
print("File \(index) upload progress: \(progress * 100)%")
}
)
}
.onChange(of: uploadManager.selectedMedia) { newMedia in
@ -108,13 +113,18 @@ struct MediaUploadView: View {
// MARK: - Private Methods
private func handleMediaPickerDismiss() {
showMediaPicker = false
if !uploadManager.selectedMedia.isEmpty && selectedMedia == nil {
selectedMedia = uploadManager.selectedMedia.first
selectedIndices = [0]
// Start upload when media picker is dismissed with new media
uploadManager.startUpload()
}
self.uploadManager.startUpload()
// showMediaPicker = false
// //
// DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
// if !self.uploadManager.selectedMedia.isEmpty {
// self.selectedMedia = self.uploadManager.selectedMedia.first
// self.selectedIndices = [0]
// //
// self.uploadManager.startUpload()
// }
// }
}
private func handleMediaChange(_ newMedia: [MediaType]) {
@ -124,7 +134,7 @@ struct MediaUploadView: View {
return
}
// Only update if needed
//
if selectedIndices.isEmpty || selectedIndices.first! >= newMedia.count {
selectedMedia = newMedia.first
selectedIndices = [0]
@ -132,11 +142,21 @@ struct MediaUploadView: View {
selectedMedia = newMedia[selectedIndex]
}
// Auto-upload when new media is added
if !newMedia.isEmpty && !uploadManager.isUploading {
//
if !newMedia.isEmpty && !isUploading() {
uploadManager.startUpload()
}
}
//
private func isUploading() -> Bool {
return uploadManager.uploadStatus.values.contains { status in
if case .uploading = status {
return true
}
return false
}
}
}
// MARK: - Main Upload Area
@ -158,9 +178,10 @@ struct MainUploadArea: View {
.padding()
if let media = selectedMedia {
MediaPreview(media: media)
MediaPreview(media: media, uploadManager: uploadManager)
.frame(width: 225, height: 225)
.onTapGesture { showMediaPicker = true }
.padding(.bottom, 10)
} else {
UploadPromptView(showMediaPicker: $showMediaPicker)
}
@ -333,31 +354,77 @@ struct AddMoreButton: View {
struct MediaPreview: View {
let media: MediaType
@ObservedObject var uploadManager: MediaUploadManager
private var uploadProgress: Double {
// Get the upload progress for this media item using its index
if let index = uploadManager.selectedMedia.firstIndex(where: { $0 == media }) {
let status = uploadManager.uploadStatus["\(index)"]
if case .uploading(let progress) = status {
return progress
}
}
return 0
}
private var isUploading: Bool {
if let index = uploadManager.selectedMedia.firstIndex(where: { $0 == media }) {
let status = uploadManager.uploadStatus["\(index)"]
if case .uploading = status {
return true
}
}
return false
}
var body: some View {
Group {
switch media {
case .image(let uiImage):
Image(uiImage: uiImage)
.resizable()
.scaledToFit()
.cornerRadius(12)
.drawingGroup() // Improves performance for large images
case .video(_, let thumbnail):
if let thumbnail = thumbnail {
Image(uiImage: thumbnail)
ZStack {
// Main media content
Group {
switch media {
case .image(let uiImage):
Image(uiImage: uiImage)
.resizable()
.scaledToFit()
.overlay(
Image(systemName: "play.circle.fill")
.font(.system(size: 48))
.foregroundColor(.white)
.shadow(radius: 10)
)
.cornerRadius(12)
.drawingGroup() // Improves performance for thumbnails
.drawingGroup()
case .video(_, let thumbnail):
if let thumbnail = thumbnail {
Image(uiImage: thumbnail)
.resizable()
.scaledToFit()
.overlay(
Image(systemName: "play.circle.fill")
.font(.system(size: 48))
.foregroundColor(.white)
.shadow(radius: 10)
)
.cornerRadius(12)
.drawingGroup()
}
}
}
// Upload progress border
if isUploading {
Circle()
.stroke(
Color.themePrimary.opacity(0.3),
style: StrokeStyle(lineWidth: 4, lineCap: .round)
)
.rotationEffect(.degrees(-90))
.padding(4)
Circle()
.trim(from: 0, to: uploadProgress)
.stroke(
Color.themePrimary,
style: StrokeStyle(lineWidth: 4, lineCap: .round)
)
.rotationEffect(.degrees(-90))
.animation(.linear, value: uploadProgress)
.padding(4)
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.themeTextWhiteSecondary)
@ -366,7 +433,7 @@ struct MediaPreview: View {
RoundedRectangle(cornerRadius: 16)
.stroke(Color.themePrimary, lineWidth: 2)
)
.contentShape(Rectangle()) // Better tap target
.contentShape(Rectangle())
}
}