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

268 lines
12 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: - Data Models
///
public struct UploadProgress {
public let current: Int
public let total: Int
public let progress: Double
public let isOriginal: Bool
public init(current: Int, total: Int, progress: Double, isOriginal: Bool) {
self.current = current
self.total = total
self.progress = progress
self.isOriginal = isOriginal
}
}
///
public struct UploadResults {
public let original: ImageUploaderGetID.UploadResult
public let compressed: ImageUploaderGetID.UploadResult
public init(original: ImageUploaderGetID.UploadResult,
compressed: ImageUploaderGetID.UploadResult) {
self.original = original
self.compressed = compressed
}
}
// MARK: - Photo Picker
///
struct PhotoPicker: UIViewControllerRepresentable {
// MARK: - Properties
@Binding var selectedImages: [UIImage]
let selectionLimit: Int
let filter: PHPickerFilter
var onImageUploaded: ((Result<UploadResults, Error>) -> Void)?
var onUploadProgress: ((UploadProgress) -> Void)?
// MARK: - Initialization
init(selectedImages: Binding<[UIImage]>,
selectionLimit: Int = 1,
filter: PHPickerFilter = .images,
onImageUploaded: ((Result<UploadResults, Error>) -> Void)? = nil,
onUploadProgress: ((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 uploader = ImageUploaderGetID()
init(_ parent: PhotoPicker) {
self.parent = parent
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
parent.selectedImages.removeAll()
let group = DispatchGroup()
var loadedImages: [Int: UIImage] = [:]
var uploadResults: [Int: (original: ImageUploaderGetID.UploadResult?,
compressed: ImageUploaderGetID.UploadResult?)] = [:]
for (index, result) in results.enumerated() {
group.enter()
if result.itemProvider.canLoadObject(ofClass: UIImage.self) {
result.itemProvider.loadObject(ofClass: UIImage.self) { [weak self] (image, error) in
guard let self = self, let image = image as? UIImage else {
group.leave()
return
}
loadedImages[index] = image
guard let compressedImage = image.jpegData(compressionQuality: 0.5).flatMap(UIImage.init(data:)) else {
group.leave()
return
}
self.uploader.uploadImage(
image,
progress: { [weak self] progress in
let progressInfo = UploadProgress(
current: Int(progress * 100),
total: 100,
progress: progress,
isOriginal: true
)
DispatchQueue.main.async {
self?.parent.onUploadProgress?(progressInfo)
}
print("📤 原图上传进度: \(Int(progress * 100))%")
},
completion: { [weak self] originalResult in
guard let self = self else {
group.leave()
return
}
switch originalResult {
case .success(let originalUploadResult):
self.uploader.uploadImage(
compressedImage,
progress: { [weak self] progress in
let progressInfo = UploadProgress(
current: Int(progress * 100),
total: 100,
progress: progress,
isOriginal: false
)
DispatchQueue.main.async {
self?.parent.onUploadProgress?(progressInfo)
}
print("📊 压缩图上传进度: \(Int(progress * 100))%")
},
completion: { compressedResult in
defer { group.leave() }
switch compressedResult {
case .success(let compressedUploadResult):
uploadResults[index] = (originalUploadResult, compressedUploadResult)
print("✅ 原图和压缩图上传成功!")
print("📂 原图信息:")
print(" - 文件ID: \(originalUploadResult.fileId)")
print(" - 文件大小: \(originalUploadResult.fileSize) 字节")
print("📦 压缩图信息:")
print(" - 文件ID: \(compressedUploadResult.fileId)")
print(" - 文件大小: \(compressedUploadResult.fileSize) 字节")
MaterialService.shared.uploadMaterialInfo(
fileId: originalUploadResult.fileId,
previewFileId: compressedUploadResult.fileId
) { success, errorMessage in
if success {
print("✅ 文件信息上传成功 素材上传成功!!!!!")
} else if let errorMessage = errorMessage {
print("❌ 文件信息上传失败: \(errorMessage)")
}
}
case .failure(let error):
print("❌ 压缩图上传失败: \(error.localizedDescription)")
uploadResults[index] = (originalUploadResult, nil)
}
}
)
case .failure(let error):
print("❌ 原图上传失败: \(error.localizedDescription)")
group.leave()
}
}
)
}
} else {
group.leave()
}
}
group.notify(queue: .main) { [weak self] in
guard let self = self else { return }
let sortedImages = loadedImages.sorted { $0.key < $1.key }.map { $0.value }
self.parent.selectedImages.append(contentsOf: sortedImages)
if let firstResult = uploadResults.first?.value,
let original = firstResult.original,
let compressed = firstResult.compressed {
let results = UploadResults(original: original, compressed: compressed)
self.parent.onImageUploaded?(.success(results))
} else {
self.parent.onImageUploaded?(.failure(NSError(
domain: "com.wake.upload",
code: -1,
userInfo: [NSLocalizedDescriptionKey: "上传过程中出现错误"]
)))
}
picker.dismiss(animated: true)
}
}
}
}
// MARK: - Avatar Uploader
///
struct AvatarUploader: View {
@Binding var selectedImage: UIImage?
let size: CGFloat
var onUploadComplete: ((Result<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),进度:\(progress.progress * 100)%")
}
)
}
}
}