
- 更新Yana项目文档,调整适用版本至iOS 17,确保与最新开发环境兼容。 - 在多个视图中重构代码,优化状态管理和视图逻辑,提升用户体验。 - 添加默认初始化器以简化状态管理,确保各个Feature的状态一致性。 - 更新视图组件,移除不必要的硬编码,增强代码可读性和维护性。 - 修复多个视图中的逻辑错误,确保功能正常运行。
190 lines
6.5 KiB
Swift
190 lines
6.5 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
|
||
|
||
init() {
|
||
// 默认初始化
|
||
}
|
||
}
|
||
|
||
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
|
||
@Dependency(\.isPresented) var isPresented
|
||
|
||
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:
|
||
// 检查是否在presentation context中
|
||
guard isPresented else {
|
||
// 如果不在presentation context中,不执行dismiss
|
||
return .none
|
||
}
|
||
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 = APIEndpoint.publishFeed.path
|
||
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
|
||
}
|