import Foundation import ComposableArchitecture import SwiftUI import PhotosUI // 修正:导入PhotosUI @Reducer struct EditFeedFeature { @ObservableState struct State: Equatable { var content: String = "" var isLoading: Bool = false var errorMessage: String? = nil var shouldDismiss: Bool = false var selectedImages: [PhotosPickerItem] = [] var processedImages: [UIImage] = [] var canAddMoreImages: Bool { processedImages.count < 9 } var canPublish: Bool { (!content.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || !processedImages.isEmpty) && !isLoading && !isUploadingImages } // 新增:图片上传相关状态 var isUploadingImages: Bool = false var imageUploadProgress: Double = 0.0 // 0.0~1.0 var uploadedResList: [ResListItem] = [] // 手动实现Equatable,selectedImages只比较数量(PhotosPickerItem不支持Equatable) static func == (lhs: State, rhs: State) -> Bool { lhs.content == rhs.content && lhs.isLoading == rhs.isLoading && lhs.errorMessage == rhs.errorMessage && lhs.shouldDismiss == rhs.shouldDismiss && lhs.processedImages == rhs.processedImages && lhs.selectedImages.count == rhs.selectedImages.count && lhs.isUploadingImages == rhs.isUploadingImages && lhs.imageUploadProgress == rhs.imageUploadProgress && lhs.uploadedResList == rhs.uploadedResList } } enum Action { case contentChanged(String) case publishButtonTapped case publishResponse(Result) case clearError case dismissView case clearDismissFlag // 新增图片相关Action case photosPickerItemsChanged([PhotosPickerItem]) case processPhotosPickerItems([PhotosPickerItem]) case updateProcessedImages([UIImage]) case removeImage(Int) // 新增:图片上传Action case uploadImages case uploadImagesResponse(Result<[ResListItem], Error>) // 新增:图片上传进度 case updateImageUploadProgress(Double) } @Dependency(\.apiService) var apiService @Dependency(\.dismiss) var dismiss @Dependency(\.isPresented) var isPresented var body: some ReducerOf { Reduce { state, action in switch action { case .contentChanged(let newContent): state.content = newContent return .none case .publishButtonTapped: guard state.canPublish else { state.errorMessage = "请输入内容" return .none } // 有图片时先上传图片 if !state.processedImages.isEmpty { state.isUploadingImages = true state.imageUploadProgress = 0.0 state.errorMessage = nil return .send(.uploadImages) } else { state.isLoading = true state.errorMessage = nil return .run { [content = state.content] send in let userId = await UserInfoManager.getCurrentUserId() ?? "" let type = content.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ? "2" : "0" let request = await PublishFeedRequest.make( content: content.trimmingCharacters(in: .whitespacesAndNewlines), uid: userId, type: type, resList: nil ) do { let response = try await apiService.request(request) await send(.publishResponse(.success(response))) } catch { await send(.publishResponse(.failure(error))) } } } case .uploadImages: let images = state.processedImages return .run { send in var resList: [ResListItem] = [] for (idx, image) in images.enumerated() { guard let data = image.jpegData(compressionQuality: 0.9) else { continue } if let url = await COSManager.shared.uploadImage(data, apiService: apiService), let cgImage = image.cgImage { let width = cgImage.width let height = cgImage.height let format = "jpeg" let item = ResListItem(resUrl: url, width: width, height: height, format: format) resList.append(item) } // 可选:进度回调 await MainActor.run { send(.updateImageUploadProgress(Double(idx + 1) / Double(images.count))) } } if resList.count == images.count { await send(.uploadImagesResponse(.success(resList))) } else { await send(.uploadImagesResponse(.failure(NSError(domain: "COSUpload", code: -1, userInfo: [NSLocalizedDescriptionKey: "部分图片上传失败"])) )) } } case .uploadImagesResponse(let result): state.isUploadingImages = false state.imageUploadProgress = 1.0 switch result { case .success(let resList): state.uploadedResList = resList state.isLoading = true return .run { [content = state.content, resList] send in let userId = await UserInfoManager.getCurrentUserId() ?? "" // type: 2 表示图片/图文 let type = resList.isEmpty ? "0" : (content.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ? "2" : "2") let request = await PublishFeedRequest.make( content: content.trimmingCharacters(in: .whitespacesAndNewlines), uid: userId, type: type, resList: resList ) do { let response = try await apiService.request(request) await send(.publishResponse(.success(response))) } catch { await send(.publishResponse(.failure(error))) } } case .failure(let error): state.errorMessage = error.localizedDescription return .none } case .publishResponse(.success(let response)): state.isLoading = false if response.code == 200 { return .send(.dismissView) } else { state.errorMessage = response.message.isEmpty ? "发布失败" : response.message return .none } case .publishResponse(.failure(let error)): state.isLoading = false state.errorMessage = error.localizedDescription return .none case .clearError: state.errorMessage = nil return .none case .dismissView: state.shouldDismiss = true return .none case .clearDismissFlag: state.shouldDismiss = false return .none case .photosPickerItemsChanged(let items): state.selectedImages = items return .run { send in await send(.processPhotosPickerItems(items)) } case .processPhotosPickerItems(let items): let currentImages = state.processedImages return .run { send in var newImages = currentImages for item in items { guard let data = try? await item.loadTransferable(type: Data.self), let image = UIImage(data: data) else { continue } if newImages.count < 9 { newImages.append(image) } } await MainActor.run { send(.updateProcessedImages(newImages)) } } case .updateProcessedImages(let images): state.processedImages = images return .none case .removeImage(let index): guard index < state.processedImages.count else { return .none } state.processedImages.remove(at: index) if index < state.selectedImages.count { state.selectedImages.remove(at: index) } return .none // 新增:图片上传进度 case .updateImageUploadProgress(let progress): state.imageUploadProgress = progress return .none } } } }