
- 在Package.swift中注释掉旧的swift-composable-architecture依赖,并添加swift-case-paths依赖。 - 在Podfile中将iOS平台版本更新至16.0,并移除QCloudCOSXML/Transfer依赖,改为使用QCloudCOSXML。 - 更新Podfile.lock以反映依赖变更,确保项目依赖的准确性。 - 新增架构分析需求文档,明确项目架构评估和改进建议。 - 在多个文件中实现async/await语法,提升异步操作的可读性和性能。 - 更新日志输出方法,确保在调试模式下提供一致的调试信息。 - 优化多个视图组件,提升用户体验和代码可维护性。
179 lines
6.1 KiB
Swift
179 lines
6.1 KiB
Swift
import Foundation
|
|
import ComposableArchitecture
|
|
import SwiftUI
|
|
import PhotosUI
|
|
|
|
@Reducer
|
|
struct CreateFeedFeature {
|
|
@ObservableState
|
|
struct State: Equatable {
|
|
var content: String = ""
|
|
var processedImages: [UIImage] = []
|
|
var errorMessage: String? = nil
|
|
var characterCount: Int = 0
|
|
var selectedImages: [PhotosPickerItem] = []
|
|
var canAddMoreImages: Bool {
|
|
processedImages.count < 9
|
|
}
|
|
var canPublish: Bool {
|
|
!content.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && !isLoading
|
|
}
|
|
var isLoading: Bool = false
|
|
}
|
|
|
|
enum Action {
|
|
case contentChanged(String)
|
|
case publishButtonTapped
|
|
case publishResponse(Result<PublishDynamicResponse, Error>)
|
|
case clearError
|
|
case dismissView
|
|
case photosPickerItemsChanged([PhotosPickerItem])
|
|
case processPhotosPickerItems([PhotosPickerItem])
|
|
case removeImage(Int)
|
|
case updateProcessedImages([UIImage])
|
|
}
|
|
|
|
@Dependency(\.apiService) var apiService
|
|
@Dependency(\.dismiss) var dismiss
|
|
|
|
var body: some ReducerOf<Self> {
|
|
Reduce { state, action in
|
|
switch action {
|
|
case .contentChanged(let newContent):
|
|
state.content = newContent
|
|
state.characterCount = newContent.count
|
|
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 .publishButtonTapped:
|
|
guard state.canPublish else {
|
|
state.errorMessage = "请输入内容"
|
|
return .none
|
|
}
|
|
state.isLoading = true
|
|
state.errorMessage = nil
|
|
let request = PublishDynamicRequest(
|
|
content: state.content.trimmingCharacters(in: .whitespacesAndNewlines),
|
|
images: state.processedImages
|
|
)
|
|
return .run { send in
|
|
do {
|
|
let response = try await apiService.request(request)
|
|
await send(.publishResponse(.success(response)))
|
|
} catch {
|
|
await send(.publishResponse(.failure(error)))
|
|
}
|
|
}
|
|
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:
|
|
return .run { _ in
|
|
await dismiss()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
extension CreateFeedFeature.Action: Equatable {
|
|
static func == (lhs: CreateFeedFeature.Action, rhs: CreateFeedFeature.Action) -> Bool {
|
|
switch (lhs, rhs) {
|
|
case let (.contentChanged(a), .contentChanged(b)):
|
|
return a == b
|
|
case (.publishButtonTapped, .publishButtonTapped):
|
|
return true
|
|
case (.clearError, .clearError):
|
|
return true
|
|
case (.dismissView, .dismissView):
|
|
return true
|
|
case let (.removeImage(a), .removeImage(b)):
|
|
return a == b
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - 发布动态相关模型
|
|
|
|
struct PublishDynamicRequest: APIRequestProtocol {
|
|
typealias Response = PublishDynamicResponse
|
|
let endpoint: String = "/dynamic/square/publish"
|
|
let method: HTTPMethod = .POST
|
|
let includeBaseParameters: Bool = true
|
|
let queryParameters: [String: String]? = nil
|
|
let timeout: TimeInterval = 30.0
|
|
let content: String
|
|
let images: [UIImage]
|
|
let type: Int // 0: 纯文字, 2: 图片
|
|
init(content: String, images: [UIImage] = []) {
|
|
self.content = content
|
|
self.images = images
|
|
self.type = images.isEmpty ? 0 : 2
|
|
}
|
|
var bodyParameters: [String: Any]? {
|
|
var params: [String: Any] = [
|
|
"content": content,
|
|
"type": type
|
|
]
|
|
if !images.isEmpty {
|
|
let imageData = images.compactMap { image in
|
|
image.jpegData(compressionQuality: 0.8)?.base64EncodedString()
|
|
}
|
|
params["images"] = imageData
|
|
}
|
|
return params
|
|
}
|
|
}
|
|
|
|
struct PublishDynamicResponse: Codable {
|
|
let code: Int
|
|
let message: String
|
|
let data: PublishDynamicData?
|
|
}
|
|
|
|
struct PublishDynamicData: Codable {
|
|
let dynamicId: Int
|
|
let publishTime: Int
|
|
} |