feat: 多选先关闭弹窗再上传
This commit is contained in:
parent
daf8476dd4
commit
2a9ef552fa
@ -57,6 +57,7 @@ struct MediaPicker: UIViewControllerRepresentable {
|
|||||||
let onDismiss: (() -> Void)?
|
let onDismiss: (() -> Void)?
|
||||||
let allowedMediaTypes: MediaTypeFilter
|
let allowedMediaTypes: MediaTypeFilter
|
||||||
let selectionMode: SelectionMode
|
let selectionMode: SelectionMode
|
||||||
|
let onUploadProgress: ((Int, Double) -> Void)?
|
||||||
|
|
||||||
/// 选择模式
|
/// 选择模式
|
||||||
enum SelectionMode {
|
enum SelectionMode {
|
||||||
@ -72,18 +73,21 @@ struct MediaPicker: UIViewControllerRepresentable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init(selectedMedia: Binding<[MediaType]>,
|
init(selectedMedia: Binding<[MediaType]>,
|
||||||
imageSelectionLimit: Int = 10,
|
imageSelectionLimit: Int = 10,
|
||||||
videoSelectionLimit: Int = 10,
|
videoSelectionLimit: Int = 10,
|
||||||
allowedMediaTypes: MediaTypeFilter = .all,
|
allowedMediaTypes: MediaTypeFilter = .all,
|
||||||
selectionMode: SelectionMode = .multiple,
|
selectionMode: SelectionMode = .multiple,
|
||||||
onDismiss: (() -> Void)? = nil) {
|
onDismiss: (() -> Void)? = nil,
|
||||||
|
onUploadProgress: ((Int, Double) -> Void)? = nil) {
|
||||||
self._selectedMedia = selectedMedia
|
self._selectedMedia = selectedMedia
|
||||||
self.imageSelectionLimit = imageSelectionLimit
|
self.imageSelectionLimit = imageSelectionLimit
|
||||||
self.videoSelectionLimit = videoSelectionLimit
|
self.videoSelectionLimit = videoSelectionLimit
|
||||||
self.allowedMediaTypes = allowedMediaTypes
|
self.allowedMediaTypes = allowedMediaTypes
|
||||||
self.selectionMode = selectionMode
|
self.selectionMode = selectionMode
|
||||||
self.onDismiss = onDismiss
|
self.onDismiss = onDismiss
|
||||||
|
self.onUploadProgress = onUploadProgress
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func makeUIViewController(context: Context) -> PHPickerViewController {
|
func makeUIViewController(context: Context) -> PHPickerViewController {
|
||||||
var configuration = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared())
|
var configuration = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared())
|
||||||
@ -182,17 +186,26 @@ struct MediaPicker: UIViewControllerRepresentable {
|
|||||||
return
|
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],
|
private func processSelectedMedia(results: [PHPickerResult],
|
||||||
picker: PHPickerViewController,
|
picker: PHPickerViewController,
|
||||||
processedMedia: inout [MediaType]) {
|
processedMedia: inout [MediaType]) {
|
||||||
let group = DispatchGroup()
|
let group = DispatchGroup()
|
||||||
let mediaCollector = MediaCollector()
|
let mediaCollector = MediaCollector()
|
||||||
|
|
||||||
for result in results {
|
for (index, result) in results.enumerated() {
|
||||||
let itemProvider = result.itemProvider
|
let itemProvider = result.itemProvider
|
||||||
|
|
||||||
if itemProvider.hasItemConformingToTypeIdentifier(UTType.image.identifier) {
|
if itemProvider.hasItemConformingToTypeIdentifier(UTType.image.identifier) {
|
||||||
@ -202,6 +215,10 @@ struct MediaPicker: UIViewControllerRepresentable {
|
|||||||
processImage(itemProvider: itemProvider) { media in
|
processImage(itemProvider: itemProvider) { media in
|
||||||
if let media = media {
|
if let media = media {
|
||||||
mediaCollector.add(media: media)
|
mediaCollector.add(media: media)
|
||||||
|
// 更新上传进度
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.parent.onUploadProgress?(index, 1.0) // 图片直接完成
|
||||||
|
}
|
||||||
}
|
}
|
||||||
group.leave()
|
group.leave()
|
||||||
}
|
}
|
||||||
@ -212,21 +229,19 @@ struct MediaPicker: UIViewControllerRepresentable {
|
|||||||
processVideo(itemProvider: itemProvider) { media in
|
processVideo(itemProvider: itemProvider) { media in
|
||||||
if let media = media {
|
if let media = media {
|
||||||
mediaCollector.add(media: media)
|
mediaCollector.add(media: media)
|
||||||
|
// 更新上传进度
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.parent.onUploadProgress?(index, 1.0) // 视频直接完成
|
||||||
|
}
|
||||||
}
|
}
|
||||||
group.leave()
|
group.leave()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a local copy of the parent reference
|
|
||||||
let parent = self.parent
|
|
||||||
|
|
||||||
group.notify(queue: .main) {
|
group.notify(queue: .main) {
|
||||||
let finalMedia = mediaCollector.mediaItems
|
let finalMedia = mediaCollector.mediaItems
|
||||||
parent.selectedMedia = finalMedia
|
self.parent.selectedMedia = finalMedia
|
||||||
picker.dismiss(animated: true) {
|
|
||||||
parent.onDismiss?()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -35,10 +35,11 @@ struct MediaUploadView: View {
|
|||||||
.padding(.trailing, 16)
|
.padding(.trailing, 16)
|
||||||
}
|
}
|
||||||
.background(Color.themeTextWhiteSecondary)
|
.background(Color.themeTextWhiteSecondary)
|
||||||
.padding(.bottom, -24)
|
.padding(.horizontal)
|
||||||
|
.padding(.bottom, -20)
|
||||||
.zIndex(1) // 确保导航栏在最上层
|
.zIndex(1) // 确保导航栏在最上层
|
||||||
// 主体内容
|
|
||||||
HStack(spacing: 20) {
|
HStack() {
|
||||||
Text("The upload process will take approximately 2 minutes. Thank you for your patience. ")
|
Text("The upload process will take approximately 2 minutes. Thank you for your patience. ")
|
||||||
.font(Typography.font(for: .caption))
|
.font(Typography.font(for: .caption))
|
||||||
.foregroundColor(.black)
|
.foregroundColor(.black)
|
||||||
@ -95,9 +96,13 @@ struct MediaUploadView: View {
|
|||||||
.sheet(isPresented: $showMediaPicker) {
|
.sheet(isPresented: $showMediaPicker) {
|
||||||
MediaPicker(
|
MediaPicker(
|
||||||
selectedMedia: $uploadManager.selectedMedia,
|
selectedMedia: $uploadManager.selectedMedia,
|
||||||
imageSelectionLimit: 10,
|
imageSelectionLimit: 20,
|
||||||
videoSelectionLimit: 10,
|
videoSelectionLimit: 5,
|
||||||
onDismiss: handleMediaPickerDismiss
|
onDismiss: handleMediaPickerDismiss,
|
||||||
|
onUploadProgress: { index, progress in
|
||||||
|
// 更新单个文件的上传进度
|
||||||
|
print("File \(index) upload progress: \(progress * 100)%")
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.onChange(of: uploadManager.selectedMedia) { newMedia in
|
.onChange(of: uploadManager.selectedMedia) { newMedia in
|
||||||
@ -108,13 +113,18 @@ struct MediaUploadView: View {
|
|||||||
// MARK: - Private Methods
|
// MARK: - Private Methods
|
||||||
|
|
||||||
private func handleMediaPickerDismiss() {
|
private func handleMediaPickerDismiss() {
|
||||||
showMediaPicker = false
|
self.uploadManager.startUpload()
|
||||||
if !uploadManager.selectedMedia.isEmpty && selectedMedia == nil {
|
// showMediaPicker = false
|
||||||
selectedMedia = uploadManager.selectedMedia.first
|
|
||||||
selectedIndices = [0]
|
// // 确保选择器完全关闭后再开始上传
|
||||||
// Start upload when media picker is dismissed with new media
|
// DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
|
||||||
uploadManager.startUpload()
|
// if !self.uploadManager.selectedMedia.isEmpty {
|
||||||
}
|
// self.selectedMedia = self.uploadManager.selectedMedia.first
|
||||||
|
// self.selectedIndices = [0]
|
||||||
|
// // 在选择器完全关闭后再开始上传
|
||||||
|
// self.uploadManager.startUpload()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handleMediaChange(_ newMedia: [MediaType]) {
|
private func handleMediaChange(_ newMedia: [MediaType]) {
|
||||||
@ -124,7 +134,7 @@ struct MediaUploadView: View {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only update if needed
|
// 只在需要时更新
|
||||||
if selectedIndices.isEmpty || selectedIndices.first! >= newMedia.count {
|
if selectedIndices.isEmpty || selectedIndices.first! >= newMedia.count {
|
||||||
selectedMedia = newMedia.first
|
selectedMedia = newMedia.first
|
||||||
selectedIndices = [0]
|
selectedIndices = [0]
|
||||||
@ -132,11 +142,21 @@ struct MediaUploadView: View {
|
|||||||
selectedMedia = newMedia[selectedIndex]
|
selectedMedia = newMedia[selectedIndex]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-upload when new media is added
|
// 当有新媒体时自动开始上传
|
||||||
if !newMedia.isEmpty && !uploadManager.isUploading {
|
if !newMedia.isEmpty && !isUploading() {
|
||||||
uploadManager.startUpload()
|
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
|
// MARK: - Main Upload Area
|
||||||
@ -158,9 +178,10 @@ struct MainUploadArea: View {
|
|||||||
.padding()
|
.padding()
|
||||||
|
|
||||||
if let media = selectedMedia {
|
if let media = selectedMedia {
|
||||||
MediaPreview(media: media)
|
MediaPreview(media: media, uploadManager: uploadManager)
|
||||||
.frame(width: 225, height: 225)
|
.frame(width: 225, height: 225)
|
||||||
.onTapGesture { showMediaPicker = true }
|
.onTapGesture { showMediaPicker = true }
|
||||||
|
.padding(.bottom, 10)
|
||||||
} else {
|
} else {
|
||||||
UploadPromptView(showMediaPicker: $showMediaPicker)
|
UploadPromptView(showMediaPicker: $showMediaPicker)
|
||||||
}
|
}
|
||||||
@ -333,31 +354,77 @@ struct AddMoreButton: View {
|
|||||||
|
|
||||||
struct MediaPreview: View {
|
struct MediaPreview: View {
|
||||||
let media: MediaType
|
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 {
|
var body: some View {
|
||||||
Group {
|
ZStack {
|
||||||
switch media {
|
// Main media content
|
||||||
case .image(let uiImage):
|
Group {
|
||||||
Image(uiImage: uiImage)
|
switch media {
|
||||||
.resizable()
|
case .image(let uiImage):
|
||||||
.scaledToFit()
|
Image(uiImage: uiImage)
|
||||||
.cornerRadius(12)
|
|
||||||
.drawingGroup() // Improves performance for large images
|
|
||||||
case .video(_, let thumbnail):
|
|
||||||
if let thumbnail = thumbnail {
|
|
||||||
Image(uiImage: thumbnail)
|
|
||||||
.resizable()
|
.resizable()
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.overlay(
|
|
||||||
Image(systemName: "play.circle.fill")
|
|
||||||
.font(.system(size: 48))
|
|
||||||
.foregroundColor(.white)
|
|
||||||
.shadow(radius: 10)
|
|
||||||
)
|
|
||||||
.cornerRadius(12)
|
.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)
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
.background(Color.themeTextWhiteSecondary)
|
.background(Color.themeTextWhiteSecondary)
|
||||||
@ -366,7 +433,7 @@ struct MediaPreview: View {
|
|||||||
RoundedRectangle(cornerRadius: 16)
|
RoundedRectangle(cornerRadius: 16)
|
||||||
.stroke(Color.themePrimary, lineWidth: 2)
|
.stroke(Color.themePrimary, lineWidth: 2)
|
||||||
)
|
)
|
||||||
.contentShape(Rectangle()) // Better tap target
|
.contentShape(Rectangle())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user