Files
e-party-iOS/yana/Features/CreateFeedFeature.swift
edwinQQQ ba991598be feat: 更新CreateFeed功能及相关视图组件
- 在CreateFeedFeature中新增isPresented依赖,确保在适当的上下文中执行视图关闭操作。
- 在FeedFeature中优化状态管理,简化CreateFeedView的呈现逻辑。
- 新增FeedListFeature和MainFeature,整合FeedListView和底部导航功能,提升用户体验。
- 更新HomeView和SplashView以集成MainView,确保应用结构一致性。
- 在多个视图中调整状态管理和导航逻辑,增强可维护性和用户体验。
2025-07-21 19:10:31 +08:00

186 lines
6.4 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
@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 contextdismiss
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 = "/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
}