feat: 更新依赖和项目配置,优化代码结构

- 在Package.swift中注释掉旧的swift-composable-architecture依赖,并添加swift-case-paths依赖。
- 在Podfile中将iOS平台版本更新至16.0,并移除QCloudCOSXML/Transfer依赖,改为使用QCloudCOSXML。
- 更新Podfile.lock以反映依赖变更,确保项目依赖的准确性。
- 新增架构分析需求文档,明确项目架构评估和改进建议。
- 在多个文件中实现async/await语法,提升异步操作的可读性和性能。
- 更新日志输出方法,确保在调试模式下提供一致的调试信息。
- 优化多个视图组件,提升用户体验和代码可维护性。
This commit is contained in:
edwinQQQ
2025-07-17 18:47:09 +08:00
parent 4bbb4f8434
commit 128bf36c88
46 changed files with 1250 additions and 1203 deletions

View File

@@ -1,11 +1,7 @@
import Foundation
import ComposableArchitecture
import SwiftUI
// PhotosUI (iOS 16.0+)
#if canImport(PhotosUI)
import PhotosUI
#endif
@Reducer
struct CreateFeedFeature {
@@ -13,52 +9,33 @@ struct CreateFeedFeature {
struct State: Equatable {
var content: String = ""
var processedImages: [UIImage] = []
var isLoading: Bool = false
var errorMessage: String? = nil
var characterCount: Int = 0
// iOS 16+ PhotosPicker
#if canImport(PhotosUI) && swift(>=5.7)
var selectedImages: [PhotosPickerItem] = []
#endif
// iOS 15 UIImagePickerController
var showingImagePicker: Bool = false
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
// iOS 16+ PhotosPicker Actions
#if canImport(PhotosUI) && swift(>=5.7)
case photosPickerItemsChanged([PhotosPickerItem])
case processPhotosPickerItems([PhotosPickerItem])
#endif
// iOS 15 UIImagePickerController Actions
case showImagePicker
case hideImagePicker
case imageSelected(UIImage)
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 {
@@ -66,72 +43,47 @@ struct CreateFeedFeature {
state.content = newContent
state.characterCount = newContent.count
return .none
#if canImport(PhotosUI) && swift(>=5.7)
case .photosPickerItemsChanged(let items):
state.selectedImages = items
return .run { send in
await send(.processPhotosPickerItems(items))
}
case .processPhotosPickerItems(let items):
return .run { [currentImages = state.processedImages] send in
let currentImages = state.processedImages
return .run { send in
var newImages = currentImages
for item in items {
if let data = try? await item.loadTransferable(type: Data.self),
let image = UIImage(data: data) {
if newImages.count < 9 {
newImages.append(image)
}
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 {
state.processedImages = newImages
send(.updateProcessedImages(newImages))
}
}
#endif
case .showImagePicker:
state.showingImagePicker = true
case .updateProcessedImages(let images):
state.processedImages = images
return .none
case .hideImagePicker:
state.showingImagePicker = false
return .none
case .imageSelected(let image):
if state.processedImages.count < 9 {
state.processedImages.append(image)
}
state.showingImagePicker = false
return .none
case .removeImage(let index):
guard index < state.processedImages.count else { return .none }
state.processedImages.remove(at: index)
#if canImport(PhotosUI) && swift(>=5.7)
if index < state.selectedImages.count {
state.selectedImages.remove(at: index)
}
#endif
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)
@@ -140,27 +92,21 @@ struct CreateFeedFeature {
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()
@@ -170,47 +116,57 @@ struct CreateFeedFeature {
}
}
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 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
]
// base64
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