2025-08-19 20:36:00 +08:00

212 lines
8.7 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import SwiftUI
import PhotosUI
// MARK: - Photo Picker
///
struct PhotoPicker: UIViewControllerRepresentable {
// MARK: - Properties
@Binding var selectedImages: [UIImage]
let selectionLimit: Int
let filter: PHPickerFilter
var onImageUploaded: ((Result<ImageUploadService.UploadResults, Error>) -> Void)?
var onUploadProgress: ((ImageUploadService.UploadProgress) -> Void)?
@Environment(\.presentationMode) private var presentationMode
// MARK: - Initialization
init(selectedImages: Binding<[UIImage]>,
selectionLimit: Int = 1,
filter: PHPickerFilter = .images,
onImageUploaded: ((Result<ImageUploadService.UploadResults, Error>) -> Void)? = nil,
onUploadProgress: ((ImageUploadService.UploadProgress) -> Void)? = nil) {
self._selectedImages = selectedImages
self.selectionLimit = selectionLimit
self.filter = filter
self.onImageUploaded = onImageUploaded
self.onUploadProgress = onUploadProgress
}
// MARK: - UIViewControllerRepresentable
func makeUIViewController(context: Context) -> PHPickerViewController {
var configuration = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared())
configuration.filter = filter
configuration.selectionLimit = selectionLimit
configuration.preferredAssetRepresentationMode = .current
let picker = PHPickerViewController(configuration: configuration)
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
// MARK: - Coordinator
class Coordinator: NSObject, PHPickerViewControllerDelegate {
let parent: PhotoPicker
private let uploadService = ImageUploadService.shared
init(_ parent: PhotoPicker) {
self.parent = parent
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
guard !results.isEmpty else {
parent.presentationMode.wrappedValue.dismiss()
return
}
parent.selectedImages.removeAll()
let group = DispatchGroup()
var loadedImages: [Int: UIImage] = [:]
var uploadResults: [Int: ImageUploadService.UploadResults] = [:]
var lastError: Error?
for (index, result) in results.enumerated() {
group.enter()
if result.itemProvider.canLoadObject(ofClass: UIImage.self) {
result.itemProvider.loadObject(ofClass: UIImage.self) { (image, error) in
if let error = error {
lastError = error
group.leave()
return
}
guard let image = image as? UIImage else {
lastError = NSError(domain: "com.wake.upload", code: -2, userInfo: [NSLocalizedDescriptionKey: "Failed to load image"])
group.leave()
return
}
loadedImages[index] = image
// Upload the image
self.uploadService.uploadOriginalAndCompressedImage(
image,
compressionQuality: 0.5,
progress: { progress in
DispatchQueue.main.async {
self.parent.onUploadProgress?(progress)
}
},
completion: { result in
defer { group.leave() }
switch result {
case .success(let results):
uploadResults[index] = results
// Upload file info to backend
MaterialService.shared.uploadMaterialInfo(
fileId: results.original.fileId,
previewFileId: results.compressed.fileId
) { success, errorMessage in
if success {
print("✅ 文件信息上传成功")
} else if let errorMessage = errorMessage {
print("❌ 文件信息上传失败: \(errorMessage)")
}
}
case .failure(let error):
lastError = error
print("❌ 图片上传失败: \(error.localizedDescription)")
}
}
)
}
} else {
group.leave()
}
}
group.notify(queue: .main) { [weak self] in
guard let self = self else { return }
if let error = lastError {
self.parent.onImageUploaded?(.failure(error))
} else {
let sortedImages = loadedImages.sorted { $0.key < $1.key }.map { $0.value }
self.parent.selectedImages.append(contentsOf: sortedImages)
if let firstResult = uploadResults.first?.value {
self.parent.onImageUploaded?(.success(firstResult))
} else {
self.parent.onImageUploaded?(.failure(NSError(
domain: "com.wake.upload",
code: -1,
userInfo: [NSLocalizedDescriptionKey: "上传过程中出现错误"]
)))
}
}
self.parent.presentationMode.wrappedValue.dismiss()
}
}
}
}
// MARK: - Avatar Uploader
///
struct AvatarUploader: View {
@Binding var selectedImage: UIImage?
let size: CGFloat
var onUploadComplete: ((Result<ImageUploadService.UploadResults, Error>) -> Void)?
@State private var isImagePickerPresented = false
var body: some View {
Button(action: { isImagePickerPresented = true }) {
ZStack {
if let selectedImage = selectedImage {
Image(uiImage: selectedImage)
.resizable()
.scaledToFill()
.frame(width: size, height: size)
.clipShape(RoundedRectangle(cornerRadius: size * 0.1))
} else {
Color.gray.opacity(0.1)
.frame(width: size, height: size)
.overlay(
SVGImage(svgName: "Avatar")
.frame(width: size * 0.8, height: size * 0.8)
)
.clipShape(RoundedRectangle(cornerRadius: size * 0.1))
.overlay(
RoundedRectangle(cornerRadius: size * 0.1)
.stroke(Color.gray.opacity(0.3), lineWidth: 1)
)
}
}
.frame(width: size, height: size)
.contentShape(Rectangle())
}
.buttonStyle(PlainButtonStyle())
.sheet(isPresented: $isImagePickerPresented) {
PhotoPicker(
selectedImages: Binding(
get: { [selectedImage].compactMap { $0 } },
set: { images in
selectedImage = images.first
}
),
selectionLimit: 1,
onImageUploaded: { result in
onUploadComplete?(result)
},
onUploadProgress: { progress in
print("上传进度:\(progress.current)/\(progress.total),进度:\(Int(progress.progress * 100))%")
}
)
}
}
}