298 lines
10 KiB
Swift
298 lines
10 KiB
Swift
import SwiftUI
|
||
import os.log
|
||
|
||
/// 媒体上传状态
|
||
public enum MediaUploadStatus: Equatable {
|
||
case pending
|
||
case uploading(progress: Double)
|
||
case completed(fileId: String)
|
||
case failed(Error)
|
||
|
||
public static func == (lhs: MediaUploadStatus, rhs: MediaUploadStatus) -> Bool {
|
||
switch (lhs, rhs) {
|
||
case (.pending, .pending):
|
||
return true
|
||
case (.uploading(let lhsProgress), .uploading(let rhsProgress)):
|
||
return lhsProgress == rhsProgress
|
||
case (.completed(let lhsId), .completed(let rhsId)):
|
||
return lhsId == rhsId
|
||
case (.failed, .failed):
|
||
return false // Errors don't need to be equatable
|
||
default:
|
||
return false
|
||
}
|
||
}
|
||
|
||
public var description: String {
|
||
switch self {
|
||
case .pending: return "等待上传"
|
||
case .uploading(let progress): return "上传中 \(Int(progress * 100))%"
|
||
case .completed(let fileId): return "上传完成 (ID: \(fileId.prefix(8))...)"
|
||
case .failed(let error): return "上传失败: \(error.localizedDescription)"
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 媒体上传管理器
|
||
public class MediaUploadManager: ObservableObject {
|
||
/// 已选媒体文件
|
||
@Published public var selectedMedia: [MediaType] = []
|
||
/// 上传状态
|
||
@Published public var uploadStatus: [String: MediaUploadStatus] = [:]
|
||
|
||
private let uploader = ImageUploadService()
|
||
|
||
public init() {}
|
||
|
||
/// 添加上传媒体
|
||
public func addMedia(_ media: [MediaType]) {
|
||
selectedMedia.append(contentsOf: media)
|
||
}
|
||
|
||
/// 移除指定索引的媒体
|
||
public func removeMedia(at index: Int) {
|
||
guard index < selectedMedia.count else { return }
|
||
selectedMedia.remove(at: index)
|
||
// 更新状态字典
|
||
var newStatus: [String: MediaUploadStatus] = [:]
|
||
uploadStatus.forEach { key, value in
|
||
if let keyInt = Int(key), keyInt < index {
|
||
newStatus[key] = value
|
||
} else if let keyInt = Int(key), keyInt > index {
|
||
newStatus["\(keyInt - 1)"] = value
|
||
}
|
||
}
|
||
uploadStatus = newStatus
|
||
}
|
||
|
||
/// 清空所有媒体
|
||
public func clearAllMedia() {
|
||
selectedMedia.removeAll()
|
||
uploadStatus.removeAll()
|
||
}
|
||
|
||
/// 开始上传所有选中的媒体
|
||
public func startUpload() {
|
||
print("🔄 开始批量上传 \(selectedMedia.count) 个文件")
|
||
// 重置上传状态
|
||
uploadStatus.removeAll()
|
||
|
||
for (index, media) in selectedMedia.enumerated() {
|
||
let id = "\(index)"
|
||
uploadStatus[id] = .pending
|
||
|
||
// Convert MediaType to ImageUploadService.MediaType
|
||
let uploadMediaType: ImageUploadService.MediaType
|
||
switch media {
|
||
case .image(let image):
|
||
uploadMediaType = .image(image)
|
||
case .video(let url, let thumbnail):
|
||
uploadMediaType = .video(url, thumbnail)
|
||
}
|
||
uploadMedia(uploadMediaType, id: id)
|
||
}
|
||
}
|
||
|
||
/// 获取上传结果
|
||
public func getUploadResults() -> [String: String] {
|
||
var results: [String: String] = [:]
|
||
for (id, status) in uploadStatus {
|
||
if case .completed(let fileId) = status {
|
||
results[id] = fileId
|
||
}
|
||
}
|
||
return results
|
||
}
|
||
|
||
/// 检查是否所有上传都已完成
|
||
public var isAllUploaded: Bool {
|
||
guard !selectedMedia.isEmpty else { return false }
|
||
return uploadStatus.allSatisfy { _, status in
|
||
if case .completed = status { return true }
|
||
return false
|
||
}
|
||
}
|
||
|
||
// MARK: - Private Methods
|
||
|
||
private func uploadMedia(_ media: ImageUploadService.MediaType, id: String) {
|
||
print("🔄 开始处理媒体: \(id)")
|
||
uploadStatus[id] = .uploading(progress: 0)
|
||
|
||
uploader.uploadMedia(
|
||
media,
|
||
progress: { progress in
|
||
print("📊 上传进度 (\(id)): \(progress.current)%")
|
||
DispatchQueue.main.async {
|
||
self.uploadStatus[id] = .uploading(progress: progress.progress)
|
||
}
|
||
},
|
||
completion: { [weak self] result in
|
||
guard let self = self else { return }
|
||
DispatchQueue.main.async {
|
||
switch result {
|
||
case .success(let uploadResult):
|
||
print("✅ 上传成功 (\(id)): \(uploadResult.fileId)")
|
||
self.uploadStatus[id] = .completed(fileId: uploadResult.fileId)
|
||
case .failure(let error):
|
||
print("❌ 上传失败 (\(id)): \(error.localizedDescription)")
|
||
self.uploadStatus[id] = .failed(error)
|
||
}
|
||
}
|
||
}
|
||
)
|
||
}
|
||
}
|
||
|
||
// MARK: - Preview Helper
|
||
|
||
/// 示例视图,展示如何使用 MediaUploadManager
|
||
struct MediaUploadExample: View {
|
||
@StateObject private var uploadManager = MediaUploadManager()
|
||
@State private var showMediaPicker = false
|
||
|
||
var body: some View {
|
||
NavigationView {
|
||
VStack(spacing: 20) {
|
||
// 选择媒体按钮
|
||
Button(action: { showMediaPicker = true }) {
|
||
Label("选择媒体", systemImage: "photo.on.rectangle")
|
||
.font(.headline)
|
||
.frame(maxWidth: .infinity)
|
||
.padding()
|
||
.background(Color.blue)
|
||
.foregroundColor(.white)
|
||
.cornerRadius(10)
|
||
}
|
||
.padding(.horizontal)
|
||
|
||
// 显示已选媒体
|
||
MediaSelectionView(uploadManager: uploadManager)
|
||
|
||
// 上传按钮
|
||
Button(action: { uploadManager.startUpload() }) {
|
||
Text("开始上传")
|
||
.font(.headline)
|
||
.frame(maxWidth: .infinity)
|
||
.padding()
|
||
.background(uploadManager.selectedMedia.isEmpty ? Color.gray : Color.green)
|
||
.foregroundColor(.white)
|
||
.cornerRadius(10)
|
||
}
|
||
.padding(.horizontal)
|
||
.disabled(uploadManager.selectedMedia.isEmpty)
|
||
|
||
Spacer()
|
||
}
|
||
.navigationTitle("媒体上传")
|
||
.sheet(isPresented: $showMediaPicker) {
|
||
MediaPickerWithLogging(
|
||
selectedMedia: $uploadManager.selectedMedia,
|
||
selectionLimit: 5,
|
||
onDismiss: { showMediaPicker = false }
|
||
)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 媒体选择视图组件
|
||
struct MediaSelectionView: View {
|
||
@ObservedObject var uploadManager: MediaUploadManager
|
||
|
||
var body: some View {
|
||
if !uploadManager.selectedMedia.isEmpty {
|
||
VStack(spacing: 10) {
|
||
Text("已选择 \(uploadManager.selectedMedia.count) 个媒体文件")
|
||
.font(.headline)
|
||
|
||
// 显示媒体缩略图和上传状态
|
||
List {
|
||
ForEach(0..<uploadManager.selectedMedia.count, id: \.self) { index in
|
||
let media = uploadManager.selectedMedia[index]
|
||
let mediaId = "\(index)"
|
||
let status = uploadManager.uploadStatus[mediaId] ?? .pending
|
||
|
||
HStack {
|
||
// 缩略图
|
||
MediaThumbnailView(media: media, onDelete: nil)
|
||
.frame(width: 60, height: 60)
|
||
|
||
VStack(alignment: .leading, spacing: 4) {
|
||
Text(media.isVideo ? "视频" : "图片")
|
||
.font(.subheadline)
|
||
|
||
// 上传状态
|
||
Text(status.description)
|
||
.font(.caption)
|
||
.foregroundColor(statusColor(status))
|
||
|
||
// 上传进度条
|
||
if case .uploading(let progress) = status {
|
||
ProgressView(value: progress, total: 1.0)
|
||
.progressViewStyle(LinearProgressViewStyle())
|
||
.frame(height: 4)
|
||
}
|
||
}
|
||
.frame(maxWidth: .infinity, alignment: .leading)
|
||
}
|
||
.padding(.vertical, 8)
|
||
}
|
||
.onDelete { indexSet in
|
||
indexSet.forEach { index in
|
||
uploadManager.removeMedia(at: index)
|
||
}
|
||
}
|
||
}
|
||
.frame(height: 300)
|
||
}
|
||
.padding(.top)
|
||
} else {
|
||
Text("未选择任何媒体")
|
||
.foregroundColor(.secondary)
|
||
.padding(.top, 50)
|
||
}
|
||
}
|
||
|
||
private func statusColor(_ status: MediaUploadStatus) -> Color {
|
||
switch status {
|
||
case .pending: return .secondary
|
||
case .uploading: return .blue
|
||
case .completed: return .green
|
||
case .failed: return .red
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 媒体缩略图视图
|
||
private struct MediaThumbnailView: View {
|
||
let media: MediaType
|
||
let onDelete: (() -> Void)?
|
||
|
||
var body: some View {
|
||
ZStack(alignment: .topTrailing) {
|
||
if let thumbnail = media.thumbnail {
|
||
Image(uiImage: thumbnail)
|
||
.resizable()
|
||
.scaledToFill()
|
||
.frame(width: 80, height: 80)
|
||
.cornerRadius(8)
|
||
.clipped()
|
||
|
||
if media.isVideo {
|
||
Image(systemName: "video.fill")
|
||
.foregroundColor(.white)
|
||
.padding(4)
|
||
.background(Color.black.opacity(0.6))
|
||
.clipShape(Circle())
|
||
.padding(4)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
#Preview {
|
||
MediaUploadExample()
|
||
.environmentObject(AuthState.shared)
|
||
} |