
- 新增COSManagerAdapter类,保持与现有COSManager相同的接口,内部使用新的TCCos组件。 - 实现获取、刷新Token及上传图片的功能,确保与腾讯云COS的兼容性。 - 在CreateFeedView中重构内容输入、图片选择和发布按钮逻辑,提升用户体验。 - 更新EditFeedView以优化视图结构和状态管理,确保功能正常运行。 - 在多个视图中添加键盘状态管理,改善用户交互体验。
259 lines
11 KiB
Swift
259 lines
11 KiB
Swift
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] = []
|
||
|
||
// 新增:PhotosPicker相关状态
|
||
var showPhotosPicker: Bool = false
|
||
var selectedPhotoItems: [PhotosPickerItem] = []
|
||
|
||
// 新增:删除图片确认相关状态
|
||
var showDeleteImageAlert: Bool = false
|
||
var imageToDeleteIndex: Int? = nil
|
||
|
||
// 默认初始化器
|
||
init() {
|
||
// 默认初始化
|
||
}
|
||
|
||
// 手动实现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 &&
|
||
lhs.showPhotosPicker == rhs.showPhotosPicker &&
|
||
lhs.selectedPhotoItems.count == rhs.selectedPhotoItems.count &&
|
||
lhs.showDeleteImageAlert == rhs.showDeleteImageAlert &&
|
||
lhs.imageToDeleteIndex == rhs.imageToDeleteIndex
|
||
}
|
||
}
|
||
|
||
enum Action {
|
||
case contentChanged(String)
|
||
case publishButtonTapped
|
||
case publishResponse(Result<PublishFeedResponse, Error>)
|
||
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)
|
||
// 新增:PhotosPicker相关Action
|
||
case photosPickerDismissed
|
||
case addImageButtonTapped
|
||
// 新增:删除图片确认相关Action
|
||
case showDeleteImageAlert(Int)
|
||
case deleteImageAlertDismissed
|
||
}
|
||
|
||
@Dependency(\.apiService) var apiService
|
||
@Dependency(\.dismiss) var dismiss
|
||
@Dependency(\.isPresented) var isPresented
|
||
|
||
var body: some ReducerOf<Self> {
|
||
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
|
||
state.selectedPhotoItems = 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)
|
||
}
|
||
if index < state.selectedPhotoItems.count {
|
||
state.selectedPhotoItems.remove(at: index)
|
||
}
|
||
return .none
|
||
// 新增:图片上传进度
|
||
case .updateImageUploadProgress(let progress):
|
||
state.imageUploadProgress = progress
|
||
return .none
|
||
// 新增:PhotosPicker相关Action
|
||
case .photosPickerDismissed:
|
||
state.showPhotosPicker = false
|
||
return .none
|
||
case .addImageButtonTapped:
|
||
state.showPhotosPicker = true
|
||
return .none
|
||
// 新增:删除图片确认相关Action
|
||
case .showDeleteImageAlert(let index):
|
||
state.imageToDeleteIndex = index
|
||
state.showDeleteImageAlert = true
|
||
return .none
|
||
case .deleteImageAlertDismissed:
|
||
state.showDeleteImageAlert = false
|
||
state.imageToDeleteIndex = nil
|
||
return .none
|
||
}
|
||
}
|
||
}
|
||
}
|